feat: 完善短剧管理后台功能,新增编辑、详情展示优化

1.  重构接口类型定义,新增创建/更新短剧和剧集的载荷类型
2.  新增表单数据转换工具,规范化表单提交参数
3.  优化短剧列表展示,新增封面图和更清晰的集数统计
4.  新增短剧和剧集的编辑弹窗功能
5.  修复测试文件导入路径和README标题
6.  优化角色管理页面的表单提示样式
This commit is contained in:
2026-07-02 17:59:31 +08:00
parent a4a1891a7e
commit 96eaa810f3
10 changed files with 542 additions and 193 deletions

View File

@@ -1,11 +1,21 @@
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Button, Card, Descriptions, InputNumber, Message, Space } from "@arco-design/web-react";
import { Button, Card, Descriptions, Form, Grid, Input, InputNumber, InputTag, Message, Modal, Select, Space, Switch } from "@arco-design/web-react";
import { IconArrowLeft, IconRefresh } from "@arco-design/web-react/icon";
import { dramaApi, mediaApi } from "../../api";
import { toDramaPayload, toEpisodePayload } from "../formPayload";
import type { Drama, DramaDetailPageProps, Episode } from "../interface";
import { EpisodeTable } from "./EpisodeTable";
const { Row, Col } = Grid;
const STATUS_OPTIONS = [
{ value: "draft", label: "草稿" },
{ value: "scheduled", label: "定时发布" },
{ value: "published", label: "已发布" },
{ value: "offline", label: "已下线" },
];
const PUBLISH_STATUS_LABEL: Record<string, string> = {
draft: "草稿",
scheduled: "定时发布",
@@ -20,6 +30,8 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
const [loading, setLoading] = useState(false);
const [episodeCount, setEpisodeCount] = useState(1);
const [uploadingEpisodeId, setUploadingEpisodeId] = useState<number | null>(null);
const [editVisible, setEditVisible] = useState(false);
const [updatingDrama, setUpdatingDrama] = useState(false);
const loadDetail = async () => {
setLoading(true);
@@ -49,6 +61,18 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
await loadDetail();
};
const updateDrama = async (values: Partial<Drama>) => {
setUpdatingDrama(true);
try {
await dramaApi.updateDrama(dramaId, toDramaPayload(values));
Message.success("短剧信息已更新");
setEditVisible(false);
await loadDetail();
} finally {
setUpdatingDrama(false);
}
};
const uploadEpisodeVideo = async (episode: Episode, file: File) => {
setUploadingEpisodeId(episode.id);
try {
@@ -76,6 +100,12 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
await loadDetail();
};
const updateEpisode = async (episode: Episode, values: Partial<Episode>) => {
await dramaApi.updateDramaEpisode(dramaId, episode.id, toEpisodePayload(values));
Message.success("剧集信息已更新");
await loadDetail();
};
return (
<div className="page-stack">
<Card
@@ -92,6 +122,9 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
<Button icon={<IconRefresh />} onClick={loadDetail}>
</Button>
<Button disabled={!drama} onClick={() => setEditVisible(true)}>
</Button>
<Button onClick={() => updateDramaStatus("draft")}>稿</Button>
<Button type="primary" onClick={() => updateDramaStatus("published")}>
@@ -135,8 +168,104 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
uploadingEpisodeId={uploadingEpisodeId}
onUploadVideo={uploadEpisodeVideo}
onPublishEpisode={publishEpisode}
onUpdateEpisode={updateEpisode}
/>
</Card>
{drama && (
<Modal title="编辑短剧" visible={editVisible} onCancel={() => setEditVisible(false)} footer={null} style={{ width: 720 }}>
<Form
key={drama.id}
layout="vertical"
onSubmit={updateDrama}
initialValues={{
title: drama.title,
type: drama.type,
status: drama.status,
coverUrl: drama.coverUrl,
description: drama.description,
tags: drama.tags,
region: drama.region,
language: drama.language,
year: drama.year,
sortOrder: drama.sortOrder,
isRecommended: drama.isRecommended,
totalEpisodes: drama.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>
{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 min={1900} style={{ width: "100%" }} />
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={8}>
<Form.Item label="排序权重" field="sortOrder">
<InputNumber style={{ width: "100%" }} />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label="剧集总数" field="totalEpisodes" rules={[{ required: true }]}>
<InputNumber min={1} max={500} 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={() => setEditVisible(false)}></Button>
<Button type="primary" htmlType="submit" loading={updatingDrama}>
</Button>
</div>
</Form>
</Modal>
)}
</div>
);
}