import { useState } from 'react' import { Dialog, Toast } from 'antd-mobile' import { columnApi } from '../../api/task.js' import { RETENTION_OPTIONS, RETENTION_LABEL, STATUS_OPTIONS, STATUS_LABEL } from './date.js' const MAX_COLUMNS = 10 // 列设置 (T-COL-*, T-BD-02/09/10, T-FL-01/02/15) // 1) 列管理: 改名 / 新增 / 删除 / 排序 // 2) 状态绑定: 每列独立选择 不绑定 / 未开始 / 执行中 / 完成 // 3) 保留选项: 每列独立 6 档 export default function ColumnSettings({ projectId, columns, onClose, onSaved }) { const ordered = [...(columns || [])].sort((a, b) => a.sortOrder - b.sortOrder) const [list, setList] = useState(ordered) const [editingName, setEditingName] = useState(null) // { id, name } const [pending, setPending] = useState(false) const patchLocal = (id, patch) => setList((prev) => prev.map((c) => (c.id === id ? { ...c, ...patch } : c))) async function save() { setPending(true) try { // 逐列保存变更(只提交与初始不同的字段) for (const c of list) { const orig = ordered.find((o) => o.id === c.id) if (!orig) continue const body = {} if (orig.name !== c.name) body.name = c.name if (orig.boundStatus !== c.boundStatus) body.boundStatus = c.boundStatus if (orig.retention !== c.retention) body.retention = c.retention if (Object.keys(body).length) await columnApi.update(c.id, body) } Toast.show({ content: '已保存' }) onSaved?.() } catch (e) { Toast.show({ content: e.message }) } finally { setPending(false) } } async function addColumn() { if (list.length >= MAX_COLUMNS) { Toast.show({ content: `最多 ${MAX_COLUMNS} 列` }); return } const name = `新列 ${list.length + 1}` try { const r = await columnApi.add(projectId, { name }) setList((prev) => [...prev, r.item]) } catch (e) { Toast.show({ content: e.message }) } } async function removeColumn(col) { if (list.length <= 1) { Toast.show({ content: '至少保留一列' }); return } const idx = list.findIndex((c) => c.id === col.id) const fallback = list[idx + 1] || list[idx - 1] const ok = await Dialog.confirm({ content: `删除「${col.name}」?其中的任务将迁移到「${fallback.name}」,其状态会按目标列的绑定重新确定。`, confirmText: '删除', }) if (!ok) return try { await columnApi.remove(col.id, fallback.id) setList((prev) => prev.filter((c) => c.id !== col.id)) Toast.show({ content: '已删除' }) } catch (e) { Toast.show({ content: e.message }) } } function move(idx, dir) { const next = [...list] const j = idx + dir if (j < 0 || j >= next.length) return ;[next[idx], next[j]] = [next[j], next[idx]] setList(next) // 立即持久化顺序 columnApi.order(projectId, next.map((c) => c.id)).catch((e) => Toast.show({ content: e.message })) } return (
‹ 列设置
{pending ? '保存中…' : '保存'}
调整列顺序会影响「改状态时自动归位」的目标列(取第一个绑定该状态的列)。
列与状态绑定
{list.map((col, idx) => (
move(idx, -1)}>↑ move(idx, 1)}>↓ setEditingName({ id: col.id, name: col.name })} > {col.name}
removeColumn(col)}>删
))}
+ 新增列 当前 {list.length} / {MAX_COLUMNS} 列
已完成任务保留
{list.map((col) => (
{col.name}
))}
保留选项:永不显示 / 当天 / 3 天 / 本周 / 本月 / 永久显示。
仅影响已完成任务;未完成任务始终展示。列表与看板按同一规则过滤。
{pending ? '保存中…' : '保存'}
取消
setEditingName((p) => ({ ...p, name: e.target.value }))} /> } actions={[ { key: 'c', text: '取消', onClick: () => setEditingName(null) }, { key: 'ok', text: '确定', bold: true, onClick: () => { const name = (editingName?.name || '').trim() if (!name) { Toast.show({ content: '列名不能为空' }); return } if (list.some((c) => c.id !== editingName.id && c.name === name)) { Toast.show({ content: '列名已存在' }); return } patchLocal(editingName.id, { name }) setEditingName(null) }, }, ]} onClose={() => setEditingName(null)} />
) }