feat(api): 完善家谱系统API客户端契约

- 实现家谱管理相关方法,包括创建、详情、概览、我的家谱和选项查询
- 添加家谱加入申请功能,支持申请、审核、取消和待审核列表操作
- 集成通知详情获取方法和通知ID安全验证机制
- 完善功德记录、谱文、相册、视频、祭祀活动的完整CRUD操作契约
- 实现家谱成员管理功能,包含成员列表、更新、移除和转让所有者操作
- 优化路径ID验证逻辑,拒绝不安全的数值ID并提供明确错误提示
- 更新测试用例以验证所有新增API方法的路径和请求体白名单机制
This commit is contained in:
fizzleaf
2026-07-29 16:57:27 +08:00
parent 59a72fb22b
commit fb1743aa2a
74 changed files with 13166 additions and 1320 deletions
+123 -21
View File
@@ -21,7 +21,7 @@
</nav>
<div class="nav-actions">
<a href="profile-family-home.html" data-genealogy-context-link>返回家谱主页</a
><a class="btn primary magnetic" href="#lineage-member-form">添加成员</a>
><a class="btn primary magnetic" href="#lineage-member-form" data-lineage-management hidden>添加成员</a>
</div>
</div>
</header>
@@ -53,7 +53,7 @@
<div class="module-main">
<section class="module-panel">
<h2>树谱操作</h2>
<div class="module-grid">
<div class="module-grid" data-lineage-management hidden>
<button class="module-card module-action-card tilt-card" type="button" data-lineage-relation="parents"
><span class="icon"></span>
<h3>添加父亲 / 母亲</h3>
@@ -91,8 +91,17 @@
</section>
<section class="module-panel">
<h2>成员搜索</h2>
<div class="lineage-toolbar">
<div class="lineage-toolbar lineage-filter-toolbar">
<input type="search" data-lineage-keyword placeholder="输入姓名、别名或人物编号搜索" />
<select data-lineage-generation-filter aria-label="按世代筛选">
<option value="">全部世代</option>
</select>
<select data-lineage-status-filter aria-label="按人物状态筛选">
<option value="">全部人物状态</option>
<option value="0">健在</option>
<option value="1">已故</option>
<option value="2">未知</option>
</select>
<button class="btn primary" type="button" data-lineage-search>搜索</button>
</div>
<p class="form-status" data-lineage-list-summary></p>
@@ -113,9 +122,40 @@
<div class="api-empty">请选择一个成员查看资料</div>
</div>
</section>
<section class="module-panel">
<h2>新增成员</h2>
<section class="module-panel" data-lineage-management hidden>
<h2>新增 / 编辑成员</h2>
<form id="lineage-member-form" class="editor-form" data-lineage-form>
<div class="editor-two">
<div class="editor-field">
<label for="lineage-binding-mode">账号绑定</label>
<select id="lineage-binding-mode" name="bindingMode" required>
<option value="NONE" selected>不绑定账号</option>
<option value="SELF">绑定当前登录账号</option>
<option value="SPECIFIED">指定其他账号</option>
</select>
<select name="appUserId" data-lineage-member-options hidden>
<option value="">请选择已绑定账号的家谱成员</option>
</select>
<p class="upload-status" data-lineage-binding-status>指定账号仅可从当前家谱正常成员中选择。</p>
</div>
<div class="editor-field">
<label for="lineage-avatar-file">头像</label>
<input id="lineage-avatar-oss-id" name="avatarOssId" type="hidden" />
<label class="upload-control" for="lineage-avatar-file">选择头像文件</label>
<input
id="lineage-avatar-file"
class="upload-input"
type="file"
accept="image/*"
data-upload-target="#lineage-avatar-oss-id"
data-upload-status="#lineage-avatar-status"
data-upload-mode="resumable"
data-upload-biz-type="avatar"
data-upload-usage-scene="lineage_avatar"
/>
<p id="lineage-avatar-status" class="upload-status">请选择头像文件</p>
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="lineage-person-name">姓名</label>
@@ -126,51 +166,112 @@
<select id="lineage-sex" name="sex">
<option value="0"></option>
<option value="1"></option>
<option value="2" selected>未知</option>
</select>
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="lineage-generation-no">世代序号</label>
<input id="lineage-generation-no" name="generation" type="number" min="1" placeholder="例如 3" />
<label for="lineage-generation-no">世代</label>
<select id="lineage-generation-no" name="generation" data-lineage-generation>
<option value="">请选择世代</option>
</select>
</div>
<div class="editor-field">
<label for="lineage-generation-name">字辈</label>
<input id="lineage-generation-name" name="generationName" type="text" placeholder="例如 忠" />
<input id="lineage-generation-name" name="generationName" type="text" readonly />
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="lineage-person-no">人物编号</label>
<input id="lineage-person-no" name="personNo" type="text" placeholder="可选" />
<input id="lineage-person-no" name="personNo" type="text" placeholder="可选;留空由系统生成" />
</div>
<div class="editor-field">
<label for="lineage-alias-name">别名</label>
<input id="lineage-alias-name" name="aliasName" type="text" placeholder="可选" />
</div>
</div>
<div class="editor-field">
<label for="lineage-sort-order">排序值</label>
<input id="lineage-sort-order" name="sortOrder" type="number" step="1" placeholder="可选,数值越小越靠前" />
</div>
<div class="editor-field">
<label for="lineage-relation-name">关系称谓</label>
<input id="lineage-relation-name" name="relationName" type="text" placeholder="添加配偶时可选,例如 妻、夫" />
<div class="editor-two">
<div class="editor-field">
<label for="lineage-father-id">父亲</label>
<select id="lineage-father-id" name="fatherId" data-lineage-parent-option data-empty-label="请选择父亲">
<option value="">请选择父亲</option>
</select>
</div>
<div class="editor-field">
<label for="lineage-mother-id">母亲</label>
<select id="lineage-mother-id" name="motherId" data-lineage-parent-option data-empty-label="请选择母亲">
<option value="">请选择母亲</option>
</select>
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="lineage-birth-date">出生日期</label>
<input id="lineage-birth-date" name="birthDate" type="date" />
<label for="lineage-birth-date">出生时间</label>
<input id="lineage-birth-date" name="birthDate" type="datetime-local" />
</div>
<div class="editor-field">
<label for="lineage-death-date">逝世日期</label>
<input id="lineage-death-date" name="deathDate" type="date" />
<label for="lineage-birth-lunar">出生历法</label>
<select id="lineage-birth-lunar" name="birthLunar">
<option value="0" selected>公历</option>
<option value="1">农历</option>
</select>
</div>
</div>
<div class="editor-field">
<label for="lineage-introduction">简介</label>
<label for="lineage-birth-place">出生地</label>
<input id="lineage-birth-place" name="birthPlace" type="text" placeholder="可选" />
</div>
<div class="editor-two">
<div class="editor-field">
<label for="lineage-death-date">逝世时间</label>
<input id="lineage-death-date" name="deathDate" type="datetime-local" />
</div>
<div class="editor-field">
<label for="lineage-death-lunar">逝世历法</label>
<select id="lineage-death-lunar" name="deathLunar">
<option value="0" selected>公历</option>
<option value="1">农历</option>
</select>
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="lineage-death-place">逝世地</label>
<input id="lineage-death-place" name="deathPlace" type="text" placeholder="可选" />
</div>
<div class="editor-field">
<label for="lineage-burial-place">安葬地</label>
<input id="lineage-burial-place" name="burialPlace" type="text" placeholder="可选" />
</div>
</div>
<div class="editor-two">
<div class="editor-field">
<label for="lineage-person-status">人物状态</label>
<select id="lineage-person-status" name="personStatus">
<option value="0">健在</option>
<option value="1">已故</option>
<option value="2" selected>未知</option>
</select>
</div>
<div class="editor-field">
<label for="lineage-sort-order">排序值</label>
<input id="lineage-sort-order" name="sortOrder" type="number" step="1" placeholder="可选,数值越小越靠前" />
</div>
</div>
<div class="editor-field" data-lineage-spouse-field hidden>
<label for="lineage-relation-name">配偶关系称谓</label>
<input id="lineage-relation-name" name="relationName" type="text" placeholder="可选,例如 妻、夫" />
</div>
<div class="editor-field">
<label for="lineage-introduction">人物简介</label>
<textarea id="lineage-introduction" name="biography" placeholder="记录成员生平简介"></textarea>
</div>
<div class="editor-field">
<label for="lineage-remark">备注</label>
<textarea id="lineage-remark" name="remark" placeholder="可选"></textarea>
</div>
<div class="bottom-actions">
<button class="btn primary" type="submit">保存成员</button>
<button class="btn ghost" type="reset">清空</button>
@@ -191,6 +292,7 @@
<script src="utils/axios.js"></script>
<script src="utils/AxiosRequestUtil.js"></script>
<script src="utils/ApiClient.js"></script>
<script src="public/js/upload-pages.js"></script>
<script src="public/js/lineage-pages.js"></script>
<script src="public/js/page-effects.js"></script>
</body>