WuYongJie 63ec6b7dfb fix: 修复排行榜按钮消失、tag排序和输入框浅色模式问题
1. 排行榜悬浮窗关闭后小按钮消失问题
   - 修复面板居中模式关闭时按钮位置错误
   - 保存并恢复按钮的居中状态

2. 首页tag按钮按素材数量排序
   - 添加按素材数量降序排序逻辑

3. 上传弹窗输入框浅色模式文字不可见
   - 添加 text-slate-900 确保浅色模式下文字可见
   - 添加 placeholder 样式确保占位符正确显示
2025-12-14 21:41:29 +08:00

636 lines
31 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { Fragment, DragEvent, useState, useMemo } from 'react'
import { cardStyles, inputStyles, inputModifiers, cn } from '@/lib/ui-styles'
import type { FieldForm, DragHoverState } from '../../template-manager/types'
import FieldValidationConfig from './FieldValidationConfig'
import FieldConditionsConfig from './FieldConditionsConfig'
interface StandardFieldOption {
key: string
label: string
}
interface LayoutOption {
value: string
label: string
}
interface TemplateFieldsEditorProps {
fields: FieldForm[]
dragFieldIndex: number | null
dragHover: DragHoverState | null
editingFieldIndex: number | null
standardFields: StandardFieldOption[]
getStandardFieldTemplate?: (key: string) => FieldForm | null
fieldTypes: string[]
layoutOptions: LayoutOption[]
lockedCategoryFields?: Set<string>
onEditingFieldChange: (index: number | null) => void
onFieldDragStart: (event: DragEvent<HTMLElement>, index: number) => void
onFieldDragOver: (event: DragEvent<HTMLDivElement>, index: number, surface: 'editor') => void
onFieldDrop: (event: DragEvent<HTMLDivElement>) => void
onFieldDragEnd: () => void
onContainerDragOver: (event: DragEvent<HTMLDivElement>) => void
onContainerDrop: (event: DragEvent<HTMLDivElement>) => void
onContainerDragLeave: (event: DragEvent<HTMLDivElement>) => void
onMoveField: (index: number, direction: 'up' | 'down') => void
onRemoveField: (index: number) => void
onDuplicateField?: (index: number) => void
onAddField: () => void
renderDropPlaceholder: (key: string) => React.ReactNode
onUpdateField: (index: number, patch: Partial<FieldForm>) => void
}
// 字段key验证函数
function validateFieldKey(key: string, currentIndex: number, allFields: FieldForm[]): {
valid: boolean
error?: string
warning?: string
} {
const trimmedKey = key.trim()
// 空值检查
if (!trimmedKey) {
return { valid: false, error: '字段键名不能为空' }
}
// 格式检查:只允许字母、数字、下划线,且必须以字母开头
const keyPattern = /^[a-zA-Z][a-zA-Z0-9_]*$/
if (!keyPattern.test(trimmedKey)) {
return {
valid: false,
error: '键名必须以字母开头,只能包含字母、数字和下划线'
}
}
// 长度检查
if (trimmedKey.length > 50) {
return { valid: false, error: '键名长度不能超过50个字符' }
}
// 唯一性检查
const duplicateIndex = allFields.findIndex(
(f, idx) => idx !== currentIndex && f.key.trim() === trimmedKey
)
if (duplicateIndex !== -1) {
return {
valid: false,
error: `键名重复:字段 ${duplicateIndex + 1} 已使用此键名`
}
}
// 警告:建议使用驼峰命名
if (trimmedKey.includes('_') && trimmedKey.toLowerCase() === trimmedKey) {
return {
valid: true,
warning: '建议使用驼峰命名(如 productModel)而非下划线命名'
}
}
return { valid: true }
}
export default function TemplateFieldsEditor({
fields,
dragFieldIndex,
dragHover,
editingFieldIndex,
standardFields,
getStandardFieldTemplate,
fieldTypes,
layoutOptions,
lockedCategoryFields,
onEditingFieldChange,
onFieldDragStart,
onFieldDragOver,
onFieldDrop,
onFieldDragEnd,
onContainerDragOver,
onContainerDrop,
onContainerDragLeave,
onMoveField,
onRemoveField,
onDuplicateField,
onAddField,
renderDropPlaceholder,
onUpdateField,
}: TemplateFieldsEditorProps) {
// 删除确认对话框状态
const [deleteConfirmIndex, setDeleteConfirmIndex] = useState<number | null>(null)
// 实时验证所有字段的key
const fieldValidations = useMemo(() => {
return fields.map((field, index) => validateFieldKey(field.key, index, fields))
}, [fields])
// 复制字段功能
const handleDuplicateField = (index: number) => {
if (onDuplicateField) {
onDuplicateField(index)
}
}
// 删除字段(带确认)
const handleDeleteField = (index: number) => {
if (fields.length === 1) {
// 不显示确认对话框,直接提示
return
}
setDeleteConfirmIndex(index)
}
const confirmDelete = () => {
if (deleteConfirmIndex !== null) {
onRemoveField(deleteConfirmIndex)
setDeleteConfirmIndex(null)
}
}
const cancelDelete = () => {
setDeleteConfirmIndex(null)
}
return (
<div className={cn(cardStyles.base, 'p-4 space-y-5')}>
<div>
<h4 className="text-lg font-semibold text-slate-900 dark:text-white">字段配置</h4>
<p className="text-sm text-slate-500 dark:text-slate-400 mt-1">添加用户上传时需要填写的字段</p>
<p className="text-[11px] text-slate-400 dark:text-slate-500">
先选预设再按需调整,使用上下箭头按钮调整字段顺序。
</p>
</div>
<div
className="space-y-3"
onDragOver={onContainerDragOver}
onDrop={onContainerDrop}
onDragLeave={onContainerDragLeave}
>
{fields.map((field, index) => {
const isDragging = dragFieldIndex === index
const placeholderBefore =
dragFieldIndex !== null &&
dragHover &&
dragHover.index === index &&
dragHover.position === 'before'
const placeholderAfter =
dragFieldIndex !== null &&
dragHover &&
dragHover.index === index &&
dragHover.position === 'after'
const standardOption = standardFields.find((opt) => opt.key === field.key)
const selectValue = standardOption ? standardOption.key : '__custom'
// 判断是否为下拉选择类型 - 使用多种方式确保准确性
const fieldTypeStr = String(field.type || '').trim()
const isSelectType = fieldTypeStr === 'select'
const isLockedCategoryField = Boolean(lockedCategoryFields?.has(field.key))
// 获取当前字段的验证结果
const validation = fieldValidations[index]
const hasError = !validation.valid
const hasWarning = validation.valid && validation.warning
return (
<Fragment key={`editor-field-${index}`}>
{placeholderBefore && renderDropPlaceholder(`editor-before-${index}`)}
<div
id={`editor-field-${index}`}
className={`group border rounded-xl p-4 transition-all duration-200 ease-out ${
hasError
? 'border-red-300 dark:border-red-600 bg-red-50/50 dark:bg-red-900/10'
: editingFieldIndex === index
? 'border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 shadow-md'
: 'border-slate-200/60 dark:border-slate-700/60 bg-slate-50/50 dark:bg-slate-800/40 hover:border-slate-300 dark:hover:border-slate-600 hover:bg-white dark:hover:bg-slate-800 hover:shadow-sm'
} ${
isDragging
? 'opacity-40 scale-[0.96] cursor-grabbing border-blue-500 dark:border-blue-400 shadow-lg shadow-blue-200/50 dark:shadow-blue-900/50 transition-all duration-200'
: 'cursor-default'
}`}
draggable={true}
onDragStart={(e) => onFieldDragStart(e, index)}
onDragOver={(e) => onFieldDragOver(e, index, 'editor')}
onDrop={onFieldDrop}
onDragEnd={onFieldDragEnd}
onMouseEnter={() => onEditingFieldChange(index)}
onMouseLeave={() => onEditingFieldChange(null)}
>
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
{/* 拖拽手柄 */}
<div
className="cursor-grab active:cursor-grabbing p-1 hover:bg-slate-200 dark:hover:bg-slate-700 rounded transition-colors"
title="拖拽排序"
>
<svg className="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 8h16M4 16h16" />
</svg>
</div>
<span className="px-2 py-0.5 text-xs font-semibold bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 rounded">
字段 {index + 1}
</span>
<span className="text-sm font-medium text-slate-900 dark:text-white">
{field.label || field.key || '未命名字段'}
</span>
{/* 验证状态图标 */}
{hasError && (
<svg className="w-4 h-4 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
)}
{hasWarning && (
<svg className="w-4 h-4 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
)}
</div>
<div className="flex items-center gap-1.5">
{/* 复制按钮 */}
<button
onClick={(e) => {
e.stopPropagation()
handleDuplicateField(index)
}}
className="p-1.5 text-slate-500 dark:text-slate-400 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/30 rounded-lg transition-all duration-200"
title="复制字段"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
</button>
<button
onClick={(e) => {
e.stopPropagation()
onMoveField(index, 'up')
}}
disabled={index === 0}
className="p-1.5 text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-slate-200 hover:bg-slate-200 dark:hover:bg-slate-700 rounded-lg transition-all duration-200 disabled:opacity-40 disabled:cursor-not-allowed"
title="上移"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 15l7-7 7 7" />
</svg>
</button>
<button
onClick={(e) => {
e.stopPropagation()
onMoveField(index, 'down')
}}
disabled={index === fields.length - 1}
className="p-1.5 text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-slate-200 hover:bg-slate-200 dark:hover:bg-slate-700 rounded-lg transition-all duration-200 disabled:opacity-40 disabled:cursor-not-allowed"
title="下移"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
<button
onClick={(e) => {
e.stopPropagation()
handleDeleteField(index)
}}
className="p-1.5 text-red-500 dark:text-red-400 hover:text-red-700 dark:hover:text-red-300 hover:bg-red-50 dark:hover:bg-red-900/30 rounded-lg transition-all duration-200"
title="删除"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
</div>
{/* 验证错误/警告提示 */}
{(hasError || hasWarning) && (
<div className={`mb-3 p-2 rounded-lg text-xs ${
hasError
? 'bg-red-100 dark:bg-red-900/20 text-red-700 dark:text-red-300 border border-red-200 dark:border-red-800'
: 'bg-yellow-100 dark:bg-yellow-900/20 text-yellow-700 dark:text-yellow-300 border border-yellow-200 dark:border-yellow-800'
}`}>
<div className="flex items-start gap-2">
<svg className="w-4 h-4 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{hasError ? (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
) : (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
)}
</svg>
<span>{hasError ? validation.error : validation.warning}</span>
</div>
</div>
)}
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs font-medium text-slate-700 dark:text-slate-300 mb-1.5">
字段键名 <span className="text-red-500">*</span>
</label>
<div className="space-y-1.5">
<select
value={selectValue}
onChange={(e) => {
e.stopPropagation()
const value = e.target.value
if (value === '__custom') {
onUpdateField(index, { key: '', label: '', autoLabel: field.autoLabel })
return
}
const option = standardFields.find((item) => item.key === value)
if (!option) return
// 如果提供了获取标准字段模板的函数,应用完整的模板
if (getStandardFieldTemplate) {
const template = getStandardFieldTemplate(option.key)
if (template) {
// 应用完整模板,但保留用户可能已修改的 autoLabel
const shouldAutoLabel = field.autoLabel !== false
const patch: Partial<FieldForm> = {
...template,
autoLabel: shouldAutoLabel ? true : field.autoLabel,
}
if (!shouldAutoLabel) {
patch.label = field.label || template.label
}
onUpdateField(index, patch)
return
}
}
// 如果没有模板函数,只更新 key 和 label(向后兼容)
const shouldAutoLabel = field.autoLabel !== false
const patch: Partial<FieldForm> = {
key: option.key,
autoLabel: shouldAutoLabel ? true : field.autoLabel,
}
if (shouldAutoLabel) {
patch.label = option.label
}
onUpdateField(index, patch)
}}
onClick={(e) => e.stopPropagation()}
className={cn(inputStyles.base, inputModifiers.compact)}
>
<option value="__custom">自定义字段(手动输入键名)</option>
<optgroup label="标准字段">
{standardFields.map((option) => (
<option key={option.key} value={option.key}>
{option.label}({option.key})
</option>
))}
</optgroup>
</select>
{selectValue === '__custom' ? (
<input
key={`field-key-input-${index}`}
type="text"
value={field.key || ''}
onChange={(e) => {
e.stopPropagation()
onUpdateField(index, { key: e.target.value })
}}
onClick={(e) => e.stopPropagation()}
className="w-full px-3 py-2 text-sm border border-slate-300 dark:border-slate-600 dark:bg-slate-900 dark:text-white rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all duration-200"
placeholder="例如:productModel"
autoComplete="off"
/>
) : (
<p className="text-[11px] text-slate-500 dark:text-slate-400">
键名:<span className="font-mono">{field.key}</span>
</p>
)}
</div>
</div>
<div>
<label className="block text-xs font-medium text-slate-700 dark:text-slate-300 mb-1.5">显示名称</label>
<input
key={`field-label-input-${index}`}
type="text"
value={field.label || ''}
onChange={(e) => {
e.stopPropagation()
onUpdateField(index, { label: e.target.value, autoLabel: false })
}}
onClick={(e) => e.stopPropagation()}
className="w-full px-3 py-2 text-sm border border-slate-300 dark:border-slate-600 dark:bg-slate-900 dark:text-white rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all duration-200"
placeholder="例如:商品型号"
autoComplete="off"
/>
</div>
<div>
<label className="block text-xs font-medium text-slate-700 dark:text-slate-300 mb-1.5">字段类型</label>
<select
value={field.type}
onChange={(e) => {
onUpdateField(index, { type: e.target.value })
}}
onClick={(e) => e.stopPropagation()}
className="w-full px-3 py-2 text-sm border border-slate-300 dark:border-slate-600 dark:bg-slate-900 dark:text-white rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all duration-200"
>
{fieldTypes
.filter((type) => type !== 'image' && type !== 'file')
.map((type) => (
<option key={type} value={type}>
{type === 'text'
? '文本'
: type === 'textarea'
? '多行文本'
: type === 'number'
? '数字'
: type === 'select'
? '下拉选择'
: type === 'checkbox'
? '复选框'
: type === 'radio'
? '单选'
: type === 'date'
? '日期'
: type === 'uppercase'
? '仅大写+字母'
: type}
</option>
))}
</select>
</div>
<div>
<label className="block text-xs font-medium text-slate-700 dark:text-slate-300 mb-1.5">布局宽度</label>
<select
value={field.layoutWidth}
onChange={(e) => onUpdateField(index, { layoutWidth: e.target.value })}
onClick={(e) => e.stopPropagation()}
className="w-full px-3 py-2 text-sm border border-slate-300 dark:border-slate-600 dark:bg-slate-900 dark:text-white rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all duration-200"
>
{layoutOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
</div>
{/* 选项管理提示 - 仅在select或radio类型时显示 */}
{(isSelectType || field.type === 'radio') && (
<div className="mt-3 p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
<div className="flex items-start gap-2">
<svg className="w-4 h-4 text-blue-500 dark:text-blue-400 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div className="text-xs text-blue-700 dark:text-blue-300">
<p className="font-medium mb-1">下拉选项管理</p>
<p className="text-blue-600 dark:text-blue-400">
勾选「添加到分类」后,可在分类管理中统一管理此字段的选项值。
这样可以确保所有模板使用一致的选项,便于维护。
</p>
</div>
</div>
</div>
)}
<div className="flex flex-wrap gap-3 mt-3 pt-3 border-t border-slate-200/60 dark:border-slate-700/60">
<label className="flex items-center gap-1.5 text-xs text-slate-700 dark:text-slate-300 cursor-pointer">
<input
type="checkbox"
checked={field.required ?? false}
onChange={(e) => onUpdateField(index, { required: e.target.checked })}
onClick={(e) => e.stopPropagation()}
className="w-3.5 h-3.5 rounded border-slate-300 dark:border-slate-600 text-blue-600 focus:ring-2 focus:ring-blue-500"
/>
<span>必填</span>
</label>
<label className="flex items-center gap-1.5 text-xs text-slate-700 dark:text-slate-300 cursor-pointer">
<input
type="checkbox"
checked={field.isSearchable ?? false}
onChange={(e) => onUpdateField(index, { isSearchable: e.target.checked })}
onClick={(e) => e.stopPropagation()}
className="w-3.5 h-3.5 rounded border-slate-300 dark:border-slate-600 text-blue-600 focus:ring-2 focus:ring-blue-500"
/>
<span>可搜索</span>
</label>
{/* 添加到分类勾选框 - 仅在下拉选择类型时显示 */}
{isSelectType ? (
<label
className={cn(
'flex items-center gap-1.5 text-xs text-slate-700 dark:text-slate-300',
isLockedCategoryField ? 'cursor-not-allowed opacity-70' : 'cursor-pointer',
)}
title={isLockedCategoryField ? '已全局启用,统一由分类管理控制' : undefined}
>
<input
type="checkbox"
checked={isLockedCategoryField ? true : Boolean(field.addToCategory)}
disabled={isLockedCategoryField}
onChange={(e) => {
onUpdateField(index, { addToCategory: e.target.checked })
}}
onClick={(e) => e.stopPropagation()}
className="w-3.5 h-3.5 rounded border-slate-300 dark:border-slate-600 text-blue-600 focus:ring-2 focus:ring-blue-500"
/>
<span>
添加到分类
{isLockedCategoryField && (
<span className="ml-1 text-[10px] text-slate-400">(全局同步)</span>
)}
</span>
</label>
) : (
// 调试信息:显示为什么不显示复选框
editingFieldIndex === index && field.type === 'select' && (
<div className="text-[10px] text-red-500 dark:text-red-400 font-mono">
调试: type=&quot;{field.type}&quot;, isSelectType={String(isSelectType)}
</div>
)
)}
</div>
{/* 验证规则配置 */}
<FieldValidationConfig
fieldType={field.type}
validations={field.validations || ''}
onChange={(validations) => onUpdateField(index, { validations })}
/>
{/* 条件显示配置 */}
<FieldConditionsConfig
currentFieldKey={field.key}
conditions={field.conditions || ''}
availableFields={fields.map(f => ({ key: f.key, label: f.label || f.key }))}
onChange={(conditions) => onUpdateField(index, { conditions })}
/>
</div>
{placeholderAfter && renderDropPlaceholder(`editor-after-${index}`)}
</Fragment>
)
})}
<button
onClick={onAddField}
className="w-full border-2 border-dashed border-slate-300 dark:border-slate-600 rounded-lg p-4 transition-all duration-200 hover:border-slate-400 dark:hover:border-slate-500 hover:bg-slate-50 dark:hover:bg-slate-800/50 group"
>
<div className="flex items-center justify-center gap-3">
<div className="w-14 h-14 rounded-full bg-slate-100 dark:bg-slate-700 flex items-center justify-center flex-shrink-0 group-hover:bg-slate-200 dark:group-hover:bg-slate-600 transition-colors">
<svg className="w-7 h-7 text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-300 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
</div>
<div className="text-left">
<p className="text-xs font-medium text-slate-600 dark:text-slate-400 group-hover:text-slate-900 dark:group-hover:text-slate-200 transition-colors">
添加元素
</p>
<p className="text-[10px] text-slate-500 dark:text-slate-500 mt-0.5">点击添加新的字段配置</p>
</div>
</div>
</button>
</div>
{/* 删除确认对话框 */}
{deleteConfirmIndex !== null && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm animate-in fade-in duration-200">
<div className="bg-white dark:bg-slate-800 rounded-2xl shadow-2xl max-w-md w-full mx-4 animate-in zoom-in-95 duration-200">
<div className="p-6">
<div className="flex items-start gap-4">
<div className="flex-shrink-0 w-12 h-12 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center">
<svg className="w-6 h-6 text-red-600 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<div className="flex-1">
<h3 className="text-lg font-semibold text-slate-900 dark:text-white mb-2">
确认删除字段
</h3>
<p className="text-sm text-slate-600 dark:text-slate-400 mb-1">
确定要删除字段「{fields[deleteConfirmIndex]?.label || fields[deleteConfirmIndex]?.key || '未命名字段'}」吗?
</p>
<p className="text-xs text-slate-500 dark:text-slate-500">
此操作不可撤销,已使用此模板的素材将无法显示该字段的数据。
</p>
</div>
</div>
</div>
<div className="flex items-center justify-end gap-3 px-6 py-4 bg-slate-50 dark:bg-slate-900/50 rounded-b-2xl border-t border-slate-200 dark:border-slate-700">
<button
onClick={cancelDelete}
className="px-4 py-2 text-sm font-medium text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700 rounded-lg transition-colors"
>
取消
</button>
<button
onClick={confirmDelete}
className="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600 rounded-lg transition-colors"
>
确认删除
</button>
</div>
</div>
</div>
)}
</div>
)
}