├── 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 | 
203 | ~~> 自定义单点
204 |
205 | 
206 | ~~> 模拟微信后退
207 |
208 | 
209 | ~~> 模拟微信菜单
210 |
211 |
212 | | Props | Description |
213 | | ------------- | ------------------------------ |
214 | | `background` | 背景色 |
215 |
216 | 
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 | 
302 | > 点击生成海报实例.
303 |
--------------------------------------------------------------------------------