import { useEffect, useState } from 'react'; import { Button, Form, Input, Modal, Popconfirm, Select, Space, Table, Tag, Typography, message, } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { DEV_PLAN_TASK_STATUS_LABELS, DEV_PLAN_TASK_TYPE_LABELS, SUPPORT_TICKET_STATUS_LABELS, type DevPlanTaskDto, type DevPlanTaskStatusDto, type DevPlanTaskTypeDto, type DevPlanVersionDto, type SupportTicketDto, } from '@dukang/shared-types'; import { request } from '../lib/api'; import { fmtTime } from '../lib/constants'; import { useAdminList } from '../lib/useAdminList'; const TYPE_OPTIONS = (Object.keys(DEV_PLAN_TASK_TYPE_LABELS) as DevPlanTaskTypeDto[]).map((v) => ({ value: v, label: DEV_PLAN_TASK_TYPE_LABELS[v], })); const STATUS_OPTIONS = (Object.keys(DEV_PLAN_TASK_STATUS_LABELS) as DevPlanTaskStatusDto[]).map( (v) => ({ value: v, label: DEV_PLAN_TASK_STATUS_LABELS[v] }), ); const STATUS_COLOR: Record = { TODO: 'default', DEVELOPED: 'blue', RELEASED: 'green', }; const DEFAULT_DISPATCH_SUPPLEMENT = '请按以下任务进入开发流程'; export default function DevPlanTasksPage() { const [filters, setFilters] = useState>({}); const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [modalOpen, setModalOpen] = useState(false); const [dispatchOpen, setDispatchOpen] = useState(false); const [batchEditOpen, setBatchEditOpen] = useState(false); const [batchSaving, setBatchSaving] = useState(false); const [versions, setVersions] = useState([]); const [supportTickets, setSupportTickets] = useState([]); const [editing, setEditing] = useState(null); const [saving, setSaving] = useState(false); const [dispatching, setDispatching] = useState(false); const [form] = Form.useForm(); const [dispatchForm] = Form.useForm<{ supplement?: string }>(); const [batchForm] = Form.useForm<{ status?: DevPlanTaskStatusDto; versionId?: string }>(); useEffect(() => { if (!batchEditOpen) return; request<{ items: DevPlanVersionDto[] }>('/admin/dev-plan/versions?pageSize=100') .then((res) => setVersions(res.items ?? [])) .catch(() => setVersions([])); }, [batchEditOpen]); useEffect(() => { if (!modalOpen || editing) return; request<{ items: SupportTicketDto[] }>('/admin/support-tickets?pageSize=100') .then((res) => setSupportTickets(res.items ?? [])) .catch(() => setSupportTickets([])); }, [modalOpen, editing]); const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList( '/admin/dev-plan/tasks', () => { const qs = new URLSearchParams(); if (filters.status) qs.set('status', filters.status); if (filters.type) qs.set('type', filters.type); if (filters.keyword) qs.set('keyword', filters.keyword); return qs; }, [filters], ); function openCreate() { setEditing(null); form.setFieldsValue({ content: '', type: 'BUG', status: 'TODO', supportTicketId: undefined }); setModalOpen(true); } function openEdit(row: DevPlanTaskDto) { setEditing(row); form.setFieldsValue({ content: row.content, type: row.type, status: row.status }); setModalOpen(true); } async function save() { const values = await form.validateFields(); setSaving(true); try { if (editing) { await request(`/admin/dev-plan/tasks/${editing.id}`, { method: 'PUT', body: JSON.stringify(values), }); message.success('已更新'); } else { await request('/admin/dev-plan/tasks', { method: 'POST', body: JSON.stringify({ content: values.content, type: values.type, supportTicketId: values.supportTicketId || undefined, }), }); message.success('已创建'); } setModalOpen(false); reload(); } catch (e) { message.error(e instanceof Error ? e.message : '保存失败'); } finally { setSaving(false); } } async function remove(id: string) { try { await request(`/admin/dev-plan/tasks/${id}`, { method: 'DELETE' }); message.success('已删除'); reload(); } catch (e) { message.error(e instanceof Error ? e.message : '删除失败'); } } function openDispatch() { dispatchForm.setFieldsValue({ supplement: DEFAULT_DISPATCH_SUPPLEMENT, }); setDispatchOpen(true); } function openBatchEdit() { batchForm.setFieldsValue({ status: undefined, versionId: undefined }); setBatchEditOpen(true); } async function submitBatchEdit() { const values = await batchForm.validateFields(); if (!values.status && !values.versionId) { message.warning('请至少选择状态或关联版本'); return; } setBatchSaving(true); try { await request('/admin/dev-plan/tasks/batch-update', { method: 'POST', body: JSON.stringify({ taskIds: selectedRowKeys, status: values.status || undefined, versionId: values.versionId || undefined, }), }); message.success('批量更新成功'); setBatchEditOpen(false); setSelectedRowKeys([]); reload(); } catch (e) { message.error(e instanceof Error ? e.message : '批量更新失败'); } finally { setBatchSaving(false); } } async function submitDispatch() { const values = await dispatchForm.validateFields(); setDispatching(true); try { await request('/admin/dev-plan/tasks/dispatch', { method: 'POST', body: JSON.stringify({ taskIds: selectedRowKeys, supplement: values.supplement?.trim() || undefined, }), }); message.success('已派发到企微群'); setDispatchOpen(false); setSelectedRowKeys([]); reload(); } catch (e) { message.error(e instanceof Error ? e.message : '派发失败'); } finally { setDispatching(false); } } const columns: ColumnsType = [ { title: '任务号', dataIndex: 'taskNo', width: 160 }, { title: '类型', dataIndex: 'type', width: 80, render: (t: DevPlanTaskTypeDto) => DEV_PLAN_TASK_TYPE_LABELS[t], }, { title: '状态', dataIndex: 'status', width: 90, render: (s: DevPlanTaskStatusDto) => ( {DEV_PLAN_TASK_STATUS_LABELS[s]} ), }, { title: '内容', dataIndex: 'content', ellipsis: true }, { title: '来源工单', dataIndex: 'supportTicketNo', width: 140, render: (v) => v || '—' }, { title: '创建人', dataIndex: 'creatorName', width: 90 }, { title: '创建时间', dataIndex: 'createdAt', width: 160, render: fmtTime }, { title: '操作', width: 120, render: (_, row) => ( void remove(row.id)}> ), }, ]; return (
开发计划 · 任务列表
{ setFilters(v); setPage(1); }} >
setSelectedRowKeys(keys as string[]), }} scroll={{ x: 1100 }} pagination={{ current: page, pageSize, total: data?.total ?? 0, showSizeChanger: true, onChange: (p, ps) => { setPage(p); setPageSize(ps); }, }} /> setModalOpen(false)} onOk={() => void save()} confirmLoading={saving} destroyOnClose >
) : (