├── .editorconfig
├── .eslintrc
├── .gitignore
├── .npmrc
├── LICENSE
├── README.md
├── config
├── dev.js
├── index.js
└── prod.js
├── global.d.ts
├── package.json
├── project.config.json
├── src
├── app.less
├── app.tsx
├── assets
│ └── images
│ │ ├── bg.png
│ │ ├── canvasBg.png
│ │ ├── close.png
│ │ ├── code.png
│ │ ├── dragon.png
│ │ ├── feedback.png
│ │ ├── icon.close.png
│ │ ├── icon_back_dark.png
│ │ ├── icon_back_light.png
│ │ ├── icon_menu_dark.png
│ │ ├── icon_menu_light.png
│ │ ├── icon_music_gif.png
│ │ ├── icon_only_gif.png
│ │ ├── icon_sound.png
│ │ ├── icon_sound_off.png
│ │ ├── pic_loading.png
│ │ ├── powerbyversa.png
│ │ ├── question.png
│ │ ├── scale.png
│ │ └── versa.png
├── components
│ ├── AuthModal
│ │ ├── index.less
│ │ └── index.tsx
│ ├── CategoryItem
│ │ ├── index.less
│ │ └── index.tsx
│ ├── Icon
│ │ ├── index.less
│ │ └── index.tsx
│ ├── ResultModal
│ │ ├── index.less
│ │ └── index.tsx
│ ├── SceneList
│ │ ├── SceneItem
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── Sticker
│ │ ├── index.less
│ │ └── index.tsx
│ └── Title
│ │ ├── index.less
│ │ └── index.tsx
├── index.html
├── model
│ ├── actions
│ │ ├── counter.ts
│ │ └── global.ts
│ ├── constants
│ │ ├── counter.ts
│ │ └── global.ts
│ ├── reducers
│ │ ├── counter.ts
│ │ ├── global.ts
│ │ └── index.ts
│ └── store
│ │ └── index.ts
├── pages
│ ├── dynamic
│ │ ├── index.less
│ │ ├── index.tsx
│ │ ├── mock_theme_data.json
│ │ └── mock_theme_dynamic_data.json
│ ├── home
│ │ ├── index.less
│ │ ├── index.tsx
│ │ └── mock.json
│ └── index
│ │ ├── index.less
│ │ └── index.tsx
├── services
│ ├── api.config.ts
│ ├── cache.ts
│ ├── config.ts
│ ├── global_data.ts
│ ├── http.ts
│ ├── service.ts
│ └── session.ts
└── utils
│ └── tool.ts
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["taro"],
3 | "rules": {
4 | "no-unused-vars": ["error", { "varsIgnorePattern": "Taro" }],
5 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx", ".tsx"] }]
6 | },
7 | "parser": "babel-eslint",
8 | "plugins": ["typescript"]
9 | }
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | .temp/
3 | .rn_temp/
4 | node_modules/
5 | .DS_Store
6 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://r.cnpmjs.org
2 | disturl=https://r.cnpmjs.org/node
3 | sass_binary_site=https://r.cnpmjs.org/node-sass/
4 | fse_binary_host_mirror=https://r.cnpmjs.org/fsevents
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## 手把手教你用Taro框架写一个图像处理类微信小程序
3 | ### 前言
4 |
5 | 18年年中的时候,笔者所在的公司让我们开发一款微信小程序(马卡龙玩图)。主要的玩法是用户上传一张人像照片,图片经过后端的AI算法处理后识别出人物,将人物和周围环境进行分割(俗称抠图);前端将返回的抠像进行样式处理,包括设置大小位置旋转等;通过预设(或自定义上传)的一些主题场景以及点缀的贴纸或滤镜,用户对这些元素进行移动或缩放,可以衍生出很多好玩的修图玩法,比如更换动态背景,合成带有音频的动态视频等(文末有微信二维码)。
6 |
7 | 
8 |
9 | 开发初期,当时可选的成熟的微信小程序框架只有[wepy](https://tencent.github.io/wepy/),经过开发实践发现,wepy在多层嵌套列表渲染,组件化支持等方面体验不是很友好。后面美团的技术团队开源了一款基于vue的小程序框架[mpvue](http://mpvue.com/),经过体验后感觉上,虽然在组件化上体验和vue别无差异,但是在性能上并不占优势。
10 |
11 | 直到某天有位朋友拉我进了一个Taro的开发群,原来京东的前端团队也在开发一款基于React规范的小程序框架,由于当时笔者担心Taro尚处早期,功能上也许不足抑或bug,迟迟没有入手。直到最近更新到1.2.4的版本,群里有道友不吝溢美之词进行了一波安利,所以笔者决定对项目的部分模块进行了重构,发现Taro确实在开发体验和性能上都得到了非常好的提高,在此向taro的贡献者致以崇高的敬意。本着开源的精神,笔者也将此次重构的demo源码以及心得体会和大家一起分享。
12 |
13 | ### 需求分析
14 | 
15 | 用户上传的人像经过抠图处理后,将展示在作图区,同时展示的元素还有背景图片,可动或固定的贴纸。为了获取更好的用户视觉体验,每个场景下,通过预设人像和贴纸的大小和位置(参数为作图区域的百分比等)。人像和贴纸需支持单指和双指手势操作来改变大小和位置等样式,因此可以将人像和贴纸都封装为Sticker的组件,子组件Sticker向页面父组件传递手势操作变更后的样式参数,触发父组件setState来刷新,最终通过传递props到子组件来控制样式。
16 |
17 | 关于Sticker组件的一些细节还包括:贴纸组件具有激活状态(点击当前组件显示控制器,而其他组件则隐藏);切换场景后,要缓存之前用户的操作,当切回到原先的场景时,则恢复到该场景下用户最后的操作状态。
18 |
19 | 用户点击保存后,将作图区的所有元素按照层级大小进行排序,然后通过微信提供的canvas接口进行绘制,最终返回所见即所得的合成美图。
20 |
21 | ### 准备工作
22 | 根据Taro的[文档](https://nervjs.github.io/taro/docs/GETTING-STARTED.html),安装CLI工具以及创建项目模板,建议选择Typescript开发方式。
23 |
24 |
25 | ### 项目目录
26 | 简要分析下项目结构
27 | ```
28 | Taro-makaron-demo
29 | ├── dist 编译结果目录
30 | ├── config 配置目录
31 | | ├── dev.js 开发时配置
32 | | ├── index.js 默认配置
33 | | └── prod.js 打包时配置
34 | ├── src 源码目录
35 | | ├── assets 静态资源
36 | | | ├── images 图片
37 | | ├── components 组件
38 | | | ├── Sticker 贴纸组件
39 | | | ├── ... 其他组件
40 | | ├── model Redux数据流
41 | | | ├── actions
42 | | | ├── constants
43 | | | ├── reducers
44 | | | ├── store
45 | | ├── pages 页面文件目录
46 | | | ├── home 首页
47 | | | | ├── index.js index 页面逻辑
48 | | | | └── index.css index 页面样式
49 | | | ├── dynamic 作图页
50 | | | | ├── index.js index 页面逻辑
51 | | | | └── index.css index 页面样式
52 | | ├── services 服务
53 | | | ├── config.ts 全局配置
54 | | | ├── api.config.ts api接口配置
55 | | | ├── http.ts 封装的http服务
56 | | | ├── global_data.ts 全局对象
57 | | | ├── cache.ts 缓存服务
58 | | | ├── session.ts 会话服务
59 | | | ├── service.ts 基础服务或业务服务
60 | | ├── utils 公共方法
61 | | | ├── tool.ts 工具函数
62 | | ├── app.css 项目总通用样式
63 | | └── app.js 项目入口文件
64 | └── package.json
65 | ```
66 | ### 核心代码分析
67 |
68 | - sticker贴纸组件
69 |
70 | 贴纸组件相较其他展示型组件,涉及手势操作,大小位置计算等,所以稍显复杂。
71 | ```
72 | // 使用
73 | class Page extends Component {
74 | state = {
75 | foreground: { // 人像state
76 | id: 'foreground', // id
77 | remoteUrl: '', // url
78 | zIndex:2, // 层级
79 | width:0, // 宽
80 | height:0, // 高
81 | x: 0, // x轴偏移量
82 | y:0, // y轴偏移量
83 | rotate: 0, // 旋转角度
84 | originWidth: 0, // 原始宽度
85 | originHeight: 0, // 原始高度
86 | autoWidth: 0, // 自适应后的宽度
87 | autoHeight: 0, // 自适应后的高度
88 | autoScale: 0, // 相对画框缩放比例
89 | fixed: false, // 是否固定
90 | isActive: true, // 是否激活
91 | visible: true, // 是否显示
92 | }
93 | }
94 | render () {
95 | reuturn
105 | }
106 | }
107 |
108 | // 组件定义
109 | class Sticker extends Component {
110 | ...
111 | render() {
112 | const { url, stylePrams } = this.props
113 | const { framePrams } = this.state
114 | const styleObj = this.formatStyle(this.props.stylePrams)
115 | return (
116 | 0) ? '' : 'hidden' }`}
118 | style={styleObj}
119 | >
120 |
128 |
129 |
134 |
135 |
136 |
137 | )
138 | }
139 | }
140 |
141 | ```
142 | - 缓存服务
143 | 缓存服务对提高性能非常有帮助,比如canvas绘图需要图片是本地图片,可以通过数据字典的方式将图片的远程地址和下载到本地的地址进行一一对应,节省了大量的网络资源和时间
144 | ```
145 | // services/cache.ts 缓存服务
146 | function Cache (name) {
147 | this.name = name
148 | }
149 | Cache.prototype = {
150 | set: function (key, value) {
151 | this[key] = value
152 | return this[key]
153 | },
154 | get: function (key) {
155 | return this[key]
156 | },
157 | clear: function () {
158 | // 清空
159 | Object.keys(this).forEach(v => {
160 | this[v] = null
161 | })
162 | }
163 | }
164 |
165 | export const createCache = (name:string) => {
166 | return new Cache(name)
167 | }
168 | // 使用
169 | import {createCache} from '../../services/cache'
170 | class Page extends Component {
171 | cache = {
172 | source: createCache('source'),
173 | }
174 | // 下载照片并存储到本地
175 | downloadRemoteImage = async (remoteUrl = '') => {
176 | const cacheKey = `${remoteUrl}_localPath`
177 | const cache_source = this.cache['source']
178 | let localImagePath = ''
179 | if (cache_source.get(cacheKey)) {
180 | // 有缓存
181 | return cache_source.get(cacheKey)
182 | } else {
183 | try {
184 | const result = await service.base.downloadFile(remoteUrl)
185 | localImagePath = result.tempFilePath
186 | } catch (err) {
187 | console.log('下载图片失败', err)
188 | }
189 | }
190 | return cache_source.set(cacheKey, localImagePath)
191 | }
192 | }
193 |
194 | ```
195 | ### 性能优化
196 | 1. 避免频繁setState
197 |
198 | 由于微信小程序逻辑层和视图层各自独立,两边的数据传输是靠转换后的字符串。因此当setData频率过快,内容庞大时,会导致阻塞。由于本项目又涉及很多的手势操作,touchmove事件的频率很快,所以项目早期时候,在安卓系统下卡顿十分明显。
199 |
200 | 优化方式有:通过做函数节流,降低setData频次;将页面无关的数据不要绑定到data上,而是绑定到组件实例上(牺牲运算效率换取空间效率)。
201 |
202 | 使用微信的自定义组件,也是一个很大的提升因素,个人认猜测可能是自定义组件内部data的改变不会导致其他组件或页面的data更新。项目早期采用的是wepy框架,由于历史局限性(当时微信还未公布自定义组件方案),所以效率问题一直很是头疼。好在Taro框架通过编译的方式完美的支持了这个方案。
203 |
204 | 2. 归并setState
205 |
206 | 例如,当图片加载,获取到原始尺寸后,需要计算出该图片在当前场景下的预设尺寸和位置。必须先计算出自适应后的宽高,然后才能计算出预设的偏移量。因此可以将尺寸和位置参数都计算完毕后,再调用setState更新视图,这样不仅降低了频次,同时也解决了图片闪烁的bug.
207 |
208 | 3. 利用缓存
209 |
210 | 前面有提到过利用缓存模块来存储组件状态或资源信息,在此不再赘述。
211 |
212 | ### 心得
213 | Taro框架采取的是一种编译的方式,将源代码分别编译出可以在不同端(微信/百度/支付宝/字节跳动小程序、H5、React-Native 等),因此可以在性能上与各个平台保持一致。
214 |
215 | 而mpvue的方案则是修改vue的runtime,将vue 实例与小程序 Page 实例建立关联以及生命周期的绑定。私以为,这种通过映射的方式可能会导致通信效率上的降低,并且vue和微信又各自独立迭代,后期的协调也越来越费劲,所以个人感觉上,还是Taro的方案略胜一筹。个人薄见,还请海涵。
216 |
217 | ### 写在最后
218 | - Github
219 |
220 | 欢迎大家来这个demo项目下进行交流,[项目地址](https://github.com/HarryChen0506/taro-makaron-demo) (https://github.com/HarryChen0506/taro-makaron-demo), 你的点赞将是我莫大的动力😊
221 | - 线上项目
222 |
223 | 本demo项目的线上小程序可通过微信扫描下面的二维码前往体验👏 
224 |
--------------------------------------------------------------------------------
/config/dev.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | NODE_ENV: '"development"'
4 | },
5 | defineConstants: {
6 | },
7 | weapp: {},
8 | h5: {}
9 | }
10 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | const config = {
2 | projectName: 'taro-makaron',
3 | date: '2018-12-28',
4 | designWidth: 750,
5 | deviceRatio: {
6 | '640': 2.34 / 2,
7 | '750': 1,
8 | '828': 1.81 / 2
9 | },
10 | sourceRoot: 'src',
11 | outputRoot: 'dist',
12 | plugins: {
13 | babel: {
14 | sourceMap: true,
15 | presets: [
16 | 'env'
17 | ],
18 | plugins: [
19 | 'transform-decorators-legacy',
20 | 'transform-class-properties',
21 | 'transform-object-rest-spread'
22 | ]
23 | }
24 | },
25 | defineConstants: {
26 | },
27 | copy: {
28 | patterns: [
29 | ],
30 | options: {
31 | }
32 | },
33 | weapp: {
34 | module: {
35 | postcss: {
36 | autoprefixer: {
37 | enable: true,
38 | config: {
39 | browsers: [
40 | 'last 3 versions',
41 | 'Android >= 4.1',
42 | 'ios >= 8'
43 | ]
44 | }
45 | },
46 | pxtransform: {
47 | enable: true,
48 | config: {
49 |
50 | }
51 | },
52 | url: {
53 | enable: true,
54 | config: {
55 | limit: 10240 // 设定转换尺寸上限
56 | }
57 | },
58 | cssModules: {
59 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
60 | config: {
61 | namingPattern: 'module', // 转换模式,取值为 global/module
62 | generateScopedName: '[name]__[local]___[hash:base64:5]'
63 | }
64 | }
65 | }
66 | }
67 | },
68 | h5: {
69 | publicPath: '/',
70 | staticDirectory: 'static',
71 | module: {
72 | postcss: {
73 | autoprefixer: {
74 | enable: true,
75 | config: {
76 | browsers: [
77 | 'last 3 versions',
78 | 'Android >= 4.1',
79 | 'ios >= 8'
80 | ]
81 | }
82 | },
83 | cssModules: {
84 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
85 | config: {
86 | namingPattern: 'module', // 转换模式,取值为 global/module
87 | generateScopedName: '[name]__[local]___[hash:base64:5]'
88 | }
89 | }
90 | }
91 | }
92 | }
93 | }
94 |
95 | module.exports = function (merge) {
96 | if (process.env.NODE_ENV === 'development') {
97 | return merge({}, config, require('./dev'))
98 | }
99 | return merge({}, config, require('./prod'))
100 | }
101 |
--------------------------------------------------------------------------------
/config/prod.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | NODE_ENV: '"production"'
4 | },
5 | defineConstants: {
6 | },
7 | weapp: {},
8 | h5: {}
9 | }
10 |
--------------------------------------------------------------------------------
/global.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.png";
2 | declare module "*.gif";
3 | declare module "*.jpg";
4 | declare module "*.jpeg";
5 | declare module "*.svg";
6 | declare module "*.css";
7 | declare module "*.less";
8 | declare module "*.scss";
9 | declare module "*.sass";
10 | declare module "*.styl";
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "taro-makaron-demo",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "马卡龙玩图-demo",
6 | "scripts": {
7 | "build:weapp": "taro build --type weapp",
8 | "build:swan": "taro build --type swan",
9 | "build:alipay": "taro build --type alipay",
10 | "build:tt": "taro build --type tt",
11 | "build:h5": "taro build --type h5",
12 | "build:rn": "taro build --type rn",
13 | "dev": "npm run build:weapp -- --watch",
14 | "dev:weapp": "npm run build:weapp -- --watch",
15 | "dev:swan": "npm run build:swan -- --watch",
16 | "dev:alipay": "npm run build:alipay -- --watch",
17 | "dev:tt": "npm run build:tt -- --watch",
18 | "dev:h5": "npm run build:h5 -- --watch",
19 | "dev:rn": "npm run build:rn -- --watch"
20 | },
21 | "author": "",
22 | "license": "MIT",
23 | "dependencies": {
24 | "@tarojs/async-await": "^1.2.2",
25 | "@tarojs/components": "^1.2.2",
26 | "@tarojs/redux": "^1.2.2",
27 | "@tarojs/redux-h5": "^1.2.2",
28 | "@tarojs/router": "^1.2.2",
29 | "@tarojs/taro": "^1.2.2",
30 | "@tarojs/taro-alipay": "^1.2.2",
31 | "@tarojs/taro-h5": "^1.2.2",
32 | "@tarojs/taro-swan": "^1.2.2",
33 | "@tarojs/taro-tt": "^1.2.2",
34 | "@tarojs/taro-weapp": "^1.2.2",
35 | "nerv-devtools": "^1.3.9",
36 | "nervjs": "^1.3.9",
37 | "path-to-regexp": "^2.4.0",
38 | "qs": "^6.6.0",
39 | "redux": "^4.0.0",
40 | "redux-logger": "^3.0.6",
41 | "redux-thunk": "^2.3.0"
42 | },
43 | "devDependencies": {
44 | "@types/react": "^16.4.8",
45 | "@types/webpack-env": "^1.13.6",
46 | "@tarojs/plugin-babel": "^1.2.2",
47 | "@tarojs/plugin-csso": "^1.2.2",
48 | "@tarojs/plugin-less": "^1.2.2",
49 | "@tarojs/plugin-uglifyjs": "^1.2.2",
50 | "@tarojs/webpack-runner": "^1.2.2",
51 | "babel-plugin-transform-class-properties": "^6.24.1",
52 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
53 | "babel-plugin-transform-jsx-stylesheet": "^0.6.5",
54 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
55 | "babel-preset-env": "^1.6.1",
56 | "babel-eslint": "^8.2.3",
57 | "eslint": "^4.19.1",
58 | "eslint-config-taro": "^1.2.2",
59 | "eslint-plugin-react": "^7.8.2",
60 | "eslint-plugin-import": "^2.12.0",
61 | "eslint-plugin-taro": "^1.2.2",
62 | "eslint-plugin-typescript": "^0.12.0",
63 | "typescript": "^3.0.1"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "miniprogramRoot": "./dist",
3 | "projectname": "taro-makaron-demo",
4 | "description": "马卡龙玩图",
5 | "appid": "wxcfe56965f4d986f0",
6 | "setting": {
7 | "urlCheck": true,
8 | "es6": false,
9 | "postcss": false,
10 | "minified": false
11 | },
12 | "compileType": "miniprogram"
13 | }
14 |
--------------------------------------------------------------------------------
/src/app.less:
--------------------------------------------------------------------------------
1 | page {
2 | width:100%;
3 | height:100%;
4 | overflow-y:hidden;
5 | }
6 | button::after{
7 | border: none;
8 | }
9 |
10 | .custom-button {
11 | border-radius:80rpx;
12 | width:100%;
13 | height:80rpx;
14 | line-height:80rpx;
15 | color:white;
16 | font-size:36rpx;
17 | text-align:center;
18 | transition:all 0.2s;
19 | &.btn-hover {
20 | transform: scale(0.85)
21 | }
22 | &.pink {
23 | background:#FF3366;
24 | }
25 | &.dark {
26 | background:#333;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/app.tsx:
--------------------------------------------------------------------------------
1 | import '@tarojs/async-await'
2 | import Taro, { Component, Config } from '@tarojs/taro'
3 | import { Provider } from '@tarojs/redux'
4 | import Index from './pages/index'
5 |
6 | import configStore from './model/store'
7 |
8 | import './app.less'
9 |
10 | // 如果需要在 h5 环境中开启 React Devtools
11 | // 取消以下注释:
12 | // if (process.env.NODE_ENV !== 'production' && process.env.TARO_ENV === 'h5') {
13 | // require('nerv-devtools')
14 | // }
15 |
16 | const store = configStore()
17 | class App extends Component {
18 |
19 | /**
20 | * 指定config的类型声明为: Taro.Config
21 | *
22 | * 由于 typescript 对于 object 类型推导只能推出 Key 的基本类型
23 | * 对于像 navigationBarTextStyle: 'black' 这样的推导出的类型是 string
24 | * 提示和声明 navigationBarTextStyle: 'black' | 'white' 类型冲突, 需要显示声明类型
25 | */
26 | config: Config = {
27 | pages: [
28 | 'pages/home/index',
29 | 'pages/dynamic/index',
30 | 'pages/index/index'
31 | ],
32 | window: {
33 | backgroundTextStyle: 'light',
34 | navigationBarBackgroundColor: '#fff',
35 | navigationBarTitleText: 'WeChat',
36 | navigationBarTextStyle: 'black',
37 | navigationStyle: 'custom'
38 | }
39 | }
40 |
41 | componentDidMount () {
42 | }
43 |
44 | componentDidShow () {}
45 |
46 | componentDidHide () {}
47 |
48 | componentCatchError () {}
49 |
50 | componentDidCatchError () {}
51 |
52 | // 在 App 类中的 render() 函数没有实际作用
53 | // 请勿修改此函数
54 | render () {
55 | return (
56 |
57 |
58 |
59 | )
60 | }
61 | }
62 |
63 | Taro.render(, document.getElementById('app'))
64 |
--------------------------------------------------------------------------------
/src/assets/images/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/bg.png
--------------------------------------------------------------------------------
/src/assets/images/canvasBg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/canvasBg.png
--------------------------------------------------------------------------------
/src/assets/images/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/close.png
--------------------------------------------------------------------------------
/src/assets/images/code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/code.png
--------------------------------------------------------------------------------
/src/assets/images/dragon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/dragon.png
--------------------------------------------------------------------------------
/src/assets/images/feedback.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/feedback.png
--------------------------------------------------------------------------------
/src/assets/images/icon.close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/icon.close.png
--------------------------------------------------------------------------------
/src/assets/images/icon_back_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/icon_back_dark.png
--------------------------------------------------------------------------------
/src/assets/images/icon_back_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/icon_back_light.png
--------------------------------------------------------------------------------
/src/assets/images/icon_menu_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/icon_menu_dark.png
--------------------------------------------------------------------------------
/src/assets/images/icon_menu_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/icon_menu_light.png
--------------------------------------------------------------------------------
/src/assets/images/icon_music_gif.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/icon_music_gif.png
--------------------------------------------------------------------------------
/src/assets/images/icon_only_gif.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/icon_only_gif.png
--------------------------------------------------------------------------------
/src/assets/images/icon_sound.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/icon_sound.png
--------------------------------------------------------------------------------
/src/assets/images/icon_sound_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/icon_sound_off.png
--------------------------------------------------------------------------------
/src/assets/images/pic_loading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/pic_loading.png
--------------------------------------------------------------------------------
/src/assets/images/powerbyversa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/powerbyversa.png
--------------------------------------------------------------------------------
/src/assets/images/question.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/question.png
--------------------------------------------------------------------------------
/src/assets/images/scale.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/scale.png
--------------------------------------------------------------------------------
/src/assets/images/versa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HarryChen0506/taro-makaron-demo/7314667c120dd2c6ff19a7b1464dc2f6be4490c9/src/assets/images/versa.png
--------------------------------------------------------------------------------
/src/components/AuthModal/index.less:
--------------------------------------------------------------------------------
1 | .auth-wrap {
2 | position: fixed;
3 | left: 0;
4 | top: 0;
5 | width: 100%;
6 | height: 100%;
7 | display: flex;
8 | justify-content: center;
9 | align-items: center;
10 | .modal {
11 | position: absolute;
12 | left: 0;
13 | top: 0;
14 | width: 100%;
15 | height: 100%;
16 | background: #000;
17 | opacity: 0.5;
18 | }
19 | button {
20 |
21 | }
22 | }
--------------------------------------------------------------------------------
/src/components/AuthModal/index.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentClass } from 'react'
2 | import Taro, { Component } from '@tarojs/taro'
3 | import { View, Button } from '@tarojs/components'
4 |
5 | import './index.less'
6 |
7 | type ComponentStateProps = {}
8 |
9 | type ComponentOwnProps = {
10 | onClick: () => void
11 | }
12 |
13 | type ComponentState = {}
14 |
15 | type IProps = ComponentStateProps & ComponentOwnProps
16 |
17 | interface AuthModal {
18 | props: IProps;
19 | }
20 |
21 | class AuthModal extends Component {
22 | handleClick = () => {
23 | this.props.onClick && this.props.onClick()
24 | }
25 | render() {
26 | return (
27 |
28 |
29 |
30 |
31 |
32 |
33 | )
34 | }
35 | }
36 |
37 | export default AuthModal as ComponentClass
--------------------------------------------------------------------------------
/src/components/CategoryItem/index.less:
--------------------------------------------------------------------------------
1 | .category-box {
2 | position:relative;
3 | width:290rpx;
4 | height:296rpx;
5 | margin-top:20px;
6 | .category-box-button {
7 | width:100%;
8 | height:100%;
9 | padding:0;
10 | margin:0;
11 | background:transparent;
12 | transition:all 0.2s;
13 | .category-box-image {
14 | width:290rpx;
15 | height:296rpx;
16 | }
17 | &.btn-hover {
18 | transform: scale(0.85)
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/src/components/CategoryItem/index.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentClass } from 'react'
2 | import Taro, { Component } from '@tarojs/taro'
3 | import { View, Button, Image } from '@tarojs/components'
4 |
5 | import './index.less'
6 | import loading from '../../assets/images/pic_loading.png'
7 |
8 | type ComponentStateProps = {}
9 |
10 | type ComponentOwnProps = {
11 | onGetUserInfo: (data:any) => void,
12 | onClick?: () => void,
13 | url: string
14 | }
15 |
16 | type ComponentState = {}
17 |
18 | type IProps = ComponentStateProps & ComponentOwnProps
19 |
20 | interface CategotyItem {
21 | props: IProps;
22 | }
23 |
24 | class CategotyItem extends Component {
25 | static defaultProps = {
26 | url: loading
27 | }
28 | componentWillReceiveProps (nextProps) {
29 | // console.log(this.props, nextProps)
30 | }
31 | handleGgetUserInfo = (data) => {
32 | const { onGetUserInfo } = this.props
33 | onGetUserInfo(data)
34 | }
35 | render() {
36 | const { onClick, url } = this.props
37 | return (
38 |
39 |
50 |
51 | )
52 | }
53 | }
54 |
55 | export default CategotyItem as ComponentClass
--------------------------------------------------------------------------------
/src/components/Icon/index.less:
--------------------------------------------------------------------------------
1 | .icon {
2 | width:70rpx;
3 | height:50rpx;
4 | display:flex;
5 | align-items:center;
6 | justify-content:center;
7 | .icon-menu {
8 | width:40rpx;
9 | height:28rpx;
10 | }
11 | .icon-back {
12 | width:22rpx;
13 | height:38rpx;
14 | }
15 | }
--------------------------------------------------------------------------------
/src/components/Icon/index.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentClass } from 'react'
2 | import Taro, { Component } from '@tarojs/taro'
3 | import { View } from '@tarojs/components'
4 |
5 | import menu_light from '../../assets/images/icon_menu_light.png'
6 | import menu_dark from '../../assets/images/icon_menu_dark.png'
7 | import back_light from '../../assets/images/icon_back_light.png'
8 | import back_dark from '../../assets/images/icon_back_dark.png'
9 | import './index.less'
10 | type ComponentStateProps = {}
11 |
12 | type ComponentOwnProps = {
13 | type: string, // icon类型
14 | theme?: string // 主题
15 | onClick?: () => void
16 | }
17 | type ComponentState = {}
18 |
19 | type IProps = ComponentStateProps & ComponentOwnProps
20 |
21 | interface Icon {
22 | props: IProps;
23 | }
24 |
25 | class Icon extends Component {
26 | static defaultProps = {
27 | theme: 'light',
28 | onClick: () => {}
29 | }
30 | handleClick = () => {
31 | const {onClick} = this.props
32 | typeof onClick === 'function' && onClick()
33 | }
34 | render() {
35 | const { type, theme } = this.props
36 | return (
37 |
38 | {type === 'menu' ? : null}
39 | {type === 'back' ? : null}
40 |
41 | )
42 | }
43 | }
44 |
45 | export default Icon as ComponentClass
--------------------------------------------------------------------------------
/src/components/ResultModal/index.less:
--------------------------------------------------------------------------------
1 | .result-wrap {
2 | width:100%;
3 | height:100%;
4 | position:absolute;
5 | top:0;
6 | left:0;
7 | z-index:1000;
8 | .modal-mask {
9 | width: 100%;
10 | height:100%;
11 | position:absolute;
12 | top:0;
13 | left:0;
14 | background-color:#fff;
15 | }
16 | .modal-content {
17 | padding-top:140rpx;
18 | position:relative;
19 | z-index:1;
20 | width:100%;
21 | height:100%;
22 | box-sizing:border-box;
23 | display:flex;
24 | flex-direction:column;
25 | align-items:center;
26 | justify-content:center;
27 | .pic-wrap {
28 | width:690rpx;
29 | height:920rpx;
30 | .pic {
31 | width:100%;
32 | height:100%;
33 | }
34 | }
35 | .btn-wrap {
36 | width:690rpx;
37 | height:920rpx;
38 | margin-top: 80rpx;
39 | }
40 | }
41 |
42 | .custom-button {
43 | border-radius:80rpx;
44 | width:100%;
45 | height:80rpx;
46 | line-height:80rpx;
47 | color:white;
48 | font-size:36rpx;
49 | text-align:center;
50 | transition:all 0.2s;
51 | &.btn-hover {
52 | transform: scale(0.85)
53 | }
54 | &.pink {
55 | background:#FF3366;
56 | }
57 | &.dark {
58 | background:#333;
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/src/components/ResultModal/index.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentClass } from 'react'
2 | import Taro, { Component } from '@tarojs/taro'
3 | import { View, Button, Image } from '@tarojs/components'
4 |
5 | import './index.less'
6 |
7 | type ComponentStateProps = {}
8 |
9 | type ComponentOwnProps = {
10 | onClick: () => void,
11 | url: string,
12 | }
13 |
14 | type ComponentState = {}
15 |
16 | type IProps = ComponentStateProps & ComponentOwnProps
17 |
18 | interface ResultModal {
19 | props: IProps;
20 | }
21 |
22 | class ResultModal extends Component {
23 | handleClick = () => {
24 | this.props.onClick && this.props.onClick()
25 | }
26 | render() {
27 | return (
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | )
40 | }
41 | }
42 |
43 | export default ResultModal as ComponentClass
--------------------------------------------------------------------------------
/src/components/SceneList/SceneItem/index.less:
--------------------------------------------------------------------------------
1 | .scene-item {
2 | position: relative;
3 | display: inline-block;
4 | width: 120rpx;
5 | height: 150rpx;
6 | margin-right: 44rpx;
7 | box-shadow:1px 1px 5px rgba(0,0,0,0.3);
8 | .music-icon {
9 | position: absolute;
10 | right: 7rpx;
11 | top: 10rpx;
12 | width: 20rpx;
13 | height: 20rpx;
14 | }
15 | .bg {
16 | width: 120rpx;
17 | height: 120rpx;
18 | position: relative;
19 | }
20 | .tag {
21 | position: absolute;
22 | left: 0;
23 | bottom: 0;
24 | box-sizing: border-box;
25 | padding: 0 10rpx;
26 | height: 32rpx;
27 | width: 120rpx;
28 | line-height: 32rpx;
29 | font-size: 16rpx;
30 | color: #fff;
31 | background: #FFA9C2;
32 | display: flex;
33 | align-items:center;
34 | justify-content: center;
35 | .tag-title {
36 | font-size:16rpx;
37 | }
38 | .icon {
39 | margin-right: 10rpx;
40 | display: inline-block;
41 | width: 8rpx;
42 | height: 8rpx;
43 | background: #fff;
44 | border-radius: 8rpx;
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/src/components/SceneList/SceneItem/index.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentClass } from 'react'
2 | import Taro, { Component } from '@tarojs/taro'
3 | import { View, Text, Image } from '@tarojs/components'
4 |
5 | import './index.less'
6 | import loading from '../../../assets/images/pic_loading.png'
7 | import icon_music_gif from '../../../assets/images/icon_music_gif.png'
8 | import icon_only_gif from '../../../assets/images/icon_only_gif.png'
9 |
10 | type ComponentStateProps = {}
11 |
12 | type ComponentOwnProps = {
13 | onClick?: () => void,
14 | bgUrl: string,
15 | thumbnailUrl: string,
16 | sceneName: string,
17 | active: boolean
18 | }
19 |
20 | type ComponentState = {}
21 |
22 | type IProps = ComponentStateProps & ComponentOwnProps
23 |
24 | interface SceneItem {
25 | props: IProps;
26 | }
27 |
28 | class SceneItem extends Component {
29 | static defaultProps = {
30 | bgUrl: loading,
31 | thumbnailUrl: loading,
32 | sceneName: '',
33 | active: false
34 | }
35 | componentWillReceiveProps (nextProps) {
36 | // console.log(this.props, nextProps)
37 | }
38 | render() {
39 | const { onClick, active, thumbnailUrl, bgUrl, sceneName } = this.props
40 | return (
41 |
42 |
47 |
48 |
52 |
56 |
57 |
58 | {active && }
59 | {sceneName}
60 |
61 |
62 | )
63 | }
64 | }
65 |
66 | export default SceneItem as ComponentClass
--------------------------------------------------------------------------------
/src/components/SceneList/index.less:
--------------------------------------------------------------------------------
1 | .scene-list {
2 | // margin-right:-68rpx;
3 | .scroll {
4 | white-space:nowrap;
5 | }
6 | }
--------------------------------------------------------------------------------
/src/components/SceneList/index.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentClass } from 'react'
2 | import Taro, { Component } from '@tarojs/taro'
3 | import { View, ScrollView, Image } from '@tarojs/components'
4 |
5 | import './index.less'
6 | import SceneItem from './SceneItem'
7 | type ComponentStateProps = {}
8 |
9 | type ComponentOwnProps = {
10 | onClick?: (item:object) => void,
11 | list: Array