优化打手注册和列表筛选

This commit is contained in:
yml2213
2026-08-20 09:52:51 +08:00
parent f1575aec85
commit 12ca943170
14 changed files with 99 additions and 35 deletions
+1 -1
View File
@@ -239,7 +239,7 @@ bash deploy/ubuntu-deploy.sh
| 等级权限 | VIP 等级体系:免押额度、最大同时接单量、大厅可见延迟 |
| 资金 | 押金冻结 / 退还 / 扣除流水,充值申请与提现审核 |
| 物品规则 | 按商品/SKU 匹配自动生成工单,可继承默认时限与拼单配置 |
| 管理后台 | 接单工单 CRUD、打手审核、问题单处置、定时任务管理,功能标签页记忆上次选择 |
| 管理后台 | 接单工单 CRUD、打手账号管理、问题单处置、定时任务管理,功能标签页记忆上次选择 |
说明:
@@ -0,0 +1,12 @@
-- 短信验证码注册已完成手机号校验,历史遗留的待审核账号统一自动启用。
UPDATE worker_users
SET status = 'active',
review_note = CASE
WHEN BTRIM(review_note) = '' THEN '短信验证码注册自动启用'
ELSE review_note
END,
reviewed_at = COALESCE(reviewed_at, updated_at, NOW()),
updated_at = NOW()
WHERE status = 'pending_review';
COMMENT ON TABLE worker_users IS '打手账号,手机号验证码注册后自动启用;后台可冻结或调整状态';
@@ -284,6 +284,8 @@ export type ListInput = {
/** 后台待办跳转时按工单主键精确定位 */
workOrderId?: number
keyword?: string
/** 打手类型筛选:internal 内部打手 / external 外部打手 */
workerType?: string
workerId?: number
categoryId?: number
workerSharingId?: number
@@ -462,8 +462,9 @@ export async function listWorkerUsers({
pageSize = 20,
status = '',
keyword = '',
workerType = '',
}: ListInput = {}): Promise<{ items: WorkerUserRow[]; total: number }> {
const { whereClause, params } = buildWorkerUserWhere({ status, keyword })
const { whereClause, params } = buildWorkerUserWhere({ status, keyword, workerType })
const totalResult = await query<{ total: number }>(
`SELECT COUNT(*)::int AS total FROM worker_users wu ${whereClause}`,
params,
@@ -1174,7 +1175,8 @@ async function getWorkerFinanceRequestByIdWithClient(
function buildWorkerUserWhere({
status = '',
keyword = '',
}: Pick<ListInput, 'status' | 'keyword'>) {
workerType = '',
}: Pick<ListInput, 'status' | 'keyword' | 'workerType'>) {
const filters: string[] = []
const params: unknown[] = []
if (status) {
@@ -1187,6 +1189,10 @@ function buildWorkerUserWhere({
`(wu.username ILIKE $${params.length} OR wu.display_name ILIKE $${params.length} OR wu.phone ILIKE $${params.length})`,
)
}
if (workerType) {
params.push(workerType)
filters.push(`wu.worker_type = $${params.length}`)
}
return {
whereClause: filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '',
params,
@@ -305,8 +305,8 @@ router.post(
createJsonHandler(
(req) => reviewAdminWorkerUser(String(req.params.workerId || ''), req.body || {}),
{
successMessage: '打手审核状态已更新',
errorMessage: '审核打手失败',
successMessage: '打手账号状态已更新',
errorMessage: '更新打手账号状态失败',
scope: '[admin/worker-platform/workers/:workerId/review]',
audit: (req, data) => ({
action: 'worker_reviewed',
+1 -1
View File
@@ -70,7 +70,7 @@ router.post(
key: getBodyFieldRateLimitKey('phone'),
}),
createRouteHandler((req) => registerWorker(req.body || {}), {
successMessage: '注册成功,请等待后台审核',
successMessage: '注册成功,请使用手机号登录',
errorMessage: '接单账号注册失败',
scope: '[worker/auth/register]',
}),
@@ -893,6 +893,7 @@ export async function listAdminWorkerUsers(query: JsonObject = {}) {
pageSize,
status: String(query.status || '').trim(),
keyword: String(query.keyword || '').trim(),
workerType: String(query.workerType || query.worker_type || '').trim(),
})
const timeoutCounts = await countTimeoutEventsByWorkerIds(items.map((item) => Number(item.id)))
return {
@@ -328,7 +328,8 @@ export async function registerWorker(payload: JsonObject = {}) {
displayName,
phone,
levelId: defaults.level?.id || null,
status: 'pending_review',
// 手机号验证码已完成注册身份校验,账号直接启用,无需人工审核。
status: 'active',
inviterId: inviter ? Number(inviter.id) : null,
inviteCode: await generateInviteCode(now),
smsCodeId: latest!.id,
@@ -344,7 +345,8 @@ export async function registerWorker(payload: JsonObject = {}) {
return {
worker: mapWorkerUser(created),
reviewRequired: true,
// 保留字段兼容旧版客户端,但注册流程不再需要后台审核。
reviewRequired: false,
}
}
@@ -510,7 +512,7 @@ export function assertWorkerLoginAllowed(worker: WorkerUserRow) {
}
if (worker.status === 'pending_review') {
throw createHttpError('接单账号待审核,暂无法登录', {
throw createHttpError('接单账号待处理,暂无法登录', {
statusCode: 403,
errorCode: 'worker_pending_review',
})
+3 -3
View File
@@ -352,9 +352,9 @@ function resolveSelectedKey(pathname: string) {
}
function formatWorkerStatus(status: string) {
if (status === 'active') return '已审核'
if (status === 'pending_review') return '待审核'
if (status === 'rejected') return '审核未通过'
if (status === 'active') return '已启用'
if (status === 'pending_review') return '待处理'
if (status === 'rejected') return '已冻结'
if (status === 'disabled') return '已停用'
return '未登录'
}
@@ -106,7 +106,7 @@ function SummaryCards() {
const summary = summaryQuery.data?.data
const items = [
{ label: '待审核打手', value: summary?.pendingWorkerCount || 0 },
{ label: '待处理打手', value: summary?.pendingWorkerCount || 0 },
{ label: '待完善订单', value: summary?.pendingMaterialCount || 0 },
{ label: '大厅订单', value: summary?.openOrderCount || 0 },
{ label: '代练中', value: summary?.inProgressOrderCount || 0 },
@@ -1,4 +1,4 @@
import { ReloadOutlined } from '@ant-design/icons'
import { ReloadOutlined, SearchOutlined } from '@ant-design/icons'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import {
App,
@@ -45,6 +45,9 @@ export default function WorkersPanel() {
const { message, modal } = App.useApp()
const queryClient = useQueryClient()
const [status, setStatus] = useState('')
const [keywordInput, setKeywordInput] = useState('')
const [keyword, setKeyword] = useState('')
const [workerType, setWorkerType] = useState('')
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE)
const [creditWorker, setCreditWorker] = useState<WorkerUser | null>(null)
@@ -59,8 +62,8 @@ export default function WorkersPanel() {
const withdrawalAccountChannel = Form.useWatch('accountChannel', withdrawalAccountForm)
const workersQuery = useQuery({
queryKey: ['admin-worker-platform-workers', status, page, pageSize],
queryFn: () => fetchAdminWorkerUsers({ status, page, pageSize }),
queryKey: ['admin-worker-platform-workers', status, keyword, workerType, page, pageSize],
queryFn: () => fetchAdminWorkerUsers({ status, keyword, workerType, page, pageSize }),
})
const levelsQuery = useQuery({
queryKey: ['admin-worker-platform-levels'],
@@ -79,6 +82,19 @@ export default function WorkersPanel() {
])
}
function searchWorkers() {
setKeyword(keywordInput.trim())
setPage(1)
}
function clearWorkerFilters() {
setKeywordInput('')
setKeyword('')
setStatus('')
setWorkerType('')
setPage(1)
}
async function review(worker: WorkerUser, nextStatus: string) {
try {
await reviewAdminWorkerUser(worker.workerId, { status: nextStatus })
@@ -269,7 +285,7 @@ export default function WorkersPanel() {
render: (_, row) => (
<Space wrap>
{row.status !== 'active' ? (
<Button onClick={() => review(row, 'active')}></Button>
<Button onClick={() => review(row, 'active')}></Button>
) : null}
{row.status !== 'rejected' ? (
<Button onClick={() => review(row, 'rejected')}></Button>
@@ -300,22 +316,49 @@ export default function WorkersPanel() {
<Card
title="打手"
extra={
<Space>
<Space wrap className="filter-form">
<Input
allowClear
value={keywordInput}
placeholder="账号 / 昵称 / 手机号"
style={{ width: 220 }}
onChange={(event) => setKeywordInput(event.target.value)}
onPressEnter={searchWorkers}
/>
<Select
allowClear
value={status}
style={{ width: 150 }}
options={[
{ value: '', label: '全部状态' },
{ value: 'pending_review', label: '待审核' },
{ value: 'active', label: '已通过' },
{ value: 'rejected', label: '已拒绝' },
{ value: 'pending_review', label: '待处理' },
{ value: 'active', label: '已启用' },
{ value: 'rejected', label: '已冻结' },
{ value: 'disabled', label: '已停用' },
]}
onChange={(nextStatus) => {
setStatus(nextStatus)
setStatus(nextStatus || '')
setPage(1)
}}
/>
<Select
allowClear
value={workerType || undefined}
style={{ width: 140 }}
placeholder="全部类型"
options={[
{ value: 'external', label: '外部打手' },
{ value: 'internal', label: '内部打手' },
]}
onChange={(nextType) => {
setWorkerType(nextType || '')
setPage(1)
}}
/>
<Button type="primary" icon={<SearchOutlined />} onClick={searchWorkers}>
</Button>
<Button onClick={clearWorkerFilters}></Button>
<Button
icon={<ReloadOutlined />}
loading={workersQuery.isFetching}
@@ -226,9 +226,9 @@ export function resolveStatusColor(status: string) {
export function formatWorkerStatus(status: string) {
const labels: Record<string, string> = {
pending_review: '待审核',
active: '已通过',
rejected: '已拒绝',
pending_review: '待处理',
active: '已启用',
rejected: '已冻结',
disabled: '已停用',
}
return labels[status] || status
@@ -155,10 +155,8 @@ export default function WorkerLoginPage() {
}) {
setLoading(true)
try {
const response = await registerWorker(values)
message.success(
response.data.reviewRequired ? '注册成功,请等待后台审核' : '注册成功,请使用手机号登录',
)
await registerWorker(values)
message.success('注册成功,请使用手机号登录')
registerForm.resetFields()
} catch (error) {
message.error(error instanceof Error ? error.message : '注册失败')
@@ -944,7 +944,7 @@ export default function WorkerProfilePage() {
</span>
</div>
<div className="worker-profile-mobile-account-item">
<span className="worker-profile-mobile-acc-label"></span>
<span className="worker-profile-mobile-acc-label"></span>
<span className="worker-profile-mobile-acc-val">
<Tag color={resolveWorkerStatusColor(worker.status)}>
{formatWorkerStatus(worker.status)}
@@ -1184,7 +1184,7 @@ export default function WorkerProfilePage() {
<Descriptions.Item label="手机号">
{worker.phone || '-'}
</Descriptions.Item>
<Descriptions.Item label="审核状态">
<Descriptions.Item label="账号状态">
<Tag color={resolveWorkerStatusColor(worker.status)}>
{formatWorkerStatus(worker.status)}
</Tag>
@@ -1814,7 +1814,7 @@ export default function WorkerProfilePage() {
<Descriptions.Item label="手机号">
<Typography.Text copyable>{worker?.phone || '-'}</Typography.Text>
</Descriptions.Item>
<Descriptions.Item label="审核状态">
<Descriptions.Item label="账号状态">
{formatWorkerStatus(worker?.status || '')}
</Descriptions.Item>
<Descriptions.Item label="当前余额">
@@ -1852,9 +1852,9 @@ function formatMoney(value: number | undefined) {
}
function formatWorkerStatus(status: string) {
if (status === 'active') return '已审核'
if (status === 'pending_review') return '待审核'
if (status === 'rejected') return '审核未通过'
if (status === 'active') return '已启用'
if (status === 'pending_review') return '待处理'
if (status === 'rejected') return '已冻结'
if (status === 'disabled') return '已停用'
return status || '-'
}