feat(worker-platform): add worker withdrawal blacklist and admin actions
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
-- 打手提现黑名单:
|
||||
-- 1) is_withdrawal_blacklisted: 是否被列入提现黑名单(默认 false)
|
||||
-- 2) withdrawal_blacklist_reason: 拉黑原因/备注
|
||||
-- 3) withdrawal_blacklisted_at: 列入黑名单的时间
|
||||
|
||||
ALTER TABLE worker_users
|
||||
ADD COLUMN IF NOT EXISTS is_withdrawal_blacklisted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
ADD COLUMN IF NOT EXISTS withdrawal_blacklist_reason TEXT NULL,
|
||||
ADD COLUMN IF NOT EXISTS withdrawal_blacklisted_at TIMESTAMPTZ NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_worker_users_withdrawal_blacklisted
|
||||
ON worker_users (is_withdrawal_blacklisted)
|
||||
WHERE is_withdrawal_blacklisted = TRUE;
|
||||
@@ -42,6 +42,9 @@ export type WorkerUserRow = {
|
||||
total_settled_amount?: number
|
||||
rename_cards?: number
|
||||
last_renamed_at?: string | null
|
||||
is_withdrawal_blacklisted?: boolean
|
||||
withdrawal_blacklist_reason?: string | null
|
||||
withdrawal_blacklisted_at?: string | null
|
||||
}
|
||||
|
||||
export type WorkerUserStatistics = {
|
||||
@@ -453,6 +456,8 @@ export type ListInput = {
|
||||
/** 后台待验收筛选:普通单按接单人,拼单按参与打手等级。 */
|
||||
workerLevelIds?: number[]
|
||||
categoryId?: number
|
||||
/** 后台打手列表按提现黑名单筛选。 */
|
||||
isWithdrawalBlacklisted?: boolean | null | undefined
|
||||
workerSharingId?: number
|
||||
/** 大厅候选集合的总展示上限;筛选与分页在候选集合内执行。 */
|
||||
hallCandidateLimit?: number
|
||||
|
||||
@@ -240,6 +240,7 @@ export async function listWorkerUsers({
|
||||
workerType = '',
|
||||
workerId = 0,
|
||||
levelId = 0,
|
||||
isWithdrawalBlacklisted,
|
||||
}: ListInput = {}): Promise<{ items: WorkerUserRow[]; total: number }> {
|
||||
const { whereClause, params } = buildWorkerUserWhere({
|
||||
status,
|
||||
@@ -247,6 +248,7 @@ export async function listWorkerUsers({
|
||||
workerType,
|
||||
workerId,
|
||||
levelId,
|
||||
isWithdrawalBlacklisted,
|
||||
})
|
||||
const totalResult = await query<{ total: number }>(
|
||||
`SELECT COUNT(*)::int AS total FROM worker_users wu ${whereClause}`,
|
||||
@@ -267,7 +269,10 @@ export async function listWorkerUsers({
|
||||
|
||||
/** 按筛选条件汇总打手,统计范围不受当前分页影响。 */
|
||||
export async function getWorkerUserStatistics(
|
||||
input: Pick<ListInput, 'status' | 'keyword' | 'workerType' | 'workerId' | 'levelId'> = {},
|
||||
input: Pick<
|
||||
ListInput,
|
||||
'status' | 'keyword' | 'workerType' | 'workerId' | 'levelId' | 'isWithdrawalBlacklisted'
|
||||
> = {},
|
||||
): Promise<WorkerUserStatistics> {
|
||||
const { whereClause, params } = buildWorkerUserWhere(input)
|
||||
const statisticsResult = await query<{
|
||||
@@ -373,13 +378,17 @@ async function getWorkerUserByIdWithClient(
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
function buildWorkerUserWhere({
|
||||
export function buildWorkerUserWhere({
|
||||
status = '',
|
||||
keyword = '',
|
||||
workerType = '',
|
||||
workerId = 0,
|
||||
levelId = 0,
|
||||
}: Pick<ListInput, 'status' | 'keyword' | 'workerType' | 'workerId' | 'levelId'>) {
|
||||
isWithdrawalBlacklisted,
|
||||
}: Pick<
|
||||
ListInput,
|
||||
'status' | 'keyword' | 'workerType' | 'workerId' | 'levelId' | 'isWithdrawalBlacklisted'
|
||||
>) {
|
||||
const filters: string[] = []
|
||||
const params: unknown[] = []
|
||||
if (status) {
|
||||
@@ -404,12 +413,47 @@ function buildWorkerUserWhere({
|
||||
params.push(levelId)
|
||||
filters.push(`wu.level_id = $${params.length}`)
|
||||
}
|
||||
if (isWithdrawalBlacklisted !== undefined && isWithdrawalBlacklisted !== null) {
|
||||
params.push(Boolean(isWithdrawalBlacklisted))
|
||||
filters.push(`wu.is_withdrawal_blacklisted = $${params.length}`)
|
||||
}
|
||||
return {
|
||||
whereClause: filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '',
|
||||
params,
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateWorkerWithdrawalBlacklist(input: {
|
||||
workerId: number | string
|
||||
isWithdrawalBlacklisted: boolean
|
||||
reason: string
|
||||
now: string
|
||||
}): Promise<WorkerUserRow | null> {
|
||||
const result = await query<{ id: number }>(
|
||||
`
|
||||
UPDATE worker_users
|
||||
SET
|
||||
is_withdrawal_blacklisted = $1,
|
||||
withdrawal_blacklist_reason = $2,
|
||||
withdrawal_blacklisted_at = $3,
|
||||
updated_at = $4
|
||||
WHERE id = $5
|
||||
RETURNING id
|
||||
`,
|
||||
[
|
||||
input.isWithdrawalBlacklisted,
|
||||
input.isWithdrawalBlacklisted ? input.reason : null,
|
||||
input.isWithdrawalBlacklisted ? input.now : null,
|
||||
input.now,
|
||||
Number(input.workerId),
|
||||
],
|
||||
)
|
||||
if (!result.rows[0]) {
|
||||
return null
|
||||
}
|
||||
return getWorkerUserById(input.workerId)
|
||||
}
|
||||
|
||||
/** 消耗一张改名卡并更新昵称;条件更新保证并发下不会超发。 */
|
||||
export async function consumeWorkerRenameCard(input: {
|
||||
workerId: number
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
reviewAdminWorkerUser,
|
||||
saveAdminWorkerWithdrawalAccount,
|
||||
setAdminWorkerPersonalDepositFreeAmount,
|
||||
updateAdminWorkerWithdrawalBlacklist,
|
||||
} from '../../../services/worker-platform/index.js'
|
||||
import { createJsonHandler, requireAdminRoles } from '../session.js'
|
||||
|
||||
@@ -120,6 +121,26 @@ router.post(
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/worker-platform/workers/:workerId/withdrawal-blacklist',
|
||||
requireAdminRoles(['admin', 'operator']),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
updateAdminWorkerWithdrawalBlacklist(String(req.params.workerId || ''), req.body || {}),
|
||||
{
|
||||
successMessage: '打手提现黑名单状态已更新',
|
||||
errorMessage: '更新打手提现黑名单状态失败',
|
||||
scope: '[admin/worker-platform/workers/:workerId/withdrawal-blacklist]',
|
||||
audit: (req, data) => ({
|
||||
action: 'worker_withdrawal_blacklist_updated',
|
||||
targetType: 'worker',
|
||||
targetId: String(req.params.workerId || ''),
|
||||
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
|
||||
}),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/worker-platform/workers/:workerId/wallet-credit',
|
||||
requireAdminRoles(['admin', 'operator']),
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
revokeAllWorkerSessions,
|
||||
updateWorkerPassword,
|
||||
updateWorkerUser,
|
||||
updateWorkerWithdrawalBlacklist,
|
||||
} from '../../repositories/worker-platform/index.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
@@ -31,12 +32,22 @@ export async function listAdminWorkerUsers(query: JsonObject = {}) {
|
||||
const pageSize = normalizePageSize(query.pageSize)
|
||||
const workerId = normalizeOptionalId(query.workerId ?? query.worker_id) || 0
|
||||
const levelId = normalizeOptionalId(query.levelId ?? query.level_id) || 0
|
||||
const rawWithdrawalBlacklisted =
|
||||
query.isWithdrawalBlacklisted ?? query.is_withdrawal_blacklisted ?? query.withdrawalBlacklisted
|
||||
const isWithdrawalBlacklisted =
|
||||
rawWithdrawalBlacklisted !== undefined &&
|
||||
rawWithdrawalBlacklisted !== null &&
|
||||
String(rawWithdrawalBlacklisted).trim() !== ''
|
||||
? normalizeBoolean(rawWithdrawalBlacklisted, false)
|
||||
: undefined
|
||||
|
||||
const filters = {
|
||||
workerId,
|
||||
levelId,
|
||||
status: String(query.status || '').trim(),
|
||||
keyword: String(query.keyword || '').trim(),
|
||||
workerType: String(query.workerType || query.worker_type || '').trim(),
|
||||
isWithdrawalBlacklisted,
|
||||
}
|
||||
const includeStatistics = normalizeBoolean(
|
||||
query.includeStatistics ?? query.include_statistics,
|
||||
@@ -147,3 +158,23 @@ export async function resetAdminWorkerPassword(
|
||||
await revokeAllWorkerSessions(worker.id, now)
|
||||
return { worker: mapWorkerUser(updated || worker), reloginRequired: true }
|
||||
}
|
||||
|
||||
export async function updateAdminWorkerWithdrawalBlacklist(
|
||||
workerId: number | string,
|
||||
payload: JsonObject = {},
|
||||
) {
|
||||
const worker = await getRequiredWorker(workerId)
|
||||
const isWithdrawalBlacklisted = normalizeBoolean(
|
||||
payload.isWithdrawalBlacklisted ?? payload.blacklisted,
|
||||
false,
|
||||
)
|
||||
const reason = String(payload.reason || payload.blacklistReason || '').trim()
|
||||
const now = nowIso()
|
||||
const updated = await updateWorkerWithdrawalBlacklist({
|
||||
workerId: worker.id,
|
||||
isWithdrawalBlacklisted,
|
||||
reason,
|
||||
now,
|
||||
})
|
||||
return { worker: mapWorkerUser(updated || worker) }
|
||||
}
|
||||
|
||||
@@ -102,6 +102,9 @@ export function mapWorkerUser(worker: WorkerUserRow) {
|
||||
createdAt: worker.created_at,
|
||||
updatedAt: worker.updated_at,
|
||||
reviewedAt: worker.reviewed_at,
|
||||
isWithdrawalBlacklisted: Boolean(worker.is_withdrawal_blacklisted),
|
||||
withdrawalBlacklistReason: worker.withdrawal_blacklist_reason || '',
|
||||
withdrawalBlacklistedAt: worker.withdrawal_blacklisted_at || null,
|
||||
renameCards: Number(worker.rename_cards ?? 1),
|
||||
lastRenamedAt: worker.last_renamed_at || null,
|
||||
renameUnlockedAt: worker.last_renamed_at
|
||||
|
||||
@@ -110,6 +110,18 @@ export async function createWorkerWithdrawRequest(
|
||||
) {
|
||||
requireActiveWorkerSession(session)
|
||||
const worker = await getRequiredWorker(session.workerId)
|
||||
if (worker.is_withdrawal_blacklisted) {
|
||||
const reason = worker.withdrawal_blacklist_reason?.trim()
|
||||
throw createHttpError(
|
||||
reason
|
||||
? `您当前已被加入提现黑名单(原因:${reason}),无法发起提现,如有疑问请联系平台客服`
|
||||
: '您当前已被加入提现黑名单,无法发起提现,如有疑问请联系平台客服',
|
||||
{
|
||||
statusCode: 403,
|
||||
errorCode: 'worker_withdrawal_blacklisted',
|
||||
},
|
||||
)
|
||||
}
|
||||
const financeConfig = getWorkerFinanceConfig()
|
||||
if (financeConfig.withdraw.enabled === false) {
|
||||
throw createHttpError('当前提现入口暂未开放,请联系管理员', {
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
} from '../../repositories/worker-platform/index.js'
|
||||
import {
|
||||
buildWorkOrderWhere,
|
||||
buildWorkerUserWhere,
|
||||
resolveWorkOrderShareCancellationStatus,
|
||||
resolveWorkOrderShareDepositAmount,
|
||||
resolveWorkOrderSharingCancellationPlan,
|
||||
@@ -26,6 +27,7 @@ import {
|
||||
canWorkerAutoAcceptWholeOrder,
|
||||
isWorkOrderSharingFilled,
|
||||
isWorkerPasswordStrong,
|
||||
mapWorkerUser,
|
||||
normalizeWithdrawChannel,
|
||||
normalizeRequirementFields,
|
||||
normalizeWorkerFreezeReason,
|
||||
@@ -1285,3 +1287,65 @@ test('资金流水映射展示关联的平台订单号', () => {
|
||||
|
||||
assert.equal(mapWalletLedger(ledger)?.relatedPlatformOrderId, '2623301845665410')
|
||||
})
|
||||
|
||||
test('打手提现黑名单信息映射到前端结构', () => {
|
||||
const worker = {
|
||||
id: 99,
|
||||
username: 'blacklisted_worker',
|
||||
password_hash: 'hash',
|
||||
display_name: '违规打手',
|
||||
phone: '13800000000',
|
||||
level_id: null,
|
||||
status: 'active',
|
||||
worker_type: 'external',
|
||||
review_note: '',
|
||||
frozen_reason: '',
|
||||
frozen_at: null,
|
||||
session_version: 1,
|
||||
created_at: '2026-09-01T00:00:00.000Z',
|
||||
updated_at: '2026-09-02T00:00:00.000Z',
|
||||
reviewed_at: null,
|
||||
inviter_id: null,
|
||||
invite_code: 'ABCDEFGH',
|
||||
is_withdrawal_blacklisted: true,
|
||||
withdrawal_blacklist_reason: '账号涉嫌刷单,暂停提现',
|
||||
withdrawal_blacklisted_at: '2026-09-13T00:00:00.000Z',
|
||||
} satisfies WorkerUserRow
|
||||
|
||||
const mapped = mapWorkerUser(worker)
|
||||
assert.equal(mapped.isWithdrawalBlacklisted, true)
|
||||
assert.equal(mapped.withdrawalBlacklistReason, '账号涉嫌刷单,暂停提现')
|
||||
assert.equal(mapped.withdrawalBlacklistedAt, '2026-09-13T00:00:00.000Z')
|
||||
|
||||
const normalWorker = {
|
||||
...worker,
|
||||
is_withdrawal_blacklisted: false,
|
||||
withdrawal_blacklist_reason: null,
|
||||
withdrawal_blacklisted_at: null,
|
||||
} satisfies WorkerUserRow
|
||||
|
||||
const normalMapped = mapWorkerUser(normalWorker)
|
||||
assert.equal(normalMapped.isWithdrawalBlacklisted, false)
|
||||
assert.equal(normalMapped.withdrawalBlacklistReason, '')
|
||||
assert.equal(normalMapped.withdrawalBlacklistedAt, null)
|
||||
})
|
||||
|
||||
test('打手查询条件支持提现黑名单筛选', () => {
|
||||
const blacklistedWhere = buildWorkerUserWhere({
|
||||
isWithdrawalBlacklisted: true,
|
||||
})
|
||||
assert.match(blacklistedWhere.whereClause, /wu\.is_withdrawal_blacklisted = \$1/)
|
||||
assert.deepEqual(blacklistedWhere.params, [true])
|
||||
|
||||
const normalWhere = buildWorkerUserWhere({
|
||||
isWithdrawalBlacklisted: false,
|
||||
})
|
||||
assert.match(normalWhere.whereClause, /wu\.is_withdrawal_blacklisted = \$1/)
|
||||
assert.deepEqual(normalWhere.params, [false])
|
||||
|
||||
const allWhere = buildWorkerUserWhere({
|
||||
isWithdrawalBlacklisted: undefined,
|
||||
})
|
||||
assert.equal(allWhere.whereClause, '')
|
||||
assert.equal(allWhere.params.length, 0)
|
||||
})
|
||||
|
||||
@@ -109,6 +109,7 @@ export default function WorkerDetailDrawer({
|
||||
<Tag color={worker.status === 'active' ? 'green' : 'orange'}>
|
||||
{formatWorkerStatus(worker.status)}
|
||||
</Tag>
|
||||
{worker.isWithdrawalBlacklisted ? <Tag color="red">提现黑名单</Tag> : null}
|
||||
</Space>
|
||||
) : null
|
||||
}
|
||||
@@ -159,6 +160,20 @@ function OverviewSection({ workerId }: { workerId: number }) {
|
||||
<Descriptions.Item label="注册时间">
|
||||
{formatAdminDateTime(worker.createdAt)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="提现权限" span={2}>
|
||||
{worker.isWithdrawalBlacklisted ? (
|
||||
<Space>
|
||||
<Tag color="red">已加入提现黑名单</Tag>
|
||||
{worker.withdrawalBlacklistReason ? (
|
||||
<Typography.Text type="secondary">
|
||||
原因:{worker.withdrawalBlacklistReason}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
</Space>
|
||||
) : (
|
||||
<Tag color="green">正常可提现</Tag>
|
||||
)}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
<Card size="small" title="钱包">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ReloadOutlined, SearchOutlined } from '@ant-design/icons'
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import {
|
||||
Alert,
|
||||
App,
|
||||
Button,
|
||||
Card,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
Space,
|
||||
Table,
|
||||
Tag,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from 'antd'
|
||||
import type { TableColumnsType } from 'antd'
|
||||
@@ -27,6 +29,7 @@ import {
|
||||
reviewAdminWorkerUser,
|
||||
saveAdminWorkerPersonalDepositFreeAmount,
|
||||
saveAdminWorkerWithdrawalAccount,
|
||||
updateAdminWorkerWithdrawalBlacklist,
|
||||
} from '@/services/admin'
|
||||
import ImageUpload from '@/components/files/ImageUpload'
|
||||
import type {
|
||||
@@ -119,6 +122,7 @@ export default function WorkersPanel() {
|
||||
const [keywordInput, setKeywordInput] = useState('')
|
||||
const [keyword, setKeyword] = useState('')
|
||||
const [workerType, setWorkerType] = useState('')
|
||||
const [withdrawalBlacklisted, setWithdrawalBlacklisted] = useState('')
|
||||
const [levelId, setLevelId] = useState(0)
|
||||
const [targetWorkerId, setTargetWorkerId] = useState(0)
|
||||
const [page, setPage] = useState(1)
|
||||
@@ -128,6 +132,8 @@ export default function WorkersPanel() {
|
||||
const [withdrawalAccountWorker, setWithdrawalAccountWorker] = useState<WorkerUser | null>(null)
|
||||
const [resetPasswordWorker, setResetPasswordWorker] = useState<WorkerUser | null>(null)
|
||||
const [freezeWorker, setFreezeWorker] = useState<WorkerUser | null>(null)
|
||||
const [blacklistWorker, setBlacklistWorker] = useState<WorkerUser | null>(null)
|
||||
const [submittingBlacklist, setSubmittingBlacklist] = useState(false)
|
||||
const [personalDepositFreeWorker, setPersonalDepositFreeWorker] = useState<WorkerUser | null>(
|
||||
null,
|
||||
)
|
||||
@@ -141,6 +147,7 @@ export default function WorkersPanel() {
|
||||
const [withdrawalAccountForm] = Form.useForm<WithdrawalAccountFormValues>()
|
||||
const [resetPasswordForm] = Form.useForm<{ password: string }>()
|
||||
const [freezeForm] = Form.useForm<FreezeWorkerFormValues>()
|
||||
const [blacklistForm] = Form.useForm<{ reason?: string }>()
|
||||
const [personalDepositFreeForm] = Form.useForm<PersonalDepositFreeAmountFormValues>()
|
||||
const withdrawalAccountChannel = Form.useWatch('accountChannel', withdrawalAccountForm)
|
||||
const editingAlipayAccount = withdrawalAccounts.find(
|
||||
@@ -158,6 +165,7 @@ export default function WorkersPanel() {
|
||||
setKeyword('')
|
||||
setStatus('')
|
||||
setWorkerType('')
|
||||
setWithdrawalBlacklisted('')
|
||||
setLevelId(0)
|
||||
setPage(1)
|
||||
}, [searchParams])
|
||||
@@ -168,6 +176,7 @@ export default function WorkersPanel() {
|
||||
status,
|
||||
keyword,
|
||||
workerType,
|
||||
withdrawalBlacklisted,
|
||||
levelId,
|
||||
targetWorkerId,
|
||||
page,
|
||||
@@ -178,6 +187,8 @@ export default function WorkersPanel() {
|
||||
status,
|
||||
keyword,
|
||||
workerType,
|
||||
isWithdrawalBlacklisted:
|
||||
withdrawalBlacklisted === '' ? undefined : withdrawalBlacklisted === 'true',
|
||||
levelId: levelId || undefined,
|
||||
workerId: targetWorkerId || undefined,
|
||||
includeStatistics: true,
|
||||
@@ -223,6 +234,7 @@ export default function WorkersPanel() {
|
||||
setKeyword('')
|
||||
setStatus('')
|
||||
setWorkerType('')
|
||||
setWithdrawalBlacklisted('')
|
||||
setLevelId(0)
|
||||
setTargetWorkerId(0)
|
||||
setPage(1)
|
||||
@@ -235,6 +247,50 @@ export default function WorkersPanel() {
|
||||
)
|
||||
}
|
||||
|
||||
function handleAddToBlacklist(worker: WorkerUser) {
|
||||
setBlacklistWorker(worker)
|
||||
blacklistForm.resetFields()
|
||||
}
|
||||
|
||||
async function submitAddToBlacklist(values: { reason?: string }) {
|
||||
if (!blacklistWorker) return
|
||||
setSubmittingBlacklist(true)
|
||||
try {
|
||||
await updateAdminWorkerWithdrawalBlacklist(blacklistWorker.workerId, {
|
||||
isWithdrawalBlacklisted: true,
|
||||
reason: values.reason?.trim() || '',
|
||||
})
|
||||
message.success('已将打手加入提现黑名单')
|
||||
setBlacklistWorker(null)
|
||||
blacklistForm.resetFields()
|
||||
await refreshWorkers()
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '加入提现黑名单失败')
|
||||
} finally {
|
||||
setSubmittingBlacklist(false)
|
||||
}
|
||||
}
|
||||
|
||||
function handleRemoveFromBlacklist(worker: WorkerUser) {
|
||||
modal.confirm({
|
||||
title: '移出提现黑名单?',
|
||||
content: `确认将打手“${worker.displayName || worker.username}”(UID: ${worker.workerId})移出提现黑名单,恢复其正常提现功能?`,
|
||||
okText: '确认移出',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
await updateAdminWorkerWithdrawalBlacklist(worker.workerId, {
|
||||
isWithdrawalBlacklisted: false,
|
||||
})
|
||||
message.success('已移出提现黑名单,提现功能已恢复')
|
||||
await refreshWorkers()
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '移出提现黑名单失败')
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function review(worker: WorkerUser, nextStatus: string) {
|
||||
try {
|
||||
await reviewAdminWorkerUser(worker.workerId, { status: nextStatus })
|
||||
@@ -478,9 +534,22 @@ export default function WorkersPanel() {
|
||||
title: '状态',
|
||||
width: 130,
|
||||
render: (_, row) => (
|
||||
<Tag color={row.status === 'active' ? 'green' : 'orange'}>
|
||||
{formatWorkerStatus(row.status)}
|
||||
</Tag>
|
||||
<Space direction="vertical" size={2}>
|
||||
<Tag color={row.status === 'active' ? 'green' : 'orange'}>
|
||||
{formatWorkerStatus(row.status)}
|
||||
</Tag>
|
||||
{row.isWithdrawalBlacklisted ? (
|
||||
<Tooltip
|
||||
title={
|
||||
row.withdrawalBlacklistReason
|
||||
? `原因:${row.withdrawalBlacklistReason}`
|
||||
: '已加入提现黑名单,无法发起提现'
|
||||
}
|
||||
>
|
||||
<Tag color="red">提现黑名单</Tag>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -497,7 +566,7 @@ export default function WorkersPanel() {
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 340,
|
||||
width: 360,
|
||||
render: (_, row) => (
|
||||
<Space wrap>
|
||||
<Button type="primary" ghost onClick={() => setDetailWorker(row)}>
|
||||
@@ -543,6 +612,13 @@ export default function WorkersPanel() {
|
||||
<Button onClick={() => setCreditWorker(row)}>充值</Button>
|
||||
<Button onClick={() => setResetPasswordWorker(row)}>重置密码</Button>
|
||||
<Button onClick={() => void openWithdrawalAccountModal(row)}>提现信息</Button>
|
||||
{row.isWithdrawalBlacklisted ? (
|
||||
<Button onClick={() => handleRemoveFromBlacklist(row)}>移出提现黑名单</Button>
|
||||
) : (
|
||||
<Button danger onClick={() => handleAddToBlacklist(row)}>
|
||||
加入提现黑名单
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
@@ -607,6 +683,21 @@ export default function WorkersPanel() {
|
||||
setPage(1)
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
allowClear
|
||||
value={withdrawalBlacklisted || undefined}
|
||||
style={{ width: 140 }}
|
||||
placeholder="提现状态"
|
||||
options={[
|
||||
{ value: '', label: '全部提现状态' },
|
||||
{ value: 'false', label: '提现正常' },
|
||||
{ value: 'true', label: '提现黑名单' },
|
||||
]}
|
||||
onChange={(nextVal) => {
|
||||
setWithdrawalBlacklisted(nextVal || '')
|
||||
setPage(1)
|
||||
}}
|
||||
/>
|
||||
<Button type="primary" icon={<SearchOutlined />} onClick={searchWorkers}>
|
||||
查询
|
||||
</Button>
|
||||
@@ -874,6 +965,47 @@ export default function WorkersPanel() {
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title="加入提现黑名单"
|
||||
open={Boolean(blacklistWorker)}
|
||||
confirmLoading={submittingBlacklist}
|
||||
onCancel={() => {
|
||||
setBlacklistWorker(null)
|
||||
blacklistForm.resetFields()
|
||||
}}
|
||||
onOk={() => blacklistForm.submit()}
|
||||
destroyOnHidden
|
||||
>
|
||||
<Form form={blacklistForm} layout="vertical" onFinish={submitAddToBlacklist}>
|
||||
<Form.Item label="打手">
|
||||
<Typography.Text strong>
|
||||
{blacklistWorker?.displayName || blacklistWorker?.username || '-'}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary" style={{ marginLeft: 8 }}>
|
||||
(UID: {blacklistWorker?.workerId} · 账号: {blacklistWorker?.username})
|
||||
</Typography.Text>
|
||||
</Form.Item>
|
||||
<Alert
|
||||
showIcon
|
||||
type="warning"
|
||||
message="加入提现黑名单后,该打手(无论是内部还是外部)将无法提交提现申请;正常接单、做单和结算不受影响。"
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<Form.Item
|
||||
label="拉黑原因 / 备注"
|
||||
name="reason"
|
||||
rules={[{ max: 200, message: '原因不能超过 200 个字符' }]}
|
||||
>
|
||||
<Input.TextArea
|
||||
autoSize={{ minRows: 3, maxRows: 6 }}
|
||||
maxLength={200}
|
||||
placeholder="例如:涉嫌违规刷单,暂停提现权限等待核实(打手发起提现时将提示该原因)"
|
||||
showCount
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<WorkerDetailDrawer worker={detailWorker} onClose={() => setDetailWorker(null)} />
|
||||
</section>
|
||||
)
|
||||
|
||||
@@ -61,6 +61,11 @@ export function WorkerProfileDesktopIdentity({
|
||||
>
|
||||
{formatWorkerStatus(worker.status)}
|
||||
</Tag>
|
||||
{worker.isWithdrawalBlacklisted ? (
|
||||
<Tag color="red" className="worker-profile-status-tag">
|
||||
提现黑名单
|
||||
</Tag>
|
||||
) : null}
|
||||
{worker.level?.name ? (
|
||||
<span className="worker-profile-level-badge">{worker.level.name}</span>
|
||||
) : null}
|
||||
|
||||
@@ -95,6 +95,11 @@ export function WorkerProfileMobileView({
|
||||
<Tag className="worker-profile-mobile-tag">
|
||||
{worker.workerType === 'internal' ? '内部打手' : '外部打手'}
|
||||
</Tag>
|
||||
{worker.isWithdrawalBlacklisted ? (
|
||||
<Tag color="red" className="worker-profile-mobile-tag">
|
||||
提现黑名单
|
||||
</Tag>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="worker-profile-mobile-account-text">
|
||||
账号: {worker.username} {worker.phone ? `· ${worker.phone}` : ''}
|
||||
@@ -156,6 +161,18 @@ export function WorkerProfileMobileView({
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{worker.isWithdrawalBlacklisted ? (
|
||||
<Alert
|
||||
showIcon
|
||||
type="error"
|
||||
message={
|
||||
worker.withdrawalBlacklistReason
|
||||
? `提现受限:当前账号已被列入提现黑名单(原因:${worker.withdrawalBlacklistReason}),无法发起提现。如有疑问请联系客服。`
|
||||
: '提现受限:当前账号已被列入提现黑名单,无法发起提现。如有疑问请联系客服。'
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<div className="worker-profile-mobile-wallet-card">
|
||||
<div className="worker-profile-mobile-wallet-top">
|
||||
<div className="worker-profile-mobile-wallet-main">
|
||||
|
||||
@@ -220,6 +220,14 @@ export default function WorkerProfilePage() {
|
||||
message.warning('当前账号尚未通过审核,暂时不能发起提现申请')
|
||||
return
|
||||
}
|
||||
if (worker?.isWithdrawalBlacklisted) {
|
||||
message.error(
|
||||
worker.withdrawalBlacklistReason?.trim()
|
||||
? `您当前已被限制提现(原因:${worker.withdrawalBlacklistReason.trim()}),如有疑问请联系客服`
|
||||
: '您当前已被限制提现,如有疑问请联系客服',
|
||||
)
|
||||
return
|
||||
}
|
||||
if (financeConfig?.withdraw.enabled === false) {
|
||||
message.warning('当前提现入口暂未开放,请联系管理员')
|
||||
return
|
||||
@@ -510,6 +518,18 @@ export default function WorkerProfilePage() {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{worker.isWithdrawalBlacklisted ? (
|
||||
<Alert
|
||||
showIcon
|
||||
type="error"
|
||||
message={
|
||||
worker.withdrawalBlacklistReason
|
||||
? `提现受限:当前账号已被列入提现黑名单(原因:${worker.withdrawalBlacklistReason}),无法发起提现。如有疑问请联系平台客服。`
|
||||
: '提现受限:当前账号已被列入提现黑名单,无法发起提现。如有疑问请联系平台客服。'
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<WorkerProfileDesktopIdentity
|
||||
worker={worker}
|
||||
summary={summary}
|
||||
|
||||
@@ -186,6 +186,16 @@ export function fetchAdminWorkerDetail(workerId: number | string) {
|
||||
return apiGet<AdminWorkerDetail>(`/api/v1/admin/worker-platform/workers/${workerId}/detail`)
|
||||
}
|
||||
|
||||
export function updateAdminWorkerWithdrawalBlacklist(
|
||||
workerId: number | string,
|
||||
payload: { isWithdrawalBlacklisted: boolean; reason?: string },
|
||||
) {
|
||||
return apiPost<{ worker: WorkerUser }>(
|
||||
`/api/v1/admin/worker-platform/workers/${workerId}/withdrawal-blacklist`,
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
export function fetchAdminWorkerWalletLedgers(
|
||||
workerId: number | string,
|
||||
params?: Record<string, unknown>,
|
||||
|
||||
@@ -43,6 +43,12 @@ export type WorkerUser = {
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
reviewedAt: string | null
|
||||
/** 是否已被列入提现黑名单 */
|
||||
isWithdrawalBlacklisted: boolean
|
||||
/** 提现黑名单原因/备注 */
|
||||
withdrawalBlacklistReason: string
|
||||
/** 列入提现黑名单时间 */
|
||||
withdrawalBlacklistedAt: string | null
|
||||
/** 剩余改名卡数量,注册默认 1 张 */
|
||||
renameCards: number
|
||||
lastRenamedAt: string | null
|
||||
|
||||
Reference in New Issue
Block a user