主题
diff --git a/frontend/src/themes/dark.ts b/frontend/src/themes/dark.ts
index 7e643a3..cd6655a 100644
--- a/frontend/src/themes/dark.ts
+++ b/frontend/src/themes/dark.ts
@@ -1,10 +1,10 @@
import { theme } from 'antd';
import type { ConfigProviderProps } from 'antd';
-const useDarkTheme = (): ConfigProviderProps => ({
+const darkConfig: ConfigProviderProps = {
theme: {
algorithm: theme.darkAlgorithm,
},
-});
+};
-export default useDarkTheme;
+export default darkConfig;
diff --git a/frontend/src/themes/default.ts b/frontend/src/themes/default.ts
index b963673..b1a2026 100644
--- a/frontend/src/themes/default.ts
+++ b/frontend/src/themes/default.ts
@@ -1,10 +1,10 @@
import { theme } from 'antd';
import type { ConfigProviderProps } from 'antd';
-const useDefaultTheme = (): ConfigProviderProps => ({
+const defaultConfig: ConfigProviderProps = {
theme: {
algorithm: theme.defaultAlgorithm,
},
-});
+};
-export default useDefaultTheme;
+export default defaultConfig;
diff --git a/frontend/src/themes/index.ts b/frontend/src/themes/index.ts
index 0dce791..5655c6e 100644
--- a/frontend/src/themes/index.ts
+++ b/frontend/src/themes/index.ts
@@ -1,6 +1,7 @@
+import { useMemo } from 'react';
import type { ConfigProviderProps } from 'antd';
-import useDefaultTheme from './default';
-import useDarkTheme from './dark';
+import defaultConfig from './default';
+import darkConfig from './dark';
import useMuiTheme from './mui';
import useShadcnTheme from './shadcn';
import useBootstrapTheme from './bootstrap';
@@ -25,23 +26,22 @@ export const themeOptions: ThemeOption[] = [
const themeIdSet = new Set
(themeOptions.map((opt) => opt.id));
export function useThemeConfig(themeId: ThemeId): ConfigProviderProps {
- const defaultConfig = useDefaultTheme();
- const darkConfig = useDarkTheme();
const muiConfig = useMuiTheme();
const shadcnConfig = useShadcnTheme();
const bootstrapConfig = useBootstrapTheme();
const glassConfig = useGlassTheme();
- const configs: Record = {
- default: defaultConfig,
- dark: darkConfig,
- mui: muiConfig,
- shadcn: shadcnConfig,
- bootstrap: bootstrapConfig,
- glass: glassConfig,
- };
-
- return configs[themeId] ?? configs.default;
+ return useMemo(() => {
+ const configs: Record = {
+ default: defaultConfig,
+ dark: darkConfig,
+ mui: muiConfig,
+ shadcn: shadcnConfig,
+ bootstrap: bootstrapConfig,
+ glass: glassConfig,
+ };
+ return configs[themeId] ?? configs.default;
+ }, [themeId, muiConfig, shadcnConfig, bootstrapConfig, glassConfig]);
}
export function isValidThemeId(value: string): value is ThemeId {
diff --git a/openspec/specs/frontend-config-ui/spec.md b/openspec/specs/frontend-config-ui/spec.md
index 801be1a..a77d04d 100644
--- a/openspec/specs/frontend-config-ui/spec.md
+++ b/openspec/specs/frontend-config-ui/spec.md
@@ -8,7 +8,7 @@ TBD - 提供供应商、模型配置和用量统计的前端管理界面
### Requirement: 提供供应商管理页面
-前端 SHALL 使用 Ant Design 组件提供供应商管理页面。
+前端 SHALL 使用 Ant Design 组件提供供应商管理页面,所有组件 SHALL 遵循 antd v6 API 规范。
#### Scenario: 显示供应商列表
@@ -41,7 +41,7 @@ TBD - 提供供应商、模型配置和用量统计的前端管理界面
- **WHEN** 用户提交包含有效数据的表单
- **THEN** 前端 SHALL 通过 useMutation 调用创建 API
- **THEN** 成功后 SHALL 关闭 Modal 并刷新供应商列表
-- **THEN** 失败 SHALL 使用 message.error() 提示
+- **THEN** 失败 SHALL 使用 `App.useApp()` 提供的 `message` 实例显示错误提示
#### Scenario: 编辑现有供应商
@@ -61,7 +61,7 @@ TBD - 提供供应商、模型配置和用量统计的前端管理界面
### Requirement: 提供模型管理界面
-前端 SHALL 在供应商页面展开行中提供模型管理。
+前端 SHALL 在供应商页面展开行中提供模型管理,所有组件 SHALL 遵循 antd v6 API 规范。
#### Scenario: 显示供应商的模型
@@ -80,6 +80,7 @@ TBD - 提供供应商、模型配置和用量统计的前端管理界面
- **WHEN** 用户在展开行中点击"添加模型"
- **THEN** 前端 SHALL 显示 Ant Design Modal + Form
- **THEN** provider_id SHALL 自动关联当前供应商
+- **THEN** 供应商选择 SHALL 使用 `options` 属性而非 `Select.Option` 子组件
- **WHEN** 用户提交表单
- **THEN** 前端 SHALL 通过 useMutation 调用创建 API
- **THEN** 成功后 SHALL 刷新模型列表
@@ -141,12 +142,12 @@ TBD - 提供供应商、模型配置和用量统计的前端管理界面
### Requirement: 优雅处理 API 错误
-前端 SHALL 处理 API 错误并显示用户友好的消息。
+前端 SHALL 处理 API 错误并显示用户友好的消息,message 组件 SHALL 通过 `App.useApp()` 获取。
#### Scenario: API 请求失败
- **WHEN** API 请求失败(网络错误、4xx、5xx)
-- **THEN** 前端 SHALL 使用 Ant Design 的 message.error() 显示全局错误提示
+- **THEN** 前端 SHALL 使用 `App.useApp()` 提供的 `message` 实例显示全局错误提示
- **THEN** 错误消息 SHALL 具有描述性
#### Scenario: 验证错误
@@ -187,21 +188,24 @@ TBD - 提供供应商、模型配置和用量统计的前端管理界面
### Requirement: 使用无组件库的最小 UI
-前端 SHALL 使用 Ant Design 6 作为 UI 组件库。
+前端 SHALL 使用 Ant Design 6 作为 UI 组件库,所有组件 SHALL 使用 antd v6 推荐的 API。
#### Scenario: Ant Design 组件使用
- **WHEN** 实现前端
-- **THEN** 它 SHALL 使用 Ant Design 6 组件(Table、Form、Modal、Menu、Tag、Popconfirm、DatePicker、Button、Select 等)
-- **THEN** 它 SHALL 使用 @ant-design/icons 提供图标
+- **THEN** Button 组件 SHALL 使用 `variant` 和 `color` 属性替代已废弃的 `type`(link/text/dashed)和 `danger` 属性
+- **THEN** Button `type="link"` SHALL 替换为 `variant="link"`
+- **THEN** Button `danger` 属性 SHALL 替换为 `color="danger"`
+- **THEN** Space 组件 SHALL 使用 `vertical` 布尔属性替代已废弃的 `direction` 属性
+- **THEN** Select 组件 SHALL 使用 `options` 属性替代 `Select.Option` 子组件
- **THEN** 图标 SHALL 优先使用图标库中已有的图标
-#### Scenario: Ant Design 主题支持
+#### Scenario: App 组件包裹
-- **WHEN** 配置 Ant Design 主题
-- **THEN** 前端 SHALL 支持亮色和暗色模式切换
-- **THEN** 前端 SHALL 使用 Ant Design v6 的 theme.darkAlgorithm 和 theme.defaultAlgorithm
-- **THEN** 前端 SHALL 通过 ConfigProvider 动态切换主题
+- **WHEN** 应用初始化
+- **THEN** `App.tsx` SHALL 在 `ConfigProvider` 内部使用 Ant Design `` 组件包裹应用
+- **THEN** hooks 中 SHALL 使用 `App.useApp()` 获取 `message` 实例
+- **THEN** `message` 实例 SHALL NOT 通过静态 `import { message } from 'antd'` 获取
### Requirement: 样式体系
@@ -351,10 +355,10 @@ TBD - 提供供应商、模型配置和用量统计的前端管理界面
- **WHEN** 用户执行创建、更新或删除操作
- **THEN** 前端 SHALL 使用 TanStack Query 的 useMutation hook
- **THEN** 操作成功后 SHALL 自动失效相关查询缓存
-- **THEN** 操作失败 SHALL 使用 Ant Design message.error() 显示错误提示
+- **THEN** 操作失败 SHALL 使用 `App.useApp()` 提供的 `message` 实例显示错误提示
#### Scenario: 错误提示
- **WHEN** API 请求失败(网络错误、4xx、5xx)
-- **THEN** 前端 SHALL 使用 Ant Design 的 message.error() 显示全局错误提示
+- **THEN** 前端 SHALL 使用 `App.useApp()` 提供的 `message` 实例显示全局错误提示
- **THEN** 错误消息 SHALL 具有描述性
diff --git a/openspec/specs/theme-system/spec.md b/openspec/specs/theme-system/spec.md
index 4e469b4..a6b6c62 100644
--- a/openspec/specs/theme-system/spec.md
+++ b/openspec/specs/theme-system/spec.md
@@ -104,13 +104,14 @@
### Requirement: 提供设置页面
-前端 SHALL 提供设置页面,使用卡片布局组织设置内容。
+前端 SHALL 提供设置页面,使用卡片布局组织设置内容,所有布局组件 SHALL 遵循 antd v6 API 规范。
#### Scenario: 主题卡片
- **WHEN** 用户访问设置页面
- **THEN** 前端 SHALL 显示主题设置卡片
- **THEN** 卡片内 SHALL 包含主题下拉栏和跟随系统开关
+- **THEN** 卡片内布局 SHALL 使用 `Space` 的 `vertical` 布尔属性替代已废弃的 `direction` 属性
#### Scenario: 主题下拉栏
@@ -136,14 +137,15 @@
### Requirement: 侧边栏跟随应用主题
-侧边栏 SHALL 根据 `effectiveThemeId` 动态切换亮色或暗色外观。
+侧边栏 SHALL 根据 `effectiveThemeId` 动态切换亮色或暗色外观,使用 antd token 获取颜色值。
#### Scenario: 亮色主题下的侧边栏
- **WHEN** `effectiveThemeId` 不为 `'dark'`
-- **THEN** 侧边栏 Sider 背景 SHALL 为浅色(`#fff`)
-- **THEN** Logo 文字颜色 SHALL 为深色
+- **THEN** 侧边栏 Sider 背景 SHALL 使用 antd token 的 `colorBgContainer` 而非硬编码颜色值
+- **THEN** Logo 文字颜色 SHALL 使用 antd token 的 `colorText` 而非硬编码颜色值
- **THEN** Menu 组件 SHALL 使用 `theme="light"`
+- **THEN** Header 背景和边框 SHALL 使用 antd token 获取颜色值
#### Scenario: 暗色主题下的侧边栏
@@ -167,6 +169,7 @@
- **WHEN** 应用初始化
- **THEN** 主题注册表 SHALL 包含 6 套主题的完整配置
- **THEN** 每套主题 SHALL 通过 `useConfig()` hook 返回 `ConfigProviderProps`
+- **THEN** `useThemeConfig` SHALL 优化加载策略,避免不必要的主题初始化开销
#### Scenario: 主题文件组织