feat: 新增客服备用金台账与后台用户中文显示名

- 新增备用金账户、流水、支出、对账与权限
- 后台用户支持中文显示名并同步备用金姓名
- 审计、登录记录、任务及工单操作人优先展示中文名
- 补充备用金与人工支出方案文档
This commit is contained in:
yml2213
2026-10-18 13:55:24 +08:00
parent 7bacb7d5eb
commit 7542d547bf
55 changed files with 6557 additions and 105 deletions
@@ -5,6 +5,7 @@ type AdminAuditLogRow = {
id: number
actor_user_id: number | null
actor_username: string
actor_display_name: string | null
actor_role: string
action: string
target_type: string
@@ -88,28 +89,30 @@ export async function listAdminAuditLogs(
const params: unknown[] = []
if (queryInput.actorUsername) {
params.push(queryInput.actorUsername)
conditions.push(`actor_username = $${params.length}`)
params.push(`%${queryInput.actorUsername}%`)
conditions.push(
`(aal.actor_username ILIKE $${params.length} OR au.display_name ILIKE $${params.length})`,
)
}
if (queryInput.action) {
params.push(queryInput.action)
conditions.push(`action = $${params.length}`)
conditions.push(`aal.action = $${params.length}`)
}
if (queryInput.targetType) {
params.push(queryInput.targetType)
conditions.push(`target_type = $${params.length}`)
conditions.push(`aal.target_type = $${params.length}`)
}
if (queryInput.dateFrom) {
params.push(queryInput.dateFrom)
conditions.push(`created_at >= $${params.length}`)
conditions.push(`aal.created_at >= $${params.length}`)
}
if (queryInput.dateTo) {
params.push(queryInput.dateTo)
conditions.push(`created_at <= $${params.length}`)
conditions.push(`aal.created_at <= $${params.length}`)
}
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
@@ -118,7 +121,12 @@ export async function listAdminAuditLogs(
const offset = (page - 1) * pageSize
const totalResult = await query<{ [column: string]: unknown; total: number }>(
`SELECT COUNT(*)::int AS total FROM admin_audit_logs ${whereClause}`,
`
SELECT COUNT(*)::int AS total
FROM admin_audit_logs aal
LEFT JOIN admin_users au ON au.id = aal.actor_user_id
${whereClause}
`,
params,
)
@@ -126,10 +134,13 @@ export async function listAdminAuditLogs(
params.push(offset)
const itemsResult = await query<AdminAuditLogRow>(
`
SELECT *
FROM admin_audit_logs
SELECT
aal.*,
COALESCE(NULLIF(au.display_name, ''), au.username) AS actor_display_name
FROM admin_audit_logs aal
LEFT JOIN admin_users au ON au.id = aal.actor_user_id
${whereClause}
ORDER BY id DESC
ORDER BY aal.id DESC
LIMIT $${params.length - 1} OFFSET $${params.length}
`,
params,
@@ -5,6 +5,7 @@ export type AdminLoginLogRow = {
id: number
user_id: number | null
username: string
display_name: string | null
role: string
ip: string
location: string
@@ -75,18 +76,20 @@ export async function listAdminLoginLogs(
const params: unknown[] = []
if (queryInput.username) {
params.push(String(queryInput.username).trim())
conditions.push(`username = $${params.length}`)
params.push(`%${String(queryInput.username).trim()}%`)
conditions.push(
`(ll.username ILIKE $${params.length} OR au.display_name ILIKE $${params.length})`,
)
}
if (queryInput.dateFrom) {
params.push(queryInput.dateFrom)
conditions.push(`created_at >= $${params.length}`)
conditions.push(`ll.created_at >= $${params.length}`)
}
if (queryInput.dateTo) {
params.push(queryInput.dateTo)
conditions.push(`created_at <= $${params.length}`)
conditions.push(`ll.created_at <= $${params.length}`)
}
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
@@ -95,7 +98,12 @@ export async function listAdminLoginLogs(
const offset = (page - 1) * pageSize
const totalResult = await query<{ total: number }>(
`SELECT COUNT(*)::int AS total FROM admin_login_logs ${whereClause}`,
`
SELECT COUNT(*)::int AS total
FROM admin_login_logs ll
LEFT JOIN admin_users au ON au.id = ll.user_id
${whereClause}
`,
params,
)
@@ -103,10 +111,13 @@ export async function listAdminLoginLogs(
params.push(offset)
const itemsResult = await query<AdminLoginLogRow>(
`
SELECT *
FROM admin_login_logs
SELECT
ll.*,
COALESCE(NULLIF(au.display_name, ''), au.username) AS display_name
FROM admin_login_logs ll
LEFT JOIN admin_users au ON au.id = ll.user_id
${whereClause}
ORDER BY id DESC
ORDER BY ll.id DESC
LIMIT $${params.length - 1} OFFSET $${params.length}
`,
params,
@@ -3,6 +3,7 @@ import { query } from '../db/client.js'
type AdminUserRow = {
id: number
username: string
display_name: string
password_hash: string
role: string
status: string
@@ -13,6 +14,7 @@ type AdminUserRow = {
type AdminUserCreateInput = {
username: string
displayName: string
passwordHash: string
role: string
status: string
@@ -23,14 +25,20 @@ type AdminUserCreateInput = {
type AdminUserPatch = Partial<
Pick<
AdminUserRow,
'username' | 'password_hash' | 'role' | 'status' | 'session_version' | 'updated_at'
| 'username'
| 'display_name'
| 'password_hash'
| 'role'
| 'status'
| 'session_version'
| 'updated_at'
>
>
type AdminUserListInput = {
page?: number
pageSize?: number
username?: string
keyword?: string
role?: string
status?: string
}
@@ -66,17 +74,19 @@ export async function createAdminUser(input: AdminUserCreateInput): Promise<Admi
`
INSERT INTO admin_users (
username,
display_name,
password_hash,
role,
status,
session_version,
created_at,
updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7)
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id
`,
[
input.username,
input.displayName,
input.passwordHash,
input.role,
input.status,
@@ -104,16 +114,18 @@ export async function updateAdminUser(
UPDATE admin_users
SET
username = $1,
password_hash = $2,
role = $3,
status = $4,
session_version = $5,
updated_at = $6
WHERE id = $7
display_name = $2,
password_hash = $3,
role = $4,
status = $5,
session_version = $6,
updated_at = $7
WHERE id = $8
RETURNING id
`,
[
next.username,
next.display_name,
next.password_hash,
next.role,
next.status,
@@ -129,7 +141,7 @@ export async function updateAdminUser(
export async function listAdminUsers({
page = 1,
pageSize = 20,
username = '',
keyword = '',
role = '',
status = '',
}: AdminUserListInput = {}): Promise<AdminUserListResult> {
@@ -137,24 +149,24 @@ export async function listAdminUsers({
const filters: string[] = []
const params: unknown[] = []
if (username) {
params.push(`%${username}%`)
filters.push(`username ILIKE $${params.length}`)
if (keyword) {
params.push(`%${keyword}%`)
filters.push(`(au.username ILIKE $${params.length} OR au.display_name ILIKE $${params.length})`)
}
if (role) {
params.push(role)
filters.push(`role = $${params.length}`)
filters.push(`au.role = $${params.length}`)
}
if (status) {
params.push(status)
filters.push(`status = $${params.length}`)
filters.push(`au.status = $${params.length}`)
}
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
const totalResult = await query<{ [column: string]: unknown; total: number }>(
`SELECT COUNT(*)::int AS total FROM admin_users ${whereClause}`,
`SELECT COUNT(*)::int AS total FROM admin_users au ${whereClause}`,
params,
)