Compare commits

...

11 Commits

Author SHA1 Message Date
lzx
bbb9b4e6b1 第一次提交 2025-12-09 10:52:00 +08:00
huzhushan
9b89d24d2e update 2024-09-04 18:05:41 +08:00
huzhushan
7cc3274ebc 版本升级到v3:element-plus升级到v2、引入国际化、pinia 2022-09-28 13:55:57 +08:00
zsen hu
4b4b49011e 不在路由中的所有标签,需要删除 2021-11-23 10:57:08 +08:00
zsen hu
543a82120a update 2021-11-15 09:53:16 +08:00
zsen hu
722437b24f Merge branch 'V2' 2021-11-12 14:18:52 +08:00
zsen hu
a55999220c 未退出的时候登录其它账号需要重置token 2021-11-12 14:17:09 +08:00
zsen hu
3c3c9405a0 Merge branch 'V2' 2021-09-29 15:24:37 +08:00
zsen hu
8d254ed319 update 2021-09-29 15:24:24 +08:00
zsen hu
4437c69bb3 Merge branch 'V2' 2021-09-29 15:18:58 +08:00
zsen hu
a296ab3c9d 2.1.0 2021-09-29 15:18:41 +08:00
114 changed files with 13521 additions and 4878 deletions

5
.env.development Normal file
View File

@ -0,0 +1,5 @@
# 开发环境空字符串走Vite代理
VITE_API_BASE_URL='http://127.0.0.1:8501'
VITE_ADMIN_BASE_URL='http://127.0.0.1:8501'
VITE_HEAT_API_BASE_URL='http://192.168.0.207:8000'
VITE_STRENGTH_API_BASE_URL='http://127.0.0.1:8501'

0
.env.production Normal file
View File

View File

@ -24,7 +24,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 12:51:42 * @LastEditTime: 2022-09-24 16:29:19
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -71,5 +71,6 @@ module.exports = {
'vue/require-default-prop': 'off', 'vue/require-default-prop': 'off',
'vue/no-unused-components': 'warn', 'vue/no-unused-components': 'warn',
'vue/no-setup-props-destructure': 'off', 'vue/no-setup-props-destructure': 'off',
'vue/script-setup-uses-vars': 'off',
}, },
} }

View File

@ -2,9 +2,10 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/ai_tu.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue3-Element-Admin</title> <!-- <title>Vue3-Element-Admin</title>-->
<title>凌空天行 AI大模型 应用系统</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@ -24,7 +24,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-07-26 16:43:22 * @LastEditTime: 2022-09-27 18:51:22
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -42,19 +42,22 @@ export default [
{ {
name: 'testList', name: 'testList',
title: '列表', title: '列表',
children: [
{
name: 'testAdd',
title: '添加',
},
{
name: 'testEdit',
title: '编辑',
},
]
}, },
{
name: 'testAdd', // {
title: '添加', // name: 'testAuth',
}, // title: '权限测试',
{ // },
name: 'testEdit',
title: '编辑',
},
{
name: 'testAuth',
title: '权限测试',
},
{ {
name: 'test-cache', name: 'test-cache',
title: '该页面可缓存', title: '该页面可缓存',
@ -83,12 +86,6 @@ export default [
}, },
] ]
if (query.role === 'admin')
childs.push({
name: 'testNoAuth',
title: '权限页面',
})
return { return {
code: 200, code: 200,
message: '获取菜单成功', message: '获取菜单成功',

View File

@ -3,7 +3,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 09:39:02 * @LastEditTime: 2022-09-25 12:27:50
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -44,4 +44,28 @@ export default [
data: null, data: null,
}, },
}, },
// 请求用户列表
{
url: '/api/test/users',
method: 'post',
timeout: 1000,
response: () => {
// 响应内容
return {
code: 200,
message: '获取成功',
data: {
'list|10': [
{
'id|+1': 1,
nickName: '@cname()',
userEmail: '@email()',
'status|1': [0, 1],
},
],
'total|50-1000': 1,
},
}
},
},
] ]

9534
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "vue3-element-admin", "name": "vue3-element-admin",
"version": "2.0.1", "version": "3.0.0",
"author": { "author": {
"name": "huzhushan", "name": "huzhushan",
"email": "huzhushan@126.com", "email": "huzhushan@126.com",
@ -21,19 +21,21 @@
], ],
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",
"vue": "^3.0.5", "vue": "^3.2.33",
"vue-router": "^4.0.5", "vue-router": "^4.0.5",
"vuex": "^4.0.0" "vuex": "^4.0.0",
"pinia": "^2.0.14"
}, },
"devDependencies": { "devDependencies": {
"@ehutch79/vite-eslint": "0.0.1", "@ehutch79/vite-eslint": "0.0.1",
"@vitejs/plugin-vue": "^1.1.5", "@vitejs/plugin-vue": "^1.2.3",
"@vue/compiler-sfc": "^3.0.5", "@vue/compiler-sfc": "^3.1.2",
"@vue/eslint-config-prettier": "^6.0.0", "@vue/eslint-config-prettier": "^6.0.0",
"autoprefixer": "^10.2.5", "autoprefixer": "^10.2.5",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"crypto-js": "^4.0.0", "crypto-js": "^4.0.0",
"element-plus": "^1.0.2-beta.71", "element-plus": "^2.2.13",
"vue-i18n": "^9.0.0",
"eslint": "^6.7.2", "eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.1.3", "eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-vue": "^7.0.0-0", "eslint-plugin-vue": "^7.0.0-0",
@ -42,7 +44,7 @@
"mockjs": "^1.1.0", "mockjs": "^1.1.0",
"prettier": "^1.19.1", "prettier": "^1.19.1",
"sass": "^1.41.1", "sass": "^1.41.1",
"vite": "^2.1.0", "vite": "^2.3.7",
"vite-plugin-mock": "^2.3.0", "vite-plugin-mock": "^2.3.0",
"vite-plugin-svg-icons": "^0.4.0" "vite-plugin-svg-icons": "^0.4.0"
}, },

BIN
public/ai_tu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

View File

@ -24,15 +24,14 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 13:17:44 * @LastEditTime: 2022-09-26 12:14:10
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
--> -->
<template> <template>
<el-config-provider :locale="locale"> <el-config-provider :locale="locales[lang]">
<router-view /> <router-view />
</el-config-provider> </el-config-provider>
</template> </template>
@ -40,16 +39,22 @@
<script> <script>
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import { ElConfigProvider } from 'element-plus' import { ElConfigProvider } from 'element-plus'
import localeZH from 'element-plus/es/locale/lang/zh-cn'
import zhCn from 'element-plus/lib/locale/lang/zh-cn' import localeEN from 'element-plus/es/locale/lang/en'
import useLang from '@/i18n/useLang'
export default defineComponent({ export default defineComponent({
components: { components: {
[ElConfigProvider.name]: ElConfigProvider, [ElConfigProvider.name]: ElConfigProvider,
}, },
data() { setup() {
const { lang } = useLang()
return { return {
locale: zhCn, lang,
locales: {
'zh-cn': localeZH,
en: localeEN,
},
} }
}, },
}) })
@ -63,5 +68,8 @@ body,
height: 100%; height: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
* {
outline: none;
}
} }
</style> </style>

16
src/api/convert.js Normal file
View File

@ -0,0 +1,16 @@
import request from '@/utils/request'
/**
* 调用外部接口转换MinIO路径传递完整data对象
* @param {Object} uploadData - uploadToMinIO返回的完整data
* @param {String} type - 'heat' 'strength'
*/
export function convertToDownloadUrl(uploadData, type) {
// ✅ 绝对不能带域名,必须是相对路径
const url =
type === 'heat'
? '/heat-api/api/calculate' // ✅ 正确:相对路径
: '/strength-api/api/convert/download-url'
return request({ url, method: 'post', data: uploadData, timeout: 600000 })
}

30
src/api/document.js Normal file
View File

@ -0,0 +1,30 @@
import request from '@/utils/request'
/**
* 搜索文档分页
*/
export function searchDocuments(params) {
return request({
url: '/api/document/search/summary/paged',
method: 'get',
params,
})
}
/**
* 获取文档详情
*/
export function getDocumentDetail(id) {
return request({
url: `/api/document/detail/${id}`,
method: 'get',
})
}
// 下载文档根据fileNo获取MinIO URL
export function downloadDocument(fileNo) {
return request({
url: `/api/document/download/search/${fileNo}`,
method: 'get',
})
}

31
src/api/excel.js Normal file
View File

@ -0,0 +1,31 @@
import request from '@/utils/request'
export function uploadToMinIOjgmodel(formData) {
return request({
url: '/api/upload/folderjgmodel',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
},
})
}
/**
* 上传 Excel 文件到后端
* @param {File} file - Excel 文件对象
* @returns {Promise} - 返回上传结果
*/
export function uploadExcelFile(file) {
const formData = new FormData()
formData.append('file', file)
return request({
url: '/api/document/upload',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
},
timeout: 60000, // 60秒超时Excel 解析可能需要较长时间
})
}

15
src/api/heat.js Normal file
View File

@ -0,0 +1,15 @@
import request from '@/utils/request'
// uploadToMinIO 增加超时和进度监听
export function uploadToMinIO(formData, onProgress) {
return request({
url: '/api/upload/folder',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
},
timeout: 600000, // ✅ 增加超时时间到10分钟
onUploadProgress: onProgress || function() {}, // ✅ 进度回调
})
}

28
src/api/image.js Normal file
View File

@ -0,0 +1,28 @@
import request from '@/utils/request'
/**
* 上传图片
*/
export function uploadImage(file) {
const formData = new FormData()
formData.append('file', file)
return request.post('/api/images/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
}
/**
* 模糊搜索图片
*/
export function searchImages(keyword) {
return request.get('/api/images/search', {
params: { keyword },
})
}
/**
* 获取原图高清 URL
*/
export function getOriginImage(id) {
return request.get(`/api/images/view/${id}`)
}

View File

@ -14,7 +14,8 @@ import request from '@/utils/request'
// 登录接口 // 登录接口
export const Login = data => { export const Login = data => {
return request({ return request({
url: '/api/login', // url: '/api/login',
url: '/admin/system/index/login',
method: 'post', method: 'post',
data, data,
}) })
@ -23,7 +24,16 @@ export const Login = data => {
// 获取登录用户信息 // 获取登录用户信息
export const GetUserinfo = () => { export const GetUserinfo = () => {
return request({ return request({
url: '/api/userinfo', // url: '/api/userinfo',
url: '/admin/system/index/getUserInfo',
method: 'get',
})
}
// 请求验证码的函数
export const GetValidateCode = () => {
return request({
url: '/admin/system/index/generateValidateCode',
method: 'get', method: 'get',
}) })
} }

View File

@ -1,3 +1,4 @@
;``
/* /*
* @Descripttion: * @Descripttion:
* @version: * @version:
@ -14,7 +15,7 @@ import request from '@/utils/request'
// 获取菜单 // 获取菜单
export const GetMenus = params => { export const GetMenus = params => {
return request({ return request({
url: '/api/menus', url: '/admin/system/index/menus',
method: 'get', method: 'get',
params, params,
}) })

48
src/api/rag.js Normal file
View File

@ -0,0 +1,48 @@
import request from '@/utils/request'
/**
* 提交RAG入库任务
* @param {string} folderId - 文件夹ID
* @returns {Promise} 任务提交结果
*/
// 提交RAG入库任务带角色参数
export function submitRagIngestTask(folderId, userRoleCode = 'default') {
return request({
url: '/api/rag/submit',
method: 'post',
data: {
folderId,
userRoleCode,
},
timeout: 50000, // 30秒超时
})
}
/**
* 查询RAG入库进度
* @param {string} taskId - 任务ID
* @returns {Promise} 进度信息
*/
export function getRagIngestProgress(taskId, userRoleCode = 'default') {
return request({
url: `/api/rag/progress/${taskId}`,
method: 'get',
params: { userRoleCode }, // 作为查询参数
timeout: 50000,
})
}
/**
* 上传文件夹到MinIO
* @param {FormData} formData - 包含files, relativePaths, sender, folderName
* @returns {Promise} 上传结果
*/
export function uploadFolder(formData) {
return request({
url: '/api/upload/folder',
method: 'post',
data: formData,
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 60000 * 10,
})
}

20
src/api/rasa.js Normal file
View File

@ -0,0 +1,20 @@
import request from '@/utils/request'
// 调用 Rasa AI 接口
// export const sendToAI = (data) => {
// return request({
// url: '/api/rasa/webhook',
// method: 'post',
// data
// })
// }
// 修改后
export function sendToAI(data) {
return request({
url: '/api/chat/message', // 新地址
method: 'post',
data,
timeout: 600000, // ✅ 增加超时时间到10分钟
})
}

10
src/api/sysFRole.js Normal file
View File

@ -0,0 +1,10 @@
import request from '@/utils/request'
// 获取当前用户角色
export function getUserRoles(userId) {
return request({
url: '/api/user/current-user-roles',
method: 'get',
params: { userId }, // ✅ 将用户ID作为查询参数传递
})
}

28
src/api/sysMenu.js Normal file
View File

@ -0,0 +1,28 @@
import request from '@/utils/request'
const api_name = '/admin/system/sysMenu'
// 分页列表
export const FindNodes = () => {
return request({
url: `${api_name}/findNodes`,
method: 'get',
})
}
// 保存信息
export const SaveSysMenu = sysMenu => {
return request({
url: `${api_name}/saveSysMenu`,
method: 'post',
data: sysMenu,
})
}
// 修改信息
export const UpdateSysMenu = sysMenu => {
return request({
url: `${api_name}/updateSysMenu`,
method: 'put',
data: sysMenu,
})
}

65
src/api/sysRole.js Normal file
View File

@ -0,0 +1,65 @@
import request from '@/utils/request'
// 获取菜单
export const GetSysRoleListByPage = (queryDto, page, limit) => {
return request({
// url: '/api/menus',
url: `/admin/system/sysRole/getSysRoleListByPage/${page}/${limit}`,
method: 'get',
params: queryDto,
})
}
//修改
export const UpdateSysRole = sysRole => {
return request({
// url: '/api/menus',
url: `/admin/system/sysRole/updateSysRole`,
method: 'put',
data: sysRole,
})
}
//添加
export const SaveSysRole = sysRole => {
return request({
// url: '/api/menus',
url: `/admin/system/sysRole/saveSysRole`,
method: 'post',
data: sysRole,
})
}
//删除
export const DeleteSysRoleById = roleId => {
return request({
// url: '/api/menus',
url: `/admin/system/sysRole/deleteSysRoleById/${roleId}`,
method: 'delete',
})
}
// 查询所有的角色数据
export const GetAllRoleList = userId => {
return request({
url: `/admin/system/sysRole/findAllRoles/${userId}`,
method: 'get',
})
}
// 查询指定角色所对应的菜单id
export const GetSysRoleMenuIds = roleId => {
return request({
url: '/admin/system/sysRoleMenu/findSysRoleMenuByRoleId/' + roleId,
method: 'get',
})
}
// 根据角色分配菜单请求方法
export const DoAssignMenuIdToSysRole = assignMenuDto => {
return request({
url: '/admin/system/sysRoleMenu/doAssign',
method: 'post',
data: assignMenuDto,
})
}

45
src/api/sysUser.js Normal file
View File

@ -0,0 +1,45 @@
import request from '@/utils/request'
// 登录接口
export const GetSysUserListByPage = (page, limit, queryDto) => {
return request({
url: `/admin/system/sysUser/getSysUserListByPage/${page}/${limit}`,
method: 'get',
params: queryDto,
})
}
// 修改
export const UpdateSysUser = sysUser => {
return request({
url: `/admin/system/sysUser/updateSysUser`,
method: 'put',
data: sysUser,
})
}
// 添加
export const SaveSysUser = sysUser => {
return request({
url: `/admin/system/sysUser/saveSysUser`,
method: 'post',
data: sysUser,
})
}
// 根据id删除用户
export const DeleteSysUserById = userId => {
return request({
url: `/admin/system/sysUser/deleteById/${userId}`,
method: 'delete',
})
}
// 给用户分配角色请求
export const DoAssignRoleToUser = assginRoleDto => {
return request({
url: '/admin/system/sysUserRole/doAssign',
method: 'post',
data: assginRoleDto,
})
}

View File

@ -3,7 +3,7 @@
* @version: * @version:
* @Date: 2021-04-20 16:35:04 * @Date: 2021-04-20 16:35:04
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 09:37:00 * @LastEditTime: 2022-09-25 11:50:39
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -18,3 +18,12 @@ export const TestError = () => {
method: 'get', method: 'get',
}) })
} }
// 用户列表
export const getUsers = data => {
return request({
url: '/api/test/users',
method: 'post',
data,
})
}

BIN
src/assets/ai_tu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

View File

@ -1,11 +1,14 @@
/* 改变主题色变量 */ /**如果需要修改其它变量可以在以下文件中查找
$--colors: ( * https://github.com/element-plus/element-plus/blob/dev/packages/theme-chalk/src/common/var.scss
'primary': ( */
'base': $mainColor,
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
$colors: (
'primary': (
'base': $mainColor,
),
), ),
); );
/* 改变 icon 字体路径变量,必需 */ @use "element-plus/theme-chalk/src/reset.scss" as *;
$--font-path: 'element-plus/lib/theme-chalk/fonts'; @use "element-plus/theme-chalk/src/index.scss" as *;
@import 'element-plus/packages/theme-chalk/src/reset';
@import 'element-plus/packages/theme-chalk/src/index';

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M510.72 41.856l435.2 361.024a32 32 0 0 1-36.16 52.48l-4.736-3.2-41.024-34.048V832c0 48.832-32.64 90.752-76.864 95.552l-8.448 0.448H245.312c-45.696 0-80.96-39.04-84.928-86.912L160 832V407.808l-55.04 44.608a32 32 0 0 1-40.96-0.64l-4.032-4.16a32 32 0 0 1 0.64-40.96l4.096-4.032L510.784 41.856z m-0.512 82.688L223.232 356.672a31.808 31.808 0 0 1 0.256 1.152l0.512 5.76V832c0 16.768 8.64 28.992 17.92 31.552l3.392 0.448h533.376c9.216 0 18.88-10.432 20.928-25.92L800 832V364.992L510.208 124.544z" /></svg>

After

Width:  |  Height:  |  Size: 766 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1664017268288" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2638" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M848.805886 805.572222c70.998007-81.260745 109.779266-184.217628 109.779266-293.14448 0-119.204939-46.421262-231.277434-130.713041-315.569212C744.876861 113.862257 634.94103 67.61598 517.788843 66.213028c-1.924839-0.599657-10.290367-0.592494-12.227486 0.01535C388.878868 67.945485 279.434224 114.159016 196.73471 196.85853 113.863281 279.730982 67.630307 389.460106 66.095347 506.415818c-0.428765 1.64957-0.436952 8.601912-0.021489 10.226922 1.082658 117.628024 47.364751 228.058113 130.660852 311.354214 84.291778 84.291778 196.36325 130.713041 315.569212 130.713041 119.204939 0 231.277434-46.421262 315.569212-130.713041 6.139837-6.139837 12.054547-12.444427 17.789155-18.871813 0.50756-0.453325 1.001817-0.928139 1.471514-1.440815C847.750857 807.012014 848.295256 806.299793 848.805886 805.572222zM107.447151 532.043499l187.501418 0c1.322112 65.678862 9.253758 127.264499 22.505573 182.112688-61.690014 16.687054-100.819197 38.371936-121.076566 51.906184C144.30971 701.336206 111.676475 620.35687 107.447151 532.043499zM195.881272 259.408121c20.090571 13.556761 59.242266 35.461653 121.340579 52.260248-12.998035 54.127781-20.827351 114.778116-22.243607 179.432649L107.525945 491.101018C112.076588 403.731134 144.437623 323.612399 195.881272 259.408121zM917.081898 491.099994 729.628576 491.099994c-1.415232-64.630996-9.240455-125.260865-22.229281-179.37432 61.95505-16.693194 101.235682-38.444591 121.56673-52.020794C880.270505 323.860039 912.537396 403.866211 917.081898 491.099994zM688.677908 491.099994 532.167319 491.099994 532.167319 335.061149c52.209082-1.094938 97.103572-6.453992 135.272893-14.033621C680.000272 373.163955 687.286212 430.896844 688.677908 491.099994zM532.167319 294.115598 532.167319 109.918435c36.84107 10.398838 72.779583 49.205679 100.926644 110.015649 8.810666 19.035542 16.645099 39.641859 23.464411 61.521169C621.531626 288.227494 580.261687 293.062616 532.167319 294.115598zM491.223814 110.273523l0 183.805236c-47.504944-1.12666-88.378863-6.001691-123.120109-12.802584 6.807033-21.812795 14.623046-42.35976 23.409153-61.344137C419.351903 159.792333 454.809463 121.175827 491.223814 110.273523zM491.223814 335.040682l0 156.059312L335.928912 491.099994c1.391696-60.213383 8.679683-117.955482 21.243837-170.099073C395.008472 328.536548 439.487499 333.887416 491.223814 335.040682zM335.893096 532.043499l155.330718 0 0 158.667719c-51.609425 1.194198-96.019891 6.563486-133.821845 14.103206C344.576873 651.927913 337.193719 593.243349 335.893096 532.043499zM491.223814 731.672118l0 182.909843c-36.415374-10.902304-71.871911-49.51881-99.709933-109.659539-8.679683-18.752086-16.409738-39.034015-23.157419-60.551074C402.9964 737.645157 443.773106 732.820268 491.223814 731.672118zM532.167319 914.937049 532.167319 731.608673c47.904033 1.025353 89.103364 5.862521 124.116809 12.656251-6.755868 21.555945-14.497179 41.87369-23.190165 60.656475C604.946902 865.73137 569.008388 904.538211 532.167319 914.937049zM532.167319 690.660052 532.167319 532.043499l156.546406 0c-1.298576 61.096497-8.66024 119.68487-21.445428 172.502819C629.154233 697.013761 584.319096 691.710988 532.167319 690.660052zM729.659275 532.043499l187.501418 0c-4.221138 88.138386-36.732599 168.973436-88.620363 233.635131-20.469194-13.668301-59.635215-35.298947-121.30374-51.868321C720.43724 659.049101 728.33921 597.585237 729.659275 532.043499zM801.518906 228.742704c-18.329461 11.570523-52.309366 29.355585-104.858186 43.493583-19.295462-63.056128-46.110177-115.004267-78.06189-150.97655C689.00025 140.410913 751.833297 178.097234 801.518906 228.742704zM406.007991 121.259738c-31.905664 35.920094-58.690704 87.768973-77.979002 150.702304-52.40351-14.241352-86.370113-32.099069-104.581893-43.587728C273.076422 177.914062 335.777463 140.364865 406.007991 121.259738zM223.917816 796.963147c18.284435-11.535731 52.098565-29.230742 104.332207-43.335994 19.271926 62.60485 45.976124 114.186645 77.757968 149.968593C335.99952 884.550994 273.472442 847.181899 223.917816 796.963147zM618.59883 903.595746c31.801287-35.803437 58.517765-87.426165 77.792761-150.08218 51.984978 14.023388 85.972047 31.631418 104.533798 43.208081C751.3329 847.061149 688.718841 884.521319 618.59883 903.595746z" p-id="2639"></path></svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -17,19 +17,16 @@
* @version: * @version:
* @Date: 2021-04-23 14:56:06 * @Date: 2021-04-23 14:56:06
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-23 15:00:31 * @LastEditTime: 2022-09-27 16:07:53
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/ */
import { storeToRefs } from 'pinia'
import { computed } from 'vue' import { useAccount } from '@/pinia/modules/account'
import { useStore } from 'vuex'
export const useUserinfo = () => { export const useUserinfo = () => {
const store = useStore() const { userinfo } = storeToRefs(useAccount())
const userinfo = computed(() => store.state.account.userinfo)
return { userinfo } return { userinfo }
} }

View File

@ -37,7 +37,7 @@
* @version: * @version:
* @Date: 2021-04-21 09:18:32 * @Date: 2021-04-21 09:18:32
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 12:45:38 * @LastEditTime: 2022-09-27 17:50:59
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -111,18 +111,20 @@
</template> </template>
<script> <script>
import { defineComponent, ref, computed } from 'vue' import { defineComponent, ref } from 'vue'
import { useStore } from 'vuex' import { useErrorlog } from '@/pinia/modules/errorLog'
import { storeToRefs } from 'pinia'
export default defineComponent({ export default defineComponent({
name: 'ErrorLog', name: 'ErrorLog',
setup() { setup() {
const dialogTableVisible = ref(false) const dialogTableVisible = ref(false)
const store = useStore() const errorStore = useErrorlog()
const errorLogs = computed(() => store.state.errorLog.logs) const { logs: errorLogs } = storeToRefs(errorStore)
const { clearErrorLog } = errorStore
const clearAll = () => { const clearAll = () => {
dialogTableVisible.value = false dialogTableVisible.value = false
store.dispatch('errorLog/clearErrorLog') clearErrorLog()
} }
return { return {

View File

@ -22,7 +22,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-29 15:04:03 * @LastEditTime: 2022-09-25 11:53:47
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -45,7 +45,7 @@
<el-form-item <el-form-item
v-for="item in search.fields" v-for="item in search.fields"
:key="item.name" :key="item.name"
:label="item.label" :label="$t(item.label)"
:prop="item.name" :prop="item.name"
> >
<slot v-if="item.type === 'custom'" :name="item.slot" /> <slot v-if="item.type === 'custom'" :name="item.slot" />
@ -55,13 +55,13 @@
:filterable="!!item.filterable" :filterable="!!item.filterable"
:multiple="!!item.multiple" :multiple="!!item.multiple"
clearable clearable
:placeholder="`请选择${item.label}`" :placeholder="$t(item.label)"
:style="{ width: search.inputWidth, ...item.style }" :style="{ width: search.inputWidth, ...item.style }"
> >
<el-option <el-option
v-for="option of item.options" v-for="option of item.options"
:key="option.value" :key="option.value"
:label="option.name" :label="$t(option.name)"
:value="option.value" :value="option.value"
></el-option> ></el-option>
</el-select> </el-select>
@ -75,7 +75,7 @@
:key="option.value" :key="option.value"
:label="option.value" :label="option.value"
> >
{{ option.name }} {{ $t(option.name) }}
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
<el-radio-group <el-radio-group
@ -88,7 +88,7 @@
:key="option.value" :key="option.value"
:label="option.value" :label="option.value"
> >
{{ option.name }} {{ $t(option.name) }}
</el-radio-button> </el-radio-button>
</el-radio-group> </el-radio-group>
<el-checkbox-group <el-checkbox-group
@ -101,7 +101,7 @@
:key="option.value" :key="option.value"
:label="option.value" :label="option.value"
> >
{{ option.name }} {{ $t(option.name) }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
<el-checkbox-group <el-checkbox-group
@ -114,7 +114,7 @@
:key="option.value" :key="option.value"
:label="option.value" :label="option.value"
> >
{{ option.name }} {{ $t(option.name) }}
</el-checkbox-button> </el-checkbox-button>
</el-checkbox-group> </el-checkbox-group>
<el-date-picker <el-date-picker
@ -124,7 +124,7 @@
format="YYYY-MM-DD" format="YYYY-MM-DD"
clearable clearable
@change="handleDateChange($event, item, 'YYYY-MM-DD')" @change="handleDateChange($event, item, 'YYYY-MM-DD')"
:placeholder="`请选择${item.label}`" :placeholder="$t(item.label)"
:style="{ width: search.inputWidth, ...item.style }" :style="{ width: search.inputWidth, ...item.style }"
></el-date-picker> ></el-date-picker>
<el-date-picker <el-date-picker
@ -134,7 +134,7 @@
clearable clearable
@change="handleDateChange($event, item, 'YYYY-MM-DD HH:mm:ss')" @change="handleDateChange($event, item, 'YYYY-MM-DD HH:mm:ss')"
format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss"
:placeholder="`请选择${item.label}`" :placeholder="$t(item.label)"
:style="{ width: search.inputWidth, ...item.style }" :style="{ width: search.inputWidth, ...item.style }"
></el-date-picker> ></el-date-picker>
<el-date-picker <el-date-picker
@ -143,11 +143,11 @@
type="daterange" type="daterange"
format="YYYY-MM-DD" format="YYYY-MM-DD"
range-separator="-" range-separator="-"
start-placeholder="开始日期" :start-placeholder="$t('public.startdate')"
end-placeholder="结束日期" :end-placeholder="$t('public.enddate')"
clearable clearable
@change="handleRangeChange($event, item, 'YYYY-MM-DD')" @change="handleRangeChange($event, item, 'YYYY-MM-DD')"
:style="{ width: search.inputWidth, ...item.style }" :style="{ ...item.style }"
></el-date-picker> ></el-date-picker>
<el-date-picker <el-date-picker
v-else-if="item.type === 'datetimerange'" v-else-if="item.type === 'datetimerange'"
@ -155,16 +155,16 @@
type="datetimerange" type="datetimerange"
format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss"
range-separator="-" range-separator="-"
start-placeholder="开始时间" :start-placeholder="$t('public.starttime')"
end-placeholder="结束时间" :end-placeholder="$t('public.endtime')"
clearable clearable
@change="handleRangeChange($event, item, 'YYYY-MM-DD HH:mm:ss')" @change="handleRangeChange($event, item, 'YYYY-MM-DD HH:mm:ss')"
:style="{ width: search.inputWidth, ...item.style }" :style="{ ...item.style }"
></el-date-picker> ></el-date-picker>
<el-input-number <el-input-number
v-else-if="item.type === 'number'" v-else-if="item.type === 'number'"
v-model="searchModel[item.name]" v-model="searchModel[item.name]"
:placeholder="`请输入${item.label}`" :placeholder="$t(item.label)"
controls-position="right" controls-position="right"
:min="item.min" :min="item.min"
:max="item.max" :max="item.max"
@ -176,7 +176,7 @@
type="textarea" type="textarea"
clearable clearable
v-model="searchModel[item.name]" v-model="searchModel[item.name]"
:placeholder="`请输入${item.label}`" :placeholder="$t(item.label)"
:style="{ width: search.inputWidth, ...item.style }" :style="{ width: search.inputWidth, ...item.style }"
></el-input> ></el-input>
<el-input <el-input
@ -184,16 +184,16 @@
:maxlength="item.maxlength" :maxlength="item.maxlength"
v-model="searchModel[item.name]" v-model="searchModel[item.name]"
clearable clearable
:placeholder="`请输入${item.label}`" :placeholder="$t(item.label)"
:style="{ width: search.inputWidth, ...item.style }" :style="{ width: search.inputWidth, ...item.style }"
></el-input> ></el-input>
</el-form-item> </el-form-item>
<el-form-item class="search-btn"> <el-form-item class="search-btn">
<el-button type="primary" icon="el-icon-search" @click="handleSearch"> <el-button type="primary" icon="Search" @click="handleSearch">
查询 {{ $t('public.search') }}
</el-button> </el-button>
<el-button @click="handleReset" icon="el-icon-refresh-right"> <el-button @click="handleReset" icon="RefreshRight">
重置 {{ $t('public.reset') }}
</el-button> </el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -224,6 +224,7 @@
:filter-method="item.filters && filterHandler" :filter-method="item.filters && filterHandler"
:show-overflow-tooltip="!item.wrap" :show-overflow-tooltip="!item.wrap"
v-bind="item" v-bind="item"
:label="item.label ? $t(item.label) : ''"
> >
<template #header="scope" v-if="!!item.labelSlot"> <template #header="scope" v-if="!!item.labelSlot">
<slot :name="item.labelSlot" v-bind="scope"></slot> <slot :name="item.labelSlot" v-bind="scope"></slot>
@ -511,6 +512,9 @@ export default defineComponent({
:deep(.el-input-number .el-input__inner) { :deep(.el-input-number .el-input__inner) {
text-align: left; text-align: left;
} }
:deep(.el-range-editor.el-input__wrapper) {
box-sizing: border-box;
}
} }
.head { .head {
@ -530,7 +534,7 @@ export default defineComponent({
.pagination { .pagination {
padding: 0 20px 20px; padding: 0 20px 20px;
background: #fff; background: #fff;
text-align: right; justify-content: flex-end;
:last-child { :last-child {
margin-right: 0; margin-right: 0;
} }

View File

@ -27,19 +27,19 @@
* @version: * @version:
* @Date: 2021-09-01 13:58:08 * @Date: 2021-09-01 13:58:08
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-29 10:10:32 * @LastEditTime: 2022-09-27 18:31:22
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/ */
import store from '@/store' import { useAccount } from '@/pinia/modules/account'
export const Permission = app => { export const Permission = app => {
app.directive('permission', { app.directive('permission', {
mounted: function(el, binding) { mounted: function(el, binding) {
const permissionList = store.state.account.permissionList || [] const { permissionList } = useAccount()
if ( if (
binding.value && binding.value &&

View File

@ -13,7 +13,7 @@
* @version: * @version:
* @Date: 2021-04-21 09:18:32 * @Date: 2021-04-21 09:18:32
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 12:48:49 * @LastEditTime: 2022-09-27 15:53:02
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -21,7 +21,8 @@
*/ */
import { nextTick } from 'vue' import { nextTick } from 'vue'
import store from '@/store' import { useErrorlog } from './pinia/modules/errorLog'
// import store from '@/store'
// 判断环境,决定是否开启错误监控 // 判断环境,决定是否开启错误监控
// - import.meta.env.DEV 布尔值,代表开发环境 // - import.meta.env.DEV 布尔值,代表开发环境
@ -34,7 +35,7 @@ export default app => {
if (flag) { if (flag) {
app.config.errorHandler = function(err, vm, info) { app.config.errorHandler = function(err, vm, info) {
nextTick(() => { nextTick(() => {
store.dispatch('errorLog/addErrorLog', { useErrorlog().addErrorLog({
err, err,
// vm, // 这里不保存vm否则渲染错误日志的时候控制台会有警告 // vm, // 这里不保存vm否则渲染错误日志的时候控制台会有警告
info, info,

View File

@ -14,7 +14,7 @@
* @version: * @version:
* @Date: 2021-09-18 09:32:01 * @Date: 2021-09-18 09:32:01
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 17:54:29 * @LastEditTime: 2022-09-24 14:43:31
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -23,4 +23,3 @@
export { default as SvgIcon } from '@/components/SvgIcon/index.vue' export { default as SvgIcon } from '@/components/SvgIcon/index.vue'
export { default as ProTable } from '@/components/ProTable/index.vue' export { default as ProTable } from '@/components/ProTable/index.vue'
export { default as ElSelectTree } from '@/components/SelectTree/index.vue'

View File

@ -27,23 +27,23 @@
* @version: * @version:
* @Date: 2021-08-20 11:15:27 * @Date: 2021-08-20 11:15:27
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 17:25:32 * @LastEditTime: 2022-09-27 16:15:56
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/ */
import { useTags } from '@/pinia/modules/tags'
import { reactive, toRefs, getCurrentInstance } from 'vue' import { reactive, toRefs, getCurrentInstance } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useStore } from 'vuex'
// 关闭当前标签 // 关闭当前标签
export default () => { export default () => {
const instance = getCurrentInstance() const instance = getCurrentInstance()
const store = useStore()
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const { delTag } = useTags()
const state = reactive({ const state = reactive({
/** /**
* @param {String} fullPath 要跳转到那个页面的地址 * @param {String} fullPath 要跳转到那个页面的地址
@ -52,7 +52,7 @@ export default () => {
* @return {*} * @return {*}
*/ */
closeTag({ fullPath, reload, f5 } = {}) { closeTag({ fullPath, reload, f5 } = {}) {
store.dispatch('tags/delTag', route) delTag(route)
fullPath ? router.push(fullPath) : router.back() fullPath ? router.push(fullPath) : router.back()
reload && reload &&
setTimeout(() => { setTimeout(() => {

19
src/i18n/index.js Normal file
View File

@ -0,0 +1,19 @@
import { createI18n } from 'vue-i18n'
const getMessage = modules => {
return Object.entries(modules).reduce((module, [path, mod]) => {
const moduleName = path.replace(/^\.\/locales\/[\w-]+\/(.*)\.\w+$/, '$1')
module[moduleName] = mod.default
return module
}, {})
}
export default createI18n({
locale: localStorage.getItem('__VEA__lang') || 'zh-cn',
messages: {
'zh-cn': getMessage(import.meta.globEager('./locales/zh-cn/**/*.js')),
en: getMessage(import.meta.globEager('./locales/en/**/*.js')),
},
legacy: false,
globalInjection: true,
})

View File

@ -0,0 +1,6 @@
export default {
noauth: 'No access',
servererror: 'Server error',
notfound: 'Page not found',
backhome: 'Back to Home',
}

View File

@ -0,0 +1,10 @@
export default {
username: 'Username',
password: 'Password',
login: 'Login',
logining: 'Login...',
loginsuccess: 'Success',
'rules-username': 'Please input username',
'rules-password': 'Please input password',
'rules-regpassword': '6 to 12 characters in length',
}

View File

@ -0,0 +1,16 @@
export default {
homepage: 'Homepage',
dashboard: 'Dashboard',
test: 'Test page',
testList: 'List',
testAdd: 'Add',
testEdit: 'Edit',
testAuth: 'Auth',
testNoAuth: 'No auth',
'test-cache': 'Cache',
'test-no-cache': 'No Cache',
nest: 'Nest page',
nestPage1: 'Page1',
nestPage2: 'Page2',
'test-error-log': 'Error log',
}

View File

@ -0,0 +1,22 @@
export default {
sure: 'Sure',
search: 'Search',
reset: 'Reset',
edit: 'Edit',
add: 'Add',
delete: 'Delete',
save: 'Save',
cancel: 'Cancel',
yes: 'Yes',
no: 'No',
status: 'Status',
operate: 'Operate',
enabled: 'Enabled',
disabled: 'Disabled',
male: 'Male',
female: 'Female',
startdate: 'From',
enddate: 'To',
starttime: 'From',
endtime: 'To',
}

View File

@ -0,0 +1,8 @@
export default {
refresh: 'Refresh',
close: 'Close',
other: 'Close other',
left: 'Close left',
right: 'Close right',
all: 'Close all',
}

View File

@ -0,0 +1,32 @@
export default {
title: 'List',
batchDelete: 'Batch delete',
add: 'Add one',
refresh: 'Refresh',
index: 'Index',
name: 'Nickname',
email: 'Email',
desc: 'Description',
publish: 'Published',
nopublish: 'Unpublished',
gender: 'Gender',
city: 'City',
bj: 'Beijing',
sh: 'Shanghai',
gz: 'Guangzhou',
sz: 'Shenzhen',
hobby: 'Hobby',
eat: 'Eat',
sleep: 'Sleep',
bit: 'Beat',
fruit: 'Fruit',
apple: 'Apple',
banana: 'Banana',
orange: 'Orange',
grape: 'Grape',
date: 'Date',
daterange: 'Date range',
time: 'Time',
timerange: 'Time range',
num: 'Number',
}

View File

@ -0,0 +1,21 @@
export default {
center: 'User center',
password: 'Modify password',
logout: 'Logout',
'lock-title': 'Lock screen',
'lock-password': 'Password',
'lock-rules-password': 'Please input Screen password',
'lock-locked': 'Screen Locked',
'lock-lock': 'Unlock',
'lock-relogin': 'Re-login',
'lock-rules-password2': 'Screen password or User password',
'lock-rules-password3': 'Password error',
'lock-error': 'Your account has been logged out, please log in directly',
'lock-week0': 'Sunday',
'lock-week1': 'Monday',
'lock-week2': 'Tuesday',
'lock-week3': 'Wednesday',
'lock-week4': 'Thursday',
'lock-week5': 'Friday',
'lock-week6': 'Saturday',
}

View File

@ -0,0 +1,6 @@
export default {
noauth: '您无权访问此页面',
servererror: '服务器出错了',
notfound: '您访问的页面不存在',
backhome: '返回首页',
}

View File

@ -0,0 +1,10 @@
export default {
username: '用户名',
password: '密码',
login: '登录',
logining: '登录中...',
loginsuccess: '登录成功',
'rules-username': '请输入用户名',
'rules-password': '请输入密码',
'rules-regpassword': '长度在 6 到 12 个字符',
}

View File

@ -0,0 +1,16 @@
export default {
homepage: '首页',
dashboard: '工作台',
test: '测试页面',
testList: '列表',
testAdd: '添加',
testEdit: '编辑',
testAuth: '权限测试',
testNoAuth: '权限页面',
'test-cache': '该页面可缓存',
'test-no-cache': '该页面不缓存',
nest: '二级页面',
nestPage1: 'Page1',
nestPage2: 'Page2',
'test-error-log': '测试错误日志',
}

View File

@ -0,0 +1,22 @@
export default {
sure: '确定',
search: '搜索',
reset: '重置',
edit: '编辑',
add: '新增',
delete: '删除',
save: '保存',
cancel: '取消',
yes: '是',
no: '否',
status: '状态',
operate: '操作',
enabled: '启用',
disabled: '禁用',
male: '男',
female: '女',
startdate: '开始日期',
enddate: '结束日期',
starttime: '开始时间',
endtime: '结束时间',
}

View File

@ -0,0 +1,8 @@
export default {
refresh: '刷新',
close: '关闭',
other: '关闭其它',
left: '关闭左侧',
right: '关闭右侧',
all: '关闭全部',
}

View File

@ -0,0 +1,32 @@
export default {
title: '列表',
batchDelete: '批量删除',
add: '添加一条',
refresh: '刷新',
index: '序号',
name: '昵称',
email: '邮箱',
desc: '描述',
publish: '已发布',
nopublish: '未发布',
gender: '性别',
city: '城市',
bj: '北京',
sh: '上海',
gz: '广州',
sz: '深圳',
hobby: '爱好',
eat: '吃饭',
sleep: '睡觉',
bit: '打豆豆',
fruit: '水果',
apple: '苹果',
banana: '香蕉',
orange: '橘子',
grape: '葡萄',
date: '日期',
daterange: '日期范围',
time: '时间',
timerange: '时间范围',
num: '数量',
}

View File

@ -0,0 +1,21 @@
export default {
center: '个人中心',
password: '修改密码',
logout: '退出登录',
'lock-title': '锁定屏幕',
'lock-password': '锁屏密码',
'lock-rules-password': '请输入锁屏密码',
'lock-locked': '屏幕已锁定',
'lock-lock': '解锁',
'lock-relogin': '重新登录',
'lock-rules-password2': '请输入锁屏密码或登录密码',
'lock-rules-password3': '密码错误',
'lock-error': '您的账号已退出,请直接登录',
'lock-week0': '星期日',
'lock-week1': '星期一',
'lock-week2': '星期二',
'lock-week3': '星期三',
'lock-week4': '星期四',
'lock-week5': '星期五',
'lock-week6': '星期六',
}

16
src/i18n/useLang.js Normal file
View File

@ -0,0 +1,16 @@
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
export default function useLang() {
const i18n = useI18n()
const lang = computed(() => i18n.locale.value)
const changeLang = value => {
i18n.locale.value = value
localStorage.setItem('__VEA__lang', value)
}
return {
i18n,
lang,
changeLang,
}
}

1516
src/layout/chat-layout.css Normal file

File diff suppressed because it is too large Load Diff

2370
src/layout/chat_layout.vue Normal file

File diff suppressed because it is too large Load Diff

View File

@ -27,7 +27,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 12:46:17 * @LastEditTime: 2022-09-27 16:27:54
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -42,15 +42,15 @@
</router-view> </router-view>
</template> </template>
<script> <script>
import { storeToRefs } from 'pinia'
import { computed, defineComponent } from 'vue' import { computed, defineComponent } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useStore } from 'vuex' import { useTags } from '@/pinia/modules/tags'
export default defineComponent({ export default defineComponent({
setup() { setup() {
const store = useStore()
const route = useRoute() const route = useRoute()
const cacheList = computed(() => store.state.tags.cacheList) const { cacheList } = storeToRefs(useTags())
const key = computed(() => route.fullPath) const key = computed(() => route.fullPath)
return { return {

View File

@ -24,7 +24,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 12:46:37 * @LastEditTime: 2022-09-24 19:33:12
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -32,9 +32,9 @@
--> -->
<template> <template>
<i v-if="isElementIcon" :class="`icon ${icon}`" /> <svg-icon class="icon" v-if="isCustomSvg" :name="icon" />
<svg-icon class="icon" v-else-if="!!icon" :name="icon" /> <component :is="icon" v-else-if="!!icon" class="icon" />
<span>{{ title }}</span> <span>{{ $t(title) }}</span>
</template> </template>
<script> <script>
@ -43,10 +43,10 @@ import { computed, defineComponent } from 'vue'
export default defineComponent({ export default defineComponent({
props: ['title', 'icon'], props: ['title', 'icon'],
setup({ icon }) { setup({ icon }) {
const isElementIcon = computed(() => icon && icon.startsWith('el-icon')) const isCustomSvg = computed(() => icon && icon.startsWith('icon-'))
return { return {
isElementIcon, isCustomSvg,
} }
}, },
}) })

View File

@ -34,8 +34,9 @@
<template> <template>
<div class="brand"> <div class="brand">
<img class="logo" src="~@/assets/logo.svg" @click="goHome" /> <!-- <img class="logo" src="~@/assets/logo.svg" @click="goHome" />-->
<div class="title">Vue3 Element Admin</div> <img class="logo" src="~@/assets/ai_tu.png" @click="goHome" />
<div class="title">凌空天行</div>
</div> </div>
</template> </template>
<script> <script>

View File

@ -27,7 +27,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-07-26 16:02:28 * @LastEditTime: 2022-09-27 16:45:42
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -40,7 +40,7 @@
class="menu" class="menu"
:mode="mode" :mode="mode"
:collapse="collapse" :collapse="collapse"
:uniqueOpened="true" :uniqueOpened="false"
:router="true" :router="true"
:default-active="activePath" :default-active="activePath"
:background-color="variables.menuBg" :background-color="variables.menuBg"
@ -54,9 +54,11 @@
<script> <script>
import { computed, defineComponent } from 'vue' import { computed, defineComponent } from 'vue'
import Submenu from './Submenu.vue' import Submenu from './Submenu.vue'
import { useStore } from 'vuex'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import config from './config/menu.module.scss' import config from './config/menu.module.scss'
import { storeToRefs } from 'pinia'
import { useMenus } from '@/pinia/modules/menu'
import { useApp } from '@/pinia/modules/app'
export default defineComponent({ export default defineComponent({
components: { components: {
@ -74,11 +76,17 @@ export default defineComponent({
}, },
setup() { setup() {
const route = useRoute() const route = useRoute()
const store = useStore() const { menus } = storeToRefs(useMenus())
const appStore = useApp()
const { matchedRoutes } = storeToRefs(appStore)
return { return {
menus: computed(() => store.state.menu.menus), menus,
activePath: computed(() => route.path), activePath: computed(() => {
return matchedRoutes.value.length > 1
? matchedRoutes.value[1].path
: route.path
}),
variables: computed(() => config), variables: computed(() => config),
} }
}, },
@ -87,15 +95,15 @@ export default defineComponent({
<style lang="scss"> <style lang="scss">
// menu hover // menu hover
.el-menu-item, .el-menu-item,
.el-submenu__title { .el-sub-menu__title {
&:hover { &:hover {
background-color: $menuHover !important; background-color: $menuHover !important;
} }
} }
.el-submenu { .el-sub-menu {
.el-menu-item, .el-menu-item,
.el-submenu .el-submenu__title { .el-sub-menu .el-sub-menu__title {
background-color: $subMenuBg !important; background-color: $subMenuBg !important;
&:hover { &:hover {
@ -112,7 +120,7 @@ export default defineComponent({
.el-menu--collapse { .el-menu--collapse {
.el-menu-item.is-active, .el-menu-item.is-active,
.el-submenu.is-active > .el-submenu__title { .el-sub-menu.is-active > .el-sub-menu__title {
position: relative; position: relative;
background-color: $collapseMenuActiveBg !important; background-color: $collapseMenuActiveBg !important;
color: $collapseMenuActiveColor !important; color: $collapseMenuActiveColor !important;
@ -128,19 +136,20 @@ export default defineComponent({
} }
} }
.el-submenu__title i { .el-sub-menu__title i {
color: $arrowColor; color: $arrowColor;
} }
// //
.el-menu--horizontal { .el-menu--horizontal {
.el-menu-item, .el-menu-item,
.el-submenu .el-submenu__title { .el-sub-menu .el-sub-menu__title {
height: $horizontalMenuHeight; height: $horizontalMenuHeight;
line-height: $horizontalMenuHeight; line-height: $horizontalMenuHeight;
border-bottom: none;
} }
.el-menu-item.is-active, .el-menu-item.is-active,
.el-submenu.is-active .el-submenu__title { .el-sub-menu.is-active .el-sub-menu__title {
border: none; border: none;
} }
} }

View File

@ -13,7 +13,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 12:46:55 * @LastEditTime: 2022-09-24 16:44:28
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -24,7 +24,7 @@
<el-menu-item v-if="!menu.children" :index="menu.url"> <el-menu-item v-if="!menu.children" :index="menu.url">
<item :icon="menu.icon" :title="menu.title" /> <item :icon="menu.icon" :title="menu.title" />
</el-menu-item> </el-menu-item>
<el-submenu v-else :index="menu.url"> <el-sub-menu v-else :index="menu.url">
<template #title> <template #title>
<item :icon="menu.icon" :title="menu.title" /> <item :icon="menu.icon" :title="menu.title" />
</template> </template>
@ -34,7 +34,7 @@
:is-nest="true" :is-nest="true"
:menu="submenu" :menu="submenu"
/> />
</el-submenu> </el-sub-menu>
</template> </template>
<script> <script>
import { defineComponent } from 'vue' import { defineComponent } from 'vue'

View File

@ -25,7 +25,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-07-23 09:36:42 * @LastEditTime: 2022-09-27 18:45:07
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -44,10 +44,11 @@
</template> </template>
<script> <script>
import { defineComponent, computed } from 'vue' import { useApp } from '@/pinia/modules/app'
import { storeToRefs } from 'pinia'
import { computed, defineComponent } from 'vue'
import Logo from './Logo.vue' import Logo from './Logo.vue'
import Menus from './Menus.vue' import Menus from './Menus.vue'
import { useStore } from 'vuex'
export default defineComponent({ export default defineComponent({
components: { components: {
@ -55,12 +56,13 @@ export default defineComponent({
Menus, Menus,
}, },
setup() { setup() {
const store = useStore() const appStore = useApp()
const collapse = computed(() => !!store.state.app.sidebar.collapse) const { sidebar, device } = storeToRefs(appStore)
const device = computed(() => store.state.app.device) const { setCollapse } = appStore
const collapse = computed(() => sidebar.value.collapse)
const closeSidebar = () => { const closeSidebar = () => {
store.commit('app/setCollapse', 1) setCollapse(1)
} }
return { return {

View File

@ -26,23 +26,24 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-29 10:52:41 * @LastEditTime: 2022-09-27 16:52:30
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/ */
import { useTags } from '@/pinia/modules/tags'
import { onMounted, onBeforeUnmount, reactive, toRefs, nextTick } from 'vue' import { onMounted, onBeforeUnmount, reactive, toRefs, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { isAffix } from './useTags' import { isAffix } from './useTags'
export const useContextMenu = tagList => { export const useContextMenu = tagList => {
const store = useStore()
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const tagsStore = useTags()
const state = reactive({ const state = reactive({
visible: false, visible: false,
top: 0, top: 0,
@ -58,7 +59,7 @@ export const useContextMenu = tagList => {
state.visible = false state.visible = false
}, },
refreshSelectedTag(tag) { refreshSelectedTag(tag) {
store.commit('tags/DEL_CACHE_LIST', tag) tagsStore.deCacheList(tag)
const { fullPath } = tag const { fullPath } = tag
nextTick(() => { nextTick(() => {
router.replace({ router.replace({
@ -72,13 +73,13 @@ export const useContextMenu = tagList => {
const closedTagIndex = tagList.value.findIndex( const closedTagIndex = tagList.value.findIndex(
item => item.fullPath === tag.fullPath item => item.fullPath === tag.fullPath
) )
store.dispatch('tags/delTag', tag) tagsStore.delTag(tag)
if (isActive(tag)) { if (isActive(tag)) {
toLastTag(closedTagIndex - 1) toLastTag(closedTagIndex - 1)
} }
}, },
closeOtherTags() { closeOtherTags() {
store.dispatch('tags/delOtherTags', state.selectedTag) tagsStore.delOtherTags(state.selectedTag)
router.push(state.selectedTag) router.push(state.selectedTag)
}, },
closeLeftTags() { closeLeftTags() {
@ -103,11 +104,11 @@ export const useContextMenu = tagList => {
direction === 'left' direction === 'left'
? tagList.value.slice(0, index) ? tagList.value.slice(0, index)
: tagList.value.slice(index + 1) : tagList.value.slice(index + 1)
store.dispatch('tags/delSomeTags', needToClose) tagsStore.delSomeTags(needToClose)
router.push(state.selectedTag) router.push(state.selectedTag)
}, },
closeAllTags() { closeAllTags() {
store.dispatch('tags/delAllTags') tagsStore.delAllTags()
router.push('/') router.push('/')
}, },
}) })

View File

@ -24,7 +24,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 12:47:07 * @LastEditTime: 2022-08-13 14:50:23
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -35,15 +35,28 @@ import { ref } from 'vue'
export const useScrollbar = tagsItem => { export const useScrollbar = tagsItem => {
const scrollContainer = ref(null) const scrollContainer = ref(null)
const scrollLeft = ref(0)
const doScroll = val => {
scrollLeft.value = val
scrollContainer.value.setScrollLeft(scrollLeft.value)
}
const handleScroll = e => { const handleScroll = e => {
const $wrap = scrollContainer.value.wrap$
if ($wrap.offsetWidth + scrollLeft.value > $wrap.children[0].scrollWidth) {
doScroll($wrap.children[0].scrollWidth - $wrap.offsetWidth)
return
} else if (scrollLeft.value < 0) {
doScroll(0)
return
}
const eventDelta = e.wheelDelta || -e.deltaY const eventDelta = e.wheelDelta || -e.deltaY
scrollContainer.value.wrap.scrollLeft -= eventDelta / 4 doScroll(scrollLeft.value - eventDelta / 4)
} }
const moveToTarget = currentTag => { const moveToTarget = currentTag => {
const containerWidth = scrollContainer.value.scrollbar.offsetWidth const $wrap = scrollContainer.value.wrap$
const scrollWrapper = scrollContainer.value.wrap
const tagList = tagsItem.value const tagList = tagsItem.value
let firstTag = null let firstTag = null
@ -54,15 +67,15 @@ export const useScrollbar = tagsItem => {
lastTag = tagList[tagList.length - 1] lastTag = tagList[tagList.length - 1]
} }
if (firstTag === currentTag) { if (firstTag === currentTag) {
scrollWrapper.scrollLeft = 0 doScroll(0)
} else if (lastTag === currentTag) { } else if (lastTag === currentTag) {
scrollWrapper.scrollLeft = scrollWrapper.scrollWidth - containerWidth doScroll($wrap.children[0].scrollWidth - $wrap.offsetWidth)
} else { } else {
const el = currentTag.$el.nextElementSibling const el = currentTag.$el.nextElementSibling
scrollWrapper.scrollLeft =
el.offsetLeft + el.offsetWidth > containerWidth el.offsetLeft + el.offsetWidth > $wrap.offsetWidth
? el.offsetLeft - el.offsetWidth ? doScroll(el.offsetLeft - el.offsetWidth)
: 0 : doScroll(0)
} }
} }

View File

@ -24,28 +24,29 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-29 17:13:31 * @LastEditTime: 2022-09-27 18:28:33
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/ */
import { storeToRefs } from 'pinia'
import { useTags as useTagsbar } from '@/pinia/modules/tags'
import { useScrollbar } from './useScrollbar' import { useScrollbar } from './useScrollbar'
import { watch, computed, ref, nextTick, onBeforeMount } from 'vue' import { watch, computed, ref, nextTick, onBeforeMount } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
export const isAffix = tag => { export const isAffix = tag => {
return !!tag.meta && !!tag.meta.affix return !!tag.meta && !!tag.meta.affix
} }
export const useTags = () => { export const useTags = () => {
const store = useStore() const tagStore = useTagsbar()
const { tagList } = storeToRefs(tagStore)
const { addTag, delTag, saveActivePosition, updateTagList } = tagStore
const router = useRouter() const router = useRouter()
const route = router.currentRoute const route = router.currentRoute
const routes = computed(() => router.getRoutes()) const routes = computed(() => router.getRoutes())
const tagList = computed(() => store.state.tags.tagList)
const tagsItem = ref([]) const tagsItem = ref([])
@ -71,24 +72,31 @@ export const useTags = () => {
for (const tag of affixTags) { for (const tag of affixTags) {
if (tag.name) { if (tag.name) {
store.dispatch('tags/addTag', tag) addTag(tag)
} }
} }
// 不在路由中的所有标签,需要删除
const noUseTags = tagList.value.filter(tag =>
routes.value.every(route => route.name !== tag.name)
)
noUseTags.forEach(tag => {
delTag(tag)
})
} }
const addTag = () => { const addTagList = () => {
const tag = route.value const tag = route.value
if (!!tag.name && tag.matched[0].components.default.name === 'layout') { if (!!tag.name && tag.matched[0].components.default.name === 'layout') {
store.dispatch('tags/addTag', tag) addTag(tag)
} }
} }
const saveActivePosition = tag => { const saveTagPosition = tag => {
const index = tagList.value.findIndex( const index = tagList.value.findIndex(
item => item.fullPath === tag.fullPath item => item.fullPath === tag.fullPath
) )
store.dispatch('tags/saveActivePosition', Math.max(0, index)) saveActivePosition(Math.max(0, index))
} }
const moveToCurrentTag = () => { const moveToCurrentTag = () => {
@ -98,7 +106,7 @@ export const useTags = () => {
scrollbar.moveToTarget(tag) scrollbar.moveToTarget(tag)
if (tag.to.fullPath !== route.value.fullPath) { if (tag.to.fullPath !== route.value.fullPath) {
store.dispatch('tags/updateTagList', route.value) updateTagList(route.value)
} }
break break
} }
@ -108,13 +116,13 @@ export const useTags = () => {
onBeforeMount(() => { onBeforeMount(() => {
initTags() initTags()
addTag() addTagList()
moveToCurrentTag() moveToCurrentTag()
}) })
watch(route, (newRoute, oldRoute) => { watch(route, (newRoute, oldRoute) => {
saveActivePosition(oldRoute) // 保存标签的位置 saveTagPosition(oldRoute) // 保存标签的位置
addTag() addTagList()
moveToCurrentTag() moveToCurrentTag()
}) })

View File

@ -13,7 +13,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 17:50:46 * @LastEditTime: 2022-09-24 20:38:36
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -43,12 +43,15 @@
@click.middle="closeTag(tag)" @click.middle="closeTag(tag)"
@contextmenu.prevent="openMenu(tag, $event)" @contextmenu.prevent="openMenu(tag, $event)"
> >
<span class="title">{{ tag.title }}</span> <span class="title">{{ $t(tag.title) }}</span>
<span
<el-icon
v-if="!isAffix(tag)" v-if="!isAffix(tag)"
class="el-icon-close" class="el-icon-close"
@click.prevent.stop="closeTag(tag)" @click.prevent.stop="closeTag(tag)"
/> >
<Close />
</el-icon>
</div> </div>
</router-link> </router-link>
</el-scrollbar> </el-scrollbar>
@ -58,12 +61,14 @@
:style="{ left: left + 'px', top: top + 'px' }" :style="{ left: left + 'px', top: top + 'px' }"
class="contextmenu" class="contextmenu"
> >
<li @click="refreshSelectedTag(selectedTag)">刷新</li> <li @click="refreshSelectedTag(selectedTag)">{{ $t('tags.refresh') }}</li>
<li v-if="!isAffix(selectedTag)" @click="closeTag(selectedTag)">关闭</li> <li v-if="!isAffix(selectedTag)" @click="closeTag(selectedTag)">
<li @click="closeOtherTags">关闭其他</li> {{ $t('tags.close') }}
<li @click="closeLeftTags">关闭左侧</li> </li>
<li @click="closeRightTags">关闭右侧</li> <li @click="closeOtherTags">{{ $t('tags.other') }}</li>
<li @click="closeAllTags">关闭全部</li> <li @click="closeLeftTags">{{ $t('tags.left') }}</li>
<li @click="closeRightTags">{{ $t('tags.right') }}</li>
<li @click="closeAllTags">{{ $t('tags.all') }}</li>
</ul> </ul>
</template> </template>
@ -71,7 +76,7 @@
import { defineComponent, computed, getCurrentInstance } from 'vue' import { defineComponent, computed, getCurrentInstance } from 'vue'
import { useTags } from './hooks/useTags' import { useTags } from './hooks/useTags'
import { useContextMenu } from './hooks/useContextMenu' import { useContextMenu } from './hooks/useContextMenu'
import { useStore } from 'vuex' import { useLayoutsettings } from '@/pinia/modules/layoutSettings'
export default defineComponent({ export default defineComponent({
name: 'Tagsbar', name: 'Tagsbar',
@ -80,9 +85,8 @@ export default defineComponent({
instance.appContext.config.globalProperties.$tagsbar = this instance.appContext.config.globalProperties.$tagsbar = this
}, },
setup() { setup() {
const store = useStore() const defaultSettings = useLayoutsettings()
const defaultSettings = computed(() => store.state.layoutSettings) const isTagsbarShow = computed(() => defaultSettings.tagsbar.isShow)
const isTagsbarShow = computed(() => defaultSettings.value.tagsbar.isShow)
const tags = useTags() const tags = useTags()
const contextMenu = useContextMenu(tags.tagList) const contextMenu = useContextMenu(tags.tagList)
@ -156,7 +160,7 @@ export default defineComponent({
margin-left: 8px; margin-left: 8px;
width: 16px; width: 16px;
height: 16px; height: 16px;
vertical-align: 2px; vertical-align: -2px;
border-radius: 50%; border-radius: 50%;
text-align: center; text-align: center;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);

View File

@ -27,7 +27,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-07-23 17:22:14 * @LastEditTime: 2022-09-27 17:38:48
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -50,37 +50,68 @@
:class="{ no_link: index === breadcrumbs.length - 1 }" :class="{ no_link: index === breadcrumbs.length - 1 }"
:to="index < breadcrumbs.length - 1 ? item.path : ''" :to="index < breadcrumbs.length - 1 ? item.path : ''"
> >
{{ item.meta.title }} {{ $t(item.meta.title) }}
</el-breadcrumb-item> </el-breadcrumb-item>
</el-breadcrumb> </el-breadcrumb>
</template> </template>
<script> <script>
import { defineComponent, computed, ref, onBeforeMount, watch } from 'vue' import { useApp } from '@/pinia/modules/app'
import { useLayoutsettings } from '@/pinia/modules/layoutSettings'
import { storeToRefs } from 'pinia'
import {
defineComponent,
computed,
ref,
onBeforeMount,
watch,
getCurrentInstance,
} from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
export default defineComponent({ export default defineComponent({
setup(props, { emit }) { setup(props, { emit }) {
const store = useStore() const { proxy } = getCurrentInstance()
const device = computed(() => store.state.app.device) const appStore = useApp()
const { device } = storeToRefs(appStore)
const router = useRouter() const router = useRouter()
const route = router.currentRoute // 使useRoutewatch const route = router.currentRoute // 使useRoutewatch
const breadcrumbs = ref([]) const breadcrumbs = ref([])
const defaultSettings = computed(() => store.state.layoutSettings) const defaultSettings = useLayoutsettings()
const isHorizontalMenu = computed( const isHorizontalMenu = computed(
() => defaultSettings.value.menus.mode === 'horizontal' () => defaultSettings.menus.mode === 'horizontal'
) )
const allRoutes = router.getRoutes()
const recursionArr = (matched, route) => {
const parent = route.meta.parent
if (parent) {
const parentRoute = allRoutes.find(item => item.name === parent)
if (parentRoute && parentRoute.meta && parentRoute.meta.title) {
matched.splice(
matched.findIndex(item => item.name === route.name),
0,
parentRoute
)
recursionArr(matched, parentRoute)
}
}
}
const getBreadcrumbs = route => { const getBreadcrumbs = route => {
const home = [{ path: '/', meta: { title: '首页' } }] const home = [{ path: '/', meta: { title: proxy.$t('menu.homepage') } }]
if (route.name === 'home') { if (route.name === 'home') {
appStore.setMatchedRoutes(home)
return home return home
} else { } else {
const matched = route.matched.filter( const matched = route.matched.filter(
item => !!item.meta && !!item.meta.title item => !!item.meta && !!item.meta.title
) )
return [...home, ...matched] // return [...home, ...matched]
recursionArr(matched, route)
appStore.setMatchedRoutes(matched)
return matched
} }
} }
@ -91,6 +122,7 @@ export default defineComponent({
watch( watch(
route, route,
newRoute => { newRoute => {
route.value.meta.truetitle = proxy.$t(route.value.meta.title)
breadcrumbs.value = getBreadcrumbs(newRoute) breadcrumbs.value = getBreadcrumbs(newRoute)
emit('on-breadcrumbs-change', breadcrumbs.value.length > 1) emit('on-breadcrumbs-change', breadcrumbs.value.length > 1)
}, },
@ -118,9 +150,9 @@ export default defineComponent({
::v-deep(.is-link) { ::v-deep(.is-link) {
font-weight: normal; font-weight: normal;
} }
::v-deep(.el-breadcrumb__item) { // ::v-deep(.el-breadcrumb__item) {
float: none; // float: none;
} // }
.no_link { .no_link {
::v-deep(.el-breadcrumb__inner) { ::v-deep(.el-breadcrumb__inner) {
color: #97a8be !important; color: #97a8be !important;

View File

@ -0,0 +1,94 @@
<!--
* _oo0oo_
* o8888888o
* 88" . "88
* (| -_- |)
* 0\ = /0
* ___/`---'\___
* .' \\| |// '.
* / \\||| : |||// \
* / _||||| -:- |||||- \
* | | \\\ - /// | |
* | \_| ''\---/'' |_/ |
* \ .-\__ '-' ___/-. /
* ___'. .' /--.--\ `. .'___
* ."" '< `.___\_<|>_/___.' >' "".
* | | : `- \`.;`\ _ /`;.`/ - ` : | |
* \ \ `_. \_ __\ /__ _/ .-` / /
* =====`-.____`.___ \_____/___.-`___.-'=====
* `=---='
*
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* 佛祖保佑 永不宕机 永无BUG
*
* 佛曰:
* 写字楼里写字间写字间里程序员
* 程序人员写程序又拿程序换酒钱
* 酒醒只在网上坐酒醉还来网下眠
* 酒醉酒醒日复日网上网下年复年
* 但愿老死电脑间不愿鞠躬老板前
* 奔驰宝马贵者趣公交自行程序员
* 别人笑我忒疯癫我笑自己命太贱
* 不见满街漂亮妹哪个归得程序员
*
* @Descripttion:
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2022-09-26 11:53:15
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
-->
<template>
<el-dropdown trigger="hover">
<div class="change-lang">
<svg-icon name="language" class="icon"></svg-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
@click="changeLang(item.value)"
v-for="item in langlist"
:key="item.value"
>
{{ item.name }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup>
import useLang from '@/i18n/useLang'
const langlist = [
{
name: '简体中文',
value: 'zh-cn',
},
{
name: 'English',
value: 'en',
},
]
const { changeLang } = useLang()
</script>
<style lang="scss" scoped>
.change-lang {
padding: 0 16px;
height: 48px;
cursor: pointer;
display: flex;
align-items: center;
&:hover {
background: #f5f5f5;
}
.icon {
font-size: 18px;
}
}
</style>

View File

@ -18,7 +18,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 12:47:40 * @LastEditTime: 2022-09-27 18:43:44
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -26,25 +26,30 @@
--> -->
<template> <template>
<i <el-icon
class="fold-btn el-icon-s-fold" :size="20"
class="fold-btn"
:class="{ collapse: collapse }" :class="{ collapse: collapse }"
@click="handleToggleMenu" @click="handleToggleMenu"
></i> >
<Fold />
</el-icon>
</template> </template>
<script> <script>
import { defineComponent, computed } from 'vue' import { useApp } from '@/pinia/modules/app'
import { useStore } from 'vuex' import { storeToRefs } from 'pinia'
import { computed, defineComponent } from 'vue'
export default defineComponent({ export default defineComponent({
setup() { setup() {
const store = useStore() const appStore = useApp()
const collapse = computed(() => !!store.state.app.sidebar.collapse) const { sidebar } = storeToRefs(appStore)
const { setCollapse } = appStore
const handleToggleMenu = () => { const handleToggleMenu = () => {
store.commit('app/setCollapse', +!collapse.value) setCollapse(+!sidebar.value.collapse)
} }
return { return {
collapse, collapse: computed(() => sidebar.value.collapse),
handleToggleMenu, handleToggleMenu,
} }
}, },
@ -54,11 +59,7 @@ export default defineComponent({
.fold-btn { .fold-btn {
line-height: 48px; line-height: 48px;
padding: 0 10px; padding: 0 10px;
font-size: 18px;
cursor: pointer; cursor: pointer;
&:hover {
background: #f5f5f5;
}
&.collapse { &.collapse {
transform: scale(-1, 1); transform: scale(-1, 1);
} }

View File

@ -44,7 +44,7 @@
* @version: * @version:
* @Date: 2021-04-23 14:15:50 * @Date: 2021-04-23 14:15:50
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-28 09:23:23 * @LastEditTime: 2022-09-27 17:55:16
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -52,17 +52,24 @@
--> -->
<template> <template>
<el-dropdown-item @click="dialogVisible = true">锁定屏幕</el-dropdown-item> <el-dropdown-item @click="dialogVisible = true">
{{ $t('topbar.lock-title') }}
</el-dropdown-item>
<el-dialog <el-dialog
title="锁定屏幕" :title="$t('topbar.lock-title')"
v-model="dialogVisible" v-model="dialogVisible"
width="640px" width="640px"
custom-class="lock-modal" custom-class="lock-modal"
append-to-body append-to-body
> >
<Avatar /> <Avatar />
<el-form :model="lockModel" :rules="lockRules" ref="lockForm"> <el-form
<el-form-item label="锁屏密码" prop="password"> :model="lockModel"
:rules="lockRules"
ref="lockForm"
label-width="90px"
>
<el-form-item :label="$t('topbar.lock-password')" prop="password">
<el-input <el-input
type="password" type="password"
v-model.trim="lockModel.password" v-model.trim="lockModel.password"
@ -71,13 +78,8 @@
></el-input> ></el-input>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button <el-button class="submit-btn" type="primary" @click="submitForm">
size="small" {{ $t('topbar.lock-title') }}
class="submit-btn"
type="primary"
@click="submitForm"
>
锁定屏幕
</el-button> </el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -85,25 +87,27 @@
</template> </template>
<script> <script>
import { defineComponent, reactive, ref } from 'vue' import { defineComponent, getCurrentInstance, reactive, ref } from 'vue'
import Avatar from '@/components/Avatar/index.vue' import Avatar from '@/components/Avatar/index.vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useStore } from 'vuex' import { useApp } from '@/pinia/modules/app'
export default defineComponent({ export default defineComponent({
components: { components: {
Avatar, Avatar,
}, },
setup() { setup() {
const { proxy } = getCurrentInstance()
const router = useRouter() const router = useRouter()
const store = useStore()
const dialogVisible = ref(false) const dialogVisible = ref(false)
const lockForm = ref(null) const lockForm = ref(null)
const lockModel = reactive({ const lockModel = reactive({
password: '', password: '',
}) })
const lockRules = reactive({ const lockRules = reactive({
password: [{ required: true, message: '请输入锁屏密码' }], password: [
{ required: true, message: proxy.$t('topbar.lock-rules-password') },
],
}) })
const submitForm = () => { const submitForm = () => {
lockForm.value.validate(valid => { lockForm.value.validate(valid => {
@ -112,7 +116,7 @@ export default defineComponent({
} }
// token // token
store.dispatch('app/setScreenCode', lockModel.password) useApp().setScreenCode(lockModel.password)
// //
router.push('/lock?redirect=' + router.currentRoute.value.fullPath) router.push('/lock?redirect=' + router.currentRoute.value.fullPath)
@ -135,8 +139,3 @@ export default defineComponent({
max-width: 90%; max-width: 90%;
} }
</style> </style>
<style lang="scss" scoped>
.submit-btn {
width: 100%;
}
</style>

View File

@ -37,7 +37,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 15:39:30 * @LastEditTime: 2022-09-27 17:56:21
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -45,7 +45,7 @@
--> -->
<template> <template>
<el-dropdown trigger="click"> <el-dropdown trigger="hover">
<div class="userinfo"> <div class="userinfo">
<template v-if="!userinfo"> <template v-if="!userinfo">
<i class="el-icon-user" /> <i class="el-icon-user" />
@ -58,27 +58,28 @@
</div> </div>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item>个人中心</el-dropdown-item> <el-dropdown-item>{{ $t('topbar.center') }}</el-dropdown-item>
<el-dropdown-item>修改密码</el-dropdown-item> <el-dropdown-item>{{ $t('topbar.password') }}</el-dropdown-item>
<lock-modal /> <lock-modal />
<el-dropdown-item @click="logout">退出登录</el-dropdown-item> <el-dropdown-item @click="logout">
{{ $t('topbar.logout') }}
</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
</template> </template>
<script> <script>
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useUserinfo } from '@/components/Avatar/hooks/useUserinfo' import { useUserinfo } from '@/components/Avatar/hooks/useUserinfo'
import LockModal from './LockModal.vue' import LockModal from './LockModal.vue'
import { useApp } from '@/pinia/modules/app'
export default defineComponent({ export default defineComponent({
components: { components: {
LockModal, LockModal,
}, },
setup() { setup() {
const store = useStore()
const router = useRouter() const router = useRouter()
const { userinfo } = useUserinfo() const { userinfo } = useUserinfo()
@ -86,7 +87,7 @@ export default defineComponent({
// 退 // 退
const logout = () => { const logout = () => {
// token // token
store.dispatch('app/clearToken') useApp().clearToken()
router.push('/login') router.push('/login')
} }

View File

@ -37,7 +37,7 @@
* @version: * @version:
* @Date: 2021-04-21 09:18:32 * @Date: 2021-04-21 09:18:32
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-07-23 16:49:39 * @LastEditTime: 2022-09-27 18:36:16
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -58,6 +58,7 @@
<div class="action"> <div class="action">
<error-log /> <error-log />
<userinfo /> <userinfo />
<change-lang />
</div> </div>
</div> </div>
</template> </template>
@ -67,8 +68,11 @@ import Logo from '@/layout/components/Sidebar/Logo.vue'
import Hamburger from './Hamburger.vue' import Hamburger from './Hamburger.vue'
import Breadcrumbs from './Breadcrumbs.vue' import Breadcrumbs from './Breadcrumbs.vue'
import Userinfo from './Userinfo.vue' import Userinfo from './Userinfo.vue'
import ErrorLog from '@/components/ErrorLog/index.vue' import ChangeLang from './ChangeLang.vue'
import { useStore } from 'vuex' // import ErrorLog from '@/components/ErrorLog/index.vue'
import { useLayoutsettings } from '@/pinia/modules/layoutSettings'
import { storeToRefs } from 'pinia'
import { useApp } from '@/pinia/modules/app'
export default defineComponent({ export default defineComponent({
components: { components: {
@ -76,16 +80,16 @@ export default defineComponent({
Hamburger, Hamburger,
Breadcrumbs, Breadcrumbs,
Userinfo, Userinfo,
ErrorLog, ChangeLang,
// ErrorLog,
}, },
setup() { setup() {
const store = useStore() const defaultSettings = useLayoutsettings()
const defaultSettings = computed(() => store.state.layoutSettings)
const device = computed(() => store.state.app.device) const { device } = storeToRefs(useApp())
const isHorizontalMenu = computed( const isHorizontalMenu = computed(
() => defaultSettings.value.menus.mode === 'horizontal' () => defaultSettings.menus.mode === 'horizontal'
) )
const isShowLogo = computed( const isShowLogo = computed(
@ -95,7 +99,7 @@ export default defineComponent({
const isShowHamburger = computed(() => !isHorizontalMenu.value) const isShowHamburger = computed(() => !isHorizontalMenu.value)
const isShowBreadcrumbs = computed( const isShowBreadcrumbs = computed(
() => defaultSettings.value.breadcrumbs.isShow && !isHorizontalMenu.value () => defaultSettings.breadcrumbs.isShow && !isHorizontalMenu.value
) )
return { return {

View File

@ -24,22 +24,23 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-29 11:31:50 * @LastEditTime: 2022-09-27 19:02:14
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/ */
import { onBeforeMount, onBeforeUnmount /*watch*/ } from 'vue' import { storeToRefs } from 'pinia'
// import { useRouter } from 'vue-router'; import { useApp } from '@/pinia/modules/app'
import { useStore } from 'vuex' import { onBeforeMount, onBeforeUnmount, computed } from 'vue'
const WIDTH = 768 const WIDTH = 768
export const useResizeHandler = () => { export const useResizeHandler = () => {
const store = useStore() const appStore = useApp()
// const router = useRouter(); const { sidebar } = storeToRefs(appStore)
// const route = router.currentRoute; const { setDevice, setCollapse } = appStore
const collapse = computed(() => sidebar.value.collapse)
const isMobile = () => { const isMobile = () => {
return window.innerWidth < WIDTH return window.innerWidth < WIDTH
@ -47,11 +48,11 @@ export const useResizeHandler = () => {
const resizeHandler = () => { const resizeHandler = () => {
if (isMobile()) { if (isMobile()) {
store.commit('app/setDevice', 'mobile') setDevice('mobile')
store.commit('app/setCollapse', 1) setCollapse(1)
} else { } else {
store.commit('app/setDevice', 'desktop') setDevice('desktop')
store.commit('app/setCollapse', 0) setCollapse(collapse.value)
} }
} }
@ -63,11 +64,4 @@ export const useResizeHandler = () => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('resize', resizeHandler) window.removeEventListener('resize', resizeHandler)
}) })
// // 监听路由的时候不能使用useRoute获取路由否则会有警告
// watch(route, () => {
// if (store.state.app.device === 'mobile' && !store.state.app.sidebar.collapse) {
// store.commit('app/setCollapse', 1)
// }
// })
} }

View File

@ -27,7 +27,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 14:58:53 * @LastEditTime: 2022-09-27 18:31:47
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -53,6 +53,7 @@
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { defineComponent, ref, computed } from 'vue' import { defineComponent, ref, computed } from 'vue'
import Sidebar from './components/Sidebar/index.vue' import Sidebar from './components/Sidebar/index.vue'
@ -62,7 +63,8 @@ import Tagsbar from './components/Tagsbar/index.vue'
import Breadcrumbs from './components/Topbar/Breadcrumbs.vue' import Breadcrumbs from './components/Topbar/Breadcrumbs.vue'
import Content from './components/Content/index.vue' import Content from './components/Content/index.vue'
import { useResizeHandler } from './hooks/useResizeHandler' import { useResizeHandler } from './hooks/useResizeHandler'
import { useStore } from 'vuex' import { storeToRefs } from 'pinia'
import { useLayoutsettings } from '@/pinia/modules/layoutSettings'
export default defineComponent({ export default defineComponent({
name: 'layout', name: 'layout',
@ -76,16 +78,13 @@ export default defineComponent({
}, },
setup() { setup() {
useResizeHandler() useResizeHandler()
const store = useStore() const defaultSettings = useLayoutsettings()
const defaultSettings = computed(() => store.state.layoutSettings) const isFluid = defaultSettings.layout.isFluid
const isFluid = computed(() => defaultSettings.value.layout.isFluid) const isTopbarFixed = defaultSettings.topbar.isFixed
const isTopbarFixed = computed(() => defaultSettings.value.topbar.isFixed) const isMenusShow = defaultSettings.menus.isShow
const isMenusShow = computed(() => defaultSettings.value.menus.isShow) const isHorizontalMenu = defaultSettings.menus.mode === 'horizontal'
const isHorizontalMenu = computed(
() => defaultSettings.value.menus.mode === 'horizontal'
)
const isBreadcrumbsShow = computed( const isBreadcrumbsShow = computed(
() => isHorizontalMenu.value && defaultSettings.value.breadcrumbs.isShow () => isHorizontalMenu && defaultSettings.breadcrumbs.isShow
) )
const paddingFlag = ref(true) const paddingFlag = ref(true)
const handleBreadcrumbsChange = boo => { const handleBreadcrumbsChange = boo => {

View File

@ -27,7 +27,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-29 10:08:00 * @LastEditTime: 2022-09-27 19:04:15
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -42,15 +42,22 @@ const app = createApp(App)
// 引入element-plus // 引入element-plus
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus'
import './assets/style/element-variables.scss' import './assets/style/element-variables.scss'
// 引入中文语言包
import 'dayjs/locale/zh-cn' // 国际化
import locale from 'element-plus/lib/locale/lang/zh-cn' import i18n from '@/i18n'
// 全局注册element-plus/icons-vue
import * as ICONS from '@element-plus/icons-vue'
Object.entries(ICONS).forEach(([key, component]) => {
// app.component(key === 'PieChart' ? 'PieChartIcon' : key, component)
app.component(key, component)
})
// 引入路由 // 引入路由
import router from './router' import router from './router'
// 引入store // 引入pinia
import store from './store' import pinia from './pinia'
// 权限控制 // 权限控制
import './permission' import './permission'
@ -73,9 +80,8 @@ import useErrorHandler from './error-log'
useErrorHandler(app) useErrorHandler(app)
app app
.use(ElementPlus, { .use(i18n)
locale, .use(ElementPlus)
}) .use(pinia)
.use(store)
.use(router) .use(router)
.mount('#app') .mount('#app')

View File

@ -26,7 +26,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-29 09:47:46 * @LastEditTime: 2022-09-27 16:35:06
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -34,90 +34,87 @@
*/ */
import { ElLoading } from 'element-plus' import { ElLoading } from 'element-plus'
import router, { asyncRoutes } from '@/router' import router from '@/router'
import store from '@/store' import { TOKEN } from './pinia/modules/app'
import { TOKEN } from '@/store/modules/app' // TOKEN变量名 import { nextTick } from 'vue'
import { useApp } from './pinia/modules/app'
import { useAccount } from './pinia/modules/account'
import { useMenus } from './pinia/modules/menu'
const getPageTitle = title => { const getPageTitle = title => {
const appTitle = store.state.app.title const { title: appTitle } = useApp()
if (title) { return title ? `${title} - ${appTitle}` : appTitle
return `${title} - ${appTitle}`
}
return appTitle
} }
// 白名单里面是路由对象的name // 白名单
const WhiteList = ['login', 'lock'] const WhiteList = ['login', 'lock']
let loadingInstance = null
// vue-router4的路由守卫不再是通过next放行而是通过return返回true或false或者一个路由地址
router.beforeEach(async to => { router.beforeEach(async to => {
document.title = getPageTitle(!!to.meta && to.meta.title) // 1. 白名单直接放行
if (WhiteList.includes(to.name)) return true
if (WhiteList.includes(to.name)) { // 2. 未登录强制跳转登录页
return true
}
if (!window.localStorage[TOKEN]) { if (!window.localStorage[TOKEN]) {
return { return {
name: 'login', name: 'login',
query: { query: { redirect: to.fullPath },
redirect: to.fullPath, // redirect是指登录之后可以跳回到redirect指定的页面
},
replace: true, replace: true,
} }
} else { }
// 获取用户角色信息,根据角色判断权限
let userinfo = store.state.account.userinfo
if (!userinfo) {
const loadingInstance = ElLoading.service({
lock: true,
text: '正在加载数据,请稍候~',
background: 'rgba(0, 0, 0, 0.7)',
})
try {
// 获取用户信息
userinfo = await store.dispatch('account/getUserinfo')
loadingInstance.close()
} catch (err) {
loadingInstance.close()
return false
}
// 删除所有动态路由 const { userinfo, getUserinfo } = useAccount()
asyncRoutes.forEach(item => {
router.removeRoute(item.name) // 3. 首次加载:获取用户信息
}) if (!userinfo) {
return to.fullPath try {
await getUserinfo()
} catch {
loadingInstance?.close()
return false
} }
// 生成菜单(如果你的项目有动态菜单,在此处会添加动态路由) // 获取完用户信息后,如果当前不是 /chat_view强制跳转
if (store.state.menu.menus.length <= 0) { // 这里的 to 可能是登录页(带 redirect或其他页面
const loadingInstance = ElLoading.service({ if (to.path !== '/chat_view') {
lock: true, return '/chat_view'
text: '正在加载数据,请稍候~',
background: 'rgba(0, 0, 0, 0.7)',
})
try {
await store.dispatch('menu/generateMenus', userinfo)
loadingInstance.close()
return to.fullPath // 添加动态路由后必须加这一句触发重定向否则会404
} catch (err) {
loadingInstance.close()
return false
}
} }
return true // 已在目标页,直接放行
}
// 判断是否处于锁屏状态 // 4. 生成动态菜单(如有)
if (to.name !== 'lock') { const { menus, generateMenus } = useMenus()
const { authorization } = store.state.app if (menus.length <= 0) {
if (!!authorization && !!authorization.screenCode) { try {
return { await generateMenus()
name: 'lock', return to.fullPath // 生成后重新触发导航
query: { } catch {
redirect: to.path, loadingInstance?.close()
}, return false
replace: true, }
} }
// 5. 锁屏检查
if (to.name !== 'lock') {
const { authorization } = useApp()
if (authorization?.screenCode) {
return {
name: 'lock',
query: { redirect: to.path },
replace: true,
} }
} }
} }
// 6. 已登录且非首次,允许自由访问
return true
})
router.afterEach(to => {
loadingInstance?.close()
if (router.currentRoute.value.name === to.name) {
nextTick(() => {
document.title = getPageTitle(to.meta?.truetitle)
})
}
}) })

View File

@ -14,22 +14,15 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 12:48:11 * @LastEditTime: 2022-09-27 14:52:09
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/ */
import { createStore } from 'vuex' import { createPinia } from 'pinia'
const modulesFiles = import.meta.globEager('./modules/*.js') const pinia = createPinia()
const modules = Object.entries(modulesFiles).reduce((modules, [path, mod]) => {
const moduleName = path.replace(/^\.\/modules\/(.*)\.\w+$/, '$1')
modules[moduleName] = mod.default
return modules
}, {})
export default createStore({ export default pinia
modules,
})

View File

@ -3,38 +3,32 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-29 10:10:14 * @LastEditTime: 2022-09-27 14:57:06
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/ */
import { defineStore } from 'pinia'
import { GetUserinfo } from '@/api/login' import { GetUserinfo } from '@/api/login'
export default { export const useAccount = defineStore('account', {
namespaced: true, state: () => ({
state: {
userinfo: null, userinfo: null,
permissionList: [], permissionList: [],
}, }),
mutations: {
// 保存用户信息
setUserinfo(state, data) {
state.userinfo = data
},
// 清除用户信息
clearUserinfo(state) {
state.userinfo = null
},
},
actions: { actions: {
// 清除用户信息
clearUserinfo() {
this.userinfo = null
},
// 获取用户信息 // 获取用户信息
async getUserinfo({ commit }) { async getUserinfo() {
const { code, data } = await GetUserinfo() const { code, data } = await GetUserinfo()
if (+code === 200) { if (+code === 200) {
commit('setUserinfo', data) this.userinfo = data
return Promise.resolve(data) return Promise.resolve(data)
} }
}, },
}, },
} })

View File

@ -3,66 +3,72 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 15:40:50 * @LastEditTime: 2022-09-27 15:42:35
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/ */
import { defineStore } from 'pinia'
import { getItem, setItem, removeItem } from '@/utils/storage' //getItem和setItem是封装的操作localStorage的方法 import { getItem, setItem, removeItem } from '@/utils/storage' //getItem和setItem是封装的操作localStorage的方法
import { AesEncryption } from '@/utils/encrypt' import { AesEncryption } from '@/utils/encrypt'
import { toRaw } from 'vue' import { toRaw } from 'vue'
import { useAccount } from './account'
import { useTags } from './tags'
import { useMenus } from './menu'
export const TOKEN = 'VEA-TOKEN' export const TOKEN = 'VEA-TOKEN'
const COLLAPSE = 'VEA-COLLAPSE' const COLLAPSE = 'VEA-COLLAPSE'
export default { export const useApp = defineStore('app', {
namespaced: true, state: () => ({
state: { // title: 'Vue3 Element Admin',
title: 'Vue3 Element Admin', title: '凌空天行 AI大模型 应用系统',
authorization: getItem(TOKEN), authorization: getItem(TOKEN),
sidebar: { sidebar: {
collapse: getItem(COLLAPSE), collapse: getItem(COLLAPSE),
}, },
device: 'desktop', device: 'desktop',
}, matchedRoutes: [],
mutations: { }),
setToken(state, data) { actions: {
state.authorization = data setCollapse(data) {
// 保存到localStorage this.sidebar.collapse = data
setItem(TOKEN, data)
},
clearToken(state) {
state.authorization = ''
removeItem(TOKEN)
},
setCollapse(state, data) {
state.sidebar.collapse = data
// 保存到localStorage // 保存到localStorage
setItem(COLLAPSE, data) setItem(COLLAPSE, data)
}, },
clearCollapse(state) { clearCollapse() {
state.sidebar.collapse = '' this.sidebar.collapse = ''
removeItem(COLLAPSE) removeItem(COLLAPSE)
}, },
setDevice(state, device) { setDevice(device) {
state.device = device this.device = device
}, },
}, setMatchedRoutes(payload) {
actions: { this.matchedRoutes = payload
clearToken({ commit }) { },
setToken(data) {
this.authorization = data
// 保存到localStorage
setItem(TOKEN, data)
},
initToken(data) {
this.clearToken()
this.setToken(data)
},
clearToken() {
// 清除token // 清除token
commit('clearToken') this.authorization = ''
removeItem(TOKEN)
// 清除用户信息 // 清除用户信息
commit('account/clearUserinfo', '', { root: true }) useAccount().clearUserinfo()
// 清除标签栏 // 清除标签栏
commit('tags/CLEAR_ALL_TAGS', '', { root: true }) useTags().clearAllTags()
// 清空menus // 清空menus
commit('menu/SET_MENUS', [], { root: true }) useMenus().setMenus([])
}, },
setScreenCode({ commit, state }, password) { setScreenCode(password) {
const authorization = toRaw(state.authorization) const authorization = toRaw(this.authorization)
if (!password) { if (!password) {
try { try {
@ -70,7 +76,10 @@ export default {
} catch (err) { } catch (err) {
console.log(err) console.log(err)
} }
commit('setToken', authorization)
this.authorization = authorization
// 保存到localStorage
setItem(TOKEN, authorization)
return return
} }
@ -78,10 +87,13 @@ export default {
// 对密码加密 // 对密码加密
const screenCode = new AesEncryption().encryptByAES(password) const screenCode = new AesEncryption().encryptByAES(password)
commit('setToken', { const res = {
...authorization, ...authorization,
screenCode, screenCode,
}) }
this.authorization = res
// 保存到localStorage
setItem(TOKEN, res)
}, },
}, },
} })

View File

@ -0,0 +1,29 @@
/*
* @Descripttion:
* @version:
* @Date: 2021-04-21 09:18:32
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2022-09-27 15:45:36
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import { defineStore } from 'pinia'
export const useErrorlog = defineStore('errorLog', {
state: () => ({
logs: [],
}),
actions: {
addErrorLog(log) {
// 可以根据需要将错误上报给服务器
// ....code.......
this.logs.push(log)
},
clearErrorLog() {
this.logs.splice(0)
},
},
})

View File

@ -27,24 +27,24 @@
* @version: * @version:
* @Date: 2021-07-23 16:10:49 * @Date: 2021-07-23 16:10:49
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 15:03:26 * @LastEditTime: 2022-09-27 15:47:50
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/ */
import { defineStore } from 'pinia'
import { getItem, setItem } from '@/utils/storage' //getItem和setItem是封装的操作localStorage的方法 import { getItem, setItem } from '@/utils/storage' //getItem和setItem是封装的操作localStorage的方法
import defaultSettings from '@/default-settings' import defaultSettings from '@/default-settings'
export default {
namespaced: true, export const useLayoutsettings = defineStore('layoutSettings', {
state: getItem('defaultSettings') || defaultSettings, state: () => getItem('defaultSettings') || defaultSettings,
mutations: { actions: {
SAVE_SETTINGS(state, data) { saveSettings(data) {
Object.entries(data).forEach(([key, value]) => { Object.entries(data).forEach(([key, value]) => {
state[key] = value this[key] = value
}) })
setItem('defaultSettings', data) setItem('defaultSettings', data)
}, },
}, },
} })

151
src/pinia/modules/menu.js Normal file
View File

@ -0,0 +1,151 @@
/*
* @Descripttion:
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2022-09-27 16:41:46
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import { defineStore } from 'pinia'
import { fixedRoutes, asyncRoutes } from '@/router'
import { GetMenus } from '@/api/menu'
import router from '@/router'
import { ref } from 'vue'
export const useMenus = defineStore('menu', () => {
const generateUrl = (path, parentPath) => {
return path.startsWith('/')
? path
: path
? `${parentPath}/${path}`
: parentPath
}
const getFilterRoutes = (targetRoutes, ajaxRoutes) => {
const filterRoutes = []
ajaxRoutes.forEach(item => {
const target = targetRoutes.find(target => target.name === item.name)
if (target) {
const { children: targetChildren, ...rest } = target
const route = {
...rest,
}
if (item.children) {
route.children = getFilterRoutes(targetChildren, item.children)
}
filterRoutes.push(route)
}
})
return filterRoutes
}
const getFilterMenus = (arr, parentPath = '') => {
const menus = []
arr.forEach(item => {
if (!item.hidden) {
const menu = {
url: generateUrl(item.path, parentPath),
title: item.meta.title,
icon: item.icon,
}
if (item.children) {
if (item.children.filter(child => !child.hidden).length <= 0) {
delete menu.children
} else {
menu.children = getFilterMenus(item.children, menu.url)
}
}
menus.push(menu)
}
})
return menus
}
// 扁平化树形数据
const flattenTree = tree => {
let result = []
tree.forEach(node => {
result.push(node) // 将当前节点添加到结果数组中
if (node.children && node.children.length) {
// 如果有子节点,将子节点添加到结果数组中
result = result.concat(flattenTree(node.children))
delete node.children
}
})
return result
}
const optimizeRoutes = (arr, parentPath = '', parentName = '') => {
return arr.map(obj => {
const item = {
...obj,
}
item.path = item.path.startsWith('/')
? item.path
: parentPath
? `${parentPath}/${item.path}`
: item.path
if (parentName) {
item.meta.parent = parentName
}
if (item.children) {
item.children = optimizeRoutes(item.children, item.path, item.name)
}
return item
})
}
const formatRoutes = routes => {
return routes.map(route => ({
...route,
children: flattenTree(optimizeRoutes(route.children || [])),
}))
}
const menus = ref([])
const setMenus = data => {
menus.value = data
}
const generateMenus = async () => {
// // 方式一:只有固定菜单
const menus = getFilterMenus(fixedRoutes)
// commit('SET_MENUS', menus)
// setMenus(menus)
// 方式二:有动态菜单
// 从后台获取菜单
const { code, data } = await GetMenus()
if (+code === 200) {
// 添加路由之前先删除所有动态路由
asyncRoutes.forEach(item => {
router.removeRoute(item.name)
})
// 过滤出需要添加的动态路由
const filterRoutes = getFilterRoutes(asyncRoutes, data)
// 生成菜单
const menus = getFilterMenus([...fixedRoutes, ...filterRoutes])
setMenus(menus)
// 添加动态路由由于只做了二级路由所以需要将三级之后的children提到二级
const arr = formatRoutes(filterRoutes)
arr.forEach(route => router.addRoute(route))
}
}
return {
menus,
setMenus,
generateMenus,
}
})

113
src/pinia/modules/tags.js Normal file
View File

@ -0,0 +1,113 @@
/*
* @Descripttion:
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2022-09-27 16:49:31
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import { defineStore } from 'pinia'
import { getItem, setItem, removeItem } from '@/utils/storage' //getItem和setItem是封装的操作localStorage的方法
const TAGLIST = 'VEA-TAGLIST'
export const useTags = defineStore('tags', {
state: () => ({
tagList: getItem(TAGLIST) || [],
cacheList: [],
activePosition: -1,
}),
actions: {
saveActivePosition(index) {
this.activePosition = index
},
addTag({ path, fullPath, name, meta, params, query }) {
if (this.tagList.some(v => v.path === path)) return false
// 添加tagList
const target = Object.assign(
{},
{ path, fullPath, name, meta, params, query },
{
title: meta.title || '未命名',
fullPath: fullPath || path,
}
)
if (this.activePosition === -1) {
if (name === 'home') {
this.tagList.unshift(target)
} else {
this.tagList.push(target)
}
} else {
this.tagList.splice(this.activePosition + 1, 0, target)
}
// 保存到localStorage
setItem(TAGLIST, this.tagList)
// 添加cacheList
if (this.cacheList.includes(name)) return
if (!meta.noCache) {
this.cacheList.push(name)
}
},
deTagList(tag) {
// 删除tagList
this.tagList = this.tagList.filter(v => v.path !== tag.path)
// 保存到localStorage
setItem(TAGLIST, this.tagList)
},
deCacheList(tag) {
// 删除cacheList
this.cacheList = this.cacheList.filter(v => v !== tag.name)
},
delTag(tag) {
// 删除tagList
this.deTagList(tag)
// 删除cacheList
this.deCacheList(tag)
},
delOtherTags(tag) {
this.tagList = this.tagList.filter(
v => !!v.meta.affix || v.path === tag.path
)
// 保存到localStorage
setItem(TAGLIST, this.tagList)
this.cacheList = this.cacheList.filter(v => v === tag.name)
},
delSomeTags(tags) {
this.tagList = this.tagList.filter(
v => !!v.meta.affix || tags.every(tag => tag.path !== v.path)
)
// 保存到localStorage
setItem(TAGLIST, this.tagList)
this.cacheList = this.cacheList.filter(v =>
tags.every(tag => tag.name !== v)
)
},
delAllTags() {
this.tagList = this.tagList.filter(v => !!v.meta.affix)
// 保存到localStorage
removeItem(TAGLIST)
this.cacheList = []
},
updateTagList(tag) {
const index = this.tagList.findIndex(v => v.path === tag.path)
if (index > -1) {
this.tagList[index] = Object.assign({}, this.tagList[index], tag)
// 保存到localStorage
setItem(TAGLIST, this.tagList)
}
},
clearAllTags() {
this.cacheList = []
this.tagList = []
// 保存到localStorage
removeItem(TAGLIST)
},
},
})

View File

@ -32,13 +32,20 @@ import error from './modules/error'
import login from './modules/login' import login from './modules/login'
import lock from './modules/lock' import lock from './modules/lock'
import home from './modules/home' import home from './modules/home'
import system from './modules/system'
import test from './modules/test' import test from './modules/test'
import lktx_jiegou_chat from './modules/lktx_jiegou_chat'
import chat from './modules/chat'
import jg_gcb from './modules/jg_gcb'
/* 菜单栏的路由 */ /* 菜单栏的路由 */
// 固定菜单 // 固定菜单
export const fixedRoutes = [...home] export const fixedRoutes = [...home, ...chat]
// export const fixedRoutes = [ ]
// 动态菜单 // 动态菜单
export const asyncRoutes = [...test] export const asyncRoutes = [...test, ...system, ...jg_gcb]
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),

View File

@ -0,0 +1,25 @@
const Layout = () => import('@/layout/chat_layout.vue')
export default [
{
path: '/chat',
component: Layout,
name: 'chat',
meta: {
title: 'chat',
},
icon: 'Setting',
children: [
{
path: '/chat_view',
name: 'chat_view',
component: Layout,
meta: {
title: 'chat界面',
affix: false,
},
icon: 'User',
},
],
},
]

View File

@ -3,16 +3,17 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 16:23:58 * @LastEditTime: 2022-09-27 18:14:03
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/ */
import store from '@/store'
import { useAccount } from '@/pinia/modules/account'
const checkUserinfo = (code, fullPath) => { const checkUserinfo = (code, fullPath) => {
const userinfo = store.state.account.userinfo const { userinfo } = useAccount()
if (userinfo) { if (userinfo) {
return `/error/${code === '404' ? fullPath : code}` return `/error/${code === '404' ? fullPath : code}`
} }

View File

@ -3,7 +3,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-07-26 14:37:08 * @LastEditTime: 2022-09-24 19:27:21
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -19,16 +19,16 @@ export default [
component: Layout, component: Layout,
name: 'Dashboard', name: 'Dashboard',
meta: { meta: {
title: '工作台', title: 'menu.dashboard',
}, },
icon: 'home', icon: 'icon-home',
children: [ children: [
{ {
path: '', path: '',
name: 'home', name: 'home',
component: Home, component: Home,
meta: { meta: {
title: '首页', title: 'menu.homepage',
affix: true, affix: true,
}, },
}, },

View File

@ -0,0 +1,50 @@
/*
* @Descripttion:
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2022-09-24 19:27:21
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
// home.js
const Layout = () => import('@/layout/index.vue')
const jg_knowledge_files_add = () =>
import('@/views/jg_gcb/jg_knowledge_files_add.vue')
const jg_model_file_add = () => import('@/views/jg_gcb/jg_model_file_add.vue')
export default [
{
path: '/jg_gcb',
component: Layout,
name: 'jg_gcb',
meta: {
title: '结构工程部-工具集合',
},
icon: 'Setting',
children: [
{
path: '/jg_knowledge_files_add',
name: 'jg_knowledge_files_add',
component: jg_knowledge_files_add,
meta: {
title: 'JG-知识库文件新增管理',
affix: false,
},
icon: 'User',
},
{
path: '/jg_model_file_add',
name: 'jg_model_file_add',
component: jg_model_file_add,
meta: {
title: 'JG-模型文件新增',
affix: false,
},
icon: '',
},
],
},
]

View File

@ -0,0 +1,48 @@
/*
* @Descripttion:
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2022-09-24 19:27:21
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
// home.js
const Layout = () => import('@/layout/chat_layout.vue')
const first = () => import('@/views/lktx_jiegou/first.vue')
const second = () => import('@/views/lktx_jiegou/second.vue')
export default [
{
path: '/lktx_jiegou',
component: Layout,
name: 'lktx_jiegou',
meta: {
title: '结构组工具集合',
},
icon: 'Setting',
children: [
{
path: '/first',
name: 'first',
component: first,
meta: {
title: '结构组-知识库',
affix: false,
},
icon: 'User',
},
{
path: '/second',
name: 'second',
component: second,
meta: {
title: '图片检索',
affix: false,
},
icon: 'Service',
},
],
},
]

View File

@ -0,0 +1,59 @@
/*
* @Descripttion:
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2022-09-24 19:27:21
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
// home.js
const Layout = () => import('@/layout/index.vue')
const sysUser = () => import('@/views/system/sysUser.vue')
const sysRole = () => import('@/views/system/sysRole.vue')
const sysMenu = () => import('@/views/system/sysMenu.vue')
export default [
{
path: '/system',
component: Layout,
name: 'system',
meta: {
title: '系统管理',
},
icon: 'Setting',
children: [
{
path: '/sysUser',
name: 'sysUser',
component: sysUser,
meta: {
title: '用户管理',
affix: false,
},
icon: 'User',
},
{
path: '/sysRole',
name: 'sysRole',
component: sysRole,
meta: {
title: '角色管理',
affix: false,
},
icon: 'Service',
},
{
path: '/sysMenu',
name: 'sysMenu',
component: sysMenu,
meta: {
title: '菜单管理',
affix: false,
},
icon: 'Document',
},
],
},
]

View File

@ -3,7 +3,7 @@
* @version: * @version:
* @Date: 2021-04-21 09:18:32 * @Date: 2021-04-21 09:18:32
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-07-26 16:30:58 * @LastEditTime: 2022-09-27 18:51:35
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -28,59 +28,62 @@ export default [
component: Layout, component: Layout,
name: 'test', name: 'test',
meta: { meta: {
title: '测试页面', title: 'menu.test',
}, },
icon: 'el-icon-location', icon: 'Location',
children: [ children: [
{ {
path: '', path: '',
name: 'testList', name: 'testList',
component: List, component: List,
meta: { meta: {
title: '列表', title: 'menu.testList',
}, },
children: [
{
path: 'add',
name: 'testAdd',
component: Add,
meta: {
title: 'menu.testAdd',
},
hidden: true, // 不在菜单中显示
},
{
path: 'edit/:id',
name: 'testEdit',
component: Edit,
meta: {
title: 'menu.testEdit',
},
hidden: true, // 不在菜单中显示
},
],
}, },
{
path: 'add', // {
name: 'testAdd', // path: 'auth',
component: Add, // name: 'testAuth',
meta: { // component: Auth,
title: '添加', // meta: {
}, // title: 'menu.testAuth',
hidden: true, // 不在菜单中显示 // },
}, // },
{ // {
path: 'edit/:id', // path: 'noauth',
name: 'testEdit', // name: 'testNoAuth',
component: Edit, // component: NoAuth,
meta: { // meta: {
title: '编辑', // title: 'menu.testNoAuth',
}, // },
hidden: true, // 不在菜单中显示 // hidden: true,
}, // },
{
path: 'auth',
name: 'testAuth',
component: Auth,
meta: {
title: '权限测试',
},
},
{
path: 'noauth',
name: 'testNoAuth',
component: NoAuth,
meta: {
title: '权限页面',
},
hidden: true,
},
{ {
path: 'cache', path: 'cache',
name: 'test-cache', name: 'test-cache',
component: Iscache, component: Iscache,
meta: { meta: {
title: '该页面可缓存', title: 'menu.test-cache',
}, },
}, },
{ {
@ -88,7 +91,7 @@ export default [
name: 'test-no-cache', name: 'test-no-cache',
component: Nocache, component: Nocache,
meta: { meta: {
title: '该页面不缓存', title: 'menu.test-no-cache',
noCache: true, // 不缓存页面 noCache: true, // 不缓存页面
}, },
}, },
@ -98,7 +101,7 @@ export default [
component: Nest, component: Nest,
redirect: '/test/nest/page1', redirect: '/test/nest/page1',
meta: { meta: {
title: '二级菜单', title: 'menu.nest',
}, },
children: [ children: [
{ {
@ -106,7 +109,7 @@ export default [
name: 'nestPage1', name: 'nestPage1',
component: NestPage1, component: NestPage1,
meta: { meta: {
title: 'page1', title: 'menu.nestPage1',
}, },
}, },
{ {
@ -114,7 +117,7 @@ export default [
name: 'nestPage2', name: 'nestPage2',
component: NestPage2, component: NestPage2,
meta: { meta: {
title: 'page2', title: 'menu.nestPage2',
}, },
}, },
], ],
@ -124,7 +127,7 @@ export default [
name: 'test-error-log', name: 'test-error-log',
component: ErrorLog, component: ErrorLog,
meta: { meta: {
title: '测试错误日志', title: 'menu.test-error-log',
}, },
}, },
], ],

View File

@ -1,43 +0,0 @@
/*
* @Descripttion:
* @version:
* @Date: 2021-04-21 09:18:32
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 09:34:13
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
const state = {
logs: [],
}
const mutations = {
ADD_ERROR_LOG: (state, log) => {
state.logs.push(log)
},
CLEAR_ERROR_LOG: state => {
state.logs.splice(0)
},
}
const actions = {
addErrorLog({ commit }, log) {
// 可以根据需要将错误上报给服务器
// ....code.......
// 触发mutations
commit('ADD_ERROR_LOG', log)
},
clearErrorLog({ commit }) {
commit('CLEAR_ERROR_LOG')
},
}
export default {
namespaced: true,
state,
mutations,
actions,
}

View File

@ -1,109 +0,0 @@
/*
* @Descripttion:
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-07-26 18:22:01
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import { fixedRoutes, asyncRoutes } from '@/router'
import { GetMenus } from '@/api/menu'
import router from '@/router'
// const hasPermission = (role, route) => {
// if (!!route.meta && !!route.meta.roles && !route.meta.roles.includes(role)) {
// return false
// }
// return true
// }
const generateUrl = (path, parentPath) => {
return path.startsWith('/')
? path
: path
? `${parentPath}/${path}`
: parentPath
}
const getFilterRoutes = (targetRoutes, ajaxRoutes) => {
const filterRoutes = []
ajaxRoutes.forEach(item => {
const target = targetRoutes.find(target => target.name === item.name)
if (target) {
const { children: targetChildren, ...rest } = target
const route = {
...rest,
}
if (item.children) {
route.children = getFilterRoutes(targetChildren, item.children)
}
filterRoutes.push(route)
}
})
return filterRoutes
}
const getFilterMenus = (arr, parentPath = '') => {
const menus = []
arr.forEach(item => {
if (!item.hidden) {
const menu = {
url: generateUrl(item.path, parentPath),
title: item.meta.title,
icon: item.icon,
}
if (item.children) {
if (item.children.filter(child => !child.hidden).length <= 1) {
menu.url = generateUrl(item.children[0].path, menu.url)
} else {
menu.children = getFilterMenus(item.children, menu.url)
}
}
menus.push(menu)
}
})
return menus
}
export default {
namespaced: true,
state: {
menus: [],
},
mutations: {
SET_MENUS(state, data) {
state.menus = data
},
},
actions: {
async generateMenus({ commit }, userinfo) {
// // 方式一:只有固定菜单
// const menus = getFilterMenus(fixedRoutes)
// commit('SET_MENUS', menus)
// 方式二:有动态菜单
// 从后台获取菜单
const { code, data } = await GetMenus({ role: userinfo.role })
if (+code === 200) {
// 过滤出需要添加的动态路由
const filterRoutes = getFilterRoutes(asyncRoutes, data)
filterRoutes.forEach(route => router.addRoute(route))
// 生成菜单
const menus = getFilterMenus([...fixedRoutes, ...filterRoutes])
commit('SET_MENUS', menus)
}
},
},
}

View File

@ -1,150 +0,0 @@
/*
* @Descripttion:
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 15:36:04
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import { getItem, setItem, removeItem } from '@/utils/storage' //getItem和setItem是封装的操作localStorage的方法
const TAGLIST = 'VEA-TAGLIST'
const state = {
tagList: getItem(TAGLIST) || [],
cacheList: [],
activePosition: -1,
}
const mutations = {
ADD_TAG_LIST: (state, { path, fullPath, name, meta, params, query }) => {
if (state.tagList.some(v => v.path === path)) return false
const target = Object.assign(
{},
{ path, fullPath, name, meta, params, query },
{
title: meta.title || '未命名',
fullPath: fullPath || path,
}
)
if (state.activePosition === -1) {
if (name === 'home') {
state.tagList.unshift(target)
} else {
state.tagList.push(target)
}
} else {
state.tagList.splice(state.activePosition + 1, 0, target)
}
// 保存到localStorage
setItem(TAGLIST, state.tagList)
},
ADD_CACHE_LIST: (state, tag) => {
if (state.cacheList.includes(tag.name)) return
if (!tag.meta.noCache) {
state.cacheList.push(tag.name)
}
},
DEL_TAG_LIST: (state, tag) => {
state.tagList = state.tagList.filter(v => v.path !== tag.path)
// 保存到localStorage
setItem(TAGLIST, state.tagList)
},
DEL_CACHE_LIST: (state, tag) => {
state.cacheList = state.cacheList.filter(v => v !== tag.name)
},
DEL_OTHER_TAG_LIST: (state, tag) => {
state.tagList = state.tagList.filter(
v => !!v.meta.affix || v.path === tag.path
)
// 保存到localStorage
setItem(TAGLIST, state.tagList)
},
DEL_OTHER_CACHE_LIST: (state, tag) => {
state.cacheList = state.cacheList.filter(v => v === tag.name)
},
DEL_SOME_TAG_LIST: (state, tags) => {
state.tagList = state.tagList.filter(
v => !!v.meta.affix || tags.every(tag => tag.path !== v.path)
)
// 保存到localStorage
setItem(TAGLIST, state.tagList)
},
DEL_SOME_CACHE_LIST: (state, tags) => {
state.cacheList = state.cacheList.filter(v =>
tags.every(tag => tag.name !== v)
)
},
DEL_ALL_TAG_LIST: state => {
state.tagList = state.tagList.filter(v => !!v.meta.affix)
// 保存到localStorage
removeItem(TAGLIST)
},
DEL_ALL_CACHE_LIST: state => {
state.cacheList = []
},
CLEAR_ALL_TAGS: state => {
state.cacheList = []
state.tagList = []
// 保存到localStorage
removeItem(TAGLIST)
},
UPDATE_TAG_LIST: (state, tag) => {
const index = state.tagList.findIndex(v => v.path === tag.path)
if (index > -1) {
state.tagList[index] = Object.assign({}, state.tagList[index], tag)
// 保存到localStorage
setItem(TAGLIST, state.tagList)
}
},
SAVE_ACTIVE_POSITION: (state, index) => {
state.activePosition = index
},
}
const actions = {
saveActivePosition({ commit }, index) {
commit('SAVE_ACTIVE_POSITION', index)
},
addTag({ commit }, tag) {
commit('ADD_TAG_LIST', tag)
commit('ADD_CACHE_LIST', tag)
},
delTag({ commit }, tag) {
commit('DEL_TAG_LIST', tag)
commit('DEL_CACHE_LIST', tag)
},
delOtherTags({ commit }, tag) {
commit('DEL_OTHER_TAG_LIST', tag)
commit('DEL_OTHER_CACHE_LIST', tag)
},
delSomeTags({ commit }, tags) {
commit('DEL_SOME_TAG_LIST', tags)
commit('DEL_SOME_CACHE_LIST', tags)
},
delAllTags({ commit }) {
commit('DEL_ALL_TAG_LIST')
commit('DEL_ALL_CACHE_LIST')
},
updateTagList({ commit }, { path, fullPath, name, meta, params, query }) {
commit('UPDATE_TAG_LIST', { path, fullPath, name, meta, params, query })
},
}
export default {
namespaced: true,
state,
mutations,
actions,
}

View File

@ -22,7 +22,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 15:44:39 * @LastEditTime: 2022-09-27 18:17:20
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -31,26 +31,55 @@
import axios from 'axios' import axios from 'axios'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import store from '@/store'
import router from '@/router' import router from '@/router'
import { useApp } from '@/pinia/modules/app'
const service = axios.create({ const service = axios.create({
baseURL: '/', baseURL: '',
// baseURL: 'http://localhost:8501',
timeout: 10000, timeout: 10000,
withCredentials: true, withCredentials: true,
}) })
// 拦截请求 // 拦截请求(合并版)
service.interceptors.request.use( service.interceptors.request.use(
config => { config => {
const { authorization } = store.state.app // ✅ 1. 添加 Token你的原逻辑
const { authorization } = useApp()
if (authorization) { if (authorization) {
config.headers.Authorization = `Bearer ${authorization.token}` config.headers.token = `${authorization.token}`
} }
// ✅ 2. 生产环境替换代理路径(跨域方案核心)
if (!import.meta.env.DEV) {
const { url } = config
if (url.startsWith('/heat-api')) {
config.url = url.replace(
'/heat-api',
import.meta.env.VITE_HEAT_API_BASE_URL
)
} else if (url.startsWith('/strength-api')) {
config.url = url.replace(
'/strength-api',
import.meta.env.VITE_STRENGTH_API_BASE_URL
)
} else if (url.startsWith('/api')) {
config.url = url.replace('/api', import.meta.env.VITE_API_BASE_URL)
} else if (url.startsWith('/admin')) {
config.url = url.replace('/admin', import.meta.env.VITE_ADMIN_BASE_URL)
} else if (url.startsWith('/system')) {
config.url = url.replace('/system', import.meta.env.VITE_API_BASE_URL)
}
console.log(`[PROD] ${config.method?.toUpperCase()} ${config.url}`)
} else {
console.log(`[DEV] ${config.method?.toUpperCase()} ${config.url}`)
}
return config return config
}, },
error => { error => {
// console.log(error);
return Promise.reject(error) return Promise.reject(error)
} }
) )
@ -67,7 +96,7 @@ service.interceptors.response.use(
// 响应拦截器中的 error 就是那个响应的错误对象 // 响应拦截器中的 error 就是那个响应的错误对象
if (error.response && error.response.status === 401) { if (error.response && error.response.status === 401) {
// 校验是否有 refresh_token // 校验是否有 refresh_token
const { authorization } = store.state.app const { authorization, clearToken, setToken } = useApp()
if (!authorization || !authorization.refresh_token) { if (!authorization || !authorization.refresh_token) {
if (router.currentRoute.value.name === 'login') { if (router.currentRoute.value.name === 'login') {
return Promise.reject(error) return Promise.reject(error)
@ -75,7 +104,7 @@ service.interceptors.response.use(
const redirect = encodeURIComponent(window.location.href) const redirect = encodeURIComponent(window.location.href)
router.push(`/login?redirect=${redirect}`) router.push(`/login?redirect=${redirect}`)
// 清除token // 清除token
store.dispatch('app/clearToken') clearToken()
setTimeout(() => { setTimeout(() => {
ElMessage.closeAll() ElMessage.closeAll()
try { try {
@ -99,7 +128,7 @@ service.interceptors.response.use(
}) })
// 如果获取成功,则把新的 token 更新到容器中 // 如果获取成功,则把新的 token 更新到容器中
// console.log('刷新 token 成功', res) // console.log('刷新 token 成功', res)
store.commit('app/setToken', { setToken({
token: res.data.data.token, // 最新获取的可用 token token: res.data.data.token, // 最新获取的可用 token
refresh_token: authorization.refresh_token, // 还是原来的 refresh_token refresh_token: authorization.refresh_token, // 还是原来的 refresh_token
}) })
@ -113,7 +142,7 @@ service.interceptors.response.use(
const redirect = encodeURIComponent(window.location.href) const redirect = encodeURIComponent(window.location.href)
router.push(`/login?redirect=${redirect}`) router.push(`/login?redirect=${redirect}`)
// 清除token // 清除token
store.dispatch('app/clearToken') clearToken()
return Promise.reject(error) return Promise.reject(error)
} }
} }

View File

@ -0,0 +1,6 @@
<!-- first.vue -->
<template>
<el-radio-button label="chat_view" size="default">
知识库
</el-radio-button>
</template>

View File

@ -3,7 +3,7 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 09:33:01 * @LastEditTime: 2022-09-24 21:52:50
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
@ -14,19 +14,19 @@
<template v-if="error === '403'"> <template v-if="error === '403'">
<span class="code-403">403</span> <span class="code-403">403</span>
<svg-icon name="error-icons-403" class="error-img" /> <svg-icon name="error-icons-403" class="error-img" />
<h2 class="title">您无权访问此页面</h2> <h2 class="title">{{ $t('error.noauth') }}</h2>
</template> </template>
<template v-else-if="error === '500'"> <template v-else-if="error === '500'">
<svg-icon name="error-icons-500" class="error-img" /> <svg-icon name="error-icons-500" class="error-img" />
<h2 class="title">服务器出错了</h2> <h2 class="title">{{ $t('error.servererror') }}</h2>
</template> </template>
<template v-else-if="error === '404'"> <template v-else-if="error === '404'">
<svg-icon name="error-icons-404" class="error-img" /> <svg-icon name="error-icons-404" class="error-img" />
<h2 class="title">您访问的页面不存在</h2> <h2 class="title">{{ $t('error.notfound') }}</h2>
</template> </template>
<router-link to="/"> <router-link to="/">
<el-button type="primary">返回首页</el-button> <el-button type="primary">{{ $t('error.backhome') }}</el-button>
</router-link> </router-link>
</div> </div>
</template> </template>

View File

@ -3,27 +3,12 @@
* @version: * @version:
* @Date: 2021-04-20 11:06:21 * @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com * @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-23 15:16:12 * @LastEditTime: 2022-09-24 18:18:43
* @Author: huzhushan@126.com * @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin * @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin * @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
--> -->
<template> <template>
<div class="home">home</div> home
</template> </template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'home',
setup() {},
})
</script>
<style lang="scss" scoped>
.home {
color: $mainColor;
}
</style>

View File

@ -0,0 +1,456 @@
<template>
<div class="knowledge-ingest">
<!-- 第一步上传区域 -->
<div class="section upload-section">
<h3>步骤1: 上传文件夹到MinIO</h3>
<!-- 文件夹选择 -->
<input
type="file"
ref="folderInput"
style="display: none"
webkitdirectory
multiple
@change="handleFolderSelect"
:disabled="processCompleted"
/>
<el-button
type="primary"
@click="$refs.folderInput.click()"
:disabled="processCompleted"
>
选择文件夹
</el-button>
<!-- 文件列表预览 -->
<div v-if="selectedFiles.length > 0" class="file-list">
<h4>待上传文件 {{ selectedFiles.length }} </h4>
<el-tree
:data="fileTree"
:props="{ label: 'name', children: 'children' }"
default-expand-all
class="tree-view"
/>
</div>
<!-- 上传按钮 -->
<el-button
type="success"
:loading="uploading"
@click="uploadFolder"
:disabled="selectedFiles.length === 0 || uploading || processCompleted"
class="action-btn"
>
{{ uploading ? '上传中...' : '开始上传' }}
</el-button>
</div>
<!-- 第二步RAG入库区域 -->
<div v-if="showIngestSection" class="section ingest-section">
<el-divider />
<h3>步骤2: RAG知识库入库</h3>
<el-alert title="上传成功!" type="success" :closable="false" show-icon>
<div class="upload-info">
<span>
文件夹ID:
<strong>{{ uploadResult.folderId }}</strong>
</span>
<span>
文件数量:
<strong>{{ uploadResult.fileCount }}</strong>
</span>
<span>
存储桶:
<strong>{{ uploadResult.bucketName }}</strong>
</span>
</div>
</el-alert>
<el-button
type="warning"
:loading="ingesting"
@click="startRagIngest"
:disabled="ingesting || ingestCompleted || processCompleted"
class="action-btn"
>
{{
ingesting ? '入库中...' : ingestCompleted ? '已完成' : '开始RAG入库'
}}
</el-button>
<!-- 进度条显示条件进行中 已完成 -->
<div v-if="ingesting || ingestCompleted" class="progress-area">
<h4>RAG入库进度</h4>
<el-progress
:percentage="ingestProgress"
:status="ingestStatus"
:stroke-width="20"
:text-inside="true"
/>
<p v-if="currentFile" class="current-file">
正在处理: {{ currentFile }}
</p>
<!-- 完成提示 -->
<div v-if="ingestCompleted" class="completion-status">
<el-icon class="success-icon" :size="48" color="#67C23A">
<CircleCheckFilled />
</el-icon>
<p class="complete-text"> RAG入库已完成</p>
<p class="complete-detail">
共处理 {{ uploadResult.fileCount }} 个文件
</p>
</div>
</div>
<!-- 修改后的刷新按钮区域 -->
<div v-if="processCompleted" class="refresh-section">
<el-divider />
<el-button
type="primary"
size="large"
@click="resetAllData"
class="refresh-btn"
>
<el-icon><RefreshRight /></el-icon>
开始新流程
</el-button>
<p class="refresh-hint">点击按钮将重置所有数据开始新的上传入库流程</p>
</div>
</div>
</div>
</template>
<script>
import { ElMessage, ElIcon } from 'element-plus'
import { CircleCheckFilled, RefreshRight } from '@element-plus/icons-vue'
import {
submitRagIngestTask,
getRagIngestProgress,
uploadFolder,
} from '@/api/rag'
import { getUserRoles } from '@/api/sysFRole'
import { GetUserinfo } from '@/api/login'
export default {
components: {
ElIcon,
CircleCheckFilled,
RefreshRight,
},
data() {
return {
selectedFiles: [],
fileTree: [],
uploading: false,
uploadResult: null,
ingesting: false,
ingestProgress: 0,
ingestStatus: '',
currentFile: '',
taskId: null,
ingestCompleted: false,
processCompleted: false, //
}
},
computed: {
showIngestSection() {
//
return Boolean(this.uploadResult?.folderId) && !this.uploading
},
},
methods: {
handleFolderSelect(event) {
//
if (this.processCompleted) return
const files = Array.from(event.target.files)
if (files.length === 0) return
// 100MB
const MAX_FILE_SIZE = 200 * 1024 * 1024 * 1024 // 100MB
const oversizedFiles = files.filter(f => f.size > MAX_FILE_SIZE)
if (oversizedFiles.length > 0) {
//
ElMessage.error({
message: `以下文件超过 2GB 限制:\n${oversizedFiles
.map(f => f.name)
.join('\n')}`,
duration: 5000, // 5
showClose: true,
dangerouslyUseHTMLString: true, // HTML
})
//
this.$refs.folderInput.value = null
this.selectedFiles = []
this.fileTree = []
return //
}
this.selectedFiles = files.map(file => ({
file: file,
relativePath: file.webkitRelativePath,
size: file.size,
}))
this.buildFileTree()
},
buildFileTree() {
const tree = { name: '根目录', children: {} }
this.selectedFiles.forEach(({ relativePath }) => {
const parts = relativePath.split('/').filter(p => p)
let current = tree.children
parts.forEach(part => {
if (!current[part]) {
current[part] = { name: part, children: {} }
}
current = current[part].children
})
})
this.fileTree = this.convertToTreeData(tree.children)
},
convertToTreeData(children) {
return Object.keys(children).map(key => {
const node = children[key]
return {
name: node.name,
children:
Object.keys(node.children).length > 0
? this.convertToTreeData(node.children)
: undefined,
}
})
},
async uploadFolder() {
//
if (this.processCompleted) return
this.uploading = true
this.uploadResult = null
const formData = new FormData()
this.selectedFiles.forEach(({ file, relativePath }) => {
formData.append('files', file)
formData.append('relativePaths', relativePath)
})
const folderName = this.$refs.folderInput.files[0].webkitRelativePath.split(
'/'
)[0]
formData.append('sender', 'user')
formData.append('folderName', folderName)
try {
const res = await uploadFolder(formData)
if (res.code === 200 && res.data) {
this.uploadResult = res.data
ElMessage.success(`上传成功!共 ${res.data.fileCount} 个文件`)
} else {
throw new Error(res.message || `上传失败 (code: ${res.code})`)
}
} catch (error) {
const msg = error.response?.data?.message || error.message || '上传失败'
ElMessage.error(msg)
} finally {
this.uploading = false
}
},
async startRagIngest() {
//
if (this.processCompleted) return
if (!this.uploadResult?.folderId) {
ElMessage.warning('请先完成文件上传')
return
}
this.ingesting = true
this.ingestCompleted = false
this.ingestProgress = 0
this.ingestStatus = ''
this.currentFile = ''
try {
const userInfoRes = await GetUserinfo()
const userId = userInfoRes.data.id
const roleRes = await getUserRoles(userId)
const userRoles = roleRes.data || []
const userRoleCode = userRoles.some(r => r?.roleCode === 'admin')
? 'admin'
: userRoles[0]?.roleCode || 'default'
const res = await submitRagIngestTask(
this.uploadResult.folderId,
userRoleCode
)
if (res.code === 200 && res.data) {
this.taskId = res.data.taskId
ElMessage.success(`入库任务已提交,角色: ${userRoleCode}`)
this.pollIngestProgress(this.taskId, userRoleCode)
} else {
throw new Error(res.message || '提交失败')
}
} catch (error) {
const msg = error.response?.data?.message || error.message || '入库失败'
ElMessage.error(msg)
this.ingesting = false
}
},
pollIngestProgress(taskId, userRoleCode) {
let retryCount = 0
const maxRetries = 10
const interval = setInterval(async () => {
try {
const res = await getRagIngestProgress(taskId, userRoleCode)
const { code, data } = res
if (code === 200 && data) {
this.ingestProgress = data.progress || 0
this.currentFile = data.currentFile || ''
switch (data.status) {
case 'completed':
this.ingestStatus = 'success'
clearInterval(interval)
this.ingesting = false
this.ingestCompleted = true
this.processCompleted = true //
ElMessage.success('RAG入库完成')
//
ElMessage.info('请点击"开始新流程"按钮进行下一次操作')
break
case 'failed':
this.ingestStatus = 'exception'
clearInterval(interval)
this.ingesting = false
ElMessage.error(`入库失败: ${data.errorMessage}`)
break
default:
retryCount = 0
}
} else {
throw new Error('查询进度失败')
}
} catch (error) {
console.error('轮询错误:', error)
if (retryCount++ >= maxRetries) {
clearInterval(interval)
this.ingesting = false
ElMessage.error('进度查询失败')
}
}
}, 1500)
},
//
refreshPage() {
// 1: 使 Vue Router ()
this.$router.go(0)
// 2: 使 location.reload()
// location.reload()
// 3:
// this.resetAllData()
},
//
resetAllData() {
//
this.selectedFiles = []
this.fileTree = []
this.uploading = false
this.uploadResult = null
this.ingesting = false
this.ingestProgress = 0
this.ingestStatus = ''
this.currentFile = ''
this.taskId = null
this.ingestCompleted = false
this.processCompleted = false
//
if (this.$refs.folderInput) {
this.$refs.folderInput.value = null
}
//
ElMessage.success('页面已重置,可以开始新流程')
},
},
}
</script>
<style lang="css" scoped>
@import 'knowledge-ingest.css';
/* ✅ 新增:完成状态样式 */
.complete-text {
margin-top: 10px;
font-size: 16px;
color: #67c23a;
font-weight: bold;
text-align: center;
}
.complete-detail {
font-size: 14px;
color: #909399;
text-align: center;
margin-top: 5px;
}
.completion-status {
margin-top: 20px;
text-align: center;
padding: 20px;
background: #f0f9ff;
border-radius: 8px;
border: 1px solid #b3e19d;
}
.success-icon {
margin-bottom: 10px;
}
/* ✅ 新增:刷新按钮区域样式 */
.refresh-section {
margin-top: 30px;
text-align: center;
padding: 20px;
background: #fef0f0;
border-radius: 8px;
border: 1px solid #fbc4c4;
}
.refresh-btn {
margin-bottom: 10px;
}
.refresh-hint {
color: #f56c6c;
font-size: 12px;
margin: 5px 0 0 0;
}
/* 禁用状态按钮 */
.action-btn:disabled,
input[type='file']:disabled {
cursor: not-allowed;
opacity: 0.6;
}
</style>

View File

@ -0,0 +1,96 @@
.upload-container {
max-width: 800px;
margin: 0 auto;
padding: 30px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.upload-container h2 {
text-align: center;
color: #303133;
margin-bottom: 30px;
}
.upload-section {
margin-bottom: 40px;
padding: 20px;
background: #f8f9fa;
border-radius: 6px;
border: 1px solid #ebeef5;
}
.upload-section h3 {
margin: 0 0 15px 0;
color: #409EFF;
font-size: 16px;
}
.upload-drop-zone {
width: 100%;
min-height: 150px;
border: 2px dashed #dcdfe6;
border-radius: 6px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
background: #fff;
}
.upload-drop-zone:hover {
border-color: #409EFF;
background: #f0f7ff;
}
.upload-drop-zone.is-dragover {
border-color: #409EFF;
background: #e6f0ff;
}
.upload-drop-zone .el-icon {
margin-bottom: 10px;
}
.upload-drop-zone p {
margin: 5px 0;
color: #606266;
font-size: 14px;
}
.upload-drop-zone .upload-tip {
font-size: 12px;
color: #909399;
}
.upload-progress {
margin-top: 15px;
padding: 10px;
background: #f5f7fa;
border-radius: 4px;
}
.folder-info, .file-info {
text-align: center;
padding: 10px;
}
.folder-info p, .file-info p {
margin: 8px 0;
color: #606266;
}
/* 深度选择器用于影响子组件样式 */
.upload-section :deep(.el-button) {
width: 100%;
margin-top: 15px;
}
@media (max-width: 768px) {
.upload-container {
padding: 20px;
}
}

View File

@ -0,0 +1,333 @@
<template>
<div class="upload-container">
<h2>文件上传管理中心</h2>
<!-- 文件夹上传区域 -->
<div class="upload-section">
<h3>📁 文件夹上传结构+文件</h3>
<div
class="upload-drop-zone"
:class="{ 'is-dragover': folderDragover }"
@click="selectFolder"
@drop.prevent="handleFolderDrop"
@dragover.prevent="folderDragover = true"
@dragleave.prevent="folderDragover = false"
>
<input
ref="folderInput"
type="file"
webkitdirectory
directory
multiple
style="display: none"
@change="handleFolderSelect"
/>
<div v-if="!selectedFolder">
<el-icon :size="48" color="#409EFF"><FolderOpened /></el-icon>
<p>点击或拖拽文件夹到此处上传</p>
<p class="upload-tip">支持保留文件夹结构将自动上传到MinIO</p>
</div>
<div v-else class="folder-info">
<el-icon :size="32" color="#67C23A"><SuccessFilled /></el-icon>
<p>
已选择文件夹:
<strong>{{ selectedFolder.name }}</strong>
</p>
<p>文件数量: {{ selectedFolder.files.length }} </p>
</div>
</div>
<!-- 进度条 -->
<div v-if="folderUploading" class="upload-progress">
<el-progress
:percentage="uploadProgress"
:status="uploadProgress === 100 ? 'success' : ''"
:stroke-width="15"
>
<span>{{ uploadStatusText }}</span>
</el-progress>
</div>
<el-button
type="primary"
@click="uploadFolder"
:loading="folderUploading"
:disabled="!selectedFolder || folderUploading"
>
{{ folderUploading ? '上传中...' : '确认上传文件夹' }}
</el-button>
</div>
<!-- Excel文件上传区域 -->
<div class="upload-section">
<h3>📊 Excel文件上传</h3>
<div
class="upload-drop-zone"
:class="{ 'is-dragover': excelDragover }"
@click="selectExcel"
@drop.prevent="handleExcelDrop"
@dragover.prevent="excelDragover = true"
@dragleave.prevent="excelDragover = false"
>
<input
ref="excelInput"
type="file"
accept=".xlsx,.xls"
style="display: none"
@change="handleExcelSelect"
/>
<div v-if="!selectedExcel">
<el-icon :size="48" color="#409EFF"><Document /></el-icon>
<p>点击或拖拽Excel文件到此处上传</p>
<p class="upload-tip">仅支持 .xlsx 格式数据将导入系统</p>
</div>
<div v-else class="file-info">
<el-icon :size="32" color="#67C23A"><SuccessFilled /></el-icon>
<p>
已选择文件:
<strong>{{ selectedExcel.name }}</strong>
</p>
<p>文件大小: {{ formatFileSize(selectedExcel.size) }}</p>
</div>
</div>
<el-button
type="success"
@click="uploadExcel"
:loading="excelUploading"
:disabled="!selectedExcel || excelUploading"
>
{{ excelUploading ? '上传解析中...' : '确认上传Excel' }}
</el-button>
<!-- 新增Excel上传结果详情 -->
<el-alert
v-if="showResult && resultDetails"
:title="resultDetails"
type="info"
:closable="false"
show-icon
style="margin-top: 15px; font-size: 13px;"
/>
</div>
<!-- 全局上传结果提示 -->
<el-alert
v-if="showResult"
:title="resultMessage"
:type="resultType"
:closable="false"
show-icon
style="margin-top: 20px"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { FolderOpened, Document, SuccessFilled } from '@element-plus/icons-vue'
import { uploadToMinIOjgmodel, uploadExcelFile } from '@/api/excel'
//
const username = ref('admin')
//
const folderInput = ref(null)
const folderDragover = ref(false)
const selectedFolder = ref(null)
const folderUploading = ref(false)
const uploadProgress = ref(0)
const uploadStatusText = ref('准备上传...')
const BATCH_SIZE = 10
// Excel
const excelInput = ref(null)
const excelDragover = ref(false)
const selectedExcel = ref(null)
const excelUploading = ref(false)
// resultDetails
const showResult = ref(false)
const resultMessage = ref('')
const resultType = ref('success')
const resultDetails = ref('') //
//
const selectFolder = () => folderInput.value.click()
const handleFolderSelect = event => {
const files = Array.from(event.target.files)
if (files.length === 0) return
const firstPath = files[0].webkitRelativePath || files[0].name
selectedFolder.value = {
name: firstPath.split('/')[0],
files: files,
}
}
const handleFolderDrop = event => {
folderDragover.value = false
const items = event.dataTransfer.items
if (!items) return
const entries = Array.from(items)
.map(item => item.webkitGetAsEntry())
.filter(Boolean)
processEntries(entries)
}
const processEntries = async (entries, path = '') => {
const files = []
for (const entry of entries) {
if (entry.isFile) {
const file = await new Promise(resolve => entry.file(resolve))
file.webkitRelativePath = path ? `${path}/${entry.name}` : entry.name
files.push(file)
} else if (entry.isDirectory) {
const dirReader = entry.createReader()
const dirEntries = await new Promise(resolve =>
dirReader.readEntries(resolve)
)
files.push(
...(await processEntries(
dirEntries,
path ? `${path}/${entry.name}` : entry.name
))
)
}
}
if (path && files.length > 0) {
selectedFolder.value = { name: path.split('/')[0], files }
}
return files
}
const uploadFolder = async () => {
if (!selectedFolder.value) {
ElMessage.warning('请先选择文件夹')
return
}
folderUploading.value = true
showResult.value = false
uploadProgress.value = 0
try {
const { name: folderName, files } = selectedFolder.value
const totalBatches = Math.ceil(files.length / BATCH_SIZE)
let uploadedFiles = 0
let failedFiles = 0
for (let i = 0; i < totalBatches; i++) {
const batchFiles = files.slice(i * BATCH_SIZE, (i + 1) * BATCH_SIZE)
uploadStatusText.value = `上传中... (${i + 1}/${totalBatches})`
const formData = new FormData()
formData.append('sender', username.value)
formData.append('folderName', folderName)
batchFiles.forEach(file => formData.append('files', file))
const res = await uploadToMinIOjgmodel(formData)
if (res.code === 200) {
uploadedFiles += batchFiles.length
} else {
failedFiles += batchFiles.length
}
uploadProgress.value = Math.round(((i + 1) / totalBatches) * 100)
}
uploadProgress.value = 100
if (failedFiles === 0) {
resultMessage.value = `文件夹上传成功!共 ${uploadedFiles} 个文件`
resultType.value = 'success'
selectedFolder.value = null
folderInput.value.value = ''
} else {
resultMessage.value = `上传完成!成功: ${uploadedFiles}, 失败: ${failedFiles}`
resultType.value = 'warning'
}
} catch (error) {
resultMessage.value = `上传异常: ${error.message}`
resultType.value = 'error'
} finally {
folderUploading.value = false
showResult.value = true
setTimeout(() => {
uploadProgress.value = 0
uploadStatusText.value = '准备上传...'
}, 3000)
}
}
// Excel
const selectExcel = () => excelInput.value.click()
const handleExcelSelect = event => {
const file = event.target.files[0]
if (!file) return
validateAndSetExcel(file)
}
const handleExcelDrop = event => {
excelDragover.value = false
const file = event.dataTransfer.files[0]
if (file) validateAndSetExcel(file)
}
const validateAndSetExcel = file => {
if (!file.name.toLowerCase().endsWith('.xlsx')) {
ElMessage.error('请上传.xlsx格式的Excel文件')
excelInput.value.value = ''
return
}
selectedExcel.value = file
}
const uploadExcel = async () => {
if (!selectedExcel.value) {
ElMessage.warning('请先选择Excel文件')
return
}
excelUploading.value = true
showResult.value = false
resultDetails.value = '' //
try {
const res = await uploadExcelFile(selectedExcel.value)
if (res.code === 200) {
resultMessage.value = 'Excel上传成功'
resultType.value = 'success'
resultDetails.value = res.data || '数据已成功导入系统'
selectedExcel.value = null
excelInput.value.value = ''
} else {
resultMessage.value = '上传失败'
resultType.value = 'error'
resultDetails.value = res.message || '解析失败,请检查文件格式'
}
} catch (error) {
resultMessage.value = '上传异常'
resultType.value = 'error'
resultDetails.value = error.message || '网络错误或服务器无响应'
} finally {
excelUploading.value = false
showResult.value = true
}
}
//
const formatFileSize = bytes => {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
</script>
<style scoped>
@import 'jg_model_file_add.css';
</style>

View File

@ -0,0 +1,87 @@
.knowledge-ingest {
padding: 20px;
max-width: 900px;
margin: 0 auto;
}
.knowledge-ingest .section {
margin-bottom: 30px;
}
.knowledge-ingest .section h3 {
color: #303133;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #409EFF;
}
.knowledge-ingest .file-list {
margin: 20px 0;
background: #f5f7fa;
padding: 15px;
border-radius: 4px;
}
.knowledge-ingest .file-list h4 {
margin: 0 0 10px 0;
color: #606266;
}
.knowledge-ingest .file-list .tree-view {
background: white;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 10px;
}
.knowledge-ingest .action-btn {
margin-top: 15px;
margin-right: 10px;
min-width: 120px;
}
.knowledge-ingest .upload-info {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 10px;
}
.knowledge-ingest .upload-info span {
font-size: 14px;
}
.knowledge-ingest .upload-info strong {
color: #409EFF;
}
.knowledge-ingest .progress-area {
margin-top: 20px;
}
.knowledge-ingest .progress-area h4 {
margin-bottom: 10px;
color: #606266;
}
.knowledge-ingest .progress-area .current-file {
margin-top: 10px;
font-size: 13px;
color: #909399;
word-break: break-all;
background: #f4f4f5;
padding: 8px 12px;
border-radius: 4px;
}
.knowledge-ingest .el-descriptions {
margin: 15px 0;
}
.knowledge-ingest .el-alert {
margin-bottom: 15px;
}
.knowledge-ingest .el-divider {
margin: 30px 0;
}

View File

@ -0,0 +1,6 @@
<!-- first.vue -->
<template>
<el-radio-button label="first" size="default">
知识库
</el-radio-button>
</template>

Some files were not shown because too many files have changed in this diff Show More