充值申请增加付款凭证与付款时间,管理端可核对后审核
- 打手端充值表单新增付款时间(必填)与付款凭证上传(最多5张) - 后端存储 paidAt 与 proofFiles 归一化数据 - 管理端资金申请列表新增付款凭证缩略图列,审核弹窗展示付款时间与凭证预览
This commit is contained in:
@@ -468,6 +468,8 @@ export async function createWorkerRechargeRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const note = String(payload.note || payload.remark || '').trim()
|
const note = String(payload.note || payload.remark || '').trim()
|
||||||
|
const proofFiles = normalizeProofFiles(payload.proofFiles || payload.proofFilesList)
|
||||||
|
const paidAt = String(payload.paidAt || '').trim()
|
||||||
const created = await createWorkerFinanceRequest({
|
const created = await createWorkerFinanceRequest({
|
||||||
workerId: worker.id,
|
workerId: worker.id,
|
||||||
requestType: 'recharge',
|
requestType: 'recharge',
|
||||||
@@ -480,6 +482,8 @@ export async function createWorkerRechargeRequest(
|
|||||||
source: 'worker_profile_recharge',
|
source: 'worker_profile_recharge',
|
||||||
username: worker.username,
|
username: worker.username,
|
||||||
phone: worker.phone || '',
|
phone: worker.phone || '',
|
||||||
|
paidAt: paidAt || null,
|
||||||
|
proofFiles,
|
||||||
financeConfig: {
|
financeConfig: {
|
||||||
channelName: financeConfig.recharge.channelName,
|
channelName: financeConfig.recharge.channelName,
|
||||||
accountName: financeConfig.recharge.accountName,
|
accountName: financeConfig.recharge.accountName,
|
||||||
@@ -2350,6 +2354,30 @@ function normalizeAmountFen(value: unknown, fallback: number): number {
|
|||||||
return Math.round(parsed * 100)
|
return Math.round(parsed * 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeProofFiles(value: unknown): Array<Record<string, unknown>> {
|
||||||
|
if (!Array.isArray(value)) return []
|
||||||
|
const files: Array<Record<string, unknown>> = []
|
||||||
|
for (const item of value) {
|
||||||
|
if (!item || typeof item !== 'object') continue
|
||||||
|
const file = item as Record<string, unknown>
|
||||||
|
const url = String(file.url || '').trim()
|
||||||
|
if (!url) continue
|
||||||
|
files.push({
|
||||||
|
url,
|
||||||
|
thumbnailUrl: String(file.thumbnailUrl || file.thumbnail_url || '').trim(),
|
||||||
|
mediumUrl: String(file.mediumUrl || file.medium_url || '').trim(),
|
||||||
|
objectKey: String(file.objectKey || file.object_key || '').trim(),
|
||||||
|
originalFilename: String(
|
||||||
|
file.originalFilename || file.original_filename || file.name || '',
|
||||||
|
).trim(),
|
||||||
|
contentType: String(file.contentType || file.content_type || '').trim(),
|
||||||
|
sizeBytes: Number(file.sizeBytes || file.size_bytes || 0),
|
||||||
|
})
|
||||||
|
if (files.length >= 10) break
|
||||||
|
}
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
function isIgnorableWorkerAuthError(error: unknown) {
|
function isIgnorableWorkerAuthError(error: unknown) {
|
||||||
return IGNORABLE_WORKER_AUTH_ERROR_CODES.has(
|
return IGNORABLE_WORKER_AUTH_ERROR_CODES.has(
|
||||||
String((error as { errorCode?: string })?.errorCode || '').trim(),
|
String((error as { errorCode?: string })?.errorCode || '').trim(),
|
||||||
|
|||||||
@@ -1617,6 +1617,16 @@ function FinancePanel() {
|
|||||||
minWidth: 240,
|
minWidth: 240,
|
||||||
render: (_, row) => renderFinanceRequestAccount(row),
|
render: (_, row) => renderFinanceRequestAccount(row),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '付款凭证',
|
||||||
|
minWidth: 140,
|
||||||
|
render: (_, row) =>
|
||||||
|
row.requestType === 'recharge' ? (
|
||||||
|
<ImagePreviewList files={getRechargeProofs(row)} size={48} />
|
||||||
|
) : (
|
||||||
|
'-'
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '备注',
|
title: '备注',
|
||||||
minWidth: 260,
|
minWidth: 260,
|
||||||
@@ -1851,6 +1861,18 @@ function FinancePanel() {
|
|||||||
<Descriptions.Item label="收款信息">
|
<Descriptions.Item label="收款信息">
|
||||||
{renderFinanceRequestAccountText(reviewState.request)}
|
{renderFinanceRequestAccountText(reviewState.request)}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
|
{reviewState.request.requestType === 'recharge' ? (
|
||||||
|
<>
|
||||||
|
<Descriptions.Item label="付款时间">
|
||||||
|
{getRechargePaidAt(reviewState.request)
|
||||||
|
? formatAdminDateTime(getRechargePaidAt(reviewState.request))
|
||||||
|
: '-'}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="付款凭证">
|
||||||
|
<ImagePreviewList files={getRechargeProofs(reviewState.request)} size={88} />
|
||||||
|
</Descriptions.Item>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
<Descriptions.Item label="申请备注">
|
<Descriptions.Item label="申请备注">
|
||||||
{reviewState.request.note || '-'}
|
{reviewState.request.note || '-'}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
@@ -2370,6 +2392,29 @@ function getRechargeSnapshot(request: WorkerFinanceRequest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRechargeProofs(request: WorkerFinanceRequest): UploadedFile[] {
|
||||||
|
if (request.requestType !== 'recharge') return []
|
||||||
|
const payload = asRecord(request.payload)
|
||||||
|
const rawFiles = Array.isArray(payload.proofFiles) ? payload.proofFiles : []
|
||||||
|
return rawFiles
|
||||||
|
.map((item) => {
|
||||||
|
const file = asRecord(item)
|
||||||
|
const url = String(file.url || '').trim()
|
||||||
|
if (!url) return null
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
mediumUrl: String(file.mediumUrl || '').trim(),
|
||||||
|
thumbnailUrl: String(file.thumbnailUrl || '').trim(),
|
||||||
|
} as UploadedFile
|
||||||
|
})
|
||||||
|
.filter((item): item is UploadedFile => Boolean(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRechargePaidAt(request: WorkerFinanceRequest): string {
|
||||||
|
const payload = asRecord(request.payload)
|
||||||
|
return String(payload.paidAt || '').trim()
|
||||||
|
}
|
||||||
|
|
||||||
function formatFinanceChannel(channel: string) {
|
function formatFinanceChannel(channel: string) {
|
||||||
if (channel === 'alipay') return '支付宝'
|
if (channel === 'alipay') return '支付宝'
|
||||||
if (channel === 'wechat') return '微信收款'
|
if (channel === 'wechat') return '微信收款'
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Col,
|
Col,
|
||||||
|
DatePicker,
|
||||||
Descriptions,
|
Descriptions,
|
||||||
Empty,
|
Empty,
|
||||||
Form,
|
Form,
|
||||||
@@ -38,6 +39,7 @@ import type { TableColumnsType } from 'antd'
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useNavigate } from 'react-router'
|
import { useNavigate } from 'react-router'
|
||||||
|
|
||||||
|
import ImageUpload from '@/components/files/ImageUpload'
|
||||||
import {
|
import {
|
||||||
changeWorkerPassword,
|
changeWorkerPassword,
|
||||||
createWorkerRechargeRequest,
|
createWorkerRechargeRequest,
|
||||||
@@ -48,6 +50,7 @@ import {
|
|||||||
logoutWorker,
|
logoutWorker,
|
||||||
} from '@/services/worker'
|
} from '@/services/worker'
|
||||||
import type {
|
import type {
|
||||||
|
UploadedFile,
|
||||||
WorkerFinanceRequest,
|
WorkerFinanceRequest,
|
||||||
WorkerProfileSummary,
|
WorkerProfileSummary,
|
||||||
WorkerUser,
|
WorkerUser,
|
||||||
@@ -59,6 +62,8 @@ import { clearWorkerSession } from '@/utils/worker-auth'
|
|||||||
type RechargeFormValues = {
|
type RechargeFormValues = {
|
||||||
amount?: number
|
amount?: number
|
||||||
note?: string
|
note?: string
|
||||||
|
paidAt?: string
|
||||||
|
proofFiles?: UploadedFile[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type WithdrawFormValues = {
|
type WithdrawFormValues = {
|
||||||
@@ -224,6 +229,8 @@ export default function WorkerProfilePage() {
|
|||||||
await createWorkerRechargeRequest({
|
await createWorkerRechargeRequest({
|
||||||
amount: values.amount,
|
amount: values.amount,
|
||||||
note: String(values.note || '').trim(),
|
note: String(values.note || '').trim(),
|
||||||
|
paidAt: values.paidAt || '',
|
||||||
|
proofFiles: values.proofFiles || [],
|
||||||
})
|
})
|
||||||
message.success('充值申请已提交,请等待管理员处理')
|
message.success('充值申请已提交,请等待管理员处理')
|
||||||
setRechargeOpen(false)
|
setRechargeOpen(false)
|
||||||
@@ -842,6 +849,25 @@ export default function WorkerProfilePage() {
|
|||||||
>
|
>
|
||||||
<InputNumber min={0.01} step={1} addonAfter="元" className="full-width" />
|
<InputNumber min={0.01} step={1} addonAfter="元" className="full-width" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="付款时间"
|
||||||
|
name="paidAt"
|
||||||
|
rules={[{ required: true, message: '请选择付款时间' }]}
|
||||||
|
>
|
||||||
|
<DatePicker
|
||||||
|
showTime
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
placeholder="选择转账完成时间"
|
||||||
|
format="YYYY-MM-DD HH:mm"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="付款凭证"
|
||||||
|
name="proofFiles"
|
||||||
|
extra="上传转账成功截图,便于管理员快速核对入账"
|
||||||
|
>
|
||||||
|
<ImageUpload scene="worker-finance-proof" scope="worker" maxCount={5} />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item label="备注说明" name="note">
|
<Form.Item label="备注说明" name="note">
|
||||||
<Input.TextArea
|
<Input.TextArea
|
||||||
rows={4}
|
rows={4}
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ export function fetchWorkerFinanceRequests(params?: Record<string, unknown>) {
|
|||||||
export function createWorkerRechargeRequest(payload: {
|
export function createWorkerRechargeRequest(payload: {
|
||||||
amount?: number
|
amount?: number
|
||||||
note?: string
|
note?: string
|
||||||
|
paidAt?: string
|
||||||
|
proofFiles?: UploadedFile[]
|
||||||
}) {
|
}) {
|
||||||
return apiPost<{ request: WorkerFinanceRequest }>(
|
return apiPost<{ request: WorkerFinanceRequest }>(
|
||||||
'/api/v1/worker/profile/recharge-requests',
|
'/api/v1/worker/profile/recharge-requests',
|
||||||
|
|||||||
Reference in New Issue
Block a user