fix: 修复供应商管理弹窗交互问题
- 导入 TDesign react-19-adapter 修复 MessagePlugin 在 React 19 下的渲染错误 - Dialog 禁用蒙版点击和 ESC 键关闭,防止误操作丢失表单数据 - 重构弹窗关闭逻辑,使用 mutateAsync 替代 useEffect 监听 isSuccess - 成功后自动关闭弹窗,失败后保持弹窗打开并显示错误提示
This commit is contained in:
@@ -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'
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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 模式。
|
||||
|
||||
Reference in New Issue
Block a user