Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dada0d978b | ||
|
|
fe3c6e5b58 |
@@ -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;该按钮与“批量修改状态”保持统一操作区间距;翻页或查询后选择清空,不选中其他页面数据 |
|
||||||
|
|||||||
@@ -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 200,12项相关服务均active,三条Redis Stream仍为`pending=0 / lag=0`,发布窗口无error级journal;全程未发送、补发、重投或重新入队短信。
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user