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_VERSION_STATUS_LABELS, type DevPlanTaskDto, type DevPlanVersionDto, type DevPlanVersionStatusDto, } from '@dukang/shared-types'; import { request } from '../lib/api'; import { fmtTime } from '../lib/constants'; import { useAdminList } from '../lib/useAdminList'; const STATUS_OPTIONS = (Object.keys(DEV_PLAN_VERSION_STATUS_LABELS) as DevPlanVersionStatusDto[]).map( (v) => ({ value: v, label: DEV_PLAN_VERSION_STATUS_LABELS[v] }), ); const STATUS_COLOR: Record = { PENDING: 'default', IN_PROGRESS: 'processing', TESTING: 'purple', RELEASED: 'green', }; function formatDuration(minutes?: number | null) { if (minutes == null) return '—'; if (minutes < 60) return `${minutes} 分钟`; return `${Math.floor(minutes / 60)} 小时 ${minutes % 60} 分`; } export default function DevPlanVersionsPage() { const [filters, setFilters] = useState>({}); const [modalOpen, setModalOpen] = useState(false); const [editing, setEditing] = useState(null); const [saving, setSaving] = useState(false); const [allTasks, setAllTasks] = useState([]); const [form] = Form.useForm(); const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList('/admin/dev-plan/versions', () => { const qs = new URLSearchParams(); if (filters.status) qs.set('status', filters.status); return qs; }, [filters]); useEffect(() => { void request<{ items: DevPlanTaskDto[] }>('/admin/dev-plan/tasks?page=1&pageSize=500') .then((res) => setAllTasks(res.items ?? [])) .catch(() => setAllTasks([])); }, []); async function openEdit(row: DevPlanVersionDto) { const detail = await request(`/admin/dev-plan/versions/${row.id}`); setEditing(detail); form.setFieldsValue({ versionNo: detail.versionNo, content: detail.content ?? '', status: detail.status, taskIds: detail.taskIds ?? [], }); setModalOpen(true); } function openCreate() { setEditing(null); form.setFieldsValue({ versionNo: '', content: '', status: 'PENDING', taskIds: [] }); setModalOpen(true); } async function save() { const values = await form.validateFields(); setSaving(true); try { const body = { versionNo: values.versionNo.trim(), content: values.content?.trim() || undefined, status: values.status, taskIds: values.taskIds ?? [], }; if (editing) { await request(`/admin/dev-plan/versions/${editing.id}`, { method: 'PUT', body: JSON.stringify(body), }); message.success('已更新'); } else { await request('/admin/dev-plan/versions', { method: 'POST', body: JSON.stringify(body) }); 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/versions/${id}`, { method: 'DELETE' }); message.success('已删除'); reload(); } catch (e) { message.error(e instanceof Error ? e.message : '删除失败'); } } const columns: ColumnsType = [ { title: '版本号', dataIndex: 'versionNo', width: 120 }, { title: '状态', dataIndex: 'status', width: 100, render: (s: DevPlanVersionStatusDto) => ( {DEV_PLAN_VERSION_STATUS_LABELS[s]} ), }, { title: '内容', dataIndex: 'content', ellipsis: true, render: (v) => v || '—' }, { title: '开发起始', dataIndex: 'devStartedAt', width: 160, render: (v) => (v ? fmtTime(v) : '—') }, { title: '开发完成', dataIndex: 'devCompletedAt', width: 160, render: (v) => (v ? fmtTime(v) : '—') }, { title: '上线时间', dataIndex: 'releasedAt', width: 160, render: (v) => (v ? fmtTime(v) : '—') }, { title: '用时', dataIndex: 'durationMinutes', width: 100, render: (v) => formatDuration(v), }, { title: '创建时间', dataIndex: 'createdAt', width: 160, render: fmtTime }, { title: '操作', width: 120, render: (_, row) => ( void remove(row.id)}> ), }, ]; return (
开发计划 · 版本列表
{ setFilters(v); setPage(1); }} > ({ value: t.id, label: `${t.taskNo} · ${t.content.slice(0, 40)}`, }))} />
); }