后台游戏管理与审核页排版修复,安全下架入口收敛到游戏管理页
- 游戏管理页:下架改为独立面板(原因必填、校验提示、仍走二次确认),操作列与作者列排版调整,gameId 省略号加 title - 游戏管理页测试:拆成空原因不提交、面板取消不请求、面板确认后仍需二次确认三条 - 审核页:移除行内游戏级安全下架及其状态与二次确认接线,理由弹窗只服务拒绝,gameId 截断加 title - 审核页测试:删除两条安全下架用例,替换为行内无该动作、microsZ 时间列渲染、窄视口 gameId 截断 - 新增 adminGameDistributionReview.css:审核页专属列宽与动作列 nowrap,含 1366/980 两档断点 - admin-web admin.css:新增游戏管理表格 5 条排版规则 - shared AdminFormat:新增 normalizeAdminTimestamp,兼容后端 seconds.microsZ 时间口径 - shared AdminFormat 测试:新增 5 条覆盖微秒口径、补零、ISO 与 epoch 兼容、非法值回落 - shared admin.css:admin-status 改为 nowrap - 文档:后台游戏审核详情计划加取代标注,说明审核页只承载版本级动作
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { formatAdminDate, formatAdminDateTime } from './AdminFormat';
|
||||
|
||||
describe('formatAdminDateTime', () => {
|
||||
test('把后端的 seconds.microsZ 口径当成同一时刻渲染', () => {
|
||||
// 游戏分发后台接口下发的是 shared-kernel `format_timestamp_micros` 的
|
||||
// `seconds.microsZ` 文本(非 ISO 8601),必须与等价 ISO 时刻渲染一致。
|
||||
const microsText = '1791008678.817257Z';
|
||||
const equivalentIso = new Date(1791008678 * 1000 + 817).toISOString();
|
||||
|
||||
expect(formatAdminDateTime(microsText)).toBe(
|
||||
formatAdminDateTime(equivalentIso),
|
||||
);
|
||||
expect(formatAdminDateTime(microsText)).not.toBe('-');
|
||||
});
|
||||
|
||||
test('微秒位数不足时按毫秒补零,而不是丢位', () => {
|
||||
expect(formatAdminDateTime('1791008678.8Z')).toBe(
|
||||
formatAdminDateTime(new Date(1791008678 * 1000 + 800).toISOString()),
|
||||
);
|
||||
});
|
||||
|
||||
test('ISO 字符串、epoch 毫秒与 Date 仍按原语义渲染', () => {
|
||||
const iso = '2026-09-20T08:00:00Z';
|
||||
expect(formatAdminDateTime(iso)).toBe(formatAdminDateTime(new Date(iso)));
|
||||
expect(formatAdminDateTime(Date.parse(iso))).toBe(
|
||||
formatAdminDateTime(new Date(iso)),
|
||||
);
|
||||
});
|
||||
|
||||
test('无法解析的值回落 fallback', () => {
|
||||
expect(formatAdminDateTime('not-a-time')).toBe('-');
|
||||
expect(formatAdminDateTime('')).toBe('-');
|
||||
expect(formatAdminDateTime(null)).toBe('-');
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatAdminDate', () => {
|
||||
test('同样接受 seconds.microsZ 口径', () => {
|
||||
expect(formatAdminDate('1791008678.817257Z')).toBe(
|
||||
formatAdminDate(new Date(1791008678 * 1000 + 817).toISOString()),
|
||||
);
|
||||
expect(formatAdminDate('1791008678.817257Z')).not.toBe('-');
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,28 @@
|
||||
/**
|
||||
* 后台统一的时间展示:ISO 字符串 / epoch 毫秒 / Date → `zh-CN` 本地时间。
|
||||
* 部分后台接口下发的微秒时间戳是 `seconds.microsZ` 文本口径(见 shared-kernel 的
|
||||
* `format_timestamp_micros`,例如 `1791008678.817257Z`)。它不是 ISO 8601,
|
||||
* JS `new Date()` 会直接判成 `Invalid Date`,因此先归一成 ISO 字符串再解析。
|
||||
* 其它输入原样返回,交给 `new Date()` 处理。
|
||||
*/
|
||||
function normalizeAdminTimestamp(value: string | number): string | number {
|
||||
if (typeof value !== 'string') {
|
||||
return value;
|
||||
}
|
||||
const micros = /^(\d+)\.(\d{1,9})Z$/.exec(value.trim());
|
||||
if (!micros) {
|
||||
return value;
|
||||
}
|
||||
const seconds = Number(micros[1]);
|
||||
const fraction = micros[2] ?? '';
|
||||
if (!Number.isSafeInteger(seconds)) {
|
||||
return value;
|
||||
}
|
||||
const millis = Number(fraction.padEnd(9, '0').slice(0, 3));
|
||||
return new Date(seconds * 1000 + millis).toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台统一的时间展示:ISO 字符串 / `seconds.microsZ` / epoch 毫秒 / Date → `zh-CN` 本地时间。
|
||||
* 解析不出来时回落到 `fallback`,避免页面上出现 `Invalid Date` 或裸 ISO 串。
|
||||
*/
|
||||
export function formatAdminDateTime(
|
||||
@@ -10,7 +33,8 @@ export function formatAdminDateTime(
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const date = value instanceof Date ? value : new Date(value);
|
||||
const date =
|
||||
value instanceof Date ? value : new Date(normalizeAdminTimestamp(value));
|
||||
if (!Number.isFinite(date.getTime())) {
|
||||
return fallback;
|
||||
}
|
||||
@@ -30,7 +54,7 @@ export function formatAdminDateTime(
|
||||
}
|
||||
}
|
||||
|
||||
/** 后台统一的日期展示(不含时分秒)。 */
|
||||
/** 后台统一的日期展示(不含时分秒):同样接受 `seconds.microsZ` 口径。 */
|
||||
export function formatAdminDate(
|
||||
value: string | number | Date | null | undefined,
|
||||
fallback = '-',
|
||||
@@ -39,7 +63,8 @@ export function formatAdminDate(
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const date = value instanceof Date ? value : new Date(value);
|
||||
const date =
|
||||
value instanceof Date ? value : new Date(normalizeAdminTimestamp(value));
|
||||
if (!Number.isFinite(date.getTime())) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
@@ -431,7 +431,8 @@
|
||||
border-radius: 999px;
|
||||
padding: 3px 8px;
|
||||
font-size: 12px;
|
||||
overflow-wrap: anywhere;
|
||||
/* 状态标签是短徽标:必须单行,避免在窄列里被拆成每行一两个字。 */
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.admin-status-ok {
|
||||
|
||||
Reference in New Issue
Block a user