├── README.md ├── js-i18n ├── .env.development ├── .env.production ├── .env.staging ├── .gitignore ├── LICENSE ├── README.md ├── VERSION.md ├── index.html ├── mock │ ├── table.js │ └── user.js ├── mockProdServer.js ├── package.json ├── public │ └── favicon.ico ├── src │ ├── App.vue │ ├── api │ │ ├── table.js │ │ └── user.js │ ├── assets │ │ ├── images │ │ │ ├── 401.gif │ │ │ ├── 404.png │ │ │ └── 404_cloud.png │ │ ├── logo.png │ │ └── style │ │ │ ├── common.scss │ │ │ └── transition.scss │ ├── components │ │ ├── layer │ │ │ └── index.vue │ │ ├── menu │ │ │ └── index.vue │ │ └── table │ │ │ └── index.vue │ ├── config │ │ └── index.js │ ├── directive │ │ └── drag │ │ │ └── index.js │ ├── layout │ │ ├── Header │ │ │ ├── Breadcrumb.vue │ │ │ ├── functionList │ │ │ │ ├── fullscreen.vue │ │ │ │ ├── sizeChange.vue │ │ │ │ ├── theme.vue │ │ │ │ ├── theme │ │ │ │ │ ├── theme-color.vue │ │ │ │ │ └── theme-icon.vue │ │ │ │ └── word.vue │ │ │ ├── index.vue │ │ │ └── passwordLayer.vue │ │ ├── Logo │ │ │ └── index.vue │ │ ├── Menu │ │ │ ├── Link.vue │ │ │ ├── MenuItem.vue │ │ │ └── index.vue │ │ ├── Tabs │ │ │ ├── index.vue │ │ │ ├── item.vue │ │ │ └── tabsHook.js │ │ └── index.vue │ ├── locale │ │ ├── index.js │ │ └── modules │ │ │ ├── en.js │ │ │ ├── en │ │ │ ├── common.js │ │ │ ├── menu.js │ │ │ └── system.js │ │ │ ├── zh-cn.js │ │ │ └── zh-cn │ │ │ ├── common.js │ │ │ ├── menu.js │ │ │ └── system.js │ ├── main.js │ ├── router │ │ ├── createNode.js │ │ ├── index.js │ │ ├── modules │ │ │ ├── dashboard.js │ │ │ ├── pages.js │ │ │ └── system.js │ │ └── reload.vue │ ├── store │ │ ├── index.js │ │ ├── modules │ │ │ ├── app.js │ │ │ ├── keepAlive.js │ │ │ └── user.js │ │ └── plugins │ │ │ └── persistent.js │ ├── theme │ │ ├── index.js │ │ ├── index.scss │ │ └── modules │ │ │ └── dark.scss │ ├── utils │ │ └── system │ │ │ ├── nprogress.js │ │ │ ├── request.js │ │ │ ├── statistics.js │ │ │ └── title.js │ └── views │ │ ├── main │ │ ├── dashboard │ │ │ └── index.vue │ │ └── pages │ │ │ ├── categoryTable │ │ │ ├── category.vue │ │ │ ├── enum.js │ │ │ ├── index.vue │ │ │ ├── layer.vue │ │ │ └── my-table.vue │ │ │ ├── crudTable │ │ │ ├── enum.js │ │ │ ├── index.vue │ │ │ └── layer.vue │ │ │ └── treeTable │ │ │ ├── enum.js │ │ │ ├── index.vue │ │ │ ├── layer.vue │ │ │ ├── my-table.vue │ │ │ └── tree.vue │ │ └── system │ │ ├── 401.vue │ │ ├── 404.vue │ │ ├── login.vue │ │ └── redirect.vue └── vite.config.js ├── js ├── .env.development ├── .env.production ├── .env.staging ├── .gitignore ├── LICENSE ├── README.md ├── VERSION.md ├── index.html ├── mock │ ├── table.js │ └── user.js ├── mockProdServer.js ├── package.json ├── public │ └── favicon.ico ├── src │ ├── App.vue │ ├── api │ │ ├── table.js │ │ └── user.js │ ├── assets │ │ ├── images │ │ │ ├── 401.gif │ │ │ ├── 404.png │ │ │ └── 404_cloud.png │ │ ├── logo.png │ │ └── style │ │ │ ├── common.scss │ │ │ └── transition.scss │ ├── components │ │ ├── layer │ │ │ └── index.vue │ │ ├── menu │ │ │ └── index.vue │ │ └── table │ │ │ └── index.vue │ ├── config │ │ └── index.js │ ├── directive │ │ └── drag │ │ │ └── index.js │ ├── layout │ │ ├── Header │ │ │ ├── Breadcrumb.vue │ │ │ ├── functionList │ │ │ │ ├── fullscreen.vue │ │ │ │ ├── sizeChange.vue │ │ │ │ ├── theme.vue │ │ │ │ └── theme │ │ │ │ │ ├── theme-color.vue │ │ │ │ │ └── theme-icon.vue │ │ │ ├── index.vue │ │ │ └── passwordLayer.vue │ │ ├── Logo │ │ │ └── index.vue │ │ ├── Menu │ │ │ ├── Link.vue │ │ │ ├── MenuItem.vue │ │ │ └── index.vue │ │ ├── Tabs │ │ │ ├── index.vue │ │ │ ├── item.vue │ │ │ └── tabsHook.js │ │ └── index.vue │ ├── main.js │ ├── router │ │ ├── createNode.js │ │ ├── index.js │ │ ├── modules │ │ │ ├── dashboard.js │ │ │ ├── pages.js │ │ │ └── system.js │ │ └── reload.vue │ ├── store │ │ ├── index.js │ │ ├── modules │ │ │ ├── app.js │ │ │ ├── keepAlive.js │ │ │ └── user.js │ │ └── plugins │ │ │ └── persistent.js │ ├── theme │ │ ├── index.js │ │ ├── index.scss │ │ └── modules │ │ │ └── dark.scss │ ├── utils │ │ └── system │ │ │ ├── nprogress.js │ │ │ ├── request.js │ │ │ ├── statistics.js │ │ │ └── title.js │ └── views │ │ ├── main │ │ ├── dashboard │ │ │ └── index.vue │ │ └── pages │ │ │ ├── categoryTable │ │ │ ├── category.vue │ │ │ ├── enum.js │ │ │ ├── index.vue │ │ │ ├── layer.vue │ │ │ └── my-table.vue │ │ │ ├── crudTable │ │ │ ├── enum.js │ │ │ ├── index.vue │ │ │ └── layer.vue │ │ │ └── treeTable │ │ │ ├── enum.js │ │ │ ├── index.vue │ │ │ ├── layer.vue │ │ │ ├── my-table.vue │ │ │ └── tree.vue │ │ └── system │ │ ├── 401.vue │ │ ├── 404.vue │ │ ├── login.vue │ │ └── redirect.vue └── vite.config.js ├── ts-i18n ├── .env.development ├── .env.production ├── .env.staging ├── .gitignore ├── LICENSE ├── README.md ├── VERSION.md ├── index.html ├── mock │ ├── table.ts │ └── user.ts ├── mockProdServer.ts ├── package.json ├── public │ └── favicon.ico ├── src │ ├── App.vue │ ├── api │ │ ├── table.ts │ │ └── user.ts │ ├── assets │ │ ├── images │ │ │ ├── 401.gif │ │ │ ├── 404.png │ │ │ └── 404_cloud.png │ │ ├── logo.png │ │ └── style │ │ │ ├── common.scss │ │ │ └── transition.scss │ ├── components │ │ ├── layer │ │ │ └── index.vue │ │ ├── menu │ │ │ └── index.vue │ │ └── table │ │ │ ├── index.vue │ │ │ └── type.ts │ ├── config │ │ └── index.ts │ ├── directive │ │ └── drag │ │ │ └── index.ts │ ├── layout │ │ ├── Header │ │ │ ├── Breadcrumb.vue │ │ │ ├── functionList │ │ │ │ ├── fullscreen.vue │ │ │ │ ├── sizeChange.vue │ │ │ │ ├── theme.vue │ │ │ │ ├── theme │ │ │ │ │ ├── theme-color.vue │ │ │ │ │ └── theme-icon.vue │ │ │ │ └── word.vue │ │ │ ├── index.vue │ │ │ └── passwordLayer.vue │ │ ├── Logo │ │ │ └── index.vue │ │ ├── Menu │ │ │ ├── Link.vue │ │ │ ├── MenuItem.vue │ │ │ └── index.vue │ │ ├── Tabs │ │ │ ├── index.vue │ │ │ ├── item.vue │ │ │ └── tabsHook.ts │ │ └── index.vue │ ├── locale │ │ ├── index.ts │ │ └── modules │ │ │ ├── en.ts │ │ │ ├── en │ │ │ ├── common.ts │ │ │ ├── menu.ts │ │ │ └── system.ts │ │ │ ├── zh-cn.ts │ │ │ └── zh-cn │ │ │ ├── common.ts │ │ │ ├── menu.ts │ │ │ └── system.ts │ ├── main.ts │ ├── router │ │ ├── createNode.ts │ │ ├── index.ts │ │ ├── modules │ │ │ ├── dashboard.ts │ │ │ ├── pages.ts │ │ │ └── system.ts │ │ └── reload.vue │ ├── shims-vue.d.ts │ ├── store │ │ ├── index.ts │ │ ├── modules │ │ │ ├── app.ts │ │ │ ├── keepAlive.ts │ │ │ └── user.ts │ │ └── plugins │ │ │ └── persistent.ts │ ├── theme │ │ ├── index.scss │ │ ├── index.ts │ │ └── modules │ │ │ └── dark.scss │ ├── utils │ │ └── system │ │ │ ├── nprogress.ts │ │ │ ├── request.ts │ │ │ ├── statistics.ts │ │ │ └── title.ts │ └── views │ │ ├── main │ │ ├── dashboard │ │ │ └── index.vue │ │ └── pages │ │ │ ├── categoryTable │ │ │ ├── category.vue │ │ │ ├── enum.ts │ │ │ ├── index.vue │ │ │ ├── layer.vue │ │ │ └── my-table.vue │ │ │ ├── crudTable │ │ │ ├── enum.ts │ │ │ ├── index.vue │ │ │ └── layer.vue │ │ │ └── treeTable │ │ │ ├── enum.ts │ │ │ ├── index.vue │ │ │ ├── layer.vue │ │ │ ├── my-table.vue │ │ │ └── tree.vue │ │ └── system │ │ ├── 401.vue │ │ ├── 404.vue │ │ ├── login.vue │ │ └── redirect.vue ├── tsconfig.json └── vite.config.ts └── ts ├── .env.development ├── .env.production ├── .env.staging ├── .gitignore ├── LICENSE ├── README.md ├── VERSION.md ├── index.html ├── mock ├── table.ts └── user.ts ├── mockProdServer.ts ├── package.json ├── public └── favicon.ico ├── src ├── App.vue ├── api │ ├── table.ts │ └── user.ts ├── assets │ ├── images │ │ ├── 401.gif │ │ ├── 404.png │ │ └── 404_cloud.png │ ├── logo.png │ └── style │ │ ├── common.scss │ │ └── transition.scss ├── components │ ├── layer │ │ └── index.vue │ ├── menu │ │ └── index.vue │ └── table │ │ ├── index.vue │ │ └── type.ts ├── config │ └── index.ts ├── directive │ └── drag │ │ └── index.ts ├── layout │ ├── Header │ │ ├── Breadcrumb.vue │ │ ├── functionList │ │ │ ├── fullscreen.vue │ │ │ ├── sizeChange.vue │ │ │ ├── theme.vue │ │ │ └── theme │ │ │ │ ├── theme-color.vue │ │ │ │ └── theme-icon.vue │ │ ├── index.vue │ │ └── passwordLayer.vue │ ├── Logo │ │ └── index.vue │ ├── Menu │ │ ├── Link.vue │ │ ├── MenuItem.vue │ │ └── index.vue │ ├── Tabs │ │ ├── index.vue │ │ ├── item.vue │ │ └── tabsHook.ts │ └── index.vue ├── main.ts ├── router │ ├── createNode.ts │ ├── index.ts │ ├── modules │ │ ├── dashboard.ts │ │ ├── pages.ts │ │ └── system.ts │ └── reload.vue ├── shims-vue.d.ts ├── store │ ├── index.ts │ ├── modules │ │ ├── app.ts │ │ ├── keepAlive.ts │ │ └── user.ts │ └── plugins │ │ └── persistent.ts ├── theme │ ├── index.scss │ ├── index.ts │ └── modules │ │ └── dark.scss ├── utils │ └── system │ │ ├── nprogress.ts │ │ ├── request.ts │ │ ├── statistics.ts │ │ └── title.ts └── views │ ├── main │ ├── dashboard │ │ └── index.vue │ └── pages │ │ ├── categoryTable │ │ ├── category.vue │ │ ├── enum.ts │ │ ├── index.vue │ │ ├── layer.vue │ │ └── my-table.vue │ │ ├── crudTable │ │ ├── enum.ts │ │ ├── index.vue │ │ └── layer.vue │ │ └── treeTable │ │ ├── enum.ts │ │ ├── index.vue │ │ ├── layer.vue │ │ ├── my-table.vue │ │ └── tree.vue │ └── system │ ├── 401.vue │ ├── 404.vue │ ├── login.vue │ └── redirect.vue ├── tsconfig.json └── vite.config.ts /README.md: -------------------------------------------------------------------------------- 1 | # vue-admin-box-template 2 | vue-admin-box中后台开源框架基础模板,包括了四个基本模板(ts版本/js版本) 3 | 4 | - ts-i18n —— ts版本+国际化 5 | - ts —— ts版本,无国际化 6 | - js —— js版本,无国际化 7 | - Js-i18n—— js版本,国际化 -------------------------------------------------------------------------------- /js-i18n/.env.development: -------------------------------------------------------------------------------- 1 | ENV = 'development' 2 | 3 | VITE_BASE_URL = '/dev-api' -------------------------------------------------------------------------------- /js-i18n/.env.production: -------------------------------------------------------------------------------- 1 | ENV = 'production' 2 | 3 | VITE_BASE_URL = '/pro-api' -------------------------------------------------------------------------------- /js-i18n/.env.staging: -------------------------------------------------------------------------------- 1 | ENV = 'staging' 2 | 3 | VITE_BASE_URL = '/test-api' -------------------------------------------------------------------------------- /js-i18n/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | yarn.lock 7 | yarn-error.log 8 | .vscode 9 | -------------------------------------------------------------------------------- /js-i18n/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 罗茜 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. -------------------------------------------------------------------------------- /js-i18n/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /js-i18n/mock/user.js: -------------------------------------------------------------------------------- 1 | const users = [ 2 | { name: 'admin', password: '123456', token: 'admin', info: { 3 | name: '系统管理员' 4 | }}, 5 | { name: 'editor', password: '123456', token: 'editor', info: { 6 | name: '编辑人员' 7 | }}, 8 | { name: 'test', password: '123456', token: 'test', info: { 9 | name: '测试人员' 10 | }}, 11 | ] 12 | export default [ 13 | { 14 | url: `/mock/user/login`, 15 | method: 'post', 16 | response: ({ body }) => { 17 | const user = users.find(user => { 18 | return body.name === user.name && body.password === user.password 19 | }) 20 | if (user) { 21 | return { 22 | code: 200, 23 | data: { 24 | token: user.token, 25 | }, 26 | }; 27 | } else { 28 | return { 29 | code: 401, 30 | data: {}, 31 | msg: '用户名或密码错误' 32 | }; 33 | } 34 | 35 | } 36 | }, 37 | { 38 | url: `/mock/user/info`, 39 | method: 'post', 40 | response: ({ body }) => { 41 | const { token } = body 42 | const info = users.find(user => { 43 | return user.token === token 44 | }).info 45 | if (info) { 46 | return { 47 | code: 200, 48 | data: { 49 | info: info 50 | }, 51 | }; 52 | } else { 53 | return { 54 | code: 403, 55 | data: {}, 56 | msg: '无访问权限' 57 | }; 58 | } 59 | 60 | } 61 | }, 62 | { 63 | url: `/mock/user/out`, 64 | method: 'post', 65 | response: () => { 66 | return { 67 | code: 200, 68 | data: {}, 69 | msg: 'success' 70 | }; 71 | } 72 | }, 73 | { 74 | url: `/mock/user/passwordChange`, 75 | method: 'post', 76 | response: () => { 77 | return { 78 | code: 200, 79 | data: {}, 80 | msg: 'success' 81 | }; 82 | } 83 | }, 84 | ] -------------------------------------------------------------------------------- /js-i18n/mockProdServer.js: -------------------------------------------------------------------------------- 1 | import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'; 2 | import userModule from './mock/user' 3 | import tableModule from './mock/table' 4 | 5 | export function setupProdMockServer() { 6 | createProdMockServer([ 7 | ...userModule, 8 | ...tableModule 9 | ]); 10 | } 11 | -------------------------------------------------------------------------------- /js-i18n/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "init", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "start": "vite", 7 | "build": "vite build --mode=production", 8 | "build:stag": "vite build --mode=staging", 9 | "serve": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@vueuse/core": "^4.10.0", 13 | "axios": "^0.21.1", 14 | "element-plus": "^1.0.2-beta.48", 15 | "mockjs": "^1.1.0", 16 | "normalize.css": "^8.0.1", 17 | "nprogress": "^0.2.0", 18 | "throttle-debounce": "^3.0.1", 19 | "vue": "^3.1.2", 20 | "vue-i18n": "^9.1.6", 21 | "vue-router": "4", 22 | "vuex": "^4.0.0" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "^15.0.3", 26 | "@vitejs/plugin-vue": "^1.2.2", 27 | "@vue/compiler-sfc": "^3.0.5", 28 | "sass": "^1.32.12", 29 | "vite": "2.3.7", 30 | "vite-plugin-mock": "2.8.0", 31 | "vue-tsc": "^0.0.24" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /js-i18n/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/js-i18n/public/favicon.ico -------------------------------------------------------------------------------- /js-i18n/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 26 | 27 | 38 | -------------------------------------------------------------------------------- /js-i18n/src/api/table.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/system/request' 2 | 3 | // 获取数据api 4 | export function getData(data) { 5 | return request({ 6 | url: '/table/list', 7 | method: 'post', 8 | baseURL: '/mock', 9 | data 10 | }) 11 | } 12 | 13 | // 获取分类数据 14 | export function getCategory(data) { 15 | return request({ 16 | url: '/table/category', 17 | method: 'post', 18 | baseURL: '/mock', 19 | data 20 | }) 21 | } 22 | 23 | // 获取树组织数据 24 | export function getTree(data) { 25 | return request({ 26 | url: '/table/tree', 27 | method: 'post', 28 | baseURL: '/mock', 29 | data 30 | }) 31 | } 32 | 33 | // 新增 34 | export function add(data) { 35 | return request({ 36 | url: '/table/add', 37 | method: 'post', 38 | baseURL: '/mock', 39 | data 40 | }) 41 | } 42 | 43 | // 编辑 44 | export function update(data) { 45 | return request({ 46 | url: '/table/update', 47 | method: 'post', 48 | baseURL: '/mock', 49 | data 50 | }) 51 | } 52 | 53 | // 删除 54 | export function del(data) { 55 | return request({ 56 | url: '/table/del', 57 | method: 'post', 58 | baseURL: '/mock', 59 | data 60 | }) 61 | } -------------------------------------------------------------------------------- /js-i18n/src/api/user.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/system/request' 2 | 3 | // 登录api 4 | export function loginApi(data) { 5 | return request({ 6 | url: '/user/login', 7 | method: 'post', 8 | baseURL: '/mock', 9 | data 10 | }) 11 | } 12 | 13 | // 获取用户信息Api 14 | export function getInfoApi(data) { 15 | return request({ 16 | url: '/user/info', 17 | method: 'post', 18 | baseURL: '/mock', 19 | data 20 | }) 21 | } 22 | 23 | // 退出登录Api 24 | export function loginOutApi() { 25 | return request({ 26 | url: '/user/out', 27 | method: 'post', 28 | baseURL: '/mock' 29 | }) 30 | } 31 | 32 | // 获取用户信息Api 33 | export function passwordChange(data) { 34 | return request({ 35 | url: '/user/passwordChange', 36 | method: 'post', 37 | baseURL: '/mock', 38 | data 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /js-i18n/src/assets/images/401.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/js-i18n/src/assets/images/401.gif -------------------------------------------------------------------------------- /js-i18n/src/assets/images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/js-i18n/src/assets/images/404.png -------------------------------------------------------------------------------- /js-i18n/src/assets/images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/js-i18n/src/assets/images/404_cloud.png -------------------------------------------------------------------------------- /js-i18n/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/js-i18n/src/assets/logo.png -------------------------------------------------------------------------------- /js-i18n/src/assets/style/common.scss: -------------------------------------------------------------------------------- 1 | @import "./transition.scss"; 2 | @import "@/theme/index.scss"; 3 | .layout-container { 4 | background-color: var(--system-container-main-background); 5 | width: calc(100% - 30px); 6 | height: calc(100% - 30px); 7 | margin: 15px; 8 | display: flex; 9 | flex-direction: column; 10 | overflow-y: auto; 11 | &-form { 12 | display: flex; 13 | justify-content: space-between; 14 | padding: 15px 15px 0; 15 | &-handle { 16 | display: flex; 17 | justify-content: flex-start; 18 | } 19 | &-search { 20 | display: flex; 21 | justify-content: flex-end; 22 | .search-btn { 23 | margin-left: 15px; 24 | } 25 | } 26 | .el-form-item { 27 | margin-bottom: 0; 28 | } 29 | } 30 | &-table { 31 | flex: 1; 32 | height: 100%; 33 | padding: 15px; 34 | overflow: auto; 35 | } 36 | } 37 | .flex-box { 38 | display: flex; 39 | flex-direction: column; 40 | width: 100%; 41 | height: 100%; 42 | padding: 15px; 43 | box-sizing: border-box; 44 | } 45 | .flex { 46 | display: flex; 47 | } 48 | .center { 49 | justify-content: center; 50 | align-items: center; 51 | text-align: center; 52 | } 53 | a { 54 | text-decoration: none; 55 | } 56 | 57 | /** element-plus **/ 58 | .el-icon{ 59 | text-align: center; 60 | } 61 | 62 | /** 用于提示信息 **/ 63 | .my-tip { 64 | background-color: #f1f1f1; 65 | padding: 5px 10px; 66 | text-align: left; 67 | border-radius: 4px; 68 | } 69 | .system-scrollbar { 70 | &::-webkit-scrollbar { 71 | display: none; 72 | width: 6px; 73 | } 74 | &::-webkit-scrollbar-thumb { 75 | border-radius: 10px; 76 | background: rgba(144, 147, 153, 0.3); 77 | } 78 | &:hover { 79 | &::-webkit-scrollbar { 80 | display: block; 81 | } 82 | &::-webkit-scrollbar-thumb { 83 | border-radius: 10px; 84 | background: rgba(144, 147, 153, 0.3); 85 | &:hover { 86 | background: rgba(144, 147, 153, 0.5); 87 | } 88 | } 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /js-i18n/src/assets/style/transition.scss: -------------------------------------------------------------------------------- 1 | /* fade-transform */ 2 | .fade-transform-leave-active, 3 | .fade-transform-enter-active { 4 | transition: all .2s; 5 | } 6 | 7 | .fade-transform-enter-from { 8 | opacity: 0; 9 | transform: translateX(-30px); 10 | transition: all .2s; 11 | } 12 | 13 | .fade-transform-leave-to { 14 | opacity: 0; 15 | transform: translateX(30px); 16 | transition: all .2s; 17 | } 18 | 19 | /* breadcrumb transition */ 20 | .breadcrumb-enter-active, 21 | .breadcrumb-leave-active { 22 | transition: all .2s; 23 | } 24 | 25 | .breadcrumb-enter, 26 | .breadcrumb-leave-active { 27 | opacity: 0; 28 | transform: translateX(80px); 29 | } 30 | 31 | .breadcrumb-move { 32 | transition: all .5s; 33 | } 34 | 35 | .breadcrumb-leave-active { 36 | position: absolute; 37 | } -------------------------------------------------------------------------------- /js-i18n/src/components/layer/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 57 | 58 | -------------------------------------------------------------------------------- /js-i18n/src/components/menu/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /js-i18n/src/config/index.js: -------------------------------------------------------------------------------- 1 | const showLogo = true; // 是否显示Logo顶部模块 2 | const systemTitle = 'message.system.title' // 系统名称,用于显示在左上角模块,以及浏览器标题上使用,使用配置项 3 | export { 4 | systemTitle 5 | } -------------------------------------------------------------------------------- /js-i18n/src/layout/Header/Breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 50 | 51 | -------------------------------------------------------------------------------- /js-i18n/src/layout/Header/functionList/fullscreen.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | 32 | -------------------------------------------------------------------------------- /js-i18n/src/layout/Header/functionList/sizeChange.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 60 | 61 | -------------------------------------------------------------------------------- /js-i18n/src/layout/Header/functionList/theme/theme-color.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 48 | 49 | -------------------------------------------------------------------------------- /js-i18n/src/layout/Header/functionList/word.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 46 | 47 | -------------------------------------------------------------------------------- /js-i18n/src/layout/Logo/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | 24 | -------------------------------------------------------------------------------- /js-i18n/src/layout/Menu/Link.vue: -------------------------------------------------------------------------------- 1 | 7 | 39 | -------------------------------------------------------------------------------- /js-i18n/src/layout/Tabs/tabsHook.js: -------------------------------------------------------------------------------- 1 | const tabsHook = { 2 | setItem: function(arr) { 3 | localStorage.setItem('tabs', JSON.stringify(arr)) 4 | }, 5 | getItem: function() { 6 | return JSON.parse(localStorage.getItem('tabs') || '[]') 7 | } 8 | } 9 | export default tabsHook 10 | -------------------------------------------------------------------------------- /js-i18n/src/locale/index.js: -------------------------------------------------------------------------------- 1 | // 提示信息仅在开发环境生效 2 | import { createI18n } from 'vue-i18n' 3 | import store from '@/store' 4 | 5 | const files= import.meta.globEager('./modules/*.js') 6 | 7 | let messages = {} 8 | Object.keys(files).forEach((c) => { 9 | const module = files[c].default 10 | const moduleName = c.replace(/^\.\/(.*)\/(.*)\.\w+$/, '$2') 11 | messages[moduleName] = module 12 | }) 13 | 14 | const lang = store.state.app.lang || navigator.userLanguage || navigator.language // 初次进入,采用浏览器当前设置的语言,默认采用中文 15 | const locale = lang.indexOf('en') !== -1 ? 'en' : 'zh-cn' 16 | 17 | const i18n = createI18n({ 18 | __VUE_I18N_LEGACY_API__: false, 19 | __VUE_I18N_FULL_INSTALL__: false, 20 | locale: locale, 21 | fallbackLocale: 'zh-cn', 22 | messages 23 | }) 24 | document.querySelector('html').setAttribute('lang', locale) 25 | 26 | export default i18n -------------------------------------------------------------------------------- /js-i18n/src/locale/modules/en.js: -------------------------------------------------------------------------------- 1 | import enLocale from 'element-plus/lib/locale/lang/en' 2 | import system from './en/system' 3 | import common from './en/common' 4 | import menu from './en/menu' 5 | const lang = { 6 | el: enLocale.el, // element-plus i18 setting 7 | message: { 8 | language: 'English', 9 | ...system, 10 | ...common, 11 | ...menu 12 | } 13 | } 14 | 15 | export default lang -------------------------------------------------------------------------------- /js-i18n/src/locale/modules/en/common.js: -------------------------------------------------------------------------------- 1 | export default { 2 | common: { 3 | search: 'search', 4 | searchTip: 'please input keyword', 5 | add: 'add', 6 | update: 'update', 7 | del: 'delete', 8 | delBat: 'delete choose', 9 | delTip: 'Are you sure delete the selection data ?', 10 | handle: 'handle' 11 | }, 12 | } -------------------------------------------------------------------------------- /js-i18n/src/locale/modules/en/menu.js: -------------------------------------------------------------------------------- 1 | export default { 2 | menu: { 3 | dashboard: { 4 | name: 'dashboard', 5 | index: 'index' 6 | }, 7 | system: { 8 | name: 'system', 9 | redirect: 'redirect', 10 | '404': '404', 11 | '401': '401' 12 | }, 13 | page: { 14 | name: 'page', 15 | crudTable: 'crudTable', 16 | categoryTable: 'categoryTable', 17 | treeTable: 'treeTable', 18 | }, 19 | }, 20 | } -------------------------------------------------------------------------------- /js-i18n/src/locale/modules/en/system.js: -------------------------------------------------------------------------------- 1 | export default { 2 | system: { 3 | title: 'backendsystem', 4 | login: 'login', 5 | userName: 'userName', 6 | password: 'password', 7 | contentScreen: 'content full screen', 8 | fullScreen: 'fullscreen', 9 | fullScreenBack: 'back fullscreen', 10 | github: 'visit github', 11 | changePassword: 'change password', 12 | loginOut: 'login out', 13 | user: 'admin', 14 | size: { 15 | default: 'default', 16 | medium: 'medium', 17 | small: 'small', 18 | mini: 'mini' 19 | }, 20 | setting: { 21 | name: 'setting', 22 | 23 | style: { 24 | name: 'full style setting', 25 | default: 'default menu style', 26 | light: 'light menu style', 27 | dark: 'dark menu style' 28 | }, 29 | primaryColor: { 30 | name: 'primary color', 31 | blue: 'default blue', 32 | red: 'rose red', 33 | violet: 'grace violet', 34 | green: 'story green', 35 | cyan: 'cyan', 36 | black: 'geek black' 37 | }, 38 | other: { 39 | name: 'other setting', 40 | showLogo: 'show logo', 41 | showBreadcrumb: 'show breadcrumb', 42 | keepOnlyOneMenu: 'keep only one menu open', 43 | } 44 | }, 45 | tab: { 46 | reload: 'refresh', 47 | closeAll: 'close all tags', 48 | closeOther: 'close other tags', 49 | closeCurrent: 'close current tag' 50 | } 51 | }, 52 | } -------------------------------------------------------------------------------- /js-i18n/src/locale/modules/zh-cn.js: -------------------------------------------------------------------------------- 1 | import zhLocale from 'element-plus/lib/locale/lang/zh-cn' 2 | import system from './zh-cn/system' 3 | import common from './zh-cn/common' 4 | import menu from './zh-cn/menu' 5 | const lang = { 6 | el: zhLocale.el, // element内部国际化 7 | message: { 8 | language: '中文', 9 | ...system, 10 | ...common, 11 | ...menu 12 | } 13 | } 14 | 15 | export default lang -------------------------------------------------------------------------------- /js-i18n/src/locale/modules/zh-cn/common.js: -------------------------------------------------------------------------------- 1 | export default { 2 | common: { 3 | search: '搜索', 4 | searchTip: '请输入关键词进行检索', 5 | add: '新增', 6 | update: '编辑', 7 | del: '删除', 8 | delBat: '批量删除', 9 | delTip: '确定删除选中的数据吗?', 10 | handle: '操作' 11 | }, 12 | } -------------------------------------------------------------------------------- /js-i18n/src/locale/modules/zh-cn/menu.js: -------------------------------------------------------------------------------- 1 | export default { 2 | menu: { 3 | dashboard: { 4 | name: 'dashboard', 5 | index: '首页' 6 | }, 7 | system: { 8 | name: '系统目录', 9 | redirect: '重定向页面', 10 | '404': '404', 11 | '401': '401' 12 | }, 13 | page: { 14 | name: '页面', 15 | crudTable: '业务表格', 16 | categoryTable: '分类联动表格', 17 | treeTable: '树联动表格' 18 | } 19 | }, 20 | } -------------------------------------------------------------------------------- /js-i18n/src/locale/modules/zh-cn/system.js: -------------------------------------------------------------------------------- 1 | export default { 2 | system: { 3 | title: '后台管理系统', 4 | login: '登录', 5 | userName: '用户名', 6 | password: '密码', 7 | contentScreen: '内容全屏', 8 | fullScreen: '全屏', 9 | fullScreenBack: '退出全屏', 10 | github: '访问github地址', 11 | changePassword: '修改密码', 12 | loginOut: '退出登录', 13 | user: '管理员', 14 | size: { 15 | default: '默认', 16 | medium: '中', 17 | small: '小', 18 | mini: '迷你' 19 | }, 20 | setting: { 21 | name: '系统设置', 22 | 23 | style: { 24 | name: '整体风格设置', 25 | default: '默认菜单风格', 26 | light: '亮色菜单风格', 27 | dark: '暗色菜单风格' 28 | }, 29 | primaryColor: { 30 | name: '主题色', 31 | blue: '默认蓝', 32 | red: '玫瑰红', 33 | violet: '优雅紫', 34 | green: '故事绿', 35 | cyan: '明青', 36 | black: '极客黑' 37 | }, 38 | other: { 39 | name: '其他设置', 40 | showLogo: '显示logo', 41 | showBreadcrumb: '显示面包屑导航', 42 | keepOnlyOneMenu: '保持一个菜单展开', 43 | } 44 | }, 45 | tab: { 46 | reload: '重新加载', 47 | closeAll: '关闭所有标签', 48 | closeOther: '关闭其他标签', 49 | closeCurrent: '关闭当前标签' 50 | } 51 | }, 52 | } -------------------------------------------------------------------------------- /js-i18n/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import ElementPlus from 'element-plus' 3 | import { baidu } from './utils/system/statistics' 4 | import 'element-plus/lib/theme-chalk/index.css' 5 | import 'element-plus/lib/theme-chalk/display.css' // 引入基于断点的隐藏类 6 | import 'normalize.css' // css初始化 7 | import './assets/style/common.scss' // 公共css 8 | import App from './App.vue' 9 | import store from './store' 10 | import router from './router' 11 | import i18n from './locale' 12 | // if (import.meta.env.MODE !== 'development') { // 非开发环境调用百度统计 13 | // baidu() 14 | // } 15 | const app = createApp(App) 16 | app.use(ElementPlus, { size: store.state.app.elementSize, i18n: i18n.global.t }) 17 | app.use(store) 18 | app.use(router) 19 | app.use(i18n) 20 | // app.config.performance = true 21 | app.mount('#app') 22 | -------------------------------------------------------------------------------- /js-i18n/src/router/createNode.js: -------------------------------------------------------------------------------- 1 | // 1. 用于解决keep-alive需要name的问题,动态生成随机name供keep-alive使用 2 | // 2. 用于解决transition动画内部结点只能为根元素的问题,单文件可写多结点 3 | import { defineComponent, h, createVNode, ref, nextTick } from 'vue' 4 | import reload from './reload.vue' 5 | import NProgress from '@/utils/system/nprogress' 6 | 7 | export function createNameComponent(component) { 8 | return () => { 9 | return new Promise((res) => { 10 | component().then((comm) => { 11 | const name = (comm.default.name || 'vueAdminBox') + '$' + Date.now(); 12 | const tempComm = defineComponent({ 13 | name, 14 | setup() { 15 | const isReload = ref(false); 16 | let timeOut = null; 17 | const handleReload = () => { 18 | isReload.value = true; 19 | timeOut && clearTimeout(timeOut); 20 | NProgress.start(); 21 | timeOut = setTimeout(() => { 22 | nextTick(() => { 23 | NProgress.done(); 24 | isReload.value = false; 25 | }); 26 | }, 260); 27 | }; 28 | return { 29 | isReload, 30 | handleReload 31 | }; 32 | }, 33 | render: function () { 34 | if (this.isReload) { 35 | return h('div', { class: 'el-main-box' }, [h(reload)]); 36 | } else { 37 | return h('div', { class: 'el-main-box' }, [createVNode(comm.default)]); 38 | } 39 | } 40 | }); 41 | res(tempComm); 42 | }); 43 | }); 44 | }; 45 | } 46 | 47 | -------------------------------------------------------------------------------- /js-i18n/src/router/modules/dashboard.js: -------------------------------------------------------------------------------- 1 | import Layout from '@/layout/index.vue' 2 | import { createNameComponent } from '../createNode' 3 | const route = [ 4 | { 5 | path: '/', 6 | component: Layout, 7 | redirect: '/dashboard', 8 | meta: { title: 'message.menu.dashboard.name', icon: 'el-icon-menu' }, 9 | children: [ 10 | { 11 | path: 'dashboard', 12 | component: createNameComponent(() => import('@/views/main/dashboard/index.vue')), 13 | meta: { title: 'message.menu.dashboard.index', icon: 'el-icon-menu', hideClose: true } 14 | } 15 | ] 16 | } 17 | ] 18 | 19 | export default route -------------------------------------------------------------------------------- /js-i18n/src/router/modules/pages.js: -------------------------------------------------------------------------------- 1 | import Layout from '@/layout/index.vue' 2 | import { createNameComponent } from '../createNode' 3 | const route = [ 4 | { 5 | path: '/pages', 6 | component: Layout, 7 | redirect: '/pages/crudTable', 8 | meta: { title: 'message.menu.page.name', icon: 'el-icon-document-copy' }, 9 | alwayShow: true, 10 | children: [ 11 | { 12 | path: 'crudTable', 13 | component: createNameComponent(() => import('@/views/main/pages/crudTable/index.vue')), 14 | meta: { title: 'message.menu.page.crudTable', cache: false, roles: ['admin', 'editor'] } 15 | }, 16 | { 17 | path: 'categoryTable', 18 | component: createNameComponent(() => import('@/views/main/pages/categoryTable/index.vue')), 19 | meta: { title: 'message.menu.page.categoryTable', cache: true, roles: ['admin'] } 20 | }, 21 | { 22 | path: 'treeTable', 23 | component: createNameComponent(() => import('@/views/main/pages/treeTable/index.vue')), 24 | meta: { title: 'message.menu.page.treeTable', cache: true } 25 | } 26 | ] 27 | } 28 | ] 29 | 30 | export default route -------------------------------------------------------------------------------- /js-i18n/src/router/modules/system.js: -------------------------------------------------------------------------------- 1 | import Layout from '@/layout/index.vue' 2 | import { createNameComponent } from '../createNode' 3 | const route = [ 4 | { 5 | path: '/system', 6 | component: Layout, 7 | redirect: '/404', 8 | hideMenu: true, 9 | meta: { title: 'message.menu.system.name' }, 10 | children: [ 11 | { 12 | path: '/404', 13 | component: createNameComponent(() => import('@/views/system/404.vue')), 14 | meta: { title: 'message.menu.system.404', hideTabs: true } 15 | }, 16 | { 17 | path: '/401', 18 | component: createNameComponent(() => import('@/views/system/401.vue')), 19 | meta: { title: 'message.menu.system.401', hideTabs: true } 20 | }, 21 | { 22 | path: '/redirect/:path(.*)', 23 | component: createNameComponent(() => import('@/views/system/redirect.vue')), 24 | meta: { title: 'message.menu.system.redirect', hideTabs: true } 25 | } 26 | ] 27 | }, 28 | { 29 | path: '/login', 30 | component: createNameComponent(() => import('@/views/system/login.vue')), 31 | hideMenu: true, 32 | meta: { title: 'message.system.login', hideTabs: true } 33 | }, 34 | { 35 | // 找不到路由重定向到404页面 36 | path: "/:pathMatch(.*)", 37 | component: Layout, 38 | redirect: "/404", 39 | hideMenu: true 40 | }, 41 | ] 42 | 43 | export default route -------------------------------------------------------------------------------- /js-i18n/src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore, createLogger } from 'vuex' 2 | import Presistent from './plugins/persistent' 3 | const debug = process.env.NODE_ENV !== 'production' 4 | 5 | const files= import.meta.globEager('./modules/*.js') 6 | 7 | let modules = {} 8 | Object.keys(files).forEach((c) => { 9 | const module = files[c].default 10 | const moduleName = c.replace(/^\.\/(.*)\/(.*)\.\w+$/, '$2') 11 | modules[moduleName] = module 12 | }) 13 | 14 | const presistent = Presistent({ key: 'vuex', modules, modulesKeys: { 15 | local: Object.keys(modules), 16 | session: [] 17 | } }) 18 | 19 | export default createStore({ 20 | modules: { 21 | ...modules 22 | }, 23 | strict: debug, 24 | plugins: debug ? [createLogger(), presistent] : [presistent] 25 | }) -------------------------------------------------------------------------------- /js-i18n/src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | const state = () => ({ 2 | isCollapse: false, // 侧边栏是否收缩展示 3 | contentFullScreen: false, // 内容是否可全屏展示 4 | showLogo: true, // 是否显示Logo 5 | fixedTop: false, // 是否固定顶部, todo,暂未使用 6 | showTabs: true, // 是否显示导航历史 7 | expandOneMenu: true, // 一次是否只能展开一个菜单 8 | elementSize: 'mini', // element默认尺寸,支持官网四个大小参数 9 | lang: 'zh', // 默认采用的国际化方案 10 | theme: { 11 | state: { 12 | style: 'default', 13 | primaryColor: '#409eff', 14 | menuType: 'side' 15 | } 16 | } 17 | }) 18 | 19 | // mutations 20 | const mutations = { 21 | isCollapseChange(state, type) { 22 | state.isCollapse = type 23 | }, 24 | contentFullScreenChange(state, type) { 25 | state.contentFullScreen = type 26 | }, 27 | menuListChange(state, arr) { 28 | state.menuList = arr 29 | }, 30 | stateChange(state, option) { 31 | state[option.name] = option.value 32 | } 33 | } 34 | 35 | // actions 36 | const actions = { 37 | 38 | } 39 | 40 | export default { 41 | namespaced: true, 42 | state, 43 | actions, 44 | mutations 45 | } -------------------------------------------------------------------------------- /js-i18n/src/store/modules/keepAlive.js: -------------------------------------------------------------------------------- 1 | 2 | const state = () => ({ 3 | keepAliveComponentsName: [] // 需要缓存的组件名称 4 | }) 5 | 6 | // mutations 7 | const mutations = { 8 | // 重置,Push, splice keep-alive对象 9 | setKeepAliveComponentsName(state, nameArr) { 10 | state.keepAliveComponentsName = nameArr 11 | }, 12 | addKeepAliveComponentsName(state, name) { 13 | state.keepAliveComponentsName.push(name) 14 | }, 15 | delKeepAliveComponentsName(state, name) { 16 | const key = state.keepAliveComponentsName.indexOf(name) 17 | if (key !== -1) { 18 | state.keepAliveComponentsName.splice(key, 1) 19 | console.log(state.keepAliveComponentsName) 20 | } 21 | } 22 | } 23 | 24 | const getters = { 25 | keepAliveComponentsName(state) { 26 | return state.keepAliveComponentsName 27 | } 28 | } 29 | 30 | // actions 31 | const actions = { 32 | 33 | } 34 | 35 | export default { 36 | namespaced: true, 37 | state, 38 | getters, 39 | actions, 40 | mutations 41 | } -------------------------------------------------------------------------------- /js-i18n/src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import { loginApi, getInfoApi, loginOutApi } from '@/api/user' 2 | 3 | const state = () => ({ 4 | token: '', // 登录token 5 | info: {}, // 用户信息 6 | }) 7 | 8 | // getters 9 | const getters = { 10 | token(state) { 11 | return state.token 12 | } 13 | } 14 | 15 | // mutations 16 | const mutations = { 17 | tokenChange(state, token) { 18 | state.token = token 19 | }, 20 | infoChange(state, info) { 21 | state.info = info 22 | } 23 | } 24 | 25 | // actions 26 | const actions = { 27 | // login by login.vue 28 | login({ commit, dispatch }, params) { 29 | return new Promise((resolve, reject) => { 30 | loginApi(params) 31 | .then(res => { 32 | commit('tokenChange', res.data.token) 33 | dispatch('getInfo', { token: res.data.token }) 34 | .then(infoRes => { 35 | resolve(res.data.token) 36 | }) 37 | }) 38 | }) 39 | }, 40 | // get user info after user logined 41 | getInfo({ commit }, params) { 42 | return new Promise((resolve, reject) => { 43 | getInfoApi(params) 44 | .then(res => { 45 | commit('infoChange', res.data.info) 46 | resolve(res.data.info) 47 | }) 48 | }) 49 | }, 50 | 51 | // login out the system after user click the loginOut button 52 | loginOut({ commit }) { 53 | loginOutApi() 54 | .then(res => { 55 | 56 | }) 57 | .catch(error => { 58 | 59 | }) 60 | .finally(() => { 61 | localStorage.removeItem('tabs') 62 | localStorage.removeItem('vuex') 63 | location.reload() 64 | }) 65 | } 66 | } 67 | 68 | export default { 69 | namespaced: true, 70 | state, 71 | actions, 72 | getters, 73 | mutations 74 | } -------------------------------------------------------------------------------- /js-i18n/src/store/plugins/persistent.js: -------------------------------------------------------------------------------- 1 | const exclude = ['actions', 'getters', 'mutations', 'namespaced'] 2 | export default function Presistent({ key, modules, modulesKeys }) { 3 | return (store) => { 4 | const localOldState = JSON.parse(localStorage.getItem(key) || '{}') 5 | const sessionOldState = JSON.parse(sessionStorage.getItem(key) || '{}') 6 | let oldState = {} 7 | Object.assign(oldState, localOldState, sessionOldState) 8 | if (Object.keys(oldState).length > 0) { 9 | for (const oldKey in oldState) { 10 | modules[oldKey] = oldState[oldKey] 11 | } 12 | store.replaceState(modules) 13 | } 14 | store.subscribe((mutation, state) => { 15 | // 判断是否需要缓存数据至localStorage 16 | if (modulesKeys.local.length > 0) { 17 | const localData = setData(store.state, modulesKeys.local) 18 | localStorage.setItem(key, JSON.stringify(localData)) 19 | } else { 20 | localStorage.removeItem(key) 21 | } 22 | // 判断是否需要缓存数据至sessionStorage 23 | if (modulesKeys.session.length > 0) { 24 | const sessionData = setData(store.state, modulesKeys.session) 25 | sessionStorage.setItem(key, JSON.stringify(sessionData)) 26 | } else { 27 | sessionStorage.removeItem(key) 28 | } 29 | }) 30 | } 31 | } 32 | 33 | function setData(state, module) { 34 | let data = {} 35 | for (const i of module) { 36 | data[i] = state[i] 37 | } 38 | return data 39 | } -------------------------------------------------------------------------------- /js-i18n/src/theme/index.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | // 主题色 3 | --system-primary-color: #409eff; // 可做背景色和文本色,用做背景色时,需要和--system-primary-text-color配合使用,避免文件颜色和主题色冲突 4 | --system-primary-text-color: #fff; // 主题色作为背景色时使用 5 | 6 | // logo颜色相关 7 | --system-logo-color: #f1f1f1; 8 | --system-logo-background: #263445; 9 | 10 | // 菜单颜色相关 11 | --system-menu-text-color: #bfcbd9; 12 | --system-menu-background: #28415a; 13 | --system-menu-children-background: #1f2d3d; 14 | --system-menu-submenu-active-color: #fff; 15 | --system-menu-hover-background: #203448; 16 | 17 | // header区域 18 | --system-header-background: #fff; 19 | --system-header-text-color: #bbb; 20 | --system-header-breadcrumb-text-color: #97a8be; 21 | --system-header-item-hover-color: #000; 22 | --system-header-border-color: #d8dce5; 23 | --system-header-tab-background: #fff; 24 | 25 | // contaier区域,父框架 26 | --system-container-background: #f0f2f5; 27 | --system-container-main-background: #fff; 28 | 29 | // 页面区域, 这一块是你在自己写的文件中使用主题,核心需要关注的地方 30 | --system-page-background: #fff; // 主背景 31 | --system-page-color: #303133; // 主要的文本颜色 32 | --system-page-tip-color: rgba(0, 0, 0, 0.45); // 协助展示的文本颜色 33 | --system-page-border-color: #000; // 通用的边框配置色,便于主题扩展 34 | 35 | // element主题色修改 36 | --el-color-primary: var(--system-primary-color); 37 | } 38 | 39 | // 进度条颜色修改为主题色 40 | body #nprogress .bar { 41 | background-color: var(--system-primary-color); 42 | } 43 | body #nprogress .peg { 44 | box-shadow: 0 0 10px var(--system-primary-color), 0 0 5px var(--system-primary-color); 45 | } 46 | body #nprogress .spinner-icon { 47 | border-top-color: var(--system-primary-color); 48 | border-left-color: var(--system-primary-color); 49 | } 50 | 51 | @import './modules/dark.scss'; 52 | -------------------------------------------------------------------------------- /js-i18n/src/theme/modules/dark.scss: -------------------------------------------------------------------------------- 1 | .dark { 2 | // 通用 3 | p, h1, h2, h3, h4, h5, h6, article { 4 | color: var(--system-page-color); 5 | } 6 | .el-tree { 7 | background-color: var(--system-page-background); 8 | .el-tree-node__content:hover { 9 | background-color: #272727; 10 | } 11 | --el-color-primary-light-9: #272727; 12 | } 13 | .el-card { 14 | background-color: var(--system-page-background); 15 | color: var(--system-page-color); 16 | border-color: var(--system-page-border-color); 17 | .el-card__header { 18 | border-color: var(--system-page-border-color); 19 | } 20 | } 21 | // 页面内部样式修改 22 | 23 | } -------------------------------------------------------------------------------- /js-i18n/src/utils/system/nprogress.js: -------------------------------------------------------------------------------- 1 | import NProgress from "nprogress" 2 | import "nprogress/nprogress.css" 3 | 4 | NProgress.configure({ 5 | easing: 'ease', // 动画方式 6 | speed: 500, // 递增进度条的速度 7 | showSpinner: true, // 是否显示加载ico 8 | trickleSpeed: 200, // 自动递增间隔 9 | minimum: 0.3 // 初始化时的最小百分比 10 | }) 11 | 12 | export default NProgress -------------------------------------------------------------------------------- /js-i18n/src/utils/system/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import store from '@/store' 3 | import { ElMessage } from 'element-plus' 4 | const baseURL = import.meta.env.VITE_BASE_URL 5 | 6 | const service = axios.create({ 7 | baseURL: baseURL, 8 | timeout: 5000 9 | }) 10 | 11 | // 请求前的统一处理 12 | service.interceptors.request.use( 13 | (config) => { 14 | // JWT鉴权处理 15 | if (store.getters['user/token']) { 16 | config.headers['token'] = store.state.user.token 17 | } 18 | return config 19 | }, 20 | (error) => { 21 | console.log(error) // for debug 22 | return Promise.reject(error) 23 | } 24 | ) 25 | 26 | service.interceptors.response.use( 27 | (response) => { 28 | const res = response.data 29 | if (res.code === 200) { 30 | return res 31 | } else { 32 | showError(res) 33 | return Promise.reject(res) 34 | } 35 | }, 36 | (error)=> { 37 | console.log(error) // for debug 38 | const badMessage = error.message || error 39 | const code = parseInt(badMessage.toString().replace('Error: Request failed with status code ', '')) 40 | showError({ code, message: badMessage }) 41 | return Promise.reject(error) 42 | } 43 | ) 44 | 45 | function showError(error) { 46 | if (error.code === 403) { 47 | // to re-login 48 | store.dispatch('user/loginOut') 49 | } else { 50 | ElMessage({ 51 | message: error.msg || error.message || '服务异常', 52 | type: 'error', 53 | duration: 3 * 1000 54 | }) 55 | } 56 | 57 | } 58 | 59 | export default service -------------------------------------------------------------------------------- /js-i18n/src/utils/system/statistics.js: -------------------------------------------------------------------------------- 1 | // 百度统计代码,需自行更换 2 | export function baidu() { 3 | const script = document.createElement('script') 4 | script.type = 'text/javascript' 5 | script.text = ` 6 | var _hmt = _hmt || []; 7 | (function() { 8 | var hm = document.createElement("script"); 9 | hm.src = "https://hm.baidu.com/hm.js?bd78bc908e66174e7dde385bf37cb4c1"; 10 | var s = document.getElementsByTagName("script")[0]; 11 | s.parentNode.insertBefore(hm, s); 12 | })(); 13 | ` 14 | document.getElementsByTagName('head')[0].appendChild(script) 15 | } -------------------------------------------------------------------------------- /js-i18n/src/utils/system/title.js: -------------------------------------------------------------------------------- 1 | import i18n from '@/locale' 2 | import { systemTitle } from '@/config' 3 | const { t } = i18n.global 4 | 5 | export function changeTitle(name) { 6 | document.title = `${t(name)}-${t(systemTitle)}` 7 | } 8 | -------------------------------------------------------------------------------- /js-i18n/src/views/main/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /js-i18n/src/views/main/pages/categoryTable/enum.js: -------------------------------------------------------------------------------- 1 | export const selectData = [ 2 | { value: 1, label: '运动' }, 3 | { value: 2, label: '健身' }, 4 | { value: 3, label: '跑酷' }, 5 | { value: 4, label: '街舞' } 6 | ] 7 | 8 | export const radioData = [ 9 | { value: 1, label: '今天' }, 10 | { value: 2, label: '明天' }, 11 | { value: 3, label: '后天' }, 12 | ] -------------------------------------------------------------------------------- /js-i18n/src/views/main/pages/categoryTable/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 28 | 29 | -------------------------------------------------------------------------------- /js-i18n/src/views/main/pages/crudTable/enum.js: -------------------------------------------------------------------------------- 1 | export const selectData = [ 2 | { value: 1, label: '运动' }, 3 | { value: 2, label: '健身' }, 4 | { value: 3, label: '跑酷' }, 5 | { value: 4, label: '街舞' } 6 | ] 7 | 8 | export const radioData = [ 9 | { value: 1, label: '今天' }, 10 | { value: 2, label: '明天' }, 11 | { value: 3, label: '后天' }, 12 | ] -------------------------------------------------------------------------------- /js-i18n/src/views/main/pages/treeTable/enum.js: -------------------------------------------------------------------------------- 1 | export const selectData = [ 2 | { value: 1, label: '运动' }, 3 | { value: 2, label: '健身' }, 4 | { value: 3, label: '跑酷' }, 5 | { value: 4, label: '街舞' } 6 | ] 7 | 8 | export const radioData = [ 9 | { value: 1, label: '今天' }, 10 | { value: 2, label: '明天' }, 11 | { value: 3, label: '后天' }, 12 | ] -------------------------------------------------------------------------------- /js-i18n/src/views/main/pages/treeTable/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 29 | 30 | -------------------------------------------------------------------------------- /js-i18n/src/views/system/redirect.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /js-i18n/vite.config.js: -------------------------------------------------------------------------------- 1 | import vue from '@vitejs/plugin-vue' 2 | import { viteMockServe } from 'vite-plugin-mock' 3 | import { resolve } from 'path' 4 | 5 | const pathResolve = (dir) => { 6 | return resolve(__dirname, ".", dir) 7 | } 8 | 9 | const alias = { 10 | '@': pathResolve("src") 11 | } 12 | 13 | // https://vitejs.dev/config/ 14 | export default ({ command }) => { 15 | const prodMock = true; 16 | return { 17 | base: './', 18 | resolve: { 19 | alias 20 | }, 21 | server: { 22 | port: 3003, 23 | host: '0.0.0.0', 24 | open: true, 25 | proxy: { // 代理配置 26 | '/dev': 'https://www.fastmock.site/mock/48cab8545e64d93ff9ba66a87ad04f6b/' 27 | }, 28 | }, 29 | build: { 30 | rollupOptions: { 31 | output: { 32 | manualChunks: { 33 | 34 | } 35 | } 36 | } 37 | }, 38 | plugins: [ 39 | vue(), 40 | viteMockServe({ 41 | mockPath: 'mock', 42 | localEnabled: command === 'serve', 43 | prodEnabled: command !== 'serve' && prodMock, 44 | watchFiles: true, 45 | injectCode: ` 46 | import { setupProdMockServer } from '../mockProdServer'; 47 | setupProdMockServer(); 48 | `, 49 | logger: true, 50 | }), 51 | ] 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /js/.env.development: -------------------------------------------------------------------------------- 1 | ENV = 'development' 2 | 3 | VITE_BASE_URL = '/dev-api' -------------------------------------------------------------------------------- /js/.env.production: -------------------------------------------------------------------------------- 1 | ENV = 'production' 2 | 3 | VITE_BASE_URL = '/pro-api' -------------------------------------------------------------------------------- /js/.env.staging: -------------------------------------------------------------------------------- 1 | ENV = 'staging' 2 | 3 | VITE_BASE_URL = '/test-api' -------------------------------------------------------------------------------- /js/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | yarn.lock 7 | yarn-error.log 8 | .vscode 9 | -------------------------------------------------------------------------------- /js/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 罗茜 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. -------------------------------------------------------------------------------- /js/VERSION.md: -------------------------------------------------------------------------------- 1 | # 版本更新日志 2 | 3 | ## 0.41版本 4 | 1. 【修改】菜单过长时,滚动条问题 5 | 2. 【新增】主题配置功能,核心编辑代码编写 6 | 7 | ## 0.40版本 8 | 1. 【优化】路由keep-alive状态保存与删除 9 | 2. 【新增】当前页刷新,缓存页刷新机制 10 | 3. 【新增】关闭标签栏时的缓存页对应状态变化 11 | 12 | ## 0.32版本 13 | 1. 【优化】左侧菜单栏复杂情况下的示例应用 14 | 2. 【新增】分类联动表格的实现 15 | 3. 【新增】树联动表格的实现 16 | 4. 【BUG】表格+开发BUG修复 17 | 18 | ## 0.31版本 19 | 1. 【新增】指令集模块完成 20 | 2. 【新增】echarts图表模块完成 21 | 22 | ## 0.24版本 23 | ``` 24 | 1. 【新增】弹窗截图功能 25 | ``` 26 | 27 | ## 0.23版本 28 | ``` 29 | 1. 【新增】代码编辑器 30 | 2. 【新增】JSON编辑器 31 | 3. 【新增】可拖拽面板 32 | 4. 【新增】地图组件 33 | ``` 34 | 35 | ## 0.22版本 36 | ``` 37 | 1. 【新增】MD编辑器 38 | ``` 39 | 40 | ## 0.21版本 41 | ``` 42 | 1. 【统计】统计大部分需要实现的功能页面,并展示于左侧菜单中 43 | 2. 【新增】新增大部分需要实现的路由,并确保,大部分能于7月1日之前完全实现 44 | ``` 45 | 46 | ## 0.11版本 47 | ``` 48 | 1. 【新增】新增组件栏目,并补充按钮组进入 49 | 2. 【优化】多级菜单跳转demo 50 | 3. 【优化】侧边栏功能优化 51 | ``` 52 | 53 | ## 0.10版本 54 | ``` 55 | 1. 【新增】实现多级菜单Demo 56 | 2. 【优化】当菜单数据量过高时,优化显示 57 | ``` 58 | 59 | ## 0.9版本 60 | ``` 61 | 1. 【优化】弹窗组件可拖拽 62 | 2. 【优化】弹窗组件内部暴露逻辑,供外部使用 63 | ``` 64 | 65 | ## 0.8版本 66 | ``` 67 | 1. 【优化】axios提示配置 68 | 2. 【优化】公用组件内部逻辑及外部调用方法 69 | 3. 【补充】业务表格模块的数据调用方式 70 | ``` 71 | 72 | ## 0.7版本 73 | ``` 74 | 1. 【优化】element-ui国际化配置指南 75 | 2. 【优化】国际化配置本地存储 76 | ``` 77 | 78 | ## 0.6版本 79 | ``` 80 | 1. 【新增】按钮尺寸调整功能,针对element-plus的全局尺寸调整 81 | 2. 【新增】首页,图表类展示 82 | 3. 【新增】页面模块,目前包含了业务表格,主要为较为完善的crud写法,正在完善中 83 | 4. 【新增】基于echarts封装的业务chart组件,用户可快速利用此组件生成需要的图表 84 | ``` 85 | 86 | ## 0.5.1版本 87 | ``` 88 | 1. 【修复】mock地址,尽量不要使用较短的url,否则,打包上线后,较长的URL容易被冲突掉,示例:登录url和退出登录URL 89 | ``` 90 | 91 | ## 0.5版本 92 | ``` 93 | 1. 【优化】本地mock地址使用细节补充及细节优化 94 | 2. 【新增】登录页鉴权 95 | 3. 【新增】退出登录功能 96 | 4. 【优化】本地封装的axios插件优化使用方案 97 | ``` 98 | 99 | ## 0.4版本 100 | 101 | ``` 102 | 1. 【新增】登录页面,及手机自适应处理 103 | 2. 【新增】axios插件,请求处理机制建立 104 | 3. 【优化】全局/@/替换为@/ 105 | 4. 【新增】本地mock地址模拟 106 | ``` 107 | 108 | ## 0.3.1版本 109 | 110 | ``` 111 | 1. 【实现】面包屑导航三种关闭功能实现 112 | ``` 113 | 114 | ## 0.3版本 115 | 116 | ``` 117 | 1. 【新增】github跳转链接 118 | 2. 【新增】主题配置功能[页面制作实现] 119 | 3. 【新增】vuex持久化插件,手写版 120 | ``` 121 | 122 | ## 0.2版本 123 | 124 | ``` 125 | 1. 【新增】框架国际化处理 126 | 2. 【BUG】打包上线上路由跳转功能异常,已修复 127 | ``` 128 | 129 | 130 | 131 | ## 0.1版本 132 | 133 | ``` 134 | 1. 集成vue-router,vuex,@vueuse/core,element-plus等核心插件 135 | 2. 全局状态管理方案实现 136 | 3. 全局路由管理方案实现 137 | 4. 核心组件库引入处理 138 | 5. 全局layout制作,及自适应处理 139 | 6. 菜单解决方案实现 140 | 7. 面包屑导航实现 141 | 8. 全屏功能实现 142 | ``` 143 | 144 | -------------------------------------------------------------------------------- /js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /js/mock/user.js: -------------------------------------------------------------------------------- 1 | const users = [ 2 | { name: 'admin', password: '123456', token: 'admin', info: { 3 | name: '系统管理员' 4 | }}, 5 | { name: 'editor', password: '123456', token: 'editor', info: { 6 | name: '编辑人员' 7 | }}, 8 | { name: 'test', password: '123456', token: 'test', info: { 9 | name: '测试人员' 10 | }}, 11 | ] 12 | export default [ 13 | { 14 | url: `/mock/user/login`, 15 | method: 'post', 16 | response: ({ body }) => { 17 | const user = users.find(user => { 18 | return body.name === user.name && body.password === user.password 19 | }) 20 | if (user) { 21 | return { 22 | code: 200, 23 | data: { 24 | token: user.token, 25 | }, 26 | }; 27 | } else { 28 | return { 29 | code: 401, 30 | data: {}, 31 | msg: '用户名或密码错误' 32 | }; 33 | } 34 | 35 | } 36 | }, 37 | { 38 | url: `/mock/user/info`, 39 | method: 'post', 40 | response: ({ body }) => { 41 | const { token } = body 42 | const info = users.find(user => { 43 | return user.token === token 44 | }).info 45 | if (info) { 46 | return { 47 | code: 200, 48 | data: { 49 | info: info 50 | }, 51 | }; 52 | } else { 53 | return { 54 | code: 403, 55 | data: {}, 56 | msg: '无访问权限' 57 | }; 58 | } 59 | 60 | } 61 | }, 62 | { 63 | url: `/mock/user/out`, 64 | method: 'post', 65 | response: () => { 66 | return { 67 | code: 200, 68 | data: {}, 69 | msg: 'success' 70 | }; 71 | } 72 | }, 73 | { 74 | url: `/mock/user/passwordChange`, 75 | method: 'post', 76 | response: () => { 77 | return { 78 | code: 200, 79 | data: {}, 80 | msg: 'success' 81 | }; 82 | } 83 | }, 84 | ] -------------------------------------------------------------------------------- /js/mockProdServer.js: -------------------------------------------------------------------------------- 1 | import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'; 2 | import userModule from './mock/user' 3 | import tableModule from './mock/table' 4 | 5 | export function setupProdMockServer() { 6 | createProdMockServer([ 7 | ...userModule, 8 | ...tableModule 9 | ]); 10 | } 11 | -------------------------------------------------------------------------------- /js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "init", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "start": "vite", 7 | "build": "vite build --mode=production", 8 | "build:stag": "vite build --mode=staging", 9 | "serve": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@vueuse/core": "^4.10.0", 13 | "axios": "^0.21.1", 14 | "element-plus": "^1.0.2-beta.48", 15 | "mockjs": "^1.1.0", 16 | "normalize.css": "^8.0.1", 17 | "nprogress": "^0.2.0", 18 | "throttle-debounce": "^3.0.1", 19 | "vue": "^3.1.2", 20 | "vue-router": "4", 21 | "vuex": "^4.0.0" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^15.0.3", 25 | "@vitejs/plugin-vue": "^1.2.2", 26 | "@vue/compiler-sfc": "^3.0.5", 27 | "sass": "^1.32.12", 28 | "vite": "2.3.7", 29 | "vite-plugin-mock": "2.8.0", 30 | "vue-tsc": "^0.0.24" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /js/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/js/public/favicon.ico -------------------------------------------------------------------------------- /js/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | 31 | -------------------------------------------------------------------------------- /js/src/api/table.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/system/request' 2 | 3 | // 获取数据api 4 | export function getData(data) { 5 | return request({ 6 | url: '/table/list', 7 | method: 'post', 8 | baseURL: '/mock', 9 | data 10 | }) 11 | } 12 | 13 | // 获取分类数据 14 | export function getCategory(data) { 15 | return request({ 16 | url: '/table/category', 17 | method: 'post', 18 | baseURL: '/mock', 19 | data 20 | }) 21 | } 22 | 23 | // 获取树组织数据 24 | export function getTree(data) { 25 | return request({ 26 | url: '/table/tree', 27 | method: 'post', 28 | baseURL: '/mock', 29 | data 30 | }) 31 | } 32 | 33 | // 新增 34 | export function add(data) { 35 | return request({ 36 | url: '/table/add', 37 | method: 'post', 38 | baseURL: '/mock', 39 | data 40 | }) 41 | } 42 | 43 | // 编辑 44 | export function update(data) { 45 | return request({ 46 | url: '/table/update', 47 | method: 'post', 48 | baseURL: '/mock', 49 | data 50 | }) 51 | } 52 | 53 | // 删除 54 | export function del(data) { 55 | return request({ 56 | url: '/table/del', 57 | method: 'post', 58 | baseURL: '/mock', 59 | data 60 | }) 61 | } -------------------------------------------------------------------------------- /js/src/api/user.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/system/request' 2 | 3 | // 登录api 4 | export function loginApi(data) { 5 | return request({ 6 | url: '/user/login', 7 | method: 'post', 8 | baseURL: '/mock', 9 | data 10 | }) 11 | } 12 | 13 | // 获取用户信息Api 14 | export function getInfoApi(data) { 15 | return request({ 16 | url: '/user/info', 17 | method: 'post', 18 | baseURL: '/mock', 19 | data 20 | }) 21 | } 22 | 23 | // 退出登录Api 24 | export function loginOutApi() { 25 | return request({ 26 | url: '/user/out', 27 | method: 'post', 28 | baseURL: '/mock' 29 | }) 30 | } 31 | 32 | // 获取用户信息Api 33 | export function passwordChange(data) { 34 | return request({ 35 | url: '/user/passwordChange', 36 | method: 'post', 37 | baseURL: '/mock', 38 | data 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /js/src/assets/images/401.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/js/src/assets/images/401.gif -------------------------------------------------------------------------------- /js/src/assets/images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/js/src/assets/images/404.png -------------------------------------------------------------------------------- /js/src/assets/images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/js/src/assets/images/404_cloud.png -------------------------------------------------------------------------------- /js/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/js/src/assets/logo.png -------------------------------------------------------------------------------- /js/src/assets/style/common.scss: -------------------------------------------------------------------------------- 1 | @import "./transition.scss"; 2 | @import "@/theme/index.scss"; 3 | .layout-container { 4 | background-color: var(--system-container-main-background); 5 | width: calc(100% - 30px); 6 | height: calc(100% - 30px); 7 | margin: 15px; 8 | display: flex; 9 | flex-direction: column; 10 | overflow-y: auto; 11 | &-form { 12 | display: flex; 13 | justify-content: space-between; 14 | padding: 15px 15px 0; 15 | &-handle { 16 | display: flex; 17 | justify-content: flex-start; 18 | } 19 | &-search { 20 | display: flex; 21 | justify-content: flex-end; 22 | .search-btn { 23 | margin-left: 15px; 24 | } 25 | } 26 | .el-form-item { 27 | margin-bottom: 0; 28 | } 29 | } 30 | &-table { 31 | flex: 1; 32 | height: 100%; 33 | padding: 15px; 34 | overflow: auto; 35 | } 36 | } 37 | .flex-box { 38 | display: flex; 39 | flex-direction: column; 40 | width: 100%; 41 | height: 100%; 42 | padding: 15px; 43 | box-sizing: border-box; 44 | } 45 | .flex { 46 | display: flex; 47 | } 48 | .center { 49 | justify-content: center; 50 | align-items: center; 51 | text-align: center; 52 | } 53 | a { 54 | text-decoration: none; 55 | } 56 | 57 | /** element-plus **/ 58 | .el-icon{ 59 | text-align: center; 60 | } 61 | 62 | /** 用于提示信息 **/ 63 | .my-tip { 64 | background-color: #f1f1f1; 65 | padding: 5px 10px; 66 | text-align: left; 67 | border-radius: 4px; 68 | } 69 | .system-scrollbar { 70 | &::-webkit-scrollbar { 71 | display: none; 72 | width: 6px; 73 | } 74 | &::-webkit-scrollbar-thumb { 75 | border-radius: 10px; 76 | background: rgba(144, 147, 153, 0.3); 77 | } 78 | &:hover { 79 | &::-webkit-scrollbar { 80 | display: block; 81 | } 82 | &::-webkit-scrollbar-thumb { 83 | border-radius: 10px; 84 | background: rgba(144, 147, 153, 0.3); 85 | &:hover { 86 | background: rgba(144, 147, 153, 0.5); 87 | } 88 | } 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /js/src/assets/style/transition.scss: -------------------------------------------------------------------------------- 1 | /* fade-transform */ 2 | .fade-transform-leave-active, 3 | .fade-transform-enter-active { 4 | transition: all .2s; 5 | } 6 | 7 | .fade-transform-enter-from { 8 | opacity: 0; 9 | transform: translateX(-30px); 10 | transition: all .2s; 11 | } 12 | 13 | .fade-transform-leave-to { 14 | opacity: 0; 15 | transform: translateX(30px); 16 | transition: all .2s; 17 | } 18 | 19 | /* breadcrumb transition */ 20 | .breadcrumb-enter-active, 21 | .breadcrumb-leave-active { 22 | transition: all .2s; 23 | } 24 | 25 | .breadcrumb-enter, 26 | .breadcrumb-leave-active { 27 | opacity: 0; 28 | transform: translateX(80px); 29 | } 30 | 31 | .breadcrumb-move { 32 | transition: all .5s; 33 | } 34 | 35 | .breadcrumb-leave-active { 36 | position: absolute; 37 | } -------------------------------------------------------------------------------- /js/src/components/layer/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 57 | 58 | -------------------------------------------------------------------------------- /js/src/components/menu/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /js/src/config/index.js: -------------------------------------------------------------------------------- 1 | const showLogo = true; // 是否显示Logo顶部模块 2 | const systemTitle = '后台管理系统' // 系统名称,用于显示在左上角模块,以及浏览器标题上使用,使用配置项 3 | export { 4 | systemTitle 5 | } -------------------------------------------------------------------------------- /js/src/layout/Header/Breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 50 | 51 | -------------------------------------------------------------------------------- /js/src/layout/Header/functionList/fullscreen.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | 32 | -------------------------------------------------------------------------------- /js/src/layout/Header/functionList/sizeChange.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 60 | 61 | -------------------------------------------------------------------------------- /js/src/layout/Header/functionList/theme/theme-color.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 48 | 49 | -------------------------------------------------------------------------------- /js/src/layout/Logo/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | 24 | -------------------------------------------------------------------------------- /js/src/layout/Menu/Link.vue: -------------------------------------------------------------------------------- 1 | 7 | 39 | -------------------------------------------------------------------------------- /js/src/layout/Tabs/tabsHook.js: -------------------------------------------------------------------------------- 1 | const tabsHook = { 2 | setItem: function(arr) { 3 | localStorage.setItem('tabs', JSON.stringify(arr)) 4 | }, 5 | getItem: function() { 6 | return JSON.parse(localStorage.getItem('tabs') || '[]') 7 | } 8 | } 9 | export default tabsHook 10 | -------------------------------------------------------------------------------- /js/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import ElementPlus from 'element-plus' 3 | import { baidu } from './utils/system/statistics' 4 | import 'element-plus/lib/theme-chalk/index.css' 5 | import 'element-plus/lib/theme-chalk/display.css' // 引入基于断点的隐藏类 6 | import 'normalize.css' // css初始化 7 | import './assets/style/common.scss' // 公共css 8 | import App from './App.vue' 9 | import store from './store' 10 | import router from './router' 11 | // if (import.meta.env.MODE !== 'development') { // 非开发环境调用百度统计 12 | // baidu() 13 | // } 14 | const app = createApp(App) 15 | app.use(ElementPlus, { size: store.state.app.elementSize }) 16 | app.use(store) 17 | app.use(router) 18 | // app.config.performance = true 19 | app.mount('#app') 20 | -------------------------------------------------------------------------------- /js/src/router/createNode.js: -------------------------------------------------------------------------------- 1 | // 1. 用于解决keep-alive需要name的问题,动态生成随机name供keep-alive使用 2 | // 2. 用于解决transition动画内部结点只能为根元素的问题,单文件可写多结点 3 | import { defineComponent, h, createVNode, ref, nextTick } from 'vue' 4 | import reload from './reload.vue' 5 | import NProgress from '@/utils/system/nprogress' 6 | 7 | export function createNameComponent(component) { 8 | return () => { 9 | return new Promise((res) => { 10 | component().then((comm) => { 11 | const name = (comm.default.name || 'vueAdminBox') + '$' + Date.now(); 12 | const tempComm = defineComponent({ 13 | name, 14 | setup() { 15 | const isReload = ref(false); 16 | let timeOut = null; 17 | const handleReload = () => { 18 | isReload.value = true; 19 | timeOut && clearTimeout(timeOut); 20 | NProgress.start(); 21 | timeOut = setTimeout(() => { 22 | nextTick(() => { 23 | NProgress.done(); 24 | isReload.value = false; 25 | }); 26 | }, 260); 27 | }; 28 | return { 29 | isReload, 30 | handleReload 31 | }; 32 | }, 33 | render: function () { 34 | if (this.isReload) { 35 | return h('div', { class: 'el-main-box' }, [h(reload)]); 36 | } else { 37 | return h('div', { class: 'el-main-box' }, [createVNode(comm.default)]); 38 | } 39 | } 40 | }); 41 | res(tempComm); 42 | }); 43 | }); 44 | }; 45 | } 46 | 47 | -------------------------------------------------------------------------------- /js/src/router/modules/dashboard.js: -------------------------------------------------------------------------------- 1 | import Layout from '@/layout/index.vue' 2 | import { createNameComponent } from '../createNode' 3 | const route = [ 4 | { 5 | path: '/', 6 | component: Layout, 7 | redirect: '/dashboard', 8 | meta: { title: 'dashboard', icon: 'el-icon-menu' }, 9 | children: [ 10 | { 11 | path: 'dashboard', 12 | component: createNameComponent(() => import('@/views/main/dashboard/index.vue')), 13 | meta: { title: '首页', icon: 'el-icon-menu', hideClose: true } 14 | } 15 | ] 16 | } 17 | ] 18 | 19 | export default route -------------------------------------------------------------------------------- /js/src/router/modules/pages.js: -------------------------------------------------------------------------------- 1 | import Layout from '@/layout/index.vue' 2 | import { createNameComponent } from '../createNode' 3 | const route = [ 4 | { 5 | path: '/pages', 6 | component: Layout, 7 | redirect: '/pages/crudTable', 8 | meta: { title: '页面', icon: 'el-icon-document-copy' }, 9 | alwayShow: true, 10 | children: [ 11 | { 12 | path: 'crudTable', 13 | component: createNameComponent(() => import('@/views/main/pages/crudTable/index.vue')), 14 | meta: { title: '业务表格', cache: false, roles: ['admin', 'editor'] } 15 | }, 16 | { 17 | path: 'categoryTable', 18 | component: createNameComponent(() => import('@/views/main/pages/categoryTable/index.vue')), 19 | meta: { title: '分类联动表格', cache: true, roles: ['admin'] } 20 | }, 21 | { 22 | path: 'treeTable', 23 | component: createNameComponent(() => import('@/views/main/pages/treeTable/index.vue')), 24 | meta: { title: '树联动表格', cache: true } 25 | } 26 | ] 27 | } 28 | ] 29 | 30 | export default route -------------------------------------------------------------------------------- /js/src/router/modules/system.js: -------------------------------------------------------------------------------- 1 | import Layout from '@/layout/index.vue' 2 | import { createNameComponent } from '../createNode' 3 | const route = [ 4 | { 5 | path: '/system', 6 | component: Layout, 7 | redirect: '/404', 8 | hideMenu: true, 9 | meta: { title: '系统目录' }, 10 | children: [ 11 | { 12 | path: '/404', 13 | component: createNameComponent(() => import('@/views/system/404.vue')), 14 | meta: { title: '404', hideTabs: true } 15 | }, 16 | { 17 | path: '/401', 18 | component: createNameComponent(() => import('@/views/system/401.vue')), 19 | meta: { title: '401', hideTabs: true } 20 | }, 21 | { 22 | path: '/redirect/:path(.*)', 23 | component: createNameComponent(() => import('@/views/system/redirect.vue')), 24 | meta: { title: 'redirect', hideTabs: true } 25 | } 26 | ] 27 | }, 28 | { 29 | path: '/login', 30 | component: createNameComponent(() => import('@/views/system/login.vue')), 31 | hideMenu: true, 32 | meta: { title: '登录', hideTabs: true } 33 | }, 34 | { 35 | // 找不到路由重定向到404页面 36 | path: "/:pathMatch(.*)", 37 | component: Layout, 38 | redirect: "/404", 39 | hideMenu: true 40 | }, 41 | ] 42 | 43 | export default route -------------------------------------------------------------------------------- /js/src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore, createLogger } from 'vuex' 2 | import Presistent from './plugins/persistent' 3 | const debug = process.env.NODE_ENV !== 'production' 4 | 5 | const files= import.meta.globEager('./modules/*.js') 6 | 7 | let modules = {} 8 | Object.keys(files).forEach((c) => { 9 | const module = files[c].default 10 | const moduleName = c.replace(/^\.\/(.*)\/(.*)\.\w+$/, '$2') 11 | modules[moduleName] = module 12 | }) 13 | 14 | const presistent = Presistent({ key: 'vuex', modules, modulesKeys: { 15 | local: Object.keys(modules), 16 | session: [] 17 | } }) 18 | 19 | export default createStore({ 20 | modules: { 21 | ...modules 22 | }, 23 | strict: debug, 24 | plugins: debug ? [createLogger(), presistent] : [presistent] 25 | }) -------------------------------------------------------------------------------- /js/src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | const state = () => ({ 2 | isCollapse: false, // 侧边栏是否收缩展示 3 | contentFullScreen: false, // 内容是否可全屏展示 4 | showLogo: true, // 是否显示Logo 5 | fixedTop: false, // 是否固定顶部, todo,暂未使用 6 | showTabs: true, // 是否显示导航历史 7 | expandOneMenu: true, // 一次是否只能展开一个菜单 8 | elementSize: 'mini', // element默认尺寸,支持官网四个大小参数 9 | theme: { 10 | state: { 11 | style: 'default', 12 | primaryColor: '#409eff', 13 | menuType: 'side' 14 | } 15 | } 16 | }) 17 | 18 | // mutations 19 | const mutations = { 20 | isCollapseChange(state, type) { 21 | state.isCollapse = type 22 | }, 23 | contentFullScreenChange(state, type) { 24 | state.contentFullScreen = type 25 | }, 26 | menuListChange(state, arr) { 27 | state.menuList = arr 28 | }, 29 | stateChange(state, option) { 30 | state[option.name] = option.value 31 | } 32 | } 33 | 34 | // actions 35 | const actions = { 36 | 37 | } 38 | 39 | export default { 40 | namespaced: true, 41 | state, 42 | actions, 43 | mutations 44 | } -------------------------------------------------------------------------------- /js/src/store/modules/keepAlive.js: -------------------------------------------------------------------------------- 1 | 2 | const state = () => ({ 3 | keepAliveComponentsName: [] // 需要缓存的组件名称 4 | }) 5 | 6 | // mutations 7 | const mutations = { 8 | // 重置,Push, splice keep-alive对象 9 | setKeepAliveComponentsName(state, nameArr) { 10 | state.keepAliveComponentsName = nameArr 11 | }, 12 | addKeepAliveComponentsName(state, name) { 13 | state.keepAliveComponentsName.push(name) 14 | }, 15 | delKeepAliveComponentsName(state, name) { 16 | const key = state.keepAliveComponentsName.indexOf(name) 17 | if (key !== -1) { 18 | state.keepAliveComponentsName.splice(key, 1) 19 | console.log(state.keepAliveComponentsName) 20 | } 21 | } 22 | } 23 | 24 | const getters = { 25 | keepAliveComponentsName(state) { 26 | return state.keepAliveComponentsName 27 | } 28 | } 29 | 30 | // actions 31 | const actions = { 32 | 33 | } 34 | 35 | export default { 36 | namespaced: true, 37 | state, 38 | getters, 39 | actions, 40 | mutations 41 | } -------------------------------------------------------------------------------- /js/src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import { loginApi, getInfoApi, loginOutApi } from '@/api/user' 2 | 3 | const state = () => ({ 4 | token: '', // 登录token 5 | info: {}, // 用户信息 6 | }) 7 | 8 | // getters 9 | const getters = { 10 | token(state) { 11 | return state.token 12 | } 13 | } 14 | 15 | // mutations 16 | const mutations = { 17 | tokenChange(state, token) { 18 | state.token = token 19 | }, 20 | infoChange(state, info) { 21 | state.info = info 22 | } 23 | } 24 | 25 | // actions 26 | const actions = { 27 | // login by login.vue 28 | login({ commit, dispatch }, params) { 29 | return new Promise((resolve, reject) => { 30 | loginApi(params) 31 | .then(res => { 32 | commit('tokenChange', res.data.token) 33 | dispatch('getInfo', { token: res.data.token }) 34 | .then(infoRes => { 35 | resolve(res.data.token) 36 | }) 37 | }) 38 | }) 39 | }, 40 | // get user info after user logined 41 | getInfo({ commit }, params) { 42 | return new Promise((resolve, reject) => { 43 | getInfoApi(params) 44 | .then(res => { 45 | commit('infoChange', res.data.info) 46 | resolve(res.data.info) 47 | }) 48 | }) 49 | }, 50 | 51 | // login out the system after user click the loginOut button 52 | loginOut({ commit }) { 53 | loginOutApi() 54 | .then(res => { 55 | 56 | }) 57 | .catch(error => { 58 | 59 | }) 60 | .finally(() => { 61 | localStorage.removeItem('tabs') 62 | localStorage.removeItem('vuex') 63 | location.reload() 64 | }) 65 | } 66 | } 67 | 68 | export default { 69 | namespaced: true, 70 | state, 71 | actions, 72 | getters, 73 | mutations 74 | } -------------------------------------------------------------------------------- /js/src/store/plugins/persistent.js: -------------------------------------------------------------------------------- 1 | const exclude = ['actions', 'getters', 'mutations', 'namespaced'] 2 | export default function Presistent({ key, modules, modulesKeys }) { 3 | return (store) => { 4 | const localOldState = JSON.parse(localStorage.getItem(key) || '{}') 5 | const sessionOldState = JSON.parse(sessionStorage.getItem(key) || '{}') 6 | let oldState = {} 7 | Object.assign(oldState, localOldState, sessionOldState) 8 | if (Object.keys(oldState).length > 0) { 9 | for (const oldKey in oldState) { 10 | modules[oldKey] = oldState[oldKey] 11 | } 12 | store.replaceState(modules) 13 | } 14 | store.subscribe((mutation, state) => { 15 | // 判断是否需要缓存数据至localStorage 16 | if (modulesKeys.local.length > 0) { 17 | const localData = setData(store.state, modulesKeys.local) 18 | localStorage.setItem(key, JSON.stringify(localData)) 19 | } else { 20 | localStorage.removeItem(key) 21 | } 22 | // 判断是否需要缓存数据至sessionStorage 23 | if (modulesKeys.session.length > 0) { 24 | const sessionData = setData(store.state, modulesKeys.session) 25 | sessionStorage.setItem(key, JSON.stringify(sessionData)) 26 | } else { 27 | sessionStorage.removeItem(key) 28 | } 29 | }) 30 | } 31 | } 32 | 33 | function setData(state, module) { 34 | let data = {} 35 | for (const i of module) { 36 | data[i] = state[i] 37 | } 38 | return data 39 | } -------------------------------------------------------------------------------- /js/src/theme/index.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | // 主题色 3 | --system-primary-color: #409eff; // 可做背景色和文本色,用做背景色时,需要和--system-primary-text-color配合使用,避免文件颜色和主题色冲突 4 | --system-primary-text-color: #fff; // 主题色作为背景色时使用 5 | 6 | // logo颜色相关 7 | --system-logo-color: #f1f1f1; 8 | --system-logo-background: #263445; 9 | 10 | // 菜单颜色相关 11 | --system-menu-text-color: #bfcbd9; 12 | --system-menu-background: #28415a; 13 | --system-menu-children-background: #1f2d3d; 14 | --system-menu-submenu-active-color: #fff; 15 | --system-menu-hover-background: #203448; 16 | 17 | // header区域 18 | --system-header-background: #fff; 19 | --system-header-text-color: #bbb; 20 | --system-header-breadcrumb-text-color: #97a8be; 21 | --system-header-item-hover-color: #000; 22 | --system-header-border-color: #d8dce5; 23 | --system-header-tab-background: #fff; 24 | 25 | // contaier区域,父框架 26 | --system-container-background: #f0f2f5; 27 | --system-container-main-background: #fff; 28 | 29 | // 页面区域, 这一块是你在自己写的文件中使用主题,核心需要关注的地方 30 | --system-page-background: #fff; // 主背景 31 | --system-page-color: #303133; // 主要的文本颜色 32 | --system-page-tip-color: rgba(0, 0, 0, 0.45); // 协助展示的文本颜色 33 | --system-page-border-color: #000; // 通用的边框配置色,便于主题扩展 34 | 35 | // element主题色修改 36 | --el-color-primary: var(--system-primary-color); 37 | } 38 | 39 | // 进度条颜色修改为主题色 40 | body #nprogress .bar { 41 | background-color: var(--system-primary-color); 42 | } 43 | body #nprogress .peg { 44 | box-shadow: 0 0 10px var(--system-primary-color), 0 0 5px var(--system-primary-color); 45 | } 46 | body #nprogress .spinner-icon { 47 | border-top-color: var(--system-primary-color); 48 | border-left-color: var(--system-primary-color); 49 | } 50 | 51 | @import './modules/dark.scss'; 52 | -------------------------------------------------------------------------------- /js/src/theme/modules/dark.scss: -------------------------------------------------------------------------------- 1 | .dark { 2 | // 通用 3 | p, h1, h2, h3, h4, h5, h6, article { 4 | color: var(--system-page-color); 5 | } 6 | .el-tree { 7 | background-color: var(--system-page-background); 8 | .el-tree-node__content:hover { 9 | background-color: #272727; 10 | } 11 | --el-color-primary-light-9: #272727; 12 | } 13 | .el-card { 14 | background-color: var(--system-page-background); 15 | color: var(--system-page-color); 16 | border-color: var(--system-page-border-color); 17 | .el-card__header { 18 | border-color: var(--system-page-border-color); 19 | } 20 | } 21 | // 页面内部样式修改 22 | 23 | } -------------------------------------------------------------------------------- /js/src/utils/system/nprogress.js: -------------------------------------------------------------------------------- 1 | import NProgress from "nprogress" 2 | import "nprogress/nprogress.css" 3 | 4 | NProgress.configure({ 5 | easing: 'ease', // 动画方式 6 | speed: 500, // 递增进度条的速度 7 | showSpinner: true, // 是否显示加载ico 8 | trickleSpeed: 200, // 自动递增间隔 9 | minimum: 0.3 // 初始化时的最小百分比 10 | }) 11 | 12 | export default NProgress -------------------------------------------------------------------------------- /js/src/utils/system/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import store from '@/store' 3 | import { ElMessage } from 'element-plus' 4 | const baseURL = import.meta.env.VITE_BASE_URL 5 | 6 | const service = axios.create({ 7 | baseURL: baseURL, 8 | timeout: 5000 9 | }) 10 | 11 | // 请求前的统一处理 12 | service.interceptors.request.use( 13 | (config) => { 14 | // JWT鉴权处理 15 | if (store.getters['user/token']) { 16 | config.headers['token'] = store.state.user.token 17 | } 18 | return config 19 | }, 20 | (error) => { 21 | console.log(error) // for debug 22 | return Promise.reject(error) 23 | } 24 | ) 25 | 26 | service.interceptors.response.use( 27 | (response) => { 28 | const res = response.data 29 | if (res.code === 200) { 30 | return res 31 | } else { 32 | showError(res) 33 | return Promise.reject(res) 34 | } 35 | }, 36 | (error)=> { 37 | console.log(error) // for debug 38 | const badMessage = error.message || error 39 | const code = parseInt(badMessage.toString().replace('Error: Request failed with status code ', '')) 40 | showError({ code, message: badMessage }) 41 | return Promise.reject(error) 42 | } 43 | ) 44 | 45 | function showError(error) { 46 | if (error.code === 403) { 47 | // to re-login 48 | store.dispatch('user/loginOut') 49 | } else { 50 | ElMessage({ 51 | message: error.msg || error.message || '服务异常', 52 | type: 'error', 53 | duration: 3 * 1000 54 | }) 55 | } 56 | 57 | } 58 | 59 | export default service -------------------------------------------------------------------------------- /js/src/utils/system/statistics.js: -------------------------------------------------------------------------------- 1 | // 百度统计代码,需自行更换 2 | export function baidu() { 3 | const script = document.createElement('script') 4 | script.type = 'text/javascript' 5 | script.text = ` 6 | var _hmt = _hmt || []; 7 | (function() { 8 | var hm = document.createElement("script"); 9 | hm.src = "https://hm.baidu.com/hm.js?bd78bc908e66174e7dde385bf37cb4c1"; 10 | var s = document.getElementsByTagName("script")[0]; 11 | s.parentNode.insertBefore(hm, s); 12 | })(); 13 | ` 14 | document.getElementsByTagName('head')[0].appendChild(script) 15 | } -------------------------------------------------------------------------------- /js/src/utils/system/title.js: -------------------------------------------------------------------------------- 1 | import { systemTitle } from '@/config' 2 | 3 | export function changeTitle(name) { 4 | document.title = `${name}-${systemTitle}` 5 | } 6 | -------------------------------------------------------------------------------- /js/src/views/main/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /js/src/views/main/pages/categoryTable/enum.js: -------------------------------------------------------------------------------- 1 | export const selectData = [ 2 | { value: 1, label: '运动' }, 3 | { value: 2, label: '健身' }, 4 | { value: 3, label: '跑酷' }, 5 | { value: 4, label: '街舞' } 6 | ] 7 | 8 | export const radioData = [ 9 | { value: 1, label: '今天' }, 10 | { value: 2, label: '明天' }, 11 | { value: 3, label: '后天' }, 12 | ] -------------------------------------------------------------------------------- /js/src/views/main/pages/categoryTable/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 28 | 29 | -------------------------------------------------------------------------------- /js/src/views/main/pages/crudTable/enum.js: -------------------------------------------------------------------------------- 1 | export const selectData = [ 2 | { value: 1, label: '运动' }, 3 | { value: 2, label: '健身' }, 4 | { value: 3, label: '跑酷' }, 5 | { value: 4, label: '街舞' } 6 | ] 7 | 8 | export const radioData = [ 9 | { value: 1, label: '今天' }, 10 | { value: 2, label: '明天' }, 11 | { value: 3, label: '后天' }, 12 | ] -------------------------------------------------------------------------------- /js/src/views/main/pages/treeTable/enum.js: -------------------------------------------------------------------------------- 1 | export const selectData = [ 2 | { value: 1, label: '运动' }, 3 | { value: 2, label: '健身' }, 4 | { value: 3, label: '跑酷' }, 5 | { value: 4, label: '街舞' } 6 | ] 7 | 8 | export const radioData = [ 9 | { value: 1, label: '今天' }, 10 | { value: 2, label: '明天' }, 11 | { value: 3, label: '后天' }, 12 | ] -------------------------------------------------------------------------------- /js/src/views/main/pages/treeTable/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 29 | 30 | -------------------------------------------------------------------------------- /js/src/views/system/redirect.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /js/vite.config.js: -------------------------------------------------------------------------------- 1 | import vue from '@vitejs/plugin-vue' 2 | import { viteMockServe } from 'vite-plugin-mock' 3 | import { resolve } from 'path' 4 | 5 | const pathResolve = (dir) => { 6 | return resolve(__dirname, ".", dir) 7 | } 8 | 9 | const alias = { 10 | '@': pathResolve("src") 11 | } 12 | 13 | // https://vitejs.dev/config/ 14 | export default ({ command }) => { 15 | const prodMock = true; 16 | return { 17 | base: './', 18 | resolve: { 19 | alias 20 | }, 21 | server: { 22 | port: 3002, 23 | host: '0.0.0.0', 24 | open: true, 25 | proxy: { // 代理配置 26 | '/dev': 'https://www.fastmock.site/mock/48cab8545e64d93ff9ba66a87ad04f6b/' 27 | }, 28 | }, 29 | build: { 30 | rollupOptions: { 31 | output: { 32 | manualChunks: { 33 | 34 | } 35 | } 36 | } 37 | }, 38 | plugins: [ 39 | vue(), 40 | viteMockServe({ 41 | mockPath: 'mock', 42 | localEnabled: command === 'serve', 43 | prodEnabled: command !== 'serve' && prodMock, 44 | watchFiles: true, 45 | injectCode: ` 46 | import { setupProdMockServer } from '../mockProdServer'; 47 | setupProdMockServer(); 48 | `, 49 | logger: true, 50 | }), 51 | ] 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /ts-i18n/.env.development: -------------------------------------------------------------------------------- 1 | ENV = 'development' 2 | 3 | VITE_BASE_URL = '/dev-api' -------------------------------------------------------------------------------- /ts-i18n/.env.production: -------------------------------------------------------------------------------- 1 | ENV = 'production' 2 | 3 | VITE_BASE_URL = '/pro-api' -------------------------------------------------------------------------------- /ts-i18n/.env.staging: -------------------------------------------------------------------------------- 1 | ENV = 'staging' 2 | 3 | VITE_BASE_URL = '/test-api' -------------------------------------------------------------------------------- /ts-i18n/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | yarn.lock 7 | yarn-error.log 8 | .vscode 9 | -------------------------------------------------------------------------------- /ts-i18n/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 罗茜 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. -------------------------------------------------------------------------------- /ts-i18n/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ts-i18n/mock/user.ts: -------------------------------------------------------------------------------- 1 | import { MockMethod } from 'vite-plugin-mock' 2 | const users = [ 3 | { name: 'admin', password: '123456', token: 'admin', info: { 4 | name: '系统管理员' 5 | }}, 6 | { name: 'editor', password: '123456', token: 'editor', info: { 7 | name: '编辑人员' 8 | }}, 9 | { name: 'test', password: '123456', token: 'test', info: { 10 | name: '测试人员' 11 | }}, 12 | ] 13 | export default [ 14 | { 15 | url: `/mock/user/login`, 16 | method: 'post', 17 | response: ({ body }) => { 18 | const user = users.find(user => { 19 | return body.name === user.name && body.password === user.password 20 | }) 21 | if (user) { 22 | return { 23 | code: 200, 24 | data: { 25 | token: user.token, 26 | }, 27 | }; 28 | } else { 29 | return { 30 | code: 401, 31 | data: {}, 32 | msg: '用户名或密码错误' 33 | }; 34 | } 35 | 36 | } 37 | }, 38 | { 39 | url: `/mock/user/info`, 40 | method: 'post', 41 | response: ({ body }) => { 42 | const { token } = body 43 | const info = users.find(user => { 44 | return user.token === token 45 | }).info 46 | if (info) { 47 | return { 48 | code: 200, 49 | data: { 50 | info: info 51 | }, 52 | }; 53 | } else { 54 | return { 55 | code: 403, 56 | data: {}, 57 | msg: '无访问权限' 58 | }; 59 | } 60 | 61 | } 62 | }, 63 | { 64 | url: `/mock/user/out`, 65 | method: 'post', 66 | response: () => { 67 | return { 68 | code: 200, 69 | data: {}, 70 | msg: 'success' 71 | }; 72 | } 73 | }, 74 | { 75 | url: `/mock/user/passwordChange`, 76 | method: 'post', 77 | response: () => { 78 | return { 79 | code: 200, 80 | data: {}, 81 | msg: 'success' 82 | }; 83 | } 84 | }, 85 | ] as MockMethod[] -------------------------------------------------------------------------------- /ts-i18n/mockProdServer.ts: -------------------------------------------------------------------------------- 1 | import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'; 2 | import userModule from './mock/user' 3 | import tableModule from './mock/table' 4 | 5 | export function setupProdMockServer() { 6 | createProdMockServer([ 7 | ...userModule, 8 | ...tableModule 9 | ]); 10 | } 11 | -------------------------------------------------------------------------------- /ts-i18n/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "init", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "start": "vite", 7 | "build": "vite build --mode=production", 8 | "build:stag": "vite build --mode=staging", 9 | "serve": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@vueuse/core": "^4.10.0", 13 | "axios": "^0.21.1", 14 | "element-plus": "^1.0.2-beta.48", 15 | "mockjs": "^1.1.0", 16 | "normalize.css": "^8.0.1", 17 | "nprogress": "^0.2.0", 18 | "throttle-debounce": "^3.0.1", 19 | "vue": "^3.1.2", 20 | "vue-i18n": "^9.1.6", 21 | "vue-router": "4", 22 | "vuex": "^4.0.0" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "^15.0.3", 26 | "@vitejs/plugin-vue": "^1.2.2", 27 | "@vue/compiler-sfc": "^3.0.5", 28 | "sass": "^1.32.12", 29 | "typescript": "^4.1.3", 30 | "vite": "2.3.7", 31 | "vite-plugin-mock": "2.8.0", 32 | "vue-tsc": "^0.0.24" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ts-i18n/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/ts-i18n/public/favicon.ico -------------------------------------------------------------------------------- /ts-i18n/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 26 | 27 | 38 | -------------------------------------------------------------------------------- /ts-i18n/src/api/table.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/system/request' 2 | 3 | // 获取数据api 4 | export function getData(data: object) { 5 | return request({ 6 | url: '/table/list', 7 | method: 'post', 8 | baseURL: '/mock', 9 | data 10 | }) 11 | } 12 | 13 | // 获取分类数据 14 | export function getCategory(data: object) { 15 | return request({ 16 | url: '/table/category', 17 | method: 'post', 18 | baseURL: '/mock', 19 | data 20 | }) 21 | } 22 | 23 | // 获取树组织数据 24 | export function getTree(data: object) { 25 | return request({ 26 | url: '/table/tree', 27 | method: 'post', 28 | baseURL: '/mock', 29 | data 30 | }) 31 | } 32 | 33 | // 新增 34 | export function add(data: object) { 35 | return request({ 36 | url: '/table/add', 37 | method: 'post', 38 | baseURL: '/mock', 39 | data 40 | }) 41 | } 42 | 43 | // 编辑 44 | export function update(data: object) { 45 | return request({ 46 | url: '/table/update', 47 | method: 'post', 48 | baseURL: '/mock', 49 | data 50 | }) 51 | } 52 | 53 | // 删除 54 | export function del(data: object) { 55 | return request({ 56 | url: '/table/del', 57 | method: 'post', 58 | baseURL: '/mock', 59 | data 60 | }) 61 | } -------------------------------------------------------------------------------- /ts-i18n/src/api/user.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/system/request' 2 | 3 | // 登录api 4 | export function loginApi(data: object) { 5 | return request({ 6 | url: '/user/login', 7 | method: 'post', 8 | baseURL: '/mock', 9 | data 10 | }) 11 | } 12 | 13 | // 获取用户信息Api 14 | export function getInfoApi(data: object) { 15 | return request({ 16 | url: '/user/info', 17 | method: 'post', 18 | baseURL: '/mock', 19 | data 20 | }) 21 | } 22 | 23 | // 退出登录Api 24 | export function loginOutApi() { 25 | return request({ 26 | url: '/user/out', 27 | method: 'post', 28 | baseURL: '/mock' 29 | }) 30 | } 31 | 32 | // 获取用户信息Api 33 | export function passwordChange(data: object) { 34 | return request({ 35 | url: '/user/passwordChange', 36 | method: 'post', 37 | baseURL: '/mock', 38 | data 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /ts-i18n/src/assets/images/401.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/ts-i18n/src/assets/images/401.gif -------------------------------------------------------------------------------- /ts-i18n/src/assets/images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/ts-i18n/src/assets/images/404.png -------------------------------------------------------------------------------- /ts-i18n/src/assets/images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/ts-i18n/src/assets/images/404_cloud.png -------------------------------------------------------------------------------- /ts-i18n/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/ts-i18n/src/assets/logo.png -------------------------------------------------------------------------------- /ts-i18n/src/assets/style/common.scss: -------------------------------------------------------------------------------- 1 | @import "./transition.scss"; 2 | @import "@/theme/index.scss"; 3 | .layout-container { 4 | background-color: var(--system-container-main-background); 5 | width: calc(100% - 30px); 6 | height: calc(100% - 30px); 7 | margin: 15px; 8 | display: flex; 9 | flex-direction: column; 10 | overflow-y: auto; 11 | &-form { 12 | display: flex; 13 | justify-content: space-between; 14 | padding: 15px 15px 0; 15 | &-handle { 16 | display: flex; 17 | justify-content: flex-start; 18 | } 19 | &-search { 20 | display: flex; 21 | justify-content: flex-end; 22 | .search-btn { 23 | margin-left: 15px; 24 | } 25 | } 26 | .el-form-item { 27 | margin-bottom: 0; 28 | } 29 | } 30 | &-table { 31 | flex: 1; 32 | height: 100%; 33 | padding: 15px; 34 | overflow: auto; 35 | } 36 | } 37 | .flex-box { 38 | display: flex; 39 | flex-direction: column; 40 | width: 100%; 41 | height: 100%; 42 | padding: 15px; 43 | box-sizing: border-box; 44 | } 45 | .flex { 46 | display: flex; 47 | } 48 | .center { 49 | justify-content: center; 50 | align-items: center; 51 | text-align: center; 52 | } 53 | a { 54 | text-decoration: none; 55 | } 56 | 57 | /** element-plus **/ 58 | .el-icon{ 59 | text-align: center; 60 | } 61 | 62 | /** 用于提示信息 **/ 63 | .my-tip { 64 | background-color: #f1f1f1; 65 | padding: 5px 10px; 66 | text-align: left; 67 | border-radius: 4px; 68 | } 69 | .system-scrollbar { 70 | &::-webkit-scrollbar { 71 | display: none; 72 | width: 6px; 73 | } 74 | &::-webkit-scrollbar-thumb { 75 | border-radius: 10px; 76 | background: rgba(144, 147, 153, 0.3); 77 | } 78 | &:hover { 79 | &::-webkit-scrollbar { 80 | display: block; 81 | } 82 | &::-webkit-scrollbar-thumb { 83 | border-radius: 10px; 84 | background: rgba(144, 147, 153, 0.3); 85 | &:hover { 86 | background: rgba(144, 147, 153, 0.5); 87 | } 88 | } 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /ts-i18n/src/assets/style/transition.scss: -------------------------------------------------------------------------------- 1 | /* fade-transform */ 2 | .fade-transform-leave-active, 3 | .fade-transform-enter-active { 4 | transition: all .2s; 5 | } 6 | 7 | .fade-transform-enter-from { 8 | opacity: 0; 9 | transform: translateX(-30px); 10 | transition: all .2s; 11 | } 12 | 13 | .fade-transform-leave-to { 14 | opacity: 0; 15 | transform: translateX(30px); 16 | transition: all .2s; 17 | } 18 | 19 | /* breadcrumb transition */ 20 | .breadcrumb-enter-active, 21 | .breadcrumb-leave-active { 22 | transition: all .2s; 23 | } 24 | 25 | .breadcrumb-enter, 26 | .breadcrumb-leave-active { 27 | opacity: 0; 28 | transform: translateX(80px); 29 | } 30 | 31 | .breadcrumb-move { 32 | transition: all .5s; 33 | } 34 | 35 | .breadcrumb-leave-active { 36 | position: absolute; 37 | } -------------------------------------------------------------------------------- /ts-i18n/src/components/layer/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 72 | 73 | -------------------------------------------------------------------------------- /ts-i18n/src/components/menu/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /ts-i18n/src/components/table/type.ts: -------------------------------------------------------------------------------- 1 | export interface Page{ 2 | index: Number, 3 | size: Number, 4 | total: Number 5 | } -------------------------------------------------------------------------------- /ts-i18n/src/config/index.ts: -------------------------------------------------------------------------------- 1 | const showLogo: Boolean = true; // 是否显示Logo顶部模块 2 | const systemTitle = 'message.system.title' // 系统名称,用于显示在左上角模块,以及浏览器标题上使用,使用配置项 3 | export { 4 | systemTitle 5 | } -------------------------------------------------------------------------------- /ts-i18n/src/layout/Header/Breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 50 | 51 | -------------------------------------------------------------------------------- /ts-i18n/src/layout/Header/functionList/fullscreen.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | 32 | -------------------------------------------------------------------------------- /ts-i18n/src/layout/Header/functionList/sizeChange.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 60 | 61 | -------------------------------------------------------------------------------- /ts-i18n/src/layout/Header/functionList/theme/theme-color.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 48 | 49 | -------------------------------------------------------------------------------- /ts-i18n/src/layout/Header/functionList/word.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 46 | 47 | -------------------------------------------------------------------------------- /ts-i18n/src/layout/Logo/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | 24 | -------------------------------------------------------------------------------- /ts-i18n/src/layout/Menu/Link.vue: -------------------------------------------------------------------------------- 1 | 7 | 39 | -------------------------------------------------------------------------------- /ts-i18n/src/layout/Tabs/tabsHook.ts: -------------------------------------------------------------------------------- 1 | const tabsHook = { 2 | setItem: function(arr: object[]) { 3 | localStorage.setItem('tabs', JSON.stringify(arr)) 4 | }, 5 | getItem: function() { 6 | return JSON.parse(localStorage.getItem('tabs') || '[]') 7 | } 8 | } 9 | export default tabsHook 10 | -------------------------------------------------------------------------------- /ts-i18n/src/locale/index.ts: -------------------------------------------------------------------------------- 1 | // 提示信息仅在开发环境生效 2 | import { createI18n, LocaleMessages, VueMessageType } from 'vue-i18n' 3 | import store from '@/store' 4 | 5 | const files= import.meta.globEager('./modules/*.ts') 6 | 7 | let messages: LocaleMessages = {} 8 | Object.keys(files).forEach((c: string) => { 9 | const module = files[c].default 10 | const moduleName: string = c.replace(/^\.\/(.*)\/(.*)\.\w+$/, '$2') 11 | messages[moduleName] = module 12 | }) 13 | 14 | const lang = store.state.app.lang || navigator.userLanguage || navigator.language // 初次进入,采用浏览器当前设置的语言,默认采用中文 15 | const locale = lang.indexOf('en') !== -1 ? 'en' : 'zh-cn' 16 | 17 | const i18n = createI18n({ 18 | __VUE_I18N_LEGACY_API__: false, 19 | __VUE_I18N_FULL_INSTALL__: false, 20 | locale: locale, 21 | fallbackLocale: 'zh-cn', 22 | messages 23 | }) 24 | 25 | document.querySelector('html')!.setAttribute('lang', locale) 26 | 27 | export default i18n -------------------------------------------------------------------------------- /ts-i18n/src/locale/modules/en.ts: -------------------------------------------------------------------------------- 1 | import enLocale from 'element-plus/lib/locale/lang/en' 2 | import system from './en/system' 3 | import common from './en/common' 4 | import menu from './en/menu' 5 | const lang = { 6 | el: enLocale.el, // element-plus i18 setting 7 | message: { 8 | language: 'English', 9 | ...system, 10 | ...common, 11 | ...menu 12 | } 13 | } 14 | 15 | export default lang -------------------------------------------------------------------------------- /ts-i18n/src/locale/modules/en/common.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | common: { 3 | search: 'search', 4 | searchTip: 'please input keyword', 5 | add: 'add', 6 | update: 'update', 7 | del: 'delete', 8 | delBat: 'delete choose', 9 | delTip: 'Are you sure delete the selection data ?', 10 | handle: 'handle' 11 | }, 12 | } -------------------------------------------------------------------------------- /ts-i18n/src/locale/modules/en/menu.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | menu: { 3 | dashboard: { 4 | name: 'dashboard', 5 | index: 'index' 6 | }, 7 | system: { 8 | name: 'system', 9 | redirect: 'redirect', 10 | '404': '404', 11 | '401': '401' 12 | }, 13 | page: { 14 | name: 'page', 15 | crudTable: 'crudTable', 16 | categoryTable: 'categoryTable', 17 | treeTable: 'treeTable', 18 | }, 19 | }, 20 | } -------------------------------------------------------------------------------- /ts-i18n/src/locale/modules/en/system.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | system: { 3 | title: 'backendsystem', 4 | login: 'login', 5 | userName: 'userName', 6 | password: 'password', 7 | contentScreen: 'content full screen', 8 | fullScreen: 'fullscreen', 9 | fullScreenBack: 'back fullscreen', 10 | github: 'visit github', 11 | changePassword: 'change password', 12 | loginOut: 'login out', 13 | user: 'admin', 14 | size: { 15 | default: 'default', 16 | medium: 'medium', 17 | small: 'small', 18 | mini: 'mini' 19 | }, 20 | setting: { 21 | name: 'setting', 22 | 23 | style: { 24 | name: 'full style setting', 25 | default: 'default menu style', 26 | light: 'light menu style', 27 | dark: 'dark menu style' 28 | }, 29 | primaryColor: { 30 | name: 'primary color', 31 | blue: 'default blue', 32 | red: 'rose red', 33 | violet: 'grace violet', 34 | green: 'story green', 35 | cyan: 'cyan', 36 | black: 'geek black' 37 | }, 38 | other: { 39 | name: 'other setting', 40 | showLogo: 'show logo', 41 | showBreadcrumb: 'show breadcrumb', 42 | keepOnlyOneMenu: 'keep only one menu open', 43 | } 44 | }, 45 | tab: { 46 | reload: 'refresh', 47 | closeAll: 'close all tags', 48 | closeOther: 'close other tags', 49 | closeCurrent: 'close current tag' 50 | } 51 | }, 52 | } -------------------------------------------------------------------------------- /ts-i18n/src/locale/modules/zh-cn.ts: -------------------------------------------------------------------------------- 1 | import zhLocale from 'element-plus/lib/locale/lang/zh-cn' 2 | import system from './zh-cn/system' 3 | import common from './zh-cn/common' 4 | import menu from './zh-cn/menu' 5 | const lang = { 6 | el: zhLocale.el, // element内部国际化 7 | message: { 8 | language: '中文', 9 | ...system, 10 | ...common, 11 | ...menu 12 | } 13 | } 14 | 15 | export default lang -------------------------------------------------------------------------------- /ts-i18n/src/locale/modules/zh-cn/common.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | common: { 3 | search: '搜索', 4 | searchTip: '请输入关键词进行检索', 5 | add: '新增', 6 | update: '编辑', 7 | del: '删除', 8 | delBat: '批量删除', 9 | delTip: '确定删除选中的数据吗?', 10 | handle: '操作' 11 | }, 12 | } -------------------------------------------------------------------------------- /ts-i18n/src/locale/modules/zh-cn/menu.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | menu: { 3 | dashboard: { 4 | name: 'dashboard', 5 | index: '首页' 6 | }, 7 | system: { 8 | name: '系统目录', 9 | redirect: '重定向页面', 10 | '404': '404', 11 | '401': '401' 12 | }, 13 | page: { 14 | name: '页面', 15 | crudTable: '业务表格', 16 | categoryTable: '分类联动表格', 17 | treeTable: '树联动表格' 18 | } 19 | }, 20 | } -------------------------------------------------------------------------------- /ts-i18n/src/locale/modules/zh-cn/system.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | system: { 3 | title: '后台管理系统', 4 | login: '登录', 5 | userName: '用户名', 6 | password: '密码', 7 | contentScreen: '内容全屏', 8 | fullScreen: '全屏', 9 | fullScreenBack: '退出全屏', 10 | github: '访问github地址', 11 | changePassword: '修改密码', 12 | loginOut: '退出登录', 13 | user: '管理员', 14 | size: { 15 | default: '默认', 16 | medium: '中', 17 | small: '小', 18 | mini: '迷你' 19 | }, 20 | setting: { 21 | name: '系统设置', 22 | 23 | style: { 24 | name: '整体风格设置', 25 | default: '默认菜单风格', 26 | light: '亮色菜单风格', 27 | dark: '暗色菜单风格' 28 | }, 29 | primaryColor: { 30 | name: '主题色', 31 | blue: '默认蓝', 32 | red: '玫瑰红', 33 | violet: '优雅紫', 34 | green: '故事绿', 35 | cyan: '明青', 36 | black: '极客黑' 37 | }, 38 | other: { 39 | name: '其他设置', 40 | showLogo: '显示logo', 41 | showBreadcrumb: '显示面包屑导航', 42 | keepOnlyOneMenu: '保持一个菜单展开', 43 | } 44 | }, 45 | tab: { 46 | reload: '重新加载', 47 | closeAll: '关闭所有标签', 48 | closeOther: '关闭其他标签', 49 | closeCurrent: '关闭当前标签' 50 | } 51 | }, 52 | } -------------------------------------------------------------------------------- /ts-i18n/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import ElementPlus from 'element-plus' 3 | import { baidu } from './utils/system/statistics' 4 | import 'element-plus/lib/theme-chalk/index.css' 5 | import 'element-plus/lib/theme-chalk/display.css' // 引入基于断点的隐藏类 6 | import 'normalize.css' // css初始化 7 | import './assets/style/common.scss' // 公共css 8 | import App from './App.vue' 9 | import store from './store' 10 | import router from './router' 11 | import i18n from './locale' 12 | // if (import.meta.env.MODE !== 'development') { // 非开发环境调用百度统计 13 | // baidu() 14 | // } 15 | const app = createApp(App) 16 | app.use(ElementPlus, { size: store.state.app.elementSize }) 17 | app.use(store) 18 | app.use(router) 19 | app.use(i18n) 20 | // app.config.performance = true 21 | app.mount('#app') 22 | -------------------------------------------------------------------------------- /ts-i18n/src/router/createNode.ts: -------------------------------------------------------------------------------- 1 | // 1. 用于解决keep-alive需要name的问题,动态生成随机name供keep-alive使用 2 | // 2. 用于解决transition动画内部结点只能为根元素的问题,单文件可写多结点 3 | import { defineComponent, h, createVNode, ref, nextTick } from 'vue' 4 | import reload from './reload.vue' 5 | import NProgress from '@/utils/system/nprogress' 6 | 7 | export function createNameComponent(component: any) { 8 | return () => { 9 | return new Promise((res) => { 10 | component().then((comm: any) => { 11 | const name = (comm.default.name || 'vueAdminBox') + '$' + Date.now(); 12 | const tempComm = defineComponent({ 13 | name, 14 | setup() { 15 | const isReload = ref(false); 16 | let timeOut: any = null; 17 | const handleReload = () => { 18 | isReload.value = true; 19 | timeOut && clearTimeout(timeOut); 20 | NProgress.start(); 21 | timeOut = setTimeout(() => { 22 | nextTick(() => { 23 | NProgress.done(); 24 | isReload.value = false; 25 | }); 26 | }, 260); 27 | }; 28 | return { 29 | isReload, 30 | handleReload 31 | }; 32 | }, 33 | render: function () { 34 | if (this.isReload) { 35 | return h('div', { class: 'el-main-box' }, [h(reload)]); 36 | } else { 37 | return h('div', { class: 'el-main-box' }, [createVNode(comm.default)]); 38 | } 39 | } 40 | }); 41 | res(tempComm); 42 | }); 43 | }); 44 | }; 45 | } 46 | 47 | -------------------------------------------------------------------------------- /ts-i18n/src/router/modules/dashboard.ts: -------------------------------------------------------------------------------- 1 | import Layout from '@/layout/index.vue' 2 | import { createNameComponent } from '../createNode' 3 | const route = [ 4 | { 5 | path: '/', 6 | component: Layout, 7 | redirect: '/dashboard', 8 | meta: { title: 'message.menu.dashboard.name', icon: 'el-icon-menu' }, 9 | children: [ 10 | { 11 | path: 'dashboard', 12 | component: createNameComponent(() => import('@/views/main/dashboard/index.vue')), 13 | meta: { title: 'message.menu.dashboard.index', icon: 'el-icon-menu', hideClose: true } 14 | } 15 | ] 16 | } 17 | ] 18 | 19 | export default route -------------------------------------------------------------------------------- /ts-i18n/src/router/modules/pages.ts: -------------------------------------------------------------------------------- 1 | import Layout from '@/layout/index.vue' 2 | import { createNameComponent } from '../createNode' 3 | const route = [ 4 | { 5 | path: '/pages', 6 | component: Layout, 7 | redirect: '/pages/crudTable', 8 | meta: { title: 'message.menu.page.name', icon: 'el-icon-document-copy' }, 9 | alwayShow: true, 10 | children: [ 11 | { 12 | path: 'crudTable', 13 | component: createNameComponent(() => import('@/views/main/pages/crudTable/index.vue')), 14 | meta: { title: 'message.menu.page.crudTable', cache: false, roles: ['admin', 'editor'] } 15 | }, 16 | { 17 | path: 'categoryTable', 18 | component: createNameComponent(() => import('@/views/main/pages/categoryTable/index.vue')), 19 | meta: { title: 'message.menu.page.categoryTable', cache: true, roles: ['admin'] } 20 | }, 21 | { 22 | path: 'treeTable', 23 | component: createNameComponent(() => import('@/views/main/pages/treeTable/index.vue')), 24 | meta: { title: 'message.menu.page.treeTable', cache: true } 25 | } 26 | ] 27 | } 28 | ] 29 | 30 | export default route -------------------------------------------------------------------------------- /ts-i18n/src/router/modules/system.ts: -------------------------------------------------------------------------------- 1 | import Layout from '@/layout/index.vue' 2 | import { createNameComponent } from '../createNode' 3 | const route = [ 4 | { 5 | path: '/system', 6 | component: Layout, 7 | redirect: '/404', 8 | hideMenu: true, 9 | meta: { title: 'message.menu.system.name' }, 10 | children: [ 11 | { 12 | path: '/404', 13 | component: createNameComponent(() => import('@/views/system/404.vue')), 14 | meta: { title: 'message.menu.system.404', hideTabs: true } 15 | }, 16 | { 17 | path: '/401', 18 | component: createNameComponent(() => import('@/views/system/401.vue')), 19 | meta: { title: 'message.menu.system.401', hideTabs: true } 20 | }, 21 | { 22 | path: '/redirect/:path(.*)', 23 | component: createNameComponent(() => import('@/views/system/redirect.vue')), 24 | meta: { title: 'message.menu.system.redirect', hideTabs: true } 25 | } 26 | ] 27 | }, 28 | { 29 | path: '/login', 30 | component: createNameComponent(() => import('@/views/system/login.vue')), 31 | hideMenu: true, 32 | meta: { title: 'message.system.login', hideTabs: true } 33 | }, 34 | { 35 | // 找不到路由重定向到404页面 36 | path: "/:pathMatch(.*)", 37 | component: Layout, 38 | redirect: "/404", 39 | hideMenu: true 40 | }, 41 | ] 42 | 43 | export default route -------------------------------------------------------------------------------- /ts-i18n/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { DefineComponent } from 'vue' 3 | const component: DefineComponent<{}, {}, any> 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /ts-i18n/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore, createLogger } from 'vuex' 2 | import Presistent from './plugins/persistent' 3 | const debug = process.env.NODE_ENV !== 'production' 4 | 5 | const files= import.meta.globEager('./modules/*.ts') 6 | 7 | let modules: any = {} 8 | Object.keys(files).forEach((c: string) => { 9 | const module = files[c].default 10 | const moduleName: string = c.replace(/^\.\/(.*)\/(.*)\.\w+$/, '$2') 11 | modules[moduleName] = module 12 | }) 13 | 14 | const presistent = Presistent({ key: 'vuex', modules, modulesKeys: { 15 | local: Object.keys(modules), 16 | session: [] 17 | } }) 18 | 19 | export default createStore({ 20 | modules: { 21 | ...modules 22 | }, 23 | strict: debug, 24 | plugins: debug ? [createLogger(), presistent] : [presistent] 25 | }) -------------------------------------------------------------------------------- /ts-i18n/src/store/modules/app.ts: -------------------------------------------------------------------------------- 1 | interface Option { 2 | name: string, 3 | value: any 4 | } 5 | interface State { 6 | isCollapse: boolean, 7 | contentFullScreen: boolean, 8 | showLogo: boolean, 9 | fixedTop: boolean, 10 | showTabs: boolean, 11 | expandOneMenu: boolean, 12 | elementSize: string, 13 | lang: string, 14 | theme: { 15 | primaryColor: string 16 | } 17 | } 18 | const state = () => ({ 19 | isCollapse: false, // 侧边栏是否收缩展示 20 | contentFullScreen: false, // 内容是否可全屏展示 21 | showLogo: true, // 是否显示Logo 22 | fixedTop: false, // 是否固定顶部, todo,暂未使用 23 | showTabs: true, // 是否显示导航历史 24 | expandOneMenu: true, // 一次是否只能展开一个菜单 25 | elementSize: 'mini', // element默认尺寸,支持官网四个大小参数 26 | lang: 'zh', // 默认采用的国际化方案 27 | theme: { 28 | state: { 29 | style: 'default', 30 | primaryColor: '#409eff', 31 | menuType: 'side' 32 | } 33 | } 34 | }) 35 | 36 | // mutations 37 | const mutations = { 38 | isCollapseChange(state: any, type: boolean) { 39 | state.isCollapse = type 40 | }, 41 | contentFullScreenChange(state: any, type: boolean) { 42 | state.contentFullScreen = type 43 | }, 44 | menuListChange(state: any, arr: []) { 45 | state.menuList = arr 46 | }, 47 | stateChange(state: any, option: Option) { 48 | state[option.name] = option.value 49 | } 50 | } 51 | 52 | // actions 53 | const actions = { 54 | 55 | } 56 | 57 | export default { 58 | namespaced: true, 59 | state, 60 | actions, 61 | mutations 62 | } -------------------------------------------------------------------------------- /ts-i18n/src/store/modules/keepAlive.ts: -------------------------------------------------------------------------------- 1 | interface Option { 2 | name: string, 3 | value: any 4 | } 5 | 6 | interface State { 7 | keepAliveComponentsName: [] 8 | } 9 | 10 | const state = () => ({ 11 | keepAliveComponentsName: [] // 需要缓存的组件名称 12 | }) 13 | 14 | // mutations 15 | const mutations = { 16 | // 重置,Push, splice keep-alive对象 17 | setKeepAliveComponentsName(state: any, nameArr: []) { 18 | state.keepAliveComponentsName = nameArr 19 | }, 20 | addKeepAliveComponentsName(state: any, name: string) { 21 | state.keepAliveComponentsName.push(name) 22 | }, 23 | delKeepAliveComponentsName(state: any, name: string) { 24 | const key = state.keepAliveComponentsName.indexOf(name) 25 | if (key !== -1) { 26 | state.keepAliveComponentsName.splice(key, 1) 27 | console.log(state.keepAliveComponentsName) 28 | } 29 | } 30 | } 31 | 32 | const getters = { 33 | keepAliveComponentsName(state: State) { 34 | return state.keepAliveComponentsName 35 | } 36 | } 37 | 38 | // actions 39 | const actions = { 40 | 41 | } 42 | 43 | export default { 44 | namespaced: true, 45 | state, 46 | getters, 47 | actions, 48 | mutations 49 | } -------------------------------------------------------------------------------- /ts-i18n/src/store/modules/user.ts: -------------------------------------------------------------------------------- 1 | import { loginApi, getInfoApi, loginOutApi } from '@/api/user' 2 | import { ActionContext } from 'vuex' 3 | 4 | interface State { 5 | token: string, 6 | info: object 7 | } 8 | const state = (): State => ({ 9 | token: '', // 登录token 10 | info: {}, // 用户信息 11 | }) 12 | 13 | // getters 14 | const getters = { 15 | token(state: State) { 16 | return state.token 17 | } 18 | } 19 | 20 | // mutations 21 | const mutations = { 22 | tokenChange(state: State, token: string) { 23 | state.token = token 24 | }, 25 | infoChange(state: State, info: object) { 26 | state.info = info 27 | } 28 | } 29 | 30 | // actions 31 | const actions = { 32 | // login by login.vue 33 | login({ commit, dispatch }: ActionContext, params: any) { 34 | return new Promise((resolve, reject) => { 35 | loginApi(params) 36 | .then(res => { 37 | commit('tokenChange', res.data.token) 38 | dispatch('getInfo', { token: res.data.token }) 39 | .then(infoRes => { 40 | resolve(res.data.token) 41 | }) 42 | }) 43 | }) 44 | }, 45 | // get user info after user logined 46 | getInfo({ commit }: ActionContext, params: any) { 47 | return new Promise((resolve, reject) => { 48 | getInfoApi(params) 49 | .then(res => { 50 | commit('infoChange', res.data.info) 51 | resolve(res.data.info) 52 | }) 53 | }) 54 | }, 55 | 56 | // login out the system after user click the loginOut button 57 | loginOut({ commit }: ActionContext) { 58 | loginOutApi() 59 | .then(res => { 60 | 61 | }) 62 | .catch(error => { 63 | 64 | }) 65 | .finally(() => { 66 | localStorage.removeItem('tabs') 67 | localStorage.removeItem('vuex') 68 | location.reload() 69 | }) 70 | } 71 | } 72 | 73 | export default { 74 | namespaced: true, 75 | state, 76 | actions, 77 | getters, 78 | mutations 79 | } -------------------------------------------------------------------------------- /ts-i18n/src/store/plugins/persistent.ts: -------------------------------------------------------------------------------- 1 | interface Socket { 2 | key: string, 3 | modules: Modules, 4 | modulesKeys: ModulesKeys 5 | } 6 | 7 | interface Modules { 8 | [propName: string]: any 9 | } 10 | 11 | interface ModulesKeys { 12 | local: string[], 13 | session: string[] 14 | } 15 | 16 | interface Mutation { 17 | type: any, 18 | payload: any 19 | } 20 | const exclude = ['actions', 'getters', 'mutations', 'namespaced'] 21 | export default function Presistent({ key, modules, modulesKeys }: Socket) { 22 | return (store: any) => { 23 | const localOldState = JSON.parse(localStorage.getItem(key) || '{}') 24 | const sessionOldState = JSON.parse(sessionStorage.getItem(key) || '{}') 25 | let oldState: Modules = {} 26 | Object.assign(oldState, localOldState, sessionOldState) 27 | if (Object.keys(oldState).length > 0) { 28 | for (const oldKey in oldState) { 29 | modules[oldKey] = oldState[oldKey] 30 | } 31 | store.replaceState(modules) 32 | } 33 | store.subscribe((mutation: Mutation, state: any) => { 34 | // 判断是否需要缓存数据至localStorage 35 | if (modulesKeys.local.length > 0) { 36 | const localData = setData(store.state, modulesKeys.local) 37 | localStorage.setItem(key, JSON.stringify(localData)) 38 | } else { 39 | localStorage.removeItem(key) 40 | } 41 | // 判断是否需要缓存数据至sessionStorage 42 | if (modulesKeys.session.length > 0) { 43 | const sessionData = setData(store.state, modulesKeys.session) 44 | sessionStorage.setItem(key, JSON.stringify(sessionData)) 45 | } else { 46 | sessionStorage.removeItem(key) 47 | } 48 | }) 49 | } 50 | } 51 | 52 | function setData(state: any, module: string[]) { 53 | let data: Modules = {} 54 | for (const i of module) { 55 | data[i] = state[i] 56 | } 57 | return data 58 | } -------------------------------------------------------------------------------- /ts-i18n/src/theme/index.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | // 主题色 3 | --system-primary-color: #409eff; // 可做背景色和文本色,用做背景色时,需要和--system-primary-text-color配合使用,避免文件颜色和主题色冲突 4 | --system-primary-text-color: #fff; // 主题色作为背景色时使用 5 | 6 | // logo颜色相关 7 | --system-logo-color: #f1f1f1; 8 | --system-logo-background: #263445; 9 | 10 | // 菜单颜色相关 11 | --system-menu-text-color: #bfcbd9; 12 | --system-menu-background: #28415a; 13 | --system-menu-children-background: #1f2d3d; 14 | --system-menu-submenu-active-color: #fff; 15 | --system-menu-hover-background: #203448; 16 | 17 | // header区域 18 | --system-header-background: #fff; 19 | --system-header-text-color: #bbb; 20 | --system-header-breadcrumb-text-color: #97a8be; 21 | --system-header-item-hover-color: #000; 22 | --system-header-border-color: #d8dce5; 23 | --system-header-tab-background: #fff; 24 | 25 | // contaier区域,父框架 26 | --system-container-background: #f0f2f5; 27 | --system-container-main-background: #fff; 28 | 29 | // 页面区域, 这一块是你在自己写的文件中使用主题,核心需要关注的地方 30 | --system-page-background: #fff; // 主背景 31 | --system-page-color: #303133; // 主要的文本颜色 32 | --system-page-tip-color: rgba(0, 0, 0, 0.45); // 协助展示的文本颜色 33 | --system-page-border-color: #000; // 通用的边框配置色,便于主题扩展 34 | 35 | // element主题色修改 36 | --el-color-primary: var(--system-primary-color); 37 | } 38 | 39 | // 进度条颜色修改为主题色 40 | body #nprogress .bar { 41 | background-color: var(--system-primary-color); 42 | } 43 | body #nprogress .peg { 44 | box-shadow: 0 0 10px var(--system-primary-color), 0 0 5px var(--system-primary-color); 45 | } 46 | body #nprogress .spinner-icon { 47 | border-top-color: var(--system-primary-color); 48 | border-left-color: var(--system-primary-color); 49 | } 50 | 51 | @import './modules/dark.scss'; 52 | -------------------------------------------------------------------------------- /ts-i18n/src/theme/modules/dark.scss: -------------------------------------------------------------------------------- 1 | .dark { 2 | // 通用 3 | p, h1, h2, h3, h4, h5, h6, article { 4 | color: var(--system-page-color); 5 | } 6 | .el-tree { 7 | background-color: var(--system-page-background); 8 | .el-tree-node__content:hover { 9 | background-color: #272727; 10 | } 11 | --el-color-primary-light-9: #272727; 12 | } 13 | .el-card { 14 | background-color: var(--system-page-background); 15 | color: var(--system-page-color); 16 | border-color: var(--system-page-border-color); 17 | .el-card__header { 18 | border-color: var(--system-page-border-color); 19 | } 20 | } 21 | // 页面内部样式修改 22 | 23 | } -------------------------------------------------------------------------------- /ts-i18n/src/utils/system/nprogress.ts: -------------------------------------------------------------------------------- 1 | import NProgress from "nprogress" 2 | import "nprogress/nprogress.css" 3 | 4 | NProgress.configure({ 5 | easing: 'ease', // 动画方式 6 | speed: 500, // 递增进度条的速度 7 | showSpinner: true, // 是否显示加载ico 8 | trickleSpeed: 200, // 自动递增间隔 9 | minimum: 0.3 // 初始化时的最小百分比 10 | }) 11 | 12 | export default NProgress -------------------------------------------------------------------------------- /ts-i18n/src/utils/system/request.ts: -------------------------------------------------------------------------------- 1 | import axios , { AxiosError, AxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios' 2 | import store from '@/store' 3 | import { ElMessage } from 'element-plus' 4 | const baseURL: any = import.meta.env.VITE_BASE_URL 5 | 6 | const service: AxiosInstance = axios.create({ 7 | baseURL: baseURL, 8 | timeout: 5000 9 | }) 10 | 11 | // 请求前的统一处理 12 | service.interceptors.request.use( 13 | (config: AxiosRequestConfig) => { 14 | // JWT鉴权处理 15 | if (store.getters['user/token']) { 16 | config.headers['token'] = store.state.user.token 17 | } 18 | return config 19 | }, 20 | (error: AxiosError) => { 21 | console.log(error) // for debug 22 | return Promise.reject(error) 23 | } 24 | ) 25 | 26 | service.interceptors.response.use( 27 | (response: AxiosResponse) => { 28 | const res = response.data 29 | if (res.code === 200) { 30 | return res 31 | } else { 32 | showError(res) 33 | return Promise.reject(res) 34 | } 35 | }, 36 | (error: AxiosError)=> { 37 | console.log(error) // for debug 38 | const badMessage: any = error.message || error 39 | const code = parseInt(badMessage.toString().replace('Error: Request failed with status code ', '')) 40 | showError({ code, message: badMessage }) 41 | return Promise.reject(error) 42 | } 43 | ) 44 | 45 | function showError(error: any) { 46 | if (error.code === 403) { 47 | // to re-login 48 | store.dispatch('user/loginOut') 49 | } else { 50 | ElMessage({ 51 | message: error.msg || error.message || '服务异常', 52 | type: 'error', 53 | duration: 3 * 1000 54 | }) 55 | } 56 | 57 | } 58 | 59 | export default service -------------------------------------------------------------------------------- /ts-i18n/src/utils/system/statistics.ts: -------------------------------------------------------------------------------- 1 | // 百度统计代码,需自行更换 2 | export function baidu() { 3 | const script = document.createElement('script') 4 | script.type = 'text/javascript' 5 | script.text = ` 6 | var _hmt = _hmt || []; 7 | (function() { 8 | var hm = document.createElement("script"); 9 | hm.src = "https://hm.baidu.com/hm.js?bd78bc908e66174e7dde385bf37cb4c1"; 10 | var s = document.getElementsByTagName("script")[0]; 11 | s.parentNode.insertBefore(hm, s); 12 | })(); 13 | ` 14 | document.getElementsByTagName('head')[0].appendChild(script) 15 | } -------------------------------------------------------------------------------- /ts-i18n/src/utils/system/title.ts: -------------------------------------------------------------------------------- 1 | import i18n from '@/locale' 2 | import { systemTitle } from '@/config' 3 | const { t } = i18n.global 4 | 5 | export function changeTitle(name: any) { 6 | document.title = `${t(name)}-${t(systemTitle)}` 7 | } 8 | -------------------------------------------------------------------------------- /ts-i18n/src/views/main/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /ts-i18n/src/views/main/pages/categoryTable/enum.ts: -------------------------------------------------------------------------------- 1 | export const selectData = [ 2 | { value: 1, label: '运动' }, 3 | { value: 2, label: '健身' }, 4 | { value: 3, label: '跑酷' }, 5 | { value: 4, label: '街舞' } 6 | ] 7 | 8 | export const radioData = [ 9 | { value: 1, label: '今天' }, 10 | { value: 2, label: '明天' }, 11 | { value: 3, label: '后天' }, 12 | ] -------------------------------------------------------------------------------- /ts-i18n/src/views/main/pages/categoryTable/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 28 | 29 | -------------------------------------------------------------------------------- /ts-i18n/src/views/main/pages/crudTable/enum.ts: -------------------------------------------------------------------------------- 1 | export const selectData = [ 2 | { value: 1, label: '运动' }, 3 | { value: 2, label: '健身' }, 4 | { value: 3, label: '跑酷' }, 5 | { value: 4, label: '街舞' } 6 | ] 7 | 8 | export const radioData = [ 9 | { value: 1, label: '今天' }, 10 | { value: 2, label: '明天' }, 11 | { value: 3, label: '后天' }, 12 | ] -------------------------------------------------------------------------------- /ts-i18n/src/views/main/pages/treeTable/enum.ts: -------------------------------------------------------------------------------- 1 | export const selectData = [ 2 | { value: 1, label: '运动' }, 3 | { value: 2, label: '健身' }, 4 | { value: 3, label: '跑酷' }, 5 | { value: 4, label: '街舞' } 6 | ] 7 | 8 | export const radioData = [ 9 | { value: 1, label: '今天' }, 10 | { value: 2, label: '明天' }, 11 | { value: 3, label: '后天' }, 12 | ] -------------------------------------------------------------------------------- /ts-i18n/src/views/main/pages/treeTable/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 29 | 30 | -------------------------------------------------------------------------------- /ts-i18n/src/views/system/redirect.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /ts-i18n/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "jsx": "preserve", 8 | "sourceMap": true, 9 | "resolveJsonModule": true, 10 | "esModuleInterop": true, 11 | "lib": ["esnext", "dom"], 12 | "types": ["vite/client"], 13 | "baseUrl": "./", 14 | "paths": { 15 | "@/*": ["src/*"] 16 | } 17 | }, 18 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] 19 | } 20 | -------------------------------------------------------------------------------- /ts-i18n/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { ConfigEnv, UserConfigExport } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import { viteMockServe } from 'vite-plugin-mock' 4 | import { resolve } from 'path' 5 | 6 | const pathResolve = (dir: string): any => { 7 | return resolve(__dirname, ".", dir) 8 | } 9 | 10 | const alias: Record = { 11 | '@': pathResolve("src") 12 | } 13 | 14 | // https://vitejs.dev/config/ 15 | export default ({ command }: ConfigEnv): UserConfigExport => { 16 | const prodMock = true; 17 | return { 18 | base: './', 19 | resolve: { 20 | alias 21 | }, 22 | server: { 23 | port: 3005, 24 | host: '0.0.0.0', 25 | open: true, 26 | proxy: { // 代理配置 27 | '/dev': 'https://www.fastmock.site/mock/48cab8545e64d93ff9ba66a87ad04f6b/' 28 | }, 29 | }, 30 | build: { 31 | rollupOptions: { 32 | output: { 33 | manualChunks: { 34 | 35 | } 36 | } 37 | } 38 | }, 39 | plugins: [ 40 | vue(), 41 | viteMockServe({ 42 | mockPath: 'mock', 43 | localEnabled: command === 'serve', 44 | prodEnabled: command !== 'serve' && prodMock, 45 | watchFiles: true, 46 | injectCode: ` 47 | import { setupProdMockServer } from '../mockProdServer'; 48 | setupProdMockServer(); 49 | `, 50 | logger: true, 51 | }), 52 | ] 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /ts/.env.development: -------------------------------------------------------------------------------- 1 | ENV = 'development' 2 | 3 | VITE_BASE_URL = '/dev-api' -------------------------------------------------------------------------------- /ts/.env.production: -------------------------------------------------------------------------------- 1 | ENV = 'production' 2 | 3 | VITE_BASE_URL = '/pro-api' -------------------------------------------------------------------------------- /ts/.env.staging: -------------------------------------------------------------------------------- 1 | ENV = 'staging' 2 | 3 | VITE_BASE_URL = '/test-api' -------------------------------------------------------------------------------- /ts/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | yarn.lock 7 | yarn-error.log 8 | .vscode 9 | -------------------------------------------------------------------------------- /ts/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 罗茜 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. -------------------------------------------------------------------------------- /ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ts/mock/user.ts: -------------------------------------------------------------------------------- 1 | import { MockMethod } from 'vite-plugin-mock' 2 | const users = [ 3 | { name: 'admin', password: '123456', token: 'admin', info: { 4 | name: '系统管理员' 5 | }}, 6 | { name: 'editor', password: '123456', token: 'editor', info: { 7 | name: '编辑人员' 8 | }}, 9 | { name: 'test', password: '123456', token: 'test', info: { 10 | name: '测试人员' 11 | }}, 12 | ] 13 | export default [ 14 | { 15 | url: `/mock/user/login`, 16 | method: 'post', 17 | response: ({ body }) => { 18 | const user = users.find(user => { 19 | return body.name === user.name && body.password === user.password 20 | }) 21 | if (user) { 22 | return { 23 | code: 200, 24 | data: { 25 | token: user.token, 26 | }, 27 | }; 28 | } else { 29 | return { 30 | code: 401, 31 | data: {}, 32 | msg: '用户名或密码错误' 33 | }; 34 | } 35 | 36 | } 37 | }, 38 | { 39 | url: `/mock/user/info`, 40 | method: 'post', 41 | response: ({ body }) => { 42 | const { token } = body 43 | const info = users.find(user => { 44 | return user.token === token 45 | }).info 46 | if (info) { 47 | return { 48 | code: 200, 49 | data: { 50 | info: info 51 | }, 52 | }; 53 | } else { 54 | return { 55 | code: 403, 56 | data: {}, 57 | msg: '无访问权限' 58 | }; 59 | } 60 | 61 | } 62 | }, 63 | { 64 | url: `/mock/user/out`, 65 | method: 'post', 66 | response: () => { 67 | return { 68 | code: 200, 69 | data: {}, 70 | msg: 'success' 71 | }; 72 | } 73 | }, 74 | { 75 | url: `/mock/user/passwordChange`, 76 | method: 'post', 77 | response: () => { 78 | return { 79 | code: 200, 80 | data: {}, 81 | msg: 'success' 82 | }; 83 | } 84 | }, 85 | ] as MockMethod[] -------------------------------------------------------------------------------- /ts/mockProdServer.ts: -------------------------------------------------------------------------------- 1 | import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'; 2 | import userModule from './mock/user' 3 | import tableModule from './mock/table' 4 | 5 | export function setupProdMockServer() { 6 | createProdMockServer([ 7 | ...userModule, 8 | ...tableModule 9 | ]); 10 | } 11 | -------------------------------------------------------------------------------- /ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "init", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "start": "vite", 7 | "build": "vite build --mode=production", 8 | "build:stag": "vite build --mode=staging", 9 | "serve": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@vueuse/core": "^4.10.0", 13 | "axios": "^0.21.1", 14 | "element-plus": "^1.0.2-beta.48", 15 | "mockjs": "^1.1.0", 16 | "normalize.css": "^8.0.1", 17 | "nprogress": "^0.2.0", 18 | "throttle-debounce": "^3.0.1", 19 | "vue": "^3.1.2", 20 | "vue-router": "4", 21 | "vuex": "^4.0.0" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^15.0.3", 25 | "@vitejs/plugin-vue": "^1.2.2", 26 | "@vue/compiler-sfc": "^3.0.5", 27 | "sass": "^1.32.12", 28 | "typescript": "^4.1.3", 29 | "vite": "2.3.7", 30 | "vite-plugin-mock": "2.8.0", 31 | "vue-tsc": "^0.0.24" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ts/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/ts/public/favicon.ico -------------------------------------------------------------------------------- /ts/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | 31 | -------------------------------------------------------------------------------- /ts/src/api/table.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/system/request' 2 | 3 | // 获取数据api 4 | export function getData(data: object) { 5 | return request({ 6 | url: '/table/list', 7 | method: 'post', 8 | baseURL: '/mock', 9 | data 10 | }) 11 | } 12 | 13 | // 获取分类数据 14 | export function getCategory(data: object) { 15 | return request({ 16 | url: '/table/category', 17 | method: 'post', 18 | baseURL: '/mock', 19 | data 20 | }) 21 | } 22 | 23 | // 获取树组织数据 24 | export function getTree(data: object) { 25 | return request({ 26 | url: '/table/tree', 27 | method: 'post', 28 | baseURL: '/mock', 29 | data 30 | }) 31 | } 32 | 33 | // 新增 34 | export function add(data: object) { 35 | return request({ 36 | url: '/table/add', 37 | method: 'post', 38 | baseURL: '/mock', 39 | data 40 | }) 41 | } 42 | 43 | // 编辑 44 | export function update(data: object) { 45 | return request({ 46 | url: '/table/update', 47 | method: 'post', 48 | baseURL: '/mock', 49 | data 50 | }) 51 | } 52 | 53 | // 删除 54 | export function del(data: object) { 55 | return request({ 56 | url: '/table/del', 57 | method: 'post', 58 | baseURL: '/mock', 59 | data 60 | }) 61 | } -------------------------------------------------------------------------------- /ts/src/api/user.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/system/request' 2 | 3 | // 登录api 4 | export function loginApi(data: object) { 5 | return request({ 6 | url: '/user/login', 7 | method: 'post', 8 | baseURL: '/mock', 9 | data 10 | }) 11 | } 12 | 13 | // 获取用户信息Api 14 | export function getInfoApi(data: object) { 15 | return request({ 16 | url: '/user/info', 17 | method: 'post', 18 | baseURL: '/mock', 19 | data 20 | }) 21 | } 22 | 23 | // 退出登录Api 24 | export function loginOutApi() { 25 | return request({ 26 | url: '/user/out', 27 | method: 'post', 28 | baseURL: '/mock' 29 | }) 30 | } 31 | 32 | // 获取用户信息Api 33 | export function passwordChange(data: object) { 34 | return request({ 35 | url: '/user/passwordChange', 36 | method: 'post', 37 | baseURL: '/mock', 38 | data 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /ts/src/assets/images/401.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/ts/src/assets/images/401.gif -------------------------------------------------------------------------------- /ts/src/assets/images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/ts/src/assets/images/404.png -------------------------------------------------------------------------------- /ts/src/assets/images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/ts/src/assets/images/404_cloud.png -------------------------------------------------------------------------------- /ts/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdparkour/vue-admin-box-template/705a4e3ad103552638d3f4b0fdb95ab96d1c1b88/ts/src/assets/logo.png -------------------------------------------------------------------------------- /ts/src/assets/style/common.scss: -------------------------------------------------------------------------------- 1 | @import "./transition.scss"; 2 | @import "@/theme/index.scss"; 3 | .layout-container { 4 | background-color: var(--system-container-main-background); 5 | width: calc(100% - 30px); 6 | height: calc(100% - 30px); 7 | margin: 15px; 8 | display: flex; 9 | flex-direction: column; 10 | overflow-y: auto; 11 | &-form { 12 | display: flex; 13 | justify-content: space-between; 14 | padding: 15px 15px 0; 15 | &-handle { 16 | display: flex; 17 | justify-content: flex-start; 18 | } 19 | &-search { 20 | display: flex; 21 | justify-content: flex-end; 22 | .search-btn { 23 | margin-left: 15px; 24 | } 25 | } 26 | .el-form-item { 27 | margin-bottom: 0; 28 | } 29 | } 30 | &-table { 31 | flex: 1; 32 | height: 100%; 33 | padding: 15px; 34 | overflow: auto; 35 | } 36 | } 37 | .flex-box { 38 | display: flex; 39 | flex-direction: column; 40 | width: 100%; 41 | height: 100%; 42 | padding: 15px; 43 | box-sizing: border-box; 44 | } 45 | .flex { 46 | display: flex; 47 | } 48 | .center { 49 | justify-content: center; 50 | align-items: center; 51 | text-align: center; 52 | } 53 | a { 54 | text-decoration: none; 55 | } 56 | 57 | /** element-plus **/ 58 | .el-icon{ 59 | text-align: center; 60 | } 61 | 62 | /** 用于提示信息 **/ 63 | .my-tip { 64 | background-color: #f1f1f1; 65 | padding: 5px 10px; 66 | text-align: left; 67 | border-radius: 4px; 68 | } 69 | .system-scrollbar { 70 | &::-webkit-scrollbar { 71 | display: none; 72 | width: 6px; 73 | } 74 | &::-webkit-scrollbar-thumb { 75 | border-radius: 10px; 76 | background: rgba(144, 147, 153, 0.3); 77 | } 78 | &:hover { 79 | &::-webkit-scrollbar { 80 | display: block; 81 | } 82 | &::-webkit-scrollbar-thumb { 83 | border-radius: 10px; 84 | background: rgba(144, 147, 153, 0.3); 85 | &:hover { 86 | background: rgba(144, 147, 153, 0.5); 87 | } 88 | } 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /ts/src/assets/style/transition.scss: -------------------------------------------------------------------------------- 1 | /* fade-transform */ 2 | .fade-transform-leave-active, 3 | .fade-transform-enter-active { 4 | transition: all .2s; 5 | } 6 | 7 | .fade-transform-enter-from { 8 | opacity: 0; 9 | transform: translateX(-30px); 10 | transition: all .2s; 11 | } 12 | 13 | .fade-transform-leave-to { 14 | opacity: 0; 15 | transform: translateX(30px); 16 | transition: all .2s; 17 | } 18 | 19 | /* breadcrumb transition */ 20 | .breadcrumb-enter-active, 21 | .breadcrumb-leave-active { 22 | transition: all .2s; 23 | } 24 | 25 | .breadcrumb-enter, 26 | .breadcrumb-leave-active { 27 | opacity: 0; 28 | transform: translateX(80px); 29 | } 30 | 31 | .breadcrumb-move { 32 | transition: all .5s; 33 | } 34 | 35 | .breadcrumb-leave-active { 36 | position: absolute; 37 | } -------------------------------------------------------------------------------- /ts/src/components/layer/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 72 | 73 | -------------------------------------------------------------------------------- /ts/src/components/menu/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /ts/src/components/table/type.ts: -------------------------------------------------------------------------------- 1 | export interface Page{ 2 | index: Number, 3 | size: Number, 4 | total: Number 5 | } -------------------------------------------------------------------------------- /ts/src/config/index.ts: -------------------------------------------------------------------------------- 1 | const showLogo: Boolean = true; // 是否显示Logo顶部模块 2 | const systemTitle = '后台管理系统' // 系统名称,用于显示在左上角模块,以及浏览器标题上使用,使用配置项 3 | export { 4 | systemTitle 5 | } -------------------------------------------------------------------------------- /ts/src/layout/Header/Breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 50 | 51 | -------------------------------------------------------------------------------- /ts/src/layout/Header/functionList/fullscreen.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | 32 | -------------------------------------------------------------------------------- /ts/src/layout/Header/functionList/sizeChange.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 60 | 61 | -------------------------------------------------------------------------------- /ts/src/layout/Header/functionList/theme/theme-color.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 48 | 49 | -------------------------------------------------------------------------------- /ts/src/layout/Logo/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | 24 | -------------------------------------------------------------------------------- /ts/src/layout/Menu/Link.vue: -------------------------------------------------------------------------------- 1 | 7 | 39 | -------------------------------------------------------------------------------- /ts/src/layout/Tabs/tabsHook.ts: -------------------------------------------------------------------------------- 1 | const tabsHook = { 2 | setItem: function(arr: object[]) { 3 | localStorage.setItem('tabs', JSON.stringify(arr)) 4 | }, 5 | getItem: function() { 6 | return JSON.parse(localStorage.getItem('tabs') || '[]') 7 | } 8 | } 9 | export default tabsHook 10 | -------------------------------------------------------------------------------- /ts/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import ElementPlus from 'element-plus' 3 | import { baidu } from './utils/system/statistics' 4 | import 'element-plus/lib/theme-chalk/index.css' 5 | import 'element-plus/lib/theme-chalk/display.css' // 引入基于断点的隐藏类 6 | import 'normalize.css' // css初始化 7 | import './assets/style/common.scss' // 公共css 8 | import App from './App.vue' 9 | import store from './store' 10 | import router from './router' 11 | // if (import.meta.env.MODE !== 'development') { // 非开发环境调用百度统计 12 | // baidu() 13 | // } 14 | const app = createApp(App) 15 | app.use(ElementPlus, { size: store.state.app.elementSize }) 16 | app.use(store) 17 | app.use(router) 18 | // app.config.performance = true 19 | app.mount('#app') 20 | -------------------------------------------------------------------------------- /ts/src/router/createNode.ts: -------------------------------------------------------------------------------- 1 | // 1. 用于解决keep-alive需要name的问题,动态生成随机name供keep-alive使用 2 | // 2. 用于解决transition动画内部结点只能为根元素的问题,单文件可写多结点 3 | import { defineComponent, h, createVNode, ref, nextTick } from 'vue' 4 | import reload from './reload.vue' 5 | import NProgress from '@/utils/system/nprogress' 6 | 7 | export function createNameComponent(component: any) { 8 | return () => { 9 | return new Promise((res) => { 10 | component().then((comm: any) => { 11 | const name = (comm.default.name || 'vueAdminBox') + '$' + Date.now(); 12 | const tempComm = defineComponent({ 13 | name, 14 | setup() { 15 | const isReload = ref(false); 16 | let timeOut: any = null; 17 | const handleReload = () => { 18 | isReload.value = true; 19 | timeOut && clearTimeout(timeOut); 20 | NProgress.start(); 21 | timeOut = setTimeout(() => { 22 | nextTick(() => { 23 | NProgress.done(); 24 | isReload.value = false; 25 | }); 26 | }, 260); 27 | }; 28 | return { 29 | isReload, 30 | handleReload 31 | }; 32 | }, 33 | render: function () { 34 | if (this.isReload) { 35 | return h('div', { class: 'el-main-box' }, [h(reload)]); 36 | } else { 37 | return h('div', { class: 'el-main-box' }, [createVNode(comm.default)]); 38 | } 39 | } 40 | }); 41 | res(tempComm); 42 | }); 43 | }); 44 | }; 45 | } 46 | 47 | -------------------------------------------------------------------------------- /ts/src/router/modules/dashboard.ts: -------------------------------------------------------------------------------- 1 | import Layout from '@/layout/index.vue' 2 | import { createNameComponent } from '../createNode' 3 | const route = [ 4 | { 5 | path: '/', 6 | component: Layout, 7 | redirect: '/dashboard', 8 | meta: { title: 'dashboard', icon: 'el-icon-menu' }, 9 | children: [ 10 | { 11 | path: 'dashboard', 12 | component: createNameComponent(() => import('@/views/main/dashboard/index.vue')), 13 | meta: { title: '首页', icon: 'el-icon-menu', hideClose: true } 14 | } 15 | ] 16 | } 17 | ] 18 | 19 | export default route -------------------------------------------------------------------------------- /ts/src/router/modules/pages.ts: -------------------------------------------------------------------------------- 1 | import Layout from '@/layout/index.vue' 2 | import { createNameComponent } from '../createNode' 3 | const route = [ 4 | { 5 | path: '/pages', 6 | component: Layout, 7 | redirect: '/pages/crudTable', 8 | meta: { title: '页面', icon: 'el-icon-document-copy' }, 9 | alwayShow: true, 10 | children: [ 11 | { 12 | path: 'crudTable', 13 | component: createNameComponent(() => import('@/views/main/pages/crudTable/index.vue')), 14 | meta: { title: '业务表格', cache: false, roles: ['admin', 'editor'] } 15 | }, 16 | { 17 | path: 'categoryTable', 18 | component: createNameComponent(() => import('@/views/main/pages/categoryTable/index.vue')), 19 | meta: { title: '分类联动表格', cache: true, roles: ['admin'] } 20 | }, 21 | { 22 | path: 'treeTable', 23 | component: createNameComponent(() => import('@/views/main/pages/treeTable/index.vue')), 24 | meta: { title: '树联动表格', cache: true } 25 | } 26 | ] 27 | } 28 | ] 29 | 30 | export default route -------------------------------------------------------------------------------- /ts/src/router/modules/system.ts: -------------------------------------------------------------------------------- 1 | import Layout from '@/layout/index.vue' 2 | import { createNameComponent } from '../createNode' 3 | const route = [ 4 | { 5 | path: '/system', 6 | component: Layout, 7 | redirect: '/404', 8 | hideMenu: true, 9 | meta: { title: '系统目录' }, 10 | children: [ 11 | { 12 | path: '/404', 13 | component: createNameComponent(() => import('@/views/system/404.vue')), 14 | meta: { title: '404', hideTabs: true } 15 | }, 16 | { 17 | path: '/401', 18 | component: createNameComponent(() => import('@/views/system/401.vue')), 19 | meta: { title: '401', hideTabs: true } 20 | }, 21 | { 22 | path: '/redirect/:path(.*)', 23 | component: createNameComponent(() => import('@/views/system/redirect.vue')), 24 | meta: { title: 'redirect', hideTabs: true } 25 | } 26 | ] 27 | }, 28 | { 29 | path: '/login', 30 | component: createNameComponent(() => import('@/views/system/login.vue')), 31 | hideMenu: true, 32 | meta: { title: '登录', hideTabs: true } 33 | }, 34 | { 35 | // 找不到路由重定向到404页面 36 | path: "/:pathMatch(.*)", 37 | component: Layout, 38 | redirect: "/404", 39 | hideMenu: true 40 | }, 41 | ] 42 | 43 | export default route -------------------------------------------------------------------------------- /ts/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { DefineComponent } from 'vue' 3 | const component: DefineComponent<{}, {}, any> 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /ts/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore, createLogger } from 'vuex' 2 | import Presistent from './plugins/persistent' 3 | const debug = process.env.NODE_ENV !== 'production' 4 | 5 | const files= import.meta.globEager('./modules/*.ts') 6 | 7 | let modules: any = {} 8 | Object.keys(files).forEach((c: string) => { 9 | const module = files[c].default 10 | const moduleName: string = c.replace(/^\.\/(.*)\/(.*)\.\w+$/, '$2') 11 | modules[moduleName] = module 12 | }) 13 | 14 | const presistent = Presistent({ key: 'vuex', modules, modulesKeys: { 15 | local: Object.keys(modules), 16 | session: [] 17 | } }) 18 | 19 | export default createStore({ 20 | modules: { 21 | ...modules 22 | }, 23 | strict: debug, 24 | plugins: debug ? [createLogger(), presistent] : [presistent] 25 | }) -------------------------------------------------------------------------------- /ts/src/store/modules/app.ts: -------------------------------------------------------------------------------- 1 | interface Option { 2 | name: string, 3 | value: any 4 | } 5 | interface State { 6 | isCollapse: boolean, 7 | contentFullScreen: boolean, 8 | showLogo: boolean, 9 | fixedTop: boolean, 10 | showTabs: boolean, 11 | expandOneMenu: boolean, 12 | elementSize: string, 13 | lang: string, 14 | theme: { 15 | primaryColor: string 16 | } 17 | } 18 | const state = () => ({ 19 | isCollapse: false, // 侧边栏是否收缩展示 20 | contentFullScreen: false, // 内容是否可全屏展示 21 | showLogo: true, // 是否显示Logo 22 | fixedTop: false, // 是否固定顶部, todo,暂未使用 23 | showTabs: true, // 是否显示导航历史 24 | expandOneMenu: true, // 一次是否只能展开一个菜单 25 | elementSize: 'mini', // element默认尺寸,支持官网四个大小参数 26 | lang: 'zh', // 默认采用的国际化方案 27 | theme: { 28 | state: { 29 | style: 'default', 30 | primaryColor: '#409eff', 31 | menuType: 'side' 32 | } 33 | } 34 | }) 35 | 36 | // mutations 37 | const mutations = { 38 | isCollapseChange(state: any, type: boolean) { 39 | state.isCollapse = type 40 | }, 41 | contentFullScreenChange(state: any, type: boolean) { 42 | state.contentFullScreen = type 43 | }, 44 | menuListChange(state: any, arr: []) { 45 | state.menuList = arr 46 | }, 47 | stateChange(state: any, option: Option) { 48 | state[option.name] = option.value 49 | } 50 | } 51 | 52 | // actions 53 | const actions = { 54 | 55 | } 56 | 57 | export default { 58 | namespaced: true, 59 | state, 60 | actions, 61 | mutations 62 | } -------------------------------------------------------------------------------- /ts/src/store/modules/keepAlive.ts: -------------------------------------------------------------------------------- 1 | interface Option { 2 | name: string, 3 | value: any 4 | } 5 | 6 | interface State { 7 | keepAliveComponentsName: [] 8 | } 9 | 10 | const state = () => ({ 11 | keepAliveComponentsName: [] // 需要缓存的组件名称 12 | }) 13 | 14 | // mutations 15 | const mutations = { 16 | // 重置,Push, splice keep-alive对象 17 | setKeepAliveComponentsName(state: any, nameArr: []) { 18 | state.keepAliveComponentsName = nameArr 19 | }, 20 | addKeepAliveComponentsName(state: any, name: string) { 21 | state.keepAliveComponentsName.push(name) 22 | }, 23 | delKeepAliveComponentsName(state: any, name: string) { 24 | const key = state.keepAliveComponentsName.indexOf(name) 25 | if (key !== -1) { 26 | state.keepAliveComponentsName.splice(key, 1) 27 | console.log(state.keepAliveComponentsName) 28 | } 29 | } 30 | } 31 | 32 | const getters = { 33 | keepAliveComponentsName(state: State) { 34 | return state.keepAliveComponentsName 35 | } 36 | } 37 | 38 | // actions 39 | const actions = { 40 | 41 | } 42 | 43 | export default { 44 | namespaced: true, 45 | state, 46 | getters, 47 | actions, 48 | mutations 49 | } -------------------------------------------------------------------------------- /ts/src/store/modules/user.ts: -------------------------------------------------------------------------------- 1 | import { loginApi, getInfoApi, loginOutApi } from '@/api/user' 2 | import { ActionContext } from 'vuex' 3 | 4 | interface State { 5 | token: string, 6 | info: object 7 | } 8 | const state = (): State => ({ 9 | token: '', // 登录token 10 | info: {}, // 用户信息 11 | }) 12 | 13 | // getters 14 | const getters = { 15 | token(state: State) { 16 | return state.token 17 | } 18 | } 19 | 20 | // mutations 21 | const mutations = { 22 | tokenChange(state: State, token: string) { 23 | state.token = token 24 | }, 25 | infoChange(state: State, info: object) { 26 | state.info = info 27 | } 28 | } 29 | 30 | // actions 31 | const actions = { 32 | // login by login.vue 33 | login({ commit, dispatch }: ActionContext, params: any) { 34 | return new Promise((resolve, reject) => { 35 | loginApi(params) 36 | .then(res => { 37 | commit('tokenChange', res.data.token) 38 | dispatch('getInfo', { token: res.data.token }) 39 | .then(infoRes => { 40 | resolve(res.data.token) 41 | }) 42 | }) 43 | }) 44 | }, 45 | // get user info after user logined 46 | getInfo({ commit }: ActionContext, params: any) { 47 | return new Promise((resolve, reject) => { 48 | getInfoApi(params) 49 | .then(res => { 50 | commit('infoChange', res.data.info) 51 | resolve(res.data.info) 52 | }) 53 | }) 54 | }, 55 | 56 | // login out the system after user click the loginOut button 57 | loginOut({ commit }: ActionContext) { 58 | loginOutApi() 59 | .then(res => { 60 | 61 | }) 62 | .catch(error => { 63 | 64 | }) 65 | .finally(() => { 66 | localStorage.removeItem('tabs') 67 | localStorage.removeItem('vuex') 68 | location.reload() 69 | }) 70 | } 71 | } 72 | 73 | export default { 74 | namespaced: true, 75 | state, 76 | actions, 77 | getters, 78 | mutations 79 | } -------------------------------------------------------------------------------- /ts/src/store/plugins/persistent.ts: -------------------------------------------------------------------------------- 1 | interface Socket { 2 | key: string, 3 | modules: Modules, 4 | modulesKeys: ModulesKeys 5 | } 6 | 7 | interface Modules { 8 | [propName: string]: any 9 | } 10 | 11 | interface ModulesKeys { 12 | local: string[], 13 | session: string[] 14 | } 15 | 16 | interface Mutation { 17 | type: any, 18 | payload: any 19 | } 20 | const exclude = ['actions', 'getters', 'mutations', 'namespaced'] 21 | export default function Presistent({ key, modules, modulesKeys }: Socket) { 22 | return (store: any) => { 23 | const localOldState = JSON.parse(localStorage.getItem(key) || '{}') 24 | const sessionOldState = JSON.parse(sessionStorage.getItem(key) || '{}') 25 | let oldState: Modules = {} 26 | Object.assign(oldState, localOldState, sessionOldState) 27 | if (Object.keys(oldState).length > 0) { 28 | for (const oldKey in oldState) { 29 | modules[oldKey] = oldState[oldKey] 30 | } 31 | store.replaceState(modules) 32 | } 33 | store.subscribe((mutation: Mutation, state: any) => { 34 | // 判断是否需要缓存数据至localStorage 35 | if (modulesKeys.local.length > 0) { 36 | const localData = setData(store.state, modulesKeys.local) 37 | localStorage.setItem(key, JSON.stringify(localData)) 38 | } else { 39 | localStorage.removeItem(key) 40 | } 41 | // 判断是否需要缓存数据至sessionStorage 42 | if (modulesKeys.session.length > 0) { 43 | const sessionData = setData(store.state, modulesKeys.session) 44 | sessionStorage.setItem(key, JSON.stringify(sessionData)) 45 | } else { 46 | sessionStorage.removeItem(key) 47 | } 48 | }) 49 | } 50 | } 51 | 52 | function setData(state: any, module: string[]) { 53 | let data: Modules = {} 54 | for (const i of module) { 55 | data[i] = state[i] 56 | } 57 | return data 58 | } -------------------------------------------------------------------------------- /ts/src/theme/index.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | // 主题色 3 | --system-primary-color: #409eff; // 可做背景色和文本色,用做背景色时,需要和--system-primary-text-color配合使用,避免文件颜色和主题色冲突 4 | --system-primary-text-color: #fff; // 主题色作为背景色时使用 5 | 6 | // logo颜色相关 7 | --system-logo-color: #f1f1f1; 8 | --system-logo-background: #263445; 9 | 10 | // 菜单颜色相关 11 | --system-menu-text-color: #bfcbd9; 12 | --system-menu-background: #28415a; 13 | --system-menu-children-background: #1f2d3d; 14 | --system-menu-submenu-active-color: #fff; 15 | --system-menu-hover-background: #203448; 16 | 17 | // header区域 18 | --system-header-background: #fff; 19 | --system-header-text-color: #bbb; 20 | --system-header-breadcrumb-text-color: #97a8be; 21 | --system-header-item-hover-color: #000; 22 | --system-header-border-color: #d8dce5; 23 | --system-header-tab-background: #fff; 24 | 25 | // contaier区域,父框架 26 | --system-container-background: #f0f2f5; 27 | --system-container-main-background: #fff; 28 | 29 | // 页面区域, 这一块是你在自己写的文件中使用主题,核心需要关注的地方 30 | --system-page-background: #fff; // 主背景 31 | --system-page-color: #303133; // 主要的文本颜色 32 | --system-page-tip-color: rgba(0, 0, 0, 0.45); // 协助展示的文本颜色 33 | --system-page-border-color: #000; // 通用的边框配置色,便于主题扩展 34 | 35 | // element主题色修改 36 | --el-color-primary: var(--system-primary-color); 37 | } 38 | 39 | // 进度条颜色修改为主题色 40 | body #nprogress .bar { 41 | background-color: var(--system-primary-color); 42 | } 43 | body #nprogress .peg { 44 | box-shadow: 0 0 10px var(--system-primary-color), 0 0 5px var(--system-primary-color); 45 | } 46 | body #nprogress .spinner-icon { 47 | border-top-color: var(--system-primary-color); 48 | border-left-color: var(--system-primary-color); 49 | } 50 | 51 | @import './modules/dark.scss'; 52 | -------------------------------------------------------------------------------- /ts/src/theme/modules/dark.scss: -------------------------------------------------------------------------------- 1 | .dark { 2 | // 通用 3 | p, h1, h2, h3, h4, h5, h6, article { 4 | color: var(--system-page-color); 5 | } 6 | .el-tree { 7 | background-color: var(--system-page-background); 8 | .el-tree-node__content:hover { 9 | background-color: #272727; 10 | } 11 | --el-color-primary-light-9: #272727; 12 | } 13 | .el-card { 14 | background-color: var(--system-page-background); 15 | color: var(--system-page-color); 16 | border-color: var(--system-page-border-color); 17 | .el-card__header { 18 | border-color: var(--system-page-border-color); 19 | } 20 | } 21 | // 页面内部样式修改 22 | 23 | } -------------------------------------------------------------------------------- /ts/src/utils/system/nprogress.ts: -------------------------------------------------------------------------------- 1 | import NProgress from "nprogress" 2 | import "nprogress/nprogress.css" 3 | 4 | NProgress.configure({ 5 | easing: 'ease', // 动画方式 6 | speed: 500, // 递增进度条的速度 7 | showSpinner: true, // 是否显示加载ico 8 | trickleSpeed: 200, // 自动递增间隔 9 | minimum: 0.3 // 初始化时的最小百分比 10 | }) 11 | 12 | export default NProgress -------------------------------------------------------------------------------- /ts/src/utils/system/request.ts: -------------------------------------------------------------------------------- 1 | import axios , { AxiosError, AxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios' 2 | import store from '@/store' 3 | import { ElMessage } from 'element-plus' 4 | const baseURL: any = import.meta.env.VITE_BASE_URL 5 | 6 | const service: AxiosInstance = axios.create({ 7 | baseURL: baseURL, 8 | timeout: 5000 9 | }) 10 | 11 | // 请求前的统一处理 12 | service.interceptors.request.use( 13 | (config: AxiosRequestConfig) => { 14 | // JWT鉴权处理 15 | if (store.getters['user/token']) { 16 | config.headers['token'] = store.state.user.token 17 | } 18 | return config 19 | }, 20 | (error: AxiosError) => { 21 | console.log(error) // for debug 22 | return Promise.reject(error) 23 | } 24 | ) 25 | 26 | service.interceptors.response.use( 27 | (response: AxiosResponse) => { 28 | const res = response.data 29 | if (res.code === 200) { 30 | return res 31 | } else { 32 | showError(res) 33 | return Promise.reject(res) 34 | } 35 | }, 36 | (error: AxiosError)=> { 37 | console.log(error) // for debug 38 | const badMessage: any = error.message || error 39 | const code = parseInt(badMessage.toString().replace('Error: Request failed with status code ', '')) 40 | showError({ code, message: badMessage }) 41 | return Promise.reject(error) 42 | } 43 | ) 44 | 45 | function showError(error: any) { 46 | if (error.code === 403) { 47 | // to re-login 48 | store.dispatch('user/loginOut') 49 | } else { 50 | ElMessage({ 51 | message: error.msg || error.message || '服务异常', 52 | type: 'error', 53 | duration: 3 * 1000 54 | }) 55 | } 56 | 57 | } 58 | 59 | export default service -------------------------------------------------------------------------------- /ts/src/utils/system/statistics.ts: -------------------------------------------------------------------------------- 1 | // 百度统计代码,需自行更换 2 | export function baidu() { 3 | const script = document.createElement('script') 4 | script.type = 'text/javascript' 5 | script.text = ` 6 | var _hmt = _hmt || []; 7 | (function() { 8 | var hm = document.createElement("script"); 9 | hm.src = "https://hm.baidu.com/hm.js?bd78bc908e66174e7dde385bf37cb4c1"; 10 | var s = document.getElementsByTagName("script")[0]; 11 | s.parentNode.insertBefore(hm, s); 12 | })(); 13 | ` 14 | document.getElementsByTagName('head')[0].appendChild(script) 15 | } -------------------------------------------------------------------------------- /ts/src/utils/system/title.ts: -------------------------------------------------------------------------------- 1 | import i18n from '@/locale' 2 | import { systemTitle } from '@/config' 3 | 4 | export function changeTitle(name: any) { 5 | document.title = `${name}-${systemTitle}` 6 | } 7 | -------------------------------------------------------------------------------- /ts/src/views/main/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /ts/src/views/main/pages/categoryTable/enum.ts: -------------------------------------------------------------------------------- 1 | export const selectData = [ 2 | { value: 1, label: '运动' }, 3 | { value: 2, label: '健身' }, 4 | { value: 3, label: '跑酷' }, 5 | { value: 4, label: '街舞' } 6 | ] 7 | 8 | export const radioData = [ 9 | { value: 1, label: '今天' }, 10 | { value: 2, label: '明天' }, 11 | { value: 3, label: '后天' }, 12 | ] -------------------------------------------------------------------------------- /ts/src/views/main/pages/categoryTable/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 28 | 29 | -------------------------------------------------------------------------------- /ts/src/views/main/pages/crudTable/enum.ts: -------------------------------------------------------------------------------- 1 | export const selectData = [ 2 | { value: 1, label: '运动' }, 3 | { value: 2, label: '健身' }, 4 | { value: 3, label: '跑酷' }, 5 | { value: 4, label: '街舞' } 6 | ] 7 | 8 | export const radioData = [ 9 | { value: 1, label: '今天' }, 10 | { value: 2, label: '明天' }, 11 | { value: 3, label: '后天' }, 12 | ] -------------------------------------------------------------------------------- /ts/src/views/main/pages/treeTable/enum.ts: -------------------------------------------------------------------------------- 1 | export const selectData = [ 2 | { value: 1, label: '运动' }, 3 | { value: 2, label: '健身' }, 4 | { value: 3, label: '跑酷' }, 5 | { value: 4, label: '街舞' } 6 | ] 7 | 8 | export const radioData = [ 9 | { value: 1, label: '今天' }, 10 | { value: 2, label: '明天' }, 11 | { value: 3, label: '后天' }, 12 | ] -------------------------------------------------------------------------------- /ts/src/views/main/pages/treeTable/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 29 | 30 | -------------------------------------------------------------------------------- /ts/src/views/system/redirect.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "jsx": "preserve", 8 | "sourceMap": true, 9 | "resolveJsonModule": true, 10 | "esModuleInterop": true, 11 | "lib": ["esnext", "dom"], 12 | "types": ["vite/client"], 13 | "baseUrl": "./", 14 | "paths": { 15 | "@/*": ["src/*"] 16 | } 17 | }, 18 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] 19 | } 20 | -------------------------------------------------------------------------------- /ts/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { ConfigEnv, UserConfigExport } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import { viteMockServe } from 'vite-plugin-mock' 4 | import { resolve } from 'path' 5 | 6 | const pathResolve = (dir: string): any => { 7 | return resolve(__dirname, ".", dir) 8 | } 9 | 10 | const alias: Record = { 11 | '@': pathResolve("src") 12 | } 13 | 14 | // https://vitejs.dev/config/ 15 | export default ({ command }: ConfigEnv): UserConfigExport => { 16 | const prodMock = true; 17 | return { 18 | base: './', 19 | resolve: { 20 | alias 21 | }, 22 | server: { 23 | port: 3004, 24 | host: '0.0.0.0', 25 | open: true, 26 | proxy: { // 代理配置 27 | '/dev': 'https://www.fastmock.site/mock/48cab8545e64d93ff9ba66a87ad04f6b/' 28 | }, 29 | }, 30 | build: { 31 | rollupOptions: { 32 | output: { 33 | manualChunks: { 34 | 35 | } 36 | } 37 | } 38 | }, 39 | plugins: [ 40 | vue(), 41 | viteMockServe({ 42 | mockPath: 'mock', 43 | localEnabled: command === 'serve', 44 | prodEnabled: command !== 'serve' && prodMock, 45 | watchFiles: true, 46 | injectCode: ` 47 | import { setupProdMockServer } from '../mockProdServer'; 48 | setupProdMockServer(); 49 | `, 50 | logger: true, 51 | }), 52 | ] 53 | }; 54 | } 55 | --------------------------------------------------------------------------------