├── .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 |
2 |
5 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
13 |
40 |
--------------------------------------------------------------------------------
/src/views/components/Content.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
27 |
32 |
--------------------------------------------------------------------------------
/src/views/dashboard/Dashboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ msg }}
4 | 基于 Vue.js 的开源集成方案
5 |
8 |
11 |
12 |
13 |
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 |
2 |
3 |
4 | This is a {{ title }}
5 |
6 |
Params 方式向 HelloWorld 组件传参
7 |
Query 方式向 HelloWorld 组件传参
8 |
9 |
10 |
11 |
39 |
60 |
--------------------------------------------------------------------------------
/src/layouts/partials/Error.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Status Code:
5 |
6 | {{ resError.statusCode }}
7 |
8 |
9 |
10 |
{{ resError.error }}
11 |
12 |
13 |
14 |
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 |
2 |
3 |
4 |
What have you done?
5 | Now Go Back Using Below LInk
6 |
7 |
8 |
9 | ! ERROR DECETED !
10 |
11 |
12 |
13 |
14 |
15 |
16 |
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 |
2 |
9 |
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 |
2 |
3 | {{ msg }}
4 | 接收 Test 组件 Params 方式的传参: {{ $route.params.payload }}
5 | 接收 Test 组件 Query 方式的传参: {{ $route.query.payload }}
6 |
7 |
8 | For a guide and recipes on how to configure / customize this project,
9 | check out the
10 | vue-cli documentation.
11 |
12 | Installed CLI Plugins
13 |
17 | Essential Links
18 |
25 | Ecosystem
26 |
33 |
34 |
35 |
46 |
47 |
66 |
--------------------------------------------------------------------------------
/src/layouts/partials/Sidebar.vue:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
53 |
90 |
--------------------------------------------------------------------------------
/src/layouts/partials/Breadcrumb.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ dashboard.name }}
5 |
6 |
7 |
8 | /
9 | {{ elem.name }}
10 |
11 |
12 |
13 |
14 |
68 |
105 |
--------------------------------------------------------------------------------
/src/layouts/partials/Tabs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {{ item.label }}
13 |
14 |
15 |
16 |
17 |
18 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
102 |
130 |
--------------------------------------------------------------------------------
/src/layouts/partials/Config.vue:
--------------------------------------------------------------------------------
1 |
2 |
36 |
37 |
101 |
114 |
--------------------------------------------------------------------------------
/src/layouts/pages/EditPassword.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
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 |
54 |
55 |
56 | This is a {{ title }}
57 |
58 |
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 |
2 |
41 |
42 |
114 |
171 |
--------------------------------------------------------------------------------
/src/layouts/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
30 |
31 |
148 |
222 |
--------------------------------------------------------------------------------
/src/components/IForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
37 |
38 |
39 |
40 |
164 |
172 |
--------------------------------------------------------------------------------
/src/views/manage/users/Users.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Selected {{ toolbar.number }} items
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
292 |
301 |
--------------------------------------------------------------------------------
/src/views/manage/users/UserEdit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
351 |
353 |
--------------------------------------------------------------------------------