diff --git a/README.md b/README.md index bedd6ee7..2ba27bc0 100644 --- a/README.md +++ b/README.md @@ -239,7 +239,7 @@ bash deploy/ubuntu-deploy.sh | 等级权限 | VIP 等级体系:免押额度、最大同时接单量、大厅可见延迟 | | 资金 | 押金冻结 / 退还 / 扣除流水,充值申请与提现审核 | | 物品规则 | 按商品/SKU 匹配自动生成工单,可继承默认时限与拼单配置 | -| 管理后台 | 接单工单 CRUD、打手审核、问题单处置、定时任务管理,功能标签页记忆上次选择 | +| 管理后台 | 接单工单 CRUD、打手账号管理、问题单处置、定时任务管理,功能标签页记忆上次选择 | 说明: diff --git a/apps/backend/src/db/migrations/042_auto_activate_sms_registered_workers.sql b/apps/backend/src/db/migrations/042_auto_activate_sms_registered_workers.sql new file mode 100644 index 00000000..90b3ad90 --- /dev/null +++ b/apps/backend/src/db/migrations/042_auto_activate_sms_registered_workers.sql @@ -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 '打手账号,手机号验证码注册后自动启用;后台可冻结或调整状态'; diff --git a/apps/backend/src/repositories/worker-platform/types.ts b/apps/backend/src/repositories/worker-platform/types.ts index 1e9a9d5a..f483daf3 100644 --- a/apps/backend/src/repositories/worker-platform/types.ts +++ b/apps/backend/src/repositories/worker-platform/types.ts @@ -284,6 +284,8 @@ export type ListInput = { /** 后台待办跳转时按工单主键精确定位 */ workOrderId?: number keyword?: string + /** 打手类型筛选:internal 内部打手 / external 外部打手 */ + workerType?: string workerId?: number categoryId?: number workerSharingId?: number diff --git a/apps/backend/src/repositories/worker-platform/worker-repo.ts b/apps/backend/src/repositories/worker-platform/worker-repo.ts index 331265c9..e596197f 100644 --- a/apps/backend/src/repositories/worker-platform/worker-repo.ts +++ b/apps/backend/src/repositories/worker-platform/worker-repo.ts @@ -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) { + workerType = '', +}: Pick) { 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, diff --git a/apps/backend/src/routes/admin/worker-platform.ts b/apps/backend/src/routes/admin/worker-platform.ts index f11f58f1..fb7f183e 100644 --- a/apps/backend/src/routes/admin/worker-platform.ts +++ b/apps/backend/src/routes/admin/worker-platform.ts @@ -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', diff --git a/apps/backend/src/routes/worker.ts b/apps/backend/src/routes/worker.ts index e7d7e3ce..4468ec29 100644 --- a/apps/backend/src/routes/worker.ts +++ b/apps/backend/src/routes/worker.ts @@ -70,7 +70,7 @@ router.post( key: getBodyFieldRateLimitKey('phone'), }), createRouteHandler((req) => registerWorker(req.body || {}), { - successMessage: '注册成功,请等待后台审核', + successMessage: '注册成功,请使用手机号登录', errorMessage: '接单账号注册失败', scope: '[worker/auth/register]', }), diff --git a/apps/backend/src/services/worker-platform/admin-service.ts b/apps/backend/src/services/worker-platform/admin-service.ts index ed78d3dc..8740921b 100644 --- a/apps/backend/src/services/worker-platform/admin-service.ts +++ b/apps/backend/src/services/worker-platform/admin-service.ts @@ -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 { diff --git a/apps/backend/src/services/worker-platform/worker-service.ts b/apps/backend/src/services/worker-platform/worker-service.ts index 6f692d51..df6df82f 100644 --- a/apps/backend/src/services/worker-platform/worker-service.ts +++ b/apps/backend/src/services/worker-platform/worker-service.ts @@ -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', }) diff --git a/apps/frontend/src/layouts/WorkerLayout.tsx b/apps/frontend/src/layouts/WorkerLayout.tsx index 4396fa89..f54096d0 100644 --- a/apps/frontend/src/layouts/WorkerLayout.tsx +++ b/apps/frontend/src/layouts/WorkerLayout.tsx @@ -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 '未登录' } diff --git a/apps/frontend/src/pages/admin/AdminWorkerPlatformPage.tsx b/apps/frontend/src/pages/admin/AdminWorkerPlatformPage.tsx index 8ca2d948..f76be514 100644 --- a/apps/frontend/src/pages/admin/AdminWorkerPlatformPage.tsx +++ b/apps/frontend/src/pages/admin/AdminWorkerPlatformPage.tsx @@ -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 }, diff --git a/apps/frontend/src/pages/admin/panels/WorkersPanel.tsx b/apps/frontend/src/pages/admin/panels/WorkersPanel.tsx index eeb3a03a..22bb3a30 100644 --- a/apps/frontend/src/pages/admin/panels/WorkersPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/WorkersPanel.tsx @@ -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(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) => ( {row.status !== 'active' ? ( - + ) : null} {row.status !== 'rejected' ? ( @@ -300,22 +316,49 @@ export default function WorkersPanel() { + + setKeywordInput(event.target.value)} + onPressEnter={searchWorkers} + /> { + setWorkerType(nextType || '') + setPage(1) + }} + /> + +