├── .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 |
2 |
7 |
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 |
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 |
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 |
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 |
2 |
3 |
4 |
5 |
6 |
{{ name }}
7 |
8 |
9 | 发现 | 学习 |
10 | 分享
11 |
12 |
13 |
14 | 快应用
15 | 是移动互联网新型应用生态,与手机系统深度整合,为用户提供更加场景化的体验。具备传统APP完整的应用体验,但无需安装、即点即用。
16 |
17 |
18 |
19 | 服务类型 探究尝试类
20 |
21 |
22 |
主体信息
23 |
24 | 晚晴幽草轩出品
26 |
27 |
28 |
29 |
推荐应用
30 |
31 | 倾城之链
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
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 |
2 |
3 |
4 |
5 |
134 |
135 |
149 |
--------------------------------------------------------------------------------
/src/pages/Main/index.ux:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
探索更广阔的世界,为您
16 |
在这个信息化的世界,海量的讯息可能让您不知所措;担心错过她而努力汲取的同时,却可能错过更多;
19 |
「倾城之链」的存在,即是为您解决这种困扰;在这里,您可以浏览全球各类智慧的结晶;
23 |
丰富视野的同时,可以标注抑或分享您喜欢的站点,从而为更多挖掘讯息的人提供建设性参考。
24 |
25 |
26 |
分享,为您所欢喜的网站
27 |
在当今这信息化时代,即便是再小的个体,也当有自己的品牌。然而,独立的才是自己的。
28 |
「倾城之链」作为一个开放平台,鼓励您创造属于您的个人品牌,烙印着自己的风格,替自己代言、发声。
32 |
如果您已经这样做了,您可以尽情分享在这里,让更多人知道,且从中受益。
33 |
当然,您也可以分享出您欢喜的其他有意思站点,让您的见识惠及更多人。
34 |
35 |
36 | 已经收录优质网站个数 {{ totalLinksCount }}
37 |
38 |
39 |
40 |
41 |
42 |
43 |
104 |
105 |
151 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
6 |
7 | 快应用脚手架模板
8 |
9 |
10 | 🔨 致力于构建更为优雅的
快应用开发脚手架模板
11 |
12 |
13 |
14 |
15 |
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 | |  |  |
|
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}});
--------------------------------------------------------------------------------