267 lines
12 KiB
HTML
267 lines
12 KiB
HTML
|
|
<!DOCTYPE html>
|
|||
|
|
<html lang="zh-CN">
|
|||
|
|
<head>
|
|||
|
|
<meta charset="UTF-8" />
|
|||
|
|
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
|||
|
|
<title>项目列表按产品分组:后端待确认事项与接口诉求</title>
|
|||
|
|
<style>
|
|||
|
|
:root {
|
|||
|
|
--bg: #f6f7f9;
|
|||
|
|
--panel: #ffffff;
|
|||
|
|
--border: #e5e7eb;
|
|||
|
|
--border-strong: #d1d5db;
|
|||
|
|
--text: #1f2937;
|
|||
|
|
--text-soft: #6b7280;
|
|||
|
|
--text-muted: #9ca3af;
|
|||
|
|
--primary: #2563eb;
|
|||
|
|
--primary-soft: #dbeafe;
|
|||
|
|
|
|||
|
|
--ok: #047857;
|
|||
|
|
--ok-bg: #d1fae5;
|
|||
|
|
--warn: #b45309;
|
|||
|
|
--warn-bg: #fef3c7;
|
|||
|
|
--bad: #b91c1c;
|
|||
|
|
--bad-bg: #fee2e2;
|
|||
|
|
|
|||
|
|
--code: #b45309;
|
|||
|
|
--code-bg: #f3f4f6;
|
|||
|
|
}
|
|||
|
|
* { box-sizing: border-box; }
|
|||
|
|
html, body { margin: 0; padding: 0; }
|
|||
|
|
body {
|
|||
|
|
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", "Segoe UI", sans-serif;
|
|||
|
|
background: var(--bg);
|
|||
|
|
color: var(--text);
|
|||
|
|
font-size: 14px;
|
|||
|
|
line-height: 1.7;
|
|||
|
|
}
|
|||
|
|
.wrap {
|
|||
|
|
max-width: 980px;
|
|||
|
|
margin: 0 auto;
|
|||
|
|
padding: 32px 28px 80px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.head { margin-bottom: 22px; }
|
|||
|
|
.head h1 { margin: 0 0 6px; font-size: 24px; font-weight: 700; line-height: 1.4; }
|
|||
|
|
.head .sub { color: var(--text-soft); font-size: 13px; }
|
|||
|
|
|
|||
|
|
section { margin-top: 28px; }
|
|||
|
|
section > h2 {
|
|||
|
|
font-size: 18px;
|
|||
|
|
font-weight: 700;
|
|||
|
|
margin: 0 0 14px;
|
|||
|
|
padding-bottom: 8px;
|
|||
|
|
border-bottom: 2px solid var(--border);
|
|||
|
|
}
|
|||
|
|
section h3 { font-size: 15px; font-weight: 700; margin: 18px 0 8px; }
|
|||
|
|
|
|||
|
|
.card {
|
|||
|
|
background: var(--panel);
|
|||
|
|
border: 1px solid var(--border);
|
|||
|
|
border-radius: 8px;
|
|||
|
|
padding: 18px 22px;
|
|||
|
|
margin-bottom: 14px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
table.cmp {
|
|||
|
|
width: 100%;
|
|||
|
|
border-collapse: collapse;
|
|||
|
|
background: var(--panel);
|
|||
|
|
border: 1px solid var(--border);
|
|||
|
|
border-radius: 8px;
|
|||
|
|
overflow: hidden;
|
|||
|
|
font-size: 13px;
|
|||
|
|
margin: 10px 0 14px;
|
|||
|
|
}
|
|||
|
|
table.cmp th, table.cmp td {
|
|||
|
|
border: 1px solid var(--border);
|
|||
|
|
padding: 8px 12px;
|
|||
|
|
text-align: left;
|
|||
|
|
vertical-align: top;
|
|||
|
|
}
|
|||
|
|
table.cmp th { background: #f9fafb; font-weight: 700; white-space: nowrap; }
|
|||
|
|
table.cmp td code, p code, li code {
|
|||
|
|
font-family: "JetBrains Mono", Consolas, monospace;
|
|||
|
|
font-size: 12.5px;
|
|||
|
|
color: var(--code);
|
|||
|
|
background: var(--code-bg);
|
|||
|
|
padding: 1px 5px;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
pre {
|
|||
|
|
background: #1f2937;
|
|||
|
|
color: #e5e7eb;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
padding: 14px 16px;
|
|||
|
|
overflow-x: auto;
|
|||
|
|
font-family: "JetBrains Mono", Consolas, monospace;
|
|||
|
|
font-size: 12.5px;
|
|||
|
|
line-height: 1.6;
|
|||
|
|
}
|
|||
|
|
pre .c { color: #9ca3af; }
|
|||
|
|
|
|||
|
|
.tag-ok, .tag-warn, .tag-bad {
|
|||
|
|
display: inline-block;
|
|||
|
|
font-size: 11px;
|
|||
|
|
font-weight: 700;
|
|||
|
|
border-radius: 999px;
|
|||
|
|
padding: 1px 10px;
|
|||
|
|
vertical-align: 1px;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
}
|
|||
|
|
.tag-ok { color: var(--ok); background: var(--ok-bg); }
|
|||
|
|
.tag-warn { color: var(--warn); background: var(--warn-bg); }
|
|||
|
|
.tag-bad { color: var(--bad); background: var(--bad-bg); }
|
|||
|
|
|
|||
|
|
ul, ol { margin: 8px 0; padding-left: 22px; }
|
|||
|
|
li { margin: 4px 0; }
|
|||
|
|
.note { color: var(--text-soft); font-size: 13px; }
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<div class="wrap">
|
|||
|
|
|
|||
|
|
<div class="head">
|
|||
|
|
<h1>项目列表按产品分组:后端待确认事项与接口诉求</h1>
|
|||
|
|
<div class="sub">2026-06-10 · 前端发起 · 涉及模块:project/project(项目)、system/dict(字典)</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<section>
|
|||
|
|
<h2>0. 背景</h2>
|
|||
|
|
<div class="card">
|
|||
|
|
<p>项目列表页将从"平铺分页"改为"按所属产品聚合展示":同一产品下的项目(基线/合同/技术支持等类型)需要聚拢在一起查看,不能被分页切散。现有 <code>GET /project/project/page</code> 平铺分页无法支撑该口径,需要后端补充以下能力。</p>
|
|||
|
|
<p>前端已按本文档第 2~4 节的契约<b>先行开发</b>(过渡期用现有平铺接口在前端聚合模拟),后端接口就绪后联调切换,<b>不阻塞后端排期</b>,但请反馈预计时间点。</p>
|
|||
|
|
</div>
|
|||
|
|
</section>
|
|||
|
|
|
|||
|
|
<section>
|
|||
|
|
<h2>1. 待查询确认:rdms_project_type 字典数据 <span class="tag-warn">请尽快回复,阻塞前端一处常量</span></h2>
|
|||
|
|
<div class="card">
|
|||
|
|
<p>请提供字典 <code>rdms_project_type</code>(项目类型)的<b>完整字典数据清单</b>(每项的 value / label / 启用状态),特别是:</p>
|
|||
|
|
<ul>
|
|||
|
|
<li><b>"基线"类型对应的字典 value 是什么</b>(前端需写入常量,用于"建立基线"入口预填与缺基线判定);</li>
|
|||
|
|
<li>除 基线 / 合同 / 技术支持 外是否还有其他在用类型值。</li>
|
|||
|
|
</ul>
|
|||
|
|
<p class="note">补充确认(口头已确认,烦请最终对齐):"基线项目每产品唯一"由后端在创建/更新项目时做唯一性校验兜底,前端仅做体验层提示。</p>
|
|||
|
|
</div>
|
|||
|
|
</section>
|
|||
|
|
|
|||
|
|
<section>
|
|||
|
|
<h2>2. 接口诉求一:项目按产品分组分页查询(新增) <span class="tag-bad">核心依赖</span></h2>
|
|||
|
|
|
|||
|
|
<h3>2.1 建议路径</h3>
|
|||
|
|
<div class="card">
|
|||
|
|
<p><code>GET /project/project/group-page</code>(路径可由后端按规范调整,前端只在 service 层适配一处)</p>
|
|||
|
|
<p>数据权限口径与现有 <code>/project/project/page</code> 一致:只返回当前用户可见的项目,按其归属产品聚合。</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<h3>2.2 入参</h3>
|
|||
|
|
<table class="cmp">
|
|||
|
|
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
|
|||
|
|
<tr><td><code>pageNo</code> / <code>pageSize</code></td><td>int</td><td>是</td><td><b>产品组维度</b>分页:一页返回 pageSize 个产品组(含组内项目),不是项目行分页。前端当前 pageSize=10。</td></tr>
|
|||
|
|
<tr><td><code>keyword</code></td><td>string</td><td>否</td><td>项目编码 / 名称模糊匹配(口径同现有 page 接口)。</td></tr>
|
|||
|
|
<tr><td><code>productId</code></td><td>string</td><td>否</td><td>限定单个产品。</td></tr>
|
|||
|
|
<tr><td><code>projectType</code></td><td>string</td><td>否</td><td>项目类型字典 value。</td></tr>
|
|||
|
|
<tr><td><code>statusCode</code></td><td>string</td><td>否</td><td>单状态过滤。<b>不传 = "全部"口径:pending / active / paused / completed 四种状态,不含 cancelled / archived。</b></td></tr>
|
|||
|
|
<tr><td><code>orphanOnly</code></td><td>boolean</td><td>否</td><td>true = 仅返回"游离组"(productId 为空的项目聚合为一个特殊组)。</td></tr>
|
|||
|
|
<tr><td><code>topN</code></td><td>int</td><td>否</td><td>每组返回的项目条数上限,建议默认 5(前端当前固定取 5)。</td></tr>
|
|||
|
|
</table>
|
|||
|
|
|
|||
|
|
<h3>2.3 返回结构(示例)</h3>
|
|||
|
|
<pre>{
|
|||
|
|
"total": 12, <span class="c">// 当前筛选口径下产品组总数(分页 total,含游离组)</span>
|
|||
|
|
"projectTotal": 47, <span class="c">// 当前筛选口径下项目总数</span>
|
|||
|
|
"directionCount": 3, <span class="c">// 可见产品去重后的方向(directionCode)数</span>
|
|||
|
|
"orphanTotal": 3, <span class="c">// 游离项目数(productId 为空)</span>
|
|||
|
|
"list": [
|
|||
|
|
{
|
|||
|
|
"productId": "1923456789012345678", <span class="c">// 字符串!见 4.1</span>
|
|||
|
|
"productName": "智能网关",
|
|||
|
|
"productCode": "RDMS-P-001",
|
|||
|
|
"directionCode": "platform",
|
|||
|
|
"managerUserId": "1001",
|
|||
|
|
"managerUserNickname": "张三",
|
|||
|
|
"projectTotal": 6, <span class="c">// 当前筛选口径下该组项目总数</span>
|
|||
|
|
"projects": [ /* 前 topN 条,字段同现有 page 接口的项目行 */ ],
|
|||
|
|
"typeCounts": { "baseline值": 1, "合同值": 3, "技术支持值": 2 },
|
|||
|
|
"hasBaseline": true,
|
|||
|
|
"orphan": false
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"productId": null, <span class="c">// 游离组:未挂产品的项目聚合</span>
|
|||
|
|
"productName": "游离项目",
|
|||
|
|
"productCode": null,
|
|||
|
|
"directionCode": "",
|
|||
|
|
"managerUserId": null,
|
|||
|
|
"managerUserNickname": null,
|
|||
|
|
"projectTotal": 3,
|
|||
|
|
"projects": [ /* ... */ ],
|
|||
|
|
"typeCounts": { "合同值": 2, "技术支持值": 1 },
|
|||
|
|
"hasBaseline": false,
|
|||
|
|
"orphan": true
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}</pre>
|
|||
|
|
|
|||
|
|
<h3>2.4 口径约定(关键,请逐条确认)</h3>
|
|||
|
|
<table class="cmp">
|
|||
|
|
<tr><th>#</th><th>约定</th><th>说明</th></tr>
|
|||
|
|
<tr><td>1</td><td>组内项目排序</td><td>按 <code>updateTime</code> 倒序,返回前 topN 条;<code>projectTotal</code> 为该口径下组内全量计数。</td></tr>
|
|||
|
|
<tr><td>2</td><td>无命中组不返回</td><td>传了 <code>statusCode</code> / <code>keyword</code> / <code>projectType</code> 任一筛选时,组内无命中项目的产品组不返回。</td></tr>
|
|||
|
|
<tr><td>3</td><td>零项目产品组</td><td>"全部"口径(statusCode 缺省)且无 keyword / projectType 筛选时,<b>返回当前用户可见、状态为 active / paused 的零项目产品组</b>(projectTotal=0、projects=[]),用于"该产品暂无项目"占位与新增引导。</td></tr>
|
|||
|
|
<tr><td>4</td><td>typeCounts / hasBaseline 统计口径</td><td>这两个字段是"产品属性",<b>恒按"全部"口径(四种状态)统计</b>,不随 statusCode 入参变化——避免"基线项目已完成时被误判为缺基线"。</td></tr>
|
|||
|
|
<tr><td>5</td><td>游离组位置</td><td>游离组(productId 为空)作为列表中<b>最后一个组</b>返回;状态筛选下同样适用约定 2(无命中则不返回该组)。</td></tr>
|
|||
|
|
<tr><td>6</td><td>组的排序</td><td>同方向(directionCode)的产品组相邻返回(方向间顺序、方向内产品顺序由后端按现有产品列表默认排序即可)。</td></tr>
|
|||
|
|
</table>
|
|||
|
|
|
|||
|
|
<h3>2.5 组内"展开剩余"的数据来源</h3>
|
|||
|
|
<div class="card">
|
|||
|
|
<p>不需要新接口:前端用现有 <code>GET /project/project/page</code> 按 <code>productId + statusCode</code> 拉取该组剩余项目。前提是该接口的排序与本接口组内排序一致(updateTime 倒序)——若现有 page 接口默认排序不是 updateTime 倒序,请告知或支持排序参数。</p>
|
|||
|
|
</div>
|
|||
|
|
</section>
|
|||
|
|
|
|||
|
|
<section>
|
|||
|
|
<h2>3. 接口诉求二、三:存量接口小改 <span class="tag-warn">两处补充</span></h2>
|
|||
|
|
|
|||
|
|
<h3>3.1 平铺分页支持"未挂产品"筛选</h3>
|
|||
|
|
<div class="card">
|
|||
|
|
<p><code>GET /project/project/page</code> 需要能表达 <b>productId 为空</b>的筛选语义(如新增 <code>orphanOnly=true</code> 参数,或约定 productId 传特殊值),用于查询"游离项目"(未关联任何产品的项目)。具体参数形式由后端定,前端适配。</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<h3>3.2 概览统计补游离计数</h3>
|
|||
|
|
<div class="card">
|
|||
|
|
<p><code>GET /project/project/overview-summary</code> 返回体增加:</p>
|
|||
|
|
<pre>{
|
|||
|
|
"statusCounts": { "active": 10, "pending": 2, ... }, <span class="c">// 现有</span>
|
|||
|
|
"orphanCount": 3 <span class="c">// 新增:未挂产品的项目数(按"全部"口径:四种状态)</span>
|
|||
|
|
}</pre>
|
|||
|
|
</div>
|
|||
|
|
</section>
|
|||
|
|
|
|||
|
|
<section>
|
|||
|
|
<h2>4. 公共约定</h2>
|
|||
|
|
<table class="cmp">
|
|||
|
|
<tr><th>#</th><th>约定</th><th>说明</th></tr>
|
|||
|
|
<tr><td>4.1</td><td>ID 一律字符串 <span class="tag-bad">必须</span></td><td>所有 Long / 雪花 ID(productId、managerUserId、项目 id 等)在 JSON 中<b>按字符串返回</b>。Long 直接作为 JSON 数字返回会在前端丢精度。</td></tr>
|
|||
|
|
<tr><td>4.2</td><td>"全部"状态集合</td><td>pending / active / paused / completed;cancelled / archived 仅在显式传对应 statusCode 时返回。</td></tr>
|
|||
|
|
<tr><td>4.3</td><td>项目行字段</td><td>分组接口 <code>projects[]</code> 内的项目对象字段与现有 <code>/project/project/page</code> 返回行保持一致(含 productId / productName / managerUserNickname / progressRate / statusCode / updateTime 等),避免前端双口径。</td></tr>
|
|||
|
|
</table>
|
|||
|
|
</section>
|
|||
|
|
|
|||
|
|
<section>
|
|||
|
|
<h2>5. 需要后端反馈的清单</h2>
|
|||
|
|
<div class="card">
|
|||
|
|
<ol>
|
|||
|
|
<li><b>第 1 节</b>:<code>rdms_project_type</code> 字典数据清单 + "基线"的 value(<span class="tag-warn">最优先</span>,一条查询即可,阻塞前端一处常量)。</li>
|
|||
|
|
<li><b>第 2 节</b>:分组分页接口的可行性确认 + 2.4 六条口径逐条确认(有异议请直接批注替代方案)+ 排期。</li>
|
|||
|
|
<li><b>第 3 节</b>:page 接口游离筛选的参数形式 + overview-summary 补 orphanCount 的排期。</li>
|
|||
|
|
<li><b>第 2.5 节</b>:现有 page 接口的默认排序是什么;是否支持/计划支持按 updateTime 倒序。</li>
|
|||
|
|
</ol>
|
|||
|
|
</div>
|
|||
|
|
</section>
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
</body>
|
|||
|
|
</html>
|