feat: 实现故居续学弹窗功能

新增功能:
- 统一续学逻辑:8个故居首页接入自动续学提示弹窗
- 完成判定:基于拼图碎片统一判断对话/体悟/内化是否完成
- 断点续学:对话回lastVisitedPlace、体悟回reflectionRoute、内化回internalization
- 触发条件:会话内每故居仅提示一次 + 首页视频播完后 + store加载成功/空时
- 安全防护:组件卸载后不再setState、同步失败不弹窗避免用旧状态误导

新增组件:
- ContinueLearningModal:纯CSS哑光米纸+朱红古籍风弹窗
- useResidenceContinue:封装触发逻辑+跳转+去重的共享hook

涉及文件:
- src/config/progression.ts:getResidenceStageCompletion、resolveResidenceContinueTarget
- src/hooks/useResidenceContinue.ts:统一续学hook
- src/components/layout/alert/continue-learning-modal.tsx:弹窗组件
- src/pages/*/home.tsx:8个故居首页接入

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
zhongyidianandClaude Opus 4.8 committed 2026-06-21 21:44:27 +08:00
1 parent 49ecabbf3a
commit 73d02eb62d
13 files changed
+539 -500

No files matched your search

@@ -0,0 +1,103 @@
import type { MouseEvent } from 'react'
export interface ContinueLearningModalProps {
/** 是否可见 */
visible?: boolean
/** 标题 */
title?: string
/** 提示正文 */
message?: string
/** 继续按钮文案 */
continueText?: string
/** 取消按钮文案 */
cancelText?: string
/** 点击「继续学习」回调 */
onContinue?: () => void
/** 点击「取消」或遮罩回调 */
onCancel?: () => void
}
/**
* ContinueLearningModal - 「未完成,是否继续学习」二选一确认弹窗
* 纯 CSS 实现,暖纸 + 朱红 + 金线古籍风,不依赖图片素材。
*/
const ContinueLearningModal = ({
visible = true,
title = '温馨提示',
message = '你还没有完成本故居的学习,是否继续上次的进度?',
continueText = '继续学习',
cancelText = '取消',
onContinue,
onCancel,
}: ContinueLearningModalProps) => {
if (!visible) return null
const swallow = (e: MouseEvent) => {
e.preventDefault()
e.stopPropagation()
}
return (
<div className="fixed inset-0 z-10000 flex items-center justify-center">
{/* 遮罩 */}
<div className="absolute inset-0 bg-[#1f0e06]/65" onClick={onCancel} />
{/* 哑光米纸卡片 */}
<div
className="relative z-10 w-[920px] rounded-[28px] border-[3px] border-[#bfa05c] bg-gradient-to-b from-[#e4d4b2] to-[#d6c198] px-20 py-14 shadow-[0_24px_70px_rgba(20,8,4,0.5)]"
onClick={swallow}
>
{/* 内层细金线描边 */}
<div className="pointer-events-none absolute inset-[10px] rounded-[20px] border border-[#bfa05c]/55" />
<div className="relative flex flex-col items-center">
{/* 标题 */}
<div className="text-5xl font-bold tracking-[10px] text-[#a8281c]">{title}</div>
{/* 菱形分隔线 */}
<div className="mt-6 flex items-center gap-4">
<span className="h-[2px] w-24 bg-gradient-to-r from-transparent to-[#c0392b]/70" />
<span className="h-3 w-3 rotate-45 bg-[#c0392b]" />
<span className="h-[2px] w-24 bg-gradient-to-l from-transparent to-[#c0392b]/70" />
</div>
{/* 正文 */}
<p className="mt-10 max-w-[720px] text-center text-4xl font-medium leading-relaxed tracking-wide text-[#5c3a1e]">
{message}
</p>
{/* 按钮组 */}
<div className="mt-12 flex items-center gap-12">
{/* 取消(次要:米底描边) */}
<button
type="button"
className="cursor-pointer rounded-full border-2 border-[#bfa05c] bg-[#ecdcb6] px-14 py-3.5 text-3xl font-bold tracking-[4px] text-[#8a5326] transition-all duration-200 hover:bg-[#e2d0a8] active:scale-95"
onMouseDown={swallow}
onClick={(e) => {
swallow(e)
onCancel?.()
}}
>
{cancelText}
</button>
{/* 继续学习(主要:朱红渐变 + 金边) */}
<button
type="button"
className="cursor-pointer rounded-full border-2 border-[#e8c06a] bg-gradient-to-b from-[#d4452f] to-[#a8281c] px-14 py-3.5 text-3xl font-bold tracking-[4px] text-[#fdf1da] shadow-[0_8px_20px_rgba(150,40,25,0.45)] transition-all duration-200 hover:brightness-110 active:scale-95"
onMouseDown={swallow}
onClick={(e) => {
swallow(e)
onContinue?.()
}}
>
{continueText}
</button>
</div>
</div>
</div>
</div>
)
}
export default ContinueLearningModal