├── .editorconfig
├── .env.development
├── .env.production
├── .env.staging
├── .eslintignore
├── .eslintrc.js
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── feature_request.md
│ └── question.md
└── workflows
│ └── ci.yml
├── .gitignore
├── .prettierrc
├── LICENSE
├── README-zh.md
├── README.md
├── babel.config.js
├── build
└── index.js
├── jsconfig.json
├── mock
├── article.js
├── charts.js
├── index.js
├── mock-server.js
├── online.js
├── public.js
├── remote-search.js
├── role
│ ├── index.js
│ └── routes.js
├── table.js
├── user.js
└── utils.js
├── package.json
├── postcss.config.js
├── public
├── favicon.ico
├── index.html
└── province
│ ├── anhui.json
│ ├── aomen.json
│ ├── beijing.json
│ ├── china-contour.json
│ ├── chongqing.json
│ ├── fujian.json
│ ├── gansu.json
│ ├── guangdong.json
│ ├── guangxi.json
│ ├── guizhou.json
│ ├── hainan.json
│ ├── hebei.json
│ ├── heilongjiang.json
│ ├── henan.json
│ ├── hubei.json
│ ├── hunan.json
│ ├── jiangsu.json
│ ├── jiangxi.json
│ ├── jilin.json
│ ├── liaoning.json
│ ├── neimenggu.json
│ ├── ningxia.json
│ ├── qinghai.json
│ ├── shandong.json
│ ├── shanghai.json
│ ├── shanxi.json
│ ├── sichuan.json
│ ├── taiwan.json
│ ├── tianjin.json
│ ├── xianggang.json
│ ├── xinjiang.json
│ ├── xizang.json
│ ├── yunnan.json
│ └── zhejiang.json
├── src
├── App.vue
├── api
│ ├── article.js
│ ├── charts.js
│ ├── online.js
│ ├── public.js
│ ├── table.js
│ └── user.js
├── assets
│ ├── 401_images
│ │ └── 401.gif
│ ├── 404_images
│ │ ├── 404.png
│ │ └── 404_cloud.png
│ └── mapJson
│ │ ├── HK.json
│ │ └── china.json
├── components
│ ├── Breadcrumb
│ │ └── index.vue
│ ├── Hamburger
│ │ └── index.vue
│ ├── Online
│ │ └── list.vue
│ ├── Pagination
│ │ └── index.vue
│ ├── ProDialog
│ │ ├── draggable.js
│ │ └── index.vue
│ ├── ProForm
│ │ └── index.vue
│ ├── ProTable
│ │ ├── index.vue
│ │ └── table-setting.vue
│ ├── Redirect
│ │ └── index.vue
│ ├── RightPanel
│ │ └── index.vue
│ ├── SearchForm
│ │ └── index.vue
│ ├── SvgIcon
│ │ └── index.vue
│ └── sendCode
│ │ └── index.vue
├── core
│ ├── echarts.js
│ ├── globalProperties.js
│ ├── lazy_use.js
│ └── use.js
├── directive
│ ├── clipboard
│ │ ├── clipboard.js
│ │ └── index.js
│ ├── fullscreen
│ │ ├── fullscreen.js
│ │ └── index.js
│ ├── index.js
│ └── waves
│ │ ├── index.js
│ │ ├── waves.css
│ │ └── waves.js
├── hooks
│ ├── dict.js
│ ├── echarts.js
│ └── table.js
├── icons
│ ├── index.js
│ ├── svg
│ │ ├── 404.svg
│ │ ├── bug.svg
│ │ ├── chart.svg
│ │ ├── clipboard.svg
│ │ ├── component.svg
│ │ ├── dashboard.svg
│ │ ├── documentation.svg
│ │ ├── drag.svg
│ │ ├── edit.svg
│ │ ├── education.svg
│ │ ├── email.svg
│ │ ├── example.svg
│ │ ├── excel.svg
│ │ ├── exit-fullscreen.svg
│ │ ├── eye-open.svg
│ │ ├── eye.svg
│ │ ├── form.svg
│ │ ├── fullscreen-exit.svg
│ │ ├── fullscreen.svg
│ │ ├── guide.svg
│ │ ├── icon.svg
│ │ ├── international.svg
│ │ ├── language.svg
│ │ ├── link.svg
│ │ ├── list.svg
│ │ ├── lock.svg
│ │ ├── message.svg
│ │ ├── money.svg
│ │ ├── nested.svg
│ │ ├── password.svg
│ │ ├── pdf.svg
│ │ ├── people.svg
│ │ ├── peoples.svg
│ │ ├── qq.svg
│ │ ├── search.svg
│ │ ├── shopping.svg
│ │ ├── size.svg
│ │ ├── skill.svg
│ │ ├── star.svg
│ │ ├── tab.svg
│ │ ├── table.svg
│ │ ├── theme.svg
│ │ ├── tree-table.svg
│ │ ├── tree.svg
│ │ ├── user.svg
│ │ ├── wechat.svg
│ │ └── zip.svg
│ └── svgo.yml
├── layout
│ ├── components
│ │ ├── AppMain.vue
│ │ ├── Navbar.vue
│ │ ├── Settings
│ │ │ └── index.vue
│ │ ├── Sidebar
│ │ │ ├── FixiOSBug.js
│ │ │ ├── Item.vue
│ │ │ ├── Link.vue
│ │ │ ├── Logo.vue
│ │ │ ├── SidebarItem.vue
│ │ │ └── index.vue
│ │ ├── TagsView
│ │ │ └── index.vue
│ │ └── index.js
│ ├── index.vue
│ └── mixin
│ │ └── ResizeHandler.js
├── main.js
├── permission.js
├── router
│ ├── index.js
│ └── modules
│ │ ├── chart.js
│ │ ├── components-page.js
│ │ ├── error-page.js
│ │ ├── form.js
│ │ ├── nested.js
│ │ └── table.js
├── settings.js
├── store
│ ├── getters.js
│ ├── index.js
│ └── modules
│ │ ├── app.js
│ │ ├── charts.js
│ │ ├── errorLog.js
│ │ ├── permission.js
│ │ ├── settings.js
│ │ ├── tagsView.js
│ │ └── user.js
├── styles
│ ├── editable-cell.scss
│ ├── element-ui.scss
│ ├── index.scss
│ ├── mixin.scss
│ ├── sidebar.scss
│ ├── transition.scss
│ └── variables.scss
├── utils
│ ├── auth.js
│ ├── clipboard.js
│ ├── echarts.js
│ ├── get-page-title.js
│ ├── index.js
│ ├── request.js
│ ├── scroll-to.js
│ └── validate.js
└── views
│ ├── charts
│ ├── Bar.vue
│ ├── Line.vue
│ ├── Map.vue
│ ├── Pie.vue
│ ├── components
│ │ └── CardChart.vue
│ └── data.js
│ ├── components-page
│ └── pro-dialog-view.vue
│ ├── dashboard
│ └── index.vue
│ ├── error-page
│ ├── 401.vue
│ └── 404.vue
│ ├── form
│ ├── advanced-form.vue
│ ├── columns
│ │ ├── advance.js
│ │ ├── linkage.js
│ │ ├── list.js
│ │ ├── modal.js
│ │ └── step-list.js
│ ├── index.vue
│ ├── linkage-form.vue
│ ├── modal-form.vue
│ └── step-form.vue
│ ├── icons
│ ├── element-icons.js
│ ├── index.vue
│ └── svg-icons.js
│ ├── login
│ └── index.vue
│ ├── nested
│ ├── menu1
│ │ ├── index.vue
│ │ ├── menu1-1
│ │ │ └── index.vue
│ │ ├── menu1-2
│ │ │ ├── index.vue
│ │ │ ├── menu1-2-1
│ │ │ │ └── index.vue
│ │ │ └── menu1-2-2
│ │ │ │ └── index.vue
│ │ └── menu1-3
│ │ │ └── index.vue
│ └── menu2
│ │ └── index.vue
│ ├── online
│ └── createList.vue
│ ├── table
│ ├── columns
│ │ ├── inline.js
│ │ └── list.js
│ ├── complex-table.vue
│ ├── components
│ │ └── editable-cell
│ │ │ ├── Important.vue
│ │ │ └── Status.vue
│ └── inline-edit-table.vue
│ ├── tailwindcss
│ └── index.vue
│ └── tree
│ └── index.vue
├── tailwind.config.js
└── vue.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | end_of_line = lf
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | insert_final_newline = false
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | # just a flag
2 | ENV = 'development'
3 |
4 | # base api
5 | VUE_APP_BASE_API = '/dev-api'
6 |
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | # just a flag
2 | ENV = 'production'
3 |
4 | # base api
5 | VUE_APP_BASE_API = '/prod-api'
6 |
7 |
--------------------------------------------------------------------------------
/.env.staging:
--------------------------------------------------------------------------------
1 | NODE_ENV = production
2 |
3 | # just a flag
4 | ENV = 'staging'
5 |
6 | # base api
7 | VUE_APP_BASE_API = '/stage-api'
8 |
9 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | src/assets
3 | public
4 | dist
5 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: 'vue-eslint-parser',
4 | parserOptions: {
5 | parser: 'babel-eslint',
6 | ecmaVersion: 2020,
7 | sourceType: 'module',
8 | },
9 | env: {
10 | browser: true,
11 | node: true,
12 | es6: true,
13 | },
14 | // https://eslint.vuejs.org/user-guide/#usage
15 | extends: [
16 | 'plugin:vue/vue3-recommended',
17 | 'eslint:recommended',
18 | 'prettier',
19 | 'prettier/vue',
20 | 'plugin:prettier/recommended',
21 | ],
22 | plugins: ['prettier'],
23 | // add your custom rules here
24 | // it is base on https://github.com/vuejs/eslint-config-vue
25 | rules: {
26 | 'vue/no-v-html': 'off',
27 | 'vue/no-v-model-argument': 'off',
28 | 'no-console': 'off',
29 | 'vue/no-mutating-props': 'off',
30 | 'no-var': 2,
31 | },
32 | }
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report(报告问题)
3 | about: Create a report to help us improve
4 | ---
5 |
10 |
11 |
12 | ## Bug report(问题描述)
13 |
14 | #### Steps to reproduce(问题复现步骤)
15 |
20 |
21 | #### Screenshot or Gif(截图或动态图)
22 |
23 |
24 | #### Link to minimal reproduction(最小可在线还原demo)
25 |
26 |
29 |
30 | #### Other relevant information(格外信息)
31 | - Your OS:
32 | - Node.js version:
33 | - vue3-admin-pro version:
34 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature Request(新功能建议)
3 | about: Suggest an idea for this project
4 | ---
5 |
6 | ## Feature request(新功能建议)
7 |
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question(提问)
3 | about: Asking questions about use
4 | ---
5 |
6 | ## Question(提问)
7 |
8 |
15 |
16 | #### Steps to reproduce(问题复现步骤)
17 |
22 |
23 | #### Screenshot or Gif(截图或动态图)
24 |
25 |
26 | #### Link to minimal reproduction(最小可在线还原demo)
27 |
28 |
31 |
32 | #### Other relevant information(格外信息)
33 | - Your OS:
34 | - Node.js version:
35 | - vue3-admin-pro version:
36 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: GitHub Actions Build and Deploy Demo
2 | on:
3 | push:
4 | branches:
5 | - master
6 | jobs:
7 | build-and-deploy:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout
11 | uses: actions/checkout@v2 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly.
12 | with:
13 | persist-credentials: false
14 | - name: Install and Build
15 | run: |
16 | npm install
17 | npm run-script build:prod
18 | - name: Deploy
19 | uses: JamesIves/github-pages-deploy-action@releases/v3
20 | with:
21 | ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
22 | BRANCH: gh-pages
23 | FOLDER: dist
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | package-lock.json
8 | tests/**/coverage/
9 |
10 | # Editor directories and files
11 | .idea
12 | .vscode
13 | *.suo
14 | *.ntvs*
15 | *.njsproj
16 | *.sln
17 | package-lock.json
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "arrowParens": "always",
4 | "singleQuote": true,
5 | "tabWidth": 2,
6 | }
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-present HaitaoWang555
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README-zh.md:
--------------------------------------------------------------------------------
1 | # vue3-admin-pro
2 |
3 | > 这是一个极简的 vue admin 管理后台。它只包含了 Element UI & axios & iconfont & permission control & lint,这些搭建后台必要的东西。
4 |
5 | [线上地址](https://HaitaoWang555.github.io/vue3-admin-pro)
6 | [线上地址](http://wanghaitao555.gitee.io/vue3-admin-pro)
7 |
8 | ## Build Setup
9 |
10 | ```bash
11 | # 克隆项目
12 | git clone https://github.com/HaitaoWang555/vue3-admin-pro.git
13 |
14 | # 进入项目目录
15 | cd vue3-admin-pro
16 |
17 | # 安装依赖
18 | npm install
19 |
20 | # 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
21 | npm install --registry=https://registry.npm.taobao.org
22 |
23 | # 启动服务
24 | npm run dev
25 | ```
26 |
27 | 浏览器访问 [http://localhost:9528](http://localhost:9528)
28 |
29 | ## 发布
30 |
31 | ```bash
32 | # 构建测试环境
33 | npm run build:stage
34 |
35 | # 构建生产环境
36 | npm run build:prod
37 | ```
38 |
39 | ## 其它
40 |
41 | ```bash
42 | # 预览发布环境效果
43 | npm run preview
44 |
45 | # 预览发布环境效果 + 静态资源分析
46 | npm run preview -- --report
47 |
48 | # 代码格式检查
49 | npm run lint
50 |
51 | # 代码格式检查并自动修复
52 | npm run lint -- --fix
53 | ```
54 |
55 | ## License
56 |
57 | [MIT](https://github.com/HaitaoWang555/vue3-admin-pro/blob/master/LICENSE) license.
58 |
59 | Copyright (c) 2017-present HaitaoWang555
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue3-admin-pro
2 |
3 | English | [简体中文](./README-zh.md)
4 |
5 | > A minimal Vue Admin Pro with Element UI & axios & iconfont & permission control & lint
6 |
7 | **Live demo:** https://HaitaoWang555.github.io/vue3-admin-pro
8 | **Live demo:** http://wanghaitao555.gitee.io/vue3-admin-pro
9 |
10 |
11 | ## Build Setup
12 |
13 | ```bash
14 | # clone the project
15 | git clone https://github.com/HaitaoWang555/vue3-admin-pro.git
16 |
17 | # enter the project directory
18 | cd vue3-admin-pro
19 |
20 | # install dependency
21 | npm install
22 |
23 | # develop
24 | npm run dev
25 | ```
26 |
27 | This will automatically open http://localhost:9528
28 |
29 | ## Build
30 |
31 | ```bash
32 | # build for test environment
33 | npm run build:stage
34 |
35 | # build for production environment
36 | npm run build:prod
37 | ```
38 |
39 | ## Advanced
40 |
41 | ```bash
42 | # preview the release environment effect
43 | npm run preview
44 |
45 | # preview the release environment effect + static resource analysis
46 | npm run preview -- --report
47 |
48 | # code format check
49 | npm run lint
50 |
51 | # code format check and auto fix
52 | npm run lint -- --fix
53 | ```
54 |
55 | ## License
56 |
57 | [MIT](https://github.com/HaitaoWang555/vue3-admin-pro/blob/master/LICENSE) license.
58 |
59 | Copyright (c) 2017-present HaitaoWang555
60 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@vue/cli-plugin-babel/preset'],
3 | plugins: [
4 | [
5 | 'import',
6 | {
7 | libraryName: 'element-plus',
8 | customStyleName: (name) => {
9 | name = name.slice(3)
10 | return `element-plus/packages/theme-chalk/src/${name}.scss`
11 | },
12 | },
13 | ],
14 | ],
15 | }
16 |
--------------------------------------------------------------------------------
/build/index.js:
--------------------------------------------------------------------------------
1 | const { run } = require('runjs')
2 | const chalk = require('chalk')
3 | const config = require('../vue.config.js')
4 | const rawArgv = process.argv.slice(2)
5 | const args = rawArgv.join(' ')
6 |
7 | if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
8 | const report = rawArgv.includes('--report')
9 |
10 | run(`vue-cli-service build ${args}`)
11 |
12 | const port = 9526
13 | const publicPath = config.publicPath
14 |
15 | var connect = require('connect')
16 | var serveStatic = require('serve-static')
17 | const app = connect()
18 |
19 | app.use(
20 | publicPath,
21 | serveStatic('./dist', {
22 | index: ['index.html', '/']
23 | })
24 | )
25 |
26 | app.listen(port, function () {
27 | console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`))
28 | if (report) {
29 | console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`))
30 | }
31 |
32 | })
33 | } else {
34 | run(`vue-cli-service build ${args}`)
35 | }
36 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "baseUrl": ".",
5 | "paths": {
6 | "@/*": ["src/*"]
7 | }
8 | },
9 | "exclude": ["node_modules", "dist"],
10 | "include": ["src/**/*"]
11 | }
12 |
--------------------------------------------------------------------------------
/mock/charts.js:
--------------------------------------------------------------------------------
1 | const Mock = require('mockjs')
2 |
3 | const data = {
4 | xData: [
5 | '1月',
6 | '2月',
7 | '3月',
8 | '4月',
9 | '5月',
10 | '6月',
11 | '7月',
12 | '8月',
13 | '9月',
14 | '10月',
15 | '11月',
16 | '12月',
17 | ],
18 | yData: [],
19 | }
20 |
21 | const stackData = {
22 | xData: [
23 | '1月',
24 | '2月',
25 | '3月',
26 | '4月',
27 | '5月',
28 | '6月',
29 | '7月',
30 | '8月',
31 | '9月',
32 | '10月',
33 | '11月',
34 | '12月',
35 | ],
36 | yData: {
37 | data1: [],
38 | data2: [],
39 | data3: [],
40 | data4: [],
41 | data5: [],
42 | data6: [],
43 | data7: [],
44 | data8: [],
45 | data9: [],
46 | },
47 | }
48 |
49 | for (let index = 0; index < 12; index++) {
50 | data.yData.push(Mock.mock('@integer(300, 5000)'))
51 | stackData.yData.data1.push(Mock.mock('@integer(300, 5000)'))
52 | stackData.yData.data2.push(Mock.mock('@integer(300, 5000)'))
53 | stackData.yData.data3.push(Mock.mock('@integer(300, 5000)'))
54 | stackData.yData.data4.push(Mock.mock('@integer(300, 5000)'))
55 | stackData.yData.data5.push(Mock.mock('@integer(300, 5000)'))
56 | stackData.yData.data6.push(Mock.mock('@integer(300, 5000)'))
57 | stackData.yData.data7.push(Mock.mock('@integer(300, 5000)'))
58 | stackData.yData.data8.push(Mock.mock('@integer(300, 5000)'))
59 | stackData.yData.data9.push(Mock.mock('@integer(300, 5000)'))
60 | }
61 |
62 | module.exports = [
63 | {
64 | url: '/vue3-admin-pro/charts/common',
65 | type: 'get',
66 | response: () => {
67 | return {
68 | code: 20000,
69 | data,
70 | }
71 | },
72 | },
73 | {
74 | url: '/vue3-admin-pro/charts/stack',
75 | type: 'get',
76 | response: () => {
77 | return {
78 | code: 20000,
79 | data: stackData,
80 | }
81 | },
82 | },
83 | ]
84 |
--------------------------------------------------------------------------------
/mock/index.js:
--------------------------------------------------------------------------------
1 | const Mock = require('mockjs')
2 | const { param2Obj } = require('./utils')
3 |
4 | const user = require('./user')
5 | const publicApi = require('./public')
6 | const table = require('./table')
7 | const role = require('./role')
8 | const article = require('./article')
9 | const search = require('./remote-search')
10 | const charts = require('./charts')
11 | const online = require('./online')
12 |
13 | const mocks = [
14 | ...publicApi,
15 | ...user,
16 | ...role,
17 | ...article,
18 | ...search,
19 | ...table,
20 | ...charts,
21 | ...online,
22 | ]
23 |
24 | // for front mock
25 | // please use it cautiously, it will redefine XMLHttpRequest,
26 | // which will cause many of your third-party libraries to be invalidated(like progress event).
27 | function mockXHR() {
28 | // mock patch
29 | // https://github.com/nuysoft/Mock/issues/300
30 | Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
31 | Mock.XHR.prototype.send = function () {
32 | if (this.custom.xhr) {
33 | this.custom.xhr.withCredentials = this.withCredentials || false
34 |
35 | if (this.responseType) {
36 | this.custom.xhr.responseType = this.responseType
37 | }
38 | }
39 | this.proxy_send(...arguments)
40 | }
41 |
42 | function XHR2ExpressReqWrap(respond) {
43 | return function (options) {
44 | let result = null
45 | if (respond instanceof Function) {
46 | const { body, type, url } = options
47 | // https://expressjs.com/en/4x/api.html#req
48 | result = respond({
49 | method: type,
50 | body: JSON.parse(body),
51 | query: param2Obj(url),
52 | })
53 | } else {
54 | result = respond
55 | }
56 | return Mock.mock(result)
57 | }
58 | }
59 |
60 | for (const i of mocks) {
61 | Mock.mock(
62 | new RegExp(i.url),
63 | i.type || 'get',
64 | XHR2ExpressReqWrap(i.response)
65 | )
66 | }
67 | }
68 |
69 | module.exports = {
70 | mocks,
71 | mockXHR,
72 | }
73 |
--------------------------------------------------------------------------------
/mock/mock-server.js:
--------------------------------------------------------------------------------
1 | const chokidar = require('chokidar')
2 | const bodyParser = require('body-parser')
3 | const chalk = require('chalk')
4 | const path = require('path')
5 | const Mock = require('mockjs')
6 |
7 | const mockDir = path.join(process.cwd(), 'mock')
8 |
9 | function registerRoutes(app) {
10 | let mockLastIndex
11 | const { mocks } = require('./index.js')
12 | const mocksForServer = mocks.map((route) => {
13 | return responseFake(route.url, route.type, route.response)
14 | })
15 | for (const mock of mocksForServer) {
16 | app[mock.type](mock.url, mock.response)
17 | mockLastIndex = app._router.stack.length
18 | }
19 | const mockRoutesLength = Object.keys(mocksForServer).length
20 | return {
21 | mockRoutesLength: mockRoutesLength,
22 | mockStartIndex: mockLastIndex - mockRoutesLength,
23 | }
24 | }
25 |
26 | function unregisterRoutes() {
27 | Object.keys(require.cache).forEach((i) => {
28 | if (i.includes(mockDir)) {
29 | delete require.cache[require.resolve(i)]
30 | }
31 | })
32 | }
33 |
34 | // for mock server
35 | const responseFake = (url, type, respond) => {
36 | return {
37 | url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`),
38 | type: type || 'get',
39 | response(req, res) {
40 | console.log('request invoke:' + req.path)
41 | res.json(
42 | Mock.mock(respond instanceof Function ? respond(req, res) : respond)
43 | )
44 | },
45 | }
46 | }
47 |
48 | module.exports = (app) => {
49 | // parse app.body
50 | // https://expressjs.com/en/4x/api.html#req.body
51 | app.use(bodyParser.json())
52 | app.use(
53 | bodyParser.urlencoded({
54 | extended: true,
55 | })
56 | )
57 |
58 | const mockRoutes = registerRoutes(app)
59 | let mockRoutesLength = mockRoutes.mockRoutesLength
60 | let mockStartIndex = mockRoutes.mockStartIndex
61 |
62 | // watch files, hot reload mock server
63 | chokidar
64 | .watch(mockDir, {
65 | ignored: /mock-server/,
66 | ignoreInitial: true,
67 | })
68 | .on('all', (event, path) => {
69 | if (event === 'change' || event === 'add') {
70 | try {
71 | // remove mock routes stack
72 | app._router.stack.splice(mockStartIndex, mockRoutesLength)
73 |
74 | // clear routes cache
75 | unregisterRoutes()
76 |
77 | const mockRoutes = registerRoutes(app)
78 | mockRoutesLength = mockRoutes.mockRoutesLength
79 | mockStartIndex = mockRoutes.mockStartIndex
80 |
81 | console.log(
82 | chalk.magentaBright(
83 | `\n > Mock Server hot reload success! changed ${path}`
84 | )
85 | )
86 | } catch (error) {
87 | console.log(chalk.redBright(error))
88 | }
89 | }
90 | })
91 | }
92 |
--------------------------------------------------------------------------------
/mock/public.js:
--------------------------------------------------------------------------------
1 | const Mock = require('mockjs')
2 |
3 | const data = Mock.mock('@integer(999, 9999)')
4 |
5 | module.exports = [
6 | {
7 | url: '/vue3-admin-pro/public/sendSms',
8 | type: 'get',
9 | response: () => {
10 | return {
11 | code: 20000,
12 | msg: 'success',
13 | data,
14 | }
15 | },
16 | },
17 | // submit
18 | {
19 | url: '/vue3-admin-pro/public/submit',
20 | type: 'post',
21 | response: () => {
22 | return {
23 | code: 20000,
24 | msg: 'success',
25 | }
26 | },
27 | },
28 | ]
29 |
--------------------------------------------------------------------------------
/mock/remote-search.js:
--------------------------------------------------------------------------------
1 | const Mock = require('mockjs')
2 |
3 | const NameList = []
4 | const count = 100
5 |
6 | for (let i = 0; i < count; i++) {
7 | NameList.push(
8 | Mock.mock({
9 | name: '@first',
10 | })
11 | )
12 | }
13 | NameList.push({ name: 'mock-Pan' })
14 |
15 | module.exports = [
16 | // username search
17 | {
18 | url: '/vue3-admin-pro/search/user',
19 | type: 'get',
20 | response: (config) => {
21 | const { name } = config.query
22 | const mockNameList = NameList.filter((item) => {
23 | const lowerCaseName = item.name.toLowerCase()
24 | return !(name && lowerCaseName.indexOf(name.toLowerCase()) < 0)
25 | })
26 | return {
27 | code: 20000,
28 | data: { items: mockNameList },
29 | }
30 | },
31 | },
32 |
33 | // transaction list
34 | {
35 | url: '/vue3-admin-pro/transaction/list',
36 | type: 'get',
37 | response: () => {
38 | return {
39 | code: 20000,
40 | data: {
41 | total: 20,
42 | 'items|20': [
43 | {
44 | order_no: '@guid()',
45 | timestamp: +Mock.Random.date('T'),
46 | username: '@name()',
47 | price: '@float(1000, 15000, 0, 2)',
48 | 'status|1': ['success', 'pending'],
49 | },
50 | ],
51 | },
52 | }
53 | },
54 | },
55 | ]
56 |
--------------------------------------------------------------------------------
/mock/role/index.js:
--------------------------------------------------------------------------------
1 | const Mock = require('mockjs')
2 | const { deepClone } = require('../utils')
3 | const { asyncRoutes, constantRoutes } = require('./routes.js')
4 |
5 | const routes = deepClone([...constantRoutes, ...asyncRoutes])
6 |
7 | const roles = [
8 | {
9 | key: 'admin',
10 | name: 'admin',
11 | description: 'Super Administrator. Have access to view all pages.',
12 | routes: routes,
13 | },
14 | {
15 | key: 'editor',
16 | name: 'editor',
17 | description: 'Normal Editor. Can see all pages except permission page',
18 | routes: routes.filter((i) => i.path !== '/permission'), // just a mock
19 | },
20 | {
21 | key: 'visitor',
22 | name: 'visitor',
23 | description:
24 | 'Just a visitor. Can only see the home page and the document page',
25 | routes: [
26 | {
27 | path: '',
28 | redirect: 'dashboard',
29 | children: [
30 | {
31 | path: 'dashboard',
32 | name: 'Dashboard',
33 | meta: { title: 'dashboard', icon: 'dashboard' },
34 | },
35 | ],
36 | },
37 | ],
38 | },
39 | ]
40 |
41 | module.exports = [
42 | // mock get all routes form server
43 | {
44 | url: '/vue3-admin-pro/routes',
45 | type: 'get',
46 | response: () => {
47 | return {
48 | code: 20000,
49 | data: routes,
50 | }
51 | },
52 | },
53 |
54 | // mock get all roles form server
55 | {
56 | url: '/vue3-admin-pro/roles',
57 | type: 'get',
58 | response: () => {
59 | return {
60 | code: 20000,
61 | data: roles,
62 | }
63 | },
64 | },
65 |
66 | // add role
67 | {
68 | url: '/vue3-admin-pro/role',
69 | type: 'post',
70 | response: {
71 | code: 20000,
72 | data: {
73 | key: Mock.mock('@integer(300, 5000)'),
74 | },
75 | },
76 | },
77 |
78 | // update role
79 | {
80 | url: '/vue3-admin-pro/role/[A-Za-z0-9]',
81 | type: 'put',
82 | response: {
83 | code: 20000,
84 | data: {
85 | status: 'success',
86 | },
87 | },
88 | },
89 |
90 | // delete role
91 | {
92 | url: '/vue3-admin-pro/role/[A-Za-z0-9]',
93 | type: 'delete',
94 | response: {
95 | code: 20000,
96 | data: {
97 | status: 'success',
98 | },
99 | },
100 | },
101 | ]
102 |
--------------------------------------------------------------------------------
/mock/table.js:
--------------------------------------------------------------------------------
1 | const Mock = require('mockjs')
2 |
3 | const data = Mock.mock({
4 | 'items|30': [
5 | {
6 | id: '@id',
7 | title: '@sentence(10, 20)',
8 | 'status|1': ['published', 'draft', 'deleted'],
9 | author: 'name',
10 | display_time: '@datetime',
11 | pageviews: '@integer(300, 5000)',
12 | },
13 | ],
14 | })
15 |
16 | module.exports = [
17 | {
18 | url: '/vue3-admin-pro/table/list',
19 | type: 'get',
20 | response: () => {
21 | const items = data.items
22 | return {
23 | code: 20000,
24 | data: {
25 | total: items.length,
26 | items: items,
27 | },
28 | }
29 | },
30 | },
31 | ]
32 |
--------------------------------------------------------------------------------
/mock/user.js:
--------------------------------------------------------------------------------
1 | const tokens = {
2 | admin: {
3 | token: 'admin-token',
4 | },
5 | editor: {
6 | token: 'editor-token',
7 | },
8 | }
9 |
10 | const users = {
11 | 'admin-token': {
12 | roles: ['admin'],
13 | introduction: 'I am a super administrator',
14 | avatar:
15 | 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
16 | name: 'Super Admin',
17 | },
18 | 'editor-token': {
19 | roles: ['editor'],
20 | introduction: 'I am an editor',
21 | avatar:
22 | 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
23 | name: 'Normal Editor',
24 | },
25 | }
26 |
27 | module.exports = [
28 | // user login
29 | {
30 | url: '/vue3-admin-pro/user/login',
31 | type: 'post',
32 | response: (config) => {
33 | const { username } = config.body
34 | const token = tokens[username]
35 |
36 | // mock error
37 | if (!token) {
38 | return {
39 | code: 60204,
40 | message: 'Account and password are incorrect.',
41 | }
42 | }
43 |
44 | return {
45 | code: 20000,
46 | data: token,
47 | }
48 | },
49 | },
50 |
51 | // get user info
52 | {
53 | url: '/vue3-admin-pro/user/info.*',
54 | type: 'get',
55 | response: (config) => {
56 | const { token } = config.query
57 | const info = users[token]
58 |
59 | // mock error
60 | if (!info) {
61 | return {
62 | code: 50008,
63 | message: 'Login failed, unable to get user details.',
64 | }
65 | }
66 |
67 | return {
68 | code: 20000,
69 | data: info,
70 | }
71 | },
72 | },
73 |
74 | // user logout
75 | {
76 | url: '/vue3-admin-pro/user/logout',
77 | type: 'post',
78 | response: () => {
79 | return {
80 | code: 20000,
81 | data: 'success',
82 | }
83 | },
84 | },
85 | ]
86 |
--------------------------------------------------------------------------------
/mock/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param {string} url
3 | * @returns {Object}
4 | */
5 | function param2Obj(url) {
6 | const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
7 | if (!search) {
8 | return {}
9 | }
10 | const obj = {}
11 | const searchArr = search.split('&')
12 | searchArr.forEach((v) => {
13 | const index = v.indexOf('=')
14 | if (index !== -1) {
15 | const name = v.substring(0, index)
16 | const val = v.substring(index + 1, v.length)
17 | obj[name] = val
18 | }
19 | })
20 | return obj
21 | }
22 |
23 | /**
24 | * This is just a simple version of deep copy
25 | * Has a lot of edge cases bug
26 | * If you want to use a perfect deep copy, use lodash's _.cloneDeep
27 | * @param {Object} source
28 | * @returns {Object}
29 | */
30 | function deepClone(source) {
31 | if (!source && typeof source !== 'object') {
32 | throw new Error('error arguments', 'deepClone')
33 | }
34 | const targetObj = source.constructor === Array ? [] : {}
35 | Object.keys(source).forEach((keys) => {
36 | if (source[keys] && typeof source[keys] === 'object') {
37 | targetObj[keys] = deepClone(source[keys])
38 | } else {
39 | targetObj[keys] = source[keys]
40 | }
41 | })
42 | return targetObj
43 | }
44 |
45 | module.exports = {
46 | param2Obj,
47 | deepClone,
48 | }
49 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue3-admin-pro",
3 | "version": "0.1.0",
4 | "private": true,
5 | "homepage": "https://HaitaoWang555.github.io/vue3-admin-pro",
6 | "scripts": {
7 | "dev": "vue-cli-service serve",
8 | "build:prod": "vue-cli-service build --report",
9 | "preview": "node build/index.js --preview",
10 | "build:stage": "vue-cli-service build --mode staging",
11 | "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
12 | "lint": "vue-cli-service lint"
13 | },
14 | "dependencies": {
15 | "axios": "^0.21.1",
16 | "clipboard": "^2.0.6",
17 | "core-js": "^3.6.5",
18 | "echarts": "^5.0.2",
19 | "element-plus": "^1.0.2-beta.71",
20 | "normalize.css": "^8.0.1",
21 | "nprogress": "^0.2.0",
22 | "path-to-regexp": "^6.2.0",
23 | "store": "^2.0.12",
24 | "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.7",
25 | "vue": "^3.2.4",
26 | "vue-router": "^4.0.11",
27 | "vuedraggable": "^4.0.1",
28 | "vuex": "^4.0.2"
29 | },
30 | "devDependencies": {
31 | "@tailwindcss/postcss7-compat": "^2.2.7",
32 | "@typescript-eslint/eslint-plugin": "^4.10.0",
33 | "@typescript-eslint/parser": "^4.10.0",
34 | "@vue/cli-plugin-babel": "~4.5.0",
35 | "@vue/cli-plugin-eslint": "~4.5.0",
36 | "@vue/cli-service": "^4.5.13",
37 | "@vue/compiler-sfc": "^3.2.4",
38 | "@vue/eslint-config-standard": "^6.0.0",
39 | "babel-eslint": "^10.1.0",
40 | "babel-plugin-component": "^1.1.1",
41 | "babel-plugin-import": "^1.13.3",
42 | "chalk": "^4.1.0",
43 | "connect": "^3.7.0",
44 | "eslint": "^7.25.0",
45 | "eslint-config-prettier": "^7.1.0",
46 | "eslint-plugin-html": "^6.1.1",
47 | "eslint-plugin-import": "^2.22.1",
48 | "eslint-plugin-node": "^11.1.0",
49 | "eslint-plugin-prettier": "^3.3.0",
50 | "eslint-plugin-promise": "^4.2.1",
51 | "eslint-plugin-standard": "^5.0.0",
52 | "eslint-plugin-vue": "^7.9.0",
53 | "mockjs": "^1.1.0",
54 | "prettier": "^2.2.1",
55 | "runjs": "^4.4.2",
56 | "sass": "^1.38.0",
57 | "sass-loader": "^10.2.0",
58 | "script-ext-html-webpack-plugin": "^2.1.5",
59 | "svg-sprite-loader": "^5.2.1",
60 | "svgo": "^1.3.2",
61 | "webpack-bundle-analyzer": "^4.4.1"
62 | },
63 | "browserslist": [
64 | "> 1%",
65 | "last 2 versions",
66 | "not dead"
67 | ]
68 | }
69 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HaitaoWang555/vue3-admin-pro/03c70012a3068dbc2fb33f4c71316f0ed091f278/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/public/province/aomen.json:
--------------------------------------------------------------------------------
1 | {"type":"FeatureCollection","features":[{"id":"820001","type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[["@@LADC^umZ@DONWE@DALBBF@H@DFBBTC"],["@@P@LC@AGM@OECMBABBTCD@DDH"]],"encodeOffsets":[[[116285,22746]],[[116303,22746]]]},"properties":{"cp":[113.552965,22.207882],"name":"花地玛堂区","childNum":2}},{"id":"820002","type":"Feature","geometry":{"type":"Polygon","coordinates":["@@MK@CA@AAGDEB@NVFJG"],"encodeOffsets":[[116281,22734]]},"properties":{"cp":[113.549052,22.199175],"name":"花王堂区","childNum":1}},{"id":"820003","type":"Feature","geometry":{"type":"Polygon","coordinates":["@@EGOB@DNLHE@C"],"encodeOffsets":[[116285,22729]]},"properties":{"cp":[113.550252,22.193791],"name":"望德堂区","childNum":1}},{"id":"820004","type":"Feature","geometry":{"type":"Polygon","coordinates":["@@YMVAN@BFCBBDAFHDBBFDHIJJEFDPCHHlYJQ"],"encodeOffsets":[[116313,22707]]},"properties":{"cp":[113.55374,22.188119],"name":"大堂区","childNum":1}},{"id":"820005","type":"Feature","geometry":{"type":"Polygon","coordinates":["@@JICGAECACGEBAAEDBFNXB@"],"encodeOffsets":[[116266,22728]]},"properties":{"cp":[113.54167,22.187778],"name":"风顺堂区","childNum":1}},{"id":"820006","type":"Feature","geometry":{"type":"Polygon","coordinates":["@@ ZNWRquZCBCC@AEA@@ADCDCAACEAGBQ@INEL"],"encodeOffsets":[[116265,22694]]},"properties":{"cp":[113.558783,22.154124],"name":"嘉模堂区","childNum":1}},{"id":"820007","type":"Feature","geometry":{"type":"Polygon","coordinates":["@@MOIAIEI@@GE@AAUCBdCFIFR@HAFBBDDBDCBC@@FB@BDDDA\\M"],"encodeOffsets":[[116316,22676]]},"properties":{"cp":[113.56925,22.136546],"name":"路凼填海区","childNum":1}},{"id":"820008","type":"Feature","geometry":{"type":"Polygon","coordinates":["@@DKMMa_GC_COD@dVDBBF@@HJ@JFJBNPZK"],"encodeOffsets":[[116329,22670]]},"properties":{"cp":[113.559954,22.124049],"name":"圣方济各堂区","childNum":1}}],"UTF8Encoding":true}
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
22 |
--------------------------------------------------------------------------------
/src/api/article.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function fetchList(query) {
4 | return request({
5 | url: '/vue3-admin-pro/article/list',
6 | method: 'get',
7 | params: query,
8 | })
9 | }
10 |
11 | export function fetchArticle(id) {
12 | return request({
13 | url: '/vue3-admin-pro/article/detail',
14 | method: 'get',
15 | params: { id },
16 | })
17 | }
18 |
19 | export function fetchPv(pv) {
20 | return request({
21 | url: '/vue3-admin-pro/article/pv',
22 | method: 'get',
23 | params: { pv },
24 | })
25 | }
26 |
27 | export function createArticle(data) {
28 | return request({
29 | url: '/vue3-admin-pro/article/create',
30 | method: 'post',
31 | data,
32 | })
33 | }
34 |
35 | export function updateArticle(data) {
36 | return request({
37 | url: '/vue3-admin-pro/article/update',
38 | method: 'post',
39 | data,
40 | })
41 | }
42 | export function getStatus() {
43 | return request({
44 | url: '/vue3-admin-pro/article/getStatus',
45 | method: 'get',
46 | })
47 | }
48 |
--------------------------------------------------------------------------------
/src/api/charts.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | /**
4 | * 获取图表通用数据
5 | * @returns
6 | */
7 | export function getLineList() {
8 | return request({
9 | url: '/vue3-admin-pro/charts/common',
10 | method: 'get',
11 | })
12 | }
13 |
14 | export function getLineStackList() {
15 | return request({
16 | url: '/vue3-admin-pro/charts/stack',
17 | method: 'get',
18 | })
19 | }
20 | export function getBarList() {
21 | return request({
22 | url: '/vue3-admin-pro/charts/common',
23 | method: 'get',
24 | })
25 | }
26 |
27 | export function getBarStackList() {
28 | return request({
29 | url: '/vue3-admin-pro/charts/stack',
30 | method: 'get',
31 | })
32 | }
33 | export function getPieList() {
34 | return request({
35 | url: '/vue3-admin-pro/charts/common',
36 | method: 'get',
37 | })
38 | }
39 |
40 | export function getPieRoseTypeList() {
41 | return request({
42 | url: '/vue3-admin-pro/charts/stack',
43 | method: 'get',
44 | })
45 | }
46 | export function getMapJson(name) {
47 | return request({
48 | url: `/vue3-admin-pro/province/${name}.json`,
49 | method: 'get',
50 | baseURL: '',
51 | })
52 | }
53 |
--------------------------------------------------------------------------------
/src/api/online.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function getOnlineListOptions(query) {
4 | return request({
5 | url: '/vue3-admin-pro/online/options',
6 | method: 'get',
7 | params: query,
8 | })
9 | }
10 | export function getOnlineList(query) {
11 | return request({
12 | url: '/vue3-admin-pro/online/list',
13 | method: 'get',
14 | params: query,
15 | })
16 | }
17 | export function get(url, query) {
18 | return request({
19 | url: url,
20 | method: 'get',
21 | params: query,
22 | })
23 | }
24 | export function post(url, data) {
25 | return request({
26 | url: url,
27 | method: 'post',
28 | data,
29 | })
30 | }
31 |
--------------------------------------------------------------------------------
/src/api/public.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function sendSms(params) {
4 | return request({
5 | url: '/vue3-admin-pro/public/sendSms',
6 | method: 'get',
7 | params,
8 | })
9 | }
10 | export function submit(data) {
11 | return request({
12 | url: '/vue3-admin-pro/public/submit',
13 | method: 'post',
14 | data,
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/src/api/table.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function getList(params) {
4 | return request({
5 | url: '/vue3-admin-pro/table/list',
6 | method: 'get',
7 | params,
8 | })
9 | }
10 |
--------------------------------------------------------------------------------
/src/api/user.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function login(data) {
4 | return request({
5 | url: '/vue3-admin-pro/user/login',
6 | method: 'post',
7 | data,
8 | })
9 | }
10 |
11 | export function getInfo(token) {
12 | return request({
13 | url: '/vue3-admin-pro/user/info',
14 | method: 'get',
15 | params: { token },
16 | })
17 | }
18 |
19 | export function logout() {
20 | return request({
21 | url: '/vue3-admin-pro/user/logout',
22 | method: 'post',
23 | })
24 | }
25 |
--------------------------------------------------------------------------------
/src/assets/401_images/401.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HaitaoWang555/vue3-admin-pro/03c70012a3068dbc2fb33f4c71316f0ed091f278/src/assets/401_images/401.gif
--------------------------------------------------------------------------------
/src/assets/404_images/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HaitaoWang555/vue3-admin-pro/03c70012a3068dbc2fb33f4c71316f0ed091f278/src/assets/404_images/404.png
--------------------------------------------------------------------------------
/src/assets/404_images/404_cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HaitaoWang555/vue3-admin-pro/03c70012a3068dbc2fb33f4c71316f0ed091f278/src/assets/404_images/404_cloud.png
--------------------------------------------------------------------------------
/src/components/Breadcrumb/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ item.meta.title }}
10 | {{ item.meta.title }}
11 |
12 |
13 |
14 |
15 |
16 |
74 |
75 |
88 |
--------------------------------------------------------------------------------
/src/components/Hamburger/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
38 |
39 |
51 |
--------------------------------------------------------------------------------
/src/components/Pagination/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
96 |
97 |
109 |
--------------------------------------------------------------------------------
/src/components/ProDialog/draggable.js:
--------------------------------------------------------------------------------
1 | export default function (el) {
2 | const dialogHeaderEl = el.querySelector('.el-dialog__header')
3 | const dragDom = el
4 | dialogHeaderEl.style.cssText += ';cursor:move;'
5 | dragDom.style.cssText += ';top:0px;'
6 |
7 | // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
8 | const getStyle = (function () {
9 | if (window.document.currentStyle) {
10 | return (dom, attr) => dom.currentStyle[attr]
11 | } else {
12 | return (dom, attr) => getComputedStyle(dom, false)[attr]
13 | }
14 | })()
15 |
16 | if (dialogHeaderEl.onmousedown) return
17 |
18 | dialogHeaderEl.onmousedown = (e) => {
19 | // 鼠标按下,计算当前元素距离可视区的距离
20 | const disX = e.clientX - dialogHeaderEl.offsetLeft
21 | const disY = e.clientY - dialogHeaderEl.offsetTop
22 |
23 | const dragDomWidth = dragDom.offsetWidth
24 | const dragDomHeight = dragDom.offsetHeight
25 |
26 | const screenWidth = document.body.clientWidth
27 | const screenHeight = document.body.clientHeight
28 |
29 | const minDragDomLeft = dragDom.offsetLeft
30 | const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth
31 |
32 | const minDragDomTop = dragDom.offsetTop
33 | const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomHeight
34 |
35 | // 获取到的值带px 正则匹配替换
36 | let styL = getStyle(dragDom, 'left')
37 | let styT = getStyle(dragDom, 'top')
38 |
39 | if (styL.includes('%')) {
40 | // eslint-disable-next-line no-useless-escape
41 | styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100)
42 | // eslint-disable-next-line no-useless-escape
43 | styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100)
44 | } else {
45 | styL = +styL.replace(/\px/g, '')
46 | styT = +styT.replace(/\px/g, '')
47 | }
48 |
49 | document.onmousemove = function (e) {
50 | // 通过事件委托,计算移动的距离
51 | let left = e.clientX - disX
52 | let top = e.clientY - disY
53 |
54 | // 边界处理
55 | if (-left > minDragDomLeft) {
56 | left = -minDragDomLeft
57 | } else if (left > maxDragDomLeft) {
58 | left = maxDragDomLeft
59 | }
60 |
61 | if (-top > minDragDomTop) {
62 | top = -minDragDomTop
63 | } else if (top > maxDragDomTop) {
64 | top = maxDragDomTop
65 | }
66 |
67 | // 移动当前元素
68 | dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`
69 | }
70 |
71 | document.onmouseup = function () {
72 | document.onmousemove = null
73 | document.onmouseup = null
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/components/ProDialog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
32 |
33 |
34 |
35 |
36 |
37 |
113 |
--------------------------------------------------------------------------------
/src/components/Redirect/index.vue:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/src/components/SvgIcon/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
11 |
12 |
59 |
60 |
75 |
--------------------------------------------------------------------------------
/src/components/sendCode/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ sendCodes.text }}
11 |
12 |
13 |
72 |
--------------------------------------------------------------------------------
/src/core/echarts.js:
--------------------------------------------------------------------------------
1 | // Minimal Bundle 按需引入
2 |
3 | import * as echarts from 'echarts/core'
4 |
5 | import {
6 | BarChart,
7 | LineChart,
8 | PieChart,
9 | MapChart,
10 | PictorialBarChart,
11 | RadarChart,
12 | ScatterChart,
13 | } from 'echarts/charts'
14 |
15 | import {
16 | TitleComponent,
17 | TooltipComponent,
18 | GridComponent,
19 | PolarComponent,
20 | AriaComponent,
21 | ParallelComponent,
22 | LegendComponent,
23 | RadarComponent,
24 | VisualMapComponent,
25 | GeoComponent,
26 | } from 'echarts/components'
27 |
28 | import { CanvasRenderer } from 'echarts/renderers'
29 |
30 | echarts.use([
31 | LegendComponent,
32 | TitleComponent,
33 | TooltipComponent,
34 | GridComponent,
35 | PolarComponent,
36 | AriaComponent,
37 | ParallelComponent,
38 | BarChart,
39 | LineChart,
40 | PieChart,
41 | MapChart,
42 | RadarChart,
43 | CanvasRenderer,
44 | PictorialBarChart,
45 | ScatterChart,
46 | RadarComponent,
47 | VisualMapComponent,
48 | GeoComponent,
49 | ])
50 |
51 | export default echarts
52 |
--------------------------------------------------------------------------------
/src/core/globalProperties.js:
--------------------------------------------------------------------------------
1 | import { parseTime } from '@/utils'
2 |
3 | export default (app) => {
4 | // filters
5 | app.config.globalProperties.$filters = {
6 | parseTime(value) {
7 | return parseTime(value, '{y}-{m}-{d} {h}:{i}:{s}')
8 | },
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/core/use.js:
--------------------------------------------------------------------------------
1 | // use components
2 | import SvgIcon from '@/components/SvgIcon/index.vue' // svg component
3 | import ProDialog from '@/components/ProDialog/index.vue'
4 | import ProTable from '@/components/ProTable/index.vue'
5 | import ProForm from '@/components/ProForm/index.vue'
6 |
7 | export default (app) => {
8 | app.component('SvgIcon', SvgIcon)
9 | app.component('ProDialog', ProDialog)
10 | app.component('ProTable', ProTable)
11 | app.component('ProForm', ProForm)
12 | }
13 |
--------------------------------------------------------------------------------
/src/directive/clipboard/clipboard.js:
--------------------------------------------------------------------------------
1 | // Inspired by https://github.com/Inndy/vue-clipboard2
2 | import Clipboard from 'clipboard'
3 | if (!Clipboard) {
4 | throw new Error('you should npm install `clipboard` --save at first ')
5 | }
6 |
7 | export default {
8 | mounted(el, binding) {
9 | if (binding.arg === 'success') {
10 | el._v_clipboard_success = binding.value
11 | } else if (binding.arg === 'error') {
12 | el._v_clipboard_error = binding.value
13 | } else {
14 | const clipboard = new Clipboard(el, {
15 | text() {
16 | return binding.value
17 | },
18 | action() {
19 | return binding.arg === 'cut' ? 'cut' : 'copy'
20 | },
21 | })
22 | clipboard.on('success', (e) => {
23 | const callback = el._v_clipboard_success
24 | callback && callback(e) // eslint-disable-line
25 | })
26 | clipboard.on('error', (e) => {
27 | const callback = el._v_clipboard_error
28 | callback && callback(e) // eslint-disable-line
29 | })
30 | el._v_clipboard = clipboard
31 | }
32 | },
33 | update(el, binding) {
34 | if (binding.arg === 'success') {
35 | el._v_clipboard_success = binding.value
36 | } else if (binding.arg === 'error') {
37 | el._v_clipboard_error = binding.value
38 | } else {
39 | el._v_clipboard.text = function () {
40 | return binding.value
41 | }
42 | el._v_clipboard.action = function () {
43 | return binding.arg === 'cut' ? 'cut' : 'copy'
44 | }
45 | }
46 | },
47 | unmounted(el, binding) {
48 | if (binding.arg === 'success') {
49 | delete el._v_clipboard_success
50 | } else if (binding.arg === 'error') {
51 | delete el._v_clipboard_error
52 | } else {
53 | el._v_clipboard.destroy()
54 | delete el._v_clipboard
55 | }
56 | },
57 | }
58 |
--------------------------------------------------------------------------------
/src/directive/clipboard/index.js:
--------------------------------------------------------------------------------
1 | import Clipboard from './clipboard'
2 |
3 | const install = function (Vue) {
4 | Vue.directive('Clipboard', Clipboard)
5 | }
6 |
7 | if (window.Vue) {
8 | window.clipboard = Clipboard
9 | Vue.use(install); // eslint-disable-line
10 | }
11 |
12 | Clipboard.install = install
13 | export default Clipboard
14 |
--------------------------------------------------------------------------------
/src/directive/fullscreen/fullscreen.js:
--------------------------------------------------------------------------------
1 | import {
2 | addClass,
3 | hasClass,
4 | removeClass,
5 | getScrollBarWidth,
6 | } from '@/utils/index'
7 |
8 | const scrollBarWidth = getScrollBarWidth()
9 | let elFull, scrollY
10 |
11 | function handleClick(el) {
12 | if (hasClass(el, 'content-screenfull')) {
13 | removeClass(el, 'content-screenfull')
14 | document.documentElement.style.overflow = ''
15 | document.body.style.borderRight = ''
16 | window.scrollTo(0, scrollY)
17 | const callback = el._v_fullscreen_normal
18 | callback && callback() // eslint-disable-line
19 | } else {
20 | scrollY = window.scrollY
21 | addClass(el, 'content-screenfull')
22 | document.documentElement.style.overflow = 'hidden'
23 | document.body.style.borderRight = scrollBarWidth + 'px solid transparent'
24 | const callback = el._v_fullscreen_big
25 | callback && callback() // eslint-disable-line
26 | }
27 | }
28 |
29 | export default {
30 | mounted(el, binding) {
31 | switch (binding.arg) {
32 | case 'top':
33 | elFull = document.createElement('i')
34 | elFull.className = 'el-icon-full-screen absolute-wrap'
35 | elFull.style.top = binding.value + 'px'
36 | addClass(el, 'relative-wrap')
37 | el.appendChild(elFull)
38 | elFull.addEventListener(
39 | 'click',
40 | () => {
41 | handleClick(el)
42 | },
43 | false
44 | )
45 | break
46 | case 'big':
47 | el._v_fullscreen_big = binding.value
48 | break
49 | case 'normal':
50 | el._v_fullscreen_normal = binding.value
51 | break
52 |
53 | default:
54 | break
55 | }
56 | },
57 | unmounted(el) {
58 | if (!elFull) return
59 | elFull.removeEventListener(
60 | 'click',
61 | () => {
62 | handleClick(el)
63 | },
64 | false
65 | )
66 | elFull = null
67 | },
68 | }
69 |
--------------------------------------------------------------------------------
/src/directive/fullscreen/index.js:
--------------------------------------------------------------------------------
1 | import fullscreen from './fullscreen'
2 |
3 | const install = function (Vue) {
4 | Vue.directive('fullscreen', fullscreen)
5 | }
6 |
7 | if (window.Vue) {
8 | window.fullscreen = fullscreen
9 | Vue.use(install); // eslint-disable-line
10 | }
11 |
12 | fullscreen.install = install
13 | export default fullscreen
14 |
--------------------------------------------------------------------------------
/src/directive/index.js:
--------------------------------------------------------------------------------
1 | import fullscreen from '@/directive/fullscreen'
2 | import clipboard from '@/directive/clipboard'
3 | import waves from '@/directive/waves'
4 |
5 | export { fullscreen, clipboard, waves }
6 |
--------------------------------------------------------------------------------
/src/directive/waves/index.js:
--------------------------------------------------------------------------------
1 | import waves from './waves'
2 |
3 | const install = function (Vue) {
4 | Vue.directive('waves', waves)
5 | }
6 |
7 | if (window.Vue) {
8 | window.waves = waves
9 | Vue.use(install); // eslint-disable-line
10 | }
11 |
12 | waves.install = install
13 | export default waves
14 |
--------------------------------------------------------------------------------
/src/directive/waves/waves.css:
--------------------------------------------------------------------------------
1 | .waves-ripple {
2 | position: absolute;
3 | border-radius: 100%;
4 | background-color: rgba(0, 0, 0, 0.15);
5 | background-clip: padding-box;
6 | pointer-events: none;
7 | -webkit-user-select: none;
8 | -moz-user-select: none;
9 | -ms-user-select: none;
10 | user-select: none;
11 | -webkit-transform: scale(0);
12 | -ms-transform: scale(0);
13 | transform: scale(0);
14 | opacity: 1;
15 | }
16 |
17 | .waves-ripple.z-active {
18 | opacity: 0;
19 | -webkit-transform: scale(2);
20 | -ms-transform: scale(2);
21 | transform: scale(2);
22 | -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
23 | transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
24 | transition: opacity 1.2s ease-out, transform 0.6s ease-out;
25 | transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
26 | }
--------------------------------------------------------------------------------
/src/directive/waves/waves.js:
--------------------------------------------------------------------------------
1 | import './waves.css'
2 |
3 | const context = '@@wavesContext'
4 |
5 | function handleClick(el, binding) {
6 | function handle(e) {
7 | const customOpts = Object.assign({}, binding.value)
8 | const opts = Object.assign(
9 | {
10 | ele: el, // 波纹作用元素
11 | type: 'hit', // hit 点击位置扩散 center中心点扩展
12 | color: 'rgba(0, 0, 0, 0.15)', // 波纹颜色
13 | },
14 | customOpts
15 | )
16 | const target = opts.ele
17 | if (target) {
18 | target.style.position = 'relative'
19 | target.style.overflow = 'hidden'
20 | const rect = target.getBoundingClientRect()
21 | let ripple = target.querySelector('.waves-ripple')
22 | if (!ripple) {
23 | ripple = document.createElement('span')
24 | ripple.className = 'waves-ripple'
25 | ripple.style.height = ripple.style.width =
26 | Math.max(rect.width, rect.height) + 'px'
27 | target.appendChild(ripple)
28 | } else {
29 | ripple.className = 'waves-ripple'
30 | }
31 | switch (opts.type) {
32 | case 'center':
33 | ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'
34 | ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px'
35 | break
36 | default:
37 | ripple.style.top =
38 | (e.pageY -
39 | rect.top -
40 | ripple.offsetHeight / 2 -
41 | document.documentElement.scrollTop || document.body.scrollTop) +
42 | 'px'
43 | ripple.style.left =
44 | (e.pageX -
45 | rect.left -
46 | ripple.offsetWidth / 2 -
47 | document.documentElement.scrollLeft || document.body.scrollLeft) +
48 | 'px'
49 | }
50 | ripple.style.backgroundColor = opts.color
51 | ripple.className = 'waves-ripple z-active'
52 | return false
53 | }
54 | }
55 |
56 | if (!el[context]) {
57 | el[context] = {
58 | removeHandle: handle,
59 | }
60 | } else {
61 | el[context].removeHandle = handle
62 | }
63 |
64 | return handle
65 | }
66 |
67 | export default {
68 | mounted(el, binding) {
69 | el.addEventListener('click', handleClick(el, binding), false)
70 | },
71 | update(el, binding) {
72 | el.removeEventListener('click', el[context].removeHandle, false)
73 | el.addEventListener('click', handleClick(el, binding), false)
74 | },
75 | unmounted(el) {
76 | el.removeEventListener('click', el[context].removeHandle, false)
77 | el[context] = null
78 | delete el[context]
79 | },
80 | }
81 |
--------------------------------------------------------------------------------
/src/hooks/dict.js:
--------------------------------------------------------------------------------
1 | const typeFilter = {
2 | CN: 'China',
3 | EU: 'Eurozone',
4 | JP: 'Japan',
5 | US: 'USA',
6 | }
7 | const statusMap = {
8 | published: 'success',
9 | draft: 'info',
10 | deleted: 'danger',
11 | }
12 |
13 | const dict = {
14 | typeFilter,
15 | statusMap,
16 | }
17 |
18 | export default function useDict(type, key) {
19 | if (type && key) {
20 | if (dict[type]) {
21 | return dict[type][key]
22 | } else {
23 | console.error('没有设置此字典 - ' + type)
24 | }
25 | } else {
26 | return ''
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/hooks/echarts.js:
--------------------------------------------------------------------------------
1 | import echarts from '@/core/echarts'
2 | import { unref, nextTick, onMounted, onUnmounted } from 'vue'
3 | import { debounce } from '@/utils'
4 | import { useStore } from 'vuex'
5 |
6 | export function useEcharts(elRef) {
7 | let mychart
8 | let voptions
9 |
10 | const store = useStore()
11 |
12 | function init() {
13 | if (!elRef) return
14 | const el = unref(elRef)
15 | mychart = echarts.init(el)
16 | }
17 |
18 | function setOptions(options) {
19 | if (!mychart) return
20 | voptions = options
21 | mychart.setOption(options)
22 | }
23 |
24 | function resize(obj) {
25 | mychart && mychart.resize(obj)
26 | if (voptions.geo && voptions.geo.roam) {
27 | const center = store.state.charts.center
28 | if (center && center.length > 0) {
29 | voptions.geo.center = center
30 | } else {
31 | voptions.geo.center = [104.114129, 35.950339]
32 | }
33 | mychart.setOption(voptions)
34 | }
35 | }
36 |
37 | function getInstance() {
38 | return mychart
39 | }
40 |
41 | // sidebar resize
42 | let $_sidebarElm = null
43 | let $_resizeHandler = null
44 |
45 | function $_sidebarResizeHandler(e) {
46 | if (e.propertyName === 'width') {
47 | $_resizeHandler()
48 | }
49 | }
50 | function initListener() {
51 | $_resizeHandler = debounce(() => {
52 | resize()
53 | }, 100)
54 | window.addEventListener('resize', $_resizeHandler)
55 |
56 | $_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
57 | $_sidebarElm &&
58 | $_sidebarElm.addEventListener('transitionend', $_sidebarResizeHandler)
59 | }
60 | function destroyListener() {
61 | window.removeEventListener('resize', $_resizeHandler)
62 | $_resizeHandler = null
63 |
64 | $_sidebarElm &&
65 | $_sidebarElm.removeEventListener('transitionend', $_sidebarResizeHandler)
66 | }
67 |
68 | onMounted(() => {
69 | nextTick(() => {
70 | init()
71 | initListener()
72 | })
73 | })
74 |
75 | onUnmounted(() => {
76 | mychart.dispose()
77 | mychart = null
78 | destroyListener()
79 | })
80 |
81 | return {
82 | setOptions,
83 | resize,
84 | echarts,
85 | getInstance,
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/hooks/table.js:
--------------------------------------------------------------------------------
1 | const calendarTypeOptions = [
2 | { key: 'CN', display_name: 'China' },
3 | { key: 'US', display_name: 'USA' },
4 | { key: 'JP', display_name: 'Japan' },
5 | { key: 'EU', display_name: 'Eurozone' },
6 | ]
7 |
8 | // arr to obj, such as { CN : "China", US : "USA" }
9 | const calendarTypeKeyValue = calendarTypeOptions.reduce((acc, cur) => {
10 | acc[cur.key] = cur.display_name
11 | return acc
12 | }, {})
13 |
14 | export function useFilter() {
15 | function typeFilter(type) {
16 | return calendarTypeKeyValue[type]
17 | }
18 | function statusFilter(status) {
19 | const statusMap = {
20 | published: 'success',
21 | draft: 'info',
22 | deleted: 'danger',
23 | }
24 | return statusMap[status]
25 | }
26 | return {
27 | typeFilter,
28 | statusFilter,
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/icons/index.js:
--------------------------------------------------------------------------------
1 | const req = require.context('./svg', false, /\.svg$/)
2 | const requireAll = (requireContext) => requireContext.keys().map(requireContext)
3 | requireAll(req)
4 |
--------------------------------------------------------------------------------
/src/icons/svg/404.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/bug.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/chart.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/clipboard.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/component.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/dashboard.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/documentation.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/drag.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/edit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/education.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/email.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/example.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/excel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/exit-fullscreen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/eye-open.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/eye.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/form.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/fullscreen-exit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/fullscreen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/guide.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/international.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/language.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/link.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/list.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/lock.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/message.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/money.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/nested.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/password.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/pdf.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/people.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/peoples.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/shopping.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/size.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/skill.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/star.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/tab.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/table.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/theme.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/tree-table.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/tree.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/user.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/wechat.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/zip.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svgo.yml:
--------------------------------------------------------------------------------
1 | # replace default config
2 |
3 | # multipass: true
4 | # full: true
5 |
6 | plugins:
7 |
8 | # - name
9 | #
10 | # or:
11 | # - name: false
12 | # - name: true
13 | #
14 | # or:
15 | # - name:
16 | # param1: 1
17 | # param2: 2
18 |
19 | - removeAttrs:
20 | attrs:
21 | - 'fill'
22 | - 'fill-rule'
23 |
--------------------------------------------------------------------------------
/src/layout/components/AppMain.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
25 |
26 |
44 |
45 |
53 |
--------------------------------------------------------------------------------
/src/layout/components/Settings/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Page style setting
5 |
6 |
7 | Open Tags-View
8 |
9 |
10 |
11 |
12 | Fixed Header
13 |
14 |
15 |
16 |
17 | Sidebar Logo
18 |
19 |
20 |
21 |
22 |
23 |
24 |
67 |
68 |
93 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/FixiOSBug.js:
--------------------------------------------------------------------------------
1 | import { ref, onMounted, computed } from 'vue'
2 | import { useStore } from 'vuex'
3 |
4 | export function useFixBug() {
5 | const store = useStore()
6 | const device = computed(() => store.state.app.device)
7 |
8 | const subMenu = ref(null)
9 |
10 | function fixBugIniOS() {
11 | if (subMenu.value) {
12 | const handleMouseleave = subMenu.value.handleMouseleave
13 | subMenu.value.handleMouseleave = (e) => {
14 | if (device.value === 'mobile') {
15 | return
16 | }
17 | handleMouseleave(e)
18 | }
19 | }
20 | }
21 |
22 | onMounted(() => {
23 | fixBugIniOS()
24 | })
25 |
26 | return {
27 | subMenu,
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/Item.vue:
--------------------------------------------------------------------------------
1 |
42 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/Link.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
48 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/Logo.vue:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
44 |
45 |
94 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
68 |
--------------------------------------------------------------------------------
/src/layout/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Navbar } from './Navbar.vue'
2 | export { default as Sidebar } from './Sidebar/index.vue'
3 | export { default as AppMain } from './AppMain.vue'
4 | export { default as Settings } from './Settings/index.vue'
5 | export { default as TagsView } from './TagsView/index.vue'
6 |
--------------------------------------------------------------------------------
/src/layout/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
73 |
74 |
115 |
--------------------------------------------------------------------------------
/src/layout/mixin/ResizeHandler.js:
--------------------------------------------------------------------------------
1 | import { onMounted, computed, onBeforeMount, onBeforeUnmount, watch } from 'vue'
2 | import { useStore } from 'vuex'
3 | import { useRoute } from 'vue-router'
4 |
5 | const { body } = document
6 | const WIDTH = 992 // refer to Bootstrap's responsive design
7 |
8 | export function useResizeHandler() {
9 | const store = useStore()
10 | const route = useRoute()
11 |
12 | const sidebar = computed(() => store.state.app.sidebar)
13 | const device = computed(() => store.state.app.device)
14 |
15 | // use $_ for mixins properties
16 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
17 | function $_isMobile() {
18 | const rect = body.getBoundingClientRect()
19 | return rect.width - 1 < WIDTH
20 | }
21 |
22 | function $_resizeHandler() {
23 | if (!document.hidden) {
24 | const isMobile = $_isMobile()
25 | store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
26 |
27 | if (isMobile) {
28 | store.dispatch('app/closeSideBar', { withoutAnimation: true })
29 | }
30 | }
31 | }
32 |
33 | onMounted(() => {
34 | const isMobile = $_isMobile()
35 | if (isMobile) {
36 | store.dispatch('app/toggleDevice', 'mobile')
37 | store.dispatch('app/closeSideBar', { withoutAnimation: true })
38 | }
39 | })
40 |
41 | onBeforeMount(() => {
42 | window.addEventListener('resize', $_resizeHandler)
43 | })
44 |
45 | onBeforeUnmount(() => {
46 | window.removeEventListener('resize', $_resizeHandler)
47 | })
48 |
49 | watch(
50 | () => route.path,
51 | () => {
52 | if (device.value === 'mobile' && sidebar.value.opened) {
53 | store.dispatch('app/closeSideBar', { withoutAnimation: false })
54 | }
55 | }
56 | )
57 | }
58 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 |
3 | import App from '@/App.vue'
4 | // style
5 | import 'tailwindcss/tailwind.css' // tailwind
6 | import 'normalize.css/normalize.css' // a modern alternative to CSS resets
7 | import '@/styles/index.scss' // global css
8 |
9 | // import bootstrap from './core/bootstrap'
10 |
11 | // use lazy load element-plus
12 | import loadElementPlus from '@/core/lazy_use'
13 | // use components
14 | import loadComponents from '@/core/use'
15 | // globalProperties
16 | import globalProperties from '@/core/globalProperties'
17 | // icon
18 | import(/* webpackChunkName: "icon" */ '@/icons')
19 | // vue router
20 | import router from '@/router'
21 | // vue vuex
22 | import store from '@/store'
23 |
24 | import '@/permission' // permission control
25 |
26 | import * as directive from '@/directive' // global directive
27 |
28 | /**
29 | * If you don't want to use mock-server
30 | * you want to use MockJs for mock api
31 | * you can execute: mockXHR()
32 | *
33 | * Currently MockJs will be used in the production environment,
34 | * please remove it before going online ! ! !
35 | */
36 | if (process.env.NODE_ENV === 'production') {
37 | const { mockXHR } = require('../mock')
38 | mockXHR()
39 | }
40 |
41 | const app = createApp(App)
42 |
43 | loadElementPlus(app)
44 | loadComponents(app)
45 | globalProperties(app)
46 |
47 | // register global directive
48 | Object.keys(directive).forEach((key) => {
49 | directive[key].install(app)
50 | })
51 |
52 | // element-plus config
53 | app.config.globalProperties.$ELEMENT = { size: 'small', zIndex: 3000 }
54 |
55 | app.use(router).use(store).mount('#app')
56 |
--------------------------------------------------------------------------------
/src/permission.js:
--------------------------------------------------------------------------------
1 | import router from './router'
2 | import store from './store'
3 | import { ElMessage } from 'element-plus'
4 | import NProgress from 'nprogress' // progress bar
5 | import 'nprogress/nprogress.css' // progress bar style
6 | import { getToken } from '@/utils/auth' // get token from cookie
7 | import getPageTitle from '@/utils/get-page-title'
8 |
9 | NProgress.configure({ showSpinner: false }) // NProgress Configuration
10 |
11 | const whiteList = ['/login'] // no redirect whitelist
12 |
13 | router.beforeEach(async (to, from, next) => {
14 | // start progress bar
15 | NProgress.start()
16 |
17 | // set page title
18 | document.title = getPageTitle(to.meta.title)
19 |
20 | // determine whether the user has logged in
21 | const hasToken = getToken()
22 |
23 | if (hasToken) {
24 | if (to.path === '/login') {
25 | // if is logged in, redirect to the home page
26 | next({ path: '/' })
27 | NProgress.done()
28 | } else {
29 | const hasGetUserInfo = store.getters.name
30 | if (hasGetUserInfo) {
31 | next()
32 | } else {
33 | try {
34 | // get user info
35 | await store.dispatch('user/getInfo')
36 |
37 | next()
38 | } catch (error) {
39 | // remove token and go to login page to re-login
40 | await store.dispatch('user/resetToken')
41 | ElMessage.error(error || 'Has Error')
42 | next(`/login?redirect=${to.path}`)
43 | NProgress.done()
44 | }
45 | }
46 | }
47 | } else {
48 | /* has no token*/
49 |
50 | if (whiteList.indexOf(to.path) !== -1) {
51 | // in the free login whitelist, go directly
52 | next()
53 | } else {
54 | // other pages that do not have permission to access are redirected to the login page.
55 | next(`/login?redirect=${to.path}`)
56 | NProgress.done()
57 | }
58 | }
59 | })
60 |
61 | router.afterEach(() => {
62 | // finish progress bar
63 | NProgress.done()
64 | })
65 |
--------------------------------------------------------------------------------
/src/router/modules/chart.js:
--------------------------------------------------------------------------------
1 | /** When your routing charts is too long, you can split it into small modules **/
2 |
3 | import Layout from '@/layout/index.vue'
4 |
5 | const chartsRouter = {
6 | path: '/charts',
7 | component: Layout,
8 | redirect: '/charts/complex-charts',
9 | name: 'charts',
10 | meta: {
11 | title: 'charts',
12 | icon: 'chart',
13 | },
14 | children: [
15 | {
16 | path: 'line-charts',
17 | component: () =>
18 | import(/* webpackChunkName: "charts" */ '@/views/charts/Line.vue'),
19 | name: 'LineCharts',
20 | meta: { title: 'line' },
21 | },
22 | {
23 | path: 'bar-charts',
24 | component: () =>
25 | import(/* webpackChunkName: "charts" */ '@/views/charts/Bar.vue'),
26 | name: 'BarCharts',
27 | meta: { title: 'bar' },
28 | },
29 | {
30 | path: 'pie-charts',
31 | component: () =>
32 | import(/* webpackChunkName: "charts" */ '@/views/charts/Pie.vue'),
33 | name: 'PieCharts',
34 | meta: { title: 'pie' },
35 | },
36 | {
37 | path: 'map-charts',
38 | component: () =>
39 | import(/* webpackChunkName: "charts" */ '@/views/charts/Map.vue'),
40 | name: 'MapCharts',
41 | meta: { title: 'map' },
42 | },
43 | ],
44 | }
45 | export default chartsRouter
46 |
--------------------------------------------------------------------------------
/src/router/modules/components-page.js:
--------------------------------------------------------------------------------
1 | /** When your routing table is too long, you can split it into small modules **/
2 |
3 | import Layout from '@/layout/index.vue'
4 |
5 | const componentsRouter = {
6 | path: '/components',
7 | component: Layout,
8 | redirect: 'noRedirect',
9 | name: 'componentsPages',
10 | meta: {
11 | title: 'Components',
12 | icon: 'el-icon-files',
13 | },
14 | children: [
15 | {
16 | path: 'pro-dialog',
17 | component: () =>
18 | import(
19 | /* webpackChunkName: "components" */ '@/views/components-page/pro-dialog-view.vue'
20 | ),
21 | name: 'pro-dialog',
22 | meta: { title: 'pro-dialog', noCache: true },
23 | },
24 | {
25 | path: 'tailwindcss',
26 | name: 'Tailwindcss',
27 | component: () =>
28 | import(
29 | /* webpackChunkName: "components" */ '@/views/tailwindcss/index'
30 | ),
31 | meta: { title: 'Tailwindcss' },
32 | },
33 | ],
34 | }
35 |
36 | export default componentsRouter
37 |
--------------------------------------------------------------------------------
/src/router/modules/error-page.js:
--------------------------------------------------------------------------------
1 | /** When your routing table is too long, you can split it into small modules **/
2 |
3 | import Layout from '@/layout/index.vue'
4 |
5 | const errorRouter = {
6 | path: '/error',
7 | component: Layout,
8 | redirect: 'noRedirect',
9 | name: 'ErrorPages',
10 | meta: {
11 | title: 'Error Pages',
12 | icon: '404',
13 | },
14 | children: [
15 | {
16 | path: '401',
17 | component: () =>
18 | import(/* webpackChunkName: "error" */ '@/views/error-page/401.vue'),
19 | name: 'Page401',
20 | meta: { title: '401', noCache: true },
21 | },
22 | {
23 | path: '404',
24 | component: () =>
25 | import(/* webpackChunkName: "error" */ '@/views/error-page/404.vue'),
26 | name: 'Page404',
27 | meta: { title: '404', noCache: true },
28 | },
29 | ],
30 | }
31 |
32 | export default errorRouter
33 |
--------------------------------------------------------------------------------
/src/router/modules/form.js:
--------------------------------------------------------------------------------
1 | /** When your routing table is too long, you can split it into small modules **/
2 |
3 | import Layout from '@/layout/index.vue'
4 |
5 | const formRouter = {
6 | path: '/form',
7 | component: Layout,
8 | name: 'Form',
9 | redirect: '/form/index',
10 | meta: {
11 | title: 'Form',
12 | icon: 'form',
13 | },
14 | children: [
15 | {
16 | path: 'index',
17 | name: 'BasicForm',
18 | component: () =>
19 | import(/* webpackChunkName: "form" */ '@/views/form/index.vue'),
20 | meta: { title: 'Basic Form' },
21 | },
22 | {
23 | path: 'advanced-form',
24 | name: 'AdvancedForm',
25 | component: () =>
26 | import(/* webpackChunkName: "form" */ '@/views/form/advanced-form.vue'),
27 | meta: { title: 'Advanced Form' },
28 | },
29 | {
30 | path: 'modal-form',
31 | name: 'ModalForm',
32 | component: () =>
33 | import(/* webpackChunkName: "form" */ '@/views/form/modal-form.vue'),
34 | meta: { title: 'Modal Form' },
35 | },
36 | {
37 | path: 'linkage-form',
38 | name: 'LinkageForm',
39 | component: () =>
40 | import(/* webpackChunkName: "form" */ '@/views/form/linkage-form.vue'),
41 | meta: { title: 'Linkage Form' },
42 | },
43 | {
44 | path: 'step-form',
45 | name: 'StepForm',
46 | component: () =>
47 | import(/* webpackChunkName: "form" */ '@/views/form/step-form.vue'),
48 | meta: { title: 'Step Form' },
49 | },
50 | ],
51 | }
52 |
53 | export default formRouter
54 |
--------------------------------------------------------------------------------
/src/router/modules/nested.js:
--------------------------------------------------------------------------------
1 | /** When your routing table is too long, you can split it into small modules **/
2 |
3 | import Layout from '@/layout/index.vue'
4 |
5 | const nestedRouter = {
6 | path: '/nested',
7 | component: Layout,
8 | redirect: '/nested/menu1/menu1-1',
9 | name: 'Nested',
10 | meta: {
11 | title: 'Nested Routes',
12 | icon: 'nested',
13 | },
14 | children: [
15 | {
16 | path: 'menu1',
17 | component: () =>
18 | import(
19 | /* webpackChunkName: "nested" */ '@/views/nested/menu1/index.vue'
20 | ), // Parent router-view
21 | name: 'Menu1',
22 | meta: { title: 'Menu 1' },
23 | redirect: '/nested/menu1/menu1-1',
24 | children: [
25 | {
26 | path: 'menu1-1',
27 | component: () =>
28 | import(
29 | /* webpackChunkName: "nested" */ '@/views/nested/menu1/menu1-1/index.vue'
30 | ),
31 | name: 'Menu1-1',
32 | meta: { title: 'Menu 1-1' },
33 | },
34 | {
35 | path: 'menu1-2',
36 | component: () =>
37 | import(
38 | /* webpackChunkName: "nested" */ '@/views/nested/menu1/menu1-2/index.vue'
39 | ),
40 | name: 'Menu1-2',
41 | redirect: '/nested/menu1/menu1-2/menu1-2-1',
42 | meta: { title: 'Menu 1-2' },
43 | children: [
44 | {
45 | path: 'menu1-2-1',
46 | component: () =>
47 | import(
48 | /* webpackChunkName: "nested" */ '@/views/nested/menu1/menu1-2/menu1-2-1/index.vue'
49 | ),
50 | name: 'Menu1-2-1',
51 | meta: { title: 'Menu 1-2-1' },
52 | },
53 | {
54 | path: 'menu1-2-2',
55 | component: () =>
56 | import(
57 | /* webpackChunkName: "nested" */ '@/views/nested/menu1/menu1-2/menu1-2-2/index.vue'
58 | ),
59 | name: 'Menu1-2-2',
60 | meta: { title: 'Menu 1-2-2' },
61 | },
62 | ],
63 | },
64 | {
65 | path: 'menu1-3',
66 | component: () =>
67 | import(
68 | /* webpackChunkName: "nested" */ '@/views/nested/menu1/menu1-3/index.vue'
69 | ),
70 | name: 'Menu1-3',
71 | meta: { title: 'Menu 1-3' },
72 | },
73 | ],
74 | },
75 | {
76 | path: 'menu2',
77 | name: 'Menu2',
78 | component: () =>
79 | import(
80 | /* webpackChunkName: "nested" */ '@/views/nested/menu2/index.vue'
81 | ),
82 | meta: { title: 'Menu 2' },
83 | },
84 | ],
85 | }
86 |
87 | export default nestedRouter
88 |
--------------------------------------------------------------------------------
/src/router/modules/table.js:
--------------------------------------------------------------------------------
1 | /** When your routing table is too long, you can split it into small modules **/
2 |
3 | import Layout from '@/layout/index.vue'
4 |
5 | const tableRouter = {
6 | path: '/table',
7 | component: Layout,
8 | redirect: '/table/complex-table',
9 | name: 'Table',
10 | meta: {
11 | title: 'Table',
12 | icon: 'table',
13 | },
14 | children: [
15 | {
16 | path: 'inline-edit-table',
17 | component: () =>
18 | import(
19 | /* webpackChunkName: "table" */ '@/views/table/inline-edit-table.vue'
20 | ),
21 | name: 'InlineEditTable',
22 | meta: { title: 'Inline Edit' },
23 | },
24 | {
25 | path: 'complex-table',
26 | component: () =>
27 | import(
28 | /* webpackChunkName: "table" */ '@/views/table/complex-table.vue'
29 | ),
30 | name: 'ComplexTable',
31 | meta: { title: 'Complex Table' },
32 | },
33 | ],
34 | }
35 | export default tableRouter
36 |
--------------------------------------------------------------------------------
/src/settings.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | title: 'Vue Admin Pro',
3 |
4 | /**
5 | * @type {boolean} true | false
6 | * @description Whether show the settings right-panel
7 | */
8 | showSettings: true,
9 |
10 | /**
11 | * @type {boolean} true | false
12 | * @description Whether need tagsView
13 | */
14 | tagsView: true,
15 |
16 | /**
17 | * @type {boolean} true | false
18 | * @description Whether fix the header
19 | */
20 | fixedHeader: false,
21 |
22 | /**
23 | * @type {boolean} true | false
24 | * @description Whether show the logo in sidebar
25 | */
26 | sidebarLogo: false,
27 | }
28 |
--------------------------------------------------------------------------------
/src/store/getters.js:
--------------------------------------------------------------------------------
1 | const getters = {
2 | sidebar: (state) => state.app.sidebar,
3 | size: (state) => state.app.size,
4 | device: (state) => state.app.device,
5 | visitedViews: (state) => state.tagsView.visitedViews,
6 | cachedViews: (state) => state.tagsView.cachedViews,
7 | token: (state) => state.user.token,
8 | avatar: (state) => state.user.avatar,
9 | name: (state) => state.user.name,
10 | introduction: (state) => state.user.introduction,
11 | roles: (state) => state.user.roles,
12 | permission_routes: (state) => state.permission.routes,
13 | errorLogs: (state) => state.errorLog.logs,
14 | }
15 | export default getters
16 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'vuex'
2 |
3 | import getters from './getters'
4 | // import app from './modules/app'
5 | // import settings from './modules/settings'
6 | // import user from './modules/user'
7 |
8 | // https://webpack.js.org/guides/dependency-management/#requirecontext
9 | const modulesFiles = require.context('./modules', true, /\.js$/)
10 |
11 | // you do not need `import app from './modules/app'`
12 | // it will auto require all vuex module from modules file
13 | const modules = modulesFiles.keys().reduce((modules, modulePath) => {
14 | // set './app.js' => 'app'
15 | const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
16 | const value = modulesFiles(modulePath)
17 | modules[moduleName] = value.default
18 | return modules
19 | }, {})
20 |
21 | const store = createStore({
22 | modules,
23 | getters,
24 | })
25 |
26 | export default store
27 |
--------------------------------------------------------------------------------
/src/store/modules/app.js:
--------------------------------------------------------------------------------
1 | import Store from 'store'
2 |
3 | const state = () => {
4 | return {
5 | sidebar: {
6 | opened: Store.get('sidebarStatus') ? !!+Store.get('sidebarStatus') : true,
7 | withoutAnimation: false,
8 | },
9 | device: 'desktop',
10 | }
11 | }
12 |
13 | const mutations = {
14 | TOGGLE_SIDEBAR: (state) => {
15 | state.sidebar.opened = !state.sidebar.opened
16 | state.sidebar.withoutAnimation = false
17 | if (state.sidebar.opened) {
18 | Store.set('sidebarStatus', 1)
19 | } else {
20 | Store.set('sidebarStatus', 0)
21 | }
22 | },
23 | CLOSE_SIDEBAR: (state, withoutAnimation) => {
24 | Store.set('sidebarStatus', 0)
25 | state.sidebar.opened = false
26 | state.sidebar.withoutAnimation = withoutAnimation
27 | },
28 | TOGGLE_DEVICE: (state, device) => {
29 | state.device = device
30 | },
31 | }
32 |
33 | const actions = {
34 | toggleSideBar({ commit }) {
35 | commit('TOGGLE_SIDEBAR')
36 | },
37 | closeSideBar({ commit }, { withoutAnimation }) {
38 | commit('CLOSE_SIDEBAR', withoutAnimation)
39 | },
40 | toggleDevice({ commit }, device) {
41 | commit('TOGGLE_DEVICE', device)
42 | },
43 | }
44 |
45 | export default {
46 | namespaced: true,
47 | state,
48 | mutations,
49 | actions,
50 | }
51 |
--------------------------------------------------------------------------------
/src/store/modules/charts.js:
--------------------------------------------------------------------------------
1 | const state = {
2 | center: [],
3 | }
4 |
5 | const mutations = {
6 | ADD_CENTER: (state, center) => {
7 | state.center = center
8 | },
9 | CLEAR_CENTER: (state) => {
10 | state.center = []
11 | },
12 | }
13 |
14 | const actions = {
15 | addCENTER({ commit }, center) {
16 | commit('ADD_CENTER', center)
17 | },
18 | clearCENTER({ commit }) {
19 | commit('CLEAR_CENTER')
20 | },
21 | }
22 |
23 | export default {
24 | namespaced: true,
25 | state,
26 | mutations,
27 | actions,
28 | }
29 |
--------------------------------------------------------------------------------
/src/store/modules/errorLog.js:
--------------------------------------------------------------------------------
1 | const state = {
2 | logs: [],
3 | }
4 |
5 | const mutations = {
6 | ADD_ERROR_LOG: (state, log) => {
7 | state.logs.push(log)
8 | },
9 | CLEAR_ERROR_LOG: (state) => {
10 | state.logs.splice(0)
11 | },
12 | }
13 |
14 | const actions = {
15 | addErrorLog({ commit }, log) {
16 | commit('ADD_ERROR_LOG', log)
17 | },
18 | clearErrorLog({ commit }) {
19 | commit('CLEAR_ERROR_LOG')
20 | },
21 | }
22 |
23 | export default {
24 | namespaced: true,
25 | state,
26 | mutations,
27 | actions,
28 | }
29 |
--------------------------------------------------------------------------------
/src/store/modules/permission.js:
--------------------------------------------------------------------------------
1 | import { asyncRoutes, constantRoutes } from '@/router'
2 |
3 | /**
4 | * Use meta.role to determine if the current user has permission
5 | * @param roles
6 | * @param route
7 | */
8 | function hasPermission(roles, route) {
9 | if (route.meta && route.meta.roles) {
10 | return roles.some((role) => route.meta.roles.includes(role))
11 | } else {
12 | return true
13 | }
14 | }
15 |
16 | /**
17 | * Filter asynchronous routing tables by recursion
18 | * @param routes asyncRoutes
19 | * @param roles
20 | */
21 | export function filterAsyncRoutes(routes, roles) {
22 | const res = []
23 |
24 | routes.forEach((route) => {
25 | const tmp = { ...route }
26 | if (hasPermission(roles, tmp)) {
27 | if (tmp.children) {
28 | tmp.children = filterAsyncRoutes(tmp.children, roles)
29 | }
30 | res.push(tmp)
31 | }
32 | })
33 |
34 | return res
35 | }
36 |
37 | const state = {
38 | routes: [],
39 | addRoutes: [],
40 | }
41 |
42 | const mutations = {
43 | SET_ROUTES: (state, routes) => {
44 | state.addRoutes = routes
45 | state.routes = constantRoutes.concat(routes)
46 | },
47 | }
48 |
49 | const actions = {
50 | generateRoutes({ commit }, roles) {
51 | return new Promise((resolve) => {
52 | let accessedRoutes
53 | if (roles.includes('admin')) {
54 | accessedRoutes = asyncRoutes || []
55 | } else {
56 | accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
57 | }
58 | commit('SET_ROUTES', accessedRoutes)
59 | resolve(accessedRoutes)
60 | })
61 | },
62 | }
63 |
64 | export default {
65 | namespaced: true,
66 | state,
67 | mutations,
68 | actions,
69 | }
70 |
--------------------------------------------------------------------------------
/src/store/modules/settings.js:
--------------------------------------------------------------------------------
1 | import defaultSettings from '@/settings'
2 |
3 | const { showSettings, tagsView, fixedHeader, sidebarLogo } = defaultSettings
4 |
5 | const state = {
6 | showSettings: showSettings,
7 | tagsView: tagsView,
8 | fixedHeader: fixedHeader,
9 | sidebarLogo: sidebarLogo,
10 | }
11 |
12 | const mutations = {
13 | CHANGE_SETTING: (state, { key, value }) => {
14 | // eslint-disable-next-line no-prototype-builtins
15 | if (state.hasOwnProperty(key)) {
16 | state[key] = value
17 | }
18 | },
19 | }
20 |
21 | const actions = {
22 | changeSetting({ commit }, data) {
23 | commit('CHANGE_SETTING', data)
24 | },
25 | }
26 |
27 | export default {
28 | namespaced: true,
29 | state,
30 | mutations,
31 | actions,
32 | }
33 |
--------------------------------------------------------------------------------
/src/styles/editable-cell.scss:
--------------------------------------------------------------------------------
1 | .editable-cell {
2 | position: relative;
3 |
4 | .editable-cell-input-wrapper,
5 | .editable-cell-text-wrapper {
6 | padding-right: 24px;
7 | display : flex;
8 | align-items : center;
9 | }
10 |
11 | .editable-cell-icon,
12 | .editable-cell-icon-check {
13 | position : absolute;
14 | right : 0;
15 | font-size: 14px;
16 | cursor : pointer;
17 | }
18 |
19 | .editable-cell-icon {
20 | line-height: 36px;
21 | display : none;
22 | right : 10px;
23 | }
24 |
25 | .editable-cell-icon-check {
26 | line-height: 36px;
27 | }
28 |
29 | .editable-cell-icon-close {
30 | right : 20px;
31 | position : absolute;
32 | font-size : 14px;
33 | cursor : pointer;
34 | line-height: 36px;
35 | }
36 |
37 | &:hover .editable-cell-icon {
38 | display: inline-block;
39 | }
40 |
41 | .editable-cell-icon:hover,
42 | .editable-cell-icon-close:hover,
43 | .editable-cell-icon-check:hover {
44 | color: #108ee9;
45 | }
46 | .el-input--mini .el-input__inner {
47 | height: 24px;
48 | line-height: 24px;
49 | }
50 | .el-input--mini .el-input__icon {
51 | line-height: 26px;
52 | }
53 | }
--------------------------------------------------------------------------------
/src/styles/element-ui.scss:
--------------------------------------------------------------------------------
1 | // cover some element-ui styles
2 |
3 | .el-breadcrumb__inner,
4 | .el-breadcrumb__inner a {
5 | font-weight: 400 !important;
6 | }
7 |
8 | .el-upload {
9 | input[type="file"] {
10 | display: none !important;
11 | }
12 | }
13 |
14 | .el-upload__input {
15 | display: none;
16 | }
17 |
18 |
19 | // to fixed https://github.com/ElemeFE/element/issues/2461
20 | .el-dialog {
21 | transform: none;
22 | left: 0;
23 | position: relative;
24 | margin: 0 auto;
25 | }
26 |
27 | // refine element ui upload
28 | .upload-container {
29 | .el-upload {
30 | width: 100%;
31 |
32 | .el-upload-dragger {
33 | width: 100%;
34 | height: 200px;
35 | }
36 | }
37 | }
38 |
39 | // dropdown
40 | .el-dropdown-menu {
41 | a {
42 | display: block
43 | }
44 | }
45 |
46 | // to fix el-date-picker css style
47 | .el-range-separator {
48 | box-sizing: content-box;
49 | }
50 |
51 | .fixed-width {
52 | .el-button--mini {
53 | padding: 7px 10px;
54 | min-width: 60px;
55 | }
56 | }
57 |
58 | .el-pagination {
59 | text-align: center;
60 | }
61 | .el-table__body-wrapper {
62 | min-height: calc(100vh - 300px);
63 | }
64 |
65 | // pro-table sticky
66 | .pro-table.sticky {
67 | .el-table {
68 | overflow: inherit;
69 | }
70 | .el-table__header-wrapper {
71 | position: sticky;
72 | top: 0;
73 | z-index: 1;
74 | }
75 | }
76 |
77 | // search-form
78 | #search-form-wrap .search-form .el-form-item {
79 | width: 100%;
80 | display: flex;
81 | align-items: center;
82 | justify-content: flex-start;
83 | .el-form-item__content {
84 | flex-grow: 1;
85 | }
86 | }
87 |
88 | // CardChart
89 | .CardChart {
90 | .el-card__body {
91 | padding: 0;
92 | }
93 | }
94 |
95 | // ProDialog
96 | .ProDialog {
97 |
98 | border-radius: 4px;
99 | box-shadow: 0 4px 12px rgb(0 0 0 / 15%);
100 | margin-bottom: 0!important;
101 |
102 | .el-dialog__header {
103 | padding: 16px 24px;
104 | color: rgba(0, 0, 0, 0.65);
105 | background: #fff;
106 | border-bottom: 1px solid #e8e8e8;
107 | border-radius: 4px 4px 0 0;
108 | }
109 | .el-dialog__footer {
110 | padding: 10px 16px;
111 | border-top: 1px solid #e8e8e8;
112 | border-radius: 0 0 4px 4px;
113 | }
114 | .el-dialog__body {
115 | max-height: calc(100vh - 15vh - 110px);
116 | overflow-y: auto;
117 | }
118 | &.is-fullscreen {
119 | overflow: hidden;
120 | .el-dialog__body {
121 | height: calc(100vh - 112px);
122 | max-height: calc(100vh - 112px);
123 | overflow: auto;
124 | }
125 | }
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/src/styles/mixin.scss:
--------------------------------------------------------------------------------
1 | @mixin clearfix {
2 | &:after {
3 | content: "";
4 | display: table;
5 | clear: both;
6 | }
7 | }
8 |
9 | @mixin scrollBar {
10 | &::-webkit-scrollbar-track-piece {
11 | background: #d3dce6;
12 | }
13 |
14 | &::-webkit-scrollbar {
15 | width: 6px;
16 | }
17 |
18 | &::-webkit-scrollbar-thumb {
19 | background: #99a9bf;
20 | border-radius: 20px;
21 | }
22 | }
23 |
24 | @mixin relative {
25 | position: relative;
26 | width: 100%;
27 | height: 100%;
28 | }
29 |
--------------------------------------------------------------------------------
/src/styles/transition.scss:
--------------------------------------------------------------------------------
1 | // global transition css
2 |
3 | /* fade */
4 | .fade-enter-active,
5 | .fade-leave-active {
6 | transition: opacity 0.28s;
7 | }
8 |
9 | .fade-enter,
10 | .fade-leave-active {
11 | opacity: 0;
12 | }
13 |
14 | /* fade-transform */
15 | .fade-transform-leave-active,
16 | .fade-transform-enter-active {
17 | transition: all .5s;
18 | }
19 |
20 | .fade-transform-enter {
21 | opacity: 0;
22 | transform: translateX(-30px);
23 | }
24 |
25 | .fade-transform-leave-to {
26 | opacity: 0;
27 | transform: translateX(30px);
28 | }
29 |
30 | /* breadcrumb transition */
31 | .breadcrumb-enter-active,
32 | .breadcrumb-leave-active {
33 | transition: all .5s;
34 | }
35 |
36 | .breadcrumb-enter,
37 | .breadcrumb-leave-active {
38 | opacity: 0;
39 | transform: translateX(20px);
40 | }
41 |
42 | .breadcrumb-move {
43 | transition: all .5s;
44 | }
45 |
46 | .breadcrumb-leave-active {
47 | position: absolute;
48 | }
49 |
--------------------------------------------------------------------------------
/src/styles/variables.scss:
--------------------------------------------------------------------------------
1 | // theme
2 | /* theme color */
3 | $--color-primary: #1890ff;
4 |
5 | /* icon font path, required */
6 | $--font-path: '~element-plus/lib/theme-chalk/fonts';
7 |
8 | @import "~element-plus/packages/theme-chalk/src/index";
9 |
10 |
11 | // sidebar
12 | $menuText:#bfcbd9;
13 | $menuActiveText:#409EFF;
14 | $subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951
15 |
16 | $menuBg:#001529;
17 | $menuHover:#263445;
18 |
19 | $subMenuBg:#000c17;
20 | $subMenuHover:#001528;
21 |
22 | $sideBarWidth: 210px;
23 |
24 | // the :export directive is the magic sauce for webpack
25 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
26 | :export {
27 | menuText: $menuText;
28 | menuActiveText: $menuActiveText;
29 | subMenuActiveText: $subMenuActiveText;
30 | menuBg: $menuBg;
31 | menuHover: $menuHover;
32 | subMenuBg: $subMenuBg;
33 | subMenuHover: $subMenuHover;
34 | sideBarWidth: $sideBarWidth;
35 | }
36 |
--------------------------------------------------------------------------------
/src/utils/auth.js:
--------------------------------------------------------------------------------
1 | import Store from 'store'
2 |
3 | const TokenKey = 'Admin-Token'
4 |
5 | export function getToken() {
6 | return Store.get(TokenKey)
7 | }
8 |
9 | export function setToken(token) {
10 | return Store.set(TokenKey, token)
11 | }
12 |
13 | export function removeToken() {
14 | return Store.remove(TokenKey)
15 | }
16 |
--------------------------------------------------------------------------------
/src/utils/clipboard.js:
--------------------------------------------------------------------------------
1 | import { ElMessage } from 'element-plus'
2 | import Clipboard from 'clipboard'
3 |
4 | function clipboardSuccess() {
5 | ElMessage({
6 | message: 'Copy successfully',
7 | type: 'success',
8 | duration: 1500,
9 | })
10 | }
11 |
12 | function clipboardError() {
13 | ElMessage({
14 | message: 'Copy failed',
15 | type: 'error',
16 | })
17 | }
18 |
19 | export default function handleClipboard(text, event) {
20 | const clipboard = new Clipboard(event.target, {
21 | text: () => text,
22 | })
23 | clipboard.on('success', () => {
24 | clipboardSuccess()
25 | clipboard.destroy()
26 | })
27 | clipboard.on('error', () => {
28 | clipboardError()
29 | clipboard.destroy()
30 | })
31 | clipboard.onClick(event)
32 | }
33 |
--------------------------------------------------------------------------------
/src/utils/get-page-title.js:
--------------------------------------------------------------------------------
1 | import defaultSettings from '@/settings'
2 |
3 | const title = defaultSettings.title || 'Vue Element-plus Admin'
4 |
5 | export default function getPageTitle(pageTitle) {
6 | if (pageTitle) {
7 | return `${pageTitle} - ${title}`
8 | }
9 | return `${title}`
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { ElMessageBox, ElMessage } from 'element-plus'
3 | import store from '@/store'
4 | import { getToken } from '@/utils/auth'
5 |
6 | // create an axios instance
7 | const service = axios.create({
8 | baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
9 | // withCredentials: true, // send cookies when cross-domain requests
10 | timeout: 5000, // request timeout
11 | })
12 |
13 | // request interceptor
14 | service.interceptors.request.use(
15 | (config) => {
16 | // do something before request is sent
17 |
18 | if (store.getters.token) {
19 | // let each request carry token
20 | // ['X-Token'] is a custom headers key
21 | // please modify it according to the actual situation
22 | config.headers['X-Token'] = getToken()
23 | }
24 | return config
25 | },
26 | (error) => {
27 | // do something with request error
28 | console.log(error) // for debug
29 | return Promise.reject(error)
30 | }
31 | )
32 |
33 | // response interceptor
34 | service.interceptors.response.use(
35 | /**
36 | * If you want to get http information such as headers or status
37 | * Please return response => response
38 | */
39 |
40 | /**
41 | * Determine the request status by custom code
42 | * Here is just an example
43 | * You can also judge the status by HTTP Status Code
44 | */
45 | (response) => {
46 | const res = response.data
47 | if (res.code === undefined) {
48 | return res
49 | }
50 |
51 | // if the custom code is not 20000, it is judged as an error.
52 | if (res.code !== 20000) {
53 | ElMessage({
54 | message: res.message || 'Error',
55 | type: 'error',
56 | duration: 5 * 1000,
57 | })
58 |
59 | // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
60 | if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
61 | // to re-login
62 | ElMessageBox.confirm(
63 | '您已登出,可以取消停留在此页面上,或者再次登录',
64 | '重新登录',
65 | {
66 | confirmButtonText: '重新登录',
67 | cancelButtonText: '离开',
68 | type: 'warning',
69 | }
70 | ).then(() => {
71 | store.dispatch('user/resetToken').then(() => {
72 | location.reload()
73 | })
74 | })
75 | }
76 | return Promise.reject(new Error(res.message || 'Error'))
77 | } else {
78 | return res
79 | }
80 | },
81 | (error) => {
82 | console.log('err' + error) // for debug
83 | ElMessage({
84 | message: error.message,
85 | type: 'error',
86 | duration: 5 * 1000,
87 | })
88 | return Promise.reject(error)
89 | }
90 | )
91 |
92 | export default service
93 |
--------------------------------------------------------------------------------
/src/utils/scroll-to.js:
--------------------------------------------------------------------------------
1 | Math.easeInOutQuad = function (t, b, c, d) {
2 | t /= d / 2
3 | if (t < 1) {
4 | return (c / 2) * t * t + b
5 | }
6 | t--
7 | return (-c / 2) * (t * (t - 2) - 1) + b
8 | }
9 |
10 | // requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
11 | let requestAnimFrame = (function () {
12 | return (
13 | window.requestAnimationFrame ||
14 | window.webkitRequestAnimationFrame ||
15 | window.mozRequestAnimationFrame ||
16 | function (callback) {
17 | window.setTimeout(callback, 1000 / 60)
18 | }
19 | )
20 | })()
21 |
22 | /**
23 | * Because it's so fucking difficult to detect the scrolling element, just move them all
24 | * @param {number} amount
25 | */
26 | function move(amount) {
27 | document.documentElement.scrollTop = amount
28 | document.body.parentNode.scrollTop = amount
29 | document.body.scrollTop = amount
30 | }
31 |
32 | function position() {
33 | return (
34 | document.documentElement.scrollTop ||
35 | document.body.parentNode.scrollTop ||
36 | document.body.scrollTop
37 | )
38 | }
39 |
40 | /**
41 | * @param {number} to
42 | * @param {number} duration
43 | * @param {Function} callback
44 | */
45 | export function scrollTo(to, duration, callback) {
46 | const start = position()
47 | const change = to - start
48 | const increment = 20
49 | let currentTime = 0
50 | duration = typeof duration === 'undefined' ? 500 : duration
51 | let animateScroll = function () {
52 | // increment the time
53 | currentTime += increment
54 | // find the value with the quadratic in-out easing function
55 | let val = Math.easeInOutQuad(currentTime, start, change, duration)
56 | // move the document.body
57 | move(val)
58 | // do the animation unless its over
59 | if (currentTime < duration) {
60 | requestAnimFrame(animateScroll)
61 | } else {
62 | if (callback && typeof callback === 'function') {
63 | // the animation is done so lets callback
64 | callback()
65 | }
66 | }
67 | }
68 | animateScroll()
69 | }
70 |
--------------------------------------------------------------------------------
/src/utils/validate.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by NineSwords on 16/11/18.
3 | */
4 |
5 | /**
6 | * @param {string} path
7 | * @returns {Boolean}
8 | */
9 | export function isExternal(path) {
10 | return /^(https?:|mailto:|tel:)/.test(path)
11 | }
12 |
13 | /**
14 | * @param {string} str
15 | * @returns {Boolean}
16 | */
17 | export function validUsername(str) {
18 | const validMap = ['admin', 'editor']
19 | return validMap.indexOf(str.trim()) >= 0
20 | }
21 |
22 | /**
23 | * @param {string} url
24 | * @returns {Boolean}
25 | */
26 | export function validURL(url) {
27 | const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
28 | return reg.test(url)
29 | }
30 |
31 | /**
32 | * @param {string} str
33 | * @returns {Boolean}
34 | */
35 | export function validLowerCase(str) {
36 | const reg = /^[a-z]+$/
37 | return reg.test(str)
38 | }
39 |
40 | /**
41 | * @param {string} str
42 | * @returns {Boolean}
43 | */
44 | export function validUpperCase(str) {
45 | const reg = /^[A-Z]+$/
46 | return reg.test(str)
47 | }
48 |
49 | /**
50 | * @param {string} str
51 | * @returns {Boolean}
52 | */
53 | export function validAlphabets(str) {
54 | const reg = /^[A-Za-z]+$/
55 | return reg.test(str)
56 | }
57 |
58 | /**
59 | * @param {string} email
60 | * @returns {Boolean}
61 | */
62 | export function validEmail(email) {
63 | const reg = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
64 | return reg.test(email)
65 | }
66 |
67 | /**
68 | * @param {string} str
69 | * @returns {Boolean}
70 | */
71 | export function isString(str) {
72 | return typeof str === 'string' || str instanceof String
73 | }
74 |
75 | /**
76 | * @param {Array} arg
77 | * @returns {Boolean}
78 | */
79 | export function isArray(arg) {
80 | if (typeof Array.isArray === 'undefined') {
81 | return Object.prototype.toString.call(arg) === '[object Array]'
82 | }
83 | return Array.isArray(arg)
84 | }
85 |
--------------------------------------------------------------------------------
/src/views/charts/Bar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
87 |
--------------------------------------------------------------------------------
/src/views/charts/Line.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
73 |
--------------------------------------------------------------------------------
/src/views/charts/Pie.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
84 |
--------------------------------------------------------------------------------
/src/views/charts/components/CardChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 | {{ title }}
9 |
12 |
13 |
14 |
15 |
69 |
--------------------------------------------------------------------------------
/src/views/charts/data.js:
--------------------------------------------------------------------------------
1 | const dict = {
2 | 北京: 'BeiJing',
3 | 天津: 'TianJin',
4 | 河北: 'HeBei',
5 | 山西: 'ShanXi',
6 | 内蒙古: 'NeiMengGu',
7 | 辽宁: 'LiaoNing',
8 | 吉林: 'JiLin',
9 | 黑龙江: 'HeiLongJiang',
10 | 上海: 'ShangHai',
11 | 江苏: 'JiangSu',
12 | 浙江: 'ZheJiang',
13 | 安徽: 'AnHui',
14 | 福建: 'FuJian',
15 | 江西: 'JiangXi',
16 | 山东: 'ShanDong',
17 | 河南: 'HeNan',
18 | 湖北: 'HuBei',
19 | 湖南: 'HuNan',
20 | 广东: 'GuangDong',
21 | 广西: 'GuangXi',
22 | 海南: 'HaiNan',
23 | 重庆: 'ZhongQing',
24 | 四川: 'SiChuan',
25 | 贵州: 'GuiZhou',
26 | 云南: 'YunNan',
27 | 西藏: 'Xizang',
28 | 陕西: 'ShanXi',
29 | 甘肃: 'GanSu',
30 | 青海: 'QingHai',
31 | 宁夏: 'NingXia',
32 | 新疆: 'XinJiang',
33 | 台湾: 'TaiWan',
34 | 香港: 'xianggang',
35 | 澳门: 'aomen',
36 | }
37 |
38 | export { dict }
39 |
--------------------------------------------------------------------------------
/src/views/components-page/pro-dialog-view.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 | 封装的Dialog
11 |
12 |
打开Dialog
18 |
27 |
28 | 全屏的Dialog {{ index }}
29 |
30 |
31 |
打开全屏Dialog
37 |
38 |
39 |
40 |
74 |
--------------------------------------------------------------------------------
/src/views/dashboard/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
22 |
23 |
31 |
--------------------------------------------------------------------------------
/src/views/error-page/401.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 返回
5 |
6 |
7 |
8 | Oops!
9 | gif来源airbnb 页面
10 | 你没有权限去该页面
11 | 如有不满请联系你领导
12 |
13 | - 或者你可以去:
14 | -
15 | 回首页
16 |
17 | -
18 | 随便看看
19 |
20 | -
21 | 点我看图
22 |
23 |
24 |
25 |
26 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
79 |
80 |
119 |
--------------------------------------------------------------------------------
/src/views/form/advanced-form.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 | 仓库管理
13 |
14 | 添加仓库
19 |
20 | 任务管理
21 |
22 |
28 |
29 |
30 |
31 |
32 |
33 |
95 |
--------------------------------------------------------------------------------
/src/views/form/columns/linkage.js:
--------------------------------------------------------------------------------
1 | const list = [
2 | {
3 | dataIndex: 'name',
4 | title: 'Activity name',
5 | isForm: true,
6 | valueType: 'input',
7 | prop: [
8 | {
9 | required: true,
10 | message: 'Please enter the Activity name',
11 | trigger: 'blur',
12 | },
13 | ],
14 | },
15 | {
16 | dataIndex: 'region',
17 | title: 'Activity zone',
18 | isForm: true,
19 | valueType: 'select',
20 | option: [
21 | {
22 | label: 'Zone one',
23 | value: 'shanghai',
24 | },
25 | {
26 | label: 'Zone two',
27 | value: 'beijing',
28 | },
29 | ],
30 | prop: [
31 | {
32 | required: true,
33 | message: 'Please select the Activity zone',
34 | trigger: 'change',
35 | },
36 | ],
37 | },
38 | {
39 | dataIndex: 'date',
40 | title: 'Activity time',
41 | valueType: 'date-picker',
42 | attrs: {
43 | type: 'daterange',
44 | format: 'YYYY-MM-DD',
45 | 'start-placeholder': 'Please select Activity time',
46 | },
47 | isForm: true,
48 | },
49 | {
50 | dataIndex: 'delivery',
51 | title: 'Instant delivery',
52 | valueType: 'switch',
53 | isForm: true,
54 | },
55 | {
56 | dataIndex: 'type',
57 | title: 'Activity type',
58 | valueType: 'checkbox',
59 | option: [
60 | {
61 | label: 'Online activities',
62 | value: 'Online activities',
63 | },
64 | {
65 | label: 'Promotion activities',
66 | value: 'Promotion activities',
67 | },
68 | {
69 | label: 'Offline activities',
70 | value: 'Offline activities',
71 | },
72 | {
73 | label: 'Simple brand exposure',
74 | value: 'Simple brand exposure',
75 | },
76 | ],
77 | isForm: true,
78 | isShowFormItem: (formParam) => {
79 | return formParam && formParam.delivery
80 | },
81 | },
82 | {
83 | dataIndex: 'resource',
84 | title: 'Resources',
85 | valueType: 'radio',
86 | option: [
87 | {
88 | label: 'Sponsor',
89 | value: 'Sponsor',
90 | },
91 | {
92 | label: 'Venue',
93 | value: 'Venue',
94 | },
95 | ],
96 | isForm: true,
97 | isShowFormItem: (formParam) => {
98 | return formParam && formParam.delivery
99 | },
100 | },
101 | {
102 | dataIndex: 'desc',
103 | title: 'Activity form',
104 | valueType: 'input',
105 | inpuType: 'textarea',
106 | isForm: true,
107 | isShowFormItem: (formParam) => {
108 | return formParam && formParam.delivery
109 | },
110 | },
111 | {
112 | dataIndex: 'check-code',
113 | title: 'Verification Code',
114 | isForm: true,
115 | valueType: 'check-code',
116 | sendCode: {
117 | smsType: 'pay',
118 | code: 'pay',
119 | },
120 | prop: [
121 | {
122 | required: true,
123 | message: 'Please enter the verification code',
124 | trigger: 'blur',
125 | },
126 | ],
127 | isShowFormItem: (formParam) => {
128 | return formParam && formParam.delivery
129 | },
130 | },
131 | ]
132 |
133 | export { list }
134 |
--------------------------------------------------------------------------------
/src/views/form/columns/list.js:
--------------------------------------------------------------------------------
1 | const list = [
2 | {
3 | dataIndex: 'name',
4 | title: 'Activity name',
5 | isForm: true,
6 | valueType: 'input',
7 | prop: [
8 | {
9 | required: true,
10 | message: 'Please enter the Activity name',
11 | trigger: 'blur',
12 | },
13 | ],
14 | },
15 | {
16 | dataIndex: 'region',
17 | title: 'Activity zone',
18 | isForm: true,
19 | valueType: 'select',
20 | option: [
21 | {
22 | label: 'Zone one',
23 | value: 'shanghai',
24 | },
25 | {
26 | label: 'Zone two',
27 | value: 'beijing',
28 | },
29 | ],
30 | prop: [
31 | {
32 | required: true,
33 | message: 'Please select the Activity zone',
34 | trigger: 'change',
35 | },
36 | ],
37 | },
38 | {
39 | dataIndex: 'date',
40 | title: 'Activity time',
41 | valueType: 'date-picker',
42 | attrs: {
43 | type: 'daterange',
44 | format: 'YYYY-MM-DD',
45 | 'start-placeholder': 'Please select Activity time',
46 | },
47 | isForm: true,
48 | },
49 | {
50 | dataIndex: 'delivery',
51 | title: 'Instant delivery',
52 | valueType: 'switch',
53 | isForm: true,
54 | },
55 | {
56 | dataIndex: 'type',
57 | title: 'Activity type',
58 | valueType: 'checkbox',
59 | option: [
60 | {
61 | label: 'Online activities',
62 | value: 'Online activities',
63 | },
64 | {
65 | label: 'Promotion activities',
66 | value: 'Promotion activities',
67 | },
68 | {
69 | label: 'Offline activities',
70 | value: 'Offline activities',
71 | },
72 | {
73 | label: 'Simple brand exposure',
74 | value: 'Simple brand exposure',
75 | },
76 | ],
77 | isForm: true,
78 | },
79 | {
80 | dataIndex: 'resource',
81 | title: 'Resources',
82 | valueType: 'radio',
83 | option: [
84 | {
85 | label: 'Sponsor',
86 | value: 'Sponsor',
87 | },
88 | {
89 | label: 'Venue',
90 | value: 'Venue',
91 | },
92 | ],
93 | isForm: true,
94 | },
95 | {
96 | dataIndex: 'desc',
97 | title: 'Activity form',
98 | valueType: 'input',
99 | inpuType: 'textarea',
100 | isForm: true,
101 | },
102 | {
103 | dataIndex: 'check-code',
104 | title: 'Verification Code',
105 | isForm: true,
106 | valueType: 'check-code',
107 | sendCode: {
108 | smsType: 'pay',
109 | code: 'pay',
110 | },
111 | prop: [
112 | {
113 | required: true,
114 | message: 'Please enter the verification code',
115 | trigger: 'blur',
116 | },
117 | ],
118 | },
119 | ]
120 |
121 | export { list }
122 |
--------------------------------------------------------------------------------
/src/views/form/columns/modal.js:
--------------------------------------------------------------------------------
1 | const list = [
2 | {
3 | dataIndex: 'name',
4 | title: 'Activity name',
5 | isForm: true,
6 | valueType: 'input',
7 | prop: [
8 | {
9 | required: true,
10 | message: 'Please enter the Activity name',
11 | trigger: 'blur',
12 | },
13 | ],
14 | },
15 | {
16 | dataIndex: 'region',
17 | title: 'Activity zone',
18 | isForm: true,
19 | valueType: 'select',
20 | option: [
21 | {
22 | label: 'Zone one',
23 | value: 'shanghai',
24 | },
25 | {
26 | label: 'Zone two',
27 | value: 'beijing',
28 | },
29 | ],
30 | prop: [
31 | {
32 | required: true,
33 | message: 'Please select the Activity zone',
34 | trigger: 'change',
35 | },
36 | ],
37 | },
38 | {
39 | dataIndex: 'date',
40 | title: 'Activity time',
41 | valueType: 'date-picker',
42 | attrs: {
43 | type: 'daterange',
44 | format: 'YYYY-MM-DD',
45 | 'start-placeholder': 'Please select Activity time',
46 | },
47 | isForm: true,
48 | },
49 | {
50 | dataIndex: 'delivery',
51 | title: 'Instant delivery',
52 | valueType: 'switch',
53 | isForm: true,
54 | },
55 | {
56 | dataIndex: 'type',
57 | title: 'Activity type',
58 | valueType: 'checkbox',
59 | option: [
60 | {
61 | label: 'Online activities',
62 | value: 'Online activities',
63 | },
64 | {
65 | label: 'Promotion activities',
66 | value: 'Promotion activities',
67 | },
68 | {
69 | label: 'Offline activities',
70 | value: 'Offline activities',
71 | },
72 | {
73 | label: 'Simple brand exposure',
74 | value: 'Simple brand exposure',
75 | },
76 | ],
77 | isForm: true,
78 | },
79 | {
80 | dataIndex: 'resource',
81 | title: 'Resources',
82 | valueType: 'radio',
83 | option: [
84 | {
85 | label: 'Sponsor',
86 | value: 'Sponsor',
87 | },
88 | {
89 | label: 'Venue',
90 | value: 'Venue',
91 | },
92 | ],
93 | isForm: true,
94 | },
95 | {
96 | dataIndex: 'desc',
97 | title: 'Activity form',
98 | valueType: 'input',
99 | inpuType: 'textarea',
100 | isForm: true,
101 | },
102 | {
103 | dataIndex: 'check-code',
104 | title: 'Verification Code',
105 | isForm: true,
106 | valueType: 'check-code',
107 | sendCode: {
108 | smsType: 'pay',
109 | code: 'pay',
110 | },
111 | prop: [
112 | {
113 | required: true,
114 | message: 'Please enter the verification code',
115 | trigger: 'blur',
116 | },
117 | ],
118 | },
119 | ]
120 |
121 | export { list }
122 |
--------------------------------------------------------------------------------
/src/views/form/columns/step-list.js:
--------------------------------------------------------------------------------
1 | const list = [
2 | {
3 | dataIndex: 'payAccount',
4 | title: '付款账户',
5 | isForm: true,
6 | valueType: 'select',
7 | option: [
8 | {
9 | label: 'ant-design@alipay.com',
10 | value: 'ant-design@alipay.com',
11 | },
12 | ],
13 | prop: [
14 | {
15 | required: true,
16 | message: '请选择付款账户',
17 | trigger: 'change',
18 | },
19 | ],
20 | step: 0,
21 | },
22 | {
23 | dataIndex: 'receiver',
24 | title: '收款账户',
25 | isForm: true,
26 | form_slot: 'receiver',
27 | step: 0,
28 | },
29 | {
30 | dataIndex: 'receiverName',
31 | title: '收款人姓名',
32 | isForm: true,
33 | valueType: 'input',
34 | prop: [
35 | {
36 | required: true,
37 | message: '请输入收款人姓名',
38 | trigger: 'blur',
39 | },
40 | ],
41 | step: 0,
42 | },
43 | {
44 | dataIndex: 'amount',
45 | title: '转账金额',
46 | isForm: true,
47 | valueType: 'input-number',
48 | attrs: {
49 | min: 0,
50 | precision: 2,
51 | step: 0.1,
52 | max: 100,
53 | },
54 | prop: [
55 | {
56 | required: true,
57 | validator: (rule, value, callback) => {
58 | if (!value) {
59 | callback(new Error('请输入转账金额!'))
60 | } else {
61 | callback()
62 | }
63 | },
64 | trigger: 'change',
65 | },
66 | ],
67 | step: 0,
68 | },
69 | {
70 | dataIndex: 'desinfo',
71 | isForm: true,
72 | form_slot: 'desinfo',
73 | step: 1,
74 | },
75 | {
76 | dataIndex: 'password',
77 | title: '支付密码',
78 | isForm: true,
79 | valueType: 'input',
80 | prop: [
81 | {
82 | required: true,
83 | message: '需要支付密码才能进行支付',
84 | trigger: 'blur',
85 | },
86 | ],
87 | attrs: {
88 | 'show-password': true,
89 | },
90 | step: 1,
91 | },
92 | {
93 | dataIndex: 'result',
94 | isForm: true,
95 | form_slot: 'result',
96 | step: 2,
97 | form_span: 24,
98 | },
99 | ]
100 |
101 | export { list }
102 |
--------------------------------------------------------------------------------
/src/views/form/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 | 提交
14 | 重置
15 |
16 |
17 |
18 |
19 |
20 |
76 |
--------------------------------------------------------------------------------
/src/views/form/linkage-form.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 | 提交
14 | 重置
15 |
16 |
17 |
18 |
19 |
20 |
76 |
--------------------------------------------------------------------------------
/src/views/form/modal-form.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
打开 modal-form
9 |
打开全屏 modal-form
15 |
16 |
24 |
32 |
33 |
34 |
35 |
36 |
95 |
--------------------------------------------------------------------------------
/src/views/icons/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |
12 |
13 | {{ generateIconCode(item) }}
14 |
15 |
16 |
17 | {{ item }}
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 | {{ generateElementIconCode(item) }}
33 |
34 |
35 |
36 | {{ item }}
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
78 |
79 |
111 |
--------------------------------------------------------------------------------
/src/views/icons/svg-icons.js:
--------------------------------------------------------------------------------
1 | const req = require.context('../../icons/svg', false, /\.svg$/)
2 | const requireAll = (requireContext) => requireContext.keys()
3 |
4 | const re = /\.\/(.*)\.svg/
5 |
6 | const svgIcons = requireAll(req).map((i) => {
7 | return i.match(re)[1]
8 | })
9 |
10 | export default svgIcons
11 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | menu 1
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/menu1-1/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/menu1-2/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | menu 1-2
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/menu1-3/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/views/nested/menu2/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/views/table/columns/inline.js:
--------------------------------------------------------------------------------
1 | const columnList = [
2 | {
3 | dataIndex: 'id',
4 | width: 80,
5 | title: 'ID',
6 | },
7 | {
8 | dataIndex: 'timestamp',
9 | width: 180,
10 | title: 'Date',
11 | scopedSlots: { customRender: 'date' },
12 | valueType: 'date-picker',
13 | attrs: {
14 | type: 'date',
15 | format: 'YYYY-MM-DD HH-mm-ss',
16 | 'start-placeholder': 'Please select Date',
17 | },
18 | },
19 | {
20 | dataIndex: 'author',
21 | width: 120,
22 | title: 'Author',
23 | },
24 | {
25 | dataIndex: 'importance',
26 | width: 200,
27 | title: 'Imp',
28 | scopedSlots: { customRender: 'importance' },
29 | },
30 | {
31 | dataIndex: 'status',
32 | width: 180,
33 | title: 'Status',
34 | scopedSlots: { customRender: 'status' },
35 | },
36 | {
37 | dataIndex: 'title',
38 | width: null,
39 | minWidth: 450,
40 | align: 'left',
41 | title: 'Title',
42 | },
43 | ]
44 |
45 | export { columnList }
46 |
--------------------------------------------------------------------------------
/src/views/table/components/editable-cell/Important.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
80 |
--------------------------------------------------------------------------------
/src/views/table/components/editable-cell/Status.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 | {{ item.label }}
14 |
15 |
16 |
20 |
24 |
25 |
26 |
27 | {{ record.status }}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
106 |
--------------------------------------------------------------------------------
/src/views/table/inline-edit-table.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
52 |
--------------------------------------------------------------------------------
/src/views/tree/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
18 |
19 |
20 |
21 |
102 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | // tailwind.config.js
2 | const colors = require('tailwindcss/colors')
3 |
4 | module.exports = {
5 | purge: ['./src/**/*.vue'],
6 | darkMode: false, // or 'media' or 'class'
7 | theme: {
8 | extend: {
9 | colors: {
10 | sky: colors.sky,
11 | cyan: colors.cyan,
12 | },
13 | },
14 | },
15 | corePlugins: {
16 | preflight: false,
17 | },
18 | variants: {},
19 | plugins: [],
20 | }
21 |
--------------------------------------------------------------------------------