1
0

fix: 修复供应商管理弹窗交互问题

- 导入 TDesign react-19-adapter 修复 MessagePlugin 在 React 19 下的渲染错误
- Dialog 禁用蒙版点击和 ESC 键关闭,防止误操作丢失表单数据
- 重构弹窗关闭逻辑,使用 mutateAsync 替代 useEffect 监听 isSuccess
- 成功后自动关闭弹窗,失败后保持弹窗打开并显示错误提示
This commit is contained in:
2026-04-22 11:36:16 +08:00
parent f488b9cc15
commit 141f5f886f
5 changed files with 72 additions and 41 deletions

View File

@@ -1,6 +1,7 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import 'tdesign-react/es/style/index.css'
import 'tdesign-react/es/_util/react-19-adapter'
import './index.scss'
import App from './App'

View File

@@ -14,7 +14,7 @@ interface ModelFormProps {
model?: Model;
providerId: string;
providers: Provider[];
onSave: (values: ModelFormValues) => void;
onSave: (values: ModelFormValues) => Promise<void> | void;
onCancel: () => void;
loading: boolean;
}
@@ -63,6 +63,8 @@ export function ModelForm({
<Dialog
header={isEdit ? '编辑模型' : '添加模型'}
visible={open}
closeOnOverlayClick={false}
closeOnEscKeydown={false}
onConfirm={() => { form?.submit(); return false; }}
onClose={onCancel}
confirmLoading={loading}

View File

@@ -15,7 +15,7 @@ interface ProviderFormValues {
interface ProviderFormProps {
open: boolean;
provider?: Provider;
onSave: (values: ProviderFormValues) => void;
onSave: (values: ProviderFormValues) => Promise<void> | void;
onCancel: () => void;
loading: boolean;
}
@@ -59,6 +59,8 @@ export function ProviderForm({
<Dialog
header={isEdit ? '编辑供应商' : '添加供应商'}
visible={open}
closeOnOverlayClick={false}
closeOnEscKeydown={false}
onConfirm={() => { form?.submit(); return false; }}
onClose={onCancel}
confirmLoading={loading}

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState } from 'react';
import type { Provider, Model, UpdateProviderInput, UpdateModelInput } from '@/types';
import { useProviders, useCreateProvider, useUpdateProvider, useDeleteProvider } from '@/hooks/useProviders';
import { useCreateModel, useUpdateModel } from '@/hooks/useModels';
@@ -20,18 +20,6 @@ export function ProvidersPage() {
const [editingModel, setEditingModel] = useState<Model | undefined>();
const [modelFormProviderId, setModelFormProviderId] = useState('');
useEffect(() => {
if ((createProvider.isSuccess || updateProvider.isSuccess) && providerFormOpen) {
setProviderFormOpen(false);
}
}, [createProvider.isSuccess, updateProvider.isSuccess, providerFormOpen]);
useEffect(() => {
if ((createModel.isSuccess || updateModel.isSuccess) && modelFormOpen) {
setModelFormOpen(false);
}
}, [createModel.isSuccess, updateModel.isSuccess, modelFormOpen]);
return (
<div>
<ProviderTable
@@ -62,16 +50,21 @@ export function ProvidersPage() {
open={providerFormOpen}
provider={editingProvider}
loading={createProvider.isPending || updateProvider.isPending}
onSave={(values) => {
if (editingProvider) {
const input: Partial<UpdateProviderInput> = {};
if (values.name !== editingProvider.name) input.name = values.name;
if (values.apiKey) input.apiKey = values.apiKey;
if (values.baseUrl !== editingProvider.baseUrl) input.baseUrl = values.baseUrl;
if (values.enabled !== editingProvider.enabled) input.enabled = values.enabled;
updateProvider.mutate({ id: editingProvider.id, input });
} else {
createProvider.mutate(values);
onSave={async (values) => {
try {
if (editingProvider) {
const input: Partial<UpdateProviderInput> = {};
if (values.name !== editingProvider.name) input.name = values.name;
if (values.apiKey) input.apiKey = values.apiKey;
if (values.baseUrl !== editingProvider.baseUrl) input.baseUrl = values.baseUrl;
if (values.enabled !== editingProvider.enabled) input.enabled = values.enabled;
await updateProvider.mutateAsync({ id: editingProvider.id, input });
} else {
await createProvider.mutateAsync(values);
}
setProviderFormOpen(false);
} catch {
// 错误已由 hooks 的 onError 处理
}
}}
onCancel={() => setProviderFormOpen(false)}
@@ -83,15 +76,20 @@ export function ProvidersPage() {
providerId={modelFormProviderId}
providers={providers}
loading={createModel.isPending || updateModel.isPending}
onSave={(values) => {
if (editingModel) {
const input: Partial<UpdateModelInput> = {};
if (values.providerId !== editingModel.providerId) input.providerId = values.providerId;
if (values.modelName !== editingModel.modelName) input.modelName = values.modelName;
if (values.enabled !== editingModel.enabled) input.enabled = values.enabled;
updateModel.mutate({ id: editingModel.id, input });
} else {
createModel.mutate(values);
onSave={async (values) => {
try {
if (editingModel) {
const input: Partial<UpdateModelInput> = {};
if (values.providerId !== editingModel.providerId) input.providerId = values.providerId;
if (values.modelName !== editingModel.modelName) input.modelName = values.modelName;
if (values.enabled !== editingModel.enabled) input.enabled = values.enabled;
await updateModel.mutateAsync({ id: editingModel.id, input });
} else {
await createModel.mutateAsync(values);
}
setModelFormOpen(false);
} catch {
// 错误已由 hooks 的 onError 处理
}
}}
onCancel={() => setModelFormOpen(false)}

View File

@@ -37,18 +37,23 @@ TBD - 提供供应商、模型配置和用量统计的前端管理界面
- **WHEN** 用户点击"添加供应商"按钮
- **THEN** 前端 SHALL 使用 TDesign Dialog + Form 显示输入表单
- **THEN** 表单 SHALL 包含 id、name、api_key、base_url 字段,带校验规则
- **THEN** Dialog SHALL 禁用蒙版点击关闭closeOnOverlayClick={false}
- **THEN** Dialog SHALL 禁用 ESC 键关闭closeOnEscKeydown={false}
- **WHEN** 用户提交包含有效数据的表单
- **THEN** 前端 SHALL 通过 useMutation 调用创建 API
- **THEN** 前端 SHALL 通过 mutateAsync 调用创建 API
- **THEN** 成功后 SHALL 关闭 Dialog 并刷新供应商列表
- **THEN** 失败 SHALL 显示错误提示
- **THEN** 失败 SHALL 保持 Dialog 打开并显示错误提示MessagePlugin.error
#### Scenario: 编辑现有供应商
- **WHEN** 用户点击供应商的"编辑"按钮
- **THEN** 前端 SHALL 使用 TDesign Dialog + Form 显示预填充数据的表单
- **THEN** Dialog SHALL 禁用蒙版点击关闭closeOnOverlayClick={false}
- **THEN** Dialog SHALL 禁用 ESC 键关闭closeOnEscKeydown={false}
- **WHEN** 用户提交包含更新数据的表单
- **THEN** 前端 SHALL 通过 useMutation 调用更新 API
- **THEN** 前端 SHALL 通过 mutateAsync 调用更新 API
- **THEN** 成功后 SHALL 关闭 Dialog 并刷新供应商列表
- **THEN** 失败 SHALL 保持 Dialog 打开并显示错误提示MessagePlugin.error
#### Scenario: 删除供应商
@@ -81,9 +86,12 @@ TBD - 提供供应商、模型配置和用量统计的前端管理界面
- **THEN** provider_id SHALL 自动关联当前供应商
- **THEN** 供应商选择 SHALL 使用 `options` 属性
- **THEN** 创建表单 SHALL NOT 包含 ID 输入框(后端自动生成 UUID
- **THEN** Dialog SHALL 禁用蒙版点击关闭closeOnOverlayClick={false}
- **THEN** Dialog SHALL 禁用 ESC 键关闭closeOnEscKeydown={false}
- **WHEN** 用户提交表单
- **THEN** 前端 SHALL 通过 useMutation 调用创建 API
- **THEN** 成功后 SHALL 刷新模型列表
- **THEN** 前端 SHALL 通过 mutateAsync 调用创建 API
- **THEN** 成功后 SHALL 关闭 Dialog 并刷新模型列表
- **THEN** 失败 SHALL 保持 Dialog 打开并显示错误提示MessagePlugin.error
#### Scenario: 编辑模型
@@ -91,9 +99,12 @@ TBD - 提供供应商、模型配置和用量统计的前端管理界面
- **THEN** 前端 SHALL 显示编辑表单
- **THEN** 编辑表单 SHALL 显示统一模型 ID只读
- **THEN** ID 字段 SHALL 为禁用状态
- **THEN** Dialog SHALL 禁用蒙版点击关闭closeOnOverlayClick={false}
- **THEN** Dialog SHALL 禁用 ESC 键关闭closeOnEscKeydown={false}
- **WHEN** 用户提交表单
- **THEN** 前端 SHALL 通过 useMutation 调用更新 API
- **THEN** 成功后 SHALL 刷新模型列表
- **THEN** 前端 SHALL 通过 mutateAsync 调用更新 API
- **THEN** 成功后 SHALL 关闭 Dialog 并刷新模型列表
- **THEN** 失败 SHALL 保持 Dialog 打开并显示错误提示MessagePlugin.error
#### Scenario: 删除模型
@@ -333,6 +344,23 @@ TBD - 提供供应商、模型配置和用量统计的前端管理界面
- **WHEN** 用户使用浏览器后退按钮
- **THEN** 前端 SHALL 正确导航到上一个页面
### Requirement: React 19 适配器
前端 SHALL 导入 TDesign react-19-adapter 以支持 React 19。
#### Scenario: 导入适配器
- **WHEN** 应用启动
- **THEN** main.tsx SHALL 导入 'tdesign-react/es/_util/react-19-adapter'
- **THEN** MessagePlugin、DialogPlugin 等插件式调用 SHALL 正常工作
#### Scenario: 错误提示显示
- **WHEN** API 请求失败
- **THEN** MessagePlugin.error SHALL 正确渲染错误提示
- **THEN** 错误提示 SHALL 显示在页面顶部placement: top
- **THEN** 错误提示 SHALL 在 3 秒后自动消失
### Requirement: 使用 React 和 TypeScript
前端 SHALL 使用 React 和 TypeScript 实现,遵循 strict 模式。