2 Commits
Author SHA1 Message Date
hectorzhao dada0d978b docs: record report status page deployment 2026-09-03 17:15:57 +08:00
hectorzhao fe3c6e5b58 fix: compact report status record table 2026-09-03 16:48:56 +08:00
5 changed files with 122 additions and 32 deletions
+1
View File
@@ -5018,6 +5018,7 @@ npm run verify:phase8
| TC-REPORT-WORKBENCH-008 | 短信通道管理进入报备详情 | 使用后端分页,默认按今日发送条数全量降序后分页;可按关键词、状态、运营商及今日发送区间查询;状态弹窗展示当前上下文和放弃风险 | | TC-REPORT-WORKBENCH-008 | 短信通道管理进入报备详情 | 使用后端分页,默认按今日发送条数全量降序后分页;可按关键词、状态、运营商及今日发送区间查询;状态弹窗展示当前上下文和放弃风险 |
| TC-REPORT-WORKBENCH-009 | 导入命中已有签名且用途列未映射或为空 | 识别为补资料;未提供字段保持原值,提供的动态字段覆盖同名值并追加新字段;用途不得被空字符串清空;审核前不改真实签名 | | TC-REPORT-WORKBENCH-009 | 导入命中已有签名且用途列未映射或为空 | 识别为补资料;未提供字段保持原值,提供的动态字段覆盖同名值并追加新字段;用途不得被空字符串清空;审核前不改真实签名 |
| TC-REPORT-WORKBENCH-010 | 在状态记录按批次、操作人、状态、入口、对象和时间搜索 | 返回真实状态记录及操作人;可追溯人工修改来源;分页、空数据、失败和历史无入口记录均正确展示 | | TC-REPORT-WORKBENCH-010 | 在状态记录按批次、操作人、状态、入口、对象和时间搜索 | 返回真实状态记录及操作人;可追溯人工修改来源;分页、空数据、失败和历史无入口记录均正确展示 |
| TC-REPORT-WORKBENCH-020 | 1600px大屏PC查看状态记录列表 | 列表合并展示“变更对象”、“变更动作”和“操作人 / 时间”,无需左右滚动;备注、修改入口等完整信息仍可在详情弹窗查看,搜索参数和真实API不变 |
| TC-REPORT-WORKBENCH-011 | 桌面及390px窄屏查看四页和状态弹窗 | 桌面表格可扫描;窄屏核心主体、状态、今日发送和操作可访问,无按钮遮挡;控制台无新增错误,所有业务数据来自真实API/PostgreSQL | | TC-REPORT-WORKBENCH-011 | 桌面及390px窄屏查看四页和状态弹窗 | 桌面表格可扫描;窄屏核心主体、状态、今日发送和操作可访问,无按钮遮挡;控制台无新增错误,所有业务数据来自真实API/PostgreSQL |
| TC-REPORT-WORKBENCH-012 | 查看企业签名表头和签名列表 | 签名列不显示升序、降序或其他排序按钮;列表不显示逐行待生成明细字段;默认按创建时间倒序 | | TC-REPORT-WORKBENCH-012 | 查看企业签名表头和签名列表 | 签名列不显示升序、降序或其他排序按钮;列表不显示逐行待生成明细字段;默认按创建时间倒序 |
| TC-REPORT-WORKBENCH-013 | 通道报备明细当前页有10条数据,点击右上角“全选当页” | 当前页10条全部选中,按钮变为“取消全选”,批量数为10;该按钮与“批量修改状态”保持统一操作区间距;翻页或查询后选择清空,不选中其他页面数据 | | TC-REPORT-WORKBENCH-013 | 通道报备明细当前页有10条数据,点击右上角“全选当页” | 当前页10条全部选中,按钮变为“取消全选”,批量数为10;该按钮与“批量修改状态”保持统一操作区间距;翻页或查询后选择清空,不选中其他页面数据 |
+9
View File
@@ -4405,3 +4405,12 @@ git diff --check
- 发布后API、Send Worker、Submit Outbox、Gateway Callback、Protocol Log Worker、Gateway、Security Agent、Nginx、PostgreSQL、Redis、MinIO和Prometheus均active;七项应用服务`NRestarts=0 / Result=success`API、Callback、Gateway健康且Redis PONG。发布窗口至稳定复核均无error级journal。 - 发布后API、Send Worker、Submit Outbox、Gateway Callback、Protocol Log Worker、Gateway、Security Agent、Nginx、PostgreSQL、Redis、MinIO和Prometheus均active;七项应用服务`NRestarts=0 / Result=success`API、Callback、Gateway健康且Redis PONG。发布窗口至稳定复核均无error级journal。
- 三条Redis Stream的发布前后组状态逐字一致;没有产生短信发送、补发、重投或重新入队事件。PostgreSQL只读基线为已通过且非待生成25条、已通过且`pendingReport=true`1条、已删除3条;未为自动审核或颜色验收修改任何签名、通道或客户配置。 - 三条Redis Stream的发布前后组状态逐字一致;没有产生短信发送、补发、重投或重新入队事件。PostgreSQL只读基线为已通过且非待生成25条、已通过且`pendingReport=true`1条、已删除3条;未为自动审核或颜色验收修改任何签名、通道或客户配置。
- 外部`12026`端口API健康HTTP 200;实际下载主资源`index-DESpdjJ1.js``index-D4ttoiTl.css`SHA-256分别为`6f612c92da38cecc2469bf1daf871918a541dee38e76fdb309edae94195d8ce4``f922d415d536224085b4eaebd3ee288833aa564ff7a8bfdc48ff0be0c584b70d`,与服务器产物一致。Playwright Chrome确认测试环境运营登录页HTTP 200、标题及登录按钮正确、控制台无错误;因没有有效平台管理员登录态,登录后的真实页面交互仍未冒充已验收。 - 外部`12026`端口API健康HTTP 200;实际下载主资源`index-DESpdjJ1.js``index-D4ttoiTl.css`SHA-256分别为`6f612c92da38cecc2469bf1daf871918a541dee38e76fdb309edae94195d8ce4``f922d415d536224085b4eaebd3ee288833aa564ff7a8bfdc48ff0be0c584b70d`,与服务器产物一致。Playwright Chrome确认测试环境运营登录页HTTP 200、标题及登录按钮正确、控制台无错误;因没有有效平台管理员登录态,登录后的真实页面交互仍未冒充已验收。
## 2026-09-03 状态记录列表精简(测试环境发布完成)
- 状态记录列表由11列精简为7列:保留报备任务号、通道名称、状态变化和操作,将报备类型与报备对象合并为“变更对象”,动作与修改入口合并为“变更动作”,操作人与时间合并展示;备注从列表移至既有详情弹窗,搜索条件继续支持按备注查询,未丢失历史数据入口。
- 表格采用固定列宽和省略展示,总列宽约1101px;大屏PC内容区可完整展示,窄于该宽度时仍保留容器滚动作为兼容兜底。详情弹窗继续展示任务、通道、类型、对象、动作、修改入口、状态前后、备注原因及状态历史。
- 定向组件6项、前端全量12文件57项、TypeScript、Vite生产构建、入口包体积预算和`git diff --check`均通过;定向ESLint为0错误,仅保留页面既有Hook依赖警告。新增功能用例`TC-REPORT-WORKBENCH-020`
- 本地生产预览连接真实本地API、PostgreSQL和Redis,运营端登录后读取11条真实状态记录;列表及详情交互正常,收起导航后1280px浏览器视口无需横向滚动且7列完整可见。未使用mock、静态数据或localStorage作为功能验收结论。
- 功能提交为`fe3c6e5b589cf475c611f5189e9243e657b5deac`,未推送远端。测试环境采用前端静态资源增量发布,没有执行数据库迁移,也没有重启API、Gateway、Worker、PostgreSQL、Redis或MinIO;恢复点为`/opt/cmpp-platform-backups/report-record-list-20260903T1654Z`
- 发布包大小2734330字节,SHA-256为`d2381d1b31cc49418f370b66131dd74e180ba4448c75f7d5795fcc787b48b2c8`;测试环境最终`.deployed-commit=fe3c6e5b589cf475c611f5189e9243e657b5deac`。内外API健康HTTP 20012项相关服务均active,三条Redis Stream仍为`pending=0 / lag=0`,发布窗口无error级journal;全程未发送、补发、重投或重新入队短信。
+29 -32
View File
@@ -192,68 +192,65 @@ export function AdminReportRecordsPage() {
{ {
key: 'task', key: 'task',
title: '报备任务号', title: '报备任务号',
width: '190px', width: '160px',
render: (record) => <strong className="admin-task-id">{record.taskId}</strong>, render: (record) => <strong className="admin-task-id admin-report-record-id" title={record.taskId}>{record.taskId}</strong>,
},
{ key: 'channel', title: '通道名称', width: '180px', render: (record) => record.channel?.name ?? '-' },
{
key: 'targetType',
title: '变更主体',
width: '110px',
render: (record) => (
<Tag tone={record.task?.reportType === 'drainage' ? 'info' : 'neutral'}>
{record.task?.reportType === 'drainage' ? '引流信息' : '签名'}
</Tag>
),
}, },
{ key: 'channel', title: '通道名称', width: '130px', render: (record) => record.channel?.name ?? '-' },
{ {
key: 'target', key: 'target',
title: '主体内容', title: '变更对象',
width: '260px', width: '230px',
render: (record) => render: (record) =>
record.task?.reportType === 'drainage' ? ( record.task?.reportType === 'drainage' ? (
<div className="admin-task-enterprise"> <div className="admin-task-enterprise admin-report-record-target">
<Tag tone="info"></Tag>
<strong>{record.task?.signature?.name ?? '-'}</strong> <strong>{record.task?.signature?.name ?? '-'}</strong>
<span>{record.task?.drainageInfo?.url ?? '-'}</span> <span>{record.task?.drainageInfo?.url ?? '-'}</span>
{record.task?.drainageInfo?.remark ? <span>{record.task.drainageInfo.remark}</span> : null}
</div> </div>
) : ( ) : (
<div className="admin-task-enterprise"> <div className="admin-task-enterprise admin-report-record-target">
<Tag tone="neutral"></Tag>
<strong>{record.task?.signature?.name ?? '-'}</strong> <strong>{record.task?.signature?.name ?? '-'}</strong>
{record.task?.signature?.purpose ? <span>{record.task.signature.purpose}</span> : null}
</div> </div>
), ),
}, },
{ key: 'source', title: '修改入口', width: '150px', render: (record) => recordSource(record) },
{ {
key: 'operator', key: 'action',
title: '操作人', title: '变更动作',
width: '140px', width: '180px',
render: (record) => record.operator?.displayName ?? record.operator?.username ?? '系统', render: (record) => (
<div className="admin-report-record-meta">
<strong>{actionLabel[record.action] ?? record.action}</strong>
<span>{recordSource(record)}</span>
</div>
),
}, },
{ key: 'action', title: '动作', width: '170px', render: (record) => actionLabel[record.action] ?? record.action },
{ {
key: 'status', key: 'status',
title: '状态变化', title: '状态变化',
width: '210px', width: '150px',
render: (record) => ( render: (record) => (
<Tag <Tag
tone={statusTone[record.statusAfter ?? 'pending'] ?? 'info'} tone={statusTone[record.statusAfter ?? 'pending'] ?? 'info'}
>{`${translateStatus(record.statusBefore)}${translateStatus(record.statusAfter)}`}</Tag> >{`${translateStatus(record.statusBefore)}${translateStatus(record.statusAfter)}`}</Tag>
), ),
}, },
{ key: 'time', title: '记录时间', width: '190px', render: (record) => record.createdAt ?? '-' },
{ {
key: 'reason', key: 'operatorTime',
title: '备注', title: '操作人 / 时间',
width: '320px', width: '175px',
render: (record) => <span className="ui-table__long-text">{record.reason ?? '-'}</span>, render: (record) => (
<div className="admin-report-record-meta">
<strong>{record.operator?.displayName ?? record.operator?.username ?? '系统'}</strong>
<span>{record.createdAt ?? '-'}</span>
</div>
),
}, },
{ {
key: 'actions', key: 'actions',
title: '操作', title: '操作',
align: 'right', align: 'right',
width: '120px', width: '76px',
render: (record) => ( render: (record) => (
<Button icon={<Eye size={14} />} onClick={() => setDetail(record)} size="sm" variant="ghost"> <Button icon={<Eye size={14} />} onClick={() => setDetail(record)} size="sm" variant="ghost">
@@ -4,6 +4,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import { AdminReportBatchesPage } from './AdminReportBatchesPage'; import { AdminReportBatchesPage } from './AdminReportBatchesPage';
import { AdminReportMaterialsPage } from './AdminReportMaterialsPage'; import { AdminReportMaterialsPage } from './AdminReportMaterialsPage';
import { AdminReportRecordsPage } from './AdminReportRecordsPage';
import { AdminReportTasksPage } from './AdminReportTasksPage'; import { AdminReportTasksPage } from './AdminReportTasksPage';
const { adminApi, clipboardWriteText } = vi.hoisted(() => ({ const { adminApi, clipboardWriteText } = vi.hoisted(() => ({
@@ -18,6 +19,7 @@ const { adminApi, clipboardWriteText } = vi.hoisted(() => ({
listReportDetailsPage: vi.fn(), listReportDetailsPage: vi.fn(),
listReportMaterialBatches: vi.fn(), listReportMaterialBatches: vi.fn(),
listReportMaterialBatchTasks: vi.fn(), listReportMaterialBatchTasks: vi.fn(),
listReportRecordsPage: vi.fn(),
preflightReportMaterialBatch: vi.fn(), preflightReportMaterialBatch: vi.fn(),
}, },
clipboardWriteText: vi.fn(), clipboardWriteText: vi.fn(),
@@ -171,6 +173,43 @@ describe('report workbench pages', () => {
); );
}); });
it('keeps the report record list compact while retaining full details in the dialog', async () => {
adminApi.listReportRecordsPage.mockResolvedValue({
items: [{
id: 'record-1',
taskId: 'report-task-with-a-long-identifier-1',
channelId: 'channel-1',
action: 'manual_status_change',
statusBefore: 'pending',
statusAfter: 'approved',
reason: '通道已确认报备通过',
sourceEntry: 'report_task',
createdAt: '2026-09-03 15:30:00',
channel: { id: 'channel-1', name: '测试通道' },
operator: { id: 'operator-1', username: 'operator', displayName: '运营一' },
task: task('record-1'),
}],
total: 1,
page: 1,
pageSize: 10,
});
render(
<MemoryRouter>
<AdminReportRecordsPage />
</MemoryRouter>,
);
expect(await screen.findByRole('columnheader', { name: '变更对象' })).toBeVisible();
expect(screen.getByRole('columnheader', { name: '变更动作' })).toBeVisible();
expect(screen.getByRole('columnheader', { name: '操作人 / 时间' })).toBeVisible();
expect(screen.queryByRole('columnheader', { name: '备注' })).not.toBeInTheDocument();
expect(screen.getByText('报备任务修改')).toBeVisible();
await userEvent.click(screen.getByRole('button', { name: '详情' }));
expect((await screen.findAllByText('通道已确认报备通过')).length).toBeGreaterThan(0);
});
it('shows and copies the channel brief returned by the batch detail API', async () => { it('shows and copies the channel brief returned by the batch detail API', async () => {
const batch = { const batch = {
id: 'batch-1', id: 'batch-1',
+44
View File
@@ -304,6 +304,50 @@
justify-content: flex-end; justify-content: flex-end;
} }
.report-record-page .report-task-table-card .ui-table {
min-width: 1101px;
table-layout: fixed;
}
.admin-report-record-id {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.admin-report-record-target .ui-tag {
justify-self: start;
}
.admin-report-record-target strong,
.admin-report-record-target span,
.admin-report-record-meta strong,
.admin-report-record-meta span {
overflow: hidden;
text-overflow: ellipsis;
}
.admin-report-record-target strong,
.admin-report-record-target span {
white-space: nowrap;
}
.admin-report-record-meta {
display: grid;
gap: var(--space-1);
}
.admin-report-record-meta strong {
color: var(--color-text-strong);
}
.admin-report-record-meta span {
color: var(--color-text-muted);
font-size: var(--font-size-xs);
white-space: nowrap;
}
.admin-task-table-card { .admin-task-table-card {
overflow: hidden; overflow: hidden;
padding: 0; padding: 0;