feat: 新增路由守卫

This commit is contained in:
何嘉悦 2025-05-25 18:06:09 +08:00
parent 16b59d6ab2
commit f4d078ca07
52 changed files with 928 additions and 282 deletions

1
.env
View File

@ -1 +0,0 @@
VITE_API_URL = https://web.pandarobot.chat/api

14
.env.development Normal file
View File

@ -0,0 +1,14 @@
# 页面标题
VITE_WEB_TITLE = elx-template
# 页面英文标题
VITE_WEB_TITLE_EN = elx-template
# 本地环境配置
VITE_WEB_ENV = 'development'
# 本地环境
VITE_WEB_BASE_API = '/dev-api'
# 本地接口
VITE_API_URL = http://122.51.75.95:6039

View File

@ -1,11 +1,14 @@
# 页面标题 # 页面标题
VITE_APP_TITLE = elx-template VITE_WEB_TITLE = elx-template
# 生产环境配置 # 页面英文标题
VITE_APP_ENV = 'production' VITE_WEB_TITLE_EN = elx-template
# 生产环境 # 生产环境配置
VITE_APP_BASE_API = '/prod-api' VITE_WEB_ENV = 'production'
# 是否在打包时开启压缩,支持 gzip 和 brotli # 生产环境
VITE_BUILD_COMPRESS = gzip VITE_WEB_BASE_API = '/prod-api'
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip

View File

@ -1,13 +1,17 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head>
<meta charset="UTF-8" /> <head>
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<title>Vite + Vue + TS</title> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head> <title>%VITE_WEB_TITLE%</title>
<body>
<div id="app"></div> </head>
<script type="module" src="/src/main.ts"></script>
</body> <body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html> </html>

View File

@ -15,10 +15,13 @@
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.1",
"@jsonlee_12138/enum": "^1.0.4", "@jsonlee_12138/enum": "^1.0.4",
"@vueuse/core": "^13.2.0", "@vueuse/core": "^13.2.0",
"@vueuse/integrations": "^13.2.0",
"element-plus": "^2.9.10", "element-plus": "^2.9.10",
"hook-fetch": "^1.0.1", "hook-fetch": "^1.0.1",
"nprogress": "^0.2.0",
"pinia": "^3.0.2", "pinia": "^3.0.2",
"pinia-plugin-persistedstate": "^4.3.0", "pinia-plugin-persistedstate": "^4.3.0",
"qrcode": "^1.5.4",
"radash": "^12.1.0", "radash": "^12.1.0",
"reset-css": "^5.0.2", "reset-css": "^5.0.2",
"vite-plugin-svg-icons": "^2.0.1", "vite-plugin-svg-icons": "^2.0.1",

185
pnpm-lock.yaml generated
View File

@ -17,18 +17,27 @@ importers:
'@vueuse/core': '@vueuse/core':
specifier: ^13.2.0 specifier: ^13.2.0
version: 13.2.0(vue@3.5.14(typescript@5.8.3)) version: 13.2.0(vue@3.5.14(typescript@5.8.3))
'@vueuse/integrations':
specifier: ^13.2.0
version: 13.2.0(async-validator@4.2.5)(nprogress@0.2.0)(qrcode@1.5.4)(vue@3.5.14(typescript@5.8.3))
element-plus: element-plus:
specifier: ^2.9.10 specifier: ^2.9.10
version: 2.9.10(vue@3.5.14(typescript@5.8.3)) version: 2.9.10(vue@3.5.14(typescript@5.8.3))
hook-fetch: hook-fetch:
specifier: ^1.0.1 specifier: ^1.0.1
version: 1.0.1(typescript-api-pro@0.0.6) version: 1.0.1(typescript-api-pro@0.0.6)
nprogress:
specifier: ^0.2.0
version: 0.2.0
pinia: pinia:
specifier: ^3.0.2 specifier: ^3.0.2
version: 3.0.2(typescript@5.8.3)(vue@3.5.14(typescript@5.8.3)) version: 3.0.2(typescript@5.8.3)(vue@3.5.14(typescript@5.8.3))
pinia-plugin-persistedstate: pinia-plugin-persistedstate:
specifier: ^4.3.0 specifier: ^4.3.0
version: 4.3.0(pinia@3.0.2(typescript@5.8.3)(vue@3.5.14(typescript@5.8.3))) version: 4.3.0(pinia@3.0.2(typescript@5.8.3)(vue@3.5.14(typescript@5.8.3)))
qrcode:
specifier: ^1.5.4
version: 1.5.4
radash: radash:
specifier: ^12.1.0 specifier: ^12.1.0
version: 12.1.0 version: 12.1.0
@ -716,56 +725,67 @@ packages:
resolution: {integrity: sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==} resolution: {integrity: sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.41.0': '@rollup/rollup-linux-arm-musleabihf@4.41.0':
resolution: {integrity: sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==} resolution: {integrity: sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.41.0': '@rollup/rollup-linux-arm64-gnu@4.41.0':
resolution: {integrity: sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==} resolution: {integrity: sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.41.0': '@rollup/rollup-linux-arm64-musl@4.41.0':
resolution: {integrity: sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==} resolution: {integrity: sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-loongarch64-gnu@4.41.0': '@rollup/rollup-linux-loongarch64-gnu@4.41.0':
resolution: {integrity: sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==} resolution: {integrity: sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-powerpc64le-gnu@4.41.0': '@rollup/rollup-linux-powerpc64le-gnu@4.41.0':
resolution: {integrity: sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==} resolution: {integrity: sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.41.0': '@rollup/rollup-linux-riscv64-gnu@4.41.0':
resolution: {integrity: sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==} resolution: {integrity: sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.41.0': '@rollup/rollup-linux-riscv64-musl@4.41.0':
resolution: {integrity: sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==} resolution: {integrity: sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.41.0': '@rollup/rollup-linux-s390x-gnu@4.41.0':
resolution: {integrity: sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==} resolution: {integrity: sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.41.0': '@rollup/rollup-linux-x64-gnu@4.41.0':
resolution: {integrity: sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==} resolution: {integrity: sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.41.0': '@rollup/rollup-linux-x64-musl@4.41.0':
resolution: {integrity: sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==} resolution: {integrity: sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-win32-arm64-msvc@4.41.0': '@rollup/rollup-win32-arm64-msvc@4.41.0':
resolution: {integrity: sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==} resolution: {integrity: sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==}
@ -1104,41 +1124,49 @@ packages:
resolution: {integrity: sha512-jon9M7DKRLGZ9VYSkFMflvNqu9hDtOCEnO2QAryFWgT6o6AXU8du56V7YqnaLKr6rAbZBWYsYpikF226v423QA==} resolution: {integrity: sha512-jon9M7DKRLGZ9VYSkFMflvNqu9hDtOCEnO2QAryFWgT6o6AXU8du56V7YqnaLKr6rAbZBWYsYpikF226v423QA==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@unrs/resolver-binding-linux-arm64-musl@1.7.2': '@unrs/resolver-binding-linux-arm64-musl@1.7.2':
resolution: {integrity: sha512-c8Cg4/h+kQ63pL43wBNaVMmOjXI/X62wQmru51qjfTvI7kmCy5uHTJvK/9LrF0G8Jdx8r34d019P1DVJmhXQpA==} resolution: {integrity: sha512-c8Cg4/h+kQ63pL43wBNaVMmOjXI/X62wQmru51qjfTvI7kmCy5uHTJvK/9LrF0G8Jdx8r34d019P1DVJmhXQpA==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@unrs/resolver-binding-linux-ppc64-gnu@1.7.2': '@unrs/resolver-binding-linux-ppc64-gnu@1.7.2':
resolution: {integrity: sha512-A+lcwRFyrjeJmv3JJvhz5NbcCkLQL6Mk16kHTNm6/aGNc4FwPHPE4DR9DwuCvCnVHvF5IAd9U4VIs/VvVir5lg==} resolution: {integrity: sha512-A+lcwRFyrjeJmv3JJvhz5NbcCkLQL6Mk16kHTNm6/aGNc4FwPHPE4DR9DwuCvCnVHvF5IAd9U4VIs/VvVir5lg==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc]
'@unrs/resolver-binding-linux-riscv64-gnu@1.7.2': '@unrs/resolver-binding-linux-riscv64-gnu@1.7.2':
resolution: {integrity: sha512-hQQ4TJQrSQW8JlPm7tRpXN8OCNP9ez7PajJNjRD1ZTHQAy685OYqPrKjfaMw/8LiHCt8AZ74rfUVHP9vn0N69Q==} resolution: {integrity: sha512-hQQ4TJQrSQW8JlPm7tRpXN8OCNP9ez7PajJNjRD1ZTHQAy685OYqPrKjfaMw/8LiHCt8AZ74rfUVHP9vn0N69Q==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@unrs/resolver-binding-linux-riscv64-musl@1.7.2': '@unrs/resolver-binding-linux-riscv64-musl@1.7.2':
resolution: {integrity: sha512-NoAGbiqrxtY8kVooZ24i70CjLDlUFI7nDj3I9y54U94p+3kPxwd2L692YsdLa+cqQ0VoqMWoehDFp21PKRUoIQ==} resolution: {integrity: sha512-NoAGbiqrxtY8kVooZ24i70CjLDlUFI7nDj3I9y54U94p+3kPxwd2L692YsdLa+cqQ0VoqMWoehDFp21PKRUoIQ==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [musl]
'@unrs/resolver-binding-linux-s390x-gnu@1.7.2': '@unrs/resolver-binding-linux-s390x-gnu@1.7.2':
resolution: {integrity: sha512-KaZByo8xuQZbUhhreBTW+yUnOIHUsv04P8lKjQ5otiGoSJ17ISGYArc+4vKdLEpGaLbemGzr4ZeUbYQQsLWFjA==} resolution: {integrity: sha512-KaZByo8xuQZbUhhreBTW+yUnOIHUsv04P8lKjQ5otiGoSJ17ISGYArc+4vKdLEpGaLbemGzr4ZeUbYQQsLWFjA==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@unrs/resolver-binding-linux-x64-gnu@1.7.2': '@unrs/resolver-binding-linux-x64-gnu@1.7.2':
resolution: {integrity: sha512-dEidzJDubxxhUCBJ/SHSMJD/9q7JkyfBMT77Px1npl4xpg9t0POLvnWywSk66BgZS/b2Hy9Y1yFaoMTFJUe9yg==} resolution: {integrity: sha512-dEidzJDubxxhUCBJ/SHSMJD/9q7JkyfBMT77Px1npl4xpg9t0POLvnWywSk66BgZS/b2Hy9Y1yFaoMTFJUe9yg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@unrs/resolver-binding-linux-x64-musl@1.7.2': '@unrs/resolver-binding-linux-x64-musl@1.7.2':
resolution: {integrity: sha512-RvP+Ux3wDjmnZDT4XWFfNBRVG0fMsc+yVzNFUqOflnDfZ9OYujv6nkh+GOr+watwrW4wdp6ASfG/e7bkDradsw==} resolution: {integrity: sha512-RvP+Ux3wDjmnZDT4XWFfNBRVG0fMsc+yVzNFUqOflnDfZ9OYujv6nkh+GOr+watwrW4wdp6ASfG/e7bkDradsw==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@unrs/resolver-binding-wasm32-wasi@1.7.2': '@unrs/resolver-binding-wasm32-wasi@1.7.2':
resolution: {integrity: sha512-y797JBmO9IsvXVRCKDXOxjyAE4+CcZpla2GSoBQ33TVb3ILXuFnMrbR/QQZoauBYeOFuu4w3ifWLw52sdHGz6g==} resolution: {integrity: sha512-y797JBmO9IsvXVRCKDXOxjyAE4+CcZpla2GSoBQ33TVb3ILXuFnMrbR/QQZoauBYeOFuu4w3ifWLw52sdHGz6g==}
@ -1259,6 +1287,48 @@ packages:
'@vueuse/core@9.13.0': '@vueuse/core@9.13.0':
resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==} resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
'@vueuse/integrations@13.2.0':
resolution: {integrity: sha512-tnwdzUYadAiewvMtBcjH/ZPgRCoQBvuVzbFA/VSysPDaIuG41Jp/Z1Sn/rYoFMOLJfcOEcVh+tN3mkrVIyumig==}
peerDependencies:
async-validator: ^4
axios: ^1
change-case: ^5
drauu: ^0.4
focus-trap: ^7
fuse.js: ^7
idb-keyval: ^6
jwt-decode: ^4
nprogress: ^0.2
qrcode: ^1.5
sortablejs: ^1
universal-cookie: ^7
vue: ^3.5.0
peerDependenciesMeta:
async-validator:
optional: true
axios:
optional: true
change-case:
optional: true
drauu:
optional: true
focus-trap:
optional: true
fuse.js:
optional: true
idb-keyval:
optional: true
jwt-decode:
optional: true
nprogress:
optional: true
qrcode:
optional: true
sortablejs:
optional: true
universal-cookie:
optional: true
'@vueuse/metadata@13.2.0': '@vueuse/metadata@13.2.0':
resolution: {integrity: sha512-kPpzuQCU0+D8DZCzK0iPpIcXI+6ufWSgwnjJ6//GNpEn+SHViaCtR+XurzORChSgvpHO9YC8gGM97Y1kB+UabA==} resolution: {integrity: sha512-kPpzuQCU0+D8DZCzK0iPpIcXI+6ufWSgwnjJ6//GNpEn+SHViaCtR+XurzORChSgvpHO9YC8gGM97Y1kB+UabA==}
@ -1482,6 +1552,10 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'} engines: {node: '>=6'}
camelcase@5.3.1:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
caniuse-lite@1.0.30001718: caniuse-lite@1.0.30001718:
resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==} resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==}
@ -1549,6 +1623,9 @@ packages:
resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==}
engines: {node: '>=18'} engines: {node: '>=18'}
cliui@6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
cliui@8.0.1: cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -1903,6 +1980,10 @@ packages:
supports-color: supports-color:
optional: true optional: true
decamelize@1.2.0:
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
engines: {node: '>=0.10.0'}
decode-named-character-reference@1.1.0: decode-named-character-reference@1.1.0:
resolution: {integrity: sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==} resolution: {integrity: sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==}
@ -1956,6 +2037,9 @@ packages:
devlop@1.1.0: devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
dijkstrajs@1.0.3:
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
dir-glob@3.0.1: dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -3331,6 +3415,9 @@ packages:
normalize-wheel-es@1.2.0: normalize-wheel-es@1.2.0:
resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==} resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==}
nprogress@0.2.0:
resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==}
nth-check@2.1.1: nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
@ -3540,6 +3627,10 @@ packages:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'} engines: {node: '>=4'}
pngjs@5.0.0:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}
pnpm-workspace-yaml@0.3.1: pnpm-workspace-yaml@0.3.1:
resolution: {integrity: sha512-3nW5RLmREmZ8Pm8MbPsO2RM+99RRjYd25ynj3NV0cFsN7CcEl4sDFzgoFmSyduFwxFQ2Qbu3y2UdCh6HlyUOeA==} resolution: {integrity: sha512-3nW5RLmREmZ8Pm8MbPsO2RM+99RRjYd25ynj3NV0cFsN7CcEl4sDFzgoFmSyduFwxFQ2Qbu3y2UdCh6HlyUOeA==}
@ -3617,6 +3708,11 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'} engines: {node: '>=6'}
qrcode@1.5.4:
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
engines: {node: '>=10.13.0'}
hasBin: true
qs@6.14.0: qs@6.14.0:
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
engines: {node: '>=0.6'} engines: {node: '>=0.6'}
@ -3698,6 +3794,9 @@ packages:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
require-main-filename@2.0.0:
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
reset-css@5.0.2: reset-css@5.0.2:
resolution: {integrity: sha512-YtgUGSq5z5W0NPSjsBW7ys7rtWa8P8AiE7S6Fg3d1TQCPpAodgYyLuZYlU0AOsLtprk/fC9ormHN/0pAavVIDw==} resolution: {integrity: sha512-YtgUGSq5z5W0NPSjsBW7ys7rtWa8P8AiE7S6Fg3d1TQCPpAodgYyLuZYlU0AOsLtprk/fC9ormHN/0pAavVIDw==}
@ -3914,6 +4013,9 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
set-function-length@1.2.2: set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -4528,6 +4630,9 @@ packages:
resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
which-module@2.0.1:
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
which-typed-array@1.1.19: which-typed-array@1.1.19:
resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -4541,6 +4646,10 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
wrap-ansi@7.0.0: wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -4553,6 +4662,9 @@ packages:
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
engines: {node: '>=12'} engines: {node: '>=12'}
y18n@4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
y18n@5.0.8: y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -4566,10 +4678,18 @@ packages:
engines: {node: '>= 14.6'} engines: {node: '>= 14.6'}
hasBin: true hasBin: true
yargs-parser@18.1.3:
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
engines: {node: '>=6'}
yargs-parser@21.1.1: yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'} engines: {node: '>=12'}
yargs@15.4.1:
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
engines: {node: '>=8'}
yargs@17.7.2: yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -5943,6 +6063,16 @@ snapshots:
- '@vue/composition-api' - '@vue/composition-api'
- vue - vue
'@vueuse/integrations@13.2.0(async-validator@4.2.5)(nprogress@0.2.0)(qrcode@1.5.4)(vue@3.5.14(typescript@5.8.3))':
dependencies:
'@vueuse/core': 13.2.0(vue@3.5.14(typescript@5.8.3))
'@vueuse/shared': 13.2.0(vue@3.5.14(typescript@5.8.3))
vue: 3.5.14(typescript@5.8.3)
optionalDependencies:
async-validator: 4.2.5
nprogress: 0.2.0
qrcode: 1.5.4
'@vueuse/metadata@13.2.0': {} '@vueuse/metadata@13.2.0': {}
'@vueuse/metadata@9.13.0': {} '@vueuse/metadata@9.13.0': {}
@ -6172,6 +6302,8 @@ snapshots:
callsites@3.1.0: {} callsites@3.1.0: {}
camelcase@5.3.1: {}
caniuse-lite@1.0.30001718: {} caniuse-lite@1.0.30001718: {}
ccount@2.0.1: {} ccount@2.0.1: {}
@ -6253,6 +6385,12 @@ snapshots:
slice-ansi: 5.0.0 slice-ansi: 5.0.0
string-width: 7.2.0 string-width: 7.2.0
cliui@6.0.0:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 6.2.0
cliui@8.0.1: cliui@8.0.1:
dependencies: dependencies:
string-width: 4.2.3 string-width: 4.2.3
@ -6617,6 +6755,8 @@ snapshots:
dependencies: dependencies:
ms: 2.1.3 ms: 2.1.3
decamelize@1.2.0: {}
decode-named-character-reference@1.1.0: decode-named-character-reference@1.1.0:
dependencies: dependencies:
character-entities: 2.0.2 character-entities: 2.0.2
@ -6668,6 +6808,8 @@ snapshots:
dependencies: dependencies:
dequal: 2.0.3 dequal: 2.0.3
dijkstrajs@1.0.3: {}
dir-glob@3.0.1: dir-glob@3.0.1:
dependencies: dependencies:
path-type: 4.0.0 path-type: 4.0.0
@ -8409,6 +8551,8 @@ snapshots:
normalize-wheel-es@1.2.0: {} normalize-wheel-es@1.2.0: {}
nprogress@0.2.0: {}
nth-check@2.1.1: nth-check@2.1.1:
dependencies: dependencies:
boolbase: 1.0.0 boolbase: 1.0.0
@ -8602,6 +8746,8 @@ snapshots:
pluralize@8.0.0: {} pluralize@8.0.0: {}
pngjs@5.0.0: {}
pnpm-workspace-yaml@0.3.1: pnpm-workspace-yaml@0.3.1:
dependencies: dependencies:
yaml: 2.8.0 yaml: 2.8.0
@ -8674,6 +8820,12 @@ snapshots:
punycode@2.3.1: {} punycode@2.3.1: {}
qrcode@1.5.4:
dependencies:
dijkstrajs: 1.0.3
pngjs: 5.0.0
yargs: 15.4.1
qs@6.14.0: qs@6.14.0:
dependencies: dependencies:
side-channel: 1.1.0 side-channel: 1.1.0
@ -8761,6 +8913,8 @@ snapshots:
require-from-string@2.0.2: {} require-from-string@2.0.2: {}
require-main-filename@2.0.0: {}
reset-css@5.0.2: {} reset-css@5.0.2: {}
resolve-from@4.0.0: {} resolve-from@4.0.0: {}
@ -8962,6 +9116,8 @@ snapshots:
semver@7.7.2: {} semver@7.7.2: {}
set-blocking@2.0.0: {}
set-function-length@1.2.2: set-function-length@1.2.2:
dependencies: dependencies:
define-data-property: 1.1.4 define-data-property: 1.1.4
@ -9720,6 +9876,8 @@ snapshots:
is-weakmap: 2.0.2 is-weakmap: 2.0.2
is-weakset: 2.0.4 is-weakset: 2.0.4
which-module@2.0.1: {}
which-typed-array@1.1.19: which-typed-array@1.1.19:
dependencies: dependencies:
available-typed-arrays: 1.0.7 available-typed-arrays: 1.0.7
@ -9736,6 +9894,12 @@ snapshots:
word-wrap@1.2.5: {} word-wrap@1.2.5: {}
wrap-ansi@6.2.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi@7.0.0: wrap-ansi@7.0.0:
dependencies: dependencies:
ansi-styles: 4.3.0 ansi-styles: 4.3.0
@ -9750,6 +9914,8 @@ snapshots:
xml-name-validator@4.0.0: {} xml-name-validator@4.0.0: {}
y18n@4.0.3: {}
y18n@5.0.8: {} y18n@5.0.8: {}
yaml-eslint-parser@1.3.0: yaml-eslint-parser@1.3.0:
@ -9759,8 +9925,27 @@ snapshots:
yaml@2.8.0: {} yaml@2.8.0: {}
yargs-parser@18.1.3:
dependencies:
camelcase: 5.3.1
decamelize: 1.2.0
yargs-parser@21.1.1: {} yargs-parser@21.1.1: {}
yargs@15.4.1:
dependencies:
cliui: 6.0.0
decamelize: 1.2.0
find-up: 4.1.0
get-caller-file: 2.0.5
require-directory: 2.1.1
require-main-filename: 2.0.0
set-blocking: 2.0.0
string-width: 4.2.3
which-module: 2.0.1
y18n: 4.0.3
yargs-parser: 18.1.3
yargs@17.7.2: yargs@17.7.2:
dependencies: dependencies:
cliui: 8.0.1 cliui: 8.0.1

View File

@ -1,7 +1,7 @@
import type { import type {
ChatSessionVo, ChatSessionVo,
CreateSessionDTO, CreateSessionDTO,
CreateSessionVO, // CreateSessionVO,
GetSessionListParams, GetSessionListParams,
} from './types'; } from './types';
import { get, post } from '@/utils/request'; import { get, post } from '@/utils/request';
@ -11,5 +11,5 @@ export function getSessionList(params: GetSessionListParams) {
} }
export function createSession(data: CreateSessionDTO) { export function createSession(data: CreateSessionDTO) {
return post<CreateSessionVO>('/system/session', data); return post('/system/session', data);
} }

View File

@ -137,6 +137,6 @@ export interface CreateSessionDTO {
userId: number; userId: number;
} }
export interface CreateSessionVO { // export interface CreateSessionVO {
id: number; // id: number;
} // }

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

View File

@ -5,7 +5,7 @@ import type { LoginDTO } from '@/api/auth/types';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { login } from '@/api'; import { login } from '@/api';
import { useUserStore } from '@/store'; import { useUserStore } from '@/stores';
const userStore = useUserStore(); const userStore = useUserStore();
@ -29,7 +29,9 @@ async function handleSubmit() {
console.log(res, 'res'); console.log(res, 'res');
res.data.token && userStore.setToken(res.data.token); res.data.token && userStore.setToken(res.data.token);
res.data.userInfo && userStore.setUserInfo(res.data.userInfo); res.data.userInfo && userStore.setUserInfo(res.data.userInfo);
ElMessage.success('登录成功');
router.replace('/'); router.replace('/');
userStore.closeLoginDialog();
} }
catch (error) { catch (error) {
console.error('请求错误:', error); console.error('请求错误:', error);
@ -39,7 +41,7 @@ async function handleSubmit() {
<template> <template>
<div class="custom-form"> <div class="custom-form">
<el-form ref="formRef" :model="formModel" :rules="rules"> <el-form ref="formRef" :model="formModel" :rules="rules" style="width: 230px">
<el-form-item prop="username"> <el-form-item prop="username">
<el-input v-model="formModel.username" placeholder="请输入用户名" clearable> <el-input v-model="formModel.username" placeholder="请输入用户名" clearable>
<template #prefix> <template #prefix>

View File

@ -2,7 +2,7 @@
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import logoPng from '@/assets/images/logo.png'; import logoPng from '@/assets/images/logo.png';
import SvgIcon from '@/components/SvgIcon/index.vue'; import SvgIcon from '@/components/SvgIcon/index.vue';
import { useUserStore } from '@/store'; import { useUserStore } from '@/stores';
import AccountPassword from './components/FormLogin/AccountPassword.vue'; import AccountPassword from './components/FormLogin/AccountPassword.vue';
import QrCodeLogin from './components/QrCodeLogin/index.vue'; import QrCodeLogin from './components/QrCodeLogin/index.vue';

View File

@ -1,7 +1,7 @@
<!-- 欢迎提示词 --> <!-- 欢迎提示词 -->
<script setup lang="ts"> <script setup lang="ts">
import { useTimeGreeting } from '@/hooks/useTimeGreeting'; import { useTimeGreeting } from '@/hooks/useTimeGreeting';
import { useUserStore } from '@/store'; import { useUserStore } from '@/stores';
const greeting = useTimeGreeting(); const greeting = useTimeGreeting();
const userStore = useUserStore(); const userStore = useUserStore();

View File

@ -3,7 +3,7 @@
export const HOME_URL: string = '/chat'; export const HOME_URL: string = '/chat';
// 登录页地址[默认] // 登录页地址[默认]
export const LOGIN_URL: string = '/login'; // export const LOGIN_URL: string = '/login';
// 默认主题颜色 // 默认主题颜色
export const DEFAULT_THEME_COLOR: string = '#2992FF'; export const DEFAULT_THEME_COLOR: string = '#2992FF';
@ -15,4 +15,4 @@ export const COLLAPSE_THRESHOLD: number = 600;
export const SIDE_BAR_WIDTH: number = 280; export const SIDE_BAR_WIDTH: number = 280;
// 路由白名单地址[本地存在的路由 staticRouter.ts 中] // 路由白名单地址[本地存在的路由 staticRouter.ts 中]
export const ROUTER_WHITE_LIST: string[] = ['/500']; export const ROUTER_WHITE_LIST: string[] = ['/chat', '/500', '/403', '/404'];

View File

@ -1,6 +1,6 @@
import { COLLAPSE_THRESHOLD } from '@/config/index'; import { COLLAPSE_THRESHOLD } from '@/config/index';
import { useWindowWidthObserver } from '@/hooks/useWindowWidthObserver'; import { useWindowWidthObserver } from '@/hooks/useWindowWidthObserver';
import { useDesignStore } from '@/store'; import { useDesignStore } from '@/stores';
/** /**
* 便 * 便

View File

@ -1,7 +1,7 @@
import type { MaybeRef } from 'vue'; import type { MaybeRef } from 'vue';
import { onBeforeUnmount, ref, unref, watch } from 'vue'; import { onBeforeUnmount, ref, unref, watch } from 'vue';
import { COLLAPSE_THRESHOLD, SIDE_BAR_WIDTH } from '@/config/index'; import { COLLAPSE_THRESHOLD, SIDE_BAR_WIDTH } from '@/config/index';
import { useDesignStore } from '@/store'; import { useDesignStore } from '@/stores';
/** /**
* *

View File

@ -5,7 +5,7 @@ import { useWindowWidthObserver } from '@/hooks/useWindowWidthObserver';
import Aside from '@/layouts/components/Aside/index.vue'; import Aside from '@/layouts/components/Aside/index.vue';
import Header from '@/layouts/components/Header/index.vue'; import Header from '@/layouts/components/Header/index.vue';
import Main from '@/layouts/components/Main/index.vue'; import Main from '@/layouts/components/Main/index.vue';
import { useDesignStore } from '@/store'; import { useDesignStore } from '@/stores';
const designStore = useDesignStore(); const designStore = useDesignStore();

View File

@ -1,27 +1,31 @@
<!-- Aside 侧边栏 --> <!-- Aside 侧边栏 -->
<script setup lang="ts"> <script setup lang="ts">
import type { GroupableOptions } from 'vue-element-plus-x/types/Conversations'; import type { ConversationItem, GroupableOptions } from 'vue-element-plus-x/types/Conversations';
import { useRoute } from 'vue-router'; import type { ChatSessionVo } from '@/api/session/types';
import { useRoute, useRouter } from 'vue-router';
import logo from '@/assets/images/logo.png'; import logo from '@/assets/images/logo.png';
import Collapse from '@/layouts/components/Header/components/Collapse.vue'; import Collapse from '@/layouts/components/Header/components/Collapse.vue';
import { useDesignStore } from '@/store'; import { useDesignStore } from '@/stores';
import { useChatStore } from '@/store/modules/chat'; import { useSessionStore } from '@/stores/modules/session';
const route = useRoute(); const route = useRoute();
const chatStore = useChatStore(); const router = useRouter();
const designStore = useDesignStore(); const designStore = useDesignStore();
const sessionStore = useSessionStore();
const chatId = computed(() => Number(route.params?.id)); // const sessionId = computed(() => Number(route.params?.id));
const conversationsList = computed(() => chatStore.chatMap[chatId.value] ?? []); const conversationsList = computed(() => sessionStore.sessionList);
/* 创建会话 开始 */ /* 创建会话 开始 */
function handleCreatChat() { function handleCreatChat() {
console.log('创建新会话'); console.log('创建新会话');
// ,
sessionStore.createSessionBtn();
} }
/* 创建会话 结束 */ /* 创建会话 结束 */
/* 会话组件 开始 */ /* 会话组件 开始 */
const active = ref('m1'); const active = computed<string>(() => (route.params?.id as string) ?? '');
// //
const customGroupOptions: GroupableOptions = { const customGroupOptions: GroupableOptions = {
@ -34,10 +38,20 @@ const customGroupOptions: GroupableOptions = {
}, },
}; };
function handleChange() { function handleChange(item: ConversationItem<ChatSessionVo>) {
console.log('点击了会话'); console.log('点击了会话 item', item);
router.replace({
name: 'chatWithId',
params: {
id: item.id,
},
});
} }
/* 会话组件 结束 */ /* 会话组件 结束 */
watchEffect(() => {
console.log('active', active.value, '>>>');
});
</script> </script>
<template> <template>

View File

@ -2,8 +2,12 @@
<script setup lang="ts"> <script setup lang="ts">
import Popover from '@/components/Popover/index.vue'; import Popover from '@/components/Popover/index.vue';
import SvgIcon from '@/components/SvgIcon/index.vue'; import SvgIcon from '@/components/SvgIcon/index.vue';
import { useUserStore } from '@/stores';
const src = ref('https://avatars.githubusercontent.com/u/76239030'); const userStore = useUserStore();
const src = computed(
() => userStore.userInfo?.avatar ?? 'https://avatars.githubusercontent.com/u/76239030',
);
/* 弹出面板 开始 */ /* 弹出面板 开始 */
const popoverStyle = ref({ const popoverStyle = ref({
@ -40,9 +44,11 @@ const popoverList = ref([
function handleClick(item: any) { function handleClick(item: any) {
switch (item.key) { switch (item.key) {
case '1': case '1':
ElMessage.warning('暂未开放');
console.log('点击了收藏夹'); console.log('点击了收藏夹');
break; break;
case '2': case '2':
ElMessage.warning('暂未开放');
console.log('点击了设置'); console.log('点击了设置');
break; break;
case '4': case '4':
@ -57,7 +63,7 @@ function handleClick(item: any) {
}) })
.then(() => { .then(() => {
// 退 // 退
userStore.logout();
ElMessage({ ElMessage({
type: 'success', type: 'success',
message: '退出成功', message: '退出成功',

View File

@ -3,7 +3,7 @@
import SvgIcon from '@/components/SvgIcon/index.vue'; import SvgIcon from '@/components/SvgIcon/index.vue';
import { SIDE_BAR_WIDTH } from '@/config/index'; import { SIDE_BAR_WIDTH } from '@/config/index';
import { useCollapseToggle } from '@/hooks/useCollapseToggle'; import { useCollapseToggle } from '@/hooks/useCollapseToggle';
import { useDesignStore } from '@/store'; import { useDesignStore } from '@/stores';
const { changeCollapse } = useCollapseToggle(); const { changeCollapse } = useCollapseToggle();
const designStore = useDesignStore(); const designStore = useDesignStore();

View File

@ -1,10 +1,9 @@
<!-- LoginBtn 登录按钮 --> <!-- LoginBtn 登录按钮 -->
<script setup lang="ts"> <script setup lang="ts">
import LoginDialog from '@/components/LoginDialog/index.vue'; import LoginDialog from '@/components/LoginDialog/index.vue';
import { useUserStore } from '@/store'; import { useUserStore } from '@/stores';
const userStore = useUserStore(); const userStore = useUserStore();
const isLoginDialogVisible = computed(() => userStore.isLoginDialogVisible); const isLoginDialogVisible = computed(() => userStore.isLoginDialogVisible);
// Store // Store

View File

@ -1,7 +1,7 @@
<!-- Header 头部 --> <!-- Header 头部 -->
<script setup lang="ts"> <script setup lang="ts">
import { SIDE_BAR_WIDTH } from '@/config/index'; import { SIDE_BAR_WIDTH } from '@/config/index';
import { useDesignStore, useUserStore } from '@/store'; import { useDesignStore, useUserStore } from '@/stores';
import Avatar from './components/Avatar.vue'; import Avatar from './components/Avatar.vue';
import Collapse from './components/Collapse.vue'; import Collapse from './components/Collapse.vue';
import CreateChat from './components/CreateChat.vue'; import CreateChat from './components/CreateChat.vue';
@ -54,10 +54,8 @@ onMounted(() => {
<!-- 右边 --> <!-- 右边 -->
<div class="right-box flex h-full items-center pr-20px flex-shrink-0 mr-auto flex-row"> <div class="right-box flex h-full items-center pr-20px flex-shrink-0 mr-auto flex-row">
<Avatar v-if="userStore.token" /> <Avatar v-show="userStore.token" />
<LoginBtn v-else /> <LoginBtn v-show="!userStore.token" />
<!-- <Avatar />
<LoginBtn /> -->
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,7 +1,7 @@
<!-- Main --> <!-- Main -->
<script setup lang="ts"> <script setup lang="ts">
import { useDesignStore } from '@/store'; import { useDesignStore } from '@/stores';
import { useKeepAliveStore } from '@/store/modules/keepAlive'; import { useKeepAliveStore } from '@/stores/modules/keepAlive';
const designStore = useDesignStore(); const designStore = useDesignStore();
const keepAliveStore = useKeepAliveStore(); const keepAliveStore = useKeepAliveStore();
@ -16,7 +16,7 @@ provide('refresh', refreshMainPage);
<el-main class="layout-main"> <el-main class="layout-main">
<router-view v-slot="{ Component, route }"> <router-view v-slot="{ Component, route }">
<transition :name="designStore.pageAnimateType" mode="out-in" appear> <transition :name="designStore.pageAnimateType" mode="out-in" appear>
<keep-alive :max="16" :include="keepAliveStore.keepAliveName"> <keep-alive :max="10" :include="keepAliveStore.keepAliveName">
<component :is="Component" v-if="isRouterShow" :key="route.fullPath" /> <component :is="Component" v-if="isRouterShow" :key="route.fullPath" />
</keep-alive> </keep-alive>
</transition> </transition>

View File

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { ConversationItem } from 'vue-element-plus-x/types/Conversations'; import type { ConversationItem } from 'vue-element-plus-x/types/Conversations';
import type { ChatSessionVo } from '@/api/session/types'; import type { ChatSessionVo } from '@/api/session/types';
// import { useUserStore } from '@/store'; // import { useUserStore } from '@/stores';
import { Conversations } from 'vue-element-plus-x'; import { Conversations } from 'vue-element-plus-x';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
// import { getSessionList } from '@/api'; // import { getSessionList } from '@/api';
@ -43,13 +43,13 @@ function handleChange(item: ConversationItem<ChatSessionVo>) {
const sessionId = computed<string>(() => route.params?.id as string); const sessionId = computed<string>(() => route.params?.id as string);
function handleNewSession() { function handleNewSession() {
if (sessionId.value) { if (sessionId.value) {
router.replace({ name: 'chatWithoutId' }); router.replace({ name: 'chatWithId' });
} }
} }
watchEffect(() => { // watchEffect(() => {
console.log('active', active.value, '>>>'); // console.log('active', active.value, '>>>');
}); // });
</script> </script>
<template> <template>

View File

@ -3,7 +3,7 @@
import type { LayoutType } from '@/config/design'; import type { LayoutType } from '@/config/design';
// import { useScreenStore } from '@/hooks/useScreen'; // import { useScreenStore } from '@/hooks/useScreen';
import LayoutVertical from '@/layouts/LayoutVertical/index.vue'; import LayoutVertical from '@/layouts/LayoutVertical/index.vue';
import { useDesignStore } from '@/store'; import { useDesignStore } from '@/stores';
// //
const LayoutComponent: Record<LayoutType, Component> = { const LayoutComponent: Record<LayoutType, Component> = {

View File

@ -4,8 +4,8 @@ import { ElMessage } from 'element-plus';
import { createApp } from 'vue'; import { createApp } from 'vue';
import ElementPlusX from 'vue-element-plus-x'; import ElementPlusX from 'vue-element-plus-x';
import App from './App.vue'; import App from './App.vue';
import router from './router'; import router from './routers';
import store from './store'; import store from './stores';
import './styles/index.scss'; import './styles/index.scss';
import 'virtual:uno.css'; import 'virtual:uno.css';
import 'element-plus/dist/index.css'; import 'element-plus/dist/index.css';

View File

@ -1,20 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { Sender } from 'vue-element-plus-x'; import { useRoute } from 'vue-router';
import { useRoute, useRouter } from 'vue-router'; import ChatDefaul from '@/pages/chat/layouts/chatDefaul/index.vue';
import { createSession } from '@/api'; import ChatWithId from '@/pages/chat/layouts/chatWithId/index.vue';
import { send } from '@/api/chat'; import { useChatStore } from '@/stores/modules/chat';
import WelecomeText from '@/components/WelecomeText/index.vue';
import { ModelEnum } from '@/constants/enums';
import { useUserStore } from '@/store';
import { useChatStore } from '@/store/modules/chat';
const route = useRoute(); const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
const chatStore = useChatStore(); const chatStore = useChatStore();
const senderValue = ref(''); const senderValue = ref('');
const isSelect = ref(false);
const chatId = computed(() => Number(route.params?.id)); const chatId = computed(() => Number(route.params?.id));
if (chatId.value) { if (chatId.value) {
@ -30,107 +23,28 @@ watch(
senderValue.value = v; senderValue.value = v;
localStorage.removeItem('chatContent'); localStorage.removeItem('chatContent');
// //
console.log(v); console.log('发送消息 v', v);
} }
} }
}, },
{ immediate: true, deep: true },
); );
// const a = JSON.parse(`{"id":"chatcmpl-BVD1f4snw4KHOCKIgu4JOMoZbOwRh","object":"chat.completion.chunk","created":1746777939,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_ded0d14823","choices":[{"delta":{"content":"","role":"assistant"},"logprobs":null,"finish_reason":null,"index":0}],"usage":null}`) // const a = JSON.parse(`{"id":"chatcmpl-BVD1f4snw4KHOCKIgu4JOMoZbOwRh","object":"chat.completion.chunk","created":1746777939,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_ded0d14823","choices":[{"delta":{"content":"","role":"assistant"},"logprobs":null,"finish_reason":null,"index":0}],"usage":null}`)
// console.log(a); // console.log(a);
const loading = ref(false);
async function handleSend() {
if (!chatId.value) {
try {
const res = await createSession({
userId: userStore.userInfo?.userId as number,
sessionContent: senderValue.value,
sessionTitle: senderValue.value.slice(0, 10),
remark: senderValue.value.slice(0, 10),
});
localStorage.setItem('chatContent', senderValue.value);
router.replace({
name: 'chat',
params: {
id: res.data.id,
},
});
}
catch (error) {
console.error('createSessionError', error);
}
return;
}
//
loading.value = true;
const req = send({
sessionId: chatId.value,
model: ModelEnum.GPT_4o_MINI.value,
messages: [
{
role: 'user',
content: senderValue.value,
},
],
});
for await (const chunk of req) {
if (chunk.result && typeof chunk.result === 'string') {
console.log('string:', chunk.result);
// const a = JSON.parse(chunk.result);
// console.log(a,'>>>');
}
console.log(chunk.result);
}
loading.value = false;
}
</script> </script>
<template> <template>
<div class="chat-home-container"> <div class="chat-container">
<div class="chat-home-wrap"> <!-- 默认聊天页面 -->
<WelecomeText /> <ChatDefaul v-if="!chatId" />
<Sender <!-- 带id的聊天页面 -->
v-model="senderValue" <ChatWithId v-else :chat-id="chatId" />
class="chat-home-sender"
:auto-size="{
maxRows: 9,
minRows: 3,
}"
:loading="loading"
variant="updown"
clearable
allow-speech
@submit="handleSend"
>
<template #prefix>
<div class="flex-1 flex items-center gap-8px flex-none w-fit overflow-hidden">
<div
class="flex items-center gap-4px px-12px py-8px rounded-15px cursor-pointer font-size-12px border-1px border-gray border-solid hover:bg-[rgba(0,0,0,.04)]"
>
<el-icon>
<Paperclip />
</el-icon>
</div>
<div
:class="{ isSelect }"
class="flex items-center gap-4px px-10px py-8px rounded-15px cursor-pointer font-size-12px border-1px border-gray border-solid hover:bg-[rgba(0,0,0,.04)]"
@click="isSelect = !isSelect"
>
<el-icon>
<ElementPlus />
</el-icon>
<span>深度思考</span>
</div>
</div>
</template>
</Sender>
</div>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.chat-home-container { .chat-container {
padding: 0 16px; padding: 0 16px;
width: calc(100% - 32px); width: calc(100% - 32px);
position: relative; position: relative;
@ -140,19 +54,5 @@ async function handleSend() {
justify-content: center; justify-content: center;
height: 100%; height: 100%;
overflow-anchor: none; overflow-anchor: none;
.chat-home-wrap {
position: relative;
width: 100%;
max-width: 800px;
min-height: 450px;
display: flex;
flex-direction: column;
align-items: center;
.chat-home-sender {
width: 100%;
}
}
} }
</style> </style>

View File

@ -0,0 +1,91 @@
<!-- -->
<script setup lang="ts">
import WelecomeText from '@/components/WelecomeText/index.vue';
import { useUserStore } from '@/stores';
import { useChatStore } from '@/stores/modules/chat';
import { useSessionStore } from '@/stores/modules/session';
const userStore = useUserStore();
const chatStore = useChatStore();
const sessionStore = useSessionStore();
const senderValue = ref('');
const isDeepThinking = computed(() => chatStore.isDeepThinking);
async function handleSend() {
await sessionStore.createSessionList({
userId: userStore.userInfo?.userId as number,
sessionContent: senderValue.value,
sessionTitle: senderValue.value.slice(0, 10),
remark: senderValue.value.slice(0, 10),
});
}
//
function setIsDeepThinking() {
chatStore.setDeepThinking(!chatStore.isDeepThinking);
}
</script>
<template>
<div class="chat-defaul-wrap">
<WelecomeText />
<Sender
v-model="senderValue"
class="chat-defaul-sender"
:auto-size="{
maxRows: 9,
minRows: 3,
}"
variant="updown"
clearable
allow-speech
@submit="handleSend"
>
<template #prefix>
<div class="flex-1 flex items-center gap-8px flex-none w-fit overflow-hidden">
<div
class="flex items-center gap-4px px-12px py-8px rounded-15px cursor-pointer font-size-12px border-1px border-gray border-solid hover:bg-[rgba(0,0,0,.04)]"
>
<el-icon>
<Paperclip />
</el-icon>
</div>
<div
:class="{ 'is-select': isDeepThinking }"
class="flex items-center gap-4px px-10px py-8px rounded-15px cursor-pointer font-size-12px border-1px border-gray border-solid hover:bg-[rgba(0,0,0,.04)]"
@click="setIsDeepThinking"
>
<el-icon>
<ElementPlus />
</el-icon>
<span>深度思考</span>
</div>
</div>
</template>
</Sender>
</div>
</template>
<style scoped lang="scss">
.chat-defaul-wrap {
position: relative;
width: 100%;
max-width: 800px;
min-height: 450px;
display: flex;
flex-direction: column;
align-items: center;
.chat-defaul-sender {
width: 100%;
}
.is-select {
color: var(--el-color-primary, #409eff);
border: 1px solid var(--el-color-primary, #409eff);
border-radius: 15px;
}
}
</style>

View File

@ -0,0 +1,18 @@
<!-- 每个回话对应的聊天内容 -->
<script setup lang="ts">
interface ChatWithIdProps {
chatId?: number;
}
const props = withDefaults(defineProps<ChatWithIdProps>(), {
chatId: undefined,
});
console.log('props ==> ', props);
</script>
<template>
<div>新建对话</div>
</template>
<style scoped lang="scss"></style>

65
src/pages/error/403.vue Normal file
View File

@ -0,0 +1,65 @@
<script setup lang="ts">
import { useRouter } from 'vue-router';
import { HOME_URL } from '@/config/index.ts';
//
const router = useRouter();
function handleHomePage() {
router.push({ path: HOME_URL });
}
</script>
<template>
<div id="box">
<div id="banner" class="elx-top" />
<div class="elx-bottom">
<div class="elx-text1">
403
</div>
<div class="elx-text2">
对不起您没有权限访问
</div>
<div class="h-20px" />
<el-button type="primary" plain @click="handleHomePage">
返回首页
</el-button>
</div>
</div>
</template>
<style lang="scss" scoped>
#box {
overflow: hidden;
}
#banner {
margin-top: 60px;
background: url("@/assets/images/error/403.png") no-repeat;
background-size: 100%;
}
.elx-top {
width: 600px;
height: 400px;
margin: 0 auto;
}
.elx-bottom {
height: 300px;
margin-top: 20px;
text-align: center;
}
.elx-text1 {
font-size: 46px;
font-weight: bold;
}
.elx-text2 {
padding-top: 30px;
font-family: YouYuan;
font-size: 24px;
font-weight: 600;
}
</style>

65
src/pages/error/404.vue Normal file
View File

@ -0,0 +1,65 @@
<script setup lang="ts">
import { useRouter } from 'vue-router';
import { HOME_URL } from '@/config/index.ts';
//
const router = useRouter();
function handleHomePage() {
router.push({ path: HOME_URL });
}
</script>
<template>
<div id="box">
<div id="banner" class="elx-top" />
<div class="elx-bottom">
<div class="elx-text1">
404
</div>
<div class="elx-text2">
您想看的页面不存在哟
</div>
<div class="h-20px" />
<el-button type="primary" plain @click="handleHomePage">
返回首页
</el-button>
</div>
</div>
</template>
<style lang="scss" scoped>
#box {
overflow: hidden;
}
#banner {
margin-top: 60px;
background: url("@/assets/images/error/404.png") no-repeat;
background-size: 100%;
}
.elx-top {
width: 600px;
height: 400px;
margin: 0 auto;
}
.elx-bottom {
height: 300px;
margin-top: 20px;
text-align: center;
}
.elx-text1 {
font-size: 46px;
font-weight: bold;
}
.elx-text2 {
padding-top: 30px;
font-family: YouYuan;
font-size: 24px;
font-weight: 600;
}
</style>

60
src/pages/error/500.vue Normal file
View File

@ -0,0 +1,60 @@
<script setup lang="ts">
import { useRouter } from 'vue-router';
import { HOME_URL } from '@/config/index.ts';
//
const router = useRouter();
function handleHomePage() {
router.push({ path: HOME_URL });
}
</script>
<template>
<div id="box">
<div id="banner" class="elx-top" />
<div class="elx-bottom">
<div class="elx-text1">
500
</div>
<div class="elx-text2">
服务器好像开小差了请稍后试试...
</div>
<div class="h-20px" />
<el-button type="primary" plain @click="handleHomePage">
返回首页
</el-button>
</div>
</div>
</template>
<style lang="scss" scoped>
#box {
overflow: hidden;
}
#banner {
margin-top: 60px;
background: url("@/assets/images/error/500.png") no-repeat;
background-size: 100%;
}
.elx-top {
width: 600px;
height: 400px;
margin: 0 auto;
}
.elx-bottom {
height: 300px;
margin-top: 20px;
text-align: center;
}
.elx-text1 {
font-size: 46px;
font-weight: bold;
}
.elx-text2 {
padding-top: 30px;
font-family: YouYuan;
font-size: 24px;
font-weight: 600;
}
</style>

View File

@ -1,53 +0,0 @@
<script setup lang="ts">
import type { FormInstance } from 'element-plus';
import type { LoginDTO } from '@/api/auth/types';
import { reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
import { login } from '@/api';
import { useUserStore } from '@/store';
const userStore = useUserStore();
const formRef = ref<FormInstance>();
const formModel = reactive<LoginDTO>({
username: '',
password: '',
});
const router = useRouter();
async function handleSubmit() {
try {
await formRef.value?.validate();
const res = await login(formModel);
console.log(res, 'res');
res.data.token && userStore.setToken(res.data.token);
res.data.userInfo && userStore.setUserInfo(res.data.userInfo);
router.replace('/');
}
catch (error) {
console.error('请求错误:', error);
}
}
</script>
<template>
<div class="w-screen h-screen justify-center items-center flex bg-black">
<div class="max-w-[500px] w-full p-5 rounded bg-white/60 backdrop-blur-md">
<h1>登录</h1>
<el-form ref="formRef" :model="formModel">
<el-form-item label="用户名" name="username">
<el-input v-model="formModel.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="密码" name="password">
<el-input v-model="formModel.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSubmit">
登录
</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>

View File

@ -1,41 +0,0 @@
import type { RouteRecordRaw } from 'vue-router';
import { createRouter, createWebHistory } from 'vue-router';
import { jwtGuard } from './permissions';
const routes: Readonly<RouteRecordRaw>[] = [
{
path: '/',
redirect: 'chat',
component: () => import('@/layouts/index.vue'),
children: [
{
path: '/chat',
name: 'chat',
component: () => import('@/pages/chat/index.vue'),
},
{
path: '/chat:id',
name: 'chatWithoutId',
component: () => import('@/pages/chat/index.vue'),
},
],
},
{
path: '/login',
name: 'login',
component: () => import('@/pages/login/index.vue'),
},
] as const;
const router = createRouter({
history: createWebHistory(),
routes,
});
console.group('Routes');
console.log('routes', router.getRoutes());
console.groupEnd();
router.beforeEach(jwtGuard());
export default router;

View File

@ -1,7 +0,0 @@
import type { NavigationGuard } from 'vue-router';
export function jwtGuard(): NavigationGuard {
return () => {
console.log('进入路由守卫');
};
}

85
src/routers/index.ts Normal file
View File

@ -0,0 +1,85 @@
import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router';
import { useNProgress } from '@vueuse/integrations/useNProgress';
import { createRouter, createWebHistory } from 'vue-router';
import { ROUTER_WHITE_LIST } from '@/config';
import { errorRouter, layoutRouter, staticRouter } from '@/routers/modules/staticRouter';
import { useUserStore } from '@/stores';
const { start, done } = useNProgress(0, {
showSpinner: false,
trickleSpeed: 200,
minimum: 0.3,
easing: 'ease',
speed: 500,
});
const router = createRouter({
history: createWebHistory(),
routes: [...layoutRouter, ...staticRouter, ...errorRouter],
strict: false,
scrollBehavior: () => ({ left: 0, top: 0 }),
});
console.group('Routes');
console.log('routes', router.getRoutes());
console.groupEnd();
// 路由前置守卫
router.beforeEach(
async (
to: RouteLocationNormalized,
_from: RouteLocationNormalized,
next: NavigationGuardNext,
) => {
const userStore = useUserStore();
// 1、NProgress 开始
start();
// 2、标题
document.title = to.meta.title || import.meta.env.VITE_WEB_TITLE;
// 3、权限 预留
// 3、判断是访问登陆页有Token访问当前页面token过期访问接口axios封装则自动跳转登录页面没有Token重置路由到登陆页。
// if (to.path.toLocaleLowerCase() === LOGIN_URL) {
// // 有Token访问当前页面
// if (userStore.token) {
// return next(from.fullPath);
// }
// else {
// ElMessage.error('账号身份已过期,请重新登录🌻');
// }
// // 没有Token重置路由到登陆页。
// // resetRouter(); // 预留
// return next();
// }
// 4、判断访问页面是否在路由白名单地址[静态路由]中,如果存在直接放行。
if (ROUTER_WHITE_LIST.includes(to.path))
return next();
// 5、判断是否有 Token没有重定向到 login 页面。
if (!userStore.token)
userStore.logout();
// 其余逻辑 预留...
// 6、正常访问页面。
next();
},
);
// 路由跳转错误
router.onError((error) => {
// 结束全屏动画
done();
console.warn('路由错误', error.message);
});
// 后置路由
router.afterEach(() => {
// 结束全屏动画
done();
});
export default router;

View File

@ -0,0 +1,39 @@
// 预留
import router from '@/routers/index';
import { useUserStore } from '@/stores';
import { useAuthStore } from '@/stores/modules/auth';
export async function initDynamicRouter() {
const userStore = useUserStore();
const authStore = useAuthStore();
try {
// 1、预留 获取菜单列表 || 按钮权限列表 || 递归菜单数据
await authStore.requestAuthMenuList();
// 2、判断当前用户是否拥有菜单权限
console.log('authStore.authMenuList', authStore.authMenuList);
if (authStore.authMenuList == null || authStore.authMenuList.length === 0) {
userStore.logout();
return;
}
// 3、添加动态路由
authStore.authMenuList.forEach((item: any) => {
if (item.isFull === '0') {
// 如果是全屏的话,直接为整个页面
router.addRoute(item);
}
else {
router.addRoute('layout', item);
}
});
}
catch (error) {
console.log(error);
// 当菜单请求出错时,重定向到首页
userStore.logout();
return Promise.reject(error);
}
}

View File

@ -0,0 +1,91 @@
import type { RouteRecordRaw } from 'vue-router';
import { HOME_URL } from '@/config';
// LayoutRouter[布局路由]
export const layoutRouter: RouteRecordRaw[] = [
{
path: '/',
redirect: HOME_URL,
component: () => import('@/layouts/index.vue'),
children: [
{
path: HOME_URL,
name: 'chat',
component: () => import('@/pages/chat/index.vue'),
meta: {
title: '通用聊天页面',
icon: 'HomeFilled',
isHide: '1',
isKeepAlive: '0', // 是否缓存路由数据[0是1否]
isFull: '1', // 是否缓存全屏[0是1否]
// enName: "Master Station", // 英文名称 预留
},
},
{
path: '/chat/:id',
name: 'chatWithId',
component: () => import('@/pages/chat/index.vue'),
meta: {
title: '带 ID 的聊天页面',
},
},
],
},
];
// staticRouter[静态路由] 预留
export const staticRouter: RouteRecordRaw[] = [];
// errorRouter (错误页面路由)
export const errorRouter = [
{
path: '/403',
name: '403',
component: () => import('@/pages/error/403.vue'),
meta: {
title: '403页面',
enName: '403 Page', // 英文名称
icon: 'QuestionFilled', // 菜单图标
isHide: '1', // 代表路由在菜单中是否隐藏,是否隐藏[0隐藏1显示]
isLink: '1', // 是否外链[有值则是外链]
isKeepAlive: '0', // 是否缓存路由数据[0是1否]
isFull: '1', // 是否缓存全屏[0是1否]
isAffix: '1', // 是否缓存固定路由[0是1否]
},
},
{
path: '/404',
name: '404',
component: () => import('@/pages/error/404.vue'),
meta: {
title: '404页面',
enName: '404 Page', // 英文名称
icon: 'CircleCloseFilled', // 菜单图标
isHide: '1', // 代表路由在菜单中是否隐藏,是否隐藏[0隐藏1显示]
isLink: '1', // 是否外链[有值则是外链]
isKeepAlive: '0', // 是否缓存路由数据[0是1否]
isFull: '1', // 是否缓存全屏[0是1否]
isAffix: '1', // 是否缓存固定路由[0是1否]
},
},
{
path: '/500',
name: '500',
component: () => import('@/pages/error/500.vue'),
meta: {
title: '500页面',
enName: '500 Page', // 英文名称
icon: 'WarningFilled', // 图标
isHide: '1', // 代表路由在菜单中是否隐藏,是否隐藏[0隐藏1显示]
isLink: '1', // 是否外链[有值则是外链]
isKeepAlive: '0', // 是否缓存路由数据[0是1否]
isFull: '1', // 是否缓存全屏[0是1否]
isAffix: '1', // 是否缓存固定路由[0是1否]
},
},
// 找不到path将跳转404页面
{
path: '/:pathMatch(.*)*',
component: () => import('@/pages/error/404.vue'),
},
];

View File

@ -0,0 +1,18 @@
import { defineStore } from 'pinia';
// 权限状态管理
export const useAuthStore = defineStore('auth', () => {
// 权限菜单列表
const authMenuList = ref<any[]>([]);
// 请求权限菜单列表
const requestAuthMenuList = async () => {
// const res = await initDynamicRouter();
authMenuList.value = [];
};
return {
authMenuList,
requestAuthMenuList,
};
});

View File

@ -5,6 +5,7 @@ import { useUserStore } from './user';
export const useChatStore = defineStore('chat', () => { export const useChatStore = defineStore('chat', () => {
const userStore = useUserStore(); const userStore = useUserStore();
const chatMap = ref<Record<number, ChatMessageVo[]>>({}); const chatMap = ref<Record<number, ChatMessageVo[]>>({});
const setChatMap = (id: number, data: ChatMessageVo[]) => { const setChatMap = (id: number, data: ChatMessageVo[]) => {
@ -26,8 +27,17 @@ export const useChatStore = defineStore('chat', () => {
} }
}; };
// 是否开启深度思考
const isDeepThinking = ref<boolean>(false);
const setDeepThinking = (value: boolean) => {
isDeepThinking.value = value;
};
return { return {
chatMap, chatMap,
requestChatList, requestChatList,
isDeepThinking,
setDeepThinking,
}; };
}); });

View File

@ -0,0 +1,69 @@
import type { CreateSessionDTO } from '@/api/session/types';
import { defineStore } from 'pinia';
import { useRouter } from 'vue-router';
import { createSession, getSessionList } from '@/api/session';
import { useUserStore } from './user';
export const useSessionStore = defineStore('session', () => {
const router = useRouter();
const userStore = useUserStore();
const sessionList = ref<CreateSessionDTO[]>([]);
// 单独点击创建新对话按钮
const createSessionBtn = async () => {
try {
// 直接回到首页
router.replace({ name: 'chat' });
}
catch (error) {
console.error('createSessionBtn:', error);
}
};
// 发送消息后,创建对话
const createSessionList = async (data: CreateSessionDTO) => {
try {
const res = await createSession(data);
localStorage.setItem('chatContent', data.sessionContent);
router.replace({
name: 'chatWithId',
params: {
id: `${res.data}`,
},
});
}
catch (error) {
console.error('createSession:', error);
}
};
// 获取会话列表
const requestSessionList = async () => {
try {
const res = await getSessionList({
userId: userStore.userInfo?.userId as number,
});
if (res.rows) {
sessionList.value = (res.rows || []).map(item => ({
...item,
// 提供默认值
remark: item.remark || '',
sessionContent: item.sessionContent || '',
sessionTitle: item.sessionTitle || '',
userId: item.userId || -1,
}));
}
}
catch (error) {
console.error('getSessionList:', error);
}
};
return {
sessionList,
createSessionBtn,
createSessionList,
requestSessionList,
};
});

View File

@ -16,4 +16,9 @@
--login-dialog-logo-background: #fff; --login-dialog-logo-background: #fff;
--login-dialog-logo-text-color: #191919; --login-dialog-logo-text-color: #191919;
/* 覆盖 element-plus 样式 */
--el-border-radius-base: 12px;
--el-messagebox-border-radius: 16px;
} }

View File

@ -2,7 +2,7 @@ import type { HookFetchPlugin } from 'hook-fetch';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import hookFetch from 'hook-fetch'; import hookFetch from 'hook-fetch';
import { sseTextDecoderPlugin } from 'hook-fetch/plugins'; import { sseTextDecoderPlugin } from 'hook-fetch/plugins';
import { useUserStore } from '@/store'; import { useUserStore } from '@/stores';
interface BaseResponse { interface BaseResponse {
code: number; code: number;
@ -34,8 +34,9 @@ function jwtPlugin(): HookFetchPlugin<BaseResponse> {
return response; return response;
} }
if (response.result?.code === 401) { if (response.result?.code === 401) {
// 如果没有权限,也不退出,则是弹框提示登录 // 如果没有权限,退出,且弹框提示登录
userStore.logout(); userStore.logout();
userStore.openLoginDialog();
} }
ElMessage.error(response.result?.msg); ElMessage.error(response.result?.msg);
return Promise.reject(response); return Promise.reject(response);