/* global React, Button, LetterCard, loadDrafts, deleteDraft, getSavedIds, UI, AudienceCard */ const { useState: useStateAcc, useEffect: useEffAcc } = React; function AccountPage({ user, onLogout, letters, onOpenLetter, onEditDraft, onCompose, goTab, onUserChanged }) { const [tab, setTab] = useStateAcc(goTab || 'letters'); const [confirmOpen, setConfirmOpen] = useStateAcc(false); const [cat, setCat] = useStateAcc('全部'); const [drafts, setDrafts] = useStateAcc(loadDrafts()); const [savedIds, setSavedIds] = useStateAcc(getSavedIds()); const [apiSaves, setApiSaves] = useStateAcc(null); // server saves take precedence when set // Settings tab editable fields const [editName, setEditName] = useStateAcc(user?.name || ''); const [pwOld, setPwOld] = useStateAcc(''); const [pwNew, setPwNew] = useStateAcc(''); const [pwBusy, setPwBusy] = useStateAcc(false); const [nameBusy, setNameBusy] = useStateAcc(false); const [delBusy, setDelBusy] = useStateAcc(false); useEffAcc(() => { setEditName(user?.name || ''); }, [user?.name]); // Sync drafts from server when online + logged in useEffAcc(() => { if (tab !== 'drafts') return; if (window.bxApi) { window.bxApi.listDrafts().then(r => { // Merge: server wins, but keep local-only drafts (id starts with 'd' string) const serverDrafts = (r.items || []).map(d => ({ id: d.id, title: d.title, body: d.body, cat: d.category, named: d.anonymous ? 'anon' : 'real', clip: d.allow_clip ? 'yes' : 'no', updated: (d.updated_at || '').slice(0, 16).replace('T', ' '), _server: true, })); const localOnly = loadDrafts().filter(d => typeof d.id === 'string' && d.id.startsWith('d')); setDrafts([...serverDrafts, ...localOnly]); }).catch(() => setDrafts(loadDrafts())); } else { setDrafts(loadDrafts()); } }, [tab]); useEffAcc(() => { if (tab !== 'favs') return; if (window.bxApi) { window.bxApi.listSaves().then(r => setApiSaves(r.items || [])).catch(() => setApiSaves(null)); } setSavedIds(getSavedIds()); }, [tab]); const tabs = [ { id: 'letters', label: '我的奏折' }, { id: 'drafts', label: '我的草稿', count: drafts.length }, { id: 'favs', label: '我的收藏', count: savedIds.length }, { id: 'settings', label: '账号设置' }, ]; const MOCK_MY_LETTERS = [ { id: 1287, title: '朕的爱人走了,何以自处', cat: '情感困扰', date: '2026.04.18', state: 'read', praised: true }, { id: 1242, title: '老板又让臣加班了', cat: '工作烦恼', date: '2026.04.11', state: 'queued' }, { id: 1198, title: '考研失利,不敢回家', cat: '学业求助', date: '2026.04.03', state: 'read' }, { id: 1171, title: '地铁上有人朝臣的背讲电话', cat: '单纯吐槽', date: '2026.03.28', state: 'missed' }, { id: 1134, title: '朕今日煮糊了一锅粥', cat: '生活趣事', date: '2026.03.21', state: 'video', praised: true }, { id: 1102, title: '尚未道完的谢意', cat: '感谢陛下', date: '2026.03.15', state: 'pending' }, ]; const [myLetters, setMyLetters] = useStateAcc(MOCK_MY_LETTERS); // Pull current user's own letters from API whenever the tab is 'letters' useEffAcc(() => { if (tab !== 'letters' || !window.bxApi) return; window.bxApi.myLetters().then(r => { // Map API status ('pending'/'selected'/'read'/'cold') → UI state key const mapStatus = (x) => x.cold ? 'missed' : ({ pending:'pending', selected:'queued', read:'read' }[x.status] || x.status); setMyLetters((r.items || []).map(x => ({ id: x.id, title: x.title, cat: x.category, date: x.date, state: mapStatus(x), praised: x.praised, has_reply: x.has_reply, }))); }).catch(() => {}); }, [tab]); const cats = ['全部','情感困扰','工作烦恼','学业求助','生活趣事','单纯吐槽','感谢陛下','其他']; const shownLetters = cat === '全部' ? myLetters : myLetters.filter(l => l.cat === cat); const stateMap = { pending: { cls: 's-pending', label: '待批阅' }, queued: { cls: 's-review', label: '静候宣读' }, read: { cls: 's-read', label: '已宣读' }, video: { cls: 's-done', label: '已成永恒' }, missed: { cls: 's-reject', label: '未予选中' }, }; const savedLetters = apiSaves !== null ? apiSaves.map(x => ({ id: x.id, title: x.title, category: x.category, status: x.status, date: x.date, praised: x.praised, video: x.video, author: x.author_name, anonymous: x.author_name === '某位爱卿', excerpt: x.excerpt || '', })) : (letters || []).filter(L => savedIds.includes(L.id)); async function saveName() { const n = (editName || '').trim(); if (!n) { window.showToast && window.showToast({ type: 'error', msg: '尊号不可空' }); return; } if (n === user?.name) { window.showToast && window.showToast({ type: 'info', msg: '尊号未变' }); return; } if (!window.bxApi) { window.showToast && window.showToast({ type: 'warning', msg: '需在云端环境' }); return; } setNameBusy(true); try { const u = await window.bxApi.patchMe({ name: n }); onUserChanged && onUserChanged(u); window.showToast && window.showToast({ type: 'success', msg: '尊号已改' }); } catch (e) { window.showToast && window.showToast({ type: 'error', msg: (e && e.message) || '改名失败' }); } finally { setNameBusy(false); } } async function changePw() { if (!pwOld || !pwNew) { window.showToast && window.showToast({ type: 'error', msg: '请填两栏口谕' }); return; } if (pwNew.length < 8 || !/[a-zA-Z]/.test(pwNew) || !/[0-9]/.test(pwNew)) { window.showToast && window.showToast({ type: 'error', msg: '新口谕至少 8 位且含字母与数字' }); return; } if (!window.bxApi) { window.showToast && window.showToast({ type: 'warning', msg: '需在云端环境' }); return; } setPwBusy(true); try { await window.bxApi.changePassword(pwOld, pwNew); setPwOld(''); setPwNew(''); window.showToast && window.showToast({ type: 'success', msg: '口谕已改 · 其他设备已下线' }); } catch (e) { window.showToast && window.showToast({ type: 'error', msg: (e && e.message) || '改口谕失败' }); } finally { setPwBusy(false); } } async function deleteAccount() { // Backend has no delete-account endpoint yet (planned, see AUDIT.md D6). // For MVP we surface a friendly notice. Wiring becomes a one-liner once // /api/user/me DELETE lands. setDelBusy(true); try { if (window.bxApi && typeof window.bxApi.deleteMe === 'function') { await window.bxApi.deleteMe(); window.showToast && window.showToast({ type: 'success', msg: '档案已尽毁,朕亦不可复之' }); setConfirmOpen(false); if (onLogout) onLogout(); } else { setConfirmOpen(false); window.showToast && window.showToast({ type: 'info', msg: '注销通道筹建中,请联系陛下小秘书代办' }); } } finally { setDelBusy(false); } } async function deleteOwnLetter(id) { if (!window.bxApi) return; if (!confirm('删除此奏折?未批阅的奏折删后不可复原。')) return; try { await window.bxApi.deleteLetter(id); setMyLetters(xs => xs.filter(l => l.id !== id)); window.showToast && window.showToast({ type: 'success', msg: '奏折已撤' }); } catch (e) { window.showToast && window.showToast({ type: 'error', msg: (e && e.message) || '撤奏失败' }); } } async function onDraftDelete(id) { if (typeof id === 'number' && window.bxApi) { try { await window.bxApi.deleteDraft(id); } catch (e) { /* fall through */ } const r = await window.bxApi.listDrafts().catch(() => ({ items: [] })); setDrafts((r.items || []).map(d => ({ id: d.id, title: d.title, body: d.body, cat: d.category, named: d.anonymous ? 'anon' : 'real', clip: d.allow_clip ? 'yes' : 'no', updated: (d.updated_at || '').slice(0, 16).replace('T', ' '), _server: true, }))); } else { deleteDraft(id); setDrafts(loadDrafts()); } window.showToast && window.showToast({ type: 'success', msg: '草稿已弃' }); } return (
抖音 · {user?.douyin_id || user?.id || '—'}{user?.joined_at ? ` · 入宫于 ${user.joined_at.slice(0,10)}` : ''}
此分类暂无奏折。
爱卿尚无未竟之奏。
{d.body.slice(0, 80)}{d.body.length > 80 ? '…' : ''}
}可前往御批精选,取你所爱。
陛下将如此称呼你 · 限 12 字
本站仅支持抖音扫码登录 · 无独立口谕,身份由抖音验证
一朝注销,档案尽毁,朕亦不可复之。
当真要注销此爱卿档案?
一朝去,档案尽毁,朕亦不可复之。