├── src ├── constants │ └── index.ts ├── resources │ └── images │ │ └── loading.gif ├── components │ ├── container │ │ ├── index.less │ │ ├── navigation.less │ │ ├── leftBtns.less │ │ ├── pullDownRefresh.less │ │ ├── navigation.tsx │ │ ├── leftBtns.tsx │ │ ├── pullDownRefresh.tsx │ │ └── index.tsx │ ├── datetimePicker.less │ ├── fullScreen │ │ ├── error │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── loading │ │ │ ├── index.tsx │ │ │ └── index.less │ │ └── login │ │ │ ├── index.less │ │ │ └── index.tsx │ ├── pagination.less │ ├── picker.tsx │ ├── datetimePicker.tsx │ ├── popup.less │ ├── pagination.tsx │ ├── area.tsx │ └── popup.tsx ├── pages │ ├── index │ │ ├── index.less │ │ └── index.tsx │ ├── pagination │ │ ├── index.less │ │ └── index.tsx │ └── tabAndSearchPagination │ │ ├── index.less │ │ └── index.tsx ├── actions │ ├── simple │ │ ├── demo.ts │ │ ├── commonTypes │ │ │ └── response.d.ts │ │ └── common.ts │ └── rapper │ │ ├── commonTypes │ │ └── response.d.ts │ │ └── types │ │ ├── demo.ts │ │ └── common.ts ├── store.ts ├── cache.ts ├── app.config.ts ├── app.less ├── utils │ ├── request │ │ ├── constants.ts │ │ ├── thirdRequest │ │ │ └── index.ts │ │ ├── innerRequest │ │ │ └── index.ts │ │ ├── tencentUpload │ │ │ └── index.ts │ │ └── index.ts │ └── index.ts ├── hooks.ts ├── index.html ├── styles │ └── index.less ├── app.tsx └── trace.ts ├── stylelint.config.js ├── .prettierrc ├── .husky ├── commit-msg ├── pre-commit └── _ │ └── husky.sh ├── project.alipay.json ├── config ├── development.js ├── webpack │ ├── h5Chain.js │ ├── miniChain.js │ ├── commonChain.js │ └── configPlugin.js ├── production.js └── index.js ├── project.tt.json ├── .editorconfig ├── project.ks.json ├── lint-staged.config.js ├── project.swan.json ├── .gitignore ├── .prettierignore ├── _antm.config.js ├── .eslintignore ├── .stylelintignore ├── babel.config.js ├── .yarnrc ├── .vscode └── settings.json ├── types └── global.d.ts ├── .eslintrc.js ├── project.config.json ├── tsconfig.json ├── README.md └── package.json /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const LOGIN_CODE = '206' 2 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@antmjs/stylelint'], 3 | } 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "semi": false 5 | } -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 5 | -------------------------------------------------------------------------------- /src/resources/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AntmJS/temptaro/HEAD/src/resources/images/loading.gif -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | # npx antm-warning webhooks 6 | -------------------------------------------------------------------------------- /src/components/container/index.less: -------------------------------------------------------------------------------- 1 | .popup-with-login { 2 | .van-popup__close-icon--top-left { 3 | top: 80px; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /project.alipay.json: -------------------------------------------------------------------------------- 1 | { 2 | "component2": true, 3 | "enableAppxNg": true, 4 | "enableParallelLoader": false, 5 | "enableHMR": false 6 | } 7 | -------------------------------------------------------------------------------- /config/development.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | NODE_ENV: '"development"', 4 | }, 5 | defineConstants: {}, 6 | mini: {}, 7 | h5: {}, 8 | } 9 | -------------------------------------------------------------------------------- /src/components/datetimePicker.less: -------------------------------------------------------------------------------- 1 | .components-normal-datetimepicker { 2 | .datetimepicker-placehold { 3 | /* prettier-ignore */ 4 | height: 34PX; 5 | background-color: @picker-background-color; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/pages/index/index.less: -------------------------------------------------------------------------------- 1 | .pages-index-index { 2 | .test { 3 | color: #ffffff; 4 | } 5 | 6 | .ttt { 7 | font-size: 14px; 8 | } 9 | 10 | .btn-panel { 11 | margin-top: 20px; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /project.tt.json: -------------------------------------------------------------------------------- 1 | { 2 | "miniprogramRoot": "./", 3 | "projectname": "temptaro", 4 | "setting": { 5 | "urlCheck": true, 6 | "es6": false, 7 | "minified": false, 8 | "postcss": false, 9 | "enhance": false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/actions/simple/demo.ts: -------------------------------------------------------------------------------- 1 | import { createFetch } from '@/utils/request' 2 | import { IResponseData } from './commonTypes/response.d' 3 | 4 | export const getCosKeyDemo = createFetch>( 5 | '/box/demo/1.0/cosKey', 6 | 'GET', 7 | ) 8 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/components/fullScreen/error/index.less: -------------------------------------------------------------------------------- 1 | .components-fullScreen-error { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: flex-end; 5 | align-items: center; 6 | box-sizing: border-box; 7 | 8 | .button { 9 | width: 260px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/fullScreen/loading/index.tsx: -------------------------------------------------------------------------------- 1 | import { View } from '@tarojs/components' 2 | import './index.less' 3 | export default function Index() { 4 | return ( 5 | 6 | 7 | 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /project.ks.json: -------------------------------------------------------------------------------- 1 | { 2 | "miniprogramRoot": "./", 3 | "projectname": "temptaro", 4 | "description": "", 5 | "setting": { 6 | "urlCheck": true, 7 | "es6": false, 8 | "postcss": false, 9 | "minified": false, 10 | "enhance": false 11 | }, 12 | "compileType": "miniprogram" 13 | } 14 | -------------------------------------------------------------------------------- /config/webpack/h5Chain.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-commonjs */ 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | const H5FixPlugin = require('@antmjs/plugin-h5-fix') 4 | const commonChain = require('./commonChain') 5 | 6 | module.exports = function (chain) { 7 | chain.plugin('H5FixPlugin').use(new H5FixPlugin()) 8 | commonChain(chain) 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/pagination/index.less: -------------------------------------------------------------------------------- 1 | .pages-pagination-index { 2 | .li { 3 | min-height: 200px; 4 | margin: 0 @padding-sm @padding-sm; 5 | box-shadow: 0 0 12px 4px rgba(194, 194, 194, 0.2); 6 | border-radius: @border-radius-lg; 7 | padding: @padding-sm; 8 | 9 | &:first-child { 10 | margin-top: @padding-sm; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '**/*.{js,jsx,ts,tsx}': [ 3 | 'npx eslint -c .eslintrc.js --fix', 4 | 'npx prettier --write', 5 | ], 6 | '**/*.ts?(x)': () => 'npx tsc -p tsconfig.json --skipLibCheck', 7 | '**/*.{css,less}': ['npx stylelint --aei --config stylelint.config.js --fix'], 8 | '**/*.{md,html,css,less}': ['npx prettier --write'], 9 | } 10 | -------------------------------------------------------------------------------- /src/components/pagination.less: -------------------------------------------------------------------------------- 1 | .components-pagination { 2 | .components-mum { 3 | text-align: center; 4 | padding: 16px 0; 5 | display: flex; 6 | justify-content: center; 7 | } 8 | 9 | .components-mum .image { 10 | display: block; 11 | width: 40px; 12 | height: 40px; 13 | } 14 | 15 | .no-data-search { 16 | padding-top: 120px; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /project.swan.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectname": "temptaro", 3 | "miniprogramRoot": "./", 4 | "compilation-args": { 5 | "common": { 6 | "ignorePrefixCss": true, 7 | "ignoreTransJs": true, 8 | "ignoreUglify": true 9 | } 10 | }, 11 | "setting": { 12 | "urlCheck": true, 13 | "es6": false, 14 | "minified": false, 15 | "postcss": false 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/actions/rapper/commonTypes/response.d.ts: -------------------------------------------------------------------------------- 1 | export type IResponseData = { 2 | /** 3 | * @rule 15-25 4 | */ 5 | data: T 6 | 7 | /** 8 | * 业务状态 9 | * @value true 10 | **/ 11 | success: boolean 12 | 13 | /** 14 | * 错误消息 15 | * @value '请求成功' 16 | **/ 17 | message?: string 18 | 19 | /** 20 | * 状态码 21 | * @value '200' 22 | **/ 23 | code: string 24 | } 25 | -------------------------------------------------------------------------------- /src/actions/simple/commonTypes/response.d.ts: -------------------------------------------------------------------------------- 1 | export type IResponseData = { 2 | /** 3 | * @rule 15-25 4 | */ 5 | data: T 6 | 7 | /** 8 | * 业务状态 9 | * @value true 10 | **/ 11 | success: boolean 12 | 13 | /** 14 | * 错误消息 15 | * @value '请求成功' 16 | **/ 17 | message?: string 18 | 19 | /** 20 | * 状态码 21 | * @value '200' 22 | **/ 23 | code: string 24 | } 25 | -------------------------------------------------------------------------------- /src/pages/tabAndSearchPagination/index.less: -------------------------------------------------------------------------------- 1 | .pages-tabandsearchpagination-index { 2 | .li { 3 | background-color: @white; 4 | min-height: 200px; 5 | margin: 0 @padding-sm @padding-sm; 6 | box-shadow: 0 0 12px 4px rgba(194, 194, 194, 0.2); 7 | border-radius: @border-radius-lg; 8 | padding: @padding-sm; 9 | 10 | &:first-child { 11 | margin-top: @padding-sm; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.swp 3 | 4 | .github 5 | .cache 6 | .temp 7 | .idea 8 | .swc 9 | .rn_temp 10 | .DS_Store 11 | 12 | antm.config.js 13 | 14 | src/actions/rapper/* 15 | !src/actions/rapper/commonTypes 16 | !src/actions/rapper/types 17 | src/actions/swagger/* 18 | !src/actions/swagger/utils 19 | node_modules 20 | coverage 21 | api-ui 22 | dist 23 | build 24 | weapp 25 | alipay 26 | kwai 27 | swan 28 | tt 29 | qq 30 | h5 31 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.md 3 | *.lock 4 | *.swp 5 | 6 | .cache 7 | .temp 8 | .idea 9 | .swc 10 | .rn_temp 11 | .DS_Store 12 | .editorconfig 13 | .eslintignore 14 | .gitignore 15 | .prettierignore 16 | .prettierrc 17 | .stylelintignore 18 | LICENSE 19 | 20 | src/actions/rapper/* 21 | !src/actions/rapper/commonTypes 22 | !src/actions/rapper/types 23 | src/actions/swagger 24 | node_modules 25 | src/iconfont.less 26 | coverage 27 | api-ui 28 | dist 29 | build 30 | weapp 31 | alipay 32 | kwai 33 | swan 34 | tt 35 | qq 36 | h5 37 | -------------------------------------------------------------------------------- /src/actions/simple/common.ts: -------------------------------------------------------------------------------- 1 | import { createFetch } from '@/utils/request' 2 | import { IResponseData } from './commonTypes/response.d' 3 | 4 | export const getRoleListCommon = createFetch>( 5 | '/box/common/1.0/role/list', 6 | 'GET', 7 | ) 8 | 9 | export const getCosKeyCommon = createFetch>( 10 | '/box/common/1.0/cosKey', 11 | 'GET', 12 | ) 13 | 14 | export const loginCommon = createFetch>( 15 | '/box/common/1.0/login', 16 | 'POST', 17 | ) 18 | -------------------------------------------------------------------------------- /_antm.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 使用这个前要把.husky/pre-commit文件内 npx antm-warning webhooks 注释去掉 3 | warning: { 4 | monitorFiles: [ 5 | './package.json', 6 | './vscode/**', 7 | './config/**', 8 | './bable.config.js', 9 | './eslint.config.js', 10 | './lint-staged.config.js', 11 | './stylelint.config.js', 12 | './tsconig.json', 13 | ], 14 | webhooks: { 15 | // url: 'https://oapi.dingtalk.com/robot/send?access_token=xxx', 16 | url: '', 17 | }, 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /src/store.ts: -------------------------------------------------------------------------------- 1 | import { atom, RecoilState } from 'recoil' 2 | 3 | export interface IMenuButton { 4 | // 用来判断是否同时通过systemInfo+menuButton得出来的数据 5 | precise: boolean 6 | bottom: number 7 | width: number 8 | height: number 9 | left: number 10 | right: number 11 | marginRight: number 12 | top: number 13 | statusBarHeight: number 14 | } 15 | 16 | // 和UI有关的全局数据存储在这里,和UI无关的全局数据存储在cache.ts文件中 17 | 18 | export const menuButtonStore = atom({ 19 | key: 'menuButtonStore', 20 | default: undefined, 21 | }) as RecoilState 22 | -------------------------------------------------------------------------------- /src/cache.ts: -------------------------------------------------------------------------------- 1 | import Cache from '@antmjs/cache' 2 | 3 | // 和UI无关的全局数据存储在这里,和UI相关的全局数据存储在store.ts文件中 4 | 5 | const { 6 | cacheGetSync, 7 | cacheGet, 8 | cacheSetSync, 9 | cacheSet, 10 | cacheRemoveSync, 11 | cacheRemove, 12 | } = Cache({ 13 | ram: {}, 14 | loc: { 15 | sysInfo: undefined, 16 | menuButton: undefined, 17 | token: '', 18 | userId: '', 19 | location: undefined, 20 | }, 21 | }) 22 | 23 | export { 24 | cacheGetSync, 25 | cacheGet, 26 | cacheSetSync, 27 | cacheSet, 28 | cacheRemoveSync, 29 | cacheRemove, 30 | } 31 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.json 3 | *.css 4 | *.less 5 | *.scss 6 | *.html 7 | *.md 8 | *.lock 9 | *.swp 10 | 11 | .github 12 | .cache 13 | .temp 14 | .idea 15 | .swc 16 | .rn_temp 17 | .DS_Store 18 | .editorconfig 19 | .eslintignore 20 | .gitignore 21 | .prettierignore 22 | .prettierrc 23 | .stylelintignore 24 | LICENSE 25 | 26 | src/actions/rapper/* 27 | !src/actions/rapper/commonTypes 28 | !src/actions/rapper/types 29 | src/actions/swagger/* 30 | !src/actions/swagger/utils 31 | node_modules 32 | coverage 33 | api-ui 34 | dist 35 | build 36 | weapp 37 | alipay 38 | kwai 39 | swan 40 | tt 41 | qq 42 | h5 43 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.json 3 | *.js 4 | *.jsx 5 | *.ts 6 | *.tsx 7 | *.md 8 | *.lock 9 | *.swp 10 | 11 | .github 12 | .cache 13 | .temp 14 | .idea 15 | .swc 16 | .rn_temp 17 | .DS_Store 18 | .editorconfig 19 | .eslintignore 20 | .gitignore 21 | .prettierignore 22 | .prettierrc 23 | .stylelintignore 24 | LICENSE 25 | 26 | src/actions/rapper/* 27 | !src/actions/rapper/commonTypes 28 | !src/actions/rapper/types 29 | src/actions/swagger/* 30 | !src/actions/swagger/utils 31 | node_modules 32 | src/iconfont.less 33 | coverage 34 | api-ui 35 | dist 36 | build 37 | weapp 38 | alipay 39 | kwai 40 | swan 41 | tt 42 | qq 43 | h5 44 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-commonjs */ 2 | // babel-preset-taro 更多选项和默认值: 3 | // https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md 4 | module.exports = { 5 | presets: [ 6 | [ 7 | 'taro', 8 | { 9 | framework: 'react', 10 | ts: true, 11 | useBuiltIns: false, 12 | hot: false, 13 | }, 14 | ], 15 | ], 16 | plugins: [ 17 | ['lodash'], 18 | [ 19 | 'import', 20 | { 21 | libraryName: '@antmjs/vantui', 22 | libraryDirectory: 'es', 23 | }, 24 | '@antmjs/vantui', 25 | ], 26 | ], 27 | } 28 | -------------------------------------------------------------------------------- /src/app.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | export default defineAppConfig({ 3 | // 打开H5路由动画 4 | animation: true, 5 | pages: [ 6 | 'pages/index/index', 7 | 'pages/pagination/index', 8 | 'pages/tabAndSearchPagination/index', 9 | ], 10 | window: { 11 | // @ts-ignore 12 | titleBarColor: '#ededed', 13 | backgroundColor: '#ededed', 14 | backgroundColorTop: '#ededed', 15 | backgroundColorBottom: '#ededed', 16 | backgroundImageColor: '#ededed', 17 | // 微信全局设置自定义导航栏 18 | navigationStyle: 'custom', 19 | // 支付宝全局设置自定义导航栏 20 | transparentTitle: 'always', 21 | titlePenetrate: 'YES', 22 | }, 23 | }) 24 | -------------------------------------------------------------------------------- /src/components/picker.tsx: -------------------------------------------------------------------------------- 1 | import type { PickerProps } from '@antmjs/vantui/types/picker' 2 | import { View } from '@tarojs/components' 3 | import { Picker, Popup } from '@antmjs/vantui' 4 | import './datetimePicker.less' 5 | 6 | export default function Index(props: PickerProps & { show: boolean }) { 7 | const { show, title, ...others } = props 8 | return ( 9 | 16 | 17 | 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /config/webpack/miniChain.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-commonjs */ 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | const MiniFixPlugin = require('@antmjs/plugin-mini-fix') 4 | const GlobalFixPlugin = require('@antmjs/plugin-global-fix') 5 | const commonChain = require('./commonChain') 6 | 7 | module.exports = function (chain) { 8 | // add @antmjs/plugin-mini-fix and @antmjs/mini-fix 9 | // 解决微信小程序和抖音小程序的path上的params没有自动decode的问题,支付宝和钉钉是有decode过的 10 | // 这个问题是因为微信抖音和支付宝钉钉原生小程序的返回结果就是不一致的,Taro目前是没有去处理的 11 | chain.plugin('MiniFixPlugin').use(new MiniFixPlugin()) 12 | 13 | //解决支付宝小程序、钉钉小程序、百度小程序没有暴露全局变量global的问题 14 | chain.plugin('GlobalFixPlugin').use(new GlobalFixPlugin()) 15 | commonChain(chain) 16 | } 17 | -------------------------------------------------------------------------------- /src/components/datetimePicker.tsx: -------------------------------------------------------------------------------- 1 | import type { DatetimePickerProps } from '@antmjs/vantui/types/datetime-picker' 2 | import { View } from '@tarojs/components' 3 | import { DatetimePicker, Popup } from '@antmjs/vantui' 4 | import './datetimePicker.less' 5 | 6 | export default function Index(props: DatetimePickerProps & { show: boolean }) { 7 | const { show, title, ...others } = props 8 | return ( 9 | 16 | 17 | 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /.husky/_/husky.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ -z "$husky_skip_init" ]; then 3 | debug () { 4 | if [ "$HUSKY_DEBUG" = "1" ]; then 5 | echo "husky (debug) - $1" 6 | fi 7 | } 8 | 9 | readonly hook_name="$(basename "$0")" 10 | debug "starting $hook_name..." 11 | 12 | if [ "$HUSKY" = "0" ]; then 13 | debug "HUSKY env variable is set to 0, skipping hook" 14 | exit 0 15 | fi 16 | 17 | if [ -f ~/.huskyrc ]; then 18 | debug "sourcing ~/.huskyrc" 19 | . ~/.huskyrc 20 | fi 21 | 22 | export readonly husky_skip_init=1 23 | sh -e "$0" "$@" 24 | exitCode="$?" 25 | 26 | if [ $exitCode != 0 ]; then 27 | echo "husky - $hook_name hook exited with code $exitCode (error)" 28 | fi 29 | 30 | exit $exitCode 31 | fi 32 | -------------------------------------------------------------------------------- /src/components/container/navigation.less: -------------------------------------------------------------------------------- 1 | .navigation_minibar { 2 | position: fixed; 3 | left: 0; 4 | right: 0; 5 | top: 0; 6 | color: @black; 7 | width: 100%; 8 | box-sizing: border-box; 9 | z-index: 800; 10 | background: @navBack; 11 | backdrop-filter: @backDropFilter; 12 | 13 | &_center { 14 | display: flex; 15 | flex-direction: column; 16 | justify-content: center; 17 | align-items: center; 18 | height: 100%; 19 | } 20 | 21 | &_content { 22 | width: 100%; 23 | font-size: 28px; 24 | /* stylelint-disable-next-line font-weight-notation */ 25 | font-weight: 500; 26 | text-align: center; 27 | } 28 | } 29 | 30 | .visibility-hidden { 31 | position: relative; 32 | top: -999px; 33 | } 34 | -------------------------------------------------------------------------------- /src/components/fullScreen/login/index.less: -------------------------------------------------------------------------------- 1 | .pages-login-index { 2 | position: fixed; 3 | left: 0; 4 | right: 0; 5 | top: 0; 6 | bottom: 0; 7 | width: 100%; 8 | height: 100%; 9 | padding: 0 50px; 10 | 11 | .logo { 12 | width: 392px; 13 | height: 392px; 14 | display: block; 15 | margin: 274px auto 0; 16 | } 17 | 18 | .login-btn { 19 | font-size: 32px; 20 | font-weight: 800; 21 | border-radius: 50px; 22 | outline: none; 23 | border: none; 24 | position: fixed; 25 | width: 653px; 26 | bottom: 203px; 27 | left: 50%; 28 | transform: translateX(-50%); 29 | 30 | &::before { 31 | border: none; 32 | } 33 | 34 | &::after { 35 | border: none; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | registry "https://registry.npm.taobao.org" 2 | disturl "https://npm.taobao.org/dist" 3 | sass_binary_site "https://npm.taobao.org/mirrors/node-sass/" 4 | sentrycli_cdnurl "https://npm.taobao.org/mirrors/sentry-cli/" 5 | electron_mirror "https://npm.taobao.org/mirrors/electron/" 6 | phantomjs_cdnurl "https://npm.taobao.org/mirrors/phantomjs/" 7 | chromedriver_cdnurl "https://npm.taobao.org/mirrors/chromedriver/" 8 | canvas_binary_host_mirror "https://npm.taobao.org/mirrors/node-canvas-prebuilt/" 9 | operadriver_cdnurl "https://npm.taobao.org/mirrors/operadriver" 10 | selenium_cdnurl "https://npm.taobao.org/mirrors/selenium" 11 | node_inspector_cdnurl "https://npm.taobao.org/mirrors/node-inspector" 12 | fsevents_binary_host_mirror "http://npm.taobao.org/mirrors/fsevents/" -------------------------------------------------------------------------------- /src/components/popup.less: -------------------------------------------------------------------------------- 1 | .components-normal-popup { 2 | display: flex; 3 | flex-direction: column; 4 | 5 | .popup-title-panel { 6 | position: absolute; 7 | left: 0; 8 | right: 0; 9 | 10 | /* prettier-ignore */ 11 | height: 40PX; 12 | overflow: hidden; 13 | display: flex; 14 | flex-direction: column; 15 | justify-content: center; 16 | align-items: center; 17 | background: @navBack; 18 | backdrop-filter: @backDropFilter; 19 | 20 | .popup-title { 21 | font-size: 32px; 22 | font-weight: 800; 23 | color: @black; 24 | } 25 | } 26 | 27 | .block-placehold { 28 | /* prettier-ignore */ 29 | height: 40PX; 30 | } 31 | 32 | .popup-body-panel { 33 | flex: 1; 34 | overflow-y: scroll; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app.less: -------------------------------------------------------------------------------- 1 | /* stylelint-disable selector-type-no-unknown, selector-max-type */ 2 | @import '@antmjs/vantui/lib/index.less'; 3 | // @import './iconfont.less'; 4 | page, 5 | .taro_page { 6 | background-color: @pageBack !important; 7 | font-size: 28px; 8 | } 9 | 10 | view, 11 | div { 12 | box-sizing: border-box; 13 | } 14 | 15 | ::-webkit-scrollbar { 16 | display: none; 17 | } 18 | 19 | body, 20 | html { 21 | // NOTE: taro h5 ios上拉遮挡底部fixed元素 22 | // overflow: hidden !important; 23 | } 24 | 25 | // 可以用的类 26 | // .van-hairline, 27 | // .van-hairline--top, 28 | // .van-hairline--left, 29 | // .van-hairline--right, 30 | // .van-hairline--bottom, 31 | // .van-hairline--top-bottom, 32 | // .van-hairline--surround 33 | // .van-ellipsis 34 | // .van-multi-ellipsis--l2 35 | // .van-multi-ellipsis--l3 36 | -------------------------------------------------------------------------------- /src/actions/rapper/types/demo.ts: -------------------------------------------------------------------------------- 1 | import { IResponseData } from '../commonTypes/response.d' 2 | 3 | /** 4 | * 获取腾讯云临时key 5 | * @url /box/demo/1.0/cosKey 6 | * @method GET 7 | */ 8 | export type getCosKey = { 9 | request: Record 10 | response: IResponseData<{ 11 | /** 12 | * id 13 | * @value 'dsafasdfasd' 14 | **/ 15 | tmpSecretId: string 16 | /** 17 | * key 18 | * @value 'adfadfasdfasf' 19 | **/ 20 | tmpSecretKey: string 21 | /** 22 | * token 23 | * @value 'asdfasdf' 24 | **/ 25 | sessionToken: string 26 | /** 27 | * 开始时间 28 | * @value 1580000000 29 | **/ 30 | startTime: number 31 | /** 32 | * 过期时间 33 | * @value 1580000000 34 | **/ 35 | expiredTime: number 36 | }> 37 | } 38 | -------------------------------------------------------------------------------- /src/components/container/leftBtns.less: -------------------------------------------------------------------------------- 1 | .navigation_minibar_left { 2 | z-index: 800; 3 | position: fixed; 4 | box-sizing: border-box; 5 | display: flex; 6 | flex-direction: row; 7 | 8 | &_back { 9 | border-radius: 50%; 10 | background-color: rgba(0, 0, 0, 0.2); 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | 15 | .van-icon { 16 | /* prettier-ignore */ 17 | font-size: 20PX; 18 | font-weight: bold; 19 | color: #ffffff; 20 | } 21 | } 22 | 23 | &_home { 24 | border-radius: 50%; 25 | background-color: rgba(0, 0, 0, 0.2); 26 | display: flex; 27 | justify-content: center; 28 | align-items: center; 29 | 30 | .van-icon { 31 | /* prettier-ignore */ 32 | font-size: 20PX; 33 | font-weight: bold; 34 | color: #ffffff; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "css.validate": false, 3 | "less.validate": false, 4 | "scss.validate": false, 5 | "html.validate.scripts": false, 6 | "html.validate.styles": false, 7 | "javascript.validate.enable": false, 8 | "typescript.validate.enable": true, 9 | "typescript.tsdk": "./node_modules/typescript/lib", 10 | "prettier.resolveGlobalModules": true, 11 | "editor.tabSize": 2, 12 | "editor.formatOnPaste": false, 13 | "editor.formatOnSave": true, 14 | "editor.defaultFormatter": "esbenp.prettier-vscode", 15 | "editor.codeActionsOnSave": { 16 | "source.fixAll.eslint": true, 17 | "source.fixAll.stylelint": true 18 | }, 19 | "eslint.options": { 20 | "overrideConfigFile": ".eslintrc.js" 21 | }, 22 | "stylelint.enable": true, 23 | "stylelint.validate": ["css", "less", "postcss", "scss"], 24 | "stylelint.configFile": "stylelint.config.js" 25 | } 26 | -------------------------------------------------------------------------------- /types/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.png' 4 | declare module '*.gif' 5 | declare module '*.jpg' 6 | declare module '*.jpeg' 7 | declare module '*.svg' 8 | declare module '*.css' 9 | declare module '*.less' 10 | declare module '*.scss' 11 | declare module '*.sass' 12 | declare module '*.styl' 13 | 14 | declare module 'cos-wx-sdk-v5' 15 | declare let wx: any 16 | declare let my: any 17 | declare let ks: any 18 | declare let tt: any 19 | 20 | declare type CreateFetchResponse = { 21 | code: string 22 | header?: any 23 | data: T 24 | message?: string 25 | } 26 | 27 | declare namespace NodeJS { 28 | interface ProcessEnv { 29 | NODE_ENV: 'production' | 'development' 30 | TARO_ENV: 'weapp' | 'alipay' | 'h5' | 'tt' | 'kwai' 31 | API_ENV: 'stable' | 'real' | 'pre' | 'dev' 32 | WATCHING: 'true' | 'false' 33 | DEPLOY_VERSION: string 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/request/constants.ts: -------------------------------------------------------------------------------- 1 | export type TProxy = 'warning' | 'info' 2 | export type IPrefix = keyof typeof domain.real 3 | 4 | export type IPathName< 5 | T extends string, 6 | K extends string, 7 | > = T extends `/${K}${infer R}` ? `/${K}${R}` : never 8 | 9 | export type IHref = T extends `https://${infer R}` 10 | ? `https://${R}` 11 | : T extends `http://${infer R}` 12 | ? `http://${R}` 13 | : never 14 | 15 | /************************************************** */ 16 | /** 上半部分类型,下半部分逻辑 */ 17 | /************************************************** */ 18 | 19 | const domain = { 20 | real: { 21 | box: 'http://rap2api.taobao.org/app/mock/299812', 22 | }, 23 | pre: { 24 | box: 'http://rap2api.taobao.org/app/mock/299812', 25 | }, 26 | stable: { 27 | box: 'http://rap2api.taobao.org/app/mock/299812', 28 | }, 29 | dev: { 30 | box: 'http://rap2api.taobao.org/app/mock/299812', 31 | pet: 'http://swagger.io', 32 | }, 33 | } 34 | 35 | export default domain 36 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: './node_modules/@antmjs/eslint/index.js', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | ecmaVersion: 2021, 7 | }, 8 | settings: { 9 | 'import/resolver': { 10 | typescript: { 11 | project: 'tsconfig.json', 12 | }, 13 | }, 14 | react: { 15 | createClass: 'createReactClass', // Regex for Component Factory to use, 16 | // default to "createReactClass" 17 | pragma: 'React', // Pragma to use, default to "React" 18 | fragment: 'Fragment', // Fragment to use (may be a property of ), default to "Fragment" 19 | version: 'detect', // React version. "detect" automatically picks the version you have installed. 20 | // You can also use `16.0`, `16.3`, etc, if you want to override the detected value. 21 | // default to latest and warns if missing 22 | // It will default to "detect" in the future 23 | // flowVersion: "0.53", // Flow version 24 | }, 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /src/components/fullScreen/error/index.tsx: -------------------------------------------------------------------------------- 1 | import { View } from '@tarojs/components' 2 | import { Button, Empty } from '@antmjs/vantui' 3 | import './index.less' 4 | interface IProps { 5 | error: { 6 | code: string 7 | message: string 8 | data?: any 9 | } 10 | onRefresh: () => void 11 | setError: React.Dispatch< 12 | | React.SetStateAction<{ 13 | code: string 14 | message: string 15 | data?: any 16 | }> 17 | | undefined 18 | > 19 | } 20 | 21 | export default function Index(props: IProps) { 22 | const { error, onRefresh, setError } = props 23 | 24 | const clearError = async function () { 25 | setError(undefined) 26 | onRefresh() 27 | } 28 | 29 | return ( 30 | 31 | 32 | 35 | 36 | 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /config/webpack/commonChain.js: -------------------------------------------------------------------------------- 1 | module.exports = function (chain) { 2 | // taro内部的配置:scriptRule.exclude = [filename => /css-loader/.test(filename) || (/node_modules/.test(filename) && !(/taro/.test(filename)))]; 3 | // 下面重写exclude的配置,部分三方包需要babel,包括taro、@antmjs等 4 | // 根据exclude可以看出,千万不要在项目名称上面带上taro字样,否则所有引用到node_modules的包都会重新被编译一次 5 | // 以下配置将不再使用usage配置,因为根据小程序官方描述,ios9开始基本都已支持了,浏览器可以使用polyfill.io 国内可以用阿里云版的,index.html有引用 6 | 7 | /* 8 | * 如果babel.config.js设置useBuiltIns:usage 9 | * /tarojs[\\/](runtime|shared|plugin-platform|components)/.test(filename) 应该被exculde 10 | * /tarojs[\\/](runtime|shared|plugin-platform)/.test(filename) 应该单独babel 且设置useBuiltIns:false 11 | */ 12 | chain.module 13 | .rule('script') 14 | .exclude.clear() 15 | .add( 16 | (filename) => 17 | /css-loader/.test(filename) || 18 | (/node_modules/.test(filename) && 19 | !/(taro)|(inversify)|(@antmjs)|(react-spring)|(recoil)|(buffer)|(qrcode)/.test( 20 | filename, 21 | )), 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/components/pagination.tsx: -------------------------------------------------------------------------------- 1 | import { View, Image } from '@tarojs/components' 2 | import { Empty } from '@antmjs/vantui' 3 | import './pagination.less' 4 | import { ReactNode } from 'react' 5 | 6 | interface IPropsModel { 7 | complete: boolean 8 | data: any[] 9 | size: number 10 | children: ReactNode 11 | } 12 | 13 | function Index(props: IPropsModel): JSX.Element { 14 | return ( 15 | 16 | {props.children} 17 | {!props.complete && props.data && props.data.length >= props.size ? ( 18 | 19 | 24 | 25 | ) : props.data && props.data.length === 0 ? ( 26 | 27 | 28 | 29 | ) : ( 30 | 31 | )} 32 | 33 | ) 34 | } 35 | 36 | export default Index 37 | -------------------------------------------------------------------------------- /src/utils/request/thirdRequest/index.ts: -------------------------------------------------------------------------------- 1 | import type { IHref } from '../constants' 2 | import Taro from '@tarojs/taro' 3 | 4 | export default function thirdRequest< 5 | T extends Omit, 6 | >(option: { 7 | [K in keyof T]: K extends 'url' ? IHref : T[K] 8 | }) { 9 | return new Promise((resolve: (res: CreateFetchResponse) => void) => { 10 | Taro.request({ 11 | ...option, 12 | }) 13 | .then((res) => { 14 | if (res.statusCode === 200) { 15 | resolve({ 16 | header: res.header, 17 | code: '200', 18 | data: res.data || res, 19 | }) 20 | } else { 21 | resolve({ 22 | header: res.header, 23 | code: (res.statusCode || 599).toString(), 24 | data: res.data || res, 25 | message: '请求错误', 26 | }) 27 | } 28 | }) 29 | .catch((error) => { 30 | resolve({ 31 | code: '499', 32 | data: error, 33 | message: '网络不稳定,请重试', 34 | }) 35 | }) 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /config/production.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | NODE_ENV: '"production"', 4 | }, 5 | defineConstants: {}, 6 | mini: {}, 7 | h5: { 8 | /** 9 | * WebpackChain 插件配置 10 | * @docs https://github.com/neutrinojs/webpack-chain 11 | */ 12 | // webpackChain (chain) { 13 | // /** 14 | // * 如果 h5 端编译后体积过大,可以使用 webpack-bundle-analyzer 插件对打包体积进行分析。 15 | // * @docs https://github.com/webpack-contrib/webpack-bundle-analyzer 16 | // */ 17 | // chain.plugin('analyzer') 18 | // .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, []) 19 | // /** 20 | // * 如果 h5 端首屏加载时间过长,可以使用 prerender-spa-plugin 插件预加载首页。 21 | // * @docs https://github.com/chrisvfritz/prerender-spa-plugin 22 | // */ 23 | // const path = require('path') 24 | // const Prerender = require('prerender-spa-plugin') 25 | // const staticDir = path.join(__dirname, '..', 'dist') 26 | // chain 27 | // .plugin('prerender') 28 | // .use(new Prerender({ 29 | // staticDir, 30 | // routes: [ '/pages/index/index' ], 31 | // postProcess: (context) => ({ ...context, outputPath: path.join(staticDir, 'index.html') }) 32 | // })) 33 | // } 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /src/components/area.tsx: -------------------------------------------------------------------------------- 1 | import type { AreaProps } from '@antmjs/vantui/types/area' 2 | import type { IPickerInstance } from '@antmjs/vantui/types/picker' 3 | import { View } from '@tarojs/components' 4 | import { areaList } from '@vant/area-data' 5 | import { Area, Popup } from '@antmjs/vantui' 6 | import './datetimePicker.less' 7 | import { useCallback } from 'react' 8 | 9 | export default function Index( 10 | props: Omit & { 11 | show: boolean 12 | onChange?: (event: { 13 | detail: { 14 | values: number[] | string[] 15 | picker: IPickerInstance 16 | index: number 17 | } 18 | }) => void 19 | }, 20 | ) { 21 | const { show, title, value, onChange, ...others } = props 22 | const _onChange = useCallback(() => {}, []) 23 | return ( 24 | 31 | 38 | 39 | 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "miniprogramRoot": "./", 3 | "projectname": "temptaro", 4 | "description": "小程序开箱即用模版", 5 | "setting": { 6 | "urlCheck": true, 7 | "es6": false, 8 | "enhance": false, 9 | "postcss": false, 10 | "preloadBackgroundData": false, 11 | "minified": false, 12 | "newFeature": false, 13 | "coverView": true, 14 | "nodeModules": false, 15 | "autoAudits": false, 16 | "showShadowRootInWxmlPanel": true, 17 | "scopeDataCheck": false, 18 | "uglifyFileName": false, 19 | "checkInvalidKey": true, 20 | "checkSiteMap": true, 21 | "uploadWithSourceMap": true, 22 | "compileHotReLoad": false, 23 | "lazyloadPlaceholderEnable": false, 24 | "useMultiFrameRuntime": true, 25 | "useApiHook": true, 26 | "useApiHostProcess": true, 27 | "babelSetting": { 28 | "ignore": [], 29 | "disablePlugins": [], 30 | "outputPath": "" 31 | }, 32 | "enableEngineNative": false, 33 | "useIsolateContext": true, 34 | "userConfirmedBundleSwitch": false, 35 | "packNpmManually": false, 36 | "packNpmRelationList": [], 37 | "minifyWXSS": false, 38 | "disableUseStrict": false, 39 | "showES6CompileOption": false, 40 | "useCompilerPlugins": false 41 | }, 42 | "compileType": "miniprogram" 43 | } 44 | -------------------------------------------------------------------------------- /src/components/popup.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | import { View } from '@tarojs/components' 3 | import { Popup } from '@antmjs/vantui' 4 | import './popup.less' 5 | 6 | interface IProps { 7 | show: boolean 8 | closeable?: boolean 9 | title?: ReactNode 10 | children: ReactNode 11 | safeAreaInsetBottom?: boolean 12 | onClose?: () => void 13 | } 14 | 15 | export default function Index(props: IProps) { 16 | const { 17 | onClose, 18 | show, 19 | children, 20 | closeable = true, 21 | title, 22 | safeAreaInsetBottom = false, 23 | } = props 24 | return ( 25 | 38 | {title && ( 39 | 40 | {title} 41 | 42 | )} 43 | {title && } 44 | {children} 45 | 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /src/hooks.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import { 3 | nextTick, 4 | useDidShow as useDidShowInTaro, 5 | useRouter as useRouterInTaro, 6 | } from '@tarojs/taro' 7 | import { parse } from '@antmjs/utils' 8 | import { debounce, throttle } from 'lodash' 9 | import { useCallback, useRef } from 'react' 10 | 11 | // 两次点击相隔300ms内只执行最后一次 12 | export function useDebounce(fn: any, ms?: number): any { 13 | const fRef: any = useRef() 14 | fRef.current = fn 15 | const result = useCallback( 16 | debounce((...args) => fRef.current(...args), ms ?? 300), 17 | [], 18 | ) 19 | return result 20 | } 21 | 22 | // 每隔300ms执行一次 23 | export function useThrottle(fn: any, ms?: number): any { 24 | const fRef: any = useRef() 25 | fRef.current = fn 26 | const result = useCallback( 27 | throttle((...args) => fRef.current(...args), ms ?? 300), 28 | [], 29 | ) 30 | return result 31 | } 32 | 33 | export function useDidShow(fn: any): void { 34 | useDidShowInTaro(() => { 35 | // Taro的生命周期里面会先执行useDidShow,再执行useEffect(() => {//后执行}, []) 36 | // 这里加nextTick延后执行 37 | nextTick(fn) 38 | }) 39 | } 40 | 41 | export function useRouter() { 42 | const routerInfo: Taro.RouterInfo = useRouterInTaro() 43 | if (process.env.TARO_ENV === 'h5') { 44 | const query = parse(location.search ? location.search.slice(1) : '') 45 | routerInfo.params = { ...routerInfo.params, ...query } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/fullScreen/loading/index.less: -------------------------------------------------------------------------------- 1 | .loading-box { 2 | height: 30vh; 3 | display: flex; 4 | justify-content: center; 5 | align-items: flex-end; 6 | 7 | .page-init-loading { 8 | position: relative; 9 | width: 14px; 10 | height: 14px; 11 | border-radius: 50%; 12 | background-color: @gray-6; 13 | color: @gray-6; 14 | animation: dotFlashing 1s infinite linear alternate; 15 | animation-delay: 0.5s; 16 | 17 | &::before { 18 | content: ''; 19 | display: inline-block; 20 | position: absolute; 21 | top: 0; 22 | width: 14px; 23 | height: 14px; 24 | border-radius: 50%; 25 | background-color: @gray-6; 26 | color: @gray-6; 27 | left: -26px; 28 | animation: dotFlashing 1s infinite alternate; 29 | animation-delay: 0s; 30 | } 31 | 32 | &::after { 33 | content: ''; 34 | display: inline-block; 35 | position: absolute; 36 | top: 0; 37 | width: 14px; 38 | height: 14px; 39 | border-radius: 50%; 40 | background-color: @gray-6; 41 | color: @gray-6; 42 | left: 26px; 43 | animation: dotFlashing 1s infinite alternate; 44 | animation-delay: 1s; 45 | } 46 | } 47 | /* stylelint-disable-next-line keyframes-name-pattern */ 48 | @keyframes dotFlashing { 49 | 0% { 50 | background-color: @gray-6; 51 | } 52 | 53 | 100% { 54 | background-color: @gray-5; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /** type checking */ 4 | "strict": true, 5 | // 默认false,是否检测定义了但是没使用的变量 6 | "noUnusedLocals": true, 7 | // 用于检查是否有在函数体中没有使用的参数 8 | "noUnusedParameters": true, 9 | "allowUnreachableCode": false, 10 | "allowUnusedLabels": false, 11 | "noFallthroughCasesInSwitch": true, 12 | "noImplicitOverride": false, 13 | "noImplicitReturns": true, 14 | "noImplicitAny": false, 15 | "noPropertyAccessFromIndexSignature": true, 16 | "noUncheckedIndexedAccess": true, 17 | /** module */ 18 | "baseUrl": "./", 19 | "module": "commonjs", 20 | // default: module === AMD or UMD or System or ES6, then Classic otherwise Node 21 | "moduleResolution": "Node", 22 | "target": "es5", 23 | "lib": ["ESNext", "dom"], 24 | /** emit */ 25 | "noEmit": true, 26 | /** Interop Constraints */ 27 | "esModuleInterop": true, 28 | "allowSyntheticDefaultImports": true, 29 | "forceConsistentCasingInFileNames": true, 30 | // "isolatedModules": true, 31 | "emitDecoratorMetadata": true, 32 | "experimentalDecorators": true, 33 | "jsx": "react-jsx", 34 | "skipLibCheck": false, 35 | "paths": { 36 | // 指定模块的路径,和baseUrl有关联,和webpack中resolve.alias配置一样 37 | "@/*": ["./src/*"] 38 | } 39 | }, 40 | "include": [ 41 | "./src", 42 | "./config", 43 | "./types", 44 | "./babel.config.js", 45 | ".eslintrc.js", 46 | "./lint-staged.config.js", 47 | "./stylelint.config.js", 48 | "./_antm.config.js", 49 | "./antm.config.js" 50 | ], 51 | "compileOnSave": false 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 注意事项 2 | 3 | * h5环境使用useRouter取到的params需要自行decode,其他环境及this.location内部已decode过了,或者使用hooks.ts内的useRouter 4 | * 如果自己在函数式组件内调用useDidShow,需要在方法内包裹一层nextTick方法,否则Taro的生命周期里面会先执行useDidShow,再执行useEffect(() => {//后执行}, []),或者使用hooks.ts内的useDidShow 5 | 6 | ## Taro项目模版 7 | 8 | 目前支持微信、支付宝、抖音、快手、百度小程序;支持H5 9 | 10 | ### 代码规范 11 | 12 | * 默认集成了 prettier eslint stylelint 解决编码规范问题 13 | * 默认集成了 commitlint commitizen 解决commit规范问题 14 | * 默认集成了 husky lint-staged 解决了commit之前自动校验代码规范 15 | 16 | ### 开发效率 17 | 18 | * 使用Unite库以空间换时间的方案加快研发速度,同时保证TS类型安全 19 | * action层使用@antmjs/api实现根据TS类型自动生成action逻辑,保证类型安全 20 | * action层也可以使用yarn swagger自动根据服务端的swagger api 自动生成action逻辑,保证类型安全 21 | * 自动埋点 22 | * 自动收集异常 23 | * 自动处理异常 24 | * 自动处理pullDownloadRefresh 25 | * 快速使用自定义导航 26 | * 快速支持事件抖动 27 | * 二次封装了部分频繁使用的组件 28 | 29 | ## 使用 30 | 31 | 1. 执行yarn rapper自动生成action层代码(需要的话) 32 | 2. 执行yarn swagger自动根据服务端swagger api生成action层代码(需要的话) 33 | 3. 需要引入iconfont可以执行 yarn iconfont 会自动生成src/iconfont.less 34 | 4. yarn 35 | 5. yarn watch:weapp(package.json里面填写对应环境的appId) 36 | 37 | ## 如果需要添加告警机制 38 | 39 | 1. 将_antm.config.js 改成 antm.config.js 40 | 2. 更新antm.config.js里面的webhooks.url的access_token 41 | 3. 将.husky/pre-commit里面的npx antm-warning webhooks 注释取消 42 | 43 | ### 执行顺序 useDidShow 优先于useEffect执行 44 | 45 | - app show 46 | - app launch 47 | 48 | - index com show 49 | - index page show 50 | - index com load 51 | - index page load 52 | 53 | - index com hide 54 | - index page hide 55 | 56 | - second com show 57 | - second page show 58 | - second com load 59 | - second page load 60 | 61 | - index com show 62 | - index pageshow 63 | 64 | - second page unload 65 | - second com unload 66 | -------------------------------------------------------------------------------- /src/components/container/pullDownRefresh.less: -------------------------------------------------------------------------------- 1 | .navigation_minibar_pulldown { 2 | display: flex; 3 | justify-content: center; 4 | position: fixed; 5 | left: 50%; 6 | transform: translateX(-50%) scale(0); 7 | width: 200px; 8 | height: 60px; 9 | border-radius: 40px; 10 | align-items: center; 11 | align-self: center; 12 | color: @gray-6; 13 | background-color: @white; 14 | transform-origin: center; 15 | z-index: 100000000; 16 | 17 | .navigation_minibar_loading { 18 | position: relative; 19 | width: 14px; 20 | height: 14px; 21 | border-radius: 50%; 22 | background-color: @gray-6; 23 | color: @gray-6; 24 | animation: dotFlashing 1s infinite linear alternate; 25 | animation-delay: 0.5s; 26 | 27 | &::before { 28 | content: ''; 29 | display: inline-block; 30 | position: absolute; 31 | top: 0; 32 | width: 14px; 33 | height: 14px; 34 | border-radius: 50%; 35 | background-color: @gray-6; 36 | color: @gray-6; 37 | left: -26px; 38 | animation: dotFlashing 1s infinite alternate; 39 | animation-delay: 0s; 40 | } 41 | 42 | &::after { 43 | content: ''; 44 | display: inline-block; 45 | position: absolute; 46 | top: 0; 47 | width: 14px; 48 | height: 14px; 49 | border-radius: 50%; 50 | background-color: @gray-6; 51 | color: @gray-6; 52 | left: 26px; 53 | animation: dotFlashing 1s infinite alternate; 54 | animation-delay: 1s; 55 | } 56 | } 57 | /* stylelint-disable-next-line keyframes-name-pattern */ 58 | @keyframes dotFlashing { 59 | 0% { 60 | background-color: @gray-6; 61 | } 62 | 63 | 100% { 64 | background-color: @gray-5; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | antmjs 15 | 19 | 22 | 49 | 50 | 51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /src/pages/pagination/index.tsx: -------------------------------------------------------------------------------- 1 | import { Unite } from '@antmjs/unite' 2 | import { View } from '@tarojs/components' 3 | import { useReachBottom } from '@tarojs/taro' 4 | import Container from '@/components/container' 5 | import Pagination from '@/components/pagination' 6 | import { getRoleListCommon } from '@/actions/simple/common' 7 | import './index.less' 8 | 9 | const PAGE_SIZE = 20 10 | 11 | export default Unite( 12 | { 13 | state: { 14 | list: null, 15 | complete: false, 16 | }, 17 | async onLoad() { 18 | await this.loadList(true) 19 | }, 20 | async loadList(refresh = false) { 21 | const list = await getRoleListCommon({ 22 | pageSize: PAGE_SIZE, 23 | offset: refresh ? 0 : this.state.list.length, 24 | }) 25 | this.setState({ 26 | list: refresh ? list : [].concat(this.state.list).concat(list as any), 27 | complete: list.length < PAGE_SIZE ? true : false, 28 | }) 29 | }, 30 | }, 31 | function ({ state, events, loading }) { 32 | const { list, complete } = state 33 | const { loadList } = events 34 | useReachBottom(() => { 35 | if (!loading.loadList && !complete) { 36 | loadList() 37 | } 38 | }) 39 | return ( 40 | 46 | 47 | {list?.map((item: any, index: number) => { 48 | return ( 49 | 50 | {item.name} 51 | 52 | ) 53 | })} 54 | 55 | 56 | ) 57 | }, 58 | { page: true }, 59 | ) 60 | 61 | definePageConfig({ 62 | // 这里不要设置标题,在Container组件上面设置 63 | navigationBarTitleText: '', 64 | }) 65 | -------------------------------------------------------------------------------- /src/actions/rapper/types/common.ts: -------------------------------------------------------------------------------- 1 | import { IResponseData } from '../commonTypes/response.d' 2 | 3 | /** 4 | * 获取角色列表 5 | * @url /box/common/1.0/role/list 6 | * @method GET 7 | */ 8 | export type getRoleList = { 9 | request: Record 10 | response: IResponseData< 11 | Array<{ 12 | /** 13 | * 名称 14 | * @value ['角色AA ', '角色BB'] 15 | * @rule +1 16 | */ 17 | name: string 18 | 19 | /** 20 | * 描述角色作用 21 | * @value 1 22 | * @rule +1 23 | */ 24 | id: number 25 | 26 | /** 27 | * 描述角色作用 28 | * @value ['描述AA', '描述BB'] 29 | * @rule +1 30 | */ 31 | desc: string 32 | }> 33 | > 34 | } 35 | 36 | /** 37 | * 获取腾讯云临时key 38 | * @url /box/common/1.0/cosKey 39 | * @method GET 40 | */ 41 | export type getCosKey = { 42 | request: Record 43 | response: IResponseData<{ 44 | /** 45 | * id 46 | * @value 'dsafasdfasd' 47 | **/ 48 | tmpSecretId: string 49 | /** 50 | * key 51 | * @value 'adfadfasdfasf' 52 | **/ 53 | tmpSecretKey: string 54 | /** 55 | * token 56 | * @value 'asdfasdf' 57 | **/ 58 | sessionToken: string 59 | /** 60 | * 开始时间 61 | * @value 1580000000 62 | **/ 63 | startTime: number 64 | /** 65 | * 过期时间 66 | * @value 1580000000 67 | **/ 68 | expiredTime: number 69 | }> 70 | } 71 | 72 | /** 73 | * 登录接口 74 | * @url /box/common/1.0/login 75 | * @method POST 76 | */ 77 | export type login = { 78 | request: { 79 | /** 80 | * 编码 81 | * @value true 82 | **/ 83 | jsCode: string 84 | /** 85 | * iv 86 | * @value true 87 | **/ 88 | iv: string 89 | /** 90 | * EncryptedData 91 | * @value true 92 | **/ 93 | userInfoEncryptedData: string 94 | } 95 | response: IResponseData<{ 96 | token: string 97 | }> 98 | } 99 | -------------------------------------------------------------------------------- /src/utils/request/innerRequest/index.ts: -------------------------------------------------------------------------------- 1 | import type { IPrefix, IPathName } from '../constants' 2 | import Taro from '@tarojs/taro' 3 | 4 | // 基于和服务端的约定,这个方法主要是用来处理返回类型是json的请求,非json类型的自己单独封装 5 | // 格式如下 { statusCode: number, data: { success: boolean, data: any, code: string, message: string } } 6 | export default function innerRequest< 7 | T extends Omit, 8 | >(option: { 9 | [K in keyof T]: K extends 'url' ? IPathName : T[K] 10 | }) { 11 | option.timeout = option.timeout || 30000 12 | option.dataType = 'json' 13 | option.responseType = 'text' 14 | return new Promise((resolve: (res: CreateFetchResponse) => void) => { 15 | Taro.request({ 16 | ...option, 17 | }) 18 | .then((res) => { 19 | // 符合返回的规范才认定为成功 20 | if ( 21 | (res.data.data || res.data.code) && 22 | typeof res.data.success === 'boolean' 23 | ) { 24 | if (res.data.success) { 25 | resolve({ 26 | header: res.header, 27 | code: '200', 28 | data: res.data.data, 29 | }) 30 | } else { 31 | resolve({ 32 | header: res.header, 33 | code: (res.data.code || 597).toString(), 34 | data: res.data.msg || res.data.message, 35 | message: res.data.msg || res.data.message, 36 | }) 37 | } 38 | } else { 39 | if (res.statusCode === 200) res.statusCode = 598 40 | resolve({ 41 | header: res.header, 42 | code: (res.statusCode || 599).toString(), 43 | data: res, 44 | message: '请求错误', 45 | }) 46 | } 47 | }) 48 | .catch((error) => { 49 | console.log(error) 50 | resolve({ 51 | code: '499', 52 | data: error, 53 | message: '网络不稳定,请重试', 54 | }) 55 | }) 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /src/styles/index.less: -------------------------------------------------------------------------------- 1 | @import '@antmjs/vantui/es/style/var.less'; 2 | 3 | // 这里可以重写主题 4 | @black: #1a1a1a; 5 | @white: #f7f7f7; 6 | @gray-1: #f7f8fa; 7 | @gray-2: #f2f3f5; 8 | @gray-3: #ededed; 9 | @gray-4: #dcdee0; 10 | @gray-5: #c8c9cc; 11 | @gray-6: #969799; 12 | @gray-7: #646566; 13 | @gray-8: #323233; 14 | @red: #ee0a24; 15 | @blue: #1989fa; 16 | @orange: #ff976a; 17 | @orange-dark: #ed6a0c; 18 | @orange-light: #fffbe8; 19 | @green: #07c160; 20 | 21 | @pageBack: @gray-3; 22 | @navBack: rgba(237, 237, 237, 0.9); 23 | @popup-background-color: @gray-3; 24 | @backDropFilter: blur(20px); 25 | 26 | @popup-close-icon-color: @gray-5; 27 | @popup-close-icon-size: 40px; 28 | @popup-close-icon-margin: 24px; 29 | @button-plain-background-color: @gray-4; 30 | 31 | // z-index 32 | @sticky-z-index: 800; 33 | @tabbar-z-index: 805; 34 | @navbar-z-index: 805; 35 | @goods-action-z-index: 806; 36 | @submit-bar-z-index: 806; 37 | @overlay-z-index: 1000; 38 | @dropdown-z-index: 1000; 39 | @popup-z-index: 1010; 40 | @popup-close-icon-z-index: 1010; 41 | @notify-z-index: 1500; 42 | 43 | // Padding or Margin 44 | @padding-base: 8px; 45 | @padding-xs: @padding-base * 2; 46 | @padding-sm: @padding-base * 3; 47 | @padding-md: @padding-base * 4; 48 | @padding-lg: @padding-base * 6; 49 | @padding-xl: @padding-base * 8; 50 | 51 | // Font 52 | @font-size-xs: 20px; 53 | @font-size-sm: 24px; 54 | @font-size-md: 28px; 55 | @font-size-lg: 32px; 56 | @font-weight-bold: 500; 57 | @line-height-xs: 28px; 58 | @line-height-sm: 36px; 59 | @line-height-md: 40px; 60 | @line-height-lg: 44px; 61 | @base-font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 62 | Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 63 | 'Microsoft Yahei', sans-serif; 64 | @price-integer-font-family: Avenir-Heavy, PingFang SC, Helvetica Neue, Arial, 65 | sans-serif; 66 | 67 | // Border 68 | @border-color: @gray-3; 69 | @border-width-base: 2px; 70 | @border-radius-sm: 4px; 71 | @border-radius-md: 8px; 72 | @border-radius-lg: 16px; 73 | @border-radius-max: 999px; 74 | -------------------------------------------------------------------------------- /src/app.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { RecoilRoot } from 'recoil' 3 | import { 4 | useDidShow, 5 | useDidHide, 6 | getUpdateManager, 7 | showModal, 8 | nextTick, 9 | } from '@tarojs/taro' 10 | import './cache' 11 | import { setSysInfoAsync } from '@/utils' 12 | import './app.less' 13 | 14 | export default function App(props: any) { 15 | // 可以使用所有的 React Hooks 16 | useEffect(() => { 17 | console.log('app launch') 18 | // app 里面发起的请求只能用info,因为不在Unite里面,拦截不了。返回结果自己处理 19 | // 请求不需要catch,内部封装过了,都会resolve出来,具体看res的类型 20 | // doSomeFunction({}, 'info').then((res) => {}) 21 | return function () { 22 | // 这个暂时不确定会不会触发 23 | console.log('app unlaunch') 24 | } 25 | // eslint-disable-next-line react-hooks/exhaustive-deps 26 | }, []) 27 | 28 | // 对应 onShow 29 | useDidShow(() => { 30 | nextTick(() => { 31 | setSysInfoAsync() 32 | if (process.env.TARO_ENV !== 'h5') { 33 | const updateManager: any = getUpdateManager() 34 | updateManager.onCheckForUpdate(async (res: any) => { 35 | if (res.hasUpdate) { 36 | } 37 | }) 38 | updateManager.onUpdateReady(() => { 39 | showModal({ 40 | title: '更新提示', 41 | content: '新版本已经准备好,立即重启应用?', 42 | confirmText: '我知道了', 43 | showCancel: false, 44 | }).then(function (mRes: any): void { 45 | if (mRes.confirm) { 46 | updateManager.applyUpdate() 47 | } 48 | }) 49 | }) 50 | 51 | updateManager.onUpdateFailed(() => { 52 | showModal({ 53 | title: '更新失败', 54 | content: '请删除小程序后重新打开', 55 | confirmText: '我知道了', 56 | showCancel: false, 57 | }).then(function (): void {}) 58 | }) 59 | } 60 | }) 61 | }) 62 | 63 | // 对应 onHide 64 | useDidHide(() => { 65 | console.log('app hide') 66 | }) 67 | 68 | return ( 69 | // 在入口组件不会渲染任何内容,但我们可以在这里做类似于状态管理的事情 70 | {props.children} 71 | ) 72 | } 73 | -------------------------------------------------------------------------------- /src/utils/request/tencentUpload/index.ts: -------------------------------------------------------------------------------- 1 | import COS from 'cos-wx-sdk-v5' 2 | import { getCosKeyDemo } from '@/actions/simple/demo' 3 | import { randomNum } from '@/utils' 4 | 5 | export default function (filePath: any, filename: string, index?: number) { 6 | return new Promise( 7 | (resolve: (res: CreateFetchResponse & { index?: number }) => void) => { 8 | const cos = new COS({ 9 | // ForcePathStyle: true, // 如果使用了很多存储桶,可以通过打开后缀式,减少配置白名单域名数量,请求时会用地域域名 10 | getAuthorization: async function ( 11 | _options: any, 12 | callback: (...prams: any) => void, 13 | ) { 14 | // 异步获取临时密钥 15 | const { 16 | tmpSecretId, 17 | tmpSecretKey, 18 | sessionToken, 19 | startTime, 20 | expiredTime, 21 | } = await getCosKeyDemo({}) 22 | callback({ 23 | TmpSecretId: tmpSecretId, 24 | TmpSecretKey: tmpSecretKey, 25 | XCosSecurityToken: sessionToken, 26 | // 建议返回服务器时间作为签名的开始时间,避免客户浏览器本地时间偏差过大导致签名错误 27 | StartTime: startTime, // 时间戳,单位秒,如:1580000000 28 | ExpiredTime: expiredTime, // 时间戳,单位秒,如:1580000900 29 | }) 30 | }, 31 | }) 32 | const date = new Date() 33 | const folder = `${date.getFullYear()}${date.getMonth()}${date.getDay()}${date.getHours()}${date.getHours()}${date.getMinutes()}${date.getSeconds()}${randomNum( 34 | 10000, 35 | 100000, 36 | )}` 37 | cos.postObject( 38 | { 39 | Bucket: '', 40 | Region: '', 41 | Key: (`img/${process.env.API_ENV}/${folder}/` + filename).replace( 42 | /[\u4E00-\u9FFF\u0020]/g, 43 | '', 44 | ), 45 | FilePath: filePath, 46 | }, 47 | function (error: any, data: any) { 48 | if (data) 49 | resolve({ 50 | code: '200', 51 | data: data, 52 | message: '', 53 | index, 54 | }) 55 | if (error) 56 | resolve({ 57 | code: '599', 58 | data: error, 59 | message: '请求异常', 60 | index, 61 | }) 62 | }, 63 | ) 64 | }, 65 | ) 66 | } 67 | -------------------------------------------------------------------------------- /config/webpack/configPlugin.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require('path') 3 | 4 | module.exports = function (ctx) { 5 | ctx.registerMethod( 6 | 'generateProjectConfig', 7 | ({ srcConfigName, distConfigName }) => { 8 | // 混合模式不需要生成项目配置 9 | const { blended } = ctx.runOpts 10 | if (blended) return 11 | 12 | const { appPath, sourcePath, outputPath } = ctx.paths 13 | const { printLog, processTypeEnum, fs } = ctx.helper 14 | 15 | // 生成 project.config.json 16 | const projectConfigFileName = srcConfigName 17 | let projectConfigPath = path.join(appPath, projectConfigFileName) 18 | if (!fs.existsSync(projectConfigPath)) { 19 | // 若项目根目录不存在对应平台的 projectConfig 文件,则尝试从源代码目录查找 20 | projectConfigPath = path.join(sourcePath, projectConfigFileName) 21 | if (!fs.existsSync(projectConfigPath)) return 22 | } 23 | 24 | const origProjectConfig = fs.readJSONSync(projectConfigPath) 25 | // compileType 是 plugin 时不修改 miniprogramRoot 字段 26 | let distProjectConfig = origProjectConfig 27 | if (origProjectConfig.compileType !== 'plugin') { 28 | distProjectConfig = Object.assign({}, origProjectConfig, { 29 | miniprogramRoot: './', 30 | }) 31 | if ( 32 | ctx.initialConfig.outputRoot === 'weapp' || 33 | ctx.initialConfig.outputRoot === 'qq' || 34 | ctx.initialConfig.outputRoot === 'kwai' || 35 | ctx.initialConfig.outputRoot === 'swan' || 36 | ctx.initialConfig.outputRoot === 'tt' 37 | ) { 38 | const pkg = fs.readJSONSync( 39 | path.resolve(process.cwd(), './package.json'), 40 | ) 41 | distProjectConfig.appid = 42 | pkg.appId[ctx.initialConfig.outputRoot][process.env.API_ENV] 43 | if ( 44 | process.env.API_ENV === 'dev' || 45 | process.env.API_ENV === 'stable' 46 | ) { 47 | distProjectConfig.setting.urlCheck = false 48 | } 49 | } 50 | } 51 | ctx.writeFileToDist({ 52 | filePath: distConfigName, 53 | content: JSON.stringify(distProjectConfig, null, 2), 54 | }) 55 | printLog( 56 | processTypeEnum.GENERATE, 57 | '工具配置', 58 | `${outputPath}/${distConfigName}`, 59 | ) 60 | }, 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /src/trace.ts: -------------------------------------------------------------------------------- 1 | import { document } from '@tarojs/runtime' 2 | // import { request as TaroRequest } from '@tarojs/taro' 3 | import Trace, { 4 | EAppType, 5 | EAppSubType, 6 | EGcs /* utf8ToBytes */, 7 | } from '@antmjs/trace' 8 | import { cacheGetSync } from './cache' 9 | 10 | const { exposure, log, monitor } = Trace( 11 | { 12 | appId: '1', 13 | appType: process.env.TARO_ENV === 'h5' ? EAppType.browser : EAppType.mini, 14 | appSubType: 15 | process.env.TARO_ENV === 'h5' 16 | ? EAppSubType.browser 17 | : EAppSubType[process.env.TARO_ENV], 18 | // 应用内应用版本号 19 | appSubTypeVersion: process.env.DEPLOY_VERSION, 20 | // Taro3需要 21 | getElementById: document.getElementById, 22 | getUserId() { 23 | return new Promise((resolve) => { 24 | const userId = cacheGetSync('userId') 25 | resolve(userId || '') 26 | }) 27 | }, 28 | getGenderId() { 29 | return new Promise((resolve) => { 30 | resolve('') 31 | }) 32 | }, 33 | getLocation() { 34 | return new Promise((resolve) => { 35 | const location = cacheGetSync('location') 36 | resolve({ 37 | gcs: EGcs.gcj02, 38 | latitude: location?.latitude || '', 39 | longitude: location?.longitude || '', 40 | }) 41 | }) 42 | }, 43 | request(type /** log|monitor */, data) { 44 | data = (data as any[])?.filter( 45 | (item) => 46 | !/(redirectTo:fail)|(hideLoading:fail)|(cancel)/.test(item.d1), 47 | ) 48 | if (process.env.API_ENV === 'real' && data?.length > 0) { 49 | console.info(type, data) 50 | // const body = { 51 | // __topic__: '1', // appId 52 | // __logs__: data, 53 | // } 54 | // TaroRequest({ 55 | // url: 56 | // type === 'log' 57 | // ? 'https://logstorename.cn-hangzhou.log.aliyuncs.com/logstores/trace/track' 58 | // : 'https://logstorename.cn-hangzhou.log.aliyuncs.com/logstores/monitor/track', 59 | // method: 'POST', 60 | // header: { 61 | // 'x-log-apiversion': '0.6.0', 62 | // 'x-log-bodyrawsize': `${utf8ToBytes(JSON.stringify(body)).length}`, 63 | // }, 64 | // responseType: 'text', 65 | // dataType: '其他', 66 | // data: body, 67 | // timeout: 10000, 68 | // success() {}, 69 | // fail() {}, 70 | // }) 71 | } else { 72 | console.info(type, data) 73 | } 74 | }, 75 | }, 76 | // 默认为0。为0的话request返回的data是对象,非0的话返回数组 77 | { interval: 3000 }, 78 | ) 79 | 80 | export { exposure, log, monitor } 81 | -------------------------------------------------------------------------------- /src/components/container/navigation.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | import { View } from '@tarojs/components' 3 | import { useRecoilValue } from 'recoil' 4 | import { menuButtonStore } from '@/store' 5 | import './navigation.less' 6 | 7 | interface INavBarProps { 8 | title?: ReactNode 9 | navClassName?: string 10 | menuButton: any 11 | renderHeader?: ( 12 | navHeight: number, 13 | statusBarHeight: number, 14 | safeRight: number, 15 | ) => void 16 | } 17 | 18 | function NavBar(props: INavBarProps) { 19 | const { title, navClassName, renderHeader, menuButton } = props 20 | 21 | const navHeight = 22 | menuButton!.top + 23 | menuButton!.height + 24 | (menuButton!.top - menuButton!.statusBarHeight) 25 | const statusBarHeight = menuButton!.statusBarHeight 26 | const paddingLeftRight = menuButton!.width + menuButton!.marginRight * 2 27 | 28 | return ( 29 | <> 30 | 31 | <> 32 | 38 | 45 | 46 | {title} 47 | 48 | 49 | 50 | {renderHeader && 51 | renderHeader(navHeight, statusBarHeight, paddingLeftRight)} 52 | 53 | 54 | 55 | <> 56 | 62 | {renderHeader?.(navHeight, statusBarHeight, paddingLeftRight)} 63 | 64 | 65 | 66 | ) 67 | } 68 | 69 | type IProps = { 70 | navTitle?: ReactNode 71 | navClassName?: string 72 | renderHeader?: ( 73 | navHeight: number, 74 | statusBarHeight: number, 75 | safeRight: number, 76 | ) => void 77 | } 78 | 79 | export default function Index(props: IProps) { 80 | const { navTitle, navClassName, renderHeader } = props 81 | const menuButton: any = useRecoilValue(menuButtonStore) 82 | 83 | return ( 84 | <> 85 | {menuButton && ( 86 | 92 | )} 93 | 94 | ) 95 | } 96 | -------------------------------------------------------------------------------- /src/components/fullScreen/login/index.tsx: -------------------------------------------------------------------------------- 1 | import { View } from '@tarojs/components' 2 | import Taro, { hideLoading, showLoading, showToast } from '@tarojs/taro' 3 | import { MiniPhoneButton } from '@antmjs/vantui' 4 | import { useEffect, useState } from 'react' 5 | import { cacheSetSync } from '@/cache' 6 | import { loginCommon } from '@/actions/simple/common' 7 | import './index.less' 8 | interface IProps { 9 | onRefresh: () => void 10 | setError: React.Dispatch< 11 | | React.SetStateAction<{ 12 | code: string 13 | message: string 14 | data: any 15 | }> 16 | | undefined 17 | > 18 | } 19 | 20 | interface Params { 21 | jsCode: string 22 | iv: string 23 | userInfoEncryptedData: string 24 | } 25 | 26 | export default function Index(props: IProps) { 27 | const { onRefresh, setError } = props 28 | const [params, setParams] = useState({ 29 | jsCode: '', 30 | iv: '', 31 | userInfoEncryptedData: '', 32 | }) 33 | 34 | useEffect(() => { 35 | getCode() 36 | // eslint-disable-next-line react-hooks/exhaustive-deps 37 | }, []) 38 | 39 | const getCode = () => { 40 | Taro.login({ 41 | success: (res) => { 42 | if (res.code) { 43 | const jsCode = res.code 44 | const _params = { ...params, jsCode } 45 | setParams(_params) 46 | } 47 | }, 48 | }) 49 | } 50 | 51 | const handleLogin = async (_params: Params) => { 52 | showLoading({ 53 | title: '登录中...', 54 | }) 55 | const res = await loginCommon(_params) 56 | hideLoading() 57 | cacheSetSync('token', res.token) 58 | setError(undefined) 59 | onRefresh() 60 | } 61 | 62 | const onGetPhoneNumber = (res: any) => { 63 | const { iv, encryptedData } = res 64 | if (iv && encryptedData) { 65 | const _params = { ...params, iv, userInfoEncryptedData: encryptedData } 66 | setParams(_params) 67 | handleLogin(_params) 68 | } else { 69 | showToast({ title: '登陆失败,请重新登录', icon: 'none' }) 70 | getCode() 71 | } 72 | } 73 | 74 | const onGetPhoneNumberFail = () => { 75 | getCode() 76 | showToast({ title: '登陆失败,请重新登录', icon: 'none' }) 77 | } 78 | 79 | return ( 80 | 81 | { 88 | if (!/(deny)|(permission)/.test(res.errMsg)) { 89 | onGetPhoneNumber(res) 90 | } else { 91 | getCode() 92 | showToast({ title: '登陆失败,请重新登录', icon: 'none' }) 93 | } 94 | }} 95 | > 96 | 登录 97 | 98 | 99 | ) 100 | } 101 | -------------------------------------------------------------------------------- /src/components/container/leftBtns.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, useCallback } from 'react' 2 | import { navigateBack, reLaunch, getCurrentPages } from '@tarojs/taro' 3 | import { View } from '@tarojs/components' 4 | import { Icon } from '@antmjs/vantui' 5 | import { useRecoilValue } from 'recoil' 6 | import { menuButtonStore } from '@/store' 7 | import './leftBtns.less' 8 | 9 | interface IMenuButtonProps { 10 | menuButton: any 11 | homeUrl: string 12 | } 13 | 14 | function MenuButton(props: IMenuButtonProps) { 15 | const { menuButton, homeUrl } = props 16 | 17 | const handleGoBack = useCallback(() => { 18 | navigateBack({ 19 | delta: 1, 20 | }) 21 | }, []) 22 | 23 | const handleGoHome = useCallback(() => { 24 | reLaunch({ 25 | url: '/' + homeUrl, 26 | }) 27 | }, [homeUrl]) 28 | 29 | const [backButton, setBackButton] = useState(false) 30 | const [homeButton, setHomeButton] = useState(false) 31 | useEffect( 32 | function () { 33 | const pages = getCurrentPages() 34 | if (pages.length > 0) { 35 | const ins = pages[pages.length - 1] 36 | let url = ins?.route || ins?.['__route__'] 37 | if (pages.length > 1) { 38 | setBackButton(true) 39 | } 40 | if (url && url[0] === '/') { 41 | url = url.substring(1) 42 | } 43 | if (url?.split('?')[0] !== homeUrl) { 44 | setHomeButton(true) 45 | } 46 | } 47 | }, 48 | [homeUrl], 49 | ) 50 | 51 | return ( 52 | <> 53 | 63 | {backButton && ( 64 | 73 | 74 | 75 | )} 76 | {homeButton && ( 77 | 85 | 86 | 87 | )} 88 | 89 | 90 | ) 91 | } 92 | 93 | type IProps = { 94 | homeUrl: string 95 | } 96 | 97 | export default function Index(props: IProps) { 98 | const { homeUrl } = props 99 | const menuButton: any = useRecoilValue(menuButtonStore) 100 | 101 | return menuButton && process.env.TARO_ENV !== 'alipay' ? ( 102 | 103 | ) : ( 104 | <> 105 | ) 106 | } 107 | -------------------------------------------------------------------------------- /src/utils/request/index.ts: -------------------------------------------------------------------------------- 1 | import type { IPrefix, IPathName, IHref, TProxy } from './constants' 2 | import { hideLoading } from '@tarojs/taro' 3 | import { EMlf } from '@antmjs/trace' 4 | import { monitor } from '@/trace' 5 | import { cacheGetSync } from '@/cache' 6 | import DOMAIN from './constants' 7 | // 注意:下面三个方法的调用不需要处理reject的场景,内部对请求做了封装,统一抛出到resolve内 8 | import _request from './innerRequest' 9 | import _thirdRequest from './thirdRequest' 10 | // import _tencentUpload from './tencentUpload' 11 | 12 | function sendMonitor(option: any, res: any) { 13 | const params = { 14 | d1: option.url, 15 | d2: JSON.stringify(option), 16 | d3: res.status, 17 | d4: res.code, 18 | d5: JSON.stringify(res), 19 | } 20 | if (process.env.NODE_ENV === 'development') { 21 | console.error('development:requestCatch', option, res) 22 | } 23 | monitor(EMlf.api, params) 24 | } 25 | 26 | function url(option: Taro.request.Option) { 27 | const prefix = option.url.split('/')[1] as IPrefix 28 | const domain = DOMAIN[process.env.API_ENV][prefix] 29 | // 暂时先不支持缓存,优化的时候再处理吧 30 | option.url = 31 | domain + 32 | option.url + 33 | (option.url.indexOf('?') > -1 ? `&t=${+new Date()}` : `?t=${+new Date()}`) 34 | } 35 | 36 | function header(option: Taro.request.Option) { 37 | const header = { 38 | Accept: '*/*', 39 | 'Content-Type': 'application/json', 40 | 'X-M-VERSION': process.env.DEPLOY_VERSION, 41 | 'X-M-TYPE': process.env.TARO_ENV, 42 | 'x-m-app': 'custom', 43 | 'x-m-token': cacheGetSync('token') || '', 44 | } 45 | option.header = header 46 | } 47 | 48 | function request< 49 | T extends Omit, 50 | M extends TProxy, 51 | >( 52 | query: { 53 | [K in keyof T]: K extends 'url' ? IPathName : T[K] 54 | }, 55 | type?: M, 56 | ): Promise : any> { 57 | // warning: 直接内部帮你做了toast 58 | // info:直接把整个数据返回给请求的await结果 59 | url(query) 60 | header(query) 61 | return _request(query).then((res) => { 62 | if (res.code !== '200') { 63 | sendMonitor(query, res) 64 | } 65 | 66 | if (res.code === '200') { 67 | if (type === 'info') { 68 | return res 69 | } else { 70 | return res.data 71 | } 72 | } else { 73 | if (type === 'info') { 74 | return res 75 | } else { 76 | try { 77 | hideLoading() 78 | } catch {} 79 | throw res 80 | } 81 | } 82 | }) 83 | } 84 | 85 | // 只处理response.data为json的情况,其他返回都属于异常 86 | // 自动化使用的方法 87 | export function createFetch< 88 | REQ extends Record, 89 | RES extends Record, 90 | >(url: any, method: any) { 91 | return ( 92 | data: REQ, 93 | type?: T, 94 | ): Promise< 95 | T extends 'info' ? CreateFetchResponse : RES['data'] 96 | > => { 97 | return request( 98 | { 99 | url, 100 | method, 101 | data: data, 102 | }, 103 | type, 104 | ) 105 | } 106 | } 107 | 108 | export function thirdRequest< 109 | T extends Omit, 110 | >(query: { 111 | [K in keyof T]: K extends 'url' ? IHref : T[K] 112 | }) { 113 | return _thirdRequest(query).then((res) => { 114 | if (res.code !== '200') { 115 | sendMonitor(query, res) 116 | } 117 | return res 118 | }) 119 | } 120 | 121 | // 暂时只支持微信小程序环境 122 | // export function tencentUpload(filePath: any, filename: string, index?: number) { 123 | // return _tencentUpload(filePath, filename, index).then((res) => { 124 | // if (res.code !== '200') { 125 | // sendMonitor(filePath + '_' + filename, res) 126 | // } 127 | // return res 128 | // }) 129 | // } 130 | -------------------------------------------------------------------------------- /src/pages/tabAndSearchPagination/index.tsx: -------------------------------------------------------------------------------- 1 | import { Search, Tabs, Tab } from '@antmjs/vantui' 2 | import { Unite } from '@antmjs/unite' 3 | import { View } from '@tarojs/components' 4 | import { useReachBottom } from '@tarojs/taro' 5 | import Container from '@/components/container' 6 | import Pagination from '@/components/pagination' 7 | import { getRoleListCommon } from '@/actions/simple/common' 8 | import './index.less' 9 | 10 | const PAGE_SIZE = 20 11 | 12 | export default Unite( 13 | { 14 | state: { 15 | list: null, 16 | searchValue: '', 17 | tabs: [ 18 | { id: 0, name: '卡片1' }, 19 | { id: 1, name: '卡片2' }, 20 | { id: 2, name: '卡片3' }, 21 | { id: 3, name: '卡片4' }, 22 | { id: 4, name: '卡片5' }, 23 | { id: 5, name: '卡片6' }, 24 | { id: 6, name: '卡片7' }, 25 | { id: 7, name: '卡片8' }, 26 | ], 27 | activeTabIndex: 0, 28 | complete: false, 29 | }, 30 | loaded: false, 31 | async onLoad() { 32 | const inTab: string | number = 33 | this.location.params['tab'] ?? this.state.activeTabIndex 34 | if (!this.loaded) { 35 | this.loaded = true 36 | this.setState({ activeTabIndex: Number(inTab) }) 37 | await this.loadList(true, { tabId: this.state.tabs[Number(inTab)]!.id }) 38 | } else { 39 | await this.loadList(true) 40 | } 41 | }, 42 | async loadList(refresh = false, params?: Record) { 43 | if (refresh) this.setState({ list: null }) 44 | const list = await getRoleListCommon({ 45 | searchValue: this.state.searchValue, 46 | tabId: this.state.tabs[this.state.activeTabIndex]?.id, 47 | pageSize: PAGE_SIZE, 48 | offset: refresh ? 0 : this.state.list.length, 49 | ...(params || {}), 50 | }) 51 | this.setState({ 52 | list: refresh ? list : [].concat(this.state.list).concat(list as any), 53 | complete: list.length < PAGE_SIZE ? true : false, 54 | }) 55 | }, 56 | onChangeSearch(e: any) { 57 | this.setState({ 58 | searchValue: e.detail, 59 | }) 60 | }, 61 | onChangeTab(e: any) { 62 | this.setState({ activeTabIndex: e.detail.index }) 63 | this.loadList(true, { 64 | tabId: this.state.tabs[e.detail.index]?.id, 65 | }) 66 | }, 67 | onSearch() { 68 | this.loadList(true) 69 | }, 70 | }, 71 | function ({ state, events, loading }) { 72 | const { list, complete, searchValue, activeTabIndex, tabs } = state 73 | const { loadList, onSearch, onChangeSearch, onChangeTab } = events 74 | useReachBottom(() => { 75 | if (!loading.loadList && !complete) { 76 | loadList() 77 | } 78 | }) 79 | return ( 80 | { 86 | return ( 87 | <> 88 | 搜索} 95 | /> 96 | 97 | {tabs.map((item) => { 98 | return 99 | })} 100 | 101 | 102 | ) 103 | }} 104 | > 105 | 106 | {list?.map((item: any, index: number) => { 107 | return ( 108 | 109 | {item.name} 110 | 111 | ) 112 | })} 113 | 114 | 115 | ) 116 | }, 117 | { page: true }, 118 | ) 119 | 120 | definePageConfig({ 121 | // 这里不要设置标题,在Container组件上面设置 122 | navigationBarTitleText: '', 123 | }) 124 | -------------------------------------------------------------------------------- /src/components/container/pullDownRefresh.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | 3 | import { ReactNode, useRef, useState } from 'react' 4 | import { showToast, usePageScroll } from '@tarojs/taro' 5 | import { View } from '@tarojs/components' 6 | import { animated, useSpring } from '@react-spring/web' 7 | import { sleep, rubberbandIfOutOfBounds } from '@/utils' 8 | import './pullDownRefresh.less' 9 | 10 | export type PullStatus = 'pulling' | 'canRelease' | 'refreshing' | 'complete' 11 | 12 | export type PullToRefreshProps = { 13 | children: ReactNode 14 | threshold?: number 15 | statusBarHeight: number 16 | onRefresh: ( 17 | catchRefresh?: T, 18 | ) => T extends true 19 | ? Promise<{ code: string; message: string; data?: any }> 20 | : void 21 | } 22 | 23 | function PullDownRefresh( 24 | props: PullToRefreshProps & { 25 | setStatus: any 26 | api: any 27 | status: any 28 | canPull: boolean 29 | }, 30 | ) { 31 | const setStatus = props.setStatus 32 | const status = props.status 33 | const api = props.api 34 | const headHeight = 40 35 | const completeDelay = 500 36 | const threshold = props.threshold ?? 60 37 | const pullingRef = useRef(false) 38 | const yRef = useRef(0) 39 | 40 | async function doRefresh() { 41 | api.start({ transform: `translateX(-50%) scale(1)`, opacity: 1 }) 42 | setStatus('refreshing') 43 | try { 44 | const res = await props.onRefresh(true) 45 | if (res.code !== '200') { 46 | showToast({ 47 | title: res.message, 48 | icon: 'none', 49 | }) 50 | } 51 | setStatus('complete') 52 | } catch {} 53 | if (completeDelay > 0) { 54 | await sleep(completeDelay) 55 | } 56 | api.start({ 57 | to: async (next: any) => { 58 | return next({ transform: `translateX(-50%) scale(0)`, opacity: 0 }) 59 | .then(() => { 60 | setStatus('pulling') 61 | }) 62 | .catch(() => { 63 | setStatus('pulling') 64 | }) 65 | }, 66 | }) 67 | } 68 | 69 | const common = function (state: any) { 70 | if (status === 'refreshing' || status === 'complete') return 71 | 72 | const { event } = state 73 | const y = event.pageY - yRef.current 74 | 75 | if (state.last) { 76 | pullingRef.current = false 77 | if (status === 'canRelease') { 78 | doRefresh() 79 | } else { 80 | api.start({ transform: `translateX(-50%) scale(0)`, opacity: 0 }) 81 | } 82 | return 83 | } 84 | 85 | if (state.first && y > 0) { 86 | pullingRef.current = true 87 | } 88 | 89 | if (!pullingRef.current) return 90 | 91 | // event?.preventDefault?.() 92 | // event?.stopPropagation?.() 93 | const height = 94 | Math.max(rubberbandIfOutOfBounds(y, 0, 0, headHeight * 5, 0.5), 0) / 1.1 95 | const rate = height / threshold 96 | api.start({ 97 | transform: `translateX(-50%) scale(${rate > 1 ? 1 : rate})`, 98 | opacity: rate > 1 ? 1 : rate, 99 | }) 100 | setStatus(height > threshold ? 'canRelease' : 'pulling') 101 | } 102 | 103 | const onStart = function (e: any) { 104 | if (props.canPull) { 105 | yRef.current = e.changedTouches[0].pageY 106 | common({ 107 | first: true, 108 | last: false, 109 | event: e.changedTouches[0], 110 | }) 111 | } 112 | } 113 | const onMove = function (e: any) { 114 | if (props.canPull) { 115 | // e?.preventDefault?.() 116 | // e?.stopPropagation?.() 117 | common({ 118 | first: true, 119 | last: false, 120 | event: e.changedTouches[0], 121 | }) 122 | } 123 | } 124 | const onEnd = function (e: any) { 125 | if (props.canPull) { 126 | common({ 127 | first: false, 128 | last: true, 129 | event: e.changedTouches[0], 130 | }) 131 | yRef.current = 0 132 | } 133 | } 134 | return ( 135 | 136 | {props.children} 137 | 138 | ) 139 | } 140 | 141 | export default function Index(props: PullToRefreshProps) { 142 | const [canPull, setCanPull] = useState(true) 143 | const [springStyles, api] = useSpring(() => ({ 144 | from: { transform: `translateX(-50%) scale(0)`, opacity: 0 }, 145 | config: { 146 | tension: 300, 147 | friction: 30, 148 | clamp: true, 149 | }, 150 | })) 151 | const [pullDownRefreshStatus, setPullDownRefreshStatus] = useState( 152 | 'pulling', 153 | ) as [ 154 | 'pulling' | 'refreshing' | 'complete' | 'canRelease', 155 | React.Dispatch< 156 | React.SetStateAction<'pulling' | 'refreshing' | 'complete' | 'canRelease'> 157 | >, 158 | ] 159 | 160 | usePageScroll((e) => { 161 | if (e.scrollTop > 0 && canPull) { 162 | setCanPull(false) 163 | } 164 | if (e.scrollTop <= 0 && !canPull) { 165 | setCanPull(true) 166 | } 167 | }) 168 | const renderStatusText = (): any => { 169 | if (pullDownRefreshStatus === 'pulling') return '下拉刷新' 170 | if (pullDownRefreshStatus === 'canRelease') return '释放立即刷新' 171 | if (pullDownRefreshStatus === 'refreshing') 172 | return 173 | if (pullDownRefreshStatus === 'complete') return '刷新成功' 174 | } 175 | const NView = animated(View) 176 | return ( 177 | <> 178 | 185 | 192 | {renderStatusText()} 193 | 194 | 195 | ) 196 | } 197 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const npath = require('path') 3 | const pkg = require('../package.json') 4 | const miniChain = require('./webpack/miniChain') 5 | const h5Chain = require('./webpack/h5Chain') 6 | 7 | process.env.TARO_ENV = process.env.TARO_ENV ?? 'weapp' 8 | process.env.NODE_ENV = process.env.NODE_ENV ?? 'production' 9 | process.env.API_ENV = process.env.API_ENV ?? 'real' 10 | 11 | function getVersion() { 12 | function fillZero(value) { 13 | return value < 10 ? `0${value}` : `${value}` 14 | } 15 | const date = new Date() 16 | 17 | return `${date.getFullYear()}${fillZero(date.getMonth() + 1)}${fillZero( 18 | date.getDate(), 19 | )}${fillZero(date.getHours())}${fillZero(date.getMinutes())}${fillZero( 20 | date.getSeconds(), 21 | )}` 22 | } 23 | 24 | const version = process.env.VERSION || getVersion() 25 | console.log('TaroEnv: ', process.env.TARO_ENV) 26 | console.log('NodeEnv: ', process.env.NODE_ENV) 27 | console.log('ApiEnv: ', process.env.API_ENV) 28 | console.log('Version: ', version) 29 | 30 | const config = { 31 | projectName: pkg.name, 32 | date: '2022-3-14', 33 | designWidth: 750, 34 | deviceRatio: { 35 | 640: 2.34 / 2, 36 | 750: 1, 37 | 828: 1.81 / 2, 38 | }, 39 | sourceRoot: 'src', 40 | outputRoot: process.env.TARO_ENV === 'h5' ? 'build' : process.env.TARO_ENV, 41 | env: { 42 | TARO_ENV: JSON.stringify(process.env.TARO_ENV), 43 | API_ENV: JSON.stringify(process.env.API_ENV), 44 | WATCHING: JSON.stringify(process.env.WATCHING || 'false'), 45 | DEPLOY_VERSION: JSON.stringify(version), 46 | }, 47 | alias: { 48 | '@': npath.resolve(process.cwd(), 'src'), 49 | }, 50 | defineConstants: {}, 51 | copy: { 52 | patterns: [], 53 | options: {}, 54 | }, 55 | compiler: 'webpack5', 56 | framework: 'react', 57 | cache: { 58 | enable: false, // Webpack 持久化缓存配置,建议开启。默认配置请参考:https://docs.taro.zone/docs/config-detail#cache 59 | }, 60 | mini: { 61 | webpackChain(chain) { 62 | miniChain(chain) 63 | }, 64 | lessLoaderOption: { 65 | lessOptions: { 66 | modifyVars: { 67 | hack: `true; @import "${npath.join( 68 | process.cwd(), 69 | 'src/styles/index.less', 70 | )}";`, 71 | }, 72 | }, 73 | // 适用于全局引入样式 74 | // additionalData: "@import '~/src/styles/index.less';", 75 | }, 76 | postcss: { 77 | autoprefixer: { 78 | enable: true, 79 | config: { 80 | // autoprefixer 配置项 81 | }, 82 | }, 83 | pxtransform: { 84 | enable: true, 85 | config: {}, 86 | }, 87 | cssModules: { 88 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 89 | config: { 90 | namingPattern: 'module', // 转换模式,取值为 global/module 91 | generateScopedName: '[name]__[local]___[hash:base64:5]', 92 | }, 93 | }, 94 | }, 95 | miniCssExtractPluginOption: { 96 | ignoreOrder: true, 97 | }, 98 | }, 99 | h5: { 100 | webpackChain(chain) { 101 | const publicPath = process.env.PUBLIC_PATH || '/' 102 | h5Chain(chain) 103 | if (process.env.NODE_ENV === 'production') { 104 | chain.mode('production') 105 | chain.performance.maxEntrypointSize(1000000).maxAssetSize(512000) 106 | chain.devtool('hidden-source-map') 107 | chain.output 108 | .path(npath.resolve('./build')) 109 | .filename('assets/js/[name]_[contenthash].js') 110 | .chunkFilename('assets/js/chunk/[name]_[contenthash].js') 111 | .publicPath(publicPath.replace('VERSION', version)) 112 | } else { 113 | chain.mode('development') 114 | chain.devtool('eval-cheap-module-source-map') 115 | chain.output 116 | .path(npath.resolve('./build')) 117 | .filename('assets/js/[name]_[contenthash].js') 118 | .chunkFilename('assets/js/chunk/[name]_[contenthash].js') 119 | .publicPath(publicPath.replace('VERSION', version)) 120 | } 121 | if (process.env.WATCHING === 'true') { 122 | chain.output.publicPath(`/`) 123 | } 124 | }, 125 | esnextModules: [/@antmjs[\\/]vantui/], 126 | lessLoaderOption: { 127 | lessOptions: { 128 | modifyVars: { 129 | // 或者可以通过 less 文件覆盖(文件路径为绝对路径) 130 | hack: `true; @import "${npath.join( 131 | process.cwd(), 132 | 'src/styles/index.less', 133 | )}";`, 134 | }, 135 | }, 136 | }, 137 | router: { 138 | mode: 'browser', 139 | }, 140 | devServer: { 141 | port: 10086, 142 | hot: false, 143 | host: '0.0.0.0', 144 | historyApiFallback: true, 145 | headers: { 146 | 'Access-Control-Allow-Origin': '*', // 表示允许跨域 147 | }, 148 | }, 149 | postcss: { 150 | autoprefixer: { 151 | enable: true, 152 | config: {}, 153 | }, 154 | pxtransform: { 155 | enable: true, 156 | config: { 157 | onePxTransform: false, 158 | }, 159 | }, 160 | cssModules: { 161 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 162 | config: { 163 | namingPattern: 'module', // 转换模式,取值为 global/module 164 | generateScopedName: '[name]__[local]___[hash:base64:5]', 165 | }, 166 | }, 167 | }, 168 | miniCssExtractPluginOption: { 169 | ignoreOrder: false, 170 | filename: 'assets/css/[name]_[contenthash].css', 171 | chunkFilename: 'assets/css/chunk/[name]_[contenthash].css', 172 | }, 173 | }, 174 | plugins: [ 175 | ['@tarojs/plugin-framework-react', { reactMode: 'concurrent' }], 176 | [npath.join(process.cwd(), 'config/webpack/configPlugin')], 177 | '@tarojs/plugin-platform-alipay-dd', 178 | ['@tarojs/plugin-platform-kwai'], 179 | ], 180 | } 181 | 182 | module.exports = function (merge) { 183 | return merge({}, config, require(`./${process.env.NODE_ENV}`)) 184 | } 185 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { getSystemInfo, getMenuButtonBoundingClientRect } from '@tarojs/taro' 2 | import { SetterOrUpdater } from 'recoil' 3 | import { cacheGet, cacheSet } from '@/cache' 4 | import { IMenuButton } from '@/store' 5 | 6 | function _setMenuButton(sysInfo: any, setStore: SetterOrUpdater) { 7 | try { 8 | let menuButton: any 9 | if (process.env.TARO_ENV === 'h5') { 10 | menuButton = { 11 | bottom: 36, 12 | height: 32, 13 | left: document.body.clientWidth - 7 - 87, 14 | right: document.body.clientWidth - 7, 15 | top: 4, 16 | width: 87, 17 | } 18 | } else { 19 | menuButton = getMenuButtonBoundingClientRect() 20 | } 21 | if (menuButton) { 22 | if (sysInfo) { 23 | setStore({ 24 | precise: true, 25 | bottom: menuButton.bottom, 26 | height: menuButton.height, 27 | width: menuButton.width, 28 | left: menuButton.left, 29 | right: menuButton.right, 30 | marginRight: sysInfo.screenWidth - menuButton.right, 31 | top: menuButton.top, 32 | statusBarHeight: sysInfo.statusBarHeight ?? menuButton.top - 4, 33 | }) 34 | cacheSet({ 35 | key: 'menuButton', 36 | data: { 37 | ...menuButton, 38 | marginRight: sysInfo.screenWidth - menuButton.right, 39 | }, 40 | }) 41 | } else { 42 | setStore({ 43 | precise: false, 44 | bottom: menuButton.bottom, 45 | height: menuButton.height, 46 | width: menuButton.width, 47 | left: menuButton.left, 48 | right: menuButton.right, 49 | marginRight: 7, 50 | top: menuButton.top, 51 | statusBarHeight: menuButton.top - 4, 52 | }) 53 | cacheSet({ 54 | key: 'menuButton', 55 | data: menuButton, 56 | }) 57 | } 58 | } else { 59 | setStore({ 60 | precise: false, 61 | bottom: 80, 62 | height: 32, 63 | width: 87, 64 | left: 281, 65 | right: 368, 66 | marginRight: 7, 67 | top: 48, 68 | statusBarHeight: sysInfo?.statusBarHeight ?? 48 - 4, 69 | }) 70 | } 71 | } catch (error) { 72 | setStore({ 73 | precise: false, 74 | bottom: 80, 75 | height: 32, 76 | width: 87, 77 | left: 281, 78 | right: 368, 79 | marginRight: 7, 80 | top: 48, 81 | statusBarHeight: sysInfo?.statusBarHeight ?? 48 - 4, 82 | }) 83 | } 84 | } 85 | 86 | function _setSysInfo( 87 | menuButton: any, 88 | setStore: SetterOrUpdater, 89 | setMenuButton?: any, 90 | ) { 91 | getSystemInfo({ 92 | success(sysInfo) { 93 | if (process.env.TARO_ENV === 'h5') { 94 | sysInfo.statusBarHeight = 0 95 | } 96 | if (menuButton) { 97 | setStore({ 98 | precise: true, 99 | bottom: menuButton.bottom, 100 | height: menuButton.height, 101 | width: menuButton.width, 102 | left: menuButton.left, 103 | right: menuButton.right, 104 | marginRight: sysInfo.screenWidth - menuButton.right, 105 | top: menuButton.top, 106 | statusBarHeight: sysInfo.statusBarHeight ?? menuButton.top - 4, 107 | }) 108 | } else { 109 | setMenuButton(sysInfo, setStore) 110 | } 111 | cacheSet({ 112 | key: 'sysInfo', 113 | data: sysInfo, 114 | }) 115 | }, 116 | fail() { 117 | if (menuButton) { 118 | setStore({ 119 | precise: false, 120 | bottom: menuButton.bottom, 121 | height: menuButton.height, 122 | width: menuButton.width, 123 | left: menuButton.left, 124 | right: menuButton.right, 125 | marginRight: 7, 126 | top: menuButton.top, 127 | statusBarHeight: 128 | process.env.TARO_ENV === 'h5' ? 0 : menuButton.top - 4, 129 | }) 130 | } else { 131 | setMenuButton(null, setStore) 132 | } 133 | }, 134 | }) 135 | } 136 | 137 | export function setMenuButtonAsync(setStore: SetterOrUpdater) { 138 | cacheGet({ key: 'menuButton' }).then((mb) => { 139 | cacheGet({ key: 'sysInfo' }).then((si) => { 140 | if (mb && si) { 141 | setStore({ 142 | precise: true, 143 | bottom: mb.bottom, 144 | height: mb.height, 145 | width: mb.width, 146 | left: mb.left, 147 | right: mb.right, 148 | marginRight: mb.marginRight ?? si.screenWidth - mb.right, 149 | top: mb.top, 150 | statusBarHeight: si.statusBarHeight ?? mb.top - 4, 151 | }) 152 | } else if (mb) { 153 | _setSysInfo(mb, setStore) 154 | } else if (si) { 155 | _setMenuButton(si, setStore) 156 | } else { 157 | _setSysInfo(null, setStore, _setMenuButton) 158 | } 159 | }) 160 | }) 161 | } 162 | 163 | export function setSysInfoAsync(force = false) { 164 | if (force) { 165 | getSystemInfo({ 166 | success(sysInfo) { 167 | cacheSet({ 168 | key: 'sysInfo', 169 | data: sysInfo, 170 | }) 171 | }, 172 | }) 173 | } else { 174 | cacheGet({ key: 'sysInfo' }).then((si) => { 175 | if (!si) { 176 | getSystemInfo({ 177 | success(sysInfo) { 178 | cacheSet({ 179 | key: 'sysInfo', 180 | data: sysInfo, 181 | }) 182 | }, 183 | }) 184 | } 185 | }) 186 | } 187 | } 188 | 189 | export function randomNum(min: number, max: number) { 190 | return Math.floor(Math.random() * (max + 1 - min) + min) 191 | } 192 | 193 | export function bound( 194 | position: number, 195 | min: number | undefined, 196 | max: number | undefined, 197 | ) { 198 | let ret = position 199 | if (min !== undefined) { 200 | ret = Math.max(position, min) 201 | } 202 | if (max !== undefined) { 203 | ret = Math.min(ret, max) 204 | } 205 | return ret 206 | } 207 | 208 | export function rubberband( 209 | distance: number, 210 | dimension: number, 211 | constant: number, 212 | ) { 213 | return (distance * dimension * constant) / (dimension + constant * distance) 214 | } 215 | 216 | export function rubberbandIfOutOfBounds( 217 | position: number, 218 | min: number, 219 | max: number, 220 | dimension: number, 221 | constant = 0.15, 222 | ) { 223 | if (constant === 0) return bound(position, min, max) 224 | if (position < min) 225 | return -rubberband(min - position, dimension, constant) + min 226 | if (position > max) 227 | return +rubberband(position - max, dimension, constant) + max 228 | return position 229 | } 230 | 231 | export const sleep = (time: number) => 232 | new Promise((resolve) => setTimeout(resolve, time)) 233 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "temptaro", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "", 6 | "engines": { 7 | "node": ">=16", 8 | "npm": ">=6.4", 9 | "yarn": ">=1.22" 10 | }, 11 | "appId": { 12 | "weapp": { 13 | "real": "", 14 | "pre": "", 15 | "dev": "" 16 | }, 17 | "tt": { 18 | "real": "", 19 | "pre": "", 20 | "dev": "" 21 | }, 22 | "kwai": { 23 | "real": "", 24 | "pre": "", 25 | "dev": "" 26 | }, 27 | "swan": { 28 | "real": "", 29 | "pre": "", 30 | "dev": "" 31 | } 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | "ios >= 9" 36 | ], 37 | "development": [ 38 | "last 1 version" 39 | ] 40 | }, 41 | "commitlint": { 42 | "extends": [ 43 | "@commitlint/config-conventional" 44 | ], 45 | "rules": { 46 | "type-enum": [ 47 | 2, 48 | "always", 49 | [ 50 | "feat", 51 | "fix", 52 | "style", 53 | "chore", 54 | "typings", 55 | "docs", 56 | "refactor", 57 | "test" 58 | ] 59 | ] 60 | } 61 | }, 62 | "config": { 63 | "commitizen": { 64 | "path": "node_modules/cz-conventional-changelog", 65 | "types": { 66 | "feat": { 67 | "description": "新功能(feature)" 68 | }, 69 | "fix": { 70 | "description": "修补bug" 71 | }, 72 | "style": { 73 | "description": "格式(不影响代码运行的变动)" 74 | }, 75 | "chore": { 76 | "description": "构建过程或辅助工具的变动" 77 | }, 78 | "typings": { 79 | "description": "Typescript 类型错误" 80 | }, 81 | "docs": { 82 | "description": "文档(documentation)" 83 | }, 84 | "refactor": { 85 | "description": "重构(既不是新增功能,也不是修改bug的代码变动)" 86 | }, 87 | "test": { 88 | "description": "增加或修改测试用例" 89 | } 90 | } 91 | } 92 | }, 93 | "scripts": { 94 | "eslint": "npx eslint -c .eslintrc.js '**/*.{js,jsx,ts,tsx}' && npx tsc -p tsconfig.json --skipLibCheck", 95 | "stylelint": "npx stylelint --aei --config stylelint.config.js '**/*.{css,less}'", 96 | "prettier": "npx prettier --write '**/*.{js,jsx,ts,tsx,md,html,css,less}'", 97 | "iconfont": "npx antm-icon --input-path https://at.alicdn.com/t/xxx.css --output-path src/iconfont.less", 98 | "watch:weapp": "cross-env NODE_ENV=development API_ENV=dev WATCHING=true npx taro build --type weapp --watch", 99 | "watch:alipay": "cross-env NODE_ENV=development API_ENV=dev WATCHING=true npx taro build --type alipay --watch", 100 | "watch:swan": "cross-env NODE_ENV=development API_ENV=dev WATCHING=true npx taro build --type swan --watch", 101 | "watch:h5": "cross-env NODE_ENV=development API_ENV=dev WATCHING=true npx taro build --type h5 --watch", 102 | "watch:tt": "cross-env NODE_ENV=development API_ENV=dev WATCHING=true npx taro build --type tt --watch", 103 | "watch:kwai": "cross-env NODE_ENV=development API_ENV=dev WATCHING=true npx taro build --type kwai --watch", 104 | "dev:weapp": "cross-env NODE_ENV=production API_ENV=dev npx taro build --type weapp", 105 | "dev:alipay": "cross-env NODE_ENV=production API_ENV=dev npx taro build --type alipay", 106 | "dev:swan": "cross-env NODE_ENV=production API_ENV=dev npx taro build --type swan", 107 | "dev:h5": "cross-env NODE_ENV=production API_ENV=dev npx taro build --type h5", 108 | "dev:tt": "cross-env NODE_ENV=production API_ENV=dev npx taro build --type tt", 109 | "dev:kwai": "cross-env NODE_ENV=production API_ENV=dev npx taro build --type kwai", 110 | "real:weapp": "cross-env NODE_ENV=production API_ENV=real npx taro build --type weapp", 111 | "real:alipay": "cross-env NODE_ENV=production API_ENV=real npx taro build --type alipay", 112 | "real:swan": "cross-env NODE_ENV=production API_ENV=real npx taro build --type swan", 113 | "real:h5": "cross-env NODE_ENV=production API_ENV=real npx taro build --type h5", 114 | "real:tt": "cross-env NODE_ENV=production API_ENV=real npx taro build --type tt", 115 | "real:kwai": "cross-env NODE_ENV=production API_ENV=real npx taro build --type kwai", 116 | "build": "cross-env NODE_ENV=production npx taro build", 117 | "prepare": "npx husky install", 118 | "api:watch": "antm-api watch --path ./src/actions/rapper/types --server true --mock true --action true", 119 | "api:build": "antm-api build --path ./src/actions/rapper/types", 120 | "rapper": "antm-api file --path ./src/actions/rapper/types --action true", 121 | "swagger": "antm-api swagger --path ./src/actions/swagger/types --url https://petstore.swagger.io/v2/swagger.json" 122 | }, 123 | "author": "", 124 | "dependencies": { 125 | "@antmjs/cache": "^2.3.21", 126 | "@antmjs/mini-fix": "^2.3.21", 127 | "@antmjs/trace": "^2.3.21", 128 | "@antmjs/unite": "^2.3.21", 129 | "@antmjs/utils": "^2.3.21", 130 | "@antmjs/vantui": "^3.1.6", 131 | "@babel/runtime": "^7.7.7", 132 | "@react-spring/web": "^9.5.2", 133 | "@tarojs/components": "3.6.14", 134 | "@tarojs/helper": "3.6.14", 135 | "@tarojs/plugin-framework-react": "3.6.14", 136 | "@tarojs/plugin-platform-alipay": "3.6.14", 137 | "@tarojs/plugin-platform-alipay-dd": "^0.1.3", 138 | "@tarojs/plugin-platform-h5": "3.6.14", 139 | "@tarojs/plugin-platform-jd": "3.6.14", 140 | "@tarojs/plugin-platform-kwai": "^2.0.0", 141 | "@tarojs/plugin-platform-qq": "3.6.14", 142 | "@tarojs/plugin-platform-swan": "3.6.14", 143 | "@tarojs/plugin-platform-tt": "3.6.14", 144 | "@tarojs/plugin-platform-weapp": "3.6.14", 145 | "@tarojs/react": "3.6.14", 146 | "@tarojs/router": "3.6.14", 147 | "@tarojs/runtime": "3.6.14", 148 | "@tarojs/shared": "3.6.14", 149 | "@tarojs/taro": "3.6.14", 150 | "@tarojs/taro-h5": "3.6.14", 151 | "@vant/area-data": "^1.3.1", 152 | "cos-wx-sdk-v5": "^1.2.1", 153 | "lodash": "^4.17.21", 154 | "react": "^18.2.0", 155 | "react-dom": "^18.2.0", 156 | "recoil": "^0.7.4" 157 | }, 158 | "devDependencies": { 159 | "@antmjs/api": "^2.3.21", 160 | "@antmjs/eslint": "^2.3.21", 161 | "@antmjs/iconfont": "^2.3.21", 162 | "@antmjs/plugin-global-fix": "^2.3.21", 163 | "@antmjs/plugin-h5-fix": "^2.3.21", 164 | "@antmjs/plugin-mini-fix": "^2.3.21", 165 | "@antmjs/stylelint": "^2.3.21", 166 | "@babel/core": "^7.12.9", 167 | "@commitlint/cli": "^12.1.4", 168 | "@commitlint/config-conventional": "^12.1.4", 169 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.5", 170 | "@tarojs/cli": "3.6.14", 171 | "@tarojs/webpack5-runner": "3.6.14", 172 | "@types/react": "^18.0.26", 173 | "@types/react-dom": "^18.0.10", 174 | "@types/webpack-env": "^1.13.6", 175 | "babel-plugin-import": "^1.13.5", 176 | "babel-plugin-lodash": "^3.3.4", 177 | "babel-preset-taro": "3.6.14", 178 | "commitizen": "^4.2.4", 179 | "cross-env": "^7.0.3", 180 | "cz-conventional-changelog": "^3.3.0", 181 | "eslint": "^8.12.0", 182 | "husky": "^7.0.1", 183 | "lint-staged": "^11.0.1", 184 | "postcss": "^8.4.18", 185 | "prettier": "^2.8.1", 186 | "react-refresh": "^0.11.0", 187 | "stylelint": "^14.16.1", 188 | "typescript": "^4.9.4", 189 | "webpack": "5.69.0" 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/pages/index/index.tsx: -------------------------------------------------------------------------------- 1 | import type { ActionSheetItem } from '@antmjs/vantui/types/action-sheet' 2 | import { useRecoilState } from 'recoil' 3 | import { Button, ActionSheet, CellGroup, Field } from '@antmjs/vantui' 4 | import { Unite } from '@antmjs/unite' 5 | import { View } from '@tarojs/components' 6 | import { useReachBottom, showModal, navigateTo } from '@tarojs/taro' 7 | import Container from '@/components/container' 8 | import DatetimePicker from '@/components/datetimePicker' 9 | import Picker from '@/components/picker' 10 | import Popup from '@/components/popup' 11 | import Area from '@/components/area' 12 | import { getRoleListCommon } from '@/actions/simple/common' 13 | import { menuButtonStore } from '@/store' 14 | import './index.less' 15 | 16 | export default Unite( 17 | { 18 | state: { 19 | info: null, 20 | popShow: false, 21 | pickerShow: false, 22 | datepickerShow: false, 23 | areaShow: false, 24 | actionsheetShow: false, 25 | actions: [ 26 | { 27 | name: '选项', 28 | }, 29 | { 30 | name: '选项', 31 | }, 32 | { 33 | name: '选项', 34 | subname: '描述信息', 35 | openType: 'share', 36 | }, 37 | ], 38 | }, 39 | async onShow() { 40 | console.log(111) 41 | }, 42 | async onHide() { 43 | console.log(222) 44 | }, 45 | async onLoad() { 46 | // const datap = await petClient.addPet({ 47 | // body: { name: 'xx', photoUrls: ['xxx'] }, 48 | // }) 49 | const data = await getRoleListCommon({}) 50 | this.setState({ 51 | info: data, 52 | }) 53 | }, 54 | }, 55 | function ({ state, events }) { 56 | useReachBottom(() => { 57 | console.log(999) 58 | }) 59 | const { setHooks, setState } = events 60 | const { 61 | info, 62 | popShow, 63 | pickerShow, 64 | datepickerShow, 65 | areaShow, 66 | actionsheetShow, 67 | actions, 68 | } = state 69 | const [menuButton, setMenuButton]: any = useRecoilState(menuButtonStore) 70 | // 可以将hooks的数据传递到实例上面,可以通过this.hooks['xxx']获取到,不过hooks是异步的,所以在不同的阶段取值有可能取不到,这是由业务决定的 71 | setHooks({ 72 | xxx: menuButton, 73 | yyy: setMenuButton, 74 | }) 75 | return ( 76 | { 82 | return ( 83 | 84 | 固定在顶部的内容 85 | 86 | ) 87 | }} 88 | > 89 | 90 | 97 | 104 | 105 | {info && ( 106 | <> 107 | 108 | 115 | 116 | 117 | 126 | 127 | 128 | 135 | 136 | 137 | 144 | 145 | 146 | 153 | 154 | 155 | 162 | 163 | 164 | 171 | 172 | 173 | 187 | 188 | 189 | )} 190 | 191 | { 195 | setState({ popShow: false }) 196 | }} 197 | > 198 | Hello world!1112 199 | Hello world!1112 200 | 201 | { 204 | console.log(e) 205 | setState({ datepickerShow: false }) 206 | }} 207 | onCancel={() => { 208 | setState({ datepickerShow: false }) 209 | }} 210 | /> 211 | { 215 | console.log(e) 216 | setState({ pickerShow: false }) 217 | }} 218 | onCancel={() => { 219 | setState({ pickerShow: false }) 220 | }} 221 | /> 222 | { 225 | console.log(e) 226 | setState({ areaShow: false }) 227 | }} 228 | onCancel={() => { 229 | setState({ areaShow: false }) 230 | }} 231 | /> 232 | setState({ actionsheetShow: false })} 240 | onCancel={() => setState({ actionsheetShow: false })} 241 | onSelect={(e) => console.log(e)} 242 | /> 243 | 244 | ) 245 | }, 246 | { page: true }, 247 | ) 248 | 249 | definePageConfig({ 250 | // 这里不要设置标题,在Container组件上面设置 251 | navigationBarTitleText: '', 252 | }) 253 | -------------------------------------------------------------------------------- /src/components/container/index.tsx: -------------------------------------------------------------------------------- 1 | import { PureComponent, ReactNode, useContext, useEffect } from 'react' 2 | import { 3 | eventCenter, 4 | getCurrentInstance, 5 | showToast, 6 | useDidShow, 7 | } from '@tarojs/taro' 8 | import { View } from '@tarojs/components' 9 | import { UniteContext } from '@antmjs/unite' 10 | import { EMlf } from '@antmjs/trace' 11 | import { useRecoilState } from 'recoil' 12 | import { monitor } from '@/trace' 13 | import { LOGIN_CODE } from '@/constants' 14 | import { menuButtonStore } from '@/store' 15 | import { setMenuButtonAsync } from '@/utils' 16 | import Error from '../fullScreen/error' 17 | import Login from '../fullScreen/login' 18 | import Loading from '../fullScreen/loading' 19 | import Navigation from './navigation' 20 | import LeftBtns from './leftBtns' 21 | import './index.less' 22 | import PullDownRefresh from './pullDownRefresh' 23 | 24 | const hackSyncWechatTitle = () => { 25 | const iframe = document.createElement('iframe') 26 | iframe.style.display = 'none' 27 | iframe.src = '/favicon.ico' 28 | iframe.onload = () => { 29 | setTimeout(() => { 30 | document.body.removeChild(iframe) 31 | }, 10) 32 | } 33 | document.body.appendChild(iframe) 34 | } 35 | 36 | class ErrorBoundary extends PureComponent<{ setError: any; children: any }> { 37 | constructor(props: any) { 38 | super(props) 39 | } 40 | componentDidCatch(error: any, errorInfo: any) { 41 | if (process.env.NODE_ENV === 'development') { 42 | console.error('componentDidCatch', error, errorInfo) 43 | } 44 | monitor(EMlf.js, { 45 | d1: 'componentDidCatch', 46 | d2: JSON.stringify(error || ''), 47 | d3: JSON.stringify(errorInfo || ''), 48 | }) 49 | const showError = { 50 | code: 'BoundaryError', 51 | message: '渲染出现了小故障', 52 | data: { error, errorInfo }, 53 | } 54 | this.props.setError(showError) 55 | } 56 | 57 | clearError() { 58 | this.setState({ 59 | error: null, 60 | }) 61 | } 62 | 63 | render() { 64 | return this.props.children 65 | } 66 | } 67 | 68 | type IProps = { 69 | className: string 70 | children: ReactNode 71 | useMenuBtns?: boolean 72 | useNav?: boolean 73 | navTitle?: ReactNode 74 | navClassName?: string 75 | loading?: any 76 | ignoreError?: boolean 77 | enablePagePullDownRefresh?: boolean 78 | renderPageTopHeader?: ( 79 | navHeight: number, 80 | statusBarHeight: number, 81 | safeRight: number, 82 | ) => void 83 | } 84 | 85 | function Render(props: IProps & { ctx: any }): any { 86 | const { ctx, loading, ignoreError, className } = props 87 | 88 | // 组件把Login、JSError、BoundaryError报错抛到页面,由页面来处理 89 | useEffect(() => { 90 | const ins = getCurrentInstance() 91 | ins['insUniqueId'] = `${+new Date()}${Math.ceil(Math.random() * 10000)}` 92 | const onListenError = (e) => { 93 | ctx.setError(e) 94 | } 95 | if (ctx.uniteConfig.page) { 96 | eventCenter.on(ins['insUniqueId'], onListenError) 97 | } 98 | return () => { 99 | eventCenter.off(ins['insUniqueId'], onListenError) 100 | } 101 | // eslint-disable-next-line react-hooks/exhaustive-deps 102 | }, []) 103 | 104 | // 异常来自于三个部分 1: Request Code 2 JSError 3: BoundaryError 105 | // 有初始数据但是请求接口报错了,则toast。JSError BoundaryError Login 三个直接展示全屏错误 106 | useEffect(() => { 107 | if ( 108 | !loading && 109 | ctx.error && 110 | ctx.error.code !== 'JSError' && 111 | ctx.error.code !== 'BoundaryError' && 112 | ctx.error.code !== LOGIN_CODE 113 | ) { 114 | if (!ignoreError) { 115 | showToast({ 116 | title: ctx.error.message, 117 | icon: 'none', 118 | }) 119 | } 120 | ctx.setError(undefined) 121 | } 122 | // eslint-disable-next-line react-hooks/exhaustive-deps 123 | }, [ctx.error, loading]) 124 | 125 | useEffect(() => { 126 | // Login、JSError、BoundaryError报错 直接传递给页面展示全屏错误 127 | if ( 128 | ctx.error && 129 | (ctx.error.code === LOGIN_CODE || 130 | ctx.error.code === 'JSError' || 131 | ctx.error.code === 'BoundaryError') 132 | ) { 133 | if (!ctx.uniteConfig.page) { 134 | const ins = getCurrentInstance() 135 | eventCenter.trigger(ins['insUniqueId'], { ...ctx.error }) 136 | } 137 | } 138 | }, [ctx]) 139 | 140 | if (ctx.error && ctx.error.code === LOGIN_CODE) { 141 | if (ctx.uniteConfig.page) { 142 | return 143 | } else { 144 | // 组件这里就直接不展示了,由页面处理 145 | return <> 146 | } 147 | } 148 | 149 | // Login、JSError、BoundaryError报错 直接传递给页面展示全屏错误,组件里面就不用展示了 150 | if ( 151 | ctx.error && 152 | (ctx.error.code === 'JSError' || ctx.error.code === 'BoundaryError') 153 | ) { 154 | if (ctx.uniteConfig.page) { 155 | return ( 156 | 161 | ) 162 | } else { 163 | // 组件这里就直接不展示了,由页面处理 164 | return <> 165 | } 166 | } 167 | // loading状态代表没有初始数据,那么没有初始数据且报错的情况需要全屏展示 168 | if (loading && ctx.error) { 169 | if (ignoreError) return <> 170 | return ( 171 | 176 | ) 177 | } 178 | 179 | if (loading) return 180 | return {props.children} 181 | } 182 | 183 | export default function Index(props: IProps) { 184 | const { navTitle, navClassName, renderPageTopHeader } = props 185 | 186 | const ctx = useContext(UniteContext) 187 | 188 | const [menuButton, setMenuButton]: any = useRecoilState(menuButtonStore) 189 | 190 | // 页面的初始化和组件的初始化要区分开来 191 | const enablePagePullDownRefresh = 192 | props.enablePagePullDownRefresh ?? (ctx.uniteConfig.page ? true : false) 193 | const useNav = props.useNav ?? (ctx.uniteConfig.page ? true : false) 194 | const useMenuBtns = props.useMenuBtns ?? (ctx.uniteConfig.page ? true : false) 195 | 196 | // 返回回来要重新设置,所以这里用useDidShow 197 | useDidShow(() => { 198 | // 设置title 199 | if (process.env.TARO_ENV === 'h5' && !useNav) { 200 | try { 201 | document.title = navTitle?.toString?.() || '' 202 | } catch { 203 | document.title = '' 204 | } 205 | if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) { 206 | hackSyncWechatTitle() 207 | } 208 | } 209 | }) 210 | // 设置导航栏位置 211 | useEffect(function () { 212 | if (!menuButton || !menuButton.precise) { 213 | setMenuButtonAsync(setMenuButton) 214 | } 215 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 216 | // @ts-ignore 217 | if (process.env.NODE_ENV === 'development') { 218 | const ins = getCurrentInstance() 219 | if (ins.page?.config?.navigationBarTitleText) 220 | console.warn( 221 | 'useNav为true的时候不要在配置文件设置navigationBarTitleText,默认为页面为true', 222 | ) 223 | } 224 | // eslint-disable-next-line react-hooks/exhaustive-deps 225 | }, []) 226 | 227 | const statusBarHeight = menuButton ? menuButton!.statusBarHeight : 0 228 | 229 | return ( 230 | 231 | <> 232 | {useNav && ctx.error?.code !== LOGIN_CODE && ( 233 | 238 | )} 239 | {useMenuBtns && ctx.error?.code !== LOGIN_CODE && ( 240 | 241 | )} 242 | {ctx.uniteConfig.page && enablePagePullDownRefresh ? ( 243 | 247 | 248 | 249 | ) : ( 250 | 251 | )} 252 | 253 | 254 | ) 255 | } 256 | --------------------------------------------------------------------------------