230 lines
9.2 KiB
TypeScript
230 lines
9.2 KiB
TypeScript
'use client'
|
||
|
||
import { useThemeStyles, cn } from '@/lib/ui-styles'
|
||
import ProBadge from '@/components/ui/ProBadge'
|
||
import type { DisplayTemplate } from './types'
|
||
|
||
interface CustomTemplateSectionProps {
|
||
loading: boolean
|
||
error: string | null
|
||
templates: DisplayTemplate[]
|
||
filteredTemplates: DisplayTemplate[]
|
||
deletingId: string | null
|
||
canManageCustomTemplates: boolean
|
||
onRequestUpgrade: () => void
|
||
onCreate: () => void
|
||
onPreview: (template: DisplayTemplate) => void
|
||
onActivate: (id: string) => void
|
||
onDeactivate: (id: string) => void
|
||
onEdit: (template: DisplayTemplate) => void
|
||
onDuplicate: (template: DisplayTemplate) => void
|
||
onDelete: (id: string) => void
|
||
}
|
||
|
||
export default function CustomTemplateSection({
|
||
loading,
|
||
error,
|
||
templates,
|
||
filteredTemplates,
|
||
deletingId,
|
||
canManageCustomTemplates,
|
||
onRequestUpgrade,
|
||
onCreate,
|
||
onPreview,
|
||
onActivate,
|
||
onDeactivate,
|
||
onEdit,
|
||
onDuplicate,
|
||
onDelete,
|
||
}: CustomTemplateSectionProps) {
|
||
const styles = useThemeStyles()
|
||
|
||
const runTemplateAction = (action: () => void) => {
|
||
if (!canManageCustomTemplates) {
|
||
onRequestUpgrade()
|
||
return
|
||
}
|
||
action()
|
||
}
|
||
|
||
return (
|
||
<div className="px-6 pt-5 pb-4">
|
||
<div className="flex items-center justify-between gap-4 mb-3">
|
||
<div>
|
||
<div className="flex items-center gap-2">
|
||
<h3 className="text-sm font-semibold text-slate-900 dark:text-white">自定义模板</h3>
|
||
{!canManageCustomTemplates && <ProBadge size="small" />}
|
||
</div>
|
||
<p className="text-xs text-slate-500 dark:text-slate-400 mt-0.5">
|
||
{canManageCustomTemplates
|
||
? '这里列出你自己创建的模板,可继续编辑、复制或删除'
|
||
: '当前许可证未包含自定义模板功能,请联系管理员处理授权'}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{loading ? (
|
||
<div className="p-8 text-center text-slate-500 dark:text-slate-400">加载中...</div>
|
||
) : error ? (
|
||
<div className="p-8 text-center text-red-500 dark:text-red-300">{error}</div>
|
||
) : (
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||
<button
|
||
type="button"
|
||
onClick={() => {
|
||
if (!canManageCustomTemplates) {
|
||
onRequestUpgrade()
|
||
return
|
||
}
|
||
onCreate()
|
||
}}
|
||
className={cn(
|
||
styles.cardStyles.flat,
|
||
'border-dashed border-2 border-slate-300 dark:border-slate-600 flex items-center justify-center gap-3'
|
||
)}
|
||
>
|
||
<div className="w-14 h-14 rounded-full bg-slate-100 dark:bg-slate-800 flex items-center justify-center flex-shrink-0 transition-colors">
|
||
<svg className="w-8 h-8 text-slate-400 dark: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-sm font-semibold text-slate-800 dark:text-slate-200">
|
||
{canManageCustomTemplates ? '新建自定义模板' : '授权后可新建'}
|
||
</p>
|
||
<p className="text-[11px] text-slate-500 dark:text-slate-400 mt-1 leading-snug">
|
||
{canManageCustomTemplates
|
||
? '从空白模板起步,按照业务需求配置字段'
|
||
: '当前不可新建、复制或迁移自定义模板'}
|
||
</p>
|
||
</div>
|
||
</button>
|
||
|
||
{templates.length === 0 ? (
|
||
<div className="col-span-2 p-8 text-center text-slate-500 dark:text-slate-400">暂无自定义模板,点击上方卡片开始创建。</div>
|
||
) : filteredTemplates.length === 0 ? (
|
||
<div className="col-span-2 p-8 text-center text-slate-500 dark:text-slate-400">未找到匹配的模板名称,请调整搜索关键词。</div>
|
||
) : (
|
||
filteredTemplates.map((tpl) => (
|
||
<div
|
||
key={tpl.id}
|
||
onClick={() => onPreview(tpl)}
|
||
className={cn(styles.cardStyles.hover, 'group relative overflow-hidden cursor-pointer')}
|
||
>
|
||
<div className="flex items-start justify-between gap-3">
|
||
<div className="space-y-1">
|
||
<p className="text-sm font-semibold text-slate-900 dark:text-white">{tpl.name}</p>
|
||
{tpl.description && (
|
||
<p className="text-xs text-slate-500 dark:text-slate-400 leading-snug line-clamp-2">{tpl.description}</p>
|
||
)}
|
||
</div>
|
||
<div className="flex flex-col items-end gap-1 text-right">
|
||
<span
|
||
className={`px-2 py-0.5 text-xs font-medium rounded-full transition-all duration-200 ${
|
||
tpl.status === 'active'
|
||
? 'bg-green-100 dark:bg-green-900/40 text-green-700 dark:text-green-300'
|
||
: tpl.status === 'draft'
|
||
? 'bg-yellow-100 dark:bg-yellow-900/40 text-yellow-700 dark:text-yellow-300'
|
||
: 'bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300'
|
||
}`}
|
||
>
|
||
{tpl.statusLabel}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div className="mt-4 flex flex-wrap items-center gap-2 border-t border-slate-200/70 dark:border-slate-700/70 pt-3">
|
||
{tpl.status === 'active' ? (
|
||
<button
|
||
type="button"
|
||
onClick={(e) => {
|
||
e.stopPropagation()
|
||
runTemplateAction(() => onDeactivate(tpl.id))
|
||
}}
|
||
className={cn(
|
||
styles.buttonStyles.secondary,
|
||
styles.buttonModifiers.compactXs,
|
||
styles.buttonModifiers.pill,
|
||
!canManageCustomTemplates && 'opacity-70'
|
||
)}
|
||
>
|
||
取消启用
|
||
</button>
|
||
) : (
|
||
<button
|
||
type="button"
|
||
onClick={(e) => {
|
||
e.stopPropagation()
|
||
runTemplateAction(() => onActivate(tpl.id))
|
||
}}
|
||
className={cn(
|
||
styles.buttonStyles.success,
|
||
styles.buttonModifiers.compactXs,
|
||
styles.buttonModifiers.pill,
|
||
!canManageCustomTemplates && 'opacity-70'
|
||
)}
|
||
>
|
||
启用
|
||
</button>
|
||
)}
|
||
<button
|
||
type="button"
|
||
onClick={(e) => {
|
||
e.stopPropagation()
|
||
runTemplateAction(() => onEdit(tpl))
|
||
}}
|
||
className={cn(
|
||
styles.buttonStyles.secondary,
|
||
styles.buttonModifiers.compactXs,
|
||
styles.buttonModifiers.pill,
|
||
!canManageCustomTemplates && 'opacity-70'
|
||
)}
|
||
>
|
||
编辑
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={(e) => {
|
||
e.stopPropagation()
|
||
runTemplateAction(() => onDuplicate(tpl))
|
||
}}
|
||
className={cn(
|
||
styles.buttonStyles.secondary,
|
||
styles.buttonModifiers.compactXs,
|
||
styles.buttonModifiers.pill,
|
||
'bg-white dark:bg-slate-800 border-slate-300 dark:border-slate-600 text-slate-700 dark:text-slate-200',
|
||
!canManageCustomTemplates && 'opacity-70'
|
||
)}
|
||
>
|
||
复制
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={(e) => {
|
||
e.stopPropagation()
|
||
runTemplateAction(() => onDelete(tpl.id))
|
||
}}
|
||
disabled={deletingId === tpl.id}
|
||
className={cn(
|
||
styles.buttonStyles.danger,
|
||
styles.buttonModifiers.compactXs,
|
||
styles.buttonModifiers.pill,
|
||
'bg-red-500 hover:bg-red-600 text-white',
|
||
!canManageCustomTemplates && 'opacity-70',
|
||
deletingId === tpl.id && 'opacity-70 cursor-wait',
|
||
)}
|
||
>
|
||
{deletingId === tpl.id ? '删除中...' : '删除'}
|
||
</button>
|
||
<span className="ml-auto text-[11px] text-slate-400 dark:text-slate-500">
|
||
{tpl.updatedLabel} · v{tpl.version}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
))
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|