├── .stylelintignore ├── .browserslistrc ├── .gitattributes ├── src ├── components │ ├── Chart │ │ ├── README.md │ │ ├── MiniBar.vue │ │ ├── Bar.vue │ │ ├── MiniArea.vue │ │ ├── MiniSearchArea.vue │ │ ├── RankList.vue │ │ ├── SalePercentCard.vue │ │ ├── MiniProgress.vue │ │ ├── ChartCard.vue │ │ ├── HotSearchCard.vue │ │ ├── TimelineChart.vue │ │ └── Pie.vue │ ├── Layout │ │ ├── index.js │ │ ├── ViewLayout.vue │ │ ├── App │ │ │ ├── LayoutFooter.vue │ │ │ ├── LayoutSider.vue │ │ │ ├── LayoutHeader.vue │ │ │ └── Layout.vue │ │ └── AppLayout.vue │ ├── Trend │ │ └── index.vue │ ├── Logo │ │ └── index.vue │ ├── ValidateCode │ │ └── index.vue │ ├── DetailList │ │ └── DetailList.vue │ ├── Setting │ │ ├── tools.js │ │ └── index.vue │ ├── ModifyPassword │ │ └── index.vue │ ├── Actions │ │ └── index.vue │ └── Menu │ │ └── index.vue ├── assets │ ├── images │ │ ├── 404.png │ │ ├── 500.png │ │ ├── excel.png │ │ ├── logo.png │ │ ├── login_bg.png │ │ ├── macbook-gold.png │ │ └── excel.svg │ └── styles │ │ ├── mixins.scss │ │ ├── variable.scss │ │ ├── index.scss │ │ ├── base.scss │ │ └── common.scss ├── plugins │ ├── d3.js │ ├── v-charts.js │ ├── viser.js │ ├── index.js │ ├── vue-ls.js │ └── ant-design-vue.js ├── views │ ├── dashboard │ │ ├── park.png │ │ ├── monitor.vue │ │ ├── components │ │ │ ├── CustomTab.vue │ │ │ ├── OfflineData.vue │ │ │ └── NumberInfo.vue │ │ ├── track.vue │ │ ├── config.js │ │ ├── vcharts.vue │ │ └── analysis.vue │ ├── d3 │ │ ├── line.vue │ │ ├── bar.vue │ │ └── components │ │ │ └── Bar │ │ │ ├── Transition.vue │ │ │ ├── TransitionOther.vue │ │ │ └── BarChart.vue │ ├── g2 │ │ ├── line.vue │ │ └── bar.vue │ ├── error │ │ ├── 404.vue │ │ └── 500.vue │ ├── table │ │ ├── components │ │ │ └── AccountModal.vue │ │ └── table.vue │ └── auth │ │ └── login.vue ├── mixins │ ├── index.js │ ├── device.js │ ├── queryForm.js │ ├── rangePicker.js │ ├── table.js │ └── appStore.js ├── api │ ├── auth.js │ └── form.js ├── store │ ├── modules │ │ ├── permission.js │ │ ├── user.js │ │ └── app.js │ ├── config.js │ ├── index.js │ ├── mutation-types.js │ └── initStore.js ├── utils │ ├── index.js │ ├── time.js │ ├── optimize.js │ ├── device.js │ └── request.js ├── main.js ├── App.vue ├── filters │ └── index.js └── router │ ├── index.js │ └── routes.js ├── public ├── favicon.ico └── index.html ├── tests ├── unit │ ├── .eslintrc.js │ └── example.spec.js └── e2e │ ├── specs │ └── test.js │ └── custom-assertions │ └── elementCount.js ├── postcss.config.js ├── .prettierrc ├── .editorconfig ├── deploy.sh ├── babel.config.js ├── .travis.yml ├── lint-staged.config.js ├── .gitignore ├── .stylelintrc.js ├── .eslintrc.js ├── server ├── package.json ├── db.json └── index.js ├── jest.config.js ├── vue.config.js ├── README.zh-CN.md ├── package.json └── README.md /.stylelintignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/dist -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.less linguist-language=Vue 2 | -------------------------------------------------------------------------------- /src/components/Chart/README.md: -------------------------------------------------------------------------------- 1 | https://github.com/ant-design/pro-blocks -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luichooy/vue-antd-pro/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/assets/images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luichooy/vue-antd-pro/HEAD/src/assets/images/404.png -------------------------------------------------------------------------------- /src/assets/images/500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luichooy/vue-antd-pro/HEAD/src/assets/images/500.png -------------------------------------------------------------------------------- /src/plugins/d3.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import * as d3 from 'd3' 3 | 4 | Vue.prototype.$d3 = d3 5 | -------------------------------------------------------------------------------- /src/plugins/v-charts.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VCharts from 'v-charts' 3 | Vue.use(VCharts) 4 | -------------------------------------------------------------------------------- /src/plugins/viser.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Viser from 'viser-vue' 3 | 4 | Vue.use(Viser) 5 | -------------------------------------------------------------------------------- /src/assets/images/excel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luichooy/vue-antd-pro/HEAD/src/assets/images/excel.png -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luichooy/vue-antd-pro/HEAD/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/views/dashboard/park.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luichooy/vue-antd-pro/HEAD/src/views/dashboard/park.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "useTabs": true 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/images/login_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luichooy/vue-antd-pro/HEAD/src/assets/images/login_bg.png -------------------------------------------------------------------------------- /src/assets/styles/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin hover-focus { 2 | &:hover, 3 | &:focus { 4 | @content; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/images/macbook-gold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luichooy/vue-antd-pro/HEAD/src/assets/images/macbook-gold.png -------------------------------------------------------------------------------- /src/assets/styles/variable.scss: -------------------------------------------------------------------------------- 1 | $spaces: ( 2 | 1: 4px, 3 | 2: 8px, 4 | 3: 16px, 5 | 4: 32px, 6 | 5: 48px 7 | ); 8 | -------------------------------------------------------------------------------- /src/views/dashboard/monitor.vue: -------------------------------------------------------------------------------- 1 | 4 | 7 | -------------------------------------------------------------------------------- /src/plugins/index.js: -------------------------------------------------------------------------------- 1 | import './ant-design-vue' 2 | import './vue-ls' 3 | import './viser' 4 | import './v-charts' 5 | import './d3' 6 | -------------------------------------------------------------------------------- /src/components/Layout/index.js: -------------------------------------------------------------------------------- 1 | export { default as ViewLayout } from './ViewLayout' 2 | 3 | export { default as AppLayout } from './AppLayout' 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /src/assets/styles/index.scss: -------------------------------------------------------------------------------- 1 | //@import "iconfont.css"; 2 | @import 'variable.scss'; 3 | @import 'mixins.scss'; 4 | @import 'base.scss'; 5 | @import 'common.scss'; 6 | -------------------------------------------------------------------------------- /src/views/d3/line.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 14 | -------------------------------------------------------------------------------- /src/views/g2/line.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 14 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 确保脚本抛出遇到的错误 4 | set -e 5 | 6 | # 项目打包 7 | yarn build --report 8 | 9 | # 删除服务器上的代码 10 | ssh root@192.0.0.0 "rm -rf /opt/app/vue-antd-pro/*" 11 | 12 | # 上传到服务器 13 | scp -r ./dist/* root@192.0.0.0:/opt/app/vue-antd-pro -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/app'], 3 | plugins: [ 4 | [ 5 | 'import', 6 | { 7 | libraryName: 'ant-design-vue', 8 | libraryDirectory: 'es', 9 | style: true 10 | } 11 | ] 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js # 设置语言 2 | 3 | node_js: '10.16.3' # 设置语言版本 4 | 5 | cache: 6 | directories: 7 | - node_modules # 缓存依赖 8 | 9 | # start: build lifecycle 10 | install: 11 | - yarn 12 | 13 | script: 14 | - yarn build 15 | 16 | branches: 17 | only: 18 | - master 19 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.{js,jsx,vue}': ['yarn lint', 'git add'], 3 | '*.{css}': ['stylelint --fix', 'git add'], 4 | '*.{scss}': ['stylelint --syntax=scss --fix', 'git add'], 5 | '*.{vue}': ['stylelint --fix', 'git add'], 6 | '*.{json}': ['prettier --write', 'git add'] 7 | } 8 | -------------------------------------------------------------------------------- /src/mixins/index.js: -------------------------------------------------------------------------------- 1 | export { default as tableMixin } from './table' 2 | 3 | export { default as queryFormMixin } from './queryForm' 4 | 5 | export { default as appStoreMixin } from './appStore' 6 | 7 | export { default as rangePickerMixin } from './rangePicker' 8 | 9 | export { default as deviceMixin } from './device' 10 | -------------------------------------------------------------------------------- /src/api/auth.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function login (params) { 4 | return request.get('/login') 5 | } 6 | 7 | export function logout () { 8 | return request.get('/logout') 9 | } 10 | 11 | export function modifyPassword (params) { 12 | return request.post('/modifyPassword', params) 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/reports/ 6 | selenium-debug.log 7 | 8 | # local env files 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /src/store/modules/permission.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { MENUS } from '../mutation-types' 3 | 4 | const permission = { 5 | namespaced: true, 6 | state: { 7 | access: [], 8 | menus: [] 9 | }, 10 | 11 | mutations: { 12 | SET_MENUS (state, menus) { 13 | Vue.ss.set(MENUS, menus) 14 | state.menus = menus 15 | } 16 | } 17 | } 18 | 19 | export default permission 20 | -------------------------------------------------------------------------------- /tests/unit/example.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import HelloWorld from '@/components/HelloWorld.vue' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('renders props.msg when passed', () => { 6 | const msg = 'new message' 7 | const wrapper = shallowMount(HelloWorld, { 8 | propsData: { msg } 9 | }) 10 | expect(wrapper.text()).toMatch(msg) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'stylelint-config-standard', 3 | plugins: [], 4 | processors: [], 5 | // defaultSeverity: 'error', //指定应用到所有rules上的严格程度,可选值有‘warning','error' 6 | rules: { 7 | 'font-family-no-missing-generic-family-keyword': null, 8 | 'no-empty-source': null, 9 | 'no-eol-whitespace': null, 10 | // 解决 vue 深度选择器 ::v-deep 11 | 'selector-pseudo-element-no-unknown': null 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/mixins/device.js: -------------------------------------------------------------------------------- 1 | import { mapState } from 'vuex' 2 | import { DEVICE_TYPE } from '@/utils/device' 3 | 4 | export default { 5 | computed: { 6 | ...mapState('app', ['device']), 7 | isDesktop () { 8 | return this.device === DEVICE_TYPE.DESKTOP 9 | }, 10 | isTablat () { 11 | return this.device === DEVICE_TYPE.TABLET 12 | }, 13 | isMobile () { 14 | return this.device === DEVICE_TYPE.MOBILE 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': browser => { 6 | browser 7 | .url(process.env.VUE_DEV_SERVER_URL) 8 | .waitForElementVisible('#app', 5000) 9 | .assert.elementPresent('.hello') 10 | .assert.containsText('h1', 'Welcome to Your Vue.js App') 11 | .assert.elementCount('img', 1) 12 | .end() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/mixins/queryForm.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data () { 3 | return { 4 | // 控制查询条件折叠 5 | formFold: true, 6 | formItemLayout: { 7 | labelCol: { 8 | md: { span: 6 }, 9 | sm: { span: 4 } 10 | }, 11 | wrapperCol: { 12 | md: { span: 18 }, 13 | sm: { span: 20 } 14 | } 15 | } 16 | } 17 | }, 18 | methods: { 19 | toggleFold () { 20 | this.formFold = !this.formFold 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/assets/styles/base.scss: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100vh; 3 | font-family: Chinese Quote, -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol; 4 | scroll-behavior: smooth; 5 | 6 | &.color-weak { 7 | filter: invert(80%); 8 | } 9 | } 10 | 11 | h1, h2, h3, h4, h5, h6, p, ul, ol { 12 | margin: 0; 13 | padding: 0; 14 | } 15 | 16 | ul, ol { 17 | list-style: none; 18 | } 19 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: ['plugin:vue/essential', '@vue/standard'], 7 | rules: { 8 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 9 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 10 | 'no-trailing-spaces': 'off', 11 | semi: ['error', 'never'], 12 | 'no-tabs': 'off', 13 | 'template-curly-spacing': 'off' 14 | }, 15 | parserOptions: { 16 | parser: 'babel-eslint' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | import { Base64 } from 'js-base64' 2 | 3 | const salt1 = 'VUE-ANTD-PRO' 4 | const salt2 = 'FRONTEND' 5 | 6 | export function encryptpwd (pwd) { 7 | return Base64.encode(salt1 + pwd + salt2) 8 | } 9 | 10 | export function generateOpenKeys (keyPath) { 11 | const openKeys = [] 12 | for (let i = 0, len = keyPath.length; i < len; i++) { 13 | let subMenu = '' 14 | for (let j = 0; j <= i; j++) { 15 | subMenu += '/' + keyPath[j] 16 | } 17 | openKeys.push(subMenu) 18 | } 19 | 20 | return openKeys 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/time.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | 3 | export function timeFix () { 4 | const time = new Date() 5 | const hour = time.getHours() 6 | return hour < 9 7 | ? '早上好' 8 | : hour <= 11 9 | ? '上午好' 10 | : hour <= 13 11 | ? '中午好' 12 | : hour < 20 13 | ? '下午好' 14 | : '晚上好' 15 | } 16 | 17 | export function getCurrent (key) { 18 | const current = moment() 19 | return [ 20 | current[key](current[key]()).startOf(key), 21 | current[key](current[key]()).endOf(key) 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "User json-server as backend API of vue-antd-pro ", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "json-server --watch index.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [ 11 | "json-server", 12 | "backend", 13 | "API" 14 | ], 15 | "author": "luichooy@163.com", 16 | "license": "ISC", 17 | "dependencies": { 18 | "casual": "^1.6.2", 19 | "json-server": "^0.15.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router/index' 4 | import { store, initStore } from './store/index' 5 | import './plugins' 6 | import * as filters from './filters' 7 | import './assets/styles/index.scss' 8 | 9 | Vue.config.productionTip = false 10 | 11 | /* 定义全局过滤器 */ 12 | Object.keys(filters).forEach(key => { 13 | Vue.filter(key, filters[key]) 14 | }) 15 | 16 | new Vue({ 17 | router, 18 | store, 19 | render: h => h(App), 20 | created () { 21 | initStore() 22 | } 23 | }).$mount('#app') 24 | -------------------------------------------------------------------------------- /src/views/d3/bar.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 24 | -------------------------------------------------------------------------------- /src/plugins/vue-ls.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Storage from 'vue-ls' 3 | 4 | /* 5 | * 同时使用 loal 和 session 6 | * https://github.com/RobinCK/vue-ls/issues/83 7 | * */ 8 | 9 | const SessionStorage = { 10 | install (Vue, options = {}) { 11 | const _options = Object.assign({}, options, { 12 | storage: options.storage || 'session', 13 | name: options.name || 'ss' 14 | }) 15 | 16 | Storage.install(Vue, _options) 17 | } 18 | } 19 | 20 | Vue.use(Storage, { namespace: 'VAPRO__' }) 21 | Vue.use(SessionStorage, { namespace: 'VAPRO__' }) 22 | -------------------------------------------------------------------------------- /src/components/Layout/ViewLayout.vue: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | -------------------------------------------------------------------------------- /src/utils/optimize.js: -------------------------------------------------------------------------------- 1 | export function throttle (fn, delay) { 2 | delay = delay || 300 3 | let prev = 0 4 | 5 | return function (args) { 6 | const context = this 7 | let now = Date.now() 8 | if (now - prev > delay) { 9 | fn.call(context, args) 10 | prev = now 11 | } 12 | } 13 | } 14 | 15 | export function debounce (fn, delay) { 16 | delay = delay || 300 17 | let timer 18 | 19 | return function (args) { 20 | const context = this 21 | if (timer) clearTimeout(timer) 22 | 23 | timer = setTimeout(() => { 24 | fn.call(context, args) 25 | }, delay) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vue-antd-pro 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/components/Layout/App/LayoutFooter.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | 18 | 30 | -------------------------------------------------------------------------------- /src/mixins/rangePicker.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | 3 | export default { 4 | data () { 5 | return { 6 | rangeDate: [moment().subtract(6, 'days'), moment()] 7 | } 8 | }, 9 | watch: { 10 | rangeDate: { 11 | handler: function (momentArr) { 12 | this.form.startTime = momentArr[0].format('YYYY-MM-DD 00:00:00') 13 | this.form.endTime = momentArr[1].format('YYYY-MM-DD 23:59:59') 14 | }, 15 | immediate: true 16 | } 17 | }, 18 | methods: { 19 | handleResetForm () { 20 | this.form = {} 21 | this.rangeDate = [moment().subtract(6, 'days'), moment()] 22 | }, 23 | moment 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/store/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | app: { 3 | menuTheme: 'dark', // theme for nav menu 4 | color: '#1890FF', // primary color of ant design 5 | layout: 'side', // nav menu position: sidemenu or topmenu 6 | contentWidth: 'fixed', // layout of content: Fluid or Fixed, only works when layout is topmenu 7 | fixedHeader: true, // sticky header 8 | fixSiderbar: true, // sticky siderbar 9 | autoHideHeader: false, // auto hide header 10 | colorWeak: false, 11 | multiTab: false 12 | }, 13 | 14 | user: { 15 | token: '', 16 | user: null, 17 | routes: null 18 | }, 19 | 20 | permission: { 21 | menus: null 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/Chart/MiniBar.vue: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import createLogger from 'vuex/dist/logger' 4 | 5 | import app from './modules/app' 6 | import user from './modules/user' 7 | import permission from './modules/permission' 8 | 9 | export { default as initStore } from './initStore' 10 | 11 | Vue.use(Vuex) 12 | 13 | const debug = process.env.NODE_ENV !== 'production' 14 | 15 | export const store = new Vuex.Store({ 16 | modules: { 17 | app, 18 | user, 19 | permission 20 | }, 21 | state: {}, 22 | mutations: {}, 23 | actions: {}, 24 | getters: {}, 25 | strict: debug, 26 | plugins: debug ? [createLogger()] : [] 27 | }) 28 | 29 | export default store 30 | -------------------------------------------------------------------------------- /src/api/form.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getUsers (params) { 4 | return request.get(`/users?_sort=updateTime&_order=desc&_page=${params.current}&_limit=${params.pageSize}`) 5 | } 6 | 7 | export function deleteAccount (id) { 8 | return request.delete(`/users/${id}`) 9 | } 10 | 11 | export function createAccount (params) { 12 | params.createTime = Date.now() 13 | params.updateTime = Date.now() 14 | return request.post('/users', params) 15 | } 16 | 17 | export function modifyAccount (params) { 18 | params.updateTime = Date.now() 19 | const { id, ...user } = params 20 | return request.patch(`/users/${id}`, user) 21 | } 22 | 23 | export function getRoles () { 24 | return request.get('/roles') 25 | } 26 | -------------------------------------------------------------------------------- /tests/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // The assertion name is the filename. 3 | // Example usage: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // For more information on custom assertions see: 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | 10 | exports.assertion = function elementCount (selector, count) { 11 | this.message = `Testing if element <${selector}> has count: ${count}` 12 | this.expected = count 13 | this.pass = val => val === count 14 | this.value = res => res.value 15 | function evaluator (_selector) { 16 | return document.querySelectorAll(_selector).length 17 | } 18 | this.command = cb => this.api.execute(evaluator, [selector], cb) 19 | } 20 | -------------------------------------------------------------------------------- /src/views/g2/bar.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 34 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | 'js', 4 | 'jsx', 5 | 'json', 6 | 'vue' 7 | ], 8 | transform: { 9 | '^.+\\.vue$': 'vue-jest', 10 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 11 | '^.+\\.jsx?$': 'babel-jest' 12 | }, 13 | transformIgnorePatterns: [ 14 | '/node_modules/' 15 | ], 16 | moduleNameMapper: { 17 | '^@/(.*)$': '/src/$1' 18 | }, 19 | snapshotSerializers: [ 20 | 'jest-serializer-vue' 21 | ], 22 | testMatch: [ 23 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 24 | ], 25 | testURL: 'http://localhost/', 26 | watchPlugins: [ 27 | 'jest-watch-typeahead/filename', 28 | 'jest-watch-typeahead/testname' 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /server/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": [ 3 | { 4 | "id": 1, 5 | "title": "json-server", 6 | "author": "typicode" 7 | } 8 | ], 9 | "comments": [ 10 | { 11 | "id": 1, 12 | "body": "some comment", 13 | "postId": 1 14 | } 15 | ], 16 | "profile": { 17 | "name": "typicode" 18 | }, 19 | "users": [ 20 | { 21 | "id": "V20190324121645AP", 22 | "username": "常山赵子龙", 23 | "contacts": "赵子龙", 24 | "contactsEmail": "zhaozilong@qq.com", 25 | "provinceName": "河北省", 26 | "cityName": "石家庄市", 27 | "areaName": "", 28 | "address": "", 29 | "roleId": "", 30 | "roleName": "管理员", 31 | "status": 1, 32 | "createTime": "2019-03-24 15:16:45", 33 | "updateTime": "2019-03-24 15:16:45" 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /src/components/Layout/AppLayout.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 21 | 22 | 37 | -------------------------------------------------------------------------------- /src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const SYSTEM_MENU_THEME = 'DEFAULT_THEME' 2 | export const SYSTEM_COLOR = 'DEFAULT_COLOR' 3 | export const SYSTEM_LAYOUT = 'SYSTEM_LAYOUT' 4 | export const SYSTEM_CONTENT_WIDTH = 'DEFAULT_CONTENT_WIDTH' 5 | export const SYSTEM_FIXED_HEADER = 'DEFAULT_FIXED_HEADER' 6 | export const SYSTEM_FIXED_SIDERBAR = 'DEFAULT_FIXED_SIDERBAR' 7 | export const SYSTEM_AUTOHIDE_HEADER = 'DEFAULT_AUTOHIDE_HEADER' 8 | export const SYSTEM_COLORWEAK = 'SYSTEM_COLORWEAK' 9 | 10 | export const ACCESS_TOKEN = 'Authorization' 11 | export const USER = 'USER' 12 | 13 | export const MENUS = 'MENUS' 14 | 15 | export const SIDEBAR_TYPE = 'SIDEBAR_TYPE' 16 | 17 | export const DEFAULT_MULTI_TAB = 'DEFAULT_MULTI_TAB' 18 | 19 | export const CONTENT_WIDTH_TYPE = { 20 | Fluid: 'Fluid', 21 | Fixed: 'Fixed' 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Chart/Bar.vue: -------------------------------------------------------------------------------- 1 | 38 | -------------------------------------------------------------------------------- /src/utils/device.js: -------------------------------------------------------------------------------- 1 | import enquireJs from 'enquire.js' 2 | 3 | export const DEVICE_TYPE = { 4 | DESKTOP: 'desktop', 5 | TABLET: 'tablet', 6 | MOBILE: 'mobile' 7 | } 8 | 9 | export const deviceEnquire = function (callback) { 10 | const matchDesktop = { 11 | match: () => { 12 | callback && callback(DEVICE_TYPE.DESKTOP) 13 | } 14 | } 15 | 16 | const matchTablet = { 17 | match: () => { 18 | callback && callback(DEVICE_TYPE.TABLET) 19 | } 20 | } 21 | 22 | const matchMobile = { 23 | match: () => { 24 | callback && callback(DEVICE_TYPE.MOBILE) 25 | } 26 | } 27 | 28 | enquireJs 29 | .register('screen and (min-width: 1200px)', matchDesktop) 30 | .register( 31 | 'screen and (min-width: 576px) and (max-width: 1200px)', 32 | matchTablet 33 | ) 34 | .register('screen and (max-width: 576px)', matchMobile) 35 | } 36 | -------------------------------------------------------------------------------- /src/mixins/table.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data () { 3 | return { 4 | // 表头数据 5 | columns: null, 6 | // 行数据 7 | rows: null, 8 | // table loading 9 | tableLoading: false, 10 | // table 分页 11 | pagination: { 12 | current: 1, 13 | pageSize: 10, 14 | total: 0, 15 | showSizeChanger: true, 16 | showTotal (total, range) { 17 | return `共${total}条` 18 | } 19 | } 20 | } 21 | }, 22 | computed: { 23 | serial () { 24 | const { 25 | pagination: { current, pageSize } 26 | } = this 27 | return (current - 1) * pageSize 28 | } 29 | }, 30 | methods: { 31 | handleTableChange (pagination, filters, sorter) { 32 | this.pagination.current = pagination.current 33 | this.pagination.pageSize = pagination.pageSize 34 | this.search() 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/mixins/appStore.js: -------------------------------------------------------------------------------- 1 | import { mapState, mapActions } from 'vuex' 2 | 3 | const appStore = { 4 | computed: { 5 | ...mapState('app', { 6 | device: state => state.device, 7 | menuTheme: state => state.menuTheme, 8 | defaultColor: state => state.color, 9 | layoutMode: state => state.layout, 10 | contentWidth: state => state.contentWidth, 11 | fixedHeader: state => state.fixedHeader, 12 | fixedSiderbar: state => state.fixedSiderbar, 13 | autoHideHeader: state => state.autoHideHeader, 14 | colorWeak: state => state.colorWeak 15 | }) 16 | }, 17 | methods: { 18 | ...mapActions('app', [ 19 | 'set_menuTheme', 20 | 'set_color', 21 | 'set_layout', 22 | 'set_contentWidth', 23 | 'set_fixedHeader', 24 | 'set_fixedSiderbar', 25 | 'set_autoHideHeader', 26 | 'set_colorWeak' 27 | ]) 28 | } 29 | } 30 | 31 | export default appStore 32 | -------------------------------------------------------------------------------- /src/filters/index.js: -------------------------------------------------------------------------------- 1 | function padLeftZero (str) { 2 | return ('00' + str).substr(str.length) 3 | } 4 | 5 | export function formatDate (date, fmt) { 6 | if (/(y+)/.test(fmt)) { 7 | fmt = fmt.replace( 8 | RegExp.$1, 9 | (date.getFullYear() + '').substr(4 - RegExp.$1.length) 10 | ) 11 | } 12 | if (/(s+)/.test(fmt)) { 13 | fmt = fmt.replace( 14 | RegExp.$1, 15 | (date.getMilliseconds() + '').substring(0, RegExp.$1.length) 16 | ) 17 | } 18 | const o = { 19 | 'M+': date.getMonth() + 1, 20 | 'd+': date.getDate(), 21 | 'h+': date.getHours(), 22 | 'm+': date.getMinutes(), 23 | 'S+': date.getSeconds() 24 | } 25 | for (let k in o) { 26 | if (new RegExp(`(${k})`).test(fmt)) { 27 | let str = o[k] + '' 28 | fmt = fmt.replace( 29 | RegExp.$1, 30 | RegExp.$1.length === 1 ? str : padLeftZero(str) 31 | ) 32 | } 33 | } 34 | return fmt 35 | } 36 | 37 | export const noDataFilter = text => 38 | text === (null || undefined || '') ? '---' : text 39 | -------------------------------------------------------------------------------- /src/views/dashboard/components/CustomTab.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 40 | 41 | 44 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import NProgress from 'nprogress' 4 | import 'nprogress/nprogress.css' // progress bar style 5 | import routes from './routes' 6 | import { ACCESS_TOKEN } from '@/store/mutation-types' 7 | 8 | Vue.use(Router) 9 | NProgress.configure({ showSpinner: false }) // NProgress Configuration 10 | 11 | const whiteList = ['login', '404'] // no redirect whitelist 12 | 13 | export const createRouter = () => 14 | new Router({ 15 | mode: 'history', 16 | base: process.env.BASE_URL, 17 | routes 18 | }) 19 | 20 | const router = createRouter() 21 | 22 | router.beforeEach((to, from, next) => { 23 | NProgress.start() 24 | const token = Vue.ss.get(ACCESS_TOKEN) 25 | if (token) { 26 | if (to.name === 'login') { 27 | next('/') 28 | } else { 29 | next() 30 | } 31 | } else { 32 | if (whiteList.includes(to.name)) { 33 | next() 34 | } else { 35 | next({ path: '/login' }) 36 | } 37 | } 38 | }) 39 | 40 | router.afterEach(() => { 41 | NProgress.done() // finish progress bar 42 | }) 43 | 44 | export default router 45 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | // const webpack = require('webpack'); 3 | 4 | function resolve (dir) { 5 | return path.join(__dirname, dir) 6 | } 7 | 8 | // vue.config.js 9 | module.exports = { 10 | publicPath: '/', 11 | 12 | transpileDependencies: ['resize-detector', 'ant-design-vue'], 13 | 14 | // configureWebpack: { 15 | // plugins: [ 16 | // // Ignore all locale files of moment.js 17 | // new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/) 18 | // ] 19 | // }, 20 | 21 | chainWebpack: config => { 22 | config.resolve.alias 23 | .set('public', resolve('public')) 24 | .set('@', resolve('src')) 25 | .set('@api', resolve('src/api')) 26 | .set('@assets', resolve('src/assets')) 27 | .set('@comp', resolve('src/components')) 28 | .set('@views', resolve('src/views')) 29 | }, 30 | 31 | devServer: { 32 | proxy: { 33 | '/api': { 34 | target: 'http://localhost:3000', // 后端服务器 35 | ws: false, 36 | changeOrigin: true, 37 | pathRewrite: { 38 | '^/api': '' 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Chart/MiniArea.vue: -------------------------------------------------------------------------------- 1 | 46 | -------------------------------------------------------------------------------- /src/components/Layout/App/LayoutSider.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 36 | 37 | 60 | -------------------------------------------------------------------------------- /src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { login as userLogin, logout as userLogout } from '@/api/auth' 3 | import { ACCESS_TOKEN, USER } from '@/store/mutation-types' 4 | 5 | const user = { 6 | namespaced: true, 7 | state: { 8 | token: '', 9 | user: null 10 | }, 11 | getters: { 12 | username (state) { 13 | return (state.user && state.user.username) || '' 14 | } 15 | }, 16 | 17 | mutations: { 18 | SET_TOKEN: (state, token) => { 19 | Vue.ss.set(ACCESS_TOKEN, token, 7 * 24 * 60 * 60 * 1000) 20 | state.token = token 21 | }, 22 | SET_USER: (state, user) => { 23 | Vue.ss.set(USER, user) 24 | state.user = user 25 | } 26 | }, 27 | 28 | actions: { 29 | // 登录 30 | Login ({ commit, dispatch }, userInfo) { 31 | return new Promise(async (resolve, reject) => { 32 | const res = await userLogin(userInfo) 33 | if (res.status === 200) { 34 | commit('SET_TOKEN', res.data.token) 35 | commit('SET_USER', res.data.user) 36 | commit('permission/SET_MENUS', res.data.menus, { 37 | root: true 38 | }) 39 | resolve(res) 40 | } else { 41 | reject(res) 42 | } 43 | }) 44 | }, 45 | Logout ({ commit, state }) { 46 | return new Promise(async resolve => { 47 | await userLogout() 48 | commit('SET_TOKEN', '') 49 | Vue.ss.remove(ACCESS_TOKEN) 50 | resolve() 51 | }) 52 | } 53 | } 54 | } 55 | 56 | export default user 57 | -------------------------------------------------------------------------------- /src/components/Trend/index.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 69 | -------------------------------------------------------------------------------- /src/components/Chart/MiniSearchArea.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 36 | 37 | 64 | -------------------------------------------------------------------------------- /src/plugins/ant-design-vue.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { 3 | Avatar, 4 | Button, 5 | Card, 6 | Col, 7 | DatePicker, 8 | Divider, 9 | Dropdown, 10 | Form, 11 | Icon, 12 | Input, 13 | Radio, 14 | Layout, 15 | List, 16 | LocaleProvider, 17 | message, 18 | Menu, 19 | Modal, 20 | notification, 21 | Pagination, 22 | Popover, 23 | Row, 24 | Select, 25 | Spin, 26 | Table, 27 | Transfer, 28 | TimePicker, 29 | Tooltip, 30 | Upload, 31 | Drawer, 32 | Skeleton, 33 | Progress, 34 | Tag, 35 | Switch, 36 | Tabs 37 | } from 'ant-design-vue' 38 | 39 | Vue.prototype.$message = message 40 | Vue.prototype.$notification = notification 41 | Vue.prototype.$info = Modal.info 42 | Vue.prototype.$success = Modal.success 43 | Vue.prototype.$error = Modal.error 44 | Vue.prototype.$warning = Modal.warning 45 | Vue.prototype.$confirm = Modal.confirm 46 | 47 | /* v1.1.3+ registration methods */ 48 | Vue.use(Avatar) 49 | Vue.use(Button) 50 | Vue.use(Card) 51 | Vue.use(Col) 52 | Vue.use(DatePicker) 53 | Vue.use(Divider) 54 | Vue.use(Drawer) 55 | Vue.use(Dropdown) 56 | Vue.use(Form) 57 | Vue.use(Icon) 58 | Vue.use(Input) 59 | Vue.use(Radio) 60 | Vue.use(Radio.Group) 61 | Vue.use(Layout) 62 | Vue.use(List) 63 | Vue.use(LocaleProvider) 64 | Vue.use(Menu) 65 | Vue.use(Modal) 66 | Vue.use(Pagination) 67 | Vue.use(Popover) 68 | Vue.use(Row) 69 | Vue.use(Select) 70 | Vue.use(Spin) 71 | Vue.use(Table) 72 | Vue.use(Transfer) 73 | Vue.use(TimePicker) 74 | Vue.use(Tooltip) 75 | Vue.use(Upload) 76 | Vue.use(Skeleton) 77 | Vue.use(Progress) 78 | Vue.use(Tag) 79 | Vue.use(Switch) 80 | Vue.use(Tabs) 81 | Vue.use(Tabs.TabPane) 82 | -------------------------------------------------------------------------------- /src/views/dashboard/components/OfflineData.vue: -------------------------------------------------------------------------------- 1 | 52 | 61 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # vue-antd-pro 2 | 基于vue-cli3 和 [ant-design-vue](https://vue.ant.design/docs/vue/introduce/)搭建的后台管理系统模板,使用json-server做数据mock。 3 | 4 | ### 数据mock服务启用 5 | 使用json-server做数据mock,关于json-server请移步[官网](https://github.com/typicode/json-server) 6 | ``` 7 | // 进入到server目录 8 | cd server 9 | 10 | // 安装依赖 11 | yarn install 12 | 13 | // 启动mock服务 14 | yarn start 15 | ``` 16 | 17 | 18 | ### 前端服务启动 19 | ``` 20 | // 返回项目根目录 21 | cd .. 22 | 23 | // 安装依赖 24 | yarn install 25 | 26 | // 启动本地服务 27 | yarn serve 28 | ``` 29 | 30 | ### 登录账号 31 | 用户名: 任意输入 32 | 密码: 字符数字[4-16]位的任意输入 33 | 34 | ### 生产环境构建 35 | ``` 36 | yarn run build 37 | ``` 38 | 39 | ### 效果预览 40 | ![login.png](https://upload-images.jianshu.io/upload_images/1918644-b25648a03fb53583.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 41 | 42 | ![table.png](https://upload-images.jianshu.io/upload_images/1918644-af6a0349ccedba08.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 43 | 44 | ![analysis.png](https://upload-images.jianshu.io/upload_images/1918644-52e3b8100691eaf9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 45 | 46 | ![setting1.png](https://upload-images.jianshu.io/upload_images/1918644-7a0d2234517ab788.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 47 | 48 | ![setting2.png](https://upload-images.jianshu.io/upload_images/1918644-d460e843cb86ab41.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 49 | 50 | ![setting3.png](https://upload-images.jianshu.io/upload_images/1918644-2f29223b39adb363.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 51 | 52 | ## Todo: 53 | 1. Pie组件颜色有问题 54 | 2. Pie组件Legend点击饼图没有变化 55 | 3. offlineData组件中选中tab字体颜色问题 56 | 5. 销售额类别占比 图和legend响应式 图没有及时更新以及mounted的时候未执行初始逻辑 57 | 6. v-slider组件onchange方法不起作用 58 | -------------------------------------------------------------------------------- /src/views/error/404.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 33 | 34 | 48 | -------------------------------------------------------------------------------- /src/store/initStore.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import store from './index' 3 | import config from './config' 4 | import { 5 | SYSTEM_MENU_THEME, 6 | SYSTEM_COLOR, 7 | SYSTEM_LAYOUT, 8 | SYSTEM_CONTENT_WIDTH, 9 | SYSTEM_FIXED_HEADER, 10 | SYSTEM_FIXED_SIDERBAR, 11 | SYSTEM_AUTOHIDE_HEADER, 12 | SYSTEM_COLORWEAK, 13 | ACCESS_TOKEN, 14 | USER, 15 | MENUS 16 | } from './mutation-types' 17 | 18 | const { app, user, permission } = config 19 | 20 | export default function initStore () { 21 | store.commit( 22 | 'app/SET_MENUTHEME', 23 | Vue.ls.get(SYSTEM_MENU_THEME, app.menuTheme) 24 | ) 25 | store.commit('app/SET_COLOR', Vue.ls.get(SYSTEM_COLOR, app.color)) 26 | store.commit('app/SET_LAYOUT', Vue.ls.get(SYSTEM_LAYOUT, app.layout)) 27 | store.commit( 28 | 'app/SET_CONTENTWIDTH', 29 | Vue.ls.get(SYSTEM_CONTENT_WIDTH, app.contentWidth) 30 | ) 31 | store.commit( 32 | 'app/SET_FIXEDHEADER', 33 | Vue.ls.get(SYSTEM_FIXED_HEADER, app.fixedHeader) 34 | ) 35 | store.commit( 36 | 'app/SET_FIXEDSIDERBAR', 37 | Vue.ls.get(SYSTEM_FIXED_SIDERBAR, app.fixSiderbar) 38 | ) 39 | store.commit( 40 | 'app/SET_AUTOHIDENHEADER', 41 | Vue.ls.get(SYSTEM_AUTOHIDE_HEADER, app.autoHideHeader) 42 | ) 43 | store.commit( 44 | 'app/SET_COLORWEAK', 45 | Vue.ls.get(SYSTEM_COLORWEAK, app.colorWeak) 46 | ) 47 | 48 | store.commit('user/SET_TOKEN', Vue.ss.get(ACCESS_TOKEN, user.token)) 49 | store.commit('user/SET_USER', Vue.ss.get(USER, user.user)) 50 | 51 | store.commit('permission/SET_MENUS', Vue.ss.get(MENUS, permission.menus)) 52 | 53 | // store.commit('SET_SIDEBAR_TYPE', Vue.ls.get(SIDEBAR_TYPE, true)); 54 | // 55 | // store.commit('TOGGLE_MULTI_TAB', Vue.ls.get(DEFAULT_MULTI_TAB, config.multiTab)); 56 | 57 | // last step 58 | } 59 | -------------------------------------------------------------------------------- /src/components/Logo/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 34 | 74 | -------------------------------------------------------------------------------- /src/components/Chart/RankList.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | 30 | 80 | -------------------------------------------------------------------------------- /src/views/dashboard/track.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 64 | 65 | 68 | -------------------------------------------------------------------------------- /src/views/dashboard/config.js: -------------------------------------------------------------------------------- 1 | export const pieOptions = { 2 | color: ['#1890FF', '#13C2C2', '#2FC25B', '#FACC14', '#F04864', '#8543E0'], 3 | tooltip: { 4 | trigger: 'item', 5 | formatter (params) { 6 | return `${params.seriesName}
${params.marker}${params.name}:${ 7 | params.data.value 8 | } (${params.percent}%)` 9 | }, 10 | confine: true, 11 | padding: [10, 10, 6, 10], 12 | backgroundColor: 'rgba(255, 255, 255, 0.9)', 13 | extraCssText: 'box-shadow: rgb(174, 174, 174) 0px 0px 10px;', 14 | textStyle: { 15 | color: 'rgb(87, 87, 87)' 16 | } 17 | }, 18 | dataset: { 19 | source: [] 20 | }, 21 | series: [ 22 | { 23 | name: '单量', 24 | type: 'pie', 25 | radius: ['45%', '60%'], 26 | label: { 27 | formatter: params => { 28 | const name = params.name 29 | return name.length > 5 ? name.substring(0, 5) + '...' : name 30 | } 31 | } 32 | } 33 | ] 34 | } 35 | 36 | // 单量分布 37 | export const countColumn = [ 38 | { 39 | title: '口岸', 40 | dataIndex: 'customsName', 41 | align: 'center', 42 | width: 200 43 | }, 44 | { 45 | title: '单量', 46 | dataIndex: 'totalDeclareCount', 47 | align: 'center', 48 | width: 100 49 | }, 50 | { 51 | title: '占比', 52 | dataIndex: 'rate', 53 | scopedSlots: { customRender: 'percent' }, 54 | align: 'center', 55 | width: 150 56 | } 57 | ] 58 | 59 | // 申报成功率 60 | export const rateColumn = [ 61 | { 62 | title: '口岸', 63 | dataIndex: 'customsName', 64 | align: 'center', 65 | width: 200 66 | }, 67 | { 68 | title: '回执单量', 69 | dataIndex: 'receiptDeclareCount', 70 | align: 'center', 71 | width: 100 72 | }, 73 | { 74 | title: '成功', 75 | dataIndex: 'succDeclareCount', 76 | align: 'center', 77 | width: 100 78 | }, 79 | { 80 | title: '成功率', 81 | dataIndex: 'successRate', 82 | scopedSlots: { customRender: 'percent' }, 83 | align: 'center', 84 | width: 200 85 | } 86 | ] 87 | -------------------------------------------------------------------------------- /src/components/Chart/SalePercentCard.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 70 | 71 | 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-antd-pro", 3 | "version": "0.1.0", 4 | "description": "A Vue.js template for PC backstage base ant-design-vue", 5 | "author": "luichooy", 6 | "license": "MIT", 7 | "homepage": "https://github.com/luichooy/vue-antd-pro", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/luichooy/vue-antd-pro.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/luichooy/vue-antd-pro/issues" 14 | }, 15 | "private": true, 16 | "scripts": { 17 | "serve": "vue-cli-service serve", 18 | "build": "vue-cli-service build", 19 | "lint": "vue-cli-service lint", 20 | "test:e2e": "vue-cli-service test:e2e", 21 | "test:unit": "vue-cli-service test:unit" 22 | }, 23 | "dependencies": { 24 | "@antv/data-set": "^0.10.2", 25 | "@antv/g2": "^3.5.11", 26 | "ant-design-vue": "^1.4.5", 27 | "axios": "^0.19.0", 28 | "core-js": "^2.6.5", 29 | "d3": "^5.14.1", 30 | "echarts": "^4.4.0", 31 | "enquire.js": "^2.1.6", 32 | "js-base64": "^2.5.1", 33 | "moment": "^2.24.0", 34 | "nprogress": "^0.2.0", 35 | "v-charts": "^1.19.0", 36 | "viser-vue": "^2.4.6", 37 | "vue": "^2.6.10", 38 | "vue-ls": "^3.2.1", 39 | "vue-router": "^3.0.3", 40 | "vuex": "^3.0.1" 41 | }, 42 | "devDependencies": { 43 | "@vue/cli-plugin-babel": "^3.8.0", 44 | "@vue/cli-plugin-e2e-nightwatch": "^3.8.0", 45 | "@vue/cli-plugin-eslint": "^3.8.0", 46 | "@vue/cli-plugin-unit-jest": "^3.8.0", 47 | "@vue/cli-service": "^3.8.0", 48 | "@vue/eslint-config-standard": "^4.0.0", 49 | "@vue/test-utils": "1.0.0-beta.29", 50 | "babel-core": "7.0.0-bridge.0", 51 | "babel-eslint": "^10.0.1", 52 | "babel-jest": "^23.6.0", 53 | "babel-plugin-import": "^1.12.0", 54 | "eslint": "^5.16.0", 55 | "eslint-plugin-vue": "^5.0.0", 56 | "husky": "^4.0.0-beta.2", 57 | "less": "2.7.3", 58 | "less-loader": "^5.0.0", 59 | "lint-staged": "^9.4.0", 60 | "sass": "^1.18.0", 61 | "sass-loader": "^7.1.0", 62 | "stylelint": "^11.0.0", 63 | "stylelint-config-standard": "^19.0.0", 64 | "vue-template-compiler": "^2.6.10" 65 | }, 66 | "husky": { 67 | "hooks": { 68 | "pre-commit": "lint-staged" 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/views/dashboard/components/NumberInfo.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 84 | -------------------------------------------------------------------------------- /src/components/Chart/MiniProgress.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 59 | 60 | 99 | -------------------------------------------------------------------------------- /src/assets/styles/common.scss: -------------------------------------------------------------------------------- 1 | @each $i, $size in $spaces { 2 | .m-#{$i} { 3 | margin: $size; 4 | } 5 | 6 | .p-#{$i} { 7 | padding: $size; 8 | } 9 | 10 | .ml-#{$i} { 11 | margin-left: $size; 12 | } 13 | 14 | .mr-#{$i} { 15 | margin-right: $size; 16 | } 17 | 18 | .mt-#{$i} { 19 | margin-top: $size; 20 | } 21 | 22 | .mb-#{$i} { 23 | margin-bottom: $size; 24 | } 25 | 26 | .pl-#{$i} { 27 | padding-left: $size; 28 | } 29 | 30 | .pr-#{$i} { 31 | padding-right: $size; 32 | } 33 | 34 | .pt-#{$i} { 35 | padding-top: $size; 36 | } 37 | 38 | .pb-#{$i} { 39 | padding-bottom: $size; 40 | } 41 | } 42 | 43 | .text-success { 44 | color: #00bcd4 !important; 45 | } 46 | 47 | .text-danger { 48 | color: #d9534f !important; 49 | } 50 | 51 | .text-center { 52 | text-align: center; 53 | } 54 | 55 | .text-right { 56 | text-align: right; 57 | } 58 | 59 | .cursor-pointer { 60 | cursor: pointer; 61 | } 62 | 63 | .border-none { 64 | border: none !important; 65 | } 66 | 67 | .page-container__stretch { 68 | position: absolute; 69 | top: 0; 70 | left: 0; 71 | bottom: 0; 72 | right: 0; 73 | } 74 | 75 | .operate-wrapper { 76 | margin: 16px 0; 77 | 78 | .btn-item { 79 | margin: 0 8px; 80 | 81 | &:first-child { 82 | margin-left: 0; 83 | } 84 | 85 | &:last-child { 86 | margin-right: 0; 87 | } 88 | } 89 | } 90 | 91 | .table-search-wrapper { 92 | .ant-form-item { 93 | display: flex; 94 | margin-bottom: 24px; 95 | margin-right: 0; 96 | 97 | .ant-form-item-control-wrapper { 98 | flex: 1 1; 99 | display: inline-block; 100 | vertical-align: middle; 101 | } 102 | 103 | .ant-form-item-label { 104 | line-height: 32px; 105 | padding-right: 8px; 106 | } 107 | .ant-form-item-control { 108 | height: 32px; 109 | line-height: 32px; 110 | } 111 | } 112 | 113 | .search-buttons { 114 | display: block; 115 | margin-bottom: 24px; 116 | margin-left: 24px; 117 | white-space: nowrap; 118 | } 119 | } 120 | 121 | .echarts { 122 | width: 100%; 123 | height: 100%; 124 | } 125 | 126 | // 手机端 左侧菜单drawer样式 127 | .ant-drawer.drawer-sider { 128 | .ant-drawer-body { 129 | padding: 0; 130 | } 131 | } 132 | 133 | // layoutHeader dropdown下拉菜单样式 134 | .user-menu .user { 135 | cursor: default; 136 | user-select: none; 137 | &:hover { 138 | background-color: #fff; 139 | } 140 | } 141 | 142 | // 表格内容单元格允许单词换行,防止内容过多将单元格撑大, 143 | .ant-table td { 144 | word-break: break-all; 145 | } 146 | -------------------------------------------------------------------------------- /src/components/Chart/ChartCard.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 43 | 44 | 108 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import store from '@/store/index' 3 | import router from '@/router' 4 | import axios from 'axios' 5 | import { ACCESS_TOKEN } from '@/store/mutation-types' 6 | import notification from 'ant-design-vue/es/notification' 7 | 8 | // api 配置 9 | 10 | let timer = null 11 | 12 | const onError = error => { 13 | if (error.response) { 14 | const status = error.response.status 15 | const message = error.response.statusText 16 | const token = Vue.ss.get(ACCESS_TOKEN) 17 | 18 | if (status === 403) { 19 | notification.error({ message: '禁止访问', description: message }) 20 | } 21 | 22 | if (status === 404) { 23 | notification.error({ message: '未知资源', description: message }) 24 | } 25 | 26 | if (status === 500) { 27 | notification.error({ 28 | message: '服务器错误', 29 | description: message 30 | }) 31 | } 32 | 33 | if (status === 401 && !timer) { 34 | timer = setTimeout(() => { 35 | notification.error({ 36 | message: '未授权', 37 | description: '授权失败,请重新登录' 38 | }) 39 | if (token) { 40 | store.dispatch('user/Logout').then(() => router.replace('/login')) 41 | } 42 | timer = null 43 | }, 500) 44 | } 45 | } 46 | return Promise.reject(error) 47 | } 48 | 49 | const request = axios.create({ 50 | baseURL: '/api', 51 | timeout: 5000, 52 | headers: { 53 | 'Content-Type': 'application/json;charset=UTF-8' 54 | }, 55 | transformRequest: [ 56 | function (data, headers) { 57 | const token = Vue.ss.get(ACCESS_TOKEN) 58 | if (token) { 59 | headers[ACCESS_TOKEN] = 'Bearer ' + token 60 | } 61 | if (headers['Content-Type'] === 'multipart/form-data') { 62 | return data 63 | } else { 64 | return JSON.stringify(data) 65 | } 66 | } 67 | ] 68 | }) 69 | 70 | // 请求拦截器 71 | request.interceptors.request.use( 72 | config => { 73 | // 开发环境下,如果请求是 post,put,patch,则打印数据体,方便调试 74 | if (process.env.NODE_ENV === 'development') { 75 | const { method } = config 76 | if (['post', 'put', 'patch'].includes(method)) { 77 | console.log(config.data) 78 | } 79 | } 80 | 81 | return config 82 | }, 83 | error => { 84 | notification.error({ 85 | message: '请求失败', 86 | description: '发送请求失败,请检查您的网络' 87 | }) 88 | return Promise.reject(error) 89 | } 90 | ) 91 | 92 | // 响应拦截器 93 | request.interceptors.response.use(res => { 94 | console.log(res) 95 | const jsonPattern = /application\/json/gi 96 | if (jsonPattern.test(res.headers['content-type'])) { 97 | return res.data 98 | } else { 99 | return res 100 | } 101 | }, onError) 102 | 103 | export default request 104 | -------------------------------------------------------------------------------- /src/components/ValidateCode/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 89 | 100 | -------------------------------------------------------------------------------- /src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { 3 | SYSTEM_MENU_THEME, 4 | SYSTEM_COLOR, 5 | SYSTEM_LAYOUT, 6 | SYSTEM_CONTENT_WIDTH, 7 | SYSTEM_FIXED_HEADER, 8 | SYSTEM_FIXED_SIDERBAR, 9 | SYSTEM_AUTOHIDE_HEADER, 10 | SYSTEM_COLORWEAK 11 | } from '../mutation-types' 12 | 13 | const app = { 14 | namespaced: true, 15 | state: { 16 | // 根据尺寸确定的客户端类型 desktop/tablat/mobile 17 | device: 'desktop', 18 | // menu的主题 dark light 19 | menuTheme: '', 20 | // 主题色 21 | color: '', 22 | // 页面布局 侧边栏布局 顶部布局 23 | layout: '', 24 | // 内容区域宽度 fixed fluid 25 | contentWidth: '', 26 | // 是否固定顶部栏 27 | fixedHeader: '', 28 | // 是否固定侧边栏 29 | fixedSiderbar: '', 30 | // 当顶部栏不固定时,页面向下滚动时,是否自动隐藏顶部栏 31 | autoHideHeader: '', 32 | // 色弱模式 33 | colorWeak: '' 34 | }, 35 | mutations: { 36 | SET_DEVICE (state, device) { 37 | state.device = device 38 | }, 39 | SET_MENUTHEME (state, theme) { 40 | Vue.ls.set(SYSTEM_MENU_THEME, theme) 41 | state.menuTheme = theme 42 | }, 43 | SET_COLOR (state, color) { 44 | Vue.ls.set(SYSTEM_COLOR, color) 45 | state.color = color 46 | }, 47 | SET_LAYOUT (state, layout) { 48 | Vue.ls.set(SYSTEM_LAYOUT, layout) 49 | state.layout = layout 50 | }, 51 | SET_CONTENTWIDTH (state, contentWidth) { 52 | Vue.ls.set(SYSTEM_CONTENT_WIDTH, contentWidth) 53 | state.contentWidth = contentWidth 54 | }, 55 | SET_FIXEDHEADER (state, isFixed) { 56 | Vue.ls.set(SYSTEM_FIXED_HEADER, isFixed) 57 | state.fixedHeader = isFixed 58 | }, 59 | SET_FIXEDSIDERBAR (state, isFixed) { 60 | Vue.ls.set(SYSTEM_FIXED_SIDERBAR, isFixed) 61 | state.fixedSiderbar = isFixed 62 | }, 63 | SET_AUTOHIDENHEADER (state, isHidden) { 64 | Vue.ls.set(SYSTEM_AUTOHIDE_HEADER, isHidden) 65 | state.autoHideHeader = isHidden 66 | }, 67 | SET_COLORWEAK (state, colorWeak) { 68 | Vue.ls.set(SYSTEM_COLORWEAK, colorWeak) 69 | state.colorWeak = colorWeak 70 | } 71 | }, 72 | actions: { 73 | set_menuTheme ({ commit }, menuTheme) { 74 | commit('SET_MENUTHEME', menuTheme) 75 | }, 76 | set_color ({ commit }, color) { 77 | commit('SET_COLOR', color) 78 | }, 79 | set_layout ({ commit }, layout) { 80 | commit('SET_LAYOUT', layout) 81 | }, 82 | set_contentWidth ({ commit }, contentWidth) { 83 | commit('SET_CONTENTWIDTH', contentWidth) 84 | }, 85 | set_fixedHeader ({ commit }, isFixed) { 86 | commit('SET_FIXEDHEADER', isFixed) 87 | }, 88 | set_fixedSiderbar ({ commit }, isFixed) { 89 | commit('SET_FIXEDSIDERBAR', isFixed) 90 | }, 91 | set_autoHideHeader ({ commit }, isHidden) { 92 | commit('SET_AUTOHIDENHEADER', isHidden) 93 | }, 94 | set_colorWeak ({ commit }, colorWeak) { 95 | commit('SET_COLORWEAK', colorWeak) 96 | } 97 | } 98 | } 99 | 100 | export default app 101 | -------------------------------------------------------------------------------- /src/components/Chart/HotSearchCard.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Welcome to vue-antd-pro 👋

2 |

3 | 4 | 5 | Documentation 6 | 7 | 8 | Maintenance 9 | 10 | 11 | License: MIT 12 | 13 |

14 | 15 | > 基于vue-cli3 和 [ant-design-vue](https://vue.ant.design/docs/vue/introduce/)搭建的后台管理系统模板,使用json-server做数据mock。 16 | 17 | ## 安装依赖 18 | 19 | 安装前端依赖 20 | ```sh 21 | yarn 22 | ``` 23 | 24 | 安装数据mock依赖 25 | ```sh 26 | cd server 27 | 28 | yarn 29 | ``` 30 | 31 | ## 运行 32 | 33 | 运行数据mock服务 34 | ```sh 35 | // enter server directory 36 | cd server 37 | 38 | yarn start 39 | ``` 40 | 41 | 运行前端本地服务 42 | ```sh 43 | // go back to project root directory 44 | cd .. 45 | 46 | yarn serve 47 | ``` 48 | 49 | ## 账号 50 | ``` 51 | 用户名: 任意输入 52 | 密码: 字符数字[4-16]位的任意输入 53 | ``` 54 | 55 | ## 预览 56 | 57 | ![login.png](https://upload-images.jianshu.io/upload_images/1918644-b25648a03fb53583.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 58 | 59 | ![table.png](https://upload-images.jianshu.io/upload_images/1918644-af6a0349ccedba08.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 60 | 61 | ![analysis.png](https://upload-images.jianshu.io/upload_images/1918644-52e3b8100691eaf9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 62 | 63 | ![setting1.png](https://upload-images.jianshu.io/upload_images/1918644-7a0d2234517ab788.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 64 | 65 | ![setting2.png](https://upload-images.jianshu.io/upload_images/1918644-d460e843cb86ab41.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 66 | 67 | ![setting3.png](https://upload-images.jianshu.io/upload_images/1918644-2f29223b39adb363.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 68 | 69 | ## Todos 70 | * Pie组件颜色有问题 71 | * Pie组件Legend点击饼图没有变化 72 | * offlineData组件中选中tab字体颜色问题 73 | * 销售额类别占比 图和legend响应式 图没有及时更新以及mounted的时候未执行初始逻辑 74 | * v-slider组件onchange方法不起作用 75 | 76 | 77 | ## Author 78 | 79 | 👤 **luichooy** 80 | 81 | * 掘金: [@luichooy ](https://juejin.im/user/57fe62225bbb50005b47e277 ) 82 | * Github: [@luichooy](https://github.com/luichooy) 83 | 84 | ## 🤝 Contributing 85 | 86 | Contributions, issues and feature requests are welcome!
Feel free to check [issues page](https://github.com/luichooy/vue-antd-pro/issues). 87 | 88 | ## Show your support 89 | 90 | Give a ⭐️ if this project helped you! 91 | 92 | 93 | 94 | 95 | 96 | ## 📝 License 97 | 98 | Copyright © 2019 [luichooy](https://github.com/luichooy).
99 | This project is [MIT]( ) licensed. 100 | 101 | *** 102 | _This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_ 103 | -------------------------------------------------------------------------------- /src/components/DetailList/DetailList.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 79 | 80 | 148 | -------------------------------------------------------------------------------- /src/components/Layout/App/LayoutHeader.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 83 | 129 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | let casual = require('casual') 2 | 3 | const roleMap = [ 4 | { 5 | label: '超级管理员', 6 | value: 1 7 | }, 8 | { 9 | label: '管理员', 10 | value: 2 11 | }, 12 | { 13 | label: '操作员A', 14 | value: 3 15 | }, 16 | { 17 | label: '操作员B', 18 | value: 4 19 | } 20 | ] 21 | 22 | const menus = [ 23 | { 24 | id: '1', 25 | icon: 'table', 26 | path: '/table', 27 | name: 'table', 28 | title: '表格页' 29 | }, 30 | { 31 | id: '2', 32 | icon: 'dashboard', 33 | path: '/dashboard', 34 | name: 'dashboard', 35 | title: 'Dashboard', 36 | children: [ 37 | { 38 | id: '21', 39 | icon: '', 40 | path: '/dashboard/analysis', 41 | name: 'analysis', 42 | title: '分析页' 43 | }, 44 | { 45 | id: '22', 46 | icon: '', 47 | path: '/dashboard/monitor', 48 | name: 'monitor', 49 | title: '监控页' 50 | }, 51 | { 52 | id: '23', 53 | icon: '', 54 | path: '/dashboard/v-charts', 55 | name: 'v-charts', 56 | title: 'v-charts' 57 | }, 58 | { 59 | id: '24', 60 | icon: '', 61 | path: '/dashboard/track', 62 | name: 'track', 63 | title: '轨迹图' 64 | } 65 | ] 66 | }, 67 | { 68 | id: '3', 69 | icon: 'stock', 70 | path: '/d3', 71 | name: 'd3', 72 | title: 'D3', 73 | children: [ 74 | { 75 | id: '31', 76 | icon: '', 77 | path: '/d3/bar', 78 | name: 'bar', 79 | title: 'bar' 80 | }, 81 | { 82 | id: '32', 83 | icon: '', 84 | path: '/d3/line', 85 | name: 'line', 86 | title: 'line' 87 | } 88 | ] 89 | }, 90 | { 91 | id: '4', 92 | icon: 'stock', 93 | path: '/g2', 94 | name: 'g2', 95 | title: 'g2', 96 | children: [ 97 | { 98 | id: '41', 99 | icon: '', 100 | path: '/g2/bar', 101 | name: 'bar', 102 | title: 'bar' 103 | }, 104 | { 105 | id: '42', 106 | icon: '', 107 | path: '/g2/line', 108 | name: 'line', 109 | title: 'line' 110 | } 111 | ] 112 | } 113 | ] 114 | 115 | casual.define('user', function (role) { 116 | return { 117 | id: casual.card_number(), 118 | username: casual.username, 119 | contacts: casual.full_name, 120 | contactsEmail: casual.email, 121 | address: casual.address, 122 | roleId: role.value, 123 | status: casual.integer(0, 1), 124 | createTime: casual.unix_time, 125 | updateTime: casual.unix_time 126 | } 127 | }) 128 | 129 | module.exports = () => { 130 | const data = { 131 | users: [], 132 | roles: roleMap, 133 | login: { 134 | status: 200, 135 | data: { 136 | token: 137 | 'eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIwMTU2MjA5MyIsImNyZWF0ZWQiOjE1NjE1MTY3NjU5MzMsImNvbXBhbnlOYW1lIjoiQee9kSIsInRlbmFudElkIjoxLCJ1c2VyVHlwZSI6InN0YWZmIiwiaWQiOjEsImV4cCI6MTU2MTUyMDM2NX0.j8sWbwXzHnSgvz7em2DjAhNDU5xaxysEFES8SlyJZnj0lVgXKax4tEDNGawZivW6Ip1734Rnvb6z2te8jGmIWQ"', 138 | menus: menus, 139 | user: casual.user(casual.random_element(roleMap)) 140 | }, 141 | message: 'success' 142 | }, 143 | logout: { 144 | status: 200, 145 | message: 'success' 146 | } 147 | } 148 | 149 | for (let i = 0; i < 54; i++) { 150 | data.users.push(casual.user(casual.random_element(roleMap))) 151 | } 152 | 153 | return data 154 | } 155 | -------------------------------------------------------------------------------- /src/components/Setting/tools.js: -------------------------------------------------------------------------------- 1 | import { message } from 'ant-design-vue/es' 2 | 3 | const colorList = [ 4 | { 5 | name: '薄暮', 6 | value: '#F5222D' 7 | }, 8 | { 9 | name: '火山', 10 | value: '#FA541C' 11 | }, 12 | { 13 | name: '日暮', 14 | value: '#FAAD14' 15 | }, 16 | { 17 | name: '明青', 18 | value: '#13C2C2' 19 | }, 20 | { 21 | name: '极光绿', 22 | value: '#52C41A' 23 | }, 24 | { 25 | name: '拂晓蓝(默认)', 26 | value: '#1890FF' 27 | }, 28 | { 29 | name: '极客蓝', 30 | value: '#2F54EB' 31 | }, 32 | { 33 | name: '酱紫', 34 | value: '#722ED1' 35 | } 36 | ] 37 | 38 | const menuThemeList = [ 39 | { 40 | name: '暗色菜单风格', 41 | value: 'dark', 42 | imgSrc: 43 | 'https://gw.alipayobjects.com/zos/rmsportal/LCkqqYNmvBEbokSDscrm.svg' 44 | }, 45 | { 46 | name: '亮色菜单风格', 47 | value: 'light', 48 | imgSrc: 49 | 'https://gw.alipayobjects.com/zos/rmsportal/jpRkZQMyYRryryPNtyIC.svg' 50 | } 51 | ] 52 | 53 | const layoutModeList = [ 54 | { 55 | name: '侧边栏导航', 56 | value: 'side', 57 | imgSrc: 58 | 'https://gw.alipayobjects.com/zos/rmsportal/JopDzEhOqwOjeNTXkoje.svg' 59 | }, 60 | { 61 | name: '顶部栏导航', 62 | value: 'top', 63 | imgSrc: 64 | 'https://gw.alipayobjects.com/zos/rmsportal/KDNDBbriJhLwuqMoxcAr.svg' 65 | } 66 | ] 67 | 68 | let lessNodesAppended 69 | const updateTheme = color => { 70 | // Don't compile less in production! 71 | /* if (process.env.NODE_ENV === 'production') { 72 | return; 73 | } */ 74 | // Determine if the component is remounted 75 | if (!color) { 76 | return 77 | } 78 | const hideMessage = message.loading('正在编译主题!', 0) 79 | 80 | function buildIt () { 81 | if (!window.less) { 82 | return 83 | } 84 | setTimeout(() => { 85 | window.less 86 | .modifyVars({ 87 | '@primary-color': color 88 | }) 89 | .then(() => { 90 | hideMessage() 91 | }) 92 | .catch(() => { 93 | message.error('更新主题色失败') 94 | hideMessage() 95 | }) 96 | }, 200) 97 | } 98 | 99 | if (!lessNodesAppended) { 100 | // insert less.js and color.less 101 | const lessStyleNode = document.createElement('link') 102 | const lessConfigNode = document.createElement('script') 103 | const lessScriptNode = document.createElement('script') 104 | lessStyleNode.setAttribute('rel', 'stylesheet/less') 105 | lessStyleNode.setAttribute('href', '/color.less') 106 | lessConfigNode.innerHTML = ` 107 | window.less = { 108 | async: true, 109 | env: 'production', 110 | javascriptEnabled: true 111 | }; 112 | ` 113 | lessScriptNode.src = 114 | 'https://gw.alipayobjects.com/os/lib/less.js/3.8.1/less.min.js' 115 | lessScriptNode.async = true 116 | lessScriptNode.onload = () => { 117 | buildIt() 118 | lessScriptNode.onload = null 119 | } 120 | document.body.appendChild(lessStyleNode) 121 | document.body.appendChild(lessConfigNode) 122 | document.body.appendChild(lessScriptNode) 123 | lessNodesAppended = true 124 | } else { 125 | buildIt() 126 | } 127 | } 128 | 129 | const updateColorWeak = colorWeak => { 130 | // document.body.className = colorWeak ? 'colorWeak' : ''; 131 | colorWeak 132 | ? document.body.classList.add('color-weak') 133 | : document.body.classList.remove('color-weak') 134 | } 135 | 136 | export { 137 | menuThemeList, 138 | colorList, 139 | layoutModeList, 140 | updateTheme, 141 | updateColorWeak 142 | } 143 | -------------------------------------------------------------------------------- /src/components/Layout/App/Layout.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 93 | 94 | 127 | -------------------------------------------------------------------------------- /src/assets/images/excel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 44 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/views/d3/components/Bar/Transition.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 107 | 108 | 130 | -------------------------------------------------------------------------------- /src/components/ModifyPassword/index.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 128 | -------------------------------------------------------------------------------- /src/views/d3/components/Bar/TransitionOther.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 107 | 108 | 133 | -------------------------------------------------------------------------------- /src/router/routes.js: -------------------------------------------------------------------------------- 1 | import { AppLayout, ViewLayout } from '@/components/Layout' 2 | 3 | const otherRoutes = [ 4 | { 5 | path: '/login', 6 | name: 'login', 7 | component: () => import(/* webpackChunkName: "login" */ '@/views/auth/login') 8 | }, 9 | { 10 | path: '/error', 11 | name: 'error', 12 | redirect: '404', 13 | component: ViewLayout, 14 | children: [ 15 | { 16 | path: '404', 17 | name: '404', 18 | component: () => import(/* webpackChunkName: "404" */ '@/views/error/404') 19 | } 20 | ] 21 | } 22 | ] 23 | 24 | export const appRoutes = [ 25 | { 26 | path: '/', 27 | name: 'index', 28 | redirect: '/dashboard/analysis', 29 | component: AppLayout, 30 | children: [ 31 | { 32 | path: 'table', 33 | name: 'table', 34 | meta: { 35 | title: '表格页', 36 | icon: 'table' 37 | }, 38 | component: () => import(/* webpackChunkName: "table" */ '@/views/table/table') 39 | }, 40 | { 41 | path: 'dashboard', 42 | name: 'dashboard', 43 | meta: { 44 | title: 'Dashboard', 45 | icon: 'dashboard' 46 | }, 47 | component: ViewLayout, 48 | children: [ 49 | { 50 | path: 'analysis', 51 | name: 'dashboard_analysis', 52 | meta: { 53 | title: '分析页' 54 | }, 55 | component: () => import(/* webpackChunkName: "analysis" */ '@/views/dashboard/analysis') 56 | }, 57 | { 58 | path: 'monitor', 59 | name: 'dashboard_monitor', 60 | meta: { 61 | title: '监控页' 62 | }, 63 | component: () => import(/* webpackChunkName: "monitor" */ '@/views/dashboard/monitor') 64 | }, 65 | { 66 | path: 'v-charts', 67 | name: 'v-charts', 68 | meta: { 69 | title: 'v-charts' 70 | }, 71 | component: () => import(/* webpackChunkName: "v-charts" */ '@/views/dashboard/vcharts') 72 | }, 73 | { 74 | path: 'track', 75 | name: 'track', 76 | meta: { 77 | title: '轨迹图' 78 | }, 79 | component: () => import(/* webpackChunkName: "track" */ '@/views/dashboard/track') 80 | } 81 | ] 82 | }, 83 | { 84 | path: 'd3', 85 | name: 'd3', 86 | meta: { 87 | title: 'D3', 88 | icon: 'stock' 89 | }, 90 | component: ViewLayout, 91 | children: [ 92 | { 93 | path: 'bar', 94 | name: 'd3_bar', 95 | meta: { 96 | title: 'bar' 97 | }, 98 | component: () => import(/* webpackChunkName: "tutorials" */ '@/views/d3/bar') 99 | }, 100 | { 101 | path: 'line', 102 | name: 'd3_line', 103 | meta: { 104 | title: 'line' 105 | }, 106 | component: () => import(/* webpackChunkName: "tutorials" */ '@/views/d3/line') 107 | } 108 | ] 109 | }, 110 | { 111 | path: 'g2', 112 | name: 'g2', 113 | meta: { 114 | title: 'g2', 115 | icon: 'stock' 116 | }, 117 | component: ViewLayout, 118 | children: [ 119 | { 120 | path: 'bar', 121 | name: 'g2_bar', 122 | meta: { 123 | title: 'bar' 124 | }, 125 | component: () => import(/* webpackChunkName: "tutorials" */ '@/views/g2/bar') 126 | }, 127 | { 128 | path: 'line', 129 | name: 'g2_line', 130 | meta: { 131 | title: 'line' 132 | }, 133 | component: () => import(/* webpackChunkName: "tutorials" */ '@/views/g2/line') 134 | } 135 | ] 136 | } 137 | ] 138 | } 139 | ] 140 | 141 | const routes = [...otherRoutes, ...appRoutes] 142 | 143 | export default routes 144 | -------------------------------------------------------------------------------- /src/components/Chart/TimelineChart.vue: -------------------------------------------------------------------------------- 1 | 155 | -------------------------------------------------------------------------------- /src/components/Actions/index.vue: -------------------------------------------------------------------------------- 1 | 64 | 104 | 105 | 138 | -------------------------------------------------------------------------------- /src/components/Menu/index.vue: -------------------------------------------------------------------------------- 1 | 148 | 173 | -------------------------------------------------------------------------------- /src/views/dashboard/vcharts.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 152 | -------------------------------------------------------------------------------- /src/views/table/components/AccountModal.vue: -------------------------------------------------------------------------------- 1 | 94 | 95 | 159 | -------------------------------------------------------------------------------- /src/views/d3/components/Bar/BarChart.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 137 | 138 | 189 | -------------------------------------------------------------------------------- /src/views/auth/login.vue: -------------------------------------------------------------------------------- 1 | 93 | 94 | 160 | 161 | 210 | -------------------------------------------------------------------------------- /src/views/table/table.vue: -------------------------------------------------------------------------------- 1 | 101 | 102 | 278 | -------------------------------------------------------------------------------- /src/views/error/500.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 39 | 40 | 60 | -------------------------------------------------------------------------------- /src/components/Chart/Pie.vue: -------------------------------------------------------------------------------- 1 | 253 | 333 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis.vue: -------------------------------------------------------------------------------- 1 | 134 | 135 | 249 | 250 | 306 | -------------------------------------------------------------------------------- /src/components/Setting/index.vue: -------------------------------------------------------------------------------- 1 | 149 | 150 | 247 | 248 | 327 | --------------------------------------------------------------------------------