Files
cyh-tk-admin/src/Drama/components/DramaDetail.tsx
zhxiao1124 82524c4312 style: 移除多余的rounded-xl样式并整理代码格式
1. 移除多个组件冗余的rounded-xl类名,统一圆角样式配置
2. 调整Dashboard页面代码缩进与引号风格为统一的双引号格式
3. 简化DramaDetail页面布局结构,重构详情展示区域
4. 清理index.css中多余的descriptions自定义样式
5. 更新gitignore忽略.trae和.mimocode目录
2026-07-03 16:12:00 +08:00

287 lines
13 KiB
TypeScript

import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Button, 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 { channelApi, dramaApi, mediaApi } from "../../api";
import { Panel } from "../../components/Panel";
import { toDramaPayload, toEpisodePayload } from "../formPayload";
import type { Channel, 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: "定时发布",
published: "已发布",
offline: "已下线",
};
export function DramaDetail({ dramaId }: DramaDetailPageProps) {
const navigate = useNavigate();
const [drama, setDrama] = useState<Drama | null>(null);
const [episodes, setEpisodes] = useState<Episode[]>([]);
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 [channels, setChannels] = useState<Channel[]>([]);
const loadDetail = async () => {
setLoading(true);
try {
const [dramaData, episodeList, channelPage] = await Promise.all([dramaApi.getDrama(dramaId), dramaApi.getDramaEpisodes(dramaId), channelApi.listChannels({ page: 1, pageSize: 100 }).catch(() => ({ list: [] }))]);
setDrama(dramaData);
setEpisodeCount(dramaData.totalEpisodes || 1);
setEpisodes(episodeList);
setChannels(channelPage.list);
} finally {
setLoading(false);
}
};
useEffect(() => {
void loadDetail();
}, [dramaId]);
const configureEpisodes = async () => {
await dramaApi.configureDramaEpisodes(dramaId, episodeCount);
Message.success("剧集数量已更新");
await loadDetail();
};
const updateDramaStatus = async (status: string) => {
await dramaApi.updateDrama(dramaId, { status });
Message.success("短剧状态已更新");
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 {
const task = await mediaApi.createUploadTask({
episodeId: episode.id,
fileName: file.name,
contentType: file.type || "video/mp4",
fileSize: file.size,
});
await mediaApi.completeUploadTask(task.jobId);
await mediaApi.syncReviewStatus();
Message.success("视频上传并审核通过");
await loadDetail();
} finally {
setUploadingEpisodeId(null);
}
};
const publishEpisode = async (episode: Episode) => {
await dramaApi.updateEpisode(episode.id, {
publishStatus: "published",
status: "published",
});
Message.success("剧集已发布");
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">
{/* 导航栏 */}
<nav className="w-full box-border bg-[#fff] p-4">
<div className="w-full flex justify-between items-center font-bold text-[1rem]">
<Space size={12}>
<Button icon={<IconArrowLeft />} onClick={() => navigate("/dramas")} />
<span></span>
</Space>
<Button icon={<IconRefresh />} onClick={loadDetail}>
</Button>
</div>
</nav>
{/* 内容区域 */}
<div className="grid grid-cols-1 gap-5 xl:grid-cols-[360px_minmax(0,1fr)]">
<Panel>
{loading && !drama ? (
<div className="py-12 text-center text-muted">...</div>
) : drama ? (
<div className="flex flex-col gap-4">
<Space wrap>
<Button disabled={!drama} onClick={() => setEditVisible(true)}>
</Button>
<Button onClick={() => updateDramaStatus("draft")}>稿</Button>
<Button type="primary" onClick={() => updateDramaStatus("published")}>
</Button>
</Space>
<div>
<div>
<img src={drama.coverUrl} alt={drama.title} className="w-full h-[13rem] object-cover" />
</div>
</div>
<Descriptions
column={1}
data={[
{ label: "短剧ID", value: drama.id },
{ label: "名称", value: drama.title },
{ label: "频道", value: drama.channelName || "-" },
{ label: "地区", value: drama.region || "-" },
{ label: "类型", value: drama.type || "-" },
{ label: "总集数", value: drama.totalEpisodes || 0 },
{ label: "已发布集数", value: drama.publishedEpisodes || 0 },
{ label: "状态", value: PUBLISH_STATUS_LABEL[drama.status] || drama.status },
{ label: "简介", value: drama.description || "-" },
]}
/>
</div>
) : null}
</Panel>
<Panel
title="剧集配置"
extra={
<Space>
<InputNumber min={1} max={500} value={episodeCount} onChange={(value) => setEpisodeCount(Number(value) || 1)} />
<Button type="primary" onClick={configureEpisodes}>
</Button>
</Space>
}
noPadding
>
<EpisodeTable episodes={episodes} loading={loading} uploadingEpisodeId={uploadingEpisodeId} onUploadVideo={uploadEpisodeVideo} onPublishEpisode={publishEpisode} onUpdateEpisode={updateEpisode} />
</Panel>
</div>
{drama && (
<Modal title="编辑短剧" visible={editVisible} onCancel={() => setEditVisible(false)} footer={null} style={{ width: "45rem" }}>
<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,
channelId: drama.channelId,
}}
>
<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>
{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 min={1900} style={{ width: "100%" }} />
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Form.Item label="排序权重" field="sortOrder">
<InputNumber style={{ width: "100%" }} />
</Form.Item>
</Col>
<Col span={12}>
<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>
);
}