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
150 lines
4.1 KiB
TypeScript
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;
|