From 59c644956bc775bcfeee3efbdca35c0836a806c2 Mon Sep 17 00:00:00 2001 From: mk <2403457699@qq.com> Date: Wed, 15 May 2024 20:11:09 +0800 Subject: [PATCH] init --- .browserslistrc | 2 + .editorconfig | 14 + .env.development | 6 + .env.production | 5 + .env.staging | 4 + .eslintignore | 4 + .eslintrc.js | 54 ++ .gitignore | 24 + .postcssrc.js | 13 + .prettierignore | 1 + .prettierrc | 24 + README.md | 1212 ++++++++++++++++++++++++++++++++ babel.config.js | 22 + jsconfig.json | 17 + package.json | 60 ++ public/favicon.ico | Bin 0 -> 4286 bytes public/index.html | 27 + src/App.vue | 14 + src/api/home.js | 6 + src/api/index.js | 7 + src/api/user.js | 32 + src/assets/css/common.less | 70 ++ src/assets/css/index.less | 35 + src/assets/css/mixin.scss | 36 + src/assets/css/vant-theme.less | 688 ++++++++++++++++++ src/assets/css/variables.scss | 3 + src/assets/logo.png | Bin 0 -> 6849 bytes src/components/TabBar.vue | 54 ++ src/config/env.development.js | 9 + src/config/env.production.js | 8 + src/config/env.staging.js | 8 + src/config/index.js | 3 + src/const/index.js | 0 src/filters/filter.js | 37 + src/filters/index.js | 7 + src/main.js | 39 + src/plugins/vant.js | 53 ++ src/router/index.js | 30 + src/router/router.config.js | 11 + src/store/getters.js | 4 + src/store/index.js | 15 + src/store/modules/app.js | 19 + src/utils/index.js | 110 +++ src/utils/jsApiList.js | 7 + src/utils/request.js | 58 ++ src/utils/storage.js | 35 + src/utils/validate.js | 20 + src/utils/vconsole.js | 3 + src/utils/wechatPlugin.js | 12 + src/views/login/index.vue | 46 ++ static/image/demo.png | Bin 0 -> 26026 bytes static/image/secret.png | Bin 0 -> 35693 bytes vue.config.js | 171 +++++ 53 files changed, 3139 insertions(+) create mode 100644 .browserslistrc create mode 100644 .editorconfig create mode 100644 .env.development create mode 100644 .env.production create mode 100644 .env.staging create mode 100644 .eslintignore create mode 100644 .eslintrc.js create mode 100644 .gitignore create mode 100644 .postcssrc.js create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 README.md create mode 100644 babel.config.js create mode 100644 jsconfig.json create mode 100644 package.json create mode 100644 public/favicon.ico create mode 100644 public/index.html create mode 100644 src/App.vue create mode 100644 src/api/home.js create mode 100644 src/api/index.js create mode 100644 src/api/user.js create mode 100644 src/assets/css/common.less create mode 100644 src/assets/css/index.less create mode 100644 src/assets/css/mixin.scss create mode 100644 src/assets/css/vant-theme.less create mode 100644 src/assets/css/variables.scss create mode 100644 src/assets/logo.png create mode 100644 src/components/TabBar.vue create mode 100644 src/config/env.development.js create mode 100644 src/config/env.production.js create mode 100644 src/config/env.staging.js create mode 100644 src/config/index.js create mode 100644 src/const/index.js create mode 100644 src/filters/filter.js create mode 100644 src/filters/index.js create mode 100644 src/main.js create mode 100644 src/plugins/vant.js create mode 100644 src/router/index.js create mode 100644 src/router/router.config.js create mode 100644 src/store/getters.js create mode 100644 src/store/index.js create mode 100644 src/store/modules/app.js create mode 100644 src/utils/index.js create mode 100644 src/utils/jsApiList.js create mode 100644 src/utils/request.js create mode 100644 src/utils/storage.js create mode 100644 src/utils/validate.js create mode 100644 src/utils/vconsole.js create mode 100644 src/utils/wechatPlugin.js create mode 100644 src/views/login/index.vue create mode 100644 static/image/demo.png create mode 100644 static/image/secret.png create mode 100644 vue.config.js 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 0000000000000000000000000000000000000000..29f86064c1c3ed58c1f7bd148d6b3f8e2b866f68 GIT binary patch literal 4286 zcmd6nYitx%7=}+TbVEybXLe_|EhSb6w`w4Y5o{u~{PBmuq*uzNAP`G$AS9Xyaw$fm zl5h(aA(To%QJ~y1F2nZq(0)nwI&;Zf4^s-&r&TMyQKHsz5ZD;6iX$3E{dG)|hU(JO01l>REw-q3T(Spq`V$ z)pNFgH7xgmTyPQoogf{I1knK3q<<_=V1Cs10Qr$o>%d8{31oL2wGWr~*&~&McE)*J z2j=$OAL?ch@EhwHsnh+X{W`o=U^RFV2p}9hQWTwkT`0e|Mk3{qo#5hVpb1<66`)A@ zLQ0l*2`0HhWb!BCp!4`Mfk*JmVx+AO3ff=9z)TW!88*&|Y+Sa61H{ULQcBi|_R*mPc7Pt-1;Q>3zl_CA^ z)-qwPQW^SLgp0f5z*f)!s0YhELXMwaiD?z-5*q?<5|{_Jff{fJ|2svl#NI!!|9l@5 z^xfYntfo_TRyifQ&laff&3GMz?)n`>bufmF|Be zsvSE;jqiIP3-`Nr!2fd`+chc5;OF!CS&Hfg!Q4C(zit@IPn`J<=E`>raTYobHy7IJ zUXi3VzHU>S*IGTUjiTEAv8W-BT))9QhmK0Dd}6p^jtrrmrCa_mWA4)-+&s@+Faa3G z@{_h=2PYBS0K{#j)TAEh(m7Y*$n>Y*pDnvA~sX#SUXefpd8^oWeZM1 zCScfyE!kod%dj*U!ow4||IG%ce;xdNZKzpJ{FdHGFpPhJUBh__{GAoKqD;MXQgaOH>&4Jstk` zU>WAv6|c@6$k0=ljC0YuldouH)2eTr&RSfeERCLs^)R;;+kN}fNBBTTmf6WNVw+;& z{yK@CsJ|6WXJ|La-g2 zS(M~y#QYl2ue-io_^5t;7G0jO`SG4%1!FuZn12=jd)P#9vNxxA*y7Qiq01+EhObQXnpUQIA2#eiyG}T?R1g7J zDKG)XNd-xGds%>qgS#*(z!|a5 + + + + + + + + <%= 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 0000000000000000000000000000000000000000..f3d2503fc2a44b5053b0837ebea6e87a2d339a43 GIT binary patch literal 6849 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 +
+ + + {{ 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 0000000000000000000000000000000000000000..0cf476f96857a268df4e5becb2e714476bf9a3b3 GIT binary patch literal 26026 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 literal 0 HcmV?d00001 diff --git a/static/image/secret.png b/static/image/secret.png new file mode 100644 index 0000000000000000000000000000000000000000..c57b0744a77489b62cc1db6a446fd2f76b848bf9 GIT binary patch literal 35693 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 RDG2ZKL9rHFMj|4 literal 0 HcmV?d00001 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') + }) + } +})