修改功能
This commit is contained in:
@@ -51,3 +51,9 @@ nbdist/
|
|||||||
|
|
||||||
robots.txt
|
robots.txt
|
||||||
sitemap.xml
|
sitemap.xml
|
||||||
|
|
||||||
|
# Local browser audit screenshots and generated reports
|
||||||
|
/artifacts/
|
||||||
|
|
||||||
|
# Local design-audit runtime metadata
|
||||||
|
/.hallmark/
|
||||||
|
|||||||
+7
-7
@@ -36,7 +36,7 @@
|
|||||||
<section class="inner-hero about-hero">
|
<section class="inner-hero about-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="hero-copy">
|
<div class="hero-copy">
|
||||||
<div class="eyebrow">About Us</div>
|
<div class="eyebrow">关于我们</div>
|
||||||
<h1>用数字化方式守护家族记忆</h1>
|
<h1>用数字化方式守护家族记忆</h1>
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
代代相传面向家庭、宗亲会与地方文化组织,提供家谱创建、资料管理、文化展示和亲人协作工具。
|
代代相传面向家庭、宗亲会与地方文化组织,提供家谱创建、资料管理、文化展示和亲人协作工具。
|
||||||
@@ -91,17 +91,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>产品入口</h4>
|
<h4>产品入口</h4>
|
||||||
<a>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
|
<a href="genealogy.html">数字家谱</a><a href="plaza.html">家谱广场</a><a href="app.html">应用下载</a><a href="help.html">帮助中心</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>文化内容</h4>
|
<h4>文化内容</h4>
|
||||||
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a><a>平台公告</a>
|
<a href="culture.html">家族文化</a><a href="surname.html">姓氏百科</a><a href="article-detail.html">家谱知识</a><a href="notice-detail.html">平台公告</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>联系我们</h4>
|
<h4>服务支持</h4>
|
||||||
<p>客服电话:400-000-0000</p>
|
<a href="help.html">帮助中心</a>
|
||||||
<p>邮箱:service@example.com</p>
|
<a href="submit-ticket.html">提交问题</a>
|
||||||
<p>地址:中国 · 成都</p>
|
<a href="about.html">关于平台</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-legal">
|
<div class="footer-legal">
|
||||||
|
|||||||
@@ -36,11 +36,10 @@
|
|||||||
<section class="inner-hero app-hero">
|
<section class="inner-hero app-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="hero-copy">
|
<div class="hero-copy">
|
||||||
<div class="eyebrow">Mobile App</div>
|
<div class="eyebrow">移动应用</div>
|
||||||
<h1>手机端随时记录,电脑端清晰浏览</h1>
|
<h1>手机端随时记录,电脑端清晰浏览</h1>
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
家族成员可以在手机端上传照片、查看家谱、发布动态;PC
|
家族成员可以在手机端上传照片、查看家谱、发布动态;电脑端适合浏览门户内容和公开家谱。
|
||||||
端适合浏览门户内容和公开家谱。
|
|
||||||
</p>
|
</p>
|
||||||
<div class="hero-buttons">
|
<div class="hero-buttons">
|
||||||
<a class="btn primary magnetic" href="download.html">下载应用</a
|
<a class="btn primary magnetic" href="download.html">下载应用</a
|
||||||
@@ -71,7 +70,7 @@
|
|||||||
<div class="section-head">
|
<div class="section-head">
|
||||||
<h2>移动端能力</h2>
|
<h2>移动端能力</h2>
|
||||||
<p>
|
<p>
|
||||||
承接 APP 里的主要功能,让用户理解手机端适合日常记录和共同维护。
|
手机端适合随时记录家族资料、上传影像并与亲人共同维护。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-4">
|
<div class="grid-4">
|
||||||
@@ -122,18 +121,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>产品入口</h4>
|
<h4>产品入口</h4>
|
||||||
<a>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
|
<a href="genealogy.html">数字家谱</a><a href="plaza.html">家谱广场</a><a href="app.html">应用下载</a><a href="help.html">帮助中心</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>文化内容</h4>
|
<h4>文化内容</h4>
|
||||||
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a
|
<a href="culture.html">家族文化</a><a href="surname.html">姓氏百科</a><a href="article-detail.html">家谱知识</a
|
||||||
><a href="notice-detail.html">平台公告</a>
|
><a href="notice-detail.html">平台公告</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>联系我们</h4>
|
<h4>服务支持</h4>
|
||||||
<p>客服电话:400-000-0000</p>
|
<a href="help.html">帮助中心</a>
|
||||||
<p>邮箱:service@example.com</p>
|
<a href="submit-ticket.html">提交问题</a>
|
||||||
<p>地址:中国 · 成都</p>
|
<a href="about.html">关于平台</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-legal">
|
<div class="footer-legal">
|
||||||
@@ -149,6 +148,7 @@
|
|||||||
<script src="utils/AxiosRequestUtil.js"></script>
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/app-promotion-pages.js"></script>
|
<script src="public/js/app-promotion-pages.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+4
-4
@@ -62,10 +62,10 @@
|
|||||||
><a href="about.html">平台公告</a>
|
><a href="about.html">平台公告</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>联系我们</h4>
|
<h4>服务支持</h4>
|
||||||
<p>客服电话:400-000-0000</p>
|
<a href="help.html">帮助中心</a>
|
||||||
<p>邮箱:service@example.com</p>
|
<a href="submit-ticket.html">提交问题</a>
|
||||||
<p>地址:中国 · 成都</p>
|
<a href="about.html">关于平台</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-legal">
|
<div class="footer-legal">
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<section class="site-info-hero">
|
<section class="site-info-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div>
|
<div>
|
||||||
<div class="site-info-kicker">Children Privacy</div>
|
<div class="site-info-kicker">儿童隐私保护</div>
|
||||||
<h1>儿童个人信息处理规则</h1>
|
<h1>儿童个人信息处理规则</h1>
|
||||||
<p>用于说明儿童个人信息的处理边界、监护人权利和保护措施。</p>
|
<p>用于说明儿童个人信息的处理边界、监护人权利和保护措施。</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<section class="inner-hero create-hero">
|
<section class="inner-hero create-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="hero-copy">
|
<div class="hero-copy">
|
||||||
<div class="eyebrow">Start Genealogy</div>
|
<div class="eyebrow">创建家谱</div>
|
||||||
<h1>创建属于家族的数字空间</h1>
|
<h1>创建属于家族的数字空间</h1>
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
填写姓氏谱名、地区与简介,先建立家谱主页,再逐步邀请亲人共同完善资料。
|
填写姓氏谱名、地区与简介,先建立家谱主页,再逐步邀请亲人共同完善资料。
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
<div class="form-ornament"></div>
|
<div class="form-ornament"></div>
|
||||||
<h2>家谱信息</h2>
|
<h2>家谱信息</h2>
|
||||||
<p class="form-note">
|
<p class="form-note">
|
||||||
标有 * 的字段为必填。登录后使用当前 PC 家谱服务创建,创建成功后进入对应家谱主页。
|
标有 * 的字段为必填。登录后即可创建,成功后会进入新家谱主页。
|
||||||
</p>
|
</p>
|
||||||
<p class="form-note" data-genealogy-create-quota role="status" aria-live="polite">正在读取家谱额度…</p>
|
<p class="form-note" data-genealogy-create-quota role="status" aria-live="polite">正在读取家谱额度…</p>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
@@ -120,7 +120,7 @@
|
|||||||
<option value="2">邀请码加入</option>
|
<option value="2">邀请码加入</option>
|
||||||
<option value="0">关闭加入</option>
|
<option value="0">关闭加入</option>
|
||||||
</select>
|
</select>
|
||||||
<span id="public-invite-mode-note" class="form-note">当前 PC 未提供邀请码生成、发送或管理接口;选择邀请模式后不能在本页面继续生成邀请码。</span>
|
<span id="public-invite-mode-note" class="form-note">创建完成后,可在家谱首页的“邀请家人”中生成一次性邀请链接。</span>
|
||||||
</label>
|
</label>
|
||||||
<div id="public-create-status" class="form-note" data-genealogy-create-status role="status" aria-live="polite"></div>
|
<div id="public-create-status" class="form-note" data-genealogy-create-status role="status" aria-live="polite"></div>
|
||||||
<div>
|
<div>
|
||||||
@@ -143,17 +143,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>产品入口</h4>
|
<h4>产品入口</h4>
|
||||||
<a>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
|
<a href="genealogy.html">数字家谱</a><a href="plaza.html">家谱广场</a><a href="app.html">应用下载</a><a href="help.html">帮助中心</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>文化内容</h4>
|
<h4>文化内容</h4>
|
||||||
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a><a>平台公告</a>
|
<a href="culture.html">家族文化</a><a href="surname.html">姓氏百科</a><a href="article-detail.html">家谱知识</a><a href="notice-detail.html">平台公告</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>联系我们</h4>
|
<h4>服务支持</h4>
|
||||||
<p>客服电话:400-000-0000</p>
|
<a href="help.html">帮助中心</a>
|
||||||
<p>邮箱:service@example.com</p>
|
<a href="submit-ticket.html">提交问题</a>
|
||||||
<p>地址:中国 · 成都</p>
|
<a href="about.html">关于平台</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-legal">
|
<div class="footer-legal">
|
||||||
|
|||||||
+7
-7
@@ -36,7 +36,7 @@
|
|||||||
<section class="inner-hero culture-hero">
|
<section class="inner-hero culture-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="hero-copy">
|
<div class="hero-copy">
|
||||||
<div class="eyebrow">Family Culture</div>
|
<div class="eyebrow">家族文化</div>
|
||||||
<h1>让家风、族训与故事被认真保存</h1>
|
<h1>让家风、族训与故事被认真保存</h1>
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
家族文化承接新闻、知识、公告和故事,让谱文之外的记忆也能被阅读、分享和传承。
|
家族文化承接新闻、知识、公告和故事,让谱文之外的记忆也能被阅读、分享和传承。
|
||||||
@@ -104,17 +104,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>产品入口</h4>
|
<h4>产品入口</h4>
|
||||||
<a>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
|
<a href="genealogy.html">数字家谱</a><a href="plaza.html">家谱广场</a><a href="app.html">应用下载</a><a href="help.html">帮助中心</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>文化内容</h4>
|
<h4>文化内容</h4>
|
||||||
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a><a>平台公告</a>
|
<a href="culture.html">家族文化</a><a href="surname.html">姓氏百科</a><a href="article-detail.html">家谱知识</a><a href="notice-detail.html">平台公告</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>联系我们</h4>
|
<h4>服务支持</h4>
|
||||||
<p>客服电话:400-000-0000</p>
|
<a href="help.html">帮助中心</a>
|
||||||
<p>邮箱:service@example.com</p>
|
<a href="submit-ticket.html">提交问题</a>
|
||||||
<p>地址:中国 · 成都</p>
|
<a href="about.html">关于平台</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-legal">
|
<div class="footer-legal">
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# Design · 代代相传家谱 PC
|
||||||
|
|
||||||
|
这是 PC 家谱项目的统一设计系统。所有页面沿用同一套品牌、排版、间距、状态和交互规则;新增规则先修改本文件,不在单个页面创建另一套视觉语言。
|
||||||
|
|
||||||
|
## 用户与任务
|
||||||
|
|
||||||
|
- 用户:家谱创建者、管理员、普通家人和浏览公开家谱的访客。
|
||||||
|
- 核心任务:创建或加入家谱,维护世系与成员,发布并浏览家族内容,处理邀请、消息与服务。
|
||||||
|
- 设计气质:中文编辑式、克制、可信。功能页以任务效率为先,公共页保留家族文化氛围。
|
||||||
|
|
||||||
|
## Genre
|
||||||
|
|
||||||
|
Editorial。登录后的应用页使用更紧凑的工作台表达,不采用营销页式的大标题和卡片堆叠。
|
||||||
|
|
||||||
|
## Macrostructure family
|
||||||
|
|
||||||
|
- 公开与营销页面:Content Portal。真实内容和图片优先,标题区只说明当前任务。
|
||||||
|
- 登录后应用页面:Workbench。固定页头、稳定的分组侧栏和单一内容画布;跨页面导航位置不改变。
|
||||||
|
- 内容与资料页面:Long Document / List Detail。列表进入独立详情页,桌面端需要连续比较时允许页内列表详情。
|
||||||
|
|
||||||
|
## Theme
|
||||||
|
|
||||||
|
- `--color-paper`: `oklch(97.5% 0.012 82)`
|
||||||
|
- `--color-paper-2`: `oklch(94.8% 0.024 78)`
|
||||||
|
- `--color-ink`: `oklch(31% 0.028 158)`
|
||||||
|
- `--color-ink-2`: `oklch(51% 0.022 158)`
|
||||||
|
- `--color-rule`: `oklch(86% 0.028 78)`
|
||||||
|
- `--color-accent`: `oklch(56% 0.185 29)`
|
||||||
|
- `--color-accent-strong`: `oklch(43% 0.13 29)`
|
||||||
|
- `--color-focus`: `oklch(61% 0.17 29)`
|
||||||
|
- 辅助绿色和金色仅用于状态与文化信息,不与主操作竞争。
|
||||||
|
|
||||||
|
## Typography
|
||||||
|
|
||||||
|
- Display:`STSong`, `Songti SC`, `SimSun`, serif;字重 600;正常体。
|
||||||
|
- Body:`Microsoft YaHei`, `PingFang SC`, system-ui, sans-serif;字重 400/600。
|
||||||
|
- Mono:`Consolas`, `SFMono-Regular`, monospace,仅用于编号和技术值。
|
||||||
|
- 标题不得使用斜体,不保留无业务意义的英文眉题。
|
||||||
|
- 正文舒适行宽为 62~76 个中文字符。
|
||||||
|
|
||||||
|
## Spacing
|
||||||
|
|
||||||
|
使用根目录 `tokens.css` 中的 4px 命名刻度。新样式只能使用 `--space-*`,页面内容区桌面最大宽度统一为 `--layout-max`。
|
||||||
|
|
||||||
|
## Motion
|
||||||
|
|
||||||
|
- 动效只表达悬停、焦点、展开和状态变化,最多使用透明度与 `transform`。
|
||||||
|
- 默认 160~220ms,无弹跳和持续漂浮。
|
||||||
|
- `prefers-reduced-motion` 下关闭空间位移并将反馈压缩为短暂透明度变化。
|
||||||
|
|
||||||
|
## Interaction decisions
|
||||||
|
|
||||||
|
- 独立页面:多字段创建/编辑、详情阅读、支付、权限和安全设置。
|
||||||
|
- 对话框:删除/退出等不可逆确认、密码输入、单一短任务;统一使用站内对话框,不调用浏览器原生 `confirm`/`prompt`。
|
||||||
|
- 图片灯箱:相册和内容图片预览;不改变当前列表位置。
|
||||||
|
- 页内展开:帮助问答、筛选、非关键补充信息。
|
||||||
|
- 抽屉:桌面端评论或需要保留主内容上下文的辅助任务;不承载长表单。
|
||||||
|
- 加载、空数据、错误、无权限:显示在对应内容区域,不使用弹窗打断。
|
||||||
|
- 成功反馈:优先更新页面状态;仅在用户可能看不到变化时使用短消息。
|
||||||
|
|
||||||
|
## Media rules
|
||||||
|
|
||||||
|
- 业务媒体只消费后端 `BusinessFileAccess.accessUrl`,不得拼接 OSS ID 或暴露内部路径。
|
||||||
|
- 家谱封面 16:9;相册封面 4:3;文章封面 16:9;头像 1:1;动态媒体根据原图比例限制最大高度。
|
||||||
|
- 有地址时显示真实图片;无图片时使用姓名首字或明确空状态,不使用伪造照片、CSS 图画或无意义占位块。
|
||||||
|
- 图片必须有与业务内容对应的替代文本;装饰图片使用空替代文本。
|
||||||
|
- 正文附件使用左对齐、最大 280px 的缩略图网格并完整显示原图;封面和视频预览按业务比例完整呈现,不把单张附件拉伸成整栏大图。
|
||||||
|
- 视频使用原生播放器,列表中只自动加载封面,不自动播放。
|
||||||
|
|
||||||
|
## Shared states
|
||||||
|
|
||||||
|
所有交互控件必须覆盖默认、悬停、键盘焦点、按下、禁用、加载、错误、成功状态。错误信息靠近问题区域,同时保留可恢复操作。
|
||||||
|
|
||||||
|
## Responsive
|
||||||
|
|
||||||
|
- 验证宽度:320、375、414、768、1024、1440。
|
||||||
|
- 768px 以下侧栏转为页内分组导航,主内容单列。
|
||||||
|
- `html` 与 `body` 使用 `overflow-x: clip`;按钮和主导航文字不得折成两行。
|
||||||
|
- 附件网格桌面端按 200–280px 自适应列展示,移动端保持最大 280px;标题允许长家谱名称安全换行。
|
||||||
|
|
||||||
|
## What pages MUST share
|
||||||
|
|
||||||
|
- 品牌标识、朱红主操作色、排版和按钮语气。
|
||||||
|
- 页头高度、内容最大宽度、侧栏位置、表单控件高度和状态样式。
|
||||||
|
- 图片比例、媒体错误回退、空状态和权限状态。
|
||||||
|
|
||||||
|
## Per-page allowances
|
||||||
|
|
||||||
|
- 公开页可使用仓库内已有的真实祠堂和服务图片。
|
||||||
|
- 应用页不增加装饰插画,数据与操作本身承担视觉层级。
|
||||||
|
- 内容页以标题、正文、真实封面和元信息为主。
|
||||||
|
|
||||||
|
## Exports
|
||||||
|
|
||||||
|
实际 CSS 变量以根目录 `tokens.css` 为准。项目为原生 HTML/CSS/JS,不启用 Tailwind 或 shadcn;如未来迁移,颜色、字体、间距必须从相同命名 token 映射,不得另建品牌值。
|
||||||
+22
-4
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## 使用边界
|
## 使用边界
|
||||||
|
|
||||||
`Jiapu-App` 只作为产品信息架构参考。本项目的真实读取、写入和权限判断只使用当前 PC 契约及 `utils/ApiClient.js`;不得复制 App 请求路径、参数、缓存编号或测试身份信息。
|
参考项目固定为 `C:\Users\Rain\Desktop\job\Jiapu-App`,只用于核对产品信息架构。本项目的真实读取、写入和权限判断只使用当前 PC 契约及 `utils/ApiClient.js`;不得复制 App 请求路径、参数、缓存编号或测试身份信息。
|
||||||
|
|
||||||
没有当前 PC 接口契约的能力必须保留明确的不可操作状态,不能以静态数据、二维码、金额或成功提示替代。
|
没有当前 PC 接口契约的能力必须保留明确的不可操作状态,不能以静态数据、二维码、金额或成功提示替代。
|
||||||
|
|
||||||
@@ -14,8 +14,22 @@
|
|||||||
| 修改密码、换绑手机、注销、退出登录 | `profile-security.html` | 使用认证 PC 契约;表单具备校验、提交中和失败反馈。 |
|
| 修改密码、换绑手机、注销、退出登录 | `profile-security.html` | 使用认证 PC 契约;表单具备校验、提交中和失败反馈。 |
|
||||||
| 意见反馈、我的工单 | `profile-feedback.html`、`submit-ticket.html`、`my-tickets.html`、`ticket-detail.html` | 使用当前账号的反馈集合,提交后重读确认。 |
|
| 意见反馈、我的工单 | `profile-feedback.html`、`submit-ticket.html`、`my-tickets.html`、`ticket-detail.html` | 使用当前账号的反馈集合,提交后重读确认。 |
|
||||||
| 帮助中心、平台联系与协议 | `help.html`、`profile-services.html` | 帮助文章读取使用 PC 契约;平台说明使用站内页面。 |
|
| 帮助中心、平台联系与协议 | `help.html`、`profile-services.html` | 帮助文章读取使用 PC 契约;平台说明使用站内页面。 |
|
||||||
| 会员套餐与订单 | `profile-services.html` | 可选择服务端套餐和我的家谱创建订单并重读;PC 未开放在线支付。 |
|
| 会员套餐与订单 | `profile-services.html` | 读取购买能力、套餐与订单,创建微信 NATIVE 支付单并生成扫码二维码,可查询或关闭支付。 |
|
||||||
| 分享奖励、推广收益、提现、推荐偏好 | `profile-share.html`、`profile-services.html` | 当前 PC 无对应读取或写入契约,明确不可操作。 |
|
| 推广收益、提现 | `profile-earnings.html`、`profile-share.html`、`profile-services.html` | 读取余额、不可变流水和提现记录;收款码由统一上传接口产生 OSS ID,提现请求使用稳定幂等号。 |
|
||||||
|
| 分享奖励、推荐偏好 | `profile-share.html`、`profile-services.html` | 当前 PC 无对应读取或写入契约,明确不可操作。 |
|
||||||
|
|
||||||
|
## App 差异与承载决策
|
||||||
|
|
||||||
|
| App 能力或入口 | 核对结果 | PC 承载方式 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 家谱功能菜单 | 字辈、世系、谱文、家族圈、相册、视频、功德、祭祀、成长、证件、备忘、亲友、成员管理、会员与订单均已有真实 PC 闭环。 | 保留在 `profile-family-home.html` 的桌面工作台,不新增重复总览页。 |
|
||||||
|
| 发布动态、邀请家人 | PC 已有独立表单和一次性邀请闭环。 | 使用 `profile-feed-edit.html`、`profile-invite.html` 独立页面;两者都有多字段或需要上下文,不使用弹窗。 |
|
||||||
|
| 应用分享与邀请奖励 | App 通过用户编号在客户端拼接分享地址;后端只有 App 推荐关系读取接口,没有 PC 分享链接契约。 | `profile-share.html` 只说明移动端分享边界,并引导到 `profile-earnings.html` 查看真实收益和提现;不伪造二维码,也不新增页面。 |
|
||||||
|
| 个性化推荐开关 | App 页面没有持久化请求,不能视为已完成能力。 | `profile-services.html` 使用不可操作状态;待后端提供 PC 读写契约后再原位开放,不新增设置页。 |
|
||||||
|
| 个人亲属关系 | App 对应页面是静态演示数据,真实关系能力属于家谱世系。 | `profile-data.html` 只做引导,维护操作进入 `profile-tree.html` 或 `profile-family-admin.html`;不复制演示页。 |
|
||||||
|
| 分享展示页、祖先排行 | App 当前分别为静态占位和空页面。 | 不移植,不制造入口、假排名或假用户数据。 |
|
||||||
|
| 余额、提现、购买记录 | App 与 PC 都有真实业务能力,PC 契约和桌面流程已完整。 | 收益与提现集中在 `profile-earnings.html`;会员、支付和订单集中在 `profile-services.html`。支付确认适合当前页扫码区,关闭订单使用确认弹窗。 |
|
||||||
|
| 账号安全与注销 | PC 已有完整表单和危险操作确认。 | 集中在 `profile-security.html`;注销使用确认弹窗,短信、密码等多字段继续使用页面内表单。 |
|
||||||
|
|
||||||
## 家谱工作台
|
## 家谱工作台
|
||||||
|
|
||||||
@@ -30,7 +44,11 @@
|
|||||||
| 谱文、相册、视频 | `profile-article.html`、`profile-album.html`、`profile-video.html` 及各自编辑/详情页 | 已提供列表、详情和编辑闭环;没有文件访问 URL 时只展示安全业务元数据。 |
|
| 谱文、相册、视频 | `profile-article.html`、`profile-album.html`、`profile-video.html` 及各自编辑/详情页 | 已提供列表、详情和编辑闭环;没有文件访问 URL 时只展示安全业务元数据。 |
|
||||||
| 祭祀、贺礼邀约 | `profile-ceremony.html`、`profile-ceremony-detail.html`、`profile-gift.html`、`profile-gift-edit.html` | 活动、祭品和当前账号邀约使用当前 PC 契约。 |
|
| 祭祀、贺礼邀约 | `profile-ceremony.html`、`profile-ceremony-detail.html`、`profile-gift.html`、`profile-gift-edit.html` | 活动、祭品和当前账号邀约使用当前 PC 契约。 |
|
||||||
| 成长、亲友、备忘、功德记录 | 对应 `profile-*-edit.html` 与列表页 | 各自使用当前 PC CRUD 契约和真实家谱上下文。 |
|
| 成长、亲友、备忘、功德记录 | 对应 `profile-*-edit.html` 与列表页 | 各自使用当前 PC CRUD 契约和真实家谱上下文。 |
|
||||||
| 家谱证件、成员邀请、家谱排序、家谱删除 | `profile-family-home.html`、`profile-invite.html`、`profile-families.html` | 当前 PC 没有完整契约,明确不可操作。 |
|
| 成员邀请 | `profile-invite.html` | 生成一次性邀请、预览/兑换、查看本人邀请及撤销待使用邀请。 |
|
||||||
|
| 资料提醒 | `profile-data-reminders.html` | 按当前家谱读取成员资料缺项,并跳转世系维护。 |
|
||||||
|
| 重要证件 | `profile-documents.html` | 按人物维护档案和附件,使用业务字典、统一上传、临时访问令牌与密码保护。 |
|
||||||
|
| 家谱生命周期 | `profile-family-settings.html` | 支持归档、恢复;永久删除先读取能力并校验家谱全名和短信验证码。 |
|
||||||
|
| 家谱排序 | `profile-families.html` | 使用“我的家谱”响应生成选择项,支持上移、下移并保存稳定 ID 顺序。 |
|
||||||
| 个人亲属关系页 | `profile-data.html` | 当前 PC 未提供个人关系列表;页面明确引导至当前家谱的成员管理与世系维护。 |
|
| 个人亲属关系页 | `profile-data.html` | 当前 PC 未提供个人关系列表;页面明确引导至当前家谱的成员管理与世系维护。 |
|
||||||
|
|
||||||
## 验收边界
|
## 验收边界
|
||||||
|
|||||||
+7
-4
@@ -19,17 +19,20 @@
|
|||||||
|
|
||||||
## 验证方式
|
## 验证方式
|
||||||
|
|
||||||
- 自动回归:`npm test`,362 项通过。
|
- 自动回归:`npm test`,379 项通过。
|
||||||
|
- 独立无界面 Chrome 点击回归:132 条流程通过。除原有认证、表单、权限和异常流程外,逐页覆盖 33 个家谱工作区页面、14 个账号工作区页面,实际点击 22 个家谱侧栏入口和 12 个账号侧栏入口,并检查顶部/左侧位置、宽度、吸顶行为、当前项、父级展开、家谱上下文以及 1440px、1024px、390px 三档横向溢出。
|
||||||
- 真实流程:登录后进入真实家谱列表、选择家谱主页,并在 1249px 桌面与 390px 手机视口检查导航和首屏。
|
- 真实流程:登录后进入真实家谱列表、选择家谱主页,并在 1249px 桌面与 390px 手机视口检查导航和首屏。
|
||||||
- 验收使用隔离浏览器配置;测试账号、登录令牌及真实家谱标识不写入仓库。
|
- 验收使用隔离浏览器配置;测试账号、登录令牌及真实家谱标识不写入仓库。
|
||||||
|
|
||||||
## 20% 后续维护记录
|
## 20% 后续维护记录
|
||||||
|
|
||||||
- 个人中心继续以 Jiapu-App 的账户与家谱能力为信息架构参考;真实读写仅使用当前 PC 客户端契约。
|
- 个人中心继续以 `C:\Users\Rain\Desktop\job\Jiapu-App` 的账户与家谱能力为信息架构参考;真实读写仅使用当前 PC 客户端契约。
|
||||||
- 已补齐当前家谱上下文传递、业务页统一加载/空数据/失败/无权限状态,以及账户、家谱和内容表单的标签与失败反馈。
|
- 已补齐当前家谱上下文传递、业务页统一加载/空数据/失败/无权限状态,以及账户、家谱和内容表单的标签与失败反馈。
|
||||||
- PC 尚未声明契约的资料提醒、家谱邀请、家谱删除、家谱排序、分享奖励、推广收益与提现,保留明确的不可操作状态;不展示模拟金额、记录、二维码或成功结果。
|
- 2026-08-27 按后端最新 `genealogy-pc-openapi.yaml` 复核后,资料提醒、家谱邀请、重要证件、家谱生命周期、推广收益与提现、VIP 微信扫码支付均已有正式 PC 契约并已接入;分享奖励、推荐偏好仍保持不可操作,不展示模拟数据。
|
||||||
- 交给后端的完整补齐范围、最小 DTO、权限和验收要求见 `docs/PC后端补齐清单.md`;该清单只描述 PC 能力,不迁用 App 接口。
|
- 交给后端的完整补齐范围、最小 DTO、权限和验收要求见 `docs/PC后端补齐清单.md`;该清单只描述 PC 能力,不迁用 App 接口。
|
||||||
- 本次当前全量回归为 362 项通过;测试账号、密码、令牌和真实家谱信息仍不写入代码、文档或 Git。
|
- 本次当前全量回归为 379 项通过;测试账号、密码、令牌和真实家谱信息仍不写入代码、文档或 Git。
|
||||||
|
- 全部根目录 HTML 的站内链接和页面锚点已做自动完整性检查;空锚点形式的退出操作已改为语义正确的按钮,避免被误判为导航和被自动巡检误触。
|
||||||
|
- 活动页在没有任何可邀请成员时不再提交空名单,避免无意义写请求及误清空邀请;该边界已纳入无界面浏览器连续提交回归。
|
||||||
- 角色运行时验收受阻:已用隔离身份只读确认谱主能力和未认证访问闭环,但该身份可访问的家谱中没有管理员或普通成员实体。未创建成员、未提交申请或写入家谱数据;解除条件是提供已有的隔离管理员、普通成员身份,或预先在隔离家谱中准备对应角色关系。
|
- 角色运行时验收受阻:已用隔离身份只读确认谱主能力和未认证访问闭环,但该身份可访问的家谱中没有管理员或普通成员实体。未创建成员、未提交申请或写入家谱数据;解除条件是提供已有的隔离管理员、普通成员身份,或预先在隔离家谱中准备对应角色关系。
|
||||||
- 最新只读复核跳过:隔离登录响应未提供可明确消费的令牌字段;为避免打印原始响应或猜测字段,未继续请求角色接口。该项不影响现有静态权限回归,待测试环境恢复标准 PC 登录响应后再验收。
|
- 最新只读复核跳过:隔离登录响应未提供可明确消费的令牌字段;为避免打印原始响应或猜测字段,未继续请求角色接口。该项不影响现有静态权限回归,待测试环境恢复标准 PC 登录响应后再验收。
|
||||||
|
|
||||||
|
|||||||
+8
-9
@@ -32,7 +32,7 @@
|
|||||||
<section class="inner-hero download-hero">
|
<section class="inner-hero download-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="hero-copy">
|
<div class="hero-copy">
|
||||||
<div class="eyebrow">Download</div>
|
<div class="eyebrow">应用下载</div>
|
||||||
<h1>下载代代相传 APP</h1>
|
<h1>下载代代相传 APP</h1>
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
手机端适合日常上传宣传相册、发布宣传视频、查看家谱和补充资料。
|
手机端适合日常上传宣传相册、发布宣传视频、查看家谱和补充资料。
|
||||||
@@ -43,9 +43,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hero-card qr-card tilt-card">
|
<div class="hero-card qr-card tilt-card">
|
||||||
<div class="qr-box"><span>扫码</span></div>
|
<h3>选择下载方式</h3>
|
||||||
<h3>手机扫码下载</h3>
|
<p>请在下方选择平台当前提供的可用下载入口。</p>
|
||||||
<p>支持 iOS 与 Android,实际项目可替换为真实下载二维码。</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -53,7 +52,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-head">
|
<div class="section-head">
|
||||||
<h2>下载方式</h2>
|
<h2>下载方式</h2>
|
||||||
<p>前台先展示下载入口,后续可以接真实应用商店地址。</p>
|
<p>下载地址会根据平台当前配置显示;没有可用入口时会明确提示。</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-3" data-promotion-download-list>
|
<div class="grid-3" data-promotion-download-list>
|
||||||
<div class="api-empty">下载推广内容加载中</div>
|
<div class="api-empty">下载推广内容加载中</div>
|
||||||
@@ -84,10 +83,10 @@
|
|||||||
><a href="notice-detail.html">平台公告</a>
|
><a href="notice-detail.html">平台公告</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>联系我们</h4>
|
<h4>服务支持</h4>
|
||||||
<p>客服电话:400-000-0000</p>
|
<a href="help.html">帮助中心</a>
|
||||||
<p>邮箱:service@example.com</p>
|
<a href="submit-ticket.html">提交问题</a>
|
||||||
<p>地址:中国 · 成都</p>
|
<a href="about.html">关于平台</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-legal">
|
<div class="footer-legal">
|
||||||
|
|||||||
+5
-5
@@ -36,7 +36,7 @@
|
|||||||
<section class="inner-hero detail-hero">
|
<section class="inner-hero detail-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="hero-copy">
|
<div class="hero-copy">
|
||||||
<div class="eyebrow">Family Detail</div>
|
<div class="eyebrow">家谱详情</div>
|
||||||
<h1 data-family-text="title">四川武胜汤氏族</h1>
|
<h1 data-family-text="title">四川武胜汤氏族</h1>
|
||||||
<p class="lead" data-family-text="intro|description|summary" data-fallback="公开展示家族源流、世系概览、字辈排行、谱文资料和宣传相册,让访客快速理解这一本家谱的内容结构。">
|
<p class="lead" data-family-text="intro|description|summary" data-fallback="公开展示家族源流、世系概览、字辈排行、谱文资料和宣传相册,让访客快速理解这一本家谱的内容结构。">
|
||||||
公开展示家族源流、世系概览、字辈排行、谱文资料和宣传相册,让访客快速理解这一本家谱的内容结构。
|
公开展示家族源流、世系概览、字辈排行、谱文资料和宣传相册,让访客快速理解这一本家谱的内容结构。
|
||||||
@@ -119,10 +119,10 @@
|
|||||||
><a href="about.html">平台公告</a>
|
><a href="about.html">平台公告</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>联系我们</h4>
|
<h4>服务支持</h4>
|
||||||
<p>客服电话:400-000-0000</p>
|
<a href="help.html">帮助中心</a>
|
||||||
<p>邮箱:service@example.com</p>
|
<a href="submit-ticket.html">提交问题</a>
|
||||||
<p>地址:中国 · 成都</p>
|
<a href="about.html">关于平台</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-legal">
|
<div class="footer-legal">
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
class="card form-card auth-card recover-card"
|
class="card form-card auth-card recover-card"
|
||||||
aria-labelledby="recover-title"
|
aria-labelledby="recover-title"
|
||||||
>
|
>
|
||||||
<div class="eyebrow">Password Recovery</div>
|
<div class="eyebrow">找回密码</div>
|
||||||
<h1 id="recover-title">找回密码</h1>
|
<h1 id="recover-title">找回密码</h1>
|
||||||
<p class="lead">输入注册手机号,获取验证码后重设登录密码。</p>
|
<p class="lead">输入注册手机号,获取验证码后重设登录密码。</p>
|
||||||
<form id="password-reset-form" class="form-grid" novalidate>
|
<form id="password-reset-form" class="form-grid" novalidate>
|
||||||
|
|||||||
+7
-7
@@ -36,7 +36,7 @@
|
|||||||
<section class="inner-hero genealogy-hero">
|
<section class="inner-hero genealogy-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="hero-copy">
|
<div class="hero-copy">
|
||||||
<div class="eyebrow">Digital Genealogy</div>
|
<div class="eyebrow">数字家谱</div>
|
||||||
<h1>把成员关系整理成清晰可查的数字家谱</h1>
|
<h1>把成员关系整理成清晰可查的数字家谱</h1>
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
从始祖、分支、排行到个人资料,数字家谱帮助家族建立长期可维护的成员档案和世系关系。
|
从始祖、分支、排行到个人资料,数字家谱帮助家族建立长期可维护的成员档案和世系关系。
|
||||||
@@ -132,17 +132,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>产品入口</h4>
|
<h4>产品入口</h4>
|
||||||
<a>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
|
<a href="genealogy.html">数字家谱</a><a href="plaza.html">家谱广场</a><a href="app.html">应用下载</a><a href="help.html">帮助中心</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>文化内容</h4>
|
<h4>文化内容</h4>
|
||||||
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a><a>平台公告</a>
|
<a href="culture.html">家族文化</a><a href="surname.html">姓氏百科</a><a href="article-detail.html">家谱知识</a><a href="notice-detail.html">平台公告</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>联系我们</h4>
|
<h4>服务支持</h4>
|
||||||
<p>客服电话:400-000-0000</p>
|
<a href="help.html">帮助中心</a>
|
||||||
<p>邮箱:service@example.com</p>
|
<a href="submit-ticket.html">提交问题</a>
|
||||||
<p>地址:中国 · 成都</p>
|
<a href="about.html">关于平台</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-legal">
|
<div class="footer-legal">
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<section class="inner-hero help-hero">
|
<section class="inner-hero help-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="hero-copy">
|
<div class="hero-copy">
|
||||||
<div class="eyebrow">Help Center</div>
|
<div class="eyebrow">帮助中心</div>
|
||||||
<h1>从创建到协作,快速解决常见问题</h1>
|
<h1>从创建到协作,快速解决常见问题</h1>
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
帮助内容由平台持续维护,覆盖账号、家谱和内容使用中的常见问题。
|
帮助内容由平台持续维护,覆盖账号、家谱和内容使用中的常见问题。
|
||||||
@@ -86,17 +86,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>产品入口</h4>
|
<h4>产品入口</h4>
|
||||||
<a>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
|
<a href="genealogy.html">数字家谱</a><a href="plaza.html">家谱广场</a><a href="app.html">应用下载</a><a href="help.html">帮助中心</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>文化内容</h4>
|
<h4>文化内容</h4>
|
||||||
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a><a>平台公告</a>
|
<a href="culture.html">家族文化</a><a href="surname.html">姓氏百科</a><a href="article-detail.html">家谱知识</a><a href="notice-detail.html">平台公告</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>联系我们</h4>
|
<h4>服务支持</h4>
|
||||||
<p>客服电话:400-000-0000</p>
|
<a href="help.html">帮助中心</a>
|
||||||
<p>邮箱:service@example.com</p>
|
<a href="submit-ticket.html">提交问题</a>
|
||||||
<p>地址:中国 · 成都</p>
|
<a href="about.html">关于平台</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-legal">
|
<div class="footer-legal">
|
||||||
|
|||||||
+32
-108
@@ -292,81 +292,63 @@
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="ad-card tilt-card"
|
class="ad-card tilt-card"
|
||||||
href="https://example.com/family-album"
|
href="promo-album.html"
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
aria-label="影像相册广告"
|
aria-label="影像相册广告"
|
||||||
>
|
>
|
||||||
<img src="public/images/ad-album-small.png" alt="影像相册" />
|
<img src="public/images/ad-album-small.png" alt="影像相册" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="ad-card tilt-card"
|
class="ad-card tilt-card"
|
||||||
href="https://example.com/family-video"
|
href="promo-video.html"
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
aria-label="活动视频广告"
|
aria-label="活动视频广告"
|
||||||
>
|
>
|
||||||
<img src="public/images/ad-video-small.png" alt="活动视频" />
|
<img src="public/images/ad-video-small.png" alt="活动视频" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="ad-card tilt-card"
|
class="ad-card tilt-card"
|
||||||
href="https://example.com/genealogy-print"
|
href="genealogy.html"
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
aria-label="族谱印制广告"
|
aria-label="族谱印制广告"
|
||||||
>
|
>
|
||||||
<img src="public/images/ad-print.png" alt="族谱印制" />
|
<img src="public/images/ad-print.png" alt="族谱印制" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="ad-card tilt-card"
|
class="ad-card tilt-card"
|
||||||
href="https://example.com/ancestral-hall"
|
href="culture.html"
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
aria-label="祠堂文化广告"
|
aria-label="祠堂文化广告"
|
||||||
>
|
>
|
||||||
<img src="public/images/ad-hall.png" alt="祠堂文化" />
|
<img src="public/images/ad-hall.png" alt="祠堂文化" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="ad-card tilt-card"
|
class="ad-card tilt-card"
|
||||||
href="https://example.com/cloud-archive"
|
href="app.html"
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
aria-label="云端存档广告"
|
aria-label="云端存档广告"
|
||||||
>
|
>
|
||||||
<img src="public/images/ad-cloud.png" alt="云端存档" />
|
<img src="public/images/ad-cloud.png" alt="云端存档" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="ad-card tilt-card"
|
class="ad-card tilt-card"
|
||||||
href="https://example.com/clan-reunion"
|
href="culture.html"
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
aria-label="宗亲联谊广告"
|
aria-label="宗亲联谊广告"
|
||||||
>
|
>
|
||||||
<img src="public/images/ad-reunion.png" alt="宗亲联谊" />
|
<img src="public/images/ad-reunion.png" alt="宗亲联谊" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="ad-card tilt-card"
|
class="ad-card tilt-card"
|
||||||
href="https://example.com/surname-research"
|
href="surname.html"
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
aria-label="姓氏研究广告"
|
aria-label="姓氏研究广告"
|
||||||
>
|
>
|
||||||
<img src="public/images/ad-research.png" alt="姓氏研究" />
|
<img src="public/images/ad-research.png" alt="姓氏研究" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="ad-card tilt-card"
|
class="ad-card tilt-card"
|
||||||
href="https://example.com/family-memory"
|
href="culture.html"
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
aria-label="家风记忆广告"
|
aria-label="家风记忆广告"
|
||||||
>
|
>
|
||||||
<img src="public/images/ad-memory.png" alt="家风记忆" />
|
<img src="public/images/ad-memory.png" alt="家风记忆" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="ad-card tilt-card"
|
class="ad-card tilt-card"
|
||||||
href="https://example.com/data-check"
|
href="help.html"
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
aria-label="资料校对广告"
|
aria-label="资料校对广告"
|
||||||
>
|
>
|
||||||
<img src="public/images/ad-data.png" alt="资料校对" />
|
<img src="public/images/ad-data.png" alt="资料校对" />
|
||||||
@@ -425,9 +407,7 @@
|
|||||||
<div class="recommend-grid">
|
<div class="recommend-grid">
|
||||||
<a
|
<a
|
||||||
class="recommend-card tilt-card"
|
class="recommend-card tilt-card"
|
||||||
href="https://example.com/archive-guide"
|
href="article-detail.html"
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>
|
>
|
||||||
<span>资料整理</span>
|
<span>资料整理</span>
|
||||||
<h3>家庭档案整理指南</h3>
|
<h3>家庭档案整理指南</h3>
|
||||||
@@ -435,9 +415,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="recommend-card tilt-card"
|
class="recommend-card tilt-card"
|
||||||
href="https://example.com/surname-origin"
|
href="surname.html"
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>
|
>
|
||||||
<span>姓氏源流</span>
|
<span>姓氏源流</span>
|
||||||
<h3>姓氏文化资料库</h3>
|
<h3>姓氏文化资料库</h3>
|
||||||
@@ -445,9 +423,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="recommend-card tilt-card"
|
class="recommend-card tilt-card"
|
||||||
href="https://example.com/clan-culture"
|
href="culture.html"
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>
|
>
|
||||||
<span>宗亲文化</span>
|
<span>宗亲文化</span>
|
||||||
<h3>宗亲会活动参考</h3>
|
<h3>宗亲会活动参考</h3>
|
||||||
@@ -455,9 +431,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="recommend-card tilt-card"
|
class="recommend-card tilt-card"
|
||||||
href="https://example.com/genealogy-tools"
|
href="help.html"
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>
|
>
|
||||||
<span>工具服务</span>
|
<span>工具服务</span>
|
||||||
<h3>修谱工具与服务</h3>
|
<h3>修谱工具与服务</h3>
|
||||||
@@ -465,9 +439,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="recommend-card tilt-card"
|
class="recommend-card tilt-card"
|
||||||
href="https://example.com/local-history"
|
href="culture.html"
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>
|
>
|
||||||
<span>地方志</span>
|
<span>地方志</span>
|
||||||
<h3>地方文献检索入口</h3>
|
<h3>地方文献检索入口</h3>
|
||||||
@@ -475,9 +447,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="recommend-card tilt-card"
|
class="recommend-card tilt-card"
|
||||||
href="https://example.com/name-study"
|
href="surname.html"
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>
|
>
|
||||||
<span>人名研究</span>
|
<span>人名研究</span>
|
||||||
<h3>字辈与命名参考</h3>
|
<h3>字辈与命名参考</h3>
|
||||||
@@ -485,9 +455,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="recommend-card tilt-card"
|
class="recommend-card tilt-card"
|
||||||
href="https://example.com/photo-restore"
|
href="promo-album.html"
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>
|
>
|
||||||
<span>影像修复</span>
|
<span>影像修复</span>
|
||||||
<h3>老照片修复服务</h3>
|
<h3>老照片修复服务</h3>
|
||||||
@@ -495,9 +463,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="recommend-card tilt-card"
|
class="recommend-card tilt-card"
|
||||||
href="https://example.com/oral-history"
|
href="article-detail.html"
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>
|
>
|
||||||
<span>口述史</span>
|
<span>口述史</span>
|
||||||
<h3>家族故事采集方法</h3>
|
<h3>家族故事采集方法</h3>
|
||||||
@@ -510,47 +476,18 @@
|
|||||||
<section class="friend-links">
|
<section class="friend-links">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="friend-links-panel">
|
<div class="friend-links-panel">
|
||||||
<h2>友情链接</h2>
|
<h2>常用入口</h2>
|
||||||
<div class="friend-link-list">
|
<div class="friend-link-list">
|
||||||
<a href="https://3d.3dyjs.cn/" target="_blank" rel="noopener"
|
<a href="https://3d.3dyjs.cn/" target="_blank" rel="noopener"
|
||||||
>3D研究室</a
|
>3D研究室</a
|
||||||
>
|
>
|
||||||
<a
|
<a href="surname.html">姓氏源流资料</a>
|
||||||
href="https://example.com/surnames"
|
<a href="plaza.html">公开家谱资料</a>
|
||||||
target="_blank"
|
<a href="article-detail.html">修谱知识库</a>
|
||||||
rel="noopener"
|
<a href="culture.html">地方文献</a>
|
||||||
>姓氏源流资料</a
|
<a href="promo-album.html">家族影像</a>
|
||||||
>
|
<a href="genealogy.html">数字家谱</a>
|
||||||
<a
|
<a href="help.html">使用帮助</a>
|
||||||
href="https://example.com/genealogy"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>公开家谱资料</a
|
|
||||||
>
|
|
||||||
<a href="https://example.com/wiki" target="_blank" rel="noopener"
|
|
||||||
>修谱知识库</a
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="https://example.com/history"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>地方文献</a
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="https://example.com/archive"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>档案服务</a
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="https://example.com/printing"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>谱书印制</a
|
|
||||||
>
|
|
||||||
<a href="https://example.com/photo" target="_blank" rel="noopener"
|
|
||||||
>老照片修复</a
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -562,26 +499,13 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2>手机端随时记录,电脑端清晰浏览</h2>
|
<h2>手机端随时记录,电脑端清晰浏览</h2>
|
||||||
<p>
|
<p>
|
||||||
家族成员可以在手机端上传照片、查看家谱、发布动态;PC
|
家族成员可以在手机端上传照片、查看家谱、发布动态;电脑端适合浏览门户内容、查看公开家谱和进入家谱空间。
|
||||||
端适合浏览门户内容、查看公开家谱和进入家谱空间。
|
|
||||||
</p>
|
</p>
|
||||||
<div class="cta">
|
<div class="cta">
|
||||||
<a class="btn primary magnetic" href="download.html">下载应用</a
|
<a class="btn primary magnetic" href="download.html">下载应用</a
|
||||||
><a class="btn ghost magnetic" href="help.html">查看帮助</a>
|
><a class="btn ghost magnetic" href="help.html">查看帮助</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="qr-wrap">
|
|
||||||
<div class="qr">
|
|
||||||
<div class="qr-box"></div>
|
|
||||||
<strong>小程序</strong>
|
|
||||||
<p class="qr-note">扫码体验</p>
|
|
||||||
</div>
|
|
||||||
<div class="qr">
|
|
||||||
<div class="qr-box"></div>
|
|
||||||
<strong>APP 下载</strong>
|
|
||||||
<p class="qr-note">手机查看</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -601,18 +525,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>产品入口</h4>
|
<h4>产品入口</h4>
|
||||||
<a>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
|
<a href="genealogy.html">数字家谱</a><a href="plaza.html">家谱广场</a><a href="app.html">应用下载</a><a href="help.html">帮助中心</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>文化内容</h4>
|
<h4>文化内容</h4>
|
||||||
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a
|
<a href="culture.html">家族文化</a><a href="surname.html">姓氏百科</a><a href="article-detail.html">家谱知识</a
|
||||||
><a href="news.html">新闻资讯</a>
|
><a href="news.html">新闻资讯</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>联系我们</h4>
|
<h4>服务支持</h4>
|
||||||
<p>客服电话:400-000-0000</p>
|
<a href="help.html">帮助中心</a>
|
||||||
<p>邮箱:service@example.com</p>
|
<a href="submit-ticket.html">提交问题</a>
|
||||||
<p>地址:中国 · 成都</p>
|
<a href="about.html">关于平台</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-legal">
|
<div class="footer-legal">
|
||||||
|
|||||||
+58
-51
@@ -1,64 +1,70 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>加入家谱 - 代代相传</title>
|
<title>加入家谱 - 个人中心 - 代代相传</title>
|
||||||
<link rel="stylesheet" href="public/css/public.css" />
|
<link rel="stylesheet" href="public/css/public.css" />
|
||||||
|
<link rel="stylesheet" href="public/layui/css/layui.css" />
|
||||||
|
<link rel="stylesheet" href="public/css/profile-module.css" />
|
||||||
<link rel="stylesheet" href="public/css/join-genealogy.css" />
|
<link rel="stylesheet" href="public/css/join-genealogy.css" />
|
||||||
</head>
|
</head>
|
||||||
<body class="page-join-genealogy" data-join-apply-page>
|
<body class="page-profile-module page-join-genealogy" data-join-apply-page>
|
||||||
<main class="auth-layout">
|
<header class="site-header">
|
||||||
<a class="brand auth-page-brand" href="index.html"
|
<div class="container nav">
|
||||||
><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a
|
<a class="brand magnetic" href="profile.html"><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a>
|
||||||
>
|
<nav class="nav-links" aria-label="个人中心主导航"></nav>
|
||||||
<section
|
<div class="nav-actions"><a href="profile-join-family.html">查看我的申请</a></div>
|
||||||
class="card form-card auth-card join-card"
|
</div>
|
||||||
aria-labelledby="join-title"
|
</header>
|
||||||
>
|
<main>
|
||||||
<div class="eyebrow">Join Family</div>
|
<section class="module-hero">
|
||||||
<h1 id="join-title">加入家谱</h1>
|
<div class="container">
|
||||||
<p class="lead">搜索并选择要加入的家谱,再补充申请资料。家谱编号由系统自动处理。</p>
|
<div class="module-kicker">我的家谱</div>
|
||||||
<div class="join-page-grid">
|
<div class="module-title-row">
|
||||||
<section class="join-search-panel" aria-labelledby="join-search-title">
|
<div>
|
||||||
<h2 id="join-search-title">选择家谱</h2>
|
<h1>加入家谱</h1>
|
||||||
<form class="form-grid" data-join-genealogy-search novalidate aria-describedby="join-options-status">
|
<p>先搜索并选择要加入的家谱,再补充申请资料;家谱编号由系统自动处理。</p>
|
||||||
<label class="field">
|
</div>
|
||||||
<span>搜索家谱</span>
|
<a class="btn ghost magnetic" href="profile-join-family.html">查看我的申请</a>
|
||||||
<input name="keyword" type="search" maxlength="50" placeholder="输入谱名或姓氏" />
|
</div>
|
||||||
</label>
|
</div>
|
||||||
<button class="btn ghost" type="submit">搜索</button>
|
</section>
|
||||||
</form>
|
<section class="section">
|
||||||
<div id="join-options-status" class="form-note" data-join-options-status role="status" aria-live="polite"></div>
|
<div class="container module-layout">
|
||||||
<div class="module-list" data-join-genealogy-options aria-live="polite"></div>
|
<aside class="module-nav" aria-label="个人中心功能导航"></aside>
|
||||||
<p class="form-note" data-join-selected-genealogy role="status" aria-live="polite">尚未选择家谱</p>
|
<div class="module-main">
|
||||||
</section>
|
<section class="module-panel">
|
||||||
<form class="form-grid join-apply-form" data-join-apply-form novalidate aria-describedby="join-apply-status">
|
<div class="join-page-grid">
|
||||||
<h2>补充申请资料</h2>
|
<section class="join-search-panel" aria-labelledby="join-search-title">
|
||||||
<p class="form-note">以下资料均为选填,审核人将据此确认您的加入申请。</p>
|
<h2 id="join-search-title">选择家谱</h2>
|
||||||
<label class="field">
|
<form class="form-grid" data-join-genealogy-search novalidate aria-describedby="join-options-status">
|
||||||
<span>申请人姓名(选填)</span>
|
<label class="field"><span>搜索家谱</span><input name="keyword" type="search" maxlength="50" placeholder="输入谱名或姓氏" /></label>
|
||||||
<input name="applicantName" maxlength="50" autocomplete="name" />
|
<button class="btn ghost" type="submit">搜索</button>
|
||||||
</label>
|
</form>
|
||||||
<label class="field">
|
<div id="join-options-status" class="form-note" data-join-options-status role="status" aria-live="polite"></div>
|
||||||
<span>联系电话(选填)</span>
|
<div class="module-list" data-join-genealogy-options aria-live="polite"></div>
|
||||||
<input name="phone" maxlength="30" autocomplete="tel" />
|
<p class="form-note" data-join-selected-genealogy role="status" aria-live="polite">尚未选择家谱</p>
|
||||||
</label>
|
</section>
|
||||||
<label class="field">
|
<form class="form-grid join-apply-form" data-join-apply-form novalidate aria-describedby="join-apply-status">
|
||||||
<span>与家谱关系(选填)</span>
|
<h2>补充申请资料</h2>
|
||||||
<input name="relationDesc" maxlength="100" placeholder="例如:族亲" />
|
<p class="form-note">以下资料均为选填,审核人将据此确认您的加入申请。</p>
|
||||||
</label>
|
<label class="field"><span>申请人姓名(选填)</span><input name="applicantName" maxlength="50" autocomplete="name" /></label>
|
||||||
<label class="field">
|
<label class="field"><span>联系电话(选填)</span><input name="phone" maxlength="30" autocomplete="tel" /></label>
|
||||||
<span>申请理由(选填)</span>
|
<label class="field"><span>与家谱关系(选填)</span><input name="relationDesc" maxlength="100" placeholder="例如:族亲" /></label>
|
||||||
<textarea name="applyReason" maxlength="500" rows="4"></textarea>
|
<label class="field"><span>申请理由(选填)</span><textarea name="applyReason" maxlength="500" rows="4"></textarea></label>
|
||||||
</label>
|
<div id="join-apply-status" class="form-note" data-join-apply-status role="status" aria-live="polite"></div>
|
||||||
<div id="join-apply-status" class="form-note" data-join-apply-status role="status" aria-live="polite"></div>
|
<button class="btn primary" type="submit" data-join-apply-submit>提交申请</button>
|
||||||
<button class="btn primary" type="submit" data-join-apply-submit>提交申请</button>
|
</form>
|
||||||
<a class="btn ghost" href="profile-join-family.html">查看我的申请</a>
|
</div>
|
||||||
</form>
|
</section>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
<script src="public/js/jquery360.js"></script>
|
||||||
|
<script src="public/layui/layui.js"></script>
|
||||||
|
<script src="public/js/lay-config.js"></script>
|
||||||
<script src="config.js"></script>
|
<script src="config.js"></script>
|
||||||
<script src="utils/StorageUtil.js"></script>
|
<script src="utils/StorageUtil.js"></script>
|
||||||
<script src="utils/axios.js"></script>
|
<script src="utils/axios.js"></script>
|
||||||
@@ -66,5 +72,6 @@
|
|||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
<script src="public/js/profile-common.js"></script>
|
<script src="public/js/profile-common.js"></script>
|
||||||
<script src="public/js/join-pages.js"></script>
|
<script src="public/js/join-pages.js"></script>
|
||||||
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a
|
><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a
|
||||||
>
|
>
|
||||||
<section class="card form-card auth-card" aria-labelledby="login-title">
|
<section class="card form-card auth-card" aria-labelledby="login-title">
|
||||||
<div class="eyebrow">Welcome Back</div>
|
<div class="eyebrow">欢迎回来</div>
|
||||||
<h1 id="login-title">登录账号</h1>
|
<h1 id="login-title">登录账号</h1>
|
||||||
<p class="lead">登录后可创建、加入和维护家谱。</p>
|
<p class="lead">登录后可创建、加入和维护家谱。</p>
|
||||||
<div class="login-mode-tabs" role="tablist" aria-label="登录方式">
|
<div class="login-mode-tabs" role="tablist" aria-label="登录方式">
|
||||||
|
|||||||
+30
-44
@@ -3,65 +3,51 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>我的工单 - 代代相传</title>
|
<title>我的工单 - 个人中心 - 代代相传</title>
|
||||||
<link rel="stylesheet" href="public/css/public.css" />
|
<link rel="stylesheet" href="public/css/public.css" />
|
||||||
<link rel="stylesheet" href="public/css/site-info.css" />
|
<link rel="stylesheet" href="public/layui/css/layui.css" />
|
||||||
|
<link rel="stylesheet" href="public/css/profile-module.css" />
|
||||||
</head>
|
</head>
|
||||||
<body data-feedback-list-page>
|
<body class="page-profile-module" data-feedback-list-page>
|
||||||
<header class="site-header">
|
<header class="site-header">
|
||||||
<div class="container nav">
|
<div class="container nav">
|
||||||
<a class="brand magnetic" href="index.html"
|
<a class="brand magnetic" href="profile.html"><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a>
|
||||||
><img
|
<nav class="nav-links" aria-label="个人中心主导航"></nav>
|
||||||
class="brand-logo"
|
<div class="nav-actions"><a href="help.html">帮助中心</a><a class="btn primary magnetic" href="submit-ticket.html">提交新工单</a></div>
|
||||||
src="public/images/logo.png"
|
|
||||||
alt="我们的家谱,代代相传"
|
|
||||||
/></a>
|
|
||||||
<nav class="nav-links">
|
|
||||||
<a href="index.html">首页</a
|
|
||||||
><a class="active" href="help.html">帮助中心</a
|
|
||||||
><a href="news.html">新闻资讯</a>
|
|
||||||
</nav>
|
|
||||||
<div class="nav-actions"><a href="login.html">登录</a></div>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<section class="site-info-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div>
|
<div class="module-kicker">服务与帮助</div>
|
||||||
<div class="site-info-kicker">My Tickets</div>
|
<div class="module-title-row">
|
||||||
<h1>我的工单</h1>
|
<div><h1>我的工单</h1><p>查看当前账号提交的问题说明和处理状态。</p></div>
|
||||||
<p>查看通过 PC 反馈接口提交的问题说明和处理状态。</p>
|
<a class="btn primary magnetic" href="submit-ticket.html">提交新工单</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="site-info-mark">单</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="site-info-section alt">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container module-layout">
|
||||||
<div class="site-info-list">
|
<aside class="module-nav" aria-label="个人中心功能导航"></aside>
|
||||||
<div class="module-title-row">
|
<div class="module-main">
|
||||||
<h2>我的反馈记录</h2>
|
<section class="module-panel">
|
||||||
<button class="btn ghost" type="button" data-feedback-refresh>刷新</button>
|
<div class="module-title-row">
|
||||||
</div>
|
<div><h2>反馈记录</h2><p>选择记录可以查看完整说明和处理状态。</p></div>
|
||||||
<div class="form-status" role="status" aria-live="polite" data-feedback-status></div>
|
<button class="btn ghost" type="button" data-feedback-refresh>刷新</button>
|
||||||
<div class="module-list" data-feedback-list>
|
</div>
|
||||||
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取反馈记录…</div>
|
<div class="form-status" role="status" aria-live="polite" data-feedback-status></div>
|
||||||
</div>
|
<div class="module-list" data-feedback-list>
|
||||||
<div class="site-info-actions">
|
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取反馈记录…</div>
|
||||||
<a class="btn primary magnetic" href="submit-ticket.html">提交新工单</a>
|
</div>
|
||||||
<a class="btn ghost magnetic" href="help.html">返回帮助中心</a>
|
</section>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<footer class="footer">
|
<script src="public/js/jquery360.js"></script>
|
||||||
<div class="container">
|
<script src="public/layui/layui.js"></script>
|
||||||
<div class="footer-legal">
|
<script src="public/js/lay-config.js"></script>
|
||||||
<div>© 2026 代代相传. All rights reserved.</div>
|
<script src="public/js/profile-common.js"></script>
|
||||||
<a href="privacy.html">隐私政策</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
<script src="config.js"></script>
|
<script src="config.js"></script>
|
||||||
<script src="utils/StorageUtil.js"></script>
|
<script src="utils/StorageUtil.js"></script>
|
||||||
<script src="utils/axios.js"></script>
|
<script src="utils/axios.js"></script>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
<section class="site-info-hero">
|
<section class="site-info-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div>
|
<div>
|
||||||
<div class="site-info-kicker">News Center</div>
|
<div class="site-info-kicker">资讯中心</div>
|
||||||
<h1>新闻资讯</h1>
|
<h1>新闻资讯</h1>
|
||||||
<p>
|
<p>
|
||||||
集中阅读平台新闻、公告与下载内容。
|
集中阅读平台新闻、公告与下载内容。
|
||||||
|
|||||||
+5
-5
@@ -32,7 +32,7 @@
|
|||||||
<section class="inner-hero notice-hero">
|
<section class="inner-hero notice-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="hero-copy">
|
<div class="hero-copy">
|
||||||
<div class="eyebrow">Notice</div>
|
<div class="eyebrow">平台公告</div>
|
||||||
<h1 data-promotion-title>平台内容公开展示规范说明</h1>
|
<h1 data-promotion-title>平台内容公开展示规范说明</h1>
|
||||||
<p class="lead" data-promotion-summary>
|
<p class="lead" data-promotion-summary>
|
||||||
为了保护家族资料安全,平台仅展示管理员确认公开的家谱介绍、文章、宣传相册和宣传视频。
|
为了保护家族资料安全,平台仅展示管理员确认公开的家谱介绍、文章、宣传相册和宣传视频。
|
||||||
@@ -94,10 +94,10 @@
|
|||||||
><a href="notice-detail.html">平台公告</a>
|
><a href="notice-detail.html">平台公告</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>联系我们</h4>
|
<h4>服务支持</h4>
|
||||||
<p>客服电话:400-000-0000</p>
|
<a href="help.html">帮助中心</a>
|
||||||
<p>邮箱:service@example.com</p>
|
<a href="submit-ticket.html">提交问题</a>
|
||||||
<p>地址:中国 · 成都</p>
|
<a href="about.html">关于平台</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-legal">
|
<div class="footer-legal">
|
||||||
|
|||||||
+7
-7
@@ -36,7 +36,7 @@
|
|||||||
<section class="inner-hero plaza-hero">
|
<section class="inner-hero plaza-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="hero-copy">
|
<div class="hero-copy">
|
||||||
<div class="eyebrow">Family Plaza</div>
|
<div class="eyebrow">家谱广场</div>
|
||||||
<h1>浏览公开家谱,寻找同宗线索</h1>
|
<h1>浏览公开家谱,寻找同宗线索</h1>
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
按姓氏、地区、堂号和家谱名称检索公开家谱,先看示例,再创建自己的家族空间。
|
按姓氏、地区、堂号和家谱名称检索公开家谱,先看示例,再创建自己的家族空间。
|
||||||
@@ -119,17 +119,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>产品入口</h4>
|
<h4>产品入口</h4>
|
||||||
<a>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
|
<a href="genealogy.html">数字家谱</a><a href="plaza.html">家谱广场</a><a href="app.html">应用下载</a><a href="help.html">帮助中心</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>文化内容</h4>
|
<h4>文化内容</h4>
|
||||||
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a><a>平台公告</a>
|
<a href="culture.html">家族文化</a><a href="surname.html">姓氏百科</a><a href="article-detail.html">家谱知识</a><a href="notice-detail.html">平台公告</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>联系我们</h4>
|
<h4>服务支持</h4>
|
||||||
<p>客服电话:400-000-0000</p>
|
<a href="help.html">帮助中心</a>
|
||||||
<p>邮箱:service@example.com</p>
|
<a href="submit-ticket.html">提交问题</a>
|
||||||
<p>地址:中国 · 成都</p>
|
<a href="about.html">关于平台</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-legal">
|
<div class="footer-legal">
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@
|
|||||||
<section class="site-info-hero">
|
<section class="site-info-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div>
|
<div>
|
||||||
<div class="site-info-kicker">Privacy Policy</div>
|
<div class="site-info-kicker">隐私政策</div>
|
||||||
<h1>隐私政策</h1>
|
<h1>隐私政策</h1>
|
||||||
<p>说明平台处理个人信息的规则、用户权利与联系渠道。</p>
|
<p>说明平台处理个人信息的规则、用户权利与联系渠道。</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,12 +27,12 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Permissions</div>
|
<div class="module-kicker">权限设置</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>管理员权限</h1>
|
<h1>管理员权限</h1>
|
||||||
<p>
|
<p>
|
||||||
当前 PC 端通过成员名册维护管理员、编辑和成员角色;具体可操作范围以服务端返回的当前角色为准。
|
在成员名册中维护管理员、编辑和成员角色;可执行的操作以当前账号权限为准。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,11 +27,11 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Album Detail</div>
|
<div class="module-kicker">相册详情</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1 data-album-detail-title>相册详情</h1>
|
<h1 data-album-detail-title>相册详情</h1>
|
||||||
<p>查看相册信息和照片;当前 PC 未返回文件访问 URL 时只展示业务元数据。</p>
|
<p>查看相册信息和照片;照片暂不可在线预览时,仍会保留名称和上传记录。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -118,6 +118,7 @@
|
|||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
<script src="public/js/md5.js"></script>
|
<script src="public/js/md5.js"></script>
|
||||||
<script src="public/js/upload-pages.js"></script>
|
<script src="public/js/upload-pages.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/album-pages.js"></script>
|
<script src="public/js/album-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Album Editor</div>
|
<div class="module-kicker">编辑相册</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1 data-album-editor-title>新建相册</h1>
|
<h1 data-album-editor-title>新建相册</h1>
|
||||||
@@ -94,6 +94,7 @@
|
|||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
<script src="public/js/md5.js"></script>
|
<script src="public/js/md5.js"></script>
|
||||||
<script src="public/js/upload-pages.js"></script>
|
<script src="public/js/upload-pages.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/album-pages.js"></script>
|
<script src="public/js/album-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+3
-2
@@ -29,11 +29,11 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Album</div>
|
<div class="module-kicker">家族相册</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>家族相册</h1>
|
<h1>家族相册</h1>
|
||||||
<p>查看和管理家谱相册;照片文件通过上传自动关联,不展示 OSS ID。</p>
|
<p>查看和管理家谱相册;照片文件由上传组件自动关联,无需填写内部编号。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,6 +69,7 @@
|
|||||||
<script src="utils/axios.js"></script>
|
<script src="utils/axios.js"></script>
|
||||||
<script src="utils/AxiosRequestUtil.js"></script>
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/album-pages.js"></script>
|
<script src="public/js/album-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Article Editor</div>
|
<div class="module-kicker">编辑谱文</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1 data-article-editor-title>新建谱文</h1>
|
<h1 data-article-editor-title>新建谱文</h1>
|
||||||
@@ -134,6 +134,7 @@
|
|||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
<script src="public/js/md5.js"></script>
|
<script src="public/js/md5.js"></script>
|
||||||
<script src="public/js/upload-pages.js"></script>
|
<script src="public/js/upload-pages.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/article-pages.js"></script>
|
<script src="public/js/article-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Article</div>
|
<div class="module-kicker">家族谱文</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>谱文管理</h1>
|
<h1>谱文管理</h1>
|
||||||
@@ -74,6 +74,7 @@
|
|||||||
<script src="utils/axios.js"></script>
|
<script src="utils/axios.js"></script>
|
||||||
<script src="utils/AxiosRequestUtil.js"></script>
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/article-pages.js"></script>
|
<script src="public/js/article-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container"><div class="module-kicker">Ceremony Detail</div><div class="module-title-row"><div><h1 data-ceremony-detail-title>活动详情</h1><p>活动信息、祭品记录与管理员受邀名单。</p></div></div></div>
|
<div class="container"><div class="module-kicker">活动详情</div><div class="module-title-row"><div><h1 data-ceremony-detail-title>活动详情</h1><p>活动信息、祭品记录与管理员受邀名单。</p></div></div></div>
|
||||||
</section>
|
</section>
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container module-layout">
|
<div class="container module-layout">
|
||||||
@@ -80,6 +80,7 @@
|
|||||||
<script src="utils/axios.js"></script>
|
<script src="utils/axios.js"></script>
|
||||||
<script src="utils/AxiosRequestUtil.js"></script>
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/ceremony-admin-pages.js"></script>
|
<script src="public/js/ceremony-admin-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Ceremony</div>
|
<div class="module-kicker">祭祀活动</div>
|
||||||
<div class="module-title-row"><div><h1>祭祀活动</h1><p>查看当前家谱的活动;具备内容维护权限时可创建、编辑和删除。</p></div></div>
|
<div class="module-title-row"><div><h1>祭祀活动</h1><p>查看当前家谱的活动;具备内容维护权限时可创建、编辑和删除。</p></div></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -48,6 +48,7 @@
|
|||||||
<script src="utils/axios.js"></script>
|
<script src="utils/axios.js"></script>
|
||||||
<script src="utils/AxiosRequestUtil.js"></script>
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/ceremony-admin-pages.js"></script>
|
<script src="public/js/ceremony-admin-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -33,13 +33,12 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Content Studio</div>
|
<div class="module-kicker">内容发布</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>内容发布</h1>
|
<h1>内容发布</h1>
|
||||||
<p>
|
<p>
|
||||||
承接 APP
|
集中管理谱文、视频、相册、功德录、贺礼邀请、成长记录、家族动态和备忘录。
|
||||||
家谱主页里的谱文、视频、相册、功德录、贺礼邀请、成长日志、动态和备忘录。
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<a class="btn primary magnetic" href="profile-article.html" data-feature-link="available" data-genealogy-context-link
|
<a class="btn primary magnetic" href="profile-article.html" data-feature-link="available" data-genealogy-context-link
|
||||||
@@ -100,18 +99,18 @@
|
|||||||
<a class="module-card tilt-card" href="profile-relative.html" data-feature-link="available" data-genealogy-context-link
|
<a class="module-card tilt-card" href="profile-relative.html" data-feature-link="available" data-genealogy-context-link
|
||||||
><span class="icon">亲</span>
|
><span class="icon">亲</span>
|
||||||
<h3>亲友往来(贺礼簿)</h3>
|
<h3>亲友往来(贺礼簿)</h3>
|
||||||
<p>记录亲友关系、往来事项和礼金,承接 APP 贺礼簿。</p></a
|
<p>记录亲友关系、往来事项和礼金。</p></a
|
||||||
>
|
>
|
||||||
<a class="module-card tilt-card" href="profile-memo.html" data-feature-link="available" data-genealogy-context-link
|
<a class="module-card tilt-card" href="profile-memo.html" data-feature-link="available" data-genealogy-context-link
|
||||||
><span class="icon">备</span>
|
><span class="icon">备</span>
|
||||||
<h3>备忘录(家族恩人)</h3>
|
<h3>备忘录(家族恩人)</h3>
|
||||||
<p>以标题、内容和提醒记录家族恩人等事项;当前 PC 不区分独立分类。</p></a
|
<p>以标题、内容和提醒记录家族恩人等事项,统一归入备忘录管理。</p></a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="module-panel">
|
<section class="module-panel">
|
||||||
<h2>使用说明</h2>
|
<h2>使用说明</h2>
|
||||||
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请先选择家谱,再进入对应模块查看和维护真实内容。</div>
|
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择上方功能,继续查看和维护当前家谱内容。</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,12 +27,12 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Create Family</div>
|
<div class="module-kicker">创建家谱</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>创建家谱</h1>
|
<h1>创建家谱</h1>
|
||||||
<p>
|
<p>
|
||||||
填写当前 PC 服务支持的家谱信息;标有 * 的字段为必填,创建后可进入家谱工作区继续维护。
|
填写家谱基本信息;标有 * 的字段为必填,创建后可进入家谱工作区继续维护。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
<option value="2">邀请码加入</option>
|
<option value="2">邀请码加入</option>
|
||||||
<option value="0">关闭加入</option>
|
<option value="0">关闭加入</option>
|
||||||
</select>
|
</select>
|
||||||
<p id="family-invite-mode-note" class="form-status">当前 PC 未提供邀请码生成、发送或管理接口;选择邀请模式后不能在本页面继续生成邀请码。</p>
|
<p id="family-invite-mode-note" class="form-status">创建完成后,可在家谱首页的“邀请家人”中生成一次性邀请链接。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="family-create-status" class="upload-status" data-genealogy-create-status role="status" aria-live="polite"></div>
|
<div id="family-create-status" class="upload-status" data-genealogy-create-status role="status" aria-live="polite"></div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<link rel="stylesheet" href="public/layui/css/layui.css" />
|
<link rel="stylesheet" href="public/layui/css/layui.css" />
|
||||||
<link rel="stylesheet" href="public/css/profile-module.css" />
|
<link rel="stylesheet" href="public/css/profile-module.css" />
|
||||||
</head>
|
</head>
|
||||||
<body class="page-profile-module" data-feature-status="pending" data-feature-message="资料提醒服务正在开发中,当前页面仅保留设计预览。">
|
<body class="page-profile-module" data-profile-reminder-page>
|
||||||
<header class="site-header">
|
<header class="site-header">
|
||||||
<div class="container nav">
|
<div class="container nav">
|
||||||
<a class="brand magnetic" href="profile.html"
|
<a class="brand magnetic" href="profile.html"
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Data Reminders</div>
|
<div class="module-kicker">资料提醒</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>资料提醒</h1>
|
<h1>资料提醒</h1>
|
||||||
@@ -49,9 +49,9 @@
|
|||||||
<div class="module-main">
|
<div class="module-main">
|
||||||
<section class="module-panel">
|
<section class="module-panel">
|
||||||
<h2>待完善成员</h2>
|
<h2>待完善成员</h2>
|
||||||
<div class="module-list">
|
<div class="module-list" data-profile-reminder-list>
|
||||||
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">
|
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
|
||||||
当前 PC 接口未提供资料缺项提醒列表,暂不能展示或处理成员提醒。
|
正在读取资料提醒…
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -63,7 +63,12 @@
|
|||||||
<script src="public/layui/layui.js"></script>
|
<script src="public/layui/layui.js"></script>
|
||||||
<script src="public/js/lay-config.js"></script>
|
<script src="public/js/lay-config.js"></script>
|
||||||
<script src="public/js/profile-common.js"></script>
|
<script src="public/js/profile-common.js"></script>
|
||||||
<script src="public/js/pending-pages.js"></script>
|
<script src="config.js"></script>
|
||||||
|
<script src="utils/StorageUtil.js"></script>
|
||||||
|
<script src="utils/axios.js"></script>
|
||||||
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
|
<script src="utils/ApiClient.js"></script>
|
||||||
|
<script src="public/js/profile-reminder-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+4
-3
@@ -33,7 +33,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Profile Data</div>
|
<div class="module-kicker">个人资料</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>个人资料</h1>
|
<h1>个人资料</h1>
|
||||||
@@ -207,7 +207,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="bottom-actions">
|
<div class="bottom-actions">
|
||||||
<button class="btn primary magnetic" type="button" disabled>暂不支持保存地区</button>
|
<button class="btn primary magnetic" type="button" disabled>暂不支持保存地区</button>
|
||||||
<span id="profile-region-contract-note" class="form-status">当前 YAML 的 ProfileUpdateBody 没有地区字段</span>
|
<span id="profile-region-contract-note" class="form-status">现居地区暂不支持保存到个人资料</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="profile-region-picker-status" class="form-status" data-region-picker-status role="status" aria-live="polite"></div>
|
<div id="profile-region-picker-status" class="form-status" data-region-picker-status role="status" aria-live="polite"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -230,7 +230,7 @@
|
|||||||
<h2>亲属关系</h2>
|
<h2>亲属关系</h2>
|
||||||
<div class="module-list">
|
<div class="module-list">
|
||||||
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">
|
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">
|
||||||
当前 PC 资料接口未提供个人亲属关系列表;请在选定家谱的成员管理中查看和维护。
|
个人资料页不单独维护亲属关系;请先选择家谱,再到成员管理或世系图中查看和维护。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bottom-actions"><a class="btn ghost magnetic" href="profile-family-admin.html" data-genealogy-context-link>前往成员管理</a></div>
|
<div class="bottom-actions"><a class="btn ghost magnetic" href="profile-family-admin.html" data-genealogy-context-link>前往成员管理</a></div>
|
||||||
@@ -252,6 +252,7 @@
|
|||||||
<script src="public/js/md5.js"></script>
|
<script src="public/js/md5.js"></script>
|
||||||
<script src="public/js/upload-pages.js"></script>
|
<script src="public/js/upload-pages.js"></script>
|
||||||
<script src="public/js/region-pages.js"></script>
|
<script src="public/js/region-pages.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/profile-pages.js"></script>
|
<script src="public/js/profile-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>重要证件 - 个人中心 - 代代相传</title><link rel="stylesheet" href="public/css/public.css" /><link rel="stylesheet" href="public/layui/css/layui.css" /><link rel="stylesheet" href="public/css/profile-module.css" /></head>
|
||||||
|
<body class="page-profile-module" data-person-document-page>
|
||||||
|
<header class="site-header"><div class="container nav"><a class="brand" href="profile.html"><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a><nav class="nav-links"><a href="profile-families.html">我的家谱</a><a class="active" href="profile-content.html" data-genealogy-context-link>家族文化</a></nav><div class="nav-actions"><a href="profile-family-home.html" data-genealogy-context-link>返回家谱主页</a></div></div></header>
|
||||||
|
<main><section class="module-hero"><div class="container"><div class="module-kicker">重要证件</div><div class="module-title-row"><div><h1>重要证件</h1><p>按人物维护证件档案、附件和访问密码。证件号码仅填写脱敏内容。</p></div></div></div></section>
|
||||||
|
<section class="section"><div class="container module-layout"><aside class="module-nav"><a href="profile-family-home.html" data-genealogy-context-link>家谱主页</a><a href="profile-content.html" data-genealogy-context-link>内容发布</a><a class="active" href="profile-documents.html" data-genealogy-context-link>重要证件</a></aside><div class="module-main">
|
||||||
|
<section class="module-panel"><h2>新增档案</h2><form class="editor-form" data-document-form novalidate><div class="editor-grid"><div class="editor-field"><label for="documentPerson">关联人物</label><select id="documentPerson" data-document-person><option>人物加载中</option></select></div><div class="editor-field"><label for="documentType">证件类型</label><select id="documentType" data-document-type><option>类型加载中</option></select></div><div class="editor-field"><label for="documentTitle">证件标题</label><input id="documentTitle" name="documentTitle" maxlength="100" /></div><div class="editor-field"><label for="maskedIdentifier">脱敏标识</label><input id="maskedIdentifier" name="maskedIdentifier" maxlength="100" placeholder="例如 310***********1234" /></div></div><div class="editor-field"><label for="documentDescription">说明</label><textarea id="documentDescription" name="description" maxlength="1000"></textarea></div><div class="editor-grid"><div class="editor-field"><label for="documentUsage">文件用途</label><select id="documentUsage" data-document-usage><option value="FRONT">正面</option><option value="BACK">反面</option><option value="ATTACHMENT">附件</option></select></div><div class="editor-field"><label for="documentFile">证件文件</label><input id="documentFile" type="file" accept="image/*,application/pdf" data-upload-target="#documentOssId" data-upload-status="#documentUploadStatus" /><input id="documentOssId" type="hidden" data-document-oss-id /><p id="documentUploadStatus" class="upload-status" role="status" aria-live="polite"></p></div></div><div class="bottom-actions"><button class="btn primary" type="submit">保存档案</button></div></form></section>
|
||||||
|
<section class="module-panel"><h2>档案列表</h2><div class="module-grid" data-document-list><div class="api-state api-state--loading" data-api-state="loading">正在读取重要证件…</div></div></section><div class="api-status" data-document-status role="status" aria-live="polite">正在初始化…</div>
|
||||||
|
</div></div></section>
|
||||||
|
</main>
|
||||||
|
<script src="public/js/jquery360.js"></script><script src="public/layui/layui.js"></script><script src="public/js/lay-config.js"></script><script src="public/js/profile-common.js"></script><script src="config.js"></script><script src="utils/StorageUtil.js"></script><script src="utils/axios.js"></script><script src="utils/AxiosRequestUtil.js"></script><script src="utils/ApiClient.js"></script><script src="public/js/upload-pages.js"></script><script src="public/js/person-document-pages.js"></script><script src="public/js/page-effects.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>推广收益 - 个人中心 - 代代相传</title><link rel="stylesheet" href="public/css/public.css" /><link rel="stylesheet" href="public/layui/css/layui.css" /><link rel="stylesheet" href="public/css/profile-module.css" /></head>
|
||||||
|
<body class="page-profile-module" data-earning-page>
|
||||||
|
<header class="site-header"><div class="container nav"><a class="brand" href="profile.html"><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a><nav class="nav-links"><a href="profile.html">个人中心</a><a class="active" href="profile-services.html">帮助与服务</a></nav><div class="nav-actions"><a href="profile-services.html">返回服务</a></div></div></header>
|
||||||
|
<main><section class="module-hero"><div class="container"><div class="module-kicker">推广收益</div><div class="module-title-row"><div><h1>推广收益</h1><p>查看可用收益、不可变流水和提现处理状态。</p></div></div></div></section><section class="section"><div class="container module-layout"><aside class="module-nav"><a href="profile-services.html">帮助与服务</a><a class="active" href="profile-earnings.html">推广收益</a><a href="profile-share.html">应用分享</a></aside><div class="module-main">
|
||||||
|
<section class="module-panel"><h2>收益概览</h2><div data-earning-summary><div class="api-state api-state--loading" data-api-state="loading">正在读取收益…</div></div></section>
|
||||||
|
<section class="module-panel"><h2>申请提现</h2><form class="editor-form" data-withdrawal-form novalidate><div class="editor-grid"><div class="editor-field"><label for="withdrawalAmount">提现金额</label><input id="withdrawalAmount" name="amount" inputmode="decimal" placeholder="0.00" /></div><div class="editor-field"><label for="payoutAccountName">收款人姓名</label><input id="payoutAccountName" name="payoutAccountName" maxlength="64" /></div></div><div class="editor-field"><label for="payoutQrFile">微信收款码</label><input id="payoutQrFile" class="upload-input" type="file" accept="image/*" data-upload-target="#payoutQrOssId" data-upload-status="#payoutQrStatus" /><label class="upload-control" for="payoutQrFile">选择收款码图片</label><input id="payoutQrOssId" name="payoutQrOssId" type="hidden" /><p id="payoutQrStatus" class="upload-status" role="status" aria-live="polite"></p></div><div class="bottom-actions"><button class="btn primary" type="submit" data-withdrawal-submit>提交提现申请</button></div></form></section>
|
||||||
|
<section class="module-panel"><h2>提现记录</h2><div class="module-list" data-earning-withdrawals><div class="api-state api-state--loading" data-api-state="loading">正在读取提现记录…</div></div></section><section class="module-panel"><h2>收益流水</h2><div class="module-list" data-earning-ledger><div class="api-state api-state--loading" data-api-state="loading">正在读取收益流水…</div></div></section><div class="api-status" data-earning-status role="status" aria-live="polite">正在初始化…</div>
|
||||||
|
</div></div></section></main>
|
||||||
|
<script src="public/js/jquery360.js"></script><script src="public/layui/layui.js"></script><script src="public/js/lay-config.js"></script><script src="public/js/profile-common.js"></script><script src="config.js"></script><script src="utils/StorageUtil.js"></script><script src="utils/axios.js"></script><script src="utils/AxiosRequestUtil.js"></script><script src="utils/ApiClient.js"></script><script src="public/js/upload-pages.js"></script><script src="public/js/earning-pages.js"></script><script src="public/js/page-effects.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+10
-3
@@ -59,7 +59,7 @@
|
|||||||
<div class="module-main">
|
<div class="module-main">
|
||||||
<section class="module-panel">
|
<section class="module-panel">
|
||||||
<h2>家谱入口</h2>
|
<h2>家谱入口</h2>
|
||||||
<p>对应 PC 的“我的家谱”入口;进入具体业务前必须取得真实家谱编号。</p>
|
<p>先选择要处理的家谱,再进入主页、成员、世系和内容管理。</p>
|
||||||
<div class="api-status" data-genealogy-entry-status role="status" aria-live="polite">正在读取家谱额度…</div>
|
<div class="api-status" data-genealogy-entry-status role="status" aria-live="polite">正在读取家谱额度…</div>
|
||||||
<div class="module-list" data-genealogy-list="mine">
|
<div class="module-list" data-genealogy-list="mine">
|
||||||
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
|
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
|
||||||
@@ -82,10 +82,17 @@
|
|||||||
<h3>加入家谱</h3>
|
<h3>加入家谱</h3>
|
||||||
<p>搜索并选择可加入的家谱,提交关系和申请说明等待审核。</p></a
|
<p>搜索并选择可加入的家谱,提交关系和申请说明等待审核。</p></a
|
||||||
>
|
>
|
||||||
<div class="module-card is-unavailable" aria-disabled="true">
|
<div class="module-card">
|
||||||
<span class="icon">序</span>
|
<span class="icon">序</span>
|
||||||
<h3>家谱排序</h3>
|
<h3>家谱排序</h3>
|
||||||
<p>当前 PC 家谱契约未提供自定义排序的读取或保存接口,暂不能调整家谱显示顺序。</p>
|
<p>选择家谱后上移或下移,保存个人的家谱展示顺序。</p>
|
||||||
|
<label for="genealogySortSelect">选择家谱</label>
|
||||||
|
<select id="genealogySortSelect" data-genealogy-sort-select disabled><option value="">家谱加载中</option></select>
|
||||||
|
<div class="row-actions">
|
||||||
|
<button class="btn outline" type="button" data-genealogy-sort-up disabled>上移</button>
|
||||||
|
<button class="btn outline" type="button" data-genealogy-sort-down disabled>下移</button>
|
||||||
|
<button class="btn primary" type="button" data-genealogy-sort-save disabled>保存顺序</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Family Admin</div>
|
<div class="module-kicker">家族管理</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div><h1 data-member-title>家族管理</h1><p>查看家谱成员;具备管理权限时可调整成员信息、角色和世系人物绑定。</p></div>
|
<div><h1 data-member-title>家族管理</h1><p>查看家谱成员;具备管理权限时可调整成员信息、角色和世系人物绑定。</p></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
<div class="editor-field"><label for="memberAdminRole">成员角色</label><select id="memberAdminRole" name="roleType" data-member-role-options></select></div>
|
<div class="editor-field"><label for="memberAdminRole">成员角色</label><select id="memberAdminRole" name="roleType" data-member-role-options></select></div>
|
||||||
<div class="editor-field"><label for="memberAdminLineage">绑定世系人物</label><select id="memberAdminLineage" name="lineagePersonId" data-member-lineage-options><option value="">不修改世系人物绑定</option></select></div>
|
<div class="editor-field"><label for="memberAdminLineage">绑定世系人物</label><select id="memberAdminLineage" name="lineagePersonId" data-member-lineage-options><option value="">不修改世系人物绑定</option></select></div>
|
||||||
</div>
|
</div>
|
||||||
<p class="upload-status">当前 PC 更新接口未声明空值清除语义;成员名称、关系和世系人物绑定留空均表示不修改,不会发送空值。</p>
|
<p class="upload-status">留空表示保持原信息不变;只填写本次需要修改的内容。</p>
|
||||||
<div class="upload-status" role="status" aria-live="polite" data-member-form-status></div>
|
<div class="upload-status" role="status" aria-live="polite" data-member-form-status></div>
|
||||||
<div class="bottom-actions"><button class="btn primary magnetic" type="submit" data-member-submit>保存成员</button><button class="btn ghost" type="reset">取消</button></div>
|
<div class="bottom-actions"><button class="btn primary magnetic" type="submit" data-member-submit>保存成员</button><button class="btn ghost" type="reset">取消</button></div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
+15
-18
@@ -75,12 +75,11 @@
|
|||||||
<h3>世系人物</h3>
|
<h3>世系人物</h3>
|
||||||
<p><strong data-family-home-person-count>--</strong> 人</p>
|
<p><strong data-family-home-person-count>--</strong> 人</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="module-card is-unavailable" aria-disabled="true">
|
<a class="module-card tilt-card" href="profile-invite.html" data-family-home-management data-genealogy-context-link hidden>
|
||||||
<span class="icon">邀</span>
|
<span class="icon">邀</span>
|
||||||
<h3>邀请家人</h3>
|
<h3>邀请家人</h3>
|
||||||
<p>PC 暂未开放邀请,请等待后端提供正式接口。</p>
|
<p>生成一次性邀请链接,指定人物并查看邀请状态。</p>
|
||||||
<span class="pill">暂不可操作</span>
|
</a>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="api-status" data-family-home-status role="status" aria-live="polite">正在读取家谱概览</div>
|
<div class="api-status" data-family-home-status role="status" aria-live="polite">正在读取家谱概览</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -114,7 +113,7 @@
|
|||||||
</section>
|
</section>
|
||||||
<section class="module-panel">
|
<section class="module-panel">
|
||||||
<h2>家族记录与协作</h2>
|
<h2>家族记录与协作</h2>
|
||||||
<p>以列表、详情与编辑分工承接 APP 的动态、日志、人情簿、备忘和活动能力。</p>
|
<p>按列表、详情和编辑页面管理动态、成长记录、亲友往来、备忘和活动。</p>
|
||||||
<div class="module-grid">
|
<div class="module-grid">
|
||||||
<a class="module-card tilt-card" href="profile-feed.html" data-feature-link="available" data-genealogy-context-link
|
<a class="module-card tilt-card" href="profile-feed.html" data-feature-link="available" data-genealogy-context-link
|
||||||
><span class="icon">动</span>
|
><span class="icon">动</span>
|
||||||
@@ -127,11 +126,11 @@
|
|||||||
><a class="module-card tilt-card" href="profile-relative.html" data-feature-link="available" data-genealogy-context-link
|
><a class="module-card tilt-card" href="profile-relative.html" data-feature-link="available" data-genealogy-context-link
|
||||||
><span class="icon">亲</span>
|
><span class="icon">亲</span>
|
||||||
<h3>亲友往来(贺礼簿)</h3>
|
<h3>亲友往来(贺礼簿)</h3>
|
||||||
<p>记录亲友关系、往来事项和礼金,承接 APP 贺礼簿。</p></a
|
<p>记录亲友关系、往来事项和礼金。</p></a
|
||||||
><a class="module-card tilt-card" href="profile-memo.html" data-feature-link="available" data-genealogy-context-link
|
><a class="module-card tilt-card" href="profile-memo.html" data-feature-link="available" data-genealogy-context-link
|
||||||
><span class="icon">备</span>
|
><span class="icon">备</span>
|
||||||
<h3>备忘录(家族恩人)</h3>
|
<h3>备忘录(家族恩人)</h3>
|
||||||
<p>以标题、内容和提醒记录家族恩人等事项;当前 PC 不区分独立分类。</p></a
|
<p>以标题、内容和提醒记录家族恩人等事项,统一归入备忘录管理。</p></a
|
||||||
><a class="module-card tilt-card" href="profile-merit.html" data-feature-link="available" data-genealogy-context-link
|
><a class="module-card tilt-card" href="profile-merit.html" data-feature-link="available" data-genealogy-context-link
|
||||||
><span class="icon">德</span>
|
><span class="icon">德</span>
|
||||||
<h3>功德录</h3>
|
<h3>功德录</h3>
|
||||||
@@ -145,12 +144,11 @@
|
|||||||
<h3>贺礼邀请</h3>
|
<h3>贺礼邀请</h3>
|
||||||
<p>查看和响应当前家谱的活动邀请。</p></a
|
<p>查看和响应当前家谱的活动邀请。</p></a
|
||||||
>
|
>
|
||||||
<div class="module-card is-unavailable" aria-disabled="true">
|
<a class="module-card tilt-card" href="profile-documents.html" data-feature-link="available" data-genealogy-context-link>
|
||||||
<span class="icon">证</span>
|
<span class="icon">证</span>
|
||||||
<h3>重要证件</h3>
|
<h3>重要证件</h3>
|
||||||
<p>当前 PC 接口未提供家谱证件的列表、上传或维护能力,暂不能查看或保存。</p>
|
<p>按人物维护证件档案、附件和密码保护。</p>
|
||||||
<span class="pill">暂不可操作</span>
|
</a>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="module-panel">
|
<section class="module-panel">
|
||||||
@@ -169,17 +167,15 @@
|
|||||||
><span class="icon">管</span>
|
><span class="icon">管</span>
|
||||||
<h3>家族管理</h3>
|
<h3>家族管理</h3>
|
||||||
<p>管理成员、入谱审核和谱主转移。</p></a
|
<p>管理成员、入谱审核和谱主转移。</p></a
|
||||||
><a class="module-card tilt-card" href="profile-services.html" data-feature-link="available" data-genealogy-context-link
|
><a class="module-card tilt-card" href="profile-services.html#membership" data-feature-link="available" data-genealogy-context-link
|
||||||
><span class="icon">会</span>
|
><span class="icon">会</span>
|
||||||
<h3>会员与订单</h3>
|
<h3>会员与订单</h3>
|
||||||
<p>关联当前家谱创建会员订单,并查看已有订单。</p></a
|
<p>关联当前家谱创建会员订单,并查看已有订单。</p></a
|
||||||
><div class="module-card is-unavailable" aria-disabled="true">
|
><a class="module-card tilt-card" href="profile-family-settings.html" data-feature-link="available" data-family-home-management data-genealogy-context-link hidden>
|
||||||
<span class="icon">删</span>
|
<span class="icon">删</span>
|
||||||
<h3>删除家谱</h3>
|
<h3>家谱维护</h3>
|
||||||
<p>当前 PC 家谱契约未提供删除接口,暂不能查看或执行删除操作。</p>
|
<p>归档、恢复或在身份校验后永久删除家谱。</p>
|
||||||
<span class="pill">暂不可操作</span>
|
</a>
|
||||||
</div>
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@@ -195,6 +191,7 @@
|
|||||||
<script src="utils/axios.js"></script>
|
<script src="utils/axios.js"></script>
|
||||||
<script src="utils/AxiosRequestUtil.js"></script>
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/lineage-pages.js"></script>
|
<script src="public/js/lineage-pages.js"></script>
|
||||||
<script src="public/js/family-home-pages.js"></script>
|
<script src="public/js/family-home-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>家谱维护 - 个人中心 - 代代相传</title>
|
||||||
|
<link rel="stylesheet" href="public/css/public.css" />
|
||||||
|
<link rel="stylesheet" href="public/layui/css/layui.css" />
|
||||||
|
<link rel="stylesheet" href="public/css/profile-module.css" />
|
||||||
|
</head>
|
||||||
|
<body class="page-profile-module" data-genealogy-lifecycle-page>
|
||||||
|
<header class="site-header"><div class="container nav"><a class="brand" href="profile.html"><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a><nav class="nav-links"><a href="profile-families.html">我的家谱</a><a class="active" href="profile-family-admin.html" data-genealogy-context-link>家族管理</a></nav><div class="nav-actions"><a href="profile-family-home.html" data-genealogy-context-link>返回家谱主页</a></div></div></header>
|
||||||
|
<main>
|
||||||
|
<section class="module-hero"><div class="container"><div class="module-kicker">家谱维护</div><div class="module-title-row"><div><h1>家谱维护</h1><p>归档、恢复或永久删除当前家谱。永久删除前会再次校验谱主身份和手机号。</p></div></div></div></section>
|
||||||
|
<section class="section"><div class="container module-layout"><aside class="module-nav"><a href="profile-family-home.html" data-genealogy-context-link>家谱主页</a><a href="profile-family-admin.html" data-genealogy-context-link>家族管理</a><a class="active" href="profile-family-settings.html" data-genealogy-context-link>家谱维护</a></aside><div class="module-main">
|
||||||
|
<section class="module-panel"><h2>归档与恢复</h2><p>归档用于暂时停止维护,不会永久删除数据。</p><div class="bottom-actions"><button class="btn outline" type="button" data-genealogy-archive>归档家谱</button><button class="btn outline" type="button" data-genealogy-restore>恢复家谱</button></div></section>
|
||||||
|
<section class="module-panel"><h2>永久删除</h2><p>此操作不可恢复。仅谱主且满足服务端删除条件时可执行。</p><form class="editor-form" data-genealogy-delete-form novalidate><div class="editor-field"><label for="deleteGenealogyName">家谱全名</label><input id="deleteGenealogyName" data-genealogy-delete-name autocomplete="off" /></div><div class="editor-field"><label for="deleteSmsCode">短信验证码</label><div class="bottom-actions"><input id="deleteSmsCode" data-genealogy-delete-sms inputmode="numeric" autocomplete="one-time-code" /><button class="btn outline" type="button" data-genealogy-delete-code>发送验证码</button></div></div><div class="bottom-actions"><button class="btn primary" type="submit" data-genealogy-delete-submit disabled>永久删除家谱</button></div></form></section>
|
||||||
|
<div class="api-status" data-lifecycle-status role="status" aria-live="polite">正在读取家谱维护能力…</div>
|
||||||
|
</div></div></section>
|
||||||
|
</main>
|
||||||
|
<script src="public/js/jquery360.js"></script><script src="public/layui/layui.js"></script><script src="public/js/lay-config.js"></script><script src="public/js/profile-common.js"></script><script src="config.js"></script><script src="utils/StorageUtil.js"></script><script src="utils/axios.js"></script><script src="utils/AxiosRequestUtil.js"></script><script src="utils/ApiClient.js"></script><script src="public/js/genealogy-lifecycle-pages.js"></script><script src="public/js/page-effects.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Feed Detail</div>
|
<div class="module-kicker">动态详情</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>动态详情</h1>
|
<h1>动态详情</h1>
|
||||||
@@ -88,6 +88,7 @@
|
|||||||
<script src="utils/axios.js"></script>
|
<script src="utils/axios.js"></script>
|
||||||
<script src="utils/AxiosRequestUtil.js"></script>
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/feed-pages.js"></script>
|
<script src="public/js/feed-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -28,10 +28,10 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Feed Editor</div>
|
<div class="module-kicker" data-feed-editor-kicker>发布动态</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>发布动态</h1>
|
<h1 data-feed-editor-title>发布动态</h1>
|
||||||
<p>面向家族圈发布文字和图片动态,与家人交流近况。</p>
|
<p>面向家族圈发布文字和图片动态,与家人交流近况。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -106,6 +106,7 @@
|
|||||||
<script src="utils/AxiosRequestUtil.js"></script>
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
<script src="public/js/upload-pages.js"></script>
|
<script src="public/js/upload-pages.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/feed-pages.js"></script>
|
<script src="public/js/feed-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+2
-1
@@ -30,7 +30,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Family Feed</div>
|
<div class="module-kicker">家族圈</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>家族圈</h1>
|
<h1>家族圈</h1>
|
||||||
@@ -97,6 +97,7 @@
|
|||||||
<script src="utils/axios.js"></script>
|
<script src="utils/axios.js"></script>
|
||||||
<script src="utils/AxiosRequestUtil.js"></script>
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/feed-pages.js"></script>
|
<script src="public/js/feed-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Feedback</div>
|
<div class="module-kicker">意见反馈</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>意见反馈</h1>
|
<h1>意见反馈</h1>
|
||||||
@@ -50,11 +50,8 @@
|
|||||||
<div class="editor-two">
|
<div class="editor-two">
|
||||||
<div class="editor-field">
|
<div class="editor-field">
|
||||||
<label for="feedbackType">反馈类型</label>
|
<label for="feedbackType">反馈类型</label>
|
||||||
<select id="feedbackType" name="feedbackType">
|
<select id="feedbackType" name="feedbackType" data-feedback-type-options>
|
||||||
<option value="advice">意见建议</option>
|
<option value="">反馈类型加载中…</option>
|
||||||
<option value="bug">问题反馈</option>
|
|
||||||
<option value="complaint">投诉反馈</option>
|
|
||||||
<option value="other">其他反馈</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="editor-field">
|
<div class="editor-field">
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Generation</div>
|
<div class="module-kicker">字辈管理</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>字辈谱</h1>
|
<h1>字辈谱</h1>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Ceremony Editor</div>
|
<div class="module-kicker">活动编辑</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1 data-ceremony-editor-title>创建祭祀活动</h1>
|
<h1 data-ceremony-editor-title>创建祭祀活动</h1>
|
||||||
@@ -139,6 +139,7 @@
|
|||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
<script src="public/js/md5.js"></script>
|
<script src="public/js/md5.js"></script>
|
||||||
<script src="public/js/upload-pages.js"></script>
|
<script src="public/js/upload-pages.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/ceremony-admin-pages.js"></script>
|
<script src="public/js/ceremony-admin-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+3
-3
@@ -26,7 +26,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Invitation</div>
|
<div class="module-kicker">贺礼邀请</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>贺礼邀请</h1>
|
<h1>贺礼邀请</h1>
|
||||||
@@ -57,8 +57,8 @@
|
|||||||
</section>
|
</section>
|
||||||
<section class="module-panel">
|
<section class="module-panel">
|
||||||
<div class="module-panel-head">
|
<div class="module-panel-head">
|
||||||
<h2>活动入口</h2>
|
<h2>关联活动</h2>
|
||||||
<a class="pill" href="profile-ceremony.html" data-genealogy-context-link>查看祭祀活动</a>
|
<a class="pill" href="profile-ceremony.html" data-genealogy-context-link>管理关联活动</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="module-panel">
|
<section class="module-panel">
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Growth Record</div>
|
<div class="module-kicker">编辑成长记录</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1 data-growth-editor-title>新建成长记录</h1>
|
<h1 data-growth-editor-title>新建成长记录</h1>
|
||||||
@@ -138,6 +138,7 @@
|
|||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
<script src="public/js/md5.js"></script>
|
<script src="public/js/md5.js"></script>
|
||||||
<script src="public/js/upload-pages.js"></script>
|
<script src="public/js/upload-pages.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/growth-pages.js"></script>
|
<script src="public/js/growth-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+3
-2
@@ -29,7 +29,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Growth Records</div>
|
<div class="module-kicker">成长记录</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>成长记录</h1>
|
<h1>成长记录</h1>
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="module-panel">
|
<section class="module-panel">
|
||||||
<h2>记录详情</h2>
|
<h2 tabindex="-1" data-growth-detail-heading>记录详情</h2>
|
||||||
<div data-growth-detail aria-live="polite">
|
<div data-growth-detail aria-live="polite">
|
||||||
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一条成长记录查看详情。</div>
|
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一条成长记录查看详情。</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,6 +72,7 @@
|
|||||||
<script src="utils/axios.js"></script>
|
<script src="utils/axios.js"></script>
|
||||||
<script src="utils/AxiosRequestUtil.js"></script>
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/growth-pages.js"></script>
|
<script src="public/js/growth-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+16
-15
@@ -8,7 +8,7 @@
|
|||||||
<link rel="stylesheet" href="public/layui/css/layui.css" />
|
<link rel="stylesheet" href="public/layui/css/layui.css" />
|
||||||
<link rel="stylesheet" href="public/css/profile-module.css" />
|
<link rel="stylesheet" href="public/css/profile-module.css" />
|
||||||
</head>
|
</head>
|
||||||
<body class="page-profile-module" data-family-invite-page data-feature-status="pending" data-feature-message="家谱成员邀请服务正在开发中,当前页面仅保留设计预览。">
|
<body class="page-profile-module" data-family-invite-page>
|
||||||
<header class="site-header">
|
<header class="site-header">
|
||||||
<div class="container nav">
|
<div class="container nav">
|
||||||
<a class="brand magnetic" href="profile.html"
|
<a class="brand magnetic" href="profile.html"
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Invite</div>
|
<div class="module-kicker">邀请家人</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>邀请家人</h1>
|
<h1>邀请家人</h1>
|
||||||
@@ -45,19 +45,20 @@
|
|||||||
</aside>
|
</aside>
|
||||||
<div class="module-main">
|
<div class="module-main">
|
||||||
<section class="module-panel">
|
<section class="module-panel">
|
||||||
<h2>邀请方式</h2>
|
<h2>生成邀请</h2>
|
||||||
<div class="module-grid two">
|
<form class="editor-form" data-invite-form novalidate>
|
||||||
<div class="module-card">
|
<div class="editor-field"><label for="invitePerson">关联人物(可选)</label><select id="invitePerson" data-invite-person><option value="">人物加载中</option></select></div>
|
||||||
<span class="icon">码</span>
|
<div class="bottom-actions"><button class="btn primary" type="submit">生成邀请链接</button><div class="form-status" data-invite-status role="status" aria-live="polite">正在读取邀请服务…</div></div>
|
||||||
<h3>邀请码</h3>
|
</form>
|
||||||
<p>当前 PC 接口未提供邀请码,暂不能生成或展示。</p>
|
<div class="editor-form" data-invite-result hidden>
|
||||||
</div>
|
<div class="editor-field"><label for="inviteLink">一次性邀请链接</label><input id="inviteLink" data-invite-link readonly /></div>
|
||||||
<div class="module-card">
|
<button class="btn outline" type="button" data-invite-copy>复制链接</button>
|
||||||
<span class="icon">链</span>
|
|
||||||
<h3>邀请链接</h3>
|
|
||||||
<p>当前 PC 接口未提供邀请链接,暂不能生成或复制。</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="module-card" data-invite-redeem hidden><h3>接受家谱邀请</h3><p>邀请令牌:<span data-invite-token></span></p><button class="btn primary" type="button" data-invite-redeem-submit>确认加入</button></div>
|
||||||
|
</section>
|
||||||
|
<section class="module-panel">
|
||||||
|
<h2>我发出的邀请</h2>
|
||||||
|
<div class="module-list" data-invite-list><div class="api-state api-state--loading" data-api-state="loading">正在读取邀请记录…</div></div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,7 +73,7 @@
|
|||||||
<script src="utils/axios.js"></script>
|
<script src="utils/axios.js"></script>
|
||||||
<script src="utils/AxiosRequestUtil.js"></script>
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
<script src="public/js/pending-pages.js"></script>
|
<script src="public/js/family-invite-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Join Family</div>
|
<div class="module-kicker">加入家谱</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>加入家谱</h1>
|
<h1>加入家谱</h1>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Review</div>
|
<div class="module-kicker">入谱审核</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>入谱审核</h1>
|
<h1>入谱审核</h1>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container"><div class="module-kicker">Memo Editor</div><div class="module-title-row"><div><h1 data-memo-editor-title>新建备忘录</h1><p>附件由上传组件自动生成内部编号,页面不允许手工填写。</p></div></div></div>
|
<div class="container"><div class="module-kicker">编辑备忘录</div><div class="module-title-row"><div><h1 data-memo-editor-title>新建备忘录</h1><p>附件由上传组件自动生成内部编号,页面不允许手工填写。</p></div></div></div>
|
||||||
</section>
|
</section>
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container module-layout">
|
<div class="container module-layout">
|
||||||
@@ -58,6 +58,7 @@
|
|||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
<script src="public/js/md5.js"></script>
|
<script src="public/js/md5.js"></script>
|
||||||
<script src="public/js/upload-pages.js"></script>
|
<script src="public/js/upload-pages.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/memo-pages.js"></script>
|
<script src="public/js/memo-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+3
-2
@@ -19,7 +19,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Memo</div>
|
<div class="module-kicker">备忘录</div>
|
||||||
<div class="module-title-row"><div><h1>备忘录</h1><p>记录家族事务、纪念事项、提醒时间和完成状态。</p></div></div>
|
<div class="module-title-row"><div><h1>备忘录</h1><p>记录家族事务、纪念事项、提醒时间和完成状态。</p></div></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="module-panel">
|
<section class="module-panel">
|
||||||
<h2>备忘详情</h2>
|
<h2 tabindex="-1" data-memo-detail-heading>备忘详情</h2>
|
||||||
<div data-memo-detail aria-live="polite">
|
<div data-memo-detail aria-live="polite">
|
||||||
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一条备忘录查看详情。</div>
|
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一条备忘录查看详情。</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -52,6 +52,7 @@
|
|||||||
<script src="utils/axios.js"></script>
|
<script src="utils/axios.js"></script>
|
||||||
<script src="utils/AxiosRequestUtil.js"></script>
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/memo-pages.js"></script>
|
<script src="public/js/memo-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -29,11 +29,11 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Merit Editor</div>
|
<div class="module-kicker">编辑功德记录</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1 data-merit-editor-title>新增功德记录</h1>
|
<h1 data-merit-editor-title>新增功德记录</h1>
|
||||||
<p>功德类型严格使用 PC 契约枚举,金额按后端未限定的数值规则提交。</p>
|
<p>选择功德类型并填写金额和说明;标有 * 的项目为必填。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -113,6 +113,7 @@
|
|||||||
<script src="utils/axios.js"></script>
|
<script src="utils/axios.js"></script>
|
||||||
<script src="utils/AxiosRequestUtil.js"></script>
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/merit-pages.js"></script>
|
<script src="public/js/merit-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+3
-2
@@ -28,7 +28,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Merit</div>
|
<div class="module-kicker">功德录</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>功德录</h1>
|
<h1>功德录</h1>
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="module-panel">
|
<section class="module-panel">
|
||||||
<h2>记录详情</h2>
|
<h2 tabindex="-1" data-merit-detail-heading>记录详情</h2>
|
||||||
<div data-merit-detail aria-live="polite">
|
<div data-merit-detail aria-live="polite">
|
||||||
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一条功德记录查看详情。</div>
|
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一条功德记录查看详情。</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,6 +72,7 @@
|
|||||||
<script src="utils/axios.js"></script>
|
<script src="utils/axios.js"></script>
|
||||||
<script src="utils/AxiosRequestUtil.js"></script>
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/merit-pages.js"></script>
|
<script src="public/js/merit-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Messages</div>
|
<div class="module-kicker">消息中心</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>消息中心</h1>
|
<h1>消息中心</h1>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Relative Record</div>
|
<div class="module-kicker">编辑亲友往来</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1 data-relative-editor-title>新建亲友记录</h1>
|
<h1 data-relative-editor-title>新建亲友记录</h1>
|
||||||
@@ -126,6 +126,7 @@
|
|||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
<script src="public/js/md5.js"></script>
|
<script src="public/js/md5.js"></script>
|
||||||
<script src="public/js/upload-pages.js"></script>
|
<script src="public/js/upload-pages.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/relative-pages.js"></script>
|
<script src="public/js/relative-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Relative Records</div>
|
<div class="module-kicker">亲友往来</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>亲友往来</h1>
|
<h1>亲友往来</h1>
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="module-panel">
|
<section class="module-panel">
|
||||||
<h2>记录详情</h2>
|
<h2 tabindex="-1" data-relative-detail-heading>记录详情</h2>
|
||||||
<div data-relative-detail aria-live="polite">
|
<div data-relative-detail aria-live="polite">
|
||||||
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一条亲友记录查看详情。</div>
|
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一条亲友记录查看详情。</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -71,6 +71,7 @@
|
|||||||
<script src="utils/axios.js"></script>
|
<script src="utils/axios.js"></script>
|
||||||
<script src="utils/AxiosRequestUtil.js"></script>
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/relative-pages.js"></script>
|
<script src="public/js/relative-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -20,14 +20,14 @@
|
|||||||
</nav>
|
</nav>
|
||||||
<div class="nav-actions">
|
<div class="nav-actions">
|
||||||
<a href="profile-services.html">返回服务中心</a
|
<a href="profile-services.html">返回服务中心</a
|
||||||
><a class="btn primary magnetic" href="#" data-security-logout>退出登录</a>
|
><button class="btn primary magnetic" type="button" data-security-logout>退出登录</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Security</div>
|
<div class="module-kicker">安全设置</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>安全设置</h1>
|
<h1>安全设置</h1>
|
||||||
@@ -135,8 +135,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<input name="validToken" type="hidden" />
|
<input name="validToken" type="hidden" />
|
||||||
<div class="bottom-actions">
|
<div class="bottom-actions">
|
||||||
<button class="btn primary magnetic" type="submit" data-security-submit>提交注销</button>
|
<button class="btn danger magnetic" type="submit" data-security-submit>提交注销</button>
|
||||||
<button class="btn ghost magnetic" type="button" data-security-logout>退出登录</button>
|
|
||||||
<div class="form-status" data-security-status role="status" aria-live="polite"></div>
|
<div class="form-status" data-security-status role="status" aria-live="polite"></div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
+15
-13
@@ -34,14 +34,11 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Services</div>
|
<div class="module-kicker">帮助与服务</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>帮助与服务</h1>
|
<h1>帮助与服务</h1>
|
||||||
<p>
|
<p>集中处理推广收益、意见反馈、账号安全、帮助说明和会员服务。</p>
|
||||||
整合 APP
|
|
||||||
“我的”页中的应用推广、意见反馈、安全设置、帮助中心和应用分享。
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<a class="btn primary magnetic" href="profile-feedback.html" data-feature-link="available"
|
<a class="btn primary magnetic" href="profile-feedback.html" data-feature-link="available"
|
||||||
>提交反馈</a
|
>提交反馈</a
|
||||||
@@ -63,16 +60,15 @@
|
|||||||
<section class="module-panel">
|
<section class="module-panel">
|
||||||
<h2>常用服务</h2>
|
<h2>常用服务</h2>
|
||||||
<div class="module-grid">
|
<div class="module-grid">
|
||||||
<div class="module-card is-unavailable" aria-disabled="true">
|
<a class="module-card tilt-card" href="profile-earnings.html" data-feature-link="available">
|
||||||
<span class="icon">享</span>
|
<span class="icon">享</span>
|
||||||
<h3>应用分享与收益</h3>
|
<h3>应用分享与收益</h3>
|
||||||
<p>分享奖励、推广收益与提现服务正在开发中,当前不提供不可用的跳转入口。</p>
|
<p>查看推广收益、可用余额、流水和提现记录。</p>
|
||||||
<span class="pill">即将开放</span>
|
</a>
|
||||||
</div>
|
|
||||||
<div class="module-card is-unavailable" aria-disabled="true">
|
<div class="module-card is-unavailable" aria-disabled="true">
|
||||||
<span class="icon">荐</span>
|
<span class="icon">荐</span>
|
||||||
<h3>个性化推荐</h3>
|
<h3>个性化推荐</h3>
|
||||||
<p>当前 PC 接口未提供推荐偏好的读取或保存,暂不能设置推荐开关。</p>
|
<p>推荐设置正在完善,目前不会保存开关状态。</p>
|
||||||
<span class="pill">暂不可设置</span>
|
<span class="pill">暂不可设置</span>
|
||||||
</div>
|
</div>
|
||||||
<a class="module-card tilt-card" href="profile-feedback.html" data-feature-link="available"
|
<a class="module-card tilt-card" href="profile-feedback.html" data-feature-link="available"
|
||||||
@@ -104,7 +100,7 @@
|
|||||||
</section>
|
</section>
|
||||||
<section class="module-panel">
|
<section class="module-panel">
|
||||||
<h2>平台设置与协议</h2>
|
<h2>平台设置与协议</h2>
|
||||||
<p>集中查看 APP“设置”中的平台说明、联系方式及协议入口;正式生效内容以平台和法务发布版本为准。</p>
|
<p>集中查看平台说明、服务支持和协议入口;正式内容以平台发布版本为准。</p>
|
||||||
<div class="module-grid">
|
<div class="module-grid">
|
||||||
<a class="module-card tilt-card" href="about.html"
|
<a class="module-card tilt-card" href="about.html"
|
||||||
><span class="icon">关</span>
|
><span class="icon">关</span>
|
||||||
@@ -128,9 +124,9 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="module-panel">
|
<section class="module-panel" id="membership" tabindex="-1">
|
||||||
<h2>会员服务</h2>
|
<h2>会员服务</h2>
|
||||||
<p>选择适合的会员套餐并创建订单。当前 PC 暂未开放在线支付。</p>
|
<p>选择适合的会员套餐并创建微信扫码支付订单。</p>
|
||||||
<div class="vip-package-list" data-vip-package-list>
|
<div class="vip-package-list" data-vip-package-list>
|
||||||
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取会员套餐…</div>
|
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取会员套餐…</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -148,6 +144,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
<section class="module-panel" data-vip-payment-panel hidden>
|
||||||
|
<h2>微信扫码支付</h2>
|
||||||
|
<div data-vip-payment-qr></div>
|
||||||
|
<p data-vip-payment-text role="status" aria-live="polite"></p>
|
||||||
|
</section>
|
||||||
<section class="module-panel">
|
<section class="module-panel">
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<h2>会员订单</h2>
|
<h2>会员订单</h2>
|
||||||
@@ -187,6 +188,7 @@
|
|||||||
<script src="utils/axios.js"></script>
|
<script src="utils/axios.js"></script>
|
||||||
<script src="utils/AxiosRequestUtil.js"></script>
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
|
<script src="public/js/qrcode.min.js"></script>
|
||||||
<script src="public/js/profile-common.js"></script>
|
<script src="public/js/profile-common.js"></script>
|
||||||
<script src="public/js/vip-pages.js"></script>
|
<script src="public/js/vip-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
|
|||||||
+11
-11
@@ -28,11 +28,11 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Share</div>
|
<div class="module-kicker">应用分享</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>应用分享与收益</h1>
|
<h1>应用分享与收益</h1>
|
||||||
<p>当前 PC 未声明应用分享、奖励、收益或提现的正式接口;本页仅说明不可操作边界。</p>
|
<p>应用邀请暂以移动端为主;这里可查看推广收益、流水和提现记录。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -52,12 +52,12 @@
|
|||||||
<div class="module-card is-unavailable" aria-disabled="true">
|
<div class="module-card is-unavailable" aria-disabled="true">
|
||||||
<span class="icon">享</span>
|
<span class="icon">享</span>
|
||||||
<h3>每邀请一位好友</h3>
|
<h3>每邀请一位好友</h3>
|
||||||
<p>当前 PC 接口未提供邀请奖励规则,暂不能展示奖励。</p>
|
<p>邀请奖励规则正在完善,当前暂不展示奖励金额。</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="module-card is-unavailable" aria-disabled="true">
|
<div class="module-card is-unavailable" aria-disabled="true">
|
||||||
<span class="icon">码</span>
|
<span class="icon">码</span>
|
||||||
<h3>分享二维码</h3>
|
<h3>分享二维码</h3>
|
||||||
<p>当前 PC 接口未提供分享链接或二维码,暂不能生成。</p>
|
<p>分享链接和二维码暂未开放,请先使用移动端分享。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -65,23 +65,23 @@
|
|||||||
<h2>分享记录</h2>
|
<h2>分享记录</h2>
|
||||||
<div class="module-list">
|
<div class="module-list">
|
||||||
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">
|
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">
|
||||||
当前 PC 接口未提供分享记录,暂不能展示邀请访问或注册结果。
|
分享记录暂未开放;推广收益和提现结果可在下方查看。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="module-panel">
|
<section class="module-panel">
|
||||||
<h2>收益与提现</h2>
|
<h2>收益与提现</h2>
|
||||||
<div class="module-grid two">
|
<div class="module-grid two">
|
||||||
<div class="module-card is-unavailable" aria-disabled="true">
|
<a class="module-card tilt-card" href="profile-earnings.html" data-feature-link="available">
|
||||||
<span class="icon">收</span>
|
<span class="icon">收</span>
|
||||||
<h3>推广收益</h3>
|
<h3>推广收益</h3>
|
||||||
<p>当前 PC 接口未提供收益账户或余额,暂不能展示收益金额。</p>
|
<p>查看可用余额、冻结金额和不可变收益流水。</p>
|
||||||
</div>
|
</a>
|
||||||
<div class="module-card is-unavailable" aria-disabled="true">
|
<a class="module-card tilt-card" href="profile-earnings.html" data-feature-link="available">
|
||||||
<span class="icon">提</span>
|
<span class="icon">提</span>
|
||||||
<h3>提现记录</h3>
|
<h3>提现记录</h3>
|
||||||
<p>当前 PC 接口未提供提现方式、申请或记录,暂不能提交提现。</p>
|
<p>上传微信收款码,提交提现并跟踪处理状态。</p>
|
||||||
</div>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+9
-7
@@ -28,7 +28,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Tree Mode</div>
|
<div class="module-kicker">世系管理</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>世系图</h1>
|
<h1>世系图</h1>
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<a class="btn ghost magnetic" href="profile-generation.html" data-genealogy-context-link
|
<a class="btn ghost magnetic" href="profile-generation.html" data-genealogy-context-link
|
||||||
>管理世代</a
|
>管理字辈</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,6 +53,12 @@
|
|||||||
<div class="module-main">
|
<div class="module-main">
|
||||||
<section class="module-panel">
|
<section class="module-panel">
|
||||||
<h2>树谱操作</h2>
|
<h2>树谱操作</h2>
|
||||||
|
<div class="lineage-toolbar lineage-current-toolbar">
|
||||||
|
<label for="lineageCurrentPerson">当前成员</label>
|
||||||
|
<select id="lineageCurrentPerson" data-lineage-person-options>
|
||||||
|
<option value="">请选择当前成员</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="module-grid" data-lineage-management hidden>
|
<div class="module-grid" data-lineage-management hidden>
|
||||||
<button class="module-card module-action-card tilt-card" type="button" data-lineage-relation="parents"
|
<button class="module-card module-action-card tilt-card" type="button" data-lineage-relation="parents"
|
||||||
><span class="icon">父</span>
|
><span class="icon">父</span>
|
||||||
@@ -76,11 +82,6 @@
|
|||||||
<p>先选择成员,再为当前成员新增同辈关系。</p></button
|
<p>先选择成员,再为当前成员新增同辈关系。</p></button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="lineage-toolbar">
|
|
||||||
<select data-lineage-person-options aria-label="选择当前成员">
|
|
||||||
<option value="">请选择当前成员</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
<section class="module-panel">
|
<section class="module-panel">
|
||||||
<h2>世系树</h2>
|
<h2>世系树</h2>
|
||||||
@@ -297,6 +298,7 @@
|
|||||||
<script src="utils/AxiosRequestUtil.js"></script>
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
<script src="public/js/upload-pages.js"></script>
|
<script src="public/js/upload-pages.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/lineage-pages.js"></script>
|
<script src="public/js/lineage-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -28,11 +28,11 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Video Editor</div>
|
<div class="module-kicker">编辑视频</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1 data-video-editor-title>发布视频</h1>
|
<h1 data-video-editor-title>发布视频</h1>
|
||||||
<p>文件通过统一分片上传取得内部 OSS ID,页面不允许手工填写文件编号。</p>
|
<p>视频文件由上传组件自动关联,无需填写内部编号。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,6 +122,7 @@
|
|||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
<script src="public/js/md5.js"></script>
|
<script src="public/js/md5.js"></script>
|
||||||
<script src="public/js/upload-pages.js"></script>
|
<script src="public/js/upload-pages.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/video-pages.js"></script>
|
<script src="public/js/video-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+2
-1
@@ -29,7 +29,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<section class="module-hero">
|
<section class="module-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="module-kicker">Video</div>
|
<div class="module-kicker">家族视频</div>
|
||||||
<div class="module-title-row">
|
<div class="module-title-row">
|
||||||
<div>
|
<div>
|
||||||
<h1>家族视频</h1>
|
<h1>家族视频</h1>
|
||||||
@@ -76,6 +76,7 @@
|
|||||||
<script src="utils/axios.js"></script>
|
<script src="utils/axios.js"></script>
|
||||||
<script src="utils/AxiosRequestUtil.js"></script>
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/video-pages.js"></script>
|
<script src="public/js/video-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+11
-10
@@ -36,7 +36,7 @@
|
|||||||
<div class="container profile-hero-grid">
|
<div class="container profile-hero-grid">
|
||||||
<div class="profile-card">
|
<div class="profile-card">
|
||||||
<div class="avatar-wrap">
|
<div class="avatar-wrap">
|
||||||
<div class="avatar" data-profile-text="avatarText" aria-hidden="true">…</div>
|
<div class="avatar" data-profile-avatar data-profile-text="avatarText">…</div>
|
||||||
<span></span>
|
<span></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="profile-info">
|
<div class="profile-info">
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
<h1 data-profile-text="displayName">正在加载账户</h1>
|
<h1 data-profile-text="displayName">正在加载账户</h1>
|
||||||
<p>账户资料与家谱工作区分开呈现。进入家谱后,再处理成员、内容和协作事务。</p>
|
<p>账户资料与家谱工作区分开呈现。进入家谱后,再处理成员、内容和协作事务。</p>
|
||||||
<div class="profile-tags">
|
<div class="profile-tags">
|
||||||
<span>已登录账户</span><span>资料以实际接口为准</span>
|
<span>已登录账户</span><span>资料与账号同步</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="profile-load-status" data-profile-load-status role="status" aria-live="polite">正在读取账户资料…</div>
|
<div class="profile-load-status" data-profile-load-status role="status" aria-live="polite">正在读取账户资料…</div>
|
||||||
<div class="toolbar profile-hero-actions">
|
<div class="toolbar profile-hero-actions">
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
<a href="profile-data.html"><span>个人资料</span><b>查看并更新账户资料</b></a>
|
<a href="profile-data.html"><span>个人资料</span><b>查看并更新账户资料</b></a>
|
||||||
<a href="profile-messages.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 href="profile-services.html"><span>帮助与服务</span><b>反馈、安全设置与会员服务</b></a>
|
||||||
<a class="logout-link" href="#logout" data-logout-open><span>退出登录</span><b>返回首页</b></a>
|
<button class="logout-link" type="button" data-logout-open><span>退出登录</span><b>返回首页</b></button>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
<div class="panel-head compact">
|
<div class="panel-head compact">
|
||||||
<div>
|
<div>
|
||||||
<h2>账户资料</h2>
|
<h2>账户资料</h2>
|
||||||
<p>以下字段由账户资料接口返回;没有填写时会明确显示“待填写”。</p>
|
<p>以下内容来自你的账户资料;未填写的信息会显示为“待填写”。</p>
|
||||||
</div>
|
</div>
|
||||||
<a class="link-btn" href="profile-data.html">编辑资料</a>
|
<a class="link-btn" href="profile-data.html">编辑资料</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
<a href="profile-messages.html"><strong>消息中心</strong><span>查看真实通知与业务提醒</span></a>
|
<a href="profile-messages.html"><strong>消息中心</strong><span>查看真实通知与业务提醒</span></a>
|
||||||
<a href="profile-data.html"><strong>个人资料</strong><span>维护昵称、实名和联系方式</span></a>
|
<a href="profile-data.html"><strong>个人资料</strong><span>维护昵称、实名和联系方式</span></a>
|
||||||
<a href="profile-security.html"><strong>账户安全</strong><span>修改密码、换绑手机或注销账号</span></a>
|
<a href="profile-security.html"><strong>账户安全</strong><span>修改密码、换绑手机或注销账号</span></a>
|
||||||
<a href="profile-services.html"><strong>会员服务</strong><span>查看套餐、订单、帮助与服务说明</span></a>
|
<a href="profile-services.html#membership"><strong>会员服务</strong><span>查看套餐、订单、帮助与服务说明</span></a>
|
||||||
<a href="profile-feedback.html"><strong>意见反馈</strong><span>提交产品问题和改进建议</span></a>
|
<a href="profile-feedback.html"><strong>意见反馈</strong><span>提交产品问题和改进建议</span></a>
|
||||||
<a href="app.html"><strong>移动端</strong><span>下载 APP,处理移动端场景</span></a>
|
<a href="app.html"><strong>移动端</strong><span>下载 APP,处理移动端场景</span></a>
|
||||||
</div>
|
</div>
|
||||||
@@ -157,10 +157,10 @@
|
|||||||
<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>
|
||||||
<div>
|
<div>
|
||||||
<h4>联系我们</h4>
|
<h4>服务支持</h4>
|
||||||
<p>客服电话:400-000-0000</p>
|
<a href="help.html">帮助中心</a>
|
||||||
<p>邮箱:service@example.com</p>
|
<a href="submit-ticket.html">提交问题</a>
|
||||||
<p>地址:中国 · 成都</p>
|
<a href="about.html">关于平台</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-legal">
|
<div class="footer-legal">
|
||||||
@@ -169,7 +169,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
<div class="logout-modal" id="logoutModal" aria-hidden="true">
|
<div class="logout-modal" id="logoutModal" aria-hidden="true" hidden>
|
||||||
<div class="logout-modal-backdrop" data-logout-close></div>
|
<div class="logout-modal-backdrop" data-logout-close></div>
|
||||||
<div class="logout-dialog" role="dialog" aria-modal="true" aria-labelledby="logoutTitle">
|
<div class="logout-dialog" role="dialog" aria-modal="true" aria-labelledby="logoutTitle">
|
||||||
<button class="logout-close" type="button" data-logout-close aria-label="关闭">×</button>
|
<button class="logout-close" type="button" data-logout-close aria-label="关闭">×</button>
|
||||||
@@ -192,6 +192,7 @@
|
|||||||
<script src="utils/AxiosRequestUtil.js"></script>
|
<script src="utils/AxiosRequestUtil.js"></script>
|
||||||
<script src="utils/ApiClient.js"></script>
|
<script src="utils/ApiClient.js"></script>
|
||||||
<script src="public/js/profile-common.js?v=20260725-entry"></script>
|
<script src="public/js/profile-common.js?v=20260725-entry"></script>
|
||||||
|
<script src="public/js/media-display.js"></script>
|
||||||
<script src="public/js/profile-pages.js"></script>
|
<script src="public/js/profile-pages.js"></script>
|
||||||
<script src="public/js/page-effects.js"></script>
|
<script src="public/js/page-effects.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+7
-7
@@ -31,7 +31,7 @@
|
|||||||
<section class="inner-hero promo-hero">
|
<section class="inner-hero promo-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="hero-copy">
|
<div class="hero-copy">
|
||||||
<div class="eyebrow">Public Album</div>
|
<div class="eyebrow">宣传相册</div>
|
||||||
<h1>用公开相册展示家族故事</h1>
|
<h1>用公开相册展示家族故事</h1>
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
宣传相册用于前台展示祖屋、祠堂、家族活动、老照片和公开纪念内容,帮助访客更直观理解家族文化。
|
宣传相册用于前台展示祖屋、祠堂、家族活动、老照片和公开纪念内容,帮助访客更直观理解家族文化。
|
||||||
@@ -74,17 +74,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>产品入口</h4>
|
<h4>产品入口</h4>
|
||||||
<a>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
|
<a href="genealogy.html">数字家谱</a><a href="plaza.html">家谱广场</a><a href="app.html">应用下载</a><a href="help.html">帮助中心</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>文化内容</h4>
|
<h4>文化内容</h4>
|
||||||
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a><a>平台公告</a>
|
<a href="culture.html">家族文化</a><a href="surname.html">姓氏百科</a><a href="article-detail.html">家谱知识</a><a href="notice-detail.html">平台公告</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>联系我们</h4>
|
<h4>服务支持</h4>
|
||||||
<p>客服电话:400-000-0000</p>
|
<a href="help.html">帮助中心</a>
|
||||||
<p>邮箱:service@example.com</p>
|
<a href="submit-ticket.html">提交问题</a>
|
||||||
<p>地址:中国 · 成都</p>
|
<a href="about.html">关于平台</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-legal">
|
<div class="footer-legal">
|
||||||
|
|||||||
+7
-7
@@ -31,7 +31,7 @@
|
|||||||
<section class="inner-hero promo-hero">
|
<section class="inner-hero promo-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="hero-copy">
|
<div class="hero-copy">
|
||||||
<div class="eyebrow">Public Video</div>
|
<div class="eyebrow">宣传视频</div>
|
||||||
<h1>用宣传视频讲述家族文化</h1>
|
<h1>用宣传视频讲述家族文化</h1>
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
宣传视频用于前台展示家族活动、祠堂风貌、修谱故事和公开纪念内容,让访问者更快进入家族叙事。
|
宣传视频用于前台展示家族活动、祠堂风貌、修谱故事和公开纪念内容,让访问者更快进入家族叙事。
|
||||||
@@ -81,17 +81,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>产品入口</h4>
|
<h4>产品入口</h4>
|
||||||
<a>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
|
<a href="genealogy.html">数字家谱</a><a href="plaza.html">家谱广场</a><a href="app.html">应用下载</a><a href="help.html">帮助中心</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>文化内容</h4>
|
<h4>文化内容</h4>
|
||||||
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a><a>平台公告</a>
|
<a href="culture.html">家族文化</a><a href="surname.html">姓氏百科</a><a href="article-detail.html">家谱知识</a><a href="notice-detail.html">平台公告</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>联系我们</h4>
|
<h4>服务支持</h4>
|
||||||
<p>客服电话:400-000-0000</p>
|
<a href="help.html">帮助中心</a>
|
||||||
<p>邮箱:service@example.com</p>
|
<a href="submit-ticket.html">提交问题</a>
|
||||||
<p>地址:中国 · 成都</p>
|
<a href="about.html">关于平台</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-legal">
|
<div class="footer-legal">
|
||||||
|
|||||||
@@ -1412,3 +1412,170 @@
|
|||||||
transition-duration: .01ms !important;
|
transition-duration: .01ms !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.hero {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-inner {
|
||||||
|
min-height: 0;
|
||||||
|
gap: 28px;
|
||||||
|
padding: 36px 0 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-copy {
|
||||||
|
min-width: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-copy:before {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lead {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-path {
|
||||||
|
gap: 7px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-path i {
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-action-card,
|
||||||
|
.cta,
|
||||||
|
.cta .btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-action-card {
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta .btn {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metrics {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric + .metric {
|
||||||
|
border-top: 1px solid rgba(218, 201, 174, .78);
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-visual {
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.archive-stage,
|
||||||
|
.book-cover {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hall,
|
||||||
|
.video-card,
|
||||||
|
.search-panel {
|
||||||
|
width: calc(100dvw - 28px);
|
||||||
|
max-width: 100%;
|
||||||
|
position: relative;
|
||||||
|
inset: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hall {
|
||||||
|
height: auto;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-card {
|
||||||
|
height: 174px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-panel {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-scroll-card,
|
||||||
|
.word-window {
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box span {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
padding-left: 14px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box b {
|
||||||
|
width: 76px;
|
||||||
|
flex: 0 0 76px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.genealogy-list,
|
||||||
|
.function-grid,
|
||||||
|
.quick-inner,
|
||||||
|
.qr-wrap {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
padding: 52px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-head h2,
|
||||||
|
.download-panel h2 {
|
||||||
|
font-size: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.surname-panel {
|
||||||
|
gap: 18px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.surname-intro {
|
||||||
|
padding: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.surname-grid {
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.surname-name {
|
||||||
|
height: 54px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-panel {
|
||||||
|
gap: 28px;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,41 @@
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.join-page-grid .field {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.join-page-grid .field > span {
|
||||||
|
color: var(--ink);
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.join-page-grid .field input,
|
||||||
|
.join-page-grid .field textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fffdf8;
|
||||||
|
color: var(--ink);
|
||||||
|
font: inherit;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color .2s ease, box-shadow .2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.join-page-grid .field textarea {
|
||||||
|
min-height: 128px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.join-page-grid .field input:focus,
|
||||||
|
.join-page-grid .field textarea:focus {
|
||||||
|
border-color: rgba(200, 59, 50, .45);
|
||||||
|
box-shadow: 0 0 0 4px rgba(200, 59, 50, .08);
|
||||||
|
}
|
||||||
|
|
||||||
.join-search-panel {
|
.join-search-panel {
|
||||||
padding-right: 28px;
|
padding-right: 28px;
|
||||||
border-right: 1px solid var(--line);
|
border-right: 1px solid var(--line);
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
.module-hero {
|
.module-hero {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
min-height: 257px;
|
||||||
|
box-sizing: border-box;
|
||||||
padding: 58px 0 42px;
|
padding: 58px 0 42px;
|
||||||
background:
|
background:
|
||||||
radial-gradient(circle at 76% 18%, rgba(196, 146, 69, .16), transparent 25%),
|
radial-gradient(circle at 76% 18%, rgba(196, 146, 69, .16), transparent 25%),
|
||||||
@@ -78,9 +80,15 @@
|
|||||||
top: 106px;
|
top: 106px;
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
max-height: calc(100vh - 126px);
|
||||||
|
overflow-y: auto;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-profile-module .module-nav:not([data-workspace-navigation]) {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.module-nav a {
|
.module-nav a {
|
||||||
padding: 14px 16px;
|
padding: 14px 16px;
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
@@ -96,9 +104,53 @@
|
|||||||
transform: translateX(4px);
|
transform: translateX(4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.workspace-nav-group {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-nav-group[open] {
|
||||||
|
border-color: rgba(224, 211, 189, .72);
|
||||||
|
background: rgba(255, 250, 242, .72);
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-nav-group summary {
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-radius: 14px;
|
||||||
|
color: #5e665f;
|
||||||
|
font-weight: 900;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color .22s ease, background .22s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-nav-group summary:hover,
|
||||||
|
.workspace-nav-group.is-open > summary {
|
||||||
|
color: var(--red);
|
||||||
|
background: #fbefea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-nav-group summary:focus-visible,
|
||||||
|
.module-nav a:focus-visible {
|
||||||
|
outline: 3px solid rgba(200, 59, 50, .32);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-nav-items {
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 2px 8px 8px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-nav .workspace-nav-items a {
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
.module-main {
|
.module-main {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.feature-status-banner {
|
.feature-status-banner {
|
||||||
@@ -321,6 +373,22 @@
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.row-actions button.pill {
|
||||||
|
border: 0;
|
||||||
|
font: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-actions .pill:focus-visible {
|
||||||
|
outline: 3px solid rgba(200, 59, 50, .28);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-panel h2[tabindex="-1"] {
|
||||||
|
scroll-margin-top: 96px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
.pill.is-danger {
|
.pill.is-danger {
|
||||||
color: #8a322b;
|
color: #8a322b;
|
||||||
background: #fde8e2;
|
background: #fde8e2;
|
||||||
@@ -396,12 +464,17 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-input {
|
.editor-field .upload-input {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 1px;
|
width: 1px;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
|
min-height: 0;
|
||||||
|
margin: -1px;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
clip: rect(0 0 0 0);
|
clip: rect(0 0 0 0);
|
||||||
|
clip-path: inset(50%);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,13 +572,19 @@
|
|||||||
.security-form {
|
.security-form {
|
||||||
/* 安全设置表单复用编辑表单结构,只补模块间距 */
|
/* 安全设置表单复用编辑表单结构,只补模块间距 */
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
|
max-width: 880px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.security-danger {
|
.security-danger {
|
||||||
|
border-left: 4px solid #a13b32;
|
||||||
border-color: rgba(138, 50, 43, .28);
|
border-color: rgba(138, 50, 43, .28);
|
||||||
background: #fff7f2;
|
background: #fff7f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.security-danger h2 {
|
||||||
|
color: #7e2d27;
|
||||||
|
}
|
||||||
|
|
||||||
.security-danger .editor-field input,
|
.security-danger .editor-field input,
|
||||||
.security-danger .editor-field textarea {
|
.security-danger .editor-field textarea {
|
||||||
background: #fffdf8;
|
background: #fffdf8;
|
||||||
@@ -791,6 +870,11 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-field > .btn {
|
||||||
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-form-actions {
|
.profile-form-actions {
|
||||||
@@ -889,6 +973,8 @@
|
|||||||
.module-nav {
|
.module-nav {
|
||||||
position: static;
|
position: static;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
max-height: none;
|
||||||
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lineage-filter-toolbar {
|
.lineage-filter-toolbar {
|
||||||
@@ -897,6 +983,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
|
.module-hero {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.page-profile-module .module-nav {
|
.page-profile-module .module-nav {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -920,3 +1010,245 @@
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 个人中心工作台的统一密度和媒体布局。所有业务页沿用同一侧栏宽度与内容节奏。 */
|
||||||
|
.page-profile-module {
|
||||||
|
background: var(--color-canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-profile-module .nav {
|
||||||
|
height: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-profile-module .section {
|
||||||
|
padding: var(--space-7) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-hero {
|
||||||
|
height: 168px;
|
||||||
|
min-height: 168px;
|
||||||
|
padding: var(--space-5) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lineage-current-toolbar {
|
||||||
|
margin-bottom: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lineage-current-toolbar label {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-kicker {
|
||||||
|
margin-bottom: var(--space-3);
|
||||||
|
padding: 6px 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-title-row h1 {
|
||||||
|
margin-bottom: var(--space-2);
|
||||||
|
font-size: var(--text-2xl);
|
||||||
|
line-height: 1.18;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-title-row p {
|
||||||
|
font-size: var(--text-body-lg);
|
||||||
|
line-height: 1.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-layout {
|
||||||
|
grid-template-columns: 248px minmax(0, 1fr);
|
||||||
|
gap: var(--space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-nav,
|
||||||
|
.module-panel,
|
||||||
|
.module-card {
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
box-shadow: var(--shadow-panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-nav {
|
||||||
|
top: 88px;
|
||||||
|
gap: var(--space-2);
|
||||||
|
max-height: calc(100vh - 108px);
|
||||||
|
padding: var(--space-3);
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(171, 65, 54, .34) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-nav::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-nav::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(171, 65, 54, .34);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-nav::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-nav a,
|
||||||
|
.workspace-nav-group summary {
|
||||||
|
padding: 11px 13px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-nav a:hover,
|
||||||
|
.module-nav a.active {
|
||||||
|
transform: translateX(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-main {
|
||||||
|
gap: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-panel {
|
||||||
|
padding: var(--space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-panel h2 {
|
||||||
|
font-size: var(--text-title);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-grid {
|
||||||
|
gap: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-card {
|
||||||
|
min-height: 144px;
|
||||||
|
padding: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-card:after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-card h3,
|
||||||
|
.module-row h3 {
|
||||||
|
font-size: var(--text-heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-row {
|
||||||
|
gap: var(--space-4);
|
||||||
|
padding: var(--space-4);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-row[data-join-genealogy-id] > span:first-child {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-1);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-row[data-join-genealogy-id] small {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-like {
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-like p {
|
||||||
|
grid-template-columns: 132px minmax(0, 1fr) auto;
|
||||||
|
gap: var(--space-4);
|
||||||
|
padding: 14px var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-row > .media-frame,
|
||||||
|
.album-photo-row > .media-frame {
|
||||||
|
width: 152px;
|
||||||
|
aspect-ratio: 4 / 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.family-cover-media,
|
||||||
|
.article-cover-media,
|
||||||
|
.album-cover-media,
|
||||||
|
.ceremony-cover-media,
|
||||||
|
.video-detail-media {
|
||||||
|
margin-bottom: var(--space-4);
|
||||||
|
aspect-ratio: 16 / 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.family-cover-media img,
|
||||||
|
.article-cover-media img,
|
||||||
|
.album-cover-media img,
|
||||||
|
.ceremony-cover-media img,
|
||||||
|
.video-detail-media video {
|
||||||
|
aspect-ratio: 16 / 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(.article-cover-media, .album-cover-media, .ceremony-cover-media) img {
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-detail-media video {
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lineage-row {
|
||||||
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lineage-node {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
|
column-gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lineage-node > .media-avatar {
|
||||||
|
grid-row: 1 / 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1060px) {
|
||||||
|
.module-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-nav {
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.module-hero {
|
||||||
|
height: auto;
|
||||||
|
padding: var(--space-5) 0 var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-title-row h1 {
|
||||||
|
font-size: var(--text-title);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-panel {
|
||||||
|
padding: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-row > .media-frame,
|
||||||
|
.album-photo-row > .media-frame {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-like p,
|
||||||
|
.module-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-actions {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-actions .pill {
|
||||||
|
display: inline-flex;
|
||||||
|
min-height: 44px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-actions .form-status {
|
||||||
|
flex-basis: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -211,16 +211,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.user-actions a,
|
.user-actions a,
|
||||||
|
.user-actions button,
|
||||||
.todo-list a {
|
.todo-list a {
|
||||||
display: block;
|
display: block;
|
||||||
|
width: 100%;
|
||||||
padding: 15px 16px;
|
padding: 15px 16px;
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
background: #fbf7ed;
|
background: #fbf7ed;
|
||||||
|
color: inherit;
|
||||||
|
font: inherit;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
transition: transform .22s ease, border-color .22s ease, background .22s ease, color .22s ease;
|
transition: transform .22s ease, border-color .22s ease, background .22s ease, color .22s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-actions a:hover,
|
.user-actions a:hover,
|
||||||
|
.user-actions button:hover,
|
||||||
.todo-list a:hover {
|
.todo-list a:hover {
|
||||||
transform: translateX(5px);
|
transform: translateX(5px);
|
||||||
border-color: rgba(71, 111, 99, .28);
|
border-color: rgba(71, 111, 99, .28);
|
||||||
@@ -499,6 +506,10 @@
|
|||||||
transition: opacity .22s ease;
|
transition: opacity .22s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logout-modal[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.logout-modal.show {
|
.logout-modal.show {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
@@ -662,3 +673,47 @@
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-profile {
|
||||||
|
background: var(--color-canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-hero {
|
||||||
|
padding: var(--space-7) 0 var(--space-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card,
|
||||||
|
.profile-stats,
|
||||||
|
.profile-next-card,
|
||||||
|
.panel,
|
||||||
|
.side-card {
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
box-shadow: var(--shadow-panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-info h1 {
|
||||||
|
font-size: var(--text-display);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar .media-avatar,
|
||||||
|
.avatar .media-avatar img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar .media-avatar {
|
||||||
|
color: #fff;
|
||||||
|
background: transparent;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 760px) {
|
||||||
|
.profile-hero {
|
||||||
|
padding: var(--space-5) 0 var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-info h1 {
|
||||||
|
font-size: var(--text-title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+360
-17
@@ -1,16 +1,18 @@
|
|||||||
|
@import url("../../tokens.css");
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--red: #c83b32;
|
--red: var(--color-accent);
|
||||||
--red-soft: #f4ded8;
|
--red-soft: var(--color-accent-soft);
|
||||||
--green: #476f63;
|
--green: var(--color-green);
|
||||||
--green-dark: #213f38;
|
--green-dark: var(--color-ink);
|
||||||
--gold: #c49245;
|
--gold: var(--color-gold);
|
||||||
--ink: #26302c;
|
--ink: var(--color-ink);
|
||||||
--muted: #737b76;
|
--muted: var(--color-ink-2);
|
||||||
--paper: #fbf7ed;
|
--paper: var(--color-paper);
|
||||||
--paper-2: #f7efe2;
|
--paper-2: var(--color-paper-2);
|
||||||
--line: #eadfce;
|
--line: var(--color-rule);
|
||||||
--white: #fffdf8;
|
--white: var(--color-panel);
|
||||||
--shadow: 0 24px 70px rgba(69, 53, 35, .12);
|
--shadow: var(--shadow-card);
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-layer-message {
|
.auth-layer-message {
|
||||||
@@ -34,10 +36,19 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
overflow-x: clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scrollbar-gutter: stable;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
font-family: "Microsoft YaHei", "PingFang SC", Arial, sans-serif;
|
font-family: var(--font-body);
|
||||||
background: var(--paper);
|
background: var(--paper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +74,7 @@ img {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
width: min(1280px, calc(100% - 72px));
|
width: min(var(--layout-max), calc(100% - 72px));
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +153,7 @@ img {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 26px;
|
gap: 26px;
|
||||||
color: rgba(255, 253, 248, .78);
|
color: #fffdf8;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
@@ -154,6 +165,7 @@ img {
|
|||||||
|
|
||||||
.nav-links a,
|
.nav-links a,
|
||||||
.nav-actions a {
|
.nav-actions a {
|
||||||
|
color: inherit;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: color .22s ease, transform .22s ease;
|
transition: color .22s ease, transform .22s ease;
|
||||||
}
|
}
|
||||||
@@ -181,7 +193,7 @@ img {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
color: rgba(255, 253, 248, .86);
|
color: #fffdf8;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
@@ -215,7 +227,9 @@ img {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.profile-mobile-menu-toggle,
|
.profile-mobile-menu-toggle,
|
||||||
.profile-mobile-menu {
|
.profile-mobile-menu,
|
||||||
|
.site-menu-toggle,
|
||||||
|
.site-mobile-menu {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,6 +277,18 @@ img {
|
|||||||
background: rgba(255, 255, 255, .68);
|
background: rgba(255, 255, 255, .68);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn.danger {
|
||||||
|
border-color: #8a322b;
|
||||||
|
color: #fffdf8;
|
||||||
|
background: #8a322b;
|
||||||
|
box-shadow: 0 10px 22px rgba(138, 50, 43, .18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.danger:hover {
|
||||||
|
border-color: #71251f;
|
||||||
|
background: #71251f;
|
||||||
|
}
|
||||||
|
|
||||||
.inner-hero {
|
.inner-hero {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -883,6 +909,8 @@ textarea {
|
|||||||
top: 100%;
|
top: 100%;
|
||||||
right: 0;
|
right: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
max-height: calc(100vh - 72px);
|
||||||
|
overflow-y: auto;
|
||||||
padding: 14px 0 18px;
|
padding: 14px 0 18px;
|
||||||
border-bottom: 1px solid rgba(255, 253, 248, .24);
|
border-bottom: 1px solid rgba(255, 253, 248, .24);
|
||||||
background: rgba(121, 42, 35, .99);
|
background: rgba(121, 42, 35, .99);
|
||||||
@@ -890,9 +918,46 @@ textarea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.profile-mobile-menu-inner {
|
.profile-mobile-menu-inner {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-mobile-workspace {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-mobile-workspace .workspace-nav-group {
|
||||||
|
border-color: rgba(255, 248, 230, .16);
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-mobile-workspace .workspace-nav-group[open] {
|
||||||
|
background: rgba(255, 253, 248, .08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-mobile-menu .profile-mobile-workspace summary {
|
||||||
|
min-height: 46px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 14px;
|
||||||
|
color: #fff8e6;
|
||||||
|
font-weight: 900;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-mobile-menu .profile-mobile-workspace .workspace-nav-group.is-open > summary {
|
||||||
|
color: #fff7d8;
|
||||||
|
background: rgba(255, 253, 248, .16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-mobile-workspace .workspace-nav-items,
|
||||||
|
.profile-mobile-account-actions {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-mobile-menu a,
|
.profile-mobile-menu a,
|
||||||
@@ -922,11 +987,76 @@ textarea {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 620px) {
|
||||||
|
.profile-mobile-workspace .workspace-nav-items,
|
||||||
|
.profile-mobile-account-actions {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 1120px) {
|
@media (max-width: 1120px) {
|
||||||
.nav-links {
|
.nav-links {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.site-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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-site-mobile-menu .nav-actions {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-mobile-menu-inner {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-mobile-menu a {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-mobile-menu a.active {
|
||||||
|
border-color: rgba(255, 241, 202, .76);
|
||||||
|
color: #fff7d8;
|
||||||
|
background: rgba(255, 253, 248, .2);
|
||||||
|
}
|
||||||
|
|
||||||
.inner-hero .container,
|
.inner-hero .container,
|
||||||
.grid-2 {
|
.grid-2 {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@@ -1002,6 +1132,10 @@ textarea {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.site-mobile-menu-inner {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
.inner-hero .container {
|
.inner-hero .container {
|
||||||
padding: 46px 0 54px;
|
padding: 46px 0 54px;
|
||||||
}
|
}
|
||||||
@@ -1034,6 +1168,215 @@ textarea {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 统一业务媒体:只承载后端授权地址,加载失败时保留可理解的回退状态。 */
|
||||||
|
.media-frame {
|
||||||
|
position: relative;
|
||||||
|
min-width: 0;
|
||||||
|
margin: 0;
|
||||||
|
overflow: clip;
|
||||||
|
border: var(--rule-thin) solid var(--color-rule);
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
background: var(--color-panel-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-preview {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-frame img,
|
||||||
|
.media-frame video {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
object-fit: cover;
|
||||||
|
background: var(--color-paper-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-frame img {
|
||||||
|
aspect-ratio: 4 / 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-frame video {
|
||||||
|
max-height: min(72vh, 720px);
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-frame figcaption {
|
||||||
|
padding: var(--space-xs) var(--space-sm);
|
||||||
|
color: var(--color-ink-2);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-fallback {
|
||||||
|
min-height: 132px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
padding: var(--space-md);
|
||||||
|
color: var(--color-ink-2);
|
||||||
|
background: var(--color-paper-2);
|
||||||
|
text-align: center;
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-fallback[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-gallery {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(min(200px, 100%), 280px));
|
||||||
|
gap: var(--space-sm);
|
||||||
|
margin-top: var(--space-md);
|
||||||
|
justify-content: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-gallery .media-frame img {
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-download {
|
||||||
|
min-height: var(--control-height);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--space-xs) var(--space-sm);
|
||||||
|
border: var(--rule-thin) solid var(--color-rule);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
color: var(--color-accent-strong);
|
||||||
|
background: var(--color-panel);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: 700;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-avatar {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
position: relative;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: inline-grid;
|
||||||
|
place-items: center;
|
||||||
|
overflow: clip;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: var(--color-accent-strong);
|
||||||
|
background: var(--color-accent-soft);
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-avatar img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-avatar-fallback {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-avatar img:not([hidden]) + .media-avatar-fallback {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-avatar.is-media-error .media-avatar-fallback {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-dialog {
|
||||||
|
width: min(1040px, calc(100% - 32px));
|
||||||
|
max-height: calc(100vh - 32px);
|
||||||
|
padding: var(--space-lg);
|
||||||
|
border: var(--rule-thin) solid var(--color-rule);
|
||||||
|
border-radius: var(--radius-dialog);
|
||||||
|
color: var(--color-ink);
|
||||||
|
background: var(--color-panel);
|
||||||
|
box-shadow: var(--shadow-dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-dialog::backdrop {
|
||||||
|
background: var(--color-overlay);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-dialog img {
|
||||||
|
width: 100%;
|
||||||
|
max-height: calc(100vh - 120px);
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-dialog-close {
|
||||||
|
min-height: 40px;
|
||||||
|
display: block;
|
||||||
|
margin: 0 0 var(--space-sm) auto;
|
||||||
|
padding: 0 var(--space-sm);
|
||||||
|
border: var(--rule-thin) solid var(--color-rule);
|
||||||
|
border-radius: var(--radius-pill);
|
||||||
|
color: var(--color-ink);
|
||||||
|
background: var(--color-panel);
|
||||||
|
font: inherit;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
|
||||||
|
outline: 3px solid var(--color-focus);
|
||||||
|
outline-offset: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:where(button, .btn, input, select, textarea):disabled,
|
||||||
|
:where(button, .btn)[aria-disabled="true"] {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: .52;
|
||||||
|
box-shadow: none;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:where(button, .btn)[aria-busy="true"] {
|
||||||
|
cursor: progress;
|
||||||
|
opacity: .72;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-state,
|
||||||
|
.api-empty {
|
||||||
|
padding: var(--space-md);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--color-panel-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-state--error,
|
||||||
|
.api-state--forbidden,
|
||||||
|
[data-state="error"] {
|
||||||
|
border-color: color-mix(in oklch, var(--color-error) 32%, var(--color-rule));
|
||||||
|
color: var(--color-error);
|
||||||
|
background: var(--color-error-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-state--success,
|
||||||
|
[data-state="success"] {
|
||||||
|
border-color: color-mix(in oklch, var(--color-success) 30%, var(--color-rule));
|
||||||
|
color: var(--color-success);
|
||||||
|
background: var(--color-success-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.media-gallery {
|
||||||
|
grid-template-columns: minmax(0, 280px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-dialog {
|
||||||
|
padding: var(--space-sm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
|
||||||
*,
|
*,
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 147 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 284 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 135 KiB |
+25
-11
@@ -13,6 +13,15 @@
|
|||||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
function confirmAction(message) {
|
||||||
|
if (root.ProfileUI && typeof root.ProfileUI.confirmAction === 'function') {
|
||||||
|
return root.ProfileUI.confirmAction(message);
|
||||||
|
}
|
||||||
|
return Promise.resolve(!root['confirm'] || root['confirm'](message));
|
||||||
|
}
|
||||||
|
|
||||||
|
var MediaDisplay = root.MediaDisplay || (typeof require === 'function' ? require('./media-display.js') : null);
|
||||||
|
|
||||||
var documentRef = root.document;
|
var documentRef = root.document;
|
||||||
var albumsById = Object.create(null);
|
var albumsById = Object.create(null);
|
||||||
var photosById = Object.create(null);
|
var photosById = Object.create(null);
|
||||||
@@ -180,7 +189,7 @@
|
|||||||
function normalizeAlbum(item) {
|
function normalizeAlbum(item) {
|
||||||
var albumId = normalizeId(item && item.albumId);
|
var albumId = normalizeId(item && item.albumId);
|
||||||
var genealogyId = normalizeId(item && item.genealogyId);
|
var genealogyId = normalizeId(item && item.genealogyId);
|
||||||
var coverOssId = normalizeNullableId(item && item.coverOssId);
|
var coverFile = MediaDisplay && MediaDisplay.normalizeFileAccess(item && item.coverFile);
|
||||||
var albumName = stringValue(item && item.albumName).trim();
|
var albumName = stringValue(item && item.albumName).trim();
|
||||||
var photoCount = optionalSafeInteger(item && item.photoCount);
|
var photoCount = optionalSafeInteger(item && item.photoCount);
|
||||||
var sortOrder = optionalSafeInteger(item && item.sortOrder);
|
var sortOrder = optionalSafeInteger(item && item.sortOrder);
|
||||||
@@ -188,7 +197,7 @@
|
|||||||
var album;
|
var album;
|
||||||
|
|
||||||
if (!albumId || !genealogyId || !albumName) return null;
|
if (!albumId || !genealogyId || !albumName) return null;
|
||||||
if (item.coverOssId !== undefined && item.coverOssId !== null && item.coverOssId !== '' && !coverOssId) return null;
|
if (item.coverFile !== undefined && item.coverFile !== null && !coverFile) return null;
|
||||||
if (item.photoCount !== undefined && item.photoCount !== null && item.photoCount !== '' && photoCount === undefined) return null;
|
if (item.photoCount !== undefined && item.photoCount !== null && item.photoCount !== '' && photoCount === undefined) return null;
|
||||||
if (item.sortOrder !== undefined && item.sortOrder !== null && item.sortOrder !== '' && sortOrder === undefined) return null;
|
if (item.sortOrder !== undefined && item.sortOrder !== null && item.sortOrder !== '' && sortOrder === undefined) return null;
|
||||||
if (status !== '0' && status !== '1') return null;
|
if (status !== '0' && status !== '1') return null;
|
||||||
@@ -203,7 +212,7 @@
|
|||||||
status: status,
|
status: status,
|
||||||
remark: stringValue(item.remark)
|
remark: stringValue(item.remark)
|
||||||
};
|
};
|
||||||
if (coverOssId) album.coverOssId = coverOssId;
|
if (coverFile) album.coverFile = coverFile;
|
||||||
if (photoCount !== undefined) album.photoCount = photoCount;
|
if (photoCount !== undefined) album.photoCount = photoCount;
|
||||||
if (sortOrder !== undefined) album.sortOrder = sortOrder;
|
if (sortOrder !== undefined) album.sortOrder = sortOrder;
|
||||||
return album;
|
return album;
|
||||||
@@ -213,12 +222,12 @@
|
|||||||
var photoId = normalizeId(item && item.photoId);
|
var photoId = normalizeId(item && item.photoId);
|
||||||
var genealogyId = normalizeId(item && item.genealogyId);
|
var genealogyId = normalizeId(item && item.genealogyId);
|
||||||
var albumId = normalizeId(item && item.albumId);
|
var albumId = normalizeId(item && item.albumId);
|
||||||
var ossId = normalizeId(item && item.ossId);
|
var photoFile = MediaDisplay && MediaDisplay.normalizeFileAccess(item && item.photoFile);
|
||||||
var sortOrder = optionalSafeInteger(item && item.sortOrder);
|
var sortOrder = optionalSafeInteger(item && item.sortOrder);
|
||||||
var status = stringValue(item && item.status);
|
var status = stringValue(item && item.status);
|
||||||
var photo;
|
var photo;
|
||||||
|
|
||||||
if (!photoId || !genealogyId || !albumId || !ossId) return null;
|
if (!photoId || !genealogyId || !albumId || !photoFile) return null;
|
||||||
if (item.sortOrder !== undefined && item.sortOrder !== null && item.sortOrder !== '' && sortOrder === undefined) return null;
|
if (item.sortOrder !== undefined && item.sortOrder !== null && item.sortOrder !== '' && sortOrder === undefined) return null;
|
||||||
if (status !== '0' && status !== '1') return null;
|
if (status !== '0' && status !== '1') return null;
|
||||||
photo = {
|
photo = {
|
||||||
@@ -229,7 +238,7 @@
|
|||||||
surname: stringValue(item.surname),
|
surname: stringValue(item.surname),
|
||||||
albumId: albumId,
|
albumId: albumId,
|
||||||
albumName: stringValue(item.albumName),
|
albumName: stringValue(item.albumName),
|
||||||
ossId: ossId,
|
photoFile: photoFile,
|
||||||
photoTitle: stringValue(item.photoTitle),
|
photoTitle: stringValue(item.photoTitle),
|
||||||
photoDesc: stringValue(item.photoDesc),
|
photoDesc: stringValue(item.photoDesc),
|
||||||
photographer: stringValue(item.photographer),
|
photographer: stringValue(item.photographer),
|
||||||
@@ -299,10 +308,12 @@
|
|||||||
if (canEditContent) {
|
if (canEditContent) {
|
||||||
actions = '<div class="bottom-actions"><a class="btn ghost" href="' +
|
actions = '<div class="bottom-actions"><a class="btn ghost" href="' +
|
||||||
escapeHtml(buildEditUrl(album.genealogyId, album.albumId)) + '">编辑相册</a>' +
|
escapeHtml(buildEditUrl(album.genealogyId, album.albumId)) + '">编辑相册</a>' +
|
||||||
'<button class="btn ghost" type="button" data-album-delete-id="' +
|
'<button class="btn danger" type="button" data-album-delete-id="' +
|
||||||
escapeHtml(album.albumId) + '">删除相册</button></div>';
|
escapeHtml(album.albumId) + '">删除相册</button></div>';
|
||||||
}
|
}
|
||||||
return '<div class="form-like album-detail">' +
|
return (MediaDisplay ? MediaDisplay.renderImage(album.coverFile, {
|
||||||
|
alt: album.albumName + '封面', className: 'album-cover-media'
|
||||||
|
}) : '') + '<div class="form-like album-detail">' +
|
||||||
detailValue('相册名称', album.albumName) +
|
detailValue('相册名称', album.albumName) +
|
||||||
detailValue('相册说明', album.albumDesc) +
|
detailValue('相册说明', album.albumDesc) +
|
||||||
detailValue('照片数量', album.photoCount === undefined ? '' : album.photoCount) +
|
detailValue('照片数量', album.photoCount === undefined ? '' : album.photoCount) +
|
||||||
@@ -334,7 +345,10 @@
|
|||||||
? '<button class="pill is-danger" type="button" data-album-photo-delete-id="' +
|
? '<button class="pill is-danger" type="button" data-album-photo-delete-id="' +
|
||||||
escapeHtml(photo.photoId) + '">删除</button>'
|
escapeHtml(photo.photoId) + '">删除</button>'
|
||||||
: '';
|
: '';
|
||||||
return '<article class="module-row album-photo-row"><div><h3>' +
|
return '<article class="module-row album-photo-row">' +
|
||||||
|
(MediaDisplay ? MediaDisplay.renderImage(photo.photoFile, {
|
||||||
|
alt: photo.photoTitle || '家族照片', className: 'album-photo-media'
|
||||||
|
}) : '') + '<div><h3>' +
|
||||||
escapeHtml(photo.photoTitle || '未命名照片') + '</h3><p>' +
|
escapeHtml(photo.photoTitle || '未命名照片') + '</h3><p>' +
|
||||||
escapeHtml([photo.photoDesc, photo.photographer, photo.shootTime].filter(Boolean).join(' · ') || '文件已上传') +
|
escapeHtml([photo.photoDesc, photo.photographer, photo.shootTime].filter(Boolean).join(' · ') || '文件已上传') +
|
||||||
'</p></div><div class="row-actions">' + action + '</div></article>';
|
'</p></div><div class="row-actions">' + action + '</div></article>';
|
||||||
@@ -706,7 +720,7 @@
|
|||||||
var album = albumsById[albumId] || currentAlbum;
|
var album = albumsById[albumId] || currentAlbum;
|
||||||
|
|
||||||
if (writePending || !canEditContent || !genealogyId || !normalizeId(albumId) || redirectUnauthorized(api)) return;
|
if (writePending || !canEditContent || !genealogyId || !normalizeId(albumId) || redirectUnauthorized(api)) return;
|
||||||
if (root.confirm && !root.confirm('确认删除“' + (album ? album.albumName : '该相册') + '”及其中全部照片吗?删除后不可恢复。')) return;
|
if (!await confirmAction('确认删除“' + (album ? album.albumName : '该相册') + '”及其中全部照片吗?删除后不可恢复。')) return;
|
||||||
setWritePending(true);
|
setWritePending(true);
|
||||||
try {
|
try {
|
||||||
await api.deleteAlbum(genealogyId, albumId);
|
await api.deleteAlbum(genealogyId, albumId);
|
||||||
@@ -727,7 +741,7 @@
|
|||||||
var photo = photosById[photoId];
|
var photo = photosById[photoId];
|
||||||
|
|
||||||
if (writePending || !canEditContent || !genealogyId || !albumId || !normalizeId(photoId) || redirectUnauthorized(api)) return;
|
if (writePending || !canEditContent || !genealogyId || !albumId || !normalizeId(photoId) || redirectUnauthorized(api)) return;
|
||||||
if (root.confirm && !root.confirm('确认删除“' + (photo && photo.photoTitle || '该照片') + '”吗?删除后不可恢复。')) return;
|
if (!await confirmAction('确认删除“' + (photo && photo.photoTitle || '该照片') + '”吗?删除后不可恢复。')) return;
|
||||||
setWritePending(true);
|
setWritePending(true);
|
||||||
try {
|
try {
|
||||||
await api.deleteAlbumPhoto(genealogyId, albumId, photoId);
|
await api.deleteAlbumPhoto(genealogyId, albumId, photoId);
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var MediaDisplay = root.MediaDisplay || (typeof require === 'function' ? require('./media-display.js') : null);
|
||||||
|
|
||||||
function escapeHtml(value) {
|
function escapeHtml(value) {
|
||||||
return String(value === undefined || value === null ? '' : value)
|
return String(value === undefined || value === null ? '' : value)
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
@@ -52,14 +54,19 @@
|
|||||||
var source = item || {};
|
var source = item || {};
|
||||||
var promotionId = normalizeId(source.promotionId);
|
var promotionId = normalizeId(source.promotionId);
|
||||||
var promotionTitle = text(source.promotionTitle);
|
var promotionTitle = text(source.promotionTitle);
|
||||||
|
var coverFile = MediaDisplay && MediaDisplay.normalizeFileAccess(source.coverFile);
|
||||||
|
var promotion;
|
||||||
|
|
||||||
if (!promotionId || !promotionTitle || String(source.status) !== '0') return null;
|
if (!promotionId || !promotionTitle || String(source.status) !== '0') return null;
|
||||||
return {
|
if (source.coverFile !== undefined && source.coverFile !== null && !coverFile) return null;
|
||||||
|
promotion = {
|
||||||
promotionId: promotionId,
|
promotionId: promotionId,
|
||||||
promotionTitle: promotionTitle,
|
promotionTitle: promotionTitle,
|
||||||
promotionDesc: text(source.promotionDesc),
|
promotionDesc: text(source.promotionDesc),
|
||||||
targetUrl: normalizeTargetUrl(source.targetUrl)
|
targetUrl: normalizeTargetUrl(source.targetUrl)
|
||||||
};
|
};
|
||||||
|
if (coverFile) promotion.coverFile = coverFile;
|
||||||
|
return promotion;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePromotionList(data) {
|
function normalizePromotionList(data) {
|
||||||
@@ -71,7 +78,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderPromotionCard(item) {
|
function renderPromotionCard(item) {
|
||||||
var content = '<div><h3>' + escapeHtml(item.promotionTitle) + '</h3>' +
|
var content = (MediaDisplay ? MediaDisplay.renderImage(item.coverFile, {
|
||||||
|
alt: item.promotionTitle + '封面', className: 'promotion-cover-media'
|
||||||
|
}) : '') + '<div><h3>' + escapeHtml(item.promotionTitle) + '</h3>' +
|
||||||
'<p>' + escapeHtml(item.promotionDesc) + '</p></div>';
|
'<p>' + escapeHtml(item.promotionDesc) + '</p></div>';
|
||||||
|
|
||||||
if (!item.targetUrl) {
|
if (!item.targetUrl) {
|
||||||
@@ -138,7 +147,7 @@
|
|||||||
setPromotionState(list, 'empty', '当前暂无应用推广。');
|
setPromotionState(list, 'empty', '当前暂无应用推广。');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
list.innerHTML = renderPromotionList(items);
|
list.innerHTML = items.map(renderPromotionCard).join('');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isUnauthorized(error)) {
|
if (isUnauthorized(error)) {
|
||||||
if (api.clearToken) api.clearToken();
|
if (api.clearToken) api.clearToken();
|
||||||
|
|||||||
@@ -13,6 +13,15 @@
|
|||||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
function confirmAction(message) {
|
||||||
|
if (root.ProfileUI && typeof root.ProfileUI.confirmAction === 'function') {
|
||||||
|
return root.ProfileUI.confirmAction(message);
|
||||||
|
}
|
||||||
|
return Promise.resolve(!root['confirm'] || root['confirm'](message));
|
||||||
|
}
|
||||||
|
|
||||||
|
var MediaDisplay = root.MediaDisplay || (typeof require === 'function' ? require('./media-display.js') : null);
|
||||||
|
|
||||||
var documentRef = root.document;
|
var documentRef = root.document;
|
||||||
var articlesById = Object.create(null);
|
var articlesById = Object.create(null);
|
||||||
var writePending = false;
|
var writePending = false;
|
||||||
@@ -119,7 +128,7 @@
|
|||||||
var articleId = normalizeId(item && item.articleId);
|
var articleId = normalizeId(item && item.articleId);
|
||||||
var genealogyId = normalizeId(item && item.genealogyId);
|
var genealogyId = normalizeId(item && item.genealogyId);
|
||||||
var categoryId = normalizeNullableId(item && item.categoryId);
|
var categoryId = normalizeNullableId(item && item.categoryId);
|
||||||
var coverOssId = normalizeNullableId(item && item.coverOssId);
|
var coverFile = MediaDisplay && MediaDisplay.normalizeFileAccess(item && item.coverFile);
|
||||||
var articleTitle = stringValue(item && item.articleTitle).trim();
|
var articleTitle = stringValue(item && item.articleTitle).trim();
|
||||||
var articleContent = stringValue(item && item.articleContent).trim();
|
var articleContent = stringValue(item && item.articleContent).trim();
|
||||||
var viewCount = optionalSafeInteger(item && item.viewCount);
|
var viewCount = optionalSafeInteger(item && item.viewCount);
|
||||||
@@ -129,7 +138,7 @@
|
|||||||
|
|
||||||
if (!articleId || !genealogyId || !articleTitle || !articleContent) return null;
|
if (!articleId || !genealogyId || !articleTitle || !articleContent) return null;
|
||||||
if (item.categoryId !== undefined && item.categoryId !== null && item.categoryId !== '' && !categoryId) return null;
|
if (item.categoryId !== undefined && item.categoryId !== null && item.categoryId !== '' && !categoryId) return null;
|
||||||
if (item.coverOssId !== undefined && item.coverOssId !== null && item.coverOssId !== '' && !coverOssId) return null;
|
if (item.coverFile !== undefined && item.coverFile !== null && !coverFile) return null;
|
||||||
if (item.viewCount !== undefined && item.viewCount !== null && item.viewCount !== '' && viewCount === undefined) return null;
|
if (item.viewCount !== undefined && item.viewCount !== null && item.viewCount !== '' && viewCount === undefined) return null;
|
||||||
if (item.sortOrder !== undefined && item.sortOrder !== null && item.sortOrder !== '' && sortOrder === undefined) return null;
|
if (item.sortOrder !== undefined && item.sortOrder !== null && item.sortOrder !== '' && sortOrder === undefined) return null;
|
||||||
if (status !== '0' && status !== '1') return null;
|
if (status !== '0' && status !== '1') return null;
|
||||||
@@ -151,7 +160,7 @@
|
|||||||
remark: stringValue(item.remark)
|
remark: stringValue(item.remark)
|
||||||
};
|
};
|
||||||
if (categoryId) article.categoryId = categoryId;
|
if (categoryId) article.categoryId = categoryId;
|
||||||
if (coverOssId) article.coverOssId = coverOssId;
|
if (coverFile) article.coverFile = coverFile;
|
||||||
if (viewCount !== undefined) article.viewCount = viewCount;
|
if (viewCount !== undefined) article.viewCount = viewCount;
|
||||||
if (sortOrder !== undefined) article.sortOrder = sortOrder;
|
if (sortOrder !== undefined) article.sortOrder = sortOrder;
|
||||||
return article;
|
return article;
|
||||||
@@ -207,10 +216,12 @@
|
|||||||
if (canEditContent) {
|
if (canEditContent) {
|
||||||
actions = '<div class="bottom-actions"><a class="btn ghost" href="' +
|
actions = '<div class="bottom-actions"><a class="btn ghost" href="' +
|
||||||
escapeHtml(buildEditUrl(article.genealogyId, article.articleId)) + '">编辑谱文</a>' +
|
escapeHtml(buildEditUrl(article.genealogyId, article.articleId)) + '">编辑谱文</a>' +
|
||||||
'<button class="btn ghost" type="button" data-article-delete-id="' +
|
'<button class="btn danger" type="button" data-article-delete-id="' +
|
||||||
escapeHtml(article.articleId) + '">删除谱文</button></div>';
|
escapeHtml(article.articleId) + '">删除谱文</button></div>';
|
||||||
}
|
}
|
||||||
return '<div class="form-like article-detail">' +
|
return (MediaDisplay ? MediaDisplay.renderImage(article.coverFile, {
|
||||||
|
alt: article.articleTitle + '封面', className: 'article-cover-media'
|
||||||
|
}) : '') + '<div class="form-like article-detail">' +
|
||||||
detailValue('标题', article.articleTitle) +
|
detailValue('标题', article.articleTitle) +
|
||||||
detailValue('摘要', article.articleSummary) +
|
detailValue('摘要', article.articleSummary) +
|
||||||
detailValue('正文', article.articleContent) +
|
detailValue('正文', article.articleContent) +
|
||||||
@@ -556,7 +567,7 @@
|
|||||||
var article = articlesById[articleId];
|
var article = articlesById[articleId];
|
||||||
|
|
||||||
if (writePending || !canEditContent || !genealogyId || !normalizeId(articleId) || redirectUnauthorized(api)) return;
|
if (writePending || !canEditContent || !genealogyId || !normalizeId(articleId) || redirectUnauthorized(api)) return;
|
||||||
if (root.confirm && !root.confirm('确认删除“' + (article ? article.articleTitle : '该谱文') + '”吗?删除后不可恢复。')) return;
|
if (!await confirmAction('确认删除“' + (article ? article.articleTitle : '该谱文') + '”吗?删除后不可恢复。')) return;
|
||||||
setWritePending(true);
|
setWritePending(true);
|
||||||
try {
|
try {
|
||||||
await api.deleteArticle(genealogyId, articleId);
|
await api.deleteArticle(genealogyId, articleId);
|
||||||
|
|||||||
@@ -13,6 +13,15 @@
|
|||||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
function confirmAction(message) {
|
||||||
|
if (root.ProfileUI && typeof root.ProfileUI.confirmAction === 'function') {
|
||||||
|
return root.ProfileUI.confirmAction(message);
|
||||||
|
}
|
||||||
|
return Promise.resolve(!root['confirm'] || root['confirm'](message));
|
||||||
|
}
|
||||||
|
|
||||||
|
var MediaDisplay = root.MediaDisplay || (typeof require === 'function' ? require('./media-display.js') : null);
|
||||||
|
|
||||||
var documentRef = root.document;
|
var documentRef = root.document;
|
||||||
var ceremoniesById = Object.create(null);
|
var ceremoniesById = Object.create(null);
|
||||||
var giftsById = Object.create(null);
|
var giftsById = Object.create(null);
|
||||||
@@ -212,7 +221,7 @@
|
|||||||
var ceremonyId = normalizeId(item && item.ceremonyId);
|
var ceremonyId = normalizeId(item && item.ceremonyId);
|
||||||
var genealogyId = normalizeId(item && item.genealogyId);
|
var genealogyId = normalizeId(item && item.genealogyId);
|
||||||
var sponsorUserId = normalizeNullableId(item && item.sponsorUserId);
|
var sponsorUserId = normalizeNullableId(item && item.sponsorUserId);
|
||||||
var coverOssId = normalizeNullableId(item && item.coverOssId);
|
var coverFile = MediaDisplay && MediaDisplay.normalizeFileAccess(item && item.coverFile);
|
||||||
var ceremonyType = stringValue(item && item.ceremonyType).trim();
|
var ceremonyType = stringValue(item && item.ceremonyType).trim();
|
||||||
var ceremonyTitle = stringValue(item && item.ceremonyTitle).trim();
|
var ceremonyTitle = stringValue(item && item.ceremonyTitle).trim();
|
||||||
var longitude = optionalFiniteNumber(item && item.longitude);
|
var longitude = optionalFiniteNumber(item && item.longitude);
|
||||||
@@ -225,7 +234,7 @@
|
|||||||
|
|
||||||
if (!ceremonyId || !genealogyId || !ceremonyType || !ceremonyTitle) return null;
|
if (!ceremonyId || !genealogyId || !ceremonyType || !ceremonyTitle) return null;
|
||||||
if (item.sponsorUserId !== undefined && item.sponsorUserId !== null && item.sponsorUserId !== '' && !sponsorUserId) return null;
|
if (item.sponsorUserId !== undefined && item.sponsorUserId !== null && item.sponsorUserId !== '' && !sponsorUserId) return null;
|
||||||
if (item.coverOssId !== undefined && item.coverOssId !== null && item.coverOssId !== '' && !coverOssId) return null;
|
if (item.coverFile !== undefined && item.coverFile !== null && !coverFile) return null;
|
||||||
if ((item.longitude !== undefined && item.longitude !== null && item.longitude !== '' && longitude === undefined) ||
|
if ((item.longitude !== undefined && item.longitude !== null && item.longitude !== '' && longitude === undefined) ||
|
||||||
(item.latitude !== undefined && item.latitude !== null && item.latitude !== '' && latitude === undefined)) return null;
|
(item.latitude !== undefined && item.latitude !== null && item.latitude !== '' && latitude === undefined)) return null;
|
||||||
if ((longitude === undefined) !== (latitude === undefined)) return null;
|
if ((longitude === undefined) !== (latitude === undefined)) return null;
|
||||||
@@ -251,7 +260,7 @@
|
|||||||
remark: stringValue(item.remark)
|
remark: stringValue(item.remark)
|
||||||
};
|
};
|
||||||
if (sponsorUserId) ceremony.sponsorUserId = sponsorUserId;
|
if (sponsorUserId) ceremony.sponsorUserId = sponsorUserId;
|
||||||
if (coverOssId) ceremony.coverOssId = coverOssId;
|
if (coverFile) ceremony.coverFile = coverFile;
|
||||||
if (longitude !== undefined) ceremony.longitude = longitude;
|
if (longitude !== undefined) ceremony.longitude = longitude;
|
||||||
if (latitude !== undefined) ceremony.latitude = latitude;
|
if (latitude !== undefined) ceremony.latitude = latitude;
|
||||||
if (giftCount !== undefined) ceremony.giftCount = giftCount;
|
if (giftCount !== undefined) ceremony.giftCount = giftCount;
|
||||||
@@ -399,10 +408,12 @@
|
|||||||
if (canEditContent) {
|
if (canEditContent) {
|
||||||
actions = '<div class="bottom-actions"><a class="btn ghost" href="' +
|
actions = '<div class="bottom-actions"><a class="btn ghost" href="' +
|
||||||
escapeHtml(buildEditUrl(ceremony.genealogyId, ceremony.ceremonyId)) + '">编辑活动</a>' +
|
escapeHtml(buildEditUrl(ceremony.genealogyId, ceremony.ceremonyId)) + '">编辑活动</a>' +
|
||||||
'<button class="btn ghost" type="button" data-ceremony-delete-id="' +
|
'<button class="btn danger" type="button" data-ceremony-delete-id="' +
|
||||||
escapeHtml(ceremony.ceremonyId) + '">删除活动</button></div>';
|
escapeHtml(ceremony.ceremonyId) + '">删除活动</button></div>';
|
||||||
}
|
}
|
||||||
return '<div class="form-like ceremony-detail">' +
|
return (MediaDisplay ? MediaDisplay.renderImage(ceremony.coverFile, {
|
||||||
|
alt: ceremony.ceremonyTitle + '封面', className: 'ceremony-cover-media'
|
||||||
|
}) : '') + '<div class="form-like ceremony-detail">' +
|
||||||
detailValue('活动标题', ceremony.ceremonyTitle) +
|
detailValue('活动标题', ceremony.ceremonyTitle) +
|
||||||
detailValue('活动类型', ceremony.ceremonyType) +
|
detailValue('活动类型', ceremony.ceremonyType) +
|
||||||
detailValue('活动说明', ceremony.ceremonyDesc) +
|
detailValue('活动说明', ceremony.ceremonyDesc) +
|
||||||
@@ -920,10 +931,18 @@
|
|||||||
var api = getApi();
|
var api = getApi();
|
||||||
var genealogyId = requireGenealogyId();
|
var genealogyId = requireGenealogyId();
|
||||||
var ceremonyId = getCurrentCeremonyId();
|
var ceremonyId = getCurrentCeremonyId();
|
||||||
|
var availableInviteeInputs;
|
||||||
var body;
|
var body;
|
||||||
|
|
||||||
if (writePending || !canEditContent || !genealogyId || !ceremonyId || redirectUnauthorized(api)) return;
|
if (writePending || !canEditContent || !genealogyId || !ceremonyId || redirectUnauthorized(api)) return;
|
||||||
body = buildInviteesBody(queryAll('[data-invitee-user-id]:checked').map(function (input) {
|
availableInviteeInputs = queryAll('[data-invitee-user-id]');
|
||||||
|
if (!availableInviteeInputs.length) {
|
||||||
|
setStatus('[data-ceremony-invitee-status]', '当前没有可邀请的家谱成员', 'empty');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
body = buildInviteesBody(availableInviteeInputs.filter(function (input) {
|
||||||
|
return input.checked;
|
||||||
|
}).map(function (input) {
|
||||||
return input.getAttribute('data-invitee-user-id');
|
return input.getAttribute('data-invitee-user-id');
|
||||||
}));
|
}));
|
||||||
setWritePending(true);
|
setWritePending(true);
|
||||||
@@ -950,7 +969,7 @@
|
|||||||
var ceremony = ceremoniesById[ceremonyId] || currentCeremony;
|
var ceremony = ceremoniesById[ceremonyId] || currentCeremony;
|
||||||
|
|
||||||
if (writePending || !canEditContent || !genealogyId || !normalizeId(ceremonyId) || redirectUnauthorized(api)) return;
|
if (writePending || !canEditContent || !genealogyId || !normalizeId(ceremonyId) || redirectUnauthorized(api)) return;
|
||||||
if (root.confirm && !root.confirm('确认删除“' + (ceremony ? ceremony.ceremonyTitle : '该活动') + '”吗?删除后不可恢复。')) return;
|
if (!await confirmAction('确认删除“' + (ceremony ? ceremony.ceremonyTitle : '该活动') + '”吗?删除后不可恢复。')) return;
|
||||||
setWritePending(true);
|
setWritePending(true);
|
||||||
try {
|
try {
|
||||||
await api.deleteCeremony(genealogyId, ceremonyId);
|
await api.deleteCeremony(genealogyId, ceremonyId);
|
||||||
@@ -971,7 +990,7 @@
|
|||||||
var gift = giftsById[giftId];
|
var gift = giftsById[giftId];
|
||||||
|
|
||||||
if (writePending || !canEditContent || !genealogyId || !ceremonyId || !normalizeId(giftId) || redirectUnauthorized(api)) return;
|
if (writePending || !canEditContent || !genealogyId || !ceremonyId || !normalizeId(giftId) || redirectUnauthorized(api)) return;
|
||||||
if (root.confirm && !root.confirm('确认删除“' + (gift && (gift.giverName || gift.giverNickName) || '该祭品') + '”的记录吗?')) return;
|
if (!await confirmAction('确认删除“' + (gift && (gift.giverName || gift.giverNickName) || '该祭品') + '”的记录吗?')) return;
|
||||||
setWritePending(true);
|
setWritePending(true);
|
||||||
try {
|
try {
|
||||||
await api.deleteCeremonyGift(genealogyId, ceremonyId, giftId);
|
await api.deleteCeremonyGift(genealogyId, ceremonyId, giftId);
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
(function (root, factory) {
|
||||||
|
if (typeof module === 'object' && module.exports) {
|
||||||
|
module.exports = factory(root);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
root.EarningPages = factory(root);
|
||||||
|
if (root.document) root.document.addEventListener('DOMContentLoaded', root.EarningPages.init);
|
||||||
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function confirmAction(message) {
|
||||||
|
if (root.ProfileUI && typeof root.ProfileUI.confirmAction === 'function') {
|
||||||
|
return root.ProfileUI.confirmAction(message);
|
||||||
|
}
|
||||||
|
return Promise.resolve(!root['confirm'] || root['confirm'](message));
|
||||||
|
}
|
||||||
|
|
||||||
|
var WITHDRAWAL_STATUS = {
|
||||||
|
PENDING: '待审核', APPROVED: '已通过', PAYING: '打款中', PAID: '已到账',
|
||||||
|
REJECTED: '已驳回', FAILED: '打款失败', CANCELLED: '已取消'
|
||||||
|
};
|
||||||
|
var ENTRY_TYPE = {
|
||||||
|
PAYMENT_REWARD: '推广奖励', PAYMENT_REFUND_REVERSAL: '退款冲销', WITHDRAWAL_FREEZE: '提现冻结',
|
||||||
|
WITHDRAWAL_CANCEL: '取消提现', WITHDRAWAL_REJECT: '提现驳回', WITHDRAWAL_PAID: '提现到账',
|
||||||
|
WITHDRAWAL_FAILED: '提现失败'
|
||||||
|
};
|
||||||
|
|
||||||
|
function escapeHtml(value) {
|
||||||
|
return String(value === undefined || value === null ? '' : value)
|
||||||
|
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"').replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeMoney(value) {
|
||||||
|
var text = String(value === undefined || value === null ? '' : value).trim();
|
||||||
|
return /^(?:0|[1-9][0-9]*)(?:\.[0-9]{1,2})?$/.test(text) ? text : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRequestId() {
|
||||||
|
if (root.crypto && typeof root.crypto.randomUUID === 'function') return root.crypto.randomUUID();
|
||||||
|
return 'pc-' + Date.now() + '-' + Math.random().toString(16).slice(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function moneyToCents(value) {
|
||||||
|
var text = normalizeMoney(value);
|
||||||
|
var parts;
|
||||||
|
|
||||||
|
if (!text) return null;
|
||||||
|
parts = text.split('.');
|
||||||
|
return BigInt(parts[0]) * 100n + BigInt((parts[1] || '').padEnd(2, '0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildWithdrawalBody(values, requestId) {
|
||||||
|
var source = values || {};
|
||||||
|
return {
|
||||||
|
requestId: String(requestId || '').trim(),
|
||||||
|
amount: normalizeMoney(source.amount),
|
||||||
|
payoutQrOssId: String(source.payoutQrOssId || '').trim(),
|
||||||
|
payoutAccountName: String(source.payoutAccountName || '').trim()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateWithdrawalBody(body, availableAmount) {
|
||||||
|
if (!body.requestId) return '提现请求号生成失败,请刷新页面重试';
|
||||||
|
if (!body.amount || Number(body.amount) <= 0) return '请输入有效提现金额,最多两位小数';
|
||||||
|
if (moneyToCents(availableAmount) !== null && moneyToCents(body.amount) > moneyToCents(availableAmount)) {
|
||||||
|
return '提现金额不能超过可用余额';
|
||||||
|
}
|
||||||
|
if (!/^[1-9][0-9]*$/.test(body.payoutQrOssId)) return '请上传收款码';
|
||||||
|
if (!body.payoutAccountName) return '请填写收款人姓名';
|
||||||
|
if (body.payoutAccountName.length > 64) return '收款人姓名不能超过 64 个字符';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSummary(value) {
|
||||||
|
var source = value || {};
|
||||||
|
return '<div class="module-grid three"><div class="module-card"><h3>可用收益</h3><strong>¥' +
|
||||||
|
escapeHtml(source.availableAmount || '0.00') + '</strong></div><div class="module-card"><h3>冻结金额</h3><strong>¥' +
|
||||||
|
escapeHtml(source.frozenAmount || '0.00') + '</strong></div><div class="module-card"><h3>最低提现</h3><strong>' +
|
||||||
|
escapeHtml(source.minimumWithdrawal ? '¥' + source.minimumWithdrawal : '以提交校验为准') + '</strong></div></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLedger(page) {
|
||||||
|
var rows = page && Array.isArray(page.rows) ? page.rows : [];
|
||||||
|
if (!rows.length) return '<div class="api-empty">当前没有收益流水</div>';
|
||||||
|
return rows.map(function (item) {
|
||||||
|
return '<article class="module-row"><div><h3>' + escapeHtml(ENTRY_TYPE[item.entryType] || item.entryType || '收益变动') +
|
||||||
|
'</h3><p>' + escapeHtml([item.createTime, item.remark].filter(Boolean).join(' · ')) +
|
||||||
|
'</p></div><strong>' + escapeHtml(String(item.availableDelta || '0.00')) + '</strong></article>';
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderWithdrawals(page) {
|
||||||
|
var rows = page && Array.isArray(page.rows) ? page.rows : [];
|
||||||
|
if (!rows.length) return '<div class="api-empty">当前没有提现记录</div>';
|
||||||
|
return rows.map(function (item) {
|
||||||
|
var id = String(item.withdrawalId || '').trim();
|
||||||
|
return '<article class="module-row"><div><h3>提现 ¥' + escapeHtml(item.amount || '0.00') +
|
||||||
|
'</h3><p>' + escapeHtml([item.withdrawalNo, item.payoutAccountName, item.createTime, item.auditRemark, item.failureReason].filter(Boolean).join(' · ')) +
|
||||||
|
'</p></div><span class="pill">' + escapeHtml(WITHDRAWAL_STATUS[item.withdrawalStatus] || item.withdrawalStatus || '未知') + '</span>' +
|
||||||
|
(item.withdrawalStatus === 'PENDING' && /^[1-9][0-9]*$/.test(id) ? '<button class="btn outline" type="button" data-withdrawal-cancel="' + escapeHtml(id) + '">取消</button>' : '') + '</article>';
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
var page = root.document && root.document.querySelector('[data-earning-page]');
|
||||||
|
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
|
||||||
|
var summaryBox = page && page.querySelector('[data-earning-summary]');
|
||||||
|
var ledgerBox = page && page.querySelector('[data-earning-ledger]');
|
||||||
|
var withdrawalBox = page && page.querySelector('[data-earning-withdrawals]');
|
||||||
|
var form = page && page.querySelector('[data-withdrawal-form]');
|
||||||
|
var status = page && page.querySelector('[data-earning-status]');
|
||||||
|
var availableAmount = '';
|
||||||
|
var requestId = createRequestId();
|
||||||
|
var pending = false;
|
||||||
|
|
||||||
|
if (!page) return;
|
||||||
|
if (!api || !api.getToken || !api.getToken()) {
|
||||||
|
if (root.location) root.location.href = 'login.html';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(type, message) {
|
||||||
|
if (type) root.ProfileUI.setApiState(status, type, message);
|
||||||
|
else status.textContent = message || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadAll() {
|
||||||
|
var values = await Promise.all([
|
||||||
|
api.earningSummary(), api.earningLedger({ pageNum: 1, pageSize: 20 }),
|
||||||
|
api.earningWithdrawals({ pageNum: 1, pageSize: 20 })
|
||||||
|
]);
|
||||||
|
availableAmount = String(values[0] && values[0].availableAmount || '');
|
||||||
|
summaryBox.innerHTML = renderSummary(values[0]);
|
||||||
|
ledgerBox.innerHTML = renderLedger(values[1]);
|
||||||
|
withdrawalBox.innerHTML = renderWithdrawals(values[2]);
|
||||||
|
form.querySelector('[data-withdrawal-submit]').disabled = values[0] && values[0].withdrawalEnabled === false;
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
try { await loadAll(); setStatus('', '收益数据已加载'); }
|
||||||
|
catch (error) { setStatus('error', error.message || '收益数据读取失败'); return; }
|
||||||
|
|
||||||
|
form.addEventListener('submit', async function (event) {
|
||||||
|
var body;
|
||||||
|
var validation;
|
||||||
|
event.preventDefault();
|
||||||
|
if (pending) return;
|
||||||
|
body = buildWithdrawalBody({
|
||||||
|
amount: form.elements.amount.value,
|
||||||
|
payoutAccountName: form.elements.payoutAccountName.value,
|
||||||
|
payoutQrOssId: form.elements.payoutQrOssId.value
|
||||||
|
}, requestId);
|
||||||
|
validation = validateWithdrawalBody(body, availableAmount);
|
||||||
|
if (validation) return setStatus('error', validation);
|
||||||
|
pending = true; setStatus('loading', '正在提交提现申请…');
|
||||||
|
try {
|
||||||
|
await api.createEarningWithdrawal(body);
|
||||||
|
requestId = createRequestId(); form.reset(); form.elements.payoutQrOssId.value = '';
|
||||||
|
await loadAll(); setStatus('', '提现申请已提交');
|
||||||
|
} catch (error) { setStatus('error', error.message || '提现申请失败'); }
|
||||||
|
finally { pending = false; }
|
||||||
|
});
|
||||||
|
|
||||||
|
withdrawalBox.addEventListener('click', async function (event) {
|
||||||
|
var button = event.target.closest('[data-withdrawal-cancel]');
|
||||||
|
if (!button || pending || !await confirmAction('确认取消这笔待审核提现吗?')) return;
|
||||||
|
pending = true; setStatus('loading', '正在取消提现…');
|
||||||
|
try { await api.cancelEarningWithdrawal(button.getAttribute('data-withdrawal-cancel')); await loadAll(); setStatus('', '提现已取消'); }
|
||||||
|
catch (error) { setStatus('error', error.message || '取消提现失败'); }
|
||||||
|
finally { pending = false; }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
normalizeMoney: normalizeMoney,
|
||||||
|
createRequestId: createRequestId,
|
||||||
|
buildWithdrawalBody: buildWithdrawalBody,
|
||||||
|
validateWithdrawalBody: validateWithdrawalBody,
|
||||||
|
renderSummary: renderSummary,
|
||||||
|
renderLedger: renderLedger,
|
||||||
|
renderWithdrawals: renderWithdrawals,
|
||||||
|
init: init
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -13,6 +13,8 @@
|
|||||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var MediaDisplay = root.MediaDisplay || (typeof require === 'function' ? require('./media-display.js') : null);
|
||||||
|
|
||||||
function escapeHtml(value) {
|
function escapeHtml(value) {
|
||||||
return String(value === undefined || value === null ? '' : value)
|
return String(value === undefined || value === null ? '' : value)
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
@@ -48,13 +50,15 @@
|
|||||||
var expectedId = normalizeId(expectedGenealogyId);
|
var expectedId = normalizeId(expectedGenealogyId);
|
||||||
var memberCount = normalizeCount(source.memberCount);
|
var memberCount = normalizeCount(source.memberCount);
|
||||||
var personCount = normalizeCount(source.personCount);
|
var personCount = normalizeCount(source.personCount);
|
||||||
|
var coverFile = MediaDisplay && MediaDisplay.normalizeFileAccess(source.coverFile);
|
||||||
|
var overview;
|
||||||
|
|
||||||
if (!genealogyId || genealogyId !== expectedId ||
|
if (!genealogyId || genealogyId !== expectedId ||
|
||||||
!text(source.genealogyName) || String(source.status) !== '0' ||
|
!text(source.genealogyName) || String(source.status) !== '0' ||
|
||||||
memberCount === null || personCount === null) {
|
memberCount === null || personCount === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return {
|
overview = {
|
||||||
genealogyId: genealogyId,
|
genealogyId: genealogyId,
|
||||||
genealogyNo: text(source.genealogyNo),
|
genealogyNo: text(source.genealogyNo),
|
||||||
genealogyName: text(source.genealogyName),
|
genealogyName: text(source.genealogyName),
|
||||||
@@ -67,6 +71,9 @@
|
|||||||
canManage: source.canManage === true,
|
canManage: source.canManage === true,
|
||||||
canEditContent: source.canEditContent === true
|
canEditContent: source.canEditContent === true
|
||||||
};
|
};
|
||||||
|
if (source.coverFile !== undefined && source.coverFile !== null && !coverFile) return null;
|
||||||
|
if (coverFile) overview.coverFile = coverFile;
|
||||||
|
return overview;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderFamilyOverview(overview) {
|
function renderFamilyOverview(overview) {
|
||||||
@@ -79,7 +86,10 @@
|
|||||||
source.regionFullName ? '地区 ' + source.regionFullName : ''
|
source.regionFullName ? '地区 ' + source.regionFullName : ''
|
||||||
].filter(Boolean).join(' · ');
|
].filter(Boolean).join(' · ');
|
||||||
|
|
||||||
return '<h3>' + escapeHtml(source.genealogyName) + '</h3><p>' +
|
return (MediaDisplay ? MediaDisplay.renderImage(source.coverFile, {
|
||||||
|
alt: source.genealogyName + '封面',
|
||||||
|
className: 'family-cover-media'
|
||||||
|
}) : '') + '<h3>' + escapeHtml(source.genealogyName) + '</h3><p>' +
|
||||||
escapeHtml(details) + '</p><p>成员 ' + escapeHtml(source.memberCount) +
|
escapeHtml(details) + '</p><p>成员 ' + escapeHtml(source.memberCount) +
|
||||||
' 人 · 世系人物 ' + escapeHtml(source.personCount) + ' 人</p>';
|
' 人 · 世系人物 ' + escapeHtml(source.personCount) + ' 人</p>';
|
||||||
}
|
}
|
||||||
@@ -154,6 +164,7 @@
|
|||||||
var element = query('[data-family-home-status]');
|
var element = query('[data-family-home-status]');
|
||||||
|
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
|
element.hidden = !message;
|
||||||
if (!message) {
|
if (!message) {
|
||||||
element.innerHTML = '';
|
element.innerHTML = '';
|
||||||
return;
|
return;
|
||||||
@@ -239,7 +250,7 @@
|
|||||||
} else {
|
} else {
|
||||||
query('[data-lineage-home-tree]').innerHTML = treeHtml;
|
query('[data-lineage-home-tree]').innerHTML = treeHtml;
|
||||||
}
|
}
|
||||||
setHomeStatus('家谱概览已更新');
|
setHomeStatus('');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (shouldRedirectToLogin(api, error)) {
|
if (shouldRedirectToLogin(api, error)) {
|
||||||
redirectToLogin(api);
|
redirectToLogin(api);
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
(function (root, factory) {
|
||||||
|
if (typeof module === 'object' && module.exports) {
|
||||||
|
module.exports = factory(root);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
root.FamilyInvitePages = factory(root);
|
||||||
|
if (root.document) root.document.addEventListener('DOMContentLoaded', root.FamilyInvitePages.init);
|
||||||
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var STATUS_TEXT = { ACTIVE: '待使用', REDEEMED: '已使用', REVOKED: '已撤销', EXPIRED: '已过期' };
|
||||||
|
|
||||||
|
function escapeHtml(value) {
|
||||||
|
return String(value === undefined || value === null ? '' : value)
|
||||||
|
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"').replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeId(value) {
|
||||||
|
var text = String(value === undefined || value === null ? '' : value).trim();
|
||||||
|
return /^[1-9][0-9]*$/.test(text) ? text : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeInvitation(item) {
|
||||||
|
var source = item || {};
|
||||||
|
var inviteId = normalizeId(source.inviteId);
|
||||||
|
var genealogyId = normalizeId(source.genealogyId);
|
||||||
|
|
||||||
|
if (!inviteId || !genealogyId || !String(source.genealogyName || '').trim()) return null;
|
||||||
|
return {
|
||||||
|
inviteId: inviteId,
|
||||||
|
genealogyId: genealogyId,
|
||||||
|
genealogyName: String(source.genealogyName).trim(),
|
||||||
|
lineagePersonName: String(source.lineagePersonName || '').trim(),
|
||||||
|
status: String(source.status || '').toUpperCase(),
|
||||||
|
token: String(source.token || '').trim(),
|
||||||
|
expiresAt: String(source.expiresAt || '').trim(),
|
||||||
|
createTime: String(source.createTime || '').trim()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function invitationLink(token) {
|
||||||
|
var base;
|
||||||
|
|
||||||
|
if (!token) return '';
|
||||||
|
if (!root.location) return 'profile-invite.html?token=' + encodeURIComponent(token);
|
||||||
|
base = String(root.location.href || '').split('?')[0].split('#')[0];
|
||||||
|
return base + '?token=' + encodeURIComponent(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderInvitations(items) {
|
||||||
|
var list = Array.isArray(items) ? items.map(normalizeInvitation).filter(Boolean) : [];
|
||||||
|
|
||||||
|
if (!list.length) return '<div class="api-empty">还没有发出的邀请</div>';
|
||||||
|
return list.map(function (item) {
|
||||||
|
var canRevoke = item.status === 'ACTIVE';
|
||||||
|
return '<article class="module-row"><div><h3>' + escapeHtml(item.genealogyName) +
|
||||||
|
(item.lineagePersonName ? ' · ' + escapeHtml(item.lineagePersonName) : '') + '</h3><p>' +
|
||||||
|
escapeHtml([item.createTime, item.expiresAt ? '有效期至 ' + item.expiresAt : ''].filter(Boolean).join(' · ')) +
|
||||||
|
'</p></div><div class="row-actions"><span class="pill">' +
|
||||||
|
escapeHtml(STATUS_TEXT[item.status] || item.status || '未知') + '</span>' +
|
||||||
|
(canRevoke ? '<button class="btn outline" type="button" data-invite-revoke="' +
|
||||||
|
escapeHtml(item.inviteId) + '">撤销</button>' : '') + '</div></article>';
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirectToLogin(api) {
|
||||||
|
if (api && api.clearToken) api.clearToken();
|
||||||
|
if (root.location) root.location.href = 'login.html';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
var page = root.document && root.document.querySelector('[data-family-invite-page]');
|
||||||
|
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
|
||||||
|
var genealogyId = root.ProfileUI && root.ProfileUI.getGenealogyId ? root.ProfileUI.getGenealogyId() : '';
|
||||||
|
var form = page && page.querySelector('[data-invite-form]');
|
||||||
|
var personSelect = page && page.querySelector('[data-invite-person]');
|
||||||
|
var status = page && page.querySelector('[data-invite-status]');
|
||||||
|
var result = page && page.querySelector('[data-invite-result]');
|
||||||
|
var list = page && page.querySelector('[data-invite-list]');
|
||||||
|
var redeem = page && page.querySelector('[data-invite-redeem]');
|
||||||
|
var token = root.location ? new URLSearchParams(root.location.search).get('token') : '';
|
||||||
|
var pending = false;
|
||||||
|
|
||||||
|
if (!page) return;
|
||||||
|
if (!api || !api.getToken || !api.getToken()) return redirectToLogin(api);
|
||||||
|
if (token && redeem) {
|
||||||
|
form.hidden = true;
|
||||||
|
list.hidden = true;
|
||||||
|
redeem.hidden = false;
|
||||||
|
redeem.querySelector('[data-invite-token]').textContent = token;
|
||||||
|
try {
|
||||||
|
var preview = normalizeInvitation(await api.previewGenealogyInvitation(token));
|
||||||
|
if (!preview) throw new Error('邀请预览响应无效');
|
||||||
|
redeem.querySelector('h3').textContent = '接受“' + preview.genealogyName + '”邀请';
|
||||||
|
setStatus('', preview.expiresAt ? '邀请有效期至 ' + preview.expiresAt : '请确认是否加入该家谱');
|
||||||
|
} catch (error) {
|
||||||
|
setStatus('error', error.message || '邀请预览失败');
|
||||||
|
redeem.querySelector('[data-invite-redeem-submit]').disabled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
redeem.querySelector('[data-invite-redeem-submit]').addEventListener('click', async function () {
|
||||||
|
if (pending) return;
|
||||||
|
pending = true;
|
||||||
|
setStatus('loading', '正在接受邀请…');
|
||||||
|
try {
|
||||||
|
await api.redeemGenealogyInvitation({ token: token });
|
||||||
|
setStatus('', '邀请已接受,你已加入该家谱。');
|
||||||
|
} catch (error) {
|
||||||
|
setStatus('error', error.message || '接受邀请失败');
|
||||||
|
} finally { pending = false; }
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!genealogyId) {
|
||||||
|
root.ProfileUI.setApiState(status, 'error', '缺少家谱上下文,请从“我的家谱”重新进入。');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(type, message) {
|
||||||
|
if (type) root.ProfileUI.setApiState(status, type, message);
|
||||||
|
else status.textContent = message || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadList() {
|
||||||
|
var invitations = await api.myGenealogyInvitations();
|
||||||
|
list.innerHTML = renderInvitations(invitations);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var initial = await Promise.all([api.lineagePersonOptions(genealogyId), api.myGenealogyInvitations()]);
|
||||||
|
personSelect.innerHTML = '<option value="">不指定人物,加入后再关联</option>' +
|
||||||
|
(Array.isArray(initial[0]) ? initial[0] : []).map(function (item) {
|
||||||
|
var id = normalizeId(item.personId || item.lineagePersonId);
|
||||||
|
var name = String(item.displayName || item.personName || item.name || '').trim();
|
||||||
|
return id && name ? '<option value="' + escapeHtml(id) + '">' + escapeHtml(name) + '</option>' : '';
|
||||||
|
}).join('');
|
||||||
|
list.innerHTML = renderInvitations(initial[1]);
|
||||||
|
setStatus('', '可生成一次性邀请链接');
|
||||||
|
} catch (error) {
|
||||||
|
setStatus('error', error.message || '邀请服务读取失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
form.addEventListener('submit', async function (event) {
|
||||||
|
var body = {};
|
||||||
|
var created;
|
||||||
|
var link;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
if (pending) return;
|
||||||
|
if (personSelect.value) body.lineagePersonId = personSelect.value;
|
||||||
|
pending = true;
|
||||||
|
setStatus('loading', '正在生成邀请…');
|
||||||
|
try {
|
||||||
|
created = normalizeInvitation(await api.issueGenealogyInvitation(genealogyId, body));
|
||||||
|
if (!created || !created.token) throw new Error('邀请已创建,但响应缺少一次性令牌');
|
||||||
|
link = invitationLink(created.token);
|
||||||
|
result.hidden = false;
|
||||||
|
result.querySelector('[data-invite-link]').value = link;
|
||||||
|
setStatus('', '邀请已生成,请及时复制给家人。');
|
||||||
|
await loadList();
|
||||||
|
} catch (error) {
|
||||||
|
setStatus('error', error.message || '邀请生成失败');
|
||||||
|
} finally { pending = false; }
|
||||||
|
});
|
||||||
|
|
||||||
|
result.querySelector('[data-invite-copy]').addEventListener('click', async function () {
|
||||||
|
var value = result.querySelector('[data-invite-link]').value;
|
||||||
|
try {
|
||||||
|
await root.navigator.clipboard.writeText(value);
|
||||||
|
setStatus('', '邀请链接已复制');
|
||||||
|
} catch (error) {
|
||||||
|
result.querySelector('[data-invite-link]').select();
|
||||||
|
setStatus('', '请按 Ctrl+C 复制邀请链接');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
list.addEventListener('click', async function (event) {
|
||||||
|
var button = event.target.closest('[data-invite-revoke]');
|
||||||
|
if (!button || pending) return;
|
||||||
|
pending = true;
|
||||||
|
setStatus('loading', '正在撤销邀请…');
|
||||||
|
try {
|
||||||
|
await api.revokeGenealogyInvitation(button.getAttribute('data-invite-revoke'));
|
||||||
|
await loadList();
|
||||||
|
setStatus('', '邀请已撤销');
|
||||||
|
} catch (error) { setStatus('error', error.message || '撤销邀请失败'); }
|
||||||
|
finally { pending = false; }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
normalizeInvitation: normalizeInvitation,
|
||||||
|
invitationLink: invitationLink,
|
||||||
|
renderInvitations: renderInvitations,
|
||||||
|
init: init
|
||||||
|
};
|
||||||
|
});
|
||||||
+28
-7
@@ -14,6 +14,15 @@
|
|||||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
function confirmAction(message) {
|
||||||
|
if (root.ProfileUI && typeof root.ProfileUI.confirmAction === 'function') {
|
||||||
|
return root.ProfileUI.confirmAction(message);
|
||||||
|
}
|
||||||
|
return Promise.resolve(!root['confirm'] || root['confirm'](message));
|
||||||
|
}
|
||||||
|
|
||||||
|
var MediaDisplay = root.MediaDisplay || (typeof require === 'function' ? require('./media-display.js') : null);
|
||||||
|
|
||||||
var documentRef = root.document;
|
var documentRef = root.document;
|
||||||
var PAGE_SIZE = 20;
|
var PAGE_SIZE = 20;
|
||||||
var currentFeedPage = 1;
|
var currentFeedPage = 1;
|
||||||
@@ -339,7 +348,6 @@
|
|||||||
'publisherNickName',
|
'publisherNickName',
|
||||||
'publisherStatus',
|
'publisherStatus',
|
||||||
'feedType',
|
'feedType',
|
||||||
'mediaOssIds',
|
|
||||||
'likedByMe',
|
'likedByMe',
|
||||||
'likeCount',
|
'likeCount',
|
||||||
'commentCount',
|
'commentCount',
|
||||||
@@ -351,6 +359,8 @@
|
|||||||
'createTime',
|
'createTime',
|
||||||
'updateTime'
|
'updateTime'
|
||||||
]);
|
]);
|
||||||
|
var mediaFiles = MediaDisplay ? MediaDisplay.normalizeFileList(item && item.mediaFiles) : [];
|
||||||
|
if (mediaFiles.length) feed.mediaFiles = mediaFiles;
|
||||||
if (toId(item.genealogyId)) feed.genealogyId = toId(item.genealogyId);
|
if (toId(item.genealogyId)) feed.genealogyId = toId(item.genealogyId);
|
||||||
if (toId(item.publisherUserId)) feed.publisherUserId = toId(item.publisherUserId);
|
if (toId(item.publisherUserId)) feed.publisherUserId = toId(item.publisherUserId);
|
||||||
return feed;
|
return feed;
|
||||||
@@ -438,7 +448,7 @@
|
|||||||
|
|
||||||
function renderFeedRow(feed, genealogyId) {
|
function renderFeedRow(feed, genealogyId) {
|
||||||
var canDelete = viewerContext.userId && viewerContext.userId === feed.publisherUserId;
|
var canDelete = viewerContext.userId && viewerContext.userId === feed.publisherUserId;
|
||||||
var mediaCount = feed.mediaOssIds ? String(feed.mediaOssIds).split(',').filter(Boolean).length : 0;
|
var mediaCount = Array.isArray(feed.mediaFiles) ? feed.mediaFiles.length : 0;
|
||||||
var title = feed.publisherNickName || feed.genealogyName || '家族动态';
|
var title = feed.publisherNickName || feed.genealogyName || '家族动态';
|
||||||
var meta = [];
|
var meta = [];
|
||||||
var actions = '';
|
var actions = '';
|
||||||
@@ -458,11 +468,12 @@
|
|||||||
actions += '<a class="pill" href="' + escapeHtml(buildFeedUrl('edit', genealogyId, feed.feedId)) + '">编辑</a>';
|
actions += '<a class="pill" href="' + escapeHtml(buildFeedUrl('edit', genealogyId, feed.feedId)) + '">编辑</a>';
|
||||||
}
|
}
|
||||||
if (canDelete) {
|
if (canDelete) {
|
||||||
actions += '<button class="pill" type="button" data-feed-delete="' + escapeHtml(feed.feedId) + '">删除</button>';
|
actions += '<button class="pill is-danger" type="button" data-feed-delete="' + escapeHtml(feed.feedId) + '">删除</button>';
|
||||||
}
|
}
|
||||||
|
|
||||||
return '<article class="module-row feed-row" data-feed-id="' + escapeHtml(feed.feedId) + '">' +
|
return '<article class="module-row feed-row" data-feed-id="' + escapeHtml(feed.feedId) + '">' +
|
||||||
'<div><h3>' + escapeHtml(title) + '</h3><p>' + escapeHtml(feed.feedContent) + '</p>' +
|
'<div><h3>' + escapeHtml(title) + '</h3><p>' + escapeHtml(feed.feedContent) + '</p>' +
|
||||||
|
(MediaDisplay ? MediaDisplay.renderGallery(feed.mediaFiles, { label: '动态附件', className: 'feed-media-gallery' }) : '') +
|
||||||
'<p class="module-meta">' + escapeHtml(meta.join(' · ')) + '</p></div>' +
|
'<p class="module-meta">' + escapeHtml(meta.join(' · ')) + '</p></div>' +
|
||||||
'<div class="row-actions">' + actions + '</div></article>';
|
'<div class="row-actions">' + actions + '</div></article>';
|
||||||
}
|
}
|
||||||
@@ -520,7 +531,7 @@
|
|||||||
if (!comment.userDeleted) {
|
if (!comment.userDeleted) {
|
||||||
actions += '<button class="pill" type="button" data-feed-comment-reply="' + escapeHtml(comment.commentId) + '" data-feed-id="' + escapeHtml(feedId) + '">回复</button>';
|
actions += '<button class="pill" type="button" data-feed-comment-reply="' + escapeHtml(comment.commentId) + '" data-feed-id="' + escapeHtml(feedId) + '">回复</button>';
|
||||||
if (canDelete) {
|
if (canDelete) {
|
||||||
actions += '<button class="pill" type="button" data-feed-comment-delete="' + escapeHtml(comment.commentId) + '" data-feed-id="' + escapeHtml(feedId) + '">删除评论</button>';
|
actions += '<button class="pill is-danger" type="button" data-feed-comment-delete="' + escapeHtml(comment.commentId) + '" data-feed-id="' + escapeHtml(feedId) + '">删除评论</button>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasReplies(comment)) {
|
if (hasReplies(comment)) {
|
||||||
@@ -671,11 +682,12 @@
|
|||||||
actions += '<a class="pill" href="' + escapeHtml(buildFeedUrl('edit', genealogyId, feed.feedId)) + '">编辑</a>';
|
actions += '<a class="pill" href="' + escapeHtml(buildFeedUrl('edit', genealogyId, feed.feedId)) + '">编辑</a>';
|
||||||
}
|
}
|
||||||
if (canDelete) {
|
if (canDelete) {
|
||||||
actions += '<button class="pill" type="button" data-feed-delete="' + escapeHtml(feed.feedId) + '">删除</button>';
|
actions += '<button class="pill is-danger" type="button" data-feed-delete="' + escapeHtml(feed.feedId) + '">删除</button>';
|
||||||
}
|
}
|
||||||
container.setAttribute('data-feed-id', feed.feedId);
|
container.setAttribute('data-feed-id', feed.feedId);
|
||||||
container.innerHTML = '<h2>' + escapeHtml(feed.publisherNickName || feed.genealogyName || '家族动态') + '</h2>' +
|
container.innerHTML = '<h2>' + escapeHtml(feed.publisherNickName || feed.genealogyName || '家族动态') + '</h2>' +
|
||||||
'<p>' + escapeHtml(feed.feedContent) + '</p>' +
|
'<p>' + escapeHtml(feed.feedContent) + '</p>' +
|
||||||
|
(MediaDisplay ? MediaDisplay.renderGallery(feed.mediaFiles, { label: '动态附件', className: 'feed-media-gallery' }) : '') +
|
||||||
'<p class="module-meta">' +
|
'<p class="module-meta">' +
|
||||||
escapeHtml([
|
escapeHtml([
|
||||||
feed.createTime || '',
|
feed.createTime || '',
|
||||||
@@ -744,6 +756,15 @@
|
|||||||
var api = getApi();
|
var api = getApi();
|
||||||
var genealogyId;
|
var genealogyId;
|
||||||
var feedId = getCurrentFeedId();
|
var feedId = getCurrentFeedId();
|
||||||
|
var isEditing = Boolean(feedId);
|
||||||
|
var editorTitle = isEditing ? '编辑动态' : '发布动态';
|
||||||
|
|
||||||
|
if (root.document) root.document.title = editorTitle + ' - 个人中心 - 代代相传';
|
||||||
|
if (query('[data-feed-editor-kicker]')) query('[data-feed-editor-kicker]').textContent = editorTitle;
|
||||||
|
if (query('[data-feed-editor-title]')) query('[data-feed-editor-title]').textContent = editorTitle;
|
||||||
|
queryAll('[data-feed-editor-action]').forEach(function (button) {
|
||||||
|
button.textContent = isEditing ? '保存修改' : '发布动态';
|
||||||
|
});
|
||||||
|
|
||||||
if (redirectUnauthorized(api)) return;
|
if (redirectUnauthorized(api)) return;
|
||||||
genealogyId = requireGenealogyId();
|
genealogyId = requireGenealogyId();
|
||||||
@@ -905,7 +926,7 @@
|
|||||||
if (redirectUnauthorized(api)) return;
|
if (redirectUnauthorized(api)) return;
|
||||||
genealogyId = requireGenealogyId();
|
genealogyId = requireGenealogyId();
|
||||||
if (!genealogyId || !feedId) return;
|
if (!genealogyId || !feedId) return;
|
||||||
if (root.confirm && !root.confirm('确认删除这条动态吗?')) return;
|
if (!await confirmAction('确认删除这条动态吗?')) return;
|
||||||
return withActionLock('delete:' + feedId, async function () {
|
return withActionLock('delete:' + feedId, async function () {
|
||||||
try {
|
try {
|
||||||
await api.deleteFeed(genealogyId, feedId);
|
await api.deleteFeed(genealogyId, feedId);
|
||||||
@@ -1044,7 +1065,7 @@
|
|||||||
if (redirectUnauthorized(api)) return;
|
if (redirectUnauthorized(api)) return;
|
||||||
genealogyId = requireGenealogyId();
|
genealogyId = requireGenealogyId();
|
||||||
if (!genealogyId || !feedId || !commentId) return;
|
if (!genealogyId || !feedId || !commentId) return;
|
||||||
if (root.confirm && !root.confirm('确认删除这条评论吗?')) return;
|
if (!await confirmAction('确认删除这条评论吗?')) return;
|
||||||
return withActionLock('delete-comment:' + commentId, async function () {
|
return withActionLock('delete-comment:' + commentId, async function () {
|
||||||
try {
|
try {
|
||||||
await api.deleteFeedComment(genealogyId, feedId, commentId);
|
await api.deleteFeedComment(genealogyId, feedId, commentId);
|
||||||
|
|||||||
+47
-13
@@ -13,12 +13,7 @@
|
|||||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var FEEDBACK_TYPES = {
|
var FEEDBACK_OPTION_STATES = ['ACTIVE', 'DISABLED', 'UNKNOWN'];
|
||||||
advice: '意见建议',
|
|
||||||
bug: '问题反馈',
|
|
||||||
complaint: '投诉反馈',
|
|
||||||
other: '其他反馈'
|
|
||||||
};
|
|
||||||
var HANDLE_STATUS = {
|
var HANDLE_STATUS = {
|
||||||
'0': '待处理',
|
'0': '待处理',
|
||||||
'1': '处理中',
|
'1': '处理中',
|
||||||
@@ -68,26 +63,41 @@
|
|||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateFeedbackBody(body) {
|
function validateFeedbackBody(body, allowedFeedbackTypes) {
|
||||||
var source = body || {};
|
var source = body || {};
|
||||||
|
var allowedTypes = Array.isArray(allowedFeedbackTypes) ? allowedFeedbackTypes : [];
|
||||||
|
|
||||||
if (!optionalText(source.feedbackContent)) return '请填写反馈内容';
|
if (!optionalText(source.feedbackContent)) return '请填写反馈内容';
|
||||||
if (source.feedbackType && !Object.prototype.hasOwnProperty.call(FEEDBACK_TYPES, source.feedbackType)) {
|
if (source.feedbackType && allowedTypes.length && allowedTypes.indexOf(source.feedbackType) < 0) {
|
||||||
return '反馈类型无效';
|
return '反馈类型无效';
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeFeedbackTypeOptions(data) {
|
||||||
|
if (!Array.isArray(data)) return [];
|
||||||
|
return data.reduce(function (options, item) {
|
||||||
|
var source = item || {};
|
||||||
|
var value = optionalText(source.value);
|
||||||
|
var label = optionalText(source.label);
|
||||||
|
|
||||||
|
if (value && label && source.enabled === true) options.push({ value: value, label: label });
|
||||||
|
return options;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeFeedback(item) {
|
function normalizeFeedback(item) {
|
||||||
var source = item || {};
|
var source = item || {};
|
||||||
var feedbackId = normalizeId(source.feedbackId);
|
var feedbackId = normalizeId(source.feedbackId);
|
||||||
var feedbackType = optionalText(source.feedbackType);
|
var feedbackType = optionalText(source.feedbackType);
|
||||||
|
var feedbackTypeLabel = optionalText(source.feedbackTypeLabel) || feedbackType;
|
||||||
|
var feedbackTypeOptionState = optionalText(source.feedbackTypeOptionState);
|
||||||
var feedbackContent = optionalText(source.feedbackContent);
|
var feedbackContent = optionalText(source.feedbackContent);
|
||||||
var handleStatus = String(source.handleStatus === undefined || source.handleStatus === null ? '' : source.handleStatus);
|
var handleStatus = String(source.handleStatus === undefined || source.handleStatus === null ? '' : source.handleStatus);
|
||||||
var status = String(source.status === undefined || source.status === null ? '' : source.status);
|
var status = String(source.status === undefined || source.status === null ? '' : source.status);
|
||||||
|
|
||||||
if (!feedbackId || !feedbackContent ||
|
if (!feedbackId || !feedbackContent || !feedbackType || !feedbackTypeLabel ||
|
||||||
!Object.prototype.hasOwnProperty.call(FEEDBACK_TYPES, feedbackType) ||
|
FEEDBACK_OPTION_STATES.indexOf(feedbackTypeOptionState) < 0 ||
|
||||||
!Object.prototype.hasOwnProperty.call(HANDLE_STATUS, handleStatus) ||
|
!Object.prototype.hasOwnProperty.call(HANDLE_STATUS, handleStatus) ||
|
||||||
['0', '1'].indexOf(status) < 0) {
|
['0', '1'].indexOf(status) < 0) {
|
||||||
return null;
|
return null;
|
||||||
@@ -95,6 +105,8 @@
|
|||||||
return {
|
return {
|
||||||
feedbackId: feedbackId,
|
feedbackId: feedbackId,
|
||||||
feedbackType: feedbackType,
|
feedbackType: feedbackType,
|
||||||
|
feedbackTypeLabel: feedbackTypeLabel,
|
||||||
|
feedbackTypeOptionState: feedbackTypeOptionState,
|
||||||
feedbackContent: feedbackContent,
|
feedbackContent: feedbackContent,
|
||||||
contactInfo: optionalText(source.contactInfo),
|
contactInfo: optionalText(source.contactInfo),
|
||||||
handleStatus: handleStatus,
|
handleStatus: handleStatus,
|
||||||
@@ -142,7 +154,7 @@
|
|||||||
return items.map(function (item) {
|
return items.map(function (item) {
|
||||||
return '<a class="module-row" href="' +
|
return '<a class="module-row" href="' +
|
||||||
escapeHtml(buildFeedbackDetailUrl(item.feedbackId, detailPage)) +
|
escapeHtml(buildFeedbackDetailUrl(item.feedbackId, detailPage)) +
|
||||||
'"><div><h3>' + escapeHtml(FEEDBACK_TYPES[item.feedbackType]) +
|
'"><div><h3>' + escapeHtml(item.feedbackTypeLabel) +
|
||||||
'</h3><p>' + escapeHtml(item.feedbackContent) +
|
'</h3><p>' + escapeHtml(item.feedbackContent) +
|
||||||
'</p></div><span class="pill">' + HANDLE_STATUS[item.handleStatus] +
|
'</p></div><span class="pill">' + HANDLE_STATUS[item.handleStatus] +
|
||||||
'</span></a>';
|
'</span></a>';
|
||||||
@@ -163,7 +175,7 @@
|
|||||||
].filter(Boolean).join('');
|
].filter(Boolean).join('');
|
||||||
|
|
||||||
return '<article class="module-panel"><div class="module-kicker">' +
|
return '<article class="module-panel"><div class="module-kicker">' +
|
||||||
escapeHtml(FEEDBACK_TYPES[feedback.feedbackType]) +
|
escapeHtml(feedback.feedbackTypeLabel) +
|
||||||
'</div><h2>问题说明</h2><p>' + escapeHtml(feedback.feedbackContent) +
|
'</div><h2>问题说明</h2><p>' + escapeHtml(feedback.feedbackContent) +
|
||||||
'</p><dl class="detail-list">' + details + '</dl></article>';
|
'</p><dl class="detail-list">' + details + '</dl></article>';
|
||||||
}
|
}
|
||||||
@@ -225,6 +237,18 @@
|
|||||||
button.textContent = pending ? label : button.dataset.defaultLabel;
|
button.textContent = pending ? label : button.dataset.defaultLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadFeedbackTypeOptions(api, select) {
|
||||||
|
var options = normalizeFeedbackTypeOptions(await api.businessDictionary('gen_feedback_type'));
|
||||||
|
|
||||||
|
if (!options.length) throw new Error('反馈类型选项响应无效');
|
||||||
|
if (select) {
|
||||||
|
select.innerHTML = '<option value="">请选择反馈类型(可选)</option>' + options.map(function (item) {
|
||||||
|
return '<option value="' + escapeHtml(item.value) + '">' + escapeHtml(item.label) + '</option>';
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
async function readMyFeedback(api) {
|
async function readMyFeedback(api) {
|
||||||
var data = await api.myFeedback();
|
var data = await api.myFeedback();
|
||||||
var items = normalizeFeedbackList(data);
|
var items = normalizeFeedbackList(data);
|
||||||
@@ -242,6 +266,8 @@
|
|||||||
var page = root.document && root.document.querySelector('[data-feedback-page]');
|
var page = root.document && root.document.querySelector('[data-feedback-page]');
|
||||||
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
|
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
|
||||||
var pageMode = page && page.getAttribute('data-feedback-page');
|
var pageMode = page && page.getAttribute('data-feedback-page');
|
||||||
|
var feedbackTypeSelect = form && form.querySelector('[data-feedback-type-options]');
|
||||||
|
var allowedFeedbackTypes = [];
|
||||||
var writePending = false;
|
var writePending = false;
|
||||||
|
|
||||||
if (!form) return;
|
if (!form) return;
|
||||||
@@ -249,6 +275,13 @@
|
|||||||
redirectToLogin(api);
|
redirectToLogin(api);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
allowedFeedbackTypes = (await loadFeedbackTypeOptions(api, feedbackTypeSelect)).map(function (item) {
|
||||||
|
return item.value;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (feedbackTypeSelect) feedbackTypeSelect.innerHTML = '<option value="">暂不选择反馈类型</option>';
|
||||||
|
}
|
||||||
|
|
||||||
async function loadList() {
|
async function loadList() {
|
||||||
var items;
|
var items;
|
||||||
@@ -290,7 +323,7 @@
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (writePending) return;
|
if (writePending) return;
|
||||||
body = buildFeedbackBody(getFormValues(form));
|
body = buildFeedbackBody(getFormValues(form));
|
||||||
validation = validateFeedbackBody(body);
|
validation = validateFeedbackBody(body, allowedFeedbackTypes);
|
||||||
if (validation) {
|
if (validation) {
|
||||||
setStatus('error', validation);
|
setStatus('error', validation);
|
||||||
return;
|
return;
|
||||||
@@ -416,6 +449,7 @@
|
|||||||
getFeedbackId: getFeedbackId,
|
getFeedbackId: getFeedbackId,
|
||||||
buildFeedbackBody: buildFeedbackBody,
|
buildFeedbackBody: buildFeedbackBody,
|
||||||
validateFeedbackBody: validateFeedbackBody,
|
validateFeedbackBody: validateFeedbackBody,
|
||||||
|
normalizeFeedbackTypeOptions: normalizeFeedbackTypeOptions,
|
||||||
normalizeFeedback: normalizeFeedback,
|
normalizeFeedback: normalizeFeedback,
|
||||||
normalizeFeedbackList: normalizeFeedbackList,
|
normalizeFeedbackList: normalizeFeedbackList,
|
||||||
findFeedbackById: findFeedbackById,
|
findFeedbackById: findFeedbackById,
|
||||||
|
|||||||
@@ -352,14 +352,19 @@
|
|||||||
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
|
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
|
||||||
var target = getTargetPage();
|
var target = getTargetPage();
|
||||||
var container = root.document && root.document.querySelector('[data-genealogy-list="mine"]');
|
var container = root.document && root.document.querySelector('[data-genealogy-list="mine"]');
|
||||||
|
var sortSelect = root.document && root.document.querySelector('[data-genealogy-sort-select]');
|
||||||
|
var sortUp = root.document && root.document.querySelector('[data-genealogy-sort-up]');
|
||||||
|
var sortDown = root.document && root.document.querySelector('[data-genealogy-sort-down]');
|
||||||
|
var sortSave = root.document && root.document.querySelector('[data-genealogy-sort-save]');
|
||||||
var result;
|
var result;
|
||||||
|
var sortPending = false;
|
||||||
|
|
||||||
if (!status) return;
|
if (!status) return;
|
||||||
if (shouldRedirectToLogin(api)) {
|
if (shouldRedirectToLogin(api)) {
|
||||||
redirectToLogin(api);
|
redirectToLogin(api);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!api || !api.genealogyQuota || !api.genealogiesMine) {
|
if (!api || !api.genealogyQuota || !api.genealogiesMine || !api.saveGenealogyOrder) {
|
||||||
setEntryStatus('家谱入口接口初始化失败,请刷新后重试。', 'error');
|
setEntryStatus('家谱入口接口初始化失败,请刷新后重试。', 'error');
|
||||||
setGenealogyListState(container, 'error', '家谱列表接口初始化失败,请刷新后重试。');
|
setGenealogyListState(container, 'error', '家谱列表接口初始化失败,请刷新后重试。');
|
||||||
return;
|
return;
|
||||||
@@ -369,6 +374,15 @@
|
|||||||
try {
|
try {
|
||||||
result = await api.genealogiesMine();
|
result = await api.genealogiesMine();
|
||||||
renderGenealogies(container, result, target);
|
renderGenealogies(container, result, target);
|
||||||
|
if (sortSelect) {
|
||||||
|
var sortItems = Array.isArray(result) ? result.map(normalizeGenealogy).filter(Boolean) : [];
|
||||||
|
sortSelect.innerHTML = sortItems.map(function (item) {
|
||||||
|
return '<option value="' + escapeHtml(item.genealogyId) + '">' + escapeHtml(item.genealogyName) + '</option>';
|
||||||
|
}).join('');
|
||||||
|
[sortSelect, sortUp, sortDown, sortSave].forEach(function (control) {
|
||||||
|
if (control) control.disabled = sortItems.length < 2;
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (shouldRedirectToLogin(api, error)) {
|
if (shouldRedirectToLogin(api, error)) {
|
||||||
redirectToLogin(api);
|
redirectToLogin(api);
|
||||||
@@ -380,6 +394,41 @@
|
|||||||
getApiStateType(error) === 'forbidden' ? '当前账号无权查看我的家谱。' : '无法读取我的家谱,请稍后重试。'
|
getApiStateType(error) === 'forbidden' ? '当前账号无权查看我的家谱。' : '无法读取我的家谱,请稍后重试。'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function moveSelected(offset) {
|
||||||
|
var index;
|
||||||
|
var targetIndex;
|
||||||
|
var option;
|
||||||
|
|
||||||
|
if (!sortSelect || sortPending) return;
|
||||||
|
index = sortSelect.selectedIndex;
|
||||||
|
targetIndex = index + offset;
|
||||||
|
if (index < 0 || targetIndex < 0 || targetIndex >= sortSelect.options.length) return;
|
||||||
|
option = sortSelect.options[index];
|
||||||
|
if (offset < 0) sortSelect.insertBefore(option, sortSelect.options[targetIndex]);
|
||||||
|
else sortSelect.insertBefore(sortSelect.options[targetIndex], option);
|
||||||
|
sortSelect.selectedIndex = targetIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortUp) sortUp.addEventListener('click', function () { moveSelected(-1); });
|
||||||
|
if (sortDown) sortDown.addEventListener('click', function () { moveSelected(1); });
|
||||||
|
if (sortSave) sortSave.addEventListener('click', async function () {
|
||||||
|
if (sortPending || !sortSelect) return;
|
||||||
|
sortPending = true;
|
||||||
|
[sortSelect, sortUp, sortDown, sortSave].forEach(function (control) { if (control) control.disabled = true; });
|
||||||
|
setEntryStatus('正在保存家谱顺序…', 'loading');
|
||||||
|
try {
|
||||||
|
await api.saveGenealogyOrder(Array.prototype.map.call(sortSelect.options, function (option) { return option.value; }));
|
||||||
|
setEntryStatus('家谱顺序已保存');
|
||||||
|
} catch (error) {
|
||||||
|
setEntryStatus(error.message || '家谱顺序保存失败,请稍后重试。', getApiStateType(error));
|
||||||
|
} finally {
|
||||||
|
sortPending = false;
|
||||||
|
[sortSelect, sortUp, sortDown, sortSave].forEach(function (control) {
|
||||||
|
if (control) control.disabled = sortSelect.options.length < 2;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
result = await api.genealogyQuota();
|
result = await api.genealogyQuota();
|
||||||
setEntryStatus(buildStatus(result));
|
setEntryStatus(buildStatus(result));
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
(function (root, factory) {
|
||||||
|
if (typeof module === 'object' && module.exports) {
|
||||||
|
module.exports = factory(root);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
root.GenealogyLifecyclePages = factory(root);
|
||||||
|
if (root.document) root.document.addEventListener('DOMContentLoaded', root.GenealogyLifecyclePages.init);
|
||||||
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function confirmAction(message) {
|
||||||
|
if (root.ProfileUI && typeof root.ProfileUI.confirmAction === 'function') {
|
||||||
|
return root.ProfileUI.confirmAction(message);
|
||||||
|
}
|
||||||
|
return Promise.resolve(!root['confirm'] || root['confirm'](message));
|
||||||
|
}
|
||||||
|
|
||||||
|
var DISABLED_REASON_TEXT = {
|
||||||
|
NOT_OWNER: '只有谱主可以永久删除家谱',
|
||||||
|
NO_VERIFIED_MOBILE: '谱主账号尚未绑定已验证手机号',
|
||||||
|
ACTIVE_PAYMENT: '当前家谱存在未完成的支付单',
|
||||||
|
DELETION_IN_PROGRESS: '永久删除任务正在处理中'
|
||||||
|
};
|
||||||
|
|
||||||
|
function normalizeCapability(value) {
|
||||||
|
var source = value || {};
|
||||||
|
return {
|
||||||
|
canDeletePermanently: source.canDeletePermanently === true,
|
||||||
|
verifiedMobileMasked: String(source.verifiedMobileMasked || '').trim(),
|
||||||
|
disabledReasons: Array.isArray(source.disabledReasons) ? source.disabledReasons.map(String) : []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function capabilityMessage(capability) {
|
||||||
|
var data = normalizeCapability(capability);
|
||||||
|
if (data.canDeletePermanently) {
|
||||||
|
return data.verifiedMobileMasked ? '验证码将发送至 ' + data.verifiedMobileMasked : '当前账号具备永久删除资格';
|
||||||
|
}
|
||||||
|
return data.disabledReasons.map(function (code) {
|
||||||
|
return DISABLED_REASON_TEXT[code] || code;
|
||||||
|
}).join(';') || '当前家谱不允许永久删除';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
var page = root.document && root.document.querySelector('[data-genealogy-lifecycle-page]');
|
||||||
|
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
|
||||||
|
var genealogyId = root.ProfileUI && root.ProfileUI.getGenealogyId ? root.ProfileUI.getGenealogyId() : '';
|
||||||
|
var status = page && page.querySelector('[data-lifecycle-status]');
|
||||||
|
var archive = page && page.querySelector('[data-genealogy-archive]');
|
||||||
|
var restore = page && page.querySelector('[data-genealogy-restore]');
|
||||||
|
var deleteForm = page && page.querySelector('[data-genealogy-delete-form]');
|
||||||
|
var sendCode = page && page.querySelector('[data-genealogy-delete-code]');
|
||||||
|
var submitDelete = page && page.querySelector('[data-genealogy-delete-submit]');
|
||||||
|
var nameInput = page && page.querySelector('[data-genealogy-delete-name]');
|
||||||
|
var codeInput = page && page.querySelector('[data-genealogy-delete-sms]');
|
||||||
|
var capability;
|
||||||
|
var pending = false;
|
||||||
|
|
||||||
|
if (!page) return;
|
||||||
|
if (!api || !api.getToken || !api.getToken()) {
|
||||||
|
if (root.location) root.location.href = 'login.html';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!genealogyId) {
|
||||||
|
root.ProfileUI.setApiState(status, 'error', '缺少家谱上下文,请从“我的家谱”重新进入。');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(type, message) {
|
||||||
|
if (type) root.ProfileUI.setApiState(status, type, message);
|
||||||
|
else status.textContent = message || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPending(value) {
|
||||||
|
pending = value;
|
||||||
|
[archive, restore, sendCode, submitDelete].forEach(function (button) {
|
||||||
|
if (button) button.disabled = value ||
|
||||||
|
((button === submitDelete || button === sendCode) && capability && !capability.canDeletePermanently);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var initial = await Promise.all([api.genealogyDetail(genealogyId), api.genealogyDeletionCapability(genealogyId)]);
|
||||||
|
capability = normalizeCapability(initial[1]);
|
||||||
|
nameInput.placeholder = '请输入“' + String(initial[0] && initial[0].genealogyName || '家谱全名') + '”确认';
|
||||||
|
submitDelete.disabled = !capability.canDeletePermanently;
|
||||||
|
sendCode.disabled = !capability.canDeletePermanently;
|
||||||
|
setStatus(capability.canDeletePermanently ? '' : 'forbidden', capabilityMessage(capability));
|
||||||
|
} catch (error) {
|
||||||
|
setStatus('error', error.message || '家谱维护能力读取失败');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
archive.addEventListener('click', async function () {
|
||||||
|
if (pending || !await confirmAction('归档后家谱将停止日常维护,确认归档吗?')) return;
|
||||||
|
setPending(true); setStatus('loading', '正在归档家谱…');
|
||||||
|
try { await api.archiveGenealogy(genealogyId); setStatus('', '家谱已归档,需要时可在此恢复。'); }
|
||||||
|
catch (error) { setStatus('error', error.message || '归档失败'); }
|
||||||
|
finally { setPending(false); }
|
||||||
|
});
|
||||||
|
|
||||||
|
restore.addEventListener('click', async function () {
|
||||||
|
if (pending) return;
|
||||||
|
setPending(true); setStatus('loading', '正在恢复家谱…');
|
||||||
|
try { await api.restoreGenealogy(genealogyId); setStatus('', '家谱已恢复。'); }
|
||||||
|
catch (error) { setStatus('error', error.message || '恢复失败'); }
|
||||||
|
finally { setPending(false); }
|
||||||
|
});
|
||||||
|
|
||||||
|
sendCode.addEventListener('click', async function () {
|
||||||
|
if (pending || !capability.canDeletePermanently) return;
|
||||||
|
setPending(true); setStatus('loading', '正在发送验证码…');
|
||||||
|
try { await api.sendGenealogyDeletionCode(genealogyId, {}); setStatus('', '验证码已发送至谱主手机号。'); }
|
||||||
|
catch (error) { setStatus('error', error.message || '验证码发送失败'); }
|
||||||
|
finally { setPending(false); }
|
||||||
|
});
|
||||||
|
|
||||||
|
deleteForm.addEventListener('submit', async function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (pending || !capability.canDeletePermanently) return;
|
||||||
|
if (!nameInput.value.trim() || !codeInput.value.trim()) {
|
||||||
|
setStatus('error', '请填写家谱全名和短信验证码');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!await confirmAction('永久删除不可恢复,确认继续吗?')) return;
|
||||||
|
setPending(true); setStatus('loading', '正在提交永久删除任务…');
|
||||||
|
try {
|
||||||
|
await api.permanentlyDeleteGenealogy(genealogyId, {
|
||||||
|
genealogyName: nameInput.value.trim(), smsCode: codeInput.value.trim()
|
||||||
|
});
|
||||||
|
if (root.ProfileUI && root.ProfileUI.clearCurrentGenealogyContext) root.ProfileUI.clearCurrentGenealogyContext();
|
||||||
|
if (root.location) root.location.href = 'profile-families.html';
|
||||||
|
} catch (error) { setStatus('error', error.message || '永久删除提交失败'); }
|
||||||
|
finally { setPending(false); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { normalizeCapability: normalizeCapability, capabilityMessage: capabilityMessage, init: init };
|
||||||
|
});
|
||||||
@@ -14,6 +14,13 @@
|
|||||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
function confirmAction(message) {
|
||||||
|
if (root.ProfileUI && typeof root.ProfileUI.confirmAction === 'function') {
|
||||||
|
return root.ProfileUI.confirmAction(message);
|
||||||
|
}
|
||||||
|
return Promise.resolve(!root['confirm'] || root['confirm'](message));
|
||||||
|
}
|
||||||
|
|
||||||
var documentRef = root.document;
|
var documentRef = root.document;
|
||||||
var poemsById = Object.create(null);
|
var poemsById = Object.create(null);
|
||||||
var lastPreviewSignature = '';
|
var lastPreviewSignature = '';
|
||||||
@@ -695,7 +702,7 @@
|
|||||||
setBatchStatus('请先预览当前内容,再保存');
|
setBatchStatus('请先预览当前内容,再保存');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (root.confirm && !root.confirm(buildBatchConfirmMessage(body.disableMissing))) return;
|
if (!await confirmAction(buildBatchConfirmMessage(body.disableMissing))) return;
|
||||||
setBatchStatus('正在保存…');
|
setBatchStatus('正在保存…');
|
||||||
setWritePending(true);
|
setWritePending(true);
|
||||||
try {
|
try {
|
||||||
|
|||||||
+28
-10
@@ -13,6 +13,15 @@
|
|||||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
function confirmAction(message) {
|
||||||
|
if (root.ProfileUI && typeof root.ProfileUI.confirmAction === 'function') {
|
||||||
|
return root.ProfileUI.confirmAction(message);
|
||||||
|
}
|
||||||
|
return Promise.resolve(!root['confirm'] || root['confirm'](message));
|
||||||
|
}
|
||||||
|
|
||||||
|
var MediaDisplay = root.MediaDisplay || (typeof require === 'function' ? require('./media-display.js') : null);
|
||||||
|
|
||||||
var documentRef = root.document;
|
var documentRef = root.document;
|
||||||
var recordsById = Object.create(null);
|
var recordsById = Object.create(null);
|
||||||
var canEditContent = false;
|
var canEditContent = false;
|
||||||
@@ -161,14 +170,13 @@
|
|||||||
var lineagePersonId = normalizeId(item && item.lineagePersonId);
|
var lineagePersonId = normalizeId(item && item.lineagePersonId);
|
||||||
var title = String(item && item.recordTitle || '').trim();
|
var title = String(item && item.recordTitle || '').trim();
|
||||||
var status = stringValue(item && item.status);
|
var status = stringValue(item && item.status);
|
||||||
var mediaOssIds = stringValue(item && item.mediaOssIds);
|
var mediaFiles = MediaDisplay ? MediaDisplay.normalizeFileList(item && item.mediaFiles) : [];
|
||||||
var sortOrder = optionalSafeInteger(item && item.sortOrder);
|
var sortOrder = optionalSafeInteger(item && item.sortOrder);
|
||||||
var record;
|
var record;
|
||||||
|
|
||||||
if (!recordId || !genealogyId || !title || (status !== '0' && status !== '1')) return null;
|
if (!recordId || !genealogyId || !title || (status !== '0' && status !== '1')) return null;
|
||||||
if (item.appUserId !== undefined && item.appUserId !== null && item.appUserId !== '' && !appUserId) return null;
|
if (item.appUserId !== undefined && item.appUserId !== null && item.appUserId !== '' && !appUserId) return null;
|
||||||
if (item.lineagePersonId !== undefined && item.lineagePersonId !== null && item.lineagePersonId !== '' && !lineagePersonId) return null;
|
if (item.lineagePersonId !== undefined && item.lineagePersonId !== null && item.lineagePersonId !== '' && !lineagePersonId) return null;
|
||||||
if (mediaOssIds && !/^[1-9][0-9]*(,[1-9][0-9]*)*$/.test(mediaOssIds)) return null;
|
|
||||||
if (item.sortOrder !== undefined && item.sortOrder !== null && item.sortOrder !== '' && sortOrder === undefined) return null;
|
if (item.sortOrder !== undefined && item.sortOrder !== null && item.sortOrder !== '' && sortOrder === undefined) return null;
|
||||||
record = {
|
record = {
|
||||||
recordId: recordId,
|
recordId: recordId,
|
||||||
@@ -185,10 +193,10 @@
|
|||||||
recordContent: stringValue(item.recordContent),
|
recordContent: stringValue(item.recordContent),
|
||||||
recordDate: stringValue(item.recordDate),
|
recordDate: stringValue(item.recordDate),
|
||||||
remindTime: stringValue(item.remindTime),
|
remindTime: stringValue(item.remindTime),
|
||||||
mediaOssIds: mediaOssIds,
|
|
||||||
status: status,
|
status: status,
|
||||||
remark: stringValue(item.remark)
|
remark: stringValue(item.remark)
|
||||||
};
|
};
|
||||||
|
if (mediaFiles.length) record.mediaFiles = mediaFiles;
|
||||||
if (appUserId) record.appUserId = appUserId;
|
if (appUserId) record.appUserId = appUserId;
|
||||||
if (lineagePersonId) record.lineagePersonId = lineagePersonId;
|
if (lineagePersonId) record.lineagePersonId = lineagePersonId;
|
||||||
if (sortOrder !== undefined) record.sortOrder = sortOrder;
|
if (sortOrder !== undefined) record.sortOrder = sortOrder;
|
||||||
@@ -238,8 +246,8 @@
|
|||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachmentCount(mediaOssIds) {
|
function attachmentCount(mediaFiles) {
|
||||||
return mediaOssIds ? mediaOssIds.split(',').filter(Boolean).length : 0;
|
return Array.isArray(mediaFiles) ? mediaFiles.length : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildListUrl(genealogyId, recordId) {
|
function buildListUrl(genealogyId, recordId) {
|
||||||
@@ -285,14 +293,15 @@
|
|||||||
var actions = '';
|
var actions = '';
|
||||||
|
|
||||||
if (!record) return stateHtml('empty', '请选择一条成长记录查看详情');
|
if (!record) return stateHtml('empty', '请选择一条成长记录查看详情');
|
||||||
count = attachmentCount(record.mediaOssIds);
|
count = attachmentCount(record.mediaFiles);
|
||||||
if (canEditContent) {
|
if (canEditContent) {
|
||||||
actions = '<div class="bottom-actions"><a class="btn ghost" href="' +
|
actions = '<div class="bottom-actions"><a class="btn ghost" href="' +
|
||||||
escapeHtml(buildEditUrl(record.genealogyId, record.recordId)) + '">编辑记录</a>' +
|
escapeHtml(buildEditUrl(record.genealogyId, record.recordId)) + '">编辑记录</a>' +
|
||||||
'<button class="btn ghost" type="button" data-growth-delete-id="' +
|
'<button class="btn danger" type="button" data-growth-delete-id="' +
|
||||||
escapeHtml(record.recordId) + '">删除记录</button></div>';
|
escapeHtml(record.recordId) + '">删除记录</button></div>';
|
||||||
}
|
}
|
||||||
return '<div class="form-like growth-detail">' +
|
return (MediaDisplay ? MediaDisplay.renderGallery(record.mediaFiles, { label: '成长记录附件' }) : '') +
|
||||||
|
'<div class="form-like growth-detail">' +
|
||||||
detailValue('标题', record.recordTitle) +
|
detailValue('标题', record.recordTitle) +
|
||||||
detailValue('关联人物', record.lineagePersonName || record.lineagePersonNo) +
|
detailValue('关联人物', record.lineagePersonName || record.lineagePersonNo) +
|
||||||
detailValue('记录类型', record.recordType) +
|
detailValue('记录类型', record.recordType) +
|
||||||
@@ -664,7 +673,7 @@
|
|||||||
var record = recordsById[recordId];
|
var record = recordsById[recordId];
|
||||||
|
|
||||||
if (writePending || !canEditContent || !genealogyId || !normalizeId(recordId) || redirectUnauthorized(api)) return;
|
if (writePending || !canEditContent || !genealogyId || !normalizeId(recordId) || redirectUnauthorized(api)) return;
|
||||||
if (root.confirm && !root.confirm('确认删除“' + (record ? record.recordTitle : '该成长记录') + '”吗?删除后不可恢复,并会释放附件引用。')) return;
|
if (!await confirmAction('确认删除“' + (record ? record.recordTitle : '该成长记录') + '”吗?删除后不可恢复,并会释放附件引用。')) return;
|
||||||
setWritePending(true);
|
setWritePending(true);
|
||||||
try {
|
try {
|
||||||
await api.deleteGrowthRecord(genealogyId, recordId);
|
await api.deleteGrowthRecord(genealogyId, recordId);
|
||||||
@@ -694,10 +703,19 @@
|
|||||||
var detailButton = event.target.closest('[data-growth-detail-id]');
|
var detailButton = event.target.closest('[data-growth-detail-id]');
|
||||||
var deleteButton = event.target.closest('[data-growth-delete-id]');
|
var deleteButton = event.target.closest('[data-growth-delete-id]');
|
||||||
var clearMediaButton = event.target.closest('[data-growth-clear-media]');
|
var clearMediaButton = event.target.closest('[data-growth-clear-media]');
|
||||||
|
var recordId;
|
||||||
|
var genealogyId;
|
||||||
|
|
||||||
if (detailButton) {
|
if (detailButton) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
loadGrowthDetail(detailButton.getAttribute('data-growth-detail-id'));
|
recordId = detailButton.getAttribute('data-growth-detail-id');
|
||||||
|
genealogyId = requireGenealogyId();
|
||||||
|
if (root.history && genealogyId) root.history.replaceState(null, '', buildListUrl(genealogyId, recordId));
|
||||||
|
loadGrowthDetail(recordId).then(function (record) {
|
||||||
|
if (record && root.ProfileUI && root.ProfileUI.revealDetail) {
|
||||||
|
root.ProfileUI.revealDetail(query('[data-growth-detail-heading]'));
|
||||||
|
}
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (deleteButton) {
|
if (deleteButton) {
|
||||||
|
|||||||
@@ -13,6 +13,13 @@
|
|||||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
function confirmAction(message) {
|
||||||
|
if (root.ProfileUI && typeof root.ProfileUI.confirmAction === 'function') {
|
||||||
|
return root.ProfileUI.confirmAction(message);
|
||||||
|
}
|
||||||
|
return Promise.resolve(!root['confirm'] || root['confirm'](message));
|
||||||
|
}
|
||||||
|
|
||||||
var APPLY_STATUS = {
|
var APPLY_STATUS = {
|
||||||
'0': '待审核',
|
'0': '待审核',
|
||||||
'1': '已通过',
|
'1': '已通过',
|
||||||
@@ -403,7 +410,7 @@
|
|||||||
if (!button || !list.contains(button) || writePending) return;
|
if (!button || !list.contains(button) || writePending) return;
|
||||||
applyId = normalizeId(button.getAttribute('data-join-cancel-id'));
|
applyId = normalizeId(button.getAttribute('data-join-cancel-id'));
|
||||||
if (!applyId) return;
|
if (!applyId) return;
|
||||||
if (root.confirm && !root.confirm('确认撤销这条加入申请吗?')) return;
|
if (!await confirmAction('确认撤销这条加入申请吗?')) return;
|
||||||
writePending = true;
|
writePending = true;
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
setText('[data-my-join-applies-status]', '正在撤销申请…', 'loading');
|
setText('[data-my-join-applies-status]', '正在撤销申请…', 'loading');
|
||||||
|
|||||||
@@ -14,6 +14,15 @@
|
|||||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
function confirmAction(message) {
|
||||||
|
if (root.ProfileUI && typeof root.ProfileUI.confirmAction === 'function') {
|
||||||
|
return root.ProfileUI.confirmAction(message);
|
||||||
|
}
|
||||||
|
return Promise.resolve(!root['confirm'] || root['confirm'](message));
|
||||||
|
}
|
||||||
|
|
||||||
|
var MediaDisplay = root.MediaDisplay || (typeof require === 'function' ? require('./media-display.js') : null);
|
||||||
|
|
||||||
var documentRef = root.document;
|
var documentRef = root.document;
|
||||||
var selectedPersonId = '';
|
var selectedPersonId = '';
|
||||||
var editingPersonId = '';
|
var editingPersonId = '';
|
||||||
@@ -200,7 +209,7 @@
|
|||||||
].forEach(function (field) {
|
].forEach(function (field) {
|
||||||
if (item && item[field] !== undefined && item[field] !== null) person[field] = String(item[field]);
|
if (item && item[field] !== undefined && item[field] !== null) person[field] = String(item[field]);
|
||||||
});
|
});
|
||||||
['appUserId', 'genealogyId', 'fatherId', 'motherId', 'avatarOssId'].forEach(function (field) {
|
['appUserId', 'genealogyId', 'fatherId', 'motherId'].forEach(function (field) {
|
||||||
if (item && item[field] !== undefined && item[field] !== null) {
|
if (item && item[field] !== undefined && item[field] !== null) {
|
||||||
var value = normalizeId(item[field]);
|
var value = normalizeId(item[field]);
|
||||||
|
|
||||||
@@ -212,6 +221,10 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (invalidId) return null;
|
if (invalidId) return null;
|
||||||
|
if (item && item.avatarFile !== undefined && item.avatarFile !== null) {
|
||||||
|
person.avatarFile = MediaDisplay && MediaDisplay.normalizeFileAccess(item.avatarFile);
|
||||||
|
if (!person.avatarFile) return null;
|
||||||
|
}
|
||||||
if (toIntegerOrUndefined(item && item.generation) !== undefined) person.generation = toIntegerOrUndefined(item.generation);
|
if (toIntegerOrUndefined(item && item.generation) !== undefined) person.generation = toIntegerOrUndefined(item.generation);
|
||||||
if (toIntegerOrUndefined(item && item.sortOrder) !== undefined) person.sortOrder = toIntegerOrUndefined(item.sortOrder);
|
if (toIntegerOrUndefined(item && item.sortOrder) !== undefined) person.sortOrder = toIntegerOrUndefined(item.sortOrder);
|
||||||
return person;
|
return person;
|
||||||
@@ -315,6 +328,7 @@
|
|||||||
var selected = person.personId === selectedPersonId ? ' is-selected' : '';
|
var selected = person.personId === selectedPersonId ? ' is-selected' : '';
|
||||||
|
|
||||||
return '<button class="module-row lineage-row' + selected + '" type="button" data-lineage-person="' + escapeHtml(person.personId) + '">' +
|
return '<button class="module-row lineage-row' + selected + '" type="button" data-lineage-person="' + escapeHtml(person.personId) + '">' +
|
||||||
|
(MediaDisplay ? MediaDisplay.renderAvatar(person.avatarFile, { name: person.name, className: 'lineage-avatar-media' }) : '') +
|
||||||
'<div><h3>' + escapeHtml(person.name) + '</h3><p>' + escapeHtml(getPersonSummary(person)) + '</p></div>' +
|
'<div><h3>' + escapeHtml(person.name) + '</h3><p>' + escapeHtml(getPersonSummary(person)) + '</p></div>' +
|
||||||
'<span class="pill">查看</span></button>';
|
'<span class="pill">查看</span></button>';
|
||||||
}).join('');
|
}).join('');
|
||||||
@@ -338,6 +352,7 @@
|
|||||||
if (spouses) branches += '<ul><li><span>配偶</span><ul>' + spouses + '</ul></li></ul>';
|
if (spouses) branches += '<ul><li><span>配偶</span><ul>' + spouses + '</ul></li></ul>';
|
||||||
if (children) branches += '<ul><li><span>子女</span><ul>' + children + '</ul></li></ul>';
|
if (children) branches += '<ul><li><span>子女</span><ul>' + children + '</ul></li></ul>';
|
||||||
return '<li><button class="lineage-node" type="button" data-lineage-person="' + escapeHtml(person.personId) + '">' +
|
return '<li><button class="lineage-node" type="button" data-lineage-person="' + escapeHtml(person.personId) + '">' +
|
||||||
|
(MediaDisplay ? MediaDisplay.renderAvatar(person.avatarFile, { name: person.name, className: 'lineage-node-avatar' }) : '') +
|
||||||
'<strong>' + escapeHtml(person.name) + '</strong><span>' + escapeHtml(getPersonSummary(person)) + '</span></button>' + branches + '</li>';
|
'<strong>' + escapeHtml(person.name) + '</strong><span>' + escapeHtml(getPersonSummary(person)) + '</span></button>' + branches + '</li>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,6 +645,9 @@
|
|||||||
queryAll('[data-lineage-relation], [data-lineage-form] button, [data-lineage-form] input, [data-lineage-form] select, [data-lineage-form] textarea, [data-lineage-edit], [data-lineage-disable]').forEach(function (control) {
|
queryAll('[data-lineage-relation], [data-lineage-form] button, [data-lineage-form] input, [data-lineage-form] select, [data-lineage-form] textarea, [data-lineage-edit], [data-lineage-disable]').forEach(function (control) {
|
||||||
control.disabled = writeDisabled;
|
control.disabled = writeDisabled;
|
||||||
});
|
});
|
||||||
|
queryAll('[data-lineage-relation]').forEach(function (control) {
|
||||||
|
control.disabled = writeDisabled || !selectedPersonId;
|
||||||
|
});
|
||||||
queryAll('[data-lineage-search], [data-lineage-person-options]').forEach(function (control) {
|
queryAll('[data-lineage-search], [data-lineage-person-options]').forEach(function (control) {
|
||||||
control.disabled = writePending;
|
control.disabled = writePending;
|
||||||
});
|
});
|
||||||
@@ -906,7 +924,7 @@
|
|||||||
if (writePending || !managementAccess || redirectUnauthorized(api)) return;
|
if (writePending || !managementAccess || redirectUnauthorized(api)) return;
|
||||||
genealogyId = requireGenealogyId();
|
genealogyId = requireGenealogyId();
|
||||||
if (!genealogyId || !personId) return;
|
if (!genealogyId || !personId) return;
|
||||||
if (root.confirm && !root.confirm('确认停用“' + (person ? person.name : '该成员') + '”吗?有正常子女时后端会拒绝停用。')) return;
|
if (!await confirmAction('确认停用“' + (person ? person.name : '该成员') + '”吗?有正常子女时后端会拒绝停用。')) return;
|
||||||
setWritePending(true);
|
setWritePending(true);
|
||||||
setActionStatus('正在停用成员…', 'loading');
|
setActionStatus('正在停用成员…', 'loading');
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,244 @@
|
|||||||
|
(function (root, factory) {
|
||||||
|
if (typeof module === 'object' && module.exports) {
|
||||||
|
module.exports = factory(root);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
root.MediaDisplay = factory(root);
|
||||||
|
if (root.document) {
|
||||||
|
root.document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
root.MediaDisplay.init(root.document);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function stringValue(value) {
|
||||||
|
return value === undefined || value === null ? '' : String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(value) {
|
||||||
|
return stringValue(value)
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeId(value) {
|
||||||
|
var text;
|
||||||
|
|
||||||
|
if (value === undefined || value === null || value === '') return '';
|
||||||
|
if (typeof value === 'number' && !Number.isSafeInteger(value)) return '';
|
||||||
|
text = String(value).trim();
|
||||||
|
return /^[1-9][0-9]*$/.test(text) ? text : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAccessUrl(value) {
|
||||||
|
var url;
|
||||||
|
|
||||||
|
try {
|
||||||
|
url = new URL(stringValue(value).trim());
|
||||||
|
} catch (error) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return url.protocol === 'https:' || url.protocol === 'http:' ? url.href : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeFileAccess(value) {
|
||||||
|
var source = value || {};
|
||||||
|
var accessUrl = normalizeAccessUrl(source.accessUrl);
|
||||||
|
var fileId = normalizeId(source.fileId);
|
||||||
|
var fileSize;
|
||||||
|
var normalized;
|
||||||
|
|
||||||
|
if (!accessUrl) return null;
|
||||||
|
if (source.fileId !== undefined && source.fileId !== null && source.fileId !== '' && !fileId) return null;
|
||||||
|
normalized = {
|
||||||
|
fileId: fileId,
|
||||||
|
fileName: stringValue(source.fileName).trim(),
|
||||||
|
mediaType: stringValue(source.mediaType).trim().toLowerCase(),
|
||||||
|
accessUrl: accessUrl,
|
||||||
|
expiresAt: stringValue(source.expiresAt).trim()
|
||||||
|
};
|
||||||
|
fileSize = Number(source.fileSize);
|
||||||
|
if (source.fileSize !== undefined && source.fileSize !== null && source.fileSize !== '' &&
|
||||||
|
Number.isSafeInteger(fileSize) && fileSize >= 0) {
|
||||||
|
normalized.fileSize = fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!normalized.fileId) delete normalized.fileId;
|
||||||
|
if (!normalized.fileName) delete normalized.fileName;
|
||||||
|
if (!normalized.mediaType) delete normalized.mediaType;
|
||||||
|
if (!normalized.expiresAt) delete normalized.expiresAt;
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeFileList(values) {
|
||||||
|
if (!Array.isArray(values)) return [];
|
||||||
|
return values.map(normalizeFileAccess).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function classNames(base, value) {
|
||||||
|
var additions = stringValue(value).split(/\s+/).filter(function (name) {
|
||||||
|
return /^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name);
|
||||||
|
});
|
||||||
|
|
||||||
|
return [base].concat(additions).join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderImage(value, options) {
|
||||||
|
var file = normalizeFileAccess(value);
|
||||||
|
var settings = options || {};
|
||||||
|
var alt;
|
||||||
|
var loading;
|
||||||
|
var caption;
|
||||||
|
|
||||||
|
if (!file) return '';
|
||||||
|
alt = stringValue(settings.alt || file.fileName || '图片').trim();
|
||||||
|
loading = settings.eager === true ? 'eager' : 'lazy';
|
||||||
|
caption = stringValue(settings.caption).trim();
|
||||||
|
return '<figure class="' + escapeHtml(classNames('media-frame', settings.className)) +
|
||||||
|
'" data-media-kind="image"><button class="media-preview" type="button" data-media-preview-url="' +
|
||||||
|
escapeHtml(file.accessUrl) + '" data-media-preview-alt="' + escapeHtml(alt) + '">' +
|
||||||
|
'<img src="' + escapeHtml(file.accessUrl) + '" alt="' + escapeHtml(alt) +
|
||||||
|
'" loading="' + loading + '" decoding="async" /></button>' +
|
||||||
|
'<span class="media-fallback" hidden>图片暂时无法显示</span>' +
|
||||||
|
(caption ? '<figcaption>' + escapeHtml(caption) + '</figcaption>' : '') + '</figure>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderAvatar(value, options) {
|
||||||
|
var file = normalizeFileAccess(value);
|
||||||
|
var settings = options || {};
|
||||||
|
var name = stringValue(settings.name || '用户').trim() || '用户';
|
||||||
|
var initial = Array.from(name)[0] || '家';
|
||||||
|
var classes = classNames('media-avatar', settings.className);
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
return '<span class="' + escapeHtml(classes) + '" aria-label="' +
|
||||||
|
escapeHtml(name + '的头像') + '">' + escapeHtml(initial) + '</span>';
|
||||||
|
}
|
||||||
|
return '<span class="' + escapeHtml(classes) + '"><img src="' + escapeHtml(file.accessUrl) +
|
||||||
|
'" alt="' + escapeHtml(name + '的头像') + '" loading="lazy" decoding="async" />' +
|
||||||
|
'<span class="media-avatar-fallback" aria-hidden="true">' + escapeHtml(initial) + '</span></span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderVideo(value, options) {
|
||||||
|
var file = normalizeFileAccess(value);
|
||||||
|
var settings = options || {};
|
||||||
|
var poster = normalizeFileAccess(settings.posterFile);
|
||||||
|
var title;
|
||||||
|
|
||||||
|
if (!file) return '';
|
||||||
|
title = stringValue(settings.title || file.fileName || '家族视频').trim();
|
||||||
|
return '<figure class="' + escapeHtml(classNames('media-frame media-video', settings.className)) +
|
||||||
|
'" data-media-kind="video"><video controls preload="metadata" src="' + escapeHtml(file.accessUrl) + '"' +
|
||||||
|
(poster ? ' poster="' + escapeHtml(poster.accessUrl) + '"' : '') +
|
||||||
|
' aria-label="' + escapeHtml(title) + '"></video>' +
|
||||||
|
'<span class="media-fallback" hidden>视频暂时无法播放</span>' +
|
||||||
|
(settings.caption ? '<figcaption>' + escapeHtml(settings.caption) + '</figcaption>' : '') + '</figure>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function mediaKind(file) {
|
||||||
|
var type = stringValue(file && file.mediaType).toLowerCase();
|
||||||
|
|
||||||
|
if (type.indexOf('image/') === 0) return 'image';
|
||||||
|
if (type.indexOf('video/') === 0) return 'video';
|
||||||
|
return 'file';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDownload(file) {
|
||||||
|
var label = file.fileName || '附件';
|
||||||
|
|
||||||
|
return '<a class="media-download" href="' + escapeHtml(file.accessUrl) +
|
||||||
|
'" target="_blank" rel="noopener noreferrer" download>下载 ' + escapeHtml(label) + '</a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderGallery(values, options) {
|
||||||
|
var files = normalizeFileList(values);
|
||||||
|
var settings = options || {};
|
||||||
|
var label = stringValue(settings.label || '媒体附件').trim();
|
||||||
|
var content;
|
||||||
|
|
||||||
|
if (!files.length) return '';
|
||||||
|
content = files.map(function (file) {
|
||||||
|
var kind = mediaKind(file);
|
||||||
|
|
||||||
|
if (kind === 'image') return renderImage(file, { alt: file.fileName || label });
|
||||||
|
if (kind === 'video') return renderVideo(file, { title: file.fileName || label });
|
||||||
|
return renderDownload(file);
|
||||||
|
}).join('');
|
||||||
|
return '<div class="' + escapeHtml(classNames('media-gallery', settings.className)) +
|
||||||
|
'" role="group" aria-label="' + escapeHtml(label) + '">' + content + '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensurePreviewDialog(documentRef) {
|
||||||
|
var dialog = documentRef.querySelector('[data-media-preview-dialog]');
|
||||||
|
var close;
|
||||||
|
|
||||||
|
if (dialog) return dialog;
|
||||||
|
dialog = documentRef.createElement('dialog');
|
||||||
|
dialog.className = 'media-dialog';
|
||||||
|
dialog.setAttribute('data-media-preview-dialog', '');
|
||||||
|
dialog.setAttribute('aria-label', '图片预览');
|
||||||
|
dialog.innerHTML = '<button class="media-dialog-close" type="button" aria-label="关闭图片预览">关闭</button>' +
|
||||||
|
'<img alt="" data-media-dialog-image />';
|
||||||
|
documentRef.body.appendChild(dialog);
|
||||||
|
close = dialog.querySelector('.media-dialog-close');
|
||||||
|
close.addEventListener('click', function () {
|
||||||
|
if (typeof dialog.close === 'function') dialog.close();
|
||||||
|
else dialog.removeAttribute('open');
|
||||||
|
});
|
||||||
|
dialog.addEventListener('click', function (event) {
|
||||||
|
if (event.target !== dialog) return;
|
||||||
|
if (typeof dialog.close === 'function') dialog.close();
|
||||||
|
else dialog.removeAttribute('open');
|
||||||
|
});
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(documentRef) {
|
||||||
|
if (!documentRef || documentRef.documentElement.getAttribute('data-media-display-ready') === 'true') return;
|
||||||
|
documentRef.documentElement.setAttribute('data-media-display-ready', 'true');
|
||||||
|
documentRef.addEventListener('error', function (event) {
|
||||||
|
var target = event.target;
|
||||||
|
var frame;
|
||||||
|
var fallback;
|
||||||
|
|
||||||
|
if (!target || !/^(IMG|VIDEO)$/.test(target.tagName)) return;
|
||||||
|
frame = target.closest && target.closest('.media-frame, .media-avatar');
|
||||||
|
if (!frame) return;
|
||||||
|
frame.classList.add('is-media-error');
|
||||||
|
target.hidden = true;
|
||||||
|
fallback = frame.querySelector('.media-fallback, .media-avatar-fallback');
|
||||||
|
if (fallback) fallback.hidden = false;
|
||||||
|
}, true);
|
||||||
|
documentRef.addEventListener('click', function (event) {
|
||||||
|
var trigger = event.target.closest && event.target.closest('[data-media-preview-url]');
|
||||||
|
var dialog;
|
||||||
|
var image;
|
||||||
|
var url;
|
||||||
|
|
||||||
|
if (!trigger) return;
|
||||||
|
url = normalizeAccessUrl(trigger.getAttribute('data-media-preview-url'));
|
||||||
|
if (!url) return;
|
||||||
|
dialog = ensurePreviewDialog(documentRef);
|
||||||
|
image = dialog.querySelector('[data-media-dialog-image]');
|
||||||
|
image.src = url;
|
||||||
|
image.alt = trigger.getAttribute('data-media-preview-alt') || '图片预览';
|
||||||
|
if (typeof dialog.showModal === 'function') dialog.showModal();
|
||||||
|
else dialog.setAttribute('open', '');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
normalizeFileAccess: normalizeFileAccess,
|
||||||
|
normalizeFileList: normalizeFileList,
|
||||||
|
renderImage: renderImage,
|
||||||
|
renderAvatar: renderAvatar,
|
||||||
|
renderVideo: renderVideo,
|
||||||
|
renderGallery: renderGallery,
|
||||||
|
init: init
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -13,6 +13,13 @@
|
|||||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
function confirmAction(message) {
|
||||||
|
if (root.ProfileUI && typeof root.ProfileUI.confirmAction === 'function') {
|
||||||
|
return root.ProfileUI.confirmAction(message);
|
||||||
|
}
|
||||||
|
return Promise.resolve(!root['confirm'] || root['confirm'](message));
|
||||||
|
}
|
||||||
|
|
||||||
var documentRef = root.document;
|
var documentRef = root.document;
|
||||||
var membersById = Object.create(null);
|
var membersById = Object.create(null);
|
||||||
var selectedMemberId = '';
|
var selectedMemberId = '';
|
||||||
@@ -496,7 +503,7 @@
|
|||||||
var target = membersById[memberId];
|
var target = membersById[memberId];
|
||||||
|
|
||||||
if (writePending || !genealogyId || !target || !managementAccess || !canRemoveMember(actorRole, target.roleType) || redirectUnauthorized(api)) return;
|
if (writePending || !genealogyId || !target || !managementAccess || !canRemoveMember(actorRole, target.roleType) || redirectUnauthorized(api)) return;
|
||||||
if (root.confirm && !root.confirm('确认将“' + memberDisplayName(target) + '”移出当前家谱吗?该操作不会删除其世系人物资料。')) return;
|
if (!await confirmAction('确认将“' + memberDisplayName(target) + '”移出当前家谱吗?该操作不会删除其世系人物资料。')) return;
|
||||||
setWritePending(true);
|
setWritePending(true);
|
||||||
setStatus('[data-member-action-status]', '正在移出成员…', 'loading');
|
setStatus('[data-member-action-status]', '正在移出成员…', 'loading');
|
||||||
try {
|
try {
|
||||||
@@ -520,7 +527,7 @@
|
|||||||
var genealogyId = getCurrentGenealogyId();
|
var genealogyId = getCurrentGenealogyId();
|
||||||
|
|
||||||
if (writePending || !genealogyId || !currentMemberId || !canLeaveGenealogy(actorRole) || redirectUnauthorized(api)) return;
|
if (writePending || !genealogyId || !currentMemberId || !canLeaveGenealogy(actorRole) || redirectUnauthorized(api)) return;
|
||||||
if (root.confirm && !root.confirm('确认退出当前家谱吗?退出后需重新受邀才能进入。世系人物资料不会被删除。')) return;
|
if (!await confirmAction('确认退出当前家谱吗?退出后需重新受邀才能进入。世系人物资料不会被删除。')) return;
|
||||||
setWritePending(true);
|
setWritePending(true);
|
||||||
setStatus('[data-member-action-status]', '正在退出当前家谱…', 'loading');
|
setStatus('[data-member-action-status]', '正在退出当前家谱…', 'loading');
|
||||||
try {
|
try {
|
||||||
@@ -547,7 +554,7 @@
|
|||||||
setStatus('[data-owner-transfer-status]', validation || '请选择成员列表中的新谱主', 'error');
|
setStatus('[data-owner-transfer-status]', validation || '请选择成员列表中的新谱主', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (root.confirm && !root.confirm('确认将谱主转让给“' + memberDisplayName(target) + '”吗?转让后当前账号将变为管理员。')) return;
|
if (!await confirmAction('确认将谱主转让给“' + memberDisplayName(target) + '”吗?转让后当前账号将变为管理员。')) return;
|
||||||
setWritePending(true);
|
setWritePending(true);
|
||||||
setStatus('[data-owner-transfer-status]', '正在转让谱主…', 'loading');
|
setStatus('[data-owner-transfer-status]', '正在转让谱主…', 'loading');
|
||||||
try {
|
try {
|
||||||
|
|||||||
+28
-10
@@ -13,6 +13,15 @@
|
|||||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
function confirmAction(message) {
|
||||||
|
if (root.ProfileUI && typeof root.ProfileUI.confirmAction === 'function') {
|
||||||
|
return root.ProfileUI.confirmAction(message);
|
||||||
|
}
|
||||||
|
return Promise.resolve(!root['confirm'] || root['confirm'](message));
|
||||||
|
}
|
||||||
|
|
||||||
|
var MediaDisplay = root.MediaDisplay || (typeof require === 'function' ? require('./media-display.js') : null);
|
||||||
|
|
||||||
var documentRef = root.document;
|
var documentRef = root.document;
|
||||||
var memosById = Object.create(null);
|
var memosById = Object.create(null);
|
||||||
var canEditContent = false;
|
var canEditContent = false;
|
||||||
@@ -161,7 +170,7 @@
|
|||||||
var appUserId = normalizeId(item && item.appUserId);
|
var appUserId = normalizeId(item && item.appUserId);
|
||||||
var memoTitle = String(item && item.memoTitle || '').trim();
|
var memoTitle = String(item && item.memoTitle || '').trim();
|
||||||
var completed = stringValue(item && item.completed);
|
var completed = stringValue(item && item.completed);
|
||||||
var mediaOssIds = stringValue(item && item.mediaOssIds);
|
var mediaFiles = MediaDisplay ? MediaDisplay.normalizeFileList(item && item.mediaFiles) : [];
|
||||||
var status = stringValue(item && item.status);
|
var status = stringValue(item && item.status);
|
||||||
var sortOrder = optionalSafeInteger(item && item.sortOrder);
|
var sortOrder = optionalSafeInteger(item && item.sortOrder);
|
||||||
var memo;
|
var memo;
|
||||||
@@ -170,7 +179,6 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (item.appUserId !== undefined && item.appUserId !== null && item.appUserId !== '' && !appUserId) return null;
|
if (item.appUserId !== undefined && item.appUserId !== null && item.appUserId !== '' && !appUserId) return null;
|
||||||
if (mediaOssIds && !/^[1-9][0-9]*(,[1-9][0-9]*)*$/.test(mediaOssIds)) return null;
|
|
||||||
if (item.sortOrder !== undefined && item.sortOrder !== null && item.sortOrder !== '' && sortOrder === undefined) return null;
|
if (item.sortOrder !== undefined && item.sortOrder !== null && item.sortOrder !== '' && sortOrder === undefined) return null;
|
||||||
if (item.remindTime && !isValidBackendDateTime(String(item.remindTime))) return null;
|
if (item.remindTime && !isValidBackendDateTime(String(item.remindTime))) return null;
|
||||||
memo = {
|
memo = {
|
||||||
@@ -185,10 +193,10 @@
|
|||||||
memoContent: stringValue(item.memoContent),
|
memoContent: stringValue(item.memoContent),
|
||||||
remindTime: stringValue(item.remindTime),
|
remindTime: stringValue(item.remindTime),
|
||||||
completed: completed,
|
completed: completed,
|
||||||
mediaOssIds: mediaOssIds,
|
|
||||||
status: status,
|
status: status,
|
||||||
remark: stringValue(item.remark)
|
remark: stringValue(item.remark)
|
||||||
};
|
};
|
||||||
|
if (mediaFiles.length) memo.mediaFiles = mediaFiles;
|
||||||
if (appUserId) memo.appUserId = appUserId;
|
if (appUserId) memo.appUserId = appUserId;
|
||||||
if (sortOrder !== undefined) memo.sortOrder = sortOrder;
|
if (sortOrder !== undefined) memo.sortOrder = sortOrder;
|
||||||
return memo;
|
return memo;
|
||||||
@@ -213,8 +221,8 @@
|
|||||||
.replace(/'/g, ''');
|
.replace(/'/g, ''');
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachmentCount(mediaOssIds) {
|
function attachmentCount(mediaFiles) {
|
||||||
return mediaOssIds ? mediaOssIds.split(',').filter(Boolean).length : 0;
|
return Array.isArray(mediaFiles) ? mediaFiles.length : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildListUrl(genealogyId, memoId) {
|
function buildListUrl(genealogyId, memoId) {
|
||||||
@@ -259,14 +267,15 @@
|
|||||||
var actions = '';
|
var actions = '';
|
||||||
|
|
||||||
if (!memo) return stateHtml('empty', '请选择一条备忘录查看详情');
|
if (!memo) return stateHtml('empty', '请选择一条备忘录查看详情');
|
||||||
count = attachmentCount(memo.mediaOssIds);
|
count = attachmentCount(memo.mediaFiles);
|
||||||
if (canEditContent) {
|
if (canEditContent) {
|
||||||
actions = '<div class="bottom-actions"><a class="btn ghost" href="' +
|
actions = '<div class="bottom-actions"><a class="btn ghost" href="' +
|
||||||
escapeHtml(buildEditUrl(memo.genealogyId, memo.memoId)) + '">编辑备忘录</a>' +
|
escapeHtml(buildEditUrl(memo.genealogyId, memo.memoId)) + '">编辑备忘录</a>' +
|
||||||
'<button class="btn ghost" type="button" data-memo-delete-id="' +
|
'<button class="btn danger" type="button" data-memo-delete-id="' +
|
||||||
escapeHtml(memo.memoId) + '">删除备忘录</button></div>';
|
escapeHtml(memo.memoId) + '">删除备忘录</button></div>';
|
||||||
}
|
}
|
||||||
return '<div class="form-like memo-detail">' +
|
return (MediaDisplay ? MediaDisplay.renderGallery(memo.mediaFiles, { label: '备忘录附件' }) : '') +
|
||||||
|
'<div class="form-like memo-detail">' +
|
||||||
detailValue('备忘标题', memo.memoTitle) +
|
detailValue('备忘标题', memo.memoTitle) +
|
||||||
detailValue('备忘内容', memo.memoContent) +
|
detailValue('备忘内容', memo.memoContent) +
|
||||||
detailValue('提醒时间', memo.remindTime) +
|
detailValue('提醒时间', memo.remindTime) +
|
||||||
@@ -606,7 +615,7 @@
|
|||||||
var memo = memosById[memoId];
|
var memo = memosById[memoId];
|
||||||
|
|
||||||
if (writePending || !canEditContent || !genealogyId || !normalizeId(memoId) || redirectUnauthorized(api)) return;
|
if (writePending || !canEditContent || !genealogyId || !normalizeId(memoId) || redirectUnauthorized(api)) return;
|
||||||
if (root.confirm && !root.confirm('确认删除“' + (memo ? memo.memoTitle : '该备忘录') + '”吗?删除后不可恢复,并会释放附件引用。')) return;
|
if (!await confirmAction('确认删除“' + (memo ? memo.memoTitle : '该备忘录') + '”吗?删除后不可恢复,并会释放附件引用。')) return;
|
||||||
setWritePending(true);
|
setWritePending(true);
|
||||||
try {
|
try {
|
||||||
await api.deleteMemo(genealogyId, memoId);
|
await api.deleteMemo(genealogyId, memoId);
|
||||||
@@ -636,10 +645,19 @@
|
|||||||
var detailButton = event.target.closest('[data-memo-detail-id]');
|
var detailButton = event.target.closest('[data-memo-detail-id]');
|
||||||
var deleteButton = event.target.closest('[data-memo-delete-id]');
|
var deleteButton = event.target.closest('[data-memo-delete-id]');
|
||||||
var clearMediaButton = event.target.closest('[data-memo-clear-media]');
|
var clearMediaButton = event.target.closest('[data-memo-clear-media]');
|
||||||
|
var memoId;
|
||||||
|
var genealogyId;
|
||||||
|
|
||||||
if (detailButton) {
|
if (detailButton) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
loadMemoDetail(detailButton.getAttribute('data-memo-detail-id'));
|
memoId = detailButton.getAttribute('data-memo-detail-id');
|
||||||
|
genealogyId = requireGenealogyId();
|
||||||
|
if (root.history && genealogyId) root.history.replaceState(null, '', buildListUrl(genealogyId, memoId));
|
||||||
|
loadMemoDetail(memoId).then(function (memo) {
|
||||||
|
if (memo && root.ProfileUI && root.ProfileUI.revealDetail) {
|
||||||
|
root.ProfileUI.revealDetail(query('[data-memo-detail-heading]'));
|
||||||
|
}
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (deleteButton) {
|
if (deleteButton) {
|
||||||
|
|||||||
@@ -13,6 +13,15 @@
|
|||||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
function confirmAction(message) {
|
||||||
|
if (root.ProfileUI && typeof root.ProfileUI.confirmAction === 'function') {
|
||||||
|
return root.ProfileUI.confirmAction(message);
|
||||||
|
}
|
||||||
|
return Promise.resolve(!root['confirm'] || root['confirm'](message));
|
||||||
|
}
|
||||||
|
|
||||||
|
var MediaDisplay = root.MediaDisplay || (typeof require === 'function' ? require('./media-display.js') : null);
|
||||||
|
|
||||||
var documentRef = root.document;
|
var documentRef = root.document;
|
||||||
var recordsById = Object.create(null);
|
var recordsById = Object.create(null);
|
||||||
var canEditContent = false;
|
var canEditContent = false;
|
||||||
@@ -249,6 +258,8 @@
|
|||||||
status: status,
|
status: status,
|
||||||
remark: stringValue(item.remark)
|
remark: stringValue(item.remark)
|
||||||
};
|
};
|
||||||
|
var mediaFiles = MediaDisplay ? MediaDisplay.normalizeFileList(item && item.mediaFiles) : [];
|
||||||
|
if (mediaFiles.length) record.mediaFiles = mediaFiles;
|
||||||
if (appUserId) record.appUserId = appUserId;
|
if (appUserId) record.appUserId = appUserId;
|
||||||
if (sortOrder !== undefined) record.sortOrder = sortOrder;
|
if (sortOrder !== undefined) record.sortOrder = sortOrder;
|
||||||
return record;
|
return record;
|
||||||
@@ -316,10 +327,11 @@
|
|||||||
if (canEditContent) {
|
if (canEditContent) {
|
||||||
actions = '<div class="bottom-actions"><a class="btn ghost" href="' +
|
actions = '<div class="bottom-actions"><a class="btn ghost" href="' +
|
||||||
escapeHtml(buildEditUrl(record.genealogyId, record.meritId)) + '">编辑功德记录</a>' +
|
escapeHtml(buildEditUrl(record.genealogyId, record.meritId)) + '">编辑功德记录</a>' +
|
||||||
'<button class="btn ghost" type="button" data-merit-delete-id="' +
|
'<button class="btn danger" type="button" data-merit-delete-id="' +
|
||||||
escapeHtml(record.meritId) + '">删除功德记录</button></div>';
|
escapeHtml(record.meritId) + '">删除功德记录</button></div>';
|
||||||
}
|
}
|
||||||
return '<div class="form-like merit-detail">' +
|
return (MediaDisplay ? MediaDisplay.renderGallery(record.mediaFiles, { label: '功德记录附件' }) : '') +
|
||||||
|
'<div class="form-like merit-detail">' +
|
||||||
detailValue('功德人', record.donorName) +
|
detailValue('功德人', record.donorName) +
|
||||||
detailValue('功德类型', meritTypes[record.meritType]) +
|
detailValue('功德类型', meritTypes[record.meritType]) +
|
||||||
detailValue('功德标题', record.meritTitle) +
|
detailValue('功德标题', record.meritTitle) +
|
||||||
@@ -670,7 +682,7 @@
|
|||||||
var record = recordsById[meritId];
|
var record = recordsById[meritId];
|
||||||
|
|
||||||
if (writePending || !canEditContent || !genealogyId || !normalizeId(meritId) || redirectUnauthorized(api)) return;
|
if (writePending || !canEditContent || !genealogyId || !normalizeId(meritId) || redirectUnauthorized(api)) return;
|
||||||
if (root.confirm && !root.confirm('确认删除“' + (record ? record.meritTitle : '该功德记录') + '”吗?删除后不可恢复。')) return;
|
if (!await confirmAction('确认删除“' + (record ? record.meritTitle : '该功德记录') + '”吗?删除后不可恢复。')) return;
|
||||||
setWritePending(true);
|
setWritePending(true);
|
||||||
try {
|
try {
|
||||||
await api.deleteMeritRecord(genealogyId, meritId);
|
await api.deleteMeritRecord(genealogyId, meritId);
|
||||||
@@ -689,10 +701,19 @@
|
|||||||
documentRef.addEventListener('click', function (event) {
|
documentRef.addEventListener('click', function (event) {
|
||||||
var detailButton = event.target.closest('[data-merit-detail-id]');
|
var detailButton = event.target.closest('[data-merit-detail-id]');
|
||||||
var deleteButton = event.target.closest('[data-merit-delete-id]');
|
var deleteButton = event.target.closest('[data-merit-delete-id]');
|
||||||
|
var meritId;
|
||||||
|
var genealogyId;
|
||||||
|
|
||||||
if (detailButton) {
|
if (detailButton) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
loadMeritDetail(detailButton.getAttribute('data-merit-detail-id'));
|
meritId = detailButton.getAttribute('data-merit-detail-id');
|
||||||
|
genealogyId = requireGenealogyId();
|
||||||
|
if (root.history && genealogyId) root.history.replaceState(null, '', buildListUrl(genealogyId, meritId));
|
||||||
|
loadMeritDetail(meritId).then(function (record) {
|
||||||
|
if (record && root.ProfileUI && root.ProfileUI.revealDetail) {
|
||||||
|
root.ProfileUI.revealDetail(query('[data-merit-detail-heading]'));
|
||||||
|
}
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (deleteButton) {
|
if (deleteButton) {
|
||||||
|
|||||||
@@ -1,4 +1,94 @@
|
|||||||
(function () {
|
(function () {
|
||||||
|
function syncPublicNavigation() {
|
||||||
|
let token = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
token = window.localStorage.getItem("genealogy_auth_token") || "";
|
||||||
|
} catch (error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!token) return;
|
||||||
|
|
||||||
|
document.querySelectorAll(".nav-actions").forEach((actions) => {
|
||||||
|
const loginLink = actions.querySelector('a[href="login.html"]');
|
||||||
|
const registerLink = actions.querySelector('a[href="register.html"]');
|
||||||
|
const createLink = actions.querySelector('a[href="create-genealogy.html"]');
|
||||||
|
|
||||||
|
if (loginLink) {
|
||||||
|
loginLink.href = "profile.html";
|
||||||
|
loginLink.textContent = "个人中心";
|
||||||
|
}
|
||||||
|
if (registerLink) {
|
||||||
|
registerLink.href = "profile-data.html";
|
||||||
|
registerLink.textContent = "账户资料";
|
||||||
|
}
|
||||||
|
if (createLink) createLink.href = "profile-create-family.html";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initSiteMenu() {
|
||||||
|
const body = document.body;
|
||||||
|
const header = document.querySelector(".site-header");
|
||||||
|
const nav = header && header.querySelector(".nav");
|
||||||
|
const navLinks = nav && nav.querySelector(".nav-links");
|
||||||
|
const navActions = nav && nav.querySelector(".nav-actions");
|
||||||
|
|
||||||
|
if (!body || body.matches(".page-profile, .page-profile-module")) return;
|
||||||
|
if (!header || !nav || !navLinks || nav.querySelector("[data-site-menu-toggle]")) return;
|
||||||
|
|
||||||
|
const menuId = "site-mobile-menu";
|
||||||
|
const toggle = document.createElement("button");
|
||||||
|
const menu = document.createElement("div");
|
||||||
|
const inner = document.createElement("div");
|
||||||
|
|
||||||
|
toggle.className = "site-menu-toggle";
|
||||||
|
toggle.type = "button";
|
||||||
|
toggle.textContent = "菜单";
|
||||||
|
toggle.setAttribute("data-site-menu-toggle", "");
|
||||||
|
toggle.setAttribute("aria-expanded", "false");
|
||||||
|
toggle.setAttribute("aria-controls", menuId);
|
||||||
|
|
||||||
|
menu.className = "site-mobile-menu";
|
||||||
|
menu.id = menuId;
|
||||||
|
menu.hidden = true;
|
||||||
|
menu.setAttribute("data-site-mobile-menu", "");
|
||||||
|
menu.setAttribute("aria-hidden", "true");
|
||||||
|
|
||||||
|
inner.className = "container site-mobile-menu-inner";
|
||||||
|
[...navLinks.querySelectorAll("a"), ...(navActions ? navActions.querySelectorAll("a") : [])].forEach((link) => {
|
||||||
|
inner.appendChild(link.cloneNode(true));
|
||||||
|
});
|
||||||
|
menu.appendChild(inner);
|
||||||
|
nav.appendChild(toggle);
|
||||||
|
header.appendChild(menu);
|
||||||
|
body.classList.add("has-site-mobile-menu");
|
||||||
|
|
||||||
|
const setOpen = (isOpen) => {
|
||||||
|
menu.hidden = !isOpen;
|
||||||
|
menu.classList.toggle("is-open", isOpen);
|
||||||
|
menu.setAttribute("aria-hidden", isOpen ? "false" : "true");
|
||||||
|
toggle.setAttribute("aria-expanded", isOpen ? "true" : "false");
|
||||||
|
};
|
||||||
|
|
||||||
|
toggle.addEventListener("click", (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
setOpen(toggle.getAttribute("aria-expanded") !== "true");
|
||||||
|
});
|
||||||
|
menu.addEventListener("click", (event) => {
|
||||||
|
if (event.target.closest("a")) setOpen(false);
|
||||||
|
event.stopPropagation();
|
||||||
|
});
|
||||||
|
document.addEventListener("click", () => setOpen(false));
|
||||||
|
document.addEventListener("keydown", (event) => {
|
||||||
|
if (event.key !== "Escape" || menu.hidden) return;
|
||||||
|
setOpen(false);
|
||||||
|
toggle.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
syncPublicNavigation();
|
||||||
|
initSiteMenu();
|
||||||
|
|
||||||
// 只在精确指针设备上启用微动效,触屏和减少动效偏好下保持静态。
|
// 只在精确指针设备上启用微动效,触屏和减少动效偏好下保持静态。
|
||||||
const finePointer = window.matchMedia("(hover: hover) and (pointer: fine)").matches;
|
const finePointer = window.matchMedia("(hover: hover) and (pointer: fine)").matches;
|
||||||
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||||
|
|||||||
@@ -0,0 +1,267 @@
|
|||||||
|
(function (root, factory) {
|
||||||
|
if (typeof module === 'object' && module.exports) {
|
||||||
|
module.exports = factory(root);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
root.PersonDocumentPages = factory(root);
|
||||||
|
if (root.document) root.document.addEventListener('DOMContentLoaded', root.PersonDocumentPages.init);
|
||||||
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function confirmAction(message) {
|
||||||
|
if (root.ProfileUI && typeof root.ProfileUI.confirmAction === 'function') {
|
||||||
|
return root.ProfileUI.confirmAction(message);
|
||||||
|
}
|
||||||
|
return Promise.resolve(!root['confirm'] || root['confirm'](message));
|
||||||
|
}
|
||||||
|
|
||||||
|
function promptAction(title) {
|
||||||
|
if (root.ProfileUI && typeof root.ProfileUI.promptAction === 'function') {
|
||||||
|
return root.ProfileUI.promptAction({ title: title, formType: 1, maxlength: 100 });
|
||||||
|
}
|
||||||
|
return Promise.resolve(root['prompt'] ? root['prompt'](title) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var USAGE_LABELS = { FRONT: '正面', BACK: '反面', ATTACHMENT: '附件' };
|
||||||
|
|
||||||
|
function escapeHtml(value) {
|
||||||
|
return String(value === undefined || value === null ? '' : value)
|
||||||
|
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"').replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeId(value) {
|
||||||
|
var text = String(value === undefined || value === null ? '' : value).trim();
|
||||||
|
return /^[1-9][0-9]*$/.test(text) ? text : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDocument(item) {
|
||||||
|
var source = item || {};
|
||||||
|
var documentId = normalizeId(source.documentId);
|
||||||
|
var personId = normalizeId(source.lineagePersonId);
|
||||||
|
var title = String(source.documentTitle || '').trim();
|
||||||
|
|
||||||
|
if (!documentId || !personId || !title || !Array.isArray(source.resources)) return null;
|
||||||
|
return {
|
||||||
|
documentId: documentId,
|
||||||
|
lineagePersonId: personId,
|
||||||
|
lineagePersonName: String(source.lineagePersonName || '').trim(),
|
||||||
|
documentType: String(source.documentType || '').trim(),
|
||||||
|
documentTypeLabel: String(source.documentTypeLabel || source.documentType || '').trim(),
|
||||||
|
documentTitle: title,
|
||||||
|
maskedIdentifier: String(source.maskedIdentifier || '').trim(),
|
||||||
|
description: String(source.description || '').trim(),
|
||||||
|
contentProtected: source.contentProtected === true,
|
||||||
|
contentUnlocked: source.contentUnlocked === true,
|
||||||
|
canEdit: source.canEdit === true,
|
||||||
|
canDelete: source.canDelete === true,
|
||||||
|
resources: source.resources.map(function (resource) {
|
||||||
|
var resourceId = normalizeId(resource && resource.resourceId);
|
||||||
|
var usageType = String(resource && resource.usageType || '').trim();
|
||||||
|
return resourceId && USAGE_LABELS[usageType] ? { resourceId: resourceId, usageType: usageType } : null;
|
||||||
|
}).filter(Boolean)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDocumentBody(values) {
|
||||||
|
var source = values || {};
|
||||||
|
return {
|
||||||
|
lineagePersonId: normalizeId(source.lineagePersonId),
|
||||||
|
documentType: String(source.documentType || '').trim(),
|
||||||
|
documentTitle: String(source.documentTitle || '').trim(),
|
||||||
|
maskedIdentifier: String(source.maskedIdentifier || '').trim(),
|
||||||
|
description: String(source.description || '').trim(),
|
||||||
|
status: '0'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateDocumentBody(body) {
|
||||||
|
if (!body.lineagePersonId) return '请选择关联人物';
|
||||||
|
if (!body.documentType) return '请选择证件类型';
|
||||||
|
if (!body.documentTitle) return '请填写证件标题';
|
||||||
|
if (body.documentTitle.length > 100) return '证件标题不能超过 100 个字符';
|
||||||
|
if (body.maskedIdentifier.length > 100) return '脱敏标识不能超过 100 个字符';
|
||||||
|
if (body.description.length > 1000) return '说明不能超过 1000 个字符';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDocuments(data) {
|
||||||
|
var items = Array.isArray(data) ? data.map(normalizeDocument).filter(Boolean) : [];
|
||||||
|
|
||||||
|
if (!items.length) return '<div class="api-empty">当前家谱还没有重要证件档案</div>';
|
||||||
|
return items.map(function (item) {
|
||||||
|
var resources = item.resources.map(function (resource) {
|
||||||
|
return '<button class="btn outline" type="button" data-document-resource="' +
|
||||||
|
escapeHtml(resource.resourceId) + '" data-document-id="' + escapeHtml(item.documentId) + '">' +
|
||||||
|
escapeHtml(USAGE_LABELS[resource.usageType]) + '</button>';
|
||||||
|
}).join('');
|
||||||
|
return '<article class="module-card" data-document-row="' + escapeHtml(item.documentId) + '"><h3>' +
|
||||||
|
escapeHtml(item.documentTitle) + '</h3><p>' +
|
||||||
|
escapeHtml([item.lineagePersonName, item.documentTypeLabel, item.maskedIdentifier, item.description].filter(Boolean).join(' · ')) +
|
||||||
|
'</p><div class="row-actions">' + resources +
|
||||||
|
(item.contentProtected && !item.contentUnlocked ? '<button class="btn outline" type="button" data-document-unlock="' + item.documentId + '">解锁</button>' : '') +
|
||||||
|
(item.canEdit ? '<button class="btn outline" type="button" data-document-protect="' + item.documentId + '">' + (item.contentProtected ? '取消密码' : '设置密码') + '</button>' : '') +
|
||||||
|
(item.canDelete ? '<button class="btn danger" type="button" data-document-delete="' + item.documentId + '">删除</button>' : '') +
|
||||||
|
'</div></article>';
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
var page = root.document && root.document.querySelector('[data-person-document-page]');
|
||||||
|
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
|
||||||
|
var genealogyId = root.ProfileUI && root.ProfileUI.getGenealogyId ? root.ProfileUI.getGenealogyId() : '';
|
||||||
|
var form = page && page.querySelector('[data-document-form]');
|
||||||
|
var list = page && page.querySelector('[data-document-list]');
|
||||||
|
var status = page && page.querySelector('[data-document-status]');
|
||||||
|
var personSelect = page && page.querySelector('[data-document-person]');
|
||||||
|
var typeSelect = page && page.querySelector('[data-document-type]');
|
||||||
|
var ossInput = page && page.querySelector('[data-document-oss-id]');
|
||||||
|
var usageSelect = page && page.querySelector('[data-document-usage]');
|
||||||
|
var documents = [];
|
||||||
|
var accessTokens = {};
|
||||||
|
var pending = false;
|
||||||
|
|
||||||
|
if (!page) return;
|
||||||
|
if (!api || !api.getToken || !api.getToken()) {
|
||||||
|
if (root.location) root.location.href = 'login.html';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!genealogyId) {
|
||||||
|
root.ProfileUI.setApiState(status, 'error', '缺少家谱上下文,请从“我的家谱”重新进入。');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(type, message) {
|
||||||
|
if (type) root.ProfileUI.setApiState(status, type, message);
|
||||||
|
else status.textContent = message || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadDocuments() {
|
||||||
|
documents = (await api.personDocuments(genealogyId)).map(normalizeDocument).filter(Boolean);
|
||||||
|
list.innerHTML = renderDocuments(documents);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var initial = await Promise.all([
|
||||||
|
api.lineagePersonOptions(genealogyId),
|
||||||
|
api.businessDictionary('gen_person_document_type'),
|
||||||
|
api.personDocuments(genealogyId)
|
||||||
|
]);
|
||||||
|
personSelect.innerHTML = '<option value="">请选择人物</option>' + (Array.isArray(initial[0]) ? initial[0] : []).map(function (item) {
|
||||||
|
var id = normalizeId(item.personId || item.lineagePersonId);
|
||||||
|
var name = String(item.displayName || item.personName || item.name || '').trim();
|
||||||
|
return id && name ? '<option value="' + escapeHtml(id) + '">' + escapeHtml(name) + '</option>' : '';
|
||||||
|
}).join('');
|
||||||
|
typeSelect.innerHTML = '<option value="">请选择证件类型</option>' + (Array.isArray(initial[1]) ? initial[1] : []).filter(function (item) {
|
||||||
|
return item && item.enabled === true && String(item.value || '').trim();
|
||||||
|
}).map(function (item) {
|
||||||
|
return '<option value="' + escapeHtml(item.value) + '">' + escapeHtml(item.label || item.value) + '</option>';
|
||||||
|
}).join('');
|
||||||
|
documents = (Array.isArray(initial[2]) ? initial[2] : []).map(normalizeDocument).filter(Boolean);
|
||||||
|
list.innerHTML = renderDocuments(documents);
|
||||||
|
setStatus('', '重要证件档案已加载');
|
||||||
|
} catch (error) {
|
||||||
|
setStatus('error', error.message || '重要证件档案读取失败');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.addEventListener('submit', async function (event) {
|
||||||
|
var body;
|
||||||
|
var validation;
|
||||||
|
var created;
|
||||||
|
var ossId;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
if (pending) return;
|
||||||
|
body = buildDocumentBody({
|
||||||
|
lineagePersonId: personSelect.value,
|
||||||
|
documentType: typeSelect.value,
|
||||||
|
documentTitle: form.elements.documentTitle.value,
|
||||||
|
maskedIdentifier: form.elements.maskedIdentifier.value,
|
||||||
|
description: form.elements.description.value
|
||||||
|
});
|
||||||
|
validation = validateDocumentBody(body);
|
||||||
|
if (validation) return setStatus('error', validation);
|
||||||
|
ossId = normalizeId(ossInput.value);
|
||||||
|
if (!ossId) return setStatus('error', '请先上传证件文件');
|
||||||
|
pending = true; setStatus('loading', '正在保存证件档案…');
|
||||||
|
try {
|
||||||
|
created = normalizeDocument(await api.createPersonDocument(genealogyId, body));
|
||||||
|
if (!created) throw new Error('创建响应缺少有效档案信息');
|
||||||
|
await api.addPersonDocumentResource(genealogyId, created.documentId, {
|
||||||
|
ossId: ossId, usageType: usageSelect.value
|
||||||
|
});
|
||||||
|
form.reset(); ossInput.value = '';
|
||||||
|
await loadDocuments();
|
||||||
|
setStatus('', '证件档案已保存');
|
||||||
|
} catch (error) { setStatus('error', error.message || '证件档案保存失败'); }
|
||||||
|
finally { pending = false; }
|
||||||
|
});
|
||||||
|
|
||||||
|
list.addEventListener('click', async function (event) {
|
||||||
|
var resourceButton = event.target.closest('[data-document-resource]');
|
||||||
|
var unlockButton = event.target.closest('[data-document-unlock]');
|
||||||
|
var protectButton = event.target.closest('[data-document-protect]');
|
||||||
|
var deleteButton = event.target.closest('[data-document-delete]');
|
||||||
|
var documentId;
|
||||||
|
var password;
|
||||||
|
var documentItem;
|
||||||
|
var accessWindow;
|
||||||
|
|
||||||
|
if (pending) return;
|
||||||
|
try {
|
||||||
|
if (resourceButton) {
|
||||||
|
accessWindow = root.open('about:blank', '_blank', 'noopener');
|
||||||
|
documentId = resourceButton.getAttribute('data-document-id');
|
||||||
|
var access = await api.personDocumentResourceAccess(
|
||||||
|
genealogyId, documentId, resourceButton.getAttribute('data-document-resource'), accessTokens[documentId]
|
||||||
|
);
|
||||||
|
if (!access || !access.accessUrl) throw new Error('文件访问响应缺少地址');
|
||||||
|
if (accessWindow) accessWindow.location.replace(access.accessUrl);
|
||||||
|
else root.location.href = access.accessUrl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (unlockButton) {
|
||||||
|
documentId = unlockButton.getAttribute('data-document-unlock');
|
||||||
|
password = await promptAction('请输入档案访问密码');
|
||||||
|
if (!password) return;
|
||||||
|
var grant = await api.unlockPersonDocument(genealogyId, documentId, password);
|
||||||
|
if (!grant || !grant.accessToken) throw new Error('解锁响应缺少访问令牌');
|
||||||
|
accessTokens[documentId] = grant.accessToken;
|
||||||
|
await loadDocuments(); setStatus('', '档案已临时解锁'); return;
|
||||||
|
}
|
||||||
|
if (protectButton) {
|
||||||
|
documentId = protectButton.getAttribute('data-document-protect');
|
||||||
|
documentItem = documents.find(function (item) { return item.documentId === documentId; });
|
||||||
|
if (!documentItem) return;
|
||||||
|
if (documentItem.contentProtected) {
|
||||||
|
if (!await confirmAction('确认取消此档案的密码保护吗?')) return;
|
||||||
|
await api.unprotectPersonDocument(genealogyId, documentId);
|
||||||
|
} else {
|
||||||
|
password = await promptAction('设置档案访问密码');
|
||||||
|
if (!password) return;
|
||||||
|
await api.protectPersonDocument(genealogyId, documentId, password);
|
||||||
|
}
|
||||||
|
await loadDocuments(); setStatus('', '密码保护设置已更新'); return;
|
||||||
|
}
|
||||||
|
if (deleteButton) {
|
||||||
|
documentId = deleteButton.getAttribute('data-document-delete');
|
||||||
|
if (!await confirmAction('确认删除这份证件档案吗?')) return;
|
||||||
|
await api.deletePersonDocument(genealogyId, documentId);
|
||||||
|
await loadDocuments(); setStatus('', '证件档案已删除');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (accessWindow) accessWindow.close();
|
||||||
|
setStatus('error', error.message || '操作失败,请稍后重试');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
normalizeDocument: normalizeDocument,
|
||||||
|
buildDocumentBody: buildDocumentBody,
|
||||||
|
validateDocumentBody: validateDocumentBody,
|
||||||
|
renderDocuments: renderDocuments,
|
||||||
|
init: init
|
||||||
|
};
|
||||||
|
});
|
||||||
+240
-19
@@ -7,6 +7,69 @@
|
|||||||
var genealogyContextStorageKey = 'genealogy_current_context_v1';
|
var genealogyContextStorageKey = 'genealogy_current_context_v1';
|
||||||
var mobileMenuId = 'profile-mobile-menu';
|
var mobileMenuId = 'profile-mobile-menu';
|
||||||
var apiStateTypes = ['loading', 'empty', 'error', 'forbidden'];
|
var apiStateTypes = ['loading', 'empty', 'error', 'forbidden'];
|
||||||
|
var familyWorkspacePages = [
|
||||||
|
'profile-family-home.html', 'profile-tree.html', 'profile-generation.html',
|
||||||
|
'profile-family-admin.html', 'profile-admin-permissions.html', 'profile-join-review.html',
|
||||||
|
'profile-content.html', 'profile-article.html', 'profile-article-edit.html',
|
||||||
|
'profile-album.html', 'profile-album-detail.html', 'profile-album-edit.html',
|
||||||
|
'profile-video.html', 'profile-video-edit.html', 'profile-feed.html',
|
||||||
|
'profile-feed-detail.html', 'profile-feed-edit.html', 'profile-ceremony.html',
|
||||||
|
'profile-ceremony-detail.html', 'profile-gift-edit.html', 'profile-gift.html',
|
||||||
|
'profile-growth.html', 'profile-growth-edit.html', 'profile-merit.html',
|
||||||
|
'profile-merit-edit.html', 'profile-memo.html', 'profile-memo-edit.html',
|
||||||
|
'profile-relative.html', 'profile-relative-edit.html', 'profile-invite.html',
|
||||||
|
'profile-data-reminders.html', 'profile-documents.html', 'profile-family-settings.html'
|
||||||
|
];
|
||||||
|
var familyWorkspaceNavigation = [
|
||||||
|
{ label: '家谱主页', href: 'profile-family-home.html', context: true },
|
||||||
|
{ label: '世系管理', items: [
|
||||||
|
{ label: '世系图', href: 'profile-tree.html', context: true },
|
||||||
|
{ label: '字辈谱', href: 'profile-generation.html', context: true },
|
||||||
|
{ label: '资料提醒', href: 'profile-data-reminders.html', context: true }
|
||||||
|
] },
|
||||||
|
{ label: '内容发布', items: [
|
||||||
|
{ label: '内容工作台', href: 'profile-content.html', context: true },
|
||||||
|
{ label: '谱文', href: 'profile-article.html', pages: ['profile-article-edit.html'], context: true },
|
||||||
|
{ label: '家族圈', href: 'profile-feed.html', pages: ['profile-feed-detail.html', 'profile-feed-edit.html'], context: true },
|
||||||
|
{ label: '相册', href: 'profile-album.html', pages: ['profile-album-detail.html', 'profile-album-edit.html'], context: true },
|
||||||
|
{ label: '视频', href: 'profile-video.html', pages: ['profile-video-edit.html'], context: true },
|
||||||
|
{ label: '祭祀活动', href: 'profile-ceremony.html', pages: ['profile-ceremony-detail.html', 'profile-gift-edit.html'], context: true },
|
||||||
|
{ label: '贺礼邀请', href: 'profile-gift.html', context: true },
|
||||||
|
{ label: '成长记录', href: 'profile-growth.html', pages: ['profile-growth-edit.html'], context: true },
|
||||||
|
{ label: '功德录', href: 'profile-merit.html', pages: ['profile-merit-edit.html'], context: true },
|
||||||
|
{ label: '备忘录', href: 'profile-memo.html', pages: ['profile-memo-edit.html'], context: true },
|
||||||
|
{ label: '亲友记录', href: 'profile-relative.html', pages: ['profile-relative-edit.html'], context: true },
|
||||||
|
{ label: '重要证件', href: 'profile-documents.html', context: true }
|
||||||
|
] },
|
||||||
|
{ label: '家族管理', items: [
|
||||||
|
{ label: '成员管理', href: 'profile-family-admin.html', context: true },
|
||||||
|
{ label: '入谱审核', href: 'profile-join-review.html', context: true },
|
||||||
|
{ label: '邀请家人', href: 'profile-invite.html', context: true },
|
||||||
|
{ label: '家谱维护', href: 'profile-family-settings.html', context: true },
|
||||||
|
{ label: '权限说明', href: 'profile-admin-permissions.html', context: true }
|
||||||
|
] },
|
||||||
|
{ label: '消息中心', href: 'profile-messages.html' }
|
||||||
|
];
|
||||||
|
var accountWorkspaceNavigation = [
|
||||||
|
{ label: '个人中心', href: 'profile.html' },
|
||||||
|
{ label: '我的家谱', items: [
|
||||||
|
{ label: '家谱列表', href: 'profile-families.html' },
|
||||||
|
{ label: '创建家谱', href: 'profile-create-family.html' },
|
||||||
|
{ label: '加入家谱', href: 'join-genealogy.html' },
|
||||||
|
{ label: '我的申请', href: 'profile-join-family.html' }
|
||||||
|
] },
|
||||||
|
{ label: '账号资料', items: [
|
||||||
|
{ label: '个人资料', href: 'profile-data.html' },
|
||||||
|
{ label: '账号安全', href: 'profile-security.html' }
|
||||||
|
] },
|
||||||
|
{ label: '服务与帮助', items: [
|
||||||
|
{ label: '帮助与服务', href: 'profile-services.html' },
|
||||||
|
{ label: '意见反馈', href: 'profile-feedback.html', pages: ['my-tickets.html', 'submit-ticket.html', 'ticket-detail.html'] },
|
||||||
|
{ label: '推广收益', href: 'profile-earnings.html' },
|
||||||
|
{ label: '分享与邀请', href: 'profile-share.html' }
|
||||||
|
] },
|
||||||
|
{ label: '消息中心', href: 'profile-messages.html' }
|
||||||
|
];
|
||||||
|
|
||||||
function escapeHtml(value) {
|
function escapeHtml(value) {
|
||||||
return String(value === undefined || value === null ? '' : value)
|
return String(value === undefined || value === null ? '' : value)
|
||||||
@@ -33,8 +96,20 @@
|
|||||||
element.innerHTML = renderApiState(type, message);
|
element.innerHTML = renderApiState(type, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function revealDetail(element) {
|
||||||
|
var reduceMotion;
|
||||||
|
|
||||||
|
if (!element) return;
|
||||||
|
reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
if (typeof element.scrollIntoView === 'function') {
|
||||||
|
element.scrollIntoView({ behavior: reduceMotion ? 'auto' : 'smooth', block: 'start' });
|
||||||
|
}
|
||||||
|
if (typeof element.focus === 'function') element.focus({ preventScroll: true });
|
||||||
|
}
|
||||||
|
|
||||||
profileUI.renderApiState = renderApiState;
|
profileUI.renderApiState = renderApiState;
|
||||||
profileUI.setApiState = setApiState;
|
profileUI.setApiState = setApiState;
|
||||||
|
profileUI.revealDetail = revealDetail;
|
||||||
window.ProfileUI = profileUI;
|
window.ProfileUI = profileUI;
|
||||||
|
|
||||||
if (!$) return;
|
if (!$) return;
|
||||||
@@ -105,21 +180,22 @@
|
|||||||
var genealogyId = getGenealogyId();
|
var genealogyId = getGenealogyId();
|
||||||
var context = getStoredGenealogyContext();
|
var context = getStoredGenealogyContext();
|
||||||
|
|
||||||
return context && context.genealogyId === genealogyId ? context : null;
|
if (!context) return null;
|
||||||
|
return !genealogyId || context.genealogyId === genealogyId ? context : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function openLegacyLogout() {
|
function openLegacyLogout() {
|
||||||
var $modal = $('#logoutModal');
|
var $modal = $('#logoutModal');
|
||||||
|
|
||||||
if (!$modal.length) return;
|
if (!$modal.length) return;
|
||||||
$modal.addClass('show').attr('aria-hidden', 'false');
|
$modal.prop('hidden', false).addClass('show').attr('aria-hidden', 'false');
|
||||||
$('body').addClass('logout-modal-open');
|
$('body').addClass('logout-modal-open');
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeLegacyLogout() {
|
function closeLegacyLogout() {
|
||||||
var $modal = $('#logoutModal');
|
var $modal = $('#logoutModal');
|
||||||
|
|
||||||
$modal.removeClass('show').attr('aria-hidden', 'true');
|
$modal.removeClass('show').attr('aria-hidden', 'true').prop('hidden', true);
|
||||||
$('body').removeClass('logout-modal-open');
|
$('body').removeClass('logout-modal-open');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +205,8 @@
|
|||||||
content: '确定继续吗?',
|
content: '确定继续吗?',
|
||||||
confirmText: '确定',
|
confirmText: '确定',
|
||||||
cancelText: '取消',
|
cancelText: '取消',
|
||||||
onConfirm: $.noop
|
onConfirm: $.noop,
|
||||||
|
onCancel: $.noop
|
||||||
}, options || {});
|
}, options || {});
|
||||||
|
|
||||||
if (layerApi) {
|
if (layerApi) {
|
||||||
@@ -140,11 +217,26 @@
|
|||||||
}, function (index) {
|
}, function (index) {
|
||||||
settings.onConfirm();
|
settings.onConfirm();
|
||||||
layerApi.close(index);
|
layerApi.close(index);
|
||||||
|
}, function () {
|
||||||
|
settings.onCancel();
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.confirm(settings.content)) settings.onConfirm();
|
if (window.confirm(settings.content)) settings.onConfirm();
|
||||||
|
else settings.onCancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmAction(content, options) {
|
||||||
|
var settings = typeof content === 'string'
|
||||||
|
? $.extend({}, options || {}, { content: content })
|
||||||
|
: $.extend({}, content || {});
|
||||||
|
|
||||||
|
return new Promise(function (resolve) {
|
||||||
|
settings.onConfirm = function () { resolve(true); };
|
||||||
|
settings.onCancel = function () { resolve(false); };
|
||||||
|
openConfirm(settings);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initLayui() {
|
function initLayui() {
|
||||||
@@ -274,7 +366,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function syncGenealogyContextLinks() {
|
function syncGenealogyContextLinks() {
|
||||||
var genealogyId = getGenealogyId();
|
var context = getCurrentGenealogyContext();
|
||||||
|
var genealogyId = getGenealogyId() || (context && context.genealogyId);
|
||||||
|
|
||||||
$('[data-genealogy-context-link], a[href]').filter(function () {
|
$('[data-genealogy-context-link], a[href]').filter(function () {
|
||||||
var $link = $(this);
|
var $link = $(this);
|
||||||
@@ -315,7 +408,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initCurrentGenealogyContext() {
|
function initCurrentGenealogyContext() {
|
||||||
var genealogyId = getGenealogyId();
|
var context = getCurrentGenealogyContext();
|
||||||
|
var genealogyId = getGenealogyId() || (context && context.genealogyId);
|
||||||
var $nav;
|
var $nav;
|
||||||
var $actions;
|
var $actions;
|
||||||
var $context;
|
var $context;
|
||||||
@@ -332,6 +426,94 @@
|
|||||||
refreshCurrentGenealogyContext();
|
refreshCurrentGenealogyContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCurrentPageName() {
|
||||||
|
return (window.location.pathname.split('/').pop() || 'profile.html').split('?')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWorkspaceItemCurrent(item, currentPage) {
|
||||||
|
return item.href === currentPage || (Array.isArray(item.pages) && item.pages.indexOf(currentPage) >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderWorkspaceLink(item, currentPage) {
|
||||||
|
var isCurrent = isWorkspaceItemCurrent(item, currentPage);
|
||||||
|
|
||||||
|
return '<a' + (isCurrent ? ' class="active" aria-current="page"' : '') +
|
||||||
|
' href="' + escapeHtml(item.href) + '"' + (item.context ? ' data-genealogy-context-link' : '') + '>' +
|
||||||
|
escapeHtml(item.label) + '</a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderWorkspaceNavigation(items, currentPage) {
|
||||||
|
return items.map(function (item) {
|
||||||
|
var isOpen;
|
||||||
|
|
||||||
|
if (!item.items) return renderWorkspaceLink(item, currentPage);
|
||||||
|
isOpen = item.items.some(function (child) { return isWorkspaceItemCurrent(child, currentPage); });
|
||||||
|
return '<details class="workspace-nav-group' + (isOpen ? ' is-open' : '') +
|
||||||
|
'" data-workspace-nav-group' + (isOpen ? ' open' : '') + '>' +
|
||||||
|
'<summary>' + escapeHtml(item.label) + '</summary>' +
|
||||||
|
'<div class="workspace-nav-items">' + item.items.map(function (child) {
|
||||||
|
return renderWorkspaceLink(child, currentPage);
|
||||||
|
}).join('') + '</div></details>';
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHeaderSection(currentPage) {
|
||||||
|
if (['profile-families.html', 'profile-create-family.html', 'join-genealogy.html', 'profile-join-family.html', 'profile-family-home.html'].indexOf(currentPage) >= 0) {
|
||||||
|
return 'profile-families.html';
|
||||||
|
}
|
||||||
|
if ([
|
||||||
|
'profile-tree.html', 'profile-generation.html', 'profile-family-admin.html',
|
||||||
|
'profile-admin-permissions.html', 'profile-join-review.html', 'profile-invite.html',
|
||||||
|
'profile-data-reminders.html', 'profile-documents.html', 'profile-family-settings.html'
|
||||||
|
].indexOf(currentPage) >= 0) return 'profile-family-admin.html';
|
||||||
|
if (familyWorkspacePages.indexOf(currentPage) >= 0) return 'profile-content.html';
|
||||||
|
if (['profile-data.html', 'profile-security.html'].indexOf(currentPage) >= 0) return 'profile-data.html';
|
||||||
|
if (['profile-services.html', 'profile-feedback.html', 'my-tickets.html', 'submit-ticket.html', 'ticket-detail.html', 'profile-earnings.html', 'profile-share.html'].indexOf(currentPage) >= 0) {
|
||||||
|
return 'profile-services.html';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderHeaderLink(item, currentSection) {
|
||||||
|
var isCurrent = item.href === currentSection;
|
||||||
|
|
||||||
|
return '<a' + (isCurrent ? ' class="active" aria-current="page"' : '') +
|
||||||
|
' href="' + escapeHtml(item.href) + '"' + (item.context ? ' data-genealogy-context-link' : '') + '>' +
|
||||||
|
escapeHtml(item.label) + '</a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function initWorkspaceNavigation() {
|
||||||
|
var currentPage = getCurrentPageName();
|
||||||
|
var isFamilyWorkspace = familyWorkspacePages.indexOf(currentPage) >= 0;
|
||||||
|
var navigation = isFamilyWorkspace ? familyWorkspaceNavigation : accountWorkspaceNavigation;
|
||||||
|
var $sidebar = $('.module-nav').first();
|
||||||
|
var $header = $('.site-header').first();
|
||||||
|
var headerItems = [
|
||||||
|
{ label: '我的家谱', href: 'profile-families.html' },
|
||||||
|
{ label: '家族管理', href: 'profile-family-admin.html', context: true },
|
||||||
|
{ label: '内容发布', href: 'profile-content.html', context: true },
|
||||||
|
{ label: '个人资料', href: 'profile-data.html' },
|
||||||
|
{ label: '帮助与服务', href: 'profile-services.html' }
|
||||||
|
];
|
||||||
|
var currentSection = getHeaderSection(currentPage);
|
||||||
|
|
||||||
|
if ($header.length) {
|
||||||
|
$header.find('.brand').first().attr('href', 'profile.html').html(
|
||||||
|
'<img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" />' +
|
||||||
|
'<span class="brand-text">个人中心</span>'
|
||||||
|
);
|
||||||
|
$header.find('.nav-links').first().attr('aria-label', '个人中心主导航').html(headerItems.map(function (item) {
|
||||||
|
return renderHeaderLink(item, currentSection);
|
||||||
|
}).join(''));
|
||||||
|
}
|
||||||
|
if (!$sidebar.length) return;
|
||||||
|
$sidebar
|
||||||
|
.attr('aria-label', isFamilyWorkspace ? '当前家谱功能导航' : '个人中心功能导航')
|
||||||
|
.attr('data-workspace-navigation', isFamilyWorkspace ? 'family' : 'account')
|
||||||
|
.html(renderWorkspaceNavigation(navigation, currentPage));
|
||||||
|
$('body').addClass('has-workspace-navigation');
|
||||||
|
}
|
||||||
|
|
||||||
function setMobileMenuOpen(isOpen) {
|
function setMobileMenuOpen(isOpen) {
|
||||||
var $menu = $('[data-profile-mobile-menu]').first();
|
var $menu = $('[data-profile-mobile-menu]').first();
|
||||||
var $toggle = $('[data-profile-mobile-menu-toggle]').first();
|
var $toggle = $('[data-profile-mobile-menu-toggle]').first();
|
||||||
@@ -345,26 +527,28 @@
|
|||||||
var $header = $('.site-header').first();
|
var $header = $('.site-header').first();
|
||||||
var $nav;
|
var $nav;
|
||||||
var currentPage;
|
var currentPage;
|
||||||
|
var isFamilyWorkspace;
|
||||||
|
var navigation;
|
||||||
|
var workspaceLabel;
|
||||||
|
|
||||||
|
if (!$('body').is('.page-profile, .page-profile-module')) return;
|
||||||
if (!$header.length || $header.find('[data-profile-mobile-menu]').length) return;
|
if (!$header.length || $header.find('[data-profile-mobile-menu]').length) return;
|
||||||
$nav = $header.find('.nav').first();
|
$nav = $header.find('.nav').first();
|
||||||
if (!$nav.length) return;
|
if (!$nav.length) return;
|
||||||
$nav.append('<button class="profile-mobile-menu-toggle" type="button" data-profile-mobile-menu-toggle ' +
|
$nav.append('<button class="profile-mobile-menu-toggle" type="button" data-profile-mobile-menu-toggle ' +
|
||||||
'aria-expanded="false" aria-controls="' + mobileMenuId + '">菜单</button>');
|
'aria-expanded="false" aria-controls="' + mobileMenuId + '">菜单</button>');
|
||||||
|
currentPage = getCurrentPageName();
|
||||||
|
isFamilyWorkspace = familyWorkspacePages.indexOf(currentPage) >= 0;
|
||||||
|
navigation = isFamilyWorkspace ? familyWorkspaceNavigation : accountWorkspaceNavigation;
|
||||||
|
workspaceLabel = isFamilyWorkspace ? '当前家谱功能导航' : '个人中心功能导航';
|
||||||
$header.append('<div class="profile-mobile-menu" id="' + mobileMenuId + '" data-profile-mobile-menu hidden aria-hidden="true">' +
|
$header.append('<div class="profile-mobile-menu" id="' + mobileMenuId + '" data-profile-mobile-menu hidden aria-hidden="true">' +
|
||||||
'<div class="container profile-mobile-menu-inner">' +
|
'<div class="container profile-mobile-menu-inner">' +
|
||||||
'<a href="profile.html">个人中心</a>' +
|
'<nav class="profile-mobile-workspace" aria-label="' + workspaceLabel + '">' +
|
||||||
'<a href="profile-families.html">我的家谱</a>' +
|
renderWorkspaceNavigation(navigation, currentPage) + '</nav>' +
|
||||||
'<a href="profile-create-family.html">创建家谱</a>' +
|
'<div class="profile-mobile-account-actions">' +
|
||||||
'<a href="profile-family-home.html" data-genealogy-context-link>家谱主页</a>' +
|
(isFamilyWorkspace ? '<a href="profile-families.html">切换家谱</a>' : '') +
|
||||||
'<a href="profile-content.html" data-genealogy-context-link>内容发布</a>' +
|
'<button type="button" data-logout-open data-logout-url="index.html">退出登录</button></div>' +
|
||||||
'<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>');
|
'</div></div>');
|
||||||
currentPage = (window.location.pathname.split('/').pop() || 'profile.html').split('?')[0];
|
|
||||||
$header.find('[data-profile-mobile-menu] a').each(function () {
|
$header.find('[data-profile-mobile-menu] a').each(function () {
|
||||||
var $link = $(this);
|
var $link = $(this);
|
||||||
var target = ($link.attr('href') || '').split('?')[0];
|
var target = ($link.attr('href') || '').split('?')[0];
|
||||||
@@ -390,7 +574,13 @@
|
|||||||
if (!$(event.target).closest('.site-header').length) setMobileMenuOpen(false);
|
if (!$(event.target).closest('.site-header').length) setMobileMenuOpen(false);
|
||||||
});
|
});
|
||||||
$(document).on('keydown', function (event) {
|
$(document).on('keydown', function (event) {
|
||||||
if (event.key === 'Escape') setMobileMenuOpen(false);
|
var $menu;
|
||||||
|
|
||||||
|
if (event.key !== 'Escape') return;
|
||||||
|
$menu = $('[data-profile-mobile-menu]').first();
|
||||||
|
if (!$menu.length || $menu.prop('hidden')) return;
|
||||||
|
setMobileMenuOpen(false);
|
||||||
|
$('[data-profile-mobile-menu-toggle]').first().trigger('focus');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,6 +601,33 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function promptAction(options) {
|
||||||
|
var settings = $.extend({
|
||||||
|
title: '填写内容',
|
||||||
|
value: '',
|
||||||
|
formType: 1,
|
||||||
|
maxlength: 200
|
||||||
|
}, options || {});
|
||||||
|
|
||||||
|
return new Promise(function (resolve) {
|
||||||
|
if (!layerApi) {
|
||||||
|
resolve(window.prompt(settings.title, settings.value) || null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
layerApi.prompt({
|
||||||
|
title: settings.title,
|
||||||
|
value: settings.value,
|
||||||
|
formType: settings.formType,
|
||||||
|
maxlength: settings.maxlength,
|
||||||
|
cancel: function () { resolve(null); }
|
||||||
|
}, function (value, index) {
|
||||||
|
resolve(value);
|
||||||
|
layerApi.close(index);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function bindPromptActions() {
|
function bindPromptActions() {
|
||||||
$(document).on('click', '[data-layer-prompt]', function (event) {
|
$(document).on('click', '[data-layer-prompt]', function (event) {
|
||||||
var $item = $(this);
|
var $item = $(this);
|
||||||
@@ -493,6 +710,7 @@
|
|||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
initLayui();
|
initLayui();
|
||||||
|
initWorkspaceNavigation();
|
||||||
bindLogout();
|
bindLogout();
|
||||||
bindConfirmActions();
|
bindConfirmActions();
|
||||||
bindPromptActions();
|
bindPromptActions();
|
||||||
@@ -508,6 +726,8 @@
|
|||||||
|
|
||||||
Object.assign(profileUI, {
|
Object.assign(profileUI, {
|
||||||
confirm: openConfirm,
|
confirm: openConfirm,
|
||||||
|
confirmAction: confirmAction,
|
||||||
|
promptAction: promptAction,
|
||||||
closeLogout: closeLegacyLogout,
|
closeLogout: closeLegacyLogout,
|
||||||
initLayui: initLayui,
|
initLayui: initLayui,
|
||||||
getGenealogyId: getGenealogyId,
|
getGenealogyId: getGenealogyId,
|
||||||
@@ -521,6 +741,7 @@
|
|||||||
refreshCurrentGenealogyContext: refreshCurrentGenealogyContext,
|
refreshCurrentGenealogyContext: refreshCurrentGenealogyContext,
|
||||||
syncGenealogyContextLinks: syncGenealogyContextLinks,
|
syncGenealogyContextLinks: syncGenealogyContextLinks,
|
||||||
renderApiState: renderApiState,
|
renderApiState: renderApiState,
|
||||||
setApiState: setApiState
|
setApiState: setApiState,
|
||||||
|
revealDetail: revealDetail
|
||||||
});
|
});
|
||||||
})(window, window.jQuery || (window.layui && window.layui.$));
|
})(window, window.jQuery || (window.layui && window.layui.$));
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var documentRef = root.document;
|
var documentRef = root.document;
|
||||||
|
var MediaDisplay = root.MediaDisplay || (typeof require === 'function' ? require('./media-display.js') : null);
|
||||||
|
|
||||||
function trimOrUndefined(value) {
|
function trimOrUndefined(value) {
|
||||||
var text = value === undefined || value === null ? '' : String(value).trim();
|
var text = value === undefined || value === null ? '' : String(value).trim();
|
||||||
@@ -40,18 +41,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildProfileView(profile) {
|
function buildProfileView(profile) {
|
||||||
|
var avatarFile = MediaDisplay && MediaDisplay.normalizeFileAccess(profile && profile.avatarFile);
|
||||||
|
var view;
|
||||||
|
|
||||||
// 页面展示模型和接口返回分开,后续改文案不影响接口契约。
|
// 页面展示模型和接口返回分开,后续改文案不影响接口契约。
|
||||||
return {
|
view = {
|
||||||
displayName: profileValue(profile, 'nickName', '未设置昵称'),
|
displayName: profileValue(profile, 'nickName', '未设置昵称'),
|
||||||
realName: profileValue(profile, 'realName', '待填写'),
|
realName: profileValue(profile, 'realName', '待填写'),
|
||||||
avatarText: getAvatarText(profile),
|
avatarText: getAvatarText(profile),
|
||||||
avatarStatus: profile && profile.avatar ? '已设置' : '未设置',
|
avatarStatus: avatarFile ? '已设置' : '未设置',
|
||||||
phone: profileValue(profile, 'phone', '未绑定'),
|
phone: profileValue(profile, 'phone', '未绑定'),
|
||||||
sex: getSexText(profile && profile.sex),
|
sex: getSexText(profile && profile.sex),
|
||||||
birthday: profileValue(profile, 'birthday', '待填写'),
|
birthday: profileValue(profile, 'birthday', '待填写'),
|
||||||
email: profileValue(profile, 'email', '待填写'),
|
email: profileValue(profile, 'email', '待填写'),
|
||||||
regionText: '当前资料接口未提供'
|
regionText: '当前资料接口未提供'
|
||||||
};
|
};
|
||||||
|
if (avatarFile) view.avatarFile = avatarFile;
|
||||||
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildProfileUpdateBody(values) {
|
function buildProfileUpdateBody(values) {
|
||||||
@@ -159,10 +165,21 @@
|
|||||||
var key = node.getAttribute('data-profile-text');
|
var key = node.getAttribute('data-profile-text');
|
||||||
var value = view[key];
|
var value = view[key];
|
||||||
|
|
||||||
|
if (key === 'avatarText' && view.avatarFile) return;
|
||||||
if (value !== undefined && value !== null && value !== '') {
|
if (value !== undefined && value !== null && value !== '') {
|
||||||
node.textContent = value;
|
node.textContent = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
queryAll('[data-profile-avatar]').forEach(function (node) {
|
||||||
|
if (!MediaDisplay) {
|
||||||
|
node.textContent = view.avatarText;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
node.innerHTML = MediaDisplay.renderAvatar(view.avatarFile, {
|
||||||
|
name: view.displayName,
|
||||||
|
className: 'profile-avatar-media'
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setProfileLoadStatus(type, message) {
|
function setProfileLoadStatus(type, message) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user