Files
xiaoxia-saas/apps/web/src/pages/editing-planner/components/TemplatePanel.tsx
T
xiaoxia 560856cf22
Deploy / Build Production Runtime Images (push) Has been skipped
Deploy / Deploy Production (push) Has been skipped
Deploy / Production Browser E2E (push) Has been skipped
Deploy / Deploy Staging (push) Failing after 210h26m26s
CI/CD Pipeline / Frontend Lint (push) Failing after 210h27m0s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 210h27m7s
fix: resolve all flake8 errors and apply Prettier formatting (#131)
2026-06-30 18:26:53 +08:00

150 lines
4.1 KiB
TypeScript

/**
* 左侧模板面板
* 搜索、分类筛选、模板卡片列表
*/
import React from "react";
import {
Input,
Select,
Card,
Tag,
Empty,
Spin,
Button,
Typography,
} from "antd";
import { SearchOutlined } from "@ant-design/icons";
import {
MODE_LABELS,
MODE_COLORS,
type EditingTemplate,
type TemplateCategory,
type TemplateMode,
} from "@/api/editingPlanner";
const { Text } = Typography;
interface TemplatePanelProps {
templates: EditingTemplate[];
categories: TemplateCategory[];
isLoading: boolean;
searchText: string;
filterCategory: string;
loadedTemplateId: string | null;
onSearchChange: (v: string) => void;
onCategoryChange: (v: string) => void;
onTemplateSelect: (tpl: EditingTemplate) => void;
onNewTemplate: () => void;
}
const TemplatePanel: React.FC<TemplatePanelProps> = ({
templates,
categories,
isLoading,
searchText,
filterCategory,
loadedTemplateId,
onSearchChange,
onCategoryChange,
onTemplateSelect,
onNewTemplate,
}) => {
return (
<div className="ep-left">
<div style={{ padding: "0 12px", marginBottom: 12 }}>
<Text
strong
style={{ fontSize: 14, display: "block", marginBottom: 8 }}
>
我的模板
</Text>
<Input
prefix={<SearchOutlined />}
placeholder="搜索模板..."
value={searchText}
onChange={(e) => onSearchChange(e.target.value)}
allowClear
size="small"
style={{ marginBottom: 8 }}
/>
<Select
placeholder="按分类筛选"
value={filterCategory || undefined}
onChange={(v) => onCategoryChange(v || "")}
allowClear
size="small"
style={{ width: "100%" }}
options={categories.map((c) => ({ value: c.name, label: c.name }))}
/>
</div>
<div style={{ padding: "0 12px", flex: 1, overflowY: "auto" }}>
{isLoading ? (
<div style={{ textAlign: "center", padding: 40 }}>
<Spin />
</div>
) : templates.length === 0 ? (
<Empty
description="暂无已保存的模板,请先编辑并保存模板"
image={Empty.PRESENTED_IMAGE_SIMPLE}
style={{ padding: 20 }}
/>
) : (
templates.map((tpl) => (
<Card
key={tpl.id}
size="small"
hoverable
className={`ep-tpl-card ${loadedTemplateId === tpl.id ? "ep-tpl-card-active" : ""}`}
onClick={() => onTemplateSelect(tpl)}
style={{ marginBottom: 8 }}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Text strong ellipsis style={{ maxWidth: 140 }}>
{tpl.name}
</Text>
<Tag
color={MODE_COLORS[tpl.mode as TemplateMode] || "blue"}
style={{ marginRight: 0 }}
>
{MODE_LABELS[tpl.mode as TemplateMode] || tpl.mode}
</Tag>
</div>
<div style={{ marginTop: 4 }}>
<Text type="secondary" style={{ fontSize: 12 }}>
{tpl.segments.length} 片段 · ~{tpl.estimated_duration}s
</Text>
{tpl.tags.length > 0 && (
<div style={{ marginTop: 4 }}>
{tpl.tags.slice(0, 3).map((tag) => (
<Tag key={tag} style={{ fontSize: 11, marginRight: 4 }}>
{tag}
</Tag>
))}
</div>
)}
</div>
</Card>
))
)}
</div>
{loadedTemplateId && (
<div style={{ padding: 12, borderTop: "1px solid #f0f0f0" }}>
<Button size="small" block onClick={onNewTemplate}>
新建空白模板
</Button>
</div>
)}
</div>
);
};
export default TemplatePanel;