xto-fronted 0.8.4 → 0.9.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/api/index.js +20 -19
- package/dist/api/system.d.ts +1 -0
- package/dist/{index-HqCkg98V.js → index-BL_15V_t.js} +1 -1
- package/dist/{index-BIRzoxFQ.js → index-BTlDtwrl.js} +1 -1
- package/dist/{index-DaSr411h.js → index-Biperpfs.js} +1 -1
- package/dist/{index-D2ArmTvR.js → index-Bv0RBhQX.js} +1 -1
- package/dist/{index-DSEQpjnn.js → index-C4XoKbUh.js} +1182 -1145
- package/dist/index.js +63 -60
- package/dist/stores/app.d.ts +8 -2
- package/dist/style.css +1 -1
- package/dist/{user-BfEOXV7Q.js → user-B0q24FmO.js} +465 -451
- package/dist/utils/config.d.ts +19 -0
- package/package.json +1 -1
- package/src/api/system.ts +6 -0
- package/src/components/Layout/Header.vue +35 -2
- package/src/index.ts +19 -1
- package/src/stores/app.ts +65 -26
- package/src/utils/config.ts +32 -0
package/dist/utils/config.d.ts
CHANGED
|
@@ -54,3 +54,22 @@ export declare const appConfig: {
|
|
|
54
54
|
apiBaseUrl: import('vue').Ref<string, string>;
|
|
55
55
|
basePath: import('vue').Ref<string, string>;
|
|
56
56
|
};
|
|
57
|
+
export interface AppUiFeatures {
|
|
58
|
+
showTabs?: boolean;
|
|
59
|
+
showBreadcrumb?: boolean;
|
|
60
|
+
showFooter?: boolean;
|
|
61
|
+
}
|
|
62
|
+
export interface AppUiConfig {
|
|
63
|
+
/** 默认布局模式 */
|
|
64
|
+
layout?: 'sidebar' | 'top' | 'mix';
|
|
65
|
+
/** 默认主题色 */
|
|
66
|
+
primaryColor?: string;
|
|
67
|
+
/** 默认深浅主题 */
|
|
68
|
+
theme?: 'light' | 'dark';
|
|
69
|
+
/** 默认功能开关 */
|
|
70
|
+
features?: AppUiFeatures;
|
|
71
|
+
/** 是否允许用户在界面右上角自行修改(false 时锁定设置入口) */
|
|
72
|
+
allowUserOverride?: boolean;
|
|
73
|
+
}
|
|
74
|
+
export declare function setServerUiConfig(config: AppUiConfig): void;
|
|
75
|
+
export declare function getServerUiConfig(): AppUiConfig;
|
package/package.json
CHANGED
package/src/api/system.ts
CHANGED
|
@@ -51,6 +51,12 @@ export function getMenuTree(appId?: string) {
|
|
|
51
51
|
return http.get<MenuItem[]>(`/user/v1.0/menu/get-menu?appId=${id}`)
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
// 获取应用界面配置(免登录:布局模式/主题色/功能开关的按应用默认值)
|
|
55
|
+
export function getAppUiConfig(appId?: string) {
|
|
56
|
+
const id = appId || getAppId()
|
|
57
|
+
return http.get<Record<string, any>>('/user/v1.0/app/ui-config', { params: { appId: id } })
|
|
58
|
+
}
|
|
59
|
+
|
|
54
60
|
// 创建菜单
|
|
55
61
|
export function createMenu(data: Partial<MenuItem>) {
|
|
56
62
|
return http.post<MenuItem>('/menu', data)
|
|
@@ -175,6 +175,12 @@ const handleDarkModeToggle = () => {
|
|
|
175
175
|
drawerVisible.value = false
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
// 恢复默认(清个人偏好,回到服务端按应用配置的默认值)
|
|
179
|
+
const handleResetDefaults = () => {
|
|
180
|
+
appStore.resetToServerDefaults()
|
|
181
|
+
drawerVisible.value = false
|
|
182
|
+
}
|
|
183
|
+
|
|
178
184
|
// 切换全屏
|
|
179
185
|
const toggleFullscreen = () => {
|
|
180
186
|
if (!document.fullscreenElement) {
|
|
@@ -334,8 +340,8 @@ onUnmounted(() => {
|
|
|
334
340
|
<Icon :name="isFullscreen ? 'fullscreen-exit' : 'fullscreen'" :size="16" />
|
|
335
341
|
</div>
|
|
336
342
|
|
|
337
|
-
<!--
|
|
338
|
-
<div class="header__action" @click="openSettingsDrawer" title="换肤设置">
|
|
343
|
+
<!-- 换肤设置(allowUserOverride=false 时由应用管理员锁定) -->
|
|
344
|
+
<div v-if="appStore.allowUserOverride" class="header__action" @click="openSettingsDrawer" title="换肤设置">
|
|
339
345
|
<Icon name="skin" :size="16" />
|
|
340
346
|
</div>
|
|
341
347
|
|
|
@@ -467,6 +473,14 @@ onUnmounted(() => {
|
|
|
467
473
|
</div>
|
|
468
474
|
</div>
|
|
469
475
|
</div>
|
|
476
|
+
|
|
477
|
+
<!-- 恢复默认:清掉个人偏好,回到管理员在用户中心配置的界面默认值 -->
|
|
478
|
+
<div class="settings-section">
|
|
479
|
+
<div class="settings-reset" @click="handleResetDefaults">
|
|
480
|
+
<Icon name="refresh" :size="14" />
|
|
481
|
+
<span>恢复默认</span>
|
|
482
|
+
</div>
|
|
483
|
+
</div>
|
|
470
484
|
</div>
|
|
471
485
|
</Drawer>
|
|
472
486
|
</div>
|
|
@@ -985,6 +999,25 @@ onUnmounted(() => {
|
|
|
985
999
|
}
|
|
986
1000
|
}
|
|
987
1001
|
}
|
|
1002
|
+
|
|
1003
|
+
.settings-reset {
|
|
1004
|
+
display: flex;
|
|
1005
|
+
align-items: center;
|
|
1006
|
+
justify-content: center;
|
|
1007
|
+
gap: 6px;
|
|
1008
|
+
height: 34px;
|
|
1009
|
+
border: 1px solid var(--xto-border-color, #D9DCE1);
|
|
1010
|
+
border-radius: 6px;
|
|
1011
|
+
font-size: 13px;
|
|
1012
|
+
color: var(--xto-color-text-regular, #4E5566);
|
|
1013
|
+
cursor: pointer;
|
|
1014
|
+
transition: all 0.2s;
|
|
1015
|
+
|
|
1016
|
+
&:hover {
|
|
1017
|
+
color: var(--xto-color-primary, #4F46E5);
|
|
1018
|
+
border-color: var(--xto-color-primary, #4F46E5);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
988
1021
|
}
|
|
989
1022
|
|
|
990
1023
|
// 下拉动画
|
package/src/index.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import './style.scss'
|
|
3
3
|
|
|
4
4
|
// 配置
|
|
5
|
-
import { initAppConfig, getAppId, getClientId, getApiBaseUrl, loadRuntimeConfig } from './utils/config'
|
|
5
|
+
import { initAppConfig, getAppId, getClientId, getApiBaseUrl, loadRuntimeConfig, setServerUiConfig, type AppUiConfig } from './utils/config'
|
|
6
6
|
import { setBasePath, setApiBaseUrl } from './utils/request'
|
|
7
|
+
import { getAppUiConfig } from './api/system'
|
|
7
8
|
import type { LocaleCode } from '@xto/core/locale'
|
|
8
9
|
|
|
9
10
|
// XtoConfig 类型定义
|
|
@@ -48,6 +49,23 @@ export async function createXtoApp(config: Partial<XtoConfig>) {
|
|
|
48
49
|
setBasePath(config.basePath)
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
// 拉取服务端按应用配置的界面默认值(免登录接口,xto-admin「界面设置」维护)
|
|
53
|
+
// 必须在 store 首次实例化前完成,失败/超时静默降级为内置默认
|
|
54
|
+
const resolvedAppId = config.appId || getAppId()
|
|
55
|
+
if (resolvedAppId) {
|
|
56
|
+
try {
|
|
57
|
+
const uiConfig = await Promise.race([
|
|
58
|
+
getAppUiConfig(resolvedAppId),
|
|
59
|
+
new Promise<null>((resolve) => setTimeout(() => resolve(null), 2000))
|
|
60
|
+
])
|
|
61
|
+
if (uiConfig && typeof uiConfig === 'object') {
|
|
62
|
+
setServerUiConfig(uiConfig as AppUiConfig)
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.warn('加载应用界面配置失败,使用内置默认', error)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
51
69
|
// 设置应用名称和路径到 store
|
|
52
70
|
import('./stores/app').then(({ useAppStore }) => {
|
|
53
71
|
const appStore = useAppStore()
|
package/src/stores/app.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { defineStore } from 'pinia'
|
|
6
6
|
import { ref, computed, watch } from 'vue'
|
|
7
7
|
import { local } from '@/utils/storage'
|
|
8
|
+
import { getServerUiConfig } from '@/utils/config'
|
|
8
9
|
|
|
9
10
|
export type ThemeMode = 'light' | 'dark'
|
|
10
11
|
export type LayoutMode = 'sidebar' | 'top' | 'mix'
|
|
@@ -17,19 +18,33 @@ function hexToRgb(hex: string): [number, number, number] | null {
|
|
|
17
18
|
return [(n >> 16) & 255, (n >> 8) & 255, n & 255]
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
/** 读取用户显式保存过的偏好;未设置过返回 undefined(与「服务端默认」区分开) */
|
|
22
|
+
function readUserPreference<T>(key: string): T | undefined {
|
|
23
|
+
const value = local.get<T>(key)
|
|
24
|
+
return value === null || value === ('' as unknown as T) ? undefined : value
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 服务端按应用下发的默认值(xto-admin 界面配置),仅在用户未显式设置时生效
|
|
28
|
+
const serverUi = getServerUiConfig()
|
|
29
|
+
const serverFeatures = serverUi.features || {}
|
|
30
|
+
|
|
31
|
+
// 重置时需要清掉的用户偏好键(isCollapsed 属个人习惯,不随默认值重置)
|
|
32
|
+
const PREFERENCE_KEYS = ['layout', 'primaryColor', 'theme', 'isDark', 'showTabs', 'showBreadcrumb', 'showFooter'] as const
|
|
33
|
+
|
|
20
34
|
export const useAppStore = defineStore('app', () => {
|
|
21
|
-
//
|
|
35
|
+
// 状态(优先级:用户本地偏好 > 服务端按应用默认 > 内置默认)
|
|
22
36
|
const appName = ref<string>(local.get<string>('appName') || 'XTO App')
|
|
23
37
|
const indexPath = ref<string>(local.get<string>('indexPath') || '/dashboard')
|
|
24
38
|
const loginPath = ref<string>(local.get<string>('loginPath') || '/login')
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const layout = ref<LayoutMode>(
|
|
39
|
+
const theme = ref<ThemeMode>(readUserPreference<ThemeMode>('theme') ?? serverUi.theme ?? 'light')
|
|
40
|
+
const isDark = ref<boolean>(theme.value === 'dark')
|
|
41
|
+
const layout = ref<LayoutMode>(readUserPreference<LayoutMode>('layout') ?? serverUi.layout ?? 'sidebar')
|
|
28
42
|
const isCollapsed = ref<boolean>(local.get<boolean>('isCollapsed') || false)
|
|
29
|
-
const showTabs = ref<boolean>(
|
|
30
|
-
const showFooter = ref<boolean>(
|
|
31
|
-
const showBreadcrumb = ref<boolean>(
|
|
32
|
-
const primaryColor = ref<string>(
|
|
43
|
+
const showTabs = ref<boolean>(readUserPreference<boolean>('showTabs') ?? serverFeatures.showTabs ?? true)
|
|
44
|
+
const showFooter = ref<boolean>(readUserPreference<boolean>('showFooter') ?? serverFeatures.showFooter ?? true)
|
|
45
|
+
const showBreadcrumb = ref<boolean>(readUserPreference<boolean>('showBreadcrumb') ?? serverFeatures.showBreadcrumb ?? true)
|
|
46
|
+
const primaryColor = ref<string>(readUserPreference<string>('primaryColor') ?? serverUi.primaryColor ?? '#4F46E5')
|
|
47
|
+
const allowUserOverride = ref<boolean>(serverUi.allowUserOverride !== false)
|
|
33
48
|
const cachedViews = ref<string[]>([])
|
|
34
49
|
const mixSubMenus = ref<any[]>([])
|
|
35
50
|
|
|
@@ -54,28 +69,33 @@ export const useAppStore = defineStore('app', () => {
|
|
|
54
69
|
local.set('loginPath', path)
|
|
55
70
|
}
|
|
56
71
|
|
|
57
|
-
//
|
|
72
|
+
// 应用深浅主题到 DOM(不写 localStorage,供初始化与重置使用)
|
|
73
|
+
const applyThemeClass = () => {
|
|
74
|
+
const html = document.documentElement
|
|
75
|
+
if (isDark.value) {
|
|
76
|
+
html.classList.add('dark')
|
|
77
|
+
} else {
|
|
78
|
+
html.classList.remove('dark')
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 切换主题(用户操作,持久化)
|
|
58
83
|
const toggleTheme = () => {
|
|
59
84
|
isDark.value = !isDark.value
|
|
60
85
|
theme.value = isDark.value ? 'dark' : 'light'
|
|
61
86
|
updateTheme()
|
|
62
87
|
}
|
|
63
88
|
|
|
64
|
-
//
|
|
89
|
+
// 设置主题(用户操作,持久化)
|
|
65
90
|
const setTheme = (mode: ThemeMode) => {
|
|
66
91
|
theme.value = mode
|
|
67
92
|
isDark.value = mode === 'dark'
|
|
68
93
|
updateTheme()
|
|
69
94
|
}
|
|
70
95
|
|
|
71
|
-
//
|
|
96
|
+
// 更新主题(用户操作路径:应用 DOM + 持久化)
|
|
72
97
|
const updateTheme = () => {
|
|
73
|
-
|
|
74
|
-
if (isDark.value) {
|
|
75
|
-
html.classList.add('dark')
|
|
76
|
-
} else {
|
|
77
|
-
html.classList.remove('dark')
|
|
78
|
-
}
|
|
98
|
+
applyThemeClass()
|
|
79
99
|
local.set('isDark', isDark.value)
|
|
80
100
|
local.set('theme', theme.value)
|
|
81
101
|
}
|
|
@@ -110,9 +130,8 @@ export const useAppStore = defineStore('app', () => {
|
|
|
110
130
|
local.set('showBreadcrumb', showBreadcrumb.value)
|
|
111
131
|
}
|
|
112
132
|
|
|
113
|
-
//
|
|
114
|
-
const
|
|
115
|
-
primaryColor.value = color
|
|
133
|
+
// 主题色 CSS 变量应用(含 light-1..9 / dark-1..3 色阶;不写 localStorage,供初始化与重置使用)
|
|
134
|
+
const applyPrimaryColorVars = (color: string) => {
|
|
116
135
|
const root = document.documentElement
|
|
117
136
|
root.style.setProperty('--xto-color-primary', color)
|
|
118
137
|
const rgb = hexToRgb(color)
|
|
@@ -130,9 +149,29 @@ export const useAppStore = defineStore('app', () => {
|
|
|
130
149
|
root.style.setProperty('--xto-color-primary-dark-3', mix(0.45, false))
|
|
131
150
|
root.style.setProperty('--xto-color-ring', color)
|
|
132
151
|
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 设置主题色(用户操作,持久化)
|
|
155
|
+
const setPrimaryColor = (color: string) => {
|
|
156
|
+
primaryColor.value = color
|
|
157
|
+
applyPrimaryColorVars(color)
|
|
133
158
|
local.set('primaryColor', color)
|
|
134
159
|
}
|
|
135
160
|
|
|
161
|
+
// 恢复默认:清掉本地偏好,回到服务端按应用配置(无配置则回内置默认)
|
|
162
|
+
const resetToServerDefaults = () => {
|
|
163
|
+
PREFERENCE_KEYS.forEach((key) => local.remove(key))
|
|
164
|
+
layout.value = serverUi.layout ?? 'sidebar'
|
|
165
|
+
primaryColor.value = serverUi.primaryColor ?? '#4F46E5'
|
|
166
|
+
theme.value = serverUi.theme ?? 'light'
|
|
167
|
+
isDark.value = theme.value === 'dark'
|
|
168
|
+
showTabs.value = serverFeatures.showTabs ?? true
|
|
169
|
+
showFooter.value = serverFeatures.showFooter ?? true
|
|
170
|
+
showBreadcrumb.value = serverFeatures.showBreadcrumb ?? true
|
|
171
|
+
applyThemeClass()
|
|
172
|
+
applyPrimaryColorVars(primaryColor.value)
|
|
173
|
+
}
|
|
174
|
+
|
|
136
175
|
// 添加缓存页面
|
|
137
176
|
const addCachedView = (name: string) => {
|
|
138
177
|
if (!cachedViews.value.includes(name)) {
|
|
@@ -158,16 +197,14 @@ export const useAppStore = defineStore('app', () => {
|
|
|
158
197
|
mixSubMenus.value = menus
|
|
159
198
|
}
|
|
160
199
|
|
|
161
|
-
//
|
|
200
|
+
// 初始化主题:只应用运行时效果,不持久化(避免把服务端默认值固化成用户偏好)
|
|
162
201
|
const initTheme = () => {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
document.documentElement.style.setProperty('--xto-color-primary', primaryColor.value)
|
|
166
|
-
}
|
|
202
|
+
applyThemeClass()
|
|
203
|
+
applyPrimaryColorVars(primaryColor.value)
|
|
167
204
|
}
|
|
168
205
|
|
|
169
206
|
// 监听主题变化
|
|
170
|
-
watch(isDark,
|
|
207
|
+
watch(isDark, applyThemeClass)
|
|
171
208
|
|
|
172
209
|
return {
|
|
173
210
|
appName,
|
|
@@ -181,6 +218,7 @@ export const useAppStore = defineStore('app', () => {
|
|
|
181
218
|
showFooter,
|
|
182
219
|
showBreadcrumb,
|
|
183
220
|
primaryColor,
|
|
221
|
+
allowUserOverride,
|
|
184
222
|
cachedViews,
|
|
185
223
|
mixSubMenus,
|
|
186
224
|
themeClass,
|
|
@@ -195,6 +233,7 @@ export const useAppStore = defineStore('app', () => {
|
|
|
195
233
|
toggleFooter,
|
|
196
234
|
toggleBreadcrumb,
|
|
197
235
|
setPrimaryColor,
|
|
236
|
+
resetToServerDefaults,
|
|
198
237
|
addCachedView,
|
|
199
238
|
removeCachedView,
|
|
200
239
|
clearCachedViews,
|
package/src/utils/config.ts
CHANGED
|
@@ -168,4 +168,36 @@ export const appConfig = {
|
|
|
168
168
|
clientId,
|
|
169
169
|
apiBaseUrl,
|
|
170
170
|
basePath
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ==================== 应用界面配置(服务端按应用下发的默认值) ====================
|
|
174
|
+
|
|
175
|
+
export interface AppUiFeatures {
|
|
176
|
+
showTabs?: boolean
|
|
177
|
+
showBreadcrumb?: boolean
|
|
178
|
+
showFooter?: boolean
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface AppUiConfig {
|
|
182
|
+
/** 默认布局模式 */
|
|
183
|
+
layout?: 'sidebar' | 'top' | 'mix'
|
|
184
|
+
/** 默认主题色 */
|
|
185
|
+
primaryColor?: string
|
|
186
|
+
/** 默认深浅主题 */
|
|
187
|
+
theme?: 'light' | 'dark'
|
|
188
|
+
/** 默认功能开关 */
|
|
189
|
+
features?: AppUiFeatures
|
|
190
|
+
/** 是否允许用户在界面右上角自行修改(false 时锁定设置入口) */
|
|
191
|
+
allowUserOverride?: boolean
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// 模块级保存,app store 首次实例化时读取(早于任何组件渲染,无闪烁)
|
|
195
|
+
let serverUiConfig: AppUiConfig = {}
|
|
196
|
+
|
|
197
|
+
export function setServerUiConfig(config: AppUiConfig): void {
|
|
198
|
+
serverUiConfig = config || {}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function getServerUiConfig(): AppUiConfig {
|
|
202
|
+
return serverUiConfig
|
|
171
203
|
}
|