增加管理员联系二维码
This commit is contained in:
@@ -4,7 +4,6 @@ import { createRateLimitMiddleware, getBodyFieldRateLimitKey } from '../middlewa
|
||||
import { uploadSingleFile } from './file-upload.js'
|
||||
import { uploadFileAsset } from '../services/file-storage/file-storage-service.js'
|
||||
import {
|
||||
cancelWorkerOrder,
|
||||
changeWorkerPassword,
|
||||
createWorkerRechargeRequest,
|
||||
createWorkerWithdrawRequest,
|
||||
@@ -284,19 +283,6 @@ router.post(
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/orders/:workOrderId/cancel',
|
||||
requireActiveWorker,
|
||||
createRouteHandler(
|
||||
(req) => cancelWorkerOrder(String(req.params.workOrderId || ''), getRequiredWorkerSession(req)),
|
||||
{
|
||||
successMessage: '已取消接单',
|
||||
errorMessage: '取消接单失败',
|
||||
scope: '[worker/orders/:workOrderId/cancel]',
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.use((req, res) => {
|
||||
res.status(404).json(buildNotFoundPayload(req))
|
||||
})
|
||||
|
||||
@@ -17,6 +17,9 @@ export type WorkerFinanceConfig = {
|
||||
qrCodeImage: UploadedFileDto | null
|
||||
instructions: string
|
||||
}
|
||||
adminContact: {
|
||||
wechatQrCodeImage: UploadedFileDto | null
|
||||
}
|
||||
withdraw: {
|
||||
enabled: boolean
|
||||
instructions: string
|
||||
@@ -44,6 +47,11 @@ export async function saveWorkerFinanceConfig(rawValue: unknown): Promise<Worker
|
||||
export function normalizeWorkerFinanceConfig(rawValue: unknown): WorkerFinanceConfig {
|
||||
const source = isPlainObject(rawValue) ? rawValue : {}
|
||||
const recharge = isPlainObject(source.recharge) ? source.recharge : {}
|
||||
const adminContact = isPlainObject(source.adminContact)
|
||||
? source.adminContact
|
||||
: isPlainObject(source.admin_contact)
|
||||
? source.admin_contact
|
||||
: {}
|
||||
const withdraw = isPlainObject(source.withdraw) ? source.withdraw : {}
|
||||
|
||||
return {
|
||||
@@ -58,6 +66,12 @@ export function normalizeWorkerFinanceConfig(rawValue: unknown): WorkerFinanceCo
|
||||
),
|
||||
instructions: String(recharge.instructions || '').trim(),
|
||||
},
|
||||
adminContact: {
|
||||
wechatQrCodeImage: normalizeUploadedFileForStorage(
|
||||
adminContact.wechatQrCodeImage || adminContact.wechat_qr_code_image,
|
||||
'管理员微信二维码',
|
||||
),
|
||||
},
|
||||
withdraw: {
|
||||
enabled: typeof withdraw.enabled === 'boolean' ? withdraw.enabled : true,
|
||||
instructions: String(withdraw.instructions || '').trim(),
|
||||
@@ -76,6 +90,9 @@ export function createDefaultWorkerFinanceConfig(): WorkerFinanceConfig {
|
||||
qrCodeImage: null,
|
||||
instructions: '',
|
||||
},
|
||||
adminContact: {
|
||||
wechatQrCodeImage: null,
|
||||
},
|
||||
withdraw: {
|
||||
enabled: true,
|
||||
instructions: '',
|
||||
@@ -92,6 +109,11 @@ function refreshWorkerFinanceConfig(config: WorkerFinanceConfig): WorkerFinanceC
|
||||
? (refreshUploadedFileUrls(config.recharge.qrCodeImage) as UploadedFileDto)
|
||||
: null,
|
||||
},
|
||||
adminContact: {
|
||||
wechatQrCodeImage: config.adminContact.wechatQrCodeImage
|
||||
? (refreshUploadedFileUrls(config.adminContact.wechatQrCodeImage) as UploadedFileDto)
|
||||
: null,
|
||||
},
|
||||
withdraw: {
|
||||
...config.withdraw,
|
||||
},
|
||||
@@ -106,7 +128,10 @@ function normalizeRangeInteger(value: unknown, fallback: number, min: number, ma
|
||||
return Math.min(max, Math.max(min, parsed))
|
||||
}
|
||||
|
||||
function normalizeUploadedFileForStorage(value: unknown): UploadedFileDto | null {
|
||||
function normalizeUploadedFileForStorage(
|
||||
value: unknown,
|
||||
fallbackFilename = '收款二维码',
|
||||
): UploadedFileDto | null {
|
||||
if (!isPlainObject(value)) {
|
||||
return null
|
||||
}
|
||||
@@ -124,7 +149,7 @@ function normalizeUploadedFileForStorage(value: unknown): UploadedFileDto | null
|
||||
objectKey,
|
||||
),
|
||||
mediumUrl: normalizeStoredFileUrl(value.mediumUrl || value.medium_url, objectKey),
|
||||
filename: String(value.filename || '').trim() || '收款二维码',
|
||||
filename: String(value.filename || '').trim() || fallbackFilename,
|
||||
contentType: String(value.contentType || value.content_type || '').trim(),
|
||||
size: Math.max(0, Number(value.size || 0) || 0),
|
||||
}
|
||||
|
||||
@@ -14,9 +14,7 @@ import {
|
||||
countWorkCategoryUsages,
|
||||
countWorkerLevelUsages,
|
||||
countWorkOrderPendingSharingSubmissions,
|
||||
countWorkerCancellationsSince,
|
||||
countWorkerSmsCodesOnDay,
|
||||
cancelWorkerWorkOrder,
|
||||
consumeWorkerSmsCode,
|
||||
createWorkerSmsCode,
|
||||
createWorkOrder,
|
||||
@@ -102,7 +100,7 @@ import {
|
||||
saveWorkerFinanceConfig,
|
||||
} from './worker-finance-config-service.js'
|
||||
|
||||
import { DEFAULT_CATEGORY_KEY, DEFAULT_LEVEL_KEY, DEFAULT_LEVEL_NAME, INVITE_CODE_LENGTH, INVITE_CODE_RETRY_LIMIT, WORKER_CANCEL_LIMIT_PER_WINDOW, WORKER_CANCEL_LIMIT_WINDOW_MS, createWorkerSession, ensureWorkerAuthConfigured, hashWorkerPassword, isIgnorableWorkerAuthError, mapCollectLookupOrder, mapFinanceRequest, mapWalletLedger, mapWorkCategory, mapWorkOrderForWorker, mapWorkOrderPublic, mapWorkOrderShare, mapWorkerUser, normalizeAmountFen, normalizeFinanceRequestStatus, normalizeFinanceRequestType, normalizeInteger, normalizeOptionalId, normalizePassword, normalizePositiveInteger, normalizeProofFiles, normalizeSessionVersion, normalizeStringArray, normalizeSubmittedFields, normalizeUploadedFiles, normalizeUsername, normalizeWalletLedgerType, normalizeWithdrawChannel, resolveFreezeDepositAmount, resolveRequirementFields, resolveWorkerPermissions, safeCompare, signWorkerPayload, throwGrabWorkOrderFailure, validateWorkerPassword, validateWorkerUsername, verifyWorkerPassword } from './mappers.js'
|
||||
import { DEFAULT_CATEGORY_KEY, DEFAULT_LEVEL_KEY, DEFAULT_LEVEL_NAME, INVITE_CODE_LENGTH, INVITE_CODE_RETRY_LIMIT, createWorkerSession, ensureWorkerAuthConfigured, hashWorkerPassword, isIgnorableWorkerAuthError, mapCollectLookupOrder, mapFinanceRequest, mapWalletLedger, mapWorkCategory, mapWorkOrderForWorker, mapWorkOrderPublic, mapWorkOrderShare, mapWorkerUser, normalizeAmountFen, normalizeFinanceRequestStatus, normalizeFinanceRequestType, normalizeInteger, normalizeOptionalId, normalizePassword, normalizePositiveInteger, normalizeProofFiles, normalizeSessionVersion, normalizeStringArray, normalizeSubmittedFields, normalizeUploadedFiles, normalizeUsername, normalizeWalletLedgerType, normalizeWithdrawChannel, resolveFreezeDepositAmount, resolveRequirementFields, resolveWorkerPermissions, safeCompare, signWorkerPayload, throwGrabWorkOrderFailure, validateWorkerPassword, validateWorkerUsername, verifyWorkerPassword } from './mappers.js'
|
||||
|
||||
export type WorkerSession = {
|
||||
sessionId: string
|
||||
@@ -1086,49 +1084,6 @@ function resolveWorkOrderShareJoinError(
|
||||
})
|
||||
}
|
||||
|
||||
export async function cancelWorkerOrder(
|
||||
workOrderId: number | string,
|
||||
session: WorkerSession,
|
||||
) {
|
||||
requireActiveWorkerSession(session)
|
||||
const worker = await getRequiredWorker(session.workerId)
|
||||
const now = nowIso()
|
||||
const since = new Date(Date.now() - WORKER_CANCEL_LIMIT_WINDOW_MS).toISOString()
|
||||
const cancelCount = await countWorkerCancellationsSince(worker.id, since)
|
||||
if (cancelCount >= WORKER_CANCEL_LIMIT_PER_WINDOW) {
|
||||
throw createHttpError(
|
||||
`30 天内取消接单已达 ${WORKER_CANCEL_LIMIT_PER_WINDOW} 次上限,请谨慎接单`,
|
||||
{
|
||||
statusCode: 409,
|
||||
errorCode: 'worker_cancel_limit_reached',
|
||||
},
|
||||
)
|
||||
}
|
||||
const result = await cancelWorkerWorkOrder({
|
||||
workOrderId: Number(workOrderId),
|
||||
workerId: Number(worker.id),
|
||||
now,
|
||||
})
|
||||
if (result.failureReason === 'work_order_owner_required') {
|
||||
throw createHttpError('只能取消自己接的订单', {
|
||||
statusCode: 403,
|
||||
errorCode: 'work_order_owner_required',
|
||||
})
|
||||
}
|
||||
if (!result.order) {
|
||||
throw createHttpError('当前订单状态不能取消,仅进行中的订单可取消', {
|
||||
statusCode: 409,
|
||||
errorCode: 'work_order_cancel_status_invalid',
|
||||
})
|
||||
}
|
||||
return {
|
||||
order: mapWorkOrderForWorker(
|
||||
result.order,
|
||||
resolveWorkerPermissions(await getRequiredWorker(session.workerId)),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
export async function submitWorkerOrderAcceptance(
|
||||
workOrderId: number | string,
|
||||
payload: JsonObject = {},
|
||||
|
||||
@@ -92,6 +92,9 @@ type FinanceConfigFormValues = {
|
||||
qrCodeImageList?: UploadedFile[]
|
||||
instructions?: string
|
||||
}
|
||||
adminContact?: {
|
||||
wechatQrCodeImageList?: UploadedFile[]
|
||||
}
|
||||
withdraw?: {
|
||||
enabled?: boolean
|
||||
instructions?: string
|
||||
@@ -177,6 +180,9 @@ export default function FinancePanel() {
|
||||
qrCodeImage: values.recharge?.qrCodeImageList?.[0] || null,
|
||||
instructions: String(values.recharge?.instructions || '').trim(),
|
||||
},
|
||||
adminContact: {
|
||||
wechatQrCodeImage: values.adminContact?.wechatQrCodeImageList?.[0] || null,
|
||||
},
|
||||
withdraw: {
|
||||
enabled: values.withdraw?.enabled !== false,
|
||||
instructions: String(values.withdraw?.instructions || '').trim(),
|
||||
@@ -371,6 +377,15 @@ export default function FinancePanel() {
|
||||
</Form.Item>
|
||||
</Card>
|
||||
|
||||
<Card title="管理员联系" size="small" style={{ marginBottom: 16 }}>
|
||||
<Form.Item
|
||||
label="管理员微信二维码"
|
||||
name={['adminContact', 'wechatQrCodeImageList']}
|
||||
>
|
||||
<ImageUpload scene="worker-admin-contact" scope="admin" maxCount={1} />
|
||||
</Form.Item>
|
||||
</Card>
|
||||
|
||||
<Card title="押金与提现规则" size="small" style={{ marginBottom: 16 }}>
|
||||
<Form.Item
|
||||
label="押金解冻天数"
|
||||
@@ -551,6 +566,11 @@ function mapFinanceConfigToFormValues(
|
||||
qrCodeImageList: config?.recharge.qrCodeImage ? [config.recharge.qrCodeImage] : [],
|
||||
instructions: config?.recharge.instructions || '',
|
||||
},
|
||||
adminContact: {
|
||||
wechatQrCodeImageList: config?.adminContact.wechatQrCodeImage
|
||||
? [config.adminContact.wechatQrCodeImage]
|
||||
: [],
|
||||
},
|
||||
withdraw: {
|
||||
enabled: config?.withdraw.enabled !== false,
|
||||
instructions: config?.withdraw.instructions || '',
|
||||
|
||||
@@ -32,7 +32,6 @@ import { DeadlineCountdown } from '@/components/DeadlineCountdown'
|
||||
import ImagePreviewList from '@/components/files/ImagePreviewList'
|
||||
import ImageUpload from '@/components/files/ImageUpload'
|
||||
import {
|
||||
cancelWorkerOrder,
|
||||
fetchWorkerMyOrders,
|
||||
saveWorkerAcceptanceDraft,
|
||||
saveWorkerOrderNote,
|
||||
@@ -73,7 +72,7 @@ type DetailFieldItem = {
|
||||
}
|
||||
|
||||
export default function WorkerOrdersPage() {
|
||||
const { message, modal } = App.useApp()
|
||||
const { message } = App.useApp()
|
||||
const queryClient = useQueryClient()
|
||||
const [status, setStatus] = useState('')
|
||||
const [keywordInput, setKeywordInput] = useState('')
|
||||
@@ -239,26 +238,6 @@ export default function WorkerOrdersPage() {
|
||||
setPage(1)
|
||||
}
|
||||
|
||||
function openCancelModal(order: WorkOrder) {
|
||||
modal.confirm({
|
||||
title: '确认取消接单?',
|
||||
content:
|
||||
'取消后订单将重新回到抢单大厅,冻结押金全额退还。30 天内取消次数有限,请谨慎操作。',
|
||||
okText: '确认取消',
|
||||
cancelText: '再想想',
|
||||
okButtonProps: { danger: true },
|
||||
onOk: async () => {
|
||||
try {
|
||||
await cancelWorkerOrder(order.workOrderId)
|
||||
message.success('已取消接单,押金已退还')
|
||||
await refreshAll()
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '取消接单失败')
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const columns: TableColumnsType<WorkOrder> = [
|
||||
{
|
||||
title: '订单信息',
|
||||
@@ -464,11 +443,6 @@ export default function WorkerOrdersPage() {
|
||||
</Button>
|
||||
) : null}
|
||||
</Space>
|
||||
{canCancelOrder(row) ? (
|
||||
<Button type="link" danger size="small" onClick={() => openCancelModal(row)}>
|
||||
取消接单
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
@@ -596,11 +570,6 @@ export default function WorkerOrdersPage() {
|
||||
<Tag color={resolveStatusColor(detailOrder.status)}>
|
||||
{formatStatus(detailOrder.status)}
|
||||
</Tag>
|
||||
{canCancelOrder(detailOrder) ? (
|
||||
<Button danger onClick={() => openCancelModal(detailOrder)}>
|
||||
取消接单
|
||||
</Button>
|
||||
) : null}
|
||||
{canEditAcceptanceImages(detailOrder) ? (
|
||||
<Button
|
||||
type={canSubmitAcceptance(detailOrder) ? 'primary' : 'default'}
|
||||
@@ -851,10 +820,6 @@ function getDisplayOrderNo(order: Pick<WorkOrder, 'platformOrderId'> | null | un
|
||||
return String(order?.platformOrderId || '').trim() || '-'
|
||||
}
|
||||
|
||||
function canCancelOrder(order: WorkOrder) {
|
||||
return !order.myShare && order.status === 'in_progress'
|
||||
}
|
||||
|
||||
function getRequirementFields(order: WorkOrder | null): CollectField[] {
|
||||
if (!order) return []
|
||||
const rawFields = Array.isArray(order.requirement?.fields)
|
||||
|
||||
@@ -1027,8 +1027,24 @@ export default function WorkerProfilePage() {
|
||||
<Alert
|
||||
showIcon
|
||||
type="info"
|
||||
message="充值、提现与审核问题目前都由后台管理员人工处理。提交申请后,如需加急,可把下方账号信息发给管理员。"
|
||||
message="如需取消已接订单,请扫码联系管理员处理。充值、提现与审核问题也由后台管理员人工处理。"
|
||||
/>
|
||||
{financeConfig?.adminContact?.wechatQrCodeImage ? (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Image
|
||||
width={200}
|
||||
src={
|
||||
financeConfig.adminContact.wechatQrCodeImage.mediumUrl ||
|
||||
financeConfig.adminContact.wechatQrCodeImage.url
|
||||
}
|
||||
alt="管理员微信二维码"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Typography.Text type="secondary">
|
||||
管理员暂未配置微信二维码,请按下方账号信息联系。
|
||||
</Typography.Text>
|
||||
)}
|
||||
<Descriptions column={1} bordered size="small">
|
||||
<Descriptions.Item label="账号">
|
||||
<Typography.Text copyable>{worker?.username || '-'}</Typography.Text>
|
||||
|
||||
@@ -179,6 +179,9 @@ export function saveAdminWorkerFinanceConfig(payload: {
|
||||
qrCodeImage?: unknown
|
||||
instructions?: string
|
||||
}
|
||||
adminContact?: {
|
||||
wechatQrCodeImage?: unknown
|
||||
}
|
||||
withdraw?: {
|
||||
enabled?: boolean
|
||||
instructions?: string
|
||||
|
||||
@@ -153,12 +153,6 @@ export function saveWorkerAcceptanceDraft(
|
||||
)
|
||||
}
|
||||
|
||||
export function cancelWorkerOrder(workOrderId: number) {
|
||||
return apiPost<{ order: WorkOrder }>(
|
||||
`/api/v1/worker/orders/${workOrderId}/cancel`,
|
||||
)
|
||||
}
|
||||
|
||||
export function lookupCollectOrder(orderNo: string) {
|
||||
return apiPost<CollectLookupResponse>('/api/v1/collect/lookup', { orderNo })
|
||||
}
|
||||
|
||||
@@ -95,6 +95,9 @@ export type WorkerFinanceConfig = {
|
||||
qrCodeImage: UploadedFile | null
|
||||
instructions: string
|
||||
}
|
||||
adminContact: {
|
||||
wechatQrCodeImage: UploadedFile | null
|
||||
}
|
||||
withdraw: {
|
||||
enabled: boolean
|
||||
instructions: string
|
||||
|
||||
Reference in New Issue
Block a user