style: 统一项目代码风格与中文文本规范

本次提交完成以下调整:
1. 在main.tsx中添加Arco Design适配React19的适配器导入,修复命令式组件兼容问题
2. 全项目替换单引号为双引号,统一代码格式化风格
3. 重构所有JSX组件的多行写法,拆分复杂属性提升可读性
4. 修正部分组件的代码缩进与换行规范
This commit is contained in:
2026-07-01 23:28:54 +08:00
parent e6d93d7614
commit 9362625b4e
2 changed files with 296 additions and 120 deletions

View File

@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from "react-router-dom";
import { import {
Button, Button,
Card, Card,
@@ -16,36 +16,38 @@ import {
Switch, Switch,
Table, Table,
Tag, Tag,
} from '@arco-design/web-react'; } from "@arco-design/web-react";
import { IconArrowLeft, IconPlus, IconRefresh } from '@arco-design/web-react/icon'; import {
import type { ApiResponse, Drama, Episode, UploadTask } from '../api/types'; IconArrowLeft,
import { api } from '../api/client'; IconPlus,
import { usePageData } from '../hooks/usePageData'; IconRefresh,
} from "@arco-design/web-react/icon";
import type { ApiResponse, Drama, Episode, UploadTask } from "../api/types";
import { api } from "../api/client";
import { usePageData } from "../hooks/usePageData";
const { Row, Col } = Grid; const { Row, Col } = Grid;
const TYPE_OPTIONS = ['revenge', 'romance', 'action'];
const STATUS_OPTIONS: { value: string; label: string }[] = [ const STATUS_OPTIONS: { value: string; label: string }[] = [
{ value: 'draft', label: '草稿' }, { value: "draft", label: "草稿" },
{ value: 'scheduled', label: '定时发布' }, { value: "scheduled", label: "定时发布" },
{ value: 'published', label: '已发布' }, { value: "published", label: "已发布" },
{ value: 'offline', label: '已下线' }, { value: "offline", label: "已下线" },
]; ];
const REVIEW_STATUS_LABEL: Record<string, string> = { const REVIEW_STATUS_LABEL: Record<string, string> = {
not_submitted: '未提交', not_submitted: "未提交",
pending: '待审核', pending: "待审核",
reviewing: '审核中', reviewing: "审核中",
approved: '已通过', approved: "已通过",
rejected: '已拒绝', rejected: "已拒绝",
}; };
const PUBLISH_STATUS_LABEL: Record<string, string> = { const PUBLISH_STATUS_LABEL: Record<string, string> = {
draft: '草稿', draft: "草稿",
scheduled: '定时发布', scheduled: "定时发布",
published: '已发布', published: "已发布",
offline: '已下线', offline: "已下线",
}; };
function DramasPage() { function DramasPage() {
@@ -60,15 +62,15 @@ function DramasPage() {
function DramaListPage() { function DramaListPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const { data, loading, reload } = usePageData<Drama>('/api/admin/v1/dramas'); const { data, loading, reload } = usePageData<Drama>("/api/admin/v1/dramas");
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [creating, setCreating] = useState(false); const [creating, setCreating] = useState(false);
const createDrama = async (values: Partial<Drama>) => { const createDrama = async (values: Partial<Drama>) => {
setCreating(true); setCreating(true);
try { try {
await api.post('/api/admin/v1/dramas', values); await api.post("/api/admin/v1/dramas", values);
Message.success('短剧创建成功'); Message.success("短剧创建成功");
setVisible(false); setVisible(false);
await reload(); await reload();
} finally { } finally {
@@ -77,23 +79,80 @@ function DramaListPage() {
}; };
return ( return (
<Card className="section-card" title="短剧管理" extra={<Button type="primary" icon={<IconPlus />} onClick={() => setVisible(true)}></Button>}> <Card
<Table rowKey="id" loading={loading} data={data.list} pagination={{ total: data.pagination.total, pageSize: data.pagination.pageSize }} columns={[ className="section-card"
{ title: 'ID', dataIndex: 'id', width: 80 }, title="短剧管理"
{ title: '标题', dataIndex: 'title' }, extra={
{ title: '地区', dataIndex: 'region', width: 120 }, <Button
{ title: '类型', dataIndex: 'type', width: 140 }, type="primary"
{ title: '总集数', dataIndex: 'totalEpisodes', width: 100 }, icon={<IconPlus />}
{ title: '已发布', dataIndex: 'publishedEpisodes', width: 100 }, onClick={() => setVisible(true)}
{ title: '状态', dataIndex: 'status', width: 120, render: (status) => <Tag color={status === 'published' ? 'green' : 'gray'}>{PUBLISH_STATUS_LABEL[String(status)] || String(status)}</Tag> }, >
{ title: '推荐', dataIndex: 'isRecommended', width: 100, render: (value) => value ? <Tag color="arcoblue"></Tag> : <Tag></Tag> },
{ title: '操作', width: 120, render: (_, record) => <Button type="text" onClick={() => navigate(`/dramas/${record.id}`)}></Button> }, </Button>
]} /> }
<Modal title="创建短剧" visible={visible} onCancel={() => setVisible(false)} footer={null} style={{ width: 640 }}> >
<Table
rowKey="id"
loading={loading}
data={data.list}
pagination={{
total: data.pagination.total,
pageSize: data.pagination.pageSize,
}}
columns={[
{ title: "ID", dataIndex: "id", width: 80 },
{ title: "标题", dataIndex: "title" },
{ title: "地区", dataIndex: "region", width: 120 },
{ title: "类型", dataIndex: "type", width: 140 },
{ title: "总集数", dataIndex: "totalEpisodes", width: 100 },
{ title: "已发布", dataIndex: "publishedEpisodes", width: 100 },
{
title: "状态",
dataIndex: "status",
width: 120,
render: (status) => (
<Tag color={status === "published" ? "green" : "gray"}>
{PUBLISH_STATUS_LABEL[String(status)] || String(status)}
</Tag>
),
},
{
title: "推荐",
dataIndex: "isRecommended",
width: 100,
render: (value) =>
value ? <Tag color="arcoblue"></Tag> : <Tag></Tag>,
},
{
title: "操作",
width: 120,
render: (_, record) => (
<Button
type="text"
onClick={() => navigate(`/dramas/${record.id}`)}
>
</Button>
),
},
]}
/>
<Modal
title="创建短剧"
visible={visible}
onCancel={() => setVisible(false)}
footer={null}
style={{ width: 640 }}
>
<Form <Form
layout="vertical" layout="vertical"
onSubmit={createDrama} onSubmit={createDrama}
initialValues={{ status: 'draft', isRecommended: false, sortOrder: 0 }} initialValues={{
status: "draft",
isRecommended: false,
sortOrder: 0,
}}
> >
<Form.Item label="标题" field="title" rules={[{ required: true }]}> <Form.Item label="标题" field="title" rules={[{ required: true }]}>
<Input placeholder="请输入短剧标题" /> <Input placeholder="请输入短剧标题" />
@@ -101,24 +160,33 @@ function DramaListPage() {
<Row gutter={16}> <Row gutter={16}>
<Col span={12}> <Col span={12}>
<Form.Item label="类型" field="type" rules={[{ required: true }]}> <Form.Item label="类型" field="type" rules={[{ required: true }]}>
<Select placeholder="选择或输入类型" allowCreate> <Input placeholder="输入类型" />
{TYPE_OPTIONS.map((type) => <Select.Option key={type} value={type}>{type}</Select.Option>)}
</Select>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item label="状态" field="status"> <Form.Item label="状态" field="status">
<Select placeholder="请选择状态"> <Select placeholder="请选择状态">
{STATUS_OPTIONS.map((option) => <Select.Option key={option.value} value={option.value}>{option.label}</Select.Option>)} {STATUS_OPTIONS.map((option) => (
<Select.Option key={option.value} value={option.value}>
{option.label}
</Select.Option>
))}
</Select> </Select>
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
<Form.Item label="封面地址" field="coverUrl" rules={[{ required: true }]}> <Form.Item
label="封面地址"
field="coverUrl"
rules={[{ required: true }]}
>
<Input placeholder="请输入封面图片 URL" /> <Input placeholder="请输入封面图片 URL" />
</Form.Item> </Form.Item>
<Form.Item label="简介" field="description"> <Form.Item label="简介" field="description">
<Input.TextArea placeholder="请输入短剧简介" autoSize={{ minRows: 2, maxRows: 4 }} /> <Input.TextArea
placeholder="请输入短剧简介"
autoSize={{ minRows: 2, maxRows: 4 }}
/>
</Form.Item> </Form.Item>
<Form.Item label="标签" field="tags"> <Form.Item label="标签" field="tags">
<InputTag placeholder="输入后回车添加标签" allowClear /> <InputTag placeholder="输入后回车添加标签" allowClear />
@@ -136,25 +204,38 @@ function DramaListPage() {
</Col> </Col>
<Col span={8}> <Col span={8}>
<Form.Item label="年份" field="year"> <Form.Item label="年份" field="year">
<InputNumber placeholder="如 2026" min={1900} style={{ width: '100%' }} /> <InputNumber
placeholder="如 2026"
min={1900}
style={{ width: "100%" }}
/>
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
<Row gutter={16}> <Row gutter={16}>
<Col span={12}> <Col span={12}>
<Form.Item label="排序权重" field="sortOrder"> <Form.Item label="排序权重" field="sortOrder">
<InputNumber placeholder="数值越大越靠前" style={{ width: '100%' }} /> <InputNumber
placeholder="数值越大越靠前"
style={{ width: "100%" }}
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item label="推荐" field="isRecommended" triggerPropName="checked"> <Form.Item
label="推荐"
field="isRecommended"
triggerPropName="checked"
>
<Switch /> <Switch />
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
<div className="modal-actions"> <div className="modal-actions">
<Button onClick={() => setVisible(false)}></Button> <Button onClick={() => setVisible(false)}></Button>
<Button type="primary" htmlType="submit" loading={creating}></Button> <Button type="primary" htmlType="submit" loading={creating}>
</Button>
</div> </div>
</Form> </Form>
</Modal> </Modal>
@@ -168,14 +249,18 @@ function DramaDetailPage({ dramaId }: { dramaId: number }) {
const [episodes, setEpisodes] = useState<Episode[]>([]); const [episodes, setEpisodes] = useState<Episode[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [episodeCount, setEpisodeCount] = useState(1); const [episodeCount, setEpisodeCount] = useState(1);
const [uploadingEpisodeId, setUploadingEpisodeId] = useState<number | null>(null); const [uploadingEpisodeId, setUploadingEpisodeId] = useState<number | null>(
null,
);
const loadDetail = async () => { const loadDetail = async () => {
setLoading(true); setLoading(true);
try { try {
const [dramaResponse, episodeResponse] = await Promise.all([ const [dramaResponse, episodeResponse] = await Promise.all([
api.get<ApiResponse<Drama>>(`/api/admin/v1/dramas/${dramaId}`), api.get<ApiResponse<Drama>>(`/api/admin/v1/dramas/${dramaId}`),
api.get<ApiResponse<{ list: Episode[] }>>(`/api/admin/v1/dramas/${dramaId}/episodes?pageSize=200`), api.get<ApiResponse<{ list: Episode[] }>>(
`/api/admin/v1/dramas/${dramaId}/episodes?pageSize=200`,
),
]); ]);
setDrama(dramaResponse.data.data); setDrama(dramaResponse.data.data);
setEpisodeCount(dramaResponse.data.data.totalEpisodes || 1); setEpisodeCount(dramaResponse.data.data.totalEpisodes || 1);
@@ -190,29 +275,37 @@ function DramaDetailPage({ dramaId }: { dramaId: number }) {
}, [dramaId]); }, [dramaId]);
const configureEpisodes = async () => { const configureEpisodes = async () => {
await api.post(`/api/admin/v1/dramas/${dramaId}/episodes/batch`, { episodeCount }); await api.post(`/api/admin/v1/dramas/${dramaId}/episodes/batch`, {
Message.success('剧集数量已更新'); episodeCount,
});
Message.success("剧集数量已更新");
await loadDetail(); await loadDetail();
}; };
const updateDramaStatus = async (status: string) => { const updateDramaStatus = async (status: string) => {
await api.patch(`/api/admin/v1/dramas/${dramaId}`, { status }); await api.patch(`/api/admin/v1/dramas/${dramaId}`, { status });
Message.success('短剧状态已更新'); Message.success("短剧状态已更新");
await loadDetail(); await loadDetail();
}; };
const uploadEpisodeVideo = async (episode: Episode, file: File) => { const uploadEpisodeVideo = async (episode: Episode, file: File) => {
setUploadingEpisodeId(episode.id); setUploadingEpisodeId(episode.id);
try { try {
const task = await api.post<ApiResponse<UploadTask>>('/api/admin/v1/media/upload-tasks', { const task = await api.post<ApiResponse<UploadTask>>(
"/api/admin/v1/media/upload-tasks",
{
episodeId: episode.id, episodeId: episode.id,
fileName: file.name, fileName: file.name,
contentType: file.type || 'video/mp4', contentType: file.type || "video/mp4",
fileSize: file.size, fileSize: file.size,
}); },
await api.patch(`/api/admin/v1/media/upload-tasks/${task.data.data.jobId}/complete`, {}); );
await api.post('/api/admin/v1/media/review-status/sync', {}); await api.patch(
Message.success('视频上传并审核通过'); `/api/admin/v1/media/upload-tasks/${task.data.data.jobId}/complete`,
{},
);
await api.post("/api/admin/v1/media/review-status/sync", {});
Message.success("视频上传并审核通过");
await loadDetail(); await loadDetail();
} finally { } finally {
setUploadingEpisodeId(null); setUploadingEpisodeId(null);
@@ -221,10 +314,10 @@ function DramaDetailPage({ dramaId }: { dramaId: number }) {
const publishEpisode = async (episode: Episode) => { const publishEpisode = async (episode: Episode) => {
await api.patch(`/api/admin/v1/episodes/${episode.id}`, { await api.patch(`/api/admin/v1/episodes/${episode.id}`, {
publishStatus: 'published', publishStatus: "published",
status: 'published', status: "published",
}); });
Message.success('剧集已发布'); Message.success("剧集已发布");
await loadDetail(); await loadDetail();
}; };
@@ -233,45 +326,118 @@ function DramaDetailPage({ dramaId }: { dramaId: number }) {
<Card <Card
className="section-card" className="section-card"
loading={loading && !drama} loading={loading && !drama}
title={<Space><Button icon={<IconArrowLeft />} onClick={() => navigate('/dramas')} /></Space>} title={
extra={<Space> <Space>
<Button icon={<IconRefresh />} onClick={loadDetail}></Button> <Button
<Button onClick={() => updateDramaStatus('draft')}>稿</Button> icon={<IconArrowLeft />}
<Button type="primary" onClick={() => updateDramaStatus('published')}></Button> onClick={() => navigate("/dramas")}
</Space>} />
</Space>
}
extra={
<Space>
<Button icon={<IconRefresh />} onClick={loadDetail}>
</Button>
<Button onClick={() => updateDramaStatus("draft")}>稿</Button>
<Button
type="primary"
onClick={() => updateDramaStatus("published")}
>
</Button>
</Space>
}
> >
{drama && ( {drama && (
<Descriptions <Descriptions
column={3} column={3}
data={[ data={[
{ label: '短剧ID', value: drama.id }, { label: "短剧ID", value: drama.id },
{ label: '名称', value: drama.title }, { label: "名称", value: drama.title },
{ label: '地区', value: drama.region || '-' }, { label: "地区", value: drama.region || "-" },
{ label: '类型', value: drama.type }, { label: "类型", value: drama.type },
{ label: '总集数', value: drama.totalEpisodes || 0 }, { label: "总集数", value: drama.totalEpisodes || 0 },
{ label: '已发布集数', value: drama.publishedEpisodes || 0 }, { label: "已发布集数", value: drama.publishedEpisodes || 0 },
{ label: '状态', value: PUBLISH_STATUS_LABEL[drama.status] || drama.status }, {
{ label: '封面', value: drama.coverUrl }, label: "状态",
{ label: '简介', value: drama.description || '-' }, value: PUBLISH_STATUS_LABEL[drama.status] || drama.status,
},
{ label: "封面", value: drama.coverUrl },
{ label: "简介", value: drama.description || "-" },
]} ]}
/> />
)} )}
</Card> </Card>
<Card className="section-card" title="剧集配置" extra={<Space> <Card
<InputNumber min={1} max={500} value={episodeCount} onChange={(value) => setEpisodeCount(Number(value) || 1)} /> className="section-card"
<Button type="primary" onClick={configureEpisodes}></Button> title="剧集配置"
</Space>}> extra={
<Table rowKey="id" loading={loading} data={episodes} pagination={false} columns={[ <Space>
{ title: '集数', dataIndex: 'episodeNo', width: 80 }, <InputNumber
{ title: '标题', dataIndex: 'title' }, min={1}
{ title: 'BytePlus VID', dataIndex: 'byteplusVid', width: 180, render: (value) => value || '-' }, max={500}
{ title: '尺寸', width: 120, render: (_, record) => record.width && record.height ? `${record.width}x${record.height}` : '-' }, value={episodeCount}
{ title: '时长', dataIndex: 'durationSeconds', width: 100, render: (value) => value ? `${value}s` : '-' }, onChange={(value) => setEpisodeCount(Number(value) || 1)}
{ title: '审核状态', dataIndex: 'reviewStatus', width: 120, render: (status) => <Tag color={status === 'approved' ? 'green' : 'orange'}>{REVIEW_STATUS_LABEL[String(status)] || String(status)}</Tag> }, />
{ title: '发布状态', dataIndex: 'publishStatus', width: 120, render: (status) => <Tag color={status === 'published' ? 'green' : 'gray'}>{PUBLISH_STATUS_LABEL[String(status)] || String(status)}</Tag> }, <Button type="primary" onClick={configureEpisodes}>
</Button>
</Space>
}
>
<Table
rowKey="id"
loading={loading}
data={episodes}
pagination={false}
columns={[
{ title: "集数", dataIndex: "episodeNo", width: 80 },
{ title: "标题", dataIndex: "title" },
{ {
title: '操作', title: "BytePlus VID",
dataIndex: "byteplusVid",
width: 180,
render: (value) => value || "-",
},
{
title: "尺寸",
width: 120,
render: (_, record) =>
record.width && record.height
? `${record.width}x${record.height}`
: "-",
},
{
title: "时长",
dataIndex: "durationSeconds",
width: 100,
render: (value) => (value ? `${value}s` : "-"),
},
{
title: "审核状态",
dataIndex: "reviewStatus",
width: 120,
render: (status) => (
<Tag color={status === "approved" ? "green" : "orange"}>
{REVIEW_STATUS_LABEL[String(status)] || String(status)}
</Tag>
),
},
{
title: "发布状态",
dataIndex: "publishStatus",
width: 120,
render: (status) => (
<Tag color={status === "published" ? "green" : "gray"}>
{PUBLISH_STATUS_LABEL[String(status)] || String(status)}
</Tag>
),
},
{
title: "操作",
width: 280, width: 280,
render: (_, record) => ( render: (_, record) => (
<Space> <Space>
@@ -281,22 +447,29 @@ function DramaDetailPage({ dramaId }: { dramaId: number }) {
<input <input
type="file" type="file"
accept="video/*" accept="video/*"
style={{ display: 'none' }} style={{ display: "none" }}
onChange={(event) => { onChange={(event) => {
const file = event.target.files?.[0]; const file = event.target.files?.[0];
if (file) { if (file) {
void uploadEpisodeVideo(record, file); void uploadEpisodeVideo(record, file);
} }
event.target.value = ''; event.target.value = "";
}} }}
/> />
</label> </label>
</Button> </Button>
<Button type="text" disabled={record.reviewStatus !== 'approved'} onClick={() => publishEpisode(record)}></Button> <Button
type="text"
disabled={record.reviewStatus !== "approved"}
onClick={() => publishEpisode(record)}
>
</Button>
</Space> </Space>
), ),
}, },
]} /> ]}
/>
</Card> </Card>
</div> </div>
); );

View File

@@ -1,3 +1,6 @@
// 让 Arco 的命令式组件Message/Notification/Modal.confirm 等)在 React 18/19 下
// 使用 react-dom/client 的 createRoot而非已被移除的 ReactDOM.render。必须在使用 Arco 前引入。
import '@arco-design/web-react/es/_util/react-19-adapter';
import { StrictMode } from 'react'; import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import '@arco-design/web-react/dist/css/arco.css'; import '@arco-design/web-react/dist/css/arco.css';