验证20%
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# 登录后体验验证 20% 记录
|
||||
|
||||
## 本轮目标
|
||||
|
||||
围绕“登录后页面层级不清、不同尺寸下显得拥挤”的反馈,先完成不改变业务接口的体验验证与 P0/P1 收口。
|
||||
|
||||
## 已完成
|
||||
|
||||
1. 登录后的真实去向
|
||||
- 仅有一部真实家谱时直接进入该家谱主页;多部或无家谱时进入“我的家谱”。
|
||||
- 个人中心不再展示无家谱上下文的演示数据或业务入口。
|
||||
2. 当前家谱上下文
|
||||
- 家谱业务页在页头展示当前家谱,并可回到“我的家谱”切换。
|
||||
- 个人中心、我的家谱、当前家谱主页使用可返回的三级路径。
|
||||
3. 响应式导航
|
||||
- 1320px 及以下的个人中心页面使用统一的折叠菜单,避免当前家谱、导航和操作按钮相互挤压。
|
||||
- 720px 及以下隐藏与顶部菜单重复的家谱侧栏,让实际内容进入首屏。
|
||||
- 折叠菜单显示当前所在页;“我的家谱”页移除页头重复的创建入口。
|
||||
|
||||
## 验证方式
|
||||
|
||||
- 自动回归:`npm test`,298 项通过。
|
||||
- 真实流程:登录后进入真实家谱列表、选择家谱主页,并在 1249px 桌面与 390px 手机视口检查导航和首屏。
|
||||
- 验收使用隔离浏览器配置;测试账号、登录令牌及真实家谱标识不写入仓库。
|
||||
|
||||
## 后续优先级
|
||||
|
||||
1. 统一业务页的加载中、空数据、请求失败和无权限状态,减少页面之间的割裂感。
|
||||
2. 收口表单标签、必填提示、提交中与提交失败反馈,优先覆盖登录、创建/加入家谱和内容发布。
|
||||
3. 继续用真实权限角色验收成员、管理员与非成员的访问边界。
|
||||
@@ -15,15 +15,15 @@
|
||||
><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a
|
||||
>
|
||||
<nav class="nav-links">
|
||||
<a href="profile.html">首页</a
|
||||
><a href="profile-families.html">数字家谱</a
|
||||
<a href="profile.html">个人中心</a
|
||||
><a href="profile-families.html">我的家谱</a
|
||||
><a href="plaza.html">家谱广场</a
|
||||
><a class="active" href="profile-content.html">家族文化</a
|
||||
><a href="surname.html">姓氏百科</a><a href="app.html">应用下载</a
|
||||
><a href="help.html">帮助中心</a><a href="about.html">关于我们</a>
|
||||
</nav>
|
||||
<div class="nav-actions">
|
||||
<a href="profile.html">个人中心</a
|
||||
<a href="profile-data.html">账户资料</a
|
||||
><a class="btn primary magnetic" href="profile-create-family.html"
|
||||
>创建家谱</a
|
||||
>
|
||||
|
||||
+3
-3
@@ -15,15 +15,15 @@
|
||||
><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a
|
||||
>
|
||||
<nav class="nav-links">
|
||||
<a href="profile.html">首页</a
|
||||
><a href="profile-families.html">数字家谱</a
|
||||
<a href="profile.html">个人中心</a
|
||||
><a href="profile-families.html">我的家谱</a
|
||||
><a href="plaza.html">家谱广场</a
|
||||
><a href="profile-content.html">家族文化</a
|
||||
><a href="surname.html">姓氏百科</a><a href="app.html">应用下载</a
|
||||
><a href="help.html">帮助中心</a><a href="about.html">关于我们</a>
|
||||
</nav>
|
||||
<div class="nav-actions">
|
||||
<a class="active" href="profile.html">个人中心</a
|
||||
<a class="active" href="profile-data.html">账户资料</a
|
||||
><a class="btn primary magnetic" href="profile-create-family.html"
|
||||
>创建家谱</a
|
||||
>
|
||||
|
||||
@@ -15,25 +15,24 @@
|
||||
><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a
|
||||
>
|
||||
<nav class="nav-links">
|
||||
<a href="profile.html">首页</a
|
||||
><a class="active" href="profile-families.html">数字家谱</a
|
||||
<a href="profile.html">个人中心</a
|
||||
><a class="active" href="profile-families.html">我的家谱</a
|
||||
><a href="plaza.html">家谱广场</a
|
||||
><a href="profile-content.html">家族文化</a
|
||||
><a href="surname.html">姓氏百科</a><a href="app.html">应用下载</a
|
||||
><a href="help.html">帮助中心</a><a href="about.html">关于我们</a>
|
||||
</nav>
|
||||
<div class="nav-actions">
|
||||
<a href="profile.html">个人中心</a
|
||||
><a class="btn primary magnetic" href="profile-create-family.html"
|
||||
>创建家谱</a
|
||||
>
|
||||
<a href="profile-data.html">账户资料</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<section class="module-hero">
|
||||
<div class="container">
|
||||
<div class="module-kicker">Personal Center</div>
|
||||
<nav class="profile-path" aria-label="当前位置">
|
||||
<a href="profile.html">个人中心</a><span aria-hidden="true">/</span><strong aria-current="page">我的家谱</strong>
|
||||
</nav>
|
||||
<div class="module-title-row">
|
||||
<div>
|
||||
<h1>我的家谱</h1>
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
<header class="site-header">
|
||||
<div class="container nav">
|
||||
<a class="brand magnetic" href="profile.html"><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a>
|
||||
<nav class="nav-links"><a href="profile.html">首页</a><a class="active" href="profile-families.html">数字家谱</a><a href="profile-content.html">家族文化</a></nav>
|
||||
<div class="nav-actions"><a href="profile.html">个人中心</a><a class="btn primary magnetic" href="profile-tree.html" data-genealogy-context-link>世系人物</a></div>
|
||||
<nav class="nav-links"><a href="profile.html">个人中心</a><a class="active" href="profile-families.html">我的家谱</a><a href="profile-content.html">家族文化</a></nav>
|
||||
<div class="nav-actions"><a href="profile-data.html">账户资料</a><a class="btn primary magnetic" href="profile-tree.html" data-genealogy-context-link>世系人物</a></div>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
><a href="profile-services.html">帮助与服务</a>
|
||||
</nav>
|
||||
<div class="nav-actions">
|
||||
<a href="profile.html">返回中心</a
|
||||
<a href="profile.html">账户中心</a
|
||||
><a class="btn primary magnetic" href="profile-feed.html" data-feature-link="available" data-genealogy-context-link>发布动态</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -30,12 +30,14 @@
|
||||
<main>
|
||||
<section class="module-hero">
|
||||
<div class="container">
|
||||
<div class="module-kicker">Family Home</div>
|
||||
<nav class="profile-path" aria-label="当前位置">
|
||||
<a href="profile.html">个人中心</a><span aria-hidden="true">/</span><a href="profile-families.html">我的家谱</a><span aria-hidden="true">/</span><strong data-family-home-context aria-current="page">当前家谱</strong>
|
||||
</nav>
|
||||
<div class="module-title-row">
|
||||
<div>
|
||||
<h1 data-family-home-title>家谱主页</h1>
|
||||
<h1 data-family-home-title>正在读取家谱</h1>
|
||||
<p>
|
||||
后台家谱主页,用于进入世系、相册、家族圈、内容管理和成员协作。
|
||||
当前家谱的工作区,用于进入世系、相册、家族圈、内容管理和成员协作。
|
||||
</p>
|
||||
</div>
|
||||
<a class="btn ghost magnetic" href="profile-family-admin.html" data-family-home-management data-genealogy-context-link hidden
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<div class="container module-layout">
|
||||
<aside class="module-nav">
|
||||
<a href="profile-services.html">帮助与服务</a
|
||||
><a href="profile-share.html">应用分享</a
|
||||
><span class="module-nav-unavailable">应用分享<small>即将开放</small></span
|
||||
><a class="active" href="profile-feedback.html">意见反馈</a
|
||||
><a href="profile-security.html">安全设置</a>
|
||||
</aside>
|
||||
|
||||
@@ -41,8 +41,8 @@
|
||||
<aside class="module-nav">
|
||||
<a href="profile-messages.html">消息中心</a
|
||||
><a class="active" href="profile-join-review.html" data-genealogy-context-link>入谱审核</a
|
||||
><a href="profile-invite.html">邀请家人</a
|
||||
><a href="profile-admin-permissions.html">管理员权限</a>
|
||||
><span class="module-nav-unavailable">邀请家人<small>即将开放</small></span
|
||||
><span class="module-nav-unavailable">管理员权限<small>即将开放</small></span>
|
||||
</aside>
|
||||
<div class="module-main">
|
||||
<section class="module-panel">
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
<img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" />
|
||||
</a>
|
||||
<nav class="nav-links">
|
||||
<a href="profile.html">首页</a>
|
||||
<a href="profile-families.html">数字家谱</a>
|
||||
<a href="profile.html">个人中心</a>
|
||||
<a href="profile-families.html">我的家谱</a>
|
||||
<a href="plaza.html">家谱广场</a>
|
||||
<a href="profile-content.html">家族文化</a>
|
||||
<a href="surname.html">姓氏百科</a>
|
||||
@@ -25,7 +25,7 @@
|
||||
<a href="about.html">关于我们</a>
|
||||
</nav>
|
||||
<div class="nav-actions">
|
||||
<a class="active" href="profile.html">个人中心</a>
|
||||
<a class="active" href="profile-messages.html">消息中心</a>
|
||||
<a class="btn primary magnetic" href="profile-create-family.html">创建家谱</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a
|
||||
>
|
||||
<nav class="nav-links">
|
||||
<a href="profile.html">首页</a
|
||||
><a href="profile-families.html">数字家谱</a
|
||||
<a href="profile.html">个人中心</a
|
||||
><a href="profile-families.html">我的家谱</a
|
||||
><a href="plaza.html">家谱广场</a
|
||||
><a href="profile-content.html">家族文化</a
|
||||
><a href="surname.html">姓氏百科</a><a href="app.html">应用下载</a
|
||||
@@ -24,7 +24,7 @@
|
||||
><a href="about.html">关于我们</a>
|
||||
</nav>
|
||||
<div class="nav-actions">
|
||||
<a href="profile.html">个人中心</a
|
||||
<a href="profile-data.html">账户资料</a
|
||||
><a class="btn primary magnetic" href="profile-create-family.html"
|
||||
>创建家谱</a
|
||||
>
|
||||
@@ -63,11 +63,12 @@
|
||||
<section class="module-panel">
|
||||
<h2>常用服务</h2>
|
||||
<div class="module-grid">
|
||||
<a class="module-card tilt-card" href="profile-share.html"
|
||||
><span class="icon">享</span>
|
||||
<div class="module-card is-unavailable" aria-disabled="true">
|
||||
<span class="icon">享</span>
|
||||
<h3>应用分享</h3>
|
||||
<p>邀请好友使用,了解应用下载和分享奖励。</p></a
|
||||
>
|
||||
<p>邀请奖励服务正在开发中,当前不提供不可用的跳转入口。</p>
|
||||
<span class="pill">即将开放</span>
|
||||
</div>
|
||||
<a class="module-card tilt-card" href="profile-feedback.html" data-feature-link="available"
|
||||
><span class="icon">馈</span>
|
||||
<h3>意见反馈</h3>
|
||||
|
||||
+49
-231
@@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
@@ -15,8 +15,8 @@
|
||||
><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a
|
||||
>
|
||||
<nav class="nav-links">
|
||||
<a href="profile.html">首页</a>
|
||||
<a href="profile-families.html">数字家谱</a>
|
||||
<a class="active" href="profile.html">个人中心</a>
|
||||
<a href="profile-families.html">我的家谱</a>
|
||||
<a href="plaza.html">家谱广场</a>
|
||||
<a href="profile-content.html">家族文化</a>
|
||||
<a href="surname.html">姓氏百科</a>
|
||||
@@ -25,10 +25,8 @@
|
||||
<a href="about.html">关于我们</a>
|
||||
</nav>
|
||||
<div class="nav-actions">
|
||||
<a class="active" href="profile.html">个人中心</a>
|
||||
<a class="btn primary magnetic" href="profile-create-family.html"
|
||||
>创建家谱</a
|
||||
>
|
||||
<a href="profile-data.html">账户资料</a>
|
||||
<a class="btn primary magnetic" href="profile-create-family.html">创建家谱</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -36,29 +34,33 @@
|
||||
<main>
|
||||
<section class="profile-hero">
|
||||
<div class="container profile-hero-grid">
|
||||
<div class="profile-card tilt-card">
|
||||
<div class="profile-card">
|
||||
<div class="avatar-wrap">
|
||||
<div class="avatar" data-profile-text="avatarText">鹿</div>
|
||||
<div class="avatar" data-profile-text="avatarText" aria-hidden="true">…</div>
|
||||
<span></span>
|
||||
</div>
|
||||
<div class="profile-info">
|
||||
<div class="eyebrow">Personal Center</div>
|
||||
<h1 data-profile-text="displayName">鹿野 Loyel</h1>
|
||||
<p>
|
||||
管理家谱、亲属资料、家族内容和协作消息。APP
|
||||
里的核心功能都收纳在这里,适合 PC 端集中处理。
|
||||
</p>
|
||||
<div class="eyebrow">个人中心</div>
|
||||
<h1 data-profile-text="displayName">正在加载账户</h1>
|
||||
<p>账户资料与家谱工作区分开呈现。进入家谱后,再处理成员、内容和协作事务。</p>
|
||||
<div class="profile-tags">
|
||||
<span>血亲</span><span>第二代 顺字辈</span
|
||||
><span>已绑定账号</span>
|
||||
<span>已登录账户</span><span>资料以实际接口为准</span>
|
||||
</div>
|
||||
<p class="profile-load-status" data-profile-load-status role="status" aria-live="polite">正在读取账户资料…</p>
|
||||
<div class="toolbar profile-hero-actions">
|
||||
<a class="btn primary magnetic" href="profile-families.html">进入我的家谱</a>
|
||||
<a class="btn ghost magnetic" href="profile-data.html">完善个人资料</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-stats tilt-card">
|
||||
<div><strong>2</strong><span>我的家谱</span></div>
|
||||
<div><strong>7</strong><span>待处理</span></div>
|
||||
<div><strong>18</strong><span>已发布内容</span></div>
|
||||
<div><strong>86%</strong><span>资料完善度</span></div>
|
||||
<div class="profile-next-card">
|
||||
<div class="eyebrow">下一步</div>
|
||||
<h2>从家谱开始</h2>
|
||||
<p>没有家谱时可创建或申请加入;有多个家谱时,请先选择本次要处理的家谱。</p>
|
||||
<div class="toolbar">
|
||||
<a class="link-btn" href="profile-create-family.html">创建家谱</a>
|
||||
<a class="link-btn" href="profile-join-family.html">申请加入</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -66,216 +68,51 @@
|
||||
<section class="section dashboard-section">
|
||||
<div class="container dashboard-layout">
|
||||
<aside class="dashboard-side">
|
||||
<div class="side-card user-actions tilt-card">
|
||||
<div class="side-card user-actions">
|
||||
<h2>账户入口</h2>
|
||||
<a href="profile-data.html"
|
||||
><span>个人资料</span><b>完善成员档案</b></a
|
||||
>
|
||||
<a href="profile-messages.html"
|
||||
><span>消息中心</span><b>审核、邀请与提醒</b></a
|
||||
>
|
||||
<a href="profile-services.html"
|
||||
><span>帮助与服务</span><b>帮助、反馈、推广</b></a
|
||||
>
|
||||
<a class="logout-link" href="#logout" data-logout-open
|
||||
><span>退出登录</span><b>返回首页</b></a
|
||||
>
|
||||
</div>
|
||||
<div class="side-card todo-card tilt-card" id="messages">
|
||||
<div class="card-head">
|
||||
<h2>待办消息</h2>
|
||||
<span>7 条</span>
|
||||
</div>
|
||||
<div class="todo-list">
|
||||
<a href="profile-join-review.html" data-genealogy-context-link
|
||||
><b>入谱申请</b><span>进入当前家谱查看</span></a
|
||||
>
|
||||
<a href="profile-gift.html"
|
||||
><b>贺礼邀请</b><span>2 条需要确认</span></a
|
||||
>
|
||||
<a href="profile-data-reminders.html"
|
||||
><b>资料提醒</b><span>2 位成员待完善</span></a
|
||||
>
|
||||
</div>
|
||||
<a href="profile-data.html"><span>个人资料</span><b>查看并更新账户资料</b></a>
|
||||
<a href="profile-messages.html"><span>消息中心</span><b>查看真实通知与业务提醒</b></a>
|
||||
<a href="profile-services.html"><span>帮助与服务</span><b>反馈、安全设置与会员服务</b></a>
|
||||
<a class="logout-link" href="#logout" data-logout-open><span>退出登录</span><b>返回首页</b></a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="dashboard-main">
|
||||
<section class="panel my-family-panel" id="families">
|
||||
<section class="panel my-family-panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h2>我的家谱</h2>
|
||||
<p>
|
||||
对应 APP
|
||||
首页和家谱主页,进入后可查看家谱主页、世系图、相册和家族圈。
|
||||
</p>
|
||||
<p>先从真实列表选择家谱,再进入对应的主页、世系图、成员和内容管理。</p>
|
||||
</div>
|
||||
<a class="link-btn" href="profile-families.html">查看全部</a>
|
||||
<div class="toolbar">
|
||||
<a
|
||||
class="btn primary magnetic"
|
||||
href="profile-create-family.html"
|
||||
>创建家谱</a
|
||||
><a class="btn ghost magnetic" href="profile-join-family.html"
|
||||
>加入家谱</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="family-grid">
|
||||
<a
|
||||
class="family-card tilt-card"
|
||||
href="profile-families.html"
|
||||
>
|
||||
<div class="book-cover">我 的<br />家 谱</div>
|
||||
<a class="family-card" href="profile-families.html">
|
||||
<div class="book-cover">选 择<br />家 谱</div>
|
||||
<div>
|
||||
<h3>进入我的家谱</h3>
|
||||
<p>
|
||||
从家谱入口选择真实家谱后,再查看主页、成员、世系图和家族内容。
|
||||
</p>
|
||||
<p>列表只展示当前账户真实可访问的家谱,避免把示例数据当成自己的内容。</p>
|
||||
<div class="mini-actions">
|
||||
<span>家谱主页</span><span>家族圈</span><span>成员管理</span>
|
||||
<span>选择家谱</span><span>进入主页</span><span>处理事务</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel" id="management">
|
||||
<section class="panel">
|
||||
<div class="panel-head compact">
|
||||
<div>
|
||||
<h2>家族管理</h2>
|
||||
<p>
|
||||
把 APP 里的世代、管理员、权限、入谱审核集中到一个管理区。
|
||||
</p>
|
||||
<h2>账户资料</h2>
|
||||
<p>以下字段由账户资料接口返回;没有填写时会明确显示“待填写”。</p>
|
||||
</div>
|
||||
<a class="link-btn" href="profile-family-admin.html" data-genealogy-context-link
|
||||
>进入管理</a
|
||||
>
|
||||
<a class="link-btn" href="profile-data.html">编辑资料</a>
|
||||
</div>
|
||||
<div class="feature-grid management-grid">
|
||||
<a class="feature-tile tilt-card" href="profile-families.html"
|
||||
><span>世</span>
|
||||
<h3>世系图</h3>
|
||||
<p>查看成员世系、搜索成员、进入树谱模式。</p></a
|
||||
>
|
||||
<a class="feature-tile tilt-card" href="profile-families.html"
|
||||
><span>辈</span>
|
||||
<h3>字辈谱</h3>
|
||||
<p>管理字辈、统计各代人数和成员分布。</p></a
|
||||
>
|
||||
<a class="feature-tile tilt-card" href="profile-generation.html" data-genealogy-context-link
|
||||
><span>代</span>
|
||||
<h3>管理世代</h3>
|
||||
<p>设置世代、调整始祖世代、维护排行。</p></a
|
||||
>
|
||||
<a class="feature-tile tilt-card" href="profile-join-review.html" data-genealogy-context-link
|
||||
><span>审</span>
|
||||
<h3>入谱审核</h3>
|
||||
<p>处理当前家谱的待审核加入申请。</p></a
|
||||
>
|
||||
<a
|
||||
class="feature-tile tilt-card"
|
||||
href="profile-admin-permissions.html"
|
||||
><span>权</span>
|
||||
<h3>管理员权限</h3>
|
||||
<p>分配全权、全系、支系管理员权限。</p></a
|
||||
>
|
||||
<a class="feature-tile tilt-card" href="profile-invite.html"
|
||||
><span>邀</span>
|
||||
<h3>邀请家人</h3>
|
||||
<p>生成邀请入口,引导亲人激活使用。</p></a
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel" id="content">
|
||||
<div class="panel-head compact">
|
||||
<div>
|
||||
<h2>内容发布</h2>
|
||||
<p>
|
||||
对应 APP
|
||||
家谱主页中的谱文、视频、相册、功德录、贺礼邀请、成长日志等内容入口。
|
||||
</p>
|
||||
</div>
|
||||
<a class="link-btn" href="profile-content.html">发布中心</a>
|
||||
</div>
|
||||
<div class="content-grid">
|
||||
<a
|
||||
class="content-tile large tilt-card"
|
||||
href="profile-article.html" data-genealogy-context-link
|
||||
><span>谱文</span>
|
||||
<h3>撰写谱文与族史</h3>
|
||||
<p>沉淀族训、谱序、谱论、恩荣录等家族文献。</p></a
|
||||
>
|
||||
<a class="content-tile tilt-card" href="profile-album.html" data-genealogy-context-link
|
||||
><span>相册</span>
|
||||
<h3>家族相册</h3>
|
||||
<p>新建相册、上传照片、编辑封面。</p></a
|
||||
>
|
||||
<a class="content-tile tilt-card" href="profile-video.html" data-genealogy-context-link
|
||||
><span>视频</span>
|
||||
<h3>家族视频</h3>
|
||||
<p>发布视频、管理家族影像。</p></a
|
||||
>
|
||||
<a class="content-tile tilt-card" href="profile-merit.html" data-genealogy-context-link
|
||||
><span>功德</span>
|
||||
<h3>功德录</h3>
|
||||
<p>记录功德人、事迹与纪念内容。</p></a
|
||||
>
|
||||
<a class="content-tile tilt-card" href="profile-gift.html" data-genealogy-context-link
|
||||
><span>贺礼</span>
|
||||
<h3>贺礼邀请</h3>
|
||||
<p>管理婚礼、生日、升学等贺礼。</p></a
|
||||
>
|
||||
<a class="content-tile tilt-card" href="profile-families.html?next=profile-feed.html"
|
||||
><span>动态</span>
|
||||
<h3>发布动态</h3>
|
||||
<p>面向家族圈发布图文近况。</p></a
|
||||
>
|
||||
<a class="content-tile tilt-card" href="profile-families.html?next=profile-growth.html"
|
||||
><span>日志</span>
|
||||
<h3>成长日志</h3>
|
||||
<p>记录成员成长和人生节点。</p></a
|
||||
>
|
||||
<a class="content-tile tilt-card" href="profile-families.html?next=profile-memo.html"
|
||||
><span>备忘</span>
|
||||
<h3>人亲簿 / 备忘录</h3>
|
||||
<p>保存亲友往来和家族事务。</p></a
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="profile-data-layout" id="profile-data">
|
||||
<div class="panel data-panel">
|
||||
<div class="panel-head compact">
|
||||
<div>
|
||||
<h2>个人资料</h2>
|
||||
<p>来自 APP 的个人资料、亲属、完善资料和绑定账号流程。</p>
|
||||
</div>
|
||||
<a class="link-btn" href="profile-data.html">编辑资料</a>
|
||||
</div>
|
||||
<div class="data-list">
|
||||
<p><span>绑定账号</span><b data-profile-text="phone">已绑定</b></p>
|
||||
<p><span>姓名</span><b data-profile-text="displayName">鹿野 Loyel</b></p>
|
||||
<p><span>出生日期</span><b data-profile-text="birthday">待填写</b></p>
|
||||
<p><span>现居地区</span><b data-profile-text="regionText">待填写</b></p>
|
||||
<p><span>亲属关系</span><b>父亲 2 人</b></p>
|
||||
<p><span>补充资料</span><b>待完善 4 项</b></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel service-panel" id="services">
|
||||
<div class="panel-head compact">
|
||||
<div>
|
||||
<h2>帮助与服务</h2>
|
||||
<p>我的页面中的常用功能。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="service-grid">
|
||||
<a href="profile-share.html">应用分享</a>
|
||||
<a href="profile-feedback.html">意见反馈</a>
|
||||
<a href="help.html">帮助中心</a>
|
||||
<a href="profile-security.html">安全设置</a>
|
||||
</div>
|
||||
<div class="data-list">
|
||||
<p><span>绑定账号</span><b data-profile-text="phone">加载中</b></p>
|
||||
<p><span>昵称</span><b data-profile-text="displayName">加载中</b></p>
|
||||
<p><span>出生日期</span><b data-profile-text="birthday">加载中</b></p>
|
||||
<p><span>现居地区</span><b data-profile-text="regionText">加载中</b></p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -294,16 +131,11 @@
|
||||
</div>
|
||||
<div>
|
||||
<h4>产品入口</h4>
|
||||
<a href="profile-families.html">数字家谱</a
|
||||
><a href="plaza.html">家谱广场</a><a href="app.html">应用下载</a
|
||||
><a href="help.html">帮助中心</a>
|
||||
<a href="profile-families.html">数字家谱</a><a href="plaza.html">家谱广场</a><a href="app.html">应用下载</a><a href="help.html">帮助中心</a>
|
||||
</div>
|
||||
<div>
|
||||
<h4>文化内容</h4>
|
||||
<a href="profile-content.html">家族文化</a
|
||||
><a href="surname.html">姓氏百科</a
|
||||
><a href="profile-article.html">家谱知识</a
|
||||
><a href="profile-messages.html">平台公告</a>
|
||||
<a href="profile-content.html">家族文化</a><a href="surname.html">姓氏百科</a><a href="profile-article.html">家谱知识</a><a href="profile-messages.html">平台公告</a>
|
||||
</div>
|
||||
<div>
|
||||
<h4>联系我们</h4>
|
||||
@@ -320,27 +152,13 @@
|
||||
</footer>
|
||||
<div class="logout-modal" id="logoutModal" aria-hidden="true">
|
||||
<div class="logout-modal-backdrop" data-logout-close></div>
|
||||
<div
|
||||
class="logout-dialog"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="logoutTitle"
|
||||
>
|
||||
<button
|
||||
class="logout-close"
|
||||
type="button"
|
||||
data-logout-close
|
||||
aria-label="关闭"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<div class="logout-dialog" role="dialog" aria-modal="true" aria-labelledby="logoutTitle">
|
||||
<button class="logout-close" type="button" data-logout-close aria-label="关闭">×</button>
|
||||
<span class="logout-icon">退</span>
|
||||
<h2 id="logoutTitle">确认退出登录?</h2>
|
||||
<p>退出后需要重新登录,才能继续管理家谱、审核消息和编辑资料。</p>
|
||||
<div class="logout-actions">
|
||||
<button class="btn ghost magnetic" type="button" data-logout-close>
|
||||
取消
|
||||
</button>
|
||||
<button class="btn ghost magnetic" type="button" data-logout-close>取消</button>
|
||||
<a class="btn primary magnetic" href="index.html" data-logout-confirm>确认退出</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -543,6 +543,68 @@
|
||||
box-shadow: 0 24px 60px rgba(57, 48, 36, .11);
|
||||
}
|
||||
|
||||
.module-card.is-unavailable {
|
||||
cursor: not-allowed;
|
||||
opacity: .68;
|
||||
}
|
||||
|
||||
.module-card.is-unavailable:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.module-card.is-unavailable .pill {
|
||||
display: inline-flex;
|
||||
margin-top: 12px;
|
||||
color: #6b542d;
|
||||
background: #fff0cf;
|
||||
}
|
||||
|
||||
.module-nav-unavailable {
|
||||
padding: 14px 16px;
|
||||
border-radius: 14px;
|
||||
color: var(--muted);
|
||||
background: #f4f1eb;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.profile-path {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 0 0 18px;
|
||||
color: #6f766f;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.profile-path a {
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.profile-path a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.profile-path span {
|
||||
color: #a89f91;
|
||||
}
|
||||
|
||||
.profile-path strong {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: var(--ink);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.module-nav-unavailable small {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
color: #8a5a3b;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.module-card.is-selected {
|
||||
border-color: rgba(200, 59, 50, .5);
|
||||
background: #fff6f1;
|
||||
@@ -825,6 +887,10 @@
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.page-profile-module .module-nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.module-title-row {
|
||||
display: grid;
|
||||
}
|
||||
@@ -833,10 +899,6 @@
|
||||
font-size: 38px;
|
||||
}
|
||||
|
||||
.module-nav {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.lineage-filter-toolbar {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
+32
-1
@@ -33,6 +33,7 @@
|
||||
|
||||
.profile-card,
|
||||
.profile-stats,
|
||||
.profile-next-card,
|
||||
.panel,
|
||||
.side-card {
|
||||
border: 1px solid rgba(224, 211, 189, .84);
|
||||
@@ -94,6 +95,13 @@
|
||||
line-height: 1.9;
|
||||
}
|
||||
|
||||
.profile-info .profile-load-status {
|
||||
margin: 14px 0 0;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.profile-tags,
|
||||
.mini-actions {
|
||||
display: flex;
|
||||
@@ -118,6 +126,29 @@
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.profile-next-card {
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.profile-next-card .eyebrow {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.profile-next-card h2 {
|
||||
margin-bottom: 12px;
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.profile-next-card p {
|
||||
margin-bottom: 22px;
|
||||
color: var(--muted);
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.profile-hero-actions {
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.profile-stats div {
|
||||
min-height: 116px;
|
||||
padding: 20px;
|
||||
@@ -610,4 +641,4 @@
|
||||
.logout-actions {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,6 +186,39 @@ img {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.profile-current-genealogy {
|
||||
min-width: 0;
|
||||
max-width: 240px;
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
margin-left: auto;
|
||||
padding: 7px 12px;
|
||||
border: 1px solid rgba(255, 248, 230, .28);
|
||||
border-radius: 12px;
|
||||
color: #fff8e6;
|
||||
background: rgba(79, 29, 26, .18);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.profile-current-genealogy span {
|
||||
color: rgba(255, 248, 230, .72);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.profile-current-genealogy strong {
|
||||
overflow: hidden;
|
||||
color: #fff8e6;
|
||||
font-size: 14px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.profile-mobile-menu-toggle,
|
||||
.profile-mobile-menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.btn {
|
||||
min-width: 118px;
|
||||
height: 44px;
|
||||
@@ -790,6 +823,78 @@ textarea {
|
||||
background: #fff4ef;
|
||||
}
|
||||
|
||||
@media (max-width: 1320px) {
|
||||
.page-profile .nav-links,
|
||||
.page-profile-module .nav-links {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.profile-mobile-menu-toggle {
|
||||
min-width: 58px;
|
||||
height: 38px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 12px;
|
||||
border: 1px solid rgba(255, 248, 230, .38);
|
||||
border-radius: 10px;
|
||||
color: #fff8e6;
|
||||
background: rgba(79, 29, 26, .2);
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.page-profile .nav-actions,
|
||||
.page-profile-module .nav-actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.profile-mobile-menu.is-open {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
left: 0;
|
||||
padding: 14px 0 18px;
|
||||
border-bottom: 1px solid rgba(255, 253, 248, .24);
|
||||
background: rgba(121, 42, 35, .99);
|
||||
box-shadow: 0 22px 38px rgba(83, 32, 26, .28);
|
||||
}
|
||||
|
||||
.profile-mobile-menu-inner {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.profile-mobile-menu a,
|
||||
.profile-mobile-menu button {
|
||||
min-height: 46px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid rgba(255, 248, 230, .16);
|
||||
border-radius: 10px;
|
||||
color: #fff8e6;
|
||||
background: rgba(255, 253, 248, .08);
|
||||
font: inherit;
|
||||
font-weight: 800;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.profile-mobile-menu button {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.profile-mobile-menu a.is-current {
|
||||
border-color: rgba(255, 241, 202, .76);
|
||||
color: #fff7d8;
|
||||
background: rgba(255, 253, 248, .2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1120px) {
|
||||
.nav-links {
|
||||
display: none;
|
||||
@@ -819,6 +924,57 @@ textarea {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-profile-module .nav,
|
||||
.page-profile .nav {
|
||||
height: 70px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.page-profile-module .brand,
|
||||
.page-profile .brand {
|
||||
padding: 7px 10px;
|
||||
}
|
||||
|
||||
.page-profile-module .brand-logo,
|
||||
.page-profile .brand-logo {
|
||||
width: 114px;
|
||||
}
|
||||
|
||||
.page-profile-module .brand-logo-mark {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.page-profile-module .brand-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-profile-module .nav-actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.profile-current-genealogy {
|
||||
max-width: 112px;
|
||||
padding: 7px 9px;
|
||||
}
|
||||
|
||||
.profile-current-genealogy span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.profile-current-genealogy strong {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.profile-mobile-menu-toggle {
|
||||
min-width: 50px;
|
||||
padding: 0 9px;
|
||||
}
|
||||
|
||||
.profile-mobile-menu-inner {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.inner-hero .container {
|
||||
padding: 46px 0 54px;
|
||||
}
|
||||
|
||||
+37
-2
@@ -104,6 +104,39 @@
|
||||
return config.successUrl || '';
|
||||
}
|
||||
|
||||
function normalizeGenealogyId(value) {
|
||||
var id = value === undefined || value === null ? '' : String(value).trim();
|
||||
|
||||
if (typeof value === 'number' && !Number.isSafeInteger(value)) return '';
|
||||
return /^[1-9][0-9]*$/.test(id) ? id : '';
|
||||
}
|
||||
|
||||
function getPostLoginUrl(genealogies) {
|
||||
var ids = (Array.isArray(genealogies) ? genealogies : []).map(function (item) {
|
||||
return normalizeGenealogyId(item && item.genealogyId);
|
||||
}).filter(Boolean).filter(function (id, index, values) {
|
||||
return values.indexOf(id) === index;
|
||||
});
|
||||
|
||||
if (ids.length === 1) {
|
||||
return 'profile-family-home.html?genealogyId=' + encodeURIComponent(ids[0]);
|
||||
}
|
||||
|
||||
return 'profile-families.html';
|
||||
}
|
||||
|
||||
async function resolvePostLoginUrl(api, formType) {
|
||||
if ((formType !== 'login' && formType !== 'sms-login') || !api || !api.genealogiesMine) {
|
||||
return getSuccessUrl(formType);
|
||||
}
|
||||
|
||||
try {
|
||||
return getPostLoginUrl(await api.genealogiesMine());
|
||||
} catch (error) {
|
||||
return getSuccessUrl(formType);
|
||||
}
|
||||
}
|
||||
|
||||
function getFormCaptchaOperation(form, formType) {
|
||||
// 业务动作由当前脚本统一管理,避免散落在 HTML 属性中。
|
||||
var operationCode = getCaptchaOperation(formType);
|
||||
@@ -327,7 +360,7 @@
|
||||
setBusy(button, true);
|
||||
try {
|
||||
await api.login(buildLoginBody(values));
|
||||
root.location.href = getSuccessUrl('login');
|
||||
root.location.href = await resolvePostLoginUrl(api, 'login');
|
||||
} catch (error) {
|
||||
showMessage(error.message || '登录失败');
|
||||
} finally {
|
||||
@@ -348,7 +381,7 @@
|
||||
setBusy(button, true);
|
||||
try {
|
||||
await api.loginBySms(buildSmsLoginBody(values));
|
||||
root.location.href = getSuccessUrl('sms-login');
|
||||
root.location.href = await resolvePostLoginUrl(api, 'sms-login');
|
||||
} catch (error) {
|
||||
showMessage(error.message || '短信登录失败');
|
||||
} finally {
|
||||
@@ -489,6 +522,8 @@
|
||||
getCaptchaOperation: getCaptchaOperation,
|
||||
getFormCaptchaOperation: getFormCaptchaOperation,
|
||||
getSuccessUrl: getSuccessUrl,
|
||||
getPostLoginUrl: getPostLoginUrl,
|
||||
resolvePostLoginUrl: resolvePostLoginUrl,
|
||||
validateAuthValues: validateAuthValues,
|
||||
init: init
|
||||
};
|
||||
|
||||
@@ -163,7 +163,11 @@
|
||||
|
||||
try {
|
||||
result = await loadFamilyHome(api, genealogyId);
|
||||
if (root.ProfileUI && root.ProfileUI.setCurrentGenealogyContext) {
|
||||
root.ProfileUI.setCurrentGenealogyContext(result.overview.genealogyId, result.overview.genealogyName);
|
||||
}
|
||||
setText('[data-family-home-title]', result.overview.genealogyName);
|
||||
setText('[data-family-home-context]', result.overview.genealogyName);
|
||||
query('[data-family-home-overview]').innerHTML = renderFamilyOverview(result.overview);
|
||||
setText('[data-family-home-member-count]', String(result.overview.memberCount));
|
||||
setText('[data-family-home-person-count]', String(result.overview.personCount));
|
||||
|
||||
@@ -209,6 +209,9 @@
|
||||
if (!created) throw new Error('创建响应缺少有效家谱信息');
|
||||
verified = normalizeCreatedGenealogy(await api.genealogyDetail(created.genealogyId));
|
||||
if (!verified || verified.genealogyId !== created.genealogyId) throw new Error('创建后无法读取同一家谱');
|
||||
if (root.ProfileUI && root.ProfileUI.setCurrentGenealogyContext) {
|
||||
root.ProfileUI.setCurrentGenealogyContext(verified.genealogyId, verified.genealogyName);
|
||||
}
|
||||
if (root.location) root.location.href = buildCreatedGenealogyUrl(verified);
|
||||
} catch (error) {
|
||||
if (shouldRedirectToLogin(api, error)) {
|
||||
@@ -266,6 +269,7 @@
|
||||
].filter(Boolean).join(' · ');
|
||||
|
||||
return '<a class="module-row" data-genealogy-id="' + escapeHtml(item.genealogyId) +
|
||||
'" data-genealogy-name="' + escapeHtml(item.genealogyName) +
|
||||
'" href="' + escapeHtml(buildEntryUrl(item.genealogyId, target)) + '">' +
|
||||
'<div><h3>' + escapeHtml(item.genealogyName) + '</h3><p>' + escapeHtml(meta) +
|
||||
'</p></div><span class="pill">' + (item.canManage ? '管理并进入' : '进入家谱') + '</span></a>';
|
||||
@@ -284,6 +288,10 @@
|
||||
var result;
|
||||
|
||||
if (!status) return;
|
||||
if (shouldRedirectToLogin(api)) {
|
||||
redirectToLogin(api);
|
||||
return;
|
||||
}
|
||||
if (!api || !api.genealogyQuota || !api.genealogiesMine) {
|
||||
status.textContent = '家谱入口接口初始化失败,请刷新后重试。';
|
||||
return;
|
||||
@@ -294,6 +302,10 @@
|
||||
renderGenealogies(container, result[0], target);
|
||||
status.textContent = buildStatus(result[1]);
|
||||
} catch (error) {
|
||||
if (shouldRedirectToLogin(api, error)) {
|
||||
redirectToLogin(api);
|
||||
return;
|
||||
}
|
||||
status.textContent = '无法读取我的家谱,请稍后重试。';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,77 @@
|
||||
|
||||
var layuiReady = false;
|
||||
var layerApi = null;
|
||||
var genealogyContextStorageKey = 'genealogy_current_context_v1';
|
||||
var mobileMenuId = 'profile-mobile-menu';
|
||||
|
||||
function getSessionStorage() {
|
||||
try {
|
||||
return window.sessionStorage;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeGenealogyName(value) {
|
||||
var name = String(value === undefined || value === null ? '' : value).trim();
|
||||
|
||||
return name && name.length <= 80 ? name : '';
|
||||
}
|
||||
|
||||
function getStoredGenealogyContext() {
|
||||
var storage = getSessionStorage();
|
||||
var stored;
|
||||
var context;
|
||||
var genealogyId;
|
||||
var genealogyName;
|
||||
|
||||
if (!storage) return null;
|
||||
try {
|
||||
stored = storage.getItem(genealogyContextStorageKey);
|
||||
context = stored ? JSON.parse(stored) : null;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
genealogyId = normalizeGenealogyId(context && context.genealogyId);
|
||||
genealogyName = normalizeGenealogyName(context && context.genealogyName);
|
||||
return genealogyId && genealogyName ? {
|
||||
genealogyId: genealogyId,
|
||||
genealogyName: genealogyName
|
||||
} : null;
|
||||
}
|
||||
|
||||
function setCurrentGenealogyContext(genealogyId, genealogyName) {
|
||||
var storage = getSessionStorage();
|
||||
var context = {
|
||||
genealogyId: normalizeGenealogyId(genealogyId),
|
||||
genealogyName: normalizeGenealogyName(genealogyName)
|
||||
};
|
||||
|
||||
if (!context.genealogyId || !context.genealogyName) return null;
|
||||
if (storage) {
|
||||
try {
|
||||
storage.setItem(genealogyContextStorageKey, JSON.stringify(context));
|
||||
} catch (error) {}
|
||||
}
|
||||
renderCurrentGenealogyContext();
|
||||
return context;
|
||||
}
|
||||
|
||||
function clearCurrentGenealogyContext() {
|
||||
var storage = getSessionStorage();
|
||||
|
||||
if (!storage) return;
|
||||
try {
|
||||
storage.removeItem(genealogyContextStorageKey);
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
function getCurrentGenealogyContext() {
|
||||
var genealogyId = getGenealogyId();
|
||||
var context = getStoredGenealogyContext();
|
||||
|
||||
return context && context.genealogyId === genealogyId ? context : null;
|
||||
}
|
||||
|
||||
function openLegacyLogout() {
|
||||
var $modal = $('#logoutModal');
|
||||
@@ -58,6 +129,7 @@
|
||||
function performLogout(targetUrl) {
|
||||
var api = window.GenealogyApi && window.GenealogyApi.defaultClient;
|
||||
var redirect = function () {
|
||||
clearCurrentGenealogyContext();
|
||||
window.location.href = targetUrl || 'index.html';
|
||||
};
|
||||
|
||||
@@ -172,6 +244,113 @@
|
||||
});
|
||||
}
|
||||
|
||||
function renderCurrentGenealogyContext() {
|
||||
var context = getCurrentGenealogyContext();
|
||||
var name = context ? context.genealogyName : '正在识别家谱';
|
||||
|
||||
$('[data-current-genealogy-name]').text(name);
|
||||
$('[data-current-genealogy-link]').attr('aria-label', context ?
|
||||
'当前家谱:' + context.genealogyName + ',点击切换家谱' :
|
||||
'当前家谱,点击切换家谱');
|
||||
}
|
||||
|
||||
function refreshCurrentGenealogyContext() {
|
||||
var genealogyId = getGenealogyId();
|
||||
var api = window.GenealogyApi && window.GenealogyApi.defaultClient;
|
||||
|
||||
if (!genealogyId || getCurrentGenealogyContext() || !api || !api.genealogyDetail) return;
|
||||
api.genealogyDetail(genealogyId).then(function (detail) {
|
||||
var detailId = normalizeGenealogyId(detail && detail.genealogyId);
|
||||
var detailName = normalizeGenealogyName(detail && detail.genealogyName);
|
||||
|
||||
if (detailId === genealogyId && detailName) {
|
||||
setCurrentGenealogyContext(detailId, detailName);
|
||||
}
|
||||
}).catch(function () {
|
||||
$('[data-current-genealogy-name]').text('当前家谱');
|
||||
});
|
||||
}
|
||||
|
||||
function initCurrentGenealogyContext() {
|
||||
var genealogyId = getGenealogyId();
|
||||
var $nav;
|
||||
var $actions;
|
||||
var $context;
|
||||
|
||||
if (!genealogyId) return;
|
||||
$nav = $('.site-header .nav').first();
|
||||
if (!$nav.length || $nav.find('[data-current-genealogy-link]').length) return;
|
||||
$context = $('<a class="profile-current-genealogy" data-current-genealogy-link href="profile-families.html">' +
|
||||
'<span>当前家谱</span><strong data-current-genealogy-name>正在识别家谱</strong></a>');
|
||||
$actions = $nav.find('.nav-actions').first();
|
||||
if ($actions.length) $context.insertBefore($actions);
|
||||
else $nav.append($context);
|
||||
renderCurrentGenealogyContext();
|
||||
refreshCurrentGenealogyContext();
|
||||
}
|
||||
|
||||
function setMobileMenuOpen(isOpen) {
|
||||
var $menu = $('[data-profile-mobile-menu]').first();
|
||||
var $toggle = $('[data-profile-mobile-menu-toggle]').first();
|
||||
|
||||
if (!$menu.length || !$toggle.length) return;
|
||||
$menu.prop('hidden', !isOpen).toggleClass('is-open', !!isOpen).attr('aria-hidden', isOpen ? 'false' : 'true');
|
||||
$toggle.attr('aria-expanded', isOpen ? 'true' : 'false');
|
||||
}
|
||||
|
||||
function initMobileMenu() {
|
||||
var $header = $('.site-header').first();
|
||||
var $nav;
|
||||
var currentPage;
|
||||
|
||||
if (!$header.length || $header.find('[data-profile-mobile-menu]').length) return;
|
||||
$nav = $header.find('.nav').first();
|
||||
if (!$nav.length) return;
|
||||
$nav.append('<button class="profile-mobile-menu-toggle" type="button" data-profile-mobile-menu-toggle ' +
|
||||
'aria-expanded="false" aria-controls="' + mobileMenuId + '">菜单</button>');
|
||||
$header.append('<div class="profile-mobile-menu" id="' + mobileMenuId + '" data-profile-mobile-menu hidden aria-hidden="true">' +
|
||||
'<div class="container profile-mobile-menu-inner">' +
|
||||
'<a href="profile.html">个人中心</a>' +
|
||||
'<a href="profile-families.html">我的家谱</a>' +
|
||||
'<a href="profile-create-family.html">创建家谱</a>' +
|
||||
'<a href="profile-family-home.html" data-genealogy-context-link>家谱主页</a>' +
|
||||
'<a href="profile-content.html" data-genealogy-context-link>内容发布</a>' +
|
||||
'<a href="profile-family-admin.html" data-genealogy-context-link>家族管理</a>' +
|
||||
'<a href="profile-data.html">个人资料</a>' +
|
||||
'<a href="profile-messages.html">消息中心</a>' +
|
||||
'<a href="profile-services.html">帮助与服务</a>' +
|
||||
'<button type="button" data-logout-open data-logout-url="index.html">退出登录</button>' +
|
||||
'</div></div>');
|
||||
currentPage = (window.location.pathname.split('/').pop() || 'profile.html').split('?')[0];
|
||||
$header.find('[data-profile-mobile-menu] a').each(function () {
|
||||
var $link = $(this);
|
||||
var target = ($link.attr('href') || '').split('?')[0];
|
||||
|
||||
if (target === currentPage) $link.addClass('is-current').attr('aria-current', 'page');
|
||||
});
|
||||
}
|
||||
|
||||
function bindGenealogySelection() {
|
||||
$(document).on('click', '[data-genealogy-id][data-genealogy-name]', function () {
|
||||
setCurrentGenealogyContext($(this).attr('data-genealogy-id'), $(this).attr('data-genealogy-name'));
|
||||
});
|
||||
}
|
||||
|
||||
function bindMobileMenu() {
|
||||
$(document).on('click', '[data-profile-mobile-menu-toggle]', function () {
|
||||
setMobileMenuOpen($(this).attr('aria-expanded') !== 'true');
|
||||
});
|
||||
$(document).on('click', '[data-profile-mobile-menu] a, [data-profile-mobile-menu] button', function () {
|
||||
setMobileMenuOpen(false);
|
||||
});
|
||||
$(document).on('click', function (event) {
|
||||
if (!$(event.target).closest('.site-header').length) setMobileMenuOpen(false);
|
||||
});
|
||||
$(document).on('keydown', function (event) {
|
||||
if (event.key === 'Escape') setMobileMenuOpen(false);
|
||||
});
|
||||
}
|
||||
|
||||
function updateDisplay($trigger, value) {
|
||||
var target = $trigger.attr('data-update-target');
|
||||
var $target = target ? $(target) : $trigger.closest('p').find('b').first();
|
||||
@@ -277,6 +456,10 @@
|
||||
bindSelectActions();
|
||||
bindMessageActions();
|
||||
bindSelectableCards();
|
||||
bindGenealogySelection();
|
||||
bindMobileMenu();
|
||||
initCurrentGenealogyContext();
|
||||
initMobileMenu();
|
||||
syncGenealogyContextLinks();
|
||||
});
|
||||
|
||||
@@ -288,6 +471,10 @@
|
||||
normalizeGenealogyId: normalizeGenealogyId,
|
||||
withGenealogyId: withGenealogyId,
|
||||
getGenealogyEntryUrl: getGenealogyEntryUrl,
|
||||
getCurrentGenealogyContext: getCurrentGenealogyContext,
|
||||
setCurrentGenealogyContext: setCurrentGenealogyContext,
|
||||
clearCurrentGenealogyContext: clearCurrentGenealogyContext,
|
||||
refreshCurrentGenealogyContext: refreshCurrentGenealogyContext,
|
||||
syncGenealogyContextLinks: syncGenealogyContextLinks
|
||||
};
|
||||
})(window, window.jQuery || (window.layui && window.layui.$));
|
||||
|
||||
@@ -155,6 +155,22 @@
|
||||
});
|
||||
}
|
||||
|
||||
function setProfileLoadStatus(message) {
|
||||
var statusNode = query('[data-profile-load-status]');
|
||||
|
||||
if (statusNode) statusNode.textContent = message || '';
|
||||
}
|
||||
|
||||
function buildUnavailableProfileView() {
|
||||
return {
|
||||
displayName: '账户资料暂时无法读取',
|
||||
avatarText: '!',
|
||||
phone: '暂无数据',
|
||||
birthday: '暂无数据',
|
||||
regionText: '暂无数据'
|
||||
};
|
||||
}
|
||||
|
||||
function getFormValues(form) {
|
||||
var values = {};
|
||||
|
||||
@@ -236,6 +252,7 @@
|
||||
|
||||
if (!api || !documentRef || !documentRef.querySelector('[data-profile-page]')) return;
|
||||
if (redirectUnauthorized(api)) return;
|
||||
setProfileLoadStatus('正在读取账户资料…');
|
||||
|
||||
try {
|
||||
profile = await api.currentProfile();
|
||||
@@ -244,8 +261,11 @@
|
||||
fillProfileForm(profile);
|
||||
fillRegionPicker(profile);
|
||||
renderRegionText(profile, requestId);
|
||||
setProfileLoadStatus('');
|
||||
} catch (error) {
|
||||
if (redirectUnauthorized(api, error)) return;
|
||||
renderProfile(buildUnavailableProfileView());
|
||||
setProfileLoadStatus('资料读取失败,请刷新页面后重试。');
|
||||
showMessage(error.message || '个人资料加载失败');
|
||||
}
|
||||
}
|
||||
@@ -293,6 +313,7 @@
|
||||
getFinalRegionCode: getFinalRegionCode,
|
||||
buildRegionText: buildRegionText,
|
||||
buildProfileView: buildProfileView,
|
||||
buildUnavailableProfileView: buildUnavailableProfileView,
|
||||
buildProfileUpdateBody: buildProfileUpdateBody,
|
||||
shouldRedirectToLogin: shouldRedirectToLogin,
|
||||
reloadProfile: loadProfile,
|
||||
|
||||
@@ -118,3 +118,27 @@ test('authentication captcha ticket is removed from the form when consumed', ()
|
||||
assert.equal(field.value, '');
|
||||
assert.equal(AuthPages.takeCaptchaToken(form), '');
|
||||
});
|
||||
|
||||
test('successful login enters one real genealogy directly and otherwise opens the chooser', async () => {
|
||||
assert.equal(
|
||||
AuthPages.getPostLoginUrl([{ genealogyId: '2062179707935264769' }]),
|
||||
'profile-family-home.html?genealogyId=2062179707935264769'
|
||||
);
|
||||
assert.equal(AuthPages.getPostLoginUrl([]), 'profile-families.html');
|
||||
assert.equal(
|
||||
AuthPages.getPostLoginUrl([{ genealogyId: '1' }, { genealogyId: '2' }]),
|
||||
'profile-families.html'
|
||||
);
|
||||
assert.equal(
|
||||
AuthPages.getPostLoginUrl([{ genealogyId: Number.MAX_SAFE_INTEGER + 1 }]),
|
||||
'profile-families.html'
|
||||
);
|
||||
assert.equal(
|
||||
await AuthPages.resolvePostLoginUrl({
|
||||
genealogiesMine: async () => {
|
||||
throw new Error('temporary failure');
|
||||
}
|
||||
}, 'login'),
|
||||
'profile.html'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -213,19 +213,48 @@ test('家谱业务模块运行时统一委托 ProfileUI 读取和传播上下文
|
||||
});
|
||||
});
|
||||
|
||||
test('个人中心和内容发布在缺少家谱上下文时统一进入家谱入口', () => {
|
||||
test('个人中心只保留真实家谱入口,内容发布仍会透传家谱上下文', () => {
|
||||
const profile = read('profile.html');
|
||||
const content = read('profile-content.html');
|
||||
const familyHome = read('profile-family-home.html');
|
||||
const familyEntry = read('profile-families.html');
|
||||
const profileCommon = read('public/js/profile-common.js');
|
||||
const profileMain = profile.slice(0, profile.indexOf('</main>'));
|
||||
|
||||
assert.match(profile, /进入我的家谱/);
|
||||
assert.doesNotMatch(profile, /四川武胜汤氏族|四川达州刘氏族/);
|
||||
assert.match(profile, /href="profile-family-admin\.html" data-genealogy-context-link/);
|
||||
assert.match(profileMain, /进入我的家谱/);
|
||||
assert.match(profileMain, /href="profile-families\.html"/);
|
||||
assert.doesNotMatch(profileMain, /鹿野 Loyel|待处理|已发布内容|资料完善度/);
|
||||
assert.doesNotMatch(profileMain, /href="profile-(?:article|album|video|merit|gift|join-review)\.html"/);
|
||||
assert.match(content, /href="profile-feed\.html" data-feature-link="available" data-genealogy-context-link/);
|
||||
assert.match(familyHome, /href="profile-family-admin\.html" data-genealogy-context-link/);
|
||||
assert.match(familyEntry, /href="profile-family-admin\.html" data-genealogy-context-link/);
|
||||
assert.match(profileCommon, /function getGenealogyEntryUrl\(href\)/);
|
||||
assert.match(profileCommon, /genealogyId \? withGenealogyId\([^\n]+\) : getGenealogyEntryUrl\(/);
|
||||
assert.match(read('public/js/genealogy-entry-pages.js'), /if \(shouldRedirectToLogin\(api\)\) \{\s+redirectToLogin\(api\);/);
|
||||
});
|
||||
|
||||
test('当前家谱名称从真实家谱响应写入会话上下文,并在页头提供切换入口', () => {
|
||||
const common = read('public/js/profile-common.js');
|
||||
const entry = read('public/js/genealogy-entry-pages.js');
|
||||
const familyHome = read('public/js/family-home-pages.js');
|
||||
|
||||
assert.match(common, /genealogyContextStorageKey/);
|
||||
assert.match(common, /function setCurrentGenealogyContext\(genealogyId, genealogyName\)/);
|
||||
assert.match(common, /class="profile-current-genealogy"/);
|
||||
assert.match(common, /href="profile-families\.html"/);
|
||||
assert.match(entry, /data-genealogy-name=/);
|
||||
assert.match(entry, /root\.ProfileUI\.setCurrentGenealogyContext\(verified\.genealogyId, verified\.genealogyName\)/);
|
||||
assert.match(familyHome, /root\.ProfileUI\.setCurrentGenealogyContext\(result\.overview\.genealogyId, result\.overview\.genealogyName\)/);
|
||||
});
|
||||
|
||||
test('小屏导航提供统一入口,并且所有家谱内链接继续透传当前上下文', () => {
|
||||
const common = read('public/js/profile-common.js');
|
||||
const css = read('public/css/public.css');
|
||||
|
||||
assert.match(common, /data-profile-mobile-menu-toggle/);
|
||||
assert.match(common, /data-profile-mobile-menu/);
|
||||
assert.match(common, /profile-family-home\.html" data-genealogy-context-link/);
|
||||
assert.match(common, /profile-family-admin\.html" data-genealogy-context-link/);
|
||||
assert.match(css, /\.profile-mobile-menu\.is-open/);
|
||||
assert.match(css, /\.profile-current-genealogy/);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
|
||||
const root = path.join(__dirname, '..');
|
||||
|
||||
function read(file) {
|
||||
return fs.readFileSync(path.join(root, file), 'utf8');
|
||||
}
|
||||
|
||||
test('profile navigation has a medium-desktop compact mode and no mobile duplicate sidebar', () => {
|
||||
const publicCss = read('public/css/public.css');
|
||||
const moduleCss = read('public/css/profile-module.css');
|
||||
|
||||
assert.match(
|
||||
publicCss,
|
||||
/@media \(max-width: 1320px\) \{[\s\S]*?\.page-profile \.nav-links,[\s\S]*?\.page-profile-module \.nav-links \{\s*display: none;/
|
||||
);
|
||||
assert.match(
|
||||
moduleCss,
|
||||
/@media \(max-width: 720px\) \{[\s\S]*?\.page-profile-module \.module-nav \{\s*display: none;/
|
||||
);
|
||||
});
|
||||
|
||||
test('my genealogies keeps one primary create action and the compact menu marks the current page', () => {
|
||||
const page = read('profile-families.html');
|
||||
const header = page.slice(0, page.indexOf('</header>'));
|
||||
const common = read('public/js/profile-common.js');
|
||||
const publicCss = read('public/css/public.css');
|
||||
|
||||
assert.doesNotMatch(header, /href="profile-create-family\.html"/);
|
||||
assert.match(page, /module-title-row[\s\S]*?href="profile-create-family\.html"/);
|
||||
assert.match(common, /addClass\('is-current'\)\.attr\('aria-current', 'page'\)/);
|
||||
assert.match(publicCss, /\.profile-mobile-menu a\.is-current/);
|
||||
});
|
||||
@@ -38,6 +38,16 @@ test('profile view reads only the documented profile fields', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('profile loading failure has an explicit non-demo view', () => {
|
||||
assert.deepEqual(ProfilePages.buildUnavailableProfileView(), {
|
||||
displayName: '账户资料暂时无法读取',
|
||||
avatarText: '!',
|
||||
phone: '暂无数据',
|
||||
birthday: '暂无数据',
|
||||
regionText: '暂无数据'
|
||||
});
|
||||
});
|
||||
|
||||
test('profile update emits only the six fields defined by ProfileUpdateBody', () => {
|
||||
assert.deepEqual(ProfilePages.buildProfileUpdateBody({
|
||||
nickName: '小林',
|
||||
|
||||
@@ -26,12 +26,12 @@ test('家谱主页公开谱文、相册、视频、功德和祭祀活动入口
|
||||
});
|
||||
});
|
||||
|
||||
test('个人中心功能入口没有把家谱业务页当作无上下文普通链接', () => {
|
||||
test('个人中心先引导进入真实家谱列表,不展示无上下文的业务入口', () => {
|
||||
const page = read('profile.html');
|
||||
const main = page.slice(0, page.indexOf('</main>'));
|
||||
|
||||
['profile-article.html', 'profile-album.html', 'profile-video.html', 'profile-merit.html', 'profile-gift.html'].forEach((href) => {
|
||||
assert.match(page, new RegExp('href="' + href.replace('.', '\\.') + '"[^>]*data-genealogy-context-link'), `${href} 缺少个人中心上下文入口`);
|
||||
});
|
||||
assert.match(main, /href="profile-families\.html"/);
|
||||
assert.doesNotMatch(main, /href="profile-(?:article|album|video|merit|gift|join-review)\.html"/);
|
||||
});
|
||||
|
||||
test('加入申请与管理员审核入口使用真实页面和家谱上下文', () => {
|
||||
@@ -42,7 +42,7 @@ test('加入申请与管理员审核入口使用真实页面和家谱上下文',
|
||||
assert.match(families, /href="profile-join-family\.html"/);
|
||||
assert.doesNotMatch(families, /邀请码|分享码|手填.*ID/);
|
||||
assert.match(familyAdmin, /href="profile-join-review\.html"[^>]*data-genealogy-context-link/);
|
||||
assert.match(profile, /href="profile-join-review\.html"[^>]*data-genealogy-context-link/);
|
||||
assert.match(profile, /href="profile-families\.html"/);
|
||||
});
|
||||
|
||||
test('帮助中心和服务中心开放同一反馈记录闭环', () => {
|
||||
@@ -54,3 +54,18 @@ test('帮助中心和服务中心开放同一反馈记录闭环', () => {
|
||||
assert.match(services, /href="profile-feedback\.html"[^>]*data-feature-link="available"/);
|
||||
assert.doesNotMatch(help + services, /手填.*feedbackId|独立.*ticket.*接口/i);
|
||||
});
|
||||
|
||||
test('个人中心、我的家谱和当前家谱主页使用明确且可返回的三级路径', () => {
|
||||
const account = read('profile.html');
|
||||
const families = read('profile-families.html');
|
||||
const familyHome = read('profile-family-home.html');
|
||||
const familyHomeRuntime = read('public/js/family-home-pages.js');
|
||||
const coreHeaders = [account, families, familyHome].map((page) => page.slice(0, page.indexOf('</header>'))).join('');
|
||||
|
||||
assert.match(account, /<a class="active" href="profile\.html">个人中心<\/a>/);
|
||||
assert.match(account, /<a href="profile-families\.html">我的家谱<\/a>/);
|
||||
assert.match(families, /<nav class="profile-path" aria-label="当前位置">[\s\S]*个人中心[\s\S]*我的家谱/);
|
||||
assert.match(familyHome, /<nav class="profile-path" aria-label="当前位置">[\s\S]*profile-families\.html[\s\S]*data-family-home-context/);
|
||||
assert.match(familyHomeRuntime, /setText\('\[data-family-home-context\]', result\.overview\.genealogyName\)/);
|
||||
assert.doesNotMatch(coreHeaders, /返回中心|数字家谱/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user