@pubinfo/core 2.0.5 → 2.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AppSetting-DXWLCrHK.js → AppSetting-Bqdk4mr0.js} +15 -15
- package/dist/{HCheckList.vue_vue_type_script_setup_true_lang-CWeTAl9J.js → HCheckList.vue_vue_type_script_setup_true_lang-Dun5ilGJ.js} +6 -7
- package/dist/{HToggle-BrA8cCVJ.js → HToggle-B1MlIVcK.js} +1 -1
- package/dist/HeaderThinMenu-C5rj1NGZ.js +4 -0
- package/dist/{PreferencesContent-6s6Bb66p.js → PreferencesContent-DLqC7RFC.js} +4 -4
- package/dist/{SettingBreadcrumb-D7XUufYP.js → SettingBreadcrumb-DWRDMfAi.js} +3 -3
- package/dist/{SettingCopyright-B1QA6f0R.js → SettingCopyright-C4K-AXxu.js} +2 -2
- package/dist/{SettingEnableTransition-C1lccvKJ.js → SettingEnableTransition-82BIgjuv.js} +2 -2
- package/dist/{SettingHome-DFzpduj_.js → SettingHome-D9qElPUi.js} +2 -2
- package/dist/{SettingMenu-BWVnHKmZ.js → SettingMenu-smmCDuqz.js} +3 -3
- package/dist/{SettingMode-CCYkyiJe.js → SettingMode-Ct8lcvcA.js} +1 -1
- package/dist/{SettingNavSearch-CSboDt7O.js → SettingNavSearch-CC4Hl4wF.js} +2 -2
- package/dist/{SettingOther-CKje3ppW.js → SettingOther-DMFPAPjR.js} +2 -2
- package/dist/{SettingPage-Cy5QBoiw.js → SettingPage-D8m-naZp.js} +2 -2
- package/dist/{SettingTabbar-CPAGoX7H.js → SettingTabbar-P3lOwe5Y.js} +3 -3
- package/dist/{SettingThemes-Cj8zHw81.js → SettingThemes-BwKbjHZZ.js} +1 -1
- package/dist/{SettingToolbar-By5fs_vK.js → SettingToolbar-NjhqqJNo.js} +2 -2
- package/dist/{SettingTopbar-CMC03lzb.js → SettingTopbar-CL9g9QwB.js} +3 -3
- package/dist/{SettingWidthMode-CsWVtIhX.js → SettingWidthMode-Bfv4uU4j.js} +1 -1
- package/dist/built-in/layout-component/Layout.vue.d.ts +1 -0
- package/dist/built-in/layout-component/index.d.ts +2 -1
- package/dist/{colors-PYwA9HeG.js → colors-BksLvfd2.js} +1 -1
- package/dist/core/interface.d.ts +5 -0
- package/dist/core/resolver/icon.d.ts +2 -0
- package/dist/features/components/PageMain/index.vue.d.ts +1 -1
- package/dist/features/components/PubinfoIcon/index.vue.d.ts +1 -13
- package/dist/features/components/index.d.ts +0 -1
- package/dist/features/stores/modules/menu.d.ts +4 -3
- package/dist/{index-CyPGB_nB.js → index-BR7USpvK.js} +2 -2
- package/dist/{index-rq5fCkC1.js → index-BZvIi9NG.js} +14814 -14767
- package/dist/{index-KfXI3f5D.js → index-BfNdcMoQ.js} +2 -2
- package/dist/index-BiFknLSr.js +4 -0
- package/dist/{index-DsXnRME_.js → index-CKhI9IyX.js} +1 -1
- package/dist/{index-C0lnDzae.js → index-CY6xyw-d.js} +2 -2
- package/dist/{index-C54-r6zd.js → index-Ck4Qus0W.js} +3 -3
- package/dist/{index-C1gew7gA.js → index-DN5TqEna.js} +4 -4
- package/dist/{index-C0MpeDJ4.js → index-icXFkR7f.js} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{pick-BtdlEifh.js → pick-DjxDa62c.js} +1 -1
- package/dist/style.css +1 -1
- package/package.json +5 -5
- package/src/built-in/layout-component/Layout.vue +14 -2
- package/src/built-in/layout-component/components/Header/HeaderThinMenu.vue +2 -6
- package/src/built-in/layout-component/components/Menu/index.vue +1 -1
- package/src/{features → built-in/layout-component}/components/NotAllowed/index.vue +2 -32
- package/src/built-in/layout-component/components/Tools/DarkModeToggle.vue +28 -10
- package/src/built-in/layout-component/index.ts +2 -1
- package/src/built-in/layout-component/provider.ts +0 -3
- package/src/core/interface.ts +7 -0
- package/src/core/resolver/icon.ts +37 -1
- package/src/core/resolver/page.ts +0 -1
- package/src/features/assets/styles/globals.css +1 -10
- package/src/features/components/PubinfoApp/index.vue +1 -14
- package/src/features/components/PubinfoIcon/index.vue +92 -7
- package/src/features/components/index.ts +0 -1
- package/src/features/settings/index.ts +1 -0
- package/src/features/stores/modules/menu.ts +43 -25
- package/src/features/stores/modules/route.ts +8 -3
- package/src/index.ts +1 -0
- package/types/settings.d.ts +6 -0
- package/dist/HeaderThinMenu-Be1Gkb6k.js +0 -4
- package/dist/index-BJQN47v_.js +0 -4
- /package/dist/{features → built-in/layout-component}/components/NotAllowed/index.vue.d.ts +0 -0
- /package/src/{features/components/NotAllowed/assets → built-in/layout-component/assets/images}/403.svg +0 -0
- /package/src/{features/components/NotAllowed/assets → built-in/layout-component/assets/images}/403_dark.svg +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pubinfo/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.7",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"pinia": "^3.0.3",
|
|
27
27
|
"vue": "^3.5.17",
|
|
28
28
|
"vue-router": "^4.5.1",
|
|
29
|
-
"@pubinfo/config": "2.0.
|
|
30
|
-
"@pubinfo/vite": "2.0.
|
|
29
|
+
"@pubinfo/config": "2.0.7",
|
|
30
|
+
"@pubinfo/vite": "2.0.7"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@alova/adapter-axios": "^2.0.16",
|
|
@@ -85,8 +85,8 @@
|
|
|
85
85
|
"vite-plugin-dts": "^4.5.4",
|
|
86
86
|
"vue": "^3.5.17",
|
|
87
87
|
"vue-router": "^4.5.1",
|
|
88
|
-
"@pubinfo/config": "2.0.
|
|
89
|
-
"@pubinfo/vite": "2.0.
|
|
88
|
+
"@pubinfo/config": "2.0.7",
|
|
89
|
+
"@pubinfo/vite": "2.0.7"
|
|
90
90
|
},
|
|
91
91
|
"scripts": {
|
|
92
92
|
"dev": "vite build -w -m watch",
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import RiLogoutBoxLine from '~icons/ri/logout-box-line';
|
|
3
|
+
import { useAuth } from '@/features/composables';
|
|
3
4
|
import BackTop from './components/BackTop/index.vue';
|
|
4
5
|
import LayoutContent from './components/Content/index.vue';
|
|
5
6
|
import Copyright from './components/Copyright/index.vue';
|
|
6
7
|
import LayoutHeader from './components/Header/index.vue';
|
|
8
|
+
import NotAllowed from './components/NotAllowed/index.vue';
|
|
7
9
|
import SettingBar from './components/SettingBar/index.vue';
|
|
8
10
|
import LayoutSidebar from './components/Sidebar/index.vue';
|
|
9
|
-
import LayoutTopbar from './components/Topbar/index.vue';
|
|
10
11
|
|
|
12
|
+
import LayoutTopbar from './components/Topbar/index.vue';
|
|
11
13
|
import { useContext } from './composables/useContext';
|
|
12
14
|
import { useGetSidebarActualWidth } from './composables/useGetComputedStyle';
|
|
13
15
|
import { useHotkey } from './composables/useHotkey';
|
|
@@ -18,6 +20,12 @@ defineOptions({
|
|
|
18
20
|
});
|
|
19
21
|
|
|
20
22
|
const routeInfo = useRoute();
|
|
23
|
+
const { auth } = useAuth();
|
|
24
|
+
|
|
25
|
+
const isAuth = computed(() => {
|
|
26
|
+
return routeInfo.matched.every(item => auth(item.meta.auth ?? ''));
|
|
27
|
+
});
|
|
28
|
+
|
|
21
29
|
const { settingsStore } = useContext();
|
|
22
30
|
const { mainSidebarActualWidth, subSidebarActualWidth } = useGetSidebarActualWidth();
|
|
23
31
|
useHotkey();
|
|
@@ -86,9 +94,13 @@ watch(() => routeInfo.path, () => {
|
|
|
86
94
|
<RiLogoutBoxLine />
|
|
87
95
|
</div>
|
|
88
96
|
|
|
89
|
-
<slot>
|
|
97
|
+
<slot v-if="isAuth">
|
|
90
98
|
<LayoutContent />
|
|
91
99
|
</slot>
|
|
100
|
+
|
|
101
|
+
<slot v-else name="notAllowed">
|
|
102
|
+
<NotAllowed />
|
|
103
|
+
</slot>
|
|
92
104
|
</div>
|
|
93
105
|
|
|
94
106
|
<slot name="footer">
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useContext } from '../../composables/useContext';
|
|
3
3
|
import Menu from '../Menu/index.vue';
|
|
4
|
-
|
|
5
|
-
= Vue SFC Options 配置 =
|
|
6
|
-
============================================= */
|
|
4
|
+
|
|
7
5
|
defineOptions({
|
|
8
6
|
name: 'LayoutHeaderThinMenu',
|
|
9
7
|
});
|
|
10
|
-
|
|
11
|
-
= 逻辑代码 =
|
|
12
|
-
============================================= */
|
|
8
|
+
|
|
13
9
|
const route = useRoute();
|
|
14
10
|
|
|
15
11
|
const { settingsStore, menuStore } = useContext();
|
|
@@ -33,7 +33,7 @@ const isMenuPopup = computed<MenuInjection['isMenuPopup']>(() => {
|
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
// 解析传入的 menu 数据,并保存到 items 和 subMenus 对象中
|
|
36
|
-
function initItems(menu: MenuProps['menu'], parentPaths: string[] = []) {
|
|
36
|
+
function initItems(menu: MenuProps['menu'] = [], parentPaths: string[] = []) {
|
|
37
37
|
menu.forEach((item) => {
|
|
38
38
|
const index = item.path ?? JSON.stringify(item);
|
|
39
39
|
if (item.children && item.children.length > 0) {
|
|
@@ -6,40 +6,15 @@ defineOptions({
|
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
const route = useRoute();
|
|
9
|
-
const router = useRouter();
|
|
10
9
|
|
|
11
10
|
const settingsStore = useSettingsStore();
|
|
12
11
|
const tabbarStore = useTabbarStore();
|
|
13
12
|
|
|
14
|
-
const data = ref({
|
|
15
|
-
inter: Number.NaN,
|
|
16
|
-
countdown: 5,
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
onUnmounted(() => {
|
|
20
|
-
if (data.value.inter) {
|
|
21
|
-
window.clearInterval(data.value.inter);
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
13
|
onMounted(() => {
|
|
26
14
|
if (settingsStore.settings.tabbar.enable) {
|
|
27
15
|
tabbarStore.remove(route.meta.activeMenu || route.fullPath);
|
|
28
16
|
}
|
|
29
|
-
data.value.inter = window.setInterval(() => {
|
|
30
|
-
data.value.countdown--;
|
|
31
|
-
if (data.value.countdown === 0) {
|
|
32
|
-
if (data.value.inter) {
|
|
33
|
-
window.clearInterval(data.value.inter);
|
|
34
|
-
}
|
|
35
|
-
goBack();
|
|
36
|
-
}
|
|
37
|
-
}, 1000);
|
|
38
17
|
});
|
|
39
|
-
|
|
40
|
-
function goBack() {
|
|
41
|
-
router.push('/');
|
|
42
|
-
}
|
|
43
18
|
</script>
|
|
44
19
|
|
|
45
20
|
<template>
|
|
@@ -52,23 +27,18 @@ function goBack() {
|
|
|
52
27
|
<div class="desc mx-0 text-xl text-stone-5 dark:text-[#C9D6EF]">
|
|
53
28
|
抱歉,你无权访问该页面
|
|
54
29
|
</div>
|
|
55
|
-
<div>
|
|
56
|
-
<HButton @click="goBack">
|
|
57
|
-
{{ data.countdown }} 秒后,返回首页
|
|
58
|
-
</HButton>
|
|
59
|
-
</div>
|
|
60
30
|
</div>
|
|
61
31
|
</div>
|
|
62
32
|
</template>
|
|
63
33
|
|
|
64
34
|
<style scoped>
|
|
65
35
|
.noPermissionIcon {
|
|
66
|
-
background: url("
|
|
36
|
+
background: url("../../assets/images/403.svg") no-repeat;
|
|
67
37
|
background-size: 100% 100%;
|
|
68
38
|
}
|
|
69
39
|
|
|
70
40
|
[data-theme="dark"] .noPermissionIcon {
|
|
71
|
-
background: url("
|
|
41
|
+
background: url("../../assets/images/403_dark.svg") no-repeat;
|
|
72
42
|
background-size: 100% 100%;
|
|
73
43
|
}
|
|
74
44
|
</style>
|
|
@@ -9,20 +9,36 @@ defineOptions({
|
|
|
9
9
|
const { settingsStore } = useContext();
|
|
10
10
|
|
|
11
11
|
function toggleColorScheme(event: MouseEvent) {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
const currentScheme = settingsStore.settings.app.colorScheme;
|
|
13
|
+
const nextScheme = currentScheme === 'dark' ? 'light' : 'dark';
|
|
14
|
+
|
|
15
|
+
const { startViewTransition } = useViewTransition(() => {
|
|
16
|
+
settingsStore.setColorScheme(nextScheme);
|
|
17
|
+
void settingsStore.setPreferencesSetting({
|
|
16
18
|
app: {
|
|
17
|
-
colorScheme,
|
|
19
|
+
colorScheme: nextScheme,
|
|
18
20
|
},
|
|
21
|
+
}).catch((error) => {
|
|
22
|
+
console.error('[DarkModeToggle] Failed to persist color scheme', error);
|
|
19
23
|
});
|
|
20
24
|
});
|
|
21
25
|
|
|
22
26
|
nextTick(() => {
|
|
23
|
-
startViewTransition()
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
const transition = startViewTransition();
|
|
28
|
+
if (!transition) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
transition.ready.then(() => {
|
|
33
|
+
const cssNamespace = globalThis.CSS;
|
|
34
|
+
const supportsClipPath = typeof cssNamespace?.supports === 'function'
|
|
35
|
+
&& cssNamespace.supports('clip-path', 'circle(0px at 0px 0px)');
|
|
36
|
+
if (!supportsClipPath) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const isKeyboardTrigger = event.detail === 0 && event.clientX === 0 && event.clientY === 0;
|
|
40
|
+
const x = isKeyboardTrigger ? innerWidth / 2 : event.clientX;
|
|
41
|
+
const y = isKeyboardTrigger ? innerHeight / 2 : event.clientY;
|
|
26
42
|
const endRadius = Math.hypot(
|
|
27
43
|
Math.max(x, innerWidth - x),
|
|
28
44
|
Math.max(y, innerHeight - y),
|
|
@@ -33,14 +49,16 @@ function toggleColorScheme(event: MouseEvent) {
|
|
|
33
49
|
];
|
|
34
50
|
document.documentElement.animate(
|
|
35
51
|
{
|
|
36
|
-
clipPath
|
|
52
|
+
clipPath,
|
|
37
53
|
},
|
|
38
54
|
{
|
|
39
55
|
duration: 300,
|
|
40
56
|
easing: 'ease-out',
|
|
41
|
-
pseudoElement:
|
|
57
|
+
pseudoElement: '::view-transition-new(root)',
|
|
42
58
|
},
|
|
43
59
|
);
|
|
60
|
+
}).catch((error) => {
|
|
61
|
+
console.error('[DarkModeToggle] View transition failed', error);
|
|
44
62
|
});
|
|
45
63
|
});
|
|
46
64
|
}
|
|
@@ -17,6 +17,7 @@ import LayoutTopbar from './components/Topbar/index.vue';
|
|
|
17
17
|
|
|
18
18
|
import Logo from './components/Logo/index.vue';
|
|
19
19
|
import Tools from './components/Tools/index.vue';
|
|
20
|
+
import NotAllowed from './components/NotAllowed/index.vue';
|
|
20
21
|
import Fullscreen from './components/Tools/Fullscreen.vue';
|
|
21
22
|
import PageReload from './components/Tools/PageReload.vue';
|
|
22
23
|
import DarkModeToggle from './components/Tools/DarkModeToggle.vue';
|
|
@@ -99,6 +100,6 @@ export {
|
|
|
99
100
|
|
|
100
101
|
Logo,
|
|
101
102
|
Tools,
|
|
102
|
-
|
|
103
|
+
NotAllowed,
|
|
103
104
|
Copyright,
|
|
104
105
|
};
|
package/src/core/interface.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Menu } from '#/menu';
|
|
1
2
|
import type { AdapterCreateOptions, AlovaAxiosRequestConfig } from '@alova/adapter-axios';
|
|
2
3
|
import type { Alova, AlovaGenerics, AlovaOptions, Method, ResponseCompleteHandler, ResponseErrorHandler } from 'alova';
|
|
3
4
|
import type { AxiosResponse, AxiosResponseHeaders } from 'axios';
|
|
@@ -88,8 +89,14 @@ export type RequestInstance = Alova<AG>;
|
|
|
88
89
|
export type RequestMethod = Method<AG>;
|
|
89
90
|
|
|
90
91
|
export interface Hooks {
|
|
92
|
+
/** http 请求拦截器 */
|
|
91
93
|
'http:request': (method: Method<AG>) => void | Promise<void>
|
|
94
|
+
|
|
95
|
+
/** http 响应拦截器 */
|
|
92
96
|
'http:response': RespondedHandler<AG> | RespondedHandlerRecord<AG>
|
|
97
|
+
|
|
98
|
+
/** 菜单数据转换 */
|
|
99
|
+
'menu:transform': (menuData: Menu.recordMainRaw[]) => Menu.recordMainRaw[] | Promise<Menu.recordMainRaw[]>
|
|
93
100
|
}
|
|
94
101
|
|
|
95
102
|
// overwrite alova
|
|
@@ -7,9 +7,29 @@ const namespace = getPubinfoNamespace();
|
|
|
7
7
|
namespace.iconModules = namespace.iconModules || new Map<string, ModuleRecord>();
|
|
8
8
|
const iconModuleMap = namespace.iconModules as Map<string, ModuleRecord>;
|
|
9
9
|
|
|
10
|
+
namespace.iconCache = namespace.iconCache || new Map<string, string>();
|
|
11
|
+
const iconCache = namespace.iconCache as Map<string, string>;
|
|
12
|
+
|
|
13
|
+
function getCacheKey(name: string, id?: string) {
|
|
14
|
+
return `${id ?? 'default'}:${name}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getCachedIcon(name: string, id?: string) {
|
|
18
|
+
return iconCache.get(getCacheKey(name, id));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function setCachedIcon(name: string, id: string | undefined, value?: string) {
|
|
22
|
+
const key = getCacheKey(name, id);
|
|
23
|
+
if (value) {
|
|
24
|
+
iconCache.set(key, value);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
iconCache.delete(key);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
10
31
|
export function defineIconModule(id: string, map: ModuleRecord) {
|
|
11
32
|
if (iconModuleMap.has(id)) {
|
|
12
|
-
console.warn(`[PUBINFO] 图标模块 ${id} 已经被定义。`);
|
|
13
33
|
return;
|
|
14
34
|
}
|
|
15
35
|
iconModuleMap.set(id, map);
|
|
@@ -31,6 +51,11 @@ export function getAllIconModules(): Map<string, ModuleRecord> {
|
|
|
31
51
|
}
|
|
32
52
|
|
|
33
53
|
export async function setupIcon(name: string, id?: string) {
|
|
54
|
+
const cached = getCachedIcon(name, id);
|
|
55
|
+
if (cached) {
|
|
56
|
+
return cached;
|
|
57
|
+
}
|
|
58
|
+
|
|
34
59
|
if (!readProjectModules()) {
|
|
35
60
|
await loadProjectModules().catch(() => {});
|
|
36
61
|
}
|
|
@@ -41,11 +66,19 @@ export async function setupIcon(name: string, id?: string) {
|
|
|
41
66
|
|
|
42
67
|
let result = await findFile(moduleMap, name);
|
|
43
68
|
if (result) {
|
|
69
|
+
setCachedIcon(name, id, result);
|
|
70
|
+
if (id) {
|
|
71
|
+
setCachedIcon(name, undefined, result);
|
|
72
|
+
}
|
|
44
73
|
return result;
|
|
45
74
|
}
|
|
46
75
|
|
|
47
76
|
result = await findFile(iconMap, name);
|
|
48
77
|
if (result) {
|
|
78
|
+
setCachedIcon(name, id, result);
|
|
79
|
+
if (id) {
|
|
80
|
+
setCachedIcon(name, undefined, result);
|
|
81
|
+
}
|
|
49
82
|
return result;
|
|
50
83
|
}
|
|
51
84
|
|
|
@@ -58,6 +91,9 @@ export async function setupIcon(name: string, id?: string) {
|
|
|
58
91
|
|
|
59
92
|
result = await findFile(modulePages, name);
|
|
60
93
|
if (result) {
|
|
94
|
+
const targetId = moduleId === 'default' ? undefined : moduleId;
|
|
95
|
+
setCachedIcon(name, targetId, result);
|
|
96
|
+
setCachedIcon(name, undefined, result);
|
|
61
97
|
return result;
|
|
62
98
|
}
|
|
63
99
|
}
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
|
|
34
34
|
/* 明暗模式 CSS 变量 */
|
|
35
35
|
:root {
|
|
36
|
-
color-scheme: light;
|
|
36
|
+
color-scheme: light dark;
|
|
37
37
|
--g-box-shadow-color: rgba(0, 0, 0, 0.12);
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -52,18 +52,9 @@
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
:root.dark {
|
|
55
|
-
color-scheme: dark;
|
|
56
55
|
--g-box-shadow-color: rgba(0, 0, 0, 0.72);
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
:root.dark::view-transition-old(root) {
|
|
60
|
-
z-index: 9999;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
:root.dark::view-transition-new(root) {
|
|
64
|
-
z-index: 1;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
58
|
::-webkit-scrollbar {
|
|
68
59
|
width: 12px;
|
|
69
60
|
height: 12px;
|
|
@@ -1,22 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { useAuth } from '@/features/composables';
|
|
3
|
-
import NotAllowed from '../NotAllowed/index.vue';
|
|
4
|
-
|
|
5
2
|
defineOptions({
|
|
6
3
|
name: 'PubinfoApp',
|
|
7
4
|
});
|
|
8
|
-
|
|
9
|
-
const route = useRoute();
|
|
10
|
-
const { auth } = useAuth();
|
|
11
|
-
|
|
12
|
-
const isAuth = computed(() => {
|
|
13
|
-
return route.matched.every(item => auth(item.meta.auth ?? ''));
|
|
14
|
-
});
|
|
15
5
|
</script>
|
|
16
6
|
|
|
17
7
|
<template>
|
|
18
|
-
<RouterView
|
|
19
|
-
<component :is="Component" v-if="isAuth" />
|
|
20
|
-
<NotAllowed v-else />
|
|
21
|
-
</RouterView>
|
|
8
|
+
<RouterView />
|
|
22
9
|
</template>
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import type { PubinfoIconOptions } from './props';
|
|
3
3
|
import * as AntdIcons from '@ant-design/icons-vue';
|
|
4
4
|
import { Icon } from '@iconify/vue';
|
|
5
|
-
import {
|
|
6
|
-
import { setupIcon } from '@/core';
|
|
5
|
+
import { shallowRef, watch } from 'vue';
|
|
6
|
+
import { getCachedIcon, setCachedIcon, setupIcon } from '@/core';
|
|
7
7
|
import { useProvider } from '../PubinfoProvider';
|
|
8
8
|
import PrismBox from './PrismBox.vue';
|
|
9
9
|
import SquareBox from './SquareBox.vue';
|
|
@@ -12,7 +12,9 @@ defineOptions({
|
|
|
12
12
|
name: 'PubinfoIcon',
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
const props = defineProps<PubinfoIconOptions>()
|
|
15
|
+
const props = withDefaults(defineProps<PubinfoIconOptions>(), {
|
|
16
|
+
size: '1rem',
|
|
17
|
+
});
|
|
16
18
|
|
|
17
19
|
const { loadIcon } = useProvider();
|
|
18
20
|
|
|
@@ -69,10 +71,93 @@ const element = computed(() => {
|
|
|
69
71
|
return 'i';
|
|
70
72
|
});
|
|
71
73
|
|
|
72
|
-
const icon =
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
const icon = shallowRef<string | undefined>(undefined);
|
|
75
|
+
|
|
76
|
+
function getIconLookupMeta(rawName: string) {
|
|
77
|
+
const parsed = parseString(rawName);
|
|
78
|
+
const fileName = `${parsed.name}${parsed.ext || '.svg'}`;
|
|
79
|
+
const moduleId = parsed.prefix || undefined;
|
|
80
|
+
return { fileName, moduleId } as const;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function resolveCachedIcon(fileName: string, moduleId?: string) {
|
|
84
|
+
return getCachedIcon(fileName, moduleId) ?? getCachedIcon(fileName, undefined);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function primeIconFromCache() {
|
|
88
|
+
if (outputType.value !== 'custom') {
|
|
89
|
+
icon.value = undefined;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const { fileName, moduleId } = getIconLookupMeta(props.name);
|
|
94
|
+
const cached = resolveCachedIcon(fileName, moduleId);
|
|
95
|
+
if (cached) {
|
|
96
|
+
icon.value = cached;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
primeIconFromCache();
|
|
101
|
+
|
|
102
|
+
watch(
|
|
103
|
+
() => ({ name: props.name, type: outputType.value }),
|
|
104
|
+
async ({ name, type }, _, onCleanup) => {
|
|
105
|
+
if (type !== 'custom') {
|
|
106
|
+
icon.value = undefined;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const { fileName, moduleId } = getIconLookupMeta(name);
|
|
111
|
+
|
|
112
|
+
const cached = resolveCachedIcon(fileName, moduleId);
|
|
113
|
+
if (cached) {
|
|
114
|
+
icon.value = cached;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let cancelled = false;
|
|
119
|
+
onCleanup(() => {
|
|
120
|
+
cancelled = true;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const resolvedFromProvider = await loadIcon?.(name);
|
|
125
|
+
if (!cancelled && resolvedFromProvider) {
|
|
126
|
+
icon.value = resolvedFromProvider;
|
|
127
|
+
setCachedIcon(fileName, moduleId, resolvedFromProvider);
|
|
128
|
+
if (moduleId) {
|
|
129
|
+
setCachedIcon(fileName, undefined, resolvedFromProvider);
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (import.meta.env.DEV) {
|
|
136
|
+
console.warn('[PUBINFO] loadIcon failed:', error);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const resolved = await setupIcon(fileName, moduleId);
|
|
142
|
+
if (cancelled) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (resolved) {
|
|
146
|
+
icon.value = resolved;
|
|
147
|
+
setCachedIcon(fileName, moduleId, resolved);
|
|
148
|
+
if (moduleId) {
|
|
149
|
+
setCachedIcon(fileName, undefined, resolved);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (import.meta.env.DEV) {
|
|
155
|
+
console.warn('[PUBINFO] setupIcon failed:', error);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
{ immediate: true, flush: 'sync' },
|
|
160
|
+
);
|
|
76
161
|
|
|
77
162
|
interface Parsed {
|
|
78
163
|
prefix: string
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export { default as NotAllowed } from './NotAllowed/index.vue';
|
|
2
1
|
export { default as PageHeader } from './PageHeader/index.vue';
|
|
3
2
|
export { default as PageMain } from './PageMain/index.vue';
|
|
4
3
|
export { default as PassStrengthValidator } from './PassStrengthValidator/index.vue';
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Menu } from '#/menu';
|
|
2
2
|
|
|
3
|
+
import { computedAsync } from '@vueuse/core';
|
|
3
4
|
import { get, isNumber } from 'lodash-es';
|
|
4
5
|
import { match } from 'path-to-regexp';
|
|
5
6
|
import { defineStore } from 'pinia';
|
|
7
|
+
import { hooks } from '@/core/ctx';
|
|
6
8
|
import { useWarn } from '@/features/composables';
|
|
7
9
|
import { resolveRoutePath } from '@/utils';
|
|
8
10
|
import { STORE_NAME } from '../enum';
|
|
@@ -45,7 +47,7 @@ const useMenuStore = defineStore(
|
|
|
45
47
|
* // { path: '/app/about', title: 'About' },
|
|
46
48
|
* // ]
|
|
47
49
|
*/
|
|
48
|
-
function convertToFullPath(menu: any[], path: string = '') {
|
|
50
|
+
function convertToFullPath(menu: any[] = [], path: string = '') {
|
|
49
51
|
return menu.map((item) => {
|
|
50
52
|
item.path = resolveRoutePath(path, item.path);
|
|
51
53
|
if (item.children) {
|
|
@@ -59,30 +61,46 @@ const useMenuStore = defineStore(
|
|
|
59
61
|
* 获取所有菜单项
|
|
60
62
|
* @returns 所有菜单项的数组
|
|
61
63
|
*/
|
|
62
|
-
const allMenus =
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (settingsStore.settings.menu.menuMode === 'single') {
|
|
69
|
-
returnMenus[0].children = [];
|
|
70
|
-
routeStore.routes.forEach((item) => {
|
|
71
|
-
if (!get(item, 'meta.isDev', false)) {
|
|
72
|
-
returnMenus[0].children?.push(...item.children as Menu.recordRaw[]);
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
useWarn(`侧边栏模式(不含主导航)下省略${item.meta.title}的子集菜单,因为该菜单开启了isDev属性。`);
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
returnMenus = routeStore.routes as Menu.recordMainRaw[];
|
|
81
|
-
}
|
|
82
|
-
returnMenus.map(item => convertToFullPath(item.children));
|
|
64
|
+
const allMenus = computedAsync<Menu.recordMainRaw[]>(
|
|
65
|
+
async () => {
|
|
66
|
+
let returnMenus: Menu.recordMainRaw[] = [{
|
|
67
|
+
meta: {},
|
|
68
|
+
children: [],
|
|
69
|
+
}];
|
|
83
70
|
|
|
84
|
-
|
|
85
|
-
|
|
71
|
+
if (settingsStore.settings.menu.menuMode === 'single') {
|
|
72
|
+
returnMenus[0].children = [];
|
|
73
|
+
routeStore.routes.forEach((item) => {
|
|
74
|
+
if (!get(item, 'meta.isDev', false)) {
|
|
75
|
+
returnMenus[0].children?.push(...item.children as Menu.recordRaw[]);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
useWarn(`侧边栏模式(不含主导航)下省略${item.meta.title}的子集菜单,因为该菜单开启了isDev属性。`);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
returnMenus = routeStore.routes as Menu.recordMainRaw[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
returnMenus.forEach(item => convertToFullPath(item.children));
|
|
87
|
+
|
|
88
|
+
const transformedMenus = await hooks.callHookWith(
|
|
89
|
+
async (hookFns, args) => {
|
|
90
|
+
let res = args[0];
|
|
91
|
+
for (const fn of hookFns) {
|
|
92
|
+
res = await fn(res);
|
|
93
|
+
}
|
|
94
|
+
return res as Menu.recordMainRaw[];
|
|
95
|
+
},
|
|
96
|
+
'menu:transform',
|
|
97
|
+
returnMenus,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return transformedMenus;
|
|
101
|
+
},
|
|
102
|
+
[],
|
|
103
|
+
);
|
|
86
104
|
|
|
87
105
|
/**
|
|
88
106
|
* 侧边栏菜单的计算属性。
|
|
@@ -174,7 +192,7 @@ const useMenuStore = defineStore(
|
|
|
174
192
|
* @param rootPath 根路径
|
|
175
193
|
* @returns 默认展开的路径数组
|
|
176
194
|
*/
|
|
177
|
-
function getDefaultOpenedPaths(menus: Menu.recordRaw[], rootPath = '') {
|
|
195
|
+
function getDefaultOpenedPaths(menus: Menu.recordRaw[] = [], rootPath = '') {
|
|
178
196
|
const defaultOpenedPaths: string[] = [];
|
|
179
197
|
menus.forEach((item) => {
|
|
180
198
|
if (item.meta?.defaultOpened && item.children) {
|
|
@@ -33,6 +33,12 @@ const useRouteStore = defineStore(
|
|
|
33
33
|
system?: boolean
|
|
34
34
|
}>>([]);
|
|
35
35
|
|
|
36
|
+
/** 如果权限功能开启 / 不展示无权限菜单,则需要对路由数据进行筛选过滤 */
|
|
37
|
+
const needFilterRoutes = computed(() => {
|
|
38
|
+
const { app, menu } = settingsStore.settings;
|
|
39
|
+
return app.enablePermission && !menu.showWithoutPermission;
|
|
40
|
+
});
|
|
41
|
+
|
|
36
42
|
/**
|
|
37
43
|
* 创建面包屑对象
|
|
38
44
|
* @param currentRouter 当前路由对象
|
|
@@ -273,8 +279,7 @@ const useRouteStore = defineStore(
|
|
|
273
279
|
*/
|
|
274
280
|
const routes = computed(() => {
|
|
275
281
|
let returnRoutes: Route.recordMainRaw[];
|
|
276
|
-
|
|
277
|
-
if (settingsStore.settings.app.enablePermission) {
|
|
282
|
+
if (needFilterRoutes.value) {
|
|
278
283
|
returnRoutes = filterAsyncRoutes(routesRaw.value, userStore.user.permissions);
|
|
279
284
|
}
|
|
280
285
|
else {
|
|
@@ -351,7 +356,7 @@ const useRouteStore = defineStore(
|
|
|
351
356
|
* 生成路由
|
|
352
357
|
*/
|
|
353
358
|
async function generateRoutes() {
|
|
354
|
-
const res = await postRbacResourceMineOrAllResourceTree({ needAll:
|
|
359
|
+
const res = await postRbacResourceMineOrAllResourceTree({ needAll: !needFilterRoutes.value });
|
|
355
360
|
if (res.success) {
|
|
356
361
|
remoteRoutesRaw.value = res?.data ?? [];
|
|
357
362
|
const staticRoutes = convertSingleRoutes(filterEnabledRoutes(cloneDeep(asyncRoutes)));
|