import { useState, useEffect } from 'react' import { Tabs, Table, Button, Modal, Form, Input, InputNumber, DatePicker, message, Space, Upload, Popconfirm } from 'antd' import { PlusOutlined, EditOutlined, DeleteOutlined, DownloadOutlined, UploadOutlined } from '@ant-design/icons' import { listEpisodes, createEpisode, updateEpisode, deleteEpisode } from '../../services/episodeService' import { listTargets, createTarget, updateTarget, deleteTarget } from '../../services/yearlyTargetService' import http from '../../services/http' const { TabPane } = Tabs // ============================================================ // Tab1: 节目期次 // ============================================================ function EpisodesTab() { const [episodes, setEpisodes] = useState([]) const [loading, setLoading] = useState(false) const [modalVisible, setModalVisible] = useState(false) const [editing, setEditing] = useState(null) const [form] = Form.useForm() useEffect(() => { loadEpisodes() }, []) const loadEpisodes = () => { setLoading(true) listEpisodes().then(data => { setEpisodes((data || []).sort((a, b) => new Date(b.air_date) - new Date(a.air_date))) setLoading(false) }).catch(() => { message.error('加载节目列表失败') setLoading(false) }) } const handleCreate = () => { setEditing(null) form.resetFields() setModalVisible(true) } const handleEdit = (record) => { setEditing(record) form.setFieldsValue({ episode_number: record.episode_number, program_name: record.program_name, air_date: record.air_date ? record.air_date : null, editor_name_snapshot: record.editor_name_snapshot || '', audience_share: record.audience_share, audience_rating: record.audience_rating, is_rerun: record.is_rerun, notes: record.notes, }) setModalVisible(true) } const handleDelete = (id) => { deleteEpisode(id).then(() => { message.success('删除成功') loadEpisodes() }).catch(err => { message.error(err.response?.data?.detail || '删除失败') }) } const handleSubmit = () => { form.validateFields().then(values => { const payload = { ...values, air_date: values.air_date ? values.air_date.format('YYYY-MM-DD') : null, } const promise = editing ? updateEpisode(editing.id, payload) : createEpisode(payload) promise.then(() => { message.success(editing ? '更新成功' : '创建成功') setModalVisible(false) loadEpisodes() }).catch(err => { message.error(err.response?.data?.detail || '操作失败') }) }) } const columns = [ { title: '期次号', dataIndex: 'episode_number', key: 'episode_number', width: 80 }, { title: '节目名', dataIndex: 'program_name', key: 'program_name' }, { title: '播出日期', dataIndex: 'air_date', key: 'air_date', render: d => d || '-' }, { title: '编导', dataIndex: 'editor_name_snapshot', key: 'editor_name_snapshot', render: v => v || '-' }, { title: '收视份额', dataIndex: 'audience_share', key: 'audience_share', render: v => v != null ? v.toFixed(4) : '-' }, { title: '收视率', dataIndex: 'audience_rating', key: 'audience_rating', render: v => v != null ? v.toFixed(2) : '-' }, { title: '操作', key: 'action', render: (_, record) => ( setModalVisible(false)} okText="保存" width={520}>
) } // ============================================================ // Tab2: 年度目标 // ============================================================ function YearlyTargetsTab() { const [targets, setTargets] = useState([]) const [loading, setLoading] = useState(false) const [modalVisible, setModalVisible] = useState(false) const [editing, setEditing] = useState(null) const [form] = Form.useForm() useEffect(() => { loadTargets() }, []) const loadTargets = () => { setLoading(true) listTargets().then(data => { setTargets((data || []).sort((a, b) => b.year - a.year)) setLoading(false) }).catch(() => { message.error('加载年度目标失败') setLoading(false) }) } const handleCreate = () => { setEditing(null) form.resetFields() setModalVisible(true) } const handleEdit = (record) => { setEditing(record) form.setFieldsValue({ year: record.year, base_target: record.base_target, stretch_target: record.stretch_target, notes: record.notes, }) setModalVisible(true) } const handleDelete = (id) => { deleteTarget(id).then(() => { message.success('删除成功') loadTargets() }).catch(err => { message.error(err.response?.data?.detail || '删除失败') }) } const handleSubmit = () => { form.validateFields().then(values => { const promise = editing ? updateTarget(editing.id, values) : createTarget(values) promise.then(() => { message.success(editing ? '更新成功' : '创建成功') setModalVisible(false) loadTargets() }).catch(err => { const detail = err.response?.data?.detail || '' if (detail.includes('已存在') || detail.includes('历史年份')) { message.error('该年目标已存在,历史年份不可覆盖') } else { message.error(detail || '操作失败') } }) }) } const columns = [ { title: '年份', dataIndex: 'year', key: 'year', width: 80 }, { title: '基础目标', dataIndex: 'base_target', key: 'base_target', render: v => v?.toFixed(4) || '-' }, { title: '摸高目标', dataIndex: 'stretch_target', key: 'stretch_target', render: v => v?.toFixed(4) || '-' }, { title: '备注', dataIndex: 'notes', key: 'notes', render: v => v || '-' }, { title: '操作', key: 'action', render: (_, record) => (
setModalVisible(false)} okText="保存">
) } // ============================================================ // Tab3: Excel 批量导入 // ============================================================ function ImportTab() { const [uploading, setUploading] = useState(false) const [result, setResult] = useState(null) const downloadTemplate = () => { window.open('/api/imports/template', '_blank') } const uploadProps = { name: 'file', accept: '.xlsx', showUploadList: false, beforeUpload: (file) => { if (!file.filename.endsWith('.xlsx') && !file.name.endsWith('.xlsx')) { message.error('仅支持 .xlsx 格式') return false } setUploading(true) const formData = new FormData() formData.append('file', file) http.post('/imports/episodes', formData, { headers: { 'Content-Type': 'multipart/form-data' }, }).then(res => { setResult(res.data) setUploading(false) if (res.data.errors?.length) { message.warning(`导入完成:成功 ${res.data.imported} 条,失败 ${res.data.errors.length} 条`) } else { message.success(`导入成功:${res.data.imported} 条`) } }).catch(err => { setUploading(false) message.error(err.response?.data?.detail || '上传失败') }) return false }, } const downloadErrors = () => { if (!result?.batch_id) return window.open(`/api/imports/errors/${result.batch_id}`, '_blank') } return (
{result && (

导入结果

总计:{result.total} 条

成功:{result.imported} 条

{result.errors?.length > 0 && ( <>

失败:{result.errors.length} 条

)}
)}

支持批量导入节目期次,导入失败时可根据提示修正后重新上传。

注意:Phase 2 不支持重播行导入,有重播行请留空。

) } // ============================================================ // 主组件 // ============================================================ function EditorDesk() { return (

责编录入

) } export default EditorDesk