/* 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 (
{/* Top user card */}
{user?.rank || '初入宫'}

{user?.name || '御膳小当家'}

抖音 · {user?.douyin_id || user?.id || '—'}{user?.joined_at ? ` · 入宫于 ${user.joined_at.slice(0,10)}` : ''}

{/* Daily audience check-in */} {/* Stats strip */}
{[ { lbl: '已投奏折', num: String(user?.stats?.submitted ?? 0), unit: '封' }, { lbl: '已被批阅', num: String(user?.stats?.read ?? 0), unit: '封' }, { lbl: '被翻牌', num: String(user?.stats?.praised ?? 0), unit: '次', accent: true }, { lbl: '已发评论', num: String(user?.stats?.comments ?? 0), unit: '条' }, ].map(s => (
{s.lbl}
{s.num} {s.unit}
))}
{/* Tabs */}
{tabs.map(t => ( ))}
{tab === 'letters' && ( <>
{cats.map(c => ( ))}
{shownLetters.length === 0 ? (

此分类暂无奏折。

) : (
{shownLetters.map(l => { const st = stateMap[l.state]; const rowClass = 'bx-me-row' + (l.praised ? ' bx-me-row--praised' : '') + (l.has_reply ? ' bx-me-row--has-reply' : ''); return (
#{l.id}

{l.title} {l.has_reply && ★ 朕曰}

{l.cat} {l.date} {l.praised && 陛下赞赏过}
{st.label}
{l.state === 'pending' && }
); })}
)} )} {tab === 'drafts' && ( drafts.length === 0 ? (

朕的案头空空如也。

爱卿尚无未竟之奏。

) : (
{drafts.map(d => (
草

{d.title || '(未命名奏折)'}

{d.cat || '未分类'} 更新于 {d.updated}
{d.body &&

{d.body.slice(0, 80)}{d.body.length > 80 ? '…' : ''}

}
))}
) )} {tab === 'favs' && ( savedLetters.length === 0 ? (

爱卿心中尚无所爱之奏。

可前往御批精选,取你所爱。

) : (
{savedLetters.map(L => onOpenLetter && onOpenLetter(L.id)}/>)}
) )} {tab === 'settings' && (

爱卿尊号

陛下将如此称呼你 · 限 12 字

setEditName(e.target.value)}/>

登录方式

本站仅支持抖音扫码登录 · 无独立口谕,身份由抖音验证

抖音 OAuth

注销账号

一朝注销,档案尽毁,朕亦不可复之。

)} {confirmOpen && (
!delBusy && setConfirmOpen(false)}>
e.stopPropagation()}> CONFIRM

朕问你

当真要注销此爱卿档案?
一朝去,档案尽毁,朕亦不可复之。

)}
); } Object.assign(window, { AccountPage });