├── .quickapp.preview.json ├── .prettierignore ├── src ├── assets │ ├── styles │ │ ├── style.less │ │ ├── mixins.less │ │ ├── variables.less │ │ └── common.less │ ├── icons │ │ ├── like.png │ │ ├── home.svg │ │ ├── main.svg │ │ └── about.svg │ ├── images │ │ └── logo.png │ └── js │ │ ├── statistics.config.js │ │ └── appStatistics.min.js ├── helper │ ├── index.js │ ├── utils │ │ ├── index.js │ │ ├── string.js │ │ ├── common.js │ │ ├── datetime.js │ │ └── system.js │ ├── apis │ │ ├── index.js │ │ └── links.js │ └── ajax.js ├── app.ux ├── manifest.json ├── pages │ ├── About │ │ └── index.ux │ └── Main │ │ └── index.ux └── components │ └── WaterWorld.ux ├── .gitignore ├── quickapp.config.js ├── command ├── gen │ ├── template.ux │ └── index.js ├── selfCloseInputTag.js ├── server.js ├── openChrome.applescript └── utils.js ├── LICENSE ├── .eslintrc.json ├── .circleci └── config.yml ├── package.json └── README.md /.quickapp.preview.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | manifest.json 3 | README.md 4 | 5 | # assets/js 6 | src/assets/js/*.js -------------------------------------------------------------------------------- /src/assets/styles/style.less: -------------------------------------------------------------------------------- 1 | @import './variables.less'; 2 | @import './mixins.less'; 3 | @import './common.less'; 4 | -------------------------------------------------------------------------------- /src/assets/icons/like.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicejade/quickapp-boilerplate-template/HEAD/src/assets/icons/like.png -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicejade/quickapp-boilerplate-template/HEAD/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/assets/js/statistics.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /* @Fixed: 使用此模板,此处需要替换为你所申请的快应用 KEY */ 3 | app_key: '888b12c8e42451e8872bc5af4f89ffaa' 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/styles/mixins.less: -------------------------------------------------------------------------------- 1 | .flex-box-mixins (@column, @justify, @align) { 2 | flex-direction: @column; 3 | justify-content: @justify; 4 | align-items: @align; 5 | } 6 | -------------------------------------------------------------------------------- /src/helper/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | export const $utils = require('./utils').default 4 | export const $ajax = require('./ajax').default 5 | export const $apis = require('./apis').default 6 | -------------------------------------------------------------------------------- /src/helper/utils/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | const files = require.context('.', true, /\.js/) 4 | const utils = {} 5 | 6 | files.keys().forEach(key => { 7 | if (key === './index.js') { 8 | return 9 | } 10 | Object.assign(utils, files(key).default) 11 | }) 12 | 13 | export default utils 14 | -------------------------------------------------------------------------------- /src/assets/styles/variables.less: -------------------------------------------------------------------------------- 1 | @brand: #20a0ff; 2 | @jade: #2edfa3; 3 | @white: #ffffff; 4 | @black: #000000; 5 | @grey: #7a8ba9; 6 | @grey-black: #454545; 7 | @black-grey: #464547; 8 | @border-grey: #eaeaea; 9 | @white-grey: #f4f6fa; 10 | @silver-grey: #707780; 11 | 12 | @tab-bar-height: 100px; 13 | 14 | @size-factor: 8px; 15 | -------------------------------------------------------------------------------- /src/helper/apis/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | const files = require.context('.', true, /\.js/) 4 | const modules = {} 5 | 6 | files.keys().forEach(key => { 7 | if (key === './index.js') { 8 | return 9 | } 10 | modules[key.replace(/(^\.\/|\.js$)/g, '')] = files(key).default 11 | }) 12 | 13 | export default modules 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /build 5 | 6 | /sign/release 7 | 8 | # local env files 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw* -------------------------------------------------------------------------------- /src/helper/apis/links.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @desc 在实际开发中,您可以将 baseUrl 替换为您的请求地址前缀; 3 | * 4 | * 已将 $apis 挂载在 global,您可以通过如下方式,进行调用: 5 | * $apis.example.getSomeApi().then().catch().finally() 6 | * 7 | * 备注:如果您不需要发起请求,删除 apis 目录,以及 app.ux 中引用即可; 8 | */ 9 | 10 | import $ajax from '../ajax' 11 | import $utils from '../utils' 12 | 13 | export default { 14 | getAllLinksCount(data) { 15 | return $ajax.get($utils.composeApiPath('getAllLinksCount'), data) 16 | }, 17 | getNiceLinks(data) { 18 | return $ajax.get($utils.composeApiPath('getNiceLinks'), data) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /quickapp.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const version = require('./package.json').version 3 | const versionName = require('./src/manifest.json').versionName 4 | const versionCode = require('./src/manifest.json').versionCode 5 | 6 | module.exports = { 7 | cli: { 8 | devtool: 'none' 9 | }, 10 | webpack: { 11 | plugins: [ 12 | new webpack.DefinePlugin({ 13 | VERSION: JSON.stringify(version), 14 | VERSION_NAME: JSON.stringify(versionName), 15 | VERSION_CODE: JSON.stringify(versionCode) 16 | }) 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/helper/utils/string.js: -------------------------------------------------------------------------------- 1 | export default { 2 | queryString(url, query) { 3 | let str = [] 4 | for (let key in query) { 5 | if (typeof query[key] === 'object') { 6 | query[key] = JSON.stringify(query[key]) 7 | } 8 | str.push(key + '=' + query[key]) 9 | } 10 | let paramStr = str.join('&') 11 | return paramStr ? `${url}?${paramStr}` : url 12 | }, 13 | 14 | // 获取字符串实际长度(包含汉字,汉字统一按照 2 字节算;) 15 | getByteLength(str = '') { 16 | if (typeof str !== 'string') return str.length 17 | return str.replace(/[^\\x00-\xff]/g, 'aa').length 18 | }, 19 | 20 | interceptString(string = '', length = 140) { 21 | if (this.getByteLength(string) > 140) { 22 | return string.substring(0, length) + '...' 23 | } else { 24 | return string 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/app.ux: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /command/gen/template.ux: -------------------------------------------------------------------------------- 1 | 8 | 9 | 29 | 30 | 35 | -------------------------------------------------------------------------------- /src/helper/utils/common.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | export default { 4 | /* @desc: 统一组装 HTTP 请求的 Url 地址 */ 5 | composeApiPath(apiName) { 6 | const requestBaseUrl = 'https://nicelinks.site/api/' 7 | return `${requestBaseUrl}${apiName}` 8 | }, 9 | 10 | getRandomInt(min, max) { 11 | min = Math.ceil(min) 12 | max = Math.floor(max) 13 | return Math.floor(Math.random() * (max - min)) + min 14 | }, 15 | 16 | setInterval(callback, interval) { 17 | const now = Date.now 18 | let startTime = now() 19 | let endTime = startTime 20 | const loop = () => { 21 | this.intervalTimer = global.requestAnimationFrame(loop) 22 | endTime = now() 23 | if (endTime - startTime >= interval) { 24 | startTime = endTime = now() 25 | callback() 26 | } 27 | } 28 | this.intervalTimer = global.requestAnimationFrame(loop) 29 | return this.intervalTimer 30 | }, 31 | 32 | clearInterval(intervalTimerId) { 33 | global.cancelAnimationFrame(intervalTimerId) 34 | intervalTimerId = null 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /command/selfCloseInputTag.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file: selfCloseInputTag.js 3 | * @desc: 遍历指定目录下 .ux 文件,将其中 input 标签由 转换为 4 | * @date: 2019-01-23 5 | */ 6 | 7 | const fs = require('fs') 8 | const path = require('path') 9 | 10 | const quickappCodePath = './src/' 11 | 12 | const main = codePath => { 13 | const traversing = cpath => { 14 | const files = fs.readdirSync(cpath) 15 | files.forEach(fileName => { 16 | const fPath = path.join(cpath, fileName) 17 | const stats = fs.statSync(fPath) 18 | stats.isDirectory() && traversing(fPath) 19 | stats.isFile() && fPath.endsWith('.ux') && matchAndReplace(fPath) 20 | }) 21 | } 22 | traversing(codePath) 23 | } 24 | 25 | const matchAndReplace = path => { 26 | const pageContent = fs.readFileSync(path, 'UTF-8') 27 | const newContent = pageContent.replace(/(<)([\s]*?)(input\b[^\/]*?)>[\s\S]*?<\/input>/gm, '$1$3 />') 28 | fs.writeFile(path, newContent, error => { 29 | if (error) throw `Something went wrong: ${error}` 30 | }) 31 | } 32 | 33 | main(quickappCodePath) 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 JadeYang(杨琼璞) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true 4 | }, 5 | "extends": "eslint:recommended", 6 | "parser": "babel-eslint", 7 | "parserOptions": { 8 | "sourceType": "module", 9 | "ecmaFeatures": { 10 | "experimentalObjectRestSpread": true, 11 | "jsx": true 12 | } 13 | }, 14 | "globals": { 15 | "loadData": false, 16 | "saveData": false, 17 | "history": false, 18 | "console": false, 19 | "setTimeout": false, 20 | "clearTimeout": false, 21 | "setInterval": false, 22 | "clearInterval": false 23 | }, 24 | "plugins": ["hybrid"], 25 | "rules": { 26 | "indent": ["warn", 2], 27 | "no-console": [ 28 | "warn", 29 | { 30 | "allow": ["info", "warn", "error"] 31 | } 32 | ], 33 | "no-unused-vars": [ 34 | "warn", 35 | { 36 | "varsIgnorePattern": "prompt" 37 | } 38 | ], 39 | "quotes": [ 40 | "warn", 41 | "single", 42 | { 43 | "avoidEscape": true, 44 | "allowTemplateLiterals": true 45 | } 46 | ], 47 | "linebreak-style": ["warn", "unix"], 48 | "semi": ["warn", "never"] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:carbon-jessie 11 | 12 | # Specify service dependencies here if necessary 13 | # CircleCI maintains a library of pre-built images 14 | # documented at https://circleci.com/docs/2.0/circleci-images/ 15 | # - image: circleci/mongo:3.4.4 16 | 17 | working_directory: ~/repo 18 | 19 | steps: 20 | - checkout 21 | 22 | # Download and cache dependencies 23 | - restore_cache: 24 | keys: 25 | - v1-dependencies-{{ checksum "package.json" }} 26 | # fallback to using the latest cache if no exact match is found 27 | - v1-dependencies- 28 | 29 | - run: yarn install 30 | 31 | - save_cache: 32 | paths: 33 | - node_modules 34 | key: v1-dependencies-{{ checksum "package.json" }} 35 | 36 | # run tests! 37 | # - run: yarn test 38 | 39 | # run build 40 | - run: yarn run build 41 | -------------------------------------------------------------------------------- /src/assets/styles/common.less: -------------------------------------------------------------------------------- 1 | .page-wrapper { 2 | font-size: 5 * @size-factor; 3 | .wrapper-padding { 4 | padding: 0 6 * @size-factor; 5 | } 6 | 7 | .app-name { 8 | color: @black; 9 | font-size: 7 * @size-factor; 10 | font-weight: bold; 11 | text-align: center; 12 | margin-top: 4 * @size-factor; 13 | margin-bottom: 4 * @size-factor; 14 | } 15 | .app-icon { 16 | width: 30 * @size-factor; 17 | height: 30 * @size-factor; 18 | border-radius: 15 * @size-factor; 19 | margin-bottom: 4 * @size-factor; 20 | } 21 | .app-desc { 22 | margin-bottom: 4 * @size-factor; 23 | } 24 | 25 | .title { 26 | color: @black; 27 | font-size: 6 * @size-factor; 28 | font-weight: bold; 29 | text-align: center; 30 | margin-top: 5 * @size-factor; 31 | } 32 | .desc { 33 | color: @grey-black; 34 | margin-top: 4 * @size-factor; 35 | font-size: 4.5 * @size-factor; 36 | } 37 | .button { 38 | height: 10 * @size-factor; 39 | border: 1px solid @brand; 40 | background-color: #ffffff; 41 | padding: 0 3 * @size-factor; 42 | color: @brand; 43 | font-size: 4.5 * @size-factor; 44 | border-radius: 5 * @size-factor; 45 | margin-top: 6 * @size-factor; 46 | margin-bottom: 6 * @size-factor; 47 | } 48 | } 49 | 50 | .external-link { 51 | color: @brand; 52 | } 53 | -------------------------------------------------------------------------------- /src/assets/icons/home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "package": "com.boilerplate.template", 3 | "name": "快应用脚手架", 4 | "versionName": "1.0.0", 5 | "versionCode": "1", 6 | "minPlatformVersion": "1020", 7 | "icon": "/assets/images/logo.png", 8 | "features": [ 9 | { "name": "system.fetch" }, 10 | { "name": "system.storage" }, 11 | { "name": "system.device" }, 12 | { "name": "system.network" }, 13 | { "name": "service.account" }, 14 | { "name": "system.prompt" }, 15 | { "name": "system.router" }, 16 | { "name": "system.shortcut" }, 17 | { "name": "system.webview" }, 18 | { 19 | "name": "service.share", 20 | "params": { 21 | "appSign": "", 22 | "wxKey": "" 23 | } 24 | } 25 | ], 26 | "permissions": [{ "origin": "*" }], 27 | "config": { 28 | "logLevel": "debug", 29 | "designWidth": 800 30 | }, 31 | "router": { 32 | "entry": "pages/Main", 33 | "pages": { 34 | "pages/Main": { 35 | "component": "index" 36 | }, 37 | "pages/About": { 38 | "component": "index" 39 | } 40 | } 41 | }, 42 | "display": { 43 | "titleBarBackgroundColor": "#f2f2f2", 44 | "titleBarTextColor": "#333333", 45 | "menu": true, 46 | "pages": { 47 | "pages/Main": { 48 | "titleBarText": "快应用脚手架", 49 | "menu": true 50 | }, 51 | "pages/About": { 52 | "menu": false 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/assets/icons/main.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /command/server.js: -------------------------------------------------------------------------------- 1 | const portfinder = require('portfinder') 2 | const chalk = require('chalk') 3 | const utils = require('./utils.js') 4 | const shelljs = require('shelljs') 5 | 6 | /** 7 | * @desc:autoOpenBrowser 开启之后,会将二维码 Server 地址,在浏览器自动打开; 8 | * @date: 2018-09-01 9 | */ 10 | const autoOpenBrowser = true 11 | 12 | const startServer = () => { 13 | portfinder.basePort = +process.env.PORT || 8080 14 | 15 | portfinder 16 | .getPortPromise() 17 | .then(port => { 18 | const urls = utils.prepareUrls('http', '0.0.0.0', port) 19 | 20 | const child = shelljs.exec(`hap server --port ${port}`, { async: true }) 21 | let isBrowserOpened = false 22 | child.stdout.on('data', () => { 23 | // 在 hap-toolkit 编译完成后,再自动打开浏览器二维码展示 @2019-01-02; 24 | autoOpenBrowser && !isBrowserOpened && utils.startBrowserProcess(urls.lanUrlForBrowser) 25 | isBrowserOpened = true 26 | }) 27 | 28 | printInfoAtTheTerminal(urls) 29 | }) 30 | .catch(error => { 31 | console.log(`${chalk.red('✘')} Opps, Something Error:\n`, error) 32 | }) 33 | } 34 | 35 | const printInfoAtTheTerminal = urls => { 36 | console.log() 37 | console.log( 38 | [ 39 | `App running at:`, 40 | ` ${chalk.green('✔')} Local: ${chalk.cyan(urls.localUrlForTerminal)}`, 41 | ` ${chalk.green('✔')} Online: ${chalk.cyan(urls.lanUrlForTerminal)}` 42 | ].join('\n') 43 | ) 44 | console.log() 45 | } 46 | 47 | startServer() 48 | -------------------------------------------------------------------------------- /src/helper/ajax.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import $fetch from '@system.fetch' 4 | import $utils from './utils' 5 | const prompt = require('@system.prompt') 6 | 7 | Promise.prototype.finally = function(callback) { 8 | const P = this.constructor 9 | return this.then( 10 | value => P.resolve(callback()).then(() => value), 11 | reason => 12 | P.resolve(callback()).then(() => { 13 | throw reason 14 | }) 15 | ) 16 | } 17 | 18 | function requestHandle(params) { 19 | return new Promise((resolve, reject) => { 20 | $fetch 21 | .fetch({ 22 | url: params.url, 23 | method: params.method, 24 | data: params.data 25 | }) 26 | .then(response => { 27 | const result = response.data 28 | const content = JSON.parse(result.data) 29 | /* @desc: 可跟具体不同业务接口数据,返回你所需要的部分,使得使用尽可能便捷 */ 30 | content.success ? resolve(content.value) : resolve(content.message) 31 | }) 32 | .catch((error, code) => { 33 | console.log(`🐛 request fail, code = ${code}`) 34 | reject(error) 35 | }) 36 | .finally(() => { 37 | console.log(`✔️ request @${params.url} has been completed.`) 38 | resolve() 39 | }) 40 | }) 41 | } 42 | 43 | export default { 44 | post: function(url, params) { 45 | return requestHandle({ 46 | method: 'post', 47 | url: url, 48 | data: params 49 | }) 50 | }, 51 | get: function(url, params) { 52 | return requestHandle({ 53 | method: 'get', 54 | url: $utils.queryString(url, params) 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/helper/utils/datetime.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /* 3 | * DESC:对Date的扩展,将 Date 转化为指定格式的String。 4 | * 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符, 5 | * 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字) 例子: 6 | * (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2018-07-02 08:09:04.423 7 | * (new Date()).Format("yyyy-M-d h:m:s.S") ==> 2018-7-2 8:9:4.18 8 | * 9 | * @format 10 | */ 11 | 12 | Date.prototype.Format = function(fmt = 'yyyy-MM-dd hh:mm:ss') { 13 | var o = { 14 | 'M+': this.getMonth() + 1, 15 | 'd+': this.getDate(), 16 | 'h+': this.getHours(), 17 | 'm+': this.getMinutes(), 18 | 's+': this.getSeconds(), 19 | 'q+': Math.floor((this.getMonth() + 3) / 3), 20 | S: this.getMilliseconds() 21 | } 22 | if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length)) 23 | for (var k in o) { 24 | if (new RegExp('(' + k + ')').test(fmt)) { 25 | fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)) 26 | } 27 | } 28 | return fmt 29 | } 30 | 31 | export default { 32 | setCurrentTime(datetime = '') { 33 | global.currentTime = datetime 34 | }, 35 | 36 | getCurrentTime() { 37 | return global.currentTime || '' 38 | }, 39 | 40 | dateOffset(thatTime, nowTime) { 41 | if (!arguments.length) return '' 42 | 43 | let now = thatTime ? thatTime : new Date().getTime() 44 | let offsetValue = now - new Date(nowTime).getTime() 45 | let minute = 1000 * 60 46 | let hour = minute * 60 47 | let day = hour * 24 48 | let week = day * 7 49 | let month = day * 30 50 | let year = month * 12 51 | 52 | let unitArr = ['年前', '月前', '周前', '天前', '小时前', '分钟前', '刚刚'] 53 | let offsetArr = [year, month, week, day, hour, minute].map((item, index) => { 54 | return { 55 | value: offsetValue / item, 56 | unit: unitArr[index] 57 | } 58 | }) 59 | 60 | for (let key in offsetArr) { 61 | if (offsetArr[key].value >= 1) { 62 | return parseInt(offsetArr[key].value) + ' ' + offsetArr[key].unit 63 | } 64 | } 65 | return unitArr[6] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/assets/icons/about.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/helper/utils/system.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | const prompt = require('@system.prompt') 4 | const router = require('@system.router') 5 | const share = require('@service.share') 6 | 7 | /** 8 | * @desc 创建桌面图标 9 | * 注意:使用加载器测试`创建桌面快捷方式`功能时,请先在`系统设置`中打开`应用加载器`的`桌面快捷方式`权限 10 | */ 11 | function createShortcut() { 12 | const shortcut = require('@system.shortcut') 13 | shortcut.hasInstalled({ 14 | success: function(ret) { 15 | if (ret) { 16 | prompt.showToast({ 17 | message: '已创建桌面图标' 18 | }) 19 | } else { 20 | shortcut.install({ 21 | success: function() { 22 | prompt.showToast({ 23 | message: '成功创建桌面图标' 24 | }) 25 | }, 26 | fail: function(errmsg, errcode) { 27 | prompt.showToast({ 28 | message: `${errcode}: ${errmsg}` 29 | }) 30 | } 31 | }) 32 | } 33 | } 34 | }) 35 | } 36 | 37 | /** 38 | * @desc 调起第三方分享 39 | */ 40 | function call3thPartyShare() { 41 | share.share({ 42 | shareType: 0, 43 | title: '快应用分享', 44 | summary: 45 | '快应用是移动互联网新型应用生态,与手机系统深度整合,为用户提供更加场景化的体验。具备传统APP完整的应用体验,但无需安装、即点即用。', 46 | imagePath: '/assets/images/logo.png', 47 | targetUrl: 'https://nicelinks.site', 48 | platforms: ['SYSTEM'], 49 | success: function(data) { 50 | prompt.showToast({ 51 | message: `已成功完成分享` 52 | }) 53 | console.log(`handling success, data = ${JSON.stringify(data)}`) 54 | }, 55 | fail: function(data, code) { 56 | prompt.showToast({ 57 | message: `handling fail, code = ${code}, message: ${JSON.stringify(data)}` 58 | }) 59 | console.log(`handling fail, code = ${code}`) 60 | } 61 | }) 62 | } 63 | 64 | function route2aboutPage() { 65 | router.push({ 66 | uri: '/pages/About' 67 | }) 68 | } 69 | 70 | export default { 71 | createShortcut, 72 | 73 | showToast(message = '', duration = 0) { 74 | if (!message) return 75 | prompt.showToast({ 76 | message: message, 77 | duration 78 | }) 79 | }, 80 | 81 | route2theUrl(url, params) { 82 | router.push({ 83 | uri: url, 84 | params: params 85 | }) 86 | }, 87 | 88 | route2nicelinks() { 89 | router.push({ 90 | uri: 'https://nicelinks.site/explore/all?utm_source=quickapp' 91 | }) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /command/openChrome.applescript: -------------------------------------------------------------------------------- 1 | (* 2 | Copyright (c) 2015-present, Facebook, Inc. 3 | This source code is licensed under the MIT license found in the 4 | LICENSE file at 5 | https://github.com/facebookincubator/create-react-app/blob/master/LICENSE 6 | *) 7 | 8 | property targetTab: null 9 | property targetTabIndex: -1 10 | property targetWindow: null 11 | 12 | on run argv 13 | set theURL to item 1 of argv 14 | 15 | tell application "Chrome" 16 | 17 | if (count every window) = 0 then 18 | make new window 19 | end if 20 | 21 | -- 1: Looking for tab running debugger 22 | -- then, Reload debugging tab if found 23 | -- then return 24 | set found to my lookupTabWithUrl(theURL) 25 | if found then 26 | set targetWindow's active tab index to targetTabIndex 27 | tell targetTab to reload 28 | tell targetWindow to activate 29 | set index of targetWindow to 1 30 | return 31 | end if 32 | 33 | -- 2: Looking for Empty tab 34 | -- In case debugging tab was not found 35 | -- We try to find an empty tab instead 36 | set found to my lookupTabWithUrl("chrome://newtab/") 37 | if found then 38 | set targetWindow's active tab index to targetTabIndex 39 | set URL of targetTab to theURL 40 | tell targetWindow to activate 41 | return 42 | end if 43 | 44 | -- 3: Create new tab 45 | -- both debugging and empty tab were not found 46 | -- make a new tab with url 47 | tell window 1 48 | activate 49 | make new tab with properties {URL:theURL} 50 | end tell 51 | end tell 52 | end run 53 | 54 | -- Function: 55 | -- Lookup tab with given url 56 | -- if found, store tab, index, and window in properties 57 | -- (properties were declared on top of file) 58 | on lookupTabWithUrl(lookupUrl) 59 | tell application "Chrome" 60 | -- Find a tab with the given url 61 | set found to false 62 | set theTabIndex to -1 63 | repeat with theWindow in every window 64 | set theTabIndex to 0 65 | repeat with theTab in every tab of theWindow 66 | set theTabIndex to theTabIndex + 1 67 | if (theTab's URL as string) contains lookupUrl then 68 | -- assign tab, tab index, and window to properties 69 | set targetTab to theTab 70 | set targetTabIndex to theTabIndex 71 | set targetWindow to theWindow 72 | set found to true 73 | exit repeat 74 | end if 75 | end repeat 76 | 77 | if found then 78 | exit repeat 79 | end if 80 | end repeat 81 | end tell 82 | return found 83 | end lookupTabWithUrl -------------------------------------------------------------------------------- /command/gen/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @desc: gen script command,make a new page generated by one click. 3 | * @author: nicejade 4 | */ 5 | 6 | const fs = require('fs') 7 | const path = require('path') 8 | const colors = require('colors') 9 | 10 | const newFolderName = process.argv[2] 11 | 12 | String.prototype.firstUpperCase = function() { 13 | return this.replace(/\b(\w)/g, $1 => { 14 | return $1.toLowerCase() 15 | }) 16 | } 17 | const resolve = dir => { 18 | return path.join(__dirname, '../..', dir) 19 | } 20 | 21 | const successExecPrint = msg => { 22 | console.log(colors.green(`✓ `) + colors.cyan(`${msg} `) + colors.green('task has been successfully executed.')) 23 | } 24 | 25 | function createNewPage(newFolderPath) { 26 | const mReg = new RegExp('@PAGE_CLASS_NAME', 'g') 27 | const pageContent = fs.readFileSync(`${__dirname}/template.ux`, 'UTF-8') 28 | const rootClassName = newFolderName 29 | .firstUpperCase() 30 | .replace(/([A-Z])/g, '-$1') 31 | .toLowerCase() 32 | const newContent = pageContent.replace(mReg, rootClassName) 33 | 34 | fs.mkdirSync(newFolderPath, 0777) 35 | fs.writeFile(`${newFolderPath}/index.ux`, newContent, error => { 36 | if (error) throw `Something went wrong: ${error}` 37 | }) 38 | successExecPrint('Create New Page') 39 | } 40 | 41 | function saveRouter2Manifest() { 42 | const manifestPath = resolve('/src/manifest.json') 43 | let manifestConf = fs.readFileSync(manifestPath, 'UTF-8') 44 | manifestConf = JSON.parse(manifestConf) 45 | const routerPages = manifestConf.router.pages 46 | routerPages[`pages/${newFolderName}`] = { 47 | component: 'index' 48 | } 49 | manifestConf = JSON.stringify(manifestConf, null, 2) 50 | fs.writeFile(manifestPath, manifestConf, error => { 51 | if (error) throw `Something went wrong[@saveRouter2Manifest]: ${error}` 52 | }) 53 | successExecPrint('Save Router Into Manifest') 54 | } 55 | 56 | function main() { 57 | if (!newFolderName) { 58 | return console.warn(`⚠️ Please enter the name of the page you want to create.`.underline.red) 59 | } 60 | 61 | const folderNameReg = /^[A-Z][[A-Za-z0-9]+$/ 62 | if (!folderNameReg.test(newFolderName)) { 63 | return console.warn(`⚠️ Please enter the standard Folder name. Eg: XyzAbcde.`.underline.red) 64 | } 65 | 66 | const newFolderPath = path.join(__dirname, `../../src/pages/${newFolderName}`) 67 | const isExist = fs.existsSync(newFolderPath) 68 | 69 | if (isExist) { 70 | return console.warn(`⚠️ ${newFolderName} already exists in the /src/pages/ directory.`.underline.red) 71 | } 72 | createNewPage(newFolderPath) 73 | saveRouter2Manifest() 74 | } 75 | 76 | main() 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quickapp-boilerplate-template", 3 | "version": "3.0.0", 4 | "description": "🔨致力于构建更为优雅的快应用开发脚手架模板。", 5 | "scripts": { 6 | "start": "npm run server", 7 | "server": "hap server --watch", 8 | "watch": "hap watch", 9 | "build": "hap build", 10 | "release": "hap release", 11 | "gen": "node ./command/gen/index.js", 12 | "debug": "hap debug", 13 | "commit": "git add . && git commit -m '✨ functional update' && git push", 14 | "clean-commit": "git checkout --orphan latest_branch && git add -A && git commit -am '🎉 Initial commit' && git branch -D master && git branch -m master && git push -f origin master", 15 | "precommit-msg": "echo '🚧 start pre-commit checks...' && exit 0", 16 | "eslint:fix": "eslint src/**/**/*.js --fix", 17 | "eslint:code": "eslint src/**/**/*.js", 18 | "format:code": "prettier-eslint --write \"src/**/**/*.{js,ux,less,scss,css}\"", 19 | "prettier": "node ./command/selfCloseInputTag.js && prettier --write \"src/**/*.{js,ux,qxml}\"", 20 | "watcher": "onchange '**/*.md' \"src/**/**/*.{js,ux,less,scss,css}\" -- prettier --write {{changed}}" 21 | }, 22 | "devDependencies": { 23 | "@babel/runtime": "^7.12.13", 24 | "address": "^1.1.0", 25 | "babel-loader": "^8.0.6", 26 | "colors": "^1.3.3", 27 | "eslint-config-prettier": "^6.1.0", 28 | "eslint-plugin-prettier": "^3.1.0", 29 | "husky": "^3.0.4", 30 | "less": "^3.10.1", 31 | "less-loader": "^5.0.0", 32 | "lint-staged": "^9.2.3", 33 | "onchange": "^6.0.0", 34 | "opn": "^6.0.0", 35 | "portfinder": "^1.0.23", 36 | "prettier": "^1.18.2", 37 | "prettier-plugin-ux": "0.3.0", 38 | "webpack": "^5.24.0" 39 | }, 40 | "husky": { 41 | "hooks": { 42 | "pre-commit": "yarn run precommit-msg && lint-staged" 43 | } 44 | }, 45 | "lint-staged": { 46 | "**/**.{ux,js,json,pcss,md,vue,less,scss}": [ 47 | "prettier --write", 48 | "git add" 49 | ] 50 | }, 51 | "prettier": { 52 | "singleQuote": true, 53 | "semi": false, 54 | "printWidth": 120, 55 | "proseWrap": "never" 56 | }, 57 | "eslintConfig": { 58 | "root": true, 59 | "env": { 60 | "node": true, 61 | "es6": true 62 | }, 63 | "rules": { 64 | "no-console": 0, 65 | "no-useless-escape": 0, 66 | "no-multiple-empty-lines": [ 67 | 2, 68 | { 69 | "max": 3 70 | } 71 | ], 72 | "prettier/prettier": [ 73 | "error", 74 | { 75 | "singleQuote": true, 76 | "semi": false, 77 | "trailingComma": "none", 78 | "bracketSpacing": true, 79 | "jsxBracketSameLine": true, 80 | "insertPragma": true, 81 | "requirePragma": false 82 | } 83 | ] 84 | }, 85 | "plugins": [], 86 | "extends": [ 87 | "plugin:prettier/recommended", 88 | "eslint:recommended" 89 | ], 90 | "parserOptions": { 91 | "ecmaVersion": 6, 92 | "parser": "babel-eslint", 93 | "sourceType": "module" 94 | } 95 | }, 96 | "eslintIgnore": [ 97 | "package.json", 98 | "src/assets/js/*.js" 99 | ], 100 | "repository": { 101 | "type": "git", 102 | "url": "git+https://github.com/nicejade/quickapp-boilerplate-template.git" 103 | }, 104 | "keywords": [ 105 | "快应用", 106 | "脚手架" 107 | ], 108 | "author": "nicejade", 109 | "license": "MIT", 110 | "dependencies": { 111 | "md5": "^2.3.0" 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/pages/About/index.ux: -------------------------------------------------------------------------------- 1 | 40 | 41 | 73 | 74 | 133 | -------------------------------------------------------------------------------- /command/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file at 6 | * https://github.com/facebookincubator/create-react-app/blob/master/LICENSE 7 | */ 8 | exports.prepareUrls = function(protocol, host, port) { 9 | const url = require('url') 10 | const chalk = require('chalk') 11 | const address = require('address') 12 | 13 | const formatUrl = hostname => 14 | url.format({ 15 | protocol, 16 | hostname, 17 | port, 18 | pathname: '/' 19 | }) 20 | const prettyPrintUrl = hostname => 21 | url.format({ 22 | protocol, 23 | hostname, 24 | port: chalk.bold(port), 25 | pathname: '/' 26 | }) 27 | 28 | const isUnspecifiedHost = host === '0.0.0.0' || host === '::' 29 | let prettyHost, lanUrlForConfig, lanUrlForTerminal 30 | if (isUnspecifiedHost) { 31 | prettyHost = 'localhost' 32 | try { 33 | // This can only return an IPv4 address 34 | lanUrlForConfig = address.ip() 35 | if (lanUrlForConfig) { 36 | // Check if the address is a private ip 37 | // https://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces 38 | if (/^10[.]|^172[.](1[6-9]|2[0-9]|3[0-1])[.]|^192[.]168[.]/.test(lanUrlForConfig)) { 39 | // Address is private, format it for later use 40 | lanUrlForTerminal = prettyPrintUrl(lanUrlForConfig) 41 | } else { 42 | // Address is not private, so we will discard it 43 | lanUrlForConfig = undefined 44 | } 45 | } 46 | } catch (_e) { 47 | // ignored 48 | } 49 | } else { 50 | prettyHost = host 51 | } 52 | const localUrlForTerminal = prettyPrintUrl(prettyHost) 53 | const localUrlForBrowser = formatUrl(prettyHost) 54 | const lanUrlForBrowser = formatUrl(lanUrlForConfig) 55 | 56 | return { 57 | lanUrlForConfig, 58 | lanUrlForTerminal, 59 | lanUrlForBrowser, 60 | localUrlForTerminal, 61 | localUrlForBrowser 62 | } 63 | } 64 | 65 | exports.startBrowserProcess = url => { 66 | const opn = require('opn') 67 | const chalk = require('chalk') 68 | const OSX_CHROME = 'google chrome' 69 | const browser = process.env.BROWSER 70 | 71 | // If we're on OS X, the user hasn't specifically 72 | // requested a different browser, we can try opening 73 | // Chrome with AppleScript. This lets us reuse an 74 | // existing tab when possible instead of creating a new one. 75 | const shouldTryOpenChromeWithAppleScript = 76 | process.platform === 'darwin' && (typeof browser !== 'string' || browser === OSX_CHROME) 77 | 78 | if (shouldTryOpenChromeWithAppleScript) { 79 | try { 80 | // Try our best to reuse existing tab 81 | // on OS X Google Chrome with AppleScript 82 | const execSync = require('child_process').execSync 83 | execSync('ps cax | grep "Google Chrome"') 84 | execSync('osascript openChrome.applescript "' + encodeURI(url) + '"', { 85 | cwd: __dirname, 86 | stdio: 'ignore' 87 | }) 88 | return true 89 | } catch (err) { 90 | console.log(`${chalk.red('✘')} Opps, Something Error:\n`, err) 91 | } 92 | } 93 | 94 | // Another special case: on OS X, check if BROWSER has been set to "open". 95 | // In this case, instead of passing `open` to `opn` (which won't work), 96 | // just ignore it (thus ensuring the intended behavior, i.e. opening the system browser): 97 | // https://github.com/facebookincubator/create-react-app/pull/1690#issuecomment-283518768 98 | if (process.platform === 'darwin' && browser === 'open') { 99 | browser = undefined 100 | } 101 | 102 | // Fallback to opn 103 | // (It will always open new tab) 104 | try { 105 | const options = { app: browser } 106 | opn(url, options).catch(() => {}) // Prevent `unhandledRejection` error. 107 | return true 108 | } catch (err) { 109 | return false 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/components/WaterWorld.ux: -------------------------------------------------------------------------------- 1 | 4 | 5 | 134 | 135 | 149 | -------------------------------------------------------------------------------- /src/pages/Main/index.ux: -------------------------------------------------------------------------------- 1 | 40 | 41 | 42 | 43 | 104 | 105 | 151 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 快应用脚手架模板 4 | 5 |
6 | 7 |

快应用脚手架模板

8 | 9 |
10 | 🔨 致力于构建更为优雅的快应用开发脚手架模板 11 |
12 | 13 |
14 | 15 |
16 | 17 | Build Status 18 | 19 | 20 | node version 21 | 22 | 23 | Prettier 24 | 25 | 26 | LICENSE 27 | 28 | 29 | Chat On My Blog 30 | 31 | Author nicejade 32 |
33 | 34 | ## 目标与哲学 35 | 36 | [快应用](https://nicelinks.site/post/5b5fb5bc615bf842b609105f)是基于手机硬件平台的新型应用形态,标准是由主流手机厂商组成的`快应用联盟`联合制定。其标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台,以平台化的生态模式对个人开发者和企业开发者全品类开放。[快应用](https://nicelinks.site/post/5b5fb5bc615bf842b609105f)具备传统 APP 完整的应用体验,`无需安装、即点即用`;`覆盖 10 亿设备`,`与操作系统深度集成,探索新型应用场景`。快应用 ── **复杂生活的简单答案,让生活更顺畅**。 37 | 38 | [快应用](https://nicelinks.site/post/5b5fb5bc615bf842b609105f)是一种新型的应用形态,由国内九大手机厂商基于硬件平台共同推出;秒开即点即用,更易于应用的传播和留存,可以为用户提供更高效的服务。在可预见的未来,其将有不错的应用场景和发展空间。此 [quickapp-boilerplate-template](https://github.com/nicejade/quickapp-boilerplate-template) 仓库的建立,旨在探索如何更为优雅的开发[快应用](https://nicelinks.site/post/5b5fb5bc615bf842b609105f),为广大`快应用开发者`提供便利和参考,尽可能提升开发效率、优化开发体验,使得可以在更短时间内,塑造出更为优质的`快应用`。关于[快应用](https://nicelinks.site/post/5b5fb5bc615bf842b609105f)开发更详细资料,可参见[快应用教程资源列表](https://github.com/nicejade/nice-front-end-tutorial/blob/master/tutorial/quickapp-tutorial.md)。 39 | 40 | ## 组织结构 41 | 42 | ``` 43 | ├── sign # 存储 rpk 包签名模块; 44 | └── src 45 | │ ├── assets # 公用的资源(images/styles/字体...) 46 | │ │ ├──images # 存储 png/jpg/svg 等公共图片资源 47 | │ │ ├──js # 存储公共 javaScript 代码资源 48 | │ │ └──styles # 存放 less/css/sass 等公共样式资源 49 | │ ├── helper # 项目自定义辅助各类工具 50 | │ │ ├──apis # 存储与后台请求接口相关(已封装好) 51 | │ │ ├──ajax.js # 对系统提供的 fetch api 进行链式封装 52 | │ │ └──utils # 存放项目所封装的工具类方法 53 | │ ├── pages # 统一存放项目页面级代码 54 | │ ├── app.ux # 应用程序代码的入口文件 55 | │ └── manifest.json # 配置快应用基本信息 56 | └── package.json # 定义项目需要的各种模块及配置信息 57 | ``` 58 | 59 | ## 如何使用 60 | 61 | ```bash 62 | git clone https://github.com/nicejade/quickapp-boilerplate-template.git 63 | cd quickapp-boilerplate-template && yarn 64 | yarn start # 推荐 ✅✅ 65 | 66 | # 或者运行以下命令 67 | yarn server & yarn watch 68 | 69 | # 或者在终端一 Tab 下运行: 70 | yarn server 71 | # 在终端另一 Tab 下运行: 72 | yarn watch 73 | 74 | # ✨ 新增「快应用」页面 75 | yarn gen YourPageName 76 | ``` 77 | 78 | 用一台 `Android` 手机,下载安装[「快应用」调试器](https://www.quickapp.cn/docCenter/post/69),打开后操作`扫码安装`,扫描如上命令生成的二维码,即可看到效果;更多讯息,请参见[快应用环境搭建](https://nice.lovejade.cn/zh/article/develop-quick-app-experience-notes.html#环境搭建)。 79 | 80 | ## 改进优势 81 | 82 | 有必要谈及的是,此项目秉承在[高效开发 Web 单页应用解决方案](https://nice.lovejade.cn/zh/article/vue-webpack-boilerplate-template.html)中所传递的理念:为**高效开发**而设计;相比于其内置的简陋 Demo,她具有以下诸多优点: 83 | 84 | - [x] **对项目结构进行优化**;如上组织结构所示,将各资源模块,更专业的分门别类,使之可以便捷的去编写、维护、查找,同时也是基于前端开发既定共识去设计,更容易为初接触者所理解 & 上手; 85 | - [x] **更优雅的处理数据请求**;采用 `Promise` 对系统内置请求 `@system.fetch` 进行封装,并抛出至全局,使得可以极简的进行链式调用,同时便捷地处理返回数据,并能够使用 `finally`,设计详情可参见[如何优雅处理「快应用」数据请求 86 | ](https://quickapp.lovejade.cn/how-to-elegantly-handle-quickapp-request/); 87 | - [x] **内置了样式处理方案**;「快应用」支持 `less`, `sass` 的预编译;这里采取 `less` 方案,并内置了部分变量,以及常用混合方法,使得可以轻松开启样式编写、复用、修改等; 88 | - [x] **封装了常用方法**;在 `helper/utils` 路径下,有对日期、字符串、系统等常用方法,分别进行封装,统一暴露给 `global.$utils`,使得维护方式更加合理且健壮,同时又可以便捷的使用,高效开发;当然,你也可以根据需要自行增删、抑或扩展; 89 | - [x] **浏览器打开调试主页二维码**;运行 `yarn start`,会启动 HTTP 调试服务器,并将该地址在**命令行终端**显示,手机端用快应用调试器扫码,即可下载并运行 rpk 包;当终端积累的信息流多了,就造成扫码不便;故增设在浏览器打开调试主页二维码;如想不使用此功能,在 _command/server.js_ 文件中,将 **autoOpenBrowser** 设置为 `false` 即可; 90 | - [x] **集成轻粒子统计分析**; [轻粒子](https://nicelinks.site/post/5bdfa8ba9fa22b1b40974f63)作为官方推荐统计方案,此脚手架已做接入;使用时只需修改 [statistics.config.js](https://github.com/nicejade/quickapp-boilerplate-template/blob/master/src/assets/js/statistics.config.js) 中的 `app_key`,为在[轻粒子](http://www.qinglizi.cn/)所申请的快应用 KEY 即可; 91 | - [x] **添加新增页面命令脚本**;使得可以一键生成新页面,只需运行:`yarn gen YourPageName` (命名推荐统一为大驼峰,将会在 `pages` 路径下新建该页面文件夹)命令即可,当然,也可以根据需要,自行定定制模板:*/command/gen/template.ux*; 92 | - [x] **集成 [Prettier](https://prettier.io/) & [Eslint](https://eslint.org/)**;在检测代码中潜在问题的同时,统一团队代码规范、风格(`js`,`less`,`scss`等),从而促使写出高质量代码,以提升工作效率(尤其针对团队开发)。 93 | - [x] **编写 [prettier-plugin-quickapp](https://github.com/nicejade/prettier-plugin-quickapp) 插件**;为快应用编写 `prettier` 插件,使其可以针对 `.ux`/`.mix` 文件也能很好地工作,从而进一步完善代码风格及规范。 94 | - [x] **新增文件监听命令**:引入 [onchange](https://github.com/Qard/onchange) 依赖来监听文件变化;使得在开发时,运行 `yarn prettier-watch` 命令,即可对所修改的 `*.md` `*.ux` `*.js` 等文件,进行 **Prettier** 格式化,从而大幅度提升编写效率。 95 | - [ ] ... ... 96 | 97 | ## 内置命令 98 | 99 | 更推荐您直接使用[快应用 IDE](https://doc.quickapp.cn/ide/new.html),具体可参见[快应用开发系列博文](https://forum.lovejade.cn/t/quickapp);如果您基于 VsCode、SublimeText 等编辑器,开发快应用,以下命令可参考。 100 | 101 | | 命令 | 描述 | 备注 | 102 | |---|---|---| 103 | | `yarn start` | 开启服务(server)和监听(watch) | 附魔[多步优化](https://nice.lovejade.cn/zh/article/quickapp-boilerplate-template.html#%E6%94%B9%E8%BF%9B%E4%BC%98%E5%8A%BF),一键开启开发,强烈推荐 ✔️| 104 | | `yarn server` | 开启服务(server) | 如不嫌麻烦,可使用,不推荐 | 105 | | `yarn watch` | 开启监听(watch) | 如不嫌麻烦,可使用,不推荐 | 106 | | `yarn build ` | 编译打包,生成 `rpk`包 | 对内置 `hap build` 命令的转接 | 107 | | `yarn release ` | 生成 `rpk`包并增加签名 | 对内置 `hap release` 命令的转接 | 108 | | `yarn gen ` | 新增「快应用」页面 | 助你高效生成页面,模版可自定义,推荐 ✓| 109 | | `yarn prettier` | 一键美化代码(js/css/less/ux) | 实在是团队开发好帮手,推荐 ✓ | 110 | | `yarn watcher` | 对变化代码文件格式、实时美化 | 极大提升代码编写效率,强烈推荐 ✔️| 111 | 112 | ## 相关链接 113 | 114 | - [**倾城之链**](https://nicelinks.site?from=github) 115 | - [About Me](https://aboutme.lovejade.cn/?from=github) 116 | - [个人博客](https://jeffjade.com/nicelinks) 117 | - [辅助博客](https://blog.lovejade.cn/) 118 | - [新浪微博](https://weibo.com/jeffjade) 119 | - [知乎主页](https://www.zhihu.com/people/yang-qiong-pu/) 120 | - [简书主页](https://www.jianshu.com/u/9aae3d8f4c3d) 121 | - [SegmentFault](https://segmentfault.com/u/jeffjade) 122 | - [Twitter](https://twitter.com/nicejadeyang) 123 | - [Facebook](https://www.facebook.com/nice.jade.yang) 124 | 125 | | 微信公众号 | 前端微信群 | 推荐 Web 应用 | 126 | | --- | --- | --- | 127 | | 😉 静晴轩 | ✨ 大前端联盟 | 🎉 倾城之链 | 128 | | ![静晴轩](https://image.nicelinks.site/qrcode_jqx.jpg) | ![倾城之链](https://image.nicelinks.site/wqycx-weixin.png?ver=1) |倾城之链| 129 | 130 | ## 许可执照 131 | 132 | [MIT](http://opensource.org/licenses/MIT) 133 | 134 | Copyright (c) 2018-present, [nicejade](https://github.com/nicejade) 135 | -------------------------------------------------------------------------------- /src/assets/js/appStatistics.min.js: -------------------------------------------------------------------------------- 1 | const config={url:"http://log.ktj.wankacn.com"};var CryptoJS=CryptoJS||function(e,t){var r={},i=r.lib={},n=function(){},s=i.Base={extend:function(e){n.prototype=this;var t=new n;return e&&t.mixIn(e),t.hasOwnProperty("init")||(t.init=function(){t.$super.init.apply(this,arguments)}),t.init.prototype=t,t.$super=this,t},create:function(){var e=this.extend();return e.init.apply(e,arguments),e},init:function(){},mixIn:function(e){for(var t in e)e.hasOwnProperty(t)&&(this[t]=e[t]);e.hasOwnProperty("toString")&&(this.toString=e.toString)},clone:function(){return this.init.prototype.extend(this)}},o=i.WordArray=s.extend({init:function(e,t){e=this.words=e||[],this.sigBytes=null!=t?t:4*e.length},toString:function(e){return(e||c).stringify(this)},concat:function(e){var t=this.words,r=e.words,i=this.sigBytes;if(e=e.sigBytes,this.clamp(),i%4)for(var n=0;n>>2]|=(r[n>>>2]>>>24-n%4*8&255)<<24-(i+n)%4*8;else if(65535>>2]=r[n>>>2];else t.push.apply(t,r);return this.sigBytes+=e,this},clamp:function(){var t=this.words,r=this.sigBytes;t[r>>>2]&=4294967295<<32-r%4*8,t.length=e.ceil(r/4)},clone:function(){var e=s.clone.call(this);return e.words=this.words.slice(0),e},random:function(t){for(var r=[],i=0;i>>2]>>>24-i%4*8&255;r.push((n>>>4).toString(16)),r.push((15&n).toString(16))}return r.join("")},parse:function(e){for(var t=e.length,r=[],i=0;i>>3]|=parseInt(e.substr(i,2),16)<<24-i%8*4;return new o.init(r,t/2)}},u=a.Latin1={stringify:function(e){var t=e.words;e=e.sigBytes;for(var r=[],i=0;i>>2]>>>24-i%4*8&255));return r.join("")},parse:function(e){for(var t=e.length,r=[],i=0;i>>2]|=(255&e.charCodeAt(i))<<24-i%4*8;return new o.init(r,t)}},h=a.Utf8={stringify:function(e){try{return decodeURIComponent(escape(u.stringify(e)))}catch(e){throw Error("Malformed UTF-8 data")}},parse:function(e){return u.parse(unescape(encodeURIComponent(e)))}},p=i.BufferedBlockAlgorithm=s.extend({reset:function(){this._data=new o.init,this._nDataBytes=0},_append:function(e){"string"==typeof e&&(e=h.parse(e)),this._data.concat(e),this._nDataBytes+=e.sigBytes},_process:function(t){var r=this._data,i=r.words,n=r.sigBytes,s=this.blockSize,a=n/(4*s);if(t=(a=t?e.ceil(a):e.max((0|a)-this._minBufferSize,0))*s,n=e.min(4*t,n),t){for(var c=0;c>>2]>>>24-n%4*8&255)<<16|(t[n+1>>>2]>>>24-(n+1)%4*8&255)<<8|t[n+2>>>2]>>>24-(n+2)%4*8&255,o=0;4>o&&n+.75*o>>6*(3-o)&63));if(t=i.charAt(64))for(;e.length%4;)e.push(t);return e.join("")},parse:function(e){var r=e.length,i=this._map;(n=i.charAt(64))&&(-1!=(n=e.indexOf(n))&&(r=n));for(var n=[],s=0,o=0;o>>6-o%4*2;n[s>>>2]|=(a|c)<<24-s%4*8,s++}return t.create(n,s)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}}(),function(e){function t(e,t,r,i,n,s,o){return((e=e+(t&r|~t&i)+n+o)<>>32-s)+t}function r(e,t,r,i,n,s,o){return((e=e+(t&i|r&~i)+n+o)<>>32-s)+t}function i(e,t,r,i,n,s,o){return((e=e+(t^r^i)+n+o)<>>32-s)+t}function n(e,t,r,i,n,s,o){return((e=e+(r^(t|~i))+n+o)<>>32-s)+t}for(var s=CryptoJS,o=(c=s.lib).WordArray,a=c.Hasher,c=s.algo,u=[],h=0;64>h;h++)u[h]=4294967296*e.abs(e.sin(h+1))|0;c=c.MD5=a.extend({_doReset:function(){this._hash=new o.init([1732584193,4023233417,2562383102,271733878])},_doProcessBlock:function(e,s){for(var o=0;16>o;o++){var a=e[c=s+o];e[c]=16711935&(a<<8|a>>>24)|4278255360&(a<<24|a>>>8)}o=this._hash.words;var c=e[s+0],h=(a=e[s+1],e[s+2]),p=e[s+3],f=e[s+4],d=e[s+5],_=e[s+6],l=e[s+7],g=e[s+8],y=e[s+9],m=e[s+10],v=e[s+11],k=e[s+12],S=e[s+13],w=e[s+14],B=e[s+15],C=t(C=o[0],P=o[1],x=o[2],b=o[3],c,7,u[0]),b=t(b,C,P,x,a,12,u[1]),x=t(x,b,C,P,h,17,u[2]),P=t(P,x,b,C,p,22,u[3]);C=t(C,P,x,b,f,7,u[4]),b=t(b,C,P,x,d,12,u[5]),x=t(x,b,C,P,_,17,u[6]),P=t(P,x,b,C,l,22,u[7]),C=t(C,P,x,b,g,7,u[8]),b=t(b,C,P,x,y,12,u[9]),x=t(x,b,C,P,m,17,u[10]),P=t(P,x,b,C,v,22,u[11]),C=t(C,P,x,b,k,7,u[12]),b=t(b,C,P,x,S,12,u[13]),x=t(x,b,C,P,w,17,u[14]),C=r(C,P=t(P,x,b,C,B,22,u[15]),x,b,a,5,u[16]),b=r(b,C,P,x,_,9,u[17]),x=r(x,b,C,P,v,14,u[18]),P=r(P,x,b,C,c,20,u[19]),C=r(C,P,x,b,d,5,u[20]),b=r(b,C,P,x,m,9,u[21]),x=r(x,b,C,P,B,14,u[22]),P=r(P,x,b,C,f,20,u[23]),C=r(C,P,x,b,y,5,u[24]),b=r(b,C,P,x,w,9,u[25]),x=r(x,b,C,P,p,14,u[26]),P=r(P,x,b,C,g,20,u[27]),C=r(C,P,x,b,S,5,u[28]),b=r(b,C,P,x,h,9,u[29]),x=r(x,b,C,P,l,14,u[30]),C=i(C,P=r(P,x,b,C,k,20,u[31]),x,b,d,4,u[32]),b=i(b,C,P,x,g,11,u[33]),x=i(x,b,C,P,v,16,u[34]),P=i(P,x,b,C,w,23,u[35]),C=i(C,P,x,b,a,4,u[36]),b=i(b,C,P,x,f,11,u[37]),x=i(x,b,C,P,l,16,u[38]),P=i(P,x,b,C,m,23,u[39]),C=i(C,P,x,b,S,4,u[40]),b=i(b,C,P,x,c,11,u[41]),x=i(x,b,C,P,p,16,u[42]),P=i(P,x,b,C,_,23,u[43]),C=i(C,P,x,b,y,4,u[44]),b=i(b,C,P,x,k,11,u[45]),x=i(x,b,C,P,B,16,u[46]),C=n(C,P=i(P,x,b,C,h,23,u[47]),x,b,c,6,u[48]),b=n(b,C,P,x,l,10,u[49]),x=n(x,b,C,P,w,15,u[50]),P=n(P,x,b,C,d,21,u[51]),C=n(C,P,x,b,k,6,u[52]),b=n(b,C,P,x,p,10,u[53]),x=n(x,b,C,P,m,15,u[54]),P=n(P,x,b,C,a,21,u[55]),C=n(C,P,x,b,g,6,u[56]),b=n(b,C,P,x,B,10,u[57]),x=n(x,b,C,P,_,15,u[58]),P=n(P,x,b,C,S,21,u[59]),C=n(C,P,x,b,f,6,u[60]),b=n(b,C,P,x,v,10,u[61]),x=n(x,b,C,P,h,15,u[62]),P=n(P,x,b,C,y,21,u[63]);o[0]=o[0]+C|0,o[1]=o[1]+P|0,o[2]=o[2]+x|0,o[3]=o[3]+b|0},_doFinalize:function(){var t=this._data,r=t.words,i=8*this._nDataBytes,n=8*t.sigBytes;r[n>>>5]|=128<<24-n%32;var s=e.floor(i/4294967296);for(r[15+(n+64>>>9<<4)]=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8),r[14+(n+64>>>9<<4)]=16711935&(i<<8|i>>>24)|4278255360&(i<<24|i>>>8),t.sigBytes=4*(r.length+1),this._process(),r=(t=this._hash).words,i=0;4>i;i++)n=r[i],r[i]=16711935&(n<<8|n>>>24)|4278255360&(n<<24|n>>>8);return t},clone:function(){var e=a.clone.call(this);return e._hash=this._hash.clone(),e}}),s.MD5=a._createHelper(c),s.HmacMD5=a._createHmacHelper(c)}(Math),function(){var e,t=CryptoJS,r=(e=t.lib).Base,i=e.WordArray,n=(e=t.algo).EvpKDF=r.extend({cfg:r.extend({keySize:4,hasher:e.MD5,iterations:1}),init:function(e){this.cfg=this.cfg.extend(e)},compute:function(e,t){for(var r=(a=this.cfg).hasher.create(),n=i.create(),s=n.words,o=a.keySize,a=a.iterations;s.length>>2]}},t.BlockCipher=a.extend({cfg:a.cfg.extend({mode:c,padding:h}),reset:function(){a.reset.call(this);var e=(t=this.cfg).iv,t=t.mode;if(this._xformMode==this._ENC_XFORM_MODE)var r=t.createEncryptor;else r=t.createDecryptor,this._minBufferSize=1;this._mode=r.call(t,this,e&&e.words)},_doProcessBlock:function(e,t){this._mode.processBlock(e,t)},_doFinalize:function(){var e=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){e.pad(this._data,this.blockSize);var t=this._process(!0)}else t=this._process(!0),e.unpad(t);return t},blockSize:4});var p=t.CipherParams=r.extend({init:function(e){this.mixIn(e)},toString:function(e){return(e||this.formatter).stringify(this)}}),f=(c=(d.format={}).OpenSSL={stringify:function(e){var t=e.ciphertext;return((e=e.salt)?i.create([1398893684,1701076831]).concat(e).concat(t):t).toString(s)},parse:function(e){var t=(e=s.parse(e)).words;if(1398893684==t[0]&&1701076831==t[1]){var r=i.create(t.slice(2,4));t.splice(0,4),e.sigBytes-=16}return p.create({ciphertext:e,salt:r})}},t.SerializableCipher=r.extend({cfg:r.extend({format:c}),encrypt:function(e,t,r,i){i=this.cfg.extend(i);var n=e.createEncryptor(r,i);return t=n.finalize(t),n=n.cfg,p.create({ciphertext:t,key:r,iv:n.iv,algorithm:e,mode:n.mode,padding:n.padding,blockSize:e.blockSize,formatter:i.format})},decrypt:function(e,t,r,i){return i=this.cfg.extend(i),t=this._parse(t,i.format),e.createDecryptor(r,i).finalize(t.ciphertext)},_parse:function(e,t){return"string"==typeof e?t.parse(e,this):e}})),d=(d.kdf={}).OpenSSL={execute:function(e,t,r,n){return n||(n=i.random(8)),e=o.create({keySize:t+r}).compute(e,n),r=i.create(e.words.slice(t),4*r),e.sigBytes=4*t,p.create({key:e,iv:r,salt:n})}},_=t.PasswordBasedCipher=f.extend({cfg:f.cfg.extend({kdf:d}),encrypt:function(e,t,r,i){return r=(i=this.cfg.extend(i)).kdf.execute(r,e.keySize,e.ivSize),i.iv=r.iv,(e=f.encrypt.call(this,e,t,r.key,i)).mixIn(r),e},decrypt:function(e,t,r,i){return i=this.cfg.extend(i),t=this._parse(t,i.format),r=i.kdf.execute(r,e.keySize,e.ivSize,t.salt),i.iv=r.iv,f.decrypt.call(this,e,t,r.key,i)}})}(),function(){for(var e=CryptoJS,t=e.lib.BlockCipher,r=e.algo,i=[],n=[],s=[],o=[],a=[],c=[],u=[],h=[],p=[],f=[],d=[],_=0;256>_;_++)d[_]=128>_?_<<1:_<<1^283;var l=0,g=0;for(_=0;256>_;_++){var y=(y=g^g<<1^g<<2^g<<3^g<<4)>>>8^255&y^99;i[l]=y,n[y]=l;var m=d[l],v=d[m],k=d[v],S=257*d[y]^16843008*y;s[l]=S<<24|S>>>8,o[l]=S<<16|S>>>16,a[l]=S<<8|S>>>24,c[l]=S,S=16843009*k^65537*v^257*m^16843008*l,u[y]=S<<24|S>>>8,h[y]=S<<16|S>>>16,p[y]=S<<8|S>>>24,f[y]=S,l?(l=m^d[d[d[k^m]]],g^=d[d[g]]):l=g=1}var w=[0,1,2,4,8,16,32,64,128,27,54];r=r.AES=t.extend({_doReset:function(){for(var e=(r=this._key).words,t=r.sigBytes/4,r=4*((this._nRounds=t+6)+1),n=this._keySchedule=[],s=0;s>>24]<<24|i[o>>>16&255]<<16|i[o>>>8&255]<<8|i[255&o]):(o=i[(o=o<<8|o>>>24)>>>24]<<24|i[o>>>16&255]<<16|i[o>>>8&255]<<8|i[255&o],o^=w[s/t|0]<<24),n[s]=n[s-t]^o}for(e=this._invKeySchedule=[],t=0;tt||4>=s?o:u[i[o>>>24]]^h[i[o>>>16&255]]^p[i[o>>>8&255]]^f[i[255&o]]},encryptBlock:function(e,t){this._doCryptBlock(e,t,this._keySchedule,s,o,a,c,i)},decryptBlock:function(e,t){var r=e[t+1];e[t+1]=e[t+3],e[t+3]=r,this._doCryptBlock(e,t,this._invKeySchedule,u,h,p,f,n),r=e[t+1],e[t+1]=e[t+3],e[t+3]=r},_doCryptBlock:function(e,t,r,i,n,s,o,a){for(var c=this._nRounds,u=e[t]^r[0],h=e[t+1]^r[1],p=e[t+2]^r[2],f=e[t+3]^r[3],d=4,_=1;_>>24]^n[h>>>16&255]^s[p>>>8&255]^o[255&f]^r[d++],g=i[h>>>24]^n[p>>>16&255]^s[f>>>8&255]^o[255&u]^r[d++],y=i[p>>>24]^n[f>>>16&255]^s[u>>>8&255]^o[255&h]^r[d++];f=i[f>>>24]^n[u>>>16&255]^s[h>>>8&255]^o[255&p]^r[d++],u=l,h=g,p=y}l=(a[u>>>24]<<24|a[h>>>16&255]<<16|a[p>>>8&255]<<8|a[255&f])^r[d++],g=(a[h>>>24]<<24|a[p>>>16&255]<<16|a[f>>>8&255]<<8|a[255&u])^r[d++],y=(a[p>>>24]<<24|a[f>>>16&255]<<16|a[u>>>8&255]<<8|a[255&h])^r[d++],f=(a[f>>>24]<<24|a[u>>>16&255]<<16|a[h>>>8&255]<<8|a[255&p])^r[d++],e[t]=l,e[t+1]=g,e[t+2]=y,e[t+3]=f},keySize:8});e.AES=t._createHelper(r)}(),CryptoJS.pad.ZeroPadding={pad:function(e,t){var r=4*t;e.clamp(),e.sigBytes+=r-(e.sigBytes%r||r)},unpad:function(e){for(var t=e.words,r=e.sigBytes-1;!(t[r>>>2]>>>24-r%4*8&255);)r--;e.sigBytes=r+1}};import storage from"@system.storage";import nativeFetch from"@system.fetch";import device from"@system.device";import network from"@system.network";import account from"@service.account";import shortcut from"@system.shortcut";import md5 from"md5";import app from"@system.app";import router from"@system.router";import APP_CONFIG from"./statistics.config";!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.APP_STATISTICS=t()}(this,function(){"use strict";function e(e){let t="";for(let r in e)t+=r+"="+e[r]+"&";return t=t.substring(0,t.length-1)}function t(e){return JSON.stringify(e)}function r(e,t){let r=md5("huanju@quickapp"+t).substring(16).toLowerCase(),i=CryptoJS.enc.Latin1.parse(r),n=CryptoJS.enc.Latin1.parse("2018080716102000");return CryptoJS.AES.encrypt(e,i,{iv:n,mode:CryptoJS.mode.CBC,padding:CryptoJS.pad.ZeroPadding}).toString()}function i(){return(268435456*(1+Math.random())|0).toString(16)}function n(e){let t=/^[0-9a-zA-Z_\u4e00-\u9fa5]{0,255}$/,r=0;if("string"==typeof e&&t.test(e))return!0;if(function(e){if("object"!=typeof e||null==e)return!1;if(null===Object.getPrototypeOf(e))return!0;let t=e;for(;null!==Object.getPrototypeOf(t);)t=Object.getPrototypeOf(t);return Object.getPrototypeOf(e)===t}(e)){for(let i in e){let n=e[i];if("string"!=typeof i&&"number"!=typeof i||!/^[0-9a-zA-Z_\u4e00-\u9fa5]{1,255}$/.test(i))return!1;if("string"!=typeof n&&"number"!=typeof n||!t.test(n))return!1;r++}return!(r>100)}return!1}const s={get:function(e){let{url:t,timeout:r}=e,i=new Promise((e,r)=>{try{nativeFetch.fetch({url:config.url+t,success:function(t){e(t)},fail:function(t,r){e(t)}})}catch(e){}}),n=new Promise((e,t)=>{setTimeout(()=>{e({code:204,massage:"Request timed out"})},r||3e3)});return Promise.race([n,i])}};function o(e){return s.get({url:"/a.gif?"+e})}function a(...e){return new Promise((t,r)=>{try{storage.set({key:e[0],value:e[1]||"",success:function(e){t(!0)},fail:function(e){t(!1)}})}catch(e){t(!1)}})}function c(e){return new Promise((t,r)=>{try{storage.get({key:e,success:function(e){t(e)},fail:function(e,r){t(!1)}})}catch(e){t(!1)}})}const u={deviceIds:()=>new Promise((e,t)=>{try{device.getId({type:["device","mac"],success:function(t){e(t)},fail:function(t,r){e({})}})}catch(t){e({})}}),getUserId:()=>new Promise((e,t)=>{try{device.getUserId({success:function(t){e(t)},fail:function(t,r){e({})}})}catch(t){e({})}}),deviceInfos:()=>new Promise((e,t)=>{try{device.getInfo({success:function(t){e(t)},fail:function(){e({})}})}catch(t){e({})}}),netType:()=>new Promise((e,t)=>{try{network.getType({success:function(t){e(t)},fail:function(){e({})}})}catch(t){e({})}}),has_shortcut:()=>new Promise((e,t)=>{try{shortcut.hasInstalled({success:function(t){e({has_icon:t?1:0})},fail:function(){e({has_icon:0})}})}catch(t){e({has_icon:0})}})},h={sdk_version:"1.3.0.0",debug:0},p="_SD_BD_CUID_",f="_SD_BD_ERR_MSG_INFO_",d={has_init:!1,has_cuid_storage:!1,has_request_id_storage:!1,has_open_log:!1};const _={device:(e,t)=>({package:e.package||"",channel:e.channel||"",name:e.name||"",svr:e.versionName||"",client_id:r(e.device||"",t),info_ma:r(e.mac||"",t),os_id:r(e.userId||"",t),make:(e.brand||"").toLowerCase(),manufacturer:(e.manufacturer||"").toLowerCase(),model:(e.model||"").toLowerCase(),product:(e.product||"").toLowerCase(),os_type:e.osType||"",ovr:e.osVersionName||"",pla_ver:e.platformVersionName||"",lan:(e.language||"").toLowerCase(),region:(e.region||"").toLowerCase(),px:`${e.screenWidth||""}*${e.screenHeight||""}`,net:e.type||"",has_icon:e.has_icon||0}),page:(e={})=>({page_name:"",page_path:"",sta_time:"",end_time:"",duration:"",is_entry:0,...e}),public:e=>({app_id:APP_CONFIG.app_key||"",cuid:e.cuid,req_id:e.req,en_code:"cuid",action:2})},l=new class{constructor(){this.state={data:null,page:null,is_entry:1,cuid:"",req:"",log_list:[]},this.init=this.init.bind(this),this.page_stat=this.page_stat.bind(this),this.page_end=this.page_end.bind(this),this.merge_datas=this.merge_datas.bind(this),this.save_to_queue=this.save_to_queue.bind(this),this.handle_submit=this.handle_submit.bind(this),this.send_queue=this.send_queue.bind(this),this.event_log=this.event_log.bind(this),this.err_report=this.err_report.bind(this)}init(e){d.has_init=!0;let r={},n=e||{_def:{}};n._def=n._def||{manifest:{}};let s,{_def:{manifest:o}}=n;try{s=app.getInfo()}catch(e){let r={err_msg:t(e.stack)||"",err_site:"app.getInfo"};l.err_report(r)}r.package=o.package||s.packageName,r.versionName=o.versionName||s.versionName,r.minPlatformVersion=o.minPlatformVersion||"",r.name=o.name||s.name;try{r.channel=account.getProvider()}catch(e){let r={err_msg:t(e.stack)||"",err_site:"account.getProvider"};l.err_report(r)}Promise.all([u.deviceInfos(),u.getUserId(),u.netType(),u.has_shortcut()]).then(e=>{let t=Object.assign(r,...e),n={};c(p).then(e=>{e?n.cuid=e:(n.cuid=function(e){let t=e||i();return md5(Date.now()+"-"+i()+"-"+i()+"-"+i()+"-"+t)}(t.userId||""),a(p,n.cuid).then(e=>{e&&(d.has_cuid_storage=!0)})),this.state.cuid=n.cuid,n.req=this.state.req=function(e){let t=Date.now()+i()+i()+(e||i());return md5(t)}(n.cuid),this.state.data={..._.device(t,this.state.cuid),..._.public(n)}})})}page_stat(e){let r,i;try{let e=router.getState()||{};r=e.name,i=e.path}catch(e){let r={err_msg:t(e.stack)||"",err_site:"router.getState"};l.err_report(r)}this.state.page=_.page({sta_time:Date.now(),page_name:r||"",page_path:i||"",is_entry:this.state.is_entry||0}),this.state.is_entry=0,d.has_open_log=!0}page_end(e){if(!this.state.cuid||!this.state.data)return;let t=Date.now();if(this.state.page)this.state.page.duration=t-this.state.page.sta_time,this.state.page.end_time=t,this.handle_submit();else{let e={err_msg:`this.state.page is ${this.state.page}`,err_site:"get_page_data"};l.err_report(e)}}merge_datas(){return{...this.state.data||{},...this.state.page||{},...h}}handle_submit(t={}){let r=e({...this.merge_datas(),...t});o(r).then(e=>{200==e.code?this.send_queue():this.save_to_queue(r)}).catch(e=>{this.save_to_queue(r)})}save_to_queue(e){let t=this.state.log_list&&this.state.log_list.length;return t&&t<50&&this.state.log_list.push(`${e}&retry=1`),this.state.log_list}send_queue(){let e=[...this.state.log_list];this.state.log_list=[],e.forEach(e=>{o(e).then(t=>{200!=t.code&&this.save_to_queue(e)}).catch(t=>{this.save_to_queue(e)})})}event_log(e){this.handle_submit({...e,action:"3"})}err_report(r){let i=e({...this.merge_datas(),...r,app_id:APP_CONFIG.app_key||"",action:"9"});c(f).then(e=>{let r=e,n=(new Date).getDate(),o=0;if(r)try{r=JSON.parse(r)&&JSON.parse(r)}catch(e){r={day:n,len:0}}(r&&r.day)==n&&(o=Number(r.len)+1),o<=50&&function(e){return s.get({url:"/e.gif?"+e})}(i).then(e=>{a(f,t({day:n,len:o})).then()})}).catch()}};const g={open_app(e){try{if(!APP_CONFIG.app_key)return void console.error("Not configured app_key!");l.init(e)}catch(e){let r={err_msg:t(e.stack)||"",err_site:"open_app"};l.err_report(r)}},page_show(e){try{d.has_init&&l.page_stat(e)}catch(e){let r={err_msg:t(e.stack)||"",err_site:"page_show"};l.err_report(r)}},page_hide(e){try{d.has_init&&d.has_open_log&&l.page_end(e)}catch(e){let r={err_msg:t(e.stack)||"",err_site:"open_app"};l.err_report(r)}d.has_open_log=!1},track_event(e,r){try{if(!APP_CONFIG.app_key)return void console.error("Not configured app_key!");let i=null==r?"":r;if(!function(e){return"string"==typeof e&&""!==e&&/^[0-9a-zA-Z_\u4e00-\u9fa5]{1,255}$/.test(e)}(e))return void console.error('"event error": please check track_event id. id should be "string" and not null.');if(!n(i))return void console.error('"event error": please check track_event parameter. parameter should be "string" or "object"');l.event_log({ev_id:e,ev_args:"string"==typeof i?i:JSON.stringify(i)})}catch(e){console.log("err",e);let r={err_msg:t(e.stack)||"",err_site:"track_event"};l.err_report(r)}}},y=global.__proto__||global;return y.APP_STATISTICS={app_sta_init:g.open_app,page_show:g.page_show,page_hide:g.page_hide,track_event:g.track_event},y.Custom_page=function(e){let t=e.onShow,r=e.onHide;return e.onShow=function(){g.page_show(this),t&&t.call(this)},e.onHide=function(){g.page_hide(this),r&&r.call(this)},e},{app_sta_init:g.open_app,page_show:g.page_show,page_hide:g.page_hide,track_event:g.track_event}}); --------------------------------------------------------------------------------