├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .prettierignore ├── .stylelintrc.json ├── LICENSE ├── README.md ├── config ├── dev.js ├── index.js └── prod.js ├── global.d.ts ├── package.json ├── project.config.json ├── src ├── app.scss ├── app.tsx ├── assets │ └── images │ │ ├── cdn │ │ ├── bg-index.png │ │ └── bg-login.png │ │ └── icon │ │ ├── icon-nav-1.png │ │ ├── icon-nav-2.png │ │ ├── icon-nav-3.png │ │ ├── icon-nav-4.png │ │ ├── icon-nav-5.png │ │ ├── icon-nav-6.png │ │ └── icon-vip.png ├── components │ └── HOC │ │ └── wrapUserAuth.ts ├── config.ts ├── index.html ├── interfaces │ ├── common.ts │ ├── dashborad.ts │ ├── order.ts │ └── product.ts ├── pages │ ├── account │ │ └── login │ │ │ ├── index.scss │ │ │ └── index.tsx │ ├── bluetooth │ │ ├── index.scss │ │ └── index.tsx │ ├── dashborad │ │ ├── index.scss │ │ └── index.tsx │ ├── index │ │ ├── index.scss │ │ └── index.tsx │ ├── order │ │ ├── index.scss │ │ └── index.tsx │ └── product │ │ ├── detail │ │ ├── index.scss │ │ └── index.tsx │ │ ├── index.scss │ │ └── index.tsx ├── services │ ├── shopService.ts │ └── userService.ts ├── stores │ ├── commonStore.ts │ ├── dashboradStore.ts │ ├── orderStore.ts │ ├── productStore.ts │ └── userStore.ts └── utils │ ├── getBELSeviceId.ts │ ├── mockPromise.ts │ ├── requestData.ts │ ├── responseHandler.ts │ └── transformPrice.ts ├── taro-ts-demo.code-workspace ├── tsconfig.json └── yarn.lock /.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 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/src/libs/** 2 | **/node_modules/** 3 | **/dist/** -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true 6 | }, 7 | "parser": "babel-eslint", 8 | "extends": ["taro", "eslint:recommended", "plugin:react/recommended"], 9 | "parserOptions": { 10 | "ecmaVersion": 2017, 11 | "sourceType": "module", 12 | "impliedStrict": true, 13 | "ecmaFeatures": { 14 | "legacyDecorators": true, 15 | "experimentalObjectRestSpread": true, 16 | "jsx": true 17 | } 18 | }, 19 | "rules": { 20 | "no-unused-vars": ["error", {"varsIgnorePattern": "Taro"}], 21 | "array-bracket-spacing": ["error"], 22 | "arrow-body-style": ["error", "as-needed", {"requireReturnForObjectLiteral": true}], 23 | "arrow-parens": ["error", "as-needed"], 24 | "arrow-spacing": ["error"], 25 | "block-spacing": ["error"], 26 | "brace-style": ["error"], 27 | "camelcase": ["error"], 28 | "capitalized-comments": 0, 29 | "comma-dangle": ["error"], 30 | "comma-spacing": ["error"], 31 | "comma-style": ["error"], 32 | "computed-property-spacing": ["error"], 33 | "curly": ["error"], 34 | "dot-location": ["error", "property"], 35 | "dot-notation": ["error"], 36 | "eol-last": ["error"], 37 | "func-call-spacing": ["error"], 38 | "func-name-matching": ["error"], 39 | "generator-star-spacing": ["error"], 40 | "indent": [ 41 | "error", 42 | 2, 43 | { 44 | "SwitchCase": 1, 45 | "MemberExpression": 1 46 | } 47 | ], 48 | "jsx-quotes": ["error"], 49 | "key-spacing": ["error"], 50 | "keyword-spacing": ["error"], 51 | "lines-around-comment": "off", 52 | "lines-around-directive": ["error"], 53 | "new-cap": ["error"], 54 | "newline-after-var": ["error"], 55 | "newline-before-return": ["error"], 56 | "new-parens": ["error"], 57 | "no-cond-assign": ["off"], 58 | "no-console": ["off"], 59 | "no-unsafe-finally": "off", 60 | "no-delete-var": ["off"], 61 | "no-extra-bind": ["error"], 62 | "no-extra-parens": ["error"], 63 | "no-floating-decimal": ["error"], 64 | "no-lonely-if": ["error"], 65 | "no-multiple-empty-lines": [ 66 | "error", 67 | { 68 | "max": 1, 69 | "maxBOF": 0, 70 | "maxEOF": 0 71 | } 72 | ], 73 | "no-multi-spaces": ["error"], 74 | "no-redeclare": ["error"], 75 | "no-trailing-spaces": ["error"], 76 | "no-undef-init": ["error"], 77 | "no-useless-computed-key": ["error"], 78 | "no-useless-rename": ["error"], 79 | "no-useless-return": ["error"], 80 | "no-var": ["error"], 81 | "no-whitespace-before-property": ["error"], 82 | "object-curly-newline": ["error"], 83 | "object-curly-spacing": ["error", "always", {"objectsInObjects": false}], 84 | "object-shorthand": ["error"], 85 | "operator-assignment": ["error"], 86 | "one-var": ["error", "never"], 87 | "one-var-declaration-per-line": ["error"], 88 | "padded-blocks": ["error", "never"], 89 | "prefer-arrow-callback": ["error"], 90 | "prefer-spread": ["error"], 91 | "quotes": ["error", "single"], 92 | "quote-props": ["error", "as-needed"], 93 | "react/prop-types": ["off"], 94 | "react/jsx-curly-spacing": ["error"], 95 | "react/jsx-tag-spacing": ["error"], 96 | "react/jsx-filename-extension": [1, {"extensions": [".js", ".jsx", ".tsx"]}], 97 | "react/no-unescaped-entities": "off", 98 | "react/no-find-dom-node": "off", 99 | "rest-spread-spacing": ["error"], 100 | "semi": ["error", "never"], 101 | "space-before-blocks": ["error"], 102 | "space-before-function-paren": ["error", "always"], 103 | "space-in-parens": ["error", "never"], 104 | "space-infix-ops": ["error"], 105 | "space-unary-ops": [ 106 | "error", 107 | { 108 | "words": true, 109 | "nonwords": false 110 | } 111 | ], 112 | "spaced-comment": ["error"], 113 | "template-curly-spacing": ["error"], 114 | "yield-star-spacing": ["error"], 115 | "yoda": ["error"], 116 | "function-paren-newline": ["error", "consistent"], 117 | "no-invalid-this": 0 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | .temp/ 3 | .rn_temp/ 4 | node_modules/ 5 | .DS_Store 6 | .vscode/ 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | package-lock.json 3 | project.config.json 4 | **/src/libs/** 5 | **/node_modules/** 6 | **/dist/** -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreFiles": ["*.js", " *.ts", " *.tsx"], 3 | "extends": "stylelint-config-standard", 4 | "rules": { 5 | "unit-case": null, 6 | "no-descending-specificity": [true, {"severity": "warning"}], 7 | "selector-type-no-unknown": [true, {"ignoreTypes": ["page"]}], 8 | "block-no-empty": null 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 XiaoLei 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 | ## 现在taro更新变化较大,所以本项目仅做参考学习 2022/04/26 2 | 3 | # 使用 React 和 TypeScript 写小程序 4 | 5 | 微信小程序端,使用 Taro 框架。React+TypeScript 语法,Mobx 管理数据,Taro UI 框架。 6 | 7 | ## Taro 框架 8 | 9 | [Taro 介绍 · Taro](https://nervjs.github.io/taro/docs/README.html) 10 | 11 | > **Taro**是一套遵循 [React](https://reactjs.org/) 语法规范的**多端开发**解决方案。现如今市面上端的形态多种多样,Web、React-Native、微信小程序等各种端大行其道,当业务要求同时在不同的端都要求有所表现的时候,针对不同的端去编写多套代码的成本显然非常高,这时候只编写一套代码就能够适配到多端的能力就显得极为需要。 12 | > 使用**Taro**,我们可以只书写一套代码,再通过**Taro**的编译工具,将源代码分别编译出可以在不同端(微信/百度/支付宝/字节跳动小程序、H5、React-Native 等)运行的代码。 13 | 14 | ## 开发环境 15 | 16 | yarn 17 | 使用终端或者 cmd 安装 yarn[安装 | Yarn 中文文档](https://yarn.bootcss.com/docs/install/#mac-stable) 18 | 19 | 进入项目根目录,执行如下命令安装依赖包: 20 | 21 | ```sh 22 | yarn 23 | ``` 24 | 25 | ## 开始开发 26 | 27 | 小程序调试模式 28 | 29 | ```sh 30 | yarn dev:weapp 31 | ``` 32 | 33 | h5 调试模式 34 | 35 | ```sh 36 | yarn dev:h5 37 | ``` 38 | 39 | 开发时可以先在 h5 模式下完成大部分业务逻辑和 UI 调试,然后同时开启小程序和 H5 模式,对照查看效果,调试页面。 40 | 41 | 构建小程序发布文件 42 | 43 | ```sh 44 | yarn build:weapp 45 | ``` 46 | 47 | ## 概述 48 | 49 | 虽然微信小程序很强大,但是小程序对于开发者来说,确实不太友好,还好现在也有一些开源的小程序框架,此项目选用了[Aotu.io「凹凸实验室」](https://aotu.io/)出品的 Taro 框架。 50 | 框架采用**React 语法风格**,组件生命周期与 React 保持一致,最后编译成各端小程序和 H5,本项目仅兼容了微信小程序。但是可以用很小的代价将项目兼容其他端。 51 | 项目采用了 Mobx 管理数据,这是 React 流行的数据管理方案之一,_简单、可扩展的状态管理_,需要注意项目使用的是 Mobx 4 版本。 52 | 同时项目使用 TypeScript 语法,给 JavaScript 加上类型系统,提高了开发效率和代码质量。 53 | 54 | 技术栈 55 | 56 | 1. Taro // 小程序框架,项目管理、开发、编译打包 57 | 2. yarn // 包管理器 58 | 3. Taro UI // UI 框架,使用了里面的组件,项目中进行了个性化定制 59 | 4. React //语法风格 60 | 5. TypeScript // 语言类型系统 61 | 6. Sass // style 模块 62 | 7. Mobx // 数据管理方案 63 | 8. lodash // 辅助函数 64 | 65 | ## 资源 66 | 67 | - 项目配置 解释 [package.json 文件 — JavaScript 标准参考教程(alpha)](http://javascript.ruanyifeng.com/nodejs/packagejson.html) 68 | - Taro 中文文档:[Taro 介绍 · Taro](https://nervjs.github.io/taro/docs/README.html) 69 | - React 中文文档:[React 中文文档 - 用于构建用户界面的 JavaScript 库](https://react.docschina.org/) 70 | - Mobx 中文文档:[MobX 介绍 · MobX 中文文档](https://cn.mobx.js.org/) 71 | - TypeScript 中文文档:[文档简介 · TypeScript 中文网 · TypeScript——JavaScript 的超集](https://www.tslang.cn/docs/home.html) 72 | - Taro UI:[Taro UI | O2Team](https://taro-ui.aotu.io/#/) 73 | - Lodash.js 中文文档: https://www.html.cn/doc/lodash/ 74 | - scss:[Sass 参考手册 | Sass 中文文档](http://sass.bootcss.com/docs/sass-reference/) 75 | 76 | ##**目录结构** 77 | 78 | ``` 79 | — config/ // 构建配置,Taro默认生成 80 | — dist/ // 小程序构建缓存和输出目录 81 | — node_modules/ // 所以依赖安装包文件 82 | — src/ // 项目文件 83 | assets/ // 所有资源文件 如图片、字体、样式文件 84 | components/// 组件目录 85 | interface/// 公共接口 86 | libs // 手动导入的库,此目录不会进行代码检查和压缩编译 87 | pages/ // 项目页面目录,对应微信小程序的pages 88 | services/// 与后端交互的接口请求文件 89 | stores/ // mobx数据管理 90 | utils/ // 一些中间件、插件方法等 91 | app.scss// 项目的配置 92 | app.tsx// 应用入口 93 | — .babelre // babel插件的配置文件 94 | - .eslintignore // eslint配置 95 | - .eslintrc.json // eslint配置 96 | - .stylelintrc.json // stylelint配置 97 | - tsconfig.json // ts语言的配置 98 | — package.json // 项目描述文件 99 | — README.md // 项目文档 100 | - project.config.json // 小程序配置文件 101 | ``` 102 | 103 | ##**PS** 104 | 105 | 1. 如果出现 node-saas 导致安装失败,可以将 yarn 源切换到淘宝的再试试 `yarn config set sass_binary_site http://cdn.npm.taobao.org/dist/node-sass -g` 106 | 107 | > Node-sass 比较特殊,建议提前进行安装,规避可能出现的各种异常错误。 108 | > npm i -g node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node_sass/ 109 | -------------------------------------------------------------------------------- /config/dev.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-commonjs 2 | module.exports = { 3 | env: { NODE_ENV: '"development"' }, 4 | defineConstants: {}, 5 | weapp: {}, 6 | h5: {} 7 | } 8 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-commonjs 2 | const path = require('path') 3 | 4 | const alias = { 5 | '@/components': path.resolve(__dirname, '..', 'src/components'), 6 | '@/assets': path.resolve(__dirname, '..', 'src/assets'), 7 | '@/interfaces': path.resolve(__dirname, '..', 'src/interfaces'), 8 | '@/services': path.resolve(__dirname, '..', 'src/services'), 9 | '@/stores': path.resolve(__dirname, '..', 'src/stores'), 10 | '@/utils': path.resolve(__dirname, '..', 'src/utils'), 11 | '@/src': path.resolve(__dirname, '..', 'src') 12 | } 13 | 14 | // NOTE 在 sass 中通过别名(alias配置,@ 或 ~)引用需要指定路径 15 | const sassImporter = function (url) { 16 | if (url[0] === '~' && url[1] !== '/') { 17 | return { file: path.resolve(__dirname, '..', 'node_modules', url.substr(1)) } 18 | } 19 | 20 | return { file: url } 21 | 22 | // const reg = /^(@\/[a-z]+)\/(.*)/ 23 | // const res = url.match(reg) 24 | 25 | // return { file: reg.test(url) && alias[res[1]] ? path.join(alias[res[1]], res[2]) : url } 26 | } 27 | 28 | const config = { 29 | projectName: 'teashop-seller', 30 | date: '2019-3-14', 31 | designWidth: 750, 32 | deviceRatio: { 33 | 640: 2.34 / 2, 34 | 750: 1, 35 | 828: 1.81 / 2 36 | }, 37 | alias, 38 | sourceRoot: 'src', 39 | outputRoot: 'dist', 40 | plugins: { 41 | babel: { 42 | sourceMap: true, 43 | presets: [['env', { modules: false }]], 44 | plugins: ['lodash', 'transform-decorators-legacy', 'transform-class-properties', 'transform-object-rest-spread'] 45 | }, 46 | sass: { importer: sassImporter } 47 | }, 48 | defineConstants: {}, 49 | copy: { 50 | patterns: [], 51 | options: {} 52 | }, 53 | weapp: { 54 | module: { 55 | postcss: { 56 | autoprefixer: { 57 | enable: true, 58 | config: { browsers: ['last 3 versions', 'Android >= 4.1', 'ios >= 8'] } 59 | }, 60 | pxtransform: { 61 | enable: true, 62 | config: {} 63 | }, 64 | url: { 65 | enable: true, 66 | config: { limit: 10240 } // 设定转换尺寸上限 67 | }, 68 | cssModules: { 69 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 70 | config: { 71 | namingPattern: 'module', // 转换模式,取值为 global/module 72 | generateScopedName: '[name]__[local]___[hash:base64:5]' 73 | } 74 | } 75 | } 76 | } 77 | }, 78 | h5: { 79 | publicPath: '/', 80 | staticDirectory: 'static', 81 | esnextModules: ['taro-ui'], 82 | module: { 83 | postcss: { 84 | autoprefixer: { 85 | enable: true, 86 | config: { browsers: ['last 3 versions', 'Android >= 4.1', 'ios >= 8'] } 87 | }, 88 | cssModules: { 89 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 90 | config: { 91 | namingPattern: 'module', // 转换模式,取值为 global/module 92 | generateScopedName: '[name]__[local]___[hash:base64:5]' 93 | } 94 | } 95 | } 96 | } 97 | } 98 | } 99 | 100 | module.exports = function (merge) { 101 | if (process.env.NODE_ENV === 'development') { 102 | return merge({}, config, require('./dev')) 103 | } 104 | 105 | return merge({}, config, require('./prod')) 106 | } 107 | -------------------------------------------------------------------------------- /config/prod.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-commonjs 2 | module.exports = { 3 | env: { NODE_ENV: '"production"' }, 4 | defineConstants: {}, 5 | weapp: {}, 6 | h5: {} 7 | } 8 | -------------------------------------------------------------------------------- /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 | 12 | declare namespace JSX { 13 | interface IntrinsicElements { 14 | import: React.DetailedHTMLProps, HTMLEmbedElement> 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "teashop-seller", 3 | "version": "1.1.0", 4 | "private": true, 5 | "description": "微信小程序 React+TypeScript 使用Taro框架", 6 | "main": "index.js", 7 | "scripts": { 8 | "build:weapp": "taro build --type weapp", 9 | "build:swan": "taro build --type swan", 10 | "build:alipay": "taro build --type alipay", 11 | "build:tt": "taro build --type tt", 12 | "build:h5": "taro build --type h5", 13 | "dev:weapp": "npm run build:weapp -- --watch", 14 | "dev:swan": "npm run build:swan -- --watch", 15 | "dev:alipay": "npm run build:alipay -- --watch", 16 | "dev:tt": "npm run build:tt -- --watch", 17 | "dev:h5": "npm run build:h5 -- --watch" 18 | }, 19 | "author": "leidenglai@qq.com", 20 | "license": "MIT", 21 | "dependencies": { 22 | "@tarojs/async-await": "^1.2.24", 23 | "@tarojs/components": "1.2.17", 24 | "@tarojs/mobx": "1.2.17", 25 | "@tarojs/mobx-h5": "1.2.17", 26 | "@tarojs/router": "1.2.17", 27 | "@tarojs/taro": "1.2.17", 28 | "@tarojs/taro-alipay": "1.2.17", 29 | "@tarojs/taro-h5": "1.2.17", 30 | "@tarojs/taro-swan": "1.2.17", 31 | "@tarojs/taro-tt": "1.2.17", 32 | "@tarojs/taro-weapp": "1.2.17", 33 | "@types/lodash": "^4.14.123", 34 | "classnames": "^2.2.6", 35 | "hoist-non-react-statics": "^3.3.0", 36 | "lodash": "4.17.19", 37 | "mobx": "4.8.0", 38 | "nerv-devtools": "^1.3.9", 39 | "nervjs": "^1.3.9", 40 | "taro-ui": "^2.0.2" 41 | }, 42 | "devDependencies": { 43 | "@tarojs/cli": "1.2.17", 44 | "@tarojs/plugin-babel": "1.2.17", 45 | "@tarojs/plugin-csso": "1.2.17", 46 | "@tarojs/plugin-less": "1.2.17", 47 | "@tarojs/plugin-sass": "1.2.17", 48 | "@tarojs/plugin-uglifyjs": "1.2.17", 49 | "@tarojs/webpack-runner": "1.2.17", 50 | "@types/react": "16.3.14", 51 | "@types/webpack-env": "^1.13.6", 52 | "babel-eslint": "^8.2.3", 53 | "babel-plugin-lodash": "^3.3.4", 54 | "babel-plugin-transform-class-properties": "^6.24.1", 55 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 56 | "babel-plugin-transform-jsx-stylesheet": "^0.6.5", 57 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 58 | "babel-preset-env": "^1.6.1", 59 | "eslint": "^4.19.1", 60 | "eslint-config-taro": "1.2.17", 61 | "eslint-plugin-import": "^2.12.0", 62 | "eslint-plugin-react": "^7.8.2", 63 | "eslint-plugin-taro": "1.2.17", 64 | "file-loader": "^3.0.1", 65 | "stylelint": "^9.10.1", 66 | "stylelint-config-standard": "^18.2.0", 67 | "typescript": "^3.0.1" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "miniprogramRoot": "dist/", 3 | "projectname": "teashop-seller", 4 | "description": "微信小程序 React+TypeScript 使用tora框架", 5 | "appid": "wxd33f69e4f6042184", 6 | "setting": { 7 | "urlCheck": false, 8 | "es6": false, 9 | "postcss": false, 10 | "minified": false, 11 | "newFeature": true, 12 | "autoAudits": false 13 | }, 14 | "compileType": "miniprogram", 15 | "simulatorType": "wechat", 16 | "simulatorPluginLibVersion": {}, 17 | "condition": {} 18 | } -------------------------------------------------------------------------------- /src/app.scss: -------------------------------------------------------------------------------- 1 | .at-loading { 2 | &, 3 | &__ring { 4 | display: inline-block; 5 | position: relative; 6 | width: 14px; 7 | height: 14px; 8 | } 9 | 10 | &__ring { 11 | box-sizing: border-box; 12 | display: block; 13 | position: absolute; 14 | margin: 1px; 15 | border-width: 1px; 16 | border-style: solid; 17 | border-color: #6190e8 transparent transparent; 18 | border-radius: 50%; 19 | animation: loading 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; 20 | 21 | &:nth-child(1) { 22 | animation-delay: -0.45s; 23 | } 24 | 25 | &:nth-child(2) { 26 | animation-delay: -0.3s; 27 | } 28 | 29 | &:nth-child(3) { 30 | animation-delay: -0.15s; 31 | } 32 | } 33 | } 34 | 35 | @keyframes loading { 36 | 0% { 37 | transform: rotate(0deg); 38 | } 39 | 40 | to { 41 | transform: rotate(360deg); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/app.tsx: -------------------------------------------------------------------------------- 1 | import '@tarojs/async-await' 2 | import Taro, { Component, Config } from '@tarojs/taro' 3 | import { Provider } from '@tarojs/mobx' 4 | import CommonStore from './stores/commonStore' 5 | import UserStore from './stores/userStore' 6 | import ProductStore from './stores/productStore' 7 | import DashboradStore from './stores/dashboradStore' 8 | import OrderStore from './stores/orderStore' 9 | import './app.scss' 10 | 11 | const store = { 12 | commonStore: new CommonStore(), 13 | userStore: new UserStore(), 14 | productStore: new ProductStore(), 15 | dashboradStore: new DashboradStore(), 16 | orderStore: new OrderStore() 17 | } 18 | 19 | class App extends Component { 20 | /** 21 | * 指定config的类型声明为: Taro.Config 22 | * 23 | * 由于 typescript 对于 object 类型推导只能推出 Key 的基本类型 24 | * 对于像 navigationBarTextStyle: 'black' 这样的推导出的类型是 string 25 | * 提示和声明 navigationBarTextStyle: 'black' | 'white' 类型冲突, 需要显示声明类型 26 | */ 27 | config: Config = { 28 | pages: [ 29 | 'pages/index/index', 30 | 'pages/account/login/index', 31 | 'pages/bluetooth/index', 32 | 'pages/dashborad/index', 33 | 'pages/order/index', 34 | 'pages/product/index', 35 | 'pages/product/detail/index' 36 | ], 37 | window: { 38 | navigationBarBackgroundColor: '#fff', 39 | navigationBarTextStyle: 'black', 40 | navigationBarTitleText: '首页', 41 | backgroundTextStyle: 'light', 42 | backgroundColor: '#ffffff' 43 | }, 44 | permission: { 'scope.userLocation': { desc: '用于连接蓝牙设备打印票据' }} 45 | } 46 | 47 | componentDidMount () {} 48 | 49 | componentDidShow () {} 50 | 51 | componentDidHide () {} 52 | 53 | componentDidCatchError () {} 54 | 55 | // 在 App 类中的 render() 函数没有实际作用 56 | // 请勿修改此函数 57 | render () { 58 | return 59 | } 60 | } 61 | 62 | Taro.render(, document.getElementById('app')) 63 | -------------------------------------------------------------------------------- /src/assets/images/cdn/bg-index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leidenglai/weapp-taro-typescript/a99b8f2c4cda58e560529e29194ddbbddaa48656/src/assets/images/cdn/bg-index.png -------------------------------------------------------------------------------- /src/assets/images/cdn/bg-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leidenglai/weapp-taro-typescript/a99b8f2c4cda58e560529e29194ddbbddaa48656/src/assets/images/cdn/bg-login.png -------------------------------------------------------------------------------- /src/assets/images/icon/icon-nav-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leidenglai/weapp-taro-typescript/a99b8f2c4cda58e560529e29194ddbbddaa48656/src/assets/images/icon/icon-nav-1.png -------------------------------------------------------------------------------- /src/assets/images/icon/icon-nav-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leidenglai/weapp-taro-typescript/a99b8f2c4cda58e560529e29194ddbbddaa48656/src/assets/images/icon/icon-nav-2.png -------------------------------------------------------------------------------- /src/assets/images/icon/icon-nav-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leidenglai/weapp-taro-typescript/a99b8f2c4cda58e560529e29194ddbbddaa48656/src/assets/images/icon/icon-nav-3.png -------------------------------------------------------------------------------- /src/assets/images/icon/icon-nav-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leidenglai/weapp-taro-typescript/a99b8f2c4cda58e560529e29194ddbbddaa48656/src/assets/images/icon/icon-nav-4.png -------------------------------------------------------------------------------- /src/assets/images/icon/icon-nav-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leidenglai/weapp-taro-typescript/a99b8f2c4cda58e560529e29194ddbbddaa48656/src/assets/images/icon/icon-nav-5.png -------------------------------------------------------------------------------- /src/assets/images/icon/icon-nav-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leidenglai/weapp-taro-typescript/a99b8f2c4cda58e560529e29194ddbbddaa48656/src/assets/images/icon/icon-nav-6.png -------------------------------------------------------------------------------- /src/assets/images/icon/icon-vip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leidenglai/weapp-taro-typescript/a99b8f2c4cda58e560529e29194ddbbddaa48656/src/assets/images/icon/icon-vip.png -------------------------------------------------------------------------------- /src/components/HOC/wrapUserAuth.ts: -------------------------------------------------------------------------------- 1 | import Taro from '@tarojs/taro' 2 | import hoistStatics from 'hoist-non-react-statics' 3 | import { ComponentClass } from '@/interfaces/common' 4 | 5 | /** 6 | * 页面级登录校验装饰器 反向继承(Inherbitance Inversion HOC) 7 | * InheritedPage.$componentType='PAGE' 8 | * 校验不通过替换为登录页,登录成功回到原页面 9 | */ 10 | export default function wrapUserAuth (InheritedPage: T): T { 11 | class UserAuthPage extends InheritedPage { 12 | static displayName = InheritedPage.displayName || InheritedPage.name 13 | static propTypes = InheritedPage.propTypes 14 | static defaultProps = InheritedPage.defaultProps 15 | 16 | /** 当前页面是否登录 */ 17 | private __isLogin: boolean = false 18 | 19 | componentWillMount () { 20 | if (this.$componentType === 'PAGE') { 21 | // 获取当前路由 22 | const path: string = this.$router.path || '' 23 | const token: string = Taro.getStorageSync('access_token') 24 | 25 | if (!token) { 26 | this.__isLogin = false 27 | 28 | Taro.redirectTo({ url: '/pages/account/login/index?redirect=' + path }) 29 | } else { 30 | this.__isLogin = true 31 | } 32 | } 33 | 34 | if (super.componentWillMount) { 35 | super.componentWillMount() 36 | } 37 | } 38 | 39 | render () { 40 | if (this.$componentType === 'PAGE' && !this.__isLogin) { 41 | return null 42 | } else { 43 | return super.render() 44 | } 45 | } 46 | } 47 | 48 | // 拷贝静态属性 49 | // 类修饰器不能让属性丢失 50 | hoistStatics(UserAuthPage, InheritedPage) 51 | 52 | return UserAuthPage 53 | } 54 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | /** 服务器协议 production 必须是https */ 2 | export let SERVER_PROTOCOL = 'http://' 3 | 4 | /** 后端 API 地址 */ 5 | export const SERVER_API_ROOT_API = 'api.xxxxx.com' 6 | 7 | /** 请求默认参数 */ 8 | export const DEF_REQUEST_CONFIG = { access_token: '' // 用户登录获取,未登录状态不用传。本地存储 key为 access_token 9 | } 10 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Taro 12 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /src/interfaces/common.ts: -------------------------------------------------------------------------------- 1 | import { ValidationMap } from 'prop-types' 2 | import { Component } from '@tarojs/taro' 3 | 4 | export interface IPager { 5 | page: number 6 | num: number 7 | count: number 8 | } 9 | 10 | /** 请求参数 */ 11 | export interface IReqData { 12 | /** 后端API */ 13 | api: string 14 | 15 | /** 请求参数*/ 16 | params?: { [key: string]: any } 17 | method?: 'POST' | 'GET' 18 | } 19 | 20 | /** 错误返回 */ 21 | export interface IResError { 22 | code: number 23 | message?: string 24 | data?: any 25 | } 26 | 27 | /** page类组件 */ 28 | interface PageComponent extends Component { 29 | // $componentType 应该必选是'PAGE' 但是框架的类型系统不完善 30 | $componentType: 'PAGE' | 'COMPONENT' 31 | 32 | $router: { 33 | params: any 34 | preload: any 35 | path?: string 36 | } 37 | } 38 | 39 | /** 40 | * 组件类类型 41 | */ 42 | export interface ComponentClass

{ 43 | new (...args: any[]): PageComponent 44 | propTypes?: ValidationMap

45 | defaultProps?: Partial

46 | displayName?: string 47 | } 48 | 49 | /** 蓝牙数据 */ 50 | export interface IBLEInformation { 51 | platform?: string 52 | /** 设备的 id */ 53 | deviceId: string 54 | } 55 | 56 | /** 蓝牙服务 */ 57 | export interface IBLEServices { 58 | /** 蓝牙设备此特征值的 uuid */ 59 | writeCharaterId: string 60 | /** 蓝牙设备此服务的 uuid */ 61 | writeServiceId: string 62 | /** 蓝牙设备此特征值的 uuid */ 63 | notifyCharaterId: string 64 | /** 蓝牙设备此服务的 uuid */ 65 | notifyServiceId: string 66 | /** 蓝牙设备此特征值的 uuid */ 67 | readCharaterId: string 68 | /** 蓝牙设备此服务的 uuid */ 69 | readServiceId: string 70 | } 71 | -------------------------------------------------------------------------------- /src/interfaces/dashborad.ts: -------------------------------------------------------------------------------- 1 | interface IProduct { 2 | /** 总数 */ 3 | total_num: number 4 | /** 单价 ,单位分*/ 5 | price: number 6 | /** 总金额 ,单位分*/ 7 | total_money: number 8 | /** 标题 */ 9 | title: string 10 | /** 规格 */ 11 | standard_text: string 12 | } 13 | 14 | /** 销售数据统计类型 */ 15 | export type ISalesType = 1 | 2 | 3 | 4 16 | 17 | /** 销售数据 */ 18 | export interface ISalesData { 19 | /** 销售总数 */ 20 | total_num: number 21 | /** 总金额,单位分 */ 22 | total_money: number 23 | lists: IProduct[] 24 | /** 列表条数数 */ 25 | count: number 26 | } 27 | -------------------------------------------------------------------------------- /src/interfaces/order.ts: -------------------------------------------------------------------------------- 1 | export interface IOrderDetail { 2 | /** 订单号 */ 3 | order_number: string 4 | /** 订单序号 */ 5 | take_number: number 6 | /** 商品金额 */ 7 | sum_money: number 8 | /** 买家电话 */ 9 | buy_phone: string 10 | /** 打印信息 */ 11 | print_data: (number | string)[][] 12 | product_lists: { 13 | /** 数量 */ 14 | num: number 15 | /** 商品名称 */ 16 | title: string 17 | /** 商品规格 */ 18 | standard_text: string 19 | }[] 20 | /** 总数量 */ 21 | total_num: number 22 | /** 事件 cancel_payment_order:退单 receipt_order:开始制作 print:打印 reprint:重新打印 */ 23 | events: ('cancel_payment_order' | 'receipt_order' | 'print' | 'reprint')[] 24 | /** 订单时间 */ 25 | insert_date: string 26 | } 27 | -------------------------------------------------------------------------------- /src/interfaces/product.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 产品基础数据 3 | */ 4 | interface ISimpleProduct { 5 | /** 单价 */ 6 | price: number 7 | /** vip价格 */ 8 | vip_price: number 9 | /** 销量 */ 10 | sale_num: number 11 | /** 子标题 */ 12 | sub_title: string 13 | /** 产品图 */ 14 | thumb: string 15 | /** 产品名 */ 16 | title: string 17 | /** 商品ID*/ 18 | product_id: number 19 | /** 上架状态 1:上架 2:下架 */ 20 | status: 1 | 2 21 | } 22 | 23 | /** 24 | * 列表产品字段 25 | */ 26 | export interface ILiProductInfo extends ISimpleProduct { 27 | /** 1有库存 2无库存 */ 28 | no_stock: 1 | 2 29 | /** 最大够买数量 */ 30 | max_buy_num: number 31 | } 32 | 33 | /** 34 | * 产品详情 35 | */ 36 | export interface IProductDetail extends ISimpleProduct { 37 | /** 备注 显示在小标题下面的sku文字 */ 38 | shop_comment: string 39 | /** 图片列表 */ 40 | images: string[] 41 | /** 长描述 */ 42 | context?: string 43 | /** 上架数量 */ 44 | on_stock_num: number 45 | /** 富文本详情 */ 46 | content: string 47 | /** 库存信息 */ 48 | stock_lists: { 49 | name: string 50 | /** SKU Id */ 51 | product_sku: string 52 | /** 库存 */ 53 | number: number 54 | }[] 55 | } 56 | 57 | export interface ISkuSelected { 58 | [standard_name: string]: string | null 59 | } 60 | -------------------------------------------------------------------------------- /src/pages/account/login/index.scss: -------------------------------------------------------------------------------- 1 | $color-brand: #ff6140; 2 | $color-brand-light: #ff8970; 3 | $color-brand-dark: #cc4e33; 4 | $color-border-base: #ccc; 5 | 6 | @import '~taro-ui/dist/style/components/input.scss'; 7 | @import '~taro-ui/dist/style/components/button.scss'; 8 | 9 | .at-input { 10 | margin-left: 0; 11 | margin-bottom: 50px; 12 | 13 | input { 14 | font-size: 36px; 15 | } 16 | } 17 | 18 | .at-button { 19 | height: 100px; 20 | line-height: 100px; 21 | width: 330px; 22 | margin: 0; 23 | font-size: 36px; 24 | font-weight: 500; 25 | } 26 | 27 | .account-login { 28 | &__page { 29 | box-sizing: border-box; 30 | height: 100vh; 31 | padding: 280px 30px 0; 32 | background-color: #fff; 33 | background-image: url(https://www.leidenglai.com/image/weapp/cdn/bg-login.png); 34 | background-size: 100% auto; 35 | background-repeat: no-repeat; 36 | background-position: bottom center; 37 | } 38 | 39 | &__input-group { 40 | width: 100%; 41 | height: 540px; 42 | padding: 30px 30px 90px; 43 | margin-bottom: 30px; 44 | box-sizing: border-box; 45 | background-color: #fff; 46 | box-shadow: 0 23px 54px 16px rgba(0, 0, 0, 0.1); 47 | border-radius: 24px; 48 | } 49 | 50 | &__title { 51 | font-size: 50px; 52 | font-weight: 500; 53 | margin-bottom: 80px; 54 | color: #3e4a59; 55 | } 56 | 57 | &__action { 58 | display: flex; 59 | justify-content: flex-end; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/pages/account/login/index.tsx: -------------------------------------------------------------------------------- 1 | import mockPromise from '@/utils/mockPromise' 2 | import Taro, { Component, Config } from '@tarojs/taro' 3 | import userService from '@/services/userService' 4 | import { AtButton, AtForm, AtInput } from 'taro-ui' 5 | import { View } from '@tarojs/components' 6 | import './index.scss' 7 | 8 | /** 当前页面路由参数 */ 9 | interface IRouter { 10 | redirect?: string 11 | } 12 | 13 | interface IState { 14 | username: string 15 | password: string 16 | } 17 | 18 | /** 19 | * 登录页 20 | **/ 21 | class AccountLoginPage extends Component<{}, IState> { 22 | /** 23 | * 指定config的类型声明为: Taro.Config 24 | * 25 | * 由于 typescript 对于 object 类型推导只能推出 Key 的基本类型 26 | * 对于像 navigationBarTextStyle: 'black' 这样的推导出的类型是 string 27 | * 提示和声明 navigationBarTextStyle: 'black' | 'white' 类型冲突, 需要显示声明类型 28 | */ 29 | config: Config = { navigationBarTitleText: '登录' } 30 | 31 | readonly state: IState = { username: '', password: '' } 32 | 33 | /** 34 | * 登录 35 | */ 36 | handleSubmitLogin () { 37 | const { password, username } = this.state 38 | const { redirect } = this.$router.params as IRouter 39 | 40 | // userService.fetchUserLogin({ username, password, os_type: 1 }).then( 41 | console.log(password, username) 42 | mockPromise({ access_token: 'xxxxx' }).then( 43 | res => { 44 | // 登陆成功 45 | // 将access_token存在本地 46 | Taro.setStorageSync('access_token', res.access_token) 47 | 48 | if (redirect) { 49 | // 回到来源页 50 | Taro.redirectTo({ url: redirect }) 51 | } else { 52 | // 跳转到首页 53 | Taro.redirectTo({ url: '/pages/index/index' }) 54 | } 55 | }, 56 | err => { 57 | // 登陆失败 58 | Taro.showToast({ title: err.message, icon: 'none' }) 59 | } 60 | ) 61 | } 62 | 63 | handleChangeUsername = (value: string) => { 64 | this.setState({ username: value }) 65 | } 66 | handleChangePassword = (value: string) => { 67 | this.setState({ password: value }) 68 | } 69 | 70 | render () { 71 | return ( 72 | 73 | 74 | 75 | 登录 76 | 77 | 78 | 79 | 80 | 81 | 82 | 登录 83 | 84 | 85 | 86 | 87 | ) 88 | } 89 | } 90 | 91 | export default AccountLoginPage 92 | -------------------------------------------------------------------------------- /src/pages/bluetooth/index.scss: -------------------------------------------------------------------------------- 1 | $color-brand: #ff6140; 2 | $color-brand-light: #ff8970; 3 | $color-brand-dark: #cc4e33; 4 | $color-border-base: #ccc; 5 | 6 | @import '~taro-ui/dist/style/components/button.scss'; 7 | 8 | .at-loading, 9 | .at-loading__ring { 10 | width: 32px; 11 | height: 32px; 12 | } 13 | 14 | .bluetoothPage { 15 | padding: 30px; 16 | } 17 | 18 | .deviceList { 19 | .item { 20 | display: flex; 21 | color: #333; 22 | padding: 16px 8px; 23 | font-size: 28px; 24 | margin-top: 20px; 25 | background-color: #f1f1f1; 26 | border-radius: 10px; 27 | // prettier-ignore 28 | border-bottom: 1Px solid #d6d6d6; 29 | 30 | .name { 31 | width: 200px; 32 | margin-right: 16px; 33 | } 34 | 35 | .deviceId { 36 | flex: 1; 37 | overflow: hidden; 38 | text-overflow: ellipsis; 39 | white-space: nowrap; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/pages/bluetooth/index.tsx: -------------------------------------------------------------------------------- 1 | import Taro, { Component, Config, getBluetoothDevices } from '@tarojs/taro' 2 | import { observer, inject } from '@tarojs/mobx' 3 | import _ from 'lodash' 4 | import { View } from '@tarojs/components' 5 | import './index.scss' 6 | import wrapUserAuth from '@/components/HOC/wrapUserAuth' 7 | import { AtButton } from 'taro-ui' 8 | import { ICommonStore } from '@/stores/commonStore' 9 | import { IBLEInformation } from '@/interfaces/common' 10 | import getBELSeviceId from '@/utils/getBELSeviceId' 11 | import { runInAction } from 'mobx' 12 | 13 | interface IState { 14 | /** 已知的蓝牙设备列表 */ 15 | deviceList: getBluetoothDevices.PromisedPropDevices 16 | isScanning: boolean 17 | } 18 | 19 | interface InjectStoreProps { 20 | commonStore: ICommonStore 21 | } 22 | 23 | /** 24 | * 蓝牙连接管理 25 | */ 26 | @wrapUserAuth 27 | @inject('commonStore') 28 | @observer 29 | class BluetoothPage extends Component<{}, IState> { 30 | get inject () { 31 | // 兼容注入store 32 | return this.props as InjectStoreProps 33 | } 34 | 35 | private BLEInformation: IBLEInformation = { 36 | platform: '', 37 | deviceId: '' 38 | } 39 | 40 | /** 41 | * 指定config的类型声明为: Taro.Config 42 | * 43 | * 由于 typescript 对于 object 类型推导只能推出 Key 的基本类型 44 | * 对于像 navigationBarTextStyle: 'black' 这样的推导出的类型是 string 45 | * 提示和声明 navigationBarTextStyle: 'black' | 'white' 类型冲突, 需要显示声明类型 46 | */ 47 | config: Config = { 48 | navigationBarTitleText: '搜索设备', 49 | enablePullDownRefresh: true 50 | } 51 | 52 | readonly state: IState = { 53 | deviceList: [], 54 | isScanning: false 55 | } 56 | 57 | componentDidMount () { 58 | const { sysinfo } = this.inject.commonStore 59 | 60 | Object.assign(sysinfo, Taro.getSystemInfoSync()) 61 | this.BLEInformation.platform = sysinfo.platform 62 | 63 | console.log('系统信息:', sysinfo) 64 | } 65 | 66 | onPullDownRefresh () { 67 | this.handleStartSearch().then(() => { 68 | Taro.stopPullDownRefresh() 69 | }) 70 | } 71 | 72 | /** 73 | * 开始搜索蓝牙设备 74 | */ 75 | handleStartSearch = () => { 76 | // console.log('lichao', 'closeBluetoothAdapter') 77 | console.log('开始搜索') 78 | Taro.closeBluetoothAdapter() 79 | 80 | return Taro.openBluetoothAdapter() 81 | .then(() => 82 | // 初始化蓝牙模块 83 | Taro.getBluetoothAdapterState()) 84 | .then((res: any) => { 85 | console.log('本机蓝牙适配器状态:', res) 86 | // 获取本机蓝牙适配器状态成功 87 | if (res.available) { 88 | if (res.discovering) { 89 | Taro.stopBluetoothDevicesDiscovery({ 90 | success (res) { 91 | console.log('停止搜寻附近蓝牙:', res) 92 | } 93 | }) 94 | } 95 | this.checkPemission() 96 | } else { 97 | Taro.showModal({ 98 | title: '提示', 99 | content: '本机蓝牙不可用' 100 | }) 101 | } 102 | }) 103 | .catch(err => { 104 | console.log(err) 105 | Taro.showModal({ 106 | title: '提示', 107 | content: '蓝牙初始化失败,请打开蓝牙' 108 | }) 109 | }) 110 | } 111 | 112 | checkPemission = () => { 113 | const { sysinfo: { system, platform }} = this.inject.commonStore 114 | 115 | if (platform == 'ios') { 116 | this.getBluetoothDevices() 117 | } else if (platform == 'android') { 118 | // console.log(system.substring(system.length - (system.length - 8), system.length - (system.length - 8) + 1)) 119 | const systemVersion = /([0-9]+)/.exec(system) 120 | 121 | console.log('android系统版本:', systemVersion) 122 | if (systemVersion && parseInt(systemVersion[1]) > 5) { 123 | Taro.getSetting() 124 | .then(res => { 125 | console.log('当前用户设置:', res) 126 | if (!res.authSetting['scope.userLocation']) { 127 | // 获取位置授权 128 | return Taro.authorize({ scope: 'scope.userLocation' }) 129 | } else { 130 | return Promise.resolve({}) 131 | } 132 | }) 133 | .then(() => { 134 | this.getBluetoothDevices() 135 | }) 136 | .catch(err => { 137 | console.log('位置授权获取失败:', err) 138 | console.log('请在设置界面重新授权后搜索蓝牙') 139 | 140 | Taro.openSetting().then(res => { 141 | if (res.authSetting['scope.userLocation'] === true) { 142 | Taro.showToast({ title: '授权成功,请重新搜索' }) 143 | } 144 | }) 145 | }) 146 | } 147 | } 148 | } 149 | 150 | /** 151 | * 获取蓝牙设备信息 152 | */ 153 | getBluetoothDevices = () => { 154 | Taro.showLoading({ title: '正在加载' }) 155 | 156 | this.setState({ isScanning: true }) 157 | 158 | Taro.startBluetoothDevicesDiscovery().then(res => { 159 | console.log('获取附近的蓝牙外围设备:', res.errMsg) 160 | 161 | setTimeout(() => { 162 | // 3s内发现的设备 163 | Taro.getBluetoothDevices().then(res => { 164 | console.log('所有已发现的蓝牙设备:', res) 165 | const devices = res.devices.filter(item => item.name !== '未知设备') 166 | 167 | console.log('所有已知的蓝牙设备:', devices) 168 | this.setState({ 169 | deviceList: devices, 170 | isScanning: false 171 | }) 172 | Taro.hideLoading() 173 | Taro.stopPullDownRefresh() 174 | }) 175 | }, 3000) 176 | }) 177 | } 178 | 179 | /** 180 | * 处理连接蓝牙 181 | * @param device 蓝牙设备数据 182 | */ 183 | handleConnect (device: getBluetoothDevices.PromisedPropDevicesItem) { 184 | console.log('开始连接:', device) 185 | 186 | // 停止搜寻附近的蓝牙外围设备 187 | Taro.stopBluetoothDevicesDiscovery() 188 | Taro.showLoading({ title: '正在连接' }) 189 | // 连接低功耗蓝牙设备 190 | Taro.createBLEConnection({ deviceId: device.deviceId }).then( 191 | () => { 192 | console.log('连接成功:', device.deviceId) 193 | this.BLEInformation.deviceId = device.deviceId 194 | // 连接成功 获取服务 195 | // this.getSeviceId(device.deviceId) 196 | getBELSeviceId(this.BLEInformation.deviceId) 197 | .then(newBLEInformation => { 198 | Taro.hideLoading() 199 | // 保存本地下次,用于下次快速连接 200 | Taro.setStorageSync('BLEInformation', this.BLEInformation) 201 | Taro.navigateBack().then(() => Taro.showToast({ title: '连接成功' })) 202 | 203 | runInAction(() => { 204 | this.inject.commonStore.isBELconnect = true 205 | this.inject.commonStore.isBELservices = true 206 | this.inject.commonStore.BLEServices = newBLEInformation 207 | }) 208 | }) 209 | .catch(err => { 210 | console.log(err) 211 | 212 | typeof err === 'string' && 213 | Taro.showModal({ 214 | title: '提示', 215 | content: '找不到该蓝牙设备读写的特征值' 216 | }) 217 | }) 218 | }, 219 | err => { 220 | console.log('连接失败: ', err) 221 | Taro.showModal({ 222 | title: '提示', 223 | content: '连接失败' 224 | }) 225 | Taro.hideLoading() 226 | } 227 | ) 228 | } 229 | 230 | render () { 231 | const { deviceList, isScanning } = this.state 232 | 233 | return ( 234 | 235 | 236 | 开始搜索 237 | 238 | 239 | 240 | {deviceList.map(device => 241 | 242 | {device.name} 243 | {device.deviceId} 244 | )} 245 | 246 | 247 | ) 248 | } 249 | } 250 | 251 | export default BluetoothPage 252 | -------------------------------------------------------------------------------- /src/pages/dashborad/index.scss: -------------------------------------------------------------------------------- 1 | $color-brand: #ff6140; 2 | $color-brand-light: #ff8970; 3 | $color-brand-dark: #cc4e33; 4 | 5 | @import '~taro-ui/dist/style/components/tabs.scss'; 6 | 7 | .at-tabs { 8 | &__header { 9 | position: fixed; 10 | z-index: 1; 11 | 12 | } 13 | 14 | &-pane { 15 | padding-top: 86px; 16 | } 17 | } 18 | 19 | .dashboardPage { 20 | background-color: #f6f6f6; 21 | min-height: 100vh; 22 | } 23 | 24 | .listWrap { 25 | padding: 20px; 26 | 27 | .topInfo { 28 | font-size: 24px; 29 | font-weight: 400; 30 | color: rgba(102, 102, 102, 1); 31 | text-align: right; 32 | } 33 | 34 | .orderItem { 35 | background-color: #fff; 36 | margin-top: 20px; 37 | padding: 20px 30px; 38 | } 39 | 40 | .title { 41 | font-size: 32px; 42 | font-weight: 500; 43 | color: rgba(51, 51, 51, 1); 44 | line-height: 24px; 45 | } 46 | 47 | .info { 48 | margin-top: 45px; 49 | display: flex; 50 | align-items: center; 51 | justify-content: space-between; 52 | 53 | .infoText { 54 | font-size: 26px; 55 | font-weight: 400; 56 | color: rgba(153, 153, 153, 1); 57 | line-height: 24px; 58 | } 59 | 60 | .infoNum { 61 | font-size: 48px; 62 | font-weight: 500; 63 | color: rgba(51, 51, 51, 1); 64 | line-height: 24px; 65 | } 66 | } 67 | 68 | .price { 69 | margin-top: 40px; 70 | display: flex; 71 | align-items: center; 72 | justify-content: space-between; 73 | 74 | .left { 75 | font-size: 32px; 76 | font-weight: 500; 77 | color: rgba(255, 44, 0, 1); 78 | line-height: 24px; 79 | } 80 | 81 | .right { 82 | font-size: 32px; 83 | font-weight: 500; 84 | color: rgba(51, 51, 51, 1); 85 | line-height: 24px; 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /src/pages/dashborad/index.tsx: -------------------------------------------------------------------------------- 1 | import Taro, { Component, Config } from '@tarojs/taro' 2 | import transformPrice from '@/utils/transformPrice' 3 | import wrapUserAuth from '@/components/HOC/wrapUserAuth' 4 | import { AtTabs, AtTabsPane } from 'taro-ui' 5 | import { IDashboardStore } from '@/stores/dashboradStore' 6 | import { inject, observer } from '@tarojs/mobx' 7 | import { ISalesData, ISalesType } from '@/interfaces/dashborad' 8 | import { View } from '@tarojs/components' 9 | import './index.scss' 10 | 11 | interface InjectStoreProps { 12 | dashboradStore: IDashboardStore 13 | } 14 | 15 | interface IState { 16 | currentTab: number 17 | } 18 | 19 | /** 20 | * 数据统计页 21 | */ 22 | @wrapUserAuth 23 | @inject('dashboradStore') 24 | @observer 25 | class DashboardPage extends Component<{}, IState> { 26 | get inject () { 27 | // 兼容注入store 28 | return this.props as InjectStoreProps 29 | } 30 | 31 | readonly state: IState = { currentTab: 0 } 32 | 33 | private tabList: { title: string; tabId: ISalesType }[] = [ 34 | { title: '今天', tabId: 1 }, 35 | { title: '昨天', tabId: 2 }, 36 | { title: '前天', tabId: 3 }, 37 | { title: '近七天', tabId: 4 } 38 | ] 39 | 40 | /** 41 | * 指定config的类型声明为: Taro.Config 42 | * 43 | * 由于 typescript 对于 object 类型推导只能推出 Key 的基本类型 44 | * 对于像 navigationBarTextStyle: 'black' 这样的推导出的类型是 string 45 | * 提示和声明 navigationBarTextStyle: 'black' | 'white' 类型冲突, 需要显示声明类型 46 | */ 47 | config: Config = { navigationBarTitleText: '统计' } 48 | 49 | componentDidMount () { 50 | // 请求初始数据 51 | this.inject.dashboradStore.fetchSalesData(1) 52 | } 53 | 54 | /** 55 | * 监听用户上拉触底事件 56 | * 触底加载新数据 57 | */ 58 | onReachBottom () { 59 | const { dashboradStore } = this.inject 60 | const { tabId } = this.tabList[this.state.currentTab] 61 | const tabData = dashboradStore.salesTabsData.get(tabId) 62 | const page = tabData && tabData.page || 1 63 | 64 | if (!dashboradStore.salesTabsListIsEnd) { 65 | // 加载下一页内容 66 | dashboradStore.fetchSalesData(tabId, page + 1) 67 | } 68 | } 69 | 70 | handleChangeTab (value) { 71 | const { tabId } = this.tabList[value] 72 | 73 | this.setState({ currentTab: value }) 74 | 75 | this.inject.dashboradStore.fetchSalesData(tabId) 76 | } 77 | 78 | render () { 79 | const { salesTabsData } = this.inject.dashboradStore 80 | 81 | return ( 82 | 83 | 84 | {this.tabList.map((item, index) => { 85 | const tabData = salesTabsData.get(item.tabId) || {} 86 | const { total_num, total_money, lists } = tabData as ISalesData 87 | 88 | return ( 89 | 90 | 91 | 92 | 共销售{total_num}杯,收入¥{transformPrice(total_money)} 93 | 94 | 95 | {lists && 96 | lists.map((order, index) => { 97 | const { title, standard_text } = order 98 | const orderTotalNum = order.total_num 99 | const orderTotalPrice = transformPrice(order.total_money) 100 | const price = transformPrice(order.price) 101 | 102 | return ( 103 | 104 | {title} 105 | 106 | {standard_text} 107 | X{orderTotalNum} 108 | 109 | 110 | ¥{price} 111 | ¥{orderTotalPrice} 112 | 113 | 114 | ) 115 | })} 116 | 117 | 118 | 119 | ) 120 | })} 121 | 122 | 123 | ) 124 | } 125 | } 126 | 127 | export default DashboardPage 128 | -------------------------------------------------------------------------------- /src/pages/index/index.scss: -------------------------------------------------------------------------------- 1 | $color-brand: #ff6140; 2 | $color-brand-light: #ff8970; 3 | $color-brand-dark: #cc4e33; 4 | 5 | .index { 6 | &__page { 7 | width: auto; 8 | padding: 70px 10px 0; 9 | background-color: #fff; 10 | background-image: url(https://www.leidenglai.com/image/weapp/cdn/bg-index.png); 11 | background-size: 100% auto; 12 | background-position: center; 13 | min-height: 100vh; 14 | } 15 | 16 | &__item-card { 17 | width: 50%; 18 | box-sizing: border-box; 19 | padding: 12px 20px; 20 | } 21 | 22 | &__nav-wrap { 23 | display: flex; 24 | flex-flow: wrap; 25 | } 26 | 27 | &__card-box { 28 | width: 100%; 29 | height: 210px; 30 | background-color: #fff; 31 | box-shadow: 0 8px 26px 3px rgba(0, 0, 0, 0.25); 32 | border-radius: 20px; 33 | text-align: center; 34 | padding-top: 32px; 35 | box-sizing: border-box; 36 | 37 | .title { 38 | color: #333; 39 | font-size: 26px; 40 | padding-top: 10px; 41 | } 42 | 43 | .icon { 44 | display: block; 45 | width: 90px; 46 | height: 90px; 47 | margin: 0 auto; 48 | background-position: center; 49 | background-repeat: no-repeat; 50 | background-size: 90px 90px; 51 | } 52 | 53 | .iconNavProduct { 54 | background-size: 88px 90px; 55 | background-image: url(../../assets/images/icon/icon-nav-1.png); 56 | } 57 | 58 | .iconNavOrder { 59 | background-image: url(../../assets/images/icon/icon-nav-2.png); 60 | } 61 | 62 | .iconNavOrderHis { 63 | background-size: 92px 90px; 64 | background-image: url(../../assets/images/icon/icon-nav-3.png); 65 | } 66 | 67 | .iconNavDashboard { 68 | background-image: url(../../assets/images/icon/icon-nav-4.png); 69 | } 70 | 71 | .iconNavBlurt { 72 | background-size: 84px 84px; 73 | background-image: url(../../assets/images/icon/icon-nav-5.png); 74 | } 75 | 76 | .iconNavLogout { 77 | background-size: 80px 83px; 78 | background-image: url(../../assets/images/icon/icon-nav-6.png); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/pages/index/index.tsx: -------------------------------------------------------------------------------- 1 | import Taro, { Component, Config } from '@tarojs/taro' 2 | import wrapUserAuth from '@/components/HOC/wrapUserAuth' 3 | import { Navigator, Text, View } from '@tarojs/components' 4 | import './index.scss' 5 | 6 | @wrapUserAuth 7 | class IndexPage extends Component { 8 | /** 9 | * 指定config的类型声明为: Taro.Config 10 | * 11 | * 由于 typescript 对于 object 类型推导只能推出 Key 的基本类型 12 | * 对于像 navigationBarTextStyle: 'black' 这样的推导出的类型是 string 13 | * 提示和声明 navigationBarTextStyle: 'black' | 'white' 类型冲突, 需要显示声明类型 14 | */ 15 | config: Config = { navigationBarTitleText: '首页' } 16 | 17 | /** 18 | * 退出登录 19 | */ 20 | handleLogout = () => { 21 | // 清除token 22 | Taro.removeStorageSync('access_token') 23 | 24 | // 跳转登录页 25 | Taro.reLaunch({ url: '/pages/account/login/index' }) 26 | } 27 | 28 | render () { 29 | return ( 30 | 31 | 32 | 33 | 34 | 35 | 我的商品 36 | 37 | 38 | 39 | 40 | 41 | 接单 42 | 43 | 44 | 45 | 46 | 47 | 历史订单 48 | 49 | 50 | 51 | 52 | 53 | 统计 54 | 55 | 56 | 57 | 58 | 59 | 蓝牙连接 60 | 61 | 62 | 63 | 64 | 65 | 退出 66 | 67 | 68 | 69 | 70 | ) 71 | } 72 | } 73 | 74 | export default IndexPage 75 | -------------------------------------------------------------------------------- /src/pages/order/index.scss: -------------------------------------------------------------------------------- 1 | $color-brand: #ff6140; 2 | $color-brand-light: #ff8970; 3 | $color-brand-dark: #cc4e33; 4 | $color-border-base: #ccc; 5 | 6 | @import '~taro-ui/dist/style/components/button.scss'; 7 | 8 | .orderPage { 9 | padding-top: 20px; 10 | background-color: #f6f6f6; 11 | min-height: 100vh; 12 | } 13 | 14 | .orderCard { 15 | padding: 30px 30px 16px; 16 | background-color: #fff; 17 | margin: 0 20px 20px; 18 | border-radius: 10px; 19 | 20 | .cardTop { 21 | // prettier-ignore 22 | border-bottom: 1Px solid #ccc; 23 | display: flex; 24 | justify-content: space-between; 25 | padding-bottom: 20px; 26 | 27 | .orderItemNo { 28 | font-size: 32px; 29 | font-weight: 500; 30 | color: rgba(51, 51, 51, 1); 31 | line-height: 24px; 32 | } 33 | 34 | .orderDate { 35 | font-size: 28px; 36 | font-weight: 400; 37 | color: rgba(153, 153, 153, 1); 38 | line-height: 24px; 39 | } 40 | 41 | .orderPhone { 42 | font-size: 28px; 43 | font-weight: 400; 44 | color: #349dff; 45 | line-height: 24px; 46 | } 47 | } 48 | 49 | .cardMain { 50 | padding: 0 0 30px; 51 | // prettier-ignore 52 | border-bottom: 1Px solid #ccc; 53 | 54 | .orderProductItem { 55 | font-size: 26px; 56 | font-weight: 400; 57 | color: rgba(153, 153, 153, 1); 58 | line-height: 24px; 59 | margin-top: 30px; 60 | display: flex; 61 | 62 | .name { 63 | flex: 4; 64 | } 65 | 66 | .num { 67 | flex: 1; 68 | } 69 | 70 | .mark { 71 | flex: 2; 72 | text-align: right; 73 | } 74 | } 75 | } 76 | 77 | .cardFooter { 78 | margin-top: 20px; 79 | display: flex; 80 | align-items: center; 81 | justify-content: space-between; 82 | 83 | .orderPrice { 84 | font-size: 32px; 85 | font-weight: 500; 86 | color: rgba(255, 44, 0, 1); 87 | line-height: 24px; 88 | } 89 | 90 | .button { 91 | margin-left: 20px; 92 | display: inline-block; 93 | 94 | &.green.at-button { 95 | background-color: #67bc16; 96 | border-color: #67bc16; 97 | color: #fff; 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/pages/order/index.tsx: -------------------------------------------------------------------------------- 1 | import getBELSeviceId from '@/utils/getBELSeviceId' 2 | import shopService from '@/services/shopService' 3 | import Taro, { Component, Config } from '@tarojs/taro' 4 | import transformPrice from '@/utils/transformPrice' 5 | import wrapUserAuth from '@/components/HOC/wrapUserAuth' 6 | import { AtButton } from 'taro-ui' 7 | import { IBLEInformation } from '@/interfaces/common' 8 | import { ICommonStore } from '@/stores/commonStore' 9 | import { inject, observer } from '@tarojs/mobx' 10 | import { IOrderDetail } from '@/interfaces/order' 11 | import { IOrderStore } from '@/stores/orderStore' 12 | import { runInAction } from 'mobx' 13 | import { Text, View } from '@tarojs/components' 14 | import './index.scss' 15 | 16 | // ArrayBuffer转16进制字符串示例 17 | function ab2hex (buffer: ArrayBuffer): string { 18 | const hexArr = Array.prototype.map.call(new Uint8Array(buffer), bit => ('00' + bit.toString(16)).slice(-2)) 19 | 20 | return hexArr.join('') 21 | } 22 | 23 | /** 24 | * 当前页面路由参数 25 | */ 26 | export interface IOrderRoute { 27 | order_type?: 'history' 28 | } 29 | 30 | interface IState { 31 | isLabelSend: boolean 32 | } 33 | 34 | interface InjectStoreProps { 35 | orderStore: IOrderStore 36 | commonStore: ICommonStore 37 | } 38 | 39 | /** 40 | * 待确认订单列表页 41 | */ 42 | @wrapUserAuth 43 | @inject('orderStore', 'commonStore') 44 | @observer 45 | class OrderPage extends Component<{}, IState> { 46 | get inject () { 47 | // 兼容注入store 48 | return this.props as InjectStoreProps 49 | } 50 | 51 | /** 当前页面状态 默认 current, current:接单页面 history: 历史订单*/ 52 | private pageType: 'current' | 'history' = 'current' 53 | 54 | private printerData = { 55 | buffSize: [] as number[], 56 | oneTimeData: 0, 57 | printNum: [] as number[], 58 | printerNum: 0, 59 | looptime: 0, 60 | lastData: 0, 61 | currentTime: 0, 62 | currentPrint: 1 63 | } 64 | 65 | readonly state: IState 66 | 67 | private BLEInformation: IBLEInformation 68 | 69 | constructor (props) { 70 | super(props) 71 | 72 | this.state = { isLabelSend: false } 73 | 74 | const list: number[] = [] 75 | const numList: number[] = [] 76 | let j = 0 77 | 78 | for (var i = 20; i < 200; i += 10) { 79 | list[j] = i 80 | j++ 81 | } 82 | for (var i = 1; i < 10; i++) { 83 | numList[i - 1] = i 84 | } 85 | 86 | this.printerData.buffSize = list 87 | this.printerData.oneTimeData = list[0] 88 | this.printerData.printNum = numList 89 | this.printerData.printerNum = numList[0] 90 | } 91 | 92 | /** 93 | * 指定config的类型声明为: Taro.Config 94 | * 95 | * 由于 typescript 对于 object 类型推导只能推出 Key 的基本类型 96 | * 对于像 navigationBarTextStyle: 'black' 这样的推导出的类型是 string 97 | * 提示和声明 navigationBarTextStyle: 'black' | 'white' 类型冲突, 需要显示声明类型 98 | */ 99 | config: Config = { 100 | navigationBarTitleText: '订单', 101 | enablePullDownRefresh: true 102 | } 103 | 104 | componentWillMount () { 105 | this.pageType = this.$router.params.order_type || 'current' 106 | this.BLEInformation = Taro.getStorageSync('BLEInformation') 107 | 108 | if (this.pageType === 'current') { 109 | Taro.setNavigationBarTitle({ title: '接单' }) 110 | } else { 111 | Taro.setNavigationBarTitle({ title: '历史订单' }) 112 | } 113 | 114 | this.checkConnectionBLE().then( 115 | () => { 116 | console.log('connectionBLE init') 117 | }, 118 | () => { 119 | console.log('尝试直接初始连接失败') 120 | } 121 | ) 122 | 123 | Taro.onBLEConnectionStateChange(res => { 124 | // 该方法回调中可以用于处理连接意外断开等异常情况 125 | console.log(`device ${res.deviceId} state has changed, connected: ${res.connected}`) 126 | 127 | if (res.deviceId === (this.BLEInformation && this.BLEInformation.deviceId)) { 128 | runInAction(() => { 129 | // 需要重新连接和获取服务 130 | this.inject.commonStore.isBELconnect = false 131 | this.inject.commonStore.isBELservices = false 132 | }) 133 | } 134 | }) 135 | } 136 | 137 | componentDidMount () { 138 | const type = this.pageType === 'current' ? 1 : 2 139 | 140 | this.inject.orderStore.fetchOrderData({ type }) 141 | } 142 | 143 | componentDidShow () { 144 | this.BLEInformation = Taro.getStorageSync('BLEInformation') 145 | } 146 | 147 | /** 148 | * 下拉刷新处理 149 | */ 150 | onPullDownRefresh () { 151 | const type = this.pageType === 'current' ? 1 : 2 152 | 153 | this.inject.orderStore.fetchOrderData({ type }).then(() => { 154 | Taro.stopPullDownRefresh() 155 | }) 156 | } 157 | 158 | /** 159 | * 监听用户上拉触底事件 160 | * 触底加载新数据 161 | */ 162 | onReachBottom () { 163 | const { orderStore: { orderListData, listIsEnd, hisListIsEnd, hisOrderListData }} = this.inject 164 | 165 | // 区分历史订单和接单页面 166 | const type = this.pageType === 'current' ? 1 : 2 167 | const isEnd = this.pageType === 'current' ? listIsEnd : hisListIsEnd 168 | const page = this.pageType === 'current' ? orderListData.page : hisOrderListData.page 169 | 170 | if (!isEnd) { 171 | // 加载下一页内容 172 | this.inject.orderStore.fetchOrderData({ type, page: page + 1 }) 173 | } 174 | } 175 | 176 | /** 177 | * 检查蓝牙连接以及服务 178 | * 尝试连接低功耗蓝牙 启用notify功能,订阅特征值 179 | */ 180 | async checkConnectionBLE () { 181 | const { commonStore } = this.inject 182 | 183 | if (this.BLEInformation && this.BLEInformation.deviceId) { 184 | if (!commonStore.isBELconnect) { 185 | try { 186 | // 如果之前已连接过,直接尝试连接设备 187 | await Taro.createBLEConnection({ deviceId: this.BLEInformation.deviceId }) 188 | 189 | console.log('连接成功:', this.BLEInformation.deviceId) 190 | } catch (err) { 191 | if (err.errCode === -1) { 192 | // 安卓如果已连接会报错 -1, 所以也不catch -1 193 | } else { 194 | console.log('低功耗蓝牙设备连接错误:', err) 195 | console.log('错误码请查看官方文档:https://developers.weixin.qq.com/miniprogram/dev/api/wx.createBLEConnection.html') 196 | 197 | return Promise.reject(err) 198 | } 199 | } 200 | 201 | // 状态修改为已连接 202 | runInAction(() => { 203 | commonStore.isBELconnect = true 204 | }) 205 | } 206 | 207 | if (!commonStore.isBELservices) { 208 | try { 209 | // 获取服务 210 | const newBLEInformation = await getBELSeviceId(this.BLEInformation.deviceId) 211 | 212 | // 启用低功耗蓝牙notify 功能,订阅特征值 213 | await Taro.notifyBLECharacteristicValueChange({ 214 | deviceId: this.BLEInformation.deviceId, 215 | serviceId: newBLEInformation.notifyServiceId, 216 | characteristicId: newBLEInformation.notifyCharaterId, 217 | state: true 218 | }) 219 | 220 | console.log('启用低功耗蓝牙notify 功能,订阅特征值') 221 | Taro.onBLECharacteristicValueChange(r => { 222 | console.log(`characteristic ${r.characteristicId} has changed, now is ${r}`) 223 | console.log(ab2hex(r.value)) 224 | }) 225 | 226 | runInAction(() => { 227 | commonStore.isBELservices = true 228 | commonStore.BLEServices = newBLEInformation 229 | }) 230 | } catch (err) { 231 | if (err.errCode === 10006) { 232 | // 当前连接已断开 233 | runInAction(() => { 234 | this.inject.commonStore.isBELconnect = false 235 | this.inject.commonStore.isBELservices = false 236 | }) 237 | } 238 | 239 | if (err.errCode) { 240 | console.log('获取服务,启用低功耗蓝牙notify功能,订阅特征值错误:', err) 241 | } else { 242 | console.log('连接失败:', err) 243 | } 244 | 245 | return Promise.reject(err) 246 | } 247 | } 248 | } else { 249 | console.log('未初始化蓝牙适配器') 250 | 251 | return Promise.reject({ errCode: 10000, errMsg: '未初始化蓝牙适配器' }) 252 | } 253 | } 254 | 255 | /** 256 | * 请求打印log 接口请求后再打印 257 | */ 258 | async handlePrintMsg (orderData: IOrderDetail) { 259 | try { 260 | await this.checkConnectionBLE() 261 | await shopService.fetchPrintOrderCb({ order_number: orderData.order_number }) 262 | 263 | this.handlePrintOrder(orderData) 264 | } catch (err) { 265 | if (err && err.errCode === 10000) { 266 | Taro.navigateTo({ url: '/pages/bluetooth/index' }).then(() => { 267 | Taro.showToast({ title: '请连接打印机', icon: 'none' }) 268 | }) 269 | } 270 | 271 | return console.log(err) 272 | } 273 | } 274 | 275 | /** 276 | * 创建打印消息 277 | */ 278 | async handlePrintOrder (orderData: IOrderDetail) { 279 | try { 280 | await this.checkConnectionBLE() 281 | } catch (err) { 282 | if (err && err.errCode === 10000) { 283 | Taro.navigateTo({ url: '/pages/bluetooth/index' }).then(() => { 284 | Taro.showToast({ title: '请连接打印机', icon: 'none' }) 285 | }) 286 | } 287 | 288 | return console.log(err) 289 | } 290 | 291 | // 调用打印标记 292 | console.log('打印订单,order_number:', orderData.order_number) 293 | 294 | this.setState({ isLabelSend: true }) 295 | this.prepareSend() 296 | } 297 | 298 | /** 299 | * 调用打印机 300 | */ 301 | prepareSend = () => { 302 | // TODO 303 | } 304 | 305 | /** 306 | * 取消订单 307 | */ 308 | handleCancelOrder (orderId: string) { 309 | Taro.showModal({ 310 | title: '取消订单', 311 | content: '你确定要取消本订单吗?', 312 | success: res => { 313 | if (res.confirm) { 314 | console.log('用户点击确定取消订单') 315 | this.inject.orderStore.cancelOrder(orderId) 316 | } else if (res.cancel) { 317 | console.log('用户点击取消') 318 | } 319 | } 320 | }) 321 | } 322 | 323 | /** 324 | * 开始制作 325 | */ 326 | handleMaking (orderId: string) { 327 | console.log(orderId) 328 | this.inject.orderStore.confirmOrder(orderId) 329 | } 330 | 331 | /** 332 | * 拨打电话 333 | */ 334 | handkeCallPhone (phoneNumber: string) { 335 | Taro.makePhoneCall({ phoneNumber }) 336 | } 337 | 338 | render () { 339 | const { orderStore: { orderData, orderListData, hisOrderListData }} = this.inject 340 | const listData = this.pageType === 'current' ? orderListData : hisOrderListData 341 | 342 | if (listData.ids.length <= 0) { 343 | return false 344 | } 345 | 346 | return ( 347 | 348 | {listData.ids.map(id => { 349 | const data = (orderData.get(id) || {}) as IOrderDetail 350 | const { order_number, sum_money, insert_date, buy_phone, take_number, product_lists, events } = data 351 | 352 | return ( 353 | 354 | 355 | {take_number}号 356 | {insert_date} 357 | 358 | {buy_phone} 359 | 360 | 361 | 362 | 363 | {product_lists.map((item, index) => 364 | 365 | {item.title} 366 | x{item.num} 367 | {item.standard_text} 368 | )} 369 | 370 | 371 | 372 | 373 | ¥{transformPrice(sum_money)} 374 | 375 | 376 | {!!~events.indexOf('reprint') && 377 | 378 | 再次打印 379 | 380 | } 381 | {!!~events.indexOf('print') && 382 | 383 | 打印 384 | 385 | } 386 | {!!~events.indexOf('cancel_payment_order') && 387 | 388 | 取消 389 | 390 | } 391 | {!!~events.indexOf('receipt_order') && 392 | 393 | 开始制作 394 | 395 | } 396 | 397 | 398 | 399 | ) 400 | })} 401 | 402 | ) 403 | } 404 | } 405 | 406 | export default OrderPage 407 | -------------------------------------------------------------------------------- /src/pages/product/detail/index.scss: -------------------------------------------------------------------------------- 1 | $color-brand: #ff6140; 2 | $color-brand-light: #ff8970; 3 | $color-brand-dark: #cc4e33; 4 | $color-border-base: #ccc; 5 | 6 | @import '~taro-ui/dist/style/components/input.scss'; 7 | 8 | .at-input { 9 | background-color: #fff; 10 | margin-left: 0; 11 | margin-bottom: 0; 12 | padding: 0 0; 13 | font-size: 28px; 14 | 15 | &__title { 16 | font-size: 28px; 17 | width: 80px; 18 | } 19 | 20 | &::after { 21 | border: none; 22 | } 23 | 24 | & input { 25 | font-size: 28px; 26 | width: 124px; 27 | text-align: center; 28 | color: $color-brand; 29 | // prettier-ignore 30 | border: 1Px solid rgba(153, 153, 153, 1); 31 | border-radius: 10px; 32 | padding-left: 18px; 33 | } 34 | } 35 | 36 | .index { 37 | width: auto; 38 | } 39 | 40 | .productPage { 41 | background-color: #f6f6f6; 42 | } 43 | 44 | .productSwiper { 45 | width: 750px; 46 | height: 750px; 47 | 48 | image { 49 | width: 750px; 50 | height: 750px; 51 | } 52 | } 53 | 54 | .productInfoWrap { 55 | padding: 30px 20px 20px; 56 | background-color: #fff; 57 | 58 | .infoName { 59 | font-size: 28px; 60 | font-weight: 500; 61 | color: rgba(51, 51, 51, 1); 62 | } 63 | 64 | .infoDesc { 65 | font-size: 22px; 66 | font-weight: 400; 67 | color: rgba(102, 102, 102, 1); 68 | margin-top: 20px; 69 | } 70 | 71 | .infoComment { 72 | font-size: 28px; 73 | font-weight: 600; 74 | color: rgba(51, 51, 51, 1); 75 | margin-top: 30px; 76 | } 77 | } 78 | 79 | .stockWrap { 80 | background-color: #fff; 81 | margin-top: 20px; 82 | 83 | .skuItem { 84 | display: flex; 85 | padding: 10px 20px; 86 | height: 80px; 87 | align-items: center; 88 | } 89 | 90 | .name { 91 | font-size: 28px; 92 | font-weight: 500; 93 | color: rgba(51, 51, 51, 1); 94 | min-width: 140px; 95 | } 96 | 97 | .stock { 98 | font-size: 28px; 99 | color: rgba(51, 51, 51, 1); 100 | } 101 | 102 | .stockNum { 103 | font-size: 28px; 104 | color: rgba(255, 44, 0, 1); 105 | text-align: left; 106 | margin-left: 30px; 107 | } 108 | } 109 | 110 | .productDetailWrap { 111 | .text { 112 | padding: 20px 0; 113 | text-align: center; 114 | font-size: 22px; 115 | font-weight: 400; 116 | color: rgba(153, 153, 153, 1); 117 | } 118 | 119 | .content { 120 | min-height: 144px; 121 | background-color: #fff; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/pages/product/detail/index.tsx: -------------------------------------------------------------------------------- 1 | import Taro, { Component, Config } from '@tarojs/taro' 2 | import wrapUserAuth from '@/components/HOC/wrapUserAuth' 3 | import { AtInput } from 'taro-ui' 4 | import { Image, RichText, Swiper, SwiperItem, View } from '@tarojs/components' 5 | import { inject, observer } from '@tarojs/mobx' 6 | import { IProductDetail } from '@/interfaces/product' 7 | import { IProductStore } from '@/stores/productStore' 8 | import './index.scss' 9 | 10 | interface InjectStoreProps { 11 | productStore: IProductStore 12 | } 13 | 14 | interface IState { 15 | stockData: { [sku: string]: number } 16 | } 17 | 18 | /** 19 | * 商品详情页 20 | */ 21 | @wrapUserAuth 22 | @inject('productStore') 23 | @observer 24 | class ProductDetailPage extends Component<{}, IState> { 25 | get inject () { 26 | // 兼容注入store 27 | return this.props as InjectStoreProps 28 | } 29 | 30 | productId: number 31 | 32 | readonly state: IState = { stockData: {}} 33 | 34 | /** 35 | * 指定config的类型声明为: Taro.Config 36 | * 37 | * 由于 typescript 对于 object 类型推导只能推出 Key 的基本类型 38 | * 对于像 navigationBarTextStyle: 'black' 这样的推导出的类型是 string 39 | * 提示和声明 navigationBarTextStyle: 'black' | 'white' 类型冲突, 需要显示声明类型 40 | */ 41 | config: Config = { navigationBarTitleText: '商品详情' } 42 | 43 | componentDidMount () { 44 | this.productId = this.$router.params.product_id 45 | 46 | if (!this.inject.productStore.productData.get(this.productId)) { 47 | this.inject.productStore.fetchProductDetail(this.productId) 48 | } 49 | } 50 | 51 | /** 52 | * 修改库存 53 | */ 54 | handleStockSubmit (sku: string, oldStock, value) { 55 | if (oldStock === value) { 56 | return false 57 | } 58 | 59 | this.inject.productStore.setProductStock(this.productId, sku, value).then(() => { 60 | Taro.showToast({ title: '库存修改成功', icon: 'none' }) 61 | }) 62 | } 63 | 64 | render () { 65 | const detail = this.inject.productStore.productDatailData 66 | 67 | if (detail.product_id === 0) { 68 | return null 69 | } 70 | 71 | const { images, thumb, title, sub_title, shop_comment, content, stock_lists } = detail as IProductDetail 72 | 73 | return ( 74 | 75 | 76 | 77 | 78 | {images ? 79 | images.map((img, index) => 80 | 81 | 82 | 83 | 84 | ) 85 | : 86 | 87 | 88 | 89 | 90 | 91 | } 92 | 93 | 94 | 95 | {title} 96 | {sub_title} 97 | {shop_comment} 98 | 99 | 100 | {stock_lists.map(item => 101 | 102 | {item.name} 103 | 104 | val} 111 | onConfirm={this.handleStockSubmit.bind(this, item.product_sku, item.number)} 112 | onBlur={this.handleStockSubmit.bind(this, item.product_sku, item.number)} 113 | /> 114 | 115 | )} 116 | 117 | 118 | —— 详情 —— 119 | 120 | 121 | 122 | 123 | 124 | 125 | ) 126 | } 127 | } 128 | 129 | export default ProductDetailPage 130 | -------------------------------------------------------------------------------- /src/pages/product/index.scss: -------------------------------------------------------------------------------- 1 | $color-brand: #ff6140; 2 | $color-brand-light: #ff8970; 3 | $color-brand-dark: #cc4e33; 4 | 5 | @import '~taro-ui/dist/style/components/button.scss'; 6 | 7 | $component: '.app-product'; 8 | 9 | .at-button { 10 | height: 54px; 11 | line-height: 54px; 12 | padding: 0 30px; 13 | margin: 0; 14 | font-size: 26px; 15 | } 16 | 17 | #{$component}__page { 18 | background-color: #f5f5f5; 19 | min-height: 100vh; 20 | 21 | #{$component} { 22 | &__card { 23 | display: flex; 24 | padding: 26px 20px; 25 | background-color: #fff; 26 | 27 | &:not(:last-child) { 28 | border-bottom: 1px solid #e6e6e6; 29 | } 30 | } 31 | 32 | &__img-wrap { 33 | border-radius: 10px; 34 | overflow: hidden; 35 | width: 180px; 36 | height: 180px; 37 | margin-right: 20px; 38 | 39 | image { 40 | width: 100%; 41 | height: 100%; 42 | } 43 | } 44 | 45 | &__cont-wrap { 46 | flex: 1; 47 | } 48 | 49 | &__info-title { 50 | font-size: 26px; 51 | font-weight: 500; 52 | color: rgba(51, 51, 51, 1); 53 | } 54 | 55 | &__info-text { 56 | font-size: 22px; 57 | font-weight: 400; 58 | color: rgba(102, 102, 102, 1); 59 | margin-top: 10px; 60 | } 61 | 62 | &__bottom { 63 | display: flex; 64 | align-items: center; 65 | justify-content: space-between; 66 | margin-top: 60px; 67 | 68 | .price { 69 | font-size: 30px; 70 | font-weight: 600; 71 | color: rgba(255, 0, 0, 1); 72 | } 73 | 74 | .vip-price { 75 | display: flex; 76 | font-size: 24px; 77 | color: #ffae00; 78 | font-weight: 300; 79 | margin-left: 4px; 80 | align-items: center; 81 | 82 | .iconVip { 83 | display: inline-block; 84 | margin-left: 8px; 85 | width: 50px; 86 | height: 20px; 87 | background-size: 100%; 88 | background-image: url('../../assets/images/icon/icon-vip.png'); 89 | } 90 | } 91 | } 92 | 93 | &__bottom-left { 94 | flex: 1; 95 | display: flex; 96 | color: #ff2c00; 97 | align-items: flex-end; 98 | } 99 | 100 | &__action-wrap { 101 | width: 150px; 102 | display: flex; 103 | align-items: center; 104 | justify-content: flex-end; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/pages/product/index.tsx: -------------------------------------------------------------------------------- 1 | import Taro, { Component, Config } from '@tarojs/taro' 2 | import transformPrice from '@/utils/transformPrice' 3 | import wrapUserAuth from '@/components/HOC/wrapUserAuth' 4 | import { AtButton } from 'taro-ui' 5 | import { ILiProductInfo } from '@/interfaces/product' 6 | import { Image, Text, View } from '@tarojs/components' 7 | import { inject, observer } from '@tarojs/mobx' 8 | import { IProductStore } from '@/stores/productStore' 9 | import './index.scss' 10 | 11 | interface InjectStoreProps { 12 | productStore: IProductStore 13 | } 14 | 15 | /** 16 | * 商品列表页 17 | */ 18 | @wrapUserAuth 19 | @inject('productStore') 20 | @observer 21 | class ProductPage extends Component { 22 | get inject () { 23 | // 兼容注入store 24 | return this.props as InjectStoreProps 25 | } 26 | 27 | /** 28 | * 指定config的类型声明为: Taro.Config 29 | * 30 | * 由于 typescript 对于 object 类型推导只能推出 Key 的基本类型 31 | * 对于像 navigationBarTextStyle: 'black' 这样的推导出的类型是 string 32 | * 提示和声明 navigationBarTextStyle: 'black' | 'white' 类型冲突, 需要显示声明类型 33 | */ 34 | config: Config = { 35 | navigationBarTitleText: '我的商品', 36 | enablePullDownRefresh: true 37 | } 38 | 39 | /** 模块className前缀 */ 40 | private prefixCls: string = 'app-product' 41 | 42 | componentWillMount () { 43 | // 检验登录 44 | } 45 | 46 | componentDidMount () { 47 | this.inject.productStore.fetchProductData() 48 | } 49 | 50 | /** 51 | * 下拉刷新处理 52 | */ 53 | onPullDownRefresh () { 54 | this.inject.productStore.fetchProductData().then(() => { 55 | Taro.stopPullDownRefresh() 56 | }) 57 | } 58 | 59 | /** 60 | * 监听用户上拉触底事件 61 | * 触底加载新数据 62 | */ 63 | onReachBottom () { 64 | const { productStore: { listIsEnd, productListData, fetchProductData }} = this.inject 65 | 66 | if (!listIsEnd) { 67 | // 加载下一页内容 68 | fetchProductData({ page: productListData.page + 1 }) 69 | } 70 | } 71 | 72 | /** 73 | * 跳转详情页 74 | */ 75 | handleNavToDetail (productId: number) { 76 | // 预加载数据 77 | this.inject.productStore.fetchProductDetail(productId) 78 | 79 | Taro.navigateTo({ url: `/pages/product/detail/index?product_id=${productId}` }) 80 | } 81 | 82 | /** 83 | * 设置商品上下架 84 | */ 85 | handleProductStatus (productId: number, type: 1 | 2) { 86 | this.inject.productStore.setProductStatus(productId, type) 87 | } 88 | 89 | render () { 90 | const { productStore: { productData, productListData }} = this.inject 91 | 92 | if (productListData.ids.length <= 0) { 93 | return false 94 | } 95 | 96 | return ( 97 | 98 | {productListData.ids.map(id => { 99 | const data = (productData.get(id) || {}) as ILiProductInfo 100 | const { thumb, title, sub_title, price, vip_price, status } = data 101 | 102 | return ( 103 | 104 | 105 | 106 | 107 | 108 | 109 | {title} 110 | {sub_title} 111 | 112 | 113 | 114 | ¥{transformPrice(price, false)} 115 | {vip_price > 0 ? `¥${transformPrice(vip_price, false)}` : ''} 116 | {vip_price > 0 && } 117 | 118 | 119 | {status === 1 ? 120 | 121 | 下架 122 | 123 | : 124 | 125 | 上架 126 | 127 | } 128 | 129 | 130 | 131 | 132 | ) 133 | })} 134 | 135 | ) 136 | } 137 | } 138 | 139 | export default ProductPage 140 | -------------------------------------------------------------------------------- /src/services/shopService.ts: -------------------------------------------------------------------------------- 1 | import requestData from "@/utils/requestData"; 2 | import { ILiProductInfo, IProductDetail } from "@/interfaces/product"; 3 | import { IOrderDetail } from "@/interfaces/order"; 4 | import { ISalesData, ISalesType } from "@/interfaces/dashborad"; 5 | 6 | /** 7 | * 对应后端商家端相关 API 8 | */ 9 | class ShopService { 10 | /** 11 | * 获取产品列表 12 | */ 13 | fetchProductList(params: { page: number; num: number }) { 14 | return requestData<{ count: number; lists: ILiProductInfo[] }>({ 15 | method: "GET", 16 | api: "product/lists", 17 | params 18 | }); 19 | } 20 | 21 | /** 22 | * 获取产品详情 23 | */ 24 | fetchProductDetail(params: { product_id: number }) { 25 | return requestData({ 26 | method: "GET", 27 | api: "product/item", 28 | params 29 | }); 30 | } 31 | 32 | /** 33 | * 获取商品富文本描述 34 | * @param params 35 | */ 36 | fetchProductDesc(params: { product_id: number }) { 37 | return requestData<{ 38 | product_id: number; 39 | /** 富文本描诉 HTML */ 40 | content: string; 41 | }>({ 42 | method: "GET", 43 | api: "product/get_content_detail", 44 | params 45 | }); 46 | } 47 | 48 | /** 49 | * 上下架产品 50 | */ 51 | setProductStatus(params: { product_id: number; status: 1 | 2 }) { 52 | return requestData({ 53 | method: "POST", 54 | api: "product/set_status", 55 | params 56 | }); 57 | } 58 | 59 | /** 60 | * 获取实时订单列表 61 | */ 62 | fetchOrderList(params: { page: number; num: number }) { 63 | return requestData<{ count: number; lists: IOrderDetail[] }>({ 64 | method: "GET", 65 | api: "order/my_lists", 66 | params 67 | }); 68 | } 69 | 70 | /** 71 | * 获取历史订单列表 72 | */ 73 | fetchHisOrderList(params: { page: number; num: number }) { 74 | return requestData<{ count: number; lists: IOrderDetail[] }>({ 75 | method: "GET", 76 | api: "order/history_lists", 77 | params 78 | }); 79 | } 80 | 81 | /** 82 | * 取消订单 83 | */ 84 | fetchCancelOrder(params: { order_number: string }) { 85 | return requestData({ 86 | method: "POST", 87 | api: "order/cancel_payment_order", 88 | params 89 | }); 90 | } 91 | 92 | /** 93 | * 接单 94 | */ 95 | fetchConfirmOrder(params: { order_number: string }) { 96 | return requestData({ 97 | method: "POST", 98 | api: "order/receipt_order", 99 | params 100 | }); 101 | } 102 | 103 | /** 104 | * 打印订单回调 105 | */ 106 | fetchPrintOrderCb(params: { order_number: string }) { 107 | return requestData({ 108 | method: "POST", 109 | api: "order/complete_order", 110 | params 111 | }); 112 | } 113 | 114 | /** 115 | * 获取销售统计 116 | * @param {Object} params 117 | * @param {1|2|3|4} params.type 类型 1:今天 2:昨天 3:前天 4:近七天 118 | */ 119 | fetchSalesDataList(params: { type: ISalesType; page: number; num: number }) { 120 | return requestData({ 121 | method: "GET", 122 | api: "order/statistics_lists", 123 | params 124 | }); 125 | } 126 | 127 | /** 128 | * 修改库存 129 | */ 130 | fetchUpdateSkuStock(params: { 131 | product_id: number; 132 | product_sku: string; 133 | number: number; 134 | }) { 135 | return requestData({ 136 | method: "POST", 137 | api: "product/set_stock", 138 | params 139 | }); 140 | } 141 | } 142 | 143 | export default new ShopService(); 144 | -------------------------------------------------------------------------------- /src/services/userService.ts: -------------------------------------------------------------------------------- 1 | import requestData from "@/utils/requestData"; 2 | 3 | /** 4 | * 对应后端用户相关 API 5 | */ 6 | class UserService { 7 | /** 8 | * 用户登录 9 | */ 10 | fetchUserLogin(params: { username: string; password: string; os_type?: 1 }) { 11 | return requestData<{ access_token: string }>({ 12 | method: "GET", 13 | api: "user/login", 14 | params 15 | }); 16 | } 17 | } 18 | 19 | export default new UserService(); 20 | -------------------------------------------------------------------------------- /src/stores/commonStore.ts: -------------------------------------------------------------------------------- 1 | import { observable } from 'mobx' 2 | import { IBLEServices } from '@/interfaces/common' 3 | 4 | export interface ICommonStore { 5 | /** 系统信息 */ 6 | sysinfo: { 7 | model: string 8 | version: string 9 | system: string 10 | platform: string 11 | SDKVersion: string 12 | } 13 | 14 | /** 是否已连接低功耗蓝牙 */ 15 | isBELconnect: boolean 16 | 17 | /** 是否已获取低功耗蓝牙服务 */ 18 | isBELservices: boolean 19 | 20 | /** 蓝牙服务 */ 21 | BLEServices: IBLEServices 22 | } 23 | 24 | /** 25 | * 公共数据 26 | **/ 27 | class CommonStore implements ICommonStore { 28 | @observable 29 | sysinfo = { 30 | model: '', 31 | version: '', 32 | system: '', 33 | platform: '', 34 | SDKVersion: '' 35 | } 36 | 37 | @observable 38 | isBELconnect = false 39 | 40 | @observable 41 | isBELservices = false 42 | 43 | // 不用转换为observable 44 | BLEServices = { 45 | writeCharaterId: '', 46 | writeServiceId: '', 47 | notifyCharaterId: '', 48 | notifyServiceId: '', 49 | readCharaterId: '', 50 | readServiceId: '' 51 | } 52 | } 53 | 54 | export default CommonStore 55 | -------------------------------------------------------------------------------- /src/stores/dashboradStore.ts: -------------------------------------------------------------------------------- 1 | import { observable, ObservableMap, action, runInAction } from 'mobx' 2 | import shopService from '@/services/shopService' 3 | import { ISalesData, ISalesType } from '@/interfaces/dashborad' 4 | import { IPager } from '@/interfaces/common' 5 | 6 | const salesType: ISalesType[] = [1, 2, 3, 4] 7 | 8 | // 页面Tabs类型数据 9 | type ISalesTabsData = ObservableMap> 10 | 11 | /** 12 | * 统计数据页面 13 | **/ 14 | 15 | export interface IDashboardStore { 16 | /** 页面数据 */ 17 | salesTabsData: ISalesTabsData 18 | 19 | /** 是否到最后一页 */ 20 | salesTabsListIsEnd: boolean 21 | 22 | /** 获取销售统计数据 type 类型 1:今天 2:昨天 3:前天 4:近七天 默认第一页20条 */ 23 | fetchSalesData: (type: ISalesType, page?: number) => Promise 24 | } 25 | 26 | class DashboardStore implements IDashboardStore { 27 | @observable 28 | salesTabsListIsEnd = false 29 | 30 | salesTabsData: ISalesTabsData = observable.map( 31 | salesType.map(key => [ 32 | key, 33 | observable.object( 34 | { 35 | total_num: 0, 36 | total_money: 0, 37 | lists: [] as any[], 38 | count: 0 39 | }, 40 | {}, 41 | { deep: false } 42 | ) 43 | ]), 44 | { deep: false } 45 | ) 46 | 47 | @action 48 | async fetchSalesData (type: ISalesType, page?: number) { 49 | // 加入默认参数 50 | const reqParams = Object.assign({ num: 20 }, { type, page: page || 1 }) 51 | const resData = await shopService.fetchSalesDataList(reqParams) 52 | 53 | if (reqParams.page === 1) { 54 | this.salesTabsListIsEnd = false 55 | } 56 | 57 | runInAction(() => { 58 | const data = this.salesTabsData.get(type) 59 | 60 | if (data) { 61 | data.total_num = resData.total_num 62 | data.total_money = resData.total_money 63 | data.page = reqParams.page 64 | 65 | if (resData.lists.length === 0) { 66 | this.salesTabsListIsEnd = true 67 | } 68 | 69 | if (reqParams.page === 1) { 70 | data.lists = resData.lists 71 | } else { 72 | data.lists.push(...resData.lists) 73 | } 74 | } 75 | }) 76 | } 77 | } 78 | 79 | export default DashboardStore 80 | -------------------------------------------------------------------------------- /src/stores/orderStore.ts: -------------------------------------------------------------------------------- 1 | import { observable, ObservableMap, action, runInAction, computed, decorate } from 'mobx' 2 | import shopService from '@/services/shopService' 3 | import { IOrderDetail } from '@/interfaces/order' 4 | 5 | /** 6 | * 订单数据 7 | **/ 8 | export interface IOrderStore { 9 | /** 实时订单数据源 */ 10 | orderData: Map 11 | 12 | /** 列表页面数据 */ 13 | orderListData: { 14 | ids: string[] 15 | page: number 16 | num: number 17 | } 18 | 19 | /** 列表数据是否已经请求完 */ 20 | listIsEnd: boolean 21 | 22 | /** 历史订单页面数据 */ 23 | hisOrderListData: { 24 | ids: string[] 25 | page: number 26 | num: number 27 | } 28 | /** 历史列表数据是否已经请求完 */ 29 | hisListIsEnd: boolean 30 | 31 | /** 获取订单数据, 默认请求第一页20条数据 type 1:待接订单 2:已处理订单 */ 32 | fetchOrderData: (params: { type: 1 | 2; page?: number; num?: number }) => Promise 33 | /** 取消订单 */ 34 | cancelOrder: (orderId: string) => Promise 35 | 36 | /** 确认打印 */ 37 | confirmOrder: (orderId: string) => Promise 38 | } 39 | 40 | class OrderStore implements IOrderStore { 41 | orderData: ObservableMap = observable.map({}, { deep: false }) 42 | 43 | @observable 44 | orderListData = { 45 | ids: [] as string[], 46 | page: 1, 47 | num: 0 48 | } 49 | @computed 50 | get listIsEnd () { 51 | if (this.orderListData.ids.length <= 0) { 52 | return false 53 | } 54 | 55 | return this.orderListData.ids.length < this.orderListData.page * 20 56 | } 57 | 58 | @observable 59 | hisOrderListData = { 60 | ids: [] as string[], 61 | page: 1, 62 | num: 0 63 | } 64 | @computed 65 | get hisListIsEnd () { 66 | if (this.hisOrderListData.ids.length <= 0) { 67 | return false 68 | } 69 | 70 | return this.hisOrderListData.ids.length < this.hisOrderListData.page * 20 71 | } 72 | 73 | @action 74 | async fetchOrderData (params) { 75 | // 加入默认参数 76 | const reqParams = Object.assign({ num: 20, page: 1 }, params) 77 | 78 | // 获取后端数据 79 | if (params.type === 1) { 80 | const resData = await shopService.fetchOrderList(reqParams) 81 | const idList = resData.lists.map(item => item.order_number) 82 | 83 | runInAction(() => { 84 | // 填入产品map对象 85 | this.orderData.merge( 86 | resData.lists.map(item => { 87 | decorate(item, { events: observable }) 88 | 89 | return [item.order_number, item] 90 | }) 91 | ) 92 | this.orderListData.page = reqParams.page 93 | this.orderListData.num = reqParams.num 94 | 95 | if (reqParams.page === 1) { 96 | // 加载第一页 97 | this.orderListData.ids = idList 98 | } else { 99 | // 翻页 100 | this.orderListData.ids.push(...idList) 101 | } 102 | }) 103 | } else { 104 | const resData = await shopService.fetchHisOrderList(reqParams) 105 | const idList = resData.lists.map(item => item.order_number) 106 | 107 | runInAction(() => { 108 | // 填入产品map对象 109 | this.orderData.merge(resData.lists.map(item => [item.order_number, item])) 110 | this.hisOrderListData.page = reqParams.page 111 | this.hisOrderListData.num = reqParams.num 112 | 113 | if (reqParams.page === 1) { 114 | // 加载第一页 115 | this.hisOrderListData.ids = idList 116 | } else { 117 | // 翻页 118 | this.hisOrderListData.ids.push(...idList) 119 | } 120 | }) 121 | } 122 | } 123 | 124 | @action 125 | async cancelOrder (orderId: string) { 126 | try { 127 | await shopService.fetchCancelOrder({ order_number: orderId }) 128 | 129 | const idList = this.orderListData.ids 130 | 131 | // 待接单中的取消 132 | runInAction(() => { 133 | this.orderListData.ids = idList.filter(id => id !== orderId) 134 | }) 135 | } catch (err) { 136 | console.log(err) 137 | } 138 | } 139 | 140 | @action 141 | async confirmOrder (orderId: string) { 142 | try { 143 | const resData = await shopService.fetchConfirmOrder({ order_number: orderId }) 144 | 145 | // 状态扭转 146 | runInAction(() => { 147 | this.orderData.get(orderId)!.events = resData.events 148 | }) 149 | } catch (err) { 150 | console.log(err) 151 | } 152 | } 153 | } 154 | 155 | export default OrderStore 156 | -------------------------------------------------------------------------------- /src/stores/productStore.ts: -------------------------------------------------------------------------------- 1 | import { observable, ObservableMap, action, runInAction, computed, decorate } from 'mobx' 2 | import { ILiProductInfo, IProductDetail } from '@/interfaces/product' 3 | import shopService from '@/services/shopService' 4 | import mockPromise from '@/utils/mockPromise' 5 | 6 | /** 7 | * 产品数据 8 | **/ 9 | export interface IProductStore { 10 | /** 产品数据源 */ 11 | productData: Map 12 | 13 | /** 列表页面数据 */ 14 | productListData: { 15 | ids: number[] 16 | page: number 17 | num: number 18 | count: number 19 | } 20 | 21 | /** 列表数据是否已经请求完 */ 22 | listIsEnd: boolean 23 | 24 | /** 产品详情 */ 25 | productDatailData: IProductDetail | { product_id: number } 26 | 27 | /** 获取产品数据, 默认请求第一页20条数据 */ 28 | fetchProductData: (params?: { page: number; num?: number }) => Promise 29 | /** 获取产品详情 */ 30 | fetchProductDetail: (product_id: number) => Promise 31 | /** 设置商品上下架 */ 32 | setProductStatus: (productId: number, status: 1 | 2) => Promise 33 | /** 设置商品库存 */ 34 | setProductStock: (productId: number, sku: string, stock: number) => Promise 35 | } 36 | 37 | class ProductStore implements IProductStore { 38 | productData: ObservableMap = observable.map({}, { deep: false }) 39 | 40 | @observable 41 | productListData = { 42 | ids: [] as number[], 43 | page: 1, 44 | num: 0, 45 | count: 0 46 | } 47 | 48 | @computed 49 | get listIsEnd () { 50 | if (this.productListData.ids.length <= 0) { 51 | return false 52 | } 53 | 54 | return this.productListData.ids.length < this.productListData.page * 20 55 | } 56 | 57 | @action 58 | async fetchProductData (params: { page: number; num?: number } = { page: 1 }) { 59 | // 加入默认参数 60 | const reqParams = Object.assign({ num: 20 }, params) 61 | 62 | // 获取后端数据 63 | // const resData = await shopService.fetchProductList(reqParams) 64 | // 模拟fetchProductList异步返回 65 | const resData = await mockPromise({ 66 | lists: [ 67 | { 68 | price: 2500, // 商口单价,单位分 69 | status: 1, // 状态, 1:已上架 2:已下架 70 | sub_title: 'Milk Tea Series: Green Milk Tea', // 子标题 71 | thumb: 'https://www.leidenglai.com/image/weapp/server/product.jpg', 72 | title: '小山绿奶茶', // 标题 73 | product_id: 1 // 商品ID 74 | }, 75 | { 76 | price: 28000, // 商口单价,单位分 77 | vip_price: 20000, // 商口单价,单位分 78 | status: 2, // 状态, 1:已上架 2:已下架 79 | sub_title: 'Mellow Latte: Royal No.9 Latte', // 子标题 80 | thumb: 'https://www.leidenglai.com/image/weapp/server/product.jpg', 81 | title: '皇家九号拿铁', // 标题 82 | product_id: 2 // 商品ID 83 | } 84 | ] 85 | }) 86 | const idList = resData.lists.map(item => item.product_id) 87 | 88 | runInAction(() => { 89 | // 填入产品map对象 90 | this.productData.merge( 91 | resData.lists.map(item => { 92 | decorate(item, { status: observable }) 93 | 94 | return [item.product_id, item] 95 | }) 96 | ) 97 | this.productListData.page = reqParams.page 98 | this.productListData.num = reqParams.num 99 | 100 | if (reqParams.page === 1) { 101 | // 加载第一页 102 | this.productListData.ids = idList 103 | } else { 104 | // 翻页 105 | this.productListData.ids.push(...idList) 106 | } 107 | }) 108 | } 109 | 110 | @observable.ref 111 | productDatailData: any = { product_id: 0 } 112 | 113 | @action 114 | async fetchProductDetail (productId) { 115 | // 获取后端数据 116 | const resData = await shopService.fetchProductDetail({ product_id: productId }) 117 | // 获取富文本 118 | const resContentData = await shopService.fetchProductDesc({ product_id: productId }) 119 | 120 | runInAction(() => { 121 | // 细粒度的控制observable 122 | this.productDatailData = decorate(Object.assign(resData, resContentData), { stock_lists: observable }) 123 | }) 124 | } 125 | 126 | @action 127 | async setProductStatus (productId: number, status: 1 | 2) { 128 | try { 129 | await shopService.setProductStatus({ product_id: productId, status }) 130 | 131 | runInAction(() => { 132 | this.productData.get(productId)!.status = status 133 | }) 134 | } catch (err) { 135 | console.log('修改失败:', err) 136 | } 137 | } 138 | 139 | @action 140 | async setProductStock (productId: number, sku: string, stock: number) { 141 | await shopService.fetchUpdateSkuStock({ product_id: productId, product_sku: sku, number: stock }) 142 | 143 | const stock_lists = this.productDatailData.stock_lists as IProductDetail['stock_lists'] 144 | const index = stock_lists.findIndex(item => item.product_sku === sku) 145 | 146 | runInAction(() => { 147 | this.productDatailData.stock_lists[index].number = stock 148 | }) 149 | } 150 | } 151 | 152 | export default ProductStore 153 | -------------------------------------------------------------------------------- /src/stores/userStore.ts: -------------------------------------------------------------------------------- 1 | import { observable } from 'mobx' 2 | 3 | export interface IUserStore { 4 | /** 登录状态 */ 5 | isLogin: boolean 6 | } 7 | 8 | class UserStore implements IUserStore { 9 | @observable 10 | isLogin = false 11 | } 12 | 13 | export default UserStore 14 | -------------------------------------------------------------------------------- /src/utils/getBELSeviceId.ts: -------------------------------------------------------------------------------- 1 | import Taro, { getBLEDeviceServices } from '@tarojs/taro' 2 | import _ from 'lodash' 3 | import { IBLEServices } from '@/interfaces/common' 4 | 5 | /** 6 | * 获取蓝牙设备所有服务(service) 7 | */ 8 | export default function getBELSeviceId (deviceId: string): Promise { 9 | // const platform = this.BLEInformation.platform 10 | 11 | return Taro.getBLEDeviceServices({ deviceId }).then(res => { 12 | console.log('获取蓝牙设备所有 service:', res) 13 | // var realId = '' 14 | // if (platform == 'android') { 15 | // // for(var i=0;i { 61 | if (!findChar.notify && item.properties.notify) { 62 | findChar.notify = true 63 | BLEServices.notifyCharaterId = item.uuid 64 | BLEServices.notifyServiceId = service.uuid 65 | } 66 | if (!findChar.read && item.properties.read) { 67 | findChar.read = true 68 | BLEServices.readCharaterId = item.uuid 69 | BLEServices.readServiceId = service.uuid 70 | } 71 | if (!findChar.write && item.properties.write) { 72 | findChar.write = true 73 | BLEServices.writeCharaterId = item.uuid 74 | BLEServices.writeServiceId = service.uuid 75 | } 76 | }) 77 | 78 | // notify、read、write 特征值都已找到 跳出循环 79 | if (_.every(findChar, v => v)) { 80 | // 跳出循环 81 | break 82 | } 83 | } 84 | 85 | console.log('获得蓝牙权限:', findChar) 86 | console.log('服务ID', BLEServices) 87 | 88 | // notify、read、write 部分或者全部未找到完全 89 | if (!_.every(findChar, v => v)) { 90 | return Promise.reject('找不到该蓝牙设备读写的特征值') 91 | } else { 92 | return Promise.resolve(BLEServices) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/utils/mockPromise.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 模拟Promise返回 3 | * @param data 需要模拟返回的数据 4 | * @param timeout 延时 5 | */ 6 | export default function mockPromise (data: T, timeout: number = 300): Promise { 7 | return new Promise(resolve => { 8 | setTimeout(() => { 9 | resolve(data) 10 | }, timeout) 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/requestData.ts: -------------------------------------------------------------------------------- 1 | import { SERVER_PROTOCOL, SERVER_API_ROOT_API, DEF_REQUEST_CONFIG } from '../config' 2 | import responseHandler from '@/utils/responseHandler' 3 | import { IReqData, IResError } from '@/interfaces/common' 4 | import { request } from '@tarojs/taro' 5 | import { isObservable, toJS } from 'mobx' 6 | 7 | /** 8 | *请求后端数据封装 9 | *@returns 直接返回data或者错误对象 10 | */ 11 | export default function requestData ({ api, params = {}, method = 'GET' }: IReqData) { 12 | return new Promise((resolve: (data) => void, reject: (err: IResError) => void) => { 13 | const completeApi = SERVER_PROTOCOL + SERVER_API_ROOT_API + api 14 | const mergeData = Object.assign({}, DEF_REQUEST_CONFIG, params) 15 | const requestParams = {} 16 | 17 | for (let key in mergeData) { 18 | if (isObservable(mergeData[key])) { 19 | // 如果是Mobx的Observable对象 将其转换 20 | requestParams[key] = toJS(mergeData[key]) 21 | } else if (typeof mergeData[key] === 'string') { 22 | requestParams[key] = mergeData[key].trim() 23 | } else { 24 | requestParams[key] = mergeData[key] 25 | } 26 | } 27 | 28 | const options: request.Param = { 29 | url: completeApi, 30 | method, 31 | header: { 'Content-Type': 'application/x-www-form-urlencoded' }, 32 | data: requestParams 33 | } 34 | 35 | // 发送请求 返回promise对象 36 | request(options) 37 | .then(responseHandler) 38 | .then(resolve) 39 | .catch(err => { 40 | console.log(err) 41 | 42 | reject(err) 43 | }) 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /src/utils/responseHandler.ts: -------------------------------------------------------------------------------- 1 | import Taro from '@tarojs/taro' 2 | import { IResError } from 'src/interfaces/common' 3 | import { request } from '@tarojs/taro' 4 | 5 | /** 6 | * 处理返回值 7 | * @param {Response} response 后端返回的response对象,微信小程序的数据结构 8 | * @returns {Promise} 直接返回data或者错误对象 9 | */ 10 | export default function responseHandler (response: request.Promised) { 11 | return new Promise((resolve: (data: any) => void, reject: (data: IResError) => void) => { 12 | // 服务器返回状态 13 | if (response.statusCode !== 200) { 14 | return reject({ code: response.statusCode, data: response.data }) 15 | } 16 | 17 | // 业务报错 18 | if (response.data.error_code === 401) { 19 | // 清除过期的登录状态 20 | Taro.removeStorageSync('access_token') 21 | // 跳转到登录页 22 | Taro.navigateTo({ url: '/pages/account/login/index' }) 23 | 24 | // 需要重新登录 25 | return reject({ code: response.data.error_code, message: response.data.error_msg }) 26 | } else if (response.data.error_code) { 27 | // 业务报错 28 | return reject({ code: response.data.error_code, message: response.data.error_msg }) 29 | } 30 | 31 | // 正常 32 | resolve(response.data.response_data) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/transformPrice.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 将价格从分转换为元 3 | * @param {number} fPrice 单位分 4 | * @param {?boolean} cents 是否保留分 5 | */ 6 | export default function transformPrice (fPrice: number, isCents: boolean = true): string { 7 | const cents = isCents ? 2 : 0 8 | const yPrice = (fPrice / 100).toFixed(cents) 9 | 10 | return yPrice 11 | } 12 | -------------------------------------------------------------------------------- /taro-ts-demo.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "git.ignoreLimitWarning": true, 9 | "search.exclude": { 10 | "**/.temp": true, 11 | "**/dist/**": true 12 | }, 13 | "sync.gist": "7b096d9f0a4ac0c68f727930c27fd8f4", 14 | "sync.forceDownload": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "commonjs", 5 | "removeComments": false, 6 | "preserveConstEnums": true, 7 | "moduleResolution": "node", 8 | "experimentalDecorators": true, 9 | "noImplicitAny": false, 10 | "allowSyntheticDefaultImports": true, 11 | "outDir": "lib", 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "strictNullChecks": true, 15 | "sourceMap": true, 16 | "baseUrl": ".", 17 | "rootDir": ".", 18 | "paths": { 19 | "@/components/*": ["./src/components/*"], 20 | "@/assets/*": ["./src/assets/*"], 21 | "@/interfaces/*": ["./src/interfaces/*"], 22 | "@/services/*": ["./src/services/*"], 23 | "@/stores/*": ["./src/stores/*"], 24 | "@/utils/*": ["./src/utils/*"], 25 | "@/src/*": ["./src/*"] 26 | }, 27 | "jsx": "preserve", 28 | "jsxFactory": "Taro.createElement", 29 | "allowJs": true, 30 | "resolveJsonModule": true, 31 | "typeRoots": ["node_modules/@types", "global.d.ts"] 32 | }, 33 | "exclude": ["node_modules", "dist", "libs"], 34 | "compileOnSave": false 35 | } 36 | --------------------------------------------------------------------------------