├── .env.production ├── .env.development ├── babel.config.js ├── src ├── layouts │ ├── index.js │ ├── partials │ │ ├── Footer.vue │ │ ├── Error.vue │ │ ├── Sidebar.vue │ │ ├── Breadcrumb.vue │ │ ├── Tabs.vue │ │ ├── Config.vue │ │ └── Header.vue │ ├── pages │ │ ├── NotFound.vue │ │ └── EditPassword.vue │ ├── Layout.vue │ └── Login.vue ├── views │ ├── manage │ │ └── users │ │ │ ├── index.js │ │ │ ├── Users.vue │ │ │ └── UserEdit.vue │ ├── dashboard │ │ ├── index.js │ │ └── Dashboard.vue │ ├── hello │ │ └── helloWorld │ │ │ ├── index.js │ │ │ └── HelloWorld.vue │ ├── components │ │ ├── index.js │ │ ├── Content.vue │ │ └── Search.vue │ └── Test.vue ├── assets │ └── logo.png ├── services │ ├── test.js │ ├── app.js │ └── manage │ │ └── users.js ├── mock │ ├── index.js │ ├── test.js │ ├── app.js │ └── users.js ├── components │ ├── index.js │ ├── ITable.vue │ └── IForm.vue ├── store │ ├── index.js │ └── modules │ │ ├── app.js │ │ ├── tabs.js │ │ └── menus.js ├── config.js ├── App.vue ├── main.js ├── router │ ├── index.js │ └── routes.js └── utils │ ├── axios.js │ └── index.js ├── .env.test ├── public ├── favicon.ico └── index.html ├── .env.release ├── .editorconfig ├── .gitignore ├── vue.config.js ├── LICENSE ├── package.json └── README.md /.env.production: -------------------------------------------------------------------------------- 1 | # 指定构建环境(生产) 2 | VUE_APP_ENV = 'production' 3 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # 指定构建环境(开发) 2 | VUE_APP_ENV = 'development' 3 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/app"] 3 | }; 4 | -------------------------------------------------------------------------------- /src/layouts/index.js: -------------------------------------------------------------------------------- 1 | import Layout from "./Layout"; 2 | export default Layout; 3 | -------------------------------------------------------------------------------- /src/views/manage/users/index.js: -------------------------------------------------------------------------------- 1 | import Users from "./Users"; 2 | export default Users; 3 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | # 指定构建模式 2 | NODE_ENV = 'production' 3 | 4 | # 指定构建环境(测试) 5 | VUE_APP_ENV = 'test' 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldy505755/vue-admin-blocks/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldy505755/vue-admin-blocks/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /src/views/dashboard/index.js: -------------------------------------------------------------------------------- 1 | import Dashboard from "./Dashboard"; 2 | export default Dashboard; 3 | -------------------------------------------------------------------------------- /.env.release: -------------------------------------------------------------------------------- 1 | # 指定构建模式 2 | NODE_ENV = 'production' 3 | 4 | # 指定构建环境(预生产) 5 | VUE_APP_ENV = 'release' 6 | -------------------------------------------------------------------------------- /src/views/hello/helloWorld/index.js: -------------------------------------------------------------------------------- 1 | import HelloWorld from "./HelloWorld"; 2 | export default HelloWorld; 3 | -------------------------------------------------------------------------------- /src/services/test.js: -------------------------------------------------------------------------------- 1 | import ax from "@/utils/axios"; 2 | 3 | export const _test = () => ax.get("/test"); // 测试接口 4 | -------------------------------------------------------------------------------- /.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/mock/index.js: -------------------------------------------------------------------------------- 1 | import app from "./app"; 2 | import users from "./users"; 3 | import test from "./test"; 4 | 5 | export default () => { 6 | // 登录,菜单,改密 7 | app(); 8 | // 用户管理 9 | users(); 10 | // 测试 11 | test(); 12 | }; 13 | -------------------------------------------------------------------------------- /src/mock/test.js: -------------------------------------------------------------------------------- 1 | import Mock from "mockjs"; 2 | 3 | export default () => { 4 | // 测试数据 5 | Mock.mock(/\/test/, () => ({ 6 | data: "test page", 7 | error: { 8 | code: 0, 9 | msg: "Get test success" 10 | } 11 | })); 12 | }; 13 | -------------------------------------------------------------------------------- /src/services/app.js: -------------------------------------------------------------------------------- 1 | import ax from "@/utils/axios"; 2 | 3 | export const _login = params => ax.post("/login", params); // 用户登录 4 | export const _getMenuList = () => ax.get("/menu"); // 获取菜单 5 | export const _editPwd = params => ax.post("/edit-password", params); // 修改密码 6 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | // 自定义组件 2 | import IForm from "./IForm"; 3 | import ITable from "./ITable"; 4 | 5 | export default Vue => { 6 | const components = { 7 | IForm, 8 | ITable 9 | }; 10 | for (const key of Object.keys(components)) { 11 | Vue.component(key, components[key]); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/views/components/index.js: -------------------------------------------------------------------------------- 1 | // 自定义组件 2 | import Content from "./Content"; 3 | import Search from "./Search"; 4 | 5 | export default Vue => { 6 | const components = { 7 | Content, 8 | Search 9 | }; 10 | for (const key of Object.keys(components)) { 11 | Vue.component(key, components[key]); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | 4 | // Vuex 模块引入 5 | import app from "@/store/modules/app"; 6 | import menus from "@/store/modules/menus"; 7 | import tabs from "@/store/modules/tabs"; 8 | 9 | // 调用 `Vuex` 10 | Vue.use(Vuex); 11 | 12 | export default new Vuex.Store({ 13 | modules: { 14 | app, 15 | menus, 16 | tabs 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /src/services/manage/users.js: -------------------------------------------------------------------------------- 1 | import ax from "@/utils/axios"; 2 | 3 | export const _getUserList = params => 4 | ax.get("/user-list", { 5 | params 6 | }); // 列表 7 | export const _delUser = params => ax.post("/user-delete", params); // 删除 8 | export const _batchDelUser = params => ax.post("/user-batch-delete", params); // 批量删除 9 | export const _editUser = params => 10 | ax.post(params.id ? "/user-edit" : "/user-create", params); // 编辑/创建 11 | -------------------------------------------------------------------------------- /src/layouts/partials/Footer.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | 22 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vue-admin-blocks 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | const version = "3.2.4"; // 系统版本 2 | const timeout = 10000; // 请求超时 3 | const baseAPI = { 4 | development: "http://dev.xx.com/api", 5 | test: "https://test.xx.com/api", 6 | release: "https://release.xx.com/api", 7 | production: "https://prod.xx.com/api" 8 | }; 9 | const env = localStorage.getItem("env") || process.env.VUE_APP_ENV; 10 | const baseURL = localStorage.getItem("newBaseAPI") || baseAPI[env]; 11 | 12 | if (env !== "production") { 13 | console.log("VUE_APP_ENV", env); 14 | console.log("API_URL", baseURL); 15 | } 16 | 17 | // 系统参数配置 18 | export default { 19 | version, // 系统版本 20 | timeout, // 请求超时 21 | baseAPI, // 所有环境接口对象 22 | env, // 当前环境 23 | baseURL // 当前环境接口地址 24 | }; 25 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | // vue.config.js 2 | const path = require("path"); 3 | const dir = `dist/${process.env.VUE_APP_ENV}`; 4 | 5 | module.exports = { 6 | // 部署生产环境和开发环境下的 URL 7 | // baseUrl: process.env.NODE_ENV === "production" ? "/your_url" : "/", 8 | // 生成构建文件的目录 9 | outputDir: path.resolve(__dirname, dir), 10 | // 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录 11 | assetsDir: "static", 12 | // 指定生成的 index.html 的输出路径 (相对于 outputDir) 13 | indexPath: path.resolve(__dirname, `${dir}/index.html`), 14 | // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建 15 | productionSourceMap: process.env.NODE_ENV === "production" ? false : true, 16 | // 指定服务端口 17 | devServer: { 18 | port: 8090 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 13 | 40 | -------------------------------------------------------------------------------- /src/views/components/Content.vue: -------------------------------------------------------------------------------- 1 | 15 | 21 | 36 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "@/App.vue"; 3 | import router from "@/router"; 4 | import store from "@/store"; 5 | 6 | // iView 完整引入 7 | import iView from "iview"; 8 | import "iview/dist/styles/iview.css"; 9 | // 自定义组件引入 10 | import iComp from "@/components"; 11 | // 工具函数引入 12 | import Utils from "@/utils"; 13 | // 自定义组件引入 14 | import vComp from "@/views/components"; 15 | 16 | // Mock 数据引入 17 | import Mock from "@/mock"; 18 | // if (process.env.NODE_ENV === 'development') { 19 | Mock(); 20 | // } 21 | 22 | // 调用 `iView` 23 | Vue.use(iView); 24 | // 调用 `iComp` 25 | Vue.use(iComp); 26 | // 调用 `vComp` 27 | Vue.use(vComp); 28 | 29 | Object.defineProperty(Vue.prototype, "$Utils", { 30 | value: Utils 31 | }); 32 | 33 | Vue.config.productionTip = false; 34 | 35 | new Vue({ 36 | router, 37 | store, 38 | render: h => h(App) 39 | }).$mount("#app"); 40 | -------------------------------------------------------------------------------- /src/views/components/Search.vue: -------------------------------------------------------------------------------- 1 | 9 | 27 | 32 | -------------------------------------------------------------------------------- /src/views/dashboard/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 14 | 22 | 23 | 42 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueRouter from "vue-router"; 3 | // import iView from 'iview' 4 | import routes from "@/router/routes"; 5 | import store from "@/store"; 6 | import utils from "@/utils"; 7 | 8 | // 调用 `VueRouter` 9 | Vue.use(VueRouter); 10 | 11 | // 创建 router 实例,然后传 `routes` 配置 12 | const router = new VueRouter({ 13 | routes // (缩写) 相当于 routes: routes 14 | }); 15 | 16 | // 刷新页面更新动态路由 17 | const menu = store.state.menus.menu; 18 | if (menu.length) { 19 | // 获取动态路由 20 | const routes = utils.getRoutes(menu); 21 | // 添加动态路由 22 | router.options.routes = routes; 23 | router.addRoutes(routes); 24 | } 25 | 26 | router.beforeEach((to, from, next) => { 27 | // iView.LoadingBar.start() 28 | if (to.path === "/login") { 29 | store.commit("MENU_RESET"); // 重置菜单 30 | store.commit("TABS_RESET"); // 重置标签页 31 | } 32 | const user = store.state.app.userInfo; 33 | if (!user && to.path !== "/login") { 34 | next("/login"); 35 | } else { 36 | next(); 37 | } 38 | }); 39 | 40 | // router.afterEach((to, from) => { 41 | // iView.LoadingBar.finish() 42 | // }) 43 | 44 | export default router; 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 ldy505755 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/router/routes.js: -------------------------------------------------------------------------------- 1 | // `routes` 配置 2 | export default [ 3 | { 4 | path: "/login", 5 | name: "Login", 6 | component: () => import("@/layouts/Login") 7 | // }, { 8 | // path: '/', 9 | // name: 'Layouts', 10 | // redirect: '/', 11 | // component: () => import('@/layouts'), 12 | // children: [{ 13 | // path: '/', 14 | // name: 'Dashboard', 15 | // component: () => import('@/views/dashboard'), 16 | // meta: { 17 | // keepAlive: true 18 | // } 19 | // }, { 20 | // path: '/test', 21 | // name: 'Test', 22 | // component: () => import('@/views/Test'), 23 | // meta: { 24 | // keepAlive: true 25 | // } 26 | // }, { 27 | // path: '/hello/hello-world', 28 | // name: 'HelloWorld', 29 | // component: () => import('@/views/hello/helloWorld'), 30 | // meta: { 31 | // keepAlive: true 32 | // } 33 | // }, { 34 | // path: '/manage/users', 35 | // name: 'Users', 36 | // component: () => import('@/views/manage/users'), 37 | // meta: { 38 | // keepAlive: true 39 | // } 40 | // }] 41 | }, 42 | { 43 | path: "*", 44 | name: "NotFound", 45 | component: () => import("@/layouts/pages/NotFound") 46 | } 47 | ]; 48 | -------------------------------------------------------------------------------- /src/views/Test.vue: -------------------------------------------------------------------------------- 1 | 11 | 39 | 60 | -------------------------------------------------------------------------------- /src/layouts/partials/Error.vue: -------------------------------------------------------------------------------- 1 | 15 | 26 | 61 | -------------------------------------------------------------------------------- /src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import qs from "qs"; 2 | import config from "@/config"; 3 | 4 | const state = { 5 | title: document.title, // 页面 title 元素内容 6 | loading: false, // 加载状态 7 | userInfo: JSON.parse(sessionStorage.getItem("userInfo")) || "", // 用户信息 8 | resError: "" // 错误数据 9 | }; 10 | 11 | const getters = { 12 | // getTitle: state => state.title, 13 | // getLoading: state => state.loading, 14 | getUserInfo: state => state.userInfo, 15 | getResError: state => state.resError 16 | }; 17 | 18 | const mutations = { 19 | // 更新页面 title 元素内容 20 | TITLE: (state, data) => { 21 | document.title = 22 | config.env === "production" 23 | ? `${state.title} | ${data}` 24 | : `${config.env} ) ${state.title} | ${data}`; 25 | }, 26 | // 更新加载状态 27 | LOADING: (state, data) => { 28 | state.loading = data; 29 | }, 30 | // 更新用户信息 31 | USER_INFO: (state, data) => { 32 | state.userInfo = data; 33 | sessionStorage.setItem("userInfo", JSON.stringify(data)); 34 | }, 35 | // 更新错误数据 36 | RES_ERROR: (state, data) => { 37 | state.resError = data 38 | ? { 39 | status: data.status, 40 | statusText: data.statusText, 41 | statusCode: `${data.status} ${data.statusText}`, 42 | error: { 43 | "Response Data": data.data, 44 | "Request URL": data.config.url, 45 | "Request Method": data.config.method.toUpperCase(), 46 | "Form Data": qs.parse(data.config.data) 47 | } 48 | } 49 | : ""; 50 | } 51 | }; 52 | 53 | export default { 54 | state, 55 | getters, 56 | mutations 57 | }; 58 | -------------------------------------------------------------------------------- /src/layouts/pages/NotFound.vue: -------------------------------------------------------------------------------- 1 | 17 | 50 | 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-admin-blocks", 3 | "version": "3.2.4", 4 | "author": "liudanyun ", 5 | "license": "MIT", 6 | "private": true, 7 | "scripts": { 8 | "start": "vue-cli-service serve", 9 | "test": "vue-cli-service build --mode test", 10 | "release": "vue-cli-service build --mode release", 11 | "build": "vue-cli-service build", 12 | "lint": "vue-cli-service lint" 13 | }, 14 | "dependencies": { 15 | "axios": "^0.18.0", 16 | "core-js": "^2.6.5", 17 | "countup.js": "^1.9.3", 18 | "iview": "^3.3.2", 19 | "vue": "^2.6.6", 20 | "vue-countup-v2": "^1.0.3", 21 | "vue-router": "^3.0.2", 22 | "vuex": "^3.1.0" 23 | }, 24 | "devDependencies": { 25 | "@vue/cli-plugin-babel": "^3.5.0", 26 | "@vue/cli-plugin-eslint": "^3.5.0", 27 | "@vue/cli-service": "^3.5.0", 28 | "@vue/eslint-config-prettier": "^4.0.1", 29 | "babel-eslint": "^10.0.1", 30 | "eslint": "^5.8.0", 31 | "eslint-plugin-vue": "5.0.0", 32 | "mockjs": "^1.0.1-beta3", 33 | "postcss-cssnext": "^3.1.0", 34 | "postcss-import": "^12.0.1", 35 | "vue-template-compiler": "^2.5.21" 36 | }, 37 | "eslintConfig": { 38 | "root": true, 39 | "env": { 40 | "node": true 41 | }, 42 | "extends": ["plugin:vue/essential", "@vue/prettier"], 43 | "rules": { 44 | "no-console": "off", 45 | "no-unused-vars": "warn", 46 | "prettier/prettier": "off", 47 | "vue/no-parsing-error": "off" 48 | }, 49 | "parserOptions": { 50 | "parser": "babel-eslint" 51 | } 52 | }, 53 | "postcss": { 54 | "plugins": { 55 | "postcss-cssnext": {}, 56 | "postcss-import": {} 57 | } 58 | }, 59 | "browserslist": ["> 1%", "last 2 versions", "not ie <= 8"] 60 | } 61 | -------------------------------------------------------------------------------- /src/store/modules/tabs.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | tabs: JSON.parse(sessionStorage.getItem("tabs")) || [] // 标签页 3 | }; 4 | 5 | const getters = { 6 | getTabs: state => state.tabs 7 | }; 8 | 9 | const mutations = { 10 | // 创建标签页 11 | NEW_TABS: (state, data) => { 12 | const menu = JSON.parse(sessionStorage.getItem("menu")); // 获取菜单(主菜单和子菜单) 13 | // 判断展开标签页或首页 14 | if ( 15 | !menu || 16 | state.tabs.some(name => name["path"] === data) || 17 | data === "/" 18 | ) { 19 | return false; 20 | } 21 | const path = `/${data.split("/")[1]}`; // 主菜单路径 22 | const childMenu = menu.filter(name => name["path"] === path)[0]["children"]; // 获取子菜单 23 | let tab; 24 | if (childMenu) { 25 | tab = childMenu.filter(name => name["path"] === data)[0]; // 当前标签页 26 | } else { 27 | tab = menu.filter(name => name["path"] === data)[0]; // 当前标签页 28 | } 29 | state.tabs.push({ 30 | name: tab["name"], 31 | path: tab["path"] 32 | }); 33 | // 添加当前标签页 34 | sessionStorage.setItem("tabs", JSON.stringify(state.tabs)); 35 | }, 36 | // 关闭标签页 37 | CLOSE_TABS: (state, data) => { 38 | switch (data[0]) { 39 | case "closeAllTabs": 40 | // 关闭所有标签页 41 | state.tabs = []; 42 | break; 43 | case "closeOtherTabs": 44 | // 关闭其它标签页 45 | state.tabs = state.tabs.filter(name => name["path"] === data[1]); 46 | break; 47 | default: 48 | // 关闭当前标签页 49 | state.tabs = state.tabs.filter(name => name["path"] !== data); 50 | } 51 | // 更新当前标签页 52 | sessionStorage.setItem("tabs", JSON.stringify(state.tabs)); 53 | }, 54 | // 重置标签页 55 | TABS_RESET: state => { 56 | state.tabs = []; 57 | } 58 | }; 59 | 60 | export default { 61 | state, 62 | getters, 63 | mutations 64 | }; 65 | -------------------------------------------------------------------------------- /src/components/ITable.vue: -------------------------------------------------------------------------------- 1 | 10 | 63 | 68 | -------------------------------------------------------------------------------- /src/store/modules/menus.js: -------------------------------------------------------------------------------- 1 | import router from "@/router"; 2 | import utils from "@/utils"; 3 | import {_getMenuList} from "@/services/app"; 4 | 5 | const state = { 6 | menu: JSON.parse(sessionStorage.getItem("menu")) || [], // 主菜单 7 | menuActive: sessionStorage.getItem("menuActive") || "/", // 激活主菜单 8 | menuOpened: sessionStorage.getItem("menuOpened") || "", // 展开子菜单 9 | routes: sessionStorage.getItem("menu") ? true : false // 动态路由添加状态 10 | }; 11 | 12 | const getters = { 13 | getMenu: state => state.menu, 14 | getMenuActive: state => state.menuActive, 15 | getMenuOpened: state => state.menuOpened 16 | }; 17 | 18 | const mutations = { 19 | // 获取菜单 20 | MENU: (state, data) => { 21 | // 判断动态路由是否已添加 22 | if (!state.routes) { 23 | // 获取动态路由 24 | const routes = utils.getRoutes(data); 25 | // 添加动态路由 26 | router.options.routes = routes; 27 | router.addRoutes(routes); 28 | // 动态路由添加状态 29 | state.routes = true; 30 | } 31 | 32 | state.menu = data; // 获取菜单 33 | sessionStorage.setItem("menu", JSON.stringify(data)); 34 | }, 35 | // 选择菜单 36 | MENU_SELECT: (state, data) => { 37 | router.push(data); // 路由跳转 38 | state.menuActive = data.split("?")[0]; // 激活菜单 39 | state.menuOpened = `/${data.split("/")[1]}`; // 展开菜单 40 | sessionStorage.setItem("menuActive", data); 41 | sessionStorage.setItem("menuOpened", state.menuOpened); 42 | }, 43 | // 重置菜单 44 | MENU_RESET: state => { 45 | state.menu = []; 46 | state.menuActive = "/"; 47 | state.menuOpened = ""; 48 | sessionStorage.clear(); 49 | } 50 | }; 51 | 52 | const actions = { 53 | // 获取菜单 54 | handleMenu: ({commit}) => { 55 | _getMenuList().then(res => { 56 | commit("MENU", res.data); 57 | commit("MENU_SELECT", "/"); 58 | }); 59 | } 60 | // 激活菜单 61 | // handleMenuSelect: ({ 62 | // commit 63 | // }, name) => { 64 | // commit('MENU_SELECT', name) 65 | // } 66 | }; 67 | 68 | export default { 69 | state, 70 | getters, 71 | mutations, 72 | actions 73 | }; 74 | -------------------------------------------------------------------------------- /src/utils/axios.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import qs from "qs"; 3 | import {Message} from "iview"; 4 | import config from "@/config"; 5 | import store from "@/store"; 6 | 7 | const {env, baseURL, timeout} = config; 8 | // 创建 axios 实例 9 | const ax = axios.create({ 10 | // 是否跨站点访问控制请求使用凭证(Cookie) 11 | withCredentials: true, 12 | baseURL: localStorage.getItem("newBaseAPI") || baseURL, // 配置接口地址 13 | // 修改请求的数据再发送到服务器 14 | transformRequest: [ 15 | data => qs.stringify(data) // 序列化请求的数据 16 | ], 17 | // 修改请求的数据再发送到服务器 18 | // transformRequest: [ 19 | // data => JSON.stringify(data) // 序列化请求的数据 20 | // ], 21 | // 修改请求头信息 22 | // headers: { 23 | // 'Content-Type': 'application/json' 24 | // }, 25 | timeout: timeout // 配置请求超时 26 | }); 27 | 28 | // 添加 axios 实例响应拦截器 29 | ax.interceptors.response.use( 30 | response => { 31 | const {data} = response; 32 | const {code, msg} = data["error"]; 33 | // const AUTH_TOKEN = data['data']['auth_token'] // 获取用户 AUTH_TOKEN 34 | // if (AUTH_TOKEN) { 35 | // setAuthToken(AUTH_TOKEN) // 配置默认参数 36 | // } 37 | // 用户 TOKEN 失效 38 | if (code === 3000) { 39 | store.commit("MENU_RESET"); // 重置菜单 40 | } 41 | // 判断开发环境 42 | if (env === "development" || env === "test") { 43 | if (code === 0) { 44 | console.log(data); // 控制台输出响应数据 45 | return data; // 响应正确的数据 46 | } 47 | store.commit("RES_ERROR", response); // 响应错误数据 48 | } else { 49 | if (code === 0) { 50 | return data; // 响应正确的数据 51 | } 52 | Message.error(msg); // 提示错误信息 53 | } 54 | }, 55 | error => { 56 | const {response, message, config} = error; 57 | if (response) { 58 | store.commit("RES_ERROR", response); // 响应错误数据 59 | } else { 60 | Message.error({ 61 | content: message 62 | }); 63 | } 64 | console.log(config); 65 | } 66 | ); 67 | 68 | // 配置默认参数 69 | const setAuthToken = AUTH_TOKEN => { 70 | // 配置用户 AUTH_TOKEN 71 | ax.defaults.headers.common["Authorization"] = AUTH_TOKEN; 72 | }; 73 | 74 | // 刷新重新配置默认参数 75 | const userInfo = sessionStorage.getItem("userInfo"); 76 | if (userInfo) { 77 | setAuthToken(JSON.parse(userInfo)["auth_token"]); // 配置默认参数 78 | } 79 | 80 | export default ax; 81 | -------------------------------------------------------------------------------- /src/mock/app.js: -------------------------------------------------------------------------------- 1 | import Mock from "mockjs"; 2 | import qs from "qs"; 3 | 4 | export default () => { 5 | // 用户登录 6 | Mock.mock(/\/login/, config => { 7 | const {user, pwd} = qs.parse(config.body); 8 | if (user === "admin" && pwd === "wasd@007") { 9 | return { 10 | data: { 11 | user_id: Mock.mock("@guid"), 12 | auth_token: Mock.mock("@guid"), 13 | real_name: "Admin" 14 | }, 15 | error: { 16 | code: 0, 17 | msg: "Login success" 18 | } 19 | }; 20 | } 21 | return { 22 | error: { 23 | code: 4000, 24 | msg: "Your account username or password is incorrect" 25 | } 26 | }; 27 | }); 28 | 29 | // 菜单获取 30 | Mock.mock(/\/menu/, { 31 | data: [ 32 | { 33 | path: "/", 34 | name: "Dashboard", 35 | icon: "md-speedometer", 36 | compName: "Dashboard", 37 | compPath: "/dashboard" 38 | }, 39 | { 40 | path: "/test", 41 | name: "Test", 42 | icon: "md-document", 43 | compName: "Test", 44 | compPath: "/Test" 45 | }, 46 | { 47 | path: "/hello", 48 | name: "Hello", 49 | icon: "md-chatbubbles", 50 | children: [ 51 | { 52 | path: "/hello/hello-world", 53 | name: "HelloWorld", 54 | icon: "md-text", 55 | compName: "HelloWorld", 56 | compPath: "/hello/helloWorld" 57 | } 58 | ] 59 | }, 60 | { 61 | path: "/manage", 62 | name: "Manage", 63 | icon: "md-folder-open", 64 | children: [ 65 | { 66 | path: "/manage/users", 67 | name: "Users", 68 | icon: "md-people", 69 | compName: "Users", 70 | compPath: "/manage/users" 71 | } 72 | ] 73 | } 74 | ], 75 | error: { 76 | code: 0, 77 | msg: "Get menu success" 78 | } 79 | }); 80 | 81 | // 密码修改 82 | Mock.mock(/\/edit-password/, config => { 83 | const {currentPassword} = qs.parse(config.body); 84 | if (currentPassword === "wasd@007") { 85 | return { 86 | error: { 87 | code: 0, 88 | msg: "Edit password success" 89 | } 90 | }; 91 | } 92 | return { 93 | error: { 94 | code: 4000, 95 | msg: "Your current password is incorrect" 96 | } 97 | }; 98 | }); 99 | }; 100 | -------------------------------------------------------------------------------- /src/views/hello/helloWorld/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 35 | 46 | 47 | 66 | -------------------------------------------------------------------------------- /src/layouts/partials/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 22 | 53 | 90 | -------------------------------------------------------------------------------- /src/layouts/partials/Breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 14 | 68 | 105 | -------------------------------------------------------------------------------- /src/layouts/partials/Tabs.vue: -------------------------------------------------------------------------------- 1 | 19 | 84 | 101 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | var SIGN_REGEXP = /([yMdhsm])(\1*)/g; 2 | var DEFAULT_PATTERN = "yyyy-MM-dd"; 3 | 4 | function padding(s, len) { 5 | len = len - (s + "").length; 6 | for (var i = 0; i < len; i++) { 7 | s = "0" + s; 8 | } 9 | return s; 10 | } 11 | 12 | export default { 13 | // 获取动态路由 14 | getRoutes: data => { 15 | // 菜单解析 16 | const menus = []; 17 | for (const menu of data) { 18 | if (menu.children) { 19 | for (const child of menu.children) { 20 | menus.push(child); 21 | } 22 | } else { 23 | menus.push(menu); 24 | } 25 | } 26 | // 路由解析 27 | const routes = [ 28 | { 29 | path: "/", 30 | name: "Layouts", 31 | redirect: "/", 32 | component: () => import("@/layouts"), 33 | children: [] 34 | } 35 | ]; 36 | for (const route of menus) { 37 | routes[0]["children"].push({ 38 | path: route.path, 39 | name: route.compName, 40 | component: () => import(`@/views${route.compPath}`), 41 | meta: { 42 | keepAlive: true, 43 | keepAliveUse: false 44 | } 45 | }); 46 | } 47 | return routes; 48 | }, 49 | // 日期格式解析 50 | formatDate: { 51 | format: function(date, pattern) { 52 | pattern = pattern || DEFAULT_PATTERN; 53 | return pattern.replace(SIGN_REGEXP, function($0) { 54 | switch ($0.charAt(0)) { 55 | case "y": 56 | return padding(date.getFullYear(), $0.length); 57 | case "M": 58 | return padding(date.getMonth() + 1, $0.length); 59 | case "d": 60 | return padding(date.getDate(), $0.length); 61 | case "w": 62 | return date.getDay() + 1; 63 | case "h": 64 | return padding(date.getHours(), $0.length); 65 | case "m": 66 | return padding(date.getMinutes(), $0.length); 67 | case "s": 68 | return padding(date.getSeconds(), $0.length); 69 | } 70 | }); 71 | }, 72 | parse: function(dateString, pattern) { 73 | var matchs1 = pattern.match(SIGN_REGEXP); 74 | var matchs2 = dateString.match(/(\d)+/g); 75 | if (matchs1.length === matchs2.length) { 76 | var _date = new Date(1970, 0, 1); 77 | for (var i = 0; i < matchs1.length; i++) { 78 | var _int = parseInt(matchs2[i]); 79 | var sign = matchs1[i]; 80 | switch (sign.charAt(0)) { 81 | case "y": 82 | _date.setFullYear(_int); 83 | break; 84 | case "M": 85 | _date.setMonth(_int - 1); 86 | break; 87 | case "d": 88 | _date.setDate(_int); 89 | break; 90 | case "h": 91 | _date.setHours(_int); 92 | break; 93 | case "m": 94 | _date.setMinutes(_int); 95 | break; 96 | case "s": 97 | _date.setSeconds(_int); 98 | break; 99 | } 100 | } 101 | return _date; 102 | } 103 | return null; 104 | } 105 | } 106 | }; 107 | -------------------------------------------------------------------------------- /src/layouts/Layout.vue: -------------------------------------------------------------------------------- 1 | 35 | 102 | 130 | -------------------------------------------------------------------------------- /src/layouts/partials/Config.vue: -------------------------------------------------------------------------------- 1 | 37 | 101 | 114 | -------------------------------------------------------------------------------- /src/layouts/pages/EditPassword.vue: -------------------------------------------------------------------------------- 1 | 9 | 129 | -------------------------------------------------------------------------------- /src/mock/users.js: -------------------------------------------------------------------------------- 1 | import Mock from "mockjs"; 2 | import qs from "qs"; 3 | 4 | export default () => { 5 | const citys = { 6 | "110100": "BeiJing", 7 | "310100": "ShangHai", 8 | "440100": "GuangZhou", 9 | "330100": "HangZhou", 10 | "330200": "NingBo" 11 | }; 12 | const hobbys = { 13 | eat: "Eat", 14 | sleep: "Sleep", 15 | run: "Run", 16 | movie: "Movie" 17 | }; 18 | const data = Mock.mock({ 19 | "users|11-87": [ 20 | { 21 | id: "@guid", 22 | name: "@name", 23 | "age|18-55": 1, 24 | "gender|0-1": 1, 25 | email: "@email", 26 | birth: "@date", 27 | "city|1": citys, 28 | "hobby|1-3": hobbys, 29 | desc: "@paragraph" 30 | } 31 | ] 32 | }); 33 | let users = data.users; 34 | 35 | // 参数函数 36 | const params = (cityStr, hobbyArr) => { 37 | const city = {}; 38 | if (cityStr) { 39 | city[cityStr] = citys[cityStr]; 40 | } 41 | const hobby = {}; 42 | if (hobbyArr) { 43 | const len = hobbyArr.length; 44 | for (let i = 0; i < len; i++) { 45 | const k = hobbyArr[i]; 46 | hobby[k] = hobbys[k]; 47 | } 48 | } 49 | return { 50 | city, 51 | hobby 52 | }; 53 | }; 54 | 55 | // 用户列表 56 | Mock.mock(/\/user-list/, config => { 57 | const {pagePara, name} = qs.parse(config.url.split("?")[1]); 58 | let {current, pageSize} = JSON.parse(pagePara); 59 | let _users = users.filter(u => { 60 | if (name && u.name.indexOf(name) === -1) { 61 | return false; 62 | } 63 | return true; 64 | }); 65 | const total = _users.length; 66 | const pageMax = Math.ceil(total / pageSize); 67 | current = current > pageMax ? pageMax : current; 68 | _users = _users.filter( 69 | (u, index) => 70 | index < pageSize * current && index >= pageSize * (current - 1) 71 | ); 72 | return { 73 | data: { 74 | total: total, 75 | users: _users 76 | }, 77 | error: { 78 | code: 0, 79 | msg: "Get users success" 80 | } 81 | }; 82 | }); 83 | 84 | // 删除用户 85 | Mock.mock(/\/user-delete/, config => { 86 | const {id} = qs.parse(config.body); 87 | users = users.filter(u => u.id !== id); 88 | return { 89 | error: { 90 | code: 0, 91 | msg: "Delete success" 92 | } 93 | }; 94 | }); 95 | 96 | // 批量删除用户 97 | Mock.mock(/\/user-batch-delete/, config => { 98 | let {ids} = qs.parse(config.body); 99 | ids = ids.split(","); 100 | users = users.filter(u => !ids.includes(u.id)); 101 | return { 102 | error: { 103 | code: 0, 104 | msg: "Delete success" 105 | } 106 | }; 107 | }); 108 | 109 | // 编辑用户 110 | Mock.mock(/\/user-edit/, config => { 111 | const {id, name, age, gender, email, birth, city, hobby, desc} = qs.parse( 112 | config.body 113 | ); 114 | const _params = params(city, hobby); 115 | users.some(u => { 116 | if (u.id === id) { 117 | u.name = name; 118 | u.age = parseInt(age); 119 | u.gender = parseInt(gender); 120 | u.email = email; 121 | u.birth = birth; 122 | u.city = _params.city; 123 | u.hobby = _params.hobby; 124 | u.desc = desc; 125 | } 126 | }); 127 | return { 128 | error: { 129 | code: 0, 130 | msg: "Update success" 131 | } 132 | }; 133 | }); 134 | 135 | // 新增用户 136 | Mock.mock(/\/user-create/, config => { 137 | const {name, age, gender, email, birth, city, hobby, desc} = qs.parse( 138 | config.body 139 | ); 140 | const _params = params(city, hobby); 141 | users.unshift({ 142 | id: Mock.mock("@guid"), 143 | name: name, 144 | age: parseInt(age), 145 | gender: parseInt(gender), 146 | email: email, 147 | birth: birth, 148 | city: _params.city, 149 | hobby: _params.hobby, 150 | desc: desc 151 | }); 152 | return { 153 | error: { 154 | code: 0, 155 | msg: "Create success" 156 | } 157 | }; 158 | }); 159 | }; 160 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Demo:** 2 | 3 | > - Username: admin 4 | > - Password: wasd@007 5 | 6 | ## 特性 7 | 8 | 1. 基于 Vue.js 的企业级中后台开源集成方案 9 | 2. 基于 Vue 官方命令行工具 Vue CLI 3 脚手架搭建 10 | 3. 使用 Vue 官方核心插件 Vue Router, Vuex 11 | 4. 使用 Vue 官方建议的 Axios 插件进行 HTTP 操作 12 | 5. 采用时下热门的 UI 组件库 iView 13 | 6. 通过 Mock.js 插件拦截 Ajax 请求并生成随机数据 14 | 7. 使用 cssnext 预处理 编写样式 15 | 16 | ## 开发构建 17 | 18 | ### 目录结构 19 | 20 | ```bash 21 | ├── /public # 静态文件 22 | ├── /src # 源码目录 23 | │ ├── /assets # 静态资源 24 | │ ├── /components # 公共组件 25 | │ ├── /layouts # 布局组件 26 | │ ├── /mock # 数据模拟 27 | │ ├── /router # 路由配置 28 | │ ├── /services # 数据接口 29 | │ ├── /store # vuex状态管理 30 | │ ├── /utils # 工具库 31 | │ ├── /views # 路由组件(页面维度) 32 | │ ├── App.vue # 组件入口 33 | │ ├── config.js # 应用配置 34 | │ └── main.js # 应用入口 35 | ├── .editorconfig # 定义代码格式 36 | ├── .env.development # 开发环境 37 | ├── .env.production # 生产环境 38 | ├── .env.release # 预生产环境 39 | ├── .env.test # 测试环境 40 | ├── .gitignore # git忽视 41 | ├── babel.config.js # ES6语法编译配置 42 | ├── LICENSE # 版权信息 43 | ├── package.json # 依赖包 44 | ├── README.md # 项目文档 45 | └── vue.config.js # 项目配置 46 | ``` 47 | 48 | ### 开发流程 49 | 50 | **Step 1,** 新建路由组件(测试)views/Test.vue 51 | 52 | ```html 53 | 59 | 87 | 99 | ``` 100 | 101 | **Step 2,** 添加临时菜单(测试)mock/app.js 102 | 103 | ```javascript 104 | import Mock from "mockjs"; 105 | 106 | export default () => { 107 | Mock.mock(/\/menu/, { 108 | data: [ 109 | { 110 | path: "/test", // 路由地址 111 | name: "Test", // 菜单名称 112 | icon: "md-document", // 菜单 Icon 图标 113 | compName: "Test", // 组件名称 114 | compPath: "/Test" // 组件地址( 默认指向 src/views 路由组件目录 115 | } 116 | ] 117 | }); 118 | }; 119 | ``` 120 | 121 | \* **注意** 122 | 123 | > 1. 添加临时菜单需要重新登录才能更新新菜单 124 | > 2. 因为路由是通过菜单动态生成,所以无需再为项目配置路由 125 | 126 | **Step 3,** 新建接口管理文件(测试) services/test.js 127 | 128 | ```javascript 129 | import ax from "@/utils/axios"; 130 | 131 | export const _test = () => ax.get("/test"); // 测试接口 132 | ``` 133 | 134 | \* **提示** 135 | 136 | > - 如果后端提供正式接口,那么无须再模拟假数据,直接跳过 Step 4 和 Step 5 137 | > - 如需使用 vuex 管理状态, 请阅读 src/layouts/partials/Sidebar.vue 组件和 src/store 目录源码 138 | 139 | **Step 4,** 新建数据模拟文件(测试) mock/test.js 140 | 141 | ```javascript 142 | import Mock from "mockjs"; 143 | 144 | export default () => { 145 | Mock.mock(/\/test/, () => ({ 146 | data: "test page" // 测试数据 147 | })); 148 | }; 149 | ``` 150 | 151 | **Step 5,** 使用数据模拟文件(测试) mock/index.js 152 | 153 | ```javascript 154 | import test from "./test"; 155 | 156 | export default () => { 157 | test(); // 测试 158 | }; 159 | ``` 160 | 161 | ### 快速开始 162 | 163 | **Step 1,** 安装依赖: 164 | 165 | ```bash 166 | # 安装依赖 167 | yarn 168 | # 或 169 | npm i 170 | ``` 171 | 172 | **Step 2,** 开发: 173 | 174 | ```bash 175 | yarn start 176 | # 或 177 | npm start 178 | ``` 179 | 180 | **Step 3,** 构建: 181 | 182 | ```bash 183 | # 构建最小测试 184 | yarn test 185 | # 或 186 | npm test 187 | 188 | # 构建最小预发布 189 | yarn release 190 | # 或 191 | npm run release 192 | 193 | # 构建最小生产 194 | yarn build 195 | # 或 196 | npm run build 197 | ``` 198 | -------------------------------------------------------------------------------- /src/layouts/partials/Header.vue: -------------------------------------------------------------------------------- 1 | 42 | 114 | 171 | -------------------------------------------------------------------------------- /src/layouts/Login.vue: -------------------------------------------------------------------------------- 1 | 31 | 148 | 222 | -------------------------------------------------------------------------------- /src/components/IForm.vue: -------------------------------------------------------------------------------- 1 | 40 | 164 | 172 | -------------------------------------------------------------------------------- /src/views/manage/users/Users.vue: -------------------------------------------------------------------------------- 1 | 22 | 292 | 301 | -------------------------------------------------------------------------------- /src/views/manage/users/UserEdit.vue: -------------------------------------------------------------------------------- 1 | 9 | 351 | 353 | --------------------------------------------------------------------------------