/** * 占位符表单组件 * 用于合同起草时填写占位符值 */ import { useEffect, useState } from 'react'; import { messageService } from '~/components/ui/MessageModal'; import type { PlaceholderSchema } from '~/types/contract-draft'; interface PlaceholderFormProps { schema: PlaceholderSchema | null; values: Record; onChange: (values: Record) => void; onComplete: () => void; isDeleting: boolean; // 是否正在删除 onSingleReplace?: (key: string, value: string) => void; // 单个替换 onFieldFocus?: (key: string) => void; // 字段聚焦(高亮) } export function PlaceholderForm({ schema, values, onChange, onComplete, isDeleting, onSingleReplace, onFieldFocus }: PlaceholderFormProps) { const [localValues, setLocalValues] = useState>(values); const [replacingFields, setReplacingFields] = useState>(new Set()); // 【新增】记录当前高亮的字段(只存一个值),避免重复高亮导致焦点被抢 const [currentHighlightedField, setCurrentHighlightedField] = useState(null); // 同步外部 values 到本地状态 useEffect(() => { setLocalValues(values); }, [values]); // 处理字段变化 const handleFieldChange = (key: string, value: string) => { const newValues = { ...localValues, [key]: value }; setLocalValues(newValues); onChange(newValues); }; // 处理字段点击(高亮文档中的占位符) const handleFieldClick = async (e: React.MouseEvent, key: string) => { // 1. 检查是否已经高亮当前字段 if (currentHighlightedField === key) { console.log(`[PlaceholderForm] 字段 "${key}" 已高亮,跳过高亮操作`); return; } // 2. 捕获当前输入框 DOM 元素 const inputElement = e.currentTarget; // 3. 更新当前高亮字段(只保存一个) setCurrentHighlightedField(key); console.log(`[PlaceholderForm] 切换高亮字段到 "${key}"`); // 4. 调用父组件的高亮回调 if (onFieldFocus) { onFieldFocus(key); } // 5. 【核心】延迟后强制夺回焦点(UNO 命令会让 iframe 抢焦点) // 分多次确保焦点回到输入框,防止被 iframe 再次抢走 const refocusWithRetry = () => { console.log(`[PlaceholderForm] 夺回焦点到输入框 "${key}"`); inputElement.focus(); // 保持光标在文字最后 const len = inputElement.value.length; if ('setSelectionRange' in inputElement) { inputElement.setSelectionRange(len, len); } }; // 第一次夺回焦点:150ms(高亮操作完成时) setTimeout(refocusWithRetry, 150); // 第二次确认焦点:300ms(确保没有被再次抢走) setTimeout(refocusWithRetry, 300); }; // 处理单个字段替换 const handleSingleReplace = async (key: string) => { const value = localValues[key]; if (!value || !onSingleReplace) return; setReplacingFields(prev => new Set(prev).add(key)); try { await onSingleReplace(key, value); } finally { setReplacingFields(prev => { const next = new Set(prev); next.delete(key); return next; }); } }; // 检查是否有未填写的必填字段 const getMissingRequiredFields = () => { if (!schema) return []; return schema.fields .filter(f => f.required && !localValues[f.key]) .map(f => f.label); }; const handleCompleteClick = () => { const missing = getMissingRequiredFields(); if (missing.length > 0) { messageService.show({ type: 'warning', title: '字段校验失败', message: '请填写以下必填字段:', children: (
    {missing.map((field, index) => (
  • {field}
  • ))}
), confirmText: '确定' }); return; } onComplete(); }; if (!schema || !schema.fields || schema.fields.length === 0) { return (

暂无占位符配置

该模板尚未配置占位符字段

请联系管理员配置模板占位符

); } return (
{/* 表单头部 */}

填写合同信息

{/* 完成按钮 */}
{/* 表单内容区域 */}
{schema?.fields.map((field) => (
{field.type === 'textarea' ? (