feat(worker-platform): 实现打手寄卖白名单权限控制与存量发布打手平滑过渡

1. 新增数据库迁移 082_worker_consignment_whitelist.sql,worker_users 表增加 can_consign 与 consignment_activated_at 字段,存量在 consignment_submissions 提报过的打手默认自动开启;
2. 打手端提报接口 publishWorkerConsignment 增加白名单权限服务端强校验拦截(403 consignment_permission_required);
3. 管理后台打手管理表格增加「寄卖权限」实时 Switch 开关列与打手详情抽屉权限展示,支持客服即时开通或关闭打手寄卖权限;
4. 打手端我要寄卖页面在未开通权限时展示专属白名单引导卡片,一键呼起联系客服开通寄卖弹窗;
5. 补充寄卖权限拦截与路由单测,前端59项单测与后端507项单测全部通过。
This commit is contained in:
yml2213
2026-09-21 13:30:33 +08:00
parent 0df5c9f34e
commit cb8b249e57
15 changed files with 295 additions and 1 deletions
@@ -174,6 +174,18 @@ function OverviewSection({ workerId }: { workerId: number }) {
<Tag color="green">正常可提现</Tag>
)}
</Descriptions.Item>
<Descriptions.Item label="寄卖权限" span={2}>
<Space>
<Tag color={worker.canConsign ? 'green' : 'default'}>
{worker.canConsign ? '已开通寄卖' : '未开通(白名单限制)'}
</Tag>
{worker.consignmentActivatedAt ? (
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
开通时间:{formatAdminDateTime(worker.consignmentActivatedAt)}
</Typography.Text>
) : null}
</Space>
</Descriptions.Item>
</Descriptions>
</Card>
<Card size="small" title="钱包">
@@ -11,6 +11,7 @@ import {
Modal,
Select,
Space,
Switch,
Table,
Tag,
Tooltip,
@@ -30,6 +31,7 @@ import {
saveAdminWorkerPersonalDepositFreeAmount,
saveAdminWorkerWithdrawalAccount,
updateAdminWorkerWithdrawalBlacklist,
updateAdminWorkerConsignmentPermission,
} from '@/services/admin'
import ImageUpload from '@/components/files/ImageUpload'
import type {
@@ -464,6 +466,27 @@ export default function WorkersPanel() {
}
}
const [updatingConsignmentWorkerId, setUpdatingConsignmentWorkerId] = useState<number | null>(
null,
)
async function handleToggleConsignmentPermission(row: WorkerUser, checked: boolean) {
setUpdatingConsignmentWorkerId(row.workerId)
try {
await updateAdminWorkerConsignmentPermission(row.workerId, { canConsign: checked })
message.success(
checked
? `已为打手 ${row.displayName || row.username} 开通寄卖权限`
: `已关闭打手 ${row.displayName || row.username} 的寄卖权限`,
)
await queryClient.invalidateQueries({ queryKey: ['admin-workers'] })
} catch (err: any) {
message.error(err instanceof Error ? err.message : '更新寄卖权限失败')
} finally {
setUpdatingConsignmentWorkerId(null)
}
}
const columns: TableColumnsType<WorkerUser> = [
{
title: '打手',
@@ -554,6 +577,19 @@ export default function WorkersPanel() {
</Space>
),
},
{
title: '寄卖权限',
width: 120,
render: (_, row) => (
<Switch
checkedChildren="已开通"
unCheckedChildren="未开通"
checked={row.canConsign}
loading={updatingConsignmentWorkerId === row.workerId}
onChange={(checked) => handleToggleConsignmentPermission(row, checked)}
/>
),
},
{
title: '冻结原因',
width: 220,
@@ -11,6 +11,7 @@ import {
ReloadOutlined,
SafetyCertificateOutlined,
UserOutlined,
CustomerServiceOutlined,
} from '@ant-design/icons'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import {
@@ -36,6 +37,7 @@ import {
import { useEffect, useMemo, useRef, useState } from 'react'
import ImageUpload from '@/components/files/ImageUpload'
import { WorkerContactModal } from '@/components/worker/WorkerContactModal'
import {
confirmWorkerConsignmentFriendAdded,
fetchWorkerConsignmentPriceSuggestion,
@@ -198,6 +200,8 @@ export default function WorkerConsignmentPage() {
const publishDepositYuan = publishDepositFen / 100
const availableAmount = Number(worker?.wallet?.availableAmount || 0)
const canPublish = availableAmount >= publishDepositFen
const canConsign = worker?.canConsign !== false
const [contactOpen, setContactOpen] = useState(false)
const reviewingCount = records.filter((item) => item.group === 'reviewing').length
const activeCount = records.filter((item) => item.group === 'active').length
@@ -338,6 +342,11 @@ export default function WorkerConsignmentPage() {
}
async function handlePublishSubmit(values: Record<string, any>) {
if (!canConsign) {
message.warning('您暂未开通寄卖权限,请联系客服申请开通')
return
}
if (!canPublish) {
message.warning(`可用余额不足 ¥${publishDepositYuan.toFixed(0)},请先联系客服充值履约押金`)
return
@@ -579,7 +588,33 @@ export default function WorkerConsignmentPage() {
<DollarCircleOutlined /> 发布寄卖
</span>
),
children: (
children: !canConsign ? (
<div style={{ padding: '36px 16px', textAlign: 'center' }}>
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description={
<div style={{ maxWidth: 420, margin: '0 auto' }}>
<Typography.Title level={5} style={{ marginBottom: 8 }}>
寄卖功能暂未开通
</Typography.Title>
<Typography.Paragraph
type="secondary"
style={{ fontSize: 13, marginBottom: 20 }}
>
为保障寄卖道具交付质量与履约安全,平台当前实行白名单审核制。请联系在线客服申请开通寄卖权限。
</Typography.Paragraph>
<Button
type="primary"
icon={<CustomerServiceOutlined />}
onClick={() => setContactOpen(true)}
>
联系客服开通寄卖
</Button>
</div>
}
/>
</div>
) : (
<Form
form={form}
layout="vertical"
@@ -929,6 +964,15 @@ export default function WorkerConsignmentPage() {
/>
</Space>
</Modal>
<WorkerContactModal
open={contactOpen}
onClose={() => setContactOpen(false)}
isMobile={isMobile}
worker={worker}
financeConfig={financeConfig}
title="联系客服开通寄卖"
/>
</section>
)
}
@@ -197,6 +197,16 @@ export function updateAdminWorkerWithdrawalBlacklist(
)
}
export function updateAdminWorkerConsignmentPermission(
workerId: number | string,
payload: { canConsign: boolean },
) {
return apiPost<{ worker: WorkerUser }>(
`/api/v1/admin/worker-platform/workers/${workerId}/consignment-permission`,
payload,
)
}
export function fetchAdminWorkerWalletLedgers(
workerId: number | string,
params?: Record<string, unknown>,
@@ -54,6 +54,10 @@ export type WorkerUser = {
lastRenamedAt: string | null
/** 上次改名后的一年锁定期结束时间 */
renameUnlockedAt: string | null
/** 是否开通寄卖权限(白名单开关) */
canConsign: boolean
/** 开通寄卖权限时间 */
consignmentActivatedAt: string | null
}
export type WorkerUserStatistics = {