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_SUBTITLE = 快速开发web应用的模板工具
|
||||
# 部署路径
|
||||
# 部署路径: 当为 ./ 时路由模式需为 hash
|
||||
VITE_BASE = /
|
||||
# 接口前缀:参见 axios 的 baseURL
|
||||
VITE_API = /
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
- 图标/样式一个类名搞定
|
||||
- 遵循 Conventional Changelog 规范, 自动生成版本记录文档
|
||||
- 内置常用 VsCode 代码片段和推荐扩展,提升开发效率
|
||||
- 支持路由动态打包、路由权限、路由缓存和动态首页
|
||||
|
||||
## 快速开始
|
||||
|
||||
|
|
|
|||
29
src/App.vue
29
src/App.vue
|
|
@ -1,13 +1,36 @@
|
|||
<template>
|
||||
<a-config-provider>
|
||||
<router-view v-slot="{ Component }">
|
||||
<page-403 v-if="Math.random() > 0.999"></page-403>
|
||||
<component v-else :is="Component"></component>
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<keep-alive :include="menuStore.cacheTopNames">
|
||||
<component v-if="hasAuth(route, Component)" :is="Component"></component>
|
||||
<page-403 v-else></page-403>
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
</a-config-provider>
|
||||
</template>
|
||||
|
||||
<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>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { has, isString } from "lodash-es";
|
|||
const successCodes = [2000];
|
||||
const expiredCodes = [4050, 4051];
|
||||
const resMessageTip = `响应异常,请检查参数或稍后重试!`;
|
||||
const resGetMessage = `数据获取失败,请检查网络或稍后重试!`;
|
||||
const reqMessageTip = `请求失败,请检查网络或稍后重试!`;
|
||||
|
||||
let logoutTipShowing = false;
|
||||
|
|
@ -49,6 +50,9 @@ export function addExceptionInterceptor(axios: AxiosInstance, exipreHandler?: (.
|
|||
}
|
||||
const resMsg = error.response?.data?.message;
|
||||
let message: string | null = resMsg ?? resMessageTip;
|
||||
if (error.config?.method === "get") {
|
||||
message = resGetMessage;
|
||||
}
|
||||
if (has(error.config, "resErrorTip")) {
|
||||
const tip = error.config.resErrorTip;
|
||||
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 { merge } from "lodash-es";
|
||||
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 { config } from "./table.config";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<template>
|
||||
<div class="w-full h-full flex justify-center items-center p-4">
|
||||
<div class="flex flex-col md:flex-row items-center">
|
||||
<div v-html="Image404">
|
||||
|
||||
</div>
|
||||
<div v-html="Image404"></div>
|
||||
<div class="slide-in-bottom">
|
||||
<h1 class="text-3xl font-bold my-0">404</h1>
|
||||
<p class="mt-2">页面不存在,请检查地址或联系管理员!</p>
|
||||
|
|
@ -28,7 +26,10 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
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();
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,32 +1,32 @@
|
|||
<script lang="tsx">
|
||||
import { MenuItem, menus } from "@/router";
|
||||
import { MenuItem } from "@/router";
|
||||
import { useMenuStore } from "@/store/menu";
|
||||
|
||||
export default defineComponent({
|
||||
name: "LayoutMenu",
|
||||
setup() {
|
||||
const selectedKeys = ref<string[]>([]);
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const menuStore = useMenuStore();
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
selectedKeys.value = route.matched.map((i) => i.aliasOf?.path ?? i.path);
|
||||
selectedKeys.value = route.matched.map((i) => i.path);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
return { selectedKeys };
|
||||
},
|
||||
methods: {
|
||||
goto(route: MenuItem) {
|
||||
function goto(route: MenuItem) {
|
||||
if (route.external) {
|
||||
window.open(route.path, "_blank");
|
||||
return;
|
||||
}
|
||||
this.$router.push(route);
|
||||
},
|
||||
router.push(route.path);
|
||||
}
|
||||
|
||||
renderItem(routes: MenuItem[], isTop = false) {
|
||||
function renderItem(routes: MenuItem[]) {
|
||||
return routes.map((route) => {
|
||||
const icon = route.icon ? () => <i class={route.icon} /> : null;
|
||||
const node: any = route.children?.length ? (
|
||||
|
|
@ -34,30 +34,21 @@ export default defineComponent({
|
|||
<div class="px-2">
|
||||
<a-divider margin={6} class="!border-slate-100"></a-divider>
|
||||
</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}
|
||||
{false && <span class="text-xs text-slate-400 ml-2">({route.sort})</span>}
|
||||
</a-menu-item>
|
||||
);
|
||||
|
||||
return node;
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<a-menu
|
||||
style={{ width: "100%" }}
|
||||
breakpoint="xl"
|
||||
selectedKeys={this.selectedKeys}
|
||||
autoOpenSelected={true}
|
||||
levelIndent={0}
|
||||
>
|
||||
{this.renderItem(menus, true)}
|
||||
return () => (
|
||||
<a-menu style={{ width: "100%" }} selectedKeys={selectedKeys.value} autoOpenSelected={true} levelIndent={0}>
|
||||
{renderItem(menuStore.menus)}
|
||||
</a-menu>
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -41,13 +41,16 @@
|
|||
:collapsible="true"
|
||||
:collapsed="isCollapsed"
|
||||
:hide-trigger="false"
|
||||
@collapse="onCollapse"
|
||||
@collapse="(val) => (isCollapsed = val)"
|
||||
>
|
||||
<a-scrollbar outer-class="h-full overflow-hidden" class="h-full overflow-hidden pt-1">
|
||||
<Menu />
|
||||
</a-scrollbar>
|
||||
<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>
|
||||
</a-layout-sider>
|
||||
<a-layout class="layout-content flex-1">
|
||||
|
|
@ -57,7 +60,9 @@
|
|||
<IconSync></IconSync>
|
||||
</template>
|
||||
<router-view v-slot="{ Component }">
|
||||
<component :is="Component"></component>
|
||||
<keep-alive :include="menuStore.cacheAppNames">
|
||||
<component :is="Component"></component>
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
</a-spin>
|
||||
</a-layout-content>
|
||||
|
|
@ -67,23 +72,19 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useAppStore, useUserStore } from "@/store";
|
||||
import { useAppStore } from "@/store";
|
||||
import { Message } from "@arco-design/web-vue";
|
||||
import { IconSync } from "@arco-design/web-vue/es/icon";
|
||||
import Menu from "./components/menu.vue";
|
||||
import userDropdown from "./components/userDropdown.vue";
|
||||
import { useMenuStore } from "@/store/menu";
|
||||
|
||||
defineOptions({ name: "LayoutPage" });
|
||||
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
const menuStore = useMenuStore();
|
||||
const isCollapsed = ref(false);
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const themeConfig = ref({ visible: false });
|
||||
const isDev = import.meta.env.DEV;
|
||||
|
||||
const onCollapse = (val: boolean) => {
|
||||
isCollapsed.value = val;
|
||||
};
|
||||
|
||||
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>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
|
@ -205,9 +178,11 @@ const tagItems = [
|
|||
<route lang="json">
|
||||
{
|
||||
"meta": {
|
||||
"name": "LayoutPage",
|
||||
"sort": 101,
|
||||
"title": "概览",
|
||||
"icon": "icon-park-outline-home"
|
||||
"icon": "icon-park-outline-home",
|
||||
"keepAlive": true
|
||||
}
|
||||
}
|
||||
</route>
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ import { useAppStore, useUserStore } from "@/store";
|
|||
import { FieldRule, Form, Message, Modal, Notification } from "@arco-design/web-vue";
|
||||
import { reactive } from "vue";
|
||||
|
||||
defineOptions({ name: "LoginPage" });
|
||||
|
||||
const meridiem = dayjs.localeData().meridiem(dayjs().hour(), dayjs().minute());
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
|
|
@ -131,6 +133,7 @@ const onSubmitForm = async () => {
|
|||
<route lang="json">
|
||||
{
|
||||
"meta": {
|
||||
"name": "LoginPage",
|
||||
"sort": 101,
|
||||
"title": "登录",
|
||||
"icon": "icon-park-outline-home"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="w-[210px] h-full overflow-hidden grid grid-rows-[auto_1fr]">
|
||||
<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">
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-add"></i>
|
||||
|
|
@ -10,42 +10,45 @@
|
|||
<form-modal></form-modal>
|
||||
</div>
|
||||
<a-scrollbar outer-class="h-full overflow-hidden" class="h-full overflow-auto">
|
||||
<ul class="pl-0 mt-0">
|
||||
<li
|
||||
v-for="item in list"
|
||||
:key="item.code"
|
||||
:class="{ active: item.id === current?.id }"
|
||||
class="group flex items-center justify-between gap-1 h-8 rounded mb-2 pl-3 hover:bg-gray-100 cursor-pointer"
|
||||
>
|
||||
<div class="flex-1 h-full flex items-center gap-2 overflow-hidden" @click="emit('change', item)">
|
||||
<i class="icon-park-outline-folder-close align-[-2px]"></i>
|
||||
<span class="flex-1 truncate">{{ item.name }}</span>
|
||||
</div>
|
||||
<div class="">
|
||||
<a-dropdown>
|
||||
<a-button size="small" type="text">
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-more-one text-gray-400 hover:text-gray-700"></i>
|
||||
<a-spin :loading="loading" class="w-full h-full">
|
||||
<ul v-if="list.length" class="pl-0 mt-0">
|
||||
<li
|
||||
v-for="item in list"
|
||||
:key="item.code"
|
||||
:class="{ active: item.id === current?.id }"
|
||||
class="group flex items-center justify-between gap-1 h-8 rounded mb-2 pl-3 hover:bg-gray-100 cursor-pointer"
|
||||
>
|
||||
<div class="flex-1 h-full flex items-center gap-2 overflow-hidden" @click="emit('change', item)">
|
||||
<i class="icon-park-outline-folder-close align-[-2px]"></i>
|
||||
<span class="flex-1 truncate">{{ item.name }}</span>
|
||||
</div>
|
||||
<div class="">
|
||||
<a-dropdown>
|
||||
<a-button size="small" type="text">
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-more-one text-gray-400 hover:text-gray-700"></i>
|
||||
</template>
|
||||
</a-button>
|
||||
<template #content>
|
||||
<a-doption @click="formCtx.open(item)">
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-edit"></i>
|
||||
</template>
|
||||
修改
|
||||
</a-doption>
|
||||
<a-doption class="!text-red-500" @click="onDeleteRow(item)">
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-delete"></i>
|
||||
</template>
|
||||
删除
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-button>
|
||||
<template #content>
|
||||
<a-doption @click="formCtx.open(item)">
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-edit"></i>
|
||||
</template>
|
||||
修改
|
||||
</a-doption>
|
||||
<a-doption class="!text-red-500" @click="onDeleteRow(item)">
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-delete"></i>
|
||||
</template>
|
||||
删除
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ani-empty v-else></ani-empty>
|
||||
</a-spin>
|
||||
</a-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -65,12 +68,20 @@ defineProps({
|
|||
|
||||
const emit = defineEmits(["change"]);
|
||||
const list = ref<FileCategory[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
const updateFileCategories = async () => {
|
||||
const res = await api.fileCategory.getFileCategorys({ size: 0 });
|
||||
list.value = res.data.data ?? [];
|
||||
list.value.unshift({ id: undefined, name: '全部' } as any)
|
||||
list.value.length && emit("change", list.value[0]);
|
||||
try {
|
||||
loading.value = true;
|
||||
const res = await api.fileCategory.getFileCategorys({ size: 0 });
|
||||
list.value = res.data.data ?? [];
|
||||
list.value.unshift({ id: undefined, name: "全部" } as any);
|
||||
list.value.length && emit("change", list.value[0]);
|
||||
} catch {
|
||||
// nothing to do
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(updateFileCategories);
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
</span>
|
||||
<span v-else-if="item.status === 'done'" class="text-green-600">
|
||||
完成(
|
||||
耗时:{{ fileMap.get(item.uid)?.cost || 0 }}秒,
|
||||
耗时:{{ fileMap.get(item.uid)?.cost || 0 }}秒,
|
||||
平均:{{ numeral(fileMap.get(item.uid)?.aspeed || 0).format("0 b") }}/s)
|
||||
</span>
|
||||
<span v-else="item.status === 'error'" class="text-red-500">
|
||||
|
|
@ -77,7 +77,7 @@
|
|||
</ul>
|
||||
|
||||
<div v-else class="h-[424px] flex items-center justify-center">
|
||||
<a-empty description="选择文件后显示"></a-empty>
|
||||
<ani-empty></ani-empty>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
<form-modal></form-modal>
|
||||
</div>
|
||||
<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
|
||||
v-for="item in list"
|
||||
:key="item.code"
|
||||
|
|
@ -46,6 +46,7 @@
|
|||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ani-empty v-else></ani-empty>
|
||||
</a-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import { DictType, api } from "@/api";
|
|||
import { createColumn, updateColumn, useAniTable } from "@/components";
|
||||
import aniGroup from "./components/group.vue";
|
||||
|
||||
defineOptions({ name: "SystemDictPage" })
|
||||
|
||||
const current = ref<DictType>();
|
||||
const onTypeChange = (item: DictType) => {
|
||||
current.value = item;
|
||||
|
|
@ -129,6 +131,7 @@ const [dictTable, dict] = useAniTable({
|
|||
<route lang="json">
|
||||
{
|
||||
"meta": {
|
||||
"name": "SystemDictPage",
|
||||
"sort": 20010,
|
||||
"title": "字典管理",
|
||||
"icon": "icon-park-outline-spanner"
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import { Table, useTable } from "@/components";
|
|||
import { Editor as aniEditor } from "@/components/editor";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
defineOptions({ name: "SystemLoglPage" })
|
||||
|
||||
const visible = ref(false);
|
||||
const table = useTable({
|
||||
data: async (model, paging) => {
|
||||
|
|
@ -132,6 +134,7 @@ const table = useTable({
|
|||
<route lang="json">
|
||||
{
|
||||
"meta": {
|
||||
"name": "SystemLoglPage",
|
||||
"sort": 10303,
|
||||
"title": "登陆日志",
|
||||
"icon": "icon-park-outline-log"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import { Table, useTable } from "@/components";
|
|||
import { dayjs } from "@/libs/dayjs";
|
||||
import { Tag } from "@arco-design/web-vue";
|
||||
|
||||
defineOptions({ name: "SystemLogoPage" })
|
||||
|
||||
const table = useTable({
|
||||
data: async (model, paging) => {
|
||||
return api.log.getLoginLogs({ ...model, ...paging });
|
||||
|
|
@ -81,6 +83,7 @@ const table = useTable({
|
|||
<route lang="json">
|
||||
{
|
||||
"meta": {
|
||||
"name": "SystemLogoPage",
|
||||
"sort": 10304,
|
||||
"title": "操作日志",
|
||||
"icon": "icon-park-outline-doc-detail"
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ import { MenuType, MenuTypes } from "@/constants/menu";
|
|||
import { flatMenus } from "@/router";
|
||||
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 toggleExpand = () => {
|
||||
expanded.value = !expanded.value;
|
||||
|
|
@ -234,6 +235,7 @@ const [menuTable, menu] = useAniTable({
|
|||
<route lang="json">
|
||||
{
|
||||
"meta": {
|
||||
"name": "SystemMenuPage",
|
||||
"sort": 10302,
|
||||
"title": "菜单管理",
|
||||
"icon": "icon-park-outline-add-subtract"
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
import { api } from "@/api";
|
||||
import { createColumn, updateColumn, useAniTable } from "@/components";
|
||||
|
||||
defineOptions({ name: 'SystemRolePage' })
|
||||
|
||||
const [roleTable, roleCtx] = useAniTable({
|
||||
data: async () => {
|
||||
return api.role.getRoles();
|
||||
|
|
@ -126,6 +128,7 @@ const [roleTable, roleCtx] = useAniTable({
|
|||
<route lang="json">
|
||||
{
|
||||
"meta": {
|
||||
"name": "SystemRolePage",
|
||||
"sort": 10302,
|
||||
"title": "角色管理",
|
||||
"icon": "icon-park-outline-key"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { Table, createColumn, updateColumn, useTable } from "@/components";
|
|||
import InputAvatar from "./components/avatar.vue";
|
||||
import { usePassworModal } from "./components/password";
|
||||
|
||||
defineOptions({ name: "SystemUserPage" });
|
||||
const [passModal, passCtx] = usePassworModal();
|
||||
|
||||
const table = useTable({
|
||||
|
|
@ -68,17 +69,6 @@ const table = useTable({
|
|||
search: {
|
||||
button: true,
|
||||
items: [
|
||||
// {
|
||||
// field: "nickname",
|
||||
// label: "用户昵称",
|
||||
// type: "input",
|
||||
// nodeProps: {
|
||||
// placeholder: '用户昵称'
|
||||
// },
|
||||
// itemProps: {
|
||||
// hideLabel: true
|
||||
// }
|
||||
// },
|
||||
{
|
||||
field: "nickname",
|
||||
label: "用户昵称",
|
||||
|
|
@ -196,6 +186,8 @@ const table = useTable({
|
|||
<route lang="json">
|
||||
{
|
||||
"meta": {
|
||||
"name": "SystemUserPage",
|
||||
"keepAlive": true,
|
||||
"sort": 10301,
|
||||
"title": "用户管理",
|
||||
"icon": "icon-park-outline-user"
|
||||
|
|
|
|||
|
|
@ -216,7 +216,8 @@ const user = reactive({
|
|||
"meta": {
|
||||
"sort": 30401,
|
||||
"title": "个人设置",
|
||||
"icon": "icon-park-outline-config"
|
||||
"icon": "icon-park-outline-config",
|
||||
"auth": ["1"]
|
||||
}
|
||||
}
|
||||
</route>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { api } from "@/api";
|
||||
import { store, useUserStore } from "@/store";
|
||||
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 { Router } from "vue-router";
|
||||
import { menus } from "../menus";
|
||||
import { MenuItem, menus } from "../menus";
|
||||
import { APP_HOME_NAME } from "../routes/base";
|
||||
import { APP_ROUTE_NAME, routes } from "../routes/page";
|
||||
import { env } from "@/config/env";
|
||||
|
|
@ -26,34 +26,81 @@ export function useAuthGuard(router: Router) {
|
|||
router.push({ path: "/login", query: { redirect } });
|
||||
};
|
||||
|
||||
router.beforeEach(async function (to) {
|
||||
router.beforeEach(async function (to, from) {
|
||||
const userStore = useUserStore(store);
|
||||
const menuStore = useMenuStore(store);
|
||||
|
||||
// 手动指定直接通过
|
||||
if (to.meta.auth?.some((i) => i === "*")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 在白名单内直接通过
|
||||
if (WHITE_LIST.includes(to.path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 未登陆才能访问的页面
|
||||
if (UNSIGNIN_LIST.includes(to.path)) {
|
||||
// 未登陆则允许通过
|
||||
if (!userStore.accessToken) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 已登陆进行提示
|
||||
Notification.warning({
|
||||
title: "跳转提示",
|
||||
content: `您已登陆,如需重新登陆请退出后再操作!`,
|
||||
});
|
||||
|
||||
// 不是从路由跳转的,跳转回首页
|
||||
if (!from.matched.length) {
|
||||
return "/";
|
||||
}
|
||||
|
||||
// 已登陆不允许
|
||||
return false;
|
||||
}
|
||||
|
||||
// 未登录跳转到登陆页面
|
||||
if (!userStore.accessToken) {
|
||||
return { path: "/login", query: { redirect: to.path } };
|
||||
}
|
||||
|
||||
// 未获取菜单进行获取
|
||||
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);
|
||||
|
||||
// 路由处理
|
||||
for (const route of routes) {
|
||||
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);
|
||||
if (home) {
|
||||
const route = { ...home, name: APP_HOME_NAME, alias: "/" };
|
||||
|
|
@ -62,6 +109,8 @@ export function useAuthGuard(router: Router) {
|
|||
return router.replace(to.path);
|
||||
}
|
||||
}
|
||||
|
||||
// 兜底处理
|
||||
return true;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ export interface MenuItem {
|
|||
title?: string;
|
||||
icon?: string;
|
||||
external?: boolean;
|
||||
name?: string;
|
||||
keepAlive: boolean;
|
||||
children?: MenuItem[];
|
||||
}
|
||||
|
||||
|
|
@ -25,7 +27,7 @@ function routesToItems(routes: RouteRecordRaw[]): MenuItem[] {
|
|||
|
||||
for (const route of routes) {
|
||||
const { meta = {}, parentMeta, path } = route as any;
|
||||
const { title, sort, icon } = meta;
|
||||
const { title, sort, icon, keepAlive = false, name } = meta;
|
||||
let id = path;
|
||||
let paths = route.path.split("/");
|
||||
let parentId = paths.slice(0, -1).join("/");
|
||||
|
|
@ -39,6 +41,7 @@ function routesToItems(routes: RouteRecordRaw[]): MenuItem[] {
|
|||
sort,
|
||||
path,
|
||||
id: path,
|
||||
keepAlive: false,
|
||||
parentId: paths.slice(0, -1).join("/"),
|
||||
});
|
||||
} else {
|
||||
|
|
@ -47,7 +50,7 @@ function routesToItems(routes: RouteRecordRaw[]): MenuItem[] {
|
|||
parentId = p;
|
||||
}
|
||||
}
|
||||
items.push({ id, title, parentId, path, icon, sort });
|
||||
items.push({ id, title, parentId, path, icon, sort, keepAlive, name });
|
||||
}
|
||||
|
||||
return items;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ export const useMenuStore = defineStore({
|
|||
state: (): MenuStore => {
|
||||
return {
|
||||
menus: [],
|
||||
cacheAppNames: [],
|
||||
cacheTopNames: [],
|
||||
home: "",
|
||||
};
|
||||
},
|
||||
|
|
@ -23,7 +25,23 @@ export const useMenuStore = defineStore({
|
|||
*/
|
||||
setHome(path: string) {
|
||||
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[];
|
||||
/**
|
||||
* KeepAlive缓存的顶级组件名字
|
||||
*/
|
||||
cacheTopNames: string[];
|
||||
/**
|
||||
* KeepAlive缓存的页面组件名字
|
||||
*/
|
||||
cacheAppNames: string[];
|
||||
/**
|
||||
* 首页路径
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export const useUserStore = defineStore({
|
|||
avatar: "https://github.com/juetan.png",
|
||||
accessToken: "",
|
||||
refreshToken: undefined,
|
||||
auth: []
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
|
|
@ -75,4 +76,8 @@ export interface UserStore {
|
|||
* 刷新令牌
|
||||
*/
|
||||
refreshToken?: string;
|
||||
/**
|
||||
* 拥有权限
|
||||
*/
|
||||
auth: string[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ declare module '@vue/runtime-core' {
|
|||
AMenu: typeof import('@arco-design/web-vue')['Menu']
|
||||
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
|
||||
AModal: typeof import('@arco-design/web-vue')['Modal']
|
||||
AniEmpty: typeof import('./../components/empty/AniEmpty.vue')['default']
|
||||
APagination: typeof import('@arco-design/web-vue')['Pagination']
|
||||
APopover: typeof import('@arco-design/web-vue')['Popover']
|
||||
AProgress: typeof import('@arco-design/web-vue')['Progress']
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@ declare module "vue-router" {
|
|||
* 是否缓存页面
|
||||
*/
|
||||
keepAlive?: boolean;
|
||||
/**
|
||||
* 组件名字(keepAlive为true时必须)
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 是否显示loading
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -24,13 +24,18 @@ export const listToTree = (list: any[], id = "id", pid = "parentId", cid = "chil
|
|||
* @param fn 函数
|
||||
* @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) {
|
||||
before && fn(item);
|
||||
before && fn(item, level);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤树结构
|
||||
* @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