164 lines
6.2 KiB
TypeScript
164 lines
6.2 KiB
TypeScript
'use client'
|
||
|
||
import { buttonStyles, buttonModifiers, inputStyles, cn } from '@/lib/ui-styles'
|
||
import type { TemplateForm, BuiltinTemplateConfig } from '../types'
|
||
|
||
interface TemplateBasicsFormProps {
|
||
form: TemplateForm
|
||
statusOptions: Array<{ value: string; label: string }>
|
||
materialTypeOptions: string[]
|
||
showPresetSelector: boolean
|
||
selectedPreset: string | null
|
||
presets: BuiltinTemplateConfig[]
|
||
onChange: (patch: Partial<TemplateForm>) => void
|
||
onSelectPreset: (presetKey: string) => void
|
||
}
|
||
|
||
export default function TemplateBasicsForm({
|
||
form,
|
||
statusOptions,
|
||
materialTypeOptions,
|
||
showPresetSelector,
|
||
selectedPreset,
|
||
presets,
|
||
onChange,
|
||
onSelectPreset,
|
||
}: TemplateBasicsFormProps) {
|
||
const inferMaterialKind = (materialType: string) => {
|
||
if (materialType === '视频') return 'VIDEO'
|
||
if (materialType === '音频') return 'AUDIO'
|
||
if (materialType === '其他') return 'OTHER'
|
||
return 'IMAGE'
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-5">
|
||
<div>
|
||
<label className="block text-sm font-semibold text-slate-900 dark:text-white mb-2">
|
||
模板名称 <span className="text-red-500">*</span>
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={form.name}
|
||
onChange={(e) => onChange({ name: e.target.value })}
|
||
className={cn(inputStyles.base)}
|
||
placeholder="例如:产品素材模板"
|
||
/>
|
||
<p className="text-xs text-slate-500 dark:text-slate-400 mt-1.5">给模板起一个容易识别的名称</p>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-semibold text-slate-900 dark:text-white mb-2">模板描述</label>
|
||
<textarea
|
||
rows={2}
|
||
value={form.description}
|
||
onChange={(e) => onChange({ description: e.target.value })}
|
||
className={cn(inputStyles.base, 'resize-none')}
|
||
placeholder="简单描述这个模板的用途"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-semibold text-slate-900 dark:text-white mb-2">模板状态</label>
|
||
<select
|
||
value={form.status}
|
||
onChange={(e) => onChange({ status: e.target.value })}
|
||
className={cn(inputStyles.base)}
|
||
>
|
||
{statusOptions.map((option) => (
|
||
<option key={option.value} value={option.value}>
|
||
{option.label}
|
||
</option>
|
||
))}
|
||
</select>
|
||
<p className="text-xs text-slate-500 dark:text-slate-400 mt-1.5">草稿:仅保存,不生效;启用:用户可以使用</p>
|
||
</div>
|
||
|
||
<div className="space-y-3">
|
||
<div>
|
||
<label className="block text-sm font-semibold text-slate-900 dark:text-white mb-2">
|
||
素材类型 <span className="text-red-500">*</span>
|
||
</label>
|
||
<select
|
||
value={form.materialType}
|
||
onChange={(e) => onChange({ materialType: e.target.value, materialKind: inferMaterialKind(e.target.value) })}
|
||
className={cn(inputStyles.base)}
|
||
>
|
||
{materialTypeOptions.map((option) => (
|
||
<option key={option} value={option}>
|
||
{option}
|
||
</option>
|
||
))}
|
||
</select>
|
||
<p className="text-xs text-slate-500 dark:text-slate-400 mt-1.5">用于展示的业务类型,例如“视频”、“PSD”等。</p>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-semibold text-slate-900 dark:text-white mb-2">
|
||
素材大类 <span className="text-red-500">*</span>
|
||
</label>
|
||
<div className="flex flex-wrap gap-2">
|
||
{[
|
||
{ label: '图片/文档', value: 'IMAGE', hint: '普通素材,走图片/文件流程' },
|
||
{ label: '音频', value: 'AUDIO', hint: '走音频上传与解析流程' },
|
||
{ label: '视频', value: 'VIDEO', hint: '走视频上传与转码流程' },
|
||
{ label: '其他', value: 'OTHER', hint: '暂不归类,可后续扩展' },
|
||
].map((option) => {
|
||
const active = (form.materialKind || 'IMAGE') === option.value
|
||
return (
|
||
<button
|
||
key={option.value}
|
||
type="button"
|
||
onClick={() => onChange({ materialKind: option.value })}
|
||
className={cn(
|
||
active ? buttonStyles.primary : buttonStyles.secondary,
|
||
buttonModifiers.pill,
|
||
'flex flex-col items-start gap-0.5 px-4 py-2 text-left min-w-[120px]',
|
||
)}
|
||
>
|
||
<span className="text-sm font-semibold">{option.label}</span>
|
||
<span className="text-[11px] text-current/70">{option.hint}</span>
|
||
</button>
|
||
)
|
||
})}
|
||
</div>
|
||
<p className="text-xs text-slate-500 dark:text-slate-400 mt-1.5">用于驱动上传/处理链路,音频模板请选择“音频”,视频模板请选择“视频”。</p>
|
||
</div>
|
||
</div>
|
||
|
||
{showPresetSelector && (
|
||
<div>
|
||
<label className="block text-sm font-semibold text-slate-900 dark:text-white mb-2">选择预设模板</label>
|
||
<div className="flex flex-wrap gap-2">
|
||
{presets.map((preset) => {
|
||
const active = selectedPreset === preset.key
|
||
return (
|
||
<button
|
||
key={preset.key}
|
||
type="button"
|
||
onClick={() => onSelectPreset(preset.key)}
|
||
className={cn(
|
||
active ? buttonStyles.primary : buttonStyles.secondary,
|
||
buttonModifiers.compactXs,
|
||
buttonModifiers.pill,
|
||
'flex items-center gap-1.5 shadow-none',
|
||
!active && 'border-dashed',
|
||
)}
|
||
>
|
||
{active && <span className="inline-block w-1.5 h-1.5 rounded-full bg-white" />}
|
||
<span>{preset.name}</span>
|
||
</button>
|
||
)
|
||
})}
|
||
</div>
|
||
<p className="text-xs text-slate-500 dark:text-slate-400 mt-1.5">
|
||
选择一个预设模板后,下方字段会自动填充,可在此基础上继续增删或调整。
|
||
</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
|