/* eslint-disable max-lines-per-function -- 提交表单(客户 / 条数 / 模式 / 备注 + 实时预估 + 幂等提交)集中在一处便于追踪 */ /** * A6 提交演练任务弹窗(方案 A:居中 Modal 560)。 * * - 预估条数 / 成本 / 时长读系统配置(useTaskEstimateConfig),缺省回退 shared 默认值; * - 同一次打开复用同一 Idempotency-Key(失败重试不会创建重复任务),关闭后销毁; * - 提交中禁用按钮防重复提交;失败保留输入并 toast 错误码 / trace_id。 */ import { Button, Input, InputNumber, Modal, Select } from '@douyinfe/semi-ui-19'; import type { MediaTaskDemoMode, MediaTaskSubmitResponse } from '@lingxi/shared'; import { notifyError, notifySuccess } from '@lingxi/ui'; import { IconAlert, IconCheck, IconPlay } from '@lingxi/ui/icons'; import type { ReactNode } from 'react'; import { useState } from 'react'; import { createTaskIdempotencyKey } from '@/api/media'; import { formatMoney, formatSeconds, formatTaskNo } from '@/components/tasks'; import { useSubmitMediaTaskMutation, useTaskEstimateConfig } from '@/hooks/queries/use-media-tasks'; import { describeRequestError, TASK_DEMO_MODE_LABELS } from './task-filter-utils'; export interface TaskSubmitCustomer { id: string; name: string; industryName: string; } export interface TaskSubmitModalProps { customers: readonly TaskSubmitCustomer[]; loadingCustomers: boolean; onClose: () => void; onSubmitted: (result: MediaTaskSubmitResponse) => void; } const DEFAULT_ITEM_COUNT = 8; const NOTE_MAX_LENGTH = 200; const DEMO_MODES: readonly { value: MediaTaskDemoMode; description: string }[] = [ { value: 'success', description: '逐项成功,结束后进入待预览' }, { value: 'mixed', description: '按序号模拟失败,可重试失败项' }, { value: 'fail', description: '验证全部失败与失败原因展示' }, ]; function clampCount(value: number, max: number): number { if (!Number.isFinite(value)) return DEFAULT_ITEM_COUNT; return Math.max(1, Math.min(max, Math.round(value))); } /** 提交演练任务弹窗(仅 material:render 由页面控制挂载) */ export function TaskSubmitModal({ customers, loadingCustomers, onClose, onSubmitted, }: TaskSubmitModalProps): ReactNode { const estimate = useTaskEstimateConfig(); const submit = useSubmitMediaTaskMutation(); /** 同一次打开复用同一幂等键;组件随弹窗关闭卸载,下次打开自动换新 */ const [idempotencyKey] = useState(createTaskIdempotencyKey); const [customerId, setCustomerId] = useState(''); const [itemCount, setItemCount] = useState(() => clampCount(DEFAULT_ITEM_COUNT, Math.max(1, estimate.config.maxItems)), ); const [mode, setMode] = useState('mixed'); const [note, setNote] = useState(''); const maxItems = Math.max(1, estimate.config.maxItems); const selectedCustomerId = customerId !== '' ? customerId : (customers[0]?.id ?? ''); const count = clampCount(itemCount, maxItems); const estimatedCost = Math.round(count * estimate.config.costPerItem * 100) / 100; const estimatedSeconds = count * estimate.config.secondsPerItem; const canSubmit = selectedCustomerId !== '' && !submit.isPending; const handleSubmit = async (): Promise => { if (!canSubmit) return; const noteText = note.trim(); try { const result = await submit.mutateAsync({ input: { type: 'demo', customerId: selectedCustomerId, itemCount: count, mode, ...(noteText === '' ? {} : { note: noteText }), }, idempotencyKey, }); notifySuccess(`已提交,任务号 ${formatTaskNo(result.taskId)}`); onSubmitted(result); } catch (error) { const info = describeRequestError(error, '提交失败,请稍后重试'); notifyError(info.message, info.traceId); } }; const customerOptions = customers.map((customer) => ({ value: customer.id, label: `${customer.name} · ${customer.industryName}`, })); return ( { if (!submit.isPending) onClose(); }} footer={
} >
提交预估 {estimate.isLoading ? 读取配置中… : null}
任务条数 {count} 项
预估成本 {formatMoney(estimatedCost)}
预估时长 {formatSeconds(estimatedSeconds)}

成本与时长按平台配置预估({formatMoney(estimate.config.costPerItem)} / 项、 {formatSeconds(estimate.config.secondsPerItem)} / 项);重复提交不会重复创建任务。

); }