├── .gitattributes ├── .gitignore ├── README.md ├── build ├── asset-manifest.json ├── favicon.ico ├── index.html ├── manifest.json └── service-worker.js ├── config ├── env.js ├── jest │ ├── cssTransform.js │ ├── fileTransform.js │ └── typescriptTransform.js ├── paths.js ├── polyfills.js ├── webpack.config.dev.js ├── webpack.config.prod.js └── webpackDevServer.config.js ├── declaration.d.ts ├── images.d.ts ├── package.json ├── public ├── favicon.ico ├── index.html ├── manifest.json └── reset.css ├── scripts ├── build.js ├── start.js └── test.js ├── src ├── App.scss ├── App.test.tsx ├── App.tsx ├── Components │ ├── AppointmentDate │ │ ├── AppointmentDate.scss │ │ ├── AppointmentDate.tsx │ │ ├── AppointmentDateBody │ │ │ ├── AppointmentDateBody.scss │ │ │ ├── AppointmentDateBody.tsx │ │ │ └── AppointmentDateBodyType.tsx │ │ └── AppointmentDateHeader │ │ │ ├── AppointmentDateHeader.scss │ │ │ └── AppointmentDateHeader.tsx │ └── UniversalComponents │ │ ├── Calendar │ │ ├── Calendar.scss │ │ └── Calendar.tsx │ │ ├── FormValidate │ │ ├── FormValidate.scss │ │ ├── FormValidate.tsx │ │ └── FromValidateType.ts │ │ ├── ListView │ │ ├── ListView.scss │ │ ├── ListView.tsx │ │ └── ListViewType.tsx │ │ ├── Map │ │ └── Map.tsx │ │ ├── Notify │ │ ├── Notify.scss │ │ └── Notify.tsx │ │ └── SharePoster │ │ └── CreateQrcode.tsx ├── Hooks │ └── IsIphoneX.ts ├── Page │ ├── Home │ │ ├── Home.scss │ │ └── Home.tsx │ ├── Login │ │ ├── Login.scss │ │ └── Login.tsx │ ├── Page.tsx │ ├── PageType.tsx │ ├── Reservation │ │ └── Reservation.tsx │ ├── Shop │ │ ├── Shop.scss │ │ └── Shop.tsx │ └── User │ │ ├── User.scss │ │ └── User.tsx ├── Redux │ ├── Actions │ │ ├── Actions.ts │ │ └── HandleAction.ts │ ├── Reducer │ │ └── Reducer.ts │ ├── State │ │ ├── State.ts │ │ └── StateType.ts │ ├── Store │ │ └── Store.ts │ └── Types │ │ └── Types.ts ├── Router │ ├── Router.scss │ └── Router.tsx ├── Static │ ├── Css │ │ ├── Base.scss │ │ └── Reset.css │ └── Images │ │ ├── AppBar │ │ ├── heigshouye_9_12.9@2x.png │ │ ├── heihuiyuan_9_12.9@2x.png │ │ ├── heishangpin_9_12.9@2x.png │ │ ├── heiyuyue_9_12.9@2x.png │ │ ├── honghuiyuan_9_12.9@2x.png │ │ ├── hongishangpin_9_12.9@2x.png │ │ ├── hongshouye_9_12.9@2x.png │ │ └── hongyuyue_9_12.9@2x.png │ │ ├── Home │ │ ├── shouyejinsedianhuahao_9_12.9@2x.png │ │ └── shouyejinsedingwe_9_12.9@2x.png │ │ └── christmas.jpg ├── Utils │ ├── Axios │ │ ├── Axios.ts │ │ └── AxiosConfig.ts │ ├── Base │ │ ├── Base.ts │ │ ├── BaseTypes.ts │ │ ├── IosQuestion.ts │ │ ├── Times.ts │ │ └── TimesType.ts │ ├── HttpList │ │ ├── HomeHttp │ │ │ ├── HomeHttp.ts │ │ │ └── HomeHttpType.ts │ │ └── PublicHttp │ │ │ └── PublicHttp.ts │ ├── MapCallback │ │ └── MapCallback.ts │ ├── UserAgent │ │ └── UserAgent.ts │ └── Wx │ │ ├── WxPay.ts │ │ └── WxShare.ts ├── index.scss ├── index.tsx ├── logo.svg ├── proxy.js └── registerServiceWorker.ts ├── tsconfig.json ├── tsconfig.prod.json ├── tsconfig.test.json └── tslint.json /.gitattributes: -------------------------------------------------------------------------------- 1 | *.tsx linguist-language=TypeScript -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /node 6 | /build 7 | # testing 8 | /coverage 9 | 10 | # production 11 | /build 12 | 13 | # misc 14 | .idea 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ```js 2 | 0. 项目使用px转换vw插件, ui图多少文件里就写多少尺寸 3 | 1. 所有路由组件首字母大写 4 | 2. 方法名首字母小写 5 | 3. hooks useState 使用获取对象用getxxx 设置用setxxxx, 6 | [示例] 7 | const [getStatus, setStatus] = useState(false); 8 | 4. hooks useEffect, useCallback 页面如果有使用定时器等监听必须在useEffect内清除, 不然会报错, 内存泄漏 9 | [示例] 10 | useEffect(() => { 11 | const time: NodeJs.Time = setTimeout(() => { 12 | // doSomething... 13 | }, 2000) 14 | return clearTimeout(time) 15 | },[]); 16 | 17 | useCallBack(() => { 18 | const time = setInterval(() => { 19 | // doSomething... 20 | },1000); 21 | return () => clearInterval(time); 22 | }, []) 23 | 5. redux 使用步骤 24 | [示例] 25 | <1> 在 src/Redux/State/State.tsx 里创建默认对象 「类似 vuex 中 state 参数」 26 | <2> 在 src/Redux/Actions/Actions.tsx 中创建 createAction 注册方法 「类似 vuex 中 actions 方法」 27 | <3> 在 src/Redux/Actions/HandleActions.tsx 中创建 handleAction 方法 「类似 vuex 中 mutations 方法」 28 | <4> 在 src/Redux/Reducer/Reucer.tsx 中导入 第三步创建的方法 「类似 vuex 中 store 主文件」 29 | <5> src/Redux/Store/Store.tsx 这个文件不用更改, 只是为了注入主入口 30 | 6. router 配置 31 | [实例] 32 | 在 src/Page/Page.tsx中加入创建的路由文件夹, 「 没有子路由说法, 创建时只循环创建了一次,没有使用递归创建, 后续有需求可以改进 」 33 | 7. 「文件夹说明」 34 | src 35 | |__Component 组件文件夹, 每个路由对应的组件创建对应的文件夹, UniversalComponents 为通用文件夹 36 | |__Page 路由页面文件夹, 一个路由创建一个文件夹 37 | |__Redux 状态管理文件夹 38 | |__Roouter 路由管理文件夹 39 | |__Static 公共资源文件夹 40 | |__Utils 公共方法文件夹 41 | |__Axios axios 文件 42 | |__HttpList 所有路由对应的文件夹的http请求 43 | |__Wx 微信方法 44 | |__UserAgent 判断浏览器类型 45 | |__App.tsx 入口文件 「最好不要更改」 46 | |__index.tsx 注入html文件, ⚠「不需要更改, 此文件为主要文件,」 47 | 8. [路径别名] 48 | <1> @/ => src 49 | <2> @Components => src/Components 50 | <3> @Static => src/Static 51 | <4> @Utils => src/Utils 52 | <5> @Page => src/Page 53 | 9. [路由懒加载, 组件懒加载] 54 | ```example 55 | import loadable from "@loadable/component"; 56 | const Home = loadable(() => import(/* webpackChunkName: "User" */ "./xx/xx")) 57 | ``` 58 | 10.[可能遇到的 bug] 59 | input输入框设置100% 可能会在ios上显示异常, 最好input输入框的宽度小于父元素的的宽度 60 | ``` -------------------------------------------------------------------------------- /build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "Home.js": "/static/js/Home.aeb23077.chunk.js", 3 | "app.js": "/static/js/app.b1e5b124.chunk.js", 4 | "main.js": "/static/js/main.489db1a8.js", 5 | "main.css": "/static/css/main_e2617d04.css", 6 | "vendors~app.js": "/static/js/vendors~app.a9f8bd66.chunk.js", 7 | "index.html": "/index.html", 8 | "static/js/Home.aeb23077.chunk.js.gz": "/static/js/Home.aeb23077.chunk.js.gz", 9 | "static/js/app.b1e5b124.chunk.js.gz": "/static/js/app.b1e5b124.chunk.js.gz", 10 | "static/js/main.489db1a8.js.gz": "/static/js/main.489db1a8.js.gz", 11 | "static/js/vendors~app.a9f8bd66.chunk.js.gz": "/static/js/vendors~app.a9f8bd66.chunk.js.gz", 12 | "static/media/WechatIMG61.c9606291.png": "/static/media/WechatIMG61.c9606291.png" 13 | } -------------------------------------------------------------------------------- /build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanglijie5997/react-mobile/53fb8d65998ef2b520b0b1c03aea3d3592163a04/build/favicon.ico -------------------------------------------------------------------------------- /build/index.html: -------------------------------------------------------------------------------- 1 | 主页
-------------------------------------------------------------------------------- /build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /build/service-worker.js: -------------------------------------------------------------------------------- 1 | "use strict";var precacheConfig=[["/index.html","06e60b4ec7b0a7028a2624dbcd7a042b"],["/static/css/main_e2617d04.css","e2617d046d14a58aecb56107c2d12e87"],["/static/js/Home.aeb23077.chunk.js","9be6c4e8b07a802f0154cdd24ed333ed"],["/static/js/Home.aeb23077.chunk.js.gz","78ee78c9ccb7ed5502686098088e50b3"],["/static/js/app.b1e5b124.chunk.js","bcd79a5eba164cedf56d0e163b124a8a"],["/static/js/app.b1e5b124.chunk.js.gz","fbbadb995346de83f87f2b545e1fc6fb"],["/static/js/main.489db1a8.js","65c4bbaa114db7794d902b9ff607a6af"],["/static/js/main.489db1a8.js.gz","54fcd8fe06b8fa0d5473fc6976afe9ea"],["/static/js/vendors~app.a9f8bd66.chunk.js","3cf86c5238ec02ab3fc8d914ce72dd70"],["/static/js/vendors~app.a9f8bd66.chunk.js.gz","424e521bfe9d5b8601ad25d3ccb0042c"],["/static/media/WechatIMG61.c9606291.png","c960629194751b879277c4a8f4ea9da6"]],cacheName="sw-precache-v3-sw-precache-webpack-plugin-"+(self.registration?self.registration.scope:""),ignoreUrlParametersMatching=[/^utm_/],addDirectoryIndex=function(e,t){var n=new URL(e);return"/"===n.pathname.slice(-1)&&(n.pathname+=t),n.toString()},cleanResponse=function(t){return t.redirected?("body"in t?Promise.resolve(t.body):t.blob()).then(function(e){return new Response(e,{headers:t.headers,status:t.status,statusText:t.statusText})}):Promise.resolve(t)},createCacheKey=function(e,t,n,a){var r=new URL(e);return a&&r.pathname.match(a)||(r.search+=(r.search?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(n)),r.toString()},isPathWhitelisted=function(e,t){if(0===e.length)return!0;var n=new URL(t).pathname;return e.some(function(e){return n.match(e)})},stripIgnoredUrlParameters=function(e,n){var t=new URL(e);return t.hash="",t.search=t.search.slice(1).split("&").map(function(e){return e.split("=")}).filter(function(t){return n.every(function(e){return!e.test(t[0])})}).map(function(e){return e.join("=")}).join("&"),t.toString()},hashParamName="_sw-precache",urlsToCacheKeys=new Map(precacheConfig.map(function(e){var t=e[0],n=e[1],a=new URL(t,self.location),r=createCacheKey(a,hashParamName,n,/\.\w{8}\./);return[a.toString(),r]}));function setOfCachedUrls(e){return e.keys().then(function(e){return e.map(function(e){return e.url})}).then(function(e){return new Set(e)})}self.addEventListener("install",function(e){e.waitUntil(caches.open(cacheName).then(function(a){return setOfCachedUrls(a).then(function(n){return Promise.all(Array.from(urlsToCacheKeys.values()).map(function(t){if(!n.has(t)){var e=new Request(t,{credentials:"same-origin"});return fetch(e).then(function(e){if(!e.ok)throw new Error("Request for "+t+" returned a response with status "+e.status);return cleanResponse(e).then(function(e){return a.put(t,e)})})}}))})}).then(function(){return self.skipWaiting()}))}),self.addEventListener("activate",function(e){var n=new Set(urlsToCacheKeys.values());e.waitUntil(caches.open(cacheName).then(function(t){return t.keys().then(function(e){return Promise.all(e.map(function(e){if(!n.has(e.url))return t.delete(e)}))})}).then(function(){return self.clients.claim()}))}),self.addEventListener("fetch",function(t){if("GET"===t.request.method){var e,n=stripIgnoredUrlParameters(t.request.url,ignoreUrlParametersMatching),a="index.html";(e=urlsToCacheKeys.has(n))||(n=addDirectoryIndex(n,a),e=urlsToCacheKeys.has(n));var r="/index.html";!e&&"navigate"===t.request.mode&&isPathWhitelisted(["^(?!\\/__).*"],t.request.url)&&(n=new URL(r,self.location).toString(),e=urlsToCacheKeys.has(n)),e&&t.respondWith(caches.open(cacheName).then(function(e){return e.match(urlsToCacheKeys.get(n)).then(function(e){if(e)return e;throw Error("The cached response that was expected is missing.")})}).catch(function(e){return console.warn('Couldn\'t serve response for "%s" from cache: %O',t.request.url,e),fetch(t.request)}))}}); -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')]; 9 | 10 | const NODE_ENV = process.env.NODE_ENV; 11 | if (!NODE_ENV) { 12 | throw new Error( 13 | 'The NODE_ENV environment variable is required but was not specified.' 14 | ); 15 | } 16 | 17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 18 | var dotenvFiles = [ 19 | `${paths.dotenv}.${NODE_ENV}.local`, 20 | `${paths.dotenv}.${NODE_ENV}`, 21 | // Don't include `.env.local` for `test` environment 22 | // since normally you expect tests to produce the same 23 | // results for everyone 24 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 25 | paths.dotenv, 26 | ].filter(Boolean); 27 | 28 | // Load environment variables from .env* files. Suppress warnings using silent 29 | // if this file is missing. dotenv will never modify any environment variables 30 | // that have already been set. Variable expansion is supported in .env files. 31 | // https://github.com/motdotla/dotenv 32 | // https://github.com/motdotla/dotenv-expand 33 | dotenvFiles.forEach(dotenvFile => { 34 | if (fs.existsSync(dotenvFile)) { 35 | require('dotenv-expand')( 36 | require('dotenv').config({ 37 | path: dotenvFile, 38 | }) 39 | ); 40 | } 41 | }); 42 | 43 | // We support resolving modules according to `NODE_PATH`. 44 | // This lets you use absolute paths in imports inside large monorepos: 45 | // https://github.com/facebookincubator/create-react-app/issues/253. 46 | // It works similar to `NODE_PATH` in Node itself: 47 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 48 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 49 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 50 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 51 | // We also resolve them to make sure all tools using them work consistently. 52 | const appDirectory = fs.realpathSync(process.cwd()); 53 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 54 | .split(path.delimiter) 55 | .filter(folder => folder && !path.isAbsolute(folder)) 56 | .map(folder => path.resolve(appDirectory, folder)) 57 | .join(path.delimiter); 58 | 59 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 60 | // injected into the application via DefinePlugin in Webpack configuration. 61 | const REACT_APP = /^REACT_APP_/i; 62 | 63 | function getClientEnvironment(publicUrl) { 64 | const raw = Object.keys(process.env) 65 | .filter(key => REACT_APP.test(key)) 66 | .reduce( 67 | (env, key) => { 68 | env[key] = process.env[key]; 69 | return env; 70 | }, 71 | { 72 | // Useful for determining whether we’re running in production mode. 73 | // Most importantly, it switches React into the correct mode. 74 | NODE_ENV: process.env.NODE_ENV || 'development', 75 | // Useful for resolving the correct path to static assets in `public`. 76 | // For example, . 77 | // This should only be used as an escape hatch. Normally you would put 78 | // images into the `src` and `import` them in code to get their paths. 79 | PUBLIC_URL: publicUrl, 80 | } 81 | ); 82 | // Stringify all values so we can feed into Webpack DefinePlugin 83 | const stringified = { 84 | 'process.env': Object.keys(raw).reduce( 85 | (env, key) => { 86 | env[key] = JSON.stringify(raw[key]); 87 | return env; 88 | }, 89 | {} 90 | ), 91 | }; 92 | 93 | return { raw, stringified }; 94 | } 95 | 96 | module.exports = getClientEnvironment; 97 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/en/webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /config/jest/typescriptTransform.js: -------------------------------------------------------------------------------- 1 | // Copyright 2004-present Facebook. All Rights Reserved. 2 | 3 | 'use strict'; 4 | 5 | const tsJestPreprocessor = require('ts-jest/preprocessor'); 6 | 7 | module.exports = tsJestPreprocessor; 8 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(path, needsSlash) { 15 | const hasSlash = path.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return path.substr(path, path.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${path}/`; 20 | } else { 21 | return path; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 38 | 39 | 主页 40 | 41 | 42 | 45 | 46 |
47 | 48 | 49 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /public/reset.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanglijie5997/react-mobile/53fb8d65998ef2b520b0b1c03aea3d3592163a04/public/reset.css -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'production'; 5 | process.env.NODE_ENV = 'production'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | const path = require('path'); 18 | const chalk = require('chalk'); 19 | const fs = require('fs-extra'); 20 | const webpack = require('webpack'); 21 | const config = require('../config/webpack.config.prod'); 22 | const paths = require('../config/paths'); 23 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 24 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); 25 | const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); 26 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); 27 | const printBuildError = require('react-dev-utils/printBuildError'); 28 | 29 | const measureFileSizesBeforeBuild = FileSizeReporter.measureFileSizesBeforeBuild; 30 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; 31 | const useYarn = fs.existsSync(paths.yarnLockFile); 32 | 33 | // These sizes are pretty large. We'll warn for bundles exceeding them. 34 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; 35 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; 36 | 37 | // Warn and crash if required files are missing 38 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 39 | process.exit(1); 40 | } 41 | 42 | // First, read the current file sizes in build directory. 43 | // This lets us display how much they changed later. 44 | measureFileSizesBeforeBuild(paths.appBuild) 45 | .then(previousFileSizes => { 46 | // Remove all content but keep the directory so that 47 | // if you're in it, you don't end up in Trash 48 | fs.emptyDirSync(paths.appBuild); 49 | // Merge with the public folder 50 | copyPublicFolder(); 51 | // Start the webpack build 52 | return build(previousFileSizes); 53 | }) 54 | .then( 55 | ({ stats, previousFileSizes, warnings }) => { 56 | if (warnings.length) { 57 | console.log(chalk.yellow('Compiled with warnings.\n')); 58 | console.log(warnings.join('\n\n')); 59 | console.log( 60 | '\nSearch for the ' + 61 | chalk.underline(chalk.yellow('keywords')) + 62 | ' to learn more about each warning.' 63 | ); 64 | console.log( 65 | 'To ignore, add ' + 66 | chalk.cyan('// eslint-disable-next-line') + 67 | ' to the line before.\n' 68 | ); 69 | } else { 70 | console.log(chalk.green('Compiled successfully.\n')); 71 | } 72 | 73 | console.log('File sizes after gzip:\n'); 74 | printFileSizesAfterBuild( 75 | stats, 76 | previousFileSizes, 77 | paths.appBuild, 78 | WARN_AFTER_BUNDLE_GZIP_SIZE, 79 | WARN_AFTER_CHUNK_GZIP_SIZE 80 | ); 81 | console.log(); 82 | 83 | const appPackage = require(paths.appPackageJson); 84 | const publicUrl = paths.publicUrl; 85 | const publicPath = config.output.publicPath; 86 | const buildFolder = path.relative(process.cwd(), paths.appBuild); 87 | printHostingInstructions( 88 | appPackage, 89 | publicUrl, 90 | publicPath, 91 | buildFolder, 92 | useYarn 93 | ); 94 | }, 95 | err => { 96 | console.log(chalk.red('Failed to compile.\n')); 97 | printBuildError(err); 98 | process.exit(1); 99 | } 100 | ); 101 | 102 | // Create the production build and print the deployment instructions. 103 | function build(previousFileSizes) { 104 | console.log('Creating an optimized production build...'); 105 | 106 | let compiler = webpack(config); 107 | return new Promise((resolve, reject) => { 108 | compiler.run((err, stats) => { 109 | if (err) { 110 | return reject(err); 111 | } 112 | const messages = formatWebpackMessages(stats.toJson({}, true)); 113 | if (messages.errors.length) { 114 | // Only keep the first error. Others are often indicative 115 | // of the same problem, but confuse the reader with noise. 116 | if (messages.errors.length > 1) { 117 | messages.errors.length = 1; 118 | } 119 | return reject(new Error(messages.errors.join('\n\n'))); 120 | } 121 | if ( 122 | process.env.CI && 123 | (typeof process.env.CI !== 'string' || 124 | process.env.CI.toLowerCase() !== 'false') && 125 | messages.warnings.length 126 | ) { 127 | console.log( 128 | chalk.yellow( 129 | '\nTreating warnings as errors because process.env.CI = true.\n' + 130 | 'Most CI servers set it automatically.\n' 131 | ) 132 | ); 133 | return reject(new Error(messages.warnings.join('\n\n'))); 134 | } 135 | return resolve({ 136 | stats, 137 | previousFileSizes, 138 | warnings: messages.warnings, 139 | }); 140 | }); 141 | }); 142 | } 143 | 144 | function copyPublicFolder() { 145 | fs.copySync(paths.appPublic, paths.appBuild, { 146 | dereference: true, 147 | filter: file => file !== paths.appHtml, 148 | }); 149 | } 150 | -------------------------------------------------------------------------------- /scripts/start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'development'; 5 | process.env.NODE_ENV = 'development'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | const fs = require('fs'); 18 | const chalk = require('chalk'); 19 | const webpack = require('webpack'); 20 | const WebpackDevServer = require('webpack-dev-server'); 21 | const clearConsole = require('react-dev-utils/clearConsole'); 22 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 23 | const { 24 | choosePort, 25 | createCompiler, 26 | prepareProxy, 27 | prepareUrls, 28 | } = require('react-dev-utils/WebpackDevServerUtils'); 29 | const openBrowser = require('react-dev-utils/openBrowser'); 30 | const paths = require('../config/paths'); 31 | const config = require('../config/webpack.config.dev'); 32 | const createDevServerConfig = require('../config/webpackDevServer.config'); 33 | 34 | const useYarn = fs.existsSync(paths.yarnLockFile); 35 | const isInteractive = process.stdout.isTTY; 36 | 37 | // Warn and crash if required files are missing 38 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 39 | process.exit(1); 40 | } 41 | 42 | // Tools like Cloud9 rely on this. 43 | const DEFAULT_PORT = 3008; 44 | const HOST = process.env.HOST || '0.0.0.0'; 45 | 46 | if (process.env.HOST) { 47 | console.log( 48 | chalk.cyan( 49 | `Attempting to bind to HOST environment variable: ${chalk.yellow( 50 | chalk.bold(process.env.HOST) 51 | )}` 52 | ) 53 | ); 54 | console.log( 55 | `If this was unintentional, check that you haven't mistakenly set it in your shell.` 56 | ); 57 | console.log(`Learn more here: ${chalk.yellow('http://bit.ly/2mwWSwH')}`); 58 | console.log(); 59 | } 60 | 61 | // We attempt to use the default port but if it is busy, we offer the user to 62 | // run on a different port. `choosePort()` Promise resolves to the next free port. 63 | choosePort(HOST, DEFAULT_PORT) 64 | .then(port => { 65 | if (port == null) { 66 | // We have not found a port. 67 | return; 68 | } 69 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; 70 | const appName = require(paths.appPackageJson).name; 71 | const urls = prepareUrls(protocol, HOST, port); 72 | // Create a webpack compiler that is configured with custom messages. 73 | const compiler = createCompiler({webpack, config, appName, urls, useYarn}); 74 | // Load proxy config 75 | const proxySetting = require(paths.appPackageJson).proxy; 76 | const proxyConfig = prepareProxy(proxySetting, paths.appPublic); 77 | // Serve webpack assets generated by the compiler over a web sever. 78 | const serverConfig = createDevServerConfig( 79 | proxyConfig, 80 | urls.lanUrlForConfig 81 | ); 82 | const devServer = new WebpackDevServer(compiler, serverConfig); 83 | require('../src/proxy')(devServer) 84 | // Launch WebpackDevServer. 85 | devServer.listen(port, HOST, err => { 86 | if (err) { 87 | return console.log(err); 88 | } 89 | if (isInteractive) { 90 | clearConsole(); 91 | } 92 | console.log(chalk.cyan('Starting the development server...\n')); 93 | openBrowser(urls.localUrlForBrowser); 94 | }); 95 | 96 | ['SIGINT', 'SIGTERM'].forEach(function(sig) { 97 | process.on(sig, function() { 98 | devServer.close(); 99 | process.exit(); 100 | }); 101 | }); 102 | }) 103 | .catch(err => { 104 | if (err && err.message) { 105 | console.log(err.message); 106 | } 107 | process.exit(1); 108 | }); 109 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | const jest = require('jest'); 19 | let argv = process.argv.slice(2); 20 | 21 | // Watch unless on CI, in coverage mode, or explicitly running all tests 22 | if ( 23 | !process.env.CI && 24 | argv.indexOf('--coverage') === -1 && 25 | argv.indexOf('--watchAll') === -1 26 | ) { 27 | argv.push('--watch'); 28 | } 29 | 30 | 31 | jest.run(argv); 32 | -------------------------------------------------------------------------------- /src/App.scss: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .appBar { 6 | 7 | } 8 | 9 | body { 10 | min-height: 100vh; 11 | } 12 | .header { 13 | background-color: #222; 14 | height: 150px; 15 | padding: 100px; 16 | color: white; 17 | } 18 | 19 | .App-title { 20 | font-size: 1.5em; 21 | } 22 | 23 | .App-intro { 24 | font-size: large; 25 | } 26 | 27 | @keyframes App-logo-spin { 28 | from { transform: rotate(0deg); } 29 | to { transform: rotate(360deg); } 30 | } 31 | -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useCallback } from 'react'; 2 | import { withRouter ,useHistory} from "react-router-dom"; 3 | import Router from './Router/Router'; 4 | import { RouteComponentProps, StaticContext } from "react-router"; 5 | import styles from "./App.scss"; 6 | import { mapKey, isIphoneX } from './Utils/Base/Base'; 7 | import { useDispatch } from 'react-redux'; 8 | import { LocationType } from './Redux/State/StateType'; 9 | import { userLocalAction, iphoneXAction } from './Redux/Actions/Actions'; 10 | import './Static/Css/Base.scss' 11 | import { IsIphoneXHook } from './Hooks/IsIphoneX'; 12 | const App = (props: RouteComponentProps) => { 13 | const iphoneXStatus = IsIphoneXHook(); 14 | const dispatch = useDispatch(); 15 | useEffect(() => { 16 | let mapObj: any = null; 17 | let geolocation: any = null; 18 | window.onLoad = () => { 19 | mapObj = new AMap.Map('container'); 20 | console.log(mapObj, '---map---'); 21 | mapObj.plugin('AMap.Geolocation', function () { 22 | geolocation = new AMap.Geolocation({ 23 | enableHighAccuracy: true,// 是否使用高精度定位,默认:true 24 | timeout: 10000, // 超过10秒后停止定位,默认:无穷大 25 | maximumAge: 0, // 定位结果缓存0毫秒,默认:0 26 | convert: true, // 自动偏移坐标,偏移后的坐标为高德坐标,默认:true 27 | showButton: true, // 显示定位按钮,默认:true 28 | buttonPosition: 'LB', // 定位按钮停靠位置,默认:'LB',左下角 29 | buttonOffset: new AMap.Pixel(10, 20),// 定位按钮与设置的停靠位置的偏移量,默认:Pixel(10, 20) 30 | showMarker: true, // 定位成功后在定位到的位置显示点标记,默认:true 31 | showCircle: true, // 定位成功后用圆圈表示定位精度范围,默认:true 32 | panToLocation: true, // 定位成功后将定位到的位置作为地图中心点,默认:true 33 | zoomToAccuracy:true // 定位成功后调整地图视野范围使定位位置及精度范围视野内可见,默认:false 34 | }); 35 | mapObj.addControl(geolocation); 36 | geolocation.getCurrentPosition(); 37 | AMap.event.addListener(geolocation, 'complete', onComplete);// 返回定位信息 38 | AMap.event.addListener(geolocation, 'error', onError); // 返回定位出错信息 39 | }); 40 | } 41 | const mapScript = document.createElement("script"); 42 | mapScript.charset = "utf-8"; 43 | mapScript.src = `https://webapi.amap.com/maps?v=1.4.15&key=${mapKey}&callback=onLoad`; 44 | document.head.appendChild(mapScript); 45 | return () => { 46 | AMap.event.removeEventListener(geolocation, "complete", onComplete); 47 | AMap.event.removeEventListener(geolocation, "error", onError); 48 | } 49 | },[]); 50 | 51 | useEffect(() => { 52 | dispatch(iphoneXAction(iphoneXStatus)) 53 | }, [iphoneXStatus]) 54 | 55 | /** 成功回调 */ 56 | const onComplete = useCallback((event: LocationType) => { 57 | console.log(event, '地理位置'); 58 | // 全局管理用户地理位置 59 | dispatch(userLocalAction(event)); 60 | },[dispatch]); 61 | /** 失败回调 */ 62 | const onError = useCallback((event) => { 63 | // Toast.fail("获取地理位置失败, 请刷新重试"); 64 | console.warn("获取地理位置失败, 请刷新重试") 65 | },[dispatch]) 66 | return ( 67 |
68 | {/*kankna*/} 69 | 70 |
71 | ) 72 | } 73 | export default withRouter(App); 74 | -------------------------------------------------------------------------------- /src/Components/AppointmentDate/AppointmentDate.scss: -------------------------------------------------------------------------------- 1 | .appointmentDate { 2 | width: 690px; 3 | margin: 20px auto 0; 4 | border-radius: 20px; 5 | background: #ffffff; 6 | } -------------------------------------------------------------------------------- /src/Components/AppointmentDate/AppointmentDate.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { RouteComponentProps } from 'react-router' 3 | import styles from "./AppointmentDate.scss"; 4 | import loadable from '@loadable/component'; 5 | const Header = loadable(() => import(/* webpackChunkName: "AppointmentDate" */"./AppointmentDateHeader/AppointmentDateHeader")); 6 | const Body = loadable(() => import(/* webpackChunkName: "AppointmentDate" */"./AppointmentDateBody/AppointmentDateBody")) 7 | 8 | // 日历组件 9 | const AppointmentDate = (props: RouteComponentProps) => { 10 | return ( 11 |
12 |
13 | 14 |
15 | ) 16 | } 17 | 18 | 19 | export default AppointmentDate; 20 | -------------------------------------------------------------------------------- /src/Components/AppointmentDate/AppointmentDateBody/AppointmentDateBody.scss: -------------------------------------------------------------------------------- 1 | .appointmentDateBody { 2 | padding: 30px 30px 40px; 3 | .choicHeader { 4 | display: flex; 5 | justify-content: space-between; 6 | p { 7 | font-size: 30px; 8 | } 9 | } 10 | 11 | .month { 12 | margin-bottom: 40px; 13 | ul { 14 | display: flex; 15 | flex-wrap: wrap; 16 | margin-top: 30px; 17 | li { 18 | width: 48px; 19 | height: 48px; 20 | border-radius: 50%; 21 | background: #f6f6f6; 22 | position: relative; 23 | margin-right: 48px; 24 | margin-top: 30px; 25 | // margin-left: 6px; 26 | &:nth-child(2n+1) { 27 | margin-left: 0; 28 | } 29 | p { 30 | background: #c2c2c2; 31 | width: 36px; 32 | height: 36px; 33 | margin: 6px auto; 34 | font-size: 24px; 35 | border-radius: 50%; 36 | line-height: 24px; 37 | text-align: center; 38 | line-height: 36px; 39 | color: #ffffff; 40 | } 41 | &:after { 42 | content: ""; 43 | width: 33px; 44 | height: 2px; 45 | background: #ececec; 46 | position: absolute; 47 | top: 24px; 48 | left: 54px; 49 | } 50 | &:nth-child(7n),&:last-child { 51 | margin-right: 0; 52 | &:after { 53 | content: ""; 54 | width:0; 55 | height: 0px; 56 | background: #ececec; 57 | position: absolute; 58 | top: 24px; 59 | right: -33px; 60 | } 61 | } 62 | // 选中颜色 63 | .select { 64 | background: #e81844!important; 65 | } 66 | } 67 | .selectCircle { 68 | background: #FFF1ED; 69 | } 70 | } 71 | } 72 | 73 | .btnList { 74 | display: flex; 75 | height: 100px; 76 | width: 100vw; 77 | left: 0; 78 | bottom: 0; 79 | position: fixed; 80 | li { 81 | flex: 1; 82 | height: 100px; 83 | button { 84 | background: none; 85 | width: 100%; 86 | color: #ffffff; 87 | height: 100%; 88 | text-align: center; 89 | font-size: 36px; 90 | } 91 | &:nth-child(1) { 92 | background: #ffa54d; 93 | } 94 | &:nth-child(2) { 95 | background: #e81844; 96 | } 97 | } 98 | } 99 | } 100 | .hideText { 101 | visibility: hidden; 102 | } 103 | // 置灰 104 | .addGray { 105 | background: #E7E7E7!important; 106 | pointer-events: none; 107 | } 108 | -------------------------------------------------------------------------------- /src/Components/AppointmentDate/AppointmentDateBody/AppointmentDateBody.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { RouteComponentProps } from 'react-router' 3 | import styles from "./AppointmentDateBody.scss"; 4 | import { dateFormat } from '@Utils/Base/Base'; 5 | import { DateFormatType } from '@Utils/Base/BaseTypes'; 6 | import { MonthHaveDay } from './AppointmentDateBodyType'; 7 | import { Toast } from 'antd-mobile'; 8 | const AppointmentDateBody = (props: {routeFn: RouteComponentProps, date: number}) => { 9 | const [getNowMonth, setNowMonth] = useState(-1); // 当前月份 10 | const [getNowDay, setNowDay] = useState(""); // 当前时间 11 | const [getNextDay, setNextDay] = useState(""); // 下一个时间 12 | const [getMonthDay, setGetMonthDay] = useState([]); // 当月天数 13 | const [getGrayDay, setGrayDay] = useState(-1); // 当前天数 14 | const [getNextMonthDay, setNextMonthDay] = useState([]); // 获取下月天数有多少天 15 | const [getSelect, setSelect] = useState(-2); // 选中的日期 16 | const [getChociDate, setChoicDate] = useState(); // 选择的日期 17 | useEffect(() => { 18 | const data: DateFormatType = dateFormat(props.date); 19 | setNowMonth(data.month) 20 | setNowDay(`${data.year}年${data.month}月`); 21 | // 获取月数是星期几 22 | const weekDay = new Date(`${data.year},${data.month},1`).getDay() || new Date(`${data.year}/${data.month}/${1}`).getDay(); 23 | console.log(weekDay, 'nnnn'); 24 | const fillArray: MonthHaveDay[] | null = weekDay > 0 ? new Array(weekDay).fill(null).map((_: null, index: number) => ({ year: data.year,month: data.month, day:-1, select: false })) : null; 25 | // 当前月数有几天 26 | const nowMonthHaveDay: number = new Date(data.year,data.month,0).getDate(); 27 | if(fillArray) { 28 | setGetMonthDay(fillArray.concat(new Array(nowMonthHaveDay).fill(null).map((_: null, index: number) => ({ day: index + 1, year: data.year, month: data.month, select: false })))); 29 | 30 | }else{ 31 | setGetMonthDay(new Array(nowMonthHaveDay).fill(null).map((_: null, index: number) => ({ day: index + 1, year: data.year, month: data.month, select: false }))); 32 | 33 | } 34 | setGrayDay(data.nowDay); 35 | // 如果是12月就轮到下一年一月 36 | if(data.month >= 12) { 37 | data.year = data.year + 1; 38 | data.month = 1; 39 | } 40 | setNextDay(`${data.year}年${data.month}月`); 41 | 42 | const nextMonthHaveDay: number = new Date(data.year,data.month,0).getDate(); 43 | const nextHaveDay = new Date(`${data.year},${data.month},1`).getDay() || new Date(`${data.year}/${data.month}/${1}`).getDay(); 44 | console.log(nextHaveDay, "我就看看"); 45 | 46 | // 填充下月个第一天前面的数组 47 | const newxtFillArray: MonthHaveDay[] | null = nextHaveDay > 0 ? new Array(nextHaveDay).fill(0).map((_: null, index: number) => ({ year: data.year,month: data.month, day:-1, select: false})) : null; 48 | if(newxtFillArray) { 49 | setNextMonthDay(newxtFillArray.concat(new Array(nextMonthHaveDay).fill(0).map((_: null, index: number) => ({ day: index + 1, year: data.year, month: data.month, select: false })))); 50 | 51 | }else { 52 | setNextMonthDay((new Array(nextMonthHaveDay).fill(0).map((_: null, index: number) => ({ day: index + 1, year: data.year, month: data.month, select: false })))); 53 | 54 | } 55 | }, [getNextDay, getNowDay]) 56 | 57 | const goNextPage = (num: number) => { 58 | console.log(props.routeFn.match, '...'); 59 | if(num === 0) { 60 | props.routeFn.history.goBack(); 61 | }else { 62 | if(getSelect < 0) { 63 | return Toast.info("请选择您要预约的日期") 64 | } 65 | if(getChociDate) { 66 | props.routeFn.history.push(`/reservationDetail/${props.routeFn.match.params["id"]}?year=${getChociDate.year}&month=${getChociDate.month}&day=${getChociDate.day}`); 67 | } 68 | } 69 | } 70 | 71 | // 点击预约 72 | const choicNowDayFn = (data: MonthHaveDay, index: number, choicStatus:number) => { 73 | if(data.day < getGrayDay && data.month === getNowMonth) { 74 | return 75 | } 76 | // 选择当前月份 77 | if(choicStatus === 1) { 78 | const newNowMonth = getMonthDay.map((data: MonthHaveDay, i: number) => (i === index ? {...data, select: true} : {...data, select: false })); 79 | 80 | const settingNextMonth = getNextMonthDay.map((data: MonthHaveDay, i: number) => ({...data, select: false})); 81 | setGetMonthDay(newNowMonth); 82 | setNextMonthDay(settingNextMonth); 83 | 84 | }else { 85 | const newNextMonth = getNextMonthDay.map((data: MonthHaveDay, i: number) => (i === index ? {...data, select: true} : {...data, select: false})); 86 | const settingCurrentDay = getMonthDay.map((data: MonthHaveDay, i: number) => ({...data, select: false})); 87 | setNextMonthDay(newNextMonth); 88 | setGetMonthDay(settingCurrentDay); 89 | } 90 | console.log(data, '---当前选择日期----'); 91 | setChoicDate(data) 92 | setSelect(index); 93 | } 94 | 95 | // 月份节点 96 | const nowDayNode:(list: MonthHaveDay[], choicStatus: number) => JSX.Element[] = (list: MonthHaveDay[], choicStatus: number) => list.map((data: MonthHaveDay, index: number) => { 97 | return
  • choicNowDayFn(data, index, choicStatus)}> 99 |

    {data.day}

    100 |
  • 101 | }) 102 | 103 | const bodyNode:(list: MonthHaveDay[], choicStatus: number) => JSX.Element = (list: MonthHaveDay[], choicStatus: number) => (
    104 |
    105 |

    { choicStatus === 1? `选择日期` : ``}

    106 |

    { choicStatus === 1? getNowDay : getNextDay}

    107 |
    108 |
      109 | { nowDayNode(list, choicStatus) } 110 |
    111 |
    ) 112 | 113 | return ( 114 |
    115 | { bodyNode(getMonthDay, 1) } 116 | { bodyNode(getNextMonthDay, -1) } 117 |
      118 |
    • goNextPage(0)}>
    • 119 |
    • goNextPage(1)}>
    • 120 |
    121 |
    122 | ) 123 | } 124 | 125 | 126 | export default AppointmentDateBody 127 | 128 | -------------------------------------------------------------------------------- /src/Components/AppointmentDate/AppointmentDateBody/AppointmentDateBodyType.tsx: -------------------------------------------------------------------------------- 1 | // 月份数组格式 2 | export interface MonthHaveDay{ 3 | year: number, 4 | month: number, 5 | day: number, 6 | select?: boolean 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/Components/AppointmentDate/AppointmentDateHeader/AppointmentDateHeader.scss: -------------------------------------------------------------------------------- 1 | .appointmentDateHeader { 2 | width: 100%; 3 | ul { 4 | padding: 30px 0px; 5 | width: 630px; 6 | margin: 0 auto; 7 | display: flex; 8 | border-bottom: 2px solid #f3f3f3; 9 | li { 10 | font-size: 22px; 11 | margin-right: 50px; 12 | white-space: nowrap; 13 | &:last-child { 14 | margin-right: 0; 15 | } 16 | } 17 | .active { 18 | color: #e81844; 19 | } 20 | } 21 | } 22 | 23 | @media only screen and (max-width:320px) { 24 | .appointmentDateHeader { 25 | ul { 26 | li { 27 | margin-right: 5.36vw!important; 28 | } 29 | } 30 | 31 | } 32 | } -------------------------------------------------------------------------------- /src/Components/AppointmentDate/AppointmentDateHeader/AppointmentDateHeader.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import styles from "./AppointmentDateHeader.scss"; 3 | const AppointmentDateHeader = (props: any) => { 4 | const [getDateWeek, setDateWeek] = useState(["周日","周一","周二","周三","周四","周五","周六"]); 5 | const [getSelectDate, setSelectDate] = useState(-1); // 当前星期几 6 | useEffect(() => { 7 | const dateNow: Date = new Date(); // 当前时间 8 | const nowWeek = dateNow.getDay(); 9 | setSelectDate(nowWeek); 10 | },[]); 11 | const titleDate = getDateWeek.map((data: string, index: number) => { 12 | return
  • {data}
  • 13 | }) 14 | return ( 15 |
    16 |
      17 | { titleDate } 18 |
    19 |
    20 | ) 21 | } 22 | 23 | 24 | 25 | export default AppointmentDateHeader 26 | 27 | -------------------------------------------------------------------------------- /src/Components/UniversalComponents/Calendar/Calendar.scss: -------------------------------------------------------------------------------- 1 | .calenderUl { 2 | display: flex; 3 | flex-wrap: wrap; 4 | justify-content: flex-start; 5 | width: 350px; 6 | li { 7 | width: 50px; 8 | height: 50px; 9 | border-radius: 50%; 10 | background: #e7e7e7; 11 | color: black; 12 | font-size: 24px; 13 | text-align: center; 14 | line-height: 50px; 15 | } 16 | .select { 17 | color: red; 18 | background: pink; 19 | } 20 | } -------------------------------------------------------------------------------- /src/Components/UniversalComponents/Calendar/Calendar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, useCallback, memo } from 'react' 2 | import styles from "./Calendar.scss"; 3 | import { RouteComponentProps } from 'react-router'; 4 | import Times from "@Utils/Base/Times"; 5 | 6 | interface ShowMonthType { 7 | year: number, 8 | month: number, 9 | day: number, 10 | haveDay?: number, 11 | week?: number, 12 | currentYearMonth?: string, 13 | list?: number[], 14 | currentTime?: number 15 | } 16 | 17 | interface CalendarProps { 18 | routerFn: RouteComponentProps 19 | date: number 20 | showMonth: number 21 | } 22 | 23 | /** 日历组件 24 | * @param props 25 | * @param routerFn 路由方法 26 | * @param date 当前时间时间戳 27 | * @param showMonth 展示多少个月 28 | */ 29 | const Calendar = (props: CalendarProps) => { 30 | const [getCurrentDay, setCurrentDay] = useState(); // NodeList 31 | const [getSelect, setSelect] = useState(-1); // 选择的时间 32 | const [getSelectSatus, setSelectStatus] = useState(false); // 状态 33 | useEffect(() => { 34 | if(!getSelectSatus) { 35 | setSelect(Times.currentTime(props.date)); 36 | } 37 | monthList(); 38 | }, [props.date, getSelect]); 39 | // 月份列表 40 | const monthList = () => { 41 | setSelectStatus(true) 42 | // 当前时间 43 | const currentTime = Times.dateFormat(props.date); 44 | let showMonth = props.showMonth; 45 | let arr = []; 46 | while (showMonth>0) { 47 | const month = currentTime.month + showMonth; 48 | const demo = Math.floor(month / 13); 49 | const showDateFormat:ShowMonthType = { 50 | year: currentTime.year + demo , 51 | month: month > 12 ? (month % 12 === 0 ? 12 : month % 12) : month, 52 | day: 0, 53 | }; 54 | const curTime = new Date(showDateFormat.year,showDateFormat.month,0).getTime(); 55 | const haveDay = Times.monthHaveDay(curTime); 56 | const week = Times.dateWeek(curTime); 57 | showDateFormat["haveDay"] = haveDay; 58 | showDateFormat["week"] = week; 59 | showDateFormat["currentTime"] = Times.currentTime(curTime); 60 | showDateFormat["currentYearMonth"] = `${showDateFormat.year}年-${showDateFormat.month}月`; 61 | arr.push(showDateFormat) 62 | showMonth--; 63 | } 64 | 65 | arr.push({year: currentTime.year, 66 | month: currentTime.month, 67 | day: currentTime.day, 68 | haveDay: Times.monthHaveDay(props.date), 69 | week: Times.dateWeek(props.date), 70 | currentYearMonth: `${currentTime.year}年-${currentTime.month}月`, 71 | currentTime: Times.currentTime(props.date) 72 | }) 73 | arr = arr.reverse(); 74 | // 列表 75 | const dayListNode = arr.map((item:ShowMonthType, index: number) => { 76 | const before = new Array(item.week).fill(-1); 77 | const after = new Array(item.haveDay).fill(1).map((_: number, index: number) => { 78 | return {day:_ + index, currentTime: Times.currentTime(new Date(`${item.year}/${item.month}/${_+index}`).getTime())}; 79 | }); 80 | const list = [...before, ...after]; 81 | const nodeList = {...item,list}; 82 | return node(nodeList); 83 | }); 84 | setCurrentDay(dayListNode); 85 | } 86 | const node = useCallback((_:ShowMonthType): JSX.Element => { 87 | return (
    88 |

    {_.currentYearMonth}

    89 |
      90 | { _.list?.map((item: any, index: number) => { 91 | return
    • setSelect(item.currentTime)}>{item.day}
    • 93 | }) } 94 |
    95 |
    96 | ) 97 | },[getSelect]) 98 | return ( 99 |
    100 | {getCurrentDay?.map((_: JSX.Element, index: number) => { 101 | return
    {_}
    102 | })} 103 |
    104 | ) 105 | } 106 | 107 | function changeProps(curProps: CalendarProps, nextProps: CalendarProps): boolean{ 108 | console.log(curProps, nextProps, '-------') 109 | if(curProps.date !== nextProps.date) { 110 | return true; 111 | } 112 | return false 113 | } 114 | 115 | export default memo(Calendar, changeProps) 116 | -------------------------------------------------------------------------------- /src/Components/UniversalComponents/FormValidate/FormValidate.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanglijie5997/react-mobile/53fb8d65998ef2b520b0b1c03aea3d3592163a04/src/Components/UniversalComponents/FormValidate/FormValidate.scss -------------------------------------------------------------------------------- /src/Components/UniversalComponents/FormValidate/FormValidate.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useState, useCallback, ChangeEvent, memo } from 'react' 2 | import styles from "./FormValidate.scss"; 3 | import { PropsType, DataType } from './FromValidateType'; 4 | 5 | const FormValidate = (props: PropsType) => { 6 | const [getData, setData] = useState([...props.data]) 7 | 8 | const changeDataFn = useCallback((index: number, value: string) => { 9 | console.log(value, 'event'); 10 | props.changeFn(index, value) 11 | }, [getData]); 12 | 13 | 14 | const validateMemo: JSX.Element[]| JSX.Element = useMemo(() => { 15 | return props.data.map((_: DataType, index: number) => { 16 | return
  • 17 | 18 | ) => changeDataFn(index, event.target.value)}/> 19 |
  • 20 | }) 21 | }, [props.data]) 22 | return ( 23 |
    24 | { validateMemo } 25 |
    26 | ) 27 | } 28 | 29 | function propsChange(curProps: PropsType, nextProps: PropsType): boolean { 30 | if(JSON.stringify(curProps.data) !== JSON.stringify(nextProps)) { 31 | return true; 32 | } 33 | return false; 34 | } 35 | 36 | export default memo(FormValidate, propsChange) 37 | -------------------------------------------------------------------------------- /src/Components/UniversalComponents/FormValidate/FromValidateType.ts: -------------------------------------------------------------------------------- 1 | export interface DataType { 2 | name: string, 3 | value: string , 4 | type: string, 5 | placeholder: string 6 | } 7 | 8 | export interface PropsType { 9 | validateMap: Map boolean>, 10 | toastMap: Map void>, 11 | data: DataType[], 12 | changeFn: (index: number, value: string) => void 13 | } -------------------------------------------------------------------------------- /src/Components/UniversalComponents/ListView/ListView.scss: -------------------------------------------------------------------------------- 1 | .listView{ 2 | position: relative; 3 | background: #ffffff; 4 | border-bottom-left-radius: 20px; 5 | border-bottom-right-radius: 20px; 6 | overflow: hidden; 7 | } 8 | .footText { 9 | text-align: center; 10 | padding: 10px 0; 11 | color: #999999; 12 | font-size: 24px; 13 | height: 30px; 14 | line-height: 30px; 15 | // position: absolute; 16 | // width: 690px; 17 | background: #ffffff; 18 | z-index: 999; 19 | } 20 | .loading { 21 | position: absolute; 22 | display: flex; 23 | justify-content: center; 24 | left: 45%; 25 | // top: -20px; 26 | span { 27 | margin-left: 10px; 28 | } 29 | } -------------------------------------------------------------------------------- /src/Components/UniversalComponents/ListView/ListView.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * ListView 使用说明 「也可以参考vant的api, 基本功能基础一致」 3 | * ```js 4 | * 元素必须有一个根标签包裹 5 | * [参数说明] 6 | * @param offset 100 - 300 触发上啦加载事件 7 | * @param loadingText 等待文本 「可以传入自定义组件」 8 | * @param finishText 完成文本 「可以传入自定义组件」 9 | * @param loading 加载状态 10 | * @param finish 加载完成状态 11 | * @param bodyHeight 元素高度 [可选] 12 | * @param load 触发的加载事件 13 | * ``` 14 | * @example 15 | * const offset = 100; 16 | * const loadingText = "加载中..." 17 | * const finishText = "我是有底线的" 18 | * const [getPage, setPage] = useState(1); 19 | * const [getLoading, setLoading] = useState(true); 20 | * const [getFinish, setFinish] = useState(false); 21 | * const [getDataList, setDataList] = useState([]); // 数据列表, 根据自己的数据类型去更改泛型的值 22 | * // 重置事件 23 | * const reset = () => { 24 | * setPage(1); 25 | * setDataList([]) 26 | * } 27 | * // 加载事件 28 | * const load = async () => { 29 | * const data = await axios.get("xxxxx.com",{ 30 | * page: getPage 31 | * }); 32 | * setDataList(data); 33 | * setPage(getPage + 1); 34 | * } 35 | */ 36 | import React, { Component, useEffect, useRef, useState, useCallback } from 'react' 37 | import { ListViewType } from './ListViewType'; 38 | import styles from "./ListView.scss"; 39 | import { Icon } from 'antd-mobile'; 40 | const MyListView = (props: ListViewType) => { 41 | const [getAnimation, setAnimation] = useState(-1); // requestAnimationFrame实例 用于清除,防止内存泄漏 42 | const [getLoadingStatus, setLoadingStatus] = useState(true); // 加载状态 true 为可继续加载 43 | const listViewRef = useRef(null); // 长列表节点 44 | const tipRef = useRef(null); // 提示节点 45 | 46 | useEffect(() => { 47 | if(!props.finish && !getLoadingStatus) { 48 | props.onload(); 49 | } 50 | 51 | if(getLoadingStatus) { 52 | setTimeout(() => { 53 | window.requestAnimationFrame(requestAnimationFn) 54 | }, 1000) 55 | } 56 | setLoadingStatus(true); 57 | return () => window.cancelAnimationFrame(getAnimation) 58 | }, [getLoadingStatus]) 59 | 60 | const requestAnimationFn = useCallback(() => { 61 | if (tipRef && tipRef.current) { 62 | const clientHeightY = tipRef.current.getBoundingClientRect(); 63 | const windowHeight = window.innerHeight; 64 | // 上啦加载 65 | if(clientHeightY.top - windowHeight < props.offset) { 66 | if(getLoadingStatus) { 67 | window.cancelAnimationFrame(getAnimation); 68 | setLoadingStatus(false); 69 | } 70 | }else{ 71 | const animationStatus = window.requestAnimationFrame(requestAnimationFn); 72 | setLoadingStatus(true) 73 | setAnimation(animationStatus) 74 | } 75 | // 下啦刷新 76 | // if(clientHeightY.top) 77 | } 78 | 79 | },[getLoadingStatus]) 80 | 81 | const loadingCom = (
    82 |

    83 | { 84 | props.loadingText ? <> 85 | { props.loadingText ? props.loadingText : "加载中..." } : null 86 | } 87 | 88 |

    89 |
    ) 90 | 91 | return ( 92 |
    93 | {props ? props["children"] : null} 94 |
    { !props.finish ? loadingCom : props.finishText }
    95 |
    96 | ) 97 | } 98 | export default MyListView; 99 | -------------------------------------------------------------------------------- /src/Components/UniversalComponents/ListView/ListViewType.tsx: -------------------------------------------------------------------------------- 1 | import { RefObject } from 'react'; 2 | 3 | export interface ListViewType { 4 | offset: number, // 距离底部多少开始加载 5 | loadingText: string , // 加载提示文字 6 | finishText: string | JSX.Element, // 完成提示文字 7 | loading: boolean, // 加载状态 8 | finish: boolean, // 加载完成状态 9 | children: JSX.Element, // 组件,可以是任意组件 10 | onload: () => Promise 11 | } 12 | 13 | 14 | export interface ListViewStateType { 15 | getAnimation: number, // requestAnimationFrame实例 16 | } -------------------------------------------------------------------------------- /src/Components/UniversalComponents/Map/Map.tsx: -------------------------------------------------------------------------------- 1 | import React , { useEffect }from 'react'; 2 | 3 | /** 地图组件 */ 4 | const Map = () => { 5 | 6 | return ( 7 |
    8 | {/* */} 9 |
    10 | ) 11 | } 12 | 13 | export default Map; -------------------------------------------------------------------------------- /src/Components/UniversalComponents/Notify/Notify.scss: -------------------------------------------------------------------------------- 1 | .notify { 2 | position: fixed; 3 | width: 50vw; 4 | left: 25vw; 5 | top: -108px; 6 | height: 104px; 7 | border-radius: 20px; 8 | border: 1px solid red; 9 | animation: topTo 2s linear; 10 | .inform { 11 | font-size: 20px; 12 | text-indent: 20px; 13 | } 14 | .msg { 15 | text-indent: 20px; 16 | padding: 20px 0 0 0; 17 | } 18 | } 19 | @keyframes topTo { 20 | 0% { 21 | top: -108px; 22 | } 23 | 100%{ 24 | top: 108px; 25 | } 26 | } -------------------------------------------------------------------------------- /src/Components/UniversalComponents/Notify/Notify.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styles from "./Notify.scss"; 3 | const Notify = (props: {top: number}) => { 4 | return ( 5 |
    6 |

    通知:

    7 |

    你今天迟到了

    8 |
    9 | ) 10 | } 11 | 12 | export default Notify 13 | -------------------------------------------------------------------------------- /src/Components/UniversalComponents/SharePoster/CreateQrcode.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback, useMemo, useEffect, useRef, RefObject} from 'react'; 2 | import QRcode from 'qrcode' 3 | /** 生成二维码组件 4 | * @param props 对象值 5 | * @param url 二维码url 6 | * @param width 二维码宽度 7 | * @param height 二维码高度 8 | */ 9 | const CreateQrcode = (props: {url: string, width: number, height: number}) => { 10 | const [getQrCodeImg, setQrcodeImg] = useState(''); // 生成二维码 11 | const qrCodeRef:RefObject = useRef(null); 12 | useEffect(() => { 13 | createQrcodeImg(); 14 | }, []) 15 | // 生成二维码函数 16 | const createQrcodeImg = useCallback(() => { 17 | // 18 | }, [getQrCodeImg]); 19 | return ( 20 |
    21 | 22 |
    23 | ) 24 | } 25 | 26 | export default CreateQrcode; -------------------------------------------------------------------------------- /src/Hooks/IsIphoneX.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | import { isIphoneX } from '@/Utils/Base/Base'; 3 | 4 | /** 是否iphonex hooks 5 | * @returns true 是 false 否 6 | */ 7 | export const IsIphoneXHook = () => { 8 | const [getIsIphoneX, setIsIphoneX] = useState(false); 9 | useEffect(() => { 10 | const iphoneX = isIphoneX(); 11 | setIsIphoneX(iphoneX); 12 | }, []); 13 | 14 | return getIsIphoneX; 15 | } -------------------------------------------------------------------------------- /src/Page/Home/Home.scss: -------------------------------------------------------------------------------- 1 | .size { 2 | font-size: 16px; 3 | width: 100vw; 4 | height: 100vh; 5 | div { 6 | &:last-child { 7 | padding-bottom: 105px; 8 | } 9 | } 10 | .mask { 11 | font-size: 50px; 12 | position: fixed; 13 | width: 100vw; 14 | height: 100vh; 15 | } 16 | .getData { 17 | width: 100vw; 18 | text-align: center; 19 | font-size: 36px; 20 | color: red; 21 | } 22 | } 23 | 24 | .img { 25 | width: 240px; 26 | height: 240px; 27 | } 28 | 29 | .show { 30 | overflow: inherit; 31 | } 32 | .hide { 33 | overflow: hidden; 34 | height: 100vh; 35 | } 36 | .bgImg { 37 | width: 100px; 38 | display: block; 39 | } 40 | .positon { 41 | position: fixed; 42 | top: 0; 43 | } 44 | -------------------------------------------------------------------------------- /src/Page/Home/Home.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useCallback, useState, useRef} from 'react'; 2 | import styles from "./Home.scss"; 3 | import LazyLoad from "react-lazyload"; 4 | import { GetMusicUrltype } from "@Utils/HttpList/HomeHttp/HomeHttpType"; 5 | import { RouteComponentProps } from 'react-router'; 6 | import { getMusicUrl } from '@/Utils/HttpList/HomeHttp/HomeHttp'; 7 | 8 | 9 | const Home = (props: RouteComponentProps) => { 10 | const [getLlist, setList] = useState(new Array(100).fill(null).map((_, index: number) => index)); // 图片懒加载示例 11 | const [getTouchmoveStatus, setTouchmoveStatus] = useState(false); // touchmove 状态 12 | const [getMusicList, setMusicList] = useState(); // 数据类型 13 | const [getShow, setShow] = useState(false); 14 | const toastRef = useRef(null); 15 | // 这里只请求一次 16 | useEffect(() => { 17 | const observing = new IntersectionObserver(function(entries: any[]) { 18 | 19 | console.log(entries[0].intersectionRatio, 'entries'); 20 | if (entries[0].intersectionRatio <= 0) { 21 | setShow(true); 22 | return; 23 | }else { 24 | // setShow(false) 25 | return; 26 | } 27 | 28 | }); 29 | observing.observe(document.getElementById("toast")!); 30 | homeListHttp(); 31 | return () => document.body.removeEventListener("touchmove", slideEvent) 32 | }, []); 33 | 34 | // 根据依赖作出反应 35 | useEffect(() => { 36 | if(getMusicList) { 37 | console.log(getMusicList, '---音乐类型---') 38 | } 39 | toastRef.current?.addEventListener("click", (event) => { 40 | console.log(event, "???"); 41 | }); 42 | 43 | return () => { /***/ } 44 | // return () => homeListHttp(); 45 | },[getMusicList]); 46 | 47 | // 请求数据 48 | const homeListHttp = useCallback(async () => { 49 | const data = await getMusicUrl('15'); 50 | // setMusicList(data); 51 | }, [getMusicList]); 52 | 53 | // 监听回调 54 | const slideEvent = (e: any) => { 55 | e.preventDefault(); 56 | e.stopPropagation(); 57 | setTouchmoveStatus(true); 58 | document.body.style.overflow = 'hidden' 59 | 60 | } 61 | 62 | const toastShow = useCallback(() => { 63 | // Toast.fail("success", 1,() => { 64 | // document.body.removeEventListener("touchmove", slideEvent); 65 | // document.body.style.overflow = "inherit"; 66 | // setTouchmoveStatus(false); 67 | // }, true); 68 | document.body.addEventListener("touchmove", slideEvent, {passive: false}); 69 | },[]) 70 | 71 | const lazyLoadList: JSX.Element[] = getLlist.map((item: number, index: number) => { 72 | return (
    73 | 74 | 75 | 76 |
    ) 77 | }) 78 | 79 | return ( 80 |
    81 | {/* */} 82 | 83 | 84 | {(lazyLoadList)} 85 | Home1315ff 86 |
    87 | ); 88 | } 89 | 90 | export default Home; 91 | -------------------------------------------------------------------------------- /src/Page/Login/Login.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanglijie5997/react-mobile/53fb8d65998ef2b520b0b1c03aea3d3592163a04/src/Page/Login/Login.scss -------------------------------------------------------------------------------- /src/Page/Login/Login.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useCallback, Fragment } from 'react' 2 | import { RouteComponentProps } from 'react-router' 3 | import { onFocusFn, onBlurFn } from 'src/Utils/Base/IosQuestion'; 4 | 5 | const Login = (props: RouteComponentProps) => { 6 | 7 | return ( 8 | <> 9 | 登录 10 | 11 | 12 | ) 13 | } 14 | export default Login; 15 | -------------------------------------------------------------------------------- /src/Page/Page.tsx: -------------------------------------------------------------------------------- 1 | import { RouteConfigType } from "./PageType"; 2 | import loadable from "@loadable/component"; 3 | const Login = loadable(() => import(/* webpackChunkName: "Login" */ "./Login/Login" )) 4 | const Home = loadable(() => import(/* webpackChunkName: "Home" */ "./Home/Home")); 5 | const User = loadable(() => import(/* webpackChunkName: "User" */"./User/User")); 6 | const Shop = loadable(() => import(/* webpackChunkName: "Shop" */"./Shop/Shop")); 7 | const Reservation = loadable(() => import(/* webpackChunkName: "Reservation" */"./Reservation/Reservation")); 8 | 9 | import home from "@Static/Images/AppBar/heigshouye_9_12.9@2x.png"; // 首页未选中 10 | import homeSelect from "@Static/Images/AppBar/hongshouye_9_12.9@2x.png"; // 首页选中 11 | import shop from "@Static/Images/AppBar/heishangpin_9_12.9@2x.png"; // 商品未选中 12 | import shopSelect from "@Static/Images/AppBar/hongishangpin_9_12.9@2x.png"; // 商品选中 13 | import vip from "@Static/Images/AppBar/heihuiyuan_9_12.9@2x.png"; // vip选中 14 | import vipSelect from "@Static/Images/AppBar/honghuiyuan_9_12.9@2x.png"; // vip选中 15 | import reservation from "@Static/Images/AppBar/heiyuyue_9_12.9@2x.png"; // 预约未选中 16 | import reservationSelect from "@Static/Images/AppBar/hongyuyue_9_12.9@2x.png"; // 预约选中 17 | 18 | // 底部路由配置 19 | export const bottomRouterConfig: RouteConfigType[] = [ 20 | { 21 | path: "/index", 22 | excat: true, 23 | component: Home, 24 | name: "主页", 25 | defaultImg: home, 26 | selectImg: homeSelect, 27 | meta: { 28 | title: "主页", 29 | requiresAuth: false 30 | } 31 | }, 32 | { 33 | path: "/shop", 34 | excat: true, 35 | component: Shop, 36 | name: "商品", 37 | defaultImg: shop, 38 | selectImg: shopSelect, 39 | meta: { 40 | title: "商品", 41 | requiresAuth: false 42 | } 43 | }, 44 | { 45 | path: "/reservation", 46 | excat: true, 47 | component: Reservation, 48 | name: "预约", 49 | defaultImg: reservation, 50 | selectImg: reservationSelect, 51 | meta: { 52 | title: "预约", 53 | requiresAuth: false 54 | } 55 | }, 56 | { 57 | path: "/user", 58 | excat: true, 59 | component: User, 60 | name: "会员", 61 | defaultImg: vip, 62 | selectImg: vipSelect, 63 | meta: { 64 | title: "会员", 65 | requiresAuth: false 66 | } 67 | } 68 | ] 69 | 70 | // 默认路由配置数组 71 | const defaultPage: RouteConfigType = { 72 | path: "*", 73 | excat: false, 74 | component: Home, 75 | meta: { 76 | title: "主页", 77 | requiresAuth: false 78 | } 79 | }; 80 | 81 | // 其他路由配置数组, 新增加的路由只需要在这里添加 82 | const routerConfigOutherPage:RouteConfigType[] = [ 83 | { 84 | path: "/login", 85 | excat: false, 86 | component: Login, 87 | meta: { 88 | title: "登录", 89 | requiresAuth: false 90 | } 91 | } 92 | ]; 93 | 94 | export const routerConfig = routerConfigOutherPage.concat(defaultPage); 95 | 96 | -------------------------------------------------------------------------------- /src/Page/PageType.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { LoadableComponent } from '@loadable/component'; 3 | import { RouteComponentProps, StaticContext } from 'react-router'; 4 | 5 | 6 | // 路由类型 7 | export interface RouteConfigType { 8 | path: string, // 路径 9 | excat: boolean,// 严格匹配还是非严格匹配 10 | component: LoadableComponent>, // 组件名称 11 | name?: string,// 底部appBar参数 12 | defaultImg?: string,// 默认图片 13 | selectImg?: string, // 选中图片 14 | meta: { 15 | title: string, // document.title 字段 16 | requiresAuth: boolean,// 是否需要登录 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Page/Reservation/Reservation.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { RouteComponentProps } from 'react-router'; 3 | import Calendar from "@Components/UniversalComponents/Calendar/Calendar"; 4 | import Notify from "@Components/UniversalComponents/Notify/Notify"; 5 | const Reservation = (props: RouteComponentProps) => { 6 | return ( 7 |
    8 | 9 | 10 |
    11 | ) 12 | } 13 | export default Reservation; 14 | -------------------------------------------------------------------------------- /src/Page/Shop/Shop.scss: -------------------------------------------------------------------------------- 1 | .shop { 2 | width: 690px; 3 | border-radius: 30px; 4 | margin: 20px auto; 5 | background: #ffffff; 6 | padding-top: 30px; 7 | padding-bottom: 30px; 8 | .christmas { 9 | width: 600px; 10 | height: 600px; 11 | margin: 0 auto; 12 | position: relative; 13 | .top { 14 | width: 100%; 15 | display: flex; 16 | } 17 | .right { 18 | position: absolute; 19 | right: 0; 20 | 21 | } 22 | .bottom { 23 | position: absolute; 24 | bottom: 0; 25 | display: flex; 26 | right: 150px; 27 | 28 | } 29 | .left { 30 | position: absolute; 31 | left: 0; 32 | top: 150px; 33 | } 34 | .active { 35 | -webkit-filter: drop-shadow(6px 6px 10px green) blur(2px); 36 | filter: drop-shadow(6px 6px s10px green) blur(2px); 37 | transform: scale(1.08); 38 | } 39 | } 40 | .childNode { 41 | width: 150px; 42 | height: 150px; 43 | // background: red; 44 | // flex: 1; 45 | // border: 1px solid #999999; 46 | background: url(http://pic.90sjimg.com/design/00/07/85/23/5a3c63e0854b9.png); 47 | background-size: cover; 48 | } 49 | } -------------------------------------------------------------------------------- /src/Page/Shop/Shop.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useCallback, useState, useEffect } from 'react' 2 | import { RouteComponentProps } from 'react-router'; 3 | import styles from "./Shop.scss"; 4 | const Shop = (props: RouteComponentProps) => { 5 | const [getActive, setActive] = useState(0); // 选中的颜色 6 | const christmasRef = useRef(null); 7 | const [getTime, setTime] = useState(); 8 | useEffect(() => { 9 | console.log(getActive, '////'); 10 | }, [getActive]) 11 | const playGame = useCallback(() => { 12 | if(christmasRef && christmasRef.current) { 13 | console.log(christmasRef.current.getElementsByClassName("Shop_childNode")); 14 | // 获取所有移动对象集合 15 | const childNodeList = christmasRef.current.getElementsByClassName("Shop_childNode"); 16 | 17 | if(getTime) { 18 | clearInterval(getTime); 19 | } 20 | // const time = setInterval(() => { 21 | // const active = getActive + 1; 22 | // const arr = Array.prototype.slice.apply(childNodeList); 23 | // arr.forEach((item: any, index: number) => { 24 | // if(+item.attributes["data-index"].value === getActive) { 25 | // arr[getActive].className = "Shop_childNode Shop_active" 26 | // }else { 27 | // arr[index].className = "Shop_childNode" 28 | // } 29 | // }) 30 | // console.log(childNodeList[+getActive]); 31 | // setActive(active); 32 | // }, 500); 33 | // setTime(time) 34 | 35 | 36 | // 给移动的增加样式 37 | for(let i =0;i < childNodeList.length; i++) { 38 | console.log(+childNodeList[i].attributes["data-index"].value); 39 | } 40 | } 41 | }, [christmasRef, getActive]) 42 | return ( 43 |
    44 |
    45 |
    46 |
    47 |
    48 |
    49 |
    50 |
    51 |
    52 |
    53 |
    54 |
    55 |
    56 |
    57 |
    58 |
    59 |
    60 | 61 |
    62 |
    63 |
    64 |
    65 |
    66 |
    67 | 68 | 69 |
    70 | ) 71 | } 72 | export default Shop; 73 | -------------------------------------------------------------------------------- /src/Page/User/User.scss: -------------------------------------------------------------------------------- 1 | .user { 2 | .container { 3 | 4 | .top { 5 | height: 30px; 6 | background-image: linear-gradient(blue, blue,blue); 7 | -webkit-background-clip:text; 8 | background-clip: text; 9 | // background-position: 0 20px, 0 100px ; 10 | color: transparent; 11 | background-repeat: no-repeat; 12 | } 13 | .bottom { 14 | height: 50px; 15 | } 16 | // .text { 17 | // // position: absolute; 18 | // top: 0; 19 | // z-index: 10; 20 | // } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Page/User/User.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, Dispatch, useState } from 'react'; 2 | import { useEffect } from 'react'; 3 | import { useDispatch } from "react-redux"; 4 | import { userStatusAction } from "src/Redux/Actions/Actions"; 5 | import { Action } from 'redux-actions'; 6 | import styles from "./User.scss"; 7 | import FormValidate from '@/Components/UniversalComponents/FormValidate/FormValidate'; 8 | import { DataType } from '@/Components/UniversalComponents/FormValidate/FromValidateType'; 9 | import { Toast } from 'antd-mobile'; 10 | 11 | const User = (props: any) => { 12 | const dispatch: Dispatch> = useDispatch(); // 使用 redux的 actions 方法 13 | const [getValidate, setValidate] = useState boolean>>(new Map([ 14 | [0, (str: string) => str.length <= 11 && str.length > 0] 15 | ])); 16 | const [getToast, setToast] = useState void>>(new Map([ 17 | [0, () => Toast.info("请输入小于12位的手机号码")] 18 | ])); 19 | const [getData, setData] = useState([ 20 | { name: "mobile", value: "", type: "number", placeholder: "请输入手机号"} 21 | ]) 22 | useEffect(() => { 23 | console.log(props, '///'); 24 | dispatchUserStatus() 25 | },[]); 26 | 27 | const changeDataFn = useCallback((index: number, value: string)=> { 28 | const data = getData; 29 | data[index].value = value; 30 | setData(data); 31 | }, [getData]); 32 | 33 | const submit = useCallback(() => { 34 | let result: boolean = true; 35 | for (let index = 0; index < getData.length; index++) { 36 | const validateResult: boolean = getValidate.get(index)!(getData[0].value); 37 | if(!validateResult) { 38 | result = validateResult; 39 | getToast.get(index)!(); 40 | break; 41 | } 42 | } 43 | console.log(result, 'result'); 44 | }, [getData]); 45 | 46 | // 更改用户登陆状态, [此处仅作为示例] 47 | const dispatchUserStatus = useCallback(() => { 48 | // 触发dispatch方法, 更改用户登陆状态 49 | dispatch(userStatusAction(true)) 50 | }, [dispatch]); 51 | 52 | return ( 53 |
    54 |
    55 | 56 | 57 |
    58 |
    59 | ); 60 | } 61 | 62 | export default User; 63 | -------------------------------------------------------------------------------- /src/Redux/Actions/Actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from "redux-actions"; 2 | import { ActionsEnum } from '../Types/Types'; 3 | import { LocationType } from '../State/StateType'; 4 | 5 | // 设置用户状态异步方法 6 | const userStatusAction = createAction(ActionsEnum.UserStatusAction, (status: boolean) => status); 7 | 8 | // 设置用户地理位置 9 | const userLocalAction = createAction(ActionsEnum.userLocalAction, (state: LocationType) => state); 10 | 11 | // 是否iphoneX 12 | const iphoneXAction = createAction(ActionsEnum.iphoneXAction, (state: boolean) => state) 13 | 14 | export { 15 | userStatusAction, 16 | userLocalAction, 17 | iphoneXAction 18 | } -------------------------------------------------------------------------------- /src/Redux/Actions/HandleAction.ts: -------------------------------------------------------------------------------- 1 | import { handleActions, Action } from "redux-actions"; 2 | import { userStatus, userLocal, isIphoneX } from '../State/State'; 3 | import { ActionsEnum } from '../Types/Types'; 4 | import { LocationType } from '../State/StateType'; 5 | 6 | // 用户状态 7 | const userStatusReducer = handleActions({ 8 | [ActionsEnum.UserStatusAction]: (state: boolean, action: Action) => { 9 | // 需要持久保留的状态存在localStorage里, 不需要持久保存的存在sessionStorage里; 10 | localStorage.setItem("userStatus", JSON.stringify(action.payload)) 11 | return action.payload 12 | } 13 | }, userStatus); 14 | 15 | // 用户地理位置 16 | const userLocaltionReducer = handleActions({ 17 | [ActionsEnum.userLocalAction]: (state: LocationType, action: Action) => { 18 | sessionStorage.setItem("userLocal", JSON.stringify(action.payload)); 19 | return action.payload; 20 | } 21 | }, userLocal) 22 | 23 | // 是否iphoneX 24 | const isIphoneXReducer = handleActions({ 25 | [ActionsEnum.iphoneXAction]: (state: boolean, action: Action) => { 26 | localStorage.setItem("isIphoneX", JSON.stringify(action.payload)); 27 | return action.payload; 28 | } 29 | }, isIphoneX) 30 | 31 | export { 32 | userStatusReducer, 33 | userLocaltionReducer, 34 | isIphoneXReducer 35 | } -------------------------------------------------------------------------------- /src/Redux/Reducer/Reducer.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux"; 2 | import { userStatusReducer, userLocaltionReducer, isIphoneXReducer } from "../Actions/HandleAction"; 3 | 4 | export default { 5 | // 所有新增加的reducer都需要在这里注入 6 | combineReducers: combineReducers({ 7 | userStatusReducer, 8 | userLocaltionReducer, 9 | isIphoneXReducer 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /src/Redux/State/State.ts: -------------------------------------------------------------------------------- 1 | import { LocationType } from './StateType'; 2 | 3 | export const userStatus: boolean = localStorage.getItem("userStatus") ? 4 | localStorage.getItem("userStatus") === "true" ? true : false 5 | : false; // 用户状态 6 | 7 | export const token: string = ''; // 用户token 8 | 9 | export const userLocal: LocationType = sessionStorage.getItem("userLocal") ? JSON.parse(sessionStorage.getItem("userLocal")!) : { 10 | addressComponent: { 11 | adcode: "", // 城市编码 12 | city: "", // 城市 13 | district: "",// 区 14 | neighborhood: "", // 区的具体位置 15 | province: "",// 省份 16 | street: "", // 街道 17 | township: "", // 街道名称 18 | streetNumber: "", // 门牌号 19 | }, 20 | position: { 21 | lat: 0, // 纬度 22 | lng: 0, // 经度 23 | } 24 | }; 25 | 26 | // 是否iphoneX 27 | export const isIphoneX: boolean = JSON.parse(localStorage.getItem('isIphoneX')!) || false; -------------------------------------------------------------------------------- /src/Redux/State/StateType.ts: -------------------------------------------------------------------------------- 1 | /** 地图类型 */ 2 | export interface LocationType { 3 | addressComponent: { 4 | adcode: string, // 城市编码 5 | city: string, // 城市 6 | district: string,// 区 7 | neighborhood: string, // 区的具体位置 8 | province: string,// 省份 9 | street: string, // 街道 10 | township: string, // 街道名称 11 | streetNumber: string, // 门牌号 12 | }, 13 | position: { 14 | lat: number, // 纬度 15 | lng: number, // 经度 16 | } 17 | } 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Redux/Store/Store.ts: -------------------------------------------------------------------------------- 1 | import { Store, applyMiddleware , createStore} from "redux"; 2 | import thunkMiddleware from "redux-thunk"; 3 | import { routerMiddleware } from "react-router-redux"; 4 | import { History } from "history"; 5 | import Reducers from "../Reducer/Reducer"; 6 | 7 | // 创建集中管理stroe 8 | const configureStore = (history: History, initState?: string):Store => { 9 | const middleware = applyMiddleware(thunkMiddleware, routerMiddleware(history)); 10 | return createStore( 11 | Reducers.combineReducers, 12 | middleware 13 | ) as Store; 14 | } 15 | 16 | export default configureStore; -------------------------------------------------------------------------------- /src/Redux/Types/Types.ts: -------------------------------------------------------------------------------- 1 | // 枚举所有redux 方法名 2 | export enum ActionsEnum { 3 | UserStatusAction = 'UserStatusAction', 4 | userLocalAction = 'userLocalAction', 5 | iphoneXAction = 'iphoneXAction' 6 | } -------------------------------------------------------------------------------- /src/Router/Router.scss: -------------------------------------------------------------------------------- 1 | 2 | .appBar { 3 | position: fixed; 4 | bottom: 0; 5 | height:100px; 6 | // padding-bottom: calc(env(safe-area-inset-bottom)); 7 | width: 100vw; 8 | background: #ffffff; 9 | 10 | // 适配iphoneX 11 | // bottom: calc(); 12 | .navBox { 13 | // width: 590px; 14 | // margin: 0 auto; 15 | display: flex; 16 | } 17 | a { 18 | flex: 1; 19 | height: 100%; 20 | div { 21 | margin: .16px auto; 22 | } 23 | } 24 | .navItem { 25 | flex: 1; 26 | height: 100%; 27 | .navitemBox { 28 | // padding: 16px 143px 12px 0; 29 | img { 30 | width: 40px; 31 | height: 40px; 32 | margin-bottom: 12px; 33 | display: block; 34 | margin: 16px auto 16px; 35 | } 36 | p { 37 | margin: 0; 38 | text-align: center; 39 | font-size: 20px; 40 | white-space: nowrap; 41 | color: black; 42 | } 43 | } 44 | } 45 | } 46 | .selectColor { 47 | color: #e81844!important; 48 | } 49 | .oageView { 50 | height: 100%; 51 | } 52 | /* x xs */ 53 | @media only screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) { 54 | // iphoneX iphoneXS样式 55 | .appBar { 56 | padding-bottom: 15px; 57 | .navBox { 58 | height: 115px; 59 | } 60 | } 61 | } 62 | /* xr */ 63 | @media only screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) { 64 | // iphoneXR样式 65 | .appBar { 66 | padding-bottom: 15px; 67 | .navBox { 68 | height: 115px; 69 | } 70 | } 71 | } 72 | /* xs max */ 73 | @media only screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) { 74 | // iphoneXR样式 75 | .appBar { 76 | padding-bottom: 15px; 77 | .navBox { 78 | height: 115px; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /src/Router/Router.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect ,useState, useCallback } from "react"; 2 | import { RouteComponentProps, } from 'react-router'; 3 | import { routerConfig, bottomRouterConfig } from '../Page/Page'; 4 | import { Route, Switch, Redirect, NavLink, Link} from "react-router-dom"; 5 | import { RouteConfigType } from '../Page/PageType'; 6 | import { useSelector } from "react-redux"; 7 | import styles from "./Router.scss"; 8 | import { Toast } from 'antd-mobile'; 9 | 10 | const RouterPage: (props: RouteComponentProps) => JSX.Element = (props: RouteComponentProps) => { 11 | const [getRouterConfigPage, setRouterConfigPage] = useState(bottomRouterConfig.concat(routerConfig)); // 路由配置 12 | const [getShowAppBar, setShowAppBar] = useState(["/user", "/index", "/", "/shop", "/reservation"]); 13 | const [getSelectPage, setSelectPage] = useState(-1); // 当前选择页面 14 | const [getPage, setPage] = useState("/index"); 15 | const userStatus = useSelector((state: { 16 | userStatusReducer: boolean, 17 | isIphoneXReducer: boolean 18 | }) => ({userStatusReducer: state.userStatusReducer, isIphoneXReducer: state.isIphoneXReducer})); // 用户登陆状态, 此处为获取redux state参数,可以用对象获取自己需要的参数 19 | 20 | useEffect(() => { 21 | const pageSelect: number = nowPage.get(props.location.pathname)!(); 22 | setSelectPage(pageSelect); 23 | const page = props.location.pathname; 24 | setPage(page); 25 | }, [userStatus, props.location]); 26 | 27 | // 当前所处页面Map对象 28 | const nowPage:Map number> = new Map([ 29 | ["/", () => 0], 30 | ["/index", () => 0], 31 | ["/shop", () => 1], 32 | ["/reservation", () => 2], 33 | ["/user", () => 3], 34 | ]); 35 | 36 | const navgiation = useCallback((index: number, to: string ) => { 37 | setSelectPage(index); 38 | if(getPage === to) { 39 | return 40 | }else { 41 | setPage(to); 42 | props.history.push(to) 43 | } 44 | },[getPage]) 45 | 46 | // 导航, 重定向路由不显示在页面 47 | const routerNav: JSX.Element[] = bottomRouterConfig.map((item: RouteConfigType, index: number) => { 48 | return
    navgiation(index, item.path)}> 49 |
    50 | 51 |

    {item.meta.title}

    52 |
    53 |
    54 | }) 55 | // 路由页面 56 | const routerPage: JSX.Element[] = getRouterConfigPage.map((item: RouteConfigType, index: number) => { 57 | return { 58 | if(!item.meta.requiresAuth || item.path === "/login") { 59 | document.title = item.meta.title; 60 | return 61 | } 62 | // 用户没有登录的情况 63 | Toast.fail("请先登录", 3, () => { 64 | 65 | }) 66 | return 67 | }}/> 68 | }); 69 | return ( 70 |
    71 | {/* 路由页面 */} 72 |
    73 | 74 | {routerPage} 75 | 76 |
    77 | {/* 导航页 */} 78 | { getShowAppBar.includes(props.location.pathname) ?
    79 |
    {routerNav}
    80 |
    : null } 81 | 82 |
    83 | ) 84 | } 85 | export default RouterPage; -------------------------------------------------------------------------------- /src/Static/Css/Base.scss: -------------------------------------------------------------------------------- 1 | /* 两端对齐 */ 2 | :global(.spaceBetween) { 3 | display: flex; 4 | justify-content: space-between; 5 | } 6 | /* 文字显示2行 */ 7 | :global(.column2Text){ 8 | display: -webkit-box; 9 | overflow: hidden; 10 | line-clamp: 2; 11 | text-overflow: ellipsis; 12 | -webkit-box-orient: vertical; 13 | } 14 | -------------------------------------------------------------------------------- /src/Static/Css/Reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } 49 | *:focus { outline: none; } 50 | html body { 51 | width: 100vw; 52 | overflow-x: hidden; 53 | } 54 | div { 55 | cursor: pointer; 56 | } 57 | li { 58 | list-style: none; 59 | } 60 | -------------------------------------------------------------------------------- /src/Static/Images/AppBar/heigshouye_9_12.9@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanglijie5997/react-mobile/53fb8d65998ef2b520b0b1c03aea3d3592163a04/src/Static/Images/AppBar/heigshouye_9_12.9@2x.png -------------------------------------------------------------------------------- /src/Static/Images/AppBar/heihuiyuan_9_12.9@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanglijie5997/react-mobile/53fb8d65998ef2b520b0b1c03aea3d3592163a04/src/Static/Images/AppBar/heihuiyuan_9_12.9@2x.png -------------------------------------------------------------------------------- /src/Static/Images/AppBar/heishangpin_9_12.9@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanglijie5997/react-mobile/53fb8d65998ef2b520b0b1c03aea3d3592163a04/src/Static/Images/AppBar/heishangpin_9_12.9@2x.png -------------------------------------------------------------------------------- /src/Static/Images/AppBar/heiyuyue_9_12.9@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanglijie5997/react-mobile/53fb8d65998ef2b520b0b1c03aea3d3592163a04/src/Static/Images/AppBar/heiyuyue_9_12.9@2x.png -------------------------------------------------------------------------------- /src/Static/Images/AppBar/honghuiyuan_9_12.9@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanglijie5997/react-mobile/53fb8d65998ef2b520b0b1c03aea3d3592163a04/src/Static/Images/AppBar/honghuiyuan_9_12.9@2x.png -------------------------------------------------------------------------------- /src/Static/Images/AppBar/hongishangpin_9_12.9@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanglijie5997/react-mobile/53fb8d65998ef2b520b0b1c03aea3d3592163a04/src/Static/Images/AppBar/hongishangpin_9_12.9@2x.png -------------------------------------------------------------------------------- /src/Static/Images/AppBar/hongshouye_9_12.9@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanglijie5997/react-mobile/53fb8d65998ef2b520b0b1c03aea3d3592163a04/src/Static/Images/AppBar/hongshouye_9_12.9@2x.png -------------------------------------------------------------------------------- /src/Static/Images/AppBar/hongyuyue_9_12.9@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanglijie5997/react-mobile/53fb8d65998ef2b520b0b1c03aea3d3592163a04/src/Static/Images/AppBar/hongyuyue_9_12.9@2x.png -------------------------------------------------------------------------------- /src/Static/Images/Home/shouyejinsedianhuahao_9_12.9@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanglijie5997/react-mobile/53fb8d65998ef2b520b0b1c03aea3d3592163a04/src/Static/Images/Home/shouyejinsedianhuahao_9_12.9@2x.png -------------------------------------------------------------------------------- /src/Static/Images/Home/shouyejinsedingwe_9_12.9@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanglijie5997/react-mobile/53fb8d65998ef2b520b0b1c03aea3d3592163a04/src/Static/Images/Home/shouyejinsedingwe_9_12.9@2x.png -------------------------------------------------------------------------------- /src/Static/Images/christmas.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanglijie5997/react-mobile/53fb8d65998ef2b520b0b1c03aea3d3592163a04/src/Static/Images/christmas.jpg -------------------------------------------------------------------------------- /src/Utils/Axios/Axios.ts: -------------------------------------------------------------------------------- 1 | import axios , { AxiosInstance, AxiosRequestConfig, AxiosResponse, CancelTokenSource, CancelTokenStatic, Canceler}from "axios"; 2 | import queryString from "qs"; 3 | import { Toast } from "antd-mobile"; 4 | import { baseUrl } from "./AxiosConfig"; 5 | 6 | const mapList:Map= new Map(); 7 | 8 | let cancelResult = null; 9 | 10 | // 取消http请求,防止多次点击触发 11 | const cancelToken:CancelTokenStatic = axios.CancelToken; 12 | 13 | /** 请求拦截, 相应拦截 14 | * @param url 请求路径 15 | * @param axiosInit axios实例 16 | */ 17 | const httpInit = async (url: string, axiosInit: AxiosInstance) => { 18 | 19 | axiosInit.interceptors.request.use((config: AxiosRequestConfig) => { 20 | // 不存在set一个对象 21 | if(!mapList.get(url)) { 22 | mapList.set(url, url); 23 | }else { 24 | // 存在则取消上一个请求 25 | config.cancelToken = new cancelToken((cancel: Canceler) => { 26 | mapList.delete(url); 27 | cancelResult = cancel; 28 | }); 29 | } 30 | // doSomthing 31 | return config; 32 | }); 33 | axiosInit.interceptors.response.use((config: AxiosResponse) => { 34 | if(mapList.has(url)) { 35 | mapList.delete(url); 36 | } 37 | return Promise.resolve(config.data); 38 | }, (err: Error) => { 39 | Toast.fail("网络错误, 请刷新重试"); 40 | }) 41 | }; 42 | 43 | /** 创建axios请求对象 44 | * @param url 请求路径 45 | * @param params 请求参数 46 | */ 47 | const init = async (url: string, paramsData: any): Promise => { 48 | const haveAxios: string = mapList.get(url)!; 49 | console.log(haveAxios, 'mmm'); 50 | const axiosInit = axios.create({ 51 | baseURL: baseUrl, 52 | method: "post", 53 | timeout: 10000, 54 | // withCredentials: false, // 表示跨域请求时是否需要使用凭证 55 | }); 56 | await httpInit(url, axiosInit); 57 | return axiosInit(url, paramsData); 58 | } 59 | 60 | /** http 请求 61 | * @param url 请求地址 62 | * @param params 请求参数 63 | * @param method get | post 64 | */ 65 | const httpConnect = (url: string, params: any, method?: string) => { 66 | return init(url,{ 67 | method: method || "post", 68 | data: queryString.stringify(params) 69 | }) 70 | } 71 | 72 | export default httpConnect; -------------------------------------------------------------------------------- /src/Utils/Axios/AxiosConfig.ts: -------------------------------------------------------------------------------- 1 | 2 | // 当前环境判断 3 | const nowEnv = process.env.NODE_ENV as string; 4 | 5 | const envMap: Map = new Map([ 6 | ["development", "http://localhost:8091"], 7 | ["production", "http://localhost:5000"], 8 | ["testProduction", "testProduction" ] 9 | ]) 10 | 11 | // axios 请求url 12 | export const baseUrl = envMap.get(nowEnv); -------------------------------------------------------------------------------- /src/Utils/Base/Base.ts: -------------------------------------------------------------------------------- 1 | import { DateFormatType } from './BaseTypes'; 2 | import { Toast } from 'antd-mobile'; 3 | 4 | export const mapKey = "3180595fffa04cb3a0988d48ffad5422"; // 高德地图key值 5 | 6 | /** 防抖 7 | * @param fn 回掉函数 8 | * @param time 时间 9 | * @example 10 | * debounce(() => { 11 | * // 做些什么 12 | * }, 500) 13 | */ 14 | export const debounce = (fn: any, time: number) => { 15 | let timer: NodeJS.Timeout | null = null; 16 | // tslint:disable-next-line:only-arrow-functions 17 | return function () { 18 | const self = debounce; 19 | const args = arguments; 20 | if(timer){ 21 | clearTimeout(timer); 22 | timer = null; 23 | }; 24 | timer = setTimeout(() => { 25 | fn.apply(self, args); 26 | }, time) 27 | } 28 | } 29 | 30 | /** 函数节流 31 | * @param fn 回调函数 32 | * @param time 时间 33 | * @example 34 | * throttle(() => { 35 | * // 做些什么 36 | * }, 1000) 37 | */ 38 | export const throttle = (fn:any, time: number) => { 39 | let timer: NodeJS.Timeout | null = null;; 40 | let lastTime: number = Date.now(); 41 | return function() { 42 | const nowTimer: number = Date.now(); 43 | const args = arguments; 44 | const context = throttle; 45 | if(nowTimer - lastTime > time) { 46 | if(time) { 47 | clearTimeout(timer!); 48 | } 49 | fn.apply(context, args); 50 | lastTime = Date.now(); 51 | }else { 52 | if(!timer) { 53 | timer = setTimeout(() => { 54 | fn.apply(context, args); 55 | lastTime = Date.now(); 56 | }, time - (nowTimer - lastTime)) 57 | } 58 | } 59 | } 60 | } 61 | 62 | /** 小数点经度问题,保留2位小数 63 | * @param str 数据 64 | */ 65 | export const decimal: (str: number) => number = (str: number) => { 66 | // 数字类型转换字符串 67 | const newStr: string = str.toString(); 68 | // 截取.前面字符串 69 | const pointBeforeReg:RegExp = /\d+\./g; 70 | const pointBefore: string = newStr.match(pointBeforeReg)![0]; 71 | // 截取.后面的2位 72 | const pointAfterReg:RegExp = /\.(\d+)/g; 73 | const pointAfter: string = newStr.match(pointAfterReg)![0].slice(1, 3); 74 | return Number(pointBefore + pointAfter); 75 | } 76 | /**时间格式化 77 | * @param day 当前时间 78 | */ 79 | export const dateFormat: (day: Date | number) => DateFormatType = (day: Date | number) => { 80 | let nowDayWeek: Date = new Date(); 81 | 82 | if(toString.call(day) === "[object Number]") { 83 | if(day.toString().length < 13) { 84 | 85 | day = Number(day); 86 | } 87 | nowDayWeek = new Date(day) 88 | }else { 89 | nowDayWeek = day as Date; 90 | } 91 | const year = nowDayWeek.getFullYear(); 92 | const month = nowDayWeek.getMonth() + 1; 93 | const nowDay = nowDayWeek.getDate(); 94 | const hours = nowDayWeek.getHours(); 95 | const minutes = nowDayWeek.getMinutes(); 96 | const seconds = nowDayWeek.getSeconds(); 97 | const milliseconds = nowDayWeek.getMilliseconds(); 98 | return { 99 | year, 100 | month, 101 | nowDay, 102 | hours, 103 | minutes, 104 | seconds, 105 | milliseconds 106 | } 107 | } 108 | 109 | // 获取url参数 110 | export const getQueryVariable = (variable:string) => { 111 | const query = window.location.search.substring(1); 112 | const vars = query.split("&"); 113 | for (let i=0;i { 125 | const backingStore = context ? context.backingStorePixelRatio || 126 | context.webkitBackingStorePixelRatio || 127 | context.mozBackingStorePixelRatio || 128 | context.msBackingStorePixelRatio || 129 | context.oBackingStorePixelRatio || 130 | context.backingStorePixelRatio || 1 : 1; 131 | return (window.devicePixelRatio || 1) / backingStore; 132 | } 133 | 134 | /** 图片转base64 135 | * @param src 图片路径 136 | */ 137 | export const imgToBase64 = (src: string) => { 138 | return new Promise((resolve, reject) => { 139 | const img = new Image(); 140 | img.crossOrigin = 'Anonymous'; 141 | img.src = src; 142 | img.width = 150; 143 | img.height = 150; 144 | img.onload = () => { 145 | const canvas = document.createElement("canvas"); 146 | const ctx = canvas.getContext("2d"); 147 | canvas.width = img.width * 2; 148 | canvas.height = img.height * 2; 149 | const ratio = canvasRatio(ctx); 150 | if(ctx) { 151 | ctx.drawImage(img, 0, 0, img.width * ratio, img.height * ratio); 152 | const blur: number = 1; 153 | const canvasToBase64 = canvas.toDataURL("img/jpg", blur); 154 | resolve(canvasToBase64); 155 | } 156 | } 157 | }).then(res => { 158 | return res; 159 | }) 160 | } 161 | 162 | /** base64转换bolb流 163 | * @param base64 base64 地址 164 | */ 165 | export const base64ToBolb = (base64: string) => { 166 | return new Promise((resolve, reject) => { 167 | const arr = base64.split(","); 168 | if(arr && arr.length > 0) { 169 | const main = arr[0].match(/:(.*?);/); 170 | if(main) { 171 | const startBolb = main[0]; 172 | const bolbStr = atob(arr[1]); 173 | let length = bolbStr.length; 174 | const unit8Arr = new Uint8Array(length); 175 | while(length--) { 176 | unit8Arr[length] = bolbStr.charCodeAt(length); 177 | } 178 | resolve(new Blob([unit8Arr], {type: startBolb})); 179 | } 180 | } 181 | }).then(res => { 182 | console.log(res); 183 | return res; 184 | }).catch(err => { 185 | Toast.fail("图片上传失败,请重试"); 186 | 187 | }) 188 | } 189 | 190 | /** 是否刘海屏 191 | * @returns true 是 false 否 192 | */ 193 | export const isIphoneX = (): boolean => { 194 | const { clientHeight, clientWidth } = document.documentElement; 195 | if(clientHeight / clientWidth <= 16 /9) { 196 | return false 197 | } 198 | return true 199 | } 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /src/Utils/Base/BaseTypes.ts: -------------------------------------------------------------------------------- 1 | // 时间格式化格式 2 | export interface DateFormatType { 3 | year: number, 4 | month: number, 5 | nowDay: number, 6 | hours: number, 7 | minutes: number, 8 | seconds: number, 9 | milliseconds: number 10 | } -------------------------------------------------------------------------------- /src/Utils/Base/IosQuestion.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 解决 Ios 光标错位问题 */ 3 | export const onFocusFn = () => { 4 | document.body.style.position = "fixed"; 5 | } 6 | /** 解决 Ios 光标错位问题 */ 7 | export const onBlurFn = () => { 8 | document.body.style.position = "static"; 9 | } -------------------------------------------------------------------------------- /src/Utils/Base/Times.ts: -------------------------------------------------------------------------------- 1 | import { DateFormatType } from './TimesType'; 2 | 3 | class Times { 4 | public static instance = new Times(); 5 | /** 时间格式化 6 | * @param date 时间戳 7 | * @returns DateFormatType 8 | */ 9 | public dateFormat(date: number):DateFormatType { 10 | const currentDate = new Date(date); 11 | const year = currentDate.getFullYear(); 12 | const month = currentDate.getMonth() + 1; 13 | const day = currentDate.getDate(); 14 | const hour = currentDate.getHours(); 15 | const milliseconds = currentDate.getMilliseconds(); 16 | return { 17 | year, 18 | month, 19 | day, 20 | hour, 21 | milliseconds 22 | } 23 | } 24 | 25 | /** 获取周几 26 | * @param date 时间戳 27 | * @returns number 28 | */ 29 | public dateWeek(date: number): number { 30 | const currentWeek = new Date(date).getDay(); 31 | return currentWeek; 32 | } 33 | 34 | /** 获取这个月有多少天 35 | * @param date 时间戳 36 | */ 37 | public monthHaveDay(date: number): number { 38 | const current = this.dateFormat(date); 39 | const { year, month, day} = current; 40 | const currentMonthHaveDay = new Date(year, month, 0).getDate(); 41 | return currentMonthHaveDay; 42 | } 43 | 44 | /** 获取今天的00:00的时间戳 45 | * @param date 时间戳 46 | */ 47 | public currentTime(date: number): number { 48 | const current = this.dateFormat(date); 49 | const { year, month, day} = current; 50 | const currentMonthHaveDay = new Date(`${year}/${month}/${day}`).getTime(); 51 | return currentMonthHaveDay; 52 | } 53 | } 54 | const TimesInstance = Times.instance; 55 | export default TimesInstance; -------------------------------------------------------------------------------- /src/Utils/Base/TimesType.ts: -------------------------------------------------------------------------------- 1 | export interface DateFormatType { 2 | year: number, 3 | month: number, 4 | day: number, 5 | hour: number, 6 | milliseconds: number, 7 | } -------------------------------------------------------------------------------- /src/Utils/HttpList/HomeHttp/HomeHttp.ts: -------------------------------------------------------------------------------- 1 | import axiosInit from "../../Axios/Axios"; 2 | import { GetMusicUrltype } from "./HomeHttpType"; 3 | 4 | 5 | /** 请求示例 6 | * @param id 7 | */ 8 | export async function getMusicUrl(id: string): Promise { 9 | const data: T = await axiosInit( "/posts", { a: 1}); 10 | return data; 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Utils/HttpList/HomeHttp/HomeHttpType.ts: -------------------------------------------------------------------------------- 1 | interface GetMusicUrltypeDatatype { 2 | id: number 3 | } 4 | 5 | // 数据类型接口 6 | export interface GetMusicUrltype { 7 | code: number, 8 | data: GetMusicUrltypeDatatype[] 9 | }; 10 | -------------------------------------------------------------------------------- /src/Utils/HttpList/PublicHttp/PublicHttp.ts: -------------------------------------------------------------------------------- 1 | import { imgToBase64, base64ToBolb } from '@/Utils/Base/Base'; 2 | import axiosInit from "../../Axios/Axios"; 3 | /** 上传图片并且压缩 4 | * @param file file对象 5 | */ 6 | export const upload = async (file: File) => { 7 | const name = file.name.split(".")[0]; 8 | const fileSize: number = file.size; 9 | // 最大尺寸 10 | const maxSize: number = 2 * 1024 * 1024; 11 | const formData = new FormData(); 12 | const reader = new FileReader(); 13 | let result; 14 | if(fileSize > maxSize) { 15 | result = await new Promise((resolve, reject) => { 16 | reader.readAsDataURL(file); 17 | reader.onload = async (e:any) => { 18 | const base64 = await imgToBase64(e.target.result) as string; 19 | const toBolb = await base64ToBolb(base64); 20 | resolve(toBolb) 21 | } 22 | }).then(async (res: Blob) => { 23 | const file = new File([res], Date.now() + name + ".jpeg", {type: "image/jpeg"}) 24 | formData.append("file", file) 25 | const data = await axiosInit("xxx",{ 26 | formData 27 | }); 28 | return data; 29 | }).catch(err => { 30 | return null; 31 | }) 32 | }else { 33 | formData.append("file", file) 34 | result = await axiosInit("xxx", { 35 | formData 36 | }); 37 | return result; 38 | } 39 | 40 | return result["data"] 41 | 42 | } -------------------------------------------------------------------------------- /src/Utils/MapCallback/MapCallback.ts: -------------------------------------------------------------------------------- 1 | /** 坐标逆解析 2 | * @param base city 指定进行编码查询的城市,支持传入城市名、adcode 和 citycode 3 | */ 4 | export const locationInverseAnalysis: (base: string) => Promise = (base: string) => { 5 | return new Promise((resolve, reject) => { 6 | const aMap = new AMap.Map("container"); 7 | aMap.plugin("AMap.Geocoder", function() { 8 | const geocoder = new AMap.Geocoder({ 9 | // city 指定进行编码查询的城市,支持传入城市名、adcode 和 citycode 10 | city: base, 11 | }) 12 | geocoder.getLocation('北京市海淀区苏州街', function(status: string, result: any) { 13 | if (status === 'complete' && result.info === 'OK') { 14 | // result中对应详细地理坐标信息 15 | resolve(result); 16 | } 17 | }) 18 | }); 19 | }).then(res => { 20 | return res; 21 | }) 22 | 23 | } -------------------------------------------------------------------------------- /src/Utils/UserAgent/UserAgent.ts: -------------------------------------------------------------------------------- 1 | /** 是否是微信浏览器 2 | * @param type 'isWx' | 'isAndrois' | 'isIos' 平台类型 3 | * @example 4 | * 微信浏览器 5 | * const phoneType: string = userAgent("isWx"); || 6 | * const phoneType: string = userAgent("isAndroid");|| 7 | * const phoneType: string = userAgent("isAndroid"); 8 | * @returns 9 | * boolean 结果返回布尔值 10 | */ 11 | type UserAgentType = "isWx" | "isAndroid" | "isIos"; 12 | const userAgent: (type: UserAgentType) => boolean = (type: UserAgentType) => { 13 | const navigatorUserAgent: string = window.navigator.userAgent.toUpperCase(); 14 | const map:Map boolean> = new Map([ 15 | ["isWx", () => navigatorUserAgent.includes("MICROMESSENGER")], 16 | ["isAndroid", () => navigatorUserAgent.includes("ANDROID")], 17 | ["isIos", () => navigatorUserAgent.includes("IOS")], 18 | ]); 19 | return map.get(type)!(); 20 | } -------------------------------------------------------------------------------- /src/Utils/Wx/WxPay.ts: -------------------------------------------------------------------------------- 1 | // 支付参数类型 2 | interface DataType { 3 | appId: string, // 公众号名称,由商户传入 4 | timeStamp: string, // 时间戳,自1970年以来的秒数 字符串类型 5 | nonceStr: string, // 随机串 6 | package: string, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*) 7 | signType: string, // 微信签名方式: 8 | paySign: string, // 微信签名 9 | } 10 | /** 微信内置浏览器支付 11 | * @param data 参数, 后台返回 12 | * @param success 成功回调 13 | * @param cancel 失败回调 14 | */ 15 | const wxPay = (data: DataType, success: () => void, cancel: () => void) => { 16 | WeixinJSBridge.invoke('getBrandWCPayRequest', 17 | {...data}, 18 | (res: any) => { 19 | if (res.err_msg === "get_brand_wcpay_request:ok") { 20 | success(); 21 | }else { 22 | cancel(); 23 | } 24 | }) 25 | }; 26 | export default wxPay; -------------------------------------------------------------------------------- /src/Utils/Wx/WxShare.ts: -------------------------------------------------------------------------------- 1 | /** 微信分享配置参数 参数没有则设置为空 2 | * @param title 分享标题 3 | * @param desc 分享描述 4 | * @param link 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致 5 | * @param imgUrl 分享图标 6 | * @param successFn 成功回调 7 | * @param cancelFn 失败回调 8 | */ 9 | const wxShare = (title: string, desc: string, link: string, imgUrl: string, successFn: () => void, cancelFn: () => void) => { 10 | // 分享平台类型 11 | const wxShareArr: string[] = ['onMenuShareWeibo', 'onMenuShareQZone', 'onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ']; 12 | wx.ready(() => { 13 | wxShareArr.forEach((item: string, index: number) => { 14 | wx[item]({ 15 | title, 16 | desc, 17 | link, 18 | imgUrl, 19 | success: () => { 20 | successFn(); 21 | }, 22 | cancel: () => { 23 | cancelFn(); 24 | } 25 | }) 26 | }); 27 | }) 28 | }; 29 | export default wxShare; -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | padding-bottom: env(safe-area-inset-bottom); 6 | } 7 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { BrowserRouter } from "react-router-dom"; 4 | import registerServiceWorker from './registerServiceWorker'; 5 | import { Provider } from "react-redux"; 6 | import { createBrowserHistory } from "history"; 7 | import configureStore from ".//Redux/Store/Store"; 8 | import loadable from '@loadable/component'; 9 | const App = loadable(() => import(/* webpackChunkName: "app" */'./App')); 10 | // 创建store注入 11 | const store = configureStore(createBrowserHistory()); 12 | import './Static/Css/Reset.css'; 13 | import 'antd-mobile/dist/antd-mobile.css'; // or 'antd-mobile/dist/antd-mobile.less' 14 | ReactDOM.render( 15 | 16 | 17 | 18 | 19 | 20 | , 21 | document.getElementById('root') as HTMLElement 22 | ); 23 | registerServiceWorker(); 24 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/proxy.js: -------------------------------------------------------------------------------- 1 | const proxy = require("http-proxy-middleware"); 2 | // console.log(1); 3 | module.exports = function (app) { 4 | app.use( 5 | proxy("/api", { 6 | changeOrigin: true, 7 | target: "http://192.168.1.106:3000/", 8 | 9 | }) 10 | ); 11 | // app.use( 12 | // proxy("/fans/**", { 13 | // target: "https://easy-mock.com/mock/5c0f31837214cf627b8d43f0/", 14 | // changeOrigin: true 15 | // }) 16 | // ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/registerServiceWorker.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-console 2 | // In production, we register a service worker to serve assets from local cache. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on the 'N+1' visit to a page, since previously 7 | // cached resources are updated in the background. 8 | 9 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 10 | // This link also includes instructions on opting out of this behavior. 11 | 12 | const isLocalhost = Boolean( 13 | window.location.hostname === 'localhost' || 14 | // [::1] is the IPv6 localhost address. 15 | window.location.hostname === '[::1]' || 16 | // 127.0.0.1/8 is considered localhost for IPv4. 17 | window.location.hostname.match( 18 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 19 | ) 20 | ); 21 | 22 | export default function register() { 23 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 24 | // The URL constructor is available in all browsers that support SW. 25 | const publicUrl = new URL( 26 | process.env.PUBLIC_URL!, 27 | window.location.toString() 28 | ); 29 | if (publicUrl.origin !== window.location.origin) { 30 | // Our service worker won't work if PUBLIC_URL is on a different origin 31 | // from what our page is served on. This might happen if a CDN is used to 32 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 33 | return; 34 | } 35 | 36 | window.addEventListener('load', () => { 37 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 38 | 39 | if (isLocalhost) { 40 | // This is running on localhost. Lets check if a service worker still exists or not. 41 | checkValidServiceWorker(swUrl); 42 | 43 | // Add some additional logging to localhost, pointing developers to the 44 | // service worker/PWA documentation. 45 | navigator.serviceWorker.ready.then(() => { 46 | console.log( 47 | 'This web app is being served cache-first by a service ' + 48 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 49 | ); 50 | }); 51 | } else { 52 | // Is not local host. Just register service worker 53 | registerValidSW(swUrl); 54 | } 55 | }); 56 | } 57 | } 58 | 59 | function registerValidSW(swUrl: string) { 60 | navigator.serviceWorker 61 | .register(swUrl) 62 | .then(registration => { 63 | registration.onupdatefound = () => { 64 | const installingWorker = registration.installing; 65 | if (installingWorker) { 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the old content will have been purged and 70 | // the fresh content will have been added to the cache. 71 | // It's the perfect time to display a 'New content is 72 | // available; please refresh.' message in your web app. 73 | console.log('New content is available; please refresh.'); 74 | } else { 75 | // At this point, everything has been precached. 76 | // It's the perfect time to display a 77 | // 'Content is cached for offline use.' message. 78 | console.log('Content is cached for offline use.'); 79 | } 80 | } 81 | }; 82 | } 83 | }; 84 | }) 85 | .catch(error => { 86 | console.error('Error during service worker registration:', error); 87 | }); 88 | } 89 | 90 | function checkValidServiceWorker(swUrl: string) { 91 | // Check if the service worker can be found. If it can't reload the page. 92 | fetch(swUrl) 93 | .then(response => { 94 | // Ensure service worker exists, and that we really are getting a JS file. 95 | if ( 96 | response.status === 404 || 97 | response.headers.get('content-type')!.indexOf('javascript') === -1 98 | ) { 99 | // No service worker found. Probably a different app. Reload the page. 100 | navigator.serviceWorker.ready.then(registration => { 101 | registration.unregister().then(() => { 102 | window.location.reload(); 103 | }); 104 | }); 105 | } else { 106 | // Service worker found. Proceed as normal. 107 | registerValidSW(swUrl); 108 | } 109 | }) 110 | .catch(() => { 111 | console.log( 112 | 'No internet connection found. App is running in offline mode.' 113 | ); 114 | }); 115 | } 116 | 117 | export function unregister() { 118 | if ('serviceWorker' in navigator) { 119 | navigator.serviceWorker.ready.then(registration => { 120 | registration.unregister(); 121 | }); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "build/dist", 5 | "module": "esnext", 6 | "target": "es5", 7 | "lib": ["esnext", "dom"], 8 | "sourceMap": true, 9 | "allowJs": true, 10 | "jsx": "react", 11 | "moduleResolution": "node", 12 | "rootDir": "..", 13 | "forceConsistentCasingInFileNames": true, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "importHelpers": true, 18 | "strictNullChecks": true, 19 | "suppressImplicitAnyIndexErrors": true, 20 | "noUnusedLocals": false, 21 | "experimentalDecorators":true, 22 | "allowUnusedLabels": true, // 不报告未使用标签错误 23 | "diagnostics": false, // 显示诊断信息 24 | "allowSyntheticDefaultImports": true, 25 | "downlevelIteration": true, // 开启全迭代模式 26 | "paths": { 27 | "@/*": ["src/*"], 28 | "@Components/*": ["src/Components/*"], 29 | "@Static/*": ["src/Static/*"], 30 | "@Utils/*": ["src/Utils/*"], 31 | "@Page/*": ["src/Page/*"], 32 | "@Redux/*": ["src/Redux/*"], 33 | } 34 | }, 35 | "exclude": [ 36 | "node_modules", 37 | "build", 38 | "scripts", 39 | "acceptance-tests", 40 | "webpack", 41 | "jest", 42 | "src/setupTests.ts" 43 | ], 44 | } 45 | -------------------------------------------------------------------------------- /tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json" 3 | } -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended", 4 | "tslint-react", 5 | "tslint-config-prettier" 6 | ], 7 | "linterOptions": { 8 | "exclude": [ 9 | "config/**/*.js", 10 | "node_modules/**/*.ts", 11 | "coverage/lcov-report/*.js" 12 | ] 13 | }, 14 | "rules":{ 15 | "no-shadowed-variable": false, // 允许接口内使用其他接口 16 | "no-debugger": false, 17 | "no-console": false, 18 | "no-empty-interface":true, 19 | "only-arrow-functions": false, // 允许箭头函数中使用 es5 函数 20 | "interface-name":false, // 接口开头不必用I 21 | //需要按字母顺序引入模块 22 | "ordered-imports": false, 23 | // jsx中匿名函数 24 | "jsx-no-lambda": false, 25 | "no-string-literal":false, 26 | "object-literal-sort-keys": false, // 检查对象文字中键的排序 27 | "no-floating-promises": false, //必须正确处理promise的返回函数 28 | "promise-function-async": false, //要求异步函数返回promise 29 | "no-empty": false, 30 | "no-implicit-dependencies":[false], // 此参数配置alias必须 31 | "no-submodule-imports": [false, "src"],// 次参数配置alias必须 32 | "forin":true, 33 | "variable-name": false, // 后台接口带下划线,不是驼峰格式需关闭 34 | "prefer-for-of":false, 35 | "no-unused-expression": false 36 | }, 37 | "jsRules": { 38 | 39 | 40 | } 41 | } 42 | --------------------------------------------------------------------------------