├── .browserslistrc
├── .editorconfig
├── .env
├── .gitignore
├── .postcssrc.js
├── .prettierrc
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── img
│ └── icons
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── apple-touch-icon-120x120.png
│ │ ├── apple-touch-icon-152x152.png
│ │ ├── apple-touch-icon-180x180.png
│ │ ├── apple-touch-icon-60x60.png
│ │ ├── apple-touch-icon-76x76.png
│ │ ├── apple-touch-icon.png
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── msapplication-icon-144x144.png
│ │ ├── mstile-150x150.png
│ │ └── safari-pinned-tab.svg
├── index.html
├── manifest.json
└── robots.txt
├── src
├── App.vue
├── api
│ └── user.ts
├── assets
│ ├── 401_images
│ │ └── 401.gif
│ └── 404_images
│ │ ├── 404.png
│ │ └── 404_cloud.png
├── components
│ ├── Breadcrumb
│ │ └── index.vue
│ ├── Charts
│ │ └── KeyboardChart.vue
│ ├── GithubCorner
│ │ └── index.vue
│ ├── Hamburger
│ │ └── index.vue
│ ├── Screenfull
│ │ └── index.vue
│ └── SvgIcon
│ │ └── index.vue
├── icons
│ ├── index.ts
│ ├── svg
│ │ ├── 404.svg
│ │ ├── bug.svg
│ │ ├── chart.svg
│ │ ├── clipboard.svg
│ │ ├── component.svg
│ │ ├── dashboard.svg
│ │ ├── documentation.svg
│ │ ├── drag.svg
│ │ ├── edit.svg
│ │ ├── email.svg
│ │ ├── example.svg
│ │ ├── excel.svg
│ │ ├── eye.svg
│ │ ├── form.svg
│ │ ├── guide 2.svg
│ │ ├── guide.svg
│ │ ├── icon.svg
│ │ ├── international.svg
│ │ ├── language.svg
│ │ ├── link.svg
│ │ ├── list.svg
│ │ ├── lock.svg
│ │ ├── message.svg
│ │ ├── money.svg
│ │ ├── nested.svg
│ │ ├── password.svg
│ │ ├── people.svg
│ │ ├── peoples.svg
│ │ ├── qq.svg
│ │ ├── shopping.svg
│ │ ├── size.svg
│ │ ├── star.svg
│ │ ├── tab.svg
│ │ ├── table.svg
│ │ ├── theme.svg
│ │ ├── tree.svg
│ │ ├── user.svg
│ │ ├── wechat.svg
│ │ └── zip.svg
│ └── svgo.yml
├── main.ts
├── mock
│ ├── index.ts
│ └── login.ts
├── permission.ts
├── registerServiceWorker.ts
├── router
│ ├── modules
│ │ ├── charts.ts
│ │ ├── errors.ts
│ │ └── tables.ts
│ └── router.ts
├── shims-tsx.d.ts
├── shims-vue.d.ts
├── store
│ ├── modules
│ │ ├── app
│ │ │ ├── actions.ts
│ │ │ ├── getters.ts
│ │ │ ├── index.ts
│ │ │ ├── mutations.ts
│ │ │ └── types.ts
│ │ ├── permission
│ │ │ ├── actions.ts
│ │ │ ├── getters.ts
│ │ │ ├── index.ts
│ │ │ ├── mutations.ts
│ │ │ └── types.ts
│ │ ├── types.ts
│ │ └── user
│ │ │ ├── actions.ts
│ │ │ ├── getters.ts
│ │ │ ├── index.ts
│ │ │ ├── mutations.ts
│ │ │ └── types.ts
│ └── store.ts
├── styles
│ ├── btn.scss
│ ├── element-ui.scss
│ ├── index.scss
│ ├── mixin.scss
│ ├── sidebar.scss
│ ├── transition.scss
│ └── variables.scss
├── types
│ └── vue-count-to.d.ts
├── utils
│ ├── auth.ts
│ ├── clipboard.ts
│ ├── getPageTitle.ts
│ ├── index.ts
│ ├── request.ts
│ └── validate.ts
└── views
│ ├── charts
│ ├── keyboard.vue
│ └── line.vue
│ ├── dashboard
│ ├── components
│ │ ├── BarChart.vue
│ │ ├── BoxCard.vue
│ │ ├── LineChart.vue
│ │ ├── PanelGroup.vue
│ │ ├── PieChart.vue
│ │ ├── RaddarChart.vue
│ │ └── TransactionTable.vue
│ └── index.vue
│ ├── errorPage
│ ├── 401.vue
│ └── 404.vue
│ ├── forgetPwd
│ └── index.vue
│ ├── icons
│ ├── element-icons.ts
│ ├── index.vue
│ └── svg-icons.ts
│ ├── layout
│ ├── Layout.vue
│ └── components
│ │ ├── AppMain.vue
│ │ ├── NavBar.vue
│ │ ├── Sidebar
│ │ ├── Item.vue
│ │ ├── Link.vue
│ │ ├── SidebarItem.vue
│ │ └── index.vue
│ │ ├── TagsView.vue
│ │ └── index.ts
│ ├── login
│ └── index.vue
│ ├── register
│ └── index.vue
│ └── tables
│ ├── dynamic.vue
│ └── simple.vue
├── tsconfig.json
├── tslint.json
└── vue.config.js
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not ie <= 8
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = crlf
7 | charset = utf-8
8 | trim_trailing_whitespace = false
9 | insert_final_newline = false
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | VUE_APP_MOCK_API=https://www.easy-mock.com/mock/5b39af8a73a49f4fe3433d6f
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw*
22 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {}
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "tabWidth": 2,
4 | "semi": false,
5 | "singleQuote": true
6 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-cli3.0 + typescript + element-ui 后台搭建
2 |
3 | ## Project setup
4 |
5 | ```
6 | # 克隆项目
7 | git clone git@github.com:oOBobbyOo/vue-element-admin.git
8 |
9 | # 安装依赖
10 | npm install
11 |
12 | # 淘宝镜像
13 | npm install --registry=https://registry.npm.taobao.org
14 | ```
15 |
16 | ### Compiles and hot-reloads for development
17 |
18 | ```
19 | npm run serve
20 | ```
21 |
22 | ### Compiles and minifies for production
23 |
24 | ```
25 | npm run build
26 | ```
27 |
28 | ### Lints and fixes files
29 |
30 | ```
31 | npm run lint
32 | ```
33 |
34 | ## 参考:[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
35 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-element-admin",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "@types/axios": "^0.14.0",
12 | "@types/clipboard": "^2.0.1",
13 | "@types/echarts": "^4.1.9",
14 | "@types/js-cookie": "^2.2.2",
15 | "@types/mockjs": "^1.0.2",
16 | "@types/nprogress": "0.0.29",
17 | "@types/screenfull": "^3.3.3",
18 | "axios": "^0.18.0",
19 | "clipboard": "^2.0.4",
20 | "echarts": "^4.2.1",
21 | "element-ui": "^2.8.2",
22 | "js-cookie": "^2.2.0",
23 | "mockjs": "^1.0.1-beta3",
24 | "normalize.css": "^8.0.1",
25 | "nprogress": "^0.2.0",
26 | "register-service-worker": "^1.6.2",
27 | "screenfull": "^3.3.3",
28 | "vue": "^2.6.10",
29 | "vue-class-component": "^6.0.0",
30 | "vue-count-to": "^1.0.13",
31 | "vue-property-decorator": "^7.3.0",
32 | "vue-router": "^3.0.6",
33 | "vuex": "^3.1.1",
34 | "vuex-class": "^0.3.2"
35 | },
36 | "devDependencies": {
37 | "@vue/cli-plugin-babel": "^3.7.0",
38 | "@vue/cli-plugin-pwa": "^3.7.0",
39 | "@vue/cli-plugin-typescript": "^3.7.0",
40 | "@vue/cli-service": "^3.7.0",
41 | "node-sass": "^4.12.0",
42 | "path": "^0.12.7",
43 | "sass-loader": "^7.0.1",
44 | "svg-sprite-loader": "^4.1.6",
45 | "typescript": "^3.4.5",
46 | "vue-template-compiler": "^2.6.10"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oOBobbyOo/vue-element-admin/da0051e753fc16642c7e056ee7d3f76eaa3175f4/public/favicon.ico
--------------------------------------------------------------------------------
/public/img/icons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oOBobbyOo/vue-element-admin/da0051e753fc16642c7e056ee7d3f76eaa3175f4/public/img/icons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/img/icons/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oOBobbyOo/vue-element-admin/da0051e753fc16642c7e056ee7d3f76eaa3175f4/public/img/icons/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/img/icons/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oOBobbyOo/vue-element-admin/da0051e753fc16642c7e056ee7d3f76eaa3175f4/public/img/icons/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/public/img/icons/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oOBobbyOo/vue-element-admin/da0051e753fc16642c7e056ee7d3f76eaa3175f4/public/img/icons/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/public/img/icons/apple-touch-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oOBobbyOo/vue-element-admin/da0051e753fc16642c7e056ee7d3f76eaa3175f4/public/img/icons/apple-touch-icon-180x180.png
--------------------------------------------------------------------------------
/public/img/icons/apple-touch-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oOBobbyOo/vue-element-admin/da0051e753fc16642c7e056ee7d3f76eaa3175f4/public/img/icons/apple-touch-icon-60x60.png
--------------------------------------------------------------------------------
/public/img/icons/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oOBobbyOo/vue-element-admin/da0051e753fc16642c7e056ee7d3f76eaa3175f4/public/img/icons/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/public/img/icons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oOBobbyOo/vue-element-admin/da0051e753fc16642c7e056ee7d3f76eaa3175f4/public/img/icons/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/img/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oOBobbyOo/vue-element-admin/da0051e753fc16642c7e056ee7d3f76eaa3175f4/public/img/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/public/img/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oOBobbyOo/vue-element-admin/da0051e753fc16642c7e056ee7d3f76eaa3175f4/public/img/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/public/img/icons/msapplication-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oOBobbyOo/vue-element-admin/da0051e753fc16642c7e056ee7d3f76eaa3175f4/public/img/icons/msapplication-icon-144x144.png
--------------------------------------------------------------------------------
/public/img/icons/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oOBobbyOo/vue-element-admin/da0051e753fc16642c7e056ee7d3f76eaa3175f4/public/img/icons/mstile-150x150.png
--------------------------------------------------------------------------------
/public/img/icons/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
150 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | vue-element-admin
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-element-admin",
3 | "short_name": "vue-element-admin",
4 | "icons": [
5 | {
6 | "src": "./img/icons/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "./img/icons/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "start_url": "./index.html",
17 | "display": "standalone",
18 | "background_color": "#000000",
19 | "theme_color": "#4DBA87"
20 | }
21 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/src/api/user.ts:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | // 登录
4 | export function login(user: any) {
5 | return request({
6 | url: '/user/login',
7 | method: 'post',
8 | data: { user }
9 | })
10 | }
11 |
12 | // 登出
13 | export function logOut() {
14 | return request({
15 | url: '/user/logout',
16 | method: 'post'
17 | })
18 | }
19 |
20 | // 获取用户信息
21 | export function getUserInfo(token: string) {
22 | return request({
23 | url: '/user/info',
24 | method: 'get',
25 | params: { token }
26 | })
27 | }
28 |
29 | // 注册
30 | export function register(user: any) {
31 | return request({
32 | url: '/user/register',
33 | method: 'post',
34 | data: { user }
35 | })
36 | }
37 |
38 | // 忘记密码
39 | export function forgetPwd(user: any) {
40 | return request({
41 | url: '/user/forgetPwd',
42 | method: 'post',
43 | data: { user }
44 | })
45 | }
46 |
--------------------------------------------------------------------------------
/src/assets/401_images/401.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oOBobbyOo/vue-element-admin/da0051e753fc16642c7e056ee7d3f76eaa3175f4/src/assets/401_images/401.gif
--------------------------------------------------------------------------------
/src/assets/404_images/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oOBobbyOo/vue-element-admin/da0051e753fc16642c7e056ee7d3f76eaa3175f4/src/assets/404_images/404.png
--------------------------------------------------------------------------------
/src/assets/404_images/404_cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oOBobbyOo/vue-element-admin/da0051e753fc16642c7e056ee7d3f76eaa3175f4/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 |
58 |
59 |
--------------------------------------------------------------------------------
/src/components/Charts/KeyboardChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/components/GithubCorner/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
19 |
20 |
21 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/components/Hamburger/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
25 |
26 |
41 |
--------------------------------------------------------------------------------
/src/components/Screenfull/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
43 |
44 |
54 |
55 |
--------------------------------------------------------------------------------
/src/components/SvgIcon/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
34 |
35 |
--------------------------------------------------------------------------------
/src/icons/index.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import SvgIcon from '@/components/SvgIcon/index.vue' // svg组件
3 |
4 | // register globally
5 | Vue.component('svg-icon', SvgIcon)
6 |
7 | const req = require.context('./svg', false, /\.svg$/)
8 | const requireAll = (requireContext: any) =>
9 | requireContext.keys().map(requireContext)
10 | requireAll(req)
11 |
--------------------------------------------------------------------------------
/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/email.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/example.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/excel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/eye.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/form.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/guide 2.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/people.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/peoples.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/qq.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/shopping.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/size.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.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/main.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | import 'normalize.css/normalize.css' // A modern alternative to CSS resets
4 |
5 | import * as ElementUI from 'element-ui'
6 | import 'element-ui/lib/theme-chalk/index.css'
7 |
8 | import '@/styles/index.scss' // global css
9 |
10 | import App from './App.vue'
11 | import router from './router/router'
12 | import store from './store/store'
13 | import './registerServiceWorker'
14 |
15 | import './icons' // icon
16 | import './permission' // permission control
17 | // import './mock' // simulation data
18 |
19 | Vue.use(ElementUI)
20 |
21 | Vue.config.productionTip = false
22 |
23 | new Vue({
24 | router,
25 | store,
26 | render: h => h(App)
27 | }).$mount('#app')
28 |
--------------------------------------------------------------------------------
/src/mock/index.ts:
--------------------------------------------------------------------------------
1 | import Mock from 'mockjs'
2 | import loginAPI from './login'
3 |
4 | // 登录相关
5 | Mock.mock(/\/user\/login/, 'post', loginAPI.loginByUsername)
6 | Mock.mock(/\/login\/logout/, 'post', loginAPI.logout)
7 | Mock.mock(/\/user\/info\.*/, 'get', loginAPI.getUserInfo)
8 |
--------------------------------------------------------------------------------
/src/mock/login.ts:
--------------------------------------------------------------------------------
1 | import { param2Obj } from '@/utils'
2 |
3 | interface UserMap {
4 | [key: string]: any
5 | }
6 |
7 | const userMap: UserMap = {
8 | admin: {
9 | roles: ['admin'],
10 | token: 'admin',
11 | introduction: '我是超级管理员',
12 | avatar:
13 | 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
14 | name: 'Super Admin'
15 | },
16 | editor: {
17 | roles: ['editor'],
18 | token: 'editor',
19 | introduction: '我是编辑',
20 | avatar:
21 | 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
22 | name: 'Normal Editor'
23 | }
24 | }
25 |
26 | export default {
27 | loginByUsername: (config: any) => {
28 | const { username } = JSON.parse(config.body)
29 | return userMap[username]
30 | },
31 | getUserInfo: (config: any) => {
32 | const { token } = param2Obj(config.url)
33 | if (userMap[token]) {
34 | return userMap[token]
35 | } else {
36 | return false
37 | }
38 | },
39 | logout: () => 'success'
40 | }
41 |
--------------------------------------------------------------------------------
/src/permission.ts:
--------------------------------------------------------------------------------
1 | import router from './router/router'
2 | import store from './store/store'
3 | import { Message } from 'element-ui'
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/getPageTitle'
8 |
9 | NProgress.configure({ showSpinner: false }) // NProgress Configuration
10 |
11 | // permission judge function
12 | function hasPermission(roles: any, permissionRoles: any) {
13 | if (roles.indexOf('admin') >= 0) return true // admin permission passed directly
14 | if (!permissionRoles) return true
15 | return roles.some((role: any) => permissionRoles.indexOf(role) >= 0)
16 | }
17 |
18 | const whiteList = ['/login', '/register', '/forgetPwd', '/auth-redirect'] // no redirect whitelist
19 |
20 | router.beforeEach((to, from, next) => {
21 | // start progress bar
22 | NProgress.start()
23 |
24 | // set page title
25 | document.title = getPageTitle(to.meta.title)
26 |
27 | // determine whether the user has logged in
28 | const hasToken = getToken()
29 |
30 | if (hasToken) {
31 | /* has token*/
32 | if (to.path === '/login') {
33 | next({ path: '/' })
34 | NProgress.done()
35 | } else {
36 | const hasRoles = store.getters.roles && store.getters.roles.length === 0
37 | if (hasRoles) {
38 | store
39 | .dispatch('user/getUserInfo')
40 | .then((res: any) => {
41 | const roles = res.roles
42 | store.dispatch('permission/generateRoutes', roles).then(() => {
43 | router.addRoutes(store.getters['permission/addRouters'])
44 | next({ ...to, replace: true })
45 | })
46 | })
47 | .catch((err: any) => {
48 | store.dispatch('user/fedLogOut').then(() => {
49 | Message.error(err || 'Verification failed, please login again')
50 | next({ path: '/' })
51 | })
52 | })
53 | } else {
54 | if (hasPermission(store.getters['user/roles'], to.meta.roles)) {
55 | next()
56 | } else {
57 | next({ path: '/401', replace: true })
58 | }
59 | }
60 | }
61 | } else {
62 | /* has no token*/
63 | if (whiteList.indexOf(to.path) !== -1) {
64 | next() // 在免登录白名单,直接进入
65 | } else {
66 | next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
67 | NProgress.done()
68 | }
69 | }
70 | })
71 |
72 | router.afterEach(() => {
73 | // finish progress bar
74 | NProgress.done()
75 | })
76 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-console */
2 |
3 | import { register } from 'register-service-worker'
4 |
5 | if (process.env.NODE_ENV === 'production') {
6 | register(`${process.env.BASE_URL}service-worker.js`, {
7 | ready() {
8 | console.log(
9 | 'App is being served from cache by a service worker.\n' +
10 | 'For more details, visit https://goo.gl/AFskqB'
11 | )
12 | },
13 | cached() {
14 | console.log('Content has been cached for offline use.')
15 | },
16 | updated() {
17 | console.log('New content is available; please refresh.')
18 | },
19 | offline() {
20 | console.log(
21 | 'No internet connection found. App is running in offline mode.'
22 | )
23 | },
24 | error(error) {
25 | console.error('Error during service worker registration:', error)
26 | }
27 | })
28 | }
29 |
--------------------------------------------------------------------------------
/src/router/modules/charts.ts:
--------------------------------------------------------------------------------
1 | import Layout from '@/views/layout/Layout.vue'
2 |
3 | const chartsRouter = {
4 | path: '/charts',
5 | component: Layout,
6 | redirect: 'noredirect',
7 | name: 'Charts',
8 | meta: { title: 'Charts', icon: 'chart' },
9 | children: [
10 | {
11 | path: 'keyboard',
12 | component: () => import('@/views/charts/keyboard.vue'),
13 | name: 'KeyboardChart',
14 | meta: { title: 'keyboard Chart', noCache: true }
15 | },
16 | {
17 | path: 'line',
18 | component: () => import('@/views/charts/line.vue'),
19 | name: 'LineChart',
20 | meta: { title: 'Line Chart', noCache: true }
21 | }
22 | ]
23 | }
24 |
25 | export default chartsRouter
26 |
--------------------------------------------------------------------------------
/src/router/modules/errors.ts:
--------------------------------------------------------------------------------
1 | import Layout from '@/views/layout/Layout.vue'
2 |
3 | const errorsRouter = {
4 | path: '/errors',
5 | component: Layout,
6 | redirect: 'noredirect',
7 | name: 'Errors',
8 | meta: { title: 'Errors', icon: '404' },
9 | children: [
10 | {
11 | path: '401',
12 | component: () => import('@/views/errorPage/401.vue'),
13 | name: 'Page401',
14 | meta: { title: '401', noCache: true }
15 | },
16 | {
17 | path: '404',
18 | component: () => import('@/views/errorPage/404.vue'),
19 | name: 'Page404',
20 | meta: { title: '404', noCache: true }
21 | }
22 | ]
23 | }
24 |
25 | export default errorsRouter
26 |
--------------------------------------------------------------------------------
/src/router/modules/tables.ts:
--------------------------------------------------------------------------------
1 | import Layout from '@/views/layout/Layout.vue'
2 |
3 | const tablesRouter = {
4 | path: '/tables',
5 | component: Layout,
6 | redirect: 'noredirect',
7 | name: 'Tables',
8 | meta: { title: 'Tables', icon: 'table' },
9 | children: [
10 | {
11 | path: 'simple',
12 | component: () => import('@/views/tables/simple.vue'),
13 | name: 'SimpleTable',
14 | meta: { title: 'Simple Table', noCache: true }
15 | },
16 | {
17 | path: 'dynamic',
18 | component: () => import('@/views/tables/dynamic.vue'),
19 | name: 'DynamicTable',
20 | meta: { title: 'Dynamic Table', noCache: true }
21 | }
22 | ]
23 | }
24 |
25 | export default tablesRouter
26 |
--------------------------------------------------------------------------------
/src/router/router.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 |
4 | Vue.use(Router)
5 |
6 | // Layout
7 | import Layout from '@/views/layout/Layout.vue'
8 |
9 | // Async Router Modules
10 | import chartsRouter from './modules/charts'
11 | import tablesRouter from './modules/tables'
12 | import errorsRouter from './modules/errors'
13 |
14 | // Constant Router
15 | export const constantRouterMap = [
16 | {
17 | path: '',
18 | component: Layout,
19 | redirect: 'dashboard',
20 | children: [
21 | {
22 | path: 'dashboard',
23 | component: () => import('@/views/dashboard/index.vue'),
24 | name: 'Dashboard',
25 | meta: { title: 'Dashboard', icon: 'dashboard', noCache: true }
26 | }
27 | ]
28 | },
29 | { path: '/login', component: () => import('@/views/login/index.vue') },
30 | { path: '/register', component: () => import('@/views/register/index.vue') },
31 | { path: '/forgetPwd', component: () => import('@/views/forgetPwd/index.vue') },
32 | { path: '/401', component: () => import('@/views/errorPage/401.vue'), hidden: true },
33 | { path: '/404', component: () => import('@/views/errorPage/404.vue'), hidden: true }
34 | ]
35 |
36 | // Async Router
37 | export const asyncRouterMap = [
38 | {
39 | path: '/icons',
40 | component: Layout,
41 | children: [
42 | {
43 | path: 'index',
44 | component: () => import('@/views/icons/index.vue'),
45 | name: 'Icons',
46 | meta: { title: 'Icons', icon: 'icon', noCache: true }
47 | }
48 | ]
49 | },
50 | chartsRouter,
51 | tablesRouter,
52 | errorsRouter
53 | ]
54 |
55 | export default new Router({
56 | mode: 'history',
57 | base: process.env.BASE_URL,
58 | routes: constantRouterMap
59 | })
60 |
--------------------------------------------------------------------------------
/src/shims-tsx.d.ts:
--------------------------------------------------------------------------------
1 | import Vue, { VNode } from 'vue';
2 |
3 | declare global {
4 | namespace JSX {
5 | // tslint:disable no-empty-interface
6 | interface Element extends VNode {}
7 | // tslint:disable no-empty-interface
8 | interface ElementClass extends Vue {}
9 | interface IntrinsicElements {
10 | [elem: string]: any;
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import Vue from 'vue'
3 | export default Vue
4 | }
5 |
6 | declare module '*.gif'
7 |
--------------------------------------------------------------------------------
/src/store/modules/app/actions.ts:
--------------------------------------------------------------------------------
1 | import { ActionTree } from 'vuex'
2 | import { AppState } from './types'
3 | import { RootState } from '../types'
4 |
5 | export const actions: ActionTree = {
6 | toggleSideBar({ commit }): any {
7 | commit('TOGGLE_SIDEBAR')
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/store/modules/app/getters.ts:
--------------------------------------------------------------------------------
1 | import { GetterTree } from 'vuex'
2 | import { AppState } from './types'
3 | import { RootState } from '../types'
4 |
5 | export const getters: GetterTree = {
6 | sidebar: state => state.sidebar,
7 | device: state => state.device
8 | }
9 |
--------------------------------------------------------------------------------
/src/store/modules/app/index.ts:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie'
2 |
3 | import { Module } from 'vuex'
4 | import { getters } from './getters'
5 | import { actions } from './actions'
6 | import { mutations } from './mutations'
7 | import { AppState } from './types'
8 | import { RootState } from '../types'
9 |
10 | const sidebarStatus = Cookies.get('sidebarStatus') === '1' ? true : false
11 |
12 | export const state: AppState = {
13 | sidebar: { opened: !sidebarStatus, withoutAnimation: false },
14 | device: 'desktop'
15 | }
16 |
17 | const namespaced: boolean = true
18 |
19 | export const app: Module = {
20 | namespaced,
21 | state,
22 | getters,
23 | actions,
24 | mutations
25 | }
26 |
--------------------------------------------------------------------------------
/src/store/modules/app/mutations.ts:
--------------------------------------------------------------------------------
1 | import { MutationTree } from 'vuex'
2 | import { AppState } from './types'
3 |
4 | import Cookies from 'js-cookie'
5 |
6 | export const mutations: MutationTree = {
7 | TOGGLE_SIDEBAR(state) {
8 | if (state.sidebar.opened) {
9 | Cookies.set('sidebarStatus', '1')
10 | } else {
11 | Cookies.set('sidebarStatus', '0')
12 | }
13 | state.sidebar.opened = !state.sidebar.opened
14 | state.sidebar.withoutAnimation = false
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/store/modules/app/types.ts:
--------------------------------------------------------------------------------
1 | export interface SidebarState {
2 | opened: boolean
3 | withoutAnimation: boolean
4 | }
5 |
6 | export interface AppState {
7 | sidebar: SidebarState
8 | device: string
9 | }
10 |
--------------------------------------------------------------------------------
/src/store/modules/permission/actions.ts:
--------------------------------------------------------------------------------
1 | import { ActionTree } from 'vuex'
2 | import { PermissionState } from './types'
3 | import { RootState } from '../types'
4 |
5 | import { asyncRouterMap } from '@/router/router'
6 |
7 | /**
8 | * Use meta.role to determine if the current user has permission
9 | * @param roles
10 | * @param route
11 | */
12 | function hasPermission(roles: any, route: any) {
13 | if (route.meta && route.meta.roles) {
14 | return roles.some((role: any) => route.meta.roles.includes(role))
15 | } else {
16 | return true
17 | }
18 | }
19 |
20 | /**
21 | * Filter asynchronous routing tables by recursion
22 | * @param routes asyncRoutes
23 | * @param roles
24 | */
25 | export function filterAsyncRoutes(routes: any, roles: any) {
26 | const res: any[] = []
27 | routes.forEach((route: any) => {
28 | const tmp = { ...route }
29 | if (hasPermission(roles, tmp)) {
30 | if (tmp.children) {
31 | tmp.children = filterAsyncRoutes(tmp.children, roles)
32 | }
33 | res.push(tmp)
34 | }
35 | })
36 | return res
37 | }
38 |
39 | export const actions: ActionTree = {
40 | generateRoutes({ commit }, roles) {
41 | return new Promise(resolve => {
42 | let accessRoutes
43 | if (roles.includes('admin')) {
44 | accessRoutes = asyncRouterMap
45 | } else {
46 | accessRoutes = filterAsyncRoutes(asyncRouterMap, roles)
47 | }
48 | commit('SET_ROUTERS', accessRoutes)
49 | resolve(accessRoutes)
50 | })
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/store/modules/permission/getters.ts:
--------------------------------------------------------------------------------
1 | import { GetterTree } from 'vuex'
2 | import { PermissionState } from './types'
3 | import { RootState } from '../types'
4 |
5 | export const getters: GetterTree = {
6 | permission_routers: state => state.routers,
7 | addRouters: state => state.addRouters
8 | }
9 |
--------------------------------------------------------------------------------
/src/store/modules/permission/index.ts:
--------------------------------------------------------------------------------
1 | import { Module } from 'vuex'
2 | import { getters } from './getters'
3 | import { actions } from './actions'
4 | import { mutations } from './mutations'
5 | import { PermissionState } from './types'
6 | import { RootState } from '../types'
7 |
8 | import { constantRouterMap } from '@/router/router'
9 |
10 | export const state: PermissionState = {
11 | routers: constantRouterMap,
12 | addRouters: []
13 | }
14 |
15 | const namespaced: boolean = true
16 |
17 | export const permission: Module = {
18 | namespaced,
19 | state,
20 | getters,
21 | actions,
22 | mutations
23 | }
24 |
--------------------------------------------------------------------------------
/src/store/modules/permission/mutations.ts:
--------------------------------------------------------------------------------
1 | import { MutationTree } from 'vuex'
2 | import { PermissionState } from './types'
3 | import { constantRouterMap } from '@/router/router'
4 |
5 | export const mutations: MutationTree = {
6 | SET_ROUTERS: (state, routers) => {
7 | state.addRouters = routers
8 | state.routers = constantRouterMap.concat(routers)
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/store/modules/permission/types.ts:
--------------------------------------------------------------------------------
1 | export interface PermissionState {
2 | routers: any
3 | addRouters: []
4 | }
5 |
--------------------------------------------------------------------------------
/src/store/modules/types.ts:
--------------------------------------------------------------------------------
1 | export interface RootState {
2 | version: string
3 | }
4 |
--------------------------------------------------------------------------------
/src/store/modules/user/actions.ts:
--------------------------------------------------------------------------------
1 | import { ActionTree } from 'vuex'
2 | import { UserState } from './types'
3 | import { RootState } from '../types'
4 |
5 | import { login, register, getUserInfo, forgetPwd, logOut } from '@/api/user'
6 | import { setToken, removeToken } from '@/utils/auth'
7 |
8 | export const actions: ActionTree = {
9 | // 用户登录
10 | async login({ commit }, user) {
11 | try {
12 | const { data } = await login(user)
13 | commit('SET_TOKEN', data.token)
14 | setToken(data.token)
15 | } catch (error) {
16 | return Promise.reject(error)
17 | }
18 | },
19 |
20 | // 用户注册
21 | async register({ commit }, user) {
22 | try {
23 | const { data } = await register(user)
24 | commit('SET_TOKEN', data.token)
25 | } catch (error) {
26 | return Promise.reject(error)
27 | }
28 | },
29 |
30 | // 获取用户信息
31 | async getUserInfo({ commit, state }) {
32 | try {
33 | const { data } = await getUserInfo(state.token)
34 |
35 | // 验证返回的roles是否是一个非空数组
36 | const hasRoles = data.roles && data.roles.length > 0
37 | if (hasRoles) {
38 | commit('SET_ROLES', data.roles)
39 | } else {
40 | return 'getInfo: roles must be a non-null array !'
41 | }
42 |
43 | commit('SET_NAME', data.name)
44 | commit('SET_AVATAR', data.avatar)
45 | commit('SET_INTRODUCTION', data.introduction)
46 | return data
47 | } catch (error) {
48 | return Promise.reject(error)
49 | }
50 | },
51 |
52 | // 忘记密码
53 | async forgetPwd({ commit }, user) {
54 | try {
55 | await forgetPwd(user)
56 | commit('SET_TOKEN', '')
57 | commit('SET_ROLES', [])
58 | } catch (error) {
59 | return Promise.reject(error)
60 | }
61 | },
62 |
63 | // 登出
64 | async logOut({ commit }) {
65 | try {
66 | await logOut()
67 | commit('SET_TOKEN', '')
68 | commit('SET_ROLES', [])
69 | removeToken()
70 | } catch (error) {
71 | return Promise.reject(error)
72 | }
73 | },
74 |
75 | // 前端登出
76 | fedLogOut({ commit }) {
77 | return new Promise(resolve => {
78 | commit('SET_TOKEN', '')
79 | commit('SET_ROLES', [])
80 | removeToken()
81 | resolve()
82 | })
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/store/modules/user/getters.ts:
--------------------------------------------------------------------------------
1 | import { GetterTree } from 'vuex'
2 | import { UserState } from './types'
3 | import { RootState } from '../types'
4 |
5 | export const getters: GetterTree = {
6 | token: state => state.token,
7 | name: state => state.name,
8 | introduction: state => state.introduction,
9 | avatar: state => state.avatar,
10 | roles: state => state.roles
11 | }
12 |
--------------------------------------------------------------------------------
/src/store/modules/user/index.ts:
--------------------------------------------------------------------------------
1 | import { Module } from 'vuex'
2 | import { getters } from './getters'
3 | import { actions } from './actions'
4 | import { mutations } from './mutations'
5 | import { UserState } from './types'
6 | import { RootState } from '../types'
7 | import { getToken } from '@/utils/auth'
8 | const token = getToken() ? getToken() : ''
9 |
10 | export const state: UserState = {
11 | token,
12 | name: '',
13 | introduction: '',
14 | avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
15 | roles: []
16 | }
17 |
18 | const namespaced: boolean = true
19 |
20 | export const user: Module = {
21 | namespaced,
22 | state,
23 | getters,
24 | actions,
25 | mutations
26 | }
27 |
--------------------------------------------------------------------------------
/src/store/modules/user/mutations.ts:
--------------------------------------------------------------------------------
1 | import { MutationTree } from 'vuex'
2 | import { UserState } from './types'
3 |
4 | export const mutations: MutationTree = {
5 | SET_TOKEN: (state, token) => {
6 | state.token = token
7 | },
8 | SET_ROLES: (state, roles) => {
9 | state.roles = roles
10 | },
11 | SET_NAME: (state, name) => {
12 | state.name = name
13 | },
14 | SET_AVATAR: (state, avatar) => {
15 | state.avatar = avatar
16 | },
17 | SET_INTRODUCTION: (state, introduction) => {
18 | state.introduction = introduction
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/store/modules/user/types.ts:
--------------------------------------------------------------------------------
1 | export interface UserState {
2 | token: any
3 | name: string
4 | introduction: string
5 | avatar: string
6 | roles: any[]
7 | }
8 |
--------------------------------------------------------------------------------
/src/store/store.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex, { StoreOptions } from 'vuex'
3 | import { RootState } from './modules/types'
4 | import { app } from './modules/app'
5 | import { user } from './modules/user'
6 | import { permission } from './modules/permission'
7 |
8 | Vue.use(Vuex)
9 |
10 | const store: StoreOptions = {
11 | state: {
12 | version: '1.0.0'
13 | },
14 | modules: {
15 | app,
16 | user,
17 | permission
18 | }
19 | }
20 |
21 | export default new Vuex.Store(store)
22 |
--------------------------------------------------------------------------------
/src/styles/btn.scss:
--------------------------------------------------------------------------------
1 | @import './variables.scss';
2 |
3 | @mixin colorBtn($color) {
4 | background: $color;
5 | &:hover {
6 | color: $color;
7 | &:before,
8 | &:after {
9 | background: $color;
10 | }
11 | }
12 | }
13 |
14 | .blue-btn {
15 | @include colorBtn($blue)
16 | }
17 |
18 | .light-blue-btn {
19 | @include colorBtn($light-blue)
20 | }
21 |
22 | .red-btn {
23 | @include colorBtn($red)
24 | }
25 |
26 | .pink-btn {
27 | @include colorBtn($pink)
28 | }
29 |
30 | .green-btn {
31 | @include colorBtn($green)
32 | }
33 |
34 | .tiffany-btn {
35 | @include colorBtn($tiffany)
36 | }
37 |
38 | .yellow-btn {
39 | @include colorBtn($yellow)
40 | }
41 |
42 | .pan-btn {
43 | font-size: 14px;
44 | color: #fff;
45 | padding: 14px 36px;
46 | border-radius: 8px;
47 | border: none;
48 | outline: none;
49 | transition: 600ms ease all;
50 | position: relative;
51 | display: inline-block;
52 | &:hover {
53 | background: #fff;
54 | &:before,
55 | &:after {
56 | width: 100%;
57 | transition: 600ms ease all;
58 | }
59 | }
60 | &:before,
61 | &:after {
62 | content: '';
63 | position: absolute;
64 | top: 0;
65 | right: 0;
66 | height: 2px;
67 | width: 0;
68 | transition: 400ms ease all;
69 | }
70 | &::after {
71 | right: inherit;
72 | top: inherit;
73 | left: 0;
74 | bottom: 0;
75 | }
76 | }
77 |
78 | .custom-button {
79 | display: inline-block;
80 | line-height: 1;
81 | white-space: nowrap;
82 | cursor: pointer;
83 | background: #fff;
84 | color: #fff;
85 | -webkit-appearance: none;
86 | text-align: center;
87 | box-sizing: border-box;
88 | outline: 0;
89 | margin: 0;
90 | padding: 10px 15px;
91 | font-size: 14px;
92 | border-radius: 4px;
93 | }
94 |
95 |
--------------------------------------------------------------------------------
/src/styles/element-ui.scss:
--------------------------------------------------------------------------------
1 | //覆盖一些element-ui样式
2 |
3 | .el-breadcrumb__inner, .el-breadcrumb__inner a{
4 | font-weight: 400!important;
5 | }
6 |
7 | .el-upload {
8 | input[type="file"] {
9 | display: none !important;
10 | }
11 | }
12 |
13 | .el-upload__input {
14 | display: none;
15 | }
16 |
17 | .cell {
18 | .el-tag {
19 | margin-right: 0px;
20 | }
21 | }
22 |
23 | .small-padding {
24 | .cell {
25 | padding-left: 5px;
26 | padding-right: 5px;
27 | }
28 | }
29 |
30 | .fixed-width{
31 | .el-button--mini{
32 | padding: 7px 10px;
33 | width: 60px;
34 | }
35 | }
36 |
37 | .status-col {
38 | .cell {
39 | padding: 0 10px;
40 | text-align: center;
41 | .el-tag {
42 | margin-right: 0px;
43 | }
44 | }
45 | }
46 |
47 | //暂时性解决dialog 问题 https://github.com/ElemeFE/element/issues/2461
48 | .el-dialog {
49 | transform: none;
50 | left: 0;
51 | position: relative;
52 | margin: 0 auto;
53 | }
54 |
55 | //文章页textarea修改样式
56 | .article-textarea {
57 | textarea {
58 | padding-right: 40px;
59 | resize: none;
60 | border: none;
61 | border-radius: 0px;
62 | border-bottom: 1px solid #bfcbd9;
63 | }
64 | }
65 |
66 | //element ui upload
67 | .upload-container {
68 | .el-upload {
69 | width: 100%;
70 | .el-upload-dragger {
71 | width: 100%;
72 | height: 200px;
73 | }
74 | }
75 | }
76 |
77 | //dropdown
78 | .el-dropdown-menu{
79 | a{
80 | display: block
81 | }
82 | }
83 |
84 |
85 | //login && register
86 | $dark_gray: #889aa4;
87 | $light_gray: #eee;
88 | .form-container {
89 | position: relative;
90 | min-height: 100%;
91 | width: 100%;
92 | background: linear-gradient(0deg, #0f3238 0%, #1b8dd4 50%, #30afc0 100%);
93 | overflow: hidden;
94 | .form-group {
95 | position: absolute;
96 | left: 50%;
97 | top: 50%;
98 | transform: translate(-50%, -50%);
99 | width: 400px;
100 | max-width: 100%;
101 | padding: 20px;
102 | color: #111123;
103 | border-radius: 5px;
104 | box-shadow: 0 0 15px #002661;
105 | border: 1px solid #74b5c9;
106 | background: rgba(0, 0, 0, 0.6);
107 | overflow: hidden;
108 | .title-container {
109 | position: relative;
110 | .title {
111 | font-size: 26px;
112 | color: $light_gray;
113 | margin: 10px auto 30px;
114 | text-align: center;
115 | font-weight: bold;
116 | }
117 | }
118 | .show-pwd {
119 | position: absolute;
120 | right: 10px;
121 | top: 2px;
122 | z-index: 9;
123 | display: block;
124 | font-size: 16px;
125 | color: $dark_gray;
126 | user-select: none;
127 | cursor: pointer;
128 | }
129 | .links {
130 | font-size: 12px;
131 | text-align: right;
132 | color: #fff;
133 | }
134 | }
135 | input {
136 | &:-webkit-autofill {
137 | -webkit-box-shadow: 0 0 0px 1000px #f5f7fa inset !important;
138 | -webkit-text-fill-color: #333 !important;
139 | }
140 | }
141 | .el-input-group__prepend {
142 | padding: 0 15px;
143 | }
144 | }
145 |
146 | // table
147 | .table-container {
148 | padding: 20px;
149 | .el-table tr.warning-row {
150 | background-color: #fdf5e6;
151 | }
152 | .el-table tr.success-row {
153 | background-color: #f0f9eb;
154 | }
155 | }
--------------------------------------------------------------------------------
/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import './variables.scss';
2 | @import './mixin.scss';
3 | @import './transition.scss';
4 | @import './element-ui.scss';
5 | @import './sidebar.scss';
6 | @import './btn.scss';
7 |
8 | body {
9 | height: 100%;
10 | -moz-osx-font-smoothing: grayscale;
11 | -webkit-font-smoothing: antialiased;
12 | text-rendering: optimizeLegibility;
13 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
14 | }
15 |
16 | label {
17 | font-weight: 700;
18 | }
19 |
20 | html {
21 | height: 100%;
22 | box-sizing: border-box;
23 | }
24 |
25 | #app{
26 | height: 100%;
27 | }
28 |
29 | *,
30 | *:before,
31 | *:after {
32 | box-sizing: inherit;
33 | }
34 |
35 | .no-padding {
36 | padding: 0px !important;
37 | }
38 |
39 | .padding-content {
40 | padding: 4px 0;
41 | }
42 |
43 | a:focus,
44 | a:active {
45 | outline: none;
46 | }
47 |
48 | a,
49 | a:focus,
50 | a:hover {
51 | cursor: pointer;
52 | color: inherit;
53 | text-decoration: none;
54 | }
55 |
56 | div:focus{
57 | outline: none;
58 | }
59 |
60 | .fr {
61 | float: right;
62 | }
63 |
64 | .fl {
65 | float: left;
66 | }
67 |
68 | .pr-5 {
69 | padding-right: 5px;
70 | }
71 |
72 | .pl-5 {
73 | padding-left: 5px;
74 | }
75 |
76 | .block {
77 | display: block;
78 | }
79 |
80 | .pointer {
81 | cursor: pointer;
82 | }
83 |
84 | .inlineBlock {
85 | display: block;
86 | }
87 |
88 | .clearfix {
89 | &:after {
90 | visibility: hidden;
91 | display: block;
92 | font-size: 0;
93 | content: " ";
94 | clear: both;
95 | height: 0;
96 | }
97 | }
98 |
99 | code {
100 | background: #eef1f6;
101 | padding: 15px 16px;
102 | margin-bottom: 20px;
103 | display: block;
104 | line-height: 36px;
105 | font-size: 15px;
106 | font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif;
107 | a {
108 | color: #337ab7;
109 | cursor: pointer;
110 | &:hover {
111 | color: rgb(32, 160, 255);
112 | }
113 | }
114 | }
115 |
116 | .warn-content{
117 | background: rgba(66,185,131,.1);
118 | border-radius: 2px;
119 | padding: 16px;
120 | padding: 1rem;
121 | line-height: 1.6rem;
122 | word-spacing: .05rem;
123 | a{
124 | color: #42b983;
125 | font-weight: 600;
126 | }
127 | }
128 |
129 | //main-container全局样式
130 | .app-container {
131 | padding: 20px;
132 | }
133 |
134 | .components-container {
135 | margin: 30px 50px;
136 | position: relative;
137 | }
138 |
139 | .pagination-container {
140 | margin-top: 30px;
141 | }
142 |
143 | .text-center {
144 | text-align: center
145 | }
146 |
147 | .sub-navbar {
148 | height: 50px;
149 | line-height: 50px;
150 | position: relative;
151 | width: 100%;
152 | text-align: right;
153 | padding-right: 20px;
154 | transition: 600ms ease position;
155 | background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
156 | .subtitle {
157 | font-size: 20px;
158 | color: #fff;
159 | }
160 | &.draft {
161 | background: #d0d0d0;
162 | }
163 | &.deleted {
164 | background: #d0d0d0;
165 | }
166 | }
167 |
168 | .link-type,
169 | .link-type:focus {
170 | color: #337ab7;
171 | cursor: pointer;
172 | &:hover {
173 | color: rgb(32, 160, 255);
174 | }
175 | }
176 |
177 | .filter-container {
178 | padding-bottom: 10px;
179 | .filter-item {
180 | display: inline-block;
181 | vertical-align: middle;
182 | margin-bottom: 10px;
183 | }
184 | }
185 |
186 | //refine vue-multiselect plugin
187 | .multiselect {
188 | line-height: 16px;
189 | }
190 |
191 | .multiselect--active {
192 | z-index: 1000 !important;
193 | }
194 |
--------------------------------------------------------------------------------
/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 | &::-webkit-scrollbar {
14 | width: 6px;
15 | }
16 | &::-webkit-scrollbar-thumb {
17 | background: #99a9bf;
18 | border-radius: 20px;
19 | }
20 | }
21 |
22 | @mixin relative {
23 | position: relative;
24 | width: 100%;
25 | height: 100%;
26 | }
27 |
28 | @mixin pct($pct) {
29 | width: #{$pct};
30 | position: relative;
31 | margin: 0 auto;
32 | }
33 |
34 | @mixin triangle($width, $height, $color, $direction) {
35 | $width: $width/2;
36 | $color-border-style: $height solid $color;
37 | $transparent-border-style: $width solid transparent;
38 | height: 0;
39 | width: 0;
40 | @if $direction==up {
41 | border-bottom: $color-border-style;
42 | border-left: $transparent-border-style;
43 | border-right: $transparent-border-style;
44 | }
45 | @else if $direction==right {
46 | border-left: $color-border-style;
47 | border-top: $transparent-border-style;
48 | border-bottom: $transparent-border-style;
49 | }
50 | @else if $direction==down {
51 | border-top: $color-border-style;
52 | border-left: $transparent-border-style;
53 | border-right: $transparent-border-style;
54 | }
55 | @else if $direction==left {
56 | border-right: $color-border-style;
57 | border-top: $transparent-border-style;
58 | border-bottom: $transparent-border-style;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/styles/sidebar.scss:
--------------------------------------------------------------------------------
1 | #app {
2 | // 主体区域
3 | .main-container {
4 | min-height: 100%;
5 | transition: margin-left .28s;
6 | margin-left: 180px;
7 | position: relative;
8 | }
9 | // 侧边栏
10 | .sidebar-container {
11 | transition: width 0.28s;
12 | width: 180px !important;
13 | height: 100%;
14 | position: fixed;
15 | font-size: 0px;
16 | top: 0;
17 | bottom: 0;
18 | left: 0;
19 | z-index: 1001;
20 | overflow: hidden;
21 | //reset element-ui css
22 | .horizontal-collapse-transition {
23 | transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
24 | }
25 | .scrollbar-wrapper {
26 | overflow-x: hidden!important;
27 | .el-scrollbar__view {
28 | height: 100%;
29 | }
30 | }
31 | .el-scrollbar__bar.is-vertical{
32 | right: 0px;
33 | }
34 | .is-horizontal {
35 | display: none;
36 | }
37 | a {
38 | display: inline-block;
39 | width: 100%;
40 | overflow: hidden;
41 | }
42 | .svg-icon {
43 | margin-right: 16px;
44 | }
45 | .el-menu {
46 | border: none;
47 | height: 100%;
48 | width: 100% !important;
49 | }
50 | .is-active > .el-submenu__title{
51 | color: #f4f4f5!important;
52 | }
53 | }
54 | .hideSidebar {
55 | .sidebar-container {
56 | width: 36px !important;
57 | }
58 | .main-container {
59 | margin-left: 36px;
60 | }
61 | .submenu-title-noDropdown {
62 | padding-left: 10px !important;
63 | position: relative;
64 | .el-tooltip {
65 | padding: 0 10px !important;
66 | }
67 | }
68 | .el-submenu {
69 | overflow: hidden;
70 | &>.el-submenu__title {
71 | padding-left: 10px !important;
72 | .el-submenu__icon-arrow {
73 | display: none;
74 | }
75 | }
76 | }
77 | .el-menu--collapse {
78 | .el-submenu {
79 | &>.el-submenu__title {
80 | &>span {
81 | height: 0;
82 | width: 0;
83 | overflow: hidden;
84 | visibility: hidden;
85 | display: inline-block;
86 | }
87 | }
88 | }
89 | }
90 | }
91 | .sidebar-container .nest-menu .el-submenu>.el-submenu__title,
92 | .sidebar-container .el-submenu .el-menu-item {
93 | min-width: 180px !important;
94 | background-color: $subMenuBg !important;
95 | &:hover {
96 | background-color: $menuHover !important;
97 | }
98 | }
99 | .el-menu--collapse .el-menu .el-submenu {
100 | min-width: 180px !important;
101 | }
102 |
103 | //适配移动端
104 | .mobile {
105 | .main-container {
106 | margin-left: 0px;
107 | }
108 | .sidebar-container {
109 | transition: transform .28s;
110 | width: 180px !important;
111 | }
112 | &.hideSidebar {
113 | .sidebar-container {
114 | transition-duration: 0.3s;
115 | transform: translate3d(-180px, 0, 0);
116 | }
117 | }
118 | }
119 | .withoutAnimation {
120 | .main-container,
121 | .sidebar-container {
122 | transition: none;
123 | }
124 | }
125 | }
126 |
127 | .el-menu--vertical{
128 | & >.el-menu{
129 | .svg-icon{
130 | margin-right: 16px;
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/styles/transition.scss:
--------------------------------------------------------------------------------
1 | //globl 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 | .fade-transform-enter {
20 | opacity: 0;
21 | transform: translateX(-30px);
22 | }
23 | .fade-transform-leave-to {
24 | opacity: 0;
25 | transform: translateX(30px);
26 | }
27 |
28 | /*breadcrumb transition*/
29 | .breadcrumb-enter-active,
30 | .breadcrumb-leave-active {
31 | transition: all .5s;
32 | }
33 |
34 | .breadcrumb-enter,
35 | .breadcrumb-leave-active {
36 | opacity: 0;
37 | transform: translateX(20px);
38 | }
39 |
40 | .breadcrumb-move {
41 | transition: all .5s;
42 | }
43 |
44 | .breadcrumb-leave-active {
45 | position: absolute;
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/src/styles/variables.scss:
--------------------------------------------------------------------------------
1 | $blue:#324157;
2 | $light-blue:#3A71A8;
3 | $red:#C03639;
4 | $pink: #E65D6E;
5 | $green: #30B08F;
6 | $tiffany: #4AB7BD;
7 | $yellow:#FEC171;
8 | $panGreen: #30B08F;
9 |
10 | //sidebar
11 | $menuBg:#304156;
12 | $subMenuBg:#1f2d3d;
13 | $menuHover:#001528;
14 |
--------------------------------------------------------------------------------
/src/types/vue-count-to.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'vue-count-to' {
2 | import { VueConstructor } from 'vue'
3 |
4 | export interface VueCountToProps {
5 | startVal?: number
6 | endVal?: number
7 | duration?: number
8 | autoplay?: boolean
9 | decimal?: number
10 | decima?: string
11 | separator?: string
12 | prefix?: string
13 | suffix?: string
14 | useEasing?: boolean
15 | easingFn?: Function
16 | }
17 |
18 | export interface VueCountToData {
19 | localStartVal: number
20 | displayValue: number
21 | printVal: any
22 | paused: boolean
23 | localDuration: number
24 | startTime: any
25 | timestamp: any
26 | remaining: any
27 | rAF: any
28 | }
29 |
30 | export interface VueCountToWatch {
31 | startVal: () => void
32 | endVal: () => void
33 | }
34 |
35 | export interface VueCountToMethods {
36 | start: () => void
37 | pauseResume: () => void
38 | pause: () => void
39 | resume: () => void
40 | reset: () => void
41 | count: (option: any) => void
42 | isNumber: (option: any) => boolean
43 | formatNumber: (option: any) => number
44 | }
45 |
46 | export interface VueCountToComputed {
47 | countDown: () => boolean
48 | }
49 |
50 | export interface VueCountToConstructor extends VueConstructor {
51 | props: VueCountToProps
52 | data: () => VueCountToData
53 | watch: VueCountToWatch
54 | methods: VueCountToMethods
55 | computed: VueCountToComputed
56 | }
57 |
58 | export const VueCountTo: VueCountToConstructor
59 | export type OptionConsumer = (option: any) => void
60 | export default VueCountTo
61 | }
62 |
--------------------------------------------------------------------------------
/src/utils/auth.ts:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie'
2 |
3 | const TokenKey = 'Admin-Token'
4 |
5 | export function getToken() {
6 | return Cookies.get(TokenKey)
7 | }
8 |
9 | export function setToken(token: string) {
10 | return Cookies.set(TokenKey, token)
11 | }
12 |
13 | export function removeToken() {
14 | return Cookies.remove(TokenKey)
15 | }
16 |
--------------------------------------------------------------------------------
/src/utils/clipboard.ts:
--------------------------------------------------------------------------------
1 | import Clipboard from 'clipboard'
2 | import { Message } from 'element-ui'
3 |
4 | function clipboardSuccess() {
5 | Message({ message: 'Copy successfully', type: 'success', duration: 500 })
6 | }
7 |
8 | function clipboardError() {
9 | Message({ message: 'Copy failed', type: 'error' })
10 | }
11 |
12 | export default function handleClipboard(text: any, event: any) {
13 | const clipboard = new Clipboard(event.target, {
14 | text: () => text
15 | })
16 | clipboard.on('success', () => {
17 | clipboardSuccess()
18 | clipboard.destroy()
19 | })
20 | clipboard.on('error', () => {
21 | clipboardError()
22 | clipboard.destroy()
23 | })
24 | }
25 |
--------------------------------------------------------------------------------
/src/utils/getPageTitle.ts:
--------------------------------------------------------------------------------
1 | const title = 'Vue Element Admin'
2 | export default function getPageTitle(pageTitle: string) {
3 | return pageTitle ? `${pageTitle} - ${title}` : `${title}`
4 | }
5 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export function param2Obj(url: string) {
2 | const search = url.split('?')[1]
3 | if (!search) {
4 | return {}
5 | }
6 | return JSON.parse(
7 | '{"' +
8 | decodeURIComponent(search)
9 | .replace(/"/g, '\\"')
10 | .replace(/&/g, '","')
11 | .replace(/=/g, '":"') +
12 | '"}'
13 | )
14 | }
15 |
16 | /**
17 | * underscore 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行
18 | * @param {function} func 回调函数
19 | * @param {number} wait 表示时间窗口的间隔
20 | * @param {boolean} immediate 设置为ture时,是否立即调用函数
21 | * @return {function} 返回客户调用函数
22 | */
23 | export function debounce(func: () => void, wait: number, immediate?: boolean) {
24 | let timeout: any, args: any, context: any, timestamp: any, result: any
25 |
26 | const later = (): void => {
27 | // 现在和上一次时间戳比较
28 | const last = +new Date() - timestamp
29 | // 如果当前间隔时间少于设定时间且大于0就重新设置定时器
30 | if (last < wait && last >= 0) {
31 | timeout = setTimeout(later, wait - last)
32 | } else {
33 | // 否则的话就是时间到了执行回调函数
34 | timeout = null
35 | if (!immediate) {
36 | result = func.apply(context, args)
37 | if (!timeout) context = args = null
38 | }
39 | }
40 | }
41 |
42 | return (): any => {
43 | args = arguments
44 | // 获得时间戳
45 | timestamp = +new Date()
46 | // 如果定时器不存在且立即执行函数
47 | const callNow = immediate && !timeout
48 | // 如果定时器不存在就创建一个
49 | if (!timeout) timeout = setTimeout(later, wait)
50 | if (callNow) {
51 | // 如果需要立即执行函数的话 通过 apply 执行
52 | result = func.apply(context, args)
53 | context = args = null
54 | }
55 | return result
56 | }
57 | }
58 |
59 | /**
60 | * 是否是外部链接
61 | * @param {string} path
62 | * @return {boolean}
63 | */
64 | export function isExternal(path: string) {
65 | return /^(https?:|mailto:|tel:)/.test(path)
66 | }
67 |
68 | /**
69 | * 通过meta.role判断是否与当前用户权限匹配
70 | * @param roles
71 | * @param route
72 | */
73 | export function hasPermission(roles: any, route: any) {
74 | if (route.meta && route.meta.roles) {
75 | return roles.some((role: any) => route.meta.roles.includes(role))
76 | } else {
77 | return true
78 | }
79 | }
80 |
81 | /**
82 | * 递归过滤异步路由表,返回符合用户角色权限的路由表
83 | * @param routes asyncRouterMap
84 | * @param roles
85 | */
86 | export function filterAsyncRouter(routes: any, roles: any) {
87 | const res: any = []
88 |
89 | routes.forEach((route: any) => {
90 | const tmp = { ...route }
91 | if (hasPermission(roles, tmp)) {
92 | if (tmp.children) {
93 | tmp.children = filterAsyncRouter(tmp.children, roles)
94 | }
95 | res.push(tmp)
96 | }
97 | })
98 |
99 | return res
100 | }
101 |
--------------------------------------------------------------------------------
/src/utils/request.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { Message } from 'element-ui'
3 | import store from '@/store/store'
4 | import { getToken } from '@/utils/auth'
5 |
6 | const service = axios.create({
7 | // baseURL: process.env.BASE_API,
8 | baseURL: process.env.VUE_APP_MOCK_API,
9 | timeout: 5000
10 | })
11 |
12 | // request interceptor
13 | service.interceptors.request.use(
14 | config => {
15 | if (store.getters.token) {
16 | config.headers['X-Token'] = getToken()
17 | }
18 | return config
19 | },
20 | error => {
21 | Promise.reject(error)
22 | }
23 | )
24 |
25 | // response interceptor
26 | service.interceptors.response.use(
27 | response => {
28 | const res = response.data
29 | if (res.code !== 0) {
30 | Message({
31 | message: res.message || 'error',
32 | type: 'error',
33 | duration: 5 * 1000
34 | })
35 | return Promise.reject(res.message)
36 | } else {
37 | return res
38 | }
39 | },
40 | error => {
41 | Message({
42 | message: error.message,
43 | type: 'error',
44 | duration: 5 * 1000
45 | })
46 | return Promise.reject(error)
47 | }
48 | )
49 |
50 | export default service
51 |
--------------------------------------------------------------------------------
/src/utils/validate.ts:
--------------------------------------------------------------------------------
1 | export function isvalidUsername(str: any) {
2 | if (!str) {
3 | return ''
4 | }
5 | const valid = ['admin', 'editor']
6 | return valid.indexOf(str.trim()) >= 0
7 | }
8 |
9 | export function isExternal(path: string) {
10 | return /^(https?:|mailto:|tel:)/.test(path)
11 | }
12 |
13 | export function validStr(str: string) {
14 | const n = /([^a-zA-Z0-9_])+/
15 | return n.test(str) ? !0 : !1
16 | }
17 |
18 | export function validEmail(str: string) {
19 | const n = /^([a-zA-Z0-9_.-])+@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/
20 | return n.test(str) ? !0 : !1
21 | }
22 |
--------------------------------------------------------------------------------
/src/views/charts/keyboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
20 |
21 |
28 |
--------------------------------------------------------------------------------
/src/views/charts/line.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | line
4 |
5 |
6 |
7 |
15 |
16 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/views/dashboard/components/BarChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
129 |
--------------------------------------------------------------------------------
/src/views/dashboard/components/BoxCard.vue:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oOBobbyOo/vue-element-admin/da0051e753fc16642c7e056ee7d3f76eaa3175f4/src/views/dashboard/components/BoxCard.vue
--------------------------------------------------------------------------------
/src/views/dashboard/components/LineChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
182 |
183 |
184 |
185 |
186 |
187 |
--------------------------------------------------------------------------------
/src/views/dashboard/components/PanelGroup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
15 |
24 |
25 |
26 |
35 |
36 |
37 |
46 |
47 |
48 |
49 |
50 |
67 |
68 |
142 |
--------------------------------------------------------------------------------
/src/views/dashboard/components/PieChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
100 |
--------------------------------------------------------------------------------
/src/views/dashboard/components/RaddarChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
137 |
--------------------------------------------------------------------------------
/src/views/dashboard/components/TransactionTable.vue:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oOBobbyOo/vue-element-admin/da0051e753fc16642c7e056ee7d3f76eaa3175f4/src/views/dashboard/components/TransactionTable.vue
--------------------------------------------------------------------------------
/src/views/dashboard/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
86 |
87 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/src/views/errorPage/401.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
返回
4 |
5 |
6 | Oops!
7 | gif来源airbnb 页面
8 | 你没有权限去该页面
9 | 如有不满请联系你领导
10 |
11 | - 或者你可以去:
12 | -
13 | 回首页
14 |
15 | -
16 | 随便看看
17 |
18 | - 点我看图
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
53 |
54 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/src/views/errorPage/404.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
OOPS!
12 |
16 |
{{ message }}
17 |
18 | 请检查您输入的网址是否正确,请点击以下按钮返回主页或者发送错误报告
19 |
20 |
返回首页
21 |
22 |
23 |
24 |
25 |
26 |
36 |
37 |
231 |
--------------------------------------------------------------------------------
/src/views/forgetPwd/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
42 |
43 |
44 |
148 |
149 |
--------------------------------------------------------------------------------
/src/views/icons/element-icons.ts:
--------------------------------------------------------------------------------
1 | const elementIcons: string[] = [
2 | 'info',
3 | 'error',
4 | 'success',
5 | 'warning',
6 | 'question',
7 | 'back',
8 | 'arrow-left',
9 | 'arrow-down',
10 | 'arrow-right',
11 | 'arrow-up',
12 | 'caret-left',
13 | 'caret-bottom',
14 | 'caret-top',
15 | 'caret-right',
16 | 'd-arrow-left',
17 | 'd-arrow-right',
18 | 'minus',
19 | 'plus',
20 | 'remove',
21 | 'circle-plus',
22 | 'remove-outline',
23 | 'circle-plus-outline',
24 | 'close',
25 | 'check',
26 | 'circle-close',
27 | 'circle-check',
28 | 'circle-close-outline',
29 | 'circle-check-outline',
30 | 'zoom-out',
31 | 'zoom-in',
32 | 'd-caret',
33 | 'sort',
34 | 'sort-down',
35 | 'sort-up',
36 | 'tickets',
37 | 'document',
38 | 'goods',
39 | 'sold-out',
40 | 'news',
41 | 'message',
42 | 'date',
43 | 'printer',
44 | 'time',
45 | 'bell',
46 | 'mobile-phone',
47 | 'service',
48 | 'view',
49 | 'menu',
50 | 'more',
51 | 'more-outline',
52 | 'star-on',
53 | 'star-off',
54 | 'location',
55 | 'location-outline',
56 | 'phone',
57 | 'phone-outline',
58 | 'picture',
59 | 'picture-outline',
60 | 'delete',
61 | 'search',
62 | 'edit',
63 | 'edit-outline',
64 | 'rank',
65 | 'refresh',
66 | 'share',
67 | 'setting',
68 | 'upload',
69 | 'upload2',
70 | 'download',
71 | 'loading'
72 | ]
73 |
74 | export default elementIcons
75 |
--------------------------------------------------------------------------------
/src/views/icons/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
15 |
16 |
17 | {{ generateSvgIconCode(item) }}
18 |
19 |
20 |
21 | {{ item }}
22 |
23 |
24 |
25 |
26 |
27 |
33 |
34 |
35 | {{ generateElementIconCode(item) }}
36 |
37 |
38 |
39 | {{ item }}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
77 |
78 |
--------------------------------------------------------------------------------
/src/views/icons/svg-icons.ts:
--------------------------------------------------------------------------------
1 | const req = require.context('../../icons/svg', false, /\.svg$/)
2 | const requireAll = (requireContext: any) => requireContext.keys()
3 |
4 | const re = /\.\/(.*)\.svg/
5 |
6 | const svgIcons = requireAll(req).map((i: any) => {
7 | return i.match(re)[1]
8 | })
9 |
10 | export default svgIcons
11 |
--------------------------------------------------------------------------------
/src/views/layout/Layout.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
48 |
49 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/src/views/layout/components/AppMain.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
17 |
18 |
27 |
--------------------------------------------------------------------------------
/src/views/layout/components/NavBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
29 |
30 |
31 |
32 |
79 |
80 |
133 |
134 |
--------------------------------------------------------------------------------
/src/views/layout/components/Sidebar/Item.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{title}}
5 |
6 |
7 |
8 |
20 |
21 |
--------------------------------------------------------------------------------
/src/views/layout/components/Sidebar/Link.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/views/layout/components/Sidebar/SidebarItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/views/layout/components/Sidebar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/layout/components/TagsView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
22 |
--------------------------------------------------------------------------------
/src/views/layout/components/index.ts:
--------------------------------------------------------------------------------
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 TagsView } from './TagsView.vue'
5 |
--------------------------------------------------------------------------------
/src/views/login/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/views/register/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
75 |
76 |
77 |
257 |
--------------------------------------------------------------------------------
/src/views/tables/dynamic.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Dynamic table
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/views/tables/simple.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "importHelpers": true,
8 | "moduleResolution": "node",
9 | "experimentalDecorators": true,
10 | "esModuleInterop": true,
11 | "allowSyntheticDefaultImports": true,
12 | "sourceMap": true,
13 | "baseUrl": ".",
14 | "types": ["webpack-env"],
15 | "paths": {
16 | "@/*": ["src/*"]
17 | },
18 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
19 | },
20 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "tests/**/*.ts", "tests/**/*.tsx"],
21 | "exclude": ["node_modules"]
22 | }
23 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "warning",
3 | "extends": [
4 | "tslint:recommended"
5 | ],
6 | "linterOptions": {
7 | "exclude": [
8 | "node_modules/**"
9 | ]
10 | },
11 | "rules": {
12 | "quotemark": [true, "single"],
13 | "indent": [true, "spaces", 2],
14 | "interface-name": false,
15 | "ordered-imports": false,
16 | "object-literal-sort-keys": false,
17 | "no-consecutive-blank-lines": false,
18 | "no-console": false, //允许使用console
19 | "member-access": [true, "no-public"], //禁止指定公共可访问性,因为这是默认值
20 | // "noImplicitAny": false, //允许参数而不声明其类型
21 | "one-variable-per-declaration": false, //允许在同一声明语句中使用多个变量定义
22 | "no-unused-expression": [true, "allow-fast-null-checks"], //允许使用逻辑运算符执行快速空检查并执行副作用的方法或函数调用( 例如e && e.preventDefault())
23 | "curly": [true, "ignore-same-line"],
24 | "arrow-parens": [true, "ban-single-arg-parens"],
25 | "semicolon": [true, "never"],
26 | "trailing-comma": [
27 | true,
28 | {
29 | "multiline": {
30 | "objects": "ignore",
31 | "arrays": "ignore",
32 | "functions": "ignore",
33 | "typeLiterals": "ignore"
34 | },
35 | "esSpecCompliant": true
36 | }
37 | ]
38 | }
39 | }
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | // vue.config.js
2 | var path = require('path');
3 |
4 | function resolve(dir) {
5 | return path.join(__dirname, './', dir);
6 | }
7 |
8 | module.exports = {
9 | publicPath: '/',
10 | outputDir: 'dist',
11 | // 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录。
12 | assetsDir: 'assets',
13 | // 指定生成的 index.html 的输出路径 (相对于 outputDir)。也可以是一个绝对路径。
14 | indexPath: 'index.html',
15 | chainWebpack: config => {
16 | config.module
17 | .rule('svg')
18 | .exclude.add(resolve('src/icons'))
19 | .end();
20 |
21 | config.module
22 | .rule('icons')
23 | .test(/\.svg$/)
24 | .include.add(resolve('src/icons'))
25 | .end()
26 | .use('svg-sprite-loader')
27 | .loader('svg-sprite-loader')
28 | .options({
29 | symbolId: 'icon-[name]'
30 | });
31 | }
32 | };
--------------------------------------------------------------------------------