xto-fronted 0.8.4 → 0.9.1
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-DSEQpjnn.js → index-5-8RNGND.js} +1266 -1227
- package/dist/{index-BIRzoxFQ.js → index-BYChodrz.js} +1 -1
- package/dist/{index-D2ArmTvR.js → index-C4x3PSxY.js} +1 -1
- package/dist/{index-HqCkg98V.js → index-CsQthEv2.js} +1 -1
- package/dist/{index-DaSr411h.js → index-cytRUeAK.js} +1 -1
- 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 +70 -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,36 @@ 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
|
+
// 重置时需要清掉的用户偏好键(isCollapsed 属个人习惯,不随默认值重置)
|
|
28
|
+
const PREFERENCE_KEYS = ['layout', 'primaryColor', 'theme', 'isDark', 'showTabs', 'showBreadcrumb', 'showFooter'] as const
|
|
29
|
+
|
|
30
|
+
// 注意:服务端界面配置必须在 store setup 内读取(getServerUiConfig())。
|
|
31
|
+
// 本模块经 index.ts 静态导出,若在模块顶层取值会在 createXtoApp 拉取配置前固化成空对象。
|
|
32
|
+
|
|
20
33
|
export const useAppStore = defineStore('app', () => {
|
|
21
|
-
//
|
|
34
|
+
// 服务端按应用下发的默认值(在首次 useAppStore() 时读取,此时 createXtoApp 已完成配置拉取)
|
|
35
|
+
const serverUi = getServerUiConfig()
|
|
36
|
+
const serverFeatures = serverUi.features || {}
|
|
37
|
+
|
|
38
|
+
// 状态(优先级:用户本地偏好 > 服务端按应用默认 > 内置默认)
|
|
22
39
|
const appName = ref<string>(local.get<string>('appName') || 'XTO App')
|
|
23
40
|
const indexPath = ref<string>(local.get<string>('indexPath') || '/dashboard')
|
|
24
41
|
const loginPath = ref<string>(local.get<string>('loginPath') || '/login')
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const layout = ref<LayoutMode>(
|
|
42
|
+
const theme = ref<ThemeMode>(readUserPreference<ThemeMode>('theme') ?? serverUi.theme ?? 'light')
|
|
43
|
+
const isDark = ref<boolean>(theme.value === 'dark')
|
|
44
|
+
const layout = ref<LayoutMode>(readUserPreference<LayoutMode>('layout') ?? serverUi.layout ?? 'sidebar')
|
|
28
45
|
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>(
|
|
46
|
+
const showTabs = ref<boolean>(readUserPreference<boolean>('showTabs') ?? serverFeatures.showTabs ?? true)
|
|
47
|
+
const showFooter = ref<boolean>(readUserPreference<boolean>('showFooter') ?? serverFeatures.showFooter ?? true)
|
|
48
|
+
const showBreadcrumb = ref<boolean>(readUserPreference<boolean>('showBreadcrumb') ?? serverFeatures.showBreadcrumb ?? true)
|
|
49
|
+
const primaryColor = ref<string>(readUserPreference<string>('primaryColor') ?? serverUi.primaryColor ?? '#4F46E5')
|
|
50
|
+
const allowUserOverride = ref<boolean>(serverUi.allowUserOverride !== false)
|
|
33
51
|
const cachedViews = ref<string[]>([])
|
|
34
52
|
const mixSubMenus = ref<any[]>([])
|
|
35
53
|
|
|
@@ -54,28 +72,33 @@ export const useAppStore = defineStore('app', () => {
|
|
|
54
72
|
local.set('loginPath', path)
|
|
55
73
|
}
|
|
56
74
|
|
|
57
|
-
//
|
|
75
|
+
// 应用深浅主题到 DOM(不写 localStorage,供初始化与重置使用)
|
|
76
|
+
const applyThemeClass = () => {
|
|
77
|
+
const html = document.documentElement
|
|
78
|
+
if (isDark.value) {
|
|
79
|
+
html.classList.add('dark')
|
|
80
|
+
} else {
|
|
81
|
+
html.classList.remove('dark')
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 切换主题(用户操作,持久化)
|
|
58
86
|
const toggleTheme = () => {
|
|
59
87
|
isDark.value = !isDark.value
|
|
60
88
|
theme.value = isDark.value ? 'dark' : 'light'
|
|
61
89
|
updateTheme()
|
|
62
90
|
}
|
|
63
91
|
|
|
64
|
-
//
|
|
92
|
+
// 设置主题(用户操作,持久化)
|
|
65
93
|
const setTheme = (mode: ThemeMode) => {
|
|
66
94
|
theme.value = mode
|
|
67
95
|
isDark.value = mode === 'dark'
|
|
68
96
|
updateTheme()
|
|
69
97
|
}
|
|
70
98
|
|
|
71
|
-
//
|
|
99
|
+
// 更新主题(用户操作路径:应用 DOM + 持久化)
|
|
72
100
|
const updateTheme = () => {
|
|
73
|
-
|
|
74
|
-
if (isDark.value) {
|
|
75
|
-
html.classList.add('dark')
|
|
76
|
-
} else {
|
|
77
|
-
html.classList.remove('dark')
|
|
78
|
-
}
|
|
101
|
+
applyThemeClass()
|
|
79
102
|
local.set('isDark', isDark.value)
|
|
80
103
|
local.set('theme', theme.value)
|
|
81
104
|
}
|
|
@@ -110,9 +133,8 @@ export const useAppStore = defineStore('app', () => {
|
|
|
110
133
|
local.set('showBreadcrumb', showBreadcrumb.value)
|
|
111
134
|
}
|
|
112
135
|
|
|
113
|
-
//
|
|
114
|
-
const
|
|
115
|
-
primaryColor.value = color
|
|
136
|
+
// 主题色 CSS 变量应用(含 light-1..9 / dark-1..3 色阶;不写 localStorage,供初始化与重置使用)
|
|
137
|
+
const applyPrimaryColorVars = (color: string) => {
|
|
116
138
|
const root = document.documentElement
|
|
117
139
|
root.style.setProperty('--xto-color-primary', color)
|
|
118
140
|
const rgb = hexToRgb(color)
|
|
@@ -130,9 +152,31 @@ export const useAppStore = defineStore('app', () => {
|
|
|
130
152
|
root.style.setProperty('--xto-color-primary-dark-3', mix(0.45, false))
|
|
131
153
|
root.style.setProperty('--xto-color-ring', color)
|
|
132
154
|
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 设置主题色(用户操作,持久化)
|
|
158
|
+
const setPrimaryColor = (color: string) => {
|
|
159
|
+
primaryColor.value = color
|
|
160
|
+
applyPrimaryColorVars(color)
|
|
133
161
|
local.set('primaryColor', color)
|
|
134
162
|
}
|
|
135
163
|
|
|
164
|
+
// 恢复默认:清掉本地偏好,回到服务端按应用配置(无配置则回内置默认)
|
|
165
|
+
const resetToServerDefaults = () => {
|
|
166
|
+
PREFERENCE_KEYS.forEach((key) => local.remove(key))
|
|
167
|
+
const ui = getServerUiConfig()
|
|
168
|
+
const features = ui.features || {}
|
|
169
|
+
layout.value = ui.layout ?? 'sidebar'
|
|
170
|
+
primaryColor.value = ui.primaryColor ?? '#4F46E5'
|
|
171
|
+
theme.value = ui.theme ?? 'light'
|
|
172
|
+
isDark.value = theme.value === 'dark'
|
|
173
|
+
showTabs.value = features.showTabs ?? true
|
|
174
|
+
showFooter.value = features.showFooter ?? true
|
|
175
|
+
showBreadcrumb.value = features.showBreadcrumb ?? true
|
|
176
|
+
applyThemeClass()
|
|
177
|
+
applyPrimaryColorVars(primaryColor.value)
|
|
178
|
+
}
|
|
179
|
+
|
|
136
180
|
// 添加缓存页面
|
|
137
181
|
const addCachedView = (name: string) => {
|
|
138
182
|
if (!cachedViews.value.includes(name)) {
|
|
@@ -158,16 +202,14 @@ export const useAppStore = defineStore('app', () => {
|
|
|
158
202
|
mixSubMenus.value = menus
|
|
159
203
|
}
|
|
160
204
|
|
|
161
|
-
//
|
|
205
|
+
// 初始化主题:只应用运行时效果,不持久化(避免把服务端默认值固化成用户偏好)
|
|
162
206
|
const initTheme = () => {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
document.documentElement.style.setProperty('--xto-color-primary', primaryColor.value)
|
|
166
|
-
}
|
|
207
|
+
applyThemeClass()
|
|
208
|
+
applyPrimaryColorVars(primaryColor.value)
|
|
167
209
|
}
|
|
168
210
|
|
|
169
211
|
// 监听主题变化
|
|
170
|
-
watch(isDark,
|
|
212
|
+
watch(isDark, applyThemeClass)
|
|
171
213
|
|
|
172
214
|
return {
|
|
173
215
|
appName,
|
|
@@ -181,6 +223,7 @@ export const useAppStore = defineStore('app', () => {
|
|
|
181
223
|
showFooter,
|
|
182
224
|
showBreadcrumb,
|
|
183
225
|
primaryColor,
|
|
226
|
+
allowUserOverride,
|
|
184
227
|
cachedViews,
|
|
185
228
|
mixSubMenus,
|
|
186
229
|
themeClass,
|
|
@@ -195,6 +238,7 @@ export const useAppStore = defineStore('app', () => {
|
|
|
195
238
|
toggleFooter,
|
|
196
239
|
toggleBreadcrumb,
|
|
197
240
|
setPrimaryColor,
|
|
241
|
+
resetToServerDefaults,
|
|
198
242
|
addCachedView,
|
|
199
243
|
removeCachedView,
|
|
200
244
|
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
|
}
|