feat: 添加路由权限
自动部署 / build (push) Successful in 1m26s
Details
自动部署 / build (push) Successful in 1m26s
Details
parent
4adfe49747
commit
34b3a73f30
2
.env
2
.env
|
|
@ -5,7 +5,7 @@
|
||||||
VITE_TITLE = 绝弹项目管理
|
VITE_TITLE = 绝弹项目管理
|
||||||
# 网站副标题
|
# 网站副标题
|
||||||
VITE_SUBTITLE = 快速开发web应用的模板工具
|
VITE_SUBTITLE = 快速开发web应用的模板工具
|
||||||
# 部署路径
|
# 部署路径: 当为 ./ 时路由模式需为 hash
|
||||||
VITE_BASE = /
|
VITE_BASE = /
|
||||||
# 接口前缀:参见 axios 的 baseURL
|
# 接口前缀:参见 axios 的 baseURL
|
||||||
VITE_API = /
|
VITE_API = /
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
- 图标/样式一个类名搞定
|
- 图标/样式一个类名搞定
|
||||||
- 遵循 Conventional Changelog 规范, 自动生成版本记录文档
|
- 遵循 Conventional Changelog 规范, 自动生成版本记录文档
|
||||||
- 内置常用 VsCode 代码片段和推荐扩展,提升开发效率
|
- 内置常用 VsCode 代码片段和推荐扩展,提升开发效率
|
||||||
|
- 支持路由动态打包、路由权限、路由缓存和动态首页
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
|
|
|
||||||
29
src/App.vue
29
src/App.vue
|
|
@ -1,13 +1,36 @@
|
||||||
<template>
|
<template>
|
||||||
<a-config-provider>
|
<a-config-provider>
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component, route }">
|
||||||
<page-403 v-if="Math.random() > 0.999"></page-403>
|
<keep-alive :include="menuStore.cacheTopNames">
|
||||||
<component v-else :is="Component"></component>
|
<component v-if="hasAuth(route, Component)" :is="Component"></component>
|
||||||
|
<page-403 v-else></page-403>
|
||||||
|
</keep-alive>
|
||||||
</router-view>
|
</router-view>
|
||||||
</a-config-provider>
|
</a-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { RouteLocationNormalizedLoaded } from "vue-router";
|
||||||
|
import { useUserStore } from "./store";
|
||||||
|
import { useMenuStore } from "./store/menu";
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const menuStore = useMenuStore();
|
||||||
|
|
||||||
|
const hasAuth = (route: RouteLocationNormalizedLoaded, c: any) => {
|
||||||
|
const aAuth = route.meta.auth;
|
||||||
|
const uAuth = userStore.auth;
|
||||||
|
if (!aAuth?.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (aAuth.some((i) => i === "*")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (uAuth.some((i) => aAuth.some((j) => j === i))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { has, isString } from "lodash-es";
|
||||||
const successCodes = [2000];
|
const successCodes = [2000];
|
||||||
const expiredCodes = [4050, 4051];
|
const expiredCodes = [4050, 4051];
|
||||||
const resMessageTip = `响应异常,请检查参数或稍后重试!`;
|
const resMessageTip = `响应异常,请检查参数或稍后重试!`;
|
||||||
|
const resGetMessage = `数据获取失败,请检查网络或稍后重试!`;
|
||||||
const reqMessageTip = `请求失败,请检查网络或稍后重试!`;
|
const reqMessageTip = `请求失败,请检查网络或稍后重试!`;
|
||||||
|
|
||||||
let logoutTipShowing = false;
|
let logoutTipShowing = false;
|
||||||
|
|
@ -49,6 +50,9 @@ export function addExceptionInterceptor(axios: AxiosInstance, exipreHandler?: (.
|
||||||
}
|
}
|
||||||
const resMsg = error.response?.data?.message;
|
const resMsg = error.response?.data?.message;
|
||||||
let message: string | null = resMsg ?? resMessageTip;
|
let message: string | null = resMsg ?? resMessageTip;
|
||||||
|
if (error.config?.method === "get") {
|
||||||
|
message = resGetMessage;
|
||||||
|
}
|
||||||
if (has(error.config, "resErrorTip")) {
|
if (has(error.config, "resErrorTip")) {
|
||||||
const tip = error.config.resErrorTip;
|
const tip = error.config.resErrorTip;
|
||||||
if (tip) {
|
if (tip) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
<template>
|
||||||
|
<div class="h-full flex items-center">
|
||||||
|
<a-empty>
|
||||||
|
<template #image>
|
||||||
|
<svg
|
||||||
|
height="104"
|
||||||
|
node-id="1"
|
||||||
|
template-height="104"
|
||||||
|
template-width="122"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 122 104"
|
||||||
|
width="122"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<defs node-id="20"></defs>
|
||||||
|
<g node-id="22">
|
||||||
|
<g node-id="23">
|
||||||
|
<g node-id="24">
|
||||||
|
<g node-id="25">
|
||||||
|
<g node-id="27">
|
||||||
|
<g node-id="29">
|
||||||
|
<path
|
||||||
|
d="M 15.00 82.30 L 14.43 82.07 L 14.20 81.50 L 14.43 80.93 L 15.00 80.70 L 85.00 80.70 L 85.57 80.93 L 85.80 81.50 L 85.57 82.07 L 85.00 82.30 L 15.00 82.30 Z M 89.00 82.30 L 88.43 82.07 L 88.20 81.50 L 88.43 80.93 L 89.00 80.70 L 91.50 80.70 L 92.07 80.93 L 92.30 81.50 L 92.07 82.07 L 91.50 82.30 L 89.00 82.30 Z M 98.00 82.30 L 97.43 82.07 L 97.20 81.50 L 97.43 80.93 L 98.00 80.70 L 107.00 80.70 L 107.57 80.93 L 107.80 81.50 L 107.57 82.07 L 107.00 82.30 L 98.00 82.30 Z M 38.00 89.80 L 37.43 89.57 L 37.20 89.00 L 37.43 88.43 L 38.00 88.20 L 45.00 88.20 L 45.57 88.43 L 45.80 89.00 L 45.57 89.57 L 45.00 89.80 L 38.00 89.80 Z M 49.50 89.80 L 48.93 89.57 L 48.70 89.00 L 48.93 88.43 L 49.50 88.20 L 80.00 88.20 L 80.57 88.43 L 80.80 89.00 L 80.57 89.57 L 80.00 89.80 L 49.50 89.80 Z M 94.20 62.00 L 94.46 61.39 L 95.00 61.20 L 95.54 61.39 L 95.80 62.00 L 95.80 65.00 L 95.57 65.57 L 95.00 65.80 L 92.00 65.80 L 91.39 65.54 L 91.20 65.00 L 91.39 64.46 L 92.00 64.20 L 94.20 64.20 L 94.20 62.00 Z M 95.80 68.00 L 95.54 68.61 L 95.00 68.80 L 94.46 68.61 L 94.20 68.00 L 94.20 65.00 L 94.43 64.43 L 95.00 64.20 L 98.00 64.20 L 98.61 64.46 L 98.80 65.00 L 98.61 65.54 L 98.00 65.80 L 95.80 65.80 L 95.80 68.00 Z M 18.20 38.00 L 18.46 37.39 L 19.00 37.20 L 19.54 37.39 L 19.80 38.00 L 19.80 41.00 L 19.57 41.57 L 19.00 41.80 L 16.00 41.80 L 15.39 41.54 L 15.20 41.00 L 15.39 40.46 L 16.00 40.20 L 18.20 40.20 L 18.20 38.00 Z M 92.30 12.70 L 95.00 12.70 L 95.61 12.96 L 95.80 13.50 L 95.61 14.04 L 95.00 14.30 L 92.30 14.30 L 92.30 17.00 L 92.04 17.61 L 91.50 17.80 L 90.96 17.61 L 90.70 17.00 L 90.70 14.30 L 88.00 14.30 L 87.39 14.04 L 87.20 13.50 L 87.39 12.96 L 88.00 12.70 L 90.70 12.70 L 90.70 10.00 L 90.96 9.39 L 91.50 9.20 L 92.04 9.39 L 92.30 10.00 L 92.30 12.70 Z M 19.80 44.00 L 19.54 44.61 L 19.00 44.80 L 18.46 44.61 L 18.20 44.00 L 18.20 41.00 L 18.43 40.43 L 19.00 40.20 L 22.00 40.20 L 22.61 40.46 L 22.80 41.00 L 22.61 41.54 L 22.00 41.80 L 19.80 41.80 L 19.80 44.00 Z"
|
||||||
|
fill="#c3cbd6"
|
||||||
|
fill-rule="nonzero"
|
||||||
|
group-id="1,2,3,4,6,8"
|
||||||
|
id="Path-2"
|
||||||
|
node-id="13"
|
||||||
|
stroke="none"
|
||||||
|
target-height="80.6"
|
||||||
|
target-width="93.6"
|
||||||
|
target-x="14.2"
|
||||||
|
target-y="9.2"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M 28.29 70.34 L 28.68 70.19 L 29.00 70.34 L 29.15 70.67 L 29.00 71.05 L 27.94 72.11 L 27.59 72.26 L 27.23 72.11 L 26.17 71.05 L 26.02 70.67 L 26.17 70.34 L 26.50 70.19 L 26.88 70.34 L 27.59 71.05 L 28.29 70.34 Z M 26.88 73.17 L 26.50 73.33 L 26.17 73.17 L 26.02 72.85 L 26.17 72.46 L 27.23 71.40 L 27.59 71.26 L 27.94 71.40 L 29.00 72.46 L 29.15 72.85 L 29.00 73.17 L 28.68 73.33 L 28.29 73.17 L 27.59 72.46 L 26.88 73.17 Z M 37.12 18.00 L 37.50 17.85 L 37.83 18.00 L 37.98 18.32 L 37.83 18.71 L 36.77 19.77 L 36.41 19.91 L 36.06 19.77 L 35.00 18.71 L 34.85 18.32 L 35.00 18.00 L 35.32 17.85 L 35.71 18.00 L 36.41 18.71 L 37.12 18.00 Z M 35.71 20.83 L 35.32 20.98 L 35.00 20.83 L 34.85 20.50 L 35.00 20.12 L 36.06 19.06 L 36.41 18.91 L 36.77 19.06 L 37.83 20.12 L 37.98 20.50 L 37.83 20.83 L 37.50 20.98 L 37.12 20.83 L 36.41 20.12 L 35.71 20.83 Z"
|
||||||
|
fill="#c3cbd6"
|
||||||
|
fill-rule="nonzero"
|
||||||
|
group-id="1,2,3,4,6,8"
|
||||||
|
id="Path复制"
|
||||||
|
node-id="14"
|
||||||
|
stroke="none"
|
||||||
|
target-height="55.480774"
|
||||||
|
target-width="11.966061"
|
||||||
|
target-x="26.016972"
|
||||||
|
target-y="17.845398"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g node-id="26">
|
||||||
|
<path
|
||||||
|
d="M 45.00 35.00 L 77.00 35.00 L 78.18 35.24 L 79.12 35.88 L 79.76 36.82 L 80.00 38.00 L 80.00 71.00 L 79.76 72.18 L 79.12 73.12 L 78.18 73.76 L 77.00 74.00 L 45.00 74.00 L 43.82 73.76 L 42.88 73.12 L 42.24 72.18 L 42.00 71.00 L 42.00 38.00 L 42.24 36.82 L 42.88 35.88 L 43.82 35.24 L 45.00 35.00 Z"
|
||||||
|
fill="#ffffff"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
group-id="1,2,3,5"
|
||||||
|
id="矩形"
|
||||||
|
node-id="16"
|
||||||
|
stroke="#c3cbd6"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-width="1.6"
|
||||||
|
target-height="39"
|
||||||
|
target-width="38"
|
||||||
|
target-x="42"
|
||||||
|
target-y="35"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M 57.00 33.00 L 57.64 32.85 L 58.16 32.48 L 59.05 31.52 L 59.50 31.14 L 60.00 31.00 L 62.00 31.00 L 62.51 31.14 L 62.99 31.52 L 63.91 32.48 L 64.42 32.85 L 65.00 33.00 L 68.00 33.00 L 68.78 33.16 L 69.41 33.59 L 69.84 34.22 L 70.00 35.00 L 70.00 36.00 L 69.84 36.78 L 69.41 37.41 L 68.78 37.84 L 68.00 38.00 L 54.00 38.00 L 53.22 37.84 L 52.59 37.41 L 52.16 36.78 L 52.00 36.00 L 52.00 35.00 L 52.16 34.22 L 52.59 33.59 L 53.22 33.16 L 54.00 33.00 L 57.00 33.00 Z"
|
||||||
|
fill="#f5f7f9"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
group-id="1,2,3,5"
|
||||||
|
id="路径"
|
||||||
|
node-id="17"
|
||||||
|
stroke="#c3cbd6"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-width="1.6"
|
||||||
|
target-height="7"
|
||||||
|
target-width="18"
|
||||||
|
target-x="52"
|
||||||
|
target-y="31"
|
||||||
|
/>
|
||||||
|
<g node-id="28">
|
||||||
|
<path
|
||||||
|
d="M 50.83 52.09 L 54.72 55.13 L 50.83 52.09 Z M 60.61 48.15 L 60.63 53.16 L 60.61 48.15 Z M 70.41 51.75 L 66.50 54.95 L 70.41 51.75 Z"
|
||||||
|
fill="none"
|
||||||
|
group-id="1,2,3,5,7"
|
||||||
|
id="路径-7"
|
||||||
|
node-id="18"
|
||||||
|
stroke="#c3cad7"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="2"
|
||||||
|
target-height="6.9805374"
|
||||||
|
target-width="19.58184"
|
||||||
|
target-x="50.827675"
|
||||||
|
target-y="48.147778"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
</a-empty>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
<template>
|
|
||||||
<a-empty>
|
|
||||||
<template #image>
|
|
||||||
<svg
|
|
||||||
height="104"
|
|
||||||
node-id="1"
|
|
||||||
template-height="104"
|
|
||||||
template-width="122"
|
|
||||||
version="1.1"
|
|
||||||
viewBox="0 0 122 104"
|
|
||||||
width="122"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<defs node-id="20"></defs>
|
|
||||||
<g node-id="22">
|
|
||||||
<g node-id="23">
|
|
||||||
<g node-id="24">
|
|
||||||
<g node-id="25">
|
|
||||||
<g node-id="27">
|
|
||||||
<g node-id="29">
|
|
||||||
<path
|
|
||||||
d="M 15.00 82.30 L 14.43 82.07 L 14.20 81.50 L 14.43 80.93 L 15.00 80.70 L 85.00 80.70 L 85.57 80.93 L 85.80 81.50 L 85.57 82.07 L 85.00 82.30 L 15.00 82.30 Z M 89.00 82.30 L 88.43 82.07 L 88.20 81.50 L 88.43 80.93 L 89.00 80.70 L 91.50 80.70 L 92.07 80.93 L 92.30 81.50 L 92.07 82.07 L 91.50 82.30 L 89.00 82.30 Z M 98.00 82.30 L 97.43 82.07 L 97.20 81.50 L 97.43 80.93 L 98.00 80.70 L 107.00 80.70 L 107.57 80.93 L 107.80 81.50 L 107.57 82.07 L 107.00 82.30 L 98.00 82.30 Z M 38.00 89.80 L 37.43 89.57 L 37.20 89.00 L 37.43 88.43 L 38.00 88.20 L 45.00 88.20 L 45.57 88.43 L 45.80 89.00 L 45.57 89.57 L 45.00 89.80 L 38.00 89.80 Z M 49.50 89.80 L 48.93 89.57 L 48.70 89.00 L 48.93 88.43 L 49.50 88.20 L 80.00 88.20 L 80.57 88.43 L 80.80 89.00 L 80.57 89.57 L 80.00 89.80 L 49.50 89.80 Z M 94.20 62.00 L 94.46 61.39 L 95.00 61.20 L 95.54 61.39 L 95.80 62.00 L 95.80 65.00 L 95.57 65.57 L 95.00 65.80 L 92.00 65.80 L 91.39 65.54 L 91.20 65.00 L 91.39 64.46 L 92.00 64.20 L 94.20 64.20 L 94.20 62.00 Z M 95.80 68.00 L 95.54 68.61 L 95.00 68.80 L 94.46 68.61 L 94.20 68.00 L 94.20 65.00 L 94.43 64.43 L 95.00 64.20 L 98.00 64.20 L 98.61 64.46 L 98.80 65.00 L 98.61 65.54 L 98.00 65.80 L 95.80 65.80 L 95.80 68.00 Z M 18.20 38.00 L 18.46 37.39 L 19.00 37.20 L 19.54 37.39 L 19.80 38.00 L 19.80 41.00 L 19.57 41.57 L 19.00 41.80 L 16.00 41.80 L 15.39 41.54 L 15.20 41.00 L 15.39 40.46 L 16.00 40.20 L 18.20 40.20 L 18.20 38.00 Z M 92.30 12.70 L 95.00 12.70 L 95.61 12.96 L 95.80 13.50 L 95.61 14.04 L 95.00 14.30 L 92.30 14.30 L 92.30 17.00 L 92.04 17.61 L 91.50 17.80 L 90.96 17.61 L 90.70 17.00 L 90.70 14.30 L 88.00 14.30 L 87.39 14.04 L 87.20 13.50 L 87.39 12.96 L 88.00 12.70 L 90.70 12.70 L 90.70 10.00 L 90.96 9.39 L 91.50 9.20 L 92.04 9.39 L 92.30 10.00 L 92.30 12.70 Z M 19.80 44.00 L 19.54 44.61 L 19.00 44.80 L 18.46 44.61 L 18.20 44.00 L 18.20 41.00 L 18.43 40.43 L 19.00 40.20 L 22.00 40.20 L 22.61 40.46 L 22.80 41.00 L 22.61 41.54 L 22.00 41.80 L 19.80 41.80 L 19.80 44.00 Z"
|
|
||||||
fill="#c3cbd6"
|
|
||||||
fill-rule="nonzero"
|
|
||||||
group-id="1,2,3,4,6,8"
|
|
||||||
id="Path-2"
|
|
||||||
node-id="13"
|
|
||||||
stroke="none"
|
|
||||||
target-height="80.6"
|
|
||||||
target-width="93.6"
|
|
||||||
target-x="14.2"
|
|
||||||
target-y="9.2"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M 28.29 70.34 L 28.68 70.19 L 29.00 70.34 L 29.15 70.67 L 29.00 71.05 L 27.94 72.11 L 27.59 72.26 L 27.23 72.11 L 26.17 71.05 L 26.02 70.67 L 26.17 70.34 L 26.50 70.19 L 26.88 70.34 L 27.59 71.05 L 28.29 70.34 Z M 26.88 73.17 L 26.50 73.33 L 26.17 73.17 L 26.02 72.85 L 26.17 72.46 L 27.23 71.40 L 27.59 71.26 L 27.94 71.40 L 29.00 72.46 L 29.15 72.85 L 29.00 73.17 L 28.68 73.33 L 28.29 73.17 L 27.59 72.46 L 26.88 73.17 Z M 37.12 18.00 L 37.50 17.85 L 37.83 18.00 L 37.98 18.32 L 37.83 18.71 L 36.77 19.77 L 36.41 19.91 L 36.06 19.77 L 35.00 18.71 L 34.85 18.32 L 35.00 18.00 L 35.32 17.85 L 35.71 18.00 L 36.41 18.71 L 37.12 18.00 Z M 35.71 20.83 L 35.32 20.98 L 35.00 20.83 L 34.85 20.50 L 35.00 20.12 L 36.06 19.06 L 36.41 18.91 L 36.77 19.06 L 37.83 20.12 L 37.98 20.50 L 37.83 20.83 L 37.50 20.98 L 37.12 20.83 L 36.41 20.12 L 35.71 20.83 Z"
|
|
||||||
fill="#c3cbd6"
|
|
||||||
fill-rule="nonzero"
|
|
||||||
group-id="1,2,3,4,6,8"
|
|
||||||
id="Path复制"
|
|
||||||
node-id="14"
|
|
||||||
stroke="none"
|
|
||||||
target-height="55.480774"
|
|
||||||
target-width="11.966061"
|
|
||||||
target-x="26.016972"
|
|
||||||
target-y="17.845398"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g node-id="26">
|
|
||||||
<path
|
|
||||||
d="M 45.00 35.00 L 77.00 35.00 L 78.18 35.24 L 79.12 35.88 L 79.76 36.82 L 80.00 38.00 L 80.00 71.00 L 79.76 72.18 L 79.12 73.12 L 78.18 73.76 L 77.00 74.00 L 45.00 74.00 L 43.82 73.76 L 42.88 73.12 L 42.24 72.18 L 42.00 71.00 L 42.00 38.00 L 42.24 36.82 L 42.88 35.88 L 43.82 35.24 L 45.00 35.00 Z"
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-rule="evenodd"
|
|
||||||
group-id="1,2,3,5"
|
|
||||||
id="矩形"
|
|
||||||
node-id="16"
|
|
||||||
stroke="#c3cbd6"
|
|
||||||
stroke-linecap="butt"
|
|
||||||
stroke-width="1.6"
|
|
||||||
target-height="39"
|
|
||||||
target-width="38"
|
|
||||||
target-x="42"
|
|
||||||
target-y="35"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M 57.00 33.00 L 57.64 32.85 L 58.16 32.48 L 59.05 31.52 L 59.50 31.14 L 60.00 31.00 L 62.00 31.00 L 62.51 31.14 L 62.99 31.52 L 63.91 32.48 L 64.42 32.85 L 65.00 33.00 L 68.00 33.00 L 68.78 33.16 L 69.41 33.59 L 69.84 34.22 L 70.00 35.00 L 70.00 36.00 L 69.84 36.78 L 69.41 37.41 L 68.78 37.84 L 68.00 38.00 L 54.00 38.00 L 53.22 37.84 L 52.59 37.41 L 52.16 36.78 L 52.00 36.00 L 52.00 35.00 L 52.16 34.22 L 52.59 33.59 L 53.22 33.16 L 54.00 33.00 L 57.00 33.00 Z"
|
|
||||||
fill="#f5f7f9"
|
|
||||||
fill-rule="evenodd"
|
|
||||||
group-id="1,2,3,5"
|
|
||||||
id="路径"
|
|
||||||
node-id="17"
|
|
||||||
stroke="#c3cbd6"
|
|
||||||
stroke-linecap="butt"
|
|
||||||
stroke-width="1.6"
|
|
||||||
target-height="7"
|
|
||||||
target-width="18"
|
|
||||||
target-x="52"
|
|
||||||
target-y="31"
|
|
||||||
/>
|
|
||||||
<g node-id="28">
|
|
||||||
<path
|
|
||||||
d="M 50.83 52.09 L 54.72 55.13 L 50.83 52.09 Z M 60.61 48.15 L 60.63 53.16 L 60.61 48.15 Z M 70.41 51.75 L 66.50 54.95 L 70.41 51.75 Z"
|
|
||||||
fill="none"
|
|
||||||
group-id="1,2,3,5,7"
|
|
||||||
id="路径-7"
|
|
||||||
node-id="18"
|
|
||||||
stroke="#c3cad7"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-width="2"
|
|
||||||
target-height="6.9805374"
|
|
||||||
target-width="19.58184"
|
|
||||||
target-x="50.827675"
|
|
||||||
target-y="48.147778"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
</a-empty>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { TableColumnData as BaseColumn, TableData as BaseData, Table as BaseTable } from "@arco-design/web-vue";
|
import { TableColumnData as BaseColumn, TableData as BaseData, Table as BaseTable } from "@arco-design/web-vue";
|
||||||
import { merge } from "lodash-es";
|
import { merge } from "lodash-es";
|
||||||
import { PropType, computed, defineComponent, reactive, ref } from "vue";
|
import { PropType, computed, defineComponent, reactive, ref } from "vue";
|
||||||
import AniEmpty from "../empty/index.vue";
|
import AniEmpty from "../empty/AniEmpty.vue";
|
||||||
import { Form, FormInstance, FormModal, FormModalInstance, FormModalProps, FormProps } from "../form";
|
import { Form, FormInstance, FormModal, FormModalInstance, FormModalProps, FormProps } from "../form";
|
||||||
import { config } from "./table.config";
|
import { config } from "./table.config";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full h-full flex justify-center items-center p-4">
|
<div class="w-full h-full flex justify-center items-center p-4">
|
||||||
<div class="flex flex-col md:flex-row items-center">
|
<div class="flex flex-col md:flex-row items-center">
|
||||||
<div v-html="Image404">
|
<div v-html="Image404"></div>
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="slide-in-bottom">
|
<div class="slide-in-bottom">
|
||||||
<h1 class="text-3xl font-bold my-0">404</h1>
|
<h1 class="text-3xl font-bold my-0">404</h1>
|
||||||
<p class="mt-2">页面不存在,请检查地址或联系管理员!</p>
|
<p class="mt-2">页面不存在,请检查地址或联系管理员!</p>
|
||||||
|
|
@ -28,7 +26,10 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import Image404 from './image-404.svg?raw';
|
import Image404 from "./image-404.svg?raw";
|
||||||
|
|
||||||
|
defineOptions({ name: "AllUncatchedPage" });
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,32 @@
|
||||||
<script lang="tsx">
|
<script lang="tsx">
|
||||||
import { MenuItem, menus } from "@/router";
|
import { MenuItem } from "@/router";
|
||||||
|
import { useMenuStore } from "@/store/menu";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "LayoutMenu",
|
name: "LayoutMenu",
|
||||||
setup() {
|
setup() {
|
||||||
const selectedKeys = ref<string[]>([]);
|
const selectedKeys = ref<string[]>([]);
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const menuStore = useMenuStore();
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.path,
|
() => route.path,
|
||||||
() => {
|
() => {
|
||||||
selectedKeys.value = route.matched.map((i) => i.aliasOf?.path ?? i.path);
|
selectedKeys.value = route.matched.map((i) => i.path);
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
return { selectedKeys };
|
function goto(route: MenuItem) {
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
goto(route: MenuItem) {
|
|
||||||
if (route.external) {
|
if (route.external) {
|
||||||
window.open(route.path, "_blank");
|
window.open(route.path, "_blank");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.$router.push(route);
|
router.push(route.path);
|
||||||
},
|
}
|
||||||
|
|
||||||
renderItem(routes: MenuItem[], isTop = false) {
|
function renderItem(routes: MenuItem[]) {
|
||||||
return routes.map((route) => {
|
return routes.map((route) => {
|
||||||
const icon = route.icon ? () => <i class={route.icon} /> : null;
|
const icon = route.icon ? () => <i class={route.icon} /> : null;
|
||||||
const node: any = route.children?.length ? (
|
const node: any = route.children?.length ? (
|
||||||
|
|
@ -34,30 +34,21 @@ export default defineComponent({
|
||||||
<div class="px-2">
|
<div class="px-2">
|
||||||
<a-divider margin={6} class="!border-slate-100"></a-divider>
|
<a-divider margin={6} class="!border-slate-100"></a-divider>
|
||||||
</div>
|
</div>
|
||||||
{this.renderItem(route?.children)}
|
{renderItem(route?.children)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<a-menu-item key={route.path} v-slots={{ icon }} onClick={() => this.goto(route)}>
|
<a-menu-item key={route.path} v-slots={{ icon }} onClick={() => goto(route)}>
|
||||||
{route.title}
|
{route.title}
|
||||||
{false && <span class="text-xs text-slate-400 ml-2">({route.sort})</span>}
|
{false && <span class="text-xs text-slate-400 ml-2">({route.sort})</span>}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
);
|
);
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
return () => (
|
||||||
return (
|
<a-menu style={{ width: "100%" }} selectedKeys={selectedKeys.value} autoOpenSelected={true} levelIndent={0}>
|
||||||
<a-menu
|
{renderItem(menuStore.menus)}
|
||||||
style={{ width: "100%" }}
|
|
||||||
breakpoint="xl"
|
|
||||||
selectedKeys={this.selectedKeys}
|
|
||||||
autoOpenSelected={true}
|
|
||||||
levelIndent={0}
|
|
||||||
>
|
|
||||||
{this.renderItem(menus, true)}
|
|
||||||
</a-menu>
|
</a-menu>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -41,13 +41,16 @@
|
||||||
:collapsible="true"
|
:collapsible="true"
|
||||||
:collapsed="isCollapsed"
|
:collapsed="isCollapsed"
|
||||||
:hide-trigger="false"
|
:hide-trigger="false"
|
||||||
@collapse="onCollapse"
|
@collapse="(val) => (isCollapsed = val)"
|
||||||
>
|
>
|
||||||
<a-scrollbar outer-class="h-full overflow-hidden" class="h-full overflow-hidden pt-1">
|
<a-scrollbar outer-class="h-full overflow-hidden" class="h-full overflow-hidden pt-1">
|
||||||
<Menu />
|
<Menu />
|
||||||
</a-scrollbar>
|
</a-scrollbar>
|
||||||
<template #trigger="{ collapsed }">
|
<template #trigger="{ collapsed }">
|
||||||
<i :class="collapsed ? `icon-park-outline-expand-left` : 'icon-park-outline-expand-right'" class="text-gray-400 text-base hover:text-gray-700"></i>
|
<i
|
||||||
|
:class="collapsed ? `icon-park-outline-expand-left` : 'icon-park-outline-expand-right'"
|
||||||
|
class="text-gray-400 text-base hover:text-gray-700"
|
||||||
|
></i>
|
||||||
</template>
|
</template>
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
<a-layout class="layout-content flex-1">
|
<a-layout class="layout-content flex-1">
|
||||||
|
|
@ -57,7 +60,9 @@
|
||||||
<IconSync></IconSync>
|
<IconSync></IconSync>
|
||||||
</template>
|
</template>
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
|
<keep-alive :include="menuStore.cacheAppNames">
|
||||||
<component :is="Component"></component>
|
<component :is="Component"></component>
|
||||||
|
</keep-alive>
|
||||||
</router-view>
|
</router-view>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
</a-layout-content>
|
</a-layout-content>
|
||||||
|
|
@ -67,23 +72,19 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useAppStore, useUserStore } from "@/store";
|
import { useAppStore } from "@/store";
|
||||||
import { Message } from "@arco-design/web-vue";
|
import { Message } from "@arco-design/web-vue";
|
||||||
import { IconSync } from "@arco-design/web-vue/es/icon";
|
import { IconSync } from "@arco-design/web-vue/es/icon";
|
||||||
import Menu from "./components/menu.vue";
|
import Menu from "./components/menu.vue";
|
||||||
import userDropdown from "./components/userDropdown.vue";
|
import userDropdown from "./components/userDropdown.vue";
|
||||||
|
import { useMenuStore } from "@/store/menu";
|
||||||
|
|
||||||
|
defineOptions({ name: "LayoutPage" });
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const userStore = useUserStore();
|
const menuStore = useMenuStore();
|
||||||
const isCollapsed = ref(false);
|
const isCollapsed = ref(false);
|
||||||
const route = useRoute();
|
|
||||||
const router = useRouter();
|
|
||||||
const themeConfig = ref({ visible: false });
|
const themeConfig = ref({ visible: false });
|
||||||
const isDev = import.meta.env.DEV;
|
|
||||||
|
|
||||||
const onCollapse = (val: boolean) => {
|
|
||||||
isCollapsed.value = val;
|
|
||||||
};
|
|
||||||
|
|
||||||
const buttons = [
|
const buttons = [
|
||||||
{
|
{
|
||||||
|
|
@ -108,34 +109,6 @@ const buttons = [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const tabButtons = [
|
|
||||||
{
|
|
||||||
icon: "icon-park-outline-refresh",
|
|
||||||
text: "刷新页面",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "icon-park-outline-full-screen",
|
|
||||||
text: "全屏显示",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "icon-park-outline-more",
|
|
||||||
text: "更多",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const tagItems = [
|
|
||||||
{
|
|
||||||
active: true,
|
|
||||||
text: "首页",
|
|
||||||
showClose: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
active: false,
|
|
||||||
text: "评论管理",
|
|
||||||
showClose: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|
@ -205,9 +178,11 @@ const tagItems = [
|
||||||
<route lang="json">
|
<route lang="json">
|
||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
|
"name": "LayoutPage",
|
||||||
"sort": 101,
|
"sort": 101,
|
||||||
"title": "概览",
|
"title": "概览",
|
||||||
"icon": "icon-park-outline-home"
|
"icon": "icon-park-outline-home",
|
||||||
|
"keepAlive": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</route>
|
</route>
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,8 @@ import { useAppStore, useUserStore } from "@/store";
|
||||||
import { FieldRule, Form, Message, Modal, Notification } from "@arco-design/web-vue";
|
import { FieldRule, Form, Message, Modal, Notification } from "@arco-design/web-vue";
|
||||||
import { reactive } from "vue";
|
import { reactive } from "vue";
|
||||||
|
|
||||||
|
defineOptions({ name: "LoginPage" });
|
||||||
|
|
||||||
const meridiem = dayjs.localeData().meridiem(dayjs().hour(), dayjs().minute());
|
const meridiem = dayjs.localeData().meridiem(dayjs().hour(), dayjs().minute());
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
@ -131,6 +133,7 @@ const onSubmitForm = async () => {
|
||||||
<route lang="json">
|
<route lang="json">
|
||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
|
"name": "LoginPage",
|
||||||
"sort": 101,
|
"sort": 101,
|
||||||
"title": "登录",
|
"title": "登录",
|
||||||
"icon": "icon-park-outline-home"
|
"icon": "icon-park-outline-home"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-[210px] h-full overflow-hidden grid grid-rows-[auto_1fr]">
|
<div class="w-[210px] h-full overflow-hidden grid grid-rows-[auto_1fr]">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<a-input-search allow-clear placeholder="文件分类" class="mb-2"></a-input-search>
|
<a-input-search allow-clear placeholder="文件分类" class="mb-2" @search="updateFileCategories"></a-input-search>
|
||||||
<a-button @click="formCtx.open">
|
<a-button @click="formCtx.open">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<i class="icon-park-outline-add"></i>
|
<i class="icon-park-outline-add"></i>
|
||||||
|
|
@ -10,7 +10,8 @@
|
||||||
<form-modal></form-modal>
|
<form-modal></form-modal>
|
||||||
</div>
|
</div>
|
||||||
<a-scrollbar outer-class="h-full overflow-hidden" class="h-full overflow-auto">
|
<a-scrollbar outer-class="h-full overflow-hidden" class="h-full overflow-auto">
|
||||||
<ul class="pl-0 mt-0">
|
<a-spin :loading="loading" class="w-full h-full">
|
||||||
|
<ul v-if="list.length" class="pl-0 mt-0">
|
||||||
<li
|
<li
|
||||||
v-for="item in list"
|
v-for="item in list"
|
||||||
:key="item.code"
|
:key="item.code"
|
||||||
|
|
@ -46,6 +47,8 @@
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<ani-empty v-else></ani-empty>
|
||||||
|
</a-spin>
|
||||||
</a-scrollbar>
|
</a-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -65,12 +68,20 @@ defineProps({
|
||||||
|
|
||||||
const emit = defineEmits(["change"]);
|
const emit = defineEmits(["change"]);
|
||||||
const list = ref<FileCategory[]>([]);
|
const list = ref<FileCategory[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
const updateFileCategories = async () => {
|
const updateFileCategories = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
const res = await api.fileCategory.getFileCategorys({ size: 0 });
|
const res = await api.fileCategory.getFileCategorys({ size: 0 });
|
||||||
list.value = res.data.data ?? [];
|
list.value = res.data.data ?? [];
|
||||||
list.value.unshift({ id: undefined, name: '全部' } as any)
|
list.value.unshift({ id: undefined, name: "全部" } as any);
|
||||||
list.value.length && emit("change", list.value[0]);
|
list.value.length && emit("change", list.value[0]);
|
||||||
|
} catch {
|
||||||
|
// nothing to do
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(updateFileCategories);
|
onMounted(updateFileCategories);
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div v-else class="h-[424px] flex items-center justify-center">
|
<div v-else class="h-[424px] flex items-center justify-center">
|
||||||
<a-empty description="选择文件后显示"></a-empty>
|
<ani-empty></ani-empty>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
<form-modal></form-modal>
|
<form-modal></form-modal>
|
||||||
</div>
|
</div>
|
||||||
<a-scrollbar outer-class="h-full overflow-hidden" class="h-full overflow-auto">
|
<a-scrollbar outer-class="h-full overflow-hidden" class="h-full overflow-auto">
|
||||||
<ul class="pl-0 mt-0">
|
<ul v-if="list.length" class="pl-0 mt-0">
|
||||||
<li
|
<li
|
||||||
v-for="item in list"
|
v-for="item in list"
|
||||||
:key="item.code"
|
:key="item.code"
|
||||||
|
|
@ -46,6 +46,7 @@
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<ani-empty v-else></ani-empty>
|
||||||
</a-scrollbar>
|
</a-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ import { DictType, api } from "@/api";
|
||||||
import { createColumn, updateColumn, useAniTable } from "@/components";
|
import { createColumn, updateColumn, useAniTable } from "@/components";
|
||||||
import aniGroup from "./components/group.vue";
|
import aniGroup from "./components/group.vue";
|
||||||
|
|
||||||
|
defineOptions({ name: "SystemDictPage" })
|
||||||
|
|
||||||
const current = ref<DictType>();
|
const current = ref<DictType>();
|
||||||
const onTypeChange = (item: DictType) => {
|
const onTypeChange = (item: DictType) => {
|
||||||
current.value = item;
|
current.value = item;
|
||||||
|
|
@ -129,6 +131,7 @@ const [dictTable, dict] = useAniTable({
|
||||||
<route lang="json">
|
<route lang="json">
|
||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
|
"name": "SystemDictPage",
|
||||||
"sort": 20010,
|
"sort": 20010,
|
||||||
"title": "字典管理",
|
"title": "字典管理",
|
||||||
"icon": "icon-park-outline-spanner"
|
"icon": "icon-park-outline-spanner"
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import { Table, useTable } from "@/components";
|
||||||
import { Editor as aniEditor } from "@/components/editor";
|
import { Editor as aniEditor } from "@/components/editor";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
defineOptions({ name: "SystemLoglPage" })
|
||||||
|
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
const table = useTable({
|
const table = useTable({
|
||||||
data: async (model, paging) => {
|
data: async (model, paging) => {
|
||||||
|
|
@ -132,6 +134,7 @@ const table = useTable({
|
||||||
<route lang="json">
|
<route lang="json">
|
||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
|
"name": "SystemLoglPage",
|
||||||
"sort": 10303,
|
"sort": 10303,
|
||||||
"title": "登陆日志",
|
"title": "登陆日志",
|
||||||
"icon": "icon-park-outline-log"
|
"icon": "icon-park-outline-log"
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ import { Table, useTable } from "@/components";
|
||||||
import { dayjs } from "@/libs/dayjs";
|
import { dayjs } from "@/libs/dayjs";
|
||||||
import { Tag } from "@arco-design/web-vue";
|
import { Tag } from "@arco-design/web-vue";
|
||||||
|
|
||||||
|
defineOptions({ name: "SystemLogoPage" })
|
||||||
|
|
||||||
const table = useTable({
|
const table = useTable({
|
||||||
data: async (model, paging) => {
|
data: async (model, paging) => {
|
||||||
return api.log.getLoginLogs({ ...model, ...paging });
|
return api.log.getLoginLogs({ ...model, ...paging });
|
||||||
|
|
@ -81,6 +83,7 @@ const table = useTable({
|
||||||
<route lang="json">
|
<route lang="json">
|
||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
|
"name": "SystemLogoPage",
|
||||||
"sort": 10304,
|
"sort": 10304,
|
||||||
"title": "操作日志",
|
"title": "操作日志",
|
||||||
"icon": "icon-park-outline-doc-detail"
|
"icon": "icon-park-outline-doc-detail"
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,9 @@ import { MenuType, MenuTypes } from "@/constants/menu";
|
||||||
import { flatMenus } from "@/router";
|
import { flatMenus } from "@/router";
|
||||||
import { listToTree } from "@/utils/listToTree";
|
import { listToTree } from "@/utils/listToTree";
|
||||||
|
|
||||||
const menuArr = flatMenus.map((i) => ({ label: i.title, value: i.id }));
|
defineOptions({ name: 'SystemMenuPage' })
|
||||||
|
|
||||||
|
const menuArr = flatMenus.map((i) => ({ label: i.title, value: i.id }));
|
||||||
const expanded = ref(false);
|
const expanded = ref(false);
|
||||||
const toggleExpand = () => {
|
const toggleExpand = () => {
|
||||||
expanded.value = !expanded.value;
|
expanded.value = !expanded.value;
|
||||||
|
|
@ -234,6 +235,7 @@ const [menuTable, menu] = useAniTable({
|
||||||
<route lang="json">
|
<route lang="json">
|
||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
|
"name": "SystemMenuPage",
|
||||||
"sort": 10302,
|
"sort": 10302,
|
||||||
"title": "菜单管理",
|
"title": "菜单管理",
|
||||||
"icon": "icon-park-outline-add-subtract"
|
"icon": "icon-park-outline-add-subtract"
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
import { createColumn, updateColumn, useAniTable } from "@/components";
|
import { createColumn, updateColumn, useAniTable } from "@/components";
|
||||||
|
|
||||||
|
defineOptions({ name: 'SystemRolePage' })
|
||||||
|
|
||||||
const [roleTable, roleCtx] = useAniTable({
|
const [roleTable, roleCtx] = useAniTable({
|
||||||
data: async () => {
|
data: async () => {
|
||||||
return api.role.getRoles();
|
return api.role.getRoles();
|
||||||
|
|
@ -126,6 +128,7 @@ const [roleTable, roleCtx] = useAniTable({
|
||||||
<route lang="json">
|
<route lang="json">
|
||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
|
"name": "SystemRolePage",
|
||||||
"sort": 10302,
|
"sort": 10302,
|
||||||
"title": "角色管理",
|
"title": "角色管理",
|
||||||
"icon": "icon-park-outline-key"
|
"icon": "icon-park-outline-key"
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { Table, createColumn, updateColumn, useTable } from "@/components";
|
||||||
import InputAvatar from "./components/avatar.vue";
|
import InputAvatar from "./components/avatar.vue";
|
||||||
import { usePassworModal } from "./components/password";
|
import { usePassworModal } from "./components/password";
|
||||||
|
|
||||||
|
defineOptions({ name: "SystemUserPage" });
|
||||||
const [passModal, passCtx] = usePassworModal();
|
const [passModal, passCtx] = usePassworModal();
|
||||||
|
|
||||||
const table = useTable({
|
const table = useTable({
|
||||||
|
|
@ -68,17 +69,6 @@ const table = useTable({
|
||||||
search: {
|
search: {
|
||||||
button: true,
|
button: true,
|
||||||
items: [
|
items: [
|
||||||
// {
|
|
||||||
// field: "nickname",
|
|
||||||
// label: "用户昵称",
|
|
||||||
// type: "input",
|
|
||||||
// nodeProps: {
|
|
||||||
// placeholder: '用户昵称'
|
|
||||||
// },
|
|
||||||
// itemProps: {
|
|
||||||
// hideLabel: true
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
field: "nickname",
|
field: "nickname",
|
||||||
label: "用户昵称",
|
label: "用户昵称",
|
||||||
|
|
@ -196,6 +186,8 @@ const table = useTable({
|
||||||
<route lang="json">
|
<route lang="json">
|
||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
|
"name": "SystemUserPage",
|
||||||
|
"keepAlive": true,
|
||||||
"sort": 10301,
|
"sort": 10301,
|
||||||
"title": "用户管理",
|
"title": "用户管理",
|
||||||
"icon": "icon-park-outline-user"
|
"icon": "icon-park-outline-user"
|
||||||
|
|
|
||||||
|
|
@ -216,7 +216,8 @@ const user = reactive({
|
||||||
"meta": {
|
"meta": {
|
||||||
"sort": 30401,
|
"sort": 30401,
|
||||||
"title": "个人设置",
|
"title": "个人设置",
|
||||||
"icon": "icon-park-outline-config"
|
"icon": "icon-park-outline-config",
|
||||||
|
"auth": ["1"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</route>
|
</route>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
import { store, useUserStore } from "@/store";
|
import { store, useUserStore } from "@/store";
|
||||||
import { useMenuStore } from "@/store/menu";
|
import { useMenuStore } from "@/store/menu";
|
||||||
import { treeFind } from "@/utils/listToTree";
|
import { treeEach, treeFilter, treeFind } from "@/utils/listToTree";
|
||||||
import { Notification } from "@arco-design/web-vue";
|
import { Notification } from "@arco-design/web-vue";
|
||||||
import { Router } from "vue-router";
|
import { Router } from "vue-router";
|
||||||
import { menus } from "../menus";
|
import { MenuItem, menus } from "../menus";
|
||||||
import { APP_HOME_NAME } from "../routes/base";
|
import { APP_HOME_NAME } from "../routes/base";
|
||||||
import { APP_ROUTE_NAME, routes } from "../routes/page";
|
import { APP_ROUTE_NAME, routes } from "../routes/page";
|
||||||
import { env } from "@/config/env";
|
import { env } from "@/config/env";
|
||||||
|
|
@ -26,34 +26,81 @@ export function useAuthGuard(router: Router) {
|
||||||
router.push({ path: "/login", query: { redirect } });
|
router.push({ path: "/login", query: { redirect } });
|
||||||
};
|
};
|
||||||
|
|
||||||
router.beforeEach(async function (to) {
|
router.beforeEach(async function (to, from) {
|
||||||
const userStore = useUserStore(store);
|
const userStore = useUserStore(store);
|
||||||
const menuStore = useMenuStore(store);
|
const menuStore = useMenuStore(store);
|
||||||
|
|
||||||
|
// 手动指定直接通过
|
||||||
if (to.meta.auth?.some((i) => i === "*")) {
|
if (to.meta.auth?.some((i) => i === "*")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 在白名单内直接通过
|
||||||
if (WHITE_LIST.includes(to.path)) {
|
if (WHITE_LIST.includes(to.path)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 未登陆才能访问的页面
|
||||||
if (UNSIGNIN_LIST.includes(to.path)) {
|
if (UNSIGNIN_LIST.includes(to.path)) {
|
||||||
|
// 未登陆则允许通过
|
||||||
if (!userStore.accessToken) {
|
if (!userStore.accessToken) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 已登陆进行提示
|
||||||
Notification.warning({
|
Notification.warning({
|
||||||
title: "跳转提示",
|
title: "跳转提示",
|
||||||
content: `您已登陆,如需重新登陆请退出后再操作!`,
|
content: `您已登陆,如需重新登陆请退出后再操作!`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 不是从路由跳转的,跳转回首页
|
||||||
|
if (!from.matched.length) {
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已登陆不允许
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 未登录跳转到登陆页面
|
||||||
if (!userStore.accessToken) {
|
if (!userStore.accessToken) {
|
||||||
return { path: "/login", query: { redirect: to.path } };
|
return { path: "/login", query: { redirect: to.path } };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 未获取菜单进行获取
|
||||||
if (!menuStore.menus.length) {
|
if (!menuStore.menus.length) {
|
||||||
menuStore.setMenus(menus);
|
// 菜单处理
|
||||||
|
const authMenus = treeFilter(menus, (item) => {
|
||||||
|
if (item.path === env.homePath) {
|
||||||
|
item.path = "/";
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
menuStore.setMenus(authMenus);
|
||||||
menuStore.setHome(env.homePath);
|
menuStore.setHome(env.homePath);
|
||||||
|
|
||||||
|
// 路由处理
|
||||||
for (const route of routes) {
|
for (const route of routes) {
|
||||||
router.addRoute(route);
|
router.addRoute(route);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 缓存处理
|
||||||
|
const topNames: string[] = [];
|
||||||
|
const appNames: string[] = [];
|
||||||
|
treeEach(routes, (item, level) => {
|
||||||
|
const { keepAlive, name } = item.meta ?? {};
|
||||||
|
if (keepAlive && name) {
|
||||||
|
if (level === 1) {
|
||||||
|
topNames.push(name);
|
||||||
|
} else {
|
||||||
|
appNames.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
menuStore.setCacheTopNames(topNames);
|
||||||
|
menuStore.setCacheAppNames(appNames);
|
||||||
|
|
||||||
|
// 首页处理
|
||||||
const home = treeFind(routes, (i) => i.path === menuStore.home);
|
const home = treeFind(routes, (i) => i.path === menuStore.home);
|
||||||
if (home) {
|
if (home) {
|
||||||
const route = { ...home, name: APP_HOME_NAME, alias: "/" };
|
const route = { ...home, name: APP_HOME_NAME, alias: "/" };
|
||||||
|
|
@ -62,6 +109,8 @@ export function useAuthGuard(router: Router) {
|
||||||
return router.replace(to.path);
|
return router.replace(to.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 兜底处理
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ export interface MenuItem {
|
||||||
title?: string;
|
title?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
external?: boolean;
|
external?: boolean;
|
||||||
|
name?: string;
|
||||||
|
keepAlive: boolean;
|
||||||
children?: MenuItem[];
|
children?: MenuItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,7 +27,7 @@ function routesToItems(routes: RouteRecordRaw[]): MenuItem[] {
|
||||||
|
|
||||||
for (const route of routes) {
|
for (const route of routes) {
|
||||||
const { meta = {}, parentMeta, path } = route as any;
|
const { meta = {}, parentMeta, path } = route as any;
|
||||||
const { title, sort, icon } = meta;
|
const { title, sort, icon, keepAlive = false, name } = meta;
|
||||||
let id = path;
|
let id = path;
|
||||||
let paths = route.path.split("/");
|
let paths = route.path.split("/");
|
||||||
let parentId = paths.slice(0, -1).join("/");
|
let parentId = paths.slice(0, -1).join("/");
|
||||||
|
|
@ -39,6 +41,7 @@ function routesToItems(routes: RouteRecordRaw[]): MenuItem[] {
|
||||||
sort,
|
sort,
|
||||||
path,
|
path,
|
||||||
id: path,
|
id: path,
|
||||||
|
keepAlive: false,
|
||||||
parentId: paths.slice(0, -1).join("/"),
|
parentId: paths.slice(0, -1).join("/"),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -47,7 +50,7 @@ function routesToItems(routes: RouteRecordRaw[]): MenuItem[] {
|
||||||
parentId = p;
|
parentId = p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items.push({ id, title, parentId, path, icon, sort });
|
items.push({ id, title, parentId, path, icon, sort, keepAlive, name });
|
||||||
}
|
}
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ export const useMenuStore = defineStore({
|
||||||
state: (): MenuStore => {
|
state: (): MenuStore => {
|
||||||
return {
|
return {
|
||||||
menus: [],
|
menus: [],
|
||||||
|
cacheAppNames: [],
|
||||||
|
cacheTopNames: [],
|
||||||
home: "",
|
home: "",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -23,7 +25,23 @@ export const useMenuStore = defineStore({
|
||||||
*/
|
*/
|
||||||
setHome(path: string) {
|
setHome(path: string) {
|
||||||
this.home = path;
|
this.home = path;
|
||||||
}
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置顶级缓存页面
|
||||||
|
* @param names 组件名字
|
||||||
|
*/
|
||||||
|
setCacheTopNames(names: string[]) {
|
||||||
|
this.cacheTopNames = names;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置应用缓存页面
|
||||||
|
* @param names 组件名字
|
||||||
|
*/
|
||||||
|
setCacheAppNames(names: string[]) {
|
||||||
|
this.cacheAppNames = names;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -32,6 +50,14 @@ export interface MenuStore {
|
||||||
* 路由列表
|
* 路由列表
|
||||||
*/
|
*/
|
||||||
menus: MenuItem[];
|
menus: MenuItem[];
|
||||||
|
/**
|
||||||
|
* KeepAlive缓存的顶级组件名字
|
||||||
|
*/
|
||||||
|
cacheTopNames: string[];
|
||||||
|
/**
|
||||||
|
* KeepAlive缓存的页面组件名字
|
||||||
|
*/
|
||||||
|
cacheAppNames: string[];
|
||||||
/**
|
/**
|
||||||
* 首页路径
|
* 首页路径
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ export const useUserStore = defineStore({
|
||||||
avatar: "https://github.com/juetan.png",
|
avatar: "https://github.com/juetan.png",
|
||||||
accessToken: "",
|
accessToken: "",
|
||||||
refreshToken: undefined,
|
refreshToken: undefined,
|
||||||
|
auth: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
|
@ -75,4 +76,8 @@ export interface UserStore {
|
||||||
* 刷新令牌
|
* 刷新令牌
|
||||||
*/
|
*/
|
||||||
refreshToken?: string;
|
refreshToken?: string;
|
||||||
|
/**
|
||||||
|
* 拥有权限
|
||||||
|
*/
|
||||||
|
auth: string[];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ declare module '@vue/runtime-core' {
|
||||||
AMenu: typeof import('@arco-design/web-vue')['Menu']
|
AMenu: typeof import('@arco-design/web-vue')['Menu']
|
||||||
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
|
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
|
||||||
AModal: typeof import('@arco-design/web-vue')['Modal']
|
AModal: typeof import('@arco-design/web-vue')['Modal']
|
||||||
|
AniEmpty: typeof import('./../components/empty/AniEmpty.vue')['default']
|
||||||
APagination: typeof import('@arco-design/web-vue')['Pagination']
|
APagination: typeof import('@arco-design/web-vue')['Pagination']
|
||||||
APopover: typeof import('@arco-design/web-vue')['Popover']
|
APopover: typeof import('@arco-design/web-vue')['Popover']
|
||||||
AProgress: typeof import('@arco-design/web-vue')['Progress']
|
AProgress: typeof import('@arco-design/web-vue')['Progress']
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,10 @@ declare module "vue-router" {
|
||||||
* 是否缓存页面
|
* 是否缓存页面
|
||||||
*/
|
*/
|
||||||
keepAlive?: boolean;
|
keepAlive?: boolean;
|
||||||
|
/**
|
||||||
|
* 组件名字(keepAlive为true时必须)
|
||||||
|
*/
|
||||||
|
name?: string;
|
||||||
/**
|
/**
|
||||||
* 是否显示loading
|
* 是否显示loading
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -24,13 +24,18 @@ export const listToTree = (list: any[], id = "id", pid = "parentId", cid = "chil
|
||||||
* @param fn 函数
|
* @param fn 函数
|
||||||
* @param before 是否广度遍历
|
* @param before 是否广度遍历
|
||||||
*/
|
*/
|
||||||
export function treeEach(tree: any[], fn: (item: any) => void, before = true) {
|
export function treeEach<T extends { children?: T[]; [key: string]: any } = any>(
|
||||||
|
tree: T[],
|
||||||
|
fn: (item: T, level: number) => void,
|
||||||
|
before = true,
|
||||||
|
level = 1
|
||||||
|
) {
|
||||||
for (const item of tree) {
|
for (const item of tree) {
|
||||||
before && fn(item);
|
before && fn(item, level);
|
||||||
if (item.children) {
|
if (item.children) {
|
||||||
treeEach(item.children, fn);
|
treeEach(item.children, fn, before, level + 1);
|
||||||
}
|
}
|
||||||
!before && fn(item);
|
!before && fn(item, level);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,3 +64,21 @@ export function treeFind<T extends { children?: T[]; [key: string]: any } = any>
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过滤树结构
|
||||||
|
* @param tree 树结构
|
||||||
|
* @param fn 函数
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function treeFilter<T extends { children?: T[]; [key: string]: any } = any>(
|
||||||
|
tree: T[],
|
||||||
|
fn: (item: T) => boolean
|
||||||
|
) {
|
||||||
|
return tree.filter((item) => {
|
||||||
|
if (item.children) {
|
||||||
|
item.children = treeFilter(item.children, fn);
|
||||||
|
}
|
||||||
|
return fn(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue