1. 移除多个组件冗余的rounded-xl类名,统一圆角样式配置 2. 调整Dashboard页面代码缩进与引号风格为统一的双引号格式 3. 简化DramaDetail页面布局结构,重构详情展示区域 4. 清理index.css中多余的descriptions自定义样式 5. 更新gitignore忽略.trae和.mimocode目录
287 lines
13 KiB
TypeScript
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>
|
|
);
|
|
}
|