feat: 添加短剧筛选功能和自定义Panel容器组件

1. 新增DramaQuery类型扩展分页查询参数,支持标题、状态、类型筛选
2. 重构usePageData hook,支持自定义查询类型和筛选状态管理
3. 新建Panel通用容器组件替代原生Card
4. 为短剧管理页面添加搜索筛选UI和逻辑
5. 统一所有页面的分页查询默认参数
6. 调整布局样式优化后台管理界面展示
This commit is contained in:
2026-07-02 18:48:51 +08:00
parent 718d1c7aac
commit 2dace1cf8d
14 changed files with 295 additions and 130 deletions

View File

@@ -1,9 +1,11 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { Button, Card, Form, Grid, Input, InputNumber, InputTag, Message, Modal, Select, Switch, Table, Tag } from "@arco-design/web-react";
import { IconPlus } from "@arco-design/web-react/icon";
import { Button, Form, Grid, Input, InputNumber, InputTag, Message, Modal, Select, Space, Switch, Table, Tag } from "@arco-design/web-react";
import { IconPlus, IconSearch, IconRefresh } from "@arco-design/web-react/icon";
import { 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 { Drama, StatusOption } from "../interface";
@@ -23,11 +25,27 @@ const PUBLISH_STATUS_LABEL: Record<string, string> = {
offline: "已下线",
};
const INITIAL_QUERY: DramaQuery = { page: 1, pageSize: 20 };
export function DramaList() {
const navigate = useNavigate();
const { data, loading, reload, changePage } = usePageData<Drama>(dramaApi.listDramas);
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 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);
@@ -43,15 +61,59 @@ export function DramaList() {
return (
<div className="page-stack">
<Card
className="section-card"
title="短剧管理"
extra={
<Button type="primary" icon={<IconPlus />} onClick={() => setVisible(true)}>
</Button>
}
>
<Panel title="短剧管理">
<Row gutter={16} className="mb-0">
<Col span={6}>
<div className="text-[13px] text-muted mb-1.5"></div>
<Input
placeholder="搜索短剧标题"
value={filterTitle}
onChange={setFilterTitle}
allowClear
/>
</Col>
<Col span={4}>
<div className="text-[13px] text-muted mb-1.5"></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="text-[13px] text-muted mb-1.5"></div>
<Input
placeholder="搜索类型"
value={filterType}
onChange={setFilterType}
allowClear
/>
</Col>
<Col span={10}>
<div className="text-[13px] mb-1.5">&nbsp;</div>
<Space>
<Button type="primary" icon={<IconSearch />} onClick={handleSearch}>
</Button>
<Button icon={<IconRefresh />} onClick={handleReset}>
</Button>
<Button type="primary" status="success" icon={<IconPlus />} onClick={() => setVisible(true)}>
</Button>
</Space>
</Col>
</Row>
</Panel>
<Panel>
<Table
rowKey="id"
loading={loading}
@@ -64,11 +126,11 @@ export function DramaList() {
}}
columns={[
{ title: "ID", dataIndex: "id", width: 80 },
{ title: "封面图", dataIndex: "coverUrl", width: 120, render: (value) => (value ? <img src={value} alt="封面图" style={{ width: 100, height: 100, objectFit: "cover" }} /> : null) },
{ 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}(已发布)/共${record.totalEpisodes}` },
{ title: "已发布 / 总集数", dataIndex: "totalEpisodes", width: 180, render: (_, record) => `${record.publishedEpisodes || 0}(已发布)/共${record.totalEpisodes || 0}` },
{
title: "状态",
dataIndex: "status",
@@ -92,81 +154,82 @@ export function DramaList() {
},
]}
/>
<Modal title="创建短剧" visible={visible} onCancel={() => setVisible(false)} footer={null} style={{ width: 640 }}>
<Form layout="vertical" onSubmit={createDrama} initialValues={{ status: "draft", isRecommended: false, sortOrder: 0, totalEpisodes: 1 }}>
<Form.Item label="标题" field="title" rules={[{ required: true }]}>
<Input placeholder="请输入短剧标题" />
</Form.Item>
<Row gutter={16}>
<Col span={12}>
<Form.Item label="类型" field="type" rules={[{ required: true }]}>
<Input placeholder="请输入类型,如复仇、爱情、动作等" />
</Form.Item>
</Col>
<Col span={12}>
<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>
</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>
</Card>
</Panel>
<Modal title="创建短剧" visible={visible} onCancel={() => setVisible(false)} footer={null} style={{ width: 640 }}>
<Form layout="vertical" onSubmit={createDrama} initialValues={{ status: "draft", isRecommended: false, sortOrder: 0, totalEpisodes: 1 }}>
<Form.Item label="标题" field="title" rules={[{ required: true }]}>
<Input placeholder="请输入短剧标题" />
</Form.Item>
<Row gutter={16}>
<Col span={12}>
<Form.Item label="类型" field="type" rules={[{ required: true }]}>
<Input placeholder="请输入类型,如复仇、爱情、动作等" />
</Form.Item>
</Col>
<Col span={12}>
<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>
</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>
);
}