openxiangda-cli 2.0.0-alpha.67 → 2.0.0-alpha.69
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 +4 -4
- package/template/AGENTS.md +12 -7
- package/template/README.md +7 -7
- package/template/apps/server/package.json +1 -1
- package/template/apps/web/e2e/resources.spec.ts +11 -0
- package/template/apps/web/package.json +1 -1
- package/template/apps/web/scripts/check.mjs +18 -13
- package/template/apps/web/src/FilePreviewPage.tsx +3 -3
- package/template/apps/web/src/Shell.tsx +323 -33
- package/template/apps/web/src/appearance.tsx +147 -0
- package/template/apps/web/src/components/resource/GeneratedResourceCrud.tsx +645 -109
- package/template/apps/web/src/components/resource/ResourceBatchActions.tsx +319 -0
- package/template/apps/web/src/components/resource/StandardResourcePages.tsx +13 -52
- package/template/apps/web/src/components/resource/SurfaceFields.tsx +1 -1
- package/template/apps/web/src/components/resource/resource-import.ts +205 -0
- package/template/apps/web/src/main.tsx +22 -21
- package/template/apps/web/src/platform-client.ts +195 -40
- package/template/apps/web/src/styles.css +455 -220
- package/template/apps/web/test/contracts.test.ts +52 -5
- package/template/apps/web/test/resource-import.test.ts +142 -0
- package/template/openxiangda.config.ts +2 -1
- package/template/package.json +4 -3
- package/template/scripts/verify-template-budget.mjs +5 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {
|
|
2
|
+
App as AntdApp,
|
|
3
|
+
ConfigProvider,
|
|
4
|
+
theme as antdTheme,
|
|
5
|
+
type ThemeConfig,
|
|
6
|
+
} from 'antd';
|
|
7
|
+
import zhCN from 'antd/locale/zh_CN';
|
|
8
|
+
import {
|
|
9
|
+
createContext,
|
|
10
|
+
useContext,
|
|
11
|
+
useEffect,
|
|
12
|
+
useLayoutEffect,
|
|
13
|
+
useMemo,
|
|
14
|
+
useState,
|
|
15
|
+
type ReactNode,
|
|
16
|
+
} from 'react';
|
|
17
|
+
|
|
18
|
+
export type AppearancePreference = 'system' | 'light' | 'dark';
|
|
19
|
+
export type EffectiveAppearance = 'light' | 'dark';
|
|
20
|
+
|
|
21
|
+
const STORAGE_KEY = 'openxiangda.admin.appearance';
|
|
22
|
+
const AppearanceContext = createContext<{
|
|
23
|
+
preference: AppearancePreference;
|
|
24
|
+
effectiveAppearance: EffectiveAppearance;
|
|
25
|
+
setPreference: (value: AppearancePreference) => void;
|
|
26
|
+
} | null>(null);
|
|
27
|
+
|
|
28
|
+
function storedPreference(): AppearancePreference {
|
|
29
|
+
try {
|
|
30
|
+
const value = window.localStorage.getItem(STORAGE_KEY);
|
|
31
|
+
return value === 'light' || value === 'dark' || value === 'system'
|
|
32
|
+
? value
|
|
33
|
+
: 'system';
|
|
34
|
+
} catch {
|
|
35
|
+
return 'system';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function systemPrefersDark() {
|
|
40
|
+
return window.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function PlatformThemeCssVariables() {
|
|
44
|
+
const { token } = antdTheme.useToken();
|
|
45
|
+
useLayoutEffect(() => {
|
|
46
|
+
const root = document.documentElement;
|
|
47
|
+
const variables: Record<string, string> = {
|
|
48
|
+
'--oxa-shell-bg': token.colorBgLayout,
|
|
49
|
+
'--oxa-shell-surface': token.colorBgContainer,
|
|
50
|
+
'--oxa-shell-elevated': token.colorBgElevated,
|
|
51
|
+
'--oxa-shell-subtle': token.colorFillAlter,
|
|
52
|
+
'--oxa-shell-hover': token.colorFillSecondary,
|
|
53
|
+
'--oxa-shell-border': token.colorBorderSecondary,
|
|
54
|
+
'--oxa-shell-border-strong': token.colorBorder,
|
|
55
|
+
'--oxa-shell-text': token.colorText,
|
|
56
|
+
'--oxa-shell-text-secondary': token.colorTextSecondary,
|
|
57
|
+
'--oxa-shell-text-tertiary': token.colorTextTertiary,
|
|
58
|
+
'--oxa-shell-text-disabled': token.colorTextDisabled,
|
|
59
|
+
'--oxa-shell-primary': token.colorPrimary,
|
|
60
|
+
'--oxa-shell-primary-bg': token.colorPrimaryBg,
|
|
61
|
+
'--oxa-shell-primary-border': token.colorPrimaryBorder,
|
|
62
|
+
'--oxa-shell-error': token.colorError,
|
|
63
|
+
'--oxa-shell-error-bg': token.colorErrorBg,
|
|
64
|
+
'--oxa-shell-error-border': token.colorErrorBorder,
|
|
65
|
+
'--oxa-shell-mask': token.colorBgMask,
|
|
66
|
+
'--oxa-shell-shadow': token.boxShadowTertiary,
|
|
67
|
+
};
|
|
68
|
+
for (const [name, value] of Object.entries(variables)) {
|
|
69
|
+
root.style.setProperty(name, value);
|
|
70
|
+
}
|
|
71
|
+
}, [token]);
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function AppearanceProvider({ children }: { children: ReactNode }) {
|
|
76
|
+
const [preference, setPreferenceState] = useState<AppearancePreference>(
|
|
77
|
+
storedPreference,
|
|
78
|
+
);
|
|
79
|
+
const [systemDark, setSystemDark] = useState(systemPrefersDark);
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const query = window.matchMedia('(prefers-color-scheme: dark)');
|
|
83
|
+
const onChange = (event: MediaQueryListEvent) => setSystemDark(event.matches);
|
|
84
|
+
query.addEventListener('change', onChange);
|
|
85
|
+
return () => query.removeEventListener('change', onChange);
|
|
86
|
+
}, []);
|
|
87
|
+
|
|
88
|
+
const effectiveAppearance: EffectiveAppearance =
|
|
89
|
+
preference === 'system' ? (systemDark ? 'dark' : 'light') : preference;
|
|
90
|
+
|
|
91
|
+
useLayoutEffect(() => {
|
|
92
|
+
document.documentElement.dataset.oxaTheme = effectiveAppearance;
|
|
93
|
+
document.documentElement.style.colorScheme = effectiveAppearance;
|
|
94
|
+
}, [effectiveAppearance]);
|
|
95
|
+
|
|
96
|
+
const setPreference = (value: AppearancePreference) => {
|
|
97
|
+
setPreferenceState(value);
|
|
98
|
+
try {
|
|
99
|
+
window.localStorage.setItem(STORAGE_KEY, value);
|
|
100
|
+
} catch {
|
|
101
|
+
// A blocked storage API must not prevent the in-memory theme switch.
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const theme = useMemo<ThemeConfig>(
|
|
106
|
+
() => ({
|
|
107
|
+
algorithm:
|
|
108
|
+
effectiveAppearance === 'dark'
|
|
109
|
+
? antdTheme.darkAlgorithm
|
|
110
|
+
: antdTheme.defaultAlgorithm,
|
|
111
|
+
cssVar: { key: effectiveAppearance, prefix: 'oxa' },
|
|
112
|
+
token: {
|
|
113
|
+
borderRadius: 6,
|
|
114
|
+
colorPrimary: '#1677ff',
|
|
115
|
+
fontSize: 14,
|
|
116
|
+
},
|
|
117
|
+
components: {
|
|
118
|
+
Card: { headerFontSize: 16 },
|
|
119
|
+
Layout: { headerHeight: 52 },
|
|
120
|
+
Menu: { itemBorderRadius: 6, itemHeight: 40 },
|
|
121
|
+
},
|
|
122
|
+
}),
|
|
123
|
+
[effectiveAppearance],
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const value = useMemo(
|
|
127
|
+
() => ({ preference, effectiveAppearance, setPreference }),
|
|
128
|
+
[effectiveAppearance, preference],
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<AppearanceContext.Provider value={value}>
|
|
133
|
+
<ConfigProvider componentSize="medium" locale={zhCN} theme={theme}>
|
|
134
|
+
<AntdApp>
|
|
135
|
+
<PlatformThemeCssVariables />
|
|
136
|
+
{children}
|
|
137
|
+
</AntdApp>
|
|
138
|
+
</ConfigProvider>
|
|
139
|
+
</AppearanceContext.Provider>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function useAppearance() {
|
|
144
|
+
const value = useContext(AppearanceContext);
|
|
145
|
+
if (!value) throw new Error('OPENXIANGDA_APPEARANCE_NOT_READY');
|
|
146
|
+
return value;
|
|
147
|
+
}
|