家谱现有接口调试50%

This commit is contained in:
rain
2026-07-09 17:29:25 +08:00
commit 6050508144
262 changed files with 63354 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"liveServer.settings.port": 5501
}
+115
View File
@@ -0,0 +1,115 @@
# 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
+115
View File
@@ -0,0 +1,115 @@
<!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/about.css" />
</head>
<body class="page-about">
<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 href="culture.html">家族文化</a>
<a href="surname.html">姓氏百科</a>
<a href="app.html">应用下载</a>
<a href="help.html">帮助中心</a>
<a class="active" 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>
<section class="inner-hero about-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">About Us</div>
<h1>用数字化方式守护家族记忆</h1>
<p class="lead">
代代相传面向家庭、宗亲会与地方文化组织,提供家谱创建、资料管理、文化展示和亲人协作工具。
</p>
<div class="about-tags">
<span>数字家谱</span><span>亲人协作</span><span>文化传承</span>
</div>
</div>
<div class="hero-card about-seal tilt-card">
<div class="seal-orb"></div>
<span></span>
<p>记录家族,连接亲人,传承文化。</p>
</div>
</div>
</section>
<section class="section alt values-section">
<div class="container">
<div class="section-head">
<h2>我们重视</h2>
<p>让家族资料真实、清晰、可协作,也能长期传下去。</p>
</div>
<div class="grid-3">
<div class="card soft value-card tilt-card">
<div class="value-icon"></div>
<h3>真实</h3>
<p>尊重家族资料来源,鼓励记录出处与修订过程。</p>
</div>
<div class="card soft value-card tilt-card">
<div class="value-icon"></div>
<h3>协作</h3>
<p>让更多亲人参与补充资料,形成长期维护机制。</p>
</div>
<div class="card soft value-card tilt-card">
<div class="value-icon"></div>
<h3>传承</h3>
<p>把家族文化整理成可持续保存的数字档案。</p>
</div>
</div>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-grid">
<div>
<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>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
</div>
<div>
<h4>文化内容</h4>
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a><a>平台公告</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>
</div>
</div>
</footer>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+150
View File
@@ -0,0 +1,150 @@
<!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/app.css" />
</head>
<body class="page-app" data-promotion-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 href="culture.html">家族文化</a>
<a href="surname.html">姓氏百科</a>
<a class="active" 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>
<section class="inner-hero app-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Mobile App</div>
<h1>手机端随时记录,电脑端清晰浏览</h1>
<p class="lead">
家族成员可以在手机端上传照片、查看家谱、发布动态;PC
端适合浏览门户内容和公开家谱。
</p>
<div class="hero-buttons">
<a class="btn primary magnetic" href="download.html">下载应用</a
><a class="btn ghost magnetic" href="help.html">查看帮助</a>
</div>
<div class="app-stats">
<span>相册上传</span><span>视频发布</span><span>家谱查看</span>
</div>
</div>
<div class="hero-card app-shot-wrap tilt-card">
<div class="app-orbit"></div>
<img
class="phone-shot first"
src="public/images/app-home.png"
alt="APP首页截图"
/>
<img
class="phone-shot second"
src="public/images/app-family-home.png"
alt="家谱主页截图"
/>
</div>
</div>
</section>
<section class="section alt app-feature-section">
<div class="container">
<div class="section-head">
<h2>移动端能力</h2>
<p>
承接 APP 里的主要功能,让用户理解手机端适合日常记录和共同维护。
</p>
</div>
<div class="grid-4">
<div class="card soft app-feature-card tilt-card">
<div class="app-icon"></div>
<h3>创建家谱</h3>
<p>手机上快速创建或加入家族空间。</p>
</div>
<div class="card soft app-feature-card tilt-card">
<div class="app-icon"></div>
<h3>宣传相册</h3>
<p>随手上传老照片、祖屋照片和聚会照片。</p>
</div>
<div class="card soft app-feature-card tilt-card">
<div class="app-icon"></div>
<h3>宣传视频</h3>
<p>发布家族活动与人物影像。</p>
</div>
<div class="card soft app-feature-card tilt-card">
<div class="app-icon"></div>
<h3>谱文详情</h3>
<p>阅读谱序、传记和家族文章。</p>
</div>
</div>
</div>
</section>
<!-- 应用推广区域:内容来自 /genealogy/app/promotions 列表接口。 -->
<section class="section app-promotion-section">
<div class="container">
<div class="section-head">
<h2>应用推广</h2>
<p>展示后台维护的应用推广、公告和下载引导内容。</p>
</div>
<div class="promotion-list" data-promotion-list>
<div class="api-empty">应用推广内容加载中</div>
</div>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-grid">
<div>
<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>数字家谱</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>
</div>
</div>
</footer>
<script src="public/js/api-client.js"></script>
<script src="public/js/promotion-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+109
View File
@@ -0,0 +1,109 @@
<!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/article-detail.css" />
</head>
<body class="page-article-detail" data-article-detail-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="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>
<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>
</main>
<footer class="footer">
<div class="container">
<div class="footer-grid">
<div>
<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="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="about.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>
</div>
</div>
</footer>
<script src="public/js/api-client.js"></script>
<script src="public/js/article-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

+54
View File
@@ -0,0 +1,54 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 520" role="img" aria-label="家族祠堂插画">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#fff8ec"/>
<stop offset=".58" stop-color="#f4e6cf"/>
<stop offset="1" stop-color="#edf4ef"/>
</linearGradient>
<linearGradient id="roof" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#2e8e94"/>
<stop offset=".58" stop-color="#17606c"/>
<stop offset="1" stop-color="#133c52"/>
</linearGradient>
<linearGradient id="wall" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#e85c47"/>
<stop offset="1" stop-color="#9f342e"/>
</linearGradient>
<linearGradient id="gold" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#f7d58a"/>
<stop offset="1" stop-color="#b77832"/>
</linearGradient>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="150%">
<feDropShadow dx="0" dy="18" stdDeviation="18" flood-color="#61462e" flood-opacity=".18"/>
</filter>
</defs>
<rect width="900" height="520" rx="34" fill="url(#bg)"/>
<circle cx="734" cy="98" r="68" fill="#c49245" opacity=".12"/>
<circle cx="130" cy="388" r="96" fill="#476f63" opacity=".08"/>
<path d="M70 426c96-34 172-42 276-18 93 22 166 16 255-17 85-32 152-29 229 8" fill="none" stroke="#d9c8aa" stroke-width="3" opacity=".42"/>
<g filter="url(#shadow)" transform="translate(0 -42)">
<path d="M95 238c72-35 128-75 166-121h378c39 46 94 86 166 121-22 8-50 6-82-4l-33 33H210l-33-33c-32 10-60 12-82 4z" fill="url(#roof)"/>
<path d="M166 230h568v38H166z" fill="#f0bd64"/>
<path d="M204 264h492v174H204z" fill="url(#wall)"/>
<path d="M236 292h87v126h-87zM577 292h87v126h-87z" fill="#80312e" opacity=".8"/>
<path d="M378 286h144v152H378z" fill="#7e2f2d"/>
<path d="M404 314h92v124h-92z" fill="#bb4638"/>
<path d="M222 274h456" stroke="#f5d083" stroke-width="8" stroke-linecap="round"/>
<path d="M192 438h516v34H192z" fill="#f3d7a5"/>
<path d="M158 472h584v22H158z" fill="#d9b176"/>
<path d="M269 321h22M269 350h22M269 379h22M609 321h22M609 350h22M609 379h22" stroke="#efbf63" stroke-width="10" stroke-linecap="round"/>
<path d="M421 336h58M421 366h58M421 396h58" stroke="#f0bd64" stroke-width="10" stroke-linecap="round"/>
<rect x="378" y="248" width="144" height="46" rx="8" fill="#123a50" stroke="#e6bc69" stroke-width="5"/>
<path d="M418 271h64" stroke="#f6e4bd" stroke-width="8" stroke-linecap="round"/>
<path d="M440 92h20v28h-20z" fill="url(#gold)"/>
<path d="M420 112h60v16h-60z" fill="url(#gold)"/>
</g>
<g opacity=".26" fill="none" stroke="#0f4150" stroke-width="2">
<path d="M160 222c138-60 432-60 580 0"/>
<path d="M210 196c122-38 352-38 480 0"/>
<path d="M280 160c97-18 242-18 340 0"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

+77
View File
@@ -0,0 +1,77 @@
(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: 'http://test-genealogy-api.ddxcjp.cn',
production: ''
};
var TEST_API_HTTP_URL = 'http://test-genealogy-api.ddxcjp.cn';
var TEST_API_HTTPS_URL = 'https://test-genealogy-api.ddxcjp.cn';
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 normalizeApiBaseUrl(value) {
// 测试域名当前 HTTPS 握手失败,开发环境统一走可访问的 HTTP 地址。
var url = String(value || '').replace(/\/+$/, '');
if (url === TEST_API_HTTPS_URL) return TEST_API_HTTP_URL;
return url;
}
function getApiBaseUrl() {
return normalizeApiBaseUrl(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;
}
};
});
+136
View File
@@ -0,0 +1,136 @@
<!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/create-genealogy.css" />
</head>
<body class="page-create-genealogy">
<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 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 active" href="create-genealogy.html"
>创建家谱</a
>
</div>
</div>
</header>
<main>
<section class="inner-hero create-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Start Genealogy</div>
<h1>创建属于家族的数字空间</h1>
<p class="lead">
填写姓氏谱名、地区与简介,先建立家谱主页,再逐步邀请亲人共同完善资料。
</p>
<div class="create-tags">
<span>基础资料</span><span>成员世系</span><span>亲人共修</span>
</div>
</div>
<div class="hero-card process-card tilt-card">
<div class="process-orb"></div>
<h3>创建流程</h3>
<div class="step-list">
<p><b>1</b><span>填写基础信息</span></p>
<p><b>2</b><span>添加第一批成员</span></p>
<p><b>3</b><span>邀请亲人共修</span></p>
</div>
</div>
</div>
</section>
<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">
<div class="form-ornament"></div>
<h2>家谱信息</h2>
<p class="form-note">
先填写基本资料,后续可以继续补充成员、相册和谱文。
</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 />
</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>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-grid">
<div>
<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>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
</div>
<div>
<h4>文化内容</h4>
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a><a>平台公告</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>
</div>
</div>
</footer>
<script src="config.js"></script>
<script src="utils/StorageUtil.js"></script>
<script src="utils/FormUtil.js"></script>
<script src="utils/RequestUtil.js"></script>
<script src="public/js/api-client.js"></script>
<script src="public/js/region-pages.js"></script>
<script src="public/js/genealogy-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+128
View File
@@ -0,0 +1,128 @@
<!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/culture.css" />
</head>
<body class="page-culture">
<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="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>
<section class="inner-hero culture-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Family Culture</div>
<h1>让家风、族训与故事被认真保存</h1>
<p class="lead">
家族文化承接新闻、知识、公告和故事,让谱文之外的记忆也能被阅读、分享和传承。
</p>
<div class="culture-stats">
<span>家风家训</span><span>谱文故事</span><span>使用指南</span>
</div>
</div>
<div class="hero-card culture-quote tilt-card">
<div class="quote-moon"></div>
<span>家风</span>
<p>
一张老照片、一段口述、一篇谱序,都可以成为后人理解家族的入口。
</p>
</div>
</div>
</section>
<section class="section alt article-section">
<div class="container">
<div class="section-head">
<h2>精选文章</h2>
<p>用可阅读的内容入口连接家谱知识、姓氏文化和平台使用指南。</p>
</div>
<div class="grid-3">
<a class="article-card tilt-card" href="article-detail.html">
<div class="article-cover"><span>家谱知识</span></div>
<div class="article-body">
<h3>为什么家谱不只是一本名册</h3>
<p>
家谱记录的是家族关系,也记录一个家庭如何理解自己的来处与传承。
</p>
<span>家谱知识 · 2026.05</span>
</div>
</a>
<a class="article-card tilt-card" href="article-detail.html">
<div class="article-cover"><span>姓氏文化</span></div>
<div class="article-body">
<h3>字辈在家族传承中的作用</h3>
<p>字辈让不同世代的人能找到自己的位置,也让同族称谓更清晰。</p>
<span>姓氏文化 · 2026.05</span>
</div>
</a>
<a class="article-card tilt-card" href="article-detail.html">
<div class="article-cover"><span>使用指南</span></div>
<div class="article-body">
<h3>如何邀请亲人共同完善家谱</h3>
<p>从创建家谱、生成邀请码到设置管理员,逐步建立协作流程。</p>
<span>使用指南 · 2026.05</span>
</div>
</a>
</div>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-grid">
<div>
<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>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
</div>
<div>
<h4>文化内容</h4>
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a><a>平台公告</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>
</div>
</div>
</footer>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+102
View File
@@ -0,0 +1,102 @@
# 接口对接规划与进度记录
## 基本信息
- 项目路径:`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 仓库无法执行。
@@ -0,0 +1,114 @@
# 接口对接编号规划与状态追踪
创建时间: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 执行完成,状态从“进行中”改为“已完成”。
@@ -0,0 +1,134 @@
# 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``https://test-genealogy-api.ddxcjp.cn` | 2026-07-09 15:14:58 +08:00 完成。确认旧 APP 接口依据错误,停止继续按 APP 路径推进;确认 PC YAML 当前只明确认证、文件、地区接口。 |
| PC-001 | 已完成 | 全项目配置层 | `config.js``https://test-genealogy-api.ddxcjp.cn` | 2026-07-09 15:57:36 +08:00 修正完成。根目录 `config.js` 作为开发/生产环境切换的唯一入口;开发环境基础地址为 `https://test-genealogy-api.ddxcjp.cn`PC `clientid` 按用户最新截图改为 `ced7e5f0498645c6ec642dcf450b036f`,客户端 Key 为 `web_pc`。验证:`node tests\config.test.js` 输出 `config tests passed``node --check config.js` 通过。 |
| PC-002 | 已完成 | `utils` 公共工具层、`public/js/api-client.js` | PC `clientid`、通用请求、token、表单、存储、PC YAML paths | 2026-07-09 15:51:34 +08:00 完成。新增 `utils/StorageUtil.js``utils/FormUtil.js``utils/RequestUtil.js`;重写 `public/js/api-client.js`,真实请求只保留 PC YAML 中的 `/captcha/*``/auth/code``/genealogy/pc/auth/*``/genealogy/pc/files/*``/genealogy/region/*`,PC YAML 未覆盖业务方法改为抛 `PC_API_NOT_AVAILABLE`,不再发旧 APP 请求。验证:`node tests\utils.test.js``node tests\api-client.test.js` 均通过;`Select-String -Path public\js\api-client.js -Pattern '/genealogy/app/'` 无输出;`node --check public\js\api-client.js` 通过。 |
| PC-003 | 已完成 | `login.html``register.html``forgot-password.html``profile-security.html``profile-data.html``profile.html` | `/genealogy/pc/auth/*``/captcha/*` | 2026-07-09 16:00:22 +08:00 完成。`login.html` 使用 `WEB_H5_LOGIN``register.html` 使用 `WEB_H5_REGISTER``forgot-password.html` 使用 `WEB_H5_FORGOT_PASSWORD`;认证/资料/安全页面均已补齐 `config.js``utils/*``api-client.js` 脚本顺序。资料提交字段按 PC YAML 使用 `provinceCode/cityCode/districtCode`。验证:`node tests\auth-pages.test.js``node tests\captcha-pages.test.js``node tests\profile-pages.test.js``node tests\security-pages.test.js` 均通过;相关 JS `node --check` 通过;旧 `PC_LOGIN/PC_REGISTER/PC_PASSWORD_RESET/APP_*` 场景扫描无输出。 |
| PC-004 | 已完成 | 文件上传相关页面 | `/genealogy/pc/files/*` | 2026-07-09 16:03:23 +08:00 完成。`api-client.js` 文件上传、分片上传、文件引用绑定均走 `/genealogy/pc/files/*``profile-album.html``profile-article-edit.html``profile-feed-edit.html``profile-growth-edit.html``profile-memo-edit.html``profile-gift-edit.html` 已补齐 `config.js``utils/*``api-client.js``upload-pages.js` 顺序。验证:`node tests\upload-pages.test.js``node tests\api-client.test.js` 通过;`Select-String -Path public\js\api-client.js,public\js\upload-pages.js -Pattern '/genealogy/app/files'` 无输出。 |
| PC-005 | 已完成 | 使用地区选择的页面 | `/genealogy/region/*` | 2026-07-09 16:05:12 +08:00 完成。`create-genealogy.html``profile-create-family.html``profile-data.html` 地区选择页面已补齐 `config.js``utils/*``api-client.js``region-pages.js` 顺序;地区请求只走 `/genealogy/region/children``/genealogy/region/path/{regionCode}``/genealogy/region/search``/genealogy/region/{regionCode}`。验证:`node tests\region-pages.test.js``node tests\profile-pages.test.js` 通过;`node --check public\js\region-pages.js` 通过;地区旧 APP 路径扫描无输出。 |
| PC-006 | 已修正 | 全项目页面覆盖矩阵 | PC YAML 现有接口覆盖范围与缺口 | 2026-07-09 15:34:26 +08:00 完成修正。该项只作为文档清单任务,不做业务代码。输出页面级覆盖矩阵:完整可闭环、接口存在但验证码待确认、只能保留公共能力、文件能力可切换但主业务待确认、PC YAML 未覆盖。避免把“页面里某个公共接口可用”误标为“整页已可对接”。 |
| PC-007 | 已完成 | 详细实施规划 | `genealogy-pc-openapi.yaml``paths``docs/superpowers/plans/2026-07-09-pc-api-replan.md` | 2026-07-09 15:41:40 +08:00 完成修正。重写详细实施计划:只以 YAML `paths` 为真实接口来源;`api-client.js` 不能保留 APP 路径 fallback;PC YAML 未覆盖页面改为受控“接口待确认/PC_API_NOT_AVAILABLE”;补充资料字段 `provinceCode/cityCode/districtCode`、TAC 登录场景 `WEB_H5_LOGIN`、文件/地区局部能力边界。 |
| PC-008 | 已完成 | PC YAML 未覆盖业务页面 | `PC_API_NOT_AVAILABLE`、旧 APP 网络路径清理 | 2026-07-09 16:06:35 +08:00 完成。`api-client.js` 中家谱主体、成员、字辈、世系、动态、文章、相册、祭祀、族务、VIP、通知、反馈、帮助、推广等 PC YAML 未覆盖业务方法保留方法名但统一抛 `PC_API_NOT_AVAILABLE`,不再发 `/genealogy/app/` 请求。验证:`Get-ChildItem -Path tests -Filter *.test.js | Sort-Object Name | ForEach-Object { node $_.FullName }` 全部通过;`Select-String -Path public\js\api-client.js -Pattern '/genealogy/app/'` 无输出。 |
| PC-009 | 已完成 | `login.html``register.html``forgot-password.html` | `/genealogy/pc/auth/login``/genealogy/pc/auth/login/sms``/genealogy/pc/auth/register``/genealogy/pc/auth/sms/code``/genealogy/pc/auth/password/reset``/captcha/*` | 2026-07-09 16:32:17 +08:00 开始,2026-07-09 16:40:02 +08:00 完成。专项计划见 `docs/superpowers/plans/2026-07-09-pc-auth-three-pages.md``login.html` 已补齐密码登录/短信登录两种模式,短信登录使用 `WEB_H5_LOGIN` 发送验证码并调用 `/genealogy/pc/auth/login/sms`;注册页继续使用 `WEB_H5_REGISTER``/genealogy/pc/auth/register`;忘记密码页继续使用 `WEB_H5_FORGOT_PASSWORD``/genealogy/pc/auth/sms/code``/genealogy/pc/auth/password/reset`。修改文件:`login.html``public/css/login.css``public/js/auth-pages.js``tests/auth-pages.test.js``tests/api-client.test.js`。验证:`node tests\auth-pages.test.js``node tests\api-client.test.js``node tests\captcha-pages.test.js` 通过;全量 `tests/*.test.js` 通过;`node --check public\js\auth-pages.js``node --check public\js\api-client.js``node --check public\js\captcha-pages.js` 通过;内联样式和旧验证码场景扫描无输出。 |
| PC-010 | 已完成 | `config.js``public/js/api-client.js` | 开发环境接口基础地址、`/captcha/require``/captcha/challenge` | 2026-07-09 16:45:35 +08:00 开始,2026-07-09 16:48:05 +08:00 完成。根因:浏览器和 `curl.exe` 复现 `https://test-genealogy-api.ddxcjp.cn` TLS 握手失败,错误为 `ERR_SSL_UNRECOGNIZED_NAME_ALERT`/`SEC_E_ILLEGAL_MESSAGE`;同一 `/captcha/require` 请求改用 `http://test-genealogy-api.ddxcjp.cn` 返回 `200 OK`。处理:开发环境基础地址改为 `http://test-genealogy-api.ddxcjp.cn``config.js` 对旧的 `https://test-genealogy-api.ddxcjp.cn` 本地覆盖值做归一,避免浏览器 localStorage 残留继续走坏地址;`api-client.js` fallback 默认地址同步改为 HTTP。修改文件:`config.js``public/js/api-client.js``tests/config.test.js``tests/api-client.test.js`。验证:`node tests\config.test.js``node tests\api-client.test.js``node --check config.js``node --check public\js\api-client.js` 通过。 |
| PC-011 | 已完成 | `login.html``register.html``forgot-password.html``public/css/public.css``public/js/captcha-pages.js` | `public/tac` 弹窗挂载、`/captcha/challenge``/captcha/verify` | 2026-07-09 16:56:22 +08:00 开始,2026-07-09 17:02:35 +08:00 完成。根因:`public/tac/css/tac.css``#tianai-captcha-parent` 是相对定位,会在绑定元素内渲染;此前把 `[data-captcha-box]` 放进表单内部,导致验证码块直接占据卡片内容区域。处理:三张认证页移除表单内部挂载点,新增页面级 `.auth-captcha-modal[data-captcha-box]``public/css/public.css` 增加固定遮罩、居中和移动端宽度约束;`captcha-pages.js` 优先使用页面级挂载点;未修改 `public/tac/js/tac.min.js`。修改文件:`login.html``register.html``forgot-password.html``public/css/public.css``public/js/captcha-pages.js``tests/auth-page-structure.test.js``tests/captcha-pages.test.js`。验证:`node tests\auth-page-structure.test.js``node tests\captcha-pages.test.js` 通过;全量 `tests/*.test.js` 通过;`node --check public\js\captcha-pages.js``node --check public\js\auth-pages.js` 通过;表单内部 `data-captcha-box` 扫描无输出。 |
## 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 测试通过。
@@ -0,0 +1,128 @@
# 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 标记为已完成,写入完成时间、修改文件、验证命令和剩余风险。
@@ -0,0 +1,762 @@
# 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.
@@ -0,0 +1,127 @@
# 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` 状态改为“已完成”,写入完成时间、修改文件、接口路径、验证码场景和验证结果。
@@ -0,0 +1,95 @@
# 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**.
+104
View File
@@ -0,0 +1,104 @@
<!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/download.css" />
</head>
<body class="page-download" data-promotion-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 href="culture.html">家族文化</a
><a href="surname.html">姓氏百科</a
><a class="active" 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>
<section class="inner-hero download-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Download</div>
<h1>下载代代相传 APP</h1>
<p class="lead">
手机端适合日常上传宣传相册、发布宣传视频、查看家谱和补充资料。
</p>
<div class="detail-actions">
<a class="btn primary magnetic" href="app.html">查看应用介绍</a
><a class="btn ghost magnetic" href="help.html">安装帮助</a>
</div>
</div>
<div class="hero-card qr-card tilt-card">
<div class="qr-box"><span>扫码</span></div>
<h3>手机扫码下载</h3>
<p>支持 iOS 与 Android,实际项目可替换为真实下载二维码。</p>
</div>
</div>
</section>
<section class="section alt">
<div class="container">
<div class="section-head">
<h2>下载方式</h2>
<p>前台先展示下载入口,后续可以接真实应用商店地址。</p>
</div>
<!-- 下载入口区域:内容来自 /genealogy/app/promotions 列表接口。 -->
<div class="grid-3" data-promotion-download-list>
<div class="api-empty">下载推广内容加载中</div>
</div>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-grid">
<div>
<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="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="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>
</div>
</div>
</footer>
<script src="public/js/api-client.js"></script>
<script src="public/js/promotion-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+138
View File
@@ -0,0 +1,138 @@
<!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/family-detail.css" />
</head>
<body class="page-family-detail">
<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 class="active" 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 data-family-detail>
<section class="inner-hero detail-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Family Detail</div>
<h1 data-family-text="title">四川武胜汤氏族</h1>
<p class="lead" data-family-text="intro|description|summary" data-fallback="公开展示家族源流、世系概览、字辈排行、谱文资料和宣传相册,让访客快速理解这一本家谱的内容结构。">
公开展示家族源流、世系概览、字辈排行、谱文资料和宣传相册,让访客快速理解这一本家谱的内容结构。
</p>
<div class="detail-actions">
<a class="btn primary magnetic" href="join-genealogy.html" data-family-join-link
>申请加入</a
>
<a class="btn ghost magnetic" href="plaza.html">返回广场</a>
</div>
</div>
<div class="hero-card family-summary tilt-card">
<span class="summary-badge">公开家谱</span>
<h3 data-family-text="title">汤氏族谱</h3>
<div class="summary-grid" data-family-summary>
<span><b>128</b>成员</span>
<span><b>6</b>世代</span>
<span><b>18</b>谱文</span>
<span><b>7</b>相册</span>
</div>
</div>
</div>
</section>
<section class="section alt">
<div class="container detail-layout">
<aside class="detail-panel tilt-card">
<h3>家谱信息</h3>
<p><b>主要姓氏:</b><span data-family-text="surname" data-fallback="汤"></span></p>
<p><b>地区:</b><span data-family-text="addressDetail|regionName|originPlace" data-fallback="四川武胜">四川武胜</span></p>
<p><b>堂号:</b><span data-family-text="ancestralHall" data-fallback="中山堂">中山堂</span></p>
<p><b>维护方式:</b><span data-family-text="joinModeName|joinMode" data-fallback="多人共修">多人共修</span></p>
</aside>
<div class="detail-main">
<article class="content-card tilt-card">
<h2>家族简介</h2>
<p data-family-text="intro|description|summary" data-fallback="本谱以四川武胜汤氏族人为主体,整理始祖源流、迁徙线索、字辈排行、重要人物与家风故事。公开内容用于展示家族文化,完整成员资料需申请加入后查看。">
本谱以四川武胜汤氏族人为主体,整理始祖源流、迁徙线索、字辈排行、重要人物与家风故事。公开内容用于展示家族文化,完整成员资料需申请加入后查看。
</p>
</article>
<div class="grid-3">
<div class="mini-card tilt-card">
<h3>世系概览</h3>
<p>按代际关系梳理成员结构,帮助族人找到自己的位置。</p>
</div>
<div class="mini-card tilt-card">
<h3>谱文资料</h3>
<p>收录谱序、家训、修谱说明和公开纪念文章。</p>
</div>
<div class="mini-card tilt-card">
<h3>宣传相册</h3>
<p>展示祠堂祖屋、修谱活动、老照片和家族纪念内容。</p>
</div>
</div>
</div>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-grid">
<div>
<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="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="about.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>
</div>
</div>
</footer>
<script src="public/js/api-client.js"></script>
<script src="public/js/genealogy-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+77
View File
@@ -0,0 +1,77 @@
<!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/layui/css/layui.css" />
<link rel="stylesheet" href="public/css/public.css" />
<link rel="stylesheet" href="public/tac/css/tac.css" />
<link rel="stylesheet" href="public/css/forgot-password.css" />
</head>
<body class="page-forgot-password">
<main class="auth-layout auth-scenic-layout recover-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 recover-card"
aria-labelledby="recover-title"
>
<div class="eyebrow">Password Recovery</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>
</div>
<input
class="input"
name="newPassword"
type="password"
autocomplete="new-password"
placeholder="新密码"
/>
<input
class="input"
name="confirmPassword"
type="password"
autocomplete="new-password"
placeholder="确认新密码"
/>
<input type="hidden" name="validToken" />
<button class="btn primary" type="submit">重设密码</button>
</form>
</section>
</main>
<!-- 滑动验证弹窗由 captcha-pages.js 按需挂载到页面级容器,避免挤占表单布局 -->
<div class="auth-captcha-modal" data-captcha-box aria-live="polite"></div>
<script src="public/js/md5.js"></script>
<script src="config.js"></script>
<script src="utils/StorageUtil.js"></script>
<script src="utils/FormUtil.js"></script>
<script src="utils/RequestUtil.js"></script>
<script src="utils/MessageUtil.js"></script>
<script src="public/js/api-client.js"></script>
<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/auth-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
File diff suppressed because it is too large Load Diff
+156
View File
@@ -0,0 +1,156 @@
<!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/genealogy.css" />
</head>
<body class="page-genealogy">
<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 class="active" 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>
<section class="inner-hero genealogy-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Digital Genealogy</div>
<h1>把成员关系整理成清晰可查的数字家谱</h1>
<p class="lead">
从始祖、分支、排行到个人资料,数字家谱帮助家族建立长期可维护的成员档案和世系关系。
</p>
<div class="genealogy-stats">
<div><strong>8</strong><span>资料模块</span></div>
<div><strong>3步</strong><span>快速建谱</span></div>
<div><strong>多人</strong><span>协作共修</span></div>
</div>
</div>
<div class="hero-card tree-preview tilt-card">
<div class="tree-glow"></div>
<div class="tree-row root"><span>始祖</span></div>
<div class="tree-line"></div>
<div class="tree-row branch">
<span>长房</span><span>二房</span><span>三房</span>
</div>
<div class="tree-line short"></div>
<div class="tree-row generation">
<span>第六世</span><span>第七世</span><span>第八世</span
><span>第九世</span>
</div>
</div>
</div>
</section>
<section class="section alt capability-section">
<div class="container">
<div class="section-head">
<h2>核心能力</h2>
<p>
围绕修谱、查谱和协作维护,把家族资料从零散记录整理成完整档案。
</p>
</div>
<div class="grid-4">
<div class="card soft feature-card tilt-card">
<div class="icon-word"></div>
<h3>世系图</h3>
<p>
以树谱方式展示成员关系,支持查看个人资料、添加亲属和调整排行。
</p>
</div>
<div class="card soft feature-card tilt-card">
<div class="icon-word"></div>
<h3>字辈谱</h3>
<p>记录各代字辈与人数,让世代脉络更加清楚。</p>
</div>
<div class="card soft feature-card tilt-card">
<div class="icon-word"></div>
<h3>谱文资料</h3>
<p>沉淀谱序、家训、人物传记和修谱说明。</p>
</div>
<div class="card soft feature-card tilt-card">
<div class="icon-word"></div>
<h3>权限管理</h3>
<p>设置管理员、内容可见范围与入谱审核,保护家族隐私。</p>
</div>
</div>
</div>
</section>
<section class="section coedit-section">
<div class="container grid-2">
<div class="card dark coedit-card tilt-card">
<h2>适合多人共修</h2>
<p>
家族成员可以分别补充照片、成员资料、谱文和故事,再由管理员统一审核,避免资料越传越散。
</p>
<a class="btn primary magnetic" href="create-genealogy.html"
>开始创建</a
>
</div>
<div class="card process-card tilt-card">
<h3>修谱流程</h3>
<div class="step-list">
<p><b>1</b><span>创建家谱并填写姓氏、地区、堂号</span></p>
<p><b>2</b><span>录入第一批成员,建立上下辈关系</span></p>
<p><b>3</b><span>邀请亲人加入,共同完善资料</span></p>
</div>
</div>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-grid">
<div>
<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>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
</div>
<div>
<h4>文化内容</h4>
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a><a>平台公告</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>
</div>
</div>
</footer>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+126
View File
@@ -0,0 +1,126 @@
<!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/help.css" />
</head>
<body class="page-help">
<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 href="culture.html">家族文化</a>
<a href="surname.html">姓氏百科</a>
<a href="app.html">应用下载</a>
<a class="active" 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>
<section class="inner-hero help-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Help Center</div>
<h1>从创建到协作,快速解决常见问题</h1>
<p class="lead">
覆盖注册登录、创建家谱、邀请成员、资料审核、隐私设置和内容发布等流程。
</p>
<div class="help-tags">
<span>创建家谱</span><span>邀请成员</span><span>隐私权限</span>
</div>
</div>
<div class="hero-card help-card tilt-card">
<div class="help-orb"></div>
<div class="help-icon">?</div>
<h3>需要人工协助?</h3>
<p class="muted">
遇到资料导入、权限或账号问题,可以提交说明,客服会协助处理。
</p>
<a class="btn primary magnetic" href="submit-ticket.html"
>提交工单</a
>
</div>
</div>
</section>
<section class="section alt faq-section">
<div class="container">
<div class="section-head">
<h2>常见问题</h2>
<p>把复杂流程拆成清楚步骤,便于用户第一次使用时快速上手。</p>
</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>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-grid">
<div>
<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>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
</div>
<div>
<h4>文化内容</h4>
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a><a>平台公告</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>
</div>
</div>
</footer>
<script src="public/js/api-client.js"></script>
<script src="public/js/help-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+625
View File
@@ -0,0 +1,625 @@
<!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>
<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="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>
</div>
</div>
</div>
</section>
<section class="recommend">
<div class="container">
<div class="section-head">
<h2>相关推荐</h2>
</div>
<div class="recommend-grid">
<a
class="recommend-card tilt-card"
href="https://example.com/archive-guide"
target="_blank"
rel="noopener"
>
<span>资料整理</span>
<h3>家庭档案整理指南</h3>
<p>参考照片、证件、谱牒、口述资料的归档方式。</p>
</a>
<a
class="recommend-card tilt-card"
href="https://example.com/surname-origin"
target="_blank"
rel="noopener"
>
<span>姓氏源流</span>
<h3>姓氏文化资料库</h3>
<p>查看姓氏源流、迁徙脉络、堂号和郡望参考资料。</p>
</a>
<a
class="recommend-card tilt-card"
href="https://example.com/clan-culture"
target="_blank"
rel="noopener"
>
<span>宗亲文化</span>
<h3>宗亲会活动参考</h3>
<p>了解宗亲联谊、祭祖活动和家风传播的组织方式。</p>
</a>
<a
class="recommend-card tilt-card"
href="https://example.com/genealogy-tools"
target="_blank"
rel="noopener"
>
<span>工具服务</span>
<h3>修谱工具与服务</h3>
<p>查找谱书排版、影像扫描、云端存储等外部服务。</p>
</a>
<a
class="recommend-card tilt-card"
href="https://example.com/local-history"
target="_blank"
rel="noopener"
>
<span>地方志</span>
<h3>地方文献检索入口</h3>
<p>从地方志、县志和族群资料中寻找家族线索。</p>
</a>
<a
class="recommend-card tilt-card"
href="https://example.com/name-study"
target="_blank"
rel="noopener"
>
<span>人名研究</span>
<h3>字辈与命名参考</h3>
<p>参考传统字辈、排行、称谓和家族命名资料。</p>
</a>
<a
class="recommend-card tilt-card"
href="https://example.com/photo-restore"
target="_blank"
rel="noopener"
>
<span>影像修复</span>
<h3>老照片修复服务</h3>
<p>了解老照片扫描、修复、上色和数字归档服务。</p>
</a>
<a
class="recommend-card tilt-card"
href="https://example.com/oral-history"
target="_blank"
rel="noopener"
>
<span>口述史</span>
<h3>家族故事采集方法</h3>
<p>参考长辈访谈、口述记录和家风故事整理方式。</p>
</a>
</div>
</div>
</section>
<section class="friend-links">
<div class="container">
<div class="friend-links-panel">
<h2>友情链接</h2>
<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>
</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>
</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
>
</div>
</div>
</footer>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+37
View File
@@ -0,0 +1,37 @@
<!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/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或邀请码" />
</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>
</section>
</main>
<script src="public/js/api-client.js"></script>
<script src="public/js/genealogy-pages.js"></script>
</body>
</html>
+107
View File
@@ -0,0 +1,107 @@
<!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/layui/css/layui.css" />
<link rel="stylesheet" href="public/css/public.css" />
<link rel="stylesheet" href="public/tac/css/tac.css" />
<link rel="stylesheet" href="public/css/login.css" />
</head>
<body class="page-login">
<main class="auth-layout auth-scenic-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" aria-labelledby="login-title">
<div class="eyebrow">Welcome Back</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>
<button class="login-mode-tab" type="button" role="tab" aria-selected="false" data-login-mode-tab="sms">
短信登录
</button>
</div>
<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"
>
<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="密码"
/>
<input type="hidden" name="validToken" />
<button class="btn primary" type="submit">登录</button>
</form>
<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
>
<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>
</div>
<input type="hidden" name="validToken" />
<button class="btn primary" type="submit">短信登录</button>
</form>
<div class="auth-links">
<a href="register.html">注册新账号</a>
<a href="forgot-password.html">忘记密码</a>
</div>
</section>
</main>
<!-- 滑动验证弹窗由 captcha-pages.js 按需挂载到页面级容器,避免挤占表单布局 -->
<div class="auth-captcha-modal" data-captcha-box aria-live="polite"></div>
<script src="public/js/md5.js"></script>
<script src="config.js"></script>
<script src="utils/StorageUtil.js"></script>
<script src="utils/FormUtil.js"></script>
<script src="utils/RequestUtil.js"></script>
<script src="utils/MessageUtil.js"></script>
<script src="public/js/api-client.js"></script>
<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/auth-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+114
View File
@@ -0,0 +1,114 @@
<!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/notice-detail.css" />
</head>
<body class="page-notice-detail" data-promotion-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="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>
<section class="inner-hero notice-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Notice</div>
<h1 data-promotion-title>平台内容公开展示规范说明</h1>
<p class="lead" data-promotion-summary>
为了保护家族资料安全,平台仅展示管理员确认公开的家谱介绍、文章、宣传相册和宣传视频。
</p>
<div class="article-meta">
<span>平台公告</span><span data-promotion-time>2026.05</span>
</div>
</div>
<div class="hero-card notice-card tilt-card">
<span></span>
<p>公开有边界,传承更长久。</p>
</div>
</div>
</section>
<section class="section alt">
<div class="container notice-layout">
<!-- 公告详情区域:从 /genealogy/app/promotions 列表中按 id 选择一条展示。 -->
<article class="notice-content tilt-card" data-promotion-content>
<p>
平台前台页面主要用于展示家族文化与公开资料。涉及成员联系方式、详细个人信息、未审核资料等内容,不会在公开页面直接呈现。
</p>
<h2>公开内容范围</h2>
<p>
可公开内容包括家族简介、姓氏源流、谱文节选、宣传相册、宣传视频、平台公告和管理员允许展示的纪念内容。
</p>
<h2>资料维护建议</h2>
<p>
建议由管理员先整理资料,再邀请亲人补充和校对。需要修改公开范围时,可以在权限设置中调整。
</p>
</article>
<aside class="notice-side tilt-card">
<h3>相关页面</h3>
<a href="help.html">帮助中心</a
><a href="article-detail.html">家谱知识</a
><a href="about.html">关于我们</a>
</aside>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-grid">
<div>
<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="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="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>
</div>
</div>
</footer>
<script src="public/js/api-client.js"></script>
<script src="public/js/promotion-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+145
View File
@@ -0,0 +1,145 @@
<!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/plaza.css" />
</head>
<body class="page-plaza">
<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 class="active" 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>
<section class="inner-hero plaza-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Family Plaza</div>
<h1>浏览公开家谱,寻找同宗线索</h1>
<p class="lead">
按姓氏、地区、堂号和家谱名称检索公开家谱,先看示例,再创建自己的家族空间。
</p>
<div class="plaza-stats">
<span><b>128+</b>公开家谱</span>
<span><b>36</b>地区线索</span>
<span><b>18</b>谱文示例</span>
</div>
</div>
<div class="hero-card search-card tilt-card">
<div class="search-ornament"></div>
<h3>查找公开家谱</h3>
<div class="toolbar search-toolbar">
<input class="input" placeholder="输入姓氏、地区或家谱名称" /><a
class="btn primary magnetic"
href="search-result.html"
>搜索</a
>
</div>
<div class="tag-row">
<span class="tag">公开家谱</span><span class="tag">字辈完整</span
><span class="tag">多人共修</span>
</div>
</div>
</div>
</section>
<section class="section alt plaza-list-section">
<div class="container">
<div class="section-head">
<h2>推荐家谱</h2>
<p>展示公开或示例家谱,让访问者理解数字家谱最终会呈现成什么样。</p>
</div>
<div class="grid-3" data-genealogy-list="public">
<a class="family-card featured tilt-card" href="family-detail.html">
<div class="family-card-shine"></div>
<div class="family-badge">推荐</div>
<h3>四川成都陈氏族</h3>
<p>
已收录成员 128 人,整理字辈 6 代,公开谱文 18 篇,宣传相册 7
组。
</p>
<div class="tag-row">
<span class="tag">四川</span><span class="tag">陈氏</span
><span class="tag">128人</span>
</div>
</a>
<a class="card family-lite tilt-card" href="family-detail.html">
<div class="family-icon"></div>
<h3>湖广刘氏家谱</h3>
<p>从始祖世系到现代家庭分支,保留字辈与迁徙记录。</p>
<div class="tag-row">
<span class="tag">公开家谱</span
><span class="tag">字辈完整</span>
</div>
</a>
<a class="card family-lite tilt-card" href="family-detail.html">
<div class="family-icon"></div>
<h3>岭南张氏宗谱</h3>
<p>以谱文、相册、家族动态为主,记录家风与重要纪念。</p>
<div class="tag-row">
<span class="tag">家族文化</span
><span class="tag">宣传相册</span>
</div>
</a>
</div>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-grid">
<div>
<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>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
</div>
<div>
<h4>文化内容</h4>
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a><a>平台公告</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>
</div>
</div>
</footer>
<script src="public/js/api-client.js"></script>
<script src="public/js/genealogy-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+85
View File
@@ -0,0 +1,85 @@
<!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-admin-permission-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-families.html">我的家谱</a
><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>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Permissions</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-admin.html">家族管理</a
><a class="active" href="profile-admin-permissions.html"
>管理员权限</a
><a href="profile-messages.html">入谱审核</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>
</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="public/js/api-client.js"></script>
<script src="public/js/member-admin-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+142
View File
@@ -0,0 +1,142 @@
<!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-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-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>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Album</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-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>
</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>
</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/RequestUtil.js"></script>
<script src="public/js/api-client.js"></script>
<script src="public/js/upload-pages.js"></script>
<script src="public/js/album-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+129
View File
@@ -0,0 +1,129 @@
<!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-article-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-article.html">谱文</a>
</nav>
<div class="nav-actions">
<a href="profile-article.html">返回谱文</a
><button class="btn primary magnetic" type="submit" form="article-edit-form">保存</button>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Article Editor</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-article.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>
<form id="article-edit-form" class="editor-form layui-form" data-article-form>
<div class="editor-field">
<label for="articleTitle">谱文标题</label
><input
id="articleTitle"
name="articleTitle"
type="text"
placeholder="请输入谱文标题"
required
/>
</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
id="articleSignature"
name="authorName"
type="text"
placeholder="请输入落款"
/>
</div>
</div>
<div class="editor-field">
<label for="articleContent">正文</label
><textarea
id="articleContent"
name="articleContent"
class="js-rich-editor"
placeholder="请输入谱文内容"
required
></textarea>
</div>
<div class="editor-field">
<label for="articleSummary">摘要</label>
<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 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>
</div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">保存</button
><a class="btn ghost magnetic" href="profile-article.html"
>取消</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/ke/kindeditor.min.js"></script>
<script src="public/js/rich-editor.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/RequestUtil.js"></script>
<script src="public/js/api-client.js"></script>
<script src="public/js/upload-pages.js"></script>
<script src="public/js/article-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+94
View File
@@ -0,0 +1,94 @@
<!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-article-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-article.html">谱文</a>
</nav>
<div class="nav-actions">
<a href="profile-content.html">返回发布中心</a
><a class="btn primary magnetic" href="profile-article-edit.html"
>添加谱文</a
>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Article</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-article.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>
<!-- 谱文列表由 /articles 接口渲染 -->
<div class="module-list" data-article-list>
<div class="api-empty">谱文加载中...</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>
</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="public/js/api-client.js"></script>
<script src="public/js/article-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+126
View File
@@ -0,0 +1,126 @@
<!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-content-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 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 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">Content Studio</div>
<div class="module-title-row">
<div>
<h1>内容发布</h1>
<p>
承接 APP
家谱主页里的谱文、视频、相册、功德录、贺礼邀请、成长日志、动态和备忘录。
</p>
</div>
<a class="btn primary magnetic" href="profile-article.html"
>添加谱文</a
>
</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 href="profile-family-admin.html">家族管理</a
><a class="active" 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-grid">
<a class="module-card tilt-card" href="profile-article.html"
><span class="icon"></span>
<h3>谱文</h3>
<p>添加谱文标题、内容、落款和图片质量设置。</p></a
>
<a class="module-card tilt-card" href="profile-album.html"
><span class="icon"></span>
<h3>家族相册</h3>
<p>新建相册、编辑封面、上传照片。</p></a
>
<a class="module-card tilt-card" href="profile-video.html"
><span class="icon"></span>
<h3>家族视频</h3>
<p>发布视频、编辑家族视频和视频描述。</p></a
>
<a class="module-card tilt-card" href="profile-merit.html"
><span class="icon"></span>
<h3>功德录</h3>
<p>添加功德人,记录事迹和时间。</p></a
>
<a class="module-card tilt-card" href="profile-gift.html"
><span class="icon"></span>
<h3>贺礼邀请</h3>
<p>创建婚礼、升学、生日等贺礼邀请。</p></a
>
<a class="module-card tilt-card" href="profile-feed.html"
><span class="icon"></span>
<h3>发布动态</h3>
<p>向家族圈发布图文动态,可设置置顶和精华。</p></a
>
<a class="module-card tilt-card" href="profile-growth.html"
><span class="icon"></span>
<h3>成长日志</h3>
<p>记录个人成长节点,支持照片附件。</p></a
>
<a class="module-card tilt-card" href="profile-memo.html"
><span class="icon"></span>
<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>
</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="public/js/api-client.js"></script>
<script src="public/js/article-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+137
View File
@@ -0,0 +1,137 @@
<!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">
<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 class="active" href="profile-families.html">我的家谱</a
><a href="profile-family-admin.html">家族管理</a
><a href="profile-content.html">内容发布</a>
</nav>
<div class="nav-actions">
<a href="profile-families.html">返回我的家谱</a>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Create Family</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-families.html">我的家谱</a
><a class="active" href="profile-create-family.html">创建家谱</a
><a href="profile-join-family.html">加入家谱</a>
</aside>
<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">
<div class="editor-two">
<div class="editor-field">
<label for="familySurname">姓氏</label>
<input id="familySurname" name="surname" type="text" required />
</div>
<div class="editor-field">
<label for="familyName">谱名</label>
<input id="familyName" name="genealogyName" type="text" required />
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="ancestralHall">堂号/祠堂</label>
<input id="ancestralHall" name="ancestralHall" type="text" />
</div>
<div class="editor-field">
<label for="originPlace">籍贯/起源地</label>
<input id="originPlace" name="originPlace" type="text" />
</div>
</div>
<div class="editor-field">
<label>家族所在地</label>
<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>
<input type="hidden" name="regionCode" data-region-code />
</div>
</div>
<div class="editor-field">
<label for="addressDetail">详细地址</label>
<input id="addressDetail" name="addressDetail" type="text" />
</div>
<div class="editor-field">
<label for="intro">家谱简介</label>
<textarea id="intro" name="intro"></textarea>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="visibility">可见范围</label>
<select id="visibility" name="visibility">
<option value="1">公开</option>
<option value="2">成员可见</option>
<option value="0">私密</option>
</select>
</div>
<div class="editor-field">
<label for="joinMode">加入方式</label>
<select id="joinMode" name="joinMode">
<option value="1">审核加入</option>
<option value="2">邀请码加入</option>
<option value="0">关闭加入</option>
</select>
</div>
</div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">确定创建</button>
<a class="btn ghost magnetic" href="profile-families.html">返回列表</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="config.js"></script>
<script src="utils/StorageUtil.js"></script>
<script src="utils/FormUtil.js"></script>
<script src="utils/RequestUtil.js"></script>
<script src="public/js/api-client.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/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">
<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-data.html">个人资料</a
><a href="profile-messages.html">消息中心</a
><a class="active" href="profile-data-reminders.html">资料提醒</a>
</nav>
<div class="nav-actions">
<a href="profile-messages.html">返回消息</a
><a class="btn primary magnetic" href="profile-data.html">完善资料</a>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Data Reminders</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-data.html">个人资料</a
><a class="active" href="profile-data-reminders.html">资料提醒</a
><a href="profile-join-review.html">入谱审核</a>
</aside>
<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>
</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="public/js/page-effects.js"></script>
</body>
</html>
+212
View File
@@ -0,0 +1,212 @@
<!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-profile-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 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
>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Profile Data</div>
<div class="module-title-row">
<div>
<h1>个人资料</h1>
<p>
对应 APP 的个人资料、亲属、完善资料、绑定账号和成员激活流程。
</p>
</div>
<a class="btn primary magnetic" href="#profile-edit-form"
>编辑资料</a
>
</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 href="profile-family-admin.html">家族管理</a
><a href="profile-content.html">内容发布</a
><a class="active" href="profile-data.html">个人资料</a
><a href="profile-services.html">帮助与服务</a>
</aside>
<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>
<div class="editor-two">
<div class="editor-field">
<label for="profileNickName">昵称</label>
<input
id="profileNickName"
name="nickName"
type="text"
autocomplete="nickname"
placeholder="请输入昵称"
/>
</div>
<div class="editor-field">
<label for="profileAvatarOssId">头像 OSS ID</label>
<input
id="profileAvatarOssId"
name="avatarOssId"
type="number"
min="1"
step="1"
placeholder="上传后填写 OSS ID"
/>
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="profileSex">性别</label>
<select id="profileSex" name="sex">
<option value="">请选择</option>
<option value="0"></option>
<option value="1"></option>
</select>
</div>
<div class="editor-field">
<label for="profileBirthday">出生日期</label>
<input id="profileBirthday" name="birthday" type="date" />
</div>
</div>
<div class="bottom-actions profile-form-actions">
<button class="btn primary magnetic" type="submit">保存资料</button>
<span class="form-status" data-profile-status></span>
</div>
</form>
</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 data-profile-text="displayName">鹿野 Loyel</b
><a class="link-btn" href="profile-data.html">编辑</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
>
</p>
</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 data-profile-text="birthday">待选择</b
><a class="link-btn" href="profile-data.html">选择</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>
</p>
</div>
</section>
<section class="module-panel">
<h2>现居地区</h2>
<!-- 地区资料表单:字段对应 ProfileUpdateBody 的省市区编码。 -->
<form class="editor-form region-profile-form" data-region-profile-form>
<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>
</div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">保存地区</button>
</div>
</form>
</section>
<section class="module-panel">
<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>
</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/FormUtil.js"></script>
<script src="utils/RequestUtil.js"></script>
<script src="public/js/api-client.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/page-effects.js"></script>
</body>
</html>
+110
View File
@@ -0,0 +1,110 @@
<!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">
<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>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Personal Center</div>
<div class="module-title-row">
<div>
<h1>我的家谱</h1>
<p>
集中管理你创建或加入的家谱,进入家谱主页后可继续处理世系图、相册、家族圈和成员协作。
</p>
</div>
<a class="btn primary magnetic" href="profile-create-family.html"
>创建家谱</a
>
</div>
</div>
</section>
<section class="section">
<div class="container module-layout">
<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-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>
<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>
</section>
<section class="module-panel">
<h2>快捷操作</h2>
<div class="module-grid two">
<a
class="module-card tilt-card"
href="profile-create-family.html"
><span class="icon"></span>
<h3>立即创建我的家谱</h3>
<p>创建新家谱,设置姓氏、谱名、堂号和访问权限。</p></a
>
<a class="module-card tilt-card" href="profile-join-family.html"
><span class="icon"></span>
<h3>加入家谱</h3>
<p>输入邀请码或由管理员邀请,加入已有家谱协作。</p></a
>
</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/api-client.js"></script>
<script src="public/js/genealogy-pages.js"></script>
<script src="public/js/profile-common.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+119
View File
@@ -0,0 +1,119 @@
<!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-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>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Family Admin</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>
</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>
<div class="module-main">
<section class="module-panel">
<h2>管理入口</h2>
<p data-member-overview>家谱概览加载中...</p>
<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
>
</div>
</section>
<section class="module-panel">
<h2>当前管理员</h2>
<!-- 成员列表由 /members 接口渲染,权限按钮会提交 /members/{memberId} -->
<div class="module-list" data-member-list>
<div class="api-empty">成员加载中...</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="public/js/api-client.js"></script>
<script src="public/js/member-admin-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+114
View File
@@ -0,0 +1,114 @@
<!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-family-home-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 class="active" href="profile-families.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>
</nav>
<div class="nav-actions">
<a href="profile.html">返回中心</a
><a class="btn primary magnetic" href="profile-feed.html">发布动态</a>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Family Home</div>
<div class="module-title-row">
<div>
<h1 data-family-home-title>四川武胜汤氏族</h1>
<p>
后台家谱主页,用于进入世系、相册、家族圈、内容管理和成员协作。
</p>
</div>
<a class="btn ghost magnetic" href="profile-family-admin.html"
>管理家谱</a
>
</div>
</div>
</section>
<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 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>
<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>
<h3>邀请家人</h3>
<p>生成邀请入口,邀请亲人协作。</p></a
>
</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>
</section>
<section class="module-panel">
<h2>常用入口</h2>
<div class="module-grid">
<a class="module-card tilt-card" href="profile-article.html"
><span class="icon"></span>
<h3>谱文</h3>
<p>管理谱文、谱序和族史。</p></a
><a class="module-card tilt-card" href="profile-video.html"
><span class="icon"></span>
<h3>视频</h3>
<p>发布和编辑家族视频。</p></a
><a class="module-card tilt-card" href="profile-merit.html"
><span class="icon"></span>
<h3>功德录</h3>
<p>记录功德人和事迹。</p></a
>
</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="public/js/api-client.js"></script>
<script src="public/js/lineage-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+113
View File
@@ -0,0 +1,113 @@
<!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-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-family-home.html">家谱主页</a
><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>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Feed Editor</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">发布动态</a
><a href="profile-album.html">相册</a
><a href="profile-video.html">视频</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>动态内容</h2>
<form id="feed-edit-form" class="editor-form layui-form" data-feed-form>
<div class="editor-field">
<label for="feedContent">内容</label
><textarea
id="feedContent"
name="content"
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 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>
</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-field">
<label for="feedStatus">状态</label>
<select id="feedStatus" name="status">
<option value="0">发布</option>
<option value="1">草稿</option>
</select>
</div>
</div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">发布</button
><a class="btn ghost magnetic" href="profile-feed.html"
>取消</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/ke/kindeditor.min.js"></script>
<script src="public/js/rich-editor.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/RequestUtil.js"></script>
<script src="public/js/api-client.js"></script>
<script src="public/js/upload-pages.js"></script>
<script src="public/js/feed-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+108
View File
@@ -0,0 +1,108 @@
<!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-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-family-home.html">家谱主页</a
><a href="profile-content.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
>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Family Feed</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">发布动态</a
><a href="profile-album.html">相册</a
><a href="profile-video.html">视频</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>动态草稿</h2>
<div class="form-like">
<p>
<span>内容</span><b>0/200</b
><a class="link-btn" href="profile-feed-edit.html">编辑</a>
</p>
<p>
<span>图片</span><b>可添加多张</b
><a
class="link-btn"
href="#"
data-layer-msg="请选择要上传的图片"
>添加</a
>
</p>
<p>
<span>置顶</span><b></b
><a
class="link-btn"
href="#"
data-layer-select="是|否"
data-select-title="切换状态"
>切换</a
>
</p>
<p>
<span>精华</span><b></b
><a
class="link-btn"
href="#"
data-layer-select="是|否"
data-select-title="切换状态"
>切换</a
>
</p>
</div>
</section>
<section class="module-panel">
<h2>最近动态</h2>
<!-- 动态列表由 /feeds/page 接口渲染,点赞和评论走对应子接口 -->
<div class="module-list" data-feed-list>
<div class="api-empty">动态加载中...</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="public/js/api-client.js"></script>
<script src="public/js/feed-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">
<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-services.html">帮助与服务</a
><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>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Feedback</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 href="profile-share.html">应用分享</a
><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>
<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>
</div>
<div class="editor-field">
<label for="contactInfo">联系方式</label>
<input id="contactInfo" name="contactInfo" type="text" placeholder="手机号 / 邮箱" />
</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>
</div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">提交反馈</button>
</div>
</form>
</section>
<section class="module-panel">
<h2>历史反馈</h2>
<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>
</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/api-client.js"></script>
<script src="public/js/feedback-pages.js"></script>
<script src="public/js/profile-common.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+153
View File
@@ -0,0 +1,153 @@
<!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">
<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-families.html">我的家谱</a
><a class="active" href="profile-family-admin.html">家族管理</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
>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Generation</div>
<div class="module-title-row">
<div>
<h1>字辈谱</h1>
<p>管理世代数字辈,查看各代人数,并维护同字辈多个用字。</p>
</div>
<button
class="btn primary magnetic"
type="button"
data-generation-add
>新增字辈</button>
</div>
</div>
</section>
<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>
</aside>
<div class="module-main">
<section class="module-panel generation-batch-panel">
<h2>批量字辈</h2>
<!-- 批量字辈表单:字段对应 GenerationPoemBatchBody。 -->
<form class="editor-form generation-batch-form" data-generation-batch-form>
<div class="editor-field">
<label for="generationBatchContent">字辈内容</label>
<textarea
id="generationBatchContent"
name="content"
rows="4"
placeholder="例如:德承家亦"
></textarea>
</div>
<label class="check-row generation-batch-check" for="generationStopMissing">
<input
id="generationStopMissing"
name="stopMissingOldGeneration"
type="checkbox"
/>
<span>遇到缺失旧世代时停止导入</span>
</label>
<div class="bottom-actions generation-batch-actions">
<button
class="btn ghost"
type="button"
data-generation-batch-action="preview"
>批量预览</button>
<button
class="btn primary magnetic"
type="button"
data-generation-batch-action="save"
>批量保存</button>
<span class="form-status" data-generation-batch-status></span>
</div>
</form>
<div class="module-list generation-batch-preview" data-generation-batch-preview>
<div class="api-empty">批量预览结果将在这里显示</div>
</div>
</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>
</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="public/js/api-client.js"></script>
<script src="public/js/generation-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+129
View File
@@ -0,0 +1,129 @@
<!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-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-gift.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>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Gift Editor</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-merit.html">功德录</a
><a class="active" href="profile-gift.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>
<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>
</div>
<div class="editor-field">
<label for="giftDate">举办时间</label
><input
id="giftDate"
name="ceremonyTime"
type="text"
placeholder="请选择时间"
/>
</div>
</div>
<div class="editor-field">
<label for="giftTitle">贺礼标题</label
><input id="giftTitle" name="ceremonyTitle" type="text" placeholder="请输入标题" required />
</div>
<div class="editor-field">
<label for="giftAddress">举办地点</label
><input
id="giftAddress"
name="location"
type="text"
placeholder="请输入地点"
/>
</div>
<div class="editor-field">
<label for="giftContent">贺礼内容</label
><textarea
id="giftContent"
name="ceremonyDesc"
class="js-rich-editor"
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 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>
</div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">确定创建</button
><a class="btn ghost magnetic" href="profile-gift.html"
>取消</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/ke/kindeditor.min.js"></script>
<script src="public/js/rich-editor.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/RequestUtil.js"></script>
<script src="public/js/api-client.js"></script>
<script src="public/js/upload-pages.js"></script>
<script src="public/js/ceremony-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+98
View File
@@ -0,0 +1,98 @@
<!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-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-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
>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Invitation</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-merit.html">功德录</a
><a class="active" href="profile-gift.html">贺礼邀请</a
><a href="profile-feed.html">发布动态</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>邀请列表</h2>
<!-- 贺礼/祭祀列表由 /ceremonies 接口渲染 -->
<div class="module-list" data-ceremony-list>
<div class="api-empty">贺礼邀请加载中...</div>
</div>
</section>
<section class="module-panel">
<h2>献礼记录</h2>
<!-- 选择贺礼后由 /ceremonies/{ceremonyId}/gifts 接口渲染 -->
<div class="module-list" data-ceremony-gift-list>
<div class="api-empty">请选择贺礼查看献礼记录</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>
</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="public/js/api-client.js"></script>
<script src="public/js/ceremony-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+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-growth-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-growth.html">成长日志</a>
</nav>
<div class="nav-actions">
<a href="profile-growth.html">返回日志</a
><button class="btn primary magnetic" type="submit" form="growth-edit-form">保存</button>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Growth Editor</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-feed.html">发布动态</a
><a class="active" href="profile-growth.html">成长日志</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>
<div class="editor-two">
<div class="editor-field">
<label for="growthPersonId">世系人物 ID</label
><input
id="growthPersonId"
name="lineagePersonId"
type="text"
placeholder="请输入世系人物 ID"
/>
</div>
<div class="editor-field">
<label for="growthDate">日期</label
><input
id="growthDate"
name="recordDate"
type="text"
placeholder="请选择日期"
/>
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="growthTitle">标题</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>
</div>
</div>
<div class="editor-field">
<label for="growthContent">内容</label
><textarea
id="growthContent"
name="recordContent"
class="js-rich-editor"
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>
</div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">保存</button
><a class="btn ghost magnetic" href="profile-growth.html"
>取消</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/ke/kindeditor.min.js"></script>
<script src="public/js/rich-editor.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/RequestUtil.js"></script>
<script src="public/js/api-client.js"></script>
<script src="public/js/upload-pages.js"></script>
<script src="public/js/growth-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+86
View File
@@ -0,0 +1,86 @@
<!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-growth-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-growth.html">成长日志</a>
</nav>
<div class="nav-actions">
<a href="profile-content.html">返回发布中心</a
><a class="btn primary magnetic" href="profile-growth-edit.html"
>新建日志</a
>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Growth</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-feed.html">发布动态</a
><a class="active" href="profile-growth.html">成长日志</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>
</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>
</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="public/js/api-client.js"></script>
<script src="public/js/growth-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+74
View File
@@ -0,0 +1,74 @@
<!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-family-invite-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-families.html">我的家谱</a
><a class="active" href="profile-family-admin.html">家族管理</a>
</nav>
<div class="nav-actions">
<a href="profile-family-admin.html">返回管理</a>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Invite</div>
<div class="module-title-row">
<div>
<h1>邀请家人</h1>
<p>生成家谱邀请链接或邀请码,引导亲人加入家谱并激活账号。</p>
</div>
<a class="btn primary magnetic" href="join-genealogy.html">打开加入页</a>
</div>
</div>
</section>
<section class="section">
<div class="container module-layout">
<aside class="module-nav">
<a href="profile-family-admin.html">家族管理</a
><a class="active" href="profile-invite.html">邀请家人</a
><a href="profile-messages.html">邀请消息</a>
</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>
</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="public/js/api-client.js"></script>
<script src="public/js/member-admin-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+77
View File
@@ -0,0 +1,77 @@
<!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">
<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 class="active" href="profile-families.html">我的家谱</a
><a href="profile-messages.html">消息中心</a>
</nav>
<div class="nav-actions">
<a href="profile-families.html">返回我的家谱</a>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Join Family</div>
<div class="module-title-row">
<div>
<h1>加入家谱</h1>
<p>
输入亲人分享的邀请码,提交后进入个人中心消息流程等待管理员审核。
</p>
</div>
<a
class="btn primary magnetic"
href="join-genealogy.html"
>提交新申请</a
>
</div>
</div>
</section>
<section class="section">
<div class="container module-layout">
<aside class="module-nav">
<a href="profile-families.html">我的家谱</a
><a href="profile-create-family.html">创建家谱</a
><a class="active" href="profile-join-family.html">加入家谱</a>
</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>
</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="public/js/api-client.js"></script>
<script src="public/js/join-apply-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+106
View File
@@ -0,0 +1,106 @@
<!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">
<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-messages.html">消息中心</a
><a href="profile-family-admin.html">家族管理</a
><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
>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Review</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-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>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>待审核申请</h2>
<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>
</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>
</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="public/js/api-client.js"></script>
<script src="public/js/join-apply-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+118
View File
@@ -0,0 +1,118 @@
<!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-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>
</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>
</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>
<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" />
</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>
</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/ke/kindeditor.min.js"></script>
<script src="public/js/rich-editor.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/RequestUtil.js"></script>
<script src="public/js/api-client.js"></script>
<script src="public/js/upload-pages.js"></script>
<script src="public/js/memo-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+91
View File
@@ -0,0 +1,91 @@
<!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-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>
</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>
</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>
<div class="module-main">
<section class="module-panel">
<h2>备忘录</h2>
<!-- 备忘录列表由 /memos 接口渲染 -->
<div class="module-list" data-memo-list>
<div class="api-empty">备忘录加载中...</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>
</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="public/js/api-client.js"></script>
<script src="public/js/memo-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+123
View File
@@ -0,0 +1,123 @@
<!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-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
>
<nav class="nav-links">
<a href="profile-content.html">内容发布</a
><a class="active" href="profile-merit.html">功德录</a>
</nav>
<div class="nav-actions">
<a href="profile-merit.html">返回功德录</a
><button class="btn primary magnetic" type="submit" form="merit-edit-form">保存</button>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Merit Editor</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-merit.html">功德录</a
><a href="profile-gift.html">贺礼邀请</a
><a href="profile-growth.html">成长日志</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>
<div class="editor-two">
<div class="editor-field">
<label for="meritName">姓名</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="请选择日期"
/>
</div>
</div>
<div class="editor-field">
<label for="meritTitle">功德标题</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>
</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="other">其他</option>
</select>
</div>
<div class="editor-field">
<label for="meritAmount">金额</label>
<input id="meritAmount" name="amount" type="number" step="0.01" placeholder="请输入金额" />
</div>
</div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">保存</button
><a class="btn ghost magnetic" href="profile-merit.html"
>取消</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/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/api-client.js"></script>
<script src="public/js/ceremony-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+92
View File
@@ -0,0 +1,92 @@
<!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-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
>
<nav class="nav-links">
<a href="profile-content.html">内容发布</a
><a class="active" href="profile-merit.html">功德录</a>
</nav>
<div class="nav-actions">
<a href="profile-content.html">返回发布中心</a
><a class="btn primary magnetic" href="profile-merit-edit.html"
>添加功德人</a
>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Merit</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-merit.html">功德录</a
><a href="profile-gift.html">贺礼邀请</a
><a href="profile-growth.html">成长日志</a
><a href="profile-memo.html">备忘录</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>
</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>
</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="public/js/api-client.js"></script>
<script src="public/js/ceremony-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+117
View File
@@ -0,0 +1,117 @@
<!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">
<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 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
>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Messages</div>
<div class="module-title-row">
<div>
<h1>消息中心</h1>
<p>
处理入谱申请、贺礼邀请、资料提醒和系统通知,减少家族协作中的遗漏。
</p>
</div>
<a class="btn ghost magnetic" href="profile.html">返回个人中心</a>
</div>
</div>
</section>
<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>
</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>
<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
>
</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>
</button>
<button class="module-card module-action-card" type="button" data-notification-refresh>
<span class="icon"></span>
<h3>刷新消息</h3>
<p>同步最新入谱申请、评论和内容通知。</p>
</button>
</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="public/js/api-client.js"></script>
<script src="public/js/notification-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+159
View File
@@ -0,0 +1,159 @@
<!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-security-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-services.html">帮助与服务</a
><a class="active" href="profile-security.html">安全设置</a>
</nav>
<div class="nav-actions">
<a href="profile-services.html">返回服务中心</a
><a class="btn primary magnetic" href="#" data-security-logout>退出登录</a>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Security</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 href="profile-data.html">个人资料</a
><a class="active" href="profile-security.html">安全设置</a
><a href="profile-feedback.html">意见反馈</a>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>账号安全</h2>
<!-- 修改密码表单:字段对应 PasswordChangeBody。 -->
<form class="editor-form security-form" data-security-form="password">
<div class="editor-two">
<div class="editor-field">
<label for="oldPassword">当前密码</label>
<input id="oldPassword" name="oldPassword" type="password" autocomplete="current-password" required />
</div>
<div class="editor-field">
<label for="newPassword">新密码</label>
<input id="newPassword" name="newPassword" type="password" autocomplete="new-password" required />
</div>
</div>
<div class="editor-field">
<label for="confirmPassword">确认新密码</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>
</div>
</form>
</section>
<section class="module-panel">
<h2>换绑手机号</h2>
<!-- 换绑手机号表单:字段对应 PhoneChangeBody。 -->
<form class="editor-form security-form" data-security-form="phone">
<div class="editor-two">
<div class="editor-field">
<label for="newPhone">新手机号</label>
<input id="newPhone" name="newPhone" type="tel" required />
</div>
<div class="editor-field">
<label for="phoneSmsCode">短信验证码</label>
<input id="phoneSmsCode" name="smsCode" type="text" required />
</div>
</div>
<div class="editor-field">
<label for="phoneValidToken">滑动验证票据</label>
<input id="phoneValidToken" 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>
</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>
</div>
</form>
</section>
<section class="module-panel security-danger">
<h2>注销账号</h2>
<!-- 注销账号属于敏感操作,必须填写当前密码并输入确认文本。 -->
<form class="editor-form security-form" data-security-form="deactivate">
<div class="editor-field">
<label for="deactivatePassword">当前密码</label>
<input id="deactivatePassword" name="password" type="password" autocomplete="current-password" required />
</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>
<input id="confirmText" name="confirmText" type="text" placeholder="请输入:确认注销" required />
</div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">提交注销</button>
<button class="btn ghost magnetic" type="button" data-security-logout>退出登录</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/md5.js"></script>
<script src="config.js"></script>
<script src="utils/StorageUtil.js"></script>
<script src="utils/FormUtil.js"></script>
<script src="utils/RequestUtil.js"></script>
<script src="public/js/api-client.js"></script>
<script src="public/js/security-pages.js"></script>
<script src="public/js/profile-common.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+167
View File
@@ -0,0 +1,167 @@
<!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-vip-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 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 class="active" 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>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Services</div>
<div class="module-title-row">
<div>
<h1>帮助与服务</h1>
<p>
整合 APP
“我的”页中的应用推广、意见反馈、安全设置、帮助中心和应用分享。
</p>
</div>
<a class="btn primary magnetic" href="profile-feedback.html"
>提交反馈</a
>
</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 href="profile-family-admin.html">家族管理</a
><a href="profile-content.html">内容发布</a
><a href="profile-data.html">个人资料</a
><a class="active" href="profile-services.html">帮助与服务</a>
</aside>
<div class="module-main">
<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"
><span class="icon"></span>
<h3>意见反馈</h3>
<p>提交产品问题、资料异常和改进建议。</p></a
>
<a class="module-card tilt-card" href="profile-security.html"
><span class="icon"></span>
<h3>安全设置</h3>
<p>修改密码、找回账号、维护登录安全。</p></a
>
<a class="module-card tilt-card" href="help.html"
><span class="icon"></span>
<h3>帮助中心</h3>
<p>查看新手常见问题、家谱优势和编辑字辈说明。</p></a
>
<a class="module-card tilt-card" href="app.html"
><span class="icon"></span>
<h3>应用下载</h3>
<p>安装移动端,继续处理图片、视频和成员资料。</p></a
>
<a class="module-card tilt-card" href="about.html"
><span class="icon"></span>
<h3>联系我们</h3>
<p>查看平台介绍、客服信息和服务说明。</p></a
>
</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>
</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>
<div class="editor-field">
<label for="payType">支付方式</label>
<select id="payType" name="payType">
<option value="wechat">微信支付</option>
<option value="alipay">支付宝</option>
<option value="balance">余额支付</option>
</select>
</div>
<div class="bottom-actions">
<button class="btn primary magnetic" type="submit">创建会员订单</button>
<span class="api-status" data-vip-order-status>等待选择套餐</span>
</div>
</form>
</section>
<!-- 会员订单区域:展示 GET /genealogy/app/vip/orders 的历史记录。 -->
<section class="module-panel">
<h2>会员订单</h2>
<div class="module-list" data-vip-order-list>
<div class="api-empty">会员订单加载中</div>
</div>
</section>
<section class="module-panel">
<h2>推荐阅读</h2>
<div class="module-list">
<a class="module-row" href="help.html"
><div>
<h3>新手常见问题</h3>
<p>注册登录、创建家谱、邀请成员、资料审核等流程。</p>
</div>
<span class="pill">查看</span></a
><a class="module-row" href="profile-article.html"
><div>
<h3>为什么家谱不只是一本名册</h3>
<p>理解关系、故事和协作维护。</p>
</div>
<span class="pill">阅读</span></a
>
</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/api-client.js"></script>
<script src="public/js/vip-pages.js"></script>
<script src="public/js/profile-common.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+89
View File
@@ -0,0 +1,89 @@
<!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-family-share-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-services.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
>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Share</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-share.html">应用分享</a
><a href="profile-feedback.html">意见反馈</a
><a href="help.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-member-overview>分享概览加载中...</p>
</div>
<div class="module-card">
<span class="icon"></span>
<h3>分享二维码</h3>
<p data-invite-link>分享链接加载中...</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>
</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="public/js/api-client.js"></script>
<script src="public/js/member-admin-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+169
View File
@@ -0,0 +1,169 @@
<!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-lineage-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-families.html">我的家谱</a
><a class="active" href="profile-family-admin.html">家族管理</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>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Tree Mode</div>
<div class="module-title-row">
<div>
<h1>世系图</h1>
<p>
个人中心内的树谱管理视图,用于查看成员关系、添加亲属和搜索成员。
</p>
</div>
<a class="btn ghost magnetic" href="profile-family-admin.html"
>管理世代</a
>
</div>
</div>
</section>
<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>
</aside>
<div class="module-main">
<section class="module-panel">
<h2>树谱操作</h2>
<div class="module-grid">
<button class="module-card module-action-card tilt-card" type="button" data-lineage-relation="parents"
><span class="icon"></span>
<h3>添加父亲 / 母亲</h3>
<p>先选择成员,再为当前成员补充上一代关系。</p></button
><button class="module-card module-action-card tilt-card" type="button" data-lineage-relation="spouses"
><span class="icon"></span>
<h3>添加配偶</h3>
<p>先选择成员,再维护配偶关系。</p></button
><button
class="module-card module-action-card tilt-card"
type="button"
data-lineage-relation="children"
><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>
</section>
<section class="module-panel">
<h2>成员搜索</h2>
<div class="lineage-toolbar">
<input type="search" data-lineage-keyword placeholder="输入姓名或字辈搜索" />
<button class="btn primary" type="button" data-lineage-search>搜索</button>
</div>
<!-- 成员列表由世系人物分页接口渲染 -->
<div class="module-list" data-lineage-list>
<div class="api-empty">成员加载中...</div>
</div>
</section>
<section class="module-panel">
<h2>成员详情</h2>
<!-- 点击成员列表或世系树节点后渲染详情 -->
<div data-lineage-detail>
<div class="api-empty">请选择一个成员查看资料</div>
</div>
</section>
<section class="module-panel">
<h2>新增成员</h2>
<form class="editor-form" data-lineage-form>
<!-- 字段名称对应 APP.openapi.json 的 LineagePersonBody -->
<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 />
</div>
<div class="editor-field">
<label for="lineage-sex">性别</label>
<select id="lineage-sex" name="sex">
<option value="0"></option>
<option value="1"></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" />
</div>
<div class="editor-field">
<label for="lineage-generation-name">字辈</label>
<input id="lineage-generation-name" name="generationName" type="text" placeholder="例如 忠" />
</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="可选" />
</div>
<div class="editor-field">
<label for="lineage-alias-name">别名</label>
<input id="lineage-alias-name" name="aliasName" type="text" placeholder="可选" />
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="lineage-birth-date">出生日期</label>
<input id="lineage-birth-date" name="birthDate" type="date" />
</div>
<div class="editor-field">
<label for="lineage-death-date">逝世日期</label>
<input id="lineage-death-date" name="deathDate" type="date" />
</div>
</div>
<div class="editor-field">
<label for="lineage-introduction">简介</label>
<textarea id="lineage-introduction" name="introduction" placeholder="记录成员生平简介"></textarea>
</div>
<div class="bottom-actions">
<button class="btn primary" type="submit">保存成员</button>
<button class="btn ghost" type="reset">清空</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="public/js/api-client.js"></script>
<script src="public/js/lineage-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+96
View File
@@ -0,0 +1,96 @@
<!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">
<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 href="profile-album.html">相册</a
><a class="active" href="profile-video.html">视频</a>
</nav>
<div class="nav-actions">
<a href="profile-content.html">返回发布中心</a
><a class="btn primary magnetic" href="profile-video.html"
>发布视频</a
>
</div>
</div>
</header>
<main>
<section class="module-hero">
<div class="container">
<div class="module-kicker">Video</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-album.html">相册</a
><a class="active" href="profile-video.html">视频</a
><a href="profile-family-home.html">家谱主页</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>
</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>
</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="public/js/page-effects.js"></script>
</body>
</html>
+374
View File
@@ -0,0 +1,374 @@
<!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.css" />
</head>
<body class="page-profile" data-profile-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 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
>
</div>
</div>
</header>
<main>
<section class="profile-hero">
<div class="container profile-hero-grid">
<div class="profile-card tilt-card">
<div class="avatar-wrap">
<div class="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="profile-tags">
<span>血亲</span><span>第二代 顺字辈</span
><span>已绑定账号</span>
</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>
</div>
</section>
<section class="section dashboard-section">
<div class="container dashboard-layout">
<aside class="dashboard-side">
<div class="side-card user-actions tilt-card">
<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>
</div>
</aside>
<div class="dashboard-main">
<section class="panel my-family-panel" id="families">
<div class="panel-head">
<div>
<h2>我的家谱</h2>
<p>
对应 APP
首页和家谱主页,进入后可查看家谱主页、世系图、相册和家族圈。
</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>
<div>
<h3>四川武胜汤氏族</h3>
<p>
共修入 7 人,激活 7 人。你是创建者,可管理内容和成员权限。
</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>
</div>
</div>
</a>
</div>
</section>
<section class="panel" id="management">
<div class="panel-head compact">
<div>
<h2>家族管理</h2>
<p>
把 APP 里的世代、管理员、权限、入谱审核集中到一个管理区。
</p>
</div>
<a class="link-btn" href="profile-family-admin.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>
</section>
<section class="panel" id="content">
<div class="panel-head compact">
<div>
<h2>内容发布</h2>
<p>
对应 APP
家谱主页中的谱文、视频、相册、功德录、贺礼邀请、成长日志等内容入口。
</p>
</div>
<a class="link-btn" href="profile-content.html">发布中心</a>
</div>
<div class="content-grid">
<a
class="content-tile large tilt-card"
href="profile-article.html"
><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>
</section>
</div>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-grid">
<div>
<a class="brand" href="profile.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>
</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>
</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>
</div>
</div>
</footer>
<div class="logout-modal" id="logoutModal" aria-hidden="true">
<div class="logout-modal-backdrop" data-logout-close></div>
<div
class="logout-dialog"
role="dialog"
aria-modal="true"
aria-labelledby="logoutTitle"
>
<button
class="logout-close"
type="button"
data-logout-close
aria-label="关闭"
>
×
</button>
<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>
</div>
</div>
</div>
<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/FormUtil.js"></script>
<script src="utils/RequestUtil.js"></script>
<script src="public/js/api-client.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>
+100
View File
@@ -0,0 +1,100 @@
<!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/promo-album.css" />
</head>
<body class="page-promo-album" data-promo-album-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 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>
<section class="inner-hero promo-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Public Album</div>
<h1>用公开相册展示家族故事</h1>
<p class="lead">
宣传相册用于前台展示祖屋、祠堂、家族活动、老照片和公开纪念内容,帮助访客更直观理解家族文化。
</p>
<div class="promo-tags">
<span>祖屋影像</span><span>活动纪念</span><span>公开展示</span>
</div>
</div>
<div class="hero-card album-preview tilt-card">
<div class="album-photo large"></div>
<div class="album-photo"></div>
<div class="album-photo warm"></div>
<div class="album-photo green"></div>
</div>
</div>
</section>
<section class="section alt">
<div class="container">
<div class="section-head">
<h2>相册分类</h2>
<p>把适合公开展示的影像整理成专题,让家族主页更有内容层次。</p>
</div>
<div class="grid-3">
<!-- 宣传相册复用 /albums 列表接口渲染公开展示内容 -->
<div data-promo-album-list>
<div class="api-empty">相册加载中...</div>
</div>
</div>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-grid">
<div>
<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>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
</div>
<div>
<h4>文化内容</h4>
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a><a>平台公告</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>
</div>
</div>
</footer>
<script src="public/js/api-client.js"></script>
<script src="public/js/album-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+105
View File
@@ -0,0 +1,105 @@
<!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/promo-video.css" />
</head>
<body class="page-promo-video">
<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 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>
<section class="inner-hero promo-hero">
<div class="container">
<div class="hero-copy">
<div class="eyebrow">Public Video</div>
<h1>用宣传视频讲述家族文化</h1>
<p class="lead">
宣传视频用于前台展示家族活动、祠堂风貌、修谱故事和公开纪念内容,让访问者更快进入家族叙事。
</p>
<div class="promo-tags">
<span>家族介绍</span><span>活动纪念</span><span>修谱故事</span>
</div>
</div>
<div class="hero-card video-preview tilt-card">
<div class="play-button"></div>
<h3>四川成都陈氏族宣传片</h3>
<p>祠堂、谱文、人物与家风的公开展示。</p>
</div>
</div>
</section>
<section class="section alt">
<div class="container">
<div class="section-head">
<h2>视频专题</h2>
<p>把适合公开传播的视频整理成栏目,增强家谱主页的展示力。</p>
</div>
<div class="grid-3">
<div class="card soft video-card tilt-card">
<h3>家族介绍</h3>
<p>概述姓氏源流、家族分布和家风传承。</p>
</div>
<div class="card soft video-card tilt-card">
<h3>活动纪念</h3>
<p>展示祭祖、聚会、修谱仪式等重要时刻。</p>
</div>
<div class="card soft video-card tilt-card">
<h3>人物故事</h3>
<p>以影像记录家族人物、贡献与纪念内容。</p>
</div>
</div>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-grid">
<div>
<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>数字家谱</a><a>家谱广场</a><a>应用下载</a><a>帮助中心</a>
</div>
<div>
<h4>文化内容</h4>
<a>家族文化</a><a>姓氏百科</a><a>家谱知识</a><a>平台公告</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>
</div>
</div>
</footer>
<script src="public/js/page-effects.js"></script>
</body>
</html>
+187
View File
@@ -0,0 +1,187 @@
.about-hero .container {
min-height: 440px;
padding-top: 74px;
padding-bottom: 74px;
}
.hero-copy {
position: relative;
padding-left: 28px;
}
.hero-copy:before {
content: "";
position: absolute;
left: 0;
top: 58px;
width: 4px;
height: 132px;
border-radius: 999px;
background: linear-gradient(180deg, var(--red), var(--gold));
}
.about-tags {
display: flex;
gap: 12px;
margin-top: 28px;
flex-wrap: wrap;
}
.about-tags span {
padding: 10px 14px;
border: 1px solid rgba(224, 211, 189, .78);
border-radius: 999px;
color: #6d766f;
background: rgba(255,253,248,.74);
box-shadow: 0 14px 32px rgba(72, 57, 38, .06);
font-weight: 800;
transition: transform .22s ease, color .22s ease, background .22s ease;
}
.about-tags span:hover {
color: #fff;
background: var(--green);
transform: translateY(-3px);
}
.about-seal {
position: relative;
min-height: 260px;
overflow: hidden;
display: grid;
place-items: center;
text-align: center;
color: #fff;
background: linear-gradient(155deg, var(--green-dark), var(--green));
}
.about-seal:before {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(rgba(255,255,255,.08) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,.08) 1px, transparent 1px);
background-size: 34px 34px;
opacity: .34;
}
.seal-orb {
position: absolute;
right: 76px;
top: 40px;
width: 110px;
height: 110px;
border-radius: 50%;
background: rgba(255,255,255,.16);
box-shadow: 0 0 40px rgba(255,255,255,.1);
animation: sealPulse 4s ease-in-out infinite;
}
.about-seal span {
position: relative;
z-index: 2;
width: 96px;
height: 96px;
margin-bottom: 20px;
border-radius: 50%;
display: grid;
place-items: center;
color: var(--red);
background: rgba(255,255,255,.94);
font-family: "SimSun", "Songti SC", serif;
font-size: 48px;
font-weight: 800;
transition: transform .24s ease, background .24s ease;
}
.about-seal:hover span {
transform: rotate(-8deg) scale(1.08);
}
.about-seal p {
position: relative;
z-index: 2;
margin: 0;
color: rgba(255,255,255,.84);
font-size: 22px;
}
.values-section {
position: relative;
overflow: hidden;
}
.values-section:before {
content: "";
position: absolute;
left: 50%;
top: 58px;
width: 520px;
height: 180px;
border-radius: 50%;
background: radial-gradient(circle, rgba(196, 146, 69, .09), transparent 68%);
transform: translateX(-50%);
}
.value-card {
min-height: 190px;
position: relative;
overflow: hidden;
}
.value-card:after {
content: "";
position: absolute;
left: -42%;
top: 0;
width: 36%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,.54), transparent);
transform: skewX(-18deg);
transition: left .56s ease;
}
.value-card:hover:after {
left: 112%;
}
.value-icon {
width: 48px;
height: 48px;
margin-bottom: 18px;
border-radius: 14px;
display: grid;
place-items: center;
color: var(--red);
background: #f7e4dc;
font-family: "SimSun", "Songti SC", serif;
font-size: 22px;
font-weight: 900;
transition: transform .26s ease, background .26s ease, color .26s ease;
}
.value-card:hover .value-icon {
color: #fff;
background: var(--red);
transform: rotate(-6deg) scale(1.08);
}
.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;
}
.tilt-card:hover {
--lift: -6px;
}
@keyframes sealPulse {
0%, 100% { opacity: .55; transform: scale(.92); }
50% { opacity: .9; transform: scale(1.08); }
}
@media (max-width: 720px) {
.hero-copy { padding-left: 0; }
.hero-copy:before { display: none; }
}
+258
View File
@@ -0,0 +1,258 @@
.app-hero .container {
min-height: 500px;
padding-top: 74px;
padding-bottom: 74px;
}
.hero-copy {
position: relative;
padding-left: 28px;
}
.hero-copy:before {
content: "";
position: absolute;
left: 0;
top: 58px;
width: 4px;
height: 132px;
border-radius: 999px;
background: linear-gradient(180deg, var(--red), var(--gold));
}
.hero-buttons {
display: flex;
gap: 16px;
flex-wrap: wrap;
margin-top: 28px;
}
.app-stats {
display: flex;
gap: 12px;
margin-top: 24px;
flex-wrap: wrap;
}
.app-stats span {
padding: 10px 14px;
border: 1px solid rgba(224, 211, 189, .78);
border-radius: 999px;
color: #6d766f;
background: rgba(255,253,248,.74);
box-shadow: 0 14px 32px rgba(72, 57, 38, .06);
font-weight: 800;
transition: transform .22s ease, color .22s ease, background .22s ease;
}
.app-stats span:hover {
color: #fff;
background: var(--green);
transform: translateY(-3px);
}
.app-shot-wrap {
position: relative;
min-height: 390px;
display: flex;
align-items: center;
justify-content: center;
gap: 30px;
overflow: hidden;
background:
radial-gradient(circle at 18% 20%, rgba(200,59,50,.12), transparent 24%),
radial-gradient(circle at 80% 24%, rgba(71,111,99,.16), transparent 30%),
linear-gradient(135deg, #f7efe2, #edf4ef);
}
.app-shot-wrap:before {
content: "";
position: absolute;
inset: 22px;
border: 1px solid rgba(224, 211, 189, .62);
border-radius: 22px;
pointer-events: none;
}
.app-orbit {
position: absolute;
right: 64px;
top: 44px;
width: 96px;
height: 96px;
border-radius: 50%;
background: rgba(196, 146, 69, .18);
animation: appOrbit 4.2s ease-in-out infinite;
}
.phone-shot {
position: relative;
z-index: 2;
width: 185px;
height: 380px;
object-fit: cover;
object-position: top center;
border: 8px solid #252b28;
border-radius: 28px;
box-shadow: 0 26px 60px rgba(33, 28, 22, .2);
transition: transform .3s ease, box-shadow .3s ease;
}
.phone-shot.first {
animation: phoneFloatA 4s ease-in-out infinite;
}
.phone-shot.second {
transform: translateY(28px);
animation: phoneFloatB 4s ease-in-out infinite;
}
.app-shot-wrap:hover .phone-shot {
box-shadow: 0 34px 70px rgba(33, 28, 22, .26);
}
.app-feature-section {
position: relative;
overflow: hidden;
}
.app-feature-section:before {
content: "";
position: absolute;
left: 50%;
top: 58px;
width: 520px;
height: 180px;
border-radius: 50%;
background: radial-gradient(circle, rgba(196, 146, 69, .09), transparent 68%);
transform: translateX(-50%);
}
.app-feature-card {
min-height: 190px;
position: relative;
overflow: hidden;
}
.app-feature-card:after {
content: "";
position: absolute;
left: -42%;
top: 0;
width: 36%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,.54), transparent);
transform: skewX(-18deg);
transition: left .56s ease;
}
.app-feature-card:hover:after {
left: 112%;
}
.app-icon {
width: 48px;
height: 48px;
margin-bottom: 18px;
border-radius: 14px;
display: grid;
place-items: center;
color: var(--red);
background: #f7e4dc;
font-family: "SimSun", "Songti SC", serif;
font-size: 22px;
font-weight: 900;
transition: transform .26s ease, background .26s ease, color .26s ease;
}
.app-feature-card:hover .app-icon {
color: #fff;
background: var(--red);
transform: rotate(-6deg) scale(1.08);
}
.app-promotion-section {
/* 应用推广列表承载后端推广内容,不在 JS 中写样式 */
background: #fffdf8;
}
.promotion-list {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 18px;
}
.promotion-card {
overflow: hidden;
border: 1px solid var(--line);
border-radius: 8px;
background: #fffaf0;
box-shadow: 0 18px 42px rgba(57, 48, 36, .08);
transition: transform .24s ease, border-color .24s ease, box-shadow .24s ease;
}
.promotion-card:hover {
border-color: rgba(196, 146, 69, .42);
box-shadow: 0 24px 54px rgba(57, 48, 36, .12);
transform: translateY(-4px);
}
.promotion-card img {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
display: block;
background: #f3eadc;
}
.promotion-card div {
padding: 18px;
}
.promotion-card h3 {
margin-bottom: 8px;
color: var(--ink);
font-size: 22px;
}
.promotion-card p {
margin: 0 0 12px;
color: var(--muted);
line-height: 1.75;
}
.promotion-card span {
color: var(--red);
font-weight: 900;
}
.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;
}
.tilt-card:hover {
--lift: -6px;
}
@keyframes phoneFloatA {
0%, 100% { transform: translateY(0) rotate(-1deg); }
50% { transform: translateY(-8px) rotate(1deg); }
}
@keyframes phoneFloatB {
0%, 100% { transform: translateY(28px) rotate(1deg); }
50% { transform: translateY(18px) rotate(-1deg); }
}
@keyframes appOrbit {
0%, 100% { opacity: .5; transform: scale(.92); }
50% { opacity: 1; transform: scale(1.08); }
}
@media (max-width: 720px) {
.hero-copy { padding-left: 0; }
.hero-copy:before { display: none; }
.phone-shot.second { display: none; }
.promotion-list { grid-template-columns: 1fr; }
}
+153
View File
@@ -0,0 +1,153 @@
.article-hero .container {
min-height: 440px;
padding-top: 74px;
padding-bottom: 74px;
}
.hero-copy {
position: relative;
padding-left: 28px;
}
.hero-copy:before {
content: "";
position: absolute;
left: 0;
top: 58px;
width: 4px;
height: 132px;
border-radius: 999px;
background: linear-gradient(180deg, var(--red), var(--gold));
}
.article-meta {
display: flex;
gap: 12px;
margin-top: 28px;
flex-wrap: wrap;
}
.article-meta span {
padding: 10px 14px;
border: 1px solid rgba(224, 211, 189, .78);
border-radius: 999px;
color: #6d766f;
background: rgba(255,253,248,.74);
font-weight: 800;
}
.article-cover-detail {
position: relative;
overflow: hidden;
display: grid;
place-items: center;
text-align: center;
color: #fff;
background:
radial-gradient(circle at 72% 24%, rgba(255,255,255,.35) 0 9%, transparent 10%),
linear-gradient(135deg, #e7c89e, #b94135 48%, #143d57);
}
.article-cover-detail:after {
content: "";
position: absolute;
left: -40%;
top: 0;
width: 34%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,.3), transparent);
transform: skewX(-18deg);
transition: left .58s ease;
}
.article-cover-detail:hover:after { left: 112%; }
.article-cover-detail span {
width: 86px;
height: 86px;
border-radius: 50%;
display: grid;
place-items: center;
color: var(--red);
background: rgba(255,253,248,.94);
font-family: "SimSun", "Songti SC", serif;
font-size: 44px;
font-weight: 800;
}
.article-cover-detail p {
margin: 18px 0 0;
color: rgba(255,255,255,.9);
font-size: 22px;
}
.article-layout {
display: grid;
grid-template-columns: minmax(0, 820px) 300px;
gap: 28px;
align-items: start;
}
.article-content,
.article-side {
border: 1px solid var(--line);
border-radius: 18px;
background: rgba(255,253,248,.82);
box-shadow: 0 20px 54px rgba(57, 48, 36, .08);
}
.article-content {
padding: 42px;
}
.article-content p {
color: #5f665f;
font-size: 18px;
line-height: 2;
}
.article-content h2 {
margin-top: 34px;
font-size: 32px;
}
.article-side {
position: sticky;
top: 108px;
padding: 24px;
}
.article-side a {
display: block;
margin-top: 12px;
padding: 14px 16px;
border: 1px solid var(--line);
border-radius: 14px;
color: #6f4a37;
background: #fbf7ed;
transition: transform .22s ease, color .22s ease, background .22s ease;
}
.article-side a:hover {
color: #fff;
background: var(--green);
transform: translateX(5px);
}
.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;
}
.tilt-card:hover { --lift: -6px; }
@media (max-width: 980px) {
.article-layout { grid-template-columns: 1fr; }
.article-side { position: static; }
}
@media (max-width: 720px) {
.hero-copy { padding-left: 0; }
.hero-copy:before { display: none; }
.article-content { padding: 28px; }
}
+210
View File
@@ -0,0 +1,210 @@
.create-hero .container {
min-height: 440px;
padding-top: 74px;
padding-bottom: 74px;
}
.hero-copy {
position: relative;
padding-left: 28px;
}
.hero-copy:before {
content: "";
position: absolute;
left: 0;
top: 58px;
width: 4px;
height: 132px;
border-radius: 999px;
background: linear-gradient(180deg, var(--red), var(--gold));
}
.create-tags {
display: flex;
gap: 12px;
margin-top: 28px;
flex-wrap: wrap;
}
.create-tags span {
padding: 10px 14px;
border: 1px solid rgba(224, 211, 189, .78);
border-radius: 999px;
color: #6d766f;
background: rgba(255,253,248,.74);
box-shadow: 0 14px 32px rgba(72, 57, 38, .06);
font-weight: 800;
transition: transform .22s ease, color .22s ease, background .22s ease;
}
.create-tags span:hover {
color: #fff;
background: var(--green);
transform: translateY(-3px);
}
.process-card {
position: relative;
overflow: hidden;
display: grid;
align-content: center;
}
.process-card:before {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(rgba(196,146,69,.06) 1px, transparent 1px),
linear-gradient(90deg, rgba(196,146,69,.06) 1px, transparent 1px);
background-size: 34px 34px;
opacity: .72;
}
.process-card h3,
.process-card .step-list {
position: relative;
z-index: 2;
}
.process-orb {
position: absolute;
right: -70px;
top: -80px;
width: 220px;
height: 220px;
border-radius: 50%;
background: radial-gradient(circle, rgba(200, 59, 50, .13), transparent 62%);
animation: createPulse 4s ease-in-out infinite;
}
.step-list {
display: grid;
gap: 16px;
margin-top: 18px;
}
.step-list p {
display: flex;
gap: 14px;
align-items: center;
margin: 0;
color: var(--muted);
transition: transform .22s ease, color .22s ease;
}
.step-list p:hover {
color: var(--green-dark);
transform: translateX(6px);
}
.step-list b {
width: 38px;
height: 38px;
border-radius: 50%;
display: grid;
place-items: center;
color: #fff;
background: var(--green);
flex: 0 0 auto;
box-shadow: 0 12px 28px rgba(71, 111, 99, .18);
transition: transform .22s ease, background .22s ease;
}
.step-list p:hover b {
background: var(--red);
transform: scale(1.08);
}
.create-form-section {
position: relative;
overflow: hidden;
}
.create-form-section:before {
content: "";
position: absolute;
left: 50%;
top: 58px;
width: 520px;
height: 180px;
border-radius: 50%;
background: radial-gradient(circle, rgba(196, 146, 69, .09), transparent 68%);
transform: translateX(-50%);
}
.create-form {
max-width: 720px;
position: relative;
overflow: hidden;
padding: 36px;
}
.form-ornament {
position: absolute;
right: -82px;
top: -88px;
width: 220px;
height: 220px;
border-radius: 50%;
background: radial-gradient(circle, rgba(71, 111, 99, .12), transparent 66%);
}
.create-form h2,
.create-form .form-note,
.create-form .form-grid {
position: relative;
z-index: 2;
}
.form-note {
margin-bottom: 22px;
color: var(--muted);
line-height: 1.8;
}
.create-form .input,
.create-form textarea {
transition: border-color .22s ease, box-shadow .22s ease, background .22s ease, transform .22s ease;
}
.create-region-picker {
/* 创建家谱页地区选择器占满表单宽度,编码通过隐藏字段提交 */
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.create-form .input:focus,
.create-form textarea:focus {
outline: none;
border-color: rgba(200, 59, 50, .62);
background: #fffdf8;
box-shadow: 0 0 0 4px rgba(200, 59, 50, .08);
transform: translateY(-2px);
}
.submit-btn {
width: 100%;
}
.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;
}
.tilt-card:hover {
--lift: -6px;
}
@keyframes createPulse {
0%, 100% { opacity: .5; transform: scale(.92); }
50% { opacity: 1; transform: scale(1.08); }
}
@media (max-width: 720px) {
.hero-copy { padding-left: 0; }
.hero-copy:before { display: none; }
.create-region-picker { grid-template-columns: 1fr; }
}
+213
View File
@@ -0,0 +1,213 @@
.culture-hero .container {
min-height: 440px;
padding-top: 74px;
padding-bottom: 74px;
}
.hero-copy {
position: relative;
padding-left: 28px;
}
.hero-copy:before {
content: "";
position: absolute;
left: 0;
top: 58px;
width: 4px;
height: 132px;
border-radius: 999px;
background: linear-gradient(180deg, var(--red), var(--gold));
}
.culture-stats {
display: flex;
gap: 12px;
margin-top: 28px;
flex-wrap: wrap;
}
.culture-stats span {
padding: 10px 14px;
border: 1px solid rgba(224, 211, 189, .78);
border-radius: 999px;
color: #6d766f;
background: rgba(255,253,248,.74);
box-shadow: 0 14px 32px rgba(72, 57, 38, .06);
font-weight: 800;
transition: transform .22s ease, color .22s ease, background .22s ease;
}
.culture-stats span:hover {
color: #fff;
background: var(--green);
transform: translateY(-3px);
}
.culture-quote {
position: relative;
min-height: 260px;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: center;
color: #fff;
background:
linear-gradient(180deg, rgba(33,63,56,.18), rgba(33,63,56,.86)),
linear-gradient(135deg, #476f63, #c49245 50%, #c83b32);
}
.culture-quote:before {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(rgba(255,255,255,.08) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,.08) 1px, transparent 1px);
background-size: 34px 34px;
opacity: .34;
}
.quote-moon {
position: absolute;
right: 72px;
top: 34px;
width: 92px;
height: 92px;
border-radius: 50%;
background: rgba(255,255,255,.32);
box-shadow: 0 0 34px rgba(255,255,255,.12);
animation: moonFloat 4s ease-in-out infinite;
}
.culture-quote span {
position: relative;
z-index: 2;
width: 64px;
height: 64px;
margin-bottom: 24px;
border-radius: 50%;
display: grid;
place-items: center;
color: var(--red);
background: rgba(255,255,255,.94);
font-family: "SimSun", "Songti SC", serif;
font-size: 30px;
font-weight: 800;
transition: transform .24s ease, background .24s ease;
}
.culture-quote:hover span {
transform: rotate(-8deg) scale(1.08);
}
.culture-quote p {
position: relative;
z-index: 2;
margin: 0;
color: rgba(255,255,255,.86);
font-size: 22px;
line-height: 1.7;
}
.article-section {
position: relative;
overflow: hidden;
}
.article-section:before {
content: "";
position: absolute;
left: 50%;
top: 58px;
width: 520px;
height: 180px;
border-radius: 50%;
background: radial-gradient(circle, rgba(196, 146, 69, .09), transparent 68%);
transform: translateX(-50%);
}
.article-card {
position: relative;
overflow: hidden;
border-radius: 18px;
background: #fff;
border: 1px solid var(--line);
box-shadow: 0 18px 48px rgba(57, 48, 36, .07);
transition: transform .24s ease, box-shadow .24s ease, border-color .24s ease;
}
.article-card:hover {
border-color: rgba(196, 146, 69, .42);
box-shadow: 0 24px 60px rgba(57, 48, 36, .11);
}
.article-cover {
position: relative;
height: 190px;
overflow: hidden;
background:
radial-gradient(circle at 74% 28%, rgba(255,255,255,.7) 0 8%, transparent 9%),
linear-gradient(135deg, #e7c89e, #b94135 48%, #143d57);
}
.article-cover:after {
content: "";
position: absolute;
left: -42%;
top: 0;
width: 36%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,.34), transparent);
transform: skewX(-18deg);
transition: left .58s ease;
}
.article-card:hover .article-cover:after {
left: 112%;
}
.article-cover span {
position: absolute;
left: 22px;
bottom: 20px;
padding: 7px 12px;
border-radius: 999px;
color: var(--green-dark);
background: rgba(255,253,248,.86);
font-weight: 800;
font-size: 13px;
}
.article-card:nth-child(2) .article-cover {
background: linear-gradient(135deg, #dfd1b5, #6b7f70 48%, #26302c);
}
.article-card:nth-child(3) .article-cover {
background: linear-gradient(135deg, #f0d8ac, #c49245 45%, #7d342d);
}
.article-body { padding: 24px; }
.article-body h3 { transition: color .22s ease; }
.article-card:hover .article-body h3 { color: var(--red); }
.article-body p { color: var(--muted); line-height: 1.75; }
.article-body span { color: #9a8f82; font-size: 14px; }
.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;
}
.tilt-card:hover {
--lift: -6px;
}
@keyframes moonFloat {
0%, 100% { transform: translateY(0) scale(1); opacity: .72; }
50% { transform: translateY(-8px) scale(1.04); opacity: .92; }
}
@media (max-width: 720px) {
.hero-copy { padding-left: 0; }
.hero-copy:before { display: none; }
}
+86
View File
@@ -0,0 +1,86 @@
.download-hero .container {
min-height: 440px;
padding-top: 74px;
padding-bottom: 74px;
}
.hero-copy {
position: relative;
padding-left: 28px;
}
.hero-copy:before {
content: "";
position: absolute;
left: 0;
top: 58px;
width: 4px;
height: 132px;
border-radius: 999px;
background: linear-gradient(180deg, var(--red), var(--gold));
}
.detail-actions {
display: flex;
gap: 14px;
margin-top: 30px;
flex-wrap: wrap;
}
.qr-card {
display: grid;
place-items: center;
text-align: center;
}
.qr-box {
width: 170px;
height: 170px;
padding: 16px;
border-radius: 24px;
background:
linear-gradient(90deg, var(--green) 12px, transparent 12px) 0 0 / 32px 32px,
linear-gradient(var(--green) 12px, transparent 12px) 0 0 / 32px 32px,
#fffdf8;
border: 1px solid var(--line);
display: grid;
place-items: center;
box-shadow: inset 0 0 0 12px #fffdf8;
}
.qr-box span {
width: 62px;
height: 62px;
border-radius: 50%;
display: grid;
place-items: center;
color: #fff;
background: var(--red);
font-weight: 900;
}
.qr-card p,
.download-card p {
color: var(--muted);
line-height: 1.8;
}
.download-card {
transition: transform .24s ease, border-color .24s ease, box-shadow .24s ease;
}
.download-card:hover {
border-color: rgba(196, 146, 69, .42);
}
.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;
}
.tilt-card:hover { --lift: -6px; }
@media (max-width: 720px) {
.hero-copy { padding-left: 0; }
.hero-copy:before { display: none; }
}
+160
View File
@@ -0,0 +1,160 @@
.page-family-detail [data-family-detail].is-loading {
/* 详情接口加载时只改变透明度,具体状态由公共 is-loading 类控制 */
opacity: .82;
}
.detail-hero .container {
min-height: 440px;
padding-top: 74px;
padding-bottom: 74px;
}
.hero-copy {
position: relative;
padding-left: 28px;
}
.hero-copy:before {
content: "";
position: absolute;
left: 0;
top: 58px;
width: 4px;
height: 132px;
border-radius: 999px;
background: linear-gradient(180deg, var(--red), var(--gold));
}
.detail-actions {
display: flex;
gap: 14px;
margin-top: 30px;
flex-wrap: wrap;
}
.family-summary {
position: relative;
overflow: hidden;
display: grid;
align-content: center;
gap: 24px;
color: #fff;
background:
linear-gradient(180deg, rgba(33,63,56,.18), rgba(33,63,56,.88)),
linear-gradient(135deg, #476f63, #c49245 52%, #8f4634);
}
.family-summary:before {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(rgba(255,255,255,.08) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,.08) 1px, transparent 1px);
background-size: 34px 34px;
opacity: .38;
}
.family-summary > * {
position: relative;
z-index: 2;
}
.summary-badge {
width: fit-content;
padding: 8px 14px;
border-radius: 999px;
color: var(--green-dark);
background: rgba(255,253,248,.88);
font-weight: 800;
}
.summary-grid {
/* 接口统计数据会替换这里的四个格子,固定列数避免加载后跳动 */
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.summary-grid span {
min-height: 84px;
padding: 16px;
border-radius: 16px;
display: grid;
place-items: center;
background: rgba(255,255,255,.13);
border: 1px solid rgba(255,255,255,.18);
}
.summary-grid b {
display: block;
font-size: 30px;
}
.detail-layout {
/* 详情页侧栏和正文为本页独有布局,不放入公共样式 */
display: grid;
grid-template-columns: 330px 1fr;
gap: 26px;
align-items: start;
}
.detail-panel,
.content-card,
.mini-card {
border: 1px solid var(--line);
border-radius: 18px;
background: rgba(255,253,248,.78);
box-shadow: 0 20px 54px rgba(57, 48, 36, .08);
}
.detail-panel {
padding: 26px;
position: sticky;
top: 108px;
}
.detail-panel p,
.content-card p,
.mini-card p {
color: var(--muted);
line-height: 1.8;
}
.detail-main {
display: grid;
gap: 24px;
}
.content-card {
padding: 30px;
}
.mini-card {
min-height: 170px;
padding: 24px;
transition: transform .24s ease, border-color .24s ease, box-shadow .24s ease;
}
.mini-card:hover {
border-color: rgba(196, 146, 69, .42);
box-shadow: 0 24px 60px rgba(57, 48, 36, .11);
}
.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;
}
.tilt-card:hover { --lift: -6px; }
@media (max-width: 900px) {
.detail-layout { grid-template-columns: 1fr; }
.detail-panel { position: static; }
.summary-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 720px) {
.hero-copy { padding-left: 0; }
.hero-copy:before { display: none; }
}
+27
View File
@@ -0,0 +1,27 @@
/* 找回密码页只保留单页装饰,公共认证卡片样式在 public.css */
.recover-card {
position: relative;
overflow: hidden;
}
.recover-card:before {
content: "";
position: absolute;
right: -80px;
top: -90px;
width: 220px;
height: 220px;
border-radius: 50%;
background: radial-gradient(circle, rgba(200, 59, 50, .13), transparent 64%);
}
.recover-card > * {
position: relative;
z-index: 2;
}
@media (max-width: 620px) {
.code-row {
grid-template-columns: 1fr;
}
}
+265
View File
@@ -0,0 +1,265 @@
.page-genealogy {
background: #fbf7ed;
}
.genealogy-hero .container {
min-height: 470px;
padding-top: 76px;
padding-bottom: 76px;
}
.hero-copy {
position: relative;
padding-left: 28px;
}
.hero-copy:before {
content: "";
position: absolute;
left: 0;
top: 58px;
width: 4px;
height: 136px;
border-radius: 999px;
background: linear-gradient(180deg, var(--red), var(--gold));
}
.genealogy-stats {
display: flex;
gap: 12px;
margin-top: 28px;
padding: 8px;
border: 1px solid rgba(224, 211, 189, .78);
border-radius: 18px;
background: rgba(255,253,248,.74);
box-shadow: 0 16px 36px rgba(72, 57, 38, .07);
}
.genealogy-stats div {
min-width: 118px;
padding: 12px 16px;
border-radius: 12px;
}
.genealogy-stats div + div {
border-left: 1px solid rgba(218, 201, 174, .78);
}
.genealogy-stats strong {
display: block;
color: var(--green);
font-size: 24px;
line-height: 1.1;
}
.genealogy-stats span {
color: #6d766f;
font-size: 14px;
}
.tree-preview {
position: relative;
min-height: 300px;
display: grid;
gap: 14px;
align-content: center;
overflow: hidden;
background:
linear-gradient(90deg, rgba(255,253,248,.95), rgba(255,253,248,.78)),
radial-gradient(circle at 78% 22%, rgba(196,146,69,.2), transparent 28%);
transform-style: preserve-3d;
}
.tree-glow {
position: absolute;
right: -70px;
top: -80px;
width: 210px;
height: 210px;
border-radius: 50%;
background: radial-gradient(circle, rgba(200, 59, 50, .13), transparent 62%);
animation: treePulse 4.2s ease-in-out infinite;
}
.tree-row {
position: relative;
z-index: 2;
display: flex;
justify-content: center;
gap: 16px;
flex-wrap: wrap;
}
.tree-row span {
min-width: 110px;
padding: 14px 18px;
border: 1px solid #dac9ae;
border-radius: 14px;
text-align: center;
color: var(--green-dark);
background: rgba(255,250,241,.9);
box-shadow: 0 12px 28px rgba(69, 53, 35, .06);
font-weight: 800;
transition: transform .24s ease, color .24s ease, background .24s ease, box-shadow .24s ease;
}
.tree-preview:hover .tree-row span {
animation: nodeFloat 1.9s ease-in-out infinite;
}
.tree-row span:hover {
color: #fff;
background: var(--green);
box-shadow: 0 16px 32px rgba(71, 111, 99, .22);
}
.tree-row.branch span:nth-child(2) { animation-delay: .08s; }
.tree-row.branch span:nth-child(3) { animation-delay: .16s; }
.tree-row.generation span:nth-child(2) { animation-delay: .08s; }
.tree-row.generation span:nth-child(3) { animation-delay: .16s; }
.tree-row.generation span:nth-child(4) { animation-delay: .24s; }
.tree-line {
position: relative;
z-index: 1;
width: min(360px, 70%);
height: 1px;
margin: 0 auto;
background: linear-gradient(90deg, transparent, rgba(196, 146, 69, .5), transparent);
}
.tree-line.short {
width: min(500px, 86%);
}
.capability-section {
position: relative;
overflow: hidden;
}
.capability-section:before {
content: "";
position: absolute;
left: 50%;
top: 58px;
width: 520px;
height: 180px;
border-radius: 50%;
background: radial-gradient(circle, rgba(196, 146, 69, .09), transparent 68%);
transform: translateX(-50%);
pointer-events: none;
}
.feature-card {
min-height: 210px;
position: relative;
overflow: hidden;
}
.feature-card:after {
content: "";
position: absolute;
left: -40%;
top: 0;
width: 36%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,.48), transparent);
transform: skewX(-18deg);
transition: left .56s ease;
}
.feature-card:hover:after {
left: 110%;
}
.icon-word {
width: 48px;
height: 48px;
margin-bottom: 18px;
border-radius: 14px;
display: grid;
place-items: center;
color: var(--red);
background: #f7e4dc;
font-size: 22px;
font-weight: 900;
transition: transform .26s ease, background .26s ease, color .26s ease;
}
.feature-card:hover .icon-word {
color: #fff;
background: var(--red);
transform: rotate(-6deg) scale(1.08);
}
.coedit-section {
background:
radial-gradient(circle at 18% 20%, rgba(200, 59, 50, .08), transparent 24%),
#f7efe2;
}
.coedit-card {
min-height: 260px;
padding: 34px;
}
.coedit-card .btn {
margin-top: 12px;
}
.process-card {
min-height: 260px;
padding: 34px;
}
.step-list {
display: grid;
gap: 16px;
margin-top: 20px;
}
.step-list p {
display: flex;
gap: 14px;
align-items: center;
margin: 0;
color: var(--muted);
}
.step-list b {
width: 38px;
height: 38px;
border-radius: 50%;
display: grid;
place-items: center;
color: #fff;
background: var(--green);
flex: 0 0 auto;
box-shadow: 0 12px 28px rgba(71, 111, 99, .18);
}
.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;
}
.tilt-card:hover {
--lift: -6px;
}
@keyframes nodeFloat {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-5px); }
}
@keyframes treePulse {
0%, 100% { opacity: .5; transform: scale(.92); }
50% { opacity: 1; transform: scale(1.08); }
}
@media (max-width: 720px) {
.hero-copy { padding-left: 0; }
.hero-copy:before { display: none; }
.genealogy-stats { flex-direction: column; }
.genealogy-stats div + div { border-left: 0; border-top: 1px solid rgba(218, 201, 174, .78); }
}
+211
View File
@@ -0,0 +1,211 @@
.help-hero .container {
min-height: 440px;
padding-top: 74px;
padding-bottom: 74px;
}
.hero-copy {
position: relative;
padding-left: 28px;
}
.hero-copy:before {
content: "";
position: absolute;
left: 0;
top: 58px;
width: 4px;
height: 132px;
border-radius: 999px;
background: linear-gradient(180deg, var(--red), var(--gold));
}
.help-tags {
display: flex;
gap: 12px;
margin-top: 28px;
flex-wrap: wrap;
}
.help-tags span {
padding: 10px 14px;
border: 1px solid rgba(224, 211, 189, .78);
border-radius: 999px;
color: #6d766f;
background: rgba(255,253,248,.74);
box-shadow: 0 14px 32px rgba(72, 57, 38, .06);
font-weight: 800;
transition: transform .22s ease, color .22s ease, background .22s ease;
}
.help-tags span:hover {
color: #fff;
background: var(--green);
transform: translateY(-3px);
}
.help-card {
position: relative;
overflow: hidden;
display: grid;
align-content: center;
gap: 14px;
}
.help-card:before {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(rgba(196,146,69,.06) 1px, transparent 1px),
linear-gradient(90deg, rgba(196,146,69,.06) 1px, transparent 1px);
background-size: 34px 34px;
opacity: .72;
}
.help-card h3,
.help-card p,
.help-card .btn,
.help-icon {
position: relative;
z-index: 2;
}
.help-orb {
position: absolute;
right: -70px;
top: -80px;
width: 220px;
height: 220px;
border-radius: 50%;
background: radial-gradient(circle, rgba(200, 59, 50, .13), transparent 62%);
animation: helpPulse 4s ease-in-out infinite;
}
.help-icon {
width: 52px;
height: 52px;
border-radius: 16px;
display: grid;
place-items: center;
color: #fff;
background: var(--green);
font-size: 26px;
font-weight: 900;
box-shadow: 0 14px 32px rgba(71, 111, 99, .2);
transition: transform .26s ease, background .26s ease;
}
.help-card:hover .help-icon {
background: var(--red);
transform: rotate(-8deg) scale(1.08);
}
.faq-section {
position: relative;
overflow: hidden;
}
.faq-section:before {
content: "";
position: absolute;
left: 50%;
top: 58px;
width: 520px;
height: 180px;
border-radius: 50%;
background: radial-gradient(circle, rgba(196, 146, 69, .09), transparent 68%);
transform: translateX(-50%);
}
.faq-list {
/* 帮助文章接口会替换 FAQ 内容,固定列表容器宽度避免加载后跳动 */
position: relative;
max-width: 880px;
margin: 0 auto;
display: grid;
gap: 14px;
}
.faq-list details {
padding: 22px 24px;
border: 1px solid var(--line);
border-radius: 18px;
background: #fffdf8;
box-shadow: 0 12px 34px rgba(57, 48, 36, .06);
transition: transform .24s ease, box-shadow .24s ease, border-color .24s ease, background .24s ease;
}
.faq-list.is-loading {
min-height: 220px;
}
.faq-list details:hover,
.faq-list details[open] {
border-color: rgba(196, 146, 69, .42);
box-shadow: 0 22px 54px rgba(57, 48, 36, .1);
}
.faq-list summary {
position: relative;
cursor: pointer;
color: var(--ink);
font-size: 20px;
font-weight: 800;
list-style: none;
padding-left: 34px;
}
.faq-list summary::-webkit-details-marker {
display: none;
}
.faq-list summary:before {
content: "";
position: absolute;
left: 0;
top: 8px;
width: 0;
height: 0;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
border-left: 10px solid var(--green-dark);
transition: transform .24s ease, border-left-color .24s ease;
}
.faq-list details[open] summary:before {
border-left-color: var(--red);
transform: rotate(90deg);
}
.faq-list p {
margin: 14px 0 0;
padding-left: 34px;
color: var(--muted);
line-height: 1.8;
animation: faqReveal .24s ease both;
}
.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;
}
.tilt-card:hover {
--lift: -6px;
}
@keyframes helpPulse {
0%, 100% { opacity: .5; transform: scale(.92); }
50% { opacity: 1; transform: scale(1.08); }
}
@keyframes faqReveal {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
@media (max-width: 720px) {
.hero-copy { padding-left: 0; }
.hero-copy:before { display: none; }
}
+1398
View File
File diff suppressed because it is too large Load Diff
+17
View File
@@ -0,0 +1,17 @@
/* 加入家谱页只保留单页差异,公共认证卡片样式在 public.css */
.page-join-genealogy .auth-card {
width: min(500px, 100%);
}
.invite-box {
padding: 14px;
border: 1px dashed var(--gold);
border-radius: 22px;
background: #fffaf1;
}
.page-join-genealogy textarea.input {
/* 申请说明需要多行输入,仍复用公共输入框视觉 */
min-height: 112px;
resize: vertical;
}
+54
View File
@@ -0,0 +1,54 @@
/* 登录页只保留单页差异,公共认证卡片样式在 public.css */
.page-login .auth-card {
width: min(440px, 100%);
}
.login-mode-tabs {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
margin: 18px 0 16px;
padding: 4px;
border: 1px solid rgba(138, 64, 42, .14);
border-radius: 8px;
background: rgba(255, 255, 255, .72);
}
/* 登录方式按钮只负责状态呈现,切换逻辑在 auth-pages.js */
.login-mode-tab {
min-height: 38px;
border: 0;
border-radius: 6px;
background: transparent;
color: var(--muted);
font: inherit;
cursor: pointer;
}
.login-mode-tab.is-active {
background: var(--brand);
color: #fff;
box-shadow: 0 8px 20px rgba(138, 64, 42, .18);
}
.login-mode-panel[hidden] {
display: none;
}
.page-login .code-row {
display: grid;
grid-template-columns: minmax(0, 1fr) 124px;
gap: 10px;
}
@media (max-width: 720px) {
.page-login .auth-layout {
align-items: start;
}
}
@media (max-width: 620px) {
.page-login .code-row {
grid-template-columns: 1fr;
}
}
+126
View File
@@ -0,0 +1,126 @@
.notice-hero .container {
min-height: 420px;
padding-top: 74px;
padding-bottom: 74px;
}
.hero-copy {
position: relative;
padding-left: 28px;
}
.hero-copy:before {
content: "";
position: absolute;
left: 0;
top: 58px;
width: 4px;
height: 132px;
border-radius: 999px;
background: linear-gradient(180deg, var(--red), var(--gold));
}
.article-meta {
display: flex;
gap: 12px;
margin-top: 28px;
flex-wrap: wrap;
}
.article-meta span {
padding: 10px 14px;
border: 1px solid rgba(224, 211, 189, .78);
border-radius: 999px;
color: #6d766f;
background: rgba(255,253,248,.74);
font-weight: 800;
}
.notice-card {
display: grid;
place-items: center;
text-align: center;
color: #fff;
background: linear-gradient(145deg, var(--green-dark), var(--green));
}
.notice-card span {
width: 88px;
height: 88px;
border-radius: 50%;
display: grid;
place-items: center;
color: var(--red);
background: rgba(255,253,248,.94);
font-family: "SimSun", "Songti SC", serif;
font-size: 42px;
font-weight: 900;
}
.notice-card p {
margin: 18px 0 0;
color: rgba(255,255,255,.88);
font-size: 22px;
}
.notice-layout {
display: grid;
grid-template-columns: minmax(0, 820px) 300px;
gap: 28px;
align-items: start;
}
.notice-content,
.notice-side {
border: 1px solid var(--line);
border-radius: 18px;
background: rgba(255,253,248,.82);
box-shadow: 0 20px 54px rgba(57, 48, 36, .08);
}
.notice-content {
padding: 42px;
}
.notice-content p {
color: #5f665f;
font-size: 18px;
line-height: 2;
}
.notice-side {
padding: 24px;
}
.notice-side a {
display: block;
margin-top: 12px;
padding: 14px 16px;
border: 1px solid var(--line);
border-radius: 14px;
color: #6f4a37;
background: #fbf7ed;
transition: transform .22s ease, color .22s ease, background .22s ease;
}
.notice-side a:hover {
color: #fff;
background: var(--green);
transform: translateX(5px);
}
.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;
}
.tilt-card:hover { --lift: -6px; }
@media (max-width: 900px) {
.notice-layout { grid-template-columns: 1fr; }
}
@media (max-width: 720px) {
.hero-copy { padding-left: 0; }
.hero-copy:before { display: none; }
}
+243
View File
@@ -0,0 +1,243 @@
.plaza-hero .container {
min-height: 440px;
padding-top: 74px;
padding-bottom: 74px;
}
.hero-copy {
position: relative;
padding-left: 28px;
}
.hero-copy:before {
content: "";
position: absolute;
left: 0;
top: 58px;
width: 4px;
height: 132px;
border-radius: 999px;
background: linear-gradient(180deg, var(--red), var(--gold));
}
.plaza-stats {
display: flex;
gap: 12px;
margin-top: 28px;
flex-wrap: wrap;
}
.plaza-stats span {
padding: 12px 16px;
border: 1px solid rgba(224, 211, 189, .78);
border-radius: 16px;
color: #6d766f;
background: rgba(255, 253, 248, .74);
box-shadow: 0 14px 32px rgba(72, 57, 38, .06);
}
.plaza-stats b {
margin-right: 6px;
color: var(--green);
font-size: 22px;
}
.search-card {
position: relative;
display: grid;
gap: 20px;
align-content: center;
overflow: hidden;
}
.search-card h3 {
position: relative;
z-index: 2;
}
.search-ornament {
position: absolute;
right: -70px;
top: -84px;
width: 220px;
height: 220px;
border-radius: 50%;
background: radial-gradient(circle, rgba(200, 59, 50, .13), transparent 62%);
animation: plazaPulse 4s ease-in-out infinite;
}
.search-toolbar {
position: relative;
z-index: 2;
padding: 8px;
border: 1px solid rgba(224, 211, 189, .78);
border-radius: 999px;
background: rgba(255, 250, 241, .82);
}
.search-card .input {
flex: 1 1 280px;
border: 0;
background: transparent;
}
.search-card .tag-row {
position: relative;
z-index: 2;
}
.family-card.featured {
min-height: 300px;
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: flex-end;
color: #fff;
background:
linear-gradient(180deg, rgba(28, 48, 45, .18), rgba(21, 55, 51, .84)),
linear-gradient(135deg, #476f63, #a95c3e);
border-radius: 18px;
padding: 28px;
box-shadow: 0 22px 58px rgba(42, 65, 59, .16);
}
.family-card.featured:before {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(rgba(255, 255, 255, .08) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, .08) 1px, transparent 1px);
background-size: 34px 34px;
opacity: .45;
}
.family-card.featured h3,
.family-card.featured p,
.family-card.featured .tag-row,
.family-badge {
position: relative;
z-index: 2;
}
.family-card.featured p {
color: rgba(255, 255, 255, .82);
}
.family-badge {
width: fit-content;
margin-bottom: auto;
padding: 7px 12px;
border-radius: 999px;
color: var(--green-dark);
background: rgba(255, 253, 248, .86);
font-weight: 800;
font-size: 13px;
}
.family-card-shine {
position: absolute;
left: -40%;
top: 0;
width: 34%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .28), transparent);
transform: skewX(-18deg);
transition: left .6s ease;
}
.family-card.featured:hover .family-card-shine {
left: 112%;
}
.family-lite {
min-height: 300px;
position: relative;
overflow: hidden;
}
.family-lite:after {
content: "";
position: absolute;
right: -60px;
bottom: -70px;
width: 170px;
height: 170px;
border-radius: 50%;
background: radial-gradient(circle, rgba(196, 146, 69, .13), transparent 68%);
transition: transform .28s ease, opacity .28s ease;
}
.family-lite:hover:after {
transform: scale(1.25);
opacity: .8;
}
.family-icon {
width: 52px;
height: 52px;
margin-bottom: 24px;
border-radius: 16px;
display: grid;
place-items: center;
color: var(--red);
background: #f7e4dc;
font-family: "SimSun", "Songti SC", serif;
font-size: 26px;
font-weight: 800;
transition: transform .26s ease, background .26s ease, color .26s ease;
}
.family-lite:hover .family-icon {
color: #fff;
background: var(--red);
transform: rotate(-6deg) scale(1.08);
}
.tag {
transition: transform .22s ease, color .22s ease, background .22s ease;
}
.tag:hover {
color: #fff;
background: var(--green);
transform: translateY(-2px);
}
.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;
}
.tilt-card:hover {
--lift: -6px;
}
@keyframes plazaPulse {
0%,
100% {
opacity: .5;
transform: scale(.92);
}
50% {
opacity: 1;
transform: scale(1.08);
}
}
@media (max-width: 720px) {
.hero-copy {
padding-left: 0;
}
.hero-copy:before {
display: none;
}
.search-toolbar {
border-radius: 18px;
}
}
+722
View File
@@ -0,0 +1,722 @@
.page-profile-module {
background: #f8f4ec;
}
.module-hero {
position: relative;
overflow: hidden;
padding: 58px 0 42px;
background:
radial-gradient(circle at 76% 18%, rgba(196, 146, 69, .16), transparent 25%),
linear-gradient(125deg, #fffaf0 0%, #f5eadb 52%, #eaf2ee 100%);
}
.module-hero:before {
content: "";
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(138, 50, 43, .05) 1px, transparent 1px),
linear-gradient(90deg, rgba(138, 50, 43, .05) 1px, transparent 1px);
background-size: 64px 64px;
mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
}
.module-hero .container {
position: relative;
z-index: 2;
}
.module-kicker {
width: fit-content;
margin-bottom: 18px;
padding: 8px 14px;
border-radius: 999px;
color: var(--red);
background: rgba(248, 229, 223, .92);
font-weight: 900;
}
.module-title-row {
display: flex;
justify-content: space-between;
gap: 22px;
align-items: flex-end;
}
.module-title-row h1 {
margin-bottom: 12px;
font-size: 48px;
}
.module-title-row p {
max-width: 780px;
margin: 0;
color: #636c65;
font-size: 17px;
line-height: 1.9;
}
.module-layout {
display: grid;
grid-template-columns: 280px minmax(0, 1fr);
gap: 24px;
align-items: start;
}
.module-nav,
.module-panel,
.module-card {
border: 1px solid rgba(224, 211, 189, .86);
border-radius: 18px;
background: rgba(255,253,248,.88);
box-shadow: 0 22px 58px rgba(57, 48, 36, .08);
}
.module-nav {
position: sticky;
top: 106px;
display: grid;
gap: 8px;
padding: 16px;
}
.module-nav a {
padding: 14px 16px;
border-radius: 14px;
color: #5e665f;
font-weight: 900;
transition: transform .22s ease, color .22s ease, background .22s ease;
}
.module-nav a:hover,
.module-nav a.active {
color: var(--red);
background: #f8e5df;
transform: translateX(4px);
}
.module-main {
display: grid;
gap: 20px;
}
.module-panel {
padding: 28px;
}
.module-panel h2 {
margin-bottom: 10px;
font-family: "SimSun", "Songti SC", serif;
font-size: 32px;
}
.module-panel > p {
color: var(--muted);
line-height: 1.85;
}
.module-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 18px;
}
.module-grid.two {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.module-card {
position: relative;
overflow: hidden;
min-height: 170px;
padding: 22px;
transition: transform .22s ease, border-color .22s ease, box-shadow .22s ease;
}
.module-action-card {
/* 按钮型模块卡片用于消息中心操作,保留卡片视觉但去掉浏览器按钮默认样式 */
width: 100%;
text-align: left;
cursor: pointer;
font: inherit;
}
.notification-row.is-unread {
/* 未读消息用轻量背景强调,不新增内联样式 */
border-color: rgba(200, 59, 50, .28);
background: #fff7f2;
}
.join-apply-row {
/* 加入申请行复用 module-row,只补充审核业务的按钮对齐 */
align-items: center;
}
.join-apply-row .bottom-actions {
justify-content: flex-end;
}
.generation-row {
/* 字辈谱行复用 module-row,补充按钮型 pill 的对齐和点击反馈 */
align-items: center;
}
.generation-row .pill {
border: 0;
cursor: pointer;
font: inherit;
}
.lineage-row {
/* 世系人物行使用 button 承载点击详情,保留列表卡片视觉 */
width: 100%;
text-align: left;
cursor: pointer;
font: inherit;
}
.lineage-row.is-selected {
border-color: rgba(200, 59, 50, .38);
background: #fff6f1;
}
.lineage-toolbar {
/* 搜索栏只做世系图页面差异布局,不放到 JS 内联样式 */
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 12px;
margin-bottom: 16px;
}
.lineage-toolbar input {
min-height: 46px;
padding: 12px 14px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fffdf8;
color: var(--ink);
font: inherit;
outline: none;
}
.lineage-tree-wrap {
overflow-x: auto;
padding: 18px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fffdf8;
}
.lineage-tree-wrap.is-compact {
max-height: 420px;
}
.lineage-tree,
.lineage-tree ul {
/* 树结构只负责层级缩进,避免用 JS 计算位置 */
display: grid;
gap: 12px;
margin: 0;
padding-left: 24px;
list-style: none;
}
.lineage-tree {
padding-left: 0;
}
.lineage-node {
min-width: 168px;
padding: 12px 14px;
border: 1px solid rgba(224, 211, 189, .86);
border-radius: 8px;
background: #fff7f2;
color: var(--ink);
text-align: left;
cursor: pointer;
font: inherit;
}
.lineage-node strong,
.lineage-node span {
display: block;
}
.lineage-node span {
margin-top: 4px;
color: var(--muted);
font-size: 13px;
}
.member-admin-row {
/* 成员管理行保留列表结构,右侧放权限和移除操作 */
align-items: center;
}
.row-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
justify-content: flex-end;
}
.pill.is-danger {
color: #8a322b;
background: #fde8e2;
}
.article-row {
/* 谱文列表行复用模块列表视觉,单独保留类名方便后续文章样式调整 */
align-items: center;
}
.feed-row {
/* 家族圈动态行右侧承载点赞和评论按钮 */
align-items: center;
}
.feed-row small {
display: block;
margin-top: 6px;
color: var(--muted);
}
.album-row,
.album-photo-row {
/* 相册和照片行使用独立类名,便于后续相册页单独调样式 */
align-items: center;
}
.album-photo-form {
margin-top: 18px;
}
.upload-control {
/* 公共上传按钮使用 label 触发文件选择,避免 JS 注入按钮样式 */
width: fit-content;
min-height: 42px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-top: 10px;
padding: 10px 14px;
border-radius: 8px;
color: var(--red);
background: #f8e5df;
font-weight: 900;
cursor: pointer;
}
.upload-input {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
.upload-status {
margin: 8px 0 0;
color: var(--muted);
font-size: 14px;
}
.ceremony-row,
.ceremony-gift-row,
.merit-row {
/* 贺礼、献礼和功德记录行复用模块列表,仅补业务类名方便维护 */
align-items: center;
}
.ceremony-gift-form {
margin-top: 18px;
}
.growth-row {
/* 成长记录行复用模块列表,单独留类名方便后续调整 */
align-items: center;
}
.memo-row {
/* 备忘录行复用模块列表,单独留类名方便后续调整 */
align-items: center;
}
.vip-package-list {
/* 会员套餐列表用网格呈现,套餐卡片由接口数据渲染 */
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
margin: 18px 0;
}
.vip-package-card {
min-height: 188px;
padding: 20px;
border: 1px solid rgba(224, 211, 189, .86);
border-radius: 8px;
background: #fffdf8;
color: var(--ink);
text-align: left;
cursor: pointer;
font: inherit;
transition: border-color .22s ease, box-shadow .22s ease, transform .22s ease;
}
.vip-package-card:hover,
.vip-package-card.is-selected {
border-color: rgba(200, 59, 50, .48);
box-shadow: 0 18px 42px rgba(57, 48, 36, .1);
transform: translateY(-2px);
}
.vip-package-name,
.vip-package-card strong,
.vip-package-card small {
display: block;
}
.vip-package-name {
margin-bottom: 12px;
color: var(--red);
font-weight: 900;
}
.vip-package-card strong {
margin-bottom: 6px;
font-size: 30px;
}
.vip-package-card small {
color: var(--muted);
}
.vip-package-card p {
margin: 14px 0 0;
color: var(--muted);
line-height: 1.7;
}
.vip-order-form {
margin-top: 20px;
}
.vip-order-row {
/* 会员订单行只补业务类名,方便和其他 module-row 分开维护 */
align-items: center;
}
.security-form {
/* 安全设置表单复用编辑表单结构,只补模块间距 */
margin-top: 16px;
}
.security-danger {
border-color: rgba(138, 50, 43, .28);
background: #fff7f2;
}
.security-danger .editor-field input,
.security-danger .editor-field textarea {
background: #fffdf8;
}
.region-profile-form {
/* 个人资料地区表单使用公共地区选择器,样式集中在 CSS */
margin-top: 16px;
}
.profile-region-picker {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.profile-region-picker select {
min-height: 48px;
padding: 12px 14px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fffdf8;
color: var(--ink);
font: inherit;
outline: none;
}
.profile-create-family-form {
/* 个人中心创建家谱页复用编辑表单,只保留页面级间距 */
margin-top: 16px;
}
.module-card:hover {
border-color: rgba(196, 146, 69, .42);
box-shadow: 0 24px 60px rgba(57, 48, 36, .11);
}
.module-card.is-selected {
border-color: rgba(200, 59, 50, .5);
background: #fff6f1;
box-shadow: 0 24px 60px rgba(200, 59, 50, .12);
}
.module-card:after {
content: "";
position: absolute;
inset: 0 auto 0 -42%;
width: 34%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,.52), transparent);
transform: skewX(-18deg);
transition: left .54s ease;
}
.module-card:hover:after {
left: 112%;
}
.module-card .icon {
width: 46px;
height: 46px;
margin-bottom: 18px;
border-radius: 14px;
display: grid;
place-items: center;
color: var(--red);
background: #f8e5df;
font-weight: 900;
}
.module-card h3 {
margin-bottom: 8px;
font-size: 23px;
}
.module-card p {
color: var(--muted);
line-height: 1.75;
}
.module-list {
display: grid;
gap: 14px;
}
.module-row {
display: grid;
grid-template-columns: 1fr auto;
gap: 16px;
align-items: center;
padding: 18px 20px;
border: 1px solid var(--line);
border-radius: 15px;
background: #fffdf8;
}
.module-row h3 {
margin-bottom: 4px;
font-size: 22px;
}
.module-row p {
margin: 0;
color: var(--muted);
}
.pill {
padding: 7px 12px;
border-radius: 999px;
color: var(--red);
background: #f8e5df;
font-weight: 900;
white-space: nowrap;
}
.form-like {
display: grid;
overflow: hidden;
border: 1px solid var(--line);
border-radius: 16px;
background: #fffdf8;
}
.form-like p {
display: grid;
grid-template-columns: 170px 1fr auto;
gap: 16px;
margin: 0;
padding: 18px 20px;
border-bottom: 1px solid var(--line);
align-items: center;
}
.form-like p:last-child {
border-bottom: 0;
}
.form-like span {
color: var(--muted);
}
.form-like b {
color: var(--ink);
}
.bottom-actions {
display: flex;
gap: 14px;
flex-wrap: wrap;
}
.editor-form {
display: grid;
gap: 18px;
}
.profile-data-form {
/* 个人资料编辑表单只补充本页间距,基础控件继续复用 editor-form。 */
margin-top: 14px;
}
.editor-field {
display: grid;
gap: 10px;
}
.editor-field label {
color: var(--ink);
font-weight: 900;
}
.editor-field input,
.editor-field select,
.editor-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;
}
.editor-field textarea {
min-height: 160px;
resize: vertical;
}
.editor-field input:focus,
.editor-field select:focus,
.editor-field textarea:focus {
border-color: rgba(200, 59, 50, .45);
box-shadow: 0 0 0 4px rgba(200, 59, 50, .08);
}
.editor-two {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
.profile-form-actions {
/* 保存状态跟随按钮排列,避免用 JS 写入临时样式。 */
align-items: center;
}
.form-status {
color: var(--muted);
font-weight: 800;
}
.generation-batch-form {
/* 批量字辈表单复用编辑表单,只补充本页预览前的间距。 */
margin-top: 14px;
}
.generation-batch-check {
display: flex;
gap: 10px;
align-items: center;
color: var(--muted);
font-weight: 800;
}
.generation-batch-check input {
width: 18px;
height: 18px;
accent-color: var(--red);
}
.generation-batch-actions {
align-items: center;
}
.generation-batch-preview {
margin-top: 18px;
}
.upload-box {
min-height: 136px;
border: 1px dashed rgba(138, 50, 43, .28);
border-radius: 8px;
display: grid;
place-items: center;
color: var(--red);
background: #fff8f2;
font-weight: 900;
cursor: pointer;
}
.switch-row {
display: flex;
gap: 18px;
flex-wrap: wrap;
align-items: center;
}
.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;
}
.tilt-card:hover {
--lift: -5px;
}
@media (max-width: 1060px) {
.module-layout,
.module-grid,
.module-grid.two,
.vip-package-list {
grid-template-columns: 1fr;
}
.module-nav {
position: static;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 720px) {
.module-title-row {
display: grid;
}
.module-title-row h1 {
font-size: 38px;
}
.module-nav {
grid-template-columns: 1fr;
}
.form-like p,
.module-row,
.editor-two,
.profile-region-picker {
grid-template-columns: 1fr;
}
}
+613
View File
@@ -0,0 +1,613 @@
.page-profile {
background: #f8f4ec;
}
.profile-hero {
position: relative;
overflow: hidden;
padding: 62px 0 34px;
background:
radial-gradient(circle at 78% 12%, rgba(196, 146, 69, .18), transparent 25%),
linear-gradient(125deg, #fffaf0 0%, #f4eadb 48%, #eaf2ee 100%);
}
.profile-hero:before {
content: "";
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(138, 50, 43, .05) 1px, transparent 1px),
linear-gradient(90deg, rgba(138, 50, 43, .05) 1px, transparent 1px);
background-size: 64px 64px;
mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
}
.profile-hero-grid {
position: relative;
z-index: 2;
display: grid;
grid-template-columns: minmax(0, 1fr) 420px;
gap: 24px;
align-items: stretch;
}
.profile-card,
.profile-stats,
.panel,
.side-card {
border: 1px solid rgba(224, 211, 189, .84);
border-radius: 18px;
background: rgba(255, 253, 248, .86);
box-shadow: 0 22px 58px rgba(57, 48, 36, .08);
}
.profile-card {
display: grid;
grid-template-columns: 116px 1fr;
gap: 28px;
padding: 34px;
align-items: center;
}
.avatar-wrap {
position: relative;
width: 108px;
height: 108px;
}
.avatar {
width: 108px;
height: 108px;
border-radius: 28px;
display: grid;
place-items: center;
color: #fff;
background:
radial-gradient(circle at 30% 24%, rgba(255, 255, 255, .38), transparent 24%),
linear-gradient(145deg, var(--red), #ee8d60 52%, var(--green));
font-family: "SimSun", "Songti SC", serif;
font-size: 46px;
font-weight: 900;
box-shadow: 0 18px 40px rgba(200, 59, 50, .18);
}
.avatar-wrap span {
position: absolute;
right: -4px;
bottom: -4px;
width: 28px;
height: 28px;
border: 5px solid #fffdf8;
border-radius: 50%;
background: #31bf6b;
}
.profile-info h1 {
margin-bottom: 12px;
font-size: 44px;
}
.profile-info p {
max-width: 720px;
color: #636c65;
font-size: 17px;
line-height: 1.9;
}
.profile-tags,
.mini-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.profile-tags span,
.mini-actions span {
padding: 7px 12px;
border-radius: 999px;
color: var(--red);
background: #f8e5df;
font-size: 14px;
font-weight: 800;
}
.profile-stats {
display: grid;
grid-template-columns: repeat(2, 1fr);
padding: 22px;
gap: 14px;
}
.profile-stats div {
min-height: 116px;
padding: 20px;
border-radius: 14px;
display: grid;
align-content: center;
background: rgba(247, 239, 226, .72);
}
.profile-stats strong {
color: var(--green);
font-size: 34px;
line-height: 1;
}
.profile-stats span,
.panel-head p,
.data-list span,
.todo-list span,
.family-card p,
.feature-tile p,
.content-tile p {
color: var(--muted);
line-height: 1.75;
}
.dashboard-section {
padding-top: 42px;
}
.dashboard-layout {
display: grid;
grid-template-columns: 300px minmax(0, 1fr);
gap: 24px;
align-items: start;
}
.dashboard-side {
position: sticky;
top: 106px;
display: grid;
gap: 20px;
}
.side-card,
.panel {
padding: 26px;
}
.side-card h2,
.panel h2 {
margin-bottom: 8px;
font-family: "SimSun", "Songti SC", serif;
font-size: 30px;
}
.user-actions {
display: grid;
gap: 10px;
}
.user-actions a,
.todo-list a {
display: block;
padding: 15px 16px;
border: 1px solid var(--line);
border-radius: 14px;
background: #fbf7ed;
transition: transform .22s ease, border-color .22s ease, background .22s ease, color .22s ease;
}
.user-actions a:hover,
.todo-list a:hover {
transform: translateX(5px);
border-color: rgba(71, 111, 99, .28);
background: #eef4ef;
}
.user-actions span,
.todo-list b {
display: block;
color: var(--ink);
font-weight: 900;
}
.user-actions b {
display: block;
margin-top: 4px;
color: var(--muted);
font-size: 13px;
}
.logout-link span {
color: var(--red);
}
.card-head,
.panel-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 18px;
margin-bottom: 22px;
}
.card-head span {
padding: 6px 11px;
border-radius: 999px;
color: #fff;
background: var(--red);
font-weight: 800;
}
.todo-list,
.dashboard-main {
display: grid;
gap: 18px;
}
.panel-head.compact {
margin-bottom: 18px;
}
.panel-head h2 {
margin-bottom: 6px;
}
.family-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px;
}
.family-card {
display: grid;
grid-template-columns: 112px 1fr;
gap: 20px;
min-height: 190px;
padding: 20px;
border: 1px solid var(--line);
border-radius: 16px;
background: #fffdf8;
transition: transform .22s ease, border-color .22s ease, box-shadow .22s ease;
}
.family-card:hover,
.feature-tile:hover,
.content-tile:hover {
border-color: rgba(196, 146, 69, .42);
box-shadow: 0 22px 52px rgba(57, 48, 36, .1);
}
.book-cover {
min-height: 150px;
border-radius: 10px;
display: grid;
place-items: center;
color: #fffdf8;
background:
linear-gradient(90deg, transparent calc(100% - 16px), rgba(255, 255, 255, .7) calc(100% - 15px), transparent calc(100% - 14px)),
linear-gradient(135deg, #0f405c, #092c42);
font-family: "SimSun", "Songti SC", serif;
font-size: 22px;
line-height: 1.7;
text-align: center;
box-shadow: inset -12px 0 0 rgba(255, 255, 255, .08);
}
.book-cover.alt {
background:
linear-gradient(90deg, transparent calc(100% - 16px), rgba(255, 255, 255, .7) calc(100% - 15px), transparent calc(100% - 14px)),
linear-gradient(135deg, #173c35, #785f35);
}
.family-card h3,
.feature-tile h3,
.content-tile h3 {
margin-bottom: 8px;
font-size: 24px;
}
.feature-grid,
.content-grid,
.service-grid {
display: grid;
gap: 16px;
}
.management-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.feature-tile,
.content-tile {
position: relative;
overflow: hidden;
min-height: 170px;
padding: 22px;
border: 1px solid var(--line);
border-radius: 16px;
background: #fffdf8;
transition: transform .22s ease, border-color .22s ease, box-shadow .22s ease;
}
.feature-tile span,
.content-tile span {
width: 48px;
height: 48px;
margin-bottom: 18px;
border-radius: 14px;
display: grid;
place-items: center;
color: var(--red);
background: #f7e4dc;
font-weight: 900;
}
.feature-tile:after,
.content-tile:after {
content: "";
position: absolute;
inset: 0 auto 0 -42%;
width: 34%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .52), transparent);
transform: skewX(-18deg);
transition: left .54s ease;
}
.feature-tile:hover:after,
.content-tile:hover:after {
left: 112%;
}
.content-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.content-tile.large {
grid-column: span 2;
color: #fff;
background: linear-gradient(145deg, var(--green-dark), var(--green));
}
.content-tile.large p,
.content-tile.large h3 {
color: rgba(255, 255, 255, .86);
}
.content-tile.large span {
color: var(--green-dark);
background: rgba(255, 253, 248, .9);
}
.profile-data-layout {
display: grid;
grid-template-columns: 1.15fr .85fr;
gap: 18px;
}
.link-btn {
flex: 0 0 auto;
padding: 9px 14px;
border-radius: 999px;
color: var(--red);
background: #f8e5df;
font-weight: 900;
}
.data-list {
display: grid;
border-top: 1px solid var(--line);
}
.data-list p {
display: grid;
grid-template-columns: 150px 1fr;
gap: 16px;
margin: 0;
padding: 16px 0;
border-bottom: 1px solid var(--line);
}
.data-list b {
color: var(--ink);
}
.service-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.service-grid a {
min-height: 88px;
padding: 18px;
border-radius: 14px;
display: grid;
place-items: center;
color: #6f4a37;
background: #fbf7ed;
border: 1px solid var(--line);
font-weight: 900;
transition: transform .22s ease, color .22s ease, background .22s ease;
}
.service-grid a:hover {
color: #fff;
background: var(--green);
transform: translateY(-3px);
}
.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;
}
.tilt-card:hover {
--lift: -5px;
}
.logout-modal {
position: fixed;
inset: 0;
z-index: 80;
display: grid;
place-items: center;
padding: 24px;
opacity: 0;
pointer-events: none;
transition: opacity .22s ease;
}
.logout-modal.show {
opacity: 1;
pointer-events: auto;
}
.logout-modal-backdrop {
position: absolute;
inset: 0;
background: rgba(32, 35, 34, .46);
backdrop-filter: blur(10px);
}
.logout-dialog {
position: relative;
z-index: 1;
width: min(420px, 100%);
padding: 34px;
border-radius: 8px;
background: #fff;
box-shadow: 0 28px 80px rgba(61, 73, 68, .24);
text-align: center;
transform: translateY(12px) scale(.98);
transition: transform .22s ease;
}
.logout-modal.show .logout-dialog {
transform: translateY(0) scale(1);
}
.logout-close {
position: absolute;
top: 14px;
right: 14px;
width: 34px;
height: 34px;
border: 0;
border-radius: 50%;
background: #f4f1eb;
color: #7f786f;
font-size: 24px;
line-height: 1;
cursor: pointer;
transition: transform .2s ease, background .2s ease, color .2s ease;
}
.logout-close:hover {
transform: rotate(90deg);
background: var(--red-soft);
color: var(--red);
}
.logout-icon {
display: inline-grid;
place-items: center;
width: 58px;
height: 58px;
border-radius: 50%;
margin-bottom: 16px;
background: var(--red-soft);
color: var(--red);
font-weight: 800;
}
.logout-dialog h2 {
margin: 0 0 10px;
font-size: 24px;
color: var(--ink);
}
.logout-dialog p {
margin: 0;
color: var(--muted);
line-height: 1.8;
}
.logout-actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-top: 26px;
}
.logout-actions .btn {
justify-content: center;
}
.logout-modal-open {
overflow: hidden;
}
@media (max-width: 1120px) {
.profile-hero-grid,
.dashboard-layout,
.profile-data-layout {
grid-template-columns: 1fr;
}
.dashboard-side {
position: static;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.content-grid,
.management-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 760px) {
.profile-hero {
padding-top: 36px;
}
.profile-card {
grid-template-columns: 1fr;
padding: 24px;
}
.profile-info h1 {
font-size: 36px;
}
.profile-stats,
.dashboard-side,
.family-grid,
.content-grid,
.management-grid,
.service-grid {
grid-template-columns: 1fr;
}
.family-card {
grid-template-columns: 88px 1fr;
}
.book-cover {
min-height: 120px;
font-size: 18px;
}
.content-tile.large {
grid-column: span 1;
}
.panel-head,
.card-head {
display: grid;
}
.data-list p {
grid-template-columns: 1fr;
gap: 4px;
}
.logout-dialog {
padding: 30px 22px;
}
.logout-actions {
grid-template-columns: 1fr;
}
}
+126
View File
@@ -0,0 +1,126 @@
.promo-hero .container {
min-height: 440px;
padding-top: 74px;
padding-bottom: 74px;
}
.hero-copy {
position: relative;
padding-left: 28px;
}
.hero-copy:before {
content: "";
position: absolute;
left: 0;
top: 58px;
width: 4px;
height: 132px;
border-radius: 999px;
background: linear-gradient(180deg, var(--red), var(--gold));
}
.promo-tags {
display: flex;
gap: 12px;
margin-top: 28px;
flex-wrap: wrap;
}
.promo-tags span {
padding: 10px 14px;
border: 1px solid rgba(224, 211, 189, .78);
border-radius: 999px;
color: #6d766f;
background: rgba(255, 253, 248, .74);
font-weight: 800;
transition: transform .22s ease, color .22s ease, background .22s ease;
}
.promo-tags span:hover {
color: #fff;
background: var(--green);
transform: translateY(-3px);
}
.album-preview {
display: grid;
grid-template-columns: 1.2fr 1fr;
grid-template-rows: repeat(2, 130px);
gap: 14px;
overflow: hidden;
}
.album-photo {
border-radius: 18px;
background: radial-gradient(circle at 72% 24%, rgba(255, 255, 255, .75) 0 8%, transparent 9%), linear-gradient(135deg, #e7c89e, #b94135 48%, #143d57);
box-shadow: 0 16px 36px rgba(57, 48, 36, .08);
transition: transform .26s ease;
}
.album-photo.large {
grid-row: 1 / 3;
background: linear-gradient(180deg, rgba(28, 48, 45, .1), rgba(21, 55, 51, .72)), url("../images/ancestral-hall.png") center/cover;
}
.album-photo.warm {
background: linear-gradient(135deg, #f0d8ac, #c49245 45%, #7d342d);
}
.album-photo.green {
background: linear-gradient(135deg, #dfd1b5, #6b7f70 48%, #26302c);
}
.album-preview:hover .album-photo {
transform: scale(1.025);
}
.album-card {
min-height: 180px;
position: relative;
overflow: hidden;
}
.album-card:after {
content: "";
position: absolute;
left: -42%;
top: 0;
width: 36%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .54), transparent);
transform: skewX(-18deg);
transition: left .56s ease;
}
.album-card:hover:after {
left: 112%;
}
.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;
}
.tilt-card:hover {
--lift: -6px;
}
@media (max-width: 720px) {
.hero-copy {
padding-left: 0;
}
.hero-copy:before {
display: none;
}
.album-preview {
grid-template-columns: 1fr;
grid-template-rows: repeat(4, 120px);
}
.album-photo.large {
grid-row: auto;
}
}
+140
View File
@@ -0,0 +1,140 @@
.promo-hero .container {
min-height: 440px;
padding-top: 74px;
padding-bottom: 74px;
}
.hero-copy {
position: relative;
padding-left: 28px;
}
.hero-copy:before {
content: "";
position: absolute;
left: 0;
top: 58px;
width: 4px;
height: 132px;
border-radius: 999px;
background: linear-gradient(180deg, var(--red), var(--gold));
}
.promo-tags {
display: flex;
gap: 12px;
margin-top: 28px;
flex-wrap: wrap;
}
.promo-tags span {
padding: 10px 14px;
border: 1px solid rgba(224, 211, 189, .78);
border-radius: 999px;
color: #6d766f;
background: rgba(255, 253, 248, .74);
font-weight: 800;
transition: transform .22s ease, color .22s ease, background .22s ease;
}
.promo-tags span:hover {
color: #fff;
background: var(--green);
transform: translateY(-3px);
}
.video-preview {
min-height: 300px;
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: flex-end;
color: #fff;
background: linear-gradient(180deg, rgba(31, 63, 55, .1), rgba(25, 39, 34, .86)), url("../images/ancestral-hall.png") center/cover;
}
.video-preview:before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(rgba(255, 255, 255, .08) 1px, transparent 1px), linear-gradient(90deg, rgba(255, 255, 255, .08) 1px, transparent 1px);
background-size: 34px 34px;
opacity: .35;
}
.video-preview h3,
.video-preview p,
.play-button {
position: relative;
z-index: 2;
}
.video-preview p {
color: rgba(255, 255, 255, .8);
}
.play-button {
width: 64px;
height: 64px;
margin-bottom: 46px;
border-radius: 50%;
background: rgba(255, 255, 255, .94);
box-shadow: 0 14px 34px rgba(0, 0, 0, .2);
transition: transform .24s ease;
}
.play-button:after {
content: "";
position: absolute;
left: 26px;
top: 19px;
border-top: 13px solid transparent;
border-bottom: 13px solid transparent;
border-left: 20px solid var(--red);
}
.video-preview:hover .play-button {
transform: scale(1.08);
}
.video-card {
min-height: 180px;
position: relative;
overflow: hidden;
}
.video-card:after {
content: "";
position: absolute;
left: -42%;
top: 0;
width: 36%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .54), transparent);
transform: skewX(-18deg);
transition: left .56s ease;
}
.video-card:hover:after {
left: 112%;
}
.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;
}
.tilt-card:hover {
--lift: -6px;
}
@media (max-width: 720px) {
.hero-copy {
padding-left: 0;
}
.hero-copy:before {
display: none;
}
}
+900
View File
@@ -0,0 +1,900 @@
: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);
}
.auth-layer-message {
/* 认证页 layui 消息提示皮肤,替代浏览器原生 alert。 */
min-width: 160px;
border-radius: 999px !important;
background: rgba(38, 48, 44, .94) !important;
box-shadow: 0 16px 42px rgba(38, 48, 44, .24) !important;
}
.auth-layer-message .layui-layer-content {
padding: 10px 18px !important;
color: #fffdf8 !important;
font-size: 14px;
font-weight: 800;
line-height: 1.6;
text-align: center;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
color: var(--ink);
font-family: "Microsoft YaHei", "PingFang SC", Arial, sans-serif;
background: var(--paper);
}
a {
color: inherit;
text-decoration: none;
}
img {
max-width: 100%;
display: block;
}
@media (hover: hover) and (pointer: fine) {
a,
.btn,
.card,
button,
summary {
cursor: pointer;
}
}
.container {
width: min(1280px, calc(100% - 72px));
margin: 0 auto;
}
.site-header {
position: sticky;
top: 0;
z-index: 20;
background:
linear-gradient(115deg, rgba(110, 38, 32, .96) 0%, rgba(200, 59, 50, .96) 48%, rgba(139, 52, 39, .96) 100%);
border-bottom: 1px solid rgba(255, 253, 248, .2);
box-shadow:
0 4px 0 rgba(93, 30, 26, .2),
0 18px 38px rgba(83, 32, 26, .34),
0 30px 70px rgba(83, 32, 26, .22);
backdrop-filter: blur(18px);
}
.nav {
height: 82px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 28px;
}
.brand {
display: flex !important;
align-items: center;
gap: 12px;
padding: 8px 14px;
border-radius: 999px;
background: rgba(255, 253, 248, .92);
box-shadow: 0 10px 28px rgba(63, 28, 22, .18);
font-size: 24px;
font-weight: 800;
white-space: nowrap;
}
.brand-logo {
display: block;
width: 270px;
height: auto;
}
.footer .brand-logo {
width: 270px;
}
.auth-page-brand .brand-logo {
width: 220px;
}
.brand-logo-mark {
width: 48px;
height: 48px;
}
.brand-text {
color: var(--ink);
font-weight: 900;
}
.brand-mark {
width: 40px;
height: 40px;
border-radius: 50%;
display: grid;
place-items: center;
color: #fff;
background: var(--red);
box-shadow: 0 10px 24px rgba(200, 59, 50, .22);
font-weight: 800;
}
.nav-links {
display: flex;
align-items: center;
gap: 26px;
color: rgba(255, 253, 248, .78);
font-size: 15px;
white-space: nowrap;
}
.nav-links a.active,
.nav-links a:hover {
color: #fff7d8;
}
.nav-links a,
.nav-actions a {
position: relative;
transition: color .22s ease, transform .22s ease;
}
.nav-links a:after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: -10px;
height: 2px;
border-radius: 999px;
background: linear-gradient(90deg, #f5d99b, #fff3c8);
transform: scaleX(0);
transform-origin: center;
transition: transform .24s ease;
}
.nav-links a.active:after,
.nav-links a:hover:after {
transform: scaleX(1);
}
.nav-actions {
display: flex;
align-items: center;
gap: 14px;
color: rgba(255, 253, 248, .86);
font-size: 15px;
white-space: nowrap;
}
.btn {
min-width: 118px;
height: 44px;
padding: 0 24px;
border-radius: 999px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid transparent;
font-weight: 800;
transition: transform .22s ease, box-shadow .22s ease, border-color .22s ease, background .22s ease;
will-change: transform;
}
.btn:hover {
transform: translateY(-2px);
}
.btn.primary {
color: #fff;
background: var(--red);
box-shadow: 0 14px 30px rgba(200, 59, 50, .24);
}
.btn.primary:hover {
box-shadow: 0 18px 38px rgba(200, 59, 50, .3);
}
.site-header .btn.primary {
color: #8f332b;
background: linear-gradient(135deg, #fff8e6, #f1d49a);
box-shadow: 0 12px 28px rgba(64, 26, 21, .22);
}
.site-header .btn.primary:hover {
box-shadow: 0 16px 34px rgba(64, 26, 21, .3);
}
.btn.ghost {
color: var(--red);
border-color: rgba(200, 59, 50, .28);
background: rgba(255, 255, 255, .68);
}
.inner-hero {
position: relative;
overflow: hidden;
background:
radial-gradient(circle at 82% 12%, rgba(196, 146, 69, .16), transparent 24%),
linear-gradient(125deg, #fffaf0 0%, #f7efe2 48%, #edf4ef 100%);
}
.inner-hero:before {
content: "";
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(138, 50, 43, .05) 1px, transparent 1px),
linear-gradient(90deg, rgba(138, 50, 43, .05) 1px, transparent 1px);
background-size: 68px 68px;
mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
}
.inner-hero .container {
position: relative;
z-index: 2;
min-height: 410px;
padding: 68px 0 72px;
display: grid;
grid-template-columns: .9fr 1.1fr;
gap: 54px;
align-items: center;
}
.eyebrow {
display: inline-flex;
align-items: center;
gap: 12px;
margin-bottom: 22px;
color: #8a5a3b;
font-weight: 800;
}
.eyebrow:before {
content: "";
width: 42px;
height: 2px;
background: var(--red);
}
h1,
h2,
h3,
h4,
p {
margin-top: 0;
}
h1 {
margin-bottom: 20px;
color: var(--ink);
font-family: "SimSun", "Songti SC", serif;
font-size: 52px;
line-height: 1.16;
}
h2 {
margin-bottom: 14px;
font-family: "SimSun", "Songti SC", serif;
font-size: 42px;
line-height: 1.25;
}
h3 {
margin-bottom: 12px;
font-size: 23px;
}
.lead {
max-width: 620px;
margin-bottom: 0;
color: #645d52;
font-size: 18px;
line-height: 1.9;
}
.hero-card {
min-height: 260px;
padding: 28px;
border: 1px solid rgba(224, 211, 189, .88);
border-radius: 24px;
background: rgba(255, 253, 248, .92);
box-shadow: var(--shadow);
}
.section {
padding: 76px 0;
}
.section.alt {
background: #fffdf8;
}
.section-head {
margin-bottom: 34px;
text-align: center;
}
.section-head p {
max-width: 720px;
margin: 14px auto 0;
color: var(--muted);
font-size: 17px;
line-height: 1.9;
}
.grid-2,
.grid-3,
.grid-4 {
display: grid;
gap: 22px;
}
.grid-2 {
grid-template-columns: repeat(2, 1fr);
}
.grid-3 {
grid-template-columns: repeat(3, 1fr);
}
.grid-4 {
grid-template-columns: repeat(4, 1fr);
}
.card {
padding: 24px;
border-radius: 18px;
background: #fffdf8;
border: 1px solid var(--line);
box-shadow: 0 16px 42px rgba(57, 48, 36, .07);
transition: transform .24s ease, box-shadow .24s ease, border-color .24s ease, background .24s ease;
will-change: transform;
}
.card:hover {
transform: translateY(-6px);
border-color: rgba(196, 146, 69, .38);
box-shadow: 0 24px 60px rgba(57, 48, 36, .11);
}
.card.soft {
background: #fbf7ed;
}
.card.dark {
color: #fff;
background: linear-gradient(155deg, var(--green-dark), var(--green));
}
.card.dark p {
color: rgba(255, 255, 255, .78);
}
.card p,
.muted {
color: var(--muted);
line-height: 1.8;
}
.tag-row {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.tag {
padding: 6px 12px;
border-radius: 999px;
color: var(--green);
background: #eaf0eb;
font-size: 14px;
font-weight: 700;
}
.toolbar {
display: flex;
gap: 12px;
align-items: center;
flex-wrap: wrap;
}
.input,
select,
textarea {
width: 100%;
min-height: 48px;
padding: 0 16px;
border: 1px solid #e2d4bf;
border-radius: 999px;
color: var(--ink);
background: #fbf8f1;
font: inherit;
}
textarea {
min-height: 128px;
padding: 14px 16px;
border-radius: 18px;
resize: vertical;
}
.form-grid {
display: grid;
gap: 16px;
}
.form-card {
max-width: 560px;
margin: 0 auto;
padding: 34px;
}
.auth-layout {
min-height: calc(100vh - 82px);
display: grid;
place-items: center;
padding: 62px 24px;
background:
radial-gradient(circle at 18% 20%, rgba(200, 59, 50, .09), transparent 24%),
linear-gradient(125deg, #fffaf0, #f7efe2 54%, #edf4ef);
}
/* 认证类页面共用祖堂背景,登录、注册、找回密码只保留自己的差异样式 */
.auth-scenic-layout {
position: relative;
isolation: isolate;
min-height: 100vh;
padding: 56px 24px 72px;
place-items: center;
background:
linear-gradient(90deg, rgba(255, 253, 248, .86), rgba(255, 253, 248, .46) 45%, rgba(255, 253, 248, .88)),
linear-gradient(135deg, rgba(200, 59, 50, .08), transparent 34%),
url("../images/ancestral-hall-new.png") center / cover no-repeat;
}
.auth-scenic-layout:before {
content: "";
position: absolute;
inset: 0;
z-index: -1;
background:
linear-gradient(180deg, rgba(251, 247, 237, .42), rgba(251, 247, 237, .86)),
radial-gradient(circle at 50% 42%, rgba(255, 253, 248, .3), transparent 30%);
backdrop-filter: blur(2px);
}
.auth-page-brand {
position: absolute;
left: clamp(18px, 4vw, 56px);
top: clamp(18px, 4vw, 42px);
z-index: 3;
}
/* 认证卡片公共样式,避免登录、注册、找回密码页面重复维护 */
.auth-card {
width: min(440px, 100%);
padding: 38px;
border-radius: 16px;
border-color: rgba(224, 211, 189, .9);
background: rgba(255, 253, 248, .94);
box-shadow: 0 26px 70px rgba(57, 48, 36, .16);
}
.auth-card:hover {
transform: none;
}
.auth-card h1 {
margin-bottom: 12px;
font-size: 40px;
}
.auth-card .lead {
margin-bottom: 28px;
font-size: 16px;
}
.auth-card .form-grid {
gap: 14px;
}
.auth-card .input {
height: 50px;
background: #fffaf1;
}
.auth-card .btn.primary,
.auth-card .btn.ghost {
position: relative;
overflow: hidden;
height: 50px;
}
.auth-card .btn.primary {
width: 100%;
margin-top: 2px;
}
.auth-card .btn.primary:before,
.auth-card .btn.ghost:before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: -45%;
width: 38%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .34), transparent);
transform: skewX(-18deg);
transition: left .48s ease;
}
.auth-card .btn.primary:hover,
.auth-card .btn.ghost:hover {
transform: translateY(-3px) scale(1.012);
}
.auth-card .btn.primary:hover {
box-shadow: 0 18px 42px rgba(200, 59, 50, .34);
}
.auth-card .btn.ghost:hover {
color: #fff;
border-color: rgba(200, 59, 50, .2);
background: var(--red);
box-shadow: 0 16px 34px rgba(200, 59, 50, .2);
}
.auth-card .btn.primary:hover:before,
.auth-card .btn.ghost:hover:before {
left: 110%;
}
.auth-card .btn.primary:active,
.auth-card .btn.ghost:active {
transform: translateY(-1px) scale(.99);
}
.auth-links {
display: flex;
justify-content: space-between;
gap: 16px;
color: #8a5a3b;
font-weight: 800;
}
.auth-links a {
transition: color .22s ease, transform .22s ease;
}
.auth-links a:hover {
color: var(--red);
transform: translateY(-1px);
}
.auth-captcha-box {
/* 认证页滑动验证挂载点,TAC 内部 DOM 由第三方库生成 */
display: flex;
justify-content: center;
}
.auth-captcha-box:empty {
display: none;
}
.auth-captcha-box #tianai-captcha-parent {
max-width: 100%;
}
.auth-captcha-modal {
/* 认证页滑动验证弹窗挂载点,TAC 内部 DOM 由第三方库生成 */
position: fixed;
inset: 0;
z-index: 1000;
display: grid;
place-items: center;
padding: 24px;
background: rgba(38, 48, 44, .32);
backdrop-filter: blur(4px);
}
.auth-captcha-modal:empty {
display: none;
}
.auth-captcha-modal #tianai-captcha-parent {
max-width: min(318px, calc(100vw - 32px));
max-height: calc(100vh - 32px);
}
/* 验证码输入和按钮的通用双列布局,窄屏在页面 CSS 中改为单列 */
.code-row {
display: grid;
grid-template-columns: 1fr auto;
gap: 12px;
}
/* 接口请求期间的公共状态,只由 JS 切换 class,不在 JS 内写样式 */
.is-loading {
pointer-events: none;
opacity: .72;
}
/* 接口列表为空时的统一占位样式 */
.api-empty {
padding: 22px;
border: 1px dashed var(--line);
border-radius: 14px;
color: var(--muted);
background: rgba(255, 253, 248, .7);
text-align: center;
font-weight: 800;
}
.footer {
padding: 60px 0 36px;
color: rgba(255, 255, 255, .78);
background: #3f3a35;
}
.footer-grid {
display: grid;
grid-template-columns: 1.2fr repeat(3, 1fr);
gap: 48px;
padding-bottom: 38px;
border-bottom: 1px solid rgba(255, 255, 255, .12);
}
.footer h4 {
margin: 0 0 18px;
color: #fff;
font-size: 17px;
}
.footer p,
.footer a {
display: block;
color: inherit;
margin: 10px 0;
line-height: 1.8;
}
.footer a {
width: fit-content;
transition: color .22s ease, transform .22s ease;
}
.footer a:hover {
color: #fff;
transform: translateX(4px);
}
.footer .brand {
padding: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
}
.footer .brand:hover {
transform: translateX(0) translateY(-2px);
}
.copyright,
.footer-legal {
padding-top: 28px;
color: rgba(255, 255, 255, .48);
font-size: 14px;
}
.footer-legal {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 14px;
flex-wrap: wrap;
text-align: left;
}
.footer-legal a {
display: inline-flex;
width: auto;
margin: 0;
color: inherit;
font-size: inherit;
line-height: inherit;
}
.footer-legal a:hover {
color: #fff;
transform: none;
}
.profile-layer-confirm {
border-radius: 8px !important;
overflow: hidden;
}
.profile-layer-confirm .layui-layer-title {
height: 50px;
line-height: 50px;
border-bottom: 1px solid rgba(224, 211, 189, .72);
background: #fffaf0;
color: var(--ink);
font-weight: 900;
}
.profile-layer-confirm .layui-layer-content {
color: var(--muted);
line-height: 1.8;
}
.profile-layer-confirm .layui-layer-btn a {
border-radius: 999px;
}
.profile-layer-confirm .layui-layer-btn .layui-layer-btn0 {
border-color: var(--red);
background: var(--red);
}
.pending-layer-dialog {
border-radius: 8px !important;
overflow: hidden;
box-shadow: 0 20px 60px rgba(38, 30, 21, .18) !important;
}
.pending-layer-dialog .layui-layer-title {
height: 52px;
line-height: 52px;
border-bottom: 1px solid rgba(224, 211, 189, .72);
background: #fffaf0;
color: var(--ink);
font-weight: 900;
font-size: 16px;
}
.pending-layer-dialog .layui-layer-content {
padding: 28px 28px 16px !important;
color: var(--ink);
font-size: 16px;
line-height: 1.8;
text-align: center;
}
.pending-layer-dialog .layui-layer-btn {
padding: 0 24px 24px;
text-align: center;
}
.pending-layer-dialog .layui-layer-btn a {
height: 38px;
min-width: 108px;
padding: 0 24px;
border-radius: 999px;
line-height: 38px;
font-weight: 800;
}
.pending-layer-dialog .layui-layer-btn .layui-layer-btn0 {
border-color: var(--red);
background: var(--red);
}
.profile-popup-select {
display: grid;
gap: 10px;
padding: 4px 0;
}
.profile-popup-select button {
width: 100%;
min-height: 42px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fffdf8;
color: var(--ink);
font: inherit;
font-weight: 900;
cursor: pointer;
transition: border-color .2s ease, color .2s ease, background .2s ease;
}
.profile-popup-select button:hover {
border-color: rgba(200, 59, 50, .38);
color: var(--red);
background: #fff4ef;
}
@media (max-width: 1120px) {
.nav-links {
display: none;
}
.inner-hero .container,
.grid-2 {
grid-template-columns: 1fr;
}
.grid-3,
.grid-4 {
grid-template-columns: repeat(2, 1fr);
}
.footer-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 720px) {
.container {
width: calc(100% - 28px);
}
.nav-actions a:not(.btn) {
display: none;
}
.inner-hero .container {
padding: 46px 0 54px;
}
h1 {
font-size: 38px;
}
h2 {
font-size: 32px;
}
.grid-3,
.grid-4,
.footer-grid {
grid-template-columns: 1fr;
}
.auth-scenic-layout {
padding: 36px 14px 48px;
align-items: start;
}
.auth-card {
padding: 28px 22px;
}
.auth-card h1 {
font-size: 34px;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*:before,
*:after {
animation-duration: .01ms !important;
animation-iteration-count: 1 !important;
scroll-behavior: auto !important;
transition-duration: .01ms !important;
}
}
+10
View File
@@ -0,0 +1,10 @@
/* 注册页只保留单页差异,公共认证卡片样式在 public.css */
.page-register .auth-links {
justify-content: center;
}
@media (max-width: 720px) {
.page-register .auth-layout {
align-items: start;
}
}
+119
View File
@@ -0,0 +1,119 @@
.result-hero .container {
min-height: 420px;
padding-top: 74px;
padding-bottom: 74px;
}
.hero-copy {
position: relative;
padding-left: 28px;
}
.hero-copy:before {
content: "";
position: absolute;
left: 0;
top: 58px;
width: 4px;
height: 132px;
border-radius: 999px;
background: linear-gradient(180deg, var(--red), var(--gold));
}
.result-search {
display: grid;
gap: 18px;
align-content: center;
}
.result-search .toolbar {
padding: 8px;
border: 1px solid rgba(224, 211, 189, .78);
border-radius: 999px;
background: rgba(255, 250, 241, .82);
}
.result-search .input {
flex: 1 1 260px;
border: 0;
background: transparent;
}
.result-list {
display: grid;
gap: 18px;
max-width: 920px;
margin: 0 auto;
}
.result-card {
position: relative;
overflow: hidden;
padding: 26px;
border: 1px solid var(--line);
border-radius: 18px;
background: rgba(255, 253, 248, .82);
box-shadow: 0 18px 48px rgba(57, 48, 36, .07);
transition: transform .24s ease, border-color .24s ease, box-shadow .24s ease;
}
.result-card:after {
content: "";
position: absolute;
right: -70px;
bottom: -80px;
width: 180px;
height: 180px;
border-radius: 50%;
background: radial-gradient(circle, rgba(196, 146, 69, .13), transparent 68%);
transition: transform .28s ease;
}
.result-card:hover {
border-color: rgba(196, 146, 69, .42);
box-shadow: 0 24px 60px rgba(57, 48, 36, .11);
}
.result-card:hover:after {
transform: scale(1.2);
}
.result-card span {
display: inline-flex;
margin-bottom: 12px;
padding: 7px 12px;
border-radius: 999px;
color: var(--green-dark);
background: #e8f0eb;
font-weight: 800;
font-size: 13px;
}
.result-card p {
margin: 0;
color: var(--muted);
line-height: 1.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;
}
.tilt-card:hover {
--lift: -6px;
}
@media (max-width: 720px) {
.hero-copy {
padding-left: 0;
}
.hero-copy:before {
display: none;
}
.result-search .toolbar {
border-radius: 18px;
}
}
+111
View File
@@ -0,0 +1,111 @@
.ticket-hero .container {
min-height: 420px;
padding-top: 74px;
padding-bottom: 74px;
}
.hero-copy {
position: relative;
padding-left: 28px;
}
.hero-copy:before {
content: "";
position: absolute;
left: 0;
top: 58px;
width: 4px;
height: 132px;
border-radius: 999px;
background: linear-gradient(180deg, var(--red), var(--gold));
}
.ticket-tip {
display: grid;
place-items: center;
text-align: center;
background: linear-gradient(145deg, var(--green-dark), var(--green));
color: #fff;
}
.ticket-tip span {
width: 88px;
height: 88px;
border-radius: 50%;
display: grid;
place-items: center;
color: var(--red);
background: rgba(255, 253, 248, .94);
font-size: 28px;
font-weight: 900;
}
.ticket-tip p {
max-width: 360px;
margin: 18px 0 0;
color: rgba(255, 255, 255, .86);
line-height: 1.8;
}
.ticket-layout {
display: grid;
grid-template-columns: minmax(0, 720px) 320px;
gap: 26px;
align-items: start;
}
.ticket-form {
max-width: none;
}
.ticket-form textarea {
min-height: 150px;
padding-top: 16px;
border-radius: 18px;
resize: vertical;
}
.ticket-side {
padding: 26px;
border: 1px solid var(--line);
border-radius: 18px;
background: rgba(255, 253, 248, .82);
box-shadow: 0 20px 54px rgba(57, 48, 36, .08);
}
.ticket-side p {
color: var(--muted);
line-height: 1.8;
}
.ticket-side a {
display: inline-flex;
margin-top: 10px;
color: var(--red);
font-weight: 800;
}
.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;
}
.tilt-card:hover {
--lift: -6px;
}
@media (max-width: 900px) {
.ticket-layout {
grid-template-columns: 1fr;
}
}
@media (max-width: 720px) {
.hero-copy {
padding-left: 0;
}
.hero-copy:before {
display: none;
}
}
+169
View File
@@ -0,0 +1,169 @@
.surname-detail-hero .container {
min-height: 440px;
padding-top: 74px;
padding-bottom: 74px;
}
.hero-copy {
position: relative;
padding-left: 28px;
}
.hero-copy:before {
content: "";
position: absolute;
left: 0;
top: 58px;
width: 4px;
height: 132px;
border-radius: 999px;
background: linear-gradient(180deg, var(--red), var(--gold));
}
.detail-actions {
display: flex;
gap: 14px;
margin-top: 30px;
flex-wrap: wrap;
}
.surname-emblem {
position: relative;
overflow: hidden;
display: grid;
place-items: center;
text-align: center;
color: #fff;
background:
radial-gradient(circle at 72% 24%, rgba(255, 255, 255, .28) 0 10%, transparent 11%),
linear-gradient(135deg, #476f63, #c49245 54%, #8f4634);
}
.surname-emblem span {
width: 112px;
height: 112px;
border-radius: 50%;
display: grid;
place-items: center;
color: var(--red);
background: rgba(255, 253, 248, .95);
font-family: "SimSun", "Songti SC", serif;
font-size: 58px;
font-weight: 800;
transition: transform .24s ease;
}
.surname-emblem:hover span {
transform: rotate(-8deg) scale(1.08);
}
.surname-emblem p {
margin: 18px 0 0;
color: rgba(255, 255, 255, .9);
font-size: 22px;
}
.surname-detail-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
.content-card,
.related-panel {
border: 1px solid var(--line);
border-radius: 18px;
background: rgba(255, 253, 248, .82);
box-shadow: 0 20px 54px rgba(57, 48, 36, .08);
}
.content-card {
padding: 30px;
}
.content-card:first-child {
grid-column: 1 / -1;
}
.content-card p {
color: var(--muted);
font-size: 17px;
line-height: 1.9;
}
.generation-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.generation-row span {
width: 54px;
height: 54px;
border-radius: 14px;
display: grid;
place-items: center;
color: #7c4a33;
background: #fbf7ed;
border: 1px solid var(--line);
font-family: "SimSun", "Songti SC", serif;
font-size: 24px;
font-weight: 800;
transition: transform .22s ease, color .22s ease, background .22s ease;
}
.generation-row span:hover {
color: #fff;
background: var(--red);
transform: translateY(-5px);
}
.related-panel {
padding: 26px;
}
.related-panel a {
display: block;
margin-top: 12px;
padding: 14px 16px;
border: 1px solid var(--line);
border-radius: 14px;
color: #6f4a37;
background: #fbf7ed;
transition: transform .22s ease, color .22s ease, background .22s ease;
}
.related-panel a:hover {
color: #fff;
background: var(--green);
transform: translateX(5px);
}
.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;
}
.tilt-card:hover {
--lift: -6px;
}
@media (max-width: 860px) {
.surname-detail-grid {
grid-template-columns: 1fr;
}
.content-card:first-child {
grid-column: auto;
}
}
@media (max-width: 720px) {
.hero-copy {
padding-left: 0;
}
.hero-copy:before {
display: none;
}
}
+229
View File
@@ -0,0 +1,229 @@
.surname-hero .container {
min-height: 440px;
padding-top: 74px;
padding-bottom: 74px;
}
.hero-copy {
position: relative;
padding-left: 28px;
}
.hero-copy:before {
content: "";
position: absolute;
left: 0;
top: 58px;
width: 4px;
height: 132px;
border-radius: 999px;
background: linear-gradient(180deg, var(--red), var(--gold));
}
.surname-stats {
display: flex;
gap: 12px;
margin-top: 28px;
flex-wrap: wrap;
}
.surname-stats span {
padding: 11px 14px;
border: 1px solid rgba(224, 211, 189, .78);
border-radius: 999px;
color: #6d766f;
background: rgba(255, 253, 248, .74);
box-shadow: 0 14px 32px rgba(72, 57, 38, .06);
}
.surname-stats b {
margin-right: 6px;
color: var(--green);
}
.surname-search-card {
position: relative;
overflow: hidden;
display: grid;
gap: 18px;
align-content: center;
}
.search-ornament {
position: absolute;
right: -72px;
top: -84px;
width: 220px;
height: 220px;
border-radius: 50%;
background: radial-gradient(circle, rgba(200, 59, 50, .13), transparent 62%);
animation: surnamePulse 4s ease-in-out infinite;
}
.surname-search-card h3,
.search-toolbar,
.hot-query {
position: relative;
z-index: 2;
}
.search-toolbar {
padding: 8px;
border: 1px solid rgba(224, 211, 189, .78);
border-radius: 999px;
background: rgba(255, 250, 241, .82);
}
.search-toolbar .input {
flex: 1 1 280px;
border: 0;
background: transparent;
}
.hot-query {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.hot-query span {
width: 38px;
height: 38px;
border-radius: 12px;
display: grid;
place-items: center;
color: #7c4a33;
background: #fbf7ed;
border: 1px solid var(--line);
font-family: "SimSun", "Songti SC", serif;
font-weight: 800;
transition: transform .22s ease, color .22s ease, background .22s ease;
}
.hot-query span:hover {
color: #fff;
background: var(--red);
transform: translateY(-4px);
}
.surname-list-section {
position: relative;
overflow: hidden;
}
.surname-list-section:before {
content: "";
position: absolute;
left: 50%;
top: 58px;
width: 520px;
height: 180px;
border-radius: 50%;
background: radial-gradient(circle, rgba(196, 146, 69, .09), transparent 68%);
transform: translateX(-50%);
}
.surname-grid {
position: relative;
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 12px;
padding: 24px;
border: 1px solid rgba(224, 211, 189, .8);
border-radius: 24px;
background: rgba(255, 253, 248, .72);
box-shadow: 0 22px 60px rgba(57, 48, 36, .08);
}
.surname-grid a {
height: 64px;
border-radius: 14px;
display: grid;
place-items: center;
color: #7c4a33;
background: #fbf7ed;
border: 1px solid var(--line);
font-family: "SimSun", "Songti SC", serif;
font-size: 24px;
font-weight: 800;
transition: transform .24s ease, color .24s ease, background .24s ease, border-color .24s ease, box-shadow .24s ease;
will-change: transform;
}
.surname-grid a:hover {
color: #fff;
background: var(--red);
border-color: rgba(200, 59, 50, .72);
box-shadow: 0 16px 34px rgba(200, 59, 50, .18);
transform: translateY(-6px) scale(1.03);
}
.surname-grid:hover a {
animation: surnameFloat 1.9s ease-in-out infinite;
}
.surname-grid:hover a:nth-child(2n) {
animation-delay: .08s;
}
.surname-grid:hover a:nth-child(3n) {
animation-delay: .14s;
}
.surname-grid:hover a:nth-child(4n) {
animation-delay: .2s;
}
.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;
}
.tilt-card:hover {
--lift: -6px;
}
@keyframes surnameFloat {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-4px);
}
}
@keyframes surnamePulse {
0%,
100% {
opacity: .5;
transform: scale(.92);
}
50% {
opacity: 1;
transform: scale(1.08);
}
}
@media (max-width: 720px) {
.hero-copy {
padding-left: 0;
}
.hero-copy:before {
display: none;
}
.search-toolbar {
border-radius: 18px;
}
.surname-grid {
grid-template-columns: repeat(4, 1fr);
padding: 14px;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

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