├── static └── .gitkeep ├── src ├── assets │ ├── scss │ │ ├── main.scss │ │ ├── mixin.scss │ │ └── base.scss │ └── images │ │ └── start_loading.svg ├── api │ ├── constant │ │ ├── file.js │ │ ├── user.js │ │ └── table.js │ ├── modules │ │ ├── file.js │ │ ├── user.js │ │ └── table.js │ └── index.js ├── pages │ ├── error │ │ ├── images │ │ │ └── 404.png │ │ └── index.vue │ ├── user │ │ └── login │ │ │ ├── images │ │ │ ├── login_body.jpg │ │ │ └── login_logo.png │ │ │ └── index.vue │ ├── charts │ │ └── bar │ │ │ └── index.vue │ ├── table │ │ ├── base │ │ │ └── index.vue │ │ └── sort │ │ │ └── index.vue │ └── home │ │ └── index.vue ├── layouts │ ├── default │ │ ├── components │ │ │ ├── images │ │ │ │ └── logo.png │ │ │ ├── header.vue │ │ │ └── menu.vue │ │ └── index.vue │ └── full │ │ └── index.vue ├── store │ ├── actions │ │ ├── type.js │ │ └── index.js │ ├── mutations │ │ ├── type.js │ │ └── index.js │ ├── states │ │ └── index.js │ ├── getters │ │ ├── type.js │ │ └── index.js │ └── index.js ├── App.vue ├── common │ ├── setting.js │ ├── storage │ │ ├── index.js │ │ ├── storage.js │ │ └── cookie.js │ ├── queryString.js │ └── verify.js ├── router │ ├── routes.js │ └── index.js ├── plugin │ └── index.js ├── components │ ├── index.js │ ├── pagination │ │ └── index.js │ └── container │ │ └── index.vue ├── main.js ├── element-ui │ └── index.js └── fetch │ └── index.js ├── favicon.ico ├── .eslintignore ├── .env.development ├── .env.production ├── .editorconfig ├── .postcssrc.js ├── .babelrc ├── .gitignore ├── .eslintrc.js ├── index.ejs ├── LICENSE ├── README.md └── package.json /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/scss/main.scss: -------------------------------------------------------------------------------- 1 | @import "./base"; 2 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imingdev/vue-admin/HEAD/favicon.ico -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | src/router/auto-routes.js 6 | -------------------------------------------------------------------------------- /src/api/constant/file.js: -------------------------------------------------------------------------------- 1 | // 图片上传 2 | export const imageUpload = '/api/post/image/upload' 3 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | WEB_APP_API=https://www.easy-mock.com/mock/5c95c746747db1013e6e1cc4/vue-admin 2 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | WEB_APP_API=https://www.easy-mock.com/mock/5c95c746747db1013e6e1cc4/vue-admin 2 | -------------------------------------------------------------------------------- /src/pages/error/images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imingdev/vue-admin/HEAD/src/pages/error/images/404.png -------------------------------------------------------------------------------- /src/pages/user/login/images/login_body.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imingdev/vue-admin/HEAD/src/pages/user/login/images/login_body.jpg -------------------------------------------------------------------------------- /src/pages/user/login/images/login_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imingdev/vue-admin/HEAD/src/pages/user/login/images/login_logo.png -------------------------------------------------------------------------------- /src/api/constant/user.js: -------------------------------------------------------------------------------- 1 | // 用户登录 2 | export const login = '/api/post/user/login' 3 | // 用户登出 4 | export const logout = '/api/post/user/logout' 5 | -------------------------------------------------------------------------------- /src/layouts/default/components/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imingdev/vue-admin/HEAD/src/layouts/default/components/images/logo.png -------------------------------------------------------------------------------- /src/store/actions/type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @intro: action的类型管理. 3 | * 4 | */ 5 | 6 | // 设置用户信息和登录 7 | export const SET_USER_INFO = 'SET_USER_INFO' 8 | -------------------------------------------------------------------------------- /src/store/mutations/type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @intro: mutation类型管理. 3 | * 4 | */ 5 | 6 | // 设置用户信息和是否登录 7 | export const SET_USER_INFO = 'SET_USER_INFO' 8 | -------------------------------------------------------------------------------- /src/store/states/index.js: -------------------------------------------------------------------------------- 1 | import {sessionStorage} from 'src/common/storage' 2 | 3 | export default { 4 | // 用户信息和是否登录 5 | userInfo: sessionStorage.get('user_info') 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /src/store/getters/type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @intro: getters的类型管理. 3 | * 4 | */ 5 | 6 | // 获取用户信息 7 | export const GET_USER_INFO = 'GET_USER_INFO' 8 | // 判断是否登录 9 | export const GET_IS_LOGIN = 'GET_IS_LOGIN' 10 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 9 | 11 | -------------------------------------------------------------------------------- /src/common/setting.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @intro: 一些配置. 3 | */ 4 | // 主页 5 | export const homePage = '/home' 6 | // 本地存储的前缀 7 | export const storagePrefix = 'adming_storage_' 8 | // 接入服务器接口地址根目录 9 | export const serverBaseUrl = process.env.WEB_APP_API 10 | -------------------------------------------------------------------------------- /src/router/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @intro: 路由. 3 | * 4 | */ 5 | import routes, {Error, Home} from './auto-routes' 6 | 7 | export default [{ 8 | path: '*', 9 | redirect: Error.path 10 | }, { 11 | path: '/', 12 | redirect: Home.path 13 | }].concat(routes) 14 | -------------------------------------------------------------------------------- /src/api/modules/file.js: -------------------------------------------------------------------------------- 1 | import fetch from 'src/fetch' 2 | import * as fileUrl from '../constant/file' 3 | 4 | // 图片上传 5 | export const imageUpload = data => { 6 | return fetch({ 7 | url: fileUrl.imageUpload, 8 | method: 'post', 9 | data 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /src/common/storage/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @intro: storage存储. 3 | * 4 | */ 5 | 6 | import {localStorage, sessionStorage} from './storage' 7 | import cookieStorage from './cookie' 8 | 9 | export { 10 | localStorage, 11 | sessionStorage, 12 | cookieStorage 13 | } 14 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/store/actions/index.js: -------------------------------------------------------------------------------- 1 | import * as actions from 'src/store/actions/type' 2 | import * as mutations from 'src/store/mutations/type' 3 | 4 | export default { 5 | // 设置用户信息和登录 6 | [actions.SET_USER_INFO] ({commit}, userInfo) { 7 | commit(mutations.SET_USER_INFO, userInfo) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/layouts/full/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 9 | 14 | 17 | -------------------------------------------------------------------------------- /src/store/getters/index.js: -------------------------------------------------------------------------------- 1 | import {GET_USER_INFO, GET_IS_LOGIN} from 'src/store/getters/type' 2 | 3 | export default { 4 | // 获取用户信息 5 | [GET_USER_INFO]: state => { 6 | return state.userInfo || {} 7 | }, 8 | // 判断是否登录 9 | [GET_IS_LOGIN]: state => !!state.userInfo && JSON.stringify(state.userInfo) !== '{}' 10 | } 11 | -------------------------------------------------------------------------------- /src/api/constant/table.js: -------------------------------------------------------------------------------- 1 | // 数据列表 2 | export const list = '/api/get/table/list' 3 | // 根据id查询数据 4 | export const get = '/api/get/table/get' 5 | // 根据id删除数据 6 | export const del = '/api/post/table/del' 7 | // 添加或修改数据 8 | export const save = '/api/post/table/save' 9 | // 批量删除 10 | export const batchDel = '/api/post/table/batch/del' 11 | -------------------------------------------------------------------------------- /src/pages/charts/bar/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 18 | -------------------------------------------------------------------------------- /src/pages/table/base/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 18 | -------------------------------------------------------------------------------- /src/pages/table/sort/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 18 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-0" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime", ["component", {"libraryName": "element-ui", "styleLibraryName": "theme-chalk"}]] 12 | } 13 | -------------------------------------------------------------------------------- /src/pages/home/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 14 | 17 | -------------------------------------------------------------------------------- /src/api/modules/user.js: -------------------------------------------------------------------------------- 1 | import fetch from 'src/fetch' 2 | import * as userUrl from '../constant/user' 3 | 4 | // 登录 5 | export const login = (data) => { 6 | return fetch({ 7 | url: userUrl.login, 8 | method: 'post', 9 | data 10 | }) 11 | } 12 | // 登出 13 | export const logout = () => { 14 | return fetch({ 15 | url: userUrl.logout, 16 | method: 'post' 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 状态管理器 4 | * 5 | */ 6 | 7 | import Vue from 'vue' 8 | import Vuex from 'vuex' 9 | 10 | // 引入模块 11 | import actions from './actions' 12 | import getters from './getters' 13 | import mutations from './mutations' 14 | import state from './states' 15 | 16 | Vue.use(Vuex) 17 | 18 | export default new Vuex.Store({ 19 | state, 20 | getters, 21 | actions, 22 | mutations 23 | }) 24 | -------------------------------------------------------------------------------- /src/store/mutations/index.js: -------------------------------------------------------------------------------- 1 | import * as type from 'src/store/mutations/type' 2 | import {sessionStorage} from 'src/common/storage' 3 | 4 | export default { 5 | // 设置用户信息和是否登录 6 | [type.SET_USER_INFO] (state, userInfo) { 7 | state.userInfo = userInfo 8 | if (!userInfo) { 9 | sessionStorage.remove('user_info') 10 | } else { 11 | sessionStorage.set('user_info', userInfo) 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/plugin/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @intro: 插件. 3 | */ 4 | import * as verify from 'src/common/verify' 5 | import api from 'src/api' 6 | 7 | const install = Vue => { 8 | if (install.installed) return false 9 | 10 | // 添加到Vue的原型链上 11 | Object.defineProperties(Vue.prototype, { 12 | $valid: {value: verify}, 13 | $api: {value: api} 14 | }) 15 | 16 | install.installed = true 17 | } 18 | 19 | export default install 20 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @intro: api集合,将扫描modules下面的所有接口,并且根据路径生成驼峰命名为key. 3 | */ 4 | let _result = {} 5 | // 驼峰命名转换 6 | const camelCaseChange = (str, separator = '/') => str.replace(new RegExp(`${separator}(\\w)`, 'g'), str => str.slice(1).toUpperCase()) 7 | const context = require.context('./modules', true, /\.js$/) 8 | context.keys().map(item => { 9 | const k = camelCaseChange(item.match(/\.\/(\S*)\.js$/)[1]) 10 | _result[k] = context(item) 11 | }) 12 | 13 | export default _result 14 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import container from './container' 2 | import pagination from './pagination' 3 | 4 | const components = [ 5 | container, 6 | pagination 7 | ] 8 | 9 | const install = Vue => { 10 | if (install.installed) return false 11 | 12 | for (let i = 0, len = components.length; i < len; i++) { 13 | const component = components[i] 14 | Vue.component(component.name, component) 15 | } 16 | 17 | install.installed = true 18 | } 19 | 20 | export default install 21 | -------------------------------------------------------------------------------- /src/common/queryString.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 将对象解析成key=value&key2=value2模式. 3 | * @param obj 对象 4 | * @returns {string} 5 | */ 6 | export default obj => { 7 | obj = obj || {} 8 | let _result = '' 9 | for (let key in obj) { 10 | let value = obj[key] 11 | if (typeof value !== 'undefined' && value !== null && value !== '') { 12 | // 有时候数值型0会转成false 13 | if (_result) { 14 | _result += '&' 15 | } 16 | _result += `${key}=${value}` 17 | } 18 | } 19 | return _result 20 | } 21 | -------------------------------------------------------------------------------- /src/assets/scss/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin defaultBackgroundImage( 2 | $url, 3 | $size:cover, 4 | $color:transparent, 5 | $position:center, 6 | $repeat:no-repeat 7 | ) { 8 | @if ($url) { 9 | background: url($url) $repeat $position $color; 10 | } @else { 11 | background: $repeat $position $color; 12 | } 13 | @if ($size) { 14 | background-size: $size; 15 | } 16 | } 17 | 18 | @mixin defaultBackgroundAttr { 19 | background-repeat: no-repeat; 20 | background-position: center; 21 | background-size: cover; 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # maven ignore 2 | target/ 3 | 4 | # eclipse ignore 5 | .settings/ 6 | .project 7 | .classpath 8 | 9 | # idea ignore 10 | .idea/ 11 | *.ipr 12 | *.iml 13 | *.iws 14 | 15 | # temp ignore 16 | *.log 17 | *.cache 18 | *.diff 19 | *.patch 20 | *.tmp 21 | 22 | # system ignore 23 | .DS_Store 24 | Thumbs.db 25 | 26 | # project ignore 27 | **/tmp 28 | pom.xml.versionsBackup 29 | 30 | # node ignore 31 | node_modules/ 32 | dist/ 33 | package-lock.json 34 | yarn.lock 35 | auto-routes.js 36 | 37 | # local env files 38 | .env.local 39 | .env.*.local 40 | -------------------------------------------------------------------------------- /src/common/verify.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @intro: 常用验证工具类. 3 | * 4 | */ 5 | /** 6 | * //验证url是否正确 7 | * @param url 8 | * @returns {boolean} 9 | */ 10 | // eslint-disable-next-line 11 | export const isUrl = url => (/(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/i).test(url) 12 | /** 13 | * 验证手机号码是否正确 14 | * @param tel 15 | * @returns {boolean} 16 | */ 17 | export const isTel = tel => (/^1[3|4|5|8][0-9]\d{4,8}$/).test(tel) 18 | 19 | /** 20 | * 判断是否是object对象 21 | * @param obj 22 | * @returns {boolean} 23 | */ 24 | export const isObject = obj => !!obj && Object.prototype.toString.call(obj) === '[object Object]' 25 | /** 26 | * 判断是否是数组 27 | * @param array 28 | * @returns {boolean} 29 | */ 30 | export const isArray = array => !!array && Object.prototype.toString.call(array) === '[object Array]' 31 | -------------------------------------------------------------------------------- /src/components/pagination/index.js: -------------------------------------------------------------------------------- 1 | import Pagination from 'element-ui/lib/pagination' 2 | 3 | /** 4 | * 重写element-ui的组件,在原有的基础上新增两个prop,top和align 5 | * @param {String} top 距离顶部的距离 6 | * @param {String} align 对齐方式 7 | */ 8 | export default { 9 | ...Pagination, 10 | name: 'UiPagination', 11 | props: { 12 | ...Pagination.props || {}, 13 | top: { 14 | type: String, 15 | default: '20px' 16 | }, 17 | align: { 18 | type: String, 19 | default: 'right', 20 | validator: align => ['left', 'center', 'right'].indexOf(align) !== -1 21 | } 22 | }, 23 | render (createdElement) { 24 | const {top, align} = this 25 | const template = Pagination.render.call(this, createdElement) 26 | return createdElement('div', {style: {marginTop: top, textAlign: align}}, [template]) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/api/modules/table.js: -------------------------------------------------------------------------------- 1 | import fetch from 'src/fetch' 2 | import * as tableUrl from '../constant/table' 3 | 4 | // 数据列表 5 | export const list = params => { 6 | return fetch({ 7 | url: tableUrl.list, 8 | params 9 | }) 10 | } 11 | 12 | // 根据id查询数据 13 | export const get = (params) => { 14 | return fetch({ 15 | url: tableUrl.get, 16 | params 17 | }) 18 | } 19 | 20 | // 根据id删除数据 21 | export const del = (data) => { 22 | return fetch({ 23 | url: tableUrl.del, 24 | method: 'delete', 25 | data 26 | }) 27 | } 28 | // 添加或修改数据 29 | export const save = (data) => { 30 | return fetch({ 31 | url: tableUrl.save, 32 | method: 'post', 33 | data 34 | }) 35 | } 36 | // 批量删除 37 | export const batchDel = (data) => { 38 | return fetch({ 39 | url: tableUrl.batchDel, 40 | method: 'delete', 41 | data 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @intro: 主程序入口. 3 | */ 4 | 5 | // 导入样式 6 | import 'normalize.css' 7 | import 'font-awesome/scss/font-awesome.scss' 8 | import 'src/assets/scss/main.scss' 9 | // 导入Vue框架 10 | import Vue from 'vue' 11 | // 导入element组件 12 | import ElementUI from './element-ui' 13 | // 导入组件 14 | import components from './components' 15 | // 导入路由 16 | import router from './router' 17 | // 导入状态管理器 18 | import store from 'src/store' 19 | // 导入请求框架 20 | // import api from './api' 21 | // 导入自定义插件 22 | import plugin from './plugin' 23 | // 导入主视图文件 24 | import App from './App' 25 | 26 | // 注册 27 | Vue.use(ElementUI) 28 | Vue.use(components) 29 | Vue.use(plugin) 30 | 31 | // 发布后是否显示提示 32 | Vue.config.productionTip = false 33 | // 只有开发时才开启工具 34 | Vue.config.devtools = process.env.NODE_ENV === 'development' 35 | 36 | new Vue({ 37 | router, 38 | store, 39 | render: h => h(App) 40 | }).$mount('mainbody') 41 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | browser: true, 10 | }, 11 | extends: [ 12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 14 | 'plugin:vue/essential', 15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 16 | 'standard' 17 | ], 18 | // required to lint *.vue files 19 | plugins: [ 20 | 'vue' 21 | ], 22 | // add your custom rules here 23 | rules: { 24 | // allow async-await 25 | 'generator-star-spacing': 'off', 26 | // allow debugger during development 27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | AdminX 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/layouts/default/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 19 | 31 | 42 | -------------------------------------------------------------------------------- /src/components/container/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 24 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 mingdev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @intro: 路由配置. 3 | */ 4 | import Vue from 'vue' 5 | import VueRouter from 'vue-router' 6 | import routes from './routes' 7 | import NProgress from 'nprogress' 8 | import 'nprogress/nprogress.css' 9 | import Store from 'src/store' 10 | import {GET_IS_LOGIN} from 'src/store/getters/type' 11 | import {UserLogin} from './auto-routes' 12 | 13 | Vue.use(VueRouter) 14 | 15 | const router = new VueRouter({ 16 | routes, 17 | mode: 'hash', // default: hash ,history 18 | scrollBehavior (to, from, savedPosition) { 19 | if (savedPosition) { 20 | return savedPosition 21 | } else { 22 | return {x: 0, y: 0} 23 | } 24 | } 25 | }) 26 | 27 | // 全局路由配置 28 | // 路由开始之前的操作 29 | router.beforeEach((to, from, next) => { 30 | if (from.path !== '/') { 31 | NProgress.done().start() 32 | } 33 | 34 | const isLogin = Store.getters[GET_IS_LOGIN] 35 | const auth = to.meta.auth 36 | 37 | if (auth === false || auth === 'false') { 38 | // 不需要验证的 39 | next() 40 | } else { 41 | // 已经登录的继续执行,没登录就跑到登录页面中去 42 | isLogin ? next() : router.replace(UserLogin.path) 43 | } 44 | }) 45 | 46 | // 路由完成之后的操作 47 | router.afterEach(route => { 48 | NProgress.done() 49 | }) 50 | 51 | export default router 52 | -------------------------------------------------------------------------------- /src/common/storage/storage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @intro: Storage工具类. 3 | * 4 | */ 5 | 6 | // 存储前缀 7 | import {storagePrefix} from 'src/common/setting' 8 | 9 | class Storage { 10 | constructor (type) { 11 | if (type === 'local') { 12 | this.store = window.localStorage 13 | } else if (type === 'session') { 14 | this.store = window.sessionStorage 15 | } 16 | this.prefix = storagePrefix 17 | } 18 | 19 | set (key, value) { 20 | try { 21 | value = JSON.stringify(value) 22 | this.store.setItem(this.prefix + key, value) 23 | } catch (e) { 24 | // eslint-disable-next-line 25 | } 26 | 27 | return this 28 | } 29 | 30 | get (key) { 31 | if (!key) { 32 | throw new Error('没有找到key。') 33 | } 34 | if (typeof key === 'object') { 35 | throw new Error('key不能是一个对象。') 36 | } 37 | let value = this.store.getItem(this.prefix + key) 38 | 39 | if (value === null) { 40 | return {} 41 | } 42 | 43 | try { 44 | value = JSON.parse(value) 45 | } catch (e) { 46 | value = {} 47 | } 48 | return value 49 | } 50 | 51 | remove (key) { 52 | this.store.removeItem(this.prefix + key) 53 | return this 54 | } 55 | } 56 | 57 | export const localStorage = new Storage('local') 58 | export const sessionStorage = new Storage('session') 59 | -------------------------------------------------------------------------------- /src/element-ui/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @intro: element-ui组件. 3 | */ 4 | import { 5 | // plugin 6 | Loading, 7 | MessageBox, 8 | Notification, 9 | Message, 10 | 11 | // component 12 | Input, 13 | Button, 14 | Dropdown, 15 | DropdownMenu, 16 | DropdownItem, 17 | Form, 18 | FormItem, 19 | Dialog, 20 | Table, 21 | TableColumn, 22 | Pagination, 23 | Main, 24 | Container, 25 | Menu, 26 | MenuItem, 27 | Submenu, 28 | Aside 29 | } from 'element-ui' 30 | // 按道理来说可以导入进来,但是提示没有这个,所以直接单独导入进来吧 31 | import Scrollbar from 'element-ui/lib/scrollbar' 32 | 33 | const components = [ 34 | Input, 35 | Button, 36 | Dropdown, 37 | DropdownMenu, 38 | DropdownItem, 39 | Form, 40 | FormItem, 41 | Dialog, 42 | Option, 43 | Table, 44 | TableColumn, 45 | Pagination, 46 | Main, 47 | Container, 48 | Menu, 49 | MenuItem, 50 | Submenu, 51 | Aside, 52 | Scrollbar 53 | ] 54 | 55 | const install = Vue => { 56 | if (install.installed) return false 57 | 58 | components.forEach(component => { 59 | Vue.component(component.name, component) 60 | }) 61 | 62 | Vue.use(Loading.directive) 63 | 64 | Vue.prototype.$loading = Loading.service 65 | Vue.prototype.$msgbox = MessageBox 66 | Vue.prototype.$alert = MessageBox.alert 67 | Vue.prototype.$confirm = MessageBox.confirm 68 | Vue.prototype.$prompt = MessageBox.prompt 69 | Vue.prototype.$notify = Notification 70 | Vue.prototype.$message = Message 71 | 72 | install.installed = true 73 | } 74 | 75 | export default install 76 | -------------------------------------------------------------------------------- /src/assets/images/start_loading.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/error/index.vue: -------------------------------------------------------------------------------- 1 | 2 | { 3 | layoutName:'full', 4 | meta:{ 5 | auth: false 6 | } 7 | } 8 | 9 | 21 | 36 | 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-admin [Live Demo](http://vue-admin.js.org) 2 | 3 | ### 安装 4 | 5 | *** 6 | 项目地址: (`git clone`) 7 | ```shell 8 | git clone https://github.com/imingdev/vue-admin.git 9 | ``` 10 | 通过`npm`安装本地服务第三方依赖模块(需要已安装[Node.js](https://nodejs.org/)) 11 | 12 | ``` 13 | npm install 14 | ``` 15 | 启动服务: (http://localhost:3001) 16 | 17 | ``` 18 | npm run dev 19 | ``` 20 | 发布代码 21 | 22 | ``` 23 | npm run build 24 | ``` 25 | *** 26 | ### 目录结构 27 |
28 | ├── build                     // 项目的 Webpack 配置文件
29 | ├── src                       // 开发目录
30 | │   ├── api                   // 请求接口
31 | │   ├── assets                // 一些资源文件
32 | │   ├── common                // 通用文件、如工具类、状态码
33 | │   ├── components            // 各种组件
34 | │   ├── element-ui            // element-ui动态注册组件
35 | │   ├── fetch                 // axios 封装
36 | │   ├── layout                // 布局文件
37 | │   ├── pages                 // 各种页面
38 | │   ├── plugin                // 各种插件
39 | │   ├── router                // 路由配置及map
40 | │   ├── store                 // Vuex 状态管理器
41 | │   ├── App.vue               // 根组件
42 | │   ├── main.js               // Webpack 编译入口文件,入口js
43 | ├── static                    // 静态资源,一般把不需要处理的文件可以放这里
44 | ├── .babelrc                  // babelrc配置文件
45 | ├── .editorconfig             // 代码风格文件,前提是要你的编辑器支持
46 | ├── .env.development          // 开发环境的一些属性
47 | ├── .env.production           // 生产环境的一些属性
48 | ├── .eslintignore             // eslint 的忽略配置
49 | ├── .eslintrc.js              // eslint 的配置
50 | ├── .gitignore                // 用于Git配置不需要加入版本管理的文件
51 | ├── .postcssrc.js             // autoprefixer的配置文件
52 | ├── favicon.ico               // ico小图标
53 | ├── index.ejs                 // 项目入口文件
54 | ├── package.json              // 项目配置文件
55 | 
56 | 57 | ![image](https://raw.githubusercontent.com/imingdev/vue-admin/images/images_1.jpg) 58 |
59 | ![image](https://raw.githubusercontent.com/imingdev/vue-admin/images/images_2.jpg) 60 |
61 | ![image](https://raw.githubusercontent.com/imingdev/vue-admin/images/images_3.jpg) 62 | *** 63 | ##### 捐赠 64 | ![image](https://raw.githubusercontent.com/imingdev/vue-admin/images/images_4.jpg) 65 | 66 | 喜欢这个项目?捐助一杯咖啡支持下(¥28) 67 | *** 68 | ### 结束 69 | 70 | 有什么想交流的请联系我:[mingdev@163.com](mailto:mingdev@163.com),QQ群:[307914534](https://jq.qq.com/?_wv=1027&k=46l9UkQ) 71 | -------------------------------------------------------------------------------- /src/fetch/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @intro: http统一封装,同时支持callback模式调用. 3 | */ 4 | import axios from 'axios' 5 | import {serverBaseUrl} from 'src/common/setting' 6 | import queryString from 'src/common/queryString' 7 | 8 | // 默认get请求 9 | const DEFAULT_METHOD = 'get' 10 | 11 | const Http = async options => { 12 | // eslint-disable-next-line 13 | if (!options) throw '请求参数不能为空!' 14 | if (typeof options === 'string') options = {url: options} 15 | 16 | const { 17 | before, // 请求开始之前的函数 18 | success, // 请求成功的函数 19 | error, // 请求失败,如果是系统错误(500,400)则type为system,其他为business 20 | complete, // 请求完成,这里不管成功还是失败都会执行 21 | method = DEFAULT_METHOD, // 请求方式,默认为get 22 | // params = {}, // get请求参数 23 | data = {} // 非get请求参数 24 | } = options 25 | 26 | // 请求之前调用一次 27 | before && before(options) 28 | 29 | if (method.toLowerCase() !== DEFAULT_METHOD) { 30 | // 将参数转成form表单的形式 31 | options.data = queryString(data) 32 | } 33 | 34 | // https://github.com/mzabriskie/axios 35 | // 创建一个axios实例 36 | const instance = axios.create({ 37 | // 设置全局默认的headers 38 | headers: { 39 | 'Content-Type': 'application/x-www-form-urlencoded' 40 | }, 41 | // 设置默认根地址 42 | baseURL: serverBaseUrl, 43 | // 设置请求超时设置 44 | timeout: 30000, 45 | // 返回类型json 46 | responseType: 'json' 47 | }) 48 | 49 | try { 50 | // 开始请求 51 | const response = await instance(options) 52 | 53 | /** 54 | * code非0是抛错 55 | */ 56 | console.log('http: response', response) 57 | const data = response.data 58 | if (+data.code === 0) { 59 | // 请求成功 60 | console.log('http: response success', data) 61 | success && success(data) 62 | return Promise.resolve(data) 63 | } else { 64 | // 失败(这里可以做未登录或者其他权限处理) 65 | if (data.code === -2) { 66 | // 用户登录失效 67 | } 68 | const errorObj = {type: 'business', ...data} 69 | console.log('http: response error', errorObj) 70 | // 失败 71 | error && error(errorObj) 72 | return Promise.reject(errorObj) 73 | } 74 | } catch (err) { 75 | console.log('err', err) 76 | let resError = err.response || {} 77 | let resCode = resError.status || '500' 78 | let resMsg = err.message || '操作失败,请稍后重试!' 79 | const errorObj = {code: resCode, message: resMsg, type: 'system'} 80 | console.log('http: response error', errorObj) 81 | error && error(errorObj) 82 | return Promise.reject(errorObj) 83 | } finally { 84 | console.log('http: complete') 85 | complete && complete() 86 | } 87 | } 88 | 89 | export default Http 90 | -------------------------------------------------------------------------------- /src/assets/scss/base.scss: -------------------------------------------------------------------------------- 1 | @import "./mixin"; 2 | 3 | * { 4 | word-break: break-all; 5 | word-wrap: break-word; 6 | padding: 0; 7 | margin: 0; 8 | outline: 0; 9 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 10 | box-sizing: border-box; 11 | } 12 | 13 | html, body { 14 | font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif; 15 | height: 100%; 16 | } 17 | 18 | ol, ul, li { 19 | list-style-type: none; 20 | } 21 | 22 | a { 23 | text-decoration: none; 24 | 25 | &:hover { 26 | text-decoration: underline; 27 | } 28 | } 29 | 30 | /************ Embedded content ************/ 31 | img { 32 | border: 0; 33 | width: auto; 34 | height: auto; 35 | max-width: 100% !important; 36 | max-height: 100%; 37 | vertical-align: top; 38 | -ms-interpolation-mode: bicubic; 39 | } 40 | 41 | img:not([src*="/"]) { 42 | display: none !important; 43 | } 44 | 45 | svg:not(:root) { 46 | overflow: hidden; 47 | } 48 | 49 | /************ Grouping content ************/ 50 | figure { 51 | margin: 1em 40px; 52 | } 53 | 54 | hr { 55 | box-sizing: content-box; 56 | height: 0; 57 | } 58 | 59 | pre { 60 | overflow: auto; 61 | } 62 | 63 | code, kbd, pre, samp { 64 | font-family: monospace, monospace; 65 | font-size: 1em; 66 | } 67 | 68 | h1, h2, h3, h4, h5, h6 { 69 | font-size: 100%; 70 | font-weight: normal; 71 | } 72 | 73 | table { 74 | border-collapse: collapse; 75 | border-spacing: 0; 76 | } 77 | 78 | .block { 79 | display: block; 80 | } 81 | 82 | .inline_block { 83 | display: inline-block; 84 | } 85 | 86 | .hide { 87 | display: none; 88 | } 89 | 90 | .ofh { 91 | overflow: hidden; 92 | } 93 | 94 | .relative { 95 | position: relative; 96 | } 97 | 98 | .absolute { 99 | position: absolute; 100 | } 101 | 102 | .fl { 103 | float: left; 104 | } 105 | 106 | .fr { 107 | float: right; 108 | } 109 | 110 | .align_left { 111 | text-align: left; 112 | } 113 | 114 | .align_center { 115 | text-align: center; 116 | } 117 | 118 | .align_right { 119 | text-align: right; 120 | } 121 | 122 | .height_100 { 123 | height: 100%; 124 | } 125 | 126 | .width_100 { 127 | width: 100%; 128 | } 129 | 130 | .nowrap { 131 | white-space: nowrap; 132 | overflow: hidden; 133 | text-overflow: ellipsis; 134 | } 135 | 136 | .flex { 137 | display: flex; 138 | } 139 | 140 | .flex__item { 141 | flex: 1; 142 | } 143 | 144 | .pointer { 145 | cursor: pointer; 146 | } 147 | 148 | .backgroundCover { 149 | @include defaultBackgroundAttr; 150 | } 151 | -------------------------------------------------------------------------------- /src/layouts/default/components/header.vue: -------------------------------------------------------------------------------- 1 | 6 | 28 | 60 | 102 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "admin-vue", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "adming ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node_modules/.bin/webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "lint": "eslint --ext .js,.vue src", 11 | "build": "node build/build.js" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.18.0", 15 | "element-ui": "^2.6.3", 16 | "font-awesome": "^4.7.0", 17 | "normalize.css": "^8.0.1", 18 | "nprogress": "^0.2.0", 19 | "vue": "^2.5.2", 20 | "vue-router": "^3.0.1", 21 | "vuex": "^3.1.0" 22 | }, 23 | "devDependencies": { 24 | "autoprefixer": "^7.1.2", 25 | "babel-core": "^6.22.1", 26 | "babel-eslint": "^8.2.1", 27 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 28 | "babel-loader": "^7.1.1", 29 | "babel-plugin-component": "^1.1.1", 30 | "babel-plugin-syntax-jsx": "^6.18.0", 31 | "babel-plugin-transform-runtime": "^6.22.0", 32 | "babel-plugin-transform-vue-jsx": "^3.5.0", 33 | "babel-preset-env": "^1.3.2", 34 | "babel-preset-stage-0": "^6.24.1", 35 | "chalk": "^2.0.1", 36 | "cheerio": "^1.0.0-rc.2", 37 | "copy-webpack-plugin": "^4.0.1", 38 | "css-loader": "^0.28.0", 39 | "dotenv-webpack": "^1.7.0", 40 | "eslint": "^4.15.0", 41 | "eslint-config-standard": "^10.2.1", 42 | "eslint-friendly-formatter": "^3.0.0", 43 | "eslint-loader": "^1.7.1", 44 | "eslint-plugin-import": "^2.7.0", 45 | "eslint-plugin-node": "^5.2.0", 46 | "eslint-plugin-promise": "^3.4.0", 47 | "eslint-plugin-standard": "^3.0.1", 48 | "eslint-plugin-vue": "^4.0.0", 49 | "extract-text-webpack-plugin": "^3.0.0", 50 | "file-loader": "^1.1.4", 51 | "friendly-errors-webpack-plugin": "^1.6.1", 52 | "html-webpack-plugin": "^2.30.1", 53 | "ip": "^1.1.5", 54 | "node-notifier": "^5.1.2", 55 | "node-sass": "^4.11.0", 56 | "optimize-css-assets-webpack-plugin": "^3.2.0", 57 | "ora": "^1.2.0", 58 | "portfinder": "^1.0.13", 59 | "postcss-import": "^11.0.0", 60 | "postcss-loader": "^2.0.8", 61 | "postcss-url": "^7.2.1", 62 | "rimraf": "^2.6.0", 63 | "sass-loader": "^7.1.0", 64 | "semver": "^5.3.0", 65 | "shelljs": "^0.7.6", 66 | "uglifyjs-webpack-plugin": "^1.1.1", 67 | "url-loader": "^0.5.8", 68 | "vue-loader": "^13.3.0", 69 | "vue-routes-auto-webpack": "^1.1.0", 70 | "vue-style-loader": "^3.0.1", 71 | "vue-template-compiler": "^2.5.2", 72 | "webpack": "^3.6.0", 73 | "webpack-bundle-analyzer": "^3.6.0", 74 | "webpack-dev-server": "^2.9.1", 75 | "webpack-merge": "^4.1.0" 76 | }, 77 | "engines": { 78 | "node": ">= 6.0.0", 79 | "npm": ">= 3.0.0" 80 | }, 81 | "browserslist": [ 82 | "> 1%", 83 | "last 2 versions", 84 | "not ie <= 8" 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /src/pages/user/login/index.vue: -------------------------------------------------------------------------------- 1 | 2 | { 3 | layoutName:'full', 4 | meta:{ 5 | auth: false 6 | } 7 | } 8 | 9 | 29 | 77 | 96 | -------------------------------------------------------------------------------- /src/common/storage/cookie.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @intro: cookie存储类. 3 | * 4 | */ 5 | 6 | import {storagePrefix} from 'src/common/setting' 7 | import {isArray, isObject} from 'src/common/verify' 8 | 9 | /** 10 | * cookies操作类 11 | */ 12 | class Cookie { 13 | /** 14 | * 构造函数 15 | */ 16 | constructor () { 17 | this.defaults = {} 18 | this.expiresMultiplier = 60 * 60 * 24 19 | this.prefix = storagePrefix 20 | } 21 | 22 | /** 23 | * 根据key获取cookie的值 24 | * @param {string} key 键 25 | * @returns {object} 值 26 | */ 27 | get (key) { 28 | if (!key) { 29 | throw new Error('没有找到key。') 30 | } 31 | if (typeof key === 'object') { 32 | throw new Error('key不能是一个对象。') 33 | } 34 | let cookies = this.all() 35 | let value = cookies[this.prefix + key] 36 | try { 37 | value = JSON.parse(value) 38 | } catch (e) { 39 | value = null 40 | } 41 | return value 42 | } 43 | 44 | /** 45 | * 设置cookies 46 | * @param key 键 47 | * @param value 值 48 | * @param options 选项 49 | * @returns {Cookie} 50 | */ 51 | set (key, value, options) { 52 | options = isObject(options) ? options : {expires: options} 53 | // 如果expires为空的话那么就设置为session. 54 | let expires = options.expires !== undefined ? options.expires : (this.defaults.expires || '') 55 | let expiresType = typeof (expires) 56 | if (expiresType === 'string' && expires !== '') { 57 | expires = new Date(expires) 58 | } else if (expiresType === 'number') { 59 | expires = new Date(+new Date() + 1000 * this.expiresMultiplier * expires) 60 | } 61 | if (expires !== '' && 'toGMTString' in expires) { 62 | expires = ';expires=' + expires.toGMTString() 63 | } 64 | // 设置path 65 | let path = options.path || this.defaults.path 66 | path = path ? ';path=' + path : '' 67 | // 设置domain 68 | let domain = options.domain || this.defaults.domain 69 | domain = domain ? ';domain=' + domain : '' 70 | // 设置secure 71 | let secure = options.secure || this.defaults.secure ? ';secure' : '' 72 | if (options.secure === false) secure = '' 73 | // 设置cookie 74 | document.cookie = this.prefix + key + '=' + JSON.stringify(value) + expires + path + domain + secure 75 | return this 76 | } 77 | 78 | /** 79 | * 删除cookie 80 | * @param {string||array} keys 删除cookie的key 81 | * @returns {Cookie} 82 | */ 83 | remove (keys) { 84 | keys = isArray(keys) ? keys : [keys] 85 | for (let i = 0, l = keys.length; i < l; i++) { 86 | this.set(keys[i], '', -1) 87 | } 88 | return this 89 | } 90 | 91 | /** 92 | * 获取所有的cookie 93 | * @returns {object} cookie对象 94 | */ 95 | all () { 96 | let cookie = document.cookie 97 | if (cookie === '') return {} 98 | let cookieArr = cookie.split('; ') 99 | let result = {} 100 | for (let i = 0, l = cookieArr.length; i < l; i++) { 101 | let item = cookieArr[i].split('=') 102 | // arr.shift()把第一个数组删除并得到删除的值 103 | let key = item.shift() 104 | let value = item.join('') 105 | result[key] = value 106 | } 107 | return result 108 | } 109 | } 110 | 111 | export default new Cookie() 112 | -------------------------------------------------------------------------------- /src/layouts/default/components/menu.vue: -------------------------------------------------------------------------------- 1 | 6 | 57 | 103 | 142 | --------------------------------------------------------------------------------