diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 82682d7..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,115 +0,0 @@ -# AGENTS.md -Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed. - -Tradeoff: These guidelines bias toward caution over speed. For trivial tasks, use judgment. - -1. Think Before Coding -Don't assume. Don't hide confusion. Surface tradeoffs. - -Before implementing: - -State your assumptions explicitly. If uncertain, ask. -If multiple interpretations exist, present them instead of picking silently. -If a simpler approach exists, say so. Push back when warranted. -If something is unclear, stop. Name what is confusing. Ask. - -2. Simplicity First -Minimum code that solves the problem. Nothing speculative. - -No features beyond what was asked. -No abstractions for single-use code. -No "flexibility" or "configurability" that was not requested. -No error handling for impossible scenarios. -If you write 200 lines and it could be 50, rewrite it. -Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. - -3. Surgical Changes -Touch only what you must. Clean up only your own mess. - -When editing existing code: - -Do not "improve" adjacent code, comments, or formatting. -Do not refactor things that are not broken. -Match existing style, even if you would do it differently. -If you notice unrelated dead code, mention it instead of deleting it. - -When your changes create orphans: - -Remove imports, variables, functions, files, docs, and references that YOUR changes made unused. -Do not remove pre-existing dead code unless asked. -The test: Every changed line should trace directly to the user's request. - -4. Contract Discipline -When a new contract lands, make it the only contract in the same change. - -A contract means any source-of-truth rule that code depends on: data shape, API boundary, schema, field name, config key, prompt format, file layout, runtime entrypoint, ownership boundary, or workflow. - -When introducing or changing a contract, do all three in the same round: - -Specify the single owner. - -Identify the one module, function, schema, document, or service that owns the contract. -Do not leave the same rule duplicated across frontend, backend, scripts, prompts, docs, or tests. -If multiple places need the value, they should consume it from the owner rather than redefine it. - -Delete old fields and old entrypoints. - -Remove obsolete fields, fallback reads, compatibility branches, legacy routes, stale config keys, old scripts, and old docs. -Do not keep the old path "just in case" unless the user explicitly asks for a compatibility period. -If compatibility is required, name it as temporary, define the removal condition, and keep it narrow. - -Tighten validators and runtime together. - -Update schemas, validators, tests, fixtures, seed data, docs, and runtime code in the same change. -Make invalid old inputs fail early and loudly. -Do not allow runtime behavior to accept shapes that validators reject, or validators to allow shapes that runtime no longer supports. -The rule: New owner, old path removed, validator and runtime tightened. If one is missing, the contract migration is not complete. - -5. Goal-Driven Execution -Define success criteria. Loop until verified. - -Transform tasks into verifiable goals: - -"Add validation" -> "Write tests for invalid inputs, then make them pass." -"Fix the bug" -> "Write a test that reproduces it, then make it pass." -"Refactor X" -> "Ensure tests pass before and after." -"Change a contract" -> "Identify owner, remove old paths, tighten validators and runtime, then verify old inputs fail." - -For multi-step tasks, state a brief plan: - -1. [Step] -> verify: [check] -2. [Step] -> verify: [check] -3. [Step] -> verify: [check] - -Strong success criteria let you loop independently. Weak criteria like "make it work" require clarification. - -6. Verification Before Closure -A change is not finished until the relevant behavior is checked. - -Before reporting completion: - -Run the smallest relevant test, script, typecheck, build, or manual verification. -Prefer focused verification over broad, noisy suites when the task is narrow. -If verification cannot be run, say exactly why. -If verification is only by inspection, say that clearly. -If the task changed a contract, verify both the new valid path and the old invalid path. -Do not claim success from intent. Claim success from evidence. - -7. Reporting Results -Be concise, concrete, and honest. - -When summarizing work: - -Say what changed. -Say how it was verified. -Mention any remaining risk or skipped verification. -Do not bury important caveats in vague language. -Do not over-explain routine edits. - -Good final answer shape: - -Changed: [short summary] -Verified: [command/check] -Notes: [only if needed] - -These guidelines are working if: fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, fewer hidden compatibility paths, clearer ownership of contracts, and clarifying questions happen before implementation rather than after mistakes. diff --git a/PC.openapi.json b/PC.openapi.json deleted file mode 100644 index 8b80861..0000000 --- a/PC.openapi.json +++ /dev/null @@ -1,8604 +0,0 @@ -{ - "openapi": "3.1.0", - "info": { - "title": "PC", - "description": "家谱业务 APP/PC 用户侧接口文档,用于 Apifox 导入。\n\n导入方式:Apifox -> 导入 API 数据 -> OpenAPI/Swagger -> 选择本文件。\n\n约定:\n- APP 路径前缀为 `/genealogy/app`\n- PC/H5 路径前缀为 `/genealogy/pc`\n- 后台管理接口不包含在本文件内\n- 登录后接口需要携带 token,且请求 Header 需携带 `clientid`\n", - "version": "1.0.0" - }, - "tags": [ - { - "name": "验证中心" - }, - { - "name": "认证登录" - }, - { - "name": "文件上传" - }, - { - "name": "家谱" - }, - { - "name": "家族圈" - }, - { - "name": "行政区划" - }, - { - "name": "字辈谱" - }, - { - "name": "世系人物" - }, - { - "name": "内容文章" - }, - { - "name": "相册" - }, - { - "name": "视频" - }, - { - "name": "贺礼邀约" - }, - { - "name": "祭祀" - }, - { - "name": "族务记录" - }, - { - "name": "消息通知" - } - ], - "paths": { - "/genealogy/pc/auth/verification/{operationCode}/require": { - "get": { - "summary": "查询 PC 认证动作是否需要验证", - "deprecated": false, - "description": "服务端按 operationCode 对应 PC 路由的当前激活绑定解析验证场景,前端不得提交 sceneCode。", - "tags": [ - "验证中心" - ], - "parameters": [ - { - "name": "operationCode", - "in": "path", - "description": "PC 认证业务动作", - "required": true, - "example": "", - "schema": { - "type": "string", - "enum": [ - "password-login", - "sms-login", - "register", - "forgot-password", - "phone-change", - "account-deactivate" - ] - } - }, - { - "name": "tenantId", - "in": "query", - "description": "租户ID,用于区分不同租户的数据和验证策略", - "required": true, - "example": "000000", - "schema": { - "type": "string" - } - }, - { - "name": "subject", - "in": "query", - "description": "验证主体,通常为手机号、用户ID或登录账号", - "required": false, - "example": "13800000000", - "schema": { - "type": "string" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/VerificationRequireResult", - "description": "验证需求查询结果" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/auth/verification/{operationCode}/challenge": { - "post": { - "summary": "生成 PC 认证动作验证挑战", - "deprecated": false, - "description": "服务端按 operationCode 的激活绑定解析场景。tenantId、subject 必须完整提交;clientid 仅从 Header 读取,前端不得提交 clientId 或 sceneCode。", - "tags": [ - "验证中心" - ], - "parameters": [ - { - "name": "operationCode", - "in": "path", - "description": "PC 认证业务动作", - "required": true, - "example": "", - "schema": { - "type": "string", - "enum": [ - "password-login", - "sms-login", - "register", - "forgot-password", - "phone-change", - "account-deactivate" - ] - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/VerificationChallengeBody" - }, - "example": { - "tenantId": "000000", - "subject": "13800000000" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/VerificationChallengeResult", - "description": "验证挑战生成结果" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/auth/verification/{operationCode}/verify": { - "post": { - "summary": "校验 PC 认证动作验证结果并换取 validToken", - "deprecated": false, - "description": "必须使用生成挑战时相同的 operationCode、tenantId、subject 和 challengeId。clientid 仅从 Header 读取;服务端重新解析同一路由的激活场景,前端不得提交 clientId 或 sceneCode。", - "tags": [ - "验证中心" - ], - "parameters": [ - { - "name": "operationCode", - "in": "path", - "description": "PC 认证业务动作", - "required": true, - "example": "", - "schema": { - "type": "string", - "enum": [ - "password-login", - "sms-login", - "register", - "forgot-password", - "phone-change", - "account-deactivate" - ] - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/VerificationCheckBody" - }, - "examples": { - "tianai_behavior": { - "value": { - "tenantId": "000000", - "subject": "13800000000", - "challengeId": "CAPTCHA_CHALLENGE_ID", - "providerCode": "tianai", - "captchaType": "SLIDER", - "payload": { - "track": { - "bgImageWidth": 340, - "bgImageHeight": 180, - "templateImageWidth": 50, - "templateImageHeight": 50, - "startTime": 1720000000000, - "stopTime": 1720000001500, - "left": 120, - "top": 0, - "trackList": [ - { - "x": 0, - "y": 0, - "t": 0, - "type": "down" - }, - { - "x": 120, - "y": 0, - "t": 650, - "type": "move" - }, - { - "x": 120, - "y": 0, - "t": 700, - "type": "up" - } - ] - } - } - }, - "summary": "天爱滑块/旋转/滑动还原/文字点选验证" - }, - "system_image": { - "value": { - "tenantId": "000000", - "subject": "13800000000", - "challengeId": "CAPTCHA_CHALLENGE_ID", - "providerCode": "ruoyi_image", - "captchaType": "math", - "payload": { - "uuid": "captcha-uuid", - "code": "6" - } - }, - "summary": "系统图形验证码" - } - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/VerificationCheckResult", - "description": "验证校验结果" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/auth/register": { - "post": { - "summary": "PC 用户注册", - "deprecated": false, - "description": "", - "tags": [ - "认证登录" - ], - "parameters": [ - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PasswordRegisterBody" - }, - "example": { - "grantType": "password", - "tenantId": "000000", - "phone": "13800000000", - "password": "e10adc3949ba59abbe56e057f20f883e", - "nickName": "测试用户", - "registerSource": "app", - "smsCode": "1234" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/LoginResult", - "description": "登录结果" - }, - "400": { - "description": "短信验证码必须为 4 位数字;验证码错误、过期、重复使用,或租户、客户端、场景不匹配时拒绝;携带 validToken、code、uuid、供应商配置等旧字段返回 HTTP 400。", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RVoid" - } - } - }, - "headers": {} - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/auth/login": { - "post": { - "summary": "PC 密码登录", - "deprecated": false, - "description": "", - "tags": [ - "认证登录" - ], - "parameters": [ - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PasswordLoginBody" - }, - "example": { - "grantType": "password", - "tenantId": "000000", - "phone": "13800000000", - "password": "e10adc3949ba59abbe56e057f20f883e", - "validToken": "valid-token-from-captcha-verify" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/LoginResult", - "description": "登录结果" - }, - "400": { - "description": "后端按 PC_PASSWORD_LOGIN 路由的当前激活绑定解析场景;该场景策略要求验证时必须提交匹配的 validToken,不要求验证时 validToken 可为空;请求携带已废弃的 sceneCode 或其他未知字段返回 HTTP 400。", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RVoid" - } - } - }, - "headers": {} - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/auth/login/sms": { - "post": { - "summary": "PC 短信登录", - "deprecated": false, - "description": "", - "tags": [ - "认证登录" - ], - "parameters": [ - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SmsLoginBody" - }, - "example": { - "grantType": "sms", - "tenantId": "000000", - "phone": "13800000000", - "smsCode": "1234" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/LoginResult", - "description": "登录结果" - }, - "400": { - "description": "短信验证码必须为 4 位数字;验证码错误、过期、重复使用,或租户、客户端、场景不匹配时拒绝;携带 validToken、code、uuid、供应商配置等旧字段返回 HTTP 400。", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RVoid" - } - } - }, - "headers": {} - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/auth/sms/{operationCode}/code": { - "post": { - "summary": "PC 发送短信验证码", - "deprecated": false, - "description": "operationCode 决定短信业务动作,服务端按对应 PC 路由的激活绑定解析验证场景。策略开启时需提交 validToken,关闭时可为空;前端不得提交 sceneCode 或短信供应商配置 ID。", - "tags": [ - "认证登录" - ], - "parameters": [ - { - "name": "operationCode", - "in": "path", - "description": "支持发送短信验证码的 PC 认证业务动作", - "required": true, - "example": "", - "schema": { - "type": "string", - "enum": [ - "sms-login", - "register", - "forgot-password", - "phone-change", - "account-deactivate" - ] - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SmsCodeBody" - }, - "example": { - "tenantId": "000000", - "phone": "13800000000", - "grantType": "sms", - "validToken": "captcha-ticket" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - }, - "400": { - "description": "拒绝原因:票据缺失;票据与租户不匹配(错租户);票据与客户端不匹配(错客户端);票据与场景不匹配(错场景);票据过期;票据已被使用(重复使用);手机号主体不匹配;请求携带供应商配置或历史字段。", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RVoid" - } - } - }, - "headers": {} - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/auth/profile": { - "get": { - "summary": "PC 当前用户资料", - "deprecated": false, - "description": "", - "tags": [ - "认证登录" - ], - "parameters": [ - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "put": { - "summary": "PC 修改用户资料", - "deprecated": false, - "description": "", - "tags": [ - "认证登录" - ], - "parameters": [ - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProfileUpdateBody" - }, - "example": { - "nickName": "张三", - "realName": "张三", - "avatar": "2060000000000000000", - "sex": "0", - "birthday": "1990-01-01", - "email": "zhangsan@example.com" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/auth/password": { - "put": { - "summary": "PC 修改密码", - "deprecated": false, - "description": "", - "tags": [ - "认证登录" - ], - "parameters": [ - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PasswordChangeBody" - }, - "example": { - "oldPassword": "e10adc3949ba59abbe56e057f20f883e", - "newPassword": "25d55ad283aa400af464c76d713c07ad" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/auth/password/reset": { - "put": { - "summary": "PC 找回密码", - "deprecated": false, - "description": "", - "tags": [ - "认证登录" - ], - "parameters": [ - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PasswordResetBody" - }, - "example": { - "grantType": "password", - "tenantId": "000000", - "phone": "13800000000", - "smsCode": "1234", - "newPassword": "25d55ad283aa400af464c76d713c07ad" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - }, - "400": { - "description": "短信验证码必须为 4 位数字;验证码错误、过期、重复使用,或租户、客户端、场景不匹配时拒绝;携带 validToken、code、uuid、供应商配置等旧字段返回 HTTP 400。", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RVoid" - } - } - }, - "headers": {} - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/auth/phone": { - "put": { - "summary": "PC 换绑手机号", - "deprecated": false, - "description": "", - "tags": [ - "认证登录" - ], - "parameters": [ - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PhoneChangeBody" - }, - "example": { - "phone": "13900000000", - "smsCode": "1234" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - }, - "400": { - "description": "短信验证码必须为 4 位数字;验证码错误、过期、重复使用,或租户、客户端、场景不匹配时拒绝;携带 validToken、code、uuid、供应商配置等旧字段返回 HTTP 400。", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RVoid" - } - } - }, - "headers": {} - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/auth/account/deactivate": { - "post": { - "summary": "PC 注销账号", - "deprecated": false, - "description": "", - "tags": [ - "认证登录" - ], - "parameters": [ - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AccountDeactivateBody" - }, - "example": { - "smsCode": "1234" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - }, - "400": { - "description": "短信验证码必须为 4 位数字;验证码错误、过期、重复使用,或租户、客户端、场景不匹配时拒绝;携带 validToken、code、uuid、供应商配置等旧字段返回 HTTP 400。", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RVoid" - } - } - }, - "headers": {} - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/auth/logout": { - "delete": { - "summary": "PC 退出登录", - "deprecated": false, - "description": "", - "tags": [ - "认证登录" - ], - "parameters": [ - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/files/resumable/init": { - "post": { - "summary": "PC 分片上传初始化", - "deprecated": false, - "description": "所有文件统一使用分片上传;普通小文件可设置为一个分片。返回 instant=true 时直接使用返回的 OSS 信息,跳过分片上传和合并。", - "tags": [ - "文件上传" - ], - "parameters": [ - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResumableInitBody" - }, - "example": { - "uploadId": "UPLOAD202607090001", - "fileName": "cover.jpg", - "fileMd5": "d41d8cd98f00b204e9800998ecf8427e", - "totalSize": 9532, - "totalChunks": 1, - "chunkSize": 4194304, - "contentType": "image/jpeg" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/files/resumable/chunk": { - "post": { - "summary": "PC 上传分片", - "deprecated": false, - "description": "uploadId 已绑定初始化契约。除最后一片外,分片实际大小必须等于 chunkSize;最后一片必须等于剩余字节数。", - "tags": [ - "文件上传" - ], - "parameters": [ - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "properties": { - "uploadId": { - "type": "string", - "example": "" - }, - "chunkIndex": { - "type": "integer", - "examples": [ - 0 - ], - "example": 0 - }, - "chunkMd5": { - "type": "string", - "examples": [ - "d41d8cd98f00b204e9800998ecf8427e" - ], - "example": "d41d8cd98f00b204e9800998ecf8427e" - }, - "file": { - "type": "string", - "format": "binary", - "example": "" - } - }, - "required": [ - "uploadId", - "chunkIndex", - "chunkMd5", - "file" - ] - }, - "example": { - "uploadId": "", - "chunkIndex": 0, - "chunkMd5": "d41d8cd98f00b204e9800998ecf8427e", - "file": "" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/files/resumable/complete": { - "post": { - "summary": "PC 完成分片上传", - "deprecated": false, - "description": "", - "tags": [ - "文件上传" - ], - "parameters": [ - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResumableCompleteBody" - }, - "example": { - "uploadId": "UPLOAD202607090001", - "fileName": "cover.jpg", - "fileMd5": "d41d8cd98f00b204e9800998ecf8427e", - "totalSize": 9532, - "totalChunks": 1 - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/FileUploadResult", - "description": "文件上传结果" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/quota": { - "get": { - "summary": "PC 查询当前用户家谱配额", - "deprecated": false, - "description": "查询已创建和已加入的家谱数量、上限、剩余额度及是否还能继续操作。加入总数包含自己创建的家谱;limit 和 remaining 为 -1 时表示不限。", - "tags": [ - "家谱" - ], - "parameters": [ - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/GenealogyQuotaResult", - "description": "当前业务用户家谱配额响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/feeds": { - "get": { - "summary": "PC 家族圈动态列表", - "deprecated": false, - "description": "", - "tags": [ - "家族圈" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/FamilyFeedListResult", - "description": "家族圈动态列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "post": { - "summary": "PC 发布家族圈动态", - "deprecated": false, - "description": "", - "tags": [ - "家族圈" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FamilyFeedBody" - }, - "example": { - "feedType": "text", - "feedContent": "今天上传一张老照片。", - "mediaOssIds": "2060000000000000001,2060000000000000002", - "sortOrder": 0, - "status": "0" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/FamilyFeedResult", - "description": "家族圈动态对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/feeds/page": { - "get": { - "summary": "PC 家族圈动态分页", - "deprecated": false, - "description": "", - "tags": [ - "家族圈" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "pageNum", - "in": "query", - "description": "页码,从1开始", - "required": false, - "schema": { - "type": "integer", - "default": 1 - } - }, - { - "name": "pageSize", - "in": "query", - "description": "每页条数", - "required": false, - "schema": { - "type": "integer", - "default": 10 - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/FamilyFeedPageResult", - "description": "家族圈动态分页响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/feeds/{feedId}": { - "get": { - "summary": "PC 家族圈动态详情", - "deprecated": false, - "description": "", - "tags": [ - "家族圈" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "feedId", - "in": "path", - "description": "家族动态ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/FamilyFeedResult", - "description": "家族圈动态对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "put": { - "summary": "PC 修改家族圈动态", - "deprecated": false, - "description": "", - "tags": [ - "家族圈" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "feedId", - "in": "path", - "description": "家族动态ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FamilyFeedBody" - }, - "example": { - "feedType": "text", - "feedContent": "今天上传一张老照片。", - "mediaOssIds": "2060000000000000001,2060000000000000002", - "sortOrder": 0, - "status": "0" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/FamilyFeedResult", - "description": "家族圈动态对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "delete": { - "summary": "PC 删除家族圈动态", - "deprecated": false, - "description": "", - "tags": [ - "家族圈" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "feedId", - "in": "path", - "description": "家族动态ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/feeds/{feedId}/likes": { - "post": { - "summary": "PC 点赞动态", - "deprecated": false, - "description": "", - "tags": [ - "家族圈" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "feedId", - "in": "path", - "description": "家族动态ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "delete": { - "summary": "PC 取消点赞动态", - "deprecated": false, - "description": "", - "tags": [ - "家族圈" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "feedId", - "in": "path", - "description": "家族动态ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/feeds/{feedId}/comments": { - "get": { - "summary": "PC 动态一级评论列表", - "deprecated": false, - "description": "仅返回正常展示的一级评论;每条评论的 replyCount 为正常展示的直接回复数量。", - "tags": [ - "家族圈" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "feedId", - "in": "path", - "description": "家族动态ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/FamilyFeedCommentListResult", - "description": "家族圈评论列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "post": { - "summary": "PC 评论动态", - "deprecated": false, - "description": "", - "tags": [ - "家族圈" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "feedId", - "in": "path", - "description": "家族动态ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FamilyFeedCommentBody" - }, - "example": { - "parentCommentId": null, - "commentContent": "这张照片很有年代感。" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/FamilyFeedCommentResult", - "description": "家族圈评论对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/feeds/{feedId}/comments/page": { - "get": { - "summary": "PC 动态一级评论分页", - "deprecated": false, - "description": "仅返回正常展示的一级评论;展开回复时请调用 replies 接口。", - "tags": [ - "家族圈" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "feedId", - "in": "path", - "description": "家族动态ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "pageNum", - "in": "query", - "description": "页码,从1开始", - "required": false, - "schema": { - "type": "integer", - "default": 1 - } - }, - { - "name": "pageSize", - "in": "query", - "description": "每页条数", - "required": false, - "schema": { - "type": "integer", - "default": 10 - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/FamilyFeedCommentPageResult", - "description": "家族圈评论分页响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/feeds/{feedId}/comments/{commentId}/replies": { - "get": { - "summary": "PC 获取评论的直接回复列表", - "deprecated": false, - "description": "仅返回指定正常评论的正常展示直接回复;回复的 replyCount 可用于继续按需展开下一层。", - "tags": [ - "家族圈" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "feedId", - "in": "path", - "description": "家族动态ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "commentId", - "in": "path", - "description": "父评论 ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/FamilyFeedCommentListResult", - "description": "家族圈评论列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/feeds/{feedId}/comments/{commentId}/replies/page": { - "get": { - "summary": "PC 分页获取评论的直接回复", - "deprecated": false, - "description": "", - "tags": [ - "家族圈" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "feedId", - "in": "path", - "description": "家族动态ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "commentId", - "in": "path", - "description": "父评论 ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "pageNum", - "in": "query", - "description": "页码,从1开始", - "required": false, - "schema": { - "type": "integer", - "default": 1 - } - }, - { - "name": "pageSize", - "in": "query", - "description": "每页条数", - "required": false, - "schema": { - "type": "integer", - "default": 10 - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/FamilyFeedCommentPageResult", - "description": "家族圈评论分页响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/feeds/{feedId}/comments/{commentId}": { - "delete": { - "summary": "PC 删除本人动态评论;有回复时保留删除占位", - "deprecated": false, - "description": "", - "tags": [ - "家族圈" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "feedId", - "in": "path", - "description": "家族动态ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "commentId", - "in": "path", - "description": "评论ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/region/children": { - "get": { - "summary": "查询下级行政区划", - "deprecated": false, - "description": "", - "tags": [ - "行政区划" - ], - "parameters": [ - { - "name": "parentCode", - "in": "query", - "description": "父级行政区划编码,不传或传0查省级", - "required": false, - "example": "51", - "schema": { - "type": "string" - } - }, - { - "name": "clientid", - "in": "header", - "description": "客户端ID", - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string", - "default": "ced7e5f0498645c6ec642dcf450b036f" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ListResult", - "description": "通用列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/region/path/{regionCode}": { - "get": { - "summary": "查询行政区划路径", - "deprecated": false, - "description": "", - "tags": [ - "行政区划" - ], - "parameters": [ - { - "name": "regionCode", - "in": "path", - "description": "行政区划编码", - "required": true, - "example": "510000", - "schema": { - "type": "string" - } - }, - { - "name": "clientid", - "in": "header", - "description": "客户端ID", - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string", - "default": "ced7e5f0498645c6ec642dcf450b036f" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ListResult", - "description": "通用列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/region/search": { - "get": { - "summary": "搜索行政区划", - "deprecated": false, - "description": "", - "tags": [ - "行政区划" - ], - "parameters": [ - { - "name": "keyword", - "in": "query", - "description": "搜索关键词,支持地区名称或编码模糊查询", - "required": true, - "example": "北京", - "schema": { - "type": "string" - } - }, - { - "name": "level", - "in": "query", - "description": "行政区划级别,1省、2市、3区县、4乡镇街道、5村社区", - "required": false, - "example": 3, - "schema": { - "type": "integer" - } - }, - { - "name": "limit", - "in": "query", - "description": "返回数量上限", - "required": false, - "example": 20, - "schema": { - "type": "integer" - } - }, - { - "name": "clientid", - "in": "header", - "description": "客户端ID", - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string", - "default": "ced7e5f0498645c6ec642dcf450b036f" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ListResult", - "description": "通用列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/region/{regionCode}": { - "get": { - "summary": "查询行政区划详情", - "deprecated": false, - "description": "", - "tags": [ - "行政区划" - ], - "parameters": [ - { - "name": "regionCode", - "in": "path", - "description": "行政区划编码", - "required": true, - "example": "510000", - "schema": { - "type": "string" - } - }, - { - "name": "clientid", - "in": "header", - "description": "客户端ID", - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string", - "default": "ced7e5f0498645c6ec642dcf450b036f" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/region/children": { - "get": { - "summary": "查询下级行政区划", - "deprecated": false, - "description": "", - "tags": [ - "行政区划" - ], - "parameters": [ - { - "name": "parentCode", - "in": "query", - "description": "父级行政区划编码,不传或传0查省级", - "required": false, - "example": "51", - "schema": { - "type": "string" - } - }, - { - "name": "clientid", - "in": "header", - "description": "客户端ID", - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string", - "default": "ced7e5f0498645c6ec642dcf450b036f" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ListResult", - "description": "通用列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/region/path/{regionCode}": { - "get": { - "summary": "查询行政区划路径", - "deprecated": false, - "description": "", - "tags": [ - "行政区划" - ], - "parameters": [ - { - "name": "regionCode", - "in": "path", - "description": "行政区划编码", - "required": true, - "example": "510000", - "schema": { - "type": "string" - } - }, - { - "name": "clientid", - "in": "header", - "description": "客户端ID", - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string", - "default": "ced7e5f0498645c6ec642dcf450b036f" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ListResult", - "description": "通用列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/region/search": { - "get": { - "summary": "搜索行政区划", - "deprecated": false, - "description": "", - "tags": [ - "行政区划" - ], - "parameters": [ - { - "name": "keyword", - "in": "query", - "description": "搜索关键词,支持地区名称或编码模糊查询", - "required": true, - "example": "北京", - "schema": { - "type": "string" - } - }, - { - "name": "level", - "in": "query", - "description": "行政区划级别,1省、2市、3区县、4乡镇街道、5村社区", - "required": false, - "example": 3, - "schema": { - "type": "integer" - } - }, - { - "name": "limit", - "in": "query", - "description": "返回数量上限", - "required": false, - "example": 20, - "schema": { - "type": "integer" - } - }, - { - "name": "clientid", - "in": "header", - "description": "客户端ID", - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string", - "default": "ced7e5f0498645c6ec642dcf450b036f" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ListResult", - "description": "通用列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/region/{regionCode}": { - "get": { - "summary": "查询行政区划详情", - "deprecated": false, - "description": "", - "tags": [ - "行政区划" - ], - "parameters": [ - { - "name": "regionCode", - "in": "path", - "description": "行政区划编码", - "required": true, - "example": "510000", - "schema": { - "type": "string" - } - }, - { - "name": "clientid", - "in": "header", - "description": "客户端ID", - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string", - "default": "ced7e5f0498645c6ec642dcf450b036f" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/generation-poems": { - "get": { - "summary": "PC 查询正常字辈谱", - "deprecated": false, - "description": "对可查看家谱的业务用户开放;仅返回正常状态字辈,供世系人物录入和展示使用。", - "tags": [ - "字辈谱" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/GenerationPoemListResult", - "description": "字辈列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "post": { - "summary": "PC 新增字辈", - "deprecated": false, - "description": "", - "tags": [ - "字辈谱" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GenerationPoemBody" - }, - "example": { - "generationNo": 5, - "generationText": "忠", - "description": "第五世字辈", - "sortOrder": 5, - "status": "0" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/GenerationPoemResult", - "description": "字辈详情响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/generation-poems/management": { - "get": { - "summary": "PC 查询字辈维护列表", - "deprecated": false, - "description": "仅家谱内容编辑者可访问,返回正常和停用字辈,用于恢复、纠错和排序调整。", - "tags": [ - "字辈谱" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/GenerationPoemListResult", - "description": "字辈列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/generation-poems/batch/preview": { - "post": { - "summary": "PC 批量字辈预览", - "deprecated": false, - "description": "", - "tags": [ - "字辈谱" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GenerationPoemBatchBody" - }, - "example": { - "poemText": "德承家亦\n忠厚传芳", - "disableMissing": false - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/GenerationPoemBatchPreviewResult", - "description": "字辈批量预览响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/generation-poems/batch/save": { - "post": { - "summary": "PC 批量保存字辈", - "deprecated": false, - "description": "保存时会重新按当前数据生成差异;停用不删除历史字辈记录。", - "tags": [ - "字辈谱" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GenerationPoemBatchBody" - }, - "example": { - "poemText": "德承家亦\n忠厚传芳", - "disableMissing": false - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/generation-poems/{poemId}": { - "put": { - "summary": "PC 修改、停用或恢复字辈", - "deprecated": false, - "description": "", - "tags": [ - "字辈谱" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "poemId", - "in": "path", - "description": "字辈记录ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GenerationPoemBody" - }, - "example": { - "generationNo": 5, - "generationText": "忠", - "description": "第五世字辈", - "sortOrder": 5, - "status": "0" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/GenerationPoemResult", - "description": "字辈详情响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/lineage/persons": { - "get": { - "summary": "PC 世系人物列表", - "deprecated": false, - "description": "", - "tags": [ - "世系人物" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/LineagePersonListResult", - "description": "世系人物列表或选项响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "post": { - "summary": "PC 新增世系人物", - "deprecated": false, - "description": "", - "tags": [ - "世系人物" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LineagePersonBody" - }, - "example": { - "bindingMode": "NONE", - "personNo": "P202607090001", - "name": "彭德明", - "aliasName": "德明", - "sex": "0", - "generation": 1, - "generationName": "德", - "avatarOssId": "2060000000000000001", - "birthDate": "1950-01-01", - "birthLunar": "0", - "birthPlace": "江西吉安", - "personStatus": "0", - "biography": "第一世人物简介", - "sortOrder": 1 - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/LineagePersonResult", - "description": "世系人物详情响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/lineage/persons/page": { - "get": { - "summary": "PC 世系人物分页", - "deprecated": false, - "description": "", - "tags": [ - "世系人物" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "pageNum", - "in": "query", - "description": "页码,从1开始", - "required": false, - "schema": { - "type": "integer", - "default": 1 - } - }, - { - "name": "pageSize", - "in": "query", - "description": "每页条数", - "required": false, - "schema": { - "type": "integer", - "default": 10 - } - }, - { - "name": "keyword", - "in": "query", - "description": "姓名、别名或人物编号关键词", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "generation", - "in": "query", - "description": "世代序号", - "required": false, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "personStatus", - "in": "query", - "description": "字典 gen_lineage_person_status:0=健在,1=已故,2=未知", - "required": false, - "schema": { - "type": "string", - "enum": [ - "0", - "1", - "2" - ], - "x-dict-type": "gen_lineage_person_status", - "examples": [ - "0" - ] - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/LineagePersonPageResult", - "description": "世系人物分页响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/lineage/persons/options": { - "get": { - "summary": "PC 世系人物选项", - "deprecated": false, - "description": "", - "tags": [ - "世系人物" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "keyword", - "in": "query", - "description": "姓名、别名或人物编号关键词", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/LineagePersonListResult", - "description": "世系人物列表或选项响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/lineage/tree": { - "get": { - "summary": "PC 世系树", - "deprecated": false, - "description": "", - "tags": [ - "世系人物" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/LineagePersonTreeResult", - "description": "世系树响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/lineage/persons/{personId}": { - "get": { - "summary": "PC 世系人物详情", - "deprecated": false, - "description": "", - "tags": [ - "世系人物" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "personId", - "in": "path", - "description": "世系人物ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/LineagePersonResult", - "description": "世系人物详情响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "put": { - "summary": "PC 修改世系人物", - "deprecated": false, - "description": "", - "tags": [ - "世系人物" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "personId", - "in": "path", - "description": "世系人物ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LineagePersonBody" - }, - "example": { - "bindingMode": "NONE", - "personNo": "P202607090001", - "name": "彭德明", - "aliasName": "德明", - "sex": "0", - "generation": 1, - "generationName": "德", - "avatarOssId": "2060000000000000001", - "birthDate": "1950-01-01", - "birthLunar": "0", - "birthPlace": "江西吉安", - "personStatus": "0", - "biography": "第一世人物简介", - "sortOrder": 1 - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/LineagePersonResult", - "description": "世系人物详情响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "delete": { - "summary": "PC 停用世系人物", - "deprecated": false, - "description": "逻辑停用人物,不执行物理删除;存在正常子女时拒绝停用。", - "tags": [ - "世系人物" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "personId", - "in": "path", - "description": "世系人物ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/lineage/persons/{personId}/children": { - "post": { - "summary": "PC 给人物添加子女", - "deprecated": false, - "description": "", - "tags": [ - "世系人物" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "personId", - "in": "path", - "description": "世系人物ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LineagePersonBody" - }, - "example": { - "bindingMode": "NONE", - "personNo": "P202607090001", - "name": "彭德明", - "aliasName": "德明", - "sex": "0", - "generation": 1, - "generationName": "德", - "avatarOssId": "2060000000000000001", - "birthDate": "1950-01-01", - "birthLunar": "0", - "birthPlace": "江西吉安", - "personStatus": "0", - "biography": "第一世人物简介", - "sortOrder": 1 - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/LineagePersonResult", - "description": "世系人物详情响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/lineage/persons/{personId}/parents": { - "post": { - "summary": "PC 给人物添加父母", - "deprecated": false, - "description": "", - "tags": [ - "世系人物" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "personId", - "in": "path", - "description": "世系人物ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LineagePersonBody" - }, - "example": { - "bindingMode": "NONE", - "personNo": "P202607090001", - "name": "彭德明", - "aliasName": "德明", - "sex": "0", - "generation": 1, - "generationName": "德", - "avatarOssId": "2060000000000000001", - "birthDate": "1950-01-01", - "birthLunar": "0", - "birthPlace": "江西吉安", - "personStatus": "0", - "biography": "第一世人物简介", - "sortOrder": 1 - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/LineagePersonResult", - "description": "世系人物详情响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/lineage/persons/{personId}/siblings": { - "post": { - "summary": "PC 给人物添加兄弟姐妹", - "deprecated": false, - "description": "", - "tags": [ - "世系人物" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "personId", - "in": "path", - "description": "世系人物ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LineagePersonBody" - }, - "example": { - "bindingMode": "NONE", - "personNo": "P202607090001", - "name": "彭德明", - "aliasName": "德明", - "sex": "0", - "generation": 1, - "generationName": "德", - "avatarOssId": "2060000000000000001", - "birthDate": "1950-01-01", - "birthLunar": "0", - "birthPlace": "江西吉安", - "personStatus": "0", - "biography": "第一世人物简介", - "sortOrder": 1 - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/LineagePersonResult", - "description": "世系人物详情响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/lineage/persons/{personId}/spouses": { - "post": { - "summary": "PC 给人物添加配偶", - "deprecated": false, - "description": "", - "tags": [ - "世系人物" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "personId", - "in": "path", - "description": "世系人物ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LineagePersonBody" - }, - "example": { - "bindingMode": "NONE", - "personNo": "P202607090001", - "name": "彭德明", - "aliasName": "德明", - "sex": "0", - "generation": 1, - "generationName": "德", - "avatarOssId": "2060000000000000001", - "birthDate": "1950-01-01", - "birthLunar": "0", - "birthPlace": "江西吉安", - "personStatus": "0", - "biography": "第一世人物简介", - "sortOrder": 1 - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/LineagePersonResult", - "description": "世系人物详情响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/articles/{articleId}": { - "delete": { - "summary": "PC 删除谱文", - "deprecated": false, - "description": "", - "tags": [ - "内容文章" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "articleId", - "in": "path", - "description": "要删除的谱文ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/albums/{albumId}": { - "delete": { - "summary": "PC 删除相册", - "deprecated": false, - "description": "逻辑删除相册及其照片,并释放封面和照片文件引用。", - "tags": [ - "相册" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "albumId", - "in": "path", - "description": "要删除的相册ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/albums/{albumId}/photos/{photoId}": { - "delete": { - "summary": "PC 删除单张相册图片", - "deprecated": false, - "description": "", - "tags": [ - "相册" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "albumId", - "in": "path", - "description": "相册ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "photoId", - "in": "path", - "description": "要删除的相册图片ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/videos/{videoId}": { - "delete": { - "summary": "PC 删除视频", - "deprecated": false, - "description": "逻辑删除视频,并释放视频文件和封面文件引用。", - "tags": [ - "视频" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "videoId", - "in": "path", - "description": "要删除的视频ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "get": { - "summary": "PC 视频详情", - "deprecated": false, - "description": "", - "tags": [ - "视频" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "videoId", - "in": "path", - "description": "视频ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "put": { - "summary": "PC 修改视频", - "deprecated": false, - "description": "", - "tags": [ - "视频" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "videoId", - "in": "path", - "description": "视频ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/VideoBody" - }, - "example": { - "videoTitle": "家族活动记录", - "videoDesc": "清明祭祖活动视频", - "coverOssId": "2060000000000000001", - "videoOssId": "2060000000000000002", - "durationSeconds": 180, - "sortOrder": 1, - "status": "0" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/videos": { - "get": { - "summary": "PC 视频列表", - "deprecated": false, - "description": "", - "tags": [ - "视频" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ListResult", - "description": "通用列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "post": { - "summary": "PC 新增视频", - "deprecated": false, - "description": "", - "tags": [ - "视频" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/VideoBody" - }, - "example": { - "videoTitle": "家族活动记录", - "videoDesc": "清明祭祖活动视频", - "coverOssId": "2060000000000000001", - "videoOssId": "2060000000000000002", - "durationSeconds": 180, - "sortOrder": 1, - "status": "0" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/ceremonies/{ceremonyId}/invitees": { - "put": { - "summary": "PC 替换活动受邀人", - "deprecated": false, - "description": "使用完整名单替换当前待响应受邀人;仅家谱内容管理员可操作,受邀人必须是同一家谱的正常成员。", - "tags": [ - "贺礼邀约" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "ceremonyId", - "in": "path", - "description": "贺礼活动ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CeremonyInviteesBody" - }, - "example": { - "inviteeUserIds": [ - 900000001, - 900000002 - ] - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/CeremonyInvitationListResult", - "description": "贺礼邀约列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/ceremonies/{ceremonyId}/invitations": { - "get": { - "summary": "PC 查询活动邀请名单", - "deprecated": false, - "description": "查询受邀人及其待响应、接受、拒绝或取消状态,仅家谱内容管理员可见。", - "tags": [ - "贺礼邀约" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "ceremonyId", - "in": "path", - "description": "贺礼活动ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/CeremonyInvitationListResult", - "description": "贺礼邀约列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/ceremonies/{ceremonyId}/invitations/me": { - "put": { - "summary": "PC 响应当前用户的活动邀请", - "deprecated": false, - "description": "当前登录用户接受或拒绝自己的待响应邀请,不能代替其他用户操作。", - "tags": [ - "贺礼邀约" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "ceremonyId", - "in": "path", - "description": "贺礼活动ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CeremonyInvitationResponseBody" - }, - "example": { - "inviteStatus": "ACCEPTED" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/CeremonyInvitationResult", - "description": "贺礼邀约详情响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/ceremony-invitations/mine": { - "get": { - "summary": "PC 查询我的活动邀请", - "deprecated": false, - "description": "查询当前租户下发给当前业务用户且活动仍正常的邀请。", - "tags": [ - "贺礼邀约" - ], - "parameters": [ - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/CeremonyInvitationListResult", - "description": "贺礼邀约列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/ceremonies/{ceremonyId}": { - "delete": { - "summary": "PC 删除祭祀活动", - "deprecated": false, - "description": "逻辑删除祭祀活动及其祭品,并释放活动封面文件引用。", - "tags": [ - "祭祀" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "ceremonyId", - "in": "path", - "description": "要删除的祭祀活动ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/ceremonies/{ceremonyId}/gifts/{giftId}": { - "delete": { - "summary": "PC 删除单条祭品", - "deprecated": false, - "description": "", - "tags": [ - "祭祀" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "ceremonyId", - "in": "path", - "description": "祭祀活动ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "giftId", - "in": "path", - "description": "要删除的祭品ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/growth-records": { - "get": { - "summary": "PC 成长记录列表", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ListResult", - "description": "通用列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "post": { - "summary": "PC 新增成长记录", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GrowthRecordBody" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/growth-records/{recordId}": { - "get": { - "summary": "PC 成长记录详情", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "recordId", - "in": "path", - "description": "成长记录ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "put": { - "summary": "PC 修改成长记录", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "recordId", - "in": "path", - "description": "成长记录ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GrowthRecordBody" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "delete": { - "summary": "PC 删除成长记录", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "recordId", - "in": "path", - "description": "要删除的成长记录ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/relative-records": { - "get": { - "summary": "PC 亲友记录列表", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ListResult", - "description": "通用列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "post": { - "summary": "PC 新增亲友记录", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RelativeRecordBody" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/relative-records/{relativeId}": { - "get": { - "summary": "PC 亲友记录详情", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "relativeId", - "in": "path", - "description": "亲友记录ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "put": { - "summary": "PC 修改亲友记录", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "relativeId", - "in": "path", - "description": "亲友记录ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RelativeRecordBody" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "delete": { - "summary": "PC 删除亲友记录", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "relativeId", - "in": "path", - "description": "要删除的亲友记录ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/memos": { - "get": { - "summary": "PC 备忘录列表", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ListResult", - "description": "通用列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "post": { - "summary": "PC 新增备忘录", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MemoBody" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/memos/{memoId}": { - "get": { - "summary": "PC 备忘录详情", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "memoId", - "in": "path", - "description": "备忘录ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "put": { - "summary": "PC 修改备忘录", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "memoId", - "in": "path", - "description": "备忘录ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MemoBody" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "delete": { - "summary": "PC 删除备忘录", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "memoId", - "in": "path", - "description": "要删除的备忘录ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/merit-records/{meritId}": { - "delete": { - "summary": "PC 删除功德记录", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "meritId", - "in": "path", - "description": "要删除的功德记录ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "get": { - "summary": "PC 功德记录详情", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "meritId", - "in": "path", - "description": "功德记录ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "put": { - "summary": "PC 修改功德记录", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "meritId", - "in": "path", - "description": "功德记录ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MeritRecordBody" - }, - "example": { - "donorName": "张三", - "meritType": "donation", - "meritTitle": "修缮祠堂捐赠", - "meritContent": "用于祠堂修缮", - "amount": 1000, - "meritTime": "2026-07-26 10:00:00", - "sortOrder": 1, - "status": "0" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/genealogies/{genealogyId}/merit-records": { - "get": { - "summary": "PC 功德记录列表", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ListResult", - "description": "通用列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - }, - "post": { - "summary": "PC 新增功德记录", - "deprecated": false, - "description": "", - "tags": [ - "族务记录" - ], - "parameters": [ - { - "name": "genealogyId", - "in": "path", - "description": "家谱ID", - "required": true, - "example": 900001001, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MeritRecordBody" - }, - "example": { - "donorName": "张三", - "meritType": "donation", - "meritTitle": "修缮祠堂捐赠", - "meritContent": "用于祠堂修缮", - "amount": 1000, - "meritTime": "2026-07-26 10:00:00", - "sortOrder": 1, - "status": "0" - } - } - }, - "required": true - }, - "responses": { - "200": { - "$ref": "#/components/responses/ObjectResult", - "description": "通用对象响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/notifications": { - "get": { - "summary": "PC 消息通知列表", - "deprecated": false, - "description": "", - "tags": [ - "消息通知" - ], - "parameters": [ - { - "name": "readStatus", - "in": "query", - "description": "字典 gen_notification_read_status:0=未读,1=已读", - "required": false, - "schema": { - "type": "string", - "enum": [ - "0", - "1" - ], - "x-dict-type": "gen_notification_read_status", - "examples": [ - "0" - ] - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/ListResult", - "description": "通用列表响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/notifications/unread-count": { - "get": { - "summary": "PC 查询未读通知数量", - "deprecated": false, - "description": "", - "tags": [ - "消息通知" - ], - "parameters": [ - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/LongResult", - "description": "长整型数据响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/notifications/{notificationId}/read": { - "post": { - "summary": "PC 标记通知已读", - "deprecated": false, - "description": "", - "tags": [ - "消息通知" - ], - "parameters": [ - { - "name": "notificationId", - "in": "path", - "description": "通知ID", - "required": true, - "example": "", - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - }, - "/genealogy/pc/notifications/read-all": { - "post": { - "summary": "PC 全部标记已读", - "deprecated": false, - "description": "", - "tags": [ - "消息通知" - ], - "parameters": [ - { - "name": "clientid", - "in": "header", - "description": "PC 认证业务请求的 clientid Header 必填;是业务客户端唯一来源;请求体不得提交 clientId,且客户端 clientKey 必须为 web_pc 并与 PC 路由一致。", - "required": true, - "example": "ced7e5f0498645c6ec642dcf450b036f", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/VoidResult", - "description": "通用成功响应" - } - }, - "security": [ - { - "SaToken1": [] - } - ] - } - } - }, - "webhooks": {}, - "components": { - "schemas": { - "CeremonyInviteesBody": { - "type": "object", - "required": [ - "inviteeUserIds" - ], - "properties": { - "inviteeUserIds": { - "description": "完整受邀业务用户ID列表;空数组表示取消全部尚未响应的邀请", - "type": "array", - "uniqueItems": true, - "items": { - "type": "integer", - "format": "int64" - } - } - }, - "examples": [ - { - "inviteeUserIds": [ - 900000001, - 900000002 - ] - } - ] - }, - "CeremonyInvitationResponseBody": { - "type": "object", - "required": [ - "inviteStatus" - ], - "properties": { - "inviteStatus": { - "description": "当前用户的邀约响应,只允许接受或拒绝", - "type": "string", - "enum": [ - "ACCEPTED", - "DECLINED" - ] - } - }, - "examples": [ - { - "inviteStatus": "ACCEPTED" - } - ] - }, - "GrowthRecordBody": { - "type": "object", - "required": [ - "recordTitle" - ], - "properties": { - "lineagePersonId": { - "description": "绑定的世系人物ID", - "type": "integer", - "format": "int64" - }, - "recordType": { - "description": "记录类型", - "type": "string" - }, - "recordTitle": { - "description": "记录标题", - "type": "string" - }, - "recordContent": { - "description": "记录内容", - "type": "string" - }, - "recordDate": { - "description": "记录日期", - "type": "string" - }, - "remindTime": { - "description": "提醒时间", - "type": "string" - }, - "mediaOssIds": { - "description": "多个正整数 OSS ID 使用英文逗号分隔", - "type": "string", - "pattern": "^$|^[1-9][0-9]*(,[1-9][0-9]*)*$" - }, - "sortOrder": { - "description": "排序值", - "type": "integer", - "format": "int64" - }, - "status": { - "description": "字典 sys_normal_disable:0=正常,1=停用", - "type": "string", - "enum": [ - "0", - "1" - ], - "x-dict-type": "sys_normal_disable", - "examples": [ - "0" - ] - } - } - }, - "RelativeRecordBody": { - "type": "object", - "required": [ - "relativeName" - ], - "properties": { - "relativeName": { - "description": "亲友姓名", - "type": "string" - }, - "relationName": { - "description": "关系名称", - "type": "string" - }, - "eventName": { - "description": "事件名称", - "type": "string" - }, - "eventTime": { - "description": "事件时间", - "type": "string" - }, - "giftAmount": { - "description": "礼金金额", - "type": "number" - }, - "recordContent": { - "description": "记录内容", - "type": "string" - }, - "mediaOssIds": { - "description": "多个正整数 OSS ID 使用英文逗号分隔", - "type": "string", - "pattern": "^$|^[1-9][0-9]*(,[1-9][0-9]*)*$" - }, - "sortOrder": { - "description": "排序值", - "type": "integer", - "format": "int64" - }, - "status": { - "description": "字典 sys_normal_disable:0=正常,1=停用", - "type": "string", - "enum": [ - "0", - "1" - ], - "x-dict-type": "sys_normal_disable", - "examples": [ - "0" - ] - } - } - }, - "MemoBody": { - "type": "object", - "required": [ - "memoTitle" - ], - "properties": { - "memoTitle": { - "description": "备忘标题", - "type": "string" - }, - "memoContent": { - "description": "备忘内容", - "type": "string" - }, - "remindTime": { - "description": "提醒时间", - "type": "string" - }, - "completed": { - "description": "是否已完成", - "type": "string" - }, - "mediaOssIds": { - "description": "多个正整数 OSS ID 使用英文逗号分隔", - "type": "string", - "pattern": "^$|^[1-9][0-9]*(,[1-9][0-9]*)*$" - }, - "sortOrder": { - "description": "排序值", - "type": "integer", - "format": "int64" - }, - "status": { - "description": "字典 sys_normal_disable:0=正常,1=停用", - "type": "string", - "enum": [ - "0", - "1" - ], - "x-dict-type": "sys_normal_disable", - "examples": [ - "0" - ] - } - } - }, - "RVoid": { - "type": "object", - "properties": { - "code": { - "description": "系统图形验证码或响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string", - "examples": [ - "操作成功" - ] - }, - "data": { - "description": "响应数据主体", - "type": "null" - } - } - }, - "PasswordRegisterBody": { - "type": "object", - "additionalProperties": false, - "required": [ - "grantType", - "tenantId", - "phone", - "password", - "smsCode" - ], - "properties": { - "grantType": { - "description": "授权类型,例如password、sms、wechat、apple", - "type": "string", - "examples": [ - "password" - ] - }, - "tenantId": { - "description": "租户ID,用于区分不同租户的数据和验证策略", - "type": "string", - "examples": [ - "000000" - ] - }, - "phone": { - "description": "手机号", - "type": "string", - "examples": [ - "13800000000" - ] - }, - "password": { - "type": "string", - "description": "32 位 MD5", - "examples": [ - "e10adc3949ba59abbe56e057f20f883e" - ] - }, - "nickName": { - "description": "用户昵称", - "type": "string" - }, - "registerSource": { - "description": "注册来源,例如APP、PC、H5、MINI_PROGRAM", - "type": "string" - }, - "smsCode": { - "description": "4 位短信验证码", - "type": "string", - "pattern": "^\\d{4}$", - "examples": [ - "1234" - ] - } - } - }, - "PasswordLoginBody": { - "description": "PC 用户密码登录请求;验证场景由后端按 PC_PASSWORD_LOGIN 路由的当前激活绑定解析,前端不得提交 sceneCode;validToken 是否需要由解析后场景的策略决定。", - "type": "object", - "additionalProperties": false, - "required": [ - "grantType", - "tenantId", - "phone", - "password" - ], - "properties": { - "grantType": { - "description": "授权类型,例如password、sms、wechat、apple", - "type": "string", - "examples": [ - "password" - ] - }, - "tenantId": { - "description": "租户ID,用于区分不同租户的数据和验证策略", - "type": "string", - "examples": [ - "000000" - ] - }, - "phone": { - "description": "登录手机号,也是行为验证票据绑定的验证主体。", - "type": "string", - "examples": [ - "13800000000" - ] - }, - "password": { - "type": "string", - "description": "32 位 MD5", - "examples": [ - "e10adc3949ba59abbe56e057f20f883e" - ] - }, - "validToken": { - "description": "验证中心通过后返回的一次性票据;后台验证策略关闭时可为空,开启时必须提交。", - "type": "string" - } - } - }, - "SmsLoginBody": { - "type": "object", - "additionalProperties": false, - "required": [ - "grantType", - "tenantId", - "phone", - "smsCode" - ], - "properties": { - "grantType": { - "description": "授权类型,例如password、sms、wechat、apple", - "type": "string", - "examples": [ - "sms" - ] - }, - "tenantId": { - "description": "租户ID,用于区分不同租户的数据和验证策略", - "type": "string", - "examples": [ - "000000" - ] - }, - "phone": { - "description": "手机号", - "type": "string", - "examples": [ - "13800000000" - ] - }, - "smsCode": { - "description": "4 位短信验证码", - "type": "string", - "pattern": "^\\d{4}$", - "examples": [ - "1234" - ] - } - } - }, - "SmsCodeBody": { - "type": "object", - "additionalProperties": false, - "required": [ - "grantType", - "tenantId", - "phone" - ], - "properties": { - "grantType": { - "description": "授权类型,例如password、sms、wechat、apple", - "type": "string", - "examples": [ - "sms" - ] - }, - "tenantId": { - "description": "租户ID,用于区分不同租户的数据和验证策略", - "type": "string", - "examples": [ - "000000" - ] - }, - "phone": { - "description": "手机号", - "type": "string", - "examples": [ - "13800000000" - ] - }, - "validToken": { - "description": "验证中心通过后返回的票据,仅用于发送短信验证码;后台验证策略关闭时可为空,开启时必填。", - "type": "string" - } - } - }, - "ProfileUpdateBody": { - "type": "object", - "additionalProperties": false, - "properties": { - "nickName": { - "description": "用户昵称", - "type": "string", - "maxLength": 30 - }, - "realName": { - "description": "真实姓名", - "type": "string", - "maxLength": 30 - }, - "avatar": { - "description": "头像文件OSS ID", - "type": "integer", - "format": "int64" - }, - "sex": { - "description": "字典 sys_user_sex:0=男,1=女,2=未知", - "type": "string", - "enum": [ - "0", - "1", - "2" - ], - "x-dict-type": "sys_user_sex", - "examples": [ - "2" - ] - }, - "birthday": { - "description": "生日,格式 yyyy-MM-dd", - "type": "string", - "format": "date" - }, - "email": { - "description": "邮箱", - "type": "string", - "format": "email", - "maxLength": 100 - } - } - }, - "PasswordChangeBody": { - "type": "object", - "required": [ - "oldPassword", - "newPassword" - ], - "properties": { - "oldPassword": { - "type": "string", - "description": "32 位 MD5" - }, - "newPassword": { - "type": "string", - "description": "32 位 MD5" - } - } - }, - "PasswordResetBody": { - "type": "object", - "additionalProperties": false, - "required": [ - "grantType", - "tenantId", - "phone", - "smsCode", - "newPassword" - ], - "properties": { - "grantType": { - "description": "授权类型", - "type": "string", - "examples": [ - "password" - ] - }, - "tenantId": { - "description": "租户ID,用于区分不同租户的数据和验证策略", - "type": "string", - "examples": [ - "000000" - ] - }, - "phone": { - "description": "手机号", - "type": "string" - }, - "smsCode": { - "description": "4 位短信验证码", - "type": "string", - "pattern": "^\\d{4}$", - "examples": [ - "1234" - ] - }, - "newPassword": { - "type": "string", - "description": "32 位 MD5" - } - } - }, - "PhoneChangeBody": { - "type": "object", - "additionalProperties": false, - "required": [ - "phone", - "smsCode" - ], - "properties": { - "phone": { - "description": "新手机号", - "type": "string" - }, - "smsCode": { - "description": "4 位短信验证码", - "type": "string", - "pattern": "^\\d{4}$", - "examples": [ - "1234" - ] - } - } - }, - "AccountDeactivateBody": { - "type": "object", - "additionalProperties": false, - "required": [ - "smsCode" - ], - "properties": { - "smsCode": { - "description": "4 位短信验证码", - "type": "string", - "pattern": "^\\d{4}$", - "examples": [ - "1234" - ] - } - } - }, - "ResumableInitBody": { - "type": "object", - "additionalProperties": false, - "required": [ - "uploadId", - "fileName", - "fileMd5", - "totalSize", - "totalChunks", - "chunkSize" - ], - "properties": { - "uploadId": { - "description": "客户端生成并在整个上传过程保持不变的上传任务ID", - "type": "string" - }, - "fileName": { - "description": "原始文件名", - "type": "string" - }, - "fileMd5": { - "description": "完整文件MD5,用于秒传和完整性校验", - "type": "string", - "pattern": "^[a-fA-F0-9]{32}$" - }, - "totalSize": { - "description": "完整文件大小,单位字节", - "type": "integer", - "format": "int64", - "minimum": 1 - }, - "totalChunks": { - "description": "总分片数", - "type": "integer", - "minimum": 1 - }, - "chunkSize": { - "description": "单个分片大小,单位字节;除最后一片外每片必须严格等于该值", - "type": "integer", - "format": "int64", - "minimum": 1, - "examples": [ - 4194304 - ] - }, - "contentType": { - "description": "文件MIME类型,例如 image/jpeg、video/mp4", - "type": "string" - } - } - }, - "ResumableCompleteBody": { - "type": "object", - "additionalProperties": false, - "required": [ - "uploadId", - "fileName", - "fileMd5", - "totalSize", - "totalChunks" - ], - "properties": { - "uploadId": { - "description": "分片上传任务ID", - "type": "string" - }, - "fileName": { - "description": "原始文件名,必须与初始化请求一致", - "type": "string" - }, - "fileMd5": { - "description": "完整文件MD5,用于秒传和完整性校验", - "type": "string", - "pattern": "^[a-fA-F0-9]{32}$" - }, - "totalSize": { - "description": "完整文件大小,单位字节", - "type": "integer", - "format": "int64", - "minimum": 1 - }, - "totalChunks": { - "description": "总分片数,必须与初始化请求一致", - "type": "integer", - "minimum": 1 - } - } - }, - "GenerationPoemBatchBody": { - "type": "object", - "required": [ - "poemText" - ], - "properties": { - "poemText": { - "description": "字辈文本。无分隔符时按单个字拆分;可用空格、逗号、分号、顿号、斜杠或竖线分隔多个字辈。单个字辈最多50个字符,一次最多500个世代。", - "type": "string", - "maxLength": 26000, - "examples": [ - "德承家亦\n忠厚传芳" - ] - }, - "disableMissing": { - "description": "保存时是否停用未出现在新文本中的后续世代;不会删除历史记录。", - "type": "boolean", - "examples": [ - false - ] - } - } - }, - "GenerationPoemBody": { - "type": "object", - "required": [ - "generationNo", - "generationText" - ], - "properties": { - "generationNo": { - "description": "世代序号", - "type": "integer", - "format": "int64", - "minimum": 1, - "maximum": 2147483647 - }, - "generationText": { - "description": "当前世代的字辈文字,最长50个字符", - "type": "string", - "maxLength": 50 - }, - "description": { - "description": "字辈说明,最长500个字符", - "type": "string", - "maxLength": 500 - }, - "sortOrder": { - "description": "排序值,数值越小越靠前", - "type": "integer", - "format": "int64", - "minimum": -2147483648, - "maximum": 2147483647 - }, - "status": { - "description": "字典 sys_normal_disable:0=正常,1=停用", - "type": "string", - "enum": [ - "0", - "1" - ], - "x-dict-type": "sys_normal_disable", - "examples": [ - "0" - ] - } - } - }, - "LineagePersonBody": { - "type": "object", - "additionalProperties": false, - "required": [ - "bindingMode", - "name" - ], - "properties": { - "bindingMode": { - "description": "账号绑定方式:NONE=不绑定账号;SELF=绑定当前登录业务用户;SPECIFIED=由有管理权限的操作者指定 appUserId", - "type": "string", - "enum": [ - "NONE", - "SELF", - "SPECIFIED" - ], - "examples": [ - "NONE" - ] - }, - "appUserId": { - "description": "仅 bindingMode=SPECIFIED 时必填;NONE/SELF 时必须不传。指定用户必须属于当前租户、状态正常,且未绑定当前家谱的其他有效世系人物", - "type": "integer", - "format": "int64" - }, - "personNo": { - "description": "人物编号;不传时由服务端生成", - "type": "string" - }, - "name": { - "description": "姓名", - "type": "string" - }, - "aliasName": { - "description": "别名或曾用名", - "type": "string" - }, - "sex": { - "description": "字典 sys_user_sex:0=男,1=女,2=未知", - "type": "string", - "enum": [ - "0", - "1", - "2" - ], - "x-dict-type": "sys_user_sex", - "examples": [ - "0" - ] - }, - "generation": { - "description": "世代序号", - "type": "integer", - "format": "int64" - }, - "generationName": { - "description": "字辈或辈分", - "type": "string" - }, - "fatherId": { - "description": "父亲人物ID,必须属于当前家谱", - "type": "integer", - "format": "int64" - }, - "motherId": { - "description": "母亲人物ID,必须属于当前家谱", - "type": "integer", - "format": "int64" - }, - "avatarOssId": { - "description": "头像文件OSS ID;用户界面应通过统一上传组件获得,不允许手工录入", - "type": [ - "string", - "null" - ], - "pattern": "^[1-9][0-9]*$" - }, - "birthDate": { - "type": [ - "string", - "null" - ], - "format": "date-time" - }, - "birthLunar": { - "description": "字典 gen_number_yes_no:0=否,1=是", - "type": "string", - "enum": [ - "0", - "1" - ], - "x-dict-type": "gen_number_yes_no", - "examples": [ - "0" - ] - }, - "birthPlace": { - "description": "出生地", - "type": "string" - }, - "deathDate": { - "type": [ - "string", - "null" - ], - "format": "date-time" - }, - "deathLunar": { - "description": "字典 gen_number_yes_no:0=否,1=是", - "type": "string", - "enum": [ - "0", - "1" - ], - "x-dict-type": "gen_number_yes_no", - "examples": [ - "0" - ] - }, - "deathPlace": { - "description": "逝世地", - "type": "string" - }, - "burialPlace": { - "description": "安葬地", - "type": "string" - }, - "personStatus": { - "description": "字典 gen_lineage_person_status:0=健在,1=已故,2=未知", - "type": "string", - "enum": [ - "0", - "1", - "2" - ], - "x-dict-type": "gen_lineage_person_status", - "examples": [ - "0" - ] - }, - "biography": { - "description": "人物简介", - "type": "string" - }, - "sortOrder": { - "description": "排序值,数值越小越靠前", - "type": "integer", - "format": "int64" - }, - "remark": { - "description": "备注", - "type": "string" - }, - "relationName": { - "description": "通过添加配偶快捷接口提交时使用的关系显示名称", - "type": "string" - } - } - }, - "FamilyFeedBody": { - "type": "object", - "required": [ - "feedContent" - ], - "properties": { - "feedType": { - "description": "动态类型,未传时默认为 text", - "type": "string", - "examples": [ - "text" - ] - }, - "feedContent": { - "description": "动态内容,不能为空", - "type": "string", - "examples": [ - "今天上传一张老照片。" - ] - }, - "mediaOssIds": { - "description": "媒体文件 OSS ID 列表,多个 ID 使用英文逗号分隔", - "type": "string", - "examples": [ - "2060000000000000001,2060000000000000002" - ] - }, - "sortOrder": { - "description": "排序值,未传时默认为 0", - "type": "integer", - "format": "int64", - "examples": [ - 0 - ] - }, - "status": { - "description": "字典 sys_normal_disable:0=正常,1=停用;未传时默认为0", - "type": "string", - "enum": [ - "0", - "1" - ], - "x-dict-type": "sys_normal_disable", - "examples": [ - "0" - ] - } - } - }, - "FamilyFeedCommentBody": { - "type": "object", - "required": [ - "commentContent" - ], - "properties": { - "parentCommentId": { - "description": "父评论 ID;不传或传 null 表示发布一级评论", - "type": [ - "integer", - "null" - ], - "format": "int64" - }, - "commentContent": { - "description": "评论内容,最多 1000 个字符", - "type": "string" - } - } - }, - "VerificationChallengeBody": { - "type": "object", - "additionalProperties": false, - "required": [ - "tenantId", - "subject" - ], - "properties": { - "tenantId": { - "type": "string", - "description": "租户 ID" - }, - "subject": { - "type": "string", - "description": "验证主体,如手机号/用户名/IP" - } - }, - "examples": [ - { - "tenantId": "000000", - "subject": "13800000000" - } - ] - }, - "VerificationCheckBody": { - "type": "object", - "additionalProperties": false, - "required": [ - "tenantId", - "subject", - "challengeId" - ], - "properties": { - "tenantId": { - "description": "租户ID,用于区分不同租户的数据和验证策略", - "type": "string" - }, - "subject": { - "description": "验证主体,通常为手机号、用户ID或登录账号", - "type": "string" - }, - "challengeId": { - "description": "验证挑战ID,由验证挑战接口返回", - "type": "string" - }, - "providerCode": { - "type": "string", - "description": "验证服务,tianai 或 ruoyi_image" - }, - "captchaType": { - "type": "string", - "description": "SLIDER/ROTATE/CONCAT/WORD_IMAGE_CLICK/math/char" - }, - "payload": { - "description": "验证结果载荷。天爱行为验证码提交 payload.track;系统图形验证码提交 payload.uuid 和 payload.code", - "oneOf": [ - { - "$ref": "#/components/schemas/TianaiVerificationPayload" - }, - { - "$ref": "#/components/schemas/SystemImageVerificationPayload" - } - ] - } - } - }, - "TianaiVerificationPayload": { - "type": "object", - "description": "将天爱 1.5.5 前端验证回调中的 data 原样放入 track;回调 id 不放入 payload,挑战标识统一提交顶层 challengeId", - "required": [ - "track" - ], - "properties": { - "track": { - "$ref": "#/components/schemas/TianaiCaptchaTrack" - } - } - }, - "TianaiCaptchaTrack": { - "type": "object", - "required": [ - "bgImageWidth", - "bgImageHeight", - "startTime", - "stopTime", - "trackList" - ], - "properties": { - "bgImageWidth": { - "type": "integer", - "description": "验证码背景图片在前端实际显示的宽度,单位像素" - }, - "bgImageHeight": { - "type": "integer", - "description": "验证码背景图片在前端实际显示的高度,单位像素" - }, - "templateImageWidth": { - "type": "integer", - "description": "滑块或旋转模板图片在前端实际显示的宽度,单位像素" - }, - "templateImageHeight": { - "type": "integer", - "description": "滑块或旋转模板图片在前端实际显示的高度,单位像素" - }, - "startTime": { - "type": "integer", - "format": "int64", - "description": "用户开始操作的时间戳,单位毫秒" - }, - "stopTime": { - "type": "integer", - "format": "int64", - "description": "用户结束操作的时间戳,单位毫秒" - }, - "left": { - "type": "integer", - "description": "滑块最终水平偏移量,单位像素;由天爱前端控件生成" - }, - "top": { - "type": "integer", - "description": "点选或模板最终垂直偏移量,单位像素;由天爱前端控件生成" - }, - "trackList": { - "type": "array", - "minItems": 1, - "description": "用户操作轨迹,不能为空,必须直接使用天爱前端控件产生的数据", - "items": { - "$ref": "#/components/schemas/TianaiCaptchaTrackPoint" - } - }, - "data": { - "type": [ - "object", - "null" - ], - "additionalProperties": true, - "description": "不同验证码类型附带的扩展数据,由天爱前端控件原样提交", - "properties": {} - } - } - }, - "TianaiCaptchaTrackPoint": { - "type": "object", - "required": [ - "x", - "y", - "t", - "type" - ], - "properties": { - "x": { - "type": "number", - "format": "float", - "description": "当前轨迹点的水平坐标" - }, - "y": { - "type": "number", - "format": "float", - "description": "当前轨迹点的垂直坐标" - }, - "t": { - "type": "number", - "format": "float", - "description": "当前轨迹点相对操作开始时的时间,单位毫秒" - }, - "type": { - "type": "string", - "description": "轨迹事件类型,例如 down、move、up" - } - } - }, - "SystemImageVerificationPayload": { - "type": "object", - "required": [ - "uuid", - "code" - ], - "properties": { - "uuid": { - "type": "string", - "description": "系统图形验证码接口返回的唯一标识" - }, - "code": { - "type": "string", - "description": "用户填写的图形验证码答案" - } - } - }, - "MeritRecordBody": { - "type": "object", - "additionalProperties": false, - "required": [ - "donorName", - "meritTitle" - ], - "properties": { - "donorName": { - "description": "功德人姓名", - "type": "string" - }, - "meritType": { - "description": "字典 gen_merit_type:donation=捐赠,repair=修祠,public=公益,other=其他", - "type": "string", - "enum": [ - "donation", - "repair", - "public", - "other" - ], - "x-dict-type": "gen_merit_type", - "examples": [ - "donation" - ] - }, - "meritTitle": { - "description": "功德标题", - "type": "string" - }, - "meritContent": { - "description": "功德内容", - "type": "string" - }, - "amount": { - "description": "金额", - "type": "number" - }, - "meritTime": { - "description": "功德时间", - "type": "string", - "format": "date-time" - }, - "sortOrder": { - "description": "排序值", - "type": "integer", - "format": "int64" - }, - "status": { - "description": "字典 sys_normal_disable:0=正常,1=停用", - "type": "string", - "enum": [ - "0", - "1" - ], - "x-dict-type": "sys_normal_disable", - "examples": [ - "0" - ] - } - } - }, - "VideoBody": { - "type": "object", - "additionalProperties": false, - "required": [ - "videoTitle", - "videoOssId" - ], - "properties": { - "videoTitle": { - "description": "视频标题", - "type": "string" - }, - "videoDesc": { - "description": "视频说明", - "type": "string" - }, - "coverOssId": { - "description": "封面图片OSS ID,由统一上传接口返回", - "type": "string", - "pattern": "^[1-9][0-9]*$" - }, - "videoOssId": { - "description": "视频文件OSS ID,由统一上传接口返回", - "type": "string", - "pattern": "^[1-9][0-9]*$" - }, - "durationSeconds": { - "description": "视频时长,单位秒", - "type": "integer", - "format": "int64" - }, - "sortOrder": { - "description": "排序值", - "type": "integer", - "format": "int64" - }, - "status": { - "description": "字典 sys_normal_disable:0=正常,1=停用", - "type": "string", - "enum": [ - "0", - "1" - ], - "x-dict-type": "sys_normal_disable", - "examples": [ - "0" - ] - } - } - }, - "RObject": { - "type": "object", - "properties": { - "code": { - "description": "系统图形验证码或响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string", - "examples": [ - "操作成功" - ] - }, - "data": { - "description": "响应数据主体", - "type": "object", - "additionalProperties": true, - "properties": {} - } - } - }, - "RGenealogyQuota": { - "type": "object", - "properties": { - "code": { - "description": "响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string", - "examples": [ - "操作成功" - ] - }, - "data": { - "$ref": "#/components/schemas/GenealogyQuota" - } - } - }, - "GenealogyQuota": { - "type": "object", - "required": [ - "createUsed", - "createLimit", - "createRemaining", - "canCreate", - "joinUsed", - "joinLimit", - "joinRemaining", - "canJoin" - ], - "properties": { - "createUsed": { - "description": "当前用户已创建的家谱数量", - "type": "integer", - "format": "int64", - "minimum": 0, - "examples": [ - 1 - ] - }, - "createLimit": { - "description": "每用户可创建家谱上限,-1 表示不限,0 表示禁止,正数表示上限", - "type": "integer", - "format": "int64", - "minimum": -1, - "examples": [ - 3 - ] - }, - "createRemaining": { - "description": "还可创建的家谱数量,-1 表示不限", - "type": "integer", - "format": "int64", - "minimum": -1, - "examples": [ - 2 - ] - }, - "canCreate": { - "description": "当前用户是否还能创建家谱", - "type": "boolean", - "examples": [ - true - ] - }, - "joinUsed": { - "description": "已加入的家谱总数,包含自己创建的家谱", - "type": "integer", - "format": "int64", - "minimum": 0, - "examples": [ - 2 - ] - }, - "joinLimit": { - "description": "每用户可加入家谱总数上限,-1 表示不限,0 表示禁止,正数表示上限", - "type": "integer", - "format": "int64", - "minimum": -1, - "examples": [ - 10 - ] - }, - "joinRemaining": { - "description": "还可加入的家谱数量,-1 表示不限", - "type": "integer", - "format": "int64", - "minimum": -1, - "examples": [ - 8 - ] - }, - "canJoin": { - "description": "当前用户是否还能加入其他家谱", - "type": "boolean", - "examples": [ - true - ] - } - } - }, - "RLong": { - "type": "object", - "properties": { - "code": { - "description": "响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string", - "examples": [ - "操作成功" - ] - }, - "data": { - "description": "当前业务用户未读通知数量", - "type": "integer", - "format": "int64", - "examples": [ - 3 - ] - } - } - }, - "RList": { - "type": "object", - "properties": { - "code": { - "description": "系统图形验证码或响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string", - "examples": [ - "操作成功" - ] - }, - "data": { - "description": "响应数据主体", - "type": "array", - "items": { - "type": "object", - "additionalProperties": true, - "properties": {} - } - } - } - }, - "RCeremonyInvitation": { - "type": "object", - "properties": { - "code": { - "description": "响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string", - "examples": [ - "操作成功" - ] - }, - "data": { - "$ref": "#/components/schemas/CeremonyInvitationVo" - } - } - }, - "CeremonyInvitationVo": { - "type": "object", - "properties": { - "invitationId": { - "description": "邀约ID", - "type": "integer", - "format": "int64" - }, - "genealogyId": { - "description": "家谱ID", - "type": "integer", - "format": "int64" - }, - "ceremonyId": { - "description": "贺礼活动ID", - "type": "integer", - "format": "int64" - }, - "inviteeUserId": { - "description": "受邀业务用户ID", - "type": "integer", - "format": "int64" - }, - "inviteStatus": { - "description": "邀约状态", - "type": "string", - "enum": [ - "PENDING", - "ACCEPTED", - "DECLINED", - "CANCELED" - ] - }, - "inviteVersion": { - "description": "邀请版本,取消后重新邀请时递增", - "type": "integer" - }, - "deliveredTime": { - "description": "邀请投递时间", - "type": "string", - "format": "date-time" - }, - "readTime": { - "description": "邀请阅读时间", - "type": "string", - "format": "date-time" - }, - "responseTime": { - "description": "接受或拒绝时间", - "type": "string", - "format": "date-time" - }, - "ceremonyTitle": { - "description": "贺礼活动标题", - "type": "string" - }, - "ceremonyTime": { - "description": "贺礼活动时间", - "type": "string", - "format": "date-time" - }, - "location": { - "description": "地点名称", - "type": "string" - }, - "locationAddress": { - "description": "详细地址", - "type": "string" - }, - "longitude": { - "description": "经度", - "type": "number" - }, - "latitude": { - "description": "纬度", - "type": "number" - } - } - }, - "RCeremonyInvitationList": { - "type": "object", - "properties": { - "code": { - "description": "响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string", - "examples": [ - "操作成功" - ] - }, - "data": { - "description": "贺礼邀约列表", - "type": "array", - "items": { - "$ref": "#/components/schemas/CeremonyInvitationVo" - } - } - } - }, - "RGenerationPoem": { - "type": "object", - "properties": { - "code": { - "description": "响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string" - }, - "data": { - "$ref": "#/components/schemas/GenerationPoemView" - } - } - }, - "GenerationPoemView": { - "type": "object", - "properties": { - "poemId": { - "description": "字辈记录ID", - "type": "integer", - "format": "int64" - }, - "genealogyId": { - "description": "所属家谱ID", - "type": "integer", - "format": "int64" - }, - "genealogyNo": { - "description": "家谱编号", - "type": "string" - }, - "genealogyName": { - "description": "家谱名称", - "type": "string" - }, - "generationNo": { - "description": "世代序号", - "type": "integer", - "format": "int64" - }, - "generationText": { - "description": "字辈文字", - "type": "string" - }, - "description": { - "description": "字辈说明", - "type": "string" - }, - "sortOrder": { - "description": "排序值,数值越小越靠前", - "type": "integer", - "format": "int64" - }, - "status": { - "description": "字典 sys_normal_disable:0=正常,1=停用", - "type": "string", - "enum": [ - "0", - "1" - ], - "x-dict-type": "sys_normal_disable", - "examples": [ - "0" - ] - } - } - }, - "RGenerationPoemList": { - "type": "object", - "properties": { - "code": { - "description": "响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string" - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/GenerationPoemView" - } - } - } - }, - "RGenerationPoemBatchPreview": { - "type": "object", - "properties": { - "code": { - "description": "响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string" - }, - "data": { - "$ref": "#/components/schemas/GenerationPoemBatchPreviewView" - } - } - }, - "GenerationPoemBatchPreviewView": { - "type": "object", - "properties": { - "genealogyId": { - "description": "所属家谱ID", - "type": "integer", - "format": "int64" - }, - "genealogyNo": { - "description": "家谱编号", - "type": "string" - }, - "genealogyName": { - "description": "家谱名称", - "type": "string" - }, - "createCount": { - "description": "将新增的字辈数量", - "type": "integer" - }, - "updateCount": { - "description": "将修改或恢复的字辈数量", - "type": "integer" - }, - "keepCount": { - "description": "保持不变的字辈数量", - "type": "integer" - }, - "disableCount": { - "description": "将停用的字辈数量", - "type": "integer" - }, - "items": { - "description": "按世代排序的逐项变更预览", - "type": "array", - "items": { - "$ref": "#/components/schemas/GenerationPoemBatchItemView" - } - } - } - }, - "GenerationPoemBatchItemView": { - "type": "object", - "properties": { - "poemId": { - "description": "现有字辈记录ID;新增时为空", - "type": "integer", - "format": "int64" - }, - "generationNo": { - "description": "世代序号", - "type": "integer", - "format": "int64" - }, - "oldGenerationText": { - "description": "保存前字辈文字", - "type": "string" - }, - "newGenerationText": { - "description": "保存后字辈文字", - "type": "string" - }, - "oldStatus": { - "description": "保存前状态", - "type": "string" - }, - "newStatus": { - "description": "保存后状态", - "type": "string" - }, - "action": { - "description": "预览动作,create、update、keep 或 disable", - "type": "string" - }, - "warning": { - "description": "需要用户确认的变更提示", - "type": "string" - } - } - }, - "RLineagePerson": { - "type": "object", - "properties": { - "code": { - "description": "响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string", - "examples": [ - "操作成功" - ] - }, - "data": { - "$ref": "#/components/schemas/LineagePersonView" - } - } - }, - "LineagePersonView": { - "type": "object", - "properties": { - "personId": { - "description": "人物ID", - "type": "integer", - "format": "int64" - }, - "genealogyId": { - "description": "家谱ID", - "type": "integer", - "format": "int64" - }, - "genealogyName": { - "description": "家谱名称", - "type": "string" - }, - "genealogyNo": { - "description": "家谱编号", - "type": "string" - }, - "appUserId": { - "description": "绑定业务用户ID", - "type": [ - "integer", - "null" - ], - "format": "int64" - }, - "appUserNickName": { - "description": "绑定业务用户昵称", - "type": "string" - }, - "personNo": { - "description": "人物编号", - "type": "string" - }, - "name": { - "description": "姓名", - "type": "string" - }, - "aliasName": { - "description": "别名或曾用名", - "type": "string" - }, - "sex": { - "description": "字典 sys_user_sex:0=男,1=女,2=未知", - "type": "string", - "enum": [ - "0", - "1", - "2" - ], - "x-dict-type": "sys_user_sex", - "examples": [ - "2" - ] - }, - "generation": { - "description": "世代序号", - "type": "integer", - "format": "int64" - }, - "generationName": { - "description": "字辈或辈分", - "type": "string" - }, - "fatherId": { - "description": "父亲人物ID", - "type": [ - "integer", - "null" - ], - "format": "int64" - }, - "fatherName": { - "description": "父亲姓名", - "type": "string" - }, - "motherId": { - "description": "母亲人物ID", - "type": [ - "integer", - "null" - ], - "format": "int64" - }, - "motherName": { - "description": "母亲姓名", - "type": "string" - }, - "spouseNames": { - "description": "配偶姓名,多个使用顿号分隔", - "type": "string" - }, - "avatarOssId": { - "description": "头像文件OSS ID", - "type": [ - "string", - "null" - ], - "pattern": "^[1-9][0-9]*$" - }, - "birthDate": { - "description": "出生日期", - "type": [ - "string", - "null" - ], - "format": "date-time" - }, - "birthLunar": { - "description": "字典 gen_number_yes_no:0=否,1=是", - "type": "string", - "enum": [ - "0", - "1" - ], - "x-dict-type": "gen_number_yes_no", - "examples": [ - "0" - ] - }, - "birthPlace": { - "description": "出生地", - "type": "string" - }, - "deathDate": { - "description": "逝世日期", - "type": [ - "string", - "null" - ], - "format": "date-time" - }, - "deathLunar": { - "description": "字典 gen_number_yes_no:0=否,1=是", - "type": "string", - "enum": [ - "0", - "1" - ], - "x-dict-type": "gen_number_yes_no", - "examples": [ - "0" - ] - }, - "deathPlace": { - "description": "逝世地", - "type": "string" - }, - "burialPlace": { - "description": "安葬地", - "type": "string" - }, - "personStatus": { - "description": "字典 gen_lineage_person_status:0=健在,1=已故,2=未知", - "type": "string", - "enum": [ - "0", - "1", - "2" - ], - "x-dict-type": "gen_lineage_person_status", - "examples": [ - "0" - ] - }, - "biography": { - "description": "人物简介", - "type": "string" - }, - "sortOrder": { - "description": "排序值", - "type": "integer", - "format": "int64" - }, - "status": { - "description": "字典 sys_normal_disable:0=正常,1=停用", - "type": "string", - "enum": [ - "0", - "1" - ], - "x-dict-type": "sys_normal_disable", - "examples": [ - "0" - ] - }, - "remark": { - "description": "备注", - "type": "string" - } - } - }, - "RLineagePersonList": { - "type": "object", - "properties": { - "code": { - "description": "响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string", - "examples": [ - "操作成功" - ] - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/LineagePersonView" - } - } - } - }, - "RLineagePersonTreeList": { - "type": "object", - "properties": { - "code": { - "description": "响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string", - "examples": [ - "操作成功" - ] - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/LineagePersonTreeView" - } - } - } - }, - "LineagePersonTreeView": { - "allOf": [ - { - "$ref": "#/components/schemas/LineagePersonView" - }, - { - "type": "object", - "properties": { - "relationType": { - "description": "字典 gen_lineage_relation_type:father=父亲,mother=母亲,spouse=配偶,child=子女,adoptive=收养", - "type": "string", - "enum": [ - "father", - "mother", - "spouse", - "child", - "adoptive" - ], - "x-dict-type": "gen_lineage_relation_type", - "examples": [ - "spouse" - ] - }, - "relationName": { - "description": "关系显示名称", - "type": "string" - }, - "spouses": { - "description": "配偶节点", - "type": "array", - "items": { - "$ref": "#/components/schemas/LineagePersonTreeView" - } - }, - "children": { - "description": "子女节点", - "type": "array", - "items": { - "$ref": "#/components/schemas/LineagePersonTreeView" - } - } - } - } - ] - }, - "LineagePersonPageResult": { - "type": "object", - "properties": { - "code": { - "description": "响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string", - "examples": [ - "操作成功" - ] - }, - "rows": { - "description": "当前页世系人物", - "type": "array", - "items": { - "$ref": "#/components/schemas/LineagePersonView" - } - }, - "total": { - "description": "总记录数", - "type": "integer", - "format": "int64", - "examples": [ - 0 - ] - } - } - }, - "RLogin": { - "type": "object", - "properties": { - "code": { - "description": "系统图形验证码或响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string", - "examples": [ - "操作成功" - ] - }, - "data": { - "description": "响应数据主体", - "$ref": "#/components/schemas/LoginVo" - } - } - }, - "LoginVo": { - "type": "object", - "properties": { - "token": { - "type": "string" - }, - "accessToken": { - "type": "string" - }, - "tokenValue": { - "type": "string" - }, - "userId": { - "type": "integer", - "format": "int64" - }, - "tenantId": { - "description": "租户ID,用于区分不同租户的数据和验证策略", - "type": "string" - }, - "clientId": { - "description": "客户端ID,对应后台客户端管理中的APP/PC/小程序等终端", - "type": "string" - }, - "clientKey": { - "type": "string" - } - } - }, - "RFileUpload": { - "type": "object", - "properties": { - "code": { - "description": "系统图形验证码或响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string", - "examples": [ - "操作成功" - ] - }, - "data": { - "description": "响应数据主体", - "$ref": "#/components/schemas/FileUploadVo" - } - } - }, - "FileUploadVo": { - "type": "object", - "properties": { - "ossId": { - "description": "OSS文件ID,表单内部使用,用户侧应通过上传组件获取", - "type": "string", - "pattern": "^[1-9][0-9]*$" - }, - "url": { - "type": "string" - }, - "thumbnailUrl": { - "type": [ - "string", - "null" - ] - }, - "fileName": { - "description": "原始文件名", - "type": "string" - }, - "originalName": { - "type": "string" - } - } - }, - "RVerificationRequire": { - "type": "object", - "properties": { - "code": { - "description": "系统图形验证码或响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string", - "examples": [ - "操作成功" - ] - }, - "data": { - "description": "响应数据主体", - "$ref": "#/components/schemas/VerificationRequireVo" - } - } - }, - "VerificationRequireVo": { - "type": "object", - "properties": { - "required": { - "description": "当前场景是否需要验证", - "type": "boolean" - }, - "providerCode": { - "description": "第三方服务商编码", - "type": "string" - }, - "captchaType": { - "description": "验证码类型,例如SLIDER、ROTATE、CONCAT、WORD_IMAGE_CLICK", - "type": "string" - }, - "sceneCode": { - "description": "服务端按当前业务动作和激活绑定解析出的实际验证场景编码", - "type": "string" - }, - "ttlSeconds": { - "description": "验证或票据有效秒数", - "type": "integer" - } - }, - "examples": [ - { - "required": true, - "providerCode": "tianai", - "captchaType": "SLIDER", - "sceneCode": "PC_LOGIN_ACTIVE_SCENE", - "ttlSeconds": 300 - } - ] - }, - "RVerificationChallenge": { - "type": "object", - "properties": { - "code": { - "description": "系统图形验证码或响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string", - "examples": [ - "操作成功" - ] - }, - "data": { - "description": "响应数据主体", - "$ref": "#/components/schemas/VerificationChallengeVo" - } - } - }, - "VerificationChallengeVo": { - "type": "object", - "properties": { - "required": { - "description": "当前场景是否需要验证", - "type": "boolean" - }, - "providerCode": { - "description": "第三方服务商编码", - "type": "string" - }, - "captchaType": { - "description": "验证码类型,例如SLIDER、ROTATE、CONCAT、WORD_IMAGE_CLICK", - "type": "string" - }, - "challengeId": { - "description": "验证挑战ID,由验证挑战接口返回", - "type": "string" - }, - "uuid": { - "type": "string", - "description": "系统图形验证码 uuid" - }, - "img": { - "type": "string", - "description": "系统图形验证码 base64 图片" - }, - "payload": { - "type": "object", - "additionalProperties": true, - "description": "天爱验证码数据,包括背景图、模板图、尺寸等", - "properties": {} - }, - "expireSeconds": { - "description": "验证挑战过期秒数", - "type": "integer" - } - }, - "examples": [ - { - "required": true, - "providerCode": "tianai", - "captchaType": "SLIDER", - "challengeId": "CAPTCHA_CHALLENGE_ID", - "payload": { - "backgroundImage": "data:image/png;base64,...", - "templateImage": "data:image/png;base64,...", - "backgroundImageWidth": 340, - "backgroundImageHeight": 180 - }, - "expireSeconds": 300 - } - ] - }, - "RVerificationCheck": { - "type": "object", - "properties": { - "code": { - "description": "系统图形验证码或响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string", - "examples": [ - "操作成功" - ] - }, - "data": { - "description": "响应数据主体", - "$ref": "#/components/schemas/VerificationCheckVo" - } - } - }, - "VerificationCheckVo": { - "type": "object", - "properties": { - "passed": { - "description": "验证是否通过", - "type": "boolean" - }, - "validToken": { - "type": "string", - "description": "验证通过后交给发送短信验证码接口的票据" - }, - "expireSeconds": { - "description": "验证挑战过期秒数", - "type": "integer" - }, - "message": { - "description": "返回消息", - "type": "string" - } - }, - "examples": [ - { - "passed": true, - "validToken": "captcha-ticket", - "expireSeconds": 300, - "message": "验证通过" - } - ] - }, - "RFamilyFeedComment": { - "type": "object", - "properties": { - "code": { - "description": "响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string" - }, - "data": { - "$ref": "#/components/schemas/FamilyFeedCommentView" - } - } - }, - "FamilyFeedCommentView": { - "type": "object", - "properties": { - "commentId": { - "description": "评论 ID", - "type": "integer", - "format": "int64" - }, - "genealogyId": { - "description": "所属家谱 ID", - "type": "integer", - "format": "int64" - }, - "feedId": { - "description": "所属动态 ID", - "type": "integer", - "format": "int64" - }, - "parentCommentId": { - "description": "父评论 ID;null 或 0 表示一级评论", - "type": [ - "integer", - "null" - ], - "format": "int64" - }, - "appUserId": { - "description": "评论业务用户 ID", - "type": "integer", - "format": "int64" - }, - "appUserNickName": { - "description": "评论人昵称", - "type": "string" - }, - "appUserAvatar": { - "description": "评论人头像文件 ID", - "type": [ - "integer", - "null" - ], - "format": "int64" - }, - "parentAppUserId": { - "description": "被直接回复的业务用户 ID", - "type": [ - "integer", - "null" - ], - "format": "int64" - }, - "parentAppUserNickName": { - "description": "被直接回复的用户昵称", - "type": [ - "string", - "null" - ] - }, - "commentContent": { - "description": "评论内容;作者删除后为 null", - "type": [ - "string", - "null" - ] - }, - "userDeleted": { - "description": "作者删除状态,0 未删除,1 已删除并保留占位", - "type": "string", - "enum": [ - "0", - "1" - ], - "examples": [ - "0" - ] - }, - "replyCount": { - "description": "正常展示的直接回复数量", - "type": "integer", - "format": "int64" - }, - "commentLevel": { - "description": "评论层级,root 为一级评论,reply 为回复", - "type": "string", - "enum": [ - "root", - "reply" - ] - }, - "status": { - "description": "字典 sys_normal_disable:0=正常展示,1=停用;会员接口仅返回0", - "type": "string", - "enum": [ - "0", - "1" - ], - "x-dict-type": "sys_normal_disable", - "examples": [ - "0" - ] - }, - "createTime": { - "description": "评论发布时间", - "type": "string", - "format": "date-time" - } - } - }, - "RFamilyFeedCommentList": { - "type": "object", - "properties": { - "code": { - "description": "响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string" - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FamilyFeedCommentView" - } - } - } - }, - "FamilyFeedCommentPageResult": { - "type": "object", - "properties": { - "code": { - "description": "响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string" - }, - "rows": { - "description": "评论分页数据", - "type": "array", - "items": { - "$ref": "#/components/schemas/FamilyFeedCommentView" - } - }, - "total": { - "description": "评论总数", - "type": "integer", - "format": "int64" - } - } - }, - "RFamilyFeed": { - "type": "object", - "properties": { - "code": { - "description": "响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string", - "examples": [ - "操作成功" - ] - }, - "data": { - "$ref": "#/components/schemas/FamilyFeedView" - } - } - }, - "FamilyFeedView": { - "type": "object", - "properties": { - "feedId": { - "description": "动态ID", - "type": "integer", - "format": "int64" - }, - "genealogyId": { - "description": "所属家谱ID", - "type": "integer", - "format": "int64" - }, - "genealogyNo": { - "description": "家谱编号", - "type": "string" - }, - "genealogyName": { - "description": "家谱名称", - "type": "string" - }, - "publisherUserId": { - "description": "发布业务用户ID", - "type": "integer", - "format": "int64" - }, - "publisherNickName": { - "description": "发布用户昵称", - "type": "string" - }, - "publisherStatus": { - "description": "发布用户状态", - "type": "string" - }, - "feedType": { - "description": "动态类型", - "type": "string" - }, - "feedContent": { - "description": "动态内容", - "type": "string" - }, - "mediaOssIds": { - "description": "媒体文件OSS ID列表,多个ID使用英文逗号分隔", - "type": "string" - }, - "likedByMe": { - "description": "当前登录用户是否已点赞", - "type": "boolean", - "examples": [ - false - ] - }, - "likeCount": { - "description": "点赞数量", - "type": "integer", - "format": "int64" - }, - "commentCount": { - "description": "评论数量", - "type": "integer", - "format": "int64" - }, - "pinned": { - "description": "字典 gen_number_yes_no:0=否,1=是", - "type": "string", - "enum": [ - "0", - "1" - ], - "x-dict-type": "gen_number_yes_no", - "examples": [ - "0" - ] - }, - "pinnedTime": { - "description": "置顶时间", - "type": [ - "string", - "null" - ], - "format": "date-time" - }, - "sortOrder": { - "description": "排序值", - "type": "integer", - "format": "int64" - }, - "status": { - "description": "字典 sys_normal_disable:0=正常,1=停用", - "type": "string", - "enum": [ - "0", - "1" - ], - "x-dict-type": "sys_normal_disable", - "examples": [ - "0" - ] - }, - "remark": { - "description": "备注", - "type": "string" - }, - "createTime": { - "description": "发布时间", - "type": "string", - "format": "date-time" - }, - "updateTime": { - "description": "更新时间", - "type": "string", - "format": "date-time" - } - } - }, - "RFamilyFeedList": { - "type": "object", - "properties": { - "code": { - "description": "响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string", - "examples": [ - "操作成功" - ] - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FamilyFeedView" - } - } - } - }, - "FamilyFeedPageResult": { - "type": "object", - "properties": { - "code": { - "description": "响应状态码", - "type": "integer", - "examples": [ - 200 - ] - }, - "msg": { - "description": "响应消息", - "type": "string", - "examples": [ - "操作成功" - ] - }, - "rows": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FamilyFeedView" - } - }, - "total": { - "description": "总记录数", - "type": "integer", - "format": "int64", - "examples": [ - 0 - ] - } - } - } - }, - "responses": { - "VoidResult": { - "description": "通用成功响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RVoid" - } - } - } - }, - "ObjectResult": { - "description": "通用对象响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RObject" - } - } - } - }, - "GenealogyQuotaResult": { - "description": "当前业务用户家谱配额响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RGenealogyQuota" - } - } - } - }, - "LongResult": { - "description": "长整型数据响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RLong" - } - } - } - }, - "ListResult": { - "description": "通用列表响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RList" - } - } - } - }, - "CeremonyInvitationResult": { - "description": "贺礼邀约详情响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RCeremonyInvitation" - } - } - } - }, - "CeremonyInvitationListResult": { - "description": "贺礼邀约列表响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RCeremonyInvitationList" - } - } - } - }, - "GenerationPoemResult": { - "description": "字辈详情响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RGenerationPoem" - } - } - } - }, - "GenerationPoemListResult": { - "description": "字辈列表响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RGenerationPoemList" - } - } - } - }, - "GenerationPoemBatchPreviewResult": { - "description": "字辈批量预览响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RGenerationPoemBatchPreview" - } - } - } - }, - "LineagePersonResult": { - "description": "世系人物详情响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RLineagePerson" - } - } - } - }, - "LineagePersonListResult": { - "description": "世系人物列表或选项响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RLineagePersonList" - } - } - } - }, - "LineagePersonTreeResult": { - "description": "世系树响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RLineagePersonTreeList" - } - } - } - }, - "LineagePersonPageResult": { - "description": "世系人物分页响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LineagePersonPageResult" - } - } - } - }, - "LoginResult": { - "description": "登录结果", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RLogin" - } - } - } - }, - "FileUploadResult": { - "description": "文件上传结果", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RFileUpload" - } - } - } - }, - "VerificationRequireResult": { - "description": "验证需求查询结果", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RVerificationRequire" - } - } - } - }, - "VerificationChallengeResult": { - "description": "验证挑战生成结果", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RVerificationChallenge" - } - } - } - }, - "VerificationCheckResult": { - "description": "验证校验结果", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RVerificationCheck" - } - } - } - }, - "FamilyFeedCommentResult": { - "description": "家族圈评论对象响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RFamilyFeedComment" - } - } - } - }, - "FamilyFeedCommentListResult": { - "description": "家族圈评论列表响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RFamilyFeedCommentList" - } - } - } - }, - "FamilyFeedCommentPageResult": { - "description": "家族圈评论分页响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FamilyFeedCommentPageResult" - } - } - } - }, - "FamilyFeedResult": { - "description": "家族圈动态对象响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RFamilyFeed" - } - } - } - }, - "FamilyFeedListResult": { - "description": "家族圈动态列表响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RFamilyFeedList" - } - } - } - }, - "FamilyFeedPageResult": { - "description": "家族圈动态分页响应", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FamilyFeedPageResult" - } - } - } - } - }, - "securitySchemes": { - "SaToken1": { - "type": "apiKey", - "in": "header", - "name": "Authorization", - "description": "登录后返回的 token。若本地配置使用 sa-token 名称,请按项目实际 token-name 调整。" - } - } - }, - "servers": [], - "security": [ - { - "SaToken1": [] - } - ] -} \ No newline at end of file diff --git a/design-qa.md b/design-qa.md deleted file mode 100644 index 81b6626..0000000 --- a/design-qa.md +++ /dev/null @@ -1,39 +0,0 @@ -# 首页设计还原 QA - -## 对照基准 - -- 设计源:用户在当前对话提供的桌面首页设计图,红色文字和箭头作为替换与结构调整标注。 -- 实现基线:原首页 `index-modern.html` 的品牌色、字体、卡片质感和 `public/js/page-effects.js` 交互。 -- 桌面审查截图:`artifacts/homepage-final-review-full.png`,CSS 视口 1440 × 2200,DPR 1。 -- 移动审查截图:`artifacts/homepage-final-review-mobile.png`,CSS 视口 500 × 1600,DPR 1。 -- 页面状态:未登录初始状态。 - -## 结构对照 - -- 页面沿用原首页 1280px 宽屏容器,顺序为双层导航、首屏、四个主入口、第一条九格广告栏、六个内容入口、推荐与友情链接、第二条九格广告栏、页脚。 -- 已删除设计图没有的家谱广场、文章列表、姓氏百科、相关推荐大卡、下载大区块。 -- “我的家谱”是独立强调入口,链接 `profile-families.html`,承担查看、新建和管理全部家谱。 -- 红色标注按替换文案处理;被替换的旧标题没有在同一卡片中保留。 - -## 视觉与交互对照 - -- 保留原首页的暖纸色、朱红渐变导航、宋体标题、黑体正文、宽屏留白、圆角卡片、层次阴影与半透明面板。 -- 首屏按设计图重组为正文、二维码、原有蓝色家谱封面、祠堂和家族记忆图片。 -- 保留原首页的 `tilt-card` 指针倾斜、按钮磁吸、图片悬停缩放、移动菜单和 reduced-motion 行为。 -- 桌面页脚链接与备案信息已恢复居中,不受原全局链接布局影响。 -- 主入口图片按左侧内容定位,避免服务、视频和相册标题被居中裁切;入口区、内容卡和广告栏已压缩无效留白。 -- 两组九格广告栏均使用项目已有广告图片,不再显示文字占位块。 -- 页脚链接区改为完整浅色横条,消除了深色背景在两侧提前露出的断层。 - -## 响应式与可访问性 - -- 1120px 以下主入口和广告栏分栏;720px 以下首屏、入口和内容卡改为单列。 -- 浏览器导航结构专项检查通过,移动断点继续沿用原首页规则。 -- 图片提供替代文本;二维码入口、导航、广告位和内容卡均使用链接语义;焦点样式沿用公共样式。 - -## 剩余差异 - -- P3:设计图右下角为多人家族合影,仓库没有对应素材,当前继续使用原有家族记忆图片。 -- P3:原首页交互动效在截图中不可见,但已通过复用原脚本和交互类保留。 - -final result: passed diff --git a/design.md b/design.md deleted file mode 100644 index 1f4d528..0000000 --- a/design.md +++ /dev/null @@ -1,95 +0,0 @@ -# Design · 代代相传家谱 PC - -这是 PC 家谱项目的统一设计系统。所有页面沿用同一套品牌、排版、间距、状态和交互规则;新增规则先修改本文件,不在单个页面创建另一套视觉语言。 - -## 用户与任务 - -- 用户:家谱创建者、管理员、普通家人和浏览公开家谱的访客。 -- 核心任务:创建或加入家谱,维护世系与成员,发布并浏览家族内容,处理邀请、消息与服务。 -- 设计气质:中文编辑式、克制、可信。功能页以任务效率为先,公共页保留家族文化氛围。 - -## Genre - -Editorial。登录后的应用页使用更紧凑的工作台表达,不采用营销页式的大标题和卡片堆叠。 - -## Macrostructure family - -- 公开与营销页面:Content Portal。真实内容和图片优先,标题区只说明当前任务。 -- 登录后应用页面:Workbench。固定页头、稳定的分组侧栏和单一内容画布;跨页面导航位置不改变。 -- 内容与资料页面:Long Document / List Detail。列表进入独立详情页,桌面端需要连续比较时允许页内列表详情。 - -## Theme - -- `--color-paper`: `oklch(97.5% 0.012 82)` -- `--color-paper-2`: `oklch(94.8% 0.024 78)` -- `--color-ink`: `oklch(31% 0.028 158)` -- `--color-ink-2`: `oklch(51% 0.022 158)` -- `--color-rule`: `oklch(86% 0.028 78)` -- `--color-accent`: `oklch(56% 0.185 29)` -- `--color-accent-strong`: `oklch(43% 0.13 29)` -- `--color-focus`: `oklch(61% 0.17 29)` -- 辅助绿色和金色仅用于状态与文化信息,不与主操作竞争。 - -## Typography - -- Display:`STSong`, `Songti SC`, `SimSun`, serif;字重 600;正常体。 -- Body:`Microsoft YaHei`, `PingFang SC`, system-ui, sans-serif;字重 400/600。 -- Mono:`Consolas`, `SFMono-Regular`, monospace,仅用于编号和技术值。 -- 标题不得使用斜体,不保留无业务意义的英文眉题。 -- 正文舒适行宽为 62~76 个中文字符。 - -## Spacing - -使用根目录 `tokens.css` 中的 4px 命名刻度。新样式只能使用 `--space-*`,页面内容区桌面最大宽度统一为 `--layout-max`。 - -## Motion - -- 动效只表达悬停、焦点、展开和状态变化,最多使用透明度与 `transform`。 -- 默认 160~220ms,无弹跳和持续漂浮。 -- `prefers-reduced-motion` 下关闭空间位移并将反馈压缩为短暂透明度变化。 - -## Interaction decisions - -- 独立页面:多字段创建/编辑、详情阅读、支付、权限和安全设置。 -- 对话框:删除/退出等不可逆确认、密码输入、单一短任务;统一使用站内对话框,不调用浏览器原生 `confirm`/`prompt`。 -- 图片灯箱:相册和内容图片预览;不改变当前列表位置。 -- 页内展开:帮助问答、筛选、非关键补充信息。 -- 抽屉:桌面端评论或需要保留主内容上下文的辅助任务;不承载长表单。 -- 加载、空数据、错误、无权限:显示在对应内容区域,不使用弹窗打断。 -- 成功反馈:优先更新页面状态;仅在用户可能看不到变化时使用短消息。 - -## Media rules - -- 业务媒体只消费后端 `BusinessFileAccess.accessUrl`,不得拼接 OSS ID 或暴露内部路径。 -- 家谱封面 16:9;相册封面 4:3;文章封面 16:9;头像 1:1;动态媒体根据原图比例限制最大高度。 -- 有地址时显示真实图片;无图片时使用姓名首字或明确空状态,不使用伪造照片、CSS 图画或无意义占位块。 -- 图片必须有与业务内容对应的替代文本;装饰图片使用空替代文本。 -- 正文附件使用左对齐、最大 280px 的缩略图网格并完整显示原图;封面和视频预览按业务比例完整呈现,不把单张附件拉伸成整栏大图。 -- 视频使用原生播放器,列表中只自动加载封面,不自动播放。 - -## Shared states - -所有交互控件必须覆盖默认、悬停、键盘焦点、按下、禁用、加载、错误、成功状态。错误信息靠近问题区域,同时保留可恢复操作。 - -## Responsive - -- 验证宽度:320、375、414、768、1024、1440。 -- 768px 以下侧栏转为页内分组导航,主内容单列。 -- `html` 与 `body` 使用 `overflow-x: clip`;按钮和主导航文字不得折成两行。 -- 附件网格桌面端按 200–280px 自适应列展示,移动端保持最大 280px;标题允许长家谱名称安全换行。 - -## What pages MUST share - -- 品牌标识、朱红主操作色、排版和按钮语气。 -- 页头高度、内容最大宽度、侧栏位置、表单控件高度和状态样式。 -- 图片比例、媒体错误回退、空状态和权限状态。 - -## Per-page allowances - -- 公开页可使用仓库内已有的真实祠堂和服务图片。 -- 应用页不增加装饰插画,数据与操作本身承担视觉层级。 -- 内容页以标题、正文、真实封面和元信息为主。 - -## Exports - -实际 CSS 变量以根目录 `tokens.css` 为准。项目为原生 HTML/CSS/JS,不启用 Tailwind 或 shadcn;如未来迁移,颜色、字体、间距必须从相同命名 token 映射,不得另建品牌值。 diff --git a/dist/about.html b/dist/about.html deleted file mode 100644 index a622cc5..0000000 --- a/dist/about.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - 关于我们 - 代代相传 - - - - - - -
-
-
-
-
关于我们
-

用数字化方式守护家族记忆

-

- 代代相传面向家庭、宗亲会与地方文化组织,提供家谱创建、资料管理、文化展示和亲人协作工具。 -

-
- 数字家谱亲人协作文化传承 -
-
-
-
- -

记录家族,连接亲人,传承文化。

-
-
-
- -
-
-
-

我们重视

-

让家族资料真实、清晰、可协作,也能长期传下去。

-
-
-
-
-

真实

-

尊重家族资料来源,鼓励记录出处与修订过程。

-
-
-
-

协作

-

让更多亲人参与补充资料,形成长期维护机制。

-
-
-
-

传承

-

把家族文化整理成可持续保存的数字档案。

-
-
-
-
-
- - - - - - diff --git a/dist/app.html b/dist/app.html deleted file mode 100644 index 8996891..0000000 --- a/dist/app.html +++ /dev/null @@ -1,154 +0,0 @@ - - - - - - 应用下载 - 代代相传 - - - - - - -
-
-
-
-
移动应用
-

手机端随时记录,电脑端清晰浏览

-

- 家族成员可以在手机端上传照片、查看家谱、发布动态;电脑端适合浏览门户内容和公开家谱。 -

- -
- 相册上传视频发布家谱查看 -
-
-
-
- APP首页截图 - 家谱主页截图 -
-
-
- -
-
-
-

移动端能力

-

- 手机端适合随时记录家族资料、上传影像并与亲人共同维护。 -

-
-
-
-
-

创建家谱

-

手机上快速创建或加入家族空间。

-
-
-
-

宣传相册

-

随手上传老照片、祖屋照片和聚会照片。

-
-
-
-

宣传视频

-

发布家族活动与人物影像。

-
-
-
-

谱文详情

-

阅读谱序、传记和家族文章。

-
-
-
-
-
-
-
-

应用推广

-

展示后台维护的应用推广、公告和下载引导内容。

-
-
-
正在读取应用推广…
-
-
-
-
- - - - - - - - - - - - - diff --git a/dist/article-detail.html b/dist/article-detail.html deleted file mode 100644 index b8b4bb7..0000000 --- a/dist/article-detail.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - 资讯详情 - 代代相传 - - - - - - -
-
-
-
-
正在读取资讯详情…
-
- - - - - - - - - - - - diff --git a/dist/children-privacy.html b/dist/children-privacy.html deleted file mode 100644 index d367156..0000000 --- a/dist/children-privacy.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - 儿童个人信息处理规则 - 代代相传 - - - - - -
-
-
-
-
儿童隐私保护
-

儿童个人信息处理规则

-

用于说明儿童个人信息的处理边界、监护人权利和保护措施。

-
-
-
-
-
-
-
-
- 待法务确认:本页面仅建立儿童个人信息规则的访问入口,不构成已生效的规则。发布前必须由运营主体与法务确认适用年龄、监护人授权与完整文本。 -
-

正式文本应涵盖

-

- 儿童信息处理目的、必要性、监护人同意方式、信息访问更正删除渠道,以及发生个人信息安全事件时的告知安排。 -

-
- -
-
-
- - - - - diff --git a/dist/config.js b/dist/config.js deleted file mode 100644 index e868646..0000000 --- a/dist/config.js +++ /dev/null @@ -1 +0,0 @@ -(function(n,i){if(typeof module=="object"&&module.exports){module.exports=i;return}n.GenealogyConfig=i(n)})(typeof globalThis!="undefined"?globalThis:window,function(n){"use strict";var i="ced7e5f0498645c6ec642dcf450b036f",c="000000",p="genealogy_auth_token",o=/^(?:javascript|data|vbscript):/i;function u(){return{open:function(t){var e=String(t||"").trim();if(!e||o.test(e)||typeof n.open!="function")return null;var r=n.open(e,"_blank","noopener");return r&&(r.opener=null),r},replace:function(t){var e=String(t||"").trim();!e||o.test(e)||n.location&&typeof n.location.replace=="function"&&n.location.replace(e)}}}var f={development:{apiBaseUrl:"https://backend-api.ddxcjp.cn/"},production:{apiBaseUrl:"https://backend-api.ddxcjp.cn/"}};function a(){var t=n.location||{},e=t.hostname||"";return e==="localhost"||e==="127.0.0.1"||t.port?"development":"production"}function l(){return f[a()].apiBaseUrl}function d(){return{environment:a(),apiBaseUrl:l(),clientId:i,tenantId:c,tokenKey:p}}return n.NavigationUtil=n.NavigationUtil||u(),{getEnvironment:a,getApiBaseUrl:l,getConfig:d}}); diff --git a/dist/create-genealogy.html b/dist/create-genealogy.html deleted file mode 100644 index 1052765..0000000 --- a/dist/create-genealogy.html +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - 创建家谱 - 代代相传 - - - - - - -
-
-
-
-
创建家谱
-

创建属于家族的数字空间

-

- 填写姓氏谱名、地区与简介,先建立家谱主页,再逐步邀请亲人共同完善资料。 -

-
- 基础资料成员世系亲人共修 -
-
-
-
-

创建流程

-
-

1填写基础信息

-

2添加第一批成员

-

3邀请亲人共修

-
-
-
-
- -
-
-
-
-

家谱信息

-

- 标有 * 的字段为必填。登录后即可创建,成功后会进入新家谱主页。 -

-

正在读取家谱额度…

-
- - -
- 家谱所在地 -
- - - - -
-
-
- - - - - - -
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - diff --git a/dist/culture.html b/dist/culture.html deleted file mode 100644 index 594df0b..0000000 --- a/dist/culture.html +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - 家族文化 - 代代相传 - - - - - - -
-
-
-
-
家族文化
-

让家风、族训与故事被认真保存

-

- 家族文化承接新闻、知识、公告和故事,让谱文之外的记忆也能被阅读、分享和传承。 -

-
- 家风家训谱文故事使用指南 -
-
-
-
- 家风 -

- 一张老照片、一段口述、一篇谱序,都可以成为后人理解家族的入口。 -

-
-
-
- -
- -
-
- - - - - - diff --git a/dist/download.html b/dist/download.html deleted file mode 100644 index a2518fa..0000000 --- a/dist/download.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - 下载应用 - 代代相传 - - - - - -
-
-
-
-
应用下载
-

下载代代相传 APP

-

- 手机端适合日常上传宣传相册、发布宣传视频、查看家谱和补充资料。 -

- -
-
-

选择下载方式

-

请在下方选择平台当前提供的可用下载入口。

-
-
-
-
-
-
-

下载方式

-

下载地址会根据平台当前配置显示;没有可用入口时会明确提示。

-
-
-
下载推广内容加载中
-
-
-
-
- - - - - - - - - diff --git a/dist/family-detail.html b/dist/family-detail.html deleted file mode 100644 index e1e9d3c..0000000 --- a/dist/family-detail.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - 四川武胜汤氏族 - 家谱详情 - 代代相传 - - - - - - -
-
-
-
-
家谱详情
-

四川武胜汤氏族

-

- 公开展示家族源流、世系概览、字辈排行、谱文资料和宣传相册,让访客快速理解这一本家谱的内容结构。 -

- -
-
- 公开家谱 -

汤氏族谱

-
- 128成员 - 6世代 - 18谱文 - 7相册 -
-
-
-
- -
-
- -
-
-

家族简介

-

- 本谱以四川武胜汤氏族人为主体,整理始祖源流、迁徙线索、字辈排行、重要人物与家风故事。公开内容用于展示家族文化,完整成员资料需申请加入后查看。 -

-
-
-
-

世系概览

-

按代际关系梳理成员结构,帮助族人找到自己的位置。

-
-
-

谱文资料

-

收录谱序、家训、修谱说明和公开纪念文章。

-
-
-

宣传相册

-

展示祠堂祖屋、修谱活动、老照片和家族纪念内容。

-
-
-
-
-
-
- - - - - - - - - - diff --git a/dist/forbidden.html b/dist/forbidden.html deleted file mode 100644 index 92e16b1..0000000 --- a/dist/forbidden.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - 无访问权限 - 代代相传 - - - - - -
-
-
-
-
!
-

无访问权限

-

- 当前账号没有访问此内容的权限。请确认登录账号,或联系家谱管理员处理。 -

- -
-
-
-
- - - - - diff --git a/dist/forgot-password.html b/dist/forgot-password.html deleted file mode 100644 index ad21b23..0000000 --- a/dist/forgot-password.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - 找回密码 - 代代相传 - - - - - - -
- -
-
找回密码
-

找回密码

-

输入注册手机号,获取验证码后重设登录密码。

-
- -
- -
- - -
-
- - - -
- -
-
-
- - - - - - - - - - - - - - - - diff --git a/dist/genealogy.html b/dist/genealogy.html deleted file mode 100644 index 93454cc..0000000 --- a/dist/genealogy.html +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - 数字家谱 - 代代相传 - - - - - - -
-
-
-
-
数字家谱
-

把成员关系整理成清晰可查的数字家谱

-

- 从始祖、分支、排行到个人资料,数字家谱帮助家族建立长期可维护的成员档案和世系关系。 -

-
-
8资料模块
-
3步快速建谱
-
多人协作共修
-
-
-
-
-
始祖
-
-
- 长房二房三房 -
-
-
- 第六世第七世第八世第九世 -
-
-
-
- -
-
-
-

核心能力

-

- 围绕修谱、查谱和协作维护,把家族资料从零散记录整理成完整档案。 -

-
-
-
-
-

世系图

-

- 以树谱方式展示成员关系,支持查看个人资料、添加亲属和调整排行。 -

-
-
-
-

字辈谱

-

记录各代字辈与人数,让世代脉络更加清楚。

-
-
-
-

谱文资料

-

沉淀谱序、家训、人物传记和修谱说明。

-
-
-
-

权限管理

-

设置管理员、内容可见范围与入谱审核,保护家族隐私。

-
-
-
-
- -
-
-
-

适合多人共修

-

- 家族成员可以分别补充照片、成员资料、谱文和故事,再由管理员统一审核,避免资料越传越散。 -

- 开始创建 -
-
-

修谱流程

-
-

1创建家谱并填写姓氏、地区、堂号

-

2录入第一批成员,建立上下辈关系

-

3邀请亲人加入,共同完善资料

-
-
-
-
-
- - - - - - diff --git a/dist/help.html b/dist/help.html deleted file mode 100644 index 2718c16..0000000 --- a/dist/help.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - 帮助中心 - 代代相传 - - - - - - -
-
-
-
-
帮助中心
-

从创建到协作,快速解决常见问题

-

- 帮助内容由平台持续维护,覆盖账号、家谱和内容使用中的常见问题。 -

-
- 账号安全家谱使用内容管理 -
-
-
-
-
?
-

需要人工协助?

-

- 遇到资料导入、权限或账号问题,可以提交说明,客服会协助处理。 -

- 提交工单 - 我的工单 -
-
-
- -
-
-
-

常见问题

-

选择问题后读取平台发布的完整解答。

- -
-
-
-
正在读取帮助文章…
-
-
-
-
- - - - - - - - - - - - diff --git a/dist/index.html b/dist/index.html deleted file mode 100644 index f6d77b8..0000000 --- a/dist/index.html +++ /dev/null @@ -1,556 +0,0 @@ - - - - - - 代代相传家谱_在线修家谱_家谱查询_姓氏寻根_祠堂祭祀 - - - - - - -
-
富众鑫科技
-
- - -
-
-
-
-
数字家谱与家族文化门户
-

- 一部会生长的
数字家谱 -

-

- 把姓氏源流、世系关系、字辈排行、宣传相册、谱文故事与亲人动态整理在一起,让家族的来处清晰可见,让记忆可以继续传下去。 -

-
-
正在记录
-
-
-
寻根问祖
-
字辈排行
-
世系关系
-
宣传相册
-
谱文故事
-
亲人动态
-
寻根问祖
-
字辈排行
-
世系关系
-
宣传相册
-
谱文故事
-
亲人动态
-
-
-
-
- 1创建家谱2邀请亲人3共同传承 -
- -
-
家谱广场浏览公开家族
-
姓氏百科了解姓氏源流
-
家族文化阅读传承故事
-
-
-
-
-
-
- 家族祠堂 -
- - 宣传影像 - 观看宣传视频 - -
-

查找公开家谱

- - 输入姓氏、地区或家谱名称搜索 - -
-
- 四川成都陈氏族共修入 128 人 -
-
- 湖广刘氏家谱收录 6 代字辈 -
-
- 岭南张氏宗谱开放谱文 18 篇 -
-
-
-
-
-
- -
- -
- -
-
-
-

数字家谱核心功能

-
-
-
-
-

世系图

-

- 以树谱方式展示成员关系,支持查看个人资料、添加亲属、调整排行。 -

-
-
-
-

字辈谱

-

记录各代字辈与人数,让家族世代脉络更加清楚。

-
- -
-

宣传相册

-

按相册归档宣传影像,展示祖屋、活动、老照片和重要时刻。

-
-
-
-

谱文资料

-

沉淀谱序、谱文、家训、恩荣录等文本内容。

-
-
-
-

家族圈

-

发布动态、分享图片、评论互动,让亲人之间保持连接。

-
-
-
-

功德录

-

记录家族贡献人物与事迹,让善行与纪念被看见。

-
-
-
-

贺礼邀请

-

发布婚礼、生日、升学等家族喜事,通知相关成员。

-
-
-
-

权限管理

-

设置管理员、内容可见范围与入谱审核,保护家族隐私。

-
-
-
-
- -
- -
- -
- -
- -
- -
- -
-
-
-
-

姓氏百科

-

- 以姓氏为入口,整理姓氏源流、名人故事、地区分布、字辈参考和相关家谱。 -

- 进入姓氏百科 -
-
- - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
- -
- -
- - - -
-
-
-
-

手机端随时记录,电脑端清晰浏览

-

- 家族成员可以在手机端上传照片、查看家谱、发布动态;电脑端适合浏览门户内容、查看公开家谱和进入家谱空间。 -

- -
-
-
-
-
- - - - - - diff --git a/dist/join-genealogy.html b/dist/join-genealogy.html deleted file mode 100644 index 70405db..0000000 --- a/dist/join-genealogy.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - 加入家谱 - 个人中心 - 代代相传 - - - - - - - -
-
-
-
我的家谱
-
-
-

加入家谱

-

先搜索并选择要加入的家谱,再补充申请资料;家谱编号由系统自动处理。

-
- 查看我的申请 -
-
-
-
-
- -
-
-
-
-

选择家谱

-
- - -
-
-
-

尚未选择家谱

-
-
-

补充申请资料

-

以下资料均为选填,审核人将据此确认您的加入申请。

- - - - -
- -
-
-
-
-
-
-
- - - - - - - - - - - - - diff --git a/dist/login.html b/dist/login.html deleted file mode 100644 index 6a4449f..0000000 --- a/dist/login.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - 登录 - 代代相传 - - - - - - -
- -
-
欢迎回来
-

登录账号

-

登录后可创建、加入和维护家谱。

- - - - -
-
- - - - - - - - - - - - - - - - diff --git a/dist/maintenance.html b/dist/maintenance.html deleted file mode 100644 index f9e13da..0000000 --- a/dist/maintenance.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - 系统维护中 - 代代相传 - - - - - -
-
-
-
-
-

系统维护中

-

- 我们正在进行必要的服务维护,请稍后再试。账号与家谱资料不会因维护而丢失。 -

- -
-
-
-
- - - - - diff --git a/dist/my-tickets.html b/dist/my-tickets.html deleted file mode 100644 index c7927b9..0000000 --- a/dist/my-tickets.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - 我的工单 - 个人中心 - 代代相传 - - - - - - -
-
-
-
服务与帮助
-
-

我的工单

查看当前账号提交的问题说明和处理状态。

- 提交新工单 -
-
-
-
-
- -
-
-
-

反馈记录

选择记录可以查看完整说明和处理状态。

- -
-
-
-
正在读取反馈记录…
-
-
-
-
-
-
- - - - - - - - - - - - - diff --git a/dist/news.html b/dist/news.html deleted file mode 100644 index 4442d6f..0000000 --- a/dist/news.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - 新闻资讯 - 代代相传 - - - - - -
-
-
-
-
资讯中心
-

新闻资讯

-

- 集中阅读平台新闻、公告与下载内容。 -

-
-
-
-
-
-
-
-
-
-
正在读取资讯…
-
-
- -
-
-
- - - - - - - - - - - diff --git a/dist/not-found.html b/dist/not-found.html deleted file mode 100644 index d57781e..0000000 --- a/dist/not-found.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - 页面不存在 - 代代相传 - - - - - -
-
-
-
-
404
-

页面不存在

-

你访问的页面可能已移动、已删除或地址输入有误。

- -
-
-
-
- - - - - diff --git a/dist/notice-detail.html b/dist/notice-detail.html deleted file mode 100644 index a421887..0000000 --- a/dist/notice-detail.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - 平台公告详情 - 代代相传 - - - - - -
-
-
-
-
平台公告
-

平台内容公开展示规范说明

-

- 为了保护家族资料安全,平台仅展示管理员确认公开的家谱介绍、文章、宣传相册和宣传视频。 -

- -
-
- -

公开有边界,传承更长久。

-
-
-
-
-
-
-

- 平台前台页面主要用于展示家族文化与公开资料。涉及成员联系方式、详细个人信息、未审核资料等内容,不会在公开页面直接呈现。 -

-

公开内容范围

-

- 可公开内容包括家族简介、姓氏源流、谱文节选、宣传相册、宣传视频、平台公告和管理员允许展示的纪念内容。 -

-

资料维护建议

-

- 建议由管理员先整理资料,再邀请亲人补充和校对。需要修改公开范围时,可以在权限设置中调整。 -

-
- -
-
-
- - - - - - - - - diff --git a/dist/plaza.html b/dist/plaza.html deleted file mode 100644 index 4e69088..0000000 --- a/dist/plaza.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - 家谱广场 - 代代相传 - - - - - - -
-
-
-
-
家谱广场
-

浏览公开家谱,寻找同宗线索

-

- 按姓氏、地区、堂号和家谱名称检索公开家谱,先看示例,再创建自己的家族空间。 -

-
- 128+公开家谱 - 36地区线索 - 18谱文示例 -
-
-
-
-

查找公开家谱

-
- 搜索 -
-
- 公开家谱字辈完整多人共修 -
-
-
-
- -
- -
-
- - - - - - - - - - diff --git a/dist/privacy.html b/dist/privacy.html deleted file mode 100644 index 83fe027..0000000 --- a/dist/privacy.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - 隐私政策 - 代代相传 - - - - - -
-
-
-
-
隐私政策
-

隐私政策

-

说明平台处理个人信息的规则、用户权利与联系渠道。

-
-
-
-
-
-
-
-
- 待法务确认:本页面仅建立政策访问入口,不构成已生效的隐私政策。发布前必须由运营主体与法务确认完整文本、版本号和生效日期。 -
-

正式文本应涵盖

-

- 个人信息处理目的、收集范围、保存期限、共享规则、用户查阅更正删除权利,以及个人信息保护负责人的联系渠道。 -

-
- -
-
-
- - - - - diff --git a/dist/profile-admin-permissions.html b/dist/profile-admin-permissions.html deleted file mode 100644 index f03dc1f..0000000 --- a/dist/profile-admin-permissions.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - 管理员权限 - 个人中心 - 代代相传 - - - - - - -
-
-
-
权限设置
-
-
-

管理员权限

-

- 在成员名册中维护管理员、编辑和成员角色;可执行的操作以当前账号权限为准。 -

-
-
-
-
-
-
- -
-
-

当前权限

-
-
正在核对当前家谱角色与权限…
-
-
-
-

角色与权限

-
-

谱主

可维护管理员、编辑和成员角色,并可转让谱主。

-

管理员

可维护编辑和成员角色,不能修改谱主或其他管理员。

-

编辑、成员

不显示成员角色编辑入口;最终权限由服务端校验。

-
- -
-
-
-
-
- - - - - - - - - - - - - diff --git a/dist/profile-album-detail.html b/dist/profile-album-detail.html deleted file mode 100644 index 15eeca6..0000000 --- a/dist/profile-album-detail.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - 相册详情 - 个人中心 - 代代相传 - - - - - - -
-
-
-
相册详情
-
-
-

相册详情

-

查看相册信息和照片;照片暂不可在线预览时,仍会保留名称和上传记录。

-
-
-
-
-
-
- -
-
-

相册信息

-
-
- 相册加载中... -
-
-
-
-

照片列表

-
-
- 照片加载中... -
-
-
- -
-
-
-
- - - - - - - - - - - - - - - - - diff --git a/dist/profile-album-edit.html b/dist/profile-album-edit.html deleted file mode 100644 index d771eba..0000000 --- a/dist/profile-album-edit.html +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - 编辑相册 - 个人中心 - 代代相传 - - - - - - -
-
-
-
编辑相册
-
-
-

新建相册

-

维护相册名称、说明和封面;封面编号由上传自动产生。

-
-
-
-
-
-
- -
-
-
正在读取家谱权限...
- -
-
-
-
-
- - - - - - - - - - - - - - - - - diff --git a/dist/profile-album.html b/dist/profile-album.html deleted file mode 100644 index 08cc671..0000000 --- a/dist/profile-album.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - 家族相册 - 个人中心 - 代代相传 - - - - - - -
-
-
-
家族相册
-
-
-

家族相册

-

查看和管理家谱相册;照片文件由上传组件自动关联,无需填写内部编号。

-
-
-
-
-
-
- -
-
-

相册列表

-
-
- 相册加载中... -
-
-
-
-
-
-
- - - - - - - - - - - - - - diff --git a/dist/profile-article-edit.html b/dist/profile-article-edit.html deleted file mode 100644 index 870103f..0000000 --- a/dist/profile-article-edit.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - 编辑谱文 - 个人中心 - 代代相传 - - - - - - - -
-
-
-
编辑谱文
-
-
-

新建谱文

-

- 使用富文本编辑器撰写谱文、谱序、谱论和恩荣录,支持图片、表格与排版。 -

-
-
-
-
-
-
- -
-
-

谱文内容

-
-
正在读取家谱权限…
-
- -
-
-
-
-
- - - - - - - - - - - - - - - - - - - diff --git a/dist/profile-article.html b/dist/profile-article.html deleted file mode 100644 index 243f190..0000000 --- a/dist/profile-article.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - 谱文管理 - 个人中心 - 代代相传 - - - - - - -
-
-
-
家族谱文
-
-
-

谱文管理

-

- 管理谱文、谱序、谱论、恩荣录等家族文献,支持添加内容、图片与落款。 -

-
-
-
-
-
-
- -
-
-

谱文列表

-
-
正在加载谱文…
-
-
-
-

谱文详情

-
-
请选择一篇谱文查看详情
-
-
-
-
-
-
- - - - - - - - - - - - - - diff --git a/dist/profile-ceremony-detail.html b/dist/profile-ceremony-detail.html deleted file mode 100644 index a9d8669..0000000 --- a/dist/profile-ceremony-detail.html +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - 活动详情 - 个人中心 - 代代相传 - - - - - - -
-
-
活动详情

活动详情

活动信息、祭品记录与管理员受邀名单。

-
-
-
- -
-
-

活动信息

-
-
- 正在加载活动详情… -
-
-
-
-

祭品记录

-
-
- 正在加载祭品… -
-
- -
- -
-
-
-
- - - - - - - - - - - - - - diff --git a/dist/profile-ceremony.html b/dist/profile-ceremony.html deleted file mode 100644 index f66cc82..0000000 --- a/dist/profile-ceremony.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - 祭祀活动 - 个人中心 - 代代相传 - - - - - - -
-
-
-
祭祀活动
-

祭祀活动

查看当前家谱的活动;具备内容维护权限时可创建、编辑和删除。

-
-
-
-
- -
-
-

活动列表

-
-
- 正在加载祭祀活动… -
-
-
-
-
-
-
- - - - - - - - - - - - - - diff --git a/dist/profile-content.html b/dist/profile-content.html deleted file mode 100644 index ea7b051..0000000 --- a/dist/profile-content.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - 内容发布 - 个人中心 - 代代相传 - - - - - - -
-
-
-
内容发布
-
-
-

内容发布

-

- 集中管理谱文、视频、相册、功德录、贺礼邀请、成长记录、家族动态和备忘录。 -

-
- 添加谱文 -
-
-
-
- -
-
- - - - - - - - - - - - diff --git a/dist/profile-create-family.html b/dist/profile-create-family.html deleted file mode 100644 index dbbf814..0000000 --- a/dist/profile-create-family.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - 创建家谱 - 个人中心 - 代代相传 - - - - - - -
-
-
-
创建家谱
-
-
-

创建家谱

-

- 填写家谱基本信息;标有 * 的字段为必填,创建后可进入家谱工作区继续维护。 -

-
-
-
-
-
-
- -
-
-

基础信息

-

正在读取家谱额度…

-
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
- 家族所在地 -
- - - - -
-
-
-
- - -
-
- - -
-
- - - - -

未选择文件

-
-
-
- - -
-
- - -

创建完成后,可在家谱首页的“邀请家人”中生成一次性邀请链接。

-
-
-
-
- - 返回列表 -
-
-
-
-
-
-
- - - - - - - - - - - - - - - - - diff --git a/dist/profile-data-reminders.html b/dist/profile-data-reminders.html deleted file mode 100644 index 2d02a4b..0000000 --- a/dist/profile-data-reminders.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - 资料提醒 - 个人中心 - 代代相传 - - - - - - -
-
-
-
资料提醒
-
-
-

资料提醒

-

- 集中查看成员资料缺项,补齐头像、出生日期、亲属关系和联系方式。 -

-
-
-
-
-
-
- -
-
-

待完善成员

-
-
- 正在读取资料提醒… -
-
-
-
-
-
-
- - - - - - - - - - - - - diff --git a/dist/profile-data.html b/dist/profile-data.html deleted file mode 100644 index 3cbdce5..0000000 --- a/dist/profile-data.html +++ /dev/null @@ -1,259 +0,0 @@ - - - - - - 个人资料 - 个人中心 - 代代相传 - - - - - - -
-
-
-
个人资料
-
-
-

个人资料

-

- 管理个人资料和账号安全信息;家谱成员关系请在当前家谱中维护。 -

-
- 编辑资料 -
-
-
-
-
- -
-
-

编辑资料

-
-
-
- - -
-
- - -
-
-
-
- - - - -

请选择头像文件

-
-
-
-
- - -
-
- - -
-
-
- - -
-
- -
-
-
-
-
-

基本资料

-
正在读取账户资料…
- -
-
-

个人信息

- -
-
-

现居地区

-
-
- - - -
-
- - 现居地区暂不支持保存到个人资料 -
-
-
-
-
-
- - -
-
- -
-
-
-
-

-
-
-
-

亲属关系

-
-
- 个人资料页不单独维护亲属关系;请先选择家谱,再到成员管理或世系图中查看和维护。 -
-
- -
-
-
-
-
- - - - - - - - - - - - - - - - - - diff --git a/dist/profile-documents.html b/dist/profile-documents.html deleted file mode 100644 index 19adc81..0000000 --- a/dist/profile-documents.html +++ /dev/null @@ -1,14 +0,0 @@ - - - 重要证件 - 个人中心 - 代代相传 - - -
重要证件

重要证件

按人物维护证件档案、附件和访问密码。证件号码仅填写脱敏内容。

-
-

新增档案

-

档案列表

正在读取重要证件…
正在初始化…
-
-
- - - diff --git a/dist/profile-earnings.html b/dist/profile-earnings.html deleted file mode 100644 index 36568ad..0000000 --- a/dist/profile-earnings.html +++ /dev/null @@ -1,13 +0,0 @@ - - - 推广收益 - 个人中心 - 代代相传 - - -
推广收益

推广收益

查看可用收益、不可变流水和提现处理状态。

-

收益概览

正在读取收益…
-

申请提现

-

提现记录

正在读取提现记录…

收益流水

正在读取收益流水…
正在初始化…
-
- - - diff --git a/dist/profile-families.html b/dist/profile-families.html deleted file mode 100644 index 2ce7f98..0000000 --- a/dist/profile-families.html +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - 我的家谱 - 个人中心 - 代代相传 - - - - - - -
-
-
- -
-
-

我的家谱

-

- 从真实列表选择要进入的家谱,后续页面会持续使用同一个家谱上下文。 -

-
- 创建家谱 -
-
-
-
-
- -
-
-

家谱入口

-

先选择要处理的家谱,再进入主页、成员、世系和内容管理。

-
正在读取家谱额度…
-
-
- 正在读取我的家谱… -
-
-
-
-

快捷操作

-
- -

立即创建我的家谱

-

创建新家谱,设置姓氏、谱名、堂号和访问权限。

- -

加入家谱

-

搜索并选择可加入的家谱,提交关系和申请说明等待审核。

-
- -

家谱排序

-

选择家谱后上移或下移,保存个人的家谱展示顺序。

- - -
- - - -
-
-
-
-
-
-
-
- - - - - - - - - - - - - diff --git a/dist/profile-family-admin.html b/dist/profile-family-admin.html deleted file mode 100644 index 7ffacfb..0000000 --- a/dist/profile-family-admin.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - 家族管理 - 个人中心 - 代代相传 - - - - - - -
-
-
-
家族管理
-
-

家族管理

查看家谱成员;具备管理权限时可调整成员信息、角色和世系人物绑定。

-
-
-
-
-
- -
-
-

家谱概览

-
正在加载家谱和当前权限…
- -
-
-

正常成员

-

成员新增必须通过后端已有的加入或邀请流程;本页不伪造直接新增成员。

-
-
成员加载中…
-
- - - -
-
-
-
- - - - - - - - - - - - - diff --git a/dist/profile-family-home.html b/dist/profile-family-home.html deleted file mode 100644 index 9653cab..0000000 --- a/dist/profile-family-home.html +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - 家谱主页 - 个人中心 - 代代相传 - - - - - - -
-
-
- -
-
-

正在读取家谱

-

- 当前家谱的工作区,用于进入世系、相册、家族圈、内容管理和成员协作。 -

-
- -
-
-
-
-
- -
-
-

家谱概览

-
-
-
- 正在加载家谱概览… -
-
-
- -

家谱成员

-

--

-
-
- -

世系人物

-

--

-
- -
-
正在读取家谱概览
-
-
-

世系预览

- -
-
- 正在加载世系树… -
-
-
-
-

家族文化

-

围绕当前家谱集中维护谱文、影像与家族资料。

- -
-
-

家族记录与协作

-

按列表、详情和编辑页面管理动态、成长记录、亲友往来、备忘和活动。

- -
-
-

家谱维护

-

世系、字辈和成员维护均与当前家谱绑定;管理权限始终以服务端结果为准。

- -
-
-
-
-
- - - - - - - - - - - - - - - diff --git a/dist/profile-family-settings.html b/dist/profile-family-settings.html deleted file mode 100644 index e8fac5d..0000000 --- a/dist/profile-family-settings.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - 家谱维护 - 个人中心 - 代代相传 - - - - - - -
-
家谱维护

家谱维护

归档、恢复或永久删除当前家谱。永久删除前会再次校验谱主身份和手机号。

-
-

归档与恢复

归档用于暂时停止维护,不会永久删除数据。

-

永久删除

此操作不可恢复。仅谱主且满足服务端删除条件时可执行。

-
正在读取家谱维护能力…
-
-
- - - diff --git a/dist/profile-feed-detail.html b/dist/profile-feed-detail.html deleted file mode 100644 index f0f2dd0..0000000 --- a/dist/profile-feed-detail.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - 动态详情 - 家族圈 - 代代相传 - - - - - - -
-
-
-
动态详情
-
-
-

动态详情

-

查看动态、评论与回复;本页链接可用于通知跳转和分享。

-
-
-
-
-
-
- -
-
-
- 正在加载动态详情… -
-
-
-

发表评论

-
- -

发表公开评论

-
- - -
-
-
正在读取动态评论权限…
-
-
- - -
-
-
-
-

评论与回复

-
-
- 正在加载评论… -
-
-
-
-
-
-
- - - - - - - - - - - - - - diff --git a/dist/profile-feed-edit.html b/dist/profile-feed-edit.html deleted file mode 100644 index 6308de3..0000000 --- a/dist/profile-feed-edit.html +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - 发布动态 - 个人中心 - 代代相传 - - - - - - - -
-
-
-
发布动态
-
-
-

发布动态

-

面向家族圈发布文字和图片动态,与家人交流近况。

-
-
-
-
-
-
- -
-
-

动态内容

-
- -
- -
-
- - - - -

未选择图片

-
- -
-
- 取消 -
-
-
-
-
-
-
- - - - - - - - - - - - - - - - - - diff --git a/dist/profile-feed.html b/dist/profile-feed.html deleted file mode 100644 index 636927d..0000000 --- a/dist/profile-feed.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - 家族圈 - 个人中心 - 代代相传 - - - - - - -
-
-
-
家族圈
-
-
-

家族圈

-

向家族圈发布文字和图片动态,与家人交流近况。

-
-
-
-
-
-
- -
-
-

发布动态

- -
-
-

最近动态

- -
-
正在加载家族动态…
-
-
- - 第 1 页 - -
-
-
-
-
-
- - - - - - - - - - - - - - diff --git a/dist/profile-feedback.html b/dist/profile-feedback.html deleted file mode 100644 index 5b1352e..0000000 --- a/dist/profile-feedback.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - 意见反馈 - 个人中心 - 代代相传 - - - - - - -
-
-
-
意见反馈
-
-
-

意见反馈

-

提交产品问题、资料异常和改进建议,个人中心内独立跟踪。

-
-
-
-
-
-
- -
-
-

提交反馈

-
-
-
- - -
-
- - -
-
-
- - -
-
-
- -
-
-
-
-
-

历史反馈

- -
-
-
正在读取反馈记录…
-
-
-
-
-
-
- - - - - - - - - - - - - diff --git a/dist/profile-generation.html b/dist/profile-generation.html deleted file mode 100644 index ad68a8a..0000000 --- a/dist/profile-generation.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - 字辈谱 - 个人中心 - 代代相传 - - - - - - -
-
-
-
字辈管理
-
-
-

字辈谱

-

管理世代数字辈,查看各代人数,并维护同字辈多个用字。

-
- -
-
-
-
-
- -
- - -
-

字辈列表

-
-
正在加载字辈记录…
-
-
-
-
-
-
- - - - - - - - - - - - - diff --git a/dist/profile-gift-edit.html b/dist/profile-gift-edit.html deleted file mode 100644 index 18b1e13..0000000 --- a/dist/profile-gift-edit.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - 创建祭祀活动 - 个人中心 - 代代相传 - - - - - - - -
-
-
-
活动编辑
-
-
-

创建祭祀活动

-

维护活动类型、标题、时间地点和说明;受邀名单在活动详情中选择家谱成员。

-
-
-
-
-
-
- -
-
-

活动信息

-
正在加载活动编辑信息…
- -
-
-
-
-
- - - - - - - - - - - - - - - - - - - diff --git a/dist/profile-gift.html b/dist/profile-gift.html deleted file mode 100644 index 085285e..0000000 --- a/dist/profile-gift.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - 贺礼邀请 - 个人中心 - 代代相传 - - - - - - -
-
-
-
贺礼邀请
-
-
-

贺礼邀请

-

- 查看发给当前账号的活动邀请,并直接接受或拒绝;具备权限时可进入活动管理。 -

-
-
-
-
-
-
- -
-
-

我的邀请

-
-
- 正在加载我的邀请… -
-
-
-
-
-
-

关联活动

- 管理关联活动 -
-
-
-

邀请详情

-
-
- 请选择一条邀请查看详情 -
-
-
-
-
-
-
- - - - - - - - - - - - - diff --git a/dist/profile-growth-edit.html b/dist/profile-growth-edit.html deleted file mode 100644 index 928c6f4..0000000 --- a/dist/profile-growth-edit.html +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - 新建成长记录 - 个人中心 - 代代相传 - - - - - - - -
-
-
-
编辑成长记录
-
-
-

新建成长记录

-

人物从当前家谱世系中选择,附件由上传组件自动生成内部编号。

-
-
-
-
-
-
- -
-
-

记录内容

- -
-
正在加载成长记录编辑信息…
-
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - diff --git a/dist/profile-growth.html b/dist/profile-growth.html deleted file mode 100644 index 73f8e9e..0000000 --- a/dist/profile-growth.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - 成长日志 - 个人中心 - 代代相传 - - - - - - -
-
-
-
成长记录
-
-
-

成长记录

-

记录家族成员的成长节点、日期、提醒和附件。

-
-
-
-
-
-
- -
-
-

记录列表

-
-
正在加载成长记录…
-
-
-
-

记录详情

-
-
请选择一条成长记录查看详情。
-
-
-
-
-
-
- - - - - - - - - - - - - - diff --git a/dist/profile-invite.html b/dist/profile-invite.html deleted file mode 100644 index e56f049..0000000 --- a/dist/profile-invite.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - 邀请家人 - 个人中心 - 代代相传 - - - - - - -
-
-
-
邀请家人
-
-
-

邀请家人

-

生成家谱邀请链接或邀请码,引导亲人加入家谱并激活账号。

-
- 返回家族管理 -
-
-
-
-
- -
-
-

生成邀请

-
-
-
正在读取邀请服务…
-
- - -
-
-

我发出的邀请

-
正在读取邀请记录…
-
-
-
-
-
- - - - - - - - - - - - - diff --git a/dist/profile-join-family.html b/dist/profile-join-family.html deleted file mode 100644 index 547d7c9..0000000 --- a/dist/profile-join-family.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - 加入家谱 - 个人中心 - 代代相传 - - - - - - -
-
-
-
加入家谱
-
-
-

加入家谱

-

查看加入申请进度;待审核申请可以撤销后重新提交。

-
- 提交新申请 -
-
-
-
-
- -
-
-
-

我的加入申请

- -
-
-
-
-
-
-
-
- - - - - - - - - - - - - diff --git a/dist/profile-join-review.html b/dist/profile-join-review.html deleted file mode 100644 index a6f5f81..0000000 --- a/dist/profile-join-review.html +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - 入谱审核 - 个人中心 - 代代相传 - - - - - - -
-
-
-
入谱审核
-
-
-

入谱审核

-

审核当前家谱的加入申请。只有家谱管理者可以执行通过或拒绝。

-
-
-
-
-
-
- -
-
-
-

待审核申请

- -
-
-
-
- 正在读取待审核申请… -
-
-
- -
-
-
-
- - - - - - - - - - - - - diff --git a/dist/profile-memo-edit.html b/dist/profile-memo-edit.html deleted file mode 100644 index 45ad0c0..0000000 --- a/dist/profile-memo-edit.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - 新建备忘录 - 个人中心 - 代代相传 - - - - - - -
-
-
编辑备忘录

新建备忘录

附件由上传组件自动生成内部编号,页面不允许手工填写。

-
-
-
- -
-
-

备忘内容

- -
正在加载备忘录编辑信息…
-
-
-
-
-
- - - - - - - - - - - - - - - - diff --git a/dist/profile-memo.html b/dist/profile-memo.html deleted file mode 100644 index ab8c4c1..0000000 --- a/dist/profile-memo.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - 备忘录 - 个人中心 - 代代相传 - - - - - - -
-
-
-
备忘录
-

备忘录

记录家族事务、纪念事项、提醒时间和完成状态。

-
-
-
-
- -
-
-

备忘列表

-
-
正在加载备忘录…
-
-
-
-

备忘详情

-
-
请选择一条备忘录查看详情。
-
-
-
-
-
-
- - - - - - - - - - - - - - diff --git a/dist/profile-merit-edit.html b/dist/profile-merit-edit.html deleted file mode 100644 index de7ecba..0000000 --- a/dist/profile-merit-edit.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - 新增功德记录 - 个人中心 - 代代相传 - - - - - - - -
-
-
-
编辑功德记录
-
-
-

新增功德记录

-

选择功德类型并填写金额和说明;标有 * 的项目为必填。

-
-
-
-
-
-
- -
-
-

功德资料

- -
-
正在加载功德记录编辑信息…
-
-
-
-
-
-
- - - - - - - - - - - - - - - - diff --git a/dist/profile-merit.html b/dist/profile-merit.html deleted file mode 100644 index d11e2ad..0000000 --- a/dist/profile-merit.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - 功德录 - 个人中心 - 代代相传 - - - - - - -
-
-
-
功德录
-
-
-

功德录

-

记录功德人、事迹、时间和金额,沉淀家族贡献。

-
-
-
-
-
-
- -
-
-

功德记录

-
-
正在加载功德记录…
-
-
-
-

记录详情

-
-
请选择一条功德记录查看详情。
-
-
-
-
-
-
- - - - - - - - - - - - - - diff --git a/dist/profile-messages.html b/dist/profile-messages.html deleted file mode 100644 index c955442..0000000 --- a/dist/profile-messages.html +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - 消息中心 - 个人中心 - 代代相传 - - - - - - -
-
-
-
消息中心
-
-
-

消息中心

-

查看当前账号的通知详情,并管理未读状态。

-
- 返回个人中心 -
-
-
-
-
- -
-
-
-
-

通知列表

-

未读数量:0

-
- -
-
-
-
正在加载消息…
-
-
-
-

通知详情

-
-
请选择一条通知查看详情。
-
-
-
-

阅读管理

-
- - -
-
-
-
-
-
- - - - - - - - - - - - - diff --git a/dist/profile-relative-edit.html b/dist/profile-relative-edit.html deleted file mode 100644 index 3ac8cad..0000000 --- a/dist/profile-relative-edit.html +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - 新建亲友记录 - 个人中心 - 代代相传 - - - - - - -
-
-
-
编辑亲友往来
-
-
-

新建亲友记录

-

附件由上传组件自动生成内部编号,页面不允许手工填写。

-
-
-
-
-
-
- -
-
-

记录内容

- -
-
正在加载亲友记录编辑信息…
-
-
-
-
-
-
- - - - - - - - - - - - - - - - diff --git a/dist/profile-relative.html b/dist/profile-relative.html deleted file mode 100644 index 69053f9..0000000 --- a/dist/profile-relative.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - 亲友往来 - 个人中心 - 代代相传 - - - - - - -
-
-
-
亲友往来
-
-
-

亲友往来

-

记录亲友关系、往来事项、时间、礼金和附件。

-
-
-
-
-
-
- -
-
-

记录列表

-
-
正在加载亲友记录…
-
-
-
-

记录详情

-
-
请选择一条亲友记录查看详情。
-
-
-
-
-
-
- - - - - - - - - - - - - - diff --git a/dist/profile-security.html b/dist/profile-security.html deleted file mode 100644 index 6a9b8c9..0000000 --- a/dist/profile-security.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - 安全设置 - 个人中心 - 代代相传 - - - - - - -
-
-
-
安全设置
-
-
-

安全设置

-

维护登录密码、手机号与账号注销安全操作。

-
-
-
-
-
-
- -
-
-

账号安全

- -
-
-
- - -
-
- - -
-
-
- - -
-
- -
-
-
-
-
-

换绑手机号

- -
-
-
- - -
-
- - - -
-
- -
- -
-
-
-
-
-

注销账号

-

验证已绑定手机号的短信验证码后提交注销申请。

-
-
- - - -
正在读取已绑定手机号…
-
-
- - -
- -
- -
-
-
-
-
-
-
-
- - - - - - - - - - - - - - - - - diff --git a/dist/profile-services.html b/dist/profile-services.html deleted file mode 100644 index d9cbc20..0000000 --- a/dist/profile-services.html +++ /dev/null @@ -1,196 +0,0 @@ - - - - - - 帮助与服务 - 个人中心 - 代代相传 - - - - - - -
-
-
-
帮助与服务
-
-
-

帮助与服务

-

集中处理推广收益、意见反馈、账号安全、帮助说明和会员服务。

-
- 提交反馈 -
-
-
-
-
- -
-
-

常用服务

- -
-
-

平台设置与协议

-

集中查看平台说明、服务支持和协议入口;正式内容以平台发布版本为准。

- -
-
-

会员服务

-

选择适合的会员套餐并创建微信扫码支付订单。

-
-
正在读取会员套餐…
-
-
-

尚未选择套餐

-
- - -
-
- -
-
-
-
- -
-
-

会员订单

- -
-
-
正在读取会员订单…
-
-
-
-

推荐阅读

- -
-
-
-
-
- - - - - - - - - - - - - - diff --git a/dist/profile-share.html b/dist/profile-share.html deleted file mode 100644 index 861c0f4..0000000 --- a/dist/profile-share.html +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - 应用分享与收益 - 个人中心 - 代代相传 - - - - - - -
-
-
-
应用分享
-
-
-

应用分享与收益

-

应用邀请暂以移动端为主;这里可查看推广收益、流水和提现记录。

-
-
-
-
-
-
- -
-
-

邀请好友奖励

-
-
- -

每邀请一位好友

-

邀请奖励规则正在完善,当前暂不展示奖励金额。

-
-
- -

分享二维码

-

分享链接和二维码暂未开放,请先使用移动端分享。

-
-
-
-
-

分享记录

-
-
- 分享记录暂未开放;推广收益和提现结果可在下方查看。 -
-
-
-
-

收益与提现

- -
-
-
-
-
- - - - - - - - - - - - diff --git a/dist/profile-tree.html b/dist/profile-tree.html deleted file mode 100644 index 8272af0..0000000 --- a/dist/profile-tree.html +++ /dev/null @@ -1,305 +0,0 @@ - - - - - - 世系图 - 个人中心 - 代代相传 - - - - - - -
-
-
-
世系管理
-
-
-

世系图

-

- 个人中心内的树谱管理视图,用于查看成员关系、添加亲属和搜索成员。 -

-
- 管理字辈 -
-
-
-
-
- -
-
-

树谱操作

-
- - -
- -
-
-

世系树

- -
-
世系树加载中…
-
-
-
-

成员搜索

-
- - - - -
-

-
- -
-
世系人物加载中…
-
- -
-
-

成员详情

- -
-
请选择一个成员查看资料
-
-
- -
-
-
-
- - - - - - - - - - - - - - - diff --git a/dist/profile-video-edit.html b/dist/profile-video-edit.html deleted file mode 100644 index f75c20a..0000000 --- a/dist/profile-video-edit.html +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - 发布视频 - 个人中心 - 代代相传 - - - - - - -
-
-
-
编辑视频
-
-
-

发布视频

-

视频文件由上传组件自动关联,无需填写内部编号。

-
-
-
-
-
-
- -
-
-

视频内容

- -
正在核对视频维护权限…
-
-
-
-
-
- - - - - - - - - - - - - - - - diff --git a/dist/profile-video.html b/dist/profile-video.html deleted file mode 100644 index 0fdbefa..0000000 --- a/dist/profile-video.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - 家族视频 - 个人中心 - 代代相传 - - - - - - -
-
-
-
家族视频
-
-
-

家族视频

-

查看家族视频资料;有内容维护权限的成员可以发布、编辑和删除视频。

-
-
-
-
-
-
- -
-
-

视频列表

-
-
- 正在加载视频… -
-
-
-
-

视频详情

-
-
- 请选择一个视频查看详情 -
-
-
-
-
-
-
- - - - - - - - - - - - - - diff --git a/dist/profile.html b/dist/profile.html deleted file mode 100644 index 35aa226..0000000 --- a/dist/profile.html +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - 个人中心 - 代代相传 - - - - - - - -
-
-
-
-
-
- -
-
-
个人中心
-

正在加载账户

-

账户资料与家谱工作区分开呈现。进入家谱后,再处理成员、内容和协作事务。

-
- 已登录账户资料与账号同步 -
-
正在读取账户资料…
- -
-
-
-
下一步
-

从家谱开始

-

没有家谱时可创建或申请加入;有多个家谱时,请先选择本次要处理的家谱。

- -
-
-
- -
-
- - -
-
-
-
-

我的家谱

-

先从真实列表选择家谱,再进入对应的主页、世系图、成员和内容管理。

-
- 查看全部 -
- -
- -
-
-
-

账户资料

-

以下内容来自你的账户资料;未填写的信息会显示为“待填写”。

-
- 编辑资料 -
-
-

绑定账号加载中

-

昵称加载中

-

真实姓名加载中

-

性别加载中

-

出生日期加载中

-

邮箱加载中

-
-
-
-
-
-

账户与服务

-

将 APP 的“我的”功能整理为桌面服务入口;家谱内容仍需从具体家谱进入。

-
- 查看服务中心 -
- -
-
-
-
-
- - - - - - - - - - - - - - - - - - diff --git a/dist/promo-album.html b/dist/promo-album.html deleted file mode 100644 index 9114856..0000000 --- a/dist/promo-album.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - 宣传相册 - 代代相传 - - - - - -
-
-
-
-
宣传相册
-

用公开相册展示家族故事

-

- 宣传相册用于前台展示祖屋、祠堂、家族活动、老照片和公开纪念内容,帮助访客更直观理解家族文化。 -

-
- 祖屋影像活动纪念公开展示 -
-
-
-
-
-
-
-
-
-
-
-
-
-

相册分类

-

把适合公开展示的影像整理成专题,让家族主页更有内容层次。

-
-
- -
-
相册加载中...
-
-
-
-
-
- - - - - - - - - diff --git a/dist/promo-video.html b/dist/promo-video.html deleted file mode 100644 index 67834dd..0000000 --- a/dist/promo-video.html +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - 宣传视频 - 代代相传 - - - - - -
-
-
-
-
宣传视频
-

用宣传视频讲述家族文化

-

- 宣传视频用于前台展示家族活动、祠堂风貌、修谱故事和公开纪念内容,让访问者更快进入家族叙事。 -

-
- 家族介绍活动纪念修谱故事 -
-
-
-
-

四川成都陈氏族宣传片

-

祠堂、谱文、人物与家风的公开展示。

-
-
-
-
-
-
-

视频专题

-

把适合公开传播的视频整理成栏目,增强家谱主页的展示力。

-
-
-
-

家族介绍

-

概述姓氏源流、家族分布和家风传承。

-
-
-

活动纪念

-

展示祭祖、聚会、修谱仪式等重要时刻。

-
-
-

人物故事

-

以影像记录家族人物、贡献与纪念内容。

-
-
-
-
-
- - - - - diff --git a/dist/public/css/about.css b/dist/public/css/about.css deleted file mode 100644 index 69a379e..0000000 --- a/dist/public/css/about.css +++ /dev/null @@ -1 +0,0 @@ -.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:#fffdf8bd;box-shadow:0 14px 32px #4839260f;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:#ffffff29;box-shadow:0 0 40px #ffffff1a;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:#fffffff0;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:#ffffffd6;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:translate(-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:skew(-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%,to{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}} diff --git a/dist/public/css/app.css b/dist/public/css/app.css deleted file mode 100644 index 6786631..0000000 --- a/dist/public/css/app.css +++ /dev/null @@ -1 +0,0 @@ -.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:#fffdf8bd;box-shadow:0 14px 32px #4839260f;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:#c492452e;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 #211c1633;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 #211c1642}.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:translate(-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:skew(-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{background:#fffdf8}.promotion-list{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px}.promotion-card{display:block;overflow:hidden;border:1px solid var(--line);border-radius:8px;background:#fffaf0;color:inherit;text-decoration:none;box-shadow:0 18px 42px #39302414;transition:transform .24s ease,border-color .24s ease,box-shadow .24s ease}.promotion-card:hover{border-color:#c492456b;box-shadow:0 24px 54px #3930241f;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%,to{transform:translateY(0) rotate(-1deg)}50%{transform:translateY(-8px) rotate(1deg)}}@keyframes phoneFloatB{0%,to{transform:translateY(28px) rotate(1deg)}50%{transform:translateY(18px) rotate(-1deg)}}@keyframes appOrbit{0%,to{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}} diff --git a/dist/public/css/article-detail.css b/dist/public/css/article-detail.css deleted file mode 100644 index 39605f6..0000000 --- a/dist/public/css/article-detail.css +++ /dev/null @@ -1 +0,0 @@ -.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:#fffdf8bd;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:skew(-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:#fffdf8f0;font-family:SimSun,Songti SC,serif;font-size:44px;font-weight:800}.article-cover-detail p{margin:18px 0 0;color:#ffffffe6;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:#fffdf8d1;box-shadow:0 20px 54px #39302414}.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:translate(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}} diff --git a/dist/public/css/create-genealogy.css b/dist/public/css/create-genealogy.css deleted file mode 100644 index e3bec7f..0000000 --- a/dist/public/css/create-genealogy.css +++ /dev/null @@ -1 +0,0 @@ -.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:#fffdf8bd;box-shadow:0 14px 32px #4839260f;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:translate(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 #476f632e;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:translate(-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-form .field{display:grid;gap:8px;color:var(--ink);font-weight:800}.create-region-picker label{display:grid;gap:8px}.create-region-picker{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}.create-form .input:focus,.create-form textarea:focus{outline:none;border-color:#c83b329e;background:#fffdf8;box-shadow:0 0 0 4px #c83b3214;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%,to{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}} diff --git a/dist/public/css/culture.css b/dist/public/css/culture.css deleted file mode 100644 index 91164f1..0000000 --- a/dist/public/css/culture.css +++ /dev/null @@ -1 +0,0 @@ -.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:#fffdf8bd;box-shadow:0 14px 32px #4839260f;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,#213f382e,#213f38db),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:#ffffff52;box-shadow:0 0 34px #ffffff1f;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:#fffffff0;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:#ffffffdb;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:translate(-50%)}.article-card{position:relative;overflow:hidden;border-radius:18px;background:#fff;border:1px solid var(--line);box-shadow:0 18px 48px #39302412;transition:transform .24s ease,box-shadow .24s ease,border-color .24s ease}.article-card:hover{border-color:#c492456b;box-shadow:0 24px 60px #3930241c}.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:skew(-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:#fffdf8db;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%,to{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}} diff --git a/dist/public/css/download.css b/dist/public/css/download.css deleted file mode 100644 index 18e47e9..0000000 --- a/dist/public/css/download.css +++ /dev/null @@ -1 +0,0 @@ -.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:#c492456b}.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}} diff --git a/dist/public/css/family-detail.css b/dist/public/css/family-detail.css deleted file mode 100644 index 3729875..0000000 --- a/dist/public/css/family-detail.css +++ /dev/null @@ -1 +0,0 @@ -.page-family-detail [data-family-detail].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,#213f382e,#213f38e0),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:#fffdf8e0;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:#ffffff21;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:#fffdf8c7;box-shadow:0 20px 54px #39302414}.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:#c492456b;box-shadow:0 24px 60px #3930241c}.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}} diff --git a/dist/public/css/forgot-password.css b/dist/public/css/forgot-password.css deleted file mode 100644 index 0c17892..0000000 --- a/dist/public/css/forgot-password.css +++ /dev/null @@ -1 +0,0 @@ -.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}} diff --git a/dist/public/css/genealogy.css b/dist/public/css/genealogy.css deleted file mode 100644 index 1915d77..0000000 --- a/dist/public/css/genealogy.css +++ /dev/null @@ -1 +0,0 @@ -.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:#fffdf8bd;box-shadow:0 16px 36px #48392612}.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,#fffdf8f2,#fffdf8c7),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:#fffaf1e6;box-shadow:0 12px 28px #4535230f;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 #476f6338}.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:translate(-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:skew(-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 #476f632e}.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%,to{transform:translateY(0)}50%{transform:translateY(-5px)}}@keyframes treePulse{0%,to{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)}} diff --git a/dist/public/css/help.css b/dist/public/css/help.css deleted file mode 100644 index 9163ec0..0000000 --- a/dist/public/css/help.css +++ /dev/null @@ -1 +0,0 @@ -.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:#fffdf8bd;box-shadow:0 14px 32px #4839260f;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 #476f6333;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:translate(-50%)}.faq-list{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 #3930240f;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:#c492456b;box-shadow:0 22px 54px #3930241a}.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%,to{opacity:.5;transform:scale(.92)}50%{opacity:1;transform:scale(1.08)}}@keyframes faqReveal{0%{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}} diff --git a/dist/public/css/index.css b/dist/public/css/index.css deleted file mode 100644 index 9401c5e..0000000 --- a/dist/public/css/index.css +++ /dev/null @@ -1 +0,0 @@ -.company-bar{height:38px;border-bottom:1px solid #eadfce;color:var(--ink);background:#fffdf8}.company-bar .container{display:flex;align-items:center;height:100%;font-size:18px;font-weight:800;letter-spacing:.12em}.hero{position:relative;overflow:hidden;min-height:760px;background:radial-gradient(circle at 82% 12%,rgba(196,146,69,.16),transparent 24%),linear-gradient(125deg,#fffaf0,#f7efe2 48%,#edf4ef)}.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)}.hero-inner{position:relative;z-index:2;min-height:680px;display:grid;grid-template-columns:.76fr 1.24fr;align-items:center;gap:34px;padding:24px 0 76px}.eyebrow{display:inline-flex;align-items:center;gap:10px;margin-bottom:28px;color:#8a5a3b;font-weight:800}.eyebrow:before{content:"";width:42px;height:2px;background:var(--red)}h1{margin:0;color:var(--ink);font-family:SimSun,Songti SC,serif;font-size:56px;line-height:1.16;letter-spacing:0}.lead{max-width:560px;margin:22px 0 0;color:#645d52;font-size:18px;line-height:1.9}.hero-copy{position:relative;padding-top:10px}.hero-copy:before{content:"";position:absolute;left:-28px;top:48px;width:4px;height:156px;border-radius:999px;background:linear-gradient(180deg,var(--red),var(--gold))}.hero-title-mark{display:inline-block;position:relative;color:var(--red)}.hero-title-mark:after{content:"";position:absolute;left:0;right:0;bottom:7px;height:12px;background:#c83b321f;z-index:-1}.hero-path{display:flex;align-items:center;gap:10px;margin-top:26px;color:#766b5e;font-weight:800}.hero-path span{min-width:34px;height:34px;border-radius:50%;display:inline-grid;place-items:center;color:#fff;background:var(--green);font-size:14px}.hero-path i{width:34px;height:1px;background:#dac9ae}.hero-action-card{display:inline-flex;align-items:center;gap:18px;margin-top:30px;padding:14px 16px;border-radius:999px;background:#fffdf8b8;border:1px solid rgba(224,211,189,.86);box-shadow:0 18px 42px #48392614}.cta{display:flex;gap:16px;margin-top:0}.metrics{display:flex;align-items:stretch;max-width:560px;margin-top:22px;padding:8px;border-radius:18px;color:#6d766f;background:#fffdf8bd;border:1px solid rgba(224,211,189,.78);box-shadow:0 16px 36px #48392612}.metric{flex:1;min-width:0;min-height:76px;padding:12px 16px;border-radius:12px}.metric+.metric{border-left:1px solid rgba(218,201,174,.78)}.metric strong{display:block;margin-bottom:5px;color:var(--green);font-size:21px;line-height:1.18}.hero-visual{position:relative;min-height:650px;margin-left:-42px}.hero-showcase{position:absolute;inset:18px 18px 20px 0;display:grid;grid-template-columns:1.08fr .92fr;grid-template-rows:214px 1fr;gap:18px}.showcase-card{overflow:hidden;border-radius:24px;background:#fffdf8eb;border:1px solid rgba(224,211,189,.88);box-shadow:0 24px 70px #4535231f}.family-preview{grid-column:1 / 3;display:grid;grid-template-columns:170px 1fr 238px;align-items:center;gap:24px;padding:28px;background:linear-gradient(90deg,#fffdf8f2,#fffdf8c7),radial-gradient(circle at 78% 22%,rgba(196,146,69,.2),transparent 26%)}.preview-book{width:132px;height:158px;border-radius:12px;position:relative;background:linear-gradient(155deg,#143d57,#092d42);box-shadow:0 18px 42px #0b293d38}.preview-book:before{content:"汤氏\a家谱";white-space:pre;position:absolute;left:24px;top:24px;width:34px;padding:8px 3px;color:#1c302d;line-height:1.2;text-align:center;background:#fbf0d8;border:1px solid rgba(203,176,122,.9);font-weight:800;font-size:14px}.family-preview h3{margin:0 0 12px;font-size:28px}.family-preview p{max-width:430px;margin:0;color:var(--muted);line-height:1.8}.preview-stats{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}.stat-pill{min-height:66px;padding:12px;border-radius:14px;background:#f7efe2;color:#716454}.stat-pill strong{display:block;color:var(--green);font-size:22px;line-height:1.1}.showcase-video{position:relative;display:block;min-height:286px;color:#fff;background:#213f38}.showcase-video img{width:100%;height:100%;min-height:286px;display:block;object-fit:cover;filter:saturate(.9)}.showcase-video:before{content:"";position:absolute;inset:0;background:linear-gradient(180deg,#14231f0d,#14231fc7)}.showcase-video .play{position:absolute;left:28px;bottom:84px;width:62px;height:62px;border-radius:50%;background:#fffffff0;box-shadow:0 12px 30px #0000002e}.showcase-video .play:after{content:"";position:absolute;left:25px;top:18px;width:0;height:0;border-top:13px solid transparent;border-bottom:13px solid transparent;border-left:20px solid var(--red)}.showcase-video h3,.showcase-video p{position:absolute;left:28px;right:28px;margin:0}.showcase-video h3{bottom:44px;font-size:26px}.showcase-video p{bottom:22px;color:#ffffffc7}.showcase-search{min-height:286px;padding:24px}.showcase-search h3{margin:0 0 16px;font-size:22px}.showcase-search .search-box{height:46px}.recommend-list{display:grid;gap:10px;margin-top:16px}.recommend-item{padding:13px 14px;border-radius:14px;background:#f8f1e5;color:#6d6357;line-height:1.55}.recommend-item strong{display:block;margin-bottom:4px;color:var(--ink);font-size:16px}.portal-card{position:absolute;inset:18px 18px 20px 0;overflow:hidden;border-radius:32px;background:#fffdf8eb;border:1px solid rgba(224,211,189,.88);box-shadow:0 34px 90px #45352321}.portal-cover{position:relative;height:250px;overflow:hidden;background:linear-gradient(180deg,#fffdf814,#1f3f37a8),radial-gradient(circle at 78% 18%,rgba(255,255,255,.55) 0 6%,transparent 7%),linear-gradient(135deg,#e9d1a1,#c4533f 45%,#173f52)}.portal-cover:before{content:"";position:absolute;left:50%;top:34px;width:390px;height:190px;transform:translate(-50%);background:linear-gradient(180deg,#f3d083 0 18%,#be4033 18% 70%,#8e3029 70% 100%);clip-path:polygon(2% 38%,20% 13%,80% 13%,98% 38%,88% 38%,88% 94%,12% 94%,12% 38%);filter:drop-shadow(0 18px 32px rgba(61,38,28,.2))}.portal-cover:after{content:"代代相传";position:absolute;left:34px;bottom:30px;color:#fffffff5;font-size:28px;font-weight:800}.portal-book{position:absolute;left:34px;top:34px;width:118px;height:164px;border-radius:12px;background:linear-gradient(155deg,#143d57,#092d42);box-shadow:0 20px 48px #0b293d42;z-index:2}.portal-book:before{content:"汤氏\a家谱";white-space:pre;position:absolute;left:18px;top:24px;width:32px;padding:8px 3px;color:#1c302d;line-height:1.2;text-align:center;background:#fbf0d8;border:1px solid rgba(203,176,122,.9);font-weight:800;font-size:14px}.portal-body{display:grid;grid-template-columns:1.18fr .82fr;gap:18px;padding:22px}.portal-box{min-height:198px;padding:22px;border-radius:20px;background:#fffaf1;border:1px solid #eadfce}.portal-box h3{margin:0 0 16px;font-size:22px}.portal-box p{margin:0;color:var(--muted);line-height:1.75}.portal-video{display:flex;flex-direction:column;justify-content:space-between;color:#fff;background:linear-gradient(180deg,#213f382e,#213f38db),radial-gradient(circle at 78% 24%,rgba(255,255,255,.46) 0 9%,transparent 10%),linear-gradient(135deg,#476f63,#c49245 50%,#c83b32);border-color:#ffffff47}.portal-video p{color:#ffffffc7}.play-line{display:flex;align-items:center;justify-content:space-between;gap:16px}.play-button{width:58px;height:58px;border-radius:50%;position:relative;flex:0 0 auto;background:#fffffff0;box-shadow:0 12px 28px #1e1e1e2e}.play-button:after{content:"";position:absolute;left:23px;top:17px;width:0;height:0;border-top:12px solid transparent;border-bottom:12px solid transparent;border-left:18px solid var(--red)}.public-list{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:16px}.public-item{min-height:66px;padding:9px 12px;border-radius:12px;background:#f5ecd9;color:#61584d;font-size:13px;line-height:1.55}.public-item strong{display:block;margin-bottom:4px;color:var(--ink);font-size:15px}.archive-stage{position:absolute;inset:14px 0 22px;border-radius:30px;background:linear-gradient(90deg,#ffffffd1,#ffffff8c),radial-gradient(circle at 78% 22%,rgba(180,51,42,.12),transparent 26%);border:1px solid rgba(196,146,69,.28);box-shadow:0 34px 90px #45352321}.book-cover{position:absolute;left:54px;top:44px;width:220px;height:310px;border-radius:18px;background:linear-gradient(155deg,#143d57,#092d42);box-shadow:0 28px 70px #13364c38}.book-cover:before{content:"张氏家谱";position:absolute;left:34px;top:34px;width:29px;padding:12px 4px;line-height:1.25;text-align:center;color:#1c302d;background:#fbf0d8;border:1px solid rgba(203,176,122,.9);font-weight:800}.hall{position:absolute;right:28px;top:34px;width:342px;height:198px;overflow:hidden;border-radius:22px;background:#fff8ec;border:1px solid rgba(224,211,189,.9);box-shadow:0 24px 60px #5d482d24}.hall:before{content:none}.hall:after{content:none}.hall img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center top}.word-scroll-card{display:flex;align-items:center;gap:16px;max-width:620px;margin-top:22px;padding:10px 0;border-radius:999px;color:#766b5e;overflow:hidden}.word-scroll-card:before{content:"";width:42px;height:2px;flex:0 0 auto;background:linear-gradient(90deg,var(--red),var(--gold))}.word-scroll-card:after{content:none}.word-scroll-label{color:#8b6c48;font-size:14px;font-weight:800}.word-window{flex:1;height:34px;overflow:hidden;mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent)}.word-track{display:flex;align-items:center;gap:24px;width:max-content;animation:wordMarquee 16s linear infinite}.word{height:34px;display:flex;align-items:center;color:var(--green-dark);font-family:SimSun,Songti SC,serif;font-size:24px;font-weight:800;line-height:1;white-space:nowrap}@keyframes wordMarquee{0%{transform:translate(0)}to{transform:translate(-50%)}}.video-card{position:absolute;right:28px;top:244px;width:342px;height:182px;overflow:hidden;border-radius:20px;color:#fff;background:linear-gradient(180deg,#1f3f371f,#192722d1),radial-gradient(circle at 78% 18%,rgba(255,255,255,.42) 0 8%,transparent 9%),linear-gradient(135deg,#476f63,#c49245 48%,#c83b32);box-shadow:0 22px 56px #2d37322e}.video-card:before{content:"";position:absolute;left:50%;top:50%;width:62px;height:62px;border-radius:50%;background:#ffffffeb;transform:translate(-50%,-50%);box-shadow:0 12px 28px #1e1e1e2e}.video-card:after{content:"";position:absolute;left:calc(50% - 8px);top:calc(50% - 14px);width:0;height:0;border-top:14px solid transparent;border-bottom:14px solid transparent;border-left:22px solid var(--red)}.video-card span{position:absolute;left:22px;bottom:20px;font-weight:800;font-size:20px}.video-card small{position:absolute;left:22px;bottom:50px;color:#ffffffc7;font-size:14px}.search-panel{position:absolute;left:68px;right:28px;top:440px;padding:18px 20px;border-radius:20px;background:#ffffffeb;border:1px solid rgba(224,211,189,.8);box-shadow:0 24px 70px #493b291f}.search-panel h3{margin:0 0 16px;font-size:22px}.search-box{display:flex;height:48px;overflow:hidden;border-radius:999px;border:1px solid #e2d4bf;background:#fbf8f1}.search-box span{flex:1;display:flex;align-items:center;padding-left:22px;color:#9c9488}.search-box b{width:118px;display:grid;place-items:center;color:#fff;background:var(--red)}.genealogy-list{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:14px}.genealogy-item{min-height:62px;padding:9px 12px;border-radius:12px;background:#f8f1e5;color:#61584d;font-size:14px;line-height:1.55}.genealogy-item strong{display:block;margin-bottom:5px;color:var(--ink);font-size:16px}.quick-strip{position:relative;z-index:3;margin-top:-58px}.quick-inner{display:grid;grid-template-columns:repeat(4,1fr);gap:18px}.quick-card{min-height:104px;padding:20px 24px;border-radius:12px;color:var(--ink);border:1px solid rgba(218,201,174,.72);background:radial-gradient(circle at 88% 18%,rgba(255,253,248,.66),transparent 30%),linear-gradient(135deg,#f6f1e7,#e9d8c6);box-shadow:0 18px 44px #242a2614}.quick-card:nth-child(2){background:radial-gradient(circle at 88% 18%,rgba(255,253,248,.64),transparent 30%),linear-gradient(135deg,#f2f5ed,#dbe8d2)}.quick-card:nth-child(3){background:radial-gradient(circle at 88% 18%,rgba(255,253,248,.64),transparent 30%),linear-gradient(135deg,#f8efe9,#ead0c7)}.quick-card:nth-child(4){background:radial-gradient(circle at 88% 18%,rgba(255,253,248,.66),transparent 30%),linear-gradient(135deg,#f4efe4,#e5d1aa)}.quick-card b{display:block;margin-bottom:10px;font-size:20px;color:var(--ink)}.quick-card span{color:var(--muted);line-height:1.7}section{padding:76px 0}.section-head{margin-bottom:34px;text-align:center}.section-head h2{margin:0;font-family:SimSun,Songti SC,serif;font-size:42px;line-height:1.25}.section-head p{margin:14px auto 0;max-width:680px;color:var(--muted);font-size:17px;line-height:1.9}.functions{background:#fffdf8;padding-bottom:0}.function-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:22px}.function-card{min-height:190px;padding:24px;border-radius:14px;background:#fbf7ed;border:1px solid #eee1cf;transition:transform .2s ease}.function-card:hover{transform:translateY(-4px)}.icon{width:46px;height:46px;margin-bottom:18px;border-radius:14px;display:grid;place-items:center;color:var(--red);background:#f7e4dc;font-size:22px;font-weight:900}.function-card h3{margin:0 0 12px;font-size:21px}.function-card p{margin:0;color:var(--muted);line-height:1.8}.plaza{background:linear-gradient(180deg,#fffdf8,#f8f0e4)}.plaza-layout{display:grid;grid-template-columns:1.05fr .95fr;gap:34px;align-items:stretch}.featured-family{min-height:430px;padding:34px;border-radius:22px;color:#fff;background:linear-gradient(180deg,#1c302d2e,#153733d6),linear-gradient(135deg,#476f63,#a95c3e);display:flex;flex-direction:column;justify-content:flex-end;box-shadow:0 24px 70px #2a413b26}.featured-family h3{margin:0 0 16px;font-size:34px}.featured-family p{max-width:560px;margin:0;color:#ffffffd1;line-height:1.9;font-size:17px}.family-cards{display:grid;gap:18px}.family-card{padding:22px;border-radius:18px;background:#fffdf8;border:1px solid #eadfce;box-shadow:0 16px 42px #39302412}.family-card h4{margin:0 0 8px;font-size:22px}.family-card p{margin:0 0 18px;color:var(--muted);line-height:1.7}.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}.culture{background:#fffdf8}.article-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:24px}.article-card{overflow:hidden;border-radius:18px;background:#fff;border:1px solid #eadfce;box-shadow:0 18px 48px #39302412}.article-cover{height:190px;background:radial-gradient(circle at 74% 28%,rgba(255,255,255,.7) 0 8%,transparent 9%),linear-gradient(135deg,#e7c89e,#b94135 48%,#143d57)}.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{margin:0 0 12px;font-size:22px;line-height:1.45}.article-body p{margin:0 0 20px;color:var(--muted);line-height:1.75}.article-meta{color:#9a8f82;font-size:14px}.ad-slots{background:linear-gradient(180deg,#fffdf8,#f8f0e4);padding:0}.ad-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:16px;align-items:start}.ad-card{position:relative;display:block;aspect-ratio:2.8 / 1;padding:8px;border-radius:12px;overflow:hidden;border:1px solid rgba(224,211,189,.84);background:#fffdf8;box-shadow:0 12px 30px #3930240f}.ad-card img{display:block;width:100%;height:100%;object-fit:contain;transition:transform .32s ease}.ad-card:hover img{transform:scale(1.025)}.surname{background:radial-gradient(circle at 12% 20%,rgba(200,59,50,.09),transparent 24%),#f8f0e4}.surname-panel{display:grid;grid-template-columns:360px 1fr;gap:34px;padding:30px;border-radius:24px;background:#ffffffb3;border:1px solid rgba(224,211,189,.8);box-shadow:0 24px 70px #39302414}.surname-intro{padding:30px;border-radius:18px;color:#fff;background:linear-gradient(155deg,var(--green-dark),var(--green))}.surname-intro h2{margin:0 0 16px;font-family:SimSun,Songti SC,serif;font-size:38px}.surname-intro p{color:#ffffffc7;line-height:1.9}.surname-grid{display:grid;grid-template-columns:repeat(8,1fr);gap:10px;align-content:start}.surname-name{height:62px;border-radius:12px;display:grid;place-items:center;color:#7c4a33;background:#fbf7ed;border:1px solid #eadfce;font-size:24px;font-family:SimSun,Songti SC,serif;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-name:hover{color:#fff;background:var(--red);border-color:#c83b32b8;box-shadow:0 16px 34px #c83b322e;transform:translateY(-6px) scale(1.03)}.surname-panel:hover .surname-name{animation:surnameFloat 1.9s ease-in-out infinite}.surname-panel:hover .surname-name:nth-child(2n){animation-delay:.08s}.surname-panel:hover .surname-name:nth-child(3n){animation-delay:.14s}.surname-panel:hover .surname-name:nth-child(4n){animation-delay:.2s}@keyframes surnameFloat{0%,to{transform:translateY(0)}50%{transform:translateY(-4px)}}.download,.recommend{background:#fffdf8}.recommend-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:18px}.recommend-card{min-height:184px;padding:22px;border-radius:14px;border:1px solid rgba(224,211,189,.84);background:linear-gradient(180deg,#fffdf8,#fbf7ed);box-shadow:0 18px 48px #39302412}.recommend-card span{display:inline-flex;margin-bottom:18px;padding:6px 12px;border-radius:999px;color:var(--green);background:#eaf0eb;font-size:13px;font-weight:900}.recommend-card h3{margin:0 0 12px;font-size:24px;line-height:1.35}.recommend-card p{margin:0;color:var(--muted);line-height:1.8}.friend-links{padding:0;background:#fffdf8}.friend-links-panel{display:flex;align-items:center;gap:24px;padding:24px 28px;border-radius:18px;border:1px solid rgba(224,211,189,.84);background:#fbf7ed}.friend-links-panel h2{flex:0 0 auto;margin:0;font-family:SimSun,Songti SC,serif;font-size:30px}.friend-link-list{display:flex;flex-wrap:wrap;gap:10px}.friend-link-list a{padding:8px 14px;border-radius:999px;color:#5e665f;background:#ffffffc2;border:1px solid rgba(224,211,189,.78);transition:color .22s ease,border-color .22s ease,transform .22s ease}.friend-link-list a:hover{color:var(--red);border-color:#c83b3252;transform:translateY(-2px)}.download-panel{display:grid;grid-template-columns:1fr 380px;gap:56px;align-items:center;padding:44px;border-radius:28px;background:radial-gradient(circle at 82% 30%,rgba(200,59,50,.12),transparent 28%),linear-gradient(135deg,#f7efe2,#edf4ef);border:1px solid #eadfce}.download-panel h2{margin:0;font-family:SimSun,Songti SC,serif;font-size:42px}.download-panel p{margin:18px 0 30px;color:var(--muted);font-size:18px;line-height:1.9}.qr-wrap{display:grid;grid-template-columns:repeat(2,1fr);gap:18px}.qr{min-height:190px;padding:18px;border-radius:18px;text-align:center;background:#ffffffc7;border:1px solid #eadfce}.qr-box{width:112px;height:112px;margin:0 auto 14px;background:linear-gradient(90deg,#26302c 10px,transparent 10px) 0 0 / 22px 22px,linear-gradient(#26302c 10px,transparent 10px) 0 0 / 22px 22px,#fff;border:10px solid #fff;box-shadow:inset 0 0 0 1px #ddd}@media(max-width:1120px){.nav-links{display:none}.hero-inner,.plaza-layout,.surname-panel,.download-panel{grid-template-columns:1fr}.function-grid,.quick-inner{grid-template-columns:repeat(2,1fr)}.article-grid,.recommend-grid{grid-template-columns:1fr}.friend-links-panel{align-items:flex-start;flex-direction:column}}.qr-note{margin:8px 0 0;color:var(--muted)}@media(hover:hover)and (pointer:fine){.quick-card,.function-card,.family-card,.article-card,.ad-card,.surname-panel,.recommend-card,.download-panel{cursor:pointer}}.quick-card,.function-card,.family-card,.article-card,.ad-card,.surname-panel,.recommend-card,.download-panel{transition:transform .24s ease,box-shadow .24s ease,border-color .24s ease,background .24s ease,color .22s ease;will-change:transform}.quick-card:hover,.function-card:hover,.family-card:hover,.article-card:hover,.ad-card:hover,.recommend-card:hover{border-color:#c492456b;box-shadow:0 24px 60px #3930241c}.function-card{position:relative;overflow:hidden}.function-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:skew(-18deg);transition:left .56s ease}.function-card:hover:after{left:112%}.function-card:hover .icon{color:#fff;background:var(--red);transform:rotate(-6deg) scale(1.08)}.icon{transition:transform .26s ease,background .26s ease,color .26s ease}.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;will-change:transform}.tilt-card:hover{--lift: -6px}.magnetic{will-change:transform}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:.01ms!important}}@media(max-width:720px){.hero{min-height:0}.hero-inner{min-height:0;gap:28px;padding:36px 0 48px}.hero-copy{min-width:0;padding-top:0}.hero-copy:before{content:none}h1{font-size:42px}.lead{font-size:16px;line-height:1.75}.hero-path{gap:7px;font-size:13px}.hero-path i{width:16px}.hero-action-card,.cta,.cta .btn{width:100%}.hero-action-card{padding:12px;border-radius:18px}.cta{display:grid;grid-template-columns:1fr;gap:10px}.cta .btn{justify-content:center}.metrics{display:grid;grid-template-columns:1fr}.metric+.metric{border-top:1px solid rgba(218,201,174,.78);border-left:0}.hero-visual{min-width:0;min-height:0;display:grid;gap:14px;margin-left:0}.archive-stage,.book-cover{display:none}.hall,.video-card,.search-panel{width:calc(100dvw - 28px);max-width:100%;position:relative;inset:auto}.hall{height:auto;aspect-ratio:16 / 9}.video-card{height:174px}.search-panel{padding:16px}.search-box{width:100%}.word-scroll-card,.word-window{min-width:0;max-width:100%}.search-box span{min-width:0;overflow:hidden;padding-left:14px;text-overflow:ellipsis;white-space:nowrap}.search-box b{width:76px;flex:0 0 76px}.genealogy-list,.function-grid,.quick-inner,.qr-wrap{grid-template-columns:1fr}section{padding:52px 0}.section-head h2,.download-panel h2{font-size:34px}.surname-panel{gap:18px;padding:16px}.surname-intro{padding:22px}.surname-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:8px}.surname-name{height:54px}.download-panel{gap:28px;padding:24px}} diff --git a/dist/public/css/join-genealogy.css b/dist/public/css/join-genealogy.css deleted file mode 100644 index f5fa96e..0000000 --- a/dist/public/css/join-genealogy.css +++ /dev/null @@ -1 +0,0 @@ -.page-join-genealogy .auth-card{width:min(1080px,100%)}.join-page-grid{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(340px,.85fr);gap:28px;align-items:start}.join-search-panel,.join-apply-form{display:grid;gap:16px}.join-page-grid .field{display:grid;gap:10px}.join-page-grid .field>span{color:var(--ink);font-weight:900}.join-page-grid .field input,.join-page-grid .field textarea{width:100%;min-height:48px;padding:12px 14px;border:1px solid var(--line);border-radius:8px;background:#fffdf8;color:var(--ink);font:inherit;outline:none;transition:border-color .2s ease,box-shadow .2s ease}.join-page-grid .field textarea{min-height:128px;resize:vertical}.join-page-grid .field input:focus,.join-page-grid .field textarea:focus{border-color:#c83b3273;box-shadow:0 0 0 4px #c83b3214}.join-search-panel{padding-right:28px;border-right:1px solid var(--line)}.join-page-grid h2{margin:0;font-size:22px}.join-page-grid .module-list{max-height:420px;overflow:auto}@media(max-width:800px){.page-join-genealogy .auth-card{width:min(500px,100%)}.join-page-grid{grid-template-columns:1fr}.join-search-panel{padding-right:0;padding-bottom:24px;border-right:0;border-bottom:1px solid var(--line)}}.invite-box{padding:14px;border:1px dashed var(--gold);border-radius:22px;background:#fffaf1}.page-join-genealogy textarea.input{min-height:112px;resize:vertical} diff --git a/dist/public/css/login.css b/dist/public/css/login.css deleted file mode 100644 index db7081c..0000000 --- a/dist/public/css/login.css +++ /dev/null @@ -1 +0,0 @@ -.page-login .auth-card{width:min(440px,100%)}.login-mode-tabs{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:4px;margin:18px 0;padding:4px;border:1px solid rgba(200,59,50,.16);border-radius:999px;background:#fffaf1e0;box-shadow:inset 0 1px #ffffffc7}.login-mode-tab{min-height:42px;border:0;border-radius:999px;background:transparent;color:var(--muted);font:inherit;font-weight:800;text-align:center;cursor:pointer;transition:color .22s ease,background .22s ease,box-shadow .22s ease}.login-mode-tab.is-active{background:#c83b321c;color:var(--red);box-shadow:inset 0 0 0 1px #c83b3229,0 8px 18px #8a402a14}.login-mode-panel[hidden]{display:none}.page-login .code-row{display:grid;grid-template-columns:minmax(0,1fr) 124px;gap:10px}.page-login .auth-links{margin-top:14px}@media(max-width:720px){.page-login .auth-layout{align-items:start}}@media(max-width:620px){.page-login .code-row{grid-template-columns:1fr}} diff --git a/dist/public/css/notice-detail.css b/dist/public/css/notice-detail.css deleted file mode 100644 index 291cf72..0000000 --- a/dist/public/css/notice-detail.css +++ /dev/null @@ -1 +0,0 @@ -.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:#fffdf8bd;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:#fffdf8f0;font-family:SimSun,Songti SC,serif;font-size:42px;font-weight:900}.notice-card p{margin:18px 0 0;color:#ffffffe0;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:#fffdf8d1;box-shadow:0 20px 54px #39302414}.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:translate(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}} diff --git a/dist/public/css/plaza.css b/dist/public/css/plaza.css deleted file mode 100644 index ae4b99d..0000000 --- a/dist/public/css/plaza.css +++ /dev/null @@ -1 +0,0 @@ -.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:#fffdf8bd;box-shadow:0 14px 32px #4839260f}.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:#fffaf1d1}.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,#1c302d2e,#153733d6),linear-gradient(135deg,#476f63,#a95c3e);border-radius:18px;padding:28px;box-shadow:0 22px 58px #2a413b29}.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:#ffffffd1}.family-badge{width:fit-content;margin-bottom:auto;padding:7px 12px;border-radius:999px;color:var(--green-dark);background:#fffdf8db;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:skew(-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%,to{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}} diff --git a/dist/public/css/profile-module.css b/dist/public/css/profile-module.css deleted file mode 100644 index ccf45e7..0000000 --- a/dist/public/css/profile-module.css +++ /dev/null @@ -1 +0,0 @@ -.page-profile-module{background:#f8f4ec}.module-hero{position:relative;overflow:hidden;min-height:257px;box-sizing:border-box;padding:58px 0 42px;background:radial-gradient(circle at 76% 18%,rgba(196,146,69,.16),transparent 25%),linear-gradient(125deg,#fffaf0,#f5eadb 52%,#eaf2ee)}.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:#f8e5dfeb;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:#fffdf8e0;box-shadow:0 22px 58px #39302414}.module-nav{position:sticky;top:106px;display:grid;gap:8px;max-height:calc(100vh - 126px);overflow-y:auto;padding:16px}.page-profile-module .module-nav:not([data-workspace-navigation]){visibility:hidden}.module-nav a{padding:14px 16px;border-radius:14px;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:translate(4px)}.workspace-nav-group{border:1px solid transparent;border-radius:14px}.workspace-nav-group[open]{border-color:#e0d3bdb8;background:#fffaf2b8}.workspace-nav-group summary{padding:14px 16px;border-radius:14px;color:#5e665f;font-weight:900;cursor:pointer;transition:color .22s ease,background .22s ease}.workspace-nav-group summary:hover,.workspace-nav-group.is-open>summary{color:var(--red);background:#fbefea}.workspace-nav-group summary:focus-visible,.module-nav a:focus-visible{outline:3px solid rgba(200,59,50,.32);outline-offset:2px}.workspace-nav-items{display:grid;gap:4px;padding:2px 8px 8px 18px}.module-nav .workspace-nav-items a{padding:10px 12px;font-size:14px;font-weight:800}.module-main{display:grid;gap:20px;min-width:0}.feature-status-banner{padding:18px 20px;border:1px solid rgba(196,146,69,.38);border-radius:16px;color:#6b542d;background:#fff8e8;box-shadow:0 12px 26px #59421a12}.feature-status-banner strong{color:var(--red);font-size:16px}.feature-status-banner p{margin:6px 0 0;color:inherit;line-height:1.75}.module-panel{padding:28px}.rich-editor-host{overflow:hidden;border:1px solid var(--line);border-radius:12px;background:var(--white)}.rich-editor-toolbar{border-bottom:1px solid var(--line)}.rich-editor-content{height:320px}.module-panel h2{margin-bottom:10px;font-family:SimSun,Songti SC,serif;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:#c83b3247;background:#fff7f2}.join-apply-row{align-items:center}.join-apply-row .bottom-actions{justify-content:flex-end}.generation-row{align-items:center}[data-generation-management][hidden],[data-lineage-management][hidden],[data-lineage-spouse-field][hidden],[data-lineage-pagination][hidden],[data-video-management][hidden]{display:none!important}.generation-row .pill{border:0;cursor:pointer;font:inherit}.lineage-row{width:100%;text-align:left;cursor:pointer;font:inherit}.lineage-row.is-selected{border-color:#c83b3261;background:#fff6f1}.lineage-toolbar{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;margin-bottom:16px}.lineage-filter-toolbar{grid-template-columns:minmax(220px,1fr) minmax(140px,.5fr) minmax(140px,.5fr) auto}.lineage-toolbar input,.lineage-toolbar select{min-height:46px;padding:12px 14px;border:1px solid var(--line);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{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;margin-top:16px}.row-actions button.pill{border:0;font:inherit;cursor:pointer}.row-actions .pill:focus-visible{outline:3px solid rgba(200,59,50,.28);outline-offset:2px}.module-panel h2[tabindex="-1"]{scroll-margin-top:96px;outline:none}.pill.is-danger{color:#8a322b;background:#fde8e2}.ceremony-invitation-row .pill{border:0;cursor:pointer;font:inherit}.ceremony-invitation-row .pill:disabled{cursor:wait;opacity:.58}.invitation-detail b{overflow-wrap:anywhere}.invitation-detail .pill{display:inline-block;margin-left:8px;font-size:13px}.video-row .pill,.video-detail+.bottom-actions button{border:0;cursor:pointer;font:inherit}.article-row,.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{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}.editor-field .upload-input{position:absolute;width:1px;height:1px;min-height:0;margin:-1px;padding:0;border:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.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,.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:#c83b327a;box-shadow:0 18px 42px #3930241a;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{align-items:center}.security-form{margin-top:16px;max-width:880px}.security-danger{border-left:4px solid #a13b32;border-color:#8a322b47;background:#fff7f2}.security-danger h2{color:#7e2d27}.security-danger .editor-field input,.security-danger .editor-field textarea{background:#fffdf8}.region-profile-form{margin-top:16px}.profile-region-picker{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}.profile-region-picker>label{display:grid;gap:6px}.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:#c492456b;box-shadow:0 24px 60px #3930241c}.module-card.is-unavailable{cursor:not-allowed;opacity:.68}.module-card.is-unavailable:after{display:none}.module-card.is-unavailable .pill{display:inline-flex;margin-top:12px;color:#6b542d;background:#fff0cf}.module-nav-unavailable{padding:14px 16px;border-radius:14px;color:var(--muted);background:#f4f1eb;font-weight:900}.profile-path{display:flex;align-items:center;gap:8px;margin:0 0 18px;color:#6f766f;font-size:14px;font-weight:800}.profile-path a{color:var(--red)}.profile-path a:hover{text-decoration:underline}.profile-path span{color:#a89f91}.profile-path strong{min-width:0;overflow:hidden;color:var(--ink);text-overflow:ellipsis;white-space:nowrap}.module-nav-unavailable small{display:block;margin-top:4px;color:#8a5a3b;font-size:12px;font-weight:700}.module-card.is-selected{border-color:#c83b3280;background:#fff6f1;box-shadow:0 24px 60px #c83b321f}.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:skew(-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}.notification-raw{margin:0;padding:16px;overflow-wrap:anywhere;white-space:pre-wrap;border:1px solid var(--line);border-radius:15px;background:#fffdf8;color:var(--ink);line-height:1.6}.module-row{display:grid;grid-template-columns:1fr auto;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;margin-top:16px}.editor-form>.bottom-actions,.editor-field>.bottom-actions,.editor-two .bottom-actions,.module-list>.row-actions,.module-row>.row-actions{margin-top:0}.editor-form{display:grid;gap:18px}.profile-data-form{margin-top:14px}.editor-field{display:grid;gap:10px}.editor-field label,.editor-label{color:var(--ink);font-weight:900}.field-required{color:var(--red)}.editor-field input,.editor-field select,.editor-field textarea{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:#c83b3273;box-shadow:0 0 0 4px #c83b3214}.editor-two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px;align-items:start}.editor-field>.btn{width:fit-content}.profile-form-actions{align-items:center}.form-status{color:var(--muted);font-weight:800}[data-growth-editor][hidden],[data-growth-editor-action][hidden],[data-relative-editor][hidden],[data-relative-editor-action][hidden],[data-memo-editor][hidden],[data-memo-editor-action][hidden],[data-merit-editor][hidden],[data-merit-editor-action][hidden]{display:none!important}.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));max-height:none;overflow:visible}.lineage-filter-toolbar{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:720px){.module-hero{min-height:0}.page-profile-module .module-nav{display:none}.module-title-row{display:grid}.module-title-row h1{font-size:38px}.lineage-filter-toolbar,.form-like p,.module-row,.editor-two,.profile-region-picker{grid-template-columns:1fr}}.page-profile-module{background:var(--color-canvas)}.page-profile-module .nav{height:72px}.page-profile-module .section{padding:var(--space-7) 0}.module-hero{height:168px;min-height:168px;padding:var(--space-5) 0}.lineage-current-toolbar{margin-bottom:var(--space-4)}.lineage-current-toolbar label{flex:0 0 auto;font-weight:800}.module-kicker{margin-bottom:var(--space-3);padding:6px 11px}.module-title-row h1{margin-bottom:var(--space-2);font-size:var(--text-2xl);line-height:1.18}.module-title-row p{font-size:var(--text-body-lg);line-height:1.75}.module-layout{grid-template-columns:248px minmax(0,1fr);gap:var(--space-5)}.module-nav,.module-panel,.module-card{border-radius:var(--radius-lg);box-shadow:var(--shadow-panel)}.module-nav{top:88px;gap:var(--space-2);max-height:calc(100vh - 108px);padding:var(--space-3);scrollbar-width:thin;scrollbar-color:rgba(171,65,54,.34) transparent}.module-nav::-webkit-scrollbar{width:6px}.module-nav::-webkit-scrollbar-thumb{border-radius:999px;background:#ab413657}.module-nav::-webkit-scrollbar-track{background:transparent}.module-nav a,.workspace-nav-group summary{padding:11px 13px;border-radius:var(--radius-md)}.module-nav a:hover,.module-nav a.active{transform:translate(2px)}.module-main{gap:var(--space-4)}.module-panel{padding:var(--space-5)}.module-panel h2{font-size:var(--text-title)}.module-grid{gap:var(--space-4)}.module-card{min-height:144px;padding:var(--space-4)}.module-card:after{content:none}.module-card h3,.module-row h3{font-size:var(--text-heading)}.module-row{gap:var(--space-4);padding:var(--space-4);border-radius:var(--radius-md)}.module-row[data-join-genealogy-id]>span:first-child{display:grid;gap:var(--space-1);text-align:left}.module-row[data-join-genealogy-id] small{color:var(--text-muted);font-weight:500}.form-like{border-radius:var(--radius-md)}.form-like p{grid-template-columns:132px minmax(0,1fr) auto;gap:var(--space-4);padding:14px var(--space-4)}.module-row>.media-frame,.album-photo-row>.media-frame{width:152px;aspect-ratio:4 / 3}.family-cover-media,.article-cover-media,.album-cover-media,.ceremony-cover-media,.video-detail-media{margin-bottom:var(--space-4);aspect-ratio:16 / 7}.family-cover-media img,.article-cover-media img,.album-cover-media img,.ceremony-cover-media img,.video-detail-media video{aspect-ratio:16 / 7}:is(.article-cover-media,.album-cover-media,.ceremony-cover-media) img{object-fit:contain}.video-detail-media video{object-fit:contain}.lineage-row{grid-template-columns:auto minmax(0,1fr) auto}.lineage-node{display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:var(--space-2)}.lineage-node>.media-avatar{grid-row:1 / 3}@media(max-width:1060px){.module-layout{grid-template-columns:1fr}.module-nav{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(max-width:720px){.module-hero{height:auto;padding:var(--space-5) 0 var(--space-4)}.module-title-row h1{font-size:var(--text-title)}.module-panel{padding:var(--space-4)}.module-row>.media-frame,.album-photo-row>.media-frame{width:100%}.form-like p,.module-row{grid-template-columns:1fr}.row-actions{justify-content:flex-start}.row-actions .pill{display:inline-flex;min-height:44px;padding:10px 14px;align-items:center;justify-content:center}.bottom-actions .form-status{flex-basis:100%}} diff --git a/dist/public/css/profile.css b/dist/public/css/profile.css deleted file mode 100644 index 925fa64..0000000 --- a/dist/public/css/profile.css +++ /dev/null @@ -1 +0,0 @@ -.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,#f4eadb 48%,#eaf2ee)}.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,.profile-next-card,.panel,.side-card{border:1px solid rgba(224,211,189,.84);border-radius:18px;background:#fffdf8db;box-shadow:0 22px 58px #39302414}.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 #c83b322e}.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-info .profile-load-status{margin:14px 0 0;color:var(--muted);font-size:14px;font-weight:800}.profile-info .profile-load-status:empty{display:none}.profile-tags,.mini-actions{display:flex;flex-wrap:wrap;gap:10px}.mini-actions{margin-top:12px}.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-next-card{padding:32px}.profile-next-card .eyebrow{margin-bottom:14px}.profile-next-card h2{margin-bottom:12px;font-size:30px}.profile-next-card p{margin-bottom:22px;color:var(--muted);line-height:1.8}.profile-hero-actions{margin-top:22px}.profile-stats div{min-height:116px;padding:20px;border-radius:14px;display:grid;align-content:center;background:#f7efe2b8}.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,.user-actions button,.todo-list a{display:block;width:100%;padding:15px 16px;border:1px solid var(--line);border-radius:14px;background:#fbf7ed;color:inherit;font:inherit;text-align:left;cursor:pointer;transition:transform .22s ease,border-color .22s ease,background .22s ease,color .22s ease}.user-actions a:hover,.user-actions button:hover,.todo-list a:hover{transform:translate(5px);border-color:#476f6347;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:#c492456b;box-shadow:0 22px 52px #3930241a}.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 #ffffff14}.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:skew(-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:#ffffffdb}.content-tile.large span{color:var(--green-dark);background:#fffdf8e6}.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)}.service-grid strong,.service-grid span{display:block;text-align:center}.service-grid strong{font-size:17px}.service-grid span{color:var(--muted);font-size:13px;line-height:1.55}.service-grid a:hover span{color:#fffc}.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[hidden]{display:none}.logout-modal.show{opacity:1;pointer-events:auto}.logout-modal-backdrop{position:absolute;inset:0;background:#20232275;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 #3d49443d;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}}.page-profile{background:var(--color-canvas)}.profile-hero{padding:var(--space-7) 0 var(--space-6)}.profile-card,.profile-stats,.profile-next-card,.panel,.side-card{border-radius:var(--radius-lg);box-shadow:var(--shadow-panel)}.profile-info h1{font-size:var(--text-display)}.avatar .media-avatar,.avatar .media-avatar img{width:100%;height:100%;border-radius:inherit}.avatar .media-avatar{color:#fff;background:transparent;font:inherit}@media(max-width:760px){.profile-hero{padding:var(--space-5) 0 var(--space-4)}.profile-info h1{font-size:var(--text-title)}} diff --git a/dist/public/css/promo-album.css b/dist/public/css/promo-album.css deleted file mode 100644 index f68cc12..0000000 --- a/dist/public/css/promo-album.css +++ /dev/null @@ -1 +0,0 @@ -.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:#fffdf8bd;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 #39302414;transition:transform .26s ease}.album-photo.large{grid-row:1 / 3;background:linear-gradient(180deg,#1c302d1a,#153733b8),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:skew(-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}} diff --git a/dist/public/css/promo-video.css b/dist/public/css/promo-video.css deleted file mode 100644 index 38c1993..0000000 --- a/dist/public/css/promo-video.css +++ /dev/null @@ -1 +0,0 @@ -.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:#fffdf8bd;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,#1f3f371a,#192722db),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:#fffc}.play-button{width:64px;height:64px;margin-bottom:46px;border-radius:50%;background:#fffffff0;box-shadow:0 14px 34px #0003;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:skew(-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}} diff --git a/dist/public/css/public.css b/dist/public/css/public.css deleted file mode 100644 index bd15ff6..0000000 --- a/dist/public/css/public.css +++ /dev/null @@ -1 +0,0 @@ -@import"../../tokens.css";:root{--red: var(--color-accent);--red-soft: var(--color-accent-soft);--green: var(--color-green);--green-dark: var(--color-ink);--gold: var(--color-gold);--ink: var(--color-ink);--muted: var(--color-ink-2);--paper: var(--color-paper);--paper-2: var(--color-paper-2);--line: var(--color-rule);--white: var(--color-panel);--shadow: var(--shadow-card)}.auth-layer-message{min-width:160px;border-radius:999px!important;background:#26302cf0!important;box-shadow:0 16px 42px #26302c3d!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}html,body{overflow-x:clip}html{scrollbar-gutter:stable}body{margin:0;color:var(--ink);font-family:var(--font-body);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(var(--layout-max),calc(100% - 72px));margin:0 auto}.site-header{position:sticky;top:0;z-index:20;background:linear-gradient(115deg,#6e2620f5,#c83b32f5 48%,#8b3427f5);border-bottom:1px solid rgba(255,253,248,.2);box-shadow:0 4px #5d1e1a33,0 18px 38px #53201a57,0 30px 70px #53201a38;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:#fffdf8eb;box-shadow:0 10px 28px #3f1c162e;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 #c83b3238;font-weight:800}.nav-links{display:flex;align-items:center;gap:26px;color:#fffdf8;font-size:15px;white-space:nowrap}.nav-links a.active,.nav-links a:hover{color:#fff7d8}.nav-links a,.nav-actions a{color:inherit;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:#fffdf8;font-size:15px;white-space:nowrap}.nav-user-entry{min-width:0;display:inline-flex;align-items:center;gap:8px;font-weight:700}.nav-user-avatar{width:32px;height:32px;position:relative;flex:0 0 auto;display:inline-grid;place-items:center;overflow:hidden;border:1px solid rgba(255,248,230,.56);border-radius:50%;color:var(--red-deep);background:#fff4dc;font-size:13px;font-weight:900}.nav-user-avatar img{width:100%;height:100%;position:relative;z-index:1;object-fit:cover}.nav-user-avatar-fallback{position:absolute;inset:0;display:grid;place-items:center}.nav-user-name{max-width:96px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.profile-current-genealogy{min-width:0;max-width:240px;display:grid;gap:2px;margin-left:auto;padding:7px 12px;border:1px solid rgba(255,248,230,.28);border-radius:12px;color:#fff8e6;background:#4f1d1a2e;line-height:1.2}.profile-current-genealogy span{color:#fff8e6b8;font-size:11px;font-weight:700}.profile-current-genealogy strong{overflow:hidden;color:#fff8e6;font-size:14px;text-overflow:ellipsis;white-space:nowrap}.profile-mobile-menu-toggle,.profile-mobile-menu,.site-menu-toggle,.site-mobile-menu{display:none}.btn{min-width:118px;height:44px;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 #c83b323d}.btn.primary:hover{box-shadow:0 18px 38px #c83b324d}.site-header .btn.primary{color:#8f332b;background:linear-gradient(135deg,#fff8e6,#f1d49a);box-shadow:0 12px 28px #401a1538}.site-header .btn.primary:hover{box-shadow:0 16px 34px #401a154d}.btn.ghost{color:var(--red);border-color:#c83b3247;background:#ffffffad}.btn.danger{border-color:#8a322b;color:#fffdf8;background:#8a322b;box-shadow:0 10px 22px #8a322b2e}.btn.danger:hover{border-color:#71251f;background:#71251f}.inner-hero{position:relative;overflow:hidden;background:radial-gradient(circle at 82% 12%,rgba(196,146,69,.16),transparent 24%),linear-gradient(125deg,#fffaf0,#f7efe2 48%,#edf4ef)}.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:#fffdf8eb;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 #39302412;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:#c4924561;box-shadow:0 24px 60px #3930241c}.card.soft{background:#fbf7ed}.card.dark{color:#fff;background:linear-gradient(155deg,var(--green-dark),var(--green))}.card.dark p{color:#ffffffc7}.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,#fffdf8db,#fffdf875 45%,#fffdf8e0),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,#fbf7ed6b,#fbf7eddb),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:#e0d3bde6;background:#fffdf8f0;box-shadow:0 26px 70px #39302429}.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 .auth-field{display:grid;gap:8px;color:var(--ink);font-weight:800}.auth-card .auth-field b{color:var(--red)}.auth-card .auth-form-status:empty{display:none}.auth-card .input{height:50px;background:#fffaf1}.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:skew(-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 #c83b3257}.auth-card .btn.ghost:hover{color:#fff;border-color:#c83b3233;background:var(--red);box-shadow:0 16px 34px #c83b3233}.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)}.code-row{display:grid;grid-template-columns:1fr auto;gap:12px}.is-loading{pointer-events:none;opacity:.72}.api-empty,.api-state{padding:22px;border:1px dashed var(--line);border-radius:14px;color:var(--muted);background:#fffdf8b3;text-align:center;font-weight:800}.api-state--loading{color:var(--muted)}.api-state--error,.api-state--forbidden{border-color:#a0322d47;color:var(--red);background:#a0322d0f}.footer{padding:60px 0 36px;color:#ffffffc7;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:translate(4px)}.footer .brand{padding:0;border-radius:0;background:transparent;box-shadow:none}.footer .brand:hover{transform:translate(0) translateY(-2px)}.copyright,.footer-legal{padding-top:28px;color:#ffffff7a;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 #261e152e!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:#c83b3261;color:var(--red);background:#fff4ef}@media(max-width:1320px){.page-profile .nav-links,.page-profile-module .nav-links{display:none}.profile-mobile-menu-toggle{min-width:58px;height:38px;display:inline-flex;align-items:center;justify-content:center;padding:0 12px;border:1px solid rgba(255,248,230,.38);border-radius:10px;color:#fff8e6;background:#4f1d1a33;font:inherit;font-size:13px;font-weight:800}.page-profile .nav-actions,.page-profile-module .nav-actions{display:none}.profile-mobile-menu.is-open{display:block;position:absolute;top:100%;right:0;left:0;max-height:calc(100vh - 72px);overflow-y:auto;padding:14px 0 18px;border-bottom:1px solid rgba(255,253,248,.24);background:#792a23fc;box-shadow:0 22px 38px #53201a47}.profile-mobile-menu-inner{display:grid;grid-template-columns:minmax(0,1fr);gap:12px}.profile-mobile-workspace{display:grid;gap:8px}.profile-mobile-workspace .workspace-nav-group{border-color:#fff8e629;background:transparent}.profile-mobile-workspace .workspace-nav-group[open]{background:#fffdf814}.profile-mobile-menu .profile-mobile-workspace summary{min-height:46px;display:flex;align-items:center;padding:10px 14px;color:#fff8e6;font-weight:900;cursor:pointer}.profile-mobile-menu .profile-mobile-workspace .workspace-nav-group.is-open>summary{color:#fff7d8;background:#fffdf829}.profile-mobile-workspace .workspace-nav-items,.profile-mobile-account-actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;padding:8px}.profile-mobile-menu a,.profile-mobile-menu button{min-height:46px;display:flex;align-items:center;padding:10px 14px;border:1px solid rgba(255,248,230,.16);border-radius:10px;color:#fff8e6;background:#fffdf814;font:inherit;font-weight:800;text-align:left}.profile-mobile-menu button{width:100%;cursor:pointer}.profile-mobile-menu a.is-current{border-color:#fff1cac2;color:#fff7d8;background:#fffdf833}}@media(max-width:620px){.profile-mobile-workspace .workspace-nav-items,.profile-mobile-account-actions{grid-template-columns:minmax(0,1fr)}}@media(max-width:1120px){.nav-links{display:none}.site-menu-toggle{min-width:58px;height:38px;display:inline-flex;align-items:center;justify-content:center;padding:0 12px;border:1px solid rgba(255,248,230,.38);border-radius:10px;color:#fff8e6;background:#4f1d1a33;font:inherit;font-size:13px;font-weight:800}.has-site-mobile-menu .nav-actions{display:none}.site-mobile-menu.is-open{display:block;position:absolute;top:100%;right:0;left:0;padding:14px 0 18px;border-bottom:1px solid rgba(255,253,248,.24);background:#792a23fc;box-shadow:0 22px 38px #53201a47}.site-mobile-menu-inner{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.site-mobile-menu a{min-height:46px;display:flex;align-items:center;padding:10px 14px;border:1px solid rgba(255,248,230,.16);border-radius:10px;color:#fff8e6;background:#fffdf814;font:inherit;font-weight:800;text-align:left}.site-mobile-menu a.active{border-color:#fff1cac2;color:#fff7d8;background:#fffdf833}.inner-hero .container,.grid-2{grid-template-columns:1fr}.grid-3,.grid-4,.footer-grid{grid-template-columns:repeat(2,1fr)}}@media(max-width:720px){.container{width:calc(100% - 28px)}.nav-actions a:not(.btn){display:none}.page-profile-module .nav,.page-profile .nav{height:70px;gap:10px}.page-profile-module .brand,.page-profile .brand{padding:7px 10px}.page-profile-module .brand-logo,.page-profile .brand-logo{width:114px}.page-profile-module .brand-logo-mark{width:38px;height:38px}.page-profile-module .brand-text,.page-profile-module .nav-actions{display:none}.profile-current-genealogy{max-width:112px;padding:7px 9px}.profile-current-genealogy span{display:none}.profile-current-genealogy strong{font-size:13px}.profile-mobile-menu-toggle{min-width:50px;padding:0 9px}.profile-mobile-menu-inner,.site-mobile-menu-inner{grid-template-columns:1fr}.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-frame{position:relative;min-width:0;margin:0;overflow:clip;border:var(--rule-thin) solid var(--color-rule);border-radius:var(--radius-card);background:var(--color-panel-soft)}.media-preview{width:100%;display:block;padding:0;border:0;background:transparent;font:inherit}.media-frame img,.media-frame video{width:100%;display:block;object-fit:cover;background:var(--color-paper-2)}.media-frame img{aspect-ratio:4 / 3}.media-frame video{max-height:min(72vh,720px);aspect-ratio:16 / 9}.media-frame figcaption{padding:var(--space-xs) var(--space-sm);color:var(--color-ink-2);font-size:var(--text-sm);line-height:1.6}.media-fallback{min-height:132px;display:grid;place-items:center;padding:var(--space-md);color:var(--color-ink-2);background:var(--color-paper-2);text-align:center;font-size:var(--text-sm);font-weight:600}.media-fallback[hidden]{display:none}.media-gallery{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(200px,100%),280px));gap:var(--space-sm);margin-top:var(--space-md);justify-content:start}.media-gallery .media-frame img{object-fit:contain}.media-download{min-height:var(--control-height);display:flex;align-items:center;padding:var(--space-xs) var(--space-sm);border:var(--rule-thin) solid var(--color-rule);border-radius:var(--radius-sm);color:var(--color-accent-strong);background:var(--color-panel);font-size:var(--text-sm);font-weight:700;overflow-wrap:anywhere}.media-avatar{width:48px;height:48px;position:relative;flex:0 0 auto;display:inline-grid;place-items:center;overflow:clip;border-radius:50%;color:var(--color-accent-strong);background:var(--color-accent-soft);font-weight:800}.media-avatar img{width:100%;height:100%;position:relative;z-index:1;object-fit:cover}.media-avatar-fallback{position:absolute;inset:0;display:grid;place-items:center}.media-avatar img:not([hidden])+.media-avatar-fallback{visibility:hidden}.media-avatar.is-media-error .media-avatar-fallback{visibility:visible}.media-dialog{width:min(1040px,calc(100% - 32px));max-height:calc(100vh - 32px);padding:var(--space-lg);border:var(--rule-thin) solid var(--color-rule);border-radius:var(--radius-dialog);color:var(--color-ink);background:var(--color-panel);box-shadow:var(--shadow-dialog)}.media-dialog::backdrop{background:var(--color-overlay);backdrop-filter:blur(4px)}.media-dialog img{width:100%;max-height:calc(100vh - 120px);object-fit:contain}.media-dialog-close{min-height:40px;display:block;margin:0 0 var(--space-sm) auto;padding:0 var(--space-sm);border:var(--rule-thin) solid var(--color-rule);border-radius:var(--radius-pill);color:var(--color-ink);background:var(--color-panel);font:inherit;font-weight:700}:where(a,button,input,select,textarea,summary,[tabindex]):focus-visible{outline:3px solid var(--color-focus);outline-offset:3px}:where(button,.btn,input,select,textarea):disabled,:where(button,.btn)[aria-disabled=true]{cursor:not-allowed;opacity:.52;box-shadow:none;transform:none}:where(button,.btn)[aria-busy=true]{cursor:progress;opacity:.72;pointer-events:none}.api-state,.api-empty{padding:var(--space-md);border-radius:var(--radius-md);background:var(--color-panel-soft)}.api-state--error,.api-state--forbidden,[data-state=error]{border-color:color-mix(in oklch,var(--color-error) 32%,var(--color-rule));color:var(--color-error);background:var(--color-error-soft)}.api-state--success,[data-state=success]{border-color:color-mix(in oklch,var(--color-success) 30%,var(--color-rule));color:var(--color-success);background:var(--color-success-soft)}@media(max-width:768px){.media-gallery{grid-template-columns:minmax(0,280px)}.media-dialog{padding:var(--space-sm)}}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:.01ms!important}} diff --git a/dist/public/css/register.css b/dist/public/css/register.css deleted file mode 100644 index be13d1d..0000000 --- a/dist/public/css/register.css +++ /dev/null @@ -1 +0,0 @@ -.page-register .auth-links{justify-content:center}@media(max-width:720px){.page-register .auth-layout{align-items:start}} diff --git a/dist/public/css/search-result.css b/dist/public/css/search-result.css deleted file mode 100644 index 886930f..0000000 --- a/dist/public/css/search-result.css +++ /dev/null @@ -1 +0,0 @@ -.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:#fffaf1d1}.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:#fffdf8d1;box-shadow:0 18px 48px #39302412;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:#c492456b;box-shadow:0 24px 60px #3930241c}.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}} diff --git a/dist/public/css/site-info.css b/dist/public/css/site-info.css deleted file mode 100644 index bc13ee2..0000000 --- a/dist/public/css/site-info.css +++ /dev/null @@ -1 +0,0 @@ -.site-info-hero{padding:78px 0 64px;background:radial-gradient(circle at 82% 18%,rgba(196,146,69,.2),transparent 24%),linear-gradient(135deg,#fffaf0,#f3eadc 62%,#edf4ef)}.site-info-hero .container{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(220px,.65fr);align-items:center;gap:44px}.site-info-kicker{margin-bottom:14px;color:var(--red);font-size:13px;font-weight:900;letter-spacing:.12em;text-transform:uppercase}.site-info-hero h1{margin:0;font-size:clamp(36px,5vw,58px);line-height:1.15}.site-info-hero p{max-width:680px;margin:20px 0 0;color:var(--muted);font-size:17px;line-height:1.9}.site-info-mark{min-height:180px;display:grid;place-items:center;border:1px solid rgba(196,146,69,.36);border-radius:28px;color:var(--red);background:#fffdf8c2;box-shadow:var(--shadow);font-size:62px;font-weight:900}.site-info-section{padding:72px 0}.site-info-section.alt{background:#f7efe2a3}.site-info-layout{display:grid;grid-template-columns:minmax(0,1fr) minmax(240px,.36fr);gap:28px}.site-info-list,.site-info-panel,.site-info-status{padding:30px;border:1px solid var(--line);border-radius:22px;background:var(--white);box-shadow:0 16px 46px #45352314}.site-info-list{display:grid;gap:16px}.site-info-item{display:block;padding:20px;border:1px solid rgba(234,223,206,.9);border-radius:16px;background:#fffaf2;transition:transform .2s ease,border-color .2s ease}.site-info-item:hover{border-color:#c83b3266;transform:translateY(-2px)}.site-info-item small,.site-info-panel small{color:var(--red);font-weight:800}.site-info-item h2,.site-info-panel h2,.site-info-status h2{margin:10px 0;font-size:22px}.site-info-item p,.site-info-panel p,.site-info-status p{margin:0;color:var(--muted);line-height:1.8}.site-info-links{display:grid;gap:12px;margin-top:18px}.site-info-links a{color:#8f4b31;font-weight:800}.site-info-links a:hover{color:var(--red)}.site-info-notice{margin-bottom:22px;padding:18px 20px;border:1px solid rgba(196,146,69,.42);border-radius:14px;color:#775227;background:#fff6e2;line-height:1.75}.site-info-status{max-width:760px;margin:0 auto;text-align:center}.site-info-status .site-info-mark{width:96px;min-height:96px;margin:0 auto 22px;border-radius:50%;font-size:34px}.site-info-actions{display:flex;justify-content:center;flex-wrap:wrap;gap:12px;margin-top:26px}@media(max-width:760px){.site-info-hero .container,.site-info-layout{grid-template-columns:1fr}.site-info-hero,.site-info-section{padding:52px 0}.site-info-mark{min-height:120px}} diff --git a/dist/public/css/submit-ticket.css b/dist/public/css/submit-ticket.css deleted file mode 100644 index 9fe8745..0000000 --- a/dist/public/css/submit-ticket.css +++ /dev/null @@ -1 +0,0 @@ -.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:#fffdf8f0;font-size:28px;font-weight:900}.ticket-tip p{max-width:360px;margin:18px 0 0;color:#ffffffdb;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:#fffdf8d1;box-shadow:0 20px 54px #39302414}.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}} diff --git a/dist/public/css/surname-detail.css b/dist/public/css/surname-detail.css deleted file mode 100644 index d11a453..0000000 --- a/dist/public/css/surname-detail.css +++ /dev/null @@ -1 +0,0 @@ -.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:#fffdf8f2;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:#ffffffe6;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:#fffdf8d1;box-shadow:0 20px 54px #39302414}.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:translate(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}} diff --git a/dist/public/css/surname.css b/dist/public/css/surname.css deleted file mode 100644 index f5dd7b4..0000000 --- a/dist/public/css/surname.css +++ /dev/null @@ -1 +0,0 @@ -.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:#fffdf8bd;box-shadow:0 14px 32px #4839260f}.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:#fffaf1d1}.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:translate(-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:#fffdf8b8;box-shadow:0 22px 60px #39302414}.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:#c83b32b8;box-shadow:0 16px 34px #c83b322e;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%,to{transform:translateY(0)}50%{transform:translateY(-4px)}}@keyframes surnamePulse{0%,to{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}} diff --git a/dist/public/images/ad-album-small.png b/dist/public/images/ad-album-small.png deleted file mode 100644 index 66c22bc..0000000 Binary files a/dist/public/images/ad-album-small.png and /dev/null differ diff --git a/dist/public/images/ad-cloud.png b/dist/public/images/ad-cloud.png deleted file mode 100644 index 26a4c52..0000000 Binary files a/dist/public/images/ad-cloud.png and /dev/null differ diff --git a/dist/public/images/ad-data.png b/dist/public/images/ad-data.png deleted file mode 100644 index 9498735..0000000 Binary files a/dist/public/images/ad-data.png and /dev/null differ diff --git a/dist/public/images/ad-family-album.png b/dist/public/images/ad-family-album.png deleted file mode 100644 index 4b03f0b..0000000 Binary files a/dist/public/images/ad-family-album.png and /dev/null differ diff --git a/dist/public/images/ad-family-video.png b/dist/public/images/ad-family-video.png deleted file mode 100644 index 8d82f09..0000000 Binary files a/dist/public/images/ad-family-video.png and /dev/null differ diff --git a/dist/public/images/ad-genealogy-service.png b/dist/public/images/ad-genealogy-service.png deleted file mode 100644 index cf144ac..0000000 Binary files a/dist/public/images/ad-genealogy-service.png and /dev/null differ diff --git a/dist/public/images/ad-hall.png b/dist/public/images/ad-hall.png deleted file mode 100644 index ab6b420..0000000 Binary files a/dist/public/images/ad-hall.png and /dev/null differ diff --git a/dist/public/images/ad-memory.png b/dist/public/images/ad-memory.png deleted file mode 100644 index b75761b..0000000 Binary files a/dist/public/images/ad-memory.png and /dev/null differ diff --git a/dist/public/images/ad-print.png b/dist/public/images/ad-print.png deleted file mode 100644 index f527271..0000000 Binary files a/dist/public/images/ad-print.png and /dev/null differ diff --git a/dist/public/images/ad-research.png b/dist/public/images/ad-research.png deleted file mode 100644 index a588a39..0000000 Binary files a/dist/public/images/ad-research.png and /dev/null differ diff --git a/dist/public/images/ad-reunion.png b/dist/public/images/ad-reunion.png deleted file mode 100644 index 0131ce9..0000000 Binary files a/dist/public/images/ad-reunion.png and /dev/null differ diff --git a/dist/public/images/ad-service.png b/dist/public/images/ad-service.png deleted file mode 100644 index fbb70ab..0000000 Binary files a/dist/public/images/ad-service.png and /dev/null differ diff --git a/dist/public/images/ad-video-small.png b/dist/public/images/ad-video-small.png deleted file mode 100644 index fe62122..0000000 Binary files a/dist/public/images/ad-video-small.png and /dev/null differ diff --git a/dist/public/images/ancestral-hall-new.png b/dist/public/images/ancestral-hall-new.png deleted file mode 100644 index 5726f6e..0000000 Binary files a/dist/public/images/ancestral-hall-new.png and /dev/null differ diff --git a/dist/public/images/ancestral-hall.png b/dist/public/images/ancestral-hall.png deleted file mode 100644 index 5726f6e..0000000 Binary files a/dist/public/images/ancestral-hall.png and /dev/null differ diff --git a/dist/public/images/app-family-home.png b/dist/public/images/app-family-home.png deleted file mode 100644 index 2916524..0000000 Binary files a/dist/public/images/app-family-home.png and /dev/null differ diff --git a/dist/public/images/app-home.png b/dist/public/images/app-home.png deleted file mode 100644 index 4017a54..0000000 Binary files a/dist/public/images/app-home.png and /dev/null differ diff --git a/dist/public/images/app-login.png b/dist/public/images/app-login.png deleted file mode 100644 index 8cd9c7c..0000000 Binary files a/dist/public/images/app-login.png and /dev/null differ diff --git a/dist/public/images/family-archive-cover.jpg b/dist/public/images/family-archive-cover.jpg deleted file mode 100644 index 8b59274..0000000 Binary files a/dist/public/images/family-archive-cover.jpg and /dev/null differ diff --git a/dist/public/images/family-ceremony-cover.jpg b/dist/public/images/family-ceremony-cover.jpg deleted file mode 100644 index 79391d7..0000000 Binary files a/dist/public/images/family-ceremony-cover.jpg and /dev/null differ diff --git a/dist/public/images/family-memory-cover.jpg b/dist/public/images/family-memory-cover.jpg deleted file mode 100644 index ed20010..0000000 Binary files a/dist/public/images/family-memory-cover.jpg and /dev/null differ diff --git a/dist/public/images/logo-light.png b/dist/public/images/logo-light.png deleted file mode 100644 index 21a2505..0000000 Binary files a/dist/public/images/logo-light.png and /dev/null differ diff --git a/dist/public/images/logo-mark.png b/dist/public/images/logo-mark.png deleted file mode 100644 index 53be143..0000000 Binary files a/dist/public/images/logo-mark.png and /dev/null differ diff --git a/dist/public/images/logo.png b/dist/public/images/logo.png deleted file mode 100644 index ef11d51..0000000 Binary files a/dist/public/images/logo.png and /dev/null differ diff --git a/dist/public/images/mlogo.png b/dist/public/images/mlogo.png deleted file mode 100644 index 8f46091..0000000 Binary files a/dist/public/images/mlogo.png and /dev/null differ diff --git a/dist/public/js/admin-permission-pages.js b/dist/public/js/admin-permission-pages.js deleted file mode 100644 index bb23a41..0000000 --- a/dist/public/js/admin-permission-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(n,a){if(typeof module=="object"&&module.exports){module.exports=a(n);return}n.AdminPermissionPages=a(n),n.document&&n.document.addEventListener("DOMContentLoaded",function(){n.AdminPermissionPages.init()})})(typeof globalThis!="undefined"?globalThis:window,function(n){"use strict";var a=n.document,c={owner:"谱主",admin:"管理员",editor:"编辑",member:"成员",visitor:"访客"};function v(){return n.GenealogyApi&&n.GenealogyApi.defaultClient}function l(e){return a?a.querySelector(e):null}function u(e){var r;return typeof e=="number"&&!Number.isSafeInteger(e)||e==null?"":(r=String(e).trim(),/^[1-9][0-9]*$/.test(r)?r:"")}function g(e){var r=e===void 0&&n.location?n.location.search:e,t,i;return e===void 0&&n.ProfileUI&&n.ProfileUI.getGenealogyId&&(i=u(n.ProfileUI.getGenealogyId()),i)?i:(t=new URLSearchParams(String(r||"").replace(/^\?/,"")),u(t.get("genealogyId")))}function p(e,r){var t=e||{},i=u(t.genealogyId),o=String(t.roleType===void 0||t.roleType===null?"":t.roleType).trim(),b=u(r);return!i||!b||i!==b||!Object.prototype.hasOwnProperty.call(c,o)?null:{genealogyId:i,roleType:o,canManage:t.canManage===!0,canEditContent:t.canEditContent===!0}}function m(e){return!!(e&&(e.canEditContent===!0||e.canManage===!0))}function P(e){return String(e==null?"":e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function y(e){return e?'

当前角色'+P(c[e.roleType])+"

成员角色维护"+(e.canManage?"可管理":"不可管理")+"

入谱审核"+(e.canManage?"可审核":"不可审核")+"

内容维护"+(m(e)?"可维护":"不可维护")+'

实际写入操作仍由服务端按当前角色与家谱权限校验。

':""}function d(e,r){var t=l("[data-admin-permission-current]");if(t){if(n.ProfileUI&&n.ProfileUI.setApiState){n.ProfileUI.setApiState(t,e,r);return}t.textContent=r}}function f(e,r){var t=r&&(r.status||r.code);return!e||!e.getToken||!e.getToken()||Number(t)===401}function I(e){e&&e.clearToken&&e.clearToken(),n.NavigationUtil.open("login.html")}function s(e){return Number(e&&(e.status||e.code))===403}async function T(){var e=v(),r,t,i;if(f(e)){I(e);return}if(r=g(),!r){d("empty","请先选择家谱,再查看当前角色权限。");return}n.ProfileUI&&n.ProfileUI.syncGenealogyContextLinks&&n.ProfileUI.syncGenealogyContextLinks(),d("loading","正在核对当前家谱角色与权限…");try{if(t=p(await e.genealogyDetail(r),r),!t)throw new Error("家谱权限响应缺少稳定角色或能力字段");i=l("[data-admin-permission-current]"),i&&(i.innerHTML=y(t))}catch(o){if(f(e,o)){I(e);return}d(s(o)?"forbidden":"error",s(o)?"当前账号无权查看该家谱权限。":"当前角色权限读取失败,请稍后重试。")}}function C(){!a||!l("[data-admin-permission-page]")||T()}return{getCurrentGenealogyId:g,normalizePermissionOverview:p,canMaintainContent:m,renderCurrentPermissions:y,shouldRedirectToLogin:f,isForbidden:s,init:C}}); diff --git a/dist/public/js/album-pages.js b/dist/public/js/album-pages.js deleted file mode 100644 index 6bee2aa..0000000 --- a/dist/public/js/album-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(u,C){if(typeof module=="object"&&module.exports){module.exports=C(u);return}u.AlbumPages=C(u),u.document&&u.document.addEventListener("DOMContentLoaded",function(){u.AlbumPages.init()})})(typeof globalThis!="undefined"?globalThis:window,function(u){"use strict";function C(e){return u.ProfileUI&&typeof u.ProfileUI.confirmAction=="function"?u.ProfileUI.confirmAction(e):Promise.resolve(!u.confirm||u.confirm(e))}var v=u.MediaDisplay||(typeof require=="function"?require("./media-display.js"):null),I=u.document,R=Object.create(null),j=Object.create(null),D=null,h=!1,N=!1;function A(){return u.GenealogyApi&&u.GenealogyApi.defaultClient}function f(e,t){return I?(t||I).querySelector(e):null}function F(e,t){return I?Array.prototype.slice.call((t||I).querySelectorAll(e)):[]}function l(e){return e==null?"":String(e)}function U(e){var t=l(e).trim();return t||void 0}function $(e,t){return new URLSearchParams(l(e).replace(/^\?/,"")).get(t)||""}function p(e){return e==null||e===""||typeof e=="number"&&!Number.isSafeInteger(e)?"":/^[1-9][0-9]*$/.test(String(e))?String(e):""}function W(e){return e==null||e===""?"":p(e)}function E(e){var t;if(!(e==null||e===""))return t=Number(e),Number.isSafeInteger(t)?t:void 0}function J(e){var t=e===void 0&&u.location?u.location.search:e,r;return e===void 0&&u.ProfileUI&&u.ProfileUI.getGenealogyId&&(r=u.ProfileUI.getGenealogyId(),r)?p(r):p($(t,"genealogyId"))}function P(e){var t=e===void 0&&u.location?u.location.search:e;return p($(t,"albumId"))}function le(e){var t=U(e);if(t)return/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(t)?t.replace("T"," ")+":00":/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/.test(t)?t.replace("T"," "):t}function we(e){var t=l(e);return/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}/.test(t)?t.slice(0,16).replace(" ","T"):""}function de(e){var t=l(e).match(/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/),r,n,a,o;return!t||(r=Number(t[1]),n=Number(t[2]),a=Number(t[3]),n<1||n>12||Number(t[4])>23||Number(t[5])>59||Number(t[6])>59)?!1:(o=[31,r%4===0&&(r%100!==0||r%400===0)?29:28,31,30,31,30,31,31,30,31,30,31],a>=1&&a<=o[n-1])}function K(e){var t=e||{},r={albumName:l(t.albumName).trim(),status:"0"},n=U(t.albumDesc),a=W(t.coverOssId),o=E(t.sortOrder);return n!==void 0&&(r.albumDesc=n),t.coverOssId!==void 0&&t.coverOssId!==null&&t.coverOssId!==""&&(a?r.coverOssId=a:r.invalidCoverOssId=!0),t.sortOrder!==void 0&&t.sortOrder!==null&&t.sortOrder!==""&&(o!==void 0?r.sortOrder=o:r.invalidSortOrder=!0),r}function Q(e){return!e||!e.albumName?"请填写相册名称":e.invalidCoverOssId?"封面文件编号无效,请重新选择文件":e.invalidSortOrder?"排序值必须是安全整数":e.status==="1"?"当前 PC 无法重新读取停用相册,暂不开放停用":e.status!=="0"?"相册状态只能是 0":""}function X(e){var t=e||{},r={status:"0"},n=W(t.ossId),a=U(t.photoTitle),o=U(t.photoDesc),d=U(t.photographer),s=le(t.shootTime),b=E(t.sortOrder);return n?r.ossId=n:t.ossId!==void 0&&t.ossId!==null&&t.ossId!==""&&(r.invalidOssId=!0),a!==void 0&&(r.photoTitle=a),o!==void 0&&(r.photoDesc=o),d!==void 0&&(r.photographer=d),s!==void 0&&(r.shootTime=s),t.sortOrder!==void 0&&t.sortOrder!==null&&t.sortOrder!==""&&(b!==void 0?r.sortOrder=b:r.invalidSortOrder=!0),r}function Y(e){return!e||!e.ossId||e.invalidOssId?"请选择并上传照片":e.shootTime!==void 0&&!de(e.shootTime)?"拍摄时间格式无效":e.invalidSortOrder?"排序值必须是安全整数":e.status==="1"?"当前 PC 无法重新读取停用照片,暂不开放停用":e.status!=="0"?"照片状态只能是 0":""}function q(e){var t=p(e&&e.albumId),r=p(e&&e.genealogyId),n=v&&v.normalizeFileAccess(e&&e.coverFile),a=l(e&&e.albumName).trim(),o=E(e&&e.photoCount),d=E(e&&e.sortOrder),s=l(e&&e.status),b;return!t||!r||!a||e.coverFile!==void 0&&e.coverFile!==null&&!n||e.photoCount!==void 0&&e.photoCount!==null&&e.photoCount!==""&&o===void 0||e.sortOrder!==void 0&&e.sortOrder!==null&&e.sortOrder!==""&&d===void 0||s!=="0"&&s!=="1"?null:(b={albumId:t,genealogyId:r,genealogyNo:l(e.genealogyNo),genealogyName:l(e.genealogyName),surname:l(e.surname),albumName:a,albumDesc:l(e.albumDesc),status:s,remark:l(e.remark)},n&&(b.coverFile=n),o!==void 0&&(b.photoCount=o),d!==void 0&&(b.sortOrder=d),b)}function B(e){var t=p(e&&e.photoId),r=p(e&&e.genealogyId),n=p(e&&e.albumId),a=v&&v.normalizeFileAccess(e&&e.photoFile),o=E(e&&e.sortOrder),d=l(e&&e.status),s;return!t||!r||!n||!a||e.sortOrder!==void 0&&e.sortOrder!==null&&e.sortOrder!==""&&o===void 0||d!=="0"&&d!=="1"?null:(s={photoId:t,genealogyId:r,genealogyNo:l(e.genealogyNo),genealogyName:l(e.genealogyName),surname:l(e.surname),albumId:n,albumName:l(e.albumName),photoFile:a,photoTitle:l(e.photoTitle),photoDesc:l(e.photoDesc),photographer:l(e.photographer),shootTime:l(e.shootTime),status:d,remark:l(e.remark)},o!==void 0&&(s.sortOrder=o),s)}function G(e){var t;return Array.isArray(e)?(t=e.map(q),t.some(function(r){return!r})?[]:t):[]}function Z(e){var t;return Array.isArray(e)?(t=e.map(B),t.some(function(r){return!r})?[]:t):[]}function k(e,t){var r=G(e),n=p(t);return r.find(function(a){return a.albumId===n})||null}function g(e){return l(e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function se(e){return"profile-album.html?genealogyId="+encodeURIComponent(e)}function _(e,t){var r="profile-album-edit.html?genealogyId="+encodeURIComponent(e);return t?r+"&albumId="+encodeURIComponent(t):r}function ee(e,t){return"profile-album-detail.html?genealogyId="+encodeURIComponent(e)+"&albumId="+encodeURIComponent(t)}function L(e,t){return"

"+g(e)+""+g(t||"未提供")+"

"}function te(e){var t="";return e?(h&&(t='
编辑相册
'),(v?v.renderImage(e.coverFile,{alt:e.albumName+"封面",className:"album-cover-media"}):"")+'
'+L("相册名称",e.albumName)+L("相册说明",e.albumDesc)+L("照片数量",e.photoCount===void 0?"":e.photoCount)+L("备注",e.remark)+"
"+t):""}function fe(e){var t='查看照片';return h&&(t+='编辑'),'

'+g(e.albumName)+"

"+g(e.albumDesc||"照片数量:"+(e.photoCount||0))+'

'+t+"
"}function re(e){var t=Array.isArray(e)?e.map(B):[];return t.some(function(r){return!r})||!t.length?"":t.map(function(r){var n=h?'':"";return'
'+(v?v.renderImage(r.photoFile,{alt:r.photoTitle||"家族照片",className:"album-photo-media"}):"")+"

"+g(r.photoTitle||"未命名照片")+"

"+g([r.photoDesc,r.photographer,r.shootTime].filter(Boolean).join(" · ")||"文件已上传")+'

'+n+"
"}).join("")}function ae(e,t){var r=t&&(t.status||t.code);return!e||!e.getToken||!e.getToken()||Number(r)===401}function m(e){return Number(e&&(e.status||e.code))===403}function c(e,t){return ae(e,t)?(e&&e.clearToken&&e.clearToken(),u.NavigationUtil.open("login.html"),!0):!1}function T(e){u.layui&&u.layui.layer?u.layui.layer.msg(e):u.alert&&u.alert(e)}function M(){u.ProfileUI&&u.ProfileUI.syncGenealogyContextLinks&&u.ProfileUI.syncGenealogyContextLinks()}function O(e){return m(e)?"forbidden":"error"}function i(e,t,r){var n=f(e);if(n){if(!t){n.innerHTML="";return}if(r&&u.ProfileUI&&u.ProfileUI.setApiState){u.ProfileUI.setApiState(n,r,t);return}n.textContent=t}}function x(e,t){var r=f("[data-album-editor-placeholder]");F("[data-album-editor], [data-album-editor-action], [data-album-photo-editor]").forEach(function(n){n.hidden=!e}),r&&(r.hidden=!!e,t&&(r.textContent=t))}function y(e){N=!!e,F("[data-album-form] button, [data-album-form] input, [data-album-form] textarea, [data-album-photo-form] button, [data-album-photo-form] input, [data-album-photo-form] textarea, [data-album-delete-id], [data-album-photo-delete-id]").forEach(function(t){t.disabled=N})}function me(){var e="请先选择家谱,再查看或维护相册。";i("[data-album-list]",e,"empty"),i("[data-album-detail]","当前没有家谱上下文。","empty"),i("[data-album-photo-list]","当前没有家谱上下文。","empty"),x(!1,e),i("[data-album-form-status]",e,"forbidden"),i("[data-album-photo-status]",e,"forbidden")}function w(){var e=J();return e||me(),e}function ne(e){return!!(e&&(e.canEditContent===!0||e.canManage===!0))}async function V(e,t){var r=await e.genealogyDetail(t);return h=ne(r),F("[data-album-create-link], [data-album-editor-action], [data-album-photo-editor]").forEach(function(n){n.hidden=!h}),r}function ce(e){var t=f("[data-album-list]"),r=G(e);return R=Object.create(null),r.forEach(function(n){R[n.albumId]=n}),t?!Array.isArray(e)||e.length&&!r.length?(i("[data-album-list]","相册响应缺少稳定 AlbumVo 字段,请联系后端核对。","error"),[]):r.length?(t.innerHTML=r.map(fe).join(""),r):(i("[data-album-list]","暂无相册","empty"),[]):r}async function oe(){var e=A(),t;if(!c(e)&&(t=w(),!!t)){M(),i("[data-album-list]","正在加载相册…","loading");try{await V(e,t),ce(await e.albums(t))}catch(r){if(c(e,r))return;i("[data-album-list]",m(r)?"当前账号无权查看该家谱的相册。":"相册加载失败,请稍后重试。",O(r)),T(m(r)?"当前账号无权查看该家谱的相册。":r.message||"相册加载失败")}}}function ue(e){var t={};return F("[name]",e).forEach(function(r){t[r.name]=r.value}),t}function S(e,t,r){var n=f(e+' [name="'+t+'"]');n&&(n.value=r==null?"":String(r))}function be(e){if(!e||e.status!=="0")throw new Error("停用相册无法通过当前 PC 接口重新读取或编辑");S("[data-album-form]","albumName",e.albumName),S("[data-album-form]","albumDesc",e.albumDesc),S("[data-album-form]","coverOssId",e.coverOssId),S("[data-album-form]","sortOrder",e.sortOrder),S("[data-album-form]","status","0")}async function pe(){var e=A(),t,r,n,a;if(!c(e)&&(t=w(),!!t)){r=P(),M(),x(!1,"正在加载相册编辑信息…"),i("[data-album-form-status]","正在读取家谱权限…","loading");try{if(await V(e,t),!h)throw{status:403,message:"当前账号无权维护该家谱的相册。"};if(r){if(n=await e.albums(t),a=k(n,r),!a)throw new Error("相册列表未返回当前相册");be(a),f("[data-album-editor-title]")&&(f("[data-album-editor-title]").textContent="编辑相册")}x(!0),i("[data-album-form-status]","")}catch(o){if(c(e,o))return;x(!1,m(o)?"当前账号无权维护该家谱的相册。":o.message||"相册编辑信息加载失败"),i("[data-album-form-status]",m(o)?"当前账号无权维护该家谱的相册。":o.message||"相册编辑信息加载失败",O(o))}}}async function H(e,t,r){var n,a,o=f("[data-album-photo-list]");return i("[data-album-photo-list]","正在加载照片…","loading"),n=await e.albumPhotos(t,r),a=Z(n),j=Object.create(null),a.forEach(function(d){j[d.photoId]=d}),o?!Array.isArray(n)||n.length&&!a.length?(i("[data-album-photo-list]","照片响应缺少稳定 AlbumPhotoVo 字段,请联系后端核对。","error"),[]):a.length?(o.innerHTML=re(a),a):(i("[data-album-photo-list]","暂无照片","empty"),[]):a}async function ge(){var e=A(),t,r,n;if(!c(e)){if(t=w(),r=P(),!t||!r){t&&(i("[data-album-detail]","缺少有效相册编号。","error"),i("[data-album-photo-list]","缺少有效相册编号。","empty"));return}M(),i("[data-album-detail]","正在加载相册信息…","loading"),i("[data-album-photo-list]","正在加载照片…","loading");try{if(await V(e,t),n=await e.albums(t),D=k(n,r),!D)throw new Error("相册列表未返回当前相册");f("[data-album-detail]")&&(f("[data-album-detail]").innerHTML=te(D)),f("[data-album-detail-title]")&&(f("[data-album-detail-title]").textContent=D.albumName)}catch(a){if(c(e,a))return;i("[data-album-detail]",m(a)?"当前账号无权查看该相册。":a.message||"相册详情加载失败",O(a)),i("[data-album-photo-list]",m(a)?"当前账号无权查看该相册。":"照片加载失败,请稍后重试。",O(a)),T(m(a)?"当前账号无权查看该相册。":a.message||"相册详情加载失败");return}try{await H(e,t,r)}catch(a){if(c(e,a))return;i("[data-album-photo-list]",m(a)?"当前账号无权查看该相册照片。":a.message||"照片加载失败",O(a)),T(m(a)?"当前账号无权查看该相册照片。":a.message||"照片加载失败")}}}async function he(e){var t=A(),r=w(),n=P(),a,o,d,s,b,ie;if(!(N||!h||!r||c(t))){if(a=K(ue(e)),o=Q(a),o){i("[data-album-form-status]",o,"error");return}delete a.invalidCoverOssId,delete a.invalidSortOrder,y(!0),i("[data-album-form-status]","正在保存相册…","loading");try{if(d=n?await t.updateAlbum(r,n,a):await t.createAlbum(r,a),s=q(d),b=n||s&&s.albumId,!b)throw new Error("保存响应缺少 albumId");if(ie=k(await t.albums(r),b),!ie)throw new Error("保存后相册列表未返回同一相册");u.NavigationUtil.open(ee(r,b))}catch(z){if(c(t,z))return;i("[data-album-form-status]",m(z)?"当前账号无权保存该相册。":z.message||"相册保存失败",O(z))}finally{y(!1)}}}async function ve(e){var t=A(),r=w(),n=P(),a,o,d;if(!(N||!h||!r||!n||c(t))){if(a=X(ue(e)),o=Y(a),o){i("[data-album-photo-status]",o,"error");return}delete a.invalidOssId,delete a.invalidSortOrder,y(!0),i("[data-album-photo-status]","正在添加照片…","loading");try{if(d=B(await t.createAlbumPhoto(r,n,a)),!d||d.albumId!==n)throw new Error("添加照片响应缺少稳定 photoId");if(!(await H(t,r,n)).some(function(s){return s.photoId===d.photoId}))throw new Error("添加后照片列表未返回同一照片");e.reset(),i("[data-album-photo-status]","照片已添加")}catch(s){if(c(t,s))return;i("[data-album-photo-status]",m(s)?"当前账号无权添加照片。":s.message||"照片添加失败",O(s))}finally{y(!1)}}}async function Ie(e){var t=A(),r=w(),n=R[e]||D;if(!(N||!h||!r||!p(e)||c(t))&&await C("确认删除“"+(n?n.albumName:"该相册")+"”及其中全部照片吗?删除后不可恢复。")){y(!0);try{await t.deleteAlbum(r,e),f("[data-album-page]")?await oe():u.NavigationUtil.open(se(r))}catch(a){if(c(t,a))return;T(m(a)?"当前账号无权删除该相册。":a.message||"相册删除失败")}finally{y(!1)}}}async function ye(e){var t=A(),r=w(),n=P(),a=j[e];if(!(N||!h||!r||!n||!p(e)||c(t))&&await C("确认删除“"+(a&&a.photoTitle||"该照片")+"”吗?删除后不可恢复。")){y(!0);try{await t.deleteAlbumPhoto(r,n,e),await H(t,r,n),T("照片已删除")}catch(o){if(c(t,o))return;T(m(o)?"当前账号无权删除该照片。":o.message||"照片删除失败")}finally{y(!1)}}}function Ae(){I&&(I.addEventListener("click",function(e){var t=e.target.closest("[data-album-delete-id]"),r=e.target.closest("[data-album-photo-delete-id]");if(t){e.preventDefault(),Ie(t.getAttribute("data-album-delete-id"));return}r&&(e.preventDefault(),ye(r.getAttribute("data-album-photo-delete-id")))}),I.addEventListener("submit",function(e){var t=e.target.closest("[data-album-form]"),r=e.target.closest("[data-album-photo-form]");t?(e.preventDefault(),he(t)):r&&(e.preventDefault(),ve(r))}))}function Oe(){I&&(Ae(),f("[data-album-page]")&&oe(),f("[data-album-edit-page]")&&pe(),f("[data-album-detail-page]")&&ge())}return{getCurrentGenealogyId:J,getCurrentAlbumId:P,buildAlbumBody:K,validateAlbumBody:Q,buildAlbumPhotoBody:X,validateAlbumPhotoBody:Y,canEditAlbums:ne,normalizeAlbum:q,normalizeAlbumPhoto:B,normalizeAlbums:G,normalizeAlbumPhotos:Z,findAlbumById:k,renderAlbumDetail:te,renderPhotoList:re,shouldRedirectToLogin:ae,isForbidden:m,init:Oe}}); diff --git a/dist/public/js/app-promotion-pages.js b/dist/public/js/app-promotion-pages.js deleted file mode 100644 index 3deef18..0000000 --- a/dist/public/js/app-promotion-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(t,o){if(typeof module=="object"&&module.exports){module.exports=o(t);return}t.AppPromotionPages=o(t),t.document&&t.document.addEventListener("DOMContentLoaded",function(){t.AppPromotionPages.init()})})(typeof globalThis!="undefined"?globalThis:window,function(t){"use strict";var o=t.MediaDisplay||(typeof require=="function"?require("./media-display.js"):null);function u(r){return String(r==null?"":r).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function P(r){var e;return typeof r=="number"&&!Number.isSafeInteger(r)||r==null?"":(e=String(r).trim(),/^[1-9][0-9]*$/.test(e)?e:"")}function d(r){return r==null?"":String(r).trim()}function f(r){var e=d(r),n;if(!e)return"";try{n=new URL(e)}catch(l){return""}return n.protocol==="http:"||n.protocol==="https:"?n.href:""}function s(r){var e=r||{},n=P(e.promotionId),l=d(e.promotionTitle),i=o&&o.normalizeFileAccess(e.coverFile),p;return!n||!l||String(e.status)!=="0"||e.coverFile!==void 0&&e.coverFile!==null&&!i?null:(p={promotionId:n,promotionTitle:l,promotionDesc:d(e.promotionDesc),targetUrl:f(e.targetUrl)},i&&(p.coverFile=i),p)}function m(r){var e;return Array.isArray(r)?(e=r.map(s),e.some(function(n){return!n})?[]:e):[]}function g(r){var e=(o?o.renderImage(r.coverFile,{alt:r.promotionTitle+"封面",className:"promotion-cover-media"}):"")+"

"+u(r.promotionTitle)+"

"+u(r.promotionDesc)+"

";return r.targetUrl?''+e+"":'
'+e+"
"}function h(r){var e=m(r);return e.map(g).join("")}function a(r,e,n){if(r){if(t.ProfileUI&&t.ProfileUI.setApiState){t.ProfileUI.setApiState(r,e,n);return}r.textContent=n}}async function v(r){var e,n;if(!r||typeof r.promotions!="function")throw new Error("应用推广接口不可用");if(e=await r.promotions({platform:"pc"}),n=m(e),!Array.isArray(e)||n.length!==e.length)throw new Error("推广列表响应无效");return n}function y(r){return Number(r&&(r.status||r.code))===401}function c(r){return Number(r&&(r.status||r.code))===403}async function T(){var r=t.document&&t.document.querySelector("[data-promotion-page]"),e=t.document&&t.document.querySelector("[data-promotion-list]"),n=t.GenealogyApi&&t.GenealogyApi.defaultClient;if(!(!r||!e)){if(!n||!n.getToken||!n.getToken()){a(e,"forbidden","登录后可查看应用推广。");return}a(e,"loading","正在读取应用推广…");try{var l=await v(n);if(!l.length){a(e,"empty","当前暂无应用推广。");return}e.innerHTML=l.map(g).join("")}catch(i){if(y(i)){n.clearToken&&n.clearToken(),a(e,"forbidden","登录后可查看应用推广。");return}a(e,c(i)?"forbidden":"error",c(i)?"当前账号无权查看应用推广。":"应用推广读取失败,请稍后重试。")}}}return{normalizePromotion:s,normalizePromotionList:m,normalizeTargetUrl:f,renderPromotionList:h,setPromotionState:a,loadPromotions:v,isUnauthorized:y,isForbidden:c,init:T}}); diff --git a/dist/public/js/article-pages.js b/dist/public/js/article-pages.js deleted file mode 100644 index 6c0a1bf..0000000 --- a/dist/public/js/article-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(i,P){if(typeof module=="object"&&module.exports){module.exports=P(i);return}i.ArticlePages=P(i),i.document&&i.document.addEventListener("DOMContentLoaded",function(){i.ArticlePages.init()})})(typeof globalThis!="undefined"?globalThis:window,function(i){"use strict";function P(e){return i.ProfileUI&&typeof i.ProfileUI.confirmAction=="function"?i.ProfileUI.confirmAction(e):Promise.resolve(!i.confirm||i.confirm(e))}var N=i.MediaDisplay||(typeof require=="function"?require("./media-display.js"):null),y=i.document,T=Object.create(null),w=!1,A=!1;function U(){return i.GenealogyApi&&i.GenealogyApi.defaultClient}function l(e,r){return y?(r||y).querySelector(e):null}function L(e,r){return y?Array.prototype.slice.call((r||y).querySelectorAll(e)):[]}function d(e){return e==null?"":String(e)}function q(e){var r=d(e).trim();return r||void 0}function z(e,r){return new URLSearchParams(d(e).replace(/^\?/,"")).get(r)||""}function o(e){return e==null||e===""||typeof e=="number"&&!Number.isSafeInteger(e)?"":/^[1-9][0-9]*$/.test(String(e))?String(e):""}function G(e){return e==null||e===""?"":o(e)}function M(e){var r=e===void 0&&i.location?i.location.search:e,t;return e===void 0&&i.ProfileUI&&i.ProfileUI.getGenealogyId&&(t=i.ProfileUI.getGenealogyId(),t)?o(t):o(z(r,"genealogyId"))}function R(e){var r=e===void 0&&i.location?i.location.search:e;return o(z(r,"articleId"))}function x(e){var r;if(!(e==null||e===""))return r=Number(e),Number.isSafeInteger(r)?r:void 0}function H(e){var r=e||{},t={articleTitle:d(r.articleTitle).trim(),articleContent:d(r.articleContent).trim(),status:"0"},n=q(r.articleSummary),a=G(r.coverOssId),v=q(r.authorName),p=x(r.sortOrder);return n!==void 0&&(t.articleSummary=n),r.coverOssId!==void 0&&r.coverOssId!==null&&r.coverOssId!==""&&(a?t.coverOssId=a:t.invalidCoverOssId=!0),v!==void 0&&(t.authorName=v),r.sortOrder!==void 0&&r.sortOrder!==null&&r.sortOrder!==""&&(p!==void 0?t.sortOrder=p:t.invalidSortOrder=!0),t}function V(e){return!e||!e.articleTitle?"请填写谱文标题":e.articleContent?e.invalidCoverOssId?"封面文件编号无效,请重新选择文件":e.invalidSortOrder?"排序值必须是安全整数":e.status==="1"?"当前 PC 无法重新读取停用谱文,暂不开放停用":e.status!=="0"?"谱文状态只能是 0":"":"请填写谱文正文"}function O(e){var r=o(e&&e.articleId),t=o(e&&e.genealogyId),n=G(e&&e.categoryId),a=N&&N.normalizeFileAccess(e&&e.coverFile),v=d(e&&e.articleTitle).trim(),p=d(e&&e.articleContent).trim(),C=x(e&&e.viewCount),g=x(e&&e.sortOrder),I=d(e&&e.status),S;return!r||!t||!v||!p||e.categoryId!==void 0&&e.categoryId!==null&&e.categoryId!==""&&!n||e.coverFile!==void 0&&e.coverFile!==null&&!a||e.viewCount!==void 0&&e.viewCount!==null&&e.viewCount!==""&&C===void 0||e.sortOrder!==void 0&&e.sortOrder!==null&&e.sortOrder!==""&&g===void 0||I!=="0"&&I!=="1"?null:(S={articleId:r,genealogyId:t,genealogyNo:d(e.genealogyNo),genealogyName:d(e.genealogyName),surname:d(e.surname),categoryName:d(e.categoryName),categoryCode:d(e.categoryCode),articleTitle:v,articleSummary:d(e.articleSummary),articleContent:p,authorName:d(e.authorName),publishTime:d(e.publishTime),status:I,remark:d(e.remark)},n&&(S.categoryId=n),a&&(S.coverFile=a),C!==void 0&&(S.viewCount=C),g!==void 0&&(S.sortOrder=g),S)}function j(e){var r;return Array.isArray(e)?(r=e.map(O),r.some(function(t){return!t})?[]:r):[]}function W(e,r){var t=O(e);return!!(t&&t.articleId===o(r))}function c(e){return d(e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function re(e,r){var t="profile-article.html?genealogyId="+encodeURIComponent(e);return r?t+"&articleId="+encodeURIComponent(r):t}function $(e,r){var t="profile-article-edit.html?genealogyId="+encodeURIComponent(e);return r?t+"&articleId="+encodeURIComponent(r):t}function J(e){return!!(e&&(e.canEditContent===!0||e.canManage===!0))}function m(e,r){return"

"+c(e)+""+c(r||"未提供")+"

"}function K(e){var r="";return e?(A&&(r='
编辑谱文
'),(N?N.renderImage(e.coverFile,{alt:e.articleTitle+"封面",className:"article-cover-media"}):"")+'
'+m("标题",e.articleTitle)+m("摘要",e.articleSummary)+m("正文",e.articleContent)+m("作者",e.authorName)+m("分类",e.categoryName)+m("发布时间",e.publishTime)+m("浏览量",e.viewCount===void 0?"":e.viewCount)+m("备注",e.remark)+"
"+r):X("empty","请选择一篇谱文查看详情")}function te(e){var r='';return A&&(r+='编辑'),'

'+c(e.articleTitle)+"

"+c(e.articleSummary||e.authorName||e.publishTime||"暂无摘要")+'

'+r+"
"}function Q(e,r){var t=r&&(r.status||r.code);return!e||!e.getToken||!e.getToken()||Number(t)===401}function u(e){return Number(e&&(e.status||e.code))===403}function f(e,r){return Q(e,r)?(e&&e.clearToken&&e.clearToken(),i.NavigationUtil.open("login.html"),!0):!1}function D(e){i.layui&&i.layui.layer?i.layui.layer.msg(e):i.alert&&i.alert(e)}function X(e,r){return i.ProfileUI&&i.ProfileUI.renderApiState?i.ProfileUI.renderApiState(e,r):'
'+c(r)+"
"}function s(e,r,t){if(e){if(i.ProfileUI&&i.ProfileUI.setApiState){i.ProfileUI.setApiState(e,r,t);return}e.innerHTML=X(r,t)}}function ie(e){return e.indexOf("无权")>=0?"forbidden":/失败|缺少/.test(e)?"error":/正在|读取中|加载中/.test(e)?"loading":"empty"}function Y(){i.ProfileUI&&i.ProfileUI.syncGenealogyContextLinks&&i.ProfileUI.syncGenealogyContextLinks()}function h(e,r){var t=l("[data-article-form-status]");if(t){if(!e){t.innerHTML="";return}if(r&&i.ProfileUI&&i.ProfileUI.setApiState){i.ProfileUI.setApiState(t,r,e);return}t.textContent=e}}function F(e,r,t){var n=l("[data-article-editor-placeholder]");L("[data-article-editor], [data-article-editor-action]").forEach(function(a){a.hidden=!e}),n&&(n.hidden=!!e,r&&s(n,t||ie(r),r))}function k(e,r){var t=l('[data-article-form] [type="submit"]');w=!!e,L("[data-article-form] button, [data-article-form] input, [data-article-form] textarea, [data-article-delete-id]").forEach(function(n){n.disabled=w}),t&&(t.dataset.defaultLabel||(t.dataset.defaultLabel=t.textContent),t.textContent=w&&r?r:t.dataset.defaultLabel,t.setAttribute("aria-busy",w?"true":"false"))}function ne(){var e="请先选择家谱,再查看或维护谱文。",r=l("[data-article-list]"),t=l("[data-article-detail]");s(r,"empty",e),s(t,"empty","当前没有家谱上下文。"),F(!1,e,"empty"),h(e,"empty")}function E(){var e=M();return e||ne(),e}async function Z(e,r){var t=await e.genealogyDetail(r);return A=J(t),L("[data-article-create-link], [data-article-editor-action]").forEach(function(n){n.hidden=!A}),t}function ae(e){var r=l("[data-article-list]"),t=j(e);return T=Object.create(null),t.forEach(function(n){T[n.articleId]=n}),r?!Array.isArray(e)||e.length&&!t.length?(s(r,"error","谱文响应缺少稳定 ArticleVo 字段,请联系后端核对。"),[]):t.length?(r.innerHTML=t.map(te).join(""),t):(s(r,"empty","暂无谱文"),[]):t}function _(e){var r=l("[data-article-detail]");r&&(r.innerHTML=K(e))}async function B(e){var r=U(),t=E(),n;if(!t||!o(e)||f(r))return null;s(l("[data-article-detail]"),"loading","正在加载谱文详情…");try{if(n=O(await r.articleDetail(t,e)),!n||n.articleId!==o(e))throw new Error("谱文详情响应缺少稳定 ArticleVo 字段");return T[n.articleId]=n,_(n),n}catch(a){return f(r,a)||(s(l("[data-article-detail]"),u(a)?"forbidden":"error",u(a)?"当前账号无权查看该谱文。":"谱文详情加载失败,请稍后重试。"),D(u(a)?"当前账号无权查看该谱文。":a.message||"谱文详情加载失败")),null}}async function ee(){var e=U(),r,t,n;if(!f(e)&&(r=E(),!!r)){Y(),s(l("[data-article-list]"),"loading","正在加载谱文…");try{await Z(e,r),t=ae(await e.articles(r)),n=R(),n&&T[n]?await B(n):t.length?await B(t[0].articleId):_(null)}catch(a){if(f(e,a))return;s(l("[data-article-list]"),u(a)?"forbidden":"error",u(a)?"当前账号无权查看该家谱的谱文。":"谱文加载失败,请稍后重试。"),D(u(a)?"当前账号无权查看该家谱的谱文。":a.message||"谱文加载失败")}}}function le(e){var r={};return L("[name]",e).forEach(function(t){r[t.name]=t.value}),r}function b(e,r){var t=l('[data-article-form] [name="'+e+'"]');t&&(t.value=r==null?"":String(r))}function de(e){var r,t;if(!e||e.status!=="0")throw new Error("停用谱文无法通过当前 PC 接口重新读取或编辑");b("articleTitle",e.articleTitle),b("articleSummary",e.articleSummary),b("coverOssId",e.coverOssId),b("articleContent",e.articleContent),b("authorName",e.authorName),b("sortOrder",e.sortOrder),b("status","0"),r=l('[name="articleContent"]'),i.AppRichEditor&&i.AppRichEditor.init&&r&&(t=i.AppRichEditor.init(r),t&&t.editor&&t.editor.setHtml&&(t.editor.setHtml(e.articleContent),t.sync()))}async function ue(){var e=U(),r,t,n;if(!f(e)&&(r=E(),!!r)){t=R(),Y(),F(!1,"正在加载谱文编辑信息…","loading"),h("正在读取家谱权限…","loading");try{if(await Z(e,r),!A)throw{status:403,message:"当前账号无权维护该家谱的谱文。"};if(t){if(n=O(await e.articleDetail(r,t)),!n||n.articleId!==t)throw new Error("谱文详情响应缺少稳定 ArticleVo 字段");de(n),l("[data-article-editor-title]")&&(l("[data-article-editor-title]").textContent="编辑谱文")}F(!0),h("")}catch(a){if(f(e,a))return;F(!1,u(a)?"当前账号无权维护该家谱的谱文。":a.message||"谱文编辑信息加载失败",u(a)?"forbidden":"error"),h(u(a)?"当前账号无权维护该家谱的谱文。":a.message||"谱文编辑信息加载失败",u(a)?"forbidden":"error")}}}async function oe(e){var r=U(),t=E(),n=R(),a,v,p,C,g;if(!(w||!A||!t||f(r))){if(i.AppRichEditor&&i.AppRichEditor.syncAll&&i.AppRichEditor.syncAll(),a=H(le(e)),v=V(a),v){h(v,"error");return}delete a.invalidCoverOssId,delete a.invalidSortOrder,k(!0,"正在保存…"),h("正在保存谱文…","loading");try{if(p=n?await r.updateArticle(t,n,a):await r.createArticle(t,a),C=O(p),g=n||C&&C.articleId,!g)throw new Error("保存响应缺少 articleId");if(!W(await r.articleDetail(t,g),g))throw new Error("保存后重读未返回同一篇谱文");i.NavigationUtil.open(re(t,g))}catch(I){if(f(r,I))return;h(u(I)?"当前账号无权保存该谱文。":I.message||"谱文保存失败",u(I)?"forbidden":"error")}finally{k(!1)}}}async function ce(e){var r=U(),t=E(),n=T[e];if(!(w||!A||!t||!o(e)||f(r))&&await P("确认删除“"+(n?n.articleTitle:"该谱文")+"”吗?删除后不可恢复。")){k(!0);try{await r.deleteArticle(t,e),await ee(),D("谱文已删除")}catch(a){if(f(r,a))return;D(u(a)?"当前账号无权删除该谱文。":a.message||"谱文删除失败")}finally{k(!1)}}}function fe(){y&&(y.addEventListener("click",function(e){var r=e.target.closest("[data-article-detail-id]"),t=e.target.closest("[data-article-delete-id]");if(r){e.preventDefault(),B(r.getAttribute("data-article-detail-id"));return}t&&(e.preventDefault(),ce(t.getAttribute("data-article-delete-id")))}),y.addEventListener("submit",function(e){var r=e.target.closest("[data-article-form]");r&&(e.preventDefault(),oe(r))}))}function se(){y&&(fe(),l("[data-article-page]")&&ee(),l("[data-article-edit-page]")&&ue())}return{getCurrentGenealogyId:M,getCurrentArticleId:R,buildArticleBody:H,validateArticleBody:V,normalizeArticle:O,canEditArticles:J,normalizeArticles:j,matchesSavedArticle:W,renderArticleDetail:K,shouldRedirectToLogin:Q,isForbidden:u,init:se}}); diff --git a/dist/public/js/auth-pages.js b/dist/public/js/auth-pages.js deleted file mode 100644 index 603b343..0000000 --- a/dist/public/js/auth-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(t,c){if(typeof module=="object"&&module.exports){module.exports=c(t);return}t.AuthPages=c(t),t.document&&t.document.addEventListener("DOMContentLoaded",function(){t.AuthPages.init()})})(typeof globalThis!="undefined"?globalThis:window,function(t){"use strict";var c=t.document,o={login:{selector:"#login-password-form",operationCode:"password-login",successUrl:"profile.html"},"sms-login":{selector:"#login-sms-form",operationCode:"sms-login",successUrl:"profile.html"},register:{selector:"#register-form",operationCode:"register",successUrl:"login.html"},"password-reset":{selector:"#password-reset-form",operationCode:"forgot-password",successUrl:"login.html"}},q=60;function w(e){return t.hex_md5?t.hex_md5(e):t.md5?t.md5(e):e}function T(e,n){var r=n||w,i={phone:e.phone,password:r(e.password)};return e.validToken&&(i.validToken=e.validToken),i}function b(e){return{phone:e.phone,smsCode:e.smsCode}}function I(e,n){var r=n||w;return{phone:e.phone,password:r(e.password),nickName:e.nickName||e.phone,smsCode:e.smsCode}}function N(e,n){var r=n||w;return{phone:e.phone,smsCode:e.smsCode,newPassword:r(e.newPassword)}}function f(){return t.GenealogyApi&&t.GenealogyApi.defaultClient}async function O(e){if(t.PublicNavigation){t.PublicNavigation.clearUser();try{t.PublicNavigation.storeUser(await e.currentProfile())}catch(n){t.PublicNavigation.clearUser()}}}function x(e){var n=o[e]||{};return n.operationCode||""}function g(e){var n=o[e]||{};return n.successUrl||""}function G(e){var n=e==null?"":String(e).trim();return typeof e=="number"&&!Number.isSafeInteger(e)?"":/^[1-9][0-9]*$/.test(n)?n:""}function B(e){var n=(Array.isArray(e)?e:[]).map(function(r){return G(r&&r.genealogyId)}).filter(Boolean).filter(function(r,i,s){return s.indexOf(r)===i});return n.length===1?"profile-family-home.html?genealogyId="+encodeURIComponent(n[0]):"profile-families.html"}async function C(e,n){if(n!=="login"&&n!=="sms-login"||!e||!e.genealogiesMine)return g(n);try{return B(await e.genealogiesMine())}catch(r){return g(n)}}function y(e,n){var r=x(n);return r}async function _(e,n,r){return n?!t.CaptchaPages||!t.CaptchaPages.ensureToken?(S("验证码组件未加载,请刷新后重试"),!1):!!await t.CaptchaPages.ensureToken(e,{operationCode:n,subject:r}):!0}function p(e,n){return(n||c).querySelector(e)}function d(e,n){return Array.prototype.slice.call((n||c).querySelectorAll(e))}function u(e){var n={};return d("input[name], textarea[name], select[name]",e).forEach(function(r){n[r.name]=String(r.value||"").trim()}),n}function P(e){var n=p('input[name="validToken"]',e);n&&(n.value="")}function U(e){var n=p('input[name="validToken"]',e),r=n&&n.value||"";return n&&(n.value=""),r}function E(e){var n,r=q,i;!e||typeof t.setInterval!="function"||(e.__smsCooldownTimer&&typeof t.clearInterval=="function"&&t.clearInterval(e.__smsCooldownTimer),n=e.getAttribute("data-sms-code-label")||e.textContent.trim(),e.setAttribute("data-sms-code-label",n),e.disabled=!0,e.textContent=r+" 秒后重试",i=t.setInterval(function(){if(r-=1,r>0){e.textContent=r+" 秒后重试";return}typeof t.clearInterval=="function"&&t.clearInterval(i),e.__smsCooldownTimer=null,e.disabled=!1,e.textContent=n},1e3),e.__smsCooldownTimer=i)}function L(e,n){e&&(e.classList.toggle("is-loading",n),"disabled"in e&&(e.disabled=n),e.setAttribute("aria-busy",n?"true":"false"))}function m(e){return Number(e&&(e.status||e.code))===403?"forbidden":"error"}function a(e,n,r){var i=p("[data-auth-form-status]",e);if(i){if(!n){i.innerHTML="";return}if(t.ProfileUI&&t.ProfileUI.setApiState){t.ProfileUI.setApiState(i,r||"error",n);return}i.textContent=n}}function l(e,n,r){e&&(e.dataset.defaultLabel||(e.dataset.defaultLabel=e.textContent),e.disabled=!!n,e.textContent=n?r:e.dataset.defaultLabel,e.setAttribute("aria-busy",n?"true":"false"))}function S(e){if(t.MessageUtil&&t.MessageUtil.show){t.MessageUtil.show(e);return}t.console&&t.console.warn&&t.console.warn(e)}function M(e){return/^1\d{10}$/.test(e||"")}function k(e){return/^\d{4}$/.test(e||"")}function A(e,n){return e?e.length<6?"密码至少需要 6 位":"":n}function R(e,n){var r=n||{};if(!r.phone)return"请填写手机号";if(!M(r.phone))return"请输入正确的手机号";if(e==="login")return A(r.password,"请填写密码");if(e==="sms-login")return r.smsCode?k(r.smsCode)?"":"请输入正确的短信验证码":"请填写短信验证码";if(e==="register"){if(!r.smsCode)return"请填写短信验证码";if(!k(r.smsCode))return"请输入正确的短信验证码";var i=A(r.password,"请填写密码");return i||(r.confirmPassword?r.password!==r.confirmPassword?"两次输入的密码不一致":"":"请再次输入密码")}if(e==="password-reset"){if(!r.smsCode)return"请填写验证码";if(!k(r.smsCode))return"请输入正确的短信验证码";var s=A(r.newPassword,"请填写新密码");if(s)return s;if(!r.confirmPassword)return"请再次输入新密码";if(r.newPassword!==r.confirmPassword)return"两次输入的新密码不一致"}return""}function j(e){var n=e||{};return n.phone?M(n.phone)?"":"请输入正确的手机号":"请填写手机号"}function h(e,n,r){var i=n==="send-code"?j(r):R(n,r);return i?(a(e,i,"error"),!1):!0}function v(e,n){var r=p(e);r&&r.addEventListener("submit",function(i){i.preventDefault(),n(r,p('[type="submit"]',r))})}function $(e){d(".login-mode-tab").forEach(function(n){var r=n.getAttribute("aria-controls")===e;n.classList.toggle("is-active",r),n.setAttribute("aria-selected",r?"true":"false")}),d(".login-mode-panel").forEach(function(n){var r=n.id===e;n.hidden=!r,r||P(n)})}async function V(e,n){var r=f(),i=u(e);if(!r){a(e,"接口初始化失败,请刷新后重试","error");return}if(h(e,"login",i)){if(!await _(e,y(e,"login"),i.phone)){a(e,"请完成验证后再提交登录。","error");return}i=u(e),i.validToken=U(e),l(n,!0,"正在登录…"),a(e,"正在验证账号…","loading");try{await r.login(T(i)),await O(r),t.NavigationUtil.open(await C(r,"login"))}catch(s){a(e,s.message||"登录失败",m(s))}finally{l(n,!1,"正在登录…")}}}async function z(e,n){var r=f(),i=u(e);if(!r){a(e,"接口初始化失败,请刷新后重试","error");return}if(h(e,"sms-login",i)){l(n,!0,"正在登录…"),a(e,"正在验证短信验证码…","loading");try{await r.loginBySms(b(i)),await O(r),t.NavigationUtil.open(await C(r,"sms-login"))}catch(s){a(e,s.message||"短信登录失败",m(s))}finally{l(n,!1,"正在登录…")}}}async function H(e,n){var r=f(),i=u(e);if(!r){a(e,"接口初始化失败,请刷新后重试","error");return}if(h(e,"register",i)){l(n,!0,"正在注册…"),a(e,"正在创建账号…","loading");try{await r.register(I(i)),t.NavigationUtil.open(g("register"))}catch(s){a(e,s.message||"注册失败",m(s))}finally{l(n,!1,"正在注册…")}}}async function W(e){var n=f(),r=e.closest("form"),i=u(r),s=Y(r),F=y(r,s);if(!n){a(r,"接口初始化失败,请刷新后重试","error");return}if(h(r,"send-code",i)){if(!await _(r,F,i.phone)){a(r,"请完成验证后再获取验证码。","error");return}i=u(r),i.validToken=U(r),L(e,!0),a(r,"正在发送验证码…","loading");try{await n.sendSmsCode(F,{phone:i.phone,validToken:i.validToken||""}),S("验证码已发送"),a(r,""),L(e,!1),E(e)}catch(D){a(r,D.message||"验证码发送失败",m(D)),L(e,!1)}}}async function J(e,n){var r=f(),i=u(e);if(!r){a(e,"接口初始化失败,请刷新后重试","error");return}if(h(e,"password-reset",i)){l(n,!0,"正在重设…"),a(e,"正在重设密码…","loading");try{await r.resetPassword(N(i)),S("密码已重设,请重新登录"),t.NavigationUtil.open(g("password-reset"))}catch(s){a(e,s.message||"重设密码失败",m(s))}finally{l(n,!1,"正在重设…")}}}function K(){d("[data-api-send-code]").forEach(function(e){e.addEventListener("click",function(n){n.preventDefault(),W(e)})})}function Q(){d(".login-mode-tab").forEach(function(e){e.addEventListener("click",function(){$(e.getAttribute("aria-controls"))})})}function X(){d('form input[name="phone"]').forEach(function(e){e.addEventListener("input",function(){P(e.closest("form"))})})}function Y(e){return e&&Object.keys(o).find(function(n){return o[n].selector==="#"+e.id})||""}function Z(){c&&(v(o.login.selector,V),v(o["sms-login"].selector,z),v(o.register.selector,H),v(o["password-reset"].selector,J),Q(),K(),X())}return{buildLoginBody:T,buildSmsLoginBody:b,buildRegisterBody:I,buildPasswordResetBody:N,clearCaptchaToken:P,takeCaptchaToken:U,startSmsCooldown:E,getCaptchaOperation:x,getFormCaptchaOperation:y,getSuccessUrl:g,getPostLoginUrl:B,resolvePostLoginUrl:C,validateAuthValues:R,init:Z}}); diff --git a/dist/public/js/captcha-pages.js b/dist/public/js/captcha-pages.js deleted file mode 100644 index 7c53421..0000000 --- a/dist/public/js/captcha-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(t,y){if(typeof module=="object"&&module.exports){module.exports=y(t);return}t.CaptchaPages=y(t)})(typeof globalThis!="undefined"?globalThis:window,function(t){"use strict";var y=["318px","318px"],L=0;function V(){return t.GenealogyApi&&t.GenealogyApi.defaultClient}function b(a,e){return(e||t.document).querySelector(a)}function T(a){if(t.MessageUtil&&t.MessageUtil.show){t.MessageUtil.show(a);return}t.console&&t.console.warn&&t.console.warn(a)}function p(a,e){var r=a||{};return{tenantId:r.tenantId||e.tenantId,subject:r.subject||""}}function j(a){var e=a||{},r=e.payload||{};return Object.assign({},r,{type:e.captchaType||r.type||"SLIDER",id:r.id||e.challengeId,challengeId:e.challengeId,providerCode:e.providerCode||"tianai",captchaType:e.captchaType||r.type||"SLIDER"})}function m(a){var e=a||{};return Number(e.code)!==200||!e.data?e:Object.assign({},e,{data:j(e.data)})}function w(a,e,r){var n=e||{},i=a||{},o=x(i.data||{});return{tenantId:n.tenantId||r.tenantId,subject:n.subject||"",challengeId:n.challengeId||i.challengeId||i.id,providerCode:n.providerCode||"tianai",captchaType:n.captchaType||"SLIDER",payload:{track:o}}}function x(a){var e=Object.assign({},a||{}),r=Array.isArray(e.trackList)?e.trackList:[],n=r[0]||{},i=r[r.length-1]||{};return typeof e.left=="undefined"&&(e.left=h(i.x)-h(n.x)),typeof e.top=="undefined"&&(e.top=h(i.y)-h(n.y)),e}function h(a){var e=Number(a);return Number.isFinite(e)?e:0}function k(a){var e=a||{};return Number(e.code)===200&&e.data&&e.data.passed===!1?Object.assign({},e,{code:4001,msg:e.data.message||e.msg||"验证失败"}):e}function R(a){var e=a||{},r=e.data||{};return Number(e.code)!==200||r.passed===!1?"":r.validToken||e.validToken||""}function I(a){return b('input[name="validToken"]',a)}function A(){if(!t.layui||!t.layui.layer||!t.layui.layer.open||!t.document)throw new Error("缺少 layui.layer 验证码弹层依赖");var a="captcha-layer-"+ ++L,e='[data-captcha-layer-box="'+a+'"]',r=t.layui.layer.open({type:1,title:!1,closeBtn:0,area:y,offset:"auto",content:'
'}),n=b(e);if(!n)throw t.layui.layer.close(r),new Error("缺少 Layui 验证码实际挂载点");return{box:n,layerIndex:r}}function f(a){a&&(a.box&&(a.box.innerHTML=""),t.layui&&t.layui.layer&&t.layui.layer.close&&t.layui.layer.close(a.layerIndex))}function O(a,e){var r=I(a);r&&(r.value=e||"")}function E(a){return a?a.required!==!1:!0}function D(a,e,r,n,i){var o=e||{},d,v,N=p(o,r),q={},C,g;if(!t.TAC||!t.CaptchaConfig)return i(new Error("滑动验证组件未加载")),null;try{d=A(),v=d.box}catch(u){return i(u),null}g=new t.CaptchaConfig({bindEl:v,requestCaptchaDataUrl:r.captchaChallengeUrl(o.operationCode),validCaptchaUrl:r.captchaVerifyUrl(o.operationCode),requestHeaders:{clientid:r.clientId},validSuccess:function(u,s,l){var c=R(u);if(!c){l&&l.destroyWindow&&l.destroyWindow(),f(d),i(new Error("滑动验证未返回有效票据"));return}O(a,c),l&&l.destroyWindow&&l.destroyWindow(),f(d),n(c)},validFail:function(u,s,l){l&&l.reloadCaptcha&&l.reloadCaptcha(),T(u&&(u.msg||u.message)||"滑动验证失败,请重试")},btnCloseFun:function(u,s){s&&s.destroyWindow(),f(d),i(new Error("已取消滑动验证"))}}),g.addRequestChain({preRequest:function(u,s){return u==="requestCaptchaData"&&(s.data=p(o,r)),u==="validCaptcha"&&(s.data=w(s.data,Object.assign({},N,q),r)),!0},postRequest:function(u,s,l){if(u==="requestCaptchaData"){var c=m(l);q={challengeId:c.data&&c.data.challengeId,providerCode:c.data&&c.data.providerCode,captchaType:c.data&&c.data.captchaType},Object.keys(l||{}).forEach(function(S){delete l[S]}),Object.assign(l,c)}return u==="validCaptcha"&&Object.assign(l,k(l)),!0}});try{C=new t.TAC(g),C.init()}catch(u){return f(d),i(u),null}return C}async function U(a,e){var r=V(),n=I(a),i=e||{},o;if(!r)return"";if(n&&n.value)return n.value;if(!i.operationCode)return"";try{if(o=await r.captchaRequirement(i.operationCode,p(i,r)),!E(o))return"CAPTCHA_NOT_REQUIRED"}catch(d){o=null}return new Promise(function(d,v){D(a,i,r,d,v)}).catch(function(d){return T(d.message||"请先完成滑动验证"),""})}return{buildChallengeBody:p,adaptChallengeResponse:m,buildVerifyBody:w,normalizeVerifyTrack:x,createCaptchaLayer:A,closeCaptchaLayer:f,extractValidToken:R,normalizeVerifyResponse:k,shouldRequireCaptcha:E,ensureToken:U}}); diff --git a/dist/public/js/ceremony-admin-pages.js b/dist/public/js/ceremony-admin-pages.js deleted file mode 100644 index 53a7914..0000000 --- a/dist/public/js/ceremony-admin-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(d,G){if(typeof module=="object"&&module.exports){module.exports=G(d);return}d.CeremonyAdminPages=G(d),d.document&&d.document.addEventListener("DOMContentLoaded",function(){d.CeremonyAdminPages.init()})})(typeof globalThis!="undefined"?globalThis:window,function(d){"use strict";function G(e){return d.ProfileUI&&typeof d.ProfileUI.confirmAction=="function"?d.ProfileUI.confirmAction(e):Promise.resolve(!d.confirm||d.confirm(e))}var F=d.MediaDisplay||(typeof require=="function"?require("./media-display.js"):null),b=d.document,V=Object.create(null),q=Object.create(null),H=Object.create(null),k=null,v=!1,w=!1,ge=["PENDING","ACCEPTED","DECLINED","CANCELED"],ve={PENDING:"待响应",ACCEPTED:"已接受",DECLINED:"已拒绝",CANCELED:"已取消"};function N(){return d.GenealogyApi&&d.GenealogyApi.defaultClient}function c(e,n){return b?(n||b).querySelector(e):null}function S(e,n){return b?Array.prototype.slice.call((n||b).querySelectorAll(e)):[]}function u(e){return e==null?"":String(e)}function M(e){var n=u(e).trim();return n||void 0}function y(e){return e==null||e===""||typeof e=="number"&&!Number.isSafeInteger(e)?"":/^[1-9][0-9]*$/.test(String(e))?String(e):""}function R(e){return e==null||e===""?"":y(e)}function Z(e,n){return new URLSearchParams(u(e).replace(/^\?/,"")).get(n)||""}function ee(e){var n=e===void 0&&d.location?d.location.search:e,r;return e===void 0&&d.ProfileUI&&d.ProfileUI.getGenealogyId&&(r=d.ProfileUI.getGenealogyId(),r)?y(r):y(Z(n,"genealogyId"))}function E(e){var n=e===void 0&&d.location?d.location.search:e;return y(Z(n,"ceremonyId"))}function x(e){var n;if(!(e==null||e===""))return n=Number(e),Number.isSafeInteger(n)?n:void 0}function O(e){var n;if(!(e==null||e===""))return n=Number(e),Number.isFinite(n)?n:void 0}function pe(e){var n=M(e);if(n)return/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(n)?n.replace("T"," ")+":00":/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/.test(n)?n.replace("T"," "):n}function Ie(e){var n=u(e);return/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}/.test(n)?n.slice(0,16).replace(" ","T"):""}function he(e){var n=u(e).match(/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/),r,t,i,a;return!n||(r=Number(n[1]),t=Number(n[2]),i=Number(n[3]),t<1||t>12||Number(n[4])>23||Number(n[5])>59||Number(n[6])>59)?!1:(a=[31,r%4===0&&(r%100!==0||r%400===0)?29:28,31,30,31,30,31,31,30,31,30,31],i>=1&&i<=a[t-1])}function ne(e){var n=e||{},r={ceremonyType:u(n.ceremonyType).trim(),ceremonyTitle:u(n.ceremonyTitle).trim(),status:"0"},t=["ceremonyDesc","location","locationAddress"],i=pe(n.ceremonyTime),a=O(n.longitude),f=O(n.latitude),l=R(n.coverOssId),I=x(n.sortOrder);return t.forEach(function(h){var P=M(n[h]);P!==void 0&&(r[h]=P)}),i!==void 0&&(r.ceremonyTime=i),n.longitude!==void 0&&n.longitude!==null&&n.longitude!==""&&(a!==void 0?r.longitude=a:r.invalidLongitude=!0),n.latitude!==void 0&&n.latitude!==null&&n.latitude!==""&&(f!==void 0?r.latitude=f:r.invalidLatitude=!0),n.coverOssId!==void 0&&n.coverOssId!==null&&n.coverOssId!==""&&(l?r.coverOssId=l:r.invalidCoverOssId=!0),n.sortOrder!==void 0&&n.sortOrder!==null&&n.sortOrder!==""&&(I!==void 0?r.sortOrder=I:r.invalidSortOrder=!0),r}function re(e){return!e||!e.ceremonyType?"请填写活动类型":e.ceremonyTitle?e.ceremonyTime!==void 0&&!he(e.ceremonyTime)?"活动时间格式无效":e.longitude===void 0!=(e.latitude===void 0)?"经度和纬度必须同时提供":e.invalidLongitude?"经度必须是有效数字":e.invalidLatitude?"纬度必须是有效数字":e.longitude!==void 0&&(e.longitude<-180||e.longitude>180)?"经度范围必须是 -180 到 180":e.latitude!==void 0&&(e.latitude<-90||e.latitude>90)?"纬度范围必须是 -90 到 90":u(e.locationAddress).length>300?"详细地址不能超过 300 个字符":e.invalidCoverOssId?"封面文件编号无效,请重新选择文件":e.invalidSortOrder?"排序值必须是安全整数":e.status==="1"?"当前 PC 无法重新读取停用活动,暂不开放停用":e.status!=="0"?"活动状态只能是 0":"":"请填写活动标题"}function te(e){var n=e||{},r={},t=M(n.giverName),i=M(n.giftMessage),a=O(n.giftAmount);return t!==void 0&&(r.giverName=t),n.giftAmount!==void 0&&n.giftAmount!==null&&n.giftAmount!==""&&(a!==void 0?r.giftAmount=a:r.invalidGiftAmount=!0),i!==void 0&&(r.giftMessage=i),r}function ie(e){return!e||e.giftAmount===void 0&&!e.invalidGiftAmount?"请填写礼金金额":e.invalidGiftAmount?"礼金金额必须是有效数字":e.giftAmount<0?"礼金金额不能小于 0":""}function L(e){var n=y(e&&e.ceremonyId),r=y(e&&e.genealogyId),t=R(e&&e.sponsorUserId),i=F&&F.normalizeFileAccess(e&&e.coverFile),a=u(e&&e.ceremonyType).trim(),f=u(e&&e.ceremonyTitle).trim(),l=O(e&&e.longitude),I=O(e&&e.latitude),h=x(e&&e.giftCount),P=O(e&&e.giftAmount),X=x(e&&e.sortOrder),Y=u(e&&e.status),T;return!n||!r||!a||!f||e.sponsorUserId!==void 0&&e.sponsorUserId!==null&&e.sponsorUserId!==""&&!t||e.coverFile!==void 0&&e.coverFile!==null&&!i||e.longitude!==void 0&&e.longitude!==null&&e.longitude!==""&&l===void 0||e.latitude!==void 0&&e.latitude!==null&&e.latitude!==""&&I===void 0||l===void 0!=(I===void 0)||e.giftCount!==void 0&&e.giftCount!==null&&e.giftCount!==""&&h===void 0||e.giftAmount!==void 0&&e.giftAmount!==null&&e.giftAmount!==""&&P===void 0||e.sortOrder!==void 0&&e.sortOrder!==null&&e.sortOrder!==""&&X===void 0||Y!=="0"&&Y!=="1"?null:(T={ceremonyId:n,genealogyId:r,genealogyNo:u(e.genealogyNo),genealogyName:u(e.genealogyName),surname:u(e.surname),sponsorNickName:u(e.sponsorNickName),sponsorPhone:u(e.sponsorPhone),ceremonyType:a,ceremonyTitle:f,ceremonyDesc:u(e.ceremonyDesc),ceremonyTime:u(e.ceremonyTime),location:u(e.location),locationAddress:u(e.locationAddress),status:Y,remark:u(e.remark)},t&&(T.sponsorUserId=t),i&&(T.coverFile=i),l!==void 0&&(T.longitude=l),I!==void 0&&(T.latitude=I),h!==void 0&&(T.giftCount=h),P!==void 0&&(T.giftAmount=P),X!==void 0&&(T.sortOrder=X),T)}function j(e){var n=y(e&&e.giftId),r=y(e&&e.genealogyId),t=y(e&&e.ceremonyId),i=R(e&&e.giverUserId),a=O(e&&e.giftAmount),f=u(e&&e.status),l;return!n||!r||!t||a===void 0||a<0||e.giverUserId!==void 0&&e.giverUserId!==null&&e.giverUserId!==""&&!i||f!=="0"&&f!=="1"?null:(l={giftId:n,genealogyId:r,genealogyNo:u(e.genealogyNo),genealogyName:u(e.genealogyName),surname:u(e.surname),ceremonyId:t,ceremonyTitle:u(e.ceremonyTitle),giverNickName:u(e.giverNickName),giverPhone:u(e.giverPhone),giverName:u(e.giverName),giftAmount:a,giftMessage:u(e.giftMessage),giftTime:u(e.giftTime),status:f,remark:u(e.remark)},i&&(l.giverUserId=i),l)}function $(e){var n=y(e&&e.invitationId),r=y(e&&e.genealogyId),t=y(e&&e.ceremonyId),i=y(e&&e.inviteeUserId),a=u(e&&e.inviteStatus),f=x(e&&e.inviteVersion),l;return!n||!r||!t||!i||ge.indexOf(a)<0||e.inviteVersion!==void 0&&e.inviteVersion!==null&&e.inviteVersion!==""&&f===void 0?null:(l={invitationId:n,genealogyId:r,ceremonyId:t,inviteeUserId:i,inviteStatus:a,deliveredTime:u(e.deliveredTime),readTime:u(e.readTime),responseTime:u(e.responseTime)},f!==void 0&&(l.inviteVersion=f),l)}function ae(e){var n=y(e&&e.memberId),r=y(e&&e.genealogyId),t=R(e&&e.appUserId),i=u(e&&e.status);return!n||!r||!t||i!=="0"?null:{memberId:n,genealogyId:r,appUserId:t,appUserNickName:u(e.appUserNickName),memberName:u(e.memberName),roleType:u(e.roleType),status:i}}function B(e,n){var r;return Array.isArray(e)?(r=e.map(n),r.some(function(t){return!t})?[]:r):[]}function oe(e){var n=Object.create(null),r=[];return(Array.isArray(e)?e:[]).forEach(function(t){var i=y(t);i&&!n[i]&&(n[i]=!0,r.push(i))}),{inviteeUserIds:r}}function m(e){return u(e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function A(e,n){return"

"+m(e)+""+m(n===""||n===void 0?"未提供":n)+"

"}function Ce(e){return"profile-ceremony.html?genealogyId="+encodeURIComponent(e)}function W(e,n){var r="profile-gift-edit.html?genealogyId="+encodeURIComponent(e);return n?r+"&ceremonyId="+encodeURIComponent(n):r}function de(e,n){return"profile-ceremony-detail.html?genealogyId="+encodeURIComponent(e)+"&ceremonyId="+encodeURIComponent(n)}function ue(e){var n="",r="";return e?(e.longitude!==void 0&&e.latitude!==void 0&&(n=' 打开地图'),v&&(r='
编辑活动
'),(F?F.renderImage(e.coverFile,{alt:e.ceremonyTitle+"封面",className:"ceremony-cover-media"}):"")+'
'+A("活动标题",e.ceremonyTitle)+A("活动类型",e.ceremonyType)+A("活动说明",e.ceremonyDesc)+A("活动时间",e.ceremonyTime)+A("地点",e.location)+"

详细地址"+m(e.locationAddress||"未提供")+n+"

"+A("发起人",e.sponsorNickName)+A("祭品数量",e.giftCount===void 0?"":e.giftCount)+A("礼金合计",e.giftAmount===void 0?"":e.giftAmount)+A("备注",e.remark)+"
"+r):""}function Ae(e){var n='查看详情';return v&&(n+='编辑'),'

'+m(e.ceremonyTitle)+"

"+m([e.ceremonyType,e.ceremonyTime,e.location].filter(Boolean).join(" · "))+'

'+n+"
"}function le(e){var n=Array.isArray(e)?e.map(j):[];return n.some(function(r){return!r})||!n.length?"":n.map(function(r){var t=v?'':"";return'

'+m(r.giverName||r.giverNickName||"匿名宗亲")+" · "+m(r.giftAmount)+"

"+m([r.giftMessage,r.giftTime].filter(Boolean).join(" · ")||"未提供留言")+'

'+t+"
"}).join("")}function Te(e){var n=B(e,$);return!Array.isArray(e)||e.length&&!n.length||!n.length?"":n.map(function(r){var t=H[r.inviteeUserId],i=t&&(t.memberName||t.appUserNickName)||"已邀请成员";return'

'+m(i)+"

"+m(ve[r.inviteStatus])+(r.deliveredTime?" · "+m(r.deliveredTime):"")+"

"}).join("")}function fe(e,n){var r=n&&(n.status||n.code);return!e||!e.getToken||!e.getToken()||Number(r)===401}function s(e){return Number(e&&(e.status||e.code))===403}function g(e,n){return fe(e,n)?(e&&e.clearToken&&e.clearToken(),d.NavigationUtil.open("login.html"),!0):!1}function D(e){d.layui&&d.layui.layer?d.layui.layer.msg(e):d.alert&&d.alert(e)}function p(e){return s(e)?"forbidden":"error"}function o(e,n,r){var t=c(e);if(t){if(!n){t.innerHTML="";return}if(r&&d.ProfileUI&&d.ProfileUI.setApiState){d.ProfileUI.setApiState(t,r,n);return}t.textContent=n}}function _(){d.ProfileUI&&d.ProfileUI.syncGenealogyContextLinks&&d.ProfileUI.syncGenealogyContextLinks()}function C(e){w=!!e,S("[data-ceremony-form] input, [data-ceremony-form] textarea, [data-ceremony-form] button, [data-ceremony-gift-form] input, [data-ceremony-gift-form] textarea, [data-ceremony-gift-form] button, [data-ceremony-invitee-form] input, [data-ceremony-invitee-form] button, [data-ceremony-delete-id], [data-ceremony-gift-delete-id]").forEach(function(n){n.disabled=w})}function z(e,n){var r=c("[data-ceremony-editor-placeholder]");S("[data-ceremony-editor], [data-ceremony-editor-action], [data-ceremony-admin-editor]").forEach(function(t){t.hidden=!e}),r&&(r.hidden=!!e,n&&(r.textContent=n))}function be(){var e="请先选择家谱,再查看或维护祭祀活动。";o("[data-ceremony-list]",e,"empty"),o("[data-ceremony-detail]","当前没有家谱上下文。","empty"),o("[data-ceremony-gift-list]","当前没有家谱上下文。","empty"),o("[data-ceremony-invitation-list]","当前没有家谱上下文。","empty"),o("[data-ceremony-invitee-options]","当前没有家谱上下文。","empty"),z(!1,e),o("[data-ceremony-form-status]",e,"forbidden")}function U(){var e=ee();return e||be(),e}function se(e){return!!(e&&(e.canEditContent===!0||e.canManage===!0))}async function J(e,n){var r=await e.genealogyDetail(n);return v=se(r),S("[data-ceremony-create-link], [data-ceremony-editor-action], [data-ceremony-admin-editor]").forEach(function(t){t.hidden=!v}),r}function Ne(e){var n=c("[data-ceremony-list]"),r=B(e,L);return V=Object.create(null),r.forEach(function(t){V[t.ceremonyId]=t}),n?!Array.isArray(e)||e.length&&!r.length?(o("[data-ceremony-list]","活动响应缺少稳定 CeremonyVo 字段,请联系后端核对。","error"),[]):r.length?(n.innerHTML=r.map(Ae).join(""),r):(o("[data-ceremony-list]","暂无祭祀活动","empty"),[]):r}async function ce(){var e=N(),n;if(!g(e)&&(n=U(),!!n)){_(),o("[data-ceremony-list]","正在加载祭祀活动…","loading");try{await J(e,n),Ne(await e.ceremonies(n))}catch(r){if(g(e,r))return;o("[data-ceremony-list]",s(r)?"当前账号无权查看该家谱的祭祀活动。":"活动加载失败,请稍后重试。",p(r)),D(s(r)?"当前账号无权查看该家谱的祭祀活动。":r.message||"活动加载失败")}}}function me(e){var n={};return S("[name]",e).forEach(function(r){n[r.name]=r.value}),n}function K(e,n,r){var t=c(e+' [name="'+n+'"]');t&&(t.value=r==null?"":String(r))}function Ue(e){var n,r;if(!e||e.status!=="0")throw new Error("停用活动无法通过当前 PC 接口重新读取或编辑");["ceremonyType","ceremonyTitle","ceremonyDesc","location","locationAddress","longitude","latitude","coverOssId","sortOrder"].forEach(function(t){K("[data-ceremony-form]",t,e[t])}),K("[data-ceremony-form]","ceremonyTime",Ie(e.ceremonyTime)),K("[data-ceremony-form]","status","0"),n=c('[data-ceremony-form] [name="ceremonyDesc"]'),n&&d.AppRichEditor&&d.AppRichEditor.init&&(r=d.AppRichEditor.init(n),r&&r.editor&&r.editor.setHtml&&(r.editor.setHtml(e.ceremonyDesc||""),r.sync()))}async function we(){var e=N(),n,r,t;if(!g(e)&&(n=U(),!!n)){r=E(),_(),z(!1,"正在加载活动编辑信息…"),o("[data-ceremony-form-status]","正在读取家谱权限…","loading");try{if(await J(e,n),!v)throw{status:403,message:"当前账号无权维护该家谱的祭祀活动。"};if(r){if(t=L(await e.ceremonyDetail(n,r)),!t||t.ceremonyId!==r)throw new Error("活动详情响应缺少稳定 ceremonyId");Ue(t),c("[data-ceremony-editor-title]")&&(c("[data-ceremony-editor-title]").textContent="编辑祭祀活动")}z(!0),o("[data-ceremony-form-status]","")}catch(i){if(g(e,i))return;z(!1,s(i)?"当前账号无权维护该家谱的祭祀活动。":i.message||"活动编辑信息加载失败"),o("[data-ceremony-form-status]",s(i)?"当前账号无权维护该家谱的祭祀活动。":i.message||"活动编辑信息加载失败",p(i))}}}async function Q(e,n,r){var t,i,a=c("[data-ceremony-gift-list]");return o("[data-ceremony-gift-list]","正在加载祭品…","loading"),t=await e.ceremonyGifts(n,r),i=B(t,j),q=Object.create(null),i.forEach(function(f){q[f.giftId]=f}),a?!Array.isArray(t)||t.length&&!i.length?(o("[data-ceremony-gift-list]","祭品响应缺少稳定 CeremonyGiftVo 字段,请联系后端核对。","error"),[]):i.length?(a.innerHTML=le(i),i):(o("[data-ceremony-gift-list]","暂无祭品记录","empty"),[]):i}function Ee(e,n){var r=c("[data-ceremony-invitee-options]"),t=Object.create(null);if(n.forEach(function(i){i.inviteStatus!=="CANCELED"&&(t[i.inviteeUserId]=!0)}),!!r){if(!e.length){o("[data-ceremony-invitee-options]","暂无已绑定账号的正常成员","empty");return}r.innerHTML=e.map(function(i){var a=i.memberName||i.appUserNickName||"家谱成员";return'"}).join("")}}async function ye(e,n,r){var t,i,a,f=c("[data-ceremony-invitation-list]");return o("[data-ceremony-invitation-list]","正在加载邀请记录…","loading"),o("[data-ceremony-invitee-options]","正在加载成员选项…","loading"),t=await Promise.all([e.genealogyMemberOptions(n),e.ceremonyInvitations(n,r)]),i=B(t[0],ae),a=B(t[1],$),H=Object.create(null),i.forEach(function(l){H[l.appUserId]=l}),!Array.isArray(t[0])||t[0].length&&!i.length?o("[data-ceremony-invitee-options]","成员选项响应字段不完整","error"):Ee(i,a),f?(!Array.isArray(t[1])||t[1].length&&!a.length?o("[data-ceremony-invitation-list]","邀请响应字段不完整","error"):a.length?f.innerHTML=Te(t[1]):o("[data-ceremony-invitation-list]","暂无邀请记录","empty"),{options:i,invitations:a}):{options:i,invitations:a}}async function Oe(){var e=N(),n,r;if(!g(e)){if(n=U(),r=E(),!n||!r){n&&(o("[data-ceremony-detail]","缺少有效活动编号。","error"),o("[data-ceremony-gift-list]","缺少有效活动编号。","empty"),o("[data-ceremony-invitation-list]","缺少有效活动编号。","empty"),o("[data-ceremony-invitee-options]","缺少有效活动编号。","empty"));return}_(),o("[data-ceremony-detail]","正在加载活动详情…","loading"),o("[data-ceremony-gift-list]","正在加载祭品…","loading");try{if(await J(e,n),k=L(await e.ceremonyDetail(n,r)),!k||k.ceremonyId!==r)throw new Error("活动详情响应缺少稳定 ceremonyId");c("[data-ceremony-detail]")&&(c("[data-ceremony-detail]").innerHTML=ue(k)),c("[data-ceremony-detail-title]")&&(c("[data-ceremony-detail-title]").textContent=k.ceremonyTitle),c("[data-ceremony-editor-action]")&&(c("[data-ceremony-editor-action]").href=W(n,r))}catch(t){if(g(e,t))return;o("[data-ceremony-detail]",s(t)?"当前账号无权查看该活动。":t.message||"活动详情加载失败",p(t)),o("[data-ceremony-gift-list]",s(t)?"当前账号无权查看该活动。":"祭品加载失败,请稍后重试。",p(t)),o("[data-ceremony-invitation-list]",s(t)?"当前账号无权查看该活动。":"邀请记录加载失败,请稍后重试。",p(t)),o("[data-ceremony-invitee-options]",s(t)?"当前账号无权查看该活动。":"成员选项加载失败,请稍后重试。",p(t)),D(s(t)?"当前账号无权查看该活动。":t.message||"活动详情加载失败");return}try{await Q(e,n,r)}catch(t){if(g(e,t))return;o("[data-ceremony-gift-list]",s(t)?"当前账号无权查看该活动祭品。":t.message||"祭品加载失败",p(t)),D(s(t)?"当前账号无权查看该活动祭品。":t.message||"祭品加载失败")}if(!v){o("[data-ceremony-invitation-list]","当前账号无权查看或维护受邀名单。","forbidden"),o("[data-ceremony-invitee-options]","当前账号无权查看或维护受邀名单。","forbidden");return}try{await ye(e,n,r)}catch(t){if(g(e,t))return;o("[data-ceremony-invitation-list]",s(t)?"当前账号无权查看或维护受邀名单。":t.message||"邀请记录加载失败",p(t)),o("[data-ceremony-invitee-options]",s(t)?"当前账号无权查看或维护受邀名单。":"成员选项加载失败,请稍后重试。",p(t)),D(s(t)?"当前账号无权查看或维护受邀名单。":t.message||"邀请记录加载失败")}}}async function De(e){var n=N(),r=U(),t=E(),i,a,f,l,I;if(!(w||!v||!r||g(n))){if(d.AppRichEditor&&d.AppRichEditor.syncAll&&d.AppRichEditor.syncAll(),i=ne(me(e)),a=re(i),a){o("[data-ceremony-form-status]",a,"error");return}delete i.invalidLongitude,delete i.invalidLatitude,delete i.invalidCoverOssId,delete i.invalidSortOrder,C(!0),o("[data-ceremony-form-status]","正在保存活动…","loading");try{if(f=L(t?await n.updateCeremony(r,t,i):await n.createCeremony(r,i)),l=t||f&&f.ceremonyId,!l)throw new Error("保存响应缺少 ceremonyId");if(I=L(await n.ceremonyDetail(r,l)),!I||I.ceremonyId!==l)throw new Error("保存后未能重读同一活动");d.NavigationUtil.open(de(r,l))}catch(h){if(g(n,h))return;o("[data-ceremony-form-status]",s(h)?"当前账号无权保存该活动。":h.message||"活动保存失败",p(h))}finally{C(!1)}}}async function ke(e){var n=N(),r=U(),t=E(),i,a,f;if(!(w||!v||!r||!t||g(n))){if(i=te(me(e)),a=ie(i),a){o("[data-ceremony-gift-status]",a,"error");return}delete i.invalidGiftAmount,C(!0),o("[data-ceremony-gift-status]","正在添加祭品…","loading");try{if(f=j(await n.createCeremonyGift(r,t,i)),!f||f.ceremonyId!==t)throw new Error("添加祭品响应缺少稳定 giftId");if(!(await Q(n,r,t)).some(function(l){return l.giftId===f.giftId}))throw new Error("添加后祭品列表未返回同一记录");e.reset(),o("[data-ceremony-gift-status]","祭品已添加")}catch(l){if(g(n,l))return;o("[data-ceremony-gift-status]",s(l)?"当前账号无权添加祭品。":l.message||"祭品添加失败",p(l))}finally{C(!1)}}}async function Le(){var e=N(),n=U(),r=E(),t,i;if(!(w||!v||!n||!r||g(e))){if(t=S("[data-invitee-user-id]"),!t.length){o("[data-ceremony-invitee-status]","当前没有可邀请的家谱成员","empty");return}i=oe(t.filter(function(a){return a.checked}).map(function(a){return a.getAttribute("data-invitee-user-id")})),C(!0),o("[data-ceremony-invitee-status]","正在更新受邀人…","loading");try{await e.replaceCeremonyInvitees(n,r,i.inviteeUserIds),await ye(e,n,r),o("[data-ceremony-invitee-status]","受邀名单已更新")}catch(a){if(g(e,a))return;o("[data-ceremony-invitee-status]",s(a)?"当前账号无权维护受邀名单。":a.message||"受邀名单更新失败",p(a))}finally{C(!1)}}}async function Pe(e){var n=N(),r=U(),t=V[e]||k;if(!(w||!v||!r||!y(e)||g(n))&&await G("确认删除“"+(t?t.ceremonyTitle:"该活动")+"”吗?删除后不可恢复。")){C(!0);try{await n.deleteCeremony(r,e),c("[data-ceremony-list-page]")?await ce():d.NavigationUtil.open(Ce(r))}catch(i){if(g(n,i))return;D(s(i)?"当前账号无权删除该活动。":i.message||"活动删除失败")}finally{C(!1)}}}async function Ge(e){var n=N(),r=U(),t=E(),i=q[e];if(!(w||!v||!r||!t||!y(e)||g(n))&&await G("确认删除“"+(i&&(i.giverName||i.giverNickName)||"该祭品")+"”的记录吗?")){C(!0);try{await n.deleteCeremonyGift(r,t,e),await Q(n,r,t),D("祭品记录已删除")}catch(a){if(g(n,a))return;D(s(a)?"当前账号无权删除该祭品。":a.message||"祭品删除失败")}finally{C(!1)}}}function Se(){b&&(b.addEventListener("click",function(e){var n=e.target.closest("[data-ceremony-delete-id]"),r=e.target.closest("[data-ceremony-gift-delete-id]");n?(e.preventDefault(),Pe(n.getAttribute("data-ceremony-delete-id"))):r&&(e.preventDefault(),Ge(r.getAttribute("data-ceremony-gift-delete-id")))}),b.addEventListener("submit",function(e){var n=e.target.closest("[data-ceremony-form]"),r=e.target.closest("[data-ceremony-gift-form]"),t=e.target.closest("[data-ceremony-invitee-form]");n?(e.preventDefault(),De(n)):r?(e.preventDefault(),ke(r)):t&&(e.preventDefault(),Le())}))}function Be(){b&&(Se(),c("[data-ceremony-list-page]")&&ce(),c("[data-ceremony-edit-page]")&&we(),c("[data-ceremony-detail-page]")&&Oe())}return{getCurrentGenealogyId:ee,getCurrentCeremonyId:E,buildCeremonyBody:ne,validateCeremonyBody:re,buildCeremonyGiftBody:te,validateCeremonyGiftBody:ie,normalizeCeremony:L,normalizeCeremonyGift:j,normalizeInvitation:$,normalizeInviteeOption:ae,buildInviteesBody:oe,canEditCeremonies:se,renderCeremonyDetail:ue,renderGiftList:le,shouldRedirectToLogin:fe,isForbidden:s,init:Be}}); diff --git a/dist/public/js/ceremony-pages.js b/dist/public/js/ceremony-pages.js deleted file mode 100644 index 89bebd4..0000000 --- a/dist/public/js/ceremony-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(r,l){if(typeof module=="object"&&module.exports){module.exports=l(r);return}r.CeremonyPages=l(r),r.document&&r.document.addEventListener("DOMContentLoaded",function(){r.CeremonyPages.init()})})(typeof globalThis!="undefined"?globalThis:window,function(r){"use strict";var l=r.document,m=Object.create(null),g="",N=!1,x=["PENDING","ACCEPTED","DECLINED","CANCELED"],G=["ACCEPTED","DECLINED"],L={PENDING:"待响应",ACCEPTED:"已接受",DECLINED:"已拒绝",CANCELED:"已取消"};function h(){return r.GenealogyApi&&r.GenealogyApi.defaultClient}function y(n,e){return l?(e||l).querySelector(n):null}function P(n,e){var t=e&&(e.status||e.code);return!n||!n.getToken||!n.getToken()||Number(t)===401}function f(n){return Number(n&&(n.status||n.code))===403}function I(n,e){return P(n,e)?(n&&n.clearToken&&n.clearToken(),r.NavigationUtil.open("login.html"),!0):!1}function p(n){return n==null||n===""||typeof n=="number"&&!Number.isSafeInteger(n)?"":/^[1-9][0-9]*$/.test(String(n))?String(n):""}function s(n){return n==null?"":String(n)}function V(n){var e;if(!(n==null||n===""))return e=Number(n),Number.isSafeInteger(e)?e:void 0}function R(n){var e;if(!(n==null||n===""))return e=Number(n),Number.isFinite(e)?e:void 0}function w(n){var e=p(n&&n.invitationId),t=p(n&&n.genealogyId),i=p(n&&n.ceremonyId),a=p(n&&n.inviteeUserId),c=s(n&&n.inviteStatus),u,C,A,S;return!e||!t||!i||x.indexOf(c)<0?null:(u={invitationId:e,genealogyId:t,ceremonyId:i,inviteStatus:c,deliveredTime:s(n.deliveredTime),readTime:s(n.readTime),responseTime:s(n.responseTime),ceremonyTitle:s(n.ceremonyTitle),ceremonyTime:s(n.ceremonyTime),location:s(n.location),locationAddress:s(n.locationAddress)},a&&(u.inviteeUserId=a),C=V(n.inviteVersion),A=R(n.longitude),S=R(n.latitude),C!==void 0&&(u.inviteVersion=C),A!==void 0&&(u.longitude=A),S!==void 0&&(u.latitude=S),u)}function U(n){return Array.isArray(n)?n:[]}function k(n){return{inviteStatus:s(n&&n.inviteStatus)}}function B(n){return!n||G.indexOf(n.inviteStatus)<0?"邀请只能选择接受或拒绝":""}function b(n){return!!(n&&n.inviteStatus==="PENDING")}function o(n){return s(n).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function T(n){return n.ceremonyTitle||"未命名贺礼活动"}function z(n){var e='';return b(n)&&(e+=''),'

'+o(T(n))+"

"+o(L[n.inviteStatus])+(n.ceremonyTime?" · "+o(n.ceremonyTime):"")+(n.location?" · "+o(n.location):"")+'

'+e+"
"}function v(n,e){return"

"+o(n)+""+o(e||"未提供")+"

"}function O(n){var e="";return n?(n.longitude!==void 0&&n.latitude!==void 0&&(e=' 打开地图'),'
'+v("活动标题",T(n))+v("邀请状态",L[n.inviteStatus])+v("活动时间",n.ceremonyTime)+v("地点",n.location)+"

详细地址"+o(n.locationAddress||"未提供")+e+"

"+v("投递时间",n.deliveredTime)+v("阅读时间",n.readTime)+v("响应时间",n.responseTime)+"
"):""}function D(n){var e=y("[data-my-invitation-detail]");if(g=n?n.invitationId:"",!!e){if(!n){d("[data-my-invitation-detail]","empty","请选择一条邀请查看详情");return}e.innerHTML=O(n)}}function j(n){var e=y("[data-my-invitation-list]"),t=U(n).map(w),i=t.filter(function(c){return!c}).length,a=t.filter(Boolean);return m=Object.create(null),a.forEach(function(c){m[c.invitationId]=c}),e?i?(d("[data-my-invitation-list]","error","邀请响应缺少稳定 ID 或合法状态,请联系后端核对 DTO。"),d("[data-my-invitation-detail]","error","邀请详情暂不可用,请联系后端核对 DTO。"),[]):a.length?(e.innerHTML=a.map(z).join(""),D(m[g]||a[0]),a):(d("[data-my-invitation-list]","empty","暂无贺礼邀请"),D(null),[]):a}function M(n){var e;N=n,l&&(e=l.querySelectorAll("[data-invitation-response]"),Array.prototype.forEach.call(e,function(t){t.disabled=n}))}function d(n,e,t){var i=y(n);if(i){if(!t){i.innerHTML="";return}if(e&&r.ProfileUI&&r.ProfileUI.setApiState){r.ProfileUI.setApiState(i,e,t);return}i.textContent=t}}async function E(){var n=h();if(!I(n)){d("[data-my-invitation-list]","loading","正在加载我的邀请…"),d("[data-my-invitation-detail]","loading","正在加载邀请详情…");try{j(await n.myCeremonyInvitations())}catch(e){if(I(n,e))return;d("[data-my-invitation-list]",f(e)?"forbidden":"error",f(e)?"当前账号无权查看邀请。":e.message||"邀请加载失败"),d("[data-my-invitation-detail]",f(e)?"forbidden":"error",f(e)?"当前账号无权查看邀请。":"邀请详情加载失败,请稍后重试。")}}}async function q(n,e){var t=h(),i=m[p(n)],a=k({inviteStatus:e}),c=B(a);if(!(N||I(t)||!i||!b(i))&&!c){M(!0),d("[data-my-invitation-status]","loading","正在更新邀请状态…");try{await t.respondCeremonyInvitation(i.genealogyId,i.ceremonyId,a),g=i.invitationId,await E(),d("[data-my-invitation-status]","","邀请状态已更新。")}catch(u){if(I(t,u))return;d("[data-my-invitation-status]",f(u)?"forbidden":"error",f(u)?"当前账号无权响应该邀请。":u.message||"邀请响应失败")}finally{M(!1)}}}function H(){l&&l.addEventListener("click",function(n){var e=n.target.closest("[data-invitation-detail]"),t=n.target.closest("[data-invitation-response]");if(e){n.preventDefault(),D(m[e.getAttribute("data-invitation-detail")]||null);return}t&&(n.preventDefault(),q(t.getAttribute("data-invitation-id"),t.getAttribute("data-invitation-response")))})}function F(){!l||!y("[data-ceremony-page]")||(H(),E())}return{normalizeInvitation:w,normalizeList:U,buildInvitationResponseBody:k,validateInvitationResponseBody:B,canRespondToInvitation:b,renderInvitationRow:z,renderInvitationDetail:O,shouldRedirectToLogin:P,isForbidden:f,loadMyInvitations:E,init:F}}); diff --git a/dist/public/js/earning-pages.js b/dist/public/js/earning-pages.js deleted file mode 100644 index 69c2641..0000000 --- a/dist/public/js/earning-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(t,c){if(typeof module=="object"&&module.exports){module.exports=c(t);return}t.EarningPages=c(t),t.document&&t.document.addEventListener("DOMContentLoaded",t.EarningPages.init)})(typeof globalThis!="undefined"?globalThis:window,function(t){"use strict";function c(r){return t.ProfileUI&&typeof t.ProfileUI.confirmAction=="function"?t.ProfileUI.confirmAction(r):Promise.resolve(!t.confirm||t.confirm(r))}var D={PENDING:"待审核",APPROVED:"已通过",PAYING:"打款中",PAID:"已到账",REJECTED:"已驳回",FAILED:"打款失败",CANCELLED:"已取消"},N={PAYMENT_REWARD:"推广奖励",PAYMENT_REFUND_REVERSAL:"退款冲销",WITHDRAWAL_FREEZE:"提现冻结",WITHDRAWAL_CANCEL:"取消提现",WITHDRAWAL_REJECT:"提现驳回",WITHDRAWAL_PAID:"提现到账",WITHDRAWAL_FAILED:"提现失败"};function i(r){return String(r==null?"":r).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function f(r){var e=String(r==null?"":r).trim();return/^(?:0|[1-9][0-9]*)(?:\.[0-9]{1,2})?$/.test(e)?e:""}function g(){return t.crypto&&typeof t.crypto.randomUUID=="function"?t.crypto.randomUUID():"pc-"+Date.now()+"-"+Math.random().toString(16).slice(2)}function p(r){var e=f(r),a;return e?(a=e.split("."),BigInt(a[0])*BigInt(100)+BigInt((a[1]||"").padEnd(2,"0"))):null}function y(r,e){var a=r||{};return{requestId:String(e||"").trim(),amount:f(a.amount),payoutQrOssId:String(a.payoutQrOssId||"").trim(),payoutAccountName:String(a.payoutAccountName||"").trim()}}function v(r,e){return r.requestId?!r.amount||Number(r.amount)<=0?"请输入有效提现金额,最多两位小数":p(e)!==null&&p(r.amount)>p(e)?"提现金额不能超过可用余额":/^[1-9][0-9]*$/.test(r.payoutQrOssId)?r.payoutAccountName?r.payoutAccountName.length>64?"收款人姓名不能超过 64 个字符":"":"请填写收款人姓名":"请上传收款码":"提现请求号生成失败,请刷新页面重试"}function A(r){var e=r||{};return'

可用收益

¥'+i(e.availableAmount||"0.00")+'

冻结金额

¥'+i(e.frozenAmount||"0.00")+'

最低提现

'+i(e.minimumWithdrawal?"¥"+e.minimumWithdrawal:"以提交校验为准")+"
"}function h(r){var e=r&&Array.isArray(r.rows)?r.rows:[];return e.length?e.map(function(a){return'

'+i(N[a.entryType]||a.entryType||"收益变动")+"

"+i([a.createTime,a.remark].filter(Boolean).join(" · "))+"

"+i(String(a.availableDelta||"0.00"))+"
"}).join(""):'
当前没有收益流水
'}function E(r){var e=r&&Array.isArray(r.rows)?r.rows:[];return e.length?e.map(function(a){var m=String(a.withdrawalId||"").trim();return'

提现 ¥'+i(a.amount||"0.00")+"

"+i([a.withdrawalNo,a.payoutAccountName,a.createTime,a.auditRemark,a.failureReason].filter(Boolean).join(" · "))+'

'+i(D[a.withdrawalStatus]||a.withdrawalStatus||"未知")+""+(a.withdrawalStatus==="PENDING"&&/^[1-9][0-9]*$/.test(m)?'':"")+"
"}).join(""):'
当前没有提现记录
'}async function L(){var r=t.document&&t.document.querySelector("[data-earning-page]"),e=t.GenealogyApi&&t.GenealogyApi.defaultClient,a=r&&r.querySelector("[data-earning-summary]"),m=r&&r.querySelector("[data-earning-ledger]"),I=r&&r.querySelector("[data-earning-withdrawals]"),o=r&&r.querySelector("[data-withdrawal-form]"),S=r&&r.querySelector("[data-earning-status]"),T="",W=g(),d=!1;if(!r)return;if(!e||!e.getToken||!e.getToken()){t.NavigationUtil.open("login.html");return}function u(n,l){n?t.ProfileUI.setApiState(S,n,l):S.textContent=l||""}async function w(){var n=await Promise.all([e.earningSummary(),e.earningLedger({pageNum:1,pageSize:20}),e.earningWithdrawals({pageNum:1,pageSize:20})]);return T=String(n[0]&&n[0].availableAmount||""),a.innerHTML=A(n[0]),m.innerHTML=h(n[1]),I.innerHTML=E(n[2]),o.querySelector("[data-withdrawal-submit]").disabled=n[0]&&n[0].withdrawalEnabled===!1,n}try{await w(),u("","收益数据已加载")}catch(n){u("error",n.message||"收益数据读取失败");return}o.addEventListener("submit",async function(n){var l,s;if(n.preventDefault(),!d){if(l=y({amount:o.elements.amount.value,payoutAccountName:o.elements.payoutAccountName.value,payoutQrOssId:o.elements.payoutQrOssId.value},W),s=v(l,T),s)return u("error",s);d=!0,u("loading","正在提交提现申请…");try{await e.createEarningWithdrawal(l),W=g(),o.reset(),o.elements.payoutQrOssId.value="",await w(),u("","提现申请已提交")}catch(R){u("error",R.message||"提现申请失败")}finally{d=!1}}}),I.addEventListener("click",async function(n){var l=n.target.closest("[data-withdrawal-cancel]");if(!(!l||d||!await c("确认取消这笔待审核提现吗?"))){d=!0,u("loading","正在取消提现…");try{await e.cancelEarningWithdrawal(l.getAttribute("data-withdrawal-cancel")),await w(),u("","提现已取消")}catch(s){u("error",s.message||"取消提现失败")}finally{d=!1}}})}return{normalizeMoney:f,createRequestId:g,buildWithdrawalBody:y,validateWithdrawalBody:v,renderSummary:A,renderLedger:h,renderWithdrawals:E,init:L}}); diff --git a/dist/public/js/family-home-pages.js b/dist/public/js/family-home-pages.js deleted file mode 100644 index 679151f..0000000 --- a/dist/public/js/family-home-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(a,m){if(typeof module=="object"&&module.exports){module.exports=m(a);return}a.FamilyHomePages=m(a),a.document&&a.document.addEventListener("DOMContentLoaded",function(){a.FamilyHomePages.init()})})(typeof globalThis!="undefined"?globalThis:window,function(a){"use strict";var m=a.MediaDisplay||(typeof require=="function"?require("./media-display.js"):null);function c(e){return String(e==null?"":e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function v(e){var n;return typeof e=="number"&&!Number.isSafeInteger(e)||e==null?"":(n=String(e).trim(),/^[1-9][0-9]*$/.test(n)?n:"")}function g(e){return e==null?"":String(e).trim()}function P(e){var n=Number(e);return Number.isSafeInteger(n)&&n>=0?n:null}function h(e,n){var r=e||{},i=v(r.genealogyId),o=v(n),t=P(r.memberCount),b=P(r.personCount),w=m&&m.normalizeFileAccess(r.coverFile),I;return!i||i!==o||!g(r.genealogyName)||String(r.status)!=="0"||t===null||b===null||(I={genealogyId:i,genealogyNo:g(r.genealogyNo),genealogyName:g(r.genealogyName),surname:g(r.surname),ancestralHall:g(r.ancestralHall),originPlace:g(r.originPlace),regionFullName:g(r.regionFullName),memberCount:t,personCount:b,canManage:r.canManage===!0,canEditContent:r.canEditContent===!0},r.coverFile!==void 0&&r.coverFile!==null&&!w)?null:(w&&(I.coverFile=w),I)}function F(e){var n=e||{},r=[n.genealogyNo?"家谱编号 "+n.genealogyNo:"",n.surname?"姓氏 "+n.surname:"",n.ancestralHall?"堂号 "+n.ancestralHall:"",n.originPlace?"祖籍 "+n.originPlace:"",n.regionFullName?"地区 "+n.regionFullName:""].filter(Boolean).join(" · ");return(m?m.renderImage(n.coverFile,{alt:n.genealogyName+"封面",className:"family-cover-media"}):"")+"

"+c(n.genealogyName)+"

"+c(r)+"

成员 "+c(n.memberCount)+" 人 · 世系人物 "+c(n.personCount)+" 人

"}function C(e){return!!e&&e.canManage===!0}function s(e,n){var r=n&&(n.status||n.code);return!e||!e.getToken||!e.getToken()||Number(r)===401}function l(e){return Number(e&&(e.status||e.code))===403}async function H(e,n){var r=v(n),i,o;if(!r||!e||!e.genealogyOverview||!e.lineageTree)throw new Error("家谱主页接口初始化失败");if(i=await Promise.all([e.genealogyOverview(r),e.lineageTree(r)]),o=h(i[0],r),!o)throw new Error("家谱概览响应无效");return{overview:o,tree:i[1]}}function p(e){e&&e.clearToken&&e.clearToken(),a.NavigationUtil.open("login.html")}function d(e){return a.document&&a.document.querySelector(e)}function S(e){return a.document?Array.prototype.slice.call(a.document.querySelectorAll(e)):[]}function y(e,n){var r=d(e);r&&(r.textContent=n)}function u(e,n,r){var i=d(e);if(i){if(a.ProfileUI&&a.ProfileUI.setApiState){a.ProfileUI.setApiState(i,n,r);return}i.textContent=r}}function f(e,n){var r=d("[data-family-home-status]");if(r){if(r.hidden=!e,!e){r.innerHTML="";return}if(n&&a.ProfileUI&&a.ProfileUI.setApiState){a.ProfileUI.setApiState(r,n,e);return}r.textContent=e}}async function N(){var e=d("[data-family-home-page]"),n=a.GenealogyApi&&a.GenealogyApi.defaultClient,r,i,o;if(e){if(s(n)){p(n);return}if(r=a.ProfileUI&&a.ProfileUI.getGenealogyId(),!r){a.NavigationUtil.open("profile-families.html?next=profile-family-home.html");return}if(a.ProfileUI&&a.ProfileUI.syncGenealogyContextLinks&&a.ProfileUI.syncGenealogyContextLinks(),!a.LineagePages||!a.LineagePages.renderLineageTreeHtml){u("[data-family-home-overview]","error","家谱主页初始化失败,请刷新后重试。"),u("[data-lineage-home-tree]","error","世系预览初始化失败,请刷新后重试。"),f("世系预览初始化失败,请刷新后重试。","error");return}u("[data-family-home-overview]","loading","正在加载家谱概览…"),u("[data-lineage-home-tree]","loading","正在加载世系树…"),f("正在读取家谱概览…","loading");try{if(i=h(await n.genealogyOverview(r),r),!i)throw new Error("家谱概览响应无效");a.ProfileUI&&a.ProfileUI.setCurrentGenealogyContext&&a.ProfileUI.setCurrentGenealogyContext(i.genealogyId,i.genealogyName),y("[data-family-home-title]",i.genealogyName),y("[data-family-home-context]",i.genealogyName),d("[data-family-home-overview]").innerHTML=F(i),y("[data-family-home-member-count]",String(i.memberCount)),y("[data-family-home-person-count]",String(i.personCount)),S("[data-family-home-management]").forEach(function(t){t.hidden=!C(i)})}catch(t){if(s(n,t)){p(n);return}u("[data-family-home-overview]",l(t)?"forbidden":"error",l(t)?"当前账号无权查看该家谱。":"家谱概览读取失败,请稍后重试。"),u("[data-lineage-home-tree]",l(t)?"forbidden":"error",l(t)?"当前账号无权查看该家谱。":"世系树读取失败,请稍后重试。"),f(l(t)?"当前账号无权查看该家谱。":t.message||"家谱主页读取失败,请稍后重试。",l(t)?"forbidden":"error");return}try{i=await n.lineageTree(r),o=Array.isArray(i)?a.LineagePages.renderLineageTreeHtml(i):"",o?d("[data-lineage-home-tree]").innerHTML=o:u("[data-lineage-home-tree]","empty","暂无世系树"),f("")}catch(t){if(s(n,t)){p(n);return}u("[data-lineage-home-tree]",l(t)?"forbidden":"error",l(t)?"当前账号无权查看该家谱世系树。":"世系树读取失败,请稍后重试。"),f(l(t)?"当前账号无权查看该家谱世系树。":t.message||"世系树读取失败,请稍后重试。",l(t)?"forbidden":"error")}}}function T(){return N()}return{normalizeFamilyOverview:h,renderFamilyOverview:F,shouldShowFamilyManagement:C,shouldRedirectToLogin:s,isForbidden:l,loadFamilyHome:H,initFamilyHomePage:N,init:T}}); diff --git a/dist/public/js/family-invite-pages.js b/dist/public/js/family-invite-pages.js deleted file mode 100644 index 63073a5..0000000 --- a/dist/public/js/family-invite-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(n,g){if(typeof module=="object"&&module.exports){module.exports=g(n);return}n.FamilyInvitePages=g(n),n.document&&n.document.addEventListener("DOMContentLoaded",n.FamilyInvitePages.init)})(typeof globalThis!="undefined"?globalThis:window,function(n){"use strict";var g={ACTIVE:"待使用",REDEEMED:"已使用",REVOKED:"已撤销",EXPIRED:"已过期"};function u(e){return String(e==null?"":e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function I(e){var t=String(e==null?"":e).trim();return/^[1-9][0-9]*$/.test(t)?t:""}function y(e){var t=e||{},a=I(t.inviteId),c=I(t.genealogyId);return!a||!c||!String(t.genealogyName||"").trim()?null:{inviteId:a,genealogyId:c,genealogyName:String(t.genealogyName).trim(),lineagePersonName:String(t.lineagePersonName||"").trim(),status:String(t.status||"").toUpperCase(),token:String(t.token||"").trim(),expiresAt:String(t.expiresAt||"").trim(),createTime:String(t.createTime||"").trim()}}function A(e){var t;return e?n.location?(t=String(n.location.href||"").split("?")[0].split("#")[0],t+"?token="+encodeURIComponent(e)):"profile-invite.html?token="+encodeURIComponent(e):""}function S(e){var t=Array.isArray(e)?e.map(y).filter(Boolean):[];return t.length?t.map(function(a){var c=a.status==="ACTIVE";return'

'+u(a.genealogyName)+(a.lineagePersonName?" · "+u(a.lineagePersonName):"")+"

"+u([a.createTime,a.expiresAt?"有效期至 "+a.expiresAt:""].filter(Boolean).join(" · "))+'

'+u(g[a.status]||a.status||"未知")+""+(c?'':"")+"
"}).join(""):'
还没有发出的邀请
'}function P(e){e&&e.clearToken&&e.clearToken(),n.NavigationUtil.open("login.html")}async function b(){var e=n.document&&n.document.querySelector("[data-family-invite-page]"),t=n.GenealogyApi&&n.GenealogyApi.defaultClient,a=n.ProfileUI&&n.ProfileUI.getGenealogyId?n.ProfileUI.getGenealogyId():"",c=e&&e.querySelector("[data-invite-form]"),k=e&&e.querySelector("[data-invite-person]"),h=e&&e.querySelector("[data-invite-status]"),v=e&&e.querySelector("[data-invite-result]"),f=e&&e.querySelector("[data-invite-list]"),d=e&&e.querySelector("[data-invite-redeem]"),m=n.location?new URLSearchParams(n.location.search).get("token"):"",l=!1;if(!e)return;if(!t||!t.getToken||!t.getToken())return P(t);if(m&&d){c.hidden=!0,f.hidden=!0,d.hidden=!1,d.querySelector("[data-invite-token]").textContent=m;try{var p=y(await t.previewGenealogyInvitation(m));if(!p)throw new Error("邀请预览响应无效");d.querySelector("h3").textContent="接受“"+p.genealogyName+"”邀请",i("",p.expiresAt?"邀请有效期至 "+p.expiresAt:"请确认是否加入该家谱")}catch(r){i("error",r.message||"邀请预览失败"),d.querySelector("[data-invite-redeem-submit]").disabled=!0;return}d.querySelector("[data-invite-redeem-submit]").addEventListener("click",async function(){if(!l){l=!0,i("loading","正在接受邀请…");try{await t.redeemGenealogyInvitation({token:m}),i("","邀请已接受,你已加入该家谱。")}catch(r){i("error",r.message||"接受邀请失败")}finally{l=!1}}});return}if(!a){n.ProfileUI.setApiState(h,"error","缺少家谱上下文,请从“我的家谱”重新进入。");return}function i(r,o){r?n.ProfileUI.setApiState(h,r,o):h.textContent=o||""}async function E(){var r=await t.myGenealogyInvitations();f.innerHTML=S(r)}try{var T=await Promise.all([t.lineagePersonOptions(a),t.myGenealogyInvitations()]);k.innerHTML=''+(Array.isArray(T[0])?T[0]:[]).map(function(r){var o=I(r.personId||r.lineagePersonId),s=String(r.displayName||r.personName||r.name||"").trim();return o&&s?'":""}).join(""),f.innerHTML=S(T[1]),i("","可生成一次性邀请链接")}catch(r){i("error",r.message||"邀请服务读取失败")}c.addEventListener("submit",async function(r){var o={},s,w;if(r.preventDefault(),!l){k.value&&(o.lineagePersonId=k.value),l=!0,i("loading","正在生成邀请…");try{if(s=y(await t.issueGenealogyInvitation(a,o)),!s||!s.token)throw new Error("邀请已创建,但响应缺少一次性令牌");w=A(s.token),v.hidden=!1,v.querySelector("[data-invite-link]").value=w,i("","邀请已生成,请及时复制给家人。"),await E()}catch(q){i("error",q.message||"邀请生成失败")}finally{l=!1}}}),v.querySelector("[data-invite-copy]").addEventListener("click",async function(){var r=v.querySelector("[data-invite-link]").value;try{await n.navigator.clipboard.writeText(r),i("","邀请链接已复制")}catch(o){v.querySelector("[data-invite-link]").select(),i("","请按 Ctrl+C 复制邀请链接")}}),f.addEventListener("click",async function(r){var o=r.target.closest("[data-invite-revoke]");if(!(!o||l)){l=!0,i("loading","正在撤销邀请…");try{await t.revokeGenealogyInvitation(o.getAttribute("data-invite-revoke")),await E(),i("","邀请已撤销")}catch(s){i("error",s.message||"撤销邀请失败")}finally{l=!1}}})}return{normalizeInvitation:y,invitationLink:A,renderInvitations:S,init:b}}); diff --git a/dist/public/js/feed-pages.js b/dist/public/js/feed-pages.js deleted file mode 100644 index bd7fa79..0000000 --- a/dist/public/js/feed-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(i,T){if(typeof module=="object"&&module.exports){module.exports=T(i);return}i.FeedPages=T(i),i.document&&i.document.addEventListener("DOMContentLoaded",function(){i.FeedPages.init()})})(typeof globalThis!="undefined"?globalThis:window,function(i){"use strict";function T(e){return i.ProfileUI&&typeof i.ProfileUI.confirmAction=="function"?i.ProfileUI.confirmAction(e):Promise.resolve(!i.confirm||i.confirm(e))}var E=i.MediaDisplay||(typeof require=="function"?require("./media-display.js"):null),F=i.document,H=20,I=1,U={},L={},M={},c={userId:"",canEditContent:!1,canManage:!1};function w(){return i.GenealogyApi&&i.GenealogyApi.defaultClient}function ee(e,t){var a=t&&(t.status||t.code);return!e||!e.getToken||!e.getToken()||Number(a)===401}function u(e,t){return ee(e,t)?(e&&e.clearToken&&e.clearToken(),i.NavigationUtil.open("login.html"),!0):!1}function o(e,t){return F?(t||F).querySelector(e):null}function y(e,t){return F?Array.prototype.slice.call((t||F).querySelectorAll(e)):[]}function m(e){if(i.layui&&i.layui.layer){i.layui.layer.msg(e);return}i.alert&&i.alert(e)}function s(e){return Number(e&&(e.status||e.code))===403?"forbidden":"error"}function C(e,t){var a=o("[data-feed-form-status]");if(a){if(!e){a.innerHTML="";return}if(i.ProfileUI&&i.ProfileUI.setApiState){i.ProfileUI.setApiState(a,t||"error",e);return}a.textContent=e}}function g(e,t,a){var n=o(e);if(n){if(i.ProfileUI&&i.ProfileUI.setApiState){i.ProfileUI.setApiState(n,t,a);return}n.innerHTML='
'+l(a)+"
"}}function q(e){var t=o("[data-feed-form]");t&&y("input, textarea, select, button",t).forEach(function(a){a.disabled=!e}),y("[data-feed-editor-action]").forEach(function(a){a.hidden=!e,a.disabled=!e})}function te(e){var t='[data-feed-form] [type="submit"], [type="submit"][form="feed-edit-form"]';y(t).forEach(function(a){a.dataset.defaultLabel||(a.dataset.defaultLabel=a.textContent),a.disabled=!!e,a.textContent=e?"正在保存…":a.dataset.defaultLabel,a.setAttribute("aria-busy",e?"true":"false")})}function v(e,t){var a=o("[data-feed-comment-form-status]");if(a){if(!e){a.innerHTML="";return}if(i.ProfileUI&&i.ProfileUI.setApiState){i.ProfileUI.setApiState(a,t||"error",e);return}a.textContent=e}}function O(e){var t=o("[data-feed-comment-form]");t&&y("input, textarea, button",t).forEach(function(a){a.disabled=!e})}function ae(e){y('[data-feed-comment-form] [type="submit"]').forEach(function(t){t.dataset.defaultLabel||(t.dataset.defaultLabel=t.textContent),t.textContent=e?"正在发布…":t.dataset.defaultLabel,t.setAttribute("aria-busy",e?"true":"false")})}function V(e){var t=o("[data-feed-comment-form]"),a=o("[data-feed-comment-form-target]"),n=e?p(e):"";t&&(o('[name="parentCommentId"]',t)&&(o('[name="parentCommentId"]',t).value=n),a&&(a.textContent=n?"正在回复这条评论":"发表公开评论"))}function ne(e,t){var a=Z(),n,r;if(!(!e||!a)){if(!o("[data-feed-detail-page]")){i.NavigationUtil.open(h("detail",a,e));return}N()===p(e)&&(n=o("[data-feed-comment-form]"),r=n&&o('[type="submit"]',n),!(!n||!r||r.disabled)&&(V(t),v(""),o('[name="commentContent"]',n)&&o('[name="commentContent"]',n).focus&&o('[name="commentContent"]',n).focus()))}}function Q(e,t){var a=new URLSearchParams(String(e||"").replace(/^\?/,""));return a.get(t)||""}function Z(e){var t=o("[data-feed-page], [data-feed-edit-page], [data-feed-detail-page]"),a=e===void 0&&i.location?i.location.search:e,n;return e===void 0&&i.ProfileUI&&i.ProfileUI.getGenealogyId&&(n=i.ProfileUI.getGenealogyId(),n)?n:Q(a,"genealogyId")||t&&t.getAttribute("data-genealogy-id")||""}function N(e){var t=e===void 0&&i.location?i.location.search:e;return Q(t,"feedId")}function P(e){var t=String(e==null?"":e).trim();return t||void 0}function ye(e){var t=P(e),a;if(t)return a=Number(t),Number.isSafeInteger(a)&&a>=0?a:void 0}function p(e){var t;return e==null||e===""?"":(t=String(e),/^\d+$/.test(t)?t:"")}function re(e,t,a){return a.forEach(function(n){t[n]!==void 0&&t[n]!==null&&(e[n]=t[n])}),e}function S(e){var t=Number(e);return(!Number.isSafeInteger(t)||t<1)&&(t=1),{pageNum:t,pageSize:H}}function h(e,t,a){var n=new URLSearchParams,r="profile-feed.html";return n.set("genealogyId",p(t)),(e==="detail"||e==="edit")&&(n.set("feedId",p(a)),r=e==="detail"?"profile-feed-detail.html":"profile-feed-edit.html"),r+"?"+n.toString()}function ie(e,t,a){return String(a||"0")==="1"?h("list",e):h("detail",e,t)}function D(e,t){var a;if(M[e])return M[e];try{a=Promise.resolve(t())}catch(n){a=Promise.reject(n)}return M[e]=a.finally(function(){delete M[e]}),M[e]}function de(e){var t=e||{},a=P(t.mediaOssIds),n=P(t.status),r={feedType:P(t.feedType)||"text",feedContent:String(t.feedContent||"").trim()},d=ye(t.sortOrder);if(n!==void 0&&n!=="0"&&n!=="1")throw new Error("动态状态只能是 0 或 1");if(P(t.sortOrder)!==void 0&&d===void 0)throw new Error("排序值必须是非负整数");return a!==void 0&&(r.mediaOssIds=a),n!==void 0&&(r.status=n),d!==void 0&&(r.sortOrder=d),r}function oe(e){var t=e||{},a={commentContent:String(t.commentContent||"").trim()},n=P(t.parentCommentId);return n!==void 0&&(a.parentCommentId=n),a}function le(e){return p(e&&e.feedId)}function Ce(e){return p(e&&e.commentId)}function x(e){var t=le(e),a=e&&e.feedContent,n;if(!t||a===void 0||a===null)return null;n=re({feedId:t,feedContent:String(a)},e,["genealogyNo","genealogyName","publisherNickName","publisherStatus","feedType","likedByMe","likeCount","commentCount","pinned","pinnedTime","sortOrder","status","remark","createTime","updateTime"]);var r=E?E.normalizeFileList(e&&e.mediaFiles):[];return r.length&&(n.mediaFiles=r),p(e.genealogyId)&&(n.genealogyId=p(e.genealogyId)),p(e.publisherUserId)&&(n.publisherUserId=p(e.publisherUserId)),n}function fe(e){var t=Ce(e),a=e&&e.commentContent,n;if(!t||a===void 0)return null;n=String(e&&e.userDeleted||"")==="1";var r={commentId:t,commentContent:a===null?"该评论已删除":String(a),appUserNickName:P(e&&e.appUserNickName),replyCount:e&&e.replyCount,userDeleted:n||a===null};return re(r,e,["appUserAvatar","parentAppUserNickName","commentLevel","status","createTime"]),["genealogyId","feedId","parentCommentId","appUserId","parentAppUserId"].forEach(function(d){p(e&&e[d])&&(r[d]=p(e[d]))}),r}function _(e){return Array.isArray(e)?e:e&&Array.isArray(e.rows)?e.rows:[]}function l(e){return String(e==null?"":e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function R(e,t){return i.ProfileUI&&i.ProfileUI.renderApiState?i.ProfileUI.renderApiState(e,t):'
'+l(t)+"
"}function B(e,t){var a=o('[data-feed-id="'+e+'"]'),n=a&&a.nextElementSibling;a&&(n&&n.getAttribute("data-feed-comment-list")===e&&n.remove(),a.insertAdjacentHTML("afterend",'
'+t+"
"))}function ue(e,t){var a=o('[data-feed-comment-node="'+e+'"]'),n=a&&a.nextElementSibling;a&&(n&&n.getAttribute("data-feed-reply-list")===e&&n.remove(),a.insertAdjacentHTML("afterend",t))}function z(e,t,a){ue(e,'
'+R(t,a)+"
")}function Me(e,t){return h(t?"edit":"list",e,t)}function he(e,t){var a=c.userId&&c.userId===e.publisherUserId,n=Array.isArray(e.mediaFiles)?e.mediaFiles.length:0,r=e.publisherNickName||e.genealogyName||"家族动态",d=[],f="";return(e.pinned===!0||String(e.pinned)==="1")&&d.push("置顶"),e.createTime&&d.push(e.createTime),n&&d.push("附件 "+n),d.push("点赞 "+Number(e.likeCount||0)),d.push("评论 "+Number(e.commentCount||0)),f+=e.likedByMe?'':'',f+='查看详情',f+='',f+='',c.canEditContent&&(f+='编辑'),a&&(f+=''),'

'+l(r)+"

"+l(e.feedContent)+"

"+(E?E.renderGallery(e.mediaFiles,{label:"动态附件",className:"feed-media-gallery"}):"")+'

'+l(d.join(" · "))+'

'+f+"
"}function Ie(e,t){var a=o("[data-feed-list]"),n=_(e).map(x),r=n.filter(function(f){return!f}).length,d=n.filter(Boolean);if(a){if(r){g("[data-feed-list]","error","动态响应缺少 feedId 或 feedContent,请联系后端补充 DTO。");return}if(!d.length){g("[data-feed-list]","empty","暂无家族动态");return}a.innerHTML=d.map(function(f){return he(f,t)}).join("")}}function $(e){var t=Number(e&&e.total);return Number.isFinite(t)&&t>=0?t:0}function we(e){var t=$(e),a=Math.max(1,Math.ceil(t/H)),n=o("[data-feed-page-prev]"),r=o("[data-feed-page-next]"),d=o("[data-feed-page-label]");n&&(n.disabled=I<=1),r&&(r.disabled=I>=a),d&&(d.textContent="第 "+I+" / "+a+" 页,共 "+t+" 条")}function Ae(e){var t=e&&e.replyCount;return t!=null&&String(t)!==""&&String(t)!=="0"}function se(e,t){var a=t.appUserNickName?""+l(t.appUserNickName)+" ":"",n="",r=c.userId&&c.userId===t.appUserId;return t.userDeleted||(n+='',r&&(n+='')),Ae(t)&&(n+=''),'

'+a+l(t.commentContent)+"

"+(t.createTime?'

'+l(t.createTime)+"

":"")+n+"
"}function J(e){var t=_(e).map(fe);return{invalidCount:t.filter(function(a){return!a}).length,comments:t.filter(Boolean)}}function Fe(e,t){var a=o("[data-feed-detail-comments]"),n=o('[data-feed-id="'+e+'"]'),r=J(t),d=r.comments,f;if(!(!n&&!a)){if(r.invalidCount){f=R("error","评论响应缺少 commentId 或 commentContent,请联系后端补充 DTO。"),a?g("[data-feed-detail-comments]","error","评论响应缺少 commentId 或 commentContent,请联系后端补充 DTO。"):B(e,f);return}if(!d.length){f=R("empty","暂无评论"),a?g("[data-feed-detail-comments]","empty","暂无评论"):B(e,f);return}f='
'+d.map(function(b){return se(e,b)}).join("")+"
"+ce("comments",e,"",U[e]||1,$(t)),a?a.innerHTML=f:B(e,f)}}function Ue(e,t,a){var n=o('[data-feed-comment-node="'+t+'"]'),r=J(a);if(n){if(r.invalidCount){z(t,"error","回复响应缺少 commentId 或 commentContent,请联系后端补充 DTO。");return}if(!r.comments.length){z(t,"empty","暂无直接回复");return}ue(t,'
'+r.comments.map(function(d){return se(e,d)}).join("")+ce("replies",e,t,L[t]||1,$(a))+"
")}}function ce(e,t,a,n,r){var d=Math.max(1,Math.ceil(r/H)),f=e==="comments"?"data-feed-comments-":"data-feed-replies-",b=' data-feed-id="'+l(t)+'"'+(a?' data-comment-id="'+l(a)+'"':"");return'
第 "+n+" / "+d+' 页
"}function me(e){var t={};return y("[name]",e).forEach(function(a){a.disabled||(t[a.name]=a.value)}),t}function Pe(e){var t=x(e);if(!t)throw new Error("动态响应缺少 feedId 或 feedContent,请联系后端补充 DTO。");y("[data-feed-form] [name]").forEach(function(a){t[a.name]!==void 0&&t[a.name]!==null&&(a.value=t[a.name])})}function pe(e){return!!(e&&(e.canEditContent===!0||e.canManage===!0))}function Se(e){y("[data-feed-create-link]").forEach(function(t){t.hidden=!e})}function ge(e,t){return{userId:p(t&&t.userId),canEditContent:pe(e),canManage:!!(e&&e.canManage===!0)}}async function K(e,t){var a,n;try{a=await e.genealogyDetail(t)}catch(r){a=null}try{n=await e.currentProfile()}catch(r){n=null}return c=ge(a,n),Se(c.canEditContent),c}function W(e,t){var a=o("[data-feed-detail]"),n=c.userId&&c.userId===e.publisherUserId,r="";a&&(r+=e.likedByMe?'':'',r+='',r+='',c.canEditContent&&(r+='编辑'),n&&(r+=''),a.setAttribute("data-feed-id",e.feedId),a.innerHTML="

"+l(e.publisherNickName||e.genealogyName||"家族动态")+"

"+l(e.feedContent)+"

"+(E?E.renderGallery(e.mediaFiles,{label:"动态附件",className:"feed-media-gallery"}):"")+'

'+l([e.createTime||"","点赞 "+Number(e.likeCount||0),"评论 "+Number(e.commentCount||0),String(e.status)==="1"?"已停用":"正常"].filter(Boolean).join(" · "))+'

'+r+"
")}function X(e){i.ProfileUI&&i.ProfileUI.syncGenealogyContextLinks&&i.ProfileUI.syncGenealogyContextLinks(),y("[data-feed-create-link], [data-feed-list-link]").forEach(function(t){t.href=t.hasAttribute("data-feed-create-link")?h("edit",e):h("list",e)})}function A(){var e=Z(),t;return e||(t=o("[data-feed-list]"),t&&g("[data-feed-list]","forbidden","请从具体家谱进入家族圈动态。"),q(!1),C("请从具体家谱进入动态发布。","forbidden"),O(!1),v("请从具体家谱进入动态详情后发表评论。","forbidden"),m("请从具体家谱进入家族圈动态")),e}async function k(e){var t=w(),a,n;if(!u(t)&&(a=A(),!!a)){X(a),I=S(e||Q(i.location&&i.location.search,"pageNum")).pageNum,g("[data-feed-list]","loading","正在加载家族动态…");try{await K(t,a),n=await t.feedsPage(a,S(I)),Ie(n,a),we(n)}catch(r){if(u(t,r))return;g("[data-feed-list]",s(r),s(r)==="forbidden"?"当前账号无权查看该家谱的动态。":"家族动态加载失败,请稍后重试。"),m(r.message||"家族动态加载失败")}}}async function Ee(){var e=w(),t,a=N(),n=!!a,r=n?"编辑动态":"发布动态";if(i.document&&(i.document.title=r+" - 个人中心 - 代代相传"),o("[data-feed-editor-kicker]")&&(o("[data-feed-editor-kicker]").textContent=r),o("[data-feed-editor-title]")&&(o("[data-feed-editor-title]").textContent=r),y("[data-feed-editor-action]").forEach(function(d){d.textContent=n?"保存修改":"发布动态"}),!u(e)&&(t=A(),!!t)){X(t),q(!1),C("正在读取家谱权限…","loading");try{if(await K(e,t),!c.canEditContent){C("当前账号无权发布或编辑该家谱的动态。","forbidden");return}if(q(!0),o("[data-feed-advanced]")&&(o("[data-feed-advanced]").hidden=!c.canManage,y("[name]",o("[data-feed-advanced]")).forEach(function(d){d.disabled=!c.canManage})),!a){C("");return}Pe(await e.feedDetail(t,a)),C("")}catch(d){if(u(e,d))return;C(s(d)==="forbidden"?"当前账号无权维护该家谱的动态。":d.message||"动态详情加载失败",s(d))}}}async function ve(){var e=w(),t,a=N(),n;if(!u(e)){if(O(!1),v("正在读取动态评论权限…","loading"),t=A(),!t||!a){g("[data-feed-detail]","error","动态链接缺少有效编号。"),v("动态链接缺少有效编号。","error");return}X(t),g("[data-feed-detail]","loading","正在加载动态详情…");try{if(await K(e,t),n=x(await e.feedDetail(t,a)),!n)throw new Error("动态响应缺少 feedId 或 feedContent,请联系后端补充 DTO。");W(n,t),O(!0),v(""),await G(a,U[a]||1)}catch(r){if(u(e,r))return;g("[data-feed-detail]",s(r),s(r)==="forbidden"?"当前账号无权查看该动态。":"动态详情加载失败,请返回列表重试。"),v(s(r)==="forbidden"?"当前账号无权评论该动态。":"动态详情加载失败,暂不能发表评论。",s(r)),m(r.message||"动态详情加载失败")}}}async function Ne(e){var t=w(),a,n=N(),r;if(!u(t)&&(a=A(),!!a)){if(!c.canEditContent){C("当前账号无权发布或编辑该家谱的动态。","forbidden");return}i.AppRichEditor&&i.AppRichEditor.syncAll&&i.AppRichEditor.syncAll();try{r=de(me(e))}catch(d){C(d.message||"动态表单校验失败","error");return}if(!r.feedContent){C("请填写动态内容","error");return}return D("save:"+(n||"new"),async function(){var d,f;te(!0),C("正在保存动态…","loading");try{if(n?d=await t.updateFeed(a,n,r):d=await t.createFeed(a,r),f=le(d)||n,!f)throw new Error("动态保存响应缺少 feedId");String(r.status||"0")==="0"&&await t.feedDetail(a,f),m("动态已保存"),i.NavigationUtil.open(ie(a,f,r.status))}catch(b){if(u(t,b))return;C(s(b)==="forbidden"?"当前账号无权保存该家谱的动态。":b.message||"动态保存失败",s(b))}finally{te(!1)}})}}async function be(e,t){var a=w(),n;if(!u(a)&&(n=A(),!(!n||!e)))return D((t?"like:":"unlike:")+e,async function(){try{t?await a.likeFeed(n,e):await a.unlikeFeed(n,e),o("[data-feed-detail-page]")?await ve():await k(I)}catch(r){if(u(a,r))return;m(r.message||(t?"点赞失败":"取消点赞失败"))}})}async function De(e){var t=w(),a;if(!u(t)&&(a=A(),!(!a||!e)&&await T("确认删除这条动态吗?")))return D("delete:"+e,async function(){try{await t.deleteFeed(a,e),o("[data-feed-detail-page]")?i.NavigationUtil.open(h("list",a)):await k(I)}catch(n){if(u(t,n))return;m(n.message||"删除动态失败")}})}async function G(e,t){var a=w(),n;if(!u(a)&&(n=A(),!(!n||!e))){U[e]=S(t).pageNum,o("[data-feed-detail-comments]")?g("[data-feed-detail-comments]","loading","正在加载评论…"):B(e,R("loading","正在加载评论…"));try{Fe(e,await a.feedCommentsPage(n,e,S(U[e])))}catch(r){if(u(a,r))return;o("[data-feed-detail-comments]")?g("[data-feed-detail-comments]",s(r),s(r)==="forbidden"?"当前账号无权查看评论。":"评论加载失败,请稍后重试。"):B(e,R(s(r),s(r)==="forbidden"?"当前账号无权查看评论。":"评论加载失败,请稍后重试。")),m(r.message||"评论加载失败")}}}async function Y(e,t,a){var n=w(),r;if(!u(n)&&(r=A(),!(!r||!e||!t))){L[t]=S(a).pageNum,z(t,"loading","正在加载回复…");try{Ue(e,t,await n.feedCommentRepliesPage(r,e,t,S(L[t])))}catch(d){if(u(n,d))return;z(t,s(d),s(d)==="forbidden"?"当前账号无权查看回复。":"回复加载失败,请稍后重试。"),m(d.message||"回复加载失败")}}}async function xe(e){var t=w(),a,n=N(),r,d,f=!1;if(!(u(t)||!e)&&(a=A(),!(!a||!n))){if(d=me(e),r=oe(d),!r.commentContent){v("请填写评论内容。","error");return}if(r.commentContent.length>1e3){v("评论不能超过 1000 字。","error");return}return D("comment:"+n+":"+(r.parentCommentId||"root"),async function(){O(!1),ae(!0),v("正在发布评论…","loading");try{await t.createFeedComment(a,n,r),await G(n,U[n]||1),r.parentCommentId&&await Y(n,r.parentCommentId,L[r.parentCommentId]||1),e.reset(),V(""),v("");var b=x(await t.feedDetail(a,n));b&&W(b,a),m("评论已发布")}catch(j){if(u(t,j))return;f=s(j)==="forbidden",v(f?"当前账号无权评论该动态。":j.message||"评论发布失败,请稍后重试。",s(j))}finally{ae(!1),f||O(!0)}})}}async function ke(e,t){var a=w(),n;if(!u(a)&&(n=A(),!(!n||!e||!t)&&await T("确认删除这条评论吗?")))return D("delete-comment:"+t,async function(){try{if(await a.deleteFeedComment(n,e,t),await G(e,U[e]||1),o("[data-feed-detail-page]")){var r=x(await a.feedDetail(n,e));r&&W(r,n)}else await k(I)}catch(d){if(u(a,d))return;m(d.message||"删除评论失败")}})}function Te(){F&&(F.addEventListener("submit",function(e){var t=e.target.closest("[data-feed-form]");if(t){e.preventDefault(),Ne(t);return}t=e.target.closest("[data-feed-comment-form]"),t&&(e.preventDefault(),xe(t))}),F.addEventListener("click",function(e){var t=e.target,a,n,r;t.closest("[data-feed-like]")&&(e.preventDefault(),be(t.closest("[data-feed-like]").getAttribute("data-feed-like"),!0)),t.closest("[data-feed-unlike]")&&(e.preventDefault(),be(t.closest("[data-feed-unlike]").getAttribute("data-feed-unlike"),!1)),t.closest("[data-feed-comments]")&&(e.preventDefault(),a=t.closest("[data-feed-comments]").getAttribute("data-feed-comments"),G(a,1)),t.closest("[data-feed-comment]")&&(e.preventDefault(),a=t.closest("[data-feed-comment]").getAttribute("data-feed-comment"),ne(a)),t.closest("[data-feed-comment-reply]")&&(e.preventDefault(),n=t.closest("[data-feed-comment-reply]").getAttribute("data-feed-comment-reply"),a=t.closest("[data-feed-comment-reply]").getAttribute("data-feed-id"),ne(a,n)),t.closest("[data-feed-comment-cancel]")&&(e.preventDefault(),V(""),v("")),t.closest("[data-feed-comment-replies]")&&(e.preventDefault(),n=t.closest("[data-feed-comment-replies]").getAttribute("data-feed-comment-replies"),a=t.closest("[data-feed-comment-replies]").getAttribute("data-feed-id"),Y(a,n,1)),t.closest("[data-feed-delete]")&&(e.preventDefault(),De(t.closest("[data-feed-delete]").getAttribute("data-feed-delete"))),t.closest("[data-feed-comment-delete]")&&(e.preventDefault(),n=t.closest("[data-feed-comment-delete]").getAttribute("data-feed-comment-delete"),a=t.closest("[data-feed-comment-delete]").getAttribute("data-feed-id"),ke(a,n)),t.closest("[data-feed-page-prev]")&&(e.preventDefault(),k(Math.max(1,I-1))),t.closest("[data-feed-page-next]")&&(e.preventDefault(),k(I+1)),t.closest("[data-feed-comments-prev], [data-feed-comments-next]")&&(e.preventDefault(),a=t.closest("[data-feed-id]").getAttribute("data-feed-id"),r=U[a]||1,G(a,t.closest("[data-feed-comments-prev]")?Math.max(1,r-1):r+1)),t.closest("[data-feed-replies-prev], [data-feed-replies-next]")&&(e.preventDefault(),a=t.closest("[data-feed-id]").getAttribute("data-feed-id"),n=t.closest("[data-comment-id]").getAttribute("data-comment-id"),r=L[n]||1,Y(a,n,t.closest("[data-feed-replies-prev]")?Math.max(1,r-1):r+1)),t.closest("[data-feed-share]")&&(e.preventDefault(),i.navigator&&i.navigator.clipboard&&i.location?i.navigator.clipboard.writeText(i.location.href).then(function(){m("分享链接已复制")}).catch(function(){m("复制失败,请从浏览器地址栏复制")}):m("请从浏览器地址栏复制分享链接"))}))}function Le(){F&&(Te(),o("[data-feed-page]")&&k(),o("[data-feed-edit-page]")&&Ee(),o("[data-feed-detail-page]")&&ve())}return{getCurrentGenealogyId:Z,getCurrentFeedId:N,buildFeedBody:de,buildCommentBody:oe,buildPageQuery:S,buildFeedUrl:h,buildPostSaveUrl:ie,withActionLock:D,normalizeFeed:x,normalizeComment:fe,canEditFeeds:pe,normalizeViewerContext:ge,normalizeComments:J,normalizeList:_,shouldRedirectToLogin:ee,init:Le}}); diff --git a/dist/public/js/feedback-pages.js b/dist/public/js/feedback-pages.js deleted file mode 100644 index 7c8ef88..0000000 --- a/dist/public/js/feedback-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(n,S){if(typeof module=="object"&&module.exports){module.exports=S(n);return}n.FeedbackPages=S(n),n.document&&n.document.addEventListener("DOMContentLoaded",function(){n.FeedbackPages.init()})})(typeof globalThis!="undefined"?globalThis:window,function(n){"use strict";var S=["ACTIVE","DISABLED","UNKNOWN"],I={0:"待处理",1:"处理中",2:"已处理",3:"已关闭"};function f(a){return String(a==null?"":a).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function T(a){var e;return typeof a=="number"&&!Number.isSafeInteger(a)||a==null?"":(e=String(a).trim(),/^[1-9][0-9]*$/.test(e)?e:"")}function u(a){return a==null?"":String(a).trim()}function O(a){var e=new URLSearchParams(String(a===void 0&&n.location?n.location.search:a||"").replace(/^\?/,""));return T(e.get("feedbackId"))}function D(a){var e=a||{},t={},r=u(e.feedbackType),d=u(e.feedbackContent),i=u(e.contactInfo);return r&&(t.feedbackType=r),d&&(t.feedbackContent=d),i&&(t.contactInfo=i),t}function U(a,e){var t=a||{},r=Array.isArray(e)?e:[];return u(t.feedbackContent)?t.feedbackType&&r.length&&r.indexOf(t.feedbackType)<0?"反馈类型无效":"":"请填写反馈内容"}function x(a){return Array.isArray(a)?a.reduce(function(e,t){var r=t||{},d=u(r.value),i=u(r.label);return d&&i&&r.enabled===!0&&e.push({value:d,label:i}),e},[]):[]}function F(a){var e=a||{},t=T(e.feedbackId),r=u(e.feedbackType),d=u(e.feedbackTypeLabel)||r,i=u(e.feedbackTypeOptionState),y=u(e.feedbackContent),h=String(e.handleStatus===void 0||e.handleStatus===null?"":e.handleStatus),v=String(e.status===void 0||e.status===null?"":e.status);return!t||!y||!r||!d||S.indexOf(i)<0||!Object.prototype.hasOwnProperty.call(I,h)||["0","1"].indexOf(v)<0?null:{feedbackId:t,feedbackType:r,feedbackTypeLabel:d,feedbackTypeOptionState:i,feedbackContent:y,contactInfo:u(e.contactInfo),handleStatus:h,handleResult:u(e.handleResult),handleTime:u(e.handleTime),status:v,remark:u(e.remark)}}function L(a){var e;return Array.isArray(a)?(e=a.map(F),e.some(function(t){return!t})?[]:e):[]}function B(a,e){var t=T(e),r=L(a),d;return t?(d=r.find(function(i){return i.feedbackId===t}),d||null):null}function A(a,e){var t=T(a),r;return t?(r=new URLSearchParams,r.set("feedbackId",t),(e||"ticket-detail.html")+"?"+r.toString()):""}function N(a,e){var t=e||{},r=L(a),d=t.detailPage||"ticket-detail.html";return r.length?r.map(function(i){return'

'+f(i.feedbackTypeLabel)+"

"+f(i.feedbackContent)+'

'+I[i.handleStatus]+"
"}).join(""):'
当前没有反馈记录
'}function z(a){var e=F(a),t;return e?(t=[e.contactInfo?"
提交联系方式
"+f(e.contactInfo)+"
":"","
处理状态
"+I[e.handleStatus]+"
",e.handleResult?"
处理结果
"+f(e.handleResult)+"
":"",e.handleTime?"
处理时间
"+f(e.handleTime)+"
":"",e.remark?"
备注
"+f(e.remark)+"
":""].filter(Boolean).join(""),'
'+f(e.feedbackTypeLabel)+"

问题说明

"+f(e.feedbackContent)+'

'+t+"
"):'
反馈记录不存在或无权查看
'}function k(a,e){var t=e&&(e.status||e.code);return!a||!a.getToken||!a.getToken()||Number(t)===401}function m(a){a&&a.clearToken&&a.clearToken(),n.NavigationUtil.open("login.html")}function c(a){return Number(a&&(a.status||a.code))===403}function _(a){var e={};return Array.prototype.forEach.call(a.querySelectorAll("[name]"),function(t){e[t.name]=t.value}),e}function o(a,e,t){if(a){if(n.ProfileUI&&n.ProfileUI.setApiState&&e){n.ProfileUI.setApiState(a,e,t);return}a.textContent=t||""}}function l(a,e){var t=n.document&&n.document.querySelector("[data-feedback-status]");o(t,a,e)}function P(a,e,t){if(a){if(!e.length){o(a,"empty","当前没有反馈记录。");return}a.innerHTML=N(e,{detailPage:t||"ticket-detail.html"})}}function p(a,e,t){a&&(a.dataset.defaultLabel||(a.dataset.defaultLabel=a.textContent),a.disabled=e,a.textContent=e?t:a.dataset.defaultLabel)}async function K(a,e){var t=x(await a.businessDictionary("gen_feedback_type"));if(!t.length)throw new Error("反馈类型选项响应无效");return e&&(e.innerHTML=''+t.map(function(r){return'"}).join("")),t}async function C(a){var e=await a.myFeedback(),t=L(e);if(!Array.isArray(e)||t.length!==e.length)throw new Error("反馈列表响应无效");return t}async function M(){var a=n.document&&n.document.querySelector("[data-feedback-form]"),e=n.document&&n.document.querySelector("[data-feedback-list]"),t=n.document&&n.document.querySelector("[data-feedback-refresh]"),r=n.document&&n.document.querySelector("[data-feedback-page]"),d=n.GenealogyApi&&n.GenealogyApi.defaultClient,i=r&&r.getAttribute("data-feedback-page"),y=a&&a.querySelector("[data-feedback-type-options]"),h=[],v=!1;if(!a)return;if(k(d)){m(d);return}try{h=(await K(d,y)).map(function(b){return b.value})}catch(b){y&&(y.innerHTML='')}async function H(){var b;if(!e)return[];o(e,"loading","正在读取反馈记录…"),l("loading","正在读取反馈记录…");try{return b=await C(d),P(e,b,"ticket-detail.html"),l("",""),b}catch(s){return k(d,s)?(m(d),[]):(o(e,c(s)?"forbidden":"error",c(s)?"暂无权限查看反馈记录。":"读取反馈记录失败,请稍后重试。"),l(c(s)?"forbidden":"error",c(s)?"暂无权限查看反馈记录。":s.message||"读取反馈记录失败,请稍后重试。"),[])}}t&&t.addEventListener("click",async function(){p(t,!0,"正在刷新…");try{await H()}finally{p(t,!1,"正在刷新…")}}),a.addEventListener("submit",async function(b){var s,q,w,E,j=a.querySelector("[data-feedback-submit]");if(b.preventDefault(),!v){if(s=D(_(a)),q=U(s,h),q){l("error",q);return}v=!0,p(j,!0,"正在提交…"),l("loading","正在提交反馈…");try{if(w=F(await d.submitFeedback(s)),!w)throw new Error("提交响应缺少有效反馈信息");if(E=await C(d),!E.some(function(g){return g.feedbackId===w.feedbackId}))throw new Error("提交后无法读取同一反馈记录");if(i==="ticket"){n.NavigationUtil.open(A(w.feedbackId));return}P(e,E,"ticket-detail.html"),a.reset(),l("","反馈已提交")}catch(g){if(k(d,g)){m(d);return}l(c(g)?"forbidden":"error",c(g)?"暂无权限提交反馈。":g.message||"提交反馈失败,请稍后重试。")}finally{v=!1,p(j,!1,"正在提交…")}}}),await H()}async function R(){var a=n.document&&n.document.querySelector("[data-feedback-list-page]"),e=n.document&&n.document.querySelector("[data-feedback-list]"),t=n.document&&n.document.querySelector("[data-feedback-refresh]"),r=n.GenealogyApi&&n.GenealogyApi.defaultClient;if(!a||!e)return;if(k(r)){m(r);return}async function d(){try{o(e,"loading","正在读取反馈记录…"),l("loading","正在读取反馈记录…"),P(e,await C(r),"ticket-detail.html"),l("","")}catch(i){if(k(r,i)){m(r);return}o(e,c(i)?"forbidden":"error",c(i)?"暂无权限查看反馈记录。":"读取反馈记录失败,请稍后重试。"),l(c(i)?"forbidden":"error",c(i)?"暂无权限查看反馈记录。":i.message||"读取反馈记录失败,请稍后重试。")}}t&&t.addEventListener("click",async function(){p(t,!0,"正在刷新…");try{await d()}finally{p(t,!1,"正在刷新…")}}),await d()}async function G(){var a=n.document&&n.document.querySelector("[data-feedback-detail-page]"),e=n.document&&n.document.querySelector("[data-feedback-detail]"),t=n.GenealogyApi&&n.GenealogyApi.defaultClient,r=O(),d;if(!(!a||!e)){if(!r){o(e,"empty","反馈记录不存在或无权查看。");return}if(k(t)){m(t);return}try{o(e,"loading","正在读取反馈详情…"),l("loading","正在读取反馈详情…"),d=B(await t.myFeedback(),r),d?e.innerHTML=z(d):o(e,"empty","反馈记录不存在或无权查看。"),l("",d?"":"反馈记录不存在或无权查看。")}catch(i){if(k(t,i)){m(t);return}o(e,c(i)?"forbidden":"error",c(i)?"暂无权限查看该反馈详情。":"反馈详情读取失败,请稍后重试。"),l(c(i)?"forbidden":"error",c(i)?"暂无权限查看该反馈详情。":i.message||"反馈详情读取失败,请稍后重试。")}}}async function V(){if(n.document&&n.document.querySelector("[data-feedback-form]")){await M();return}if(n.document&&n.document.querySelector("[data-feedback-list-page]")){await R();return}n.document&&n.document.querySelector("[data-feedback-detail-page]")&&await G()}return{getFeedbackId:O,buildFeedbackBody:D,validateFeedbackBody:U,normalizeFeedbackTypeOptions:x,normalizeFeedback:F,normalizeFeedbackList:L,findFeedbackById:B,buildFeedbackDetailUrl:A,renderFeedbackList:N,renderFeedbackDetail:z,isForbidden:c,initFeedbackFormPage:M,initFeedbackListPage:R,initFeedbackDetailPage:G,init:V}}); diff --git a/dist/public/js/genealogy-entry-pages.js b/dist/public/js/genealogy-entry-pages.js deleted file mode 100644 index c41f46c..0000000 --- a/dist/public/js/genealogy-entry-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(a,p){if(typeof module=="object"&&module.exports){module.exports=p(a);return}a.GenealogyEntryPages=p(a),a.document&&a.document.addEventListener("DOMContentLoaded",function(){a.GenealogyEntryPages.init()})})(typeof globalThis!="undefined"?globalThis:window,function(a){"use strict";function p(n){var e=String(n||""),r=e.indexOf("#"),l=r<0?"":e.slice(r),t,u;return r>=0&&(e=e.slice(0,r)),t=e.split("?"),/^profile-[a-z-]+\.html$/.test(t[0])?(u=new URLSearchParams(t[1]||""),u.delete("genealogyId"),t[0]+(u.toString()?"?"+u.toString():"")+l):"profile-family-home.html"}function A(n){var e=new URLSearchParams(String(n===void 0&&a.location?a.location.search:n||"").replace(/^\?/,""));return p(e.get("next"))}function G(n){var e=n||{},r=[];return Number(e.createRemaining)===-1?r.push("创建不限"):Number(e.createRemaining)>=0&&r.push("还可创建 "+e.createRemaining+" 部"),Number(e.joinRemaining)===-1?r.push("加入不限"):Number(e.joinRemaining)>=0&&r.push("还可加入 "+e.joinRemaining+" 部"),r.length?"家谱额度:"+r.join(",")+"。":""}function f(n){return String(n==null?"":n).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function P(n){var e;if(n!=null)return e=String(n).trim(),e||void 0}function w(n){var e;return typeof n=="number"&&!Number.isSafeInteger(n)||n==null?"":(e=String(n).trim(),/^[1-9][0-9]*$/.test(e)?e:"")}function L(n){var e=n||{},r={genealogyName:String(e.genealogyName||"").trim(),surname:String(e.surname||"").trim(),regionCode:String(e.regionCode||"").trim()},l=["ancestralHall","originPlace","addressDetail","intro"],t=w(e.coverOssId),u=P(e.visibility),o=P(e.joinMode);return l.forEach(function(g){var d=P(e[g]);d!==void 0&&(r[g]=d)}),e.coverOssId!==void 0&&e.coverOssId!==null&&e.coverOssId!==""&&(t?r.coverOssId=t:r.invalidCoverOssId=!0),u!==void 0&&(r.visibility=u),o!==void 0&&(r.joinMode=o),r}function E(n){return!n||!n.genealogyName?"请填写谱名":n.surname?n.regionCode?n.visibility!==void 0&&["0","1","2"].indexOf(n.visibility)<0?"可见范围选项无效":n.joinMode!==void 0&&["0","1","2"].indexOf(n.joinMode)<0?"加入方式选项无效":n.invalidCoverOssId?"封面文件无效,请重新选择":"":"请选择家谱所在地区":"请填写姓氏"}function O(n){var e=Number(n&&n.createRemaining);return n&&typeof n.canCreate=="boolean"?n.canCreate:Number.isInteger(e)&&(e===-1||e>0)}function I(n){var e=w(n&&n.genealogyId),r=String(n&&n.genealogyName||"").trim(),l=String(n&&n.surname||"").trim();return!e||!r||!l?null:{genealogyId:e,genealogyName:r,surname:l,regionFullName:String(n&&n.regionFullName||"")}}function q(n){var e=I(n);return e?U(e.genealogyId,"profile-family-home.html"):""}function c(n,e){var r=e&&(e.status||e.code);return!n||!n.getToken||!n.getToken()||Number(r)===401}function S(n){n&&n.clearToken&&n.clearToken(),a.NavigationUtil.open("login.html")}function z(n){var e={};return Array.prototype.forEach.call(n.querySelectorAll("[name]"),function(r){e[r.name]=r.value}),e}function y(n){return Number(n&&(n.status||n.code))===403?"forbidden":"error"}function m(n,e){var r=a.document&&a.document.querySelector("[data-genealogy-create-status]");if(r){if(!n){r.innerHTML="";return}if(a.ProfileUI&&a.ProfileUI.setApiState){a.ProfileUI.setApiState(r,e||"error",n);return}r.textContent=n}}function v(n,e){var r=a.document&&a.document.querySelector("[data-genealogy-entry-status]");if(r){if(!n){r.innerHTML="";return}if(e&&a.ProfileUI&&a.ProfileUI.setApiState){a.ProfileUI.setApiState(r,e,n);return}r.textContent=n}}function b(n,e,r){if(n){if(a.ProfileUI&&a.ProfileUI.setApiState){a.ProfileUI.setApiState(n,e,r);return}n.textContent=r}}function M(n){var e=a.document&&a.document.querySelector("[data-genealogy-create-submit]");e&&(e.dataset.defaultLabel||(e.dataset.defaultLabel=e.textContent),e.disabled=!!n,e.textContent=n?"正在创建…":e.dataset.defaultLabel,e.setAttribute("aria-busy",n?"true":"false"))}async function j(){var n=a.document&&a.document.querySelector("[data-genealogy-create-form]"),e=a.document&&a.document.querySelector("[data-genealogy-create-quota]"),r=a.GenealogyApi&&a.GenealogyApi.defaultClient,l,t=!1;if(n){if(c(r)){S(r);return}m("正在读取创建额度…","loading");try{if(l=await r.genealogyQuota(),e&&(e.textContent=G(l)||"当前额度信息不可用"),!O(l)){m("当前没有可用的家谱创建额度","empty"),n.querySelector("[data-genealogy-create-submit]").disabled=!0;return}m("")}catch(u){if(c(r,u)){S(r);return}e&&(e.textContent="家谱额度读取失败"),m(u.message||"无法读取家谱创建额度",y(u)),n.querySelector("[data-genealogy-create-submit]").disabled=!0;return}n.__genealogyCreateBound||(n.__genealogyCreateBound=!0,n.addEventListener("submit",async function(u){var o,g,d,s;if(u.preventDefault(),!t){if(o=L(z(n)),g=E(o),g){m(g,"error");return}delete o.invalidCoverOssId,t=!0,M(!0),m("正在创建家谱…","loading");try{if(d=I(await r.createGenealogy(o)),!d)throw new Error("创建响应缺少有效家谱信息");if(s=I(await r.genealogyDetail(d.genealogyId)),!s||s.genealogyId!==d.genealogyId)throw new Error("创建后无法读取同一家谱");a.ProfileUI&&a.ProfileUI.setCurrentGenealogyContext&&a.ProfileUI.setCurrentGenealogyContext(s.genealogyId,s.genealogyName),a.NavigationUtil.open(q(s))}catch(h){if(c(r,h)){S(r);return}m(h.message||"创建家谱失败,请稍后重试",y(h))}finally{t=!1,M(!1)}}}))}}function T(n){var e=n||{},r=e.genealogyId===void 0||e.genealogyId===null?"":String(e.genealogyId);return!/^[1-9][0-9]*$/.test(r)||!e.genealogyName?null:{genealogyId:r,genealogyName:String(e.genealogyName),surname:e.surname===void 0||e.surname===null?"":String(e.surname),regionFullName:e.regionFullName===void 0||e.regionFullName===null?"":String(e.regionFullName),memberCount:e.memberCount,personCount:e.personCount,roleType:e.roleType===void 0||e.roleType===null?"":String(e.roleType),canManage:e.canManage===!0}}function U(n,e){var r=p(e),l=r.split("#"),t=l[0],u=l.length>1?"#"+l.slice(1).join("#"):"",o=t.split("?"),g=new URLSearchParams(o[1]||"");return g.set("genealogyId",String(n)),o[0]+"?"+g.toString()+u}function B(n,e,r){var l=Array.isArray(e)?e.map(T).filter(Boolean):[];return n?!Array.isArray(e)||e.length&&!l.length?(b(n,"error","家谱响应字段不完整,请联系后端核对。"),[]):l.length?(n.innerHTML=l.map(function(t){var u=[t.surname?t.surname+"氏":"",t.regionFullName,t.memberCount===void 0||t.memberCount===null?"":t.memberCount+" 位成员",t.personCount===void 0||t.personCount===null?"":t.personCount+" 位世系人物"].filter(Boolean).join(" · ");return'

'+f(t.genealogyName)+"

"+f(u)+'

'+(t.canManage?"管理并进入":"进入家谱")+"
"}).join(""),l):(b(n,"empty","当前账号还没有可进入的家谱"),[]):l}async function F(){if(a.document&&a.document.querySelector("[data-genealogy-create-page]")){await j();return}var n=a.document&&a.document.querySelector("[data-genealogy-entry-status]"),e=a.GenealogyApi&&a.GenealogyApi.defaultClient,r=A(),l=a.document&&a.document.querySelector('[data-genealogy-list="mine"]'),t=a.document&&a.document.querySelector("[data-genealogy-sort-select]"),u=a.document&&a.document.querySelector("[data-genealogy-sort-up]"),o=a.document&&a.document.querySelector("[data-genealogy-sort-down]"),g=a.document&&a.document.querySelector("[data-genealogy-sort-save]"),d,s=!1;if(!n)return;if(c(e)){S(e);return}if(!e||!e.genealogyQuota||!e.genealogiesMine||!e.saveGenealogyOrder){v("家谱入口接口初始化失败,请刷新后重试。","error"),b(l,"error","家谱列表接口初始化失败,请刷新后重试。");return}b(l,"loading","正在读取我的家谱…"),v("正在读取家谱额度…","loading");try{if(d=await e.genealogiesMine(),B(l,d,r),t){var h=Array.isArray(d)?d.map(T).filter(Boolean):[];t.innerHTML=h.map(function(i){return'"}).join(""),[t,u,o,g].forEach(function(i){i&&(i.disabled=h.length<2)})}}catch(i){if(c(e,i)){S(e);return}b(l,y(i),y(i)==="forbidden"?"当前账号无权查看我的家谱。":"无法读取我的家谱,请稍后重试。")}function R(i){var N,C,x;!t||s||(N=t.selectedIndex,C=N+i,!(N<0||C<0||C>=t.options.length)&&(x=t.options[N],i<0?t.insertBefore(x,t.options[C]):t.insertBefore(t.options[C],x),t.selectedIndex=C))}u&&u.addEventListener("click",function(){R(-1)}),o&&o.addEventListener("click",function(){R(1)}),g&&g.addEventListener("click",async function(){if(!(s||!t)){s=!0,[t,u,o,g].forEach(function(i){i&&(i.disabled=!0)}),v("正在保存家谱顺序…","loading");try{await e.saveGenealogyOrder(Array.prototype.map.call(t.options,function(i){return i.value})),v("家谱顺序已保存")}catch(i){v(i.message||"家谱顺序保存失败,请稍后重试。",y(i))}finally{s=!1,[t,u,o,g].forEach(function(i){i&&(i.disabled=t.options.length<2)})}}});try{d=await e.genealogyQuota(),v(G(d))}catch(i){if(c(e,i)){S(e);return}v(y(i)==="forbidden"?"当前账号无权读取家谱额度。":"家谱额度读取失败,请稍后重试。",y(i))}}return{normalizeTargetPage:p,getTargetPage:A,buildStatus:G,buildGenealogyCreateBody:L,validateGenealogyCreateBody:E,canCreateGenealogy:O,normalizeCreatedGenealogy:I,buildCreatedGenealogyUrl:q,shouldRedirectToLogin:c,initGenealogyCreatePage:j,normalizeGenealogy:T,buildEntryUrl:U,renderGenealogies:B,init:F}}); diff --git a/dist/public/js/genealogy-lifecycle-pages.js b/dist/public/js/genealogy-lifecycle-pages.js deleted file mode 100644 index e096d6b..0000000 --- a/dist/public/js/genealogy-lifecycle-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(e,s){if(typeof module=="object"&&module.exports){module.exports=s(e);return}e.GenealogyLifecyclePages=s(e),e.document&&e.document.addEventListener("DOMContentLoaded",e.GenealogyLifecyclePages.init)})(typeof globalThis!="undefined"?globalThis:window,function(e){"use strict";function s(a){return e.ProfileUI&&typeof e.ProfileUI.confirmAction=="function"?e.ProfileUI.confirmAction(a):Promise.resolve(!e.confirm||e.confirm(a))}var D={NOT_OWNER:"只有谱主可以永久删除家谱",NO_VERIFIED_MOBILE:"谱主账号尚未绑定已验证手机号",ACTIVE_PAYMENT:"当前家谱存在未完成的支付单",DELETION_IN_PROGRESS:"永久删除任务正在处理中"};function y(a){var n=a||{};return{canDeletePermanently:n.canDeletePermanently===!0,verifiedMobileMasked:String(n.verifiedMobileMasked||"").trim(),disabledReasons:Array.isArray(n.disabledReasons)?n.disabledReasons.map(String):[]}}function p(a){var n=y(a);return n.canDeletePermanently?n.verifiedMobileMasked?"验证码将发送至 "+n.verifiedMobileMasked:"当前账号具备永久删除资格":n.disabledReasons.map(function(i){return D[i]||i}).join(";")||"当前家谱不允许永久删除"}async function S(){var a=e.document&&e.document.querySelector("[data-genealogy-lifecycle-page]"),n=e.GenealogyApi&&e.GenealogyApi.defaultClient,i=e.ProfileUI&&e.ProfileUI.getGenealogyId?e.ProfileUI.getGenealogyId():"",u=a&&a.querySelector("[data-lifecycle-status]"),P=a&&a.querySelector("[data-genealogy-archive]"),I=a&&a.querySelector("[data-genealogy-restore]"),E=a&&a.querySelector("[data-genealogy-delete-form]"),f=a&&a.querySelector("[data-genealogy-delete-code]"),g=a&&a.querySelector("[data-genealogy-delete-submit]"),m=a&&a.querySelector("[data-genealogy-delete-name]"),b=a&&a.querySelector("[data-genealogy-delete-sms]"),l,d=!1;if(!a)return;if(!n||!n.getToken||!n.getToken()){e.NavigationUtil.open("login.html");return}if(!i){e.ProfileUI.setApiState(u,"error","缺少家谱上下文,请从“我的家谱”重新进入。");return}function t(r,o){r?e.ProfileUI.setApiState(u,r,o):u.textContent=o||""}function c(r){d=r,[P,I,f,g].forEach(function(o){o&&(o.disabled=r||(o===g||o===f)&&l&&!l.canDeletePermanently)})}try{var v=await Promise.all([n.genealogyDetail(i),n.genealogyDeletionCapability(i)]);l=y(v[1]),m.placeholder="请输入“"+String(v[0]&&v[0].genealogyName||"家谱全名")+"”确认",g.disabled=!l.canDeletePermanently,f.disabled=!l.canDeletePermanently,t(l.canDeletePermanently?"":"forbidden",p(l))}catch(r){t("error",r.message||"家谱维护能力读取失败");return}P.addEventListener("click",async function(){if(!(d||!await s("归档后家谱将停止日常维护,确认归档吗?"))){c(!0),t("loading","正在归档家谱…");try{await n.archiveGenealogy(i),t("","家谱已归档,需要时可在此恢复。")}catch(r){t("error",r.message||"归档失败")}finally{c(!1)}}}),I.addEventListener("click",async function(){if(!d){c(!0),t("loading","正在恢复家谱…");try{await n.restoreGenealogy(i),t("","家谱已恢复。")}catch(r){t("error",r.message||"恢复失败")}finally{c(!1)}}}),f.addEventListener("click",async function(){if(!(d||!l.canDeletePermanently)){c(!0),t("loading","正在发送验证码…");try{await n.sendGenealogyDeletionCode(i,{}),t("","验证码已发送至谱主手机号。")}catch(r){t("error",r.message||"验证码发送失败")}finally{c(!1)}}}),E.addEventListener("submit",async function(r){if(r.preventDefault(),!(d||!l.canDeletePermanently)){if(!m.value.trim()||!b.value.trim()){t("error","请填写家谱全名和短信验证码");return}if(await s("永久删除不可恢复,确认继续吗?")){c(!0),t("loading","正在提交永久删除任务…");try{await n.permanentlyDeleteGenealogy(i,{genealogyName:m.value.trim(),smsCode:b.value.trim()}),e.ProfileUI&&e.ProfileUI.clearCurrentGenealogyContext&&e.ProfileUI.clearCurrentGenealogyContext(),e.NavigationUtil.open("profile-families.html")}catch(o){t("error",o.message||"永久删除提交失败")}finally{c(!1)}}}})}return{normalizeCapability:y,capabilityMessage:p,init:S}}); diff --git a/dist/public/js/generation-pages.js b/dist/public/js/generation-pages.js deleted file mode 100644 index eea54fc..0000000 --- a/dist/public/js/generation-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(o,B){if(typeof module=="object"&&module.exports){module.exports=B(o);return}o.GenerationPages=B(o),o.document&&o.document.addEventListener("DOMContentLoaded",function(){o.GenerationPages.init()})})(typeof globalThis!="undefined"?globalThis:window,function(o){"use strict";function B(e){return o.ProfileUI&&typeof o.ProfileUI.confirmAction=="function"?o.ProfileUI.confirmAction(e):Promise.resolve(!o.confirm||o.confirm(e))}var m=o.document,S=Object.create(null),P="",s=!1,y=!1;function N(){return o.GenealogyApi&&o.GenealogyApi.defaultClient}function q(e,n){var t=n&&(n.status||n.code);return!e||!e.getToken||!e.getToken()||Number(t)===401}function w(e){return Number(e&&(e.status||e.code))===403}function v(e,n){return q(e,n)?(e&&e.clearToken&&e.clearToken(),o.NavigationUtil.open("login.html"),!0):!1}function d(e,n){return m?(n||m).querySelector(e):null}function b(e,n){return m?Array.prototype.slice.call((n||m).querySelectorAll(e)):[]}function C(e){if(o.layui&&o.layui.layer){o.layui.layer.msg(e);return}o.alert&&o.alert(e)}function I(e,n,t){var r=d(e);if(r){if(o.ProfileUI&&o.ProfileUI.setApiState){o.ProfileUI.setApiState(r,n,t);return}r.innerHTML='
'+c(t)+"
"}}function p(e,n){var t=d("[data-generation-form-status]");if(t){if(!e){t.innerHTML="";return}if(o.ProfileUI&&o.ProfileUI.setApiState){o.ProfileUI.setApiState(t,n||"error",e);return}t.textContent=e}}function ie(e,n){var t=new URLSearchParams(String(e||"").replace(/^\?/,""));return t.get(n)||""}function F(e){var n=d("[data-generation-page]"),t=e===void 0&&o.location?o.location.search:e,r;return e===void 0&&o.ProfileUI&&o.ProfileUI.getGenealogyId&&(r=o.ProfileUI.getGenealogyId(),r)?r:ie(t,"genealogyId")||n&&n.getAttribute("data-genealogy-id")||""}function l(e){var n=String(e==null?"":e).trim();return n||void 0}function T(e){var n=l(e),t;if(n)return t=Number(n),Number.isInteger(t)&&Number.isSafeInteger(t)?t:void 0}function M(e){var n=e||{},t={generationNo:T(n.generationNo),generationText:l(n.generationText)},r=l(n.description),a=l(n.sortOrder),i=l(n.status);return r!==void 0&&(t.description=r),a!==void 0&&(t.sortOrder=Number(a)),i!==void 0&&(t.status=i),t}function z(e){var n=e||{};return{poemText:String(n.poemText||"").trim(),disableMissing:!!n.disableMissing}}function H(e){return!e||!Number.isInteger(e.generationNo)||e.generationNo<1||e.generationNo>2147483647?"世代序号必须是 1 到 2147483647 之间的整数":e.generationText?e.generationText.length>50?"字辈文字不能超过 50 个字符":e.description&&e.description.length>500?"说明不能超过 500 个字符":e.sortOrder!==void 0&&(!Number.isInteger(e.sortOrder)||!Number.isSafeInteger(e.sortOrder))?"排序值必须是整数":e.sortOrder!==void 0&&(e.sortOrder<-2147483648||e.sortOrder>2147483647)?"排序值必须在 -2147483648 到 2147483647 之间":e.status!==void 0&&e.status!=="0"&&e.status!=="1"?"字辈状态只能是 0 或 1":"":"请填写字辈文字"}function D(e){var n;return!e||!e.poemText?"请填写批量字辈内容":e.poemText.length>26e3?"批量字辈内容不能超过 26000 个字符":(n=R(e.poemText),n.length>500?"一次最多导入 500 个世代":n.some(function(t){return t.length>50})?"单个字辈不能超过 50 个字符":"")}function R(e){var n=String(e||"").trim();return n?/[\s,,;;、//||]/.test(n)?n.split(/[\s,,;;、//||]+/).filter(Boolean):Array.from(n):[]}function j(e){var n;return e==null||e===""||typeof e=="number"&&!Number.isSafeInteger(e)?"":(n=String(e),/^\d+$/.test(n)?n:"")}function J(e){return j(e&&e.poemId)}function V(e){var n=J(e),t=j(e&&e.genealogyId),r=T(e&&e.generationNo),a=l(e&&e.generationText),i=l(e&&e.status),u;return!n||r===void 0||!a||i!=="0"&&i!=="1"?null:(u={poemId:n,generationNo:r,generationText:a,status:i},t&&(u.genealogyId=t),e.genealogyNo!==void 0&&e.genealogyNo!==null&&(u.genealogyNo=String(e.genealogyNo)),e.genealogyName!==void 0&&e.genealogyName!==null&&(u.genealogyName=String(e.genealogyName)),e.description!==void 0&&e.description!==null&&(u.description=String(e.description)),T(e.sortOrder)!==void 0&&(u.sortOrder=T(e.sortOrder)),e.remark!==void 0&&e.remark!==null&&(u.remark=String(e.remark)),u)}function Q(e){return!!(e&&(e.canEditContent===!0||e.canManage===!0))}function W(e){var n=Q(e);return{canEditContent:n,listMethod:n?"generationPoemsManagement":"generationPoems"}}function $(e){return Array.isArray(e)?e:e&&Array.isArray(e.rows)?e.rows:[]}function c(e){return String(e==null?"":e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function oe(e){var n=[],t=e.status==="1"?"已停用":"正常",r="";return e.description&&n.push("说明:"+e.description),e.sortOrder!==void 0&&n.push("排序:"+e.sortOrder),n.push("状态:"+t),s&&(r='
"),'

第 '+c(e.generationNo)+" 代:"+c(e.generationText)+"

"+c(n.join(" · "))+"

"+r+"
"}function ue(e){var n=d("[data-generation-list]"),t=$(e).map(V),r=t.filter(function(i){return!i}).length,a=t.filter(Boolean);if(S=Object.create(null),a.forEach(function(i){S[i.poemId]=i}),!!n){if(r){I("[data-generation-list]","error","字辈响应缺少 poemId、generationNo、generationText 或 status,请联系后端补充 DTO。");return}if(!a.length){I("[data-generation-list]","empty","暂无字辈记录");return}n.innerHTML=a.map(oe).join("")}}function k(e){var n=T(e);return n!==void 0&&n>=0?n:void 0}function de(e){var n=e&&e.action,t=T(e&&e.generationNo),r=J(e),a=l(e&&e.oldGenerationText),i=l(e&&e.newGenerationText),u=l(e&&e.oldStatus),x=l(e&&e.newStatus),f;return!t||["create","update","keep","disable"].indexOf(n)===-1||x!=="0"&&x!=="1"||n==="create"&&!i||n!=="create"&&(!r||!a||u!=="0"&&u!=="1")||(n==="update"||n==="keep")&&!i?null:(f={generationNo:t},r&&(f.poemId=r),a&&(f.oldGenerationText=a),i&&(f.newGenerationText=i),u!==void 0&&(f.oldStatus=u),f.newStatus=x,f.action=n,e.warning!==void 0&&e.warning!==null&&(f.warning=String(e.warning)),f)}function K(e){var n,t,r={create:0,update:0,keep:0,disable:0};return!e||!Array.isArray(e.items)||!e.items.length||(n={createCount:k(e.createCount),updateCount:k(e.updateCount),keepCount:k(e.keepCount),disableCount:k(e.disableCount)},Object.keys(n).some(function(a){return n[a]===void 0}))||(t=e.items.map(de),t.some(function(a){return!a}))||(t.forEach(function(a){r[a.action]+=1}),n.createCount!==r.create||n.updateCount!==r.update||n.keepCount!==r.keep||n.disableCount!==r.disable)?null:(n.items=t,n)}function X(e){return e?"确认按当前预览保存字辈,并停用未出现在文本中的后续世代吗?历史记录不会删除。":"确认按当前预览保存字辈吗?"}function fe(e){var n=d("[data-generation-batch-preview]"),t=K(e),r;return n?t?(r="新增 "+(t.createCount===void 0?"-":t.createCount)+" 条 · 更新 "+(t.updateCount===void 0?"-":t.updateCount)+" 条 · 保留 "+(t.keepCount===void 0?"-":t.keepCount)+" 条 · 停用 "+(t.disableCount===void 0?"-":t.disableCount)+" 条",n.innerHTML='

变更预览

'+c(r)+"

"+t.items.map(function(a){var i=a&&a.generationNo,u=a&&a.action,x=a&&a.oldGenerationText,f=a&&a.newGenerationText,te=a&&a.warning,re=a&&a.oldStatus,ae=a&&a.newStatus,L="第 "+(i==null?"-":i)+" 代:"+(x==null?"(新增)":x)+" → "+(f==null?"(无)":f);return u&&(L+=" · "+u),(re!==void 0||ae!==void 0)&&(L+=" · 状态:"+Y(re)+" → "+Y(ae)),te&&(L+=" · 警告:"+te),'

'+c(L)+"

"}).join(""),!0):(I("[data-generation-batch-preview]","error","批量预览响应缺少 items,请联系后端补充 DTO。"),!1):!1}function Y(e){return String(e)==="0"?"正常":String(e)==="1"?"停用":e==null?"未提供":String(e)}function G(e){var n={};return b("[name]",e).forEach(function(t){n[t.name]=t.type==="checkbox"?t.checked:t.value}),n}function g(e){var n=d("[data-generation-batch-status]");n&&(n.textContent=e||"")}function E(e){var n=d("[data-generation-editor]");n&&(n.hidden=!e)}function se(e){b("[data-generation-editor-submit]").forEach(function(n){n.dataset.defaultLabel||(n.dataset.defaultLabel=n.textContent),n.disabled=!!e||!s,n.textContent=e?"正在保存…":n.dataset.defaultLabel,n.setAttribute("aria-busy",e?"true":"false")})}function Z(){var e=!s||y;b("[data-generation-management]").forEach(function(n){n.hidden=!s}),s||E(!1),b("[data-generation-add], [data-generation-batch-action], [data-generation-edit], [data-generation-status]").forEach(function(n){n.disabled=e}),b("[data-generation-management] textarea, [data-generation-management] input, [data-generation-editor] textarea, [data-generation-editor] input, [data-generation-editor] select, [data-generation-editor] button").forEach(function(n){n.disabled=e}),se(y)}function _(e){s=!!e,Z()}function h(e){y=!!e,Z()}function A(){I("[data-generation-list]","forbidden","当前账号没有字辈维护权限"),P="",_(!1),g("当前账号没有字辈维护权限"),p("当前账号没有字辈维护权限","forbidden")}function le(e){if(o.ProfileUI&&o.ProfileUI.syncGenealogyContextLinks){o.ProfileUI.syncGenealogyContextLinks();return}b("[data-genealogy-context-link]").forEach(function(n){var t=n.getAttribute("href"),r;!t||t==="#"||(r=t.split("?")[0],n.href=r+"?genealogyId="+encodeURIComponent(e))})}function O(){var e=F(),n;return e||(n=d("[data-generation-list]"),n&&I("[data-generation-list]","error","请从具体家谱进入字辈管理"),E(!1),p("请从具体家谱进入字辈管理","error"),C("请从具体家谱进入字辈管理")),e}function ee(e){var n=d("[data-generation-form]"),t=e?S[e]:null,r=d("[data-generation-editor-title]");!s||y||!n||e&&!t||(n.reset(),b("[name]",n).forEach(function(a){var i;a.name==="poemId"&&(i=e||""),a.name==="generationNo"&&(i=t?t.generationNo:""),a.name==="generationText"&&(i=t?t.generationText:""),a.name==="description"&&(i=t&&t.description?t.description:""),a.name==="sortOrder"&&(i=t&&t.sortOrder!==void 0?t.sortOrder:""),a.name==="status"&&(i=t?t.status:""),i!==void 0&&(a.value=i)}),r&&(r.textContent=t?"编辑字辈":"新增字辈"),p(""),E(!0),o.location&&o.location.hash!=="#generation-editor"&&(o.location.hash="generation-editor"),d('[name="generationNo"]',n)&&d('[name="generationNo"]',n).focus&&d('[name="generationNo"]',n).focus())}function ne(){var e=d("[data-generation-form]");e&&e.reset(),p(""),E(!1)}async function U(){var e=N(),n,t;if(!v(e)&&(n=O(),!!n)){le(n),I("[data-generation-list]","loading","正在加载字辈记录…");try{t=W(await e.genealogyDetail(n)),_(t.canEditContent),ue(await e[t.listMethod](n))}catch(r){if(v(e,r))return;if(w(r)){A();return}I("[data-generation-list]","error","字辈列表加载失败,请稍后重试。"),C(r.message||"字辈列表加载失败")}}}async function ce(e){var n=N(),t,r,a,i;if(!(y||!s||v(n)||!e)&&(t=O(),!!t)){if(r=j(G(e).poemId),G(e).poemId&&!r){p("字辈编号无效,请返回列表重新编辑。","error");return}if(r&&!S[r]){p("当前字辈不存在或已更新,请返回列表刷新后重试。","error");return}if(a=M(G(e)),i=H(a),i){p(i,"error");return}p("正在保存字辈…","loading"),h(!0);try{r?await n.updateGenerationPoem(t,r,a):await n.createGenerationPoem(t,a),C("字辈已保存"),ne(),await U()}catch(u){if(v(n,u))return;if(w(u)){A();return}p(u.message||"字辈保存失败","error")}finally{h(!1)}}}async function ge(e){var n=N(),t,r=S[e],a;if(!(y||!s||v(n))&&(t=O(),!(!t||!r))){a=M({generationNo:r.generationNo,generationText:r.generationText,description:r.description,sortOrder:r.sortOrder,status:r.status==="1"?"0":"1"}),h(!0);try{await n.updateGenerationPoem(t,e,a),C(r.status==="1"?"字辈已恢复":"字辈已停用"),await U()}catch(i){if(v(n,i))return;if(w(i)){A();return}C(i.message||"字辈状态更新失败")}finally{h(!1)}}}async function ve(){var e=N(),n,t=d("[data-generation-batch-form]"),r,a;if(!(y||!s||v(e)||!t)&&(n=O(),!!n)){if(r=z(G(t)),a=D(r),a){g(a);return}g("正在预览…"),h(!0);try{fe(await e.previewGenerationPoems(n,r))?(P=JSON.stringify(r),g("预览完成,请确认后保存")):(P="",g("预览响应不完整"))}catch(i){if(v(e,i))return;if(w(i)){A();return}P="",g(i.message||"批量预览失败")}finally{h(!1)}}}async function pe(){var e=N(),n,t=d("[data-generation-batch-form]"),r,a;if(!(y||!s||v(e)||!t)&&(n=O(),!!n)){if(r=z(G(t)),a=D(r),a){g(a);return}if(P!==JSON.stringify(r)){g("请先预览当前内容,再保存");return}if(await B(X(r.disableMissing))){g("正在保存…"),h(!0);try{await e.saveGenerationPoems(n,r),P="",g("批量字辈已保存"),await U()}catch(i){if(v(e,i))return;if(w(i)){A();return}g(i.message||"批量保存失败")}finally{h(!1)}}}}function me(){m&&(m.addEventListener("submit",function(e){var n=e.target.closest("[data-generation-form]");n&&(e.preventDefault(),ce(n))}),m.addEventListener("click",function(e){var n=e.target,t=n.closest("[data-generation-batch-action]"),r=n.closest("[data-generation-edit], [data-generation-status]");if(t){e.preventDefault(),t.getAttribute("data-generation-batch-action")==="preview"&&ve(),t.getAttribute("data-generation-batch-action")==="save"&&pe();return}if(n.closest("[data-generation-add]")){e.preventDefault(),ee("");return}if(n.closest("[data-generation-editor-cancel]")){e.preventDefault(),ne();return}r&&(e.preventDefault(),r.hasAttribute("data-generation-edit")&&ee(r.getAttribute("data-generation-edit")),r.hasAttribute("data-generation-status")&&ge(r.getAttribute("data-generation-status")))}))}function ye(){m&&(me(),d("[data-generation-page]")&&U())}return{getCurrentGenealogyId:F,buildGenerationPoemBody:M,buildBatchBody:z,validateGenerationPoemBody:H,validateBatchBody:D,canEditGenerations:Q,splitPoemText:R,normalizeGenerationPoem:V,normalizeGenerationAccess:W,normalizePreview:K,buildBatchConfirmMessage:X,normalizeList:$,shouldRedirectToLogin:q,isForbidden:w,init:ye}}); diff --git a/dist/public/js/growth-pages.js b/dist/public/js/growth-pages.js deleted file mode 100644 index 74bb853..0000000 --- a/dist/public/js/growth-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(a,D){if(typeof module=="object"&&module.exports){module.exports=D(a);return}a.GrowthPages=D(a),a.document&&a.document.addEventListener("DOMContentLoaded",function(){a.GrowthPages.init()})})(typeof globalThis!="undefined"?globalThis:window,function(a){"use strict";function D(e){return a.ProfileUI&&typeof a.ProfileUI.confirmAction=="function"?a.ProfileUI.confirmAction(e):Promise.resolve(!a.confirm||a.confirm(e))}var A=a.MediaDisplay||(typeof require=="function"?require("./media-display.js"):null),P=a.document,C=Object.create(null),T=!1,R=!1;function G(){return a.GenealogyApi&&a.GenealogyApi.defaultClient}function o(e,r){return P?(r||P).querySelector(e):null}function E(e,r){return P?Array.prototype.slice.call((r||P).querySelectorAll(e)):[]}function q(e,r){var t=new URLSearchParams(String(e||"").replace(/^\?/,""));return t.get(r)||""}function s(e){return e==null||e===""||typeof e=="number"&&!Number.isSafeInteger(e)?"":/^[1-9][0-9]*$/.test(String(e))?String(e):""}function H(e){var r=e===void 0&&a.location?a.location.search:e,t;return e===void 0&&a.ProfileUI&&a.ProfileUI.getGenealogyId&&(t=a.ProfileUI.getGenealogyId(),t)?s(t):s(q(r,"genealogyId"))}function L(e){var r=e===void 0&&a.location?a.location.search:e;return s(q(r,"recordId"))}function v(e){var r=String(e==null?"":e).trim();return r||void 0}function ne(e){var r=v(e),t;if(r!==void 0)return t=Number(r),Number.isSafeInteger(t)?t:void 0}function ae(e){var r=v(e);if(r)return/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(r)?r.replace("T"," ")+":00":/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/.test(r)?r.replace("T"," "):r}function ie(e){var r=String(e||"");return/^\d{4}-\d{2}-\d{2}/.test(r)?r.slice(0,10):""}function de(e){var r=String(e||"");return/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}/.test(r)?r.slice(0,16).replace(" ","T"):""}function V(e){var r=e||{},t={recordTitle:String(r.recordTitle||"").trim()},i=v(r.lineagePersonId),n=v(r.recordType),d=v(r.recordContent),l=v(r.recordDate),y=ae(r.remindTime),c=v(r.mediaOssIds),g=v(r.sortOrder),b=v(r.status);return i!==void 0&&(t.lineagePersonId=i),n!==void 0&&(t.recordType=n),d!==void 0&&(t.recordContent=d),l!==void 0&&(t.recordDate=l),y!==void 0&&(t.remindTime=y),c!==void 0&&(t.mediaOssIds=c),g!==void 0&&(t.sortOrder=ne(g),t.sortOrder===void 0&&(t.invalidSortOrder=!0)),b!==void 0&&(t.status=b),t}function j(e){return!e||!e.recordTitle?"请填写记录标题":e.lineagePersonId!==void 0&&!s(e.lineagePersonId)?"请选择有效的世系人物":e.mediaOssIds!==void 0&&!/^[1-9][0-9]*(,[1-9][0-9]*)*$/.test(e.mediaOssIds)?"附件上传结果无效":e.invalidSortOrder||Object.prototype.hasOwnProperty.call(e,"sortOrder")&&!Number.isSafeInteger(e.sortOrder)?"排序值必须是安全整数":e.recordDate!==void 0&&!/^\d{4}-\d{2}-\d{2}$/.test(e.recordDate)?"记录日期格式无效":e.remindTime!==void 0&&!/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(e.remindTime)?"提醒时间格式无效":e.status==="1"?"当前 PC 无法重新读取停用记录,暂不开放停用":e.status!==void 0&&e.status!=="0"?"记录状态只能是 0":""}function u(e){return e==null?"":String(e)}function oe(e){var r;if(!(e==null||e===""))return r=Number(e),Number.isSafeInteger(r)?r:void 0}function O(e){var r=s(e&&e.recordId),t=s(e&&e.genealogyId),i=s(e&&e.appUserId),n=s(e&&e.lineagePersonId),d=String(e&&e.recordTitle||"").trim(),l=u(e&&e.status),y=A?A.normalizeFileList(e&&e.mediaFiles):[],c=oe(e&&e.sortOrder),g;return!r||!t||!d||l!=="0"&&l!=="1"||e.appUserId!==void 0&&e.appUserId!==null&&e.appUserId!==""&&!i||e.lineagePersonId!==void 0&&e.lineagePersonId!==null&&e.lineagePersonId!==""&&!n||e.sortOrder!==void 0&&e.sortOrder!==null&&e.sortOrder!==""&&c===void 0?null:(g={recordId:r,genealogyId:t,genealogyNo:u(e.genealogyNo),genealogyName:u(e.genealogyName),surname:u(e.surname),appUserNickName:u(e.appUserNickName),appUserPhone:u(e.appUserPhone),lineagePersonNo:u(e.lineagePersonNo),lineagePersonName:u(e.lineagePersonName),recordType:u(e.recordType),recordTitle:d,recordContent:u(e.recordContent),recordDate:u(e.recordDate),remindTime:u(e.remindTime),status:l,remark:u(e.remark)},y.length&&(g.mediaFiles=y),i&&(g.appUserId=i),n&&(g.lineagePersonId=n),c!==void 0&&(g.sortOrder=c),g)}function z(e,r){var t=O(e);return!!(t&&t.recordId===s(r))}function le(e){var r=s(e&&(e.personId||e.lineagePersonId)),t=String(e&&(e.name||e.lineagePersonName)||"").trim();return!r||!t?null:{personId:r,name:t,generationName:u(e.generationName)}}function f(e){return u(e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function $(e,r,t){var i=s(r),n=Array.isArray(e)?e.map(le).filter(Boolean):[],d=n.some(function(l){return l.personId===i});return i&&!d&&t&&n.push({personId:i,name:String(t),generationName:""}),''+n.map(function(l){return'"}).join("")}function Q(e){return Array.isArray(e)?e.length:0}function W(e,r){var t="profile-growth.html?genealogyId="+encodeURIComponent(e);return r?t+"&recordId="+encodeURIComponent(r):t}function J(e,r){var t="profile-growth-edit.html?genealogyId="+encodeURIComponent(e);return r?t+"&recordId="+encodeURIComponent(r):t}function K(e){return!!(e&&(e.canEditContent===!0||e.canManage===!0))}function ue(e){var r=[],t='';return e.lineagePersonName&&r.push(e.lineagePersonName),e.recordType&&r.push(e.recordType),e.recordDate&&r.push(e.recordDate),r.length||r.push("未填写日期和关联人物"),T&&(t+='编辑'),'

'+f(e.recordTitle)+"

"+f(r.join(" · "))+'

'+t+"
"}function p(e,r){return"

"+f(e)+""+f(r||"未提供")+"

"}function X(e){var r,t="";return e?(r=Q(e.mediaFiles),T&&(t='
编辑记录
'),(A?A.renderGallery(e.mediaFiles,{label:"成长记录附件"}):"")+'
'+p("标题",e.recordTitle)+p("关联人物",e.lineagePersonName||e.lineagePersonNo)+p("记录类型",e.recordType)+p("记录日期",e.recordDate)+p("提醒时间",e.remindTime)+p("记录内容",e.recordContent)+p("附件",r?r+" 个附件":"未上传")+p("记录人",e.appUserNickName)+p("状态",e.status==="0"?"正常":"停用")+p("备注",e.remark)+"
"+t):_("empty","请选择一条成长记录查看详情")}function x(e){if(a.layui&&a.layui.layer){a.layui.layer.msg(e);return}a.alert&&a.alert(e)}function Y(e,r){var t=r&&(r.status||r.code);return!e||!e.getToken||!e.getToken()||Number(t)===401}function h(e){return Number(e&&(e.status||e.code))===403}function N(e){return h(e)?"forbidden":"error"}function m(e,r){return Y(e,r)?(e&&e.clearToken&&e.clearToken(),a.NavigationUtil.open("login.html"),!0):!1}function Z(){a.ProfileUI&&a.ProfileUI.syncGenealogyContextLinks&&a.ProfileUI.syncGenealogyContextLinks()}function _(e,r){return a.ProfileUI&&a.ProfileUI.renderApiState?a.ProfileUI.renderApiState(e,r):'
'+f(r)+"
"}function I(e,r,t){if(e){if(a.ProfileUI&&a.ProfileUI.setApiState){a.ProfileUI.setApiState(e,r,t);return}e.innerHTML=_(r,t)}}function U(e,r){var t=o("[data-growth-form-status]");if(t){if(!e){t.innerHTML="";return}if(r&&a.ProfileUI&&a.ProfileUI.setApiState){a.ProfileUI.setApiState(t,r,e);return}t.textContent=e}}function k(e,r,t){var i=o("[data-growth-editor-placeholder]");E("[data-growth-editor], [data-growth-editor-action]").forEach(function(n){n.hidden=!e}),i&&(i.hidden=!!e,!e&&r&&(t?I(i,t,r):i.textContent=r))}function B(e){T=!!e,E("[data-growth-create-link]").forEach(function(r){r.hidden=!T})}async function ee(e,r){var t=await e.genealogyDetail(r);return B(K(t)),t}function se(){var e=o("[data-growth-list]"),r=o("[data-growth-detail]"),t="请先选择家谱,再查看或维护成长记录。";I(e,"empty",t),I(r,"empty","当前没有家谱上下文。"),B(!1),k(!1,t,"empty"),U(t,"empty")}function S(){var e=H();return e||se(),e}function F(e,r){var t=o('[data-growth-form] [type="submit"]');R=!!e,E("[data-growth-form] input, [data-growth-form] textarea, [data-growth-form] select, [data-growth-form] button, [data-growth-delete-id]").forEach(function(i){i.disabled=R}),t&&(t.dataset.defaultLabel||(t.dataset.defaultLabel=t.textContent),t.textContent=R&&r?r:t.dataset.defaultLabel,t.setAttribute("aria-busy",R?"true":"false"))}function fe(e){var r=o("[data-growth-list]"),t=Array.isArray(e)?e.map(O):[],i=t.filter(function(d){return!d}).length,n=t.filter(Boolean);return C=Object.create(null),n.forEach(function(d){C[d.recordId]=d}),r?!Array.isArray(e)||i?(I(r,"error","成长记录响应缺少稳定 GrowthRecordVo 字段,请联系后端核对。"),[]):n.length?(r.innerHTML=n.map(ue).join(""),n):(I(r,"empty","暂无成长记录"),[]):n}function re(e){var r=o("[data-growth-detail]");r&&(r.innerHTML=X(e))}async function M(e){var r=G(),t=S(),i;if(!t||!s(e)||m(r))return null;I(o("[data-growth-detail]"),"loading","正在加载成长记录详情…");try{if(i=O(await r.growthRecordDetail(t,e)),!i)throw new Error("成长记录详情响应缺少稳定 GrowthRecordVo 字段");return C[i.recordId]=i,re(i),i}catch(n){return m(r,n)||(I(o("[data-growth-detail]"),N(n),h(n)?"当前账号无权查看该成长记录。":"成长记录详情加载失败,请稍后重试。"),x(h(n)?"当前账号无权查看该成长记录。":n.message||"成长记录详情加载失败")),null}}async function te(){var e=G(),r,t,i;if(!m(e)&&(r=S(),!!r)){Z(),I(o("[data-growth-list]"),"loading","正在加载成长记录…");try{await ee(e,r),t=fe(await e.growthRecords(r)),i=L(),i&&C[i]?await M(i):t.length?await M(t[0].recordId):re(null)}catch(n){if(m(e,n))return;B(!1),I(o("[data-growth-list]"),N(n),h(n)?"当前账号无权查看该家谱的成长记录。":"成长记录加载失败,请稍后重试。"),x(h(n)?"当前账号无权查看该家谱的成长记录。":n.message||"成长记录加载失败")}}}function ce(e){var r={};return E("[name]",e).forEach(function(t){r[t.name]=t.value}),r}function w(e,r){var t=o('[data-growth-form] [name="'+e+'"]');t&&(t.value=r==null?"":String(r))}function ge(e){var r,t;w("lineagePersonId",e.lineagePersonId),w("recordType",e.recordType),w("recordTitle",e.recordTitle),w("recordContent",e.recordContent),w("recordDate",ie(e.recordDate)),w("remindTime",de(e.remindTime)),w("mediaOssIds",e.mediaOssIds),w("sortOrder",e.sortOrder),w("status","0"),r=o('[data-growth-form] [name="recordContent"]'),r&&a.AppRichEditor&&a.AppRichEditor.init&&(t=a.AppRichEditor.init(r),t&&t.editor&&t.editor.setHtml&&t.editor.setHtml(e.recordContent||"")),o("[data-growth-media-status]")&&(o("[data-growth-media-status]").textContent=e.mediaOssIds?"已保留 "+Q(e.mediaOssIds)+" 个附件,可继续选择追加":"未选择文件")}function pe(e,r,t){var i=o("[data-growth-lineage-person]");i&&(i.innerHTML=$(e,r,t))}async function he(){var e=G(),r,t,i,n;if(!m(e)&&(r=S(),!!r)){t=L(),Z(),k(!1,"正在加载成长记录编辑信息…","loading"),U("正在读取家谱权限…","loading");try{if(await ee(e,r),!T)throw{status:403,message:"当前账号无权维护该家谱的成长记录。"};if(i=await Promise.all([e.lineagePersonOptions(r),t?e.growthRecordDetail(r,t):Promise.resolve(null)]),n=t?O(i[1]):null,t&&!n)throw new Error("成长记录详情响应缺少稳定 GrowthRecordVo 字段");pe(i[0],n&&n.lineagePersonId,n&&n.lineagePersonName),n&&(ge(n),o("[data-growth-editor-title]")&&(o("[data-growth-editor-title]").textContent="编辑成长记录")),k(!0),U("")}catch(d){if(m(e,d))return;B(!1),k(!1,h(d)?"当前账号无权维护该家谱的成长记录。":d.message||"成长记录编辑信息加载失败",N(d)),U(h(d)?"当前账号无权维护该家谱的成长记录。":d.message||"成长记录编辑信息加载失败",N(d))}}}async function me(e){var r=G(),t=S(),i=L(),n,d,l,y,c,g;if(!(R||!T||!t||m(r))){if(n=V(ce(e)),d=j(n),d){U(d,"error");return}delete n.invalidSortOrder,F(!0,"正在保存…"),U("正在保存成长记录…","loading");try{if(l=i?await r.updateGrowthRecord(t,i,n):await r.createGrowthRecord(t,n),y=O(l),c=i||y&&y.recordId,!c)throw new Error("保存响应缺少 recordId");if(g=await r.growthRecordDetail(t,c),!z(g,c))throw new Error("保存后重读未返回同一条成长记录");a.NavigationUtil.open(W(t,c))}catch(b){if(m(r,b))return;U(h(b)?"当前账号无权保存该成长记录。":b.message||"成长记录保存失败",N(b))}finally{F(!1)}}}async function ve(e){var r=G(),t=S(),i=C[e];if(!(R||!T||!t||!s(e)||m(r))&&await D("确认删除“"+(i?i.recordTitle:"该成长记录")+"”吗?删除后不可恢复,并会释放附件引用。")){F(!0);try{await r.deleteGrowthRecord(t,e),await te(),x("成长记录已删除")}catch(n){if(m(r,n))return;x(h(n)?"当前账号无权删除该成长记录。":n.message||"成长记录删除失败")}finally{F(!1)}}}function Ie(){var e=o('[data-growth-form] [name="mediaOssIds"]'),r=o("[data-growth-media-status]"),t=o("[data-growth-media-input]");e&&(e.value=""),t&&(t.value=""),r&&(r.textContent="未选择文件")}function we(){P&&(P.addEventListener("click",function(e){var r=e.target.closest("[data-growth-detail-id]"),t=e.target.closest("[data-growth-delete-id]"),i=e.target.closest("[data-growth-clear-media]"),n,d;if(r){e.preventDefault(),n=r.getAttribute("data-growth-detail-id"),d=S(),a.history&&d&&a.history.replaceState(null,"",W(d,n)),M(n).then(function(l){l&&a.ProfileUI&&a.ProfileUI.revealDetail&&a.ProfileUI.revealDetail(o("[data-growth-detail-heading]"))});return}if(t){e.preventDefault(),ve(t.getAttribute("data-growth-delete-id"));return}i&&(e.preventDefault(),Ie())}),P.addEventListener("submit",function(e){var r=e.target.closest("[data-growth-form]");r&&(e.preventDefault(),me(r))}))}function ye(){P&&(we(),o("[data-growth-page]")&&te(),o("[data-growth-edit-page]")&&he())}return{getCurrentGenealogyId:H,getCurrentRecordId:L,buildGrowthRecordBody:V,validateGrowthRecordBody:j,normalizeGrowthRecord:O,matchesSavedRecord:z,canEditRecords:K,renderLineageOptions:$,renderGrowthDetail:X,shouldRedirectToLogin:Y,isForbidden:h,init:ye}}); diff --git a/dist/public/js/help-pages.js b/dist/public/js/help-pages.js deleted file mode 100644 index 18c065c..0000000 --- a/dist/public/js/help-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(i,d){if(typeof module=="object"&&module.exports){module.exports=d(i);return}i.HelpPages=d(i),i.document&&i.document.addEventListener("DOMContentLoaded",function(){i.HelpPages.init()})})(typeof globalThis!="undefined"?globalThis:window,function(i){"use strict";function d(e){return String(e==null?"":e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function A(e){var t;return typeof e=="number"&&!Number.isSafeInteger(e)||e==null?"":(t=String(e).trim(),/^[1-9][0-9]*$/.test(t)?t:"")}function S(e){var t;return e==null||e===""?"0":typeof e=="number"&&(!Number.isSafeInteger(e)||e<0)?"":(t=String(e).trim(),/^(?:0|[1-9][0-9]*)$/.test(t)?t:"")}function m(e){return e==null?"":String(e).trim()}function p(e){var t=e||{},n=A(t.helpId),l=m(t.helpTitle),o=m(t.helpContent),c=S(t.viewCount);return!n||!l||!o||!c||String(t.status)!=="0"?null:{helpId:n,helpCategory:m(t.helpCategory),helpTitle:l,helpContent:o,viewCount:c}}function y(e){var t;return Array.isArray(e)?(t=e.map(p),t.some(function(n){return!n})?[]:t):[]}function w(e){var t=y(e);return t.length?t.map(function(n){var l=[n.helpCategory,"浏览 "+n.viewCount+" 次"].filter(Boolean).join(" · ");return'
'+d(n.helpTitle)+"

"+d(l)+'

'}).join(""):'
当前暂无帮助文章
'}function b(e){var t=p(e),n;return t?(n=[t.helpCategory,"浏览 "+t.viewCount+" 次"].filter(Boolean).join(" · "),'

'+d(n)+"

"+d(t.helpContent).replace(/\r?\n/g,"
")+"

"):'
帮助文章不存在或已停用
'}async function L(e,t){var n=A(t),l;if(!e||typeof e.helpArticleDetail!="function")throw new Error("帮助文章接口不可用");if(!n)throw new Error("帮助文章编号无效");if(l=p(await e.helpArticleDetail(n)),!l)throw new Error("帮助文章详情响应无效");if(l.helpId!==n)throw new Error("详情响应与请求编号不一致");return l}async function C(e){var t,n;if(!e||typeof e.helpArticles!="function")throw new Error("帮助文章接口不可用");if(t=await e.helpArticles(),n=y(t),!Array.isArray(t)||n.length!==t.length)throw new Error("帮助文章列表响应无效");return n}function h(e,t){var n=t&&(t.status||t.code);return!e||!e.getToken||!e.getToken()||Number(n)===401}function v(e){e&&e.clearToken&&e.clearToken(),i.NavigationUtil.open("login.html")}function s(e){return Number(e&&(e.status||e.code))===403}async function T(){var e=i.document&&i.document.querySelector("[data-help-list]"),t=i.document&&i.document.querySelector("[data-help-status]"),n=i.document&&i.document.querySelector("[data-help-refresh]"),l=i.GenealogyApi&&i.GenealogyApi.defaultClient;if(!e)return;if(h(l)){v(l);return}function o(r,a,u){if(r){if(i.ProfileUI&&i.ProfileUI.setApiState&&a){i.ProfileUI.setApiState(r,a,u);return}r.textContent=u||""}}function c(r,a){o(t,r,a)}function E(r){if(!r.length){o(e,"empty","当前暂无帮助文章。");return}e.innerHTML=w(r)}function H(r,a,u){r&&(r.dataset.defaultLabel||(r.dataset.defaultLabel=r.textContent),r.disabled=a,r.textContent=a?u:r.dataset.defaultLabel)}async function I(){try{e.classList.add("is-loading"),o(e,"loading","正在读取帮助文章…"),c("loading","正在读取帮助文章…"),E(await C(l)),c("","")}catch(r){if(h(l,r)){v(l);return}o(e,s(r)?"forbidden":"error",s(r)?"暂无权限查看帮助文章。":"帮助文章读取失败,请稍后重试。"),c(s(r)?"forbidden":"error",s(r)?"暂无权限查看帮助文章。":r.message||"帮助文章读取失败,请稍后重试。")}finally{e.classList.remove("is-loading")}}e.addEventListener("click",async function(r){var a=r.target.closest("[data-help-detail-id]"),u,f;if(!(!a||a.disabled)&&(u=a.getAttribute("data-help-detail-id"),f=e.querySelector('[data-help-detail-panel="'+u+'"]'),!!f)){a.disabled=!0,o(f,"loading","正在读取完整解答…");try{f.innerHTML=b(await L(l,u))}catch(g){if(h(l,g)){v(l);return}o(f,s(g)?"forbidden":"error",s(g)?"暂无权限查看该帮助文章。":g.message||"帮助文章详情读取失败,请稍后重试。")}}}),n&&n.addEventListener("click",async function(){H(n,!0,"正在刷新…");try{await I()}finally{H(n,!1,"正在刷新…")}}),await I()}return{normalizeHelpArticle:p,normalizeHelpArticleList:y,renderHelpArticleList:w,renderHelpArticleDetail:b,loadHelpArticleDetail:L,loadHelpArticles:C,isForbidden:s,shouldRedirectToLogin:h,init:T}}); diff --git a/dist/public/js/join-pages.js b/dist/public/js/join-pages.js deleted file mode 100644 index 4b708c6..0000000 --- a/dist/public/js/join-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(t,I){if(typeof module=="object"&&module.exports){module.exports=I(t);return}t.JoinPages=I(t),t.document&&t.document.addEventListener("DOMContentLoaded",function(){t.JoinPages.init()})})(typeof globalThis!="undefined"?globalThis:window,function(t){"use strict";function I(n){return t.ProfileUI&&typeof t.ProfileUI.confirmAction=="function"?t.ProfileUI.confirmAction(n):Promise.resolve(!t.confirm||t.confirm(n))}var T={0:"待审核",1:"已通过",2:"已拒绝",3:"已撤销"};function f(n){return String(n==null?"":n).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function A(n,e){return t.ProfileUI&&t.ProfileUI.renderApiState?t.ProfileUI.renderApiState(n,e):'
'+f(e)+"
"}function b(n){var e;return typeof n=="number"&&!Number.isSafeInteger(n)||n==null?"":(e=String(n).trim(),/^[1-9][0-9]*$/.test(e)?e:"")}function p(n){var e;return n==null?"":(e=String(n).trim(),e)}function N(n){var e=n||{},a={};return["applicantName","phone","relationDesc","applyReason"].forEach(function(i){var r=p(e[i]);r&&(a[i]=r)}),a}function L(n){var e=n||{},a={applicantName:[50,"申请人姓名不能超过 50 个字符"],phone:[30,"联系电话不能超过 30 个字符"],relationDesc:[100,"关系说明不能超过 100 个字符"],applyReason:[500,"申请理由不能超过 500 个字符"]},i=Object.keys(a),r;for(r=0;ra[i[r]][0])return a[i[r]][1];return""}function G(n){var e=n||{},a=b(e.genealogyId),i=p(e.genealogyName),r=p(e.surname),l=e.status===void 0||e.status===null?"":String(e.status);return!a||!i||!r||l&&l!=="0"?null:{genealogyId:a,genealogyName:i,surname:r,regionFullName:p(e.regionFullName),memberCount:e.memberCount,joinMode:e.joinMode===void 0||e.joinMode===null?"":String(e.joinMode)}}function w(n){var e=n||{},a=b(e.applyId),i=b(e.genealogyId),r=p(e.genealogyName),l=e.status===void 0||e.status===null?"":String(e.status);return!a||!i||!r||!Object.prototype.hasOwnProperty.call(T,l)?null:{applyId:a,genealogyId:i,genealogyName:r,surname:p(e.surname),relationDesc:p(e.relationDesc),applyReason:p(e.applyReason),auditRemark:p(e.auditRemark),auditTime:p(e.auditTime),status:l}}function J(n){var e;return Array.isArray(n)?(e=n.map(w),e.some(function(a){return!a})?[]:e):[]}function q(n){var e;return Array.isArray(n)?(e=n.map(G).filter(Boolean),e.length?e.map(function(a){var i=[a.surname+"氏",a.regionFullName,a.memberCount===void 0||a.memberCount===null?"":a.memberCount+" 位成员"].filter(Boolean).join(" · ");return''}).join(""):A("empty","没有找到可申请加入的家谱")):A("error","家谱列表数据无效,请刷新重试")}function U(n){var e=J(n);return e.length?e.map(function(a){var i=[a.surname?a.surname+"氏":"",a.relationDesc,a.applyReason,a.auditRemark?"审核说明:"+a.auditRemark:"",a.auditTime?"审核时间:"+a.auditTime:""].filter(Boolean).join(" · "),r=a.status==="0"?'':"";return'

'+f(a.genealogyName)+"

"+f(i||"等待申请状态更新")+'

'+T[a.status]+""+r+"
"}).join(""):A("empty","当前没有加入申请")}function g(n,e){var a=e&&(e.status||e.code);return!n||!n.getToken||!n.getToken()||Number(a)===401}function m(n){n&&n.clearToken&&n.clearToken(),t.NavigationUtil.open("login.html")}function v(n){return Number(n&&(n.status||n.code))===403?"forbidden":"error"}function u(n,e,a){var i=t.document&&t.document.querySelector(n);if(i){if(!e){i.innerHTML="";return}if(t.ProfileUI&&t.ProfileUI.setApiState){t.ProfileUI.setApiState(i,a||"error",e);return}i.textContent=e}}function M(n,e,a){if(n){if(t.ProfileUI&&t.ProfileUI.setApiState){t.ProfileUI.setApiState(n,e,a);return}n.innerHTML=A(e,a)}}function k(n,e,a){n&&(n.dataset.defaultLabel||(n.dataset.defaultLabel=n.textContent),n.disabled=!!e,n.textContent=e?a:n.dataset.defaultLabel,n.setAttribute("aria-busy",e?"true":"false"))}function O(n){var e={};return Array.prototype.forEach.call(n.querySelectorAll("[name]"),function(a){e[a.name]=a.value}),e}function C(n){var e=Number(n&&n.joinRemaining);return n&&typeof n.canJoin=="boolean"?n.canJoin:Number.isInteger(e)&&(e===-1||e>0)}async function x(){var n=t.document&&t.document.querySelector("[data-join-apply-page]"),e=t.document&&t.document.querySelector("[data-join-genealogy-search]"),a=t.document&&t.document.querySelector("[data-join-genealogy-options]"),i=t.document&&t.document.querySelector("[data-join-apply-form]"),r=t.document&&t.document.querySelector("[data-join-selected-genealogy]"),l=t.GenealogyApi&&t.GenealogyApi.defaultClient,c="",s=!1;if(!n)return;if(g(l)){m(l);return}function j(){c="",r&&(r.textContent="尚未选择家谱")}async function S(o){var d;j(),u("[data-join-options-status]","正在读取可申请家谱…","loading"),M(a,"loading","正在读取可申请家谱…");try{d=await l.genealogyOptions(o?{keyword:o}:{}),a.innerHTML=q(d),u("[data-join-options-status]","")}catch(y){if(g(l,y)){m(l);return}M(a,v(y),"读取家谱列表失败"),u("[data-join-options-status]",y.message||"读取家谱列表失败,请稍后重试",v(y))}}try{if(!C(await l.genealogyQuota())){u("[data-join-apply-status]","当前没有可用的家谱加入额度","empty"),i.querySelector("[data-join-apply-submit]").disabled=!0;return}}catch(o){if(g(l,o)){m(l);return}u("[data-join-apply-status]",o.message||"无法读取家谱加入额度",v(o)),i.querySelector("[data-join-apply-submit]").disabled=!0;return}e.addEventListener("submit",function(o){o.preventDefault(),S(p(e.querySelector('[name="keyword"]').value))}),a.addEventListener("click",function(o){var d=o.target.closest("[data-join-genealogy-id]");!d||!a.contains(d)||(c=b(d.getAttribute("data-join-genealogy-id")),c&&(r.textContent="已选择:"+d.getAttribute("data-join-genealogy-name"),Array.prototype.forEach.call(a.querySelectorAll("[data-join-genealogy-id]"),function(y){y.classList.toggle("is-selected",y===d)})))}),i.addEventListener("submit",async function(o){var d,y,P,R;if(o.preventDefault(),!s){if(!c){u("[data-join-apply-status]","请先从家谱列表中选择要加入的家谱","error");return}if(d=N(O(i)),y=L(d),y){u("[data-join-apply-status]",y,"error");return}s=!0,k(i.querySelector("[data-join-apply-submit]"),!0,"正在提交…"),u("[data-join-apply-status]","正在提交申请…","loading");try{if(P=w(await l.applyToGenealogy(c,d)),!P)throw new Error("申请响应缺少有效申请信息");if(R=J(await l.myGenealogyJoinApplies()),!R.some(function(h){return h.applyId===P.applyId}))throw new Error("提交后无法读取同一申请");t.NavigationUtil.open("profile-join-family.html")}catch(h){if(g(l,h)){m(l);return}u("[data-join-apply-status]",h.message||"提交加入申请失败,请稍后重试",v(h))}finally{s=!1,k(i.querySelector("[data-join-apply-submit]"),!1,"正在提交…")}}}),await S("")}async function E(){var n=t.document&&t.document.querySelector("[data-my-join-applies-page]"),e=t.document&&t.document.querySelector('[data-join-apply-list="mine"]'),a=t.document&&t.document.querySelector("[data-join-apply-refresh]"),i=t.GenealogyApi&&t.GenealogyApi.defaultClient,r=!1;if(!n)return;if(g(i)){m(i);return}async function l(){var c;u("[data-my-join-applies-status]","正在读取申请记录…","loading");try{return c=await i.myGenealogyJoinApplies(),e.innerHTML=U(c),u("[data-my-join-applies-status]",""),J(c)}catch(s){return g(i,s)?(m(i),[]):(e.innerHTML=A(v(s),"读取加入申请失败"),u("[data-my-join-applies-status]",s.message||"读取加入申请失败,请稍后重试",v(s)),[])}}a.addEventListener("click",function(){l()}),e.addEventListener("click",async function(c){var s=c.target.closest("[data-join-cancel-id]"),j,S;if(!(!s||!e.contains(s)||r)&&(j=b(s.getAttribute("data-join-cancel-id")),!!j&&await I("确认撤销这条加入申请吗?"))){r=!0,s.disabled=!0,u("[data-my-join-applies-status]","正在撤销申请…","loading");try{if(await i.cancelGenealogyJoinApply(j),S=await l(),S.some(function(o){return o.applyId===j&&o.status==="0"}))throw new Error("撤销后申请仍处于待审核状态")}catch(o){if(g(i,o)){m(i);return}u("[data-my-join-applies-status]",o.message||"撤销加入申请失败,请稍后重试",v(o))}finally{r=!1}}}),await l()}async function B(){if(t.document&&t.document.querySelector("[data-join-apply-page]")){await x();return}t.document&&t.document.querySelector("[data-my-join-applies-page]")&&await E()}return{buildJoinApplyBody:N,validateJoinApplyBody:L,normalizeJoinGenealogy:G,normalizeJoinApply:w,normalizeJoinApplies:J,renderJoinGenealogyOptions:q,renderMyJoinApplies:U,canJoinGenealogy:C,initJoinApplyPage:x,initMyJoinAppliesPage:E,init:B}}); diff --git a/dist/public/js/join-review-pages.js b/dist/public/js/join-review-pages.js deleted file mode 100644 index 70490b2..0000000 --- a/dist/public/js/join-review-pages.js +++ /dev/null @@ -1 +0,0 @@ -(function(t,c){if(typeof module=="object"&&module.exports){module.exports=c(t);return}t.JoinReviewPages=c(t),t.document&&t.document.addEventListener("DOMContentLoaded",function(){t.JoinReviewPages.initJoinReviewPage()})})(typeof globalThis!="undefined"?globalThis:window,function(t){"use strict";function c(e){return String(e==null?"":e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function v(e){var n;return typeof e=="number"&&!Number.isSafeInteger(e)||e==null?"":(n=String(e).trim(),/^[1-9][0-9]*$/.test(n)?n:"")}function y(e){return e==null?"":String(e).trim()}function x(e){var n,r;return e===void 0&&t.ProfileUI&&t.ProfileUI.getGenealogyId?(n=t.ProfileUI.getGenealogyId(),v(n)):(r=new URLSearchParams(String(e||"").replace(/^\?/,"")),v(r.get("genealogyId")))}function L(e){var n=e||{},r={status:String(n.status===void 0||n.status===null?"":n.status).trim()},i=y(n.auditRemark);return i&&(r.auditRemark=i),r}function U(e){return!e||["1","2"].indexOf(e.status)<0?"审核状态无效":String(e.auditRemark||"").length>500?"审核说明不能超过 500 个字符":""}function k(e){var n=e||{},r=v(n.applyId),i=v(n.genealogyId);return!r||!i||String(n.status)!=="0"?null:{applyId:r,genealogyId:i,genealogyName:y(n.genealogyName),applicantName:y(n.applicantName)||y(n.appUserNickName)||"未填写姓名",phone:y(n.phone),relationDesc:y(n.relationDesc),applyReason:y(n.applyReason)}}function j(e){var n;return Array.isArray(e)?(n=e.map(k),n.some(function(r){return!r})?[]:n):[]}function T(e,n){var r=j(e);return r.length?r.map(function(i){var u=[i.phone?"申请联系电话:"+i.phone:"",i.relationDesc?"关系:"+i.relationDesc:"",i.applyReason?"理由:"+i.applyReason:""].filter(Boolean).join(" · "),o=n?'
':"";return'

'+c(i.applicantName)+"申请加入"+c(i.genealogyName||"当前家谱")+"

"+c(u||"申请人未补充说明")+"

"+o+"
"}).join(""):""}function h(e,n){var r=n&&(n.status||n.code);return!e||!e.getToken||!e.getToken()||Number(r)===401}function A(e){e&&e.clearToken&&e.clearToken(),t.NavigationUtil.open("login.html")}function s(e){return Number(e&&(e.status||e.code))===403?"forbidden":"error"}function l(e,n){var r=t.document&&t.document.querySelector("[data-join-review-status]");if(r){if(!e){r.innerHTML="";return}if(t.ProfileUI&&t.ProfileUI.setApiState){t.ProfileUI.setApiState(r,n||"error",e);return}r.textContent=e}}function b(e,n){var r=t.document&&t.document.querySelector("[data-join-review-form-status]");if(r){if(!e){r.innerHTML="";return}if(t.ProfileUI&&t.ProfileUI.setApiState){t.ProfileUI.setApiState(r,n||"error",e);return}r.textContent=e}}function w(e,n){var r=t.document&&t.document.querySelector('[data-join-apply-list="pending"]');if(r){if(t.ProfileUI&&t.ProfileUI.setApiState){t.ProfileUI.setApiState(r,n||"error",e);return}r.textContent=e}}function S(e){var n=t.document&&t.document.querySelector("[data-join-review-form]");n&&Array.prototype.slice.call(n.querySelectorAll("textarea, button")).forEach(function(r){r.disabled=!e})}function C(e){var n=t.document&&t.document.querySelector("[data-join-review-submit]");n&&(n.dataset.defaultLabel||(n.dataset.defaultLabel=n.textContent),n.textContent=e?"正在提交…":n.dataset.defaultLabel,n.setAttribute("aria-busy",e?"true":"false"))}function E(e,n){var r=t.document&&t.document.querySelector("[data-join-review-form]"),i=t.document&&t.document.querySelector("[data-join-review-editor]"),u=t.document&&t.document.querySelector("[data-join-review-form-title]"),o=t.document&&t.document.querySelector("[data-join-review-submit]");!r||!i||!e||["1","2"].indexOf(n)<0||(r.reset(),r.querySelector('[name="applyId"]').value=e,r.querySelector('[name="status"]').value=n,u&&(u.textContent=n==="1"?"通过加入申请":"拒绝加入申请"),o&&(o.textContent=n==="1"?"确认通过":"确认拒绝"),b(""),i.hidden=!1,t.location&&t.location.hash!=="#join-review-form"&&(t.location.hash="join-review-form"),r.querySelector('[name="auditRemark"]')&&r.querySelector('[name="auditRemark"]').focus&&r.querySelector('[name="auditRemark"]').focus())}function J(){var e=t.document&&t.document.querySelector("[data-join-review-form]"),n=t.document&&t.document.querySelector("[data-join-review-editor]");e&&e.reset(),n&&(n.hidden=!0),b("")}async function D(){var e=t.document&&t.document.querySelector("[data-join-review-page]"),n=t.document&&t.document.querySelector('[data-join-apply-list="pending"]'),r=t.document&&t.document.querySelector("[data-join-review-refresh]"),i=t.document&&t.document.querySelector("[data-join-review-form]"),u=t.GenealogyApi&&t.GenealogyApi.defaultClient,o=x(),p=!1,I=!1;if(!e)return;if(!o){l("请先从“我的家谱”选择要管理的家谱","forbidden"),S(!1),n.innerHTML='选择家谱后继续';return}if(t.ProfileUI&&t.ProfileUI.syncGenealogyContextLinks&&t.ProfileUI.syncGenealogyContextLinks(),h(u)){A(u);return}async function q(){var a;l("正在读取待审核申请…","loading");try{return a=await u.pendingGenealogyJoinApplies(o),!Array.isArray(a)||a.length&&!j(a).length?(w("待审核申请响应字段不完整,请联系后端核对。","error"),l("待审核申请响应字段不完整,请联系后端核对。","error"),[]):a.length?(n.innerHTML=T(a,p),l(p?"":"当前账号没有审核该家谱申请的权限",p?void 0:"forbidden"),j(a)):(w("当前没有待审核申请","empty"),l(""),[])}catch(d){return h(u,d)?(A(u),[]):(w(s(d)==="forbidden"?"当前账号没有审核该家谱申请的权限。":"读取待审核申请失败,请稍后重试。",s(d)),l(d.message||"读取待审核申请失败,请稍后重试",s(d)),[])}}try{var N=await u.genealogyDetail(o);if(p=N&&N.canManage===!0,!p){w("当前账号没有审核该家谱申请的权限。","forbidden"),l("当前账号没有审核该家谱申请的权限","forbidden"),S(!1);return}}catch(a){if(h(u,a)){A(u);return}l(a.message||"无法核验当前家谱管理权限",s(a)),w(s(a)==="forbidden"?"当前账号没有审核该家谱申请的权限。":"无法读取待审核申请,请稍后重试。",s(a)),S(!1);return}S(!0),r.addEventListener("click",function(){q()}),n.addEventListener("click",function(a){var d=a.target.closest("[data-join-audit-id]"),m,g;!d||!n.contains(d)||I||!p||(m=v(d.getAttribute("data-join-audit-id")),g=d.getAttribute("data-join-audit-status"),!(!m||["1","2"].indexOf(g)<0)&&E(m,g))}),i.addEventListener("submit",async function(a){var d,m,g,R,G,P=!1;if(a.preventDefault(),!(I||!p)){if(d={},Array.prototype.slice.call(i.querySelectorAll("[name]")).forEach(function(f){d[f.name]=f.value}),m=v(d.applyId),g=L(d),!m){b("申请编号无效,请返回列表重新操作。","error");return}if(R=U(g),R){b(R,"error");return}I=!0,S(!1),C(!0),b("正在提交审核结果…","loading");try{if(await u.auditGenealogyJoinApply(o,m,g),G=await q(),G.some(function(f){return f.applyId===m}))throw new Error("审核后申请仍在待审核列表");J()}catch(f){if(h(u,f)){A(u);return}P=s(f)==="forbidden",b(P?"当前账号无权审核该家谱申请。":f.message||"提交审核结果失败,请稍后重试。",s(f)),P&&(p=!1,l("当前账号无权审核该家谱申请。","forbidden"))}finally{I=!1,C(!1),P||S(!0)}}}),t.document.querySelector("[data-join-review-cancel]").addEventListener("click",function(){I||J()}),await q()}return{getCurrentGenealogyId:x,buildJoinAuditBody:L,validateJoinAuditBody:U,normalizePendingApply:k,renderPendingJoinApplies:T,initJoinReviewPage:D}}); diff --git a/dist/public/js/jquery360.js b/dist/public/js/jquery360.js deleted file mode 100644 index 5bd12a8..0000000 --- a/dist/public/js/jquery360.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */(function(H,Je){"use strict";typeof module=="object"&&typeof module.exports=="object"?module.exports=H.document?Je(H,!0):function(pe){if(!pe.document)throw new Error("jQuery requires a window with a document");return Je(pe)}:Je(H)})(typeof window!="undefined"?window:this,function(H,Je){"use strict";var pe=[],Qt=Object.getPrototypeOf,je=pe.slice,Jt=pe.flat?function(e){return pe.flat.call(e)}:function(e){return pe.concat.apply([],e)},At=pe.push,ut=pe.indexOf,lt={},Kt=lt.toString,ct=lt.hasOwnProperty,Zt=ct.toString,Jn=Zt.call(Object),F={},P=function(e){return typeof e=="function"&&typeof e.nodeType!="number"&&typeof e.item!="function"},Me=function(e){return e!=null&&e===e.window},R=H.document,Kn={type:!0,src:!0,nonce:!0,noModule:!0};function en(e,t,n){var r,o,a=(n=n||R).createElement("script");if(a.text=e,t)for(r in Kn)(o=t[r]||t.getAttribute&&t.getAttribute(r))&&a.setAttribute(r,o);n.head.appendChild(a).parentNode.removeChild(a)}function Ie(e){return e==null?e+"":typeof e=="object"||typeof e=="function"?lt[Kt.call(e)]||"object":typeof e}var tn="3.6.0",i=function(e,t){return new i.fn.init(e,t)};function Nt(e){var t=!!e&&"length"in e&&e.length,n=Ie(e);return!P(e)&&!Me(e)&&(n==="array"||t===0||typeof t=="number"&&0+~]|"+L+")"+L+"*"),Mr=new RegExp(L+"|>"),Ir=new RegExp(ce),Wr=new RegExp("^"+W+"$"),wt={ID:new RegExp("^#("+W+")"),CLASS:new RegExp("^\\.("+W+")"),TAG:new RegExp("^("+W+"|[*])"),ATTR:new RegExp("^"+J),PSEUDO:new RegExp("^"+ce),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+Ce+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Fr=/HTML$/i,Br=/^(?:input|select|textarea|button)$/i,$r=/^h\d$/i,st=/^[^{]+\{\s*\[native \w/,_r=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,zt=/[+~]/,Ae=new RegExp("\\\\[\\da-fA-F]{1,6}"+L+"?|\\\\([^\\r\\n\\f])","g"),Ne=function(u,l){var p="0x"+u.slice(1)-65536;return l||(p<0?String.fromCharCode(p+65536):String.fromCharCode(p>>10|55296,1023&p|56320))},_n=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,zn=function(u,l){return l?u==="\0"?"�":u.slice(0,-1)+"\\"+u.charCodeAt(u.length-1).toString(16)+" ":"\\"+u},Un=function(){g()},zr=Et(function(u){return u.disabled===!0&&u.nodeName.toLowerCase()==="fieldset"},{dir:"parentNode",next:"legend"});try{X.apply(oe=ue.call(q.childNodes),q.childNodes),oe[q.childNodes.length].nodeType}catch(u){X={apply:oe.length?function(l,p){ge.apply(l,ue.call(p))}:function(l,p){for(var y=l.length,h=0;l[y++]=p[h++];);l.length=y-1}}}function $(u,l,p,y){var h,b,w,T,C,D,N,j=l&&l.ownerDocument,M=l?l.nodeType:9;if(p=p||[],typeof u!="string"||!u||M!==1&&M!==9&&M!==11)return p;if(!y&&(g(l),l=l||v,k)){if(M!==11&&(C=_r.exec(u)))if(h=C[1]){if(M===9){if(!(w=l.getElementById(h)))return p;if(w.id===h)return p.push(w),p}else if(j&&(w=j.getElementById(h))&&ae(l,w)&&w.id===h)return p.push(w),p}else{if(C[2])return X.apply(p,l.getElementsByTagName(u)),p;if((h=C[3])&&n.getElementsByClassName&&l.getElementsByClassName)return X.apply(p,l.getElementsByClassName(h)),p}if(n.qsa&&!se[u+" "]&&(!S||!S.test(u))&&(M!==1||l.nodeName.toLowerCase()!=="object")){if(N=u,j=l,M===1&&(Mr.test(u)||bt.test(u))){for((j=zt.test(u)&&Xt(l.parentNode)||l)===l&&n.scope||((T=l.getAttribute("id"))?T=T.replace(_n,zn):l.setAttribute("id",T=B)),b=(D=s(u)).length;b--;)D[b]=(T?"#"+T:":scope")+" "+Ct(D[b]);N=D.join(",")}try{return X.apply(p,j.querySelectorAll(N)),p}catch(V){se(u,!0)}finally{T===B&&l.removeAttribute("id")}}}return f(u.replace(fe,"$1"),l,p,y)}function Tt(){var u=[];return function l(p,y){return u.push(p+" ")>r.cacheLength&&delete l[u.shift()],l[p+" "]=y}}function ye(u){return u[B]=!0,u}function xe(u){var l=v.createElement("fieldset");try{return!!u(l)}catch(p){return!1}finally{l.parentNode&&l.parentNode.removeChild(l),l=null}}function Ut(u,l){for(var p=u.split("|"),y=p.length;y--;)r.attrHandle[p[y]]=l}function Xn(u,l){var p=l&&u,y=p&&u.nodeType===1&&l.nodeType===1&&u.sourceIndex-l.sourceIndex;if(y)return y;if(p){for(;p=p.nextSibling;)if(p===l)return-1}return u?1:-1}function Ur(u){return function(l){return l.nodeName.toLowerCase()==="input"&&l.type===u}}function Xr(u){return function(l){var p=l.nodeName.toLowerCase();return(p==="input"||p==="button")&&l.type===u}}function Vn(u){return function(l){return"form"in l?l.parentNode&&l.disabled===!1?"label"in l?"label"in l.parentNode?l.parentNode.disabled===u:l.disabled===u:l.isDisabled===u||l.isDisabled!==!u&&zr(l)===u:l.disabled===u:"label"in l&&l.disabled===u}}function Re(u){return ye(function(l){return l=+l,ye(function(p,y){for(var h,b=u([],p.length,l),w=b.length;w--;)p[h=b[w]]&&(p[h]=!(y[h]=p[h]))})})}function Xt(u){return u&&typeof u.getElementsByTagName!="undefined"&&u}for(t in n=$.support={},a=$.isXML=function(u){var l=u&&u.namespaceURI,p=u&&(u.ownerDocument||u).documentElement;return!Fr.test(l||p&&p.nodeName||"HTML")},g=$.setDocument=function(u){var l,p,y=u?u.ownerDocument||u:q;return y!=v&&y.nodeType===9&&y.documentElement&&(E=(v=y).documentElement,k=!a(v),q!=v&&(p=v.defaultView)&&p.top!==p&&(p.addEventListener?p.addEventListener("unload",Un,!1):p.attachEvent&&p.attachEvent("onunload",Un)),n.scope=xe(function(h){return E.appendChild(h).appendChild(v.createElement("div")),typeof h.querySelectorAll!="undefined"&&!h.querySelectorAll(":scope fieldset div").length}),n.attributes=xe(function(h){return h.className="i",!h.getAttribute("className")}),n.getElementsByTagName=xe(function(h){return h.appendChild(v.createComment("")),!h.getElementsByTagName("*").length}),n.getElementsByClassName=st.test(v.getElementsByClassName),n.getById=xe(function(h){return E.appendChild(h).id=B,!v.getElementsByName||!v.getElementsByName(B).length}),n.getById?(r.filter.ID=function(h){var b=h.replace(Ae,Ne);return function(w){return w.getAttribute("id")===b}},r.find.ID=function(h,b){if(typeof b.getElementById!="undefined"&&k){var w=b.getElementById(h);return w?[w]:[]}}):(r.filter.ID=function(h){var b=h.replace(Ae,Ne);return function(w){var T=typeof w.getAttributeNode!="undefined"&&w.getAttributeNode("id");return T&&T.value===b}},r.find.ID=function(h,b){if(typeof b.getElementById!="undefined"&&k){var w,T,C,D=b.getElementById(h);if(D){if((w=D.getAttributeNode("id"))&&w.value===h)return[D];for(C=b.getElementsByName(h),T=0;D=C[T++];)if((w=D.getAttributeNode("id"))&&w.value===h)return[D]}return[]}}),r.find.TAG=n.getElementsByTagName?function(h,b){return typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName(h):n.qsa?b.querySelectorAll(h):void 0}:function(h,b){var w,T=[],C=0,D=b.getElementsByTagName(h);if(h==="*"){for(;w=D[C++];)w.nodeType===1&&T.push(w);return T}return D},r.find.CLASS=n.getElementsByClassName&&function(h,b){if(typeof b.getElementsByClassName!="undefined"&&k)return b.getElementsByClassName(h)},z=[],S=[],(n.qsa=st.test(v.querySelectorAll))&&(xe(function(h){var b;E.appendChild(h).innerHTML="",h.querySelectorAll("[msallowcapture^='']").length&&S.push("[*^$]="+L+`*(?:''|"")`),h.querySelectorAll("[selected]").length||S.push("\\["+L+"*(?:value|"+Ce+")"),h.querySelectorAll("[id~="+B+"-]").length||S.push("~="),(b=v.createElement("input")).setAttribute("name",""),h.appendChild(b),h.querySelectorAll("[name='']").length||S.push("\\["+L+"*name"+L+"*="+L+`*(?:''|"")`),h.querySelectorAll(":checked").length||S.push(":checked"),h.querySelectorAll("a#"+B+"+*").length||S.push(".#.+[+~]"),h.querySelectorAll("\\\f"),S.push("[\\r\\n\\f]")}),xe(function(h){h.innerHTML="";var b=v.createElement("input");b.setAttribute("type","hidden"),h.appendChild(b).setAttribute("name","D"),h.querySelectorAll("[name=d]").length&&S.push("name"+L+"*[*^$|!~]?="),h.querySelectorAll(":enabled").length!==2&&S.push(":enabled",":disabled"),E.appendChild(h).disabled=!0,h.querySelectorAll(":disabled").length!==2&&S.push(":enabled",":disabled"),h.querySelectorAll("*,:x"),S.push(",.*:")})),(n.matchesSelector=st.test(Q=E.matches||E.webkitMatchesSelector||E.mozMatchesSelector||E.oMatchesSelector||E.msMatchesSelector))&&xe(function(h){n.disconnectedMatch=Q.call(h,"*"),Q.call(h,"[s!='']:x"),z.push("!=",ce)}),S=S.length&&new RegExp(S.join("|")),z=z.length&&new RegExp(z.join("|")),l=st.test(E.compareDocumentPosition),ae=l||st.test(E.contains)?function(h,b){var w=h.nodeType===9?h.documentElement:h,T=b&&b.parentNode;return h===T||!(!T||T.nodeType!==1||!(w.contains?w.contains(T):h.compareDocumentPosition&&16&h.compareDocumentPosition(T)))}:function(h,b){if(b){for(;b=b.parentNode;)if(b===h)return!0}return!1},Pe=l?function(h,b){if(h===b)return x=!0,0;var w=!h.compareDocumentPosition-!b.compareDocumentPosition;return w||(1&(w=(h.ownerDocument||h)==(b.ownerDocument||b)?h.compareDocumentPosition(b):1)||!n.sortDetached&&b.compareDocumentPosition(h)===w?h==v||h.ownerDocument==q&&ae(q,h)?-1:b==v||b.ownerDocument==q&&ae(q,b)?1:m?le(m,h)-le(m,b):0:4&w?-1:1)}:function(h,b){if(h===b)return x=!0,0;var w,T=0,C=h.parentNode,D=b.parentNode,N=[h],j=[b];if(!C||!D)return h==v?-1:b==v?1:C?-1:D?1:m?le(m,h)-le(m,b):0;if(C===D)return Xn(h,b);for(w=h;w=w.parentNode;)N.unshift(w);for(w=b;w=w.parentNode;)j.unshift(w);for(;N[T]===j[T];)T++;return T?Xn(N[T],j[T]):N[T]==q?-1:j[T]==q?1:0}),v},$.matches=function(u,l){return $(u,null,null,l)},$.matchesSelector=function(u,l){if(g(u),n.matchesSelector&&k&&!se[l+" "]&&(!z||!z.test(l))&&(!S||!S.test(l)))try{var p=Q.call(u,l);if(p||n.disconnectedMatch||u.document&&u.document.nodeType!==11)return p}catch(y){se(l,!0)}return 0<$(l,v,null,[u]).length},$.contains=function(u,l){return(u.ownerDocument||u)!=v&&g(u),ae(u,l)},$.attr=function(u,l){(u.ownerDocument||u)!=v&&g(u);var p=r.attrHandle[l.toLowerCase()],y=p&&Ge.call(r.attrHandle,l.toLowerCase())?p(u,l,!k):void 0;return y!==void 0?y:n.attributes||!k?u.getAttribute(l):(y=u.getAttributeNode(l))&&y.specified?y.value:null},$.escape=function(u){return(u+"").replace(_n,zn)},$.error=function(u){throw new Error("Syntax error, unrecognized expression: "+u)},$.uniqueSort=function(u){var l,p=[],y=0,h=0;if(x=!n.detectDuplicates,m=!n.sortStable&&u.slice(0),u.sort(Pe),x){for(;l=u[h++];)l===u[h]&&(y=p.push(h));for(;y--;)u.splice(p[y],1)}return m=null,u},o=$.getText=function(u){var l,p="",y=0,h=u.nodeType;if(h){if(h===1||h===9||h===11){if(typeof u.textContent=="string")return u.textContent;for(u=u.firstChild;u;u=u.nextSibling)p+=o(u)}else if(h===3||h===4)return u.nodeValue}else for(;l=u[y++];)p+=o(l);return p},(r=$.selectors={cacheLength:50,createPseudo:ye,match:wt,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(u){return u[1]=u[1].replace(Ae,Ne),u[3]=(u[3]||u[4]||u[5]||"").replace(Ae,Ne),u[2]==="~="&&(u[3]=" "+u[3]+" "),u.slice(0,4)},CHILD:function(u){return u[1]=u[1].toLowerCase(),u[1].slice(0,3)==="nth"?(u[3]||$.error(u[0]),u[4]=+(u[4]?u[5]+(u[6]||1):2*(u[3]==="even"||u[3]==="odd")),u[5]=+(u[7]+u[8]||u[3]==="odd")):u[3]&&$.error(u[0]),u},PSEUDO:function(u){var l,p=!u[6]&&u[2];return wt.CHILD.test(u[0])?null:(u[3]?u[2]=u[4]||u[5]||"":p&&Ir.test(p)&&(l=s(p,!0))&&(l=p.indexOf(")",p.length-l)-p.length)&&(u[0]=u[0].slice(0,l),u[2]=p.slice(0,l)),u.slice(0,3))}},filter:{TAG:function(u){var l=u.replace(Ae,Ne).toLowerCase();return u==="*"?function(){return!0}:function(p){return p.nodeName&&p.nodeName.toLowerCase()===l}},CLASS:function(u){var l=U[u+" "];return l||(l=new RegExp("(^|"+L+")"+u+"("+L+"|$)"))&&U(u,function(p){return l.test(typeof p.className=="string"&&p.className||typeof p.getAttribute!="undefined"&&p.getAttribute("class")||"")})},ATTR:function(u,l,p){return function(y){var h=$.attr(y,u);return h==null?l==="!=":!l||(h+="",l==="="?h===p:l==="!="?h!==p:l==="^="?p&&h.indexOf(p)===0:l==="*="?p&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function jt(e,t,n){return P(t)?i.grep(e,function(r,o){return!!t.call(r,o,r)!==n}):t.nodeType?i.grep(e,function(r){return r===t!==n}):typeof t!="string"?i.grep(e,function(r){return-1)[^>]*|#([\w-]+))$/;(i.fn.init=function(e,t,n){var r,o;if(!e)return this;if(n=n||an,typeof e=="string"){if(!(r=e[0]==="<"&&e[e.length-1]===">"&&3<=e.length?[null,e,null]:Zn.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof i?t[0]:t,i.merge(this,i.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:R,!0)),on.test(r[1])&&i.isPlainObject(t))for(r in t)P(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(o=R.getElementById(r[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):P(e)?n.ready!==void 0?n.ready(e):e(i):i.makeArray(e,this)}).prototype=i.fn,an=i(R);var er=/^(?:parents|prev(?:Until|All))/,tr={children:!0,contents:!0,next:!0,prev:!0};function sn(e,t){for(;(e=e[t])&&e.nodeType!==1;);return e}i.fn.extend({has:function(e){var t=i(e,this),n=t.length;return this.filter(function(){for(var r=0;r\x20\t\r\n\f]*)/i,hn=/^$|^module$|\/(?:java|ecma)script/i;Le=R.createDocumentFragment().appendChild(R.createElement("div")),(ht=R.createElement("input")).setAttribute("type","radio"),ht.setAttribute("checked","checked"),ht.setAttribute("name","t"),Le.appendChild(ht),F.checkClone=Le.cloneNode(!0).cloneNode(!0).lastChild.checked,Le.innerHTML="",F.noCloneChecked=!!Le.cloneNode(!0).lastChild.defaultValue,Le.innerHTML="",F.option=!!Le.lastChild;var he={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function re(e,t){var n;return n=typeof e.getElementsByTagName!="undefined"?e.getElementsByTagName(t||"*"):typeof e.querySelectorAll!="undefined"?e.querySelectorAll(t||"*"):[],t===void 0||t&&de(e,t)?i.merge([e],n):n}function qt(e,t){for(var n=0,r=e.length;n",""]);var lr=/<|&#?\w+;/;function gn(e,t,n,r,o){for(var a,s,c,f,d,m,x=t.createDocumentFragment(),g=[],v=0,E=e.length;v\s*$/g;function vn(e,t){return de(e,"table")&&de(t.nodeType!==11?t:t.firstChild,"tr")&&i(e).children("tbody")[0]||e}function hr(e){return e.type=(e.getAttribute("type")!==null)+"/"+e.type,e}function gr(e){return(e.type||"").slice(0,5)==="true/"?e.type=e.type.slice(5):e.removeAttribute("type"),e}function yn(e,t){var n,r,o,a,s,c;if(t.nodeType===1){if(A.hasData(e)&&(c=A.get(e).events))for(o in A.remove(t,"handle events"),c)for(n=0,r=c[o].length;n").attr(e.scriptAttrs||{}).prop({charset:e.scriptCharset,src:e.url}).on("load error",n=function(a){t.remove(),n=null,a&&o(a.type==="error"?404:200,a.type)}),R.head.appendChild(t[0])},abort:function(){n&&n()}}});var Bn,$n=[],_t=/(=)\?(?=&|$)|\?\?/;i.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=$n.pop()||i.expando+"_"+Pn.guid++;return this[e]=!0,e}}),i.ajaxPrefilter("json jsonp",function(e,t,n){var r,o,a,s=e.jsonp!==!1&&(_t.test(e.url)?"url":typeof e.data=="string"&&(e.contentType||"").indexOf("application/x-www-form-urlencoded")===0&&_t.test(e.data)&&"data");if(s||e.dataTypes[0]==="jsonp")return r=e.jsonpCallback=P(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,s?e[s]=e[s].replace(_t,"$1"+r):e.jsonp!==!1&&(e.url+=(It.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return a||i.error(r+" was not called"),a[0]},e.dataTypes[0]="json",o=H[r],H[r]=function(){a=arguments},n.always(function(){o===void 0?i(H).removeProp(r):H[r]=o,e[r]&&(e.jsonpCallback=t.jsonpCallback,$n.push(r)),a&&P(o)&&o(a[0]),a=o=void 0}),"script"}),F.createHTMLDocument=((Bn=R.implementation.createHTMLDocument("").body).innerHTML="
",Bn.childNodes.length===2),i.parseHTML=function(e,t,n){return typeof e!="string"?[]:(typeof t=="boolean"&&(n=t,t=!1),t||(F.createHTMLDocument?((r=(t=R.implementation.createHTMLDocument("")).createElement("base")).href=R.location.href,t.head.appendChild(r)):t=R),a=!n&&[],(o=on.exec(e))?[t.createElement(o[1])]:(o=gn([e],t,a),a&&a.length&&i(a).remove(),i.merge([],o.childNodes)));var r,o,a},i.fn.load=function(e,t,n){var r,o,a,s=this,c=e.indexOf(" ");return-1").append(i.parseHTML(f)).find(r):f)}).always(n&&function(f,d){s.each(function(){n.apply(this,a||[f.responseText,d,f])})}),this},i.expr.pseudos.animated=function(e){return i.grep(i.timers,function(t){return e===t.elem}).length},i.offset={setOffset:function(e,t,n){var r,o,a,s,c,f,d=i.css(e,"position"),m=i(e),x={};d==="static"&&(e.style.position="relative"),c=m.offset(),a=i.css(e,"top"),f=i.css(e,"left"),(d==="absolute"||d==="fixed")&&-1<(a+f).indexOf("auto")?(s=(r=m.position()).top,o=r.left):(s=parseFloat(a)||0,o=parseFloat(f)||0),P(t)&&(t=t.call(e,n,i.extend({},c))),t.top!=null&&(x.top=t.top-c.top+s),t.left!=null&&(x.left=t.left-c.left+o),"using"in t?t.using.call(e,x):m.css(x)}},i.fn.extend({offset:function(e){if(arguments.length)return e===void 0?this:this.each(function(o){i.offset.setOffset(this,e,o)});var t,n,r=this[0];return r?r.getClientRects().length?(t=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:t.top+n.pageYOffset,left:t.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],o={top:0,left:0};if(i.css(r,"position")==="fixed")t=r.getBoundingClientRect();else{for(t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;e&&(e===n.body||e===n.documentElement)&&i.css(e,"position")==="static";)e=e.parentNode;e&&e!==r&&e.nodeType===1&&((o=i(e).offset()).top+=i.css(e,"borderTopWidth",!0),o.left+=i.css(e,"borderLeftWidth",!0))}return{top:t.top-o.top-i.css(r,"marginTop",!0),left:t.left-o.left-i.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){for(var e=this.offsetParent;e&&i.css(e,"position")==="static";)e=e.offsetParent;return e||qe})}}),i.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,t){var n=t==="pageYOffset";i.fn[e]=function(r){return Se(this,function(o,a,s){var c;if(Me(o)?c=o:o.nodeType===9&&(c=o.defaultView),s===void 0)return c?c[t]:o[a];c?c.scrollTo(n?c.pageXOffset:s,n?s:c.pageYOffset):o[a]=s},e,r,arguments.length)}}),i.each(["top","left"],function(e,t){i.cssHooks[t]=wn(F.pixelPosition,function(n,r){if(r)return r=nt(n,t),Ht.test(r)?i(n).position()[t]+"px":r})}),i.each({Height:"height",Width:"width"},function(e,t){i.each({padding:"inner"+e,content:t,"":"outer"+e},function(n,r){i.fn[r]=function(o,a){var s=arguments.length&&(n||typeof o!="boolean"),c=n||(o===!0||a===!0?"margin":"border");return Se(this,function(f,d,m){var x;return Me(f)?r.indexOf("outer")===0?f["inner"+e]:f.document.documentElement["client"+e]:f.nodeType===9?(x=f.documentElement,Math.max(f.body["scroll"+e],x["scroll"+e],f.body["offset"+e],x["offset"+e],x["client"+e])):m===void 0?i.css(f,d,c):i.style(f,d,m,c)},t,s?o:void 0,s)}})}),i.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){i.fn[t]=function(n){return this.on(t,n)}}),i.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return arguments.length===1?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),i.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,t){i.fn[t]=function(n,r){return 0.el-scrollbar__bar,.el-scrollbar:focus>.el-scrollbar__bar,.el-scrollbar:hover>.el-scrollbar__bar{opacity:1;transition:opacity .34s ease-out}.el-scrollbar__wrap{overflow:scroll;height:100%}.el-scrollbar__wrap--hidden-default{scrollbar-width:none}.el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:#9093994d;transition:background-color .3s}.el-scrollbar__thumb:hover{background-color:#90939980}.el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;transition:opacity .12s ease-out}.el-scrollbar__bar.is-vertical{width:6px;top:2px}.el-scrollbar__bar.is-vertical>div{width:100%}.el-scrollbar__bar.is-horizontal{height:6px;left:2px}.el-scrollbar__bar.is-horizontal>div{height:100%}.el-cascader{display:inline-block;position:relative;font-size:14px;line-height:40px}.el-cascader:not(.is-disabled):hover .el-input__inner{cursor:pointer;border-color:#c0c4cc}.el-cascader .el-input{cursor:pointer}.el-cascader .el-input .el-input__inner{text-overflow:ellipsis}.el-cascader .el-input .el-input__inner:focus{border-color:#409eff}.el-cascader .el-input .el-icon-arrow-down{display:inline-block;transition:transform .3s;font-size:14px}.el-cascader .el-input .el-icon-arrow-down.is-reverse{transform:rotate(180deg)}.el-cascader .el-input .el-icon-circle-close:hover{color:#909399}.el-cascader .el-input.is-focus .el-input__inner{border-color:#409eff}.el-cascader--medium{font-size:14px;line-height:36px}.el-cascader--small{font-size:13px;line-height:32px}.el-cascader--mini{font-size:12px;line-height:28px}.el-cascader.is-disabled .el-cascader__label{z-index:2;color:#c0c4cc}.el-cascader__dropdown{margin:5px 0;font-size:14px;background:#fff;border:1px solid #e4e7ed;border-radius:4px;box-shadow:0 2px 12px #0000001a}.el-cascader__tags{position:absolute;left:0;right:30px;top:50%;transform:translateY(-50%);display:flex;flex-wrap:wrap;line-height:normal;text-align:left;box-sizing:border-box}.el-cascader__tags .el-tag{display:inline-flex;align-items:center;max-width:100%;margin:2px 0 2px 6px;text-overflow:ellipsis;background:#f0f2f5}.el-cascader__tags .el-tag:not(.is-hit){border-color:transparent}.el-cascader__tags .el-tag>span{flex:1;overflow:hidden;text-overflow:ellipsis}.el-cascader__tags .el-tag .el-icon-close{flex:none;background-color:#c0c4cc;color:#fff}.el-cascader__tags .el-tag .el-icon-close:hover{background-color:#909399}.el-cascader__suggestion-panel{border-radius:4px}.el-cascader__suggestion-list{max-height:204px;margin:0;padding:6px 0;font-size:14px;color:#606266;text-align:center}.el-cascader__suggestion-item{display:flex;justify-content:space-between;align-items:center;height:34px;padding:0 15px;text-align:left;outline:none;cursor:pointer}.el-cascader__suggestion-item:focus,.el-cascader__suggestion-item:hover{background:#f5f7fa}.el-cascader__suggestion-item.is-checked{color:#409eff;font-weight:700}.el-cascader__suggestion-item .el-icon-check{margin-right:15px}.el-cascader__suggestion-item>span{margin-right:10px}.el-cascader__empty-text{margin:10px 0;color:#c0c4cc}.el-cascader__search-input{flex:1;height:24px;min-width:60px;margin:2px 0 2px 15px;padding:0;color:#606266;border:none;outline:none;box-sizing:border-box}.el-cascader__search-input::placeholder{color:#c0c4cc}.el-cascader-panel{display:flex;border-radius:4px;font-size:14px}.el-cascader-panel.is-bordered{border:1px solid #e4e7ed;border-radius:4px}.el-cascader-menu{min-width:180px;box-sizing:border-box;color:#606266;border-right:1px solid #e4e7ed}.el-cascader-menu:last-child{border-right:none}.el-cascader-menu:last-child .el-cascader-node{padding-right:26px}.el-cascader-menu__wrap{height:204px}.el-cascader-menu__list{position:relative;min-height:100%;padding:6px 0;list-style:none;box-sizing:border-box}.el-cascader-menu__hover-zone{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}.el-cascader-menu__empty-text{position:absolute;top:50%;left:46%;transform:translate(-50%,-50%);text-align:center;color:#c0c4cc}.el-cascader-node{position:relative;display:flex;align-items:center;padding:0 25px 0 15px;height:34px;line-height:34px;outline:none}.el-cascader-node.is-selectable.in-active-path{color:#606266}.el-cascader-node.in-active-path,.el-cascader-node.is-active,.el-cascader-node.is-selectable.in-checked-path{color:#409eff;font-weight:700}.el-cascader-node:not(.is-disabled){cursor:pointer}.el-cascader-node:not(.is-disabled):focus,.el-cascader-node:not(.is-disabled):hover{background:#f5f7fa}.el-cascader-node.is-disabled{color:#c0c4cc;cursor:not-allowed}.el-cascader-node__prefix{position:absolute;left:10px}.el-cascader-node__postfix{position:absolute;right:10px}.el-cascader-node__label{flex:1;padding:0 10px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.el-cascader-node>.el-radio{margin-right:0}.el-cascader-node>.el-radio .el-radio__label{padding-left:0}.el-input{position:relative;font-size:14px;display:inline-block;width:100%}.el-input::-webkit-scrollbar{z-index:11;width:6px}.el-input::-webkit-scrollbar:horizontal{height:6px}.el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.el-input::-webkit-scrollbar-corner,.el-input::-webkit-scrollbar-track{background:#fff}.el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.el-input .el-input__clear{color:#c0c4cc;font-size:14px;cursor:pointer;transition:color .2s cubic-bezier(.645,.045,.355,1)}.el-input .el-input__clear:hover{color:#909399}.el-input .el-input__count{height:100%;display:inline-flex;align-items:center;color:#909399;font-size:12px}.el-input .el-input__count .el-input__count-inner{background:#fff;line-height:normal;display:inline-block;padding:0 5px}.el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #dcdfe6;box-sizing:border-box;color:#606266;display:inline-block;font-size:inherit;height:40px;line-height:40px;outline:none;padding:0 15px;transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.el-input__inner::-ms-reveal{display:none}.el-input__inner::placeholder{color:#c0c4cc}.el-input__inner:hover{border-color:#c0c4cc}.el-input__inner:focus{outline:none;border-color:#409eff}.el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#c0c4cc;transition:all .3s;pointer-events:none;display:flex;flex-direction:column;justify-content:center}.el-input__suffix-inner{pointer-events:all}.el-input__prefix{position:absolute;left:5px;top:0;color:#c0c4cc}.el-input__icon,.el-input__prefix{height:100%;text-align:center;transition:all .3s}.el-input__icon{width:25px;line-height:40px}.el-input__icon:after{content:"";height:100%;width:0;display:inline-block;vertical-align:middle}.el-input__validateIcon{pointer-events:none}.el-input.is-active .el-input__inner{outline:none;border-color:#409eff}.el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#e4e7ed;color:#c0c4cc;cursor:not-allowed}.el-input.is-disabled .el-input__inner::placeholder{color:#c0c4cc}.el-input.is-disabled .el-input__icon{cursor:not-allowed}.el-input.is-exceed .el-input__inner{border-color:#f56c6c}.el-input.is-exceed .el-input__suffix .el-input__count{color:#f56c6c}.el-input--suffix .el-input__inner{padding-right:30px}.el-input--prefix .el-input__inner{padding-left:30px}.el-input--medium{font-size:14px}.el-input--medium .el-input__inner{height:36px;line-height:36px}.el-input--medium .el-input__icon{line-height:36px}.el-input--small{font-size:13px}.el-input--small .el-input__inner{height:32px;line-height:32px}.el-input--small .el-input__icon{line-height:32px}.el-input--mini{font-size:12px}.el-input--mini .el-input__inner{height:28px;line-height:28px}.el-input--mini .el-input__icon{line-height:28px}.el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate;border-spacing:0}.el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.el-input-group__append,.el-input-group__prepend{background-color:#f5f7fa;color:#909399;vertical-align:middle;display:table-cell;position:relative;border:1px solid #dcdfe6;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.el-input-group__append:focus,.el-input-group__prepend:focus{outline:none}.el-input-group__append .el-button,.el-input-group__append .el-select,.el-input-group__prepend .el-button,.el-input-group__prepend .el-select{display:inline-block;margin:-10px -20px}.el-input-group__append button.el-button,.el-input-group__append div.el-select .el-input__inner,.el-input-group__append div.el-select:hover .el-input__inner,.el-input-group__prepend button.el-button,.el-input-group__prepend div.el-select .el-input__inner,.el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.el-input-group__append .el-button,.el-input-group__append .el-input,.el-input-group__prepend .el-button,.el-input-group__prepend .el-input{font-size:inherit}.el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.el-input-group__append{border-left:0}.el-input-group--prepend .el-input__inner,.el-input-group__append{border-top-left-radius:0;border-bottom-left-radius:0}.el-input-group--prepend .el-select .el-input.is-focus .el-input__inner{border-color:transparent}.el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.el-input-group--append .el-select .el-input.is-focus .el-input__inner{border-color:transparent}.el-input__inner::-ms-clear{display:none;width:0;height:0}::-webkit-scrollbar{width:0} diff --git a/dist/public/js/lay-module/cascader/cascader.js b/dist/public/js/lay-module/cascader/cascader.js deleted file mode 100644 index 550aa1e..0000000 --- a/dist/public/js/lay-module/cascader/cascader.js +++ /dev/null @@ -1 +0,0 @@ -(function(y){var h=y.document.currentScript.src,p=/(\S*\/)cascader(\.min)?\.js(\?.*)?/i.exec(h);p&&p.length>=2&&(!p[3]||p[3].indexOf("css=true")!==-1)&&layui.link(p[1]+"cascader"+(p[2]||"")+".css")})(window),layui.define(["jquery"],function(y){var h=layui.jquery;function p(e){e=e||window.event,e.stopPropagation?e.stopPropagation():e.cancelBubble=!0}function C(e,i,t,a){this.data=e,this.cascader=i,this.config=i.config,this.props=i.props,this.level=t,this.parentNode=a,this.icons=i.icons,this._checked=0,this._loading=!1,this.nodeId=i.data.nodeId++}C.prototype={constructor:C,get topParentNode(){return!this.parentNode&&this||this.topParentNode},childrenNode:void 0,get loading(){return this._loading},set loading(e){var i=this.$li;if(i){var t=this.icons.right,a=this.icons.loading,s=i.find("i");e?(s.addClass(a),s.removeClass(t)):(s.addClass(t),s.removeClass(a))}return this._loading=e},get label(){return this.data[this.props.label]},get value(){return this.data[this.props.value]},get disabled(){var e=this.props.multiple,i=this.config.maxSize,t=this.cascader.data.checkedNodeIds,a=this.props.disabled,s=this.props.checkStrictly;if(e&&i!==0&&t.length>=i&&t.indexOf(this.nodeId)===-1){if(s)return!0;var r=this.getAllLeafChildren(),n=r.map(function(c){return c.nodeId});if(!n.some(function(c){return t.indexOf(c)!==-1}))return!0}if(s)return this.data[a];var l=this.path;return l.some(function(c){return c.data[a]})},get children(){return this.data[this.props.children]},set children(e){this.data[this.props.children]=e},get leaf(){var e=this.data[this.props.leaf];return typeof e=="boolean"?e:this.children?this.children.length<=0:!0},get activeNodeId(){return this.cascader.data.activeNodeId},get checkedNodeIds(){return this.cascader.data.checkedNodeIds},get path(){var e=this.parentNode;return e?e.path.concat([this]):[this]},get isFiltering(){return this.cascader.isFiltering},get $tag(){var e=this.cascader,i=this.config.showAllLevels,t=this.config.disabled,a=this.disabled,s=this.config.disabledFixed,r=this.getPathLabel(i),n=e.get$tag(r,!t&&(!a||!s)),l=this;return n.find("i").click(function(c){p(c),l.selectedValue(),e.removeTag(l.value,l)}),n},getPathLabel:function(e){var i=this.path,t=this.config.separator,a;return e?a=i.map(function(s){return s.label}).join(t):a=i[i.length-1].label,a},init:function(){var e=this.props.multiple,i=this.props.checkStrictly,t=this.icons.from,a=this.icons.right,s="",r=this.label;this.leaf||(s=a),this.$li=h(''),!e&&!i?this._renderRadio():!e&&i?this._renderRadioCheckStrictly():e&&!i?this._renderMultiple():e&&i&&this._renderMultipleCheckStrictly()},initSuggestionLi:function(){var e=this.getPathLabel(!0);this.$suggestionLi=h('
  • '+e+"
  • "),this._renderFiltering()},bind:function(e){this.init(),e.append(this.$li)},bindSuggestion:function(e){this.initSuggestionLi(),e.append(this.$suggestionLi)},_renderFiltering:function(){var e=this.$suggestionLi,i=this.nodeId,t=this.icons.from,a=this.icons.ok,s=this,r=this.cascader,n=this.props.multiple,l='';e.click(function(c){p(c),s.selectedValue(),n?s.checkedNodeIds.indexOf(i)===-1?(e.removeClass("is-checked"),e.find(".el-icon-check").remove()):(e.addClass("is-checked"),e.append(l)):r.close()}),(n&&s.checkedNodeIds.indexOf(i)!==-1||!n&&s.activeNodeId===i)&&(e.addClass("is-checked"),e.append(l))},_renderRadio:function(){var e=this.$li,i=this.nodeId,t=this.icons.from,a=this.icons.ok,s=this.level,r=this.leaf,n=this,l=this.cascader,c=this.cascader.data.activeNode,o=this.parentNode;if(n.activeNodeId&&c.path.some(function(d){return d.nodeId===i})&&(n.activeNodeId===i&&e.prepend(''),e.addClass("is-active"),e.addClass("in-checked-path")),this.disabled){e.addClass("is-disabled");return}e.addClass("is-selectable"),o&&(o.$li.siblings().removeClass("in-active-path"),o.$li.addClass("in-active-path")),this._liClick(function(d){p(d);var f=n.childrenNode;r&&d.type==="click"&&(n.selectedValue(),l.close()),l._appendMenu(f,s+1,n)})},_renderRadioCheckStrictly:function(){var e=this.$li,i=this.nodeId,t=this.level,a=this.leaf,s=this,r=this.cascader,n=r.data.activeNode,l=this.parentNode;e.addClass("is-selectable");var c=h('');if(this.$radio=c,e.prepend(c),l&&(l.$li.siblings().removeClass("in-active-path"),l.$li.addClass("in-active-path")),this._liClick(function(o){p(o);var d=s.childrenNode;!s.disabled&&a&&o.type==="click"&&s.selectedValue(),r._appendMenu(d,t+1,s)}),s.activeNodeId&&n.path.some(function(o){return o.nodeId===i})&&(s.activeNodeId===i&&c.find(".el-radio__input").addClass("is-checked"),e.addClass("is-active"),e.addClass("in-checked-path")),this.disabled){c.addClass("is-disabled"),c.find(".el-radio__input").addClass("is-disabled");return}c.click(function(o){o.preventDefault(),!a&&s.selectedValue()})},_renderMultiple:function(){var e=this.$li,i=this.level,t=this.leaf,a=this,s=this.cascader,r=this._checked,n=this.parentNode;e.addClass("el-cascader-node");var l=h('');if(this.$checked=l,e.prepend(l),r===1?this.$checked.find(".el-checkbox__input").addClass("is-checked"):r===2&&this.$checked.find(".el-checkbox__input").addClass("is-indeterminate"),n&&(n.$li.siblings().removeClass("in-active-path"),n.$li.addClass("in-active-path")),this._liClick(function(c){p(c);var o=a.childrenNode;!a.disabled&&t&&c.type==="click"&&a.selectedValue(),s._appendMenu(o,i+1,a)}),this.disabled){e.addClass("is-disabled"),l.addClass("is-disabled"),l.find(".el-checkbox__input").addClass("is-disabled");return}l.click(function(c){if(c.preventDefault(),!t){var o=a.childrenNode;a.selectedValue(),s._appendMenu(o,i+1,a)}})},_renderMultipleCheckStrictly:function(){var e=this.$li,i=this.level,t=this.leaf,a=this,s=this.cascader,r=s.data.checkedNodeIds,n=s.data.checkedNodes,l=this.nodeId,c=this.parentNode;e.addClass("el-cascader-node is-selectable");var o=h('');this.$checked=o,e.prepend(o);var d=n.some(function(f){return f.path.some(function(u){return u.nodeId===l})});if(d&&(e.addClass("in-checked-path"),r.indexOf(l)!==-1&&this.$checked.find(".el-checkbox__input").addClass("is-checked")),c&&(c.$li.siblings().removeClass("in-active-path"),c.$li.addClass("in-active-path")),this._liClick(function(f){p(f);var u=a.childrenNode;!a.disabled&&t&&f.type==="click"&&a.selectedValue(),s._appendMenu(u,i+1,a)}),this.disabled){o.addClass("is-disabled"),o.find(".el-checkbox__input").addClass("is-disabled");return}o.click(function(f){if(f.preventDefault(),!t){a.selectedValue();var u=a.childrenNode;s._appendMenu(u,i+1,a)}})},transferParent:function(e,i,t){if(t||(t=this),this!==t||i){var a=e&&e(this);if(a===!1)return}this.parentNode&&this.parentNode.transferParent(e,i,t)},transferChildren:function(e,i,t){if(t||(t=this),this!==t||i){var a=e&&e(this);if(a===!1)return}var s=this.getChildren();if(s&&s.length>0)for(var r in s)s[r].transferChildren(e,i,t)},selectedValue:function(){var e=this.nodeId,i=this.cascader,t=this.props.multiple,a=this.props.checkStrictly,s=this.leaf;if(!t&&(s||a))i._setActiveValue(e,this);else if(t){var r=i.data.checkedNodeIds,n=i.data.checkedNodes,l=this.config.disabledFixed,c;if(a){var o=r.indexOf(e);o===-1?c=n.concat([this]):(c=n.concat(),c.splice(o,1))}else{var d=this.getAllLeafChildren(),f;if(this._checked!==1&&l?f=this._getMultipleChecked(d):f=this._checked,f===1)c=n.filter(function(g){return!d.some(function(b){return g.nodeId===b.nodeId})});else{var u=d.filter(function(g){return r.indexOf(g.nodeId)===-1});c=n.concat(u)}}var v=c.map(function(g){return g.nodeId});i._setCheckedValue(v,c)}},_liLoad:function(e,i){var t=this.leaf,a=this.props.lazy,s=this.props.lazyLoad,r=this.children,n=this,l=this.cascader,c=this.level,o=this.props.multiple,d=this.props.checkStrictly;!t&&(!r||r.length===0)&&a?n.loading||(n.loading=!0,s(n,function(f){n.loading=!1,n.setChildren(l.initNodes(f,c+1,n)),n.children=f,i&&i(e),o&&!d&&n.transferParent(function(u){u.syncStyle()},!0)})):i&&i(e)},_liClick:function(e){var i=this.leaf,t=this.$li,a=this;function s(r){a._liLoad(r,e)}(this.props.expandTrigger==="click"||i)&&t.click(s),this.props.expandTrigger==="hover"&&t.mouseenter(s)},setChildren:function(e){this.childrenNode=e},getChildren:function(){return this.childrenNode},syncStyle:function(){var e=this.props.multiple,i=this.props.checkStrictly;e?i?this._sync.syncMultipleCheckStrictly(this):this._sync.syncMultiple(this):i?this._sync.syncRadioCheckStrictly(this):this._sync.syncRadio(this)},_sync:{syncRadio:function(e){var i=e.$li,t=e.icons.from,a=e.icons.ok,s=e.props.multiple,r=e.props.checkStrictly,n=e.nodeId;if(!(!i||s||r)){var l=e.cascader.data.activeNode;if(e.activeNodeId===n){var c=i.find("."+a);c.length===0&&i.prepend('')}else i.find("."+a).remove();l&&l.path.some(function(o){return o.nodeId===n})?(i.addClass("is-active"),i.addClass("in-checked-path")):(i.removeClass("is-active"),i.removeClass("in-checked-path"))}},syncRadioCheckStrictly:function(e){var i=e.$li,t=e.props.checkStrictly,a=e.props.multiple;if(!(!i||a||!t)){var s=e.$radio,r=e.cascader.data.activeNode,n=e.nodeId;e.activeNodeId===n?s.find(".el-radio__input").addClass("is-checked"):s.find(".el-radio__input").removeClass("is-checked"),r&&r.path.some(function(l){return l.nodeId===n})?(i.addClass("is-active"),i.addClass("in-checked-path")):(i.removeClass("is-active"),i.removeClass("in-checked-path"))}},syncMultiple:function(e){var i=e.$li,t=e.props.checkStrictly,a=e.props.multiple,s=e.config.disabledFixed;if(!(!a||t)){var r=e.getAllLeafChildren(s),n=e._getMultipleChecked(r);if(e._checked=n,!!i){var l=e.$checked.find(".el-checkbox__input");n===0?(l.removeClass("is-checked"),l.removeClass("is-indeterminate")):n===1?(l.removeClass("is-indeterminate"),l.addClass("is-checked")):n===2&&(l.removeClass("is-checked"),l.addClass("is-indeterminate"))}}},syncMultipleCheckStrictly:function(e){var i=e.$li,t=e.props.checkStrictly,a=e.props.multiple;if(!(!i||!a||!t)){var s=e.cascader.data.checkedNodes,r=e.checkedNodeIds,n=e.nodeId,l=s.some(function(o){return o.path.some(function(d){return d.nodeId===n})}),c=e.$checked.find(".el-checkbox__input");r.some(function(o){return o===n})?c.addClass("is-checked"):c.removeClass("is-checked"),l?i.addClass("in-checked-path"):i.removeClass("in-checked-path")}}},getAllLeafChildren:function(e){var i=this.leaf;if(i)return[this];var t=[];return this.transferChildren(function(a){if(a.disabled&&!e)return!1;a.leaf&&t.push(a)}),t},expandPanel:function(){var e=this.path,i=this.cascader;e.forEach(function(t,a,s){if(a!==s.length-1){var r=t.childrenNode;i._appendMenu(r,t.level+1,t.parentNode)}})},_getMultipleChecked:function(e){for(var i=this.cascader,t=i.data.checkedNodeIds,a=e.map(function(l){return l.nodeId}),s=0,r=0;r0&&s!==1?s=2:s=1:s=s===1?2:0}return s}};function $(e){this.config=h.extend(!0,{elem:"",value:null,options:[],empty:"暂无数据",placeholder:"请选择",disabled:!1,clearable:!1,showAllLevels:!0,collapseTags:!1,minCollapseTagsNumber:1,separator:" / ",filterable:!1,filterMethod:function(i,t){return i.path.some(function(a){return a.label.indexOf(t)!==-1})},debounce:300,beforeFilter:function(i){return!0},popperClass:"",extendClass:!1,extendStyle:!1,disabledFixed:!1,maxSize:0,props:{strictMode:!1,expandTrigger:"click",multiple:!1,checkStrictly:!1,lazy:!1,lazyLoad:function(i,t){},value:"value",label:"label",children:"children",disabled:"disabled",leaf:"leaf"}},e),this.data={nodeId:1,nodes:[],menuData:[],activeNodeId:null,activeNode:null,checkedNodeIds:[],checkedNodes:[]},this.showPanel=!1,this.event={change:[],open:[],close:[]},this.filtering=!1,this._init(),this.closeEventId=0,this._maxSizeMode=null}$.prototype={constructor:$,get props(){return this.config.props},get isFiltering(){return this.filtering},set isFiltering(e){if(this.filtering!==e){this.filtering=!!e;var i=this.$panel;this.filtering?(i.find(".el-cascader-panel").hide(),i.find(".el-cascader__suggestion-panel").show()):(i.find(".el-cascader-panel").show(),i.find(".el-cascader__suggestion-panel").hide(),this.$tagsInput&&this.$tagsInput.val(""))}},set maxSizeMode(e){this._maxSizeMode!==e&&(this._maxSizeMode=e,this.refreshMenu())},icons:{from:"layui-icon",down:"layui-icon-down",close:"layui-icon-close",right:"layui-icon-right",ok:"layui-icon-ok",loading:"layui-icon-loading-1 layui-anim layui-anim-rotate layui-anim-loop"},_init:function(){this._checkConfig(),this._initInput(),this._initPanel(),this.setOptions(this.config.options);var e=this;h(window).scroll(function(){e._resetXY()}),h(window).resize(function(){e._resetXY()}),this.$div.click(function(i){if(!e.config.disabled){var t=e.showPanel;t?e.close():e.open()}})},_checkConfig:function(){var e=this.config.elem;if(!e||h(e).length===0)throw new Error("缺少elem节点选择器");var i=this.config.maxSize;if(typeof i!="number"||i<0)throw new Error("maxSize应是一个大于等于0的有效的number值");if(!Array.isArray(this.config.options))throw new Error("options不是一个有效的数组")},_initRoot:function(){var e=this.props.lazy,i=this.props.lazyLoad,t=this,a=this.data.nodes;a.length>0||!e?this._appendMenu(a,0):e&&(this._appendMenu(a,0),i({root:!0,level:0},function(s){t.data.nodes=t.initNodes(s,0,null),t._appendMenu(t.data.nodes,0)}))},setOptions:function(e){this.config.options=e,this.data.nodes=this.initNodes(e,0,null),this._initRoot(),this.setValue(this.config.value)},_resetXY:function(){var e=this.$div,i=e.offset(),t=this.$panel;if(t){var a=window.innerHeight,s=window.innerWidth,r=t.height(),n=t.width(),l=e.height(),c=e[0].getBoundingClientRect(),o=t.find(".popper__arrow"),d=Math.min(s-c.x-n-5,0),f=a-(c.top+l);if(fr+20)t.attr("x-placement","top-start"),t.css({top:i.top-20-r+"px",left:i.left+d+"px"});else{t.attr("x-placement","bottom-start");var u=Math.max(r-(a-c.y-l-15),0);t.css({top:i.top+l-u+"px",left:i.left+d+"px"})}o.css("left",35-d+"px")}},get $menus(){return this.$panel&&this.$panel.find(".el-cascader-panel .el-cascader-menu")},_initInput:function(){var e=h(this.config.elem),i=this;this.config.value===null&&e.attr("value")&&(this.config.value=e.attr("value"));var t=this.config.placeholder,a=this.icons.from,s=this.icons.down,r=this.props.multiple,n=this.config.extendClass,l=this.config.extendStyle;if(this.$div=h('
    '),l){var c=e.attr("style");c&&this.$div.attr("style",c)}if(n){var o=e.attr("class");o&&o.split(" ").forEach(function(f){i.$div.addClass(f)})}this.$input=h('
    '),this.$div.append(this.$input),this.$inputRow=this.$input.find(".el-input__inner"),r&&(this.$tags=h('
    '),this.$div.append(this.$tags));var d=this._initHideElement(e);d.after(this.$div),this.$icon=this.$input.find("i"),this._initFilterableInputEvent(),this.disabled(this.config.disabled)},_initHideElement:function(e){var i=e.prop("tagName").toLowerCase();if(i==="input")return e.hide(),e.attr("type","hidden"),this.$ec=e,e;var t=e[0].attributes,a=h(""),s=Object.keys(t);for(var r in s){var n=t[r];a.attr(n.name,n.value)}return a.hide(),a.attr("type","hidden"),this.$ec=a,e.before(a),e.remove(),a},_initFilterableInputEvent:function(){var e=this.config.filterable;if(!e)return;var i,t=this.props.multiple,a=this.config.debounce,s=this.config.placeholder,r=this.config.beforeFilter,n=this.config.filterMethod,l=this.props.checkStrictly,c=this;function o(u){var v=this;i&&clearTimeout(i),i=setTimeout(function(){i=null;var g=h(v).val();if(!g){c.isFiltering=!1;return}if(c.open(),typeof r=="function"&&r(g)){c.isFiltering=!0;var b=c.getNodes(),m=b.filter(function(_){var k;return l?k=_.disabled:k=_.path.some(function(N){return N.disabled}),!!((_.leaf||l)&&!k&&typeof n=="function"&&n(_,g))});c._setSuggestionMenu(m)}},a)}if(t){this.$tagsInput=h('');var d=this.$tagsInput;this.$tags.append(d),d.on("keydown",o),d.click(function(u){c.isFiltering&&p(u)})}else{var f=this.$inputRow;f.removeAttr("readonly"),f.on("keydown",o),f.click(function(u){c.isFiltering&&p(u)})}},_initPanel:function(){var e=this.$panel,i=this.config.popperClass||"";e||(this.$panel=h(''),e=this.$panel,e.appendTo("body"),e.click(function(t){p(t)}),this._initSuggestionPanel())},_appendMenu:function(e,i,t,a){if(this._removeMenu(i),!(t&&t.leaf)){var s=this.data.menuData,r=h('');this.$panel.find(".el-cascader-panel").append(r),this._appendLi(r,e);var n={nodes:e,level:i,parentNode:t,scrollbar:{top:0,left:0}};a&&(n.scrollbar=a.scrollbar),this._initScrollbar(r,n),this._resetXY(),s.push(n)}},_removeMenu:function(e){var i=e-1;i!==-1?this.$panel.find(".el-cascader-panel .el-cascader-menu:gt("+i+")").remove():this.$panel.find(".el-cascader-panel .el-cascader-menu").remove();var t=this.data.menuData;t.length>e&&t.splice(e,t.length-e)},_appendLi:function(e,i){var t=e.find(".el-cascader-menu__list");if(!i||i.length===0){var a=this.config.empty;t.append('
    '+a+"
    ");return}h.each(i,function(s,r){r.bind(t)})},refreshMenu:function(){var e=this.data.menuData.concat([]),i=this;e.forEach(function(t){i._appendMenu(t.nodes,t.level,t.parentNode,t)})},_initSuggestionPanel:function(){var e=this.config.filterable;if(e){var i=this.$suggestionPanel;i||(this.$suggestionPanel=h(''),i=this.$suggestionPanel,this.$panel.find(".popper__arrow").before(i),i.click(function(t){p(t)}))}},_setSuggestionMenu:function(e){var i=this.$suggestionPanel,t=i.find(".el-cascader__suggestion-list");if(t.empty(),i.find(".el-scrollbar__bar").remove(),!e||e.length===0){t.append('
  • 无匹配数据
  • ');return}h.each(e,function(a,s){s.bindSuggestion(t)}),this._initScrollbar(i,{scrollbar:{top:0,left:0}}),this._resetXY()},initNodes:function(e,i,t){var a=[];for(var s in e){var r=e[s],n=new C(r,this,i,t);n.value===null||n.value===void 0||(a.push(n),n.children&&n.children.length>0&&n.setChildren(this.initNodes(n.children,i+1,n)))}return a},_setActiveValue:function(e,i){if(this.data.activeNodeId!==e){var t=this.data.activeNode;this.data.activeNodeId=e,this.data.activeNode=i,t&&t.transferParent(function(a){a.syncStyle()},!0),i&&i.transferParent(function(a){a.syncStyle()},!0),this.change(i&&i.value,i),e!==null&&this._setClear()}},_setCheckedValue:function(e,i){var t=this.data.checkedNodes,a=this.config.maxSize,s;e.length>0&&a!==0&&e.length>=a?(e=e.slice(0,a),i=i.slice(0,a),s=!0):s=!1,this.data.checkedNodeIds=e||[],this.data.checkedNodes=i||[];var r=[],n=[];t.forEach(function(l){l.path.forEach(function(c){n.indexOf(c.nodeId)===-1&&(r.push(c),n.push(c.nodeId))})}),i.forEach(function(l){l.path.forEach(function(c){n.indexOf(c.nodeId)===-1&&(r.push(c),n.push(c.nodeId))})}),r.forEach(function(l){l.syncStyle()}),this.change(i.map(function(l){return l.value}),i),this._setClear(),this.maxSizeMode=s},setValue:function(e){if((this.data.activeNodeId||this.data.checkedNodeIds.length>0)&&this.clearCheckedNodes(),!!e){var i=this.props.strictMode;if(i&&!Array.isArray(e))throw new Error("严格模式下,value必须是一个包含父子节点数组结构.");var t=this.getNodes(this.data.nodes),a=this.props.checkStrictly,s=this.props.multiple,r=this.config.disabledFixed;if(s){var n=t.filter(function(v){return(a||v.leaf)&&(!v.disabled||r)?i?e.some(function(g){if(!Array.isArray(g))throw new Error("多选严格模式下,value必须是一个二维数组结构.");var b=v.path;return g.length===b.length&&g.every(function(m,_){return b[_].value===m})}):e.indexOf(v.value)!==-1:!1}),l=n.map(function(v){return v.nodeId});if(this._setCheckedValue(l,n),n.length>0){var c=n[0];c.expandPanel()}}else for(var o=0;o
    ');e.append(t);var a=h(t[1]).find(".el-scrollbar__thumb"),s=h(t[0]).find(".el-scrollbar__thumb"),r=e.find(".el-scrollbar__wrap"),n=this.$panel,l=e.find("li"),c=Math.max(n.height(),e.height()),o=(c-6)/(l.height()*l.length),d=n.width()/l.width();function f(u,v){o<1&&(a.css("height",o*100+"%"),a.css("transform","translateY("+u/e.height()*100+"%)")),d<1&&(s.css("width",d*100+"%"),s.css("transform","translateY("+v/e.width()*100+"%)"))}a.mousedown(function(u){u.stopImmediatePropagation(),p(u);var v=function(){return!1};h(document).bind("selectstart",v);var g=u.clientY,b=r.scrollTop(),m=function(_){_.stopImmediatePropagation();var k=b+(_.clientY-g)/o;r.scrollTop(k)};h(document).bind("mousemove",m),h(document).one("mouseup",function(_){p(_),_.stopImmediatePropagation(),h(document).off("mousemove",m),h(document).off("selectstart",v)})}),r.scroll(function(){var u=h(this);i.scrollbar.top=u.scrollTop(),i.scrollbar.left=u.scrollLeft(),f(i.scrollbar.top,i.scrollbar.left)}),r.scrollTop(i.scrollbar.top),f(i.scrollbar.top,i.scrollbar.left)},_fillingPath:function(){var e=this.props.multiple,i=this.config.showAllLevels,t=this.config.separator,a=this.config.collapseTags,s=this.$inputRow,r=this.config.placeholder,n=this;if(e){this.$tags.find(".el-tag").remove();var o=this.$tagsInput;s.css("height","");var d=this.data.checkedNodes,f=Math.max(this.config.minCollapseTagsNumber,1);if(d.length>0){var u=[],v=d;a&&(v=d.slice(0,Math.min(d.length,f))),v.forEach(function(m){u.push(m.$tag)}),a&&d.length>f&&u.push(n.get$tag("+ "+(d.length-f),!1)),u.forEach(function(m){o?o.before(m):n.$tags.append(m)})}var g=n.$tags.height(),b=s.height();g>b&&s.css("height",g+4+"px"),this._resetXY(),d.length>0?(s.removeAttr("placeholder"),o&&o.removeAttr("placeholder",r)):(s.attr("placeholder",r),o&&o.attr("placeholder",r))}else{var l=this.data.activeNode,c=l&&l.path||[];i?this._$inputRowSetValue(c.map(function(m){return m.label}).join(t)):this._$inputRowSetValue(l&&l.label||"")}},_$inputRowSetValue:function(e){e=e||"";var i=this.$inputRow;i.attr("value",e),i.val(e)},get$tag:function(e,i){var t=this.icons.from,a=this.icons.close,s=i?'':"";return h(''+e+""+s+"")},_setClear:function(){var e=this;function i(){e.$icon.removeClass(e.icons.down),e.$icon.addClass(e.icons.close)}function t(){e.$icon.removeClass(e.icons.close),e.$icon.addClass(e.icons.down)}e.$div.mouseenter(function(){i()}),e.$div.mouseleave(function(){t()}),e.$icon.off("click");var a=this.props.multiple,s;a?s=this.data.checkedNodeIds.length>0:s=!!this.data.activeNodeId,s&&!this.config.disabled&&this.config.clearable?e.$icon.one("click",function(r){p(r),e.close(),e.clearCheckedNodes(),t(),e.$icon.off("mouseenter"),e.$div.off("mouseenter"),e.$div.off("mouseleave")}):(t(),e.$icon.off("mouseenter"),e.$div.off("mouseenter"),e.$div.off("mouseleave"))},disabled:function(e){this.config.disabled=!!e,this.config.disabled?(this.$div.addClass("is-disabled"),this.$div.find(".el-input--suffix").addClass("is-disabled"),this.$inputRow.attr("disabled","disabled"),this.$tagsInput&&this.$tagsInput.attr("disabled","disabled").hide()):(this.$div.removeClass("is-disabled"),this.$div.find(".el-input--suffix").removeClass("is-disabled"),this.$inputRow.removeAttr("disabled"),this.$tagsInput&&this.$tagsInput.removeAttr("disabled").show()),this._setClear(),this._fillingPath()},change:function(e,i){var t=this.props.multiple;t?e&&e.length>0?this.$ec.attr("value",JSON.stringify(e)):this.$ec.removeAttr("value"):this.$ec.attr("value",e||""),this._fillingPath(),this.event.change.forEach(function(a){typeof a=="function"&&a(e,i)})},close:function(e){if(this.showPanel&&(!e||this.closeEventId===e)){this.showPanel=!1,this.$div.find(".layui-icon-down").removeClass("is-reverse"),this.$panel.slideUp(100),this.visibleChange(!1),this.$input.removeClass("is-focus");var i=this.config.filterable;i&&(this.isFiltering=!1,this._fillingPath()),this.event.close.forEach(function(t){typeof t=="function"&&t()})}},open:function(){if(!this.showPanel){this.showPanel=!0,this.closeEventId++;var e=this;setTimeout(function(){h(document).one("click",e.close.bind(e,e.closeEventId))}),this._resetXY(),this.$div.find(".layui-icon-down").addClass("is-reverse"),this.$panel.slideDown(200),this.visibleChange(!0),this.$input.addClass("is-focus"),this.event.open.forEach(function(i){typeof i=="function"&&i()})}},visibleChange:function(e){},removeTag:function(e,i){},getCheckedValues:function(){var e=this.props.strictMode;if(this.props.multiple){var i=this.data.checkedNodes;return e?i.map(function(a){return a.path.map(function(s){return s.value})}):i.map(function(a){return a.value})}else{var t=this.data.activeNode;return e?t&&t.path.map(function(a){return a.value}):t&&t.value}},getCheckedNodes:function(){var e=this.props.strictMode;if(this.props.multiple){var i=this.data.checkedNodes;return e?i&&i.map(function(a){return a.path}):i}else{var t=this.data.activeNode;return e?t&&t.path:t}},clearCheckedNodes:function(e){var i=this.props.multiple;if(i){var t=this.config.disabledFixed;if(!e&&t){var a=this.data.checkedNodes,s=a.filter(function(n){return n.disabled}),r=s.map(function(n){return n.nodeId});this._setCheckedValue(r,s)}else this._setCheckedValue([],[])}else this._setActiveValue(null,null)}};var x=function(){var e=this;return{setOptions:function(i){e.setOptions(i)},setValue:function(i){e.setValue(i)},changeEvent:function(i){e.event.change.push(i)},closeEvent:function(i){e.event.close.push(i)},openEvent:function(i){e.event.open.push(i)},disabled:function(i){e.disabled(i)},close:function(){e.close()},open:function(){e.open()},getCheckedNodes:function(){return e.getCheckedNodes()},getCheckedValues:function(){return e.getCheckedValues()},clearCheckedNodes:function(i){e.clearCheckedNodes(i)},expandNode:function(i){for(var t=e.getNodes(e.data.nodes),a=0;a`+e.name,''+(e.on?'':"")+""].join(""));n.append(t),typeof c.click=="function"&&o.click(t,e),typeof c.del=="function"&&o.del(t,e)})},a.prototype.click=function(n,i){var o=this,c=o.options;n.on("click",function(l){n.toggleClass("on"),n.hasClass("on")?(i.on=!0,n.children("span.hide").html('')):(i.on=!1,n.children("span.hide").html("")),layui.stope(l),c.click(i)})},a.prototype.del=function(n,i){var o=this,c=o.options;n.children("i.del").on("click",function(l){var u=layer.confirm("确定删除 ["+i.name+"] 吗?",{btn:["删除","取消"]},function(){layer.close(u),c.del(i)&&(n.closest(".block").remove(),layui.stope(l))});return!1})},r("checkbox",function(n){var i=new a(n=n||{}),o=s(n.elem);if(!o[0])return h.error("layui.checkbox 没有找到"+n.elem+"元素");i.init(o)})}).link(layui.cache.base+"checkbox/checkbox.css?v="+new Date().getTime()); diff --git a/dist/public/js/lay-module/dropdownTable/dropdownTable.js b/dist/public/js/lay-module/dropdownTable/dropdownTable.js deleted file mode 100644 index ee4c9c5..0000000 --- a/dist/public/js/lay-module/dropdownTable/dropdownTable.js +++ /dev/null @@ -1,70 +0,0 @@ -layui.define(["jquery","dropdown","table","form"],function(C){"use strict";var O=layui.dropdown,u=layui.table,a=layui.jquery,E=layui.form,k="dropdownTable",S={version:"1.0.4",config:{selectType:"radio",emptyMsg:"点击此处选择"},index:layui[k]?layui[k].index+1e4:0,set:function(e){var o,r;this.config=a.extend(!0,{},that.config,e);let t=this.config,l=t.selectType,i={type:l,fixed:"left"};return((r=(o=t==null?void 0:t.selectTable)==null?void 0:o.cols)!=null?r:[[]]).forEach(f=>{JSON.stringify(f).indexOf(JSON.stringify(i))<0&&f.splice(0,0,i)}),that}},v=function(){let e=this,t=e.config,l=t.id||e.index;return v.that[l]=e,{config:t,reload:function(i){e.reload.call(e,i)}}},x=function(e){var r,f;let t=++S.index;this.index=t,this.tableId="dropdown-select-table-"+t,this.dropdownId="dropdown-"+t,this.config=a.extend(!0,{},S.config,e,{selectedIds:[],selectedDisplayValues:[]});let l=this.config,i=l.selectType,n={type:i,fixed:"left"},o=(f=(r=l==null?void 0:l.selectTable)==null?void 0:r.cols)!=null?f:[[]];i==="checkbox"&&o.forEach(d=>{JSON.stringify(d).indexOf(JSON.stringify(n))<0&&d.splice(0,0,n)}),this.render()};x.prototype.reloadSelectTable=function(e){let t=this.config;layui.each(e,function(l,i){layui.type(i)==="array"&&delete t.selectTable[l]}),a.extend(!0,t.selectTable,e),this.clearSelected()},x.prototype.clearSelected=function(){let e=this.config,t=e.bindInput;a(t).val(""),a(t)[0].dataset.displayValue="",a.extend(e,{selectedIds:[],selectedDisplayValues:[]}),this.setSelectedDisplayAndValue()},x.prototype.render=function(){let e=this,t=e.config;(t.elem=a(t.elem))[0]&&(e.initElemArea(),e.iniDropdown())},x.prototype.initElemArea=function(){var w,V,D;let e=this,t=this.config,l=t.elem,i=(V=(w=this.config)==null?void 0:w.searchName)!=null?V:"keywords";a(l).css({display:"inline-block",width:"80%",height:"100%","min-height":"35px",border:"1px solid #eee","line-height":"35px","box-sizing":"border-box",padding:"0 10px",cursor:"pointer"}),a(l).hover(()=>{a(l).css({"border-color":"#e2e2e2"})},()=>{a(l).css({"border-color":"#eee"})});let n=a("");n.css({display:"inline-block",width:"20%","min-width":"45px",height:"100%","box-sizing":"border-box","font-size":"12px",padding:"0","line-height":"36px"}),(D=t==null?void 0:t.needPopup)==null||D?a(l).after(n):a(l).css({width:"100%"});let o=` -
    -
    -
    -
    -
    选择
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    - - -
    -
    -
    -
    -
    -
    -
    选择结果
    -
    - -
    -
    - - - - - - - - - - - - - - - - diff --git a/dist/search-result.html b/dist/search-result.html deleted file mode 100644 index 893f7a7..0000000 --- a/dist/search-result.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - 搜索结果 - 代代相传 - - - - - -
    -
    -
    -
    -
    搜索结果
    -

    搜索公开家谱与姓氏资料

    -

    - 输入姓氏、地区、堂号或家谱名称,快速找到公开展示的家谱、文章和姓氏百科内容。 -

    -
    - -
    -
    -
    - -
    -
    - - - - - diff --git a/dist/submit-ticket.html b/dist/submit-ticket.html deleted file mode 100644 index 7535681..0000000 --- a/dist/submit-ticket.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - 提交工单 - 个人中心 - 代代相传 - - - - - - -
    -
    -
    -
    服务与帮助
    -
    -
    -

    提交问题说明

    -

    遇到账号、权限、资料导入或家谱展示问题时,可以提交工单交给客服跟进。

    -
    - 查看我的工单 -
    -
    -
    -
    -
    - -
    -
    -

    工单信息

    -

    标有 * 的字段为必填。请说明问题页面、操作过程和希望处理的结果。

    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    -
    -
    -

    提交前可准备

    -

    家谱名称、问题页面、账号联系方式、截图说明和期望处理结果。

    -
    -
    -
    -
    -
    - - - - - - - - - - - - - diff --git a/dist/surname-detail.html b/dist/surname-detail.html deleted file mode 100644 index 5ebd6da..0000000 --- a/dist/surname-detail.html +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - 汤姓详情 - 姓氏详情 - 代代相传 - - - - - - -
    -
    -
    -
    -
    姓氏详情
    -

    汤姓

    -

    - 从姓氏源流、郡望堂号、迁徙分布、名人故事和相关家谱出发,认识汤姓族人的文化脉络。 -

    - -
    -
    - -

    源流有序,字辈相承。

    -
    -
    -
    - -
    -
    -
    -

    姓氏源流

    -

    - 汤姓是中国传统姓氏之一,常见说法与商汤、封地、官职和族群迁徙有关。姓氏详情页用于集中展示可公开阅读的源流资料,并连接平台内相关公开家谱。 -

    -
    -
    -

    堂号与分布

    -

    - 汤姓族人分布广泛,不同支系可能有不同堂号、字辈和迁徙故事。平台可按地区、堂号和谱名继续关联检索。 -

    -
    -
    -

    字辈参考

    -
    - -
    -
    - -
    -
    -
    - - - - - - diff --git a/dist/surname.html b/dist/surname.html deleted file mode 100644 index 952cb72..0000000 --- a/dist/surname.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - 姓氏百科 - 代代相传 - - - - - - -
    -
    -
    -
    -
    姓氏百科
    -

    从姓氏源流开始认识自己的来处

    -

    - 以姓氏为入口,整理姓氏源流、名人故事、地区分布、字辈参考和相关家谱。 -

    -
    - 百家姓氏源流家谱关联检索字辈参考线索 -
    -
    -
    -
    -

    查询姓氏

    -
    - 查询 -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -

    热门姓氏

    -

    姓氏百科可与公开家谱互相连接,帮助用户从姓氏进入家族资料。

    -
    - -
    -
    -
    - - - - - - diff --git a/dist/terms.html b/dist/terms.html deleted file mode 100644 index 35faa27..0000000 --- a/dist/terms.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - 服务协议 - 代代相传 - - - - - -
    -
    -
    -
    -
    服务协议
    -

    服务协议

    -

    用于说明用户使用平台服务时的权利、义务与争议处理规则。

    -
    -
    -
    -
    -
    -
    -
    -
    - 待法务确认:本页面仅建立服务协议访问入口,不构成已生效的服务协议。发布前必须由运营主体与法务确认完整文本、签约主体、版本号和生效日期。 -
    -

    正式文本应涵盖

    -

    账户使用规则、内容发布边界、知识产权、服务变更、责任限制、投诉处理与争议解决方式。

    -
    - -
    -
    -
    - - - - - diff --git a/dist/ticket-detail.html b/dist/ticket-detail.html deleted file mode 100644 index f1d2050..0000000 --- a/dist/ticket-detail.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - 工单详情 - 个人中心 - 代代相传 - - - - - - -
    -
    -
    -
    服务与帮助
    -
    -

    工单详情

    查看当前账号提交的问题详情和处理状态。

    - 返回我的工单 -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    正在读取反馈详情…
    -
    -
    -
    -
    -
    -
    - - - - - - - - - - - - - diff --git a/dist/tokens.css b/dist/tokens.css deleted file mode 100644 index f975ce3..0000000 --- a/dist/tokens.css +++ /dev/null @@ -1 +0,0 @@ -:root{color-scheme:light;--color-paper: oklch(97.5% .012 82);--color-paper-2: oklch(94.8% .024 78);--color-paper-3: oklch(91.8% .035 75);--color-panel: oklch(99% .006 82);--color-panel-soft: oklch(97% .018 77);--color-ink: oklch(31% .028 158);--color-ink-2: oklch(51% .022 158);--color-ink-3: oklch(62% .018 158);--color-rule: oklch(86% .028 78);--color-rule-strong: oklch(78% .044 76);--color-accent: oklch(56% .185 29);--color-accent-strong: oklch(43% .13 29);--color-accent-soft: oklch(93% .045 31);--color-accent-ink: oklch(99% .006 82);--color-focus: oklch(61% .17 29);--color-green: oklch(47% .075 167);--color-green-soft: oklch(93% .035 157);--color-gold: oklch(68% .115 77);--color-gold-soft: oklch(94% .045 82);--color-error: oklch(54% .2 27);--color-error-soft: oklch(95% .035 27);--color-success: oklch(48% .11 153);--color-success-soft: oklch(94% .035 153);--color-warning: oklch(62% .13 72);--color-warning-soft: oklch(95% .045 82);--color-overlay: oklch(19% .02 158 / 62%);--font-display: "STSong", "Songti SC", "SimSun", serif;--font-body: "Microsoft YaHei", "PingFang SC", system-ui, -apple-system, sans-serif;--font-mono: "Consolas", "SFMono-Regular", monospace;--space-3xs: .25rem;--space-2xs: .5rem;--space-xs: .75rem;--space-sm: 1rem;--space-md: 1.5rem;--space-lg: 2rem;--space-xl: 3rem;--space-2xl: 4.5rem;--space-3xl: 7rem;--text-xs: .75rem;--text-sm: .875rem;--text-base: 1rem;--text-md: 1.125rem;--text-lg: 1.375rem;--text-xl: 1.75rem;--text-2xl: 2.25rem;--text-display: clamp(2.25rem, 4.5vw, 4.25rem);--layout-max: 1280px;--layout-reading: 760px;--layout-sidebar: 248px;--control-height: 46px;--radius-xs: 6px;--radius-sm: 8px;--radius-md: 12px;--radius-card: 16px;--radius-dialog: 18px;--radius-pill: 999px;--rule-thin: 1px;--shadow-card: 0 14px 36px oklch(25% .025 70 / 9%);--shadow-dialog: 0 28px 80px oklch(20% .025 70 / 24%);--ease-out: cubic-bezier(.16, 1, .3, 1);--ease-in: cubic-bezier(.7, 0, .84, 0);--ease-in-out: cubic-bezier(.65, 0, .35, 1);--dur-short: .16s;--dur-base: .22s;--color-canvas: var(--color-paper);--space-2: var(--space-2xs);--space-3: var(--space-xs);--space-4: var(--space-sm);--space-5: var(--space-md);--space-6: var(--space-lg);--space-7: var(--space-xl);--text-body-lg: var(--text-md);--text-heading: var(--text-lg);--text-title: var(--text-2xl);--radius-lg: var(--radius-card);--shadow-panel: var(--shadow-card)} diff --git a/dist/utils/ApiClient.js b/dist/utils/ApiClient.js deleted file mode 100644 index c827377..0000000 --- a/dist/utils/ApiClient.js +++ /dev/null @@ -1 +0,0 @@ -(function(d,v){if(typeof module=="object"&&module.exports){module.exports=v(d);return}d.GenealogyApi=v(d)})(typeof globalThis!="undefined"?globalThis:window,function(d){"use strict";var v="ced7e5f0498645c6ec642dcf450b036f",z="000000",B="genealogy_auth_token",te=["password-login","sms-login","register","forgot-password","phone-change","account-deactivate"],re=["sms-login","register","forgot-password","phone-change","account-deactivate"];function L(u){if(typeof require!="function")return null;try{return require(u)}catch(c){return null}}function oe(){var u;return d.GenealogyConfig?d.GenealogyConfig:(u=L("../config.js"),u?u(d):null)}function ie(){return d.StorageUtil||L("./StorageUtil.js")}function ae(){return d.AxiosRequestUtil||L("./AxiosRequestUtil.js")}function ue(u){if(u)return u;try{return d.localStorage}catch(c){return null}}function W(u){return u&&u.access_token||""}function i(u,c){var g=u||{},l={};return c.forEach(function(s){g[s]!==void 0&&(l[s]=g[s])}),l}function ce(u){var c=d.FormData,g=u||{},l;if(c&&u instanceof c)return u;if(!c)throw new Error("当前环境不支持文件上传");return l=new c,l.append("uploadId",g.uploadId),l.append("chunkIndex",g.chunkIndex),l.append("chunkMd5",g.chunkMd5),l.append("file",g.file),l}function N(u,c,g){var l=String(u||"").replace(/\/+$/,"")+"/"+String(c||"").replace(/^\/+/,""),s=new URLSearchParams;return Object.keys(g||{}).forEach(function(T){var p=g[T];p!=null&&p!==""&&s.append(T,p)}),s.toString()?l+"?"+s.toString():l}function H(u){var c=u||{},g=oe(),l=g&&g.getConfig?g.getConfig():{},s=ie(),T=ae(),p=ue(c.tokenStore||c.storage),Q=c.clientId||l.clientId||v,y=c.tenantId||l.tenantId||z,U=c.tokenKey||l.tokenKey||B,G=c.baseUrl||l.apiBaseUrl,X;if(!T||!T.createRequester)throw new Error("缺少 AxiosRequestUtil.createRequester");if(!G)throw new Error("缺少接口基础地址");function Y(){return s&&s.read&&s.read(p,U)||""}function q(e){e&&s&&s.write&&s.write(p,U,e)}function A(){s&&s.remove&&s.remove(p,U)}X=T.createRequester({baseUrl:G,clientId:Q,getToken:Y,onUnauthorized:A,axiosInstance:c.axiosInstance||d.axios});function t(e,n,r){return X(e,n,r||{})}function $(e){return Object.assign({},e||{},{tenantId:y})}function O(e,n){return"/genealogy/pc/auth/verification/"+Z(e,"PC 认证动作",te)+(n||"")}function se(e){return"/genealogy/pc/auth/sms/"+Z(e,"PC 短信认证动作",re)+"/code"}function o(e,n){if(typeof e=="number"&&!Number.isSafeInteger(e))throw new Error("无效"+n);var r=String(e==null?"":e).trim();if(!r)throw new Error("缺少"+n);return encodeURIComponent(r)}function Z(e,n,r){var a=String(e==null?"":e).trim();if(!a)throw new Error("缺少"+n);if(r.indexOf(a)===-1)throw new Error("不支持的"+n+":"+a);return encodeURIComponent(a)}function k(e,n){return"/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/feeds"+(n||"")}function E(e,n,r){return k(e,"/"+o(n,"动态编号")+(r||""))}function w(e,n,r){var a=E(e,n,"/comments");return r==null||r===""?a:a+"/"+o(r,"评论编号")}function ee(e,n,r,a){return w(e,n,r)+"/replies"+(a||"")}function b(e,n){return"/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/generation-poems"+(n||"")}function P(e,n){return"/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/lineage"+(n||"")}function m(e,n,r){return P(e,"/persons/"+o(n,"人物编号")+(r||""))}function S(e,n){var r="/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/growth-records";return n==null||n===""?r:r+"/"+o(n,"成长记录编号")}function C(e,n){var r="/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/relative-records";return n==null||n===""?r:r+"/"+o(n,"亲友记录编号")}function D(e,n){var r="/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/memos";return n==null||n===""?r:r+"/"+o(n,"备忘录编号")}function I(e,n){var r="/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/merit-records";return n==null||n===""?r:r+"/"+o(n,"功德记录编号")}function M(e){return"/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/articles"}function j(e,n){return M(e)+"/"+o(n,"谱文编号")}function F(e){return"/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/albums"}function x(e,n){return F(e)+"/"+o(n,"相册编号")}function _(e,n){return x(e,n)+"/photos"}function ge(e,n,r){return _(e,n)+"/"+o(r,"相册照片编号")}function V(e){return"/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/videos"}function K(e,n){return V(e)+"/"+o(n,"视频编号")}function J(e){return"/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/ceremonies"}function f(e,n){return J(e)+"/"+o(n,"祭祀活动编号")}function de(e,n,r){return f(e,n)+"/gifts/"+o(r,"祭品编号")}function R(e){return"/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/members"}function ne(e,n){return R(e)+"/"+o(n,"成员编号")}async function fe(e){var n=await t("POST","/genealogy/pc/auth/login",{auth:!1,body:Object.assign(i(e,["phone","password","validToken"]),{grantType:"password",tenantId:y})}),r=W(n);if(!r)throw new Error("登录响应缺少 token");return q(r),n}async function pe(e){var n=await t("POST","/genealogy/pc/auth/login/sms",{auth:!1,body:Object.assign(i(e,["phone","smsCode"]),{grantType:"sms",tenantId:y})}),r=W(n);if(!r)throw new Error("登录响应缺少 token");return q(r),n}async function ye(e){var n=await t("POST","/genealogy/pc/auth/account/deactivate",{body:i(e,["smsCode"])});return A(),n}async function me(){try{return await t("DELETE","/genealogy/pc/auth/logout")}finally{A()}}return{clientId:Q,tenantId:y,tokenKey:U,getToken:Y,setToken:q,clearToken:A,buildApiUrl:function(e,n){return N(G,e,n)},register:function(e){return t("POST","/genealogy/pc/auth/register",{auth:!1,body:Object.assign(i(e,["phone","password","nickName","smsCode"]),{grantType:"password",tenantId:y,registerSource:"PC"})})},login:fe,loginBySms:pe,sendSmsCode:function(e,n){return t("POST",se(e),{auth:!1,body:Object.assign(i(n,["phone","validToken"]),{grantType:"sms",tenantId:y})})},currentProfile:function(){return t("GET","/genealogy/pc/auth/profile")},updateProfile:function(e){return t("PUT","/genealogy/pc/auth/profile",{body:i(e,["nickName","realName","avatar","sex","birthday","email"])})},changePassword:function(e){return t("PUT","/genealogy/pc/auth/password",{body:i(e,["oldPassword","newPassword"])})},resetPassword:function(e){return t("PUT","/genealogy/pc/auth/password/reset",{auth:!1,body:Object.assign(i(e,["phone","smsCode","newPassword"]),{grantType:"password",tenantId:y})})},changePhone:function(e){return t("PUT","/genealogy/pc/auth/phone",{body:i(e,["phone","smsCode"])})},deactivateAccount:ye,logout:me,initResumableUpload:function(e){return t("POST","/genealogy/pc/files/resumable/init",{body:e})},uploadResumableChunk:function(e){return t("POST","/genealogy/pc/files/resumable/chunk",{body:ce(e)})},completeResumableUpload:function(e){return t("POST","/genealogy/pc/files/resumable/complete",{body:e})},captchaRequirement:function(e,n){var r=n||{};return t("GET",O(e,"/require"),{auth:!1,query:{tenantId:r.tenantId||y,subject:r.subject}})},captchaChallenge:function(e,n){return t("POST",O(e,"/challenge"),{auth:!1,body:$(i(n,["subject"]))})},captchaVerify:function(e,n){return t("POST",O(e,"/verify"),{auth:!1,body:$(i(n,["subject","challengeId","providerCode","captchaType","payload"]))})},captchaChallengeUrl:function(e){return N(G,O(e,"/challenge"))},captchaVerifyUrl:function(e){return N(G,O(e,"/verify"))},regionChildren:function(e){return t("GET","/genealogy/pc/region/children",{query:{parentCode:e}})},regionPath:function(e){return t("GET","/genealogy/pc/region/path/"+encodeURIComponent(e))},regionSearch:function(e){return t("GET","/genealogy/pc/region/search",{query:e})},regionDetail:function(e){return t("GET","/genealogy/pc/region/"+encodeURIComponent(e))},genealogyQuota:function(){return t("GET","/genealogy/pc/genealogies/quota")},genealogiesMine:function(){return t("GET","/genealogy/pc/genealogies/mine")},businessDictionary:function(e){return t("GET","/genealogy/pc/dictionaries/"+o(e,"业务字典类型"))},saveGenealogyOrder:function(e){return t("PUT","/genealogy/pc/genealogies/mine/order",{body:{genealogyIds:Array.isArray(e)?e:[]}})},publicGenealogies:function(e){return t("GET","/genealogy/pc/genealogies/public",{query:i(e,["keyword"])})},genealogyOptions:function(e){return t("GET","/genealogy/pc/genealogies/options",{query:e})},genealogyDetail:function(e){return t("GET","/genealogy/pc/genealogies/"+o(e,"家谱编号"))},genealogyOverview:function(e){return t("GET","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/overview")},genealogyCompleteness:function(e){return t("GET","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/completeness")},archiveGenealogy:function(e){return t("PUT","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/archive")},restoreGenealogy:function(e){return t("PUT","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/restore")},updateGenealogy:function(e,n){return t("PUT","/genealogy/pc/genealogies/"+o(e,"家谱编号"),{body:i(n,["genealogyName","surname","regionCode","ancestralHall","originPlace","addressDetail","coverOssId","intro","visibility","joinMode"])})},genealogyProfileReminders:function(e){return t("GET","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/profile-reminders")},issueGenealogyInvitation:function(e,n){return t("POST","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/invitations",{body:i(n,["lineagePersonId"])})},previewGenealogyInvitation:function(e){var n=String(e==null?"":e).trim();if(!n)throw new Error("缺少邀请令牌");return t("GET","/genealogy/pc/genealogies/invitations/preview",{query:{token:n}})},redeemGenealogyInvitation:function(e){return t("POST","/genealogy/pc/genealogies/invitations/redeem",{body:i(e,["token"])})},myGenealogyInvitations:function(){return t("GET","/genealogy/pc/genealogies/invitations/mine")},revokeGenealogyInvitation:function(e){return t("DELETE","/genealogy/pc/genealogies/invitations/"+o(e,"邀请编号"))},genealogyDeletionCapability:function(e){return t("GET","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/permanent-deletion/capability")},sendGenealogyDeletionCode:function(e,n){return t("POST","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/permanent-deletion/code",{body:i(n,["validToken"])})},permanentlyDeleteGenealogy:function(e,n){return t("POST","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/permanent-deletion",{body:i(n,["genealogyName","smsCode"])})},createGenealogy:function(e){return t("POST","/genealogy/pc/genealogies",{body:i(e,["genealogyName","surname","regionCode","ancestralHall","originPlace","addressDetail","coverOssId","intro","visibility","joinMode"])})},applyToGenealogy:function(e,n){return t("POST","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/join-applies",{body:i(n,["applicantName","phone","relationDesc","applyReason"])})},myGenealogyJoinApplies:function(){return t("GET","/genealogy/pc/genealogies/join-applies/mine")},pendingGenealogyJoinApplies:function(e){return t("GET","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/join-applies/pending")},auditGenealogyJoinApply:function(e,n,r){return t("PUT","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/join-applies/"+o(n,"申请编号")+"/audit",{body:i(r,["status","auditRemark"])})},cancelGenealogyJoinApply:function(e){return t("DELETE","/genealogy/pc/genealogies/join-applies/"+o(e,"申请编号"))},submitFeedback:function(e){return t("POST","/genealogy/pc/feedback",{body:i(e,["feedbackType","feedbackContent","contactInfo"])})},myFeedback:function(){return t("GET","/genealogy/pc/feedback")},helpArticles:function(e){return t("GET","/genealogy/pc/help-articles",{query:i(e,["helpCategory"])})},helpArticleDetail:function(e){return t("GET","/genealogy/pc/help-articles/"+o(e,"帮助文章编号"))},siteArticles:function(e){var n=e||{},r=n.articleType===void 0||n.articleType===null?"":String(n.articleType).trim(),a=n.limit,h={};if(r&&["news","notice","download"].indexOf(r)===-1)throw new Error("不支持的资讯类型:"+r);if(a!==void 0&&(typeof a!="number"||!Number.isInteger(a)||a<1||a>100))throw new Error("资讯数量限制必须是 1 到 100 的整数");return r&&(h.articleType=r),a!==void 0&&(h.limit=a),t("GET","/genealogy/pc/site/articles",{auth:!1,query:h})},promotions:function(e){var n=e||{},r=n.platform===void 0||n.platform===null?"":String(n.platform).trim();if(r&&["all","app","pc","wechat"].indexOf(r)===-1)throw new Error("不支持的推广平台:"+r);return t("GET","/genealogy/pc/promotions",{query:r?{platform:r}:{}})},vipPackages:function(){return t("GET","/genealogy/pc/vip/packages")},vipCapability:function(){return t("GET","/genealogy/pc/vip/capability")},createVipOrder:function(e){return t("POST","/genealogy/pc/vip/orders",{body:i(e,["packageId","genealogyId"])})},vipOrders:function(){return t("GET","/genealogy/pc/vip/orders")},vipPaymentStatus:function(e){return t("GET","/genealogy/pc/vip/orders/"+o(e,"支付流水编号")+"/payment")},closeVipPayment:function(e){return t("POST","/genealogy/pc/vip/orders/"+o(e,"支付流水编号")+"/close")},earningSummary:function(){return t("GET","/genealogy/pc/earnings/summary")},earningLedger:function(e){return t("GET","/genealogy/pc/earnings/ledger",{query:i(e,["pageNum","pageSize"])})},earningWithdrawals:function(e){return t("GET","/genealogy/pc/earnings/withdrawals",{query:i(e,["pageNum","pageSize"])})},createEarningWithdrawal:function(e){return t("POST","/genealogy/pc/earnings/withdrawals",{body:i(e,["requestId","amount","payoutQrOssId","payoutAccountName"])})},cancelEarningWithdrawal:function(e){return t("POST","/genealogy/pc/earnings/withdrawals/"+o(e,"提现编号")+"/cancel")},personDocuments:function(e,n){return t("GET","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/person-documents",{query:i(n,["lineagePersonId"])})},personDocument:function(e,n,r){var a={};return r&&(a.headers={"X-Content-Access-Token":r}),t("GET","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/person-documents/"+o(n,"档案编号"),a)},createPersonDocument:function(e,n){return t("POST","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/person-documents",{body:i(n,["lineagePersonId","documentType","documentTitle","maskedIdentifier","description","sortOrder","status"])})},updatePersonDocument:function(e,n,r){return t("PUT","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/person-documents/"+o(n,"档案编号"),{body:i(r,["lineagePersonId","documentType","documentTitle","maskedIdentifier","description","sortOrder","status"])})},deletePersonDocument:function(e,n){return t("DELETE","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/person-documents/"+o(n,"档案编号"))},addPersonDocumentResource:function(e,n,r){return t("POST","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/person-documents/"+o(n,"档案编号")+"/resources",{body:i(r,["ossId","usageType","sortOrder"])})},deletePersonDocumentResource:function(e,n,r){return t("DELETE","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/person-documents/"+o(n,"档案编号")+"/resources/"+o(r,"附件编号"))},personDocumentResourceAccess:function(e,n,r,a){var h={};return a&&(h.headers={"X-Content-Access-Token":a}),t("GET","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/person-documents/"+o(n,"档案编号")+"/resources/"+o(r,"附件编号")+"/access",h)},protectPersonDocument:function(e,n,r){return t("PUT","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/person-documents/"+o(n,"档案编号")+"/content-protection",{body:{password:r}})},unlockPersonDocument:function(e,n,r){return t("POST","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/person-documents/"+o(n,"档案编号")+"/content-unlock",{body:{password:r}})},unprotectPersonDocument:function(e,n){return t("DELETE","/genealogy/pc/genealogies/"+o(e,"家谱编号")+"/person-documents/"+o(n,"档案编号")+"/content-protection")},notifications:function(e){return t("GET","/genealogy/pc/notifications",{query:i(e,["readStatus"])})},notificationDetail:function(e){return t("GET","/genealogy/pc/notifications/"+o(e,"通知编号"))},unreadNotificationCount:function(){return t("GET","/genealogy/pc/notifications/unread-count")},markNotificationRead:function(e){return t("POST","/genealogy/pc/notifications/"+o(e,"通知编号")+"/read")},markAllNotificationsRead:function(){return t("POST","/genealogy/pc/notifications/read-all")},feeds:function(e){return t("GET",k(e))},feedsPage:function(e,n){return t("GET",k(e,"/page"),{query:n})},feedDetail:function(e,n){return t("GET",E(e,n))},createFeed:function(e,n){return t("POST",k(e),{body:n})},updateFeed:function(e,n,r){return t("PUT",E(e,n),{body:r})},deleteFeed:function(e,n){return t("DELETE",E(e,n))},likeFeed:function(e,n){return t("POST",E(e,n,"/likes"))},unlikeFeed:function(e,n){return t("DELETE",E(e,n,"/likes"))},feedComments:function(e,n){return t("GET",w(e,n))},feedCommentsPage:function(e,n,r){return t("GET",w(e,n)+"/page",{query:r})},feedCommentReplies:function(e,n,r){return t("GET",ee(e,n,r))},feedCommentRepliesPage:function(e,n,r,a){return t("GET",ee(e,n,r,"/page"),{query:a})},createFeedComment:function(e,n,r){return t("POST",w(e,n),{body:r})},deleteFeedComment:function(e,n,r){return t("DELETE",w(e,n,r))},generationPoems:function(e){return t("GET",b(e))},generationPoemsManagement:function(e){return t("GET",b(e,"/management"))},createGenerationPoem:function(e,n){return t("POST",b(e),{body:n})},updateGenerationPoem:function(e,n,r){return t("PUT",b(e,"/"+o(n,"字辈编号")),{body:r})},previewGenerationPoems:function(e,n){return t("POST",b(e,"/batch/preview"),{body:n})},saveGenerationPoems:function(e,n){return t("POST",b(e,"/batch/save"),{body:n})},lineagePersons:function(e){return t("GET",P(e,"/persons"))},lineagePersonsPage:function(e,n){return t("GET",P(e,"/persons/page"),{query:n})},lineagePersonOptions:function(e){return t("GET",P(e,"/persons/options"))},lineageTree:function(e){return t("GET",P(e,"/tree"))},createLineagePerson:function(e,n){return t("POST",P(e,"/persons"),{body:n})},lineagePersonDetail:function(e,n){return t("GET",m(e,n))},updateLineagePerson:function(e,n,r){return t("PUT",m(e,n),{body:r})},disableLineagePerson:function(e,n){return t("DELETE",m(e,n))},createLineageChild:function(e,n,r){return t("POST",m(e,n,"/children"),{body:r})},createLineageParent:function(e,n,r){return t("POST",m(e,n,"/parents"),{body:r})},createLineageSibling:function(e,n,r){return t("POST",m(e,n,"/siblings"),{body:r})},createLineageSpouse:function(e,n,r){return t("POST",m(e,n,"/spouses"),{body:r})},growthRecords:function(e){return t("GET",S(e))},createGrowthRecord:function(e,n){return t("POST",S(e),{body:n})},growthRecordDetail:function(e,n){return t("GET",S(e,n))},updateGrowthRecord:function(e,n,r){return t("PUT",S(e,n),{body:r})},deleteGrowthRecord:function(e,n){return t("DELETE",S(e,n))},relativeRecords:function(e){return t("GET",C(e))},createRelativeRecord:function(e,n){return t("POST",C(e),{body:n})},relativeRecordDetail:function(e,n){return t("GET",C(e,n))},updateRelativeRecord:function(e,n,r){return t("PUT",C(e,n),{body:r})},deleteRelativeRecord:function(e,n){return t("DELETE",C(e,n))},memos:function(e){return t("GET",D(e))},createMemo:function(e,n){return t("POST",D(e),{body:n})},memoDetail:function(e,n){return t("GET",D(e,n))},updateMemo:function(e,n,r){return t("PUT",D(e,n),{body:r})},deleteMemo:function(e,n){return t("DELETE",D(e,n))},meritRecords:function(e){return t("GET",I(e))},createMeritRecord:function(e,n){return t("POST",I(e),{body:i(n,["donorName","meritType","meritTitle","meritContent","amount","meritTime","sortOrder","status"])})},meritRecordDetail:function(e,n){return t("GET",I(e,n))},updateMeritRecord:function(e,n,r){return t("PUT",I(e,n),{body:i(r,["donorName","meritType","meritTitle","meritContent","amount","meritTime","sortOrder","status"])})},deleteMeritRecord:function(e,n){return t("DELETE",I(e,n))},articles:function(e){return t("GET",M(e))},createArticle:function(e,n){return t("POST",M(e),{body:i(n,["categoryId","articleTitle","articleSummary","coverOssId","articleContent","authorName","sortOrder","status"])})},articleDetail:function(e,n){return t("GET",j(e,n))},updateArticle:function(e,n,r){return t("PUT",j(e,n),{body:i(r,["categoryId","articleTitle","articleSummary","coverOssId","articleContent","authorName","sortOrder","status"])})},deleteArticle:function(e,n){return t("DELETE",j(e,n))},albums:function(e){return t("GET",F(e))},createAlbum:function(e,n){return t("POST",F(e),{body:i(n,["albumName","albumDesc","coverOssId","sortOrder","status"])})},updateAlbum:function(e,n,r){return t("PUT",x(e,n),{body:i(r,["albumName","albumDesc","coverOssId","sortOrder","status"])})},albumPhotos:function(e,n){return t("GET",_(e,n))},createAlbumPhoto:function(e,n,r){return t("POST",_(e,n),{body:i(r,["ossId","photoTitle","photoDesc","photographer","shootTime","sortOrder","status"])})},deleteAlbum:function(e,n){return t("DELETE",x(e,n))},deleteAlbumPhoto:function(e,n,r){return t("DELETE",ge(e,n,r))},videos:function(e){return t("GET",V(e))},createVideo:function(e,n){return t("POST",V(e),{body:n})},videoDetail:function(e,n){return t("GET",K(e,n))},updateVideo:function(e,n,r){return t("PUT",K(e,n),{body:r})},deleteVideo:function(e,n){return t("DELETE",K(e,n))},ceremonies:function(e){return t("GET",J(e))},createCeremony:function(e,n){return t("POST",J(e),{body:i(n,["ceremonyType","ceremonyTitle","ceremonyDesc","ceremonyTime","location","locationAddress","longitude","latitude","coverOssId","sortOrder","status"])})},ceremonyDetail:function(e,n){return t("GET",f(e,n))},updateCeremony:function(e,n,r){return t("PUT",f(e,n),{body:i(r,["ceremonyType","ceremonyTitle","ceremonyDesc","ceremonyTime","location","locationAddress","longitude","latitude","coverOssId","sortOrder","status"])})},ceremonyGifts:function(e,n){return t("GET",f(e,n)+"/gifts")},createCeremonyGift:function(e,n,r){return t("POST",f(e,n)+"/gifts",{body:i(r,["giverName","giftAmount","giftMessage"])})},replaceCeremonyInvitees:function(e,n,r){return t("PUT",f(e,n)+"/invitees",{body:{inviteeUserIds:Array.isArray(r)?r:[]}})},ceremonyInvitations:function(e,n){return t("GET",f(e,n)+"/invitations")},respondCeremonyInvitation:function(e,n,r){return t("PUT",f(e,n)+"/invitations/me",{body:r})},myCeremonyInvitations:function(){return t("GET","/genealogy/pc/genealogies/ceremony-invitations/mine")},deleteCeremony:function(e,n){return t("DELETE",f(e,n))},deleteCeremonyGift:function(e,n,r){return t("DELETE",de(e,n,r))},genealogyMembers:function(e){return t("GET",R(e))},genealogyMemberOptions:function(e){return t("GET",R(e)+"/options")},updateGenealogyMember:function(e,n,r){return t("PUT",ne(e,n),{body:i(r,["memberName","relationName","roleType","lineagePersonId"])})},removeGenealogyMember:function(e,n){return t("DELETE",ne(e,n))},leaveGenealogy:function(e){return t("DELETE",R(e)+"/me")},transferGenealogyOwner:function(e,n){return t("PUT",R(e)+"/owner-transfer",{body:i(n,["targetMemberId"])})}}}function le(){try{return H()}catch(u){return null}}return{DEFAULT_CLIENT_ID:v,DEFAULT_TENANT_ID:z,TOKEN_KEY:B,createClient:H,defaultClient:le()}}); diff --git a/dist/utils/AxiosRequestUtil.js b/dist/utils/AxiosRequestUtil.js deleted file mode 100644 index edb5dfa..0000000 --- a/dist/utils/AxiosRequestUtil.js +++ /dev/null @@ -1 +0,0 @@ -(function(s,u){if(typeof module=="object"&&module.exports){module.exports=u(s);return}s.AxiosRequestUtil=u(s)})(typeof globalThis!="undefined"?globalThis:window,function(s){"use strict";function u(e){return typeof FormData!="undefined"&&e instanceof FormData}function h(e,r){var t=new Error(e);return Object.keys(r||{}).forEach(function(a){t[a]=r[a]}),t}function d(e){if(!e||typeof e!="object")return e;if(e.code!==void 0&&Number(e.code)!==200)throw h(e.msg||"接口请求失败",{code:e.code,response:e});return Object.prototype.hasOwnProperty.call(e,"data")?e.data:Object.prototype.hasOwnProperty.call(e,"rows")?{rows:e.rows,total:e.total}:e}function v(e){var r=e||{},t=r.axiosInstance||s.axios;if(!t||typeof t.request!="function"&&typeof t.create!="function")throw new Error("缺少 Axios 公共依赖");return typeof t.create=="function"?t.create({baseURL:r.baseUrl}):t}function g(e){var r=e||{},t=v(r);function a(p){Number(p)===401&&r.onUnauthorized&&r.onUnauthorized()}return async function(l,x,q){var o=q||{},i=Object.assign({},o.headers||{}),w=r.getToken?r.getToken():"",j=r.tokenHeaderName||"Authorization",c=o.body,m;i.clientid=r.clientId,w&&o.auth!==!1&&(i[j]="Bearer "+w),c!=null&&!u(c)&&(i["Content-Type"]=i["Content-Type"]||"application/json");try{m=await t.request({method:String(l||"GET").toLowerCase(),url:x,params:o.query,data:c,headers:i})}catch(n){var f=n.response&&n.response.data,b=n.response&&n.response.status;throw o.auth!==!1&&a(b),h(f&&f.msg||n.message||"接口请求失败",{status:b,response:f,cause:n})}try{return d(m.data)}catch(n){throw o.auth!==!1&&a(n.code),n}}}return{createRequester:g,unwrapResponse:d}}); diff --git a/dist/utils/DateUtil.js b/dist/utils/DateUtil.js deleted file mode 100644 index aa1bc07..0000000 --- a/dist/utils/DateUtil.js +++ /dev/null @@ -1 +0,0 @@ -class DateUtil{static formatDateToMonthDay(e){try{const t=DateUtil._parseDate(e);if(!DateUtil._isValidDate(t))return"";const r=t.getMonth()+1,a=t.getDate();return`${r}月${a}日`}catch(t){return""}}static formatDateToYearMonthDay(e){try{const t=DateUtil._parseDate(e);if(!DateUtil._isValidDate(t))return"";const r=t.getFullYear(),a=t.getMonth()+1,n=t.getDate();return`${r}年${a}月${n}日`}catch(t){return""}}static formatDateToYMD(e){try{const t=DateUtil._parseDate(e);if(!DateUtil._isValidDate(t))return"";const r=t.getFullYear(),a=String(t.getMonth()+1).padStart(2,"0"),n=String(t.getDate()).padStart(2,"0");return`${r}-${a}-${n}`}catch(t){return""}}static formatDateTimeToYMDHMS(e){try{const t=DateUtil._parseDate(e);if(!DateUtil._isValidDate(t))return"";const r=t.getFullYear(),a=String(t.getMonth()+1).padStart(2,"0"),n=String(t.getDate()).padStart(2,"0"),o=String(t.getHours()).padStart(2,"0"),s=String(t.getMinutes()).padStart(2,"0"),c=String(t.getSeconds()).padStart(2,"0");return`${r}-${a}-${n} ${o}:${s}:${c}`}catch(t){return""}}static formatDateTimeToYMDHM(e){try{const t=DateUtil._parseDate(e);if(!DateUtil._isValidDate(t))return"";const r=t.getFullYear(),a=String(t.getMonth()+1).padStart(2,"0"),n=String(t.getDate()).padStart(2,"0"),o=String(t.getHours()).padStart(2,"0"),s=String(t.getMinutes()).padStart(2,"0");return`${r}-${a}-${n} ${o}:${s}`}catch(t){return""}}static formatTimeToHMS(e){try{const t=typeof e=="number"?new Date(e):DateUtil._parseDate(e);if(!DateUtil._isValidDate(t))return"";const r=String(t.getHours()).padStart(2,"0"),a=String(t.getMinutes()).padStart(2,"0"),n=String(t.getSeconds()).padStart(2,"0");return`${r}:${a}:${n}`}catch(t){return""}}static getCurrentDate(){return new Date}static isSameDay(e,t){try{const r=DateUtil._parseDate(e),a=DateUtil._parseDate(t);return!DateUtil._isValidDate(r)||!DateUtil._isValidDate(a)?!1:r.getFullYear()===a.getFullYear()&&r.getMonth()===a.getMonth()&&r.getDate()===a.getDate()}catch(r){return!1}}static _parseDate(e){if(e instanceof Date)return e;if(typeof e!="string")return null;const t=e.trim();return t?/^\d{4}-\d{2}-\d{2}$/.test(t)?new Date(t.replace(/-/g,"/")):new Date(t):null}static _isValidDate(e){return e instanceof Date&&!Number.isNaN(e.getTime())}}typeof module!="undefined"&&module.exports?module.exports=DateUtil:typeof window!="undefined"&&(window.DateUtil=DateUtil); diff --git a/dist/utils/FormUtil.js b/dist/utils/FormUtil.js deleted file mode 100644 index 80f9eaa..0000000 --- a/dist/utils/FormUtil.js +++ /dev/null @@ -1 +0,0 @@ -(function(u,i){if(typeof module=="object"&&module.exports){module.exports=i();return}u.FormUtil=i()})(typeof globalThis!="undefined"?globalThis:window,function(){"use strict";function u(e){if(e!=null){var n=String(e).trim();return n||void 0}}function i(e){var n=u(e);if(n!==void 0){var r=Number(n);return Number.isNaN(r)?void 0:r}}function d(e){if(e===!0||e===!1)return e;var n=u(e);return n===void 0?!1:!["0","false","off","no"].includes(n.toLowerCase())}function f(e){if(!(!e||e.disabled||!e.name))return e.type==="checkbox"?e.checked?e.value||"on":void 0:e.type==="radio"?e.checked?e.value:void 0:e.value}function a(e){var n={};return!e||!e.elements||Array.prototype.forEach.call(e.elements,function(r){var t=f(r);t!==void 0&&(n[r.name]=t)}),n}return{trimOrUndefined:u,toNumberOrUndefined:i,toBoolean:d,getFormValues:a}}); diff --git a/dist/utils/MessageUtil.js b/dist/utils/MessageUtil.js deleted file mode 100644 index 86a14dc..0000000 --- a/dist/utils/MessageUtil.js +++ /dev/null @@ -1 +0,0 @@ -(function(e,s){if(typeof module=="object"&&module.exports){module.exports=s(e);return}e.MessageUtil=s(e)})(typeof globalThis!="undefined"?globalThis:window,function(e){"use strict";var s={skin:"auth-layer-message",time:2200};function n(l,a){var i=String(l||""),u=Object.assign({},s,a||{});if(e.layui&&e.layui.layer&&e.layui.layer.msg){e.layui.layer.msg(i,u);return}e.console&&e.console.warn&&e.console.warn(i)}return{show:n}}); diff --git a/dist/utils/StorageUtil.js b/dist/utils/StorageUtil.js deleted file mode 100644 index c911007..0000000 --- a/dist/utils/StorageUtil.js +++ /dev/null @@ -1 +0,0 @@ -(function(o,t){if(typeof module=="object"&&module.exports){module.exports=t();return}o.StorageUtil=t()})(typeof globalThis!="undefined"?globalThis:window,function(){"use strict";function o(e,r){try{return e&&e.getItem(r)}catch(n){return null}}function t(e,r,n){try{e&&e.setItem(r,n)}catch(u){}}function i(e,r){try{e&&e.removeItem(r)}catch(n){}}return{read:o,write:t,remove:i}}); diff --git a/dist/utils/axios.js b/dist/utils/axios.js deleted file mode 100644 index 39f66f5..0000000 --- a/dist/utils/axios.js +++ /dev/null @@ -1,9 +0,0 @@ -(function(J,V){typeof exports=="object"&&typeof module!="undefined"?module.exports=V():typeof define=="function"&&define.amd?define(V):(J=typeof globalThis!="undefined"?globalThis:J||self,J.axios=V())})(this,(function(){"use strict";function J(r){var e,t;function n(i,o){try{var s=r[i](o),u=s.value,c=u instanceof V;Promise.resolve(c?u.v:u).then(function(l){if(c){var h=i==="return"?"return":"next";if(!u.k||l.done)return n(h,l);l=r[h](l).value}a(s.done?"return":"normal",l)},function(l){n("throw",l)})}catch(l){a("throw",l)}}function a(i,o){switch(i){case"return":e.resolve({value:o,done:!0});break;case"throw":e.reject(o);break;default:e.resolve({value:o,done:!1})}(e=e.next)?n(e.key,e.arg):t=null}this._invoke=function(i,o){return new Promise(function(s,u){var c={key:i,arg:o,resolve:s,reject:u,next:null};t?t=t.next=c:(e=t=c,n(i,o))})},typeof r.return!="function"&&(this.return=void 0)}J.prototype[typeof Symbol=="function"&&Symbol.asyncIterator||"@@asyncIterator"]=function(){return this},J.prototype.next=function(r){return this._invoke("next",r)},J.prototype.throw=function(r){return this._invoke("throw",r)},J.prototype.return=function(r){return this._invoke("return",r)};function V(r,e){this.v=r,this.k=e}function Ve(r){var e={},t=!1;function n(a,i){return t=!0,i=new Promise(function(o){o(r[a](i))}),{done:!1,value:new V(i,1)}}return e[typeof Symbol!="undefined"&&Symbol.iterator||"@@iterator"]=function(){return this},e.next=function(a){return t?(t=!1,a):n("next",a)},typeof r.throw=="function"&&(e.throw=function(a){if(t)throw t=!1,a;return n("throw",a)}),typeof r.return=="function"&&(e.return=function(a){return t?(t=!1,a):n("return",a)}),e}function je(r){var e,t,n,a=2;for(typeof Symbol!="undefined"&&(t=Symbol.asyncIterator,n=Symbol.iterator);a--;){if(t&&(e=r[t])!=null)return e.call(r);if(n&&(e=r[n])!=null)return new de(e.call(r));t="@@asyncIterator",n="@@iterator"}throw new TypeError("Object is not async iterable")}function de(r){function e(t){if(Object(t)!==t)return Promise.reject(new TypeError(t+" is not an object."));var n=t.done;return Promise.resolve(t.value).then(function(a){return{value:a,done:n}})}return de=function(t){this.s=t,this.n=t.next},de.prototype={s:null,n:null,next:function(){return e(this.n.apply(this.s,arguments))},return:function(t){var n=this.s.return;return n===void 0?Promise.resolve({value:t,done:!0}):e(n.apply(this.s,arguments))},throw:function(t){var n=this.s.return;return n===void 0?Promise.reject(t):e(n.apply(this.s,arguments))}},new de(r)}function pe(r){return new V(r,0)}function zr(r,e){var t=r==null?null:typeof Symbol!="undefined"&&r[Symbol.iterator]||r["@@iterator"];if(t!=null){var n,a,i,o,s=[],u=!0,c=!1;try{if(i=(t=t.call(r)).next,e===0){if(Object(t)!==t)return;u=!1}else for(;!(u=(n=i.call(t)).done)&&(s.push(n.value),s.length!==e);u=!0);}catch(l){c=!0,a=l}finally{try{if(!u&&t.return!=null&&(o=t.return(),Object(o)!==o))return}finally{if(c)throw a}}return s}}function Xe(r,e){var t=Object.keys(r);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(r);e&&(n=n.filter(function(a){return Object.getOwnPropertyDescriptor(r,a).enumerable})),t.push.apply(t,n)}return t}function se(r){for(var e=1;e=0;--O){var w=this.tryEntries[O],P=w.completion;if(w.tryLoc==="root")return v("end");if(w.tryLoc<=this.prev){var g=n.call(w,"catchLoc"),j=n.call(w,"finallyLoc");if(g&&j){if(this.prev=0;--v){var O=this.tryEntries[v];if(O.tryLoc<=this.prev&&n.call(O,"finallyLoc")&&this.prev=0;--d){var v=this.tryEntries[d];if(v.finallyLoc===p)return this.complete(v.completion,v.afterLoc),Q(v),m}},catch:function(p){for(var d=this.tryEntries.length-1;d>=0;--d){var v=this.tryEntries[d];if(v.tryLoc===p){var O=v.completion;if(O.type==="throw"){var w=O.arg;Q(v)}return w}}throw new Error("illegal catch attempt")},delegateYield:function(p,d,v){return this.delegate={iterator:ie(p),resultName:d,nextLoc:v},this.method==="next"&&(this.arg=r),m}},e}function $r(r,e){if(typeof r!="object"||!r)return r;var t=r[Symbol.toPrimitive];if(t!==void 0){var n=t.call(r,e||"default");if(typeof n!="object")return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return(e==="string"?String:Number)(r)}function Ye(r){var e=$r(r,"string");return typeof e=="symbol"?e:String(e)}function G(r){"@babel/helpers - typeof";return G=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},G(r)}function Qe(r){return function(){return new J(r.apply(this,arguments))}}function Ze(r,e,t,n,a,i,o){try{var s=r[i](o),u=s.value}catch(c){t(c);return}s.done?e(u):Promise.resolve(u).then(n,a)}function re(r){return function(){var e=this,t=arguments;return new Promise(function(n,a){var i=r.apply(e,t);function o(u){Ze(i,n,a,o,s,"next",u)}function s(u){Ze(i,n,a,o,s,"throw",u)}o(void 0)})}}function he(r,e){if(!(r instanceof e))throw new TypeError("Cannot call a class as a function")}function er(r,e){for(var t=0;tr.length)&&(e=r.length);for(var t=0,n=new Array(e);t=r.length?{done:!0}:{done:!1,value:r[n++]}},e:function(u){throw u},f:a}}throw new TypeError(`Invalid attempt to iterate non-iterable instance. -In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}var i=!0,o=!1,s;return{s:function(){t=t.call(r)},n:function(){var u=t.next();return i=u.done,u},e:function(u){o=!0,s=u},f:function(){try{!i&&t.return!=null&&t.return()}finally{if(o)throw s}}}}function ir(r,e){return function(){return r.apply(e,arguments)}}var Xr=Object.prototype.toString,De=Object.getPrototypeOf,ye=(function(r){return function(e){var t=Xr.call(e);return r[t]||(r[t]=t.slice(8,-1).toLowerCase())}})(Object.create(null)),q=function(e){return e=e.toLowerCase(),function(t){return ye(t)===e}},be=function(e){return function(t){return G(t)===e}},te=Array.isArray,ue=be("undefined");function Yr(r){return r!==null&&!ue(r)&&r.constructor!==null&&!ue(r.constructor)&&U(r.constructor.isBuffer)&&r.constructor.isBuffer(r)}var or=q("ArrayBuffer");function Qr(r){var e;return typeof ArrayBuffer!="undefined"&&ArrayBuffer.isView?e=ArrayBuffer.isView(r):e=r&&r.buffer&&or(r.buffer),e}var Zr=be("string"),U=be("function"),sr=be("number"),we=function(e){return e!==null&&G(e)==="object"},et=function(e){return e===!0||e===!1},ge=function(e){if(ye(e)!=="object")return!1;var t=De(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(Symbol.toStringTag in e)&&!(Symbol.iterator in e)},rt=q("Date"),tt=q("File"),nt=q("Blob"),at=q("FileList"),it=function(e){return we(e)&&U(e.pipe)},ot=function(e){var t;return e&&(typeof FormData=="function"&&e instanceof FormData||U(e.append)&&((t=ye(e))==="formdata"||t==="object"&&U(e.toString)&&e.toString()==="[object FormData]"))},st=q("URLSearchParams"),ut=["ReadableStream","Request","Response","Headers"].map(q),Ee=K(ut,4),ft=Ee[0],ct=Ee[1],lt=Ee[2],dt=Ee[3],pt=function(e){return e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")};function fe(r,e){var t=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{},n=t.allOwnKeys,a=n===void 0?!1:n;if(!(r===null||typeof r=="undefined")){var i,o;if(G(r)!=="object"&&(r=[r]),te(r))for(i=0,o=r.length;i0;)if(a=t[n],e===a.toLowerCase())return a;return null}var X=(function(){return typeof globalThis!="undefined"?globalThis:typeof self!="undefined"?self:typeof window!="undefined"?window:global})(),fr=function(e){return!ue(e)&&e!==X};function Be(){for(var r=fr(this)&&this||{},e=r.caseless,t={},n=function(s,u){var c=e&&ur(t,u)||u;ge(t[c])&&ge(s)?t[c]=Be(t[c],s):ge(s)?t[c]=Be({},s):te(s)?t[c]=s.slice():t[c]=s},a=0,i=arguments.length;a3&&arguments[3]!==void 0?arguments[3]:{},i=a.allOwnKeys;return fe(t,function(o,s){n&&U(o)?e[s]=ir(o,n):e[s]=o},{allOwnKeys:i}),e},vt=function(e){return e.charCodeAt(0)===65279&&(e=e.slice(1)),e},mt=function(e,t,n,a){e.prototype=Object.create(t.prototype,a),e.prototype.constructor=e,Object.defineProperty(e,"super",{value:t.prototype}),n&&Object.assign(e.prototype,n)},yt=function(e,t,n,a){var i,o,s,u={};if(t=t||{},e==null)return t;do{for(i=Object.getOwnPropertyNames(e),o=i.length;o-- >0;)s=i[o],(!a||a(s,e,t))&&!u[s]&&(t[s]=e[s],u[s]=!0);e=n!==!1&&De(e)}while(e&&(!n||n(e,t))&&e!==Object.prototype);return t},bt=function(e,t,n){e=String(e),(n===void 0||n>e.length)&&(n=e.length),n-=t.length;var a=e.indexOf(t,n);return a!==-1&&a===n},wt=function(e){if(!e)return null;if(te(e))return e;var t=e.length;if(!sr(t))return null;for(var n=new Array(t);t-- >0;)n[t]=e[t];return n},gt=(function(r){return function(e){return r&&e instanceof r}})(typeof Uint8Array!="undefined"&&De(Uint8Array)),Et=function(e,t){for(var n=e&&e[Symbol.iterator],a=n.call(e),i;(i=a.next())&&!i.done;){var o=i.value;t.call(e,o[0],o[1])}},St=function(e,t){for(var n,a=[];(n=e.exec(t))!==null;)a.push(n);return a},Ot=q("HTMLFormElement"),Rt=function(e){return e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(n,a,i){return a.toUpperCase()+i})},cr=(function(r){var e=r.hasOwnProperty;return function(t,n){return e.call(t,n)}})(Object.prototype),At=q("RegExp"),lr=function(e,t){var n=Object.getOwnPropertyDescriptors(e),a={};fe(n,function(i,o){var s;(s=t(i,o,e))!==!1&&(a[o]=s||i)}),Object.defineProperties(e,a)},Tt=function(e){lr(e,function(t,n){if(U(e)&&["arguments","caller","callee"].indexOf(n)!==-1)return!1;var a=e[n];if(U(a)){if(t.enumerable=!1,"writable"in t){t.writable=!1;return}t.set||(t.set=function(){throw Error("Can not rewrite read-only method '"+n+"'")})}})},Pt=function(e,t){var n={},a=function(o){o.forEach(function(s){n[s]=!0})};return te(e)?a(e):a(String(e).split(t)),n},Ct=function(){},kt=function(e,t){return e!=null&&Number.isFinite(e=+e)?e:t},xe="abcdefghijklmnopqrstuvwxyz",dr="0123456789",pr={DIGIT:dr,ALPHA:xe,ALPHA_DIGIT:xe+xe.toUpperCase()+dr},Lt=function(){for(var e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:16,t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:pr.ALPHA_DIGIT,n="",a=t.length;e--;)n+=t[Math.random()*a|0];return n};function Nt(r){return!!(r&&U(r.append)&&r[Symbol.toStringTag]==="FormData"&&r[Symbol.iterator])}var jt=function(e){var t=new Array(10),n=function a(i,o){if(we(i)){if(t.indexOf(i)>=0)return;if(!("toJSON"in i)){t[o]=i;var s=te(i)?[]:{};return fe(i,function(u,c){var l=a(u,o+1);!ue(l)&&(s[c]=l)}),t[o]=void 0,s}}return i};return n(e,0)},Ft=q("AsyncFunction"),Dt=function(e){return e&&(we(e)||U(e))&&U(e.then)&&U(e.catch)},hr=(function(r,e){return r?setImmediate:e?(function(t,n){return X.addEventListener("message",function(a){var i=a.source,o=a.data;i===X&&o===t&&n.length&&n.shift()()},!1),function(a){n.push(a),X.postMessage(t,"*")}})("axios@".concat(Math.random()),[]):function(t){return setTimeout(t)}})(typeof setImmediate=="function",U(X.postMessage)),Bt=typeof queueMicrotask!="undefined"?queueMicrotask.bind(X):typeof process!="undefined"&&process.nextTick||hr,f={isArray:te,isArrayBuffer:or,isBuffer:Yr,isFormData:ot,isArrayBufferView:Qr,isString:Zr,isNumber:sr,isBoolean:et,isObject:we,isPlainObject:ge,isReadableStream:ft,isRequest:ct,isResponse:lt,isHeaders:dt,isUndefined:ue,isDate:rt,isFile:tt,isBlob:nt,isRegExp:At,isFunction:U,isStream:it,isURLSearchParams:st,isTypedArray:gt,isFileList:at,forEach:fe,merge:Be,extend:ht,trim:pt,stripBOM:vt,inherits:mt,toFlatObject:yt,kindOf:ye,kindOfTest:q,endsWith:bt,toArray:wt,forEachEntry:Et,matchAll:St,isHTMLForm:Ot,hasOwnProperty:cr,hasOwnProp:cr,reduceDescriptors:lr,freezeMethods:Tt,toObjectSet:Pt,toCamelCase:Rt,noop:Ct,toFiniteNumber:kt,findKey:ur,global:X,isContextDefined:fr,ALPHABET:pr,generateString:Lt,isSpecCompliantForm:Nt,toJSONObject:jt,isAsyncFn:Ft,isThenable:Dt,setImmediate:hr,asap:Bt};function R(r,e,t,n,a){Error.call(this),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error().stack,this.message=r,this.name="AxiosError",e&&(this.code=e),t&&(this.config=t),n&&(this.request=n),a&&(this.response=a,this.status=a.status?a.status:null)}f.inherits(R,Error,{toJSON:function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:f.toJSONObject(this.config),code:this.code,status:this.status}}});var vr=R.prototype,mr={};["ERR_BAD_OPTION_VALUE","ERR_BAD_OPTION","ECONNABORTED","ETIMEDOUT","ERR_NETWORK","ERR_FR_TOO_MANY_REDIRECTS","ERR_DEPRECATED","ERR_BAD_RESPONSE","ERR_BAD_REQUEST","ERR_CANCELED","ERR_NOT_SUPPORT","ERR_INVALID_URL"].forEach(function(r){mr[r]={value:r}}),Object.defineProperties(R,mr),Object.defineProperty(vr,"isAxiosError",{value:!0}),R.from=function(r,e,t,n,a,i){var o=Object.create(vr);return f.toFlatObject(r,o,function(u){return u!==Error.prototype},function(s){return s!=="isAxiosError"}),R.call(o,r.message,e,t,n,a),o.cause=r,o.name=r.name,i&&Object.assign(o,i),o};var xt=null;function Ue(r){return f.isPlainObject(r)||f.isArray(r)}function yr(r){return f.endsWith(r,"[]")?r.slice(0,-2):r}function br(r,e,t){return r?r.concat(e).map(function(a,i){return a=yr(a),!t&&i?"["+a+"]":a}).join(t?".":""):e}function Ut(r){return f.isArray(r)&&!r.some(Ue)}var It=f.toFlatObject(f,{},null,function(e){return/^is[A-Z]/.test(e)});function Se(r,e,t){if(!f.isObject(r))throw new TypeError("target must be an object");e=e||new FormData,t=f.toFlatObject(t,{metaTokens:!0,dots:!1,indexes:!1},!1,function(S,m){return!f.isUndefined(m[S])});var n=t.metaTokens,a=t.visitor||l,i=t.dots,o=t.indexes,s=t.Blob||typeof Blob!="undefined"&&Blob,u=s&&f.isSpecCompliantForm(e);if(!f.isFunction(a))throw new TypeError("visitor must be a function");function c(b){if(b===null)return"";if(f.isDate(b))return b.toISOString();if(!u&&f.isBlob(b))throw new R("Blob is not supported. Use a Buffer instead.");return f.isArrayBuffer(b)||f.isTypedArray(b)?u&&typeof Blob=="function"?new Blob([b]):Buffer.from(b):b}function l(b,S,m){var C=b;if(b&&!m&&G(b)==="object"){if(f.endsWith(S,"{}"))S=n?S:S.slice(0,-2),b=JSON.stringify(b);else if(f.isArray(b)&&Ut(b)||(f.isFileList(b)||f.endsWith(S,"[]"))&&(C=f.toArray(b)))return S=yr(S),C.forEach(function(T,N){!(f.isUndefined(T)||T===null)&&e.append(o===!0?br([S],N,i):o===null?S:S+"[]",c(T))}),!1}return Ue(b)?!0:(e.append(br(m,S,i),c(b)),!1)}var h=[],y=Object.assign(It,{defaultVisitor:l,convertValue:c,isVisitable:Ue});function E(b,S){if(!f.isUndefined(b)){if(h.indexOf(b)!==-1)throw Error("Circular reference detected in "+S.join("."));h.push(b),f.forEach(b,function(C,A){var T=!(f.isUndefined(C)||C===null)&&a.call(e,C,f.isString(A)?A.trim():A,S,y);T===!0&&E(C,S?S.concat(A):[A])}),h.pop()}}if(!f.isObject(r))throw new TypeError("data must be an object");return E(r),e}function wr(r){var e={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+","%00":"\0"};return encodeURIComponent(r).replace(/[!'()~]|%20|%00/g,function(n){return e[n]})}function Ie(r,e){this._pairs=[],r&&Se(r,this,e)}var gr=Ie.prototype;gr.append=function(e,t){this._pairs.push([e,t])},gr.toString=function(e){var t=e?function(n){return e.call(this,n,wr)}:wr;return this._pairs.map(function(a){return t(a[0])+"="+t(a[1])},"").join("&")};function qt(r){return encodeURIComponent(r).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}function Er(r,e,t){if(!e)return r;var n=t&&t.encode||qt;f.isFunction(t)&&(t={serialize:t});var a=t&&t.serialize,i;if(a?i=a(e,t):i=f.isURLSearchParams(e)?e.toString():new Ie(e,t).toString(n),i){var o=r.indexOf("#");o!==-1&&(r=r.slice(0,o)),r+=(r.indexOf("?")===-1?"?":"&")+i}return r}var Ht=(function(){function r(){he(this,r),this.handlers=[]}return ve(r,[{key:"use",value:function(t,n,a){return this.handlers.push({fulfilled:t,rejected:n,synchronous:a?a.synchronous:!1,runWhen:a?a.runWhen:null}),this.handlers.length-1}},{key:"eject",value:function(t){this.handlers[t]&&(this.handlers[t]=null)}},{key:"clear",value:function(){this.handlers&&(this.handlers=[])}},{key:"forEach",value:function(t){f.forEach(this.handlers,function(a){a!==null&&t(a)})}}]),r})(),Sr=Ht,Or={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1},Mt=typeof URLSearchParams!="undefined"?URLSearchParams:Ie,_t=typeof FormData!="undefined"?FormData:null,zt=typeof Blob!="undefined"?Blob:null,$t={isBrowser:!0,classes:{URLSearchParams:Mt,FormData:_t,Blob:zt},protocols:["http","https","file","blob","url","data"]},qe=typeof window!="undefined"&&typeof document!="undefined",He=(typeof navigator=="undefined"?"undefined":G(navigator))==="object"&&navigator||void 0,Jt=qe&&(!He||["ReactNative","NativeScript","NS"].indexOf(He.product)<0),Gt=(function(){return typeof WorkerGlobalScope!="undefined"&&self instanceof WorkerGlobalScope&&typeof self.importScripts=="function"})(),Kt=qe&&window.location.href||"http://localhost",Wt=Object.freeze({__proto__:null,hasBrowserEnv:qe,hasStandardBrowserWebWorkerEnv:Gt,hasStandardBrowserEnv:Jt,navigator:He,origin:Kt}),B=se(se({},Wt),$t);function Vt(r,e){return Se(r,new B.classes.URLSearchParams,Object.assign({visitor:function(n,a,i,o){return B.isNode&&f.isBuffer(n)?(this.append(a,n.toString("base64")),!1):o.defaultVisitor.apply(this,arguments)}},e))}function Xt(r){return f.matchAll(/\w+|\[(\w*)]/g,r).map(function(e){return e[0]==="[]"?"":e[1]||e[0]})}function Yt(r){var e={},t=Object.keys(r),n,a=t.length,i;for(n=0;n=n.length;if(s=!s&&f.isArray(i)?i.length:s,c)return f.hasOwnProp(i,s)?i[s]=[i[s],a]:i[s]=a,!u;(!i[s]||!f.isObject(i[s]))&&(i[s]=[]);var l=e(n,a,i[s],o);return l&&f.isArray(i[s])&&(i[s]=Yt(i[s])),!u}if(f.isFormData(r)&&f.isFunction(r.entries)){var t={};return f.forEachEntry(r,function(n,a){e(Xt(n),a,t,0)}),t}return null}function Qt(r,e,t){if(f.isString(r))try{return(e||JSON.parse)(r),f.trim(r)}catch(n){if(n.name!=="SyntaxError")throw n}return(t||JSON.stringify)(r)}var Me={transitional:Or,adapter:["xhr","http","fetch"],transformRequest:[function(e,t){var n=t.getContentType()||"",a=n.indexOf("application/json")>-1,i=f.isObject(e);i&&f.isHTMLForm(e)&&(e=new FormData(e));var o=f.isFormData(e);if(o)return a?JSON.stringify(Rr(e)):e;if(f.isArrayBuffer(e)||f.isBuffer(e)||f.isStream(e)||f.isFile(e)||f.isBlob(e)||f.isReadableStream(e))return e;if(f.isArrayBufferView(e))return e.buffer;if(f.isURLSearchParams(e))return t.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),e.toString();var s;if(i){if(n.indexOf("application/x-www-form-urlencoded")>-1)return Vt(e,this.formSerializer).toString();if((s=f.isFileList(e))||n.indexOf("multipart/form-data")>-1){var u=this.env&&this.env.FormData;return Se(s?{"files[]":e}:e,u&&new u,this.formSerializer)}}return i||a?(t.setContentType("application/json",!1),Qt(e)):e}],transformResponse:[function(e){var t=this.transitional||Me.transitional,n=t&&t.forcedJSONParsing,a=this.responseType==="json";if(f.isResponse(e)||f.isReadableStream(e))return e;if(e&&f.isString(e)&&(n&&!this.responseType||a)){var i=t&&t.silentJSONParsing,o=!i&&a;try{return JSON.parse(e)}catch(s){if(o)throw s.name==="SyntaxError"?R.from(s,R.ERR_BAD_RESPONSE,this,null,this.response):s}}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:B.classes.FormData,Blob:B.classes.Blob},validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};f.forEach(["delete","get","head","post","put","patch"],function(r){Me.headers[r]={}});var _e=Me,Zt=f.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),en=(function(r){var e={},t,n,a;return r&&r.split(` -`).forEach(function(o){a=o.indexOf(":"),t=o.substring(0,a).trim().toLowerCase(),n=o.substring(a+1).trim(),!(!t||e[t]&&Zt[t])&&(t==="set-cookie"?e[t]?e[t].push(n):e[t]=[n]:e[t]=e[t]?e[t]+", "+n:n)}),e}),Ar=Symbol("internals");function ce(r){return r&&String(r).trim().toLowerCase()}function Oe(r){return r===!1||r==null?r:f.isArray(r)?r.map(Oe):String(r)}function rn(r){for(var e=Object.create(null),t=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g,n;n=t.exec(r);)e[n[1]]=n[2];return e}var tn=function(e){return/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(e.trim())};function ze(r,e,t,n,a){if(f.isFunction(n))return n.call(this,e,t);if(a&&(e=t),!!f.isString(e)){if(f.isString(n))return e.indexOf(n)!==-1;if(f.isRegExp(n))return n.test(e)}}function nn(r){return r.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,function(e,t,n){return t.toUpperCase()+n})}function an(r,e){var t=f.toCamelCase(" "+e);["get","set","has"].forEach(function(n){Object.defineProperty(r,n+t,{value:function(i,o,s){return this[n].call(this,e,i,o,s)},configurable:!0})})}var Re=(function(r,e){function t(n){he(this,t),n&&this.set(n)}return ve(t,[{key:"set",value:function(a,i,o){var s=this;function u(S,m,C){var A=ce(m);if(!A)throw new Error("header name must be a non-empty string");var T=f.findKey(s,A);(!T||s[T]===void 0||C===!0||C===void 0&&s[T]!==!1)&&(s[T||m]=Oe(S))}var c=function(m,C){return f.forEach(m,function(A,T){return u(A,T,C)})};if(f.isPlainObject(a)||a instanceof this.constructor)c(a,i);else if(f.isString(a)&&(a=a.trim())&&!tn(a))c(en(a),i);else if(f.isHeaders(a)){var l=Vr(a.entries()),h;try{for(l.s();!(h=l.n()).done;){var y=K(h.value,2),E=y[0],b=y[1];u(b,E,o)}}catch(S){l.e(S)}finally{l.f()}}else a!=null&&u(i,a,o);return this}},{key:"get",value:function(a,i){if(a=ce(a),a){var o=f.findKey(this,a);if(o){var s=this[o];if(!i)return s;if(i===!0)return rn(s);if(f.isFunction(i))return i.call(this,s,o);if(f.isRegExp(i))return i.exec(s);throw new TypeError("parser must be boolean|regexp|function")}}}},{key:"has",value:function(a,i){if(a=ce(a),a){var o=f.findKey(this,a);return!!(o&&this[o]!==void 0&&(!i||ze(this,this[o],o,i)))}return!1}},{key:"delete",value:function(a,i){var o=this,s=!1;function u(c){if(c=ce(c),c){var l=f.findKey(o,c);l&&(!i||ze(o,o[l],l,i))&&(delete o[l],s=!0)}}return f.isArray(a)?a.forEach(u):u(a),s}},{key:"clear",value:function(a){for(var i=Object.keys(this),o=i.length,s=!1;o--;){var u=i[o];(!a||ze(this,this[u],u,a,!0))&&(delete this[u],s=!0)}return s}},{key:"normalize",value:function(a){var i=this,o={};return f.forEach(this,function(s,u){var c=f.findKey(o,u);if(c){i[c]=Oe(s),delete i[u];return}var l=a?nn(u):String(u).trim();l!==u&&delete i[u],i[l]=Oe(s),o[l]=!0}),this}},{key:"concat",value:function(){for(var a,i=arguments.length,o=new Array(i),s=0;s1?o-1:0),u=1;u1&&arguments[1]!==void 0?arguments[1]:Date.now();t=h,a=null,i&&(clearTimeout(i),i=null),r.apply(null,l)},s=function(){for(var l=Date.now(),h=l-t,y=arguments.length,E=new Array(y),b=0;b=n?o(E,l):(a=E,i||(i=setTimeout(function(){i=null,o(a)},n-h)))},u=function(){return a&&o(a)};return[s,u]}var Ae=function(e,t){var n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:3,a=0,i=sn(50,250);return un(function(o){var s=o.loaded,u=o.lengthComputable?o.total:void 0,c=s-a,l=i(c),h=s<=u;a=s;var y=rr({loaded:s,total:u,progress:u?s/u:void 0,bytes:c,rate:l||void 0,estimated:l&&u&&h?(u-s)/l:void 0,event:o,lengthComputable:u!=null},t?"download":"upload",!0);e(y)},n)},Cr=function(e,t){var n=e!=null;return[function(a){return t[0]({lengthComputable:n,total:e,loaded:a})},t[1]]},kr=function(e){return function(){for(var t=arguments.length,n=new Array(t),a=0;a1?t-1:0),a=1;a1?`since : -`+c.map(Ur).join(` -`):" "+Ur(c[0]):"as no adapter specified";throw new R("There is no suitable adapter to dispatch the request "+l,"ERR_NOT_SUPPORT")}return i},adapters:Ge};function Ke(r){if(r.cancelToken&&r.cancelToken.throwIfRequested(),r.signal&&r.signal.aborted)throw new ne(null,r)}function qr(r){Ke(r),r.headers=H.from(r.headers),r.data=$e.call(r,r.transformRequest),["post","put","patch"].indexOf(r.method)!==-1&&r.headers.setContentType("application/x-www-form-urlencoded",!1);var e=Ir.getAdapter(r.adapter||_e.adapter);return e(r).then(function(n){return Ke(r),n.data=$e.call(r,r.transformResponse,n),n.headers=H.from(n.headers),n},function(n){return Tr(n)||(Ke(r),n&&n.response&&(n.response.data=$e.call(r,r.transformResponse,n.response),n.response.headers=H.from(n.response.headers))),Promise.reject(n)})}var Hr="1.7.9",Ce={};["object","boolean","number","function","string","symbol"].forEach(function(r,e){Ce[r]=function(n){return G(n)===r||"a"+(e<1?"n ":" ")+r}});var Mr={};Ce.transitional=function(e,t,n){function a(i,o){return"[Axios v"+Hr+"] Transitional option '"+i+"'"+o+(n?". "+n:"")}return function(i,o,s){if(e===!1)throw new R(a(o," has been removed"+(t?" in "+t:"")),R.ERR_DEPRECATED);return t&&!Mr[o]&&(Mr[o]=!0,console.warn(a(o," has been deprecated since v"+t+" and will be removed in the near future"))),e?e(i,o,s):!0}},Ce.spelling=function(e){return function(t,n){return console.warn("".concat(n," is likely a misspelling of ").concat(e)),!0}};function Tn(r,e,t){if(G(r)!=="object")throw new R("options must be an object",R.ERR_BAD_OPTION_VALUE);for(var n=Object.keys(r),a=n.length;a-- >0;){var i=n[a],o=e[i];if(o){var s=r[i],u=s===void 0||o(s,i,r);if(u!==!0)throw new R("option "+i+" must be "+u,R.ERR_BAD_OPTION_VALUE);continue}if(t!==!0)throw new R("Unknown option "+i,R.ERR_BAD_OPTION)}}var ke={assertOptions:Tn,validators:Ce},$=ke.validators,Le=(function(){function r(e){he(this,r),this.defaults=e,this.interceptors={request:new Sr,response:new Sr}}return ve(r,[{key:"request",value:(function(){var e=re(L().mark(function n(a,i){var o,s;return L().wrap(function(c){for(;;)switch(c.prev=c.next){case 0:return c.prev=0,c.next=3,this._request(a,i);case 3:return c.abrupt("return",c.sent);case 6:if(c.prev=6,c.t0=c.catch(0),c.t0 instanceof Error){o={},Error.captureStackTrace?Error.captureStackTrace(o):o=new Error,s=o.stack?o.stack.replace(/^.+\n/,""):"";try{c.t0.stack?s&&!String(c.t0.stack).endsWith(s.replace(/^.+\n.+\n/,""))&&(c.t0.stack+=` -`+s):c.t0.stack=s}catch(l){}}throw c.t0;case 10:case"end":return c.stop()}},n,this,[[0,6]])}));function t(n,a){return e.apply(this,arguments)}return t})()},{key:"_request",value:function(t,n){typeof t=="string"?(n=n||{},n.url=t):n=t||{},n=Y(this.defaults,n);var a=n,i=a.transitional,o=a.paramsSerializer,s=a.headers;i!==void 0&&ke.assertOptions(i,{silentJSONParsing:$.transitional($.boolean),forcedJSONParsing:$.transitional($.boolean),clarifyTimeoutError:$.transitional($.boolean)},!1),o!=null&&(f.isFunction(o)?n.paramsSerializer={serialize:o}:ke.assertOptions(o,{encode:$.function,serialize:$.function},!0)),ke.assertOptions(n,{baseUrl:$.spelling("baseURL"),withXsrfToken:$.spelling("withXSRFToken")},!0),n.method=(n.method||this.defaults.method||"get").toLowerCase();var u=s&&f.merge(s.common,s[n.method]);s&&f.forEach(["delete","get","head","post","put","patch","common"],function(T){delete s[T]}),n.headers=H.concat(u,s);var c=[],l=!0;this.interceptors.request.forEach(function(N){typeof N.runWhen=="function"&&N.runWhen(n)===!1||(l=l&&N.synchronous,c.unshift(N.fulfilled,N.rejected))});var h=[];this.interceptors.response.forEach(function(N){h.push(N.fulfilled,N.rejected)});var y,E=0,b;if(!l){var S=[qr.bind(this),void 0];for(S.unshift.apply(S,c),S.push.apply(S,h),b=S.length,y=Promise.resolve(n);E0;)n._listeners[i](a);n._listeners=null}}),this.promise.then=function(a){var i,o=new Promise(function(s){n.subscribe(s),i=s}).then(a);return o.cancel=function(){n.unsubscribe(i)},o},e(function(i,o,s){n.reason||(n.reason=new ne(i,o,s),t(n.reason))})}return ve(r,[{key:"throwIfRequested",value:function(){if(this.reason)throw this.reason}},{key:"subscribe",value:function(t){if(this.reason){t(this.reason);return}this._listeners?this._listeners.push(t):this._listeners=[t]}},{key:"unsubscribe",value:function(t){if(this._listeners){var n=this._listeners.indexOf(t);n!==-1&&this._listeners.splice(n,1)}}},{key:"toAbortSignal",value:function(){var t=this,n=new AbortController,a=function(o){n.abort(o)};return this.subscribe(a),n.signal.unsubscribe=function(){return t.unsubscribe(a)},n.signal}}],[{key:"source",value:function(){var t,n=new r(function(i){t=i});return{token:n,cancel:t}}}]),r})(),Cn=Pn;function kn(r){return function(t){return r.apply(null,t)}}function Ln(r){return f.isObject(r)&&r.isAxiosError===!0}var We={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511};Object.entries(We).forEach(function(r){var e=K(r,2),t=e[0],n=e[1];We[n]=t});var Nn=We;function _r(r){var e=new Ne(r),t=ir(Ne.prototype.request,e);return f.extend(t,Ne.prototype,e,{allOwnKeys:!0}),f.extend(t,e,null,{allOwnKeys:!0}),t.create=function(a){return _r(Y(r,a))},t}var k=_r(_e);return k.Axios=Ne,k.CanceledError=ne,k.CancelToken=Cn,k.isCancel=Tr,k.VERSION=Hr,k.toFormData=Se,k.AxiosError=R,k.Cancel=k.CanceledError,k.all=function(e){return Promise.all(e)},k.spread=kn,k.isAxiosError=Ln,k.mergeConfig=Y,k.AxiosHeaders=H,k.formToJSON=function(r){return Rr(f.isHTMLForm(r)?new FormData(r):r)},k.getAdapter=Ir.getAdapter,k.HttpStatusCode=Nn,k.default=k,k})); diff --git a/docs/PC后端补齐清单.md b/docs/PC后端补齐清单.md deleted file mode 100644 index cce5450..0000000 --- a/docs/PC后端补齐清单.md +++ /dev/null @@ -1,140 +0,0 @@ -# PC 后端补齐清单(交给后端 GPT) - -> 状态:待后端确认与实施 -> -> 前端基线:`ac58235`(“验证20%”)之后的 PC 工作区。当前前端回归为 362 项通过。 -> -> 本文目的:让后端直接知道 PC 端还缺什么能力、每项最小需要补什么,以及如何验收。它是 `PC接口对接规划.md` 中 C01–C46 的精简交接版;详细的历史证据仍以该文档为准。 - -## 0. 不可违反的边界 - -1. 这是 **PC** 契约补齐,不是把 `Jiapu-App` 的接口、参数、缓存键或数据结构搬过来。路径和 DTO 以 PC 后端为唯一 owner。 -2. 请先在 PC 的 Apifox/OpenAPI 中定义完整请求、响应、权限、枚举和错误码,再实现 Controller/Service;完成后重新导出契约。前端只会通过 `utils/ApiClient.js` 接入已确认的 PC 契约。 -3. 不要以泛型 `ObjectResult`、无元素类型的列表或“前端自行猜字段”作为交付。每个列表、详情和写入结果都必须有明确 DTO。 -4. 所有资源 ID、OSS ID、订单号等可能超过 JavaScript 安全整数的值,统一以十进制字符串传输和返回;不得要求用户手填内部 ID。 -5. 不返回或展示成员、邀请人、通知发送人的原始手机号。确有业务必要的内部用户 ID 只在受权限保护的选项或写入场景返回,PC 页面不会展示它。 -6. 不记录、更不要求提交真实测试账号、密码、令牌或真实家谱数据。角色验收使用既有隔离身份或后端准备的隔离夹具。 - -## 1. 已有能力,不要重复实现 - -以下 PC 能力已被当前前端使用:登录/账户基础资料、家谱列表与创建、申请加入与审核、成员名册与角色维护、成员选项、世系人物及四类关系新增、字辈、家族圈常规 CRUD、视频、成长/亲友/备忘/功德记录、谱文、相册/照片、祭祀活动/祭品/活动邀约、通知已读、反馈工单、会员套餐与会员订单、帮助文章、PC 推广和官网资讯。 - -本清单要求补的是这些能力的缺口或契约精度,不要求重新设计上述已有流程。已有写入接口仍必须由后端做最终权限校验,不能只依赖前端隐藏按钮。 - -## 2. P0:PC 当前完全缺失的业务契约 - -| 编号 | 前端位置与业务目标 | 后端必须补齐的最小能力 | 验收标准 | -| --- | --- | --- | --- | -| P0-01 | `profile-data-reminders.html`:查看当前家谱中待完善的成员资料 | 只读“资料缺项提醒”列表,按当前家谱和当前操作者权限过滤。每项至少返回稳定的成员/世系人物定位 ID、脱敏展示名、缺失字段枚举、可处理状态和更新时间;缺失字段至少能区分头像、出生日期、关系等。联系方式只能表达“缺失/已填写”,不能下发原始号码。 | 有权限角色能读取真实空列表和非空列表;无权限、无家谱、资源不存在分别有可区分失败;前端能从响应跳到已有成员/世系编辑页。 | -| P0-02 | `profile-invite.html`、家谱主页:邀请家人加入家谱 | 家谱成员邀请的创建、活动邀请列表、撤销/失效、受邀人校验和受邀人接受流程。邀请方式如为链接或邀请码,响应需给出 `inviteId`、受限邀请凭据/链接、过期时间、使用次数或上限、状态及可展示的创建时间;不得把可枚举的内部 ID 当邀请码。 | 仅后端定义的可管理角色可创建和撤销;过期、撤销、次数用尽、非受邀人、重复接受均有稳定业务错误;接受后能在家谱成员/审核链路中重读结果。 | -| P0-03 | `profile-family-home.html`:重要证件 | 家谱重要证件的列表、详情、新增、修改、删除(或明确仅支持其中一部分)。证件 DTO 至少含稳定 ID、家谱 ID、标题/类型、说明、文件对象、创建/更新时间、状态和权限能力。文件必须使用第 3.1 节的安全文件对象。 | 无权用户不能读写他人家谱证件;上传后重读同一条记录;删除后的可见性和是否可恢复有明确定义。 | -| P0-04 | `profile-families.html`:我的家谱自定义排序 | 读取当前账号的家谱顺序及保存顺序的接口。保存请求应提交完整有序的“当前账号可见家谱 ID”数组,后端在一个事务内校验归属、去重和完整性,不接受跨账号或不可见家谱 ID。 | 重排后重新读取顺序一致;重复、缺失、越权 ID 返回明确校验/权限错误;并发保存不会产生重复排序值。 | -| P0-05 | `profile-family-home.html`:删除家谱 | 先由产品/后端确定唯一语义:**逻辑归档** 或 **物理删除**,不能接口名叫删除而实际效果不明确。接口需只允许谱主执行,并返回影响范围(成员、人物、内容、文件、订单等)、最终状态和可否恢复;如需二次确认,应采用后端签发的一次性确认令牌或明确确认字段。 | 非谱主必定拒绝;成功后“我的家谱”重读不再出现或以归档状态出现;重复请求幂等;关联资源处理与定义一致。 | -| P0-06 | `profile-share.html`、`profile-services.html`:应用分享、奖励、推广收益、提现 | 一套完整账户权益契约:分享规则/分享凭据、分享记录、奖励或收益流水、账户可用/冻结/累计余额、提现方式选项、提现申请及提现记录。金额和状态必须由服务端计算与驱动,前端不能传入奖励金额、余额或审核状态。 | 普通用户只能读取自己的记录;提现前校验可用余额、金额精度、最低/最高额、风控状态和收款方式;重复提交幂等;流水、余额与提现状态可重读且一致。 | -| P0-07 | `profile-services.html`:个性化推荐开关 | 当前用户的推荐偏好读取与保存。至少定义可配置项枚举、默认值、保存后的版本/更新时间,以及未登录、无权限或功能未开通的处理。 | 未知枚举和值非法会被拒绝;保存后重读一致;不允许操作其他用户的偏好。 | -| P0-08 | `profile-services.html`:会员订单在线支付 | 现有“创建订单/读取订单”之外,补支付发起、支付参数、安全回跳/异步通知后的订单状态查询和关闭/超时语义。`payType` 必须有正式枚举,订单状态机由后端维护,不能让前端提交“已支付”。 | 创建支付后返回可安全使用的支付会话参数;支付成功、失败、取消、超时、退款均能重读到准确状态;重复通知和用户刷新幂等。 | - -## 3. P1:已有 PC 页面被契约限制的能力 - -### 3.1 统一安全文件访问能力(C21、C23、C25、C28、C30、C36、C38、C40) - -下列资源目前大多只返回 `ossId` 或 `mediaOssIds`,PC 无法安全回显图片、封面、视频、附件名称或下载入口:家族圈、视频、成长记录、亲友记录、备忘录、谱文、相册/照片、祭祀活动。 - -请在资源 View 内直接返回文件对象数组,或提供一个受资源权限保护的 PC 文件解析接口。两种方案只保留一种,推荐统一文件对象: - -| 字段 | 要求 | -| --- | --- | -| `ossId` | string,原始文件稳定标识 | -| `fileName` | 可安全展示的文件名 | -| `mediaType` | 明确枚举,例如 image/video/document | -| `accessUrl` | 短时授权 URL;不能由前端用 OSS ID 拼接 | -| `expiresAt` | 授权 URL 失效时间,使用统一日期时间格式 | - -文件解析必须根据“该文件所属业务资源 + 当前操作者”校验权限,不能做成任意登录用户可按 OSS ID 探测或下载文件的接口。 - -### 3.2 停用记录的管理、详情与恢复(C22、C24、C26、C29、C31、C33、C37、C39、C41) - -以下资源可以或可能写入 `status=1`,但普通列表不返回、普通详情拒绝读取,导致管理员无法恢复或验证:家族圈、视频、成长记录、亲友记录、备忘录、功德、谱文、相册/照片、祭祀活动。 - -请为每类资源提供唯一且一致的方案: - -1. 给有管理权限的用户提供 `management` 列表和详情,包含正常/停用状态;或 -2. 提供受权限保护的独立状态读取与恢复接口。 - -必须明确 `status=0/1` 的中文业务含义、谁可停用/恢复、停用后文件和关联数据如何处理、普通用户是否仍可读取。前端在此完成前会持续固定提交正常状态,避免产生不可重读数据。 - -### 3.3 金额、分类与可清空字段(C18、C27、C32、C35、C42) - -| 范围 | 缺失内容 | 必须定义 | -| --- | --- | --- | -| 亲友往来 `giftAmount`、功德金额、祭品金额 | 只有 number/BigDecimal,缺少精度和边界 | 货币单位、`precision`、`scale`、`minimum`、`maximum`、是否允许负数及其业务含义。建议金额响应使用十进制字符串,避免浮点误差。 | -| 谱文分类 | 列表的 `categoryId` 未进入正式契约,且没有分类选项来源 | 正式 query、当前家谱文章分类选项接口、分类 DTO、是否允许无分类和分类停用后的处理。 | -| 所有可选更新字段 | 未定义“不传字段”、`null`、空字符串、空数组的差别 | 每个更新 DTO 对每个可清空字段写明语义,并用后端测试覆盖。特别是备忘录提醒时间、说明、附件和成员绑定。 | - -### 3.4 个人资料、成员、通知与关系契约(C04–C08、C11–C13、C19、C34、C43–C46) - -| 范围 | 当前问题 | 后端补齐要求 | -| --- | --- | --- | -| 当前用户资料 | 资料读取仍可能是泛型响应;更新 DTO 没有地区字段 | 提供完整 `ProfileView`;如产品保留现居地区,更新/读取都使用 `regionCode`;明确头像 ID 类型和所有可清空字段语义。 | -| 行政区划 | 历史上存在双路径和无元素 DTO | 只保留一套 PC 正式路径;`RegionView` 固定 `regionCode`、`regionName`、`regionLevel`、父级关系;删除旧路径。 | -| 世系树 | `LineagePersonTreeView` 曾无属性 | OpenAPI 展开递归节点:人物 ID、基础展示资料、配偶、子女及必要的状态/权限。所有日期必须声明 `date` 或带时区规则的 `date-time`。 | -| 枚举与能力 | `completed`、`feedType`、`recordType` 等可能无完整枚举;多数 View 缺 `canEdit/canDelete` | 明确自由文本或完整枚举;对页面需要的操作返回稳定的严格布尔能力字段,例如 `canManage`、`canEditContent`、`canEdit`、`canDelete`。后端仍做最终鉴权。 | -| 通知 | 声明的家谱名、发送人、业务摘要等字段实际常为空,且含隐私风险 | 补齐真正可用的非敏感字段,或从 Schema 删除不会返回的字段;提供安全、明确的 `bizType/bizId` 深链规则,不能让前端猜路径;始终不返回发送人手机号。 | -| 家谱成员 | `GenealogyMemberVo` 声明字段与实际填充不一致,角色枚举也不一致 | View 补齐真实需要的非敏感展示字段,或收紧 Schema;可分配角色正式收敛为后端实际支持的 `admin`、`editor`、`member`,不能接受 `owner`、`visitor`。 | -| 世系关系解除 | 只有新增关系,没有解除关系 | 新增带家谱权限与关系完整性校验的解除接口;需明确父母、配偶、子女、兄弟姐妹解除后的双向影响。 | -| 成员与世系人物解绑 | 当前更新中空 `lineagePersonId` 只代表不修改 | 提供专用解绑动作,或正式约定 `null`/空值表示解绑并给出可验证示例;不能由前端猜测。 | -| 个人亲属关系 | `profile-data.html` 当前无个人关系数据 | 这是独立于家谱世系的产品决策:若保留个人关系页,请提供当前用户私有的列表/维护契约;若不做,请明确下线该需求,继续引导到家谱世系维护。 | - -## 4. P2:契约质量与一致性必须同时修复 - -这些事项不一定新增页面,却会使 PC 前后端长期出现“接口能调但不知道如何正确用”的问题: - -1. 删除/废弃缺少开头 `/` 的旧短信路径,只保留正式 PC 路径。 -2. 未登录可调用的注册、登录、短信、找回密码、验证接口在 OpenAPI 中标为公开;登录后接口标明 Bearer 鉴权。不要让公开接口错误继承 Authorization。 -3. 所有列表、详情、上传初始化/完成、写入结果都展开具体 Schema,尤其不能保留无元素的通用列表。上传初始化要明确 `uploadId`、秒传、已传分片、完成后的 OSS 信息。 -4. 用统一错误响应表达:未登录、无权限、资源不存在、字段校验失败、业务冲突、状态不可操作、频率/幂等冲突。每一类要有稳定 code 和用户可读 message;不要只返回模糊失败文本。 -5. 统一日期格式、时区、分页字段和排序字段;同一资源的列表、详情、创建、更新应使用同一 ID 类型和状态枚举。 -6. 更新 Apifox/OpenAPI、DTO、Controller、Service 和集成测试必须在同一提交完成;不要只改其中一处,也不要保留 APP 路径 fallback。 - -## 5. 权限与数据安全验收矩阵 - -后端实施每个新增或补齐接口后,至少用既有隔离的谱主、管理员/编辑、普通成员、非成员和未登录身份验证。不要为了验收在真实家谱中创建成员或写入数据。 - -| 场景 | 必须验证 | -| --- | --- | -| 未登录 | 仅公开认证接口可用;所有用户/家谱资源拒绝。 | -| 非成员 | 不能读取私有家谱内容、成员、文件、邀请或管理列表。 | -| 普通成员 | 只能使用后端允许的只读或本人操作,不能修改成员角色、家谱设置、排序、删除、证件或全局邀请。 | -| 管理员/编辑 | 仅获得已声明能力范围内的内容或成员维护权限;不得越过谱主做家谱删除、谱主转让等操作。 | -| 谱主 | 可执行后端定义的家谱级管理动作,但仍受资源状态、确认和业务完整性约束。 | - -每个写入接口还要验证:重复请求幂等、写后可精确重读、越权返回明确错误、停用/恢复状态可验证、敏感字段不会越权泄漏。 - -## 6. 建议实施顺序 - -1. 先完成第 4 节的 OpenAPI/DTO 基础收口,并冻结唯一 PC 契约。 -2. 其次完成 3.1 文件访问和 3.2 停用管理;这两项能同时解除多个已有内容页的展示和状态闭环。 -3. 再完成 3.3、3.4 的金额、分类、资料、成员和关系精度。 -4. 最后按 P0-01 至 P0-08 补齐新业务;其中家谱删除、提现和支付必须先确认产品状态机与风控规则,不能只给一个无约束写接口。 - -## 7. 给后端 GPT 的执行指令 - -```text -你负责为现有“家谱”项目补齐 PC API,不得复用或复制 Jiapu-App API。 - -先阅读 docs/PC后端补齐清单.md 与 docs/PC接口对接规划.md,确认当前 PC OpenAPI 和 Controller 的实际差异;不要重做“已有能力”章节列出的功能。 - -按第 6 节顺序实施。每个模块都要在同一改动中完成:PC OpenAPI/Apifox、请求 DTO、响应 View、Controller/Service、权限校验、错误码和集成测试。不要返回泛型 Object,不要让前端手填内部 ID,不要返回原始手机号,也不要新增 APP 路径兼容层。 - -实施前先输出:将新增/修改的 PC 接口、每个请求和响应字段、角色权限、状态机、数据库迁移(如有)以及与本清单的对应编号。实施后用隔离角色完成读写、越权、重复提交和写后重读验证,并重新导出 OpenAPI。 -``` - -## 8. 交付回执模板 - -后端完成后,请按以下格式回传,便于前端逐项接入: - -| 清单编号 | 状态 | 正式 PC method/path | 请求 DTO | 响应 View | 权限/状态机 | OpenAPI 位置 | 集成测试证据 | -| --- | --- | --- | --- | --- | --- | --- | --- | -| 例如 P0-01 | 已完成/不做(附产品结论) | 仅填 PC 路径 | 字段和必填性 | 字段和类型 | 可用角色与错误码 | tag/schema 名 | 隔离测试名称,不含真实凭据 | - -如某项决定不做,必须给出明确产品结论和对应前端页面应继续保留的不可操作文案;不能以“暂时没有接口”结束。 diff --git a/docs/PC接口对接规划.md b/docs/PC接口对接规划.md deleted file mode 100644 index eec2923..0000000 --- a/docs/PC接口对接规划.md +++ /dev/null @@ -1,1666 +0,0 @@ -# PC 接口字段与页面落地规划(供 AI 执行) - -> 文档状态:规划,不代表当前代码已经完成 -> 基线日期:2026-07-27 -> 当前本地快照:根目录 `PC.openapi.json`,OpenAPI 3.1.0 -> 使用对象:另一台电脑上的执行 AI -> 本轮边界:只写规划;执行 AI 才能修改页面、脚本和测试 - -## 0. 强制要求:必须打开 Apifox 查看 - -执行 AI 在修改任何代码前,必须实际登录并打开 Apifox 的“家谱”项目,进入每一个本轮接口的详情页逐项查看。不得只读取 `PC.openapi.json`、Apifox 本地缓存、旧截图或历史文档。 - -每个接口都必须在 Apifox 详情页记录: - -1. method、完整 path、接口状态和所属 PC 目录; -2. Path、Query、Header、Cookie 和 Body 的全部字段; -3. 每个字段的类型、必填/可选/条件必填、默认值、枚举、格式、长度和取值范围; -4. 字段说明、示例值、关联字典和条件显示规则; -5. 请求体中折叠对象、数组元素和嵌套字段的完整结构; -6. 成功响应、失败响应、列表元素、详情对象和分页结构的完整字段; -7. 登录要求、角色权限、业务前置条件和错误码; -8. Apifox 页面有但 OpenAPI 导出遗漏的字段或说明。 - -核对结果必须写入本规划对应模块的字段表,并为每个字段标明 U/S/F/A/R/I/V 分类、使用页面、数据来源和提交时机。Apifox 详情没有确认的字段不得进入代码;Apifox 与导出文件不一致时,以现场核对结果形成问题清单,先让接口 owner 修正并重新导出,再实施。 - -## 1. 最终目标 - -执行 AI 要把 Apifox 中的 PC 接口准确落到现有 PC 页面,最终同时满足: - -1. 请求体中的必填字段、可选字段全部有明确页面归属。 -2. 字段被明确分成: - - 用户输入; - - 用户选择; - - 文件选择后自动生成; - - 页面上下文自动带入; - - 接口响应后只读展示; - - 仅内部保存、不得展示或手工填写; - - 纯前端校验字段、不得发给后端。 -3. ID、Token、`clientid`、`tenantId`、OSS ID、分页参数等系统字段不得让用户手工输入。 -4. 下拉框和选择器必须有真实数据源;没有选项接口时不得改成“请输入 ID”。 -5. Apifox 未展开的响应 DTO、未声明的枚举和互相冲突的路径不得靠前端猜测。 -6. APP 接口不得用于填补 PC 接口缺口。 -7. 所有家谱内业务必须使用真实 `genealogyId`,不得写死示例值。 -8. 每一阶段必须有契约测试、页面行为测试和真实联调结果。 - -## 2. 执行 AI 的工作协议 - -执行 AI 开始前必须按顺序完成: - -1. 阅读项目根目录 `AGENTS.md`。 -2. 检查工作树,只修改本计划点名的文件;保留用户已有改动。 -3. 登录 Apifox,打开“家谱”项目,逐条核对实时 PC 接口详情。 -4. 从 Apifox 重新导出最新 OpenAPI,和仓库 `PC.openapi.json` 做结构化差异比较。 -5. 完成第 5 节“契约阻断项”;阻断项未解决的模块只保留页面设计,不接真实写操作。 -6. 按第 8 节阶段顺序施工,每完成一阶段立即验证,不能把所有模块一次性混改。 - -遇到以下情况时,停止当前模块并整理成一份待后端确认清单,同时继续其他不受影响的模块: - -- Apifox 页面和导出文件的 method、path、必填性、枚举或类型不一致; -- 响应仍是无属性的 `ObjectResult` / `ListResult`; -- 选择字段没有合法选项来源; -- 更新接口没有说明“字段不传”和“传 null/空串”的区别; -- 页面需要的业务能力在 PC 目录中不存在。 - -不得通过兼容多个旧字段、读取 APP DTO、保留旧路径 fallback 或展示原始 JSON 绕过阻断。 - -## 3. 契约依据与优先级 - -发生冲突时按以下顺序处理: - -1. Apifox “家谱”项目中当前 PC 接口详情; -2. 从该详情当场重新导出的 OpenAPI 文件; -3. 本规划中的字段和流程分类; -4. 当前仓库代码; -5. 旧规划和历史交接记录。 - -通常以 Apifox 的 PC 目录作为 PC 前端接口的正式契约源。2026-07-28 用户明确指定本轮直接以 `D:/WorkSpace/Java/Genealogy/doc/apifox/genealogy-pc-openapi.yaml` 对接,因此该 YAML 是本轮冻结契约;在线 Apifox 的旧重复记录不再覆盖它。`utils/ApiClient.js` 是前端路径和请求方法的唯一 owner。页面脚本只能调用 `ApiClient` 业务方法,不能直接拼 URL 或调用 Axios。 - -旧 OpenAPI 文件不再作为新增功能依据,只用于结构化差异比较和追踪导出遗漏。 - -如果 Apifox 在线内容发生变化,先更新本地 OpenAPI、本规划的受影响章节和契约测试,再修改运行时代码。不得让文档、测试和运行时同时保留两套契约。 - -## 4. 当前基线与复核结论 - -### 4.1 当前导出范围 - -当前 `PC.openapi.json` 共 96 个操作、15 个标签、68 个 Schema: - -| 模块 | 操作数 | 当前主要页面 | -| --- | ---: | --- | -| 验证中心 | 3 | 登录、注册、找回密码、安全设置 | -| 认证登录 | 11 | `login.html`、`register.html`、`forgot-password.html`、个人资料和安全页 | -| 文件上传 | 3 | 所有头像、图片、附件和视频选择控件 | -| 家谱 | 1 | `profile-families.html` | -| 家族圈 | 14 | `profile-feed.html`、`profile-feed-edit.html`、待新增动态详情页 | -| 行政区划 | 8 | 个人资料及以后需要地区的表单 | -| 字辈谱 | 6 | `profile-generation.html` | -| 世系人物 | 12 | `profile-tree.html` | -| 内容文章 | 1 | `profile-article.html`、`profile-article-edit.html` | -| 相册 | 2 | `profile-album.html` | -| 视频 | 5 | `profile-video.html`、待新增视频编辑页 | -| 贺礼邀约 | 4 | `profile-gift.html`、邀约管理面板 | -| 祭祀 | 2 | `profile-gift.html` | -| 族务记录 | 20 | 成长、亲友、备忘录、功德录页面 | -| 消息通知 | 4 | `profile-messages.html` | -| **合计** | **96** | | - -### 4.2 已对照到的 Apifox 现状 - -本机 Apifox 缓存中的“家谱”项目包含 APP、PC、共享区划等 246 条接口记录。当前导出的 96 条路径均能在该项目缓存的接口树中找到,但存在以下差异: - -1. Apifox 缓存同时保留了两条逻辑相同的短信接口: - - 错误旧路径:`genealogy/pc/auth/sms/{operationCode}/code` - - 正确路径:`/genealogy/pc/auth/sms/{operationCode}/code` - - 当前导出只保留正确路径。执行前必须在 Apifox 删除或正式废弃缺少 `/` 的旧定义,前端只实现正确路径。 -2. 导出文件同时含 `/genealogy/region/*` 和 `/genealogy/pc/region/*` 两套共 8 个区划操作;两套的参数、响应和描述完全相同。执行前必须由后端选定一套唯一正式路径并删除另一套。选定后实际唯一操作数应从 96 收口为 92。 -3. 旧版“85 接口规划”已经失效。新增范围主要包括 PC 区划 4 条、视频列表/新增/详情/修改 4 条、功德列表/新增/详情/修改 4 条。 -4. 当前 96 个操作全部被声明为需要 `Authorization`,连注册、登录、短信发送和验证中心也不例外。这与未登录流程冲突,必须在 Apifox 逐项修正 security。 -5. 当前有 21 个成功响应使用无业务属性的 `ObjectResult`,12 个使用无元素 Schema 的 `ListResult`;另有 `LineagePersonTreeView` 名义上是专用 DTO,但没有任何属性。 - -### 4.3 2026-07-28 补齐版 YAML 与在线 Apifox 复核 - -补齐文件 `genealogy-pc-openapi.yaml` 含 98 个 path、137 个 HTTP operation、17 个 tag、319 个 schema;旧 `PC.openapi.json` 含 71 个 path、96 个 operation、68 个 schema。补齐文件新增了家谱、成员、相册、文章、祭祀、通知、反馈、VIP 和官网内容等路径及 DTO,但它还不能替代在线 Apifox: - -1. 已实际登录并打开 Apifox“家谱”项目的 PC 模块。在线概览显示 **142 个接口、87 个数据模型**,与补齐 YAML 的 137 个 HTTP operation 不一致。 -2. 在线“认证登录”目录仍显示 **12 个接口**,同时存在: - - 错误旧路径:`genealogy/pc/auth/sms/{operationCode}/code` - - 正确路径:`/genealogy/pc/auth/sms/{operationCode}/code` - - 补齐 YAML 只包含正确路径,因此 C02 在线尚未完成。 -3. 补齐 YAML 给注册、密码登录、短信登录和短信发送标了 `security: []`;在线密码登录详情仍展示必需的 `Authorization` API Key,并显示 `Auth 1`。因此 C03 的导出定义和在线详情冲突。 -4. 在线 `GET /genealogy/pc/auth/profile` 成功响应仍为泛型 `ObjectResult`;展开后的 `data` 只有任意附加属性,没有 `ProfileView` 字段。补齐 YAML 同样引用泛型 `ObjectResult`,C04/C06 未完成。 -5. `ProfileUpdateBody.avatar` 仍为 `integer`,而 `FileUploadVo.ossId`、`LineagePersonView.avatarOssId` 等 OSS ID 为 `string`;部分头像字段也仍为 `integer`。C10 未完成。 -6. 已用真实登录响应和后端 `AppLoginVo` 复核:token 的唯一 JSON 字段是 `access_token`;原 YAML 的 `token`、`accessToken`、`tokenValue`、`userId`、`tenantId`、`clientId` 均不是该响应的直接字段,现已删除。 -7. `ProfileUpdateBody` 仍只有 `nickName`、`realName`、`avatar`、`sex`、`birthday`、`email`,没有地区字段,也没有说明缺省、`null`、空串的清空语义。C07/C18 未完成。 - -用户已明确要求本轮直接按补齐 YAML 施工,所以在线旧短信路径和公开接口 security 残留不再阻断本轮实现。YAML 自身仍存在的 profile 泛型响应、头像 ID 类型冲突和更新清空语义缺失继续保留为阻断项,前端不得自行补造。 - -## 5. 实施前必须解决的契约阻断项 - -以下事项不是前端自由选择,必须先在 Apifox 明确: - -| 编号 | 问题 | 影响 | 完成条件 | -| --- | --- | --- | --- | -| C01 | 两套区划路径完全重复 | 页面和客户端会形成双路径 | Apifox 只保留一套;导出和客户端只出现一次 | -| C02 | 短信接口有缺少开头 `/` 的旧记录 | 可能生成错误 URL | 删除/废弃旧记录,只保留正确路径 | -| C03 | 所有操作均要求 Authorization | 未登录流程无法成立 | 注册、登录、找回、验证、短信等逐项标明公开;登录后接口明确鉴权 | -| C04 | 33 个操作使用泛型对象/列表响应 | 列表、详情、编辑回填和 ID 来源不确定 | 为每个业务资源增加明确 View/DTO Schema | -| C05 | `LineagePersonTreeView` 没有属性 | 世系树只能猜字段 | 补齐节点 ID、人物资料、配偶、子女和递归结构 | -| C06 | 用户资料响应未展开 | 个人资料无法可靠回填 | 增加 `ProfileView`,明确所有可读字段 | -| C07 | `ProfileUpdateBody` 没有地区字段 | 现居地区无法保存 | 明确增加地区编码字段或删除“保存地区”功能 | -| C08 | 区划响应没有元素 DTO | 下拉选项的 code/name/level 不受契约约束 | 增加 `RegionView` 及列表/路径响应 | -| C09 | 上传初始化响应未展开,却描述了 `instant=true` | 秒传、续传和 `uploadId` 状态不明确 | 增加初始化响应 DTO,明确 `instant`、已传分片和 OSS 信息 | -| C10 | OSS ID 类型冲突 | 可能发生 JS 精度丢失或校验失败 | 所有 `ossId`、`avatar`、`avatarOssId`、视频和附件 ID 统一为 string | -| C11 | `completed` 只有 string 类型,无枚举 | 不能决定复选框提交 `0/1`、true/false 或其他值 | Apifox 增加枚举和中文含义 | -| C12 | `feedType`、`recordType`、`registerSource` 等无枚举 | 无法判断文本输入还是选择项 | 明确为自由文本,或补全枚举 | -| C13 | 多个日期字段没有 format/时区规则 | 页面值和后端解析可能不一致 | 明确 date 或 date-time,并统一时区 | -| C14 | 已由后端 PC 实链接决 | `GET /genealogy/pc/genealogies/mine` 和 `/options` 返回完整 `AppGenealogyVo`,`genealogyId` 从响应取得 | 前端只从真实列表选择,不允许手填 ID | -| C15 | `bindingMode=SPECIFIED` 需要 `appUserId`,但无业务用户选项接口 | 不能提供合法选择器 | 增加家谱成员/业务用户选项接口 | -| C16 | 邀约的 `inviteeUserIds` 没有选项接口 | 不能让管理员选择受邀人 | 增加同一家谱成员选项接口 | -| C17 | 视频、成长、亲友、备忘、功德、通知响应未展开 | 无法安全展示、编辑或单条操作 | 分别增加资源 View 和列表/详情响应 | -| C18 | 更新接口未说明缺省、null、空串语义 | 可选字段可能无法清空 | Apifox 对每个可清空字段写明更新规则 | -| C19 | 多数 View 没有 `canEdit` / `canDelete` 等权限字段 | 页面无法可靠决定按钮可见性 | 增加能力字段,或提供明确且可实现的角色规则 | -| C20 | 文章、相册、祭祀只有删除操作 | 没有真实 ID 来源和完整资源流程 | 补全 PC 列表/详情/新增/修改后才开放页面 | -| C21 | 家族圈动态只返回 `mediaOssIds`,PC 没有 OSS ID 批量解析/访问 URL 接口 | 已上传图片只能统计数量,不能在列表和详情安全回显缩略图 | PC 增加按 OSS ID 返回授权 URL、文件名和媒体类型的接口,或让动态 View 直接返回媒体对象数组 | -| C22 | 停用动态不会出现在普通列表,普通详情接口也拒绝读取 | 管理员可以停用,但停用后无法从 PC 页面重新读取、恢复或验证最终状态 | 增加包含停用记录的管理列表/详情,或增加独立的状态恢复接口 | -| C23 | `VideoVo` 只返回 `coverOssId` / `videoOssId`,没有 PC 文件访问 URL 或解析接口 | 可以维护视频记录,但不能安全播放视频或回显封面 | 视频 View 直接返回授权 URL,或增加按 OSS ID 获取文件访问地址的 PC 接口 | -| C24 | 视频列表只返回正常记录,详情拒绝读取停用记录 | `status=1` 后无法从页面重新读取和恢复 | 增加视频 management 列表/详情,或增加独立状态恢复接口 | -| C25 | `GrowthRecordVo` 只返回附件 `mediaOssIds`,没有 PC 文件访问 URL 或解析接口 | 可以维护附件引用,但不能安全预览、下载或回显文件名 | 成长记录 View 返回附件对象,或增加按 OSS ID 获取授权地址、文件名和类型的 PC 接口 | -| C26 | 成长记录列表只返回正常记录,详情拒绝读取停用记录 | `status=1` 后无法写后重读、恢复或再次编辑 | 增加成长记录 management 列表/详情,或增加独立状态恢复接口 | -| C27 | `giftAmount` 只有 `number` / `BigDecimal` 类型,没有精度、范围和正负规则 | 前端不能自行限定两位小数、最大金额或禁止负数 | Apifox 明确 scale、precision、minimum/maximum 和负数业务含义 | -| C28 | `RelativeRecordVo` 只返回附件 `mediaOssIds`,没有 PC 文件访问 URL 或解析接口 | 可以维护附件引用,但不能安全预览、下载或回显文件名 | 亲友记录 View 返回附件对象,或增加按 OSS ID 获取授权地址、文件名和类型的 PC 接口 | -| C29 | 亲友记录列表只返回正常记录,详情拒绝读取停用记录 | `status=1` 后无法写后重读、恢复或再次编辑 | 增加亲友记录 management 列表/详情,或增加独立状态恢复接口 | -| C30 | `MemoVo` 只返回附件 `mediaOssIds`,没有 PC 文件访问 URL 或解析接口 | 可以维护附件引用,但不能安全预览、下载或回显文件名 | 备忘录 View 返回附件对象,或增加按 OSS ID 获取授权地址、文件名和类型的 PC 接口 | -| C31 | 备忘录列表只返回正常记录,详情拒绝读取停用记录 | `status=1` 后无法写后重读、恢复或再次编辑 | 增加备忘录 management 列表/详情,或增加独立状态恢复接口 | -| C32 | 功德金额只有 `number` / `BigDecimal` 类型,没有精度、范围和正负规则 | 前端不能自行限定两位小数、最大金额或禁止负数 | Apifox 明确 scale、precision、minimum/maximum 和负数业务含义 | -| C33 | 功德记录列表只返回正常记录,详情拒绝读取停用记录 | `status=1` 后无法写后重读、恢复或再次编辑 | 增加功德记录 management 列表/详情,或增加独立状态恢复接口 | -| C34 | YAML `NotificationView` 声明了家谱编号/名称、发送人昵称/手机号和业务摘要,但 PC `toVo` 未填充这些字段 | 页面可能长期显示空白,导出契约与真实 PC 响应不一致 | 后端补齐字段,或从 PC Schema 删除不会返回的字段并明确隐私策略 | -| C35 | PC 谱文后端列表支持可选 `categoryId`,YAML 未声明该 query,且没有文章分类选项接口 | 前端无法可靠提供分类筛选或编辑分类 | YAML 补齐 query,并提供当前家谱文章分类选项接口 | -| C36 | `ArticleVo` 只返回 `coverOssId`,没有 PC 文件访问 URL | 谱文可维护封面引用,但不能安全回显封面图片 | Article View 返回封面 URL,或增加 OSS ID 授权解析接口 | -| C37 | 谱文列表只返回正常记录,详情拒绝读取停用记录 | `status=1` 后无法写后重读或恢复 | 增加谱文 management 列表/详情,或独立状态恢复接口 | -| C38 | `AlbumVo` / `AlbumPhotoVo` 只返回 `coverOssId` / `ossId`,没有 PC 文件访问 URL | 相册和照片记录可维护,但不能安全回显封面或照片内容 | View 返回授权 URL,或增加 OSS ID 授权解析接口 | -| C39 | 相册和照片列表只返回正常记录,且没有停用记录的 management 入口 | `status=1` 后无法从页面重读、恢复或再次编辑 | 增加相册/照片 management 列表,或独立状态恢复接口 | -| C40 | `CeremonyVo` 只返回 `coverOssId`,没有 PC 文件访问 URL | 活动记录可维护,但不能安全回显封面 | View 返回封面授权 URL,或增加 OSS ID 授权解析接口 | -| C41 | 活动列表和详情只允许读取正常记录 | `status=1` 后无法从页面重读、恢复或再次编辑 | 增加活动 management 列表/详情,或独立状态恢复接口 | -| C42 | `CeremonyGiftBody.giftAmount` 的 YAML 没有精度和最大值,后端实际拒绝负数 | 页面可按后端约束拒绝负数,但不能自行限定小数位或最大金额 | YAML 补充 minimum、scale、precision 和 maximum | -| C43 | YAML 成员更新角色枚举含 `owner/visitor`,PC 后端 DTO 只接受 `admin/editor/member` | 按 YAML 展示会产生必然失败的写入 | YAML 收紧枚举;本轮前端按 PC 后端校验冻结为三种可分配角色 | -| C44 | `GenealogyMemberVo` 声明昵称、手机号、家谱和世系人物名称等字段,PC `toVo` 实际只填充 ID、成员名、角色、关系、来源和时间 | 列表部分说明可能长期为空,手机号还有隐私风险 | 后端补齐非敏感展示字段并删除手机号,或从 PC Schema 删除不会返回的字段 | -| C45 | PC 没有世系关系解除接口 | 页面无法合法解除父母、配偶、子女或兄弟姐妹关系 | 后端增加带权限和关系完整性校验的解除接口 | -| C46 | 成员更新只在 `lineagePersonId != null` 时写入,没有解除成员与世系人物绑定的语义 | 下拉留空只能表示“不修改”,不能完成解绑 | 增加明确解绑接口或约定可验证的 null/空值语义 | - -当前复核状态: - -| 阻断项 | 状态 | 在线/补齐文件证据 | -| --- | --- | --- | -| C02 | 本轮按 YAML 解决 | 只实现带 `/` 的正确路径,不保留旧路径 fallback | -| C03 | 本轮按 YAML 解决 | 验证、注册、登录、短信、找回使用 `security: []`;其他认证接口携带 Bearer | -| C04/C06 | 阻断 | profile GET 在线及 YAML 都只有泛型 `ObjectResult` | -| C07 | 阻断 | `ProfileUpdateBody` 没有地区字段 | -| C01/C08 | 前端按后端 PC 实链解决 | 只调用 `/genealogy/pc/region/*`;列表按 `RegionSelectVo` 的 `regionCode/regionName/regionLevel` 读取,不保留公共路径 fallback | -| C09 | 前端按后端 PC 实链解决 | `SysOssResumableInitVo` 明确提供 `uploadId/instant/ossId/url/fileName/uploadedChunks`,完成响应使用 `SysOssUploadVo` | -| C10 | 前端边界解决 | 后端初始化 `ossId` 为 Long、完成响应为 string;浏览器统一转十进制字符串,隐藏回填且不转 `Number` | -| C11(备忘录) | 本轮按后端 PC 实链解决 | YAML 仍只有 string;PC `AppMemoServiceImpl` 明确校验 `completed` 只能为 `0/1`,含义为否/是,前端冻结为未完成/已完成 | -| C13 | 后续日期模块仍阻断 | 本阶段上传与区划响应不消费日期;其他模块仍需逐字段核验 Java 日期类型 | -| C17(成长记录) | 本轮按后端 PC 实链解决 | YAML 响应仍为泛型;PC `PcGrowthRecordController` 明确返回 `GrowthRecordVo`,前端只冻结该 PC 响应,不读取 APP 路径 | -| C17(亲友记录) | 本轮按后端 PC 实链解决 | YAML 响应仍为泛型;PC `PcRelativeRecordController` 明确返回 `RelativeRecordVo`,前端只冻结该 PC 响应,不读取 APP 路径 | -| C17(备忘录) | 本轮按后端 PC 实链解决 | YAML 响应仍为泛型;PC `PcMemoController` 复用的 PC 支持层明确返回 `MemoVo`,前端只冻结该 PC 响应,不读取 APP 路径 | -| C17(功德记录) | 本轮按后端 PC 实链解决 | YAML 响应仍为泛型;PC `PcMeritRecordController` 复用的 PC 支持层明确返回 `MeritRecordVo`,前端只冻结该 PC 响应,不读取 APP 路径 | -| C17(消息通知) | 本轮按 YAML 详情 DTO 和后端 PC 实链解决 | 详情为完整 `NotificationView`;列表导出仍是泛型数组,但 PC 控制器实际返回同一 `NotificationVo`。前端只接受直接数组,不读取 APP 路径 | -| C20(谱文) | 本轮按后端 PC 实链解决 | PC 控制器实际具备列表、详情、新增、修改、删除并返回 `ArticleVo`;页面不再只开放删除 | -| C18 | 阻断 | 未说明可选更新字段的省略、`null`、空串语义 | -| C34 | 阻断非核心展示 | PC 实际未填充 `genealogyNo/genealogyName/senderNickName/senderPhone/bizSummary`;页面兼容空值并始终隐藏手机号,核心通知读取和已读闭环不依赖这些字段 | -| C35 | 阻断分类能力 | 本轮不发送 YAML 未声明的 `categoryId` query,也不提供手填分类 ID | -| C36/C37 | 阻断非核心展示/停用管理 | 本轮不猜封面 URL,并固定提交 `status=0`,避免产生不可重读谱文 | -| C20(相册/照片) | 本轮按后端 PC 实链接决 | PC 控制器具备相册列表、新增、修改、删除和照片列表、新增、删除;页面只从响应取得稳定 ID | -| C38/C39 | 阻断非核心展示/停用管理 | 本轮不猜文件 URL,并固定提交 `status=0`,避免产生不可重读的相册或照片 | -| C20(祭祀活动/祭品) | 本轮按后端 PC 实链接决 | PC 控制器具备活动完整 CRUD 和祭品列表、新增、删除;页面只使用响应中的稳定 ID | -| C16 | 本轮按后端 PC 实链接决 | `members/options` 返回同一家谱成员及真实 `appUserId`;管理员邀约名单只允许选择正常且已绑定账号的成员 | -| C15 | 本轮按后端 PC 实链接决 | `members/options` 是当前家谱真实业务账号的唯一选择源;`SPECIFIED` 只提交正常且已绑定账号成员的 `appUserId` | -| C40/C41 | 阻断非核心展示/停用管理 | 本轮不猜封面 URL,并固定提交 `status=0`,避免产生不可重读活动 | -| C42 | 阻断金额精度/上限 | 前端只执行后端已确认的非负校验,不自行限制小数位和最大金额 | -| C43 | 前端按后端 PC 实链收紧 | 成员角色选择器只允许 `admin/editor/member`;不发送 YAML 中后端拒绝的 `owner/visitor` | -| C44 | 阻断非核心 enrichment | 页面兼容未填充名称,始终隐藏成员、邀请人手机号和内部用户 ID;核心成员管理不依赖这些字段 | -| C45/C46 | 阻断 | 页面不提供世系关系解除,也不把空 `lineagePersonId` 解释为解绑 | - -阶段 0 验收标准: - -- Apifox 中不存在旧短信路径和双区划路径; -- 重新导出的 OpenAPI 只包含一套正式契约; -- 公开与鉴权接口标记正确; -- 本阶段要实现的每个列表/详情都有明确响应 DTO; -- 所有枚举、日期和 ID 类型已确定; -- 将旧输入发送给后端时会明确失败,前端不保留兼容读取。 - -## 6. 页面字段分类标准 - -执行 AI 对每个请求字段都要采用下面一种处理类型: - -| 类型 | 含义 | 页面处理 | -| --- | --- | --- | -| U:用户输入 | 姓名、标题、正文、手机号等 | 可见输入框/文本域,展示必填或选填 | -| S:用户选择 | 枚举、人员、字辈、地区、状态等 | 单选、下拉、多选、日期或开关;选项必须有真实来源 | -| F:文件派生 | OSS ID、文件名、MD5、大小等 | 用户只选文件;值由上传组件生成并隐藏 | -| A:自动填写 | header、租户、上下文 ID、分页、默认值等 | 页面不可编辑,由统一 owner 注入 | -| R:响应只读 | 创建时间、计数、发布人、状态文本等 | 列表、详情、徽标或只读信息 | -| I:内部状态 | token、challengeId、validToken、资源 ID 等 | 仅内存/安全存储/URL 中保存,不显示原值 | -| V:前端校验 | 确认密码、注销确认文字等 | 可见但不得发给后端 | - -通用规则: - -1. 必填字段使用明确的必填标识,并在提交前校验。 -2. 可选字段也必须有页面归属;不填写时直接省略,除非 Apifox 明确要求传 null。 -3. 枚举必须使用选择控件,禁止自由文本。 -4. 外键 ID 必须使用搜索/选择控件,禁止“请输入人物 ID”“请输入 OSS ID”。 -5. `sortOrder`、`status` 这类管理字段: - - 有管理权限时放在“高级设置”; - - 普通用户使用隐藏默认值; - - 后端未返回权限时不得自行开放高级设置。 -6. 所有 int64 ID 在浏览器中按字符串保存和比较。 -7. 响应中的 `code`、`msg` 由请求层统一处理;业务页只消费 `data` 或 `rows/total`。 - -## 7. 字段、页面和流程规划 - -### 7.1 全局自动字段 - -| 字段 | 类型 | 唯一来源 | 规则 | -| --- | --- | --- | --- | -| `clientid` | A | `ApiClient` 配置 | 每个需要它的请求自动加 Header,不进入表单或 body | -| `Authorization` | I/A | 登录响应 token | 仅鉴权接口携带;注册登录等公开接口不携带 | -| `tenantId` | A | 环境配置 | 登录、注册、验证等自动带入,不让用户输入 | -| `genealogyId` | I/A | PC 家谱上下文 | `profile-common.js` 读取和传播;缺失时阻止家谱内请求 | -| `feedId`、`commentId`、`personId`、`poemId`、`articleId`、`albumId`、`photoId`、`videoId`、`ceremonyId`、`giftId`、`recordId`、`relativeId`、`memoId`、`meritId`、`notificationId` | I/A | 列表/详情响应或 URL | 由被点击记录带入,禁止文本框输入 | -| `pageNum`、`pageSize` | A/S | 分页组件 | 页码由组件维护;用户只操作翻页/每页条数 | -| `ossId` / `mediaOssIds` | F/I | 文件上传响应 | 页面展示预览和文件名,隐藏保存 ID | -| `sortOrder` | S/A | 高级设置或默认值 | 不得把空字符串转成 0;未填写时省略 | -| `status` | S/A | 权限化状态选择或默认值 | 枚举 0=正常、1=停用;不得把 1 标成“草稿” | - -### 7.2 验证中心与认证登录 - -#### 页面字段矩阵 - -| 字段 | 必填性 | 类型 | 页面/流程 | 处理 | -| --- | --- | --- | --- | --- | -| `operationCode` | 必填 path | A | 所有认证动作 | 页面动作固定为 `password-login`、`sms-login`、`register`、`forgot-password`、`phone-change` 或 `account-deactivate` | -| `clientid` | 必填 header | A | 所有认证请求 | `ApiClient` 自动注入 | -| `tenantId` | 必填 | A | 所有登录/验证表单 | 环境配置自动注入 | -| `subject` | require 可选,challenge/verify 必填 | A | 验证流程 | 从当前手机号/账号派生,用户不重复填写 | -| `grantType` | 必填 | A | 注册、登录、短信、找回 | 密码动作固定 `password`,短信动作固定 `sms` | -| `phone` | 必填 | U | 登录、注册、找回、换绑 | 手机号输入和格式校验 | -| `password` | 必填 | U/A | 密码登录、注册 | 用户输入明文,提交前生成 32 位 MD5;日志不得记录 | -| `oldPassword` | 必填 | U/A | 修改密码 | 同上 | -| `newPassword` | 必填 | U/A | 修改/重置密码 | 同上 | -| `confirmPassword` | 前端字段 | V | 注册、修改、找回 | 只比较一致性,不发送 | -| `smsCode` | 必填 | U | 短信登录、注册、找回、换绑、注销 | 4 位验证码输入 | -| `nickName` | 可选 | U | 注册 | 昵称输入 | -| `registerSource` | 可选 | A | 注册 | 当前端固定 `PC`;Apifox 需确认枚举 | -| `validToken` | 条件必填 | I/A | 密码登录、发送短信 | 验证成功后仅存内存,使用一次即清除 | -| `challengeId` | 必填 | I/A | 验证校验 | 挑战响应自动回填 | -| `providerCode`、`captchaType` | 可选 | A | 验证校验 | 挑战响应决定,用户不可修改 | -| `payload.track` | 条件必填 | A | 天爱验证码 | 控件原样生成轨迹、尺寸和时间 | -| `payload.uuid` | 条件必填 | I/A | 系统图形验证码 | 挑战响应回填 | -| `payload.code` | 条件必填 | U | 系统图形验证码 | 用户输入图片答案 | - -天爱控件产生的嵌套字段全部属于 A,不得另做表单让用户填写: - -- `bgImageWidth`、`bgImageHeight`:控件实际显示尺寸,必填; -- `templateImageWidth`、`templateImageHeight`:模板实际显示尺寸,可选; -- `startTime`、`stopTime`:操作开始和结束毫秒时间戳,必填; -- `left`、`top`:控件最终偏移,可选; -- `trackList`:必填轨迹数组; -- 每个轨迹点的 `x`、`y`、`t`、`type` 均由控件生成; -- `data`:不同验证码类型的扩展数据,原样提交。 - -验证和登录响应字段: - -| 响应字段 | 类型 | 页面处理 | -| --- | --- | --- | -| `required` | R/A | 决定是否进入挑战流程 | -| `providerCode`、`captchaType` | I/A | 选择正确验证码控件 | -| `sceneCode` | I/R | 服务端解析结果,可用于只读诊断;不得回传 | -| `ttlSeconds`、`expireSeconds` | R/A | 倒计时和过期重试 | -| `challengeId`、`uuid` | I | 只在当前挑战内保存 | -| `img` | R | 系统图形验证码图片 | -| `payload` | I/A | 天爱控件初始化数据 | -| `passed` | R/A | 决定校验成功或失败 | -| `validToken` | I | 一次性票据,不显示、不落长期存储 | -| `message` | R | 安全地显示验证结果 | -| `access_token` | string / I | 登录接口 `data`;密码或短信登录成功时立即保存,后续仅用于 Bearer 鉴权 | -| `expire_in` | integer / I/R | 登录接口 `data`;登录成功时取得,可用于会话到期提示,不由用户填写 | -| `client_id`、`clientKey`、`deviceType`、`userType` | string / I/R | 登录接口 `data`;登录成功时取得,仅作会话上下文或诊断,不回传为用户输入 | -| `profile` | object / R | 登录接口 `data.profile`;登录成功后取得,可用于页面只读展示或资料页初始回填 | -| `profile.userId`、`profile.tenantId`、`profile.userNo` | integer/string、string、string / I/R | 后端自动产生;不得让用户手工填写,业务 ID 超出 JS 安全整数时保持字符串 | -| `profile.phone`、`profile.nickName`、`profile.realName`、`profile.email` | string/null / R | 后端用户资料;登录成功后只读展示或作为资料页回填,编辑时仍只提交 `ProfileUpdateBody` 允许字段 | -| `profile.avatar` | integer/string/null / R | 后端头像 OSS ID;只读回填,用户换头像必须经文件选择和上传组件产生,禁止手填 OSS ID | -| `profile.sex` | enum `0/1/2` / R | 后端用户资料;页面映射男/女/未知 | -| `profile.birthday` | date/null / R | 后端用户资料;页面按日期展示 | -| `profile.registerSource`、`profile.loginIp`、`profile.loginDate` | string/null / R/I | 后端自动产生;只读或内部诊断,不作为表单输入 | -| `profile.status` | string / R/I | 后端账号状态;只读或内部诊断,不作为表单输入 | -| `profile.clientKey`、`profile.deviceType` | string / I/R | 后端登录上下文;只读或内部诊断 | - -#### 认证业务流程 - -密码登录: - -1. 用户输入手机号和密码。 -2. 以 `password-login + tenantId + phone` 调用 `require`。 -3. 若 `required=false`,直接提交密码登录。 -4. 若需要验证,调用 challenge,展示服务端指定控件,再调用 verify。 -5. 得到 `validToken` 后与 MD5 密码一起登录。 -6. 成功后只读取并保存 `data.access_token`;`token`、`accessToken`、`tokenValue` 均视为非法旧响应,不做兼容读取。 - -短信类动作: - -1. 用户输入手机号。 -2. 按当前动作完成 require → challenge → verify。 -3. 把一次性 `validToken` 提交给发送短信接口。 -4. 用户输入短信码后执行短信登录、注册、找回、换绑或注销。 -5. `validToken` 不进入最终注册、短信登录、换绑或注销 body,除非 Apifox 明确修改契约。 - -#### 个人资料字段 - -`ProfileUpdateBody` 的所有字段必须进入 `profile-data.html`: - -| 字段 | 必填性 | 类型 | 控件 | -| --- | --- | --- | --- | -| `nickName` | 可选 | U | 文本,最长 30 | -| `realName` | 可选 | U | 文本,最长 30 | -| `avatar` | 可选 | F/I | 图片上传、预览、删除;隐藏保存 OSS ID | -| `sex` | 可选 | S | 男 0、女 1、未知 2,三项都要有 | -| `birthday` | 可选 | S | 日期选择器,`yyyy-MM-dd` | -| `email` | 可选 | U | email 输入,最长 100 | - -现有页面整改点: - -- 当前 `avatarOssId` 字段名与请求契约 `avatar` 不一致; -- 补齐 YAML 中 `avatar` 仍是 `integer`,但示例值已超过 JavaScript 安全整数范围;浏览器边界必须按十进制字符串保留精度,且只能由上传响应自动回填,不能转 `Number` 或让用户手填; -- 缺少 `realName`、`email` 和性别“未知”; -- 页面现居地区、父亲、微信/QQ、学历/职业不在 `ProfileUpdateBody`,不得混入保存请求; -- 在后端增加地区字段前,“保存地区”按钮必须关闭或改成纯查询演示; -- `GET /auth/profile` 必须补齐 `ProfileView` 后才能严格回填。 - -### 7.3 文件上传 - -用户在任何业务页只操作“选择文件、取消、重试”;下面字段全部自动产生: - -| 阶段 | 字段 | 必填性 | 类型 | 来源 | -| --- | --- | --- | --- | --- | -| 初始化 | `uploadId` | 必填 | I/A | 客户端生成一次并贯穿全过程 | -| 初始化 | `fileName` | 必填 | F | `File.name` | -| 初始化 | `fileMd5` | 必填 | F | 文件内容计算 | -| 初始化 | `totalSize` | 必填 | F | `File.size` | -| 初始化 | `totalChunks` | 必填 | F | 根据大小和分片大小计算 | -| 初始化 | `chunkSize` | 必填 | A | 上传组件统一配置,默认示例 4 MiB,最后一片除外 | -| 初始化 | `contentType` | 可选 | F | `File.type` | -| 分片 | `chunkIndex` | 必填 | A | 上传循环生成 | -| 分片 | `chunkMd5` | 必填 | F | 当前分片计算 | -| 分片 | `file` | 必填 | F | 当前 Blob | -| 完成 | `uploadId/fileName/fileMd5/totalSize/totalChunks` | 必填 | I/A/F | 必须与初始化一致 | - -响应处理: - -- `FileUploadVo.ossId`:I,隐藏写入业务表单; -- `url`、`thumbnailUrl`:R,用于预览; -- `fileName`、`originalName`:R,用于文件列表; -- 初始化 `instant=true` 时直接读取 `ossId/url/fileName` 完成秒传;`instant=false` 时使用服务端 `uploadId` 与 `uploadedChunks` 跳过已上传分片; -- 初始化 Long `ossId` 和完成响应 string `ossId` 均在浏览器边界转为十进制字符串; -- 上传成功但业务保存失败时,当前契约没有释放引用接口,必须让后端补充生命周期规则。 - -统一由 `public/js/upload-pages.js` 管理分片、进度、重试、取消、MD5 和隐藏 ID。各业务页面不得复制上传算法,也不得出现“请输入 OSS ID”的可见输入框。 - -### 7.4 家谱上下文与配额 - -`GET /genealogy/pc/genealogies/quota` 只负责在 `profile-families.html` 展示: - -| 响应字段 | 类型 | 页面呈现 | -| --- | --- | --- | -| `createUsed`、`createLimit`、`createRemaining` | R | 创建额度已用/上限/剩余 | -| `canCreate` | R | 控制“创建家谱”是否可用 | -| `joinUsed`、`joinLimit`、`joinRemaining` | R | 加入额度已用/上限/剩余 | -| `canJoin` | R | 控制“加入家谱”是否可用 | - -`-1` 显示为“不限”,不能参与普通减法或显示负数。 - -配额响应不包含 `genealogyId`,不能替代“我的家谱”。当前由 `GET /genealogy/pc/genealogies/mine` 返回完整 `AppGenealogyVo`: - -- `profile-families.html` 从真实列表生成家谱入口; -- `genealogyId` 在浏览器边界保持字符串,并由 `profile-common.js` 统一读取和传播; -- 所有家谱内页面缺少真实 `genealogyId` 时必须阻止请求并回到家谱选择入口; -- 不显示静态家谱卡片,不从 APP 接口获取家谱,不允许用户手工输入家谱 ID; -- 切换家谱通过进入新的带 `genealogyId` URL 完成整页导航,旧页面模块状态不会复用。 - -### 7.5 行政区划 - -本 PC 前端只实现 `/genealogy/pc/region/*`,不调用重复的 `/genealogy/region/*`,也不保留 fallback。 - -| 字段 | 必填性 | 类型 | 页面处理 | -| --- | --- | --- | --- | -| `parentCode` | 可选 query | A/S | 省级不传;选择上一级后自动查询下级 | -| `regionCode` | 必填 path | I/A | 从被选择的区划记录带入 | -| `keyword` | 必填 query | U | 搜索框 | -| `level` | 可选 query | S | 1 省、2 市、3 区县、4 乡镇街道、5 村社区 | -| `limit` | 可选 query | A | 搜索组件固定合理上限,不让用户自由输入 | -| `clientid` | 可选 header | A | 统一请求层注入 | - -真实 Java `RegionSelectVo` 已明确 `regionCode`、`regionName`、`regionLevel` 等字段。个人资料当前仍没有可保存的地区字段,因此区划组件只提供级联、搜索和路径回显,不提交到资料更新接口。 - -### 7.6 家族圈 - -页面归属: - -- `profile-feed.html`:分页列表、点赞、轻量评论; -- `profile-feed-edit.html`:新增和编辑; -- 新增 `profile-feed-detail.html`:详情、完整评论树、回复、通知跳转和分享深链。 - -#### 写入字段 - -| 字段 | 必填性 | 类型 | 页面处理 | -| --- | --- | --- | --- | -| `genealogyId` | 必填 path | I/A | 家谱上下文 | -| `feedId` | 详情/修改/删除必填 | I/A | 列表记录或 URL | -| `feedType` | 可选 | A 或 S | 当前默认隐藏 `text`;Apifox 补枚举后才能改成选择 | -| `feedContent` | 必填 | U | 正文编辑器 | -| `mediaOssIds` | 可选 | F/I | 多文件上传结果拼成英文逗号串,用户只看缩略图 | -| `sortOrder` | 可选 | S/A | 管理员高级设置;普通发布默认省略/0 | -| `status` | 可选 | S/A | 0 正常、1 停用;不得显示为“发布/草稿” | -| `parentCommentId` | 可选 | I/A | 点击“回复”后自动带入;一级评论省略或 null | -| `commentContent` | 必填 | U | 评论输入,最多 1000 字 | -| `pageNum/pageSize` | 可选 | A/S | 分页组件 | - -#### 动态响应字段 - -| 字段 | 页面处理 | -| --- | --- | -| `feedId`、`genealogyId` | I,用于路由和后续操作 | -| `genealogyNo`、`genealogyName` | R,详情来源信息 | -| `publisherUserId` | I,用于权限判断,但不能单独代替后端鉴权 | -| `publisherNickName`、`publisherStatus` | R,发布人和状态 | -| `feedType`、`feedContent` | R,类型和正文 | -| `mediaOssIds` | I/F,解析后通过文件 URL 规则展示,不直接显示 ID | -| `likedByMe` | R,决定点赞按钮状态 | -| `likeCount`、`commentCount` | R,计数 | -| `pinned`、`pinnedTime` | R,置顶徽标和时间 | -| `sortOrder`、`status` | R/管理信息 | -| `remark` | R,仅在产品确认需要时展示,不能当正文 | -| `createTime`、`updateTime` | R,发布时间和编辑时间 | - -评论响应字段: - -- `commentId`、`genealogyId`、`feedId`、`parentCommentId`、`appUserId`、`parentAppUserId`:I; -- `appUserNickName`、`appUserAvatar`、`parentAppUserNickName`:R; -- `commentContent`:R;为 null 且 `userDeleted=1` 时显示“该评论已删除”占位; -- `replyCount`:R,控制“展开回复”; -- `commentLevel`:R,`root` 或 `reply`; -- `status`、`createTime`:R。 - -流程: - -1. 列表首屏调用分页接口,不同时调用非分页接口重复取数。 -2. 点击记录进入详情,以 `genealogyId + feedId` 取详情。 -3. 一级评论调用 comments 分页;回复只调用对应 comment 的 replies 分页。 -4. 点赞、取消、评论、删除成功后以接口返回和重新读取结果校正计数。 -5. 删除有回复的评论后保留占位,不从 DOM 直接移除整棵回复。 - -2026-07-28 家族圈实施状态: - -- `ApiClient` 的 14 个家族圈 PC 操作已由契约测试锁定;页面首屏只调用 `/feeds/page`,评论和回复分别调用各自的 `/page`。 -- 已新增 `profile-feed-detail.html`,以 `genealogyId + feedId` 形成稳定详情深链;列表、详情和编辑链接都保留字符串 ID。 -- 列表和详情消费 `AppFamilyFeedVo` 的发布人、点赞状态与计数、评论计数、置顶、状态和时间字段;评论消费 `FamilyFeedCommentVo` 的归属、父评论、层级、删除占位、回复计数和时间字段。 -- `GET /genealogy/pc/genealogies/{genealogyId}` 的 `canEditContent/canManage` 控制编辑和高级设置,`GET /genealogy/pc/auth/profile` 的 `userId` 结合响应中的发布人/评论人 ID 控制本人删除按钮;后端仍执行最终权限校验。 -- 发布图片只允许文件选择;`mediaOssIds` 为隐藏的上传派生字段,多文件结果使用英文逗号连接,不允许手填 OSS ID。 -- 新增、修改、点赞、取消点赞、评论和删除均有同资源动作锁;成功后重新读取详情、分页列表或评论分页以校正页面状态。 -- `status=1` 使用后端定义的“停用”语义,不再显示为“草稿”。C21、C22 未解决前,图片缩略图回显和停用记录恢复仍属于后端阻断,不宣称这两项闭环完成。 -- 已在真实 Chrome 登录态验证:当前账号的 `/genealogies/mine` 返回空列表;无家谱上下文时家族圈不发业务请求,所有发布入口统一改写到 `profile-families.html?next=profile-feed-edit.html`。发布表单实测为多文件选择、隐藏 OSS ID、普通用户隐藏且禁用管理字段。由于账号没有家谱,本轮不能进行真实动态列表/详情读取或写操作联调。 - -### 7.7 字辈谱 - -`profile-generation.html` 同时提供“单条维护”和“批量维护”。 - -单条字段: - -| 字段 | 必填性 | 类型 | 控件 | -| --- | --- | --- | --- | -| `generationNo` | 必填 | U/S | 正整数世代序号 | -| `generationText` | 必填 | U | 最长 50 | -| `description` | 可选 | U | 文本域,最长 500 | -| `sortOrder` | 可选 | U/S | 管理员高级数字字段 | -| `status` | 可选 | S | 0 正常、1 停用 | -| `poemId` | 修改必填 path | I/A | 列表记录 | - -批量字段: - -| 字段 | 必填性 | 类型 | 控件 | -| --- | --- | --- | --- | -| `poemText` | 必填 | U | 文本域,最长 26000;支持合同中列出的分隔符 | -| `disableMissing` | 可选 | S | 复选框,必须有“将停用后续世代、不删除历史”的确认说明 | - -批量流程必须是 preview → 用户检查 → save: - -- 预览展示 `createCount`、`updateCount`、`keepCount`、`disableCount`; -- 逐项结果来自 `items`;每项展示 `generationNo`、`oldGenerationText`、`newGenerationText`、`oldStatus`、`newStatus`、`action`、`warning`; -- `poemId`、`genealogyId` 是内部字段; -- 保存前再次确认,因为服务端会基于最新数据重新计算差异。 - -普通展示列表使用正常字辈接口;管理页面使用 management 接口以包含停用项。 - -当前实现状态: - -- 页面先读取家谱详情,以 `canEditContent` 决定调用正常列表还是 management 列表;无维护权限时不渲染新增、编辑、停用和批量维护控件,后端继续执行最终权限校验。 -- `genealogyId` 只来自家谱上下文,`poemId` 只来自列表响应;所有业务 ID 在浏览器边界保持字符串,不提供手工 ID 输入。 -- 单条新增、修改和状态切换成功后重新读取列表;批量维护严格执行 preview → 当前请求体签名确认 → save → 重新读取列表。 -- 批量预览逐项校验 `action`、状态、必需的新旧文本和内部 ID,并核对四类汇总计数;勾选 `disableMissing` 时保存确认明确说明只停用后续世代、不删除历史。 -- 已在真实 Chrome 登录态验证无家谱上下文分支:不发送字辈业务请求,新增按钮和批量面板隐藏,家谱上下文入口统一返回家谱选择页。当前账号家谱列表为空,因此正常/management 列表、单条写入和批量写入仍缺少真实家谱数据联调证据。 - -### 7.8 世系人物 - -全部操作集中在 `profile-tree.html`,详情和编辑可使用抽屉/弹窗,但所有字段都要有明确控件。 - -| 字段 | 必填性 | 类型 | 页面处理 | -| --- | --- | --- | --- | -| `bindingMode` | 必填 | S | NONE 不绑定、SELF 当前账号、SPECIFIED 指定账号 | -| `appUserId` | SPECIFIED 时必填 | S/I | 从当前家谱 `members/options` 的正常且已绑定账号成员中选择;禁止文本 ID | -| `personNo` | 可选 | U/A | 高级设置可填;留空由服务端生成 | -| `name` | 必填 | U | 姓名 | -| `aliasName` | 可选 | U | 别名/曾用名 | -| `sex` | 可选 | S | 0 男、1 女、2 未知 | -| `generation` | 可选 | S/I | 从正常字辈列表选择世代 | -| `generationName` | 可选 | A/R | 随字辈选项自动回填并只读显示 | -| `fatherId` | 可选 | S/I | 世系人物搜索选择器 | -| `motherId` | 可选 | S/I | 世系人物搜索选择器 | -| `avatarOssId` | 可选 | F/I | 上传头像自动回填 | -| `birthDate` | 可选 | S | date-time 选择器 | -| `birthLunar` | 可选 | S | 0 公历、1 农历开关 | -| `birthPlace` | 可选 | U | 出生地文本 | -| `deathDate` | 可选 | S | date-time 选择器 | -| `deathLunar` | 可选 | S | 0 公历、1 农历开关 | -| `deathPlace` | 可选 | U | 逝世地 | -| `burialPlace` | 可选 | U | 安葬地 | -| `personStatus` | 可选 | S | 0 健在、1 已故、2 未知 | -| `biography` | 可选 | U | 人物简介 | -| `sortOrder` | 可选 | U/S | 管理高级设置 | -| `remark` | 可选 | U | 备注 | -| `relationName` | 可选 | U | 仅“添加配偶”快捷流程显示 | - -人物响应字段按以下方式使用: - -- `personId`、`genealogyId`、`appUserId`、`fatherId`、`motherId`、`avatarOssId`:I; -- `genealogyName`、`genealogyNo`:R; -- `appUserNickName`:R,显示绑定账号; -- `personNo`、`name`、`aliasName`、`sex`、`generation`、`generationName`:R/编辑回填; -- `fatherName`、`motherName`、`spouseNames`:R; -- 出生、逝世、安葬、状态、简介、备注、排序字段:R/编辑回填。 - -关系流程: - -1. 先选中一个现有 `personId` 作为关系锚点。 -2. 点击父母、子女、兄弟姐妹或配偶。 -3. 打开完整 `LineagePersonBody` 表单,创建的是一个新人物并建立关系,不是绑定两个现有人物 ID。 -4. 配偶流程才显示 `relationName`。 -5. 当前没有解除关系接口,页面不得伪造“解除关系”。 -6. DELETE 是逻辑停用;存在正常子女时可能失败,确认文案不能写成物理删除。 - -列表筛选: - -- `keyword`:U,搜索姓名、别名或人物编号; -- `generation`:S,从字辈接口选择; -- `personStatus`:S,0/1/2; -- `pageNum/pageSize`:分页组件自动维护。 - -`LineagePersonTreeView` 补全前,不得继续依靠多个字段 fallback 猜树结构。 - -当前实现状态: - -- 已接入 PC 世系树、普通列表、分页列表、人物选项、详情、新增、修改、逻辑停用,以及父母、子女、兄弟姐妹、配偶四类关系新增;成功写入后统一重新读取树、列表、分页和选项,返回稳定 `personId` 时再读取详情。 -- 页面先读取家谱详情,`canEditContent` 只控制新增、编辑、停用和关系维护;树、列表、详情、筛选和分页保持只读可用,后端继续执行最终查看和编辑权限校验。 -- `LineagePersonBody` 的全部字段均有明确来源:世代从正常字辈列表选择并自动回填字辈,父母从世系人物选项选择,头像通过统一上传组件产生隐藏 `avatarOssId`,所有业务 ID 在浏览器边界保持字符串且不可手填。 -- 新增和编辑人物开放 `NONE`、`SELF`、`SPECIFIED`;`SPECIFIED` 选择器只消费当前家谱 `members/options`,过滤未绑定账号、停用成员和当前账号,不显示手机号,不允许手填 `appUserId`。 -- 编辑已绑定其他账号的人物时,用详情响应的 `appUserId` 精确匹配同一成员选项;真实选项不存在时不伪造回填,保存会被前端校验阻止。 -- 分页查询已接入 `keyword`、`generation`、`personStatus`、`pageNum/pageSize`;详情展示家谱、绑定账号、父母、配偶、出生、逝世、安葬、人物状态、简介和备注等响应字段。 -- DELETE 确认明确使用“停用”语义,并提示存在正常子女时后端会拒绝;页面没有伪造解除关系或物理删除。 -- 已在真实 Chrome 登录态验证无家谱上下文分支:不发送世系业务请求,读区域显示选择家谱提示,新增、关系维护和完整表单隐藏,分页与配偶专用字段不误显示,所有家谱链接返回家谱选择页。当前账号家谱列表为空,因此真实树、列表、详情和写操作仍缺少有数据账号联调证据。 -- C18 仍适用于本模块:可选字段清空语义未明确,本轮仅提交非空值,不宣称可选字段清空闭环。 -- `SPECIFIED` 账号绑定相关聚焦测试 42/42 通过;真实账号页面验证统一留到阶段 6 收尾执行。 - -### 7.9 视频 - -计划新增 `profile-video-edit.html`,`profile-video.html` 负责列表。 - -| 字段 | 必填性 | 类型 | 页面处理 | -| --- | --- | --- | --- | -| `videoTitle` | 必填 | U | 标题 | -| `videoDesc` | 可选 | U | 说明文本域 | -| `coverOssId` | 可选 | F/I | 封面上传、预览、隐藏 ID | -| `videoOssId` | 必填 | F/I | 视频文件上传;不得输入 ID | -| `durationSeconds` | 可选 | F/A/R | 从视频元数据读取,只读显示 | -| `sortOrder` | 可选 | U/S | 管理高级设置 | -| `status` | 可选 | S | 0 正常、1 停用 | -| `genealogyId`、`videoId` | path | I/A | 上下文和记录 | - -流程:选视频 → 分片上传 → 得到 `videoOssId` → 可选封面上传 → 填标题说明 → 保存 → 重新读取详情。 - -YAML 的视频响应仍使用泛型 `ObjectResult` / `ListResult`,但后端已存在独立 `PcVideoController`,其 PC method/path 与 YAML 一致,并明确返回 `VideoVo`。根据本轮“后端源码可作为可靠补充契约”的约定,页面响应字段由该 PC 控制器及 `VideoVo` 冻结: - -- I:`videoId`、`genealogyId`、`coverOssId`、`videoOssId`、`publisherUserId`; -- R:`genealogyNo`、`genealogyName`、`surname`、`publisherNickName`、`publisherPhone`、`publishTime`、`viewCount`、`remark`; -- U/R:`videoTitle`、`videoDesc`; -- F/A/R:`durationSeconds`; -- U/S/R:`sortOrder`; -- A/R:`status`,当前固定提交 `0`。 - -当前接入状态: - -- `profile-video.html` 已接入正常视频列表、详情读取、权限化编辑/删除入口、空错状态和无家谱上下文分支; -- 新增 `profile-video-edit.html`,新增和修改只提交 `VideoBody` 七个字段;视频与封面都通过统一分片上传产生隐藏 OSS ID,不允许手填; -- 选择视频文件后由浏览器媒体元数据自动读取时长,`durationSeconds` 只读; -- 保存成功后使用稳定 `videoId` 重新读取详情,再返回列表;删除确认明确会释放视频和封面文件引用,删除后重读列表; -- 后端 `BigNumberSerializer` 对超出 JavaScript 安全整数范围的 `Long` 输出字符串,对安全整数输出 number;前端接受两种输入并统一保留为字符串; -- C23 未解决前只显示文件“已上传”,不猜测 OSS URL,不宣称播放或封面预览完成; -- C24 未解决前不开放 `status=1`,避免产生无法重新读取和恢复的停用视频; -- 2026-07-29 已在真实登录 Chrome 验证无家谱上下文:视频列表不发业务请求,发布入口及编辑表单保持隐藏,所有上下文链接返回家谱选择页,控制台无错误。当前账号没有家谱,真实列表、详情、上传和 CRUD 写入仍待有数据账号验证。 - -### 7.10 贺礼邀约与祭祀 - -邀约字段: - -| 字段 | 必填性 | 类型 | 页面处理 | -| --- | --- | --- | --- | -| `inviteeUserIds` | 必填 | S/I | 家谱成员多选;提交完整名单,空数组表示取消全部未响应邀请 | -| `inviteStatus` | 必填 | S | 当前用户只能选择 ACCEPTED 接受或 DECLINED 拒绝 | -| `genealogyId`、`ceremonyId` | path | I/A | 活动上下文 | - -邀约响应在 `profile-gift.html` 展示: - -- `invitationId`、`genealogyId`、`ceremonyId`、`inviteeUserId`:I; -- `inviteStatus`:R,PENDING/ACCEPTED/DECLINED/CANCELED 徽标; -- `inviteVersion`:I/R,只在管理审计需要时显示; -- `deliveredTime`、`readTime`、`responseTime`:R; -- `ceremonyTitle`、`ceremonyTime`、`location`、`locationAddress`:R; -- `longitude`、`latitude`:地图定位内部值,页面显示地图/地址而非原始数字。 - -“我的邀请”接口有完整响应,可独立实现接受/拒绝流程。管理端替换受邀人依赖 C16,且必须先有真实活动 ID。 - -当前接入状态:`profile-gift.html` 已使用 `GET /genealogy/pc/genealogies/ceremony-invitations/mine` 展示当前账号的邀请列表和响应内详情;仅 `PENDING` 邀请显示接受/拒绝,并向 `PUT /genealogy/pc/genealogies/{genealogyId}/ceremonies/{ceremonyId}/invitations/me` 提交唯一字段 `inviteStatus`,成功后重读列表。内部 ID 和坐标不直接展示,坐标仅用于生成地图链接。2026-07-29 使用真实账号联调,PC “我的邀请”接口成功返回空数组,页面空状态和鉴权正常;由于当前账号没有待响应邀请,真实接受/拒绝写入仍待有邀请数据时复核。后端仓库中的同组 `AppCeremonyController` 仍以 `/genealogy/app/genealogies` 为基础路径,与冻结 YAML 的 `/genealogy/pc` 不一致,但线上 PC mine 路径已实际可用,前端继续以 YAML 为唯一契约,不回退 APP 路径。 - -当前没有祭祀活动列表、详情、新增、修改和献礼列表/新增接口。现有 `profile-gift-edit.html` 中的 `ceremonyType`、`ceremonyTitle`、`ceremonyTime`、`location`、`ceremonyDesc`、`coverOssId` 不属于任何现有 PC 写入 DTO,必须继续保持禁用,不得向猜测路径提交。 - -两条删除接口只能在后端补齐列表和详情、产生稳定 ID 后开放: - -- 删除活动是逻辑删除活动及祭品并释放封面引用; -- 删除祭品只操作被选中的真实 `giftId`; -- 删除确认必须说明影响范围。 - -### 7.11 成长记录 - -页面:`profile-growth.html`、`profile-growth-edit.html`。 - -| 字段 | 必填性 | 类型 | 页面处理 | -| --- | --- | --- | --- | -| `lineagePersonId` | 可选 | S/I | 世系人物搜索选择器,禁止 ID 输入框 | -| `recordType` | 可选 | U 或 S | C12 未解决前按 Apifox 明确结果决定,不能猜枚举 | -| `recordTitle` | 必填 | U | 标题 | -| `recordContent` | 可选 | U | 正文编辑器 | -| `recordDate` | 可选 | S | 日期控件,等待 C13 明确格式 | -| `remindTime` | 可选 | S | 日期时间控件 | -| `mediaOssIds` | 可选 | F/I | 多附件上传 | -| `sortOrder` | 可选 | U/S | 管理高级设置 | -| `status` | 可选 | A | 当前固定提交 0 正常;C26 解决前不开放 1 停用 | - -YAML 的列表、详情、新增和修改响应仍是泛型对象,但后端已存在独立 `PcGrowthRecordController`,其 PC method/path 与 YAML 一致,并明确返回 `GrowthRecordVo`。根据本轮“后端源码可作为可靠补充契约”的约定,页面响应字段由该 PC 控制器及 `GrowthRecordVo` 冻结: - -- I:`recordId`、`genealogyId`、`appUserId`、`lineagePersonId`、`mediaOssIds`; -- R:`genealogyNo`、`genealogyName`、`surname`、`appUserNickName`、`appUserPhone`、`lineagePersonNo`、`lineagePersonName`、`remark`; -- U/R:`recordType`、`recordTitle`、`recordContent`; -- S/R:`recordDate`、`remindTime`; -- U/S/R:`sortOrder`; -- A/R:`status`,当前固定提交 `0`。 - -当前接入状态: - -- `profile-growth.html` 已接入当前账号成长记录列表、详情、编辑和删除入口;列表接口不传后端额外的 `all` 参数,只消费当前 YAML 声明的请求; -- `profile-growth-edit.html` 已接入新增和修改;`lineagePersonId` 只从真实世系人物选项接口选择,`recordId` 只来自列表响应或 URL,均按字符串处理且不能手填; -- `recordType` 后端 DTO 和服务均为不带枚举的自由字符串,因此保留可选文本输入;`recordDate` 提交 `yyyy-MM-dd`,`remindTime` 由 `datetime-local` 转为后端 `Date` 接受的 `yyyy-MM-dd HH:mm:ss`; -- 多附件通过统一分片上传产生隐藏 `mediaOssIds`,支持保留、追加和清除附件选择,不显示 OSS ID; -- 新增和修改成功后必须使用稳定 `recordId` 重读同一条详情并校验身份,再返回列表;删除成功后重读列表,确认文案明确逻辑删除且会释放附件引用; -- C18 未解决前,标题以外可选文本、人物和日期字段清空不宣称闭环;前端对空可选字段保持省略,附件清除使用后端已明确的空引用处理; -- C25 未解决前只显示附件数量,不猜测 OSS URL、文件名或下载地址;C26 未解决前固定 `status=0`,不产生无法重新读取和恢复的停用记录; -- 2026-07-29 已在真实登录 Chrome 验证无家谱上下文:列表和详情不发成长记录业务请求,编辑表单及页头保存按钮隐藏,入口统一返回家谱选择页。当前账号没有家谱,真实列表、详情、世系选项、上传和 CRUD 写入仍待有数据账号验证。 - -### 7.12 亲友记录 - -页面:`profile-relative.html`、`profile-relative-edit.html`。 - -| 字段 | 必填性 | 类型 | 页面处理 | -| --- | --- | --- | --- | -| `relativeName` | 必填 | U | 亲友姓名 | -| `relationName` | 可选 | U | 关系名称 | -| `eventName` | 可选 | U | 事件名称 | -| `eventTime` | 可选 | S | 日期时间选择器 | -| `giftAmount` | 可选 | U | 金额输入;只提交 JSON 序列化后数值不变的有限数字,C27 解决前不猜精度、范围或非负规则 | -| `recordContent` | 可选 | U | 内容 | -| `mediaOssIds` | 可选 | F/I | 多附件上传 | -| `sortOrder` | 可选 | U/S | 管理高级设置 | -| `status` | 可选 | A | 当前固定提交 0 正常;C29 解决前不开放 1 停用 | - -YAML 的列表、详情、新增和修改响应仍是泛型对象,但后端已存在独立 `PcRelativeRecordController`,其 PC method/path 与 YAML 一致,并明确返回 `RelativeRecordVo`。根据本轮“后端源码可作为可靠补充契约”的约定,页面响应字段由该 PC 控制器及 `RelativeRecordVo` 冻结: - -- I:`relativeId`、`genealogyId`、`appUserId`、`mediaOssIds`; -- R:`genealogyNo`、`genealogyName`、`surname`、`appUserNickName`、`appUserPhone`、`remark`; -- U/R:`relativeName`、`relationName`、`eventName`、`recordContent`; -- S/R:`eventTime`; -- U/R:`giftAmount`,后端 `BigDecimal` 响应按字符串保留; -- U/S/R:`sortOrder`; -- A/R:`status`,当前固定提交 `0`。 - -当前接入状态: - -- `profile-relative.html` 已接入当前账号亲友记录列表、详情、编辑和删除入口;列表接口不传后端额外的 `all` 参数,只消费当前 YAML 声明的请求; -- `profile-relative-edit.html` 已接入新增和修改;`relativeId` 只来自列表响应或 URL,按字符串处理且不能手填; -- `eventTime` 使用 `datetime-local`,提交前转为后端 `Date` 接受的 `yyyy-MM-dd HH:mm:ss`,并校验真实月日和时分秒; -- `giftAmount` 请求按 YAML 的 `number` 提交;提交前比较用户十进制输入与 `JSON.stringify(Number(value))` 的精确数值,序列化会改值的超大或高精度金额直接拒绝。后端 `BigDecimal` 响应按原字符串展示,不转回 `Number`; -- 多附件通过统一分片上传产生隐藏 `mediaOssIds`,支持保留、追加和清除附件选择,不显示 OSS ID; -- 新增和修改成功后必须使用稳定 `relativeId` 重读同一条详情并校验身份,再返回列表;删除成功后重读列表,确认文案明确逻辑删除且会释放附件引用; -- C18 未解决前,可选文本、时间和金额字段的清空不宣称闭环;附件清除使用后端已明确的空引用处理; -- C27 未解决前不增加两位小数、最大金额或非负限制;C28 未解决前只显示附件数量;C29 未解决前固定 `status=0`,并拒绝编辑停用响应,避免意外恢复; -- 2026-07-29 已在真实登录 Chrome 验证无家谱上下文:列表和详情不发亲友记录业务请求,编辑表单及页头保存按钮隐藏,入口统一返回家谱选择页。当前账号没有家谱,真实列表、详情、上传和 CRUD 写入仍待有数据账号验证。 - -### 7.13 备忘录 - -页面:`profile-memo.html`、`profile-memo-edit.html`。 - -| 字段 | 必填性 | 类型 | 页面处理 | -| --- | --- | --- | --- | -| `memoTitle` | 必填 | U | 标题 | -| `memoContent` | 可选 | U | 内容 | -| `remindTime` | 可选 | S | `datetime-local` 选择,提交为 `yyyy-MM-dd HH:mm:ss` 并校验真实日期 | -| `completed` | 可选 | S | PC 后端已核验:`0` 未完成、`1` 已完成;创建默认 `0` | -| `mediaOssIds` | 可选 | F/I | 多附件上传自动产生;隐藏保存,允许追加和清除,不允许手填 | -| `sortOrder` | 可选 | U/S | 安全整数;创建默认 `0` | -| `status` | 可选 | I/A | 页面固定隐藏提交 `0`;不开放不可重读的停用状态 | - -PC `MemoVo` 响应字段及页面用途: - -| 字段 | 类型 | 标记 | 页面处理 | -| --- | --- | --- | --- | -| `memoId`、`genealogyId`、`appUserId` | Long | I | 仅从 PC 响应取得并按十进制字符串保存;用于详情、编辑、删除和写后重读,不显示、不手填 | -| `genealogyNo`、`genealogyName`、`surname` | string | R | 家谱上下文只读信息;当前备忘录页不重复展示 | -| `appUserNickName` | string | R | 详情显示记录人 | -| `appUserPhone` | string | I | 隐私字段,不渲染 | -| `memoTitle`、`memoContent`、`remindTime`、`completed` | string | R | 列表摘要、详情展示和编辑回填 | -| `mediaOssIds` | string | I | 只统计附件数量并回填隐藏上传字段,不暴露原始 OSS ID | -| `sortOrder` | Long | I | 编辑回填;列表顺序由后端负责 | -| `status` | string | I | 仅接受正常记录 `0`;停用响应拒绝进入编辑器 | -| `remark` | string | R | 详情只读展示,MemoBody 不含此字段,因此不提交 | - -`completed` 表示业务完成状态,`status` 表示记录正常/停用,两者不能合并。列表、详情、新增、修改和删除只调用 `/genealogy/pc/genealogies/{genealogyId}/memos` 及其 `/{memoId}` 子路径;列表不发送后端管理专用的 `all` 查询。新增/修改成功后使用稳定 `memoId` 重读同一详情,删除后重读列表。 - -- C18 未解决前,可选内容和提醒时间的清空不宣称闭环;附件清除使用后端已明确的空引用处理; -- C30 未解决前只显示附件数量;C31 未解决前固定 `status=0`,拒绝编辑停用响应,避免意外恢复; -- 2026-07-29 已在真实登录 Chrome 验证无家谱上下文:列表和详情显示明确提示,编辑表单及页头保存按钮隐藏,控制台无错误。当前账号没有可用家谱,真实列表、详情、上传和 CRUD 写入仍待有数据账号验证。 - -### 7.14 功德记录 - -页面:`profile-merit.html`、`profile-merit-edit.html`。 - -| 字段 | 必填性 | 类型 | 页面处理 | -| --- | --- | --- | --- | -| `donorName` | 必填 | U | 功德人姓名 | -| `meritType` | 可选 | S | donation 捐赠、repair 修祠、public 公益、other 其他 | -| `meritTitle` | 必填 | U | 标题 | -| `meritContent` | 可选 | U | 内容 | -| `amount` | 可选 | U | YAML `number`;提交前拒绝 JSON 序列化会改变精确数值的输入,不自行限制小数位、范围或正负 | -| `meritTime` | 可选 | S | `datetime-local` 选择,提交为后端示例和 `Date` 接受的 `yyyy-MM-dd HH:mm:ss` | -| `sortOrder` | 可选 | U/S | 安全整数;创建默认 `0` | -| `status` | 可选 | I/A | 页面固定隐藏提交 `0`;不开放不可重读的停用状态 | - -PC `MeritRecordVo` 响应字段及页面用途: - -| 字段 | 类型 | 标记 | 页面处理 | -| --- | --- | --- | --- | -| `meritId`、`genealogyId`、`appUserId` | Long | I | 仅从 PC 响应取得并按十进制字符串保存;用于详情、编辑、删除和写后重读,不显示、不手填 | -| `genealogyNo`、`genealogyName`、`surname` | string | R | 家谱上下文只读信息;当前功德页不重复展示 | -| `appUserNickName` | string | R | 详情显示记录人 | -| `appUserPhone` | string | I | 隐私字段,不渲染 | -| `donorName`、`meritType`、`meritTitle`、`meritContent` | string | R | 列表摘要、详情展示和编辑回填;HTML 内容转义后展示 | -| `amount` | BigDecimal | R | 按响应原始字符串展示,不转回 `Number` | -| `meritTime` | Date | R | 列表、详情展示和 `datetime-local` 编辑回填 | -| `sortOrder` | Long | I | 编辑回填;列表顺序由后端负责 | -| `status` | string | I | 仅接受正常记录 `0`;停用响应拒绝进入编辑器 | -| `remark` | string | R | 详情只读展示,`MeritRecordBody` 不含此字段,因此不提交 | - -现有页面中的契约外 `service` 已删除,补齐 `repair` 和 `public`;由于 `MeritRecordBody` 没有附件字段,功德页不借用其他模块的 `mediaOssIds` 或上传控件。ApiClient 的新增和修改方法再次按 8 个 YAML 字段白名单过滤,调用者额外传入 `appUserId`、`mediaOssIds` 等字段也不会发送。 - -- 列表、详情、新增、修改和删除只调用 `/genealogy/pc/genealogies/{genealogyId}/merit-records` 及其 `/{meritId}` 子路径; -- 新增/修改成功后使用稳定 `meritId` 重读同一详情,删除成功后重读列表; -- C18 未解决前,可选内容、金额和时间的清空不宣称闭环;C32 未解决前不增加两位小数、最大金额或非负限制;C33 未解决前固定 `status=0` 并拒绝编辑停用响应; -- PC 写操作最终由 `assertCanEditContent` 鉴权;当前 View 没有 `canEdit/canDelete`,页面保留操作入口并明确处理 403,不猜角色字段; -- 2026-07-29 已在真实登录 Chrome 验证无家谱上下文:列表和详情显示明确提示,编辑表单及页头保存按钮隐藏,契约外类型和附件控件不存在,控制台无错误。当前账号没有可用家谱,真实列表、详情和 CRUD 写入仍待有数据账号验证。 - -列表、详情、新增和修改仍是泛型响应。补齐 `MeritRecordView` 后再开放完整 CRUD。 - -### 7.15 消息通知 - -页面:`profile-messages.html`。 - -接口冻结: - -- `GET /genealogy/pc/notifications`:可选 query `readStatus=0|1`,返回非分页直接数组;全部通知时省略 query。 -- `GET /genealogy/pc/notifications/unread-count`:返回非负 int64 未读数量。 -- `GET /genealogy/pc/notifications/{notificationId}`:读取当前账号可见的正常通知详情。 -- `POST /genealogy/pc/notifications/{notificationId}/read`:将当前账号的一条正常未读通知标记已读。 -- `POST /genealogy/pc/notifications/read-all`:将当前账号全部未读通知标记已读。 -- 五个接口均由 `ApiClient` 自动携带 Bearer 和 `clientid`;页面不采集 Header。YAML 未声明业务错误码;前端统一处理未登录/401、403、404、业务失败和网络失败。 - -| 字段 | 必填性 / 类型 | 标记 | 页面用途、来源与提交时机 | -| --- | --- | --- | --- | -| query `readStatus` | 可选 string;枚举 `0/1` | S | 消息页筛选选择;全部=省略、未读=0、已读=1;切换筛选时提交 | -| path `notificationId` | 详情/单条已读必填 int64 | I/A | 从列表响应的稳定十进制字符串取得;点击详情或“标记已读”时自动进入 path,不允许手填或转 `Number` | -| `notificationId` | 响应 int64 | I | 列表行内部关联详情和已读动作;不在可见页面展示 | -| `genealogyId` | 响应 int64,可空 | I | 仅保留为响应内部上下文;不展示、不提交、不允许手填 | -| `genealogyNo` | 响应 string | R | 详情只读候选字段;当前 PC 未填充,不作为功能依赖 | -| `genealogyName` | 响应 string | R | 详情只读展示;当前 PC 可能为空 | -| `senderUserId` | 响应 int64,可空 | I | 内部关联字段;不展示、不提交 | -| `senderNickName` | 响应 string | R | 详情只读展示;当前 PC 可能为空 | -| `senderPhone` | 响应 string | I | 隐私字段,页面始终隐藏 | -| `noticeType` | 响应 string,无枚举/默认值 | R | 列表和详情只读展示;不据此猜测跳转 | -| `noticeTitle` | 响应 string | R | 列表和详情标题,转义后展示 | -| `noticeContent` | 响应 string | R | 列表摘要和详情正文,转义后展示 | -| `bizType` | 响应 string,无枚举/默认值 | R | 详情只读展示;不据此拼业务 URL | -| `bizId` | 响应 int64,可空 | I | 内部业务关联字段;PC 未提供目标路由契约前不展示、不跳转 | -| `bizSummary` | 响应 string | R | 详情只读展示;当前 PC 可能为空 | -| `publishTime` | 响应 string `date-time` | R | 列表和详情只读展示,不由页面提交 | -| `readStatus` | 响应 string;枚举 `0/1` | R | 0=未读、1=已读;仅正常未读记录显示单条已读按钮 | -| `readTime` | 响应 string `date-time`,可空 | R | 详情只读展示,未读时为空 | -| `status` | 响应 string;枚举 `0/1` | I | 仅用于确认正常状态 `0` 才能执行单条已读,不提供页面编辑 | -| `remark` | 响应 string | R | 详情只读展示 | -| 未读数量 `data` | 响应 int64 | R | 顶部计数;只接受非负安全整数 | - -响应和权限边界: - -- YAML 的详情对象没有声明字段级 `required`、长度、范围或默认值;页面不补造限制,并兼容可选展示字段为空。 -- PC 后端按当前 `APP_USER` 登录账号隔离通知,只返回 `status=0` 的正常通知;列表按接收记录倒序,非分页。 -- 详情和单条已读必须命中当前账号的接收记录;重复单条已读保持已读状态,页面仍做写操作防重复。 -- C34 未解决前,`genealogyNo/genealogyName/senderNickName/senderPhone/bizSummary` 不能被视为稳定可用;手机号即使后端补齐也不展示。 -- 页面不展示原始 JSON,不使用 APP 接口,不开放删除,不根据 `bizType/bizId` 猜业务深链。 -- 2026-07-29 已在真实登录 Chrome 验证当前账号:未读数为 0,全部和仅未读筛选均成功同步,空列表/空详情状态正确。账号暂无通知,因此有数据详情和单条/全部已读真实写入仍待有数据时复核。 - -### 7.16 谱文 - -页面:`profile-article.html`、`profile-article-edit.html`。 - -| 请求字段 | 必填性 / 类型 | 标记 | 页面来源与提交时机 | -| --- | --- | --- | --- | -| `categoryId` | 可选 int64 | I/S | C35 未解决前省略;没有真实分类选项时不提供输入框 | -| `articleTitle` | 必填 string | U | 编辑页标题;保存时提交 | -| `articleSummary` | 可选 string | U | 编辑页摘要;非空时提交 | -| `coverOssId` | 可选 string/int64 | F/I | 选择封面并完成统一上传后自动产生;隐藏保存,禁止手填 | -| `articleContent` | 必填 string | U | wangEditor 正文;同步到 textarea 后提交 | -| `authorName` | 可选 string | U | 编辑页落款;非空时提交 | -| `sortOrder` | 可选 int64 | U/S | 编辑页安全整数;非空时提交 | -| `status` | 可选 string `0/1` | A/I | C37 未解决前固定隐藏提交 `0` | -| `genealogyId`、`articleId` | path int64 | I/A | 家谱上下文和列表/详情响应;按十进制字符串处理,不显示、不手填 | - -PC `ArticleVo` 响应字段及页面用途: - -| 字段 | 类型 | 标记 | 页面处理 | -| --- | --- | --- | --- | -| `articleId`、`genealogyId` | Long | I | 列表、详情、编辑、删除和写后重读的稳定 ID | -| `genealogyNo`、`genealogyName`、`surname` | string | R | 家谱只读上下文 | -| `categoryId` | Long,可空 | I | 仅内部保留;无分类选项源时不回填为可编辑 ID | -| `categoryName`、`categoryCode` | string | R | 详情分类只读展示 | -| `articleTitle`、`articleSummary`、`articleContent`、`authorName` | string | R/U | 列表、详情展示和编辑回填;可见内容均转义 | -| `coverOssId` | Long,可空 | I | 编辑隐藏回填;C36 未解决前不拼封面 URL | -| `publishTime`、`viewCount` | Date/Long | R | 详情发布时间和浏览量 | -| `sortOrder` | Long | I/U | 编辑回填;列表顺序由后端负责 | -| `status` | string `0/1` | I | 只允许正常记录进入编辑器 | -| `remark` | string | R | 详情只读展示,不进入 ArticleBody | - -接入边界: - -- 列表、详情、新增、修改和删除只调用 `/genealogy/pc/genealogies/{genealogyId}/articles` 及其 `/{articleId}` 子路径; -- YAML 响应仍为泛型包装,但 PC 控制器明确返回 `ArticleVo`,前端严格校验该 VO,不读取 APP 路径; -- 新增/修改成功后使用稳定 `articleId` 重读同一详情;删除后重读列表;所有写操作防重复; -- 页面先读取家谱详情,只有 `canEditContent/canManage` 显示新增、编辑和删除;后端继续执行最终权限校验; -- C35 未解决前不开放分类筛选/编辑;C36 未解决前不猜封面地址;C37 未解决前不开放停用; -- 相关聚焦测试 49/49 通过;真实账号页面验证统一留到阶段 6 收尾执行。 - -### 7.17 相册与照片 - -页面:`profile-album.html`、`profile-album-edit.html`、`profile-album-detail.html`。 - -相册请求字段: - -| 请求字段 | 必填性 / 类型 | 标记 | 页面来源与提交时机 | -| --- | --- | --- | --- | -| `albumName` | 必填 string | U | 编辑页相册名称;保存时提交 | -| `albumDesc` | 可选 string | U | 编辑页描述;非空时提交 | -| `coverOssId` | 可选 string/int64 | F/I | 选择封面并完成统一上传后自动产生;隐藏保存,禁止手填 | -| `sortOrder` | 可选 int64 | U/S | 编辑页安全整数;非空时提交 | -| `status` | 可选 string `0/1` | A/I | C39 未解决前固定隐藏提交 `0` | -| `genealogyId`、`albumId` | path int64 | I/A | 家谱上下文及相册响应;按十进制字符串处理,不显示、不手填 | - -照片请求字段: - -| 请求字段 | 必填性 / 类型 | 标记 | 页面来源与提交时机 | -| --- | --- | --- | --- | -| `ossId` | 必填 string/int64 | F/I | 选择照片并完成统一上传后自动产生;隐藏保存,禁止手填 | -| `photoTitle`、`photoDesc`、`photographer` | 可选 string | U | 照片表单;非空时提交 | -| `shootTime` | 可选 date | S | 日期选择器;非空时提交 `YYYY-MM-DD` | -| `sortOrder` | 可选 int64 | U/S | 照片表单安全整数;非空时提交 | -| `status` | 可选 string `0/1` | A/I | C39 未解决前固定隐藏提交 `0` | -| `albumId`、`photoId` | path int64 | I/A | 相册和照片响应;按十进制字符串处理,不显示、不手填 | - -PC 响应字段及页面用途: - -| 对象 | 字段 | 标记 | 页面处理 | -| --- | --- | --- | --- | -| `AlbumVo` | `albumId`、`genealogyId` | I | 列表、编辑、详情、删除和写后重读的稳定 ID | -| `AlbumVo` | `genealogyNo`、`genealogyName`、`surname` | R | 家谱只读上下文 | -| `AlbumVo` | `albumName`、`albumDesc` | R/U | 列表/详情展示和编辑回填;可见内容均转义 | -| `AlbumVo` | `coverOssId` | F/I | 编辑隐藏回填;C38 未解决前不拼封面 URL | -| `AlbumVo` | `photoCount` | R | 列表和详情照片数量 | -| `AlbumVo` | `sortOrder`、`status` | I/U | 编辑回填;只允许正常记录进入编辑器 | -| `AlbumVo` | `remark` | R | 详情只读展示,不进入请求 Body | -| `AlbumPhotoVo` | `photoId`、`genealogyId`、`albumId` | I | 照片删除和写后重读的稳定 ID | -| `AlbumPhotoVo` | `genealogyNo`、`genealogyName`、`surname`、`albumName` | R | 家谱与相册只读上下文 | -| `AlbumPhotoVo` | `ossId` | F/I | 文件引用;C38 未解决前不拼照片 URL | -| `AlbumPhotoVo` | `photoTitle`、`photoDesc`、`photographer`、`shootTime` | R/U | 照片列表展示;可见内容均转义 | -| `AlbumPhotoVo` | `sortOrder`、`status` | I | 列表顺序和正常状态校验 | -| `AlbumPhotoVo` | `remark` | R | 只读展示,不进入请求 Body | - -接入边界: - -- 相册只调用 `/genealogy/pc/genealogies/{genealogyId}/albums` 及其 `/{albumId}` 子路径;照片只调用该相册下的 `/photos` 及 `/{photoId}`; -- 后端没有单独的相册详情接口,详情和编辑页必须重读相册列表并用响应中的稳定 `albumId` 精确匹配; -- YAML 响应仍为泛型包装,前端只接受后端 PC 控制器实际返回的 `AlbumVo` / `AlbumPhotoVo` 直接数组,不读取 APP 路径; -- 新增/修改相册后重读相册列表;新增/删除照片后重读照片列表;所有写操作防重复; -- 页面先读取家谱详情,只有 `canEditContent/canManage` 显示新增、编辑、删除和照片维护; -- C38 未解决前不猜文件地址;C39 未解决前不开放停用; -- 相关聚焦测试 49/49 通过;真实账号页面验证统一留到阶段 6 收尾执行。 - -### 7.18 祭祀活动、祭品与管理员受邀名单 - -页面:`profile-ceremony.html`、`profile-gift-edit.html`、`profile-ceremony-detail.html`;当前账号收到的邀请继续由 `profile-gift.html` 负责。 - -活动请求字段: - -| 请求字段 | 必填性 / 类型 | 标记 | 页面来源与提交时机 | -| --- | --- | --- | --- | -| `ceremonyType` | 必填 string,无枚举 | U | 编辑页活动类型;保存时提交,不借用其他端枚举 | -| `ceremonyTitle` | 必填 string | U | 编辑页标题;保存时提交 | -| `ceremonyDesc` | 可选 string | U | 编辑页说明;非空时提交 | -| `ceremonyTime` | 可选 date-time | S | 日期时间选择器;转换为后端 Date 可解析格式后提交 | -| `location` | 可选 string | U | 编辑页地点名称;非空时提交 | -| `locationAddress` | 可选 string,最长 300 | U | 编辑页详细地址;非空时提交 | -| `longitude` | 可选 number,`[-180, 180]` | U/S | 与纬度同时填写并保存 | -| `latitude` | 可选 number,`[-90, 90]` | U/S | 与经度同时填写并保存 | -| `coverOssId` | 可选 string/int64 | F/I | 选择封面并完成统一上传后自动产生;隐藏保存,禁止手填 | -| `sortOrder` | 可选 int64 | U/S | 编辑页安全整数;非空时提交 | -| `status` | 可选 string `0/1` | A/I | C41 未解决前固定隐藏提交 `0` | -| `genealogyId`、`ceremonyId` | path int64 | I/A | 家谱上下文和活动响应;按十进制字符串处理,不显示、不手填 | - -祭品与受邀名单请求字段: - -| 请求字段 | 必填性 / 类型 | 标记 | 页面来源与提交时机 | -| --- | --- | --- | --- | -| `giverName` | 可选 string | U | 祭品表单赠送人姓名;非空时提交 | -| `giftAmount` | 必填 number,后端要求非负 | U | 祭品表单金额;添加祭品时提交;C42 未解决前不限制精度和最大值 | -| `giftMessage` | 可选 string | U | 祭品留言;非空时提交 | -| `inviteeUserIds` | 必填、唯一 int64 数组,可为空 | S/I | 从 `members/options` 的正常且已绑定账号成员多选产生;更新名单时提交完整数组,禁止手填 | -| `giftId`、`invitationId`、`inviteeUserId` | path/response int64 | I/A | 只从 PC 响应取得;用于删除、名单匹配和写后重读,不显示原值 | - -PC 响应字段及页面用途: - -| 对象 | 字段 | 标记 | 页面处理 | -| --- | --- | --- | --- | -| `CeremonyVo` | `ceremonyId`、`genealogyId`、`sponsorUserId` | I | 活动 CRUD、详情和写后重读的稳定 ID | -| `CeremonyVo` | `genealogyNo`、`genealogyName`、`surname` | R | 家谱只读上下文 | -| `CeremonyVo` | `sponsorNickName` | R | 发起人只读展示 | -| `CeremonyVo` | `sponsorPhone` | I | 隐私字段,始终不展示 | -| `CeremonyVo` | `ceremonyType`、`ceremonyTitle`、`ceremonyDesc`、`ceremonyTime`、`location`、`locationAddress` | R/U | 列表/详情展示和编辑回填;可见内容均转义 | -| `CeremonyVo` | `longitude`、`latitude` | I/S | 仅用于生成安全地图链接,不直接展示数值 | -| `CeremonyVo` | `coverOssId` | F/I | 编辑隐藏回填;C40 未解决前不拼封面 URL | -| `CeremonyVo` | `giftCount`、`giftAmount` | R | 详情祭品数量与礼金合计 | -| `CeremonyVo` | `sortOrder`、`status` | I/U | 编辑回填;只允许正常记录进入编辑器 | -| `CeremonyVo` | `remark` | R | 详情只读展示,不进入请求 Body | -| `CeremonyGiftVo` | `giftId`、`genealogyId`、`ceremonyId`、`giverUserId` | I | 祭品删除、关联和写后重读的稳定 ID | -| `CeremonyGiftVo` | `genealogyNo`、`genealogyName`、`surname`、`ceremonyTitle` | R | 家谱与活动只读上下文 | -| `CeremonyGiftVo` | `giverNickName`、`giverName`、`giftAmount`、`giftMessage`、`giftTime` | R | 祭品列表展示;可见内容均转义 | -| `CeremonyGiftVo` | `giverPhone` | I | 隐私字段,始终不展示 | -| `CeremonyGiftVo` | `status`、`remark` | R/I | 正常状态校验和只读备注 | -| `CeremonyInvitationVo` | 全部 ID、版本 | I | 邀请状态匹配和更新后的重读校验,不显示原值 | -| `CeremonyInvitationVo` | `inviteStatus`、投递/阅读/响应时间 | R | 管理员邀请列表只读展示 | -| `GenealogyMemberVo` 选项 | `appUserId` | I/S | 受邀人的唯一真实业务用户 ID 来源 | -| `GenealogyMemberVo` 选项 | `memberName`、`appUserNickName` | R/S | 受邀人选择器标签;手机号不展示 | - -接入边界: - -- 活动只调用 `/genealogy/pc/genealogies/{genealogyId}/ceremonies` 及 `/{ceremonyId}`;祭品只调用其 `/gifts` 子路径; -- 管理员邀请列表和替换只调用同一活动下的 `/invitations` 与 `/invitees`,受邀人来源只调用当前家谱 `/members/options`; -- 新增/修改活动后重读同一详情,新增/删除祭品后重读祭品列表,更新受邀人后重读邀请列表;所有写操作防重复; -- 页面先读取家谱详情,只有 `canEditContent/canManage` 显示活动、祭品和受邀名单维护; -- 空数组允许取消全部尚未响应邀请;响应过的历史邀请由后端保留状态,前端不伪造删除; -- C40 未解决前不猜封面地址;C41 未解决前不开放停用;C42 未解决前不猜金额精度和上限; -- 相关聚焦测试 56/56 通过;真实账号页面验证统一留到阶段 6 收尾执行。 - -### 7.19 家谱成员管理、退出与谱主转移 - -页面:`profile-family-admin.html`。 - -成员修改请求字段: - -| 请求字段 | 必填性 / 类型 | 标记 | 页面来源与提交时机 | -| --- | --- | --- | --- | -| `memberName` | 可选 string,最长 50 | U | 管理员编辑表单;非空时提交 | -| `relationName` | 可选 string,最长 100 | U | 管理员编辑表单;后端明确接收空串时可清空 | -| `roleType` | 可选 string,`admin/editor/member` | S | 按当前管理者和目标角色过滤;保存时提交,不发送 `owner/visitor` | -| `lineagePersonId` | 可选 int64 | S/I | 从当前家谱世系人物选项选择;非空时提交,留空表示不修改而不是解绑 | -| `genealogyId`、`memberId` | path int64 | I/A | 家谱上下文和成员列表响应;按字符串处理,不显示、不手填 | - -退出与谱主转移: - -| 字段/操作 | 必填性 / 类型 | 标记 | 页面来源与提交时机 | -| --- | --- | --- | --- | -| `DELETE /members/me` | 无 Body | S/A | 当前正常成员二次确认后提交;谱主不显示退出按钮 | -| `targetMemberId` | 必填 int64 | S/I | 谱主从当前正常成员列表选择新谱主;禁止选择自己和手填 ID | -| `DELETE /members/{memberId}` | path int64 | S/I | 管理员从真实成员行执行“移出家谱”;谱主不可被移出 | - -PC `GenealogyMemberVo` 响应字段及页面用途: - -| 字段 | 标记 | 页面处理 | -| --- | --- | --- | -| `memberId`、`genealogyId`、`appUserId`、`lineagePersonId`、`inviterUserId` | I | 当前成员匹配、修改、移出、转让和世系绑定的稳定 ID;不显示原值 | -| `genealogyNo`、`genealogyName`、`surname` | R | C44 未解决前允许为空的家谱只读上下文 | -| `appUserNickName`、`lineagePersonNo`、`lineagePersonName`、`memberName` | R/U | 成员列表标签和编辑回填;可见内容均转义 | -| `appUserPhone`、`inviterPhone` | I | 隐私字段,始终不展示 | -| `roleType` | R/S | 响应可含 `owner/admin/editor/member/visitor`;修改只提交三种后端可分配角色 | -| `relationName` | R/U | 列表展示和编辑回填 | -| `joinSource`、`inviterNickName`、`joinTime` | R | 加入来源只读说明;C44 未填充时允许为空 | -| `status` | I | 列表只接受后端返回的正常状态 `0` | - -权限与状态边界: - -- 所有账号可按家谱查看权限读取正常成员;只有家谱详情 `canManage=true` 时显示修改和移出; -- 谱主不能直接修改或移出;只有谱主可管理管理员、分配 `admin` 或转让谱主;管理员只能管理非管理员成员并分配 `editor/member`; -- 普通成员、编辑和管理员可退出家谱;谱主必须先转让谱主。退出和移出只停用成员关系,不删除世系人物; -- 更新后重读成员列表并核对同一 `memberId`;移出后核对该成员不再出现在正常列表;谱主转移后重读家谱详情和成员列表; -- 页面没有直接新增成员接口;新增成员必须通过后端已有的加入/邀请业务闭环; -- C44 未解决前不依赖 enrichment 字段并隐藏手机号;C45/C46 未解决前不伪造关系解除或成员-世系人物解绑; -- 相关聚焦测试 49/49 通过;真实账号页面验证统一留到阶段 6 收尾执行。 - -### 7.20 家谱创建与加入申请契约 - -页面:`profile-create-family.html`、`join-genealogy.html`、`profile-join-family.html`、`profile-join-review.html`。 - -`AppGenealogyCreateBody`: - -| 字段 | 类型 | 必填 | 来源 | 页面与提交时机 | -| --- | --- | --- | --- | --- | -| `genealogyName` | string | 是 | U | 创建页谱名;提交时发送 | -| `surname` | string | 是 | U | 创建页姓氏;提交时发送 | -| `regionCode` | string | 是 | S | 省市区选择器自动取最后一级;提交时发送 | -| `ancestralHall`、`originPlace`、`addressDetail`、`intro` | string | 否 | U | 创建页可选输入;非空时发送 | -| `coverOssId` | int64/string | 否 | F | 封面上传成功后自动产生;不允许手填 | -| `visibility` | string enum `0/1/2` | 否 | S | 私密/公开/成员可见 | -| `joinMode` | string enum `0/1/2` | 否 | S | 关闭/审核/邀请模式 | - -实现边界: - -- 创建只调用 `POST /genealogy/pc/genealogies`,提交前读取 `GET /genealogy/pc/genealogies/quota`; -- 地区选项复用 `/genealogy/pc/region/children?parentCode=...`,不手写地区编码; -- 创建成功后使用响应中的稳定 `genealogyId` 重读同一家谱,匹配后进入家谱主页; -- 申请页先从 `GET /genealogy/pc/genealogies/options` 选择响应中的家谱,再调用 `POST /genealogy/pc/genealogies/{genealogyId}/join-applies`; -- 我的申请页调用 `GET /genealogy/pc/genealogies/join-applies/mine`;只有 `status=0` 的响应项可以调用 `DELETE /genealogy/pc/genealogies/join-applies/{applyId}`; -- 审核页只从当前家谱上下文取得 `genealogyId`,先读取家谱详情并要求 `canManage=true`,再读取 `GET /genealogy/pc/genealogies/{genealogyId}/join-applies/pending`; -- 审核只调用 `PUT /genealogy/pc/genealogies/{genealogyId}/join-applies/{applyId}/audit`,`status=1` 通过、`status=2` 拒绝; -- `inviterUserId` 没有安全 PC 邀请来源,本轮加入申请契约不发送该字段; -- 创建/申请/审核页面均无家谱 ID、申请 ID、用户 ID 或 OSS ID 文本输入,所有写操作防重复并在成功后重读对应资源。 - -`AppGenealogyJoinApplyBody`: - -| 字段 | 类型/约束 | 必填 | 来源 | 页面与提交时机 | -| --- | --- | --- | --- | --- | -| `applicantName` | string,最长 50 | 否 | U | 申请页姓名;非空时提交 | -| `phone` | string,最长 30 | 否 | U | 申请页联系电话;非空时提交 | -| `relationDesc` | string,最长 100 | 否 | U | 申请页关系说明;非空时提交 | -| `applyReason` | string,最长 500 | 否 | U | 申请页申请理由;非空时提交 | -| `inviterUserId` | int64/string | 邀请模式条件必填 | I | PC 无安全来源,不展示、不发送;邀请模式继续阻断 | - -`GenealogyJoinApplyVo` 与审核: - -| 字段 | 类型/枚举 | 来源 | 页面用途 | -| --- | --- | --- | --- | -| `applyId`、`genealogyId` | int64/string | R/I | 响应按钮属性和写入 path;不显示、不手填 | -| `genealogyName`、`surname` | string | R | 我的申请列表只读展示 | -| `applicantName`、`phone`、`relationDesc`、`applyReason` | string | R | 管理员待审核列表;只展示申请人主动提交的 `phone` | -| `auditTime`、`auditRemark` | datetime/string | R | 我的申请审核结果展示 | -| `status` | string `0/1/2/3` | R | 待审核/通过/拒绝/撤销;仅 `0` 可撤销 | -| `appUserId`、`inviterUserId`、`auditUserId` | int64/string | I | 普通列表和审核列表均不展示 | -| `appUserPhone`、`inviterPhone`、`auditPhone` | string | I | 账号/邀请/审核人员隐私字段均不展示 | -| 审核 `status` | string `1/2` | S | 管理员点击通过或拒绝时提交 | -| 审核 `auditRemark` | string,最长 500 | U | 拒绝时可选填写,非空时提交 | - -### 7.21 意见反馈与工单契约 - -页面:`profile-feedback.html`、`submit-ticket.html`、`my-tickets.html`、`ticket-detail.html`。 - -后端只提供统一反馈集合: - -- `POST /genealogy/pc/feedback`:当前业务用户提交反馈; -- `GET /genealogy/pc/feedback`:读取当前业务用户自己的反馈列表; -- 没有独立 ticket path;“工单”只是帮助中心对同一反馈记录的页面名称; -- 没有用户侧回复、追问、关闭或删除接口,页面不制造这些操作。 - -`AppFeedbackBody`: - -| 字段 | 类型/约束 | 必填 | 来源 | 页面与提交时机 | -| --- | --- | --- | --- | --- | -| `feedbackType` | string enum `advice/bug/complaint/other` | 否 | S | 两个提交页选择;空值省略并由后端默认 `advice` | -| `feedbackContent` | string,非空 | 是 | U | 问题或建议正文;提交时发送 | -| `contactInfo` | string | 否 | U | 用户主动提供的联系方式;非空时发送 | -| `feedbackTitle` | — | — | V | 旧页面字段不属于 DTO,已从表单移除且不发送 | - -`FeedbackVo`: - -| 字段 | 类型/枚举 | 来源 | 页面用途 | -| --- | --- | --- | --- | -| `feedbackId` | int64/string | R/I | 提交后重读匹配、列表详情链接和 URL;不展示、不手填 | -| `feedbackType`、`feedbackContent`、`contactInfo` | string | R | 列表或详情只读展示 | -| `handleStatus` | string `0/1/2/3` | R | 待处理/处理中/已处理/已关闭 | -| `handleResult`、`handleTime`、`remark` | string/datetime | R | 后端存在时在详情展示 | -| `status` | string `0/1` | I | 响应有效性校验,不提供用户修改入口 | -| `appUserId`、`handlerId` | int64/string | I | 内部用户/处理人编号,不展示 | -| `appUserNickName`、`appUserPhone` | string | I | 后台 enrichment,不在用户页面展示 | - -实现边界: - -- ApiClient 是 GET/POST path 和三字段 body 白名单唯一 owner; -- 提交使用防重复锁;成功响应必须包含稳定字符串 `feedbackId`; -- 提交后重读我的反馈列表并精确找到同一 `feedbackId`,否则不宣称成功; -- 工单详情从 URL 读取列表响应产生的 `feedbackId`,只在我的列表中精确匹配,不回退第一条; -- 401 清理登录态;403 保留登录态并显示后端权限错误;页面不展示原始 JSON。 - -### 7.22 VIP 套餐与会员订单契约 - -页面:`profile-services.html`。 - -PC 端只开放三条业务用户接口: - -- `GET /genealogy/pc/vip/packages`:读取可购买套餐,成功响应 `List`,无分页结构; -- `POST /genealogy/pc/vip/orders`:创建当前业务用户的会员订单,body 为 `AppVipOrderBody`,成功响应 `VipOrderVo`; -- `GET /genealogy/pc/vip/orders`:读取当前业务用户的订单,成功响应 `List`,无分页结构; -- 三条接口均要求 APP_USER 登录态;创建接口有后端防重复提交; -- PC Controller 没有支付发起、支付回调、取消、关闭或退款接口,页面不得制造这些动作。 - -`AppVipOrderBody`: - -| 字段 | 类型/约束 | 必填 | 来源 | 页面与提交时机 | -| --- | --- | --- | --- | --- | -| `packageId` | int64,稳定字符串 ID | 是 | S/I | 用户点击套餐响应生成的卡片时选择;创建订单时发送,禁止手填 | -| `genealogyId` | int64,稳定字符串 ID | 否 | S/I | 用户从“我的家谱”响应生成的下拉项中选择;非空时创建订单发送,禁止手填 | -| `payType` | string;后端空值默认 `wechat` | 否 | A | PC 页面不提供支付方式选择且默认省略;等待后端开放真实支付能力后再确认枚举 | - -`VipPackageVo`: - -| 字段 | 类型/枚举 | 来源 | 页面用途 | -| --- | --- | --- | --- | -| `packageId` | int64/string | R/I | 套餐卡片选择和订单 body;不展示、不手填 | -| `packageName`、`packageDesc` | string | R | 套餐名称和说明 | -| `packageType` | string `vip/storage` | R | 显示为会员套餐/存储扩容;未知值整条过滤 | -| `price`、`originalPrice` | decimal,非负 | R | 价格展示;不参与前端金额计算 | -| `durationValue` | integer,非负,可空 | R | 与期限单位组合展示 | -| `durationUnit` | string `permanent/day/month/year` | R | 永久/天/月/年 | -| `genealogyLimit`、`memberLimit`、`storageLimitMb` | integer,非负,可空 | R | 套餐额度只读展示 | -| `featureJson` | string/JSON | I | 契约未说明用户侧展示结构,当前不解析、不展示 | -| `sortOrder` | integer | I | 后端列表顺序 owner,前端不重新排序 | -| `status` | string `0/1` | I | 只接受正常 `0` 套餐;不提供修改入口 | -| `remark` | string | R | 非空时作为套餐补充说明展示 | - -`VipOrderVo`: - -| 字段 | 类型/枚举 | 来源 | 页面用途 | -| --- | --- | --- | --- | -| `orderId` | int64/string | R/I | 创建后重读精确匹配;不展示、不手填 | -| `orderNo` | string | R | 订单列表展示 | -| `packageId` | int64/string | R/I | 响应有效性校验,不展示 | -| `packageName` | string | R | 订单套餐名称 | -| `genealogyId` | int64/string,可空 | R/I | 响应有效性校验,不展示 | -| `genealogyNo`、`genealogyName` | string,可空 | R | 关联家谱信息展示 | -| `orderAmount`、`payAmount` | decimal,非负 | R | 订单金额和应付金额展示;以后端值为准 | -| `payType` | string,可空 | R | 后端返回时只读展示,不作为支付入口 | -| `payStatus` | string `0/1/2/3` | R | 待支付/已支付/已关闭/已退款 | -| `payTime`、`expireTime` | datetime,可空 | R | 支付和有效期信息展示 | -| `status` | string `0/1` | I | 响应有效性校验,不提供修改入口 | -| `remark` | string | R | 非空时展示 | -| `appUserId`、`appUserNickName`、`appUserPhone` | int64/string | I | 当前账号及 enrichment 字段,不在页面展示 | - -实现边界: - -- `ApiClient` 是三条 path 与三字段请求白名单唯一 owner; -- 页面启动时并行读取套餐、我的家谱和订单;所有业务 ID 仅来自响应; -- 创建使用前端防重复锁;创建响应必须是完整有效 `VipOrderVo`,随后重读订单列表并精确找到同一 `orderId` 才宣称成功; -- 页面省略 `payType`,由后端使用已确认的默认值;不展示后端尚未提供的支付、取消或退款按钮; -- 401 清理登录态;403 保留登录态并展示后端错误;非法套餐、订单、金额、枚举或不安全数字 ID 整条过滤。 - -### 7.23 家谱主页只读概览契约 - -页面:`profile-family-home.html`。 - -只使用两条现有 PC 读取接口: - -- `GET /genealogy/pc/genealogies/{genealogyId}/overview`:返回当前家谱 `AppGenealogyVo`; -- `GET /genealogy/pc/genealogies/{genealogyId}/lineage/tree`:返回 `List`; -- `genealogyId` 只从 `profile-common.js` 的当前家谱上下文取得,两条请求使用同一个稳定字符串 ID; -- 后端 `overview` 当前实际复用家谱详情服务,不包含文章、相册、视频、祭祀或动态聚合统计。 - -`AppGenealogyVo` 主页字段: - -| 字段 | 类型/约束 | 来源 | 页面用途 | -| --- | --- | --- | --- | -| `genealogyId` | int64/string | I/A/R | 当前上下文、两条请求 path、响应一致性校验;不展示、不手填 | -| `genealogyNo` | string | R | 家谱编号只读展示 | -| `genealogyName` | string,非空 | R | 页面标题和概览名称 | -| `surname` | string | R | 姓氏只读展示 | -| `ancestralHall` | string,可空 | R | 堂号非空时展示 | -| `originPlace` | string,可空 | R | 祖籍非空时展示 | -| `regionFullName` | string,可空 | R | 完整地区非空时展示 | -| `memberCount`、`personCount` | integer,非负 | R | 家谱成员数和世系人物数;不在前端计算 | -| `status` | string `0/1` | I | 只接受正常 `0` 响应 | -| `canManage` | boolean | I | 严格为 `true` 时显示家谱管理入口 | -| `canEditContent` | boolean | I | 保留为内容权限上下文,主页不据此制造写操作 | -| `ownerUserId`、`coverOssId` | int64/string | I | 当前主页不展示、不手填 | -| 其余地址、角色、成员状态字段 | 对应 DTO 类型 | I | 当前主页不消费,不输出原始 JSON | - -`LineagePersonTreeView`: - -| 字段 | 类型/结构 | 来源 | 页面用途 | -| --- | --- | --- | --- | -| `personId`、`genealogyId` | int64/string | R/I | 树节点稳定标识和响应校验;不直接展示 | -| `name`、`generationName`、人物摘要字段 | string | R | 复用世系页的安全节点渲染 | -| `relationType` | string `father/mother/spouse/child/adoptive` | R | 关系数据;主页不提供关系写操作 | -| `relationName` | string | R | 非空时作为关系说明 | -| `spouses`、`children` | `LineagePersonTreeView[]` | R | 递归世系预览 | - -实现边界: - -- `lineage-pages.js` 是世系节点规范化和树 HTML 的唯一 owner;主页不得复制一套树字段解释; -- 页面并行读取概览和世系树,只展示基础信息、后端已有计数和真实树; -- `/genealogy/dashboard/overview` 是后台权限接口,不属于 PC 家谱主页契约,前端不调用; -- PC 没有成员邀请创建或分享接口,“邀请家人”只显示阻断说明,不提供可点击操作; -- 主页没有写请求;401 清理登录态,403 保留登录态并显示读取错误。 - -### 7.24 帮助中心文章契约 - -页面:`help.html`。契约 owner:`utils/ApiClient.js` 的 `helpArticles` / `helpArticleDetail` 和 `public/js/help-pages.js`。 - -接口: - -| method | 完整 path | 目录 | 鉴权 | 页面时机 | -| --- | --- | --- | --- | --- | -| GET | `/genealogy/pc/help-articles` | 内容文章 / PC 帮助文章列表 | APP_USER 登录态 | 帮助中心初始化或用户点击刷新 | -| GET | `/genealogy/pc/help-articles/{helpId}` | 内容文章 / PC 帮助文章详情 | APP_USER 登录态 | 用户从列表点击“查看完整解答” | - -请求字段: - -| 位置 | 字段 | 必填 | 类型/约束 | 分类 | 页面来源与提交时机 | -| --- | --- | --- | --- | --- | --- | -| Query | `helpCategory` | 否 | string;后端按完全相等过滤 | S | 当前页面不提供猜测分类选项,因此初始化时省略;后端提供正式分类字典后才能开放选择 | -| Path | `helpId` | 是 | int64;前端始终按非零十进制字符串处理 | I | 只能来自列表响应;用户点击该文章详情时进入 path | -| Header | `clientid` | 基础请求自动 | string | A | `AxiosRequestUtil` 统一填写;页面不输入 | -| Header | `Authorization` | 是 | `Bearer ` | A | `AxiosRequestUtil` 从登录态自动填写;页面不读取、不展示 token | -| Body | — | — | 两个接口均无 Body | — | — | - -`HelpArticleVo` 响应字段: - -| 字段 | 类型 | 分类 | 页面使用 | -| --- | --- | --- | --- | -| `helpId` | int64/string | I | 校验稳定 ID、绑定列表项并精确重读详情;不向用户显示 | -| `helpCategory` | string | R | 列表和详情分类文字,可空 | -| `helpTitle` | string | R | 列表标题,空值使整条响应失败 | -| `helpContent` | string | R | 详情正文;转义并保留换行,不执行响应 HTML | -| `coverOssId` | int64/string | I | 当前响应没有文件 URL,页面不显示、不拼接、不允许手填 | -| `sortOrder` | int64 | I | 仅后端排序,不展示 | -| `viewCount` | int64/string | R | 非负十进制计数;详情 GET 会由后端累计浏览量 | -| `status` | string | I | 只接受正常值 `0`;停用文章整条拒绝 | -| `remark` | string | I | 后台备注,不展示 | - -响应与错误边界: - -- 列表响应必须是直接数组,不接受分页 `rows` 猜测;任一元素缺少稳定 ID、标题、正文或正常状态时整批失败; -- 详情必须返回与 path 中相同的 `helpId`,不回退到列表第一条; -- YAML 把两条接口标为 `security: []`,但 `PcHelpArticleController` 没有 `@SaIgnore`,部署环境无 token 实测返回“认证失败”;按用户指定的后端项目为准,当前客户端必须携带登录 token,此项作为 Apifox/后端冲突保留; -- 后端只声明统一成功包装,未提供帮助文章专属失败码;详情不存在或停用时按业务错误展示,不制造 404 枚举; -- 无登录态或 401 清理登录状态并进入登录页;403 保留登录态并展示读取错误; -- 页面只有读取动作,不提供编辑、发布、分类管理、封面 OSS ID 或成员邀请操作。 - -### 7.25 应用推广契约 - -页面:`app.html` 的“应用推广”区域。契约 owner:`utils/ApiClient.js` 的 `promotions` 和 `public/js/app-promotion-pages.js`。 - -接口: - -| method | 完整 path | 目录 | 鉴权 | 页面时机 | -| --- | --- | --- | --- | --- | -| GET | `/genealogy/pc/promotions` | 应用推广 / PC 应用推广列表 | APP_USER 登录态 | 应用下载页初始化;仅在本地已有登录 token 时请求一次 | - -请求字段: - -| 位置 | 字段 | 必填 | 类型/约束 | 分类 | 页面来源与提交时机 | -| --- | --- | --- | --- | --- | --- | -| Query | `platform` | 否 | string;SQL 字典 `gen_promotion_platform`:`all` 全部(默认)、`app` APP、`pc` PC、`wechat` 小程序 | A | 应用下载页初始化时固定发送 `pc`;服务端返回 `platform=pc` 或 `platform=all` 的正常记录,不让用户选择或手填 | -| Header | `clientid` | 基础请求自动 | string | A | `AxiosRequestUtil` 统一填写;页面不输入 | -| Header | `Authorization` | 是 | `Bearer ` | A | `AxiosRequestUtil` 从登录态自动填写;页面不读取、不展示 token | -| Path | — | — | 无 Path 参数 | — | — | -| Body | — | — | 无 Body | — | — | - -`AppPromotionVo` 列表元素字段: - -| 字段 | 类型 | 必填/约束 | 分类 | 页面使用 | -| --- | --- | --- | --- | --- | -| `promotionId` | int64/string | SQL `BIGINT NOT NULL` 主键;前端按非零十进制字符串处理 | I | 稳定绑定卡片,不展示、不允许手填 | -| `promotionKey` | string | SQL `varchar(80) NOT NULL DEFAULT 'banner'` | I | 后台识别字段,页面不展示、不提交 | -| `promotionTitle` | string | SQL `varchar(200) NOT NULL`;页面要求非空 | R | 推广卡片标题,HTML 转义后展示 | -| `promotionDesc` | string | SQL `varchar(500) DEFAULT ''`,可空 | R | 推广卡片说明,HTML 转义后展示 | -| `coverOssId` | int64/string | 可空;响应没有可访问文件 URL | I | 不显示、不拼接 URL、不允许用户手填 OSS ID | -| `targetUrl` | string | SQL `varchar(500) DEFAULT ''`,可空;页面只接受绝对 `http`/`https` URL | R | 合法时作为新窗口外链;危险、相对或无效地址降级为不可点击卡片 | -| `platform` | string | SQL `varchar(30) NOT NULL DEFAULT 'all'`;字典枚举 `all/app/pc/wechat` | I | 仅后端筛选,当前页面不展示 | -| `sortOrder` | int64 | SQL `BIGINT NOT NULL DEFAULT 0`;后端按升序排序,同序按 `promotionId` 降序 | I | 仅后端排序,页面不二次解释或展示 | -| `status` | string | SQL `char(1) NOT NULL DEFAULT '0'`;`0` 正常、`1` 停用,页面只接受 `0` | I | 停用或缺失状态的记录使整批响应失败 | -| `remark` | string | SQL `varchar(500) DEFAULT NULL`,可空 | I | 后台备注,不展示 | - -响应、权限与错误边界: - -- 成功响应经统一请求层解包后必须是直接 `AppPromotionVo[]`,不是分页结构;任一元素缺少稳定 ID、标题或正常状态时整批失败; -- 空数组显示“当前暂无应用推广”;未登录时页面本身仍可浏览,但推广区域只显示登录入口且不发请求; -- YAML 把该接口标为 `security: []`,并且未导出真实 Query `platform`;后端 `PcPromotionApiController` 没有 `@SaIgnore`,全局拦截器要求 APP_USER 登录态。SQL 字典已确认 `all/app/pc/wechat`,服务层按“请求平台或 `all`”过滤,因此当前实现携带登录 token 并固定发送 `platform=pc`; -- SQL `gen_app_promotion` 另有 `(tenant_id, platform, status, del_flag)` 和 `(tenant_id, sort_order)` 索引;`del_flag`、租户及审计字段不属于 `AppPromotionVo`,页面不接收、不展示; -- 401 清理失效 token 并把推广区域切回登录提示;403、网络失败或畸形响应只在推广区域显示读取失败,不跳转、不清理有效登录态; -- 后端只声明统一成功包装,未提供推广专属失败响应和错误码;字段长度、默认值及平台枚举已由后端 SQL 冻结,不再作为阻断项; -- 页面没有推广新增、编辑、上下架、排序、封面上传能力,不调用 APP 或后台管理接口,也不向用户暴露推广 ID、推广键或 OSS ID。 - -### 7.26 官网资讯契约 - -页面:`news.html`、`article-detail.html`。契约 owner:`utils/ApiClient.js` 的 `siteArticles`、`utils/AxiosRequestUtil.js` 的公开请求鉴权语义和 `public/js/site-news-pages.js`。 - -接口: - -| method | 完整 path | 目录 | 鉴权 | 页面时机 | -| --- | --- | --- | --- | --- | -| GET | `/genealogy/pc/site/articles` | 站点内容 / PC 站点文章列表 | 公开,`auth: false` | 资讯列表初始化、分类切换后的页面初始化,以及详情页按列表响应 ID 精确重读 | - -请求字段: - -| 位置 | 字段 | 必填 | 类型/约束 | 分类 | 页面来源与提交时机 | -| --- | --- | --- | --- | --- | --- | -| Query | `articleType` | 否 | string;SQL 字典 `gen_site_article_type`:`news` 新闻(默认)、`notice` 公告、`download` 下载 | S | 用户点击固定分类链接后从 URL Query 取得;“全部”省略该字段,非法值不发请求 | -| Query | `limit` | 否 | integer,客户端只允许 1–100;服务端正数最大取 100 | A | 列表和详情重读均由页面固定提交 `100` | -| Header | `clientid` | 基础请求自动 | string | A | `AxiosRequestUtil` 统一填写;页面不输入 | -| Header | `Authorization` | 不发送 | — | — | 方法设置 `auth: false`;即使本地已有 token 也不发送,公开请求 401/403 不清理既有登录态 | -| Path | — | — | 无 Path 参数 | — | — | -| Body | — | — | 无 Body | — | — | - -`SiteArticleVo` 列表元素字段: - -| 字段 | 类型 | 必填/约束 | 分类 | 页面使用 | -| --- | --- | --- | --- | --- | -| `articleId` | int64/string | SQL `BIGINT NOT NULL` 主键;前端按非零十进制字符串处理 | I | 只用于生成详情链接和精确匹配,不展示、不允许手填 | -| `articleType` | string | SQL `varchar(50) NOT NULL DEFAULT 'news'`;枚举 `news/notice/download` | R | 转换为新闻、公告、下载标签;枚举外记录使整批失败 | -| `articleTitle` | string | SQL `varchar(200) NOT NULL`;页面要求非空 | R | 列表和详情标题,HTML 转义 | -| `articleSummary` | string | SQL `varchar(500) DEFAULT ''`,可空 | R | 列表摘要和详情导语,HTML 转义 | -| `articleContent` | string | SQL `text`,可空 | R | 详情正文;HTML 转义并只保留换行,不执行响应 HTML | -| `coverOssId` | int64/string | SQL `BIGINT`,可空;没有文件 URL 契约 | I | 不显示、不拼接 URL、不允许手填 | -| `externalUrl` | string | SQL `varchar(500) DEFAULT ''`,可空 | R | 只接受绝对 HTTP/HTTPS;合法时显示安全新窗口外链 | -| `publishTime` | string/date | SQL `datetime`,可空 | R | 列表和详情发布时间文本,HTML 转义 | -| `sortOrder` | int64 | SQL `BIGINT NOT NULL DEFAULT 0` | I | 仅后端排序,不展示 | -| `status` | string | SQL `char(1) NOT NULL DEFAULT '0'`;`0` 正常、`1` 停用 | I | 页面只接受 `0`,其他值使整批失败 | -| `remark` | string | SQL `varchar(500) DEFAULT NULL`,可空 | I | 后台备注,不展示 | - -响应、详情与错误边界: - -- 成功响应经统一请求层解包后必须是直接 `SiteArticleVo[]`;不接受分页 `rows`、旧字段别名或任一非法元素; -- 服务端只查正常状态,按 `sortOrder ASC`、`publishTime DESC`、`articleId DESC` 排序;页面保持服务端顺序; -- 后端没有站点文章单条详情接口;详情 ID 只能来自列表链接,详情页重读 `{ limit: 100 }` 后精确匹配同一字符串 ID,不存在时不得回退第一条; -- 标题、摘要、正文、类型和时间全部转义;正文只保留换行,危险或相对外链不渲染; -- 空数组显示“当前暂无资讯”;非法分类/详情 ID 不发请求;网络、业务或畸形响应只更新内容区域,不跳转登录; -- YAML 已导出 method/path、`articleType`、`limit` 和 `security: []`,但响应只引用通用列表结果,未导出 `SiteArticleVo` 11 个字段;完整字段、长度、默认值和枚举由后端 VO、SQL 与字典补齐; -- 2026-07-30 使用本地页面对当前部署环境做匿名实测时,接口返回“认证失败,无法访问系统资源”;页面保持在资讯页且不清理已有 token。该行为与 YAML 的 `security: []` 冲突,后端需确认部署环境是否遗漏公开放行;前端不通过附带登录 token 绕过公开契约; -- `coverOssId` 的文件 URL 和站点文章单条详情接口继续作为后端契约阻断;页面不猜 URL、不制造详情路径; -- 本批不修改 `about/culture/privacy/terms/surname`,也不复用登录后的家谱谱文 CRUD 脚本。 - -## 8. 分阶段执行顺序 - -### 阶段 0:冻结契约 - -工作: - -1. 完成第 5 节 C01-C20 的本阶段相关项。 -2. 从 Apifox 重导 OpenAPI。 -3. 写契约快照测试,统计操作、请求字段、required、枚举、response `$ref`。 -4. 确认旧路径和旧字段会失败,不保留兼容代码。 - -验证: - -- JSON 可被解析; -- 实际接口数与收口后的 Apifox 一致; -- 0 个重复逻辑路径; -- 本轮实现模块不存在泛型业务响应; -- 登录注册等公开接口不带 Authorization。 - -### 阶段 1:请求基础设施、验证和认证 - -工作: - -1. 收口 `config.js`、`AxiosRequestUtil.js`、`ApiClient.js` 的 header、token、tenant 和错误处理。 -2. 完成验证中心、登录、注册、短信、找回、换绑、注销、退出。 -3. 完成个人资料全部 6 个写入字段。 -4. 为每条认证流程测试 body 中不存在 `clientId`、`sceneCode` 和旧验证码字段。 - -验证: - -- 密码、短信两种登录成功; -- 验证策略开/关两条路径都成功; -- 一次性 `validToken` 不能复用; -- 401 清除登录态,403 保留登录态; -- 确认密码等 V 类字段未进入请求。 - -### 阶段 2:文件上传和区划 - -前提:C01、C08、C09、C10、C13 完成。 - -工作: - -1. 实现单分片小文件和多分片大文件同一流程。 -2. 实现秒传、进度、重试、重复提交锁。 -3. 只保留一套区划 API,并实现级联、搜索、路径回显。 -4. 所有业务页移除可见 OSS ID 输入框。 - -验证: - -- 普通图片、最后一片不足 chunkSize、大视频、秒传分别通过; -- MD5、大小、分片数量与初始化一致; -- 页面刷新后的业务对象能重新显示文件; -- 区划不同时请求两套路径。 - -### 阶段 3:家谱上下文 - -前提:C14 完成,或产品正式给出可验证的外部上下文契约。 - -工作: - -1. `profile-common.js` 成为 `genealogyId` 的唯一 owner。 -2. 我的家谱页提供真实选择入口。 -3. 所有家谱内链接传播同一个 ID,切换家谱时清空旧页面状态。 - -验证: - -- 无 ID 不发请求; -- 切换两个家谱不串数据; -- ID 始终按字符串处理; -- 不存在示例 ID 和 APP 请求。 - -### 阶段 4:已具备明确 DTO 的家谱模块 - -顺序: - -1. 家族圈; -2. 字辈谱; -3. 世系人物; -4. 我的贺礼邀请。 - -当前进度:阶段 4 已按顺序完成。家族圈的列表、正常状态详情、新增、修改、删除、点赞、评论、回复、权限显隐、空/错状态、防重复和写后重读已接入,C21 图片 URL 回显和 C22 停用记录管理继续阻断;字辈谱的正常/management 权限分流、单条新增修改和状态切换、批量预览保存、响应校验、防重复、写后重读及无家谱上下文分支已接入;世系人物的树、列表、分页、详情、完整字段表单、权限显隐、四类关系新增、逻辑停用、防重复和写后重读已接入,C15 继续阻断新增 `SPECIFIED` 账号绑定,C18 继续阻断可选字段清空闭环;我的贺礼邀请已接入当前账号列表、响应内详情、PENDING 接受/拒绝、长 ID 校验、权限/空错状态、防重复和写后重读,C16 继续阻断管理员替换受邀人。真实账号已验证邀请读取和空状态;该账号没有待响应邀请,接受/拒绝真实写入仍待有数据时复核。下一阶段为阶段 5,按顺序先核对视频 DTO 是否已完整。 - -每个模块采用同一闭环: - -1. `ApiClient` 方法和契约测试; -2. 列表/详情读取; -3. 新增; -4. 修改; -5. 删除/停用; -6. 权限、空状态、错误状态、重复提交; -7. 写入后重新读取验证。 - -### 阶段 5:补齐 DTO 后的资源模块 - -顺序: - -1. 视频; -2. 成长记录; -3. 亲友记录; -4. 备忘录; -5. 功德记录; -6. 消息通知。 - -任何模块的 View/List DTO 未补齐时,只完成表单布局和契约测试,不宣称真实功能完成。 - -当前进度:阶段 5 已按顺序完成。视频、成长记录、亲友记录、备忘录、功德记录和消息通知均已按对应 PC 契约开放;各模块的文件 URL、停用记录、可选字段清空和金额业务边界仍按 C18、C23–C34 保持阻断,不通过 APP 接口或猜测字段补齐。 - -### 阶段 6:当前 PC 契约可闭环模块 - -已完成: - -- 谱文列表、详情、新增、修改和删除; -- 相册列表、新增、修改、删除,以及照片列表、新增和删除; -- 祭祀活动列表、详情、新增、修改和删除,祭品列表、新增和删除; -- 管理员受邀名单读取和完整替换,当前账号邀请读取与响应; -- 从同家谱正常成员选项中选择账号完成 `SPECIFIED` 世系人物绑定; -- 家谱成员列表、修改、移出、当前成员退出和谱主转移; -- 个人中心、家谱首页和家谱内容页的稳定入口。 - -契约收口: - -- `replaceCeremonyInvitees(genealogyId, ceremonyId, inviteeUserIds)` 只接收真实成员选项产生的 ID 数组,`utils/ApiClient.js` 唯一封装 `{ inviteeUserIds }`; -- `/members/options` 严格按 YAML 不发送 Query;后端存在但 YAML 未定义的 `keyword` 不进入前端契约; -- 成员角色构造边界只接受 `admin/editor/member`,`owner/visitor` 在请求构造前失败; -- 所有业务 ID 来自 URL、上下文或响应选项,页面不提供手填 ID/OSS ID。 - -仍阻断: - -- C35–C46 中记录的分类选项、文件 URL、停用记录、金额业务精度/上限、响应 enrichment 与解绑契约; -- 世系关系解除没有 PC 接口;成员移出/退出不会伪造成世系关系解除或成员与世系人物解绑; -- 隔离测试账号已验证登录和无家谱上下文分支:阶段 6 页面均阻止业务请求、隐藏管理操作并返回家谱选择页,浏览器控制台无错误。该账号当前无家谱数据,因此列表有数据态、详情以及真实写操作仍待有数据账号复核,未制造家谱或业务记录。 - -### 阶段 7:剩余 PC 页面分批开放 - -首批已完成家谱生命周期 ApiClient 契约和创建家谱页面:额度、三级地区、封面上传派生、严格 DTO 校验、防重复与创建后重读均已接入。第二批已完成申请加入、我的申请、撤销和管理员审核页面;家谱/申请 ID 均来自响应或当前上下文,写后重读确认。第三批已完成意见反馈和工单展示:四页共用 PC 反馈集合、提交后三字段白名单重读、详情精确匹配和隐私过滤。第四批已完成 VIP 套餐、我的家谱选项和会员订单:页面不手填业务 ID,不伪造支付能力,创建后重读同一订单确认。第五批已完成家谱主页只读概览:复用 PC overview 与世系树,权限控制管理入口,不制造聚合统计或邀请能力。第六批已完成帮助中心:登录后读取 PC 帮助文章列表,列表响应产生详情 ID,详情精确重读并安全展示正文;Apifox 的匿名标记与后端实际鉴权冲突已记录。第七批已完成应用下载页的 PC 推广列表:未登录不发请求,登录后固定发送 SQL 字典确认的 `platform=pc`,只读取服务端返回的 `pc + all` 正常记录,外链仅允许绝对 HTTP/HTTPS;推广 ID、后台键和 OSS ID 均不向用户开放。第八批已完成官网资讯列表与详情:公开请求不发送或清理 token,分类使用 SQL 字典 `news/notice/download`,详情 ID 只来自列表响应并通过重读列表精确匹配;YAML 通用响应缺失完整 `SiteArticleVo`,且当前部署环境匿名访问仍返回认证失败,两项差异均已记录。成员邀请、分享和资料完善提醒没有 PC Controller,继续阻断。 - -## 9. 文件级施工建议 - -执行 AI 应优先修改现有 owner,避免页面各自复制逻辑: - -| 文件 | 职责 | -| --- | --- | -| `PC.openapi.json` | 当次 Apifox 导出快照,不手工补字段 | -| `config.js` | 环境、API 基础地址、非敏感客户端配置 | -| `utils/ApiClient.js` | 所有正式 PC method/path/query/body | -| `utils/AxiosRequestUtil.js` | header、鉴权、响应解包、401/403 | -| `utils/FormUtil.js` | 表单取值、空可选字段省略、前端校验 | -| `public/js/profile-common.js` | 登录页外的家谱上下文 | -| `public/js/upload-pages.js` | 唯一上传流程 | -| `public/js/auth-pages.js` / `security-pages.js` | 认证业务流程 | -| `public/js/feed-pages.js` | 家族圈 | -| `public/js/generation-pages.js` | 字辈 | -| `public/js/lineage-pages.js` | 世系与指定账号绑定 | -| `public/js/video-pages.js` | 视频列表、详情、编辑、上传元数据和 CRUD | -| `public/js/growth-pages.js`、`relative-pages.js`、`memo-pages.js` | 对应族务记录 | -| `public/js/ceremony-pages.js` | 当前用户贺礼邀请列表、响应内详情和接受/拒绝 | -| `public/js/article-pages.js`、`album-pages.js` | 谱文、相册和照片 | -| `public/js/ceremony-admin-pages.js` | 祭祀活动、祭品与管理员邀约 | -| `public/js/member-admin-pages.js` | 成员修改、移出、退出和谱主转移 | - -不得创建一个包揽所有业务的巨型页面脚本,也不得为了单一页面引入通用框架。 - -## 10. 测试与验收 - -### 10.1 必须新增或更新的测试 - -1. OpenAPI 契约快照: - - operation 数; - - method/path; - - body Schema; - - required; - - enum; - - response `$ref`; - - 公开/鉴权标记。 -2. `ApiClient` 契约测试: - - URL、query、body、header; - - int64 ID 不转 Number; - - 可选空字段被省略; - - 旧路径/旧字段不存在。 -3. 页面字段覆盖测试: - - 每个请求 Schema 字段都能在本规划的 U/S/F/A/I/V 分类中找到; - - 用户可编辑字段有对应 `name`; - - 自动字段没有可见文本输入; - - 枚举选项和值与 Apifox 一致。 -4. 流程测试: - - 验证与短信; - - 上传; - - 家谱上下文; - - 列表 → 详情 → 编辑 → 重读; - - 删除/停用; - - 401/403/404/409/422/429。 -5. 安全测试: - - 富文本经过净化; - - 密码、token、validToken 不写日志; - - 外链和上传文件类型受限; - - 错误消息不直接插入 HTML。 - -### 10.2 建议验证命令 - -先跑最小相关测试,再跑全量: - -```powershell -node --test tests/api-client-contract.test.js -node --test tests/auth-pages.test.js tests/captcha-pages-contract.test.js -node --test tests/feed-pages.test.js tests/generation-pages.test.js tests/lineage-pages.test.js -node --test tests/growth-pages.test.js tests/relative-pages.test.js tests/memo-pages.test.js tests/notification-pages.test.js -node --test tests/*.test.js -``` - -执行 AI 若新增测试文件,应在本节补上精确命令。 - -### 10.3 模块完成定义 - -一个模块只有同时满足以下条件才可标记完成: - -1. Apifox 契约无未决冲突。 -2. 所有请求字段已分类并落到页面。 -3. 列表和详情响应有明确 DTO。 -4. `ApiClient` 不存在旧路径或 fallback。 -5. 页面有加载、空、成功、校验失败、无权限和网络失败状态。 -6. 写操作防重复提交。 -7. 成功写入后通过重新读取验证。 -8. 相关测试和全量测试通过。 -9. 没有原始 JSON、手填 ID、示例业务数据或 APP 接口冒充真实能力。 - -## 11. 执行完成后的报告格式 - -执行 AI 每个阶段只报告: - -```text -Changed: 修改了哪些 owner、页面和契约。 -Verified: 运行了哪些测试,真实联调覆盖了哪些流程。 -Blocked: 哪些字段或流程仍缺 Apifox/后端契约,具体缺什么。 -Next: 下一阶段可以开始的最小范围。 -``` - -不得用“应该可以”“大概完成”代替测试证据。 diff --git a/docs/superpowers/plans/2026-07-11-content-page-state-closure.md b/docs/superpowers/plans/2026-07-11-content-page-state-closure.md deleted file mode 100644 index 6ae23a6..0000000 --- a/docs/superpowers/plans/2026-07-11-content-page-state-closure.md +++ /dev/null @@ -1,129 +0,0 @@ -# 内容与协作页面状态收口 Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** 让缺少正式接口的内容、协作、消息与提醒页面明确展示开发状态,同时保留家族圈动态的真实功能。 - -**Architecture:** 复用 `public/js/pending-pages.js` 的页面状态契约。每个待开发页面仅增加 `data-feature-status`、中文提示文案和状态脚本引用;脚本阻止主内容区未声明可用的业务链接,不新增接口调用、示例数据或业务脚本。 - -**Tech Stack:** 原生 JavaScript、静态 HTML/CSS、Node LTS `node:test`。 - -## Global Constraints - -- 保留现有页面、布局、视觉类名和导航结构,不删除用户设计。 -- `PC.openapi2.json` 中缺少正式接口的功能不得伪造请求或静态数据为真实业务。 -- 家族圈动态 `profile-feed.html` 与 `profile-feed-edit.html` 已接入正式接口,不能标记为待开发。 -- 所有新增注释使用中文;不写入测试账号或密码。 -- 当前工作区含用户未提交修改,不执行提交、重置或清理操作。 - ---- - -### Task 1: 扩展页面状态测试 - -**Files:** -- Modify: `tests/pending-pages.test.js` - -**Interfaces:** -- Consumes: 每页的 `data-feature-status="pending"` 与 `public/js/pending-pages.js`。 -- Produces: 内容与协作待开发页面的静态约束,以及家族圈动态未被误标记的约束。 - -- [x] **Step 1: 写入失败测试** - -在 `contentPendingPages` 中列出以下页面: - -```js -const contentPendingPages = [ - 'profile-content.html', 'profile-article.html', 'profile-article-edit.html', - 'profile-album.html', 'profile-video.html', 'profile-gift.html', - 'profile-gift-edit.html', 'profile-growth.html', 'profile-growth-edit.html', - 'profile-memo.html', 'profile-memo-edit.html', 'profile-merit.html', - 'profile-merit-edit.html', 'profile-messages.html', 'profile-feedback.html', - 'profile-admin-permissions.html', 'profile-data-reminders.html' -]; -``` - -同时断言 `profile-feed.html`、`profile-feed-edit.html` 不含待开发状态标记,并验证只有 `data-feature-link="available"` 的业务链接可跳转。 - -- [x] **Step 2: 运行失败测试** - -Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; node --test tests/pending-pages.test.js` - -Expected: 新增页面尚未标记,测试失败。 - -### Task 2: 标记内容与协作页面 - -**Files:** -- Modify: `profile-content.html` -- Modify: `profile-article.html` -- Modify: `profile-article-edit.html` -- Modify: `profile-album.html` -- Modify: `profile-video.html` -- Modify: `profile-gift.html` -- Modify: `profile-gift-edit.html` -- Modify: `profile-growth.html` -- Modify: `profile-growth-edit.html` -- Modify: `profile-memo.html` -- Modify: `profile-memo-edit.html` -- Modify: `profile-merit.html` -- Modify: `profile-merit-edit.html` -- Modify: `profile-messages.html` -- Modify: `profile-feedback.html` -- Modify: `profile-admin-permissions.html` -- Modify: `profile-data-reminders.html` -- Test: `tests/pending-pages.test.js` - -**Interfaces:** -- Consumes: `PageAvailability.init()` 读取的 ``。 -- Produces: 可见的“功能开发中”提示,并拦截主内容区提交与操作按钮。 - -- [x] **Step 1: 为每页增加状态契约** - -在目标页面的 `body` 增加 `data-feature-status="pending"` 和与业务相符的中文 `data-feature-message`。例如谱文页面使用“谱文服务正在开发中,当前页面仅保留设计预览。” - -- [x] **Step 2: 加载统一状态脚本** - -有 `ApiClient.js` 的页面在其后加载: - -```html - - -``` - -`profile-video.html` 与 `profile-data-reminders.html` 不依赖 `ApiClient.js`,在 `page-effects.js` 前加载状态脚本。 - -- [x] **Step 3: 收束业务入口跳转** - -待开发页面主内容区的业务链接由状态脚本拦截并显示当前页面提示;侧栏保留页面浏览导航。`profile-content.html` 的家族圈动态入口使用 `data-feature-link="available"`,继续跳转到 `profile-feed.html`。 - -- [x] **Step 4: 运行测试确认通过** - -Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; node --test tests/pending-pages.test.js` - -Expected: 待开发页面均加载状态脚本,家族圈动态页面未被标记。 - -### Task 3: 更新进度并做静态验证 - -**Files:** -- Modify: `docs/规划.md` -- Modify: `docs/superpowers/plans/2026-07-11-content-page-state-closure.md` -- Test: `tests/*.test.js` - -**Interfaces:** -- Consumes: 全部页面的本地脚本引用。 -- Produces: 已完成第二轮进度与可重复的验证结果。 - -- [x] **Step 1: 更新总规划的第二轮状态** - -将 `docs/规划.md` 中“第二轮:内容与协作页面状态收口”从未完成更新为已完成,并写明家族圈动态未受影响。 - -- [x] **Step 2: 执行完整测试** - -Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test` - -Expected: 所有 Node 测试通过。 - -- [x] **Step 3: 执行静态检查** - -Run: PowerShell 扫描全部 HTML 的 `src="*.js"`,随后运行 `git diff --check`。 - -Expected: 每个本地脚本文件存在,且没有空白格式错误。 diff --git a/docs/superpowers/plans/2026-07-11-family-feed-integration.md b/docs/superpowers/plans/2026-07-11-family-feed-integration.md deleted file mode 100644 index 1be5559..0000000 --- a/docs/superpowers/plans/2026-07-11-family-feed-integration.md +++ /dev/null @@ -1,192 +0,0 @@ -# 家族圈动态接口接入 Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** 将 `PC.openapi2.json` 中已交付的 12 个家族圈动态接口接入既有动态页面,并保持未开发的家谱业务不调用不存在的接口。 - -**Architecture:** `PC.openapi2.json` 是接口唯一依据;`ApiClient` 提供一一对应的私有请求封装,`feed-pages.js` 只协调页面事件和已定义的接口方法。动态页从 URL 查询参数读取真实 `genealogyId`,不写入示例家谱 ID。 - -**Tech Stack:** 原生 JavaScript、Axios、Node LTS `node:test`、静态 HTML/CSS。 - -## Global Constraints - -- 所有新增或修改的注释使用清晰中文。 -- 不删除现有页面和样式;不接入尚未出现在 `PC.openapi2.json` 的业务。 -- 不提交当前工作区:其中含用户已有的未提交修改。 -- 动态读取响应目前只有通用 `ListResult`/`PageResult`,页面仅使用 `feedId` 与 `feedContent`;后端返回缺少这两个字段时展示明确错误,不猜测字段名。 - ---- - -### Task 1: 更新接口边界测试和动态请求测试 - -**Files:** -- Modify: `tests/api-client-contract.test.js` -- Modify: `tests/pc-scope.test.js` -- Create: `tests/feed-pages.test.js` - -**Interfaces:** -- Consumes: `PC.openapi2.json` 的 12 个 `/genealogy/pc/genealogies/{genealogyId}/feeds` 操作。 -- Produces: 对客户端路径、HTTP 方法、请求体和页面请求体转换的自动化约束。 - -- [ ] **Step 1: 写入失败测试** - -```js -test('家族圈动态方法使用文档定义的路径和请求体', async () => { - const calls = []; - const client = createClientWithRecorder(calls); - await client.createFeed(900001001, { feedContent: '今天上传一张老照片。' }); - await client.feedCommentsPage(900001001, 7001, { pageNum: 1, pageSize: 10 }); - assert.deepEqual(calls.map((call) => [call.method, call.url]), [ - ['post', '/genealogy/pc/genealogies/900001001/feeds'], - ['get', '/genealogy/pc/genealogies/900001001/feeds/7001/comments/page'] - ]); -}); - -test('动态表单只构造 FamilyFeedBody 字段', () => { - assert.deepEqual(FeedPages.buildFeedBody({ - feedContent: '家谱动态', mediaOssIds: '101,102', status: '0' - }), { - feedType: 'text', feedContent: '家谱动态', mediaOssIds: '101,102', status: '0' - }); -}); -``` - -- [ ] **Step 2: 运行失败测试** - -Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test` - -Expected: 动态方法或 `feed-pages.js` 尚不存在导致失败。 - -- [ ] **Step 3: 调整范围检查** - -```js -assert.match(contract, /\/genealogy\/pc\/genealogies\/\{genealogyId\}\/feeds/); -assert.equal(read('profile-feed.html').includes('src="public/js/feed-pages.js"'), true); -assert.equal(read('profile-feed-edit.html').includes('src="public/js/feed-pages.js"'), true); -``` - -- [ ] **Step 4: 重新运行测试,确认仍处于预期失败状态** - -Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test` - -Expected: 仅动态实现相关断言失败。 - -### Task 2: 增加 12 个家族圈 API 方法 - -**Files:** -- Modify: `utils/ApiClient.js` -- Modify: `tests/api-client-contract.test.js` - -**Interfaces:** -- Consumes: `genealogyId:number|string`、`feedId:number|string`、`commentId:number|string`。 -- Produces: `feeds`、`feedsPage`、`feedDetail`、`createFeed`、`updateFeed`、`deleteFeed`、`likeFeed`、`unlikeFeed`、`feedComments`、`feedCommentsPage`、`createFeedComment`、`deleteFeedComment`。 - -- [ ] **Step 1: 保持失败测试不变** - -```js -await client.createFeed(900001001, { feedContent: '家谱动态' }); -assert.equal(calls[0].url, '/genealogy/pc/genealogies/900001001/feeds'); -assert.deepEqual(calls[0].data, { feedContent: '家谱动态' }); -``` - -- [ ] **Step 2: 实现最小路径构造** - -```js -function feedPath(genealogyId, suffix) { - return '/genealogy/pc/genealogies/' + encodeURIComponent(genealogyId) + '/feeds' + suffix; -} - -createFeed: function (genealogyId, body) { - return request('POST', feedPath(genealogyId, ''), { body: body }); -} -``` - -- [ ] **Step 3: 运行测试,确认通过** - -Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test` - -Expected: 动态客户端路径和请求体断言通过。 - -### Task 3: 恢复动态页的真实交互 - -**Files:** -- Create: `public/js/feed-pages.js` -- Modify: `profile-feed.html` -- Modify: `profile-feed-edit.html` -- Modify: `tests/feed-pages.test.js` - -**Interfaces:** -- Consumes: `GenealogyApi.defaultClient` 的 12 个动态方法和 URL 中的 `genealogyId`、可选 `feedId`。 -- Produces: 动态列表、发布/编辑、点赞/取消点赞、评论查看/发布/删除、动态删除事件。 - -- [ ] **Step 1: 使页面测试失败** - -```js -assert.equal(FeedPages.getCurrentGenealogyId('?genealogyId=900001001'), '900001001'); -assert.equal(FeedPages.getCurrentGenealogyId(''), ''); -assert.equal(FeedPages.getFeedId({ feedId: 7001 }), '7001'); -``` - -- [ ] **Step 2: 实现页面数据转换和事件** - -```js -function buildFeedBody(values) { - return { - feedType: 'text', - feedContent: String(values.feedContent || '').trim(), - mediaOssIds: trimOrUndefined(values.mediaOssIds), - status: trimOrUndefined(values.status) - }; -} -``` - -列表只读取 `feedId`、`feedContent`,并在缺少其中任一字段时显示“动态响应缺少 feedId 或 feedContent,请联系后端补充 DTO”。编辑页以 `feedId` 查询详情并调用 `updateFeed`;列表操作调用点赞、取消点赞、评论、删除接口后重新加载当前页。 - -- [ ] **Step 3: 挂载脚本和契约字段** - -```html - - -``` - -移除未在 `FamilyFeedBody` 中定义的“可见范围”提交字段;保持页面布局与既有视觉类名。 - -- [ ] **Step 4: 运行测试,确认通过** - -Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test` - -Expected: `feed-pages.test.js` 和已有测试全部通过。 - -### Task 4: 更新文档来源与最终验证 - -**Files:** -- Modify: `tests/pc-scope.test.js` -- Modify: `PC.openapi.yaml` - -**Interfaces:** -- Consumes: `PC.openapi2.json` 的接口路径。 -- Produces: 明确标记旧 YAML 不再为接口源,并使静态检查以 JSON 为准。 - -- [ ] **Step 1: 写入失败检查** - -```js -const contract = JSON.parse(read('PC.openapi2.json')); -assert.ok(contract.paths['/genealogy/pc/genealogies/{genealogyId}/feeds']); -``` - -- [ ] **Step 2: 标记旧 YAML 失效** - -在 `PC.openapi.yaml` 文件首行写入中文说明:该文件不再作为代码或测试接口源,正式接口源为 `PC.openapi2.json`。不复制或手改 JSON 中未定义的动态响应 DTO。 - -- [ ] **Step 3: 全量验证** - -Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test` - -Expected: 全部测试通过,且所有动态页面加载的脚本文件都存在。 - -## Self-review - -- 范围只覆盖最新文档已交付的家族圈动态接口,没有加入家谱创建、成员或世系树接口。 -- 所有请求体字段来自 `FamilyFeedBody` 或 `FamilyFeedCommentBody`。 -- 读取响应不使用历史字段兜底,不把未知字段猜成有效数据。 -- 任何 URL 缺少 `genealogyId` 的页面都会明确提示,不使用硬编码示例 ID。 diff --git a/docs/superpowers/plans/2026-07-11-family-page-state-closure.md b/docs/superpowers/plans/2026-07-11-family-page-state-closure.md deleted file mode 100644 index 01f4da7..0000000 --- a/docs/superpowers/plans/2026-07-11-family-page-state-closure.md +++ /dev/null @@ -1,140 +0,0 @@ -# 家谱基础页面状态收口 Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** 让尚未获得后端家谱接口的基础页面明确展示开发状态,并阻止表单和操作按钮伪造成功结果。 - -**Architecture:** 仅在家谱列表、创建/加入、审核、成员管理、邀请、世系和字辈页面加载一个轻量状态脚本。页面通过 `data-feature-status` 和 `data-feature-message` 提供文案;脚本统一插入提示条,并拦截页面主内容区的提交与按钮操作。 - -**Tech Stack:** 原生 JavaScript、静态 HTML/CSS、Node LTS `node:test`。 - -## Global Constraints - -- 保留现有页面、布局、视觉类名和导航结构,不删除用户设计。 -- 不调用 `PC.openapi2.json` 中不存在的家谱、成员、世系或字辈接口。 -- 所有新增注释使用中文;不写入测试账号或密码。 -- 当前工作区含用户未提交修改,不执行提交、重置或清理操作。 - ---- - -### Task 1: 建立失败测试 - -**Files:** -- Create: `tests/pending-pages.test.js` -- Modify: `tests/pc-scope.test.js` - -**Interfaces:** -- Consumes: `PageAvailability.buildBanner(message)` 与 `PageAvailability.isPendingPage(body)`。 -- Produces: 对状态脚本、页面标记和脚本加载的约束。 - -- [x] **Step 1: 写入失败测试** - -```js -test('待开发页面生成明确且转义后的状态提示', () => { - assert.match(PageAvailability.buildBanner('家谱基础服务正在开发中'), /家谱基础服务正在开发中/); - assert.match(PageAvailability.buildBanner(' - - -``` - -- [x] **Step 3: 运行完整测试** - -Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test` - -Expected: 全部测试通过。 - -### Task 4: 静态验证 - -**Files:** -- Test: `tests/pc-scope.test.js` - -**Interfaces:** -- Consumes: 8 个已标记页面。 -- Produces: 所有待开发页面有状态文案,所有页面脚本路径存在。 - -- [x] **Step 1: 执行脚本引用检查** - -Run: PowerShell 扫描全部 HTML 的 `src="*.js"`,确认每个本地脚本文件存在。 - -- [x] **Step 2: 执行格式检查** - -Run: `git diff --check` - -Expected: 无缺失脚本和空白格式错误。 diff --git a/docs/superpowers/plans/2026-07-11-public-static-pages.md b/docs/superpowers/plans/2026-07-11-public-static-pages.md deleted file mode 100644 index 4bf2f5e..0000000 --- a/docs/superpowers/plans/2026-07-11-public-static-pages.md +++ /dev/null @@ -1,139 +0,0 @@ -# 官网静态页面补齐 Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** 补齐官网新闻、法律文档入口、通用状态与工单页面,使无接口的线上服务不再伪装为可用功能。 - -**Architecture:** 新增静态 HTML 页面共用 `public/css/site-info.css` 和现有官网头尾布局。新闻页只链接现有静态文章与公告详情;法律页面明确标明待法务确认;工单页面复用 `pending-pages.js` 阻止无接口提交。 - -**Tech Stack:** 静态 HTML、原生 CSS、原生 JavaScript、Node LTS `node:test`。 - -## Global Constraints - -- 保留现有页面、布局、视觉类名和导航结构,不删除用户设计。 -- 不新增 `PC.openapi2.json` 外的接口调用,不伪造新闻接口、法律协议、工单列表或工单详情数据。 -- 法律页面只能显示“待法务确认”说明,发布前必须替换为主体与法务确认的正式文本。 -- 所有新增注释使用中文;不写入测试账号或密码。 -- 当前工作区含用户未提交修改,不执行提交、重置或清理操作。 - ---- - -### Task 1: 建立静态页面约束测试 - -**Files:** -- Create: `tests/public-static-pages.test.js` - -**Interfaces:** -- Consumes: 静态页面文件、`data-feature-status="pending"` 和本地 `href`。 -- Produces: 页面存在、关键说明、状态脚本与本地链接的可重复校验。 - -- [x] **Step 1: 写入失败测试** - -测试必须断言存在以下文件: - -```js -const staticPages = [ - 'news.html', 'privacy.html', 'children-privacy.html', 'terms.html', - 'not-found.html', 'forbidden.html', 'maintenance.html', - 'my-tickets.html', 'ticket-detail.html' -]; -``` - -同时断言:`news.html` 含有新闻分类锚点并链接 `article-detail.html`、`notice-detail.html`;三个法律页面含“待法务确认”;`submit-ticket.html`、`my-tickets.html`、`ticket-detail.html` 有待开发状态和 `pending-pages.js`;新页面中的本地 `href` 全部存在。 - -- [x] **Step 2: 运行失败测试** - -Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; node --test tests/public-static-pages.test.js` - -Expected: 新页面不存在导致测试失败。 - -### Task 2: 建立通用静态页面外观与新闻、法律入口 - -**Files:** -- Create: `public/css/site-info.css` -- Create: `news.html` -- Create: `privacy.html` -- Create: `children-privacy.html` -- Create: `terms.html` -- Modify: `index.html` - -**Interfaces:** -- Consumes: 现有 `public/css/public.css` 的 `site-header`、`footer`、`container`、`btn` 与 `card` 样式。 -- Produces: 新闻分类和详情跳转,以及能从首页底部访问的法律入口。 - -- [x] **Step 1: 创建复用样式** - -`site-info.css` 提供 `.site-info-hero`、`.site-info-layout`、`.site-info-card`、`.site-info-status`、`.site-info-links`,仅处理本轮新增页面的排版和窄屏适配。 - -- [x] **Step 2: 创建新闻页面** - -`news.html` 必须有“平台公告”“家谱文化”分类锚点,并将两条静态内容分别链接到 `notice-detail.html` 与 `article-detail.html`。 - -- [x] **Step 3: 创建法律页面并连接首页** - -三个法律页面均写明“待法务确认”,并互相提供跳转链接。首页 `footer-legal` 增加三个页面链接。 - -### Task 3: 建立状态与工单边界 - -**Files:** -- Create: `not-found.html` -- Create: `forbidden.html` -- Create: `maintenance.html` -- Create: `my-tickets.html` -- Create: `ticket-detail.html` -- Modify: `submit-ticket.html` - -**Interfaces:** -- Consumes: `PageAvailability.init()` 读取的待开发页面属性。 -- Produces: 三种无接口提交的工单状态,和可返回首页、帮助中心的静态状态页。 - -- [x] **Step 1: 创建三个通用状态页** - -三个页面分别显示“页面不存在”“无访问权限”“系统维护中”,并提供 `index.html` 返回入口。 - -- [x] **Step 2: 创建工单列表与详情状态页** - -`my-tickets.html`、`ticket-detail.html` 均使用: - -```html - -``` - -并在 `public/js/pending-pages.js` 后仅放行帮助中心链接。 - -- [x] **Step 3: 收束提交工单表单** - -`submit-ticket.html` 增加: - -```html - -``` - -在 `ApiClient.js` 后加载状态脚本,并为“返回帮助中心”链接添加 `data-feature-link="available"`。 - -### Task 4: 回写进度与验证 - -**Files:** -- Modify: `docs/规划.md` -- Modify: `docs/superpowers/plans/2026-07-11-public-static-pages.md` -- Test: `tests/*.test.js` - -**Interfaces:** -- Consumes: 新增静态页面和全部 HTML 本地脚本引用。 -- Produces: 第四轮进度记录和验证结果。 - -- [x] **Step 1: 写入第四轮完成状态** - -在 `docs/规划.md` 的“实施进度”中增加静态官网页面补齐已完成,并注明法律文本需法务替换。 - -- [x] **Step 2: 执行完整测试** - -Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test` - -Expected: 所有 Node 测试通过。 - -- [x] **Step 3: 执行静态检查** - -Run: PowerShell 扫描全部 HTML 的 `src="*.js"`,随后运行 `git diff --check`。 - -Expected: 每个本地脚本文件存在,且没有空白格式错误。 diff --git a/docs/superpowers/plans/2026-07-11-residual-pc-entry-closure.md b/docs/superpowers/plans/2026-07-11-residual-pc-entry-closure.md deleted file mode 100644 index 3d493a4..0000000 --- a/docs/superpowers/plans/2026-07-11-residual-pc-entry-closure.md +++ /dev/null @@ -1,112 +0,0 @@ -# 剩余用户 PC 业务入口收口 Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** 让剩余未有正式接口支持的用户 PC 业务入口显示明确状态,避免跳转到看似可用的分享、会员或家谱管理流程。 - -**Architecture:** 复用 `public/js/pending-pages.js` 的状态契约和链接收束规则。仅标记 `profile-family-home.html`、`profile-share.html`、`profile-services.html`;账号主页、个人资料、安全设置和家族圈动态继续使用现有真实功能。 - -**Tech Stack:** 原生 JavaScript、静态 HTML/CSS、Node LTS `node:test`。 - -## Global Constraints - -- 保留现有页面、布局、视觉类名和导航结构,不删除用户设计。 -- `PC.openapi2.json` 中不存在家谱主页、邀请奖励和会员订单的正式接口,不得伪造请求或静态数据为真实业务。 -- `profile-family-home.html` 的家族圈动态入口继续跳转到 `profile-feed.html`。 -- 所有新增注释使用中文;不写入测试账号或密码。 -- 当前工作区含用户未提交修改,不执行提交、重置或清理操作。 - ---- - -### Task 1: 扩展入口状态测试 - -**Files:** -- Modify: `tests/pending-pages.test.js` - -**Interfaces:** -- Consumes: 目标页面的 `data-feature-status="pending"`、状态脚本引用与 `data-feature-link="available"`。 -- Produces: 剩余业务入口的静态约束,及真实账户页面不被误标记的约束。 - -- [x] **Step 1: 写入失败测试** - -新增以下数组并加入现有待开发页面检查: - -```js -const residualPendingPages = [ - 'profile-family-home.html', - 'profile-share.html', - 'profile-services.html' -]; -``` - -新增断言:`profile-family-home.html` 的 `profile-feed.html` 链接必须带有 `data-feature-link="available"`;`profile.html`、`profile-data.html`、`profile-security.html` 不得含待开发状态标记。 - -- [x] **Step 2: 运行失败测试** - -Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; node --test tests/pending-pages.test.js` - -Expected: 三个剩余入口尚未标记,测试失败。 - -### Task 2: 标记剩余业务入口 - -**Files:** -- Modify: `profile-family-home.html` -- Modify: `profile-share.html` -- Modify: `profile-services.html` -- Test: `tests/pending-pages.test.js` - -**Interfaces:** -- Consumes: `PageAvailability.init()` 读取的页面状态属性,和 `PageAvailability.shouldBlockLink(link)` 的可用入口约定。 -- Produces: 状态提示、业务链接拦截,以及唯一保留的动态入口。 - -- [x] **Step 1: 标记页面状态** - -分别在三个 `body` 中增加 `data-feature-status="pending"` 和对应中文提示:家谱主页使用“家谱概览与管理服务正在开发中,当前页面仅保留设计预览。”;应用分享使用“应用分享与邀请奖励服务正在开发中,当前页面仅保留设计预览。”;帮助与服务使用“会员与在线服务正在开发中,当前页面仅保留设计预览。” - -- [x] **Step 2: 加载统一状态脚本并放行动态入口** - -每页都在 `utils/ApiClient.js` 后增加: - -```html - - -``` - -在 `profile-family-home.html` 中将动态链接改为: - -```html -发布动态 -``` - -- [x] **Step 3: 运行测试确认通过** - -Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; node --test tests/pending-pages.test.js` - -Expected: 全部状态页面和真实账户页面断言通过。 - -### Task 3: 更新进度与验证 - -**Files:** -- Modify: `docs/规划.md` -- Modify: `docs/superpowers/plans/2026-07-11-residual-pc-entry-closure.md` -- Test: `tests/*.test.js` - -**Interfaces:** -- Consumes: 全部 HTML 的本地脚本引用。 -- Produces: 已完成第三轮进度和可重复验证记录。 - -- [x] **Step 1: 写入第三轮完成状态** - -在 `docs/规划.md` 的“实施进度”中增加已完成第三轮,说明三类剩余 PC 业务入口已收口,家族圈动态未受影响。 - -- [x] **Step 2: 执行完整测试** - -Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test` - -Expected: 所有 Node 测试通过。 - -- [x] **Step 3: 执行静态检查** - -Run: PowerShell 扫描全部 HTML 的 `src="*.js"`,随后运行 `git diff --check`。 - -Expected: 每个本地脚本文件存在,且没有空白格式错误。 diff --git a/docs/superpowers/plans/2026-07-29-app-promotions.md b/docs/superpowers/plans/2026-07-29-app-promotions.md deleted file mode 100644 index 9b66705..0000000 --- a/docs/superpowers/plans/2026-07-29-app-promotions.md +++ /dev/null @@ -1,391 +0,0 @@ -# 应用下载页 PC 推广列表 Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** 在 `app.html` 现有推广区域读取并安全展示后端 PC 推广列表,同时保持应用下载页公开可访问。 - -**Architecture:** `utils/ApiClient.js` 独占 method/path/query 契约;新建 `public/js/app-promotion-pages.js` 独占 `AppPromotionVo` 规范化、链接安全、区域状态和渲染。页面只提供容器与脚本加载,不复制接口逻辑。 - -**Tech Stack:** 原生 JavaScript UMD、HTML/CSS、Node.js `node:test`、现有 `GenealogyApi` / `AxiosRequestUtil`。 - -## Global Constraints - -- 只调用 `GET /genealogy/pc/promotions`,不得调用 APP 或后台管理接口。 -- 后端控制器实际要求 PC 登录态;请求携带当前 token 和基础 `clientid`。 -- 页面初始化不发送 `platform`;客户端即使被其它调用方使用,也只允许可选 Query `platform`。 -- 页面不展示或拼接 `coverOssId`,不允许用户输入业务 ID 或 OSS ID。 -- `targetUrl` 只允许绝对 HTTP(S) URL;其它值按无链接卡片展示。 -- 页面本身保持公开;未登录和 401 只改变推广区域,不跳走整个下载页。 -- 旧 `promotion-pages.js` 不恢复,不保留兼容入口。 -- 后端项目 `D:\WorkSpace\Java\Genealogy` 只读。 -- 当前共享 `main` 工作区包含多批未提交改动;本计划不执行 Git stage、commit、merge 或 push。 - ---- - -### Task 1: 冻结 PC 推广客户端契约 - -**Files:** -- Modify: `tests/api-client-contract.test.js` -- Modify: `utils/ApiClient.js` - -**Interfaces:** -- Consumes: 私有 `request(method, path, options)` 与 `pickDefined(source, allowedFields)`。 -- Produces: `client.promotions(query?: { platform?: string }): Promise>`。 - -- [ ] **Step 1: 在客户端允许方法集合中加入 `promotions`,并写失败测试** - -测试使用带 `access-token` 的真实客户端边界,传入: - -```js -await client.promotions({ - platform: 'pc', - keyword: 'must-drop' -}); -``` - -手工断言请求为: - -```js -[ - 'get', - '/genealogy/pc/promotions', - { platform: 'pc' }, - 'Bearer access-token' -] -``` - -该测试捕获错误 path、错误 method、契约外 Query 被透传或误设 `auth: false`。 - -- [ ] **Step 2: 运行客户端测试观察 RED** - -Run: - -```powershell -node --test tests/api-client-contract.test.js -``` - -Expected: FAIL,提示 `promotions` 未导出或不是函数。 - -- [ ] **Step 3: 添加最小客户端实现** - -在 `helpArticleDetail` 和 VIP 方法附近添加: - -```js -promotions: function (query) { - return request('GET', '/genealogy/pc/promotions', { - query: pickDefined(query, ['platform']) - }); -}, -``` - -不得增加 `auth: false`、默认 `platform` 或旧路径 fallback。 - -- [ ] **Step 4: 运行客户端测试观察 GREEN** - -Run: - -```powershell -node --test tests/api-client-contract.test.js -``` - -Expected: 全部通过。 - ---- - -### Task 2: 实现推广响应和安全渲染边界 - -**Files:** -- Create: `tests/app-promotion-pages.test.js` -- Create: `public/js/app-promotion-pages.js` - -**Interfaces:** -- Consumes: `GenealogyApi.defaultClient.promotions()`、`getToken()`、`clearToken()`。 -- Produces: - - `normalizeTargetUrl(value): string` - - `normalizePromotion(item): PromotionView | null` - - `normalizePromotionList(data): PromotionView[]` - - `renderPromotionList(data): string` - - `renderPromotionLoginRequired(): string` - - `loadPromotions(api): Promise` - - `isUnauthorized(error): boolean` - - `init(): Promise` - -`PromotionView` 的唯一形状: - -```js -{ - promotionId: '2062179707935264769', - promotionTitle: '下载移动端', - promotionDesc: '随时查看家谱内容', - targetUrl: 'https://example.com/download' -} -``` - -- [ ] **Step 1: 写完整真实 DTO fixture 和失败测试** - -Fixture 必须包含后端全部 10 个字段: - -```js -{ - promotionId: '2062179707935264769', - promotionKey: 'app-download', - promotionTitle: '下载移动端', - promotionDesc: '随时查看家谱内容', - coverOssId: '2062179707935264701', - targetUrl: 'https://example.com/download', - platform: 'all', - sortOrder: 10, - status: '0', - remark: 'internal-only' -} -``` - -分别覆盖: - -1. 规范化结果只保留 `PromotionView` 四字段; -2. 不安全数字长 ID、空标题和 `status=1` 返回 `null`; -3. 直接数组正常,`{ rows: [...] }` 和混入非法元素返回空数组; -4. `https://`、`http://` 保留,`javascript:`、`data:`、相对路径和空值返回空字符串; -5. 渲染转义标题与说明,不出现 `coverOssId`、`promotionKey`、`sortOrder`、`status`、`remark`; -6. 合法 URL 使用 `target="_blank"` 和 `rel="noopener noreferrer"`,非法 URL 不生成 ``; -7. `loadPromotions(api)` 只调用一次 `api.promotions()`,不传 `platform`; -8. `isUnauthorized` 仅把 HTTP/业务 401 视为登录失效,403 为普通错误。 - -- [ ] **Step 2: 运行模块测试观察 RED** - -Run: - -```powershell -node --test tests/app-promotion-pages.test.js -``` - -Expected: FAIL,提示 `public/js/app-promotion-pages.js` 不存在。 - -- [ ] **Step 3: 实现 UMD 模块的纯函数** - -实现稳定 ID: - -```js -function normalizeId(value) { - if (typeof value === 'number' && !Number.isSafeInteger(value)) return ''; - var result = String(value == null ? '' : value).trim(); - return /^[1-9][0-9]*$/.test(result) ? result : ''; -} -``` - -实现安全 URL: - -```js -function normalizeTargetUrl(value) { - var text = String(value == null ? '' : value).trim(); - var parsed; - if (!text) return ''; - try { - parsed = new URL(text); - } catch (error) { - return ''; - } - return parsed.protocol === 'http:' || parsed.protocol === 'https:' ? parsed.href : ''; -} -``` - -`normalizePromotion` 必须要求稳定 `promotionId`、非空 `promotionTitle` 和 `status === '0'`,然后只返回 `PromotionView`。`normalizePromotionList` 必须拒绝非直接数组以及包含任一非法元素的数组。 - -- [ ] **Step 4: 实现渲染和读取函数** - -有链接时结构: - -```html - -

    ...

    ...

    了解详情
    -
    -``` - -无链接时使用 `
    `,不输出 `href`、`target` 或 `rel`。空列表固定返回: - -```html -
    当前暂无应用推广
    -``` - -未登录固定返回: - -```html -
    登录后可查看应用推广。去登录
    -``` - -`loadPromotions(api)` 必须执行 `await api.promotions()`,并通过“原数组长度等于规范化后数组长度”确认响应完整。 - -- [ ] **Step 5: 实现区域级初始化** - -`init()` 只在 `[data-promotion-page]` 和 `[data-promotion-list]` 同时存在时运行: - -```js -if (!api || !api.getToken || !api.getToken()) { - list.innerHTML = renderPromotionLoginRequired(); - return; -} -``` - -有 token 时加载一次。401 调用 `api.clearToken()` 并渲染登录入口;403、网络失败或非法响应渲染“应用推广读取失败,请稍后重试”。不得修改 `root.location`。 - -- [ ] **Step 6: 运行模块测试观察 GREEN** - -Run: - -```powershell -node --test tests/app-promotion-pages.test.js tests/api-client-contract.test.js -``` - -Expected: 全部通过。 - ---- - -### Task 3: 开放应用下载页推广区域 - -**Files:** -- Modify: `tests/app-promotion-pages.test.js` -- Modify: `tests/pc-scope.test.js` -- Modify: `app.html` -- Modify only if required by rendered markup: `public/css/app.css` - -**Interfaces:** -- Consumes: `window.AppPromotionPages.init()` 的 DOMContentLoaded 自动初始化。 -- Produces: `app.html` 的真实推广列表入口。 - -- [ ] **Step 1: 写页面开放状态失败测试** - -在 `tests/app-promotion-pages.test.js` 读取 `app.html`,断言: - -```js -assert.match(page, /data-promotion-page/); -assert.match(page, /data-promotion-list/); -assert.match(page, /public\/js\/app-promotion-pages\.js/); -assert.doesNotMatch(page, /public\/js\/promotion-pages\.js/); -assert.doesNotMatch(page, /name="(?:promotionId|coverOssId|platform)"/); -``` - -在 `tests/pc-scope.test.js` 保留旧 `promotion-pages.js` deny-list,并新增 `app.html` 必须加载 `app-promotion-pages.js` 的断言。 - -- [ ] **Step 2: 运行页面测试观察 RED** - -Run: - -```powershell -node --test tests/app-promotion-pages.test.js tests/pc-scope.test.js -``` - -Expected: FAIL,提示 `app.html` 尚未加载新脚本。 - -- [ ] **Step 3: 修改页面加载脚本** - -在 `page-effects.js` 后加载: - -```html - -``` - -保留现有 `data-promotion-page`、`data-promotion-list` 和初始加载文案。不得添加筛选器、刷新按钮、示例推广或隐藏 ID 输入。 - -- [ ] **Step 4: 仅在需要时补充无图片卡片样式** - -如果 `` 不能继承卡片文字颜色与块级点击区域,只增加: - -```css -.promotion-card { - display: block; - color: inherit; - text-decoration: none; -} -``` - -不得调整首页广告样式或重做应用下载页布局。 - -- [ ] **Step 5: 运行页面与模块组合测试观察 GREEN** - -Run: - -```powershell -node --test tests/app-promotion-pages.test.js tests/api-client-contract.test.js tests/pc-scope.test.js tests/public-static-pages.test.js -``` - -Expected: 全部通过。 - ---- - -### Task 4: 更新规划并完成验证 - -**Files:** -- Modify: `docs/PC接口对接规划.md` -- Modify: `docs/superpowers/plans/2026-07-29-app-promotions.md` - -**Interfaces:** -- Consumes: 已实现的 ApiClient、模块、页面和测试证据。 -- Produces: 阶段 7 第七批契约记录和验收报告。 - -- [ ] **Step 1: 在规划中新增推广字段表** - -记录: - -- method/path/内容推广目录; -- `platform` 为可选 Query、当前页面省略; -- Authorization 与 clientid 为 A; -- Body 为空; -- `promotionId`、`promotionKey`、`coverOssId`、`platform`、`sortOrder`、`status`、`remark` 为 I; -- `promotionTitle`、`promotionDesc`、安全 `targetUrl` 为 R; -- YAML `security: []` / 未导出 Query 与后端实际鉴权 / `platform` 的冲突; -- 页面初始化、未登录、401、403、空数组、非法响应和真实列表时机。 - -- [ ] **Step 2: 运行专项测试** - -Run: - -```powershell -node --test tests/app-promotion-pages.test.js tests/api-client-contract.test.js tests/pc-scope.test.js tests/public-static-pages.test.js -``` - -Expected: 0 failures。 - -- [ ] **Step 3: 运行语法和差异检查** - -Run: - -```powershell -node --check public/js/app-promotion-pages.js -node --check utils/ApiClient.js -git -c safe.directory=D:/WorkSpace/Web/jiapu diff --check -``` - -Expected: 两个语法检查 exit 0;`diff --check` 无错误,CRLF warning 可记录但不算失败。 - -- [ ] **Step 4: 运行全量测试** - -Run: - -```powershell -npm test -``` - -Expected: 0 failures。 - -- [ ] **Step 5: 浏览器验证真实分支** - -只读验证: - -1. 未登录打开 `app.html`,确认下载内容保留、推广区域显示登录入口、未发生推广请求; -2. 使用已授权测试账号登录后打开 `app.html`; -3. 后端返回空数组时显示“当前暂无应用推广”;有数据时检查标题、说明、安全外链和内部字段隐藏; -4. 检查页面控制台; -5. 不点击外部推广链接,不创建或修改后端数据; -6. 浏览器控制不稳定时停止自动关闭标签页,只报告已取得的验证结果并清理本地临时服务。 - -- [ ] **Step 6: 标记计划状态并按阶段格式汇报** - -报告必须包含: - -```text -Changed: ApiClient、推广模块、app.html 和规划新增内容。 -Verified: RED/GREEN、专项、全量和浏览器覆盖数量。 -Conflicts: YAML 匿名/无 Query 与后端真实鉴权/platform 的差异。 -Blocked: 缺少封面 URL、platform 枚举或真实推广数据时的剩余联调项。 -Next: 官网内容或公开家谱接口的下一最小批次。 -``` diff --git a/docs/superpowers/plans/2026-07-29-family-home.md b/docs/superpowers/plans/2026-07-29-family-home.md deleted file mode 100644 index ca1ac54..0000000 --- a/docs/superpowers/plans/2026-07-29-family-home.md +++ /dev/null @@ -1,376 +0,0 @@ -# 家谱主页 Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** 将 `profile-family-home.html` 从静态预览开放为真实 PC 家谱主页,展示当前家谱概览和世系树预览,并保留已接入业务页入口。 - -**Architecture:** `profile-common.js` 继续唯一拥有当前 `genealogyId`;页面只调用已有 `genealogyOverview(genealogyId)` 和 `lineageTree(genealogyId)`。`lineage-pages.js` 继续唯一拥有世系树节点规范化与 HTML 生成,新建 `family-home-pages.js` 只负责概览 DTO、权限显隐、并行读取和页面状态。 - -**Tech Stack:** 静态 HTML、原生 JavaScript UMD、Axios、Node `node:test`。 - -**Status:** 2026-07-29 已按 Task 1–4 实施;自动化与无家谱真实账号分支已验证,有家谱数据态等待具备真实家谱的账号复核。 - -## Global Constraints - -- 后端 `D:\WorkSpace\Java\Genealogy` 全程只读。 -- 只使用 PC 接口;不得调用 `/genealogy/dashboard/overview`、APP 或管理后台接口。 -- `genealogyId` 只能来自 `ProfileUI.getGenealogyId()`,始终按字符串处理。 -- `GET /genealogy/pc/genealogies/{genealogyId}/overview` 返回 `AppGenealogyVo`,它不是内容统计接口;不得制造文章、相册、视频或活动数量。 -- 世系树只读调用 `GET /genealogy/pc/genealogies/{genealogyId}/lineage/tree`。 -- 家谱管理入口仅在响应 `canManage=true` 时显示;内容页自身继续负责更细权限。 -- PC 没有成员邀请创建/分享接口;主页不得开放“邀请家人”操作。 -- 本批次没有写接口,不创建、修改或删除任何真实业务数据。 - ---- - -### Task 1: 家谱主页 DTO 与页面契约 - -**Files:** -- Create: `public/js/family-home-pages.js` -- Create: `tests/family-home-pages.test.js` - -**Interfaces:** -- Consumes: `ProfileUI.getGenealogyId()`、`GenealogyApi.defaultClient` -- Produces: - - `normalizeFamilyOverview(item, expectedGenealogyId)` - - `renderFamilyOverview(overview)` - - `shouldShowFamilyManagement(overview)` - - `shouldRedirectToLogin(api, error)` - -- [ ] **Step 1: 写失败数据边界测试** - -```js -const overview = FamilyHomePages.normalizeFamilyOverview({ - genealogyId: '2062179707935264769', - genealogyNo: 'G20260729001', - genealogyName: '叶氏家谱', - surname: '叶', - ancestralHall: '南阳堂', - originPlace: '四川成都', - regionFullName: '四川省 成都市', - memberCount: 12, - personCount: 36, - status: '0', - canManage: true, - canEditContent: true, - ownerUserId: 'must-not-render', - coverOssId: 'must-not-render' -}, '2062179707935264769'); - -assert.deepEqual(overview, { - genealogyId: '2062179707935264769', - genealogyNo: 'G20260729001', - genealogyName: '叶氏家谱', - surname: '叶', - ancestralHall: '南阳堂', - originPlace: '四川成都', - regionFullName: '四川省 成都市', - memberCount: 12, - personCount: 36, - canManage: true, - canEditContent: true -}); -assert.equal( - FamilyHomePages.normalizeFamilyOverview( - { genealogyId: Number.MAX_SAFE_INTEGER + 1, genealogyName: '非法', status: '0' }, - '2062179707935264769' - ), - null -); -assert.equal( - FamilyHomePages.normalizeFamilyOverview( - { genealogyId: '2', genealogyName: '串谱', status: '0' }, - '2062179707935264769' - ), - null -); -``` - -同时断言: - -- `genealogyName` 为空、`status!='0'`、负数/非安全计数均拒绝; -- 渲染转义全部文本,不出现 `ownerUserId`、`coverOssId` 或原始 JSON; -- 只有布尔值 `canManage===true` 才开放管理入口; -- 401 清理登录态,403 不清理登录态。 - -- [ ] **Step 2: 运行 RED** - -Run: `node --test tests/family-home-pages.test.js` - -Expected: FAIL,`family-home-pages.js` 不存在。 - -- [ ] **Step 3: 最小实现概览规范化** - -```js -function normalizeFamilyOverview(item, expectedGenealogyId) { - var source = item || {}; - var genealogyId = normalizeId(source.genealogyId); - var expectedId = normalizeId(expectedGenealogyId); - var memberCount = normalizeCount(source.memberCount); - var personCount = normalizeCount(source.personCount); - - if (!genealogyId || genealogyId !== expectedId || - !text(source.genealogyName) || String(source.status) !== '0' || - memberCount === null || personCount === null) return null; - return { - genealogyId: genealogyId, - genealogyNo: text(source.genealogyNo), - genealogyName: text(source.genealogyName), - surname: text(source.surname), - ancestralHall: text(source.ancestralHall), - originPlace: text(source.originPlace), - regionFullName: text(source.regionFullName), - memberCount: memberCount, - personCount: personCount, - canManage: source.canManage === true, - canEditContent: source.canEditContent === true - }; -} -``` - -- [ ] **Step 4: 运行 GREEN** - -Run: - -```powershell -node --test tests/family-home-pages.test.js -node --check public/js/family-home-pages.js -``` - -Expected: PASS。 - ---- - -### Task 2: 复用世系树唯一渲染 owner - -**Files:** -- Modify: `public/js/lineage-pages.js` -- Modify: `tests/lineage-pages.test.js` -- Modify: `tests/family-home-pages.test.js` - -**Interfaces:** -- Produces: `LineagePages.renderLineageTreeHtml(data)` -- Consumes: 现有 `normalizeLineagePerson(item)`、`renderTreeNode(item, ancestry)` - -- [ ] **Step 1: 写失败共享渲染测试** - -```js -const tree = [{ - personId: '2062179707935264770', - genealogyId: '2062179707935264769', - name: '<始祖>', - status: '0', - spouses: [], - children: [] -}]; - -const html = LineagePages.renderLineageTreeHtml(tree); -assert.match(html, /<始祖>/); -assert.match(html, /data-lineage-person="2062179707935264770"/); -assert.doesNotMatch(html, /2062179707935264769/); -assert.match(LineagePages.renderLineageTreeHtml([]), /暂无世系树/); -``` - -- [ ] **Step 2: 运行 RED** - -Run: `node --test tests/lineage-pages.test.js tests/family-home-pages.test.js` - -Expected: FAIL,`renderLineageTreeHtml` 未导出。 - -- [ ] **Step 3: 从现有 `renderTree` 提取纯 HTML owner** - -```js -function renderLineageTreeHtml(data) { - var nodes = normalizeList(data) - .map(function (item) { return renderTreeNode(item, {}); }) - .filter(Boolean); - - return nodes.length - ? '
      ' + nodes.join('') + '
    ' - : '
    暂无世系树
    '; -} - -function renderTree(data) { - var container = query('[data-lineage-tree]'); - if (container) container.innerHTML = renderLineageTreeHtml(data); -} -``` - -在 UMD 导出对象加入: - -```js -renderLineageTreeHtml: renderLineageTreeHtml -``` - -- [ ] **Step 4: 运行 GREEN** - -Run: - -```powershell -node --test tests/lineage-pages.test.js tests/family-home-pages.test.js -node --check public/js/lineage-pages.js -``` - -Expected: PASS,现有世系管理页输出不变。 - ---- - -### Task 3: 开放真实家谱主页 - -**Files:** -- Modify: `profile-family-home.html` -- Modify: `public/js/family-home-pages.js` -- Modify: `tests/family-home-pages.test.js` -- Modify: `tests/pending-pages.test.js` -- Modify: `tests/stage6-navigation.test.js` - -**Interfaces:** -- Consumes: - - `api.genealogyOverview(genealogyId)` - - `api.lineageTree(genealogyId)` - - `LineagePages.renderLineageTreeHtml(data)` -- Produces: `initFamilyHomePage()`、`init()` - -- [ ] **Step 1: 写失败页面测试** - -断言: - -- `profile-family-home.html` 不再包含 `data-feature-status="pending"` 或 `pending-pages.js`; -- 加载顺序为 `profile-common.js`、`lineage-pages.js`、`family-home-pages.js`; -- 标题、摘要、计数、管理入口、世系预览均有稳定 `data-*` hook; -- 硬编码“四川武胜汤氏族”被删除; -- “邀请家人”不再是可点击业务入口,并明确提示“PC 暂未开放邀请”; -- 谱文、相册、视频、功德、祭祀、世系、动态入口继续携带 `data-genealogy-context-link`; -- 初始化只并行调用: - -```js -Promise.all([ - api.genealogyOverview(genealogyId), - api.lineageTree(genealogyId) -]) -``` - -- [ ] **Step 2: 运行 RED** - -Run: - -```powershell -node --test tests/family-home-pages.test.js tests/pending-pages.test.js tests/stage6-navigation.test.js -``` - -Expected: FAIL,主页仍为 pending 且包含硬编码家谱。 - -- [ ] **Step 3: 实现只读初始化流程** - -```js -async function initFamilyHomePage() { - var api = root.GenealogyApi && root.GenealogyApi.defaultClient; - var genealogyId = root.ProfileUI && root.ProfileUI.getGenealogyId(); - var results; - var overview; - - if (!genealogyId) { - root.location.replace('profile-families.html?next=profile-family-home.html'); - return; - } - try { - results = await Promise.all([ - api.genealogyOverview(genealogyId), - api.lineageTree(genealogyId) - ]); - overview = normalizeFamilyOverview(results[0], genealogyId); - if (!overview) throw new Error('家谱概览响应无效'); - renderFamilyOverview(overview); - renderManagementAccess(overview.canManage); - query('[data-lineage-home-tree]').innerHTML = - root.LineagePages.renderLineageTreeHtml(results[1]); - } catch (error) { - if (shouldRedirectToLogin(api, error)) return redirectToLogin(api); - renderFamilyHomeError(error); - } -} -``` - -页面只展示: - -- 家谱名称、编号、姓氏、堂号、祖籍/地区; -- `memberCount`、`personCount`; -- 真实世系树及进入完整世系页的链接; -- 后端已经接入的内容模块入口。 - -- [ ] **Step 4: 运行 GREEN** - -Run: - -```powershell -node --test tests/family-home-pages.test.js tests/pending-pages.test.js tests/stage6-navigation.test.js -node --check public/js/family-home-pages.js -``` - -Expected: PASS。 - ---- - -### Task 4: 规划记录与完整验收 - -**Files:** -- Modify: `docs/PC接口对接规划.md` -- Modify: `docs/superpowers/plans/2026-07-29-family-home.md` - -**Interfaces:** -- Consumes: Task 1–3 的只读家谱主页闭环。 - -- [ ] **Step 1: 更新规划字段表** - -新增家谱主页小节,逐字段记录: - -| 字段 | 分类 | 页面用途 | -| --- | --- | --- | -| `genealogyId` | I/A | 当前上下文、两条请求 path、响应一致性校验 | -| `genealogyNo`、`genealogyName`、`surname` | R | 标题和基础信息 | -| `ancestralHall`、`originPlace`、`regionFullName` | R | 非空时展示 | -| `memberCount`、`personCount` | R | 非负只读计数 | -| `canManage`、`canEditContent` | I | 权限显隐,不作为用户输入 | -| `ownerUserId`、`coverOssId` | I | 当前主页不展示、不手填 | -| `LineagePersonTreeView.spouses/children` | R | 递归世系预览 | - -同时记录: - -- `/overview` 实际是详情别名,不包含内容聚合统计; -- `/genealogy/dashboard/overview` 是后台权限接口,不进入 PC 前端; -- 成员邀请没有 PC 接口,继续阻断。 - -- [ ] **Step 2: 聚焦验证** - -Run: - -```powershell -node --test tests/family-home-pages.test.js tests/lineage-pages.test.js tests/pending-pages.test.js tests/stage6-navigation.test.js tests/api-client-contract.test.js -node --check public/js/family-home-pages.js -node --check public/js/lineage-pages.js -``` - -Expected: 全部 PASS。 - -- [ ] **Step 3: 全量和差异验证** - -Run: - -```powershell -npm test -git -c safe.directory=D:/WorkSpace/Web/jiapu diff --check -``` - -Expected: 0 failed,差异检查无错误。 - -- [ ] **Step 4: 浏览器真实只读验证** - -使用真实登录态验证: - -1. 无家谱上下文时只跳转选择页,不发家谱业务请求; -2. 有真实家谱时标题、概览计数和世系树来自 PC 响应; -3. 普通成员看不到管理按钮,管理者可见; -4. 所有入口透传同一 `genealogyId`; -5. 空世系、403、404、网络错误都有明确页面状态; -6. 控制台无错误; -7. 不创建或修改任何真实数据。 diff --git a/docs/superpowers/plans/2026-07-29-feedback-tickets.md b/docs/superpowers/plans/2026-07-29-feedback-tickets.md deleted file mode 100644 index 42dffd4..0000000 --- a/docs/superpowers/plans/2026-07-29-feedback-tickets.md +++ /dev/null @@ -1,310 +0,0 @@ -# 反馈与工单 Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** 开放意见反馈、提交工单、我的工单和工单详情,以同一套 PC 反馈记录形成提交、列表和详情闭环。 - -**Architecture:** `utils/ApiClient.js` 唯一拥有 `/genealogy/pc/feedback` 的 GET/POST 契约;新增 UMD 模块 `feedback-pages.js` 负责 DTO 构造、完整 VO 规范化、列表/详情渲染和四类页面初始化。“工单”只是反馈记录的帮助中心展示名称,详情通过 URL 中由列表响应产生的 `feedbackId` 在我的反馈列表中精确匹配。 - -**Tech Stack:** 静态 HTML、原生 JavaScript UMD、Axios、Node `node:test`。 - -## Global Constraints - -- 后端 `D:\WorkSpace\Java\Genealogy` 全程只读。 -- 只调用 `POST /genealogy/pc/feedback` 和 `GET /genealogy/pc/feedback`。 -- 请求只发送 `feedbackType`、`feedbackContent`、`contactInfo`;不发送 `feedbackTitle`。 -- `feedbackType` 只允许后端已确认字典值 `advice/bug/complaint/other`;空值省略并由后端默认 `advice`。 -- `feedbackId` 只能来自提交或列表响应,不提供文本输入。 -- 页面不展示 `appUserId`、`handlerId`、`appUserPhone`、原始 JSON。 -- 所有提交防重复;提交成功后必须重读列表并匹配同一 `feedbackId`。 - ---- - -### Task 1: ApiClient 反馈契约 - -**Files:** -- Modify: `utils/ApiClient.js` -- Modify: `tests/api-client-contract.test.js` - -**Interfaces:** -- Produces: `submitFeedback(body)`、`myFeedback()` - -- [ ] **Step 1: 写失败的契约测试** - -增加测试,使用完整请求字面量断言: - -```js -await client.submitFeedback({ - feedbackType: 'bug', - feedbackContent: '页面按钮无响应', - contactInfo: '<测试联系电话>', - feedbackTitle: 'must-drop', - appUserId: 'must-drop' -}); -await client.myFeedback(); - -assert.deepEqual(calls, [ - { - method: 'POST', - url: '/genealogy/pc/feedback', - body: { - feedbackType: 'bug', - feedbackContent: '页面按钮无响应', - contactInfo: '<测试联系电话>' - } - }, - { method: 'GET', url: '/genealogy/pc/feedback' } -]); -``` - -- [ ] **Step 2: 运行 RED** - -Run: - -```powershell -node --test tests/api-client-contract.test.js -``` - -Expected: FAIL,`submitFeedback` 或 `myFeedback` 不存在。 - -- [ ] **Step 3: 最小实现** - -在 `createClient()` 返回对象中增加: - -```js -submitFeedback: function (body) { - return request('POST', '/genealogy/pc/feedback', { - body: pickDefined(body, ['feedbackType', 'feedbackContent', 'contactInfo']) - }); -}, -myFeedback: function () { - return request('GET', '/genealogy/pc/feedback'); -} -``` - -- [ ] **Step 4: 运行 GREEN** - -Run: - -```powershell -node --test tests/api-client-contract.test.js -``` - -Expected: PASS。 - ---- - -### Task 2: 反馈 DTO、VO、列表和详情边界 - -**Files:** -- Create: `public/js/feedback-pages.js` -- Create: `tests/feedback-pages.test.js` - -**Interfaces:** -- Produces: - - `getFeedbackId(search)` - - `buildFeedbackBody(values)` - - `validateFeedbackBody(body)` - - `normalizeFeedback(item)` - - `normalizeFeedbackList(data)` - - `findFeedbackById(data, feedbackId)` - - `buildFeedbackDetailUrl(feedbackId)` - - `renderFeedbackList(data, options)` - - `renderFeedbackDetail(item)` - -- [ ] **Step 1: 写失败的纯行为测试** - -用完整 `FeedbackVo` 字面量验证: - -```js -assert.deepEqual(FeedbackPages.buildFeedbackBody({ - feedbackType: ' bug ', - feedbackContent: ' 页面按钮无响应 ', - contactInfo: ' <测试联系电话> ', - feedbackTitle: 'must-drop', - appUserId: 'must-drop' -}), { - feedbackType: 'bug', - feedbackContent: '页面按钮无响应', - contactInfo: '<测试联系电话>' -}); -``` - -同时断言: - -- `feedbackContent` 为空时报错; -- 类型只允许 `advice/bug/complaint/other`,空值省略; -- 不安全 number ID、空内容、`handleStatus` 非 `0/1/2/3`、`status` 非 `0/1` 的响应拒绝; -- 数组包含一个非法元素时整批返回空数组; -- 详情只精确匹配同一字符串 `feedbackId`,不存在时不回退第一条; -- 列表和详情转义可见文本,不出现内部用户/处理人 ID、账号手机号或原始 JSON; -- 详情 URL 使用 `ticket-detail.html?feedbackId=...`。 - -- [ ] **Step 2: 运行 RED** - -Run: - -```powershell -node --test tests/feedback-pages.test.js -``` - -Expected: FAIL,模块不存在。 - -- [ ] **Step 3: 最小实现纯函数** - -`normalizeFeedback()` 返回且只返回: - -```js -{ - feedbackId, - feedbackType, - feedbackContent, - contactInfo, - handleStatus, - handleResult, - handleTime, - status, - remark -} -``` - -状态展示固定为: - -```js -{ '0': '待处理', '1': '处理中', '2': '已处理', '3': '已关闭' } -``` - -- [ ] **Step 4: 运行 GREEN** - -Run: - -```powershell -node --test tests/feedback-pages.test.js -node --check public/js/feedback-pages.js -``` - -Expected: PASS。 - ---- - -### Task 3: 四个页面的真实反馈闭环 - -**Files:** -- Modify: `profile-feedback.html` -- Modify: `submit-ticket.html` -- Modify: `my-tickets.html` -- Modify: `ticket-detail.html` -- Modify: `public/js/feedback-pages.js` -- Modify: `tests/feedback-pages.test.js` -- Modify: `tests/pending-pages.test.js` -- Modify: `tests/public-static-pages.test.js` -- Modify: `tests/pc-scope.test.js` - -**Interfaces:** -- Produces: `initFeedbackFormPage()`、`initFeedbackListPage()`、`initFeedbackDetailPage()`、`init()` - -- [ ] **Step 1: 写失败的页面行为测试** - -断言四个页面: - -- 不再包含 `data-feature-status="pending"` 或 `pending-pages.js`; -- 均加载 `feedback-pages.js` 和 ApiClient 依赖; -- 两个提交页只提供 `feedbackType/feedbackContent/contactInfo`,不存在 `feedbackTitle` 或任意业务 ID 输入; -- 列表页有 `data-feedback-list` 和刷新按钮; -- 详情页只有 `data-feedback-detail`,不提供回复、追问、删除或关闭操作。 - -断言脚本提交后调用 `myFeedback()`,必须按提交响应的 `feedbackId` 重读匹配;详情按 URL `feedbackId` 精确匹配。 - -- [ ] **Step 2: 运行 RED** - -Run: - -```powershell -node --test tests/feedback-pages.test.js tests/pending-pages.test.js tests/public-static-pages.test.js tests/pc-scope.test.js -``` - -Expected: FAIL,四个页面仍为 pending。 - -- [ ] **Step 3: 实现页面初始化** - -提交页: - -1. 构造并校验请求; -2. `writePending` 锁和按钮禁用; -3. 调用 `submitFeedback(body)`; -4. 规范化提交响应; -5. 重读 `myFeedback()`,精确找到同一 `feedbackId`; -6. 个人反馈页刷新历史列表;工单页进入 `ticket-detail.html?feedbackId=...`。 - -列表页读取 `myFeedback()` 并生成详情链接。详情页从 URL 读取 ID、重读列表并精确匹配;未匹配显示“反馈记录不存在或无权查看”。 - -- [ ] **Step 4: 运行 GREEN** - -Run: - -```powershell -node --test tests/feedback-pages.test.js tests/pending-pages.test.js tests/public-static-pages.test.js tests/pc-scope.test.js -node --check public/js/feedback-pages.js -``` - -Expected: PASS。 - ---- - -### Task 4: 导航、规划和收尾验证 - -**Files:** -- Modify: `help.html` -- Modify: `profile-services.html` -- Modify: `docs/PC接口对接规划.md` -- Modify: `tests/stage6-navigation.test.js` -- Modify: `docs/superpowers/plans/2026-07-29-feedback-tickets.md` - -**Interfaces:** -- Consumes: Task 1–3 的真实反馈闭环。 - -- [ ] **Step 1: 写失败的导航测试** - -断言: - -- 帮助中心可直接进入提交工单和我的工单; -- 服务中心可进入意见反馈; -- 所有入口不再被 pending 状态拦截; -- 不存在独立 ticket API、反馈标题或手填 `feedbackId` 的入口。 - -- [ ] **Step 2: 运行 RED** - -Run: - -```powershell -node --test tests/stage6-navigation.test.js tests/public-static-pages.test.js -``` - -Expected: FAIL,帮助中心尚未开放我的工单入口或旧 pending 断言仍存在。 - -- [ ] **Step 3: 更新入口和规划** - -规划记录: - -- `AppFeedbackBody` 三个字段来源与提交时机; -- `FeedbackVo` 可见字段、内部隐藏字段和四种处理状态; -- 无独立工单 path,四个页面共用反馈记录; -- 提交后重读、详情精确匹配、401/403 和隐私边界; -- 后端没有用户侧回复、追问、关闭或删除接口,继续阻断。 - -- [ ] **Step 4: 聚焦和全量验证** - -Run: - -```powershell -node --test tests/feedback-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js tests/public-static-pages.test.js tests/pc-scope.test.js tests/stage6-navigation.test.js -npm test -node --check public/js/feedback-pages.js -git -c safe.directory=D:/WorkSpace/Web/jiapu diff --check -``` - -Expected: 全部 PASS。 - -- [ ] **Step 5: 浏览器只读验证** - -使用真实登录态验证四个页面、我的反馈列表、无记录详情、隐私和控制台错误。未经用户本轮明确授权,不提交真实反馈。 diff --git a/docs/superpowers/plans/2026-07-29-genealogy-create-first-batch.md b/docs/superpowers/plans/2026-07-29-genealogy-create-first-batch.md deleted file mode 100644 index b50be1d..0000000 --- a/docs/superpowers/plans/2026-07-29-genealogy-create-first-batch.md +++ /dev/null @@ -1,258 +0,0 @@ -# 家谱创建首批 Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** 在约一小时的首批施工中冻结剩余家谱生命周期 PC 请求契约,并把 `profile-create-family.html` 从 pending 预览改成可用的真实创建家谱页面。 - -**Architecture:** `utils/ApiClient.js` 唯一拥有请求路径和 body 白名单;`public/js/genealogy-entry-pages.js` 扩展家谱创建的纯构造、校验、响应规范化和页面初始化;现有 `region-pages.js` 负责三级地区选项,`upload-pages.js` 负责生成 `coverOssId`。创建成功后只使用响应中的稳定 `genealogyId` 跳转到家谱主页。 - -**Tech Stack:** 静态 HTML、原生 JavaScript UMD、Axios、Node `node:test`。 - -## Global Constraints - -- 后端 `D:\WorkSpace\Java\Genealogy` 全程只读。 -- 只调用 `/genealogy/pc/**`,不调用 APP 或后台管理接口。 -- 不允许用户手填家谱 ID、申请 ID、用户 ID 或 OSS ID。 -- 每个生产行为必须先有失败测试并实际运行 RED。 -- 每个阶段完成后运行聚焦测试并更新 `docs/PC接口对接规划.md`。 - ---- - -### Task 1: 冻结家谱生命周期 ApiClient 契约 - -**Files:** -- Modify: `tests/api-client-contract.test.js` -- Modify: `utils/ApiClient.js` - -**Interfaces:** -- Produces: - - `createGenealogy(body)` - - `applyToGenealogy(genealogyId, body)` - - `myGenealogyJoinApplies()` - - `pendingGenealogyJoinApplies(genealogyId)` - - `auditGenealogyJoinApply(genealogyId, applyId, body)` - - `cancelGenealogyJoinApply(applyId)` - -- [x] **Step 1: 写失败的路径和 body 白名单测试** - -测试固定以下请求: - -```js -await client.createGenealogy({ - genealogyName: '汤氏家谱', - surname: '汤', - regionCode: '511622', - coverOssId: '2062179707935264769', - legacyField: 'drop' -}); - -await client.applyToGenealogy(genealogyId, { - applicantName: '申请人', - phone: '<测试联系电话>', - relationDesc: '族亲', - applyReason: '申请加入', - inviterUserId: 'drop-without-safe-source' -}); - -await client.auditGenealogyJoinApply(genealogyId, applyId, { - status: '1', - auditRemark: '资料一致', - legacyField: 'drop' -}); -``` - -断言: - -- 创建请求为 `POST /genealogy/pc/genealogies`,只保留 `genealogyName/surname/regionCode/ancestralHall/originPlace/addressDetail/coverOssId/intro/visibility/joinMode`。 -- 加入请求为 `POST /genealogy/pc/genealogies/{genealogyId}/join-applies`,本轮安全 PC 页面只保留 `applicantName/phone/relationDesc/applyReason`,不发送没有安全来源的 `inviterUserId`。 -- 我的申请、待审核、审核和撤销使用后端 PC Controller 的完整路径。 -- 审核 body 只保留 `status/auditRemark`。 -- 所有 path ID 拒绝不安全数字。 - -- [x] **Step 2: 运行 RED** - -Run: - -```powershell -node --test --test-name-pattern "genealogy lifecycle" tests/api-client-contract.test.js -``` - -Expected: FAIL,新方法尚不存在。 - -- [x] **Step 3: 最小实现方法和白名单** - -使用现有 `request()`、`pickDefined()` 和 `toRequiredPathId()`;不增加兼容别名或 APP fallback。 - -- [x] **Step 4: 运行 GREEN** - -Run: - -```powershell -node --test tests/api-client-contract.test.js -node --check utils/ApiClient.js -``` - -Expected: PASS。 - ---- - -### Task 2: 家谱创建纯行为 - -**Files:** -- Modify: `tests/genealogy-entry-pages.test.js` -- Modify: `public/js/genealogy-entry-pages.js` - -**Interfaces:** -- Consumes: `createGenealogy(body)`、`genealogyQuota()` -- Produces: - - `buildGenealogyCreateBody(values)` - - `validateGenealogyCreateBody(body)` - - `normalizeCreatedGenealogy(item)` - - `buildCreatedGenealogyUrl(item)` - -- [x] **Step 1: 写失败的创建行为测试** - -```js -assert.deepEqual(GenealogyEntryPages.buildGenealogyCreateBody({ - genealogyName: ' 汤氏家谱 ', - surname: ' 汤 ', - regionCode: '511622', - ancestralHall: '', - coverOssId: '2062179707935264769', - visibility: '1', - joinMode: '1', - manualId: 'drop' -}), { - genealogyName: '汤氏家谱', - surname: '汤', - regionCode: '511622', - coverOssId: '2062179707935264769', - visibility: '1', - joinMode: '1' -}); -``` - -同时断言: - -- 缺少谱名、姓氏或地区时返回明确校验错误。 -- `visibility` 只允许 `0/1/2`,`joinMode` 只允许 `0/1/2`。 -- `coverOssId` 必须是安全字符串 ID;没有文件可省略。 -- 额度 `createRemaining <= 0` 时阻止提交。 -- 创建响应必须有稳定 `genealogyId`、谱名和姓氏。 -- 跳转地址只能由响应 ID 产生:`profile-family-home.html?genealogyId=...`。 - -- [x] **Step 2: 运行 RED** - -Run: - -```powershell -node --test tests/genealogy-entry-pages.test.js -``` - -Expected: FAIL,纯函数不存在。 - -- [x] **Step 3: 最小实现纯函数** - -严格构造 `AppGenealogyCreateBody`,可选空值省略,不读取页面外字段。 - -- [x] **Step 4: 运行 GREEN** - -Run: - -```powershell -node --test tests/genealogy-entry-pages.test.js -node --check public/js/genealogy-entry-pages.js -``` - -Expected: PASS。 - ---- - -### Task 3: 开放创建家谱页面 - -**Files:** -- Modify: `profile-create-family.html` -- Modify: `public/js/genealogy-entry-pages.js` -- Modify: `tests/genealogy-entry-pages.test.js` -- Modify: `tests/pending-pages.test.js` -- Modify: `tests/security-upload-scope.test.js` -- Modify: `tests/pc-scope.test.js` - -**Interfaces:** -- Consumes: Task 1 和 Task 2、`RegionPages`、`UploadPages` -- Produces: `initGenealogyCreatePage()` - -- [x] **Step 1: 写失败的页面测试** - -断言页面: - -- 不再包含 `data-feature-status="pending"` 和 `pending-pages.js`。 -- 加载 `region-pages.js`、`md5.js`、`upload-pages.js` 和 `genealogy-entry-pages.js`。 -- 存在隐藏 `coverOssId` 与文件选择控件,不能手填 OSS ID。 -- 不存在 `genealogyId/applyId/inviterUserId` 输入。 -- 表单只包含后端创建 DTO 字段和地区选择辅助字段。 -- 创建按钮、状态区域和额度区域可由脚本控制。 - -- [x] **Step 2: 运行 RED** - -Run: - -```powershell -node --test tests/genealogy-entry-pages.test.js tests/pending-pages.test.js tests/security-upload-scope.test.js tests/pc-scope.test.js -``` - -Expected: FAIL,页面仍 pending 且缺上传/初始化行为。 - -- [x] **Step 3: 实现页面初始化和提交** - -初始化顺序: - -1. 校验登录态。 -2. 调用 `genealogyQuota()` 并展示真实额度。 -3. `RegionPages` 初始化三级地区。 -4. `UploadPages` 通过 `data-upload-target` 把封面上传结果写入隐藏 `coverOssId`。 -5. 提交前同步表单、构造并校验 body。 -6. 使用 `writePending` 阻止重复提交。 -7. 调用 `createGenealogy(body)`。 -8. 规范化响应并跳转到真实家谱主页。 - -401 清登录态;403 保留登录态;业务错误显示在表单状态区域。 - -- [x] **Step 4: 运行 GREEN** - -Run: - -```powershell -node --test tests/genealogy-entry-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js tests/security-upload-scope.test.js tests/pc-scope.test.js -node --check public/js/genealogy-entry-pages.js -``` - -Expected: PASS。 - ---- - -### Task 4: 首批收口 - -**Files:** -- Modify: `docs/PC接口对接规划.md` -- Modify: `docs/superpowers/plans/2026-07-29-genealogy-create-first-batch.md` - -- [x] **Step 1: 更新契约和阶段状态** - -记录 `AppGenealogyCreateBody`、`AppGenealogyVo`、额度、权限、上传派生字段、加入申请 ApiClient 契约及仍未开放的加入 UI。 - -- [x] **Step 2: 聚焦和全量验证** - -Run: - -```powershell -node --test tests/api-client-contract.test.js tests/genealogy-entry-pages.test.js tests/pending-pages.test.js tests/security-upload-scope.test.js tests/pc-scope.test.js -npm test -git -c safe.directory=D:/WorkSpace/Web/jiapu diff --check -``` - -Expected: 全部 PASS。 - -- [x] **Step 3: 浏览器验证并报告** - -使用真实账号验证创建页登录态、额度、地区加载、无手填 ID、控制台错误和提交前校验。不得为测试消耗真实创建额度;除非用户明确授权,不执行最终创建写操作。 diff --git a/docs/superpowers/plans/2026-07-29-genealogy-join-review.md b/docs/superpowers/plans/2026-07-29-genealogy-join-review.md deleted file mode 100644 index 3b8df44..0000000 --- a/docs/superpowers/plans/2026-07-29-genealogy-join-review.md +++ /dev/null @@ -1,266 +0,0 @@ -# 家谱加入申请与审核 Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** 开放家谱申请加入、我的申请、撤销待审核申请,以及家谱管理员审核申请的完整 PC 页面闭环。 - -**Architecture:** `utils/ApiClient.js` 已拥有本批全部 PC path/body。新增 `join-pages.js` 负责可申请家谱选择、申请表单、我的申请和撤销;新增 `join-review-pages.js` 负责当前家谱待审核列表和通过/拒绝。两个 UMD 模块只使用响应中的字符串 ID,写入后重读对应列表。 - -**Tech Stack:** 静态 HTML、原生 JavaScript UMD、Axios、Node `node:test`。 - -## Global Constraints - -- 后端 `D:\WorkSpace\Java\Genealogy` 全程只读。 -- 只调用 `/genealogy/pc/**`。 -- 家谱 ID 和申请 ID 必须来自 PC 响应或当前家谱上下文,不能手填。 -- `inviterUserId` 没有安全 PC 来源,不展示、不发送。 -- 所有写操作防重复,401 清登录态,403 保留登录态。 -- 每项生产行为先运行失败测试,再最小实现。 - ---- - -### Task 1: 申请数据边界 - -**Files:** -- Create: `tests/join-pages.test.js` -- Create: `public/js/join-pages.js` - -**Interfaces:** -- Consumes: `genealogyOptions(query)`、`applyToGenealogy(genealogyId, body)`、`myGenealogyJoinApplies()`、`cancelGenealogyJoinApply(applyId)` -- Produces: - - `normalizeJoinGenealogy(item)` - - `buildJoinApplyBody(values)` - - `validateJoinApplyBody(body)` - - `normalizeJoinApply(item)` - - `normalizeJoinApplies(data)` - - `renderJoinGenealogyOptions(data)` - - `renderMyJoinApplies(data)` - -- [ ] **Step 1: 写失败的纯行为测试** - -用完整 `AppGenealogyVo` 和 `GenealogyJoinApplyVo` 字面量验证: - -```js -assert.deepEqual(JoinPages.buildJoinApplyBody({ - applicantName: ' 叶子 ', - phone: ' <测试联系电话> ', - relationDesc: ' 族亲 ', - applyReason: ' 申请加入 ', - inviterUserId: 'must-drop' -}), { - applicantName: '叶子', - phone: '<测试联系电话>', - relationDesc: '族亲', - applyReason: '申请加入' -}); -``` - -断言: - -- `applicantName <= 50`、`phone <= 30`、`relationDesc <= 100`、`applyReason <= 500`。 -- 可申请家谱必须有稳定 `genealogyId/genealogyName/surname`,停用家谱拒绝。 -- 申请必须有稳定 `applyId/genealogyId` 和 `status`;状态只允许 `0/1/2/3`。 -- 安全数字长 ID 只接受字符串;不安全 number 拒绝。 -- 普通用户列表不展示内部用户 ID、邀请人 ID、审核人 ID或原始 JSON。 -- 只有 `status=0` 渲染撤销按钮。 - -- [ ] **Step 2: 运行 RED** - -Run: - -```powershell -node --test tests/join-pages.test.js -``` - -Expected: FAIL,模块不存在。 - -- [ ] **Step 3: 最小实现纯函数** - -所有可见文本使用 `escapeHtml`;响应任一必需字段无效时拒绝该条,数组包含非法元素时整批返回空数组。 - -- [ ] **Step 4: 运行 GREEN** - -Run: - -```powershell -node --test tests/join-pages.test.js -node --check public/js/join-pages.js -``` - -Expected: PASS。 - ---- - -### Task 2: 申请页面与我的申请 - -**Files:** -- Modify: `join-genealogy.html` -- Modify: `profile-join-family.html` -- Modify: `public/js/join-pages.js` -- Modify: `tests/join-pages.test.js` -- Modify: `tests/pending-pages.test.js` -- Modify: `tests/pc-scope.test.js` - -**Interfaces:** -- Produces: `initJoinApplyPage()`、`initMyJoinAppliesPage()` - -- [ ] **Step 1: 写失败的页面行为测试** - -断言: - -- 两页均不再 pending,并加载 `join-pages.js`。 -- `join-genealogy.html` 有关键词搜索、后端家谱选项、申请资料表单;不存在 `genealogyId/inviterUserId/applyId` 文本输入。 -- `profile-join-family.html` 有我的申请列表和刷新入口;申请 ID 只存在于响应渲染按钮属性。 -- 页面没有邀请码输入或“分享码”伪语义。 - -- [ ] **Step 2: 运行 RED** - -Run: - -```powershell -node --test tests/join-pages.test.js tests/pending-pages.test.js tests/pc-scope.test.js -``` - -Expected: FAIL,页面仍 pending/缺少真实行为。 - -- [ ] **Step 3: 实现申请和撤销** - -`join-genealogy.html`: - -1. 读取 `genealogyOptions({keyword})`。 -2. 用户只能点击响应卡片选择家谱,内部保存字符串 `genealogyId`。 -3. 构造并校验申请 body。 -4. 提交后重读 `myGenealogyJoinApplies()`,必须找到同一响应 `applyId`。 -5. 跳转 `profile-join-family.html`。 - -`profile-join-family.html`: - -1. 读取并渲染我的申请。 -2. 只有待审核申请显示撤销。 -3. 二次确认后调用撤销接口并重读列表,确保该申请不再为待审核。 - -- [ ] **Step 4: 运行 GREEN** - -Run: - -```powershell -node --test tests/join-pages.test.js tests/pending-pages.test.js tests/pc-scope.test.js -node --check public/js/join-pages.js -``` - -Expected: PASS。 - ---- - -### Task 3: 管理员审核 - -**Files:** -- Create: `tests/join-review-pages.test.js` -- Create: `public/js/join-review-pages.js` -- Modify: `profile-join-review.html` -- Modify: `tests/pending-pages.test.js` -- Modify: `tests/pc-scope.test.js` - -**Interfaces:** -- Consumes: `genealogyDetail(genealogyId)`、`pendingGenealogyJoinApplies(genealogyId)`、`auditGenealogyJoinApply(genealogyId, applyId, body)` -- Produces: - - `getCurrentGenealogyId(search)` - - `buildJoinAuditBody(values)` - - `validateJoinAuditBody(body)` - - `renderPendingJoinApplies(data, canManage)` - - `initJoinReviewPage()` - -- [ ] **Step 1: 写失败的审核行为测试** - -```js -assert.deepEqual(JoinReviewPages.buildJoinAuditBody({ - status: '2', - auditRemark: ' 资料不一致 ', - applyId: 'must-drop' -}), { - status: '2', - auditRemark: '资料不一致' -}); -``` - -断言: - -- `status` 只允许 `1/2`,`auditRemark <= 500`。 -- 页面只从 URL/`ProfileUI` 读取家谱 ID。 -- 只有 `genealogyDetail.canManage=true` 显示审核动作。 -- 待审核列表显示申请人名称、申请手机号、关系和原因,但隐藏内部用户 ID、邀请人/审核人 ID。 -- 通过和拒绝按钮使用响应中的稳定 `applyId`。 - -- [ ] **Step 2: 运行 RED** - -Run: - -```powershell -node --test tests/join-review-pages.test.js -``` - -Expected: FAIL,模块不存在且页面仍 pending。 - -- [ ] **Step 3: 实现审核页面** - -加载当前家谱详情和待审核列表;没有上下文时阻止请求并返回家谱选择入口。点击通过使用 `{status:'1'}`;拒绝要求输入可选审核说明并使用 `{status:'2', auditRemark}`。审核成功后重读待审核列表,确保同一 `applyId` 不再存在。 - -- [ ] **Step 4: 运行 GREEN** - -Run: - -```powershell -node --test tests/join-review-pages.test.js tests/pending-pages.test.js tests/pc-scope.test.js -node --check public/js/join-review-pages.js -``` - -Expected: PASS。 - ---- - -### Task 4: 导航、规划与验证 - -**Files:** -- Modify: `profile-families.html` -- Modify: `profile-family-admin.html` -- Modify: `profile.html` -- Modify: `tests/stage6-navigation.test.js` -- Modify: `docs/PC接口对接规划.md` -- Modify: `docs/superpowers/plans/2026-07-29-genealogy-join-review.md` - -- [ ] **Step 1: 写失败的导航测试** - -断言我的家谱入口可达申请页,家谱管理入口携带上下文进入审核页;不存在指向手填家谱 ID 或邀请码页面的入口。 - -- [ ] **Step 2: 运行 RED** - -Run: - -```powershell -node --test tests/stage6-navigation.test.js tests/pending-pages.test.js -``` - -Expected: FAIL,审核导航尚未稳定携带家谱上下文。 - -- [ ] **Step 3: 开放导航并更新规划** - -记录申请/审核 DTO、VO、权限、状态、字段来源、写后重读和 `inviterUserId` 阻断。 - -- [ ] **Step 4: 聚焦和全量验证** - -Run: - -```powershell -node --test tests/join-pages.test.js tests/join-review-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js tests/pc-scope.test.js tests/stage6-navigation.test.js -npm test -node --check public/js/join-pages.js -node --check public/js/join-review-pages.js -git -c safe.directory=D:/WorkSpace/Web/jiapu diff --check -``` - -Expected: 全部 PASS。 - -- [ ] **Step 5: 浏览器验证并报告** - -真实账号验证可申请家谱列表、我的申请空状态、无家谱上下文审核分支、隐私和控制台错误。未经用户明确授权,不提交或审核真实申请。 - diff --git a/docs/superpowers/plans/2026-07-29-help-center.md b/docs/superpowers/plans/2026-07-29-help-center.md deleted file mode 100644 index cac917e..0000000 --- a/docs/superpowers/plans/2026-07-29-help-center.md +++ /dev/null @@ -1,39 +0,0 @@ -# 帮助中心 PC 接口对接计划 - -目标:把现有 `help.html` 的示例问答替换为后端正式 PC 帮助文章列表和详情,不混入官网文章、推广或家谱业务。 - -## 全局约束 - -- 只调用 `GET /genealogy/pc/help-articles` 和 `GET /genealogy/pc/help-articles/{helpId}`。 -- 后端控制器未标记匿名访问,部署环境要求登录;两个接口发送当前 PC token。 -- `helpCategory` 是唯一 Query 字段;`helpId` 只能来自列表响应。 -- 展示字段仅为 `helpCategory`、`helpTitle`、`helpContent`、`viewCount`。 -- `coverOssId`、`sortOrder`、`status`、`remark` 不展示,不允许用户输入 ID。 -- 正文转义后展示,不执行响应中的 HTML。 -- 后端项目只读。 - -## 任务 1:冻结客户端契约 - -- 在 `tests/api-client-contract.test.js` 先增加失败测试。 -- 在 `utils/ApiClient.js` 增加 `helpArticles(query)` 与 `helpArticleDetail(helpId)`。 -- 验证 method、path、Query 白名单、登录鉴权和长 ID 字符串。 - -## 任务 2:实现帮助文章边界 - -- 新增 `tests/help-pages.test.js` 并先观察失败。 -- 新增 `public/js/help-pages.js`。 -- 校验完整 `HelpArticleVo` 输入,只向页面返回安全展示字段。 -- 列表任一元素非法时整批失败;详情必须与请求 ID 一致。 -- 转义标题、分类和正文。 - -## 任务 3:开放帮助中心 - -- 先用页面契约测试证明现有示例内容不符合真实接口状态。 -- 修改 `help.html`,提供加载、空、成功和失败状态。 -- 展开文章时调用详情接口,不制造编辑、邀请或后台操作。 - -## 任务 4:规划与验证 - -- 更新 `docs/PC接口对接规划.md` 的字段来源、页面时机与阶段 7 进度。 -- 运行帮助中心专项测试、全量测试、语法检查和 `diff --check`。 -- 浏览器验证真实列表/空状态和控制台。 diff --git a/docs/superpowers/plans/2026-07-29-stage6-pc-pages.md b/docs/superpowers/plans/2026-07-29-stage6-pc-pages.md deleted file mode 100644 index 6073f18..0000000 --- a/docs/superpowers/plans/2026-07-29-stage6-pc-pages.md +++ /dev/null @@ -1,509 +0,0 @@ -# 阶段 6 PC 功能页面 Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. Project rules reserve code edits for the primary agent; subagents may only perform read-only exploration and review. - -**Goal:** 实现所有当前 PC 接口已具备闭环但前端缺失或未开放的谱文、相册/照片、祭祀/祭品、管理员邀约、指定账号绑定和家谱成员管理页面。 - -**Architecture:** `utils/ApiClient.js` 唯一拥有 PC method/path/query/body;每个业务使用独立 UMD 页面脚本,页面只消费脚本导出的规范化、校验、渲染和初始化接口。`profile-common.js` 提供家谱上下文,`upload-pages.js` 产生 OSS ID,所有写操作使用稳定字符串 ID、防重复提交并在成功后重读。 - -**Tech Stack:** 静态 HTML、原生 JavaScript UMD、Axios 请求封装、Node `node:test`、现有 profile CSS、wangEditor v5、统一分片上传。 - -## Global Constraints - -- 后端目录 `D:/WorkSpace/Java/Genealogy` 严格只读。 -- 只使用 `/genealogy/pc/**`,不得使用 APP 接口或旧路径 fallback。 -- 不允许手填业务 ID、用户 ID 或 OSS ID;int64 在浏览器边界保存为十进制字符串。 -- 普通列表无法重读停用记录的模块固定提交 `status=0`。 -- 每个模块严格执行 RED → GREEN → 聚焦测试 → 真实账号非破坏性验证 → 规划更新。 -- 当前工作树已有前几阶段改动;不得重置、覆盖或提交无关文件。除非用户另行要求,本计划不创建 Git commit。 - ---- - -### Task 1: 扩展 PC ApiClient 契约 - -**Files:** -- Modify: `utils/ApiClient.js` -- Modify: `tests/api-client-contract.test.js` - -**Interfaces:** -- Produces: - - `articles(genealogyId)`, `articleDetail(genealogyId, articleId)`, `createArticle(genealogyId, body)`, `updateArticle(genealogyId, articleId, body)` - - `albums(genealogyId)`, `createAlbum(genealogyId, body)`, `updateAlbum(genealogyId, albumId, body)`, `albumPhotos(genealogyId, albumId)`, `createAlbumPhoto(genealogyId, albumId, body)` - - `ceremonies(genealogyId)`, `ceremonyDetail(genealogyId, ceremonyId)`, `createCeremony`, `updateCeremony`, `ceremonyGifts`, `createCeremonyGift` - - `replaceCeremonyInvitees(genealogyId, ceremonyId, inviteeUserIds)` - - `genealogyMembers(genealogyId)`, `genealogyMemberOptions(genealogyId)`, `updateGenealogyMember`, `removeGenealogyMember`, `leaveGenealogy`, `transferGenealogyOwner` - -- [x] **Step 1: 写失败的路径和 body 白名单测试** - -```js -await client.createArticle('9007199254740993001', { - articleTitle: '族史', - articleContent: '

    正文

    ', - coverOssId: '9007199254740993002', - appUserId: 'must-drop', - status: '0' -}); -assert.deepEqual(calls[0].data, { - articleTitle: '族史', - articleContent: '

    正文

    ', - coverOssId: '9007199254740993002', - status: '0' -}); - -await client.replaceCeremonyInvitees( - '9007199254740993001', - '9007199254740993003', - ['9007199254740993004'] -); -assert.deepEqual(calls.at(-1).data, { - inviteeUserIds: ['9007199254740993004'] -}); -``` - -- [x] **Step 2: 运行 RED** - -Run: `node --test --test-name-pattern "article|album|ceremony admin|genealogy member" tests/api-client-contract.test.js` - -Expected: FAIL,原因是新方法不存在或仍只有删除方法。 - -- [x] **Step 3: 最小实现所有方法和白名单** - -```js -var ARTICLE_FIELDS = [ - 'categoryId', 'articleTitle', 'articleSummary', 'coverOssId', - 'articleContent', 'authorName', 'sortOrder', 'status' -]; -var ALBUM_FIELDS = ['albumName', 'albumDesc', 'coverOssId', 'sortOrder', 'status']; -var ALBUM_PHOTO_FIELDS = [ - 'ossId', 'photoTitle', 'photoDesc', 'photographer', - 'shootTime', 'sortOrder', 'status' -]; -var CEREMONY_FIELDS = [ - 'ceremonyType', 'ceremonyTitle', 'ceremonyDesc', 'ceremonyTime', - 'location', 'locationAddress', 'longitude', 'latitude', - 'coverOssId', 'sortOrder', 'status' -]; -var CEREMONY_GIFT_FIELDS = ['giverName', 'giftAmount', 'giftMessage']; -var MEMBER_UPDATE_FIELDS = ['memberName', 'relationName', 'roleType', 'lineagePersonId']; -``` - -每个方法只通过既有 `request()`、`pickDefined()`、`toRequiredPathId()` 和路径 builder 发请求;成员 options 按 YAML 不发送 Query。 - -- [x] **Step 4: 运行 GREEN 和语法检查** - -Run: `node --test tests/api-client-contract.test.js` - -Run: `node --check utils/ApiClient.js` - -Expected: PASS。 - ---- - -### Task 2: 谱文列表、详情、创建、修改和删除 - -**Files:** -- Create: `public/js/article-pages.js` -- Create: `tests/article-pages.test.js` -- Modify: `profile-article.html` -- Modify: `profile-article-edit.html` -- Modify: `tests/pending-pages.test.js` -- Modify: `tests/pc-scope.test.js` - -**Interfaces:** -- Consumes: Task 1 article ApiClient methods、`ProfileUI.requireGenealogyContext()`、`ProfileUpload.bindUploadField()`、`RichEditorPages` -- Produces: `buildArticleBody`, `normalizeArticle`, `normalizeArticles`, `matchesArticle`, `renderArticleList`, `renderArticleDetail`, `initArticleListPage`, `initArticleEditPage` - -- [x] **Step 1: 写失败的谱文行为测试** - -```js -assert.deepEqual(ArticlePages.buildArticleBody({ - articleTitle: '族史', - articleContent: '

    正文

    ', - coverOssId: '9007199254740993002', - categoryId: '', - status: '1' -}), { - articleTitle: '族史', - articleContent: '

    正文

    ', - coverOssId: '9007199254740993002', - status: '0' -}); -assert.equal( - ArticlePages.normalizeArticle({ articleId: Number.MAX_SAFE_INTEGER + 1 }), - null -); -assert.doesNotMatch( - ArticlePages.renderArticleDetail({ - articleId: '9007199254740993003', - articleTitle: '', - articleContent: '', - status: '0' - }), - /