commit 59c644956bc775bcfeee3efbdca35c0836a806c2
Author: mk <2403457699@qq.com>
Date: Wed May 15 20:11:09 2024 +0800
init
diff --git a/.browserslistrc b/.browserslistrc
new file mode 100644
index 0000000..d6471a3
--- /dev/null
+++ b/.browserslistrc
@@ -0,0 +1,2 @@
+> 1%
+last 2 versions
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..ea6e20f
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,14 @@
+# http://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+insert_final_newline = false
+trim_trailing_whitespace = false
diff --git a/.env.development b/.env.development
new file mode 100644
index 0000000..54e3512
--- /dev/null
+++ b/.env.development
@@ -0,0 +1,6 @@
+NODE_ENV='development'
+# must start with VUE_APP_
+VUE_APP_ENV = 'development'
+outputDir = 'epmet-work-h5-dev'
+
+
diff --git a/.env.production b/.env.production
new file mode 100644
index 0000000..8deecc0
--- /dev/null
+++ b/.env.production
@@ -0,0 +1,5 @@
+NODE_ENV='production'
+# must start with VUE_APP_
+VUE_APP_ENV = 'production'
+outputDir = 'epmet-work-h5-prod'
+
\ No newline at end of file
diff --git a/.env.staging b/.env.staging
new file mode 100644
index 0000000..92749e3
--- /dev/null
+++ b/.env.staging
@@ -0,0 +1,4 @@
+NODE_ENV='production'
+# must start with VUE_APP_
+VUE_APP_ENV = 'staging'
+
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..e6529fc
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,4 @@
+build/*.js
+src/assets
+public
+dist
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..610b9aa
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,54 @@
+module.exports = {
+ root: true,
+ env: {
+ browser: true,
+ node: true,
+ es6: true,
+ 'vue/setup-compiler-macros': true
+ },
+ globals: {
+ defineProps: 'readonly',
+ defineEmits: 'readonly'
+ },
+ extends: ['plugin:vue/essential', 'eslint:recommended', 'plugin:prettier/recommended'],
+ parserOptions: {
+ parser: '@babel/eslint-parser'
+ },
+ rules: {
+ 'no-console': 'warn',
+ 'no-debugger': 'warn',
+ 'vue/script-setup-uses-vars': 'error',
+ 'vue/custom-event-name-casing': 'off',
+ 'no-use-before-define': 'off',
+ 'no-unused-vars': [
+ 'error',
+ {
+ argsIgnorePattern: '^_',
+ varsIgnorePattern: '^_'
+ }
+ ],
+ 'space-before-function-paren': 'off',
+ 'vue/attributes-order': 'off',
+ 'vue/one-component-per-file': 'off',
+ 'vue/html-closing-bracket-newline': 'off',
+ 'vue/max-attributes-per-line': 'off',
+ 'vue/multiline-html-element-content-newline': 'off',
+ 'vue/singleline-html-element-content-newline': 'off',
+ 'vue/attribute-hyphenation': 'off',
+ 'vue/require-default-prop': 'off',
+ 'vue/html-self-closing': [
+ 'error',
+ {
+ html: {
+ void: 'always',
+ normal: 'never',
+ component: 'always'
+ },
+ svg: 'always',
+ math: 'always'
+ }
+ ],
+ 'vue/v-on-event-hyphenation': 'off',
+ 'vue/multi-word-component-names': 'off'
+ }
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fdc3bc3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+.DS_Store
+node_modules
+/dist
+/docs
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+package-lock.json
+yarn.lock
\ No newline at end of file
diff --git a/.postcssrc.js b/.postcssrc.js
new file mode 100644
index 0000000..1b534ef
--- /dev/null
+++ b/.postcssrc.js
@@ -0,0 +1,13 @@
+// https://github.com/michael-ciniawsky/postcss-load-config
+module.exports = {
+ plugins: {
+ autoprefixer: {
+ overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 8']
+ },
+ 'postcss-pxtorem': {
+ rootValue: 37.5,
+ propList: ['*'],
+ //selectorBlackList: ['van-']
+ }
+ }
+}
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..42061c0
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1 @@
+README.md
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..26e9376
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,24 @@
+{
+ "printWidth": 120,
+ "tabWidth": 2,
+ "singleQuote": true,
+ "trailingComma": "none",
+ "semi": false,
+ "wrap_line_length": 120,
+ "wrap_attributes": "auto",
+ "proseWrap": "always",
+ "arrowParens": "avoid",
+ "bracketSpacing": true,
+ "jsxBracketSameLine": true,
+ "useTabs": false,
+ "eslintIntegration":true,
+ "overrides": [
+ {
+ "files": ".prettierrc",
+ "options": {
+ "parser": "json"
+ }
+ }
+ ],
+ "endOfLine": "auto"
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..880838d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,1212 @@
+# vue-h5-template
+
+基于 vue-cli4.0 + webpack 4 + vant ui + sass+ rem 适配方案+axios 封装 + jssdk配置 + vconsole移动端调试,构建手机端模板脚手架
+
+掘金: [vue-cli4 vant rem 移动端框架方案](https://juejin.im/post/5cfefc73f265da1bba58f9f7)
+
+[查看 demo](https://solui.cn/vue-h5-template/#/) 建议手机端查看
+
+
+
+
+
+### Node 版本要求
+
+`Vue CLI` 需要 Node.js 8.9 或更高版本 (推荐 8.11.0+)。你可以使用 [nvm](https://github.com/nvm-sh/nvm) 或
+[nvm-windows](https://github.com/coreybutler/nvm-windows) 在同一台电脑中管理多个 Node 版本。
+
+本示例 Node.js 12.14.1
+
+### 项目结构
+>vue-h5-template -- UI主目录
+├── public -- 静态资源
+├ ├── favicon.ico -- 图标
+├ └── index.html -- 首页
+├── src -- 源码目录
+├ ├── api -- 后端交互的接口
+├ ├── assets -- 静态资源目录
+├ ├── css
+├ ├── index.scss -- 全局通用样式
+├ ├── mixin.scss -- 全局mixin
+├ └── variables.scss -- 全局变量
+├ ├── components -- 封装的组件
+├ ├── config -- 环境配置
+├ ├── const -- 放vue页面的配置常量
+├ ├── filters -- 过滤器
+├ ├── plugins -- 插件
+├ └── route -- VUE路由
+├ ├── index -- 路由入口
+├ └── router.config.js -- 路由表
+├ ├── store -- VUEX
+├ └── util -- 工具包
+├ ├── request.js -- axios封装
+├ ├── vconsole.js -- 移动端调试插件
+├ ├── jsApiList.js -- 微信JS接口列表
+├ ├── wechatPlugin.js -- jssdk插件配置
+├ ├── storage.js -- 本地存储封装
+├ └── util -- 工具包
+├ └── views -- 业务上的vue页面
+├ ├── layouts -- 路由布局页面(是否缓存页面)
+├ └── home -- 公众号
+├ ├── App.vue -- 根组件
+├ └── main.js -- 入口js
+├── .env.development -- 开发环境
+├── .env.production -- 生产环境
+├── .env.staging -- 测试环境
+├── .editorconfig -- ESLint配置
+├── .gitignore -- git忽略
+├── .postcssrc.js -- CSS预处理配置(rem适配)
+├── babel.config.js -- barbel配置入口
+├── jsconfig.json -- vscode路径引入配置
+├── package.json -- 依赖管理
+└── vue.config.js -- vue cli3的webpack配置
+
+### 启动项目
+
+```bash
+
+git clone https://github.com/sunniejs/vue-h5-template.git
+
+cd vue-h5-template
+
+npm install
+
+npm run serve
+```
+目录
+
+- √ Vue-cli4
+- [√ 配置多环境变量](#env)
+- [√ rem 适配方案](#rem)
+- [√ VantUI 组件按需加载](#vant)
+- [√ Sass 全局样式](#sass)
+- [√ Vuex 状态管理](#vuex)
+- [√ Vue-router](#router)
+- [√ Axios 封装及接口管理](#axios)
+- [√ Webpack 4 vue.config.js 基础配置](#base)
+- [√ 配置 alias 别名](#alias)
+- [√ 配置 proxy 跨域](#proxy)
+- [√ 配置 打包分析](#bundle)
+- [√ 配置 externals 引入 cdn 资源 ](#externals)
+- [√ 去掉 console.log ](#console)
+- [√ splitChunks 单独打包第三方模块](#chunks)
+- [√ 添加 IE 兼容 ](#ie)
+- [√ Eslint+Pettier 统一开发规范 ](#pettier)
+- [√ vconsole ](#vconsole)
+- [√ 动态设置title ](#dyntitle)
+- [√ 配置Jssdk ](#jssdk)
+- [√ 本地存储storage封装 ](#storage)
+
+### ✅ 配置多环境变量
+
+`package.json` 里的 `scripts` 配置 `serve` `stage` `build`,通过 `--mode xxx` 来执行不同环境
+
+- 通过 `npm run serve` 启动本地 , 执行 `development`
+- 通过 `npm run stage` 启动测试 , 执行 `development`
+- 通过 `npm run prod` 启动开发 , 执行 `development`
+- 通过 `npm run stageBuild` 打包测试 , 执行 `staging`
+- 通过 `npm run build` 打包正式 , 执行 `production`
+
+```javascript
+"scripts": {
+ "serve": "vue-cli-service serve --open",
+ "stage": "cross-env NODE_ENV=dev vue-cli-service serve --mode staging",
+ "prod": "cross-env NODE_ENV=dev vue-cli-service serve --mode production",
+ "stageBuild": "vue-cli-service build --mode staging",
+ "build": "vue-cli-service build",
+}
+```
+
+##### 配置介绍
+
+ 以 `VUE_APP_` 开头的变量,在代码中可以通过 `process.env.VUE_APP_` 访问。
+ 比如,`VUE_APP_ENV = 'development'` 通过`process.env.VUE_APP_ENV` 访问。
+ 除了 `VUE_APP_*` 变量之外,在你的应用代码中始终可用的还有两个特殊的变量`NODE_ENV` 和`BASE_URL`
+
+在项目根目录中新建`.env.*`
+
+- .env.development 本地开发环境配置
+
+```bash
+NODE_ENV='development'
+# must start with VUE_APP_
+VUE_APP_ENV = 'development'
+
+```
+
+- .env.staging 测试环境配置
+
+```bash
+NODE_ENV='production'
+# must start with VUE_APP_
+VUE_APP_ENV = 'staging'
+```
+
+- .env.production 正式环境配置
+
+```bash
+ NODE_ENV='production'
+# must start with VUE_APP_
+VUE_APP_ENV = 'production'
+```
+
+这里我们并没有定义很多变量,只定义了基础的 VUE_APP_ENV `development` `staging` `production`
+变量我们统一在 `src/config/env.*.js` 里进行管理。
+
+这里有个问题,既然这里有了根据不同环境设置变量的文件,为什么还要去 config 下新建三个对应的文件呢?
+**修改起来方便,不需要重启项目,符合开发习惯。**
+
+config/index.js
+
+```javascript
+// 根据环境引入不同配置 process.env.NODE_ENV
+const config = require('./env.' + process.env.VUE_APP_ENV)
+module.exports = config
+```
+
+配置对应环境的变量,拿本地环境文件 `env.development.js` 举例,用户可以根据需求修改
+
+```javascript
+// 本地环境配置
+module.exports = {
+ title: 'vue-h5-template',
+ baseUrl: 'http://localhost:9018', // 项目地址
+ baseApi: 'https://test.xxx.com/api', // 本地api请求地址
+ APPID: 'xxx',
+ APPSECRET: 'xxx'
+}
+```
+
+根据环境不同,变量就会不同了
+
+```javascript
+// 根据环境不同引入不同baseApi地址
+import { baseApi } from '@/config'
+console.log(baseApi)
+```
+
+[▲ 回顶部](#top)
+
+### ✅ rem 适配方案
+
+不用担心,项目已经配置好了 `rem` 适配, 下面仅做介绍:
+
+Vant 中的样式默认使用`px`作为单位,如果需要使用`rem`单位,推荐使用以下两个工具:
+
+- [postcss-pxtorem](https://github.com/cuth/postcss-pxtorem) 是一款 `postcss` 插件,用于将单位转化为 `rem`
+- [lib-flexible](https://github.com/amfe/lib-flexible) 用于设置 `rem` 基准值
+
+##### PostCSS 配置
+
+下面提供了一份基本的 `postcss` 配置,可以在此配置的基础上根据项目需求进行修改
+
+```javascript
+// https://github.com/michael-ciniawsky/postcss-load-config
+module.exports = {
+ plugins: {
+ autoprefixer: {
+ overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 8']
+ },
+ 'postcss-pxtorem': {
+ rootValue: 37.5,
+ propList: ['*']
+ }
+ }
+}
+```
+
+更多详细信息: [vant](https://youzan.github.io/vant/#/zh-CN/quickstart#jin-jie-yong-fa)
+
+**新手必看,老鸟跳过**
+
+很多小伙伴会问我,适配的问题。
+
+我们知道 `1rem` 等于`html` 根元素设定的 `font-size` 的 `px` 值。Vant UI 设置 `rootValue: 37.5`,你可以看到在 iPhone 6 下
+看到 (`1rem 等于 37.5px`):
+
+```html
+
+```
+
+切换不同的机型,根元素可能会有不同的`font-size`。当你写 css px 样式时,会被程序换算成 `rem` 达到适配。
+
+因为我们用了 Vant 的组件,需要按照 `rootValue: 37.5` 来写样式。
+
+举个例子:设计给了你一张 750px \* 1334px 图片,在 iPhone6 上铺满屏幕,其他机型适配。
+
+- 当`rootValue: 70` , 样式 `width: 750px;height: 1334px;` 图片会撑满 iPhone6 屏幕,这个时候切换其他机型,图片也会跟着撑
+ 满。
+- 当`rootValue: 37.5` 的时候,样式 `width: 375px;height: 667px;` 图片会撑满 iPhone6 屏幕。
+
+也就是 iphone 6 下 375px 宽度写 CSS。其他的你就可以根据你设计图,去写对应的样式就可以了。
+
+当然,想要撑满屏幕你可以使用 100%,这里只是举例说明。
+
+```html
+
+
+
+```
+
+[▲ 回顶部](#top)
+
+### ✅ VantUI 组件按需加载
+
+项目采
+用[Vant 自动按需引入组件 (推荐)](https://youzan.github.io/vant/#/zh-CN/quickstart#fang-shi-yi.-zi-dong-an-xu-yin-ru-zu-jian-tui-jian)下
+面安装插件介绍:
+
+[babel-plugin-import](https://github.com/ant-design/babel-plugin-import) 是一款 `babel` 插件,它会在编译过程中将
+`import` 的写法自动转换为按需引入的方式
+
+#### 安装插件
+
+```bash
+npm i babel-plugin-import -D
+```
+
+在`babel.config.js` 设置
+
+```javascript
+// 对于使用 babel7 的用户,可以在 babel.config.js 中配置
+const plugins = [
+ [
+ 'import',
+ {
+ libraryName: 'vant',
+ libraryDirectory: 'es',
+ style: true
+ },
+ 'vant'
+ ]
+]
+module.exports = {
+ presets: [['@vue/cli-plugin-babel/preset', { useBuiltIns: 'usage', corejs: 3 }]],
+ plugins
+}
+```
+
+#### 使用组件
+
+项目在 `src/plugins/vant.js` 下统一管理组件,用哪个引入哪个,无需在页面里重复引用
+
+```javascript
+// 按需全局引入 vant组件
+import Vue from 'vue'
+import { Button, List, Cell, Tabbar, TabbarItem } from 'vant'
+Vue.use(Button)
+Vue.use(Cell)
+Vue.use(List)
+Vue.use(Tabbar).use(TabbarItem)
+```
+
+[▲ 回顶部](#top)
+
+### ✅ Sass 全局样式
+
+首先 你可能会遇到 `node-sass` 安装不成功,别放弃多试几次!!!
+
+每个页面自己对应的样式都写在自己的 .vue 文件之中 `scoped` 它顾名思义给 css 加了一个域的概念。
+
+```html
+
+
+
+```
+
+#### 目录结构
+
+vue-h5-template 所有全局样式都在 `@/src/assets/css` 目录下设置
+
+```bash
+├── assets
+│ ├── css
+│ │ ├── index.scss # 全局通用样式
+│ │ ├── mixin.scss # 全局mixin
+│ │ └── variables.scss # 全局变量
+```
+
+#### 自定义 vant-ui 样式
+
+现在我们来说说怎么重写 `vant-ui` 样式。由于 `vant-ui` 的样式我们是在全局引入的,所以你想在某个页面里面覆盖它的样式就不能
+加 `scoped`,但你又想只覆盖这个页面的 `vant` 样式,你就可在它的父级加一个 `class`,用命名空间来解决问题。
+
+```css
+.about-container {
+ /* 你的命名空间 */
+ .van-button {
+ /* vant-ui 元素*/
+ margin-right: 0px;
+ }
+}
+```
+
+#### 父组件改变子组件样式 深度选择器
+
+当你子组件使用了 `scoped` 但在父组件又想修改子组件的样式可以 通过 `>>>` 来实现:
+
+```css
+
+```
+
+#### 全局变量
+
+`vue.config.js` 配置使用 `css.loaderOptions` 选项,注入 `sass` 的 `mixin` `variables` 到全局,不需要手动引入 ,配
+置`$cdn`通过变量形式引入 cdn 地址,这样向所有 Sass/Less 样式传入共享的全局变量:
+
+```javascript
+const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV)
+const defaultSettings = require('./src/config/index.js')
+module.exports = {
+ css: {
+ extract: IS_PROD,
+ sourceMap: false,
+ loaderOptions: {
+ // 给 scss-loader 传递选项
+ scss: {
+ // 注入 `sass` 的 `mixin` `variables` 到全局, $cdn可以配置图片cdn
+ // 详情: https://cli.vuejs.org/guide/css.html#passing-options-to-pre-processor-loaders
+ prependData: `
+ @import "assets/css/mixin.scss";
+ @import "assets/css/variables.scss";
+ $cdn: "${defaultSettings.$cdn}";
+ `
+ }
+ }
+ }
+}
+```
+
+设置 js 中可以访问 `$cdn`,`.vue` 文件中使用`this.$cdn`访问
+
+```javascript
+// 引入全局样式
+import '@/assets/css/index.scss'
+
+// 设置 js中可以访问 $cdn
+// 引入cdn
+import { $cdn } from '@/config'
+Vue.prototype.$cdn = $cdn
+```
+
+在 css 和 js 使用
+
+```html
+
+
+```
+
+[▲ 回顶部](#top)
+
+### ✅ Vuex 状态管理
+
+目录结构
+
+```bash
+├── store
+│ ├── modules
+│ │ └── app.js
+│ ├── index.js
+│ ├── getters.js
+```
+
+`main.js` 引入
+
+```javascript
+import Vue from 'vue'
+import App from './App.vue'
+import store from './store'
+new Vue({
+ el: '#app',
+ router,
+ store,
+ render: h => h(App)
+})
+```
+
+使用
+
+```html
+
+```
+
+[▲ 回顶部](#top)
+
+### ✅ Vue-router
+
+本案例采用 `hash` 模式,开发者根据需求修改 `mode` `base`
+
+**注意**:如果你使用了 `history` 模式,`vue.config.js` 中的 `publicPath` 要做对应的**修改**
+
+前往:[vue.config.js 基础配置](#base)
+
+```javascript
+import Vue from 'vue'
+import Router from 'vue-router'
+
+Vue.use(Router)
+export const router = [
+ {
+ path: '/',
+ name: 'index',
+ component: () => import('@/views/home/index'), // 路由懒加载
+ meta: {
+ title: '首页', // 页面标题
+ keepAlive: false // keep-alive 标识
+ }
+ }
+]
+const createRouter = () =>
+ new Router({
+ // mode: 'history', // 如果你是 history模式 需要配置 vue.config.js publicPath
+ // base: '/app/',
+ scrollBehavior: () => ({ y: 0 }),
+ routes: router
+ })
+
+export default createRouter()
+```
+
+更多:[Vue Router](https://router.vuejs.org/zh/)
+
+[▲ 回顶部](#top)
+
+### ✅ Axios 封装及接口管理
+
+`utils/request.js` 封装 axios ,开发者需要根据后台接口做修改。
+
+- `service.interceptors.request.use` 里可以设置请求头,比如设置 `token`
+- `config.hideloading` 是在 api 文件夹下的接口参数里设置,下文会讲
+- `service.interceptors.response.use` 里可以对接口返回数据处理,比如 401 删除本地信息,重新登录
+
+```javascript
+import axios from 'axios'
+import store from '@/store'
+import { Toast } from 'vant'
+// 根据环境不同引入不同api地址
+import { baseApi } from '@/config'
+// create an axios instance
+const service = axios.create({
+ baseURL: baseApi, // url = base api url + request url
+ withCredentials: true, // send cookies when cross-domain requests
+ timeout: 5000 // request timeout
+})
+
+// request 拦截器 request interceptor
+service.interceptors.request.use(
+ config => {
+ // 不传递默认开启loading
+ if (!config.hideloading) {
+ // loading
+ Toast.loading({
+ forbidClick: true
+ })
+ }
+ if (store.getters.token) {
+ config.headers['X-Token'] = ''
+ }
+ return config
+ },
+ error => {
+ // do something with request error
+ console.log(error) // for debug
+ return Promise.reject(error)
+ }
+)
+// respone拦截器
+service.interceptors.response.use(
+ response => {
+ Toast.clear()
+ const res = response.data
+ if (res.status && res.status !== 200) {
+ // 登录超时,重新登录
+ if (res.status === 401) {
+ store.dispatch('FedLogOut').then(() => {
+ location.reload()
+ })
+ }
+ return Promise.reject(res || 'error')
+ } else {
+ return Promise.resolve(res)
+ }
+ },
+ error => {
+ Toast.clear()
+ console.log('err' + error) // for debug
+ return Promise.reject(error)
+ }
+)
+export default service
+```
+
+#### 接口管理
+
+在`src/api` 文件夹下统一管理接口
+
+- 你可以建立多个模块对接接口, 比如 `home.js` 里是首页的接口这里讲解 `user.js`
+- `url` 接口地址,请求的时候会拼接上 `config` 下的 `baseApi`
+- `method` 请求方法
+- `data` 请求参数 `qs.stringify(params)` 是对数据系列化操作
+- `hideloading` 默认 `false`,设置为 `true` 后,不显示 loading ui 交互中有些接口不需要让用户感知
+
+```javascript
+import qs from 'qs'
+// axios
+import request from '@/utils/request'
+//user api
+
+// 用户信息
+export function getUserInfo(params) {
+ return request({
+ url: '/user/userinfo',
+ method: 'post',
+ data: qs.stringify(params),
+ hideloading: true // 隐藏 loading 组件
+ })
+}
+```
+
+#### 如何调用
+
+```javascript
+// 请求接口
+import { getUserInfo } from '@/api/user.js'
+
+const params = { user: 'sunnie' }
+getUserInfo(params)
+ .then(() => {})
+ .catch(() => {})
+```
+
+[▲ 回顶部](#top)
+
+### ✅ Webpack 4 vue.config.js 基础配置
+
+如果你的 `Vue Router` 模式是 hash
+
+```javascript
+publicPath: './',
+```
+
+如果你的 `Vue Router` 模式是 history 这里的 publicPath 和你的 `Vue Router` `base` **保持一直**
+
+```javascript
+publicPath: '/app/',
+```
+
+```javascript
+const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV)
+
+module.exports = {
+ publicPath: './', // 署应用包时的基本 URL。 vue-router hash 模式使用
+ // publicPath: '/app/', // 署应用包时的基本 URL。 vue-router history模式使用
+ outputDir: 'dist', // 生产环境构建文件的目录
+ assetsDir: 'static', // outputDir的静态资源(js、css、img、fonts)目录
+ lintOnSave: !IS_PROD,
+ productionSourceMap: false, // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
+ devServer: {
+ port: 9020, // 端口号
+ open: false, // 启动后打开浏览器
+ overlay: {
+ // 当出现编译器错误或警告时,在浏览器中显示全屏覆盖层
+ warnings: false,
+ errors: true
+ }
+ // ...
+ }
+}
+```
+
+[▲ 回顶部](#top)
+
+### ✅ 配置 alias 别名
+
+```javascript
+const path = require('path')
+const resolve = dir => path.join(__dirname, dir)
+const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV)
+
+module.exports = {
+ chainWebpack: config => {
+ // 添加别名
+ config.resolve.alias
+ .set('@', resolve('src'))
+ .set('assets', resolve('src/assets'))
+ .set('api', resolve('src/api'))
+ .set('views', resolve('src/views'))
+ .set('components', resolve('src/components'))
+ }
+}
+```
+
+[▲ 回顶部](#top)
+
+### ✅ 配置 proxy 跨域
+
+如果你的项目需要跨域设置,你需要打来 `vue.config.js` `proxy` 注释 并且配置相应参数
+
+**!!!注意:你还需要将 `src/config/env.development.js` 里的 `baseApi` 设置成 '/'**
+
+```javascript
+module.exports = {
+ devServer: {
+ // ....
+ proxy: {
+ //配置跨域
+ '/api': {
+ target: 'https://test.xxx.com', // 接口的域名
+ // ws: true, // 是否启用websockets
+ changOrigin: true, // 开启代理,在本地创建一个虚拟服务端
+ pathRewrite: {
+ '^/api': '/'
+ }
+ }
+ }
+ }
+}
+```
+
+使用 例如: `src/api/home.js`
+
+```javascript
+export function getUserInfo(params) {
+ return request({
+ url: '/api/userinfo',
+ method: 'post',
+ data: qs.stringify(params)
+ })
+}
+```
+
+[▲ 回顶部](#top)
+
+### ✅ 配置 打包分析
+
+```javascript
+const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
+
+module.exports = {
+ chainWebpack: config => {
+ // 打包分析
+ if (IS_PROD) {
+ config.plugin('webpack-report').use(BundleAnalyzerPlugin, [
+ {
+ analyzerMode: 'static'
+ }
+ ])
+ }
+ }
+}
+```
+
+```bash
+npm run build
+```
+
+[▲ 回顶部](#top)
+
+### ✅ 配置 externals 引入 cdn 资源
+
+这个版本 CDN 不再引入,我测试了一下使用引入 CDN 和不使用,不使用会比使用时间少。网上不少文章测试 CDN 速度块,这个开发者可
+以实际测试一下。
+
+另外项目中使用的是公共 CDN 不稳定,域名解析也是需要时间的(如果你要使用请尽量使用同一个域名)
+
+因为页面每次遇到`
+ <% } %>
+```
+
+[▲ 回顶部](#top)
+
+### ✅ 去掉 console.log
+
+保留了测试环境和本地环境的 `console.log`
+
+```bash
+npm i -D babel-plugin-transform-remove-console
+```
+
+在 babel.config.js 中配置
+
+```javascript
+// 获取 VUE_APP_ENV 非 NODE_ENV,测试环境依然 console
+const IS_PROD = ['production', 'prod'].includes(process.env.VUE_APP_ENV)
+const plugins = [
+ [
+ 'import',
+ {
+ libraryName: 'vant',
+ libraryDirectory: 'es',
+ style: true
+ },
+ 'vant'
+ ]
+]
+// 去除 console.log
+if (IS_PROD) {
+ plugins.push('transform-remove-console')
+}
+
+module.exports = {
+ presets: [['@vue/cli-plugin-babel/preset', { useBuiltIns: 'entry' }]],
+ plugins
+}
+```
+
+[▲ 回顶部](#top)
+
+### ✅ splitChunks 单独打包第三方模块
+
+```javascript
+module.exports = {
+ chainWebpack: config => {
+ config.when(IS_PROD, config => {
+ config
+ .plugin('ScriptExtHtmlWebpackPlugin')
+ .after('html')
+ .use('script-ext-html-webpack-plugin', [
+ {
+ // 将 runtime 作为内联引入不单独存在
+ inline: /runtime\..*\.js$/
+ }
+ ])
+ .end()
+ config.optimization.splitChunks({
+ chunks: 'all',
+ cacheGroups: {
+ // cacheGroups 下可以可以配置多个组,每个组根据test设置条件,符合test条件的模块
+ commons: {
+ name: 'chunk-commons',
+ test: resolve('src/components'),
+ minChunks: 3, // 被至少用三次以上打包分离
+ priority: 5, // 优先级
+ reuseExistingChunk: true // 表示是否使用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的。
+ },
+ node_vendors: {
+ name: 'chunk-libs',
+ chunks: 'initial', // 只打包初始时依赖的第三方
+ test: /[\\/]node_modules[\\/]/,
+ priority: 10
+ },
+ vantUI: {
+ name: 'chunk-vantUI', // 单独将 vantUI 拆包
+ priority: 20, // 数字大权重到,满足多个 cacheGroups 的条件时候分到权重高的
+ test: /[\\/]node_modules[\\/]_?vant(.*)/
+ }
+ }
+ })
+ config.optimization.runtimeChunk('single')
+ })
+ }
+}
+```
+
+[▲ 回顶部](#top)
+
+### ✅ 添加 IE 兼容
+
+之前的方式 会报 `@babel/polyfill` is deprecated. Please, use required parts of `core-js` and
+`regenerator-runtime/runtime` separately
+
+`@babel/polyfill` 废弃,使用 `core-js` 和 `regenerator-runtime`
+
+```bash
+npm i --save core-js regenerator-runtime
+```
+
+在 `main.js` 中添加
+
+```javascript
+// 兼容 IE
+// https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md#babelpolyfill
+import 'core-js/stable'
+import 'regenerator-runtime/runtime'
+```
+
+配置 `babel.config.js`
+
+```javascript
+const plugins = []
+
+module.exports = {
+ presets: [['@vue/cli-plugin-babel/preset', { useBuiltIns: 'usage', corejs: 3 }]],
+ plugins
+}
+```
+
+[▲ 回顶部](#top)
+
+### ✅ Eslint + Pettier 统一开发规范
+
+VScode 安装 `eslint` `prettier` `vetur` 插件
+
+在文件 `.prettierrc` 里写 属于你的 pettier 规则
+
+```bash
+{
+ "printWidth": 120,
+ "tabWidth": 2,
+ "singleQuote": true,
+ "trailingComma": "none",
+ "semi": false,
+ "wrap_line_length": 120,
+ "wrap_attributes": "auto",
+ "proseWrap": "always",
+ "arrowParens": "avoid",
+ "bracketSpacing": false,
+ "jsxBracketSameLine": true,
+ "useTabs": false,
+ "overrides": [{
+ "files": ".prettierrc",
+ "options": {
+ "parser": "json"
+ }
+ }]
+}
+```
+
+Vscode setting.json 设置
+
+```bash
+ "[vue]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[javascript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ // 保存时用eslint格式化
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": true
+ },
+ // 两者会在格式化js时冲突,所以需要关闭默认js格式化程序
+ "javascript.format.enable": false,
+ "typescript.format.enable": false,
+ "vetur.format.defaultFormatter.html": "none",
+ // js/ts程序用eslint,防止vetur中的prettier与eslint格式化冲突
+ "vetur.format.defaultFormatter.js": "none",
+ "vetur.format.defaultFormatter.ts": "none",
+```
+
+[▲ 回顶部](#top)
+
+### ✅ vconsole 移动端调试
+参考地址:https://github.com/AlloyTeam/AlloyLever
+参考地址:https://www.cnblogs.com/liyinSakura/p/9883777.html
+```js
+import Vconsole from 'vconsole'
+const vConsole = new Vconsole()
+export default vConsole
+```
+* app.vue中设置暗门,点击几次显示vconsole
+ * 在app.vue中通过limit进行设置
+ * 开发测试环境点击一次就可显示
+ * 生产环境点击10次
+
+
+
+
+[▲ 回顶部](#top)
+
+### ✅ 动态设置title
+参考地址:https://github.com/deboyblog/vue-wechat-title
+参考地址:https://www.cnblogs.com/guiyishanren/p/10666127.html
+```js
+// main.js
+// 引入插件
+Vue.use(require('vue-wechat-title'))
+```
+使用
+```js
+// app.vue
+
+```
+
+[▲ 回顶部](#top)
+
+### ✅ 配置Jssdk
+安装:
+```js
+yarn add weixin-js-sdk
+```
+引用:
+```js
+// util
+wechatPlugin.js // jssdk插件配置
+jsApiList.js // 微信JS接口列表
+// main.js
+// 全局注册微信js-sdk
+import WechatPlugin from '@/utils/wechatPlugin'
+Vue.use(WechatPlugin)
+```
+调用:
+```js
+created() {
+ console.log(this.$wx)
+},
+```
+[▲ 回顶部](#top)
+
+### ✅ 本地存储storage封装
+安装:
+```js
+storage.js
+```
+引用:
+```js
+// 引入本地存储
+import { storage, sessionStorage } from '@/utils/storage'
+Vue.prototype.$storage = storage
+Vue.prototype.$sessionStorage = sessionStorage
+```
+调用:
+```js
+created() {
+ this.$storage.set('key','value')
+ this.$storage.get('key')
+ this.$sessionStorage.set('key','value')
+ this.$sessionStorage.get('key')
+},
+```
+[▲ 回顶部](#top)
+
+# 鸣谢
+
+[vue-cli4-config](https://github.com/staven630/vue-cli4-config)
+[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
+
+Path` 要做对应的**修改**
+
+前往:[vue.config.js 基础配置](#base)
+
+```javascript
+import Vue from 'vue'
+import Router from 'vue-router'
+
+Vue.use(Router)
+export const router = [
+ {
+ path: '/',
+ name: 'index',
+ component: () => import('@/views/home/index'), // 路由懒加载
+ meta: {
+ title: '首页', // 页面标题
+ keepAlive: false // keep-alive 标识
+ }
+ }
+]
+const createRouter = () =>
+ new Router({
+ // mode: 'history', // 如果你是 history模式 需要配置 vue.config.js publicPath
+ // base: '/app/',
+ scrollBehavior: () => ({ y: 0 }),
+ routes: router
+ })
+
+export default createRouter()
+```
+
+更多:[Vue Router](https://router.vuejs.org/zh/)
+
+[▲ 回顶部](#top)
+
+### ✅ Axios 封装及接口管理
+
+`utils/request.js` 封装 axios ,开发者需要根据后台接口做修改。
+
+- `service.interceptors.request.use` 里可以设置请求头,比如设置 `token`
+- `config.hideloading` 是在 api 文件夹下的接口参数里设置,下文会讲
+- `service.interceptors.response.use` 里可以对接口返回数据处理,比如 401 删除本地信息,重新登录
+
+```javascript
+import axios from 'axios'
+import store from '@/store'
+import { Toast } from 'vant'
+// 根据环境不同引入不同api地址
+import { baseApi } from '@/config'
+// create an axios instance
+const service = axios.create({
+ baseURL: baseApi, // url = base api url + request url
+ withCredentials: true, // send cookies when cross-domain requests
+ timeout: 5000 // request timeout
+})
+
+// request 拦截器 request interceptor
+service.interceptors.request.use(
+ config => {
+ // 不传递默认开启loading
+ if (!config.hideloading) {
+ // loading
+ Toast.loading({
+ forbidClick: true
+ })
+ }
+ if (store.getters.token) {
+ config.headers['X-Token'] = ''
+ }
+ return config
+ },
+ error => {
+ // do something with request error
+ console.log(error) // for debug
+ return Promise.reject(error)
+ }
+)
+// respone拦截器
+service.interceptors.response.use(
+ response => {
+ Toast.clear()
+ const res = response.data
+ if (res.status && res.status !== 200) {
+ // 登录超时,重新登录
+ if (res.status === 401) {
+ store.dispatch('FedLogOut').then(() => {
+ location.reload()
+ })
+ }
+ return Promise.reject(res || 'error')
+ } else {
+ return Promise.resolve(res)
+ }
+ },
+ error => {
+ Toast.clear()
+ console.log('err' + error) // for debug
+ return Promise.reject(error)
+ }
+)
+export default service
+```
+
+#### 接口管理
+
+在`src/api` 文件夹下统一管理接口
+
+- 你可以建立多个模块对接接口, 比如 `home.js` 里是首页的接口这里讲解 `user.js`
+- `url` 接口地址,请求的时候会拼接上 `config` 下的 `baseApi`
+- `method` 请求方法
+- `data` 请求参数 `qs.stringify(params)` 是对数据系列化操作
+- `hideloading` 默认 `false`,设置为 `true` 后,不显示 loading ui 交互中有些接口不需要让用户感知
+
+```javascript
+import qs from 'qs'
+// axios
+import request from '@/utils/request'
+//user api
+
+// 用户信息
+export function getUserInfo(params) {
+ return request({
+ url: '/user/userinfo
diff --git a/babel.config.js b/babel.config.js
new file mode 100644
index 0000000..0355640
--- /dev/null
+++ b/babel.config.js
@@ -0,0 +1,22 @@
+// 获取 VUE_APP_ENV 非 NODE_ENV,测试环境依然 console
+const IS_PROD = ['production', 'prod'].includes(process.env.VUE_APP_ENV)
+const plugins = [
+ [
+ 'import',
+ {
+ libraryName: 'vant',
+ libraryDirectory: 'es',
+ style: true
+ },
+ 'vant'
+ ]
+]
+// 去除 console.log
+if (IS_PROD) {
+ plugins.push('transform-remove-console')
+}
+
+module.exports = {
+ presets: [['@vue/cli-plugin-babel/preset', {useBuiltIns: 'usage', corejs: 3}]],
+ plugins
+}
diff --git a/jsconfig.json b/jsconfig.json
new file mode 100644
index 0000000..5097104
--- /dev/null
+++ b/jsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "target": "ES6",
+ "module": "commonjs",
+ "allowSyntheticDefaultImports": true,
+ "baseUrl": "./",
+ "paths": {
+ // 路径匹配
+ "@/*": ["src/*"]
+ }
+ },
+ "exclude": [
+ // 排除某些文件
+ "node_modules"
+ ],
+ "include": ["./src/**/*"] //自动引入Vue组件和普通Js模块
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..64cbe56
--- /dev/null
+++ b/package.json
@@ -0,0 +1,60 @@
+{
+ "name": "epmet-work-h5",
+ "version": "1.0.0",
+ "description": "",
+ "author": "",
+ "private": true,
+ "scripts": {
+ "serve": "vue-cli-service serve --host 0.0.0.0",
+ "dev": "vue-cli-service serve",
+ "build": "vue-cli-service build",
+ "build:dev": "vue-cli-service build --mode development",
+ "build:prod": "vue-cli-service build --mode production",
+ "build:stage": "vue-cli-service build --mode staging",
+ "lint": "vue-cli-service lint",
+ "deps": "yarn upgrade-interactive --latest"
+ },
+ "dependencies": {
+ "@vue-office/docx": "^1.0.0",
+ "@vue-office/excel": "^1.0.0",
+ "@vue-office/pdf": "^1.0.0",
+ "amfe-flexible": "^2.2.1",
+ "axios": "^0.27.2",
+ "core-js": "^3.23.3",
+ "dayjs": "^1.11.7",
+ "eslint": "^8.12.0",
+ "eslint-plugin-vue": "^8.4.0",
+ "filemanager-webpack-plugin": "^8.0.0",
+ "html-webpack-plugin": "^5.5.3",
+ "less-loader": "^11.1.0",
+ "postcss-pxtorem": "^6.0.0",
+ "regenerator-runtime": "^0.13.5",
+ "vant": "^2.12.48",
+ "vconsole": "^3.15.0",
+ "vue": "^2.7.8",
+ "vue-demi": "^0.14.0",
+ "vue-router": "^3.5.4",
+ "vuex": "^3.6.2"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.18.10",
+ "@babel/eslint-parser": "^7.18.2",
+ "@vue/cli-plugin-babel": "~5.0.8",
+ "@vue/cli-plugin-eslint": "~5.0.8",
+ "@vue/cli-plugin-router": "~5.0.8",
+ "@vue/cli-plugin-vuex": "~5.0.8",
+ "@vue/cli-service": "~5.0.8",
+ "ajv": "^7.2.4",
+ "babel-eslint": "^10.1.0",
+ "babel-plugin-import": "^1.13.5",
+ "babel-plugin-transform-remove-console": "^6.9.4",
+ "eslint-config-prettier": "^8.5.0",
+ "eslint-plugin-prettier": "^4.0.0",
+ "less": "^4.1.2",
+ "postcss": "^8.4.12",
+ "prettier": "2.6.2",
+ "script-ext-html-webpack-plugin": "2.1.4",
+ "svg-sprite-loader": "5.1.1",
+ "webpack-bundle-analyzer": "^4.5.0"
+ }
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..29f8606
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..f34d51c
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+ <%= webpackConfig.name %>
+
+
+
+
+
+
+
+
+
diff --git a/src/App.vue b/src/App.vue
new file mode 100644
index 0000000..2d238e1
--- /dev/null
+++ b/src/App.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/src/api/home.js b/src/api/home.js
new file mode 100644
index 0000000..4239f3b
--- /dev/null
+++ b/src/api/home.js
@@ -0,0 +1,6 @@
+// import qs from 'qs'
+// axios
+// import request from '@/utils/request'
+//home api
+
+
\ No newline at end of file
diff --git a/src/api/index.js b/src/api/index.js
new file mode 100644
index 0000000..6147745
--- /dev/null
+++ b/src/api/index.js
@@ -0,0 +1,7 @@
+const api = {
+ Login: '/user/login',
+ UserInfo: '/user/userinfo',
+ UserName: '/user/name'
+}
+
+export default api
diff --git a/src/api/user.js b/src/api/user.js
new file mode 100644
index 0000000..7a0ebff
--- /dev/null
+++ b/src/api/user.js
@@ -0,0 +1,32 @@
+import api from './index'
+// axios
+import request from '@/utils/request'
+
+// 登录
+export function login(data) {
+ return request({
+ url: api.Login,
+ method: 'post',
+ data
+ })
+}
+
+// 用户信息 post 方法
+export function getUserInfo(data) {
+ return request({
+ url: api.UserInfo,
+ method: 'post',
+ data,
+ hideloading: true
+ })
+}
+
+// 用户名称 get 方法
+export function getUserName(params) {
+ return request({
+ url: api.UserName,
+ method: 'get',
+ params,
+ hideloading: true
+ })
+}
diff --git a/src/assets/css/common.less b/src/assets/css/common.less
new file mode 100644
index 0000000..92853f5
--- /dev/null
+++ b/src/assets/css/common.less
@@ -0,0 +1,70 @@
+
+html,
+body #app {
+ color: @black;
+ background-color: @background-color;
+}
+
+#app {
+ font-size: 16px;
+ background-size: 100% auto;
+}
+
+.flex {
+ display: flex;
+}
+
+.mr10 {
+ margin-right: 10px;
+}
+
+.primary-color {
+ color: @blue;
+}
+
+.jcsb {
+ justify-content: space-between;
+}
+
+.aic {
+ align-items: center;
+}
+.container {
+ padding: 10px;
+}
+
+
+.card{
+ background-color: #fff;
+ border-radius: 2px;
+ box-shadow: 0 1px 3px rgba(0,0,0,.3);
+ box-sizing: border-box;
+ width: 100%;
+}
+.flex{
+ display: flex;
+ .flex1{
+ flex: 1;
+ }
+ &-y{
+ flex-direction: column;
+ }
+ &-center1{
+ justify-content: center;
+ }
+ &-center2{
+ align-items: center;
+ }
+ &-mean{
+ justify-content: space-around;
+ }
+ &-end{
+ justify-content: space-between;
+ }
+}
+.m-top12{
+ margin-top: 12px;
+}
+
+
+
diff --git a/src/assets/css/index.less b/src/assets/css/index.less
new file mode 100644
index 0000000..6572567
--- /dev/null
+++ b/src/assets/css/index.less
@@ -0,0 +1,35 @@
+
+html,
+body #app {
+ color: @black;
+ background-color: @background-color;
+}
+
+#app {
+ font-size: 16px;
+ background-size: 100% auto;
+}
+
+.flex {
+ display: flex;
+}
+
+.mr10 {
+ margin-right: 10px;
+}
+
+.primary-color {
+ color: @blue;
+}
+
+.jcsb {
+ justify-content: space-between;
+}
+
+.aic {
+ align-items: center;
+}
+.container {
+ padding: 10px;
+}
+
diff --git a/src/assets/css/mixin.scss b/src/assets/css/mixin.scss
new file mode 100644
index 0000000..327b6d4
--- /dev/null
+++ b/src/assets/css/mixin.scss
@@ -0,0 +1,36 @@
+// mixin
+// 清除浮动
+@mixin clearfix {
+ &:after {
+ content: "";
+ display: table;
+ clear: both;
+ }
+}
+
+// 多行隐藏
+@mixin textoverflow($clamp:1) {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ -o-text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: $clamp;
+ /*! autoprefixer: ignore next */
+ -webkit-box-orient: vertical;
+}
+
+//flex box
+@mixin flexbox($jc:space-between, $ai:center, $fd:row, $fw:nowrap) {
+ display: flex;
+ display: -webkit-flex;
+ flex: 1;
+ justify-content: $jc;
+ -webkit-justify-content: $jc;
+ align-items: $ai;
+ -webkit-align-items: $ai;
+ flex-direction: $fd;
+ -webkit-flex-direction: $fd;
+ flex-wrap: $fw;
+ -webkit-flex-wrap: $fw;
+}
diff --git a/src/assets/css/vant-theme.less b/src/assets/css/vant-theme.less
new file mode 100644
index 0000000..25374ca
--- /dev/null
+++ b/src/assets/css/vant-theme.less
@@ -0,0 +1,688 @@
+// Color Palette
+@black: #000;
+@white: #fff;
+@gray-1: #f7f8fa;
+@gray-2: #f2f3f5;
+@gray-3: #ebedf0;
+@gray-4: #dcdee0;
+@gray-5: #c8c9cc;
+@gray-6: #969799;
+@gray-7: #646566;
+@gray-8: #323233;
+@red: #ee0a24;
+@blue: #1989fa;
+@orange: #ff976a;
+@orange-dark: #ed6a0c;
+@orange-light: #fffbe8;
+@green: #07c160;
+
+// Gradient Colors
+@gradient-red: linear-gradient(to right, #ff6034, #ee0a24);
+@gradient-orange: linear-gradient(to right, #ffd01e, #ff8917);
+
+// Component Colors
+@text-color: @gray-8;
+@active-color: @gray-2;
+@active-opacity: 0.7;
+@disabled-opacity: 0.5;
+@background-color: @gray-1;
+@background-color-light: #fafafa;
+@text-link-color: #576b95;
+
+// Padding
+@padding-base: 4px;
+@padding-xs: @padding-base * 2;
+@padding-sm: @padding-base * 3;
+@padding-md: @padding-base * 4;
+@padding-lg: @padding-base * 6;
+@padding-xl: @padding-base * 8;
+
+// Font
+@font-size-xs: 10px;
+@font-size-sm: 12px;
+@font-size-md: 14px;
+@font-size-lg: 16px;
+@font-weight-bold: 500;
+@line-height-xs: 14px;
+@line-height-sm: 18px;
+@line-height-md: 20px;
+@line-height-lg: 22px;
+@base-font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue',
+ Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB',
+ 'Microsoft Yahei', sans-serif;
+@price-integer-font-family: Avenir-Heavy, PingFang SC, Helvetica Neue, Arial,
+ sans-serif;
+
+// Animation
+@animation-duration-base: 0.3s;
+@animation-duration-fast: 0.2s;
+
+// Border
+@border-color: @gray-3;
+@border-width-base: 1px;
+@border-radius-sm: 2px;
+@border-radius-md: 4px;
+@border-radius-lg: 8px;
+@border-radius-max: 999px;
+
+//ActionSheet
+@action-sheet-max-height: 90%;
+@action-sheet-header-height: 48px;
+@action-sheet-header-font-size: @font-size-lg;
+@action-sheet-description-color: @gray-6;
+@action-sheet-description-font-size: @font-size-md;
+@action-sheet-description-line-height: 20px;
+@action-sheet-item-background: @white;
+@action-sheet-item-font-size: @font-size-lg;
+@action-sheet-item-line-height: 22px;
+@action-sheet-item-text-color: @text-color;
+@action-sheet-item-disabled-text-color: @gray-5;
+@action-sheet-subname-color: @gray-6;
+@action-sheet-subname-font-size: @font-size-sm;
+@action-sheet-subname-line-height: 20px;
+@action-sheet-close-icon-size: 22px;
+@action-sheet-close-icon-color: @gray-5;
+@action-sheet-close-icon-padding: 0 @padding-md;
+@action-sheet-cancel-text-color: @gray-7;
+@action-sheet-cancel-padding-top: @padding-xs;
+@action-sheet-cancel-padding-color: @background-color;
+
+// Button
+@button-mini-height: 22px;
+@button-mini-min-width: 50px;
+@button-mini-font-size: @font-size-xs;
+@button-small-height: 30px;
+@button-small-font-size: @font-size-sm;
+@button-small-min-width: 60px;
+@button-normal-font-size: @font-size-md;
+@button-large-height: 50px;
+@button-default-color: @text-color;
+@button-default-height: 44px;
+@button-default-font-size: @font-size-lg;
+@button-default-background-color: @white;
+@button-default-border-color: @border-color;
+@button-primary-color: @white;
+@button-primary-background-color: @blue;
+@button-primary-border-color: @blue;
+@button-info-color: @white;
+@button-info-background-color: @blue;
+@button-info-border-color: @blue;
+@button-danger-color: @white;
+@button-danger-background-color: @red;
+@button-danger-border-color: @red;
+@button-warning-color: @white;
+@button-warning-background-color: @orange;
+@button-warning-border-color: @orange;
+@button-line-height: 20px;
+@button-border-width: 1px;
+@button-border-radius: @border-radius-sm;
+@button-round-border-radius: @border-radius-max;
+@button-plain-background-color: @white;
+@button-disabled-opacity: @disabled-opacity;
+
+// Calendar
+@calendar-height: 100%;
+@calendar-background-color: @white;
+@calendar-popup-height: 90%;
+@calendar-header-box-shadow: 0 2px 10px rgba(125, 126, 128, 0.16);
+@calendar-header-title-height: 44px;
+@calendar-header-title-font-size: @font-size-lg;
+@calendar-header-subtitle-font-size: @font-size-md;
+@calendar-weekdays-height: 30px;
+@calendar-weekdays-font-size: @font-size-sm;
+@calendar-month-title-font-size: @font-size-md;
+@calendar-month-mark-color: fade(@gray-2, 80%);
+@calendar-month-mark-font-size: 160px;
+@calendar-day-height: 64px;
+@calendar-day-font-size: @font-size-lg;
+@calendar-range-edge-color: @white;
+@calendar-range-edge-background-color: @red;
+@calendar-range-middle-color: @red;
+@calendar-range-middle-background-opacity: 0.1;
+@calendar-selected-day-size: 54px;
+@calendar-selected-day-color: @white;
+@calendar-info-font-size: @font-size-xs;
+@calendar-info-line-height: 14px;
+@calendar-selected-day-background-color: @red;
+@calendar-day-disabled-color: @gray-5;
+@calendar-confirm-button-height: 36px;
+@calendar-confirm-button-margin: 7px 0;
+@calendar-confirm-button-line-height: 34px;
+
+// Card
+@card-padding: @padding-xs @padding-md;
+@card-font-size: @font-size-sm;
+@card-text-color: @text-color;
+@card-background-color: @background-color-light;
+@card-thumb-size: 88px;
+@card-title-line-height: 16px;
+@card-desc-color: @gray-7;
+@card-desc-line-height: 20px;
+@card-price-color: @red;
+@card-origin-price-color: @gray-7;
+@card-origin-price-font-size: @font-size-xs;
+@card-price-font-size: @font-size-sm;
+@card-price-integer-font-size: @font-size-lg;
+@card-price-font-family: @price-integer-font-family;
+
+// Cell
+@cell-font-size: @font-size-md;
+@cell-line-height: 24px;
+@cell-vertical-padding: 10px;
+@cell-horizontal-padding: @padding-md;
+@cell-text-color: @text-color;
+@cell-background-color: @white;
+@cell-border-color: @border-color;
+@cell-active-color: @active-color;
+@cell-required-color: @red;
+@cell-label-color: @gray-6;
+@cell-label-font-size: @font-size-sm;
+@cell-label-line-height: 18px;
+@cell-label-margin-top: 3px;
+@cell-value-color: @gray-6;
+@cell-icon-size: 16px;
+@cell-right-icon-color: @gray-6;
+@cell-large-vertical-padding: @padding-sm;
+@cell-large-title-font-size: @font-size-lg;
+@cell-large-value-font-size: @font-size-lg;
+@cell-large-label-font-size: @font-size-md;
+
+// CellGroup
+@cell-group-background-color: @white;
+@cell-group-title-color: @gray-6;
+@cell-group-title-padding: @padding-md @padding-md @padding-xs;
+@cell-group-title-font-size: @font-size-md;
+@cell-group-title-line-height: 16px;
+@cell-group-inset-padding: 0 @padding-md;
+@cell-group-inset-border-radius: @border-radius-lg;
+@cell-group-inset-title-padding: @padding-md @padding-md @padding-xs @padding-xl;
+
+// Checkbox
+@checkbox-size: 20px;
+@checkbox-border-color: @gray-5;
+@checkbox-transition-duration: 0.2s;
+@checkbox-label-margin: 10px;
+@checkbox-label-color: @text-color;
+@checkbox-checked-icon-color: @blue;
+@checkbox-disabled-icon-color: @gray-5;
+@checkbox-disabled-label-color: @gray-5;
+@checkbox-disabled-background-color: @border-color;
+
+// Circle
+@circle-text-color: @text-color;
+
+// Collapse
+@collapse-item-transition-duration: 0.3s;
+@collapse-item-content-padding: 15px;
+@collapse-item-content-font-size: 13px;
+@collapse-item-content-line-height: 1.5;
+@collapse-item-content-text-color: @gray-6;
+@collapse-item-content-background-color: @white;
+@collapse-item-title-disabled-color: @gray-5;
+
+// CountDown
+@count-down-text-color: @text-color;
+@count-down-font-size: @font-size-md;
+@count-down-line-height: 20px;
+
+// Dialog
+@dialog-width: 320px;
+@dialog-small-screen-width: 90%;
+@dialog-font-size: @font-size-lg;
+@dialog-border-radius: 16px;
+@dialog-background-color: @white;
+@dialog-header-font-weight: @font-weight-bold;
+@dialog-header-line-height: 24px;
+@dialog-header-padding-top: @padding-lg;
+@dialog-header-isolated-padding: @padding-lg 0;
+@dialog-message-padding: @padding-lg;
+@dialog-message-font-size: @font-size-md;
+@dialog-message-line-height: 20px;
+@dialog-message-max-height: 60vh;
+@dialog-has-title-message-text-color: @gray-7;
+@dialog-has-title-message-padding-top: @padding-xs;
+
+// Field
+@field-label-color: @gray-7;
+@field-input-text-color: @text-color;
+@field-input-error-text-color: @red;
+@field-input-disabled-text-color: @gray-5;
+@field-placeholder-text-color: @gray-5;
+@field-icon-size: 16px;
+@field-clear-icon-size: 16px;
+@field-clear-icon-color: @gray-5;
+@field-icon-container-color: @gray-6;
+@field-error-message-color: @red;
+@field-error-message-text-font-size: @font-size-sm;
+@field-text-area-min-height: 18px;
+@field-word-limit-color: @gray-7;
+@field-word-limit-font-size: @font-size-sm;
+@field-word-limit-line-height: 16px;
+@field-word-num-full-color: @red;
+@field-disabled-text-color: @gray-5;
+
+// GoodsAction
+@goods-action-background-color: @white;
+@goods-action-height: 50px;
+@goods-action-icon-width: 48px;
+@goods-action-icon-height: @goods-action-height;
+@goods-action-icon-color: @text-color;
+@goods-action-icon-size: 18px;
+@goods-action-icon-font-size: @font-size-xs;
+@goods-action-icon-text-color: @gray-7;
+@goods-action-button-height: 40px;
+@goods-action-button-line-height: @button-line-height;
+@goods-action-button-border-radius: @border-radius-max;
+@goods-action-button-warning-color: @gradient-orange;
+@goods-action-button-danger-color: @gradient-red;
+@goods-action-button-plain-color: @white;
+
+// Image
+@image-placeholder-text-color: @gray-6;
+@image-placeholder-font-size: @font-size-md;
+@image-placeholder-background-color: @background-color;
+@image-loading-icon-size: 32px;
+@image-loading-icon-color: @gray-4;
+@image-error-icon-size: 32px;
+@image-error-icon-color: @gray-4;
+
+// Info
+@info-size: 16px;
+@info-color: @white;
+@info-padding: 0 3px;
+@info-font-size: 12px;
+@info-font-weight: 500;
+@info-border-width: 1px;
+@info-background-color: @red;
+@info-dot-color: @red;
+@info-dot-size: 8px;
+@info-font-family: -apple-system-font, Helvetica Neue, Arial, sans-serif;
+
+// Loading
+@loading-text-color: @gray-6;
+@loading-text-font-size: @font-size-md;
+@loading-text-line-height: 20px;
+@loading-spinner-color: @gray-5;
+@loading-spinner-size: 30px;
+@loading-spinner-animation-duration: 0.8s;
+
+// NavBar
+@nav-bar-height: 46px;
+@nav-bar-background-color: @white;
+@nav-bar-arrow-size: 16px;
+@nav-bar-icon-color: @blue;
+@nav-bar-text-color: @blue;
+@nav-bar-title-font-size: @font-size-lg;
+@nav-bar-title-text-color: @text-color;
+
+// NoticeBar
+@notice-bar-height: 40px;
+@notice-bar-padding: 0 @padding-md;
+@notice-bar-wrapable-padding: @padding-xs @padding-md;
+@notice-bar-font-size: @font-size-md;
+@notice-bar-text-color: @orange-dark;
+@notice-bar-line-height: 24px;
+@notice-bar-background-color: @orange-light;
+@notice-bar-icon-size: 16px;
+@notice-bar-icon-min-width: 22px;
+
+// Notify
+@notify-padding: 6px 15px;
+@notify-font-size: 14px;
+@notify-line-height: 20px;
+@notify-primary-background-color: @blue;
+@notify-success-background-color: @blue;
+@notify-danger-background-color: @red;
+@notify-warning-background-color: @orange;
+
+// Overlay
+@overlay-background-color: rgba(0, 0, 0, 0.7);
+
+// Panel
+@panel-background-color: @white;
+@panel-header-value-color: @red;
+@panel-footer-padding: @padding-xs @padding-md;
+
+// Picker
+@picker-background-color: @white;
+@picker-toolbar-height: 44px;
+@picker-title-font-size: @font-size-lg;
+@picker-action-padding: 0 @padding-md;
+@picker-action-font-size: @font-size-md;
+@picker-confirm-action-color: @text-link-color;
+@picker-cancel-action-color: @gray-6;
+@picker-option-font-size: @font-size-lg;
+@picker-option-text-color: @black;
+@picker-loading-icon-color: @blue;
+@picker-loading-mask-color: rgba(255, 255, 255, 0.9);
+@picker-option-disabled-opacity: 0.3;
+@picker-option-selected-text-color: @text-color;
+
+// Popup
+@popup-background-color: @white;
+@popup-round-border-radius: 16px;
+@popup-close-icon-size: 18px;
+@popup-close-icon-color: @gray-6;
+@popup-close-icon-margin: 16px;
+@popup-close-icon-z-index: 1;
+
+// Progress
+@progress-height: 4px;
+@progress-background-color: @gray-3;
+@progress-pivot-padding: 0 5px;
+@progress-color: @blue;
+@progress-pivot-font-size: @font-size-xs;
+@progress-pivot-line-height: 1.6;
+@progress-pivot-background-color: @blue;
+@progress-pivot-text-color: @white;
+
+// Radio
+@radio-size: 20px;
+@radio-border-color: @gray-5;
+@radio-transition-duration: 0.2s;
+@radio-label-margin: 10px;
+@radio-label-color: @text-color;
+@radio-checked-icon-color: @blue;
+@radio-disabled-icon-color: @gray-5;
+@radio-disabled-label-color: @gray-5;
+@radio-disabled-background-color: @border-color;
+
+// Rate
+@rate-horizontal-padding: 2px;
+@rate-icon-size: 20px;
+@rate-icon-void-color: @gray-5;
+@rate-icon-full-color: @red;
+@rate-icon-disabled-color: @gray-5;
+@rate-icon-gutter: @padding-base;
+
+// Switch
+@switch-width: 2em;
+@switch-height: 1em;
+@switch-node-size: 1em;
+@switch-node-z-index: 1;
+@switch-node-background-color: @white;
+@switch-node-box-shadow: 0 3px 1px 0 rgba(0, 0, 0, 0.05),
+ 0 2px 2px 0 rgba(0, 0, 0, 0.1), 0 3px 3px 0 rgba(0, 0, 0, 0.05);
+@switch-background-color: @white;
+@switch-on-background-color: @blue;
+@switch-transition-duration: 0.3s;
+@switch-disabled-opacity: 0.4;
+@switch-border: 1px solid rgba(0, 0, 0, 0.1);
+
+// ShareSheet
+@share-sheet-header-padding: @padding-sm @padding-md @padding-base;
+@share-sheet-title-color: @text-color;
+@share-sheet-title-font-size: @font-size-md;
+@share-sheet-title-line-height: @line-height-md;
+@share-sheet-description-color: @gray-6;
+@share-sheet-description-font-size: @font-size-sm;
+@share-sheet-description-line-height: 16px;
+@share-sheet-icon-size: 48px;
+@share-sheet-option-name-color: @gray-7;
+@share-sheet-option-name-font-size: @font-size-sm;
+@share-sheet-option-description-color: @gray-5;
+@share-sheet-option-description-font-size: @font-size-sm;
+@share-sheet-cancel-button-font-size: @font-size-lg;
+@share-sheet-cancel-button-height: 48px;
+@share-sheet-cancel-button-background: @white;
+
+// Search
+@search-background-color: @gray-1;
+@search-padding: 10px @padding-sm;
+@search-input-height: 34px;
+@search-label-padding: 0 5px;
+@search-label-color: @text-color;
+@search-label-font-size: @font-size-md;
+@search-left-icon-color: @gray-6;
+@search-action-padding: 0 @padding-xs;
+@search-action-text-color: @text-color;
+@search-action-font-size: @font-size-md;
+
+// Sidebar
+@sidebar-width: 80px;
+
+// SidebarItem
+@sidebar-font-size: @font-size-md;
+@sidebar-line-height: 20px;
+@sidebar-text-color: @text-color;
+@sidebar-disabled-text-color: @gray-5;
+@sidebar-padding: 20px @padding-sm 20px @padding-xs;
+@sidebar-active-color: @active-color;
+@sidebar-background-color: @background-color;
+@sidebar-selected-font-weight: @font-weight-bold;
+@sidebar-selected-text-color: @text-color;
+@sidebar-selected-border-color: @red;
+@sidebar-selected-background-color: @white;
+
+// Slider
+@slider-active-background-color: @blue;
+@slider-inactive-background-color: @gray-3;
+@slider-disabled-opacity: @disabled-opacity;
+@slider-bar-height: 2px;
+@slider-button-width: 24px;
+@slider-button-height: 24px;
+@slider-button-border-radius: 50%;
+@slider-button-background-color: @white;
+@slider-button-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
+
+// Step
+@step-text-color: @gray-6;
+@step-process-text-color: @text-color;
+@step-font-size: @font-size-md;
+@step-line-color: @border-color;
+@step-finish-line-color: @blue;
+@step-finish-text-color: @text-color;
+@step-icon-size: 12px;
+@step-circle-size: 5px;
+@step-circle-color: @gray-6;
+@step-horizontal-title-font-size: @font-size-sm;
+
+// Steps
+@steps-background-color: @white;
+
+// Stepper
+@stepper-active-color: #e8e8e8;
+@stepper-background-color: @active-color;
+@stepper-button-icon-color: @text-color;
+@stepper-button-disabled-color: @background-color;
+@stepper-button-disabled-icon-color: @gray-5;
+@stepper-button-round-theme-color: @red;
+@stepper-input-width: 32px;
+@stepper-input-height: 28px;
+@stepper-input-font-size: @font-size-md;
+@stepper-input-line-height: normal;
+@stepper-input-text-color: @text-color;
+@stepper-input-disabled-text-color: @gray-5;
+@stepper-input-disabled-background-color: @active-color;
+@stepper-border-radius: @border-radius-md;
+
+// SubmitBar
+@submit-bar-height: 50px;
+@submit-bar-z-index: 100;
+@submit-bar-background-color: @white;
+@submit-bar-button-width: 110px;
+@submit-bar-price-color: @red;
+@submit-bar-price-font-size: @font-size-sm;
+@submit-bar-currency-font-size: @font-size-sm;
+@submit-bar-text-color: @text-color;
+@submit-bar-text-font-size: 14px;
+@submit-bar-tip-padding: 10px;
+@submit-bar-tip-font-size: 12px;
+@submit-bar-tip-line-height: 1.5;
+@submit-bar-tip-color: #f56723;
+@submit-bar-tip-background-color: #fff7cc;
+@submit-bar-tip-icon-size: 12px;
+@submit-bar-button-height: 40px;
+@submit-bar-padding: 0 @padding-md;
+@submit-bar-price-integer-font-size: 20px;
+@submit-bar-price-font-family: @price-integer-font-family;
+
+// Tabbar
+@tabbar-height: 50px;
+@tabbar-background-color: @white;
+
+// TabbarItem
+@tabbar-item-font-size: @font-size-sm;
+@tabbar-item-text-color: @gray-7;
+@tabbar-item-active-color: @blue;
+@tabbar-item-line-height: 1;
+@tabbar-item-icon-size: 22px;
+@tabbar-item-margin-bottom: 4px;
+
+// Tab
+@tab-text-color: @gray-7;
+@tab-active-text-color: @text-color;
+@tab-disabled-text-color: @gray-5;
+@tab-font-size: @font-size-md;
+
+// Tabs
+@tabs-default-color: @red;
+@tabs-line-height: 44px;
+@tabs-card-height: 30px;
+@tabs-nav-background-color: @white;
+@tabs-bottom-bar-height: 3px;
+@tabs-bottom-bar-color: @tabs-default-color;
+
+// Tag
+@tag-padding: 0 @padding-base;
+@tag-text-color: @white;
+@tag-font-size: @font-size-sm;
+@tag-border-radius: 2px;
+@tag-line-height: 16px;
+@tag-medium-padding: 2px 6px;
+@tag-large-padding: @padding-base @padding-xs;
+@tag-large-border-radius: @border-radius-md;
+@tag-large-font-size: @font-size-md;
+@tag-round-border-radius: @border-radius-max;
+@tag-danger-color: @red;
+@tag-primary-color: @blue;
+@tag-success-color: @blue;
+@tag-warning-color: @orange;
+@tag-default-color: @gray-6;
+@tag-plain-background-color: @white;
+
+// Toast
+@toast-max-width: 70%;
+@toast-font-size: 14px;
+@toast-text-color: @white;
+@toast-line-height: 20px;
+@toast-border-radius: @border-radius-lg;
+@toast-background-color: fade(@black, 70%);
+@toast-icon-size: 36px;
+@toast-text-min-width: 96px;
+@toast-text-padding: @padding-xs @padding-sm;
+@toast-default-padding: @padding-md;
+@toast-default-width: 88px;
+@toast-default-min-height: 88px;
+
+// GridItem
+@grid-item-content-padding: @padding-md @padding-xs;
+@grid-item-content-background-color: @white;
+@grid-item-content-active-color: @active-color;
+@grid-item-icon-size: 26px;
+@grid-item-text-color: @gray-7;
+@grid-item-text-font-size: @font-size-sm;
+
+// Divider
+@divider-margin: @padding-md 0;
+@divider-text-color: @gray-6;
+@divider-font-size: @font-size-md;
+@divider-line-height: 24px;
+@divider-border-color: @border-color;
+@divider-content-padding: @padding-md;
+@divider-content-left-width: 10%;
+@divider-content-right-width: 10%;
+
+// Empty
+@empty-padding: @padding-xl 0;
+@empty-image-size: 160px;
+@empty-description-margin-top: @padding-md;
+@empty-description-padding: 0 60px;
+@empty-description-color: @gray-6;
+@empty-description-font-size: 14px;
+@empty-description-line-height: 20px;
+@empty-bottom-margin-top: 24px;
+
+// TreeSelect
+@tree-select-font-size: @font-size-md;
+@tree-select-nav-background-color: @background-color;
+@tree-select-content-background-color: @white;
+@tree-select-nav-item-padding: @padding-sm @padding-xs @padding-sm @padding-sm;
+@tree-select-item-height: 44px;
+@tree-select-item-active-color: @red;
+@tree-select-item-disabled-color: @gray-5;
+
+// Uploader
+@uploader-size: 80px;
+@uploader-icon-size: 24px;
+@uploader-icon-color: @gray-4;
+@uploader-text-color: @gray-6;
+@uploader-text-font-size: @font-size-sm;
+@uploader-upload-background-color: @gray-1;
+@uploader-upload-active-color: @active-color;
+@uploader-delete-color: @white;
+@uploader-delete-icon-size: 14px;
+@uploader-delete-background-color: rgba(0, 0, 0, 0.7);
+@uploader-file-background-color: @background-color;
+@uploader-file-icon-size: 20px;
+@uploader-file-icon-color: @gray-7;
+@uploader-file-name-padding: 0 @padding-base;
+@uploader-file-name-margin-top: @padding-xs;
+@uploader-file-name-font-size: @font-size-sm;
+@uploader-file-name-text-color: @gray-7;
+@uploader-mask-background-color: fade(@gray-8, 88%);
+@uploader-mask-icon-size: 22px;
+@uploader-mask-message-font-size: @font-size-sm;
+@uploader-mask-message-line-height: 14px;
+@uploader-loading-icon-size: 22px;
+@uploader-loading-icon-color: @white;
+@uploader-disabled-opacity: @disabled-opacity;
+
+// DropdownMenu
+@dropdown-menu-height: 50px;
+@dropdown-menu-background-color: @white;
+@dropdown-menu-title-font-size: 15px;
+@dropdown-menu-title-text-color: @text-color;
+@dropdown-menu-title-active-text-color: @red;
+@dropdown-menu-title-disabled-text-color: @gray-6;
+@dropdown-menu-title-padding: 0 @padding-lg 0 @padding-xs;
+@dropdown-menu-title-line-height: 18px;
+@dropdown-menu-option-active-color: @red;
+@dropdown-menu-box-shadow: 0 2px 12px fade(@gray-7, 12);
+
+// IndexAnchor
+@index-anchor-padding: 0 @padding-md;
+@index-anchor-text-color: @text-color;
+@index-anchor-font-weight: 500;
+@index-anchor-font-size: @font-size-md;
+@index-anchor-line-height: 32px;
+@index-anchor-background-color: transparent;
+@index-anchor-active-background-color: @white;
+@index-anchor-active-text-color: @blue;
+
+// IndexBar
+@index-bar-index-font-size: @font-size-xs;
+@index-bar-index-line-height: 14px;
+
+// skeleton
+@skeleton-padding: 0 @padding-md;
+@skeleton-row-height: 16px;
+@skeleton-row-background-color: @gray-2;
+@skeleton-row-margin-top: @padding-sm;
+@skeleton-avatar-background-color: @gray-2;
+@skeleton-animation-duration: 1.2s;
+
+// Cascader
+@cascader-header-height: 48px;
+@cascader-header-padding: 0 16px;
+@cascader-title-font-size: 16px;
+@cascader-title-line-height: 20px;
+@cascader-close-icon-size: 22px;
+@cascader-close-icon-color: #c8c9cc;
+@cascader-selected-icon-size: 18px;
+@cascader-tabs-height: 48px;
+@cascader-active-color: @blue;
+@cascader-options-height: 384px;
+@cascader-option-disabled-color: #c8c9cc;
+@cascader-tab-color: #323233;
+@cascader-unselected-tab-color: #969799;
\ No newline at end of file
diff --git a/src/assets/css/variables.scss b/src/assets/css/variables.scss
new file mode 100644
index 0000000..ee5a721
--- /dev/null
+++ b/src/assets/css/variables.scss
@@ -0,0 +1,3 @@
+// variables
+$background-color: #f8f8f8;
+$theme-color: #07b0b8;
diff --git a/src/assets/logo.png b/src/assets/logo.png
new file mode 100644
index 0000000..f3d2503
Binary files /dev/null and b/src/assets/logo.png differ
diff --git a/src/components/TabBar.vue b/src/components/TabBar.vue
new file mode 100644
index 0000000..631e926
--- /dev/null
+++ b/src/components/TabBar.vue
@@ -0,0 +1,54 @@
+
+
+
+
+ {{ item.title }}
+
+
+
+
+
+
+
+
diff --git a/src/config/env.development.js b/src/config/env.development.js
new file mode 100644
index 0000000..046d75b
--- /dev/null
+++ b/src/config/env.development.js
@@ -0,0 +1,9 @@
+// 本地环境配置
+module.exports = {
+ env: 'development',
+ title: 'e联社区',
+ baseUrl: 'http://192.168.1.144/', // 项目地址
+ baseApi: 'http://192.168.1.144/api', // 本地api请求地址,注意:如果你使用了代理,请设置成'/'
+ APPID: 'xxx',
+ APPSECRET: 'xxx',
+}
diff --git a/src/config/env.production.js b/src/config/env.production.js
new file mode 100644
index 0000000..fc3e233
--- /dev/null
+++ b/src/config/env.production.js
@@ -0,0 +1,8 @@
+// 正式
+module.exports = {
+ env: 'production',
+ title: 'e联社区',
+ baseUrl: 'https://epmet-preview.elinkservice.cn/', // 正式项目地址
+ baseApi: 'https://epmet-preview.elinkservice.cn/api', // 正式api请求地址
+ APPSECRET: 'xxx',
+}
diff --git a/src/config/env.staging.js b/src/config/env.staging.js
new file mode 100644
index 0000000..0f67f68
--- /dev/null
+++ b/src/config/env.staging.js
@@ -0,0 +1,8 @@
+module.exports = {
+ env: 'staging',
+ title: 'e联社区',
+ baseUrl: 'https://test.xxx.com', // 测试项目地址
+ baseApi: 'https://test.xxx.com/api', // 测试api请求地址
+ APPID: 'xxx',
+ APPSECRET: 'xxx',
+}
diff --git a/src/config/index.js b/src/config/index.js
new file mode 100644
index 0000000..72a4fa1
--- /dev/null
+++ b/src/config/index.js
@@ -0,0 +1,3 @@
+// 根据环境引入不同配置 process.env.NODE_ENV
+const config = require('./env.' + process.env.VUE_APP_ENV)
+module.exports = config
diff --git a/src/const/index.js b/src/const/index.js
new file mode 100644
index 0000000..e69de29
diff --git a/src/filters/filter.js b/src/filters/filter.js
new file mode 100644
index 0000000..e8bb4c0
--- /dev/null
+++ b/src/filters/filter.js
@@ -0,0 +1,37 @@
+/**
+ *格式化时间
+ *yyyy-MM-dd hh:mm:ss
+ */
+export function formatDate(time, fmt) {
+ if (time === undefined || '') {
+ return
+ }
+ const date = new Date(time)
+ if (/(y+)/.test(fmt)) {
+ fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
+ }
+ const o = {
+ 'M+': date.getMonth() + 1,
+ 'd+': date.getDate(),
+ 'h+': date.getHours(),
+ 'm+': date.getMinutes(),
+ 's+': date.getSeconds()
+ }
+ for (const k in o) {
+ if (new RegExp(`(${k})`).test(fmt)) {
+ const str = o[k] + ''
+ fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? str : padLeftZero(str))
+ }
+ }
+ return fmt
+}
+
+function padLeftZero(str) {
+ return ('00' + str).substr(str.length)
+}
+/*
+ * 隐藏用户手机号中间四位
+ */
+export function hidePhone(phone) {
+ return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
+}
diff --git a/src/filters/index.js b/src/filters/index.js
new file mode 100644
index 0000000..a889078
--- /dev/null
+++ b/src/filters/index.js
@@ -0,0 +1,7 @@
+import Vue from 'vue'
+import * as filter from './filter'
+
+Object.keys(filter).forEach(k => Vue.filter(k, filter[k]))
+
+Vue.prototype.$formatDate = Vue.filter('formatDate')
+Vue.prototype.$hidePhone = Vue.filter('hidePhone')
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..92f038e
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,39 @@
+import 'core-js/stable'
+import 'regenerator-runtime/runtime'
+
+import Vue from 'vue'
+import App from './App.vue'
+import router from './router'
+import store from './store'
+
+// 全局引入按需引入UI库 vant
+import '@/plugins/vant'
+
+// 引入全局样式
+import '@/assets/css/common.less'
+import '@/assets/css/index.less'
+// 移动端适配
+import 'amfe-flexible'
+
+Vue.config.productionTip = false
+
+// 日期格式化插件
+import dayjs from 'dayjs'
+
+Vue.prototype.$dayjs = dayjs
+// 提示框封装
+// import { Tips } from '@/utils'
+
+// Vue.prototype.$tips = Tips
+//开发环境使用,生产环境自动取消
+import Vconsole from 'vconsole'
+
+if (process.env.NODE_ENV !== 'production') {
+ new Vconsole()
+}
+new Vue({
+ el: '#app',
+ router,
+ store,
+ render: h => h(App)
+})
diff --git a/src/plugins/vant.js b/src/plugins/vant.js
new file mode 100644
index 0000000..343ce83
--- /dev/null
+++ b/src/plugins/vant.js
@@ -0,0 +1,53 @@
+// 按需全局引入 @组件
+import Vue from 'vue'
+import {
+ Button,
+ Field,
+ Cell,
+ Uploader,
+ Image,
+ Picker,
+ Popup,
+ Icon,
+ Toast,
+ Progress,
+ NavBar,
+ RadioGroup,
+ Radio,
+ Dialog,
+ Tabs,
+ Tab,
+ Tag,
+ Steps,
+ Step,
+ Divider,
+ List,
+ ActionSheet,
+ Checkbox,
+ CellGroup
+} from 'vant'
+
+Vue.use(Button)
+ .use(Field)
+ .use(Cell)
+ .use(Uploader)
+ .use(Image)
+ .use(Picker)
+ .use(Popup)
+ .use(Icon)
+ .use(Toast)
+ .use(Progress)
+ .use(NavBar)
+ .use(RadioGroup)
+ .use(Radio)
+ .use(Dialog)
+ .use(Tabs)
+ .use(Tab)
+ .use(Tag)
+ .use(Steps)
+ .use(Step)
+ .use(Divider)
+ .use(List)
+ .use(ActionSheet)
+ .use(Checkbox)
+ .use(CellGroup)
diff --git a/src/router/index.js b/src/router/index.js
new file mode 100644
index 0000000..5889228
--- /dev/null
+++ b/src/router/index.js
@@ -0,0 +1,30 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+import { constantRouterMap } from './router.config.js'
+
+// hack router push callback
+const originalPush = Router.prototype.push
+Router.prototype.push = function push(location, onResolve, onReject) {
+ if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
+ return originalPush.call(this, location).catch(err => err)
+}
+
+Vue.use(Router)
+
+const createRouter = () =>
+ new Router({
+ scrollBehavior: () => ({ y: 0 }),
+ routes: constantRouterMap
+ })
+
+const router = createRouter()
+
+// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
+export function resetRouter() {
+ const newRouter = createRouter()
+ router.matcher = newRouter.matcher // reset router
+}
+
+
+
+export default router
\ No newline at end of file
diff --git a/src/router/router.config.js b/src/router/router.config.js
new file mode 100644
index 0000000..34b3015
--- /dev/null
+++ b/src/router/router.config.js
@@ -0,0 +1,11 @@
+/**
+ * 基础路由
+ * @type { *[] }
+ */
+export const constantRouterMap = [
+ {
+ path: '/',
+ component: () => import('@/views/login'),
+ meta: { title: '登录', keepAlive: false }
+ }
+]
diff --git a/src/store/getters.js b/src/store/getters.js
new file mode 100644
index 0000000..93634df
--- /dev/null
+++ b/src/store/getters.js
@@ -0,0 +1,4 @@
+const getters = {
+ userName: state => state.app.userName
+}
+export default getters
diff --git a/src/store/index.js b/src/store/index.js
new file mode 100644
index 0000000..1ee10b9
--- /dev/null
+++ b/src/store/index.js
@@ -0,0 +1,15 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import getters from './getters'
+import app from './modules/app'
+
+Vue.use(Vuex)
+
+const store = new Vuex.Store({
+ modules: {
+ app
+ },
+ getters
+})
+
+export default store
diff --git a/src/store/modules/app.js b/src/store/modules/app.js
new file mode 100644
index 0000000..7f81758
--- /dev/null
+++ b/src/store/modules/app.js
@@ -0,0 +1,19 @@
+const state = {
+ userName: ''
+}
+const mutations = {
+ SET_USER_NAME(state, name) {
+ state.userName = name
+ }
+}
+const actions = {
+ // 设置name
+ setUserName({ commit }, name) {
+ commit('SET_USER_NAME', name)
+ }
+}
+export default {
+ state,
+ mutations,
+ actions
+}
diff --git a/src/utils/index.js b/src/utils/index.js
new file mode 100644
index 0000000..cd14bd5
--- /dev/null
+++ b/src/utils/index.js
@@ -0,0 +1,110 @@
+/**
+ * Created by PanJiaChen on 16/11/18.
+ */
+
+/**
+ * Parse the time to string
+ * @param {(Object|string|number)} time
+ * @param {string} cFormat
+ * @returns {string}
+ */
+export function parseTime(time, cFormat) {
+ if (arguments.length === 0) {
+ return null
+ }
+ const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
+ let date
+ if (typeof time === 'object') {
+ date = time
+ } else {
+ if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
+ time = parseInt(time)
+ }
+ if ((typeof time === 'number') && (time.toString().length === 10)) {
+ time = time * 1000
+ }
+ date = new Date(time)
+ }
+ const formatObj = {
+ y: date.getFullYear(),
+ m: date.getMonth() + 1,
+ d: date.getDate(),
+ h: date.getHours(),
+ i: date.getMinutes(),
+ s: date.getSeconds(),
+ a: date.getDay()
+ }
+ const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+ let value = formatObj[key]
+ // Note: getDay() returns 0 on Sunday
+ if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
+ if (result.length > 0 && value < 10) {
+ value = '0' + value
+ }
+ return value || 0
+ })
+ return time_str
+}
+
+/**
+ * @param {number} time
+ * @param {string} option
+ * @returns {string}
+ */
+export function formatTime(time, option) {
+ if (('' + time).length === 10) {
+ time = parseInt(time) * 1000
+ } else {
+ time = +time
+ }
+ const d = new Date(time)
+ const now = Date.now()
+
+ const diff = (now - d) / 1000
+
+ if (diff < 30) {
+ return '刚刚'
+ } else if (diff < 3600) {
+ // less 1 hour
+ return Math.ceil(diff / 60) + '分钟前'
+ } else if (diff < 3600 * 24) {
+ return Math.ceil(diff / 3600) + '小时前'
+ } else if (diff < 3600 * 24 * 2) {
+ return '1天前'
+ }
+ if (option) {
+ return parseTime(time, option)
+ } else {
+ return (
+ d.getMonth() +
+ 1 +
+ '月' +
+ d.getDate() +
+ '日' +
+ d.getHours() +
+ '时' +
+ d.getMinutes() +
+ '分'
+ )
+ }
+}
+
+/**
+ * @param {string} url
+ * @returns {Object}
+ */
+export function param2Obj(url) {
+ const search = url.split('?')[1]
+ if (!search) {
+ return {}
+ }
+ return JSON.parse(
+ '{"' +
+ decodeURIComponent(search)
+ .replace(/"/g, '\\"')
+ .replace(/&/g, '","')
+ .replace(/=/g, '":"')
+ .replace(/\+/g, ' ') +
+ '"}'
+ )
+}
diff --git a/src/utils/jsApiList.js b/src/utils/jsApiList.js
new file mode 100644
index 0000000..6d3cd63
--- /dev/null
+++ b/src/utils/jsApiList.js
@@ -0,0 +1,7 @@
+export const jsApiList = [
+ 'updateAppMessageShareData',
+ 'updateTimelineShareData',
+ 'getLocation',
+ 'openLocation',
+ 'chooseWXPay'
+]
diff --git a/src/utils/request.js b/src/utils/request.js
new file mode 100644
index 0000000..6788f91
--- /dev/null
+++ b/src/utils/request.js
@@ -0,0 +1,58 @@
+import axios from 'axios'
+import store from '@/store'
+import { Toast } from 'vant'
+// 根据环境不同引入不同api地址
+import { baseApi } from '@/config'
+// create an axios instance
+const service = axios.create({
+ baseURL: baseApi, // url = base api url + request url
+ withCredentials: true, // send cookies when cross-domain requests
+ timeout: 5000 // request timeout
+})
+
+// request拦截器 request interceptor
+service.interceptors.request.use(
+ config => {
+ // 不传递默认开启loading
+ if (!config.hideloading) {
+ // loading
+ Toast.loading({
+ forbidClick: true
+ })
+ }
+ if (store.getters.token) {
+ config.headers['X-Token'] = ''
+ }
+ return config
+ },
+ error => {
+ // do something with request error
+ console.log(error) // for debug
+ return Promise.reject(error)
+ }
+)
+// respone拦截器
+service.interceptors.response.use(
+ response => {
+ Toast.clear()
+ const res = response.data
+ if (res.status && res.status !== 200) {
+ // 登录超时,重新登录
+ if (res.status === 401) {
+ store.dispatch('FedLogOut').then(() => {
+ location.reload()
+ })
+ }
+ return Promise.reject(res || 'error')
+ } else {
+ return Promise.resolve(res)
+ }
+ },
+ error => {
+ Toast.clear()
+ console.log('err' + error) // for debug
+ return Promise.reject(error)
+ }
+)
+
+export default service
diff --git a/src/utils/storage.js b/src/utils/storage.js
new file mode 100644
index 0000000..8105661
--- /dev/null
+++ b/src/utils/storage.js
@@ -0,0 +1,35 @@
+/**
+ * 封装操作localstorage本地存储的方法
+ */
+export const storage = {
+ //存储
+ set(key, value) {
+ localStorage.setItem(key, JSON.stringify(value))
+ },
+ //取出数据
+ get(key) {
+ return JSON.parse(localStorage.getItem(key))
+ },
+ // 删除数据
+ remove(key) {
+ localStorage.removeItem(key)
+ }
+}
+
+/**
+ * 封装操作sessionStorage本地存储的方法
+ */
+export const sessionStorage = {
+ //存储
+ set(key, value) {
+ window.sessionStorage.setItem(key, JSON.stringify(value))
+ },
+ //取出数据
+ get(key) {
+ return JSON.parse(window.sessionStorage.getItem(key))
+ },
+ // 删除数据
+ remove(key) {
+ window.sessionStorage.removeItem(key)
+ }
+}
diff --git a/src/utils/validate.js b/src/utils/validate.js
new file mode 100644
index 0000000..e9bd1ba
--- /dev/null
+++ b/src/utils/validate.js
@@ -0,0 +1,20 @@
+/**
+ * Created by Sunnie on 19/06/04.
+ */
+
+/**
+ * @param {string} path
+ * @returns {Boolean}
+ */
+export function isExternal(path) {
+ return /^(https?:|mailto:|tel:)/.test(path)
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validUsername(str) {
+ const valid_map = ['admin', 'editor']
+ return valid_map.indexOf(str.trim()) >= 0
+}
diff --git a/src/utils/vconsole.js b/src/utils/vconsole.js
new file mode 100644
index 0000000..dd6121a
--- /dev/null
+++ b/src/utils/vconsole.js
@@ -0,0 +1,3 @@
+import Vconsole from 'vconsole'
+const vConsole = new Vconsole()
+export default vConsole
diff --git a/src/utils/wechatPlugin.js b/src/utils/wechatPlugin.js
new file mode 100644
index 0000000..90e6ab4
--- /dev/null
+++ b/src/utils/wechatPlugin.js
@@ -0,0 +1,12 @@
+import wx from 'weixin-js-sdk'
+
+const plugin = {
+ install(Vue) {
+ Vue.prototype.$wx = wx
+ Vue.wx = wx
+ },
+ $wx: wx
+}
+
+export default plugin
+export const install = plugin.install
diff --git a/src/views/login/index.vue b/src/views/login/index.vue
new file mode 100644
index 0000000..d25101f
--- /dev/null
+++ b/src/views/login/index.vue
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
登录
+
+ 下次自动登录
+ 忘记密码?
+
+
+
+
+
+
+
diff --git a/static/image/demo.png b/static/image/demo.png
new file mode 100644
index 0000000..0cf476f
Binary files /dev/null and b/static/image/demo.png differ
diff --git a/static/image/secret.png b/static/image/secret.png
new file mode 100644
index 0000000..c57b074
Binary files /dev/null and b/static/image/secret.png differ
diff --git a/vue.config.js b/vue.config.js
new file mode 100644
index 0000000..d63a012
--- /dev/null
+++ b/vue.config.js
@@ -0,0 +1,171 @@
+const path = require('path')
+const defaultSettings = require('./src/config/index.js')
+const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
+const OUTPUT_FILE_NAME = process.env.outputDir
+const FileManagerPlugin = require('filemanager-webpack-plugin')
+const resolve = dir => path.join(__dirname, dir)
+// page title
+const name = defaultSettings.title || 'vue mobile template'
+// 生产环境,测试和正式
+const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV)
+
+const { defineConfig } = require('@vue/cli-service')
+module.exports = defineConfig({
+ publicPath: process.env.NODE_ENV === 'development' ? '/' : '/epmet-work-h5', // 署应用包时的基本 URL。 vue-router hash 模式使用
+ // publicPath: process.env.NODE_ENV === 'development' ? '/' : '/h5', //署应用包时的基本 URL。 vue-router history模式使用
+ outputDir: `dist/${OUTPUT_FILE_NAME}`, // 生产环境构建文件的目录
+ assetsDir: 'static', // outputDir的静态资源(js、css、img、fonts)目录
+ lintOnSave: false,
+ productionSourceMap: false, // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
+ devServer: {
+ // host: 'epmet-cloud.elinkservice.cn',
+ port: 80, // 端口
+ open: false, // 启动后打开浏览器
+ client: {
+ overlay: {
+ // 当出现编译器错误或警告时,在浏览器中显示全屏覆盖层
+ warnings: false,
+ errors: true
+ }
+ },
+ proxy: {
+ //配置跨域
+ '/api': {
+ // target: `http://219.146.91.110:30801`,
+ target: `http://127.0.0.1:8080`,
+ changeOrigin: true,
+ ws: false,
+ pathRewrite: {
+ '^api': ''
+ }
+ }
+ }
+ },
+ css: {
+ extract: IS_PROD,
+ sourceMap: false,
+ loaderOptions: {
+ less: {
+ lessOptions: {
+ modifyVars: {
+ hack: `true; @import "assets/css/vant-theme.less";`
+ }
+ }
+ }
+ }
+ },
+ configureWebpack: config => {
+ config.name = name
+ config.plugins.push(
+ new FileManagerPlugin({
+ events: {
+ onEnd: {
+ delete: [`./${OUTPUT_FILE_NAME}.zip`],
+ archive: [
+ {
+ source: `./dist/${OUTPUT_FILE_NAME}`,
+ destination: `./${OUTPUT_FILE_NAME}.zip`
+ }
+ ]
+ }
+ }
+ })
+ )
+
+ // 为生产环境修改配置...
+ // if (IS_PROD) {
+ // // externals
+ // config.externals = externals
+ // }
+ },
+
+ chainWebpack: config => {
+ config.plugins.delete('preload') // TODO: need test
+ config.plugins.delete('prefetch') // TODO: need test
+
+ // set svg-sprite-loader
+ config.module.rule('svg').exclude.add(resolve('src/assets/icons')).end()
+ config.module
+ .rule('icons')
+ .test(/\.svg$/)
+ .include.add(resolve('src/assets/icons'))
+ .end()
+ .use('svg-sprite-loader')
+ .loader('svg-sprite-loader')
+ .options({
+ symbolId: 'icon-[name]'
+ })
+ .end()
+
+ // 别名 alias
+ config.resolve.alias
+ .set('@', resolve('src'))
+ .set('assets', resolve('src/assets'))
+ .set('api', resolve('src/api'))
+ .set('views', resolve('src/views'))
+ .set('components', resolve('src/components'))
+ /**
+ * 设置保留空格
+ */
+ config.module
+ .rule('vue')
+ .use('vue-loader')
+ .loader('vue-loader')
+ .tap(options => {
+ options.compilerOptions.preserveWhitespace = true
+ return options
+ })
+ .end()
+ /**
+ * 打包分析
+ */
+ if (IS_PROD) {
+ config.plugin('webpack-report').use(BundleAnalyzerPlugin, [
+ {
+ analyzerMode: 'static'
+ }
+ ])
+ }
+ config
+ // https://webpack.js.org/configuration/devtool/#development
+ .when(!IS_PROD, config => config.devtool('cheap-source-map'))
+
+ config.when(IS_PROD, config => {
+ config
+ .plugin('ScriptExtHtmlWebpackPlugin')
+ .after('html')
+ .use('script-ext-html-webpack-plugin', [
+ {
+ // 将 runtime 作为内联引入不单独存在
+ inline: /runtime\..*\.js$/
+ }
+ ])
+ .end()
+ config.optimization.splitChunks({
+ chunks: 'all',
+ cacheGroups: {
+ // cacheGroups 下可以可以配置多个组,每个组根据test设置条件,符合test条件的模块
+ commons: {
+ name: 'chunk-commons',
+ test: resolve('src/components'),
+ minChunks: 3, // 被至少用三次以上打包分离
+ priority: 5, // 优先级
+ reuseExistingChunk: true // 表示是否使用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的。
+ },
+ node_vendors: {
+ name: 'chunk-libs',
+ chunks: 'initial', // 只打包初始时依赖的第三方
+ test: /[\\/]node_modules[\\/]/,
+ priority: 10
+ },
+ vantUI: {
+ name: 'chunk-vantUI', // 单独将 vantUI 拆包
+ priority: 20, // 数字大权重到,满足多个 cacheGroups 的条件时候分到权重高的
+ test: /[\\/]node_modules[\\/]_?vant(.*)/
+ }
+ }
+ })
+ config.optimization.runtimeChunk('single')
+ })
+ }
+})