104 lines
3.8 KiB
TypeScript
104 lines
3.8 KiB
TypeScript
/** 数据权限分组列表模型(任务 59;module 13 task 253 扩展编辑能力):解析 VO + 摘要 + 提交载荷。 */
|
||
import { unwrap } from '../../api/envelope.ts'
|
||
|
||
export interface ShopGroupItem {
|
||
id: number
|
||
name: string
|
||
leaderUsername: string
|
||
leaderUserId?: number
|
||
memberCount: number
|
||
memberUsernames: string[]
|
||
memberUserIds?: number[]
|
||
createdAt?: string
|
||
updatedAt?: string
|
||
}
|
||
|
||
export interface ShopGroupFormErrors {
|
||
groupName?: string
|
||
}
|
||
|
||
function text(value: unknown): string {
|
||
return typeof value === 'string' ? value.trim() : ''
|
||
}
|
||
|
||
function numberOrNull(value: unknown): number | null {
|
||
return typeof value === 'number' && Number.isFinite(value) ? Math.floor(value) : null
|
||
}
|
||
|
||
/** 解析单条分组项;缺 id 视为无效。 */
|
||
export function parseShopGroupItem(raw: unknown): ShopGroupItem | null {
|
||
if (!raw || typeof raw !== 'object') return null
|
||
const record = raw as Record<string, unknown>
|
||
const id = numberOrNull(record.id)
|
||
if (id === null) return null
|
||
const names = Array.isArray(record.memberUsernames)
|
||
? record.memberUsernames.map((v) => text(v)).filter(Boolean)
|
||
: []
|
||
const memberIds = Array.isArray(record.memberUserIds)
|
||
? record.memberUserIds.map((v) => numberOrNull(v)).filter((v): v is number => v !== null && v > 0)
|
||
: []
|
||
const count = numberOrNull(record.memberCount)
|
||
const item: ShopGroupItem = {
|
||
id,
|
||
name: text(record.groupName ?? record.name) || `分组${id}`,
|
||
leaderUsername: text(record.leaderUsername ?? record.leader_name),
|
||
memberCount: count === null ? names.length : count,
|
||
memberUsernames: names,
|
||
}
|
||
const leaderId = numberOrNull(record.leaderUserId ?? record.leader_user_id)
|
||
if (leaderId !== null && leaderId > 0) item.leaderUserId = leaderId
|
||
if (memberIds.length) item.memberUserIds = memberIds
|
||
const createdAt = text(record.createdAt ?? record.created_at)
|
||
if (createdAt) item.createdAt = createdAt
|
||
const updatedAt = text(record.updatedAt ?? record.updated_at)
|
||
if (updatedAt) item.updatedAt = updatedAt
|
||
return item
|
||
}
|
||
|
||
/** 归一化分组列表响应(信封或裸数组)。 */
|
||
export function parseShopGroupList(payload: unknown): ShopGroupItem[] {
|
||
const data = unwrap<unknown>(payload)
|
||
if (!Array.isArray(data)) return []
|
||
return data
|
||
.map((raw) => parseShopGroupItem(raw))
|
||
.filter((item): item is ShopGroupItem => item !== null)
|
||
}
|
||
|
||
/** 分组摘要:分组总数 + 组员总数。 */
|
||
export function shopGroupSummary(groups: ShopGroupItem[]): { groupCount: number; memberTotal: number } {
|
||
let memberTotal = 0
|
||
for (const group of groups) memberTotal += group.memberCount || 0
|
||
return { groupCount: groups.length, memberTotal }
|
||
}
|
||
|
||
/** 分组名必填校验(镜像后端 NotBlank 文案「请输入分组名称」)。 */
|
||
export function validateShopGroupForm(name: string): ShopGroupFormErrors {
|
||
const errors: ShopGroupFormErrors = {}
|
||
if (!text(name)) errors.groupName = '请输入分组名称'
|
||
return errors
|
||
}
|
||
|
||
/** 创建载荷:groupName + 成员 + 创建人(= 组长,当前操作者)。 */
|
||
export function toShopGroupCreatePayload(
|
||
name: string,
|
||
memberUserIds: number[],
|
||
createdById: number,
|
||
): { groupName: string; memberUserIds: number[]; createdById: number } {
|
||
return { groupName: text(name), memberUserIds: dedupeIds(memberUserIds), createdById }
|
||
}
|
||
|
||
/** 更新载荷:groupName + 成员(组长/创建人不可改)。 */
|
||
export function toShopGroupUpdatePayload(name: string, memberUserIds: number[]): { groupName: string; memberUserIds: number[] } {
|
||
return { groupName: text(name), memberUserIds: dedupeIds(memberUserIds) }
|
||
}
|
||
|
||
function dedupeIds(ids: unknown[]): number[] {
|
||
if (!Array.isArray(ids)) return []
|
||
const set = new Set<number>()
|
||
for (const v of ids) {
|
||
const id = numberOrNull(v)
|
||
if (id !== null && id > 0) set.add(id)
|
||
}
|
||
return [...set].sort((a, b) => a - b)
|
||
}
|