import { useCallback, useEffect, useMemo, useState } from 'react' import { Button, Dialog, DotLoading, Empty, Input, Popup, Segmented, Selector, Toast, } from 'antd-mobile' import { Celebrate } from '../../components/RingProgress.jsx' import { checklistApi } from '../../api/checkin.js' import useRefreshOnVisible from '../../hooks/useRefreshOnVisible.js' const SCENES = ['出差', '回老家', '其他'] // 清单视图: 出差/回老家等场景的一次性检查清单 (F-LS-*) export default function ListView() { const [archived, setArchived] = useState(false) const [lists, setLists] = useState(null) const [err, setErr] = useState('') const [openId, setOpenId] = useState(null) const [newItem, setNewItem] = useState({}) const [bursts, setBursts] = useState({}) const [templates, setTemplates] = useState([]) const [showForm, setShowForm] = useState(false) const [form, setForm] = useState({ title: '', scene: '出差', templateId: '' }) const load = useCallback((silent) => { if (!silent) setErr('') checklistApi.lists(archived) .then((d) => setLists(d.items || [])) .catch((e) => { if (!silent) setErr(e.message) }) }, [archived]) useEffect(() => { setLists(null); load(false) }, [load]) useRefreshOnVisible(() => load(true)) useEffect(() => { checklistApi.templates().then((d) => setTemplates(d.items || [])).catch(() => {}) }, []) const withProgress = useCallback((list) => { const items = list.items || [] const done = items.filter((i) => i.done).length return { ...list, total: items.length, done, percent: items.length > 0 ? done / items.length : 0, completed: items.length > 0 && done === items.length, } }, []) const patchList = (id, updater) => setLists((prev) => (prev || []).map((l) => (l.id === id ? updater(l) : l))) // 勾选: 乐观更新 + 服务端确认 (F-LS-03, F-SY-06) async function toggleItem(listId, item) { const snapshot = lists const next = !item.done patchList(listId, (l) => withProgress({ ...l, items: l.items.map((i) => (i.id === item.id ? { ...i, done: next } : i)), })) try { await checklistApi.updateItem(item.id, { done: next }) const l = (lists || []).find((x) => x.id === listId) const updated = withProgress({ ...l, items: l.items.map((i) => (i.id === item.id ? { ...i, done: next } : i)), }) if (updated.completed) setBursts((m) => ({ ...m, [listId]: (m[listId] || 0) + 1 })) } catch (e) { setLists(snapshot) Toast.show({ content: e.message || '更新失败' }) } } async function addItem(listId, title) { const t = String(title || '').trim() if (!t) return try { const { item } = await checklistApi.addItem(listId, { title: t }) patchList(listId, (l) => withProgress({ ...l, items: [...l.items, item] })) setNewItem((m) => ({ ...m, [listId]: '' })) } catch (e) { Toast.show({ content: e.message || '添加失败' }) } } async function removeItem(listId, itemId) { const snapshot = lists patchList(listId, (l) => withProgress({ ...l, items: l.items.filter((i) => i.id !== itemId) })) try { await checklistApi.removeItem(itemId) } catch (e) { setLists(snapshot) Toast.show({ content: e.message || '删除失败' }) } } async function checkAll(listId, done) { const snapshot = lists patchList(listId, (l) => withProgress({ ...l, items: l.items.map((i) => ({ ...i, done })) })) try { const { item } = await checklistApi.checkAll(listId, done) patchList(listId, () => withProgress(item)) if (item.completed) setBursts((m) => ({ ...m, [listId]: (m[listId] || 0) + 1 })) } catch (e) { setLists(snapshot) Toast.show({ content: e.message || '操作失败' }) } } async function createList() { const title = String(form.title || '').trim() if (!title) return Toast.show({ content: '请填写清单名称' }) try { await checklistApi.create({ title, scene: form.scene, templateId: form.templateId || undefined }) setShowForm(false) setForm({ title: '', scene: '出差', templateId: '' }) load(true) } catch (e) { Toast.show({ content: e.message || '创建失败' }) } } async function duplicate(list) { try { await checklistApi.duplicate(list.id) Toast.show({ content: '已复制为新清单' }) load(true) } catch (e) { Toast.show({ content: e.message || '复制失败' }) } } async function saveAsTemplate(list) { try { await checklistApi.createTemplate({ name: list.title, scene: list.scene || '其他', items: (list.items || []).map((i) => ({ title: i.title, note: i.note })), }) Toast.show({ content: '已保存为模板' }) const d = await checklistApi.templates() setTemplates(d.items || []) } catch (e) { Toast.show({ content: e.message || '保存失败' }) } } async function toggleArchive(list) { try { await checklistApi.update(list.id, { archived: !list.archived }) Toast.show({ content: list.archived ? '已恢复到进行中' : '已归档' }) load(true) } catch (e) { Toast.show({ content: e.message || '操作失败' }) } } async function removeList(list) { const ok = await Dialog.confirm({ content: `删除「${list.title}」?`, confirmText: '删除', cancelText: '取消' }) if (!ok) return try { await checklistApi.remove(list.id) load(true) } catch (e) { Toast.show({ content: e.message || '删除失败' }) } } const sorted = useMemo(() => lists || [], [lists]) return (