import { history, useAccess } from '@umijs/max';
|
import { Form, Image, Input, Modal, Space, Table, Tag, message } from 'antd';
|
import type { ColumnsType } from 'antd/es/table';
|
import React, { useEffect, useState } from 'react';
|
import { getLiveCoursePage, updateLiveCoursePassword, updateLiveCourseStatus } from './service';
|
import './style.less';
|
|
type CourseItem = {
|
id: number;
|
title: string;
|
coverImg?: string;
|
reserveCount: number;
|
watchCount: number;
|
quizPoints: number;
|
startTime?: string;
|
liveStatus: number;
|
status: number;
|
expert?: {
|
id: number;
|
name: string;
|
avatar?: string;
|
};
|
};
|
|
const liveStatusMap: Record<number, { text: string; color: string }> = {
|
0: { text: '未开始', color: 'default' },
|
1: { text: '直播中', color: 'processing' },
|
2: { text: '已结束', color: 'default' },
|
};
|
|
const courseStatusMap: Record<number, { text: string; color: string }> = {
|
1: { text: '启用', color: 'success' },
|
0: { text: '禁用', color: 'error' },
|
};
|
|
const LiveBroadcastListPage: React.FC = () => {
|
const access = useAccess();
|
const [list, setList] = useState<CourseItem[]>([]);
|
const [loading, setLoading] = useState(false);
|
const [passwordModalOpen, setPasswordModalOpen] = useState(false);
|
const [passwordSubmitting, setPasswordSubmitting] = useState(false);
|
const [passwordCourseId, setPasswordCourseId] = useState<number>();
|
const [passwordForm] = Form.useForm<{ password: string; confirmPassword: string }>();
|
|
const canView = access['live:course:view'];
|
const canDetail = access['live:course:detail'];
|
const canEdit = access['live:course:edit'];
|
const canStatus = access['live:course:status'];
|
const canPassword = access['live:course:password'];
|
|
const loadList = async () => {
|
setLoading(true);
|
try {
|
const res = await getLiveCoursePage({ page: 1, pageSize: 10 });
|
setList(res.data?.list || []);
|
} finally {
|
setLoading(false);
|
}
|
};
|
|
useEffect(() => {
|
if (!canView) {
|
history.replace('/Welcome');
|
return;
|
}
|
loadList();
|
}, [canView]);
|
|
const handleToggleStatus = async (record: CourseItem) => {
|
const nextStatus = record.status === 1 ? 0 : 1;
|
await updateLiveCourseStatus(record.id, { status: nextStatus });
|
message.success(nextStatus === 1 ? '启用成功' : '禁用成功');
|
await loadList();
|
};
|
|
const openPasswordModal = (record: CourseItem) => {
|
setPasswordCourseId(record.id);
|
passwordForm.resetFields();
|
setPasswordModalOpen(true);
|
};
|
|
const handlePasswordSubmit = async () => {
|
const values = await passwordForm.validateFields();
|
if (passwordCourseId === undefined) {
|
return;
|
}
|
setPasswordSubmitting(true);
|
try {
|
await updateLiveCoursePassword(passwordCourseId, { password: values.password || '' });
|
message.success(values.password ? '直播间密码设置成功' : '直播间密码已清除');
|
setPasswordModalOpen(false);
|
passwordForm.resetFields();
|
} finally {
|
setPasswordSubmitting(false);
|
}
|
};
|
|
const columns: ColumnsType<CourseItem> = [
|
{
|
title: '序号',
|
width: 72,
|
render: (_, __, index) => index + 1,
|
},
|
{
|
title: '课程封面图',
|
dataIndex: 'coverImg',
|
width: 120,
|
render: (value, record) =>
|
value ? (
|
<Image src={value} width={72} height={48} preview={false} />
|
) : (
|
<div className="live-broadcast-table__cover-placeholder">{record.title?.slice(0, 2) || '-'}</div>
|
),
|
},
|
{
|
title: '课程名称',
|
dataIndex: 'title',
|
width: 220,
|
},
|
{
|
title: '开播时间',
|
dataIndex: 'startTime',
|
width: 180,
|
render: (value) => value || '-',
|
},
|
{
|
title: '累计预约人次',
|
dataIndex: 'reserveCount',
|
width: 120,
|
},
|
{
|
title: '累计观看次数',
|
dataIndex: 'watchCount',
|
width: 120,
|
},
|
{
|
title: '可得积分',
|
dataIndex: 'quizPoints',
|
width: 100,
|
render: (value) => `${value || 0}积分`,
|
},
|
{
|
title: '授课专家',
|
dataIndex: 'expert',
|
width: 150,
|
render: (value) => value?.name || '-',
|
},
|
{
|
title: '直播状态',
|
dataIndex: 'liveStatus',
|
width: 110,
|
render: (value) => {
|
const config = liveStatusMap[value] || liveStatusMap[0];
|
return <Tag color={config.color}>{config.text}</Tag>;
|
},
|
},
|
{
|
title: '启用状态',
|
dataIndex: 'status',
|
width: 110,
|
render: (value) => {
|
const config = courseStatusMap[value] || courseStatusMap[0];
|
return <Tag color={config.color}>{config.text}</Tag>;
|
},
|
},
|
{
|
title: '操作',
|
width: 180,
|
render: (_, record, index) => (
|
<Space size={16} className="live-broadcast-table__actions">
|
{canDetail ? (
|
<button type="button" onClick={() => history.push(`/live/course/broadcast/${record.id}`)}>
|
类直播
|
</button>
|
) : null}
|
{canEdit ? (
|
<button type="button" onClick={() => history.push(`/live/course/broadcast/${record.id}/edit`)}>
|
编辑
|
</button>
|
) : null}
|
{canStatus ? (
|
<button type="button" onClick={() => handleToggleStatus(record)}>
|
{record.status === 1 ? '禁用' : '启用'}
|
</button>
|
) : null}
|
{index === 0 && canPassword ? (
|
<button type="button" onClick={() => openPasswordModal(record)}>
|
直播间密码
|
</button>
|
) : null}
|
</Space>
|
),
|
},
|
];
|
|
return (
|
<div className="live-broadcast-page">
|
<div className="live-broadcast-page__panel">
|
<div className="live-broadcast-page__title">养生直播</div>
|
<div className="live-broadcast-page__hint">直播课程固定为 2 条,仅支持详情、编辑和启用/禁用操作。</div>
|
<div className="live-broadcast-table-card">
|
<Table<CourseItem> rowKey="id" loading={loading} columns={columns} dataSource={list} pagination={false} />
|
</div>
|
</div>
|
<Modal
|
title="直播间密码"
|
open={passwordModalOpen}
|
okText="保存"
|
cancelText="取消"
|
confirmLoading={passwordSubmitting}
|
onOk={handlePasswordSubmit}
|
onCancel={() => {
|
setPasswordModalOpen(false);
|
passwordForm.resetFields();
|
}}
|
destroyOnClose
|
>
|
<Form form={passwordForm} layout="vertical" preserve={false}>
|
<Form.Item
|
label="直播间密码"
|
name="password"
|
rules={[
|
{ len: 6, message: '直播间密码必须为6个字符' },
|
]}
|
>
|
<Input.Password autoComplete="new-password" placeholder="请输入直播间密码" />
|
</Form.Item>
|
<Form.Item
|
label="确认密码"
|
name="confirmPassword"
|
dependencies={['password']}
|
rules={[
|
({ getFieldValue }) => ({
|
validator(_, value) {
|
return !value || getFieldValue('password') === value
|
? Promise.resolve()
|
: Promise.reject(new Error('两次输入的密码不一致'));
|
},
|
}),
|
]}
|
>
|
<Input.Password autoComplete="new-password" placeholder="请再次输入直播间密码" />
|
</Form.Item>
|
</Form>
|
</Modal>
|
</div>
|
);
|
};
|
|
export default LiveBroadcastListPage;
|