feat: 新增频道管理功能,优化整体UI与交互体验

本次提交完成了以下核心变更:
1.  新增完整的频道管理模块,包括频道增删改查API、页面组件与权限配置
2.  为短剧功能添加频道关联字段,支持将短剧绑定到指定频道
3.  全局UI优化:统一使用Panel组件重构页面布局,统一样式规范与交互细节
4.  表格与日志组件优化:新增请求方法颜色标记、耗时高亮、状态码分级展示
5.  登录页、个人中心、布局等页面的视觉升级与交互优化
6.  完善全局TailwindCSS样式与组件样式自定义
This commit is contained in:
2026-07-03 15:01:27 +08:00
parent 2dace1cf8d
commit e830f5a733
29 changed files with 667 additions and 170 deletions

View File

@@ -1,10 +1,11 @@
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Button, Card, Descriptions, Form, Grid, Input, InputNumber, InputTag, Message, Modal, Select, Space, Switch } from "@arco-design/web-react";
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 { dramaApi, mediaApi } from "../../api";
import { channelApi, dramaApi, mediaApi } from "../../api";
import { Panel } from "../../components/Panel";
import { toDramaPayload, toEpisodePayload } from "../formPayload";
import type { Drama, DramaDetailPageProps, Episode } from "../interface";
import type { Channel, Drama, DramaDetailPageProps, Episode } from "../interface";
import { EpisodeTable } from "./EpisodeTable";
const { Row, Col } = Grid;
@@ -32,14 +33,20 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
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] = await Promise.all([dramaApi.getDrama(dramaId), dramaApi.getDramaEpisodes(dramaId)]);
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);
}
@@ -108,9 +115,7 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
return (
<div className="page-stack">
<Card
className="section-card"
loading={loading && !drama}
<Panel
title={
<Space>
<Button icon={<IconArrowLeft />} onClick={() => navigate("/dramas")} />
@@ -132,12 +137,15 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
</Space>
}
>
{drama && (
{loading && !drama ? (
<div className="py-12 text-center text-muted">...</div>
) : drama ? (
<Descriptions
column={3}
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 },
@@ -147,11 +155,10 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
{ label: "简介", value: drama.description || "-" },
]}
/>
)}
</Card>
) : null}
</Panel>
<Card
className="section-card"
<Panel
title="剧集配置"
extra={
<Space>
@@ -161,6 +168,7 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
</Button>
</Space>
}
noPadding
>
<EpisodeTable
episodes={episodes}
@@ -170,7 +178,7 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
onPublishEpisode={publishEpisode}
onUpdateEpisode={updateEpisode}
/>
</Card>
</Panel>
{drama && (
<Modal title="编辑短剧" visible={editVisible} onCancel={() => setEditVisible(false)} footer={null} style={{ width: 720 }}>
@@ -190,19 +198,25 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
year: drama.year,
sortOrder: drama.sortOrder,
isRecommended: drama.isRecommended,
totalEpisodes: drama.totalEpisodes || 1,
channelId: drama.channelId,
}}
>
<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="请输入类型" />
<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={12}>
<Col span={8}>
<Form.Item label="状态" field="status">
<Select>
{STATUS_OPTIONS.map((option) => (
@@ -213,6 +227,11 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
</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" />
@@ -241,17 +260,12 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
</Col>
</Row>
<Row gutter={16}>
<Col span={8}>
<Col span={12}>
<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}>
<Col span={12}>
<Form.Item label="推荐" field="isRecommended" triggerPropName="checked">
<Switch />
</Form.Item>