├── src ├── assets │ └── .gitkeep ├── components │ └── .gitkeep ├── pages │ ├── index │ │ ├── index.json │ │ ├── index.scss │ │ ├── index.wxml │ │ └── index.ts │ └── logs │ │ ├── logs.json │ │ ├── logs.scss │ │ ├── logs.ts │ │ └── logs.wxml ├── app.scss ├── index.d.ts ├── styles │ ├── core │ │ ├── core.scss │ │ ├── base.scss │ │ └── weui.scss │ ├── index.scss │ ├── mixin │ │ ├── _core.scss │ │ ├── _ellipsis.scss │ │ ├── _flex.scss │ │ ├── _loading.scss │ │ └── _triangle.scss │ └── theme │ │ ├── _size.scss │ │ ├── _color.scss │ │ └── _core.scss ├── store │ ├── getters │ │ ├── config.ts │ │ ├── index.ts │ │ └── account.ts │ ├── state │ │ ├── account.ts │ │ ├── config.ts │ │ └── index.ts │ ├── mutations │ │ ├── index.ts │ │ ├── config.ts │ │ └── account.ts │ ├── actions │ │ ├── index.ts │ │ ├── config.ts │ │ └── account.ts │ └── index.ts ├── wxs │ ├── index.wxs │ └── utils.wxs ├── libs │ ├── base64.d.ts │ ├── utils │ │ ├── validate.ts │ │ ├── uri.ts │ │ ├── _array.ts │ │ ├── index.ts │ │ ├── _event.ts │ │ ├── _string.ts │ │ └── _date.ts │ ├── wx │ │ ├── api │ │ │ ├── index.ts │ │ │ ├── api.d.ts │ │ │ └── api.ts │ │ ├── index.d.ts │ │ ├── httpClient │ │ │ ├── httpClient.d.ts │ │ │ └── index.ts │ │ ├── watch │ │ │ ├── page.ts │ │ │ └── index.ts │ │ ├── store │ │ │ ├── store.d.ts │ │ │ ├── store.ts │ │ │ └── index.ts │ │ ├── utils.ts │ │ └── log │ │ │ ├── log.d.ts │ │ │ ├── apiLog.ts │ │ │ └── index.ts │ ├── typings │ │ └── wx │ │ │ ├── lib.wx.custom.d.ts │ │ │ ├── index.d.ts │ │ │ ├── lib.wx.component.d.ts │ │ │ ├── lib.wx.page.d.ts │ │ │ ├── lib.wx.app.d.ts │ │ │ └── lib.wx.cloud.d.ts │ ├── touch.ts │ ├── wx-charts.d.ts │ └── base64.js ├── wxml │ └── avatar.wxml ├── config │ ├── index.ts │ ├── theme.ts │ ├── API │ │ ├── account.ts │ │ └── api.d.ts │ ├── typing │ │ ├── process.d.ts │ │ └── API.d.ts │ ├── app.ts │ └── route.ts ├── services │ ├── accountService.ts │ ├── BASE_SERVICE.ts │ └── index.ts ├── app.json ├── app.d.ts ├── app.ts └── utils │ ├── tools.ts │ └── index.ts ├── .stylelintignore ├── _config.yml ├── config ├── dev.env.js ├── prod.env.js └── index.js ├── readme.md ├── .gitignore ├── .editorconfig ├── tsconfig.json ├── project.config.json ├── .babelrc ├── .stylelintrc ├── .eslintrc ├── package.json └── tslint.json /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | weui.scss -------------------------------------------------------------------------------- /src/pages/index/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-architect -------------------------------------------------------------------------------- /src/app.scss: -------------------------------------------------------------------------------- 1 | @import "./styles/core/core"; 2 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/styles/core/core.scss: -------------------------------------------------------------------------------- 1 | @import 'base'; 2 | @import 'weui'; 3 | -------------------------------------------------------------------------------- /src/pages/logs/logs.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "查看启动日志" 3 | } -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import 'theme/core'; 2 | @import 'mixin/core'; 3 | -------------------------------------------------------------------------------- /src/store/getters/config.ts: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | } as wxStore.getterFunc; 4 | -------------------------------------------------------------------------------- /src/wxs/index.wxs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | utils: require('./utils.wxs') 3 | } -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ENV: '"development"', 3 | BASE_API: "'https://your host'" 4 | }; 5 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ENV: '"production"', 3 | BASE_API: "'https://your host'" 4 | }; 5 | -------------------------------------------------------------------------------- /src/styles/mixin/_core.scss: -------------------------------------------------------------------------------- 1 | @import 'flex'; 2 | @import 'ellipsis'; 3 | @import 'triangle'; 4 | @import 'loading'; 5 | -------------------------------------------------------------------------------- /src/libs/base64.d.ts: -------------------------------------------------------------------------------- 1 | declare class Base64 { 2 | encode(str: string): string 3 | decode(base64: string): string 4 | } 5 | -------------------------------------------------------------------------------- /src/wxml/avatar.wxml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | 1.微信小程序代码打包压缩、优化、css预处理、babel、typescript等 3 | 4 | 2.支持引入微信小程序NPM组件包(无需官方npm的支持) 5 | 6 | 3.完全原汁原味的小程序开发,没有引入其他的语法 7 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | import APP from './app'; 2 | import THEME from './theme'; 3 | 4 | export default { 5 | APP, 6 | THEME 7 | }; 8 | -------------------------------------------------------------------------------- /src/config/theme.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | cancelColor: '#394256', 3 | confirmColor: '#2384E8', 4 | toastDuration: 2000, 5 | toastMask: true 6 | }; 7 | -------------------------------------------------------------------------------- /src/store/state/account.ts: -------------------------------------------------------------------------------- 1 | export interface IAccount { 2 | token?: string; 3 | } 4 | 5 | export const account: IAccount = { 6 | token: '', 7 | }; 8 | -------------------------------------------------------------------------------- /src/store/state/config.ts: -------------------------------------------------------------------------------- 1 | export const config = { 2 | networkStatus: { 3 | isConnected: true, 4 | networkType: '4g' 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /src/store/mutations/index.ts: -------------------------------------------------------------------------------- 1 | import account from './account'; 2 | import config from './config'; 3 | 4 | export default { 5 | config, 6 | account, 7 | }; 8 | -------------------------------------------------------------------------------- /src/store/actions/index.ts: -------------------------------------------------------------------------------- 1 | import account from './account'; 2 | import config from './config'; 3 | 4 | export default { 5 | config, 6 | account, 7 | }; 8 | -------------------------------------------------------------------------------- /src/store/getters/index.ts: -------------------------------------------------------------------------------- 1 | import account from './account'; 2 | import config from './config'; 3 | 4 | export default { 5 | config, 6 | account, 7 | }; 8 | -------------------------------------------------------------------------------- /src/styles/theme/_size.scss: -------------------------------------------------------------------------------- 1 | $gap-tiny: 5rpx; 2 | $gap-small: 10rpx; 3 | $gap-normal: 20rpx; 4 | $gap-big: 40rpx; 5 | $gap-gaint: 80rpx; 6 | $content-padding: 24rpx; 7 | -------------------------------------------------------------------------------- /src/store/state/index.ts: -------------------------------------------------------------------------------- 1 | import { account } from './account'; 2 | import { config } from './config'; 3 | 4 | export default { 5 | config, 6 | account, 7 | }; 8 | -------------------------------------------------------------------------------- /src/config/API/account.ts: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | Login: { 4 | url: 'account/public/miniappLogin', 5 | method: 'post', 6 | isAuth: false 7 | } 8 | } as IApi.module; 9 | -------------------------------------------------------------------------------- /src/libs/utils/validate.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | validateMobile(mobile: string): boolean { 3 | const regexp = /^1[3|4|5|6|7|8|9]\d{9}$/; 4 | return regexp.test(mobile); 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /src/pages/logs/logs.scss: -------------------------------------------------------------------------------- 1 | .log { 2 | &-list { 3 | display: flex; 4 | flex-direction: column; 5 | padding: 40rpx; 6 | } 7 | 8 | &-item { 9 | margin: 10rpx; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/libs/wx/api/index.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from './api'; 2 | 3 | export function createWxApi(options= { 4 | toastMask: true, 5 | toastDuration: 2000 6 | }) { 7 | this.wxApi = promisify(options); 8 | } 9 | -------------------------------------------------------------------------------- /src/libs/wx/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | -------------------------------------------------------------------------------- /src/services/accountService.ts: -------------------------------------------------------------------------------- 1 | 2 | import account from '@C/API/account'; 3 | import { Service } from '@S/index'; 4 | 5 | const accountService: any = new Service(account); 6 | 7 | export default accountService;​​ 8 | -------------------------------------------------------------------------------- /src/styles/mixin/_ellipsis.scss: -------------------------------------------------------------------------------- 1 | @mixin ellipse-text($line-clamp: 1) { 2 | overflow: hidden; 3 | text-overflow: ellipsis; 4 | display: -webkit-box; 5 | -webkit-box-orient: vertical; 6 | -webkit-line-clamp: $line-clamp; 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | *.suo 11 | *.ntvs* 12 | *.njsproj 13 | *.sln 14 | .awcache 15 | -------------------------------------------------------------------------------- /src/libs/wx/httpClient/httpClient.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace wxHttpClient { 2 | interface requestOptions { 3 | url: string 4 | method: string 5 | data: any 6 | header: object 7 | [key: string]: any 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/logs/logs.ts: -------------------------------------------------------------------------------- 1 | Page({ 2 | data: { 3 | logs: [] as string[] 4 | }, 5 | onLoad() { 6 | this.setData({ 7 | logs: (wx.getStorageSync('logs') || []).map((log: number) => { 8 | return log; 9 | }) 10 | }); 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /src/config/typing/process.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * process webpack环境变量 3 | */ 4 | declare interface IEnvironmentMode { 5 | ENV: 'production' | 'development'; 6 | BASE_API: string; 7 | APP_ID: number; 8 | } 9 | 10 | declare var process: { 11 | env: IEnvironmentMode 12 | }; 13 | -------------------------------------------------------------------------------- /src/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index", 4 | "pages/logs/logs" 5 | ], 6 | "window": { 7 | "backgroundTextStyle": "light", 8 | "navigationBarBackgroundColor": "#fff", 9 | "navigationBarTitleText": "Wechat", 10 | "navigationBarTextStyle": "black" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import actionModules from './actions'; 2 | import getterModules from './getters'; 3 | import mutationModules from './mutations'; 4 | import stateModules from './state'; 5 | 6 | export default { 7 | stateModules, 8 | actionModules, 9 | mutationModules, 10 | getterModules 11 | }; 12 | -------------------------------------------------------------------------------- /src/pages/logs/logs.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{index + 1}}. {{logic.utils.dateStringFormat(log)}} 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/config/typing/API.d.ts: -------------------------------------------------------------------------------- 1 | declare interface IApiModule { 2 | [action: string]: { 3 | url: string; 4 | type: 'post' | 'get' | 'put' | 'delete'; 5 | isAuth?: boolean, // check login status 6 | hasLoading?: boolean, // loading modal 7 | isLock?: boolean, 8 | headers?: object // default headers 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /src/store/actions/config.ts: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | async detectNetwork() { 4 | this.commit('networkStatus', await this.wxApi.getNetworkType()); 5 | 6 | wx.onNetworkStatusChange((res: wx.OnNetworkStatusChangeCallbackResult) => { 7 | this.commit('networkStatus', res); 8 | }); 9 | return; 10 | } 11 | } as wxStore.dispatchFunc; 12 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: 'src/app.ts', 3 | alias: { 4 | 'style': 'src/styles/index.scss', 5 | '@S': 'src/services', 6 | '@T': 'src/utils', 7 | '@C': 'src/config', 8 | '@L': 'src/libs' 9 | }, 10 | build: { 11 | env: require('./prod.env') 12 | }, 13 | dev: { 14 | env: require('./dev.env') 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /src/libs/wx/watch/page.ts: -------------------------------------------------------------------------------- 1 | import { setWatcher } from './index'; 2 | 3 | export const WatcherPage: Page.PageConstructor = opt => { 4 | const { onLoad, ...page } = opt; 5 | // @ts-ignore 6 | page.onLoad = function (options) { 7 | setWatcher.call(this); 8 | if (onLoad) { 9 | onLoad.call(this, options); 10 | } 11 | }; 12 | 13 | Page(page); 14 | }; 15 | -------------------------------------------------------------------------------- /src/libs/wx/store/store.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace wxStore { 2 | interface dispatchFunc { 3 | [funcName: string]: (state: object, params?: any) => Promise; 4 | } 5 | 6 | interface mutationFunc { 7 | [funcName: string]: (state: object, params?: any) => void; 8 | } 9 | 10 | interface getterFunc { 11 | [funcName: string]: (state: object, params?: any) => any; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/libs/typings/wx/lib.wx.custom.d.ts: -------------------------------------------------------------------------------- 1 | interface WXEventTarget { 2 | id: string; 3 | offsetLeft: number; 4 | offsetTop: number; 5 | dataset: { 6 | [key: string]: any; 7 | } 8 | } 9 | 10 | interface WXEventBasic { 11 | type: string; 12 | timeStamp: number; 13 | target: WXEventTarget; 14 | currentTarget: WXEventTarget; 15 | detail: { 16 | formId?: string; 17 | value?: any; 18 | userInfo?: Object; 19 | [key: string]: any; 20 | } 21 | } -------------------------------------------------------------------------------- /src/config/API/api.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace IApi { 2 | 3 | interface api { 4 | url: string; 5 | method: 'get' | 'post' | 'delete' | 'put'; 6 | isAuth?: boolean; // add session_id 7 | isJson?: boolean; // json or formdata 8 | isFile?: boolean; // multipart file 9 | headers?: object; // default header 10 | } 11 | 12 | interface module { 13 | [funcName: string]: api 14 | } 15 | 16 | interface modules { 17 | [moduleName: string]: module; 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare interface requestLock { 4 | status: boolean; 5 | request: Promise | null, 6 | count: number, 7 | } 8 | 9 | declare interface IApp extends App.GetApp { 10 | logger 11 | state 12 | wxApi: wxApi 13 | 14 | request_id: number 15 | requestLock: requestLock 16 | 17 | dispatch(action: string, params?: any) 18 | commit(action: string, params?: any) 19 | getter(action: string, params?: any) 20 | } 21 | 22 | declare function getApp(): IApp; 23 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | 9 | [*.{js,jsx,ts,tsx}] 10 | indent_style = space 11 | indent_size = 2 12 | trim_trailing_whitespace = true 13 | 14 | [*.{html,hbr,rt,css,less,scss}] 15 | indent_style = tab 16 | tab_width = 2 17 | trim_trailing_whitespace = true 18 | 19 | [*.json] 20 | indent_style = space 21 | indent_size = 2 22 | trim_trailing_whitespace = true 23 | 24 | [*.md] 25 | trim_trailing_whitespace = false 26 | indent_size = 2 27 | -------------------------------------------------------------------------------- /src/pages/index/index.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | height: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | justify-content: space-between; 7 | padding: 200rpx 0; 8 | box-sizing: border-box; 9 | } 10 | 11 | .userinfo { 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | 16 | &-avatar { 17 | width: 128rpx; 18 | height: 128rpx; 19 | margin: 20rpx; 20 | border-radius: 50%; 21 | } 22 | 23 | &-nickname { 24 | color: #aaa; 25 | } 26 | } 27 | 28 | .usermotto { 29 | margin-top: 200px; 30 | } 31 | -------------------------------------------------------------------------------- /src/store/getters/account.ts: -------------------------------------------------------------------------------- 1 | import Conf from '../../config'; 2 | 3 | export default { 4 | getToken(state: any) { 5 | return state.token; 6 | }, 7 | getSellerInfo(state: any) { 8 | if (Object.keys(state.sellerInfo).length) { 9 | return state.sellerInfo; 10 | } 11 | return null; 12 | }, 13 | getCustomerInfo(state: any) { 14 | if (Object.keys(state.customerInfo).length) { 15 | return state.customerInfo; 16 | } 17 | return null; 18 | } 19 | }as wxStore.getterFunc; 20 | -------------------------------------------------------------------------------- /src/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{userInfo.nickName}} 8 | 9 | 10 | 11 | {{motto}} 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/styles/theme/_color.scss: -------------------------------------------------------------------------------- 1 | $black: #000; 2 | $white: #fff; 3 | $grey-light: #e9e9e9; 4 | $grey: #b4b4b4; 5 | $grey-darken: #a3a7ac; 6 | $grey-ink: #9da2a6; 7 | $dark: #3b444d; 8 | $page-background: #f8f8f8; 9 | $gold: #ffd142; 10 | $orange-lighten: #ff9f00; 11 | $orange: #ff7500; 12 | $orange-darken: #e83f44; 13 | $primary: #3685e2; 14 | $mainColor: #2384e8; 15 | $primary-disable: #5fa7f2; 16 | 17 | $theme-color: #656ce8; 18 | $gary-text: #8b8fae; 19 | 20 | $line-color: #dfe3f4; 21 | $black-text: #393a46; 22 | 23 | $time-color: #cfd2e3; 24 | 25 | $dot-color: #ff5d5d; 26 | -------------------------------------------------------------------------------- /src/store/mutations/config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | setAppConfig(state: any, action: any) { 3 | Object.assign(state, action); 4 | return state; 5 | }, 6 | networkStatus(state: any, action: any) { 7 | console.warn('[network.networkType]: ', action.networkType); 8 | 9 | const { networkType } = action; 10 | let { isConnected } = action; 11 | if (isConnected === undefined) { 12 | isConnected = networkType === 'none' ? false : true; 13 | } 14 | 15 | state.networkStatus = { isConnected, networkType }; 16 | return state; 17 | } 18 | } as wxStore.mutationFunc; 19 | -------------------------------------------------------------------------------- /src/styles/theme/_core.scss: -------------------------------------------------------------------------------- 1 | @import 'color'; 2 | @import 'size'; 3 | 4 | $title-color: lighten($black, 0.75); 5 | $body-color: lighten($black, 0.65); 6 | $caption-color: lighten($black, 0.43); 7 | $disabled-color: lighten($black, 0.25); 8 | $link: #108ee9; 9 | $line: #edeced; 10 | $border-color: #e9e9e9; 11 | $base-color: #838af5; 12 | 13 | $font-color-gray: #737373; 14 | 15 | $BG_BODY: #fff; 16 | $BG_LINE: rgba(2, 0, 0, 0); 17 | $T1: rgba(9, 0, 0, 0); 18 | $T2: rgba(5, 0, 0, 0); 19 | $T3: rgba(2, 0, 0, 0); 20 | $TW: $orange; 21 | $TD: $orange-darken; 22 | 23 | $poster-title: $dark; 24 | $poster-summary: #9ca1a5; 25 | 26 | $BG_GRAY: #f5f6fa; 27 | -------------------------------------------------------------------------------- /src/config/app.ts: -------------------------------------------------------------------------------- 1 | 2 | interface IConfig { 3 | wxAppId: string; 4 | } 5 | 6 | // app custom config 7 | const config: IConfig = { 8 | wxAppId: wx.getAccountInfoSync().miniProgram.appId 9 | }; 10 | 11 | // app version 12 | const version: string = 'quickstart-miniprogram-1.0.0'; 13 | 14 | function getCurrentConfig(): IConfig & IEnvironmentMode { 15 | return Object.assign({}, config, process.env); 16 | } 17 | 18 | const currentConfig = getCurrentConfig(); 19 | console.warn('App Config', currentConfig); 20 | 21 | const isProd = currentConfig.ENV === 'production'; 22 | 23 | export default { 24 | version, 25 | isProd, 26 | config: currentConfig, 27 | }; 28 | -------------------------------------------------------------------------------- /src/libs/utils/uri.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | /** 3 | * 返回 url 4 | * @param {string} url 5 | * @param {object} data 描述url需要的query 6 | */ 7 | getUrlQuery(url: string, data: object = {}) { 8 | const hasQuery = data instanceof Object && Object.keys(data).length; 9 | return { 10 | url: hasQuery ? `${url}?${this.obj2urlquery(data)}` : url 11 | }; 12 | }, 13 | 14 | obj2urlquery(params: object = {}): string { 15 | if (Object.keys(params).length) { 16 | return Object.keys(params).map(key => 17 | `${encodeURIComponent(key)}=${encodeURIComponent((params as any)[key])}` 18 | ).join('&'); 19 | } 20 | return ''; 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /src/styles/core/base.scss: -------------------------------------------------------------------------------- 1 | body, 2 | div, 3 | dl, 4 | dt, 5 | dd, 6 | ul, 7 | ol, 8 | li, 9 | h1, 10 | h2, 11 | h3, 12 | h4, 13 | h5, 14 | h6, 15 | pre, 16 | code, 17 | form, 18 | fieldset, 19 | legend, 20 | input, 21 | textarea, 22 | p, 23 | blockquote, 24 | th, 25 | td, 26 | hr, 27 | button, 28 | article, 29 | aside, 30 | details, 31 | figcaption, 32 | figure, 33 | footer, 34 | header, 35 | hgroup, 36 | menu, 37 | nav, 38 | section { 39 | margin: 0; 40 | padding: 0; 41 | box-sizing: border-box; 42 | } 43 | 44 | body { 45 | font-weight: 200; 46 | } 47 | 48 | a { 49 | outline: none; 50 | cursor: pointer; 51 | } 52 | 53 | button, 54 | input, 55 | select, 56 | textarea { 57 | font-family: inherit; 58 | font-size: inherit; 59 | color: inherit; 60 | } 61 | 62 | h1, 63 | h2, 64 | h3, 65 | h4, 66 | h5, 67 | h6 { 68 | font-weight: 300; 69 | } 70 | -------------------------------------------------------------------------------- /src/styles/mixin/_flex.scss: -------------------------------------------------------------------------------- 1 | // 水平垂直居中 2 | @mixin flex-center($display: flex, $direction: row) { 3 | @if ($display != '') { 4 | display: $display; 5 | } 6 | 7 | @else { 8 | display: flex; 9 | } 10 | 11 | @if ($direction != row) { 12 | flex-direction: $direction; 13 | } 14 | 15 | justify-content: center; 16 | align-items: center; 17 | } 18 | 19 | // 主轴居中 20 | @mixin flex-main-center($display: flex, $direction: row) { 21 | @if ($display) { 22 | display: $display; 23 | } 24 | 25 | @else { 26 | display: flex; 27 | } 28 | 29 | @if ($direction != row) { 30 | flex-direction: $direction; 31 | } 32 | 33 | justify-content: center; 34 | } 35 | 36 | // 交叉轴居中 37 | @mixin flex-cross-center($display: flex, $direction: row) { 38 | display: $display; 39 | 40 | @if ($direction != row) { 41 | flex-direction: $direction; 42 | } 43 | 44 | align-items: center; 45 | } 46 | -------------------------------------------------------------------------------- /src/libs/utils/_array.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | /** 3 | * 将数组按照chunklen大小进行分段 4 | * @param sourceArr Array 5 | * @param chunkLen number 6 | */ 7 | chunkArray(sourceArr: any[], chunkLen: number): any[] { 8 | const res: any[] = []; 9 | 10 | for (let i = 0, len = sourceArr.length; i < len; i += chunkLen) { 11 | res.push(sourceArr.slice(i, i + chunkLen)); 12 | } 13 | 14 | return res; 15 | }, 16 | 17 | unique(...arr: any[]) { 18 | return [...new Set([...arr])]; 19 | }, 20 | 21 | // 交集 22 | intersect(arr1: any[], arr2: any[]) { 23 | return this.unique(arr1).filter(v => arr2.includes(v)); 24 | }, 25 | 26 | // 差集 27 | minus(arr1: any[], arr2: any[]) { 28 | return this.unique(arr1).filter(v => !arr2.includes(v)); 29 | }, 30 | 31 | // 并集 32 | union(arr1: any[], arr2: any[]) { 33 | return this.unique(arr1, arr2); 34 | } 35 | 36 | }; 37 | -------------------------------------------------------------------------------- /src/services/BASE_SERVICE.ts: -------------------------------------------------------------------------------- 1 | import t from '@/libs/utils'; 2 | import HttpClient from '@/libs/wx/httpClient'; 3 | 4 | export class BaseService extends HttpClient { 5 | constructor(prefix: string, apis: IApi.module = {}) { 6 | super(prefix); 7 | 8 | const self = this; 9 | for (const k in apis) { 10 | const { isJson = true, isFile = false, ...item } = apis[k]; 11 | self[k] = (data: object = {}, params = {}, header = {}): Promise => { 12 | const opt: wxHttpClient.requestOptions = t.deepClone(item); 13 | opt.data = data; 14 | opt.header = Object.assign({}, opt.header, header); 15 | if (!isFile) { 16 | if (isJson) { 17 | return this.jsonRequest(opt, params); 18 | } 19 | return this.formRequest(opt, params); 20 | } 21 | return this.uploadFile(opt, params); 22 | }; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import Conf from '@/config/index'; 2 | import { createWxApi } from '@/libs/wx/api/index'; 3 | import { createLogger } from '@/libs/wx/log/index'; 4 | import { createStore } from '@/libs/wx/store/index'; 5 | import store from '@/store/index'; 6 | 7 | App({ 8 | requestLock: { 9 | status: false, 10 | request: null, 11 | count: 0 12 | }, 13 | async onLaunch(options) { 14 | try { 15 | // mount wxApi in getApp().wxApi 16 | createWxApi.call(this, Conf.THEME); 17 | // mount store in getApp().$state 18 | createStore.call(this, store); 19 | // mount logger in getApp().logger 20 | createLogger.call(this, { 21 | wxAppid: Conf.APP.config.wxAppId, 22 | version: Conf.APP.version 23 | }); 24 | // check network status 25 | await this.dispatch('detectNetwork'); 26 | } catch (error) { 27 | console.log(error); 28 | } 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es6", 4 | "skipLibCheck": true, 5 | "skipDefaultLibCheck": true, 6 | "strictNullChecks": true, 7 | "downlevelIteration": true, 8 | "noImplicitAny": false, 9 | "strict": true, 10 | "sourceMap": true, 11 | "allowJs": true, 12 | "baseUrl": "./src/", 13 | "outDir": "./dist/", 14 | "noImplicitThis": false, 15 | "removeComments": true, 16 | "lib": [ 17 | "es5", 18 | "es6", 19 | "es2015", 20 | "es7", 21 | "es2016", 22 | "es2018", 23 | "es2017" 24 | ], 25 | "paths": { 26 | "@/*": ["./*"], 27 | "@S/*": ["./services/*"], 28 | "@T/*": ["./utils/*"], 29 | "@C/*": ["./config/*"], 30 | "@L/*": ["./libs/*"] 31 | } 32 | }, 33 | "awesomeTypescriptLoaderOptions": {}, 34 | "declaration": true, 35 | "exclude": [ 36 | "node_modules" 37 | ], 38 | "include": [ 39 | "**/*.ts" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /src/libs/wx/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * eg wxPromise(wx.request) 3 | * @param func 只接收带success fail的api 4 | */ 5 | export function wxPromise(func: (_: any) => void) { 6 | return (opt?: any): Promise => { 7 | return new Promise((resolve, reject) => { 8 | func(Object.assign({}, opt, { 9 | success: data => resolve(data), 10 | fail: err => reject(err), 11 | })); 12 | }); 13 | }; 14 | } 15 | 16 | /** 17 | * format string 18 | * @param str eg:`user: {name}` 19 | * @param args eg: { name:"123" } 20 | */ 21 | export const strFormat = (str: string = '', args: any = {}): string => { 22 | for (const key in args) { 23 | const reg = new RegExp('({' + key + '})', 'g'); 24 | str = str.replace(reg, args[key]); 25 | } 26 | return str; 27 | }; 28 | 29 | /** 30 | * throw error 31 | * @param keys params Array 32 | */ 33 | export const MissingError = (...keys: string[]) => { 34 | console.error(`Missing parameters [${keys.join(',')}]`); 35 | }; 36 | -------------------------------------------------------------------------------- /project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "使用webpack和npm开发小程序", 3 | "setting": { 4 | "urlCheck": false, 5 | "es6": false, 6 | "postcss": true, 7 | "minified": true, 8 | "newFeature": true 9 | }, 10 | "miniprogramRoot": "dist/", 11 | "compileType": "miniprogram", 12 | "appid": "wxe8440a52dab9bd67", 13 | "projectname": "quickstart-miniprogram", 14 | "simulatorType": "wechat", 15 | "simulatorPluginLibVersion": {}, 16 | "condition": { 17 | "search": { 18 | "current": -1, 19 | "list": [] 20 | }, 21 | "conversation": { 22 | "current": -1, 23 | "list": [] 24 | }, 25 | "plugin": { 26 | "current": -1, 27 | "list": [] 28 | }, 29 | "game": { 30 | "currentL": -1, 31 | "list": [] 32 | }, 33 | "miniprogram": { 34 | "current": 1, 35 | "list": [ 36 | { 37 | "id": -1, 38 | "name": "index", 39 | "pathName": "pages/index/index", 40 | "query": "" 41 | }, 42 | { 43 | "id": -1, 44 | "name": "logs", 45 | "pathName": "pages/logs/logs" 46 | } 47 | ] 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/libs/wx/log/log.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace wxLog { 2 | 3 | // rid type 4 | const enum LogType { 5 | SLOW_API = 'slow_api', 6 | ERROR_API = 'error_api', 7 | SCRIPT_ERROR = 'js_error', 8 | PAGE_ERROR = 'page_error', 9 | DEVICE_ERROR = 'device_error' 10 | } 11 | 12 | // cat type 13 | const enum ErrorType { 14 | JS_ERROR = 'term_report', 15 | API_ERROR = 'term_report', 16 | NETWORK_ERROR = 'term_report' 17 | } 18 | 19 | // deviceinfo 20 | interface IAppInfo { 21 | version: string; // app version 22 | wxAppid: string; // wx app id 23 | } 24 | 25 | // log module options 26 | export interface ILoggerOptions extends IAppInfo { 27 | getLocation?: boolean; // get user loacation 28 | statShareApp?: boolean; // capture shareApp 29 | statApiSpeed?: boolean; 30 | apiMaxRequestTime?: number; 31 | } 32 | 33 | // report item 34 | interface IReportData extends IAppInfo { 35 | rid: LogType; 36 | cat: ErrorType; 37 | data: ApiLog | string; 38 | deviceInfo: wx.GetSystemInfoSyncResult; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/pages/index/index.ts: -------------------------------------------------------------------------------- 1 | import router from '@C/route'; 2 | import t from '@L/utils/index'; 3 | import { WatcherPage } from '@L/wx/watch/page'; 4 | 5 | WatcherPage({ 6 | data: { 7 | motto: '点击 “编译” 以构建', 8 | userInfo: {}, 9 | hasUserInfo: false, 10 | canIUse: wx.canIUse('button.open-type.getUserInfo'), 11 | }, 12 | 13 | watch: { 14 | userInfo(val) { 15 | console.log(val); 16 | } 17 | }, 18 | 19 | bindViewTap() { 20 | getApp().wxApi.navigateTo(router.LOGS).then(res => { 21 | console.log(res); 22 | }); 23 | }, 24 | 25 | async onLoad() { 26 | let logs: number[] = []; 27 | try { 28 | logs = await getApp().wxApi.getStorage('logs'); 29 | } catch (error) { 30 | console.log(error); 31 | } finally { 32 | logs.unshift(Date.now()); 33 | getApp().wxApi.setStorage('logs', logs); 34 | } 35 | }, 36 | 37 | getUserInfo: t.debounce(function ({ detail: { userInfo } }: WXEventBasic) { 38 | this.setData({ 39 | userInfo, 40 | hasUserInfo: true 41 | }); 42 | }, 300), 43 | }); 44 | -------------------------------------------------------------------------------- /src/config/route.ts: -------------------------------------------------------------------------------- 1 | 2 | declare interface IRouteModule { 3 | [moduleName: string]: { 4 | url: string; 5 | }; 6 | } 7 | 8 | // app pages config map 9 | const pageRoutes: IRouteModule = { 10 | INDEX: { 11 | url: 'index' 12 | }, 13 | LOGS: { 14 | url: 'logs' 15 | } 16 | }; 17 | 18 | /** 19 | * 根据 pagename 自动补全路径 20 | * (文件命名规则为 /pages/pagename/pagename.[ts/scss/json/wxml]) 21 | */ 22 | function prefixRouter(rootName: string, routesConf: IRouteModule): IRouteModule { 23 | const res: IRouteModule = {}; 24 | 25 | for (const route in routesConf) { 26 | const url = routesConf[route].url; 27 | const temp = url.split('/').filter(v => v); 28 | 29 | if (url[0] === '/' || temp[0] === rootName) { 30 | res[route] = routesConf[route]; 31 | } else { 32 | res[route] = { 33 | url: `/${rootName}/${url}/${temp[temp.length - 1]}` 34 | }; 35 | } 36 | } 37 | return res; 38 | } 39 | 40 | const routeConf = { 41 | ...prefixRouter('pages', pageRoutes) 42 | }; 43 | console.warn('Route Config ', routeConf); 44 | 45 | export default routeConf; 46 | -------------------------------------------------------------------------------- /src/styles/mixin/_loading.scss: -------------------------------------------------------------------------------- 1 | .xlading_bg { 2 | background: #000; 3 | opacity: 0.5; 4 | position: fixed; 5 | top: 0; 6 | right: 0; 7 | bottom: 0; 8 | left: 0; 9 | z-index: 301; 10 | } 11 | 12 | .xloading { 13 | overflow: hidden; 14 | display: flex; 15 | justify-content: center; 16 | align-items: center; 17 | } 18 | 19 | .xloading.full_page { 20 | position: fixed; 21 | top: 0; 22 | right: 0; 23 | bottom: 0; 24 | left: 0; 25 | z-index: 301; 26 | } 27 | 28 | .xloading::after { 29 | content: ""; 30 | display: inline-block; 31 | margin-top: 30rpx; 32 | height: 30rpx; 33 | width: 60rpx; 34 | border: 4rpx solid #00f; 35 | border-top: 0; 36 | border-radius: 0 0 30rpx 30rpx; 37 | transform-origin: top center; 38 | box-sizing: border-box; 39 | animation: spin 1s linear infinite; 40 | } 41 | 42 | @-webkit-keyframes spin { 43 | from { 44 | transform: rotate(0deg); 45 | } 46 | 47 | to { 48 | transform: rotate(360deg); 49 | } 50 | } 51 | 52 | @keyframes spin { 53 | from { 54 | transform: rotate(0deg); 55 | } 56 | 57 | to { 58 | transform: rotate(360deg); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/libs/utils/index.ts: -------------------------------------------------------------------------------- 1 | import _array from './_array'; 2 | import _date from './_date'; 3 | import _event from './_event'; 4 | import _string from './_string'; 5 | import uri from './uri'; 6 | 7 | export default { 8 | ..._array, 9 | ..._date, 10 | ..._string, 11 | ..._event, 12 | ...uri, 13 | 14 | deepClone(obj: any): any { 15 | if (typeof obj !== 'object' || obj === null) { 16 | return obj; 17 | } 18 | 19 | if (Array.isArray(obj)) { 20 | return obj.map(v => { 21 | if (typeof v === 'object' && v !== null) { return this.deepClone(v); } else { return v; } 22 | }); 23 | } else { 24 | const newObj: any = {}; 25 | 26 | Object.keys(obj).forEach(v => { 27 | if (typeof obj[v] === 'object' && obj[v] !== null) { 28 | newObj[v] = this.deepClone(obj[v]); 29 | } else { 30 | newObj[v] = obj[v]; 31 | } 32 | }); 33 | 34 | return newObj; 35 | } 36 | }, 37 | 38 | convertToBoolean(input: string): boolean | undefined { 39 | try { 40 | return JSON.parse(input); 41 | } catch (e) { 42 | return undefined; 43 | } 44 | }, 45 | 46 | }; 47 | -------------------------------------------------------------------------------- /src/libs/wx/api/api.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare interface wxApi { 4 | login(): Promise 5 | getSetting(): Promise 6 | getNetworkType(): Promise 7 | showLoading(options?: wx.ShowLoadingOption): Promise 8 | hideLoading(): Promise 9 | showToast(options?: wx.ShowToastOption): Promise 10 | showModal(options?: wx.ShowModalOption): Promise 11 | setStorage(key: string, data: any): Promise 12 | getStorage(key: string): Promise 13 | removeStorage(key: string): Promise 14 | navigateTo(options: wx.NavigateToOption, data: object = {}): Promise 15 | redirectTo(options: wx.NavigateToOption, data: object = {}): Promise 16 | switchTab(options: wx.NavigateToOption, data: object = {}): Promise 17 | reLaunch(options: wx.NavigateToOption, data: object = {}): Promise 18 | setNavigationBarTitle(title: string): Promise 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/tools.ts: -------------------------------------------------------------------------------- 1 | interface IAnyObject { 2 | [key: string]: any; 3 | } 4 | 5 | export default { 6 | 7 | generateRequestID(): string { 8 | let num: number = 0; 9 | const rando = (m: number): number => { 10 | let num = ''; 11 | for (let i = 0; i < m; i++) { 12 | const val = parseInt((Math.random() * 10).toString(), 10); 13 | if (i === 0 && val === 0) { 14 | i--; 15 | continue; 16 | } 17 | num += val; 18 | } 19 | return parseInt(num, 10); 20 | }; 21 | if (!getApp().request_id) { 22 | num = rando(8); 23 | getApp().request_id = num; 24 | } else { 25 | getApp().request_id++; 26 | num = getApp().request_id; 27 | } 28 | return num.toString(); 29 | }, 30 | 31 | /** 32 | * check if userinfo auth is true 33 | */ 34 | async checkSpecificAuth(key: string): Promise { 35 | try { 36 | const { authSetting } = await getApp().wxApi.getSetting(); 37 | return authSetting.hasOwnProperty(key); 38 | } catch (error) { 39 | return false; 40 | } 41 | }, 42 | 43 | /** 44 | * check if userinfo auth is true 45 | */ 46 | async checkUserInfoAuth(): Promise { 47 | return await this.checkSpecificAuth('scope.userInfo'); 48 | }, 49 | }; 50 | -------------------------------------------------------------------------------- /src/styles/mixin/_triangle.scss: -------------------------------------------------------------------------------- 1 | // size color angle-direction 2 | @mixin triangle ($size, $color, $direction) { 3 | height: 0; 4 | width: 0; 5 | 6 | @if ($direction == top) or ($direction == bottom) or ($direction == right) or ($direction == left) { 7 | border-color: transparent; 8 | border-style: solid; 9 | border-width: $size / 2; 10 | 11 | @if $direction == top { 12 | border-bottom-color: $color; 13 | } 14 | 15 | @else if $direction == right { 16 | border-left-color: $color; 17 | } 18 | 19 | @else if $direction == bottom { 20 | border-top-color: $color; 21 | } 22 | 23 | @else if $direction == left { 24 | border-right-color: $color; 25 | } 26 | } 27 | 28 | @else if ($direction == top-right) or ($direction == top-left) { 29 | border-top: $size solid $color; 30 | 31 | @if $direction == top-right { 32 | border-left: $size solid transparent; 33 | } 34 | 35 | @else if $direction == top-left { 36 | border-right: $size solid transparent; 37 | } 38 | } 39 | 40 | @else if ($direction == bottom-right) or ($direction == bottom-left) { 41 | border-bottom: $size solid $color; 42 | 43 | @if $direction == bottom-right { 44 | border-left: $size solid transparent; 45 | } 46 | 47 | @else if $direction == bottom-left { 48 | border-right: $size solid transparent; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/libs/wx/watch/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 设置监听器 3 | */ 4 | export function setWatcher() { 5 | const page = this; 6 | const { data = {}, watch = {} } = page; 7 | Object.keys(watch).forEach(v => { 8 | const key = v.split('.'); 9 | let nowData = data; 10 | for (let i = 0; i < key.length - 1; i++) { 11 | nowData = nowData[key[i]]; 12 | } 13 | const lastKey = key[key.length - 1]; 14 | const watchFun = watch[v].handler || watch[v]; 15 | const deep = watch[v].deep; 16 | observe(nowData, lastKey, watchFun, deep, page); 17 | }); 18 | } 19 | 20 | /** 21 | * 监听属性 并执行监听函数 22 | */ 23 | function observe(obj, key, watchFun, deep, page) { 24 | let val = obj[key]; 25 | if (deep && val != null && typeof val === 'object') { 26 | Object.keys(val).forEach(childKey => { 27 | observe(val, childKey, watchFun, deep, page); 28 | }); 29 | } 30 | Object.defineProperty(obj, key, { 31 | configurable: true, 32 | enumerable: true, 33 | set(value) { 34 | watchFun.call(page, value, val); 35 | val = value; 36 | if (deep) { 37 | observe(obj, key, watchFun, deep, page); 38 | } 39 | }, 40 | get() { 41 | return val; 42 | } 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /src/services/index.ts: -------------------------------------------------------------------------------- 1 | import Conf from '@C/index'; 2 | import { BaseService } from '@S/BASE_SERVICE'; 3 | 4 | export class Service extends BaseService { 5 | constructor(apis) { 6 | super(Conf.APP.config.BASE_API, apis); 7 | this.interceptors.request.use(async ({ isAuth = true, ...data }) => { 8 | if (isAuth) { 9 | try { 10 | const { token } = await getApp().dispatch('getLoginResp'); 11 | data.header = Object.assign({}, data.header, { 'Cookie': `sid=${token}` }); 12 | } catch (error) { 13 | getApp().wxApi.showToast({ title: '登录失败' }); 14 | return Promise.reject(error); 15 | } 16 | } 17 | return data; 18 | }); 19 | 20 | this.interceptors.response.use(async ({ data }) => { 21 | if (data.hasOwnProperty('errcode')) { 22 | let title = ''; 23 | title = data.errmsg || '系统错误'; 24 | switch (data.errcode) { 25 | case 0: 26 | return data.data; 27 | case -1: 28 | break; 29 | case 1002: // not login 30 | await getApp().dispatch('login'); 31 | break; 32 | default: { } 33 | } 34 | console.error(data); 35 | getApp().wxApi.showToast({ title }); 36 | return Promise.reject(data); 37 | } 38 | return data; 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/store/mutations/account.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | login(state: any, action: any = {}): any { 3 | console.log(state, action); 4 | if (Object.keys(action).length) { 5 | state.baseInfo = Object.assign({}, action); 6 | } 7 | return state; 8 | }, 9 | setToken(state: any, action: any = {}): any { 10 | state.token = action || {}; 11 | wx.setStorageSync('TOKEN', state.token); 12 | wx.setStorageSync('TOKEN_TIME', new Date().valueOf() + 86400 * 7); 13 | return state; 14 | }, 15 | setCorpInfo(state: any, action: any = {}): any { 16 | state.corpInfo = action || {}; 17 | if (Object.keys(action)) { 18 | wx.setStorageSync('corpInfo', state.corpInfo); 19 | } 20 | return state; 21 | }, 22 | setSellerInfo(state: any, action: any = {}): any { 23 | state.sellerInfo = action || {}; 24 | if (Object.keys(action)) { 25 | wx.setStorageSync('sellerInfo', state.sellerInfo); 26 | } 27 | return state; 28 | }, 29 | setCustomerInfo(state: any, action: any = {}): any { 30 | state.customerInfo = action || {}; 31 | // if (Object.keys(action)) { 32 | // wx.setStorageSync('customerInfo', state.customerInfo); 33 | // } 34 | return state; 35 | }, 36 | setSpreadInfo(state: any, action: any = {}): any { 37 | state.spreadInfo = action || {}; 38 | return state; 39 | }, 40 | } as wxStore.mutationFunc; 41 | -------------------------------------------------------------------------------- /src/libs/wx/store/store.ts: -------------------------------------------------------------------------------- 1 | import t from '@/libs/utils'; 2 | 3 | function checkRepeatFunc(modules) { 4 | const funcName: string[] = []; 5 | Object.keys(modules).forEach(moduleName => { 6 | const moduleFuncName = Object.keys(modules[moduleName]); 7 | const intersectArr = t.intersect(funcName, moduleFuncName); 8 | if (intersectArr.length) { 9 | throw new Error(`Store error : ${moduleName} has repeat func ${JSON.stringify(intersectArr)}`); 10 | } else { 11 | funcName.push(...moduleFuncName); 12 | } 13 | }); 14 | } 15 | 16 | export function findModuleNameByFuncName(modules: object, funcName: string) { 17 | return Object.keys(modules).find(moduleName => modules[moduleName].hasOwnProperty(funcName)); 18 | } 19 | 20 | export class Store { 21 | 22 | $state: object = {}; 23 | actions: wxStore.dispatchFunc = {}; 24 | mutations: wxStore.mutationFunc = {}; 25 | getters: wxStore.getterFunc = {}; 26 | 27 | constructor({ 28 | stateModules = {}, 29 | actionModules = {}, 30 | mutationModules = {}, 31 | getterModules = {} 32 | }) { 33 | try { 34 | checkRepeatFunc(actionModules); 35 | checkRepeatFunc(mutationModules); 36 | checkRepeatFunc(getterModules); 37 | this.actions = actionModules; 38 | this.mutations = mutationModules; 39 | this.getters = getterModules; 40 | this.$state = stateModules; 41 | } catch (error) { 42 | console.error(error); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/libs/wx/store/index.ts: -------------------------------------------------------------------------------- 1 | import { findModuleNameByFuncName, Store } from './store'; 2 | 3 | export function createStore(storeModules) { 4 | // mount $state in App 5 | const store = new Store(storeModules); 6 | this.$state = store.$state; 7 | 8 | // mount dispatch function func in App 9 | this.dispatch = function dispatch(funcName: string, params?: any) { 10 | const moduleName = findModuleNameByFuncName(store.actions, funcName); 11 | if (moduleName) { 12 | return store.actions[moduleName][funcName].call(this, this.$state[moduleName], params); 13 | } else { 14 | console.log(`dispatch ${funcName} callback is undefined.`); 15 | } 16 | }; 17 | 18 | // mount commit function in App 19 | this.commit = function dispatch(funcName: string, params?: any) { 20 | const moduleName = findModuleNameByFuncName(store.mutations, funcName); 21 | if (moduleName) { 22 | this.$state[moduleName] = store.mutations[moduleName][funcName].call(this, this.$state[moduleName], params); 23 | } else { 24 | console.log(`commit ${funcName} callback is undefined.`); 25 | } 26 | }; 27 | 28 | // mount getter function in App 29 | this.getter = function dispatch(funcName: string, params?: any) { 30 | const moduleName = findModuleNameByFuncName(store.getters, funcName); 31 | if (moduleName) { 32 | return store.getters[moduleName][funcName].call(this, this.$state[moduleName], params); 33 | } else { 34 | console.log(`getter ${funcName} callback is undefined.`); 35 | } 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "modules": false, 7 | "loose": true 8 | } 9 | ] 10 | ], 11 | "plugins": [ 12 | "transform-function-bind", 13 | "@babel/plugin-transform-runtime", 14 | [ 15 | "@babel/plugin-transform-destructuring", 16 | { 17 | "loose": false 18 | } 19 | ], 20 | [ 21 | "@babel/plugin-transform-modules-commonjs", 22 | { 23 | "loose": false 24 | } 25 | ], 26 | "@babel/plugin-transform-async-to-generator", 27 | "@babel/plugin-transform-parameters", 28 | "@babel/plugin-transform-spread", 29 | "@babel/plugin-syntax-dynamic-import", 30 | "@babel/plugin-syntax-import-meta", 31 | "@babel/plugin-proposal-class-properties", 32 | "@babel/plugin-proposal-json-strings", 33 | [ 34 | "@babel/plugin-proposal-decorators", 35 | { 36 | "legacy": true 37 | } 38 | ], 39 | "@babel/plugin-proposal-function-sent", 40 | "@babel/plugin-proposal-export-namespace-from", 41 | "@babel/plugin-proposal-numeric-separator", 42 | "@babel/plugin-proposal-throw-expressions", 43 | "@babel/plugin-proposal-export-default-from", 44 | "@babel/plugin-proposal-logical-assignment-operators", 45 | "@babel/plugin-proposal-optional-chaining", 46 | [ 47 | "@babel/plugin-proposal-pipeline-operator", 48 | { 49 | "proposal": "minimal" 50 | } 51 | ], 52 | "@babel/plugin-proposal-nullish-coalescing-operator", 53 | "@babel/plugin-proposal-do-expressions", 54 | "@babel/plugin-proposal-function-bind" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /src/libs/wx/log/apiLog.ts: -------------------------------------------------------------------------------- 1 | 2 | import { MissingError } from '@/libs/wx/utils'; 3 | 4 | export default class ApiLog { 5 | method: string = ''; 6 | requestId: string = ''; 7 | isError: boolean = false; 8 | requestStart: number = this.getTimeStamp(); 9 | requestEnd: number = 0; 10 | timeCut: number = 0; 11 | data: any; 12 | response: any; 13 | desc: string = ''; 14 | url: string = ''; 15 | 16 | constructor({ method, requestId = '', data, url }: 17 | { method: string, url: string, requestId?: string, data?: any }) { 18 | 19 | this.method = method; 20 | this.url = url; 21 | this.requestId = requestId; 22 | this.data = data; 23 | this.requestStart = this.getTimeStamp(); 24 | } 25 | 26 | logerRespSuccess(resp: any) { 27 | this.logResp(resp); 28 | } 29 | 30 | logerRespError(resp: any) { 31 | this.logResp(resp, true); 32 | } 33 | 34 | isVaild(): boolean { 35 | const { method, url } = this; 36 | if (!method || !url) { 37 | return false; 38 | } 39 | return true; 40 | } 41 | 42 | private logResp(resp: any, isError = false) { 43 | this.requestEnd = this.getTimeStamp(); 44 | this.response = resp; 45 | this.isError = isError; 46 | this.timeCut = this.requestEnd - this.requestStart; 47 | const self = getApp() || this; 48 | self.logger.createApiLog(this); 49 | } 50 | 51 | private getTimeStamp(): number { 52 | return new Date().valueOf(); 53 | } 54 | } 55 | 56 | // new api log 57 | export function createApiLog({ url, method, ...other }: ApiLog) { 58 | if (!method || !url) { 59 | MissingError('method', 'url'); 60 | } 61 | return new ApiLog({ 62 | url, 63 | method, 64 | ...other 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "stylelint-scss" 4 | ], 5 | "extends": "stylelint-config-standard", 6 | "rules": { 7 | "scss/at-mixin-argumentless-call-parentheses": "always", 8 | "scss/dollar-variable-colon-newline-after": "always-multi-line", 9 | "scss/dollar-variable-colon-space-after": "always", 10 | "scss/dollar-variable-colon-space-before": "never", 11 | "scss/dollar-variable-no-missing-interpolation": true, 12 | "scss/double-slash-comment-whitespace-inside": "always", 13 | "scss/double-slash-comment-inline": "never", 14 | "scss/operator-no-newline-after": true, 15 | "scss/operator-no-newline-before": true, 16 | "scss/operator-no-unspaced": true, 17 | "scss/selector-no-redundant-nesting-selector": true, 18 | "no-descending-specificity": null, 19 | "no-empty-source": true, 20 | "selector-list-comma-newline-after": "always-multi-line", 21 | "indentation": [ 22 | "tab" 23 | ], 24 | "at-rule-no-unknown": [ 25 | true, 26 | { 27 | "ignoreAtRules": [ 28 | "mixin", 29 | "extend", 30 | "include", 31 | "if", 32 | "else", 33 | "function", 34 | "each" 35 | ] 36 | } 37 | ], 38 | "unit-no-unknown": [ 39 | true, 40 | { 41 | "ignoreUnits": [ 42 | "rpx" 43 | ] 44 | } 45 | ], 46 | "selector-type-no-unknown": [ 47 | true, 48 | { 49 | "ignoreTypes": [ 50 | "page", 51 | "navigator", 52 | "picker", 53 | "image", 54 | "open-data", 55 | "scroll-view", 56 | "icon" 57 | ] 58 | } 59 | ] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/store/actions/account.ts: -------------------------------------------------------------------------------- 1 | import accountService from '@S/accountService'; 2 | 3 | export default { 4 | async login(state: any): Promise { 5 | 6 | if (this.requestLock.request) { 7 | return await this.requestLock.request; 8 | } 9 | 10 | this.requestLock.status = true; 11 | this.requestLock.count++; 12 | this.requestLock.request = new Promise(async (resolve, reject) => { 13 | try { 14 | const { code } = await this.wxApi.login(); 15 | 16 | const { data } = await accountService.Login({ code }); 17 | 18 | this.commit('setToken', data); 19 | resolve(data); 20 | } catch (error) { 21 | console.log(error); 22 | this.wxApi.showToast({ 23 | title: '登陆失败,请稍后再试!' 24 | }); 25 | reject(error); 26 | } finally { 27 | this.requestLock.status = false; 28 | this.requestLock.request = null; 29 | this.requestLock.count = 0; 30 | } 31 | }); 32 | 33 | try { 34 | return await this.requestLock.request; 35 | } catch (error) { } 36 | }, 37 | 38 | async reportFormId(state: any, fromId: string = '') { 39 | console.info('fromId', fromId); 40 | if (fromId && fromId === 'the formId is a mock one') { 41 | return; 42 | } 43 | const self = getApp() || this; 44 | // const account: any = self.getter('getAccount'); 45 | // wechatService.AddWxaFormId({ 46 | // form_id: { 47 | // app_id: Conf.APP.config.APP_ID, 48 | // openid: account.wechat_id, 49 | // account_id: account.account_id, 50 | // form_id: fromId 51 | // } 52 | // }).then((result: any) => { 53 | // }).catch((err: any) => { 54 | // }); 55 | } 56 | } as wxStore.dispatchFunc; 57 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "root": true, 4 | "extends": [ 5 | "standard", 6 | "plugin:promise/recommended", 7 | "plugin:import/recommended" 8 | ], 9 | "env": { 10 | "browser": true, 11 | "node": true, 12 | "es6": true, 13 | "mocha": true, 14 | "jest": true 15 | }, 16 | "rules": { 17 | "import/no-absolute-path": 2, 18 | "import/no-extraneous-dependencies": 2, 19 | "import/no-mutable-exports": 2, 20 | "import/newline-after-import": 1, 21 | "import/unambiguous": 0, 22 | "promise/avoid-new": 0, 23 | "promise/no-callback-in-promise": 0, 24 | "promise/always-return": 0, 25 | "semi": [ 26 | 1, 27 | "always" 28 | ], 29 | "no-tabs": 0, 30 | "comma-dangle": 0, 31 | "indent": [ 32 | 2, 33 | 2, 34 | { 35 | "SwitchCase": 1 36 | } 37 | ], 38 | "padded-blocks": 0, 39 | "space-before-function-paren": [ 40 | 1, 41 | { 42 | "anonymous": "always", 43 | "named": "never" 44 | } 45 | ], 46 | "max-len": [ 47 | 1, 48 | { 49 | "code": 80, 50 | "tabWidth": 2, 51 | "ignoreComments": true, 52 | "ignoreStrings": true, 53 | "ignoreUrls": true, 54 | "ignoreRegExpLiterals": true 55 | } 56 | ], 57 | "brace-style": 0, 58 | "operator-linebreak": [ 59 | 1, 60 | "after" 61 | ], 62 | "camelcase": 0, 63 | "no-multiple-empty-lines": [ 64 | 1, 65 | { 66 | "max": 2 67 | } 68 | ], 69 | "no-unused-vars": [ 70 | 1, 71 | { 72 | "vars": "all", 73 | "args": "after-used", 74 | "caughtErrors": "none", 75 | "ignoreRestSiblings": true 76 | } 77 | ], 78 | "spaced-comment": 0 79 | }, 80 | "globals": { 81 | "App": true, 82 | "Page": true, 83 | "Component": true, 84 | "wx": true, 85 | "getCurrentPages": true, 86 | "getApp": true 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/libs/touch.ts: -------------------------------------------------------------------------------- 1 | 2 | export default class { 3 | dataList: any = []; 4 | startClientX: any = null; 5 | operationWrapperWidth: any = null; 6 | 7 | /** 8 | * init relative data 9 | * @param {array} dataList list data 10 | * @param {number} operationWrapperWidth the operater width 11 | */ 12 | initData({ datalist, operationWrapperWidth }: any) { 13 | this.operationWrapperWidth = operationWrapperWidth; 14 | this.dataList = datalist instanceof Array 15 | ? datalist.concat() 16 | : [datalist]; 17 | } 18 | 19 | /** 20 | * touch start 21 | * 1. reset data 22 | * 2. get touch start x 23 | * @return {array} reseted list data 24 | */ 25 | touchStart(e: any) { 26 | this._resetData(); 27 | this.startClientX = this._getClientX(e); 28 | return this.dataList; 29 | } 30 | 31 | /** 32 | * touch move 33 | * @return {object} current item 34 | */ 35 | touchMove(e: any) { 36 | const moveWidth = this._getMoveWidth(e); 37 | if (moveWidth > 0) { return; } 38 | 39 | this.dataList[this.getItemIndex(e)].left = Math.abs(moveWidth) > this.operationWrapperWidth 40 | ? -this.operationWrapperWidth 41 | : moveWidth; 42 | 43 | return this.dataList[this.getItemIndex(e)]; 44 | } 45 | 46 | /** 47 | * touch end 48 | * @return {object} current item 49 | */ 50 | touchEnd(e: any) { 51 | const moveWidth = this._getMoveWidth(e); 52 | let left = 0; 53 | 54 | // 向左滑动 且 滑动的距离已大于操作块宽度的一半 55 | if (moveWidth < 0 && Math.abs(moveWidth) > this.operationWrapperWidth / 2) { 56 | left = -this.operationWrapperWidth; 57 | } 58 | 59 | this.dataList[this.getItemIndex(e)].left = left; 60 | return this.dataList[this.getItemIndex(e)]; 61 | } 62 | 63 | getItemIndex(e: any) { 64 | return e.currentTarget.dataset.index; 65 | } 66 | 67 | // 获取当前滑动手势下 距离页面可显示区域的 横坐标 68 | _getClientX(e: any) { 69 | const touch = e.changedTouches; 70 | if (touch.length === 1) { return touch[0].clientX; } 71 | } 72 | 73 | // 获取滑动过程中 滑动的宽度 74 | _getMoveWidth(e: any) { 75 | return this._getClientX(e) - this.startClientX; 76 | } 77 | 78 | _resetData() { 79 | this.startClientX = null; 80 | this.dataList.forEach((v: any) => { v.left = 0; }); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/wxs/utils.wxs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dateStringFormat: function (value, format = 'yyyy-MM-dd hh:mm') { 3 | var result = ''; 4 | if (value != undefined && value != '') { 5 | if (typeof value === 'string') { 6 | var date = value.substring(0, 19); 7 | var regexp = getRegExp('-', 'g'); 8 | value = value.replace(regexp, '/'); 9 | } 10 | if (typeof value === 'number' && value.toString().length === 10) { 11 | value = value * 1000 12 | } 13 | var date = getDate(value); 14 | var year = date.getFullYear(); 15 | var montTmp = parseInt(date.getMonth()) + 1; 16 | var month = montTmp >= 10 ? montTmp : '0' + montTmp; 17 | var day = date.getDate() >= 10 ? date.getDate() : '0' + date.getDate(); 18 | var hour = date.getHours() >= 10 ? date.getHours() : '0' + date.getHours(); 19 | var min = date.getMinutes() >= 10 ? date.getMinutes() : '0' + date.getMinutes(); 20 | var sec = date.getSeconds() >= 10 ? date.getSeconds() : '0' + date.getSeconds(); 21 | if (format.indexOf('yyyy') >= 0) { 22 | result = year; 23 | } 24 | if (format.indexOf('MM') >= 0) { 25 | var yindex = format.indexOf('yyyy'); 26 | if (yindex >= 0) { 27 | result += format.substring(yindex + 4, yindex + 4 + 1); 28 | } 29 | result += month; 30 | } 31 | if (format.indexOf('dd') >= 0) { 32 | var Mindex = format.indexOf('MM'); 33 | if (Mindex >= 0) { 34 | result += format.substring(Mindex + 2, Mindex + 2 + 1); 35 | } 36 | result += day; 37 | } 38 | if (format.indexOf('hh') >= 0) { 39 | var dindex = format.indexOf('dd'); 40 | if (dindex >= 0) { 41 | result += ' '; 42 | } 43 | result += hour; 44 | } 45 | if (format.indexOf('mm') >= 0) { 46 | var hindex = format.indexOf('hh'); 47 | if (hindex >= 0) { 48 | result += format.substring(hindex + 2, hindex + 2 + 1); 49 | } 50 | result += min; 51 | } 52 | if (format.indexOf('ss') >= 0) { 53 | var mindex = format.indexOf('mm'); 54 | if (mindex >= 0) { 55 | result += format.substring(mindex + 2, mindex + 2 + 1); 56 | } 57 | result += sec; 58 | } 59 | } 60 | return result; 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /src/libs/wx-charts.d.ts: -------------------------------------------------------------------------------- 1 | export = WxChart; 2 | 3 | interface ChartOpts { 4 | canvasId: string; 5 | width: number; 6 | height: number; 7 | background?: string; 8 | enableScroll?: boolean; 9 | title?: Title; 10 | subtitle?: Title; 11 | animation?: boolean; 12 | legend?: boolean; 13 | type: "pie" | "line" | "column" | "area" | "ring" | "radar"; 14 | categories: string[]; 15 | dataLabel?: boolean; 16 | dataPointShape?: boolean; 17 | disablePieStroke?: boolean; 18 | xAxis?: XAxis; 19 | yAxis?: YAxis; 20 | extra?: object; 21 | series: DataItem[]; 22 | } 23 | 24 | interface Title { 25 | name?: string; 26 | fontSize?: number; 27 | color?: string; 28 | offsetX: number; 29 | } 30 | 31 | interface XAxis { 32 | gridColor?: string; 33 | fontColor?: string; 34 | disableGrid?: boolean; 35 | type?: "calibration"; 36 | } 37 | 38 | interface YAxis { 39 | format?: Function; 40 | min?: number; 41 | max?: number; 42 | title?: string; 43 | gridColor?: string; 44 | fontColor?: string; 45 | titleFontColor?: string; 46 | disabled: boolean; 47 | } 48 | 49 | interface DataItem { 50 | data: any[]; 51 | color?: string; 52 | name?: string; 53 | format?: Function; 54 | } 55 | 56 | interface Extra { 57 | ringWidth?: number; 58 | lineStyle?: "curve" | "stright"; 59 | column?: Column; 60 | legendTextColor?: string; 61 | radar?: Radar; 62 | pie?: Pie; 63 | } 64 | 65 | interface Column { 66 | width?: number; 67 | } 68 | 69 | interface Radar { 70 | max?: number; 71 | labelColor?: string; 72 | gridColor?: string; 73 | } 74 | 75 | interface Pie { 76 | offsetAngle?: number; 77 | } 78 | 79 | declare class WxChart { 80 | constructor(opts: ChartOpts); 81 | 82 | updateData(data: { 83 | categories?: string[], 84 | series?: DataItem[], 85 | title?: Title, 86 | subtitle?: Title 87 | }); 88 | stopAnimation(); 89 | addEventListener(type: "renderComplete", listener: () => void); 90 | getCurrentDataIndex(e); 91 | showToolTip(e, options?: ShowToolTipOptions); 92 | scrollStart(e); 93 | scroll(e); 94 | scrollEnd(e); 95 | } 96 | 97 | interface ShowToolTipOptions { 98 | background?: string; 99 | format?: (seriesItem: SeriesItem, category: string) => string; 100 | } 101 | 102 | interface SeriesItem { 103 | name: string; 104 | data: number; 105 | } -------------------------------------------------------------------------------- /src/libs/typings/wx/index.d.ts: -------------------------------------------------------------------------------- 1 | /*! ***************************************************************************** 2 | Copyright (c) 2018 Tencent, Inc. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | ***************************************************************************** */ 10 | 11 | /// 12 | /// 13 | /// 14 | /// 15 | /// 16 | /// 17 | /// 18 | 19 | declare type IAnyObject = Record 20 | 21 | declare type KVInfer = { 22 | [K in keyof T]: T[K] 23 | } 24 | 25 | declare type Void = T | undefined | null 26 | 27 | type PartialOptional = Partial> & Pick> 28 | 29 | /** 30 | * Make all properties in T required 31 | */ 32 | type Required = { 33 | [P in keyof T]-?: T[P]; 34 | }; 35 | 36 | /** 37 | * Exclude from T those types that are assignable to U 38 | */ 39 | type Exclude = T extends U ? never : T; 40 | 41 | /** 42 | * Extract from T those types that are assignable to U 43 | */ 44 | type Extract = T extends U ? T : never; 45 | 46 | /** 47 | * Exclude null and undefined from T 48 | */ 49 | type NonNullable = T extends null | undefined ? never : T; 50 | 51 | /** 52 | * Obtain the return type of a function type 53 | */ 54 | type ReturnType any> = T extends (...args: any[]) => infer R ? R : any; 55 | 56 | /** 57 | * Obtain the return type of a constructor function type 58 | */ 59 | type InstanceType any> = T extends new (...args: any[]) => infer R ? R : any; 60 | 61 | type Optional = { 62 | [K in keyof T]+?: T[K] 63 | } 64 | -------------------------------------------------------------------------------- /src/libs/utils/_event.ts: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | /* options的默认值 4 | * 表示首次调用返回值方法时,会马上调用func;否则仅会记录当前时刻,当第二次调用的时间间隔超过wait时,才调用func。 5 | * options.leading = true; 6 | * 表示当调用方法时,未到达wait指定的时间间隔,则启动计时器延迟调用func函数,若后续在既未达到wait指定的时间间隔和func函数又未被调用的情况下调用返回值方法,则被调用请求将被丢弃。 7 | * options.trailing = true; 8 | * 注意:当options.trailing = false时,效果与上面的简单实现效果相同 9 | */ 10 | 11 | throttle(func, wait, options = { leading: true, trailing: true }) { 12 | // @ts-ignore 13 | let context; 14 | let args; 15 | let result; 16 | let timeout: any = null; 17 | 18 | let previous = 0; 19 | 20 | function later() { 21 | previous = !options.leading ? 0 : new Date().valueOf(); 22 | timeout = null; 23 | result = func.apply(context, args); 24 | if (!timeout) { context = args = null; } 25 | } 26 | return function () { 27 | const now = new Date().valueOf(); 28 | if (!previous && options.leading === false) { previous = now; } 29 | // 计算剩余时间 30 | const remaining = wait - (now - previous); 31 | context = this; 32 | args = arguments; 33 | // 当到达wait指定的时间间隔,则调用func函数 34 | // 精彩之处:按理来说remaining <= 0已经足够证明已经到达wait的时间间隔,但这里还考虑到假如客户端修改了系统时间则马上执行func函数。 35 | if (remaining <= 0 || remaining > wait) { 36 | // 由于setTimeout存在最小时间精度问题,因此会存在到达wait的时间间隔,但之前设置的setTimeout操作还没被执行,因此为保险起见,这里先清理setTimeout操作 37 | if (timeout) { 38 | clearTimeout(timeout); 39 | timeout = null; 40 | } 41 | previous = now; 42 | result = func.apply(context, args); 43 | if (!timeout) { context = args = null; } 44 | } else if (!timeout && options.trailing !== false) { 45 | // options.trailing=true时,延时执行func函数 46 | timeout = setTimeout(later, remaining); 47 | } 48 | return result; 49 | }; 50 | }, 51 | 52 | debounce(func, wait, immediate = false) { 53 | // immediate默认为false 54 | let timeout; 55 | let args; 56 | let context; 57 | let timestamp; 58 | let result; 59 | 60 | function later() { 61 | // 当wait指定的时间间隔期间多次调用_.debounce返回的函数,则会不断更新timestamp的值,导致last < wait && last >= 0一直为true,从而不断启动新的计时器延时执行func 62 | const last = new Date().valueOf() - timestamp; 63 | 64 | if (last < wait && last >= 0) { 65 | timeout = setTimeout(later, wait - last); 66 | } else { 67 | timeout = null; 68 | if (!immediate) { 69 | result = func.apply(context, args); 70 | if (!timeout) { context = args = null; } 71 | } 72 | } 73 | } 74 | 75 | return function () { 76 | context = this; 77 | args = arguments; 78 | timestamp = new Date().valueOf(); 79 | // 第一次调用该方法时,且immediate为true,则调用func函数 80 | const callNow = immediate && !timeout; 81 | // 在wait指定的时间间隔内首次调用该方法,则启动计时器定时调用func函数 82 | if (!timeout) { timeout = setTimeout(later, wait); } 83 | if (callNow) { 84 | result = func.apply(context, args); 85 | context = args = null; 86 | } 87 | 88 | return result; 89 | }; 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /src/libs/wx/api/api.ts: -------------------------------------------------------------------------------- 1 | import t from '@/libs/utils'; 2 | import { wxPromise } from '@/libs/wx/utils'; 3 | 4 | const linkTo = ({ url, type, data }: { url: string, type: string, data: object }) => { 5 | return wxPromise(wx[type])(t.getUrlQuery(url, data)); 6 | }; 7 | 8 | // rewrite wx api to promise functions 9 | const functionNames = [ 10 | 'login', 11 | 'getUserInfo', 12 | 'checkSession', 13 | 'getStorageInfo', 14 | 'chooseAddress', 15 | 'clearStorage', 16 | 'getNetworkType', 17 | 'getSystemInfo', 18 | 'canvasToTempFilePath', 19 | 'saveImageToPhotosAlbum', 20 | 'getImageInfo', 21 | 'setClipboardData', 22 | 'makePhoneCall', 23 | 'chooseImage', 24 | 'getSetting', 25 | 'requestPayment', 26 | 'navigateBack', 27 | 'stopPullDownRefresh' 28 | ]; 29 | 30 | interface IWxApiTheme { 31 | toastMask: boolean; 32 | toastDuration: number; 33 | cancelColor?: string; 34 | confirmColor?: string; 35 | } 36 | 37 | // promise wxApi 38 | export function promisify({ toastMask, toastDuration, confirmColor, cancelColor }: IWxApiTheme) { 39 | // @ts-ignore 40 | const wxApi: wxApi = {}; 41 | 42 | functionNames.forEach(fnName => { 43 | wxApi[fnName] = opt => wxPromise(wx[fnName])(opt); 44 | }); 45 | 46 | wxApi.getStorage = async (key: string) => { 47 | try { 48 | return (await wxPromise(wx.getStorage)({ key }) as wx.GetStorageSuccessCallbackResult).data; 49 | } catch (error) { 50 | return Promise.reject(error); 51 | } 52 | }; 53 | 54 | wxApi.setStorage = (key: string, data: any) => wxPromise(wx.setStorage)({ key, data }); 55 | 56 | wxApi.removeStorage = (key: string) => wxPromise(wx.removeStorage)({ key } as wx.RemoveStorageOption); 57 | 58 | wxApi.showLoading = (options?: wx.ShowLoadingOption) => wxPromise(wx.showLoading)(Object.assign({}, { 59 | title: '加载中', 60 | mask: toastMask 61 | } as wx.ShowLoadingOption, options)); 62 | 63 | wxApi.hideLoading = () => wxPromise(wx.hideLoading)(); 64 | 65 | wxApi.showToast = (options: wx.ShowToastOption) => wxPromise(wx.showToast)(Object.assign({}, { 66 | mask: toastMask, 67 | icon: 'none', 68 | duration: toastDuration 69 | } as wx.ShowToastOption, options)); 70 | 71 | wxApi.showModal = (options: wx.ShowModalOption) => wxPromise(wx.showModal)(Object.assign({}, { 72 | title: '提示', 73 | content: '', 74 | cancelColor, 75 | confirmColor 76 | } as wx.ShowModalOption, options)); 77 | 78 | wxApi.setNavigationBarTitle = (title: string) => wxPromise(wx.setNavigationBarTitle)({ title } as wx.SetNavigationBarTitleOption); 79 | 80 | wxApi.navigateTo = ({ url }: wx.NavigateToOption, data: object = {}) => linkTo({ url, data, type: 'navigateTo' }); 81 | 82 | wxApi.redirectTo = ({ url }: wx.NavigateToOption, data: object = {}) => linkTo({ url, data, type: 'redirectTo' }); 83 | 84 | wxApi.switchTab = ({ url }: wx.NavigateToOption, data: object = {}) => linkTo({ url, data, type: 'switchTab' }); 85 | 86 | wxApi.reLaunch = ({ url }: wx.NavigateToOption, data: object = {}) => linkTo({ url, data, type: 'reLaunch' }); 87 | 88 | return wxApi; 89 | } 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quickstart-miniprogram", 3 | "version": "0.0.1", 4 | "description": "微信小程序模板", 5 | "license": "ISC", 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "mpc build", 9 | "start": "mpc serve", 10 | "dev:prod": "cross-env CONF=build mpc serve", 11 | "build:dev": "cross-env CONF=dev GENERATE_SOURCEMAP=false mpc build", 12 | "lint:scss": "stylelint --fix src/**/*.scss", 13 | "sort": "npx sort-package-json", 14 | "test": "echo \"Error: no test specified\" && exit 1" 15 | }, 16 | "author": "fupengl", 17 | "repository": "fupengl/quickstart-miniprogram", 18 | "dependencies": { 19 | "miniprogram-navigation-bar": "0.0.4", 20 | "miniprogram-recycle-view": "0.0.6", 21 | "miniprogram-slide-view": "0.0.3", 22 | "wapp-cli": "^0.0.71" 23 | }, 24 | "devDependencies": { 25 | "@babel/plugin-proposal-class-properties": "^7.3.0", 26 | "@babel/plugin-proposal-decorators": "^7.3.0", 27 | "@babel/plugin-proposal-do-expressions": "^7.2.0", 28 | "@babel/plugin-proposal-export-default-from": "^7.2.0", 29 | "@babel/plugin-proposal-export-namespace-from": "^7.2.0", 30 | "@babel/plugin-proposal-function-bind": "^7.2.0", 31 | "@babel/plugin-proposal-function-sent": "^7.2.0", 32 | "@babel/plugin-proposal-json-strings": "^7.2.0", 33 | "@babel/plugin-proposal-logical-assignment-operators": "^7.2.0", 34 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.2.0", 35 | "@babel/plugin-proposal-numeric-separator": "^7.2.0", 36 | "@babel/plugin-proposal-optional-chaining": "^7.2.0", 37 | "@babel/plugin-proposal-pipeline-operator": "^7.3.2", 38 | "@babel/plugin-proposal-throw-expressions": "^7.2.0", 39 | "@babel/plugin-syntax-dynamic-import": "^7.2.0", 40 | "@babel/plugin-syntax-import-meta": "^7.2.0", 41 | "@babel/plugin-transform-async-to-generator": "^7.2.0", 42 | "@babel/plugin-transform-destructuring": "^7.3.2", 43 | "@babel/plugin-transform-modules-commonjs": "^7.2.0", 44 | "@babel/plugin-transform-parameters": "^7.2.0", 45 | "@babel/plugin-transform-runtime": "^7.2.0", 46 | "@babel/plugin-transform-spread": "^7.2.2", 47 | "@babel/preset-env": "^7.3.1", 48 | "babel-loader": "^8.0.5", 49 | "babel-plugin-transform-function-bind": "^6.22.0", 50 | "babel-eslint": "^10.0.1", 51 | "cross-env": "^5.2.0", 52 | "eslint": "^5.8.0", 53 | "eslint-config-standard": "^12.0.0", 54 | "eslint-loader": "^2.1.1", 55 | "eslint-plugin-import": "^2.14.0", 56 | "eslint-plugin-node": "^8.0.0", 57 | "eslint-plugin-promise": "^4.0.1", 58 | "eslint-plugin-standard": "^4.0.0", 59 | "postcss-load-plugins": "^2.3.0", 60 | "postcss-plugin": "^1.0.0", 61 | "postcss-preset-env": "^6.3.0", 62 | "stylelint": "^9.7.1", 63 | "stylelint-config-standard": "^18.2.0", 64 | "stylelint-scss": "^3.3.2", 65 | "tslint": "^5.11.0" 66 | }, 67 | "engines": { 68 | "node": ">= 4.0.0", 69 | "npm": ">= 3.0.0" 70 | }, 71 | "browserslist": [ 72 | "> 1%", 73 | "last 2 versions", 74 | "Android >= 3.2", 75 | "Firefox >= 20", 76 | "iOS 7" 77 | ], 78 | "postcss": { 79 | "plugins": { 80 | "postcss-plugin": { 81 | "autoprefixer": { 82 | "grid": true 83 | }, 84 | "postcss-preset-env": {} 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/libs/typings/wx/lib.wx.component.d.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | declare namespace Component { 4 | 5 | interface ComponentOptions { 6 | multipleSlots: boolean 7 | } 8 | 9 | interface ComponentInstanceBaseProps { 10 | /** 页面的初始数据 11 | * 12 | * `data` 是页面第一次渲染使用的**初始数据**。 13 | * 14 | * 页面加载时,`data` 将会以`JSON`字符串的形式由逻辑层传至渲染层,因此`data`中的数据必须是可以转成`JSON`的类型:字符串,数字,布尔值,对象,数组。 15 | * 16 | * 渲染层可以通过 `WXML` 对数据进行绑定。 17 | */ 18 | data?: D 19 | 20 | /** `setData` 函数用于将数据从逻辑层发送到视图层(异步),同时改变对应的 `this.data` 的值(同步)。 21 | * 22 | * **注意:** 23 | * 24 | * 1. **直接修改 this.data 而不调用 this.setData 是无法改变页面的状态的,还会造成数据不一致**。 25 | * 1. 仅支持设置可 JSON 化的数据。 26 | * 1. 单次设置的数据不能超过1024kB,请尽量避免一次设置过多的数据。 27 | * 1. 请不要把 data 中任何一项的 value 设为 `undefined` ,否则这一项将不被设置并可能遗留一些潜在问题。 28 | */ 29 | 30 | setData?( 31 | /** 这次要改变的数据 32 | * 33 | * 以 `key: value` 的形式表示,将 `this.data` 中的 `key` 对应的值改变成 `value`。 34 | * 35 | * 其中 `key` 可以以数据路径的形式给出,支持改变数组中的某一项或对象的某个属性,如 `array[2].message`,`a.b.c.d`,并且不需要在 this.data 中预先定义。 36 | */ 37 | data: D | Pick | IAnyObject, 38 | /** setData引起的界面更新渲染完毕后的回调函数,最低基础库: `1.5.0` */ 39 | callback?: () => void 40 | ): void 41 | 42 | options?: ComponentOptions 43 | 44 | behaviors?: Array[string] 45 | 46 | properties?: { 47 | [prop: string]: { 48 | value?: any 49 | type?: Boolean | Array | String | Number, 50 | observer?(): void 51 | } 52 | }, 53 | 54 | methods?: { 55 | [eventName: string]: (...params?: any) => any 56 | } 57 | } 58 | 59 | // https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/lifetimes.html 60 | interface ComponentInstance extends ComponentInstanceBaseProps { 61 | created?(): void // 在组件实例刚刚被创建时执行 62 | attached?(): void // 在组件实例进入页面节点树时执行 63 | ready?(): void // 在组件在视图层布局完成后执行 64 | moved?(): void // 在组件实例被移动到节点树另一个位置时执行 65 | detached?(): void // 在组件实例被从页面节点树移除时执行 66 | error?(err: Error): void // 每当组件方法抛出错误时执行 67 | 68 | lifetimes?: { 69 | created?(): void // 在组件实例刚刚被创建时执行 70 | attached?(): void // 在组件实例进入页面节点树时执行 71 | ready?(): void // 在组件在视图层布局完成后执行 72 | moved?(): void // 在组件实例被移动到节点树另一个位置时执行 73 | detached?(): void // 在组件实例被从页面节点树移除时执行 74 | error?(err: Error): void // 每当组件方法抛出错误时执行 75 | } 76 | 77 | pageLifetimes?: { 78 | show?(): void // 组件所在的页面被展示时执行 79 | hide?(): void // 组件所在的页面被隐藏时执行 80 | resize?(): void // 组件所在的页面尺寸变化时执行 81 | } 82 | } 83 | 84 | interface ComponentConstructor { 85 | ( 86 | options: ComponentInstance & T 87 | ): void 88 | } 89 | 90 | interface GetCurrentPages { 91 | (): (PageInstance & T)[] 92 | } 93 | } 94 | 95 | declare const Component: Component.ComponentConstructor -------------------------------------------------------------------------------- /src/libs/base64.js: -------------------------------------------------------------------------------- 1 | export default class Base64 { 2 | // private property 3 | _keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' 4 | 5 | // method for encoding 6 | encode(input) { 7 | let output = ''; 8 | let chr1, chr2, chr3, enc1, enc2, enc3, enc4; 9 | let i = 0; 10 | input = Base64._utf8_encode(input); 11 | 12 | while (i < input.length) { 13 | 14 | chr1 = input.charCodeAt(i++); 15 | chr2 = input.charCodeAt(i++); 16 | chr3 = input.charCodeAt(i++); 17 | 18 | enc1 = chr1 >> 2; 19 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 20 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 21 | enc4 = chr3 & 63; 22 | 23 | if (isNaN(chr2)) { 24 | enc3 = enc4 = 64; 25 | } else if (isNaN(chr3)) { 26 | enc4 = 64; 27 | } 28 | output = output + this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4); 29 | } 30 | 31 | return output; 32 | } 33 | 34 | // method for decoding 35 | decode(input) { 36 | let output = ''; 37 | let chr1, chr2, chr3; 38 | let enc1, enc2, enc3, enc4; 39 | let i = 0; 40 | 41 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); 42 | 43 | while (i < input.length) { 44 | 45 | enc1 = this._keyStr.indexOf(input.charAt(i++)); 46 | enc2 = this._keyStr.indexOf(input.charAt(i++)); 47 | enc3 = this._keyStr.indexOf(input.charAt(i++)); 48 | enc4 = this._keyStr.indexOf(input.charAt(i++)); 49 | 50 | chr1 = (enc1 << 2) | (enc2 >> 4); 51 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 52 | chr3 = ((enc3 & 3) << 6) | enc4; 53 | 54 | output = output + String.fromCharCode(chr1); 55 | 56 | if (enc3 !== 64) { 57 | output = output + String.fromCharCode(chr2); 58 | } 59 | if (enc4 !== 64) { 60 | output = output + String.fromCharCode(chr3); 61 | } 62 | 63 | } 64 | 65 | output = Base64._utf8_decode(output); 66 | 67 | return output; 68 | } 69 | 70 | // method for UTF-8 encoding 71 | static _utf8_encode(string) { 72 | string = string.replace(/\r\n/g, '\n'); 73 | let utftext = ''; 74 | 75 | for (let n = 0; n < string.length; n++) { 76 | 77 | const c = string.charCodeAt(n); 78 | 79 | if (c < 128) { 80 | utftext += String.fromCharCode(c); 81 | } else if ((c > 127) && (c < 2048)) { 82 | utftext += String.fromCharCode((c >> 6) | 192); 83 | utftext += String.fromCharCode((c & 63) | 128); 84 | } else { 85 | utftext += String.fromCharCode((c >> 12) | 224); 86 | utftext += String.fromCharCode(((c >> 6) & 63) | 128); 87 | utftext += String.fromCharCode((c & 63) | 128); 88 | } 89 | 90 | } 91 | 92 | return utftext; 93 | } 94 | 95 | // method for UTF-8 decoding 96 | static _utf8_decode(utftext) { 97 | let string = ''; 98 | let i = 0; 99 | let c = 0; 100 | const c1 = 0; 101 | let c2 = 0; 102 | 103 | while (i < utftext.length) { 104 | 105 | c = utftext.charCodeAt(i); 106 | 107 | if (c < 128) { 108 | string += String.fromCharCode(c); 109 | i++; 110 | } else if ((c > 191) && (c < 224)) { 111 | c2 = utftext.charCodeAt(i + 1); 112 | string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); 113 | i += 2; 114 | } else { 115 | c2 = utftext.charCodeAt(i + 1); 116 | c3 = utftext.charCodeAt(i + 2); 117 | string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); 118 | i += 3; 119 | } 120 | 121 | } 122 | 123 | return string; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/libs/wx/log/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import { MissingError } from '@/libs/wx/utils'; 3 | import ApiLog from './apiLog'; 4 | 5 | export class Log { 6 | static networkType: string; 7 | static isConnected: boolean = true; 8 | 9 | options: wxLog.ILoggerOptions = { 10 | wxAppid: '', 11 | version: '', 12 | getLocation: false, 13 | statApiSpeed: true, 14 | statShareApp: true, 15 | apiMaxRequestTime: 300 16 | }; 17 | 18 | logs: wxLog.IReportData[] = []; // all log 19 | PageLog: any[] = []; // page event log 20 | AppLog: ApiLog[] = []; // app event log 21 | 22 | private requestUrl: string = 'https://app.pinquest.cn/api/oss/Report'; 23 | 24 | constructor({ wxAppid, version, ...options }: wxLog.ILoggerOptions) { 25 | if (!wxAppid || !version) { 26 | MissingError('wxAppid', 'version'); 27 | return; 28 | } 29 | 30 | this.options = Object.assign({}, this.options, options, { wxAppid, version }); 31 | console.warn('Log Options', this.options); 32 | 33 | this.handleNetworkStatus(); 34 | } 35 | 36 | // add slow api and err log 37 | createApiLog({ isError, timeCut, ...other }: ApiLog) { 38 | this.handleNetworkStatus(); 39 | if (timeCut > this.options.apiMaxRequestTime! || !isError) { 40 | this.createLog(wxLog.LogType.SLOW_API, wxLog.ErrorType.API_ERROR, { 41 | timeCut, 42 | ...other 43 | } as ApiLog); 44 | } 45 | if (isError) { 46 | this.createLog(wxLog.LogType.ERROR_API, wxLog.ErrorType.API_ERROR, { 47 | timeCut, 48 | ...other 49 | } as ApiLog); 50 | } 51 | } 52 | 53 | // add script error 54 | createScriptLog(err: string) { 55 | this.createLog(wxLog.LogType.SCRIPT_ERROR, wxLog.ErrorType.JS_ERROR, err); 56 | } 57 | 58 | // add log 59 | createLog(rid: wxLog.LogType, cat: wxLog.ErrorType, data: string | ApiLog) { 60 | const { wxAppid, version } = this.options; 61 | this.logs.push({ 62 | rid, 63 | cat, 64 | data, 65 | wxAppid, 66 | version, 67 | deviceInfo: wx.getSystemInfoSync() 68 | }); 69 | } 70 | 71 | // todo review 72 | // report log 73 | doReportLog() { 74 | const self = this; 75 | const { logs } = this; 76 | if (!logs.length) { 77 | return; 78 | } 79 | wx.request({ 80 | url: this.requestUrl, 81 | data: JSON.stringify(logs), 82 | method: 'POST', 83 | success(resp) { 84 | console.log(resp); 85 | self.clearLog(); 86 | }, 87 | fail(err) { 88 | console.log(err); 89 | } 90 | }); 91 | } 92 | 93 | // clear upload success log 94 | private clearLog() { 95 | this.logs = []; 96 | } 97 | 98 | // get network state 99 | private handleNetworkStatus() { 100 | wx.getNetworkType({ 101 | success({ networkType }) { 102 | Log.networkType = networkType; 103 | }, 104 | fail(err) { 105 | console.log(err); 106 | }, 107 | }); 108 | wx.onNetworkStatusChange(({ isConnected, networkType }: 109 | { isConnected: boolean, networkType: string }) => { 110 | Log.networkType = networkType; 111 | Log.isConnected = isConnected; 112 | }); 113 | } 114 | 115 | } 116 | 117 | // mount log event on APP onLaunch 118 | export function mountLogEvent() { 119 | wx.onError(err => { 120 | this.logger.createScriptLog(err); 121 | }); 122 | 123 | wx.onAppHide(() => { 124 | this.logger.doReportLog(); 125 | }); 126 | 127 | wx.onPageNotFound(() => { }); 128 | } 129 | 130 | // mount logger in App 131 | export function createLogger(options: wxLog.ILoggerOptions) { 132 | this.logger = new Log(options); 133 | mountLogEvent.call(this); 134 | } 135 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended", 4 | "tslint-config-prettier" 5 | ], 6 | "lib": [ 7 | "ES2015", 8 | "ES2016", 9 | "ES2017" 10 | ], 11 | "linterOptions": { 12 | "exclude": [ 13 | "node_modules/**/*.ts", 14 | "dist", 15 | "config", 16 | "build", 17 | "src/**/*.d.ts", 18 | "./**/*.js" 19 | ] 20 | }, 21 | "rules": { 22 | "member-access": [ 23 | true, 24 | "no-public" 25 | ], 26 | "interface-name": [ 27 | true, 28 | "always-prefix" 29 | ], 30 | "align": [ 31 | true, 32 | "parameters", 33 | "statements" 34 | ], 35 | "class-name": true, 36 | "comment-format": [ 37 | true, 38 | "check-space" 39 | ], 40 | "curly": true, 41 | "eofline": true, 42 | "forin": false, 43 | "indent": [ 44 | true, 45 | "spaces", 46 | 2 47 | ], 48 | "noImplicitThis": false, 49 | "jsdoc-format": false, 50 | "label-position": true, 51 | "max-line-length": [ 52 | true, 53 | 200 54 | ], 55 | "member-ordering": [ 56 | true, 57 | { 58 | "order": "statics-first" 59 | } 60 | ], 61 | "new-parens": true, 62 | "no-any": false, 63 | "no-arg": true, 64 | "no-bitwise": false, 65 | "no-conditional-assignment": true, 66 | "no-consecutive-blank-lines": true, 67 | "no-console": [ 68 | false, 69 | "debug", 70 | "info", 71 | "log", 72 | "time", 73 | "timeEnd", 74 | "warn", 75 | "trace" 76 | ], 77 | "no-construct": true, 78 | "no-constructor-vars": false, 79 | "no-debugger": false, 80 | "no-duplicate-variable": true, 81 | "no-empty": false, 82 | "no-eval": true, 83 | "no-internal-module": true, 84 | "no-namespace": true, 85 | "no-reference": true, 86 | "no-shadowed-variable": false, 87 | "no-string-literal": true, 88 | "no-switch-case-fall-through": false, 89 | "no-trailing-whitespace": true, 90 | "no-unused-expression": [ 91 | true, 92 | "allow-fast-null-checks" 93 | ], 94 | "no-use-before-declare": false, 95 | "no-var-keyword": true, 96 | "no-var-requires": false, 97 | "object-literal-sort-keys": false, 98 | "one-line": [ 99 | true, 100 | "check-catch", 101 | "check-else", 102 | "check-finally", 103 | "check-open-brace", 104 | "check-whitespace" 105 | ], 106 | "one-variable-per-declaration": [ 107 | true, 108 | "ignore-for-loop" 109 | ], 110 | "quotemark": [ 111 | true, 112 | "single", 113 | "jsx-double" 114 | ], 115 | "radix": true, 116 | "semicolon": [ 117 | true, 118 | "always" 119 | ], 120 | "switch-default": true, 121 | "trailing-comma": [ 122 | false, 123 | { 124 | "singleline": "never", 125 | "multiline": "always", 126 | "esSpecCompliant": true 127 | } 128 | ], 129 | "triple-equals": [ 130 | true, 131 | "allow-null-check" 132 | ], 133 | "typedef": false, 134 | "max-classes-per-file": [ 135 | true, 136 | 10, 137 | "exclude-class-expressions" 138 | ], 139 | "typedef-whitespace": [ 140 | true, 141 | { 142 | "call-signature": "nospace", 143 | "index-signature": "nospace", 144 | "parameter": "nospace", 145 | "property-declaration": "nospace", 146 | "variable-declaration": "nospace" 147 | }, 148 | { 149 | "call-signature": "onespace", 150 | "index-signature": "onespace", 151 | "parameter": "onespace", 152 | "property-declaration": "onespace", 153 | "variable-declaration": "onespace" 154 | } 155 | ], 156 | "use-isnan": true, 157 | "variable-name": [ 158 | true, 159 | "allow-leading-underscore", 160 | "allow-trailing-underscore", 161 | "ban-keywords", 162 | "check-format", 163 | "allow-pascal-case", 164 | "allow-snake-case" 165 | ], 166 | "whitespace": [ 167 | true, 168 | "check-branch", 169 | "check-decl", 170 | "check-operator", 171 | "check-separator", 172 | "check-type", 173 | "check-typecast" 174 | ], 175 | "jsx-no-lambda": false, 176 | "jsx-no-string-ref": false, 177 | "jsx-wrap-multiline": false 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/libs/wx/httpClient/index.ts: -------------------------------------------------------------------------------- 1 | import apiLog, { createApiLog } from '@/libs/wx/log/apiLog'; 2 | import { strFormat, wxPromise } from '@/libs/wx/utils'; 3 | 4 | type IFunction = (_: any) => any; 5 | 6 | class InterceptorManager { 7 | 8 | static defaultFunc: IFunction = data => data; 9 | 10 | rejected: IFunction = InterceptorManager.defaultFunc; // fail request 11 | fulfilled: IFunction = InterceptorManager.defaultFunc; // success response 12 | 13 | use(fulfilled: IFunction = InterceptorManager.defaultFunc, 14 | rejected: IFunction = InterceptorManager.defaultFunc) { 15 | this.fulfilled = fulfilled; 16 | this.rejected = rejected; 17 | } 18 | } 19 | 20 | export default class HttpClient { 21 | 22 | interceptors = { 23 | request: new InterceptorManager(), 24 | response: new InterceptorManager() 25 | }; 26 | 27 | strFormat = strFormat; 28 | 29 | // host 30 | protected baseHost: string; 31 | protected logModel: boolean = true; 32 | 33 | constructor(baseHost: string = '', logModel: boolean = true) { 34 | this.baseHost = baseHost; 35 | this.logModel = logModel; 36 | } 37 | 38 | // parse url (add host and parse uri params) 39 | getURI(uri: string = '', params: object = {}): string { 40 | uri = strFormat(uri, params); 41 | if (uri.match(/^http/)) { 42 | return uri; 43 | } 44 | 45 | return uri[0] === '/' 46 | ? this.baseHost + uri 47 | : this.baseHost + '/' + uri; 48 | } 49 | 50 | // json request 51 | jsonRequest(opt, params: object = {}) { 52 | opt.header = Object.assign({}, opt.header, { 53 | 'Content-Type': 'application/json; charset=utf-8' 54 | }); 55 | return this._doRequest(opt, params); 56 | } 57 | 58 | // formdata request 59 | formRequest(opt, params: object = {}) { 60 | opt.header = Object.assign({}, opt.header, { 61 | 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8' 62 | }); 63 | return this._doRequest(opt, params); 64 | } 65 | 66 | uploadFile({ data: { filePath, ...data }, name = 'file', ...options }, params: object = {}) { 67 | const opt: any = options; 68 | opt.formData = data; 69 | opt.filePath = filePath; 70 | opt.name = name; 71 | opt.url = this.getURI(options.url, params); 72 | 73 | return this._upload(opt); 74 | } 75 | 76 | private async _upload(opt) { 77 | try { 78 | opt = await Promise.resolve(this.interceptors.request.fulfilled(opt)); 79 | } catch (error) { 80 | return await Promise.reject(error); 81 | } 82 | 83 | return new Promise((resolve, reject) => { 84 | wxPromise(wx.uploadFile)(opt).then(async (resp: any) => { 85 | // uploadFile return json string 86 | resp.data = JSON.parse(resp.data); 87 | try { 88 | resolve(await Promise.resolve(this.interceptors.response.fulfilled(resp))); 89 | } catch (err) { 90 | reject(await Promise.resolve(this.interceptors.response.rejected(err))); 91 | } 92 | }).catch(async err => { 93 | reject(await Promise.resolve(this.interceptors.response.rejected(err))); 94 | }); 95 | }); 96 | } 97 | 98 | private async _doRequest(opt, params: object = {}) { 99 | this._parseRequestOpt(opt, params); 100 | 101 | if (this.logModel) { 102 | const log = createApiLog(opt as apiLog); 103 | try { 104 | const resp = await this._request(opt); 105 | log.logerRespSuccess('success'); 106 | return Promise.resolve(resp); 107 | } catch (error) { 108 | log.logerRespError(error); 109 | return Promise.reject(error); 110 | } 111 | } 112 | 113 | return this._request(opt); 114 | } 115 | 116 | // format request params 117 | private _parseRequestOpt(opt, params: object = {}) { 118 | opt.url = this.getURI(opt.url, params); 119 | opt.method = opt.method ? opt.method.toUpperCase() : 'GET'; 120 | } 121 | 122 | // wx request 123 | private async _request(opt) { 124 | try { 125 | opt = await Promise.resolve(this.interceptors.request.fulfilled(opt)); 126 | } catch (error) { 127 | return await Promise.reject(error); 128 | } 129 | 130 | return new Promise((resolve, reject) => { 131 | wxPromise(wx.request)(opt).then(async resp => { 132 | try { 133 | resolve(await Promise.resolve(this.interceptors.response.fulfilled(resp))); 134 | } catch (err) { 135 | reject(await Promise.resolve(this.interceptors.response.rejected(err))); 136 | } 137 | }).catch(async err => { 138 | reject(await Promise.resolve(this.interceptors.response.rejected(err))); 139 | }); 140 | }); 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/libs/utils/_string.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | /** 3 | * 去除空格 4 | * @param {str} 5 | * @param {type} 6 | * type: 1-所有空格 2-前后空格 3-前空格 4-后空格 7 | * @return {String} 8 | */ 9 | trim(str, type) { 10 | type = type || 1; 11 | switch (type) { 12 | case 1: 13 | return str.replace(/\s+/g, ''); 14 | case 2: 15 | return str.replace(/(^\s*)|(\s*$)/g, ''); 16 | case 3: 17 | return str.replace(/(^\s*)/g, ''); 18 | case 4: 19 | return str.replace(/(\s*$)/g, ''); 20 | default: 21 | return str; 22 | } 23 | }, 24 | 25 | /** 26 | * @param {str} 27 | * @param {type} 28 | * type: 1:首字母大写 2:首页母小写 3:大小写转换 4:全部大写 5:全部小写 29 | * @return {String} 30 | */ 31 | changeCase(str, type) { 32 | type = type || 4; 33 | switch (type) { 34 | case 1: 35 | return str.replace(/\b\w+\b/g, (word) => { 36 | return word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase(); 37 | 38 | }); 39 | case 2: 40 | return str.replace(/\b\w+\b/g, (word) => { 41 | return word.substring(0, 1).toLowerCase() + word.substring(1).toUpperCase(); 42 | }); 43 | case 3: 44 | return str.split('').map((word) => { 45 | if (/[a-z]/.test(word)) { 46 | return word.toUpperCase(); 47 | } else { 48 | return word.toLowerCase(); 49 | } 50 | }).join(''); 51 | case 4: 52 | return str.toUpperCase(); 53 | case 5: 54 | return str.toLowerCase(); 55 | default: 56 | return str; 57 | } 58 | }, 59 | 60 | /*将阿拉伯数字翻译成中文的大写数字*/ 61 | numberToChinese(num) { 62 | const AA = new Array('零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十'); 63 | const BB = new Array('', '十', '百', '仟', '萬', '億', '点', ''); 64 | const a = ('' + num).replace(/(^0*)/g, '').split('.'); 65 | let k = 0; 66 | let re = ''; 67 | for (let i = a[0].length - 1; i >= 0; i--) { 68 | switch (k) { 69 | case 0: 70 | re = BB[7] + re; 71 | break; 72 | case 4: 73 | if (!new RegExp('0{4}//d{' + (a[0].length - i - 1) + '}$') 74 | .test(a[0])) { 75 | re = BB[4] + re; 76 | } 77 | break; 78 | case 8: 79 | re = BB[5] + re; 80 | BB[7] = BB[5]; 81 | k = 0; 82 | break; 83 | default: 84 | break; 85 | } 86 | if (k % 4 === 2 && +a[0].charAt(i + 2) !== 0 && +a[0].charAt(i + 1) === 0) { 87 | re = AA[0] + re; 88 | } 89 | if (+a[0].charAt(i) !== 0) { 90 | re = AA[a[0].charAt(i)] + BB[k % 4] + re; 91 | } 92 | k++; 93 | } 94 | 95 | if (a.length > 1) { 96 | re += BB[6]; 97 | for (let i = 0; i < a[1].length; i++) { 98 | re += AA[a[1].charAt(i)]; 99 | } 100 | } 101 | if (re === '一十') { 102 | re = '十'; 103 | } 104 | if (re.match(/^一/) && re.length === 3) { 105 | re = re.replace('一', ''); 106 | } 107 | return re; 108 | }, 109 | 110 | /** 111 | * Description: Count a string (mixing English and Chinese characters) length. 112 | * A basic and rough function. 113 | * 114 | * Performance: 115 | * Multiple methods performance test on http://jsperf.com/count-string-length. 116 | * You can see that using regexp to check range is very slow from the above test page. 117 | */ 118 | strLen(str: string) { 119 | let count = 0; 120 | for (let i = 0, len = str.length; i < len; i++) { 121 | count += str.charCodeAt(i) < 256 ? 1 : 2; 122 | } 123 | return count; 124 | }, 125 | 126 | strFormat(result: string = '', args: any = {}): string { 127 | for (const key in args) { 128 | const reg = new RegExp('({' + key + '})', 'g'); 129 | result = result.replace(reg, args[key]); 130 | } 131 | return result; 132 | }, 133 | 134 | getSpecificLengthCharMsg(str: string, maxLength: number) { 135 | let count = 0; 136 | let content = ''; 137 | let maxIndex = 0; 138 | 139 | for (let i = 0, len = str.length; i < len; i++) { 140 | if (count < maxLength) { 141 | count += str.charCodeAt(i) < 256 ? 1 : 2; 142 | content += str[i]; 143 | maxIndex = i; 144 | } else { 145 | break; 146 | } 147 | } 148 | return { count, content, maxIndex }; 149 | }, 150 | 151 | sliceDecimal(val: number, precision: number) { 152 | return `${val}`.slice(0, `${val}`.indexOf('.') + 1 + precision); 153 | }, 154 | 155 | dataMask(str: string, type: string) { 156 | let result; 157 | switch (type) { 158 | case 'phone': 159 | result = str.substr(0, 3) + '****' + str.substr(7); 160 | break; 161 | default: 162 | break; 163 | } 164 | return result; 165 | } 166 | 167 | }; 168 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export const queryStringfy = (params: object = {}): string => { 2 | if (Object.keys(params).length) { 3 | return Object.keys(params).map(key => 4 | `${encodeURIComponent(key)}=${encodeURIComponent((params as any)[key])}` 5 | ).join('&'); 6 | } 7 | return ''; 8 | }; 9 | 10 | export const formatDate = (timestamp: string | number = new Date().valueOf(), fmt: string = 'yyyy-MM-dd hh:mm:ss'): string => { 11 | if (typeof timestamp === 'string') { 12 | timestamp = parseInt(timestamp, 10); 13 | } 14 | const date: Date = new Date(timestamp); 15 | if (date.toString() === 'Invalid Date') { 16 | return 'Invalid Date'; 17 | } 18 | const o: any = { 19 | 'M+': date.getMonth() + 1, // 月份 20 | 'd+': date.getDate(), // 日 21 | 'h+': date.getHours() % 12 === 0 ? 12 : date.getHours() % 12, // 小时 22 | 'H+': date.getHours(), // 小时 23 | 'm+': date.getMinutes(), // 分 24 | 's+': date.getSeconds(), // 秒 25 | 'q+': Math.floor((date.getMonth() + 3) / 3), // 季度 26 | 'S': date.getMilliseconds() // 毫秒 27 | }; 28 | 29 | const week: any = { 30 | '0': '/u65e5', 31 | '1': '/u4e00', 32 | '2': '/u4e8c', 33 | '3': '/u4e09', 34 | '4': '/u56db', 35 | '5': '/u4e94', 36 | '6': '/u516d' 37 | }; 38 | 39 | if (/(y+)/.test(fmt)) { 40 | fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); 41 | } 42 | if (/(E+)/.test(fmt)) { 43 | fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? '/u661f/u671f' : '/u5468') : '') + week[date.getDay() + '']); 44 | } 45 | for (const k in o) { 46 | if (new RegExp('(' + k + ')').test(fmt)) { 47 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))); 48 | } 49 | } 50 | return fmt; 51 | }; 52 | 53 | export const deepClone = (obj: any): any => { 54 | if (typeof obj !== 'object' || obj === null) { 55 | return obj; 56 | } 57 | 58 | if (Array.isArray(obj)) { 59 | return obj.map(v => { 60 | if (typeof v === 'object' && v !== null) { return deepClone(v); } else { return v; } 61 | }); 62 | } else { 63 | const newObj: any = {}; 64 | 65 | Object.keys(obj).forEach(v => { 66 | if (typeof obj[v] === 'object' && obj[v] !== null) { 67 | newObj[v] = deepClone(obj[v]); 68 | } else { 69 | newObj[v] = obj[v]; 70 | } 71 | }); 72 | 73 | return newObj; 74 | } 75 | }; 76 | 77 | /** 78 | * 格式化字符串 79 | * @param result 格式化字符串 eg:`user: {name}` 80 | * @param args 格式化数据 eg: { name:"123" } 81 | */ 82 | export const strFormat = (result: string = '', args: any = {}): string => { 83 | for (const key in args) { 84 | const reg = new RegExp('({' + key + '})', 'g'); 85 | result = result.replace(reg, args[key]); 86 | } 87 | return result; 88 | }; 89 | 90 | /** 91 | * 获取字符串英文字符长度 92 | * @param str 93 | */ 94 | export const strLen = (str: string): number => { 95 | let count = 0; 96 | for (let i = 0, len = str.length; i < len; i++) { 97 | count += str.charCodeAt(i) < 256 ? 1 : 2; 98 | } 99 | return count; 100 | }; 101 | 102 | export function parseTime(time: number | string | Date, cFormat?: string) { 103 | if (arguments.length === 0) { 104 | return null; 105 | } 106 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'; 107 | let date: Date; 108 | if (typeof time === 'object') { 109 | date = time; 110 | } else { 111 | if (('' + time).length === 10) { time = parseInt(time as string, 10) * 1000; } 112 | date = new Date((time as string).replace(/-/g, '/')); 113 | } 114 | const formatObj: any = { 115 | y: date.getFullYear(), 116 | m: date.getMonth() + 1, 117 | d: date.getDate(), 118 | h: date.getHours(), 119 | i: date.getMinutes(), 120 | s: date.getSeconds(), 121 | a: date.getDay() 122 | }; 123 | const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { 124 | let value = formatObj[key]; 125 | // Note: getDay() returns 0 on Sunday 126 | if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value]; } 127 | if (result.length > 0 && value < 10) { 128 | value = '0' + value; 129 | } 130 | return value || 0; 131 | }); 132 | return time_str; 133 | } 134 | 135 | export function formatTime(time: number, option?: string) { 136 | time = +time * 1000; 137 | const d: any = new Date(time); 138 | const now = Date.now(); 139 | 140 | const diff = (now - d) / 1000; 141 | 142 | if (diff < 30) { 143 | return '刚刚'; 144 | } else if (diff < 3600) { 145 | // less 1 hour 146 | return Math.ceil(diff / 60) + '分钟前'; 147 | } else if (diff < 3600 * 24) { 148 | return Math.ceil(diff / 3600) + '小时前'; 149 | } else if (diff < 3600 * 24 * 2) { 150 | return '1天前'; 151 | } 152 | if (option) { 153 | return parseTime(time, option); 154 | } else { 155 | return ( 156 | d.getMonth() + 157 | 1 + 158 | '月' + 159 | d.getDate() + 160 | '日' + 161 | d.getHours() + 162 | '时' + 163 | d.getMinutes() + 164 | '分' 165 | ); 166 | } 167 | } 168 | 169 | export const validateMobile = (num: string): boolean => { 170 | const regexp = /^1[3|4|5|6|7|8|9]\d{9}$/; 171 | return regexp.test(num); 172 | }; 173 | 174 | // 获取用户是否授权保存到相册 wx.saveImageToPhotosAlbum, wx.saveVideoToPhotosAlbum 175 | export const getUserwritePhotosAlbum = async () => { 176 | return new Promise((resolve) => { 177 | wx.getSetting({ 178 | complete: (res: any) => { 179 | console.log(res, '-----auth-------wx.getSetting------'); 180 | let result = false; 181 | if (res && res.authSetting && res.authSetting['scope.writePhotosAlbum']) { 182 | result = true; 183 | } 184 | resolve(result); 185 | } 186 | }); 187 | }); 188 | }; 189 | -------------------------------------------------------------------------------- /src/libs/typings/wx/lib.wx.page.d.ts: -------------------------------------------------------------------------------- 1 | /*! ***************************************************************************** 2 | Copyright (c) 2018 Tencent, Inc. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | ***************************************************************************** */ 10 | 11 | declare namespace Page { 12 | 13 | interface ICustomShareContent { 14 | /** 转发标题。默认值:当前小程序名称 */ 15 | title?: string 16 | /** 转发路径,必须是以 / 开头的完整路径。默认值:当前页面 path */ 17 | path?: string 18 | /** 自定义图片路径,可以是本地文件路径、代码包文件路径或者网络图片路径。支持PNG及JPG。显示图片长宽比是 5:4,最低基础库: `1.5.0`。默认值:使用默认截图 */ 19 | imageUrl?: string 20 | } 21 | 22 | interface IPageScrollOption { 23 | /** 页面在垂直方向已滚动的距离(单位px) */ 24 | scrollTop: number 25 | } 26 | 27 | interface IShareAppMessageOption { 28 | /** 转发事件来源。 29 | * 30 | * 可选值: 31 | * - `button`:页面内转发按钮; 32 | * - `menu`:右上角转发菜单。 33 | * 34 | * 最低基础库: `1.2.4` 35 | */ 36 | from: 'button' | 'menu' | string 37 | /** 如果 `from` 值是 `button`,则 `target` 是触发这次转发事件的 `button`,否则为 `undefined` 38 | * 39 | * 最低基础库: `1.2.4` */ 40 | target: any 41 | /** 页面中包含``组件时,返回当前``的url 42 | * 43 | * 最低基础库: `1.6.4` 44 | */ 45 | webViewUrl?: string 46 | } 47 | 48 | interface ITabItemTapOption { 49 | /** 被点击tabItem的序号,从0开始,最低基础库: `1.9.0` */ 50 | index: string 51 | /** 被点击tabItem的页面路径,最低基础库: `1.9.0` */ 52 | pagePath: string 53 | /** 被点击tabItem的按钮文字,最低基础库: `1.9.0` */ 54 | text: string 55 | } 56 | 57 | interface PageInstanceBaseProps { 58 | /** 页面的初始数据 59 | * 60 | * `data` 是页面第一次渲染使用的**初始数据**。 61 | * 62 | * 页面加载时,`data` 将会以`JSON`字符串的形式由逻辑层传至渲染层,因此`data`中的数据必须是可以转成`JSON`的类型:字符串,数字,布尔值,对象,数组。 63 | * 64 | * 渲染层可以通过 `WXML` 对数据进行绑定。 65 | */ 66 | data?: D 67 | 68 | /** `setData` 函数用于将数据从逻辑层发送到视图层(异步),同时改变对应的 `this.data` 的值(同步)。 69 | * 70 | * **注意:** 71 | * 72 | * 1. **直接修改 this.data 而不调用 this.setData 是无法改变页面的状态的,还会造成数据不一致**。 73 | * 1. 仅支持设置可 JSON 化的数据。 74 | * 1. 单次设置的数据不能超过1024kB,请尽量避免一次设置过多的数据。 75 | * 1. 请不要把 data 中任何一项的 value 设为 `undefined` ,否则这一项将不被设置并可能遗留一些潜在问题。 76 | */ 77 | 78 | setData?( 79 | /** 这次要改变的数据 80 | * 81 | * 以 `key: value` 的形式表示,将 `this.data` 中的 `key` 对应的值改变成 `value`。 82 | * 83 | * 其中 `key` 可以以数据路径的形式给出,支持改变数组中的某一项或对象的某个属性,如 `array[2].message`,`a.b.c.d`,并且不需要在 this.data 中预先定义。 84 | */ 85 | data: D | Pick | IAnyObject, 86 | /** setData引起的界面更新渲染完毕后的回调函数,最低基础库: `1.5.0` */ 87 | callback?: () => void 88 | ): void 89 | 90 | /** 到当前页面的路径,类型为`String`。最低基础库: `1.2.0` */ 91 | route?: string 92 | } 93 | 94 | interface PageInstance extends PageInstanceBaseProps { 95 | /** 生命周期回调—监听页面加载 96 | * 97 | * 页面加载时触发。一个页面只会调用一次,可以在 onLoad 的参数中获取打开当前页面路径中的参数。 98 | */ 99 | onLoad?( 100 | /** 打开当前页面路径中的参数 */ 101 | query?: { [queryKey: string]: string } 102 | ): void 103 | /** 生命周期回调—监听页面显示 104 | * 105 | * 页面显示/切入前台时触发。 106 | */ 107 | onShow?(): void 108 | /** 生命周期回调—监听页面初次渲染完成 109 | * 110 | * 页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互。 111 | * 112 | 113 | * 注意:对界面内容进行设置的 API 如`wx.setNavigationBarTitle`,请在`onReady`之后进行。 114 | */ 115 | onReady?(): void 116 | /** 生命周期回调—监听页面隐藏 117 | * 118 | * 页面隐藏/切入后台时触发。 如 `navigateTo` 或底部 `tab` 切换到其他页面,小程序切入后台等。 119 | */ 120 | onHide?(): void 121 | /** 生命周期回调—监听页面卸载 122 | * 123 | * 页面卸载时触发。如`redirectTo`或`navigateBack`到其他页面时。 124 | */ 125 | onUnload?(): void 126 | /** 监听用户下拉动作 127 | * 128 | * 监听用户下拉刷新事件。 129 | * - 需要在`app.json`的`window`选项中或页面配置中开启`enablePullDownRefresh`。 130 | * - 可以通过`wx.startPullDownRefresh`触发下拉刷新,调用后触发下拉刷新动画,效果与用户手动下拉刷新一致。 131 | * - 当处理完数据刷新后,`wx.stopPullDownRefresh`可以停止当前页面的下拉刷新。 132 | */ 133 | onPullDownRefresh?(): void 134 | /** 页面上拉触底事件的处理函数 135 | * 136 | * 监听用户上拉触底事件。 137 | * - 可以在`app.json`的`window`选项中或页面配置中设置触发距离`onReachBottomDistance`。 138 | * - 在触发距离内滑动期间,本事件只会被触发一次。 139 | */ 140 | onReachBottom?(): void 141 | /** 用户点击右上角转发 142 | * 143 | * 监听用户点击页面内转发按钮(`