Commit 1207dee0 authored by jyx's avatar jyx

代码提交

parents
Pipeline #284 canceled with stages
# 快应用示例模版
## 文件结构
```
├── sign # 存储 rpk 包签名模块;
│ ├── certificate.pem # 证书文件
│ └── private.pem # 私钥文件
└── src
│ ├── assets # 公用的资源(images/styles/字体...)
│ │ ├──images # 存储 png/jpg/svg 等公共图片资源
│ │ └──styles # 存放 less/css/sass 等公共样式资源
│ │ └──js # 存储公共 javaScript 代码资源
│ │ └──iconfont # 存放图标字体
│ ├── CardDemo # 快应用卡片示例页面
│ ├── helper # 项目自定义辅助各类工具
│ │ ├──ajax.js # 对系统提供的 fetch api 进行链式封装
│ │ └──utils # 存放项目所封装的工具类方法
│ ├── pages # 统一存放项目页面级代码
│ ├── app.ux # 应用程序代码的人口文件
│ ├── manifest.json # 配置快应用基本信息
│ └── components # 存放快应用组件
└── package.json # 定义项目需要的各种模块及配置信息
```
### 如果需要轻粒子统计功能服务
首先需要前往轻粒子官网注册, 在创建应用之后可以获得 app_key,然后需要在 `/src/assets/js/statistics.config.js` 文件中配置好自己的 app_key。
### 模版说明
本项目为快应用模版(含卡片,`CardDemo` 目录)。
- `Demo` 页面:示例页面;
- `DemoDetail`页面:详情页面;
`CardDemo` 页面:快应用卡片示例页面,快应用卡片开发各厂商的实现方式不同,需到各个厂商的开发者平台查看具体文档。
## 如何使用
推荐下载[快应用开发工具](https://www.quickapp.cn/docCenter/IDEPublicity),可以进行扫码调试 / USB 调试,还有模拟器预览、语法提示等功能。使用方法,请参见[快应用开发工具文档](https://doc.quickapp.cn/tutorial/ide/overview.html)
- **更优雅的处理数据请求**;采用 `Promise` 对系统内置请求 `@system.fetch` 进行封装,并抛出至全局,使得可以极简的进行链式调用,并能够使用 `finally`
- **内置样式处理方案**;「快应用」支持 `less`, `sass` 的预编译;这里采取 [dart sass](https://sass-lang.com/documentation) 方案,并内置了部分变量,以及常用混合方法,使得可以轻松开启样式编写、复用、修改等;
- **封装常用方法**;在 `helper/utils` 路径下,有对日期、字符串、系统等常用方法,分别进行封装,统一暴露给 `global.$utils`,使得维护方式更加合理且健壮,同时又可以便捷的使用,高效开发;当然,你也可以根据需要自行增删、抑或扩展;
- **添加新增页面命令脚本**;如果需要新建页面,只需运行:`yarn gen YourPageName` ,当然,也可以根据需要,自行定定制模板:*/command/gen/template.ux*
- **集成 [Prettier](https://prettier.io/) & [Eslint](https://eslint.org/)**;在检测代码中潜在问题的同时,统一团队代码规范、风格(`js``less``scss`等),从而促使写出高质量代码,以提升工作效率(尤其针对团队开发);
- **新增文件监听命令**:引入 [onchange](https://github.com/Qard/onchange) 依赖来监听文件变化;开发时,运行 `yarn prettier-watch` 命令,即可对所修改的 `*.ux` `*.js` 等文件,进行 **Prettier** 格式化,从而大幅度提升编写效率;
## 内置命令
强烈推荐使用[快应用开发工具](https://www.quickapp.cn/docCenter/IDEPublicity),进行快应用开发;当然,如果您喜欢命令行,本模版也内置了些常用命令,供您参考:
| 命令 | 描述 | 备注 |
|---|---|---|
| `yarn start` | 开启服务(server)和监听(watch) | 如不嫌麻烦,可使用,不推荐 |
| `yarn server` | 开启服务(server) | 如不嫌麻烦,可使用,不推荐 |
| `yarn watch` | 开启监听(watch) | 如不嫌麻烦,可使用,不推荐 |
| `yarn build ` | 编译打包,生成 `rpk`包 | 对内置 `hap build` 命令的转接 |
| `yarn release ` | 生成 `rpk`包并增加签名 | 对内置 `hap release` 命令的转接 |
| `yarn gen ` | 新增「快应用」页面 | 助你高效生成页面,模版可自定义,推荐 ✓|
| `yarn prettier` | 一键美化代码(js/css/less/ux) | 实在是团队开发好帮手,推荐 ✓ |
| `yarn prettier-watch` | 对变化代码文件格式、实时美化 | 极大提升代码编写效率,强烈推荐 ✔️|
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "quickapp-test",
"version": "2.0.0",
"description": "Quickapp Sample Template",
"scripts": {
"start": "hap server --watch",
"server": "hap server",
"build": "hap build",
"release": "hap release",
"watch": "hap watch",
"debug": "hap debug",
"gen": "node ./scripts/gen/index.js",
"precommit-msg": "echo '🚧 start pre-commit checks ...' && exit 0",
"prettier": "node ./scripts/selfCloseInputTag.js && prettier --write \"src/**/*.{ux,js,json,less,scss,css,pcss,md,vue}\"",
"prettier-watcher": "onchange '**/*.md' \"src/**/**/*.{ux,js,json,less,scss,css,pcss,md,vue}\" -- prettier --write {{changed}}"
},
"dependencies": {
"aes-js": "^3.1.2",
"apex-ui": "^1.9.2",
"crypto-js": "^4.1.1",
"crypto-md5": "^1.0.0",
"js-base64": "^3.7.2",
"js-md5": "^0.7.3",
"md5": "^2.3.0"
},
"devDependencies": {
"@babel/runtime": "^7.12.5",
"@types/quickapp": "npm:quickapp-interface@^1.0.0",
"colors": "^1.4.0",
"husky": "^7.0.1",
"less": "^4.1.1",
"less-loader": "^10.0.1",
"lint-staged": "^11.0.1",
"onchange": "^5.2.0",
"prettier": "^2.3.2",
"prettier-plugin-ux": "^0.3.0",
"webpack": "^5.70.0"
},
"prettier": {
"singleQuote": true,
"semi": false,
"printWidth": 80,
"proseWrap": "never",
"tabWidth": 2
},
"husky": {
"hooks": {
"pre-commit": "yarn run precommit-msg && lint-staged"
}
},
"lint-staged": {
"**/**.{ux,js,json,less,scss,css,pcss,md,vue}": [
"prettier --write",
"git add"
]
},
"keywords": [
"快应用",
"快应用示例",
"快应用模版"
],
"browserslist": [
"chrome 65"
]
}
/**
* @desc: gen script command,make a new page generated by one click.
* @author: nicejade
*/
const fs = require('fs')
const path = require('path')
const colors = require('colors')
const newFolderName = process.argv[2]
String.prototype.firstUpperCase = function() {
return this.replace(/\b(\w)/g, $1 => {
return $1.toLowerCase()
})
}
const resolve = dir => {
return path.join(__dirname, '../..', dir)
}
const successExecPrint = msg => {
console.log(
colors.green(`✓ `) +
colors.cyan(`${msg} `) +
colors.green('task has been successfully executed.')
)
}
function createNewPage(newFolderPath) {
const mReg = new RegExp('@PAGE_CLASS_NAME', 'g')
const pageContent = fs.readFileSync(`${__dirname}/template.ux`, 'UTF-8')
const rootClassName = newFolderName
.firstUpperCase()
.replace(/([A-Z])/g, '-$1')
.toLowerCase()
const newContent = pageContent.replace(mReg, rootClassName)
fs.mkdirSync(newFolderPath, 0777)
fs.writeFile(`${newFolderPath}/index.ux`, newContent, error => {
if (error) throw `Something went wrong: ${error}`
})
successExecPrint('Create New Page')
}
function saveRouter2Manifest() {
const manifestPath = resolve('/src/manifest.json')
let manifestConf = fs.readFileSync(manifestPath, 'UTF-8')
manifestConf = JSON.parse(manifestConf)
const routerPages = manifestConf.router.pages
routerPages[`pages/${newFolderName}`] = {
component: 'index'
}
manifestConf = JSON.stringify(manifestConf, null, 2)
fs.writeFile(manifestPath, manifestConf, error => {
if (error) throw `Something went wrong[@saveRouter2Manifest]: ${error}`
})
successExecPrint('Save Router Into Manifest')
}
function main() {
if (!newFolderName) {
return console.warn(
`⚠️ Please enter the name of the page you want to create.`.underline.red
)
}
const folderNameReg = /^[A-Z][[A-Za-z0-9]+$/
if (!folderNameReg.test(newFolderName)) {
return console.warn(
`⚠️ Please enter the standard Folder name. Eg: XyzAbcde.`.underline.red
)
}
const newFolderPath = path.join(__dirname, `../../src/pages/${newFolderName}`)
const isExist = fs.existsSync(newFolderPath)
if (isExist) {
return console.warn(
`⚠️ ${newFolderName} already exists in the /src/pages/ directory.`
.underline.red
)
}
createNewPage(newFolderPath)
saveRouter2Manifest()
}
main()
<template>
<div class="wrapper">
<text class="title">{{ title }}</text>
</div>
</template>
<script>
export default {
private: {
title: '欢迎体验快应用开发'
},
onInit() {}
}
</script>
<style lang="less">
@import './../../assets/styles/style.less';
.wrapper {
.flex-box-mixins(column, center, center);
.title {
font-size: 8 * @size-factor;
text-align: center;
color: @black;
}
}
</style>
\ No newline at end of file
/**
* @file: selfCloseInputTag.js
* @desc: 遍历指定目录下 .ux 文件,将其中 input 标签由 <input **></input> 转换为 <input ** />
* @date: 2019-01-23
*/
const fs = require('fs')
const path = require('path')
const quickappCodePath = './src/'
const main = codePath => {
const traversing = cpath => {
const files = fs.readdirSync(cpath)
files.forEach(fileName => {
const fPath = path.join(cpath, fileName)
const stats = fs.statSync(fPath)
stats.isDirectory() && traversing(fPath)
stats.isFile() && fPath.endsWith('.ux') && matchAndReplace(fPath)
})
}
traversing(codePath)
}
const matchAndReplace = path => {
const pageContent = fs.readFileSync(path, 'UTF-8')
const newContent = pageContent.replace(
/(<)([\s]*?)(input\b[^\/]*?)>[\s\S]*?<\/input>/gm,
'$1$3 />'
)
fs.writeFile(path, newContent, error => {
if (error) throw `Something went wrong: ${error}`
})
}
main(quickappCodePath)
import api from './index'
export function getClassifyList() {
return api.getClassifyList()
.then((response) => {
return Promise.resolve(JSON.parse(response.data).data)
}).catch((err) => {
return Promise.reject(err)
})
}
export function getArticleByClassify(page = 0, cid) {
return api.getArticleByClassify(page, cid)
.then((response) => {
return Promise.resolve(JSON.parse(response.data).data)
}).catch((err) => {
return Promise.reject(err)
})
}
\ No newline at end of file
import api from './index'
export function getCollectArticle(page) {
return api.getCollectArticle(page)
.then((response) => {
return Promise.resolve(JSON.parse(response.data).data)
}).catch((err) => {
return Promise.reject(err)
})
}
export function collectArticle(id) {
return api.collectArticle(id)
.then((response) => {
var value = JSON.parse(response.data)
if(value.errorCode === -1) {
return Promise.reject('请先登录')
} else {
return Promise.resolve(value.data)
}
}).catch((err) => {
return Promise.reject(err)
})
}
export function collectArticleAdd(title, author, link) {
return api.collectArticleAdd({
title: title,
author: author,
link: link
})
.then((response) => {
return Promise.resolve(JSON.parse(response.data).data)
}).catch((err) => {
return Promise.reject(err)
})
}
export function uncollectArticle(id) {
return api.uncollectArticle(id)
.then((response) => {
return Promise.resolve(JSON.parse(response.data).data)
}).catch((err) => {
return Promise.reject(err)
})
}
export function uncollect(id, originId) {
return api.uncollect(id, originId)
.then((response) => {
return Promise.resolve(JSON.parse(response.data).data)
}).catch((err) => {
return Promise.reject(err)
})
}
export function getCollectWeb() {
return api.getCollectWeb()
.then((response) => {
return Promise.resolve(JSON.parse(response.data).data)
}).catch((err) => {
return Promise.reject(err)
})
}
export function collectWeb(name, link) {
return api.collectWeb({
name: name,
link: link
})
.then((response) => {
return Promise.resolve(JSON.parse(response.data).data)
}).catch((err) => {
return Promise.reject(err)
})
}
export function editCollectWeb(id, name, link) {
return api.editCollectWeb({
id: id,
name: name,
link: link
})
.then((response) => {
return Promise.resolve(JSON.parse(response.data).data)
}).catch((err) => {
return Promise.reject(err)
})
}
export function deleteCollectWeb(id) {
return api.deleteCollectWeb(id)
.then((response) => {
return Promise.resolve(JSON.parse(response.data).data)
}).catch((err) => {
return Promise.reject(err)
})
}
\ No newline at end of file
const CryptoJS = require('./crypto-js.js')
const key = CryptoJS.enc.Utf8.parse("abcdnnnnnn123456"); //十六位十六进制数作为密钥
var iv = CryptoJS.enc.Utf8.parse('7d0c9683cfcb0c7c'); //十六位十六进制数作为密钥偏移量
/**
* 加密(需要先加载lib/aes/aes.min.js文件)
* @param word
* @returns {*}
*/
export function encrypt(word){
var srcs = CryptoJS.enc.Utf8.parse(word);
var encrypted = CryptoJS.AES.encrypt(srcs, key, {iv: iv,mode:CryptoJS.mode.CBC,padding: CryptoJS.pad.Pkcs7});
let ens= encrypted.ciphertext.toString(CryptoJS.enc.Base64)
return ens;
}
/**
* 解密
* @param word
* @returns {*}
*/
export function decrypt(word){
// 去除 换行符 空格 下划线
var replaceWord = word.replace(/[\r\n]/g,"").replace(/\-g/,'+').replace(/_/,'/');
//返回的是解密后的对象
let decrypt = CryptoJS.AES.decrypt(replaceWord,key,{
iv:iv,
mode:CryptoJS.mode.CBC,
padding:CryptoJS.pad.Pkcs7
});
//将解密对象转换成UTF8的字符串
let decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
// 返回解密结果
return decryptedStr.toString();
}
\ No newline at end of file
This diff is collapsed.
import api from './index'
export function getBanner() {
return api.getBanner()
.then((response) => {
return Promise.resolve(JSON.parse(response.data).data)
}).catch((err) => {
return Promise.reject(err)
})
}
export function getArticle(page = 0) {
return api.getArticle(page)
.then((response) => {
return Promise.resolve(JSON.parse(response.data).data)
}).catch((err) => {
return Promise.reject(err)
})
}
\ No newline at end of file
import {encrypt,decrypt} from './encry/aesUtil.js'
import md5 from 'js-md5'
import { encode, decode } from 'js-base64';
var fetch = require('@system.fetch')
var storage = require('@system.storage')
var AES_KEY = 'abcdnnnn123456'
var API_ROOT = 'http://test.mints-id.com/news-api/'
var headers = {
'version':'1.0.0',
'pkgName':'com.wenshu.aiyuebao',
'channel':'quickapp_' + 'aiyuebao',
'Content-Type' :'application/json',
}
// function getAuth(next) {
// storage.get({
// key: 'auth',
// success: function(data) {
// headers.Cookie = data
// next(true)
// },
// fail: function(data, code) {
// next(false)
// }
// })
// }
function getAuth() {
return new Promise((resolve, reject) => {
storage.get({
key: 'auth',
success: function(data) {
headers.Cookie = data
resolve(true)
},
fail: function(data, code) {
resolve(false)
}
})
})
}
function realFetch(url, data = null, method = 'get') {
// 打印日志
console.log('┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
console.log('┃ url: ', API_ROOT + url)
console.log('┃ method: ', method)
console.log('┃ data: ', JSON.stringify(data))
console.log('┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
//
var token = '.'
var time = '' + new Date().getTime()
var jsonData = JSON.stringify(data)
var channel = 'quickapp_' + 'aiyuebao'
var _sign = token + ':' + channel + ':' + time
var sign = '' + md5(_sign)
var check = '' + md5(_sign+':' + jsonData)
var params = {
'channel': channel,
'sign': sign,
'check': check,
'data': encrypt(jsonData)
}
console.log(params)
headers['token'] = '.'
headers['last-session'] = encode(time) // base64 编码
headers['new-session'] = md5(time) // md5 加密
return new Promise((resolve, reject) => {
fetch.fetch({
url: API_ROOT + url,
data: params,
header: headers,
method: method,
success: function(data) {
var obj = JSON.parse(data.data)
var decyptData = decrypt(obj.data)
console.log(decyptData)
resolve(data)
},
fail: function(data, code) {
reject(data)
}
})
})
}
function withAuth(url, data = null, method = 'get', canSkip = false) {
return getAuth().then((auth) => {
if(auth || canSkip) {
return realFetch(url, data, method)
} else {
return new Promise((resolve, reject) => {
reject('请先登录!')
})
}
})
}
function post(url, data = null, config = {}) {
if(config.withAuth) {
return withAuth(url, data, 'post', config.canSkip)
} else {
return realFetch(url, data, 'post')
}
}
function get(url, data = null, config = {}) {
if(config.withAuth) {
return withAuth(url, data, 'get', config.canSkip)
} else {
return realFetch(url, data, 'get')
}
}
export default {
/**
* 手机号登录
*/
mobileLogin(params){
return post('api/mobilelogin', params)
}
}
\ No newline at end of file
import api from './index'
var storage = require('@system.storage')
export function login(username, password) {
var shumeiId = '123'
var mobile = username
var smsCode = password
var device = '123'
var mac = '123'
return api.mobileLogin({
shumeiId: shumeiId,
mobile: mobile,
smsCode: smsCode,
device: device,
mac: mac
})
.then((response) => {
var value = JSON.parse(response.data)
if(value.errorCode === -1) {
Promise.reject(value.errorMsg)
}
storage.set({
key: 'auth',
value: response.headers['Set-Cookie'],
success:function(data){
console.log('cookies保存成功')
}
})
storage.set({
key: 'user',
value: value.data
})
storage.set({
key: 'isLogin',
value: true
})
return Promise.resolve(value.data)
}).catch((err) => {
return Promise.reject('登录失败')
})
}
export function register(username, password, repassword) {
return api.register({
username: username,
password: password,
repassword: repassword
})
.then((response) => {
var value = JSON.parse(response.data)
if(value.errorCode === -1) {
Promise.reject(value.errorMsg)
}
storage.set({
key: 'auth',
value: response.headers['Set-Cookie'],
success:function(data){
console.log('cookies保存成功')
}
})
storage.set({
key: 'user',
value: value.data
})
storage.set({
key: 'isLogin',
value: true
})
return Promise.resolve(value.data)
}).catch((err) => {
return Promise.reject('注册失败')
})
}
\ No newline at end of file
<template>
<div>
<stack if="{{ visible }}" class="dialog">
<div class="backdrop" @click="onClick(-1)"></div>
<div class="dialog-container">
<text class="btn-item" for="(idx, btn) in btnList" @click="onClick(idx)" style="color: {{ btn.color ? btn.color : '#909399' }}">
{{btn.label}}
</text>
</div>
</stack>
</div>
</template>
<script>
export default {
props: ['visible', 'btnList'],
propsDefault: {
visible: false,
btnList: [
{
label: 'OK',
color: '#909399'
}
]
},
data: () => ({
dialogVisible: false
}),
onInit () {
this.$watch('visible', 'showdialog')
},
showdialog () {
if (this.visible) {
this.dialogVisible = true
} else {
this.onClose()
}
},
onClick (idx) {
this.onClose()
this.$dispatch('click', { idx })
},
onClose () {
this.$dispatch('close')
}
}
</script>
<style>
.dialog {
position: fixed;
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.dialog-container {
display: flex;
flex-direction: column;
width: 450px;
background-color: #ffffff;
border-radius: 10px;
border: 1px solid #dcdfe6;
}
.btn-item {
text-align: center;
padding: 25px;
width: 100%;
}
.backdrop {
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.3);
}
</style>
\ No newline at end of file
<template>
<div>
<stack if="{{ visible }}" class="dialog">
<div class="backdrop" style="opacity : {{ opacity }}"></div>
<div class="dialog-container {{ dialogVisible ? 'fly-in' : 'fly-out' }}">
<div class='dialog-title'>
<text>{{title}}</text>
</div>
<div class="dialog-body">
<text>{{message}}</text>
</div>
<div class="dialog-btn">
<div class="btn-item" for="(idx, btn) in btnList" @click="onClick(idx)"
style="border-left-width: {{ idx === 0 ? 0 : 1 }}px;">
<text style="color: {{ btn.color ? btn.color : '#909399' }}">{{btn.label}}</text>
</div>
</div>
</div>
</stack>
</div>
</template>
<script>
export default {
props: ['visible', 'title', 'message', 'btnList'],
propsDefault: {
visible: false,
title: '提示',
btnList: [
{
label: 'OK',
color: '#909399'
}
]
},
data: () => ({
opacity: 0,
dialogVisible: false
}),
onInit () {
this.$watch('visible', 'showdialog')
},
showdialog () {
if (this.visible) {
this.dialogVisible = true
let id = setInterval(() => {
this.opacity += 0.05
if (this.opacity > 1) {
this.opacity = 1
clearInterval(id)
}
}, 16)
} else {
this.onClose()
}
},
onClick (idx) {
this.onClose()
this.$dispatch('click', { idx })
},
onClose () {
let id = setInterval(() => {
this.dialogVisible = false
this.opacity -= 0.05
if (this.opacity <= 0) {
clearInterval(id)
this.opacity = 0
this.$dispatch('close')
}
}, 16)
}
}
</script>
<style>
.dialog {
position: fixed;
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.dialog-container {
display: flex;
align-items: center;
justify-content: flex-start;
flex-direction: column;
width: 450px;
background-color: #ffffff;
border-radius: 10px;
border: 1px solid #dcdfe6;
}
.dialog-title {
display: flex;
align-items: center;
justify-content: center;
color: #303133;
padding: 10px 0;
width: 100%;
}
.dialog-title > text {
font-size: 35px;
font-weight: bold;
}
.dialog-body {
display: flex;
align-items: center;
justify-content: center;
color: #888888;
font-size: 30px;
padding: 20px 15px 30px 15px;
width: 100%;
}
.dialog-btn {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 80px;
border: solid #dcdfe6;
border-top-width: 1px;
}
.btn-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
border: solid #dddddd;
}
.backdrop {
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.3);
}
.fly-in {
animation-name: flyIn;
animation-duration: 500ms;
animation-timing-function: ease-out;
}
.fly-out {
animation-name: flyOut;
animation-duration: 500ms;
animation-timing-function: ease-in;
}
@keyframes flyIn {
from {
opacity: 0;
transform: translateY(-3000px);
}
to {
opacity: 1;
transform: translateY(1px);
}
}
@keyframes flyOut {
from {
opacity: 1;
transform: translateY(1px);
}
to {
opacity: 0;
transform: translateY(-3000px);
}
}
</style>
\ No newline at end of file
<template>
<div class="c-input">
<div class="c-input-content">
<text class="input-label" if="{{label}}"> {{label}} </text>
<input type="{{type}}" class="input-native" placeholder="{{placeholder}}" value="{{inputValue}}" onchange="onInput"/>
<div class="clear-box" if="{{clearable}}">
<div class="input-clear" if="{{clear}}" onclick="onClear">
<text>×</text>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['label', 'placeholder', 'type', 'value', 'clearable'],
propsDefault: {
placeholder: '请输入...',
type: 'text',
value: '',
clearable: false
},
data: () => ({
clear: false,
inputValue: ''
}),
onInit () {
this.inputValue = this.value
},
onInput ({ value }) {
this.clear = value !== ''
this.inputValue = value
this.$dispatch('input', { value: value })
},
onClear () {
this.clear = false
this.inputValue = ''
this.$dispatch('clear', { value: this.value })
}
}
</script>
<style>
.c-input-content {
display: flex;
width: 100%;
padding-left: 40px;
font-size: 30px;
padding-right: 20px;
height: 80px;
align-items: center;
}
.input-label {
width: 120px;
}
.input-native {
flex: 1;
font-size: 30px;
}
.clear-box {
height: 40px;
width: 40px;
}
.input-clear {
display: flex;
height: 40px;
width: 40px;
border-radius: 25px;
background-color: #dddddd;
}
.input-clear text {
width: 100%;
text-align: center;
font-size: 35px;
color: #ffffff;
margin-top: -5px;
}
</style>
\ No newline at end of file
<template>
<div class="c-title">
<image class="icon" if="showBack" src="/Common/Image/icon_back.png" onclick="onBackClick"></image>
<text class="title">{{ title }}</text>
<image class="icon" if="{{ rightIcon !== '' }}" src="{{ rightIcon }}" onclick="onRightIconClick"></image>
</div>
</template>
<script>
export default {
props: ['showBack', 'title', 'rightIcon'],
propsDefault: {
showBack: true,
title: '',
rightIcon: ''
},
onBackClick() {
this.$dispatch('iconclick', { icon: 'back' })
this.$app.$def.router.back()
},
onRightIconClick() {
this.$dispatch('iconclick', { icon: 'right' })
}
}
</script>
<style>
.c-title {
display: flex;
height: 100px;
align-items: center;
background-color: #24b9ff;
}
.title {
text-overflow: ellipsis;
lines: 1;
flex: 1;
font-size: 36px;
color: #ffffff;
margin-left: 20px;
}
.icon {
width: 100px;
height: 100px;
padding: 26px;
}
</style>
\ No newline at end of file
<script>
/**
* 应用级别的配置,供所有页面公用
*/
const $utils = require('./helper/utils').default
const $apis = require('./helper/apis').default
/* @desc: 注入方法至全局 global,以便页面调用 */
const hook2global = global.__proto__ || global
hook2global.$utils = $utils
hook2global.$apis = $apis
export default {
onCreate() {}
}
</script>
.flex-box-mixins (@column, @justify, @align) {
flex-direction: @column;
justify-content: @justify;
align-items: @align;
}
\ No newline at end of file
@import './variables.less';
@import './mixins.less';
@brand: #09ba07;
@white: #ffffff;
@black: #000000;
@grey: #9393aa;
@red: #fa0101;
@green: #ffff00;
@size-factor: 5px;
/**
* 封装了一些网络请求方法,方便通过 Promise 的形式请求接口
*/
import $fetch from '@system.fetch'
import $utils from './utils'
const TIMEOUT = 20000
Promise.prototype.finally = function(callback) {
const P = this.constructor
return this.then(
value => P.resolve(callback()).then(() => value),
reason =>
P.resolve(callback()).then(() => {
throw reason
})
)
}
/**
* 调用快应用 fetch 接口做网络请求
* @param params
*/
function fetchPromise(params) {
return new Promise((resolve, reject) => {
$fetch
.fetch({
url: params.url,
method: params.method,
data: params.data
})
.then(response => {
const result = response.data
const content = JSON.parse(result.data)
/* @desc: 可跟具体不同业务接口数据,返回你所需要的部分,使得使用尽可能便捷 */
content.success ? resolve(content.value) : resolve(content.message)
})
.catch((error, code) => {
console.log(`🐛 request fail, code = ${code}`)
reject(error)
})
.finally(() => {
console.log(`✔️ request @${params.url} has been completed.`)
resolve()
})
})
}
/**
* 处理网络请求,timeout 是网络请求超时之后返回,默认 20s 可自行修改
* @param params
*/
function requestHandle(params, timeout = TIMEOUT) {
try {
return Promise.race([
fetchPromise(params),
new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('网络状况不太好,再刷新一次?'))
}, timeout)
})
])
} catch (error) {
console.log(error)
}
}
export default {
post: function(url, params) {
return requestHandle({
method: 'post',
url: url,
data: params
})
},
get: function(url, params) {
return requestHandle({
method: 'get',
url: $utils.queryString(url, params)
})
},
put: function(url, params) {
return requestHandle({
method: 'put',
url: url,
data: params
})
}
// 如果,method 您需要更多类型,可自行添加更多方法;
}
import $ajax from '../ajax'
/**
* @desc 在实际开发中,您可以将 baseUrl 替换为您的请求地址前缀;
*
* 已将 $apis 挂载在 global,您可以通过如下方式,进行调用:
* $apis.example.getApi().then().catch().finally()
*
* 备注:如果您不需要发起请求,删除 apis 目录,以及 app.ux 中引用即可;
*/
const baseUrl = 'https://api.exampel.com/'
export default {
getApi(data) {
return $ajax.get(`${baseUrl}your-project-api`, data)
},
postOtherApi(data) {
return $ajax.post(`${baseUrl}your-project-api`, data)
}
}
/**
* 导出 apis 下目录的所有接口
*/
const files = require.context('.', true, /\.js/)
const modules = {}
files.keys().forEach(key => {
if (key === './index.js') {
return
}
modules[key.replace(/(^\.\/|\.js$)/g, '')] = files(key).default
})
export default modules
/**
* 您可以将常用的方法、或系统 API,统一封装,暴露全局,以便各页面、组件调用,而无需 require / import.
*/
const prompt = require('@system.prompt')
/**
* 拼接 url 和参数
*/
function queryString(url, query) {
let str = []
for (let key in query) {
str.push(key + '=' + query[key])
}
let paramStr = str.join('&')
return paramStr ? `${url}?${paramStr}` : url
}
function showToast(message = '', duration = 0) {
if (!message) return
prompt.showToast({
message: message,
duration
})
}
export default {
showToast,
queryString
}
{
"package": "com.example.demo",
"name": "demo",
"versionName": "1.0.0",
"versionCode": 1,
"minPlatformVersion": 1070,
"icon": "/assets/images/logo.png",
"features": [
{
"name": "system.prompt"
},
{
"name": "system.router"
},
{
"name": "system.shortcut"
},
{
"name": "system.fetch"
}
],
"permissions": [
{
"origin": "*"
}
],
"template/official": "demo-template",
"config": {
"logLevel": "debug"
},
"router": {
"entry": "pages/Home",
"pages": {
"pages/Home": {
"component": "index"
},
"pages/Sign": {
"component": "index"
},
"pages/My": {
"component": "index"
},
"pages/News": {
"component": "index"
},
"pages/Login": {
"component": "index"
}
}
},
"display": {
"titleBarBackgroundColor": "#f2f2f2",
"titleBarTextColor": "#414141",
"pages": {
"pages/Home": {
"titleBarText": "快应用示例模版"
},
"pages/Sign": {
"titleBarText": ""
},
"pages/My": {
"titleBarText": ""
},
"pages/News": {
"titleBarText": ""
},
"pages/Login": {
"titleBarText": ""
}
}
}
}
<template>
<div class="tutorial-page">
<!-- 灵活使用tabs组件 -->
<div class="flexible-tabs">
<!-- 监听change事件,触发时动态修改tabs的index属性 -->
<tabs onchange="changeTabactive" index="{{currentIndex}}">
<tab-content class="flexible-tab-content">
<div class="tab-content-section">
<sign></sign>
</div>
<div class="tab-content-section">
<news></news>
</div>
<div class="tab-content-section">
<my></my>
</div>
</tab-content>
</tabs>
<!-- 自定义tab-bar组件 -->
<div class="flexible-tabbar">
<text class="{{currentIndex === 0 ? 'active' : ''}}" onclick="clickTabBar(0)">{{tabItems[0].text}}</text>
<text class="{{currentIndex === 1 ? 'active' : ''}}" onclick="clickTabBar(1)">{{tabItems[1].text}}</text>
<text class="{{currentIndex === 2 ? 'active' : ''}}" onclick="clickTabBar(2)">{{tabItems[2].text}}</text>
</div>
</div>
</div>
</template>
<import name="sign" src="../Sign/index"></import>
<import name="my" src="../My/index"></import>
<import name="news" src="../News/index"></import>
<script>
import router from '@system.router'
export default {
// 页面级组件的数据模型,影响传入数据的覆盖机制:private内定义的属性不允许被覆盖
private: {
currentIndex:0,
title: '欢迎体验快应用开发',
type: 'content_1',
tabItems:[
{
text:"签到"
},
{
text:"新闻"
},
{
text:"我的"
},
]
},
onInit() {
this.$page.setTitleBar({ text: '测试' })
},
changeTabactive (evt) {
this.currentIndex = evt.index
},
clickTabBar (index) {
this.currentIndex = index
},
routePage (param) {
router.push({
uri: 'ComponentTabs/complex/' + param
})
}
}
</script>
<style lang="less">
@import './../../assets/styles/style.less';
.tutorial-page {
flex: 1;
.flexible-tabs {
flex: 1;
flex-direction: column;
.flexible-tabbar {
height: 100px;
padding: 0 30px;
background-color: #f1f1f1;
align-items: center;
text {
flex-grow: 1;
height: 100px;
margin: 0 30px;
text-align: center;
border: 0px solid #f1f1f1;
border-bottom-width: 5px;
}
image {
height: 50px;
width: 50px;
resize-mode: contain;
}
.active {
color: #0faeff;
border-bottom-color: #0faeff;
}
}
.flexible-tab-content {
flex: 1;
.tab-content-section {
flex: 1;
background-color: #ff0;
justify-content: center;
}
}
}
}
</style>
<template>
<div class="login-wrapper">
<c-input label="手机号" clearable="true" placeholder="请输入手机号" type="num" class="input-border" oninput="onInputUsername"></c-input>
<c-input label="密码" clearable="true" placeholder="请输入密码" type="password" class="input-border" oninput="onInputPassword"></c-input>
<text class="login-btn" onclick="login">登录</text>
</div>
</template>
<import name="c-input" src="../../Common/Component/CInput/index"></import>
<script>
import {login} from '../../Common/Api/user'
export default {
data() {
return {
username: '15156975131',
password: '试试水123456'
}
},
onInit() {
this.$page.setTitleBar({ text: '登录' })
},
onInputUsername({ detail: { value } }) {
this.username = value
},
onInputPassword({ detail: { value } }) {
this.password = value
},
login() {
if(this.username === null || this.username.length < 6) {
$utils.showToast('用户名长度不能小于6')
} else if (this.password === null || this.password.length < 6) {
$utils.showToast('密码长度不能小于6')
} else {
login(this.username, this.password)
.then((data) => {
this.$dispatch('success', { data: data })
})
.catch((err) => {
this.$utils.showToast({ message: data })
})
}
}
}
</script>
<style>
.login-wrapper {
display: flex;
flex-direction: column;
}
.login-tip {
width: 100%;
height: 100px;
text-align: center;
border-bottom-width: 1px;
border-bottom-color: #eeeeee;
}
.input-border {
border-bottom-width: 1px;
border-bottom-color: #eeeeee;
}
.login-btn {
height: 80px;
width: 100%;
text-align: center;
margin-top: 50px;
background-color: #24b9ff;
color: #ffffff;
}
</style>
\ No newline at end of file
<template>
<div class="wrapper">
<text class="title">{{ title }}</text>
<text onclick="toLogin" class="loginWrapper">登录</text>
</div>
</template>
<script>
import router from '@system.router'
export default {
data: {
title: '我的',
},
onInit() {},
toLogin:function() {
router.push({
uri: '/pages/Login'
})
},
}
</script>
<style>
.wrapper {
flex: 1;
flex-direction: column;
justify-content: center;
align-items: center;
}
.title {
text-align: center;
color: #212121;
}
.loginWrapper {
margin-top: 10px;
}
</style>
\ No newline at end of file
<template>
<div class="wrapper">
<text class="title">{{ title }}</text>
<text onclick="jiami" class="title">加密</text>
<text onclick="jiemi" class="title">解密</text>
</div>
</template>
<script>
import {encrypt,decrypt} from '../../Common/Api/encry/aesUtil.js'
import md5 from 'crypto-js/md5'
export default {
data: {
title: '新闻'
},
onInit() {},
jiami() {
// console.log(""+md5('你好'))
console.log(encrypt('手动佛山东'))
},
jiemi() {
var str = 'oQD9u2UYqu1+lXxthjc13nOI6k3eiY5UEJ1PJf/rtIfdYlEoXzKNfg6lbrzuR0xD6INijMcRIWqifoaARZZ3/X+7SeLnC3whRAdthyZwk+2D1Zykrif7GqPO0JtpDlezQaPmqxCC3AYEbV9PiQsm3IlgQAitX/ErcRG+p573xmV7Bdh7ogfmz9Fr4wkgzd/XsW08tMCnULjQOn0OXvAeyM5ygrp9aAkBUHwgKZ0Y9syb2PhSKzyiPqfwrYvoQtJD9AmAnIafKg2DPb1IjtgvdsWP+M/AYh24HOr9YfSAITYptG2cOIPyw3A717aPQu6ru99RChkUHqipFt/mTctKMfw//YHxm5XqdzdMf1I0u+DHFHa3uAFbwJ4yeClPWhKYaATgivFFACGuo08ySKQzk6FMsb7SdBdpCepbxlrD5/1TfokW1/DfuF2lSNRZrYxZtz2RSN3OukYLJBFLhirWXD6U6OwW7DKieDYNvOqBYFg14MIM26lahuEWGrAbWWHO2N3EN0pgChQCjVy+bQOsdqraGCuPbPP2gNv81xvK3H/Dy6wXxXPIhkLI0qpnqVO8yvv45Iiw4qzxkitQgo0kzfERzYM2yqmI5dL0N7nZy8slE5v3lLLqEq+wLT62E7NbRO6tEt9Ne9WhmmxfRY2g5MZy26P8DOxpcQSSombmp+YphDK1pu5AmP+EZp4iibWl75S2YdneQK5xie+kr8ftmMO7xcJHpwAsJDTam2CRbAnu0BHu/se/ShcQEsS1OqK0I6h4S2n+tPwKh64qTCgQx1k89l6fL3ulrav7AKDx/lSUsdxKato8pOqhxpiTrh+xzaq1NU5PlTJgH+wcsEEPLKoT16OMnKdunNoNVcXAnIF2CVBmzUFRf6x+4SdnhBMGpy/VLnRxs0MNIbjXYGtB8+IJZSvDfP0Wvr3IhI4lmbpvb34C9gJXtVhMEO0Y51ZGcGqJ/aX6OFDSs6iNKXzBhg=='
console.log( decrypt(str))
},
}
</script>
<style>
.wrapper {
flex: 1;
background-color: #00ffff;
flex-direction: column;
justify-content: center;
align-items: center;
}
.title {
text-align: center;
color: #212121;
}
</style>
\ No newline at end of file
<template>
<div class="wrapper">
<text class="title">{{ title }}</text>
</div>
</template>
<script>
export default {
data: {
title: '视频'
},
onInit() {}
}
</script>
<style>
.wrapper {
flex: 1;
flex-direction: column;
justify-content: center;
align-items: center;
}
.title {
text-align: center;
color: #212121;
}
</style>
\ No newline at end of file
{
"rules": [{ "rule": "enable", "page": "*" }]
}
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment