├── 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 |
2 |
3 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/module1/views/demo/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | module1__demo
4 |
5 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
13 |
14 |
15 |
16 |
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 |
2 |
4 |
5 |
17 |
18 |
Loading
19 |
20 |
21 |
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 |
--------------------------------------------------------------------------------