5b8105ec2b
用户管理、菜单管理、不符合ASIN、数据去重总数据、查询ASIN、最低价ASIN、 商品类目、密钥管理共 8 个实体管理列表补齐两列。分组管理/店铺密钥/店铺管理 此前已带创建+修改时间,任务列表与统计报表(撞款监控、密钥用量、日志、 记录与版本)不含实体更新语义,均未改动。 关键点——时间列必须由数据库维护,否则新列是假的: 这些表的更新走 selectById → 改字段 → updateById,实体带着读出的旧 updated_at 一起写回。MySQL 规则是「UPDATE 显式给某列赋值时不触发该列的 ON UPDATE 自动更新」,不禁写就会把旧值写回去,更新时间永远冻结在首次写入 时刻。按 V125(biz_file_result)既有样板,给 7 个实体标注 @TableField(insertStrategy=NEVER, updateStrategy=NEVER)。 - V131:users / columns 补 updated_at(幂等 ADD COLUMN,仿 V125 写法)。 存量行被回填为迁移执行时刻,非真实历史变更时间(历史上无记录,无法还原) - 实体/VO:AdminUserEntity、PermissionMenuEntity、InvalidAsinDataEntity、 DedupeTotalDataEntity、ProductCategoryEntity、QueryAsinEntity、 SkipPriceAsinEntity 加/改写 updatedAt;AdminUserItemVo、PermissionMenuItemVo、 InvalidAsinDataItemVo、DedupeTotalDataItemVo 补 updatedAt; AdminUserSecretRowVo 补 createdAt(行级首次配置时间 = 三模块最早) - 查询ASIN/最低价ASIN 后端 VO 与前端 model 本就有两字段,仅补渲染 - 前端 8 页表格加列,同步修正空态/加载行的 colspan(手写表格,不同步会错位) - 测试:align-query-asin / align-skip-price 原断言「不允许有更新时间列」 (像素复刻旧版),按新需求改为断言两列存在;新增 e2e list-time-columns 覆盖 8 页表头与真实时间值渲染
93 lines
3.1 KiB
TypeScript
93 lines
3.1 KiB
TypeScript
import { http } from './http'
|
||
import { unwrap } from './envelope'
|
||
|
||
/** 单模块状态(脱敏值 + 连通性)。 */
|
||
export interface AdminUserSecretModule {
|
||
moduleKey: string
|
||
moduleLabel: string
|
||
masked: string
|
||
/** 完整明文值:仅代理列有值(后端对密钥列不下发明文)。 */
|
||
full: string
|
||
exists: boolean
|
||
checkStatus: string
|
||
checkCode: string
|
||
checkMessage: string
|
||
checkLatencyMs: number | null
|
||
checkedAt: string | null
|
||
updatedAt: string | null
|
||
}
|
||
|
||
/** 一行一用户:三个字段列 + 行级状态(三类都检测通过才算 passed)。 */
|
||
export interface AdminUserSecretRow {
|
||
userId: number
|
||
username: string
|
||
/** 所属数据权限分组名(可能多个)。 */
|
||
groups: string[]
|
||
/** 所属主管(创建人)用户名;无分组时的兜底展示。 */
|
||
leaderUsername: string
|
||
similarAsin: AdminUserSecretModule
|
||
appearancePatent: AdminUserSecretModule
|
||
proxy: AdminUserSecretModule
|
||
status: string
|
||
statusMessage: string
|
||
/** 首次配置时间(三模块中最早);从未配置为 null。 */
|
||
createdAt: string | null
|
||
updatedAt: string | null
|
||
}
|
||
|
||
export interface AdminUserSecretPage {
|
||
items: AdminUserSecretRow[]
|
||
total: number
|
||
page: number
|
||
pageSize: number
|
||
/** 分组筛选项(超管=全部;主管=自己带的分组)。 */
|
||
groupOptions?: GroupOption[]
|
||
}
|
||
|
||
export interface GroupOption {
|
||
id: number
|
||
groupName: string
|
||
}
|
||
|
||
export interface UserSecretQuery {
|
||
keyword?: string
|
||
checkStatus?: string
|
||
/** 按数据权限分组筛选(仅超管生效);后端参数为 snake_case。 */
|
||
groupId?: number
|
||
page: number
|
||
pageSize: number
|
||
}
|
||
|
||
/** 分页查询用户密钥(一行一用户):GET /api/admin/user-secrets(筛选参数必须 snake_case,camel 会被后端静默忽略)。 */
|
||
export async function fetchUserSecretList(params: UserSecretQuery): Promise<AdminUserSecretPage> {
|
||
const query: Record<string, string | number> = { page: params.page, pageSize: params.pageSize }
|
||
if (params.keyword) query.keyword = params.keyword
|
||
if (params.checkStatus) query.checkStatus = params.checkStatus
|
||
if (params.groupId) query.group_id = params.groupId
|
||
const { data } = await http.get('/api/admin/user-secrets', { params: query })
|
||
return unwrap<AdminUserSecretPage>(data)
|
||
}
|
||
|
||
/** 立即检测该用户全部已配置项:POST /api/admin/user-secrets/{userId}/check
|
||
* 逐模块检测(每项最多「首查 + 传输层失败重试一次」,最坏约 31s/项),故放宽超时到 180s。 */
|
||
export async function checkUserSecret(userId: number) {
|
||
const { data } = await http.post(`/api/admin/user-secrets/${userId}/check`, undefined, { timeout: 180_000 })
|
||
return unwrap<
|
||
Array<{
|
||
moduleKey: string
|
||
checkStatus: string
|
||
checkCode: string
|
||
checkMessage: string
|
||
checkLatencyMs: number | null
|
||
checkedAt: string | null
|
||
viaProxy: boolean
|
||
}>
|
||
>(data)
|
||
}
|
||
|
||
/** 清空该用户全部密钥与代理配置:DELETE /api/admin/user-secrets/{userId} */
|
||
export async function deleteUserSecret(userId: number): Promise<void> {
|
||
const { data } = await http.delete(`/api/admin/user-secrets/${userId}`)
|
||
unwrap<unknown>(data)
|
||
}
|