255 lines
12 KiB
TypeScript
255 lines
12 KiB
TypeScript
import { useEffect, useMemo, useState } from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { Button, Form, Grid, Input, InputNumber, InputTag, Message, Modal, Select, Space, Switch, Table, Tag } from "@arco-design/web-react";
|
|
import { IconDelete, IconPlus, IconSearch, IconRefresh } from "@arco-design/web-react/icon";
|
|
import { channelApi, dramaApi } from "../../api";
|
|
import type { DramaQuery } from "../../api/interface";
|
|
import { usePageData } from "../../hooks/usePageData";
|
|
import { Panel } from "../../components/Panel";
|
|
import { toDramaPayload } from "../formPayload";
|
|
import type { Channel, Drama, StatusOption } from "../interface";
|
|
|
|
const { Row, Col } = Grid;
|
|
|
|
const STATUS_OPTIONS: StatusOption[] = [
|
|
{ value: "draft", label: "草稿" },
|
|
{ value: "scheduled", label: "定时发布" },
|
|
{ value: "published", label: "已发布" },
|
|
{ value: "offline", label: "已下线" },
|
|
];
|
|
|
|
const PUBLISH_STATUS_LABEL: Record<string, string> = {
|
|
draft: "草稿",
|
|
scheduled: "定时发布",
|
|
published: "已发布",
|
|
offline: "已下线",
|
|
};
|
|
|
|
const INITIAL_QUERY: DramaQuery = { page: 1, pageSize: 20 };
|
|
|
|
export function DramaList() {
|
|
const navigate = useNavigate();
|
|
const { data, loading, reload, changePage, setFilter } = usePageData<Drama, DramaQuery>(dramaApi.listDramas, INITIAL_QUERY);
|
|
const [visible, setVisible] = useState(false);
|
|
const [creating, setCreating] = useState(false);
|
|
const [filterTitle, setFilterTitle] = useState("");
|
|
const [filterStatus, setFilterStatus] = useState<string | undefined>(undefined);
|
|
const [filterType, setFilterType] = useState("");
|
|
const [channels, setChannels] = useState<Channel[]>([]);
|
|
const defaultChannel = useMemo(() => channels.find((channel) => channel.isDefault) ?? channels[0], [channels]);
|
|
|
|
useEffect(() => {
|
|
channelApi.listChannels({ page: 1, pageSize: 100 }).then((page) => setChannels(page.list)).catch(() => setChannels([]));
|
|
}, []);
|
|
|
|
const handleSearch = () => {
|
|
setFilter({ title: filterTitle || undefined, status: filterStatus, type: filterType || undefined });
|
|
};
|
|
|
|
const handleReset = () => {
|
|
setFilterTitle("");
|
|
setFilterStatus(undefined);
|
|
setFilterType("");
|
|
setFilter({ title: undefined, status: undefined, type: undefined });
|
|
};
|
|
|
|
const createDrama = async (values: Partial<Drama>) => {
|
|
setCreating(true);
|
|
try {
|
|
await dramaApi.createDrama(toDramaPayload(values));
|
|
Message.success("短剧创建成功");
|
|
setVisible(false);
|
|
await reload();
|
|
} finally {
|
|
setCreating(false);
|
|
}
|
|
};
|
|
|
|
const deleteDrama = (drama: Drama) => {
|
|
Modal.confirm({
|
|
title: "删除短剧",
|
|
content: `确认删除短剧「${drama.title}」?删除后该短剧下的剧集也会同步删除。`,
|
|
okButtonProps: { status: "danger" },
|
|
onOk: async () => {
|
|
await dramaApi.deleteDrama(drama.id);
|
|
Message.success("短剧已删除");
|
|
await reload();
|
|
},
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className="page-stack">
|
|
<Panel title="短剧管理" extra={<Button type="primary" icon={<IconPlus />} onClick={() => setVisible(true)}>新增短剧</Button>}>
|
|
<Row gutter={16} align="center">
|
|
<Col span={6}>
|
|
<div className="form-label">短剧标题</div>
|
|
<Input placeholder="搜索短剧标题" value={filterTitle} onChange={setFilterTitle} allowClear />
|
|
</Col>
|
|
<Col span={4}>
|
|
<div className="form-label">状态</div>
|
|
<Select placeholder="全部状态" value={filterStatus} onChange={setFilterStatus} allowClear>
|
|
{STATUS_OPTIONS.map((option) => (
|
|
<Select.Option key={option.value} value={option.value}>
|
|
{option.label}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Col>
|
|
<Col span={4}>
|
|
<div className="form-label">类型</div>
|
|
<Input placeholder="搜索类型" value={filterType} onChange={setFilterType} allowClear />
|
|
</Col>
|
|
<Col span={10}>
|
|
<div className="form-label invisible">操作</div>
|
|
<Space>
|
|
<Button type="primary" icon={<IconSearch />} onClick={handleSearch}>
|
|
查询
|
|
</Button>
|
|
<Button icon={<IconRefresh />} onClick={handleReset}>
|
|
重置
|
|
</Button>
|
|
</Space>
|
|
</Col>
|
|
</Row>
|
|
</Panel>
|
|
|
|
<Panel noPadding>
|
|
<Table
|
|
rowKey="id"
|
|
loading={loading}
|
|
data={data.list}
|
|
pagination={{
|
|
current: data.pagination.page,
|
|
sizeCanChange: true,
|
|
showTotal: true,
|
|
total: data.pagination.total,
|
|
pageSize: data.pagination.pageSize,
|
|
onChange: (page, pageSize) => changePage(page, pageSize),
|
|
}}
|
|
columns={[
|
|
{ title: "ID", dataIndex: "id", width: 80 },
|
|
{ title: "封面图", dataIndex: "coverUrl", width: 120, render: (value) => (value ? <img src={value} alt="封面图" className="rounded-lg object-cover" style={{ width: 80, height: 80 }} /> : null) },
|
|
{ title: "标题", dataIndex: "title" },
|
|
{ title: "地区", dataIndex: "region", width: 120 },
|
|
{ title: "类型", dataIndex: "type", width: 140 },
|
|
{ title: "已发布 / 总集数", dataIndex: "totalEpisodes", width: 180, render: (_, record) => `${record.publishedEpisodes || 0}(已发布)/共${record.totalEpisodes || 0}集` },
|
|
{
|
|
title: "状态",
|
|
dataIndex: "status",
|
|
width: 120,
|
|
render: (status) => <Tag color={status === "published" ? "green" : "gray"}>{PUBLISH_STATUS_LABEL[String(status)] || String(status)}</Tag>,
|
|
},
|
|
{
|
|
title: "推荐",
|
|
dataIndex: "isRecommended",
|
|
width: 100,
|
|
render: (value) => (value ? <Tag color="arcoblue">是</Tag> : <Tag>否</Tag>),
|
|
},
|
|
{
|
|
title: "操作",
|
|
width: 180,
|
|
render: (_, record) => (
|
|
<Space>
|
|
<Button type="text" onClick={() => navigate(`/dramas/${record.id}`)}>
|
|
详情
|
|
</Button>
|
|
<Button type="text" status="danger" icon={<IconDelete />} onClick={() => deleteDrama(record)}>
|
|
删除
|
|
</Button>
|
|
</Space>
|
|
),
|
|
},
|
|
]}
|
|
/>
|
|
</Panel>
|
|
|
|
<Modal title="创建短剧" visible={visible} onCancel={() => setVisible(false)} footer={null} style={{ width: "40rem" }}>
|
|
<Form layout="vertical" onSubmit={createDrama} initialValues={{ status: "draft", isRecommended: false, sortOrder: 0, totalEpisodes: 1, channelId: defaultChannel?.id }}>
|
|
<Form.Item label="标题" field="title" rules={[{ required: true }]}>
|
|
<Input placeholder="请输入短剧标题" />
|
|
</Form.Item>
|
|
<Row gutter={16}>
|
|
<Col span={8}>
|
|
<Form.Item label="频道" field="channelId">
|
|
<Select placeholder="默认频道" allowClear>
|
|
{channels.map((channel) => (
|
|
<Select.Option key={channel.id} value={channel.id}>
|
|
{channel.name}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={8}>
|
|
<Form.Item label="状态" field="status">
|
|
<Select placeholder="请选择状态">
|
|
{STATUS_OPTIONS.map((option) => (
|
|
<Select.Option key={option.value} value={option.value}>
|
|
{option.label}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={8}>
|
|
<Form.Item label="类型" field="type">
|
|
<Input placeholder="请输入类型,如复仇、爱情、动作等" />
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
<Form.Item label="封面地址" field="coverUrl" rules={[{ required: true }]}>
|
|
<Input placeholder="请输入封面图片 URL" />
|
|
</Form.Item>
|
|
<Form.Item label="简介" field="description">
|
|
<Input.TextArea placeholder="请输入短剧简介" autoSize={{ minRows: 2, maxRows: 4 }} />
|
|
</Form.Item>
|
|
<Form.Item label="标签" field="tags">
|
|
<InputTag placeholder="输入后回车添加标签" allowClear />
|
|
</Form.Item>
|
|
<Row gutter={16}>
|
|
<Col span={8}>
|
|
<Form.Item label="地区" field="region">
|
|
<Input placeholder="如 中国" />
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={8}>
|
|
<Form.Item label="语言" field="language">
|
|
<Input placeholder="如 zh-CN" />
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={8}>
|
|
<Form.Item label="年份" field="year">
|
|
<InputNumber placeholder="如 2026" min={1900} style={{ width: "100%" }} />
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
<Row gutter={16}>
|
|
<Col span={8}>
|
|
<Form.Item label="排序权重" field="sortOrder">
|
|
<InputNumber placeholder="数值越大越靠前" style={{ width: "100%" }} />
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={8}>
|
|
<Form.Item label="剧集总数" field="totalEpisodes" rules={[{ required: true }]}>
|
|
<InputNumber min={1} max={500} placeholder="创建剧集占位" style={{ width: "100%" }} />
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={8}>
|
|
<Form.Item label="推荐" field="isRecommended" triggerPropName="checked">
|
|
<Switch />
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
<div className="modal-actions">
|
|
<Button onClick={() => setVisible(false)}>取消</Button>
|
|
<Button type="primary" htmlType="submit" loading={creating}>
|
|
创建
|
|
</Button>
|
|
</div>
|
|
</Form>
|
|
</Modal>
|
|
</div>
|
|
);
|
|
}
|