docs(api): 删除关心人功能和成员列表接口文档

- 移除关心人功能API接口文档文件
- 移除成员列表接口变更前端对接说明文档
- 清理相关HTML格式的API文档文件
This commit is contained in:
2026-05-14 14:12:35 +08:00
parent 59b73f3dae
commit 960fe805ec
2 changed files with 0 additions and 1898 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,754 +0,0 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>成员列表接口变更 — 前端对接说明</title>
<style>
:root {
--fg: #1f2328;
--fg-muted: #57606a;
--bg: #ffffff;
--bg-soft: #f6f8fa;
--border: #d0d7de;
--border-soft: #e7ecf2;
--accent: #0969da;
--accent-soft: #ddf4ff;
--warn: #9a6700;
--warn-soft: #fff8c5;
--danger: #cf222e;
--danger-soft: #ffebe9;
--ok: #1a7f37;
--ok-soft: #dafbe1;
--code-bg: #f6f8fa;
}
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
background: var(--bg);
color: var(--fg);
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', 'Hiragino Sans GB',
'Source Han Sans CN', 'Noto Sans CJK SC', Helvetica, Arial, sans-serif;
font-size: 15px;
line-height: 1.75;
}
.wrap {
max-width: 1000px;
margin: 0 auto;
padding: 48px 32px 96px;
}
header.doc-header {
border-bottom: 1px solid var(--border);
padding-bottom: 16px;
margin-bottom: 24px;
}
header.doc-header h1 {
margin: 0 0 8px;
font-size: 28px;
}
header.doc-header .meta {
color: var(--fg-muted);
font-size: 13px;
margin: 0;
}
h2 {
margin: 40px 0 12px;
padding-bottom: 6px;
border-bottom: 1px solid var(--border-soft);
font-size: 22px;
}
h3 {
margin: 28px 0 8px;
font-size: 17px;
}
h4 {
margin: 20px 0 6px;
font-size: 15px;
}
p {
margin: 8px 0;
}
ul,
ol {
margin: 8px 0;
padding-left: 24px;
}
ul li,
ol li {
margin: 3px 0;
}
code,
pre {
font-family: 'JetBrains Mono', Menlo, Consolas, 'Courier New', monospace;
font-size: 13px;
}
code {
background: var(--code-bg);
padding: 1px 6px;
border-radius: 4px;
border: 1px solid var(--border-soft);
}
pre {
background: var(--code-bg);
border: 1px solid var(--border-soft);
border-radius: 6px;
padding: 14px 16px;
overflow-x: auto;
line-height: 1.55;
}
pre code {
background: transparent;
border: 0;
padding: 0;
}
table {
border-collapse: collapse;
width: 100%;
margin: 12px 0;
font-size: 13px;
}
th,
td {
border: 1px solid var(--border);
padding: 7px 10px;
text-align: left;
vertical-align: top;
}
th {
background: var(--bg-soft);
font-weight: 600;
}
tr:nth-child(2n) td {
background: #fafbfc;
}
.callout {
border-left: 4px solid var(--accent);
background: var(--accent-soft);
padding: 10px 14px;
border-radius: 4px;
margin: 12px 0;
}
.callout.warn {
border-left-color: var(--warn);
background: var(--warn-soft);
}
.callout.danger {
border-left-color: var(--danger);
background: var(--danger-soft);
}
.callout.ok {
border-left-color: var(--ok);
background: var(--ok-soft);
}
.callout .title {
font-weight: 600;
margin-bottom: 4px;
}
.endpoint {
border: 1px solid var(--border);
border-radius: 8px;
padding: 16px 20px;
margin: 14px 0;
background: var(--bg);
}
.method {
display: inline-block;
padding: 3px 12px;
border-radius: 4px;
font-family: 'JetBrains Mono', Menlo, Consolas, monospace;
font-size: 12px;
font-weight: 700;
margin-right: 10px;
}
.method.get {
background: #0969da;
color: white;
}
.badge {
display: inline-block;
padding: 1px 8px;
border-radius: 999px;
font-size: 12px;
font-weight: 500;
border: 1px solid var(--border);
}
.badge.new {
background: var(--ok-soft);
color: var(--ok);
border-color: #aceeb6;
}
.badge.keep {
background: var(--bg-soft);
color: var(--fg-muted);
}
.compare {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin: 12px 0;
}
.compare > div {
border: 1px solid var(--border);
border-radius: 6px;
padding: 12px 14px;
}
.compare .before {
border-top: 3px solid var(--warn);
}
.compare .after {
border-top: 3px solid var(--ok);
}
.compare h4 {
margin-top: 0;
}
hr {
border: 0;
border-top: 1px solid var(--border-soft);
margin: 32px 0;
}
.toc {
background: var(--bg-soft);
border: 1px solid var(--border-soft);
border-radius: 6px;
padding: 14px 20px;
margin: 16px 0 24px;
}
.toc ol {
margin: 4px 0;
}
.toc a {
color: var(--accent);
text-decoration: none;
}
.toc a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="wrap">
<header class="doc-header">
<h1>成员列表接口变更 — 前端对接说明</h1>
<p class="meta">变更日期 2026-05-14 · 后端已就绪 · 前端需要配合调整渲染逻辑</p>
</header>
<div class="callout">
<div class="title">一句话总结</div>
<strong>产品 / 项目成员列表接口</strong>
现在按"
<strong>一人一行</strong>
"返回,同一用户的多个角色合并到主角色行,其他角色名通过新增的
<code>additionalRoleNames: string[]</code>
字段返回。前端只需要把这个数组拼到角色名旁边显示即可。
</div>
<div class="toc">
<strong>目录</strong>
<ol>
<li><a href="#sec-1">背景:为什么改</a></li>
<li><a href="#sec-2">受影响的接口(仅 2 个)</a></li>
<li><a href="#sec-3">改动前后对比</a></li>
<li><a href="#sec-4">新增字段 additionalRoleNames</a></li>
<li><a href="#sec-5">前端渲染建议</a></li>
<li><a href="#sec-6">边界场景</a></li>
<li><a href="#sec-7">不受影响的接口</a></li>
<li><a href="#sec-8">前端落地 checklist</a></li>
</ol>
</div>
<!-- ====== 1 ====== -->
<h2 id="sec-1">1. 背景:为什么改</h2>
<p>
多角色改造后,
<strong>
产品/项目的"创建者"角色会自动落一条
<code>rdms_user_object_role</code>
</strong>
。当
<strong>创建者本人就是负责人</strong>
时,同一用户在同一对象内会出现两条 ACTIVE 记录:
</p>
<ul>
<li>
一条
<code>product_manager</code>
/
<code>project_manager</code>
</li>
<li>
一条
<code>product_creator</code>
/
<code>project_creator</code>
</li>
</ul>
<p>
如果后端原样返回,前端列表会出现"同一个人重复两行"。讨论后约定:后端在
<strong>列表接口</strong>
层做合并展示,
<strong>不影响</strong>
底层数据(每个角色行仍独立存在,便于后续可能的"按角色单独操作")。
</p>
<div class="callout warn">
<div class="title">业务边界(重要)</div>
当前业务上
<strong>
只有
<code>creator + manager</code>
这一种组合
</strong>
会让同人多角色出现。其他角色(产品专员、开发等)仍是一人一角色。所以前端可以放心按"主角色 + 附加角色名"
的方式渲染,附加列表通常是 0 或 1 个元素。
</div>
<!-- ====== 2 ====== -->
<h2 id="sec-2">2. 受影响的接口(仅 2 个)</h2>
<div class="endpoint">
<span class="method get">GET</span>
<code>/admin-api/project/product/{productId}/members</code>
<p style="color: var(--fg-muted); font-size: 13px; margin: 6px 0 0">产品团队成员列表</p>
</div>
<div class="endpoint">
<span class="method get">GET</span>
<code>/admin-api/project/project/{projectId}/members</code>
<p style="color: var(--fg-muted); font-size: 13px; margin: 6px 0 0">项目团队成员列表</p>
</div>
<p>
响应结构两个接口对称,本文档下文统一用
<code>RespVO</code>
表示,字段路径一致。
</p>
<!-- ====== 3 ====== -->
<h2 id="sec-3">3. 改动前后对比</h2>
<p>
假设:产品 P 里
<code>用户A</code>
是产品经理(同时创建了该产品,所以也有
<code>product_creator</code>
角色),
<code>用户B</code>
是产品专员,
<code>用户C</code>
是已退场的历史成员。
</p>
<div class="compare">
<div class="before">
<h4>改之前数据原样返回4 行)</h4>
<pre><code>[
{ id: "100", userId: "A", roleCode: "product_manager",
roleName: "产品经理", status: 0, ... },
{ id: "101", userId: "A", roleCode: "product_creator",
roleName: "产品创建者", status: 0, ... },
{ id: "102", userId: "B", roleCode: "product_specialist",
roleName: "产品专员", status: 0, ... },
{ id: "103", userId: "C", roleCode: "product_specialist",
roleName: "产品专员", status: 1, leftTime: "..." }
]</code></pre>
<p style="color: var(--fg-muted); font-size: 12px">用户 A 出现 2 次 — 前端要么重复显示,要么自己去重。</p>
</div>
<div class="after">
<h4>改之后合并后3 行)</h4>
<pre><code>[
{ id: "100", userId: "A", roleCode: "product_manager",
roleName: "产品经理",
additionalRoleNames: ["产品创建者"], // ← 新增字段
status: 0, ... },
{ id: "102", userId: "B", roleCode: "product_specialist",
roleName: "产品专员",
additionalRoleNames: [],
status: 0, ... },
{ id: "103", userId: "C", roleCode: "product_specialist",
roleName: "产品专员",
additionalRoleNames: [],
status: 1, leftTime: "..." }
]</code></pre>
<p style="color: var(--fg-muted); font-size: 12px">
用户 A 1 行 — 主行 managercreator 名字进
<code>additionalRoleNames</code>
</p>
</div>
</div>
<h3>合并规则</h3>
<table>
<tr>
<th>规则</th>
<th>说明</th>
</tr>
<tr>
<td>仅合并 ACTIVE 行</td>
<td>
<code>status = 0</code>
的多角色行才聚合;
<code>status = 1</code>
的历史 INACTIVE 行
<strong>保持每条独立成行</strong>
(历史角色留痕,便于审计)
</td>
</tr>
<tr>
<td>主角色选择</td>
<td>
同人多角色时,
<code>product_manager</code>
/
<code>project_manager</code>
角色行优先做主;不在则按
<code>roleId</code>
升序选第一条(理论不该走到这条兜底)
</td>
</tr>
<tr>
<td>主行字段</td>
<td>
<code>id</code>
/
<code>roleId</code>
/
<code>roleCode</code>
/
<code>roleName</code>
/
<code>joinedTime</code>
/
<code>leftTime</code>
/
<code>remark</code>
等都按主角色行的值返回
</td>
</tr>
<tr>
<td>非主角色名顺序</td>
<td>
<code>additionalRoleNames</code>
按角色中文名字典序升序,前端可以直接顺序渲染
</td>
</tr>
</table>
<!-- ====== 4 ====== -->
<h2 id="sec-4">
4. 新增字段
<code>additionalRoleNames</code>
</h2>
<table>
<tr>
<th>字段名</th>
<th>类型</th>
<th>状态</th>
<th>说明</th>
</tr>
<tr>
<td><code>additionalRoleNames</code></td>
<td><code>string[]</code></td>
<td><span class="badge new">本次新增</span></td>
<td>
非主角色的中文名列表,多角色场景使用;
<strong>
单角色时为空数组
<code>[]</code>
</strong>
,前端可以放心
<code>length</code>
判空
</td>
</tr>
</table>
<p>
所有
<strong>原有字段保持不变</strong>
<code>id</code>
<code>userId</code>
<code>userNickname</code>
<code>roleId</code>
<code>roleName</code>
<code>roleCode</code>
<code>managerFlag</code>
<code>status</code>
<code>joinedTime</code>
<code>leftTime</code>
<code>remark</code>
),前端原有代码不会因为字段消失而报错。
</p>
<!-- ====== 5 ====== -->
<h2 id="sec-5">5. 前端渲染建议</h2>
<h3>5.1 最简单的展示方式 — 拼成一个字符串</h3>
<pre><code>const displayRoleName = additionalRoleNames?.length
? `${roleName} + ${additionalRoleNames.join(', ')}`
: roleName;
//
// 例roleName="产品经理", additionalRoleNames=["产品创建者"]
// → "产品经理 + 产品创建者"
//
// 例roleName="产品专员", additionalRoleNames=[]
// → "产品专员"</code></pre>
<h3>5.2 更友好的展示 — 主角色 + 浅色 chip 标签</h3>
<pre><code>&lt;span class="role-main"&gt;{{ roleName }}&lt;/span&gt;
&lt;span v-for="extra in additionalRoleNames" :key="extra" class="role-tag"&gt;
{{ extra }}
&lt;/span&gt;
//
// CSSrole-tag 设计成浅色 background + 小圆角,跟主角色拉开视觉层级</code></pre>
<h3>5.3 表格单元格示意</h3>
<pre><code>| 用户 | 角色 | 状态 |
|-----------|---------------------------|------|
| 灿能管理 | 产品经理 [产品创建者] | 有效 |
| 洪圣文 | 产品专员 | 有效 |
| 李凡 | 游客 | 历史 |</code></pre>
<!-- ====== 6 ====== -->
<h2 id="sec-6">6. 边界场景</h2>
<table>
<tr>
<th>场景</th>
<th>预期返回</th>
<th>前端展示</th>
</tr>
<tr>
<td>用户 A 仅是产品经理(不是创建者)</td>
<td>
1 行,
<code>additionalRoleNames=[]</code>
</td>
<td>"产品经理"</td>
</tr>
<tr>
<td>用户 A 同时是产品经理 + 创建者</td>
<td>
1 行(主行 manager
<code>additionalRoleNames=["产品创建者"]</code>
</td>
<td>"产品经理 + 产品创建者"</td>
</tr>
<tr>
<td>用户 A 仅是创建者(罕见,理论上创建后立即把 manager 转给别人才会出现)</td>
<td>
1 行,主行就是 creator
<code>additionalRoleNames=[]</code>
</td>
<td>"产品创建者"</td>
</tr>
<tr>
<td>用户 A 是产品专员(单角色非 manager</td>
<td>
1 行,
<code>additionalRoleNames=[]</code>
</td>
<td>"产品专员"</td>
</tr>
<tr>
<td>用户 A 退场status=1 INACTIVE 历史行)</td>
<td>
每条 INACTIVE 行独立 1 行,
<code>additionalRoleNames=[]</code>
</td>
<td>"产品专员(已退场)"等灰显</td>
</tr>
<tr>
<td>
用户 A 同时有
<strong>历史失效</strong>
的角色行 +
<strong>当前生效</strong>
的角色行
</td>
<td>ACTIVE 行合并 1 行 + 每条 INACTIVE 行各占 1 行;同用户在列表里会出现多次(不同 status</td>
<td>分别用"有效 / 历史"区分;不要再二次合并</td>
</tr>
</table>
<!-- ====== 7 ====== -->
<h2 id="sec-7">7. 不受影响的接口</h2>
<p>
以下接口
<strong>不变</strong>
,前端原有调用方式保持:
</p>
<table>
<tr>
<th>接口</th>
<th>说明</th>
</tr>
<tr>
<td>
<code>POST /admin-api/project/product/{productId}/members</code>
<br />
<code>POST /admin-api/project/project/{projectId}/members</code>
</td>
<td>
新增成员 — 仍按"一个角色一条记录"操作,传
<code>userId + roleId</code>
</td>
</tr>
<tr>
<td><code>PUT /admin-api/project/.../members/{memberId}</code></td>
<td>
更新成员 — 按
<code>memberId</code>
(即
<code>rdms_user_object_role.id</code>
)定位具体角色行操作
</td>
</tr>
<tr>
<td><code>PUT /admin-api/project/.../members/{memberId}/inactive</code></td>
<td>
失效成员 — 同上,按
<code>memberId</code>
操作
</td>
</tr>
<tr>
<td>
<code>GET /admin-api/project/product/{productId}/context</code>
<br />
<code>GET /admin-api/project/project/{projectId}/context</code>
</td>
<td>
对象上下文 —
<code>currentRole.additionalRoleNames</code>
字段早已存在,
<strong>不在本次变更范围</strong>
</td>
</tr>
</table>
<div class="callout warn">
<div class="title">特别提醒 — 编辑 / 失效操作</div>
当用户 A 同时是 manager + creator合并展示成 1 行)时,前端如果允许"编辑这一行的角色"或"踢出",传的
<code>memberId</code>
<strong>
主角色行的
<code>id</code>
</strong>
(也就是 manager 那条)。
<strong>不会影响</strong>
同人的 creator 角色行 — creator 角色仍保留。
<br />
<br />
这是符合设计的行为creator 是"留痕"角色,原则上不应该被踢出。如果业务上要踢出整个人,需要前端先调一次 list
接口拿到该用户的所有 active 角色行(注意 — 合并后的 list 里看不到 creator 那条的
<code>id</code>
),后续
<strong>是否要单独提供"列出某 user 所有角色行"接口</strong>
取决于业务实际诉求,目前没有这个接口。
</div>
<!-- ====== 8 ====== -->
<h2 id="sec-8">8. 前端落地 checklist</h2>
<div class="callout ok">
<ul style="margin: 6px 0">
<li>
✅ 把
<code>additionalRoleNames</code>
数组加到 TypeScript 类型定义ProductMemberRespVO / ProjectMemberRespVO 两处)
</li>
<li>
✅ 列表渲染逻辑:把
<code>additionalRoleNames</code>
拼到
<code>roleName</code>
旁边显示(字符串或 chip 标签均可)
</li>
<li>
✅ 验证:找一个"创建者 = 经理"的产品/项目,确认列表里这个人只出现
<strong>1 行</strong>
,且能看到"产品经理 + 产品创建者"字样
</li>
<li>
✅ 验证:找一个普通成员(产品专员等),
<code>additionalRoleNames</code>
应该是
<code>[]</code>
,不影响展示
</li>
<li>
✅ 验证:历史退场成员(
<code>status=1</code>
),仍按原方式各占一行
</li>
<li>
⏸ 编辑/失效操作 — 当前不动,仍按
<code>memberId</code>
操作主角色行;若业务需要"踢人整体",后端再补接口
</li>
</ul>
</div>
<hr />
<h3>变更影响最小范围说明</h3>
<p style="color: var(--fg-muted); font-size: 13px">
本次后端改动仅在
<code>ProductMemberServiceImpl.getProductMemberList</code>
<code>ProjectMemberServiceImpl.getProjectMemberList</code>
两个方法中实现,
<strong>不修改底层数据</strong>
<code>rdms_user_object_role</code>
仍按一行一角色存储)。即使前端暂时不读
<code>additionalRoleNames</code>
字段,也只是看不到"+ 产品创建者"字样,不会出现数据错误或重复行问题。
</p>
</div>
</body>
</html>