├── core ├── apis │ ├── user.js │ ├── order.js │ └── index.js └── fetch.js ├── pages └── home │ ├── index.json │ ├── index.wxml │ ├── index.wxss │ └── index.js ├── package └── extend │ └── pages │ ├── auth │ ├── index.json │ ├── index.wxss │ ├── index.wxml │ └── index.js │ ├── cache │ ├── index.json │ ├── index.wxss │ ├── index.wxml │ └── index.js │ ├── fetch │ ├── index.json │ ├── index.wxss │ ├── index.wxml │ └── index.js │ ├── poster │ ├── index.wxss │ ├── index.json │ ├── index.wxml │ └── index.js │ ├── cascader │ ├── index.wxss │ ├── index.json │ ├── index.wxml │ └── index.js │ ├── countdown │ ├── index.wxss │ ├── index.json │ ├── index.wxml │ └── index.js │ └── customer │ ├── index.json │ ├── index.js │ ├── index.wxss │ └── index.wxml ├── components ├── auth │ ├── index.json │ ├── index.wxml │ ├── index.wxss │ └── index.js ├── poster │ ├── index.json │ ├── index.wxss │ ├── index.wxml │ ├── panel.js │ └── index.js ├── cascader │ ├── index.json │ ├── index.wxml │ ├── index.wxss │ └── index.js ├── countdown │ ├── index.json │ ├── index.wxss │ ├── index.wxml │ └── index.js └── navigationBar │ ├── index.json │ ├── source │ ├── back.png │ ├── home.png │ ├── logo.png │ └── logo-slack.png │ ├── index.wxml │ ├── index.wxss │ └── index.js ├── assets └── d.jpeg ├── .gitignore ├── sitemap.json ├── app.wxss ├── libs ├── config.js ├── pageDecorator.js ├── page.js ├── cache.js ├── wxauth.js └── util.js ├── package.json ├── app.js ├── app.json ├── project.config.json └── README.md /core/apis/user.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pages/home/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } -------------------------------------------------------------------------------- /package/extend/pages/auth/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } -------------------------------------------------------------------------------- /package/extend/pages/cache/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } -------------------------------------------------------------------------------- /package/extend/pages/fetch/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } -------------------------------------------------------------------------------- /package/extend/pages/auth/index.wxss: -------------------------------------------------------------------------------- 1 | /* package/extend/pages/activity/index.wxss */ -------------------------------------------------------------------------------- /package/extend/pages/cache/index.wxss: -------------------------------------------------------------------------------- 1 | /* package/extend/pages/cache/index.wxss */ -------------------------------------------------------------------------------- /package/extend/pages/fetch/index.wxss: -------------------------------------------------------------------------------- 1 | /* package/extend/pages/fetch/index.wxss */ -------------------------------------------------------------------------------- /package/extend/pages/poster/index.wxss: -------------------------------------------------------------------------------- 1 | /* package/extend/pages/poster/index.wxss */ -------------------------------------------------------------------------------- /components/auth/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /components/poster/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /package/extend/pages/cascader/index.wxss: -------------------------------------------------------------------------------- 1 | /* package/extend/pages/cascader/index.wxss */ -------------------------------------------------------------------------------- /package/extend/pages/countdown/index.wxss: -------------------------------------------------------------------------------- 1 | /* package/extend/pages/countdown/index.wxss */ -------------------------------------------------------------------------------- /components/cascader/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /components/countdown/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /assets/d.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoweiBlog/wechat-miniprogram-dev/HEAD/assets/d.jpeg -------------------------------------------------------------------------------- /components/navigationBar/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /core/apis/order.js: -------------------------------------------------------------------------------- 1 | export default { 2 | getOrderList: { 3 | url: '/channel/list', 4 | method: 'GET', 5 | } 6 | } -------------------------------------------------------------------------------- /core/apis/index.js: -------------------------------------------------------------------------------- 1 | import order from './order.js' 2 | import user from './user.js' 3 | 4 | export default { 5 | order, 6 | user, 7 | } -------------------------------------------------------------------------------- /package/extend/pages/fetch/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | package/extend/pages/fetch/index.wxml 3 | -------------------------------------------------------------------------------- /package/extend/pages/poster/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "poster": "../../../../../components/poster/index" 4 | } 5 | } -------------------------------------------------------------------------------- /components/navigationBar/source/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoweiBlog/wechat-miniprogram-dev/HEAD/components/navigationBar/source/back.png -------------------------------------------------------------------------------- /components/navigationBar/source/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoweiBlog/wechat-miniprogram-dev/HEAD/components/navigationBar/source/home.png -------------------------------------------------------------------------------- /components/navigationBar/source/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoweiBlog/wechat-miniprogram-dev/HEAD/components/navigationBar/source/logo.png -------------------------------------------------------------------------------- /package/extend/pages/cascader/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "cascader": "../../../../../components/cascader/index" 4 | } 5 | } -------------------------------------------------------------------------------- /package/extend/pages/countdown/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "countdown": "../../../../../components/countdown/index" 4 | } 5 | } -------------------------------------------------------------------------------- /components/navigationBar/source/logo-slack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoweiBlog/wechat-miniprogram-dev/HEAD/components/navigationBar/source/logo-slack.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | # Editor directories and files 5 | .idea 6 | .vscode 7 | *.suo 8 | *.ntvs* 9 | *.njsproj 10 | *.sln 11 | *.sw* -------------------------------------------------------------------------------- /components/poster/index.wxss: -------------------------------------------------------------------------------- 1 | .poster-canvas { 2 | position: absolute; 3 | top: 0; 4 | z-index: -1; 5 | left: 0; 6 | transform: translate3d(-10000px, 0, 0); 7 | } -------------------------------------------------------------------------------- /package/extend/pages/customer/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "navigation-bar": "../../../../../components/navigationBar/index" 4 | }, 5 | "navigationStyle": "custom" 6 | } -------------------------------------------------------------------------------- /components/countdown/index.wxss: -------------------------------------------------------------------------------- 1 | /* components/countdown/index.wxss */ 2 | .cm-countdown-box, .cm-countdown { 3 | display: inline-block; 4 | } 5 | .cm-countdown-box text { 6 | display: inline-block; 7 | } -------------------------------------------------------------------------------- /sitemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", 3 | "rules": [{ 4 | "action": "allow", 5 | "page": "pages/home/index" 6 | }] 7 | } -------------------------------------------------------------------------------- /app.wxss: -------------------------------------------------------------------------------- 1 | /**app.wxss**/ 2 | 3 | page { 4 | display: block; 5 | min-height: 100%; 6 | background-color: #fff; 7 | color: #353535; 8 | font-size: 30rpx; 9 | } 10 | 11 | view, text { 12 | box-sizing: border-box; 13 | } -------------------------------------------------------------------------------- /components/poster/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /libs/config.js: -------------------------------------------------------------------------------- 1 | // api version 2 | export const API_VERSION = 1 3 | 4 | // keys 5 | export const KEYS = { 6 | map: 'gjaiowjgowajido23j0r24u9304', 7 | } 8 | 9 | // domain 10 | export const DM = { 11 | domain: 'https://xxx.com', 12 | // domain: 'https://jowei.com' 13 | } -------------------------------------------------------------------------------- /pages/home/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{item.title}} 6 | {{item.desc}} 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /package/extend/pages/customer/index.js: -------------------------------------------------------------------------------- 1 | Page({ 2 | data: { 3 | barBg: 'rgba(255,255,255,0)' 4 | }, 5 | onLoad() {}, 6 | onPageScroll(v) { 7 | const limit = 200 8 | const opacity = (Math.max(0, v.scrollTop) / limit).toFixed(2) 9 | this.setData({ 10 | barBg: `rgba(255,255,255, ${opacity})` 11 | }) 12 | } 13 | }) -------------------------------------------------------------------------------- /package/extend/pages/cascader/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /components/countdown/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{item.n}} 6 | {{item.s}} 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /pages/home/index.wxss: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 30rpx; 3 | } 4 | 5 | .item { 6 | display: block; 7 | box-shadow: 0 1px 20px rgba(0,0,0,.1); 8 | padding: 24rpx 32rpx; 9 | margin-bottom: 24rpx; 10 | } 11 | 12 | .item .title { 13 | font-size: 36rpx; 14 | font-weight: bold; 15 | } 16 | 17 | .item .desc { 18 | padding-top: 6rpx; 19 | font-size: 26rpx; 20 | color: #999; 21 | } -------------------------------------------------------------------------------- /package/extend/pages/customer/index.wxss: -------------------------------------------------------------------------------- 1 | .pane .pic { 2 | width: 100%; 3 | display: block; 4 | } 5 | 6 | .pane .box { 7 | overflow: hidden; 8 | border-bottom-left-radius: 120rpx; 9 | } 10 | 11 | .pane .name { 12 | font-size: 44rpx; 13 | font-weight: bold; 14 | color: #000; 15 | padding: 48rpx 48rpx 24rpx 48rpx; 16 | } 17 | 18 | .pane .desc { 19 | padding: 0 48rpx 24rpx 48rpx; 20 | font-size: 26rpx; 21 | color: #777; 22 | } 23 | -------------------------------------------------------------------------------- /package/extend/pages/poster/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | {{(!img) ? '资源图' : '海报'}} 14 | 15 | 16 | -------------------------------------------------------------------------------- /libs/pageDecorator.js: -------------------------------------------------------------------------------- 1 | // Page 装饰器 2 | const oriPage = Page 3 | 4 | export default Page = (data) => { 5 | 6 | // 页面加载统计 PV 7 | const onLoad = data.onLoad; 8 | data.onLoad = function(...args) { 9 | RecordPV.call(this) 10 | return onLoad && onLoad.call(this, ...args) 11 | } 12 | 13 | oriPage(data) 14 | } 15 | 16 | // Record PV 17 | function RecordPV() { 18 | wx.showToast({ 19 | title: 'pv.', 20 | }) 21 | console.log('view the page : ', this.route) 22 | // sync serve.. 23 | // fetch(apis.pv, data: { page: this.route }) 24 | } -------------------------------------------------------------------------------- /package/extend/pages/auth/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | :> 调用微信需要授权的api(如userLocation等)时,提前预检查其授权状态, 9 | 若未授权将自动调起对应授权弹窗,授权完成继续执行。 10 | 11 | 12 | 13 | :> 提示: 该授权过程为 强授权,未授权用户将无法继续其他操作, 若较注重用户体验 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /package/extend/pages/countdown/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 1::: 3 | 10 | 11 | 2::: 12 | 21 | -------------------------------------------------------------------------------- /package/extend/pages/countdown/index.js: -------------------------------------------------------------------------------- 1 | Page({ 2 | data: { 3 | timer: { 4 | time: 10, // s 5 | format: 'dd天hh时mm分ss秒', 6 | sign: 'TIMER_1' // 当单页多个组件使用时需要 标识每个组件 7 | }, 8 | count: { 9 | time: 43895, 10 | format: 'hh:mm:ss', 11 | timeStyle: 'color: red', 12 | symbolStyle: 'padding: 0 5px;', 13 | sign: 'TIMER_2' 14 | } 15 | }, 16 | 17 | onRunning(v) { 18 | const {detail = {}} = v 19 | console.log(detail.sign, detail.time) 20 | }, 21 | 22 | onEnd(v) { 23 | const { detail = {} } = v 24 | console.log(detail.sign, '结束.') 25 | }, 26 | 27 | }) 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-program---template", 3 | "version": "1.0.0", 4 | "description": "template", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@gitlab.com/Joweiblog/mini-program---template.git" 12 | }, 13 | "keywords": [ 14 | "mini-program" 15 | ], 16 | "author": "joweiblog@163.com", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://gitlab.com/Joweiblog/mini-program---template/issues" 20 | }, 21 | "homepage": "https://gitlab.com/Joweiblog/mini-program---template#readme" 22 | } 23 | -------------------------------------------------------------------------------- /package/extend/pages/fetch/index.js: -------------------------------------------------------------------------------- 1 | import fetch, { apis } from '../../../../core/fetch.js' 2 | 3 | Page({ 4 | data: { 5 | 6 | }, 7 | 8 | getSomething() { 9 | // req 10 | fetch({ 11 | ...apis.order.getOrderList, 12 | data: { 13 | page: 1, 14 | page_size: 10 15 | }, 16 | succ: (res) => { 17 | console.log(res) 18 | }, 19 | fail: (err) => { 20 | console.log(err) 21 | }, 22 | done: () => { 23 | wx.showModal({ 24 | title: 'fetch已结束', 25 | content: '', 26 | }) 27 | } 28 | }) 29 | }, 30 | 31 | onLoad() { 32 | this.getSomething() 33 | } 34 | }) 35 | -------------------------------------------------------------------------------- /libs/page.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * PageModal 4 | * 页面栈,自行添加需要通信的页面Page 5 | * 6 | * # page a 7 | * onLoad: app.pages.add(this) 8 | * onUnload:app.pages.delete(this) 9 | * 10 | * # page c 11 | * app.pages.get('/pages/a/index').doSomething() 12 | * 13 | */ 14 | 15 | export default class PageModal { 16 | constructor () { 17 | this.cache = {} 18 | } 19 | 20 | add (page) { 21 | let path = this._getPath(page) 22 | this.cache[path] = page 23 | } 24 | 25 | get (path) { 26 | return this.cache[path] || null 27 | } 28 | 29 | delete (page) { 30 | try { 31 | delete this.cache[this._getPath(page)] 32 | } catch(e) {} 33 | } 34 | 35 | _getPath (page) { 36 | return page.__route__ 37 | } 38 | } -------------------------------------------------------------------------------- /package/extend/pages/cache/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | user id:: 7 | 8 | {{user.id || ''}} 9 | 10 | 11 | 12 | 13 | 14 | 15 | share id:: 16 | 17 | {{shareId || ''}} 18 | 19 | 20 | 21 | 22 | 23 | 24 | id:: 25 | 26 | {{id || ''}} 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /components/auth/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 申请获取以下权限 10 | {{scopeDesc || ''}} 11 | 12 | 13 | 14 | 20 | 确认授权 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | import PageModal from 'libs/page.js' 2 | import 'libs/pageDecorator.js' 3 | 4 | App({ 5 | // 系统信息 6 | sysInfo: null, 7 | // 页面栈, 注意使用:仅需要通信的页面加入栈中 8 | pages: new PageModal(), 9 | 10 | onLaunch(opt) { 11 | // 检查新版本 12 | checkVersion() 13 | 14 | // 获取设备信息 15 | this.sysInfo = wx.getSystemInfoSync() 16 | }, 17 | 18 | // 404 redirect 19 | onPageNotFound(res) { 20 | wx.switchTab({ 21 | url: '/pages/home/index', 22 | }) 23 | }, 24 | }) 25 | 26 | /** 27 | * checkVersionn 28 | */ 29 | function checkVersion() { 30 | if (wx.canIUse('getUpdateManager')) { 31 | const updateManager = wx.getUpdateManager() 32 | updateManager.onCheckForUpdate(function (res) { }) 33 | updateManager.onUpdateReady(function () { 34 | wx.showModal({ 35 | title: '更新提示', 36 | content: '新版本已经准备好,重启体验新功能?', 37 | success: function (res) { 38 | if (res.confirm) { 39 | updateManager.applyUpdate() 40 | } 41 | } 42 | }) 43 | }) 44 | } 45 | } -------------------------------------------------------------------------------- /components/cascader/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | function outLabel(item, label = 'label') { 3 | return item[label]; 4 | } 5 | 6 | module.exports.outLabel = outLabel; 7 | 8 | 9 | 10 | 11 | 12 | 13 | 取消 14 | 确认 15 | 16 | 17 | 18 | {{tls.outLabel(item, _props.label)}} 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /package/extend/pages/cache/index.js: -------------------------------------------------------------------------------- 1 | import $cache, * as caches from '../../../../libs/cache.js' 2 | 3 | Page({ 4 | data: { 5 | user: '', 6 | shareId: '', 7 | id: '', 8 | }, 9 | 10 | setUserCache () { 11 | const user = { 12 | id: Math.random(), 13 | user: 'Jowei' 14 | } 15 | $cache.set(caches.USER_INFO, user) 16 | getApp().pages.get('pages/home/index').doSomething() 17 | }, 18 | 19 | getUserCache() { 20 | this.setData({ 21 | user: $cache.get(caches.USER_INFO) 22 | }) 23 | }, 24 | 25 | setShareCache() { 26 | $cache.session.set(caches.SHARE_ID, Math.random()) 27 | }, 28 | 29 | getShareCache() { 30 | this.setData({ 31 | shareId: $cache.session.get(caches.SHARE_ID) 32 | }) 33 | }, 34 | 35 | setIdCache() { 36 | $cache.set('id', Math.random(), 10000) // 10s后过期 37 | }, 38 | 39 | getIdCache() { 40 | this.setData({ 41 | id: $cache.get('id') 42 | }) 43 | }, 44 | 45 | onLoad() { 46 | console.log(getApp().pages) 47 | this.getUserCache() 48 | this.getShareCache() 49 | this.getIdCache() 50 | } 51 | }) -------------------------------------------------------------------------------- /package/extend/pages/cascader/index.js: -------------------------------------------------------------------------------- 1 | Page({ 2 | data: { 3 | city: { 4 | options: [ 5 | { 6 | id: 12, 7 | name: 'shanghai', 8 | child: [ 9 | { 10 | id: 121, 11 | name: 'xuhui' 12 | }, { 13 | id: 122, 14 | name: 'putuo' 15 | } 16 | ] 17 | }, { 18 | id: 13, 19 | name: 'beijing', 20 | child: [ 21 | { 22 | id: 131, 23 | name: 'nanhui' 24 | }, { 25 | id: 132, 26 | name: 'pingcheng' 27 | } 28 | ] 29 | } 30 | ], 31 | props: { 32 | label: 'name', 33 | value: 'id', 34 | children: 'child' 35 | }, 36 | display: false, 37 | value: [] // 最终选择的值 38 | }, 39 | }, 40 | 41 | choose() { 42 | this.setData({ 43 | 'city.display': true 44 | }) 45 | }, 46 | 47 | onClose(v) { 48 | const { detail = {} } = v 49 | this.setData({ 50 | 'city.display': false, 51 | 'city.value': detail.valueIns || [] 52 | }) 53 | console.log(detail) 54 | }, 55 | }) 56 | -------------------------------------------------------------------------------- /package/extend/pages/auth/index.js: -------------------------------------------------------------------------------- 1 | import WxAuth from '../../../../libs/wxauth.js' 2 | 3 | // Mng 4 | const recorderMng = wx.getRecorderManager() 5 | 6 | Page({ 7 | data: { 8 | // 预检查授权相关 9 | authDisplay: false, 10 | authScope: '', 11 | }, 12 | 13 | // 检查 record 权限 14 | checkRecordPerm() { 15 | this.wxa.checkScope({ 16 | scope: 'scope.record', 17 | done() { 18 | console.log('done record auth'); 19 | wx.showToast({ 20 | title: '可以用record', 21 | }) 22 | // eg: 开始录制 23 | // recorderMng.start({ 24 | // duration: 60000, 25 | // format: 'mp3' 26 | // }) 27 | } 28 | }) 29 | }, 30 | 31 | // 检查 userInfo 权限 32 | checkUserInfoPerm() { 33 | this.wxa.checkScope({ 34 | scope: 'scope.userInfo', 35 | done(res) { 36 | console.log('done userinfo auth,', res) 37 | // sync serve.. 38 | wx.showToast({ 39 | title: '可以用userinfo~', 40 | }) 41 | } 42 | }) 43 | }, 44 | 45 | // 监听授权 46 | _onAuthDone(e) { 47 | this.wxa.checkScope(e) 48 | }, 49 | 50 | onLoad: function () { 51 | this.wxa = new WxAuth(this, 'authScope', 'authDisplay') 52 | }, 53 | }) 54 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/home/index" 4 | ], 5 | "subpackages": [ 6 | { 7 | "root": "package/extend", 8 | "name": "extendPkg", 9 | "pages": [ 10 | "pages/auth/index", 11 | "pages/countdown/index", 12 | "pages/cascader/index", 13 | "pages/poster/index", 14 | "pages/fetch/index", 15 | "pages/cache/index", 16 | "pages/customer/index" 17 | ] 18 | } 19 | ], 20 | "preloadRule": { 21 | "pages/home/index": { 22 | "network": "all", 23 | "packages": [ 24 | "extendPkg" 25 | ] 26 | } 27 | }, 28 | "usingComponents": { 29 | "auth": "/components/auth/index" 30 | }, 31 | "window": { 32 | "navigationBarBackgroundColor": "#FFFFFF", 33 | "navigationBarTextStyle": "black", 34 | "navigationBarTitleText": "模板", 35 | "backgroundColor": "#FFFFFF", 36 | "backgroundTextStyle": "light", 37 | "enablePullDownRefresh": false 38 | }, 39 | "networkTimeout": { 40 | "request": 10000, 41 | "connectSocket": 10000, 42 | "uploadFile": 15000, 43 | "downloadFile": 15000 44 | }, 45 | "navigateToMiniProgramAppIdList": [], 46 | "permission": { 47 | "scope.userLocation": { 48 | "desc": "您的位置信息将用于小程序获取附近地址" 49 | } 50 | }, 51 | "debug": false, 52 | "sitemapLocation": "sitemap.json" 53 | } -------------------------------------------------------------------------------- /project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目配置文件", 3 | "packOptions": { 4 | "ignore": [] 5 | }, 6 | "setting": { 7 | "urlCheck": false, 8 | "es6": true, 9 | "postcss": true, 10 | "minified": true, 11 | "newFeature": true, 12 | "autoAudits": false, 13 | "uglifyFileName": true, 14 | "checkInvalidKey": true, 15 | "checkSiteMap": true, 16 | "uploadWithSourceMap": true, 17 | "babelSetting": { 18 | "ignore": [], 19 | "disablePlugins": [], 20 | "outputPath": "" 21 | }, 22 | "nodeModules": true 23 | }, 24 | "compileType": "miniprogram", 25 | "libVersion": "2.6.5", 26 | "appid": "wxa033a21c32aa5b81", 27 | "projectname": "mini-program---template", 28 | "debugOptions": { 29 | "hidedInDevtools": [] 30 | }, 31 | "isGameTourist": false, 32 | "simulatorType": "wechat", 33 | "simulatorPluginLibVersion": {}, 34 | "condition": { 35 | "search": { 36 | "current": -1, 37 | "list": [] 38 | }, 39 | "conversation": { 40 | "current": -1, 41 | "list": [] 42 | }, 43 | "plugin": { 44 | "current": -1, 45 | "list": [] 46 | }, 47 | "game": { 48 | "currentL": -1, 49 | "list": [] 50 | }, 51 | "miniprogram": { 52 | "current": 0, 53 | "list": [ 54 | { 55 | "id": -1, 56 | "name": "customer", 57 | "pathName": "package/extend/pages/customer/index", 58 | "scene": null 59 | } 60 | ] 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /components/navigationBar/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 31 | 32 | Dots 33 | 34 | 35 | -------------------------------------------------------------------------------- /package/extend/pages/poster/index.js: -------------------------------------------------------------------------------- 1 | // 海报配置 2 | const config = { 3 | width: 750, 4 | height: 1200, 5 | image: [ 6 | { 7 | url: 'https://joweiblog.oss-cn-shanghai.aliyuncs.com/x.png', 8 | width: 750, 9 | height: 1000, 10 | x: 0, 11 | y: 0, 12 | borderRadius: 50, 13 | borderWidth: 1, 14 | zIndex: 2, 15 | } 16 | ], 17 | text: [ 18 | { 19 | text: 'I am Groot.', 20 | x: 375, 21 | y: 1050, 22 | color: '#fff', 23 | fontSize: 48, 24 | textAlign: 'center', 25 | zIndex: 4 26 | } 27 | ], 28 | block: [ 29 | { 30 | width: 750, 31 | height: 300, 32 | x: 0, 33 | y: 900, 34 | borderRadius: 50, 35 | borderWidth: 1, 36 | bgColor: { 37 | type: 'linear', 38 | x1: 0, 39 | y1: 1200, 40 | x2: 0, 41 | y2: 900, 42 | colorStep: [ 43 | [0, '#717b9c'], 44 | [1, '#283049'], 45 | ] 46 | }, 47 | zIndex: 3, 48 | }, 49 | { 50 | width: 700, 51 | height: 1150, 52 | x: 25, 53 | y: 25, 54 | borderRadius: 50, 55 | borderWidth: 1, 56 | borderColor: '#fff', 57 | zIndex: 4, 58 | }, 59 | ] 60 | } 61 | 62 | Page({ 63 | data: { 64 | config: config, 65 | ori: 'https://joweiblog.oss-cn-shanghai.aliyuncs.com/x.png', 66 | img: '', 67 | autoMake: false, 68 | }, 69 | 70 | onMakeSucc(v) { 71 | this.setData({ 72 | 'img': v.detail || '' 73 | }) 74 | }, 75 | 76 | onMakeFail(e) { 77 | console.log(e) 78 | }, 79 | }) 80 | 81 | 82 | -------------------------------------------------------------------------------- /pages/home/index.js: -------------------------------------------------------------------------------- 1 | // home 2 | Page({ 3 | data: { 4 | log: '', 5 | spec: [ 6 | { 7 | title: 'fetch', 8 | desc: '异步请求, 以及api模块化', 9 | url: '/package/extend/pages/fetch/index' 10 | }, 11 | { 12 | title: 'cache', 13 | desc: '模拟 local / session / expire storage 等', 14 | url: '/package/extend/pages/cache/index' 15 | }, 16 | { 17 | title: 'page', 18 | desc: '页面栈通信', 19 | url: '/' 20 | }, 21 | { 22 | title: 'page decorator', 23 | desc: 'Page装饰器实例,如:该例每个页面pv统计;', 24 | url: '/' 25 | }, 26 | { 27 | title: '[组件] 预检强授权', 28 | desc: '提前检查授权状态,(已)授权后将完成原有操作', 29 | url: '/package/extend/pages/auth/index' 30 | }, 31 | { 32 | title: '[组件] 自定义navigationBar', 33 | desc: '自定义 ', 34 | url: '/package/extend/pages/customer/index' 35 | }, 36 | { 37 | title: '[组件] 倒计时', 38 | desc: '提供简易倒计时功能', 39 | url: '/package/extend/pages/countdown/index' 40 | }, 41 | { 42 | title: '[组件] 生成海报', 43 | desc: '提供配置生成海报、分享图组件', 44 | url: '/package/extend/pages/poster/index' 45 | }, 46 | { 47 | title: '[组件] 级联选择', 48 | desc: '提供底部弹窗类型级联选择功能', 49 | url: '/package/extend/pages/cascader/index' 50 | }, 51 | ] 52 | }, 53 | doSomething() { 54 | this.setData({ 55 | log: 'log changed' 56 | }) 57 | }, 58 | onLoad() { 59 | getApp().pages.add(this) 60 | }, 61 | onUnload() { 62 | getApp().pages.delete(this) 63 | } 64 | }) -------------------------------------------------------------------------------- /components/cascader/index.wxss: -------------------------------------------------------------------------------- 1 | .cm-cascading-picker { 2 | position: fixed; 3 | z-index: 99; 4 | top: 0; 5 | bottom: 0; 6 | left: 0; 7 | right: 0; 8 | opacity: 0; 9 | visibility: hidden; 10 | transition: all 0.25s linear; 11 | } 12 | 13 | .cm-cascading-picker .pane { 14 | position: absolute; 15 | z-index: 2; 16 | left: 0; 17 | right: 0; 18 | bottom: 0; 19 | background: #fff; 20 | opacity: 0; 21 | transition: all 0.24s ease-out; 22 | transform: translateY(200rpx); 23 | } 24 | 25 | .cm-cascading-picker .mask { 26 | position: absolute; 27 | z-index: 1; 28 | top: 0; 29 | left: 0; 30 | right: 0; 31 | bottom: 0; 32 | opacity: 0; 33 | transition: all 0.2s linear; 34 | background: rgba(0, 0, 0, 0.1); 35 | backdrop-filter: blur(2px); 36 | -webkit-backdrop-filter: blur(2px); 37 | } 38 | 39 | .cm-cascading-picker.show, .cm-cascading-picker.show .mask { 40 | opacity: 1; 41 | visibility: visible; 42 | } 43 | 44 | .cm-cascading-picker.show .pane { 45 | opacity: 1; 46 | transform: translateY(0); 47 | } 48 | 49 | 50 | /* 时间-取消确定按钮 */ 51 | .cm-cascading-picker .pane .btns { 52 | position: relative; 53 | overflow: hidden; 54 | } 55 | 56 | .cm-cascading-picker .pane .btns .btn { 57 | color: #999; 58 | font-size: 28rpx; 59 | padding: 35rpx; 60 | } 61 | 62 | .cm-cascading-picker .pane .btns .btn.cancel { 63 | float: left; 64 | color: #999; 65 | } 66 | 67 | .cm-cascading-picker .pane .btns .btn.confirm { 68 | float: right; 69 | color: #333; 70 | } 71 | 72 | .cm-cascading-picker .pane picker-view { 73 | position: relative; 74 | /* display: flex; */ 75 | height: 500rpx; 76 | width: 100%; 77 | text-align: center; 78 | background: #f5f5f5; 79 | } 80 | 81 | .cm-cascading-picker-item { 82 | height: 100rpx; 83 | } 84 | -------------------------------------------------------------------------------- /core/fetch.js: -------------------------------------------------------------------------------- 1 | import { DM, API_VERSION } from '../libs/config.js' 2 | import Apis from './apis/index.js' 3 | 4 | // default 5 | const defaultHeader = { 6 | 'content-type': 'application/json' 7 | } 8 | 9 | const defaultBody = { 10 | protocol: API_VERSION 11 | } 12 | 13 | // apis 14 | export const apis = Apis 15 | 16 | // fetch 17 | export default function fetch({ 18 | url = '', 19 | method = 'GET', 20 | data = {}, 21 | header = {}, 22 | succ = noop, 23 | fail = noop, 24 | done = noop 25 | } = {}) { 26 | const reqTask = wx.request({ 27 | url: `${DM.domain}${url}`, 28 | data: { 29 | ...defaultBody, 30 | ...data 31 | }, 32 | header: { 33 | ...defaultHeader, 34 | ...header 35 | }, 36 | method, 37 | success(response) { 38 | const res = response.data 39 | // rules 40 | if (res.resultStatus.code === 1000) { 41 | succ(res) 42 | } else { 43 | succ(null) 44 | } 45 | }, 46 | fail(err) { 47 | isNetworkError((retry) => { 48 | fail(err) // 网络问题, 重试(暂无重试机制) 49 | }, () => { 50 | fail(err) 51 | }) 52 | }, 53 | complete: done 54 | }) 55 | 56 | return reqTask 57 | } 58 | 59 | /** 60 | * 检查网络 61 | * */ 62 | function isNetworkError(resolve = noop, reject = noop) { 63 | wx.getNetworkType({ 64 | success(res) { 65 | if (res.networkType === 'none') { 66 | wx.hideLoading() 67 | wx.showModal({ 68 | title: '提示', 69 | content: '网络中断,建议检查网络连接', 70 | confirmText: '稍后重试', 71 | showCancel: false, 72 | success(res) { 73 | resolve(res.confirm) 74 | } 75 | }) 76 | } else { 77 | reject() 78 | } 79 | } 80 | }) 81 | } 82 | 83 | function noop () {} 84 | -------------------------------------------------------------------------------- /package/extend/pages/customer/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Alexandra Gorn 9 | 10 | brown wooden framed white padded chair in between green indoor leaf plants inside bedroom. 11 | black alarm clock at 10:10 on white wooden table near table. 12 | brown wooden framed white padded chair in between green indoor leaf plants inside bedroom. 13 | brown wooden framed white padded chair in between green indoor leaf plants inside bedroom. 14 | 15 | 16 | gray table lamp beside white bed pillow 17 | brown wooden framed white padded chair in between green indoor leaf plants inside bedroom. 18 | black alarm clock at 10:10 on white wooden table near table. 19 | 20 | 21 | gray table lamp beside white bed pillow 22 | brown wooden framed white padded chair in between green indoor leaf plants inside bedroom. 23 | black alarm clock at 10:10 on white wooden table near table. 24 | 25 | 26 | brown wooden framed white padded chair in between green indoor leaf plants inside bedroom. 27 | black alarm clock at 10:10 on white wooden table near table. 28 | brown wooden framed white padded chair in between green indoor leaf plants inside bedroom. 29 | brown wooden framed white padded chair in between green indoor leaf plants inside bedroom. 30 | 31 | 32 | gray table lamp beside white bed pillow 33 | brown wooden framed white padded chair in between green indoor leaf plants inside bedroom. 34 | black alarm clock at 10:10 on white wooden table near table. 35 | 36 | 37 | -------------------------------------------------------------------------------- /libs/cache.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * cache 4 | * 5 | * # localstorage 6 | * cache.set('n', 1) 7 | * cache.get('n') // 1 8 | * 9 | * # localstorage expire 10 | * cache.set('n', 1, 10000) 11 | * cache.get('n') // 1 ,10s内访问有效 12 | * 13 | * # localstorage 延续上次缓存时间,若上次没缓存,此次设置也将取消(return) 14 | * cache.set('n', 1, true) 15 | * cache.get('n') // 1 16 | * 17 | * # sessionstorage 本次启动有效 18 | * cache.session.set('n', 1) 19 | * cache.session.get('n') // 1 20 | * 21 | * # remove 22 | * cache.remove('n') 23 | * cache.session.remove('') 24 | * 25 | */ 26 | 27 | const sessionId = +new Date() 28 | const cache = { 29 | session: { 30 | set: (key, value) => cache.set(`session_${key}`, value, -1*sessionId), 31 | get: (key) => cache.get(`session_${key}`), 32 | remove: (key) => cache.remove(`session_${key}`), 33 | }, 34 | 35 | set: (key, value, expire) => { 36 | let o = { 37 | expr: 0, 38 | data: value, 39 | } 40 | 41 | if (expire === true) { 42 | const _c = wx.getStorageSync(`_cache_${key}`) 43 | if (!_c) return 44 | o.expr = _c.expr || 0 45 | } else { 46 | let _expire = expire || 0 47 | if (_expire > 0) { 48 | let t = +new Date() 49 | _expire += t 50 | } 51 | o.expr = +_expire 52 | } 53 | 54 | wx.setStorageSync(`_cache_${key}`, o) 55 | }, 56 | 57 | get: (key) => { 58 | const k = `_cache_${key}` 59 | const v = wx.getStorageSync(k) 60 | if (!v) return null 61 | // 永久存储 62 | if (!v.expr) return v.data 63 | else { 64 | if (v.expr > 0 && new Date() < v.expr) { 65 | return v.data 66 | } else if (v.expr < 0 && (v.expr*-1) === sessionId) { 67 | return v.data 68 | } else { 69 | wx.removeStorage({ key: k }) 70 | return null 71 | } 72 | } 73 | }, 74 | 75 | remove: (key) => { 76 | wx.removeStorageSync(`_cache_${key}`) 77 | } 78 | } 79 | 80 | export default cache 81 | 82 | // cache config 83 | export const USER_INFO = 'userInfo' 84 | export const SHARE_ID = 'shareId' 85 | 86 | // export const .. -------------------------------------------------------------------------------- /libs/wxauth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 微信授权类 3 | * 4 | * scopes: 5 | * scope.userInfo wx.getUserInfo 用户信息 6 | * scope.userLocation wx.getLocation, wx.chooseLocation, wx.openLocation 地理位置 7 | * scope.address wx.chooseAddress 通讯地址 8 | * scope.invoiceTitle wx.chooseInvoiceTitle 发票抬头 9 | * scope.werun wx.getWeRunData 微信运动步数 10 | * scope.record wx.startRecord 录音功能 11 | * scope.writePhotosAlbum wx.saveImageToPhotosAlbum, wx.saveVideoToPhotosAlbum 保存到相册 12 | * scope.camera 摄像头 13 | */ 14 | 15 | export default class WxAuth { 16 | /** 17 | * @ cmScope:关联页面auth组件 scope 参数 18 | * @ cmDisplay:关联页面auth组件 display 参数 19 | * 20 | */ 21 | constructor(that, cmScope, cmDisplay) { 22 | this.cmScope = cmScope 23 | this.cmDisplay = cmDisplay 24 | this.cmDone = () => { } 25 | this._setData = this._setData.bind(that) 26 | } 27 | 28 | /** 29 | * Evt: checkScopeStatus 30 | * 检查授权状态 31 | * @param scope : '' 32 | * @param cb : ([Boolean flag]) => {} 33 | * 34 | */ 35 | _checkScopeStatus(scope = '', cb = () => { }) { 36 | wx.getSetting({ 37 | success: (res) => { 38 | const flag = res.authSetting[scope] 39 | if (scope === 'scope.userInfo' || scope === 'scope.userPhone') { 40 | cb(false) 41 | } else if (flag === undefined) { 42 | // 初次授权 43 | wx.authorize({ 44 | scope, 45 | success() { cb(true) }, 46 | fail() { cb(false) } 47 | }) 48 | } else { 49 | cb(flag) // 未授权 || 已授权 50 | } 51 | }, 52 | fail: () => { cb(false) } 53 | }) 54 | } 55 | 56 | /** 57 | * setData 58 | */ 59 | _setData(k, v) { 60 | const o = Object.assign({}, this.data || {}) 61 | o[k] = v 62 | this.setData(o) 63 | } 64 | 65 | /** 66 | * Evt: checkScope 67 | * 检查授权状态 68 | * @param scope : auth scope 69 | * @param detail : auth success body 70 | * @param done : auth success callback 71 | * 72 | */ 73 | checkScope({ 74 | scope = '', 75 | detail = {}, 76 | done = () => { }, 77 | } = {}) { 78 | this._setData(this.cmScope, detail.scope || scope) 79 | if (detail.scope) { 80 | this._setData(this.cmDisplay, false) 81 | this.cmDone(detail.result) 82 | } else { 83 | this.cmDone = done 84 | this._checkScopeStatus(scope, (flag) => { 85 | this._setData(this.cmDisplay, !flag) 86 | flag && done() 87 | }) 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /components/navigationBar/index.wxss: -------------------------------------------------------------------------------- 1 | view, cover-view, cover-image { 2 | box-sizing: border-box; 3 | } 4 | 5 | .navigation-bar { 6 | /* height: 176rpx; */ 7 | } 8 | 9 | .navigation-bar .wrapper { 10 | position: fixed; 11 | top: 0; 12 | left: 0; 13 | right: 0; 14 | max-height: 160rpx; 15 | box-sizing: content-box; 16 | text-align: left; 17 | z-index: 999; 18 | background: transparent; 19 | } 20 | 21 | .navigation-bar .box { 22 | position: absolute; 23 | left: 0; 24 | right: 0; 25 | top: 0; 26 | bottom: 0; 27 | /* background: #333; */ 28 | } 29 | 30 | .navigation-bar .box .tools { 31 | position: absolute; 32 | top: 0; 33 | bottom: 0; 34 | left: 0; 35 | } 36 | 37 | /* dot */ 38 | .navigation-bar .box .tools .dot { 39 | position: relative; 40 | display: flex; 41 | height: 100%; 42 | align-items: center; 43 | box-sizing: border-box; 44 | background: rgba(255,255,255,.5); 45 | border: 1rpx solid rgba(0,0,0,.1); 46 | border-radius: 60rpx; 47 | } 48 | 49 | .navigation-bar .box .tools .dot .dot-i { 50 | position: relative; 51 | flex: 1; 52 | display: flex; 53 | align-items: center; 54 | justify-content: center; 55 | } 56 | 57 | .navigation-bar .box .tools .dot .dot-line { 58 | flex-shrink: 0; 59 | width: 1rpx; 60 | /* border: 1rpx solid #333; */ 61 | background: rgba(0,0,0,.08); 62 | height: 55%; 63 | } 64 | 65 | .navigation-bar .box .tools .dot .dot-i cover-image { 66 | width: 32rpx; 67 | height: 32rpx; 68 | } 69 | 70 | /* back */ 71 | .navigation-bar .box .tools .back { 72 | position: relative; 73 | display: flex; 74 | align-items: center; 75 | justify-content: center; 76 | } 77 | 78 | .navigation-bar .box .tools .back cover-image { 79 | width: 40rpx; 80 | height: 40rpx; 81 | } 82 | 83 | /* custom */ 84 | .navigation-bar .box .tools .custom-dot { 85 | position: relative; 86 | display: flex; 87 | align-items: center; 88 | margin-left: 24rpx; 89 | justify-content: center; 90 | border: 1rpx solid rgba(0,0,0,.1); 91 | border-radius: 50%; 92 | } 93 | 94 | .navigation-bar .box .tools .custom-dot .custom-dot-box { 95 | position: absolute; 96 | top: 50%; 97 | left: 50%; 98 | transform: translate(-50%,-50%); 99 | width: 76%; 100 | height: 76%; 101 | border-radius: 50%; 102 | background: #3F434D; 103 | padding: 10rpx; 104 | } 105 | 106 | .navigation-bar .box .tools .custom-dot cover-image { 107 | width: 100%; 108 | } 109 | 110 | .navigation-bar .box .title { 111 | position: relative; 112 | margin: 0 auto; 113 | width: 100%; 114 | text-align: center; 115 | font-size: 38rpx; 116 | font-weight: bold; 117 | color: #000; 118 | white-space: nowrap; 119 | text-overflow: ellipsis; 120 | overflow: hidden; 121 | } -------------------------------------------------------------------------------- /components/auth/index.wxss: -------------------------------------------------------------------------------- 1 | :host { 2 | font-size: 28rpx; 3 | color: #333; 4 | font-weight: 400; 5 | } 6 | 7 | @keyframes tinUpIn { 8 | 0% { 9 | opacity: 0; 10 | transform: scale(1,1) translateY(-120%) 11 | } 12 | 13 | 100% { 14 | opacity: 1; 15 | transform: scale(1,1) translateY(-50%) 16 | } 17 | } 18 | 19 | @keyframes vanishOut { 20 | 0% { 21 | opacity: 1; 22 | transform: scale(1,1) translateY(-50%) 23 | } 24 | 25 | 100% { 26 | opacity: 0; 27 | transform: scale(1,1) translateY(-120%) 28 | } 29 | } 30 | 31 | .component-auth-box { 32 | position: fixed; 33 | top: 0; 34 | bottom: 0; 35 | left: 0; 36 | right: 0; 37 | z-index: 99; 38 | transition: all .3s linear; 39 | } 40 | .component-auth-box .auth-box { 41 | position: absolute; 42 | top: 50%; 43 | left: 48rpx; 44 | right: 48rpx; 45 | transform: translateY(-50%); 46 | background: #FFFFFF; 47 | border-radius: 10rpx; 48 | padding: 60rpx 48rpx 44rpx 48rpx; 49 | } 50 | .component-auth-box .auth-mask { 51 | position: absolute; 52 | top: 0; 53 | left: 0; 54 | right: 0; 55 | bottom: 0; 56 | background: rgba(0,0,0, .4); 57 | z-index: 1; 58 | } 59 | .component-auth-box.hide { 60 | opacity: 0; 61 | visibility: hidden; 62 | } 63 | .component-auth-box.show { 64 | opacity: 1; 65 | visibility: visible; 66 | } 67 | .component-auth-box .auth-box { 68 | position: absolute; 69 | top: 50%; 70 | left: 48rpx; 71 | right: 48rpx; 72 | transform: translateY(-50%); 73 | background: #FFFFFF; 74 | border-radius: 10rpx; 75 | padding: 60rpx 48rpx 44rpx 48rpx; 76 | z-index: 2; 77 | } 78 | .component-auth-box.hide .auth-box { 79 | animation: vanishOut .4s both; 80 | } 81 | .component-auth-box.show .auth-box { 82 | animation: tinUpIn .4s both; 83 | } 84 | 85 | .component-auth-box .auth-box .auth-text { 86 | position: relative; 87 | overflow: hidden; 88 | margin: 0 auto; 89 | } 90 | .component-auth-box .auth-box .auth-text .auth-logo { 91 | display: block; 92 | float: left; 93 | margin-right: 24rpx; 94 | width: 80rpx; 95 | height: 81rpx; 96 | background: #f2f2f2; 97 | border-radius: 6rpx; 98 | } 99 | .component-auth-box .auth-box .auth-text .text-a { 100 | float: left; 101 | } 102 | .component-auth-box .auth-box .auth-text .text-t { 103 | font-size: 30rpx; 104 | color: #666666; 105 | padding-bottom: 4rpx; 106 | } 107 | .component-auth-box .auth-box .auth-text .text-i { 108 | font-size: 24rpx; 109 | color: #999999; 110 | } 111 | .component-auth-box .auth-box .auth-btn { 112 | position: relative; 113 | margin-top: 48rpx; 114 | background: #1AAD19; 115 | border-radius: 10rpx; 116 | padding: 22rpx 0; 117 | font-size: 32rpx; 118 | color: #FFFFFF; 119 | text-align: center; 120 | } 121 | .component-auth-box .auth-box .auth-btn button { 122 | position: absolute; 123 | top: 0; 124 | left: 0; 125 | width: 100%; 126 | height: 100%; 127 | z-index: 2; 128 | opacity: 0; 129 | } 130 | -------------------------------------------------------------------------------- /components/navigationBar/index.js: -------------------------------------------------------------------------------- 1 | Component({ 2 | properties: { 3 | background: { 4 | type: String, 5 | value: '#fff', 6 | observer(nv) { 7 | this.setStyle('wrapper', Object.assign({}, this.data.wrapper, { 8 | background: nv 9 | })) 10 | }, 11 | } 12 | }, 13 | 14 | data: { 15 | wrapper: {}, 16 | wrapperStyle: '', 17 | box: {}, 18 | boxStyle: '', 19 | title: {}, 20 | titleStyle: '', 21 | tool: {}, 22 | toolStyle: '', 23 | dot: {}, 24 | dotStyle: '', 25 | }, 26 | 27 | lifetimes: { 28 | attached() { 29 | this.getSysInfo() 30 | this.getMenuBtnOps() 31 | }, 32 | }, 33 | 34 | methods: { 35 | // get device 36 | getSysInfo () { 37 | this.sysInfo = wx.getSystemInfoSync() || {} 38 | }, 39 | 40 | // get menu btn 41 | getMenuBtnOps () { 42 | const { 43 | width : mw = 0, 44 | height : mh = 0, 45 | top : mt = 0, 46 | bottom: mb = 0, 47 | right : mr = 0, 48 | } = wx.getMenuButtonBoundingClientRect() 49 | 50 | const bottomDis = '24rpx' 51 | const titleWid = `calc(100% - (2 * ${mw}px + 16px))` 52 | const dis = (this.sysInfo.windowWidth || 0) - mr 53 | 54 | this.setStyle('wrapper', { 55 | height: mb, 56 | paddingBottom: bottomDis, 57 | background: this.data.background, 58 | }) 59 | 60 | this.setStyle('box', { 61 | top: mt, 62 | bottom: bottomDis, 63 | // fix: mock devtool dis 64 | left: dis > 100 ? 16 : dis, 65 | right: dis > 100 ? 16 : dis, 66 | height: mh, 67 | }) 68 | 69 | this.setStyle('title', { 70 | width: titleWid, 71 | lineHeight: mh, 72 | color: '#000', 73 | }) 74 | 75 | this.setStyle('tool', { 76 | width: mw, 77 | }) 78 | 79 | this.setStyle('dot', { 80 | width: mh, 81 | height: mh, 82 | }) 83 | 84 | }, 85 | 86 | // setStyle 87 | setStyle (o = '', style = {}) { 88 | this.setData({ 89 | // cache 90 | [o]: style, 91 | }, () => { 92 | this.setData({ 93 | [`${o}Style`]: this.toStyleStr(style) 94 | }) 95 | }) 96 | }, 97 | 98 | // back 99 | back () { 100 | wx.navigateBack() 101 | }, 102 | 103 | // home 104 | home () { 105 | wx.redirectTo({ 106 | url: '/pages/home/index', 107 | }) 108 | }, 109 | 110 | // to style 111 | toStyleStr (o = {}) { 112 | if (Object.keys(o).length <= 0) return ''; 113 | 114 | // num + suit => str 115 | const toPxStr = (v) => typeof v === 'number' ? `${v}px` : v 116 | 117 | // paddingTop => padding-top 118 | const toLower = (k = '') => 119 | k.replace(/([A-Z])([a-z]+)/g, (full, first, more) => `-${first.toLowerCase()}${more}`) 120 | 121 | return Object 122 | .entries(o) 123 | .map(([k, v]) => `${toLower(k)}: ${toPxStr(v)}`) 124 | .join(';') 125 | }, 126 | } 127 | }) 128 | -------------------------------------------------------------------------------- /components/auth/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 微信强授权组件 4 | * 5 | * prop: 6 | * @display: 显示/隐藏 7 | * @bg:背景色 8 | * @scope:微信权限值 9 | * 有以下参考值: 10 | * scope.userInfo 用户信息 11 | * scope.userLocation 地理位置 12 | * scope.address 通讯地址 13 | * scope.invoiceTitle 发票抬头 14 | * scope.werun 微信运动步数 15 | * scope.record 录音功能 16 | * scope.writePhotosAlbum 保存到相册 17 | * scope.camera 摄像头 18 | * scope.userPhone 用户手机 // 自定义scope 19 | * 20 | * event: 21 | * @bind:success:授权成功,返回: 22 | * 23 | * scope === 'scope.userInfo' 24 | * { 25 | * scope: 'scope.userInfo', 26 | * result: { 27 | * detail: {} // userinfo detail 28 | * } 29 | * } 30 | * 31 | * scope === scope.userPhone 32 | * { 33 | * scope: 'scope.userPhone', 34 | * result: { 35 | * detail: {} // userinfo detail 36 | * code: code || '' // login code || '' (session 过期才会重新获取) 37 | * } 38 | * } 39 | * 40 | * === scope.* // 'userLocation' || 'address' || ... 41 | * { 42 | * scope: 'scope.*' 43 | * } 44 | */ 45 | 46 | const scopes = { 47 | 'scope.userInfo': { tip: '获取你的公开信息(昵称、头像等)', open: 'getUserInfo' }, 48 | 'scope.userLocation': { tip: '获取你的地理位置', open: 'openSetting' }, 49 | 'scope.address': { tip: '获取你的通讯地址信息', open: 'openSetting' }, 50 | 'scope.invoiceTitle': { tip: '获取你的发票抬头信息', open: 'openSetting' }, 51 | 'scope.werun': { tip: '获取你的微信运动步数信息', open: 'openSetting' }, 52 | 'scope.record': { tip: '使用录音功能', open: 'openSetting' }, 53 | 'scope.writePhotosAlbum': { tip: '获取读写相册权限', open: 'openSetting' }, 54 | 'scope.camera': { tip: '获取使用摄像头权限', open: 'openSetting' }, 55 | 'scope.userPhone': { tip: '获取你的微信关联手机号', open: 'getPhoneNumber' }, 56 | '': { tip: '', open: 'openSetting' }, 57 | } 58 | 59 | Component({ 60 | properties: { 61 | display: { 62 | type: Boolean, 63 | value: false, 64 | observer(nv, ov, cp) { 65 | if (nv) { 66 | wx.vibrateShort() 67 | } 68 | } 69 | }, 70 | bg: { 71 | type: String, 72 | value: 'rgba(0,0,0,.5)', 73 | }, 74 | scope: { 75 | type: String, 76 | value: '', 77 | observer(nv, ov, cp) { 78 | const { tip, open } = scopes[nv || ''] 79 | this.setData({ 80 | 'scopeDesc': tip, 81 | 'openType': open 82 | }) 83 | } 84 | } 85 | }, 86 | 87 | data: { 88 | scopeDesc: '', 89 | openType: '', 90 | }, 91 | 92 | methods: { 93 | checkSession(cb) { 94 | wx.checkSession({ 95 | success() { cb && cb('') }, 96 | fail() { 97 | // session_key 已经失效,需要重新执行登录 98 | wx.login({ 99 | success(res) { 100 | cb && cb(res.code || '') 101 | } 102 | }) 103 | } 104 | }) 105 | }, 106 | onGetUserInfo(e) { 107 | const cv = this 108 | if (!e.detail.iv) return 109 | this.triggerEvent('success', { 110 | scope: cv.data.scope, 111 | result: { 112 | detail: e.detail || {} 113 | } 114 | }) 115 | }, 116 | onGetUserPhone(e) { 117 | const cv = this 118 | if (!e.detail.iv) return 119 | this.checkSession((code) => { 120 | this.triggerEvent('success', { 121 | scope: cv.data.scope, 122 | result: { 123 | detail: e.detail || {}, 124 | code, 125 | } 126 | }) 127 | }) 128 | }, 129 | onOpenSetting(e) { 130 | const cv = this 131 | if (e.detail.authSetting[cv.data.scope]) { 132 | this.triggerEvent('success', { 133 | scope: cv.data.scope, 134 | }) 135 | } 136 | }, 137 | } 138 | }) 139 | -------------------------------------------------------------------------------- /components/cascader/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 级联选择器 3 | * 4 | * prop: 5 | * @display: 显示/隐藏 6 | * @options: 源数据 7 | * @props: 可配置 级联 属性名, 默认:{ label: 'label', value: 'value', children: 'children' } 8 | * @_default: 可设置默认选择项; 默认 [] 9 | * event: 10 | * @bind:close: 选择器关闭触发,返回: 11 | * { 12 | * type: 'cancel', // cancel: 取消选择/点击遮罩层; submit: 确认 13 | * valueIns: [], // 返回已选择的下标;(深度层级) 14 | * valueArr: [], // 返回已选择的下标对应 option 内容 15 | * } 16 | * 17 | */ 18 | 19 | Component({ 20 | properties: { 21 | display: { 22 | type: Boolean, 23 | value: false, 24 | observer(nv, ov, cp) { 25 | this.setData({ '_display': nv || false }) 26 | }, 27 | }, 28 | options: { 29 | // 以: label/value/children 为默认配置属性; 30 | type: Array, // [{ label: '', value: '', children: [...{ label, value }] }] 31 | value: [], 32 | observer: '_initOpt', 33 | }, 34 | _default: { 35 | type: Array, 36 | value: [], 37 | }, 38 | props: { 39 | type: Object, 40 | value: {}, 41 | } 42 | }, 43 | 44 | data: { 45 | _display: false, 46 | _ops: [], 47 | _value: [], 48 | }, 49 | 50 | methods: { 51 | 52 | bindBtn(e) { 53 | if (this._pickerLock) return // 基础库 >= v2.3.1 有效 54 | 55 | const _t = e.currentTarget.dataset.type || '' 56 | this.setData({ '_display': false }) 57 | this.triggerEvent('close', { 58 | type: _t, // 'cancel' || 'submit' 59 | valueIns: this.data._value, // 所选 index 数组 60 | valueArr: this.data._ops.map((v, i) => v[this.data._value[i]]) // 所选 value 数组 61 | }) 62 | }, 63 | 64 | bindChange(e) { 65 | let _v = e.detail.value || [] 66 | let _ov = (this.data._value || []).slice(0, -1) 67 | 68 | let _diffIn = _v.slice(0, -1).findIndex((v, i) => _ov[i] !== v) 69 | if (_diffIn > -1) { 70 | _v.fill(0, _diffIn + 1) 71 | this.setData({ 72 | '_ops': this._formatOps(this.data.options, _v) 73 | }, () => { 74 | // 滞后更新: 防止 _value 先与_ops 改变,导致数据多次异常更新 75 | this.setData({ '_value': _v }) 76 | }) 77 | } else { 78 | this.setData({ '_value': _v }) 79 | } 80 | }, 81 | 82 | bindChangeStart() { 83 | this._pickerLock = true 84 | }, 85 | 86 | bindChangeEnd() { 87 | this._pickerLock = false 88 | }, 89 | 90 | _initOpt(nv) { 91 | const _def = this.data._default || [] 92 | const { label, value, children } = this.data.props || {} 93 | 94 | this.setData({ 95 | '_props': { 96 | value: value || 'value', 97 | label: label || 'label', 98 | children: children || 'children' 99 | } 100 | }, () => { 101 | this.setData({ 102 | '_ops': this._formatOps(nv || [], _def) 103 | }, () => { 104 | this.setData({ 105 | '_value': (_def.length <= 0) 106 | ? new Array(this.data._ops.length).fill(0) 107 | : _def, 108 | }) 109 | }) 110 | }) 111 | }, 112 | 113 | // arr: 源数据; ins: 已选项 114 | _formatOps(arr = [], ins = []) { 115 | if (arr.length <= 0) return [] 116 | 117 | const { label, value, children} = this.data._props 118 | let _ins = JSON.parse(JSON.stringify(ins)) 119 | let _child = (arr[_ins[0] || 0] || {})[children] 120 | let _r = [arr.map(ri => { 121 | let _o = {} 122 | _o[label] = ri[label] 123 | _o[value] = ri[value] 124 | return _o 125 | })] 126 | 127 | if (_child && _child.length > 0) { 128 | _ins.length > 0 && _ins.splice(0, 1) 129 | _r = [..._r, ...this._formatOps(_child, _ins)] 130 | } 131 | 132 | return _r 133 | }, 134 | } 135 | }) 136 | -------------------------------------------------------------------------------- /components/countdown/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 倒计时 3 | * 4 | * props: 5 | * @time: 时间(秒) 6 | * @format: 格式化显示, 默认:dd天hh时mm分ss秒 7 | * @timeStyle: 时间style样式;默认: '' (::font/color 等可继承父级,一般不用配置) 8 | * @symbolStyle: 时间symbol样式:默认:'' (::font/color 等可继承父级,一般不用配置) 9 | * @sign: 同时使用多个 countdown 组件时,需要sign 标注唯一,通知时会回传sign 10 | * 11 | * event: 12 | * @running: 倒计时进行中, 返回:{ sign, time } // time: 实时计时(s) 13 | * @end: 倒计时结束后执行,返回: { sign } 14 | * 15 | */ 16 | 17 | Component({ 18 | properties: { 19 | sign: { 20 | type: String, 21 | value: '', 22 | }, 23 | time: { 24 | type: Number, 25 | value: 0, 26 | observer: '_init' 27 | }, 28 | format: { 29 | type: String, 30 | // 保留字段: 31 | // d => 天,h => 时, m => 分, s => 秒 32 | // dd, hh, mm, ss 为补0数, 1分 => 01分 33 | // 提示:不可间隔格式, 例如: 'hh:ss' 仅显示秒 34 | value: 'dd天hh时mm分ss秒', // 'hh:mm:ss' || 'd+h+m+s' 35 | }, 36 | timeStyle: { 37 | type: String, 38 | value: '', 39 | }, 40 | symbolStyle: { 41 | type: String, 42 | value: '', 43 | } 44 | }, 45 | 46 | methods: { 47 | _init() { 48 | const { time, format } = this.data 49 | this.cms = Math.max(time, 0) 50 | this.endTms = Date.now() + (time * 1000) 51 | this.format = format 52 | 53 | this._startTimer() 54 | }, 55 | 56 | _startTimer() { 57 | clearInterval(this.timer) 58 | 59 | let cmpMs = Math.max(this.endTms - Date.now(), 0) 60 | let timeout = cmpMs % 1000 || 0 61 | 62 | this.timer = setTimeout(() => { 63 | this._startTimer() 64 | }, timeout) 65 | 66 | this._setTime(cmpMs) 67 | }, 68 | 69 | _setTime(cmpMs = 0) { 70 | this.cms = parseInt(Math.ceil(cmpMs / 1000)) 71 | this._emitRun(this.cms) 72 | 73 | if (this.cms <= 0) { 74 | clearInterval(this.timer) 75 | this._emitEnd() 76 | } 77 | 78 | let arr = this.format.match(/[a-zA-Z]{1,2}/g) || [] 79 | let symbolArr = this.format.match(/[\u4e00-\u9fa5]+|[^a-zA-Z]/g) || [] 80 | let timeLabel = this._getTimes(this.cms || 0, this.format) 81 | 82 | this.setData({ 83 | times: arr.map((t, i) => { 84 | return { 85 | n: timeLabel[t], 86 | s: symbolArr[i] 87 | } 88 | }) 89 | }) 90 | }, 91 | 92 | _getTimes(cmpMs, format) { 93 | let d = cmpMs 94 | let [s, m, h] = [60, 60, 24].map(u => { 95 | let num = d % u 96 | d = Math.floor(d / u) 97 | return num 98 | }) 99 | 100 | if (cmpMs > 86400 && format.indexOf('d') === -1) { 101 | h += d * 24 102 | } 103 | if (cmpMs > 3600 && format.indexOf('h') === -1) { 104 | m += h * 60 105 | } 106 | if (cmpMs > 60 && format.indexOf('m') === -1) { 107 | s += m * 60 108 | } 109 | 110 | return { 111 | d, h, m, s, 112 | dd: this._formatTime(d), 113 | hh: this._formatTime(h), 114 | mm: this._formatTime(m), 115 | ss: this._formatTime(s), 116 | } 117 | }, 118 | 119 | _emitEnd() { 120 | this.triggerEvent('end', { 121 | sign: this.data.sign || '', 122 | }) 123 | }, 124 | 125 | _emitRun(second = 0) { 126 | this.triggerEvent('running', { 127 | sign: this.data.sign || '', 128 | time: second // 运行中,当前倒计时时间 (秒) 129 | }) 130 | }, 131 | 132 | _formatTime(val) { 133 | return val < 10 ? `0${val}` : val; 134 | }, 135 | 136 | onPageShow() { 137 | if (this.format && this.endTms) { 138 | // 若存在 format / endTms, 继续执行 139 | this.cms = parseInt(Math.ceil(this.endTms - Date.now() / 1000)); 140 | this._startTimer() 141 | } 142 | }, 143 | 144 | onPageHide() { 145 | clearInterval(this.timer) 146 | }, 147 | }, 148 | 149 | detached() { 150 | this.onPageHide() 151 | }, 152 | 153 | pageLifetimes: { 154 | show() { 155 | this.onPageShow() 156 | }, 157 | hide() { 158 | this.onPageHide() 159 | }, 160 | }, 161 | }) 162 | -------------------------------------------------------------------------------- /libs/util.js: -------------------------------------------------------------------------------- 1 | // util 2 | 3 | const formatNumber = n => { 4 | const s = n.toString() 5 | return s[1] ? s : `0${s}` 6 | } 7 | 8 | export default { 9 | /** 10 | * 解析二维码场景参数 (同服务端协定 scene 以及 params)) 11 | * scene: '' 12 | * keys: [] // 参数名,按顺序填写 13 | * 14 | * eg: 15 | * scene: 'activity', 16 | * keys: ['actId', 'userId, 'userName'] 17 | * path: /pages/activity?scene=21;43;lilei 18 | * 19 | * return: { actId: '21', userId: '43', userName: 'lilei' } 20 | */ 21 | resolveSceneParams(scene = '', keys = []) { 22 | if (keys.length <= 0) return null 23 | let params = {} 24 | const hashes = decodeURIComponent(scene + '').split(';') 25 | hashes.map((val, index) => { 26 | params[keys[index]] = decodeURIComponent(val) 27 | }) 28 | return params 29 | }, 30 | 31 | /** 32 | * @description deepClone 深拷贝(简易) 33 | * @param {Object|Array} obj 34 | * @parsm .obj 35 | * */ 36 | deepClone(obj) { 37 | return JSON.parse(JSON.stringify(obj)) 38 | }, 39 | 40 | /** 41 | * @description typeof 类型判断扩展 42 | * @param {Any} o 43 | * @return {String} 类型 44 | * */ 45 | typeOf(o) { 46 | const { toString } = Object.prototype 47 | const map = { 48 | '[object Boolean]': 'boolean', 49 | '[object Number]': 'number', 50 | '[object String]': 'string', 51 | '[object Function]': 'function', 52 | '[object Array]': 'array', 53 | '[object Date]': 'date', 54 | '[object RegExp]': 'regExp', 55 | '[object Undefined]': 'undefined', 56 | '[object Null]': 'null', 57 | '[object Object]': 'object', 58 | '[object Symbol]': 'symbol' 59 | } 60 | 61 | return map[toString.call(o)] 62 | }, 63 | 64 | /** 65 | * @description throttle 66 | * @param {Function} fn 67 | * @param {Number} interval 68 | * 节流 69 | * */ 70 | throttle(fn, interval = 200) { 71 | let last 72 | let timer = null 73 | const time = interval 74 | return function _throttle(...args) { 75 | const that = this 76 | const now = +new Date() 77 | if (last && last - now < time) { 78 | clearTimeout(timer) 79 | timer = setTimeout(() => { 80 | last = now 81 | fn.apply(that, args) 82 | }, time) 83 | } else { 84 | last = now 85 | fn.apply(that, args) 86 | } 87 | } 88 | }, 89 | 90 | /** 91 | * @description 格式化日期 92 | * @param {Date|Number|String} date 93 | * @return {String} 'yyyy-MM-dd hh:mm:ss' 94 | * */ 95 | formatDate(date = new Date()) { 96 | let _date = null 97 | 98 | if (/^\d+$/.test(date)) { 99 | _date = parseInt(date, 10) 100 | } 101 | 102 | if (['string', 'number'].includes(typeof _date)) { 103 | _date = new Date(_date) 104 | } 105 | 106 | if (!(_date.getFullYear && _date.getFullYear())) { 107 | throw new Error('日期格式错误') 108 | } else { 109 | const [Y, M, D, h, m, s] = [ 110 | date.getFullYear(), 111 | date.getMonth() + 1, 112 | date.getDate(), 113 | date.getHours(), 114 | date.getMinutes(), 115 | date.getSeconds() 116 | ] 117 | const dt = `${[Y, M, D].map(formatNumber).join('-')}` 118 | const time = `${[h, m, s].map(formatNumber).join('-')}` 119 | return `${dt} ${time}` 120 | } 121 | }, 122 | 123 | /** 124 | * @description 浮点数计算; 规避浮点数的精度丢失; 125 | * @example eg: 32.80*100 // 3279.9999999999995; 126 | * @operate abb | sub | mul | div 127 | * @param {Number} a 128 | * @param {Number} b 129 | * @return {Number} result 130 | * */ 131 | numberCal: { 132 | add(a, b) { 133 | let c 134 | let d 135 | try { 136 | c = a.toString().split('.')[1].length 137 | } catch (f) { 138 | c = 0 139 | } 140 | try { 141 | d = b.toString().split('.')[1].length 142 | } catch (f) { 143 | d = 0 144 | } 145 | const e = 10 ** Math.max(c, d) 146 | return (this.mul(a, e) + this.mul(b, e)) / e 147 | }, 148 | sub(a, b) { 149 | let c 150 | let d 151 | try { 152 | c = a.toString().split('.')[1].length 153 | } catch (f) { 154 | c = 0 155 | } 156 | try { 157 | d = b.toString().split('.')[1].length 158 | } catch (f) { 159 | d = 0 160 | } 161 | const e = 10 ** Math.max(c, d) 162 | return (this.mul(a, e) - this.mul(b, e)) / e 163 | }, 164 | mul(a, b) { 165 | let c = 0 166 | const d = a.toString() 167 | const e = b.toString() 168 | try { 169 | c += d.split('.')[1].length 170 | } catch (f) { } 171 | try { 172 | c += e.split('.')[1].length 173 | } catch (f) { } 174 | return (Number(d.replace('.', '')) * Number(e.replace('.', ''))) / 10 ** c 175 | }, 176 | div(a, b) { 177 | let e = 0 178 | let f = 0 179 | try { 180 | e = a.toString().split('.')[1].length 181 | } catch (c) { } 182 | try { 183 | f = b.toString().split('.')[1].length 184 | } catch (c) { } 185 | const c = Number(a.toString().replace('.', '')) 186 | const d = Number(b.toString().replace('.', '')) 187 | return this.mul(c / d, 10 ** (f - e)) 188 | } 189 | }, 190 | 191 | /** 192 | * 部分属性复制 193 | * @param {Object} obj 目标对象 194 | * @param {Array} attrs 所需属性 195 | * @param {Boolean} deep 深拷贝? 默认:false 196 | * */ 197 | pick(obj = {}, attrs = [], deep = false) { 198 | return attrs.reduce((i, v) => { 199 | v in obj && (i[v] = deep ? JSON.parse(JSON.stringify(obj[v])) : obj[v]) 200 | return i 201 | }, {}) 202 | }, 203 | } 204 | -------------------------------------------------------------------------------- /components/poster/panel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 画板 3 | * @class Panel 4 | * 5 | */ 6 | class Panel { 7 | /** 8 | * @constructor 9 | * @param {String} canvasid 10 | */ 11 | constructor(canvasid, cmp) { 12 | this.cid = canvasid 13 | this.cmp = cmp 14 | this.ctx = wx.createCanvasContext(canvasid, cmp) 15 | } 16 | 17 | /** 18 | * 初始化 19 | * @param {Number} width 20 | * @param {Number} height 21 | * @param {String|Object} bgColor 22 | * @param {Array} box 23 | */ 24 | init({ 25 | width = 1, 26 | height = 1, 27 | bgColor = 'transparent', 28 | box = [] 29 | } = {}) { 30 | 31 | // 绘制背景 32 | this._drawBlock({ width, height, bgColor}) 33 | 34 | // 依层级绘制模块 35 | box.forEach((bi) => { 36 | if (bi._type === 'block') { 37 | this._drawBlock(bi._con) 38 | } else if (bi._type === 'image') { 39 | this._drawImage(bi._con) 40 | } else if (bi._type === 'text' ) { 41 | this._drawText(bi._con) 42 | } 43 | }) 44 | 45 | return new Promise((resolve, reject) => { 46 | // 渲染海报 47 | this.ctx.draw(false, () => { 48 | this._canvasToImg() 49 | .then(res => resolve(res), errMsg => reject(errMsg)) 50 | .catch(err => reject(err)) 51 | }) 52 | }) 53 | } 54 | 55 | /** 56 | * 绘制块 57 | * @param {Number} x 58 | * @param {Number} y 59 | * @param {Number} width 60 | * @param {Number} height 61 | * @param {String|Object} bgColor 62 | * @param {Number} borderRadius 63 | * @param {Number} borderWidth 64 | * @param {String} borderColor 65 | * @param {Number} opacity 66 | */ 67 | _drawBlock({ 68 | x = 0, 69 | y = 0, 70 | width = 0, 71 | height = 0, 72 | borderRadius = 0, 73 | borderWidth = 0, 74 | borderColor = 'transparent', 75 | bgColor = 'transparent', 76 | opacity = 1, // 0~1 77 | } = {}) { 78 | this.ctx.save() 79 | 80 | this.ctx.globalAlpha = opacity 81 | // 设置背景色 82 | this.ctx.fillStyle = this._formatColor(bgColor) 83 | // 设置边框 84 | this.ctx.strokeStyle = this._formatColor(borderColor) 85 | this.ctx.lineWidth = borderWidth 86 | // 绘制矩形 87 | this.ctx.beginPath() 88 | this.ctx.moveTo(x + borderRadius, y) 89 | this.ctx.lineTo(x + width - borderRadius, y) 90 | this.ctx.arcTo(x + width, y, x + width, y + borderRadius, borderRadius) 91 | this.ctx.lineTo(x + width, y + height - borderRadius) 92 | this.ctx.arcTo(x + width, y + height, x + width - borderRadius, y + height, borderRadius) 93 | this.ctx.lineTo(x + borderRadius, y + height) 94 | this.ctx.arcTo(x, y + height, x, y + height - borderRadius, borderRadius) 95 | this.ctx.lineTo(x, y + borderRadius) 96 | this.ctx.arcTo(x, y, x + borderRadius, y, borderRadius) 97 | this.ctx.closePath() 98 | // 填充 99 | this.ctx.fill() 100 | // 描边 101 | this.ctx.stroke() 102 | 103 | this.ctx.restore() 104 | } 105 | 106 | /** 107 | * 绘制图片 108 | * @param {String} url 109 | * @param {Number} x 110 | * @param {Number} y 111 | * @param {Number} width 112 | * @param {Number} height 113 | * @param {Number} borderWidth 114 | * @param {String|Object} borderColor 115 | * @param {Number} borderRadius 116 | */ 117 | _drawImage({ 118 | url = '', 119 | x = 0, 120 | y = 0, 121 | width = 0, 122 | height = 0, 123 | borderWidth = 0, 124 | borderColor = 'transparent', 125 | borderRadius = 0, 126 | } = {}) { 127 | if (!url) return 128 | this.ctx.save() 129 | this._drawBlock({ x, y, width, height, borderRadius, borderColor, borderWidth }) 130 | this.ctx.clip() 131 | this.ctx.drawImage(url, x, y, width, height) 132 | this.ctx.restore() 133 | } 134 | 135 | /** 136 | * 绘制文本 137 | * @param {Number} x 138 | * @param {Number} y 139 | * @param {String} text 140 | * @param {String|Object} color 141 | * @param {Number} fontSize 142 | * @param {String} fontWeight 143 | * @param {String} fontFamily 144 | * @param {String} textAlign 145 | * @param {Number} maxWidth 146 | * @param {Number} lineHeight 147 | * @param {Number} opacity 148 | */ 149 | _drawText({ 150 | x = 0, 151 | y = 0, 152 | text = '', 153 | color = '#000', 154 | fontSize = 24, 155 | fontWeight = 'normal', // 'normal' || 'bold' 156 | fontFamily = 'Arial', 157 | textAlign = 'left', // 'center' || 'left' || 'right' 158 | lineHeight = 36, 159 | lineLimit = 0, // 0 ~ 999; 0 标识不限制 160 | maxWidth = 0, 161 | opacity = 1, // 0~1 162 | } = {}) { 163 | if (!text) return 164 | this.ctx.save() 165 | let [ lineWidth, lastStrIndex, tx, ty ] = [ 0, 0, x, y ] 166 | this.ctx.globalAlpha = opacity 167 | this.ctx.font = `normal ${fontWeight} ${fontSize}px ${fontFamily}` 168 | this.ctx.fillStyle = this._formatColor(color) 169 | this.ctx.setTextAlign(textAlign) 170 | this.ctx.setTextBaseline('middle') 171 | for ( let i = 0, l = text.length; i < l; i++ ) { 172 | lineWidth += this.ctx.measureText(text[i]).width 173 | if (lineWidth > maxWidth) { 174 | // line limit 175 | if ((lineLimit > 0 && Math.ceil((ty - y + lineHeight) / lineHeight) <= lineLimit) || lineLimit === 0) { 176 | let isLimitLastLine = Math.ceil((ty - y + lineHeight) / lineHeight) === lineLimit 177 | if (isLimitLastLine) { 178 | this.ctx.fillText(text.slice(lastStrIndex, i - 2) + '...', tx, ty) 179 | break; 180 | } 181 | this.ctx.fillText(text.slice(lastStrIndex, i), tx, ty) 182 | lineWidth = 0 183 | ty += lineHeight 184 | lastStrIndex = i 185 | } 186 | } else if (i === l - 1) { 187 | this.ctx.fillText(text.slice(lastStrIndex, text.length), tx, ty) 188 | } 189 | } 190 | // last line 191 | // if (this.ctx.measureText(text.slice(lastStrIndex, text.length)).width > maxWidth) { 192 | // this.ctx.fillText(text.slice(lastStrIndex, lastStrIndex + maxWidth / 2) + '...', tx, ty) 193 | // } else { 194 | // this.ctx.fillText(text.slice(lastStrIndex, text.length), tx, ty) 195 | // } 196 | this.ctx.restore() 197 | } 198 | 199 | /** 200 | * 格式化颜色 201 | * @param {String|Object} color 202 | */ 203 | _formatColor(color = '') { 204 | if (typeof color === 'string') return color 205 | 206 | let grd 207 | const { 208 | type = 'linear', 209 | x1 = 0, // [x1 ,y1, x2, y2] : linear 参数 210 | y1 = 0, 211 | x2 = 0, 212 | y2 = 0, 213 | ox = 0, // [ox, oy, radius] : circular 参数 214 | oy = 0, 215 | radius = 0, 216 | colorStep = [], // [[0, 'red'], ...[], [1, 'white']] // 0~1 217 | } = color || {} 218 | 219 | if (type === 'linear') { 220 | grd = this.ctx.createLinearGradient(x1, y1, x2, y2) 221 | } else if (type === 'circular') { 222 | grd = this.ctx.createCircularGradient(ox, oy, radius) 223 | } 224 | colorStep.forEach(c => { 225 | grd.addColorStop((c[0] || 0) * 1, c[1] || '') 226 | }) 227 | 228 | return grd 229 | } 230 | 231 | /** 232 | * canvas => 海报图片 233 | */ 234 | _canvasToImg() { 235 | return new Promise((resolve, reject) => { 236 | wx.canvasToTempFilePath({ 237 | canvasId: this.cid, 238 | success: res => resolve(res.tempFilePath), 239 | fail: err => reject(err) 240 | }, this.cmp) 241 | }) 242 | } 243 | 244 | } 245 | 246 | module.exports = Panel -------------------------------------------------------------------------------- /components/poster/index.js: -------------------------------------------------------------------------------- 1 | // components/poster/index.js 2 | const Panel = require('panel.js') 3 | let panel 4 | 5 | Component({ 6 | /** 7 | * 组件的属性列表 8 | */ 9 | properties: { 10 | /** 11 | * @param {Boolean} autoMake: 自动生成海报;(不用点击) 12 | * 默认为false: 需要slot 点击触发; 13 | * 14 | * @param {Object} config 15 | * '注释中带 “*” 为必要参数' 16 | * 17 | * { 18 | * width: 0, // * 画布宽度 19 | * height: 0, // * 画布高度 20 | * bgColor: 'transparent', // 画布背景 21 | * block: [...{@Block}], // 块元素配置 22 | * image: [...{@Image}], // 图片元素配置 23 | * text: [...{@Text}], // 文字元素配置 24 | * } 25 | * 26 | * @Block 27 | * { 28 | * width: 0, // * 块宽度 29 | * height: 0, // * 块高度 30 | * x: 0, // 块左上角位置x 31 | * y: 0, // 块左上角位置y 32 | * bgColor: 'transparent', // 背景色 33 | * borderRadius: 0, // 边框圆弧度 34 | * borderWidth: 0, // 边框宽度 35 | * borderColor: 'transparent', // 边框颜色 36 | * opacity: 1, // 块透明度 0~1 37 | * } 38 | * 39 | * @Image 40 | * { 41 | * url: '', // * 图片资源链接,非本地图片 42 | * width: 0, // * 图片宽度 43 | * height: 0, // * 图片高度 44 | * x: 0, // 图片左上角位置x 45 | * y: 0, // 图片左上角位置y 46 | * borderRadius: 0, // 图片边框圆弧度 47 | * borderWidth: 0, // 图片边框宽度 48 | * borderColor: 'transparent', // 图片边框颜色 49 | * } 50 | * 51 | * @Text 52 | * { 53 | * text: '', // * 文本内容 54 | * x: 0, // 文本原点位置x 55 | * y: 0, // 文本原点位置y 56 | * color: '#000', // 文本颜色 57 | * fontSize: 24, // 文本字号大小 58 | * fontUrl: '', // 文字字体文件链接 'https..ttf', 须配合fontFamily使用 59 | * fontFamily: 'Arial', // 文本字体,也可以是 fontUrl 字体名称; 60 | * fontWeight: 'normal', // 字重: 'normal' || 'bold' 61 | * textAlign: 'left', // 文本相对原点对齐方式 'center' || 'left' || 'right' 62 | * lineHeight: 36, // 文本行高 63 | * lineLimit: 0, // 文本行数限制:0 标识不限制行数; 0~999 64 | * maxWidth: panelWidth,// 文本最大宽度,超出最大宽度会换行处理,默认为画布宽度 65 | * opacity: 1, // 文本透明度 0~1 66 | * } 67 | * 68 | * 69 | * @tip: 涉及到Color(bgColor/color/borderColor...):分纯色类型,渐变类型: 具体查看 70 | * 纯色=> eg: bgColor: 'red', color: '#eee' ... 71 | * 渐变=> eg: 72 | * bgColor: { 73 | * type: 'linear', // 'linear' 线性渐变 74 | * x1: 0, 75 | * y1: 0, 76 | * x2: 0, 77 | * y2: 958, 78 | * colorStep: [ 79 | * [0, '#3E5151'], 80 | * [0.5, '#11998e'], 81 | * [1, '#DECBA4'] 82 | * ] 83 | * }, 84 | * color: { 85 | * type: 'circular', // 圆形渐变 86 | * ox: 0, 87 | * oy: 0, 88 | * radius: 10, 89 | * colorStep: [ 90 | * [0, '#3E5151'], 91 | * [0.5, '#11998e'], 92 | * [1, '#DECBA4'] 93 | * ] 94 | * } 95 | * 96 | */ 97 | config: { 98 | type: Object, 99 | value: {}, 100 | }, 101 | autoMake: { 102 | type: Boolean, 103 | value: false, 104 | observer(nv, ov, cp) { 105 | if (!!nv && !this.data.loading) { 106 | this.onCreate() 107 | } 108 | } 109 | } 110 | }, 111 | 112 | data: { 113 | loading: false, 114 | }, 115 | 116 | ready() { 117 | panel = new Panel('poster', this) 118 | }, 119 | 120 | /** 121 | * 组件的方法列表 122 | */ 123 | methods: { 124 | /** 125 | * 初始化config & 画板绘制 126 | */ 127 | onCreate() { 128 | try { 129 | const wv = this 130 | if (wv.data.loading) return 131 | wv.setData({ 'loading': true }) 132 | 133 | let box = [] 134 | const config = JSON.parse(JSON.stringify(wv.data.config || {})) 135 | const { width, height, bgColor, block, image, text } = config 136 | 137 | // download async resource 138 | const fontResPro = [...(text || []).filter(t => t.fontUrl).map(i => this._downloadFontResource(i))] 139 | const imgResPro = [...(image || []).map(i => wv._downloadImgResource(i.url))] 140 | 141 | // download font resource 142 | wv._downloadAllResource(fontResPro).then(() => { 143 | // download image resource 144 | wv._downloadAllResource(imgResPro).then(ire => { 145 | image.forEach((m, j) => { m.url = ire[j].tempFilePath }) 146 | 147 | // init box 148 | box = [ 149 | ...(block || []).map(i => ({ _type: 'block', _con: Object.assign({}, i, { zIndex: i.zIndex || 0 }) })), 150 | ...(image || []).map(i => ({ _type: 'image', _con: Object.assign({}, i, { zIndex: i.zIndex || 0 }) })), 151 | ...(text || []).map(i => ({ _type: 'text', _con: Object.assign({}, i, { zIndex: i.zIndex || 0, maxWidth: i.maxWidth || width }) })), 152 | ] 153 | box.sort((i, j) => i._con.zIndex - j._con.zIndex) 154 | 155 | // init panel 156 | panel.init({ box, width, height, bgColor }).then(poster => { 157 | wv.setData({ 'loading': false }) 158 | wv.triggerEvent('success', poster) 159 | }) 160 | }) 161 | }) 162 | 163 | } catch (e) { 164 | this.setData({ 'loading': false }) 165 | this.triggerEvent('fail', e) 166 | } 167 | }, 168 | 169 | /** 170 | * 下载图片等资源 171 | * @param {String} url 172 | * @return promise 173 | */ 174 | _downloadImgResource(url = '') { 175 | return new Promise((resolve, reject) => { 176 | if (!url) reject('请补全图片参数url') 177 | wx.downloadFile({ 178 | url, 179 | success: res => resolve(res), 180 | fail: err => reject(err) 181 | }) 182 | }) 183 | }, 184 | 185 | /** 186 | * 下载文字等资源 187 | * @param {String} fontFamily 188 | * @param {String} fontUrl 189 | */ 190 | _downloadFontResource({ 191 | fontFamily = '', 192 | fontUrl = '' 193 | } = {}) { 194 | return new Promise((resolve, reject) => { 195 | if (!fontFamily || !fontUrl) reject('请补全字体参数fontFamily, fontUrl') 196 | wx.loadFontFace({ 197 | family: fontFamily, 198 | source: `url("${fontUrl}")`, 199 | success: res => resolve(res), 200 | fail: err => reject(err) 201 | }) 202 | }) 203 | }, 204 | 205 | /** 206 | * 统一资源下载 207 | * @param p promises [] 208 | * @return promise 209 | */ 210 | _downloadAllResource(promises = []) { 211 | return new Promise((resolve, reject) => { 212 | Promise.all(promises) 213 | .then(rs => resolve(rs), err => reject(err)) 214 | .catch(e => reject(e)) 215 | }) 216 | }, 217 | } 218 | }) 219 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 微信小程序开发经验+ 2 | 3 | > 总结了一些开发微信小程序过程中遇到问题的解决方式/经验分享,另外共享几个通用组件 4 | 5 | ### !!!小程序迭代较快,有些内容已过时或有更好的替代方案,酌情参考。 6 | ---- 7 | ### # cache 8 | 为什么需要cache工具? 9 | - 微信api提供了[storage](https://developers.weixin.qq.com/miniprogram/dev/api/wx.setStorage.html), 类似 localStorage `永久性存储?` (除用户主动删除或超一定时间被自动清理) 10 | - 希望可以提供类似 sessionStorage 的模拟,当次打开小程序/热启动内(本次使用期间)有效(冷启动清理) 11 | - 希望某个缓存一定时间内有效,类似 expire cookie 12 | - 可以更新expire缓存,但不改变其过期时间 13 | 14 | ``` javascript 15 | import $cache, * as caches from 'path/to/./libs/cache.js' 16 | 17 | // localstorage 18 | $cache.set('key', 1) 19 | $cache.get('key') // 1 20 | 21 | // localstorage expire 22 | $cache.set('key', 1, 10000) 23 | $cache.get('key') // 1, 10s内访问有效 24 | 25 | // localstorage 延续上次缓存时间,若上次没缓存,此次设置也将取消 26 | $cache.set('key', 1, true) 27 | $cache.get('key') // 1 28 | 29 | // sessionstorage 30 | $cache.session.set('key', 1) 31 | $cache.session.get('key') // 1 32 | 33 | // remove 34 | $cache.remove('key') 35 | $cache.session.remove('key') 36 | ``` 37 | ---- 38 | ### # PageModal 39 | 40 | 路由页面间的相互通信(限已存在路由历史记录中的路由) 41 | : App 已注入 pages: new PageModal() 42 | 43 | ``` javascript 44 | // page A 45 | Page({ 46 | data: { 47 | log: 'ori log content' 48 | }, 49 | //.. 50 | changeLog () { 51 | this.setData({ log: 'log changed.' }) 52 | }, 53 | onLoad() { 54 | // 页面栈, 建议:仅需要通信的页面加入栈中 55 | getApp().pages.add(this) 56 | }, 57 | onUnload() { 58 | // 页面卸载关闭 59 | getApp().pages.delete(this) 60 | } 61 | }) 62 | 63 | // page B 64 | Page({ 65 | //... 66 | doSomething() { 67 | // .. 68 | // 调起 page A / changeLog 69 | getApp().pages.get('path/to/A').changeLog() 70 | }, 71 | }) 72 | ``` 73 | 74 | ---- 75 | ### # page decorator 76 | 77 | Page 装饰器实例,可扩展为mixin (组件已提供behaviors,不必要时不用mixin) 78 | : App 已引用 import 'libs/pageDecorator.js' 79 | 80 | ``` javascript 81 | // Page 装饰器实例 pageDecorator.js 82 | const oriPage = Page 83 | 84 | export default Page = (data) => { 85 | 86 | // PV统计 87 | const onLoad = data.onLoad; 88 | data.onLoad = function(...args) { 89 | RecordPV.call(this) 90 | // do something before oriPage onLoad 91 | // .. 92 | return onLoad && onLoad.call(this, ...args) 93 | } 94 | 95 | oriPage(data) 96 | } 97 | 98 | // Record PV 99 | function RecordPV() { 100 | console.log('view the page : ', this.route) 101 | // sync serve.. 102 | // fetch(apis.pv, data: { page: this.route }) 103 | } 104 | ``` 105 | ---- 106 | ### # fetch 107 | 108 | 封装请求,api模块化 109 | ; tip: 目的只是为了简化wx.request,统一作部分rule code拦截,以及error处理; 110 | _因业务场景不同,已取消 ~~promise风格化~~、~~重试机制~~、~~auth请求加锁~~、~~请求取消~~等_ 111 | 112 | ``` javascript 113 | // req 114 | import fetch, { apis } from 'path/to/./core/fetch.js' 115 | 116 | Page({ 117 | req () { 118 | fetch({ 119 | ...apis.order.getOrderList, 120 | data: { 121 | page: 1, 122 | page_size: 10 123 | }, 124 | succ: (res) => { 125 | console.log(res) 126 | }, 127 | fail: (err) => { 128 | console.log(err) 129 | }, 130 | done: () => { 131 | console.log('done') 132 | } 133 | }) 134 | } 135 | }) 136 | 137 | ``` 138 | 139 | ---- 140 | ### # utils 141 | 142 | | Event | Description | 143 | | ------------- | ------------------------------ | 144 | | `typeOf()` | 类型 | 145 | | `throttle()` | 节流 | 146 | | `formatDate()` | 格式化日期 | 147 | | `numberCal` | 浮点计算 | 148 | | `pick()` | 复制部分属性 | 149 | 150 | 151 | ---- 152 | ### # 倒计时组件 153 | 154 | 提供简易倒计时功能 155 | 156 | | Props | Type | Description | 157 | | ------------- | --- | ------------------------------ | 158 | | `time` | `Number` | 时间(秒) | 159 | | `format` | `String`| 格式化显示, 默认:dd天hh时mm分ss秒 | 160 | | `timeStyle` | `String`| 时间style样式;默认: '' (::font/color 等可继承父级,一般不用配置) | 161 | | `symbolStyle` | `String`| 时间symbol样式:默认:'' (::font/color 等可继承父级,一般不用配置) | 162 | | `sign` | `String` |同时使用多个 countdown 组件时,需要 **sign** 标注唯一,通知时会回传sign | 163 | 164 | | Event | Description | 165 | | ------------- | ------------------------------ | 166 | | `running()` | 倒计时进行中, 返回:**{ sign, time }**// time: 实时计时(s) | 167 | | `end()` |倒计时结束后执行,返回:**{ sign }** | 168 | 169 | ---- 170 | ### # 级联选择组件 171 | 172 | 提供弹窗形式cascader 173 | 174 | | Props |Type | Description | 175 | | ------------- | --- | ------------------------------ | 176 | | `display` | `Boolean` | 显示/隐藏 | 177 | | `options` | `Object`| 源数据 | 178 | | `props` | `Object`| 可配置 级联 属性名, 默认:{ label: 'label', value: 'value', children: 'children' } | 179 | | `_default` | `Array`| 可设置默认选择项; 默认 [] | 180 | 181 | | Event | Description | 182 | | ------------- | ------------------------------ | 183 | | `close()` | 选择器关闭触发 | 184 | 185 | ``` javascript 186 | * close 187 | * @return 188 | * { 189 | * type: 'cancel', // cancel: 取消选择/点击遮罩层; submit: 确认 190 | * valueIns: [], // 返回已选择的下标;(深度层级) 191 | * valueArr: [], // 返回已选择的下标对应 option 内容 192 | * } 193 | ``` 194 | 195 | ---- 196 | ### # ~~自定义navigationBar~~ 197 | ### # 尝试WeUI组件: mp-navigation-bar 198 | ~~组件为自定义navigationBar实例,暂无复杂配置,通常也不建议在navigationBar做过多业务/UI; 199 | 提供了三种模式参考,`自定义单点` `模拟微信后退` `模拟微信菜单` 可依据自身业务自行定义。 200 | 提示:目前(19/5/13前)获取菜单位置api 开发工具与真机结果有差别,注意调试; 201 | 202 | ![](https://joweiblog.oss-cn-shanghai.aliyuncs.com/demo-header-ui-3.png?x-oss-process=style/scalesmall) 203 | ~~> 自定义单点 204 | 205 | ![](https://joweiblog.oss-cn-shanghai.aliyuncs.com/demo-header-ui-2.png?x-oss-process=style/scalesmall) 206 | ~~> 模拟微信后退 207 | 208 | ![](https://joweiblog.oss-cn-shanghai.aliyuncs.com/demo-header-ui-1.png?x-oss-process=style/scalesmall) 209 | ~~> 模拟微信菜单 210 | 211 | 212 | | Props | Description | 213 | | ------------- | ------------------------------ | 214 | | `background` | 背景色 | 215 | 216 | ![](https://joweiblog.oss-cn-shanghai.aliyuncs.com/demo-custom-header.gif) 217 | ~~> bar跟随滚动淡出实例. 218 | 219 | ---- 220 | ### # 预检授权组件 && WxAuth 221 | 222 | 提前检查授权状态,(已)授权后将完成原有操作 223 | - 解决获取权限场景 224 | - 授权成功回调 225 | - 组件默认强制授权继续操作,可在基础上优化改进用户体验 226 | - 需要配合**WxAuth**桥接组件,详细查看demo 227 | 228 | | Props | Type | Description | 229 | | ------------- | --- | ------------------------------ | 230 | | `display` | `Boolean` | 显示/隐藏,提供该属性仅用与WxAuth控制显隐组件 | 231 | | `scope` | `String` | [微信scope权限值](https://developers.weixin.qq.com/miniprogram/dev/api/authorize-index.html), 如:scope.userLocation | 232 | 233 | | Event | Description | 234 | | ------------- | ------------------------------ | 235 | | `success()` | 授权成功: { scope, [result: { detail, [code] }] } | 236 | 237 | _detail: userinfo / userPhone 时会返回detail信息_ 238 | _code: login code(session 过期才会重新获取)_ 239 | 240 | ``` javascript 241 | // Mng 242 | const recorderMng = wx.getRecorderManager() 243 | 244 | // 录音 245 | record() { 246 | // 检查授权 247 | this.wxa.checkScope({ 248 | scope: 'scope.record', 249 | done() { 250 | console.log('finish record auth,'); 251 | // 现在可以用 recorderMng api 咯 252 | // recorderMng.start({ 253 | // duration: 60000, 254 | // format: 'mp3' 255 | // }) 256 | } 257 | }) 258 | } 259 | ``` 260 | 261 | ---- 262 | ### # 生成海报组件 263 | 264 | 配置生成海报 265 | 266 | - 方便前端配置生成简单海报 267 | - 提供 `Block` `Image` `Text` 三种模式, 以及`Color`纯色、渐变等模式配置 268 | - config => canvas => image 269 | - 组件属性值较多,详细Config查看组件内注释文档 270 | - 暂不支持本地图片 271 | - 真机暂不支持自定义字体 272 | 273 | > Props 274 | 275 | | Props | Type | Description | 276 | | ------------ | ---- | -----| 277 | | config | `Object` | Config配置, 详细见下表 | 278 | | autoMake | `Boolean` | 自动生成 ? 点击slot生成 | 279 | 280 | > Config 281 | 282 | | Config | Type | Description | 283 | | ------------ | ---- | -----| 284 | | width | `Number` | 画布宽度 | 285 | | height | `Number` | 画布高度 | 286 | | bgColor | `String` `Object` | 画布背景色 Color | 287 | | block | `Array` | 占位配置 [...Block] | 288 | | image | `Array` | 图片配置 [...Image]| 289 | | text | `Array` | 文字配置 [...Text] | 290 | 291 | > Module 292 | 293 | | Module | Type | Description | 294 | | ------------ | ---- | -----| 295 | | Block | `Object` | 占位配置(文档) | 296 | | Image | `Object` | 图片配置(文档) | 297 | | Text | `Object` | 文字配置(文档) | 298 | | Color | `String` `Object` | 颜色模式,纯色、渐变等 | 299 | 300 | 301 | ![](https://joweiblog.oss-cn-shanghai.aliyuncs.com/demo-poster.gif) 302 | > 点击生成海报实例. 303 | --------------------------------------------------------------------------------