Compare commits

...

8 Commits

Author SHA1 Message Date
rain 24ccdf251e 交接文档 2026-09-17 16:37:00 +08:00
rain d040507cb8 修改完成 2026-09-13 19:41:18 +08:00
rain 15b301b358 修改页面,完成单页模式新增首页返回上一页按钮 2026-08-31 14:49:08 +08:00
rain aaed0584b1 修改功能 2026-08-29 19:06:07 +08:00
rain f94c652c7c 样式修改,添加名称 2026-08-05 14:37:00 +08:00
rain 5b79893650 等待后端更新接口 2026-08-03 16:49:38 +08:00
rain ac5823547a 验证20% 2026-07-31 11:31:26 +08:00
fizzleaf 309598bfa6 feat(api): 添加帮助中心反馈系统和VIP服务功能
- 在ApiClient中新增submitFeedback、myFeedback、helpArticles、helpArticleDetail、
  siteArticles、promotions、vipPackages、createVipOrder、vipOrders等方法
- 添加帮助文章和站点资讯的参数验证逻辑
- 更新测试文件添加新的API方法测试用例
- 在HTML页面中添加反馈、帮助和VIP服务相关页面的脚本引用
- 更新加入家谱页面为完整的申请流程界面
- 修改资讯详情页面为站点资讯展示页面
- 更新AxiosRequestUtil中认证处理逻辑
- 添加世系树渲染的HTML生成函数用于页面复用
- 更新文档中的API契约说明和页面规划
2026-07-30 16:04:48 +08:00
176 changed files with 13219 additions and 20183 deletions
+7
View File
@@ -1,6 +1,7 @@
###################################################################### ######################################################################
# Build Tools # Build Tools
/node_modules/
.gradle .gradle
/build/ /build/
!gradle/wrapper/gradle-wrapper.jar !gradle/wrapper/gradle-wrapper.jar
@@ -51,3 +52,9 @@ nbdist/
robots.txt robots.txt
sitemap.xml sitemap.xml
# Local browser audit screenshots and generated reports
/artifacts/
# Local design-audit runtime metadata
/.hallmark/
-115
View File
@@ -1,115 +0,0 @@
# AGENTS.md
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
Tradeoff: These guidelines bias toward caution over speed. For trivial tasks, use judgment.
1. Think Before Coding
Don't assume. Don't hide confusion. Surface tradeoffs.
Before implementing:
State your assumptions explicitly. If uncertain, ask.
If multiple interpretations exist, present them instead of picking silently.
If a simpler approach exists, say so. Push back when warranted.
If something is unclear, stop. Name what is confusing. Ask.
2. Simplicity First
Minimum code that solves the problem. Nothing speculative.
No features beyond what was asked.
No abstractions for single-use code.
No "flexibility" or "configurability" that was not requested.
No error handling for impossible scenarios.
If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
3. Surgical Changes
Touch only what you must. Clean up only your own mess.
When editing existing code:
Do not "improve" adjacent code, comments, or formatting.
Do not refactor things that are not broken.
Match existing style, even if you would do it differently.
If you notice unrelated dead code, mention it instead of deleting it.
When your changes create orphans:
Remove imports, variables, functions, files, docs, and references that YOUR changes made unused.
Do not remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
4. Contract Discipline
When a new contract lands, make it the only contract in the same change.
A contract means any source-of-truth rule that code depends on: data shape, API boundary, schema, field name, config key, prompt format, file layout, runtime entrypoint, ownership boundary, or workflow.
When introducing or changing a contract, do all three in the same round:
Specify the single owner.
Identify the one module, function, schema, document, or service that owns the contract.
Do not leave the same rule duplicated across frontend, backend, scripts, prompts, docs, or tests.
If multiple places need the value, they should consume it from the owner rather than redefine it.
Delete old fields and old entrypoints.
Remove obsolete fields, fallback reads, compatibility branches, legacy routes, stale config keys, old scripts, and old docs.
Do not keep the old path "just in case" unless the user explicitly asks for a compatibility period.
If compatibility is required, name it as temporary, define the removal condition, and keep it narrow.
Tighten validators and runtime together.
Update schemas, validators, tests, fixtures, seed data, docs, and runtime code in the same change.
Make invalid old inputs fail early and loudly.
Do not allow runtime behavior to accept shapes that validators reject, or validators to allow shapes that runtime no longer supports.
The rule: New owner, old path removed, validator and runtime tightened. If one is missing, the contract migration is not complete.
5. Goal-Driven Execution
Define success criteria. Loop until verified.
Transform tasks into verifiable goals:
"Add validation" -> "Write tests for invalid inputs, then make them pass."
"Fix the bug" -> "Write a test that reproduces it, then make it pass."
"Refactor X" -> "Ensure tests pass before and after."
"Change a contract" -> "Identify owner, remove old paths, tighten validators and runtime, then verify old inputs fail."
For multi-step tasks, state a brief plan:
1. [Step] -> verify: [check]
2. [Step] -> verify: [check]
3. [Step] -> verify: [check]
Strong success criteria let you loop independently. Weak criteria like "make it work" require clarification.
6. Verification Before Closure
A change is not finished until the relevant behavior is checked.
Before reporting completion:
Run the smallest relevant test, script, typecheck, build, or manual verification.
Prefer focused verification over broad, noisy suites when the task is narrow.
If verification cannot be run, say exactly why.
If verification is only by inspection, say that clearly.
If the task changed a contract, verify both the new valid path and the old invalid path.
Do not claim success from intent. Claim success from evidence.
7. Reporting Results
Be concise, concrete, and honest.
When summarizing work:
Say what changed.
Say how it was verified.
Mention any remaining risk or skipped verification.
Do not bury important caveats in vague language.
Do not over-explain routine edits.
Good final answer shape:
Changed: [short summary]
Verified: [command/check]
Notes: [only if needed]
These guidelines are working if: fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, fewer hidden compatibility paths, clearer ownership of contracts, and clarifying questions happen before implementation rather than after mistakes.
-8604
View File
File diff suppressed because it is too large Load Diff
+8 -7
View File
@@ -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">
@@ -110,6 +110,7 @@
</div> </div>
</div> </div>
</footer> </footer>
<script src="config.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+13 -11
View File
@@ -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">
@@ -105,7 +104,7 @@
<p>展示后台维护的应用推广、公告和下载引导内容。</p> <p>展示后台维护的应用推广、公告和下载引导内容。</p>
</div> </div>
<div class="promotion-list" data-promotion-list> <div class="promotion-list" data-promotion-list>
<div class="api-empty">应用推广内容加载中</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取应用推广…</div>
</div> </div>
</div> </div>
</section> </section>
@@ -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">
@@ -142,11 +141,14 @@
</div> </div>
</div> </div>
</footer> </footer>
<script src="public/js/profile-common.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>
<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>
</body> </body>
</html> </html>
+12 -38
View File
@@ -3,11 +3,11 @@
<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/article-detail.css" /> <link rel="stylesheet" href="public/css/article-detail.css" />
</head> </head>
<body class="page-article-detail" data-article-detail-page> <body class="page-article-detail" data-site-article-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="index.html"
@@ -32,39 +32,11 @@
</div> </div>
</header> </header>
<main>
<section class="inner-hero article-hero">
<div class="container"> <div class="container">
<div class="hero-copy"> <div class="module-meta" data-site-article-status role="status" aria-live="polite"></div>
<div class="eyebrow">Article Detail</div>
<h1 data-article-title>为什么家谱不只是一本名册</h1>
<p class="lead" data-article-summary>
家谱记录的是家族关系,也记录一个家庭如何理解自己的来处、秩序和传承。
</p>
<div class="article-meta" data-article-meta>
<span>家谱知识</span><span>2026.05</span><span>阅读 2,418</span>
</div> </div>
</div> <main data-site-article-detail>
<div class="hero-card article-cover-detail tilt-card"> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取资讯详情…</div>
<span></span>
<p>记录来处,也照见未来。</p>
</div>
</div>
</section>
<section class="section alt">
<div class="container article-layout">
<article class="article-content tilt-card" data-article-content>
<p>谱文详情加载中...</p>
</article>
<aside class="article-side tilt-card">
<h3>相关入口</h3>
<a href="genealogy.html">了解数字家谱</a>
<a href="create-genealogy.html">创建一本家谱</a>
<a href="culture.html">返回家族文化</a>
</aside>
</div>
</section>
</main> </main>
<footer class="footer"> <footer class="footer">
@@ -86,14 +58,14 @@
<h4>文化内容</h4> <h4>文化内容</h4>
<a href="culture.html">家族文化</a <a href="culture.html">家族文化</a
><a href="surname.html">姓氏百科</a ><a href="surname.html">姓氏百科</a
><a href="article-detail.html">家谱知识</a ><a href="news.html">新闻资讯</a
><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">
@@ -102,11 +74,13 @@
</div> </div>
</div> </div>
</footer> </footer>
<script src="public/js/profile-common.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>
<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/site-news-pages.js"></script>
</body> </body>
</html> </html>
+2 -1
View File
@@ -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>
@@ -67,6 +67,7 @@
</div> </div>
</div> </div>
</footer> </footer>
<script src="config.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+28 -1
View File
@@ -1,5 +1,5 @@
(function (root, factory) { (function (root, factory) {
// 根配置负责运行环境接口基础地址。 // 根配置负责运行环境接口基础地址与全站跳转入口
if (typeof module === 'object' && module.exports) { if (typeof module === 'object' && module.exports) {
module.exports = factory; module.exports = factory;
return; return;
@@ -12,6 +12,31 @@
var CLIENT_ID = 'ced7e5f0498645c6ec642dcf450b036f'; var CLIENT_ID = 'ced7e5f0498645c6ec642dcf450b036f';
var TENANT_ID = '000000'; var TENANT_ID = '000000';
var TOKEN_KEY = 'genealogy_auth_token'; var TOKEN_KEY = 'genealogy_auth_token';
var BLOCKED_NAVIGATION_PROTOCOL = /^(?:javascript|data|vbscript):/i;
function createNavigationUtil() {
return {
open: function (url) {
var targetUrl = String(url || '').trim();
if (!targetUrl || BLOCKED_NAVIGATION_PROTOCOL.test(targetUrl) || typeof root.open !== 'function') {
return null;
}
var openedWindow = root.open(targetUrl, '_blank', 'noopener');
if (openedWindow) openedWindow.opener = null;
return openedWindow;
},
replace: function (url) {
var targetUrl = String(url || '').trim();
if (!targetUrl || BLOCKED_NAVIGATION_PROTOCOL.test(targetUrl)) return;
if (root.location && typeof root.location.replace === 'function') {
root.location.replace(targetUrl);
}
}
};
}
// 这里只维护运行环境和后端基础地址,具体接口路径统一放在 ApiClient 中。 // 这里只维护运行环境和后端基础地址,具体接口路径统一放在 ApiClient 中。
var ENVIRONMENTS = { var ENVIRONMENTS = {
@@ -48,6 +73,8 @@
}; };
} }
root.NavigationUtil = root.NavigationUtil || createNavigationUtil();
return { return {
getEnvironment: getEnvironment, getEnvironment: getEnvironment,
getApiBaseUrl: getApiBaseUrl, getApiBaseUrl: getApiBaseUrl,
+65 -23
View File
@@ -7,7 +7,7 @@
<link rel="stylesheet" href="public/css/public.css" /> <link rel="stylesheet" href="public/css/public.css" />
<link rel="stylesheet" href="public/css/create-genealogy.css" /> <link rel="stylesheet" href="public/css/create-genealogy.css" />
</head> </head>
<body class="page-create-genealogy"> <body class="page-create-genealogy" data-genealogy-create-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="index.html"
@@ -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">
填写姓氏谱名、地区与简介,先建立家谱主页,再逐步邀请亲人共同完善资料。 填写姓氏谱名、地区与简介,先建立家谱主页,再逐步邀请亲人共同完善资料。
@@ -59,35 +59,73 @@
<section class="section alt create-form-section"> <section class="section alt create-form-section">
<div class="container"> <div class="container">
<form class="card form-card create-form tilt-card" data-genealogy-form="create" data-success-url="profile-families.html"> <form class="card form-card create-form tilt-card" data-genealogy-create-form novalidate aria-describedby="public-create-status">
<div class="form-ornament"></div> <div class="form-ornament"></div>
<h2>家谱信息</h2> <h2>家谱信息</h2>
<p class="form-note"> <p class="form-note">
先填写基本资料,后续可以继续补充成员、相册和谱文 标有 * 的字段为必填。登录后即可创建,成功后会进入新家谱主页
</p> </p>
<p class="form-note" data-genealogy-create-quota role="status" aria-live="polite">正在读取家谱额度…</p>
<div class="form-grid"> <div class="form-grid">
<input <label class="field" for="publicFamilyName">谱名 <b aria-hidden="true">*</b>
class="input" <input id="publicFamilyName" class="input" name="genealogyName" placeholder="例如:四川武胜汤氏族谱" required />
name="genealogyName" </label>
placeholder="家谱名称,如:四川武胜汤氏族谱" <label class="field" for="publicFamilySurname">主要姓氏 <b aria-hidden="true">*</b>
/> <input id="publicFamilySurname" class="input" name="surname" placeholder="例如:汤" required />
<input class="input" name="surname" placeholder="主要姓氏" /> </label>
<!-- 地区选择器:最终行政区编码回填到 regionCode,供创建家谱接口使用。 --> <div class="field" role="group" aria-labelledby="public-family-region-label" aria-describedby="public-region-status public-create-status">
<span id="public-family-region-label">家谱所在地 <b aria-hidden="true">*</b></span>
<div class="region-picker create-region-picker" data-region-picker> <div class="region-picker create-region-picker" data-region-picker>
<select class="input" name="provinceCode" data-region-level="province"> <label for="publicFamilyProvince">
<select id="publicFamilyProvince" class="input" name="provinceCode" data-region-level="province">
<option value="">请选择省</option> <option value="">请选择省</option>
</select> </select>
<select class="input" name="cityCode" data-region-level="city"> </label>
<label for="publicFamilyCity">
<select id="publicFamilyCity" class="input" name="cityCode" data-region-level="city">
<option value="">请选择市</option> <option value="">请选择市</option>
</select> </select>
<select class="input" name="districtCode" data-region-level="district"> </label>
<label for="publicFamilyDistrict">区县
<select id="publicFamilyDistrict" class="input" name="districtCode" data-region-level="district">
<option value="">请选择区县</option> <option value="">请选择区县</option>
</select> </select>
</label>
<input type="hidden" name="regionCode" data-region-code /> <input type="hidden" name="regionCode" data-region-code />
</div> </div>
<input class="input" name="addressDetail" placeholder="家族所在地" /> <div id="public-region-status" class="form-note" data-region-picker-status role="status" aria-live="polite"></div>
<textarea name="intro" placeholder="家族简介"></textarea> </div>
<button class="btn primary magnetic submit-btn" type="submit">提交创建</button> <label class="field" for="publicAncestralHall">堂号/祠堂
<input id="publicAncestralHall" class="input" name="ancestralHall" placeholder="选填" />
</label>
<label class="field" for="publicOriginPlace">籍贯/起源地
<input id="publicOriginPlace" class="input" name="originPlace" placeholder="选填" />
</label>
<label class="field" for="publicAddressDetail">详细地址
<input id="publicAddressDetail" class="input" name="addressDetail" placeholder="选填" />
</label>
<label class="field" for="publicIntro">家谱简介
<textarea id="publicIntro" name="intro" placeholder="选填"></textarea>
</label>
<label class="field" for="publicVisibility">可见范围
<select id="publicVisibility" class="input" name="visibility">
<option value="1">公开</option>
<option value="2">成员可见</option>
<option value="0">私密</option>
</select>
</label>
<label class="field" for="publicJoinMode">加入方式
<select id="publicJoinMode" class="input" name="joinMode" aria-describedby="public-invite-mode-note">
<option value="1">审核加入</option>
<option value="2">邀请码加入</option>
<option value="0">关闭加入</option>
</select>
<span id="public-invite-mode-note" class="form-note">创建完成后,可在家谱首页的“邀请家人”中生成一次性邀请链接。</span>
</label>
<div id="public-create-status" class="form-note" data-genealogy-create-status role="status" aria-live="polite"></div>
<div>
<button class="btn primary magnetic submit-btn" type="submit" data-genealogy-create-submit>提交创建</button>
</div>
</div> </div>
</form> </form>
</div> </div>
@@ -105,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">
@@ -124,12 +162,16 @@
</div> </div>
</div> </div>
</footer> </footer>
<script src="public/js/jquery360.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/FormUtil.js"></script> <script src="utils/FormUtil.js"></script>
<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/profile-common.js"></script>
<script src="public/js/region-pages.js"></script>
<script src="public/js/genealogy-entry-pages.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+8 -7
View File
@@ -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">
@@ -123,6 +123,7 @@
</div> </div>
</div> </div>
</footer> </footer>
<script src="config.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
File diff suppressed because it is too large Load Diff
@@ -1,129 +0,0 @@
# 内容与协作页面状态收口 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 让缺少正式接口的内容、协作、消息与提醒页面明确展示开发状态,同时保留家族圈动态的真实功能。
**Architecture:** 复用 `public/js/pending-pages.js` 的页面状态契约。每个待开发页面仅增加 `data-feature-status`、中文提示文案和状态脚本引用;脚本阻止主内容区未声明可用的业务链接,不新增接口调用、示例数据或业务脚本。
**Tech Stack:** 原生 JavaScript、静态 HTML/CSS、Node LTS `node:test`
## Global Constraints
- 保留现有页面、布局、视觉类名和导航结构,不删除用户设计。
- `PC.openapi2.json` 中缺少正式接口的功能不得伪造请求或静态数据为真实业务。
- 家族圈动态 `profile-feed.html``profile-feed-edit.html` 已接入正式接口,不能标记为待开发。
- 所有新增注释使用中文;不写入测试账号或密码。
- 当前工作区含用户未提交修改,不执行提交、重置或清理操作。
---
### Task 1: 扩展页面状态测试
**Files:**
- Modify: `tests/pending-pages.test.js`
**Interfaces:**
- Consumes: 每页的 `data-feature-status="pending"``public/js/pending-pages.js`
- Produces: 内容与协作待开发页面的静态约束,以及家族圈动态未被误标记的约束。
- [x] **Step 1: 写入失败测试**
`contentPendingPages` 中列出以下页面:
```js
const contentPendingPages = [
'profile-content.html', 'profile-article.html', 'profile-article-edit.html',
'profile-album.html', 'profile-video.html', 'profile-gift.html',
'profile-gift-edit.html', 'profile-growth.html', 'profile-growth-edit.html',
'profile-memo.html', 'profile-memo-edit.html', 'profile-merit.html',
'profile-merit-edit.html', 'profile-messages.html', 'profile-feedback.html',
'profile-admin-permissions.html', 'profile-data-reminders.html'
];
```
同时断言 `profile-feed.html``profile-feed-edit.html` 不含待开发状态标记,并验证只有 `data-feature-link="available"` 的业务链接可跳转。
- [x] **Step 2: 运行失败测试**
Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; node --test tests/pending-pages.test.js`
Expected: 新增页面尚未标记,测试失败。
### Task 2: 标记内容与协作页面
**Files:**
- Modify: `profile-content.html`
- Modify: `profile-article.html`
- Modify: `profile-article-edit.html`
- Modify: `profile-album.html`
- Modify: `profile-video.html`
- Modify: `profile-gift.html`
- Modify: `profile-gift-edit.html`
- Modify: `profile-growth.html`
- Modify: `profile-growth-edit.html`
- Modify: `profile-memo.html`
- Modify: `profile-memo-edit.html`
- Modify: `profile-merit.html`
- Modify: `profile-merit-edit.html`
- Modify: `profile-messages.html`
- Modify: `profile-feedback.html`
- Modify: `profile-admin-permissions.html`
- Modify: `profile-data-reminders.html`
- Test: `tests/pending-pages.test.js`
**Interfaces:**
- Consumes: `PageAvailability.init()` 读取的 `<body data-feature-status="pending" data-feature-message="..."></body>`
- Produces: 可见的“功能开发中”提示,并拦截主内容区提交与操作按钮。
- [x] **Step 1: 为每页增加状态契约**
在目标页面的 `body` 增加 `data-feature-status="pending"` 和与业务相符的中文 `data-feature-message`。例如谱文页面使用“谱文服务正在开发中,当前页面仅保留设计预览。”
- [x] **Step 2: 加载统一状态脚本**
`ApiClient.js` 的页面在其后加载:
```html
<script src="utils/ApiClient.js"></script>
<script src="public/js/pending-pages.js"></script>
```
`profile-video.html``profile-data-reminders.html` 不依赖 `ApiClient.js`,在 `page-effects.js` 前加载状态脚本。
- [x] **Step 3: 收束业务入口跳转**
待开发页面主内容区的业务链接由状态脚本拦截并显示当前页面提示;侧栏保留页面浏览导航。`profile-content.html` 的家族圈动态入口使用 `data-feature-link="available"`,继续跳转到 `profile-feed.html`
- [x] **Step 4: 运行测试确认通过**
Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; node --test tests/pending-pages.test.js`
Expected: 待开发页面均加载状态脚本,家族圈动态页面未被标记。
### Task 3: 更新进度并做静态验证
**Files:**
- Modify: `docs/规划.md`
- Modify: `docs/superpowers/plans/2026-07-11-content-page-state-closure.md`
- Test: `tests/*.test.js`
**Interfaces:**
- Consumes: 全部页面的本地脚本引用。
- Produces: 已完成第二轮进度与可重复的验证结果。
- [x] **Step 1: 更新总规划的第二轮状态**
`docs/规划.md` 中“第二轮:内容与协作页面状态收口”从未完成更新为已完成,并写明家族圈动态未受影响。
- [x] **Step 2: 执行完整测试**
Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test`
Expected: 所有 Node 测试通过。
- [x] **Step 3: 执行静态检查**
Run: PowerShell 扫描全部 HTML 的 `src="*.js"`,随后运行 `git diff --check`
Expected: 每个本地脚本文件存在,且没有空白格式错误。
@@ -1,192 +0,0 @@
# 家族圈动态接口接入 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:**`PC.openapi2.json` 中已交付的 12 个家族圈动态接口接入既有动态页面,并保持未开发的家谱业务不调用不存在的接口。
**Architecture:** `PC.openapi2.json` 是接口唯一依据;`ApiClient` 提供一一对应的私有请求封装,`feed-pages.js` 只协调页面事件和已定义的接口方法。动态页从 URL 查询参数读取真实 `genealogyId`,不写入示例家谱 ID。
**Tech Stack:** 原生 JavaScript、Axios、Node LTS `node:test`、静态 HTML/CSS。
## Global Constraints
- 所有新增或修改的注释使用清晰中文。
- 不删除现有页面和样式;不接入尚未出现在 `PC.openapi2.json` 的业务。
- 不提交当前工作区:其中含用户已有的未提交修改。
- 动态读取响应目前只有通用 `ListResult`/`PageResult`,页面仅使用 `feedId``feedContent`;后端返回缺少这两个字段时展示明确错误,不猜测字段名。
---
### Task 1: 更新接口边界测试和动态请求测试
**Files:**
- Modify: `tests/api-client-contract.test.js`
- Modify: `tests/pc-scope.test.js`
- Create: `tests/feed-pages.test.js`
**Interfaces:**
- Consumes: `PC.openapi2.json` 的 12 个 `/genealogy/pc/genealogies/{genealogyId}/feeds` 操作。
- Produces: 对客户端路径、HTTP 方法、请求体和页面请求体转换的自动化约束。
- [ ] **Step 1: 写入失败测试**
```js
test('家族圈动态方法使用文档定义的路径和请求体', async () => {
const calls = [];
const client = createClientWithRecorder(calls);
await client.createFeed(900001001, { feedContent: '今天上传一张老照片。' });
await client.feedCommentsPage(900001001, 7001, { pageNum: 1, pageSize: 10 });
assert.deepEqual(calls.map((call) => [call.method, call.url]), [
['post', '/genealogy/pc/genealogies/900001001/feeds'],
['get', '/genealogy/pc/genealogies/900001001/feeds/7001/comments/page']
]);
});
test('动态表单只构造 FamilyFeedBody 字段', () => {
assert.deepEqual(FeedPages.buildFeedBody({
feedContent: '家谱动态', mediaOssIds: '101,102', status: '0'
}), {
feedType: 'text', feedContent: '家谱动态', mediaOssIds: '101,102', status: '0'
});
});
```
- [ ] **Step 2: 运行失败测试**
Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test`
Expected: 动态方法或 `feed-pages.js` 尚不存在导致失败。
- [ ] **Step 3: 调整范围检查**
```js
assert.match(contract, /\/genealogy\/pc\/genealogies\/\{genealogyId\}\/feeds/);
assert.equal(read('profile-feed.html').includes('src="public/js/feed-pages.js"'), true);
assert.equal(read('profile-feed-edit.html').includes('src="public/js/feed-pages.js"'), true);
```
- [ ] **Step 4: 重新运行测试,确认仍处于预期失败状态**
Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test`
Expected: 仅动态实现相关断言失败。
### Task 2: 增加 12 个家族圈 API 方法
**Files:**
- Modify: `utils/ApiClient.js`
- Modify: `tests/api-client-contract.test.js`
**Interfaces:**
- Consumes: `genealogyId:number|string``feedId:number|string``commentId:number|string`
- Produces: `feeds``feedsPage``feedDetail``createFeed``updateFeed``deleteFeed``likeFeed``unlikeFeed``feedComments``feedCommentsPage``createFeedComment``deleteFeedComment`
- [ ] **Step 1: 保持失败测试不变**
```js
await client.createFeed(900001001, { feedContent: '家谱动态' });
assert.equal(calls[0].url, '/genealogy/pc/genealogies/900001001/feeds');
assert.deepEqual(calls[0].data, { feedContent: '家谱动态' });
```
- [ ] **Step 2: 实现最小路径构造**
```js
function feedPath(genealogyId, suffix) {
return '/genealogy/pc/genealogies/' + encodeURIComponent(genealogyId) + '/feeds' + suffix;
}
createFeed: function (genealogyId, body) {
return request('POST', feedPath(genealogyId, ''), { body: body });
}
```
- [ ] **Step 3: 运行测试,确认通过**
Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test`
Expected: 动态客户端路径和请求体断言通过。
### Task 3: 恢复动态页的真实交互
**Files:**
- Create: `public/js/feed-pages.js`
- Modify: `profile-feed.html`
- Modify: `profile-feed-edit.html`
- Modify: `tests/feed-pages.test.js`
**Interfaces:**
- Consumes: `GenealogyApi.defaultClient` 的 12 个动态方法和 URL 中的 `genealogyId`、可选 `feedId`
- Produces: 动态列表、发布/编辑、点赞/取消点赞、评论查看/发布/删除、动态删除事件。
- [ ] **Step 1: 使页面测试失败**
```js
assert.equal(FeedPages.getCurrentGenealogyId('?genealogyId=900001001'), '900001001');
assert.equal(FeedPages.getCurrentGenealogyId(''), '');
assert.equal(FeedPages.getFeedId({ feedId: 7001 }), '7001');
```
- [ ] **Step 2: 实现页面数据转换和事件**
```js
function buildFeedBody(values) {
return {
feedType: 'text',
feedContent: String(values.feedContent || '').trim(),
mediaOssIds: trimOrUndefined(values.mediaOssIds),
status: trimOrUndefined(values.status)
};
}
```
列表只读取 `feedId``feedContent`,并在缺少其中任一字段时显示“动态响应缺少 feedId 或 feedContent,请联系后端补充 DTO”。编辑页以 `feedId` 查询详情并调用 `updateFeed`;列表操作调用点赞、取消点赞、评论、删除接口后重新加载当前页。
- [ ] **Step 3: 挂载脚本和契约字段**
```html
<textarea id="feedContent" name="feedContent" required></textarea>
<script src="public/js/feed-pages.js"></script>
```
移除未在 `FamilyFeedBody` 中定义的“可见范围”提交字段;保持页面布局与既有视觉类名。
- [ ] **Step 4: 运行测试,确认通过**
Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test`
Expected: `feed-pages.test.js` 和已有测试全部通过。
### Task 4: 更新文档来源与最终验证
**Files:**
- Modify: `tests/pc-scope.test.js`
- Modify: `PC.openapi.yaml`
**Interfaces:**
- Consumes: `PC.openapi2.json` 的接口路径。
- Produces: 明确标记旧 YAML 不再为接口源,并使静态检查以 JSON 为准。
- [ ] **Step 1: 写入失败检查**
```js
const contract = JSON.parse(read('PC.openapi2.json'));
assert.ok(contract.paths['/genealogy/pc/genealogies/{genealogyId}/feeds']);
```
- [ ] **Step 2: 标记旧 YAML 失效**
`PC.openapi.yaml` 文件首行写入中文说明:该文件不再作为代码或测试接口源,正式接口源为 `PC.openapi2.json`。不复制或手改 JSON 中未定义的动态响应 DTO。
- [ ] **Step 3: 全量验证**
Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test`
Expected: 全部测试通过,且所有动态页面加载的脚本文件都存在。
## Self-review
- 范围只覆盖最新文档已交付的家族圈动态接口,没有加入家谱创建、成员或世系树接口。
- 所有请求体字段来自 `FamilyFeedBody``FamilyFeedCommentBody`
- 读取响应不使用历史字段兜底,不把未知字段猜成有效数据。
- 任何 URL 缺少 `genealogyId` 的页面都会明确提示,不使用硬编码示例 ID。
@@ -1,140 +0,0 @@
# 家谱基础页面状态收口 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 让尚未获得后端家谱接口的基础页面明确展示开发状态,并阻止表单和操作按钮伪造成功结果。
**Architecture:** 仅在家谱列表、创建/加入、审核、成员管理、邀请、世系和字辈页面加载一个轻量状态脚本。页面通过 `data-feature-status``data-feature-message` 提供文案;脚本统一插入提示条,并拦截页面主内容区的提交与按钮操作。
**Tech Stack:** 原生 JavaScript、静态 HTML/CSS、Node LTS `node:test`
## Global Constraints
- 保留现有页面、布局、视觉类名和导航结构,不删除用户设计。
- 不调用 `PC.openapi2.json` 中不存在的家谱、成员、世系或字辈接口。
- 所有新增注释使用中文;不写入测试账号或密码。
- 当前工作区含用户未提交修改,不执行提交、重置或清理操作。
---
### Task 1: 建立失败测试
**Files:**
- Create: `tests/pending-pages.test.js`
- Modify: `tests/pc-scope.test.js`
**Interfaces:**
- Consumes: `PageAvailability.buildBanner(message)``PageAvailability.isPendingPage(body)`
- Produces: 对状态脚本、页面标记和脚本加载的约束。
- [x] **Step 1: 写入失败测试**
```js
test('待开发页面生成明确且转义后的状态提示', () => {
assert.match(PageAvailability.buildBanner('家谱基础服务正在开发中'), /家谱基础服务正在开发中/);
assert.match(PageAvailability.buildBanner('<script>'), /&lt;script&gt;/);
});
test('家谱基础页面显式加载状态脚本', () => {
assert.equal(read('profile-families.html').includes('data-feature-status="pending"'), true);
assert.equal(read('profile-tree.html').includes('src="public/js/pending-pages.js"'), true);
});
```
- [x] **Step 2: 运行失败测试**
Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; node --test tests/pending-pages.test.js tests/pc-scope.test.js`
Expected: `pending-pages.js` 不存在且页面未标记导致失败。
### Task 2: 实现统一状态脚本与样式
**Files:**
- Create: `public/js/pending-pages.js`
- Modify: `public/css/profile-module.css`
- Test: `tests/pending-pages.test.js`
**Interfaces:**
- Consumes: `<body data-feature-status="pending" data-feature-message="..."></body>`
- Produces: `PageAvailability.buildBanner(message)``PageAvailability.init()`
- [x] **Step 1: 实现最小状态脚本**
```js
function buildBanner(message) {
return '<section class="feature-status-banner" role="status"><strong>功能开发中</strong><p>' + escapeHtml(message) + '</p></section>';
}
```
脚本只在 `data-feature-status="pending"` 页面运行,将提示条放在 `.module-main` 首位;拦截该页面 `main` 区域内的表单提交和非重置按钮,显示同一提示文案。
- [x] **Step 2: 增加复用样式**
```css
.feature-status-banner {
border: 1px solid rgba(196, 146, 69, .38);
border-radius: 16px;
background: #fff8e8;
}
```
- [x] **Step 3: 运行测试确认通过**
Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; node --test tests/pending-pages.test.js tests/pc-scope.test.js`
Expected: 新增状态脚本和页面标记断言通过。
### Task 3: 标记家谱基础页面
**Files:**
- Modify: `profile-families.html`
- Modify: `profile-create-family.html`
- Modify: `profile-join-family.html`
- Modify: `profile-join-review.html`
- Modify: `profile-family-admin.html`
- Modify: `profile-invite.html`
- Modify: `profile-tree.html`
- Modify: `profile-generation.html`
**Interfaces:**
- Consumes: `pending-pages.js` 和每页 `data-feature-message`
- Produces: 明确状态提示,不提交文档外请求。
- [x] **Step 1: 在 body 标记状态**
```html
<body class="page-profile-module" data-feature-status="pending" data-feature-message="家谱成员与权限服务正在开发中,当前页面仅保留设计预览。">
```
- [x] **Step 2: 在 ApiClient 后加载状态脚本**
```html
<script src="utils/ApiClient.js"></script>
<script src="public/js/pending-pages.js"></script>
<script src="public/js/page-effects.js"></script>
```
- [x] **Step 3: 运行完整测试**
Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test`
Expected: 全部测试通过。
### Task 4: 静态验证
**Files:**
- Test: `tests/pc-scope.test.js`
**Interfaces:**
- Consumes: 8 个已标记页面。
- Produces: 所有待开发页面有状态文案,所有页面脚本路径存在。
- [x] **Step 1: 执行脚本引用检查**
Run: PowerShell 扫描全部 HTML 的 `src="*.js"`,确认每个本地脚本文件存在。
- [x] **Step 2: 执行格式检查**
Run: `git diff --check`
Expected: 无缺失脚本和空白格式错误。
@@ -1,139 +0,0 @@
# 官网静态页面补齐 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 补齐官网新闻、法律文档入口、通用状态与工单页面,使无接口的线上服务不再伪装为可用功能。
**Architecture:** 新增静态 HTML 页面共用 `public/css/site-info.css` 和现有官网头尾布局。新闻页只链接现有静态文章与公告详情;法律页面明确标明待法务确认;工单页面复用 `pending-pages.js` 阻止无接口提交。
**Tech Stack:** 静态 HTML、原生 CSS、原生 JavaScript、Node LTS `node:test`
## Global Constraints
- 保留现有页面、布局、视觉类名和导航结构,不删除用户设计。
- 不新增 `PC.openapi2.json` 外的接口调用,不伪造新闻接口、法律协议、工单列表或工单详情数据。
- 法律页面只能显示“待法务确认”说明,发布前必须替换为主体与法务确认的正式文本。
- 所有新增注释使用中文;不写入测试账号或密码。
- 当前工作区含用户未提交修改,不执行提交、重置或清理操作。
---
### Task 1: 建立静态页面约束测试
**Files:**
- Create: `tests/public-static-pages.test.js`
**Interfaces:**
- Consumes: 静态页面文件、`data-feature-status="pending"` 和本地 `href`
- Produces: 页面存在、关键说明、状态脚本与本地链接的可重复校验。
- [x] **Step 1: 写入失败测试**
测试必须断言存在以下文件:
```js
const staticPages = [
'news.html', 'privacy.html', 'children-privacy.html', 'terms.html',
'not-found.html', 'forbidden.html', 'maintenance.html',
'my-tickets.html', 'ticket-detail.html'
];
```
同时断言:`news.html` 含有新闻分类锚点并链接 `article-detail.html``notice-detail.html`;三个法律页面含“待法务确认”;`submit-ticket.html``my-tickets.html``ticket-detail.html` 有待开发状态和 `pending-pages.js`;新页面中的本地 `href` 全部存在。
- [x] **Step 2: 运行失败测试**
Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; node --test tests/public-static-pages.test.js`
Expected: 新页面不存在导致测试失败。
### Task 2: 建立通用静态页面外观与新闻、法律入口
**Files:**
- Create: `public/css/site-info.css`
- Create: `news.html`
- Create: `privacy.html`
- Create: `children-privacy.html`
- Create: `terms.html`
- Modify: `index.html`
**Interfaces:**
- Consumes: 现有 `public/css/public.css``site-header``footer``container``btn``card` 样式。
- Produces: 新闻分类和详情跳转,以及能从首页底部访问的法律入口。
- [x] **Step 1: 创建复用样式**
`site-info.css` 提供 `.site-info-hero``.site-info-layout``.site-info-card``.site-info-status``.site-info-links`,仅处理本轮新增页面的排版和窄屏适配。
- [x] **Step 2: 创建新闻页面**
`news.html` 必须有“平台公告”“家谱文化”分类锚点,并将两条静态内容分别链接到 `notice-detail.html``article-detail.html`
- [x] **Step 3: 创建法律页面并连接首页**
三个法律页面均写明“待法务确认”,并互相提供跳转链接。首页 `footer-legal` 增加三个页面链接。
### Task 3: 建立状态与工单边界
**Files:**
- Create: `not-found.html`
- Create: `forbidden.html`
- Create: `maintenance.html`
- Create: `my-tickets.html`
- Create: `ticket-detail.html`
- Modify: `submit-ticket.html`
**Interfaces:**
- Consumes: `PageAvailability.init()` 读取的待开发页面属性。
- Produces: 三种无接口提交的工单状态,和可返回首页、帮助中心的静态状态页。
- [x] **Step 1: 创建三个通用状态页**
三个页面分别显示“页面不存在”“无访问权限”“系统维护中”,并提供 `index.html` 返回入口。
- [x] **Step 2: 创建工单列表与详情状态页**
`my-tickets.html``ticket-detail.html` 均使用:
```html
<body data-feature-status="pending" data-feature-message="在线工单查询服务正在开发中,当前页面仅保留设计预览。">
```
并在 `public/js/pending-pages.js` 后仅放行帮助中心链接。
- [x] **Step 3: 收束提交工单表单**
`submit-ticket.html` 增加:
```html
<body class="page-submit-ticket" data-feature-status="pending" data-feature-message="在线提交工单服务正在开发中,请先通过帮助中心了解常见问题。">
```
`ApiClient.js` 后加载状态脚本,并为“返回帮助中心”链接添加 `data-feature-link="available"`
### Task 4: 回写进度与验证
**Files:**
- Modify: `docs/规划.md`
- Modify: `docs/superpowers/plans/2026-07-11-public-static-pages.md`
- Test: `tests/*.test.js`
**Interfaces:**
- Consumes: 新增静态页面和全部 HTML 本地脚本引用。
- Produces: 第四轮进度记录和验证结果。
- [x] **Step 1: 写入第四轮完成状态**
`docs/规划.md` 的“实施进度”中增加静态官网页面补齐已完成,并注明法律文本需法务替换。
- [x] **Step 2: 执行完整测试**
Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test`
Expected: 所有 Node 测试通过。
- [x] **Step 3: 执行静态检查**
Run: PowerShell 扫描全部 HTML 的 `src="*.js"`,随后运行 `git diff --check`
Expected: 每个本地脚本文件存在,且没有空白格式错误。
@@ -1,112 +0,0 @@
# 剩余用户 PC 业务入口收口 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 让剩余未有正式接口支持的用户 PC 业务入口显示明确状态,避免跳转到看似可用的分享、会员或家谱管理流程。
**Architecture:** 复用 `public/js/pending-pages.js` 的状态契约和链接收束规则。仅标记 `profile-family-home.html``profile-share.html``profile-services.html`;账号主页、个人资料、安全设置和家族圈动态继续使用现有真实功能。
**Tech Stack:** 原生 JavaScript、静态 HTML/CSS、Node LTS `node:test`
## Global Constraints
- 保留现有页面、布局、视觉类名和导航结构,不删除用户设计。
- `PC.openapi2.json` 中不存在家谱主页、邀请奖励和会员订单的正式接口,不得伪造请求或静态数据为真实业务。
- `profile-family-home.html` 的家族圈动态入口继续跳转到 `profile-feed.html`
- 所有新增注释使用中文;不写入测试账号或密码。
- 当前工作区含用户未提交修改,不执行提交、重置或清理操作。
---
### Task 1: 扩展入口状态测试
**Files:**
- Modify: `tests/pending-pages.test.js`
**Interfaces:**
- Consumes: 目标页面的 `data-feature-status="pending"`、状态脚本引用与 `data-feature-link="available"`
- Produces: 剩余业务入口的静态约束,及真实账户页面不被误标记的约束。
- [x] **Step 1: 写入失败测试**
新增以下数组并加入现有待开发页面检查:
```js
const residualPendingPages = [
'profile-family-home.html',
'profile-share.html',
'profile-services.html'
];
```
新增断言:`profile-family-home.html``profile-feed.html` 链接必须带有 `data-feature-link="available"``profile.html``profile-data.html``profile-security.html` 不得含待开发状态标记。
- [x] **Step 2: 运行失败测试**
Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; node --test tests/pending-pages.test.js`
Expected: 三个剩余入口尚未标记,测试失败。
### Task 2: 标记剩余业务入口
**Files:**
- Modify: `profile-family-home.html`
- Modify: `profile-share.html`
- Modify: `profile-services.html`
- Test: `tests/pending-pages.test.js`
**Interfaces:**
- Consumes: `PageAvailability.init()` 读取的页面状态属性,和 `PageAvailability.shouldBlockLink(link)` 的可用入口约定。
- Produces: 状态提示、业务链接拦截,以及唯一保留的动态入口。
- [x] **Step 1: 标记页面状态**
分别在三个 `body` 中增加 `data-feature-status="pending"` 和对应中文提示:家谱主页使用“家谱概览与管理服务正在开发中,当前页面仅保留设计预览。”;应用分享使用“应用分享与邀请奖励服务正在开发中,当前页面仅保留设计预览。”;帮助与服务使用“会员与在线服务正在开发中,当前页面仅保留设计预览。”
- [x] **Step 2: 加载统一状态脚本并放行动态入口**
每页都在 `utils/ApiClient.js` 后增加:
```html
<script src="utils/ApiClient.js"></script>
<script src="public/js/pending-pages.js"></script>
```
`profile-family-home.html` 中将动态链接改为:
```html
<a class="btn primary magnetic" href="profile-feed.html" data-feature-link="available">发布动态</a>
```
- [x] **Step 3: 运行测试确认通过**
Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; node --test tests/pending-pages.test.js`
Expected: 全部状态页面和真实账户页面断言通过。
### Task 3: 更新进度与验证
**Files:**
- Modify: `docs/规划.md`
- Modify: `docs/superpowers/plans/2026-07-11-residual-pc-entry-closure.md`
- Test: `tests/*.test.js`
**Interfaces:**
- Consumes: 全部 HTML 的本地脚本引用。
- Produces: 已完成第三轮进度和可重复验证记录。
- [x] **Step 1: 写入第三轮完成状态**
`docs/规划.md` 的“实施进度”中增加已完成第三轮,说明三类剩余 PC 业务入口已收口,家族圈动态未受影响。
- [x] **Step 2: 执行完整测试**
Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test`
Expected: 所有 Node 测试通过。
- [x] **Step 3: 执行静态检查**
Run: PowerShell 扫描全部 HTML 的 `src="*.js"`,随后运行 `git diff --check`
Expected: 每个本地脚本文件存在,且没有空白格式错误。
@@ -1,258 +0,0 @@
# 家谱创建首批 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 在约一小时的首批施工中冻结剩余家谱生命周期 PC 请求契约,并把 `profile-create-family.html` 从 pending 预览改成可用的真实创建家谱页面。
**Architecture:** `utils/ApiClient.js` 唯一拥有请求路径和 body 白名单;`public/js/genealogy-entry-pages.js` 扩展家谱创建的纯构造、校验、响应规范化和页面初始化;现有 `region-pages.js` 负责三级地区选项,`upload-pages.js` 负责生成 `coverOssId`。创建成功后只使用响应中的稳定 `genealogyId` 跳转到家谱主页。
**Tech Stack:** 静态 HTML、原生 JavaScript UMD、Axios、Node `node:test`
## Global Constraints
- 后端 `D:\WorkSpace\Java\Genealogy` 全程只读。
- 只调用 `/genealogy/pc/**`,不调用 APP 或后台管理接口。
- 不允许用户手填家谱 ID、申请 ID、用户 ID 或 OSS ID。
- 每个生产行为必须先有失败测试并实际运行 RED。
- 每个阶段完成后运行聚焦测试并更新 `docs/PC接口对接规划.md`
---
### Task 1: 冻结家谱生命周期 ApiClient 契约
**Files:**
- Modify: `tests/api-client-contract.test.js`
- Modify: `utils/ApiClient.js`
**Interfaces:**
- Produces:
- `createGenealogy(body)`
- `applyToGenealogy(genealogyId, body)`
- `myGenealogyJoinApplies()`
- `pendingGenealogyJoinApplies(genealogyId)`
- `auditGenealogyJoinApply(genealogyId, applyId, body)`
- `cancelGenealogyJoinApply(applyId)`
- [x] **Step 1: 写失败的路径和 body 白名单测试**
测试固定以下请求:
```js
await client.createGenealogy({
genealogyName: '汤氏家谱',
surname: '汤',
regionCode: '511622',
coverOssId: '2062179707935264769',
legacyField: 'drop'
});
await client.applyToGenealogy(genealogyId, {
applicantName: '申请人',
phone: '19100000000',
relationDesc: '族亲',
applyReason: '申请加入',
inviterUserId: 'drop-without-safe-source'
});
await client.auditGenealogyJoinApply(genealogyId, applyId, {
status: '1',
auditRemark: '资料一致',
legacyField: 'drop'
});
```
断言:
- 创建请求为 `POST /genealogy/pc/genealogies`,只保留 `genealogyName/surname/regionCode/ancestralHall/originPlace/addressDetail/coverOssId/intro/visibility/joinMode`
- 加入请求为 `POST /genealogy/pc/genealogies/{genealogyId}/join-applies`,本轮安全 PC 页面只保留 `applicantName/phone/relationDesc/applyReason`,不发送没有安全来源的 `inviterUserId`
- 我的申请、待审核、审核和撤销使用后端 PC Controller 的完整路径。
- 审核 body 只保留 `status/auditRemark`
- 所有 path ID 拒绝不安全数字。
- [x] **Step 2: 运行 RED**
Run:
```powershell
node --test --test-name-pattern "genealogy lifecycle" tests/api-client-contract.test.js
```
Expected: FAIL,新方法尚不存在。
- [x] **Step 3: 最小实现方法和白名单**
使用现有 `request()``pickDefined()``toRequiredPathId()`;不增加兼容别名或 APP fallback。
- [x] **Step 4: 运行 GREEN**
Run:
```powershell
node --test tests/api-client-contract.test.js
node --check utils/ApiClient.js
```
Expected: PASS。
---
### Task 2: 家谱创建纯行为
**Files:**
- Modify: `tests/genealogy-entry-pages.test.js`
- Modify: `public/js/genealogy-entry-pages.js`
**Interfaces:**
- Consumes: `createGenealogy(body)``genealogyQuota()`
- Produces:
- `buildGenealogyCreateBody(values)`
- `validateGenealogyCreateBody(body)`
- `normalizeCreatedGenealogy(item)`
- `buildCreatedGenealogyUrl(item)`
- [x] **Step 1: 写失败的创建行为测试**
```js
assert.deepEqual(GenealogyEntryPages.buildGenealogyCreateBody({
genealogyName: ' 汤氏家谱 ',
surname: ' 汤 ',
regionCode: '511622',
ancestralHall: '',
coverOssId: '2062179707935264769',
visibility: '1',
joinMode: '1',
manualId: 'drop'
}), {
genealogyName: '汤氏家谱',
surname: '汤',
regionCode: '511622',
coverOssId: '2062179707935264769',
visibility: '1',
joinMode: '1'
});
```
同时断言:
- 缺少谱名、姓氏或地区时返回明确校验错误。
- `visibility` 只允许 `0/1/2``joinMode` 只允许 `0/1/2`
- `coverOssId` 必须是安全字符串 ID;没有文件可省略。
- 额度 `createRemaining <= 0` 时阻止提交。
- 创建响应必须有稳定 `genealogyId`、谱名和姓氏。
- 跳转地址只能由响应 ID 产生:`profile-family-home.html?genealogyId=...`
- [x] **Step 2: 运行 RED**
Run:
```powershell
node --test tests/genealogy-entry-pages.test.js
```
Expected: FAIL,纯函数不存在。
- [x] **Step 3: 最小实现纯函数**
严格构造 `AppGenealogyCreateBody`,可选空值省略,不读取页面外字段。
- [x] **Step 4: 运行 GREEN**
Run:
```powershell
node --test tests/genealogy-entry-pages.test.js
node --check public/js/genealogy-entry-pages.js
```
Expected: PASS。
---
### Task 3: 开放创建家谱页面
**Files:**
- Modify: `profile-create-family.html`
- Modify: `public/js/genealogy-entry-pages.js`
- Modify: `tests/genealogy-entry-pages.test.js`
- Modify: `tests/pending-pages.test.js`
- Modify: `tests/security-upload-scope.test.js`
- Modify: `tests/pc-scope.test.js`
**Interfaces:**
- Consumes: Task 1 和 Task 2、`RegionPages``UploadPages`
- Produces: `initGenealogyCreatePage()`
- [x] **Step 1: 写失败的页面测试**
断言页面:
- 不再包含 `data-feature-status="pending"``pending-pages.js`
- 加载 `region-pages.js``md5.js``upload-pages.js``genealogy-entry-pages.js`
- 存在隐藏 `coverOssId` 与文件选择控件,不能手填 OSS ID。
- 不存在 `genealogyId/applyId/inviterUserId` 输入。
- 表单只包含后端创建 DTO 字段和地区选择辅助字段。
- 创建按钮、状态区域和额度区域可由脚本控制。
- [x] **Step 2: 运行 RED**
Run:
```powershell
node --test tests/genealogy-entry-pages.test.js tests/pending-pages.test.js tests/security-upload-scope.test.js tests/pc-scope.test.js
```
Expected: FAIL,页面仍 pending 且缺上传/初始化行为。
- [x] **Step 3: 实现页面初始化和提交**
初始化顺序:
1. 校验登录态。
2. 调用 `genealogyQuota()` 并展示真实额度。
3. `RegionPages` 初始化三级地区。
4. `UploadPages` 通过 `data-upload-target` 把封面上传结果写入隐藏 `coverOssId`
5. 提交前同步表单、构造并校验 body。
6. 使用 `writePending` 阻止重复提交。
7. 调用 `createGenealogy(body)`
8. 规范化响应并跳转到真实家谱主页。
401 清登录态;403 保留登录态;业务错误显示在表单状态区域。
- [x] **Step 4: 运行 GREEN**
Run:
```powershell
node --test tests/genealogy-entry-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js tests/security-upload-scope.test.js tests/pc-scope.test.js
node --check public/js/genealogy-entry-pages.js
```
Expected: PASS。
---
### Task 4: 首批收口
**Files:**
- Modify: `docs/PC接口对接规划.md`
- Modify: `docs/superpowers/plans/2026-07-29-genealogy-create-first-batch.md`
- [x] **Step 1: 更新契约和阶段状态**
记录 `AppGenealogyCreateBody``AppGenealogyVo`、额度、权限、上传派生字段、加入申请 ApiClient 契约及仍未开放的加入 UI。
- [x] **Step 2: 聚焦和全量验证**
Run:
```powershell
node --test tests/api-client-contract.test.js tests/genealogy-entry-pages.test.js tests/pending-pages.test.js tests/security-upload-scope.test.js tests/pc-scope.test.js
npm test
git -c safe.directory=D:/WorkSpace/Web/jiapu diff --check
```
Expected: 全部 PASS。
- [x] **Step 3: 浏览器验证并报告**
使用真实账号验证创建页登录态、额度、地区加载、无手填 ID、控制台错误和提交前校验。不得为测试消耗真实创建额度;除非用户明确授权,不执行最终创建写操作。
@@ -1,509 +0,0 @@
# 阶段 6 PC 功能页面 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. Project rules reserve code edits for the primary agent; subagents may only perform read-only exploration and review.
**Goal:** 实现所有当前 PC 接口已具备闭环但前端缺失或未开放的谱文、相册/照片、祭祀/祭品、管理员邀约、指定账号绑定和家谱成员管理页面。
**Architecture:** `utils/ApiClient.js` 唯一拥有 PC method/path/query/body;每个业务使用独立 UMD 页面脚本,页面只消费脚本导出的规范化、校验、渲染和初始化接口。`profile-common.js` 提供家谱上下文,`upload-pages.js` 产生 OSS ID,所有写操作使用稳定字符串 ID、防重复提交并在成功后重读。
**Tech Stack:** 静态 HTML、原生 JavaScript UMD、Axios 请求封装、Node `node:test`、现有 profile CSS、wangEditor v5、统一分片上传。
## Global Constraints
- 后端目录 `D:/WorkSpace/Java/Genealogy` 严格只读。
- 只使用 `/genealogy/pc/**`,不得使用 APP 接口或旧路径 fallback。
- 不允许手填业务 ID、用户 ID 或 OSS ID;int64 在浏览器边界保存为十进制字符串。
- 普通列表无法重读停用记录的模块固定提交 `status=0`
- 每个模块严格执行 RED → GREEN → 聚焦测试 → 真实账号非破坏性验证 → 规划更新。
- 当前工作树已有前几阶段改动;不得重置、覆盖或提交无关文件。除非用户另行要求,本计划不创建 Git commit。
---
### Task 1: 扩展 PC ApiClient 契约
**Files:**
- Modify: `utils/ApiClient.js`
- Modify: `tests/api-client-contract.test.js`
**Interfaces:**
- Produces:
- `articles(genealogyId)`, `articleDetail(genealogyId, articleId)`, `createArticle(genealogyId, body)`, `updateArticle(genealogyId, articleId, body)`
- `albums(genealogyId)`, `createAlbum(genealogyId, body)`, `updateAlbum(genealogyId, albumId, body)`, `albumPhotos(genealogyId, albumId)`, `createAlbumPhoto(genealogyId, albumId, body)`
- `ceremonies(genealogyId)`, `ceremonyDetail(genealogyId, ceremonyId)`, `createCeremony`, `updateCeremony`, `ceremonyGifts`, `createCeremonyGift`
- `replaceCeremonyInvitees(genealogyId, ceremonyId, inviteeUserIds)`
- `genealogyMembers(genealogyId)`, `genealogyMemberOptions(genealogyId)`, `updateGenealogyMember`, `removeGenealogyMember`, `leaveGenealogy`, `transferGenealogyOwner`
- [x] **Step 1: 写失败的路径和 body 白名单测试**
```js
await client.createArticle('9007199254740993001', {
articleTitle: '族史',
articleContent: '<p>正文</p>',
coverOssId: '9007199254740993002',
appUserId: 'must-drop',
status: '0'
});
assert.deepEqual(calls[0].data, {
articleTitle: '族史',
articleContent: '<p>正文</p>',
coverOssId: '9007199254740993002',
status: '0'
});
await client.replaceCeremonyInvitees(
'9007199254740993001',
'9007199254740993003',
['9007199254740993004']
);
assert.deepEqual(calls.at(-1).data, {
inviteeUserIds: ['9007199254740993004']
});
```
- [x] **Step 2: 运行 RED**
Run: `node --test --test-name-pattern "article|album|ceremony admin|genealogy member" tests/api-client-contract.test.js`
Expected: FAIL,原因是新方法不存在或仍只有删除方法。
- [x] **Step 3: 最小实现所有方法和白名单**
```js
var ARTICLE_FIELDS = [
'categoryId', 'articleTitle', 'articleSummary', 'coverOssId',
'articleContent', 'authorName', 'sortOrder', 'status'
];
var ALBUM_FIELDS = ['albumName', 'albumDesc', 'coverOssId', 'sortOrder', 'status'];
var ALBUM_PHOTO_FIELDS = [
'ossId', 'photoTitle', 'photoDesc', 'photographer',
'shootTime', 'sortOrder', 'status'
];
var CEREMONY_FIELDS = [
'ceremonyType', 'ceremonyTitle', 'ceremonyDesc', 'ceremonyTime',
'location', 'locationAddress', 'longitude', 'latitude',
'coverOssId', 'sortOrder', 'status'
];
var CEREMONY_GIFT_FIELDS = ['giverName', 'giftAmount', 'giftMessage'];
var MEMBER_UPDATE_FIELDS = ['memberName', 'relationName', 'roleType', 'lineagePersonId'];
```
每个方法只通过既有 `request()``pickDefined()``toRequiredPathId()` 和路径 builder 发请求;成员 options 按 YAML 不发送 Query。
- [x] **Step 4: 运行 GREEN 和语法检查**
Run: `node --test tests/api-client-contract.test.js`
Run: `node --check utils/ApiClient.js`
Expected: PASS。
---
### Task 2: 谱文列表、详情、创建、修改和删除
**Files:**
- Create: `public/js/article-pages.js`
- Create: `tests/article-pages.test.js`
- Modify: `profile-article.html`
- Modify: `profile-article-edit.html`
- Modify: `tests/pending-pages.test.js`
- Modify: `tests/pc-scope.test.js`
**Interfaces:**
- Consumes: Task 1 article ApiClient methods、`ProfileUI.requireGenealogyContext()``ProfileUpload.bindUploadField()``RichEditorPages`
- Produces: `buildArticleBody`, `normalizeArticle`, `normalizeArticles`, `matchesArticle`, `renderArticleList`, `renderArticleDetail`, `initArticleListPage`, `initArticleEditPage`
- [x] **Step 1: 写失败的谱文行为测试**
```js
assert.deepEqual(ArticlePages.buildArticleBody({
articleTitle: '族史',
articleContent: '<p>正文</p>',
coverOssId: '9007199254740993002',
categoryId: '',
status: '1'
}), {
articleTitle: '族史',
articleContent: '<p>正文</p>',
coverOssId: '9007199254740993002',
status: '0'
});
assert.equal(
ArticlePages.normalizeArticle({ articleId: Number.MAX_SAFE_INTEGER + 1 }),
null
);
assert.doesNotMatch(
ArticlePages.renderArticleDetail({
articleId: '9007199254740993003',
articleTitle: '<img src=x onerror=alert(1)>',
articleContent: '<script>alert(1)</script>',
status: '0'
}),
/<script|onerror/
);
```
页面测试必须断言两个 HTML 不再 pending、加载 `article-pages.js`、没有手填 `articleId/categoryId/coverOssId/status`,列表页存在详情区域,编辑页存在标题/摘要/正文/作者/封面上传。
- [x] **Step 2: 运行 RED**
Run: `node --test tests/article-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js tests/pc-scope.test.js`
Expected: FAIL,原因是模块不存在且页面仍为 pending。
- [x] **Step 3: 实现谱文脚本和页面**
```js
function buildArticleBody(values) {
return compact({
articleTitle: requiredText(values.articleTitle, '请输入谱文标题'),
articleSummary: optionalText(values.articleSummary),
coverOssId: normalizeNullableId(values.coverOssId),
articleContent: requiredText(values.articleContent, '请输入谱文正文'),
authorName: optionalText(values.authorName),
sortOrder: normalizeOptionalSafeInteger(values.sortOrder),
status: '0'
});
}
```
列表从直接数组读取;详情按 `articleId` 匹配;列表没有独立分类选项源时不渲染 `categoryId` 输入。新增/修改成功后用返回 ID 或 URL ID 重读详情,删除后重读列表。所有可见文本转义,正文使用现有安全富文本展示规则。
- [x] **Step 4: 运行 GREEN**
Run: `node --test tests/article-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js tests/pc-scope.test.js`
Run: `node --check public/js/article-pages.js`
Expected: PASS。
- [x] **Step 5: 更新规划并报告谱文阶段**
`docs/PC接口对接规划.md` 记录 ArticleBody/ArticleVo 字段、C20 谱文状态、`categoryId` query 差异、封面 URL 和停用记录阻断。
---
### Task 3: 相册列表、相册编辑和照片管理
**Files:**
- Create: `profile-album-edit.html`
- Create: `profile-album-detail.html`
- Create: `public/js/album-pages.js`
- Create: `tests/album-pages.test.js`
- Modify: `profile-album.html`
- Modify: `tests/pending-pages.test.js`
**Interfaces:**
- Consumes: Task 1 album ApiClient methods、ProfileUI、ProfileUpload
- Produces: `buildAlbumBody`, `buildAlbumPhotoBody`, `normalizeAlbum`, `normalizeAlbumPhoto`, `findAlbumById`, `renderAlbumList`, `renderPhotoList`, three page initializers
- [x] **Step 1: 写失败的相册/照片测试**
```js
assert.deepEqual(AlbumPages.buildAlbumPhotoBody({
ossId: '9007199254740993010',
photoTitle: '祠堂合影',
shootTime: '2026-07-29T10:30',
status: '1'
}), {
ossId: '9007199254740993010',
photoTitle: '祠堂合影',
shootTime: '2026-07-29 10:30:00',
status: '0'
});
assert.equal(AlbumPages.findAlbumById(
[{ albumId: '9007199254740993011', albumName: '旧影', status: '0' }],
'9007199254740993011'
).albumName, '旧影');
```
页面测试断言 OSS ID 只能是 hidden/upload target,列表链接传播 `genealogyId + albumId`,详情页具有照片上传和真实删除入口。
- [x] **Step 2: 运行 RED**
Run: `node --test tests/album-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js`
Expected: FAIL,原因是脚本和新增页面不存在。
- [x] **Step 3: 实现三个页面和脚本**
```js
function buildAlbumBody(values) {
return compact({
albumName: requiredText(values.albumName, '请输入相册名称'),
albumDesc: optionalText(values.albumDesc),
coverOssId: normalizeNullableId(values.coverOssId),
sortOrder: normalizeOptionalSafeInteger(values.sortOrder),
status: '0'
});
}
```
相册编辑/详情通过重新读取相册列表并按稳定 `albumId` 匹配;照片列表调用 `/photos`。照片新增后重读照片列表;删除照片后重读并更新相册计数;删除相册后返回并重读相册列表。
- [x] **Step 4: 运行 GREEN**
Run: `node --test tests/album-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js`
Run: `node --check public/js/album-pages.js`
Expected: PASS。
- [x] **Step 5: 更新规划并报告相册阶段**
记录 AlbumBody、AlbumPhotoBody、AlbumVo、AlbumPhotoVo、文件 URL 缺口和停用记录阻断。
---
### Task 4: 祭祀活动、祭品和管理员邀约
**Files:**
- Create: `profile-ceremony.html`
- Create: `profile-ceremony-detail.html`
- Create: `public/js/ceremony-admin-pages.js`
- Create: `tests/ceremony-admin-pages.test.js`
- Modify: `profile-gift-edit.html`
- Modify: `profile-gift.html`
- Modify: `tests/pending-pages.test.js`
**Interfaces:**
- Consumes: Task 1 ceremony/member ApiClient methods、ProfileUI、ProfileUpload
- Produces: `buildCeremonyBody`, `buildCeremonyGiftBody`, `normalizeCeremony`, `normalizeCeremonyGift`, `normalizeInviteeOption`, `buildInviteeBody`, list/detail/edit initializers
- [x] **Step 1: 写失败的祭祀测试**
```js
assert.deepEqual(CeremonyAdminPages.buildCeremonyGiftBody({
giverName: '宗亲',
giftAmount: '88.50',
giftMessage: '敬献'
}), {
giverName: '宗亲',
giftAmount: 88.5,
giftMessage: '敬献'
});
assert.throws(() => CeremonyAdminPages.buildCeremonyGiftBody({
giftAmount: '-1'
}));
assert.deepEqual(CeremonyAdminPages.buildInviteeBody([
'9007199254740993020',
'9007199254740993020',
'9007199254740993021'
]), {
inviteeUserIds: ['9007199254740993020', '9007199254740993021']
});
```
页面测试断言个人邀请脚本和管理员脚本职责分离;活动编辑没有手填封面 ID;详情页有祭品和受邀成员选择器。
- [x] **Step 2: 运行 RED**
Run: `node --test tests/ceremony-admin-pages.test.js tests/ceremony-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js`
Expected: FAIL,原因是管理模块和页面不存在。
- [x] **Step 3: 实现活动、祭品和邀约管理**
```js
function buildInviteeBody(ids) {
var values = Array.from(new Set(ids.map(normalizeId).filter(Boolean)));
return { inviteeUserIds: values };
}
```
活动固定 `status=0`;经纬度必须成对且范围合法;祭品金额非负并通过精确 JSON 数值检查。管理员候选过滤空 `appUserId`,提交空数组时显示取消全部待响应邀请确认。各写操作完成后重读活动详情、祭品或邀请名单。
- [x] **Step 4: 运行 GREEN**
Run: `node --test tests/ceremony-admin-pages.test.js tests/ceremony-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js`
Run: `node --check public/js/ceremony-admin-pages.js`
Expected: PASS。
- [x] **Step 5: 更新规划并报告祭祀阶段**
记录 CeremonyBody、CeremonyGiftBody、CeremonyInviteesBody、权限与金额/文件/停用阻断。
---
### Task 5: 启用 SPECIFIED 世系账号绑定
**Files:**
- Modify: `public/js/lineage-pages.js`
- Modify: `profile-tree.html`
- Modify: `tests/lineage-pages.test.js`
**Interfaces:**
- Consumes: Task 1 `genealogyMemberOptions(genealogyId)`
- Produces: `normalizeBindingMemberOption`, `renderBindingMemberOptions`;扩展现有表单初始化和 `buildLineagePersonBody`
- [x] **Step 1: 写失败的绑定测试**
```js
assert.deepEqual(LineagePages.normalizeBindingMemberOption({
memberId: '9007199254740993030',
appUserId: '9007199254740993031',
memberName: '族员甲',
appUserNickName: '账号甲',
status: '0'
}), {
value: '9007199254740993031',
label: '族员甲(账号甲)'
});
assert.equal(LineagePages.normalizeBindingMemberOption({
memberId: '9007199254740993032',
appUserId: null,
status: '0'
}), null);
```
页面测试断言 `SPECIFIED` 不再 disabled、只显示成员选择器且不存在 appUserId 文本输入。
- [x] **Step 2: 运行 RED**
Run: `node --test tests/lineage-pages.test.js tests/api-client-contract.test.js`
Expected: FAIL,原因是选项仍禁用且规范化函数不存在。
- [x] **Step 3: 最小启用成员选项绑定**
只在 `canManage` 时显示 `SPECIFIED`;选择后内部保存字符串 `appUserId``NONE/SELF` 必须删除 body 中的 `appUserId``SPECIFIED` 必须选择有效 option;403 作为权限错误处理,不清登录态。
- [x] **Step 4: 运行 GREEN**
Run: `node --test tests/lineage-pages.test.js tests/api-client-contract.test.js`
Run: `node --check public/js/lineage-pages.js`
Expected: PASS。
- [x] **Step 5: 更新规划并报告绑定阶段**
把 C15 更新为“由同家谱正常成员 options 解决;未绑定账号的成员不可选”,保留没有任意 AppUser 搜索能力的边界。
---
### Task 6: 家谱成员管理、退出和所有权转移
**Files:**
- Create: `public/js/member-admin-pages.js`
- Create: `tests/member-admin-pages.test.js`
- Modify: `profile-family-admin.html`
- Modify: `tests/pending-pages.test.js`
**Interfaces:**
- Consumes: Task 1 member ApiClient methods、`lineagePersonOptions`、ProfileUI
- Produces: `normalizeMember`, `normalizeMemberOptions`, `buildMemberUpdateBody`, `canEditMember`, `canRemoveMember`, `canTransferOwner`, `renderMemberList`, `initMemberAdminPage`
- [x] **Step 1: 写失败的成员管理测试**
```js
assert.deepEqual(MemberAdminPages.buildMemberUpdateBody({
memberName: '族员甲',
relationName: '侄',
roleType: 'editor',
lineagePersonId: '9007199254740993040',
appUserId: 'must-drop'
}), {
memberName: '族员甲',
relationName: '侄',
roleType: 'editor',
lineagePersonId: '9007199254740993040'
});
assert.throws(() => MemberAdminPages.buildMemberUpdateBody({
roleType: 'owner'
}));
```
渲染测试断言手机号和内部 ID 不可见;owner 不显示移除按钮;当前 owner 不显示退出按钮而显示所有权转移。
- [x] **Step 2: 运行 RED**
Run: `node --test tests/member-admin-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js`
Expected: FAIL,原因是脚本不存在且页面 pending。
- [x] **Step 3: 实现成员管理页面**
成员列表支持关键词刷新;编辑只提交 `memberName/relationName/roleType/lineagePersonId`。角色选择只有 `admin/editor/member`。移除、退出、转让都二次确认并使用响应中的 memberId;成功后重读成员列表,退出成功后清除当前家谱上下文并返回家谱选择页。
- [x] **Step 4: 运行 GREEN**
Run: `node --test tests/member-admin-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js`
Run: `node --check public/js/member-admin-pages.js`
Expected: PASS。
- [x] **Step 5: 更新规划并报告成员阶段**
记录成员更新字段、角色差异、退出/移除/转让权限与世系关系解除继续阻断。
---
### Task 7: 导航开放、真实验证和阶段收尾
**Files:**
- Modify: `profile.html`
- Modify: `profile-content.html`
- Modify: `profile-families.html`
- Modify: `docs/PC接口对接规划.md`
- Modify: `tests/pending-pages.test.js`
- Modify: `tests/pc-scope.test.js`
**Interfaces:**
- Consumes: Tasks 16 completed pages
- Produces: 所有功能的稳定导航入口和阶段 6 完成记录
- [x] **Step 1: 写失败的入口行为测试**
```js
for (const page of [
'profile-article.html',
'profile-album.html',
'profile-ceremony.html',
'profile-family-admin.html'
]) {
assert.match(navigationHtml, new RegExp(page.replace('.', '\\.')));
}
```
测试同时断言新增/开放页面不加载 `pending-pages.js`,不存在 APP path、手填 ID 或原始 JSON。
- [x] **Step 2: 运行 RED**
Run: `node --test tests/pending-pages.test.js tests/pc-scope.test.js`
Expected: FAIL,原因是导航尚未开放。
- [x] **Step 3: 开放入口并更新规划**
个人中心和家谱内容入口链接到谱文、相册和祭祀;家谱管理入口链接到成员管理。规划逐模块记录字段表、验证结果、契约差异和唯一未实现的世系关系解除。
- [x] **Step 4: 聚焦和全量自动验证**
Run:
```powershell
node --test tests/article-pages.test.js tests/album-pages.test.js
node --test tests/ceremony-admin-pages.test.js tests/ceremony-pages.test.js
node --test tests/lineage-pages.test.js tests/member-admin-pages.test.js
node --test tests/api-client-contract.test.js tests/pending-pages.test.js tests/pc-scope.test.js
npm test
node --check public/js/article-pages.js
node --check public/js/album-pages.js
node --check public/js/ceremony-admin-pages.js
node --check public/js/member-admin-pages.js
git -c safe.directory=D:/WorkSpace/Web/jiapu diff --check
```
Expected: 全部 PASS。
- [x] **Step 5: 真实账号浏览器验证**
逐页验证登录态、家谱上下文、无数据空状态、筛选、隐私隐藏和控制台错误。真实账号没有家谱时,确认业务请求被阻止且所有入口回到 `profile-families.html?next=...`;不得制造家谱或业务记录。
- [x] **Step 6: 独立只读复核和最终报告**
安排默认子代理只读检查所有阶段 6 文件,主代理按 file:line 抽查。报告使用 `Changed / Verified / Blocked / Next`,明确世系关系解除、文件 URL、停用记录和缺真实数据写入验证的剩余风险。
@@ -1,155 +0,0 @@
# 剩余 PC 页面对接设计
## 目标
`D:\WorkSpace\Java\Genealogy` 中的 PC Controller、DTO、VO 和 Service 实现为本轮唯一运行时契约,开放当前仍为 pending、但后端 PC 能力已经形成闭环的页面。
后端目录全程只读。前端不得调用 `/genealogy/app/**`、后台管理端 `/genealogy/**`,不得猜测接口、字段、业务 ID、OSS ID、支付动作、邀请令牌或分享地址。
## 契约所有者
- `utils/ApiClient.js` 唯一拥有 PC method、path、query 和 body 白名单。
- 页面脚本只接收 `ApiClient` 解包后的业务数据,不自行拼接接口路径。
- 页面中的家谱、申请、反馈、套餐和订单 ID 只能来自 URL、当前家谱上下文或后端响应。
- 后端 PC Controller 继承共用 Support 类时,以 PC Controller 的 `@RequestMapping` 作为路径,以 Support 类方法、DTO、VO 和 Service 作为字段与行为依据;这不构成借用 APP 接口。
## 范围
### 1. 家谱创建
页面:`profile-create-family.html`
接入:
- `POST /genealogy/pc/genealogies`
- `GET /genealogy/pc/genealogies/quota`
- `GET /genealogy/pc/region/children?parentCode=0`
- `GET /genealogy/pc/region/children?parentCode={regionCode}`
请求只提交 `AppGenealogyCreateBody`
- 必填:`genealogyName``surname``regionCode`
- 可选:`ancestralHall``originPlace``addressDetail``coverOssId``intro``visibility``joinMode`
`regionCode` 由省市区选择产生;`coverOssId` 只能由文件上传产生。成功后使用响应中的真实 `genealogyId` 写入当前家谱上下文并进入家谱主页。额度不足、未登录业务用户、地区无效和服务端创建失败均展示后端错误,不降级到虚假成功。
### 2. 加入申请与审核
页面:`profile-join-family.html``join-genealogy.html``profile-join-review.html`
接入:
- `POST /genealogy/pc/genealogies/{genealogyId}/join-applies`
- `GET /genealogy/pc/genealogies/join-applies/mine`
- `DELETE /genealogy/pc/genealogies/join-applies/{applyId}`
- `GET /genealogy/pc/genealogies/{genealogyId}/join-applies/pending`
- `PUT /genealogy/pc/genealogies/{genealogyId}/join-applies/{applyId}/audit`
加入申请字段:
- `applicantName`:可选,最长 50
- `phone`:可选,最长 30
- `relationDesc`:可选,最长 100
- `applyReason`:可选,最长 500
- `inviterUserId`:仅后端 `INVITE` 模式使用;当前 PC 没有安全邀请来源,前端不展示、不提交
审核字段:
- `status`:必填,只允许 `1` 通过、`2` 拒绝
- `auditRemark`:可选,最长 500
申请列表、审核列表和写入结果使用完整 `GenealogyJoinApplyVo`。页面隐藏申请人、邀请人和审核人的内部用户 ID;手机号只在家谱管理员审核场景按后端响应展示。撤销只允许当前申请人的待审核申请;审核只对当前家谱待审核申请开放。重复提交、已处理申请、已是成员、家谱关闭加入和权限不足均使用真实错误分支。
### 3. 意见反馈与工单
页面:`profile-feedback.html``submit-ticket.html``my-tickets.html``ticket-detail.html`
接入:
- `POST /genealogy/pc/feedback`
- `GET /genealogy/pc/feedback`
请求只提交 `AppFeedbackBody`
- `feedbackContent`:必填
- `feedbackType`:可选,空值由后端默认 `advice`
- `contactInfo`:可选
现有页面里的 `feedbackTitle` 不属于后端 DTO,不发送。工单页面与意见反馈页面共用 `feedback-pages.js`;“工单”是同一反馈记录在帮助中心的展示名称,不制造独立 ticket path。
列表和详情使用完整 `FeedbackVo`。详情页从 PC 反馈列表按 URL 中真实 `feedbackId` 匹配,未匹配时显示不存在,不回退到第一条。用户只展示反馈类型、内容、联系方式、处理状态、处理结果、处理时间和备注,不展示 `appUserId``handlerId`、手机号 enrichment 或原始 JSON。提交成功后重读列表并核对同一反馈 ID。
### 4. VIP 套餐和订单
页面:`profile-services.html`
接入:
- `GET /genealogy/pc/vip/packages`
- `POST /genealogy/pc/vip/orders`
- `GET /genealogy/pc/vip/orders`
订单请求只提交 `AppVipOrderBody`
- `packageId`:必填,只能来自套餐响应
- `genealogyId`:可选,只能来自用户家谱选项
- `payType`:可选,空值由后端默认 `wechat`
页面展示完整 `VipPackageVo` 中的套餐名称、说明、价格、原价、有效期、家谱/成员/存储限制与正常状态;展示 `VipOrderVo` 中的订单号、套餐、关联家谱、金额、支付类型、支付状态和有效期。
后端当前只落订单,没有 PC 支付发起、支付回调或取消接口。页面只允许“创建订单”和“刷新订单”,不显示“立即支付”“模拟成功”“取消订单”。新订单按后端 `payStatus=0` 展示为待支付,并明确支付能力尚未开放。
### 5. 家谱主页与权限入口收口
页面:`profile-family-home.html``profile-admin-permissions.html`
家谱主页使用:
- `GET /genealogy/pc/genealogies/{genealogyId}/overview`
主页展示 `AppGenealogyVo` 可确认的家谱名称、编号、姓氏、地区、成员数、人物数、当前角色和能力;继续保留已经开放的世系、谱文、相册、视频、功德、祭祀和家族圈入口。没有家谱上下文时阻止业务请求并返回家谱选择页。
管理员权限能力已经由 `profile-family-admin.html` 的成员角色修改、移出、退出和谱主转移覆盖。`profile-admin-permissions.html` 不再维护第二套表单,改为携带家谱上下文进入成员管理页;所有现有导航同步指向唯一 owner 页面。
### 6. 保持阻断的页面和能力
以下能力在后端没有 PC Controller,不实施假功能:
- `profile-invite.html`:没有成员邀请、邀请令牌或邀请链接接口
- `profile-share.html`:没有家谱分享、二维码或奖励记录接口
- `profile-data-reminders.html`:没有资料完善提醒查询或配置接口
- 个人资料地区保存:`ProfileUpdateBody` 没有地区字段
后端备忘录和成长记录的 `remindTime` 继续由已经开放的对应页面维护,不把它们误作“资料完善提醒”接口。
## 页面脚本边界
- 扩展 `public/js/genealogy-entry-pages.js`:家谱创建、我的加入申请、撤销申请。
- 新增 `public/js/join-review-pages.js`:待审核申请与审核。
- 新增 `public/js/feedback-pages.js`:反馈提交、列表和基于列表的详情匹配。
- 新增 `public/js/vip-pages.js`:套餐、订单创建和订单列表。
- 新增 `public/js/family-home-pages.js`:家谱 overview 展示。
- `public/js/member-admin-pages.js` 继续作为家谱成员权限唯一实现,不复制到旧权限页。
每个脚本导出纯规范化、校验、渲染函数和页面初始化函数,沿用 UMD 结构、`ProfileUI.requireGenealogyContext()`、401/403 分流、可见文本转义、稳定字符串 ID、防重复提交和写后重读。
## 错误和隐私
- 401 清理登录态;403 保留登录态并展示权限不足。
- 没有家谱上下文时不发送家谱业务请求。
- 所有写操作使用单一 `writePending` 锁,提交按钮同步禁用。
- 后端响应缺少必需稳定 ID、返回不安全数字长 ID、状态不在已确认枚举内时,整条记录拒绝渲染。
- 普通页面不展示内部用户 ID、处理人 ID、OSS ID、原始 JSON。
- 创建、申请、审核、反馈和订单成功后均重读对应资源,不能仅凭成功提示宣称完成。
## 测试与阶段顺序
每个阶段严格执行 RED → GREEN → 聚焦回归:
1. ApiClient 契约:新增 method/path/query/body 白名单测试。
2. 家谱创建:额度、地区选择、上传派生封面、成功上下文。
3. 加入申请:申请、我的申请、撤销、审核和权限。
4. 反馈与工单:共享 DTO、列表详情匹配、隐私和无独立 ticket path。
5. VIP:套餐选择、订单白名单、待支付边界、无伪支付。
6. 家谱主页与权限入口:overview、上下文传播、唯一成员管理入口。
7. 更新 pending/PC scope/navigation 测试,运行全量测试并使用真实账号验证无家谱和有数据分支;没有测试数据时明确记录未覆盖,不制造记录。
@@ -1,119 +0,0 @@
# 阶段 6 PC 功能页面设计
## 目标
把当前 PC YAML 与只读后端已经具备完整业务闭环、但前端页面缺失或仍处于待开发状态的功能全部落到现有个人中心中。不得使用 APP 接口、静态业务 ID、手填用户 ID/OSS ID、猜测响应字段或不可重读的停用流程。
## 实施范围
### 1. 谱文
- 开放 `profile-article.html`,提供正常谱文列表、分类筛选、详情和权限化操作入口。
- 开放 `profile-article-edit.html`,提供新增与修改。
- 新增成功后使用响应中的稳定 `articleId` 重读详情;修改后重读同一详情;删除后重读列表。
- `categoryId` 只能来自真实文章分类响应;如果 PC 没有分类列表/选项源,则分类保持省略,不能输入 ID。
- 封面只能通过文件选择和上传产生 `coverOssId`
- 普通列表和详情只读取正常状态,因此创建、修改固定提交 `status=0`,不开放停用。
### 2. 相册与照片
- 开放 `profile-album.html` 作为相册列表入口。
- 新增 `profile-album-edit.html` 负责相册新增、修改。
- 新增 `profile-album-detail.html` 负责相册详情、照片列表、照片上传和照片删除。
- 相册封面与照片文件都必须通过统一分片上传产生 OSS ID。
- 相册或照片写入后重新读取相册/照片列表;删除后重新读取并校正数量。
- 普通接口只返回正常记录,因此不开放相册或照片停用状态。
- 在没有 PC 文件访问 URL 时,只显示上传状态、标题和业务元数据,不拼接 OSS 地址。
### 3. 祭祀活动与祭品
- 保留 `profile-gift.html` 的“我的邀请”能力,并增加进入祭祀活动管理的入口。
- 开放 `profile-gift-edit.html`,负责祭祀活动新增、修改。
- 新增 `profile-ceremony.html`,负责活动列表和详情。
- 新增 `profile-ceremony-detail.html`,负责活动详情、祭品列表、新增祭品、删除祭品和管理员邀约名单。
- 活动封面通过统一上传产生 `coverOssId`
- 祭品金额按 YAML `number` 和后端 `BigDecimal` 边界处理;拒绝负数以及 JSON 数值序列化会改变输入值的金额。
- 活动与祭品写操作成功后重新读取对应详情/列表。
- 普通活动列表只返回正常记录,因此不开放活动停用。
### 4. 管理员邀约名单
- 邀约管理放在 `profile-ceremony-detail.html`,不与当前用户“我的邀请”响应流程混合。
- 候选人来自 `/genealogy/pc/genealogies/{genealogyId}/members/options`
- 只允许选择拥有稳定 `appUserId` 的同家谱正常成员;当前登录人由后端拒绝,前端不猜测替代账号。
- 保存时一次提交完整 `inviteeUserIds`;空数组表示取消所有仍未响应邀请,提交前必须二次确认。
- 保存成功后重新读取活动邀请名单。
### 5. 指定账号绑定世系人物
-`profile-tree.html` 启用 `SPECIFIED`,仅对具有家谱管理权限的用户开放。
- 账号候选来自家谱成员选项;过滤没有 `appUserId` 的成员。
- 用户看到成员昵称/成员名称,不看到或手填 `appUserId`
- `NONE``SELF``SPECIFIED` 继续遵守互斥请求规则;后端负责最终租户、账号状态和重复绑定校验。
### 6. 家谱成员管理
- 开放 `profile-family-admin.html`
- 提供成员列表、关键词搜索、成员详情摘要、成员资料/角色修改、成员移除、当前用户退出家谱和所有权转移。
- 角色编辑只发送后端实际接受的 `admin/editor/member`,不允许把 `owner/visitor` 作为更新值。
- 世系人物关联只能来自真实世系人物选项。
- 所有权转移只能从当前正常成员记录中选择稳定 `memberId`
- 移除、退出和所有权转移必须有明确影响说明与二次确认;成功后重新读取成员列表和家谱上下文。
## 明确保留的阻断
- PC 没有世系关系解除接口,不创建“解除父母/配偶/兄弟关系”按钮。
- PC 没有统一 OSS ID 访问地址解析能力;谱文封面、相册照片、祭祀封面只显示后端已直接返回的 URL,否则不猜 URL。
- 普通谱文、相册、照片和祭祀列表过滤停用记录,详情也不能稳定读取停用项;前端固定提交正常状态。
- 谱文 YAML 遗漏后端可选 `categoryId` 列表 query,且文章分类选项来源需要单独核实;未确认前不发送该 query。
- YAML 中部分 OSS ID 为 string、Java DTO 为 Long;浏览器始终保存十进制字符串,上传响应是唯一来源。
- YAML 的文章、相册和祭祀响应仍为泛型包装;页面只消费对应 PC 控制器明确返回的 `ArticleVo``AlbumVo``AlbumPhotoVo``CeremonyVo``CeremonyGiftVo`,不读取 APP 路径。
## 前端结构
- `utils/ApiClient.js` 是所有 method/path/query/body 的唯一 owner,并为每个 Body 使用字段白名单。
- 每个业务模块使用独立脚本:
- `public/js/article-pages.js`
- `public/js/album-pages.js`
- `public/js/ceremony-admin-pages.js`
- `public/js/member-admin-pages.js`
- `public/js/ceremony-pages.js` 继续只负责当前账号的邀请读取与接受/拒绝,避免管理端与个人端状态混在一起。
- `public/js/profile-common.js` 继续作为 `genealogyId` 的唯一上下文 owner。
- `public/js/upload-pages.js` 继续作为 OSS ID 的唯一前端产生来源。
## 数据流与状态
1. 页面从 URL 和 `profile-common.js` 取得真实家谱上下文。
2. 页面先读取家谱详情,按 `canView/canEditContent/canManage` 控制可见入口;后端继续执行最终权限校验。
3. 列表记录返回稳定字符串 ID,用户点击后进入详情或编辑。
4. 表单只提交 YAML/后端 PC DTO 声明的字段;可选空值默认省略。
5. 写操作使用模块级提交锁,完成后重新读取服务端状态。
6. 页面分别展示加载、空、校验失败、403、404、业务失败和网络失败状态。
7. 所有响应文本经过转义;手机号、内部 ID、OSS ID 和原始 JSON不进入可见页面。
## 页面入口
- 个人中心和家谱管理导航开放谱文、相册、祭祀活动、成员管理入口。
- 页面缺少 `genealogyId` 时返回家谱选择页,并通过 `next` 保留目标页面。
- 已有待开发页面在功能完成并通过测试后移除 `data-feature-status="pending"``pending-pages.js`
- 新增页面沿用现有个人中心头部、侧边栏、按钮、卡片和移动端断点,不引入新的前端框架。
## 测试与验收
每个模块单独执行:
1. 先新增 `ApiClient` 路径、query、body 白名单和长 ID 失败测试并观察 RED。
2. 新增规范化、权限、渲染转义、写后重读、防重复和页面字段测试并观察 RED。
3. 最小实现后运行模块聚焦测试直到 GREEN。
4. 使用真实登录账号验证读取、空状态和非破坏性筛选。
5. 有真实业务数据时验证详情和写操作;没有数据时明确记录未覆盖项,不制造示例数据。
6. 每个模块完成后运行相关测试并更新 `docs/PC接口对接规划.md`
7. 阶段完成后运行 `npm test`、JavaScript 语法检查、`git diff --check` 和凭据扫描。
## 完成标准
- 所有当前 PC 可闭环功能都有可访问页面和导航入口。
- 所有请求字段有真实页面来源,所有 ID 均由响应、选择器或上传产生。
- 不存在 APP 路径、旧路径 fallback、手填业务 ID、原始 JSON或静态业务数据。
- 所有写操作防重复并在成功后重新读取。
- 仍缺后端能力的功能保持明确禁用并写入规划阻断项。
+45 -160
View File
@@ -1,182 +1,67 @@
# PC 接口对接交接文档 # 家谱 PC 前端交接
> 更新:2026-07-25 更新日期2026-09-17
> 历史交接记录:本文的 79/85 条接口数量已过期,不再作为 AI 施工依据。当前接口基线、字段分类、阻断项和执行顺序统一以 [PC接口对接规划.md](PC接口对接规划.md) 为准;执行前仍须在 Apifox 实时复核并重新导出。
## 一、必须遵守的范围 ## 项目是什么
1. Apifox 的 **PC 目录**是唯一正式契约源 这是一个静态 HTML 多页面项目。页面直接加载 CSS 和 JavaScript,没有 React、Vue 或单页应用入口
2. 本地 OpenAPI 导出文件可能少接口,只能辅助查找,不能据此决定接口存在、路径或字段。
3. 不使用 APP 接口,也不能把 `/app/` 改为 `/pc/` 后猜测使用。
4. 继续任何新接口前,必须直接在 Apifox PC 详情核对 method、path、query、body、响应和权限。
5. 家谱业务只能使用真实 `?genealogyId=...`;不写死编号、不从 APP 获取。
6. 页面不直接调用 Axios、不自行拼请求路径,统一通过 `utils/ApiClient.js`
## 二、当前目录与历史施工范围 官网页面在根目录,例如 `index.html``login.html``genealogy.html`。登录后的个人中心和家谱功能页面以 `profile-` 开头。
### 当前 PC 目录(2026-07-25 ## 主要目录
| 分组 | 条数 | 当前处理状态 | | 位置 | 用途 |
| --- | ---: | --- |
| 验证中心 | 3 | 已逐条复核并迁移 |
| 认证登录 | 12 | 已逐条复核并迁移;其中两条短信发送定义同为当前 `operationCode` 路径 |
| 文件上传 | 3 | 已逐条复核、`ApiClient` 仅保留分片三条;初始化响应未展开,头像上传保持阻止 |
| 家谱 | 1 | 已核验并映射配额查询;入口页仅展示配额并阻止进入需 `genealogyId` 的业务页,不能伪造编号 |
| 家族圈 | 14 | 已逐条复核;动态、点赞、评论、直接回复及两类分页均与当前实现一致 |
| 行政区划 | 4 | 已逐条复核;当前 PC 目录下实际路径均为 `/genealogy/region/*`,与 `ApiClient` 一致 |
| 字辈谱 | 6 | 已逐条复核;正常查询、维护、新增、批量预览/保存和修改/停用/恢复均与当前实现一致 |
| 世系人物 | 12 | 已逐条复核;人物列表/分页/选项/树、CRUD/停用和四种新增关系均与当前实现一致 |
| 内容文章 | 1 | 已核验;仅删除,页面继续关闭危险入口 |
| 相册 | 2 | 已核验;仅删除,页面继续关闭危险入口 |
| 视频 | 1 | 已核验;仅删除,页面继续关闭危险入口 |
| 贺礼邀约 | 4 | 已逐条核验并映射;缺少活动列表/创建/详情,页面保持预览 |
| 祭祀 | 2 | 已核验;仅删除活动/祭品,页面继续关闭危险入口 |
| 族务记录 | 16 | 已逐条复核;成长记录、亲友往来、备忘录各 5 条与功德删除 1 条均保持当前契约 |
| 消息通知 | 4 | 已逐条核验并映射;消息页已接入原始列表、未读数和全部已读 |
| 合计 | 85 | 以桌面版 PC 目录为准 |
### 历史 79 条施工清单(2026-07-24,已被当前目录基线替代)
| 分组 | 条数 | 状态 |
| --- | ---: | --- |
| 验证中心 | 4 | 已核验、已接入 |
| 认证登录 | 11 | 已核验、已接入 |
| 文件上传 | 6 | 已核验;仅单文件上传页面闭环开放 |
| 行政区划 | 4 | 已核验、已接入 |
| 家族圈 | 14 | 已核验、已接入 |
| 字辈谱 | 6 | 已核验、已接入 |
| 世系人物 | 12 | 已核验、已接入 |
| 族务记录·成长记录 | 5 | 已核验、`ApiClient` 已映射;页面开放列表与新增 |
| 族务记录·亲友往来 | 5 | 已核验、`ApiClient` 已映射;页面开放列表与新增 |
| 族务记录·备忘录 | 5 | 已核验、`ApiClient` 已映射;页面开放列表与新增 |
| 族务记录·功德记录 | 1 | 已核验、`ApiClient` 已映射;缺少真实列表/详情,页面不开放删除 |
| 内容文章 | 1 | 已核验、`ApiClient` 已映射;缺少真实列表/详情,页面不开放删除 |
| 相册 | 2 | 已核验、`ApiClient` 已映射;缺少真实列表/详情,页面不开放删除 |
| 视频 | 1 | 已核验、`ApiClient` 已映射;缺少真实列表/详情,页面不开放删除 |
| 祭祀 | 2 | 已核验、`ApiClient` 已映射;缺少真实列表/详情,页面不开放删除 |
| 合计 | 79 | |
完整映射见 [PC接口对接规划.md](PC接口对接规划.md)。
### 已核验关键规则
- 验证中心现为三条 PC 接口:`GET /genealogy/pc/auth/verification/{operationCode}/require``POST .../{operationCode}/challenge``POST .../{operationCode}/verify``operationCode` 仅允许 `password-login``sms-login``register``forgot-password``phone-change``account-deactivate`;前端不得提交旧 `sceneCode`
- `require` 的 query 为必填 `tenantId` 和可选 `subject`challenge 的 body 为必填 `tenantId``subject`verify 的 body 在此基础上增加 `challengeId`,可附 `providerCode``captchaType``payload`。三条都由请求头传 `clientid`,不得在 query/body 传 `clientId`
- 短信发送为 `POST /genealogy/pc/auth/sms/{operationCode}/code`,支持除 `password-login` 外的五个短信动作。body 使用 `grantType: "sms"``tenantId``phone` 和策略开启时的 `validToken``clientid` 只走请求头。
- 当前认证登录的上述已核验接口一律要求 `clientid` 请求头,且 body 明确禁止 `clientId`。注册/密码登录/找回密码的 body 为 `grantType: "password"``tenantId` 加业务字段;密码登录在验证中心策略要求时还提交本次验证取得的 `validToken`;短信登录为 `grantType: "sms"``tenantId``phone``smsCode`;换绑为 `phone``smsCode`;注销为 `smsCode`
- 族务记录当前 16 条由成长记录、亲友往来、备忘录三套完整 CRUD(各 5 条)和 `DELETE .../merit-records/{meritId}` 组成。三套 CRUD 都要求 `genealogyId`,详情/修改/删除还要求各自记录 ID;列表元素与新增/详情响应 DTO 仍未展开,页面只展示原始记录并保持无稳定 ID 的编辑、详情、删除入口关闭。
- 行政区划四条当前路径为 `GET /genealogy/region/children`(可选 `parentCode`)、`GET /genealogy/region/path/{regionCode}``GET /genealogy/region/search`(必填 `keyword`,可选 `level``limit`)、`GET /genealogy/region/{regionCode}`;均已与 `ApiClient` 核对一致。
- 字辈谱 6 条当前路径分别为 `GET .../generation-poems``GET .../generation-poems/management``POST .../generation-poems``POST .../generation-poems/batch/preview``POST .../generation-poems/batch/save``PUT .../generation-poems/{poemId}`;实现仍只使用其明确 DTO 字段。
- 世系人物 12 条当前包含 `persons` 列表/新增、`persons/page``persons/options``tree`、人物详情/修改/停用,以及 `children``parents``siblings``spouses` 四种以完整 `LineagePersonBody` 新建关系人物的接口;`genealogyId``personId` 均保持真实入口传入。
- 内容文章、相册、视频和祭祀当前 6 条均为删除孤岛:`DELETE .../articles/{articleId}``.../albums/{albumId}``.../albums/{albumId}/photos/{photoId}``.../videos/{videoId}``.../ceremonies/{ceremonyId}``.../ceremonies/{ceremonyId}/gifts/{giftId}`。它们已映射但无当前 PC 列表/详情来源,不能开放页面删除。
- 文件上传现仅有 `POST /genealogy/pc/files/resumable/init``POST .../chunk``POST .../complete`。所有文件统一先初始化;普通小文件可按初始化响应的 `instant=true` 直接取 OSS 信息,但该响应的字段 Schema 尚未展开,页面不得猜测 `uploadId``ossId``instant`,头像上传继续阻止。
- 家谱当前只提供 `GET /genealogy/pc/genealogies/quota`,返回创建/加入的已用数量、上限、剩余额度以及 `canCreate`/`canJoin`。它不返回家谱对象或 `genealogyId`;只映射为 `genealogyQuota`,不据此开放家谱业务页面。
- 贺礼邀约四条为替换受邀人、查询活动邀请名单、当前用户接受/拒绝、查询我的邀请。受邀人 body 只允许 `inviteeUserIds`,当前用户响应 body 只允许 `inviteStatus: ACCEPTED|DECLINED`。缺少活动列表/创建/详情,`profile-gift*.html` 继续为明确的待开发预览。
- 消息通知为列表(可选 `readStatus: 0|1`)、未读数量、单条已读和全部已读。列表元素 DTO 未展开,`profile-messages.html` 只安全展示服务端原始记录,不猜测标题、内容、时间或单条 `notificationId`;未读数和全部已读已开放。
- 认证相关均使用 PC 路径;密码为 32 位 MD5。改绑/注销只使用 PC DTO 所需字段,不擅自加 `tenantId`
- 家族圈 14 条当前路径为动态列表/分页/详情/增改删、点赞/取消点赞、一级评论列表/分页/发表/删除和直接回复列表/分页;均要求真实 `genealogyId`,现有 `ApiClient` 与页面调用已逐项比对一致。新评论只提交必填 `commentContent` 和可选 `parentCommentId`,不能发旧字段 `content``replyUserId`
- 字辈管理列表使用 `GET .../generation-poems/management`;批量操作使用 `poemText` 和可选 `disableMissing`。单项最多 50 字符、一次最多 500 代、总长最多 26000。
- 世系分页 query`pageNum``pageSize``keyword``generation``personStatus``keyword` 只查姓名、别名、人物编号。
- 世系写入字段是 `name``generation``biography`,不是旧字段 `personName``generationNo``introduction``sortOrder``relationName` 是可选字段。
- `DELETE .../lineage/persons/{personId}` 是逻辑停用,不是物理删除;有正常子女时后端会拒绝。
- 新增父母/配偶/兄弟姐妹/子女四个关系接口均接收完整 `LineagePersonBody` 来新建关系人物,并非绑定两个已有 ID。
- 已末次回到 Apifox 复核:世系分页 query、创建人物 body、`POST .../lineage/persons/{personId}/spouses``relationName`
- 成长记录 5 条均为登录接口,路径固定为 `/genealogy/pc/genealogies/{genealogyId}/growth-records``/{recordId}``genealogyId``recordId` 均是必填 int64 路径参数,`clientid` 为必填请求头。
- 成长记录的 `GrowthRecordBody` 只有 `recordTitle` 必填;可选字段为 `lineagePersonId``recordType``recordContent``recordDate``remindTime``mediaOssIds``sortOrder``status``mediaOssIds` 只接受英文逗号分隔的正整数 OSS ID。
- 成长记录列表响应是未展开元素 DTO 的 `ListResult`,详情/新增/修改为未展开元素 DTO 的 `ObjectResult`,删除为 `VoidResult`。不能据此猜测 `recordId`、标题或权限字段;页面仅作原始列表展示和新增,不开放编辑、详情或删除入口。
- 亲友往来 5 条均为登录接口,路径固定为 `/genealogy/pc/genealogies/{genealogyId}/relative-records``/{relativeId}``RelativeRecordBody` 只有 `relativeName` 必填,可选 `relationName``eventName``eventTime``giftAmount``recordContent``mediaOssIds``sortOrder``status`。列表/详情元素 DTO 同样未展开,页面不开放编辑、详情或删除入口。
- 备忘录 5 条均为登录接口,路径固定为 `/genealogy/pc/genealogies/{genealogyId}/memos``/{memoId}``genealogyId``memoId` 均是必填 int64 路径参数,`clientid` 为必填请求头。备忘录请求体只有 `memoTitle` 必填,可选 `memoContent``remindTime``completed``mediaOssIds``sortOrder``status``completed` 在 Apifox 中是 string,不能擅自改成布尔值。列表/详情元素 DTO 未展开,页面仅作原始列表展示和新增。
- 功德记录在 PC 目录中当前只存在 `DELETE /genealogy/pc/genealogies/{genealogyId}/merit-records/{meritId}`;两个路径参数均为必填 int64,登录和 `clientid` 必填,响应为 `VoidResult``deleteMeritRecord` 已映射,但页面没有真实列表、详情或稳定 `meritId` 来源,必须保持删除入口关闭。
- 其余 6 个删除孤岛接口也已核验并映射:`deleteArticle``deleteAlbum``deleteAlbumPhoto``deleteVideo``deleteCeremony``deleteCeremonyGift`。它们都要求登录、`clientid` 和 int64 路径 ID,响应均为 `VoidResult`;相册、视频与祭祀删除会由后端释放相应文件引用。由于没有 PC 列表/详情和稳定 ID 来源,所有相关页面继续保持预览,不能触发删除。
## 三、已经落地的页面
### 账号与资料
- `login.html`:密码登录、短信登录。
- `register.html`:短信注册。
- `forgot-password.html`:短信重置密码。
- `profile-security.html`:改密码、改绑手机、注销账号。
- `profile-data.html` / `profile.html`:资料读取保存、行政区划;头像上传等待当前 PC 分片初始化响应 Schema 补齐。
### 家谱业务
- `profile-feed.html` / `profile-feed-edit.html`:家族圈列表、详情、发布/修改、点赞、评论、回复。
- `profile-generation.html` + `public/js/generation-pages.js`:字辈管理列表、新增、编辑、停用/恢复、批量预览/保存。所有写操作有防重复提交锁;批量示例要求用空格或支持的标点分隔。
- `profile-tree.html` + `public/js/lineage-pages.js`:成员总览、世系树、分页搜索/翻页、下拉选择、详情、新增/编辑、逻辑停用、父母/配偶/兄弟姐妹/子女新增。所有写操作有防重复提交锁;响应 int64 ID 只以安全字符串用于后续请求。
- `profile-growth.html` / `profile-growth-edit.html` + `public/js/growth-pages.js`:成长记录列表和新增;写入仅使用 `GrowthRecordBody`,并有防重复提交锁。列表/详情元素 DTO 尚未展开,编辑、详情和删除保持关闭。
- `profile-relative.html` / `profile-relative-edit.html` + `public/js/relative-pages.js`:亲友往来列表和新增;写入仅使用 `RelativeRecordBody`,并有防重复提交锁。列表/详情元素 DTO 尚未展开,编辑、详情和删除保持关闭。
- `profile-memo.html` / `profile-memo-edit.html` + `public/js/memo-pages.js`:备忘录列表和新增;写入仅使用已核验的备忘录请求体字段,并有防重复提交锁。列表/详情元素 DTO 尚未展开,编辑、详情和删除保持关闭。
### 关键代码文件
| 文件 | 责任 |
| --- | --- | | --- | --- |
| `utils/ApiClient.js` | 已核验 PC 路径、请求头、DTO | | 根目录 `*.html` | 各页面入口 |
| `public/js/profile-common.js` | `genealogyId` 读取和链接传播的唯一 owner | | `public/css/` | 页面样式,`public.css` 是全局样式 |
| `public/js/genealogy-entry-pages.js` | 无真实家谱上下文时的统一入口页;仅查询 PC 配额并阻止业务跳转 | | `public/js/` | 页面逻辑、公共交互和第三方脚本 |
| `public/js/auth-pages.js``captcha-pages.js``security-pages.js` | 登录、注册、验证码、账号安全 | | `public/images/` | 官网图片素材 |
| `public/js/profile-pages.js``region-pages.js``upload-pages.js` | 资料、区划、头像 | | `utils/` | 请求、存储、表单和提示工具 |
| `public/js/feed-pages.js` | 家族圈 | | `config.js` | 前端接口配置 |
| `public/js/generation-pages.js` | 字辈谱 | | `scripts/build-minified.mjs` | 构建脚本 |
| `public/js/lineage-pages.js` | 世系人物 | | `dist/` | 构建产物;当前已清理,需要时重新构建 |
| `public/js/growth-pages.js` | 成长记录 |
| `public/js/relative-pages.js` | 亲友往来 |
| `public/js/memo-pages.js` | 备忘录 |
## 四、当前阻塞和未完成范围 ## 已清理内容
### PC 家谱入口缺失 本次已删除测试文件、测试截图、临时接口响应、设计审查记录和旧文档。`dist/`、接口快照和 `package.json` 也已清理。仓库不再保留自动化测试,也没有 `npm test` 命令。
PC 目录没有“我的家谱、家谱详情、创建/加入/选择家谱”接口。前端无法自行得到真实 `genealogyId`,因此当前正确行为是:个人中心和内容入口先统一进入 `profile-families.html`,该页只读取 `genealogyQuota()` 并明确说明当前不能选择家谱;缺少 ID 时阻止业务请求。静态家谱卡片已移除,不要伪造编号,也不要用 APP 补这个缺口 后续不要在仓库中保存测试截图、测试账号信息、接口返回样本或重复的过程记录。需要说明验证结果时,写在当前提交或任务交付里即可
### 已核验但仍缺业务闭环的页面范围 ## 请求和登录
| 分组 | 已核验条数 | 当前原则 | 接口地址和请求封装在 `utils/ApiClient.js``utils/AxiosRequestUtil.js``config.js`
| --- | ---: | --- |
| 内容文章 | 1 | 仅删除;没有真实列表/详情来源时不开放删除 |
| 相册 | 2 | 仅删除;不猜 DTO 或文件引用来源 |
| 视频 | 1 | 仅删除;不猜 DTO 或文件引用来源 |
| 祭祀 | 2 | 仅删除;不猜实体和献礼字段 |
| 功德记录 | 1 | 仅删除;不猜实体和 `meritId` 来源 |
## 五、通用行为和安全约束 页面不要直接发 Axios 请求,也不要在页面里拼接口地址。新增接口时先在 `ApiClient` 中统一定义,再由页面调用。
- `ApiClient` 统一加 `clientid`;登录后统一加 `Authorization: Bearer <token>` 登录信息由 `StorageUtil.js` 管理。登录后的请求会携带 Bearer Token401 会清除登录态并跳转到 `login.html`403 保留登录态并显示无权限状态
- 401 清 token 跳转 `login.html`;403 保留登录态并展示无权限状态。
- 浏览器中的 int64 ID 均按字符串保存,避免精度丢失。
- 当前接口没有定义的删除、解绑、关系解除和业务文件引用值,保持禁用/待开发,不能猜测实现。
- 这是脏工作树;不要使用 `git reset --hard``git checkout --`,也不要删除未跟踪文件。
## 六、验证结果 后端返回的长整型 ID 在浏览器里按字符串处理,避免数字精度丢失。
本轮已直接完成当前 PC 目录 85/85 条的详情复核,并逐项比对 `ApiClient`、对应页面和契约测试;历史 79 条记录不作为本轮验收依据。完整测试结果以本轮末次运行记录为准。 ## 家谱业务规则
已运行的检查包括: 家谱业务必须使用真实的 `genealogyId`。没有家谱上下文时,应阻止请求或提示用户选择家谱,不能写死示例 ID。
- `node --check utils/ApiClient.js` 接口字段、权限和接口是否存在,以 Apifox 的 PC 目录为准。不要从 APP 接口推测 PC 接口,也不要根据旧文件猜字段。
- `node --check public/js/profile-common.js`
- `node --check public/js/genealogy-entry-pages.js`
- `node --check public/js/generation-pages.js`
- `node --check public/js/lineage-pages.js`
- `node --check public/js/growth-pages.js`
- `node --check public/js/memo-pages.js`
- `node --check public/js/captcha-pages.js`
- `node --check public/js/auth-pages.js`
- `node --check public/js/security-pages.js`
- `node --test tests/api-client-contract.test.js tests/captcha-pages-contract.test.js tests/auth-pages.test.js tests/security-upload-scope.test.js tests/profile-pages.test.js`
- `node --test tests/auth-pages.test.js tests/api-client-contract.test.js tests/profile-pages.test.js tests/security-upload-scope.test.js tests/feed-pages.test.js tests/generation-pages.test.js tests/lineage-pages.test.js tests/growth-pages.test.js tests/relative-pages.test.js tests/memo-pages.test.js tests/pending-pages.test.js tests/pc-scope.test.js`
- `npm.cmd test`91/91 通过)
- `git diff --check`
本地运行态检查已使用 Chrome DevTools 逐页打开 `profile-feed*`、成长记录、亲友往来、备忘录、消息、资料、字辈和世系页面:12 个静态页面均返回 200;无登录态时需鉴权的页面转到登录且没有发送业务请求,三类族务编辑页缺少真实 `genealogyId` 时保持阻止且不发请求。已使用测试账号完成密码登录的真实滑块验证与登录;资料页实际修改昵称后已恢复原值,页面两次均显示保存成功;消息页实际完成列表和未读数读取,服务端返回空列表。个人中心到家谱入口页的真实点击已验证,入口页仅调用配额接口且在缺少真实 `genealogyId` 时不进入家族业务。当前仍没有 PC 家谱列表/详情返回的真实家谱上下文,因此未对需 `genealogyId` 的写接口发送提交 接口还缺少列表、详情或删除能力时,页面应保持禁用或提示状态,不要补造数据把入口打开
当前完整测试集已运行并通过。 ## 常用位置
## 七、下一位 GPT 的执行顺序 - 个人中心通用布局和导航:`public/js/profile-common.js`
- 个人中心样式:`public/css/profile.css``public/css/profile-module.css`
- 富文本编辑:`public/js/rich-editor.js``public/js/wangeditor5/`
- 附件上传:`public/js/attachment-editor.js``public/js/upload-pages.js`
- Layui 配置:`public/js/lay-config.js`
1. 读本文和 `docs/PC接口对接规划.md` 改公共样式或公共脚本前,先检查相关 `profile-*.html` 的引用,避免影响其他页面
2. 先检查桌面版 Apifox PC 目录是否新增或变更;只逐条复核受影响接口,不要借用 APP 接口或猜 DTO。
3. 每次只处理一个完整小分组:Apifox 核验 → `ApiClient` → 对应页面 → 聚焦测试;家谱配额接口不是入口,继续不写死 `genealogyId` ## 接手一个功能时
4. 只有删除能力、没有真实列表/详情来源的模块,不开放危险操作,只登记后端缺口。
5. 每完成一组,更新本文、`PC接口对接规划.md` 和相关测试,并报告改动、验证和剩余阻塞 1. 找到页面入口、对应脚本和已有接口调用
2. 在 Apifox 的 PC 目录确认请求方法、参数、响应字段和权限。
3. 修改请求封装和页面逻辑,保持加载、空数据、失败、无权限和禁用状态完整。
4. JavaScript 改动后执行 `node --check 路径/文件.js`,并在浏览器走通受影响页面。
5. 需要生成发布文件时执行 `node scripts/build-minified.mjs`。当前 `package.json` 已删除;如果后续重新引入 npm 脚本,需要同时恢复并维护对应配置。
6. 提交前执行 `git diff --check`,确认没有格式问题。
工作区可能有未提交修改。不要使用 `git reset --hard``git checkout --` 覆盖现有文件。
-99
View File
@@ -1,99 +0,0 @@
# 规划
## 项目目标
将“代代相传”建设为由官网、用户 PC 管理端、用户 APP 端和平台后台组成的家谱服务。四个端共享家谱、成员、内容和权限规则,但分别交付,不能用用户 PC 页面替代 APP 或平台后台。
## 当前基线
- 当前工作区共有 55 个 HTML 页面,现有页面之间没有指向不存在 HTML 页的站内链接。
- 本文保留项目级历史规划,不再维护接口数量和字段契约。当前 PC 接口基线、字段页面归属和 AI 执行顺序统一见 [PC接口对接规划.md](PC接口对接规划.md)。
- 已完成真实接口闭环:注册、密码登录、短信登录、找回密码、个人资料、安全设置、头像上传、地区选择、家族圈动态。
- 家谱列表、家谱创建/加入、成员、权限、世系、谱文、相册、视频、贺礼、成长、备忘、功德、消息、反馈等页面已保留设计,但尚无对应正式接口,不得伪造请求或静态数据为真实业务。
## 实施进度
- [x] 第一轮:家谱基础页面状态收口。已覆盖家谱列表、创建与加入、审核、家族管理、邀请、世系和字辈页面;未开发功能会显示统一提示并阻止伪造操作。
- [x] 第二轮:内容与协作页面状态收口。已覆盖除家族圈动态外的内容、记录、消息、反馈、管理员权限与资料提醒页面;家族圈动态保持真实功能并保留跳转入口。
- [x] 第三轮:剩余用户 PC 业务入口收口。已覆盖家谱主页、应用分享和帮助与会员服务页面;家族圈动态入口保持真实跳转。
- [x] 第四轮:官网静态页面补齐。已新增新闻、法律文档入口、通用状态和工单页面;在线工单功能明确处于开发中,法律正文必须在发布前由法务替换确认。
## 产品边界
### 官网
保留首页、数字家谱、家谱广场、姓氏百科、家族文化、应用下载、帮助中心、关于我们、搜索、公告和工单入口。
需要补齐的独立页面:
1. 新闻列表、新闻分类、新闻详情。
2. 隐私政策、儿童个人信息处理规则、服务协议。
3. 我的工单、工单详情。
4. 404、无权限、系统维护等通用状态页。
### 用户 PC 管理端
现有页面覆盖了思维导图中的大部分视觉入口:我的家谱、家谱主页、世系图、字辈、谱文、相册、视频、家族圈、贺礼、成长日志、备忘录、功德录、家族管理、管理员权限、入谱审核、消息、个人资料和安全设置。
需要补齐的关键页面:
1. 家族成员名册与成员详情,用于按成员筛选、查看资料、维护角色;世系图不能替代成员名册。
2. 家族圈动态详情,用于承载可分享链接、完整评论和通知跳转。
3. 无家谱首次引导、无权限、空列表等状态视图。
### 用户 APP 端
APP 是独立交付物,负责移动端查看家谱、上传照片/视频、发布内容、消息和个人中心。当前 PC 项目只需共享接口契约和视觉规范,不直接复制为 APP 页面。
### 平台后台
平台后台是独立项目,负责全站家谱审核、内容治理、会员/VIP、系统设置和运营数据。它不应通过隐藏按钮混入用户 PC 端。
## 后端接口开发顺序
### 第一阶段:家谱基础
1. 我的家谱列表、家谱详情、创建家谱、加入家谱、加入审核。
2. 家谱成员列表、成员详情、邀请、移除、角色与管理员权限。
3. 家谱入口必须返回真实 `genealogyId`,让世系、内容和家族圈能从同一上下文进入。
### 第二阶段:世系与资料
1. 世系树查询。
2. 成员新增、编辑、删除。
3. 父母、配偶、子女等关系维护。
4. 字辈列表与维护。
### 第三阶段:内容与协作
1. 谱文及分类。
2. 相册、照片、视频。
3. 贺礼、成长日志、备忘录、功德录。
4. 消息中心、点赞评论通知、生日提醒、入谱审核通知。
5. 意见反馈、工单列表与详情。
### 第四阶段:官网内容与合规
1. 新闻资讯、公告、公开内容检索。
2. 隐私、未成年人保护和服务协议。
3. 客服工单的提交、查询和回复。
## 契约要求
每个业务对象都必须提供明确 DTO,至少包括列表、详情和写入请求模型。禁止前端依赖泛型 `object` 或猜测字段名。
最优先需要后端补充的模型:
1. `GenealogyDTO``GenealogyMemberDTO``MemberRoleDTO`
2. `LineagePersonDTO``LineageRelationDTO``GenerationDTO`
3. `FeedDTO``CommentDTO`
4. `ArticleDTO``AlbumDTO``VideoDTO``CeremonyDTO``GrowthLogDTO``MemoDTO``MeritDTO`
5. `NotificationDTO``TicketDTO``NewsDTO`
## 交付规则
1. 有正式接口和 DTO 的页面,才接入真实读写功能。
2. 未开发接口对应的页面保留设计,但明确显示“功能开发中”或空状态,不伪造成功数据。
3. 家谱业务全部从真实 `genealogyId` 进入,不写死示例家谱编号。
4. 访客、普通成员、家谱管理员、平台管理员必须有独立权限边界。
5. 测试账号仅用于运行时验收;账号和密码不写入仓库、文档、测试、日志或配置。
+8 -9
View File
@@ -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
View File
@@ -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">
+1
View File
@@ -48,6 +48,7 @@
</div> </div>
</div> </div>
</footer> </footer>
<script src="config.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+20 -34
View File
@@ -18,46 +18,31 @@
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"> <form id="password-reset-form" class="form-grid" novalidate>
<input <label class="auth-field" for="reset-phone">
class="input" <span>手机号 <b aria-hidden="true">*</b></span>
name="phone" <input id="reset-phone" class="input" name="phone" type="tel" inputmode="numeric" autocomplete="tel" maxlength="11" placeholder="请输入注册手机号" required />
type="tel" </label>
inputmode="numeric" <div class="auth-field">
autocomplete="tel" <label for="reset-sms-code">短信验证码 <b aria-hidden="true">*</b></label>
maxlength="11"
placeholder="手机号"
/>
<div class="code-row"> <div class="code-row">
<input <input id="reset-sms-code" class="input" name="smsCode" autocomplete="one-time-code" inputmode="numeric" maxlength="4" pattern="\d{4}" placeholder="请输入四位验证码" required />
class="input"
name="smsCode"
autocomplete="one-time-code"
inputmode="numeric"
maxlength="4"
pattern="\d{4}"
placeholder="验证码"
/>
<button class="btn ghost" type="button" data-api-send-code>获取验证码</button> <button class="btn ghost" type="button" data-api-send-code>获取验证码</button>
</div> </div>
<input </div>
class="input" <label class="auth-field" for="reset-password">
name="newPassword" <span>新密码 <b aria-hidden="true">*</b></span>
type="password" <input id="reset-password" class="input" name="newPassword" type="password" autocomplete="new-password" placeholder="至少六位" required />
autocomplete="new-password" </label>
placeholder="新密码" <label class="auth-field" for="reset-confirm-password">
/> <span>确认新密码 <b aria-hidden="true">*</b></span>
<input <input id="reset-confirm-password" class="input" name="confirmPassword" type="password" autocomplete="new-password" placeholder="请再次输入新密码" required />
class="input" </label>
name="confirmPassword"
type="password"
autocomplete="new-password"
placeholder="确认新密码"
/>
<input type="hidden" name="validToken" /> <input type="hidden" name="validToken" />
<div id="reset-status" class="auth-form-status" role="status" aria-live="polite" data-auth-form-status></div>
<button class="btn primary" type="submit">重设密码</button> <button class="btn primary" type="submit">重设密码</button>
</form> </form>
</section> </section>
@@ -73,6 +58,7 @@
<script src="public/layui/layui.js"></script> <script src="public/layui/layui.js"></script>
<script src="public/tac/js/tac.min.js"></script> <script src="public/tac/js/tac.min.js"></script>
<script src="public/js/captcha-pages.js"></script> <script src="public/js/captcha-pages.js"></script>
<script src="public/js/profile-common.js"></script>
<script src="public/js/auth-pages.js"></script> <script src="public/js/auth-pages.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
+8 -7
View File
@@ -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">
@@ -151,6 +151,7 @@
</div> </div>
</div> </div>
</footer> </footer>
<script src="config.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+17 -29
View File
@@ -36,13 +36,13 @@
<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">
覆盖注册登录、创建家谱、邀请成员、资料审核、隐私设置和内容发布等流程 帮助内容由平台持续维护,覆盖账号、家谱和内容使用中的常见问题
</p> </p>
<div class="help-tags"> <div class="help-tags">
<span>创建家谱</span><span>邀请成员</span><span>隐私权限</span> <span>账号安全</span><span>家谱使用</span><span>内容管理</span>
</div> </div>
</div> </div>
<div class="hero-card help-card tilt-card"> <div class="hero-card help-card tilt-card">
@@ -55,6 +55,7 @@
<a class="btn primary magnetic" href="submit-ticket.html" <a class="btn primary magnetic" href="submit-ticket.html"
>提交工单</a >提交工单</a
> >
<a class="btn ghost magnetic" href="my-tickets.html">我的工单</a>
</div> </div>
</div> </div>
</section> </section>
@@ -63,27 +64,12 @@
<div class="container"> <div class="container">
<div class="section-head"> <div class="section-head">
<h2>常见问题</h2> <h2>常见问题</h2>
<p>把复杂流程拆成清楚步骤,便于用户第一次使用时快速上手</p> <p>选择问题后读取平台发布的完整解答</p>
<button class="btn ghost" type="button" data-help-refresh>刷新帮助</button>
</div> </div>
<div class="faq-list" data-help-list> <div class="muted" data-help-status role="status" aria-live="polite"></div>
<details open class="tilt-card"> <div class="faq-list is-loading" data-help-list>
<summary>如何创建第一本家谱?</summary> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取帮助文章…</div>
<p>
进入创建家谱页面,填写家族名称、姓氏、地区和简介,即可生成家谱空间。
</p>
</details>
<details class="tilt-card">
<summary>如何邀请亲人一起维护?</summary>
<p>在家谱管理中生成邀请码,亲人可通过加入家谱页面提交申请。</p>
</details>
<details class="tilt-card">
<summary>公开家谱会展示哪些内容?</summary>
<p>仅展示管理员允许公开的家族介绍、动态和部分资料。</p>
</details>
<details class="tilt-card">
<summary>资料填错了怎么办?</summary>
<p>管理员可以编辑成员资料、调整世系关系,也可以设置审核流程。</p>
</details>
</div> </div>
</div> </div>
</section> </section>
@@ -100,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">
@@ -125,5 +111,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/profile-common.js"></script>
<script src="public/js/help-pages.js"></script>
</body> </body>
</html> </html>
+556
View File
@@ -0,0 +1,556 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>代代相传家谱_在线修家谱_家谱查询_姓氏寻根_祠堂祭祀</title>
<meta
name="keywords"
content="电子家谱,在线修家谱,家谱制作,家谱查询,中国家谱网,寻根,祠堂,祭祀,祭拜,中华传统文化,生命文化,儒释道,姓氏源流;敬祖睦族,忠孝两全,传承家风;族谱网,族记;清明节家谱,过年修家谱,海外寻根"
/>
<meta
name="description"
content="相传相传家谱网,大型生命文化平台,综合儒释道思想,弘扬中华传统文化,提供电子家谱_在线修家谱_网上修家谱,家谱制作_族谱制作_修谱软件,家谱查询_姓氏寻根_寻根问祖_姓氏源流,祠堂祭祀_祭拜_祭奠,弘扬孝敬_忠孝两全_相夫教子_敬祖睦族_慎终追远_传承家风,覆盖清明节家谱_过年修家谱_90后修家谱_海外华人寻根_老家谱电子化,代代相传_薪火相传"
/>
<link rel="stylesheet" href="public/css/public.css" />
<link rel="stylesheet" href="public/css/index.css" />
</head>
<body class="page-home">
<div class="company-bar">
<div class="container">富众鑫科技</div>
</div>
<header class="site-header">
<div class="container nav">
<a class="brand magnetic" href="index.html"
><img
class="brand-logo"
src="public/images/logo.png"
alt="我们的家谱,代代相传"
/></a>
<nav class="nav-links">
<a class="active" href="index.html">首页</a>
<a href="genealogy.html">数字家谱</a>
<a href="plaza.html">家谱广场</a>
<a href="culture.html">家族文化</a>
<a href="surname.html">姓氏百科</a>
<a href="app.html">应用下载</a>
<a href="help.html">帮助中心</a>
<a href="about.html">关于我们</a>
</nav>
<div class="nav-actions">
<a href="login.html">登录</a>
<a href="register.html">注册</a>
<a class="btn primary magnetic" href="create-genealogy.html"
>创建家谱</a
>
</div>
</div>
</header>
<main>
<div class="hero">
<div class="container hero-inner">
<div class="hero-copy">
<div class="eyebrow">数字家谱与家族文化门户</div>
<h1>
一部会生长的<br /><span class="hero-title-mark">数字家谱</span>
</h1>
<p class="lead">
把姓氏源流、世系关系、字辈排行、宣传相册、谱文故事与亲人动态整理在一起,让家族的来处清晰可见,让记忆可以继续传下去。
</p>
<div class="word-scroll-card">
<div class="word-scroll-label">正在记录</div>
<div class="word-window">
<div class="word-track">
<div class="word">寻根问祖</div>
<div class="word">字辈排行</div>
<div class="word">世系关系</div>
<div class="word">宣传相册</div>
<div class="word">谱文故事</div>
<div class="word">亲人动态</div>
<div class="word">寻根问祖</div>
<div class="word">字辈排行</div>
<div class="word">世系关系</div>
<div class="word">宣传相册</div>
<div class="word">谱文故事</div>
<div class="word">亲人动态</div>
</div>
</div>
</div>
<div class="hero-path">
<span>1</span>创建家谱<i></i><span>2</span>邀请亲人<i></i
><span>3</span>共同传承
</div>
<div class="hero-action-card">
<div class="cta">
<a class="btn primary magnetic" href="create-genealogy.html"
>立即创建家谱</a
>
<a class="btn ghost magnetic" href="plaza.html">查看家谱示例</a>
</div>
</div>
<div class="metrics">
<div class="metric"><strong>家谱广场</strong>浏览公开家族</div>
<div class="metric"><strong>姓氏百科</strong>了解姓氏源流</div>
<div class="metric"><strong>家族文化</strong>阅读传承故事</div>
</div>
</div>
<div class="hero-visual">
<div class="archive-stage"></div>
<div class="book-cover"></div>
<div class="hall">
<img src="public/images/ancestral-hall.png" alt="家族祠堂" />
</div>
<a class="video-card" href="promo-video.html">
<small>宣传影像</small>
<span>观看宣传视频</span>
</a>
<div class="search-panel">
<h3>查找公开家谱</h3>
<a class="search-box" href="search-result.html">
<span>输入姓氏、地区或家谱名称</span><b>搜索</b>
</a>
<div class="genealogy-list">
<div class="genealogy-item">
<strong>四川成都陈氏族</strong>共修入 128 人
</div>
<div class="genealogy-item">
<strong>湖广刘氏家谱</strong>收录 6 代字辈
</div>
<div class="genealogy-item">
<strong>岭南张氏宗谱</strong>开放谱文 18 篇
</div>
</div>
</div>
</div>
</div>
</div>
<div class="quick-strip">
<div class="container quick-inner">
<a class="quick-card tilt-card" href="create-genealogy.html">
<b>创建家谱</b><span>填写姓氏谱名,建立属于家族的数字空间。</span>
</a>
<a class="quick-card tilt-card" href="join-genealogy.html">
<b>加入家谱</b><span>通过邀请码加入亲人创建的家谱。</span>
</a>
<a class="quick-card tilt-card" href="plaza.html">
<b>浏览广场</b><span>查看公开家谱示例与家族故事。</span>
</a>
<a
class="quick-card tilt-card"
id="promo-video"
href="promo-video.html"
><b>宣传视频</b
><span>进入视频列表,查看宣传影像和纪念视频。</span></a
>
</div>
</div>
<section class="functions">
<div class="container">
<div class="section-head">
<h2>数字家谱核心功能</h2>
</div>
<div class="function-grid">
<div class="function-card tilt-card">
<div class="icon"></div>
<h3>世系图</h3>
<p>
以树谱方式展示成员关系,支持查看个人资料、添加亲属、调整排行。
</p>
</div>
<div class="function-card tilt-card">
<div class="icon"></div>
<h3>字辈谱</h3>
<p>记录各代字辈与人数,让家族世代脉络更加清楚。</p>
</div>
<a class="function-card tilt-card" href="promo-album.html">
<div class="icon"></div>
<h3>宣传相册</h3>
<p>按相册归档宣传影像,展示祖屋、活动、老照片和重要时刻。</p>
</a>
<div class="function-card tilt-card">
<div class="icon"></div>
<h3>谱文资料</h3>
<p>沉淀谱序、谱文、家训、恩荣录等文本内容。</p>
</div>
<div class="function-card tilt-card">
<div class="icon"></div>
<h3>家族圈</h3>
<p>发布动态、分享图片、评论互动,让亲人之间保持连接。</p>
</div>
<div class="function-card tilt-card">
<div class="icon"></div>
<h3>功德录</h3>
<p>记录家族贡献人物与事迹,让善行与纪念被看见。</p>
</div>
<div class="function-card tilt-card">
<div class="icon"></div>
<h3>贺礼邀请</h3>
<p>发布婚礼、生日、升学等家族喜事,通知相关成员。</p>
</div>
<div class="function-card tilt-card">
<div class="icon"></div>
<h3>权限管理</h3>
<p>设置管理员、内容可见范围与入谱审核,保护家族隐私。</p>
</div>
</div>
</div>
</section>
<section class="plaza">
<div class="container">
<div class="section-head">
<h2>家谱广场</h2>
</div>
<div class="plaza-layout">
<a class="featured-family tilt-card" href="family-detail.html">
<h3>四川成都陈氏族</h3>
<p>
已收录成员 128 人,整理字辈 6 代,公开谱文 18 篇,宣传相册 7
组。家族资料由成员共同维护,持续更新。
</p>
</a>
<div class="family-cards">
<a class="family-card tilt-card" href="family-detail.html">
<h4>湖广刘氏家谱</h4>
<p>从始祖世系到现代家庭分支,保留字辈与迁徙记录。</p>
<div class="tag-row">
<span class="tag">公开家谱</span
><span class="tag">字辈完整</span>
</div>
</a>
<a class="family-card tilt-card" href="family-detail.html">
<h4>岭南张氏宗谱</h4>
<p>以谱文、相册、家族动态为主,记录家风与重要纪念。</p>
<div class="tag-row">
<span class="tag">家族文化</span
><span class="tag">宣传相册</span>
</div>
</a>
<a class="family-card tilt-card" href="family-detail.html">
<h4>川东李氏家谱</h4>
<p>多人共同完善成员资料,形成清晰的家族成员档案。</p>
<div class="tag-row">
<span class="tag">多人共修</span
><span class="tag">成员资料</span>
</div>
</a>
</div>
</div>
</div>
</section>
<section class="culture">
<div class="container">
<div class="section-head">
<h2>家族文化</h2>
</div>
<div class="article-grid">
<a class="article-card tilt-card" href="article-detail.html">
<div class="article-cover"></div>
<div class="article-body">
<h3>为什么家谱不只是一本名册</h3>
<p>
家谱记录的是家族关系,也记录一个家庭如何理解自己的来处与传承。
</p>
<div class="article-meta">家谱知识 · 2026.05</div>
</div>
</a>
<a class="article-card tilt-card" href="article-detail.html">
<div class="article-cover"></div>
<div class="article-body">
<h3>字辈在家族传承中的作用</h3>
<p>字辈让不同世代的人能找到自己的位置,也让同族称谓更清晰。</p>
<div class="article-meta">姓氏文化 · 2026.05</div>
</div>
</a>
<a class="article-card tilt-card" href="article-detail.html">
<div class="article-cover"></div>
<div class="article-body">
<h3>如何邀请亲人共同完善家谱</h3>
<p>从创建家谱、生成邀请码到设置管理员,逐步建立协作流程。</p>
<div class="article-meta">使用指南 · 2026.05</div>
</div>
</a>
</div>
</div>
</section>
<section class="ad-slots">
<div class="container">
<div class="ad-grid">
<a
class="ad-card tilt-card"
href="https://3dyjs.cn/"
target="_blank"
rel="noopener"
aria-label="3D研究室广告"
>
<img src="public/images/mlogo.png" alt="3D研究室" />
</a>
<a
class="ad-card tilt-card"
href="promo-album.html"
aria-label="影像相册广告"
>
<img src="public/images/ad-album-small.png" alt="影像相册" />
</a>
<a
class="ad-card tilt-card"
href="promo-video.html"
aria-label="活动视频广告"
>
<img src="public/images/ad-video-small.png" alt="活动视频" />
</a>
<a
class="ad-card tilt-card"
href="genealogy.html"
aria-label="族谱印制广告"
>
<img src="public/images/ad-print.png" alt="族谱印制" />
</a>
<a
class="ad-card tilt-card"
href="culture.html"
aria-label="祠堂文化广告"
>
<img src="public/images/ad-hall.png" alt="祠堂文化" />
</a>
<a
class="ad-card tilt-card"
href="app.html"
aria-label="云端存档广告"
>
<img src="public/images/ad-cloud.png" alt="云端存档" />
</a>
<a
class="ad-card tilt-card"
href="culture.html"
aria-label="宗亲联谊广告"
>
<img src="public/images/ad-reunion.png" alt="宗亲联谊" />
</a>
<a
class="ad-card tilt-card"
href="surname.html"
aria-label="姓氏研究广告"
>
<img src="public/images/ad-research.png" alt="姓氏研究" />
</a>
<a
class="ad-card tilt-card"
href="culture.html"
aria-label="家风记忆广告"
>
<img src="public/images/ad-memory.png" alt="家风记忆" />
</a>
<a
class="ad-card tilt-card"
href="help.html"
aria-label="资料校对广告"
>
<img src="public/images/ad-data.png" alt="资料校对" />
</a>
</div>
</div>
</section>
<section class="surname">
<div class="container">
<div class="surname-panel tilt-card">
<div class="surname-intro">
<h2>姓氏百科</h2>
<p>
以姓氏为入口,整理姓氏源流、名人故事、地区分布、字辈参考和相关家谱。
</p>
<a class="btn primary magnetic" href="surname.html"
>进入姓氏百科</a
>
</div>
<div class="surname-grid">
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
</div>
</div>
</div>
</section>
<section class="recommend">
<div class="container">
<div class="section-head">
<h2>相关推荐</h2>
</div>
<div class="recommend-grid">
<a
class="recommend-card tilt-card"
href="article-detail.html"
>
<span>资料整理</span>
<h3>家庭档案整理指南</h3>
<p>参考照片、证件、谱牒、口述资料的归档方式。</p>
</a>
<a
class="recommend-card tilt-card"
href="surname.html"
>
<span>姓氏源流</span>
<h3>姓氏文化资料库</h3>
<p>查看姓氏源流、迁徙脉络、堂号和郡望参考资料。</p>
</a>
<a
class="recommend-card tilt-card"
href="culture.html"
>
<span>宗亲文化</span>
<h3>宗亲会活动参考</h3>
<p>了解宗亲联谊、祭祖活动和家风传播的组织方式。</p>
</a>
<a
class="recommend-card tilt-card"
href="help.html"
>
<span>工具服务</span>
<h3>修谱工具与服务</h3>
<p>查找谱书排版、影像扫描、云端存储等外部服务。</p>
</a>
<a
class="recommend-card tilt-card"
href="culture.html"
>
<span>地方志</span>
<h3>地方文献检索入口</h3>
<p>从地方志、县志和族群资料中寻找家族线索。</p>
</a>
<a
class="recommend-card tilt-card"
href="surname.html"
>
<span>人名研究</span>
<h3>字辈与命名参考</h3>
<p>参考传统字辈、排行、称谓和家族命名资料。</p>
</a>
<a
class="recommend-card tilt-card"
href="promo-album.html"
>
<span>影像修复</span>
<h3>老照片修复服务</h3>
<p>了解老照片扫描、修复、上色和数字归档服务。</p>
</a>
<a
class="recommend-card tilt-card"
href="article-detail.html"
>
<span>口述史</span>
<h3>家族故事采集方法</h3>
<p>参考长辈访谈、口述记录和家风故事整理方式。</p>
</a>
</div>
</div>
</section>
<section class="friend-links">
<div class="container">
<div class="friend-links-panel">
<h2>常用入口</h2>
<div class="friend-link-list">
<a href="https://3d.3dyjs.cn/" target="_blank" rel="noopener"
>3D研究室</a
>
<a href="surname.html">姓氏源流资料</a>
<a href="plaza.html">公开家谱资料</a>
<a href="article-detail.html">修谱知识库</a>
<a href="culture.html">地方文献</a>
<a href="promo-album.html">家族影像</a>
<a href="genealogy.html">数字家谱</a>
<a href="help.html">使用帮助</a>
</div>
</div>
</div>
</section>
<section class="download">
<div class="container">
<div class="download-panel tilt-card">
<div>
<h2>手机端随时记录,电脑端清晰浏览</h2>
<p>
家族成员可以在手机端上传照片、查看家谱、发布动态;电脑端适合浏览门户内容、查看公开家谱和进入家谱空间。
</p>
<div class="cta">
<a class="btn primary magnetic" href="download.html">下载应用</a
><a class="btn ghost magnetic" href="help.html">查看帮助</a>
</div>
</div>
</div>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-grid">
<div>
<a class="brand magnetic" href="index.html"
><img
class="brand-logo"
src="public/images/logo-light.png"
alt="我们的家谱,代代相传"
/></a>
<p>一个面向家庭和宗族的数字家谱与家族文化门户。</p>
</div>
<div>
<h4>产品入口</h4>
<a href="genealogy.html">数字家谱</a><a href="plaza.html">家谱广场</a><a href="app.html">应用下载</a><a href="help.html">帮助中心</a>
</div>
<div>
<h4>文化内容</h4>
<a href="culture.html">家族文化</a><a href="surname.html">姓氏百科</a><a href="article-detail.html">家谱知识</a
><a href="news.html">新闻资讯</a>
</div>
<div>
<h4>服务支持</h4>
<a href="help.html">帮助中心</a>
<a href="submit-ticket.html">提交问题</a>
<a href="about.html">关于平台</a>
</div>
</div>
<div class="footer-legal">
<div>© 2026 代代相传. All rights reserved.</div>
<a href="privacy.html">隐私政策</a>
<a href="children-privacy.html">儿童个人信息处理规则</a>
<a href="terms.html">服务协议</a>
<a href="https://beian.miit.gov.cn" target="_blank" rel="noopener"
>ICP备案:蜀ICP备2024044594号-1</a
>
</div>
</div>
</footer>
<script src="config.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+180 -540
View File
@@ -1,4 +1,4 @@
<!doctype html> <!doctype html>
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -15,7 +15,12 @@
<link rel="stylesheet" href="public/css/public.css" /> <link rel="stylesheet" href="public/css/public.css" />
<link rel="stylesheet" href="public/css/index.css" /> <link rel="stylesheet" href="public/css/index.css" />
</head> </head>
<body> <body class="page-home page-home-portal">
<div class="company-bar">
<div class="container company-bar-inner">
<strong>富众鑫文化科技</strong>
</div>
</div>
<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="index.html"
@@ -24,19 +29,17 @@
src="public/images/logo.png" src="public/images/logo.png"
alt="我们的家谱,代代相传" alt="我们的家谱,代代相传"
/></a> /></a>
<nav class="nav-links"> <nav class="nav-links" aria-label="主导航">
<a class="active" href="index.html">首页</a> <a class="active" href="index.html" aria-current="page">首页</a>
<a href="genealogy.html">数字家谱</a> <a href="genealogy.html">数字家谱</a>
<a href="plaza.html">家谱广场</a>
<a href="culture.html">家族文化</a>
<a href="surname.html">姓氏百科</a>
<a href="app.html">应用下载</a>
<a href="help.html">帮助中心</a>
<a href="about.html">关于我们</a> <a href="about.html">关于我们</a>
<a href="app.html">APP下载</a>
<a href="help.html">在线帮助</a>
<button type="button" data-add-favorite>收藏</button>
</nav> </nav>
<div class="nav-actions"> <div class="nav-actions">
<a href="login.html">登录</a>
<a href="register.html">注册</a> <a href="register.html">注册</a>
<a href="login.html">登录</a>
<a class="btn primary magnetic" href="create-genealogy.html" <a class="btn primary magnetic" href="create-genealogy.html"
>创建家谱</a >创建家谱</a
> >
@@ -45,584 +48,221 @@
</header> </header>
<main> <main>
<div class="hero"> <section class="portal-overview" aria-labelledby="home-title">
<div class="container hero-inner"> <div class="container portal-overview-grid">
<div class="hero-copy"> <div class="hero-copy portal-copy">
<div class="portal-copy-heading">
<div>
<div class="eyebrow">数字家谱与家族文化门户</div> <div class="eyebrow">数字家谱与家族文化门户</div>
<h1> <h1 id="home-title">一部会生长的<br /><span class="hero-title-mark">数字家谱</span></h1>
一部会生长的<br /><span class="hero-title-mark">数字家谱</span> </div>
</h1> <a class="portal-qr-card" href="download.html" aria-label="进入应用下载页面">
<strong>扫码下载</strong>
<span id="home-app-qr" class="portal-qr" aria-hidden="true"></span>
<small>手机端随时记录</small>
</a>
</div>
<p class="lead"> <p class="lead">
把姓氏源流、世系关系、字辈排行、宣传相册、谱文故事与亲人动态整理在一起,让家族的来处清晰可见,让记忆可以继续传下去。 把姓氏源流、世系关系、字辈排行、宣传相册、谱文故事与亲人动态整理在一起,让家族的来处清晰可见,让记忆可以继续传下去。
</p> </p>
<div class="word-scroll-card"> <div class="word-scroll-card portal-word-scroll" aria-label="家谱内容范围">
<div class="word-scroll-label">正在记录</div> <button class="word-scroll-label" type="button" data-toggle-word-scroll aria-label="暂停词条滚动" aria-pressed="false" title="点击暂停或继续滚动">正在记录</button>
<div class="word-window"> <div class="word-window">
<div class="word-track"> <div class="word-track">
<div class="word">寻根问祖</div> <div class="portal-word-group">
<div class="word">字辈排行</div> <span class="word">寻根问祖</span><span class="word">字辈排行</span><span class="word">世系关系</span><span class="word">宣传相册</span><span class="word">谱文故事</span><span class="word">亲人动态</span>
<div class="word">世系关系</div> </div>
<div class="word">宣传相册</div> <div class="portal-word-group" aria-hidden="true">
<div class="word">谱文故事</div> <span class="word">寻根问祖</span><span class="word">字辈排行</span><span class="word">世系关系</span><span class="word">宣传相册</span><span class="word">谱文故事</span><span class="word">亲人动态</span>
<div class="word">亲人动态</div>
<div class="word">寻根问祖</div>
<div class="word">字辈排行</div>
<div class="word">世系关系</div>
<div class="word">宣传相册</div>
<div class="word">谱文故事</div>
<div class="word">亲人动态</div>
</div> </div>
</div> </div>
</div> </div>
<div class="hero-path">
<span>1</span>创建家谱<i></i><span>2</span>邀请亲人<i></i
><span>3</span>共同传承
</div> </div>
<div class="hero-action-card"> <ol class="hero-path" aria-label="修谱步骤">
<div class="cta"> <li><b>1</b>创建家谱</li>
<a class="btn primary magnetic" href="create-genealogy.html" <li><b>2</b>邀请亲人</li>
>立即创建家谱</a <li><b>3</b>共同传承</li>
> </ol>
<a class="btn ghost magnetic" href="plaza.html">查看家谱示例</a>
</div> </div>
</div>
<div class="metrics"> <div class="portal-gallery" aria-label="家谱与家族影像">
<div class="metric"><strong>家谱广场</strong>浏览公开家族</div> <div class="book-cover portal-gallery-book" role="img" aria-label="张氏家谱封面"></div>
<div class="metric"><strong>姓氏百科</strong>了解姓氏源流</div>
<div class="metric"><strong>家族文化</strong>阅读传承故事</div>
</div>
</div>
<div class="hero-visual">
<div class="archive-stage"></div>
<div class="book-cover"></div>
<div class="hall">
<img src="public/images/ancestral-hall.png" alt="家族祠堂" /> <img src="public/images/ancestral-hall.png" alt="家族祠堂" />
<img src="public/images/family-memory-cover.jpg" alt="家族照片与记忆档案" />
</div> </div>
<a class="video-card" href="promo-video.html"> </div>
</section>
<section class="portal-entry-section" aria-label="主要服务入口">
<div class="container portal-entry-grid">
<a class="portal-entry portal-service-entry tilt-card" href="create-genealogy.html" aria-label="修谱服务,创建家谱">
<img src="public/images/ad-genealogy-service.png" alt="家族数字化修谱服务" />
</a>
<a class="portal-entry portal-family-entry tilt-card" href="profile-families.html">
<span>进入管理</span>
<strong>我的家谱</strong>
<small>查看、新建和管理全部家谱</small>
</a>
<a class="video-card portal-entry portal-media-entry tilt-card" href="promo-video.html" aria-label="查看宣传视频介绍">
<small>宣传影像</small> <small>宣传影像</small>
<span>观看宣传视频</span> <span>观看宣传视频</span>
</a> </a>
<div class="search-panel"> <a class="video-card portal-entry portal-media-entry tilt-card" href="promo-video.html" aria-label="查看宣传视频介绍">
<h3>查找公开家谱</h3> <small>宣传影像</small>
<a class="search-box" href="search-result.html"> <span>观看宣传视频</span>
<span>输入姓氏、地区或家谱名称</span><b>搜索</b>
</a> </a>
<div class="genealogy-list">
<div class="genealogy-item">
<strong>四川成都陈氏族</strong>共修入 128 人
</div>
<div class="genealogy-item">
<strong>湖广刘氏家谱</strong>收录 6 代字辈
</div>
<div class="genealogy-item">
<strong>岭南张氏宗谱</strong>开放谱文 18 篇
</div>
</div>
</div>
</div>
</div>
</div>
<div class="quick-strip">
<div class="container quick-inner">
<a class="quick-card tilt-card" href="create-genealogy.html">
<b>创建家谱</b><span>填写姓氏谱名,建立属于家族的数字空间。</span>
</a>
<a class="quick-card tilt-card" href="join-genealogy.html">
<b>加入家谱</b><span>通过邀请码加入亲人创建的家谱。</span>
</a>
<a class="quick-card tilt-card" href="plaza.html">
<b>浏览广场</b><span>查看公开家谱示例与家族故事。</span>
</a>
<a
class="quick-card tilt-card"
id="promo-video"
href="promo-video.html"
><b>宣传视频</b
><span>进入视频列表,查看宣传影像和纪念视频。</span></a
>
</div>
</div>
<section class="functions">
<div class="container">
<div class="section-head">
<h2>数字家谱核心功能</h2>
</div>
<div class="function-grid">
<div class="function-card tilt-card">
<div class="icon"></div>
<h3>世系图</h3>
<p>
以树谱方式展示成员关系,支持查看个人资料、添加亲属、调整排行。
</p>
</div>
<div class="function-card tilt-card">
<div class="icon"></div>
<h3>字辈谱</h3>
<p>记录各代字辈与人数,让家族世代脉络更加清楚。</p>
</div>
<a class="function-card tilt-card" href="promo-album.html">
<div class="icon"></div>
<h3>宣传相册</h3>
<p>按相册归档宣传影像,展示祖屋、活动、老照片和重要时刻。</p>
</a>
<div class="function-card tilt-card">
<div class="icon"></div>
<h3>谱文资料</h3>
<p>沉淀谱序、谱文、家训、恩荣录等文本内容。</p>
</div>
<div class="function-card tilt-card">
<div class="icon"></div>
<h3>家族圈</h3>
<p>发布动态、分享图片、评论互动,让亲人之间保持连接。</p>
</div>
<div class="function-card tilt-card">
<div class="icon"></div>
<h3>功德录</h3>
<p>记录家族贡献人物与事迹,让善行与纪念被看见。</p>
</div>
<div class="function-card tilt-card">
<div class="icon"></div>
<h3>贺礼邀请</h3>
<p>发布婚礼、生日、升学等家族喜事,通知相关成员。</p>
</div>
<div class="function-card tilt-card">
<div class="icon"></div>
<h3>权限管理</h3>
<p>设置管理员、内容可见范围与入谱审核,保护家族隐私。</p>
</div>
</div>
</div> </div>
</section> </section>
<section class="plaza"> <section class="portal-ad-strip" aria-label="第一组广告栏">
<div class="container"> <div class="container portal-ad-grid">
<div class="section-head"> <a class="tilt-card" href="genealogy.html"><img src="public/images/ad-service.png" alt="家谱服务" /></a>
<h2>家谱广场</h2> <a class="tilt-card" href="promo-album.html"><img src="public/images/ad-album-small.png" alt="影像相册" /></a>
</div> <a class="tilt-card" href="promo-video.html"><img src="public/images/ad-video-small.png" alt="活动视频" /></a>
<div class="plaza-layout"> <a class="tilt-card" href="genealogy.html"><img src="public/images/ad-print.png" alt="族谱印制" /></a>
<a class="featured-family tilt-card" href="family-detail.html"> <a class="tilt-card" href="culture.html"><img src="public/images/ad-hall.png" alt="祠堂文化" /></a>
<h3>四川成都陈氏族</h3> <a class="tilt-card" href="app.html"><img src="public/images/ad-cloud.png" alt="云端存档" /></a>
<p> <a class="tilt-card" href="culture.html"><img src="public/images/ad-reunion.png" alt="宗亲联谊" /></a>
已收录成员 128 人,整理字辈 6 代,公开谱文 18 篇,宣传相册 7 <a class="tilt-card" href="surname.html"><img src="public/images/ad-research.png" alt="姓氏研究" /></a>
组。家族资料由成员共同维护,持续更新。 <a class="tilt-card" href="article-detail.html"><img src="public/images/ad-memory.png" alt="家风记忆" /></a>
</p>
</a>
<div class="family-cards">
<a class="family-card tilt-card" href="family-detail.html">
<h4>湖广刘氏家谱</h4>
<p>从始祖世系到现代家庭分支,保留字辈与迁徙记录。</p>
<div class="tag-row">
<span class="tag">公开家谱</span
><span class="tag">字辈完整</span>
</div>
</a>
<a class="family-card tilt-card" href="family-detail.html">
<h4>岭南张氏宗谱</h4>
<p>以谱文、相册、家族动态为主,记录家风与重要纪念。</p>
<div class="tag-row">
<span class="tag">家族文化</span
><span class="tag">宣传相册</span>
</div>
</a>
<a class="family-card tilt-card" href="family-detail.html">
<h4>川东李氏家谱</h4>
<p>多人共同完善成员资料,形成清晰的家族成员档案。</p>
<div class="tag-row">
<span class="tag">多人共修</span
><span class="tag">成员资料</span>
</div>
</a>
</div>
</div>
</div> </div>
</section> </section>
<section class="culture"> <section class="portal-directory" aria-label="家族文化与资讯">
<div class="container"> <div class="container">
<div class="section-head"> <div class="portal-directory-grid">
<h2>家族文化</h2> <a class="function-card portal-directory-card tilt-card" href="genealogy.html">
</div> <span class="icon" aria-hidden="true"></span>
<div class="article-grid"> <h3>家族知识</h3>
<a class="article-card tilt-card" href="article-detail.html"> <p>了解谱牒方式和成员关系,查看资料、添加亲属并完善家谱。</p>
<div class="article-cover"></div>
<div class="article-body">
<h3>为什么家谱不只是一本名册</h3>
<p>
家谱记录的是家族关系,也记录一个家庭如何理解自己的来处与传承。
</p>
<div class="article-meta">家谱知识 · 2026.05</div>
</div>
</a> </a>
<a class="article-card tilt-card" href="article-detail.html"> <a class="function-card portal-directory-card tilt-card" href="culture.html">
<div class="article-cover"></div> <span class="icon" aria-hidden="true"></span>
<div class="article-body"> <h3>宗亲文化</h3>
<h3>字辈在家族传承中的作用</h3> <p>了解宗亲礼俗、家风家训与传承故事,延续家族文化。</p>
<p>字辈让不同世代的人能找到自己的位置,也让同族称谓更清晰。</p>
<div class="article-meta">姓氏文化 · 2026.05</div>
</div>
</a> </a>
<a class="article-card tilt-card" href="article-detail.html"> <a class="function-card portal-directory-card tilt-card" href="notice-detail.html">
<div class="article-cover"></div> <span class="icon" aria-hidden="true"></span>
<div class="article-body"> <h3>网站公告</h3>
<h3>如何邀请亲人共同完善家谱</h3> <p>集中查看平台通知、服务调整与家谱相关公告。</p>
<p>从创建家谱、生成邀请码到设置管理员,逐步建立协作流程。</p> </a>
<div class="article-meta">使用指南 · 2026.05</div> <a class="function-card portal-directory-card tilt-card" href="news.html">
</div> <span class="icon" aria-hidden="true"></span>
<h3>最近新闻</h3>
<p>查看家族动态、平台资讯和近期发布的重要消息。</p>
</a>
<a class="function-card portal-directory-card tilt-card" href="surname.html">
<span class="icon" aria-hidden="true"></span>
<h3>姓氏源流</h3>
<p>追溯姓氏来处、迁徙脉络、堂号与郡望资料。</p>
</a>
<a class="function-card portal-directory-card tilt-card" href="culture.html">
<span class="icon" aria-hidden="true"></span>
<h3>网站活动</h3>
<p>查看婚礼、生日、升学等家族喜事和宗亲活动。</p>
</a> </a>
</div> </div>
</div> </div>
</section> </section>
<section class="ad-slots"> <section class="portal-resources" id="site-map" aria-label="推荐与友情链接">
<div class="container"> <div class="container portal-resource-grid">
<div class="ad-grid"> <div class="portal-resource-group">
<a <h2>其他推荐</h2>
class="ad-card tilt-card" <div class="portal-resource-list">
href="https://3dyjs.cn/" <a href="news.html">新闻资讯</a>
target="_blank" <a href="culture.html">信息公开</a>
rel="noopener" <a href="surname.html">地方动态</a>
aria-label="3D研究室广告" <a href="article-detail.html">专题报道</a>
> <a href="promo-album.html">家族影像</a>
<img src="public/images/mlogo.png" alt="3D研究室" /> <a href="promo-video.html">视频内容</a>
</a>
<a
class="ad-card tilt-card"
href="https://example.com/family-album"
target="_blank"
rel="noopener"
aria-label="影像相册广告"
>
<img src="public/images/ad-album-small.png" alt="影像相册" />
</a>
<a
class="ad-card tilt-card"
href="https://example.com/family-video"
target="_blank"
rel="noopener"
aria-label="活动视频广告"
>
<img src="public/images/ad-video-small.png" alt="活动视频" />
</a>
<a
class="ad-card tilt-card"
href="https://example.com/genealogy-print"
target="_blank"
rel="noopener"
aria-label="族谱印制广告"
>
<img src="public/images/ad-print.png" alt="族谱印制" />
</a>
<a
class="ad-card tilt-card"
href="https://example.com/ancestral-hall"
target="_blank"
rel="noopener"
aria-label="祠堂文化广告"
>
<img src="public/images/ad-hall.png" alt="祠堂文化" />
</a>
<a
class="ad-card tilt-card"
href="https://example.com/cloud-archive"
target="_blank"
rel="noopener"
aria-label="云端存档广告"
>
<img src="public/images/ad-cloud.png" alt="云端存档" />
</a>
<a
class="ad-card tilt-card"
href="https://example.com/clan-reunion"
target="_blank"
rel="noopener"
aria-label="宗亲联谊广告"
>
<img src="public/images/ad-reunion.png" alt="宗亲联谊" />
</a>
<a
class="ad-card tilt-card"
href="https://example.com/surname-research"
target="_blank"
rel="noopener"
aria-label="姓氏研究广告"
>
<img src="public/images/ad-research.png" alt="姓氏研究" />
</a>
<a
class="ad-card tilt-card"
href="https://example.com/family-memory"
target="_blank"
rel="noopener"
aria-label="家风记忆广告"
>
<img src="public/images/ad-memory.png" alt="家风记忆" />
</a>
<a
class="ad-card tilt-card"
href="https://example.com/data-check"
target="_blank"
rel="noopener"
aria-label="资料校对广告"
>
<img src="public/images/ad-data.png" alt="资料校对" />
</a>
</div> </div>
</div> </div>
</section> <div class="portal-resource-group">
<section class="surname">
<div class="container">
<div class="surname-panel tilt-card">
<div class="surname-intro">
<h2>姓氏百科</h2>
<p>
以姓氏为入口,整理姓氏源流、名人故事、地区分布、字辈参考和相关家谱。
</p>
<a class="btn primary magnetic" href="surname.html"
>进入姓氏百科</a
>
</div>
<div class="surname-grid">
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
<a class="surname-name" href="surname-detail.html"></a>
</div>
</div>
</div>
</section>
<section class="recommend">
<div class="container">
<div class="section-head">
<h2>相关推荐</h2>
</div>
<div class="recommend-grid">
<a
class="recommend-card tilt-card"
href="https://example.com/archive-guide"
target="_blank"
rel="noopener"
>
<span>资料整理</span>
<h3>家庭档案整理指南</h3>
<p>参考照片、证件、谱牒、口述资料的归档方式。</p>
</a>
<a
class="recommend-card tilt-card"
href="https://example.com/surname-origin"
target="_blank"
rel="noopener"
>
<span>姓氏源流</span>
<h3>姓氏文化资料库</h3>
<p>查看姓氏源流、迁徙脉络、堂号和郡望参考资料。</p>
</a>
<a
class="recommend-card tilt-card"
href="https://example.com/clan-culture"
target="_blank"
rel="noopener"
>
<span>宗亲文化</span>
<h3>宗亲会活动参考</h3>
<p>了解宗亲联谊、祭祖活动和家风传播的组织方式。</p>
</a>
<a
class="recommend-card tilt-card"
href="https://example.com/genealogy-tools"
target="_blank"
rel="noopener"
>
<span>工具服务</span>
<h3>修谱工具与服务</h3>
<p>查找谱书排版、影像扫描、云端存储等外部服务。</p>
</a>
<a
class="recommend-card tilt-card"
href="https://example.com/local-history"
target="_blank"
rel="noopener"
>
<span>地方志</span>
<h3>地方文献检索入口</h3>
<p>从地方志、县志和族群资料中寻找家族线索。</p>
</a>
<a
class="recommend-card tilt-card"
href="https://example.com/name-study"
target="_blank"
rel="noopener"
>
<span>人名研究</span>
<h3>字辈与命名参考</h3>
<p>参考传统字辈、排行、称谓和家族命名资料。</p>
</a>
<a
class="recommend-card tilt-card"
href="https://example.com/photo-restore"
target="_blank"
rel="noopener"
>
<span>影像修复</span>
<h3>老照片修复服务</h3>
<p>了解老照片扫描、修复、上色和数字归档服务。</p>
</a>
<a
class="recommend-card tilt-card"
href="https://example.com/oral-history"
target="_blank"
rel="noopener"
>
<span>口述史</span>
<h3>家族故事采集方法</h3>
<p>参考长辈访谈、口述记录和家风故事整理方式。</p>
</a>
</div>
</div>
</section>
<section class="friend-links">
<div class="container">
<div class="friend-links-panel">
<h2>友情链接</h2> <h2>友情链接</h2>
<div class="friend-link-list"> <div class="portal-resource-list">
<a href="https://3d.3dyjs.cn/" target="_blank" rel="noopener" <a href="genealogy.html">代代相传家谱</a>
>3D研究室</a <a href="plaza.html">家谱广场</a>
> <a href="surname.html">姓氏百科</a>
<a <a href="culture.html">家族文化</a>
href="https://example.com/surnames" <a href="help.html">在线帮助</a>
target="_blank"
rel="noopener"
>姓氏源流资料</a
>
<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>
</section> </section>
<section class="download"> <section class="portal-ad-strip portal-ad-strip-bottom" aria-label="第二组广告栏">
<div class="container"> <div class="container portal-ad-grid">
<div class="download-panel tilt-card"> <a class="tilt-card" href="help.html"><img src="public/images/ad-data.png" alt="资料校对" loading="lazy" /></a>
<div> <a class="tilt-card" href="article-detail.html"><img src="public/images/ad-memory.png" alt="家风记忆" loading="lazy" /></a>
<h2>手机端随时记录,电脑端清晰浏览</h2> <a class="tilt-card" href="surname.html"><img src="public/images/ad-research.png" alt="姓氏研究" loading="lazy" /></a>
<p> <a class="tilt-card" href="culture.html"><img src="public/images/ad-reunion.png" alt="宗亲联谊" loading="lazy" /></a>
家族成员可以在手机端上传照片、查看家谱、发布动态;PC <a class="tilt-card" href="app.html"><img src="public/images/ad-cloud.png" alt="云端存档" loading="lazy" /></a>
端适合浏览门户内容、查看公开家谱和进入家谱空间。 <a class="tilt-card" href="culture.html"><img src="public/images/ad-hall.png" alt="祠堂文化" loading="lazy" /></a>
</p> <a class="tilt-card" href="genealogy.html"><img src="public/images/ad-print.png" alt="族谱印制" loading="lazy" /></a>
<div class="cta"> <a class="tilt-card" href="promo-video.html"><img src="public/images/ad-video-small.png" alt="活动视频" loading="lazy" /></a>
<a class="btn primary magnetic" href="download.html">下载应用</a <a class="tilt-card" href="promo-album.html"><img src="public/images/ad-album-small.png" alt="影像相册" loading="lazy" /></a>
><a class="btn ghost magnetic" href="help.html">查看帮助</a>
</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>
</section> </section>
</main> </main>
<footer class="footer"> <footer class="footer portal-footer">
<div class="container"> <nav class="container portal-footer-links" aria-label="页脚导航">
<div class="footer-grid"> <a href="about.html">关于家谱</a>
<div> <a href="help.html">联系客服</a>
<a class="brand magnetic" href="index.html" <a href="submit-ticket.html">意见反馈</a>
><img <a href="about.html">免责声明</a>
class="brand-logo" <a href="terms.html">用户协议</a>
src="public/images/logo-light.png"
alt="我们的家谱,代代相传"
/></a>
<p>一个面向家庭和宗族的数字家谱与家族文化门户。</p>
</div>
<div>
<h4>产品入口</h4>
<a>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
</div>
<div>
<h4>文化内容</h4>
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a
><a href="news.html">新闻资讯</a>
</div>
<div>
<h4>联系我们</h4>
<p>客服电话:400-000-0000</p>
<p>邮箱:service@example.com</p>
<p>地址:中国 · 成都</p>
</div>
</div>
<div class="footer-legal">
<div>© 2026 代代相传. All rights reserved.</div>
<a href="privacy.html">隐私政策</a> <a href="privacy.html">隐私政策</a>
<a href="index.html#site-map">网站地图</a>
</nav>
<div class="portal-footer-legal">
<div class="container">
<p>Copyright 20142026 代代相传家谱 版权所有</p>
<p>
<a href="https://beian.miit.gov.cn" target="_blank" rel="noopener">工信部:蜀ICP备2024044594号-1</a>
<a href="children-privacy.html">儿童个人信息处理规则</a> <a href="children-privacy.html">儿童个人信息处理规则</a>
<a href="terms.html">服务协议</a> </p>
<a href="https://beian.miit.gov.cn" target="_blank" rel="noopener" <p>家族文化资料请以家谱原件与当地正式记录为准</p>
>ICP备案:蜀ICP备2024044594号-1</a
>
</div> </div>
</div> </div>
</footer> </footer>
<script src="config.js"></script>
<script src="public/js/qrcode.min.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
<script>
(function () {
var qrTarget = document.getElementById('home-app-qr');
if (qrTarget && typeof window.QRCode === 'function') {
new window.QRCode(qrTarget, {
text: new URL('download.html', window.location.href).href,
width: 92,
height: 92
});
}
var scrollButton = document.querySelector('[data-toggle-word-scroll]');
var wordScroll = document.querySelector('.portal-word-scroll');
function syncWordScroll() {
var isPaused = wordScroll.classList.contains('is-paused');
scrollButton.setAttribute('aria-pressed', String(isPaused));
scrollButton.setAttribute('aria-label', isPaused ? '开始词条滚动' : '暂停词条滚动');
scrollButton.textContent = isPaused ? '开始滚动' : '正在记录';
}
scrollButton.addEventListener('click', function () {
var shouldPlay = wordScroll.classList.contains('is-paused');
wordScroll.classList.toggle('is-paused', !shouldPlay);
wordScroll.classList.toggle('is-playing', shouldPlay);
syncWordScroll();
});
syncWordScroll();
document.querySelectorAll('[data-add-favorite]').forEach(function (bookmarkButton) {
bookmarkButton.addEventListener('click', function () {
window.alert('请按 Ctrl+DMac 请按 Command+D)收藏本站');
});
});
})();
</script>
</body> </body>
</html> </html>
+60 -17
View File
@@ -1,34 +1,77 @@
<!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"> <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="index.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="form-grid"> <div class="module-title-row">
<a class="btn primary" href="plaza.html">前往家谱广场</a> <div>
<a class="btn ghost" href="profile-families.html">返回我的家谱</a> <h1>加入家谱</h1>
<p>先搜索并选择要加入的家谱,再补充申请资料;家谱编号由系统自动处理。</p>
</div>
<a class="btn ghost magnetic" href="profile-join-family.html">查看我的申请</a>
</div>
</div>
</section>
<section class="section">
<div class="container module-layout">
<aside class="module-nav" aria-label="个人中心功能导航"></aside>
<div class="module-main">
<section class="module-panel">
<div class="join-page-grid">
<section class="join-search-panel" aria-labelledby="join-search-title">
<h2 id="join-search-title">选择家谱</h2>
<form class="form-grid" data-join-genealogy-search novalidate aria-describedby="join-options-status">
<label class="field"><span>搜索家谱</span><input name="keyword" type="search" maxlength="50" placeholder="输入谱名或姓氏" /></label>
<button class="btn ghost" type="submit">搜索</button>
</form>
<div id="join-options-status" class="form-note" data-join-options-status role="status" aria-live="polite"></div>
<div class="module-list" data-join-genealogy-options aria-live="polite"></div>
<p class="form-note" data-join-selected-genealogy role="status" aria-live="polite">尚未选择家谱</p>
</section>
<form class="form-grid join-apply-form" data-join-apply-form novalidate aria-describedby="join-apply-status">
<h2>补充申请资料</h2>
<p class="form-note">以下资料均为选填,审核人将据此确认您的加入申请。</p>
<label class="field"><span>申请人姓名(选填)</span><input name="applicantName" maxlength="50" autocomplete="name" /></label>
<label class="field"><span>联系电话(选填)</span><input name="phone" maxlength="30" autocomplete="tel" /></label>
<label class="field"><span>与家谱关系(选填)</span><input name="relationDesc" maxlength="100" placeholder="例如:族亲" /></label>
<label class="field"><span>申请理由(选填)</span><textarea name="applyReason" maxlength="500" rows="4"></textarea></label>
<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>
</form>
</div>
</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>
<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"></script>
<script src="public/js/join-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+23 -38
View File
@@ -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="登录方式">
@@ -29,55 +29,39 @@
<form <form
id="login-password-form" id="login-password-form"
class="form-grid login-mode-panel" class="form-grid login-mode-panel"
novalidate
> >
<input <label class="auth-field" for="login-password-phone">
class="input" <span>手机号 <b aria-hidden="true">*</b></span>
name="phone" <input id="login-password-phone" class="input" name="phone" type="tel" inputmode="numeric" autocomplete="tel" maxlength="11" placeholder="请输入手机号" required />
type="tel" </label>
inputmode="numeric" <label class="auth-field" for="login-password-value">
autocomplete="tel" <span>密码 <b aria-hidden="true">*</b></span>
maxlength="11" <input id="login-password-value" class="input" name="password" type="password" autocomplete="current-password" placeholder="请输入密码" required />
placeholder="手机号" </label>
/>
<input
class="input"
name="password"
type="password"
autocomplete="current-password"
placeholder="密码"
/>
<input type="hidden" name="validToken" /> <input type="hidden" name="validToken" />
<div id="login-password-status" class="auth-form-status" role="status" aria-live="polite" data-auth-form-status></div>
<button class="btn primary" type="submit">登录</button> <button class="btn primary" type="submit">登录</button>
</form> </form>
<form <form
id="login-sms-form" id="login-sms-form"
class="form-grid login-mode-panel" class="form-grid login-mode-panel"
hidden hidden
novalidate
> >
<input <label class="auth-field" for="login-sms-phone">
class="input" <span>手机号 <b aria-hidden="true">*</b></span>
name="phone" <input id="login-sms-phone" class="input" name="phone" type="tel" inputmode="numeric" autocomplete="tel" maxlength="11" placeholder="请输入手机号" required />
type="tel" </label>
inputmode="numeric" <div class="auth-field">
autocomplete="tel" <label for="login-sms-code">短信验证码 <b aria-hidden="true">*</b></label>
maxlength="11"
placeholder="手机号"
/>
<div class="code-row"> <div class="code-row">
<input <input id="login-sms-code" class="input" name="smsCode" autocomplete="one-time-code" inputmode="numeric" maxlength="4" pattern="\d{4}" placeholder="请输入四位验证码" required />
class="input" <button class="btn ghost" type="button" data-api-send-code>获取验证码</button>
name="smsCode" </div>
autocomplete="one-time-code"
inputmode="numeric"
maxlength="4"
pattern="\d{4}"
placeholder="短信验证码"
/>
<button class="btn ghost" type="button" data-api-send-code>
获取验证码
</button>
</div> </div>
<input type="hidden" name="validToken" /> <input type="hidden" name="validToken" />
<div id="login-sms-status" class="auth-form-status" role="status" aria-live="polite" data-auth-form-status></div>
<button class="btn primary" type="submit">短信登录</button> <button class="btn primary" type="submit">短信登录</button>
</form> </form>
<div class="auth-links"> <div class="auth-links">
@@ -97,6 +81,7 @@
<script src="public/layui/layui.js"></script> <script src="public/layui/layui.js"></script>
<script src="public/tac/js/tac.min.js"></script> <script src="public/tac/js/tac.min.js"></script>
<script src="public/js/captcha-pages.js"></script> <script src="public/js/captcha-pages.js"></script>
<script src="public/js/profile-common.js"></script>
<script src="public/js/auth-pages.js"></script> <script src="public/js/auth-pages.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
+1
View File
@@ -48,6 +48,7 @@
</div> </div>
</div> </div>
</footer> </footer>
<script src="config.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+35 -45
View File
@@ -3,67 +3,57 @@
<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 <body class="page-profile-module" data-feedback-list-page>
data-feature-status="pending"
data-feature-message="在线工单查询服务正在开发中,当前页面仅保留设计预览。"
>
<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="index.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>工单列表、处理进度与客服回复将在后续工单接口交付后接入。</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-status"> <aside class="module-nav" aria-label="个人中心功能导航"></aside>
<h2>暂未开放在线查询</h2> <div class="module-main">
<p>当前不能读取或展示工单数据,也不会伪造历史记录。</p> <section class="module-panel">
<div class="site-info-actions"> <div class="module-title-row">
<a <div><h2>反馈记录</h2><p>选择记录可以查看完整说明和处理状态。</p></div>
class="btn ghost magnetic" <button class="btn ghost" type="button" data-feedback-refresh>刷新</button>
href="help.html"
data-feature-link="available"
>返回帮助中心</a
>
</div> </div>
<div class="form-status" role="status" aria-live="polite" data-feedback-status></div>
<div class="module-list" data-feedback-list>
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取反馈记录…</div>
</div>
</section>
</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> <script src="config.js"></script>
</div> <script src="utils/StorageUtil.js"></script>
</div> <script src="utils/axios.js"></script>
</footer> <script src="utils/AxiosRequestUtil.js"></script>
<script src="public/js/pending-pages.js"></script> <script src="utils/ApiClient.js"></script>
<script src="public/js/feedback-pages.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+19 -20
View File
@@ -7,7 +7,7 @@
<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/css/site-info.css" />
</head> </head>
<body> <body data-site-news-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="index.html"
@@ -31,10 +31,10 @@
<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>
集中阅读平台公告与家谱文化内容。当前为静态展示,后续由新闻内容接口统一提供列表、分类和详情 集中阅读平台新闻、公告与下载内容
</p> </p>
</div> </div>
<div class="site-info-mark"></div> <div class="site-info-mark"></div>
@@ -42,27 +42,19 @@
</section> </section>
<section class="site-info-section"> <section class="site-info-section">
<div class="container site-info-layout"> <div class="container site-info-layout">
<div class="site-info-list"> <div>
<a class="site-info-item" id="platform" href="notice-detail.html" <div class="module-meta" data-site-news-status role="status" aria-live="polite"></div>
><small>平台公告</small> <div class="site-info-list" data-site-news-list>
<h2>平台内容公开展示规范说明</h2> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取资讯…</div>
<p> </div>
说明公开展示的家谱与文化内容范围,以及资料维护时应注意的边界。
</p></a
><a class="site-info-item" id="culture" href="article-detail.html"
><small>家谱文化</small>
<h2>为什么家谱不只是一本名册</h2>
<p>
从成员关系、家风记忆与共同维护三个角度理解数字家谱的价值。
</p></a
>
</div> </div>
<aside class="site-info-panel"> <aside class="site-info-panel">
<h2>新闻分类</h2> <h2>新闻分类</h2>
<div class="site-info-links"> <div class="site-info-links">
<a href="#platform">平台公告</a><a href="#culture">家谱文化</a <a href="news.html">全部</a
><a href="notice-detail.html">公告详情</a ><a href="news.html?articleType=news">新闻</a
><a href="article-detail.html">文化文章</a> ><a href="news.html?articleType=notice">公告</a
><a href="news.html?articleType=download">下载</a>
</div> </div>
</aside> </aside>
</div> </div>
@@ -78,6 +70,13 @@
</div> </div>
</div> </div>
</footer> </footer>
<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/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
<script src="public/js/site-news-pages.js"></script>
</body> </body>
</html> </html>
+1
View File
@@ -46,6 +46,7 @@
</div> </div>
</div> </div>
</footer> </footer>
<script src="config.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+5 -5
View File
@@ -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">
+496
View File
@@ -0,0 +1,496 @@
{
"name": "jiapu",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"devDependencies": {
"esbuild": "^0.28.2"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
"integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
"integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
"integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
"integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
"integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
"integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
"integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
"integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
"integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
"integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
"integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
"integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
"integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
"integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
"integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
"integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
"integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
"integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
"integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
"integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
"integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
"integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
"integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
"integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
"integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
"integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
"integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.28.2",
"@esbuild/android-arm": "0.28.2",
"@esbuild/android-arm64": "0.28.2",
"@esbuild/android-x64": "0.28.2",
"@esbuild/darwin-arm64": "0.28.2",
"@esbuild/darwin-x64": "0.28.2",
"@esbuild/freebsd-arm64": "0.28.2",
"@esbuild/freebsd-x64": "0.28.2",
"@esbuild/linux-arm": "0.28.2",
"@esbuild/linux-arm64": "0.28.2",
"@esbuild/linux-ia32": "0.28.2",
"@esbuild/linux-loong64": "0.28.2",
"@esbuild/linux-mips64el": "0.28.2",
"@esbuild/linux-ppc64": "0.28.2",
"@esbuild/linux-riscv64": "0.28.2",
"@esbuild/linux-s390x": "0.28.2",
"@esbuild/linux-x64": "0.28.2",
"@esbuild/netbsd-arm64": "0.28.2",
"@esbuild/netbsd-x64": "0.28.2",
"@esbuild/openbsd-arm64": "0.28.2",
"@esbuild/openbsd-x64": "0.28.2",
"@esbuild/openharmony-arm64": "0.28.2",
"@esbuild/sunos-x64": "0.28.2",
"@esbuild/win32-arm64": "0.28.2",
"@esbuild/win32-ia32": "0.28.2",
"@esbuild/win32-x64": "0.28.2"
}
}
}
}
-6
View File
@@ -1,6 +0,0 @@
{
"private": true,
"scripts": {
"test": "node --test tests/*.test.js"
}
}
+7 -7
View File
@@ -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">
+2 -1
View File
@@ -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>
@@ -67,6 +67,7 @@
</div> </div>
</div> </div>
</footer> </footer>
<script src="config.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+20 -28
View File
@@ -8,10 +8,10 @@
<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-admin-permission-page data-feature-status="pending" data-feature-message="管理员权限服务正在开发中,当前页面仅保留设计预览。"> <body class="page-profile-module" data-admin-permission-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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
@@ -19,20 +19,20 @@
><a class="active" href="profile-family-admin.html">家族管理</a> ><a class="active" href="profile-family-admin.html">家族管理</a>
</nav> </nav>
<div class="nav-actions"> <div class="nav-actions">
<a href="profile-family-admin.html">返回管理</a <a href="profile-family-admin.html" data-genealogy-context-link>返回管理</a
><button class="btn primary magnetic" type="submit" form="admin-permission-form">确定</button> ><a class="btn primary magnetic" href="profile-family-admin.html" data-genealogy-context-link>管理成员</a>
</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">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>
分配全权管理员、全系管理员、支系管理员,以及入谱审核等具体权限 在成员名册中维护管理员、编辑和成员角色;可执行的操作以当前账号权限为准
</p> </p>
</div> </div>
</div> </div>
@@ -41,34 +41,26 @@
<section class="section"> <section class="section">
<div class="container module-layout"> <div class="container module-layout">
<aside class="module-nav"> <aside class="module-nav">
<a href="profile-family-admin.html">家族管理</a <a href="profile-family-admin.html" data-genealogy-context-link>家族管理</a
><a class="active" href="profile-admin-permissions.html" ><a class="active" href="profile-admin-permissions.html"
>管理员权限</a >管理员权限</a
><a href="profile-messages.html">入谱审核</a> ><a href="profile-join-review.html" data-genealogy-context-link>入谱审核</a>
</aside> </aside>
<div class="module-main"> <div class="module-main">
<section class="module-panel"> <section class="module-panel">
<h2>权限设置</h2> <h2>当前权限</h2>
<form id="admin-permission-form" class="editor-form" data-permission-form> <div data-admin-permission-current>
<!-- 成员下拉由 /members/options 接口渲染 --> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在核对当前家谱角色与权限…</div>
<div class="editor-field">
<label for="permission-member">选择成员</label>
<select id="permission-member" data-member-select>
<option value="">成员加载中...</option>
</select>
</div> </div>
<div class="editor-field"> </section>
<label for="permission-role">角色权限</label> <section class="module-panel">
<select id="permission-role" name="roleType"> <h2>角色与权限</h2>
<option value="admin">管理员</option> <div class="module-list">
<option value="member">普通成员</option> <article class="module-row"><div><h3>谱主</h3><p>可维护管理员、编辑和成员角色,并可转让谱主。</p></div></article>
</select> <article class="module-row"><div><h3>管理员</h3><p>可维护编辑和成员角色,不能修改谱主或其他管理员。</p></div></article>
<article class="module-row"><div><h3>编辑、成员</h3><p>不显示成员角色编辑入口;最终权限由服务端校验。</p></div></article>
</div> </div>
<div class="bottom-actions"> <div class="bottom-actions"><a class="btn primary magnetic" href="profile-family-admin.html" data-genealogy-context-link>进入成员名册管理</a></div>
<button class="btn primary" type="submit">保存权限</button>
<button class="btn ghost" type="button" data-owner-transfer>转让所有者</button>
</div>
</form>
</section> </section>
</div> </div>
</div> </div>
@@ -83,7 +75,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/admin-permission-pages.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+14 -9
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-album-detail-page> <body class="page-profile-module" data-album-detail-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="index.html">
<img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /> <img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" />
<span class="brand-text">个人中心</span> <span class="brand-text">个人中心</span>
</a> </a>
@@ -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>
@@ -48,25 +48,29 @@
<section class="module-panel"> <section class="module-panel">
<h2>相册信息</h2> <h2>相册信息</h2>
<div data-album-detail> <div data-album-detail>
<div class="api-empty">相册加载中...</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
相册加载中...
</div>
</div> </div>
</section> </section>
<section class="module-panel"> <section class="module-panel">
<h2>照片列表</h2> <h2>照片列表</h2>
<div class="module-list" data-album-photo-list> <div class="module-list" data-album-photo-list>
<div class="api-empty">照片加载中...</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
照片加载中...
</div>
</div> </div>
</section> </section>
<section class="module-panel" data-album-photo-editor hidden> <section class="module-panel" data-album-photo-editor hidden>
<h2>添加照片</h2> <h2>添加照片</h2>
<form class="editor-form" data-album-photo-form> <form class="editor-form" data-album-photo-form novalidate>
<div class="editor-two"> <div class="editor-two">
<div class="editor-field"> <div class="editor-field">
<label for="albumPhotoFile">照片文件</label> <label for="albumPhotoFile">照片文件 <span class="field-required" aria-hidden="true">*</span></label>
<input id="albumPhotoOssId" name="ossId" type="hidden" /> <input id="albumPhotoOssId" name="ossId" type="hidden" />
<label class="upload-control" for="albumPhotoFile">选择照片</label> <label class="upload-control" for="albumPhotoFile">选择照片</label>
<input id="albumPhotoFile" class="upload-input" type="file" accept="image/*" data-upload-target="#albumPhotoOssId" data-upload-status="#albumPhotoUploadStatus" /> <input id="albumPhotoFile" class="upload-input" type="file" accept="image/*" data-upload-target="#albumPhotoOssId" data-upload-status="#albumPhotoUploadStatus" />
<p id="albumPhotoUploadStatus" class="upload-status">未选择文件</p> <p id="albumPhotoUploadStatus" class="upload-status" role="status" aria-live="polite">未选择文件</p>
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label for="albumPhotoTitle">照片标题</label> <label for="albumPhotoTitle">照片标题</label>
@@ -92,7 +96,7 @@
<input id="albumPhotoSortOrder" name="sortOrder" type="number" step="1" placeholder="数字越小越靠前" /> <input id="albumPhotoSortOrder" name="sortOrder" type="number" step="1" placeholder="数字越小越靠前" />
</div> </div>
<input name="status" type="hidden" value="0" /> <input name="status" type="hidden" value="0" />
<p class="api-status" data-album-photo-status></p> <div class="api-status" data-album-photo-status role="status" aria-live="polite"></div>
<div class="bottom-actions"> <div class="bottom-actions">
<button class="btn primary" type="submit">添加照片</button> <button class="btn primary" type="submit">添加照片</button>
</div> </div>
@@ -114,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>
+10 -8
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-album-edit-page> <body class="page-profile-module" data-album-edit-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="index.html">
<img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /> <img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" />
<span class="brand-text">个人中心</span> <span class="brand-text">个人中心</span>
</a> </a>
@@ -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>
@@ -47,10 +47,10 @@
</aside> </aside>
<div class="module-main"> <div class="module-main">
<section class="module-panel"> <section class="module-panel">
<div class="api-empty" data-album-editor-placeholder>正在读取家谱权限...</div> <div class="api-state api-state--loading" data-api-state="loading" data-album-editor-placeholder role="status" aria-live="polite" aria-busy="true">正在读取家谱权限...</div>
<form id="album-edit-form" class="editor-form" data-album-form data-album-editor hidden> <form id="album-edit-form" class="editor-form" data-album-form data-album-editor hidden novalidate>
<div class="editor-field"> <div class="editor-field">
<label for="albumName">相册名称</label> <label for="albumName">相册名称 <span class="field-required" aria-hidden="true">*</span></label>
<input id="albumName" name="albumName" type="text" required placeholder="请输入相册名称" /> <input id="albumName" name="albumName" type="text" required placeholder="请输入相册名称" />
</div> </div>
<div class="editor-field"> <div class="editor-field">
@@ -62,8 +62,8 @@
<label for="albumCoverFile">封面文件</label> <label for="albumCoverFile">封面文件</label>
<input id="albumCoverOssId" name="coverOssId" type="hidden" /> <input id="albumCoverOssId" name="coverOssId" type="hidden" />
<label class="upload-control" for="albumCoverFile">选择封面图片</label> <label class="upload-control" for="albumCoverFile">选择封面图片</label>
<input id="albumCoverFile" class="upload-input" type="file" accept="image/*" data-upload-target="#albumCoverOssId" data-upload-status="#albumCoverStatus" /> <input id="albumCoverFile" class="upload-input" type="file" accept="image/*" data-attachment-editor data-attachment-replace-only data-upload-target="#albumCoverOssId" data-upload-status="#albumCoverStatus" />
<p id="albumCoverStatus" class="upload-status">未选择文件</p> <p id="albumCoverStatus" class="upload-status" role="status" aria-live="polite">未选择文件</p>
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label for="albumSortOrder">排序</label> <label for="albumSortOrder">排序</label>
@@ -71,7 +71,7 @@
</div> </div>
</div> </div>
<input name="status" type="hidden" value="0" /> <input name="status" type="hidden" value="0" />
<p class="api-status" data-album-form-status></p> <div class="api-status" data-album-form-status role="status" aria-live="polite"></div>
<div class="bottom-actions"> <div class="bottom-actions">
<button class="btn primary" type="submit">保存相册</button> <button class="btn primary" type="submit">保存相册</button>
<a class="btn ghost" href="profile-album.html" data-genealogy-context-link>取消</a> <a class="btn ghost" href="profile-album.html" data-genealogy-context-link>取消</a>
@@ -93,7 +93,9 @@
<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/md5.js"></script> <script src="public/js/md5.js"></script>
<script src="public/js/attachment-editor.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>
+7 -4
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-album-page> <body class="page-profile-module" data-album-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="index.html">
<img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /> <img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" />
<span class="brand-text">个人中心</span> <span class="brand-text">个人中心</span>
</a> </a>
@@ -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>
@@ -51,7 +51,9 @@
<section class="module-panel"> <section class="module-panel">
<h2>相册列表</h2> <h2>相册列表</h2>
<div class="module-list" data-album-list> <div class="module-list" data-album-list>
<div class="api-empty">相册加载中...</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
相册加载中...
</div>
</div> </div>
</section> </section>
</div> </div>
@@ -67,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>
+13 -9
View File
@@ -12,7 +12,7 @@
<body class="page-profile-module" data-article-edit-page> <body class="page-profile-module" data-article-edit-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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
@@ -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>
@@ -50,10 +50,12 @@
<div class="module-main"> <div class="module-main">
<section class="module-panel"> <section class="module-panel">
<h2>谱文内容</h2> <h2>谱文内容</h2>
<div class="api-empty" data-article-editor-placeholder>正在读取家谱权限...</div> <div data-article-editor-placeholder>
<form id="article-edit-form" class="editor-form layui-form" data-article-form data-article-editor hidden> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取家谱权限…</div>
</div>
<form id="article-edit-form" class="editor-form layui-form" data-article-form data-article-editor hidden novalidate>
<div class="editor-field"> <div class="editor-field">
<label for="articleTitle">谱文标题</label <label for="articleTitle">谱文标题<span class="field-required" aria-hidden="true"> *</span></label
><input ><input
id="articleTitle" id="articleTitle"
name="articleTitle" name="articleTitle"
@@ -84,7 +86,7 @@
</div> </div>
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label for="articleContent">正文</label <label for="articleContent">正文<span class="field-required" aria-hidden="true"> *</span></label
><textarea ><textarea
id="articleContent" id="articleContent"
name="articleContent" name="articleContent"
@@ -101,11 +103,11 @@
<label for="articleCoverFile">封面文件</label> <label for="articleCoverFile">封面文件</label>
<input id="articleCoverOssId" name="coverOssId" type="hidden" /> <input id="articleCoverOssId" name="coverOssId" type="hidden" />
<label class="upload-control" for="articleCoverFile">选择封面图片</label> <label class="upload-control" for="articleCoverFile">选择封面图片</label>
<input id="articleCoverFile" class="upload-input" type="file" accept="image/*" data-upload-target="#articleCoverOssId" data-upload-status="#articleCoverStatus" /> <input id="articleCoverFile" class="upload-input" type="file" accept="image/*" data-attachment-editor data-upload-target="#articleCoverOssId" data-upload-status="#articleCoverStatus" />
<p id="articleCoverStatus" class="upload-status">未选择文件</p> <p id="articleCoverStatus" class="upload-status" role="status" aria-live="polite">未选择文件</p>
</div> </div>
<input name="status" type="hidden" value="0" /> <input name="status" type="hidden" value="0" />
<p class="api-status" data-article-form-status></p> <div class="form-status" data-article-form-status role="status" aria-live="polite"></div>
<div class="bottom-actions"> <div class="bottom-actions">
<button class="btn primary magnetic" type="submit">保存</button <button class="btn primary magnetic" type="submit">保存</button
><a class="btn ghost magnetic" href="profile-article.html" ><a class="btn ghost magnetic" href="profile-article.html"
@@ -131,7 +133,9 @@
<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/md5.js"></script> <script src="public/js/md5.js"></script>
<script src="public/js/attachment-editor.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>
+5 -4
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-article-page> <body class="page-profile-module" data-article-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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
@@ -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>
@@ -52,13 +52,13 @@
<section class="module-panel"> <section class="module-panel">
<h2>谱文列表</h2> <h2>谱文列表</h2>
<div class="module-list" data-article-list> <div class="module-list" data-article-list>
<div class="api-empty">谱文加载中...</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在加载谱文…</div>
</div> </div>
</section> </section>
<section class="module-panel"> <section class="module-panel">
<h2>谱文详情</h2> <h2>谱文详情</h2>
<div data-article-detail> <div data-article-detail>
<div class="api-empty">请选择一篇谱文查看详情</div> <div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一篇谱文查看详情</div>
</div> </div>
</section> </section>
</div> </div>
@@ -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>
+31 -11
View File
@@ -11,40 +11,59 @@
<body class="page-profile-module" data-ceremony-detail-page> <body class="page-profile-module" data-ceremony-detail-page>
<header class="site-header"> <header class="site-header">
<div class="container nav"> <div class="container nav">
<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> <a class="brand magnetic" href="index.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-content.html">内容发布</a><a class="active" href="profile-ceremony.html">祭祀活动</a></nav> <nav class="nav-links"><a href="profile-content.html">内容发布</a><a class="active" href="profile-ceremony.html">祭祀活动</a></nav>
<div class="nav-actions"><a href="profile-ceremony.html">返回活动列表</a><a class="btn primary magnetic" href="profile-gift-edit.html" data-ceremony-editor-action hidden>编辑活动</a></div> <div class="nav-actions"><a href="profile-ceremony.html">返回活动列表</a><a class="btn primary magnetic" href="profile-gift-edit.html" data-ceremony-editor-action hidden>编辑活动</a></div>
</div> </div>
</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">
<aside class="module-nav"><a href="profile-content.html">内容发布</a><a href="profile-gift.html">我的邀请</a><a class="active" href="profile-ceremony.html">祭祀活动</a><a href="profile-merit.html">功德录</a></aside> <aside class="module-nav"><a href="profile-content.html">内容发布</a><a href="profile-gift.html">我的邀请</a><a class="active" href="profile-ceremony.html">祭祀活动</a><a href="profile-merit.html">功德录</a></aside>
<div class="module-main"> <div class="module-main">
<section class="module-panel"><h2>活动信息</h2><div data-ceremony-detail aria-live="polite"><div class="api-empty">正在加载活动详情…</div></div></section> <section class="module-panel">
<h2>活动信息</h2>
<div data-ceremony-detail aria-live="polite">
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
正在加载活动详情…
</div>
</div>
</section>
<section class="module-panel"> <section class="module-panel">
<h2>祭品记录</h2> <h2>祭品记录</h2>
<div class="module-list" data-ceremony-gift-list aria-live="polite"><div class="api-empty">正在加载祭品…</div></div> <div class="module-list" data-ceremony-gift-list aria-live="polite">
<form class="editor-form" data-ceremony-gift-form data-ceremony-admin-editor hidden> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
正在加载祭品…
</div>
</div>
<form class="editor-form" data-ceremony-gift-form data-ceremony-admin-editor hidden novalidate>
<div class="editor-two"> <div class="editor-two">
<div class="editor-field"><label for="ceremonyGiverName">赠送人姓名</label><input id="ceremonyGiverName" name="giverName" type="text" placeholder="可选" /></div> <div class="editor-field"><label for="ceremonyGiverName">赠送人姓名</label><input id="ceremonyGiverName" name="giverName" type="text" placeholder="可选" /></div>
<div class="editor-field"><label for="ceremonyGiftAmount">礼金金额</label><input id="ceremonyGiftAmount" name="giftAmount" type="number" min="0" step="any" required /></div> <div class="editor-field"><label for="ceremonyGiftAmount">礼金金额 <span class="field-required" aria-hidden="true">*</span></label><input id="ceremonyGiftAmount" name="giftAmount" type="number" min="0" step="any" required /></div>
</div> </div>
<div class="editor-field"><label for="ceremonyGiftMessage">祭品留言</label><textarea id="ceremonyGiftMessage" name="giftMessage" placeholder="可选"></textarea></div> <div class="editor-field"><label for="ceremonyGiftMessage">祭品留言</label><textarea id="ceremonyGiftMessage" name="giftMessage" placeholder="可选"></textarea></div>
<p class="upload-status" data-ceremony-gift-status aria-live="polite"></p> <div class="upload-status" data-ceremony-gift-status role="status" aria-live="polite"></div>
<div class="bottom-actions"><button class="btn primary magnetic" type="submit">添加祭品</button></div> <div class="bottom-actions"><button class="btn primary magnetic" type="submit">添加祭品</button></div>
</form> </form>
</section> </section>
<section class="module-panel" data-ceremony-admin-editor hidden> <section class="module-panel" data-ceremony-admin-editor hidden>
<h2>受邀名单</h2> <h2>受邀名单</h2>
<div class="module-list" data-ceremony-invitation-list aria-live="polite"><div class="api-empty">正在加载邀请记录…</div></div> <div class="module-list" data-ceremony-invitation-list aria-live="polite">
<form class="editor-form" data-ceremony-invitee-form> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
正在加载邀请记录…
</div>
</div>
<form class="editor-form" data-ceremony-invitee-form data-ceremony-admin-editor hidden novalidate>
<p>仅显示当前家谱中已绑定业务账号的正常成员;提交完整勾选名单,取消勾选会撤销尚未响应的邀请。</p> <p>仅显示当前家谱中已绑定业务账号的正常成员;提交完整勾选名单,取消勾选会撤销尚未响应的邀请。</p>
<div class="editor-choice-list" data-ceremony-invitee-options><div class="api-empty">正在加载成员选项…</div></div> <div class="editor-choice-list" data-ceremony-invitee-options>
<p class="upload-status" data-ceremony-invitee-status aria-live="polite"></p> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
正在加载成员选项…
</div>
</div>
<div class="upload-status" data-ceremony-invitee-status role="status" aria-live="polite"></div>
<div class="bottom-actions"><button class="btn primary magnetic" type="submit">更新受邀名单</button></div> <div class="bottom-actions"><button class="btn primary magnetic" type="submit">更新受邀名单</button></div>
</form> </form>
</section> </section>
@@ -61,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>
+8 -3
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-ceremony-list-page> <body class="page-profile-module" data-ceremony-list-page>
<header class="site-header"> <header class="site-header">
<div class="container nav"> <div class="container nav">
<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> <a class="brand magnetic" href="index.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-content.html">内容发布</a><a class="active" href="profile-ceremony.html">祭祀活动</a></nav> <nav class="nav-links"><a href="profile-content.html">内容发布</a><a class="active" href="profile-ceremony.html">祭祀活动</a></nav>
<div class="nav-actions"><a href="profile-content.html">返回发布中心</a><a class="btn primary magnetic" href="profile-gift-edit.html" data-ceremony-create-link data-genealogy-context-link hidden>创建活动</a></div> <div class="nav-actions"><a href="profile-content.html">返回发布中心</a><a class="btn primary magnetic" href="profile-gift-edit.html" data-ceremony-create-link data-genealogy-context-link hidden>创建活动</a></div>
</div> </div>
@@ -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>
@@ -29,7 +29,11 @@
<div class="module-main"> <div class="module-main">
<section class="module-panel"> <section class="module-panel">
<div class="module-panel-head"><h2>活动列表</h2><a class="pill" href="profile-gift-edit.html" data-ceremony-create-link data-genealogy-context-link hidden>创建活动</a></div> <div class="module-panel-head"><h2>活动列表</h2><a class="pill" href="profile-gift-edit.html" data-ceremony-create-link data-genealogy-context-link hidden>创建活动</a></div>
<div class="module-list" data-ceremony-list aria-live="polite"><div class="api-empty">正在加载祭祀活动…</div></div> <div class="module-list" data-ceremony-list aria-live="polite">
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
正在加载祭祀活动…
</div>
</div>
</section> </section>
</div> </div>
</div> </div>
@@ -44,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>
+12 -13
View File
@@ -1,4 +1,4 @@
<!doctype html> <!doctype html>
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -11,19 +11,19 @@
<body class="page-profile-module" data-content-page> <body class="page-profile-module" data-content-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="index.html"
><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a ><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a
> >
<nav class="nav-links"> <nav class="nav-links">
<a href="profile.html">首页</a <a href="profile.html">个人中心</a
><a href="profile-families.html">数字家谱</a ><a href="profile-families.html">我的家谱</a
><a href="plaza.html">家谱广场</a ><a href="plaza.html">家谱广场</a
><a class="active" href="profile-content.html">家族文化</a ><a class="active" href="profile-content.html">家族文化</a
><a href="surname.html">姓氏百科</a><a href="app.html">应用下载</a ><a href="surname.html">姓氏百科</a><a href="app.html">应用下载</a
><a href="help.html">帮助中心</a><a href="about.html">关于我们</a> ><a href="help.html">帮助中心</a><a href="about.html">关于我们</a>
</nav> </nav>
<div class="nav-actions"> <div class="nav-actions">
<a href="profile.html">个人中心</a <a href="profile-data.html">账户资料</a
><a class="btn primary magnetic" href="profile-create-family.html" ><a class="btn primary magnetic" href="profile-create-family.html"
>创建家谱</a >创建家谱</a
> >
@@ -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
@@ -99,19 +98,19 @@
> >
<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>记录亲友关系、事项和礼金。</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>记录家族事务、提醒和完成状态</p></a <p>以标题、内容和提醒记录家族恩人等事项,统一归入备忘录管理</p></a
> >
</div> </div>
</section> </section>
<section class="module-panel"> <section class="module-panel">
<h2>使用说明</h2> <h2>使用说明</h2>
<div class="api-empty">选择家谱,再进入对应模块查看和维护真实内容。</div> <div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择上方功能,继续查看和维护当前家谱内容。</div>
</section> </section>
</div> </div>
</div> </div>
+25 -17
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-genealogy-create-page> <body class="page-profile-module" data-genealogy-create-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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
@@ -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>
个人中心专用创建流程,用于录入姓氏、谱名、堂号、地望、祠堂和访问权限 填写家谱基本信息;标有 * 的字段为必填,创建后可进入家谱工作区继续维护
</p> </p>
</div> </div>
</div> </div>
@@ -48,16 +48,16 @@
<div class="module-main"> <div class="module-main">
<section class="module-panel"> <section class="module-panel">
<h2>基础信息</h2> <h2>基础信息</h2>
<p class="upload-status" data-genealogy-create-quota>正在读取家谱额度…</p> <p class="upload-status" data-genealogy-create-quota role="status" aria-live="polite">正在读取家谱额度…</p>
<form class="editor-form profile-create-family-form" data-genealogy-create-form> <form class="editor-form profile-create-family-form" data-genealogy-create-form novalidate>
<div class="editor-two"> <div class="editor-two">
<div class="editor-field"> <div class="editor-field">
<label for="familySurname">姓氏</label> <label for="familySurname">姓氏 <span class="field-required" aria-hidden="true">*</span></label>
<input id="familySurname" name="surname" type="text" required /> <input id="familySurname" name="surname" type="text" required placeholder="例如:王" />
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label for="familyName">谱名</label> <label for="familyName">谱名 <span class="field-required" aria-hidden="true">*</span></label>
<input id="familyName" name="genealogyName" type="text" required /> <input id="familyName" name="genealogyName" type="text" required placeholder="例如:王氏家谱" />
</div> </div>
</div> </div>
<div class="editor-two"> <div class="editor-two">
@@ -70,20 +70,27 @@
<input id="originPlace" name="originPlace" type="text" /> <input id="originPlace" name="originPlace" type="text" />
</div> </div>
</div> </div>
<div class="editor-field"> <div class="editor-field" role="group" aria-labelledby="family-region-label" aria-describedby="family-create-status">
<label>家族所在地</label> <span id="family-region-label" class="editor-label">家族所在地 <span class="field-required" aria-hidden="true">*</span></span>
<div class="profile-region-picker" data-region-picker> <div class="profile-region-picker" data-region-picker>
<select name="provinceCode" data-region-level="province"> <label for="familyProvinceCode">
<select id="familyProvinceCode" name="provinceCode" data-region-level="province">
<option value="">请选择省</option> <option value="">请选择省</option>
</select> </select>
<select name="cityCode" data-region-level="city"> </label>
<label for="familyCityCode">
<select id="familyCityCode" name="cityCode" data-region-level="city">
<option value="">请选择市</option> <option value="">请选择市</option>
</select> </select>
<select name="districtCode" data-region-level="district"> </label>
<label for="familyDistrictCode">区县
<select id="familyDistrictCode" name="districtCode" data-region-level="district">
<option value="">请选择区县</option> <option value="">请选择区县</option>
</select> </select>
</label>
<input type="hidden" name="regionCode" data-region-code /> <input type="hidden" name="regionCode" data-region-code />
</div> </div>
<div class="upload-status" data-region-picker-status role="status" aria-live="polite"></div>
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label for="addressDetail">详细地址</label> <label for="addressDetail">详细地址</label>
@@ -105,7 +112,7 @@
data-upload-target="#familyCoverOssId" data-upload-target="#familyCoverOssId"
data-upload-status="#familyCoverStatus" data-upload-status="#familyCoverStatus"
/> />
<p id="familyCoverStatus" class="upload-status">未选择文件</p> <p id="familyCoverStatus" class="upload-status" role="status" aria-live="polite">未选择文件</p>
</div> </div>
<div class="editor-two"> <div class="editor-two">
<div class="editor-field"> <div class="editor-field">
@@ -118,14 +125,15 @@
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label for="joinMode">加入方式</label> <label for="joinMode">加入方式</label>
<select id="joinMode" name="joinMode"> <select id="joinMode" name="joinMode" aria-describedby="family-invite-mode-note">
<option value="1">审核加入</option> <option value="1">审核加入</option>
<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">创建完成后,可在家谱首页的“邀请家人”中生成一次性邀请链接。</p>
</div> </div>
</div> </div>
<p class="upload-status" data-genealogy-create-status aria-live="polite"></p> <div id="family-create-status" class="upload-status" data-genealogy-create-status role="status" aria-live="polite"></div>
<div class="bottom-actions"> <div class="bottom-actions">
<button class="btn primary magnetic" type="submit" data-genealogy-create-submit>确定创建</button> <button class="btn primary magnetic" type="submit" data-genealogy-create-submit>确定创建</button>
<a class="btn ghost magnetic" href="profile-families.html">返回列表</a> <a class="btn ghost magnetic" href="profile-families.html">返回列表</a>
+12 -17
View File
@@ -8,10 +8,10 @@
<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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
@@ -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,20 +49,10 @@
<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>
<a class="module-row" href="profile-data.html" <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
><div> 正在读取资料提醒…
<h3>鹿野 Loyel</h3>
<p>缺少出生日期、出生地址和微信信息</p>
</div> </div>
<span class="pill">完善</span></a
><a class="module-row" href="profile-data.html"
><div>
<h3>张三</h3>
<p>缺少头像和亲属关系</p>
</div>
<span class="pill">编辑</span></a
>
</div> </div>
</section> </section>
</div> </div>
@@ -73,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>
+49 -53
View File
@@ -1,4 +1,4 @@
<!doctype html> <!doctype html>
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -11,19 +11,19 @@
<body class="page-profile-module" data-profile-page> <body class="page-profile-module" data-profile-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="index.html"
><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a ><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a
> >
<nav class="nav-links"> <nav class="nav-links">
<a href="profile.html">首页</a <a href="profile.html">个人中心</a
><a href="profile-families.html">数字家谱</a ><a href="profile-families.html">我的家谱</a
><a href="plaza.html">家谱广场</a ><a href="plaza.html">家谱广场</a
><a href="profile-content.html">家族文化</a ><a href="profile-content.html">家族文化</a
><a href="surname.html">姓氏百科</a><a href="app.html">应用下载</a ><a href="surname.html">姓氏百科</a><a href="app.html">应用下载</a
><a href="help.html">帮助中心</a><a href="about.html">关于我们</a> ><a href="help.html">帮助中心</a><a href="about.html">关于我们</a>
</nav> </nav>
<div class="nav-actions"> <div class="nav-actions">
<a class="active" href="profile.html">个人中心</a <a class="active" href="profile-data.html">账户资料</a
><a class="btn primary magnetic" href="profile-create-family.html" ><a class="btn primary magnetic" href="profile-create-family.html"
>创建家谱</a >创建家谱</a
> >
@@ -33,12 +33,12 @@
<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>
<p> <p>
管理个人资料、亲属关系和账号安全信息。 管理个人资料和账号安全信息;家谱成员关系请在当前家谱中维护
</p> </p>
</div> </div>
<a class="btn primary magnetic" href="#profile-edit-form" <a class="btn primary magnetic" href="#profile-edit-form"
@@ -60,7 +60,7 @@
<div class="module-main"> <div class="module-main">
<section class="module-panel"> <section class="module-panel">
<h2>编辑资料</h2> <h2>编辑资料</h2>
<form class="editor-form profile-data-form" id="profile-edit-form" data-profile-form> <form class="editor-form profile-data-form" id="profile-edit-form" data-profile-form novalidate>
<div class="editor-two"> <div class="editor-two">
<div class="editor-field"> <div class="editor-field">
<label for="profileNickName">昵称</label> <label for="profileNickName">昵称</label>
@@ -105,7 +105,7 @@
data-upload-biz-type="avatar" data-upload-biz-type="avatar"
data-upload-usage-scene="profile_avatar" data-upload-usage-scene="profile_avatar"
/> />
<p id="profileAvatarStatus" class="upload-status">请选择头像文件</p> <p id="profileAvatarStatus" class="upload-status" role="status" aria-live="polite">请选择头像文件</p>
</div> </div>
</div> </div>
<div class="editor-two"> <div class="editor-two">
@@ -135,30 +135,35 @@
/> />
</div> </div>
<div class="bottom-actions profile-form-actions"> <div class="bottom-actions profile-form-actions">
<button class="btn primary magnetic" type="submit">保存资料</button> <button class="btn primary magnetic" type="submit" data-profile-submit>保存资料</button>
<span class="form-status" data-profile-status></span> <div class="form-status" data-profile-status role="status" aria-live="polite"></div>
</div> </div>
</form> </form>
</section> </section>
<section class="module-panel"> <section class="module-panel">
<h2>基本资料</h2> <h2>基本资料</h2>
<div data-profile-load-status><div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取账户资料…</div></div>
<div class="form-like"> <div class="form-like">
<p> <p>
<span>头像</span><b>已上传</b <span>头像</span><b data-profile-text="avatarStatus">正在读取</b
><a class="link-btn" href="profile-data.html">更换</a> ><a class="link-btn" href="profile-data.html">更换</a>
</p> </p>
<p> <p>
<span>姓名</span><b data-profile-text="displayName">鹿野 Loyel</b <span>昵称</span><b data-profile-text="displayName">正在读取</b
><a class="link-btn" href="profile-data.html">编辑</a> ><a class="link-btn" href="profile-data.html">编辑</a>
</p> </p>
<p> <p>
<span>绑定账号</span><b data-profile-text="phone">已绑定</b <span>真实姓名</span><b data-profile-text="realName">正在读取</b
><a class="link-btn" href="#profile-edit-form">编辑</a>
</p>
<p>
<span>绑定账号</span><b data-profile-text="phone">正在读取</b
><a class="link-btn" href="profile-security.html">安全设置</a> ><a class="link-btn" href="profile-security.html">安全设置</a>
</p> </p>
<p> <p>
<span>血亲关系</span><b>第二代 顺字辈</b <span>家谱关系</span><b>请在当前家谱中查看</b
><a class="link-btn" href="profile-family-admin.html" ><a class="link-btn" href="profile-family-admin.html" data-genealogy-context-link
>查看世系</a >成员管理</a
> >
</p> </p>
</div> </div>
@@ -167,78 +172,68 @@
<h2>个人信息</h2> <h2>个人信息</h2>
<div class="form-like"> <div class="form-like">
<p> <p>
<span>父亲</span><b>某某</b <span>性别</span><b data-profile-text="sex">正在读取</b
><a class="link-btn" href="profile-data.html">编辑</a> ><a class="link-btn" href="#profile-edit-form">编辑</a>
</p> </p>
<p> <p>
<span>出生日期</span><b data-profile-text="birthday">待选择</b <span>出生日期</span><b data-profile-text="birthday">待选择</b
><a class="link-btn" href="profile-data.html">选择</a> ><a class="link-btn" href="#profile-edit-form">编辑</a>
</p> </p>
<p> <p>
<span>现居地址</span><b data-profile-text="regionText">待填写</b <span>邮箱</span><b data-profile-text="email">正在读取</b
><a class="link-btn" href="profile-data.html">填写</a> ><a class="link-btn" href="#profile-edit-form">编辑</a>
</p>
<p>
<span>微信 / QQ</span><b>待填写</b
><a class="link-btn" href="profile-data.html">填写</a>
</p>
<p>
<span>学历 / 职业</span><b>待填写</b
><a class="link-btn" href="profile-data.html">填写</a>
</p> </p>
</div> </div>
</section> </section>
<section class="module-panel"> <section class="module-panel">
<h2>现居地区</h2> <h2 id="profile-region-heading">现居地区</h2>
<div class="editor-form region-profile-form"> <div class="editor-form region-profile-form" role="group" aria-labelledby="profile-region-heading" aria-describedby="profile-region-contract-note profile-region-picker-status">
<div class="region-picker profile-region-picker" data-region-picker> <div class="region-picker profile-region-picker" data-region-picker>
<select class="input" name="provinceCode" data-region-level="province"> <label for="profileProvinceCode">
<select id="profileProvinceCode" class="input" name="provinceCode" data-region-level="province">
<option value="">请选择省</option> <option value="">请选择省</option>
</select> </select>
<select class="input" name="cityCode" data-region-level="city"> </label>
<label for="profileCityCode">
<select id="profileCityCode" class="input" name="cityCode" data-region-level="city">
<option value="">请选择市</option> <option value="">请选择市</option>
</select> </select>
<select class="input" name="districtCode" data-region-level="district"> </label>
<label for="profileDistrictCode">区县
<select id="profileDistrictCode" class="input" name="districtCode" data-region-level="district">
<option value="">请选择区县</option> <option value="">请选择区县</option>
</select> </select>
</label>
</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 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> </div>
<form class="editor-form" data-region-search-form data-region-search-panel> <form class="editor-form" data-region-search-form data-region-search-panel novalidate>
<div class="editor-two"> <div class="editor-two">
<div class="editor-field"> <div class="editor-field">
<label for="profileRegionKeyword">搜索地区</label> <label for="profileRegionKeyword">搜索地区</label>
<input id="profileRegionKeyword" name="regionKeyword" type="search" placeholder="地区名称或编码" /> <input id="profileRegionKeyword" name="regionKeyword" type="search" placeholder="地区名称或编码" aria-describedby="profile-region-search-status" required />
</div> </div>
<div class="bottom-actions"> <div class="bottom-actions">
<button class="btn ghost magnetic" type="submit">搜索</button> <button class="btn ghost magnetic" type="submit">搜索</button>
</div> </div>
</div> </div>
<div id="profile-region-search-status" class="form-status" data-region-search-status role="status" aria-live="polite"></div>
</form> </form>
<p class="api-status" data-region-search-path></p> <p class="api-status" data-region-search-path></p>
<div class="module-list" data-region-search-results></div> <div class="module-list" data-region-search-results></div>
</section> </section>
<section class="module-panel"> <section class="module-panel">
<h2>亲属</h2> <h2>亲属关系</h2>
<div class="module-list"> <div class="module-list">
<div class="module-row"> <div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">
<div> 个人资料页不单独维护亲属关系;请先选择家谱,再到成员管理或世系图中查看和维护。
<h3>张三</h3>
<p>父亲 · 已绑定成员资料</p>
</div>
<span class="pill">查看</span>
</div>
<div class="module-row">
<div>
<h3>张三</h3>
<p>父亲 · 未绑定账号</p>
</div>
<span class="pill">邀请激活</span>
</div> </div>
</div> </div>
<div class="bottom-actions"><a class="btn ghost magnetic" href="profile-family-admin.html" data-genealogy-context-link>前往成员管理</a></div>
</section> </section>
</div> </div>
</div> </div>
@@ -253,11 +248,12 @@
<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/profile-common.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/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/profile-common.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+14
View File
@@ -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="index.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>
+13
View File
@@ -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="index.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>
+27 -14
View File
@@ -1,4 +1,4 @@
<!doctype html> <!doctype html>
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -11,29 +11,28 @@
<body class="page-profile-module"> <body class="page-profile-module">
<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="index.html"
><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a ><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a
> >
<nav class="nav-links"> <nav class="nav-links">
<a href="profile.html">首页</a <a href="profile.html">个人中心</a
><a class="active" href="profile-families.html">数字家谱</a ><a class="active" href="profile-families.html">我的家谱</a
><a href="plaza.html">家谱广场</a ><a href="plaza.html">家谱广场</a
><a href="profile-content.html">家族文化</a ><a href="profile-content.html">家族文化</a
><a href="surname.html">姓氏百科</a><a href="app.html">应用下载</a ><a href="surname.html">姓氏百科</a><a href="app.html">应用下载</a
><a href="help.html">帮助中心</a><a href="about.html">关于我们</a> ><a href="help.html">帮助中心</a><a href="about.html">关于我们</a>
</nav> </nav>
<div class="nav-actions"> <div class="nav-actions">
<a href="profile.html">个人中心</a <a href="profile-data.html">账户资料</a>
><a class="btn primary magnetic" href="profile-create-family.html"
>创建家谱</a
>
</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">Personal Center</div> <nav class="profile-path" aria-label="当前位置">
<a href="profile.html">个人中心</a><span aria-hidden="true">/</span><strong aria-current="page">我的家谱</strong>
</nav>
<div class="module-title-row"> <div class="module-title-row">
<div> <div>
<h1>我的家谱</h1> <h1>我的家谱</h1>
@@ -60,10 +59,12 @@
<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>
<p class="api-status" data-genealogy-entry-status>正在读取我的家谱…</p> <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-empty">正在加载列表…</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
正在读取我的家谱…
</div>
</div> </div>
</section> </section>
<section class="module-panel"> <section class="module-panel">
@@ -79,8 +80,20 @@
<a class="module-card tilt-card" href="profile-join-family.html" <a class="module-card tilt-card" href="profile-join-family.html"
><span class="icon"></span> ><span class="icon"></span>
<h3>加入家谱</h3> <h3>加入家谱</h3>
<p>输入邀请码或由管理员邀请,加入已有家谱协作</p></a <p>搜索并选择可加入的家谱,提交关系和申请说明等待审核</p></a
> >
<div class="module-card">
<span class="icon"></span>
<h3>家谱排序</h3>
<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>
</section> </section>
</div> </div>
@@ -95,8 +108,8 @@
<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/genealogy-entry-pages.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/genealogy-entry-pages.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+16 -14
View File
@@ -11,15 +11,15 @@
<body class="page-profile-module" data-member-admin-page> <body class="page-profile-module" data-member-admin-page>
<header class="site-header"> <header class="site-header">
<div class="container nav"> <div class="container nav">
<a class="brand magnetic" href="profile.html"><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a> <a class="brand magnetic" href="index.html"><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a>
<nav class="nav-links"><a href="profile.html">首页</a><a class="active" href="profile-families.html">数字家谱</a><a href="profile-content.html">家族文化</a></nav> <nav class="nav-links"><a href="profile.html">个人中心</a><a class="active" href="profile-families.html">我的家谱</a><a href="profile-content.html">家族文化</a></nav>
<div class="nav-actions"><a href="profile.html">个人中心</a><a class="btn primary magnetic" href="profile-tree.html" data-genealogy-context-link>世系人物</a></div> <div class="nav-actions"><a href="profile-data.html">账户资料</a><a class="btn primary magnetic" href="profile-tree.html" data-genealogy-context-link>世系人物</a></div>
</div> </div>
</header> </header>
<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>
@@ -31,22 +31,24 @@
<div class="module-main"> <div class="module-main">
<section class="module-panel"> <section class="module-panel">
<h2>家谱概览</h2> <h2>家谱概览</h2>
<p data-member-overview>正在加载家谱和当前权限…</p> <div data-member-overview><div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在加载家谱和当前权限…</div></div>
<div class="module-grid"> <div class="module-grid">
<a class="module-card tilt-card" href="profile-tree.html" data-feature-link="available" data-genealogy-context-link><span class="icon"></span><h3>世系图</h3><p>查看或维护世系人物与亲属关系。</p></a> <a class="module-card tilt-card" href="profile-tree.html" data-feature-link="available" data-genealogy-context-link><span class="icon"></span><h3>世系图</h3><p>查看或维护世系人物与亲属关系。</p></a>
<a class="module-card tilt-card" href="profile-generation.html" data-feature-link="available" data-genealogy-context-link><span class="icon"></span><h3>字辈谱</h3><p>查看或维护家谱字辈。</p></a> <a class="module-card tilt-card" href="profile-generation.html" data-feature-link="available" data-genealogy-context-link><span class="icon"></span><h3>字辈谱</h3><p>查看或维护家谱字辈。</p></a>
<a class="module-card tilt-card" href="profile-ceremony.html" data-genealogy-context-link><span class="icon"></span><h3>祭祀活动</h3><p>维护活动、祭品与受邀名单。</p></a> <a class="module-card tilt-card" href="profile-ceremony.html" data-genealogy-context-link><span class="icon"></span><h3>祭祀活动</h3><p>维护活动、祭品与受邀名单。</p></a>
<a class="module-card tilt-card" href="profile-article.html" data-genealogy-context-link><span class="icon"></span><h3>谱文</h3><p>查看或维护当前家谱谱文。</p></a> <a class="module-card tilt-card" href="profile-article.html" data-genealogy-context-link><span class="icon"></span><h3>谱文</h3><p>查看或维护当前家谱谱文。</p></a>
<a class="module-card tilt-card" href="profile-join-review.html" data-genealogy-context-link><span class="icon"></span><h3>入谱审核</h3><p>审核当前家谱的待处理加入申请。</p></a>
</div> </div>
</section> </section>
<section class="module-panel"> <section class="module-panel">
<h2>正常成员</h2> <h2>正常成员</h2>
<p>成员新增必须通过后端已有的加入或邀请流程;本页不伪造直接新增成员。</p> <p>成员新增必须通过后端已有的加入或邀请流程;本页不伪造直接新增成员。</p>
<div class="module-list" data-member-list aria-live="polite"><div class="api-empty">成员加载中…</div></div> <div class="form-status" role="status" aria-live="polite" data-member-action-status></div>
<div class="module-list" data-member-list aria-live="polite"><div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">成员加载中…</div></div>
</section> </section>
<section class="module-panel" data-member-management hidden> <section class="module-panel" data-member-management hidden>
<h2 data-member-form-title>编辑成员</h2> <h2 data-member-form-title>编辑成员</h2>
<form class="editor-form" data-member-form hidden> <form class="editor-form" data-member-form novalidate hidden>
<div class="editor-two"> <div class="editor-two">
<div class="editor-field"><label for="memberAdminName">成员名称</label><input id="memberAdminName" name="memberName" type="text" maxlength="50" /></div> <div class="editor-field"><label for="memberAdminName">成员名称</label><input id="memberAdminName" name="memberName" type="text" maxlength="50" /></div>
<div class="editor-field"><label for="memberAdminRelation">与家谱关系</label><input id="memberAdminRelation" name="relationName" type="text" maxlength="100" /></div> <div class="editor-field"><label for="memberAdminRelation">与家谱关系</label><input id="memberAdminRelation" name="relationName" type="text" maxlength="100" /></div>
@@ -55,18 +57,18 @@
<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 更新接口不提供解除世系人物绑定;留空表示不修改,不会发送空 ID</p> <p class="upload-status">留空表示保持原信息不变;只填写本次需要修改的内容</p>
<p class="upload-status" data-member-form-status aria-live="polite"></p> <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">保存成员</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>
</section> </section>
<section class="module-panel" data-owner-transfer-section hidden> <section class="module-panel" data-owner-transfer-section hidden>
<h2>转让谱主</h2> <h2>转让谱主</h2>
<form class="editor-form" data-owner-transfer-form> <form class="editor-form" data-owner-transfer-form novalidate>
<div class="editor-field"><label for="ownerTransferTarget">新谱主</label><select id="ownerTransferTarget" name="targetMemberId" data-owner-transfer-target><option value="">请选择新谱主</option></select></div> <div class="editor-field"><label for="ownerTransferTarget">新谱主 <span class="field-required" aria-hidden="true">*</span></label><select id="ownerTransferTarget" name="targetMemberId" data-owner-transfer-target required><option value="">请选择新谱主</option></select></div>
<p>只能选择当前正常成员。转让后当前账号变为管理员,新谱主受其账号家谱拥有额度约束。</p> <p>只能选择当前正常成员。转让后当前账号变为管理员,新谱主受其账号家谱拥有额度约束。</p>
<p class="upload-status" data-owner-transfer-status aria-live="polite"></p> <div class="upload-status" role="status" aria-live="polite" data-owner-transfer-status></div>
<div class="bottom-actions"><button class="btn ghost" type="submit">确认转让谱主</button></div> <div class="bottom-actions"><button class="btn ghost" type="submit" data-owner-transfer-submit>确认转让谱主</button></div>
</form> </form>
</section> </section>
<section class="module-panel" data-member-leave-section hidden> <section class="module-panel" data-member-leave-section hidden>
+98 -25
View File
@@ -8,21 +8,21 @@
<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-home-page data-feature-status="pending" data-feature-message="家谱概览与管理服务正在开发中,当前页面仅保留设计预览。"> <body class="page-profile-module" data-family-home-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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
<a class="active" href="profile-families.html">我的家谱</a <a class="active" href="profile-families.html">我的家谱</a
><a href="profile-family-admin.html" data-genealogy-context-link>家族管理</a ><a href="profile-family-admin.html" data-genealogy-context-link>家族管理</a
><a href="profile-content.html">内容发布</a ><a href="profile-content.html" data-genealogy-context-link>内容发布</a
><a href="profile-data.html">个人资料</a ><a href="profile-data.html">个人资料</a
><a href="profile-services.html">帮助与服务</a> ><a href="profile-services.html">帮助与服务</a>
</nav> </nav>
<div class="nav-actions"> <div class="nav-actions">
<a href="profile.html">返回中心</a <a href="profile.html">账户中心</a
><a class="btn primary magnetic" href="profile-feed.html" data-feature-link="available" data-genealogy-context-link>发布动态</a> ><a class="btn primary magnetic" href="profile-feed.html" data-feature-link="available" data-genealogy-context-link>发布动态</a>
</div> </div>
</div> </div>
@@ -30,15 +30,17 @@
<main> <main>
<section class="module-hero"> <section class="module-hero">
<div class="container"> <div class="container">
<div class="module-kicker">Family Home</div> <nav class="profile-path" aria-label="当前位置">
<a href="profile.html">个人中心</a><span aria-hidden="true">/</span><a href="profile-families.html">我的家谱</a><span aria-hidden="true">/</span><strong data-family-home-context aria-current="page">当前家谱</strong>
</nav>
<div class="module-title-row"> <div class="module-title-row">
<div> <div>
<h1 data-family-home-title>四川武胜汤氏族</h1> <h1 data-family-home-title>正在读取家谱</h1>
<p> <p>
后台家谱主页,用于进入世系、相册、家族圈、内容管理和成员协作。 当前家谱的工作区,用于进入世系、相册、家族圈、内容管理和成员协作。
</p> </p>
</div> </div>
<a class="btn ghost magnetic" href="profile-family-admin.html" data-genealogy-context-link <a class="btn ghost magnetic" href="profile-family-admin.html" data-family-home-management data-genealogy-context-link hidden
>管理家谱</a >管理家谱</a
> >
</div> </div>
@@ -50,39 +52,49 @@
<a class="active" href="profile-family-home.html" data-genealogy-context-link>家谱主页</a <a class="active" href="profile-family-home.html" data-genealogy-context-link>家谱主页</a
><a href="profile-tree.html" data-genealogy-context-link>世系图</a ><a href="profile-tree.html" data-genealogy-context-link>世系图</a
><a href="profile-album.html" data-feature-link="available" data-genealogy-context-link>相册</a ><a href="profile-album.html" data-feature-link="available" data-genealogy-context-link>相册</a
><a href="profile-content.html">内容发布</a ><a href="profile-content.html" data-genealogy-context-link>内容发布</a
><a href="profile-family-admin.html" data-genealogy-context-link>家族管理</a ><a href="profile-family-admin.html" data-family-home-management data-genealogy-context-link hidden>家族管理</a
><a href="profile-messages.html">消息中心</a> ><a href="profile-messages.html">消息中心</a>
</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"> <div class="module-grid">
<div class="module-card" data-family-home-overview>
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
正在加载家谱概览…
</div>
</div>
<div class="module-card"> <div class="module-card">
<span class="icon"></span> <span class="icon"></span>
<h3>家谱编号</h3> <h3>家谱成员</h3>
<p data-family-home-summary>家谱信息加载中...</p> <p><strong data-family-home-member-count>--</strong></p>
</div> </div>
<a class="module-card tilt-card" href="profile-tree.html" data-genealogy-context-link <div class="module-card">
><span class="icon"></span> <span class="icon"></span>
<h3>世系</h3> <h3>世系人物</h3>
<p>查看树谱模式和成员关系。</p></a <p><strong data-family-home-person-count>--</strong></p>
><a class="module-card tilt-card" href="profile-invite.html" </div>
><span class="icon"></span> <a class="module-card tilt-card" href="profile-invite.html" data-family-home-management data-genealogy-context-link hidden>
<span class="icon"></span>
<h3>邀请家人</h3> <h3>邀请家人</h3>
<p>生成邀请入口,邀请亲人协作</p></a <p>生成一次性邀请链接,指定人物并查看邀请状态</p>
> </a>
</div> </div>
<div class="api-status" data-family-home-status role="status" aria-live="polite">正在读取家谱概览</div>
</section> </section>
<section class="module-panel"> <section class="module-panel">
<h2>世系预览</h2> <h2>世系预览</h2>
<!-- 家谱主页只展示世系树预览,完整维护进入世系图页面 --> <!-- 家谱主页只展示世系树预览,完整维护进入世系图页面 -->
<div class="lineage-tree-wrap is-compact" data-lineage-home-tree> <div class="lineage-tree-wrap is-compact" data-lineage-home-tree>
<div class="api-empty">世系树加载中...</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
正在加载世系树…
</div>
</div> </div>
</section> </section>
<section class="module-panel"> <section class="module-panel">
<h2>常用入口</h2> <h2>家族文化</h2>
<p>围绕当前家谱集中维护谱文、影像与家族资料。</p>
<div class="module-grid"> <div class="module-grid">
<a class="module-card tilt-card" href="profile-article.html" data-feature-link="available" data-genealogy-context-link <a class="module-card tilt-card" href="profile-article.html" data-feature-link="available" data-genealogy-context-link
><span class="icon"></span> ><span class="icon"></span>
@@ -96,15 +108,74 @@
><span class="icon"></span> ><span class="icon"></span>
<h3>视频</h3> <h3>视频</h3>
<p>发布和编辑家族视频。</p></a <p>发布和编辑家族视频。</p></a
>
</div>
</section>
<section class="module-panel">
<h2>家族记录与协作</h2>
<p>按列表、详情和编辑页面管理动态、成长记录、亲友往来、备忘和活动。</p>
<div class="module-grid">
<a class="module-card tilt-card" href="profile-feed.html" data-feature-link="available" data-genealogy-context-link
><span class="icon"></span>
<h3>家族圈</h3>
<p>发布图文动态、查看评论和家族互动。</p></a
><a class="module-card tilt-card" href="profile-growth.html" data-feature-link="available" data-genealogy-context-link
><span class="icon"></span>
<h3>成长日志</h3>
<p>记录成员成长节点、日期和附件。</p></a
><a class="module-card tilt-card" href="profile-relative.html" data-feature-link="available" data-genealogy-context-link
><span class="icon"></span>
<h3>亲友往来(贺礼簿)</h3>
<p>记录亲友关系、往来事项和礼金。</p></a
><a class="module-card tilt-card" href="profile-memo.html" data-feature-link="available" data-genealogy-context-link
><span class="icon"></span>
<h3>备忘录(家族恩人)</h3>
<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>
<p>记录功德人事迹。</p></a <p>记录功德人事迹和相关金额</p></a
><a class="module-card tilt-card" href="profile-ceremony.html" data-feature-link="available" data-genealogy-context-link ><a class="module-card tilt-card" href="profile-ceremony.html" 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
><a class="module-card tilt-card" href="profile-gift.html" data-feature-link="available" data-genealogy-context-link
><span class="icon"></span>
<h3>贺礼邀请</h3>
<p>查看和响应当前家谱的活动邀请。</p></a
> >
<a class="module-card tilt-card" href="profile-documents.html" data-feature-link="available" data-genealogy-context-link>
<span class="icon"></span>
<h3>重要证件</h3>
<p>按人物维护证件档案、附件和密码保护。</p>
</a>
</div>
</section>
<section class="module-panel">
<h2>家谱维护</h2>
<p>世系、字辈和成员维护均与当前家谱绑定;管理权限始终以服务端结果为准。</p>
<div class="module-grid">
<a class="module-card tilt-card" href="profile-tree.html" data-feature-link="available" data-genealogy-context-link
><span class="icon"></span>
<h3>世系图</h3>
<p>查看世系结构并维护人物与关系。</p></a
><a class="module-card tilt-card" href="profile-generation.html" data-feature-link="available" data-genealogy-context-link
><span class="icon"></span>
<h3>字辈谱</h3>
<p>查询和维护家谱字辈。</p></a
><a class="module-card tilt-card" href="profile-family-admin.html" data-feature-link="available" data-family-home-management data-genealogy-context-link hidden
><span class="icon"></span>
<h3>家族管理</h3>
<p>管理成员、入谱审核和谱主转移。</p></a
><a class="module-card tilt-card" href="profile-services.html#membership" data-feature-link="available" data-genealogy-context-link
><span class="icon"></span>
<h3>会员与订单</h3>
<p>关联当前家谱创建会员订单,并查看已有订单。</p></a
><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>
<h3>家谱维护</h3>
<p>归档、恢复或在身份校验后永久删除家谱。</p>
</a>
</div> </div>
</section> </section>
</div> </div>
@@ -120,7 +191,9 @@
<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/media-display.js"></script>
<script src="public/js/lineage-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>
</body> </body>
</html> </html>
+23
View File
@@ -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="index.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>
+27 -4
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-feed-detail-page> <body class="page-profile-module" data-feed-detail-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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
@@ -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>
@@ -45,12 +45,34 @@
</aside> </aside>
<div class="module-main"> <div class="module-main">
<section class="module-panel" data-feed-detail> <section class="module-panel" data-feed-detail>
<div class="api-empty">动态详情加载中...</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
正在加载动态详情…
</div>
</section>
<section class="module-panel">
<h2>发表评论</h2>
<form class="editor-form" data-feed-comment-form novalidate>
<input name="parentCommentId" type="hidden" />
<p class="module-meta" data-feed-comment-form-target>发表公开评论</p>
<div class="editor-field">
<label for="feedCommentContent">评论内容 <span class="field-required" aria-hidden="true">*</span></label>
<textarea id="feedCommentContent" name="commentContent" rows="3" maxlength="1000" required disabled placeholder="写下想说的话"></textarea>
</div>
<div class="form-status" role="status" aria-live="polite" data-feed-comment-form-status>
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取动态评论权限…</div>
</div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit" disabled>发表评论</button>
<button class="btn ghost" type="button" data-feed-comment-cancel disabled>取消回复</button>
</div>
</form>
</section> </section>
<section class="module-panel"> <section class="module-panel">
<h2>评论与回复</h2> <h2>评论与回复</h2>
<div data-feed-detail-comments> <div data-feed-detail-comments>
<div class="api-empty">评论加载中...</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
正在加载评论…
</div>
</div> </div>
</section> </section>
</div> </div>
@@ -66,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>
+13 -9
View File
@@ -12,7 +12,7 @@
<body class="page-profile-module" data-feed-edit-page> <body class="page-profile-module" data-feed-edit-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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
@@ -21,17 +21,17 @@
</nav> </nav>
<div class="nav-actions"> <div class="nav-actions">
<a href="profile-feed.html" data-feed-list-link data-genealogy-context-link>返回动态</a <a href="profile-feed.html" data-feed-list-link data-genealogy-context-link>返回动态</a
><button class="btn primary magnetic" type="submit" form="feed-edit-form">发布</button> ><button class="btn primary magnetic" type="submit" form="feed-edit-form" data-feed-editor-action hidden disabled>发布</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">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>
@@ -48,10 +48,10 @@
<div class="module-main"> <div class="module-main">
<section class="module-panel"> <section class="module-panel">
<h2>动态内容</h2> <h2>动态内容</h2>
<form id="feed-edit-form" class="editor-form layui-form" data-feed-form> <form id="feed-edit-form" class="editor-form layui-form" data-feed-form novalidate>
<input name="feedType" type="hidden" value="text" /> <input name="feedType" type="hidden" value="text" />
<div class="editor-field"> <div class="editor-field">
<label for="feedContent">内容</label <label for="feedContent">内容 <span class="field-required" aria-hidden="true">*</span></label
><textarea ><textarea
id="feedContent" id="feedContent"
name="feedContent" name="feedContent"
@@ -64,8 +64,8 @@
<label for="feedMediaFile">动态图片</label> <label for="feedMediaFile">动态图片</label>
<input id="feedMedia" name="mediaOssIds" type="hidden" /> <input id="feedMedia" name="mediaOssIds" type="hidden" />
<label class="upload-control" for="feedMediaFile">选择动态图片</label> <label class="upload-control" for="feedMediaFile">选择动态图片</label>
<input id="feedMediaFile" class="upload-input" type="file" accept="image/*" multiple data-upload-multiple="true" data-upload-target="#feedMedia" data-upload-status="#feedMediaStatus" /> <input id="feedMediaFile" class="upload-input" type="file" accept="image/*" multiple data-upload-multiple="true" data-attachment-editor data-upload-target="#feedMedia" data-upload-status="#feedMediaStatus" />
<p id="feedMediaStatus" class="upload-status">未选择图片</p> <p id="feedMediaStatus" class="upload-status" role="status" aria-live="polite">未选择图片</p>
</div> </div>
<div class="editor-two" data-feed-advanced hidden> <div class="editor-two" data-feed-advanced hidden>
<div class="editor-field"> <div class="editor-field">
@@ -80,8 +80,9 @@
<input id="feedSortOrder" name="sortOrder" type="number" min="0" value="0" disabled /> <input id="feedSortOrder" name="sortOrder" type="number" min="0" value="0" disabled />
</div> </div>
</div> </div>
<div class="upload-status" role="status" aria-live="polite" data-feed-form-status></div>
<div class="bottom-actions"> <div class="bottom-actions">
<button class="btn primary magnetic" type="submit">发布</button <button class="btn primary magnetic" type="submit" data-feed-editor-action hidden disabled>发布</button
><a class="btn ghost magnetic" href="profile-feed.html" data-feed-list-link data-genealogy-context-link ><a class="btn ghost magnetic" href="profile-feed.html" data-feed-list-link data-genealogy-context-link
>取消</a >取消</a
> >
@@ -104,7 +105,10 @@
<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/md5.js"></script>
<script src="public/js/attachment-editor.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>
+9 -8
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-feed-page> <body class="page-profile-module" data-feed-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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
@@ -21,7 +21,7 @@
</nav> </nav>
<div class="nav-actions"> <div class="nav-actions">
<a href="profile-family-home.html">返回家谱主页</a <a href="profile-family-home.html">返回家谱主页</a
><a class="btn primary magnetic" href="profile-feed-edit.html" data-feed-create-link data-genealogy-context-link ><a class="btn primary magnetic" href="profile-feed-edit.html" data-feed-create-link data-genealogy-context-link hidden
>发布</a >发布</a
> >
</div> </div>
@@ -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>
@@ -53,21 +53,21 @@
<div class="form-like"> <div class="form-like">
<p> <p>
<span>内容</span><b>在发布页填写</b <span>内容</span><b>在发布页填写</b
><a class="link-btn" href="profile-feed-edit.html" data-feed-create-link data-genealogy-context-link>编辑</a> ><a class="link-btn" href="profile-feed-edit.html" data-feed-create-link data-genealogy-context-link hidden>编辑</a>
</p> </p>
<p> <p>
<span>图片</span><b>可直接选择并上传多张图片</b <span>图片</span><b>可直接选择并上传多张图片</b
><a class="link-btn" href="profile-feed-edit.html" data-feed-create-link data-genealogy-context-link>添加</a ><a class="link-btn" href="profile-feed-edit.html" data-feed-create-link data-genealogy-context-link hidden>添加</a
> >
</p> </p>
<p> <p>
<span>动态类型</span><b>图文动态</b <span>动态类型</span><b>图文动态</b
><a class="link-btn" href="profile-feed-edit.html" data-feed-create-link data-genealogy-context-link>编辑</a ><a class="link-btn" href="profile-feed-edit.html" data-feed-create-link data-genealogy-context-link hidden>编辑</a
> >
</p> </p>
<p> <p>
<span>发布状态</span><b>正常或停用</b <span>发布状态</span><b>正常或停用</b
><a class="link-btn" href="profile-feed-edit.html" data-feed-create-link data-genealogy-context-link>设置</a ><a class="link-btn" href="profile-feed-edit.html" data-feed-create-link data-genealogy-context-link hidden>设置</a
> >
</p> </p>
</div> </div>
@@ -76,7 +76,7 @@
<h2>最近动态</h2> <h2>最近动态</h2>
<!-- 动态列表由 /feeds/page 接口渲染,点赞和评论走对应子接口 --> <!-- 动态列表由 /feeds/page 接口渲染,点赞和评论走对应子接口 -->
<div class="module-list" data-feed-list> <div class="module-list" data-feed-list>
<div class="api-empty">动态加载中...</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在加载家族动态…</div>
</div> </div>
<div class="row-actions"> <div class="row-actions">
<button class="pill" type="button" data-feed-page-prev disabled>上一页</button> <button class="pill" type="button" data-feed-page-prev disabled>上一页</button>
@@ -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>
+17 -26
View File
@@ -8,10 +8,10 @@
<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-feedback-page="profile">
<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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
@@ -19,15 +19,14 @@
><a class="active" href="profile-feedback.html">意见反馈</a> ><a class="active" href="profile-feedback.html">意见反馈</a>
</nav> </nav>
<div class="nav-actions"> <div class="nav-actions">
<a href="profile-services.html">返回服务中心</a <a href="profile-services.html">返回服务中心</a>
><button class="btn primary magnetic" type="submit" form="profileFeedbackForm">提交</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">Feedback</div> <div class="module-kicker">意见反馈</div>
<div class="module-title-row"> <div class="module-title-row">
<div> <div>
<h1>意见反馈</h1> <h1>意见反馈</h1>
@@ -40,21 +39,19 @@
<div class="container module-layout"> <div class="container module-layout">
<aside class="module-nav"> <aside class="module-nav">
<a href="profile-services.html">帮助与服务</a <a href="profile-services.html">帮助与服务</a
><a href="profile-share.html">应用分享</a ><span class="module-nav-unavailable">应用分享<small>即将开放</small></span
><a class="active" href="profile-feedback.html">意见反馈</a ><a class="active" href="profile-feedback.html">意见反馈</a
><a href="profile-security.html">安全设置</a> ><a href="profile-security.html">安全设置</a>
</aside> </aside>
<div class="module-main"> <div class="module-main">
<section class="module-panel"> <section class="module-panel">
<h2>提交反馈</h2> <h2>提交反馈</h2>
<form class="editor-form" id="profileFeedbackForm" data-feedback-form> <form class="editor-form" id="profileFeedbackForm" data-feedback-form novalidate>
<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="suggestion">改进建议</option> <option value="">反馈类型加载中…</option>
<option value="bug">问题反馈</option>
<option value="data">资料异常</option>
</select> </select>
</div> </div>
<div class="editor-field"> <div class="editor-field">
@@ -63,28 +60,22 @@
</div> </div>
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label for="feedbackTitle">反馈标题</label> <label for="feedbackContent">反馈描述 <span class="field-required" aria-hidden="true">*</span></label>
<input id="feedbackTitle" name="feedbackTitle" type="text" placeholder="请填写反馈标题" /> <textarea id="feedbackContent" name="feedbackContent" required placeholder="请描述问题、页面位置和期望结果"></textarea>
</div>
<div class="editor-field">
<label for="feedbackContent">反馈描述</label>
<textarea id="feedbackContent" name="feedbackContent" placeholder="请描述问题、页面位置和期望结果"></textarea>
</div> </div>
<div class="form-status" role="status" aria-live="polite" data-feedback-status></div>
<div class="bottom-actions"> <div class="bottom-actions">
<button class="btn primary magnetic" type="submit">提交反馈</button> <button class="btn primary magnetic" type="submit" data-feedback-submit>提交反馈</button>
</div> </div>
</form> </form>
</section> </section>
<section class="module-panel"> <section class="module-panel">
<div class="module-title-row">
<h2>历史反馈</h2> <h2>历史反馈</h2>
<button class="btn ghost" type="button" data-feedback-refresh>刷新</button>
</div>
<div class="module-list" data-feedback-list> <div class="module-list" data-feedback-list>
<div class="module-row"> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取反馈记录…</div>
<div>
<h3>资料导入建议</h3>
<p>已受理 · 2026.05</p>
</div>
<span class="pill">查看</span>
</div>
</div> </div>
</section> </section>
</div> </div>
@@ -99,8 +90,8 @@
<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/profile-common.js"></script> <script src="public/js/profile-common.js"></script>
<script src="public/js/feedback-pages.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+37 -7
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-generation-page> <body class="page-profile-module" data-generation-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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
@@ -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>
@@ -55,9 +55,9 @@
<section class="module-panel generation-batch-panel" data-generation-management hidden> <section class="module-panel generation-batch-panel" data-generation-management hidden>
<h2>批量字辈</h2> <h2>批量字辈</h2>
<!-- 批量字辈表单:字段对应 Apifox 的 GenerationPoemBatchBody。 --> <!-- 批量字辈表单:字段对应 Apifox 的 GenerationPoemBatchBody。 -->
<form class="editor-form generation-batch-form" data-generation-batch-form> <form class="editor-form generation-batch-form" data-generation-batch-form novalidate>
<div class="editor-field"> <div class="editor-field">
<label for="generationBatchContent">字辈内容</label> <label for="generationBatchContent">字辈内容 <span class="field-required" aria-hidden="true">*</span></label>
<textarea <textarea
id="generationBatchContent" id="generationBatchContent"
name="poemText" name="poemText"
@@ -84,17 +84,47 @@
type="button" type="button"
data-generation-batch-action="save" data-generation-batch-action="save"
>批量保存</button> >批量保存</button>
<span class="form-status" data-generation-batch-status></span> <span class="form-status" role="status" aria-live="polite" data-generation-batch-status></span>
</div> </div>
</form> </form>
<div class="module-list generation-batch-preview" data-generation-batch-preview> <div class="module-list generation-batch-preview" data-generation-batch-preview>
<div class="api-empty">批量预览结果将在这里显示</div> <div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">批量预览结果将在这里显示</div>
</div> </div>
</section> </section>
<section id="generation-editor" class="module-panel" data-generation-editor hidden>
<h2 data-generation-editor-title>新增字辈</h2>
<form class="editor-form" data-generation-form novalidate>
<input name="poemId" type="hidden" />
<input name="status" type="hidden" />
<div class="editor-two">
<div class="editor-field">
<label for="generationNo">世代序号 <span class="field-required" aria-hidden="true">*</span></label>
<input id="generationNo" name="generationNo" type="number" min="1" max="2147483647" step="1" required />
</div>
<div class="editor-field">
<label for="generationText">字辈文字 <span class="field-required" aria-hidden="true">*</span></label>
<input id="generationText" name="generationText" type="text" maxlength="50" required />
</div>
</div>
<div class="editor-field">
<label for="generationDescription">说明</label>
<textarea id="generationDescription" name="description" rows="3" maxlength="500" placeholder="可选,最多 500 个字符"></textarea>
</div>
<div class="editor-field">
<label for="generationSortOrder">排序值</label>
<input id="generationSortOrder" name="sortOrder" type="number" min="-2147483648" max="2147483647" step="1" placeholder="可留空,越小越靠前" />
</div>
<div class="form-status" role="status" aria-live="polite" data-generation-form-status></div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit" data-generation-editor-submit>保存字辈</button>
<button class="btn ghost" type="button" data-generation-editor-cancel>取消</button>
</div>
</form>
</section>
<section class="module-panel"> <section class="module-panel">
<h2>字辈列表</h2> <h2>字辈列表</h2>
<div class="module-list" data-generation-list> <div class="module-list" data-generation-list>
<div class="api-empty">正在加载字辈记录…</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在加载字辈记录…</div>
</div> </div>
</section> </section>
</div> </div>
+19 -17
View File
@@ -3,7 +3,7 @@
<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/layui/css/layui.css" />
<link rel="stylesheet" href="public/css/profile-module.css" /> <link rel="stylesheet" href="public/css/profile-module.css" />
@@ -12,23 +12,23 @@
<body class="page-profile-module" data-ceremony-edit-page> <body class="page-profile-module" data-ceremony-edit-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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
<a href="profile-content.html">内容发布</a <a href="profile-content.html">内容发布</a
><a class="active" href="profile-gift.html">贺礼邀请</a> ><a class="active" href="profile-ceremony.html">祭祀活动</a>
</nav> </nav>
<div class="nav-actions"> <div class="nav-actions">
<a href="profile-gift.html">返回贺礼</a <a href="profile-ceremony.html">返回活动列表</a
><button class="btn primary magnetic" type="submit" form="ceremony-edit-form">确定创建</button> ><button class="btn primary magnetic" type="submit" form="ceremony-edit-form" data-ceremony-editor-action hidden>确定创建</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">Gift 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>
@@ -42,17 +42,17 @@
<aside class="module-nav"> <aside class="module-nav">
<a href="profile-content.html">内容发布</a <a href="profile-content.html">内容发布</a
><a href="profile-merit.html">功德录</a ><a href="profile-merit.html">功德录</a
><a class="active" href="profile-gift.html">贺礼邀请</a ><a class="active" href="profile-ceremony.html">祭祀活动</a
><a href="profile-feed.html">发布动态</a> ><a href="profile-feed.html">发布动态</a>
</aside> </aside>
<div class="module-main"> <div class="module-main">
<section class="module-panel"> <section class="module-panel">
<h2>活动信息</h2> <h2>活动信息</h2>
<div class="api-empty" data-ceremony-editor-placeholder>正在加载活动编辑信息…</div> <div class="api-state api-state--loading" data-api-state="loading" data-ceremony-editor-placeholder role="status" aria-live="polite" aria-busy="true">正在加载活动编辑信息…</div>
<form id="ceremony-edit-form" class="editor-form layui-form" data-ceremony-form data-ceremony-editor hidden> <form id="ceremony-edit-form" class="editor-form layui-form" data-ceremony-form data-ceremony-editor hidden novalidate>
<div class="editor-two"> <div class="editor-two">
<div class="editor-field"> <div class="editor-field">
<label for="giftType">活动类型</label <label for="giftType">活动类型 <span class="field-required" aria-hidden="true">*</span></label
><input id="giftType" name="ceremonyType" type="text" placeholder="请输入后端约定的活动类型" required /> ><input id="giftType" name="ceremonyType" type="text" placeholder="请输入后端约定的活动类型" required />
</div> </div>
<div class="editor-field"> <div class="editor-field">
@@ -66,7 +66,7 @@
</div> </div>
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label for="giftTitle">贺礼标题</label <label for="giftTitle">活动标题 <span class="field-required" aria-hidden="true">*</span></label
><input id="giftTitle" name="ceremonyTitle" type="text" placeholder="请输入标题" required /> ><input id="giftTitle" name="ceremonyTitle" type="text" placeholder="请输入标题" required />
</div> </div>
<div class="editor-field"> <div class="editor-field">
@@ -93,29 +93,29 @@
</div> </div>
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label for="giftContent">贺礼内容</label <label for="giftContent">活动说明</label
><textarea class="js-rich-editor" ><textarea class="js-rich-editor"
id="giftContent" id="giftContent"
name="ceremonyDesc" name="ceremonyDesc"
placeholder="请输入贺礼内容" placeholder="请输入活动说明"
></textarea> ></textarea>
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label for="giftCoverFile">封面文件</label> <label for="giftCoverFile">封面文件</label>
<input id="giftCoverOssId" name="coverOssId" type="hidden" /> <input id="giftCoverOssId" name="coverOssId" type="hidden" />
<label class="upload-control" for="giftCoverFile">选择封面图片</label> <label class="upload-control" for="giftCoverFile">选择封面图片</label>
<input id="giftCoverFile" class="upload-input" type="file" accept="image/*" data-upload-target="#giftCoverOssId" data-upload-status="#giftCoverStatus" /> <input id="giftCoverFile" class="upload-input" type="file" accept="image/*" data-attachment-editor data-upload-target="#giftCoverOssId" data-upload-status="#giftCoverStatus" />
<p id="giftCoverStatus" class="upload-status">未选择文件</p> <p id="giftCoverStatus" class="upload-status" role="status" aria-live="polite">未选择文件</p>
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label for="giftSortOrder">排序值</label <label for="giftSortOrder">排序值</label
><input id="giftSortOrder" name="sortOrder" type="number" step="1" placeholder="可选,数值越小越靠前" /> ><input id="giftSortOrder" name="sortOrder" type="number" step="1" placeholder="可选,数值越小越靠前" />
</div> </div>
<input name="status" type="hidden" value="0" /> <input name="status" type="hidden" value="0" />
<p class="upload-status" data-ceremony-form-status aria-live="polite"></p> <div class="upload-status" data-ceremony-form-status role="status" aria-live="polite"></div>
<div class="bottom-actions"> <div class="bottom-actions">
<button class="btn primary magnetic" type="submit">确定创建</button <button class="btn primary magnetic" type="submit">确定创建</button
><a class="btn ghost magnetic" href="profile-gift.html" ><a class="btn ghost magnetic" href="profile-ceremony.html"
>取消</a >取消</a
> >
</div> </div>
@@ -138,7 +138,9 @@
<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/md5.js"></script> <script src="public/js/md5.js"></script>
<script src="public/js/attachment-editor.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>
+11 -6
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-ceremony-page> <body class="page-profile-module" data-ceremony-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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
@@ -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>
@@ -49,19 +49,24 @@
<section class="module-panel"> <section class="module-panel">
<h2>我的邀请</h2> <h2>我的邀请</h2>
<div class="module-list" data-my-invitation-list aria-live="polite"> <div class="module-list" data-my-invitation-list aria-live="polite">
<div class="api-empty">正在加载我的邀请…</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
正在加载我的邀请…
</div> </div>
</div>
<div class="form-status" data-my-invitation-status role="status" aria-live="polite"></div>
</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">
<h2>邀请详情</h2> <h2>邀请详情</h2>
<div data-my-invitation-detail aria-live="polite"> <div data-my-invitation-detail aria-live="polite">
<div class="api-empty">请选择一条邀请查看详情</div> <div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">
请选择一条邀请查看详情
</div>
</div> </div>
</section> </section>
</div> </div>
+12 -8
View File
@@ -12,7 +12,7 @@
<body class="page-profile-module" data-growth-edit-page> <body class="page-profile-module" data-growth-edit-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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
@@ -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>
@@ -48,7 +48,7 @@
<div class="module-main"> <div class="module-main">
<section class="module-panel"> <section class="module-panel">
<h2>记录内容</h2> <h2>记录内容</h2>
<form id="growth-edit-form" class="editor-form" data-growth-form data-growth-editor hidden> <form id="growth-edit-form" class="editor-form" data-growth-form data-growth-editor hidden novalidate>
<div class="editor-two"> <div class="editor-two">
<div class="editor-field"> <div class="editor-field">
<label for="growthPersonId">关联世系人物</label <label for="growthPersonId">关联世系人物</label
@@ -72,7 +72,7 @@
</div> </div>
<div class="editor-two"> <div class="editor-two">
<div class="editor-field"> <div class="editor-field">
<label for="growthTitle">标题</label> <label for="growthTitle">标题<span class="field-required" aria-hidden="true"> *</span></label>
<input id="growthTitle" name="recordTitle" type="text" placeholder="请输入成长记录标题" required /> <input id="growthTitle" name="recordTitle" type="text" placeholder="请输入成长记录标题" required />
</div> </div>
<div class="editor-field"> <div class="editor-field">
@@ -98,8 +98,8 @@
<label for="growthMediaFiles">附件文件</label> <label for="growthMediaFiles">附件文件</label>
<input id="growthMediaOssIds" name="mediaOssIds" type="hidden" /> <input id="growthMediaOssIds" name="mediaOssIds" type="hidden" />
<label class="upload-control" for="growthMediaFiles">选择附件</label> <label class="upload-control" for="growthMediaFiles">选择附件</label>
<input id="growthMediaFiles" class="upload-input" type="file" multiple data-upload-multiple="true" data-upload-target="#growthMediaOssIds" data-upload-status="#growthMediaStatus" data-growth-media-input /> <input id="growthMediaFiles" class="upload-input" type="file" multiple data-upload-multiple="true" data-attachment-editor data-upload-target="#growthMediaOssIds" data-upload-status="#growthMediaStatus" data-growth-media-input />
<p id="growthMediaStatus" class="upload-status" data-growth-media-status>未选择文件</p> <p id="growthMediaStatus" class="upload-status" data-growth-media-status role="status" aria-live="polite">未选择文件</p>
<button class="pill" type="button" data-growth-clear-media>清除附件</button> <button class="pill" type="button" data-growth-clear-media>清除附件</button>
</div> </div>
<div class="editor-field"> <div class="editor-field">
@@ -108,15 +108,17 @@
</div> </div>
</div> </div>
<input id="growthStatus" name="status" type="hidden" value="0" /> <input id="growthStatus" name="status" type="hidden" value="0" />
<div class="form-status" data-growth-form-status role="status" aria-live="polite"></div>
<div class="bottom-actions"> <div class="bottom-actions">
<button class="btn primary magnetic" type="submit">保存</button <button class="btn primary magnetic" type="submit">保存</button
><a class="btn ghost magnetic" href="profile-growth.html" data-genealogy-context-link ><a class="btn ghost magnetic" href="profile-growth.html" data-genealogy-context-link
>取消</a >取消</a
> >
<span class="form-status" data-growth-form-status></span>
</div> </div>
</form> </form>
<div class="api-empty" data-growth-editor-placeholder>正在加载成长记录编辑信息…</div> <div data-growth-editor-placeholder>
<div class="api-state api-state--loading" role="status" aria-live="polite" aria-busy="true">正在加载成长记录编辑信息…</div>
</div>
</section> </section>
</div> </div>
</div> </div>
@@ -135,7 +137,9 @@
<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/md5.js"></script> <script src="public/js/md5.js"></script>
<script src="public/js/attachment-editor.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>
+7 -6
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-growth-page> <body class="page-profile-module" data-growth-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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
@@ -20,7 +20,7 @@
</nav> </nav>
<div class="nav-actions"> <div class="nav-actions">
<a href="profile-content.html">返回发布中心</a <a href="profile-content.html">返回发布中心</a
><a class="btn primary magnetic" href="profile-growth-edit.html" data-genealogy-context-link ><a class="btn primary magnetic" href="profile-growth-edit.html" data-genealogy-context-link data-growth-create-link hidden
>新建记录</a >新建记录</a
> >
</div> </div>
@@ -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>
@@ -50,13 +50,13 @@
<section class="module-panel"> <section class="module-panel">
<h2>记录列表</h2> <h2>记录列表</h2>
<div class="module-list" data-growth-list aria-live="polite"> <div class="module-list" data-growth-list aria-live="polite">
<div class="api-empty">正在加载成长记录…</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在加载成长记录…</div>
</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-empty">请选择一条成长记录查看详情</div> <div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一条成长记录查看详情</div>
</div> </div>
</section> </section>
</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>
+18 -17
View File
@@ -8,10 +8,10 @@
<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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
@@ -26,13 +26,13 @@
<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>
<p>生成家谱邀请链接或邀请码,引导亲人加入家谱并激活账号。</p> <p>生成家谱邀请链接或邀请码,引导亲人加入家谱并激活账号。</p>
</div> </div>
<a class="btn primary magnetic" href="join-genealogy.html">打开加入页</a> <a class="btn ghost magnetic" href="profile-family-admin.html" data-feature-link="available" data-genealogy-context-link>返回家族管理</a>
</div> </div>
</div> </div>
</section> </section>
@@ -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>当前邀请码:<b data-invite-code>加载中...</b></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 data-invite-link>邀请链接加载中...</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>
+9 -15
View File
@@ -8,10 +8,10 @@
<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-my-join-applies-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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
@@ -26,13 +26,11 @@
<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>
<p> <p>查看加入申请进度;待审核申请可以撤销后重新提交。</p>
输入亲人分享的邀请码,提交后进入个人中心消息流程等待管理员审核。
</p>
</div> </div>
<a <a
class="btn primary magnetic" class="btn primary magnetic"
@@ -51,16 +49,12 @@
</aside> </aside>
<div class="module-main"> <div class="module-main">
<section class="module-panel"> <section class="module-panel">
<div class="module-title-row">
<h2>我的加入申请</h2> <h2>我的加入申请</h2>
<div class="module-list" data-join-apply-list="mine"> <button class="btn ghost" type="button" data-join-apply-refresh>刷新</button>
<a class="module-row" href="join-genealogy.html">
<div>
<h3>提交新的加入申请</h3>
<p>输入家谱 ID 和关系说明,等待管理员审核。</p>
</div>
<span class="pill">去申请</span>
</a>
</div> </div>
<div class="form-note" role="status" aria-live="polite" data-my-join-applies-status></div>
<div class="module-list" data-join-apply-list="mine"></div>
</section> </section>
</div> </div>
</div> </div>
@@ -75,7 +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/pending-pages.js"></script> <script src="public/js/join-pages.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+29 -47
View File
@@ -8,10 +8,10 @@
<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-join-review-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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
@@ -20,27 +20,18 @@
><a class="active" href="profile-join-review.html">入谱审核</a> ><a class="active" href="profile-join-review.html">入谱审核</a>
</nav> </nav>
<div class="nav-actions"> <div class="nav-actions">
<a href="profile-messages.html">返回消息</a <a href="profile-family-admin.html" data-genealogy-context-link>返回家族管理</a>
><a
class="btn primary magnetic"
href="#"
data-layer-confirm="确认批量处理当前审核申请?"
data-confirm-title="全部处理"
>全部处理</a
>
</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">Review</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>
个人中心专用审核页,处理加入家谱、绑定账号和成员资料补全申请。
</p>
</div> </div>
</div> </div>
</div> </div>
@@ -49,47 +40,38 @@
<div class="container module-layout"> <div class="container module-layout">
<aside class="module-nav"> <aside class="module-nav">
<a href="profile-messages.html">消息中心</a <a href="profile-messages.html">消息中心</a
><a class="active" href="profile-join-review.html">入谱审核</a ><a class="active" href="profile-join-review.html" data-genealogy-context-link>入谱审核</a
><a href="profile-invite.html">邀请家人</a ><span class="module-nav-unavailable">邀请家人<small>即将开放</small></span
><a href="profile-admin-permissions.html">管理员权限</a> ><a href="profile-admin-permissions.html" data-genealogy-context-link>管理员权限</a>
</aside> </aside>
<div class="module-main"> <div class="module-main">
<section class="module-panel"> <section class="module-panel">
<div class="module-title-row">
<h2>待审核申请</h2> <h2>待审核申请</h2>
<button class="btn ghost" type="button" data-join-review-refresh>刷新</button>
</div>
<div class="form-note" role="status" aria-live="polite" data-join-review-status></div>
<div class="module-list" data-join-apply-list="pending"> <div class="module-list" data-join-apply-list="pending">
<a class="module-row" href="profile-join-review.html" <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
><div> 正在读取待审核申请…
<h3>某某人申请加入四川武胜汤氏族</h3>
<p>提交时间:2026.05.26 · 关系说明待确认</p>
</div> </div>
<span class="pill">审核</span></a
><a class="module-row" href="profile-join-review.html"
><div>
<h3>张三申请绑定成员账号</h3>
<p>需要核对手机号和成员资料</p>
</div>
<span class="pill">处理</span></a
>
</div> </div>
</section> </section>
<section class="module-panel"> <section id="join-review-form" class="module-panel" data-join-review-editor hidden>
<h2>审核操作</h2> <h2 data-join-review-form-title>审核加入申请</h2>
<div class="bottom-actions"> <form class="editor-form" data-join-review-form novalidate>
<a <input name="applyId" type="hidden" />
class="btn primary magnetic" <input name="status" type="hidden" />
href="#" <div class="editor-field">
data-layer-confirm="同意当前入谱申请?" <label for="joinAuditRemark">审核说明</label>
data-confirm-title="审核通过" <textarea id="joinAuditRemark" name="auditRemark" rows="4" maxlength="500" disabled placeholder="可选;拒绝时可说明原因,最多 500 个字符"></textarea>
>同意</a
><a
class="btn ghost magnetic"
href="#"
data-layer-prompt="请输入拒绝原因"
data-prompt-title="拒绝申请"
data-prompt-type="textarea"
>拒绝</a
>
</div> </div>
<div class="form-status" role="status" aria-live="polite" data-join-review-form-status></div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit" data-join-review-submit disabled>提交审核</button>
<button class="btn ghost" type="button" data-join-review-cancel disabled>取消</button>
</div>
</form>
</section> </section>
</div> </div>
</div> </div>
@@ -104,7 +86,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/join-review-pages.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+10 -7
View File
@@ -11,14 +11,14 @@
<body class="page-profile-module" data-memo-edit-page> <body class="page-profile-module" data-memo-edit-page>
<header class="site-header"> <header class="site-header">
<div class="container nav"> <div class="container nav">
<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> <a class="brand magnetic" href="index.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-content.html">内容发布</a><a class="active" href="profile-memo.html" data-genealogy-context-link>备忘录</a></nav> <nav class="nav-links"><a href="profile-content.html">内容发布</a><a class="active" href="profile-memo.html" data-genealogy-context-link>备忘录</a></nav>
<div class="nav-actions"><a href="profile-memo.html" data-genealogy-context-link>返回记录</a><button class="btn primary magnetic" type="submit" form="memo-edit-form" data-memo-editor-action hidden>保存</button></div> <div class="nav-actions"><a href="profile-memo.html" data-genealogy-context-link>返回记录</a><button class="btn primary magnetic" type="submit" form="memo-edit-form" data-memo-editor-action hidden>保存</button></div>
</div> </div>
</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">
@@ -26,21 +26,22 @@
<div class="module-main"> <div class="module-main">
<section class="module-panel"> <section class="module-panel">
<h2>备忘内容</h2> <h2>备忘内容</h2>
<form id="memo-edit-form" class="editor-form" data-memo-form data-memo-editor hidden> <form id="memo-edit-form" class="editor-form" data-memo-form data-memo-editor hidden novalidate>
<div class="editor-field"><label for="memoTitle">备忘标题</label><input id="memoTitle" name="memoTitle" type="text" required /></div> <div class="editor-field"><label for="memoTitle">备忘标题<span class="field-required" aria-hidden="true"> *</span></label><input id="memoTitle" name="memoTitle" type="text" required /></div>
<div class="editor-field"><label for="memoContent">备忘内容</label><textarea id="memoContent" name="memoContent" placeholder="可选"></textarea></div> <div class="editor-field"><label for="memoContent">备忘内容</label><textarea id="memoContent" name="memoContent" placeholder="可选"></textarea></div>
<div class="editor-two"> <div class="editor-two">
<div class="editor-field"><label for="memoRemindTime">提醒时间</label><input id="memoRemindTime" name="remindTime" type="datetime-local" /></div> <div class="editor-field"><label for="memoRemindTime">提醒时间</label><input id="memoRemindTime" name="remindTime" type="datetime-local" /></div>
<div class="editor-field"><label for="memoCompleted">完成状态</label><select id="memoCompleted" name="completed"><option value="0">未完成</option><option value="1">已完成</option></select></div> <div class="editor-field"><label for="memoCompleted">完成状态</label><select id="memoCompleted" name="completed"><option value="0">未完成</option><option value="1">已完成</option></select></div>
</div> </div>
<div class="editor-two"> <div class="editor-two">
<div class="editor-field"><label for="memoMediaFiles">附件文件</label><input id="memoMediaOssIds" name="mediaOssIds" type="hidden" /><label class="upload-control" for="memoMediaFiles">选择附件</label><input id="memoMediaFiles" class="upload-input" type="file" multiple data-upload-multiple="true" data-upload-target="#memoMediaOssIds" data-upload-status="#memoMediaStatus" data-memo-media-input /><p id="memoMediaStatus" class="upload-status" data-memo-media-status>未选择文件</p><button class="pill" type="button" data-memo-clear-media>清除附件</button></div> <div class="editor-field"><label for="memoMediaFiles">附件文件</label><input id="memoMediaOssIds" name="mediaOssIds" type="hidden" /><label class="upload-control" for="memoMediaFiles">选择附件</label><input id="memoMediaFiles" class="upload-input" type="file" multiple data-upload-multiple="true" data-attachment-editor data-upload-target="#memoMediaOssIds" data-upload-status="#memoMediaStatus" data-memo-media-input /><p id="memoMediaStatus" class="upload-status" data-memo-media-status role="status" aria-live="polite">未选择文件</p><button class="pill" type="button" data-memo-clear-media>清除附件</button></div>
<div class="editor-field"><label for="memoSortOrder">排序值</label><input id="memoSortOrder" name="sortOrder" type="number" step="1" placeholder="可选;安全整数" /></div> <div class="editor-field"><label for="memoSortOrder">排序值</label><input id="memoSortOrder" name="sortOrder" type="number" step="1" placeholder="可选;安全整数" /></div>
</div> </div>
<input name="status" type="hidden" value="0" /> <input name="status" type="hidden" value="0" />
<div class="bottom-actions"><button class="btn primary magnetic" type="submit">保存</button><a class="btn ghost magnetic" href="profile-memo.html" data-genealogy-context-link>取消</a><span class="form-status" data-memo-form-status></span></div> <div class="form-status" data-memo-form-status role="status" aria-live="polite"></div>
<div class="bottom-actions"><button class="btn primary magnetic" type="submit">保存</button><a class="btn ghost magnetic" href="profile-memo.html" data-genealogy-context-link>取消</a></div>
</form> </form>
<div class="api-empty" data-memo-editor-placeholder>正在加载备忘录编辑信息…</div> <div data-memo-editor-placeholder><div class="api-state api-state--loading" role="status" aria-live="polite" aria-busy="true">正在加载备忘录编辑信息…</div></div>
</section> </section>
</div> </div>
</div> </div>
@@ -56,7 +57,9 @@
<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/md5.js"></script> <script src="public/js/md5.js"></script>
<script src="public/js/attachment-editor.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>
+7 -6
View File
@@ -11,15 +11,15 @@
<body class="page-profile-module" data-memo-page> <body class="page-profile-module" data-memo-page>
<header class="site-header"> <header class="site-header">
<div class="container nav"> <div class="container nav">
<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> <a class="brand magnetic" href="index.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-content.html">内容发布</a><a class="active" href="profile-memo.html" data-genealogy-context-link>备忘录</a></nav> <nav class="nav-links"><a href="profile-content.html">内容发布</a><a class="active" href="profile-memo.html" data-genealogy-context-link>备忘录</a></nav>
<div class="nav-actions"><a href="profile-content.html">返回发布中心</a><a class="btn primary magnetic" href="profile-memo-edit.html" data-genealogy-context-link>新建备忘</a></div> <div class="nav-actions"><a href="profile-content.html">返回发布中心</a><a class="btn primary magnetic" href="profile-memo-edit.html" data-genealogy-context-link data-memo-create-link hidden>新建备忘</a></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">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>
@@ -30,13 +30,13 @@
<section class="module-panel"> <section class="module-panel">
<h2>备忘列表</h2> <h2>备忘列表</h2>
<div class="module-list" data-memo-list aria-live="polite"> <div class="module-list" data-memo-list aria-live="polite">
<div class="api-empty">正在加载备忘录…</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在加载备忘录…</div>
</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-empty">请选择一条备忘录查看详情</div> <div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一条备忘录查看详情</div>
</div> </div>
</section> </section>
</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>
+11 -8
View File
@@ -12,7 +12,7 @@
<body class="page-profile-module" data-merit-edit-page> <body class="page-profile-module" data-merit-edit-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="index.html">
<img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /> <img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" />
<span class="brand-text">个人中心</span> <span class="brand-text">个人中心</span>
</a> </a>
@@ -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>
@@ -49,10 +49,10 @@
<div class="module-main"> <div class="module-main">
<section class="module-panel"> <section class="module-panel">
<h2>功德资料</h2> <h2>功德资料</h2>
<form id="merit-edit-form" class="editor-form" data-merit-form data-merit-editor hidden> <form id="merit-edit-form" class="editor-form" data-merit-form data-merit-editor hidden novalidate>
<div class="editor-two"> <div class="editor-two">
<div class="editor-field"> <div class="editor-field">
<label for="meritName">功德人姓名</label> <label for="meritName">功德人姓名<span class="field-required" aria-hidden="true"> *</span></label>
<input id="meritName" name="donorName" type="text" placeholder="请输入功德人姓名" required /> <input id="meritName" name="donorName" type="text" placeholder="请输入功德人姓名" required />
</div> </div>
<div class="editor-field"> <div class="editor-field">
@@ -61,7 +61,7 @@
</div> </div>
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label for="meritTitle">功德标题</label> <label for="meritTitle">功德标题<span class="field-required" aria-hidden="true"> *</span></label>
<input id="meritTitle" name="meritTitle" type="text" placeholder="请输入功德标题" required /> <input id="meritTitle" name="meritTitle" type="text" placeholder="请输入功德标题" required />
</div> </div>
<div class="editor-field"> <div class="editor-field">
@@ -88,13 +88,15 @@
<input id="meritSortOrder" name="sortOrder" type="number" step="1" placeholder="可选;安全整数" /> <input id="meritSortOrder" name="sortOrder" type="number" step="1" placeholder="可选;安全整数" />
</div> </div>
<input name="status" type="hidden" value="0" /> <input name="status" type="hidden" value="0" />
<div class="form-status" data-merit-form-status role="status" aria-live="polite"></div>
<div class="bottom-actions"> <div class="bottom-actions">
<button class="btn primary magnetic" type="submit">保存</button> <button class="btn primary magnetic" type="submit">保存</button>
<a class="btn ghost magnetic" href="profile-merit.html" data-genealogy-context-link>取消</a> <a class="btn ghost magnetic" href="profile-merit.html" data-genealogy-context-link>取消</a>
<span class="form-status" data-merit-form-status></span>
</div> </div>
</form> </form>
<div class="api-empty" data-merit-editor-placeholder>正在加载功德记录编辑信息…</div> <div data-merit-editor-placeholder>
<div class="api-state api-state--loading" role="status" aria-live="polite" aria-busy="true">正在加载功德记录编辑信息…</div>
</div>
</section> </section>
</div> </div>
</div> </div>
@@ -111,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>
+7 -6
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-merit-page> <body class="page-profile-module" data-merit-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="index.html">
<img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /> <img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" />
<span class="brand-text">个人中心</span> <span class="brand-text">个人中心</span>
</a> </a>
@@ -21,14 +21,14 @@
</nav> </nav>
<div class="nav-actions"> <div class="nav-actions">
<a href="profile-content.html">返回发布中心</a> <a href="profile-content.html">返回发布中心</a>
<a class="btn primary magnetic" href="profile-merit-edit.html" data-genealogy-context-link>新增功德记录</a> <a class="btn primary magnetic" href="profile-merit-edit.html" data-genealogy-context-link data-merit-create-link hidden>新增功德记录</a>
</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">Merit</div> <div class="module-kicker">功德录</div>
<div class="module-title-row"> <div class="module-title-row">
<div> <div>
<h1>功德录</h1> <h1>功德录</h1>
@@ -50,13 +50,13 @@
<section class="module-panel"> <section class="module-panel">
<h2>功德记录</h2> <h2>功德记录</h2>
<div class="module-list" data-merit-list aria-live="polite"> <div class="module-list" data-merit-list aria-live="polite">
<div class="api-empty">正在加载功德记录…</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在加载功德记录…</div>
</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-empty">请选择一条功德记录查看详情</div> <div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一条功德记录查看详情</div>
</div> </div>
</section> </section>
</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>
+8 -8
View File
@@ -11,12 +11,12 @@
<body class="page-profile-module"> <body class="page-profile-module">
<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="index.html">
<img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /> <img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" />
</a> </a>
<nav class="nav-links"> <nav class="nav-links">
<a href="profile.html">首页</a> <a href="profile.html">个人中心</a>
<a href="profile-families.html">数字家谱</a> <a href="profile-families.html">我的家谱</a>
<a href="plaza.html">家谱广场</a> <a href="plaza.html">家谱广场</a>
<a href="profile-content.html">家族文化</a> <a href="profile-content.html">家族文化</a>
<a href="surname.html">姓氏百科</a> <a href="surname.html">姓氏百科</a>
@@ -25,7 +25,7 @@
<a href="about.html">关于我们</a> <a href="about.html">关于我们</a>
</nav> </nav>
<div class="nav-actions"> <div class="nav-actions">
<a class="active" href="profile.html">个人中心</a> <a class="active" href="profile-messages.html">消息中心</a>
<a class="btn primary magnetic" href="profile-create-family.html">创建家谱</a> <a class="btn primary magnetic" href="profile-create-family.html">创建家谱</a>
</div> </div>
</div> </div>
@@ -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>
@@ -69,15 +69,15 @@
</select> </select>
</label> </label>
</div> </div>
<p class="api-empty" data-notification-status>正在加载消息…</p> <div class="form-status" data-notification-status role="status" aria-live="polite"></div>
<div class="module-list" data-notification-list aria-live="polite"> <div class="module-list" data-notification-list aria-live="polite">
<div class="api-empty">正在加载消息…</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在加载消息…</div>
</div> </div>
</section> </section>
<section class="module-panel"> <section class="module-panel">
<h2>通知详情</h2> <h2>通知详情</h2>
<div data-notification-detail aria-live="polite"> <div data-notification-detail aria-live="polite">
<div class="api-empty">请选择一条通知查看详情</div> <div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一条通知查看详情</div>
</div> </div>
</section> </section>
<section class="module-panel"> <section class="module-panel">
+12 -8
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-relative-edit-page> <body class="page-profile-module" data-relative-edit-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="index.html">
<img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /> <img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" />
<span class="brand-text">个人中心</span> <span class="brand-text">个人中心</span>
</a> </a>
@@ -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>
@@ -48,10 +48,10 @@
<div class="module-main"> <div class="module-main">
<section class="module-panel"> <section class="module-panel">
<h2>记录内容</h2> <h2>记录内容</h2>
<form id="relative-edit-form" class="editor-form" data-relative-form data-relative-editor hidden> <form id="relative-edit-form" class="editor-form" data-relative-form data-relative-editor hidden novalidate>
<div class="editor-two"> <div class="editor-two">
<div class="editor-field"> <div class="editor-field">
<label for="relativeName">亲友姓名</label> <label for="relativeName">亲友姓名<span class="field-required" aria-hidden="true"> *</span></label>
<input id="relativeName" name="relativeName" type="text" placeholder="请输入亲友姓名" required /> <input id="relativeName" name="relativeName" type="text" placeholder="请输入亲友姓名" required />
</div> </div>
<div class="editor-field"> <div class="editor-field">
@@ -93,21 +93,23 @@
type="file" type="file"
multiple multiple
data-upload-multiple="true" data-upload-multiple="true"
data-upload-target="#relativeMediaOssIds" data-attachment-editor data-upload-target="#relativeMediaOssIds"
data-upload-status="#relativeMediaStatus" data-upload-status="#relativeMediaStatus"
data-relative-media-input data-relative-media-input
/> />
<p id="relativeMediaStatus" class="upload-status" data-relative-media-status>未选择文件</p> <p id="relativeMediaStatus" class="upload-status" data-relative-media-status role="status" aria-live="polite">未选择文件</p>
<button class="pill" type="button" data-relative-clear-media>清除附件</button> <button class="pill" type="button" data-relative-clear-media>清除附件</button>
</div> </div>
<input name="status" type="hidden" value="0" /> <input name="status" type="hidden" value="0" />
<div class="form-status" data-relative-form-status role="status" aria-live="polite"></div>
<div class="bottom-actions"> <div class="bottom-actions">
<button class="btn primary magnetic" type="submit">保存</button> <button class="btn primary magnetic" type="submit">保存</button>
<a class="btn ghost magnetic" href="profile-relative.html" data-genealogy-context-link>取消</a> <a class="btn ghost magnetic" href="profile-relative.html" data-genealogy-context-link>取消</a>
<span class="form-status" data-relative-form-status></span>
</div> </div>
</form> </form>
<div class="api-empty" data-relative-editor-placeholder>正在加载亲友记录编辑信息…</div> <div data-relative-editor-placeholder>
<div class="api-state api-state--loading" role="status" aria-live="polite" aria-busy="true">正在加载亲友记录编辑信息…</div>
</div>
</section> </section>
</div> </div>
</div> </div>
@@ -123,7 +125,9 @@
<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/md5.js"></script> <script src="public/js/md5.js"></script>
<script src="public/js/attachment-editor.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>
+7 -6
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-relative-page> <body class="page-profile-module" data-relative-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="index.html">
<img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /> <img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" />
<span class="brand-text">个人中心</span> <span class="brand-text">个人中心</span>
</a> </a>
@@ -21,14 +21,14 @@
</nav> </nav>
<div class="nav-actions"> <div class="nav-actions">
<a href="profile-content.html">返回发布中心</a> <a href="profile-content.html">返回发布中心</a>
<a class="btn primary magnetic" href="profile-relative-edit.html" data-genealogy-context-link>新建记录</a> <a class="btn primary magnetic" href="profile-relative-edit.html" data-genealogy-context-link data-relative-create-link hidden>新建记录</a>
</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">Relative Records</div> <div class="module-kicker">亲友往来</div>
<div class="module-title-row"> <div class="module-title-row">
<div> <div>
<h1>亲友往来</h1> <h1>亲友往来</h1>
@@ -49,13 +49,13 @@
<section class="module-panel"> <section class="module-panel">
<h2>记录列表</h2> <h2>记录列表</h2>
<div class="module-list" data-relative-list aria-live="polite"> <div class="module-list" data-relative-list aria-live="polite">
<div class="api-empty">正在加载亲友记录…</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在加载亲友记录…</div>
</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-empty">请选择一条亲友记录查看详情</div> <div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一条亲友记录查看详情</div>
</div> </div>
</section> </section>
</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>
+22 -23
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-security-page> <body class="page-profile-module" data-security-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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
@@ -20,18 +20,18 @@
</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>
<p>维护登录密码、手机号、微信快捷登录和账号安全提醒</p> <p>维护登录密码、手机号与账号注销安全操作</p>
</div> </div>
</div> </div>
</div> </div>
@@ -48,34 +48,34 @@
<section class="module-panel"> <section class="module-panel">
<h2>账号安全</h2> <h2>账号安全</h2>
<!-- 修改密码表单:字段对应 PasswordChangeBody。 --> <!-- 修改密码表单:字段对应 PasswordChangeBody。 -->
<form class="editor-form security-form" data-security-form="password"> <form class="editor-form security-form" data-security-form="password" novalidate>
<div class="editor-two"> <div class="editor-two">
<div class="editor-field"> <div class="editor-field">
<label for="oldPassword">当前密码</label> <label for="oldPassword">当前密码 <span class="field-required" aria-hidden="true">*</span></label>
<input id="oldPassword" name="oldPassword" type="password" autocomplete="current-password" required /> <input id="oldPassword" name="oldPassword" type="password" autocomplete="current-password" required />
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label for="newPassword">新密码</label> <label for="newPassword">新密码 <span class="field-required" aria-hidden="true">*</span></label>
<input id="newPassword" name="newPassword" type="password" autocomplete="new-password" required /> <input id="newPassword" name="newPassword" type="password" autocomplete="new-password" required />
</div> </div>
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label for="confirmPassword">确认新密码</label> <label for="confirmPassword">确认新密码 <span class="field-required" aria-hidden="true">*</span></label>
<input id="confirmPassword" name="confirmPassword" type="password" autocomplete="new-password" required /> <input id="confirmPassword" name="confirmPassword" type="password" autocomplete="new-password" required />
</div> </div>
<div class="bottom-actions"> <div class="bottom-actions">
<button class="btn primary magnetic" type="submit">修改密码</button> <button class="btn primary magnetic" type="submit" data-security-submit>修改密码</button>
<span class="api-status" data-security-status>等待提交</span> <div class="form-status" data-security-status role="status" aria-live="polite"></div>
</div> </div>
</form> </form>
</section> </section>
<section class="module-panel"> <section class="module-panel">
<h2>换绑手机号</h2> <h2>换绑手机号</h2>
<!-- 换绑手机号表单:字段对应 PhoneChangeBody。 --> <!-- 换绑手机号表单:字段对应 PhoneChangeBody。 -->
<form class="editor-form security-form" data-security-form="phone"> <form class="editor-form security-form" data-security-form="phone" novalidate>
<div class="editor-two"> <div class="editor-two">
<div class="editor-field"> <div class="editor-field">
<label for="newPhone">新手机号</label> <label for="newPhone">新手机号 <span class="field-required" aria-hidden="true">*</span></label>
<input <input
id="newPhone" id="newPhone"
name="phone" name="phone"
@@ -87,7 +87,7 @@
/> />
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label for="phoneSmsCode">短信验证码</label> <label for="phoneSmsCode">短信验证码 <span class="field-required" aria-hidden="true">*</span></label>
<input <input
id="phoneSmsCode" id="phoneSmsCode"
name="smsCode" name="smsCode"
@@ -103,17 +103,17 @@
</div> </div>
<input id="phoneValidToken" name="validToken" type="hidden" /> <input id="phoneValidToken" name="validToken" type="hidden" />
<div class="bottom-actions"> <div class="bottom-actions">
<button class="btn primary magnetic" type="submit">确认换绑</button> <button class="btn primary magnetic" type="submit" data-security-submit>确认换绑</button>
<span class="api-status" data-security-status>等待验证码</span> <div class="form-status" data-security-status role="status" aria-live="polite"></div>
</div> </div>
</form> </form>
</section> </section>
<section class="module-panel security-danger"> <section class="module-panel security-danger">
<h2>注销账号</h2> <h2>注销账号</h2>
<p>验证已绑定手机号的短信验证码后提交注销申请。</p> <p>验证已绑定手机号的短信验证码后提交注销申请。</p>
<form class="editor-form security-form" data-security-form="deactivate"> <form class="editor-form security-form" data-security-form="deactivate" novalidate>
<div class="editor-field"> <div class="editor-field">
<label for="deactivateSmsCode">短信验证码</label> <label for="deactivateSmsCode">短信验证码 <span class="field-required" aria-hidden="true">*</span></label>
<input <input
id="deactivateSmsCode" id="deactivateSmsCode"
name="smsCode" name="smsCode"
@@ -127,17 +127,16 @@
<button class="btn ghost magnetic" type="button" data-security-send-deactivate-code> <button class="btn ghost magnetic" type="button" data-security-send-deactivate-code>
获取验证码 获取验证码
</button> </button>
<p class="api-status" data-security-bound-phone>正在读取已绑定手机号</p> <div class="form-status" data-security-bound-phone role="status" aria-live="polite"><div class="api-state api-state--loading" data-api-state="loading" aria-busy="true">正在读取已绑定手机号</div></div>
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label for="confirmText">确认文本</label> <label for="confirmText">确认文本 <span class="field-required" aria-hidden="true">*</span></label>
<input id="confirmText" name="confirmText" type="text" placeholder="请输入:确认注销" required /> <input id="confirmText" name="confirmText" type="text" placeholder="请输入:确认注销" required />
</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">提交注销</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>
<span class="api-status" data-security-status>等待验证</span>
</div> </div>
</form> </form>
</section> </section>
@@ -155,10 +154,10 @@
<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/profile-common.js"></script>
<script src="public/tac/js/tac.min.js"></script> <script src="public/tac/js/tac.min.js"></script>
<script src="public/js/captcha-pages.js"></script> <script src="public/js/captcha-pages.js"></script>
<script src="public/js/security-pages.js"></script> <script src="public/js/security-pages.js"></script>
<script src="public/js/profile-common.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+67 -40
View File
@@ -1,4 +1,4 @@
<!doctype html> <!doctype html>
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -8,15 +8,15 @@
<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-vip-page data-feature-status="pending" data-feature-message="会员与在线服务正在开发中,当前页面仅保留设计预览。"> <body class="page-profile-module" data-vip-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="index.html"
><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a ><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a
> >
<nav class="nav-links"> <nav class="nav-links">
<a href="profile.html">首页</a <a href="profile.html">个人中心</a
><a href="profile-families.html">数字家谱</a ><a href="profile-families.html">我的家谱</a
><a href="plaza.html">家谱广场</a ><a href="plaza.html">家谱广场</a
><a href="profile-content.html">家族文化</a ><a href="profile-content.html">家族文化</a
><a href="surname.html">姓氏百科</a><a href="app.html">应用下载</a ><a href="surname.html">姓氏百科</a><a href="app.html">应用下载</a
@@ -24,7 +24,7 @@
><a href="about.html">关于我们</a> ><a href="about.html">关于我们</a>
</nav> </nav>
<div class="nav-actions"> <div class="nav-actions">
<a href="profile.html">个人中心</a <a href="profile-data.html">账户资料</a
><a class="btn primary magnetic" href="profile-create-family.html" ><a class="btn primary magnetic" href="profile-create-family.html"
>创建家谱</a >创建家谱</a
> >
@@ -34,16 +34,13 @@
<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" <a class="btn primary magnetic" href="profile-feedback.html" data-feature-link="available"
>提交反馈</a >提交反馈</a
> >
</div> </div>
@@ -63,12 +60,18 @@
<section class="module-panel"> <section class="module-panel">
<h2>常用服务</h2> <h2>常用服务</h2>
<div class="module-grid"> <div class="module-grid">
<a class="module-card tilt-card" href="profile-share.html" <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></a <p>查看推广收益、可用余额、流水和提现记录</p>
> </a>
<a class="module-card tilt-card" href="profile-feedback.html" <div class="module-card is-unavailable" aria-disabled="true">
<span class="icon"></span>
<h3>个性化推荐</h3>
<p>推荐设置正在完善,目前不会保存开关状态。</p>
<span class="pill">暂不可设置</span>
</div>
<a class="module-card tilt-card" href="profile-feedback.html" data-feature-link="available"
><span class="icon"></span> ><span class="icon"></span>
<h3>意见反馈</h3> <h3>意见反馈</h3>
<p>提交产品问题、资料异常和改进建议。</p></a <p>提交产品问题、资料异常和改进建议。</p></a
@@ -96,40 +99,63 @@
</div> </div>
</section> </section>
<section class="module-panel"> <section class="module-panel">
<h2>平台设置与协议</h2>
<p>集中查看平台说明、服务支持和协议入口;正式内容以平台发布版本为准。</p>
<div class="module-grid">
<a class="module-card tilt-card" href="about.html"
><span class="icon"></span>
<h3>关于我们与联系我们</h3>
<p>查看平台介绍、客服信息和服务说明。</p></a
>
<a class="module-card tilt-card" href="privacy.html"
><span class="icon"></span>
<h3>隐私政策</h3>
<p>查看个人信息处理说明。</p></a
>
<a class="module-card tilt-card" href="children-privacy.html"
><span class="icon"></span>
<h3>儿童信息保护</h3>
<p>查看儿童个人信息处理规则。</p></a
>
<a class="module-card tilt-card" href="terms.html"
><span class="icon"></span>
<h3>服务协议</h3>
<p>查看平台服务协议入口。</p></a
>
</div>
</section>
<section class="module-panel" id="membership" tabindex="-1">
<h2>会员服务</h2> <h2>会员服务</h2>
<p>选择适合当前家谱的会员套餐创建订单后继续完成支付</p> <p>选择适合的会员套餐创建微信扫码支付订单。</p>
<div class="vip-package-list" data-vip-package-list> <div class="vip-package-list" data-vip-package-list>
<div class="api-empty">会员套餐加载中</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取会员套餐</div>
</div> </div>
<form class="editor-form vip-order-form" data-vip-order-form> <form class="editor-form vip-order-form" data-vip-order-form novalidate>
<div class="editor-two"> <p class="form-status" data-vip-selected-package role="status" aria-live="polite">尚未选择套餐</p>
<div class="editor-field"> <div class="editor-field">
<label for="packageId">套餐 ID</label> <label for="vipGenealogy">关联家谱(可选)</label>
<input id="packageId" name="packageId" type="number" placeholder="请先选择套餐" required /> <select id="vipGenealogy" data-vip-genealogy-options>
</div> <option value="">家谱选项加载中</option>
<div class="editor-field">
<label for="genealogyId">家谱 ID</label>
<input id="genealogyId" name="genealogyId" type="number" placeholder="可选,指定要开通的家谱" />
</div>
</div>
<div class="editor-field">
<label for="payType">支付方式</label>
<select id="payType" name="payType">
<option value="wechat">微信支付</option>
<option value="alipay">支付宝</option>
<option value="balance">余额支付</option>
</select> </select>
</div> </div>
<div class="bottom-actions"> <div class="bottom-actions">
<button class="btn primary magnetic" type="submit">创建会员订单</button> <button class="btn primary magnetic" type="submit" data-vip-order-submit>创建会员订单</button>
<span class="api-status" data-vip-order-status>等待选择套餐</span> <div class="form-status" data-vip-order-status role="status" aria-live="polite"></div>
</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">
<h2>会员订单</h2> <h2>会员订单</h2>
<button class="btn outline" type="button" data-vip-order-refresh>刷新订单</button>
</div>
<div class="module-list" data-vip-order-list> <div class="module-list" data-vip-order-list>
<div class="api-empty">会员订单加载中</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取会员订单</div>
</div> </div>
</section> </section>
<section class="module-panel"> <section class="module-panel">
@@ -162,8 +188,9 @@
<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/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/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+30 -21
View File
@@ -3,25 +3,24 @@
<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/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-share-page data-feature-status="pending" data-feature-message="应用分享与邀请奖励服务正在开发中,当前页面仅保留设计预览。"> <body class="page-profile-module" data-family-share-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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
<a href="profile-services.html">帮助与服务</a <a href="profile-services.html">帮助与服务</a
><a class="active" href="profile-share.html">应用分享</a> ><a class="active" href="profile-share.html">应用分享与收益</a>
</nav> </nav>
<div class="nav-actions"> <div class="nav-actions">
<a href="profile-services.html">返回服务中心</a <a class="btn ghost magnetic" href="profile-services.html"
><a class="btn primary magnetic" href="profile-share.html" >返回服务中心</a
>立即分享</a
> >
</div> </div>
</div> </div>
@@ -29,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>邀请好友创建和维护家谱,查看应用下载、分享奖励和推广入口</p> <p>应用邀请暂以移动端为主;这里可查看推广收益、流水和提现记录</p>
</div> </div>
</div> </div>
</div> </div>
@@ -42,7 +41,7 @@
<div class="container module-layout"> <div class="container module-layout">
<aside class="module-nav"> <aside class="module-nav">
<a href="profile-services.html">帮助与服务</a <a href="profile-services.html">帮助与服务</a
><a class="active" href="profile-share.html">应用分享</a ><a class="active" href="profile-share.html">应用分享与收益</a
><a href="profile-feedback.html">意见反馈</a ><a href="profile-feedback.html">意见反馈</a
><a href="help.html">帮助中心</a> ><a href="help.html">帮助中心</a>
</aside> </aside>
@@ -50,28 +49,39 @@
<section class="module-panel"> <section class="module-panel">
<h2>邀请好友奖励</h2> <h2>邀请好友奖励</h2>
<div class="module-grid"> <div class="module-grid">
<div class="module-card"> <div class="module-card is-unavailable" aria-disabled="true">
<span class="icon"></span> <span class="icon"></span>
<h3>每邀请一位好友</h3> <h3>每邀请一位好友</h3>
<p data-member-overview>分享概览加载中...</p> <p>邀请奖励规则正在完善,当前暂不展示奖励金额。</p>
</div> </div>
<div class="module-card"> <div class="module-card is-unavailable" aria-disabled="true">
<span class="icon"></span> <span class="icon"></span>
<h3>分享二维码</h3> <h3>分享二维码</h3>
<p data-invite-link>分享链接加载中...</p> <p>分享链接和二维码暂未开放,请先使用移动端分享。</p>
</div> </div>
</div> </div>
</section> </section>
<section class="module-panel"> <section class="module-panel">
<h2>分享记录</h2> <h2>分享记录</h2>
<div class="module-list"> <div class="module-list">
<div class="module-row"> <div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">
<div> 分享记录暂未开放;推广收益和提现结果可在下方查看。
<h3>本月邀请</h3>
<p>3 人访问 · 1 人完成注册</p>
</div> </div>
<span class="pill">查看</span>
</div> </div>
</section>
<section class="module-panel">
<h2>收益与提现</h2>
<div class="module-grid two">
<a class="module-card tilt-card" href="profile-earnings.html" data-feature-link="available">
<span class="icon"></span>
<h3>推广收益</h3>
<p>查看可用余额、冻结金额和不可变收益流水。</p>
</a>
<a class="module-card tilt-card" href="profile-earnings.html" data-feature-link="available">
<span class="icon"></span>
<h3>提现记录</h3>
<p>上传微信收款码,提交提现并跟踪处理状态。</p>
</a>
</div> </div>
</section> </section>
</div> </div>
@@ -87,7 +97,6 @@
<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/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+28 -22
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-lineage-page> <body class="page-profile-module" data-lineage-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="index.html"
><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a ><img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /><span class="brand-text">个人中心</span></a
> >
<nav class="nav-links"> <nav class="nav-links">
@@ -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,23 +82,18 @@
<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>
<!-- 世系树由 public/js/lineage-pages.js 按接口数据渲染 --> <!-- 世系树由 public/js/lineage-pages.js 按接口数据渲染 -->
<div class="lineage-tree-wrap" data-lineage-tree> <div class="lineage-tree-wrap" data-lineage-tree>
<div class="api-empty">世系树加载中...</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">世系树加载中</div>
</div> </div>
</section> </section>
<section class="module-panel"> <section class="module-panel">
<h2>成员搜索</h2> <h2>成员搜索</h2>
<div class="lineage-toolbar lineage-filter-toolbar"> <div class="lineage-toolbar lineage-filter-toolbar">
<input type="search" data-lineage-keyword placeholder="输入姓名、别名或人物编号搜索" /> <input type="search" data-lineage-keyword aria-label="按姓名、别名或人物编号搜索成员" placeholder="输入姓名、别名或人物编号搜索" />
<select data-lineage-generation-filter aria-label="按世代筛选"> <select data-lineage-generation-filter aria-label="按世代筛选">
<option value="">全部世代</option> <option value="">全部世代</option>
</select> </select>
@@ -104,14 +105,15 @@
</select> </select>
<button class="btn primary" type="button" data-lineage-search>搜索</button> <button class="btn primary" type="button" data-lineage-search>搜索</button>
</div> </div>
<p class="form-status" data-lineage-list-summary></p> <p class="form-status" role="status" aria-live="polite" data-lineage-list-summary></p>
<div class="form-status" role="status" aria-live="polite" data-lineage-action-status></div>
<!-- 成员列表由世系人物分页接口渲染 --> <!-- 成员列表由世系人物分页接口渲染 -->
<div class="module-list" data-lineage-list> <div class="module-list" data-lineage-list>
<div class="api-empty">成员加载中...</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">世系人物加载中…</div>
</div> </div>
<div class="bottom-actions" data-lineage-pagination hidden> <div class="bottom-actions" data-lineage-pagination hidden>
<button class="btn ghost" type="button" data-lineage-page-action="previous">上一页</button> <button class="btn ghost" type="button" data-lineage-page-action="previous">上一页</button>
<span class="form-status" data-lineage-page-status></span> <span class="form-status" data-lineage-page-status role="status" aria-live="polite"></span>
<button class="btn ghost" type="button" data-lineage-page-action="next">下一页</button> <button class="btn ghost" type="button" data-lineage-page-action="next">下一页</button>
</div> </div>
</section> </section>
@@ -119,24 +121,27 @@
<h2>成员详情</h2> <h2>成员详情</h2>
<!-- 点击成员列表或世系树节点后渲染详情 --> <!-- 点击成员列表或世系树节点后渲染详情 -->
<div data-lineage-detail> <div data-lineage-detail>
<div class="api-empty">请选择一个成员查看资料</div> <div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一个成员查看资料</div>
</div> </div>
</section> </section>
<section class="module-panel" data-lineage-management hidden> <section class="module-panel" data-lineage-management hidden>
<h2>新增 / 编辑成员</h2> <h2>新增 / 编辑成员</h2>
<form id="lineage-member-form" class="editor-form" data-lineage-form> <form id="lineage-member-form" class="editor-form" data-lineage-form novalidate>
<div class="editor-two"> <div class="editor-two">
<div class="editor-field"> <div class="editor-field">
<label for="lineage-binding-mode">账号绑定</label> <label for="lineage-binding-mode">账号绑定 <span class="field-required" aria-hidden="true">*</span></label>
<select id="lineage-binding-mode" name="bindingMode" required> <select id="lineage-binding-mode" name="bindingMode" required>
<option value="NONE" selected>不绑定账号</option> <option value="NONE" selected>不绑定账号</option>
<option value="SELF">绑定当前登录账号</option> <option value="SELF">绑定当前登录账号</option>
<option value="SPECIFIED">指定其他账号</option> <option value="SPECIFIED">指定其他账号</option>
</select> </select>
<select name="appUserId" data-lineage-member-options hidden> <div class="editor-field" data-lineage-specified-member hidden>
<label for="lineage-specified-member">指定账号</label>
<select id="lineage-specified-member" name="appUserId" data-lineage-member-options hidden>
<option value="">请选择已绑定账号的家谱成员</option> <option value="">请选择已绑定账号的家谱成员</option>
</select> </select>
<p class="upload-status" data-lineage-binding-status>指定账号仅可从当前家谱正常成员中选择。</p> </div>
<p class="upload-status" data-lineage-binding-status role="status" aria-live="polite">指定账号仅可从当前家谱正常成员中选择。</p>
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label for="lineage-avatar-file">头像</label> <label for="lineage-avatar-file">头像</label>
@@ -153,12 +158,12 @@
data-upload-biz-type="avatar" data-upload-biz-type="avatar"
data-upload-usage-scene="lineage_avatar" data-upload-usage-scene="lineage_avatar"
/> />
<p id="lineage-avatar-status" class="upload-status">请选择头像文件</p> <p id="lineage-avatar-status" class="upload-status" role="status" aria-live="polite">请选择头像文件</p>
</div> </div>
</div> </div>
<div class="editor-two"> <div class="editor-two">
<div class="editor-field"> <div class="editor-field">
<label for="lineage-person-name">姓名</label> <label for="lineage-person-name">姓名 <span class="field-required" aria-hidden="true">*</span></label>
<input id="lineage-person-name" name="name" type="text" placeholder="请输入成员姓名" required /> <input id="lineage-person-name" name="name" type="text" placeholder="请输入成员姓名" required />
</div> </div>
<div class="editor-field"> <div class="editor-field">
@@ -273,9 +278,9 @@
<textarea id="lineage-remark" name="remark" placeholder="可选"></textarea> <textarea id="lineage-remark" name="remark" placeholder="可选"></textarea>
</div> </div>
<div class="bottom-actions"> <div class="bottom-actions">
<button class="btn primary" type="submit">保存成员</button> <button class="btn primary" type="submit" data-lineage-submit>保存成员</button>
<button class="btn ghost" type="reset">清空</button> <button class="btn ghost" type="reset">清空</button>
<span class="form-status" data-lineage-form-status></span> <span class="form-status" role="status" aria-live="polite" data-lineage-form-status></span>
</div> </div>
</form> </form>
</section> </section>
@@ -293,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>
+14 -12
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-video-edit-page> <body class="page-profile-module" data-video-edit-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="index.html">
<img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /> <img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" />
<span class="brand-text">个人中心</span> <span class="brand-text">个人中心</span>
</a> </a>
@@ -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>
@@ -47,9 +47,9 @@
<div class="module-main"> <div class="module-main">
<section class="module-panel"> <section class="module-panel">
<h2>视频内容</h2> <h2>视频内容</h2>
<form id="video-edit-form" class="editor-form" data-video-form data-video-management hidden> <form id="video-edit-form" class="editor-form" data-video-form data-video-management hidden novalidate>
<div class="editor-field"> <div class="editor-field">
<label for="video-title">视频标题</label> <label for="video-title">视频标题 <span class="field-required" aria-hidden="true">*</span></label>
<input id="video-title" name="videoTitle" type="text" placeholder="请输入视频标题" required /> <input id="video-title" name="videoTitle" type="text" placeholder="请输入视频标题" required />
</div> </div>
<div class="editor-field"> <div class="editor-field">
@@ -58,7 +58,7 @@
</div> </div>
<div class="editor-two"> <div class="editor-two">
<div class="editor-field"> <div class="editor-field">
<label for="video-file">视频文件</label> <label for="video-file">视频文件 <span class="field-required" aria-hidden="true">*</span></label>
<input id="video-oss-id" name="videoOssId" type="hidden" /> <input id="video-oss-id" name="videoOssId" type="hidden" />
<label class="upload-control" for="video-file">选择视频文件</label> <label class="upload-control" for="video-file">选择视频文件</label>
<input <input
@@ -66,11 +66,11 @@
class="upload-input" class="upload-input"
type="file" type="file"
accept="video/*" accept="video/*"
data-upload-target="#video-oss-id" data-attachment-editor data-upload-target="#video-oss-id"
data-upload-status="#video-file-status" data-upload-status="#video-file-status"
data-video-duration-target="#video-duration" data-video-duration-target="#video-duration"
/> />
<p id="video-file-status" class="upload-status" data-video-file-status>请选择视频文件</p> <p id="video-file-status" class="upload-status" data-video-file-status role="status" aria-live="polite">请选择视频文件</p>
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label for="video-cover-file">封面图片</label> <label for="video-cover-file">封面图片</label>
@@ -81,10 +81,10 @@
class="upload-input" class="upload-input"
type="file" type="file"
accept="image/*" accept="image/*"
data-upload-target="#video-cover-oss-id" data-attachment-editor data-upload-target="#video-cover-oss-id"
data-upload-status="#video-cover-status" data-upload-status="#video-cover-status"
/> />
<p id="video-cover-status" class="upload-status" data-video-cover-status>未选择封面</p> <p id="video-cover-status" class="upload-status" data-video-cover-status role="status" aria-live="polite">未选择封面</p>
</div> </div>
</div> </div>
<div class="editor-two"> <div class="editor-two">
@@ -99,13 +99,13 @@
</div> </div>
</div> </div>
<input name="status" type="hidden" value="0" /> <input name="status" type="hidden" value="0" />
<div class="form-status" data-video-form-status role="status" aria-live="polite"></div>
<div class="bottom-actions"> <div class="bottom-actions">
<button class="btn primary magnetic" type="submit">保存视频</button> <button class="btn primary magnetic" type="submit">保存视频</button>
<a class="btn ghost magnetic" href="profile-video.html" data-genealogy-context-link>取消</a> <a class="btn ghost magnetic" href="profile-video.html" data-genealogy-context-link>取消</a>
<span class="form-status" data-video-form-status></span>
</div> </div>
</form> </form>
<div class="api-empty" data-video-permission-placeholder>正在核对视频维护权限…</div> <div class="api-state api-state--loading" data-api-state="loading" data-video-permission-placeholder role="status" aria-live="polite" aria-busy="true">正在核对视频维护权限…</div>
</section> </section>
</div> </div>
</div> </div>
@@ -121,7 +121,9 @@
<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/md5.js"></script> <script src="public/js/md5.js"></script>
<script src="public/js/attachment-editor.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>
+9 -4
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-video-page> <body class="page-profile-module" data-video-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="index.html">
<img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" /> <img class="brand-logo brand-logo-mark" src="public/images/logo-mark.png" alt="" />
<span class="brand-text">个人中心</span> <span class="brand-text">个人中心</span>
</a> </a>
@@ -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>
@@ -50,13 +50,17 @@
<section class="module-panel"> <section class="module-panel">
<h2>视频列表</h2> <h2>视频列表</h2>
<div class="module-list" data-video-list aria-live="polite"> <div class="module-list" data-video-list aria-live="polite">
<div class="api-empty">正在加载视频…</div> <div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
正在加载视频…
</div>
</div> </div>
</section> </section>
<section class="module-panel"> <section class="module-panel">
<h2>视频详情</h2> <h2>视频详情</h2>
<div data-video-detail aria-live="polite"> <div data-video-detail aria-live="polite">
<div class="api-empty">请选择一个视频查看详情</div> <div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">
请选择一个视频查看详情
</div>
</div> </div>
</section> </section>
</div> </div>
@@ -72,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>
+70 -232
View File
@@ -1,4 +1,4 @@
<!doctype html> <!doctype html>
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -11,12 +11,12 @@
<body class="page-profile" data-profile-page> <body class="page-profile" data-profile-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="index.html"
><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a ><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a
> >
<nav class="nav-links"> <nav class="nav-links">
<a href="profile.html">首页</a> <a class="active" href="profile.html">个人中心</a>
<a href="profile-families.html">数字家谱</a> <a href="profile-families.html">我的家谱</a>
<a href="plaza.html">家谱广场</a> <a href="plaza.html">家谱广场</a>
<a href="profile-content.html">家族文化</a> <a href="profile-content.html">家族文化</a>
<a href="surname.html">姓氏百科</a> <a href="surname.html">姓氏百科</a>
@@ -25,10 +25,8 @@
<a href="about.html">关于我们</a> <a href="about.html">关于我们</a>
</nav> </nav>
<div class="nav-actions"> <div class="nav-actions">
<a class="active" href="profile.html">个人中心</a> <a href="profile-data.html">账户资料</a>
<a class="btn primary magnetic" href="profile-create-family.html" <a class="btn primary magnetic" href="profile-create-family.html">创建家谱</a>
>创建家谱</a
>
</div> </div>
</div> </div>
</header> </header>
@@ -36,29 +34,33 @@
<main> <main>
<section class="profile-hero"> <section class="profile-hero">
<div class="container profile-hero-grid"> <div class="container profile-hero-grid">
<div class="profile-card tilt-card"> <div class="profile-card">
<div class="avatar-wrap"> <div class="avatar-wrap">
<div class="avatar" data-profile-text="avatarText">鹿</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">
<div class="eyebrow">Personal Center</div> <div class="eyebrow">个人中心</div>
<h1 data-profile-text="displayName">鹿野 Loyel</h1> <h1 data-profile-text="displayName">正在加载账户</h1>
<p> <p>账户资料与家谱工作区分开呈现。进入家谱后,再处理成员、内容和协作事务。</p>
管理家谱、亲属资料、家族内容和协作消息。APP
里的核心功能都收纳在这里,适合 PC 端集中处理。
</p>
<div class="profile-tags"> <div class="profile-tags">
<span>血亲</span><span>第二代 顺字辈</span <span>已登录账户</span><span>资料与账号同步</span>
><span>已绑定账号</span> </div>
<div class="profile-load-status" data-profile-load-status role="status" aria-live="polite">正在读取账户资料…</div>
<div class="toolbar profile-hero-actions">
<a class="btn primary magnetic" href="profile-families.html">进入我的家谱</a>
<a class="btn ghost magnetic" href="profile-data.html">完善个人资料</a>
</div> </div>
</div> </div>
</div> </div>
<div class="profile-stats tilt-card"> <div class="profile-next-card">
<div><strong>2</strong><span>我的家谱</span></div> <div class="eyebrow">下一步</div>
<div><strong>7</strong><span>待处理</span></div> <h2>从家谱开始</h2>
<div><strong>18</strong><span>已发布内容</span></div> <p>没有家谱时可创建或申请加入;有多个家谱时,请先选择本次要处理的家谱。</p>
<div><strong>86%</strong><span>资料完善度</span></div> <div class="toolbar">
<a class="link-btn" href="profile-create-family.html">创建家谱</a>
<a class="link-btn" href="profile-join-family.html">申请加入</a>
</div>
</div> </div>
</div> </div>
</section> </section>
@@ -66,216 +68,70 @@
<section class="section dashboard-section"> <section class="section dashboard-section">
<div class="container dashboard-layout"> <div class="container dashboard-layout">
<aside class="dashboard-side"> <aside class="dashboard-side">
<div class="side-card user-actions tilt-card"> <div class="side-card user-actions">
<h2>账户入口</h2> <h2>账户入口</h2>
<a href="profile-data.html" <a href="profile-data.html"><span>个人资料</span><b>查看并更新账户资料</b></a>
><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-messages.html" <button class="logout-link" type="button" data-logout-open><span>退出登录</span><b>返回首页</b></button>
><span>消息中心</span><b>审核、邀请与提醒</b></a
>
<a href="profile-services.html"
><span>帮助与服务</span><b>帮助、反馈、推广</b></a
>
<a class="logout-link" href="#logout" data-logout-open
><span>退出登录</span><b>返回首页</b></a
>
</div>
<div class="side-card todo-card tilt-card" id="messages">
<div class="card-head">
<h2>待办消息</h2>
<span>7 条</span>
</div>
<div class="todo-list">
<a href="profile-join-review.html"
><b>入谱申请</b><span>3 人等待审核</span></a
>
<a href="profile-gift.html"
><b>贺礼邀请</b><span>2 条需要确认</span></a
>
<a href="profile-data-reminders.html"
><b>资料提醒</b><span>2 位成员待完善</span></a
>
</div>
</div> </div>
</aside> </aside>
<div class="dashboard-main"> <div class="dashboard-main">
<section class="panel my-family-panel" id="families"> <section class="panel my-family-panel">
<div class="panel-head"> <div class="panel-head">
<div> <div>
<h2>我的家谱</h2> <h2>我的家谱</h2>
<p> <p>先从真实列表选择家谱,再进入对应的主页、世系图、成员和内容管理。</p>
对应 APP
首页和家谱主页,进入后可查看家谱主页、世系图、相册和家族圈。
</p>
</div> </div>
<a class="link-btn" href="profile-families.html">查看全部</a> <a class="link-btn" href="profile-families.html">查看全部</a>
<div class="toolbar">
<a
class="btn primary magnetic"
href="profile-create-family.html"
>创建家谱</a
><a class="btn ghost magnetic" href="profile-join-family.html"
>加入家谱</a
>
</div>
</div> </div>
<div class="family-grid"> <div class="family-grid">
<a <a class="family-card" href="profile-families.html">
class="family-card tilt-card" <div class="book-cover">选 择<br />家 谱</div>
href="profile-families.html"
>
<div class="book-cover">我 的<br />家 谱</div>
<div> <div>
<h3>进入我的家谱</h3> <h3>进入我的家谱</h3>
<p> <p>列表只展示当前账户真实可访问的家谱,避免把示例数据当成自己的内容。</p>
从家谱入口选择真实家谱后,再查看主页、成员、世系图和家族内容。
</p>
<div class="mini-actions"> <div class="mini-actions">
<span>家谱主页</span><span>家族圈</span><span>成员管理</span> <span>选择家谱</span><span>进入主页</span><span>处理事务</span>
</div> </div>
</div> </div>
</a> </a>
</div> </div>
</section> </section>
<section class="panel" id="management"> <section class="panel">
<div class="panel-head compact"> <div class="panel-head compact">
<div> <div>
<h2>家族管理</h2> <h2>账户资料</h2>
<p> <p>以下内容来自你的账户资料;未填写的信息会显示为“待填写”。</p>
把 APP 里的世代、管理员、权限、入谱审核集中到一个管理区。
</p>
</div>
<a class="link-btn" href="profile-family-admin.html" data-genealogy-context-link
>进入管理</a
>
</div>
<div class="feature-grid management-grid">
<a class="feature-tile tilt-card" href="profile-families.html"
><span></span>
<h3>世系图</h3>
<p>查看成员世系、搜索成员、进入树谱模式。</p></a
>
<a class="feature-tile tilt-card" href="profile-families.html"
><span></span>
<h3>字辈谱</h3>
<p>管理字辈、统计各代人数和成员分布。</p></a
>
<a class="feature-tile tilt-card" href="profile-generation.html" data-genealogy-context-link
><span></span>
<h3>管理世代</h3>
<p>设置世代、调整始祖世代、维护排行。</p></a
>
<a class="feature-tile tilt-card" href="profile-messages.html"
><span></span>
<h3>入谱审核</h3>
<p>处理加入家谱、成员绑定和资料审核。</p></a
>
<a
class="feature-tile tilt-card"
href="profile-admin-permissions.html"
><span></span>
<h3>管理员权限</h3>
<p>分配全权、全系、支系管理员权限。</p></a
>
<a class="feature-tile tilt-card" href="profile-invite.html"
><span></span>
<h3>邀请家人</h3>
<p>生成邀请入口,引导亲人激活使用。</p></a
>
</div>
</section>
<section class="panel" id="content">
<div class="panel-head compact">
<div>
<h2>内容发布</h2>
<p>
对应 APP
家谱主页中的谱文、视频、相册、功德录、贺礼邀请、成长日志等内容入口。
</p>
</div>
<a class="link-btn" href="profile-content.html">发布中心</a>
</div>
<div class="content-grid">
<a
class="content-tile large tilt-card"
href="profile-article.html" data-genealogy-context-link
><span>谱文</span>
<h3>撰写谱文与族史</h3>
<p>沉淀族训、谱序、谱论、恩荣录等家族文献。</p></a
>
<a class="content-tile tilt-card" href="profile-album.html" data-genealogy-context-link
><span>相册</span>
<h3>家族相册</h3>
<p>新建相册、上传照片、编辑封面。</p></a
>
<a class="content-tile tilt-card" href="profile-video.html" data-genealogy-context-link
><span>视频</span>
<h3>家族视频</h3>
<p>发布视频、管理家族影像。</p></a
>
<a class="content-tile tilt-card" href="profile-merit.html" data-genealogy-context-link
><span>功德</span>
<h3>功德录</h3>
<p>记录功德人、事迹与纪念内容。</p></a
>
<a class="content-tile tilt-card" href="profile-gift.html" data-genealogy-context-link
><span>贺礼</span>
<h3>贺礼邀请</h3>
<p>管理婚礼、生日、升学等贺礼。</p></a
>
<a class="content-tile tilt-card" href="profile-families.html?next=profile-feed.html"
><span>动态</span>
<h3>发布动态</h3>
<p>面向家族圈发布图文近况。</p></a
>
<a class="content-tile tilt-card" href="profile-families.html?next=profile-growth.html"
><span>日志</span>
<h3>成长日志</h3>
<p>记录成员成长和人生节点。</p></a
>
<a class="content-tile tilt-card" href="profile-families.html?next=profile-memo.html"
><span>备忘</span>
<h3>人亲簿 / 备忘录</h3>
<p>保存亲友往来和家族事务。</p></a
>
</div>
</section>
<section class="profile-data-layout" id="profile-data">
<div class="panel data-panel">
<div class="panel-head compact">
<div>
<h2>个人资料</h2>
<p>来自 APP 的个人资料、亲属、完善资料和绑定账号流程。</p>
</div> </div>
<a class="link-btn" href="profile-data.html">编辑资料</a> <a class="link-btn" href="profile-data.html">编辑资料</a>
</div> </div>
<div class="data-list"> <div class="data-list">
<p><span>绑定账号</span><b data-profile-text="phone">已绑定</b></p> <p><span>绑定账号</span><b data-profile-text="phone">加载中</b></p>
<p><span>姓名</span><b data-profile-text="displayName">鹿野 Loyel</b></p> <p><span>昵称</span><b data-profile-text="displayName">加载中</b></p>
<p><span>出生日期</span><b data-profile-text="birthday">待填写</b></p> <p><span>真实姓名</span><b data-profile-text="realName">加载中</b></p>
<p><span>现居地区</span><b data-profile-text="regionText">待填写</b></p> <p><span>性别</span><b data-profile-text="sex">加载中</b></p>
<p><span>亲属关系</span><b>父亲 2 人</b></p> <p><span>出生日期</span><b data-profile-text="birthday">加载中</b></p>
<p><span>补充资料</span><b>待完善 4 项</b></p> <p><span>邮箱</span><b data-profile-text="email">加载中</b></p>
</div> </div>
</div> </section>
<div class="panel service-panel" id="services"> <section class="panel">
<div class="panel-head compact"> <div class="panel-head compact">
<div> <div>
<h2>帮助与服务</h2> <h2>账户与服务</h2>
<p>我的页面中的常用功能</p> <p>将 APP 的“我的”功能整理为桌面服务入口;家谱内容仍需从具体家谱进入</p>
</div> </div>
<a class="link-btn" href="profile-services.html">查看服务中心</a>
</div> </div>
<div class="service-grid"> <div class="service-grid" aria-label="账户与服务入口">
<a href="profile-share.html">应用分享</a> <a href="profile-messages.html"><strong>消息中心</strong><span>查看真实通知与业务提醒</span></a>
<a href="profile-feedback.html">意见反馈</a> <a href="profile-data.html"><strong>个人资料</strong><span>维护昵称、实名和联系方式</span></a>
<a href="help.html">帮助中心</a> <a href="profile-security.html"><strong>账户安全</strong><span>修改密码、换绑手机或注销账号</span></a>
<a href="profile-security.html">安全设置</a> <a href="profile-services.html#membership"><strong>会员服务</strong><span>查看套餐、订单、帮助与服务说明</span></a>
</div> <a href="profile-feedback.html"><strong>意见反馈</strong><span>提交产品问题和改进建议</span></a>
<a href="app.html"><strong>移动端</strong><span>下载 APP,处理移动端场景</span></a>
</div> </div>
</section> </section>
</div> </div>
@@ -287,29 +143,24 @@
<div class="container"> <div class="container">
<div class="footer-grid"> <div class="footer-grid">
<div> <div>
<a class="brand" href="profile.html" <a class="brand" href="index.html"
><img class="brand-logo" src="public/images/logo-light.png" alt="我们的家谱,代代相传" /></a ><img class="brand-logo" src="public/images/logo-light.png" alt="我们的家谱,代代相传" /></a
> >
<p>一个面向家庭和宗族的数字家谱与家族文化门户。</p> <p>一个面向家庭和宗族的数字家谱与家族文化门户。</p>
</div> </div>
<div> <div>
<h4>产品入口</h4> <h4>产品入口</h4>
<a href="profile-families.html">数字家谱</a <a href="profile-families.html">数字家谱</a><a href="plaza.html">家谱广场</a><a href="app.html">应用下载</a><a href="help.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 href="profile-content.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>
><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">
@@ -318,29 +169,15 @@
</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 <div class="logout-dialog" role="dialog" aria-modal="true" aria-labelledby="logoutTitle">
class="logout-dialog" <button class="logout-close" type="button" data-logout-close aria-label="关闭">×</button>
role="dialog"
aria-modal="true"
aria-labelledby="logoutTitle"
>
<button
class="logout-close"
type="button"
data-logout-close
aria-label="关闭"
>
×
</button>
<span class="logout-icon">退</span> <span class="logout-icon">退</span>
<h2 id="logoutTitle">确认退出登录?</h2> <h2 id="logoutTitle">确认退出登录?</h2>
<p>退出后需要重新登录,才能继续管理家谱、审核消息和编辑资料。</p> <p>退出后需要重新登录,才能继续管理家谱、审核消息和编辑资料。</p>
<div class="logout-actions"> <div class="logout-actions">
<button class="btn ghost magnetic" type="button" data-logout-close> <button class="btn ghost magnetic" type="button" data-logout-close>取消</button>
取消
</button>
<a class="btn primary magnetic" href="index.html" data-logout-confirm>确认退出</a> <a class="btn primary magnetic" href="index.html" data-logout-confirm>确认退出</a>
</div> </div>
</div> </div>
@@ -354,8 +191,9 @@
<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/profile-pages.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/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+7 -7
View File
@@ -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">
+8 -7
View File
@@ -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">
@@ -100,6 +100,7 @@
</div> </div>
</div> </div>
</footer> </footer>
<script src="config.js"></script>
<script src="public/js/page-effects.js"></script> <script src="public/js/page-effects.js"></script>
</body> </body>
</html> </html>
+3
View File
@@ -183,10 +183,13 @@
} }
.promotion-card { .promotion-card {
display: block;
overflow: hidden; overflow: hidden;
border: 1px solid var(--line); border: 1px solid var(--line);
border-radius: 8px; border-radius: 8px;
background: #fffaf0; background: #fffaf0;
color: inherit;
text-decoration: none;
box-shadow: 0 18px 42px rgba(57, 48, 36, .08); box-shadow: 0 18px 42px rgba(57, 48, 36, .08);
transition: transform .24s ease, border-color .24s ease, box-shadow .24s ease; transition: transform .24s ease, border-color .24s ease, box-shadow .24s ease;
} }
+12
View File
@@ -169,6 +169,18 @@
transition: border-color .22s ease, box-shadow .22s ease, background .22s ease, transform .22s ease; transition: border-color .22s ease, box-shadow .22s ease, background .22s ease, transform .22s ease;
} }
.create-form .field {
display: grid;
gap: 8px;
color: var(--ink);
font-weight: 800;
}
.create-region-picker label {
display: grid;
gap: 8px;
}
.create-region-picker { .create-region-picker {
/* 创建家谱页地区选择器占满表单宽度,编码通过隐藏字段提交 */ /* 创建家谱页地区选择器占满表单宽度,编码通过隐藏字段提交 */
display: grid; display: grid;
+1155 -3
View File
File diff suppressed because it is too large Load Diff
+80
View File
@@ -1,6 +1,86 @@
/* 加入家谱页只保留单页差异,公共认证卡片样式在 public.css */ /* 加入家谱页只保留单页差异,公共认证卡片样式在 public.css */
.page-join-genealogy .auth-card { .page-join-genealogy .auth-card {
width: min(1080px, 100%);
}
.join-page-grid {
display: grid;
grid-template-columns: minmax(0, 1.15fr) minmax(340px, .85fr);
gap: 28px;
align-items: start;
}
.join-search-panel,
.join-apply-form {
display: grid;
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 {
padding-right: 28px;
border-right: 1px solid var(--line);
}
.join-page-grid h2 {
margin: 0;
font-size: 22px;
}
.join-page-grid .module-list {
max-height: 420px;
overflow: auto;
}
@media (max-width: 800px) {
.page-join-genealogy .auth-card {
width: min(500px, 100%); width: min(500px, 100%);
}
.join-page-grid {
grid-template-columns: 1fr;
}
.join-search-panel {
padding-right: 0;
padding-bottom: 24px;
border-right: 0;
border-bottom: 1px solid var(--line);
}
} }
.invite-box { .invite-box {
+452 -6
View File
@@ -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 {
@@ -319,6 +371,23 @@
gap: 10px; gap: 10px;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: flex-end; justify-content: flex-end;
margin-top: 16px;
}
.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 {
@@ -396,12 +465,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 +573,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;
@@ -522,6 +602,11 @@
gap: 12px; gap: 12px;
} }
.profile-region-picker > label {
display: grid;
gap: 6px;
}
.profile-region-picker select { .profile-region-picker select {
min-height: 48px; min-height: 48px;
padding: 12px 14px; padding: 12px 14px;
@@ -543,6 +628,68 @@
box-shadow: 0 24px 60px rgba(57, 48, 36, .11); box-shadow: 0 24px 60px rgba(57, 48, 36, .11);
} }
.module-card.is-unavailable {
cursor: not-allowed;
opacity: .68;
}
.module-card.is-unavailable:after {
display: none;
}
.module-card.is-unavailable .pill {
display: inline-flex;
margin-top: 12px;
color: #6b542d;
background: #fff0cf;
}
.module-nav-unavailable {
padding: 14px 16px;
border-radius: 14px;
color: var(--muted);
background: #f4f1eb;
font-weight: 900;
}
.profile-path {
display: flex;
align-items: center;
gap: 8px;
margin: 0 0 18px;
color: #6f766f;
font-size: 14px;
font-weight: 800;
}
.profile-path a {
color: var(--red);
}
.profile-path a:hover {
text-decoration: underline;
}
.profile-path span {
color: #a89f91;
}
.profile-path strong {
min-width: 0;
overflow: hidden;
color: var(--ink);
text-overflow: ellipsis;
white-space: nowrap;
}
.module-nav-unavailable small {
display: block;
margin-top: 4px;
color: #8a5a3b;
font-size: 12px;
font-weight: 700;
}
.module-card.is-selected { .module-card.is-selected {
border-color: rgba(200, 59, 50, .5); border-color: rgba(200, 59, 50, .5);
background: #fff6f1; background: #fff6f1;
@@ -666,6 +813,16 @@
display: flex; display: flex;
gap: 14px; gap: 14px;
flex-wrap: wrap; flex-wrap: wrap;
margin-top: 16px;
}
/* 网格和横向行已经通过 gap 管理间距,避免操作区重复叠加外边距。 */
.editor-form > .bottom-actions,
.editor-field > .bottom-actions,
.editor-two .bottom-actions,
.module-list > .row-actions,
.module-row > .row-actions {
margin-top: 0;
} }
.editor-form { .editor-form {
@@ -683,11 +840,16 @@
gap: 10px; gap: 10px;
} }
.editor-field label { .editor-field label,
.editor-label {
color: var(--ink); color: var(--ink);
font-weight: 900; font-weight: 900;
} }
.field-required {
color: var(--red);
}
.editor-field input, .editor-field input,
.editor-field select, .editor-field select,
.editor-field textarea { .editor-field textarea {
@@ -719,6 +881,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 {
@@ -817,6 +984,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 {
@@ -825,6 +994,14 @@
} }
@media (max-width: 720px) { @media (max-width: 720px) {
.module-hero {
min-height: 0;
}
.page-profile-module .module-nav {
display: none;
}
.module-title-row { .module-title-row {
display: grid; display: grid;
} }
@@ -833,10 +1010,6 @@
font-size: 38px; font-size: 38px;
} }
.module-nav {
grid-template-columns: 1fr;
}
.lineage-filter-toolbar { .lineage-filter-toolbar {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
@@ -848,3 +1021,276 @@
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%;
}
}
.attachment-edit-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(180px, 100%), 220px));
gap: 12px;
margin-top: 12px;
}
.attachment-edit-list:empty { display: none; }
.attachment-edit-item {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
min-width: 0;
padding: 10px;
border: 1px solid var(--border, #e6ddcf);
border-radius: 8px;
}
.attachment-edit-item :is(img, video) {
width: 100%;
height: 140px;
object-fit: contain;
background: var(--paper, #faf7f0);
}
.attachment-edit-name { overflow-wrap: anywhere; font-size: 13px; }
.attachment-edit-remove { margin-top: auto; }
.attachment-edit-remove:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }
.attachment-edit-remove:disabled { opacity: .5; cursor: not-allowed; }
+114
View File
@@ -33,6 +33,7 @@
.profile-card, .profile-card,
.profile-stats, .profile-stats,
.profile-next-card,
.panel, .panel,
.side-card { .side-card {
border: 1px solid rgba(224, 211, 189, .84); border: 1px solid rgba(224, 211, 189, .84);
@@ -94,6 +95,17 @@
line-height: 1.9; line-height: 1.9;
} }
.profile-info .profile-load-status {
margin: 14px 0 0;
color: var(--muted);
font-size: 14px;
font-weight: 800;
}
.profile-info .profile-load-status:empty {
display: none;
}
.profile-tags, .profile-tags,
.mini-actions { .mini-actions {
display: flex; display: flex;
@@ -101,6 +113,10 @@
gap: 10px; gap: 10px;
} }
.mini-actions {
margin-top: 12px;
}
.profile-tags span, .profile-tags span,
.mini-actions span { .mini-actions span {
padding: 7px 12px; padding: 7px 12px;
@@ -118,6 +134,29 @@
gap: 14px; gap: 14px;
} }
.profile-next-card {
padding: 32px;
}
.profile-next-card .eyebrow {
margin-bottom: 14px;
}
.profile-next-card h2 {
margin-bottom: 12px;
font-size: 30px;
}
.profile-next-card p {
margin-bottom: 22px;
color: var(--muted);
line-height: 1.8;
}
.profile-hero-actions {
margin-top: 22px;
}
.profile-stats div { .profile-stats div {
min-height: 116px; min-height: 116px;
padding: 20px; padding: 20px;
@@ -180,16 +219,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);
@@ -427,6 +473,26 @@
transform: translateY(-3px); transform: translateY(-3px);
} }
.service-grid strong,
.service-grid span {
display: block;
text-align: center;
}
.service-grid strong {
font-size: 17px;
}
.service-grid span {
color: var(--muted);
font-size: 13px;
line-height: 1.55;
}
.service-grid a:hover span {
color: rgba(255, 255, 255, .8);
}
.tilt-card { .tilt-card {
transform: perspective(900px) rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg)) translateY(var(--lift, 0)); transform: perspective(900px) rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg)) translateY(var(--lift, 0));
transition: transform .18s ease, box-shadow .24s ease, border-color .24s ease; transition: transform .18s ease, box-shadow .24s ease, border-color .24s ease;
@@ -448,6 +514,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;
@@ -611,3 +681,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);
}
}
+593 -21
View File
@@ -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,11 +193,92 @@ 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;
} }
.nav-user-entry {
min-width: 0;
display: inline-flex;
align-items: center;
gap: 8px;
font-weight: 700;
}
.nav-user-avatar {
width: 32px;
height: 32px;
position: relative;
flex: 0 0 auto;
display: inline-grid;
place-items: center;
overflow: hidden;
border: 1px solid rgba(255, 248, 230, .56);
border-radius: 50%;
color: var(--red-deep);
background: #fff4dc;
font-size: 13px;
font-weight: 900;
}
.nav-user-avatar img {
width: 100%;
height: 100%;
position: relative;
z-index: 1;
object-fit: cover;
}
.nav-user-avatar-fallback {
position: absolute;
inset: 0;
display: grid;
place-items: center;
}
.nav-user-name {
max-width: 96px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.profile-current-genealogy {
min-width: 0;
max-width: 240px;
display: grid;
gap: 2px;
margin-left: auto;
padding: 7px 12px;
border: 1px solid rgba(255, 248, 230, .28);
border-radius: 12px;
color: #fff8e6;
background: rgba(79, 29, 26, .18);
line-height: 1.2;
}
.profile-current-genealogy span {
color: rgba(255, 248, 230, .72);
font-size: 11px;
font-weight: 700;
}
.profile-current-genealogy strong {
overflow: hidden;
color: #fff8e6;
font-size: 14px;
text-overflow: ellipsis;
white-space: nowrap;
}
.profile-mobile-menu-toggle,
.profile-mobile-menu,
.site-menu-toggle,
.site-mobile-menu {
display: none;
}
.btn { .btn {
min-width: 118px; min-width: 118px;
height: 44px; height: 44px;
@@ -230,6 +323,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;
@@ -518,6 +623,21 @@ textarea {
gap: 14px; gap: 14px;
} }
.auth-card .auth-field {
display: grid;
gap: 8px;
color: var(--ink);
font-weight: 800;
}
.auth-card .auth-field b {
color: var(--red);
}
.auth-card .auth-form-status:empty {
display: none;
}
.auth-card .input { .auth-card .input {
height: 50px; height: 50px;
background: #fffaf1; background: #fffaf1;
@@ -604,8 +724,9 @@ textarea {
opacity: .72; opacity: .72;
} }
/* 接口列表为空时的统一占位样式 */ /* 业务请求的加载、空数据、失败和无权限状态 */
.api-empty { .api-empty,
.api-state {
padding: 22px; padding: 22px;
border: 1px dashed var(--line); border: 1px dashed var(--line);
border-radius: 14px; border-radius: 14px;
@@ -615,6 +736,17 @@ textarea {
font-weight: 800; font-weight: 800;
} }
.api-state--loading {
color: var(--muted);
}
.api-state--error,
.api-state--forbidden {
border-color: rgba(160, 50, 45, .28);
color: var(--red);
background: rgba(160, 50, 45, .06);
}
.footer { .footer {
padding: 60px 0 36px; padding: 60px 0 36px;
color: rgba(255, 255, 255, .78); color: rgba(255, 255, 255, .78);
@@ -790,11 +922,187 @@ textarea {
background: #fff4ef; background: #fff4ef;
} }
@media (max-width: 1320px) {
.page-profile .nav-links,
.page-profile-module .nav-links {
display: none;
}
.profile-mobile-menu-toggle {
min-width: 58px;
height: 38px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 12px;
border: 1px solid rgba(255, 248, 230, .38);
border-radius: 10px;
color: #fff8e6;
background: rgba(79, 29, 26, .2);
font: inherit;
font-size: 13px;
font-weight: 800;
}
.page-profile .nav-actions,
.page-profile-module .nav-actions {
display: none;
}
.profile-mobile-menu.is-open {
display: block;
position: absolute;
top: 100%;
right: 0;
left: 0;
max-height: calc(100vh - 72px);
overflow-y: auto;
padding: 14px 0 18px;
border-bottom: 1px solid rgba(255, 253, 248, .24);
background: rgba(121, 42, 35, .99);
box-shadow: 0 22px 38px rgba(83, 32, 26, .28);
}
.profile-mobile-menu-inner {
display: grid;
grid-template-columns: 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;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
padding: 8px;
}
.profile-mobile-menu a,
.profile-mobile-menu button {
min-height: 46px;
display: flex;
align-items: center;
padding: 10px 14px;
border: 1px solid rgba(255, 248, 230, .16);
border-radius: 10px;
color: #fff8e6;
background: rgba(255, 253, 248, .08);
font: inherit;
font-weight: 800;
text-align: left;
}
.profile-mobile-menu button {
width: 100%;
cursor: pointer;
}
.profile-mobile-menu a.is-current {
border-color: rgba(255, 241, 202, .76);
color: #fff7d8;
background: rgba(255, 253, 248, .2);
}
}
@media (max-width: 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;
@@ -819,6 +1127,61 @@ textarea {
display: none; display: none;
} }
.page-profile-module .nav,
.page-profile .nav {
height: 70px;
gap: 10px;
}
.page-profile-module .brand,
.page-profile .brand {
padding: 7px 10px;
}
.page-profile-module .brand-logo,
.page-profile .brand-logo {
width: 114px;
}
.page-profile-module .brand-logo-mark {
width: 38px;
height: 38px;
}
.page-profile-module .brand-text {
display: none;
}
.page-profile-module .nav-actions {
display: none;
}
.profile-current-genealogy {
max-width: 112px;
padding: 7px 9px;
}
.profile-current-genealogy span {
display: none;
}
.profile-current-genealogy strong {
font-size: 13px;
}
.profile-mobile-menu-toggle {
min-width: 50px;
padding: 0 9px;
}
.profile-mobile-menu-inner {
grid-template-columns: 1fr;
}
.site-mobile-menu-inner {
grid-template-columns: 1fr;
}
.inner-hero .container { .inner-hero .container {
padding: 46px 0 54px; padding: 46px 0 54px;
} }
@@ -851,11 +1214,220 @@ 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) {
*, *:not(:where(.page-home-portal, .page-home-portal *)),
*:before, *:not(:where(.page-home-portal, .page-home-portal *))::before,
*:after { *:not(:where(.page-home-portal, .page-home-portal *))::after {
animation-duration: .01ms !important; animation-duration: .01ms !important;
animation-iteration-count: 1 !important; animation-iteration-count: 1 !important;
scroll-behavior: auto !important; scroll-behavior: auto !important;
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

+169
View File
@@ -0,0 +1,169 @@
(function (root, factory) {
if (typeof module === 'object' && module.exports) {
module.exports = factory(root);
return;
}
root.AdminPermissionPages = factory(root);
if (root.document) {
root.document.addEventListener('DOMContentLoaded', function () {
root.AdminPermissionPages.init();
});
}
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
'use strict';
var documentRef = root.document;
var roleLabels = {
owner: '谱主',
admin: '管理员',
editor: '编辑',
member: '成员',
visitor: '访客'
};
function getApi() {
return root.GenealogyApi && root.GenealogyApi.defaultClient;
}
function query(selector) {
return documentRef ? documentRef.querySelector(selector) : null;
}
function normalizeId(value) {
var text;
if (typeof value === 'number' && !Number.isSafeInteger(value)) return '';
if (value === undefined || value === null) return '';
text = String(value).trim();
return /^[1-9][0-9]*$/.test(text) ? text : '';
}
function getCurrentGenealogyId(search) {
var source = search === undefined && root.location ? root.location.search : search;
var params;
var contextId;
if (search === undefined && root.ProfileUI && root.ProfileUI.getGenealogyId) {
contextId = normalizeId(root.ProfileUI.getGenealogyId());
if (contextId) return contextId;
}
params = new URLSearchParams(String(source || '').replace(/^\?/, ''));
return normalizeId(params.get('genealogyId'));
}
function normalizePermissionOverview(data, expectedGenealogyId) {
var source = data || {};
var genealogyId = normalizeId(source.genealogyId);
var roleType = String(source.roleType === undefined || source.roleType === null ? '' : source.roleType).trim();
var expectedId = normalizeId(expectedGenealogyId);
if (!genealogyId || !expectedId || genealogyId !== expectedId || !Object.prototype.hasOwnProperty.call(roleLabels, roleType)) {
return null;
}
return {
genealogyId: genealogyId,
roleType: roleType,
canManage: source.canManage === true,
canEditContent: source.canEditContent === true
};
}
function canMaintainContent(access) {
return Boolean(access && (access.canEditContent === true || access.canManage === true));
}
function escapeHtml(value) {
return String(value === undefined || value === null ? '' : value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function renderCurrentPermissions(access) {
if (!access) return '';
return '<div class="form-like admin-permission-current">' +
'<p><span>当前角色</span><b>' + escapeHtml(roleLabels[access.roleType]) + '</b></p>' +
'<p><span>成员角色维护</span><b>' + (access.canManage ? '可管理' : '不可管理') + '</b></p>' +
'<p><span>入谱审核</span><b>' + (access.canManage ? '可审核' : '不可审核') + '</b></p>' +
'<p><span>内容维护</span><b>' + (canMaintainContent(access) ? '可维护' : '不可维护') + '</b></p>' +
'</div><p class="module-meta">实际写入操作仍由服务端按当前角色与家谱权限校验。</p>';
}
function setState(type, message) {
var container = query('[data-admin-permission-current]');
if (!container) return;
if (root.ProfileUI && root.ProfileUI.setApiState) {
root.ProfileUI.setApiState(container, type, message);
return;
}
container.textContent = message;
}
function shouldRedirectToLogin(api, error) {
var status = error && (error.status || error.code);
return !api || !api.getToken || !api.getToken() || Number(status) === 401;
}
function redirectToLogin(api) {
if (api && api.clearToken) api.clearToken();
root.NavigationUtil.open('login.html');
}
function isForbidden(error) {
return Number(error && (error.status || error.code)) === 403;
}
async function loadPage() {
var api = getApi();
var genealogyId;
var overview;
var container;
if (shouldRedirectToLogin(api)) {
redirectToLogin(api);
return;
}
genealogyId = getCurrentGenealogyId();
if (!genealogyId) {
setState('empty', '请先选择家谱,再查看当前角色权限。');
return;
}
if (root.ProfileUI && root.ProfileUI.syncGenealogyContextLinks) root.ProfileUI.syncGenealogyContextLinks();
setState('loading', '正在核对当前家谱角色与权限…');
try {
overview = normalizePermissionOverview(await api.genealogyDetail(genealogyId), genealogyId);
if (!overview) throw new Error('家谱权限响应缺少稳定角色或能力字段');
container = query('[data-admin-permission-current]');
if (container) container.innerHTML = renderCurrentPermissions(overview);
} catch (error) {
if (shouldRedirectToLogin(api, error)) {
redirectToLogin(api);
return;
}
setState(
isForbidden(error) ? 'forbidden' : 'error',
isForbidden(error) ? '当前账号无权查看该家谱权限。' : '当前角色权限读取失败,请稍后重试。'
);
}
}
function init() {
if (!documentRef || !query('[data-admin-permission-page]')) return;
loadPage();
}
return {
getCurrentGenealogyId: getCurrentGenealogyId,
normalizePermissionOverview: normalizePermissionOverview,
canMaintainContent: canMaintainContent,
renderCurrentPermissions: renderCurrentPermissions,
shouldRedirectToLogin: shouldRedirectToLogin,
isForbidden: isForbidden,
init: init
};
});
+134 -56
View File
@@ -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),
@@ -295,14 +304,16 @@
function renderAlbumDetail(album) { function renderAlbumDetail(album) {
var actions = ''; var actions = '';
if (!album) return '<div class="api-empty">未找到该相册</div>'; if (!album) return '';
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) +
@@ -327,14 +338,17 @@
function renderPhotoList(data) { function renderPhotoList(data) {
var photos = Array.isArray(data) ? data.map(normalizeAlbumPhoto) : []; var photos = Array.isArray(data) ? data.map(normalizeAlbumPhoto) : [];
if (photos.some(function (photo) { return !photo; })) return '<div class="api-empty">照片响应字段不完整</div>'; if (photos.some(function (photo) { return !photo; })) return '';
if (!photos.length) return '<div class="api-empty">暂无照片</div>'; if (!photos.length) return '';
return photos.map(function (photo) { return photos.map(function (photo) {
var action = canEditContent var action = canEditContent
? '<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>';
@@ -354,9 +368,7 @@
function redirectUnauthorized(api, error) { function redirectUnauthorized(api, error) {
if (!shouldRedirectToLogin(api, error)) return false; if (!shouldRedirectToLogin(api, error)) return false;
if (api && api.clearToken) api.clearToken(); if (api && api.clearToken) api.clearToken();
if (!root.location) return true; root.NavigationUtil.open('login.html');
if (typeof root.location.replace === 'function') root.location.replace('login.html');
else root.location.href = 'login.html';
return true; return true;
} }
@@ -369,10 +381,23 @@
if (root.ProfileUI && root.ProfileUI.syncGenealogyContextLinks) root.ProfileUI.syncGenealogyContextLinks(); if (root.ProfileUI && root.ProfileUI.syncGenealogyContextLinks) root.ProfileUI.syncGenealogyContextLinks();
} }
function setStatus(selector, message) { function getApiStateType(error) {
return isForbidden(error) ? 'forbidden' : 'error';
}
function setStatus(selector, message, type) {
var target = query(selector); var target = query(selector);
if (target) target.textContent = message || ''; if (!target) return;
if (!message) {
target.innerHTML = '';
return;
}
if (type && root.ProfileUI && root.ProfileUI.setApiState) {
root.ProfileUI.setApiState(target, type, message);
return;
}
target.textContent = message;
} }
function setEditorEnabled(enabled, message) { function setEditorEnabled(enabled, message) {
@@ -397,12 +422,12 @@
function renderNoContext() { function renderNoContext() {
var message = '请先选择家谱,再查看或维护相册。'; var message = '请先选择家谱,再查看或维护相册。';
if (query('[data-album-list]')) query('[data-album-list]').innerHTML = '<div class="api-empty">' + message + '</div>'; setStatus('[data-album-list]', message, 'empty');
if (query('[data-album-detail]')) query('[data-album-detail]').innerHTML = '<div class="api-empty">当前没有家谱上下文。</div>'; setStatus('[data-album-detail]', '当前没有家谱上下文。', 'empty');
if (query('[data-album-photo-list]')) query('[data-album-photo-list]').innerHTML = '<div class="api-empty">当前没有家谱上下文。</div>'; setStatus('[data-album-photo-list]', '当前没有家谱上下文。', 'empty');
setEditorEnabled(false, message); setEditorEnabled(false, message);
setStatus('[data-album-form-status]', message); setStatus('[data-album-form-status]', message, 'forbidden');
setStatus('[data-album-photo-status]', message); setStatus('[data-album-photo-status]', message, 'forbidden');
} }
function requireGenealogyId() { function requireGenealogyId() {
@@ -412,10 +437,14 @@
return genealogyId; return genealogyId;
} }
function canEditAlbums(genealogy) {
return Boolean(genealogy && (genealogy.canEditContent === true || genealogy.canManage === true));
}
async function loadCapability(api, genealogyId) { async function loadCapability(api, genealogyId) {
var detail = await api.genealogyDetail(genealogyId); var detail = await api.genealogyDetail(genealogyId);
canEditContent = Boolean(detail && (detail.canEditContent || detail.canManage)); canEditContent = canEditAlbums(detail);
queryAll('[data-album-create-link], [data-album-editor-action], [data-album-photo-editor]').forEach(function (element) { queryAll('[data-album-create-link], [data-album-editor-action], [data-album-photo-editor]').forEach(function (element) {
element.hidden = !canEditContent; element.hidden = !canEditContent;
}); });
@@ -430,11 +459,11 @@
albums.forEach(function (album) { albumsById[album.albumId] = album; }); albums.forEach(function (album) { albumsById[album.albumId] = album; });
if (!container) return albums; if (!container) return albums;
if (!Array.isArray(data) || (data.length && !albums.length)) { if (!Array.isArray(data) || (data.length && !albums.length)) {
container.innerHTML = '<div class="api-empty">相册响应缺少稳定 AlbumVo 字段,请联系后端核对。</div>'; setStatus('[data-album-list]', '相册响应缺少稳定 AlbumVo 字段,请联系后端核对。', 'error');
return []; return [];
} }
if (!albums.length) { if (!albums.length) {
container.innerHTML = '<div class="api-empty">暂无相册</div>'; setStatus('[data-album-list]', '暂无相册', 'empty');
return []; return [];
} }
container.innerHTML = albums.map(renderAlbumRow).join(''); container.innerHTML = albums.map(renderAlbumRow).join('');
@@ -449,16 +478,17 @@
genealogyId = requireGenealogyId(); genealogyId = requireGenealogyId();
if (!genealogyId) return; if (!genealogyId) return;
syncLinks(); syncLinks();
setStatus('[data-album-list]', '正在加载相册…', 'loading');
try { try {
await loadCapability(api, genealogyId); await loadCapability(api, genealogyId);
renderAlbums(await api.albums(genealogyId)); renderAlbums(await api.albums(genealogyId));
} catch (error) { } catch (error) {
if (redirectUnauthorized(api, error)) return; if (redirectUnauthorized(api, error)) return;
if (query('[data-album-list]')) { setStatus(
query('[data-album-list]').innerHTML = '<div class="api-empty">' + '[data-album-list]',
(isForbidden(error) ? '当前账号无权查看该家谱的相册。' : '相册加载失败,请稍后重试。') + isForbidden(error) ? '当前账号无权查看该家谱的相册。' : '相册加载失败,请稍后重试。',
'</div>'; getApiStateType(error)
} );
showMessage(isForbidden(error) ? '当前账号无权查看该家谱的相册。' : (error.message || '相册加载失败')); showMessage(isForbidden(error) ? '当前账号无权查看该家谱的相册。' : (error.message || '相册加载失败'));
} }
} }
@@ -480,7 +510,6 @@
if (!album || album.status !== '0') throw new Error('停用相册无法通过当前 PC 接口重新读取或编辑'); if (!album || album.status !== '0') throw new Error('停用相册无法通过当前 PC 接口重新读取或编辑');
setFieldValue('[data-album-form]', 'albumName', album.albumName); setFieldValue('[data-album-form]', 'albumName', album.albumName);
setFieldValue('[data-album-form]', 'albumDesc', album.albumDesc); setFieldValue('[data-album-form]', 'albumDesc', album.albumDesc);
setFieldValue('[data-album-form]', 'coverOssId', album.coverOssId);
setFieldValue('[data-album-form]', 'sortOrder', album.sortOrder); setFieldValue('[data-album-form]', 'sortOrder', album.sortOrder);
setFieldValue('[data-album-form]', 'status', '0'); setFieldValue('[data-album-form]', 'status', '0');
} }
@@ -498,6 +527,7 @@
albumId = getCurrentAlbumId(); albumId = getCurrentAlbumId();
syncLinks(); syncLinks();
setEditorEnabled(false, '正在加载相册编辑信息…'); setEditorEnabled(false, '正在加载相册编辑信息…');
setStatus('[data-album-form-status]', '正在读取家谱权限…', 'loading');
try { try {
await loadCapability(api, genealogyId); await loadCapability(api, genealogyId);
if (!canEditContent) throw { status: 403, message: '当前账号无权维护该家谱的相册。' }; if (!canEditContent) throw { status: 403, message: '当前账号无权维护该家谱的相册。' };
@@ -506,28 +536,43 @@
album = findAlbumById(albums, albumId); album = findAlbumById(albums, albumId);
if (!album) throw new Error('相册列表未返回当前相册'); if (!album) throw new Error('相册列表未返回当前相册');
fillAlbumForm(album); fillAlbumForm(album);
var albumResponse = albums.find(function (entry) { return String(entry.albumId) === albumId; });
root.AttachmentEditor.setFiles('#albumCoverOssId', albumResponse.coverFile ? [albumResponse.coverFile] : []);
if (query('[data-album-editor-title]')) query('[data-album-editor-title]').textContent = '编辑相册'; if (query('[data-album-editor-title]')) query('[data-album-editor-title]').textContent = '编辑相册';
} }
setEditorEnabled(true); setEditorEnabled(true);
setStatus('[data-album-form-status]', '');
} catch (error) { } catch (error) {
if (redirectUnauthorized(api, error)) return; if (redirectUnauthorized(api, error)) return;
setEditorEnabled(false, isForbidden(error) ? '当前账号无权维护该家谱的相册。' : (error.message || '相册编辑信息加载失败')); setEditorEnabled(false, isForbidden(error) ? '当前账号无权维护该家谱的相册。' : (error.message || '相册编辑信息加载失败'));
setStatus('[data-album-form-status]', isForbidden(error) ? '当前账号无权维护该家谱的相册。' : (error.message || '相册编辑信息加载失败')); setStatus(
'[data-album-form-status]',
isForbidden(error) ? '当前账号无权维护该家谱的相册。' : (error.message || '相册编辑信息加载失败'),
getApiStateType(error)
);
} }
} }
async function loadPhotos(api, genealogyId, albumId) { async function loadPhotos(api, genealogyId, albumId) {
var data = await api.albumPhotos(genealogyId, albumId); var data;
var photos = normalizeAlbumPhotos(data); var photos;
var container = query('[data-album-photo-list]'); var container = query('[data-album-photo-list]');
setStatus('[data-album-photo-list]', '正在加载照片…', 'loading');
data = await api.albumPhotos(genealogyId, albumId);
photos = normalizeAlbumPhotos(data);
photosById = Object.create(null); photosById = Object.create(null);
photos.forEach(function (photo) { photosById[photo.photoId] = photo; }); photos.forEach(function (photo) { photosById[photo.photoId] = photo; });
if (container) { if (!container) return photos;
container.innerHTML = !Array.isArray(data) || (data.length && !photos.length) if (!Array.isArray(data) || (data.length && !photos.length)) {
? '<div class="api-empty">照片响应缺少稳定 AlbumPhotoVo 字段,请联系后端核对。</div>' setStatus('[data-album-photo-list]', '照片响应缺少稳定 AlbumPhotoVo 字段,请联系后端核对。', 'error');
: renderPhotoList(photos); return [];
} }
if (!photos.length) {
setStatus('[data-album-photo-list]', '暂无照片', 'empty');
return [];
}
container.innerHTML = renderPhotoList(photos);
return photos; return photos;
} }
@@ -541,10 +586,15 @@
genealogyId = requireGenealogyId(); genealogyId = requireGenealogyId();
albumId = getCurrentAlbumId(); albumId = getCurrentAlbumId();
if (!genealogyId || !albumId) { if (!genealogyId || !albumId) {
if (genealogyId && query('[data-album-detail]')) query('[data-album-detail]').innerHTML = '<div class="api-empty">缺少有效相册编号。</div>'; if (genealogyId) {
setStatus('[data-album-detail]', '缺少有效相册编号。', 'error');
setStatus('[data-album-photo-list]', '缺少有效相册编号。', 'empty');
}
return; return;
} }
syncLinks(); syncLinks();
setStatus('[data-album-detail]', '正在加载相册信息…', 'loading');
setStatus('[data-album-photo-list]', '正在加载照片…', 'loading');
try { try {
await loadCapability(api, genealogyId); await loadCapability(api, genealogyId);
albums = await api.albums(genealogyId); albums = await api.albums(genealogyId);
@@ -552,12 +602,31 @@
if (!currentAlbum) throw new Error('相册列表未返回当前相册'); if (!currentAlbum) throw new Error('相册列表未返回当前相册');
if (query('[data-album-detail]')) query('[data-album-detail]').innerHTML = renderAlbumDetail(currentAlbum); if (query('[data-album-detail]')) query('[data-album-detail]').innerHTML = renderAlbumDetail(currentAlbum);
if (query('[data-album-detail-title]')) query('[data-album-detail-title]').textContent = currentAlbum.albumName; if (query('[data-album-detail-title]')) query('[data-album-detail-title]').textContent = currentAlbum.albumName;
} catch (error) {
if (redirectUnauthorized(api, error)) return;
setStatus(
'[data-album-detail]',
isForbidden(error) ? '当前账号无权查看该相册。' : (error.message || '相册详情加载失败'),
getApiStateType(error)
);
setStatus(
'[data-album-photo-list]',
isForbidden(error) ? '当前账号无权查看该相册。' : '照片加载失败,请稍后重试。',
getApiStateType(error)
);
showMessage(isForbidden(error) ? '当前账号无权查看该相册。' : (error.message || '相册详情加载失败'));
return;
}
try {
await loadPhotos(api, genealogyId, albumId); await loadPhotos(api, genealogyId, albumId);
} catch (error) { } catch (error) {
if (redirectUnauthorized(api, error)) return; if (redirectUnauthorized(api, error)) return;
if (query('[data-album-detail]')) query('[data-album-detail]').innerHTML = '<div class="api-empty">' + setStatus(
(isForbidden(error) ? '当前账号无权查看该相册。' : escapeHtml(error.message || '相册详情加载失败')) + '</div>'; '[data-album-photo-list]',
showMessage(isForbidden(error) ? '当前账号无权查看该相册。' : (error.message || '相册详情加载失败')); isForbidden(error) ? '当前账号无权查看该相册照片。' : (error.message || '照片加载失败'),
getApiStateType(error)
);
showMessage(isForbidden(error) ? '当前账号无权查看该相册照片。' : (error.message || '照片加载失败'));
} }
} }
@@ -572,17 +641,17 @@
var savedId; var savedId;
var verified; var verified;
if (writePending || !genealogyId || redirectUnauthorized(api)) return; if (writePending || !canEditContent || !genealogyId || redirectUnauthorized(api)) return;
body = buildAlbumBody(getFormValues(form)); body = buildAlbumBody(getFormValues(form));
validation = validateAlbumBody(body); validation = validateAlbumBody(body);
if (validation) { if (validation) {
setStatus('[data-album-form-status]', validation); setStatus('[data-album-form-status]', validation, 'error');
return; return;
} }
delete body.invalidCoverOssId; delete body.invalidCoverOssId;
delete body.invalidSortOrder; delete body.invalidSortOrder;
setWritePending(true); setWritePending(true);
setStatus('[data-album-form-status]', '正在保存相册…'); setStatus('[data-album-form-status]', '正在保存相册…', 'loading');
try { try {
result = albumId result = albumId
? await api.updateAlbum(genealogyId, albumId, body) ? await api.updateAlbum(genealogyId, albumId, body)
@@ -592,10 +661,14 @@
if (!savedId) throw new Error('保存响应缺少 albumId'); if (!savedId) throw new Error('保存响应缺少 albumId');
verified = findAlbumById(await api.albums(genealogyId), savedId); verified = findAlbumById(await api.albums(genealogyId), savedId);
if (!verified) throw new Error('保存后相册列表未返回同一相册'); if (!verified) throw new Error('保存后相册列表未返回同一相册');
if (root.location) root.location.href = buildDetailUrl(genealogyId, savedId); root.NavigationUtil.open(buildDetailUrl(genealogyId, savedId));
} catch (error) { } catch (error) {
if (redirectUnauthorized(api, error)) return; if (redirectUnauthorized(api, error)) return;
setStatus('[data-album-form-status]', isForbidden(error) ? '当前账号无权保存该相册。' : (error.message || '相册保存失败')); setStatus(
'[data-album-form-status]',
isForbidden(error) ? '当前账号无权保存该相册。' : (error.message || '相册保存失败'),
getApiStateType(error)
);
} finally { } finally {
setWritePending(false); setWritePending(false);
} }
@@ -609,17 +682,17 @@
var validation; var validation;
var saved; var saved;
if (writePending || !genealogyId || !albumId || redirectUnauthorized(api)) return; if (writePending || !canEditContent || !genealogyId || !albumId || redirectUnauthorized(api)) return;
body = buildAlbumPhotoBody(getFormValues(form)); body = buildAlbumPhotoBody(getFormValues(form));
validation = validateAlbumPhotoBody(body); validation = validateAlbumPhotoBody(body);
if (validation) { if (validation) {
setStatus('[data-album-photo-status]', validation); setStatus('[data-album-photo-status]', validation, 'error');
return; return;
} }
delete body.invalidOssId; delete body.invalidOssId;
delete body.invalidSortOrder; delete body.invalidSortOrder;
setWritePending(true); setWritePending(true);
setStatus('[data-album-photo-status]', '正在添加照片…'); setStatus('[data-album-photo-status]', '正在添加照片…', 'loading');
try { try {
saved = normalizeAlbumPhoto(await api.createAlbumPhoto(genealogyId, albumId, body)); saved = normalizeAlbumPhoto(await api.createAlbumPhoto(genealogyId, albumId, body));
if (!saved || saved.albumId !== albumId) throw new Error('添加照片响应缺少稳定 photoId'); if (!saved || saved.albumId !== albumId) throw new Error('添加照片响应缺少稳定 photoId');
@@ -630,7 +703,11 @@
setStatus('[data-album-photo-status]', '照片已添加'); setStatus('[data-album-photo-status]', '照片已添加');
} catch (error) { } catch (error) {
if (redirectUnauthorized(api, error)) return; if (redirectUnauthorized(api, error)) return;
setStatus('[data-album-photo-status]', isForbidden(error) ? '当前账号无权添加照片。' : (error.message || '照片添加失败')); setStatus(
'[data-album-photo-status]',
isForbidden(error) ? '当前账号无权添加照片。' : (error.message || '照片添加失败'),
getApiStateType(error)
);
} finally { } finally {
setWritePending(false); setWritePending(false);
} }
@@ -641,13 +718,13 @@
var genealogyId = requireGenealogyId(); var genealogyId = requireGenealogyId();
var album = albumsById[albumId] || currentAlbum; var album = albumsById[albumId] || currentAlbum;
if (writePending || !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);
if (query('[data-album-page]')) await loadAlbums(); if (query('[data-album-page]')) await loadAlbums();
else if (root.location) root.location.href = buildListUrl(genealogyId); else root.NavigationUtil.open(buildListUrl(genealogyId));
} catch (error) { } catch (error) {
if (redirectUnauthorized(api, error)) return; if (redirectUnauthorized(api, error)) return;
showMessage(isForbidden(error) ? '当前账号无权删除该相册。' : (error.message || '相册删除失败')); showMessage(isForbidden(error) ? '当前账号无权删除该相册。' : (error.message || '相册删除失败'));
@@ -662,8 +739,8 @@
var albumId = getCurrentAlbumId(); var albumId = getCurrentAlbumId();
var photo = photosById[photoId]; var photo = photosById[photoId];
if (writePending || !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);
@@ -722,6 +799,7 @@
validateAlbumBody: validateAlbumBody, validateAlbumBody: validateAlbumBody,
buildAlbumPhotoBody: buildAlbumPhotoBody, buildAlbumPhotoBody: buildAlbumPhotoBody,
validateAlbumPhotoBody: validateAlbumPhotoBody, validateAlbumPhotoBody: validateAlbumPhotoBody,
canEditAlbums: canEditAlbums,
normalizeAlbum: normalizeAlbum, normalizeAlbum: normalizeAlbum,
normalizeAlbumPhoto: normalizeAlbumPhoto, normalizeAlbumPhoto: normalizeAlbumPhoto,
normalizeAlbums: normalizeAlbums, normalizeAlbums: normalizeAlbums,
+176
View File
@@ -0,0 +1,176 @@
(function (root, factory) {
if (typeof module === 'object' && module.exports) {
module.exports = factory(root);
return;
}
root.AppPromotionPages = factory(root);
if (root.document) {
root.document.addEventListener('DOMContentLoaded', function () {
root.AppPromotionPages.init();
});
}
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
'use strict';
var MediaDisplay = root.MediaDisplay || (typeof require === 'function' ? require('./media-display.js') : null);
function escapeHtml(value) {
return String(value === undefined || value === null ? '' : value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function normalizeId(value) {
var normalized;
if (typeof value === 'number' && !Number.isSafeInteger(value)) return '';
if (value === undefined || value === null) return '';
normalized = String(value).trim();
return /^[1-9][0-9]*$/.test(normalized) ? normalized : '';
}
function text(value) {
return value === undefined || value === null ? '' : String(value).trim();
}
function normalizeTargetUrl(value) {
var normalized = text(value);
var parsed;
if (!normalized) return '';
try {
parsed = new URL(normalized);
} catch (error) {
return '';
}
return parsed.protocol === 'http:' || parsed.protocol === 'https:' ? parsed.href : '';
}
function normalizePromotion(item) {
var source = item || {};
var promotionId = normalizeId(source.promotionId);
var promotionTitle = text(source.promotionTitle);
var coverFile = MediaDisplay && MediaDisplay.normalizeFileAccess(source.coverFile);
var promotion;
if (!promotionId || !promotionTitle || String(source.status) !== '0') return null;
if (source.coverFile !== undefined && source.coverFile !== null && !coverFile) return null;
promotion = {
promotionId: promotionId,
promotionTitle: promotionTitle,
promotionDesc: text(source.promotionDesc),
targetUrl: normalizeTargetUrl(source.targetUrl)
};
if (coverFile) promotion.coverFile = coverFile;
return promotion;
}
function normalizePromotionList(data) {
var items;
if (!Array.isArray(data)) return [];
items = data.map(normalizePromotion);
return items.some(function (item) { return !item; }) ? [] : items;
}
function renderPromotionCard(item) {
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>';
if (!item.targetUrl) {
return '<article class="promotion-card" data-promotion-id="' +
escapeHtml(item.promotionId) + '">' + content + '</article>';
}
return '<a class="promotion-card" data-promotion-id="' + escapeHtml(item.promotionId) +
'" href="' + escapeHtml(item.targetUrl) +
'" target="_blank" rel="noopener noreferrer">' + content + '</a>';
}
function renderPromotionList(data) {
var items = normalizePromotionList(data);
return items.map(renderPromotionCard).join('');
}
function setPromotionState(container, type, message) {
if (!container) return;
if (root.ProfileUI && root.ProfileUI.setApiState) {
root.ProfileUI.setApiState(container, type, message);
return;
}
container.textContent = message;
}
async function loadPromotions(api) {
var data;
var items;
if (!api || typeof api.promotions !== 'function') throw new Error('应用推广接口不可用');
data = await api.promotions({ platform: 'pc' });
items = normalizePromotionList(data);
if (!Array.isArray(data) || items.length !== data.length) {
throw new Error('推广列表响应无效');
}
return items;
}
function isUnauthorized(error) {
return Number(error && (error.status || error.code)) === 401;
}
function isForbidden(error) {
return Number(error && (error.status || error.code)) === 403;
}
async function init() {
var page = root.document && root.document.querySelector('[data-promotion-page]');
var list = root.document && root.document.querySelector('[data-promotion-list]');
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
if (!page || !list) return;
if (!api || !api.getToken || !api.getToken()) {
setPromotionState(list, 'forbidden', '登录后可查看应用推广。');
return;
}
setPromotionState(list, 'loading', '正在读取应用推广…');
try {
var items = await loadPromotions(api);
if (!items.length) {
setPromotionState(list, 'empty', '当前暂无应用推广。');
return;
}
list.innerHTML = items.map(renderPromotionCard).join('');
} catch (error) {
if (isUnauthorized(error)) {
if (api.clearToken) api.clearToken();
setPromotionState(list, 'forbidden', '登录后可查看应用推广。');
return;
}
setPromotionState(
list,
isForbidden(error) ? 'forbidden' : 'error',
isForbidden(error) ? '当前账号无权查看应用推广。' : '应用推广读取失败,请稍后重试。'
);
}
}
return {
normalizePromotion: normalizePromotion,
normalizePromotionList: normalizePromotionList,
normalizeTargetUrl: normalizeTargetUrl,
renderPromotionList: renderPromotionList,
setPromotionState: setPromotionState,
loadPromotions: loadPromotions,
isUnauthorized: isUnauthorized,
isForbidden: isForbidden,
init: init
};
});

Some files were not shown because too many files have changed in this diff Show More