├── .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 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 148 | 149 | 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 | 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 | 15 | 16 | 58 | 59 | -------------------------------------------------------------------------------- /src/components/Charts/KeyboardChart.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/components/GithubCorner/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | 20 | 21 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 25 | 26 | 41 | -------------------------------------------------------------------------------- /src/components/Screenfull/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 43 | 44 | 54 | 55 | -------------------------------------------------------------------------------- /src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 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 | 6 | 7 | 20 | 21 | 28 | -------------------------------------------------------------------------------- /src/views/charts/line.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/views/dashboard/components/BarChart.vue: -------------------------------------------------------------------------------- 1 | 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 | 4 | 5 | 182 | 183 | 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /src/views/dashboard/components/PanelGroup.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 67 | 68 | 142 | -------------------------------------------------------------------------------- /src/views/dashboard/components/PieChart.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 100 | -------------------------------------------------------------------------------- /src/views/dashboard/components/RaddarChart.vue: -------------------------------------------------------------------------------- 1 | 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 | 31 | 32 | 33 | 86 | 87 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /src/views/errorPage/401.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 53 | 54 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/views/errorPage/404.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 36 | 37 | 231 | -------------------------------------------------------------------------------- /src/views/forgetPwd/index.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 12 | 13 | 48 | 49 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/views/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | 18 | 27 | -------------------------------------------------------------------------------- /src/views/layout/components/NavBar.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 79 | 80 | 133 | 134 | -------------------------------------------------------------------------------- /src/views/layout/components/Sidebar/Item.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 20 | 21 | -------------------------------------------------------------------------------- /src/views/layout/components/Sidebar/Link.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/views/layout/components/Sidebar/SidebarItem.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | -------------------------------------------------------------------------------- /src/views/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/views/layout/components/TagsView.vue: -------------------------------------------------------------------------------- 1 | 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 | 54 | 55 | -------------------------------------------------------------------------------- /src/views/register/index.vue: -------------------------------------------------------------------------------- 1 | 76 | 77 | 257 | -------------------------------------------------------------------------------- /src/views/tables/dynamic.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/tables/simple.vue: -------------------------------------------------------------------------------- 1 | 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 | }; --------------------------------------------------------------------------------