├── src ├── module1 │ └── views │ │ ├── $app │ │ ├── index.scss │ │ ├── index.js │ │ └── index.vue │ │ └── demo │ │ ├── index.scss │ │ ├── index.js │ │ └── index.vue ├── common │ ├── views │ │ ├── demo │ │ │ ├── index.js │ │ │ ├── index.vue │ │ │ └── index.scss │ │ └── $app │ │ │ ├── index.js │ │ │ ├── index.scss │ │ │ └── index.vue │ ├── utils │ │ ├── $dicts │ │ │ └── index.js │ │ ├── $api-conf │ │ │ ├── index.js │ │ │ └── modules │ │ │ │ └── common.js │ │ ├── $store │ │ │ ├── modules │ │ │ │ ├── $group │ │ │ │ │ └── index.js │ │ │ │ └── $app │ │ │ │ │ └── index.js │ │ │ └── index.js │ │ ├── $cache │ │ │ └── index.js │ │ ├── $router │ │ │ ├── modules │ │ │ │ └── module1.js │ │ │ └── index.js │ │ ├── $handler │ │ │ ├── assets │ │ │ │ └── math-conf.js │ │ │ └── index.js │ │ └── $http │ │ │ └── index.js │ └── widgets │ │ ├── $iview │ │ ├── theme.less │ │ └── index.js │ │ ├── base-chart │ │ ├── index.vue │ │ └── index.js │ │ ├── $vant │ │ └── index.js │ │ ├── $icons │ │ └── index.js │ │ └── base-loading │ │ ├── index.vue │ │ ├── index.scss │ │ └── index.js ├── index.html ├── index~pc.js └── index~mobile.js ├── .eslintignore ├── .gitignore ├── configs ├── base.config.js ├── dev.config.js └── prod.config.js ├── jsconfig.json ├── .editorconfig ├── scripts ├── jenkins │ ├── deploy.sh │ └── build.sh └── webpack │ ├── base.config.js │ ├── dev.config.js │ └── prod.config.js ├── .vscode ├── settings.json └── pluginList.md ├── babel.config.js ├── .stylelintrc ├── postcss.config.js ├── .jsbeautifyrc ├── package.json ├── docs └── README.md ├── .eslintrc └── .csscomb.json /src/module1/views/$app/index.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/module1/views/demo/index.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | /*.* 3 | /src/common/utils/$handler/math-conf.js -------------------------------------------------------------------------------- /src/common/views/demo/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'demo' 3 | }; 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | npm-debug.log 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /src/module1/views/$app/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'module1-app' 3 | }; 4 | 5 | -------------------------------------------------------------------------------- /src/common/utils/$dicts/index.js: -------------------------------------------------------------------------------- 1 | const list = []; 2 | 3 | 4 | export { 5 | list 6 | }; 7 | -------------------------------------------------------------------------------- /src/module1/views/demo/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'module1__demo' 3 | }; 4 | 5 | -------------------------------------------------------------------------------- /src/common/widgets/$iview/theme.less: -------------------------------------------------------------------------------- 1 | @import "~iview/src/styles/index.less"; 2 | 3 | @primary-color: #ff238d; 4 | -------------------------------------------------------------------------------- /src/common/utils/$api-conf/index.js: -------------------------------------------------------------------------------- 1 | import common from './modules/common'; 2 | 3 | 4 | export default Object.assign({}, 5 | common 6 | ); 7 | -------------------------------------------------------------------------------- /configs/base.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /** 3 | * 设备类型 4 | * 5 | * pc || mobile 6 | */ 7 | deviceType: 'mobile' 8 | }; 9 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@src/*": ["./src/*"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/common/widgets/base-chart/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/common/widgets/$vant/index.js: -------------------------------------------------------------------------------- 1 | // https://youzan.github.io/vant/#/zh-CN/intro 2 | import Vue from 'vue'; 3 | import { Button, Icon } from 'vant'; 4 | 5 | 6 | Vue.use(Button).use(Icon); 7 | 8 | -------------------------------------------------------------------------------- /src/common/views/demo/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/module1/views/demo/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/common/utils/$store/modules/$group/index.js: -------------------------------------------------------------------------------- 1 | const state = {}; 2 | const mutations = {}; 3 | const actions = {}; 4 | const getters = {}; 5 | 6 | 7 | 8 | export default { 9 | state, 10 | mutations, 11 | actions, 12 | getters 13 | }; 14 | -------------------------------------------------------------------------------- /configs/dev.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 全局环境变量 3 | env: { 4 | __API__: { 5 | BASE_URL: '" "' 6 | } 7 | }, 8 | 9 | // 构建变量 10 | build: { 11 | // 部署阶段文件加载路径 12 | publicPath: '/' 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /configs/prod.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 全局环境变量 3 | env: { 4 | __API__: { 5 | BASE_URL: '" "' 6 | } 7 | }, 8 | 9 | // 构建变量 10 | build: { 11 | // 部署阶段文件加载路径 12 | publicPath: '/' 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_size = 4 9 | indent_style = space 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /scripts/jenkins/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin 2 | source ~/.bashrc 3 | 4 | 5 | # 删除所有文件(to-deploy、deploy.sh 除外) 6 | rm -rf $(ls | grep -v to-deploy.tar |grep -v deploy.sh |grep -v h5 |grep -v admin) 7 | 8 | 9 | # 将 to-deploy下所有文件移至当前目录 10 | tar -xvf to-deploy.tar 11 | 12 | 13 | # 删除 to-deploy、deploy.sh 14 | rm to-deploy.tar deploy.sh 15 | -------------------------------------------------------------------------------- /src/common/views/demo/index.scss: -------------------------------------------------------------------------------- 1 | .demo { 2 | font-size: 12px; 3 | font-size: 24px; 4 | 5 | display: flex; 6 | 7 | width: 670px; 8 | height: 670px; 9 | margin: 40px; 10 | margin-top: 0; 11 | padding: 40px; 12 | 13 | color: #000; 14 | border: 12px solid red; 15 | border-radius: 100px; 16 | } 17 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | SPA-Vue 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/common/widgets/$icons/index.js: -------------------------------------------------------------------------------- 1 | // https://fontawesome.com/ 2 | import Vue from 'vue'; 3 | import { library } from '@fortawesome/fontawesome-svg-core'; 4 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; 5 | 6 | 7 | 8 | // 注册全局 图标组件 9 | Vue.component('font-icon', FontAwesomeIcon); 10 | 11 | 12 | 13 | // 图标组件 配置(按需引入) 14 | library.add(); 15 | -------------------------------------------------------------------------------- /src/common/widgets/$iview/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 要求: 所有 iview 组件都重命名 3 | * 4 | * 命名规则: https://www.iviewui.com/docs/guide/iview-loader 5 | */ 6 | import Vue from 'vue'; 7 | import 'iview/dist/styles/iview.css'; 8 | import { Input, Message } from 'iview'; 9 | import './theme.less'; 10 | 11 | 12 | // 按需注册 iview 组件 13 | Vue.component('i-input', Input); 14 | Vue.prototype.$Message = Message; 15 | -------------------------------------------------------------------------------- /src/common/utils/$cache/index.js: -------------------------------------------------------------------------------- 1 | import localforage from 'localforage'; 2 | 3 | 4 | 5 | export default localforage.createInstance({ 6 | driver: [ 7 | 8 | // remove support for WEBSQL, the specification work has stopped 9 | // https://dev.w3.org/html5/webdatabase/ 10 | localforage.INDEXEDDB, 11 | localforage.LOCALSTORAGE 12 | ], 13 | name: 'WeireFE' 14 | }); 15 | -------------------------------------------------------------------------------- /src/common/utils/$router/modules/module1.js: -------------------------------------------------------------------------------- 1 | export default [{ 2 | path: 'demo', // 概览页 3 | name: 'module1__demo', 4 | component(resolve) { 5 | require.ensure([], () => { 6 | resolve(require('@src/module1/views/demo/index.vue')); 7 | }, 'views/module1/demo/index'); 8 | }, 9 | meta: { 10 | keepAlive: true, 11 | rank: 20 12 | } 13 | }]; 14 | 15 | -------------------------------------------------------------------------------- /src/common/views/$app/index.js: -------------------------------------------------------------------------------- 1 | import { mapState } from 'vuex'; 2 | import BaseLoading from '@src/common/widgets/base-loading'; 3 | 4 | 5 | 6 | export default { 7 | name: 'app', 8 | 9 | components: { 10 | BaseLoading 11 | }, 12 | 13 | computed: { 14 | ...mapState({ 15 | $loadingState: state => state.$app.$loadingState 16 | }) 17 | } 18 | }; 19 | 20 | -------------------------------------------------------------------------------- /src/module1/views/$app/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /scripts/jenkins/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin 2 | source ~/.bashrc 3 | deployFolder=$(pwd | awk '{print $1}')/'to-deploy' 4 | 5 | 6 | # 处理 jenkins 服务器上 to-deploy 7 | if [ -d $deployFolder ] 8 | then 9 | rm -rf $deployFolder/* 10 | else 11 | mkdir $deployFolder 12 | fi 13 | 14 | # 安装依赖 15 | npm install 16 | 17 | # 打包 18 | npm run build:$1 19 | 20 | # 移动打包后的文件 21 | mv dist/* $deployFolder/ 22 | 23 | 24 | # 压缩 deployFolder 25 | cd $deployFolder 26 | tar -cvf ../to-deploy.tar * 27 | rm -rf * 28 | 29 | -------------------------------------------------------------------------------- /src/common/views/$app/index.scss: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | box-sizing: border-box; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | html, 10 | body { 11 | height: 100%; 12 | min-height: 100%; 13 | 14 | background-color: #fff; 15 | } 16 | 17 | #app { 18 | position: relative; 19 | 20 | width: 100%; 21 | min-height: 100%; 22 | 23 | &__body { 24 | margin: 0 auto; 25 | } 26 | 27 | &__loading { 28 | position: fixed; 29 | z-index: 2222; 30 | top: 0; 31 | right: 0; 32 | bottom: 0; 33 | left: 0; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/index~pc.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import '@src/common/widgets/$iview'; 3 | import '@src/common/widgets/$icons'; 4 | import $http from '@src/common/utils/$http'; 5 | import $store from '@src/common/utils/$store'; 6 | import $router from '@src/common/utils/$router'; 7 | import $app from '@src/common/views/$app'; 8 | 9 | 10 | 11 | // 创建 vue根实例 12 | const vm = new Vue({ 13 | el: '#mount', 14 | router: $router, 15 | store: $store, 16 | render: createElement => createElement($app) 17 | }); 18 | 19 | 20 | 21 | Vue.prototype.$vm = vm; 22 | Vue.prototype.$http = $http; 23 | 24 | 25 | export default vm; 26 | 27 | -------------------------------------------------------------------------------- /src/common/views/$app/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/common/utils/$store/index.js: -------------------------------------------------------------------------------- 1 | // https://github.com/vuejs/vuex/issues/451 2 | import 'core-js/fn/promise'; 3 | import Vue from 'vue'; 4 | import Vuex from 'vuex'; 5 | import createPersistedState from 'vuex-persistedstate'; 6 | import $group from './modules/$group'; 7 | import $app from './modules/$app'; 8 | 9 | 10 | 11 | Vue.use(Vuex); 12 | 13 | 14 | 15 | export default new Vuex.Store({ 16 | strict: process.env.NODE_ENV !== 'production', 17 | plugins: [ 18 | createPersistedState({ 19 | key: '$group', 20 | paths: ['$group'] 21 | }) 22 | ], 23 | modules: { 24 | $group, 25 | $app 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.autoFixOnSave": true, 3 | "eslint.validate": [ 4 | { 5 | "language": "html", 6 | "autoFix": true 7 | }, 8 | { 9 | "language": "javascript", 10 | "autoFix": true 11 | }, 12 | { 13 | "language": "vue", 14 | "autoFix": true 15 | } 16 | ], 17 | 18 | "eslint.options": { 19 | "extensions": [ 20 | ".js", 21 | ".vue" 22 | ] 23 | }, 24 | 25 | // vetur设置 26 | "vetur.validation.template": false, 27 | 28 | // csscomb 设置 29 | "csscomb.formatOnSave": true, 30 | "csscomb.preset": ".csscomb.json", 31 | "emmet.includeLanguages": { 32 | "vue-html": "html", 33 | "vue": "html", 34 | "wxml": "html" 35 | } 36 | } -------------------------------------------------------------------------------- /src/common/widgets/base-loading/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /.vscode/pluginList.md: -------------------------------------------------------------------------------- 1 | - vscode 插件列表如下: 2 | 3 | - [Beautify](https://marketplace.visualstudio.com/items?itemName=HookyQR.beautify) 4 | 5 | - [Chinese (Simplified) Language Pack for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=MS-CEINTL.vscode-language-pack-zh-hans) 6 | 7 | - [Color Highlight](https://marketplace.visualstudio.com/items?itemName=naumovs.color-highlight) 8 | 9 | - [CSScomb](https://marketplace.visualstudio.com/items?itemName=mrmlnc.vscode-csscomb) 10 | 11 | - [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) 12 | 13 | - [Git Blame](https://marketplace.visualstudio.com/items?itemName=waderyan.gitblame) 14 | 15 | - [Path Autocomplete](https://marketplace.visualstudio.com/items?itemName=ionutvmi.path-autocomplete) 16 | 17 | - [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) 18 | 19 | - [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur) -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const deviceType = require('./configs/base.config').deviceType; 2 | 3 | 4 | 5 | const config = { 6 | presets: [ 7 | [ 8 | '@babel/preset-env', 9 | { 10 | modules: false 11 | } 12 | ] 13 | ], 14 | plugins: [ 15 | '@babel/plugin-transform-runtime', 16 | '@babel/plugin-syntax-dynamic-import' 17 | ], 18 | comments: false 19 | }; 20 | 21 | 22 | 23 | // 如果是移动端 24 | if (deviceType === 'mobile') { 25 | // 动态加载 vant 样式 26 | config.plugins.push([ 27 | 'import', 28 | { 29 | libraryName: 'vant', 30 | libraryDirectory: 'es', 31 | style: true 32 | }, 33 | 'vant' 34 | ]); 35 | } else { 36 | // 动态加载 iview 样式 37 | config.plugins.push([ 38 | 'import', 39 | { 40 | libraryName: 'iview', 41 | libraryDirectory: 'src/components' 42 | } 43 | ]); 44 | } 45 | 46 | 47 | module.exports = config; 48 | -------------------------------------------------------------------------------- /src/common/utils/$store/modules/$app/index.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | // loading 显示状态 3 | $loadingState: false 4 | }; 5 | 6 | const $APP_SET_LOADING_STATE = '$APP_SET_LOADING_STATE'; 7 | 8 | const mutations = { 9 | /** 10 | * 设置loading显示 11 | * @param {Object} state state 12 | * @param {FSA} mutation mutation 13 | */ 14 | [$APP_SET_LOADING_STATE](state, mutation) { 15 | state.$loadingState = mutation.payload; 16 | } 17 | }; 18 | 19 | const actions = { 20 | // 设置loading状态 21 | $appSetLoadingState({ 22 | commit 23 | }, state) { 24 | if (typeof state === 'boolean') { 25 | commit({ 26 | type: $APP_SET_LOADING_STATE, 27 | payload: state 28 | }); 29 | } else { 30 | throw new Error('[$appSetLoadingState] invalid state'); 31 | } 32 | } 33 | }; 34 | 35 | const getters = {}; 36 | 37 | export default { 38 | state, 39 | mutations, 40 | actions, 41 | getters 42 | }; 43 | -------------------------------------------------------------------------------- /src/common/widgets/base-loading/index.scss: -------------------------------------------------------------------------------- 1 | .base-loading { 2 | width: 100%; 3 | height: 100%; 4 | 5 | background: hsla(0,0%,100%,0.9); 6 | 7 | &__content { 8 | position: absolute; 9 | z-index: 3333; 10 | top: 45%; 11 | left: 50%; 12 | 13 | display: flex; 14 | flex-direction: column; 15 | 16 | transform: translateX(-50%); 17 | 18 | color: #ff238d; 19 | 20 | align-items: center; 21 | justify-content: center; 22 | 23 | .ignore-to-vw { 24 | font-size: 14px; 25 | } 26 | } 27 | 28 | .spin-icon { 29 | width: 1em; 30 | height: 1em; 31 | margin-bottom: 8px; 32 | 33 | animation: ani-spin 1s linear infinite; 34 | } 35 | 36 | @keyframes ani-spin { 37 | from { 38 | transform: rotate(0deg); 39 | } 40 | 50% { 41 | transform: rotate(180deg); 42 | } 43 | to { 44 | transform: rotate(360deg); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/common/widgets/base-loading/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'base-loading', 3 | 4 | model: { 5 | prop: 'isShow', 6 | event: 'on-change' 7 | }, 8 | 9 | props: { 10 | isShow: { 11 | type: Boolean, 12 | default: false, 13 | required: false 14 | } 15 | }, 16 | 17 | data() { 18 | return { 19 | svgPath: 20 | 'M440.65 12.57l4 82.77A247.16 247.16 0 0 0 255.83 8C134.73 8 33.91 94.92 12.29 209.82A12 12 0 0 0 24.09 224h49.05a12 12 0 0 0 11.67-9.26 175.91 175.91 0 0 1 317-56.94l-101.46-4.86a12 12 0 0 0-12.57 12v47.41a12 12 0 0 0 12 12H500a12 12 0 0 0 12-12V12a12 12 0 0 0-12-12h-47.37a12 12 0 0 0-11.98 12.57zM255.83 432a175.61 175.61 0 0 1-146-77.8l101.8 4.87a12 12 0 0 0 12.57-12v-47.4a12 12 0 0 0-12-12H12a12 12 0 0 0-12 12V500a12 12 0 0 0 12 12h47.35a12 12 0 0 0 12-12.6l-4.15-82.57A247.17 247.17 0 0 0 255.83 504c121.11 0 221.93-86.92 243.55-201.82a12 12 0 0 0-11.8-14.18h-49.05a12 12 0 0 0-11.67 9.26A175.86 175.86 0 0 1 255.83 432z' 21 | }; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/common/utils/$api-conf/modules/common.js: -------------------------------------------------------------------------------- 1 | /* global __API__ */ 2 | export default { 3 | /** 4 | * 用户登陆 5 | * @type {Object} 6 | */ 7 | USER_LOGIN: { 8 | name: 'USER_LOGIN', 9 | proxy: { 10 | url: `${__API__.BASE_URL}/user/login`, 11 | method: 'POST', 12 | headers: { 13 | 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' 14 | }, 15 | transformRequest: [function (data) { 16 | const params = new URLSearchParams(); 17 | 18 | for (const key in data) { 19 | params.append(key, data[key]); 20 | } 21 | 22 | return params; 23 | }], 24 | withCredentials: true 25 | } 26 | }, 27 | 28 | /** 29 | * 用户注销 30 | * @type {Object} 31 | */ 32 | USER_LOGOUT: { 33 | name: 'USER_LOGOUT', 34 | proxy: { 35 | url: `${__API__.BASE_URL}/user/logout`, 36 | method: 'POST', 37 | withCredentials: true 38 | } 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /src/index~mobile.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import '@src/common/widgets/$vant'; 3 | import $http from '@src/common/utils/$http'; 4 | import $store from '@src/common/utils/$store'; 5 | import $router from '@src/common/utils/$router'; 6 | import $app from '@src/common/views/$app'; 7 | import viewportBuggyfill from 'viewport-units-buggyfill'; 8 | import viewportBuggyfillHack from 'viewport-units-buggyfill/viewport-units-buggyfill.hacks'; 9 | 10 | 11 | 12 | /** 13 | * mobile 生产环境下 兼容 vm 14 | * 15 | * viewport units (vh|vw|vmin|vmax) in Mobile Safari 16 | * viewport units inside calc() expressions in Mobile Safari and IE9+ (hack) 17 | * vmin, vmax in IE9+ (hack) 18 | * viewport units in old Android Stock Browser (hack) 19 | */ 20 | if (process.env.NODE_ENV === 'production') { 21 | viewportBuggyfill.init({ 22 | hacks: viewportBuggyfillHack 23 | }); 24 | } 25 | 26 | 27 | // 创建 vue根实例 28 | const vm = new Vue({ 29 | el: '#mount', 30 | router: $router, 31 | store: $store, 32 | render: createElement => createElement($app) 33 | }); 34 | 35 | 36 | Vue.prototype.$vm = vm; 37 | Vue.prototype.$http = $http; 38 | 39 | 40 | export default vm; 41 | 42 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "rules": { 4 | 5 | "value-no-vendor-prefix": [true, { "severity": "warning" }], 6 | 7 | "value-keyword-case": "lower", 8 | 9 | "property-no-vendor-prefix": [true, { "severity": "warning" }], 10 | 11 | "declaration-empty-line-before": ["always", { 12 | "except": ["after-comment", "after-declaration", "first-nested"], 13 | "ignore": ["after-comment", "after-declaration", "inside-single-line-block"] 14 | }], 15 | 16 | "declaration-block-no-duplicate-properties": [true, { "severity": "warning" }], 17 | 18 | "selector-no-vendor-prefix": true, 19 | 20 | "media-feature-name-no-vendor-prefix": true, 21 | 22 | "at-rule-empty-line-before": ["always", { 23 | ignore: ["after-comment"] 24 | }], 25 | 26 | "at-rule-no-vendor-prefix": [true, { "severity": "warning" }], 27 | 28 | "comment-empty-line-before": null, 29 | 30 | "indentation": 4, 31 | 32 | "max-empty-lines": 3, 33 | 34 | "max-line-length": 100, 35 | 36 | "no-duplicate-selectors": true, 37 | 38 | "no-unknown-animations": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const autoprefixer = require('autoprefixer'); 2 | const postcssPxToViewport = require('postcss-px-to-viewport'); 3 | const deviceType = require('./configs/base.config').deviceType; 4 | 5 | 6 | const config = { 7 | plugins: [ 8 | autoprefixer({ 9 | browsers: [ 10 | '> 1%', 11 | 'not ie <= 11', 12 | 'last 2 versions', 13 | 'last 3 iOS versions', 14 | 'Android >= 4.0' 15 | ] 16 | }) 17 | ] 18 | }; 19 | 20 | 21 | // 如果是移动端 22 | if (deviceType === 'mobile') { 23 | config.plugins.push( 24 | // https://github.com/evrone/postcss-px-to-viewport/blob/master/README_CN.md 25 | postcssPxToViewport({ 26 | dpr: 2, 27 | unitToConvert: 'px', // 需要转换的单位 28 | viewportWidth: 750, // 设计稿的视口宽度 29 | unitPrecision: 5, // 单位转换后保留的精度 30 | viewportUnit: 'vw', // 希望使用的视口单位 31 | propList: ['*'], // 要转化为vw的属性列表 32 | selectorBlackList: ['.ignore-to-vw'], // 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位 33 | minPixelValue: 1, // 设置最小的转换数值,如果为1的话,只有大于1的值会被转换 34 | mediaQuery: false, // 是否需要转换 媒体查询里的单位 35 | exclude: /(\/|\\)(node_modules)(\/|\\)/ 36 | }) 37 | ); 38 | } 39 | 40 | 41 | module.exports = config; 42 | -------------------------------------------------------------------------------- /.jsbeautifyrc: -------------------------------------------------------------------------------- 1 | // https://cloud.tencent.com/developer/article/1359988 2 | 3 | { 4 | "html": { 5 | "brace_style": "collapse", 6 | "end_with_newline": true, 7 | "extra_liners": [ 8 | "head", 9 | "body", 10 | "/html" 11 | ], 12 | "indent_char": " ", 13 | "indent_inner_html": true, 14 | "indent_scripts": "normal", 15 | "indent_size": 4, 16 | "max_preserve_newlines": 4, 17 | "preserve_newlines": true, 18 | "wrap_attributes": "auto", 19 | "wrap_attributes_indent_size": 4, 20 | "wrap_line_length": 80 21 | }, 22 | "css": { 23 | "configPath": "" 24 | }, 25 | "js": { 26 | "brace_style": "collapse", 27 | "break_chained_methods": false, 28 | "end_with_comma": false, 29 | "end_with_newline": true, 30 | "eval_code": false, 31 | "indent_char": " ", 32 | "indent_level": 0, 33 | "indent_size": 4, 34 | "indent_with_tabs": false, 35 | "jslint_happy": true, 36 | "keep_array_indentation": false, 37 | "keep_function_indentation": false, 38 | "max_preserve_newlines": 4, 39 | "preserve_newlines": true, 40 | "space_after_anon_function": true, 41 | "space_before_conditional": true, 42 | "space_in_paren": false, 43 | "unescape_strings": false, 44 | "wrap_line_length": 100 45 | }, 46 | "json": { 47 | "brace_style": "collapse", 48 | "break_chained_methods": false, 49 | "end_with_comma": false, 50 | "end_with_newline": true, 51 | "eval_code": false, 52 | "indent_char": " ", 53 | "indent_level": 0, 54 | "indent_size": 4, 55 | "indent_with_tabs": false, 56 | "jslint_happy": true, 57 | "keep_array_indentation": false, 58 | "keep_function_indentation": false, 59 | "max_preserve_newlines": 4, 60 | "preserve_newlines": true, 61 | "space_after_anon_function": true, 62 | "space_before_conditional": true, 63 | "space_in_paren": false, 64 | "unescape_strings": false, 65 | "wrap_line_length": 100 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/common/utils/$handler/assets/math-conf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 按需引入 mathjs 3 | * https://mathjs.org/docs/custom_bundling.html 4 | * https://mathjs.org/docs/datatypes/numbers.html 5 | */ 6 | const core = require('mathjs/core'); 7 | 8 | const math = core.create(); 9 | 10 | 11 | math.import(require('mathjs/lib/expression/function/eval')); 12 | math.import(require('mathjs/lib/function/string/format')); 13 | 14 | // create simple functions for all operators 15 | math.import({ 16 | // arithmetic 17 | add(a, b) { 18 | return a + b; 19 | }, 20 | subtract(a, b) { 21 | return a - b; 22 | }, 23 | multiply(a, b) { 24 | return a * b; 25 | }, 26 | divide(a, b) { 27 | return a / b; 28 | }, 29 | mod(a, b) { 30 | return a % b; 31 | }, 32 | unaryPlus(a) { 33 | return a; 34 | }, 35 | unaryMinus(a) { 36 | return -a; 37 | }, 38 | 39 | // bitwise 40 | // bitOr(a, b) { 41 | // return a | b; 42 | // }, 43 | // bitXor(a, b) { 44 | // return a ^ b; 45 | // }, 46 | // bitAnd(a, b) { 47 | // return a & b; 48 | // }, 49 | // bitNot(a) { 50 | // return ~a; 51 | // }, 52 | // leftShift(a, b) { 53 | // return a << b; 54 | // }, 55 | // rightArithShift(a, b) { 56 | // return a >> b; 57 | // }, 58 | // rightLogShift(a, b) { 59 | // return a >>> b; 60 | // }, 61 | 62 | // logical 63 | or(a, b) { 64 | return !!(a || b); 65 | }, 66 | xor(a, b) { 67 | return !!a !== !!b; 68 | }, 69 | and(a, b) { 70 | return !!(a && b); 71 | }, 72 | not(a) { 73 | return !a; 74 | }, 75 | 76 | // relational 77 | equal(a, b) { 78 | return a === b; 79 | }, 80 | unequal(a, b) { 81 | return a !== b; 82 | }, 83 | smaller(a, b) { 84 | return a < b; 85 | }, 86 | larger(a, b) { 87 | return a > b; 88 | }, 89 | smallerEq(a, b) { 90 | return a <= b; 91 | }, 92 | largerEq(a, b) { 93 | return a >= b; 94 | }, 95 | 96 | // matrix 97 | // matrix: function (a) { return a }, 98 | matrix() { 99 | throw new Error('Matrices not supported'); 100 | }, 101 | index() { 102 | throw new Error('Matrix indexes not supported'); 103 | }, 104 | 105 | // add pi and e as lowercase 106 | pi: Math.PI, 107 | e: Math.E, 108 | true: true, 109 | false: false, 110 | null: null 111 | }); 112 | 113 | export default math; 114 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-4-spa", 3 | "version": "0.1.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "serve:dev": "webpack-dev-server --config scripts/webpack/dev.config.js", 7 | "serve:prod": "webpack-dev-server --config scripts/webpack/prod.config.js", 8 | "build:dev": "webpack --config scripts/webpack/dev.config.js --build", 9 | "build:prod": "webpack --config scripts/webpack/prod.config.js --build", 10 | "build:show": "webpack --config scripts/webpack/prod.config.js --build --show", 11 | "eslint:fix": "eslint --fix src" 12 | }, 13 | "devDependencies": { 14 | "@babel/core": "^7.2.2", 15 | "@babel/plugin-syntax-dynamic-import": "^7.2.0", 16 | "@babel/plugin-transform-runtime": "^7.2.0", 17 | "@babel/preset-env": "^7.2.3", 18 | "@babel/runtime": "^7.2.0", 19 | "autoprefixer": "^9.4.3", 20 | "babel-loader": "^8.0.4", 21 | "babel-plugin-import": "^1.11.0", 22 | "babel-plugin-transform-merge-sibling-variables": "^6.9.4", 23 | "babel-plugin-transform-remove-console": "^6.9.4", 24 | "babel-plugin-transform-remove-debugger": "^6.9.4", 25 | "babel-plugin-transform-remove-undefined": "^0.5.0", 26 | "clean-webpack-plugin": "^1.0.0", 27 | "css-loader": "^2.1.0", 28 | "eslint": "^3.15.0", 29 | "eslint-config-airbnb": "^14.1.0", 30 | "eslint-plugin-html": "^2.0.0", 31 | "eslint-plugin-import": "^2.2.0", 32 | "eslint-plugin-jsx-a11y": "^4.0.0", 33 | "eslint-plugin-react": "^6.9.0", 34 | "eslint-plugin-vue": "^4.7.1", 35 | "file-loader": "^3.0.1", 36 | "html-webpack-plugin": "^3.2.0", 37 | "html-withimg-loader": "^0.1.16", 38 | "install": "^0.12.2", 39 | "less": "^2.7.2", 40 | "less-loader": "^4.1.0", 41 | "mini-css-extract-plugin": "^0.5.0", 42 | "minimist": "^1.2.0", 43 | "node-sass": "^4.11.0", 44 | "npm": "^6.9.0", 45 | "optimize-css-assets-webpack-plugin": "^5.0.1", 46 | "postcss-loader": "^3.0.0", 47 | "postcss-px-to-viewport": "^1.1.0", 48 | "progress-webpack-plugin": "0.0.24", 49 | "sass-loader": "^7.1.0", 50 | "style-loader": "^0.23.1", 51 | "stylelint": "^9.10.1", 52 | "stylelint-config-standard": "^18.2.0", 53 | "url-loader": "^1.1.2", 54 | "viewport-units-buggyfill": "^0.6.2", 55 | "vue-loader": "^15.4.2", 56 | "vue-style-loader": "^4.1.2", 57 | "vue-template-compiler": "^2.5.21", 58 | "webpack": "^4.28.3", 59 | "webpack-bundle-analyzer": "^3.0.3", 60 | "webpack-cli": "^3.1.2", 61 | "webpack-dev-server": "^3.1.14", 62 | "webpack-merge": "^4.1.5" 63 | }, 64 | "dependencies": { 65 | "@fortawesome/fontawesome-svg-core": "^1.2.12", 66 | "@fortawesome/free-regular-svg-icons": "^5.6.3", 67 | "@fortawesome/free-solid-svg-icons": "^5.6.3", 68 | "@fortawesome/vue-fontawesome": "^0.1.5", 69 | "axios": "^0.18.0", 70 | "echarts": "^4.2.0-rc.2", 71 | "iview": "^3.2.2", 72 | "localforage": "^1.5.0", 73 | "lodash": "^4.17.11", 74 | "mathjs": "^5.7.0", 75 | "vant": "^1.6.16", 76 | "vue": "^2.5.21", 77 | "vue-router": "^3.0.2", 78 | "vuex": "^3.1.0", 79 | "vuex-persistedstate": "^1.4.1" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/common/widgets/base-chart/index.js: -------------------------------------------------------------------------------- 1 | import echarts from 'echarts/lib/echarts'; // 引入 ECharts 主模块 2 | import 'echarts/lib/component/tooltip'; // 引入提示框 3 | import 'echarts/lib/component/title'; // 引入标题组件 4 | import 'echarts/lib/component/legendScroll'; // 引入 legend 图例 5 | import 'echarts/lib/chart/pie'; // 引入饼图 6 | 7 | 8 | 9 | export default { 10 | name: 'base-chart', 11 | 12 | props: { 13 | /** 14 | * 图表 宽 15 | */ 16 | width: { 17 | type: [String, Number], 18 | default: '100%' 19 | }, 20 | 21 | /** 22 | * 图表 高 23 | */ 24 | height: { 25 | type: [String, Number], 26 | default: '100%' 27 | }, 28 | 29 | /** 30 | * 图表配置项 31 | * 参考:http://echarts.baidu.com/option.html#title 32 | */ 33 | options: { 34 | type: Object, 35 | default: () => {} 36 | }, 37 | 38 | /** 39 | * loading 是否显示 40 | */ 41 | loadingIsShow: { 42 | type: Boolean, 43 | default: false 44 | }, 45 | 46 | /** 47 | * loading 样式 48 | * 参考:https://echarts.baidu.com/api.html#echartsInstance.showLoading 49 | */ 50 | loadingStyle: { 51 | type: Object, 52 | default: () => ({ 53 | text: '', 54 | color: '#ff238d', 55 | maskColor: 'rgba(255, 255, 255, 0.8)', 56 | zlevel: 0 57 | }) 58 | } 59 | }, 60 | 61 | data() { 62 | return { 63 | // chart 实例 64 | chartInstance: null 65 | }; 66 | }, 67 | 68 | computed: { 69 | containerStyle() { 70 | return [{ 71 | width: typeof this.width === 'string' ? this.width : `${this.width}px` 72 | }, { 73 | height: typeof this.height === 'string' ? this.height : `${this.height}px` 74 | }]; 75 | } 76 | }, 77 | 78 | watch: { 79 | options: { 80 | deep: true, 81 | handler() { 82 | this.chartInstance.setOption(this.options, true); 83 | } 84 | }, 85 | 86 | loadingIsShow: { 87 | immediate: true, 88 | handler(val) { 89 | this.$nextTick(() => { 90 | if (val) { 91 | this.chartInstance.showLoading('default', this.loadingStyle); 92 | } else { 93 | this.chartInstance.hideLoading(); 94 | } 95 | }); 96 | } 97 | } 98 | }, 99 | 100 | mounted() { 101 | this.chartInstance = echarts.init(this.$el); 102 | this.chartInstance.setOption(this.options, true); 103 | 104 | window.addEventListener('resize', this.chartInstance.resize); 105 | }, 106 | 107 | destroyed() { 108 | window.removeEventListener('resize', this.chartInstance.resize); 109 | } 110 | }; 111 | -------------------------------------------------------------------------------- /src/common/utils/$router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | import module1Conf from './modules/module1'; 4 | 5 | 6 | // 加载 vue-router 7 | Vue.use(VueRouter); 8 | 9 | 10 | 11 | /** 12 | * 路由命名约束: 13 | * 父路由: name 带 模块名;path 带 模块名 14 | * 子路由: name 带 模块名;path 不带 模块名 15 | */ 16 | const router = new VueRouter({ 17 | routes: [{ 18 | path: '/', 19 | redirect: { 20 | name: 'demo' 21 | } 22 | }, { 23 | path: '/demo', 24 | name: 'demo', 25 | component(resolve) { 26 | require.ensure([], () => { 27 | resolve(require('@src/common/views/demo/index.vue')); 28 | }, 'views/common/demo/index'); 29 | }, 30 | meta: { 31 | keepAlive: false, // 判断 页面是否需要 keep-alive 缓存 32 | rank: 20 // 动态判断 已缓存的页面 是否需要销毁缓存 33 | } 34 | }, { 35 | path: '/module1', 36 | name: 'module1', 37 | redirect: { 38 | name: 'module1__demo' 39 | }, 40 | component(resolve) { 41 | require.ensure([], () => { 42 | resolve(require('@src/module1/views/$app/index.vue')); 43 | }, 'views/module1/$app/index'); 44 | }, 45 | meta: { 46 | keepAlive: false, // 判断 页面是否需要 keep-alive 缓存 47 | rank: 20 // 动态判断 已缓存的页面 是否需要销毁缓存 48 | }, 49 | children: module1Conf 50 | }] 51 | }); 52 | 53 | 54 | router.beforeEach((to, from, next) => { 55 | // 系统初始化逻辑 56 | setTimeout(async () => { 57 | next(); 58 | }, 0); 59 | }); 60 | 61 | router.afterEach(() => { 62 | // 切换页面后将屏幕滚动至顶端 63 | window.scrollTo(0, 0); 64 | }); 65 | 66 | /** 67 | * 通过配置路由 meta.rank,动态销毁 已缓存的页面 68 | * 69 | * from.meta.rank > to.meta.rank 时,且 from.meta.allowDestroy 为true,销毁 from页面 70 | */ 71 | Vue.mixin({ 72 | beforeRouteLeave(to, from, next) { 73 | if (from && from.meta.rank && to.meta.rank && from.meta.rank > to.meta.rank && 74 | (from.meta.allowDestroy === undefined || from.meta.allowDestroy)) { 75 | // 此处判断是如果返回上一层,你可以根据自己的业务更改此处的判断逻辑,酌情决定是否摧毁本层缓存。 76 | if (this.$vnode && this.$vnode.data.keepAlive) { 77 | if (this.$vnode.parent && this.$vnode.parent.componentInstance && 78 | this.$vnode.parent.componentInstance.cache && 79 | this.$vnode.componentOptions) { 80 | const key = this.$vnode.key == null ? 81 | this.$vnode.componentOptions.Ctor.cid + 82 | (this.$vnode.componentOptions.tag ? `::${this.$vnode.componentOptions.tag}` : '') : 83 | this.$vnode.key; 84 | const cache = this.$vnode.parent.componentInstance.cache; 85 | const keys = this.$vnode.parent.componentInstance.keys; 86 | 87 | 88 | if (cache[key]) { 89 | delete cache[key]; 90 | } 91 | 92 | if (cache[key] && keys.length && keys.indexOf(key) > -1) { 93 | keys.splice(keys.indexOf(key), 1); 94 | } 95 | } 96 | } 97 | 98 | this.$destroy(); 99 | } 100 | next(); 101 | } 102 | }); 103 | 104 | export default router; 105 | 106 | -------------------------------------------------------------------------------- /scripts/webpack/base.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const VueLoaderPlugin = require('vue-loader/lib/plugin'); 4 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 5 | 6 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 7 | const isBuildMode = require('minimist')(process.argv.slice(2)).build; 8 | const isShowMode = require('minimist')(process.argv.slice(2)).show; 9 | const ProgressBarPlugin = require('progress-webpack-plugin'); 10 | 11 | const rootPath = path.resolve(__dirname, '../../'); 12 | const contextPath = path.resolve(rootPath, './src/'); 13 | const nodeModulesPath = path.resolve(rootPath, './node_modules/'); 14 | const deviceType = require('../../configs/base.config').deviceType; 15 | 16 | 17 | 18 | // 部署打包时, 需要运行的插件 19 | const plugins = []; 20 | 21 | // 构建模式 22 | if (isBuildMode) { 23 | plugins.push( 24 | 25 | // 清空dist文件夹 26 | new CleanWebpackPlugin(['dist'], { 27 | root: rootPath, 28 | verbose: true 29 | }) 30 | ); 31 | 32 | // 构建查看可视化体积 33 | if (isShowMode) { 34 | plugins.push( 35 | 36 | // 可视化展示打包体积 37 | new BundleAnalyzerPlugin() 38 | ); 39 | } 40 | } 41 | 42 | 43 | 44 | module.exports = { 45 | entry: { 46 | index: `./src/index~${deviceType}.js` 47 | }, 48 | 49 | output: { 50 | path: path.resolve(rootPath, './dist') 51 | }, 52 | 53 | resolve: { 54 | // 路径别名 55 | alias: { 56 | '@src': path.resolve(rootPath, 'src') 57 | }, 58 | 59 | // 依次 自动添加 文件后缀 60 | extensions: ['.vue', '.js', '.scss', '.css', '.json'], 61 | 62 | // 指明第三方模块存放的位置,以减少搜索步骤 63 | modules: [nodeModulesPath] 64 | }, 65 | 66 | module: { 67 | rules: [{ 68 | test: /\.(htm|html)$/, 69 | use: [{ 70 | loader: 'html-withimg-loader' 71 | }] 72 | }, { 73 | test: /\.vue$/, 74 | use: [{ 75 | loader: 'vue-loader' 76 | }], 77 | include: [contextPath, nodeModulesPath] 78 | }] 79 | }, 80 | 81 | plugins: [ 82 | // 部署打包 需要运行的插件(清空dist文件夹、可视化分析) 83 | ...plugins, 84 | 85 | // 将定义过的其它规则复制并应用到 .vue 文件里相应语言的块 86 | new VueLoaderPlugin(), 87 | 88 | // 当开启 HMR 的时候使用该插件会显示模块的相对路径,建议用于开发环境 89 | new webpack.NamedModulesPlugin(), 90 | 91 | // 配合热替换使用 92 | new webpack.HotModuleReplacementPlugin(), 93 | 94 | // 打包时显示进度 95 | new ProgressBarPlugin() 96 | ], 97 | 98 | // webpack 代码分割 99 | optimization: { 100 | runtimeChunk: { 101 | name: 'static/runtime/index' 102 | } 103 | }, 104 | 105 | // 精简 终端输出(打包部署) 106 | stats: { 107 | modules: false, 108 | children: false, 109 | chunks: false, 110 | chunkModules: false 111 | }, 112 | 113 | devServer: { 114 | // 精简 终端输出(本地运行) 115 | stats: { 116 | modules: false, 117 | children: false, 118 | chunks: false, 119 | chunkModules: false 120 | }, 121 | contentBase: path.resolve(rootPath, './dist'), // 启动本地服务时,从哪加载内容 122 | host: '0.0.0.0', // 可 通过IP 访问,也可以通过 localhost 访问 123 | useLocalIp: true, // browser open with your local IP 124 | port: 3000, 125 | open: true, // 启动本地服务后,自动打开页面 126 | hot: true, // 是否启用webpack热替换(局部筛选) 127 | compress: true, // 是否启用 gzip 压缩(服务器) 128 | overlay: true, // 编译器错误或警告时, 在浏览器中显示全屏覆盖; 默认false 129 | inline: true // 在打包后文件里注入一个websocket客户端 130 | } 131 | }; 132 | 133 | -------------------------------------------------------------------------------- /src/common/utils/$http/index.js: -------------------------------------------------------------------------------- 1 | /* global __API__ */ 2 | import axios from 'axios'; 3 | import $cache from '../$cache'; 4 | 5 | 6 | 7 | /** 8 | * 接口状态,分别对应等待响应、响应成功和响应失败 9 | * @type {String} 10 | */ 11 | const STATUS_PENDING = 'PENDING'; 12 | const STATUS_SUCCESS = 'SUCCESS'; 13 | const STATUS_FAILURE = 'FAILURE'; 14 | 15 | 16 | 17 | /** 18 | * 用于记录每个接口的信息(请求参数、详情参数、请求状态、XHR) 19 | * @type {Object} 20 | */ 21 | const requestApis = {}; 22 | 23 | 24 | 25 | /** 26 | * 全局 http 请求方法 27 | * 28 | * @param {Object} {} 请求信息 29 | * @return {Object} 响应数据 30 | */ 31 | const $http = async ({ 32 | config, 33 | params 34 | }) => { 35 | if (!(typeof config === 'object' && config !== null)) { 36 | throw new Error('$http invalid $api-config'); 37 | } 38 | 39 | if (requestApis[config.name] && requestApis[config.name].status === STATUS_PENDING && 40 | JSON.stringify(requestApis[config.name].params) === JSON.stringify(params)) { 41 | // 中断尚未收到响应的请求 42 | requestApis[config.name].canceler(); 43 | 44 | // 标记 同一个请求被取消次数 45 | if (!requestApis[config.name].canceledNumber) { 46 | requestApis[config.name].canceledNumber = 0; 47 | } 48 | 49 | requestApis[config.name].canceledNumber += 1; 50 | } 51 | 52 | // 尝试读取已有缓存 53 | let cacheKey = config.cacheKey; 54 | let cacheValue; 55 | const useCache = typeof cacheKey === 'string'; 56 | 57 | if (useCache) { 58 | cacheKey = cacheKey.replace(/\{(.[^{}]*)}/g, (value, $1) => params[$1]); 59 | cacheValue = await $cache.getItem(cacheKey); 60 | 61 | if (cacheValue !== null) { 62 | Object.assign(params, { 63 | timestamp: cacheValue.timestamp 64 | }); 65 | } 66 | } 67 | 68 | // 组装 axios() 参数 69 | let canceler = null; 70 | const proxy = config.proxy; 71 | const methodIsPostRelevant = proxy.method.toUpperCase() === 'POST' || 72 | proxy.method.toUpperCase() === 'PUT' || 73 | proxy.method.toUpperCase() === 'PATCH'; 74 | const _options = Object.assign(proxy, { 75 | // 不修改绝对路径 76 | url: /^http(s)?:\/\//.test(proxy.url) ? proxy.url : __API__.BASEURL + proxy.url, 77 | params: !methodIsPostRelevant ? params : '', 78 | data: !methodIsPostRelevant ? '' : params, 79 | cancelToken: new axios.CancelToken((cancel) => { 80 | canceler = cancel; 81 | }) 82 | }); 83 | 84 | 85 | 86 | const axiosXHR = axios(_options); 87 | 88 | 89 | if (!requestApis[config.name]) { 90 | requestApis[config.name] = {}; 91 | } 92 | 93 | requestApis[config.name].status = STATUS_PENDING; 94 | requestApis[config.name].params = proxy.data; 95 | requestApis[config.name].XHR = axiosXHR; 96 | requestApis[config.name].canceler = canceler; 97 | 98 | try { 99 | const response = await Promise.resolve(axiosXHR); 100 | 101 | // 判断已有缓存是否有效 102 | // TODO: 张新 验证 103 | if (useCache) { 104 | switch (response.data.code) { 105 | case 0: 106 | await $cache.setItem(cacheKey, response.data); 107 | break; 108 | 109 | case 208: 110 | response.data = cacheValue; 111 | break; 112 | 113 | default: 114 | } 115 | } 116 | 117 | if (response.data.code === 0) { 118 | requestApis[config.name].status = STATUS_SUCCESS; 119 | requestApis[config.name].response = response.data; 120 | 121 | return response.data.data; 122 | } 123 | 124 | 125 | 126 | throw response.data; 127 | } catch (error) { 128 | let response; 129 | 130 | // TODO: 登录校验 跳转 131 | 132 | if (error.code) { 133 | response = error; 134 | } else if (requestApis[config.name].canceledNumber) { 135 | response = { 136 | code: -1, 137 | data: `$api ${config.name} abort` 138 | }; 139 | 140 | requestApis[config.name].canceledNumber -= 1; 141 | } else { 142 | response = { 143 | code: -2, 144 | data: `$api ${config.name} error` 145 | }; 146 | } 147 | 148 | requestApis[config.name].status = STATUS_FAILURE; 149 | 150 | throw response; 151 | } 152 | }; 153 | 154 | export default $http; 155 | -------------------------------------------------------------------------------- /scripts/webpack/dev.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const webpackMerge = require('webpack-merge'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const baseWebpackConfig = require('./base.config'); 6 | const devEnvConfig = require('../../configs/dev.config'); 7 | 8 | const rootPath = path.resolve(__dirname, '../../'); 9 | const contextPath = path.resolve(rootPath, './src/'); 10 | const nodeModulesPath = path.resolve(rootPath, './node_modules/'); 11 | const isBuildMode = require('minimist')(process.argv.slice(2)).build; 12 | const deviceType = require('../../configs/base.config').deviceType; 13 | 14 | 15 | 16 | module.exports = webpackMerge(baseWebpackConfig, { 17 | mode: 'development', 18 | 19 | devtool: isBuildMode ? '' : 'cheap-module-eval-source-map', 20 | 21 | output: { 22 | filename: '[name].js', 23 | chunkFilename: 'static/[name].js', 24 | publicPath: devEnvConfig.build.publicPath // CDN 路径 25 | }, 26 | 27 | module: { 28 | rules: [{ 29 | test: /\.css$/, 30 | use: [{ 31 | loader: 'vue-style-loader' 32 | }, { 33 | loader: 'css-loader' 34 | }, { 35 | loader: 'postcss-loader' 36 | }], 37 | include: [contextPath, nodeModulesPath] 38 | }, { 39 | test: /\.scss$/, 40 | use: [{ 41 | loader: 'vue-style-loader' 42 | }, { 43 | loader: 'css-loader' 44 | }, { 45 | loader: 'postcss-loader' 46 | }, { 47 | loader: 'sass-loader' 48 | }], 49 | include: [contextPath, nodeModulesPath] 50 | }, { 51 | test: /\.sass$/, 52 | use: [{ 53 | loader: 'vue-style-loader' 54 | }, { 55 | loader: 'css-loader' 56 | }, { 57 | loader: 'postcss-loader' 58 | }, { 59 | loader: 'sass-loader', 60 | options: { 61 | indentedSyntax: true 62 | } 63 | }], 64 | include: [contextPath, nodeModulesPath] 65 | }, { 66 | test: /\.less$/, 67 | use: [{ 68 | loader: 'vue-style-loader' 69 | }, { 70 | loader: 'css-loader' 71 | }, { 72 | loader: 'postcss-loader' 73 | }, { 74 | loader: 'less-loader' 75 | }], 76 | include: [contextPath, nodeModulesPath] 77 | }, { 78 | test: /\.js$/, 79 | use: [{ 80 | loader: 'babel-loader' 81 | }], 82 | include: [contextPath], 83 | exclude: [nodeModulesPath] 84 | }, { 85 | test: /\.(png|jpg|gif|ico)$/, 86 | use: [{ 87 | loader: 'url-loader', 88 | options: { 89 | limit: 8192, 90 | name: '[name].[ext]', 91 | outputPath: 'static/assets/', 92 | publicPath: `${devEnvConfig.build.publicPath}static/assets/` 93 | } 94 | }] 95 | }, { 96 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 97 | use: [{ 98 | loader: 'url-loader', 99 | options: { 100 | name: '[name].[ext]', 101 | outputPath: 'static/assets/', 102 | publicPath: `${devEnvConfig.build.publicPath}static/assets/` 103 | } 104 | }] 105 | }, { 106 | test: /\.(woff|woff2|otf|svg|eot|ttf)(\?#(iefix|fontawesomeregular))?$/, 107 | use: [{ 108 | loader: 'file-loader', 109 | options: { 110 | name: '[name].[ext]', 111 | outputPath: 'static/assets/', 112 | publicPath: `${devEnvConfig.build.publicPath}static/assets/` 113 | } 114 | }] 115 | }] 116 | }, 117 | 118 | plugins: [ 119 | // 环境变量 120 | new webpack.DefinePlugin(devEnvConfig.env), 121 | 122 | 123 | new HtmlWebpackPlugin({ 124 | filename: 'index.html', 125 | template: './src/index.html', 126 | meta: deviceType === 'mobile' ? { 127 | viewport: 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;' 128 | } : {} 129 | }) 130 | ] 131 | }); 132 | -------------------------------------------------------------------------------- /scripts/webpack/prod.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const webpackMerge = require('webpack-merge'); 4 | const baseWebpackConfig = require('./base.config'); 5 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | const prodEnvConfig = require('../../configs/prod.config'); 7 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 8 | const OptimizeCss = require('optimize-css-assets-webpack-plugin'); 9 | 10 | const rootPath = path.resolve(__dirname, '../../'); 11 | const contextPath = path.resolve(rootPath, './src/'); 12 | const nodeModulesPath = path.resolve(rootPath, './node_modules/'); 13 | const deviceType = require('../../configs/base.config').deviceType; 14 | 15 | 16 | 17 | module.exports = webpackMerge(baseWebpackConfig, { 18 | mode: 'production', 19 | 20 | output: { 21 | filename: '[name].js', 22 | chunkFilename: 'static/[name].[chunkHash:8].js', 23 | publicPath: prodEnvConfig.build.publicPath // CDN 路径 24 | 25 | }, 26 | 27 | module: { 28 | rules: [{ 29 | test: /\.css$/, 30 | use: [{ 31 | loader: MiniCssExtractPlugin.loader 32 | }, { 33 | loader: 'css-loader' 34 | }, { 35 | loader: 'postcss-loader' 36 | }], 37 | include: [contextPath, nodeModulesPath] 38 | }, { 39 | test: /\.scss$/, 40 | use: [{ 41 | loader: MiniCssExtractPlugin.loader 42 | }, { 43 | loader: 'css-loader' 44 | }, { 45 | loader: 'postcss-loader' 46 | }, { 47 | loader: 'sass-loader' 48 | }], 49 | include: [contextPath, nodeModulesPath] 50 | }, { 51 | test: /\.sass$/, 52 | use: [{ 53 | loader: MiniCssExtractPlugin.loader 54 | }, { 55 | loader: 'css-loader' 56 | }, { 57 | loader: 'postcss-loader' 58 | }, { 59 | loader: 'sass-loader', 60 | options: { 61 | indentedSyntax: true 62 | } 63 | }], 64 | include: [contextPath, nodeModulesPath] 65 | }, { 66 | test: /\.less$/, 67 | use: [{ 68 | loader: MiniCssExtractPlugin.loader 69 | }, { 70 | loader: 'css-loader' 71 | }, { 72 | loader: 'postcss-loader' 73 | }, { 74 | loader: 'less-loader' 75 | }], 76 | include: [contextPath, nodeModulesPath] 77 | }, { 78 | test: /\.js$/, 79 | use: [{ 80 | loader: 'babel-loader', 81 | options: { 82 | plugins: [ 83 | 'transform-merge-sibling-variables', 84 | 'transform-remove-console', 85 | 'transform-remove-debugger', 86 | 'transform-remove-undefined' 87 | ] 88 | } 89 | }], 90 | include: [contextPath], 91 | exclude: [nodeModulesPath] 92 | }, { 93 | test: /\.(png|jpg|gif|ico)$/, 94 | use: [{ 95 | loader: 'url-loader', 96 | options: { 97 | limit: 8192, 98 | name: '[name].[hash:8].[ext]', 99 | outputPath: 'static/assets/', 100 | publicPath: `${prodEnvConfig.build.publicPath}static/assets/` 101 | } 102 | }] 103 | }, { 104 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 105 | use: [{ 106 | loader: 'url-loader', 107 | options: { 108 | name: '[name].[hash:8].[ext]', 109 | outputPath: 'static/assets/', 110 | publicPath: `${prodEnvConfig.build.publicPath}static/assets/` 111 | } 112 | }] 113 | }, { 114 | test: /\.(woff|woff2|otf|svg|eot|ttf)(\?#(iefix|fontawesomeregular))?$/, 115 | use: [{ 116 | loader: 'file-loader', 117 | options: { 118 | name: '[name].[hash:8].[ext]', 119 | outputPath: 'static/assets/', 120 | publicPath: `${prodEnvConfig.build.publicPath}static/assets/` 121 | } 122 | }] 123 | }] 124 | }, 125 | 126 | plugins: [ 127 | // 环境变量 128 | new webpack.DefinePlugin(prodEnvConfig.env), 129 | 130 | // 抽离 CSS 131 | new MiniCssExtractPlugin({ 132 | filename: 'static/css/[name].[contentHash:8].css' 133 | }), 134 | 135 | // 压缩 CSS 136 | new OptimizeCss(), 137 | 138 | new HtmlWebpackPlugin({ 139 | filename: 'index.html', 140 | template: './src/index.html', 141 | meta: deviceType === 'mobile' ? { 142 | viewport: 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;' 143 | } : {}, 144 | hash: true, // html 中引入的资源 加哈希(避免缓存导致的问题); 默认false 145 | minify: { 146 | removeComments: true, // 去掉注释 147 | removeAttributeQuotes: true, // 去掉标签上 属性的双引号 148 | collapseWhitespace: true, // 去掉空行 149 | removeRedundantAttributes: true, // 去掉多余的属性 150 | removeEmptyAttributes: true, // 去掉空属性 151 | useShortDoctype: true, 152 | removeStyleLinkTypeAttributes: true, 153 | keepClosingSlash: true, 154 | minifyJS: true, 155 | minifyCSS: true, 156 | minifyURLs: true 157 | } 158 | }) 159 | ], 160 | 161 | performance: { 162 | hints: false // 关闭 文件超过250kb的警告 163 | } 164 | }); 165 | 166 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | 3 | - 这是一个多模块的 Vue 单页应用架构,适用于 PC、Mobile 环境 4 | 5 | - 提供统一的开发规范、构建脚本、基础配置、基础组件、初始代码 6 | 7 | - 内置前端集成解决方案,快速实现底层通用功能 8 | 9 | ## 目录结构 10 | 11 | - 根目录 12 | 13 | ``` 14 | ├── /.vscode # vscode 编辑器配置 15 | │ ├── /pluginList.md # vscode 插件列表 16 | │ ├── /settings.json # vscode 基础配置 17 | │ │ 18 | ├── /configs # 项目 构建配置 19 | │ ├── /base.conf.js # 基础 构建配置 20 | │ ├── /dev.conf.js # 开发模式 构建配置 21 | │ ├── /prod.conf.js # 生产模式 构建配置 22 | │ │ 23 | ├── /dist # 构建后文件 24 | ├── /docs # 项目文档 25 | ├── /node_modules # 项目依赖 26 | ├── /scripts # 构建脚本 27 | │ ├── /jenkins # jenkins 构建、部署 脚本 28 | │ ├── /webpack # webpack 配置文件 29 | │ │ 30 | ├── /src # 项目路径 31 | ├── .csscomb.json # csscomb 配置 32 | ├── .editorconfig # editorconfig 配置 33 | ├── .eslintignore # eslint 忽略的文件 34 | ├── .eslintrc # eslint 配置 35 | ├── .gitignore # git 忽略的文件 36 | ├── .jsbeautifyrc # js 格式化配置 37 | ├── .stylelintrc # css 格式化配置 38 | ├── .babel.config.js # babel 配置 39 | ├── .jsconfig.json 40 | ├── package-lock.json # 锁定依赖包版本 41 | ├── package.json # 项目组 npm 配置 42 | ├── postcss.config.js # postcss 配置 43 | ``` 44 | 45 | - 配置目录 46 | 47 | ``` 48 | ├── /configs # 项目 构建配置 49 | │ ├── /base.conf.js # 基础 构建配置(设备类型) 50 | │ ├── /dev.conf.js # 开发模式 构建配置(环境变量、构建路径) 51 | │ ├── /prod.conf.js # 生产模式 构建配置(环境变量、构建路径) 52 | ``` 53 | 54 | - 项目目录 55 | 56 | ``` 57 | ├── /src 58 | │ ├── /common # 通用 模块 59 | │ ├── /utils # 通用 脚本 60 | │ ├── /$api-conf # ajax API 配置 61 | │ ├── /$cache # localforage 配置 62 | │ ├── /$dicts # 数据字典 配置 63 | │ ├── /$handler # 方法集 64 | │ ├── /$http # ajax 封装方法 65 | │ ├── /$router # 路由配置 66 | │ ├── /$store # 状态树配置 67 | │ │ 68 | │ ├── /views # 通用 页面组件 69 | │ ├── /$app # 通用模块 入口页面 70 | │ ├── /demo # 通用模块 demo页面 71 | │ │ 72 | │ ├── /widgets # 通用 UI组件 73 | │ ├── /$icons # fontawesome 配置 74 | │ ├── /$iview # iview 配置(pc端) 75 | │ ├── /$vant # vant 配置(mobile端) 76 | │ ├── /base-chart # 图表组件(基于 echarts) 77 | │ ├── /base-loading # loading 组件 78 | │ ├── /base-scroller # 滚动条组件(统一 终端样式,内置滚动加载) 79 | │ │ 80 | │ ├── /module1 # module1 模块 81 | │ ├── /utils # 独立模块 脚本 82 | │ │ 83 | │ ├── /views # 独立模块 页面组件 84 | │ ├── /$app # 独立模块 入口页面 85 | │ ├── /demo # 独立模块 demo页面 86 | │ │ 87 | │ ├── /widgets # 独立模块 UI组件 88 | │ │ 89 | │ ├── /index.html # html 模板 90 | │ ├── /index~mobile.js # 入口 JS(mobile端) 91 | │ ├── /index~pc.js # 入口 JS(pc端) 92 | ``` 93 | 94 | 95 | ## 开发约束 96 | 97 | ### 共同约束 98 | 99 | > 关于 路径 100 | 101 | - 路径别名 `@src`:等同于 `/src`;项目中不允许 使用绝对路径 102 | 103 | - 引入路径层级较多:尽量使用 `@src/` 104 | 105 | > 关于 ajax 106 | 107 | - 通用模块下 `$http` 是基于 axios 封装的通用方法,该方法被挂载到 vue实例下,接口配置位于 `@src/common/utils/$api-conf` 108 | 109 | - 项目中发请求 均使用 `this.$http()`;状态树中发请求 需要引入 `$http` 文件 110 | 111 | 112 | > 关于 `$router` 113 | 114 | - 每个模块均有自己的 入口页面组件,即模块下的 `views/$app`;`@src/common/views/$app` 为通用模块 入口页面组件,层级最高 115 | 116 | ``` 117 | 页面组件层级:通用模块 `$app` > 通用模块 其他页面组件 = 独立模块 `$app` > 独立模块 其他页面组件 118 | ``` 119 | 120 | - 路由配置 `@src/common/utils/$router` 中,路由参数 `meta.keepAlive` 决定当前页面是否开启 `keep-alive` 缓存 121 | 122 | - 路由配置 `@src/common/utils/$router` 中,路由参数 `meta.rank` 决定已缓存的页面,何时 动态销毁缓存(具体机制 见 路由配置代码) 123 | 124 | 125 | > 关于 `$store` 126 | 127 | - 在非必要情况下,避免使用状态管理方案(vuex) 128 | 129 | - 状态树中定义的变量,均以 `$` 开头,与组件内变量的命名 做明显区分 130 | 131 | - 避免 `getters` 直接返回 `state` 中的值,这种场景下 在组件中直接获取 `state` 中的值,是个不错的选择 132 | 133 | > 关于 依赖包的引入 134 | 135 | - dev开发者 不能擅自引入 依赖包,需master开发者确认 136 | 137 | - 依赖包 能按需引入的一定要按需引入,如 `lodash`、`iview`、`fontawesome` 等等 138 | 139 | ``` 140 | // 最佳实践 141 | 142 | import _cloneDeep from 'lodash/cloneDeep'; 143 | ``` 144 | 145 | > 关于 代码校验 146 | 147 | - 严格遵循 框架集成的 开发规范,如下 148 | > CSS 149 | - 规范: styleLint 150 | 151 | - 转换: postcss 152 | 153 | - 美化: csscomb 154 | 155 | - 预编译: scss(推荐)、sass、less 156 | 157 | > JavaScript 158 | - 规范: eslint + airbnb 159 | 160 | - 转换: babel7 161 | 162 | - 美化: jsbeautifier 163 | 164 | 165 | - 不允许 `commit` 的代码 有规范错误,保存代码时会有错误提示(警告提示可忽略) 166 | 167 | > `commit` 代码前,先执行命令 `npm run eslint:fix`,查看项目中存在的代码规范错误,改正后再提交 168 | 169 | 170 | 171 | ### PC 端约束 172 | 173 | - 使用 [iview](https://www.iviewui.com/) 组件库,相关配置位于 `@src/common/widgets/$iview` 174 | 175 | - 使用 [fontawesome](https://fontawesome.com/) 字体图标,相关配置位于 `@src/common/widgets/$icons` 176 | 177 | - 图片 **尽量压缩**,压缩用具 [TinyPNG](https://tinypng.com/) 178 | 179 | 180 | ### Mobile 端约束 181 | 182 | - 避免使用过多的lib,以保证 mobile端页面性能;必要时可使用 [vant](https://youzan.github.io/vant/#/zh-CN/intro) 组件库,相关配置位于 `@src/common/widgets/$vant` 183 | 184 | - 图片 **必须压缩**,压缩用具 [TinyPNG](https://tinypng.com/);尽量使用 `jpg` 格式的图片,压缩率高 185 | 186 | - 任何文件名、路径中 不得出现中文 187 | 188 | - mobile端设计稿 是以 `iPhone6/7/8` 屏幕为基准 的二倍图,设计稿宽度 750px,`dpr` 为 2 189 | 190 | - 架构使用 `postcss-px-to-viewport`,对 `@src` 目录下的 CSS 代码中以 `px` 为单位的样式进行自动转换 成 `vm` 为单位的样式;项目开发时,CSS代码中直接写设计稿上量出来的 `px` 尺寸即可 191 | 192 | > 默认 `font` 及 `font` 开头的属性的 `px` 尺寸,不会被转换;所以 CSS代码中 `font` 相关 `px` 尺寸,应为设计稿的 `1/2` 193 | 194 | > 默认 CSS选择器 `.ignore-to-vw` 内的属性的 `px` 尺寸,不会被转换;所以相关属性的 `px` 尺寸,应为设计稿的 `1/2` 195 | 196 | 197 | 198 | ## 命令列表 199 | 200 | > 如下命令,需要在项目根目录下执行 201 | 202 | - `npm run serve:dev` 203 | 204 | 以开发模式 本地运行项目 205 | 206 | - `npm run serve:prod` 207 | 208 | 以生产模式 本地运行项目 209 | 210 | - `npm run build:dev` 211 | 212 | 以开发模式 构建项目 213 | 214 | - `npm run build:prod` 215 | 216 | 以生产模式 构建项目 217 | 218 | - `npm run build:show` 219 | 220 | 以生产模式 构建项目,并可视化展示 打包后代码体积 221 | 222 | - `npm run eslint:fix` 223 | 224 | 根据 eslint、airbnb 规范 校验 JavaScript,并自动修复 部分问题 225 | 226 | 227 | 228 | ## 架构补充说明 229 | 230 | - 支持 CDN资源发布 231 | 232 | - 本地运行项目支持IP访问 233 | 234 | - 支持 Jenkins 自动化部署 235 | 236 | - 支持 可视化查看项目打包体积 237 | 238 | - `npm run build:XXX` 之前自动清空上一次打包文件 239 | 240 | - 项目打包机制:更多的遵循 webpack默认打包机制,在此基础上作如下调整 241 | > 分离 webpack `runtime` 代码 242 | 243 | > 每一个路由 单独打包 244 | 245 | > 生产环境下:分离css、tree-shaking、remove console 等 246 | 247 | > 开发环境下:webpack 热加载、热替换 248 | 249 | > autoprefixer 自动添加 css 属性前缀 250 | -------------------------------------------------------------------------------- /src/common/utils/$handler/index.js: -------------------------------------------------------------------------------- 1 | import math from './assets/math-conf'; 2 | 3 | 4 | 5 | /** 6 | * 精准计算 7 | * 8 | * @param {Number} expre 数学表达式 9 | * @returns {String} 转换后的值 10 | */ 11 | const getCalculation = (expre) => { 12 | const value = Number(math.format(math.eval(expre), 14)); 13 | 14 | return Number(value.toString()); 15 | }; 16 | 17 | 18 | /** 19 | * 将数字 转成 千分位分割的字符串 20 | * 21 | * @param {Number} value 数字 22 | * @returns {String} 转换后的值 23 | * */ 24 | const _formatNumberToSeparator = (value) => { 25 | const re = /\d{1,3}(?=(\d{3})+$)/g; 26 | const result = String(Math.abs(value)).replace(/^(\d+)((\.\d+)?)$/, (s, s1, s2) => s1.replace(re, '$&,') + s2); 27 | 28 | return `${value < 0 ? '-' : ''}${result}`; 29 | }; 30 | 31 | /** 32 | * 千分位分割的字符串 转成 数字 33 | * 34 | * @param {String} value 千分位分割的字符串 35 | * @returns {Number} 转换后的值 36 | * */ 37 | const formatSeparatorToNumber = value => Number(String(value).replace(/[,]/g, '')); 38 | 39 | 40 | /** 41 | * 将数字 转成 保留n位小数的字符串(千分位分割) 42 | * 43 | * @param {Number} value 数字 44 | * @param {Number} n 保留n位小数(默认值 2) 45 | * @returns {String} 转换后的值 46 | * */ 47 | const formatNumberToFixed = (value, n = 2) => { 48 | if (typeof value !== 'number') { 49 | throw new Error('formatNumberToFixed value should be number'); 50 | } 51 | 52 | return _formatNumberToSeparator(Number(value.toFixed(n))); 53 | }; 54 | 55 | 56 | /** 57 | * 将数字 转成 不带单位的数字(与 formatNumberUnit 配合使用) 58 | * 59 | * @param {Number} value 数字 60 | * @param {Boolean} English 单位是否是英文 61 | * @param {Number} n 数字保留几位小数 62 | * @returns {String} 转换后的值 63 | * */ 64 | const formatNumberValue = (value, English = false, n = 2) => { 65 | if (typeof value !== 'number') { 66 | throw new Error('formatNumberValue value should be number'); 67 | } 68 | 69 | const valueString = String(value); 70 | const length = valueString.indexOf('.') > -1 ? valueString.indexOf('.') : valueString 71 | .length; 72 | const dicts = { 73 | 1: formatNumberToFixed(value, n), 74 | 2: formatNumberToFixed(value, n), 75 | 3: formatNumberToFixed(value, n), 76 | 4: English ? formatNumberToFixed(value / 1000, n) : formatNumberToFixed(value, n), 77 | 5: English ? formatNumberToFixed(value / 1000, n) : formatNumberToFixed(value / 78 | 10000, n), 79 | 6: English ? formatNumberToFixed(value / 1000, n) : formatNumberToFixed(value / 80 | 10000, n), 81 | 7: English ? formatNumberToFixed(value / 1000, n) : formatNumberToFixed(value / 82 | 10000, n), 83 | 8: English ? formatNumberToFixed(value / 1000, n) : formatNumberToFixed(value / 84 | 10000, n), 85 | 9: English ? formatNumberToFixed(value / 1000, n) : formatNumberToFixed(value / 86 | 100000000, n), 87 | 10: English ? formatNumberToFixed(value / 1000, n) : formatNumberToFixed(value / 88 | 100000000, n), 89 | 11: English ? formatNumberToFixed(value / 1000, n) : formatNumberToFixed(value / 90 | 100000000, n) 91 | }; 92 | 93 | return dicts[length]; 94 | }; 95 | 96 | /** 97 | * 将数字 转成 单位(与 formatNumberValue 配合使用) 98 | * 99 | * @param {Number} value 数字 100 | * @param {Boolean} English 单位是否是英文 101 | * @returns {String} 单位 102 | * */ 103 | const formatNumberUnit = (value, English = false) => { 104 | if (typeof value !== 'number') { 105 | throw new Error('formatNumberUnit value should be number'); 106 | } 107 | 108 | const valueString = String(value); 109 | const length = valueString.indexOf('.') > -1 ? valueString.indexOf('.') : valueString 110 | .length; 111 | const dicts = { 112 | 1: '', 113 | 2: '', 114 | 3: '', 115 | 4: English ? 'K' : '', 116 | 5: English ? 'K' : '万', 117 | 6: English ? 'K' : '万', 118 | 7: English ? 'K' : '万', 119 | 8: English ? 'K' : '万', 120 | 9: English ? 'K' : '亿', 121 | 10: English ? 'K' : '亿', 122 | 11: English ? 'K' : '亿' 123 | }; 124 | 125 | return dicts[length]; 126 | }; 127 | 128 | 129 | /** 130 | * 计算 日期间隔 131 | * 132 | * @param {String} date1 开始日期 133 | * @param {String} date2 结束日期 134 | * @returns {Number} 共多少天 135 | * */ 136 | const getDateDays = (date1, date2) => { 137 | const dateStart = new Date(date1.replace(/-/g, '/')); // 将-转化为/,使用new Date 138 | const dateEnd = new Date(date2.replace(/-/g, '/')); // 将-转化为/,使用new Date 139 | const dateDiff = dateEnd.getTime() - dateStart.getTime(); // 时间差的毫秒数 140 | const dayDiff = Math.floor(dateDiff / (24 * 3600 * 1000)); // 计算出相差天数 141 | 142 | return dayDiff + 1; 143 | }; 144 | 145 | 146 | /** 147 | * 获取一定范围内的 n个 随机整数 148 | * 149 | * @param {Number} start 开始数字 150 | * @param {Number} end 结束数字 151 | * @param {Number} n 取多少个 152 | * @returns {Array} 取出后的数组 153 | * */ 154 | const getRandomNumber = (start, end, n) => { 155 | const arr = []; 156 | 157 | if (start > end) { 158 | return arr; 159 | } 160 | 161 | for (let i = 0; i < n; i += 1) { 162 | const number = Math.floor((Math.random() * ((end - start) + 1)) + start); 163 | 164 | if (arr.indexOf(number) < 0) { 165 | arr.push(number); 166 | } else { 167 | i -= 1; 168 | } 169 | } 170 | 171 | return arr; 172 | }; 173 | 174 | 175 | /** 176 | * 获取 对象/数组 中的值 177 | * 178 | * @param {Object} obj 对象 或 数组 179 | * @param {String} keys 字符串 或 数组;键值关联关系,可以是 'a.b.c',也可以是 ['a', 'b', 'c'] 180 | * @param {result} defaultValue 取不到值时定义的返回值,默认为 undefined 181 | * @returns {Array} 取出的值 182 | * */ 183 | const getObjectValue = (obj, keys, defaultValue = undefined) => { 184 | if (!obj) { 185 | return defaultValue; 186 | } 187 | 188 | const keyArr = typeof keys === 'string' ? keys.split('.') : keys; 189 | 190 | 191 | if (keyArr.length === 1 && obj[keyArr[0]] !== undefined) { 192 | return obj[keyArr[0]]; 193 | } 194 | 195 | 196 | if (obj[keyArr[0]] && keyArr.length > 1) { 197 | return getObjectValue(obj[keyArr[0]], keyArr.slice(1), defaultValue); 198 | } 199 | 200 | return defaultValue; 201 | }; 202 | 203 | /** 204 | * 时间格式化 205 | * @param {DATE} date 时间对象 206 | * @param {String} formater 格式化样式 yyyy-MM-dd hh:mm:ss 207 | * @return {String} 格式化后的时间 208 | */ 209 | const formatDate = (date, formater) => { 210 | const o = { 211 | 'M+': date.getMonth() + 1, // 月份 212 | 'd+': date.getDate(), // 日 213 | 'h+': date.getHours(), // 小时 214 | 'm+': date.getMinutes(), // 分 215 | 's+': date.getSeconds(), // 秒 216 | 'q+': Math.floor((date.getMonth() + 3) / 3), // 季度 217 | S: date.getMilliseconds() // 毫秒 218 | }; 219 | 220 | if (/(y+)/.test(formater)) { 221 | formater = formater.replace(RegExp.$1, (`${date.getFullYear()}`).substr(4 - RegExp.$1.length)); 222 | } 223 | for (const k in o) { 224 | if (new RegExp(`(${k})`).test(formater)) { 225 | formater = formater.replace(RegExp.$1, (RegExp.$1.length === 1) ? 226 | (o[k]) : 227 | ((`00${o[k]}`).substr((`${o[k]}`).length))); 228 | } 229 | } 230 | 231 | return formater; 232 | }; 233 | 234 | 235 | export { 236 | getDateDays, // 获取 日期天数 237 | getCalculation, // 高精度计算 238 | getObjectValue, // 获取 对象/数组 中的值(优化层层判空) 239 | getRandomNumber, // 获取一定范围内的 n个 随机整数 240 | 241 | formatDate, // 时间格式化 242 | formatNumberToFixed, // 将数字 千分位分割的字符串(默认保留2位小数) 243 | formatSeparatorToNumber, // 千分位分割的字符串 转成 将数字= 244 | formatNumberValue, // 与 formatNumberUnit 配合使用:将原数值 转成 去掉单位后的数值 245 | formatNumberUnit // 与 formatNumberValue 配合使用:将原数值 转成 单位 246 | }; 247 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | "airbnb", 5 | "plugin:vue/recommended" 6 | ], 7 | "env": { 8 | "es6": true, 9 | "browser": true, 10 | "node": true 11 | }, 12 | "parserOptions": { 13 | "ecmaVersion": 2017 14 | }, 15 | "rules": { 16 | // For more information about these rules, see: http://eslint.org/docs/rules/ 17 | // Possible Errors 18 | // require or disallow trailing commas 19 | "comma-dangle": [ 20 | 2, 21 | "never" 22 | ], 23 | 24 | "global-require": [ 25 | 0 26 | ], 27 | 28 | "no-return-await": [ 29 | 0 30 | ], 31 | 32 | "max-len": ["error", { 33 | "code": 120 34 | }], 35 | 36 | // disallow use of Object.prototypes builtins directly 37 | "no-prototype-builtins": 2, 38 | // enforce valid JSDoc comments 39 | "valid-jsdoc": [ 40 | 2, 41 | { 42 | "requireReturn": false 43 | } 44 | ], 45 | // Best Practices 46 | // enforce consistent brace style for all control statements 47 | "curly": [ 48 | 2, 49 | "all" 50 | ], 51 | 52 | 53 | // require for-in loops to include an if statement 54 | "guard-for-in": [ 55 | 1 56 | ], 57 | // disallow new operators outside of assignments or comparisons 58 | "no-new": [ 59 | 0 60 | ], 61 | // disallow reassigning function parameters 62 | "no-param-reassign": [ 63 | 1 64 | ], 65 | // Variables 66 | // disallow var declarations from shadowing variables in the outer scope 67 | "no-shadow": [ 68 | 1 69 | ], 70 | // disallow the use of variables before they are defined 71 | "no-use-before-define": [ 72 | "error", 73 | { 74 | "functions": false, 75 | "classes": true 76 | } 77 | ], 78 | // Node.js and CommonJS 79 | // require error handling in callbacks 80 | "handle-callback-err": [ 81 | 2, 82 | "err" 83 | ], 84 | // Stylistic Issues 85 | // enforce consistent brace style for blocks 86 | "brace-style": [ 87 | 2, 88 | "1tbs", 89 | { 90 | "allowSingleLine": false 91 | } 92 | ], 93 | // enforce camelcase naming convention 94 | "camelcase": [ 95 | 2, 96 | { 97 | "properties": "always" 98 | } 99 | ], 100 | // enforce consistent naming when capturing the current execution context 101 | "consistent-this": [ 102 | 2, 103 | "self" 104 | ], 105 | // disallow specified identifiers 106 | "id-blacklist": 0, 107 | // enforce consistent indentation 108 | "indent": [ 109 | 2, 110 | 4, 111 | { 112 | "SwitchCase": 1, 113 | "VariableDeclarator": 1 114 | } 115 | ], 116 | // enforce the consistent use of either double or single quotes in JSX attributes 117 | "jsx-quotes": [ 118 | 1, 119 | "prefer-double" 120 | ], 121 | // require empty lines around comments 122 | "lines-around-comment": [ 123 | 2, 124 | { 125 | "beforeBlockComment": true, 126 | "beforeLineComment": true, 127 | "allowBlockStart": true, 128 | "allowObjectStart": true, 129 | "allowArrayStart": true 130 | } 131 | ], 132 | // enforce a maximum depth that blocks can be nested 133 | "max-depth": [ 134 | 2, 135 | 4 136 | ], 137 | // enforce a maximum depth that callbacks can be nested 138 | "max-nested-callbacks": [ 139 | 2, 140 | 4 141 | ], 142 | // enforce a maximum number of parameters in function definitions 143 | "max-params": [ 144 | 2, 145 | 4 146 | ], 147 | // enforce a maximum number of statements allowed per line 148 | "max-statements-per-line": [ 149 | 2, 150 | { 151 | "max": 1 152 | } 153 | ], 154 | // require parentheses when invoking a constructor with no arguments 155 | "new-parens": 2, 156 | // require or disallow an empty line after var declarations 157 | "newline-after-var": [ 158 | 2, 159 | "always" 160 | ], 161 | // require an empty line before return statements 162 | "newline-before-return": 2, 163 | // disallow use of chained assignment expressions 164 | "no-multi-assign": 1, 165 | // disallow multiple empty lines 166 | "no-multiple-empty-lines": [ 167 | 2, 168 | { 169 | "max": 3, 170 | "maxEOF": 1 171 | } 172 | ], 173 | // disallow specified syntax 174 | "no-restricted-syntax": [ 175 | 2, 176 | "DebuggerStatement", 177 | "LabeledStatement", 178 | "WithStatement" 179 | ], 180 | // disallow dangling underscores in identifiers 181 | "no-underscore-dangle": 1, 182 | // enforce consistent line breaks inside braces 183 | "object-curly-newline": [ 184 | 2, 185 | { 186 | "multiline": true, 187 | "minProperties": 1 188 | } 189 | ], 190 | // enforce placing object properties on separate lines 191 | "object-property-newline": 2, 192 | // enforce consistent linebreak style for operators 193 | "operator-linebreak": [ 194 | 2, 195 | "after" 196 | ], 197 | // require or disallow padding within blocks 198 | "padded-blocks": 1, 199 | // require JSDoc comments 200 | "require-jsdoc": 2, 201 | // require or disallow the Unicode BOM 202 | "unicode-bom": [ 203 | 2, 204 | "never" 205 | ], 206 | // ES6 207 | // enforce consistent spacing around * operators in generator functions 208 | "generator-star-spacing": [ 209 | 2, 210 | "after" 211 | ], 212 | // disallow this/super before calling super() in constructors 213 | "no-this-before-super": 2, 214 | // disallow renaming import, export, and destructured assignments to the same name 215 | "no-useless-rename": 2, 216 | // plugin/import 217 | // rorbid the use of extraneous packages 218 | "import/no-extraneous-dependencies": 1, 219 | // ensure imports point to a file/module that can be resolved 220 | "import/no-unresolved": 1, 221 | // Forbid require() calls with expressions 222 | "import/no-dynamic-require": 1, 223 | "import/prefer-default-export": 1, 224 | // Ensure consistent use of file extension within the import path 225 | "import/extensions": 1, 226 | 227 | "func-names": [ 228 | 1, 229 | "as-needed" 230 | ], 231 | "prefer-destructuring": "off", 232 | /* https://github.com/vuejs/eslint-plugin-vue */ 233 | "vue/html-self-closing": "off", 234 | "vue/name-property-casing": ["error", "kebab-case"], 235 | "vue/html-indent": [ 236 | "error", 237 | 4, 238 | { 239 | "attribute": 1, 240 | "closeBracket": 0, 241 | "alignAttributesVertically": true, 242 | "ignores": [] 243 | } 244 | ], 245 | "vue/max-attributes-per-line": [ 246 | 2, 247 | { 248 | "singleline": 1, 249 | "multiline": { 250 | "max": 1, 251 | "allowFirstLine": true 252 | } 253 | } 254 | ] 255 | }, 256 | "globals": { 257 | "__GLOBALS__": false 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /.csscomb.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": ["node_modules/**"], 3 | "remove-empty-rulesets": true, 4 | "always-semicolon": true, 5 | "color-case": "lower", 6 | "block-indent": " ", 7 | "color-shorthand": true, 8 | "element-case": "lower", 9 | "eof-newline": true, 10 | "leading-zero": true, 11 | "quotes": "double", 12 | "space-before-colon": "", 13 | "space-after-colon": " ", 14 | "space-before-combinator": " ", 15 | "space-after-combinator": " ", 16 | "space-between-declarations": "\n", 17 | "space-before-opening-brace": " ", 18 | "space-after-opening-brace": "\n", 19 | "space-after-selector-delimiter": "\n", 20 | "space-before-selector-delimiter": "", 21 | "space-before-closing-brace": "\n", 22 | "strip-spaces": true, 23 | "tab-size": true, 24 | "unitless-zero": true, 25 | "vendor-prefix-align": true, 26 | "sort-order": [ 27 | [ 28 | "font", 29 | "font-family", 30 | "font-size", 31 | "font-weight", 32 | "font-style", 33 | "font-variant", 34 | "font-size-adjust", 35 | "font-stretch", 36 | "font-effect", 37 | "font-emphasize", 38 | "font-emphasize-position", 39 | "font-emphasize-style", 40 | "font-smooth", 41 | "line-height" 42 | ], 43 | [ 44 | "position", 45 | "z-index", 46 | "top", 47 | "right", 48 | "bottom", 49 | "left" 50 | ], 51 | [ 52 | "display", 53 | "visibility", 54 | "float", 55 | "clear", 56 | "overflow", 57 | "overflow-x", 58 | "overflow-y", 59 | "-ms-overflow-x", 60 | "-ms-overflow-y", 61 | "clip", 62 | "zoom", 63 | "flex-direction", 64 | "flex-order", 65 | "flex-pack", 66 | "flex-align" 67 | ], 68 | [ 69 | "-webkit-box-sizing", 70 | "-moz-box-sizing", 71 | "box-sizing", 72 | "width", 73 | "min-width", 74 | "max-width", 75 | "height", 76 | "min-height", 77 | "max-height", 78 | "margin", 79 | "margin-top", 80 | "margin-right", 81 | "margin-bottom", 82 | "margin-left", 83 | "padding", 84 | "padding-top", 85 | "padding-right", 86 | "padding-bottom", 87 | "padding-left" 88 | ], 89 | [ 90 | "table-layout", 91 | "empty-cells", 92 | "caption-side", 93 | "border-spacing", 94 | "border-collapse", 95 | "list-style", 96 | "list-style-position", 97 | "list-style-type", 98 | "list-style-image" 99 | ], 100 | [ 101 | "content", 102 | "quotes", 103 | "counter-reset", 104 | "counter-increment", 105 | "resize", 106 | "cursor", 107 | "-webkit-user-select", 108 | "-moz-user-select", 109 | "-ms-user-select", 110 | "user-select", 111 | "nav-index", 112 | "nav-up", 113 | "nav-right", 114 | "nav-down", 115 | "nav-left", 116 | "-webkit-transition", 117 | "-moz-transition", 118 | "-ms-transition", 119 | "-o-transition", 120 | "transition", 121 | "-webkit-transition-delay", 122 | "-moz-transition-delay", 123 | "-ms-transition-delay", 124 | "-o-transition-delay", 125 | "transition-delay", 126 | "-webkit-transition-timing-function", 127 | "-moz-transition-timing-function", 128 | "-ms-transition-timing-function", 129 | "-o-transition-timing-function", 130 | "transition-timing-function", 131 | "-webkit-transition-duration", 132 | "-moz-transition-duration", 133 | "-ms-transition-duration", 134 | "-o-transition-duration", 135 | "transition-duration", 136 | "-webkit-transition-property", 137 | "-moz-transition-property", 138 | "-ms-transition-property", 139 | "-o-transition-property", 140 | "transition-property", 141 | "-webkit-transform", 142 | "-moz-transform", 143 | "-ms-transform", 144 | "-o-transform", 145 | "transform", 146 | "-webkit-transform-origin", 147 | "-moz-transform-origin", 148 | "-ms-transform-origin", 149 | "-o-transform-origin", 150 | "transform-origin", 151 | "-webkit-animation", 152 | "-moz-animation", 153 | "-ms-animation", 154 | "-o-animation", 155 | "animation", 156 | "-webkit-animation-name", 157 | "-moz-animation-name", 158 | "-ms-animation-name", 159 | "-o-animation-name", 160 | "animation-name", 161 | "-webkit-animation-duration", 162 | "-moz-animation-duration", 163 | "-ms-animation-duration", 164 | "-o-animation-duration", 165 | "animation-duration", 166 | "-webkit-animation-play-state", 167 | "-moz-animation-play-state", 168 | "-ms-animation-play-state", 169 | "-o-animation-play-state", 170 | "animation-play-state", 171 | "-webkit-animation-timing-function", 172 | "-moz-animation-timing-function", 173 | "-ms-animation-timing-function", 174 | "-o-animation-timing-function", 175 | "animation-timing-function", 176 | "-webkit-animation-delay", 177 | "-moz-animation-delay", 178 | "-ms-animation-delay", 179 | "-o-animation-delay", 180 | "animation-delay", 181 | "-webkit-animation-iteration-count", 182 | "-moz-animation-iteration-count", 183 | "-ms-animation-iteration-count", 184 | "-o-animation-iteration-count", 185 | "animation-iteration-count", 186 | "-webkit-animation-direction", 187 | "-moz-animation-direction", 188 | "-ms-animation-direction", 189 | "-o-animation-direction", 190 | "animation-direction", 191 | "text-align", 192 | "-webkit-text-align-last", 193 | "-moz-text-align-last", 194 | "-ms-text-align-last", 195 | "text-align-last", 196 | "vertical-align", 197 | "white-space", 198 | "text-decoration", 199 | "text-emphasis", 200 | "text-emphasis-color", 201 | "text-emphasis-style", 202 | "text-emphasis-position", 203 | "text-indent", 204 | "-ms-text-justify", 205 | "text-justify", 206 | "letter-spacing", 207 | "word-spacing", 208 | "-ms-writing-mode", 209 | "text-outline", 210 | "text-transform", 211 | "text-wrap", 212 | "text-overflow", 213 | "-ms-text-overflow", 214 | "text-overflow-ellipsis", 215 | "text-overflow-mode", 216 | "-ms-word-wrap", 217 | "word-wrap", 218 | "word-break", 219 | "-ms-word-break", 220 | "-moz-tab-size", 221 | "-o-tab-size", 222 | "tab-size", 223 | "-webkit-hyphens", 224 | "-moz-hyphens", 225 | "hyphens", 226 | "pointer-events" 227 | ], 228 | [ 229 | "opacity", 230 | "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity", 231 | "-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha", 232 | "-ms-interpolation-mode", 233 | "color", 234 | "border", 235 | "border-width", 236 | "border-style", 237 | "border-color", 238 | "border-top", 239 | "border-top-width", 240 | "border-top-style", 241 | "border-top-color", 242 | "border-right", 243 | "border-right-width", 244 | "border-right-style", 245 | "border-right-color", 246 | "border-bottom", 247 | "border-bottom-width", 248 | "border-bottom-style", 249 | "border-bottom-color", 250 | "border-left", 251 | "border-left-width", 252 | "border-left-style", 253 | "border-left-color", 254 | "-webkit-border-radius", 255 | "-moz-border-radius", 256 | "border-radius", 257 | "-webkit-border-top-left-radius", 258 | "-moz-border-radius-topleft", 259 | "border-top-left-radius", 260 | "-webkit-border-top-right-radius", 261 | "-moz-border-radius-topright", 262 | "border-top-right-radius", 263 | "-webkit-border-bottom-right-radius", 264 | "-moz-border-radius-bottomright", 265 | "border-bottom-right-radius", 266 | "-webkit-border-bottom-left-radius", 267 | "-moz-border-radius-bottomleft", 268 | "border-bottom-left-radius", 269 | "-webkit-border-image", 270 | "-moz-border-image", 271 | "-o-border-image", 272 | "border-image", 273 | "-webkit-border-image-source", 274 | "-moz-border-image-source", 275 | "-o-border-image-source", 276 | "border-image-source", 277 | "-webkit-border-image-slice", 278 | "-moz-border-image-slice", 279 | "-o-border-image-slice", 280 | "border-image-slice", 281 | "-webkit-border-image-width", 282 | "-moz-border-image-width", 283 | "-o-border-image-width", 284 | "border-image-width", 285 | "-webkit-border-image-outset", 286 | "-moz-border-image-outset", 287 | "-o-border-image-outset", 288 | "border-image-outset", 289 | "-webkit-border-image-repeat", 290 | "-moz-border-image-repeat", 291 | "-o-border-image-repeat", 292 | "border-image-repeat", 293 | "outline", 294 | "outline-width", 295 | "outline-style", 296 | "outline-color", 297 | "outline-offset", 298 | "background", 299 | "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader", 300 | "background-color", 301 | "background-image", 302 | "background-repeat", 303 | "background-attachment", 304 | "background-position", 305 | "background-position-x", 306 | "-ms-background-position-x", 307 | "background-position-y", 308 | "-ms-background-position-y", 309 | "-webkit-background-clip", 310 | "-moz-background-clip", 311 | "background-clip", 312 | "background-origin", 313 | "-webkit-background-size", 314 | "-moz-background-size", 315 | "-o-background-size", 316 | "background-size", 317 | "box-decoration-break", 318 | "-webkit-box-shadow", 319 | "-moz-box-shadow", 320 | "box-shadow", 321 | "filter:progid:DXImageTransform.Microsoft.gradient", 322 | "-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient", 323 | "text-shadow" 324 | ] 325 | ] 326 | } 327 | --------------------------------------------------------------------------------