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

180 lines
7.9 KiB
TypeScript

import { useState } from "react";
import { Button, Form, Input, InputNumber, Message, Modal, Select, Space, Switch, Table, Tag } from "@arco-design/web-react";
import { IconDelete, IconEdit, IconPlus } from "@arco-design/web-react/icon";
import { channelApi } from "../api";
import type { Channel, ChannelQuery, CreateChannelPayload } from "../api";
import { Panel } from "../components/Panel";
import { usePageData } from "../hooks/usePageData";
const INITIAL_QUERY: ChannelQuery = { page: 1, pageSize: 20 };
const CHANNEL_STATUS_LABEL: Record<string, string> = {
active: "启用",
disabled: "停用",
};
type ChannelFormValues = CreateChannelPayload;
export default function ChannelPage() {
const { data, loading, reload, changePage } = usePageData<Channel, ChannelQuery>(channelApi.listChannels, INITIAL_QUERY);
const [createVisible, setCreateVisible] = useState(false);
const [editingChannel, setEditingChannel] = useState<Channel | null>(null);
const [saving, setSaving] = useState(false);
const createChannel = async (values: ChannelFormValues) => {
setSaving(true);
try {
await channelApi.createChannel({
...values,
sortOrder: Number(values.sortOrder) || 0,
isDefault: Boolean(values.isDefault),
status: values.status || "active",
});
Message.success("频道创建成功");
setCreateVisible(false);
await reload();
} finally {
setSaving(false);
}
};
const updateChannel = async (values: ChannelFormValues) => {
if (!editingChannel) {
return;
}
setSaving(true);
try {
await channelApi.updateChannel(editingChannel.id, {
...values,
sortOrder: Number(values.sortOrder) || 0,
isDefault: Boolean(values.isDefault),
status: values.status || "active",
});
Message.success("频道已更新");
setEditingChannel(null);
await reload();
} finally {
setSaving(false);
}
};
const deleteChannel = (channel: Channel) => {
Modal.confirm({
title: "删除频道",
content: `确认删除频道「${channel.name}」?`,
okButtonProps: { status: "danger" },
onOk: async () => {
await channelApi.deleteChannel(channel.id);
Message.success("频道已删除");
await reload();
},
});
};
const renderForm = (onSubmit: (values: ChannelFormValues) => Promise<void>, initialValues: ChannelFormValues) => (
<Form layout="vertical" initialValues={initialValues} onSubmit={onSubmit}>
<Form.Item label="频道名称" field="name" rules={[{ required: true }]}>
<Input placeholder="请输入频道名称" />
</Form.Item>
<Form.Item label="频道编码" field="code" rules={[{ required: true }]}>
<Input placeholder="请输入频道编码,如 default" disabled={Boolean(editingChannel?.isDefault)} />
</Form.Item>
<Form.Item label="描述" field="description">
<Input.TextArea placeholder="请输入频道描述" autoSize={{ minRows: 2, maxRows: 4 }} />
</Form.Item>
<Space size="large" align="start">
<Form.Item label="排序权重" field="sortOrder">
<InputNumber min={0} style={{ width: 180 }} />
</Form.Item>
<Form.Item label="状态" field="status">
<Select style={{ width: 180 }} disabled={Boolean(editingChannel?.isDefault)}>
<Select.Option value="active"></Select.Option>
<Select.Option value="disabled"></Select.Option>
</Select>
</Form.Item>
<Form.Item label="默认频道" field="isDefault" triggerPropName="checked">
<Switch disabled={Boolean(editingChannel?.isDefault)} />
</Form.Item>
</Space>
<div className="modal-actions">
<Button onClick={() => {
setCreateVisible(false);
setEditingChannel(null);
}}>
</Button>
<Button type="primary" htmlType="submit" loading={saving}>
</Button>
</div>
</Form>
);
return (
<div className="page-stack">
<Panel title="频道管理" extra={<Button type="primary" icon={<IconPlus />} onClick={() => setCreateVisible(true)}></Button>} noPadding>
<Table
rowKey="id"
loading={loading}
data={data.list}
pagination={{
current: data.pagination.page,
pageSize: data.pagination.pageSize,
total: data.pagination.total,
sizeCanChange: true,
showTotal: true,
onChange: (page, pageSize) => changePage(page, pageSize),
}}
columns={[
{ title: "ID", dataIndex: "id", width: 80 },
{ title: "名称", dataIndex: "name" },
{ title: "编码", dataIndex: "code", width: 180 },
{ title: "排序", dataIndex: "sortOrder", width: 100 },
{
title: "默认",
dataIndex: "isDefault",
width: 100,
render: (value) => (value ? <Tag color="arcoblue"></Tag> : <Tag></Tag>),
},
{
title: "状态",
dataIndex: "status",
width: 100,
render: (status) => <Tag color={status === "active" ? "green" : "gray"}>{CHANNEL_STATUS_LABEL[String(status)] || String(status)}</Tag>,
},
{
title: "操作",
width: 180,
render: (_, record) => (
<Space>
<Button type="text" icon={<IconEdit />} onClick={() => setEditingChannel(record)}>
</Button>
<Button type="text" status="danger" icon={<IconDelete />} disabled={record.isDefault} onClick={() => deleteChannel(record)}>
</Button>
</Space>
),
},
]}
/>
</Panel>
<Modal title="新增频道" visible={createVisible} onCancel={() => setCreateVisible(false)} footer={null} style={{ width: 640 }}>
{renderForm(createChannel, { sortOrder: 0, status: "active", isDefault: false })}
</Modal>
<Modal title="编辑频道" visible={Boolean(editingChannel)} onCancel={() => setEditingChannel(null)} footer={null} style={{ width: 640 }}>
{editingChannel && renderForm(updateChannel, {
name: editingChannel.name,
code: editingChannel.code,
description: editingChannel.description,
sortOrder: editingChannel.sortOrder,
status: editingChannel.status,
isDefault: editingChannel.isDefault,
})}
</Modal>
</div>
);
}