flnxi-Wu/app/admin/_components/template-manager/CustomTemplateSection.tsx
2026-05-28 17:37:27 +08:00

230 lines
9.2 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 { 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>
)
}