@jiangood/open-admin 3.1.6 → 3.1.8
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/package.json +2 -5
- package/src/framework/biz/PermActions.tsx +1 -1
- package/src/framework/biz/UserPicker.less +17 -0
- package/src/framework/biz/UserPicker.tsx +296 -0
- package/src/framework/biz/index.tsx +2 -0
- package/src/framework/biz/pickerUser.ts +19 -0
- package/src/framework/components/ContextMenu/index.tsx +11 -8
- package/src/framework/components/DownloadModal/index.tsx +7 -7
- package/src/framework/components/ErrorBoundary/index.tsx +1 -1
- package/src/framework/components/FormModal/index.tsx +4 -4
- package/src/framework/components/NamedIcon/index.tsx +1 -1
- package/src/framework/components/OrgSwitcher/index.tsx +1 -1
- package/src/framework/components/Page/index.tsx +18 -4
- package/src/framework/components/PageLoading/index.tsx +2 -2
- package/src/framework/components/ProTable/index.less +70 -8
- package/src/framework/components/ProTable/index.tsx +63 -16
- package/src/framework/components/ThemeSettings/index.tsx +173 -0
- package/src/framework/components/index.ts +1 -0
- package/src/framework/config/index.ts +44 -2
- package/src/framework/config/layoutConfig.ts +52 -0
- package/src/framework/config/themeConfig.ts +449 -14
- package/src/framework/fields/BaseRemoteSelect/index.tsx +4 -4
- package/src/framework/fields/FieldDateRange/index.tsx +1 -1
- package/src/framework/fields/FieldDictSelect/index.tsx +1 -1
- package/src/framework/fields/FieldEditor/index.tsx +2 -2
- package/src/framework/fields/FieldNumberRange/index.tsx +1 -1
- package/src/framework/fields/FieldUploadFile/index.tsx +3 -3
- package/src/framework/fields/FieldUploadImage/index.tsx +19 -19
- package/src/framework/fields/FieldUserPicker/index.tsx +124 -0
- package/src/framework/fields/index.ts +1 -0
- package/src/framework/utils/HttpClient.ts +15 -4
- package/src/framework/utils/MessageApi.ts +16 -3
- package/src/framework/utils/UrlUtils.ts +1 -1
- package/src/framework/views/ViewText/index.tsx +1 -1
- package/src/layouts/admin/AdminTabs.less +11 -11
- package/src/layouts/admin/HeaderRight.jsx +23 -12
- package/src/layouts/admin/SinglePageLayout.jsx +36 -0
- package/src/layouts/admin/TabLayout.jsx +21 -1
- package/src/layouts/admin/index.jsx +46 -26
- package/src/layouts/admin/index.less +68 -24
- package/src/layouts/index.tsx +29 -4
- package/src/pages/article/$code.jsx +2 -2
- package/src/pages/article/article.less +13 -13
- package/src/pages/index.jsx +1 -1
- package/src/pages/public/login.jsx +29 -15
- package/src/pages/public/login.less +243 -22
- package/src/pages/standalone/forceUpdatePwd.jsx +8 -7
- package/src/pages/system/article/index.jsx +13 -15
- package/src/pages/system/codegen/index.jsx +54 -51
- package/src/pages/system/dict/index.jsx +2 -2
- package/src/pages/system/file/index.jsx +3 -3
- package/src/pages/system/job/index.jsx +57 -20
- package/src/pages/system/log/index.jsx +2 -2
- package/src/pages/system/org/index.jsx +5 -5
- package/src/pages/system/role/index.jsx +28 -55
- package/src/pages/system/role/rolePerm.jsx +1 -1
- package/src/pages/system/user/index.jsx +7 -11
- package/src/pages/system/user/userPerm.jsx +5 -5
- package/src/pages/userCenter/permView.jsx +2 -2
- package/src/style/global.less +6 -6
|
@@ -1,49 +1,401 @@
|
|
|
1
1
|
import { theme } from 'antd';
|
|
2
2
|
import type { ThemeConfig } from 'antd';
|
|
3
|
+
import { EventBus } from '../utils/EventBus';
|
|
3
4
|
|
|
4
5
|
const { getDesignToken } = theme;
|
|
5
6
|
|
|
7
|
+
export type ThemeMode = 'light' | 'dark';
|
|
8
|
+
|
|
6
9
|
export interface ThemeColors {
|
|
7
10
|
colorPrimary?: string;
|
|
8
11
|
colorSuccess?: string;
|
|
9
12
|
colorWarning?: string;
|
|
10
13
|
colorError?: string;
|
|
14
|
+
colorInfo?: string;
|
|
11
15
|
colorBgLayout?: string;
|
|
16
|
+
/** 侧栏底色(深色系);不设置时使用 antd 默认值 */
|
|
17
|
+
siderBg?: string;
|
|
18
|
+
/** 侧栏子菜单底色;缺省与 siderBg 相同 */
|
|
19
|
+
siderSubBg?: string;
|
|
20
|
+
/** 侧栏菜单项悬停底色;缺省 antd 默认(transparent) */
|
|
21
|
+
siderHoverBg?: string;
|
|
22
|
+
/** 侧栏折叠触发条底色;不设置时使用 antd 默认值 */
|
|
23
|
+
siderTriggerBg?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ThemePreset {
|
|
27
|
+
key: string;
|
|
28
|
+
name: string;
|
|
29
|
+
colors: ThemeColors;
|
|
12
30
|
}
|
|
13
31
|
|
|
14
|
-
export
|
|
15
|
-
|
|
32
|
+
export type ThemeDensity = 'default' | 'compact';
|
|
33
|
+
export type ThemeRadius = 'sharp' | 'default' | 'round';
|
|
34
|
+
|
|
35
|
+
export interface ThemeDensityPreset {
|
|
36
|
+
key: ThemeDensity;
|
|
37
|
+
name: string;
|
|
38
|
+
/** 是否叠加 antd 紧凑算法 */
|
|
39
|
+
compact?: boolean;
|
|
40
|
+
/** 追加的 Seed/Map Token 覆盖 */
|
|
41
|
+
token?: Record<string, unknown>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ThemeRadiusPreset {
|
|
45
|
+
key: ThemeRadius;
|
|
46
|
+
name: string;
|
|
47
|
+
token: Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 框架默认颜色,回归 antd 原生主题(不再覆盖主色) */
|
|
51
|
+
export const DEFAULT_COLORS: ThemeColors = {
|
|
52
|
+
colorPrimary: '#1677ff',
|
|
16
53
|
colorSuccess: '#52c41a',
|
|
17
54
|
colorWarning: '#faad14',
|
|
18
55
|
colorError: '#ff4d4f',
|
|
56
|
+
colorInfo: '#1677ff',
|
|
19
57
|
colorBgLayout: '#f5f5f5',
|
|
20
58
|
};
|
|
21
59
|
|
|
22
|
-
|
|
60
|
+
export const DEFAULT_PRESET_KEY = 'antd';
|
|
61
|
+
|
|
62
|
+
/** 侧栏默认底色(预设未指定 siderBg 时的预览/回退值) */
|
|
63
|
+
export const DEFAULT_SIDER_BG = '#001529';
|
|
64
|
+
/** 侧栏折叠触发条默认底色 */
|
|
65
|
+
export const DEFAULT_SIDER_TRIGGER_BG = '#002140';
|
|
66
|
+
|
|
67
|
+
/** 界面密度预设:默认保持 antd 原生,紧凑叠加 compactAlgorithm */
|
|
68
|
+
export const DENSITY_PRESETS: ThemeDensityPreset[] = [
|
|
69
|
+
{ key: 'default', name: '默认' },
|
|
70
|
+
{
|
|
71
|
+
key: 'compact',
|
|
72
|
+
name: '紧凑',
|
|
73
|
+
compact: true,
|
|
74
|
+
token: { sizeUnit: 4, sizeStep: 4, controlHeight: 28, fontSize: 13 },
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
export const DEFAULT_DENSITY_KEY: ThemeDensity = 'default';
|
|
79
|
+
|
|
80
|
+
/** 圆角预设:默认回归 antd 原生 6 */
|
|
81
|
+
export const RADIUS_PRESETS: ThemeRadiusPreset[] = [
|
|
82
|
+
{ key: 'default', name: '默认', token: { borderRadius: 6 } },
|
|
83
|
+
{ key: 'sharp', name: '直角', token: { borderRadius: 0 } },
|
|
84
|
+
{ key: 'round', name: '圆润', token: { borderRadius: 10 } },
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
export const DEFAULT_RADIUS_KEY: ThemeRadius = 'default';
|
|
88
|
+
|
|
89
|
+
/** 可选主题预设;antd 预设不做任何颜色覆盖,保持 100% 原生观感 */
|
|
90
|
+
export const THEME_PRESETS: ThemePreset[] = [
|
|
91
|
+
{
|
|
92
|
+
key: 'antd',
|
|
93
|
+
name: 'Ant Design 默认',
|
|
94
|
+
colors: {},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
key: 'cyan',
|
|
98
|
+
name: '青碧',
|
|
99
|
+
colors: {
|
|
100
|
+
colorPrimary: '#13c2c2',
|
|
101
|
+
siderBg: '#0C2E2E',
|
|
102
|
+
siderSubBg: '#0C2E2E',
|
|
103
|
+
siderTriggerBg: '#082222',
|
|
104
|
+
siderHoverBg: 'rgba(255,255,255,0.06)',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
key: 'green',
|
|
109
|
+
name: '翠竹',
|
|
110
|
+
colors: {
|
|
111
|
+
colorPrimary: '#389e0d',
|
|
112
|
+
siderBg: '#10291A',
|
|
113
|
+
siderSubBg: '#10291A',
|
|
114
|
+
siderTriggerBg: '#0B1E13',
|
|
115
|
+
siderHoverBg: 'rgba(255,255,255,0.06)',
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
key: 'purple',
|
|
120
|
+
name: '紫罗兰',
|
|
121
|
+
colors: {
|
|
122
|
+
colorPrimary: '#722ed1',
|
|
123
|
+
siderBg: '#1E1440',
|
|
124
|
+
siderSubBg: '#1E1440',
|
|
125
|
+
siderTriggerBg: '#170F31',
|
|
126
|
+
siderHoverBg: 'rgba(255,255,255,0.06)',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
key: 'red',
|
|
131
|
+
name: '中国红',
|
|
132
|
+
colors: {
|
|
133
|
+
colorPrimary: '#cf1322',
|
|
134
|
+
siderBg: '#2A1418',
|
|
135
|
+
siderSubBg: '#2A1418',
|
|
136
|
+
siderTriggerBg: '#201014',
|
|
137
|
+
siderHoverBg: 'rgba(255,255,255,0.06)',
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
key: 'orange',
|
|
142
|
+
name: '暖橙',
|
|
143
|
+
colors: {
|
|
144
|
+
colorPrimary: '#d46b08',
|
|
145
|
+
siderBg: '#2A1D10',
|
|
146
|
+
siderSubBg: '#2A1D10',
|
|
147
|
+
siderTriggerBg: '#20160C',
|
|
148
|
+
siderHoverBg: 'rgba(255,255,255,0.06)',
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
key: 'graphite',
|
|
153
|
+
name: '石墨灰',
|
|
154
|
+
colors: {
|
|
155
|
+
colorPrimary: '#2f3542',
|
|
156
|
+
siderBg: '#1B2026',
|
|
157
|
+
siderSubBg: '#1B2026',
|
|
158
|
+
siderTriggerBg: '#141A20',
|
|
159
|
+
siderHoverBg: 'rgba(255,255,255,0.06)',
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
const THEME_MODE_KEY = 'oa-theme-mode';
|
|
165
|
+
const THEME_PRESET_KEY = 'oa-theme-preset';
|
|
166
|
+
const THEME_DENSITY_KEY = 'oa-theme-density';
|
|
167
|
+
const THEME_RADIUS_KEY = 'oa-theme-radius';
|
|
168
|
+
|
|
169
|
+
/** 业务侧传入的颜色基线(由 setThemeColors 设置) */
|
|
170
|
+
let businessColors: Partial<ThemeColors> | null = null;
|
|
171
|
+
/** 用户选择的预设 key(持久化到 localStorage) */
|
|
172
|
+
let presetKey: string = readStoredPreset();
|
|
173
|
+
let densityKey: ThemeDensity = readStoredDensity();
|
|
174
|
+
let radiusKey: ThemeRadius = readStoredRadius();
|
|
175
|
+
let currentMode: ThemeMode = readStoredMode();
|
|
23
176
|
let cachedConfig: ThemeConfig | null = null;
|
|
177
|
+
let cachedSiderConfig: ThemeConfig | null = null;
|
|
24
178
|
let cachedToken: Record<string, unknown> | null = null;
|
|
25
179
|
|
|
180
|
+
function readStoredMode(): ThemeMode {
|
|
181
|
+
if (typeof window === 'undefined') return 'light';
|
|
182
|
+
try {
|
|
183
|
+
return localStorage.getItem(THEME_MODE_KEY) === 'dark' ? 'dark' : 'light';
|
|
184
|
+
} catch {
|
|
185
|
+
return 'light';
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function readStoredPreset(): string {
|
|
190
|
+
if (typeof window === 'undefined') return DEFAULT_PRESET_KEY;
|
|
191
|
+
try {
|
|
192
|
+
const key = localStorage.getItem(THEME_PRESET_KEY);
|
|
193
|
+
if (key && THEME_PRESETS.some(p => p.key === key)) return key;
|
|
194
|
+
} catch {
|
|
195
|
+
// 隐私模式下 localStorage 不可用,忽略
|
|
196
|
+
}
|
|
197
|
+
return DEFAULT_PRESET_KEY;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function persistPreset(key: string) {
|
|
201
|
+
if (typeof window === 'undefined') return;
|
|
202
|
+
try {
|
|
203
|
+
localStorage.setItem(THEME_PRESET_KEY, key);
|
|
204
|
+
} catch {
|
|
205
|
+
// 忽略
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function readStoredDensity(): ThemeDensity {
|
|
210
|
+
if (typeof window === 'undefined') return DEFAULT_DENSITY_KEY;
|
|
211
|
+
try {
|
|
212
|
+
const key = localStorage.getItem(THEME_DENSITY_KEY);
|
|
213
|
+
if (key && DENSITY_PRESETS.some(p => p.key === key)) return key as ThemeDensity;
|
|
214
|
+
} catch {
|
|
215
|
+
// 隐私模式下 localStorage 不可用,忽略
|
|
216
|
+
}
|
|
217
|
+
return DEFAULT_DENSITY_KEY;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function readStoredRadius(): ThemeRadius {
|
|
221
|
+
if (typeof window === 'undefined') return DEFAULT_RADIUS_KEY;
|
|
222
|
+
try {
|
|
223
|
+
const key = localStorage.getItem(THEME_RADIUS_KEY);
|
|
224
|
+
if (key && RADIUS_PRESETS.some(p => p.key === key)) return key as ThemeRadius;
|
|
225
|
+
} catch {
|
|
226
|
+
// 隐私模式下 localStorage 不可用,忽略
|
|
227
|
+
}
|
|
228
|
+
return DEFAULT_RADIUS_KEY;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function writeStored(key: string, value: string) {
|
|
232
|
+
if (typeof window === 'undefined') return;
|
|
233
|
+
try {
|
|
234
|
+
localStorage.setItem(key, value);
|
|
235
|
+
} catch {
|
|
236
|
+
// 忽略
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function getActiveDensity(): ThemeDensityPreset | undefined {
|
|
241
|
+
return DENSITY_PRESETS.find(p => p.key === densityKey);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function getActiveRadius(): ThemeRadiusPreset | undefined {
|
|
245
|
+
return RADIUS_PRESETS.find(p => p.key === radiusKey);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** 尺寸维度 Token 合并:密度 ← 圆角(用户显式选择优先) */
|
|
249
|
+
function getMergedSizeToken(): Record<string, unknown> {
|
|
250
|
+
return {
|
|
251
|
+
...(getActiveDensity()?.token || {}),
|
|
252
|
+
...(getActiveRadius()?.token || {}),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function getActivePreset(): ThemePreset | undefined {
|
|
257
|
+
return THEME_PRESETS.find(p => p.key === presetKey);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** 合并优先级:antd 默认 ← 业务基线 ← 用户预设 */
|
|
261
|
+
function getMergedColors(): ThemeColors {
|
|
262
|
+
return {
|
|
263
|
+
...DEFAULT_COLORS,
|
|
264
|
+
...(businessColors || {}),
|
|
265
|
+
...(getActivePreset()?.colors || {}),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function hasExplicitBgLayout(): boolean {
|
|
270
|
+
return businessColors?.colorBgLayout != null || getActivePreset()?.colors.colorBgLayout != null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function invalidate() {
|
|
274
|
+
cachedConfig = null;
|
|
275
|
+
cachedToken = null;
|
|
276
|
+
cachedSiderConfig = null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function buildAlgorithm(): ThemeConfig['algorithm'] {
|
|
280
|
+
const algorithms = [currentMode === 'dark' ? theme.darkAlgorithm : theme.defaultAlgorithm];
|
|
281
|
+
if (getActiveDensity()?.compact) {
|
|
282
|
+
algorithms.push(theme.compactAlgorithm);
|
|
283
|
+
}
|
|
284
|
+
return algorithms;
|
|
285
|
+
}
|
|
286
|
+
|
|
26
287
|
function buildConfig(): ThemeConfig {
|
|
27
|
-
const
|
|
288
|
+
const merged = getMergedColors();
|
|
289
|
+
const algorithm = buildAlgorithm();
|
|
290
|
+
const token: Record<string, unknown> = {
|
|
291
|
+
...getMergedSizeToken(),
|
|
292
|
+
colorPrimary: merged.colorPrimary,
|
|
293
|
+
colorSuccess: merged.colorSuccess,
|
|
294
|
+
colorWarning: merged.colorWarning,
|
|
295
|
+
colorError: merged.colorError,
|
|
296
|
+
colorInfo: merged.colorInfo,
|
|
297
|
+
};
|
|
298
|
+
// colorBgLayout 仅浅色模式默认覆盖;暗色模式交给算法推导,避免被强制成浅灰。
|
|
299
|
+
// 业务/预设显式传入 colorBgLayout 时两种模式都尊重。
|
|
300
|
+
if (currentMode === 'light' || hasExplicitBgLayout()) {
|
|
301
|
+
token.colorBgLayout = merged.colorBgLayout;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const resolved = getDesignToken({ token, algorithm } as ThemeConfig);
|
|
28
305
|
const components: Record<string, unknown> = {
|
|
29
|
-
|
|
306
|
+
// 顶栏用容器色,跟随明/暗主题
|
|
307
|
+
Layout: { headerBg: resolved.colorBgContainer, triggerHeight: 32 },
|
|
30
308
|
};
|
|
31
309
|
|
|
32
|
-
return { token, components };
|
|
310
|
+
return { token, algorithm, components } as ThemeConfig;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* 侧栏专用主题:单独套一层暗色算法,使分割线、边框、填充等按暗色派生,
|
|
315
|
+
* 避免浅色算法下色值不可见;同时按预设覆盖侧栏底色。
|
|
316
|
+
*/
|
|
317
|
+
export function getSiderThemeConfig(): ThemeConfig {
|
|
318
|
+
if (cachedSiderConfig) return cachedSiderConfig;
|
|
319
|
+
|
|
320
|
+
const merged = getMergedColors();
|
|
321
|
+
const token: Record<string, unknown> = {
|
|
322
|
+
...getMergedSizeToken(),
|
|
323
|
+
colorPrimary: merged.colorPrimary,
|
|
324
|
+
};
|
|
325
|
+
const layout: Record<string, unknown> = {};
|
|
326
|
+
const menu: Record<string, unknown> = {
|
|
327
|
+
darkItemSelectedBg: merged.colorPrimary,
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
if (merged.siderBg) {
|
|
331
|
+
layout.siderBg = merged.siderBg;
|
|
332
|
+
menu.darkItemBg = merged.siderBg;
|
|
333
|
+
menu.darkSubMenuItemBg = merged.siderSubBg || merged.siderBg;
|
|
334
|
+
menu.darkItemHoverBg = merged.siderHoverBg || 'rgba(255,255,255,0.06)';
|
|
335
|
+
}
|
|
336
|
+
if (merged.siderTriggerBg) {
|
|
337
|
+
layout.triggerBg = merged.siderTriggerBg;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const siderAlgorithms = [theme.darkAlgorithm];
|
|
341
|
+
if (getActiveDensity()?.compact) {
|
|
342
|
+
siderAlgorithms.push(theme.compactAlgorithm);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
cachedSiderConfig = {
|
|
346
|
+
algorithm: siderAlgorithms,
|
|
347
|
+
inherit: true,
|
|
348
|
+
token,
|
|
349
|
+
components: { Layout: layout, Menu: menu },
|
|
350
|
+
} as ThemeConfig;
|
|
351
|
+
return cachedSiderConfig;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function setVar(el: HTMLElement, name: string, value: unknown) {
|
|
355
|
+
if (value == null) return;
|
|
356
|
+
el.style.setProperty(name, String(value));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** 像素类 Token 以数值返回(如 32),需补 px 后写入,避免 line-height 被当作倍数 */
|
|
360
|
+
function setPxVar(el: HTMLElement, name: string, value: unknown) {
|
|
361
|
+
if (value == null) return;
|
|
362
|
+
const str = typeof value === 'number' ? `${value}px` : String(value);
|
|
363
|
+
el.style.setProperty(name, str);
|
|
33
364
|
}
|
|
34
365
|
|
|
35
366
|
function syncCssVars(token: Record<string, unknown>) {
|
|
36
367
|
if (typeof document === 'undefined') return;
|
|
368
|
+
const merged = getMergedColors();
|
|
37
369
|
const el = document.documentElement;
|
|
38
|
-
el
|
|
39
|
-
el
|
|
370
|
+
setVar(el, '--primary-color', token.colorPrimary);
|
|
371
|
+
setVar(el, '--primary-color-hover', token.colorPrimaryHover || token.colorPrimary);
|
|
372
|
+
// 供 less 中替代硬编码颜色,使自定义样式跟随明/暗主题
|
|
373
|
+
setVar(el, '--oa-color-bg-container', token.colorBgContainer);
|
|
374
|
+
setVar(el, '--oa-color-bg-layout', token.colorBgLayout);
|
|
375
|
+
setVar(el, '--oa-color-border', token.colorBorderSecondary ?? token.colorBorder);
|
|
376
|
+
setVar(el, '--oa-color-text', token.colorText);
|
|
377
|
+
setVar(el, '--oa-color-text-secondary', token.colorTextSecondary);
|
|
378
|
+
setVar(el, '--oa-color-text-tertiary', token.colorTextTertiary);
|
|
379
|
+
setVar(el, '--oa-color-fill', token.colorFillTertiary);
|
|
380
|
+
setVar(el, '--oa-color-primary-bg', token.colorPrimaryBg);
|
|
381
|
+
setVar(el, '--oa-color-error', token.colorError);
|
|
382
|
+
// 尺寸/圆角变量,供 less 复用,使自定义样式跟随密度与圆角预设
|
|
383
|
+
setPxVar(el, '--oa-border-radius', token.borderRadius);
|
|
384
|
+
setPxVar(el, '--oa-control-height', token.controlHeight);
|
|
385
|
+
setPxVar(el, '--oa-font-size', token.fontSize);
|
|
386
|
+
// 侧栏相关变量
|
|
387
|
+
setVar(el, '--oa-sider-bg', merged.siderBg || DEFAULT_SIDER_BG);
|
|
388
|
+
setVar(el, '--oa-sider-text', 'rgba(255,255,255,0.85)');
|
|
389
|
+
setVar(el, '--oa-sider-hover-bg', merged.siderHoverBg || 'transparent');
|
|
390
|
+
setVar(el, '--oa-sider-trigger-bg', merged.siderTriggerBg || DEFAULT_SIDER_TRIGGER_BG);
|
|
391
|
+
el.dataset.oaTheme = currentMode;
|
|
392
|
+
el.style.colorScheme = currentMode;
|
|
40
393
|
}
|
|
41
394
|
|
|
42
|
-
/** 由 Layouts
|
|
395
|
+
/** 由 Layouts 在渲染前调用,传入业务侧颜色基线;不传则恢复 antd 默认主题 */
|
|
43
396
|
export function setThemeColors(colors?: Partial<ThemeColors>) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
cachedToken = null;
|
|
397
|
+
businessColors = colors || null;
|
|
398
|
+
invalidate();
|
|
47
399
|
syncCssVars(getToken());
|
|
48
400
|
}
|
|
49
401
|
|
|
@@ -56,7 +408,90 @@ export function getThemeConfig(): ThemeConfig {
|
|
|
56
408
|
|
|
57
409
|
export function getToken(): ThemeColors & Record<string, unknown> {
|
|
58
410
|
if (!cachedToken) {
|
|
59
|
-
cachedToken = getDesignToken(getThemeConfig())
|
|
411
|
+
cachedToken = getDesignToken(getThemeConfig()) as unknown as Record<string, unknown>;
|
|
412
|
+
}
|
|
413
|
+
return cachedToken as ThemeColors & Record<string, unknown>;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function getThemeMode(): ThemeMode {
|
|
417
|
+
return currentMode;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** 应用预设主题色,持久化并广播 themeChange 供布局重渲染 */
|
|
421
|
+
export function applyThemePreset(key: string) {
|
|
422
|
+
if (!THEME_PRESETS.some(p => p.key === key)) return;
|
|
423
|
+
presetKey = key;
|
|
424
|
+
persistPreset(key);
|
|
425
|
+
invalidate();
|
|
426
|
+
syncCssVars(getToken());
|
|
427
|
+
EventBus.emit('themeChange', currentMode);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export function getActivePresetKey(): string {
|
|
431
|
+
return presetKey;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** 应用界面密度预设,持久化并广播 themeChange */
|
|
435
|
+
export function applyDensityPreset(key: ThemeDensity) {
|
|
436
|
+
if (!DENSITY_PRESETS.some(p => p.key === key)) return;
|
|
437
|
+
densityKey = key;
|
|
438
|
+
writeStored(THEME_DENSITY_KEY, key);
|
|
439
|
+
invalidate();
|
|
440
|
+
syncCssVars(getToken());
|
|
441
|
+
EventBus.emit('themeChange', currentMode);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export function getActiveDensityKey(): ThemeDensity {
|
|
445
|
+
return densityKey;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** 应用圆角预设,持久化并广播 themeChange */
|
|
449
|
+
export function applyRadiusPreset(key: ThemeRadius) {
|
|
450
|
+
if (!RADIUS_PRESETS.some(p => p.key === key)) return;
|
|
451
|
+
radiusKey = key;
|
|
452
|
+
writeStored(THEME_RADIUS_KEY, key);
|
|
453
|
+
invalidate();
|
|
454
|
+
syncCssVars(getToken());
|
|
455
|
+
EventBus.emit('themeChange', currentMode);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export function getActiveRadiusKey(): ThemeRadius {
|
|
459
|
+
return radiusKey;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** 恢复默认:颜色 / 密度 / 圆角 / 明暗 全部复位(不影响布局,布局由 resetAllSettings 统一处理) */
|
|
463
|
+
export function resetUserTheme() {
|
|
464
|
+
presetKey = DEFAULT_PRESET_KEY;
|
|
465
|
+
densityKey = DEFAULT_DENSITY_KEY;
|
|
466
|
+
radiusKey = DEFAULT_RADIUS_KEY;
|
|
467
|
+
currentMode = 'light';
|
|
468
|
+
persistPreset(presetKey);
|
|
469
|
+
writeStored(THEME_DENSITY_KEY, densityKey);
|
|
470
|
+
writeStored(THEME_RADIUS_KEY, radiusKey);
|
|
471
|
+
writeStored(THEME_MODE_KEY, currentMode);
|
|
472
|
+
invalidate();
|
|
473
|
+
syncCssVars(getToken());
|
|
474
|
+
EventBus.emit('themeChange', currentMode);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** 切换明/暗模式:清缓存、同步 CSS 变量并广播 themeChange 供布局重渲染 */
|
|
478
|
+
export function setThemeMode(mode: ThemeMode) {
|
|
479
|
+
if (currentMode === mode) return;
|
|
480
|
+
currentMode = mode;
|
|
481
|
+
if (typeof window !== 'undefined') {
|
|
482
|
+
try {
|
|
483
|
+
localStorage.setItem(THEME_MODE_KEY, mode);
|
|
484
|
+
} catch {
|
|
485
|
+
// 隐私模式下 localStorage 不可用,忽略
|
|
486
|
+
}
|
|
60
487
|
}
|
|
61
|
-
|
|
488
|
+
invalidate();
|
|
489
|
+
syncCssVars(getToken());
|
|
490
|
+
EventBus.emit('themeChange', mode);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export function toggleThemeMode(): ThemeMode {
|
|
494
|
+
const next: ThemeMode = currentMode === 'dark' ? 'light' : 'dark';
|
|
495
|
+
setThemeMode(next);
|
|
496
|
+
return next;
|
|
62
497
|
}
|
|
@@ -21,8 +21,8 @@ export interface BaseRemoteSelectProps {
|
|
|
21
21
|
* 处理通用的数据加载、防抖搜索、竞态处理、错误提示逻辑。
|
|
22
22
|
* 子类只需覆写 getLoadParams() 和 render()。
|
|
23
23
|
*/
|
|
24
|
-
export class BaseRemoteSelect<P extends BaseRemoteSelectProps = BaseRemoteSelectProps> extends React.Component<P, BaseRemoteSelectState> {
|
|
25
|
-
private fetchIdRef: number = 0;
|
|
24
|
+
export class BaseRemoteSelect<P extends BaseRemoteSelectProps = BaseRemoteSelectProps> extends React.Component<P, BaseRemoteSelectState> {
|
|
25
|
+
private fetchIdRef: number = 0;
|
|
26
26
|
private loadDataDebounce: ReturnType<typeof debounce>;
|
|
27
27
|
|
|
28
28
|
static readonly defaultProps = {
|
|
@@ -117,14 +117,14 @@ export class BaseRemoteSelect<P extends BaseRemoteSelectProps = BaseRemoteSelect
|
|
|
117
117
|
|
|
118
118
|
// ========== Helper 方法 ==========
|
|
119
119
|
|
|
120
|
-
getShowSearch(): { filterOption: false; onSearch: (value: string) => void } {
|
|
120
|
+
getShowSearch(): { filterOption: false; onSearch: (value: string) => void } {
|
|
121
121
|
return {
|
|
122
122
|
filterOption: false,
|
|
123
123
|
onSearch: this.handleSearch,
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
getNotFoundContent(): React.ReactNode {
|
|
127
|
+
getNotFoundContent(): React.ReactNode {
|
|
128
128
|
return this.state.loading ? <Spin size="small" /> : '数据为空';
|
|
129
129
|
}
|
|
130
130
|
}
|
|
@@ -11,7 +11,7 @@ import type {FieldProps} from '../types';
|
|
|
11
11
|
const SP = StringUtils.ISO_SPLITTER;
|
|
12
12
|
|
|
13
13
|
/** 日期范围值,形如 "2024-01-01/2024-01-31"(起止以 / 分隔) */
|
|
14
|
-
export type FieldDateRangeValue = string;
|
|
14
|
+
export type FieldDateRangeValue = string;
|
|
15
15
|
|
|
16
16
|
export interface FieldDateRangeProps extends FieldProps<FieldDateRangeValue> {
|
|
17
17
|
/** 日期类型,如 YYYY-MM-DD、YYYY-MM、HH:mm:ss 等,默认 YYYY-MM-DD */
|
|
@@ -12,7 +12,7 @@ export class FieldDictSelect extends React.Component<FieldDictSelectProps> {
|
|
|
12
12
|
render() {
|
|
13
13
|
const {value, typeCode} = this.props;
|
|
14
14
|
const options = DictUtils.dictOptions(typeCode);
|
|
15
|
-
const strValue = value == null ? null : String(value);
|
|
15
|
+
const strValue = value == null ? null : String(value);
|
|
16
16
|
|
|
17
17
|
return <Select value={strValue}
|
|
18
18
|
onChange={this.props.onChange}
|
|
@@ -94,11 +94,11 @@ export class FieldEditor extends React.Component<FieldEditorProps, {loading: boo
|
|
|
94
94
|
alignItems: 'center',
|
|
95
95
|
justifyContent: 'center',
|
|
96
96
|
gap: 8,
|
|
97
|
-
background: '#fff',
|
|
97
|
+
background: 'var(--oa-color-bg-container, #fff)',
|
|
98
98
|
zIndex: 1,
|
|
99
99
|
}}>
|
|
100
100
|
<Spin/>
|
|
101
|
-
<span style={{color: '#999'}}>编辑器加载中...</span>
|
|
101
|
+
<span style={{color: 'var(--oa-color-text-tertiary, #999)'}}>编辑器加载中...</span>
|
|
102
102
|
</div>
|
|
103
103
|
)}
|
|
104
104
|
</div>
|
|
@@ -6,7 +6,7 @@ import type {FieldProps} from '../types';
|
|
|
6
6
|
const SP = StringUtils.ISO_SPLITTER;
|
|
7
7
|
|
|
8
8
|
/** 数字范围值,形如 "1/100"(起止以 / 分隔) */
|
|
9
|
-
export type FieldNumberRangeValue = string;
|
|
9
|
+
export type FieldNumberRangeValue = string;
|
|
10
10
|
|
|
11
11
|
export type RangePartValue = number | string | null;
|
|
12
12
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {Modal, Upload} from "antd";
|
|
3
3
|
import type {UploadChangeParam, UploadFile, UploadProps} from "antd";
|
|
4
4
|
import { UploadOutlined } from "@ant-design/icons";
|
|
5
5
|
import {ViewFile} from "../../views";
|
|
6
|
-
import {ObjectUtils, UrlUtils} from "../../utils";
|
|
6
|
+
import {getMessageApi, ObjectUtils, UrlUtils} from "../../utils";
|
|
7
7
|
import type {FieldProps} from '../types';
|
|
8
8
|
|
|
9
9
|
/** 框架内使用的上传文件对象,额外携带 sysFile 的 objectName */
|
|
@@ -110,7 +110,7 @@ export class FieldUploadFile extends React.Component<FieldUploadFileProps, Field
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
if (file.status === 'done' && rs?.success) {
|
|
113
|
-
|
|
113
|
+
getMessageApi().success(`文件「${rs.data?.name || ''}」上传成功`);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
const newIds = this.convertComponentValueToOutput(fileList);
|