mirror of
https://github.com/zclzone/vue-naive-admin.git
synced 2026-01-22 23:50:22 +08:00
Compare commits
34 Commits
v2.0.0
...
a63e72bc2f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a63e72bc2f | ||
|
|
04723ffbfa | ||
|
|
fd9480e92f | ||
|
|
0520cd015a | ||
|
|
f5a26c32e9 | ||
|
|
886ef9e11c | ||
|
|
7d0a17b2b5 | ||
|
|
fa4967efc3 | ||
|
|
207150623e | ||
|
|
6981692c54 | ||
|
|
b64e1c7595 | ||
|
|
efcbe3bea4 | ||
|
|
2c808c6d8b | ||
|
|
447db11c52 | ||
|
|
a3d0e863cc | ||
|
|
a2827e4c0d | ||
|
|
f888c2fbfd | ||
|
|
1c37a38b92 | ||
|
|
f2eb40357d | ||
|
|
58de3c1ad6 | ||
|
|
567e306a5c | ||
|
|
065c6b50c6 | ||
|
|
95a1ef654c | ||
|
|
346da8772a | ||
|
|
a6773cbfec | ||
|
|
4c337d3aa8 | ||
|
|
2c9604a829 | ||
|
|
c4fd0459ab | ||
|
|
aa92455dbb | ||
|
|
857381471e | ||
|
|
b780113f18 | ||
|
|
fe4bbded53 | ||
|
|
d801cf28cc | ||
|
|
334174d442 |
2
.npmrc
2
.npmrc
@@ -1,2 +1,2 @@
|
||||
registry=https://registry.npm.taobao.org
|
||||
registry=https://registry.npmmirror.com
|
||||
shamefully-hoist=true
|
||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -18,5 +18,8 @@
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"eslint.validate": ["javascript", "typescript", "javascriptreact", "typescriptreact", "vue"]
|
||||
"eslint.validate": ["javascript", "typescript", "javascriptreact", "typescriptreact", "vue"],
|
||||
"eslint.options": {
|
||||
"overrideConfigFile": "package.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ Vue Naive Admin 是一款极简风格的后台管理模板,包含前后端解
|
||||
|
||||
## 设计理念
|
||||
|
||||
Vue Naive Admin 2022年2月开始开源,从 1.0 到现在的 2.0,一直秉持着`简单即正义`的理念,旨在帮助中小企业、在校大学生及个人开发者快速上手开发后台管理项目,为了降低使用者的学习成本,没有使用看似主流的 TypeScript(前端),这也使得 Vue Naive Admin 成为了市面上少有的 `使用 JavaScript 的 Vue3 后台管理模板`,而且还算优秀,得到了大量朋友的认可和喜爱,截至 2023-11-17, github `1.1k+` star,gitee `260+` star。
|
||||
Vue Naive Admin 2022年2月开始开源,从 1.0 到现在的 2.0,一直秉持着`简单即正义`的理念,旨在帮助中小企业、在校大学生及个人开发者快速上手开发后台管理项目,为了降低使用者的学习成本,没有使用看似主流的 TypeScript(前端),这也使得 Vue Naive Admin 成为了市面上少有的 `使用 JavaScript 的 Vue3 后台管理模板`,而且还算优秀,得到了大量朋友的认可和喜爱。
|
||||
|
||||
## 特性
|
||||
|
||||
@@ -52,6 +52,7 @@ Vue Naive Admin 提供一套后端代码,技术栈使用 Nestjs + TypeOrm + My
|
||||
|
||||
- 源码-github: [isme-nest-serve | github](https://github.com/zclzone/isme-nest-serve)
|
||||
- 源码-gitee: [isme-nest-serve | gitee](https://gitee.com/isme-admin/isme-nest-serve)
|
||||
- 接口文档: [apidoc | isme-nest-serve](https://apifox.com/apidoc/shared-ff4a4d32-c0d1-4caf-b0ee-6abc130f734a)
|
||||
|
||||
## 版权说明
|
||||
|
||||
@@ -64,4 +65,5 @@ Vue Naive Admin 提供一套后端代码,技术栈使用 Nestjs + TypeOrm + My
|
||||
## 其他已对接本项目的后端项目
|
||||
|
||||
- [isme-java-serve](https://github.com/DHBin/isme-java-serve): 一个轻量级的Java后端服务,基于SpringBoot、MybatisPlus、SaToken、MapStruct等实现,已对接 Vue Naive Admin 2.0。
|
||||
- [naive-admin-go](https://github.com/ituserxxx/naive-admin-go): 一个 Go 后端服务,基于 gin、gorm、mysql、jwt和session,已对接 Vue Naive Admin 2.0。
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -11,7 +11,6 @@
|
||||
<div id="app">
|
||||
<!-- 白屏时的loading效果 -->
|
||||
<div class="loading-container">
|
||||
<img src="/resource/logo.png" alt="logo" height="128" />
|
||||
<div class="loading-spin__container">
|
||||
<div class="loading-spin">
|
||||
<div class="left-0 top-0 loading-spin-item"></div>
|
||||
|
||||
56
package.json
56
package.json
@@ -10,37 +10,36 @@
|
||||
"lint:fix": "eslint --fix --ext .js,.vue ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/json": "^2.2.129",
|
||||
"@iconify/utils": "^2.1.11",
|
||||
"@unocss/eslint-config": "^0.58.0",
|
||||
"@unocss/preset-rem-to-px": "^0.58.0",
|
||||
"@vueuse/core": "^10.5.0",
|
||||
"axios": "^1.5.1",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"axios": "^1.6.7",
|
||||
"dayjs": "^1.11.10",
|
||||
"echarts": "^5.4.3",
|
||||
"echarts": "^5.5.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"naive-ui": "^2.35.0",
|
||||
"naive-ui": "^2.38.1",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.0",
|
||||
"sass": "^1.69.3",
|
||||
"unocss": "^0.58.0",
|
||||
"vue": "^3.3.11",
|
||||
"vue-echarts": "^6.6.1",
|
||||
"vue-router": "^4.2.5",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"vue": "^3.4.21",
|
||||
"vue-echarts": "^6.6.9",
|
||||
"vue-router": "^4.3.0",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.5.2",
|
||||
"@iconify/json": "^2.2.188",
|
||||
"@iconify/utils": "^2.1.22",
|
||||
"@unocss/eslint-config": "^0.58.5",
|
||||
"@unocss/preset-rem-to-px": "^0.58.5",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@zclzone/eslint-config": "^0.0.5",
|
||||
"esno": "^0.17.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"esno": "^4.0.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"glob": "^10.3.10",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"unplugin-auto-import": "^0.16.6",
|
||||
"unplugin-vue-components": "^0.25.2",
|
||||
"vite": "^5.0.7",
|
||||
"vite-plugin-simple-html": "^0.1.1",
|
||||
"vite-plugin-vue-devtools": "1.0.0-rc.7"
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"sass": "^1.71.1",
|
||||
"unocss": "^0.58.5",
|
||||
"unplugin-auto-import": "^0.17.5",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
"vite": "^5.1.4",
|
||||
"vite-plugin-simple-html": "^0.1.2"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
@@ -48,13 +47,10 @@
|
||||
"@unocss",
|
||||
".eslint-global-variables.json"
|
||||
],
|
||||
"rules": {
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"varsIgnorePattern": "^_"
|
||||
}
|
||||
]
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6396
pnpm-lock.yaml
generated
6396
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 8.4 KiB |
16
src/assets/icons/isme/apifox.svg
Normal file
16
src/assets/icons/isme/apifox.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg
|
||||
t="1710490574771"
|
||||
class="icon"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="4141"
|
||||
width="200"
|
||||
height="200"
|
||||
>
|
||||
<path
|
||||
d="M1022.934187 429.44a446.122667 446.122667 0 0 0-19.456-100.437333 175.786667 175.786667 0 0 1-15.530667 31.701333 401.92 401.92 0 0 0-164.224-267.264A399.701333 399.701333 0 0 0 724.566187 41.770667a313.6 313.6 0 0 0-14.421334-4.778667 300.458667 300.458667 0 0 1 60.8 148.096c0.256 1.877333 0.469333 3.754667 0.64 5.674667a2.56 2.56 0 0 1 0 0.426666c0.213333 1.92 0.426667 3.882667 0.554667 5.845334v1.024a223.957333 223.957333 0 0 1 0.426667 5.845333 246.357333 246.357333 0 0 1 0.426666 10.026667 8.277333 8.277333 0 0 0 0 0.682666v6.997334a300.672 300.672 0 0 1-54.101333 172.458666 270.293333 270.293333 0 0 1 3.2 93.653334 263.552 263.552 0 0 0 49.664-17.109334 263.850667 263.850667 0 0 0 34.133333-18.773333 268.074667 268.074667 0 0 1 10.368 74.410667 269.525333 269.525333 0 0 1-2.986666 40.362666 266.197333 266.197333 0 0 0 68.906666-16.64c11.605333-4.522667 22.869333-9.813333 33.706667-15.914666a266.410667 266.410667 0 0 1 4.266667 47.701333 269.141333 269.141333 0 0 1-5.76 55.381333 266.752 266.752 0 0 0 81.322666-16.213333A446.165333 446.165333 0 0 0 1024.000853 464.213333a366.933333 366.933333 0 0 0-1.024-34.816z m-119.808 247.210667a242.56 242.56 0 0 1-16.64 0.512h-5.504a266.666667 266.666667 0 0 0 8.448-66.858667 269.653333 269.653333 0 0 0-1.706667-30.08 265.813333 265.813333 0 0 1-82.944 25.344 257.152 257.152 0 0 1-24.448 2.133333 264.618667 264.618667 0 0 0 4.906667-39.594666 276.352 276.352 0 0 0 0.341333-13.312 268.373333 268.373333 0 0 0-6.058667-56.832 265.984 265.984 0 0 1-90.453333 33.109333l-0.213333-0.298667a263.594667 263.594667 0 0 0 4.736-37.802666 239.658667 239.658667 0 0 0 0.426666-14.506667c0-16.128-1.450667-32.256-4.352-48.128a301.098667 301.098667 0 0 1-218.325333 93.354667 300.501333 300.501333 0 0 1-138.538667-33.621334 267.221333 267.221333 0 0 0 55.210667-29.226666 268.8 268.8 0 0 1-152.448-242.346667 268.629333 268.629333 0 0 1 4.352-48.426667 264.362667 264.362667 0 0 1 12.885333-46.421333 264.832 264.832 0 0 1 20.650667-42.752A267.221333 267.221333 0 0 1 305.664853 37.546667 521.045333 521.045333 0 0 0 191.14752 108.8 520.832 520.832 0 0 0 13.99552 391.722667 520.362667 520.362667 0 0 0 0.000853 512.042667a518.528 518.528 0 0 0 63.573334 249.642666 266.709333 266.709333 0 0 1 18.986666-73.088 497.450667 497.450667 0 0 0 366.293334 298.410667 266.069333 266.069333 0 0 1-34.304-59.050667 469.674667 469.674667 0 0 0 133.376 22.784c4.010667 0 8.021333 0.128 12.074666 0.128a468.650667 468.650667 0 0 0 207.658667-48.170666 263.509333 263.509333 0 0 1-30.890667-20.394667 448.298667 448.298667 0 0 0 184.32-132.010667 446.677333 446.677333 0 0 0 57.856-89.301333 267.008 267.008 0 0 1-75.818666 15.616z"
|
||||
fill="#F44A53"
|
||||
p-id="4142"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
15
src/assets/icons/isme/docs.svg
Normal file
15
src/assets/icons/isme/docs.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
t="1710490291582"
|
||||
class="icon"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="3145"
|
||||
width="200"
|
||||
height="200"
|
||||
>
|
||||
<path
|
||||
d="M476.811454 234.638085S575.875862 38.761663 627.148553 9.152912A275.775 275.775 0 0 1 707.702374 0.204763c22.66728 0.839529 111.186386 4.218121 152.753304 63.804195a84.403367 84.403367 0 0 1 4.914315 7.719571 189.938291 189.938291 0 0 1-16.196764 145.996121 204.517427 204.517427 0 0 1-131.048411 98.900597l-179.822992 34.154493a62.616569 62.616569 0 0 0 51.190785 47.83267l163.953849-2.313824a443.353156 443.353156 0 0 1-66.302306 69.373753c-65.524206 55.081285-131.048411 85.242896-177.77536 91.140075-34.400208 2.3343-68.00184-1.474295-102.012998-2.190966 0 0 17.077246 96.812014 172.00104 79.734768a152.978544 152.978544 0 0 1-17.363915 47.382191 166.861485 166.861485 0 0 1-111.432102 79.243336c-52.624128 4.607171-103.917295 2.047631-155.88618 3.173829 0 0-52.378412 189.037333-84.260034 259.639665H176.956308s14.988662-92.143414 47.976004-262.956828A311.731408 311.731408 0 0 1 170.567698 423.634466s28.032074 159.838109 124.905517 181.481573c-7.350997-129.881261-43.55312-267.420664-15.930573-385.568997a382.907077 382.907077 0 0 1 50.105541-76.294747 400.844328 400.844328 0 0 1 166.533864-115.875463s-87.679578 63.763243-66.05659 214.100342z"
|
||||
p-id="3146"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -1,21 +0,0 @@
|
||||
<svg
|
||||
t="1702480351321"
|
||||
class="icon"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="11122"
|
||||
width="200"
|
||||
height="200"
|
||||
>
|
||||
<path
|
||||
d="M509.9008 519.8336m-450.816 0a450.816 450.816 0 1 0 901.632 0 450.816 450.816 0 1 0-901.632 0Z"
|
||||
fill="#C65EDB"
|
||||
p-id="11123"
|
||||
></path>
|
||||
<path
|
||||
d="M798.1568 512.512l-113.3056-78.3872a47.4112 47.4112 0 0 1-20.4288-39.0656l0.3584-137.7792c0.1024-39.2704-44.9024-61.5936-76.0832-37.7856l-109.568 83.5072a47.2832 47.2832 0 0 1-43.4688 7.3216l-130.9184-42.9056c-37.3248-12.2368-72.448 23.6544-59.4432 60.7232l45.568 129.9968A47.3088 47.3088 0 0 1 284.416 501.76l-81.2544 111.2576c-23.1424 31.6928 0.1024 76.2368 39.3728 75.3152l137.728-3.1744a47.3344 47.3344 0 0 1 39.4752 19.6096l80.6912 111.6672c22.9888 31.8464 72.4992 23.4496 83.7632-14.1824l37.9392-126.6176 126.5664 118.272a27.648 27.648 0 0 0 17.7664 7.4752c7.8848 0.3584 15.872-2.6112 21.7088-8.8064a27.91936 27.91936 0 0 0-1.3312-39.4752l-124.8768-116.6848 123.8016-39.8848c37.376-11.9808 44.6976-61.6448 12.3904-84.0192z m-389.6832-6.5024l-42.8032 77.824a27.86816 27.86816 0 0 1-37.9392 11.008 27.93984 27.93984 0 0 1-11.008-37.9392l40.0384-72.7552-19.0464-63.6928c-4.4032-14.7968 3.9936-30.3616 18.7392-34.7648 14.7456-4.4032 30.3616 3.9936 34.7648 18.7392l20.6848 69.2224c3.2256 10.752 1.9968 22.528-3.4304 32.3584z"
|
||||
fill="#FFFFFF"
|
||||
p-id="11124"
|
||||
></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
BIN
src/assets/images/isme.png
Normal file
BIN
src/assets/images/isme.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
12
src/components/common/TheLogo.vue
Normal file
12
src/components/common/TheLogo.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<!--------------------------------
|
||||
- @Author: Ronnie Zhang
|
||||
- @LastEditor: Ronnie Zhang
|
||||
- @LastEditTime: 2024/03/04 16:09:47
|
||||
- @Email: zclzone@outlook.com
|
||||
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
||||
--------------------------------->
|
||||
<template>
|
||||
<div class="h-40 w-40 rounded-full bg-primary p-1/100">
|
||||
<img src="@/assets/images/isme.png" alt="Logo" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -7,25 +7,22 @@
|
||||
--------------------------------->
|
||||
|
||||
<template>
|
||||
<AppCard
|
||||
v-if="$slots.default"
|
||||
bordered
|
||||
bg="#fafafc dark:black"
|
||||
class="mb-30 min-h-60 flex justify-between rounded-4 p-16"
|
||||
>
|
||||
<n-space wrap :size="[32, 16]">
|
||||
<slot />
|
||||
</n-space>
|
||||
<div class="flex-shrink-0">
|
||||
<n-button ghost type="primary" @click="handleReset">
|
||||
<i class="i-fe:rotate-ccw mr-4" />
|
||||
重置
|
||||
</n-button>
|
||||
<n-button class="ml-20" type="primary" @click="handleSearch">
|
||||
<i class="i-fe:search mr-4" />
|
||||
搜索
|
||||
</n-button>
|
||||
</div>
|
||||
<AppCard v-if="$slots.default" bordered bg="#fafafc dark:black" class="mb-30 min-h-60 rounded-4">
|
||||
<form class="flex justify-between p-16" @submit.prevent="handleSearch()">
|
||||
<n-space wrap :size="[32, 16]">
|
||||
<slot />
|
||||
</n-space>
|
||||
<div class="flex-shrink-0">
|
||||
<n-button ghost type="primary" @click="handleReset">
|
||||
<i class="i-fe:rotate-ccw mr-4" />
|
||||
重置
|
||||
</n-button>
|
||||
<n-button attr-type="submit" class="ml-20" type="primary">
|
||||
<i class="i-fe:search mr-4" />
|
||||
搜索
|
||||
</n-button>
|
||||
</div>
|
||||
</form>
|
||||
</AppCard>
|
||||
|
||||
<n-data-table
|
||||
@@ -129,7 +126,7 @@ function handleSearch() {
|
||||
async function handleReset() {
|
||||
const queryItems = { ...props.queryItems }
|
||||
for (const key in queryItems) {
|
||||
queryItems[key] = ''
|
||||
queryItems[key] = null
|
||||
}
|
||||
emit('update:queryItems', { ...queryItems, ...initQuery })
|
||||
await nextTick()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!--------------------------------
|
||||
- @Author: Ronnie Zhang
|
||||
- @LastEditor: Ronnie Zhang
|
||||
- @LastEditTime: 2023/12/16 18:50:02
|
||||
- @LastEditTime: 2024/01/13 17:41:38
|
||||
- @Email: zclzone@outlook.com
|
||||
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
||||
--------------------------------->
|
||||
@@ -9,17 +9,17 @@
|
||||
<template>
|
||||
<n-modal
|
||||
v-model:show="show"
|
||||
class="modal-box"
|
||||
:style="{ width: modalOptions.width, ...modalOptions.modalStyle }"
|
||||
:preset="undefined"
|
||||
size="huge"
|
||||
:bordered="false"
|
||||
@after-leave="onAfterLeave"
|
||||
>
|
||||
<n-card
|
||||
:title="modalOptions.title"
|
||||
:style="modalOptions.contentStyle"
|
||||
:closable="modalOptions.closable"
|
||||
@close="close()"
|
||||
>
|
||||
<n-card :style="modalOptions.contentStyle" :closable="modalOptions.closable" @close="close()">
|
||||
<template #header>
|
||||
<header class="modal-header">{{ modalOptions.title }}</header>
|
||||
</template>
|
||||
<slot></slot>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
@@ -45,6 +45,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { initDrag } from './utils'
|
||||
const props = defineProps({
|
||||
width: {
|
||||
type: String,
|
||||
@@ -101,12 +102,17 @@ const show = ref(false)
|
||||
const modalOptions = ref({})
|
||||
|
||||
// 打开模态框
|
||||
function open(options = {}) {
|
||||
async function open(options = {}) {
|
||||
// 将props和options合并赋值给modalOptions
|
||||
modalOptions.value = { ...props, ...options }
|
||||
|
||||
// 将show的值设置为true
|
||||
show.value = true
|
||||
await nextTick()
|
||||
initDrag(
|
||||
Array.prototype.at.call(document.querySelectorAll('.modal-header'), -1),
|
||||
Array.prototype.at.call(document.querySelectorAll('.modal-box'), -1)
|
||||
)
|
||||
}
|
||||
|
||||
// 定义一个close函数,用于关闭模态框
|
||||
@@ -149,6 +155,14 @@ async function handleCancel(data) {
|
||||
}
|
||||
}
|
||||
|
||||
async function onAfterLeave() {
|
||||
await nextTick()
|
||||
initDrag(
|
||||
Array.prototype.at.call(document.querySelectorAll('.modal-header'), -1),
|
||||
Array.prototype.at.call(document.querySelectorAll('.modal-box'), -1)
|
||||
)
|
||||
}
|
||||
|
||||
const okLoading = computed({
|
||||
get() {
|
||||
return !!modalOptions.value?.okLoading
|
||||
|
||||
94
src/components/me/modal/utils.js
Normal file
94
src/components/me/modal/utils.js
Normal file
@@ -0,0 +1,94 @@
|
||||
/**********************************
|
||||
* @Author: Ronnie Zhang
|
||||
* @LastEditor: Ronnie Zhang
|
||||
* @LastEditTime: 2024/01/13 17:41:26
|
||||
* @Email: zclzone@outlook.com
|
||||
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
||||
**********************************/
|
||||
|
||||
// 获取元素的CSS样式
|
||||
function getCss(element, key) {
|
||||
return element.currentStyle
|
||||
? element.currentStyle[key]
|
||||
: window.getComputedStyle(element, null)[key]
|
||||
}
|
||||
|
||||
const params = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
currentX: 0,
|
||||
currentY: 0,
|
||||
flag: false,
|
||||
}
|
||||
|
||||
// 初始化拖拽
|
||||
export function initDrag(bar, box) {
|
||||
if (!bar || !box) return
|
||||
const screenWidth = document.body.clientWidth // 页面宽度
|
||||
const screenHeight = document.documentElement.clientHeight // 页面可见区域高度
|
||||
|
||||
const dragDomWidth = box.offsetWidth // 盒子宽度
|
||||
const dragDomHeight = box.offsetHeight // 盒子高度
|
||||
|
||||
const minDomLeft = box.offsetLeft // 盒子相对于父元素的左偏移量
|
||||
const minDomTop = box.offsetTop // 盒子相对于父元素的上偏移量
|
||||
|
||||
const maxDragDomLeft = screenWidth - minDomLeft - dragDomWidth // 盒子在水平方向上可拖拽的最大距离
|
||||
const maxDragDomTop = screenHeight - minDomTop - dragDomHeight // 盒子在垂直方向上可拖拽的最大距离
|
||||
|
||||
if (getCss(box, 'left') !== 'auto') {
|
||||
params.left = getCss(box, 'left')
|
||||
}
|
||||
if (getCss(box, 'top') !== 'auto') {
|
||||
params.top = getCss(box, 'top')
|
||||
}
|
||||
|
||||
// 设置触发拖动元素的鼠标样式为移动图标
|
||||
bar.style.cursor = 'move'
|
||||
|
||||
// 鼠标按下事件处理函数
|
||||
bar.onmousedown = function (e) {
|
||||
params.flag = true // 设置拖拽标志为true
|
||||
e.preventDefault() // 阻止默认事件
|
||||
params.currentX = e.clientX // 鼠标当前位置的X坐标
|
||||
params.currentY = e.clientY // 鼠标当前位置的Y坐标
|
||||
}
|
||||
document.onmouseup = function () {
|
||||
params.flag = false // 设置拖拽标志为false
|
||||
if (getCss(box, 'left') !== 'auto') {
|
||||
params.left = getCss(box, 'left')
|
||||
}
|
||||
if (getCss(box, 'top') !== 'auto') {
|
||||
params.top = getCss(box, 'top')
|
||||
}
|
||||
}
|
||||
document.onmousemove = function (e) {
|
||||
e.preventDefault() // 阻止默认事件
|
||||
// 如果拖拽标志为true
|
||||
if (params.flag) {
|
||||
const nowX = e.clientX // 鼠标当前位置的X坐标
|
||||
const nowY = e.clientY // 鼠标当前位置的Y坐标
|
||||
const disX = nowX - params.currentX // 鼠标移动的X距离
|
||||
const disY = nowY - params.currentY // 鼠标移动的Y距离
|
||||
|
||||
let left = parseInt(params.left) + disX // 盒子元素的新left值
|
||||
let top = parseInt(params.top) + disY // 盒子元素的新top值
|
||||
|
||||
// 拖出屏幕边缘
|
||||
if (-left > minDomLeft) {
|
||||
left = -minDomLeft
|
||||
} else if (left > maxDragDomLeft) {
|
||||
left = maxDragDomLeft
|
||||
}
|
||||
|
||||
if (-top > minDomTop) {
|
||||
top = -minDomTop
|
||||
} else if (top > maxDragDomTop) {
|
||||
top = maxDragDomTop
|
||||
}
|
||||
|
||||
box.style.left = left + 'px'
|
||||
box.style.top = top + 'px'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,8 @@ function open(options) {
|
||||
async function setCurrentRole() {
|
||||
try {
|
||||
okLoading.value = true
|
||||
await userStore.switchCurrentRole(roleCode.value)
|
||||
const { data } = await api.switchCurrentRole(roleCode.value)
|
||||
await authStore.switchCurrentRole(data)
|
||||
okLoading.value = false
|
||||
$message.success('切换成功')
|
||||
modalRef.value?.handleOk()
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
|
||||
<template>
|
||||
<router-link class="h-60 f-c-c" to="/">
|
||||
<img src="@/assets/images/logo.png" class="h-40" />
|
||||
<!-- <img src="@/assets/images/logo.png" class="h-40" /> -->
|
||||
<TheLogo class="rounded-8!" />
|
||||
<h2
|
||||
v-show="!appStore.collapsed"
|
||||
class="ml-10 max-w-140 flex-shrink-0 text-16 color-primary font-bold"
|
||||
|
||||
@@ -39,8 +39,19 @@ watch(route, async () => {
|
||||
})
|
||||
|
||||
function handleMenuSelect(key, item) {
|
||||
if (isExternal(item.path)) {
|
||||
window.open(item.path)
|
||||
if (isExternal(item.originPath)) {
|
||||
$dialog.confirm({
|
||||
type: 'info',
|
||||
title: `请选择打开方式`,
|
||||
positiveText: '外链打开',
|
||||
negativeText: '在本站内嵌打开',
|
||||
confirm() {
|
||||
window.open(item.originPath)
|
||||
},
|
||||
cancel: () => {
|
||||
router.push(item.path)
|
||||
},
|
||||
})
|
||||
} else {
|
||||
router.push(item.path)
|
||||
}
|
||||
|
||||
40
src/router/basic-routes.js
Normal file
40
src/router/basic-routes.js
Normal file
@@ -0,0 +1,40 @@
|
||||
export const basicRoutes = [
|
||||
{
|
||||
name: 'Login',
|
||||
path: '/login',
|
||||
component: () => import('@/views/login/index.vue'),
|
||||
meta: {
|
||||
title: '登录页',
|
||||
layout: 'empty',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Home',
|
||||
path: '/',
|
||||
component: () => import('@/views/home/index.vue'),
|
||||
meta: {
|
||||
title: '首页',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: '404',
|
||||
path: '/404',
|
||||
component: () => import('@/views/error-page/404.vue'),
|
||||
meta: {
|
||||
title: '页面飞走了',
|
||||
layout: 'empty',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: '403',
|
||||
path: '/403',
|
||||
component: () => import('@/views/error-page/403.vue'),
|
||||
meta: {
|
||||
title: '没有权限',
|
||||
layout: 'empty',
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -9,7 +9,7 @@
|
||||
import { useAuthStore } from '@/store'
|
||||
import api from '@/api'
|
||||
|
||||
const WHITE_LIST = ['/login', '/404', '/role-select']
|
||||
const WHITE_LIST = ['/login', '/404']
|
||||
export function createPermissionGuard(router) {
|
||||
router.beforeEach(async (to) => {
|
||||
const authStore = useAuthStore()
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import { useTabStore } from '@/store'
|
||||
|
||||
export const EXCLUDE_TAB = ['/404', '/403', '/login', '/role-select']
|
||||
export const EXCLUDE_TAB = ['/404', '/403', '/login']
|
||||
|
||||
export function createTabGuard(router) {
|
||||
router.afterEach((to) => {
|
||||
|
||||
@@ -9,51 +9,14 @@
|
||||
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
|
||||
import { setupRouterGuards } from './guards'
|
||||
import { useAuthStore, usePermissionStore, useUserStore } from '@/store'
|
||||
|
||||
export const basicRoutes = [
|
||||
{
|
||||
name: 'Login',
|
||||
path: '/login',
|
||||
component: () => import('@/views/login/index.vue'),
|
||||
meta: {
|
||||
title: '登录页',
|
||||
layout: 'empty',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Home',
|
||||
path: '/',
|
||||
component: () => import('@/views/home/index.vue'),
|
||||
meta: {
|
||||
title: '首页',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: '404',
|
||||
path: '/404',
|
||||
component: () => import('@/views/error-page/404.vue'),
|
||||
meta: {
|
||||
title: '页面飞走了',
|
||||
layout: 'empty',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: '403',
|
||||
path: '/403',
|
||||
component: () => import('@/views/error-page/403.vue'),
|
||||
meta: {
|
||||
title: '没有权限',
|
||||
layout: 'empty',
|
||||
},
|
||||
},
|
||||
]
|
||||
import { getPermissions, getUserInfo } from '@/store/helper'
|
||||
import { basicRoutes } from './basic-routes'
|
||||
|
||||
export const router = createRouter({
|
||||
history:
|
||||
import.meta.env.VITE_USE_HASH === 'true' ? createWebHashHistory('/') : createWebHistory('/'),
|
||||
import.meta.env.VITE_USE_HASH === 'true'
|
||||
? createWebHashHistory(import.meta.env.VITE_PUBLIC_PATH || '/')
|
||||
: createWebHistory(import.meta.env.VITE_PUBLIC_PATH || '/'),
|
||||
routes: basicRoutes,
|
||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||
})
|
||||
@@ -74,32 +37,21 @@ export async function initUserAndPermissions() {
|
||||
const authStore = useAuthStore()
|
||||
|
||||
if (!authStore.accessToken) {
|
||||
authStore.toLogin()
|
||||
const route = unref(router.currentRoute)
|
||||
if (route.path !== '/login') {
|
||||
router.replace({
|
||||
path: '/login',
|
||||
query: route.query,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
await Promise.all([userStore.getUserInfo(), permissionStore.initPermissions()])
|
||||
const [user, permissions] = await Promise.all([getUserInfo(), getPermissions()])
|
||||
userStore.setUser(user)
|
||||
permissionStore.setPermissions(permissions)
|
||||
const routeComponents = import.meta.glob('@/views/**/*.vue')
|
||||
permissionStore.accessRoutes.forEach((route) => {
|
||||
route.component = routeComponents[route.component] || undefined
|
||||
!router.hasRoute(route.name) && router.addRoute(route)
|
||||
})
|
||||
}
|
||||
|
||||
export async function resetRouter() {
|
||||
const basicRouteNames = getRouteNames(basicRoutes)
|
||||
router.getRoutes().forEach((route) => {
|
||||
const name = route.name
|
||||
if (!basicRouteNames.includes(name)) {
|
||||
router.removeRoute(name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function getRouteNames(routes) {
|
||||
const names = []
|
||||
for (const route of routes) {
|
||||
names.push(route.name)
|
||||
if (route.children?.length) {
|
||||
names.push(...getRouteNames(route.children))
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
@@ -40,20 +40,40 @@ export const naiveThemeOverrides = {
|
||||
export const basePermissions = [
|
||||
{
|
||||
code: 'ExternalLink',
|
||||
name: '外链',
|
||||
name: '外链(可内嵌打开)',
|
||||
type: 'MENU',
|
||||
icon: 'i-fe:external-link',
|
||||
order: 98,
|
||||
enable: true,
|
||||
show: true,
|
||||
children: [
|
||||
{
|
||||
code: 'ShowDocs',
|
||||
name: '项目文档',
|
||||
type: 'MENU',
|
||||
path: 'https://docs.isme.top/web/#/624306705/188522224',
|
||||
icon: 'i-me:docs',
|
||||
order: 1,
|
||||
enable: true,
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
code: 'ApiFoxDocs',
|
||||
name: '接口文档',
|
||||
type: 'MENU',
|
||||
path: 'https://apifox.com/apidoc/shared-ff4a4d32-c0d1-4caf-b0ee-6abc130f734a',
|
||||
icon: 'i-me:apifox',
|
||||
order: 2,
|
||||
enable: true,
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
code: 'MyBlog',
|
||||
name: '博客-掘金',
|
||||
type: 'MENU',
|
||||
path: 'https://juejin.cn/user/1961184475483255',
|
||||
path: 'https://juejin.cn/user/1961184475483255/posts',
|
||||
icon: 'i-simple-icons:juejin',
|
||||
order: 1,
|
||||
order: 3,
|
||||
enable: true,
|
||||
show: true,
|
||||
},
|
||||
|
||||
29
src/store/helper.js
Normal file
29
src/store/helper.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { basePermissions } from '@/settings'
|
||||
import api from '@/api'
|
||||
|
||||
export async function getUserInfo() {
|
||||
const res = await api.getUser()
|
||||
const { id, username, profile, roles, currentRole } = res.data || {}
|
||||
return {
|
||||
id,
|
||||
username,
|
||||
avatar: profile?.avatar,
|
||||
nickName: profile?.nickName,
|
||||
gender: profile?.gender,
|
||||
address: profile?.address,
|
||||
email: profile?.email,
|
||||
roles,
|
||||
currentRole,
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPermissions() {
|
||||
let asyncPermissions = []
|
||||
try {
|
||||
const res = await api.getRolePermissions()
|
||||
asyncPermissions = res?.data || []
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
return basePermissions.concat(asyncPermissions)
|
||||
}
|
||||
@@ -33,6 +33,6 @@ export const useAppStore = defineStore('app', {
|
||||
},
|
||||
persist: {
|
||||
paths: ['collapsed', 'naiveThemeOverrides'],
|
||||
storage: localStorage,
|
||||
storage: sessionStorage,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
**********************************/
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import { useUserStore, usePermissionStore, useTabStore } from '@/store'
|
||||
import { resetRouter, router } from '@/router'
|
||||
import { useUserStore, usePermissionStore, useTabStore, useRouterStore } from '@/store'
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
state: () => ({
|
||||
@@ -22,24 +21,30 @@ export const useAuthStore = defineStore('auth', {
|
||||
this.$reset()
|
||||
},
|
||||
toLogin() {
|
||||
const currentRoute = unref(router.currentRoute)
|
||||
const { router, route } = useRouterStore()
|
||||
router.replace({
|
||||
path: '/login',
|
||||
query: currentRoute.query,
|
||||
query: route.query,
|
||||
})
|
||||
},
|
||||
async switchCurrentRole(data) {
|
||||
this.resetLoginState()
|
||||
await nextTick()
|
||||
this.setToken(data)
|
||||
},
|
||||
resetLoginState() {
|
||||
const { resetUser } = useUserStore()
|
||||
const { resetPermission } = usePermissionStore()
|
||||
const { resetRouter } = useRouterStore()
|
||||
const { resetPermission, accessRoutes } = usePermissionStore()
|
||||
const { resetTabs } = useTabStore()
|
||||
// 重置路由
|
||||
resetRouter(accessRoutes)
|
||||
// 重置用户
|
||||
resetUser()
|
||||
// 重置权限
|
||||
resetPermission()
|
||||
// 重置Tabs
|
||||
resetTabs()
|
||||
// 重置路由
|
||||
resetRouter()
|
||||
// 重置token
|
||||
this.resetToken()
|
||||
},
|
||||
|
||||
@@ -3,3 +3,4 @@ export * from './auth'
|
||||
export * from './permission'
|
||||
export * from './tab'
|
||||
export * from './user'
|
||||
export * from './router'
|
||||
|
||||
@@ -6,28 +6,19 @@
|
||||
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
||||
**********************************/
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import { isExternal } from '@/utils'
|
||||
import { basePermissions } from '@/settings'
|
||||
import api from '@/api'
|
||||
|
||||
const routeComponents = import.meta.glob('@/views/**/*.vue')
|
||||
import { hyphenate } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const usePermissionStore = defineStore('permission', {
|
||||
state: () => ({
|
||||
menus: [],
|
||||
accessRoutes: [],
|
||||
asyncPermissions: [],
|
||||
permissions: [],
|
||||
menus: [],
|
||||
}),
|
||||
getters: {
|
||||
permissions() {
|
||||
return basePermissions.concat(this.asyncPermissions)
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async initPermissions() {
|
||||
const { data } = (await api.getRolePermissions()) || []
|
||||
this.asyncPermissions = data
|
||||
setPermissions(permissions) {
|
||||
this.permissions = permissions
|
||||
this.menus = this.permissions
|
||||
.filter((item) => item.type === 'MENU')
|
||||
.map((item) => this.getMenuItem(item))
|
||||
@@ -36,12 +27,13 @@ export const usePermissionStore = defineStore('permission', {
|
||||
},
|
||||
getMenuItem(item, parent) {
|
||||
const route = this.generateRoute(item, item.show ? null : parent?.key)
|
||||
if (item.enable && route.path && !isExternal(route.path)) this.accessRoutes.push(route)
|
||||
if (item.enable && route.path && !route.path.startsWith('http')) this.accessRoutes.push(route)
|
||||
if (!item.show) return null
|
||||
const menuItem = {
|
||||
label: route.meta.title,
|
||||
key: route.name,
|
||||
path: route.path,
|
||||
originPath: route.meta.originPath,
|
||||
icon: () => h('i', { class: `${route.meta.icon}?mask text-16` }),
|
||||
order: item.order ?? 0,
|
||||
}
|
||||
@@ -56,12 +48,19 @@ export const usePermissionStore = defineStore('permission', {
|
||||
return menuItem
|
||||
},
|
||||
generateRoute(item, parentKey) {
|
||||
let originPath = undefined
|
||||
if (isExternal(item.path)) {
|
||||
originPath = item.path
|
||||
item.component = '/src/views/iframe/index.vue'
|
||||
item.path = `/iframe/${hyphenate(item.code)}`
|
||||
}
|
||||
return {
|
||||
name: item.code,
|
||||
path: item.path,
|
||||
redirect: item.redirect,
|
||||
component: routeComponents[item.component] || undefined,
|
||||
component: item.component,
|
||||
meta: {
|
||||
originPath,
|
||||
icon: item.icon,
|
||||
title: item.name,
|
||||
layout: item.layout,
|
||||
|
||||
26
src/store/modules/router.js
Normal file
26
src/store/modules/router.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/**********************************
|
||||
* @Author: Ronnie Zhang
|
||||
* @LastEditor: Ronnie Zhang
|
||||
* @LastEditTime: 2024/01/06 17:18:40
|
||||
* @Email: zclzone@outlook.com
|
||||
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
||||
**********************************/
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useRouterStore = defineStore('router', () => {
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
function resetRouter(accessRoutes) {
|
||||
accessRoutes.forEach((item) => {
|
||||
router.hasRoute(item.name) && router.removeRoute(item.name)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
router,
|
||||
route,
|
||||
resetRouter,
|
||||
}
|
||||
})
|
||||
@@ -7,7 +7,7 @@
|
||||
**********************************/
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import { router } from '@/router'
|
||||
import { useRouterStore } from './router'
|
||||
|
||||
export const useTabStore = defineStore('tab', {
|
||||
state: () => ({
|
||||
@@ -55,13 +55,13 @@ export const useTabStore = defineStore('tab', {
|
||||
async removeTab(path) {
|
||||
this.setTabs(this.tabs.filter((tab) => tab.path !== path))
|
||||
if (path === this.activeTab) {
|
||||
router.push(this.tabs[this.tabs.length - 1].path)
|
||||
useRouterStore().router?.push(this.tabs[this.tabs.length - 1].path)
|
||||
}
|
||||
},
|
||||
removeOther(curPath = this.activeTab) {
|
||||
this.setTabs(this.tabs.filter((tab) => tab.path === curPath))
|
||||
if (curPath !== this.activeTab) {
|
||||
router.push(this.tabs[this.tabs.length - 1].path)
|
||||
useRouterStore().router?.push(this.tabs[this.tabs.length - 1].path)
|
||||
}
|
||||
},
|
||||
removeLeft(curPath) {
|
||||
@@ -69,7 +69,7 @@ export const useTabStore = defineStore('tab', {
|
||||
const filterTabs = this.tabs.filter((item, index) => index >= curIndex)
|
||||
this.setTabs(filterTabs)
|
||||
if (!filterTabs.find((item) => item.path === this.activeTab)) {
|
||||
router.push(filterTabs[filterTabs.length - 1].path)
|
||||
useRouterStore().router?.push(filterTabs[filterTabs.length - 1].path)
|
||||
}
|
||||
},
|
||||
removeRight(curPath) {
|
||||
@@ -77,7 +77,7 @@ export const useTabStore = defineStore('tab', {
|
||||
const filterTabs = this.tabs.filter((item, index) => index <= curIndex)
|
||||
this.setTabs(filterTabs)
|
||||
if (!filterTabs.find((item) => item.path === this.activeTab.value)) {
|
||||
router.push(filterTabs[filterTabs.length - 1].path)
|
||||
useRouterStore().router?.push(filterTabs[filterTabs.length - 1].path)
|
||||
}
|
||||
},
|
||||
resetTabs() {
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
**********************************/
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import api from '@/api'
|
||||
import { useAuthStore } from '@/store'
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
@@ -35,32 +33,8 @@ export const useUserStore = defineStore('user', {
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async getUserInfo() {
|
||||
try {
|
||||
const res = await api.getUser()
|
||||
const { id, username, profile, roles, currentRole } = res.data || {}
|
||||
this.userInfo = {
|
||||
id,
|
||||
username,
|
||||
avatar: profile?.avatar,
|
||||
nickName: profile?.nickName,
|
||||
gender: profile?.gender,
|
||||
address: profile?.address,
|
||||
email: profile?.email,
|
||||
roles,
|
||||
currentRole,
|
||||
}
|
||||
return Promise.resolve(res.data)
|
||||
} catch (error) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
},
|
||||
async switchCurrentRole(roleCode) {
|
||||
const { data } = await api.switchCurrentRole(roleCode)
|
||||
const authStore = useAuthStore()
|
||||
authStore.resetLoginState()
|
||||
await nextTick()
|
||||
authStore.setToken(data)
|
||||
setUser(user) {
|
||||
this.userInfo = user
|
||||
},
|
||||
resetUser() {
|
||||
this.$reset()
|
||||
|
||||
@@ -41,3 +41,9 @@ textarea {
|
||||
border: none;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
img,
|
||||
video {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export function setupInterceptors(axiosInstance) {
|
||||
const message = resolveResError(code, data?.message ?? statusText)
|
||||
|
||||
//需要错误提醒
|
||||
!config.noNeedTip && window.$message?.error(message)
|
||||
!config?.noNeedTip && message && window.$message?.error(message)
|
||||
return Promise.reject({ code, message, error: data ?? response })
|
||||
}
|
||||
return Promise.resolve(data ?? response)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import * as NaiveUI from 'naive-ui'
|
||||
import { isNullOrUndef } from '@/utils'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useAppStore } from '@/store'
|
||||
|
||||
export function setupMessage(NMessage) {
|
||||
class Message {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!--------------------------------
|
||||
- @Author: Ronnie Zhang
|
||||
- @LastEditor: Ronnie Zhang
|
||||
- @LastEditTime: 2023/12/16 18:51:56
|
||||
- @LastEditTime: 2024/01/13 17:41:47
|
||||
- @Email: zclzone@outlook.com
|
||||
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
||||
--------------------------------->
|
||||
@@ -55,7 +55,7 @@ function openModal2() {
|
||||
$modal2.value?.open({
|
||||
cancelText: '关闭当前',
|
||||
okText: '关闭所有弹窗',
|
||||
modalStyle: { width: '320px', padding: '12px', top: '100px' },
|
||||
width: '400px',
|
||||
async onOk() {
|
||||
okLoading2.value = true
|
||||
$message.loading('正在关闭...', { key: 'modal2' })
|
||||
|
||||
9
src/views/iframe/index.vue
Normal file
9
src/views/iframe/index.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<AppPage full>
|
||||
<iframe :src="route.meta.originPath" frameborder="0" class="wh-full"></iframe>
|
||||
</AppPage>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const route = useRoute()
|
||||
</script>
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<div class="w-320 flex-col px-20 py-32">
|
||||
<h2 class="f-c-c text-24 text-#6a6a6a font-normal">
|
||||
<img src="@/assets/images/logo.png" height="50" class="mr-12" />
|
||||
<img src="@/assets/images/logo.png" class="mr-12 h-50" />
|
||||
{{ title }}
|
||||
</h2>
|
||||
<n-input
|
||||
@@ -104,42 +104,30 @@
|
||||
import { throttle, lStorage } from '@/utils'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import api from './api'
|
||||
import { useUserStore, useAuthStore } from '@/store'
|
||||
import { useAuthStore } from '@/store'
|
||||
import { initUserAndPermissions } from '@/router'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const authStore = useAuthStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const title = import.meta.env.VITE_TITLE
|
||||
|
||||
const isLogined = computed(() => {
|
||||
return authStore.accessToken && userStore.roles
|
||||
})
|
||||
|
||||
const loginInfo = ref({
|
||||
username: '',
|
||||
password: '',
|
||||
})
|
||||
function initLoginInfo() {
|
||||
const localLoginInfo = lStorage.get('loginInfo')
|
||||
if (localLoginInfo) {
|
||||
loginInfo.value.username = localLoginInfo.username || ''
|
||||
loginInfo.value.password = localLoginInfo.password || ''
|
||||
}
|
||||
}
|
||||
|
||||
const captchaUrl = ref('')
|
||||
const initCaptcha = throttle(() => {
|
||||
captchaUrl.value = '/api/auth/captcha?' + Date.now()
|
||||
}, 500)
|
||||
|
||||
if (isLogined.value) {
|
||||
router.push({ path: '/role-select', query: route.query })
|
||||
} else {
|
||||
initLoginInfo()
|
||||
initCaptcha()
|
||||
const localLoginInfo = lStorage.get('loginInfo')
|
||||
if (localLoginInfo) {
|
||||
loginInfo.value.username = localLoginInfo.username || ''
|
||||
loginInfo.value.password = localLoginInfo.password || ''
|
||||
}
|
||||
initCaptcha()
|
||||
|
||||
function quickLogin() {
|
||||
loginInfo.value.username = 'admin'
|
||||
|
||||
@@ -106,10 +106,14 @@ function handleDelete(item) {
|
||||
$dialog.confirm({
|
||||
content: `确认删除【${item.name}】?`,
|
||||
async confirm() {
|
||||
$message.loading('正在删除', { key: 'deleteMenu' })
|
||||
await api.deletePermission(item.id)
|
||||
$message.success('删除成功', { key: 'deleteMenu' })
|
||||
emit('refresh')
|
||||
try {
|
||||
$message.loading('正在删除', { key: 'deleteMenu' })
|
||||
await api.deletePermission(item.id)
|
||||
$message.success('删除成功', { key: 'deleteMenu' })
|
||||
emit('refresh')
|
||||
} catch (error) {
|
||||
$message.destroy('deleteMenu')
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,7 +38,21 @@
|
||||
</template>
|
||||
<n-input v-model:value="modalForm.code" />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="12" path="path">
|
||||
<n-form-item-gi
|
||||
:span="12"
|
||||
path="path"
|
||||
:rule="{
|
||||
trigger: ['blur', 'change'],
|
||||
type: 'string',
|
||||
message: '必须是/、http、https开头',
|
||||
validator(rule, value) {
|
||||
if (value) {
|
||||
return /\/|http|https/.test(value)
|
||||
}
|
||||
return true
|
||||
},
|
||||
}"
|
||||
>
|
||||
<template #label>
|
||||
<QuestionLabel label="路由地址" content="父级菜单可不填" />
|
||||
</template>
|
||||
|
||||
@@ -23,13 +23,7 @@
|
||||
:get-data="api.read"
|
||||
>
|
||||
<MeQueryItem label="角色名" :label-width="50">
|
||||
<n-input
|
||||
v-model:value="queryItems.name"
|
||||
type="text"
|
||||
placeholder="请输入角色名"
|
||||
clearable
|
||||
@keydown.enter="() => $table?.handleSearch()"
|
||||
/>
|
||||
<n-input v-model:value="queryItems.name" type="text" placeholder="请输入角色名" clearable />
|
||||
</MeQueryItem>
|
||||
<MeQueryItem label="状态" :label-width="50">
|
||||
<n-select
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
type="text"
|
||||
placeholder="请输入用户名"
|
||||
clearable
|
||||
@keydown.enter="() => $table?.handleSearch"
|
||||
/>
|
||||
</MeQueryItem>
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
type="text"
|
||||
placeholder="请输入用户名"
|
||||
clearable
|
||||
@keydown.enter="() => $table?.handleSearch"
|
||||
/>
|
||||
</MeQueryItem>
|
||||
|
||||
|
||||
@@ -100,9 +100,10 @@
|
||||
import { MeModal } from '@/components'
|
||||
import { useForm, useModal } from '@/composables'
|
||||
import { useUserStore } from '@/store'
|
||||
import { getUserInfo } from '@/store/helper'
|
||||
import api from './api'
|
||||
const userStore = useUserStore()
|
||||
|
||||
const userStore = useUserStore()
|
||||
const required = {
|
||||
required: true,
|
||||
message: '此为必填项',
|
||||
@@ -116,7 +117,7 @@ async function handlePwdSave() {
|
||||
await pwdValidation()
|
||||
await api.changePassword(pwdForm.value)
|
||||
$message.success('密码修改成功')
|
||||
userStore.getUserInfo()
|
||||
refreshUserInfo()
|
||||
}
|
||||
|
||||
const newAvatar = ref(userStore.avatar)
|
||||
@@ -128,7 +129,7 @@ async function handleAvatarSave() {
|
||||
}
|
||||
await api.updateProfile({ id: userStore.userId, avatar: newAvatar.value })
|
||||
$message.success('头像修改成功')
|
||||
userStore.getUserInfo()
|
||||
refreshUserInfo()
|
||||
}
|
||||
|
||||
const genders = [
|
||||
@@ -148,6 +149,11 @@ async function handleProfileSave() {
|
||||
await profileValidation()
|
||||
await api.updateProfile(profileForm.value)
|
||||
$message.success('资料修改成功')
|
||||
userStore.getUserInfo()
|
||||
refreshUserInfo()
|
||||
}
|
||||
|
||||
async function refreshUserInfo() {
|
||||
const user = await getUserInfo()
|
||||
userStore.setUser(user)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -14,7 +14,6 @@ import AutoImport from 'unplugin-auto-import/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
|
||||
import simpleHtmlPlugin from 'vite-plugin-simple-html'
|
||||
import VueDevTools from 'vite-plugin-vue-devtools'
|
||||
import { pluginPagePathes, pluginIcons } from './build/plugin-isme'
|
||||
|
||||
export default defineConfig(({ command, mode }) => {
|
||||
@@ -25,7 +24,6 @@ export default defineConfig(({ command, mode }) => {
|
||||
return {
|
||||
base: VITE_PUBLIC_PATH || '/',
|
||||
plugins: [
|
||||
VueDevTools(),
|
||||
Vue(),
|
||||
Unocss(),
|
||||
AutoImport({
|
||||
@@ -64,6 +62,7 @@ export default defineConfig(({ command, mode }) => {
|
||||
target: VITE_PROXY_TARGET,
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(new RegExp('^/api'), ''),
|
||||
secure: false,
|
||||
configure: (proxy, options) => {
|
||||
// 配置此项可在响应头中看到请求的真实地址
|
||||
proxy.on('proxyRes', (proxyRes, req) => {
|
||||
|
||||
Reference in New Issue
Block a user