flnxi-Wu/app/admin/_components/template-manager/CustomTemplateSection.tsx
WuYongJie 8705b9846a
Some checks failed
CI Check / check (push) Has been cancelled
feat: 统一弹窗样式系统与分类管理优化
- 创建统一的Modal组件,采用Apple风格设计
- 实现毛玻璃效果、圆角、动画和细边框描边
- 修复弹窗打开时背景滚动问题
- 修复分类管理中重复卡片和数组字符串显示问题
- 优化选项解析逻辑,添加去重和过滤机制
2025-11-25 09:37:00 +08:00

184 lines
8.3 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 type { DisplayTemplate } from './types'
interface CustomTemplateSectionProps {
loading: boolean
error: string | null
templates: DisplayTemplate[]
filteredTemplates: DisplayTemplate[]
deletingId: string | null
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,
onCreate,
onPreview,
onActivate,
onDeactivate,
onEdit,
onDuplicate,
onDelete,
}: CustomTemplateSectionProps) {
const styles = useThemeStyles()
return (
<div className="px-6 pt-5 pb-4">
<div className="flex items-center justify-between gap-4 mb-3">
<div>
<h3 className="text-sm font-semibold text-slate-900 dark:text-white">自定义模板</h3>
<p className="text-xs text-slate-500 dark:text-slate-400 mt-0.5">这里列出你自己创建的模板,可继续编辑、复制或删除</p>
<p className="text-[11px] text-slate-400 dark:text-slate-500">这些模板才会被真实上传面板使用,确保状态为“启用”后用户才能看到。</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={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 group-hover:bg-slate-200 dark:group-hover:bg-slate-700 transition-colors">
<svg className="w-8 h-8 text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-200 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">新建自定义模板</p>
<p className="text-[11px] text-slate-500 dark:text-slate-400 mt-1 leading-snug">从空白模板起步,完全按照业务需求自由配置字段</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="absolute inset-x-0 top-0 h-1 bg-gradient-to-r from-purple-500/70 via-blue-500/60 to-purple-500/70 opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
<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 tracking-tight">{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>
<span className="text-[10px] font-semibold uppercase tracking-[0.18em] text-blue-500/80 dark:text-blue-300/80 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
预览
</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()
onDeactivate(tpl.id)
}}
className={cn(styles.buttonStyles.secondary, styles.buttonModifiers.compactXs, styles.buttonModifiers.pill)}
>
取消启用
</button>
) : (
<button
type="button"
onClick={(e) => {
e.stopPropagation()
onActivate(tpl.id)
}}
className={cn(styles.buttonStyles.success, styles.buttonModifiers.compactXs, styles.buttonModifiers.pill)}
>
启用
</button>
)}
<button
type="button"
onClick={(e) => {
e.stopPropagation()
onEdit(tpl)
}}
className={cn(styles.buttonStyles.secondary, styles.buttonModifiers.compactXs, styles.buttonModifiers.pill)}
>
编辑
</button>
<button
type="button"
onClick={(e) => {
e.stopPropagation()
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' // 显式添加背景色、边框和文字色
)}
>
复制
</button>
<button
type="button"
onClick={(e) => {
e.stopPropagation()
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', // 显式添加背景色和文字色,确保在浅色模式下可见
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>
)
}