Some checks failed
CI Check / check (push) Has been cancelled
- 创建统一的Modal组件,采用Apple风格设计 - 实现毛玻璃效果、圆角、动画和细边框描边 - 修复弹窗打开时背景滚动问题 - 修复分类管理中重复卡片和数组字符串显示问题 - 优化选项解析逻辑,添加去重和过滤机制
184 lines
8.3 KiB
TypeScript
184 lines
8.3 KiB
TypeScript
'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>
|
||
)
|
||
}
|
||
|
||
|