Compare commits

..

27 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
fizzleaf fb1743aa2a feat(api): 完善家谱系统API客户端契约
- 实现家谱管理相关方法,包括创建、详情、概览、我的家谱和选项查询
- 添加家谱加入申请功能,支持申请、审核、取消和待审核列表操作
- 集成通知详情获取方法和通知ID安全验证机制
- 完善功德记录、谱文、相册、视频、祭祀活动的完整CRUD操作契约
- 实现家谱成员管理功能,包含成员列表、更新、移除和转让所有者操作
- 优化路径ID验证逻辑,拒绝不安全的数值ID并提供明确错误提示
- 更新测试用例以验证所有新增API方法的路径和请求体白名单机制
2026-07-29 16:57:27 +08:00
fizzleaf 59a72fb22b docs: plan genealogy creation first batch 2026-07-29 16:35:21 +08:00
fizzleaf 3c775e1359 docs: define remaining PC page integrations 2026-07-29 16:04:12 +08:00
fizzleaf ce4f05b60f test(api): 更新API客户端契约测试以符合YAML规范
- 更新登录响应模拟数据以匹配真实的AppLoginVo结构
- 添加认证客户端租户和授权字段验证
- 增加操作码枚举验证测试用例
- 移除对旧token别名的兼容性测试
- 修复测试用例中的短信验证码长度一致性问题
- 更新区域接口路径为PC专用路径
- 调整分片上传接口参数以符合新契约定义

refactor(api): 重构API客户端实现以严格遵循YAML契约

- 添加认证操作码和短信操作码枚举验证
- 实现严格的token响应解析只接受access_token字段
- 使用pickDefined函数过滤请求体中未定义的字段
- 重构认证接口参数映射以符合契约定义
- 更新区域接口路径为PC专用路径/genealogy/pc/region/*
- 优化分片上传接口参数结构与契约保持一致
- 添加操作码枚举验证函数toRequiredOperationCode
- 实现请求体字段选择性提取功能

feat(auth): 优化认证页面的验证码处理流程

- 添加takeCaptchaToken函数用于一次性获取验证码票据
- 更新短信验证码长度验证从4-6位改为精确4位
- 在登录和密码重置流程中集成验证码票据处理
- 修复验证码发送后票据清理逻辑
- 更新HTML模板中的验证码输入字段属性

chore(config): 提取常量配置并扩展配置对象结构

- 将客户端ID、租户ID和令牌键提取为常量
- 扩展配置对象返回客户端配置信息
- 更新配置测试用例以验证新增配置项

docs(planning): 更新PC接口对接规划文档

- 更新契约源说明以反映YAML冻结契约
- 添加YAML与在线Apifox复核对比内容
- 更新阻断项状态表格
- 修订登录响应token字段处理规范
- 更新文件上传和行政区划接口规范说明

style(profile): 优化相册管理页面的文件上传交互

- 将封面和照片OSS ID输入改为隐藏字段
- 添加文件选择标签以改善用户体验
- 移除手动输入OSS ID的选项保持界面简洁
2026-07-28 15:06:34 +08:00
rain 735a06e330 1 2026-07-27 19:46:40 +08:00
rain 817d85e117 现在有的接口对接测试完成 2026-07-25 19:20:01 +08:00
rain 8870136d1b 完成10% 2026-07-24 18:11:32 +08:00
rain 8716a68fdb 1 2026-07-24 11:49:15 +08:00
rain a6038be376 修改功,现在补齐页面 2026-07-11 21:36:26 +08:00
rain db397a7917 修改问题 2026-07-11 11:03:47 +08:00
rain edc0a58a54 fix: accept access token response 2026-07-11 09:41:32 +08:00
rain 8a18202878 docs: record auth profile flow 2026-07-11 09:36:02 +08:00
rain f60cc67749 fix: guard profile auth and logout 2026-07-11 09:32:06 +08:00
rain 8b896f4ba3 fix: complete auth token lifecycle 2026-07-11 09:29:17 +08:00
rain 68f72eb960 test: sync configured api address 2026-07-11 09:27:48 +08:00
rain 9769401b10 docs: define auth to profile flow 2026-07-11 09:15:50 +08:00
rain a549ca5ca3 修改注册功能问题 2026-07-10 17:08:22 +08:00
rain 66f3204349 fix: clear token after registration 2026-07-10 16:29:31 +08:00
rain 5d195cda45 docs: define registration token state 2026-07-10 16:17:56 +08:00
195 changed files with 48947 additions and 20506 deletions
+60
View File
@@ -0,0 +1,60 @@
######################################################################
# Build Tools
/node_modules/
.gradle
/build/
!gradle/wrapper/gradle-wrapper.jar
target/
!.mvn/wrapper/maven-wrapper.jar
######################################################################
# IDE
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### JRebel ###
rebel.xml
### NetBeans ###
nbproject/private/
build/*
nbbuild/
nbdist/
.nb-gradle/
######################################################################
# Others
*.log
*.log.gz
*.xml.versionsBackup
*.swp
!*/build/*.java
!*/build/*.html
!*/build/*.xml
.flattened-pom.xml
/.maven-home/.msp/incrementMark
robots.txt
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.
-7613
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">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">About Us</div>
<div class="eyebrow">关于我们</div>
<h1>用数字化方式守护家族记忆</h1>
<p class="lead">
代代相传面向家庭、宗亲会与地方文化组织,提供家谱创建、资料管理、文化展示和亲人协作工具。
@@ -91,17 +91,17 @@
</div>
<div>
<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>
<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>
<h4>联系我们</h4>
<p>客服电话:400-000-0000</p>
<p>邮箱:service@example.com</p>
<p>地址:中国 · 成都</p>
<h4>服务支持</h4>
<a href="help.html">帮助中心</a>
<a href="submit-ticket.html">提交问题</a>
<a href="about.html">关于平台</a>
</div>
</div>
<div class="footer-legal">
@@ -110,6 +110,7 @@
</div>
</div>
</footer>
<script src="config.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+13 -13
View File
@@ -36,11 +36,10 @@
<section class="inner-hero app-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Mobile App</div>
<div class="eyebrow">移动应用</div>
<h1>手机端随时记录,电脑端清晰浏览</h1>
<p class="lead">
家族成员可以在手机端上传照片、查看家谱、发布动态;PC
端适合浏览门户内容和公开家谱。
家族成员可以在手机端上传照片、查看家谱、发布动态;电脑端适合浏览门户内容和公开家谱。
</p>
<div class="hero-buttons">
<a class="btn primary magnetic" href="download.html">下载应用</a
@@ -71,7 +70,7 @@
<div class="section-head">
<h2>移动端能力</h2>
<p>
承接 APP 里的主要功能,让用户理解手机端适合日常记录和共同维护。
手机端适合随时记录家族资料、上传影像并与亲人共同维护。
</p>
</div>
<div class="grid-4">
@@ -98,7 +97,6 @@
</div>
</div>
</section>
<!-- 应用推广区域:内容来自 /genealogy/app/promotions 列表接口。 -->
<section class="section app-promotion-section">
<div class="container">
<div class="section-head">
@@ -106,7 +104,7 @@
<p>展示后台维护的应用推广、公告和下载引导内容。</p>
</div>
<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>
</section>
@@ -123,18 +121,18 @@
</div>
<div>
<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>
<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>
</div>
<div>
<h4>联系我们</h4>
<p>客服电话:400-000-0000</p>
<p>邮箱:service@example.com</p>
<p>地址:中国 · 成都</p>
<h4>服务支持</h4>
<a href="help.html">帮助中心</a>
<a href="submit-ticket.html">提交问题</a>
<a href="about.html">关于平台</a>
</div>
</div>
<div class="footer-legal">
@@ -143,12 +141,14 @@
</div>
</div>
</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/promotion-pages.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>
</html>
+14 -41
View File
@@ -3,11 +3,11 @@
<head>
<meta charset="utf-8" />
<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/article-detail.css" />
</head>
<body class="page-article-detail" data-article-detail-page>
<body class="page-article-detail" data-site-article-page>
<header class="site-header">
<div class="container nav">
<a class="brand magnetic" href="index.html"
@@ -32,39 +32,11 @@
</div>
</header>
<main>
<section class="inner-hero article-hero">
<div class="container">
<div class="hero-copy">
<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 class="hero-card article-cover-detail tilt-card">
<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>
<div class="container">
<div class="module-meta" data-site-article-status role="status" aria-live="polite"></div>
</div>
<main data-site-article-detail>
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取资讯详情…</div>
</main>
<footer class="footer">
@@ -86,14 +58,14 @@
<h4>文化内容</h4>
<a href="culture.html">家族文化</a
><a href="surname.html">姓氏百科</a
><a href="article-detail.html">家谱知识</a
><a href="news.html">新闻资讯</a
><a href="about.html">平台公告</a>
</div>
<div>
<h4>联系我们</h4>
<p>客服电话:400-000-0000</p>
<p>邮箱:service@example.com</p>
<p>地址:中国 · 成都</p>
<h4>服务支持</h4>
<a href="help.html">帮助中心</a>
<a href="submit-ticket.html">提交问题</a>
<a href="about.html">关于平台</a>
</div>
</div>
<div class="footer-legal">
@@ -102,12 +74,13 @@
</div>
</div>
</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/article-pages.js"></script>
<script src="public/js/page-effects.js"></script>
<script src="public/js/site-news-pages.js"></script>
</body>
</html>
+73
View File
@@ -0,0 +1,73 @@
<!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/css/site-info.css" />
</head>
<body>
<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 href="index.html">首页</a>
<a href="news.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>
</div>
</div>
</header>
<main>
<section class="site-info-hero">
<div class="container">
<div>
<div class="site-info-kicker">儿童隐私保护</div>
<h1>儿童个人信息处理规则</h1>
<p>用于说明儿童个人信息的处理边界、监护人权利和保护措施。</p>
</div>
<div class="site-info-mark"></div>
</div>
</section>
<section class="site-info-section">
<div class="container site-info-layout">
<article class="site-info-panel">
<div class="site-info-notice">
待法务确认:本页面仅建立儿童个人信息规则的访问入口,不构成已生效的规则。发布前必须由运营主体与法务确认适用年龄、监护人授权与完整文本。
</div>
<h2>正式文本应涵盖</h2>
<p>
儿童信息处理目的、必要性、监护人同意方式、信息访问更正删除渠道,以及发生个人信息安全事件时的告知安排。
</p>
</article>
<aside class="site-info-panel">
<h2>相关文档</h2>
<div class="site-info-links">
<a href="privacy.html">隐私政策</a>
<a href="terms.html">服务协议</a>
<a href="help.html">帮助中心</a>
</div>
</aside>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-legal">
<div>© 2026 代代相传. All rights reserved.</div>
<a href="privacy.html">隐私政策</a>
<a href="terms.html">服务协议</a>
</div>
</div>
</footer>
<script src="config.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+39 -5
View File
@@ -1,5 +1,5 @@
(function (root, factory) {
// 根配置负责运行环境接口基础地址。
// 根配置负责运行环境接口基础地址与全站跳转入口
if (typeof module === 'object' && module.exports) {
module.exports = factory;
return;
@@ -9,13 +9,42 @@
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
'use strict';
var CLIENT_ID = 'ced7e5f0498645c6ec642dcf450b036f';
var TENANT_ID = '000000';
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 中。
var ENVIRONMENTS = {
development: {
apiBaseUrl: 'http://test-genealogy-api.ddxcjp.cn'
apiBaseUrl: 'https://backend-api.ddxcjp.cn/'
},
production: {
// 后端尚未提供正式生产域名,暂时保持用户指定的测试地址。
apiBaseUrl: 'http://test-genealogy-api.ddxcjp.cn'
apiBaseUrl: 'https://backend-api.ddxcjp.cn/'
}
};
@@ -37,10 +66,15 @@
function getConfig() {
return {
environment: getEnvironment(),
apiBaseUrl: getApiBaseUrl()
apiBaseUrl: getApiBaseUrl(),
clientId: CLIENT_ID,
tenantId: TENANT_ID,
tokenKey: TOKEN_KEY
};
}
root.NavigationUtil = root.NavigationUtil || createNavigationUtil();
return {
getEnvironment: getEnvironment,
getApiBaseUrl: getApiBaseUrl,
+72 -32
View File
@@ -7,7 +7,7 @@
<link rel="stylesheet" href="public/css/public.css" />
<link rel="stylesheet" href="public/css/create-genealogy.css" />
</head>
<body class="page-create-genealogy">
<body class="page-create-genealogy" data-genealogy-create-page>
<header class="site-header">
<div class="container nav">
<a class="brand magnetic" href="index.html"
@@ -36,7 +36,7 @@
<section class="inner-hero create-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Start Genealogy</div>
<div class="eyebrow">创建家谱</div>
<h1>创建属于家族的数字空间</h1>
<p class="lead">
填写姓氏谱名、地区与简介,先建立家谱主页,再逐步邀请亲人共同完善资料。
@@ -59,35 +59,73 @@
<section class="section alt create-form-section">
<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>
<h2>家谱信息</h2>
<p class="form-note">
先填写基本资料,后续可以继续补充成员、相册和谱文
标有 * 的字段为必填。登录后即可创建,成功后会进入新家谱主页
</p>
<p class="form-note" data-genealogy-create-quota role="status" aria-live="polite">正在读取家谱额度…</p>
<div class="form-grid">
<input
class="input"
name="genealogyName"
placeholder="家谱名称,如:四川武胜汤氏族谱"
/>
<input class="input" name="surname" placeholder="主要姓氏" />
<!-- 地区选择器:最终行政区编码回填到 regionCode,供创建家谱接口使用。 -->
<div class="region-picker create-region-picker" data-region-picker>
<select class="input" name="provinceCode" data-region-level="province">
<option value="">请选择省</option>
</select>
<select class="input" name="cityCode" data-region-level="city">
<option value="">请选择市</option>
</select>
<select class="input" name="districtCode" data-region-level="district">
<option value="">请选择区县</option>
</select>
<input type="hidden" name="regionCode" data-region-code />
<label class="field" for="publicFamilyName">谱名 <b aria-hidden="true">*</b>
<input id="publicFamilyName" class="input" name="genealogyName" placeholder="例如:四川武胜汤氏族谱" required />
</label>
<label class="field" for="publicFamilySurname">主要姓氏 <b aria-hidden="true">*</b>
<input id="publicFamilySurname" class="input" name="surname" placeholder="例如:汤" required />
</label>
<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>
<label for="publicFamilyProvince">
<select id="publicFamilyProvince" class="input" name="provinceCode" data-region-level="province">
<option value="">请选择省</option>
</select>
</label>
<label for="publicFamilyCity">
<select id="publicFamilyCity" class="input" name="cityCode" data-region-level="city">
<option value="">请选择市</option>
</select>
</label>
<label for="publicFamilyDistrict">区县
<select id="publicFamilyDistrict" class="input" name="districtCode" data-region-level="district">
<option value="">请选择区县</option>
</select>
</label>
<input type="hidden" name="regionCode" data-region-code />
</div>
<div id="public-region-status" class="form-note" data-region-picker-status role="status" aria-live="polite"></div>
</div>
<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>
<input class="input" name="addressDetail" placeholder="家族所在地" />
<textarea name="intro" placeholder="家族简介"></textarea>
<button class="btn primary magnetic submit-btn" type="submit">提交创建</button>
</div>
</form>
</div>
@@ -105,17 +143,17 @@
</div>
<div>
<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>
<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>
<h4>联系我们</h4>
<p>客服电话:400-000-0000</p>
<p>邮箱:service@example.com</p>
<p>地址:中国 · 成都</p>
<h4>服务支持</h4>
<a href="help.html">帮助中心</a>
<a href="submit-ticket.html">提交问题</a>
<a href="about.html">关于平台</a>
</div>
</div>
<div class="footer-legal">
@@ -124,14 +162,16 @@
</div>
</div>
</footer>
<script src="public/js/jquery360.js"></script>
<script src="config.js"></script>
<script src="utils/StorageUtil.js"></script>
<script src="utils/FormUtil.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-common.js"></script>
<script src="public/js/region-pages.js"></script>
<script src="public/js/genealogy-pages.js"></script>
<script src="public/js/genealogy-entry-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+8 -7
View File
@@ -36,7 +36,7 @@
<section class="inner-hero culture-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Family Culture</div>
<div class="eyebrow">家族文化</div>
<h1>让家风、族训与故事被认真保存</h1>
<p class="lead">
家族文化承接新闻、知识、公告和故事,让谱文之外的记忆也能被阅读、分享和传承。
@@ -104,17 +104,17 @@
</div>
<div>
<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>
<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>
<h4>联系我们</h4>
<p>客服电话:400-000-0000</p>
<p>邮箱:service@example.com</p>
<p>地址:中国 · 成都</p>
<h4>服务支持</h4>
<a href="help.html">帮助中心</a>
<a href="submit-ticket.html">提交问题</a>
<a href="about.html">关于平台</a>
</div>
</div>
<div class="footer-legal">
@@ -123,6 +123,7 @@
</div>
</div>
</footer>
<script src="config.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
-102
View File
@@ -1,102 +0,0 @@
# 接口对接规划与进度记录
## 基本信息
- 项目路径:`C:\Users\Administrator\Desktop\job\家谱`
- 接口文档:`APP.openapi.json`
- 记录创建时间:2026-07-09 11:51:16 +08:00
- 执行范围:先做核心闭环页面接口对接,再按页面模块继续扩展。
- 当前假设:接口文档以 `/genealogy/app` 为主,PC/H5 页面先复用这些用户侧接口。
## 约束摘要
- 先规划再实现,所有执行进度需要记录年月日时间。
- 改动要小,只触碰接口对接必需文件。
- 新接口契约由共享 API 客户端统一维护,页面脚本只调用封装方法。
- 完成后做最小可验证检查;不能验证的地方要明确说明。
- 样式尽量写入 `public.css` 或页面对应 CSS 文件,通过 class 控制,不用 JS 注入样式。
- 新增 HTML、JS、CSS 注释统一使用中文。
- CSS 结构先公共后页面:`public.css` 放全站公共组件,页面 CSS 只放本页面差异样式。
- 个人中心模块页优先复用 `profile-module.css`,只有页面独有样式再新增或修改对应页面 CSS。
- 页面专用 JS 放 `public/js`,命名跟页面或模块对应;公告详情页如需 JS 使用 `public/js/notice-detail.js`
- 业务代码不压缩,保持展开、可读;需要解释意图、边界或接口字段映射的位置使用中文注释。
- 第三方库、压缩库、已存在乱码文件不做无关重写,避免引入额外风险。
## 核心闭环范围
1. 共享接口层
- 新增统一 API 客户端,负责 `baseUrl``clientid``tenantId`、token 存取、请求头、响应解包和错误提示。
- 默认 `clientid` 使用接口文档示例:`ced7e5f0498645c6ec642dcf450b036f`
- 默认 `tenantId` 使用接口文档示例:`000000`
- API 基础地址从 `window.GENEALOGY_API_BASE_URL``localStorage.genealogy_api_base_url` 读取,未配置时使用同域相对路径。
- 浏览器端公共脚本放 `public/js``utils` 目前只适合作为纯工具类目录,不能作为页面脚本入口。
- 页面专用 JS 按页面或模块放 `public/js`,例如认证模块用 `auth-pages.js`,公告详情页如需脚本则用 `notice-detail.js`
2. 登录注册找回
- `login.html` -> `POST /genealogy/app/auth/login`
- `register.html` -> `POST /genealogy/app/auth/register`
- `forgot-password.html` -> `POST /genealogy/app/auth/sms/code``PUT /genealogy/app/auth/password/reset`
- 密码按文档要求提交 32 位 MD5,复用已有 `public/js/md5.js`
3. 家谱核心
- `create-genealogy.html` -> `POST /genealogy/app/genealogies`
- `profile-families.html` -> `GET /genealogy/app/genealogies/mine`
- `plaza.html` -> `GET /genealogy/app/genealogies/public`
- `family-detail.html` -> `GET /genealogy/app/genealogies/{genealogyId}`
4. 个人资料与反馈
- `profile.html``profile-data.html` -> `GET /genealogy/app/auth/profile`
- `profile-feedback.html``submit-ticket.html` -> `GET/POST /genealogy/app/feedback`
5. 拦截处理
- `public/js/page-effects.js` 当前会拦截登录、注册、创建家谱、个人中心页面并显示“功能待开发”。
- 对已接入接口的页面移除该拦截,保留未接入页面的提示。
## 验证计划
1. 新增共享 API 客户端测试,先确认测试失败,再实现通过。
2. 检查核心页面均加载共享脚本和页面脚本。
3. 用静态结构检查确认接口路径、表单字段、脚本引用存在。
4. 运行语法检查或 Node 烟测,确保 JS 可加载、核心方法可调用。
5. 检查新增样式是否位于 CSS 文件,不通过 JS 注入。
## 进度日志
- 2026-07-09 11:47:20 +08:00:已读取 `AGENTS.md`、项目目录、`APP.openapi.json`,确认项目是静态 HTML + jQuery/Layui,接口文档主要为 `/genealogy/app`
- 2026-07-09 11:51:16 +08:00:用户确认按核心闭环方案执行,创建本规划与进度记录。
- 2026-07-09 11:52:53 +08:00:新增 `tests/api-client.test.js`,先运行 `node tests\api-client.test.js`,确认失败原因为缺少 `public/js/api-client.js`,进入共享 API 客户端实现。
- 2026-07-09 11:54:00 +08:00:用户补充要求:尽量不用 JS 注入样式,样式写在公共或页面 CSS 中并通过 class 查找;新增 HTML/JS/CSS 注释使用中文。
- 2026-07-09 11:57:19 +08:00:复查项目结构,确认 HTML 当前统一引用 `public/js``utils/DateUtil.js` 未被页面引用且存在乱码/语法风险;撤回过宽的 `api-pages.js` 方案,改为共享 `api-client.js` + 按模块/页面拆分脚本。
- 2026-07-09 11:59:22 +08:00:用户补充 CSS 架构要求:抽出可复用公共样式,每个页面 CSS 只写差异样式;将 CSS 结构整理加入接口接入前置约束。
- 2026-07-09 12:06:46 +08:00:完成认证模块第一步:新增 `public/js/auth-pages.js``tests/auth-pages.test.js``login.html``register.html``forgot-password.html` 改为真实表单;认证页公共 CSS 抽到 `public.css`,页面 CSS 只保留差异;重写 `page-effects.js`,移除“功能待开发”拦截和 JS 注入样式。
- 2026-07-09 12:10:54 +08:00:完成家谱核心第一步:新增 `public/js/genealogy-pages.js``tests/genealogy-pages.test.js``create-genealogy.html` 接入创建家谱接口;`plaza.html` 接入公开家谱列表;`profile-families.html` 接入我的家谱列表。验证:`node tests\api-client.test.js``node tests\auth-pages.test.js``node tests\genealogy-pages.test.js` 均通过;`node --check` 检查新增业务 JS 均通过;新增业务 JS 未发现 `createElement("style")``style.textContent` 注入样式。
- 2026-07-09 12:13:36 +08:00:用户补充可读性要求:现有该注释的位置补中文注释,代码不要压缩。执行范围限定为业务代码和本次维护的 CSS,第三方压缩库不改。
- 2026-07-09 12:16:27 +08:00:已给本次新增/重写业务代码补中文注释,覆盖 `api-client.js``auth-pages.js``genealogy-pages.js``page-effects.js``public.css` 的关键逻辑/样式区块;代码保持展开可读。验证:三组 Node 测试通过,四个业务 JS `node --check` 通过,业务 JS 未发现样式注入写法。
- 2026-07-09 12:23:36 +08:00:开始下一阶段:个人资料读取展示与反馈提交/历史反馈列表。范围:`profile.html``profile-data.html``profile-feedback.html``submit-ticket.html`;先写测试,再实现模块脚本和页面接入。
- 2026-07-09 12:28:30 +08:00:完成个人资料与反馈阶段:新增 `public/js/profile-pages.js``public/js/feedback-pages.js``tests/profile-pages.test.js``tests/feedback-pages.test.js``profile.html``profile-data.html` 接入当前用户资料读取展示;`profile-feedback.html``submit-ticket.html` 接入反馈提交,`profile-feedback.html` 接入历史反馈列表。验证:五组 Node 测试通过,六个业务 JS `node --check` 通过,业务 JS 未发现样式注入写法。
- 2026-07-09 12:31:51 +08:00:按用户要求重新验证第一阶段和第二阶段。第一阶段 API 客户端、认证页、家谱核心页测试均重新通过;第二阶段个人资料、反馈模块测试重新通过。复验命令包括 `node tests\api-client.test.js``node tests\auth-pages.test.js``node tests\genealogy-pages.test.js``node tests\profile-pages.test.js``node tests\feedback-pages.test.js`,以及六个业务脚本的 `node --check`。同时重新检查页面脚本引用、`data-*` 挂载点、业务 JS 样式注入写法和 `TODO/TBD/待实现/压缩` 标记,未发现异常。
- 2026-07-09 12:38:13 +08:00:继续推进家谱详情与加入申请阶段。`family-detail.html` 接入 `GET /genealogy/app/genealogies/{genealogyId}`,通过 `data-family-*` 挂载标题、简介、统计和家谱信息,并把“申请加入”链接带上家谱 ID;`join-genealogy.html` 改为真实申请表单,接入 `POST /genealogy/app/genealogies/{genealogyId}/join-applies`。新增/更新 `api-client.js``genealogy-pages.js` 中的申请加入、详情渲染、表单提交逻辑,并在对应 CSS 中补中文注释和页面差异样式。验证:五组 Node 测试全部通过,六个业务 JS `node --check` 全部通过,页面脚本引用与 `data-*` 挂载点检查通过,业务 JS 未发现样式注入写法,核心文件未发现 `TODO/TBD/待实现/压缩` 标记。
- 2026-07-09 12:40:15 +08:00:根据接口 schema 补充 `joinMode` 枚举中文展示映射:`0` 显示为“关闭加入”、`1` 显示为“审核加入”、`2` 显示为“邀请码加入”,避免详情页直接显示数字。重新验证五组 Node 测试、六个业务 JS 语法检查、详情页/加入页挂载点、业务 JS 样式注入检查和核心文件遗留标记检查,结果均正常。
- 2026-07-09 12:45:24 +08:00:继续推进消息中心阶段。`profile-messages.html` 接入 `GET /genealogy/app/notifications``POST /genealogy/app/notifications/{notificationId}/read``POST /genealogy/app/notifications/read-all`,新增 `public/js/notification-pages.js` 负责消息列表渲染、单条已读、全部已读和刷新;`api-client.js` 新增通知相关封装;`profile-module.css` 增加按钮型模块卡片和未读消息样式,保持样式写入 CSS。验证:六组 Node 测试全部通过,七个业务 JS `node --check` 全部通过,消息页脚本引用和 `data-*` 挂载点检查通过,业务 JS 未发现样式注入写法,核心文件未发现 `TODO/TBD/待实现/压缩` 标记。
- 2026-07-09 12:59:39 +08:00:按用户要求接入 `public/tac` 滑动验证到 `login.html``register.html``forgot-password.html`。依据 `APP.openapi.json``/captcha/require``/captcha/challenge``/captcha/verify` 字段实现 `public/js/captcha-pages.js` 适配层,将接口返回的 `captchaType/challengeId/payload` 转为 TAC 需要的 `type/id/backgroundImage/templateImage`,验证成功后把 `data.validToken` 写入当前表单隐藏字段;`auth-pages.js` 在登录、注册、发送短信验证码、重置密码前先执行滑动验证;`register.html` 原可见 `validToken` 输入改为昵称输入,避免和滑动验证隐藏字段冲突。三张认证页均引入 `public/tac/css/tac.css``public/tac/js/tac.min.js``public/js/captcha-pages.js`,并增加 `data-captcha-box` 挂载容器;`public.css` 增加认证页验证码容器样式。验证:七组 Node 测试全部通过,八个业务 JS `node --check` 全部通过,三张认证页 TAC 资源、隐藏 `validToken``data-captcha-box` 检查通过,自有业务 JS 未发现样式注入写法,核心文件未发现 `TODO/TBD/待实现/压缩` 标记;`git diff --check` 因当前目录不是 git 仓库无法执行。
- 2026-07-09 13:07:18 +08:00:按用户提醒重新以 `APP.openapi.json` 为依据梳理剩余 `/genealogy/app` 页面归属,优先推进接口和页面最匹配的帮助中心。`help.html` 接入 `GET /genealogy/app/help-articles` 帮助文章列表,并在展开条目时按需调用 `GET /genealogy/app/help-articles/{helpId}` 补充详情;新增 `public/js/help-pages.js``api-client.js` 新增 `helpArticles``helpArticleDetail` 封装;`help.css` 补帮助列表加载状态注释和样式。验证:八组 Node 测试全部通过,九个业务 JS `node --check` 全部通过,帮助页 `data-help-list``api-client.js``help-pages.js` 引用检查通过,自有业务 JS 未发现样式注入写法,核心文件未发现 `TODO/TBD/待实现/压缩` 标记。
- 2026-07-09 13:13:33 +08:00:继续按 `APP.openapi.json` 推进加入申请阶段。`profile-join-review.html` 接入 `GET /genealogy/app/genealogies/{genealogyId}/join-applies/pending` 待审核申请和 `PUT /genealogy/app/genealogies/{genealogyId}/join-applies/{applyId}/audit` 审核接口;`profile-join-family.html` 接入 `GET /genealogy/app/genealogies/join-applies/mine` 我的加入申请,并支持 `DELETE /genealogy/app/genealogies/join-applies/{applyId}` 撤销申请。新增 `public/js/join-apply-pages.js``api-client.js` 新增 `myJoinApplies``pendingJoinApplies``auditJoinApply``cancelJoinApply` 封装;`profile-module.css` 补加入申请行样式。验证:九组 Node 测试全部通过,十个业务 JS `node --check` 全部通过,两张页面脚本引用与 `data-join-apply-list` 挂载点检查通过,自有业务 JS 未发现样式注入写法,核心文件未发现 `TODO/TBD/待实现/压缩` 标记。
- 2026-07-09 13:19:07 +08:00:继续按 `APP.openapi.json` 推进字辈谱阶段。`profile-generation.html` 接入 `GET /genealogy/app/genealogies/{genealogyId}/generation-poems` 查询字辈谱、`POST /genealogy/app/genealogies/{genealogyId}/generation-poems` 新增字辈、`PUT /genealogy/app/genealogies/{genealogyId}/generation-poems/{poemId}` 修改字辈;新增 `public/js/generation-pages.js``api-client.js` 新增 `generationPoems``createGenerationPoem``updateGenerationPoem` 封装;`profile-module.css` 补字辈谱行样式。本轮暂不接批量预览/批量保存接口,避免没有页面入口时过度实现。验证:十组 Node 测试全部通过,十一个业务 JS `node --check` 全部通过,字辈页 `data-generation-list``data-generation-add``generation-pages.js` 引用检查通过,自有业务 JS 未发现样式注入写法,核心文件未发现 `TODO/TBD/待实现/压缩` 标记。
- 2026-07-09 13:21:31 +08:00:根据用户提醒停止无编号推进,新增 `docs/api-page-integration-tracker-2026-07-09.md` 作为后续唯一编号规划与状态追踪表。后续每个模块必须先在追踪表中标记“进行中”,完成并验证后再标记“已完成”。
- 2026-07-09 13:33:41 +08:00:完成 API-011 世系树与人物管理。`profile-tree.html` 接入世系树、人物分页搜索、人物详情、新增成员和父母/配偶/子女快捷关系添加;`profile-family-home.html` 接入世系树预览和家谱主页摘要;新增 `public/js/lineage-pages.js``api-client.js` 新增 `lineageTree``lineagePersons``lineagePersonsPage``lineagePersonOptions``lineagePersonDetail``createLineagePerson``updateLineagePerson``deleteLineagePerson``addLineageRelation` 封装;`profile-module.css` 补世系列表、搜索栏和树结构样式。验证:全部 Node 测试通过,十二个业务 JS `node --check` 通过,两张页面脚本引用与 `data-*` 挂载点检查通过,`public/js/api-client.js``public/js/lineage-pages.js` 未发现 `style` 样式注入片段。
- 2026-07-09 13:40:47 +08:00:完成 API-012 成员与家族管理。`profile-family-admin.html` 接入家谱概览和成员列表,支持成员权限更新和移除;`profile-admin-permissions.html` 接入成员选项、角色保存和所有者转让;`profile-invite.html``profile-share.html` 使用家谱概览生成邀请码/分享链接展示,不伪造接口文档中不存在的邀请生成接口。新增 `public/js/member-admin-pages.js``api-client.js` 新增 `genealogyOverview``genealogyMembers``genealogyMemberOptions``updateGenealogyMember``removeGenealogyMember``leaveGenealogy``transferGenealogyOwner` 封装;`profile-module.css` 补成员操作行样式。验证:全部 Node 测试通过,十三个业务 JS `node --check` 通过,四张页面脚本引用与 `data-*` 挂载点检查通过,`public/js/api-client.js``public/js/member-admin-pages.js` 未发现 `style` 样式注入片段。
- 2026-07-09 13:46:31 +08:00:完成 API-014 谱文与内容文章。`profile-article.html` 接入谱文列表;`profile-article-edit.html` 接入分类列表、谱文新增和修改;`article-detail.html` 接入谱文详情;`profile-content.html` 的最近内容先接谱文列表,其他内容类型按后续编号继续接。新增 `public/js/article-pages.js``api-client.js` 新增 `articleCategories``articles``articleDetail``createArticle``updateArticle` 封装;`profile-module.css` 补谱文行样式。验证:全部 Node 测试通过,十四个业务 JS `node --check` 通过,四张页面脚本引用与 `data-*` 挂载点检查通过,`public/js/api-client.js``public/js/article-pages.js` 未发现 `style` 样式注入片段。
- 2026-07-09 13:52:03 +08:00:完成 API-013 家族圈动态。`profile-feed.html` 接入动态分页列表、点赞和评论;`profile-feed-edit.html` 接入动态发布和修改。新增 `public/js/feed-pages.js``api-client.js` 新增 `feeds``feedsPage``feedDetail``createFeed``updateFeed``likeFeed``unlikeFeed``feedComments``feedCommentsPage``createFeedComment``deleteFeedComment` 封装;`profile-module.css` 补动态行样式。验证:全部 Node 测试通过,十五个业务 JS `node --check` 通过,两张页面脚本引用与 `data-*` 挂载点检查通过,`public/js/api-client.js``public/js/feed-pages.js` 未发现 `style` 样式注入片段。
- 2026-07-09 13:57:31 +08:00:完成 API-015 相册照片。`profile-album.html` 接入相册列表、新增/修改相册、照片列表和新增照片;`promo-album.html` 复用相册列表做宣传相册展示。新增 `public/js/album-pages.js``api-client.js` 新增 `albums``createAlbum``updateAlbum``albumPhotos``createAlbumPhoto` 封装;`profile-module.css` 补相册和照片行样式。验证:全部 Node 测试通过,十六个业务 JS `node --check` 通过,两张页面脚本引用与 `data-*` 挂载点检查通过,`public/js/api-client.js``public/js/album-pages.js` 未发现 `style` 样式注入片段。
- 2026-07-09 14:04:52 +08:00:完成 API-019 文件上传公共能力。`api-client.js` 接入单文件上传、文件引用绑定/释放、分片初始化、分片上传、分片完成接口;新增 `public/js/upload-pages.js` 统一处理文件选择、上传、OSS ID 回填和上传状态展示;`profile-article-edit.html` 接入谱文封面上传,`profile-feed-edit.html` 接入动态图片上传,`profile-album.html` 接入相册封面和照片上传;`profile-module.css` 补上传控件样式。验证:全部 Node 测试通过,十七个业务 JS `node --check` 通过,上传控件挂载点检查通过,`public/js/api-client.js``public/js/upload-pages.js` 未发现样式注入片段。
- 2026-07-09 14:12:27 +08:00:完成 API-016 祭祀、献礼与功德记录。`profile-gift.html` 接入祭祀/贺礼列表、献礼列表和新增献礼;`profile-gift-edit.html` 接入贺礼创建/修改和封面上传;`profile-merit.html` 接入功德记录列表;`profile-merit-edit.html` 接入新增功德记录。新增 `public/js/ceremony-pages.js``api-client.js` 新增 `ceremonies``ceremonyDetail``createCeremony``updateCeremony``ceremonyGifts``createCeremonyGift``meritRecords``createMeritRecord` 封装;`profile-module.css` 补贺礼、献礼和功德记录行样式。验证:全部 Node 测试通过,十八个业务 JS `node --check` 通过,四张页面脚本引用与 `data-*` 挂载点检查通过,`public/js/api-client.js``public/js/ceremony-pages.js` 未发现样式注入片段。
- 2026-07-09 14:18:15 +08:00:完成 API-017 成长记录。`profile-growth.html` 接入成长记录列表;`profile-growth-edit.html` 接入成长记录新增/修改和附件上传。新增 `public/js/growth-pages.js``api-client.js` 新增 `growthRecords``growthRecordDetail``createGrowthRecord``updateGrowthRecord` 封装;`profile-module.css` 补成长记录行样式。验证:全部 Node 测试通过,十九个业务 JS `node --check` 通过,两张页面脚本引用与 `data-*` 挂载点检查通过,`public/js/api-client.js``public/js/growth-pages.js` 未发现样式注入片段。
- 2026-07-09 14:24:10 +08:00:完成 API-018 备忘录。`profile-memo.html` 接入备忘录列表;`profile-memo-edit.html` 接入备忘录新增/修改和附件上传。新增 `public/js/memo-pages.js``api-client.js` 新增 `memos``memoDetail``createMemo``updateMemo` 封装;`profile-module.css` 补备忘录行样式。验证:全部 Node 测试通过,二十个业务 JS `node --check` 通过,两张页面脚本引用与 `data-*` 挂载点检查通过,`public/js/api-client.js``public/js/memo-pages.js` 未发现样式注入片段。
- 2026-07-09 14:31:29 +08:00:完成 API-020 会员套餐与订单创建。`profile-services.html` 接入会员套餐列表、会员订单创建表单和会员订单列表;新增 `public/js/vip-pages.js``api-client.js` 新增 `vipPackages``vipOrders``createVipOrder` 封装,`profile-module.css` 补会员套餐卡片和订单行样式。验证:`node tests\vip-pages.test.js``node tests\api-client.test.js`、全部 `tests/*.test.js`、业务 JS `node --check`、会员页挂载点/字段检查均通过;API-020 自有 JS 未发现样式注入片段;`git diff --check` 因当前目录不是 Git 仓库无法执行。
- 2026-07-09 14:37:25 +08:00:完成 API-021 推广/公告类内容。`app.html` 接入应用推广列表,`download.html` 接入下载推广入口,`notice-detail.html``/genealogy/app/promotions` 列表按 `id` 选择公告详情,未伪造接口文档不存在的详情接口。新增 `public/js/promotion-pages.js``api-client.js` 新增 `promotions` 封装,`app.css` 补推广卡片样式。验证:`node tests\promotion-pages.test.js``node tests\api-client.test.js`、全部 `tests/*.test.js`、业务 JS `node --check`、三张页面推广挂载点检查均通过;API-021 自有 JS 未发现样式注入片段;`git diff --check` 因当前目录不是 Git 仓库无法执行。
- 2026-07-09 14:42:47 +08:00:完成 API-022 账号安全。`profile-security.html` 接入修改密码、换绑手机号、短信登录校验、退出登录和账号注销表单;新增 `public/js/security-pages.js``api-client.js` 新增 `changePassword``changePhone``smsLogin``logout``deactivateAccount` 封装,`profile-module.css` 补安全表单和危险操作样式。验证:`node tests\security-pages.test.js``node tests\api-client.test.js`、全部 `tests/*.test.js`、业务 JS `node --check`、账号安全页挂载点检查均通过;API-022 自有 JS 未发现样式注入片段;`git diff --check` 因当前目录不是 Git 仓库无法执行。
- 2026-07-09 14:49:54 +08:00:完成 API-023 地区选择公共能力。`create-genealogy.html` 接入省/市/区选择器并回填 `regionCode``profile-data.html` 接入个人资料省市区更新表单;新增 `public/js/region-pages.js``api-client.js` 新增 `regionChildren``regionPath``regionSearch``regionDetail` 封装,`genealogy-pages.js` 去掉 `regionCode=000000` 兜底并改为提交前校验真实地区编码,两个 CSS 文件补地区选择器样式。验证:`node tests\region-pages.test.js``node tests\api-client.test.js`、全部 `tests/*.test.js`、业务 JS `node --check`、两张页面地区挂载点检查均通过;API-023 自有 JS 未发现样式注入片段;`git diff --check` 因当前目录不是 Git 仓库无法执行。
- 2026-07-09 14:50:47 +08:00:复查 API-024/API-025。`surname.html``surname-detail.html``search-result.html``genealogy.html``culture.html``about.html``index.html` 未发现明确同名接口,公开家谱接口也不支持搜索参数,因此标记暂缓;`profile-video.html``promo-video.html``profile-data-reminders.html` 暂无视频/资料提醒独立接口,标记暂缓;`profile-create-family.html` 可复用创建家谱能力,单独规划为 API-026。
- 2026-07-09 14:53:50 +08:00:完成 API-026 个人中心创建家谱页。`profile-create-family.html` 改为真实创建家谱表单,接入 `api-client.js``region-pages.js``genealogy-pages.js`,复用 `/genealogy/app/genealogies` 和地区选择公共能力;`profile-module.css` 补个人中心创建表单样式,`tests/genealogy-pages.test.js` 补真实 `regionCode` 断言。验证:`node tests\genealogy-pages.test.js``node tests\region-pages.test.js``node tests\api-client.test.js`、全部 `tests/*.test.js`、业务 JS `node --check`、页面挂载点检查均通过;API-026 自有 JS 未发现样式注入片段;`git diff --check` 因当前目录不是 Git 仓库无法执行。
- 2026-07-09 15:03:27 +08:00:完成 API-027 个人资料编辑提交。`profile-data.html` 增加真实个人资料编辑表单,`profile-pages.js` 增加 `ProfileUpdateBody` 构造、表单填充和 `PUT /genealogy/app/auth/profile` 提交,`profile-module.css` 补本页表单状态样式,`tests/profile-pages.test.js``tests/api-client.test.js` 补转换与接口契约测试。验证:先观察 `node tests\profile-pages.test.js` 因缺少 `buildProfileUpdateBody` 失败;实现后 `node tests\profile-pages.test.js``node tests\api-client.test.js`、全部 `tests/*.test.js`、业务 JS `node --check`、页面挂载点检查均通过,未发现样式注入片段和 TODO 类占位;`git diff --check` 因当前目录不是 Git 仓库无法执行。
- 2026-07-09 15:11:37 +08:00:完成 API-028 字辈批量预览与批量保存。`profile-generation.html` 增加批量字辈表单和预览区域,`api-client.js` 增加 `previewGenerationPoems``saveGenerationPoemsBatch``generation-pages.js` 增加 `GenerationPoemBatchBody` 构造、预览渲染和保存动作,`profile-module.css` 补批量面板样式,`tests/generation-pages.test.js``tests/api-client.test.js` 补批量接口测试。验证:先观察 `node tests\generation-pages.test.js` 因缺少 `buildGenerationBatchBody` 失败、`node tests\api-client.test.js` 因缺少 `previewGenerationPoems` 失败;实现后 `node tests\generation-pages.test.js``node tests\api-client.test.js`、全部 `tests/*.test.js`、业务 JS `node --check`、页面挂载点检查均通过,未发现样式注入片段和 TODO 类占位;`git diff --check` 因当前目录不是 Git 仓库无法执行。
@@ -1,114 +0,0 @@
# 接口对接编号规划与状态追踪
创建时间:2026-07-09 13:21:31 +08:00
流程修正时间:2026-07-09 13:21:31 +08:00
接口依据:`APP.openapi.json`
主进度记录:`docs/api-page-integration-2026-07-09.md`
## 执行规则
1. 后续不再无编号推进,所有接口对接必须先进入本追踪表。
2. 每一项开始前,先把状态从“待开始”改为“进行中”,并记录开始时间。
3. 每一项完成后,必须记录完成时间、修改文件、接口路径、验证命令或静态检查结果。
4. 每一项验证通过后,才能把状态改为“已完成”。
5. 若接口文档没有明确对应接口,状态标记为“待确认”或“暂缓”,不强行接入。
6. 样式继续遵守公共 `public.css` + 页面 CSS 的结构,业务 JS 不注入样式。
7. 新增或修改的 HTML、JS、CSS 注释使用中文,代码保持可读、不压缩。
## 状态说明
- 已完成:已按接口文档接入,并完成对应验证。
- 进行中:当前正在推进,未完成前不能开始下一个无关模块。
- 待开始:已规划,尚未改代码。
- 待确认:页面存在,但接口归属需要进一步确认。
- 暂缓:接口或页面入口不足,暂不实现,避免过度开发。
## 已完成回填
| 编号 | 状态 | 页面范围 | 接口范围 | 完成记录 |
| --- | --- | --- | --- | --- |
| API-000 | 已完成 | 全项目 | `APP.openapi.json``AGENTS.md`、项目结构 | 2026-07-09 11:47:20 +08:00 已读取约束、接口文档和目录结构。 |
| API-001 | 已完成 | 全项目公共请求 | 共享请求客户端、认证头、租户头、Token 存取 | 2026-07-09 11:52:53 +08:00 至 12:16:27 +08:00 完成 `public/js/api-client.js` 与测试。 |
| API-002 | 已完成 | `login.html``register.html``forgot-password.html` | `/genealogy/app/auth/login``/genealogy/app/auth/register``/genealogy/app/auth/sms/code``/genealogy/app/auth/password/reset``/captcha/require``/captcha/challenge``/captcha/verify` | 2026-07-09 12:06:46 +08:00 完成认证页;2026-07-09 12:59:39 +08:00 完成 TAC 滑动验证接入。 |
| API-003 | 已完成 | `create-genealogy.html``plaza.html``profile-families.html` | `/genealogy/app/genealogies``/genealogy/app/genealogies/public``/genealogy/app/genealogies/mine` | 2026-07-09 12:10:54 +08:00 完成创建家谱、公开家谱、我的家谱列表。 |
| API-004 | 已完成 | `family-detail.html``join-genealogy.html` | `/genealogy/app/genealogies/{genealogyId}``/genealogy/app/genealogies/{genealogyId}/join-applies` | 2026-07-09 12:38:13 +08:00 完成家谱详情与申请加入;2026-07-09 12:40:15 +08:00 补 `joinMode` 展示映射。 |
| API-005 | 已完成 | `profile.html``profile-data.html` | `/genealogy/app/auth/profile` | 2026-07-09 12:28:30 +08:00 完成个人资料读取展示。 |
| API-006 | 已完成 | `profile-feedback.html``submit-ticket.html` | `/genealogy/app/feedback` | 2026-07-09 12:28:30 +08:00 完成反馈提交与历史反馈。 |
| API-007 | 已完成 | `profile-messages.html` | `/genealogy/app/notifications``/genealogy/app/notifications/{notificationId}/read``/genealogy/app/notifications/read-all` | 2026-07-09 12:45:24 +08:00 完成消息中心。 |
| API-008 | 已完成 | `help.html` | `/genealogy/app/help-articles``/genealogy/app/help-articles/{helpId}` | 2026-07-09 13:07:18 +08:00 完成帮助中心。 |
| API-009 | 已完成 | `profile-join-review.html``profile-join-family.html` | `/genealogy/app/genealogies/{genealogyId}/join-applies/pending``/genealogy/app/genealogies/{genealogyId}/join-applies/{applyId}/audit``/genealogy/app/genealogies/join-applies/mine``/genealogy/app/genealogies/join-applies/{applyId}` | 2026-07-09 13:13:33 +08:00 完成入谱申请审核与我的申请。 |
| API-010 | 已完成 | `profile-generation.html` | `/genealogy/app/genealogies/{genealogyId}/generation-poems``/genealogy/app/genealogies/{genealogyId}/generation-poems/{poemId}` | 2026-07-09 13:19:07 +08:00 完成字辈谱查询、新增、修改;批量接口因页面无入口暂缓。 |
## 后续编号规划
| 编号 | 状态 | 页面范围 | 接口范围 | 计划与验证 |
| --- | --- | --- | --- | --- |
| API-011 | 已完成 | `profile-tree.html``profile-family-home.html` | `/genealogy/app/genealogies/{genealogyId}/lineage/tree``/genealogy/app/genealogies/{genealogyId}/lineage/persons``/genealogy/app/genealogies/{genealogyId}/lineage/persons/page``/genealogy/app/genealogies/{genealogyId}/lineage/persons/{personId}``/genealogy/app/genealogies/{genealogyId}/lineage/persons/{personId}/children``/parents``/siblings``/spouses` | 2026-07-09 13:25:03 +08:00 开始;2026-07-09 13:33:41 +08:00 完成。新增 `public/js/lineage-pages.js``tests/lineage-pages.test.js`;更新 `public/js/api-client.js``tests/api-client.test.js``profile-tree.html``profile-family-home.html``public/css/profile-module.css`。验证:全部 Node 测试通过,业务 JS 语法检查通过,页面挂载点检查通过,API-011 自有 JS 未发现样式注入片段。 |
| API-012 | 已完成 | `profile-family-admin.html``profile-admin-permissions.html``profile-invite.html``profile-share.html` | `/genealogy/app/genealogies/{genealogyId}/members``/genealogy/app/genealogies/{genealogyId}/members/{memberId}``/genealogy/app/genealogies/{genealogyId}/members/me``/genealogy/app/genealogies/{genealogyId}/members/options``/genealogy/app/genealogies/{genealogyId}/members/owner-transfer``/genealogy/app/genealogies/{genealogyId}/overview` | 2026-07-09 13:34:36 +08:00 开始;2026-07-09 13:40:47 +08:00 完成。新增 `public/js/member-admin-pages.js``tests/member-admin-pages.test.js`;更新 `public/js/api-client.js``tests/api-client.test.js`、四张成员/邀请页面和 `public/css/profile-module.css`。验证:全部 Node 测试通过,业务 JS 语法检查通过,页面挂载点检查通过,API-012 自有 JS 未发现样式注入片段。 |
| API-013 | 已完成 | `profile-feed.html``profile-feed-edit.html` | `/genealogy/app/genealogies/{genealogyId}/feeds``/genealogy/app/genealogies/{genealogyId}/feeds/page``/genealogy/app/genealogies/{genealogyId}/feeds/{feedId}``/genealogy/app/genealogies/{genealogyId}/feeds/{feedId}/likes`、评论接口 | 2026-07-09 13:47:29 +08:00 开始;2026-07-09 13:52:03 +08:00 完成。新增 `public/js/feed-pages.js``tests/feed-pages.test.js`;更新 `public/js/api-client.js``tests/api-client.test.js`、两张动态页面和 `public/css/profile-module.css`。验证:全部 Node 测试通过,业务 JS 语法检查通过,页面挂载点检查通过,API-013 自有 JS 未发现样式注入片段。 |
| API-014 | 已完成 | `profile-article.html``profile-article-edit.html``article-detail.html``profile-content.html` | `/genealogy/app/genealogies/{genealogyId}/article-categories``/genealogy/app/genealogies/{genealogyId}/articles``/genealogy/app/genealogies/{genealogyId}/articles/{articleId}` | 2026-07-09 13:41:44 +08:00 开始;2026-07-09 13:46:31 +08:00 完成。新增 `public/js/article-pages.js``tests/article-pages.test.js`;更新 `public/js/api-client.js``tests/api-client.test.js`、四张谱文/内容页面和 `public/css/profile-module.css`。验证:全部 Node 测试通过,业务 JS 语法检查通过,页面挂载点检查通过,API-014 自有 JS 未发现样式注入片段。 |
| API-015 | 已完成 | `profile-album.html``promo-album.html` | `/genealogy/app/genealogies/{genealogyId}/albums``/genealogy/app/genealogies/{genealogyId}/albums/{albumId}``/genealogy/app/genealogies/{genealogyId}/albums/{albumId}/photos` | 2026-07-09 13:52:58 +08:00 开始;2026-07-09 13:57:31 +08:00 完成。新增 `public/js/album-pages.js``tests/album-pages.test.js`;更新 `public/js/api-client.js``tests/api-client.test.js`、两张相册页面和 `public/css/profile-module.css`。验证:全部 Node 测试通过,业务 JS 语法检查通过,页面挂载点检查通过,API-015 自有 JS 未发现样式注入片段。 |
| API-016 | 已完成 | `profile-gift.html``profile-gift-edit.html``profile-merit.html``profile-merit-edit.html` | `/genealogy/app/genealogies/{genealogyId}/ceremonies``/genealogy/app/genealogies/{genealogyId}/ceremonies/{ceremonyId}``/genealogy/app/genealogies/{genealogyId}/ceremonies/{ceremonyId}/gifts``/genealogy/app/genealogies/{genealogyId}/merit-records` | 2026-07-09 14:05:57 +08:00 开始;2026-07-09 14:12:27 +08:00 完成。新增 `public/js/ceremony-pages.js``tests/ceremony-pages.test.js`;更新 `public/js/api-client.js``tests/api-client.test.js`、四张贺礼/功德页面和 `public/css/profile-module.css`。验证:全部 Node 测试通过,业务 JS 语法检查通过,页面挂载点检查通过,API-016 自有 JS 未发现样式注入片段。 |
| API-017 | 已完成 | `profile-growth.html``profile-growth-edit.html` | `/genealogy/app/genealogies/{genealogyId}/growth-records``/genealogy/app/genealogies/{genealogyId}/growth-records/{recordId}` | 2026-07-09 14:13:25 +08:00 开始;2026-07-09 14:18:15 +08:00 完成。新增 `public/js/growth-pages.js``tests/growth-pages.test.js`;更新 `public/js/api-client.js``tests/api-client.test.js`、两张成长记录页面和 `public/css/profile-module.css`。验证:全部 Node 测试通过,业务 JS 语法检查通过,页面挂载点检查通过,API-017 自有 JS 未发现样式注入片段。 |
| API-018 | 已完成 | `profile-memo.html``profile-memo-edit.html` | `/genealogy/app/genealogies/{genealogyId}/memos``/genealogy/app/genealogies/{genealogyId}/memos/{memoId}` | 2026-07-09 14:19:26 +08:00 开始;2026-07-09 14:24:10 +08:00 完成。新增 `public/js/memo-pages.js``tests/memo-pages.test.js`;更新 `public/js/api-client.js``tests/api-client.test.js`、两张备忘录页面和 `public/css/profile-module.css`。验证:全部 Node 测试通过,业务 JS 语法检查通过,页面挂载点检查通过,API-018 自有 JS 未发现样式注入片段。 |
| API-019 | 已完成 | 文章、动态、相册、成长记录等需要上传的页面 | `/genealogy/app/files/upload``/genealogy/app/files/reference``/genealogy/app/files/resumable/init``/genealogy/app/files/resumable/chunk``/genealogy/app/files/resumable/complete` | 2026-07-09 14:00:01 +08:00 开始;2026-07-09 14:04:52 +08:00 完成。新增 `public/js/upload-pages.js``tests/upload-pages.test.js`;更新 `public/js/api-client.js``tests/api-client.test.js``profile-article-edit.html``profile-feed-edit.html``profile-album.html``public/css/profile-module.css`。验证:全部 Node 测试通过,业务 JS 语法检查通过,上传控件挂载点检查通过,API-019 自有 JS 未发现样式注入片段。 |
| API-020 | 已完成 | `profile-services.html` | `/genealogy/app/vip/packages``/genealogy/app/vip/orders` | 2026-07-09 14:26:34 +08:00 开始;2026-07-09 14:31:29 +08:00 完成。新增 `public/js/vip-pages.js``tests/vip-pages.test.js`;更新 `public/js/api-client.js``tests/api-client.test.js``profile-services.html``public/css/profile-module.css`。验证:全部 Node 测试通过,业务 JS 语法检查通过,会员页挂载点和表单字段检查通过,API-020 自有 JS 未发现样式注入片段;`git diff --check` 因当前目录不是 Git 仓库无法执行。 |
| API-021 | 已完成 | `app.html``download.html``notice-detail.html` | `/genealogy/app/promotions` | 2026-07-09 14:33:01 +08:00 开始;2026-07-09 14:37:25 +08:00 完成。新增 `public/js/promotion-pages.js``tests/promotion-pages.test.js`;更新 `public/js/api-client.js``tests/api-client.test.js``app.html``download.html``notice-detail.html``public/css/app.css`。验证:全部 Node 测试通过,业务 JS 语法检查通过,三张页面推广挂载点检查通过,API-021 自有 JS 未发现样式注入片段;`git diff --check` 因当前目录不是 Git 仓库无法执行。 |
| API-022 | 已完成 | `profile-security.html` | `/genealogy/app/auth/password``/genealogy/app/auth/phone``/genealogy/app/auth/account/deactivate``/genealogy/app/auth/logout``/genealogy/app/auth/login/sms` | 2026-07-09 14:38:22 +08:00 开始;2026-07-09 14:42:47 +08:00 完成。新增 `public/js/security-pages.js``tests/security-pages.test.js`;更新 `public/js/api-client.js``tests/api-client.test.js``profile-security.html``public/css/profile-module.css`。验证:全部 Node 测试通过,业务 JS 语法检查通过,账号安全页表单挂载点检查通过,API-022 自有 JS 未发现样式注入片段;`git diff --check` 因当前目录不是 Git 仓库无法执行。 |
| API-023 | 已完成 | `create-genealogy.html``profile-data.html`、涉及地区选择的编辑页 | `/genealogy/region/children``/genealogy/region/search``/genealogy/region/{regionCode}``/genealogy/region/path/{regionCode}` | 2026-07-09 14:44:43 +08:00 开始;2026-07-09 14:49:54 +08:00 完成。新增 `public/js/region-pages.js``tests/region-pages.test.js`;更新 `public/js/api-client.js``public/js/genealogy-pages.js``tests/api-client.test.js``tests/genealogy-pages.test.js``create-genealogy.html``profile-data.html``public/css/create-genealogy.css``public/css/profile-module.css`。验证:全部 Node 测试通过,业务 JS 语法检查通过,两张页面地区选择挂载点检查通过,API-023 自有 JS 未发现样式注入片段;`git diff --check` 因当前目录不是 Git 仓库无法执行。 |
| API-024 | 暂缓 | `surname.html``surname-detail.html``search-result.html``genealogy.html``culture.html``about.html``index.html` | 当前接口文档未发现明确同名业务接口 | 2026-07-09 14:50:47 +08:00 复查。公开家谱接口不支持搜索参数,文章接口依赖 `genealogyId`,推广接口只有列表,不强行映射为姓氏百科或全站搜索。 |
| API-025 | 暂缓 | `profile-video.html``promo-video.html``profile-data-reminders.html` | 当前接口文档未发现明确视频、资料提醒独立接口 | 2026-07-09 14:50:47 +08:00 复查。视频和资料提醒没有独立接口,暂不伪造接口;`profile-create-family.html` 归入新增 API-026。 |
| API-026 | 已完成 | `profile-create-family.html` | `/genealogy/app/genealogies``/genealogy/region/children` | 2026-07-09 14:51:39 +08:00 开始;2026-07-09 14:53:50 +08:00 完成。更新 `profile-create-family.html``public/css/profile-module.css``tests/genealogy-pages.test.js`,复用 `public/js/api-client.js``public/js/region-pages.js``public/js/genealogy-pages.js`。验证:全部 Node 测试通过,业务 JS 语法检查通过,页面创建表单和地区选择挂载点检查通过,API-026 自有 JS 未发现样式注入片段;`git diff --check` 因当前目录不是 Git 仓库无法执行。 |
| API-027 | 已完成 | `profile-data.html` | `/genealogy/app/auth/profile` | 2026-07-09 14:57:51 +08:00 开始;2026-07-09 15:03:27 +08:00 完成。更新 `profile-data.html``public/js/profile-pages.js``public/css/profile-module.css``tests/profile-pages.test.js``tests/api-client.test.js`。补齐个人资料编辑提交,字段对应 `ProfileUpdateBody``nickName``avatarOssId``sex``birthday`;地区字段继续沿用 API-023。验证:已观察 `node tests\profile-pages.test.js` 因缺少 `buildProfileUpdateBody` 失败;实现后 `node tests\profile-pages.test.js``node tests\api-client.test.js`、全部 `tests/*.test.js`、业务 JS `node --check`、页面挂载点检查均通过;未发现样式注入片段和 TODO 类占位;`git diff --check` 因当前目录不是 Git 仓库无法执行。 |
| API-028 | 已完成 | `profile-generation.html` | `/genealogy/app/genealogies/{genealogyId}/generation-poems/batch/preview``/genealogy/app/genealogies/{genealogyId}/generation-poems/batch/save` | 2026-07-09 15:05:58 +08:00 开始;2026-07-09 15:11:37 +08:00 完成。更新 `profile-generation.html``public/js/api-client.js``public/js/generation-pages.js``public/css/profile-module.css``tests/generation-pages.test.js``tests/api-client.test.js`。补齐字辈批量预览和批量保存,字段对应 `GenerationPoemBatchBody``content``stopMissingOldGeneration`。验证:已观察 `node tests\generation-pages.test.js` 因缺少 `buildGenerationBatchBody` 失败,`node tests\api-client.test.js` 因缺少 `previewGenerationPoems` 失败;实现后 `node tests\generation-pages.test.js``node tests\api-client.test.js`、全部 `tests/*.test.js`、业务 JS `node --check`、页面挂载点检查均通过;未发现样式注入片段和 TODO 类占位;`git diff --check` 因当前目录不是 Git 仓库无法执行。 |
| API-029 | 已中止 | `profile-services.html` | `/genealogy/app/genealogies/options` | 2026-07-09 15:13:03 +08:00 开始;2026-07-09 15:14:58 +08:00 中止。原因:用户确认此前依据的 `APP.openapi.json` 不是 PC 接口文档,PC/H5 应改用 `genealogy-pc-openapi.yaml`,且接口地址使用 `https://test-genealogy-api.ddxcjp.cn`。本项不再继续按 APP 接口推进。 |
## 下一步执行顺序
1. API-011:世系树与人物管理。
2. API-012:成员与家族管理。
3. API-014:谱文与内容文章。
4. API-013:家族圈动态。
5. API-015:相册照片。
6. API-019:上传公共能力。
7. API-016 至 API-023:按页面入口完整度继续推进。
8. API-024、API-025:只在确认接口归属后推进。
## 本次流程修正记录
- 2026-07-09 13:21:31 +08:00:收到用户提醒,停止继续无编号推进;补充本编号追踪表,并要求后续每项先标记状态再实现,完成后再标记已完成。
- 2026-07-09 14:26:34 +08:00:开始执行 API-020,状态从“待开始”改为“进行中”。
- 2026-07-09 14:31:29 +08:00API-020 执行完成,状态从“进行中”改为“已完成”。
- 2026-07-09 14:33:01 +08:00:开始执行 API-021,状态从“待开始”改为“进行中”。
- 2026-07-09 14:37:25 +08:00API-021 执行完成,状态从“进行中”改为“已完成”。
- 2026-07-09 14:38:22 +08:00:开始执行 API-022,状态从“待开始”改为“进行中”。
- 2026-07-09 14:42:47 +08:00API-022 执行完成,状态从“进行中”改为“已完成”。
- 2026-07-09 14:44:43 +08:00:开始执行 API-023,状态从“待开始”改为“进行中”。
- 2026-07-09 14:49:54 +08:00API-023 执行完成,状态从“进行中”改为“已完成”。
- 2026-07-09 14:50:47 +08:00:复查 API-024/API-025,缺少明确接口的页面标记为“暂缓”,将 `profile-create-family.html` 单独规划为 API-026。
- 2026-07-09 14:51:39 +08:00:开始执行 API-026,状态从“待开始”改为“进行中”。
- 2026-07-09 14:53:50 +08:00API-026 执行完成,状态从“进行中”改为“已完成”。
- 2026-07-09 14:57:51 +08:00:开始执行 API-027,状态标记为“进行中”;按接口文档把 `profile-data.html` 的个人资料更新归到 `/genealogy/app/auth/profile` PUT,先写测试再实现。
- 2026-07-09 15:03:27 +08:00API-027 执行完成,状态从“进行中”改为“已完成”;完成个人资料编辑表单、提交逻辑、页面样式和测试验证。
- 2026-07-09 15:05:58 +08:00:开始执行 API-028,状态标记为“进行中”;按接口文档把 `profile-generation.html` 的批量字辈能力归到 `/generation-poems/batch/preview``/generation-poems/batch/save`
- 2026-07-09 15:11:37 +08:00API-028 执行完成,状态从“进行中”改为“已完成”;完成批量字辈预览、批量保存、页面入口、样式和测试验证。
- 2026-07-09 15:13:03 +08:00:开始执行 API-029,状态标记为“进行中”;把 `profile-services.html` 的家谱选择归到 `/genealogy/app/genealogies/options`
- 2026-07-09 15:14:58 +08:00:收到用户纠正,确认旧依据 `APP.openapi.json` 不适合作为 PC 页面接口文档;中止 API-029,后续改以 `genealogy-pc-openapi.yaml``https://test-genealogy-api.ddxcjp.cn` 重新规划。
- 2026-07-09 13:25:03 +08:00:开始执行 API-011,状态从“待开始”改为“进行中”。
- 2026-07-09 13:33:41 +08:00API-011 执行完成,状态从“进行中”改为“已完成”。
- 2026-07-09 13:34:36 +08:00:开始执行 API-012,状态从“待开始”改为“进行中”。
- 2026-07-09 13:40:47 +08:00API-012 执行完成,状态从“进行中”改为“已完成”。
- 2026-07-09 13:41:44 +08:00:开始执行 API-014,状态从“待开始”改为“进行中”。
- 2026-07-09 13:46:31 +08:00API-014 执行完成,状态从“进行中”改为“已完成”。
- 2026-07-09 13:47:29 +08:00:开始执行 API-013,状态从“待开始”改为“进行中”。
- 2026-07-09 13:52:03 +08:00API-013 执行完成,状态从“进行中”改为“已完成”。
- 2026-07-09 13:52:58 +08:00:开始执行 API-015,状态从“待开始”改为“进行中”。
- 2026-07-09 13:57:31 +08:00API-015 执行完成,状态从“进行中”改为“已完成”。
- 2026-07-09 14:00:01 +08:00:开始执行 API-019,状态从“待开始”改为“进行中”。
- 2026-07-09 14:04:52 +08:00API-019 执行完成,状态从“进行中”改为“已完成”。
- 2026-07-09 14:05:57 +08:00:开始执行 API-016,状态从“待开始”改为“进行中”。
- 2026-07-09 14:12:27 +08:00API-016 执行完成,状态从“进行中”改为“已完成”。
- 2026-07-09 14:13:25 +08:00:开始执行 API-017,状态从“待开始”改为“进行中”。
- 2026-07-09 14:18:15 +08:00API-017 执行完成,状态从“进行中”改为“已完成”。
- 2026-07-09 14:19:26 +08:00:开始执行 API-018,状态从“待开始”改为“进行中”。
- 2026-07-09 14:24:10 +08:00API-018 执行完成,状态从“进行中”改为“已完成”。
@@ -1,243 +0,0 @@
# PC 接口对接重新规划与状态追踪
创建时间:2026-07-09 15:14:58 +08:00
接口依据:`genealogy-pc-openapi.yaml`
接口基础地址:`http://test-genealogy-api.ddxcjp.cn`
旧依据处理:`APP.openapi.json` 只作为历史误用记录,不再作为 PC 页面继续对接依据。
旧进度记录:`docs/api-page-integration-tracker-2026-07-09.md`
## 执行规则
1. 所有 PC 接口对接必须先进入本追踪表,再改代码。
2. 每一项开始前先标记“进行中”,记录年月日时分秒和时区。
3. 每一项完成后记录完成时间、修改文件、接口路径、验证命令和结果。
4. 只有完成验证后才能标记“已完成”。
5. `genealogy-pc-openapi.yaml` 没有明确接口的页面,标记“暂缓”或“待确认”,不继续套用 APP 路径。
6. 样式继续写入 CSS 文件,业务 JS 不注入样式。
7. 新增或修改的 HTML、JS、CSS 注释使用中文,代码保持可读、不压缩。
## PC 文档当前接口范围
- 验证中心:`/captcha/require``/captcha/challenge``/captcha/verify``/auth/code`
- PC 认证:`/genealogy/pc/auth/register``/genealogy/pc/auth/login``/genealogy/pc/auth/login/sms``/genealogy/pc/auth/sms/code``/genealogy/pc/auth/profile``/genealogy/pc/auth/password``/genealogy/pc/auth/password/reset``/genealogy/pc/auth/phone``/genealogy/pc/auth/account/deactivate``/genealogy/pc/auth/logout`
- PC 文件:`/genealogy/pc/files/upload``/genealogy/pc/files/resumable/init``/genealogy/pc/files/resumable/chunk``/genealogy/pc/files/resumable/complete``/genealogy/pc/files/reference`
- 行政区划:`/genealogy/region/children``/genealogy/region/path/{regionCode}``/genealogy/region/search``/genealogy/region/{regionCode}`
## 最新 PC YAML 分析记录
分析时间:2026-07-09 15:36:16 +08:00
分析文件:`genealogy-pc-openapi.yaml`
1. `paths` 中实际存在 25 个接口操作,分为验证码、PC 认证、PC 文件、行政区划四类。
2. `servers` 仍是本地地址示例,项目实际对接基础地址继续按用户指定使用 `https://test-genealogy-api.ddxcjp.cn`
3. 登录后接口使用 `Authorization` 作为 token header,同时所有 PC 认证和文件接口都要求 header `clientid`;用户最新提供的 PC `clientid``ced7e5f0498645c6ec642dcf450b036f`,客户端 Key 为 `web_pc`
4. `components.tags``components.schemas/requestBodies` 中出现家谱、动态、世系、字辈等业务模型,但 `paths` 没有对应接口路径;实现时不能把这些 schema 当成可调用 PC 接口。
5. 验证码 schema 示例里仍出现 `APP_LOGIN``PC_LOGIN` 等文本示例;实际页面场景以后台配置为准,当前明确可用的 PC/H5 场景为 `WEB_H5_LOGIN``WEB_H5_REGISTER``WEB_H5_FORGOT_PASSWORD`
6. `ProfileUpdate` 示例出现 `regionCode/addressDetail`,但 schema 实际字段是 `provinceCode/cityCode/districtCode`,页面资料提交时应以 schema 字段为准。
7. `SmsLoginBody` 未声明 `validToken` 字段,但短信发送 `SmsCodeBody` 必填 `validToken`;短信登录流程应先通过验证码拿票据,再发短信验证码,最后用短信码登录。
8. 文件引用 `FileReferenceBody` 必填 `bizType/bizTable/bizId/bizField``ossId/ossIds/usageScene/usageName` 可选;但文章、动态、相册等业务主体接口未提供,文件引用只能作为公共上传能力,不代表主业务已可提交。
## 验证码与 TAC 调用记录
- 后台验证码配置截图中,PC/H5 当前明确可用场景是 `WEB_H5_LOGIN``WEB_H5_REGISTER``WEB_H5_FORGOT_PASSWORD`,客户端 Key 为 `web_pc`,验证服务为“天爱行为验证码”,验证码类型为“滑块验证码”。
- `ADMIN_LOGIN` 是后台管理登录,类型为旋转验证码,不归当前 PC/H5 页面使用。
- `APP_LOGIN``APP_REGISTER``APP_PHONE_CHANGE``APP_FORGOT_PASSWORD``APP_SMS_CODE``APP_ACCOUNT_DEACTIVATE` 的客户端 Key 为 `app`,不能继续作为 PC/H5 场景默认值。
- PC/H5 登录页调用链:
1. 页面加载 `public/tac/css/tac.css``public/tac/js/tac.min.js``public/js/captcha-pages.js`
2. 表单提供 `input[name="validToken"]` 隐藏字段和 `[data-captcha-box]` 容器。
3. `auth-pages.js` 在登录提交前调用 `CaptchaPages.ensureToken(form, { sceneCode: 'WEB_H5_LOGIN', subject: 手机号 })`
4. `captcha-pages.js` 先请求 `GET /captcha/require?tenantId=000000&clientId=ced7e5f0498645c6ec642dcf450b036f&sceneCode=WEB_H5_LOGIN&subject=手机号`
5. 如果返回 `required=false`,业务表单可继续提交;如果需要验证,则创建 `new CaptchaConfig(...)``new TAC(config, ...)`
6. TAC 内部请求 `POST /captcha/challenge` 获取 `captchaType/challengeId/payload`,适配为 TAC 需要的 `data.type/data.id/backgroundImage/templateImage`
7. 用户拖动完成后,TAC 请求 `POST /captcha/verify`,提交 `challengeId/providerCode/captchaType/payload.data.trackList`
8. 验证成功后从返回 `data.validToken` 取票据,写回表单隐藏字段 `validToken`,再提交 PC 登录接口。
- PC/H5 注册使用 `WEB_H5_REGISTER`,找回密码和找回密码短信验证码使用 `WEB_H5_FORGOT_PASSWORD`;换绑手机、注销账号暂未看到明确 PC/H5 场景编码,仍待后端补充后再接。
## 编号计划
| 编号 | 状态 | 页面范围 | 接口范围 | 计划与验证 |
| --- | --- | --- | --- | --- |
| PC-000 | 复核通过 | 全项目 | `genealogy-pc-openapi.yaml` | 历史记录保留;当前 PC YAML `paths` 再次核对通过,真实接口范围仍只有验证码、PC 认证、PC 文件和地区。 |
| PC-001 | 复核通过 | 根目录 `config.js` | 开发/生产环境、API 基础地址 | 当前 `config.js` 已收敛为环境和基础地址唯一入口;`node tests\config.test.js``node --check config.js` 通过。 |
| PC-002 | 复核通过 | `utils` 公共工具层 | Axios、请求封装、PC 接口客户端 | 当前公共层为 `utils/axios.js``utils/AxiosRequestUtil.js``utils/ApiClient.js`;旧 fetch 工具和旧 public API 客户端已删除。 |
| PC-003 | 本地复核通过 | 认证、资料、安全页面 | `/genealogy/pc/auth/*``/captcha/*` | 认证、资料、安全测试和脚本顺序检查均通过;真实登录业务仍依赖后端账户数据。 |
| PC-004 | 本地复核通过 | 文件上传页面 | `/genealogy/pc/files/*` | `node tests\upload-pages.test.js``node tests\api-client.test.js` 通过;文件上传仍需真实文件和后端授权进行端到端验证。 |
| PC-005 | 本地复核通过 | 地区选择页面 | `/genealogy/region/*` | `node tests\region-pages.test.js``node tests\profile-pages.test.js` 通过。 |
| PC-006 | 复核通过 | 页面覆盖矩阵 | PC YAML 覆盖边界 | 保持“公共能力不等于整页业务已对接”的边界,未覆盖业务继续受控失败。 |
| PC-007 | 复核通过 | 实施规划 | `genealogy-pc-openapi.yaml` | 当前计划与 YAML `paths` 一致,不使用旧 APP 路径 fallback。 |
| PC-008 | 复核通过 | PC YAML 未覆盖业务页面 | `PC_API_NOT_AVAILABLE` | `utils/ApiClient.js` 统一抛 `PC_API_NOT_AVAILABLE`,旧 `/genealogy/app/` 请求路径扫描无结果。 |
| PC-009 | 本地复核通过 | 登录、注册、找回密码 | PC auth、`/captcha/*` | 三页场景编码、表单契约、Axios 请求客户端和相关测试均通过;真实账号流程待业务数据联调。 |
| PC-010 | 复核通过(网关可达) | `config.js``utils/ApiClient.js` | 测试 API 基础地址 | 开发地址为 `http://test-genealogy-api.ddxcjp.cn`2026-07-10 10:02:48 +08:00 外部 `curl.exe -I` 返回 `HTTP 200 OK`。 |
| PC-011 | 本地复核通过 | 三张认证页 TAC 弹窗 | `public/tac``/captcha/*` | 页面级挂载、移动端约束和结构测试通过;未修改 `tac.min.js`。 |
## PC 页面覆盖矩阵草案
| 分类 | 页面/模块 | 当前判断 | 处理方式 |
| --- | --- | --- | --- |
| 完整可闭环 | `login.html` | PC YAML 有 `/genealogy/pc/auth/login`,后台验证码配置明确 `WEB_H5_LOGIN`,TAC 可完整串联。 | 可作为第一批执行页面。 |
| 接口存在但部分验证码场景待确认 | `register.html``forgot-password.html``profile-security.html` 中的换绑/注销相关动作 | PC YAML 有对应认证接口;注册场景为 `WEB_H5_REGISTER`,找回密码场景为 `WEB_H5_FORGOT_PASSWORD`,换绑和注销暂未提供 PC/H5 场景。 | 注册和找回密码可接 TAC;换绑、注销涉及验证码的提交先记录待确认,不能套用 `APP_*` 场景。 |
| 资料接口可对接 | `profile.html``profile-data.html` 中用户资料读取/修改 | PC YAML 有 `/genealogy/pc/auth/profile`。 | 只处理用户资料读写;页面里其他非资料业务不因此视为完成。 |
| 公共地区能力可保留 | `create-genealogy.html``profile-create-family.html``profile-data.html` 中地区选择控件 | PC YAML 有 `/genealogy/region/*`。 | 只保留和验证地区选择;创建家谱等主业务接口 PC YAML 未覆盖,仍待确认。 |
| 文件能力可切换 | 文章、动态、相册、成长、备忘、祭祀等页面里的上传控件 | PC YAML 有 `/genealogy/pc/files/*`。 | 只把上传、分片、文件引用切到 PC 文件接口;文章/动态/相册等主业务接口仍待确认。 |
| PC YAML 未覆盖 | 家谱主体、成员、字辈、世系、动态、文章、相册、祭祀、族务、VIP、通知、反馈、帮助、推广、姓氏、搜索等主业务页面 | 当前 PC YAML 没有这些业务路径。 | 不套用 APP 路径;等待补充 PC 接口后再逐页规划。 |
## 流程记录
- 2026-07-09 15:14:58 +08:00:收到用户纠正,停止 APP 接口继续推进;读取 `genealogy-pc-openapi.yaml`,确认 PC 路径前缀为 `/genealogy/pc`,基础地址改用 `https://test-genealogy-api.ddxcjp.cn`
- 2026-07-09 15:14:58 +08:00:创建本 PC 专用追踪表,PC-001 标记为“进行中”。
- 2026-07-09 15:17:57 +08:00:收到用户补充要求,重新规划 `config.js` 环境切换和 `utils` 公共 JS 分层;停止直接执行旧 PC-001,把 PC-001 到 PC-006 重排为待执行计划。
- 2026-07-09 15:24:02 +08:00:收到用户补充验证码要求;记录 `public/tac` 调用链,确认 PC/H5 登录使用 `WEB_H5_LOGIN`,其他未配置 PC/H5 场景的认证验证码先待确认。
- 2026-07-09 15:31:14 +08:00:根据用户质疑修正 PC-006;不再把“公共地区/文件接口可用”写成“整页可对接”,改为页面覆盖矩阵和缺口清单。
- 2026-07-09 15:34:26 +08:00:同步修正详细规划文件 `docs/superpowers/plans/2026-07-09-pc-api-replan.md` 的 Task 6,并将 PC-006 标记为“已修正”。
- 2026-07-09 15:41:40 +08:00:按最新 YAML 分析结果重写 `docs/superpowers/plans/2026-07-09-pc-api-replan.md`,新增 PC-007;明确旧 APP 路径不能作为 PC fallback,缺失业务接口统一降级为接口待确认。
- 2026-07-09 15:44:18 +08:00:开始执行 PC-001/PC-002,先按 TDD 创建 `config.js``utils` 公共工具测试,再实现最小配置与工具层。
- 2026-07-09 15:47:17 +08:00:完成 PC-001 和 PC-002 的公共工具层部分;`config.js` 位于根目录,`utils` 只放跨页面公共 JS,页面业务仍留在 `public/js`
- 2026-07-09 15:51:34 +08:00:完成 PC-002 的 `api-client.js` 切换;旧 APP 路径不再作为网络请求路径,未覆盖业务统一抛 `PC_API_NOT_AVAILABLE`
- 2026-07-09 15:55:28 +08:00PC-003 认证页验证码阶段完成;登录使用 `WEB_H5_LOGIN`,未确认 PC/H5 场景的注册和找回密码不调用验证中心。
- 2026-07-09 15:57:36 +08:00:收到用户补充截图,确认 PC `clientid``ced7e5f0498645c6ec642dcf450b036f`、客户端 Key 为 `web_pc`,新增注册场景 `WEB_H5_REGISTER` 和找回密码场景 `WEB_H5_FORGOT_PASSWORD`;同步修正配置、计划和认证页场景映射。
- 2026-07-09 16:00:22 +08:00PC-003 完成;认证页、资料页、安全页的 PC auth/captcha 接入和脚本顺序已验证。
- 2026-07-09 16:03:23 +08:00PC-004 完成;上传公共能力切换到 PC 文件接口,相关上传页已补齐公共层脚本顺序。
- 2026-07-09 16:05:12 +08:00PC-005 完成;地区选择只保留公共行政区划能力,创建家谱等主业务仍按 PC YAML 未覆盖处理。
- 2026-07-09 16:06:35 +08:00PC-008 完成;PC YAML 未覆盖业务不会继续发送旧 APP 请求,全量 Node 测试通过。
- 2026-07-09 16:07:42 +08:00:收尾验证完成;全量 `tests/*.test.js` 通过,`public/js` 非压缩业务脚本 `node --check` 通过,旧 `/genealogy/app/`、旧验证码场景、旧 PC clientId 扫描无输出;`git diff --check` 因当前目录不是 Git 仓库无法执行。
- 2026-07-09 16:09:55 +08:00:开始登录、注册、找回密码三页专项核对;按 PC YAML 的 `PasswordLoginBody``PasswordRegisterBody``SmsCodeBody``PasswordResetBody` 修正页面字段、输入类型、验证码场景声明和文案。
- 2026-07-09 16:12:10 +08:00:登录、注册、找回密码三页专项完成;三页表单已显式声明 `data-captcha-scene`,手机号输入改为 `type="tel"``inputmode="numeric"``maxlength="11"`,找回密码文案改为只支持注册手机号;`auth-pages.js` 优先读取页面 `data-captcha-scene`。验证:`node tests\auth-pages.test.js``node tests\captcha-pages.test.js``node tests\api-client.test.js` 通过;三页字段/脚本顺序扫描通过;旧邮箱文案和旧验证码场景扫描无输出。
- 2026-07-09 16:19:11 +08:00:根据用户反馈“接口文档少了很多东西”重新核对 `genealogy-pc-openapi.yaml` 的真实 `paths`;确认当前 YAML 只有验证中心、认证登录、文件上传、行政区划四类共 25 个操作,虽然声明了家谱、成员、字辈、世系、家族圈、文章、相册、祭祀、族务、VIP、消息、反馈等标签和模型,但没有对应可调用路径。结论:这些业务页面不能继续视为已对接,只能保留 `PC_API_NOT_AVAILABLE`,等待后端补充 PC 接口文档。
- 2026-07-09 16:48:05 +08:00:处理注册页滑动验证请求失败。确认失败不是注册字段问题,而是测试域名 HTTPS 握手失败;开发环境接口基础地址修正为 HTTP,并兼容清理旧 HTTPS 本地覆盖值。
- 2026-07-09 16:56:22 +08:00:处理 TAC 验证码显示位置问题。确认不是后端验证码类型问题,也不是 `tac.min.js` 被改;问题来自前端挂载点在表单中,导致 TAC 相对定位弹窗被卡片布局吞进去。
- 2026-07-09 17:02:35 +08:00TAC 弹窗挂载修复完成。三张认证页改为页面级固定遮罩容器承载 TAC,表单内部不再放验证码挂载点;新增结构测试防止回退。
- 2026-07-09 17:04:21 +08:00:复现用户反馈的 `503 Service Unavailable``curl.exe` 请求 `/captcha/require` 和域名根路径 `/` 均返回 `Server: SakuraFrp``503 Service Unavailable`,响应页提示检查 SakuraFrp 隧道节点、隧道配置、frpc 是否运行、隧道是否在线。结论:这是测试接口域名/内网穿透服务当前不可用,不是前端字段、TAC 样式或请求参数导致。
## PC-012 认证页 layui 提示与 TAC 弹窗样式修正
- 状态:本地复核通过
- 计划开始时间:2026-07-09 17:14:08 +08:00
- 计划文件:`docs/superpowers/plans/2026-07-09-auth-layer-message.md`
- 页面范围:`login.html``register.html``forgot-password.html`
- 接口范围:不新增接口,只修正 `/captcha/*` 流程中的前端提示方式和弹窗展示方式。
- 完成时间:2026-07-09 17:20:24 +08:00
- 修改文件:`login.html``register.html``forgot-password.html``utils/MessageUtil.js``public/js/auth-pages.js``public/js/captcha-pages.js``public/css/public.css``tests/auth-page-structure.test.js``tests/auth-message.test.js``docs/superpowers/plans/2026-07-09-auth-layer-message.md`
- 验证结果:
- `node tests\auth-page-structure.test.js` 通过。
- `node tests\auth-message.test.js` 通过。
- `node tests\auth-pages.test.js` 通过。
- `node tests\captcha-pages.test.js` 通过。
- `node --check public\js\auth-pages.js` 通过。
- `node --check public\js\captcha-pages.js` 通过。
- `node --check utils\MessageUtil.js` 通过。
- `Get-ChildItem -Path tests -Filter *.test.js | Sort-Object Name | ForEach-Object { node $_.FullName }` 全部通过。
- 剩余风险:测试域名当前仍可能返回 SakuraFrp `503 Service Unavailable`,这是后端/隧道可用性问题,不是本次提示和样式改动导致。
- 执行计划:
1. 已完成失败测试:`tests/auth-page-structure.test.js``tests/auth-message.test.js`
2. 已完成:三页加载 layui 样式和脚本。
3. 已完成:`auth-pages.js``captcha-pages.js` 提示统一走 `MessageUtil`/`layui.layer.msg`,不再使用浏览器原生 `alert`
4. 已完成:`public/css/public.css` 增加认证页 layer 提示皮肤。
5. 已完成:焦点测试、相关测试、语法检查和全量 Node 测试通过。
## PC-013 TAC 取消或请求失败后的遮罩清理
- 状态:本地复核通过
- 计划开始时间:2026-07-10 09:10:03 +08:00
- 计划文件:`docs/superpowers/plans/2026-07-10-tac-modal-cleanup.md`
- 页面范围:`login.html``register.html``forgot-password.html`
- 接口范围:`/captcha/require``/captcha/challenge``/captcha/verify`
- 根因记录:现有 `.auth-captcha-modal` 仅依赖 `:empty` 判断隐藏;TAC 在取消、CORS 失败或 503 失败后仍可能保留子节点,导致外层遮罩层不再为空从而不会消失。
- 处理计划:由 `captcha-pages.js` 显式管理 `is-active` 状态、`aria-hidden` 和残留 DOM;成功、取消、初始化异常、Promise 失败路径全部调用统一清理函数。
- 完成时间:2026-07-10 09:15:55 +08:00
- 修改文件:`public/js/captcha-pages.js``public/css/captcha-modal.css``login.html``register.html``forgot-password.html``tests/captcha-pages.test.js``tests/auth-page-structure.test.js``docs/superpowers/plans/2026-07-10-tac-modal-cleanup.md`
- 完成结果:`openCaptchaBox` 在每次初始化前清理旧 TAC DOM 并显示遮罩;`closeCaptchaBox` 在成功、取消、初始化异常、Promise 失败后清空 DOM、隐藏遮罩并更新 `aria-hidden`;三张认证页加载新的公共验证码弹窗 CSS。
- 验证结果:`node tests\\captcha-pages.test.js``node tests\\auth-page-structure.test.js``node --check public\\js\\captcha-pages.js``Get-ChildItem -Path tests -Filter *.test.js | Sort-Object Name | ForEach-Object { node $_.FullName }` 均通过。
- 剩余风险:`/captcha/*` 的 503、CORS 或 TLS 错误属于测试网关/服务端可用性问题;本项只保证它们触发取消或异常后,前端不会继续遗留遮罩。
## PC-014 已完成记录全量复核
- 状态:已完成
- 开始时间:2026-07-10 09:20:38 +08:00
- 计划文件:`docs/superpowers/plans/2026-07-10-pc-tracker-reverification.md`
- 复核范围:PC-000 至 PC-013 中所有标记为“已完成”或“已修正”的项目。
- 复核规则:以当前 `genealogy-pc-openapi.yaml``paths`、当前代码、当前 Node 测试、语法检查和静态扫描为准;历史记录只作为待核对内容,不作为证明。
- 状态规则:当前证据完整才保留“已完成”;代码或记录不一致改为“待修正”;仅受测试网关影响的项目标记“外部环境待验证”。
- 暂停时间:2026-07-10 09:27:30 +08:00
- 暂停原因:用户要求删除现有 fetch 请求层并整体切换为 Axios;重构前取得的复核结果不能作为重构后的最终证据。
- 完成时间:2026-07-10 10:03:18 +08:00
- 当前结论:上方 PC-000 至 PC-011 及下方 PC-012、PC-013 的复核状态为本次有效状态,历史文件名、历史命令和历史时间仅用于追溯。
- 验证证据:当前 YAML 路径扫描、Axios 公共层测试、认证/验证码/资料/安全/上传/地区测试、45 页脚本顺序测试、全量 Node 测试、语法检查均通过;受限环境外 `curl.exe -I http://test-genealogy-api.ddxcjp.cn/captcha/require` 返回 `HTTP 200 OK`
## PC-015 Axios 公共请求层重构
- 状态:已完成
- 开始时间:2026-07-10 09:27:30 +08:00
- 计划文件:`docs/superpowers/plans/2026-07-10-axios-request-layer-refactor.md`
- 目录边界:`utils/axios.js` 存第三方 Axios 非压缩浏览器构建;`utils/AxiosRequestUtil.js` 存公共请求封装;`utils/ApiClient.js` 存跨页面 PC 接口客户端;`public/js` 只存页面业务脚本。
- 清理目标:删除 `utils/RequestUtil.js``public/js/api-client.js`,并清除 `fetch``fetchImpl``root.fetch` 和旧页面脚本引用;不提供兼容分支。
- 范围修正:2026-07-10 09:27:30 +08:00 初始清单只按旧 RequestUtil 引用列出 14 页;本轮静态扫描发现总共有 45 页加载接口客户端,已修正为自动扫描全部相关 HTML 页面,剩余 31 页必须迁移后才能完成 PC-015。
- 配置职责修正:2026-07-10 09:47:25 +08:00 参考 `C:\Users\Administrator\Desktop\job\3Dyjs\config.js` 后确认,根目录 `config.js` 只负责开发/生产环境与 API 基础地址;PC clientId、tenantId、token key 和 Authorization header 由 `utils/ApiClient.js` 负责。当前未提供生产地址,生产环境暂时使用用户指定的测试地址,待后端提供生产域名后单独替换。
- 配置单一来源复核:2026-07-10 10:10:56 +08:00 已移除 `utils/ApiClient.js``DEFAULT_BASE_URL`;客户端现在只能消费根目录 `config.js``apiBaseUrl` 或调用方显式传入的 `baseUrl`,缺失时立即抛出明确错误。
- 完成时间:2026-07-10 10:03:18 +08:00
- 完成结果:下载 Axios 1.7.9 非压缩浏览器构建至 `utils/axios.js`;创建 `utils/AxiosRequestUtil.js``utils/ApiClient.js`;删除 `utils/RequestUtil.js``public/js/api-client.js`45 页统一加载 `config.js``StorageUtil.js`、Axios、AxiosRequestUtil、ApiClient 后再加载页面业务脚本。
- 验证结果:`node tests\config.test.js``node tests\utils.test.js``node tests\api-client.test.js``node tests\api-script-order.test.js`、全量 Node 测试、`node --check` 和旧 fetch/旧脚本扫描均通过;`git diff --check` 无空白错误。2026-07-10 10:10:56 +08:00 再次回归通过,并确认客户端不再导出 `DEFAULT_BASE_URL`
## PC-016 TAC 原生样式调用清理
- 状态:已完成(由 PC-017 修正调用位置)
- 开始时间:2026-07-10 10:19:46 +08:00
- 计划文件:`docs/superpowers/plans/2026-07-10-tac-native-style-cleanup.md`
- 页面范围:`login.html``register.html``forgot-password.html`
- 约束:只调用 `public/tac` 自带的 CSS 和 JS;移除项目自定义验证码遮罩、尺寸约束与视觉配置,不改动 TAC 第三方文件。
- 完成时间:2026-07-10 10:32:20 +08:00
- 修改结果:删除 `public/css/captcha-modal.css`;删除 `public/css/public.css` 中 35 行自定义验证码规则;三页仅保留 `public/tac/css/tac.css`,并使用无 class 的 `[data-captcha-box]``captcha-pages.js` 只调用 `new TAC(config)`
- 验证结果:`node tests\auth-page-structure.test.js``node tests\captcha-pages.test.js`、全量 Node 测试、`node --check public\js\captcha-pages.js`、自定义验证码视觉标识扫描和 `git diff --check` 均通过。
- 外部边界:本项只恢复 TAC 原生视觉与调用边界;验证码背景尺寸校验失败仍需以后端挑战响应的原始图片尺寸为依据另行修复。
- 复核时间:2026-07-10 10:38:10 +08:00
- 复核结论:TAC 的 `init()` 只将组件追加到 `CaptchaConfig.bindEl`,不负责页面级定位;删除静态挂载点定位样式后,组件被追加到 `body` 末尾,因此渲染到页面底部。本项不能保留“已完成”。
## PC-017 TAC 默认弹层调用纠正
- 状态:已完成
- 开始时间:2026-07-10 10:38:10 +08:00
- 计划文件:`docs/superpowers/plans/2026-07-10-tac-layer-call-correction.md`
- 调用位置:`auth-pages.js` 表单提交前调用 `CaptchaPages.ensureToken`;验证码适配层使用 Layui 默认 `layer.open` 创建临时容器,容器传入 `CaptchaConfig.bindEl`,然后调用 `new TAC(config)`
- 功能边界:不限制后端下发的 `SLIDER``ROTATE``CONCAT``WORD_IMAGE_CLICK``DISABLED` 类型;刷新由 TAC `reloadCaptcha()` 处理;成功、关闭与异常路径由适配层关闭对应 Layui 层。
- 完成时间:2026-07-10 10:44:11 +08:00
- 修改结果:删除三页静态 `[data-captcha-box]``captcha-pages.js` 用 Layui 默认 `layer.open` 创建临时容器作为 `CaptchaConfig.bindEl`,并保持 `new TAC(config)` 原样调用;TAC 结束后关闭同一 Layui 层。
- 验证结果:`node tests\auth-page-structure.test.js``node tests\captcha-pages.test.js`、全量 Node 测试、`node --check public\js\captcha-pages.js``git diff --check` 均通过;静态扫描确认三页、公共 CSS 和验证码适配层没有 `data-captcha-box``auth-captcha-modal``captcha-modal.css``bgUrl``logoUrl`
- 浏览器边界:尚未在真实测试网关完成有效轨迹验证;需在浏览器发起一次注册、登录或找回密码操作,确认 Layui 默认层居中和后端 `validToken` 流程。
## PC-018 TAC Layui content 契约修复
- 状态:已完成(由 PC-019 修正真实挂载点)
- 开始时间:2026-07-10 10:50:06 +08:00
- 完成时间:2026-07-10 10:54:03 +08:00
- 计划文件:`docs/superpowers/plans/2026-07-10-tac-layui-content-contract-fix.md`
- 页面范围:`login.html``register.html``forgot-password.html`
- 接口范围:`/captcha/require``/captcha/challenge``/captcha/verify`
- 根因记录:浏览器中 `/captcha/require` 已返回 `200`,但点击提交后提示 `d.parents is not a function` 且 TAC 不显示。根因是 `captcha-pages.js` 将原生 DOM 节点直接传入 `layui.layer.open({ content })`;当前 Layui 2.11.5 弹层内部会对 `content` 调用 `.parents()`,原生 DOM 没有该方法,导致弹层创建中断。
- 修改文件:`public/js/captcha-pages.js``tests/captcha-pages.test.js``docs/superpowers/plans/2026-07-10-tac-layui-content-contract-fix.md``docs/pc-api-page-integration-tracker-2026-07-09.md`
- 修改结果:`createCaptchaLayer` 现在使用 `layui.$``$``jQuery` 包装临时 DOM 后传给 `layer.open`;返回给 TAC 的 `box` 仍是真实 DOM 节点,继续作为 `CaptchaConfig.bindEl`。未修改 `public/tac`,未新增验证码 CSS,未传 TAC 视觉配置。
- 验证结果:先运行 `node tests\captcha-pages.test.js` 复现失败;修复后 `node tests\auth-page-structure.test.js``node tests\captcha-pages.test.js``node --check public\js\captcha-pages.js``git diff --check` 通过。`git diff --check` 仅输出既有 CRLF 行尾提示,没有空白错误。
## PC-020 TAC Layui 弹层居中修复
- 状态:已完成
- 开始时间:2026-07-10 11:11:05 +08:00
- 完成时间:2026-07-10 11:14:31 +08:00
- 计划文件:`docs/superpowers/plans/2026-07-10-tac-layer-center-fix.md`
- 页面范围:`login.html``register.html``forgot-password.html`
- 接口范围:`/captcha/require``/captcha/challenge``/captcha/verify`
- 根因记录:Layui 打开弹层时验证码挂载点仍是空节点,TAC 后续异步创建自己的 `#tianai-captcha-parent`,原生尺寸为 `318px * 318px`,导致 Layui 先按空内容定位后,最终验证码窗口偏离视觉中心。
- 修改文件:`public/js/captcha-pages.js``tests/captcha-pages.test.js``docs/superpowers/plans/2026-07-10-tac-layer-center-fix.md``docs/pc-api-page-integration-tracker-2026-07-09.md`
- 修改结果:`createCaptchaLayer` 使用 Layui 默认 `layer.open` 时增加 `area: ['318px', '318px']``offset: 'auto'`,尺寸来自 TAC 原生 `#tianai-captcha-parent`;未修改 `public/tac`,未新增验证码自定义 CSS,未传 TAC 视觉配置。
- 验证结果:先运行 `node tests\captcha-pages.test.js` 复现 `layerOptions.area` 缺失失败;修复后 `node tests\captcha-pages.test.js``node tests\auth-page-structure.test.js``node --check public\js\captcha-pages.js``git diff --check` 通过。`git diff --check` 仅输出既有 LF/CRLF 换行提示,没有空白错误。
- 复核结论:该项只解决 `d.parents is not a function`;用户 2026-07-10 10:59 左右浏览器复测显示 Layui 遮罩出现、`/captcha/challenge` 返回 200,但 TAC 本体未显示,因此真实挂载点问题进入 PC-019 修正。
## PC-019 TAC Layui 实际挂载点修复
- 状态:已完成
- 开始时间:2026-07-10 10:59:52 +08:00
- 完成时间:2026-07-10 11:04:18 +08:00
- 计划文件:`docs/superpowers/plans/2026-07-10-tac-layer-real-mount-fix.md`
- 页面范围:`login.html``register.html``forgot-password.html`
- 接口范围:`/captcha/require``/captcha/challenge``/captcha/verify`
- 根因记录:用户浏览器截图显示 Layui 遮罩已经出现,`/captcha/require``/captcha/challenge` 都返回 `200`,但弹层内没有 TAC 验证窗口。根因是 PC-018 仍返回打开前的临时 DOM 给 `CaptchaConfig.bindEl`,不能保证该节点就是 Layui 弹层内容区里的真实节点。
- 修改文件:`public/js/captcha-pages.js``tests/captcha-pages.test.js``docs/superpowers/plans/2026-07-10-tac-layer-real-mount-fix.md``docs/pc-api-page-integration-tracker-2026-07-09.md`
- 修改结果:`createCaptchaLayer` 现在用字符串 `content` 让 Layui 创建 `<div data-captcha-layer-box="..."></div>`,弹层打开后再通过 `document.querySelector` 获取这个实际节点,并将它作为 TAC 的 `bindEl`。未修改 `public/tac`,未新增验证码 CSS,未传 TAC 视觉配置。
- 验证结果:先运行 `node tests\captcha-pages.test.js` 复现失败;修复后 `node tests\auth-page-structure.test.js``node tests\captcha-pages.test.js``node --check public\js\captcha-pages.js``git diff --check` 通过。`git diff --check` 仅输出既有 CRLF 行尾提示,没有空白错误。
@@ -1,128 +0,0 @@
# Auth Layer Message 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.
**Goal:** 让登录、注册、忘记密码三页的提示统一使用 layui `layer.msg`,不再出现浏览器原生 alert,并补好认证页提示和 TAC 弹窗样式。
**Architecture:** 页面加载 `public/layui/css/layui.css``public/layui/layui.js``auth-pages.js``captcha-pages.js` 只负责调用提示,不写样式;提示皮肤与 TAC 弹窗样式写入 `public/css/public.css`
**Tech Stack:** 原生 HTML/CSS/JavaScript、layui layer、public/tac、Node 断言测试。
## Global Constraints
- 计划开始时间:2026-07-09 17:14:08 +08:00。
- 只处理登录、注册、忘记密码三页的提示与弹窗样式问题。
- 不修改 `public/tac/js/tac.min.js`
- 不使用 HTML 原生 `alert` 作为用户提示。
- 样式写在 CSS 文件里,JS 只切 class 或调用组件。
- 新增或修改的 HTML、CSS、JS 注释使用中文,代码保持可读。
---
### Task 1: 补充失败测试
**Files:**
- Modify: `tests/auth-page-structure.test.js`
- Create: `tests/auth-message.test.js`
**Interfaces:**
- Consumes: 当前三页 HTML 与认证脚本。
- Produces: 能捕获缺少 layui 和原生 alert 兜底的测试。
- [x] **Step 1: 添加页面结构断言**
检查 `login.html``register.html``forgot-password.html` 必须加载 `public/layui/css/layui.css``public/layui/layui.js`,且 `layui.js``auth-pages.js` 前面。
- [x] **Step 2: 添加提示脚本断言**
检查 `public/js/auth-pages.js``public/js/captcha-pages.js` 不再包含 `root.alert`,并检查 `public/css/public.css` 提供 `.auth-layer-message`
- [x] **Step 3: 验证红灯**
运行:
```powershell
node tests\auth-page-structure.test.js
node tests\auth-message.test.js
```
预期:两个测试均失败,分别提示缺少 layui 和仍存在 `root.alert`
### Task 2: 接入 layui 并替换原生提示
**Files:**
- Modify: `login.html`
- Modify: `register.html`
- Modify: `forgot-password.html`
- Modify: `public/js/auth-pages.js`
- Modify: `public/js/captcha-pages.js`
**Interfaces:**
- Consumes: `layui.layer.msg(message, options)`
- Produces: `showMessage(message)` 统一使用 layer,缺少 layui 时只输出控制台警告,不再弹原生 alert。
- [x] **Step 1: 三页加载 layui 样式和脚本**
在三页 `<head>` 中加入 `public/layui/css/layui.css`,在 `auth-pages.js` 前加入 `public/layui/layui.js`
- [x] **Step 2: 修改认证脚本提示**
`auth-pages.js``showMessage` 改为优先调用 `layui.layer.msg(message, { skin: 'auth-layer-message' })`,没有 layui 时使用 `console.warn`
- [x] **Step 3: 修改验证码脚本提示**
`captcha-pages.js``showMessage` 同步改为同样的 layer 提示策略。
### Task 3: 补齐公共样式
**Files:**
- Modify: `public/css/public.css`
**Interfaces:**
- Consumes: layui layer 生成的 `.auth-layer-message`
- Produces: 认证页统一提示皮肤和移动端更稳的 TAC 弹窗尺寸。
- [x] **Step 1: 添加 layui 消息皮肤**
在公共 CSS 中添加 `.auth-layer-message`,控制圆角、背景、文字和阴影。
- [x] **Step 2: 微调 TAC 弹窗移动端尺寸**
继续使用 `.auth-captcha-modal` 页面级挂载,限制 TAC 宽度不超过视口,避免盖层内容顶到边缘。
### Task 4: 验证并记录完成
**Files:**
- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md`
**Interfaces:**
- Consumes: Task 1-3 的实现结果。
- Produces: PC-012 完成记录。
- [x] **Step 1: 跑焦点测试**
运行:
```powershell
node tests\auth-page-structure.test.js
node tests\auth-message.test.js
```
预期:全部通过。
- [x] **Step 2: 跑相关测试和语法检查**
运行:
```powershell
node tests\auth-pages.test.js
node tests\captcha-pages.test.js
node --check public\js\auth-pages.js
node --check public\js\captcha-pages.js
```
预期:全部通过。
- [x] **Step 3: 更新追踪记录**
把 PC-012 标记为已完成,写入完成时间、修改文件、验证命令和剩余风险。
@@ -1,762 +0,0 @@
# PC API Replan 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:** 按最新 `genealogy-pc-openapi.yaml` 重新整理 PC/H5 页面接口对接,只接入 `paths` 中真实存在的验证码、PC 认证、PC 文件和行政区划接口,并清除旧 APP 路径作为 PC 合同的风险。
**Architecture:** 根目录 `config.js` 是接口基础地址、客户端 ID、租户、token key 的唯一配置入口;`utils` 只放跨页面公共 JS`public/js/api-client.js` 只暴露 PC YAML `paths` 中存在的接口和受控的“PC 接口未提供”错误。页面业务 JS 继续放在 `public/js`,页面只能调用 PC 已确认方法;PC YAML 未覆盖的页面保留静态/本地展示或显示接口待确认状态。
**Tech Stack:** 静态 HTML、原生 JS、jQuery/Layui、Node 测试脚本、`public/tac` 天爱验证码静态资源。
## Global Constraints
- 接口依据只使用 `genealogy-pc-openapi.yaml``paths`
- 开发环境接口基础地址固定为 `https://test-genealogy-api.ddxcjp.cn`
- PC 默认 `clientid` 使用用户最新截图确认值:`ced7e5f0498645c6ec642dcf450b036f`,客户端 Key 为 `web_pc`
- 登录后请求使用 `Authorization` header,PC 认证和文件请求同时携带 `clientid` header。
- 样式写入 CSS 文件,不用 JS 注入样式。
- 新增或修改的 HTML、JS、CSS 注释使用中文。
- `utils` 只放跨页面公共 JS;页面业务 JS 继续放 `public/js`
- PC YAML 没有明确接口的页面不套用 APP 路径,不把 schema/requestBodies 当成可调用接口。
- 验证码只使用后台已配置的 PC/H5 场景;当前明确场景为 `WEB_H5_LOGIN``WEB_H5_REGISTER``WEB_H5_FORGOT_PASSWORD`
- 换绑手机、注销账号的 PC/H5 验证码场景未确认,不套用 `APP_*` 场景。
---
## File Structure
- Create or Modify: `config.js`
- 唯一负责环境切换、开发接口地址、PC clientid、tenantId、token key、token header name。
- Create or Modify: `utils/StorageUtil.js`
- 负责 localStorage 安全读写。
- Create or Modify: `utils/FormUtil.js`
- 负责表单读取、空值清理、数字转换、布尔转换。
- Create or Modify: `utils/RequestUtil.js`
- 负责 URL 拼接、header 合并、FormData 判断、JSON 解包、业务错误对象。
- Modify: `public/js/api-client.js`
- 只把 PC YAML `paths` 中存在的接口作为真实请求方法;旧 APP 路径不得作为 fallback。
- Modify: `public/js/auth-pages.js`
- 登录使用 `WEB_H5_LOGIN`;注册使用 `WEB_H5_REGISTER`;找回密码使用 `WEB_H5_FORGOT_PASSWORD`
- Modify: `public/js/captcha-pages.js`
-`/captcha/require``/captcha/challenge``/captcha/verify` 组织 TAC 调用。
- Modify: `public/js/profile-pages.js`
- 用户资料提交字段以 `ProfileUpdateBody` schema 为准:`provinceCode/cityCode/districtCode`
- Modify: `public/js/security-pages.js`
- 修改密码、换绑手机、注销账号只调用 PC 认证接口;涉及验证码的动作保留 PC/H5 场景待确认记录。
- Modify: `public/js/upload-pages.js`
- 上传、分片上传、文件引用只调用 `/genealogy/pc/files/*`
- Modify: `public/js/region-pages.js`
- 地区选择只调用 `/genealogy/region/*`
- Modify: related HTML files
- API 页面按顺序加载 `config.js``utils/*.js``public/js/api-client.js`、页面业务脚本。
- Modify: `tests/*.test.js`
- 测试只验证 PC YAML 已确认接口;旧 APP 路径测试改为“不可作为 PC 合同”。
- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md`
- 每项开始/完成都回填时间、文件、接口、验证结果。
---
### Task 1: 配置入口与公共工具合同
**Files:**
- Create or Modify: `config.js`
- Create or Modify: `utils/StorageUtil.js`
- Create or Modify: `utils/FormUtil.js`
- Create or Modify: `utils/RequestUtil.js`
- Test: `tests/config.test.js`
- Test: `tests/utils.test.js`
**Interfaces:**
- Produces: `GenealogyConfig.getConfig(): { env, apiBaseUrl, clientId, tenantId, tokenKey, tokenHeaderName }`
- Produces: `GenealogyConfig.getApiBaseUrl(): string`
- Produces: `GenealogyConfig.getClientId(): string`
- Produces: `GenealogyConfig.getTenantId(): string`
- Produces: `GenealogyConfig.getTokenKey(): string`
- Produces: `StorageUtil.read(storage, key): string|null`
- Produces: `StorageUtil.write(storage, key, value): void`
- Produces: `StorageUtil.remove(storage, key): void`
- Produces: `FormUtil.trimOrUndefined(value): string|undefined`
- Produces: `FormUtil.toNumberOrUndefined(value): number|undefined`
- Produces: `FormUtil.toBoolean(value): boolean`
- Produces: `FormUtil.getFormValues(form): object`
- Produces: `RequestUtil.createRequester(options)(method, path, requestOptions): Promise<any>`
- [ ] **Step 1: Write config test**
Create or replace `tests/config.test.js` with:
```js
const assert = require('assert');
const configFactory = require('../config.js');
const store = {};
const config = configFactory({
localStorage: {
getItem: (key) => Object.prototype.hasOwnProperty.call(store, key) ? store[key] : null,
setItem: (key, value) => { store[key] = String(value); }
}
});
assert.strictEqual(config.getApiBaseUrl(), 'https://test-genealogy-api.ddxcjp.cn');
assert.strictEqual(config.getClientId(), 'ced7e5f0498645c6ec642dcf450b036f');
assert.strictEqual(config.getTenantId(), '000000');
assert.strictEqual(config.getTokenKey(), 'genealogy_auth_token');
assert.strictEqual(config.getConfig().tokenHeaderName, 'Authorization');
store.genealogy_api_base_url = 'https://custom.example.test';
assert.strictEqual(config.getApiBaseUrl(), 'https://custom.example.test');
console.log('config tests passed');
```
- [ ] **Step 2: Run config test**
Run: `node tests\config.test.js`
Expected before implementation: FAIL if `config.js` is absent or lacks these methods.
Expected after implementation: PASS with `config tests passed`.
- [ ] **Step 3: Implement `config.js`**
Use this implementation:
```js
(function (root, factory) {
// 全局配置同时支持浏览器页面和 Node 测试。
if (typeof module === 'object' && module.exports) {
module.exports = factory;
return;
}
root.GenealogyConfig = factory(root);
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
'use strict';
var DEFAULT_ENV = 'development';
var API_BASE_URLS = {
development: 'https://test-genealogy-api.ddxcjp.cn',
production: ''
};
var CLIENT_ID = 'ced7e5f0498645c6ec642dcf450b036f';
var TENANT_ID = '000000';
var TOKEN_KEY = 'genealogy_auth_token';
var TOKEN_HEADER_NAME = 'Authorization';
var ENV_KEY = 'genealogy_env';
var BASE_URL_KEY = 'genealogy_api_base_url';
function readStorage(key) {
try {
return root.localStorage && root.localStorage.getItem(key);
} catch (error) {
return null;
}
}
function getEnv() {
return readStorage(ENV_KEY) || DEFAULT_ENV;
}
function getApiBaseUrl() {
return readStorage(BASE_URL_KEY) || API_BASE_URLS[getEnv()] || API_BASE_URLS.development;
}
function getConfig() {
return {
env: getEnv(),
apiBaseUrl: getApiBaseUrl(),
clientId: CLIENT_ID,
tenantId: TENANT_ID,
tokenKey: TOKEN_KEY,
tokenHeaderName: TOKEN_HEADER_NAME
};
}
return {
getConfig: getConfig,
getApiBaseUrl: getApiBaseUrl,
getClientId: function () { return CLIENT_ID; },
getTenantId: function () { return TENANT_ID; },
getTokenKey: function () { return TOKEN_KEY; },
getTokenHeaderName: function () { return TOKEN_HEADER_NAME; }
};
});
```
- [ ] **Step 4: Write utilities test**
Create or replace `tests/utils.test.js` with focused utility assertions:
```js
const assert = require('assert');
const StorageUtil = require('../utils/StorageUtil.js');
const FormUtil = require('../utils/FormUtil.js');
const RequestUtil = require('../utils/RequestUtil.js');
assert.strictEqual(FormUtil.trimOrUndefined(' 张三 '), '张三');
assert.strictEqual(FormUtil.trimOrUndefined(' '), undefined);
assert.strictEqual(FormUtil.toNumberOrUndefined('12'), 12);
assert.strictEqual(FormUtil.toNumberOrUndefined(''), undefined);
assert.strictEqual(FormUtil.toBoolean('on'), true);
assert.strictEqual(FormUtil.toBoolean('0'), false);
const store = {};
const storage = {
getItem: (key) => Object.prototype.hasOwnProperty.call(store, key) ? store[key] : null,
setItem: (key, value) => { store[key] = String(value); },
removeItem: (key) => { delete store[key]; }
};
StorageUtil.write(storage, 'token', 'abc');
assert.strictEqual(StorageUtil.read(storage, 'token'), 'abc');
StorageUtil.remove(storage, 'token');
assert.strictEqual(StorageUtil.read(storage, 'token'), null);
const calls = [];
const requester = RequestUtil.createRequester({
baseUrl: 'https://test-genealogy-api.ddxcjp.cn',
clientId: 'pc-client',
tokenHeaderName: 'Authorization',
getToken: () => 'token-x',
fetchImpl: async (url, options) => {
calls.push({ url, options });
return {
ok: true,
status: 200,
json: async () => ({ code: 200, data: { ok: true } })
};
}
});
requester('GET', '/genealogy/pc/auth/profile', {
query: { keyword: '汤 氏' }
}).then((data) => {
assert.deepStrictEqual(data, { ok: true });
assert.strictEqual(calls[0].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/profile?keyword=%E6%B1%A4+%E6%B0%8F');
assert.strictEqual(calls[0].options.headers.clientid, 'pc-client');
assert.strictEqual(calls[0].options.headers.Authorization, 'Bearer token-x');
console.log('utils tests passed');
});
```
- [ ] **Step 5: Implement utility files**
Implement the utilities with Chinese comments around storage try/catch, FormData detection, JSON response parsing and `code !== 200` error creation. `RequestUtil.createRequester` must omit `Authorization` when `requestOptions.auth === false`.
- [ ] **Step 6: Run utility tests**
Run: `node tests\utils.test.js`
Expected: PASS with `utils tests passed`.
---
### Task 2: API 客户端收紧到 PC YAML `paths`
**Files:**
- Modify: `public/js/api-client.js`
- Test: `tests/api-client.test.js`
**Interfaces:**
- Consumes: `GenealogyConfig`
- Consumes: `StorageUtil`
- Consumes: `RequestUtil.createRequester`
- Produces: `GenealogyApi.createClient(options)`
- Produces: `GenealogyApi.defaultClient`
- Produces PC methods:
- `login(body)`, `register(body)`, `loginBySms(body)`, `sendSmsCode(body)`
- `getProfile()`, `updateProfile(body)`, `changePassword(body)`, `resetPassword(body)`, `changePhone(body)`, `deactivateAccount(body)`, `logout()`
- `uploadFile(formData)`, `initResumableUpload(body)`, `uploadChunk(formData)`, `completeResumableUpload(body)`, `bindFileReference(body)`, `releaseFileReference(query)`
- `getRegionChildren(parentCode)`, `getRegionPath(regionCode)`, `searchRegions(query)`, `getRegion(regionCode)`
- `captchaRequire(query)`, `captchaChallenge(body)`, `captchaVerify(body)`, `legacyCaptcha()`
- Produces unsupported method behavior: methods for PC YAML 未覆盖业务 throw `Error` with `code === 'PC_API_NOT_AVAILABLE'` and no network request.
- [ ] **Step 1: Replace API client test**
Rewrite `tests/api-client.test.js` so it verifies only PC-confirmed paths and unsupported APP-era methods:
```js
const assert = require('assert');
const GenealogyApi = require('../public/js/api-client.js');
const calls = [];
const client = GenealogyApi.createClient({
baseUrl: 'https://test-genealogy-api.ddxcjp.cn',
clientId: 'client-x',
tenantId: 'tenant-x',
tokenKey: 'token-key',
tokenHeaderName: 'Authorization',
storage: { getItem: () => 'abc-token' },
fetchImpl: async (url, options) => {
calls.push({ url, options });
return {
ok: true,
status: 200,
json: async () => ({ code: 200, data: { url } })
};
}
});
(async () => {
await client.login({ tenantId: 'tenant-x', phone: '13800000000', password: 'md5' });
assert.strictEqual(calls[0].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/login');
assert.strictEqual(calls[0].options.headers.clientid, 'client-x');
assert.strictEqual(calls[0].options.headers.Authorization, undefined);
await client.getProfile();
assert.strictEqual(calls[1].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/profile');
assert.strictEqual(calls[1].options.headers.Authorization, 'Bearer abc-token');
await client.updateProfile({ nickName: '张三', provinceCode: '510000', cityCode: '510100', districtCode: '510104' });
assert.strictEqual(calls[2].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/profile');
assert.strictEqual(calls[2].options.method, 'PUT');
await client.uploadFile(new FormData());
assert.strictEqual(calls[3].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/files/upload');
await client.getRegionChildren('51');
assert.strictEqual(calls[4].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/region/children?parentCode=51');
await client.captchaRequire({ sceneCode: 'WEB_H5_LOGIN', subject: '13800000000' });
assert.strictEqual(calls[5].url, 'https://test-genealogy-api.ddxcjp.cn/captcha/require?tenantId=tenant-x&clientId=client-x&sceneCode=WEB_H5_LOGIN&subject=13800000000');
assert.strictEqual(calls[5].options.headers.Authorization, undefined);
assert.throws(
() => client.listGenealogies(),
(error) => error.code === 'PC_API_NOT_AVAILABLE'
);
assert.strictEqual(calls.length, 6);
console.log('api-client tests passed');
})();
```
- [ ] **Step 2: Run API client test**
Run: `node tests\api-client.test.js`
Expected before implementation: FAIL while old APP paths or old scene tests remain.
Expected after implementation: PASS with `api-client tests passed`.
- [ ] **Step 3: Implement confirmed PC methods**
In `public/js/api-client.js`, remove APP auth/files paths. Map methods exactly:
```js
login: function (body) {
return request('POST', '/genealogy/pc/auth/login', { body: body, auth: false });
},
register: function (body) {
return request('POST', '/genealogy/pc/auth/register', { body: body, auth: false });
},
sendSmsCode: function (body) {
return request('POST', '/genealogy/pc/auth/sms/code', { body: body, auth: false });
},
loginBySms: function (body) {
return request('POST', '/genealogy/pc/auth/login/sms', { body: body, auth: false });
},
getProfile: function () {
return request('GET', '/genealogy/pc/auth/profile');
},
updateProfile: function (body) {
return request('PUT', '/genealogy/pc/auth/profile', { body: body });
}
```
Add the remaining PC auth/files/region/captcha methods with the same direct mapping from YAML `paths`.
- [ ] **Step 4: Implement unsupported method guard**
For old business methods whose PC paths do not exist, keep the method name only if current pages call it, but route it to:
```js
function createUnavailableMethod(name) {
return function () {
var error = new Error('PC 接口文档未提供该业务接口:' + name);
error.code = 'PC_API_NOT_AVAILABLE';
throw error;
};
}
```
Use a Chinese comment above the mapping: `// PC YAML 未提供这些业务 paths,保留方法名只用于阻止旧 APP 路径继续发请求。`
- [ ] **Step 5: Verify no APP auth/files network paths remain**
Run: `Select-String -Path public\js\api-client.js -Pattern '/genealogy/app/auth|/genealogy/app/files'`
Expected: no matches.
- [ ] **Step 6: Run API client test again**
Run: `node tests\api-client.test.js`
Expected: PASS with `api-client tests passed`.
---
### Task 3: 登录验证码与 `public/tac` 调用
**Files:**
- Modify: `public/js/auth-pages.js`
- Modify: `public/js/captcha-pages.js`
- Modify: `login.html`
- Modify: `register.html`
- Modify: `forgot-password.html`
- Test: `tests/auth-pages.test.js`
- Test: `tests/captcha-pages.test.js`
**Interfaces:**
- Consumes: `public/tac/css/tac.css`
- Consumes: `public/tac/js/tac.min.js`
- Consumes: `GenealogyApi.defaultClient.captchaRequire/challenge/verify`
- Produces: `AuthPages.getCaptchaScene('login') === 'WEB_H5_LOGIN'`
- Produces: `AuthPages.getCaptchaScene('register') === 'WEB_H5_REGISTER'`
- Produces: `AuthPages.getCaptchaScene('password-reset') === 'WEB_H5_FORGOT_PASSWORD'`
- Produces: `CaptchaPages.buildChallengeBody(options, api)`
- Produces: `CaptchaPages.buildVerifyBody(options, api)`
- Produces: `CaptchaPages.ensureToken(form, options)`
- [ ] **Step 1: Replace auth scene tests**
In `tests/auth-pages.test.js`, require these expectations:
```js
assert.strictEqual(AuthPages.getCaptchaScene('login'), 'WEB_H5_LOGIN');
assert.strictEqual(AuthPages.getCaptchaScene('register'), 'WEB_H5_REGISTER');
assert.strictEqual(AuthPages.getCaptchaScene('password-reset'), 'WEB_H5_FORGOT_PASSWORD');
```
- [ ] **Step 2: Replace captcha tests**
In `tests/captcha-pages.test.js`, all challenge/verify body tests must use `WEB_H5_LOGIN`:
```js
assert.deepStrictEqual(CaptchaPages.buildChallengeBody({
sceneCode: 'WEB_H5_LOGIN',
subject: '13800000000'
}, fakeApi), {
tenantId: 'tenant-x',
clientId: 'client-x',
sceneCode: 'WEB_H5_LOGIN',
subject: '13800000000'
});
```
- [ ] **Step 3: Run auth and captcha tests**
Run:
```powershell
node tests\auth-pages.test.js
node tests\captcha-pages.test.js
```
Expected before implementation: FAIL if `PC_LOGIN` or `PC_REGISTER` remains.
Expected after implementation: PASS.
- [ ] **Step 4: Implement scene mapping**
In `public/js/auth-pages.js`:
```js
function getCaptchaScene(formType) {
// PC/H5 认证场景来自后台验证码配置,客户端 Key 为 web_pc。
var map = {
login: 'WEB_H5_LOGIN',
register: 'WEB_H5_REGISTER',
'password-reset': 'WEB_H5_FORGOT_PASSWORD'
};
return map[formType] || '';
}
```
If `sceneCode` is empty, `ensureCaptcha` must return `true` without calling `/captcha/require` and must not fabricate `validToken`.
- [ ] **Step 5: Verify TAC assets in auth pages**
Run:
```powershell
Select-String -Path login.html,register.html,forgot-password.html -Pattern 'public/tac/css/tac.css|public/tac/js/tac.min.js|public/js/captcha-pages.js|name="validToken"|data-captcha-box'
```
Expected:
- `login.html` has TAC CSS, TAC JS, `captcha-pages.js`, hidden `validToken`, and `[data-captcha-box]`.
- `register.html` and `forgot-password.html` may keep TAC assets and hidden fields, but no PC/H5 scene is configured until backend confirms scene code.
---
### Task 4: 认证与资料页面只接 PC auth schema
**Files:**
- Modify: `public/js/auth-pages.js`
- Modify: `public/js/profile-pages.js`
- Modify: `public/js/security-pages.js`
- Modify: `login.html`
- Modify: `register.html`
- Modify: `forgot-password.html`
- Modify: `profile.html`
- Modify: `profile-data.html`
- Modify: `profile-security.html`
- Test: `tests/auth-pages.test.js`
- Test: `tests/profile-pages.test.js`
- Test: `tests/security-pages.test.js`
**Interfaces:**
- Consumes: `GenealogyApi.defaultClient`
- Produces request bodies:
- `PasswordLoginBody`: `grantType/tenantId/phone/password/validToken`
- `PasswordRegisterBody`: `grantType/tenantId/phone/password/nickName/registerSource/validToken`
- `PasswordResetBody`: `tenantId/phone/smsCode/newPassword/validToken`
- `ProfileUpdateBody`: `nickName/avatarOssId/sex/birthday/provinceCode/cityCode/districtCode`
- [ ] **Step 1: Update body-builder tests**
`tests/auth-pages.test.js` must assert `registerSource: 'PC'` for register body and no `APP_*` scene in any body.
`tests/profile-pages.test.js` must assert profile update uses `provinceCode/cityCode/districtCode`, not `regionCode/addressDetail`.
- [ ] **Step 2: Run focused tests**
Run:
```powershell
node tests\auth-pages.test.js
node tests\profile-pages.test.js
node tests\security-pages.test.js
```
Expected before implementation: FAIL where old scene/body fields remain.
Expected after implementation: PASS.
- [ ] **Step 3: Implement auth body builders**
Use PC YAML schema fields only. For register:
```js
function buildRegisterBody(values, config) {
var body = {
grantType: 'password',
tenantId: config.tenantId,
phone: values.phone,
password: values.password,
registerSource: 'PC'
};
if (values.nickName) body.nickName = values.nickName;
if (values.validToken) body.validToken = values.validToken;
return body;
}
```
- [ ] **Step 4: Implement profile body builder**
Use schema fields:
```js
function buildProfileUpdateBody(values) {
return removeEmpty({
nickName: values.nickName,
avatarOssId: toNumberOrUndefined(values.avatarOssId),
sex: values.sex,
birthday: values.birthday,
provinceCode: values.provinceCode,
cityCode: values.cityCode,
districtCode: values.districtCode
});
}
```
- [ ] **Step 5: Verify page script order**
Run:
```powershell
Select-String -Path login.html,register.html,forgot-password.html,profile.html,profile-data.html,profile-security.html -Pattern 'config.js|utils/StorageUtil.js|utils/FormUtil.js|utils/RequestUtil.js|public/js/api-client.js'
```
Expected: every page that uses `GenealogyApi` loads config and utils before `api-client.js`.
---
### Task 5: 文件上传和文件引用只切 PC files
**Files:**
- Modify: `public/js/upload-pages.js`
- Modify: upload-related page scripts that call `GenealogyApi.defaultClient.uploadFile`
- Test: `tests/upload-pages.test.js`
- Test: `tests/api-client.test.js`
**Interfaces:**
- Consumes: `GenealogyApi.defaultClient.uploadFile(formData)`
- Consumes: `GenealogyApi.defaultClient.initResumableUpload(body)`
- Consumes: `GenealogyApi.defaultClient.uploadChunk(formData)`
- Consumes: `GenealogyApi.defaultClient.completeResumableUpload(body)`
- Consumes: `GenealogyApi.defaultClient.bindFileReference(body)`
- Consumes: `GenealogyApi.defaultClient.releaseFileReference(query)`
- [ ] **Step 1: Add upload tests**
`tests/upload-pages.test.js` must verify:
- single upload calls `/genealogy/pc/files/upload`
- resumable init calls `/genealogy/pc/files/resumable/init`
- file reference body requires `bizType/bizTable/bizId/bizField`
- [ ] **Step 2: Run upload tests**
Run: `node tests\upload-pages.test.js`
Expected before implementation: FAIL if old APP file paths remain.
Expected after implementation: PASS.
- [ ] **Step 3: Implement upload mapping**
Only map file capability. Do not submit article/feed/album/ceremony/growth/memo business forms unless PC YAML provides their business endpoints.
- [ ] **Step 4: Verify no APP file path remains**
Run: `Select-String -Path public\js\api-client.js,public\js\upload-pages.js -Pattern '/genealogy/app/files'`
Expected: no matches.
---
### Task 6: 行政区划接口和资料地区字段
**Files:**
- Modify: `public/js/region-pages.js`
- Modify: `public/js/profile-pages.js`
- Test: `tests/region-pages.test.js`
- Test: `tests/profile-pages.test.js`
**Interfaces:**
- Consumes: `GET /genealogy/region/children`
- Consumes: `GET /genealogy/region/path/{regionCode}`
- Consumes: `GET /genealogy/region/search`
- Consumes: `GET /genealogy/region/{regionCode}`
- Produces profile fields: `provinceCode/cityCode/districtCode`
- [ ] **Step 1: Add region tests**
`tests/region-pages.test.js` must assert:
- empty parent loads province list with no required parent code.
- search requires `keyword`.
- selected province/city/district writes `provinceCode/cityCode/districtCode` to the form.
- [ ] **Step 2: Run region tests**
Run: `node tests\region-pages.test.js`
Expected before implementation: FAIL if form still expects `regionCode/addressDetail`.
Expected after implementation: PASS.
- [ ] **Step 3: Implement region mapping**
Use `GenealogyApi.defaultClient.getRegionChildren`, `getRegionPath`, `searchRegions`, `getRegion`. Do not create genealogy submit behavior here.
---
### Task 7: PC YAML 未覆盖页面降级为接口待确认
**Files:**
- Modify: page scripts that currently call APP-era business methods:
- `public/js/genealogy-pages.js`
- `public/js/join-apply-pages.js`
- `public/js/member-admin-pages.js`
- `public/js/article-pages.js`
- `public/js/feed-pages.js`
- `public/js/album-pages.js`
- `public/js/ceremony-pages.js`
- `public/js/growth-pages.js`
- `public/js/memo-pages.js`
- `public/js/generation-pages.js`
- `public/js/lineage-pages.js`
- `public/js/notification-pages.js`
- `public/js/help-pages.js`
- `public/js/promotion-pages.js`
- `public/js/feedback-pages.js`
- `public/js/vip-pages.js`
- Test: `tests/unsupported-pages.test.js`
**Interfaces:**
- Consumes: `PC_API_NOT_AVAILABLE` errors from `GenealogyApi`
- Produces: visible or console-safe page state that says PC interface is not configured, without issuing APP requests.
- [ ] **Step 1: Add unsupported-page smoke test**
Create `tests/unsupported-pages.test.js` to load each page script in Node with a fake client method that throws `{ code: 'PC_API_NOT_AVAILABLE' }`. Assert each script handles the error without rethrowing from its init function.
- [ ] **Step 2: Run unsupported-page test**
Run: `node tests\unsupported-pages.test.js`
Expected before implementation: FAIL for scripts that assume APP methods are valid.
Expected after implementation: PASS.
- [ ] **Step 3: Implement shared page fallback**
In each affected page script, catch `PC_API_NOT_AVAILABLE` and render a small existing-style empty state. Add a Chinese comment: `// PC YAML 未提供该业务接口,页面先展示接口待确认状态。`
- [ ] **Step 4: Verify no APP business path is emitted from API client**
Run: `Select-String -Path public\js\api-client.js -Pattern '/genealogy/app/'`
Expected: no network path matches.
---
### Task 8: 追踪表和执行记录
**Files:**
- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md`
**Interfaces:**
- Consumes: Task 1-7 verification results.
- Produces: updated status rows with exact timestamps, changed files, interface paths and verification commands.
- [ ] **Step 1: Mark task start before edits**
Before executing each PC task, update the row status from `待开始` to `进行中` with exact time from:
```powershell
Get-Date -Format "yyyy-MM-dd HH:mm:ss zzz"
```
- [ ] **Step 2: Mark task completion after verification**
After the task's verification commands pass, update status to `已完成` and record:
- completion time
- modified files
- interface paths
- verification commands
- verification result
- [ ] **Step 3: Keep missing interfaces in coverage matrix**
Any page whose main business path is absent from `genealogy-pc-openapi.yaml` remains in `PC YAML 未覆盖` or `接口待确认` and must not be promoted to full integration.
---
## Verification Gate
Run these before marking the replan execution complete:
```powershell
node tests\config.test.js
node tests\utils.test.js
node tests\api-client.test.js
node tests\auth-pages.test.js
node tests\captcha-pages.test.js
node tests\profile-pages.test.js
node tests\security-pages.test.js
node tests\upload-pages.test.js
node tests\region-pages.test.js
node tests\unsupported-pages.test.js
Get-ChildItem -Path public\js -Filter *.js | Where-Object { $_.Name -notin @('jquery360.js','jquery.min.js') } | Sort-Object Name | ForEach-Object { node --check $_.FullName }
Select-String -Path public\js\api-client.js -Pattern '/genealogy/app/'
Select-String -Path public\js\auth-pages.js,public\js\captcha-pages.js,tests\auth-pages.test.js,tests\captcha-pages.test.js -Pattern 'PC_LOGIN|PC_REGISTER|PC_PASSWORD_RESET|APP_LOGIN|APP_REGISTER|APP_FORGOT_PASSWORD'
git diff --check
```
Expected:
- All listed Node tests pass.
- Business JS syntax check passes.
- `api-client.js` contains no `/genealogy/app/` network path.
- Auth and captcha code/tests contain no stale APP/old PC scene defaults.
- `git diff --check` passes, or if this directory is not a Git repository, record the exact output.
## Self-Review
- Spec coverage: this plan covers latest PC YAML `paths`, test domain, config.js, utils/public-js separation, CSS/no style injection, Chinese comments, TAC login scene, profile schema mismatch, and PC-missing business interfaces.
- Placeholder scan: no implementation task asks the worker to invent missing PC endpoints or use APP paths as compatibility.
- Type consistency: `GenealogyConfig`, `StorageUtil`, `FormUtil`, `RequestUtil`, `GenealogyApi.createClient`, `AuthPages.getCaptchaScene`, and `CaptchaPages.ensureToken` names are consistent across tasks.
@@ -1,127 +0,0 @@
# PC Auth Three Pages 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:**`genealogy-pc-openapi.yaml` 和 Apifox 截图,把登录、注册、忘记密码三页与 PC 认证接口、验证中心接口对齐。
**Architecture:** 根目录 `config.js` 继续作为环境、`clientid``tenantId` 的唯一配置入口;`utils` 继续存放跨页面公共工具;页面业务逻辑集中在 `public/js/auth-pages.js`,接口调用集中在 `public/js/api-client.js`,验证码适配集中在 `public/js/captcha-pages.js`。页面差异样式只写入对应 CSS 文件。
**Tech Stack:** 原生 HTML/CSS/JavaScript、`public/tac` 滑动验证、Node 断言测试。
## Global Constraints
- 接口依据只使用 `genealogy-pc-openapi.yaml` 与用户补充的 Apifox 截图,不再使用 `APP.openapi.json`
- 开发环境接口地址为 `https://test-genealogy-api.ddxcjp.cn`
- PC `clientid``ced7e5f0498645c6ec642dcf450b036f`,租户 ID 为 `000000`
- 登录验证码场景为 `WEB_H5_LOGIN`,注册验证码场景为 `WEB_H5_REGISTER`,忘记密码验证码场景为 `WEB_H5_FORGOT_PASSWORD`
- 不在 JS 中注入样式;页面差异样式写入对应 CSS 文件,公共样式写入 `public/css/public.css`
- 新增或修改的 HTML、CSS、JS 注释使用中文,代码保持可读,不压缩。
---
### Task 1: 记录专项计划
**Files:**
- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md`
- Create: `docs/superpowers/plans/2026-07-09-pc-auth-three-pages.md`
**Interfaces:**
- Consumes: 用户确认“可以”先做登录、注册、忘记密码三页。
- Produces: PC-009 专项计划记录。
- [x] **Step 1: 写入本计划**
创建本计划,明确页面、接口、验证码场景、样式和验证约束。
- [x] **Step 2: 写入追踪记录**
在追踪文档中新增 `PC-009`,状态为“进行中”,开始时间为 `2026-07-09 16:32:17 +08:00`
### Task 2: 登录页补齐短信登录
**Files:**
- Modify: `login.html`
- Modify: `public/css/login.css`
- Modify: `public/js/auth-pages.js`
- Modify: `tests/auth-pages.test.js`
- Modify: `tests/api-client.test.js`
**Interfaces:**
- Consumes: `ApiClient.login(body)``ApiClient.loginBySms(body)``ApiClient.sendSmsCode(body)``CaptchaPages.ensureToken(form, options)`
- Produces: 登录页同时支持密码登录和短信登录。
- [x] **Step 1: 页面增加登录方式切换**
`login.html` 的登录表单内增加模式切换按钮,密码登录区域保留 `phone/password/validToken`,短信登录区域增加 `phone/smsCode/validToken` 和发送验证码按钮。
- [x] **Step 2: CSS 写入登录页专属样式**
`public/css/login.css` 增加模式切换、隐藏区域、验证码行样式,不在 JS 中写样式。
- [x] **Step 3: JS 支持短信登录提交**
`public/js/auth-pages.js` 增加 `buildSmsLoginBody(values)`,提交时按当前模式调用 `/genealogy/pc/auth/login``/genealogy/pc/auth/login/sms`
- [x] **Step 4: 发送短信复用场景码**
登录页短信验证码按钮使用 `data-scene-code="WEB_H5_LOGIN"`,忘记密码页继续使用 `WEB_H5_FORGOT_PASSWORD`
- [x] **Step 5: 更新测试**
`tests/auth-pages.test.js` 增加短信登录 body 断言;`tests/api-client.test.js` 增加 `loginBySms``sendSmsCode` 路径/入参断言。
### Task 3: 注册和忘记密码复核
**Files:**
- Verify: `register.html`
- Verify: `forgot-password.html`
- Verify: `public/js/auth-pages.js`
**Interfaces:**
- Consumes: `/genealogy/pc/auth/register``/genealogy/pc/auth/sms/code``/genealogy/pc/auth/password/reset`
- Produces: 注册和忘记密码页面确认继续按 PC 字段提交。
- [x] **Step 1: 注册页字段复核**
确认注册页只提交 `phone/password/nickName/validToken``grantType/tenantId/clientId/registerSource``api-client.js` 统一补齐。
- [x] **Step 2: 忘记密码页字段复核**
确认忘记密码页提交 `tenantId/phone/smsCode/newPassword/validToken`,不再出现邮箱找回文案。
### Task 4: 专项验证和完成记录
**Files:**
- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md`
**Interfaces:**
- Consumes: Task 2 和 Task 3 的实现结果。
- Produces: PC-009 完成记录和验证结果。
- [x] **Step 1: 运行测试**
运行:
```powershell
node tests\auth-pages.test.js
node tests\api-client.test.js
node tests\captcha-pages.test.js
```
期望全部输出 `passed`
- [x] **Step 2: 运行语法检查**
运行:
```powershell
node --check public\js\auth-pages.js
node --check public\js\api-client.js
node --check public\js\captcha-pages.js
```
期望无输出且退出码为 0。
- [x] **Step 3: 更新追踪文档**
`PC-009` 状态改为“已完成”,写入完成时间、修改文件、接口路径、验证码场景和验证结果。
@@ -1,95 +0,0 @@
# TAC Captcha Auth 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:**`public/tac` 滑动验证接入登录、注册、忘记密码三个认证页面,并按 `APP.openapi.json` 的验证中心接口换取 `validToken` 后再提交业务接口。
**Architecture:** 新增 `public/js/captcha-pages.js` 作为 TAC 与验证中心接口之间的适配层;`auth-pages.js` 在提交登录、注册、发送短信、重置密码前调用验证码适配层。API 基础地址、clientId、tenantId 继续由 `api-client.js` 统一提供。
**Tech Stack:** 静态 HTML、原生 JS、现有 `public/tac/js/tac.min.js`、现有 `public/tac/css/tac.css`、Node 单元测试。
## Global Constraints
- 严格依据 `APP.openapi.json``/captcha/require``/captcha/challenge``/captcha/verify` 字段接入。
- 业务 JS 不压缩,新增注释使用中文。
- 自有样式写入 CSS 文件,不通过自有 JS 注入样式。
- 第三方 `tac.min.js` 保持原样,不重写其内部样式和 DOM 生成逻辑。
- 登录、注册、忘记密码三页都必须有隐藏 `validToken` 字段和验证码挂载容器。
---
### Task 1: API Client Captcha Helpers
**Files:**
- Modify: `public/js/api-client.js`
- Modify: `tests/api-client.test.js`
**Interfaces:**
- Produces: `client.buildApiUrl(path, query)`
- Produces: `client.captchaRequirement({ sceneCode, subject })`
- [ ] **Step 1: Write failing tests** for captcha requirement URL, query, and public URL building.
- [ ] **Step 2: Run tests** and confirm missing methods fail.
- [ ] **Step 3: Implement minimal API client helpers**.
- [ ] **Step 4: Run tests** and confirm pass.
### Task 2: TAC Adapter Module
**Files:**
- Create: `public/js/captcha-pages.js`
- Create: `tests/captcha-pages.test.js`
**Interfaces:**
- Produces: `CaptchaPages.buildChallengeBody(options, api)`
- Produces: `CaptchaPages.adaptChallengeResponse(response)`
- Produces: `CaptchaPages.buildVerifyBody(requestData, context, api)`
- Produces: `CaptchaPages.extractValidToken(response)`
- Produces: `CaptchaPages.ensureToken(form, options)`
- [ ] **Step 1: Write failing tests** for OpenAPI-to-TAC mapping and validToken extraction.
- [ ] **Step 2: Run tests** and confirm module missing fails.
- [ ] **Step 3: Implement adapter with Chinese comments**.
- [ ] **Step 4: Run tests** and confirm pass.
### Task 3: Auth Flow Integration
**Files:**
- Modify: `public/js/auth-pages.js`
- Modify: `tests/auth-pages.test.js`
**Interfaces:**
- Consumes: `CaptchaPages.ensureToken(form, { sceneCode, subject })`
- Updates: login/register/password reset/send code flows wait for captcha before API submission.
- [ ] **Step 1: Write failing tests** for login body validToken and scene code mapping.
- [ ] **Step 2: Run tests** and confirm expected failure.
- [ ] **Step 3: Integrate captcha gate before submit/send code**.
- [ ] **Step 4: Run tests** and confirm pass.
### Task 4: HTML/CSS Wiring
**Files:**
- Modify: `login.html`
- Modify: `register.html`
- Modify: `forgot-password.html`
- Modify: `public/css/public.css`
**Interfaces:**
- Adds: `<link rel="stylesheet" href="public/tac/css/tac.css" />`
- Adds: hidden `validToken`
- Adds: `data-captcha-box`
- Adds: `public/tac/js/tac.min.js` and `public/js/captcha-pages.js`
- [ ] **Step 1: Add page hooks** with Chinese HTML comments where helpful.
- [ ] **Step 2: Add CSS-only layout for captcha container**.
- [ ] **Step 3: Run static checks** for scripts, hooks, no self JS style injection.
### Task 5: Verification And Records
**Files:**
- Modify: `docs/api-page-integration-2026-07-09.md`
- [ ] **Step 1: Run all Node tests**.
- [ ] **Step 2: Run `node --check` for all business JS**.
- [ ] **Step 3: Run static checks for HTML hooks and style-injection patterns**.
- [ ] **Step 4: Append dated progress record**.
@@ -1,251 +0,0 @@
# Axios 公共请求层重构 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.
**Goal:** 删除现有 `fetch` 请求实现,以本地 Axios 浏览器版和 `utils/AxiosRequestUtil.js` 替换为唯一公共请求层,页面业务 JS 不再依赖或实现底层网络请求。
**Architecture:** `utils/axios.js` 是固定版本的第三方浏览器依赖;`utils/AxiosRequestUtil.js` 负责 Axios 实例、请求头、token、参数、响应解包和错误转换;`utils/ApiClient.js` 负责所有 PC 接口契约与 token 生命周期。所有页面在页面业务脚本前加载三个 utils 文件,旧 `utils/RequestUtil.js``public/js/api-client.js` 被删除且不保留兼容入口。
**Tech Stack:** Axios 1.7.9 浏览器非压缩构建、原生 JavaScript、Node `assert` 测试、PowerShell 静态扫描。
## Global Constraints
- 计划创建时间:2026-07-10 09:27:30 +08:00。
- `utils` 只存通用依赖和公共请求封装;`public/js` 只存页面业务脚本,不存 API 客户端或底层请求实现。
- 不保留 `fetch``fetchImpl``root.fetch``RequestUtil``public/js/api-client.js` 或旧脚本引用;不做 Axios/fetch 双实现兼容。
- Axios 使用本地 `utils/axios.js`,页面运行时不依赖 CDN。
- 普通 JSON 请求传对象给 Axios;文件上传传 `FormData`;不手写 JSON 字符串。
- 每个请求都带 `clientid`;有 token 且 `auth !== false` 时带 `Authorization: Bearer <token>`
- PC-014 在本重构完成后重新执行,重构前的复核结论不作为最终证据。
---
### Task 0: 收敛环境配置职责
**Files:**
- Modify: `config.js`
- Modify: `tests/config.test.js`
- Modify: `utils/ApiClient.js`
**Interfaces:**
- Produces: `GenealogyConfig.getEnvironment()`,只返回 `development``production`
- Produces: `GenealogyConfig.getApiBaseUrl()`,只返回当前环境的接口基础地址。
- Produces: `GenealogyConfig.getConfig()`,只返回 `{ environment, apiBaseUrl }`
- Consumes: `ApiClient` 自己定义 PC `clientId``tenantId`、token key 和 token header。
- [x] **Step 1: 写配置职责失败测试**
```js
assert.deepStrictEqual(config.getConfig(), {
environment: 'development',
apiBaseUrl: 'http://test-genealogy-api.ddxcjp.cn'
});
assert.strictEqual(config.getClientId, undefined);
assert.strictEqual(config.getTenantId, undefined);
assert.strictEqual(config.getTokenKey, undefined);
```
- [x] **Step 2: 运行测试确认旧配置职责过宽**
Run: `node tests\\config.test.js`
Expected: FAIL,旧配置仍暴露 clientId、tenantId、token key 或 token header。
- [x] **Step 3: 重写 config.js 为环境和域名唯一入口**
```js
var environments = {
development: { apiBaseUrl: 'http://test-genealogy-api.ddxcjp.cn' },
production: { apiBaseUrl: 'http://test-genealogy-api.ddxcjp.cn' }
};
function getConfig() {
return {
environment: getEnvironment(),
apiBaseUrl: getApiBaseUrl()
};
}
```
- [x] **Step 4: 将 PC 身份和 token 常量保留在 ApiClient**
```js
var DEFAULT_CLIENT_ID = 'ced7e5f0498645c6ec642dcf450b036f';
var DEFAULT_TENANT_ID = '000000';
var DEFAULT_TOKEN_KEY = 'genealogy_auth_token';
var DEFAULT_TOKEN_HEADER_NAME = 'Authorization';
```
补充约束:`ApiClient` 不定义接口基础地址;必须由根目录 `config.js``apiBaseUrl` 或调用方显式 `baseUrl` 提供。
- [x] **Step 5: 运行配置与 API 客户端测试**
Run: `node tests\\config.test.js; node tests\\api-client.test.js; node --check config.js; node --check utils\\ApiClient.js`
Expected: 所有命令通过。
### Task 1: 锁定 Axios 请求契约的失败测试
**Files:**
- Modify: `tests/utils.test.js`
- Modify: `tests/api-client.test.js`
**Interfaces:**
- Produces: `AxiosRequestUtil.createRequester(options)`
- Consumes: `options.axiosInstance`,该对象实现 `request(config)`
- Produces: `request(method, path, { query, body, headers, auth })`,向 Axios 传入 `{ method, url, params, data, headers }`
- [x] **Step 1: 把工具测试改为 Axios 假实例**
```js
const axiosCalls = [];
const requester = AxiosRequestUtil.createRequester({
baseUrl: 'https://api.example.test',
clientId: 'pc-client',
getToken: () => 'token-x',
axiosInstance: {
request: async (config) => {
axiosCalls.push(config);
return { status: 200, data: { code: 200, data: { ok: true } } };
}
}
});
```
- [x] **Step 2: 运行测试确认因旧 fetch 契约失败**
Run: `node tests\\utils.test.js; node tests\\api-client.test.js`
Expected: FAIL,提示缺少 `AxiosRequestUtil` 或仍使用 `fetchImpl`
### Task 2: 创建 Axios 工具层并删除 fetch 工具层
**Files:**
- Create: `utils/axios.js`
- Create: `utils/AxiosRequestUtil.js`
- Create: `utils/ApiClient.js`
- Delete: `utils/RequestUtil.js`
- Delete: `public/js/api-client.js`
**Interfaces:**
- `AxiosRequestUtil.createRequester(options)` 只接受 Axios 实例,不接受 `fetchImpl`
- `GenealogyApi.createClient(options)``utils/ApiClient.js` 提供,只接受 `axiosInstance` 测试注入;生产环境读取 `window.axios`
- [x] **Step 1: 下载固定 Axios 非压缩浏览器构建**
Run: `curl.exe --fail --location https://cdn.jsdelivr.net/npm/axios@1.7.9/dist/axios.js --output utils\\axios.js`
Expected: `utils/axios.js` 存在且首行包含 Axios 版本信息。
- [x] **Step 2: 实现 AxiosRequestUtil 最小请求流程**
```js
var response = await axiosInstance.request({
method: method.toLowerCase(),
url: path,
params: requestOptions.query,
data: requestOptions.body,
headers: headers
});
return unwrapResponse(response.data);
```
- [x] **Step 3: 从零实现 utils/ApiClient.js 并只依赖 AxiosRequestUtil**
```js
var requestUtil = getAxiosRequestUtil();
var requester = requestUtil.createRequester({
baseUrl: baseUrl,
clientId: clientId,
getToken: getToken,
axiosInstance: settings.axiosInstance || root.axios
});
```
- [x] **Step 4: 删除旧 fetch 请求文件和旧公共 API 文件**
Run: `Remove-Item -LiteralPath utils\\RequestUtil.js; Remove-Item -LiteralPath public\\js\\api-client.js`
Expected: 两个文件不存在,后续扫描无 `RequestUtil``fetchImpl``root.fetch``fetch(``public/js/api-client.js` 匹配。
- [x] **Step 5: 运行工具和 API 客户端测试**
Run: `node tests\\utils.test.js; node tests\\api-client.test.js; node --check utils\\AxiosRequestUtil.js; node --check utils\\ApiClient.js`
Expected: 所有命令通过。
### Task 3: 更新页面公共脚本顺序
**Files:**
- Modify: 所有根目录中加载 `public/js/api-client.js``utils/ApiClient.js` 的 HTML 页面。
- Test: `tests/auth-page-structure.test.js`
- Test: `tests/api-script-order.test.js`
- [x] **Step 1: 以自动发现的脚本顺序测试锁定页面依赖**
```js
assert.strictEqual(pages.length, 45);
assert.ok(axiosIndex < axiosRequestUtilIndex);
assert.ok(axiosRequestUtilIndex < apiClientIndex);
assert.strictEqual(html.includes('utils/RequestUtil.js'), false);
```
- [x] **Step 2: 运行测试确认旧脚本顺序不满足新契约**
Run: `node tests\\auth-page-structure.test.js`
Expected: FAIL,仍有页面加载旧 API 客户端或未加载 Axios 和 AxiosRequestUtil。
- [x] **Step 3: 逐页替换公共脚本引用**
```html
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
```
- [x] **Step 4: 运行结构测试和静态扫描**
Run: `node tests\\auth-page-structure.test.js; rg -n "utils/RequestUtil\\.js|public/js/api-client\\.js|fetchImpl|root\\.fetch|fetch\\(" --glob "!public/tac/**" --glob "!utils/axios.js" .`
Expected: 测试通过,旧请求层标识无匹配。
### Task 4: 重跑 PC 复核并更新记录
**Files:**
- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md`
- Modify: `docs/superpowers/plans/2026-07-10-axios-request-layer-refactor.md`
- Modify: `docs/superpowers/plans/2026-07-10-pc-tracker-reverification.md`
- [x] **Step 1: 运行所有 Node 测试与语法检查**
Run: `Get-ChildItem -Path tests -Filter *.test.js | Sort-Object Name | ForEach-Object { node $_.FullName }; node --check utils\\AxiosRequestUtil.js; node --check utils\\ApiClient.js; git diff --check`
Expected: 所有测试通过,语法检查和差异检查无错误。
- [x] **Step 2: 执行 PC-014 复核命令并记录外部网关状态**
Run: `curl.exe -I --max-time 15 http://test-genealogy-api.ddxcjp.cn/captcha/require`
Expected: 记录当次结果;仅本地验证通过的项标为“本地复核通过”,网关不可用时标注“外部环境待验证”。
- [x] **Step 3: 写入完成时间和清理结论**
Record: Axios 文件来源、删除的 `fetch` 文件、页面脚本替换范围、所有验证命令及结果。
## 自检
- 所有底层网络调用都从 `utils/AxiosRequestUtil.js` 发起。
- `public/js` 中不含 `fetch`、Axios 实例创建或请求头拼装。
- 所有页面在页面业务脚本前加载 `utils/axios.js``utils/AxiosRequestUtil.js``utils/ApiClient.js`
- `utils/RequestUtil.js``public/js/api-client.js``fetchImpl``root.fetch` 和旧页面脚本引用均不存在。
## 完成记录
- 完成时间:2026-07-10 10:10:56 +08:00。
- Axios 来源:`axios@1.7.9/dist/axios.js` 的本地非压缩浏览器构建,存放于 `utils/axios.js`
- 请求契约:所有 PC 请求均由 `utils/AxiosRequestUtil.js` 通过 `axios.request` 发起;`utils/ApiClient.js` 仅封装 PC 接口和 token 生命周期。
- 配置边界:根目录 `config.js` 是接口基础地址唯一来源;`ApiClient` 不再保存 `DEFAULT_BASE_URL`
- 页面范围:45 个加载接口客户端的 HTML 页面已统一脚本顺序;旧 `utils/RequestUtil.js``public/js/api-client.js` 已删除。
- 验证:全量 Node 测试、语法检查、45 页脚本顺序检查、旧 fetch/旧脚本扫描和 `git diff --check` 均通过。
@@ -1,150 +0,0 @@
# 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.
**Goal:** 重新验证 `pc-api-page-integration-tracker-2026-07-09.md` 中 PC-000 至 PC-013 的完成状态,并以当前代码、当前 PC YAML 和本次命令结果修正记录。
**Architecture:** `genealogy-pc-openapi.yaml``paths` 是接口范围唯一来源;代码和 HTML 是当前实现来源;Node 测试、语法检查和静态扫描是可重复验证来源。无法由前端离线验证的测试网关可用性明确标记为“外部环境待验证”,不将其当作页面对接完成证据。
**Tech Stack:** OpenAPI YAML、原生 JavaScript、Node `assert` 测试、PowerShell 静态扫描、Git 差异检查。
## Global Constraints
- 计划创建时间:2026-07-10 09:20:38 +08:00。
- 范围仅限追踪表中状态为“已完成”或“已修正”的 PC-000 至 PC-013;不新增业务接口或页面功能。
- 复核时不采用旧 APP 路径,不以历史测试文字代替当前命令结果。
- 每个结论必须记录命令、结果、时间和证据边界;证据不足必须改为“复核失败/待修正”或“外部环境待验证”。
- 仅在记录与事实不一致时修改文档;不改无关业务代码。
---
### Task 1: 复核契约和配置项
**Files:**
- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md`
- Read: `genealogy-pc-openapi.yaml`
- Read: `config.js`
- Read: `public/js/api-client.js`
- Test: `tests/config.test.js`
- Test: `tests/api-client.test.js`
**Covers:** PC-000、PC-001、PC-007、PC-010。
- [x] **Step 1: 核对 YAML 路径与客户端配置**
Run: `rg -n "^ /captcha/|^ /auth/code:|^ /genealogy/pc/auth/|^ /genealogy/pc/files/|^ /genealogy/region/" genealogy-pc-openapi.yaml; rg -n "test-genealogy-api|ced7e5f0498645c6ec642dcf450b036f|web_pc" config.js public/js/api-client.js`
Expected: 当前 YAML 只包含验证码、PC 认证、PC 文件、地区路径;代码使用当前 PC clientId 和开发环境 HTTP 基础地址。
- [x] **Step 2: 运行配置与 API 客户端测试**
Run: `node tests\\config.test.js; node tests\\api-client.test.js; node --check config.js; node --check public\\js\\api-client.js`
Expected: 四条命令均通过。
### Task 2: 复核通用工具与未覆盖业务降级
**Files:**
- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md`
- Read: `utils/StorageUtil.js`
- Read: `utils/FormUtil.js`
- Read: `utils/RequestUtil.js`
- Read: `public/js/api-client.js`
- Test: `tests/utils.test.js`
- Test: `tests/api-client.test.js`
**Covers:** PC-002、PC-008。
- [x] **Step 1: 运行工具和 API 降级测试**
Run: `node tests\\utils.test.js; node tests\\api-client.test.js; rg -n "/genealogy/app/" public\\js\\api-client.js`
Expected: 两个测试通过,旧 `/genealogy/app/` 请求路径无匹配结果。
- [x] **Step 2: 核对未覆盖业务的错误边界**
Run: `rg -n "PC_API_NOT_AVAILABLE" public\\js\\api-client.js`
Expected: 未被 YAML `paths` 覆盖的业务方法统一受控失败,不生成旧 APP 网络请求。
### Task 3: 复核认证、验证码与资料页面
**Files:**
- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md`
- Read: `login.html`
- Read: `register.html`
- Read: `forgot-password.html`
- Read: `public/js/auth-pages.js`
- Read: `public/js/captcha-pages.js`
- Read: `utils/MessageUtil.js`
- Test: `tests/auth-pages.test.js`
- Test: `tests/captcha-pages.test.js`
- Test: `tests/auth-page-structure.test.js`
- Test: `tests/auth-message.test.js`
- Test: `tests/profile-pages.test.js`
- Test: `tests/security-pages.test.js`
**Covers:** PC-003、PC-009、PC-011、PC-012、PC-013。
- [x] **Step 1: 检查认证场景、接口路径、TAC 挂载和 layui 提示**
Run: `rg -n "WEB_H5_LOGIN|WEB_H5_REGISTER|WEB_H5_FORGOT_PASSWORD|captcha-modal.css|auth-captcha-modal|MessageUtil|layer.msg" login.html register.html forgot-password.html public\\js\\auth-pages.js public\\js\\captcha-pages.js utils\\MessageUtil.js`
Expected: 三张认证页分别使用正确场景;验证码使用页面级挂载点;提示由 `MessageUtil`/layui 提供。
- [x] **Step 2: 运行认证和验证码测试**
Run: `node tests\\auth-pages.test.js; node tests\\captcha-pages.test.js; node tests\\auth-page-structure.test.js; node tests\\auth-message.test.js; node tests\\profile-pages.test.js; node tests\\security-pages.test.js; node --check public\\js\\auth-pages.js; node --check public\\js\\captcha-pages.js; node --check utils\\MessageUtil.js`
Expected: 所有测试和语法检查通过。
- [x] **Step 3: 记录网关外部依赖边界**
Run: `curl.exe -I --max-time 15 http://test-genealogy-api.ddxcjp.cn/captcha/require`
Expected: 仅记录当次 HTTP 结果;无论结果如何,不将测试网关可用性视为前端实现的通过或失败。
### Task 4: 复核文件与地区公共能力
**Files:**
- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md`
- Read: `public/js/upload-pages.js`
- Read: `public/js/region-pages.js`
- Test: `tests/upload-pages.test.js`
- Test: `tests/region-pages.test.js`
**Covers:** PC-004、PC-005、PC-006。
- [x] **Step 1: 运行文件和地区测试**
Run: `node tests\\upload-pages.test.js; node tests\\region-pages.test.js; node --check public\\js\\upload-pages.js; node --check public\\js\\region-pages.js`
Expected: 所有测试和语法检查通过。
- [x] **Step 2: 扫描旧路径与页面覆盖边界**
Run: `rg -n "/genealogy/app/files|/genealogy/app/region" public\\js; rg -n "PC_API_NOT_AVAILABLE" public\\js\\api-client.js`
Expected: 文件、地区旧 APP 路径无匹配;业务页面覆盖边界仍由 `PC_API_NOT_AVAILABLE` 保护。
### Task 5: 全量验证与追踪表修正
**Files:**
- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md`
- Modify: `docs/superpowers/plans/2026-07-10-pc-tracker-reverification.md`
- [x] **Step 1: 运行全量测试和差异检查**
Run: `Get-ChildItem -Path tests -Filter *.test.js | Sort-Object Name | ForEach-Object { node $_.FullName }; git diff --check`
Expected: 所有测试通过,差异检查无空白错误。
- [x] **Step 2: 写入复核矩阵和最终状态**
Record: PC-000 至 PC-013 每项的本次复核时间、验证命令、结论和外部环境边界;任何不一致项从“已完成”改为“待修正”。
## 自检
- 覆盖范围:PC-000 至 PC-013 的所有“已完成/已修正”项均被归入一个复核任务。
- 来源一致性:接口范围只引用 `genealogy-pc-openapi.yaml``paths`,不引用旧 APP 文档。
- 状态规则:只有当前命令和当前代码同时支持的结论才能保留“已完成”。
@@ -1,84 +0,0 @@
# TAC 默认层调用纠正实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**目标:** 在不写任何验证码 CSS、也不修改 TAC 第三方文件的前提下,将 TAC 挂载到 Layui 默认弹层中,不再渲染到认证页底部。
**架构:** `auth-pages.js` 仍在表单提交时调用 `CaptchaPages.ensureToken``captcha-pages.js` 通过已加载的 `layui.layer.open` 创建默认容器,将该容器传给 `CaptchaConfig.bindEl`,然后原样调用 `new TAC(config)`。TAC 继续根据后端下发的 `captchaType` 自己选择滑块、旋转、拼图、文字点击或禁用态。
**技术栈:** Layui 内置 `layer``public/tac/css/tac.css``public/tac/js/tac.min.js`、原生 JavaScript、Node `assert` 测试。
## 全局约束
- 计划创建时间:2026-07-10 10:38:10 +08:00。
- 不修改 `public/tac/css/tac.css``public/tac/js/tac.min.js` 或 Layui 第三方文件。
- 不创建或注入任何验证码 CSS;不传 `bgUrl``logoUrl`、尺寸、皮肤等 TAC 视觉配置。
- 只使用 Layui 默认 `layer.open` 作为弹层容器,TAC 的内部外观完全由其自带文件负责。
- 只修改认证三页、验证码适配、对应测试和记录。
---
### 任务 1:锁定正确的弹层挂载契约
**文件:**
- 修改:`tests/auth-page-structure.test.js`
- 修改:`tests/captcha-pages.test.js`
- [x] **步骤 1:写页面结构失败测试。**
断言 `login.html``register.html``forgot-password.html` 不再保留 `[data-captcha-box]` 静态挂载点,且 `public/layui/layui.js``captcha-pages.js` 前加载。
- [x] **步骤 2:写验证码适配失败测试。**
断言适配层调用 `root.layui.layer.open`、在结束时调用 `root.layui.layer.close`,仍只调用 `new root.TAC(config)`,不存在自定义验证码视觉标识。
- [x] **步骤 3:运行测试并确认当前底部挂载实现失败。**
运行:`node tests\auth-page-structure.test.js; node tests\captcha-pages.test.js`
预期:因静态 `[data-captcha-box]` 与缺少 `layer.open` 而失败。
### 任务 2:使用 Layui 默认层承载 TAC
**文件:**
- 修改:`login.html`
- 修改:`register.html`
- 修改:`forgot-password.html`
- 修改:`public/js/captcha-pages.js`
- [x] **步骤 1:删除三页静态 `[data-captcha-box]`。**
- [x] **步骤 2:在 `captcha-pages.js` 创建无样式的 DOM 容器,并以 `layui.layer.open({ type: 1, content: mount })` 打开默认弹层。**
- [x] **步骤 3:将该 DOM 容器作为 `CaptchaConfig.bindEl`,并保持 `new TAC(config)` 原样调用。**
- [x] **步骤 4:在 TAC 成功、关闭、初始化异常和 Promise 异常时销毁 TAC 并关闭对应 Layui 层。**
- [x] **步骤 5:运行聚焦测试和语法检查。**
运行:`node tests\auth-page-structure.test.js; node tests\captcha-pages.test.js; node --check public\js\captcha-pages.js`
预期:全部通过。
### 任务 3:全量验证和记录
**文件:**
- 修改:`docs/pc-api-page-integration-tracker-2026-07-09.md`
- 修改:`docs/superpowers/plans/2026-07-10-tac-layer-call-correction.md`
- [x] **步骤 1:运行全量 Node 测试、静态扫描和差异检查。**
运行:`Get-ChildItem -Path tests -Filter *.test.js | Sort-Object Name | ForEach-Object { node $_.FullName }; rg -n "data-captcha-box|auth-captcha-modal|captcha-modal.css|bgUrl|logoUrl" login.html register.html forgot-password.html public\css public\js; git diff --check`
预期:测试和差异检查通过;认证页中不再有静态验证码挂载点或自定义视觉标识。
- [x] **步骤 2:记录完成时间、TAC 功能边界和浏览器实测边界。**
## 完成记录
- 完成时间:2026-07-10 10:44:11 +08:00。
- 根因:TAC 的 `init()` 只向 `CaptchaConfig.bindEl` 追加组件;原先静态挂载点位于 `body` 末尾,移除定位样式后自然渲染在页面底部。
- 修改:删除三页静态 `[data-captcha-box]`;验证码适配层使用 Layui 默认 `layer.open` 创建临时容器并传给 `bindEl`;成功、TAC 关闭、初始化异常与 Promise 异常都会清空容器并关闭同一层。
- TAC 边界:保留原生 `new TAC(config)`、原生样式、原生刷新和后端类型分发;不传视觉配置,不写验证码 CSS,不改动任何 `public/tac` 文件。
- 验证:失败测试已复现底部挂载;聚焦测试、全量 Node 测试、`node --check public/js/captcha-pages.js``git diff --check` 均通过。
- 浏览器边界:未在真实测试网关完成一次有效轨迹验证;仍需在浏览器点击注册后确认 Layui 默认层居中显示及后端 `validToken` 返回。
@@ -1,23 +0,0 @@
# 2026-07-10 TAC 弹层居中修复计划
- 计划编号:PC-020
- 开始时间:2026-07-10 11:11:05 +08:00
- 页面范围:`login.html``register.html``forgot-password.html`
- 接口范围:`/captcha/require``/captcha/challenge``/captcha/verify`
- 约束:不修改 `public/tac`,不新增验证码自定义样式,不传 TAC 视觉配置,只调整 Layui 弹层调用参数。
## 根因
Layui 打开页面层时,`content` 里的验证码挂载点还是空节点;TAC 后续异步创建自己的 `#tianai-captcha-parent`,该原生外层尺寸是 `318px * 318px`。Layui 已经按空内容完成定位,所以最终验证码窗口会偏离预期位置。
## 执行清单
1. [x] 写失败测试:要求验证码弹层通过 Layui `area` 预留 `318px * 318px`,并保持默认居中。
2. [x] 修改 `captcha-pages.js`:只给 `layer.open` 增加 TAC 原生尺寸和默认居中参数。
3. [x] 运行焦点测试、结构测试、语法检查和空白检查。
4. [x] 将完成结果同步回 `pc-api-page-integration-tracker-2026-07-09.md`
## 验证记录
- 2026-07-10 11:11:05 +08:00`node tests\captcha-pages.test.js` 已按预期失败,失败点为 `layerOptions.area` 缺失。
- 2026-07-10 11:14:31 +08:00:修复后 `node tests\captcha-pages.test.js``node tests\auth-page-structure.test.js``node --check public\js\captcha-pages.js` 均通过;`git diff --check` 无空白错误,仅有既有 LF/CRLF 换行提示。
@@ -1,72 +0,0 @@
# TAC Layui 实际挂载点修复实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**目标:** 修复 Layui 遮罩已显示、验证码接口已返回 200,但 TAC 验证窗口仍不显示的问题。
**架构:** `CaptchaPages.createCaptchaLayer` 使用 Layui `layer.open` 的字符串 `content` 创建弹层内部真实挂载节点;弹层创建完成后再从 `document` 查询该实际节点,并把它交给 `CaptchaConfig.bindEl`。这样 Layui 负责弹层,TAC 挂到已经进入页面的真实 DOM。
**技术栈:** Layui 2.11.5、TAC 原生 CSS/JS、原生 JavaScript、Node `assert` 测试。
## 全局约束
- 计划创建时间:2026-07-10 10:59:52 +08:00。
- 不修改 `public/tac/css/tac.css``public/tac/js/tac.min.js` 或 Layui 第三方文件。
- 不新增验证码自定义 CSS,不注入样式,不传 TAC 视觉配置。
- 保留 `auth-pages.js -> CaptchaPages.ensureToken -> TAC` 调用链,只修正弹层挂载点。
---
### 任务 1:复现“遮罩有但实际挂载点不可见”
**文件:**
- 修改:`tests/captcha-pages.test.js`
- [x] **步骤 1:写失败测试**
断言 `layer.open``content` 是包含唯一 `data-captcha-layer-box` 的字符串;`createCaptchaLayer` 必须在弹层打开后从 `document.querySelector` 取到实际挂载节点并返回。
- [x] **步骤 2:运行失败测试**
运行:`node tests\captcha-pages.test.js`
预期:失败,当前实现仍返回打开前创建的原生 DOM。
### 任务 2:修正实际挂载点
**文件:**
- 修改:`public/js/captcha-pages.js`
- [x] **步骤 1:最小实现**
增加内部自增 ID,用字符串内容创建 `<div data-captcha-layer-box="..."></div>``layer.open` 后查询该节点并作为 `box` 返回。
- [x] **步骤 2:运行聚焦测试**
运行:`node tests\captcha-pages.test.js; node --check public\js\captcha-pages.js`
预期:全部通过。
### 任务 3:记录和回归
**文件:**
- 修改:`docs/pc-api-page-integration-tracker-2026-07-09.md`
- 修改:`docs/superpowers/plans/2026-07-10-tac-layer-real-mount-fix.md`
- [x] **步骤 1:补充 PC-019 记录**
记录用户浏览器复现现象、根因、修正方式、验证命令。
- [x] **步骤 2:最终验证**
运行:`node tests\auth-page-structure.test.js; node tests\captcha-pages.test.js; node --check public\js\captcha-pages.js; git diff --check`
预期:全部通过;`git diff --check` 仅允许 Git 行尾提示,不允许空白错误。
## 完成记录
- 完成时间:2026-07-10 11:04:18 +08:00。
- 根因:PC-018 只让 `layer.open` 不再因 `.parents()` 报错,但仍把打开前创建的临时 DOM 返回给 TAC;真实浏览器里 Layui 遮罩已出现,`/captcha/challenge` 也返回 200,但 TAC 没有挂到可见弹层内容区。
- 修复:`layer.open` 使用字符串 `content` 创建弹层内部真实节点,随后通过 `document.querySelector('[data-captcha-layer-box="..."]')` 获取实际挂载点并交给 `CaptchaConfig.bindEl`
- 约束:未修改 `public/tac`,未新增验证码 CSS,未传 TAC 视觉配置。
- 验证:失败测试已先复现;修复后 `node tests\captcha-pages.test.js``node --check public\js\captcha-pages.js` 通过。最终回归记录见 `docs/pc-api-page-integration-tracker-2026-07-09.md` 的 PC-019。
@@ -1,71 +0,0 @@
# TAC Layui content 契约修复实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**目标:** 修复认证页点击后只出现 `d.parents is not a function` 提示、TAC 弹层不显示的问题。
**架构:** `CaptchaPages.createCaptchaLayer` 继续只负责创建 Layui 默认弹层容器,不写验证码样式、不改 TAC 文件。Layui `layer.open``content` 使用 Layui/jQuery 包装对象满足弹层内部 `.parents()` 契约,TAC `bindEl` 继续使用对应的真实 DOM 节点。
**技术栈:** Layui 2.11.5、TAC 原生 CSS/JS、原生 JavaScript、Node `assert` 测试。
## 全局约束
- 计划创建时间:2026-07-10 10:50:06 +08:00。
- 不修改 `public/tac/css/tac.css``public/tac/js/tac.min.js` 或 Layui 第三方文件。
- 不新增验证码自定义 CSS,不注入样式,不传 TAC 视觉配置。
- 只修正 Layui 弹层 content 契约、对应测试和记录文档。
---
### 任务 1:复现 Layui content 契约错误
**文件:**
- 修改:`tests/captcha-pages.test.js`
- [x] **步骤 1:写失败测试**
断言 `createCaptchaLayer` 传给 `layui.layer.open``content` 必须是包装对象,包装对象需要保留真实 DOM 节点,并具备 `.parents()` 方法。
- [x] **步骤 2:运行失败测试**
运行:`node tests\captcha-pages.test.js`
预期:失败,当前实现把原生 DOM 直接传给 `content`
### 任务 2:修正 createCaptchaLayer
**文件:**
- 修改:`public/js/captcha-pages.js`
- [x] **步骤 1:最小实现**
`createCaptchaLayer` 内用 `root.layui.$ || root.$ || root.jQuery` 包装真实 DOM 节点;`layer.open({ content })` 使用包装对象;返回值中的 `box` 仍为真实 DOM,供 TAC `bindEl` 使用。
- [x] **步骤 2:运行聚焦测试**
运行:`node tests\captcha-pages.test.js; node --check public\js\captcha-pages.js`
预期:全部通过。
### 任务 3:记录和回归
**文件:**
- 修改:`docs/pc-api-page-integration-tracker-2026-07-09.md`
- 修改:`docs/superpowers/plans/2026-07-10-tac-layui-content-contract-fix.md`
- [x] **步骤 1:补充 PC-018 完成记录**
记录根因、修改文件、验证命令和结果。
- [x] **步骤 2:运行最终验证**
运行:`node tests\auth-page-structure.test.js; node tests\captcha-pages.test.js; node --check public\js\captcha-pages.js; git diff --check`
预期:全部通过;`git diff --check` 仅允许 Git 行尾提示,不允许空白错误。
## 完成记录
- 完成时间:2026-07-10 10:54:03 +08:00。
- 根因:Layui 2.11.5 的 `layer.open` 在处理 DOM 内容时会调用 `.parents()`;原实现将原生 DOM 节点直接传给 `content`,导致浏览器报 `d.parents is not a function`,弹层创建中断。
- 修复:`createCaptchaLayer` 使用 `layui.$``$``jQuery` 包装临时 DOM 后传给 `layer.open({ content })`,同时继续把真实 DOM 节点传给 `CaptchaConfig.bindEl`
- 验证:失败测试已先复现;修复后 `node tests\captcha-pages.test.js``node --check public\js\captcha-pages.js` 通过。最终回归记录见 `docs/pc-api-page-integration-tracker-2026-07-09.md` 的 PC-018。
@@ -1,150 +0,0 @@
# TAC 验证弹窗遮罩清理 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.
**Goal:** 在 TAC 验证被取消、初始化异常或接口失败时,主动关闭认证页遮罩并清空残留的验证码 DOM。
**Architecture:** `public/js/captcha-pages.js` 作为验证码弹窗生命周期的唯一拥有者,新增打开和关闭挂载容器的函数。由于原 `public.css` 不是 UTF-8,无法使用补丁安全编辑,`public/css/captcha-modal.css` 作为新增公共样式文件只根据 `is-active` 类决定遮罩显示;三张认证页继续复用同一个页面级挂载点。
**Tech Stack:** 原生 JavaScript、Node `assert` 测试、layui 提示、TAC `public/tac/js/tac.min.js`
## Global Constraints
- 计划创建时间:2026-07-10 09:10:03 +08:00。
- 仅修改 TAC 遮罩生命周期,不修改 `public/tac/js/tac.min.js`,不改变后端验证码类型选择。
- 新增或修改的 JS、CSS 注释使用中文;样式只写入 CSS 文件。
- 失败提示继续使用 `MessageUtil`/layui,禁止回退到浏览器原生 `alert`
- 503/CORS 是服务端或跨域环境可用性问题;本任务只保证前端失败后不遗留遮罩。
---
### Task 1: 用例锁定与生命周期工具
**Files:**
- Modify: `tests/captcha-pages.test.js`
- Modify: `public/js/captcha-pages.js`
**Interfaces:**
- Produces: `openCaptchaBox(box)`,清空旧内容、激活容器、设置 `aria-hidden="false"`
- Produces: `closeCaptchaBox(box)`,清空内容、取消激活、设置 `aria-hidden="true"`
- [x] **Step 1: 写失败测试**
```js
CaptchaPages.openCaptchaBox(box);
assert.strictEqual(box.classList.contains('is-active'), true);
assert.strictEqual(box.attributes['aria-hidden'], 'false');
CaptchaPages.closeCaptchaBox(box);
assert.strictEqual(box.classList.contains('is-active'), false);
assert.strictEqual(box.attributes['aria-hidden'], 'true');
assert.strictEqual(box.innerHTML, '');
```
- [x] **Step 2: 运行失败测试**
Run: `node tests\\captcha-pages.test.js`
Expected: FAIL,提示 `openCaptchaBox is not a function`
- [x] **Step 3: 实现最小生命周期函数**
```js
function openCaptchaBox(box) {
if (!box) return;
box.innerHTML = '';
box.classList.add('is-active');
box.setAttribute('aria-hidden', 'false');
}
function closeCaptchaBox(box) {
if (!box) return;
box.innerHTML = '';
box.classList.remove('is-active');
box.setAttribute('aria-hidden', 'true');
}
```
- [x] **Step 4: 将关闭函数接入 TAC 成功、取消、初始化异常和 Promise 捕获路径**
```js
if (captchaInstance && captchaInstance.destroyWindow) captchaInstance.destroyWindow();
closeCaptchaBox(box);
```
- [x] **Step 5: 运行单元测试与语法检查**
Run: `node tests\\captcha-pages.test.js; node --check public\\js\\captcha-pages.js`
Expected: 两条命令均通过。
### Task 2: CSS 显示状态收口
**Files:**
- Create: `public/css/captcha-modal.css`
- Modify: `login.html`
- Modify: `register.html`
- Modify: `forgot-password.html`
- Test: `tests/auth-page-structure.test.js`
**Interfaces:**
- Consumes: `.auth-captcha-modal.is-active`
- Produces: 默认隐藏、激活后显示的页面级遮罩。
- [x] **Step 1: 写失败结构测试**
```js
assert.ok(publicCss.includes('.auth-captcha-modal.is-active'));
assert.ok(publicCss.includes('display: none;'));
```
- [x] **Step 2: 运行失败测试**
Run: `node tests\\auth-page-structure.test.js`
Expected: FAIL,缺少 `.auth-captcha-modal.is-active` 规则。
- [x] **Step 3: 实现 CSS 状态规则**
```css
.auth-captcha-modal {
display: none;
}
.auth-captcha-modal.is-active {
display: grid;
}
```
- [x] **Step 4: 运行结构测试**
Run: `node tests\\auth-page-structure.test.js`
Expected: PASS。
### Task 3: 全量验证与记录
**Files:**
- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md`
- Modify: `docs/superpowers/plans/2026-07-10-tac-modal-cleanup.md`
- [x] **Step 1: 运行验证码相关和全量 Node 测试**
Run: `Get-ChildItem -Path tests -Filter *.test.js | Sort-Object Name | ForEach-Object { node $_.FullName }`
Expected: 所有测试通过。
- [x] **Step 2: 记录完成时间、修改文件和验证结果**
```markdown
- [x] 完成时间:YYYY-MM-DD HH:mm:ss +08:00
- [x] 验证:相关测试、语法检查、全量 Node 测试均通过。
```
## 自检
- 需求覆盖:取消后遮罩清理、失败后遮罩清理、成功后遮罩清理、CSS 显示状态和回归测试均有对应任务。
- 占位扫描:无 `TODO``TBD` 或未定义接口。
- 接口一致性:JS 导出名、测试调用名和 CSS 类名统一为 `openCaptchaBox``closeCaptchaBox``is-active`
## 完成记录
- 完成时间:2026-07-10 09:15:55 +08:00。
- 修改文件:`public/js/captcha-pages.js``public/css/captcha-modal.css``login.html``register.html``forgot-password.html``tests/captcha-pages.test.js``tests/auth-page-structure.test.js`
- 验证:`node tests\\captcha-pages.test.js``node tests\\auth-page-structure.test.js``node --check public\\js\\captcha-pages.js` 和全量 `tests/*.test.js` 均通过。
- 已知边界:测试域名的 503、CORS 或 TLS 可用性问题仍需后端或网关处理;该前端修复保证取消或异常后不残留页面遮罩。
@@ -1,85 +0,0 @@
# TAC 原生样式调用清理实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**目标:** 登录、注册、找回密码页面只调用 `public/tac` 自带的验证码组件和样式,不再追加任何验证码视觉样式或视觉配置。
**架构:** 页面保留一个无 class 的 `[data-captcha-box]` 挂载点;`captcha-pages.js` 只负责验证码接口参数和成功、取消、异常后的 DOM 清理;TAC 的外观完全由 `public/tac/css/tac.css``tac.min.js` 决定。
**技术栈:** 原生 JavaScript、TAC 本地组件、Node `assert` 测试。
## 全局约束
- 计划创建时间:2026-07-10 10:19:46 +08:00。
- 不修改 `public/tac/css/tac.css``public/tac/js/tac.min.js`
- 不为验证码新增、覆盖或注入 CSS;不向 `new TAC` 传入 `bgUrl``logoUrl` 等视觉配置。
- 仅修改登录、注册、找回密码三页及其验证码适配、测试和记录。
---
### 任务 1:用测试锁定原生 TAC 调用边界
**文件:**
- 修改:`tests/auth-page-structure.test.js`
- 修改:`tests/captcha-pages.test.js`
- [x] **步骤 1:将页面结构测试改为禁止自定义验证码 CSS 与容器 class。**
断言三页加载 `public/tac/css/tac.css`,不加载 `public/css/captcha-modal.css`,且 `[data-captcha-box]` 不含 `auth-captcha-modal` class。
- [x] **步骤 2:将组件测试改为要求原生构造调用。**
断言 `captcha-pages.js` 中不存在 `bgUrl``logoUrl``.auth-captcha-modal` 视觉控制。
- [x] **步骤 3:运行测试并确认当前实现失败。**
运行:`node tests\auth-page-structure.test.js; node tests\captcha-pages.test.js`
预期:因当前自定义验证码 CSS、容器 class 和 TAC 视觉配置而失败。
### 任务 2:删除自定义验证码视觉层
**文件:**
- 修改:`login.html`
- 修改:`register.html`
- 修改:`forgot-password.html`
- 修改:`public/css/public.css`
- 删除:`public/css/captcha-modal.css`
- 修改:`public/js/captcha-pages.js`
- [x] **步骤 1:删除三页对 `captcha-modal.css` 的引用,并将挂载点改为无 class 的 `data-captcha-box`。**
- [x] **步骤 2:删除 `public.css` 中 `.auth-captcha-modal` 及其 TAC 尺寸限制规则。**
- [x] **步骤 3:删除 `captcha-modal.css`。**
- [x] **步骤 4:将 `new TAC(config, { ... })` 改为 `new TAC(config)`,并将挂载点查询、打开和关闭逻辑改为无视觉状态的 DOM 清理。**
- [x] **步骤 5:运行两项聚焦测试并确认通过。**
运行:`node tests\auth-page-structure.test.js; node tests\captcha-pages.test.js; node --check public\js\captcha-pages.js`
预期:全部通过。
### 任务 3:全量验证并记录
**文件:**
- 修改:`docs/pc-api-page-integration-tracker-2026-07-09.md`
- 修改:`docs/superpowers/plans/2026-07-10-tac-native-style-cleanup.md`
- [x] **步骤 1:运行全量 Node 测试、验证码样式静态扫描和差异检查。**
运行:`Get-ChildItem -Path tests -Filter *.test.js | Sort-Object Name | ForEach-Object { node $_.FullName }; rg -n "captcha-modal.css|auth-captcha-modal|bgUrl|logoUrl" login.html register.html forgot-password.html public\css public\js; git diff --check`
预期:测试与差异检查通过;扫描没有自定义验证码视觉标识。
- [x] **步骤 2:记录完成时间、修改范围、验证结果和外部验证码实测边界。**
## 完成记录
- 完成时间:2026-07-10 10:32:20 +08:00。
- 删除 `public/css/captcha-modal.css`,并从 `public/css/public.css` 删除 35 行 `.auth-captcha-box``.auth-captcha-modal` 和 TAC 尺寸限制规则。
- 三个认证页仅加载 `public/tac/css/tac.css`,无 class 的 `[data-captcha-box]` 只作为 TAC 挂载点。
- `captcha-pages.js` 现在只执行 `new TAC(config)`;不再传入 `bgUrl``logoUrl` 或切换任何视觉 class。成功、取消、初始化异常后仍清空 TAC 第三方 DOM。
- 验证:聚焦结构/验证码测试、全量 Node 测试、`node --check public/js/captcha-pages.js`、自定义样式标识扫描和 `git diff --check` 均通过。
- 外部边界:尚未用有效验证码轨迹完成真实服务端验证;`/captcha/verify` 的尺寸校验问题仍需以后端挑战响应原始尺寸为准单独处理。
+67
View File
@@ -0,0 +1,67 @@
# 家谱 PC 前端交接
更新日期:2026-09-17
## 项目是什么
这是一个静态 HTML 多页面项目。页面直接加载 CSS 和 JavaScript,没有 React、Vue 或单页应用入口。
官网页面在根目录,例如 `index.html``login.html``genealogy.html`。登录后的个人中心和家谱功能页面以 `profile-` 开头。
## 主要目录
| 位置 | 用途 |
| --- | --- |
| 根目录 `*.html` | 各页面入口 |
| `public/css/` | 页面样式,`public.css` 是全局样式 |
| `public/js/` | 页面逻辑、公共交互和第三方脚本 |
| `public/images/` | 官网图片素材 |
| `utils/` | 请求、存储、表单和提示工具 |
| `config.js` | 前端接口配置 |
| `scripts/build-minified.mjs` | 构建脚本 |
| `dist/` | 构建产物;当前已清理,需要时重新构建 |
## 已清理内容
本次已删除测试文件、测试截图、临时接口响应、设计审查记录和旧文档。`dist/`、接口快照和 `package.json` 也已清理。仓库不再保留自动化测试,也没有 `npm test` 命令。
后续不要在仓库中保存测试截图、测试账号信息、接口返回样本或重复的过程记录。需要说明验证结果时,写在当前提交或任务交付里即可。
## 请求和登录
接口地址和请求封装在 `utils/ApiClient.js``utils/AxiosRequestUtil.js``config.js`
页面不要直接发 Axios 请求,也不要在页面里拼接口地址。新增接口时先在 `ApiClient` 中统一定义,再由页面调用。
登录信息由 `StorageUtil.js` 管理。登录后的请求会携带 Bearer Token;401 会清除登录态并跳转到 `login.html`;403 保留登录态并显示无权限状态。
后端返回的长整型 ID 在浏览器里按字符串处理,避免数字精度丢失。
## 家谱业务规则
家谱业务必须使用真实的 `genealogyId`。没有家谱上下文时,应阻止请求或提示用户选择家谱,不能写死示例 ID。
接口字段、权限和接口是否存在,以 Apifox 的 PC 目录为准。不要从 APP 接口推测 PC 接口,也不要根据旧文件猜字段。
接口还缺少列表、详情或删除能力时,页面应保持禁用或提示状态,不要补造数据把入口打开。
## 常用位置
- 个人中心通用布局和导航:`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`
改公共样式或公共脚本前,先检查相关 `profile-*.html` 的引用,避免影响其他页面。
## 接手一个功能时
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 --` 覆盖现有文件。
+8 -11
View File
@@ -32,7 +32,7 @@
<section class="inner-hero download-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Download</div>
<div class="eyebrow">应用下载</div>
<h1>下载代代相传 APP</h1>
<p class="lead">
手机端适合日常上传宣传相册、发布宣传视频、查看家谱和补充资料。
@@ -43,9 +43,8 @@
</div>
</div>
<div class="hero-card qr-card tilt-card">
<div class="qr-box"><span>扫码</span></div>
<h3>手机扫码下载</h3>
<p>支持 iOS 与 Android,实际项目可替换为真实下载二维码。</p>
<h3>选择下载方式</h3>
<p>请在下方选择平台当前提供的可用下载入口。</p>
</div>
</div>
</section>
@@ -53,9 +52,8 @@
<div class="container">
<div class="section-head">
<h2>下载方式</h2>
<p>前台先展示下载入口,后续可以接真实应用商店地址</p>
<p>下载地址会根据平台当前配置显示;没有可用入口时会明确提示</p>
</div>
<!-- 下载入口区域:内容来自 /genealogy/app/promotions 列表接口。 -->
<div class="grid-3" data-promotion-download-list>
<div class="api-empty">下载推广内容加载中</div>
</div>
@@ -85,10 +83,10 @@
><a href="notice-detail.html">平台公告</a>
</div>
<div>
<h4>联系我们</h4>
<p>客服电话:400-000-0000</p>
<p>邮箱:service@example.com</p>
<p>地址:中国 · 成都</p>
<h4>服务支持</h4>
<a href="help.html">帮助中心</a>
<a href="submit-ticket.html">提交问题</a>
<a href="about.html">关于平台</a>
</div>
</div>
<div class="footer-legal">
@@ -102,7 +100,6 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/promotion-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+5 -6
View File
@@ -36,7 +36,7 @@
<section class="inner-hero detail-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Family Detail</div>
<div class="eyebrow">家谱详情</div>
<h1 data-family-text="title">四川武胜汤氏族</h1>
<p class="lead" data-family-text="intro|description|summary" data-fallback="公开展示家族源流、世系概览、字辈排行、谱文资料和宣传相册,让访客快速理解这一本家谱的内容结构。">
公开展示家族源流、世系概览、字辈排行、谱文资料和宣传相册,让访客快速理解这一本家谱的内容结构。
@@ -119,10 +119,10 @@
><a href="about.html">平台公告</a>
</div>
<div>
<h4>联系我们</h4>
<p>客服电话:400-000-0000</p>
<p>邮箱:service@example.com</p>
<p>地址:中国 · 成都</p>
<h4>服务支持</h4>
<a href="help.html">帮助中心</a>
<a href="submit-ticket.html">提交问题</a>
<a href="about.html">关于平台</a>
</div>
</div>
<div class="footer-legal">
@@ -136,7 +136,6 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/genealogy-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+54
View File
@@ -0,0 +1,54 @@
<!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/css/site-info.css" />
</head>
<body>
<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 href="index.html">首页</a><a href="help.html">帮助中心</a
><a href="about.html">关于我们</a>
</nav>
<div class="nav-actions"><a href="login.html">登录</a></div>
</div>
</header>
<main>
<section class="site-info-section">
<div class="container">
<div class="site-info-status">
<div class="site-info-mark">!</div>
<h2>无访问权限</h2>
<p>
当前账号没有访问此内容的权限。请确认登录账号,或联系家谱管理员处理。
</p>
<div class="site-info-actions">
<a class="btn primary magnetic" href="index.html">返回首页</a
><a class="btn ghost magnetic" href="help.html">查看帮助</a>
</div>
</div>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-legal">
<div>© 2026 代代相传. All rights reserved.</div>
<a href="terms.html">服务协议</a>
</div>
</div>
</footer>
<script src="config.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+22 -33
View File
@@ -18,43 +18,31 @@
class="card form-card auth-card recover-card"
aria-labelledby="recover-title"
>
<div class="eyebrow">Password Recovery</div>
<div class="eyebrow">找回密码</div>
<h1 id="recover-title">找回密码</h1>
<p class="lead">输入注册手机号,获取验证码后重设登录密码。</p>
<form class="form-grid" data-auth-form="password-reset" data-captcha-scene="WEB_H5_FORGOT_PASSWORD" data-success-url="login.html">
<input
class="input"
name="phone"
type="tel"
inputmode="numeric"
autocomplete="tel"
maxlength="11"
placeholder="手机号"
/>
<div class="code-row">
<input
class="input"
name="smsCode"
autocomplete="one-time-code"
placeholder="验证码"
/>
<button class="btn ghost" type="button" data-api-send-code data-scene-code="WEB_H5_FORGOT_PASSWORD">获取验证码</button>
<form id="password-reset-form" class="form-grid" novalidate>
<label class="auth-field" for="reset-phone">
<span>手机号 <b aria-hidden="true">*</b></span>
<input id="reset-phone" class="input" name="phone" type="tel" inputmode="numeric" autocomplete="tel" maxlength="11" placeholder="请输入注册手机号" required />
</label>
<div class="auth-field">
<label for="reset-sms-code">短信验证码 <b aria-hidden="true">*</b></label>
<div class="code-row">
<input id="reset-sms-code" class="input" name="smsCode" autocomplete="one-time-code" inputmode="numeric" maxlength="4" pattern="\d{4}" placeholder="请输入四位验证码" required />
<button class="btn ghost" type="button" data-api-send-code>获取验证码</button>
</div>
</div>
<input
class="input"
name="newPassword"
type="password"
autocomplete="new-password"
placeholder="新密码"
/>
<input
class="input"
name="confirmPassword"
type="password"
autocomplete="new-password"
placeholder="确认新密码"
/>
<label class="auth-field" for="reset-password">
<span>新密码 <b aria-hidden="true">*</b></span>
<input id="reset-password" class="input" name="newPassword" type="password" autocomplete="new-password" placeholder="至少六位" required />
</label>
<label class="auth-field" for="reset-confirm-password">
<span>确认新密码 <b aria-hidden="true">*</b></span>
<input id="reset-confirm-password" class="input" name="confirmPassword" type="password" autocomplete="new-password" placeholder="请再次输入新密码" required />
</label>
<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>
</form>
</section>
@@ -70,6 +58,7 @@
<script src="public/layui/layui.js"></script>
<script src="public/tac/js/tac.min.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/page-effects.js"></script>
</body>
File diff suppressed because it is too large Load Diff
+8 -7
View File
@@ -36,7 +36,7 @@
<section class="inner-hero genealogy-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Digital Genealogy</div>
<div class="eyebrow">数字家谱</div>
<h1>把成员关系整理成清晰可查的数字家谱</h1>
<p class="lead">
从始祖、分支、排行到个人资料,数字家谱帮助家族建立长期可维护的成员档案和世系关系。
@@ -132,17 +132,17 @@
</div>
<div>
<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>
<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>
<h4>联系我们</h4>
<p>客服电话:400-000-0000</p>
<p>邮箱:service@example.com</p>
<p>地址:中国 · 成都</p>
<h4>服务支持</h4>
<a href="help.html">帮助中心</a>
<a href="submit-ticket.html">提交问题</a>
<a href="about.html">关于平台</a>
</div>
</div>
<div class="footer-legal">
@@ -151,6 +151,7 @@
</div>
</div>
</footer>
<script src="config.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+17 -30
View File
@@ -36,13 +36,13 @@
<section class="inner-hero help-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Help Center</div>
<div class="eyebrow">帮助中心</div>
<h1>从创建到协作,快速解决常见问题</h1>
<p class="lead">
覆盖注册登录、创建家谱、邀请成员、资料审核、隐私设置和内容发布等流程
帮助内容由平台持续维护,覆盖账号、家谱和内容使用中的常见问题
</p>
<div class="help-tags">
<span>创建家谱</span><span>邀请成员</span><span>隐私权限</span>
<span>账号安全</span><span>家谱使用</span><span>内容管理</span>
</div>
</div>
<div class="hero-card help-card tilt-card">
@@ -55,6 +55,7 @@
<a class="btn primary magnetic" href="submit-ticket.html"
>提交工单</a
>
<a class="btn ghost magnetic" href="my-tickets.html">我的工单</a>
</div>
</div>
</section>
@@ -63,27 +64,12 @@
<div class="container">
<div class="section-head">
<h2>常见问题</h2>
<p>把复杂流程拆成清楚步骤,便于用户第一次使用时快速上手</p>
<p>选择问题后读取平台发布的完整解答</p>
<button class="btn ghost" type="button" data-help-refresh>刷新帮助</button>
</div>
<div class="faq-list" data-help-list>
<details open 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>
<details class="tilt-card">
<summary>资料填错了怎么办?</summary>
<p>管理员可以编辑成员资料、调整世系关系,也可以设置审核流程。</p>
</details>
<div class="muted" data-help-status role="status" aria-live="polite"></div>
<div class="faq-list is-loading" data-help-list>
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取帮助文章…</div>
</div>
</div>
</section>
@@ -100,17 +86,17 @@
</div>
<div>
<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>
<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>
<h4>联系我们</h4>
<p>客服电话:400-000-0000</p>
<p>邮箱:service@example.com</p>
<p>地址:中国 · 成都</p>
<h4>服务支持</h4>
<a href="help.html">帮助中心</a>
<a href="submit-ticket.html">提交问题</a>
<a href="about.html">关于平台</a>
</div>
</div>
<div class="footer-legal">
@@ -124,7 +110,8 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/help-pages.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>
</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>
+184 -541
View File
@@ -1,4 +1,4 @@
<!doctype html>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
@@ -15,7 +15,12 @@
<link rel="stylesheet" href="public/css/public.css" />
<link rel="stylesheet" href="public/css/index.css" />
</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">
<div class="container nav">
<a class="brand magnetic" href="index.html"
@@ -24,19 +29,17 @@
src="public/images/logo.png"
alt="我们的家谱,代代相传"
/></a>
<nav class="nav-links">
<a class="active" href="index.html">首页</a>
<nav class="nav-links" aria-label="主导航">
<a class="active" href="index.html" aria-current="page">首页</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="app.html">APP下载</a>
<a href="help.html">在线帮助</a>
<button type="button" data-add-favorite>收藏</button>
</nav>
<div class="nav-actions">
<a href="login.html">登录</a>
<a href="register.html">注册</a>
<a href="login.html">登录</a>
<a class="btn primary magnetic" href="create-genealogy.html"
>创建家谱</a
>
@@ -45,581 +48,221 @@
</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>
<section class="portal-overview" aria-labelledby="home-title">
<div class="container portal-overview-grid">
<div class="hero-copy portal-copy">
<div class="portal-copy-heading">
<div>
<div class="eyebrow">数字家谱与家族文化门户</div>
<h1 id="home-title">一部会生长的<br /><span class="hero-title-mark">数字家谱</span></h1>
</div>
<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>
<div class="word-scroll-card">
<div class="word-scroll-label">正在记录</div>
<div class="word-scroll-card portal-word-scroll" aria-label="家谱内容范围">
<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-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 class="portal-word-group">
<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>
<div class="portal-word-group" aria-hidden="true">
<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>
</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>
<ol class="hero-path" aria-label="修谱步骤">
<li><b>1</b>创建家谱</li>
<li><b>2</b>邀请亲人</li>
<li><b>3</b>共同传承</li>
</ol>
</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 class="portal-gallery" aria-label="家谱与家族影像">
<div class="book-cover portal-gallery-book" role="img" aria-label="张氏家谱封面"></div>
<img src="public/images/ancestral-hall.png" alt="家族祠堂" />
<img src="public/images/family-memory-cover.jpg" alt="家族照片与记忆档案" />
</div>
</div>
</div>
</section>
<div class="quick-strip">
<div class="container quick-inner">
<a class="quick-card tilt-card" href="create-genealogy.html">
<b>创建家谱</b><span>填写姓氏谱名,建立属于家族数字空间。</span>
<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="quick-card tilt-card" href="join-genealogy.html">
<b>加入家谱</b><span>通过邀请码加入亲人创建的家谱。</span>
<a class="portal-entry portal-family-entry tilt-card" href="profile-families.html">
<span>进入管理</span>
<strong>我的家谱</strong>
<small>查看、新建和管理全部家谱</small>
</a>
<a class="quick-card tilt-card" href="plaza.html">
<b>浏览广场</b><span>查看公开家谱示例与家族故事。</span>
<a class="video-card portal-entry portal-media-entry tilt-card" href="promo-video.html" aria-label="查看宣传视频介绍">
<small>宣传影像</small>
<span>观看宣传视频</span>
</a>
<a class="video-card portal-entry portal-media-entry tilt-card" href="promo-video.html" aria-label="查看宣传视频介绍">
<small>宣传影像</small>
<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>
<section class="portal-ad-strip" aria-label="第一组广告栏">
<div class="container portal-ad-grid">
<a class="tilt-card" href="genealogy.html"><img src="public/images/ad-service.png" alt="家谱服务" /></a>
<a class="tilt-card" href="promo-album.html"><img src="public/images/ad-album-small.png" alt="影像相册" /></a>
<a class="tilt-card" href="promo-video.html"><img src="public/images/ad-video-small.png" alt="活动视频" /></a>
<a class="tilt-card" href="genealogy.html"><img src="public/images/ad-print.png" alt="族谱印制" /></a>
<a class="tilt-card" href="culture.html"><img src="public/images/ad-hall.png" alt="祠堂文化" /></a>
<a class="tilt-card" href="app.html"><img src="public/images/ad-cloud.png" alt="云端存档" /></a>
<a class="tilt-card" href="culture.html"><img src="public/images/ad-reunion.png" alt="宗亲联谊" /></a>
<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>
</div>
</section>
<section class="culture">
<section class="portal-directory" aria-label="家族文化与资讯">
<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>
<div class="portal-directory-grid">
<a class="function-card portal-directory-card tilt-card" href="genealogy.html">
<span class="icon" aria-hidden="true"></span>
<h3>家族知识</h3>
<p>了解谱牒方式和成员关系,查看资料、添加亲属并完善家谱。</p>
</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 class="function-card portal-directory-card tilt-card" href="culture.html">
<span class="icon" aria-hidden="true"></span>
<h3>宗亲文化</h3>
<p>了解宗亲礼俗、家风家训与传承故事,延续家族文化。</p>
</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 class="function-card portal-directory-card tilt-card" href="notice-detail.html">
<span class="icon" aria-hidden="true"></span>
<h3>网站公告</h3>
<p>集中查看平台通知、服务调整与家谱相关公告。</p>
</a>
<a class="function-card portal-directory-card tilt-card" href="news.html">
<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>
</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="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>
</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>
<section class="portal-resources" id="site-map" aria-label="推荐与友情链接">
<div class="container portal-resource-grid">
<div class="portal-resource-group">
<h2>其他推荐</h2>
<div class="portal-resource-list">
<a href="news.html">新闻资讯</a>
<a href="culture.html">信息公开</a>
<a href="surname.html">地方动态</a>
<a href="article-detail.html">专题报道</a>
<a href="promo-album.html">家族影像</a>
<a href="promo-video.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">
<div class="portal-resource-group">
<h2>友情链接</h2>
<div class="friend-link-list">
<a href="https://3d.3dyjs.cn/" target="_blank" rel="noopener"
>3D研究室</a
>
<a
href="https://example.com/surnames"
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 class="portal-resource-list">
<a href="genealogy.html">代代相传家谱</a>
<a href="plaza.html">家谱广场</a>
<a href="surname.html">姓氏百科</a>
<a href="culture.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>
家族成员可以在手机端上传照片、查看家谱、发布动态;PC
端适合浏览门户内容、查看公开家谱和进入家谱空间。
</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 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>
<section class="portal-ad-strip portal-ad-strip-bottom" aria-label="第二组广告栏">
<div class="container portal-ad-grid">
<a class="tilt-card" href="help.html"><img src="public/images/ad-data.png" alt="资料校对" loading="lazy" /></a>
<a class="tilt-card" href="article-detail.html"><img src="public/images/ad-memory.png" alt="家风记忆" loading="lazy" /></a>
<a class="tilt-card" href="surname.html"><img src="public/images/ad-research.png" alt="姓氏研究" loading="lazy" /></a>
<a class="tilt-card" href="culture.html"><img src="public/images/ad-reunion.png" alt="宗亲联谊" loading="lazy" /></a>
<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>
<a class="tilt-card" href="genealogy.html"><img src="public/images/ad-print.png" alt="族谱印制" loading="lazy" /></a>
<a class="tilt-card" href="promo-video.html"><img src="public/images/ad-video-small.png" alt="活动视频" loading="lazy" /></a>
<a class="tilt-card" href="promo-album.html"><img src="public/images/ad-album-small.png" alt="影像相册" loading="lazy" /></a>
</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>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
</div>
<div>
<h4>文化内容</h4>
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a
><a href="notice-detail.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="https://beian.miit.gov.cn" target="_blank" rel="noopener"
>ICP备案:蜀ICP备2024044594号-1</a
>
<footer class="footer portal-footer">
<nav class="container portal-footer-links" aria-label="页脚导航">
<a href="about.html">关于家谱</a>
<a href="help.html">联系客服</a>
<a href="submit-ticket.html">意见反馈</a>
<a href="about.html">免责声明</a>
<a href="terms.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>
</p>
<p>家族文化资料请以家谱原件与当地正式记录为准</p>
</div>
</div>
</footer>
<script src="config.js"></script>
<script src="public/js/qrcode.min.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>
</html>
+60 -24
View File
@@ -1,41 +1,77 @@
<!doctype html>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<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/layui/css/layui.css" />
<link rel="stylesheet" href="public/css/profile-module.css" />
<link rel="stylesheet" href="public/css/join-genealogy.css" />
</head>
<body class="page-join-genealogy">
<main class="auth-layout">
<a class="brand auth-page-brand" href="index.html"
><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a
>
<section
class="card form-card auth-card join-card"
aria-labelledby="join-title"
>
<div class="eyebrow">Join Family</div>
<h1 id="join-title">加入家谱</h1>
<p class="lead">输入亲人分享的家谱ID或从详情页带入的链接,提交后等待管理员审核。</p>
<form class="form-grid" data-genealogy-form="join" data-success-url="profile-join-family.html">
<div class="invite-box">
<input class="input" name="genealogyId" inputmode="numeric" placeholder="请输入家谱ID或邀请码" />
<body class="page-profile-module page-join-genealogy" data-join-apply-page>
<header class="site-header">
<div class="container nav">
<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>
<div class="nav-actions"><a href="profile-join-family.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>
<a class="btn ghost magnetic" href="profile-join-family.html">查看我的申请</a>
</div>
<input class="input" name="applicantName" autocomplete="name" placeholder="你的姓名" />
<input class="input" name="phone" autocomplete="tel" placeholder="手机号" />
<input class="input" name="relationDesc" placeholder="与家族关系说明" />
<textarea class="input" name="applyReason" rows="4" placeholder="申请说明(选填)"></textarea>
<button class="btn primary" type="submit">提交申请</button>
</form>
</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>
</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="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-pages.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>
</html>
+28 -46
View File
@@ -15,72 +15,53 @@
><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a
>
<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>
<p class="lead">登录后可创建、加入和维护家谱。</p>
<div class="login-mode-tabs" role="tablist" aria-label="登录方式">
<button class="login-mode-tab is-active" type="button" role="tab" aria-selected="true" data-login-mode-tab="password">
<button class="login-mode-tab is-active" type="button" role="tab" aria-selected="true" aria-controls="login-password-form">
密码登录
</button>
<button class="login-mode-tab" type="button" role="tab" aria-selected="false" data-login-mode-tab="sms">
<button class="login-mode-tab" type="button" role="tab" aria-selected="false" aria-controls="login-sms-form">
短信登录
</button>
</div>
<form
id="login-password-form"
class="form-grid login-mode-panel"
data-auth-form="login"
data-login-mode-panel="password"
data-captcha-scene="WEB_H5_LOGIN"
data-success-url="profile.html"
novalidate
>
<input
class="input"
name="phone"
type="tel"
inputmode="numeric"
autocomplete="tel"
maxlength="11"
placeholder="手机号"
/>
<input
class="input"
name="password"
type="password"
autocomplete="current-password"
placeholder="密码"
/>
<label class="auth-field" for="login-password-phone">
<span>手机号 <b aria-hidden="true">*</b></span>
<input id="login-password-phone" class="input" name="phone" type="tel" inputmode="numeric" autocomplete="tel" maxlength="11" placeholder="请输入手机号" required />
</label>
<label class="auth-field" for="login-password-value">
<span>密码 <b aria-hidden="true">*</b></span>
<input id="login-password-value" class="input" name="password" type="password" autocomplete="current-password" placeholder="请输入密码" required />
</label>
<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>
</form>
<form
id="login-sms-form"
class="form-grid login-mode-panel"
data-auth-form="sms-login"
data-login-mode-panel="sms"
data-captcha-scene="WEB_H5_LOGIN"
data-success-url="profile.html"
hidden
novalidate
>
<input
class="input"
name="phone"
type="tel"
inputmode="numeric"
autocomplete="tel"
maxlength="11"
placeholder="手机号"
/>
<div class="code-row">
<input
class="input"
name="smsCode"
autocomplete="one-time-code"
placeholder="短信验证码"
/>
<button class="btn ghost" type="button" data-api-send-code data-scene-code="WEB_H5_LOGIN">
获取验证码
</button>
<label class="auth-field" for="login-sms-phone">
<span>手机号 <b aria-hidden="true">*</b></span>
<input id="login-sms-phone" class="input" name="phone" type="tel" inputmode="numeric" autocomplete="tel" maxlength="11" placeholder="请输入手机号" required />
</label>
<div class="auth-field">
<label for="login-sms-code">短信验证码 <b aria-hidden="true">*</b></label>
<div class="code-row">
<input id="login-sms-code" class="input" name="smsCode" autocomplete="one-time-code" inputmode="numeric" maxlength="4" pattern="\d{4}" placeholder="请输入四位验证码" required />
<button class="btn ghost" type="button" data-api-send-code>获取验证码</button>
</div>
</div>
<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>
</form>
<div class="auth-links">
@@ -100,6 +81,7 @@
<script src="public/layui/layui.js"></script>
<script src="public/tac/js/tac.min.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/page-effects.js"></script>
</body>
+54
View File
@@ -0,0 +1,54 @@
<!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/css/site-info.css" />
</head>
<body>
<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 href="index.html">首页</a><a href="help.html">帮助中心</a
><a href="about.html">关于我们</a>
</nav>
<div class="nav-actions"><a href="login.html">登录</a></div>
</div>
</header>
<main>
<section class="site-info-section">
<div class="container">
<div class="site-info-status">
<div class="site-info-mark"></div>
<h2>系统维护中</h2>
<p>
我们正在进行必要的服务维护,请稍后再试。账号与家谱资料不会因维护而丢失。
</p>
<div class="site-info-actions">
<a class="btn primary magnetic" href="index.html">返回首页</a
><a class="btn ghost magnetic" href="news.html">查看新闻资讯</a>
</div>
</div>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-legal">
<div>© 2026 代代相传. All rights reserved.</div>
<a href="privacy.html">隐私政策</a>
</div>
</div>
</footer>
<script src="config.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+59
View File
@@ -0,0 +1,59 @@
<!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-feedback-list-page>
<header class="site-header">
<div class="container nav">
<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>
<div class="nav-actions"><a href="help.html">帮助中心</a><a class="btn primary magnetic" href="submit-ticket.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>
<a class="btn primary magnetic" href="submit-ticket.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="module-title-row">
<div><h2>反馈记录</h2><p>选择记录可以查看完整说明和处理状态。</p></div>
<button class="btn ghost" type="button" data-feedback-refresh>刷新</button>
</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>
</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/feedback-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+82
View File
@@ -0,0 +1,82 @@
<!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/css/site-info.css" />
</head>
<body data-site-news-page>
<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 href="index.html">首页</a><a href="genealogy.html">数字家谱</a
><a href="plaza.html">家谱广场</a
><a class="active" href="news.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>
</div>
</div>
</header>
<main>
<section class="site-info-hero">
<div class="container">
<div>
<div class="site-info-kicker">资讯中心</div>
<h1>新闻资讯</h1>
<p>
集中阅读平台新闻、公告与下载内容。
</p>
</div>
<div class="site-info-mark"></div>
</div>
</section>
<section class="site-info-section">
<div class="container site-info-layout">
<div>
<div class="module-meta" data-site-news-status role="status" aria-live="polite"></div>
<div class="site-info-list" data-site-news-list>
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取资讯…</div>
</div>
</div>
<aside class="site-info-panel">
<h2>新闻分类</h2>
<div class="site-info-links">
<a href="news.html">全部</a
><a href="news.html?articleType=news">新闻</a
><a href="news.html?articleType=notice">公告</a
><a href="news.html?articleType=download">下载</a>
</div>
</aside>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<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>
</div>
</div>
</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/site-news-pages.js"></script>
</body>
</html>
+52
View File
@@ -0,0 +1,52 @@
<!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/css/site-info.css" />
</head>
<body>
<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 href="index.html">首页</a><a href="help.html">帮助中心</a
><a href="about.html">关于我们</a>
</nav>
<div class="nav-actions"><a href="login.html">登录</a></div>
</div>
</header>
<main>
<section class="site-info-section">
<div class="container">
<div class="site-info-status">
<div class="site-info-mark">404</div>
<h2>页面不存在</h2>
<p>你访问的页面可能已移动、已删除或地址输入有误。</p>
<div class="site-info-actions">
<a class="btn primary magnetic" href="index.html">返回首页</a
><a class="btn ghost magnetic" href="help.html">前往帮助中心</a>
</div>
</div>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-legal">
<div>© 2026 代代相传. All rights reserved.</div>
<a href="privacy.html">隐私政策</a>
</div>
</div>
</footer>
<script src="config.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+5 -7
View File
@@ -32,7 +32,7 @@
<section class="inner-hero notice-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Notice</div>
<div class="eyebrow">平台公告</div>
<h1 data-promotion-title>平台内容公开展示规范说明</h1>
<p class="lead" data-promotion-summary>
为了保护家族资料安全,平台仅展示管理员确认公开的家谱介绍、文章、宣传相册和宣传视频。
@@ -49,7 +49,6 @@
</section>
<section class="section alt">
<div class="container notice-layout">
<!-- 公告详情区域:从 /genealogy/app/promotions 列表中按 id 选择一条展示。 -->
<article class="notice-content tilt-card" data-promotion-content>
<p>
平台前台页面主要用于展示家族文化与公开资料。涉及成员联系方式、详细个人信息、未审核资料等内容,不会在公开页面直接呈现。
@@ -95,10 +94,10 @@
><a href="notice-detail.html">平台公告</a>
</div>
<div>
<h4>联系我们</h4>
<p>客服电话:400-000-0000</p>
<p>邮箱:service@example.com</p>
<p>地址:中国 · 成都</p>
<h4>服务支持</h4>
<a href="help.html">帮助中心</a>
<a href="submit-ticket.html">提交问题</a>
<a href="about.html">关于平台</a>
</div>
</div>
<div class="footer-legal">
@@ -112,7 +111,6 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/promotion-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+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"
}
}
}
}
+7 -8
View File
@@ -36,7 +36,7 @@
<section class="inner-hero plaza-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Family Plaza</div>
<div class="eyebrow">家谱广场</div>
<h1>浏览公开家谱,寻找同宗线索</h1>
<p class="lead">
按姓氏、地区、堂号和家谱名称检索公开家谱,先看示例,再创建自己的家族空间。
@@ -119,17 +119,17 @@
</div>
<div>
<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>
<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>
<h4>联系我们</h4>
<p>客服电话:400-000-0000</p>
<p>邮箱:service@example.com</p>
<p>地址:中国 · 成都</p>
<h4>服务支持</h4>
<a href="help.html">帮助中心</a>
<a href="submit-ticket.html">提交问题</a>
<a href="about.html">关于平台</a>
</div>
</div>
<div class="footer-legal">
@@ -143,7 +143,6 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/genealogy-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+73
View File
@@ -0,0 +1,73 @@
<!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/css/site-info.css" />
</head>
<body>
<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 href="index.html">首页</a>
<a href="news.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>
</div>
</div>
</header>
<main>
<section class="site-info-hero">
<div class="container">
<div>
<div class="site-info-kicker">隐私政策</div>
<h1>隐私政策</h1>
<p>说明平台处理个人信息的规则、用户权利与联系渠道。</p>
</div>
<div class="site-info-mark"></div>
</div>
</section>
<section class="site-info-section">
<div class="container site-info-layout">
<article class="site-info-panel">
<div class="site-info-notice">
待法务确认:本页面仅建立政策访问入口,不构成已生效的隐私政策。发布前必须由运营主体与法务确认完整文本、版本号和生效日期。
</div>
<h2>正式文本应涵盖</h2>
<p>
个人信息处理目的、收集范围、保存期限、共享规则、用户查阅更正删除权利,以及个人信息保护负责人的联系渠道。
</p>
</article>
<aside class="site-info-panel">
<h2>相关文档</h2>
<div class="site-info-links">
<a href="children-privacy.html">儿童个人信息处理规则</a>
<a href="terms.html">服务协议</a>
<a href="help.html">帮助中心</a>
</div>
</aside>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-legal">
<div>© 2026 代代相传. All rights reserved.</div>
<a href="news.html">新闻资讯</a>
<a href="terms.html">服务协议</a>
</div>
</div>
</footer>
<script src="config.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+21 -29
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-admin-permission-page>
<header class="site-header">
<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
>
<nav class="nav-links">
@@ -19,20 +19,20 @@
><a class="active" href="profile-family-admin.html">家族管理</a>
</nav>
<div class="nav-actions">
<a href="profile-family-admin.html">返回管理</a
><button class="btn primary magnetic" type="submit" form="admin-permission-form">确定</button>
<a href="profile-family-admin.html" data-genealogy-context-link>返回管理</a
><a class="btn primary magnetic" href="profile-family-admin.html" data-genealogy-context-link>管理成员</a>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Permissions</div>
<div class="module-kicker">权限设置</div>
<div class="module-title-row">
<div>
<h1>管理员权限</h1>
<p>
分配全权管理员、全系管理员、支系管理员,以及入谱审核等具体权限
在成员名册中维护管理员、编辑和成员角色;可执行的操作以当前账号权限为准
</p>
</div>
</div>
@@ -41,34 +41,26 @@
<section class="section">
<div class="container module-layout">
<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
><a href="profile-messages.html">入谱审核</a>
><a href="profile-join-review.html" data-genealogy-context-link>入谱审核</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>权限设置</h2>
<form id="admin-permission-form" class="editor-form" data-permission-form>
<!-- 成员下拉由 /members/options 接口渲染 -->
<div class="editor-field">
<label for="permission-member">选择成员</label>
<select id="permission-member" data-member-select>
<option value="">成员加载中...</option>
</select>
</div>
<div class="editor-field">
<label for="permission-role">角色权限</label>
<select id="permission-role" name="roleType">
<option value="admin">管理员</option>
<option value="member">普通成员</option>
</select>
</div>
<div class="bottom-actions">
<button class="btn primary" type="submit">保存权限</button>
<button class="btn ghost" type="button" data-owner-transfer>转让所有者</button>
</div>
</form>
<h2>当前权限</h2>
<div data-admin-permission-current>
<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">
<h2>角色与权限</h2>
<div class="module-list">
<article class="module-row"><div><h3>谱主</h3><p>可维护管理员、编辑和成员角色,并可转让谱主。</p></div></article>
<article class="module-row"><div><h3>管理员</h3><p>可维护编辑和成员角色,不能修改谱主或其他管理员。</p></div></article>
<article class="module-row"><div><h3>编辑、成员</h3><p>不显示成员角色编辑入口;最终权限由服务端校验。</p></div></article>
</div>
<div class="bottom-actions"><a class="btn primary magnetic" href="profile-family-admin.html" data-genealogy-context-link>进入成员名册管理</a></div>
</section>
</div>
</div>
@@ -83,7 +75,7 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/member-admin-pages.js"></script>
<script src="public/js/admin-permission-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+125
View File
@@ -0,0 +1,125 @@
<!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-album-detail-page>
<header class="site-header">
<div class="container nav">
<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-album.html">相册</a>
</nav>
<div class="nav-actions">
<a href="profile-album.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 data-album-detail-title>相册详情</h1>
<p>查看相册信息和照片;照片暂不可在线预览时,仍会保留名称和上传记录。</p>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container module-layout">
<aside class="module-nav">
<a href="profile-content.html">内容发布</a>
<a href="profile-article.html">谱文</a>
<a class="active" href="profile-album.html">相册</a>
<a href="profile-video.html">视频</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>相册信息</h2>
<div data-album-detail>
<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">
<h2>照片列表</h2>
<div class="module-list" data-album-photo-list>
<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" data-album-photo-editor hidden>
<h2>添加照片</h2>
<form class="editor-form" data-album-photo-form novalidate>
<div class="editor-two">
<div class="editor-field">
<label for="albumPhotoFile">照片文件 <span class="field-required" aria-hidden="true">*</span></label>
<input id="albumPhotoOssId" name="ossId" type="hidden" />
<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" />
<p id="albumPhotoUploadStatus" class="upload-status" role="status" aria-live="polite">未选择文件</p>
</div>
<div class="editor-field">
<label for="albumPhotoTitle">照片标题</label>
<input id="albumPhotoTitle" name="photoTitle" type="text" placeholder="请输入照片标题" />
</div>
</div>
<div class="editor-field">
<label for="albumPhotoDesc">照片说明</label>
<textarea id="albumPhotoDesc" name="photoDesc" placeholder="请输入照片说明"></textarea>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="albumPhotographer">拍摄人</label>
<input id="albumPhotographer" name="photographer" type="text" placeholder="请输入拍摄人" />
</div>
<div class="editor-field">
<label for="albumShootTime">拍摄时间</label>
<input id="albumShootTime" name="shootTime" type="datetime-local" />
</div>
</div>
<div class="editor-field">
<label for="albumPhotoSortOrder">排序</label>
<input id="albumPhotoSortOrder" name="sortOrder" type="number" step="1" placeholder="数字越小越靠前" />
</div>
<input name="status" type="hidden" value="0" />
<div class="api-status" data-album-photo-status role="status" aria-live="polite"></div>
<div class="bottom-actions">
<button class="btn primary" type="submit">添加照片</button>
</div>
</form>
</section>
</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/FormUtil.js"></script>
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/md5.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/page-effects.js"></script>
</body>
</html>
+102
View File
@@ -0,0 +1,102 @@
<!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-album-edit-page>
<header class="site-header">
<div class="container nav">
<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-album.html">相册</a>
</nav>
<div class="nav-actions">
<a href="profile-album.html" data-genealogy-context-link>返回相册</a>
<button class="btn primary magnetic" type="submit" form="album-edit-form" data-album-editor-action hidden>保存</button>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">编辑相册</div>
<div class="module-title-row">
<div>
<h1 data-album-editor-title>新建相册</h1>
<p>维护相册名称、说明和封面;封面编号由上传自动产生。</p>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container module-layout">
<aside class="module-nav">
<a href="profile-content.html">内容发布</a>
<a href="profile-article.html">谱文</a>
<a class="active" href="profile-album.html">相册</a>
<a href="profile-video.html">视频</a>
</aside>
<div class="module-main">
<section class="module-panel">
<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 novalidate>
<div class="editor-field">
<label for="albumName">相册名称 <span class="field-required" aria-hidden="true">*</span></label>
<input id="albumName" name="albumName" type="text" required placeholder="请输入相册名称" />
</div>
<div class="editor-field">
<label for="albumDesc">相册说明</label>
<textarea id="albumDesc" name="albumDesc" placeholder="请输入相册说明"></textarea>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="albumCoverFile">封面文件</label>
<input id="albumCoverOssId" name="coverOssId" type="hidden" />
<label class="upload-control" for="albumCoverFile">选择封面图片</label>
<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" role="status" aria-live="polite">未选择文件</p>
</div>
<div class="editor-field">
<label for="albumSortOrder">排序</label>
<input id="albumSortOrder" name="sortOrder" type="number" step="1" placeholder="数字越小越靠前" />
</div>
</div>
<input name="status" type="hidden" value="0" />
<div class="api-status" data-album-form-status role="status" aria-live="polite"></div>
<div class="bottom-actions">
<button class="btn primary" type="submit">保存相册</button>
<a class="btn ghost" href="profile-album.html" data-genealogy-context-link>取消</a>
</div>
</form>
</section>
</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/FormUtil.js"></script>
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.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/media-display.js"></script>
<script src="public/js/album-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+20 -87
View File
@@ -11,30 +11,29 @@
<body class="page-profile-module" data-album-page>
<header class="site-header">
<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-album.html">相册</a
><a href="profile-video.html">视频</a>
<a href="profile-content.html">内容发布</a>
<a class="active" href="profile-album.html">相册</a>
<a href="profile-video.html">视频</a>
</nav>
<div class="nav-actions">
<a href="profile-content.html">返回发布中心</a
><a class="btn primary magnetic" href="#album-form">新建相册</a>
<a href="profile-content.html">返回发布中心</a>
<a class="btn primary magnetic" href="profile-album-edit.html" data-album-create-link hidden>新建相册</a>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Album</div>
<div class="module-kicker">家族相册</div>
<div class="module-title-row">
<div>
<h1>家族相册</h1>
<p>
管理相册名称、封面、描述和照片,作为个人中心内的独立相册管理页。
</p>
<p>查看和管理家谱相册;照片文件由上传组件自动关联,无需填写内部编号。</p>
</div>
</div>
</div>
@@ -42,86 +41,21 @@
<section class="section">
<div class="container module-layout">
<aside class="module-nav">
<a href="profile-content.html">内容发布</a
><a href="profile-article.html">谱文</a
><a class="active" href="profile-album.html">相册</a
><a href="profile-video.html">视频</a
><a href="profile-family-home.html">家谱主页</a>
<a href="profile-content.html">内容发布</a>
<a href="profile-article.html">谱文</a>
<a class="active" href="profile-album.html">相册</a>
<a href="profile-video.html">视频</a>
<a href="profile-family-home.html">家谱主页</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>相册列表</h2>
<!-- 相册列表由 /albums 接口渲染,点击后加载照片 -->
<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>
</section>
<section class="module-panel">
<h2>新建 / 编辑相册</h2>
<form id="album-form" class="editor-form" data-album-form>
<!-- 字段名称对应 APP.openapi.json 的 AlbumBody -->
<div class="editor-field">
<label for="albumName">相册名称</label>
<input id="albumName" name="albumName" type="text" placeholder="请输入相册名称" required />
</div>
<div class="editor-field">
<label for="albumDesc">相册描述</label>
<textarea id="albumDesc" name="albumDesc" placeholder="请输入相册描述"></textarea>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="coverOssId">封面 OSS ID</label>
<input id="coverOssId" name="coverOssId" type="text" placeholder="上传后填写 OSS ID" />
<!-- 公共上传脚本会调用 /files/upload 并回填封面 OSS ID -->
<label class="upload-control" for="albumCoverFile">选择封面图片</label>
<input id="albumCoverFile" class="upload-input" type="file" accept="image/*" data-upload-target="#coverOssId" data-upload-status="#albumCoverStatus" />
<p id="albumCoverStatus" class="upload-status">未选择文件</p>
</div>
<div class="editor-field">
<label for="albumStatus">状态</label>
<select id="albumStatus" name="status">
<option value="0">启用</option>
<option value="1">停用</option>
</select>
</div>
</div>
<div class="bottom-actions">
<button class="btn primary" type="submit">保存相册</button>
<button class="btn ghost" type="reset">清空</button>
</div>
</form>
</section>
<section class="module-panel">
<h2>照片管理</h2>
<!-- 选择相册后由 /albums/{albumId}/photos 接口渲染 -->
<div class="module-list" data-album-photo-list>
<div class="api-empty">请选择相册查看照片</div>
</div>
<form class="editor-form album-photo-form" data-album-photo-form>
<!-- 字段名称对应 APP.openapi.json 的 AlbumPhotoBody -->
<div class="editor-two">
<div class="editor-field">
<label for="photoOssId">照片 OSS ID</label>
<input id="photoOssId" name="ossId" type="text" placeholder="上传后填写 OSS ID" required />
<!-- 公共上传脚本会调用 /files/upload 并回填照片 OSS ID -->
<label class="upload-control" for="albumPhotoFile">选择照片</label>
<input id="albumPhotoFile" class="upload-input" type="file" accept="image/*" data-upload-target="#photoOssId" data-upload-status="#albumPhotoStatus" />
<p id="albumPhotoStatus" class="upload-status">未选择文件</p>
</div>
<div class="editor-field">
<label for="photoTitle">照片标题</label>
<input id="photoTitle" name="photoTitle" type="text" placeholder="请输入照片标题" />
</div>
</div>
<div class="editor-field">
<label for="photoDesc">照片说明</label>
<textarea id="photoDesc" name="photoDesc" placeholder="请输入照片说明"></textarea>
</div>
<div class="bottom-actions">
<button class="btn primary" type="submit">添加照片</button>
</div>
</form>
</section>
</div>
</div>
</section>
@@ -132,11 +66,10 @@
<script src="public/js/profile-common.js"></script>
<script src="config.js"></script>
<script src="utils/StorageUtil.js"></script>
<script src="utils/FormUtil.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/media-display.js"></script>
<script src="public/js/album-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
+31 -19
View File
@@ -7,11 +7,12 @@
<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/js/wangeditor5/css/style.css" />
</head>
<body class="page-profile-module" data-article-edit-page>
<header class="site-header">
<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
>
<nav class="nav-links">
@@ -20,17 +21,17 @@
</nav>
<div class="nav-actions">
<a href="profile-article.html">返回谱文</a
><button class="btn primary magnetic" type="submit" form="article-edit-form">保存</button>
><button class="btn primary magnetic" type="submit" form="article-edit-form" data-article-editor-action hidden>保存</button>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Article Editor</div>
<div class="module-kicker">编辑谱文</div>
<div class="module-title-row">
<div>
<h1>编辑谱文</h1>
<h1 data-article-editor-title>新建谱文</h1>
<p>
使用富文本编辑器撰写谱文、谱序、谱论和恩荣录,支持图片、表格与排版。
</p>
@@ -49,9 +50,12 @@
<div class="module-main">
<section class="module-panel">
<h2>谱文内容</h2>
<form id="article-edit-form" class="editor-form layui-form" data-article-form>
<div data-article-editor-placeholder>
<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">
<label for="articleTitle">谱文标题</label
<label for="articleTitle">谱文标题<span class="field-required" aria-hidden="true"> *</span></label
><input
id="articleTitle"
name="articleTitle"
@@ -61,12 +65,6 @@
/>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="articleCategory">谱文类型</label
><select id="articleCategory" name="categoryId" data-article-category>
<option value="">分类加载中...</option>
</select>
</div>
<div class="editor-field">
<label for="articleSignature">落款</label
><input
@@ -76,9 +74,19 @@
placeholder="请输入落款"
/>
</div>
<div class="editor-field">
<label for="articleSortOrder">排序</label
><input
id="articleSortOrder"
name="sortOrder"
type="number"
step="1"
placeholder="数字越小越靠前"
/>
</div>
</div>
<div class="editor-field">
<label for="articleContent">正文</label
<label for="articleContent">正文<span class="field-required" aria-hidden="true"> *</span></label
><textarea
id="articleContent"
name="articleContent"
@@ -92,13 +100,14 @@
<textarea id="articleSummary" name="articleSummary" placeholder="请输入谱文摘要"></textarea>
</div>
<div class="editor-field">
<label for="articleCoverOssId">封面 OSS ID</label>
<input id="articleCoverOssId" name="coverOssId" type="text" placeholder="上传封面后自动回填" />
<!-- 公共上传脚本会调用 /files/upload 并把 OSS ID 写入上方输入框 -->
<label for="articleCoverFile">封面文件</label>
<input id="articleCoverOssId" name="coverOssId" type="hidden" />
<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" />
<p id="articleCoverStatus" class="upload-status">未选择文件</p>
<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" role="status" aria-live="polite">未选择文件</p>
</div>
<input name="status" type="hidden" value="0" />
<div class="form-status" data-article-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-article.html"
@@ -114,7 +123,7 @@
<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/ke/kindeditor.min.js"></script>
<script src="public/js/wangeditor5/index.js"></script>
<script src="public/js/rich-editor.js"></script>
<script src="public/js/profile-common.js"></script>
<script src="config.js"></script>
@@ -123,7 +132,10 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.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/media-display.js"></script>
<script src="public/js/article-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
+8 -25
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-article-page>
<header class="site-header">
<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
>
<nav class="nav-links">
@@ -20,7 +20,7 @@
</nav>
<div class="nav-actions">
<a href="profile-content.html">返回发布中心</a
><a class="btn primary magnetic" href="profile-article-edit.html"
><a class="btn primary magnetic" href="profile-article-edit.html" data-article-create-link hidden
>添加谱文</a
>
</div>
@@ -29,7 +29,7 @@
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Article</div>
<div class="module-kicker">家族谱文</div>
<div class="module-title-row">
<div>
<h1>谱文管理</h1>
@@ -51,32 +51,14 @@
<div class="module-main">
<section class="module-panel">
<h2>谱文列表</h2>
<!-- 谱文列表由 /articles 接口渲染 -->
<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>
</section>
<section class="module-panel">
<h2>新建谱文</h2>
<div class="form-like">
<p>
<span>谱文标题</span><b>待输入</b
><a class="link-btn" href="profile-article-edit.html">填写</a>
</p>
<p>
<span>谱文内容</span><b>待输入</b
><a class="link-btn" href="profile-article-edit.html">编辑</a>
</p>
<p>
<span>图片质量</span><b>原图</b
><a
class="link-btn"
href="#"
data-layer-select="原图|高清|普通"
data-select-title="选择图片质量"
>选择</a
>
</p>
<h2>谱文详情</h2>
<div data-article-detail>
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一篇谱文查看详情</div>
</div>
</section>
</div>
@@ -92,6 +74,7 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.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/page-effects.js"></script>
</body>
+87
View File
@@ -0,0 +1,87 @@
<!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-ceremony-detail-page>
<header class="site-header">
<div class="container nav">
<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>
<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>
</header>
<main>
<section class="module-hero">
<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 class="section">
<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>
<div class="module-main">
<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">
<h2>祭品记录</h2>
<div class="module-list" data-ceremony-gift-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>
<form class="editor-form" data-ceremony-gift-form data-ceremony-admin-editor hidden novalidate>
<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="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 class="editor-field"><label for="ceremonyGiftMessage">祭品留言</label><textarea id="ceremonyGiftMessage" name="giftMessage" placeholder="可选"></textarea></div>
<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>
</form>
</section>
<section class="module-panel" data-ceremony-admin-editor hidden>
<h2>受邀名单</h2>
<div class="module-list" data-ceremony-invitation-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>
<form class="editor-form" data-ceremony-invitee-form data-ceremony-admin-editor hidden novalidate>
<p>仅显示当前家谱中已绑定业务账号的正常成员;提交完整勾选名单,取消勾选会撤销尚未响应的邀请。</p>
<div class="editor-choice-list" data-ceremony-invitee-options>
<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>
</form>
</section>
</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/media-display.js"></script>
<script src="public/js/ceremony-admin-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+55
View File
@@ -0,0 +1,55 @@
<!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-ceremony-list-page>
<header class="site-header">
<div class="container nav">
<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>
<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>
</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-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">
<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-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>
</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/media-display.js"></script>
<script src="public/js/ceremony-admin-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+27 -27
View File
@@ -1,4 +1,4 @@
<!doctype html>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
@@ -11,19 +11,19 @@
<body class="page-profile-module" data-content-page>
<header class="site-header">
<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
>
<nav class="nav-links">
<a href="profile.html">首页</a
><a href="profile-families.html">数字家谱</a
<a href="profile.html">个人中心</a
><a href="profile-families.html">我的家谱</a
><a href="plaza.html">家谱广场</a
><a class="active" href="profile-content.html">家族文化</a
><a href="surname.html">姓氏百科</a><a href="app.html">应用下载</a
><a href="help.html">帮助中心</a><a href="about.html">关于我们</a>
</nav>
<div class="nav-actions">
<a href="profile.html">个人中心</a
<a href="profile-data.html">账户资料</a
><a class="btn primary magnetic" href="profile-create-family.html"
>创建家谱</a
>
@@ -33,16 +33,15 @@
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Content Studio</div>
<div class="module-kicker">内容发布</div>
<div class="module-title-row">
<div>
<h1>内容发布</h1>
<p>
承接 APP
家谱主页里的谱文、视频、相册、功德录、贺礼邀请、成长日志、动态和备忘录。
集中管理谱文、视频、相册、功德录、贺礼邀请、成长记录、家族动态和备忘录。
</p>
</div>
<a class="btn primary magnetic" href="profile-article.html"
<a class="btn primary magnetic" href="profile-article.html" data-feature-link="available" data-genealogy-context-link
>添加谱文</a
>
</div>
@@ -53,7 +52,7 @@
<aside class="module-nav">
<a href="profile-families.html">我的家谱</a
><a href="profile-messages.html">消息中心</a
><a href="profile-family-admin.html">家族管理</a
><a href="profile-family-admin.html" data-genealogy-context-link>家族管理</a
><a class="active" href="profile-content.html">内容发布</a
><a href="profile-data.html">个人资料</a
><a href="profile-services.html">帮助与服务</a>
@@ -62,54 +61,56 @@
<section class="module-panel">
<h2>发布入口</h2>
<div class="module-grid">
<a class="module-card tilt-card" href="profile-article.html"
<a class="module-card tilt-card" href="profile-article.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-album.html"
<a class="module-card tilt-card" href="profile-album.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-video.html"
<a class="module-card tilt-card" href="profile-video.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"
<a class="module-card tilt-card" href="profile-merit.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-gift.html"
<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-feed.html"
<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"
<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-memo.html"
<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
<h3>备忘录(家族恩人)</h3>
<p>以标题、内容和提醒记录家族恩人等事项,统一归入备忘录管理</p></a
>
</div>
</section>
<section class="module-panel">
<h2>最近内容</h2>
<!-- 最近内容先接谱文接口,其他内容类型按后续编号逐步接入 -->
<div class="module-list" data-recent-content>
<div class="api-empty">最近内容加载中...</div>
</div>
<h2>使用说明</h2>
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择上方功能,继续查看和维护当前家谱内容。</div>
</section>
</div>
</div>
@@ -118,13 +119,12 @@
<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="public/js/profile-common.js?v=20260725-entry"></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/article-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+50 -25
View File
@@ -8,10 +8,10 @@
<link rel="stylesheet" href="public/layui/css/layui.css" />
<link rel="stylesheet" href="public/css/profile-module.css" />
</head>
<body class="page-profile-module">
<body class="page-profile-module" data-genealogy-create-page>
<header class="site-header">
<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
>
<nav class="nav-links">
@@ -27,12 +27,12 @@
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Create Family</div>
<div class="module-kicker">创建家谱</div>
<div class="module-title-row">
<div>
<h1>创建家谱</h1>
<p>
个人中心专用创建流程,用于录入姓氏、谱名、堂号、地望、祠堂和访问权限
填写家谱基本信息;标有 * 的字段为必填,创建后可进入家谱工作区继续维护
</p>
</div>
</div>
@@ -48,16 +48,16 @@
<div class="module-main">
<section class="module-panel">
<h2>基础信息</h2>
<!-- 个人中心创建家谱复用创建接口,字段对应 GenealogyCreateBody。 -->
<form class="editor-form profile-create-family-form" data-genealogy-form="create" data-success-url="profile-families.html">
<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 novalidate>
<div class="editor-two">
<div class="editor-field">
<label for="familySurname">姓氏</label>
<input id="familySurname" name="surname" type="text" required />
<label for="familySurname">姓氏 <span class="field-required" aria-hidden="true">*</span></label>
<input id="familySurname" name="surname" type="text" required placeholder="例如:王" />
</div>
<div class="editor-field">
<label for="familyName">谱名</label>
<input id="familyName" name="genealogyName" type="text" required />
<label for="familyName">谱名 <span class="field-required" aria-hidden="true">*</span></label>
<input id="familyName" name="genealogyName" type="text" required placeholder="例如:王氏家谱" />
</div>
</div>
<div class="editor-two">
@@ -70,20 +70,27 @@
<input id="originPlace" name="originPlace" type="text" />
</div>
</div>
<div class="editor-field">
<label>家族所在地</label>
<div class="editor-field" role="group" aria-labelledby="family-region-label" aria-describedby="family-create-status">
<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>
<select name="provinceCode" data-region-level="province">
<option value="">请选择省</option>
</select>
<select name="cityCode" data-region-level="city">
<option value="">请选择市</option>
</select>
<select name="districtCode" data-region-level="district">
<option value="">请选择区县</option>
</select>
<label for="familyProvinceCode">
<select id="familyProvinceCode" name="provinceCode" data-region-level="province">
<option value="">请选择省</option>
</select>
</label>
<label for="familyCityCode">
<select id="familyCityCode" name="cityCode" data-region-level="city">
<option value="">请选择</option>
</select>
</label>
<label for="familyDistrictCode">区县
<select id="familyDistrictCode" name="districtCode" data-region-level="district">
<option value="">请选择区县</option>
</select>
</label>
<input type="hidden" name="regionCode" data-region-code />
</div>
<div class="upload-status" data-region-picker-status role="status" aria-live="polite"></div>
</div>
<div class="editor-field">
<label for="addressDetail">详细地址</label>
@@ -93,6 +100,20 @@
<label for="intro">家谱简介</label>
<textarea id="intro" name="intro"></textarea>
</div>
<div class="editor-field">
<label for="familyCoverFile">家谱封面</label>
<input id="familyCoverOssId" name="coverOssId" type="hidden" />
<label class="upload-control" for="familyCoverFile">选择封面图片</label>
<input
id="familyCoverFile"
class="upload-input"
type="file"
accept="image/*"
data-upload-target="#familyCoverOssId"
data-upload-status="#familyCoverStatus"
/>
<p id="familyCoverStatus" class="upload-status" role="status" aria-live="polite">未选择文件</p>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="visibility">可见范围</label>
@@ -104,15 +125,17 @@
</div>
<div class="editor-field">
<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="2">邀请码加入</option>
<option value="0">关闭加入</option>
</select>
<p id="family-invite-mode-note" class="form-status">创建完成后,可在家谱首页的“邀请家人”中生成一次性邀请链接。</p>
</div>
</div>
<div id="family-create-status" class="upload-status" data-genealogy-create-status role="status" aria-live="polite"></div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">确定创建</button>
<button class="btn primary magnetic" type="submit" data-genealogy-create-submit>确定创建</button>
<a class="btn ghost magnetic" href="profile-families.html">返回列表</a>
</div>
</form>
@@ -130,9 +153,11 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/region-pages.js"></script>
<script src="public/js/genealogy-pages.js"></script>
<script src="public/js/profile-common.js"></script>
<script src="public/js/region-pages.js"></script>
<script src="public/js/md5.js"></script>
<script src="public/js/upload-pages.js"></script>
<script src="public/js/genealogy-entry-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+13 -17
View File
@@ -8,10 +8,10 @@
<link rel="stylesheet" href="public/layui/css/layui.css" />
<link rel="stylesheet" href="public/css/profile-module.css" />
</head>
<body class="page-profile-module">
<body class="page-profile-module" data-profile-reminder-page>
<header class="site-header">
<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
>
<nav class="nav-links">
@@ -28,7 +28,7 @@
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Data Reminders</div>
<div class="module-kicker">资料提醒</div>
<div class="module-title-row">
<div>
<h1>资料提醒</h1>
@@ -49,20 +49,10 @@
<div class="module-main">
<section class="module-panel">
<h2>待完善成员</h2>
<div class="module-list">
<a class="module-row" href="profile-data.html"
><div>
<h3>鹿野 Loyel</h3>
<p>缺少出生日期、出生地址和微信信息</p>
</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 class="module-list" data-profile-reminder-list>
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
正在读取资料提醒…
</div>
</div>
</section>
</div>
@@ -73,6 +63,12 @@
<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/profile-reminder-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+112 -66
View File
@@ -1,4 +1,4 @@
<!doctype html>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
@@ -11,19 +11,19 @@
<body class="page-profile-module" data-profile-page>
<header class="site-header">
<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
>
<nav class="nav-links">
<a href="profile.html">首页</a
><a href="profile-families.html">数字家谱</a
<a href="profile.html">个人中心</a
><a href="profile-families.html">我的家谱</a
><a href="plaza.html">家谱广场</a
><a href="profile-content.html">家族文化</a
><a href="surname.html">姓氏百科</a><a href="app.html">应用下载</a
><a href="help.html">帮助中心</a><a href="about.html">关于我们</a>
</nav>
<div class="nav-actions">
<a class="active" href="profile.html">个人中心</a
<a class="active" href="profile-data.html">账户资料</a
><a class="btn primary magnetic" href="profile-create-family.html"
>创建家谱</a
>
@@ -33,12 +33,12 @@
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Profile Data</div>
<div class="module-kicker">个人资料</div>
<div class="module-title-row">
<div>
<h1>个人资料</h1>
<p>
对应 APP 的个人资料、亲属、完善资料、绑定账号和成员激活流程
管理个人资料和账号安全信息;家谱成员关系请在当前家谱中维护
</p>
</div>
<a class="btn primary magnetic" href="#profile-edit-form"
@@ -60,8 +60,7 @@
<div class="module-main">
<section class="module-panel">
<h2>编辑资料</h2>
<!-- 个人资料表单:字段对应 /genealogy/app/auth/profile 的 ProfileUpdateBody。 -->
<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-field">
<label for="profileNickName">昵称</label>
@@ -69,22 +68,46 @@
id="profileNickName"
name="nickName"
type="text"
maxlength="30"
autocomplete="nickname"
placeholder="请输入昵称"
/>
</div>
<div class="editor-field">
<label for="profileAvatarOssId">头像 OSS ID</label>
<label for="profileRealName">真实姓名</label>
<input
id="profileAvatarOssId"
name="avatarOssId"
type="number"
min="1"
step="1"
placeholder="上传后填写 OSS ID"
id="profileRealName"
name="realName"
type="text"
maxlength="30"
autocomplete="name"
placeholder="请输入真实姓名"
/>
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="profileAvatarFile">头像</label>
<input
id="profileAvatar"
name="avatar"
type="hidden"
/>
<label class="upload-control" for="profileAvatarFile">选择头像文件</label>
<input
id="profileAvatarFile"
class="upload-input"
type="file"
accept="image/*"
data-upload-target="#profileAvatar"
data-upload-status="#profileAvatarStatus"
data-upload-mode="resumable"
data-upload-biz-type="avatar"
data-upload-usage-scene="profile_avatar"
/>
<p id="profileAvatarStatus" class="upload-status" role="status" aria-live="polite">请选择头像文件</p>
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="profileSex">性别</label>
@@ -92,6 +115,7 @@
<option value="">请选择</option>
<option value="0"></option>
<option value="1"></option>
<option value="2">未知</option>
</select>
</div>
<div class="editor-field">
@@ -99,31 +123,47 @@
<input id="profileBirthday" name="birthday" type="date" />
</div>
</div>
<div class="editor-field">
<label for="profileEmail">邮箱</label>
<input
id="profileEmail"
name="email"
type="email"
maxlength="100"
autocomplete="email"
placeholder="请输入邮箱"
/>
</div>
<div class="bottom-actions profile-form-actions">
<button class="btn primary magnetic" type="submit">保存资料</button>
<span class="form-status" data-profile-status></span>
<button class="btn primary magnetic" type="submit" data-profile-submit>保存资料</button>
<div class="form-status" data-profile-status role="status" aria-live="polite"></div>
</div>
</form>
</section>
<section class="module-panel">
<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">
<p>
<span>头像</span><b>已上传</b
<span>头像</span><b data-profile-text="avatarStatus">正在读取</b
><a class="link-btn" href="profile-data.html">更换</a>
</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>
</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>
</p>
<p>
<span>血亲关系</span><b>第二代 顺字辈</b
><a class="link-btn" href="profile-family-admin.html"
>查看世系</a
<span>家谱关系</span><b>请在当前家谱中查看</b
><a class="link-btn" href="profile-family-admin.html" data-genealogy-context-link
>成员管理</a
>
</p>
</div>
@@ -132,65 +172,68 @@
<h2>个人信息</h2>
<div class="form-like">
<p>
<span>父亲</span><b>某某</b
><a class="link-btn" href="profile-data.html">编辑</a>
<span>性别</span><b data-profile-text="sex">正在读取</b
><a class="link-btn" href="#profile-edit-form">编辑</a>
</p>
<p>
<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>
<span>现居地址</span><b data-profile-text="regionText">待填写</b
><a class="link-btn" href="profile-data.html">填写</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>
<span>邮箱</span><b data-profile-text="email">正在读取</b
><a class="link-btn" href="#profile-edit-form">编辑</a>
</p>
</div>
</section>
<section class="module-panel">
<h2>现居地区</h2>
<!-- 地区资料表单:字段对应 ProfileUpdateBody 的省市区编码。 -->
<form class="editor-form region-profile-form" data-region-profile-form>
<h2 id="profile-region-heading">现居地区</h2>
<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>
<select class="input" name="provinceCode" data-region-level="province">
<option value="">请选择省</option>
</select>
<select class="input" name="cityCode" data-region-level="city">
<option value="">请选择市</option>
</select>
<select class="input" name="districtCode" data-region-level="district">
<option value="">请选择区县</option>
</select>
<label for="profileProvinceCode">
<select id="profileProvinceCode" class="input" name="provinceCode" data-region-level="province">
<option value="">请选择省</option>
</select>
</label>
<label for="profileCityCode">
<select id="profileCityCode" class="input" name="cityCode" data-region-level="city">
<option value="">请选择</option>
</select>
</label>
<label for="profileDistrictCode">区县
<select id="profileDistrictCode" class="input" name="districtCode" data-region-level="district">
<option value="">请选择区县</option>
</select>
</label>
</div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">保存地区</button>
<button class="btn primary magnetic" type="button" disabled>暂不支持保存地区</button>
<span id="profile-region-contract-note" class="form-status">现居地区暂不支持保存到个人资料</span>
</div>
<div id="profile-region-picker-status" class="form-status" data-region-picker-status role="status" aria-live="polite"></div>
</div>
<form class="editor-form" data-region-search-form data-region-search-panel novalidate>
<div class="editor-two">
<div class="editor-field">
<label for="profileRegionKeyword">搜索地区</label>
<input id="profileRegionKeyword" name="regionKeyword" type="search" placeholder="地区名称或编码" aria-describedby="profile-region-search-status" required />
</div>
<div class="bottom-actions">
<button class="btn ghost magnetic" type="submit">搜索</button>
</div>
</div>
<div id="profile-region-search-status" class="form-status" data-region-search-status role="status" aria-live="polite"></div>
</form>
<p class="api-status" data-region-search-path></p>
<div class="module-list" data-region-search-results></div>
</section>
<section class="module-panel">
<h2>亲属</h2>
<h2>亲属关系</h2>
<div class="module-list">
<div class="module-row">
<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 class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">
个人资料页不单独维护亲属关系;请先选择家谱,再到成员管理或世系图中查看和维护。
</div>
</div>
<div class="bottom-actions"><a class="btn ghost magnetic" href="profile-family-admin.html" data-genealogy-context-link>前往成员管理</a></div>
</section>
</div>
</div>
@@ -205,9 +248,12 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/region-pages.js"></script>
<script src="public/js/profile-pages.js"></script>
<script src="public/js/profile-common.js"></script>
<script src="public/js/md5.js"></script>
<script src="public/js/upload-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/page-effects.js"></script>
</body>
</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>
+31 -30
View File
@@ -1,4 +1,4 @@
<!doctype html>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
@@ -11,34 +11,33 @@
<body class="page-profile-module">
<header class="site-header">
<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
>
<nav class="nav-links">
<a href="profile.html">首页</a
><a class="active" href="profile-families.html">数字家谱</a
<a href="profile.html">个人中心</a
><a class="active" href="profile-families.html">我的家谱</a
><a href="plaza.html">家谱广场</a
><a href="profile-content.html">家族文化</a
><a href="surname.html">姓氏百科</a><a href="app.html">应用下载</a
><a href="help.html">帮助中心</a><a href="about.html">关于我们</a>
</nav>
<div class="nav-actions">
<a href="profile.html">个人中心</a
><a class="btn primary magnetic" href="profile-create-family.html"
>创建家谱</a
>
<a href="profile-data.html">账户资料</a>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Personal Center</div>
<nav class="profile-path" aria-label="当前位置">
<a href="profile.html">个人中心</a><span aria-hidden="true">/</span><strong aria-current="page">我的家谱</strong>
</nav>
<div class="module-title-row">
<div>
<h1>我的家谱</h1>
<p>
集中管理你创建或加入的家谱,进入家谱主页后可继续处理世系图、相册、家族圈和成员协作
从真实列表选择要进入的家谱,后续页面会持续使用同一个家谱上下文
</p>
</div>
<a class="btn primary magnetic" href="profile-create-family.html"
@@ -52,30 +51,20 @@
<aside class="module-nav">
<a class="active" href="profile-families.html">我的家谱</a
><a href="profile-messages.html">消息中心</a
><a href="profile-family-admin.html">家族管理</a
><a href="profile-family-admin.html" data-genealogy-context-link>家族管理</a
><a href="profile-content.html">内容发布</a
><a href="profile-data.html">个人资料</a
><a href="profile-services.html">帮助与服务</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>家谱列表</h2>
<p>对应 APP 的“我的家谱”和“家谱主页”</p>
<h2>家谱入口</h2>
<p>先选择要处理的家谱,再进入主页、成员、世系和内容管理</p>
<div class="api-status" data-genealogy-entry-status role="status" aria-live="polite">正在读取家谱额度…</div>
<div class="module-list" data-genealogy-list="mine">
<a class="module-row" href="profile-family-home.html"
><div>
<h3>四川武胜汤氏族</h3>
<p>创建者 · 共修入 7 人 · 最近更新:家族圈动态</p>
</div>
<span class="pill">进入主页</span></a
>
<a class="module-row" href="profile-family-home.html"
><div>
<h3>四川达州刘氏族</h3>
<p>管理员 · 共修入 7 人 · 2 条内容待查看</p>
</div>
<span class="pill">进入主页</span></a
>
<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">
@@ -91,8 +80,20 @@
<a class="module-card tilt-card" href="profile-join-family.html"
><span class="icon"></span>
<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>
</section>
</div>
@@ -107,8 +108,8 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/genealogy-pages.js"></script>
<script src="public/js/profile-common.js"></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>
</body>
</html>
+49 -77
View File
@@ -1,4 +1,4 @@
<!doctype html>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
@@ -11,98 +11,70 @@
<body class="page-profile-module" data-member-admin-page>
<header class="site-header">
<div class="container nav">
<a class="brand magnetic" href="profile.html"
><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a
>
<nav class="nav-links">
<a href="profile.html">首页</a
><a class="active" href="profile-families.html">数字家谱</a
><a href="plaza.html">家谱广场</a
><a href="profile-content.html">家族文化</a
><a href="surname.html">姓氏百科</a><a href="app.html">应用下载</a
><a href="help.html">帮助中心</a><a href="about.html">关于我们</a>
</nav>
<div class="nav-actions">
<a href="profile.html">个人中心</a
><a class="btn primary magnetic" href="profile-create-family.html"
>创建家谱</a
>
</div>
<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>
<div class="nav-actions"><a href="profile-data.html">账户资料</a><a class="btn primary magnetic" href="profile-tree.html" data-genealogy-context-link>世系人物</a></div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Family Admin</div>
<div class="module-kicker">家族管理</div>
<div class="module-title-row">
<div>
<h1 data-member-title>家族管理</h1>
<p>
承接 APP
里的世代管理、管理员权限、入谱审核和邀请绑定等后台能力。
</p>
</div>
<a class="btn primary magnetic" href="profile-data.html"
>添加成员</a
>
<div><h1 data-member-title>家族管理</h1><p>查看家谱成员;具备管理权限时可调整成员信息、角色和世系人物绑定。</p></div>
</div>
</div>
</section>
<section class="section">
<div class="container module-layout">
<aside class="module-nav">
<a href="profile-families.html">我的家谱</a
><a href="profile-messages.html">消息中心</a
><a class="active" href="profile-family-admin.html">家族管理</a
><a href="profile-content.html">内容发布</a
><a href="profile-data.html">个人资料</a
><a href="profile-services.html">帮助与服务</a>
</aside>
<aside class="module-nav"><a href="profile-families.html">我的家谱</a><a href="profile-messages.html">消息中心</a><a class="active" href="profile-family-admin.html" data-genealogy-context-link>家族管理</a><a href="profile-tree.html" data-genealogy-context-link>世系图</a><a href="profile-generation.html" data-genealogy-context-link>字辈谱</a></aside>
<div class="module-main">
<section class="module-panel">
<h2>管理入口</h2>
<p data-member-overview>家谱概览加载中...</p>
<h2>家谱概览</h2>
<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">
<a class="module-card tilt-card" href="profile-tree.html"
><span class="icon"></span>
<h3>世系图</h3>
<p>查看树谱模式、搜索成员、进入个人资料</p></a
>
<a class="module-card tilt-card" href="profile-generation.html"
><span class="icon"></span>
<h3>字辈谱</h3>
<p>维护字辈和各世代人数。</p></a
>
<a class="module-card tilt-card" href="profile-generation.html"
><span class="icon"></span>
<h3>设置世代</h3>
<p>新增、编辑、调整始祖世代。</p></a
>
<a class="module-card tilt-card" href="profile-messages.html"
><span class="icon"></span>
<h3>入谱审核</h3>
<p>审核加入家谱与绑定账号申请。</p></a
>
<a
class="module-card tilt-card"
href="profile-admin-permissions.html"
><span class="icon"></span>
<h3>管理员权限</h3>
<p>分配全权、全系、支系管理员。</p></a
>
<a class="module-card tilt-card" href="profile-invite.html"
><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-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-join-review.html" data-genealogy-context-link><span class="icon"></span><h3>入谱审核</h3><p>审核当前家谱的待处理加入申请。</p></a>
</div>
</section>
<section class="module-panel">
<h2>当前管理</h2>
<!-- 成员列表由 /members 接口渲染,权限按钮会提交 /members/{memberId} -->
<div class="module-list" data-member-list>
<div class="api-empty">成员加载中...</div>
</div>
<h2>正常成</h2>
<p>成员新增必须通过后端已有的加入或邀请流程;本页不伪造直接新增成员。</p>
<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 class="module-panel" data-member-management hidden>
<h2 data-member-form-title>编辑成员</h2>
<form class="editor-form" data-member-form novalidate hidden>
<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="memberAdminRelation">与家谱关系</label><input id="memberAdminRelation" name="relationName" type="text" maxlength="100" /></div>
</div>
<div class="editor-two">
<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>
<p class="upload-status">留空表示保持原信息不变;只填写本次需要修改的内容。</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" data-member-submit>保存成员</button><button class="btn ghost" type="reset">取消</button></div>
</form>
</section>
<section class="module-panel" data-owner-transfer-section hidden>
<h2>转让谱主</h2>
<form class="editor-form" data-owner-transfer-form novalidate>
<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>
<div class="upload-status" role="status" aria-live="polite" data-owner-transfer-status></div>
<div class="bottom-actions"><button class="btn ghost" type="submit" data-owner-transfer-submit>确认转让谱主</button></div>
</form>
</section>
<section class="module-panel" data-member-leave-section hidden>
<h2>退出家谱</h2>
<p>退出只停用当前账号的成员关系,不删除世系人物资料;谱主必须先转让谱主。</p>
<div class="bottom-actions"><button class="btn ghost" type="button" data-member-leave>退出当前家谱</button></div>
</section>
</div>
</div>
@@ -111,7 +83,7 @@
<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="public/js/profile-common.js?v=20260725-entry"></script>
<script src="config.js"></script>
<script src="utils/StorageUtil.js"></script>
<script src="utils/axios.js"></script>
+114 -33
View File
@@ -11,34 +11,36 @@
<body class="page-profile-module" data-family-home-page>
<header class="site-header">
<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
>
<nav class="nav-links">
<a class="active" href="profile-families.html">我的家谱</a
><a href="profile-family-admin.html">家族管理</a
><a href="profile-content.html">内容发布</a
><a href="profile-family-admin.html" data-genealogy-context-link>家族管理</a
><a href="profile-content.html" data-genealogy-context-link>内容发布</a
><a href="profile-data.html">个人资料</a
><a href="profile-services.html">帮助与服务</a>
</nav>
<div class="nav-actions">
<a href="profile.html">返回中心</a
><a class="btn primary magnetic" href="profile-feed.html">发布动态</a>
<a href="profile.html">账户中心</a
><a class="btn primary magnetic" href="profile-feed.html" data-feature-link="available" data-genealogy-context-link>发布动态</a>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Family Home</div>
<nav class="profile-path" aria-label="当前位置">
<a href="profile.html">个人中心</a><span aria-hidden="true">/</span><a href="profile-families.html">我的家谱</a><span aria-hidden="true">/</span><strong data-family-home-context aria-current="page">当前家谱</strong>
</nav>
<div class="module-title-row">
<div>
<h1 data-family-home-title>四川武胜汤氏族</h1>
<h1 data-family-home-title>正在读取家谱</h1>
<p>
后台家谱主页,用于进入世系、相册、家族圈、内容管理和成员协作。
当前家谱的工作区,用于进入世系、相册、家族圈、内容管理和成员协作。
</p>
</div>
<a class="btn ghost magnetic" href="profile-family-admin.html"
<a class="btn ghost magnetic" href="profile-family-admin.html" data-family-home-management data-genealogy-context-link hidden
>管理家谱</a
>
</div>
@@ -47,56 +49,133 @@
<section class="section">
<div class="container module-layout">
<aside class="module-nav">
<a class="active" href="profile-family-home.html">家谱主页</a
><a href="profile-tree.html">世系图</a
><a href="profile-album.html">相册</a
><a href="profile-content.html">内容发布</a
><a href="profile-family-admin.html">家族管理</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-album.html" data-feature-link="available" data-genealogy-context-link>相册</a
><a href="profile-content.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>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>家谱概览</h2>
<div class="module-grid">
<div class="module-card">
<span class="icon"></span>
<h3>家谱编号</h3>
<p data-family-home-summary>家谱信息加载中...</p>
<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>
<a class="module-card tilt-card" href="profile-tree.html"
><span class="icon"></span>
<h3>世系图</h3>
<p>查看树谱模式和成员关系。</p></a
><a class="module-card tilt-card" href="profile-invite.html"
><span class="icon"></span>
<div class="module-card">
<span class="icon"></span>
<h3>家谱成员</h3>
<p><strong data-family-home-member-count>--</strong></p>
</div>
<div class="module-card">
<span class="icon"></span>
<h3>世系人物</h3>
<p><strong data-family-home-person-count>--</strong></p>
</div>
<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>
<p>生成邀请入口,邀请亲人协作</p></a
>
<p>生成一次性邀请链接,指定人物并查看邀请状态</p>
</a>
</div>
<div class="api-status" data-family-home-status role="status" aria-live="polite">正在读取家谱概览</div>
</section>
<section class="module-panel">
<h2>世系预览</h2>
<!-- 家谱主页只展示世系树预览,完整维护进入世系图页面 -->
<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>
</section>
<section class="module-panel">
<h2>常用入口</h2>
<h2>家族文化</h2>
<p>围绕当前家谱集中维护谱文、影像与家族资料。</p>
<div class="module-grid">
<a class="module-card tilt-card" href="profile-article.html"
<a class="module-card tilt-card" href="profile-article.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-video.html"
><a class="module-card tilt-card" href="profile-album.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-video.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"
>
</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
><span class="icon"></span>
<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
><span class="icon"></span>
<h3>祭祀活动</h3>
<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>
</section>
</div>
@@ -106,13 +185,15 @@
<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="public/js/profile-common.js?v=20260725-entry"></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/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>
</body>
</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>
+95
View File
@@ -0,0 +1,95 @@
<!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-feed-detail-page>
<header class="site-header">
<div class="container nav">
<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-family-home.html">家谱主页</a
><a class="active" href="profile-feed.html" data-feed-list-link data-genealogy-context-link>家族圈</a>
</nav>
<div class="nav-actions">
<a href="profile-feed.html" data-feed-list-link 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-content.html">内容发布</a
><a class="active" href="profile-feed.html" data-feed-list-link data-genealogy-context-link>家族圈</a
><a href="profile-album.html">相册</a
><a href="profile-video.html">视频</a>
</aside>
<div class="module-main">
<section class="module-panel" data-feed-detail>
<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 class="module-panel">
<h2>评论与回复</h2>
<div data-feed-detail-comments>
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">
正在加载评论…
</div>
</div>
</section>
</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/media-display.js"></script>
<script src="public/js/feed-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+30 -29
View File
@@ -7,11 +7,12 @@
<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/js/wangeditor5/css/style.css" />
</head>
<body class="page-profile-module" data-feed-edit-page>
<header class="site-header">
<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
>
<nav class="nav-links">
@@ -19,19 +20,19 @@
><a class="active" href="profile-feed.html">发布动态</a>
</nav>
<div class="nav-actions">
<a href="profile-feed.html">返回动态</a
><button class="btn primary magnetic" type="submit" form="feed-edit-form">发布</button>
<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" data-feed-editor-action hidden disabled>发布</button>
</div>
</div>
</header>
<main>
<section class="module-hero">
<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>
<h1>发布动态</h1>
<p>面向家族圈发布图文动态,可设置置顶和精华</p>
<h1 data-feed-editor-title>发布动态</h1>
<p>面向家族圈发布文字和图片动态,与家人交流近况</p>
</div>
</div>
</div>
@@ -47,45 +48,42 @@
<div class="module-main">
<section class="module-panel">
<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" />
<div class="editor-field">
<label for="feedContent">内容</label
<label for="feedContent">内容 <span class="field-required" aria-hidden="true">*</span></label
><textarea
id="feedContent"
name="content"
name="feedContent"
class="js-rich-editor"
placeholder="请输入动态内容"
required
></textarea>
</div>
<div class="editor-field">
<label for="feedMedia">图片 OSS ID</label>
<input id="feedMedia" name="mediaOssIds" type="text" placeholder="多个图片 ID 用英文逗号分隔" />
<!-- 多图上传会追加 OSS ID 到 mediaOssIds 字段 -->
<label for="feedMediaFile">动态图片</label>
<input id="feedMedia" name="mediaOssIds" type="hidden" />
<label class="upload-control" for="feedMediaFile">选择动态图片</label>
<input id="feedMediaFile" class="upload-input" type="file" accept="image/*" data-upload-target="#feedMedia" data-upload-status="#feedMediaStatus" data-upload-multiple="true" />
<p id="feedMediaStatus" class="upload-status">未选择文件</p>
<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" role="status" aria-live="polite">未选择图片</p>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="feedVisibility">可见范围</label>
<select id="feedVisibility" name="visibility">
<option value="2">成员可见</option>
<option value="1">公开</option>
<option value="0">私密</option>
</select>
</div>
<div class="editor-two" data-feed-advanced hidden>
<div class="editor-field">
<label for="feedStatus">状态</label>
<select id="feedStatus" name="status">
<option value="0">发布</option>
<option value="1">草稿</option>
<select id="feedStatus" name="status" disabled>
<option value="0">正常</option>
<option value="1">停用</option>
</select>
</div>
<div class="editor-field">
<label for="feedSortOrder">排序值</label>
<input id="feedSortOrder" name="sortOrder" type="number" min="0" value="0" disabled />
</div>
</div>
<div class="upload-status" role="status" aria-live="polite" data-feed-form-status></div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">发布</button
><a class="btn ghost magnetic" href="profile-feed.html"
<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
>
</div>
@@ -98,7 +96,7 @@
<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/ke/kindeditor.min.js"></script>
<script src="public/js/wangeditor5/index.js"></script>
<script src="public/js/rich-editor.js"></script>
<script src="public/js/profile-common.js"></script>
<script src="config.js"></script>
@@ -107,7 +105,10 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.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/media-display.js"></script>
<script src="public/js/feed-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
+24 -32
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<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/layui/css/layui.css" />
<link rel="stylesheet" href="public/css/profile-module.css" />
@@ -11,17 +11,17 @@
<body class="page-profile-module" data-feed-page>
<header class="site-header">
<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
>
<nav class="nav-links">
<a href="profile-family-home.html">家谱主页</a
><a href="profile-content.html">内容发布</a
><a class="active" href="profile-feed.html">发布动态</a>
><a class="active" href="profile-feed.html">家族圈</a>
</nav>
<div class="nav-actions">
<a href="profile-family-home.html">返回家谱主页</a
><a class="btn primary magnetic" href="profile-feed-edit.html"
><a class="btn primary magnetic" href="profile-feed-edit.html" data-feed-create-link data-genealogy-context-link hidden
>发布</a
>
</div>
@@ -30,11 +30,11 @@
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Family Feed</div>
<div class="module-kicker">家族圈</div>
<div class="module-title-row">
<div>
<h1>发布动态</h1>
<p>向家族圈发布图文动态,可设置置顶和精华</p>
<h1>家族圈</h1>
<p>向家族圈发布文字和图片动态,与家人交流近况</p>
</div>
</div>
</div>
@@ -43,45 +43,31 @@
<div class="container module-layout">
<aside class="module-nav">
<a href="profile-content.html">内容发布</a
><a class="active" href="profile-feed.html">发布动态</a
><a class="active" href="profile-feed.html">家族圈</a
><a href="profile-album.html">相册</a
><a href="profile-video.html">视频</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>动态草稿</h2>
<h2>发布动态</h2>
<div class="form-like">
<p>
<span>内容</span><b>0/200</b
><a class="link-btn" href="profile-feed-edit.html">编辑</a>
<span>内容</span><b>在发布页填写</b
><a class="link-btn" href="profile-feed-edit.html" data-feed-create-link data-genealogy-context-link hidden>编辑</a>
</p>
<p>
<span>图片</span><b>添加多张</b
><a
class="link-btn"
href="#"
data-layer-msg="请选择要上传的图片"
>添加</a
<span>图片</span><b>直接选择并上传多张图片</b
><a class="link-btn" href="profile-feed-edit.html" data-feed-create-link data-genealogy-context-link hidden>添加</a
>
</p>
<p>
<span>置顶</span><b></b
><a
class="link-btn"
href="#"
data-layer-select="是|否"
data-select-title="切换状态"
>切换</a
<span>动态类型</span><b>图文动态</b
><a class="link-btn" href="profile-feed-edit.html" data-feed-create-link data-genealogy-context-link hidden>编辑</a
>
</p>
<p>
<span>精华</span><b></b
><a
class="link-btn"
href="#"
data-layer-select="是|否"
data-select-title="切换状态"
>切换</a
<span>发布状态</span><b>正常或停用</b
><a class="link-btn" href="profile-feed-edit.html" data-feed-create-link data-genealogy-context-link hidden>设置</a
>
</p>
</div>
@@ -90,7 +76,12 @@
<h2>最近动态</h2>
<!-- 动态列表由 /feeds/page 接口渲染,点赞和评论走对应子接口 -->
<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 class="row-actions">
<button class="pill" type="button" data-feed-page-prev disabled>上一页</button>
<span data-feed-page-label>第 1 页</span>
<button class="pill" type="button" data-feed-page-next disabled>下一页</button>
</div>
</section>
</div>
@@ -106,6 +97,7 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.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/page-effects.js"></script>
</body>
+18 -27
View File
@@ -8,10 +8,10 @@
<link rel="stylesheet" href="public/layui/css/layui.css" />
<link rel="stylesheet" href="public/css/profile-module.css" />
</head>
<body class="page-profile-module">
<body class="page-profile-module" data-feedback-page="profile">
<header class="site-header">
<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
>
<nav class="nav-links">
@@ -19,15 +19,14 @@
><a class="active" href="profile-feedback.html">意见反馈</a>
</nav>
<div class="nav-actions">
<a href="profile-services.html">返回服务中心</a
><button class="btn primary magnetic" type="submit" form="profileFeedbackForm">提交</button>
<a href="profile-services.html">返回服务中心</a>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Feedback</div>
<div class="module-kicker">意见反馈</div>
<div class="module-title-row">
<div>
<h1>意见反馈</h1>
@@ -40,21 +39,19 @@
<div class="container module-layout">
<aside class="module-nav">
<a href="profile-services.html">帮助与服务</a
><a href="profile-share.html">应用分享</a
><span class="module-nav-unavailable">应用分享<small>即将开放</small></span
><a class="active" href="profile-feedback.html">意见反馈</a
><a href="profile-security.html">安全设置</a>
</aside>
<div class="module-main">
<section class="module-panel">
<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-field">
<label for="feedbackType">反馈类型</label>
<select id="feedbackType" name="feedbackType">
<option value="suggestion">改进建议</option>
<option value="bug">问题反馈</option>
<option value="data">资料异常</option>
<select id="feedbackType" name="feedbackType" data-feedback-type-options>
<option value="">反馈类型加载中…</option>
</select>
</div>
<div class="editor-field">
@@ -63,28 +60,22 @@
</div>
</div>
<div class="editor-field">
<label for="feedbackTitle">反馈标题</label>
<input id="feedbackTitle" name="feedbackTitle" type="text" placeholder="请填写反馈标题" />
</div>
<div class="editor-field">
<label for="feedbackContent">反馈描述</label>
<textarea id="feedbackContent" name="feedbackContent" placeholder="请描述问题、页面位置和期望结果"></textarea>
<label for="feedbackContent">反馈描述 <span class="field-required" aria-hidden="true">*</span></label>
<textarea id="feedbackContent" name="feedbackContent" required placeholder="请描述问题、页面位置和期望结果"></textarea>
</div>
<div class="form-status" role="status" aria-live="polite" data-feedback-status></div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">提交反馈</button>
<button class="btn primary magnetic" type="submit" data-feedback-submit>提交反馈</button>
</div>
</form>
</section>
<section class="module-panel">
<h2>历史反馈</h2>
<div class="module-title-row">
<h2>历史反馈</h2>
<button class="btn ghost" type="button" data-feedback-refresh>刷新</button>
</div>
<div class="module-list" data-feedback-list>
<div class="module-row">
<div>
<h3>资料导入建议</h3>
<p>已受理 · 2026.05</p>
</div>
<span class="pill">查看</span>
</div>
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取反馈记录…</div>
</div>
</section>
</div>
@@ -99,8 +90,8 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/feedback-pages.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>
</body>
</html>
+55 -66
View File
@@ -8,32 +8,26 @@
<link rel="stylesheet" href="public/layui/css/layui.css" />
<link rel="stylesheet" href="public/css/profile-module.css" />
</head>
<body class="page-profile-module">
<body class="page-profile-module" data-generation-page>
<header class="site-header">
<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
>
<nav class="nav-links">
<a href="profile-families.html">我的家谱</a
><a class="active" href="profile-family-admin.html">家族管理</a>
><a class="active" href="profile-family-admin.html" data-genealogy-context-link>家族管理</a>
</nav>
<div class="nav-actions">
<a href="profile-family-admin.html">返回管理</a
><a
class="btn primary magnetic"
href="#"
data-layer-confirm="进入字辈管理模式?"
data-confirm-title="管理字辈"
>管理</a
>
<a href="profile-family-admin.html" data-genealogy-context-link>返回管理</a
><a class="btn primary magnetic" href="#generation-content">管理</a>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Generation</div>
<div class="module-kicker">字辈管理</div>
<div class="module-title-row">
<div>
<h1>字辈谱</h1>
@@ -43,6 +37,9 @@
class="btn primary magnetic"
type="button"
data-generation-add
data-generation-management
hidden
disabled
>新增字辈</button>
</div>
</div>
@@ -50,31 +47,31 @@
<section class="section">
<div class="container module-layout">
<aside class="module-nav">
<a href="profile-tree.html">世系图</a
><a class="active" href="profile-generation.html">字辈谱</a
><a href="profile-family-admin.html">家族管理</a>
<a href="profile-tree.html" data-genealogy-context-link>世系图</a
><a class="active" href="profile-generation.html" data-genealogy-context-link>字辈谱</a
><a href="profile-family-admin.html" data-genealogy-context-link>家族管理</a>
</aside>
<div class="module-main">
<section class="module-panel generation-batch-panel">
<div id="generation-content" class="module-main">
<section class="module-panel generation-batch-panel" data-generation-management hidden>
<h2>批量字辈</h2>
<!-- 批量字辈表单:字段对应 GenerationPoemBatchBody。 -->
<form class="editor-form generation-batch-form" data-generation-batch-form>
<!-- 批量字辈表单:字段对应 Apifox 的 GenerationPoemBatchBody。 -->
<form class="editor-form generation-batch-form" data-generation-batch-form novalidate>
<div class="editor-field">
<label for="generationBatchContent">字辈内容</label>
<label for="generationBatchContent">字辈内容 <span class="field-required" aria-hidden="true">*</span></label>
<textarea
id="generationBatchContent"
name="content"
name="poemText"
rows="4"
placeholder="例如:德承家亦"
placeholder="例如:德 承 家 亦(用空格或标点分隔)"
></textarea>
</div>
<label class="check-row generation-batch-check" for="generationStopMissing">
<input
id="generationStopMissing"
name="stopMissingOldGeneration"
name="disableMissing"
type="checkbox"
/>
<span>遇到缺失旧世代时停止导入</span>
<span>保存时停用未出现在文本中的后续世代(不删除历史记录)</span>
</label>
<div class="bottom-actions generation-batch-actions">
<button
@@ -87,55 +84,47 @@
type="button"
data-generation-batch-action="save"
>批量保存</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>
</form>
<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>
</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">
<h2>字辈列表</h2>
<div class="module-list" data-generation-list>
<div class="module-row">
<div>
<h3>第 1 代:代</h3>
<p>1 人</p>
</div>
<a
class="pill"
href="#"
data-layer-prompt="请输入新的字辈"
data-prompt-title="编辑字辈"
>编辑</a
>
</div>
<div class="module-row">
<div>
<h3>第 2 代:顺</h3>
<p>2 人</p>
</div>
<a
class="pill"
href="#"
data-layer-prompt="请输入新的字辈"
data-prompt-title="编辑字辈"
>编辑</a
>
</div>
<div class="module-row">
<div>
<h3>第 3 代:万</h3>
<p>3 人</p>
</div>
<a
class="pill"
href="#"
data-layer-prompt="请输入新的字辈"
data-prompt-title="编辑字辈"
>编辑</a
>
</div>
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在加载字辈记录…</div>
</div>
</section>
</div>
@@ -145,7 +134,7 @@
<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="public/js/profile-common.js?v=20260725-entry"></script>
<script src="config.js"></script>
<script src="utils/StorageUtil.js"></script>
<script src="utils/axios.js"></script>
+50 -33
View File
@@ -3,35 +3,36 @@
<head>
<meta charset="utf-8" />
<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/layui/css/layui.css" />
<link rel="stylesheet" href="public/css/profile-module.css" />
<link rel="stylesheet" href="public/js/wangeditor5/css/style.css" />
</head>
<body class="page-profile-module" data-ceremony-edit-page>
<header class="site-header">
<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
>
<nav class="nav-links">
<a href="profile-content.html">内容发布</a
><a class="active" href="profile-gift.html">贺礼邀请</a>
><a class="active" href="profile-ceremony.html">祭祀活动</a>
</nav>
<div class="nav-actions">
<a href="profile-gift.html">返回贺礼</a
><button class="btn primary magnetic" type="submit" form="ceremony-edit-form">确定创建</button>
<a href="profile-ceremony.html">返回活动列表</a
><button class="btn primary magnetic" type="submit" form="ceremony-edit-form" data-ceremony-editor-action hidden>确定创建</button>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Gift Editor</div>
<div class="module-kicker">活动编辑</div>
<div class="module-title-row">
<div>
<h1>创建贺礼</h1>
<p>维护贺礼类型、标题、时间地点、通知人员和邀请正文</p>
<h1 data-ceremony-editor-title>创建祭祀活动</h1>
<p>维护活动类型、标题、时间地点和说明;受邀名单在活动详情中选择家谱成员</p>
</div>
</div>
</div>
@@ -41,36 +42,31 @@
<aside class="module-nav">
<a href="profile-content.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>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>贺礼信息</h2>
<form id="ceremony-edit-form" class="editor-form layui-form" data-ceremony-form>
<h2>活动信息</h2>
<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 novalidate>
<div class="editor-two">
<div class="editor-field">
<label for="giftType">贺礼类型</label
><select id="giftType" name="ceremonyType">
<option value="wedding">婚礼</option>
<option value="education">升学</option>
<option value="birthday">生日</option>
<option value="ancestor">祭祀</option>
<option value="other">其他</option>
</select>
<label for="giftType">活动类型 <span class="field-required" aria-hidden="true">*</span></label
><input id="giftType" name="ceremonyType" type="text" placeholder="请输入后端约定的活动类型" required />
</div>
<div class="editor-field">
<label for="giftDate">举办时间</label
><input
id="giftDate"
name="ceremonyTime"
type="text"
type="datetime-local"
placeholder="请选择时间"
/>
</div>
</div>
<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 />
</div>
<div class="editor-field">
@@ -83,25 +79,43 @@
/>
</div>
<div class="editor-field">
<label for="giftContent">贺礼内容</label
><textarea
<label for="giftLocationAddress">详细地址</label
><input id="giftLocationAddress" name="locationAddress" type="text" maxlength="300" placeholder="最多 300 个字符" />
</div>
<div class="editor-two">
<div class="editor-field">
<label for="giftLongitude">经度</label
><input id="giftLongitude" name="longitude" type="number" min="-180" max="180" step="any" placeholder="与纬度同时填写" />
</div>
<div class="editor-field">
<label for="giftLatitude">纬度</label
><input id="giftLatitude" name="latitude" type="number" min="-90" max="90" step="any" placeholder="与经度同时填写" />
</div>
</div>
<div class="editor-field">
<label for="giftContent">活动说明</label
><textarea class="js-rich-editor"
id="giftContent"
name="ceremonyDesc"
class="js-rich-editor"
placeholder="请输入贺礼内容"
placeholder="请输入活动说明"
></textarea>
</div>
<div class="editor-field">
<label for="giftCoverOssId">封面 OSS ID</label>
<input id="giftCoverOssId" name="coverOssId" type="text" placeholder="上传封面后自动回填" />
<!-- 公共上传脚本会调用 /files/upload 并回填封面 OSS ID -->
<label for="giftCoverFile">封面文件</label>
<input id="giftCoverOssId" name="coverOssId" type="hidden" />
<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" />
<p id="giftCoverStatus" class="upload-status">未选择文件</p>
<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" role="status" aria-live="polite">未选择文件</p>
</div>
<div class="editor-field">
<label for="giftSortOrder">排序值</label
><input id="giftSortOrder" name="sortOrder" type="number" step="1" placeholder="可选,数值越小越靠前" />
</div>
<input name="status" type="hidden" value="0" />
<div class="upload-status" data-ceremony-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-gift.html"
><a class="btn ghost magnetic" href="profile-ceremony.html"
>取消</a
>
</div>
@@ -114,7 +128,7 @@
<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/ke/kindeditor.min.js"></script>
<script src="public/js/wangeditor5/index.js"></script>
<script src="public/js/rich-editor.js"></script>
<script src="public/js/profile-common.js"></script>
<script src="config.js"></script>
@@ -123,8 +137,11 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.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/ceremony-pages.js"></script>
<script src="public/js/media-display.js"></script>
<script src="public/js/ceremony-admin-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+21 -35
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-ceremony-page>
<header class="site-header">
<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
>
<nav class="nav-links">
@@ -19,22 +19,19 @@
><a class="active" href="profile-gift.html">贺礼邀请</a>
</nav>
<div class="nav-actions">
<a href="profile-content.html">返回发布中心</a
><a class="btn primary magnetic" href="profile-gift-edit.html"
>创建贺礼</a
>
<a href="profile-content.html">返回发布中心</a>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Invitation</div>
<div class="module-kicker">贺礼邀请</div>
<div class="module-title-row">
<div>
<h1>贺礼邀请</h1>
<p>
管理婚礼、升学、生日和其他家族贺礼邀请,可进入管理模式删除或编辑
查看发给当前账号的活动邀请,并直接接受或拒绝;具备权限时可进入活动管理
</p>
</div>
</div>
@@ -50,38 +47,27 @@
</aside>
<div class="module-main">
<section class="module-panel">
<h2>邀请列表</h2>
<!-- 贺礼/祭祀列表由 /ceremonies 接口渲染 -->
<div class="module-list" data-ceremony-list>
<div class="api-empty">贺礼邀请加载中...</div>
<h2>我的邀请</h2>
<div class="module-list" data-my-invitation-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>
<div class="form-status" data-my-invitation-status role="status" aria-live="polite"></div>
</section>
<section class="module-panel">
<div class="module-panel-head">
<h2>关联活动</h2>
<a class="pill" href="profile-ceremony.html" data-genealogy-context-link>管理关联活动</a>
</div>
</section>
<section class="module-panel">
<h2>献礼记录</h2>
<!-- 选择贺礼后由 /ceremonies/{ceremonyId}/gifts 接口渲染 -->
<div class="module-list" data-ceremony-gift-list>
<div class="api-empty">请选择贺礼查看献礼记录</div>
<h2>邀请详情</h2>
<div data-my-invitation-detail aria-live="polite">
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">
请选择一条邀请查看详情
</div>
</div>
<form class="editor-form ceremony-gift-form" data-ceremony-gift-form>
<!-- 字段名称对应 APP.openapi.json 的 CeremonyGiftBody -->
<div class="editor-two">
<div class="editor-field">
<label for="giftGiverName">献礼人</label>
<input id="giftGiverName" name="giverName" type="text" placeholder="请输入献礼人姓名" />
</div>
<div class="editor-field">
<label for="giftAmount">礼金金额</label>
<input id="giftAmount" name="giftAmount" type="number" step="0.01" placeholder="请输入金额" />
</div>
</div>
<div class="editor-field">
<label for="giftMessage">献礼留言</label>
<textarea id="giftMessage" name="giftMessage" placeholder="请输入留言"></textarea>
</div>
<div class="bottom-actions">
<button class="btn primary" type="submit">保存献礼</button>
</div>
</form>
</section>
</div>
</div>
+49 -34
View File
@@ -3,35 +3,36 @@
<head>
<meta charset="utf-8" />
<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/layui/css/layui.css" />
<link rel="stylesheet" href="public/css/profile-module.css" />
<link rel="stylesheet" href="public/js/wangeditor5/css/style.css" />
</head>
<body class="page-profile-module" data-growth-edit-page>
<header class="site-header">
<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
>
<nav class="nav-links">
<a href="profile-content.html">内容发布</a
><a class="active" href="profile-growth.html">成长日志</a>
><a class="active" href="profile-growth.html" data-genealogy-context-link>成长记录</a>
</nav>
<div class="nav-actions">
<a href="profile-growth.html">返回日志</a
><button class="btn primary magnetic" type="submit" form="growth-edit-form">保存</button>
<a href="profile-growth.html" data-genealogy-context-link>返回记录</a
><button class="btn primary magnetic" type="submit" form="growth-edit-form" data-growth-editor-action hidden>保存</button>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Growth Editor</div>
<div class="module-kicker">编辑成长记录</div>
<div class="module-title-row">
<div>
<h1>新建成长日志</h1>
<p>记录成员成长节点、特殊情况、照片附件和补充资料</p>
<h1 data-growth-editor-title>新建成长记录</h1>
<p>人物从当前家谱世系中选择,附件由上传组件自动生成内部编号</p>
</div>
</div>
</div>
@@ -41,46 +42,42 @@
<aside class="module-nav">
<a href="profile-content.html">内容发布</a
><a href="profile-feed.html">发布动态</a
><a class="active" href="profile-growth.html">成长日志</a
><a class="active" href="profile-growth.html" data-genealogy-context-link>成长记录</a
><a href="profile-memo.html">备忘录</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>日志内容</h2>
<form id="growth-edit-form" class="editor-form layui-form" data-growth-form>
<h2>记录内容</h2>
<form id="growth-edit-form" class="editor-form" data-growth-form data-growth-editor hidden novalidate>
<div class="editor-two">
<div class="editor-field">
<label for="growthPersonId">世系人物 ID</label
><input
<label for="growthPersonId">关联世系人物</label
><select
id="growthPersonId"
name="lineagePersonId"
type="text"
placeholder="请输入世系人物 ID"
/>
data-growth-lineage-person
>
<option value="">正在加载世系人物…</option>
</select>
</div>
<div class="editor-field">
<label for="growthDate">日期</label
><input
id="growthDate"
name="recordDate"
type="text"
type="date"
placeholder="请选择日期"
/>
</div>
</div>
<div class="editor-two">
<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 />
</div>
<div class="editor-field">
<label for="growthType">类型</label>
<select id="growthType" name="recordType">
<option value="growth">成长</option>
<option value="birth">出生</option>
<option value="education">教育</option>
<option value="family">家庭</option>
</select>
<input id="growthType" name="recordType" type="text" placeholder="可选;按后端定义填写类型" />
</div>
</div>
<div class="editor-field">
@@ -89,24 +86,39 @@
id="growthContent"
name="recordContent"
class="js-rich-editor"
placeholder="请输入成长日志内容"
placeholder="请输入成长记录内容"
></textarea>
</div>
<div class="editor-field">
<label for="growthMediaOssIds">附件 OSS ID</label>
<input id="growthMediaOssIds" name="mediaOssIds" type="text" placeholder="多个附件 ID 用英文逗号分隔" />
<!-- 公共上传脚本会调用 /files/upload 并追加 OSS ID -->
<label class="upload-control" for="growthMediaFile">添加照片</label>
<input id="growthMediaFile" class="upload-input" type="file" accept="image/*" data-upload-target="#growthMediaOssIds" data-upload-status="#growthMediaStatus" data-upload-multiple="true" />
<p id="growthMediaStatus" class="upload-status">未选择文件</p>
<label for="growthRemindTime">提醒时间</label>
<input id="growthRemindTime" name="remindTime" type="datetime-local" />
</div>
<div class="editor-two">
<div class="editor-field">
<label for="growthMediaFiles">附件文件</label>
<input id="growthMediaOssIds" name="mediaOssIds" type="hidden" />
<label class="upload-control" for="growthMediaFiles">选择附件</label>
<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 role="status" aria-live="polite">未选择文件</p>
<button class="pill" type="button" data-growth-clear-media>清除附件</button>
</div>
<div class="editor-field">
<label for="growthSortOrder">排序值</label>
<input id="growthSortOrder" name="sortOrder" type="number" step="1" placeholder="可选;安全整数" />
</div>
</div>
<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">
<button class="btn primary magnetic" type="submit">保存</button
><a class="btn ghost magnetic" href="profile-growth.html"
><a class="btn ghost magnetic" href="profile-growth.html" data-genealogy-context-link
>取消</a
>
</div>
</form>
<div data-growth-editor-placeholder>
<div class="api-state api-state--loading" role="status" aria-live="polite" aria-busy="true">正在加载成长记录编辑信息…</div>
</div>
</section>
</div>
</div>
@@ -115,16 +127,19 @@
<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/ke/kindeditor.min.js"></script>
<script src="public/js/wangeditor5/index.js"></script>
<script src="public/js/rich-editor.js"></script>
<script src="public/js/profile-common.js"></script>
<script src="public/js/profile-common.js?v=20260725-entry"></script>
<script src="config.js"></script>
<script src="utils/StorageUtil.js"></script>
<script src="utils/FormUtil.js"></script>
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.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/media-display.js"></script>
<script src="public/js/growth-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
+16 -27
View File
@@ -11,17 +11,17 @@
<body class="page-profile-module" data-growth-page>
<header class="site-header">
<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
>
<nav class="nav-links">
<a href="profile-content.html">内容发布</a
><a class="active" href="profile-growth.html">成长日志</a>
><a class="active" href="profile-growth.html" data-genealogy-context-link>成长记录</a>
</nav>
<div class="nav-actions">
<a href="profile-content.html">返回发布中心</a
><a class="btn primary magnetic" href="profile-growth-edit.html"
>新建日志</a
><a class="btn primary magnetic" href="profile-growth-edit.html" data-genealogy-context-link data-growth-create-link hidden
>新建记录</a
>
</div>
</div>
@@ -29,11 +29,11 @@
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Growth</div>
<div class="module-kicker">成长记录</div>
<div class="module-title-row">
<div>
<h1>成长日志</h1>
<p>记录成员成长节点、特殊情况、继承关系和个人资料补充</p>
<h1>成长记录</h1>
<p>记录家族成员成长节点、日期、提醒和附件</p>
</div>
</div>
</div>
@@ -43,32 +43,20 @@
<aside class="module-nav">
<a href="profile-content.html">内容发布</a
><a href="profile-feed.html">发布动态</a
><a class="active" href="profile-growth.html">成长日志</a
><a class="active" href="profile-growth.html" data-genealogy-context-link>成长记录</a
><a href="profile-memo.html">备忘录</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>日志列表</h2>
<!-- 成长记录由 /growth-records 接口渲染 -->
<div class="module-list" data-growth-list>
<div class="api-empty">成长记录加载中...</div>
<h2>记录列表</h2>
<div class="module-list" data-growth-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 class="module-panel">
<h2>新建成长日志</h2>
<div class="form-like">
<p>
<span>绑定账号</span><b>点击绑定</b
><a class="link-btn" href="profile-data.html">绑定</a>
</p>
<p>
<span>姓名</span><b>待输入</b
><a class="link-btn" href="profile-growth-edit.html">填写</a>
</p>
<p>
<span>内容</span><b>待输入</b
><a class="link-btn" href="profile-growth-edit.html">编辑</a>
</p>
<h2 tabindex="-1" data-growth-detail-heading>记录详情</h2>
<div data-growth-detail aria-live="polite">
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一条成长记录查看详情。</div>
</div>
</section>
</div>
@@ -78,12 +66,13 @@
<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="public/js/profile-common.js?v=20260725-entry"></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/media-display.js"></script>
<script src="public/js/growth-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
+17 -16
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-family-invite-page>
<header class="site-header">
<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
>
<nav class="nav-links">
@@ -26,13 +26,13 @@
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Invite</div>
<div class="module-kicker">邀请家人</div>
<div class="module-title-row">
<div>
<h1>邀请家人</h1>
<p>生成家谱邀请链接或邀请码,引导亲人加入家谱并激活账号。</p>
</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>
</section>
@@ -45,19 +45,20 @@
</aside>
<div class="module-main">
<section class="module-panel">
<h2>邀请方式</h2>
<div class="module-grid two">
<div class="module-card">
<span class="icon"></span>
<h3>邀请码</h3>
<p>当前邀请码:<b data-invite-code>加载中...</b></p>
</div>
<div class="module-card">
<span class="icon"></span>
<h3>邀请链接</h3>
<p data-invite-link>邀请链接加载中...</p>
</div>
<h2>生成邀请</h2>
<form class="editor-form" data-invite-form novalidate>
<div class="editor-field"><label for="invitePerson">关联人物(可选)</label><select id="invitePerson" data-invite-person><option value="">人物加载中</option></select></div>
<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>
</form>
<div class="editor-form" data-invite-result hidden>
<div class="editor-field"><label for="inviteLink">一次性邀请链接</label><input id="inviteLink" data-invite-link readonly /></div>
<button class="btn outline" type="button" data-invite-copy>复制链接</button>
</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>
</div>
</div>
@@ -72,7 +73,7 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/member-admin-pages.js"></script>
<script src="public/js/family-invite-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+10 -16
View File
@@ -8,10 +8,10 @@
<link rel="stylesheet" href="public/layui/css/layui.css" />
<link rel="stylesheet" href="public/css/profile-module.css" />
</head>
<body class="page-profile-module">
<body class="page-profile-module" data-my-join-applies-page>
<header class="site-header">
<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
>
<nav class="nav-links">
@@ -26,13 +26,11 @@
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Join Family</div>
<div class="module-kicker">加入家谱</div>
<div class="module-title-row">
<div>
<h1>加入家谱</h1>
<p>
输入亲人分享的邀请码,提交后进入个人中心消息流程等待管理员审核。
</p>
<p>查看加入申请进度;待审核申请可以撤销后重新提交。</p>
</div>
<a
class="btn primary magnetic"
@@ -51,16 +49,12 @@
</aside>
<div class="module-main">
<section class="module-panel">
<h2>我的加入申请</h2>
<div class="module-list" data-join-apply-list="mine">
<a class="module-row" href="join-genealogy.html">
<div>
<h3>提交新的加入申请</h3>
<p>输入家谱 ID 和关系说明,等待管理员审核。</p>
</div>
<span class="pill">去申请</span>
</a>
<div class="module-title-row">
<h2>我的加入申请</h2>
<button class="btn ghost" type="button" data-join-apply-refresh>刷新</button>
</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>
</div>
</div>
@@ -75,7 +69,7 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/join-apply-pages.js"></script>
<script src="public/js/join-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+32 -50
View File
@@ -8,10 +8,10 @@
<link rel="stylesheet" href="public/layui/css/layui.css" />
<link rel="stylesheet" href="public/css/profile-module.css" />
</head>
<body class="page-profile-module">
<body class="page-profile-module" data-join-review-page>
<header class="site-header">
<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
>
<nav class="nav-links">
@@ -20,27 +20,18 @@
><a class="active" href="profile-join-review.html">入谱审核</a>
</nav>
<div class="nav-actions">
<a href="profile-messages.html">返回消息</a
><a
class="btn primary magnetic"
href="#"
data-layer-confirm="确认批量处理当前审核申请?"
data-confirm-title="全部处理"
>全部处理</a
>
<a href="profile-family-admin.html" data-genealogy-context-link>返回家族管理</a>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Review</div>
<div class="module-kicker">入谱审核</div>
<div class="module-title-row">
<div>
<h1>入谱审核</h1>
<p>
个人中心专用审核页,处理加入家谱、绑定账号和成员资料补全申请。
</p>
<p>审核当前家谱的加入申请。只有家谱管理者可以执行通过或拒绝。</p>
</div>
</div>
</div>
@@ -49,47 +40,38 @@
<div class="container module-layout">
<aside class="module-nav">
<a href="profile-messages.html">消息中心</a
><a class="active" href="profile-join-review.html">入谱审核</a
><a href="profile-invite.html">邀请家人</a
><a href="profile-admin-permissions.html">管理员权限</a>
><a class="active" href="profile-join-review.html" data-genealogy-context-link>入谱审核</a
><span class="module-nav-unavailable">邀请家人<small>即将开放</small></span
><a href="profile-admin-permissions.html" data-genealogy-context-link>管理员权限</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>待审核申请</h2>
<div class="module-title-row">
<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">
<a class="module-row" href="profile-join-review.html"
><div>
<h3>某某人申请加入四川武胜汤氏族</h3>
<p>提交时间:2026.05.26 · 关系说明待确认</p>
</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 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">
<h2>审核操作</h2>
<div class="bottom-actions">
<a
class="btn primary magnetic"
href="#"
data-layer-confirm="同意当前入谱申请?"
data-confirm-title="审核通过"
>同意</a
><a
class="btn ghost magnetic"
href="#"
data-layer-prompt="请输入拒绝原因"
data-prompt-title="拒绝申请"
data-prompt-type="textarea"
>拒绝</a
>
</div>
<section id="join-review-form" class="module-panel" data-join-review-editor hidden>
<h2 data-join-review-form-title>审核加入申请</h2>
<form class="editor-form" data-join-review-form novalidate>
<input name="applyId" type="hidden" />
<input name="status" type="hidden" />
<div class="editor-field">
<label for="joinAuditRemark">审核说明</label>
<textarea id="joinAuditRemark" name="auditRemark" rows="4" maxlength="500" disabled placeholder="可选;拒绝时可说明原因,最多 500 个字符"></textarea>
</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>
</div>
</div>
@@ -104,7 +86,7 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/join-apply-pages.js"></script>
<script src="public/js/join-review-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+22 -75
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<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/layui/css/layui.css" />
<link rel="stylesheet" href="public/css/profile-module.css" />
@@ -11,90 +11,37 @@
<body class="page-profile-module" data-memo-edit-page>
<header class="site-header">
<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
>
<nav class="nav-links">
<a href="profile-content.html">内容发布</a
><a class="active" href="profile-memo.html">备忘录</a>
</nav>
<div class="nav-actions">
<a href="profile-memo.html">返回备忘录</a
><button class="btn primary magnetic" type="submit" form="memo-edit-form">保存</button>
</div>
<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>
<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>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Memo Editor</div>
<div class="module-title-row">
<div>
<h1>添加备忘</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 class="section">
<div class="container module-layout">
<aside class="module-nav">
<a href="profile-content.html">内容发布</a
><a href="profile-growth.html">成长日志</a
><a class="active" href="profile-memo.html">备忘录</a
><a href="profile-gift.html">贺礼邀请</a>
</aside>
<aside class="module-nav"><a href="profile-content.html">内容发布</a><a href="profile-growth.html" data-genealogy-context-link>成长记录</a><a href="profile-relative.html" data-genealogy-context-link>亲友往来</a><a class="active" href="profile-memo.html" data-genealogy-context-link>备忘录</a></aside>
<div class="module-main">
<section class="module-panel">
<h2>备忘内容</h2>
<form id="memo-edit-form" class="editor-form layui-form" data-memo-form>
<div class="editor-field">
<label for="memoTitle">姓名 / 标题</label
><input
id="memoTitle"
name="memoTitle"
type="text"
placeholder="请输入姓名或标题"
required
/>
</div>
<div class="editor-field">
<label for="memoContent">内容</label
><textarea
id="memoContent"
name="memoContent"
class="js-rich-editor"
placeholder="请输入内容"
></textarea>
</div>
<div class="editor-field">
<label for="memoRemindTime">提醒时间</label>
<input id="memoRemindTime" name="remindTime" type="text" placeholder="例如 2026-08-01 09:00:00" />
<form id="memo-edit-form" class="editor-form" data-memo-form data-memo-editor hidden novalidate>
<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-two">
<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>
<div class="editor-two">
<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="memoMediaOssIds">附件 OSS ID</label>
<input id="memoMediaOssIds" name="mediaOssIds" type="text" placeholder="多个附件 ID 用英文逗号分隔" />
<!-- 公共上传脚本会调用 /files/upload 并追加 OSS ID -->
<label class="upload-control" for="memoMediaFile">添加图片</label>
<input id="memoMediaFile" class="upload-input" type="file" accept="image/*" data-upload-target="#memoMediaOssIds" data-upload-status="#memoMediaStatus" data-upload-multiple="true" />
<p id="memoMediaStatus" class="upload-status">未选择文件</p>
</div>
</div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">保存</button
><a class="btn ghost magnetic" href="profile-memo.html"
>取消</a
>
<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>
<input name="status" type="hidden" value="0" />
<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>
<div data-memo-editor-placeholder><div class="api-state api-state--loading" role="status" aria-live="polite" aria-busy="true">正在加载备忘录编辑信息…</div></div>
</section>
</div>
</div>
@@ -103,16 +50,16 @@
<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/ke/kindeditor.min.js"></script>
<script src="public/js/rich-editor.js"></script>
<script src="public/js/profile-common.js"></script>
<script src="public/js/profile-common.js?v=20260725-entry"></script>
<script src="config.js"></script>
<script src="utils/StorageUtil.js"></script>
<script src="utils/FormUtil.js"></script>
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.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/media-display.js"></script>
<script src="public/js/memo-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
+15 -51
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<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/layui/css/layui.css" />
<link rel="stylesheet" href="public/css/profile-module.css" />
@@ -11,69 +11,32 @@
<body class="page-profile-module" data-memo-page>
<header class="site-header">
<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
>
<nav class="nav-links">
<a href="profile-content.html">内容发布</a
><a class="active" href="profile-memo.html">备忘录</a>
</nav>
<div class="nav-actions">
<a href="profile-content.html">返回发布中心</a
><a class="btn primary magnetic" href="profile-memo-edit.html"
>发布备忘录</a
>
</div>
<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>
<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>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Memo</div>
<div class="module-title-row">
<div>
<h1>人情薄 / 备忘录</h1>
<p>保存亲友往来、家族事务、纪念事项和待办提醒。</p>
</div>
</div>
<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-content.html">内容发布</a
><a href="profile-growth.html">成长日志</a
><a class="active" href="profile-memo.html">备忘录</a
><a href="profile-gift.html">贺礼邀请</a>
</aside>
<aside class="module-nav"><a href="profile-content.html">内容发布</a><a href="profile-growth.html" data-genealogy-context-link>成长记录</a><a href="profile-relative.html" data-genealogy-context-link>亲友往来</a><a class="active" href="profile-memo.html" data-genealogy-context-link>备忘录</a></aside>
<div class="module-main">
<section class="module-panel">
<h2>备忘</h2>
<!-- 备忘录列表由 /memos 接口渲染 -->
<div class="module-list" data-memo-list>
<div class="api-empty">备忘录加载中...</div>
<h2>备忘列表</h2>
<div class="module-list" data-memo-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 class="module-panel">
<h2>添加备忘</h2>
<div class="form-like">
<p>
<span>姓名 / 标题</span><b>待输入</b
><a class="link-btn" href="profile-memo-edit.html">填写</a>
</p>
<p>
<span>内容</span><b>待输入</b
><a class="link-btn" href="profile-memo-edit.html">编辑</a>
</p>
<p>
<span>附件</span><b>可添加图片</b
><a
class="link-btn"
href="#"
data-layer-msg="请选择要上传的附件"
>添加</a
>
</p>
<h2 tabindex="-1" data-memo-detail-heading>备忘详情</h2>
<div data-memo-detail aria-live="polite">
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一条备忘录查看详情。</div>
</div>
</section>
</div>
@@ -83,12 +46,13 @@
<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="public/js/profile-common.js?v=20260725-entry"></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/media-display.js"></script>
<script src="public/js/memo-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
+43 -50
View File
@@ -3,37 +3,37 @@
<head>
<meta charset="utf-8" />
<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/layui/css/layui.css" />
<link rel="stylesheet" href="public/css/profile-module.css" />
<link rel="stylesheet" href="public/js/wangeditor5/css/style.css" />
</head>
<body class="page-profile-module" data-merit-edit-page>
<header class="site-header">
<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-merit.html">功德录</a>
<a href="profile-content.html">内容发布</a>
<a class="active" href="profile-merit.html" data-genealogy-context-link>功德录</a>
</nav>
<div class="nav-actions">
<a href="profile-merit.html">返回功德录</a
><button class="btn primary magnetic" type="submit" form="merit-edit-form">保存</button>
<a href="profile-merit.html" data-genealogy-context-link>返回功德录</a>
<button class="btn primary magnetic" type="submit" form="merit-edit-form" data-merit-editor-action hidden>保存</button>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Merit Editor</div>
<div class="module-kicker">编辑功德记录</div>
<div class="module-title-row">
<div>
<h1>添加功德人</h1>
<p>
记录功德人姓名、事迹、时间和图片资料,正文使用富文本编辑器。
</p>
<h1 data-merit-editor-title>新增功德记录</h1>
<p>选择功德类型并填写金额和说明;标有 * 的项目为必填。</p>
</div>
</div>
</div>
@@ -41,70 +41,62 @@
<section class="section">
<div class="container module-layout">
<aside class="module-nav">
<a href="profile-content.html">内容发布</a
><a class="active" href="profile-merit.html">功德录</a
><a href="profile-gift.html">贺礼邀请</a
><a href="profile-growth.html">成长日志</a>
<a href="profile-content.html">内容发布</a>
<a class="active" href="profile-merit.html" data-genealogy-context-link>功德录</a>
<a href="profile-gift.html" data-genealogy-context-link>贺礼邀请</a>
<a href="profile-growth.html" data-genealogy-context-link>成长日志</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>功德资料</h2>
<form id="merit-edit-form" class="editor-form layui-form" data-merit-form>
<form id="merit-edit-form" class="editor-form" data-merit-form data-merit-editor hidden novalidate>
<div class="editor-two">
<div class="editor-field">
<label for="meritName">姓名</label
><input
id="meritName"
name="donorName"
type="text"
placeholder="请输入姓名"
required
/>
<label for="meritName">功德人姓名<span class="field-required" aria-hidden="true"> *</span></label>
<input id="meritName" name="donorName" type="text" placeholder="请输入功德人姓名" required />
</div>
<div class="editor-field">
<label for="meritDate">日期</label
><input
id="meritDate"
name="meritTime"
type="text"
placeholder="请选择日期"
/>
<label for="meritTime">功德时间</label>
<input id="meritTime" name="meritTime" type="datetime-local" />
</div>
</div>
<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 />
</div>
<div class="editor-field">
<label for="meritContent">事迹内容</label
><textarea
id="meritContent"
name="meritContent"
class="js-rich-editor"
placeholder="请输入内容"
></textarea>
<label for="meritContent">事迹内容</label>
<textarea id="meritContent" name="meritContent" class="js-rich-editor" placeholder="可选,记录功德事迹"></textarea>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="meritType">功德类型</label>
<select id="meritType" name="meritType">
<option value="donation">捐赠</option>
<option value="service">义务服务</option>
<option value="repair">修缮</option>
<option value="public">公益</option>
<option value="other">其他</option>
</select>
</div>
<div class="editor-field">
<label for="meritAmount">金额</label>
<input id="meritAmount" name="amount" type="number" step="0.01" placeholder="请输入金额" />
<input id="meritAmount" name="amount" type="number" step="any" placeholder="可选;后端未限定精度、范围或正负" />
</div>
</div>
<div class="editor-field">
<label for="meritSortOrder">排序值</label>
<input id="meritSortOrder" name="sortOrder" type="number" step="1" placeholder="可选;安全整数" />
</div>
<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">
<button class="btn primary magnetic" type="submit">保存</button
><a class="btn ghost magnetic" href="profile-merit.html"
>取消</a
>
<button class="btn primary magnetic" type="submit">保存</button>
<a class="btn ghost magnetic" href="profile-merit.html" data-genealogy-context-link>取消</a>
</div>
</form>
<div data-merit-editor-placeholder>
<div class="api-state api-state--loading" role="status" aria-live="polite" aria-busy="true">正在加载功德记录编辑信息…</div>
</div>
</section>
</div>
</div>
@@ -113,15 +105,16 @@
<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/ke/kindeditor.min.js"></script>
<script src="public/js/wangeditor5/index.js"></script>
<script src="public/js/rich-editor.js"></script>
<script src="public/js/profile-common.js"></script>
<script src="public/js/profile-common.js?v=20260725-entry"></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/ceremony-pages.js"></script>
<script src="public/js/media-display.js"></script>
<script src="public/js/merit-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+24 -41
View File
@@ -11,29 +11,28 @@
<body class="page-profile-module" data-merit-page>
<header class="site-header">
<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-merit.html">功德录</a>
<a href="profile-content.html">内容发布</a>
<a class="active" href="profile-merit.html" data-genealogy-context-link>功德录</a>
</nav>
<div class="nav-actions">
<a href="profile-content.html">返回发布中心</a
><a class="btn primary magnetic" href="profile-merit-edit.html"
>添加功德人</a
>
<a href="profile-content.html">返回发布中心</a>
<a class="btn primary magnetic" href="profile-merit-edit.html" data-genealogy-context-link data-merit-create-link hidden>新增功德记录</a>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Merit</div>
<div class="module-kicker">功德录</div>
<div class="module-title-row">
<div>
<h1>功德录</h1>
<p>记录功德人、事迹、时间和相关图片,沉淀家族贡献。</p>
<p>记录功德人、事迹、时间和金额,沉淀家族贡献。</p>
</div>
</div>
</div>
@@ -41,40 +40,23 @@
<section class="section">
<div class="container module-layout">
<aside class="module-nav">
<a href="profile-content.html">内容发布</a
><a class="active" href="profile-merit.html">功德录</a
><a href="profile-gift.html">贺礼邀请</a
><a href="profile-growth.html">成长日志</a
><a href="profile-memo.html">备忘录</a>
<a href="profile-content.html">内容发布</a>
<a class="active" href="profile-merit.html" data-genealogy-context-link>功德录</a>
<a href="profile-gift.html" data-genealogy-context-link>贺礼邀请</a>
<a href="profile-growth.html" data-genealogy-context-link>成长日志</a>
<a href="profile-memo.html" data-genealogy-context-link>备忘录</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>功德</h2>
<!-- 功德记录由 /merit-records 接口渲染 -->
<div class="module-list" data-merit-list>
<div class="api-empty">功德记录加载中...</div>
<h2>功德记录</h2>
<div class="module-list" data-merit-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 class="module-panel">
<h2>添加功德人</h2>
<div class="form-like">
<p>
<span>姓名</span><b>待输入</b
><a class="link-btn" href="profile-merit-edit.html">填写</a>
</p>
<p>
<span>内容</span><b>待输入</b
><a class="link-btn" href="profile-merit-edit.html">编辑</a>
</p>
<p>
<span>附件</span><b>可添加图片</b
><a
class="link-btn"
href="#"
data-layer-msg="请选择要上传的附件"
>添加</a
>
</p>
<h2 tabindex="-1" data-merit-detail-heading>记录详情</h2>
<div data-merit-detail aria-live="polite">
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一条功德记录查看详情。</div>
</div>
</section>
</div>
@@ -84,13 +66,14 @@
<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="public/js/profile-common.js?v=20260725-entry"></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/ceremony-pages.js"></script>
<script src="public/js/media-display.js"></script>
<script src="public/js/merit-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+48 -55
View File
@@ -1,4 +1,4 @@
<!doctype html>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
@@ -11,35 +11,33 @@
<body class="page-profile-module">
<header class="site-header">
<div class="container nav">
<a class="brand magnetic" href="profile.html"
><img class="brand-logo" src="public/images/logo.png" alt="我们的家谱,代代相传" /></a
>
<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 href="profile-families.html">数字家谱</a
><a href="plaza.html">家谱广场</a
><a href="profile-content.html">家族文化</a
><a href="surname.html">姓氏百科</a><a href="app.html">应用下载</a
><a href="help.html">帮助中心</a><a href="about.html">关于我们</a>
<a href="profile.html">个人中心</a>
<a href="profile-families.html">我的家谱</a>
<a href="plaza.html">家谱广场</a>
<a href="profile-content.html">家族文化</a>
<a href="surname.html">姓氏百科</a>
<a href="app.html">应用下载</a>
<a href="help.html">帮助中心</a>
<a href="about.html">关于我们</a>
</nav>
<div class="nav-actions">
<a class="active" href="profile.html">个人中心</a
><a class="btn primary magnetic" href="profile-create-family.html"
>创建家谱</a
>
<a class="active" href="profile-messages.html">消息中心</a>
<a class="btn primary magnetic" href="profile-create-family.html">创建家谱</a>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Messages</div>
<div class="module-kicker">消息中心</div>
<div class="module-title-row">
<div>
<h1>消息中心</h1>
<p>
处理入谱申请、贺礼邀请、资料提醒和系统通知,减少家族协作中的遗漏。
</p>
<p>查看当前账号的通知详情,并管理未读状态。</p>
</div>
<a class="btn ghost magnetic" href="profile.html">返回个人中心</a>
</div>
@@ -48,57 +46,52 @@
<section class="section">
<div class="container module-layout">
<aside class="module-nav">
<a href="profile-families.html">我的家谱</a
><a class="active" href="profile-messages.html">消息中心</a
><a href="profile-family-admin.html">家族管理</a
><a href="profile-content.html">内容发布</a
><a href="profile-data.html">个人资料</a
><a href="profile-services.html">帮助与服务</a>
<a href="profile-families.html">我的家谱</a>
<a class="active" href="profile-messages.html">消息中心</a>
<a href="profile-family-admin.html">家族管理</a>
<a href="profile-content.html">内容发布</a>
<a href="profile-data.html">个人资料</a>
<a href="profile-services.html">帮助与服务</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>待处理</h2>
<div class="module-list" data-notification-list>
<div class="module-row">
<div>
<h3>某某人申请加入四川武胜汤氏族</h3>
<p>2026.05.25 09:20 · 需要管理员审核</p>
</div>
<div class="bottom-actions">
<a class="btn ghost" href="profile-messages.html">拒绝</a
><a class="btn primary" href="profile-messages.html"
>同意</a
>
</div>
<div class="module-title-row">
<div>
<h2>通知列表</h2>
<p class="module-note">未读数量:<b data-notification-unread-count>0</b></p>
</div>
<a class="module-row" href="profile-content.html"
><div>
<h3>张三&里斯于 2023年3月23日举办婚礼</h3>
<p>贺礼邀请 · 待确认公开状态</p>
</div>
<span class="pill">查看</span></a
>
<a class="module-row" href="profile-data-reminders.html"
><div>
<h3>2 位成员资料待完善</h3>
<p>头像、出生日期、亲属关系信息不完整</p>
</div>
<span class="pill">处理</span></a
>
<label class="editor-field">
<span>阅读状态</span>
<select data-notification-filter>
<option value="">全部通知</option>
<option value="0">仅未读</option>
<option value="1">仅已读</option>
</select>
</label>
</div>
<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="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在加载消息…</div>
</div>
</section>
<section class="module-panel">
<h2>系统通知</h2>
<h2>通知详情</h2>
<div data-notification-detail aria-live="polite">
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一条通知查看详情。</div>
</div>
</section>
<section class="module-panel">
<h2>阅读管理</h2>
<div class="module-grid two">
<button class="module-card module-action-card" type="button" data-notification-read-all>
<span class="icon"></span>
<h3>全部标记已读</h3>
<p>清理已处理提醒,保留重要审核消息</p>
<p>将当前账号所有未读通知标记为已读</p>
</button>
<button class="module-card module-action-card" type="button" data-notification-refresh>
<span class="icon"></span>
<h3>刷新消息</h3>
<p>同步最新入谱申请、评论和内容通知</p>
<p>重新读取通知列表、详情与未读数量</p>
</button>
</div>
</section>
@@ -109,7 +102,7 @@
<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="public/js/profile-common.js?v=20260725-entry"></script>
<script src="config.js"></script>
<script src="utils/StorageUtil.js"></script>
<script src="utils/axios.js"></script>
+134
View File
@@ -0,0 +1,134 @@
<!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-relative-edit-page>
<header class="site-header">
<div class="container nav">
<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-relative.html" data-genealogy-context-link>亲友往来</a>
</nav>
<div class="nav-actions">
<a href="profile-relative.html" data-genealogy-context-link>返回记录</a>
<button class="btn primary magnetic" type="submit" form="relative-edit-form" data-relative-editor-action hidden>保存</button>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">编辑亲友往来</div>
<div class="module-title-row">
<div>
<h1 data-relative-editor-title>新建亲友记录</h1>
<p>附件由上传组件自动生成内部编号,页面不允许手工填写。</p>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container module-layout">
<aside class="module-nav">
<a href="profile-content.html">内容发布</a>
<a href="profile-growth.html" data-genealogy-context-link>成长记录</a>
<a class="active" href="profile-relative.html" data-genealogy-context-link>亲友往来</a>
<a href="profile-memo.html">备忘录</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>记录内容</h2>
<form id="relative-edit-form" class="editor-form" data-relative-form data-relative-editor hidden novalidate>
<div class="editor-two">
<div class="editor-field">
<label for="relativeName">亲友姓名<span class="field-required" aria-hidden="true"> *</span></label>
<input id="relativeName" name="relativeName" type="text" placeholder="请输入亲友姓名" required />
</div>
<div class="editor-field">
<label for="relationName">关系名称</label>
<input id="relationName" name="relationName" type="text" placeholder="可选,例如长辈、同学" />
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="eventName">事件名称</label>
<input id="eventName" name="eventName" type="text" placeholder="可选,例如寿宴、婚礼" />
</div>
<div class="editor-field">
<label for="eventTime">事件时间</label>
<input id="eventTime" name="eventTime" type="datetime-local" />
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="giftAmount">礼金金额</label>
<input id="giftAmount" name="giftAmount" type="number" step="any" placeholder="可选;后端未限定精度或正负" />
</div>
<div class="editor-field">
<label for="relativeSortOrder">排序值</label>
<input id="relativeSortOrder" name="sortOrder" type="number" step="1" placeholder="可选;安全整数" />
</div>
</div>
<div class="editor-field">
<label for="relativeContent">记录内容</label>
<textarea id="relativeContent" name="recordContent" placeholder="可选,记录往来详情"></textarea>
</div>
<div class="editor-field">
<label for="relativeMediaFiles">附件文件</label>
<input id="relativeMediaOssIds" name="mediaOssIds" type="hidden" />
<label class="upload-control" for="relativeMediaFiles">选择附件</label>
<input
id="relativeMediaFiles"
class="upload-input"
type="file"
multiple
data-upload-multiple="true"
data-attachment-editor data-upload-target="#relativeMediaOssIds"
data-upload-status="#relativeMediaStatus"
data-relative-media-input
/>
<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>
</div>
<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">
<button class="btn primary magnetic" type="submit">保存</button>
<a class="btn ghost magnetic" href="profile-relative.html" data-genealogy-context-link>取消</a>
</div>
</form>
<div data-relative-editor-placeholder>
<div class="api-state api-state--loading" role="status" aria-live="polite" aria-busy="true">正在加载亲友记录编辑信息…</div>
</div>
</section>
</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?v=20260725-entry"></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/md5.js"></script>
<script src="public/js/attachment-editor.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/page-effects.js"></script>
</body>
</html>
+78
View File
@@ -0,0 +1,78 @@
<!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-relative-page>
<header class="site-header">
<div class="container nav">
<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-relative.html" data-genealogy-context-link>亲友往来</a>
</nav>
<div class="nav-actions">
<a href="profile-content.html">返回发布中心</a>
<a class="btn primary magnetic" href="profile-relative-edit.html" data-genealogy-context-link data-relative-create-link hidden>新建记录</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-content.html">内容发布</a>
<a href="profile-growth.html" data-genealogy-context-link>成长记录</a>
<a class="active" href="profile-relative.html" data-genealogy-context-link>亲友往来</a>
<a href="profile-memo.html">备忘录</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>记录列表</h2>
<div class="module-list" data-relative-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 class="module-panel">
<h2 tabindex="-1" data-relative-detail-heading>记录详情</h2>
<div data-relative-detail aria-live="polite">
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">请选择一条亲友记录查看详情。</div>
</div>
</section>
</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?v=20260725-entry"></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/media-display.js"></script>
<script src="public/js/relative-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+60 -57
View File
@@ -11,7 +11,7 @@
<body class="page-profile-module" data-security-page>
<header class="site-header">
<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
>
<nav class="nav-links">
@@ -20,18 +20,18 @@
</nav>
<div class="nav-actions">
<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>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Security</div>
<div class="module-kicker">安全设置</div>
<div class="module-title-row">
<div>
<h1>安全设置</h1>
<p>维护登录密码、手机号、微信快捷登录和账号安全提醒</p>
<p>维护登录密码、手机号与账号注销安全操作</p>
</div>
</div>
</div>
@@ -48,94 +48,95 @@
<section class="module-panel">
<h2>账号安全</h2>
<!-- 修改密码表单:字段对应 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-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 />
</div>
<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 />
</div>
</div>
<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 />
</div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">修改密码</button>
<span class="api-status" data-security-status>等待提交</span>
<button class="btn primary magnetic" type="submit" data-security-submit>修改密码</button>
<div class="form-status" data-security-status role="status" aria-live="polite"></div>
</div>
</form>
</section>
<section class="module-panel">
<h2>换绑手机号</h2>
<!-- 换绑手机号表单:字段对应 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-field">
<label for="newPhone">新手机号</label>
<input id="newPhone" name="newPhone" type="tel" required />
<label for="newPhone">新手机号 <span class="field-required" aria-hidden="true">*</span></label>
<input
id="newPhone"
name="phone"
type="tel"
inputmode="numeric"
maxlength="11"
autocomplete="tel"
required
/>
</div>
<div class="editor-field">
<label for="phoneSmsCode">短信验证码</label>
<input id="phoneSmsCode" name="smsCode" type="text" required />
<label for="phoneSmsCode">短信验证码 <span class="field-required" aria-hidden="true">*</span></label>
<input
id="phoneSmsCode"
name="smsCode"
type="text"
inputmode="numeric"
maxlength="4"
pattern="\d{4}"
autocomplete="one-time-code"
required
/>
<button class="btn ghost magnetic" type="button" data-security-send-code>获取验证码</button>
</div>
</div>
<div class="editor-field">
<label for="phoneValidToken">滑动验证票据</label>
<input id="phoneValidToken" name="validToken" type="text" placeholder="可选,由验证组件返回" />
</div>
<input id="phoneValidToken" name="validToken" type="hidden" />
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">确认换绑</button>
<span class="api-status" data-security-status>等待验证码</span>
</div>
</form>
</section>
<section class="module-panel">
<h2>短信登录校验</h2>
<!-- 短信登录表单:用于接入 /genealogy/app/auth/login/sms。 -->
<form class="editor-form security-form" data-security-form="sms-login">
<div class="editor-two">
<div class="editor-field">
<label for="loginPhone">手机号</label>
<input id="loginPhone" name="phone" type="tel" required />
</div>
<div class="editor-field">
<label for="loginSmsCode">短信验证码</label>
<input id="loginSmsCode" name="smsCode" type="text" required />
</div>
</div>
<div class="editor-field">
<label for="loginValidToken">滑动验证票据</label>
<input id="loginValidToken" name="validToken" type="text" placeholder="可选,由验证组件返回" />
</div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">短信登录验证</button>
<span class="api-status" data-security-status>等待提交</span>
<button class="btn primary magnetic" type="submit" data-security-submit>确认换绑</button>
<div class="form-status" data-security-status role="status" aria-live="polite"></div>
</div>
</form>
</section>
<section class="module-panel security-danger">
<h2>注销账号</h2>
<!-- 注销账号属于敏感操作,必须填写当前密码并输入确认文本。 -->
<form class="editor-form security-form" data-security-form="deactivate">
<p>验证已绑定手机号的短信验证码后提交注销申请。</p>
<form class="editor-form security-form" data-security-form="deactivate" novalidate>
<div class="editor-field">
<label for="deactivatePassword">当前密码</label>
<input id="deactivatePassword" name="password" type="password" autocomplete="current-password" required />
<label for="deactivateSmsCode">短信验证码 <span class="field-required" aria-hidden="true">*</span></label>
<input
id="deactivateSmsCode"
name="smsCode"
type="text"
inputmode="numeric"
maxlength="4"
pattern="\d{4}"
autocomplete="one-time-code"
required
/>
<button class="btn ghost magnetic" type="button" data-security-send-deactivate-code>
获取验证码
</button>
<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 class="editor-field">
<label for="deactivateReason">注销原因</label>
<textarea id="deactivateReason" name="reason" placeholder="可选"></textarea>
</div>
<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 />
</div>
<input name="validToken" type="hidden" />
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">提交注销</button>
<button class="btn ghost magnetic" type="button" data-security-logout>退出登录</button>
<button class="btn danger magnetic" type="submit" data-security-submit>提交注销</button>
<div class="form-status" data-security-status role="status" aria-live="polite"></div>
</div>
</form>
</section>
@@ -153,8 +154,10 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/security-pages.js"></script>
<script src="public/js/profile-common.js"></script>
<script src="public/tac/js/tac.min.js"></script>
<script src="public/js/captcha-pages.js"></script>
<script src="public/js/security-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+69 -44
View File
@@ -1,4 +1,4 @@
<!doctype html>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
@@ -11,12 +11,12 @@
<body class="page-profile-module" data-vip-page>
<header class="site-header">
<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
>
<nav class="nav-links">
<a href="profile.html">首页</a
><a href="profile-families.html">数字家谱</a
<a href="profile.html">个人中心</a
><a href="profile-families.html">我的家谱</a
><a href="plaza.html">家谱广场</a
><a href="profile-content.html">家族文化</a
><a href="surname.html">姓氏百科</a><a href="app.html">应用下载</a
@@ -24,7 +24,7 @@
><a href="about.html">关于我们</a>
</nav>
<div class="nav-actions">
<a href="profile.html">个人中心</a
<a href="profile-data.html">账户资料</a
><a class="btn primary magnetic" href="profile-create-family.html"
>创建家谱</a
>
@@ -34,16 +34,13 @@
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Services</div>
<div class="module-kicker">帮助与服务</div>
<div class="module-title-row">
<div>
<h1>帮助与服务</h1>
<p>
整合 APP
“我的”页中的应用推广、意见反馈、安全设置、帮助中心和应用分享。
</p>
<p>集中处理推广收益、意见反馈、账号安全、帮助说明和会员服务。</p>
</div>
<a class="btn primary magnetic" href="profile-feedback.html"
<a class="btn primary magnetic" href="profile-feedback.html" data-feature-link="available"
>提交反馈</a
>
</div>
@@ -63,12 +60,18 @@
<section class="module-panel">
<h2>常用服务</h2>
<div class="module-grid">
<a class="module-card tilt-card" href="profile-share.html"
><span class="icon"></span>
<h3>应用分享</h3>
<p>邀请好友使用,了解应用下载和分享奖励</p></a
>
<a class="module-card tilt-card" href="profile-feedback.html"
<a class="module-card tilt-card" href="profile-earnings.html" data-feature-link="available">
<span class="icon"></span>
<h3>应用分享与收益</h3>
<p>查看推广收益、可用余额、流水和提现记录</p>
</a>
<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>
<h3>意见反馈</h3>
<p>提交产品问题、资料异常和改进建议。</p></a
@@ -95,43 +98,64 @@
>
</div>
</section>
<!-- 会员套餐区域:套餐列表和订单创建字段来自 APP.openapi.json 的 VIP 接口。 -->
<section class="module-panel">
<h2>会员服务</h2>
<p>选择适合当前家谱的会员套餐,创建订单后继续完成支付</p>
<div class="vip-package-list" data-vip-package-list>
<div class="api-empty">会员套餐加载中</div>
<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>
<form class="editor-form vip-order-form" data-vip-order-form>
<div class="editor-two">
<div class="editor-field">
<label for="packageId">套餐 ID</label>
<input id="packageId" name="packageId" type="number" placeholder="请先选择套餐" required />
</div>
<div class="editor-field">
<label for="genealogyId">家谱 ID</label>
<input id="genealogyId" name="genealogyId" type="number" placeholder="可选,指定要开通的家谱" />
</div>
</div>
</section>
<section class="module-panel" id="membership" tabindex="-1">
<h2>会员服务</h2>
<p>选择适合的会员套餐并创建微信扫码支付订单。</p>
<div class="vip-package-list" data-vip-package-list>
<div class="api-state api-state--loading" data-api-state="loading" role="status" aria-live="polite" aria-busy="true">正在读取会员套餐…</div>
</div>
<form class="editor-form vip-order-form" data-vip-order-form novalidate>
<p class="form-status" data-vip-selected-package role="status" aria-live="polite">尚未选择套餐</p>
<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>
<label for="vipGenealogy">关联家谱(可选)</label>
<select id="vipGenealogy" data-vip-genealogy-options>
<option value="">家谱选项加载中</option>
</select>
</div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">创建会员订单</button>
<span class="api-status" data-vip-order-status>等待选择套餐</span>
<button class="btn primary magnetic" type="submit" data-vip-order-submit>创建会员订单</button>
<div class="form-status" data-vip-order-status role="status" aria-live="polite"></div>
</div>
</form>
</section>
<!-- 会员订单区域:展示 GET /genealogy/app/vip/orders 的历史记录。 -->
<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">
<h2>会员订单</h2>
<div class="module-title-row">
<h2>会员订单</h2>
<button class="btn outline" type="button" data-vip-order-refresh>刷新订单</button>
</div>
<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>
</section>
<section class="module-panel">
@@ -164,8 +188,9 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/vip-pages.js"></script>
<script src="public/js/qrcode.min.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>
</body>
</html>
+30 -21
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<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/layui/css/layui.css" />
<link rel="stylesheet" href="public/css/profile-module.css" />
@@ -11,17 +11,16 @@
<body class="page-profile-module" data-family-share-page>
<header class="site-header">
<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
>
<nav class="nav-links">
<a href="profile-services.html">帮助与服务</a
><a class="active" href="profile-share.html">应用分享</a>
><a class="active" href="profile-share.html">应用分享与收益</a>
</nav>
<div class="nav-actions">
<a href="profile-services.html">返回服务中心</a
><a class="btn primary magnetic" href="profile-share.html"
>立即分享</a
<a class="btn ghost magnetic" href="profile-services.html"
>返回服务中心</a
>
</div>
</div>
@@ -29,11 +28,11 @@
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Share</div>
<div class="module-kicker">应用分享</div>
<div class="module-title-row">
<div>
<h1>应用分享</h1>
<p>邀请好友创建和维护家谱,查看应用下载、分享奖励和推广入口</p>
<h1>应用分享与收益</h1>
<p>应用邀请暂以移动端为主;这里可查看推广收益、流水和提现记录</p>
</div>
</div>
</div>
@@ -42,7 +41,7 @@
<div class="container module-layout">
<aside class="module-nav">
<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="help.html">帮助中心</a>
</aside>
@@ -50,30 +49,41 @@
<section class="module-panel">
<h2>邀请好友奖励</h2>
<div class="module-grid">
<div class="module-card">
<div class="module-card is-unavailable" aria-disabled="true">
<span class="icon"></span>
<h3>每邀请一位好友</h3>
<p data-member-overview>分享概览加载中...</p>
<p>邀请奖励规则正在完善,当前暂不展示奖励金额。</p>
</div>
<div class="module-card">
<div class="module-card is-unavailable" aria-disabled="true">
<span class="icon"></span>
<h3>分享二维码</h3>
<p data-invite-link>分享链接加载中...</p>
<p>分享链接和二维码暂未开放,请先使用移动端分享。</p>
</div>
</div>
</section>
<section class="module-panel">
<h2>分享记录</h2>
<div class="module-list">
<div class="module-row">
<div>
<h3>本月邀请</h3>
<p>3 人访问 · 1 人完成注册</p>
</div>
<span class="pill">查看</span>
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">
分享记录暂未开放;推广收益和提现结果可在下方查看。
</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>
</section>
</div>
</div>
</section>
@@ -87,7 +97,6 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/member-admin-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+167 -35
View File
@@ -11,24 +11,24 @@
<body class="page-profile-module" data-lineage-page>
<header class="site-header">
<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
>
<nav class="nav-links">
<a href="profile-families.html">我的家谱</a
><a class="active" href="profile-family-admin.html">家族管理</a
><a class="active" href="profile-family-admin.html" data-genealogy-context-link>家族管理</a
><a href="profile-data.html">个人资料</a>
</nav>
<div class="nav-actions">
<a href="profile-family-home.html">返回家谱主页</a
><a class="btn primary magnetic" href="profile-data.html">添加成员</a>
<a href="profile-family-home.html" data-genealogy-context-link>返回家谱主页</a
><a class="btn primary magnetic" href="#lineage-member-form" data-lineage-management hidden>添加成员</a>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Tree Mode</div>
<div class="module-kicker">世系管理</div>
<div class="module-title-row">
<div>
<h1>世系图</h1>
@@ -36,8 +36,8 @@
个人中心内的树谱管理视图,用于查看成员关系、添加亲属和搜索成员。
</p>
</div>
<a class="btn ghost magnetic" href="profile-family-admin.html"
>管理世代</a
<a class="btn ghost magnetic" href="profile-generation.html" data-genealogy-context-link
>管理字辈</a
>
</div>
</div>
@@ -45,15 +45,21 @@
<section class="section">
<div class="container module-layout">
<aside class="module-nav">
<a href="profile-family-home.html">家谱主页</a
><a class="active" href="profile-tree.html">世系图</a
><a href="profile-generation.html">字辈谱</a
><a href="profile-family-admin.html">家族管理</a>
<a href="profile-family-home.html" data-genealogy-context-link>家谱主页</a
><a class="active" href="profile-tree.html" data-genealogy-context-link>世系图</a
><a href="profile-generation.html" data-genealogy-context-link>字辈谱</a
><a href="profile-family-admin.html" data-genealogy-context-link>家族管理</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>树谱操作</h2>
<div class="module-grid">
<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>
<button class="module-card module-action-card tilt-card" type="button" data-lineage-relation="parents"
><span class="icon"></span>
<h3>添加父亲 / 母亲</h3>
@@ -70,64 +76,121 @@
<h3>添加子女</h3>
<p>先选择成员,再为当前成员补充下一代关系。</p></button
>
<button class="module-card module-action-card tilt-card" type="button" data-lineage-relation="siblings"
><span class="icon"></span>
<h3>添加兄弟姐妹</h3>
<p>先选择成员,再为当前成员新增同辈关系。</p></button
>
</div>
</section>
<section class="module-panel">
<h2>世系树</h2>
<!-- 世系树由 public/js/lineage-pages.js 按接口数据渲染 -->
<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>
</section>
<section class="module-panel">
<h2>成员搜索</h2>
<div class="lineage-toolbar">
<input type="search" data-lineage-keyword placeholder="输入姓名或字辈搜索" />
<div class="lineage-toolbar lineage-filter-toolbar">
<input type="search" data-lineage-keyword aria-label="按姓名、别名或人物编号搜索成员" placeholder="输入姓名、别名或人物编号搜索" />
<select data-lineage-generation-filter aria-label="按世代筛选">
<option value="">全部世代</option>
</select>
<select data-lineage-status-filter aria-label="按人物状态筛选">
<option value="">全部人物状态</option>
<option value="0">健在</option>
<option value="1">已故</option>
<option value="2">未知</option>
</select>
<button class="btn primary" type="button" data-lineage-search>搜索</button>
</div>
<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="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 class="bottom-actions" data-lineage-pagination hidden>
<button class="btn ghost" type="button" data-lineage-page-action="previous">上一页</button>
<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>
</div>
</section>
<section class="module-panel">
<h2>成员详情</h2>
<!-- 点击成员列表或世系树节点后渲染详情 -->
<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>
</section>
<section class="module-panel">
<h2>新增成员</h2>
<form class="editor-form" data-lineage-form>
<!-- 字段名称对应 APP.openapi.json 的 LineagePersonBody -->
<section class="module-panel" data-lineage-management hidden>
<h2>新增 / 编辑成员</h2>
<form id="lineage-member-form" class="editor-form" data-lineage-form novalidate>
<div class="editor-two">
<div class="editor-field">
<label for="lineage-person-name">姓名</label>
<input id="lineage-person-name" name="personName" type="text" placeholder="请输入成员姓名" required />
<label for="lineage-binding-mode">账号绑定 <span class="field-required" aria-hidden="true">*</span></label>
<select id="lineage-binding-mode" name="bindingMode" required>
<option value="NONE" selected>不绑定账号</option>
<option value="SELF">绑定当前登录账号</option>
<option value="SPECIFIED">指定其他账号</option>
</select>
<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>
</select>
</div>
<p class="upload-status" data-lineage-binding-status role="status" aria-live="polite">指定账号仅可从当前家谱正常成员中选择。</p>
</div>
<div class="editor-field">
<label for="lineage-avatar-file">头像</label>
<input id="lineage-avatar-oss-id" name="avatarOssId" type="hidden" />
<label class="upload-control" for="lineage-avatar-file">选择头像文件</label>
<input
id="lineage-avatar-file"
class="upload-input"
type="file"
accept="image/*"
data-upload-target="#lineage-avatar-oss-id"
data-upload-status="#lineage-avatar-status"
data-upload-mode="resumable"
data-upload-biz-type="avatar"
data-upload-usage-scene="lineage_avatar"
/>
<p id="lineage-avatar-status" class="upload-status" role="status" aria-live="polite">请选择头像文件</p>
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<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 />
</div>
<div class="editor-field">
<label for="lineage-sex">性别</label>
<select id="lineage-sex" name="sex">
<option value="0"></option>
<option value="1"></option>
<option value="2" selected>未知</option>
</select>
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="lineage-generation-no">世代序号</label>
<input id="lineage-generation-no" name="generationNo" type="number" min="1" placeholder="例如 3" />
<label for="lineage-generation-no">世代</label>
<select id="lineage-generation-no" name="generation" data-lineage-generation>
<option value="">请选择世代</option>
</select>
</div>
<div class="editor-field">
<label for="lineage-generation-name">字辈</label>
<input id="lineage-generation-name" name="generationName" type="text" placeholder="例如 忠" />
<input id="lineage-generation-name" name="generationName" type="text" readonly />
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="lineage-person-no">人物编号</label>
<input id="lineage-person-no" name="personNo" type="text" placeholder="可选" />
<input id="lineage-person-no" name="personNo" type="text" placeholder="可选;留空由系统生成" />
</div>
<div class="editor-field">
<label for="lineage-alias-name">别名</label>
@@ -136,21 +199,88 @@
</div>
<div class="editor-two">
<div class="editor-field">
<label for="lineage-birth-date">出生日期</label>
<input id="lineage-birth-date" name="birthDate" type="date" />
<label for="lineage-father-id">父亲</label>
<select id="lineage-father-id" name="fatherId" data-lineage-parent-option data-empty-label="请选择父亲">
<option value="">请选择父亲</option>
</select>
</div>
<div class="editor-field">
<label for="lineage-death-date">逝世日期</label>
<input id="lineage-death-date" name="deathDate" type="date" />
<label for="lineage-mother-id">母亲</label>
<select id="lineage-mother-id" name="motherId" data-lineage-parent-option data-empty-label="请选择母亲">
<option value="">请选择母亲</option>
</select>
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="lineage-birth-date">出生时间</label>
<input id="lineage-birth-date" name="birthDate" type="datetime-local" />
</div>
<div class="editor-field">
<label for="lineage-birth-lunar">出生历法</label>
<select id="lineage-birth-lunar" name="birthLunar">
<option value="0" selected>公历</option>
<option value="1">农历</option>
</select>
</div>
</div>
<div class="editor-field">
<label for="lineage-introduction">简介</label>
<textarea id="lineage-introduction" name="introduction" placeholder="记录成员生平简介"></textarea>
<label for="lineage-birth-place">出生地</label>
<input id="lineage-birth-place" name="birthPlace" type="text" placeholder="可选" />
</div>
<div class="editor-two">
<div class="editor-field">
<label for="lineage-death-date">逝世时间</label>
<input id="lineage-death-date" name="deathDate" type="datetime-local" />
</div>
<div class="editor-field">
<label for="lineage-death-lunar">逝世历法</label>
<select id="lineage-death-lunar" name="deathLunar">
<option value="0" selected>公历</option>
<option value="1">农历</option>
</select>
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="lineage-death-place">逝世地</label>
<input id="lineage-death-place" name="deathPlace" type="text" placeholder="可选" />
</div>
<div class="editor-field">
<label for="lineage-burial-place">安葬地</label>
<input id="lineage-burial-place" name="burialPlace" type="text" placeholder="可选" />
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="lineage-person-status">人物状态</label>
<select id="lineage-person-status" name="personStatus">
<option value="0">健在</option>
<option value="1">已故</option>
<option value="2" selected>未知</option>
</select>
</div>
<div class="editor-field">
<label for="lineage-sort-order">排序值</label>
<input id="lineage-sort-order" name="sortOrder" type="number" step="1" placeholder="可选,数值越小越靠前" />
</div>
</div>
<div class="editor-field" data-lineage-spouse-field hidden>
<label for="lineage-relation-name">配偶关系称谓</label>
<input id="lineage-relation-name" name="relationName" type="text" placeholder="可选,例如 妻、夫" />
</div>
<div class="editor-field">
<label for="lineage-introduction">人物简介</label>
<textarea id="lineage-introduction" name="biography" placeholder="记录成员生平简介"></textarea>
</div>
<div class="editor-field">
<label for="lineage-remark">备注</label>
<textarea id="lineage-remark" name="remark" placeholder="可选"></textarea>
</div>
<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>
<span class="form-status" role="status" aria-live="polite" data-lineage-form-status></span>
</div>
</form>
</section>
@@ -161,12 +291,14 @@
<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="public/js/profile-common.js?v=20260725-entry"></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/media-display.js"></script>
<script src="public/js/lineage-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
+130
View File
@@ -0,0 +1,130 @@
<!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-video-edit-page>
<header class="site-header">
<div class="container nav">
<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-video.html" data-genealogy-context-link>视频</a>
</nav>
<div class="nav-actions">
<a href="profile-video.html" data-genealogy-context-link>返回视频列表</a>
<button class="btn primary magnetic" type="submit" form="video-edit-form" data-video-management hidden>保存视频</button>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">编辑视频</div>
<div class="module-title-row">
<div>
<h1 data-video-editor-title>发布视频</h1>
<p>视频文件由上传组件自动关联,无需填写内部编号。</p>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container module-layout">
<aside class="module-nav">
<a href="profile-content.html">内容发布</a>
<a class="active" href="profile-video.html" data-genealogy-context-link>视频</a>
<a href="profile-family-home.html" data-genealogy-context-link>家谱主页</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>视频内容</h2>
<form id="video-edit-form" class="editor-form" data-video-form data-video-management hidden novalidate>
<div class="editor-field">
<label for="video-title">视频标题 <span class="field-required" aria-hidden="true">*</span></label>
<input id="video-title" name="videoTitle" type="text" placeholder="请输入视频标题" required />
</div>
<div class="editor-field">
<label for="video-description">视频说明</label>
<textarea id="video-description" name="videoDesc" placeholder="可选,记录视频内容"></textarea>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="video-file">视频文件 <span class="field-required" aria-hidden="true">*</span></label>
<input id="video-oss-id" name="videoOssId" type="hidden" />
<label class="upload-control" for="video-file">选择视频文件</label>
<input
id="video-file"
class="upload-input"
type="file"
accept="video/*"
data-attachment-editor data-upload-target="#video-oss-id"
data-upload-status="#video-file-status"
data-video-duration-target="#video-duration"
/>
<p id="video-file-status" class="upload-status" data-video-file-status role="status" aria-live="polite">请选择视频文件</p>
</div>
<div class="editor-field">
<label for="video-cover-file">封面图片</label>
<input id="video-cover-oss-id" name="coverOssId" type="hidden" />
<label class="upload-control" for="video-cover-file">选择封面图片</label>
<input
id="video-cover-file"
class="upload-input"
type="file"
accept="image/*"
data-attachment-editor data-upload-target="#video-cover-oss-id"
data-upload-status="#video-cover-status"
/>
<p id="video-cover-status" class="upload-status" data-video-cover-status role="status" aria-live="polite">未选择封面</p>
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="video-duration">视频时长(秒)</label>
<input id="video-duration" name="durationSeconds" type="number" step="1" min="0" readonly />
<p class="upload-status">选择视频后自动读取,不会要求手工填写。</p>
</div>
<div class="editor-field">
<label for="video-sort-order">排序值</label>
<input id="video-sort-order" name="sortOrder" type="number" step="1" placeholder="可选,数值越小越靠前" />
</div>
</div>
<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">
<button class="btn primary magnetic" type="submit">保存视频</button>
<a class="btn ghost magnetic" href="profile-video.html" data-genealogy-context-link>取消</a>
</div>
</form>
<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>
</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?v=20260725-entry"></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/md5.js"></script>
<script src="public/js/attachment-editor.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/page-effects.js"></script>
</body>
</html>
+33 -46
View File
@@ -8,33 +8,32 @@
<link rel="stylesheet" href="public/layui/css/layui.css" />
<link rel="stylesheet" href="public/css/profile-module.css" />
</head>
<body class="page-profile-module">
<body class="page-profile-module" data-video-page>
<header class="site-header">
<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 href="profile-album.html">相册</a
><a class="active" href="profile-video.html">视频</a>
<a href="profile-content.html">内容发布</a>
<a href="profile-album.html">相册</a>
<a class="active" href="profile-video.html" data-genealogy-context-link>视频</a>
</nav>
<div class="nav-actions">
<a href="profile-content.html">返回发布中心</a
><a class="btn primary magnetic" href="profile-video.html"
>发布视频</a
>
<a href="profile-content.html">返回发布中心</a>
<a class="btn primary magnetic" href="profile-video-edit.html" data-genealogy-context-link data-video-management hidden>发布视频</a>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Video</div>
<div class="module-kicker">家族视频</div>
<div class="module-title-row">
<div>
<h1>家族视频</h1>
<p>发布和管理家族视频,支持标题、描述、封面和上传状态维护</p>
<p>查看家族视频资料;有内容维护权限的成员可以发布、编辑和删除视频</p>
</div>
</div>
</div>
@@ -42,45 +41,26 @@
<section class="section">
<div class="container module-layout">
<aside class="module-nav">
<a href="profile-content.html">内容发布</a
><a href="profile-album.html">相册</a
><a class="active" href="profile-video.html">视频</a
><a href="profile-family-home.html">家谱主页</a>
<a href="profile-content.html">内容发布</a>
<a href="profile-album.html">相册</a>
<a class="active" href="profile-video.html" data-genealogy-context-link>视频</a>
<a href="profile-family-home.html" data-genealogy-context-link>家谱主页</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>视频列表</h2>
<div class="module-list">
<a class="module-row" href="profile-video.html"
><div>
<h3>四川武胜汤氏族视频</h3>
<p>已发布 · 2026.05 · 可编辑</p>
</div>
<span class="pill">管理</span></a
><a class="module-row" href="profile-video.html"
><div>
<h3>祭祖活动记录</h3>
<p>草稿 · 等待上传视频</p>
</div>
<span class="pill">继续</span></a
>
<div class="module-list" data-video-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 class="module-panel">
<h2>发布视频</h2>
<div class="form-like">
<p>
<span>视频标题</span><b>必填</b
><a class="link-btn" href="profile-video.html">填写</a>
</p>
<p>
<span>视频描述</span><b>0/200</b
><a class="link-btn" href="profile-video.html">编辑</a>
</p>
<p>
<span>上传视频</span><b>未上传</b
><a class="link-btn" href="profile-video.html">选择</a>
</p>
<h2>视频详情</h2>
<div data-video-detail aria-live="polite">
<div class="api-state api-state--empty" data-api-state="empty" role="status" aria-live="polite">
请选择一个视频查看详情
</div>
</div>
</section>
</div>
@@ -90,7 +70,14 @@
<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="public/js/profile-common.js?v=20260725-entry"></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/media-display.js"></script>
<script src="public/js/video-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+73 -249
View File
@@ -1,4 +1,4 @@
<!doctype html>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
@@ -11,12 +11,12 @@
<body class="page-profile" data-profile-page>
<header class="site-header">
<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
>
<nav class="nav-links">
<a href="profile.html">首页</a>
<a href="profile-families.html">数字家谱</a>
<a class="active" href="profile.html">个人中心</a>
<a href="profile-families.html">我的家谱</a>
<a href="plaza.html">家谱广场</a>
<a href="profile-content.html">家族文化</a>
<a href="surname.html">姓氏百科</a>
@@ -25,10 +25,8 @@
<a href="about.html">关于我们</a>
</nav>
<div class="nav-actions">
<a class="active" href="profile.html">个人中心</a>
<a class="btn primary magnetic" href="profile-create-family.html"
>创建家谱</a
>
<a href="profile-data.html">账户资料</a>
<a class="btn primary magnetic" href="profile-create-family.html">创建家谱</a>
</div>
</div>
</header>
@@ -36,29 +34,33 @@
<main>
<section class="profile-hero">
<div class="container profile-hero-grid">
<div class="profile-card tilt-card">
<div class="profile-card">
<div class="avatar-wrap">
<div class="avatar" data-profile-text="avatarText">鹿</div>
<div class="avatar" data-profile-avatar data-profile-text="avatarText"></div>
<span></span>
</div>
<div class="profile-info">
<div class="eyebrow">Personal Center</div>
<h1 data-profile-text="displayName">鹿野 Loyel</h1>
<p>
管理家谱、亲属资料、家族内容和协作消息。APP
里的核心功能都收纳在这里,适合 PC 端集中处理。
</p>
<div class="eyebrow">个人中心</div>
<h1 data-profile-text="displayName">正在加载账户</h1>
<p>账户资料与家谱工作区分开呈现。进入家谱后,再处理成员、内容和协作事务。</p>
<div class="profile-tags">
<span>血亲</span><span>第二代 顺字辈</span
><span>已绑定账号</span>
<span>已登录账户</span><span>资料与账号同步</span>
</div>
<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 class="profile-stats tilt-card">
<div><strong>2</strong><span>我的家谱</span></div>
<div><strong>7</strong><span>待处理</span></div>
<div><strong>18</strong><span>已发布内容</span></div>
<div><strong>86%</strong><span>资料完善度</span></div>
<div class="profile-next-card">
<div class="eyebrow">下一步</div>
<h2>从家谱开始</h2>
<p>没有家谱时可创建或申请加入;有多个家谱时,请先选择本次要处理的家谱。</p>
<div class="toolbar">
<a class="link-btn" href="profile-create-family.html">创建家谱</a>
<a class="link-btn" href="profile-join-family.html">申请加入</a>
</div>
</div>
</div>
</section>
@@ -66,230 +68,70 @@
<section class="section dashboard-section">
<div class="container dashboard-layout">
<aside class="dashboard-side">
<div class="side-card user-actions tilt-card">
<div class="side-card user-actions">
<h2>账户入口</h2>
<a href="profile-data.html"
><span>个人资料</span><b>完善成员档案</b></a
>
<a href="profile-messages.html"
><span>消息中心</span><b>审核、邀请与提醒</b></a
>
<a href="profile-services.html"
><span>帮助与服务</span><b>帮助、反馈、推广</b></a
>
<a class="logout-link" href="#logout" data-logout-open
><span>退出登录</span><b>返回登录页</b></a
>
</div>
<div class="side-card todo-card tilt-card" id="messages">
<div class="card-head">
<h2>待办消息</h2>
<span>7 条</span>
</div>
<div class="todo-list">
<a href="profile-join-review.html"
><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>
<a href="profile-data.html"><span>个人资料</span><b>查看并更新账户资料</b></a>
<a href="profile-messages.html"><span>消息中心</span><b>查看真实通知与业务提醒</b></a>
<a href="profile-services.html"><span>帮助与服务</span><b>反馈、安全设置与会员服务</b></a>
<button class="logout-link" type="button" data-logout-open><span>退出登录</span><b>返回首页</b></button>
</div>
</aside>
<div class="dashboard-main">
<section class="panel my-family-panel" id="families">
<section class="panel my-family-panel">
<div class="panel-head">
<div>
<h2>我的家谱</h2>
<p>
对应 APP
首页和家谱主页,进入后可查看家谱主页、世系图、相册和家族圈。
</p>
<p>先从真实列表选择家谱,再进入对应的主页、世系图、成员和内容管理。</p>
</div>
<a class="link-btn" href="profile-families.html">查看全部</a>
<div class="toolbar">
<a
class="btn primary magnetic"
href="profile-create-family.html"
>创建家谱</a
><a class="btn ghost magnetic" href="profile-join-family.html"
>加入家谱</a
>
</div>
</div>
<div class="family-grid">
<a
class="family-card tilt-card"
href="profile-family-home.html"
>
<div class="book-cover">汤氏<br />家谱</div>
<a class="family-card" href="profile-families.html">
<div class="book-cover">选 择<br />家 谱</div>
<div>
<h3>四川武胜汤氏族</h3>
<p>
共修入 7 人,激活 7 人。你是创建者,可管理内容和成员权限。
</p>
<h3>进入我的家谱</h3>
<p>列表只展示当前账户真实可访问的家谱,避免把示例数据当成自己的内容。</p>
<div class="mini-actions">
<span>家谱主页</span><span>家族圈</span
><span>管理员</span>
</div>
</div>
</a>
<a
class="family-card tilt-card"
href="profile-family-home.html"
>
<div class="book-cover alt">刘氏<br />家谱</div>
<div>
<h3>四川达州刘氏族</h3>
<p>共修入 7 人,近期有 2 条内容更新等待查看。</p>
<div class="mini-actions">
<span>世系图</span><span>相册</span><span>消息</span>
<span>选择家谱</span><span>进入主页</span><span>处理事务</span>
</div>
</div>
</a>
</div>
</section>
<section class="panel" id="management">
<section class="panel">
<div class="panel-head compact">
<div>
<h2>家族管理</h2>
<p>
把 APP 里的世代、管理员、权限、入谱审核集中到一个管理区。
</p>
<h2>账户资料</h2>
<p>以下内容来自你的账户资料;未填写的信息会显示为“待填写”。</p>
</div>
<a class="link-btn" href="profile-family-admin.html"
>进入管理</a
>
<a class="link-btn" href="profile-data.html">编辑资料</a>
</div>
<div class="feature-grid management-grid">
<a class="feature-tile tilt-card" href="profile-families.html"
><span></span>
<h3>世系图</h3>
<p>查看成员世系、搜索成员、进入树谱模式。</p></a
>
<a class="feature-tile tilt-card" href="profile-families.html"
><span></span>
<h3>字辈谱</h3>
<p>管理字辈、统计各代人数和成员分布。</p></a
>
<a class="feature-tile tilt-card" href="profile-generation.html"
><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 class="data-list">
<p><span>绑定账号</span><b data-profile-text="phone">加载中</b></p>
<p><span>昵称</span><b data-profile-text="displayName">加载中</b></p>
<p><span>真实姓名</span><b data-profile-text="realName">加载中</b></p>
<p><span>性别</span><b data-profile-text="sex">加载中</b></p>
<p><span>出生日期</span><b data-profile-text="birthday">加载中</b></p>
<p><span>邮箱</span><b data-profile-text="email">加载中</b></p>
</div>
</section>
<section class="panel" id="content">
<section class="panel">
<div class="panel-head compact">
<div>
<h2>内容发布</h2>
<p>
对应 APP
家谱主页中的谱文、视频、相册、功德录、贺礼邀请、成长日志等内容入口。
</p>
<h2>账户与服务</h2>
<p>将 APP 的“我的”功能整理为桌面服务入口;家谱内容仍需从具体家谱进入。</p>
</div>
<a class="link-btn" href="profile-content.html">发布中心</a>
<a class="link-btn" href="profile-services.html">查看服务中心</a>
</div>
<div class="content-grid">
<a
class="content-tile large tilt-card"
href="profile-article.html"
><span>谱文</span>
<h3>撰写谱文与族史</h3>
<p>沉淀族训、谱序、谱论、恩荣录等家族文献。</p></a
>
<a class="content-tile tilt-card" href="profile-album.html"
><span>相册</span>
<h3>家族相册</h3>
<p>新建相册、上传照片、编辑封面。</p></a
>
<a class="content-tile tilt-card" href="profile-video.html"
><span>视频</span>
<h3>家族视频</h3>
<p>发布视频、管理家族影像。</p></a
>
<a class="content-tile tilt-card" href="profile-merit.html"
><span>功德</span>
<h3>功德录</h3>
<p>记录功德人、事迹与纪念内容。</p></a
>
<a class="content-tile tilt-card" href="profile-gift.html"
><span>贺礼</span>
<h3>贺礼邀请</h3>
<p>管理婚礼、生日、升学等贺礼。</p></a
>
<a class="content-tile tilt-card" href="profile-feed.html"
><span>动态</span>
<h3>发布动态</h3>
<p>面向家族圈发布图文近况。</p></a
>
<a class="content-tile tilt-card" href="profile-growth.html"
><span>日志</span>
<h3>成长日志</h3>
<p>记录成员成长和人生节点。</p></a
>
<a class="content-tile tilt-card" href="profile-memo.html"
><span>备忘</span>
<h3>人亲簿 / 备忘录</h3>
<p>保存亲友往来和家族事务。</p></a
>
</div>
</section>
<section class="profile-data-layout" id="profile-data">
<div class="panel data-panel">
<div class="panel-head compact">
<div>
<h2>个人资料</h2>
<p>来自 APP 的个人资料、亲属、完善资料和绑定账号流程。</p>
</div>
<a class="link-btn" href="profile-data.html">编辑资料</a>
</div>
<div class="data-list">
<p><span>绑定账号</span><b data-profile-text="phone">已绑定</b></p>
<p><span>姓名</span><b data-profile-text="displayName">鹿野 Loyel</b></p>
<p><span>出生日期</span><b data-profile-text="birthday">待填写</b></p>
<p><span>现居地区</span><b data-profile-text="regionText">待填写</b></p>
<p><span>亲属关系</span><b>父亲 2 人</b></p>
<p><span>补充资料</span><b>待完善 4 项</b></p>
</div>
</div>
<div class="panel service-panel" id="services">
<div class="panel-head compact">
<div>
<h2>帮助与服务</h2>
<p>我的页面中的常用功能。</p>
</div>
</div>
<div class="service-grid">
<a href="profile-share.html">应用分享</a>
<a href="profile-feedback.html">意见反馈</a>
<a href="help.html">帮助中心</a>
<a href="profile-security.html">安全设置</a>
</div>
<div class="service-grid" aria-label="账户与服务入口">
<a href="profile-messages.html"><strong>消息中心</strong><span>查看真实通知与业务提醒</span></a>
<a href="profile-data.html"><strong>个人资料</strong><span>维护昵称、实名和联系方式</span></a>
<a href="profile-security.html"><strong>账户安全</strong><span>修改密码、换绑手机或注销账号</span></a>
<a href="profile-services.html#membership"><strong>会员服务</strong><span>查看套餐、订单、帮助与服务说明</span></a>
<a href="profile-feedback.html"><strong>意见反馈</strong><span>提交产品问题和改进建议</span></a>
<a href="app.html"><strong>移动端</strong><span>下载 APP,处理移动端场景</span></a>
</div>
</section>
</div>
@@ -301,29 +143,24 @@
<div class="container">
<div class="footer-grid">
<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
>
<p>一个面向家庭和宗族的数字家谱与家族文化门户。</p>
</div>
<div>
<h4>产品入口</h4>
<a href="profile-families.html">数字家谱</a
><a href="plaza.html">家谱广场</a><a href="app.html">应用下载</a
><a href="help.html">帮助中心</a>
<a href="profile-families.html">数字家谱</a><a href="plaza.html">家谱广场</a><a href="app.html">应用下载</a><a href="help.html">帮助中心</a>
</div>
<div>
<h4>文化内容</h4>
<a href="profile-content.html">家族文化</a
><a href="surname.html">姓氏百科</a
><a href="profile-article.html">家谱知识</a
><a href="profile-messages.html">平台公告</a>
<a href="profile-content.html">家族文化</a><a href="surname.html">姓氏百科</a><a href="profile-article.html">家谱知识</a><a href="profile-messages.html">平台公告</a>
</div>
<div>
<h4>联系我们</h4>
<p>客服电话:400-000-0000</p>
<p>邮箱:service@example.com</p>
<p>地址:中国 · 成都</p>
<h4>服务支持</h4>
<a href="help.html">帮助中心</a>
<a href="submit-ticket.html">提交问题</a>
<a href="about.html">关于平台</a>
</div>
</div>
<div class="footer-legal">
@@ -332,30 +169,16 @@
</div>
</div>
</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-dialog"
role="dialog"
aria-modal="true"
aria-labelledby="logoutTitle"
>
<button
class="logout-close"
type="button"
data-logout-close
aria-label="关闭"
>
×
</button>
<div class="logout-dialog" role="dialog" aria-modal="true" aria-labelledby="logoutTitle">
<button class="logout-close" type="button" data-logout-close aria-label="关闭">×</button>
<span class="logout-icon">退</span>
<h2 id="logoutTitle">确认退出登录?</h2>
<p>退出后需要重新登录,才能继续管理家谱、审核消息和编辑资料。</p>
<div class="logout-actions">
<button class="btn ghost magnetic" type="button" data-logout-close>
取消
</button>
<a class="btn primary magnetic" href="login.html">确认退出</a>
<button class="btn ghost magnetic" type="button" data-logout-close>取消</button>
<a class="btn primary magnetic" href="index.html" data-logout-confirm>确认退出</a>
</div>
</div>
</div>
@@ -368,8 +191,9 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/profile-common.js?v=20260725-entry"></script>
<script src="public/js/media-display.js"></script>
<script src="public/js/profile-pages.js"></script>
<script src="public/js/profile-common.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+7 -8
View File
@@ -31,7 +31,7 @@
<section class="inner-hero promo-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Public Album</div>
<div class="eyebrow">宣传相册</div>
<h1>用公开相册展示家族故事</h1>
<p class="lead">
宣传相册用于前台展示祖屋、祠堂、家族活动、老照片和公开纪念内容,帮助访客更直观理解家族文化。
@@ -74,17 +74,17 @@
</div>
<div>
<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>
<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>
<h4>联系我们</h4>
<p>客服电话:400-000-0000</p>
<p>邮箱:service@example.com</p>
<p>地址:中国 · 成都</p>
<h4>服务支持</h4>
<a href="help.html">帮助中心</a>
<a href="submit-ticket.html">提交问题</a>
<a href="about.html">关于平台</a>
</div>
</div>
<div class="footer-legal">
@@ -98,7 +98,6 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/album-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+8 -7
View File
@@ -31,7 +31,7 @@
<section class="inner-hero promo-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Public Video</div>
<div class="eyebrow">宣传视频</div>
<h1>用宣传视频讲述家族文化</h1>
<p class="lead">
宣传视频用于前台展示家族活动、祠堂风貌、修谱故事和公开纪念内容,让访问者更快进入家族叙事。
@@ -81,17 +81,17 @@
</div>
<div>
<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>
<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>
<h4>联系我们</h4>
<p>客服电话:400-000-0000</p>
<p>邮箱:service@example.com</p>
<p>地址:中国 · 成都</p>
<h4>服务支持</h4>
<a href="help.html">帮助中心</a>
<a href="submit-ticket.html">提交问题</a>
<a href="about.html">关于平台</a>
</div>
</div>
<div class="footer-legal">
@@ -100,6 +100,7 @@
</div>
</div>
</footer>
<script src="config.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+3
View File
@@ -183,10 +183,13 @@
}
.promotion-card {
display: block;
overflow: hidden;
border: 1px solid var(--line);
border-radius: 8px;
background: #fffaf0;
color: inherit;
text-decoration: none;
box-shadow: 0 18px 42px rgba(57, 48, 36, .08);
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;
}
.create-form .field {
display: grid;
gap: 8px;
color: var(--ink);
font-weight: 800;
}
.create-region-picker label {
display: grid;
gap: 8px;
}
.create-region-picker {
/* 创建家谱页地区选择器占满表单宽度,编码通过隐藏字段提交 */
display: grid;
+1155 -3
View File
File diff suppressed because it is too large Load Diff
+81 -1
View File
@@ -1,6 +1,86 @@
/* 加入家谱页只保留单页差异,公共认证卡片样式在 public.css */
.page-join-genealogy .auth-card {
width: min(500px, 100%);
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%);
}
.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 {
+20 -10
View File
@@ -6,29 +6,35 @@
.login-mode-tabs {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
margin: 18px 0 16px;
gap: 4px;
margin: 18px 0 18px;
padding: 4px;
border: 1px solid rgba(138, 64, 42, .14);
border-radius: 8px;
background: rgba(255, 255, 255, .72);
border: 1px solid rgba(200, 59, 50, .16);
border-radius: 999px;
background: rgba(255, 250, 241, .88);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .78);
}
/* 登录方式按钮只负责状态呈现,切换逻辑在 auth-pages.js */
.login-mode-tab {
min-height: 38px;
min-height: 42px;
border: 0;
border-radius: 6px;
border-radius: 999px;
background: transparent;
color: var(--muted);
font: inherit;
font-weight: 800;
text-align: center;
cursor: pointer;
transition: color .22s ease, background .22s ease, box-shadow .22s ease;
}
.login-mode-tab.is-active {
background: var(--brand);
color: #fff;
box-shadow: 0 8px 20px rgba(138, 64, 42, .18);
background: rgba(200, 59, 50, .11);
color: var(--red);
box-shadow:
inset 0 0 0 1px rgba(200, 59, 50, .16),
0 8px 18px rgba(138, 64, 42, .08);
}
.login-mode-panel[hidden] {
@@ -41,6 +47,10 @@
gap: 10px;
}
.page-login .auth-links {
margin-top: 14px;
}
@media (max-width: 720px) {
.page-login .auth-layout {
align-items: start;
+578 -4
View File
@@ -5,6 +5,8 @@
.module-hero {
position: relative;
overflow: hidden;
min-height: 257px;
box-sizing: border-box;
padding: 58px 0 42px;
background:
radial-gradient(circle at 76% 18%, rgba(196, 146, 69, .16), transparent 25%),
@@ -78,9 +80,15 @@
top: 106px;
display: grid;
gap: 8px;
max-height: calc(100vh - 126px);
overflow-y: auto;
padding: 16px;
}
.page-profile-module .module-nav:not([data-workspace-navigation]) {
visibility: hidden;
}
.module-nav a {
padding: 14px 16px;
border-radius: 14px;
@@ -96,15 +104,94 @@
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 {
display: grid;
gap: 20px;
min-width: 0;
}
.feature-status-banner {
padding: 18px 20px;
border: 1px solid rgba(196, 146, 69, .38);
border-radius: 16px;
color: #6b542d;
background: #fff8e8;
box-shadow: 0 12px 26px rgba(89, 66, 26, .07);
}
.feature-status-banner strong {
color: var(--red);
font-size: 16px;
}
.feature-status-banner p {
margin: 6px 0 0;
color: inherit;
line-height: 1.75;
}
.module-panel {
padding: 28px;
}
.rich-editor-host {
overflow: hidden;
border: 1px solid var(--line);
border-radius: 12px;
background: var(--white);
}
.rich-editor-toolbar {
border-bottom: 1px solid var(--line);
}
.rich-editor-content {
height: 320px;
}
.module-panel h2 {
margin-bottom: 10px;
font-family: "SimSun", "Songti SC", serif;
@@ -162,6 +249,26 @@
align-items: center;
}
[data-generation-management][hidden] {
display: none !important;
}
[data-lineage-management][hidden] {
display: none !important;
}
[data-lineage-spouse-field][hidden] {
display: none !important;
}
[data-lineage-pagination][hidden] {
display: none !important;
}
[data-video-management][hidden] {
display: none !important;
}
.generation-row .pill {
border: 0;
cursor: pointer;
@@ -189,7 +296,12 @@
margin-bottom: 16px;
}
.lineage-toolbar input {
.lineage-filter-toolbar {
grid-template-columns: minmax(220px, 1fr) minmax(140px, .5fr) minmax(140px, .5fr) auto;
}
.lineage-toolbar input,
.lineage-toolbar select {
min-height: 46px;
padding: 12px 14px;
border: 1px solid var(--line);
@@ -259,6 +371,23 @@
gap: 10px;
flex-wrap: wrap;
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 {
@@ -266,6 +395,34 @@
background: #fde8e2;
}
.ceremony-invitation-row .pill {
border: 0;
cursor: pointer;
font: inherit;
}
.ceremony-invitation-row .pill:disabled {
cursor: wait;
opacity: .58;
}
.invitation-detail b {
overflow-wrap: anywhere;
}
.invitation-detail .pill {
display: inline-block;
margin-left: 8px;
font-size: 13px;
}
.video-row .pill,
.video-detail + .bottom-actions button {
border: 0;
cursor: pointer;
font: inherit;
}
.article-row {
/* 谱文列表行复用模块列表视觉,单独保留类名方便后续文章样式调整 */
align-items: center;
@@ -308,12 +465,17 @@
cursor: pointer;
}
.upload-input {
.editor-field .upload-input {
position: absolute;
width: 1px;
height: 1px;
min-height: 0;
margin: -1px;
padding: 0;
border: 0;
overflow: hidden;
clip: rect(0 0 0 0);
clip-path: inset(50%);
white-space: nowrap;
}
@@ -411,13 +573,19 @@
.security-form {
/* 安全设置表单复用编辑表单结构,只补模块间距 */
margin-top: 16px;
max-width: 880px;
}
.security-danger {
border-left: 4px solid #a13b32;
border-color: rgba(138, 50, 43, .28);
background: #fff7f2;
}
.security-danger h2 {
color: #7e2d27;
}
.security-danger .editor-field input,
.security-danger .editor-field textarea {
background: #fffdf8;
@@ -434,6 +602,11 @@
gap: 12px;
}
.profile-region-picker > label {
display: grid;
gap: 6px;
}
.profile-region-picker select {
min-height: 48px;
padding: 12px 14px;
@@ -455,6 +628,68 @@
box-shadow: 0 24px 60px rgba(57, 48, 36, .11);
}
.module-card.is-unavailable {
cursor: not-allowed;
opacity: .68;
}
.module-card.is-unavailable:after {
display: none;
}
.module-card.is-unavailable .pill {
display: inline-flex;
margin-top: 12px;
color: #6b542d;
background: #fff0cf;
}
.module-nav-unavailable {
padding: 14px 16px;
border-radius: 14px;
color: var(--muted);
background: #f4f1eb;
font-weight: 900;
}
.profile-path {
display: flex;
align-items: center;
gap: 8px;
margin: 0 0 18px;
color: #6f766f;
font-size: 14px;
font-weight: 800;
}
.profile-path a {
color: var(--red);
}
.profile-path a:hover {
text-decoration: underline;
}
.profile-path span {
color: #a89f91;
}
.profile-path strong {
min-width: 0;
overflow: hidden;
color: var(--ink);
text-overflow: ellipsis;
white-space: nowrap;
}
.module-nav-unavailable small {
display: block;
margin-top: 4px;
color: #8a5a3b;
font-size: 12px;
font-weight: 700;
}
.module-card.is-selected {
border-color: rgba(200, 59, 50, .5);
background: #fff6f1;
@@ -502,6 +737,18 @@
gap: 14px;
}
.notification-raw {
margin: 0;
padding: 16px;
overflow-wrap: anywhere;
white-space: pre-wrap;
border: 1px solid var(--line);
border-radius: 15px;
background: #fffdf8;
color: var(--ink);
line-height: 1.6;
}
.module-row {
display: grid;
grid-template-columns: 1fr auto;
@@ -566,6 +813,16 @@
display: flex;
gap: 14px;
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 {
@@ -583,11 +840,16 @@
gap: 10px;
}
.editor-field label {
.editor-field label,
.editor-label {
color: var(--ink);
font-weight: 900;
}
.field-required {
color: var(--red);
}
.editor-field input,
.editor-field select,
.editor-field textarea {
@@ -619,6 +881,11 @@
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
align-items: start;
}
.editor-field > .btn {
width: fit-content;
}
.profile-form-actions {
@@ -631,6 +898,26 @@
font-weight: 800;
}
[data-growth-editor][hidden],
[data-growth-editor-action][hidden] {
display: none !important;
}
[data-relative-editor][hidden],
[data-relative-editor-action][hidden] {
display: none !important;
}
[data-memo-editor][hidden],
[data-memo-editor-action][hidden] {
display: none !important;
}
[data-merit-editor][hidden],
[data-merit-editor-action][hidden] {
display: none !important;
}
.generation-batch-form {
/* 批量字辈表单复用编辑表单,只补充本页预览前的间距。 */
margin-top: 14px;
@@ -697,10 +984,24 @@
.module-nav {
position: static;
grid-template-columns: repeat(2, minmax(0, 1fr));
max-height: none;
overflow: visible;
}
.lineage-filter-toolbar {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 720px) {
.module-hero {
min-height: 0;
}
.page-profile-module .module-nav {
display: none;
}
.module-title-row {
display: grid;
}
@@ -709,7 +1010,7 @@
font-size: 38px;
}
.module-nav {
.lineage-filter-toolbar {
grid-template-columns: 1fr;
}
@@ -720,3 +1021,276 @@
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; }
+115 -1
View File
@@ -33,6 +33,7 @@
.profile-card,
.profile-stats,
.profile-next-card,
.panel,
.side-card {
border: 1px solid rgba(224, 211, 189, .84);
@@ -94,6 +95,17 @@
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,
.mini-actions {
display: flex;
@@ -101,6 +113,10 @@
gap: 10px;
}
.mini-actions {
margin-top: 12px;
}
.profile-tags span,
.mini-actions span {
padding: 7px 12px;
@@ -118,6 +134,29 @@
gap: 14px;
}
.profile-next-card {
padding: 32px;
}
.profile-next-card .eyebrow {
margin-bottom: 14px;
}
.profile-next-card h2 {
margin-bottom: 12px;
font-size: 30px;
}
.profile-next-card p {
margin-bottom: 22px;
color: var(--muted);
line-height: 1.8;
}
.profile-hero-actions {
margin-top: 22px;
}
.profile-stats div {
min-height: 116px;
padding: 20px;
@@ -180,16 +219,23 @@
}
.user-actions a,
.user-actions button,
.todo-list a {
display: block;
width: 100%;
padding: 15px 16px;
border: 1px solid var(--line);
border-radius: 14px;
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;
}
.user-actions a:hover,
.user-actions button:hover,
.todo-list a:hover {
transform: translateX(5px);
border-color: rgba(71, 111, 99, .28);
@@ -427,6 +473,26 @@
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 {
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;
@@ -448,6 +514,10 @@
transition: opacity .22s ease;
}
.logout-modal[hidden] {
display: none;
}
.logout-modal.show {
opacity: 1;
pointer-events: auto;
@@ -610,4 +680,48 @@
.logout-actions {
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 {
--red: #c83b32;
--red-soft: #f4ded8;
--green: #476f63;
--green-dark: #213f38;
--gold: #c49245;
--ink: #26302c;
--muted: #737b76;
--paper: #fbf7ed;
--paper-2: #f7efe2;
--line: #eadfce;
--white: #fffdf8;
--shadow: 0 24px 70px rgba(69, 53, 35, .12);
--red: var(--color-accent);
--red-soft: var(--color-accent-soft);
--green: var(--color-green);
--green-dark: var(--color-ink);
--gold: var(--color-gold);
--ink: var(--color-ink);
--muted: var(--color-ink-2);
--paper: var(--color-paper);
--paper-2: var(--color-paper-2);
--line: var(--color-rule);
--white: var(--color-panel);
--shadow: var(--shadow-card);
}
.auth-layer-message {
@@ -34,10 +36,19 @@
box-sizing: border-box;
}
html,
body {
overflow-x: clip;
}
html {
scrollbar-gutter: stable;
}
body {
margin: 0;
color: var(--ink);
font-family: "Microsoft YaHei", "PingFang SC", Arial, sans-serif;
font-family: var(--font-body);
background: var(--paper);
}
@@ -63,7 +74,7 @@ img {
}
.container {
width: min(1280px, calc(100% - 72px));
width: min(var(--layout-max), calc(100% - 72px));
margin: 0 auto;
}
@@ -142,7 +153,7 @@ img {
display: flex;
align-items: center;
gap: 26px;
color: rgba(255, 253, 248, .78);
color: #fffdf8;
font-size: 15px;
white-space: nowrap;
}
@@ -154,6 +165,7 @@ img {
.nav-links a,
.nav-actions a {
color: inherit;
position: relative;
transition: color .22s ease, transform .22s ease;
}
@@ -181,11 +193,92 @@ img {
display: flex;
align-items: center;
gap: 14px;
color: rgba(255, 253, 248, .86);
color: #fffdf8;
font-size: 15px;
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 {
min-width: 118px;
height: 44px;
@@ -230,6 +323,18 @@ img {
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 {
position: relative;
overflow: hidden;
@@ -518,6 +623,21 @@ textarea {
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 {
height: 50px;
background: #fffaf1;
@@ -604,8 +724,9 @@ textarea {
opacity: .72;
}
/* 接口列表为空时的统一占位样式 */
.api-empty {
/* 业务请求的加载、空数据、失败和无权限状态 */
.api-empty,
.api-state {
padding: 22px;
border: 1px dashed var(--line);
border-radius: 14px;
@@ -615,6 +736,17 @@ textarea {
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 {
padding: 60px 0 36px;
color: rgba(255, 255, 255, .78);
@@ -790,11 +922,187 @@ textarea {
background: #fff4ef;
}
@media (max-width: 1320px) {
.page-profile .nav-links,
.page-profile-module .nav-links {
display: none;
}
.profile-mobile-menu-toggle {
min-width: 58px;
height: 38px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 12px;
border: 1px solid rgba(255, 248, 230, .38);
border-radius: 10px;
color: #fff8e6;
background: rgba(79, 29, 26, .2);
font: inherit;
font-size: 13px;
font-weight: 800;
}
.page-profile .nav-actions,
.page-profile-module .nav-actions {
display: none;
}
.profile-mobile-menu.is-open {
display: block;
position: absolute;
top: 100%;
right: 0;
left: 0;
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) {
.nav-links {
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,
.grid-2 {
grid-template-columns: 1fr;
@@ -819,6 +1127,61 @@ textarea {
display: none;
}
.page-profile-module .nav,
.page-profile .nav {
height: 70px;
gap: 10px;
}
.page-profile-module .brand,
.page-profile .brand {
padding: 7px 10px;
}
.page-profile-module .brand-logo,
.page-profile .brand-logo {
width: 114px;
}
.page-profile-module .brand-logo-mark {
width: 38px;
height: 38px;
}
.page-profile-module .brand-text {
display: none;
}
.page-profile-module .nav-actions {
display: none;
}
.profile-current-genealogy {
max-width: 112px;
padding: 7px 9px;
}
.profile-current-genealogy span {
display: none;
}
.profile-current-genealogy strong {
font-size: 13px;
}
.profile-mobile-menu-toggle {
min-width: 50px;
padding: 0 9px;
}
.profile-mobile-menu-inner {
grid-template-columns: 1fr;
}
.site-mobile-menu-inner {
grid-template-columns: 1fr;
}
.inner-hero .container {
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) {
*,
*:before,
*:after {
*:not(:where(.page-home-portal, .page-home-portal *)),
*:not(:where(.page-home-portal, .page-home-portal *))::before,
*:not(:where(.page-home-portal, .page-home-portal *))::after {
animation-duration: .01ms !important;
animation-iteration-count: 1 !important;
scroll-behavior: auto !important;
+176
View File
@@ -0,0 +1,176 @@
.site-info-hero {
padding: 78px 0 64px;
background:
radial-gradient(circle at 82% 18%, rgba(196, 146, 69, .2), transparent 24%),
linear-gradient(135deg, #fffaf0, #f3eadc 62%, #edf4ef);
}
.site-info-hero .container {
display: grid;
grid-template-columns: minmax(0, 1.35fr) minmax(220px, .65fr);
align-items: center;
gap: 44px;
}
.site-info-kicker {
margin-bottom: 14px;
color: var(--red);
font-size: 13px;
font-weight: 900;
letter-spacing: .12em;
text-transform: uppercase;
}
.site-info-hero h1 {
margin: 0;
font-size: clamp(36px, 5vw, 58px);
line-height: 1.15;
}
.site-info-hero p {
max-width: 680px;
margin: 20px 0 0;
color: var(--muted);
font-size: 17px;
line-height: 1.9;
}
.site-info-mark {
min-height: 180px;
display: grid;
place-items: center;
border: 1px solid rgba(196, 146, 69, .36);
border-radius: 28px;
color: var(--red);
background: rgba(255, 253, 248, .76);
box-shadow: var(--shadow);
font-size: 62px;
font-weight: 900;
}
.site-info-section {
padding: 72px 0;
}
.site-info-section.alt {
background: rgba(247, 239, 226, .64);
}
.site-info-layout {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(240px, .36fr);
gap: 28px;
}
.site-info-list,
.site-info-panel,
.site-info-status {
padding: 30px;
border: 1px solid var(--line);
border-radius: 22px;
background: var(--white);
box-shadow: 0 16px 46px rgba(69, 53, 35, .08);
}
.site-info-list {
display: grid;
gap: 16px;
}
.site-info-item {
display: block;
padding: 20px;
border: 1px solid rgba(234, 223, 206, .9);
border-radius: 16px;
background: #fffaf2;
transition: transform .2s ease, border-color .2s ease;
}
.site-info-item:hover {
border-color: rgba(200, 59, 50, .4);
transform: translateY(-2px);
}
.site-info-item small,
.site-info-panel small {
color: var(--red);
font-weight: 800;
}
.site-info-item h2,
.site-info-panel h2,
.site-info-status h2 {
margin: 10px 0;
font-size: 22px;
}
.site-info-item p,
.site-info-panel p,
.site-info-status p {
margin: 0;
color: var(--muted);
line-height: 1.8;
}
.site-info-links {
display: grid;
gap: 12px;
margin-top: 18px;
}
.site-info-links a {
color: #8f4b31;
font-weight: 800;
}
.site-info-links a:hover {
color: var(--red);
}
.site-info-notice {
margin-bottom: 22px;
padding: 18px 20px;
border: 1px solid rgba(196, 146, 69, .42);
border-radius: 14px;
color: #775227;
background: #fff6e2;
line-height: 1.75;
}
.site-info-status {
max-width: 760px;
margin: 0 auto;
text-align: center;
}
.site-info-status .site-info-mark {
width: 96px;
min-height: 96px;
margin: 0 auto 22px;
border-radius: 50%;
font-size: 34px;
}
.site-info-actions {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 12px;
margin-top: 26px;
}
@media (max-width: 760px) {
.site-info-hero .container,
.site-info-layout {
grid-template-columns: 1fr;
}
.site-info-hero,
.site-info-section {
padding: 52px 0;
}
.site-info-mark {
min-height: 120px;
}
}

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