feat: 数据删除机制(purge 物理清空 + 报告页隐私区块 + 删后回同意门)+ 测试桩补 purge
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import type { EventStore } from '../../engine/eventStore';
|
||||
import type { JevClient } from '../../services/jev';
|
||||
import { BADGE_MAP } from './badges';
|
||||
@@ -11,14 +11,33 @@ interface Props {
|
||||
store: EventStore;
|
||||
jev: JevClient;
|
||||
onReplay: () => void;
|
||||
/** 删除全部个人数据(ADR-0004 数据删除权):purge + 清空进度 + 回到同意门,由 App 编排 */
|
||||
onPurge: () => Promise<void>;
|
||||
}
|
||||
|
||||
/** Act 1 结束后的画像报告页:Jev 一次调用并行问 7 个维度 score */
|
||||
export function ReportPage({ store, jev, onReplay }: Props) {
|
||||
export function ReportPage({ store, jev, onReplay, onPurge }: Props) {
|
||||
const [scores, setScores] = useState<DimensionScore[] | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [retry, setRetry] = useState(0);
|
||||
const [playerName, setPlayerName] = useState('');
|
||||
const purgeDialogRef = useRef<HTMLDialogElement>(null);
|
||||
const [purging, setPurging] = useState(false);
|
||||
const [purgeError, setPurgeError] = useState<string | null>(null);
|
||||
|
||||
async function confirmPurge() {
|
||||
setPurging(true);
|
||||
setPurgeError(null);
|
||||
try {
|
||||
// 成功后 App 把应用切回同意门,本页随之卸载
|
||||
await onPurge();
|
||||
} catch (e) {
|
||||
setPurgeError(e instanceof Error ? e.message : String(e));
|
||||
purgeDialogRef.current?.close();
|
||||
} finally {
|
||||
setPurging(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
@@ -107,6 +126,44 @@ export function ReportPage({ store, jev, onReplay }: Props) {
|
||||
<button className="secondary" onClick={onReplay}>
|
||||
重玩 Act 1(新轨迹,历史保留)
|
||||
</button>
|
||||
<section style={{ marginTop: 48, borderTop: '1px solid rgba(201,219,203,.2)', paddingTop: 24 }}>
|
||||
<h2 style={{ fontSize: 18, color: '#f1eee3' }}>数据与隐私</h2>
|
||||
<p className="hint" style={{ maxWidth: 560 }}>
|
||||
本应用只在本机记录你的剧情选择、语音讲述转写与人格画像事件,用于人格评估与伴侣匹配推荐;数据不出设备,删除即彻底消失。
|
||||
</p>
|
||||
<button className="secondary" onClick={() => purgeDialogRef.current?.showModal()}>
|
||||
删除全部数据
|
||||
</button>
|
||||
{purgeError && <p className="scene-error">删除失败:{purgeError}</p>}
|
||||
<dialog
|
||||
ref={purgeDialogRef}
|
||||
aria-labelledby="purge-title"
|
||||
style={{
|
||||
background: 'rgba(13,25,23,.98)',
|
||||
color: '#e6eae0',
|
||||
border: '1px solid rgba(201,219,203,.34)',
|
||||
borderRadius: 3,
|
||||
padding: '24px 28px',
|
||||
maxWidth: 480,
|
||||
}}
|
||||
>
|
||||
<h3 id="purge-title" style={{ marginTop: 0 }}>
|
||||
确定删除全部数据?
|
||||
</h3>
|
||||
<p style={{ lineHeight: 1.8 }}>
|
||||
此操作不可恢复,所有剧情进度、人格画像、讲述内容将被永久删除,且不提供导出留档。
|
||||
删除后应用将回到初始的告知与同意页面,如同首次安装。
|
||||
</p>
|
||||
<div style={{ display: 'flex', gap: 12, marginTop: 20, flexWrap: 'wrap' }}>
|
||||
<button className="secondary" disabled={purging} onClick={confirmPurge}>
|
||||
{purging ? '正在删除……' : '确认永久删除'}
|
||||
</button>
|
||||
<button className="secondary" disabled={purging} onClick={() => purgeDialogRef.current?.close()}>
|
||||
取消
|
||||
</button>
|
||||
</div>
|
||||
</dialog>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user