@pubinfo/core 2.0.14 → 2.1.0
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-Bfabd02_.js → AppSetting-Dzj6YBgW.js} +17 -17
- package/dist/{HCheckList.vue_vue_type_script_setup_true_lang-LHEWLVoX.js → HCheckList.vue_vue_type_script_setup_true_lang-OWv01eXk.js} +1 -1
- package/dist/{HToggle-B3C623BZ.js → HToggle-BnESuuPG.js} +1 -1
- package/dist/HeaderThinMenu-Co2S6vIB.js +4 -0
- package/dist/{PreferencesContent-B42MPjzA.js → PreferencesContent-DBKQJ8Ob.js} +6 -6
- package/dist/{SettingBreadcrumb-D3GS-3MN.js → SettingBreadcrumb-DmP1nJLZ.js} +3 -3
- package/dist/{SettingCopyright-KDpCggiE.js → SettingCopyright-C8_F7BB2.js} +2 -2
- package/dist/{SettingEnableTransition-BF27WYg0.js → SettingEnableTransition-BeJMq6ny.js} +2 -2
- package/dist/{SettingHome-DWyDzFcn.js → SettingHome-yGmbxzJm.js} +3 -3
- package/dist/{SettingMenu-Dz0fbIa3.js → SettingMenu-yWnqZqmz.js} +4 -4
- package/dist/{SettingMode-AD_S9ZkO.js → SettingMode-B8fgUhvi.js} +1 -1
- package/dist/{SettingNavSearch-oWIPjQFc.js → SettingNavSearch--yQEXqEL.js} +3 -3
- package/dist/{SettingOther-ZlPSoHz3.js → SettingOther-CSs1_D9L.js} +3 -3
- package/dist/{SettingPage-BnwAMror.js → SettingPage-Pm7KblGG.js} +2 -2
- package/dist/{SettingTabbar-Vvz-1zTa.js → SettingTabbar-DM5hdlQf.js} +6 -6
- package/dist/{SettingThemes-CLhV2OOt.js → SettingThemes-DqygGgDK.js} +5 -5
- package/dist/{SettingToolbar-nCkGwG2H.js → SettingToolbar-BP-yWiKo.js} +3 -3
- package/dist/{SettingTopbar-DMujCz4n.js → SettingTopbar-Dd_SXy2e.js} +4 -4
- package/dist/{SettingWidthMode-7qsluuMR.js → SettingWidthMode-DOtJg5uB.js} +2 -2
- package/dist/built-in/devtools/index.d.ts +13 -0
- package/dist/built-in/index.d.ts +1 -0
- package/dist/built-in/layout-component/Layout.vue.d.ts +7 -1
- package/dist/built-in/layout-component/components/Topbar/index.vue.d.ts +1 -1
- package/dist/built-in/layout-component/composables/useLayoutVisible.d.ts +8 -0
- package/dist/built-in/layout-component/interface.d.ts +22 -0
- package/dist/features/stores/modules/settings.d.ts +6 -6
- package/dist/features/stores/modules/user.d.ts +14 -2
- package/dist/{index-D210UZpV.js → index--xVOZLmn.js} +1 -1
- package/dist/{index-DSNuCtw6.js → index-BSTB6EhQ.js} +1 -1
- package/dist/{index-BN5BYOEh.js → index-C88VclMA.js} +15618 -15440
- package/dist/{index-2Zh2rSjN.js → index-CnbD4YWA.js} +3 -3
- package/dist/{index-BaRQkDKe.js → index-D8kKHmiD.js} +1 -1
- package/dist/index-GSKGoyv_.js +4 -0
- package/dist/{index-DQnnCCLU.js → index-igxVB1m3.js} +4 -5
- package/dist/{index-XjjX4TvH.js → index-npW0xuOi.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/{pick-BZcCK3Xe.js → pick-l2RrF3SE.js} +1 -1
- package/dist/{question-line-CfkciTFq.js → question-line-DCMVyZ3e.js} +2 -2
- package/dist/{right-Bfe2p1o0.js → right-aIVrXLnr.js} +4 -4
- package/dist/style.css +1 -1
- package/package.json +8 -8
- package/src/built-in/devtools/index.ts +292 -0
- package/src/built-in/index.ts +1 -0
- package/src/built-in/layout-component/Layout.vue +93 -9
- package/src/built-in/layout-component/components/Header/HeaderFullMenu/index.vue +8 -4
- package/src/built-in/layout-component/components/Header/index.vue +4 -11
- package/src/built-in/layout-component/components/Sidebar/MainSidebar.vue +3 -1
- package/src/built-in/layout-component/components/Sidebar/SubSidebar.vue +4 -21
- package/src/built-in/layout-component/components/Topbar/index.vue +4 -105
- package/src/built-in/layout-component/composables/useLayoutVisible.ts +87 -0
- package/src/built-in/layout-component/interface.ts +25 -0
- package/src/core/create.ts +1 -3
- package/src/core/request.ts +0 -1
- package/src/features/assets/styles/globals.css +2 -1
- package/src/features/composables/log.ts +3 -5
- package/src/features/stores/modules/keepAlive.ts +10 -14
- package/src/features/stores/modules/menu.ts +20 -13
- package/src/features/stores/modules/route.ts +3 -3
- package/src/features/stores/modules/settings.ts +2 -7
- package/src/features/stores/modules/user.ts +57 -2
- package/src/index.ts +3 -0
- package/types/vue-router.d.ts +9 -2
- package/dist/HeaderThinMenu-B6cpu5jx.js +0 -4
- package/dist/index-iASxH_cj.js +0 -4
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.1.0",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"pinia": "^3.0.3",
|
|
32
32
|
"vue": "^3.5.17",
|
|
33
33
|
"vue-router": "^4.5.1",
|
|
34
|
-
"@pubinfo/config": "2.0
|
|
35
|
-
"@pubinfo/devtools": "2.0
|
|
36
|
-
"@pubinfo/vite": "2.0
|
|
34
|
+
"@pubinfo/config": "2.1.0",
|
|
35
|
+
"@pubinfo/devtools": "2.1.0",
|
|
36
|
+
"@pubinfo/vite": "2.1.0"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@alova/adapter-axios": "^2.0.16",
|
|
@@ -47,7 +47,6 @@
|
|
|
47
47
|
"@vueuse/integrations": "^13.5.0",
|
|
48
48
|
"ant-design-vue": "^4.2.6",
|
|
49
49
|
"axios": "^1.10.0",
|
|
50
|
-
"consola": "^3.4.2",
|
|
51
50
|
"floating-vue": "5.2.2",
|
|
52
51
|
"hookable": "^5.5.3",
|
|
53
52
|
"hotkeys-js": "^3.13.14",
|
|
@@ -64,7 +63,8 @@
|
|
|
64
63
|
"sortablejs": "^1.15.6",
|
|
65
64
|
"unctx": "^2.4.1",
|
|
66
65
|
"vue-m-message": "^4.0.2",
|
|
67
|
-
"zxcvbn": "^4.4.2"
|
|
66
|
+
"zxcvbn": "^4.4.2",
|
|
67
|
+
"@pubinfo/shared": "2.1.0"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@alova/mock": "^2.0.17",
|
|
@@ -92,8 +92,8 @@
|
|
|
92
92
|
"vite-plugin-dts": "^4.5.4",
|
|
93
93
|
"vue": "^3.5.17",
|
|
94
94
|
"vue-router": "^4.5.1",
|
|
95
|
-
"@pubinfo/config": "2.0
|
|
96
|
-
"@pubinfo/vite": "2.0
|
|
95
|
+
"@pubinfo/config": "2.1.0",
|
|
96
|
+
"@pubinfo/vite": "2.1.0"
|
|
97
97
|
},
|
|
98
98
|
"scripts": {
|
|
99
99
|
"dev": "vite build -w -m watch",
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import type { ModuleOptions } from '@/core';
|
|
2
|
+
import { devtoolsStorageKey } from '@pubinfo/devtools/constants';
|
|
3
|
+
import { setupDevtoolsPanel } from '@pubinfo/devtools/panel';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 本模块将宿主应用与 @pubinfo/devtools 打通:
|
|
7
|
+
* - 在应用启动时注册 Devtools 面板(UnoCSS / Chrome / Server Router)。
|
|
8
|
+
* - 监听 HTTP 钩子,在请求打上 `meta.__devtools__` 时,
|
|
9
|
+
* 自动把响应快照写入 localStorage,便于面板读取与回放。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const DEVTOOLS_RAW_RESPONSE_KEY = devtoolsStorageKey.response;
|
|
13
|
+
|
|
14
|
+
const methodNameMap: Record<string, string> = {
|
|
15
|
+
GET: 'Get',
|
|
16
|
+
POST: 'Post',
|
|
17
|
+
PUT: 'Put',
|
|
18
|
+
PATCH: 'Patch',
|
|
19
|
+
DELETE: 'Delete',
|
|
20
|
+
HEAD: 'Head',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const requestMethodNames = Object.values(methodNameMap);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 粗略判断一个值是否为「请求实例」。
|
|
27
|
+
*
|
|
28
|
+
* 判定规则:对象上是否存在大小写首字母形式的方法(Get/Post/...)。
|
|
29
|
+
* 这是对 packages/core/src/core/request.ts 导出的请求封装的一个宽松契约检查。
|
|
30
|
+
*/
|
|
31
|
+
function isRequestInstance(value: any) {
|
|
32
|
+
if (!value || typeof value !== 'object') {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return requestMethodNames.some(name => typeof (value as any)[name] === 'function');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 将任意 payload 转为可序列化的普通对象(若可能)。
|
|
40
|
+
*
|
|
41
|
+
* - 通过 JSON 序列化/反序列化去除响应对象上的循环引用、原型链等复杂结构。
|
|
42
|
+
* - 若序列化失败(比如含不可序列化值或循环引用),则返回原值。
|
|
43
|
+
*/
|
|
44
|
+
function toPlainObject<T>(payload: T): T {
|
|
45
|
+
if (payload === undefined || payload === null) {
|
|
46
|
+
return payload;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(JSON.stringify(payload)) as T;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return payload;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface DevtoolsRequestPayload {
|
|
57
|
+
moduleCandidates?: string[]
|
|
58
|
+
exportName?: string
|
|
59
|
+
method: string
|
|
60
|
+
args?: any[]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface DevtoolsResponsePayload {
|
|
64
|
+
success: boolean
|
|
65
|
+
result?: any
|
|
66
|
+
error?: {
|
|
67
|
+
message: string
|
|
68
|
+
stack?: string
|
|
69
|
+
name?: string
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 将一次请求的响应快照持久化到 localStorage。
|
|
75
|
+
*
|
|
76
|
+
* - 仅在浏览器环境生效(SSR 时直接跳过)。
|
|
77
|
+
* - 存储失败(超配额/序列化失败)时静默忽略,避免影响应用行为。
|
|
78
|
+
*
|
|
79
|
+
* @param _method 原始请求方法(预留位,当前未使用)
|
|
80
|
+
* @param payload 响应对象或错误对象(尽量保持原貌)
|
|
81
|
+
*/
|
|
82
|
+
function storeDevtoolsResponse(_method: any, payload: any) {
|
|
83
|
+
if (typeof localStorage === 'undefined') {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
localStorage.setItem(DEVTOOLS_RAW_RESPONSE_KEY, JSON.stringify(payload));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// ignore serialization/storage failures
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 解析一组候选模块,尝试找到并返回「请求实例」。
|
|
96
|
+
*
|
|
97
|
+
* 查找顺序:
|
|
98
|
+
* 1) 指定的具名导出(若提供 exportName 且命中并为请求实例则返回)。
|
|
99
|
+
* 2) 默认导出(若存在且为请求实例)。
|
|
100
|
+
* 3) 其他任一导出成员中第一个满足 isRequestInstance 的值。
|
|
101
|
+
*
|
|
102
|
+
* 任一候选路径导入失败时会被忽略,继续尝试下一个。
|
|
103
|
+
*/
|
|
104
|
+
async function resolveRequestInstance(
|
|
105
|
+
candidates: string[] = [],
|
|
106
|
+
exportName?: string,
|
|
107
|
+
) {
|
|
108
|
+
for (const specifier of candidates) {
|
|
109
|
+
if (!specifier) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const module = await import(/* @vite-ignore */ specifier);
|
|
114
|
+
const namedExport = exportName ? module?.[exportName] : undefined;
|
|
115
|
+
if (namedExport && isRequestInstance(namedExport)) {
|
|
116
|
+
return namedExport;
|
|
117
|
+
}
|
|
118
|
+
const defaultExport = module?.default;
|
|
119
|
+
if (defaultExport && isRequestInstance(defaultExport)) {
|
|
120
|
+
return defaultExport;
|
|
121
|
+
}
|
|
122
|
+
for (const value of Object.values(module)) {
|
|
123
|
+
if (isRequestInstance(value)) {
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// 忽略导入失败的候选路径,继续尝试下一个。
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 根据面板发来的 payload 执行一次 API 请求。
|
|
137
|
+
*
|
|
138
|
+
* 输入契约(payload):
|
|
139
|
+
* - moduleCandidates: string[] 可能导出请求实例的模块路径(可为绝对/相对/别名)。
|
|
140
|
+
* - exportName: string 可选,期望从模块中拿到的具名导出名。
|
|
141
|
+
* - method: string HTTP 方法(GET/POST/...)。
|
|
142
|
+
* - args: any[] 传递给请求方法的参数(如 path/query/body 等)。
|
|
143
|
+
*
|
|
144
|
+
* 行为:
|
|
145
|
+
* - 解析请求实例 → 获取对应的方法 → 调用并等待结果。
|
|
146
|
+
* - 若返回值带有 .send(),则优先调用 .send() 以与内部请求封装对齐。
|
|
147
|
+
* - 返回标准化的结果:{ success, result } 或 { success:false, error }。
|
|
148
|
+
*/
|
|
149
|
+
async function executeDevtoolsRequest(payload: DevtoolsRequestPayload): Promise<DevtoolsResponsePayload> {
|
|
150
|
+
const response: DevtoolsResponsePayload = {
|
|
151
|
+
success: false,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const method = (payload.method ?? '').toUpperCase();
|
|
156
|
+
const mappedMethod = methodNameMap[method];
|
|
157
|
+
if (!mappedMethod) {
|
|
158
|
+
throw new Error(`请求实例不支持 ${method} 方法`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const requestInstance: any = await resolveRequestInstance(
|
|
162
|
+
payload.moduleCandidates,
|
|
163
|
+
payload.exportName,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
if (!requestInstance) {
|
|
167
|
+
throw new TypeError('未找到匹配的请求实例,请确认 API 文件的 request 导入可被 devtools 解析。');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const requestMethod = requestInstance[mappedMethod];
|
|
171
|
+
if (typeof requestMethod !== 'function') {
|
|
172
|
+
throw new TypeError(`请求实例不支持 ${method} 方法`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let methodResult = requestMethod.apply(requestInstance, payload.args ?? []);
|
|
176
|
+
if (methodResult && typeof methodResult.send === 'function') {
|
|
177
|
+
methodResult = await methodResult.send();
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
methodResult = await methodResult;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
response.success = true;
|
|
184
|
+
response.result = toPlainObject(methodResult);
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
if (error && typeof error === 'object' && 'response' in (error as any) && (error as any).response) {
|
|
188
|
+
response.success = true;
|
|
189
|
+
response.result = toPlainObject((error as any).response);
|
|
190
|
+
}
|
|
191
|
+
else if (error && typeof error === 'object' && !(error instanceof Error)) {
|
|
192
|
+
response.success = true;
|
|
193
|
+
response.result = toPlainObject(error);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
response.success = false;
|
|
197
|
+
response.error = {
|
|
198
|
+
message: error instanceof Error ? error.message : String(error),
|
|
199
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
200
|
+
name: error instanceof Error ? error.name : undefined,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return response;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const MESSAGE_REQUEST_TYPE = 'pubinfo:devtools-request';
|
|
209
|
+
const MESSAGE_RESPONSE_TYPE = 'pubinfo:devtools-response';
|
|
210
|
+
|
|
211
|
+
let devtoolsBridgeInitialised = false;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* 在宿主页面和 Devtools iframe 之间建立 message 通信桥。
|
|
215
|
+
*
|
|
216
|
+
* 协议:
|
|
217
|
+
* - request: { type: 'pubinfo:devtools-request', id, payload }
|
|
218
|
+
* - response: { type: 'pubinfo:devtools-response', id, ...result }
|
|
219
|
+
*
|
|
220
|
+
* 注意:
|
|
221
|
+
* - 仅初始化一次;SSR/非浏览器环境跳过。
|
|
222
|
+
* - postMessage 使用 event.origin → fallback '*',尽量遵循同源策略。
|
|
223
|
+
*/
|
|
224
|
+
function setupDevtoolsRequestBridge() {
|
|
225
|
+
if (devtoolsBridgeInitialised || typeof window === 'undefined') {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
devtoolsBridgeInitialised = true;
|
|
229
|
+
window.addEventListener('message', async (event) => {
|
|
230
|
+
const data = (event as MessageEvent<any>).data;
|
|
231
|
+
if (!data || data.type !== MESSAGE_REQUEST_TYPE || !data.id) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const payload: DevtoolsRequestPayload = data.payload ?? {};
|
|
235
|
+
const responsePayload = await executeDevtoolsRequest(payload);
|
|
236
|
+
const message = {
|
|
237
|
+
type: MESSAGE_RESPONSE_TYPE,
|
|
238
|
+
id: data.id,
|
|
239
|
+
...responsePayload,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const target = (event.source as Window | null) ?? window.parent ?? window;
|
|
243
|
+
const origin = (event.origin && event.origin !== 'null') ? event.origin : (window.location?.origin ?? '*');
|
|
244
|
+
try {
|
|
245
|
+
target.postMessage(message, origin);
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
target.postMessage(message, '*');
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 内置 Devtools 模块。
|
|
255
|
+
*
|
|
256
|
+
* 注册时机:`enforce: 'post'`,确保在路由/请求等核心模块之后挂载。
|
|
257
|
+
*
|
|
258
|
+
* 行为:
|
|
259
|
+
* - setup:注册各类 devtools 面板。
|
|
260
|
+
* - setup:监听来自 Devtools iframe 的消息,响应接口调试请求。
|
|
261
|
+
* - hooks.http:response:当请求元信息中显式标记 `__devtools__` 时,
|
|
262
|
+
* 将响应(或错误响应)快照保存到 localStorage,供面板读取。
|
|
263
|
+
*/
|
|
264
|
+
export function Devtools(): ModuleOptions {
|
|
265
|
+
return {
|
|
266
|
+
name: 'built-in:devtools',
|
|
267
|
+
enforce: 'post',
|
|
268
|
+
setup() {
|
|
269
|
+
setupDevtoolsPanel();
|
|
270
|
+
setupDevtoolsRequestBridge();
|
|
271
|
+
},
|
|
272
|
+
hooks: {
|
|
273
|
+
'http:response': {
|
|
274
|
+
onSuccess(response, method) {
|
|
275
|
+
const methodMeta = (method as any)?.meta ?? (method as any)?.config?.meta;
|
|
276
|
+
if (methodMeta?.__devtools__) {
|
|
277
|
+
storeDevtoolsResponse(method, response);
|
|
278
|
+
}
|
|
279
|
+
return response;
|
|
280
|
+
},
|
|
281
|
+
onError(error, method) {
|
|
282
|
+
const methodMeta = (method as any)?.meta ?? (method as any)?.config?.meta;
|
|
283
|
+
if (methodMeta?.__devtools__) {
|
|
284
|
+
const payload = (error as any)?.response ?? error;
|
|
285
|
+
storeDevtoolsResponse(method, payload);
|
|
286
|
+
}
|
|
287
|
+
return error;
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
}
|
package/src/built-in/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import type { LayoutProps } from './interface';
|
|
3
|
+
import { useCssVar } from '@vueuse/core';
|
|
2
4
|
import RiLogoutBoxLine from '~icons/ri/logout-box-line';
|
|
3
5
|
import { useAuth } from '@/features/composables';
|
|
4
6
|
import BackTop from './components/BackTop/index.vue';
|
|
@@ -6,31 +8,44 @@ import LayoutContent from './components/Content/index.vue';
|
|
|
6
8
|
import Copyright from './components/Copyright/index.vue';
|
|
7
9
|
import LayoutHeader from './components/Header/index.vue';
|
|
8
10
|
import NotAllowed from './components/NotAllowed/index.vue';
|
|
11
|
+
|
|
9
12
|
import SettingBar from './components/SettingBar/index.vue';
|
|
10
13
|
import LayoutSidebar from './components/Sidebar/index.vue';
|
|
11
|
-
|
|
12
14
|
import LayoutTopbar from './components/Topbar/index.vue';
|
|
13
15
|
import { useContext } from './composables/useContext';
|
|
14
16
|
import { useGetSidebarActualWidth } from './composables/useGetComputedStyle';
|
|
15
17
|
import { useHotkey } from './composables/useHotkey';
|
|
18
|
+
import { createLayoutVisible, useLayoutVisible } from './composables/useLayoutVisible';
|
|
16
19
|
import { useWatermark } from './composables/useWatermark';
|
|
17
20
|
|
|
18
21
|
defineOptions({
|
|
19
22
|
name: 'Layout',
|
|
20
23
|
});
|
|
21
24
|
|
|
22
|
-
const
|
|
23
|
-
|
|
25
|
+
const props = withDefaults(
|
|
26
|
+
defineProps<LayoutProps>(),
|
|
27
|
+
{
|
|
28
|
+
showHeader: true,
|
|
29
|
+
showSidebar: true,
|
|
30
|
+
showTabbar: true,
|
|
31
|
+
showToolbar: true,
|
|
32
|
+
},
|
|
33
|
+
);
|
|
24
34
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
});
|
|
35
|
+
createLayoutVisible(props);
|
|
36
|
+
const { show } = useLayoutVisible(props);
|
|
28
37
|
|
|
38
|
+
const routeInfo = useRoute();
|
|
39
|
+
const { auth } = useAuth();
|
|
29
40
|
const { settingsStore } = useContext();
|
|
30
41
|
const { mainSidebarActualWidth, subSidebarActualWidth } = useGetSidebarActualWidth();
|
|
31
42
|
useHotkey();
|
|
32
43
|
useWatermark();
|
|
33
44
|
|
|
45
|
+
const isAuth = computed(() => {
|
|
46
|
+
return routeInfo.matched.every(item => auth(item.meta.auth ?? ''));
|
|
47
|
+
});
|
|
48
|
+
|
|
34
49
|
watch(() => settingsStore.settings.menu.subMenuCollapse, (val) => {
|
|
35
50
|
if (settingsStore.mode === 'mobile') {
|
|
36
51
|
if (!val) {
|
|
@@ -49,6 +64,37 @@ watch(() => routeInfo.path, () => {
|
|
|
49
64
|
});
|
|
50
65
|
}
|
|
51
66
|
});
|
|
67
|
+
|
|
68
|
+
const scrollTop = ref(0);
|
|
69
|
+
const scrollOnHide = ref(false);
|
|
70
|
+
const globalTabbarHeight = useCssVar('--g-tabbar-height', document.documentElement || document.body);
|
|
71
|
+
const globalToolBarHeight = useCssVar('--g-toolbar-height', document.documentElement || document.body);
|
|
72
|
+
|
|
73
|
+
const topbarHeight = computed(() => {
|
|
74
|
+
const tabbarHeight = show.value.tabbar
|
|
75
|
+
? Number.parseInt(globalTabbarHeight.value!)
|
|
76
|
+
: 0;
|
|
77
|
+
const toolbarHeight = show.value.toolbar
|
|
78
|
+
? Number.parseInt(globalToolBarHeight.value!)
|
|
79
|
+
: 0;
|
|
80
|
+
return tabbarHeight + toolbarHeight;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
function onScroll() {
|
|
84
|
+
scrollTop.value = (document.documentElement || document.body).scrollTop;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
onMounted(() => {
|
|
88
|
+
window.addEventListener('scroll', onScroll);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
onUnmounted(() => {
|
|
92
|
+
window.removeEventListener('scroll', onScroll);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
watch(scrollTop, (val, oldVal) => {
|
|
96
|
+
scrollOnHide.value = settingsStore.settings.topbar.mode === 'sticky' && val > oldVal && val > topbarHeight.value;
|
|
97
|
+
});
|
|
52
98
|
</script>
|
|
53
99
|
|
|
54
100
|
<template>
|
|
@@ -81,9 +127,22 @@ watch(() => routeInfo.path, () => {
|
|
|
81
127
|
/>
|
|
82
128
|
|
|
83
129
|
<div class="main-container" :style="{ 'padding-bottom': routeInfo.meta.paddingBottom }">
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
130
|
+
<div
|
|
131
|
+
class="topbar-container"
|
|
132
|
+
:class="{
|
|
133
|
+
'has-tabbar': show.tabbar,
|
|
134
|
+
'has-toolbar': show.toolbar,
|
|
135
|
+
[`topbar-${settingsStore.settings.topbar.mode}`]: true,
|
|
136
|
+
'shadow': !settingsStore.settings.topbar.switchTabbarAndToolbar && scrollTop,
|
|
137
|
+
'hide': scrollOnHide,
|
|
138
|
+
'switch-tabbar-toolbar': settingsStore.settings.topbar.switchTabbarAndToolbar,
|
|
139
|
+
}"
|
|
140
|
+
data-fixed-calc-width
|
|
141
|
+
>
|
|
142
|
+
<slot name="topbar">
|
|
143
|
+
<LayoutTopbar />
|
|
144
|
+
</slot>
|
|
145
|
+
</div>
|
|
87
146
|
|
|
88
147
|
<div class="main">
|
|
89
148
|
<div
|
|
@@ -271,4 +330,29 @@ header:not(.header-leave-active) + .wrapper .main-container .topbar-container {
|
|
|
271
330
|
header:not(.header-leave-active) + .wrapper .main-container .topbar-container :deep(.tools) {
|
|
272
331
|
--at-apply: hidden;
|
|
273
332
|
}
|
|
333
|
+
|
|
334
|
+
/* 顶部栏 */
|
|
335
|
+
.topbar-container {
|
|
336
|
+
--at-apply: absolute top-0 z-100 flex flex-col;
|
|
337
|
+
box-shadow: 0 1px 0 0 var(--g-border-color);
|
|
338
|
+
transition: width 0.3s, top 0.3s, transform 0.3s, box-shadow 0.3s;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.topbar-container.topbar-fixed,
|
|
342
|
+
.topbar-container.topbar-sticky {
|
|
343
|
+
--at-apply: fixed;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.topbar-container.topbar-fixed.shadow,
|
|
347
|
+
.topbar-container.topbar-sticky.shadow {
|
|
348
|
+
box-shadow: 0 10px 10px -10px var(--g-box-shadow-color);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.topbar-container.topbar-sticky.hide {
|
|
352
|
+
top: calc((var(--g-tabbar-height) + var(--g-toolbar-height)) * -1) !important;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.topbar-container.switch-tabbar-toolbar {
|
|
356
|
+
flex-direction: column-reverse;
|
|
357
|
+
}
|
|
274
358
|
</style>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type {
|
|
2
|
+
import type { MenuData } from '@/features';
|
|
3
3
|
import { useElementSize } from '@vueuse/core';
|
|
4
4
|
import SolarWidget5BoldDuotone from '~icons/solar/widget-5-bold-duotone';
|
|
5
5
|
import { PubinfoIcon } from '@/features/components';
|
|
@@ -35,7 +35,7 @@ const moreApple = computed(() => {
|
|
|
35
35
|
return menuHideIndex.value === menuStore.allMenus.length - 1;
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
function moreApplyClick(index: number, item:
|
|
38
|
+
function moreApplyClick(index: number, item: MenuData) {
|
|
39
39
|
const menuIndex = index + menuHideIndex.value + 1;
|
|
40
40
|
switchTo(menuIndex, item);
|
|
41
41
|
}
|
|
@@ -75,6 +75,10 @@ function fitMenus() {
|
|
|
75
75
|
return restrictedIndex;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
function showMenu(item: MenuData) {
|
|
79
|
+
return (item.children && item.children.length !== 0) || item?.meta?.isDev;
|
|
80
|
+
}
|
|
81
|
+
|
|
78
82
|
onMounted(() => {
|
|
79
83
|
watch(
|
|
80
84
|
width,
|
|
@@ -101,13 +105,13 @@ onMounted(() => {
|
|
|
101
105
|
invisible
|
|
102
106
|
:class="{
|
|
103
107
|
'active': index === menuStore.actived,
|
|
104
|
-
'px-1 py-2': settingsStore.settings.menu.isRounded,
|
|
108
|
+
'px-1 py-2': showMenu(item) && settingsStore.settings.menu.isRounded,
|
|
105
109
|
'is-dev': item.meta?.isDev,
|
|
106
110
|
}"
|
|
107
111
|
>
|
|
108
112
|
<NotCursor :is-cursor="item.meta?.isDev" :dev-text="item.meta?.devText">
|
|
109
113
|
<div
|
|
110
|
-
v-if="(item
|
|
114
|
+
v-if="showMenu(item)"
|
|
111
115
|
class="group menu-item-container h-full w-full flex cursor-pointer items-center justify-between gap-1 px-5 py-4 text-[var(--g-header-menu-color)] transition-all hover:(bg-[var(--g-header-menu-hover-bg)] text-[var(--g-header-menu-hover-color)])"
|
|
112
116
|
:class="{
|
|
113
117
|
'text-[var(--g-header-menu-active-color)]! bg-[var(--g-header-menu-active-bg)]!': index === menuStore.actived,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { useLayoutVisible } from '../../composables/useLayoutVisible';
|
|
3
3
|
import Logo from '../Logo/index.vue';
|
|
4
4
|
import Tools from '../Tools/index.vue';
|
|
5
5
|
import HeaderMenu from './HeaderMenu.vue';
|
|
@@ -17,19 +17,12 @@ withDefaults(
|
|
|
17
17
|
},
|
|
18
18
|
);
|
|
19
19
|
|
|
20
|
-
const {
|
|
21
|
-
|
|
22
|
-
const route = useRoute();
|
|
23
|
-
|
|
24
|
-
// 在这里需要判断当前的菜单模式是否渲染 Header
|
|
25
|
-
const showHeader = computed<boolean>(() => {
|
|
26
|
-
return (routeStore.getRemoteAppRouteById(route)?.meta?.layout as any)?.header ?? true;
|
|
27
|
-
});
|
|
20
|
+
const { show } = useLayoutVisible();
|
|
28
21
|
</script>
|
|
29
22
|
|
|
30
23
|
<template>
|
|
31
|
-
<Transition
|
|
32
|
-
<header v-if="
|
|
24
|
+
<Transition name="header">
|
|
25
|
+
<header v-if="show.header">
|
|
33
26
|
<div class="header-container">
|
|
34
27
|
<div class="main">
|
|
35
28
|
<slot name="logo">
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { PubinfoIcon } from '@/features/components';
|
|
3
3
|
import { useContext } from '../../composables/useContext';
|
|
4
|
+
import { useLayoutVisible } from '../../composables/useLayoutVisible';
|
|
4
5
|
import { useMenu } from '../../composables/useMenu';
|
|
5
6
|
import Logo from '../Logo/index.vue';
|
|
6
7
|
import Menu from '../Menu/index.vue';
|
|
@@ -13,6 +14,7 @@ const route = useRoute();
|
|
|
13
14
|
|
|
14
15
|
const { settingsStore, menuStore, generateTitle } = useContext();
|
|
15
16
|
const { switchTo } = useMenu();
|
|
17
|
+
const { show } = useLayoutVisible();
|
|
16
18
|
|
|
17
19
|
function iconName(isActive: boolean, icon?: string, activeIcon?: string) {
|
|
18
20
|
if (isActive && activeIcon) {
|
|
@@ -25,7 +27,7 @@ function iconName(isActive: boolean, icon?: string, activeIcon?: string) {
|
|
|
25
27
|
|
|
26
28
|
<template>
|
|
27
29
|
<Transition name="main-sidebar">
|
|
28
|
-
<div v-if="
|
|
30
|
+
<div v-if="show.mainSidebar" class="main-sidebar-container">
|
|
29
31
|
<Logo :show-title="false" class="sidebar-logo" />
|
|
30
32
|
<!-- 侧边栏模式(含主导航) -->
|
|
31
33
|
<div
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useContext } from '../../composables/useContext';
|
|
3
|
+
import { useLayoutVisible } from '../../composables/useLayoutVisible';
|
|
3
4
|
import Logo from '../Logo/index.vue';
|
|
4
5
|
import Menu from '../Menu/index.vue';
|
|
5
6
|
|
|
@@ -18,37 +19,19 @@ withDefaults(
|
|
|
18
19
|
|
|
19
20
|
const route = useRoute();
|
|
20
21
|
|
|
21
|
-
const { settingsStore, menuStore
|
|
22
|
+
const { settingsStore, menuStore } = useContext();
|
|
23
|
+
const { show } = useLayoutVisible();
|
|
22
24
|
|
|
23
25
|
const sidebarScrollTop = ref(0);
|
|
24
26
|
|
|
25
27
|
function onSidebarScroll(e: Event) {
|
|
26
28
|
sidebarScrollTop.value = (e.target as HTMLElement).scrollTop;
|
|
27
29
|
}
|
|
28
|
-
|
|
29
|
-
const enableSidebar = computed(() => {
|
|
30
|
-
return settingsStore.mode === 'mobile' || (
|
|
31
|
-
['side', 'head', 'single'].includes(settingsStore.settings.menu.menuMode)
|
|
32
|
-
&& menuStore.sidebarMenus.length !== 0
|
|
33
|
-
&& !(
|
|
34
|
-
settingsStore.settings.menu.subMenuOnlyOneHide
|
|
35
|
-
&& menuStore.sidebarMenus.length === 1
|
|
36
|
-
&& (
|
|
37
|
-
!menuStore.sidebarMenus[0].children
|
|
38
|
-
|| menuStore.sidebarMenus[0]?.children.every(item => item.meta?.sidebar === false)
|
|
39
|
-
)
|
|
40
|
-
)
|
|
41
|
-
);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const showSidebar = computed<boolean>(() => {
|
|
45
|
-
return (routeStore.getRemoteAppRouteById(route)?.meta?.layout as any)?.sidebar ?? true;
|
|
46
|
-
});
|
|
47
30
|
</script>
|
|
48
31
|
|
|
49
32
|
<template>
|
|
50
33
|
<div
|
|
51
|
-
v-if="
|
|
34
|
+
v-if="show.subSidebar"
|
|
52
35
|
class="sub-sidebar-container"
|
|
53
36
|
:class="{
|
|
54
37
|
'is-collapse': settingsStore.mode === 'pc' && settingsStore.settings.menu.subMenuCollapse,
|