fix: 回调配置, 调整信用位置, 套餐
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 6m55s
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 6m55s
This commit is contained in:
@@ -53,6 +53,7 @@ export const buildAgentRechargeActions = (
|
||||
if (
|
||||
!hasApprovalRecord &&
|
||||
row.status === AgentRechargeStatus.PENDING &&
|
||||
row.payment_method === 'offline' &&
|
||||
options.hasAuth('agent_recharge:reject')
|
||||
) {
|
||||
actions.push({
|
||||
|
||||
23
src/views/finance/agent-recharge/agentRechargeOnline.ts
Normal file
23
src/views/finance/agent-recharge/agentRechargeOnline.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { normalizeApiError } from '@/utils/business/apiError'
|
||||
import type { AgentRechargeStatus } from '@/types/api'
|
||||
|
||||
export const ONLINE_RECHARGE_TERMINAL_STATUSES: AgentRechargeStatus[] = [3, 4, 5, 6]
|
||||
|
||||
export const isOnlineRechargeTerminal = (status: AgentRechargeStatus): boolean => {
|
||||
return ONLINE_RECHARGE_TERMINAL_STATUSES.includes(status)
|
||||
}
|
||||
|
||||
export const createOnlineRechargeRequestId = (): string => {
|
||||
const randomId = globalThis.crypto?.randomUUID?.()
|
||||
const suffix = randomId || Math.random().toString(36).slice(2, 12)
|
||||
return `recharge-${Date.now()}-${suffix}`
|
||||
}
|
||||
|
||||
export const amountYuanToFen = (amount: number): number => {
|
||||
return Math.round(amount * 100)
|
||||
}
|
||||
|
||||
export const shouldRetryOnlineCreate = (error: unknown): boolean => {
|
||||
const kind = normalizeApiError(error).kind
|
||||
return kind === 'timeout' || kind === 'server' || kind === 'unknown'
|
||||
}
|
||||
@@ -78,11 +78,19 @@
|
||||
const getPaymentMethodText = (method: AgentRechargePaymentMethod): string => {
|
||||
const methodMap: Record<AgentRechargePaymentMethod, string> = {
|
||||
wechat: '微信在线支付',
|
||||
alipay: '支付宝在线支付',
|
||||
offline: '线下转账'
|
||||
}
|
||||
return methodMap[method] || method
|
||||
}
|
||||
|
||||
const getRechargeSourceText = (data: AgentRecharge): string => {
|
||||
if (data.recharge_source_name) return data.recharge_source_name
|
||||
if (data.recharge_source === 'agent_online') return '代理在线自充'
|
||||
if (data.recharge_source === 'platform_offline') return '平台线下代充'
|
||||
return '-'
|
||||
}
|
||||
|
||||
const getApprovalProviderText = (data: AgentRecharge) => {
|
||||
if (data.approval_provider === 'wecom' || data.approval_source === 'wecom') return '企微'
|
||||
if (data.approval_source === 'legacy') return '历史审批'
|
||||
@@ -90,124 +98,155 @@
|
||||
}
|
||||
|
||||
// 详情配置
|
||||
const detailSections = computed((): DetailSection[] => [
|
||||
{
|
||||
title: '订单信息',
|
||||
fields: [
|
||||
{ label: '充值单号', prop: 'recharge_no' },
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
{ label: '提交人', prop: 'submitter_name', formatter: (value) => value || '-' },
|
||||
{
|
||||
label: '充值金额',
|
||||
formatter: (_, data) => formatCurrency(data.amount)
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
render: (data) =>
|
||||
h(ElTag, { type: getStatusType(data.status) }, () => data.status_name || '-')
|
||||
},
|
||||
{
|
||||
label: '驳回原因',
|
||||
prop: 'rejection_reason',
|
||||
formatter: (value) => formatRejectionReason(value),
|
||||
fullWidth: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '企微审批信息',
|
||||
fields: [
|
||||
{ label: '审批渠道', formatter: (_, data) => getApprovalProviderText(data) },
|
||||
{
|
||||
label: '审批状态',
|
||||
formatter: (_, data) => data.approval_status_name || '-'
|
||||
},
|
||||
{
|
||||
label: '当前审批人摘要',
|
||||
formatter: (_, data) => data.current_approver_summary || '-',
|
||||
fullWidth: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '业务处理结果',
|
||||
fields: [
|
||||
{
|
||||
label: '处理状态',
|
||||
formatter: (_, data) => data.processing_status_name || '-'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '支付信息',
|
||||
fields: [
|
||||
{
|
||||
label: '支付方式',
|
||||
formatter: (_, data) => getPaymentMethodText(data.payment_method)
|
||||
},
|
||||
{
|
||||
label: '支付通道',
|
||||
prop: 'payment_channel',
|
||||
formatter: (value) => value || '-'
|
||||
},
|
||||
{
|
||||
label: '第三方支付流水号',
|
||||
prop: 'payment_transaction_id',
|
||||
formatter: (value) => value || '-',
|
||||
fullWidth: true
|
||||
},
|
||||
{
|
||||
label: '支付凭证',
|
||||
fullWidth: true,
|
||||
render: (data) =>
|
||||
hasVoucherKeys(data.payment_voucher_key)
|
||||
? h(
|
||||
ElButton,
|
||||
{
|
||||
type: 'primary',
|
||||
link: true,
|
||||
onClick: () => {
|
||||
paymentVoucherFileKeys.value = toVoucherKeyList(data.payment_voucher_key)
|
||||
}
|
||||
},
|
||||
() => '查看支付凭证'
|
||||
)
|
||||
: h('span', '-')
|
||||
},
|
||||
{
|
||||
label: '运营备注',
|
||||
prop: 'remark',
|
||||
formatter: (value) => value || '-',
|
||||
fullWidth: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '时间信息',
|
||||
fields: [
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'created_at',
|
||||
formatter: (value) => formatDateTime(value)
|
||||
},
|
||||
{
|
||||
label: '支付时间',
|
||||
prop: 'paid_at',
|
||||
formatter: (value) => (value ? formatDateTime(value) : '-')
|
||||
},
|
||||
{
|
||||
label: '完成时间',
|
||||
prop: 'completed_at',
|
||||
formatter: (value) => (value ? formatDateTime(value) : '-')
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updated_at',
|
||||
formatter: (value) => formatDateTime(value)
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
const detailSections = computed((): DetailSection[] => {
|
||||
if (!detailData.value) return []
|
||||
|
||||
const isOfflineRecharge =
|
||||
detailData.value.recharge_source === 'platform_offline' ||
|
||||
detailData.value.payment_method === 'offline'
|
||||
|
||||
const sections: DetailSection[] = [
|
||||
{
|
||||
title: '订单信息',
|
||||
fields: [
|
||||
{ label: '充值单号', prop: 'recharge_no' },
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
{ label: '充值来源', formatter: (_, data) => getRechargeSourceText(data) },
|
||||
{ label: '提交人', prop: 'submitter_name', formatter: (value) => value || '-' },
|
||||
{
|
||||
label: '充值金额',
|
||||
formatter: (_, data) => formatCurrency(data.amount)
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
render: (data) =>
|
||||
h(ElTag, { type: getStatusType(data.status) }, () => data.status_name || '-')
|
||||
},
|
||||
{
|
||||
label: '驳回原因',
|
||||
prop: 'rejection_reason',
|
||||
formatter: (value) => formatRejectionReason(value),
|
||||
fullWidth: true
|
||||
}
|
||||
]
|
||||
},
|
||||
...(isOfflineRecharge
|
||||
? [
|
||||
{
|
||||
title: '企微审批信息',
|
||||
fields: [
|
||||
{
|
||||
label: '审批渠道',
|
||||
formatter: (_: unknown, data: AgentRecharge) => getApprovalProviderText(data)
|
||||
},
|
||||
{
|
||||
label: '审批状态',
|
||||
formatter: (_: unknown, data: AgentRecharge) => data.approval_status_name || '-'
|
||||
},
|
||||
{
|
||||
label: '当前审批人摘要',
|
||||
formatter: (_: unknown, data: AgentRecharge) =>
|
||||
data.current_approver_summary || '-',
|
||||
fullWidth: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
title: '业务处理结果',
|
||||
fields: [
|
||||
{
|
||||
label: '处理状态',
|
||||
formatter: (_, data) => data.processing_status_name || '-'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '支付信息',
|
||||
fields: [
|
||||
{
|
||||
label: '支付方式',
|
||||
formatter: (_, data) => getPaymentMethodText(data.payment_method)
|
||||
},
|
||||
{
|
||||
label: '支付通道',
|
||||
prop: 'payment_channel',
|
||||
formatter: (value) => value || '-'
|
||||
},
|
||||
{
|
||||
label: '第三方支付流水号',
|
||||
prop: 'payment_transaction_id',
|
||||
formatter: (value) => value || '-',
|
||||
fullWidth: true
|
||||
},
|
||||
{
|
||||
label: '支付单号',
|
||||
prop: 'payment_no',
|
||||
formatter: (value) => value || '-',
|
||||
fullWidth: true
|
||||
},
|
||||
...(isOfflineRecharge
|
||||
? [
|
||||
{
|
||||
label: '支付凭证',
|
||||
fullWidth: true,
|
||||
render: (data: AgentRecharge) =>
|
||||
hasVoucherKeys(data.payment_voucher_key)
|
||||
? h(
|
||||
ElButton,
|
||||
{
|
||||
type: 'primary',
|
||||
link: true,
|
||||
onClick: () => {
|
||||
paymentVoucherFileKeys.value = toVoucherKeyList(
|
||||
data.payment_voucher_key
|
||||
)
|
||||
}
|
||||
},
|
||||
() => '查看支付凭证'
|
||||
)
|
||||
: h('span', '-')
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: '运营备注',
|
||||
prop: 'remark',
|
||||
formatter: (value) => value || '-',
|
||||
fullWidth: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '时间信息',
|
||||
fields: [
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'created_at',
|
||||
formatter: (value) => formatDateTime(value)
|
||||
},
|
||||
{
|
||||
label: '支付时间',
|
||||
prop: 'paid_at',
|
||||
formatter: (value) => (value ? formatDateTime(value) : '-')
|
||||
},
|
||||
{
|
||||
label: '完成时间',
|
||||
prop: 'completed_at',
|
||||
formatter: (value) => (value ? formatDateTime(value) : '-')
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updated_at',
|
||||
formatter: (value) => formatDateTime(value)
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
return sections
|
||||
})
|
||||
|
||||
// 加载详情数据
|
||||
const loadDetailData = async () => {
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="showCreateDialog"
|
||||
v-if="hasAuth('agent_recharge:create')"
|
||||
>创建充值订单</ElButton
|
||||
v-if="hasAuth('agent_recharge:create') && canCreateRecharge"
|
||||
>{{ createButtonLabel }}</ElButton
|
||||
>
|
||||
<ElButton v-if="hasAuth('agent_recharge:export')" @click="exportDialogVisible = true">
|
||||
导出
|
||||
@@ -54,7 +54,7 @@
|
||||
<!-- 创建充值订单对话框 -->
|
||||
<ElDialog
|
||||
v-model="createDialogVisible"
|
||||
title="创建充值订单"
|
||||
:title="createDialogTitle"
|
||||
width="500px"
|
||||
@closed="handleCreateDialogClosed"
|
||||
>
|
||||
@@ -62,14 +62,17 @@
|
||||
<ElFormItem label="充值金额" prop="amount">
|
||||
<ElInputNumber
|
||||
v-model="createForm.amount"
|
||||
:min="0.01"
|
||||
:min="minimumAmountYuan"
|
||||
:max="maximumAmountYuan"
|
||||
:precision="2"
|
||||
:step="0.01"
|
||||
style="width: 100%"
|
||||
placeholder="请输入充值金额(元)"
|
||||
/>
|
||||
<div style="margin-top: 8px; font-size: 12px; color: var(--el-text-color-secondary)">
|
||||
最小: ¥0.01,最大: ¥1,000,000.00
|
||||
金额范围:¥{{ minimumAmountYuan.toFixed(2) }} - ¥{{
|
||||
maximumAmountYuan.toLocaleString('zh-CN', { minimumFractionDigits: 2 })
|
||||
}}
|
||||
</div>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="支付方式" prop="payment_method">
|
||||
@@ -77,11 +80,27 @@
|
||||
v-model="createForm.payment_method"
|
||||
placeholder="请选择支付方式"
|
||||
style="width: 100%"
|
||||
:loading="paymentMethodsLoading"
|
||||
>
|
||||
<ElOption label="线下转账" value="offline" />
|
||||
<ElOption
|
||||
v-for="option in availablePaymentMethodOptions"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
<div
|
||||
v-if="
|
||||
createMode === 'online' &&
|
||||
!paymentMethodsLoading &&
|
||||
onlinePaymentMethods.length === 0
|
||||
"
|
||||
class="online-recharge-empty-hint"
|
||||
>
|
||||
当前暂无可用在线支付方式,暂时无法提交充值。
|
||||
</div>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="店铺" prop="shop_id">
|
||||
<ElFormItem v-if="createMode === 'offline'" label="店铺" prop="shop_id">
|
||||
<ElCascader
|
||||
v-model="createForm.shop_id"
|
||||
:options="shopCascadeOptions"
|
||||
@@ -106,7 +125,7 @@
|
||||
@change="createFormRef?.validateField('payment_voucher_key')"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="运营备注" prop="remark">
|
||||
<ElFormItem v-if="createMode === 'offline'" label="运营备注" prop="remark">
|
||||
<ElInput
|
||||
v-model="createForm.remark"
|
||||
type="textarea"
|
||||
@@ -123,15 +142,54 @@
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="handleCreateRecharge"
|
||||
:loading="createLoading || voucherUploading"
|
||||
:disabled="voucherUploading"
|
||||
:loading="createLoading || voucherUploading || paymentMethodsLoading"
|
||||
:disabled="createSubmitDisabled"
|
||||
>
|
||||
{{ voucherUploading ? '凭证上传中...' : '确认创建' }}
|
||||
{{ voucherUploading ? '凭证上传中...' : createButtonLabel }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 在线充值二维码对话框 -->
|
||||
<ElDialog
|
||||
v-model="onlineQrDialogVisible"
|
||||
title="扫码完成充值"
|
||||
width="430px"
|
||||
align-center
|
||||
@closed="handleQrDialogClosed"
|
||||
>
|
||||
<div class="online-recharge-qr-dialog">
|
||||
<QrcodeVue
|
||||
v-if="qrContent"
|
||||
:value="qrContent"
|
||||
:size="240"
|
||||
level="H"
|
||||
render-as="canvas"
|
||||
/>
|
||||
<ElEmpty v-else description="暂未获取到支付二维码" />
|
||||
<div v-if="onlineQrRecharge" class="online-recharge-qr-dialog__summary">
|
||||
<div>充值单号:{{ onlineQrRecharge.recharge_no || '-' }}</div>
|
||||
<div>充值金额:{{ formatCurrency(onlineQrRecharge.amount) }}</div>
|
||||
<div v-if="onlineWalletBalance !== null">
|
||||
当前主钱包余额:{{ formatCurrency(onlineWalletBalance) }}
|
||||
</div>
|
||||
<ElTag :type="getStatusType(onlinePaymentStatus?.status || onlineQrRecharge.status)">
|
||||
{{ onlinePaymentStatus?.status_name || onlineQrRecharge.status_name || '-' }}
|
||||
</ElTag>
|
||||
<div class="online-recharge-qr-dialog__hint">
|
||||
{{ onlinePaymentStatusMessage }}
|
||||
</div>
|
||||
<div v-if="paymentStatusLoading" class="online-recharge-qr-dialog__loading">
|
||||
正在查询支付状态...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<ElButton @click="onlineQrDialogVisible = false">关闭</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 导出任务对话框 -->
|
||||
<ExportTaskCreateDialog
|
||||
v-model="exportDialogVisible"
|
||||
@@ -224,7 +282,8 @@
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { AgentRechargeService, ShopService } from '@/api/modules'
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
import { AgentRechargeService, CommissionService, ShopService } from '@/api/modules'
|
||||
import {
|
||||
ElMessage,
|
||||
ElTag,
|
||||
@@ -240,7 +299,9 @@
|
||||
AgentRecharge,
|
||||
AgentRechargeQueryParams,
|
||||
AgentRechargeStatus,
|
||||
AgentRechargeOnlinePaymentMethod,
|
||||
AgentRechargePaymentMethod,
|
||||
AgentRechargePaymentStatusResponse,
|
||||
CreateAgentRechargeRequest,
|
||||
ConfirmOfflinePaymentRequest,
|
||||
RejectAgentRechargeRequest
|
||||
@@ -248,6 +309,7 @@
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { normalizeApiError } from '@/utils/business/apiError'
|
||||
import {
|
||||
getApprovalStatusText,
|
||||
getCurrentApproverSummaryText,
|
||||
@@ -255,16 +317,36 @@
|
||||
} from '@/utils/business/approvalSummary'
|
||||
import { hasVoucherKeys, toVoucherKeyList } from '@/utils/business'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
|
||||
import VoucherUpload from '@/components/business/VoucherUpload.vue'
|
||||
import ExportTaskCreateDialog from '@/components/business/ExportTaskCreateDialog.vue'
|
||||
import { buildAgentRechargeActions } from './agentRechargeActions'
|
||||
import { formatRejectionReason } from './agentRechargeDisplay'
|
||||
import {
|
||||
amountYuanToFen,
|
||||
createOnlineRechargeRequestId,
|
||||
isOnlineRechargeTerminal,
|
||||
shouldRetryOnlineCreate
|
||||
} from './agentRechargeOnline'
|
||||
|
||||
defineOptions({ name: 'AgentRechargeList' })
|
||||
|
||||
const router = useRouter()
|
||||
const { hasAuth } = useAuth()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const isAgentAccount = computed(() => Number(userStore.info.user_type) === 3)
|
||||
const isPlatformAccount = computed(() => [1, 2].includes(Number(userStore.info.user_type)))
|
||||
const canViewRecharge = computed(() => [1, 2, 3].includes(Number(userStore.info.user_type)))
|
||||
const canCreateRecharge = computed(() => isAgentAccount.value || isPlatformAccount.value)
|
||||
const createMode = ref<'online' | 'offline'>('offline')
|
||||
const createDialogTitle = computed(() =>
|
||||
createMode.value === 'online' ? '代理钱包在线扫码充值' : '创建平台线下代充'
|
||||
)
|
||||
const createButtonLabel = computed(() =>
|
||||
createMode.value === 'online' ? '立即充值' : '创建充值订单'
|
||||
)
|
||||
|
||||
const loading = ref(false)
|
||||
const createLoading = ref(false)
|
||||
@@ -276,13 +358,29 @@
|
||||
const exportDialogVisible = ref(false)
|
||||
const confirmPayDialogVisible = ref(false)
|
||||
const rejectDialogVisible = ref(false)
|
||||
const onlineQrDialogVisible = ref(false)
|
||||
const currentRecharge = ref<AgentRecharge | null>(null)
|
||||
const onlineQrRecharge = ref<AgentRecharge | null>(null)
|
||||
const onlinePaymentStatus = ref<AgentRechargePaymentStatusResponse | null>(null)
|
||||
const onlineWalletBalance = ref<number | null>(null)
|
||||
const onlinePaymentMethods = ref<AgentRechargeOnlinePaymentMethod[]>([])
|
||||
const paymentMethodsLoading = ref(false)
|
||||
const paymentStatusLoading = ref(false)
|
||||
const qrContent = ref('')
|
||||
const onlineRequestId = ref<string | null>(null)
|
||||
const onlineCreateRetried = ref(false)
|
||||
const paymentStatusTimer = ref<ReturnType<typeof setInterval> | null>(null)
|
||||
const paymentMethodsBounds = reactive({
|
||||
min_amount: 10000,
|
||||
max_amount: 100000000
|
||||
})
|
||||
const paymentVoucherFileKeys = ref<string[]>([])
|
||||
|
||||
// 搜索表单初始值
|
||||
const initialSearchState: AgentRechargeQueryParams = {
|
||||
shop_id: undefined,
|
||||
status: undefined,
|
||||
recharge_source: undefined,
|
||||
dateRange: [],
|
||||
start_date: '',
|
||||
end_date: ''
|
||||
@@ -325,54 +423,72 @@
|
||||
}
|
||||
|
||||
// 搜索表单配置
|
||||
const searchFormItems: SearchFormItem[] = [
|
||||
{
|
||||
label: '店铺名称',
|
||||
prop: 'shop_id',
|
||||
type: 'select',
|
||||
placeholder: '请选择店铺',
|
||||
options: () =>
|
||||
shopOptions.value.map((shop) => ({
|
||||
label: shop.shop_name,
|
||||
value: shop.id
|
||||
})),
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: (query: string) => searchShops(query)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '支付状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
placeholder: '请选择状态',
|
||||
options: [
|
||||
{ label: '待支付', value: 1 },
|
||||
{ label: '已支付', value: 2 },
|
||||
{ label: '已完成', value: 3 },
|
||||
{ label: '已关闭', value: 4 },
|
||||
{ label: '已退款', value: 5 },
|
||||
{ label: '已驳回', value: 6 }
|
||||
],
|
||||
config: {
|
||||
clearable: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '起止时间',
|
||||
prop: 'dateRange',
|
||||
type: 'date',
|
||||
config: {
|
||||
type: 'daterange',
|
||||
rangeSeparator: '至',
|
||||
startPlaceholder: '开始日期',
|
||||
endPlaceholder: '结束日期',
|
||||
valueFormat: 'YYYY-MM-DD'
|
||||
}
|
||||
const searchFormItems = computed<SearchFormItem[]>(() => {
|
||||
const items: SearchFormItem[] = []
|
||||
|
||||
if (isPlatformAccount.value) {
|
||||
items.push({
|
||||
label: '店铺名称',
|
||||
prop: 'shop_id',
|
||||
type: 'select',
|
||||
placeholder: '请选择店铺',
|
||||
options: () =>
|
||||
shopOptions.value.map((shop) => ({
|
||||
label: shop.shop_name,
|
||||
value: shop.id
|
||||
})),
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: (query: string) => searchShops(query)
|
||||
}
|
||||
})
|
||||
}
|
||||
]
|
||||
|
||||
items.push(
|
||||
{
|
||||
label: '充值状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
placeholder: '请选择状态',
|
||||
options: [
|
||||
{ label: '待支付', value: 1 },
|
||||
{ label: '已支付', value: 2 },
|
||||
{ label: '已完成', value: 3 },
|
||||
{ label: '已关闭', value: 4 },
|
||||
{ label: '已退款', value: 5 },
|
||||
{ label: '已驳回', value: 6 }
|
||||
],
|
||||
config: { clearable: true }
|
||||
},
|
||||
{
|
||||
label: '充值来源',
|
||||
prop: 'recharge_source',
|
||||
type: 'select',
|
||||
placeholder: '请选择充值来源',
|
||||
options: [
|
||||
{ label: '代理在线自充', value: 'agent_online' },
|
||||
{ label: '平台线下代充', value: 'platform_offline' }
|
||||
],
|
||||
config: { clearable: true }
|
||||
},
|
||||
{
|
||||
label: '起止时间',
|
||||
prop: 'dateRange',
|
||||
type: 'date',
|
||||
config: {
|
||||
type: 'daterange',
|
||||
rangeSeparator: '至',
|
||||
startPlaceholder: '开始日期',
|
||||
endPlaceholder: '结束日期',
|
||||
valueFormat: 'YYYY-MM-DD'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return items
|
||||
})
|
||||
|
||||
// 分页
|
||||
const pagination = reactive({
|
||||
@@ -385,6 +501,7 @@
|
||||
const columnOptions = [
|
||||
{ label: '充值单号', prop: 'recharge_no' },
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
{ label: '充值来源', prop: 'recharge_source' },
|
||||
{ label: '充值金额', prop: 'amount' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
{ label: '审批渠道', prop: 'approval_provider' },
|
||||
@@ -406,8 +523,43 @@
|
||||
const confirmPayFormRef = ref<FormInstance>()
|
||||
const rejectFormRef = ref<FormInstance>()
|
||||
const uploadRef = ref<InstanceType<typeof VoucherUpload>>()
|
||||
const MIN_RECHARGE_AMOUNT = 0.01
|
||||
const MAX_RECHARGE_AMOUNT = 1_000_000
|
||||
const OFFLINE_MIN_RECHARGE_AMOUNT = 0.01
|
||||
const OFFLINE_MAX_RECHARGE_AMOUNT = 1_000_000
|
||||
|
||||
const minimumAmountYuan = computed(() =>
|
||||
createMode.value === 'online'
|
||||
? paymentMethodsBounds.min_amount / 100
|
||||
: OFFLINE_MIN_RECHARGE_AMOUNT
|
||||
)
|
||||
const maximumAmountYuan = computed(() =>
|
||||
createMode.value === 'online'
|
||||
? paymentMethodsBounds.max_amount / 100
|
||||
: OFFLINE_MAX_RECHARGE_AMOUNT
|
||||
)
|
||||
const availablePaymentMethodOptions = computed(() => {
|
||||
if (createMode.value === 'offline') return [{ label: '线下转账', value: 'offline' }]
|
||||
|
||||
return onlinePaymentMethods.value.map((method) => ({
|
||||
label: method === 'wechat' ? '微信支付' : '支付宝',
|
||||
value: method
|
||||
}))
|
||||
})
|
||||
const createSubmitDisabled = computed(
|
||||
() =>
|
||||
voucherUploading.value ||
|
||||
paymentMethodsLoading.value ||
|
||||
(createMode.value === 'online' && onlinePaymentMethods.value.length === 0)
|
||||
)
|
||||
const onlinePaymentStatusMessage = computed(() => {
|
||||
const status = onlinePaymentStatus.value?.status ?? onlineQrRecharge.value?.status
|
||||
if (status === 1) return '请使用对应支付方式扫码完成支付'
|
||||
if (status === 2) return '支付已成功,钱包正在入账,请稍候'
|
||||
if (status === 3) return '充值成功,钱包已到账'
|
||||
if (status === 4) return '充值订单已关闭,请重新发起充值'
|
||||
if (status === 5) return '充值订单已退款'
|
||||
if (status === 6) return '充值订单已驳回'
|
||||
return '正在获取支付状态'
|
||||
})
|
||||
|
||||
const createRules = computed<FormRules>(() => {
|
||||
const rules: FormRules = {
|
||||
@@ -419,12 +571,16 @@
|
||||
callback()
|
||||
return
|
||||
}
|
||||
if (Number(value) < MIN_RECHARGE_AMOUNT) {
|
||||
callback(new Error(`充值金额最小为 ¥${MIN_RECHARGE_AMOUNT.toFixed(2)}`))
|
||||
if (Number(value) < minimumAmountYuan.value) {
|
||||
callback(new Error(`充值金额最小为 ¥${minimumAmountYuan.value.toFixed(2)}`))
|
||||
return
|
||||
}
|
||||
if (Number(value) > MAX_RECHARGE_AMOUNT) {
|
||||
callback(new Error(`充值金额最大为 ¥${MAX_RECHARGE_AMOUNT.toFixed(2)}`))
|
||||
if (Number(value) > maximumAmountYuan.value) {
|
||||
callback(
|
||||
new Error(
|
||||
`充值金额最大为 ¥${maximumAmountYuan.value.toLocaleString('zh-CN', { minimumFractionDigits: 2 })}`
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
callback()
|
||||
@@ -435,9 +591,10 @@
|
||||
payment_method: [{ required: true, message: '请选择支付方式', trigger: 'change' }],
|
||||
shop_id: [{ required: true, message: '请选择目标店铺', trigger: 'change' }]
|
||||
}
|
||||
if (createForm.payment_method === 'offline') {
|
||||
if (createMode.value === 'offline') {
|
||||
rules.payment_voucher_key = [{ required: true, message: '请上传支付凭证', trigger: 'change' }]
|
||||
}
|
||||
if (createMode.value === 'online') delete rules.shop_id
|
||||
return rules
|
||||
})
|
||||
|
||||
@@ -452,13 +609,13 @@
|
||||
})
|
||||
|
||||
const createForm = reactive<{
|
||||
amount: number
|
||||
payment_method: string
|
||||
amount: number | null
|
||||
payment_method: AgentRechargePaymentMethod | ''
|
||||
shop_id: number | null
|
||||
payment_voucher_key: string[]
|
||||
remark: string
|
||||
}>({
|
||||
amount: MIN_RECHARGE_AMOUNT,
|
||||
amount: OFFLINE_MIN_RECHARGE_AMOUNT,
|
||||
payment_method: '',
|
||||
shop_id: null,
|
||||
payment_voucher_key: [],
|
||||
@@ -499,11 +656,19 @@
|
||||
const getPaymentMethodText = (method: AgentRechargePaymentMethod): string => {
|
||||
const methodMap: Record<AgentRechargePaymentMethod, string> = {
|
||||
wechat: '微信在线支付',
|
||||
alipay: '支付宝在线支付',
|
||||
offline: '线下转账'
|
||||
}
|
||||
return methodMap[method] || method
|
||||
}
|
||||
|
||||
const getRechargeSourceText = (row: AgentRecharge): string => {
|
||||
if (row.recharge_source_name) return row.recharge_source_name
|
||||
if (row.recharge_source === 'agent_online') return '代理在线自充'
|
||||
if (row.recharge_source === 'platform_offline') return '平台线下代充'
|
||||
return '-'
|
||||
}
|
||||
|
||||
// 动态列配置
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{
|
||||
@@ -530,6 +695,12 @@
|
||||
minWidth: 150,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'recharge_source',
|
||||
label: '充值来源',
|
||||
width: 140,
|
||||
formatter: (row: AgentRecharge) => getRechargeSourceText(row)
|
||||
},
|
||||
{
|
||||
prop: 'amount',
|
||||
label: '充值金额',
|
||||
@@ -635,7 +806,17 @@
|
||||
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
loadShops()
|
||||
if (isPlatformAccount.value) loadShops()
|
||||
document.addEventListener('visibilitychange', handleDocumentVisibilityChange)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopPaymentStatusPolling()
|
||||
document.removeEventListener('visibilitychange', handleDocumentVisibilityChange)
|
||||
})
|
||||
|
||||
onDeactivated(() => {
|
||||
stopPaymentStatusPolling()
|
||||
})
|
||||
|
||||
let isFirstActivation = true
|
||||
@@ -643,6 +824,9 @@
|
||||
if (!isFirstActivation) {
|
||||
getTableData()
|
||||
}
|
||||
if (onlineQrDialogVisible.value && document.visibilityState === 'visible') {
|
||||
startPaymentStatusPolling()
|
||||
}
|
||||
isFirstActivation = false
|
||||
})
|
||||
|
||||
@@ -693,13 +877,20 @@
|
||||
|
||||
// 获取充值订单列表
|
||||
const getTableData = async () => {
|
||||
if (!canViewRecharge.value) {
|
||||
rechargeList.value = []
|
||||
pagination.total = 0
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const params: AgentRechargeQueryParams = {
|
||||
page: pagination.page,
|
||||
page_size: pagination.page_size,
|
||||
shop_id: searchForm.shop_id,
|
||||
shop_id: isPlatformAccount.value ? searchForm.shop_id : undefined,
|
||||
status: searchForm.status,
|
||||
recharge_source: searchForm.recharge_source,
|
||||
start_date: searchForm.start_date || undefined,
|
||||
end_date: searchForm.end_date || undefined
|
||||
}
|
||||
@@ -737,8 +928,9 @@
|
||||
}
|
||||
|
||||
const exportQuery = computed(() => ({
|
||||
shop_id: searchForm.shop_id,
|
||||
shop_id: isPlatformAccount.value ? searchForm.shop_id : undefined,
|
||||
status: searchForm.status,
|
||||
recharge_source: searchForm.recharge_source,
|
||||
start_date: searchForm.start_date || searchForm.dateRange?.[0],
|
||||
end_date: searchForm.end_date || searchForm.dateRange?.[1]
|
||||
}))
|
||||
@@ -761,22 +953,56 @@
|
||||
|
||||
// 显示创建订单对话框
|
||||
const showCreateDialog = async () => {
|
||||
// 重新加载店铺列表,确保获取最新数据
|
||||
await loadShops()
|
||||
createForm.payment_method = ''
|
||||
createMode.value = isAgentAccount.value ? 'online' : 'offline'
|
||||
resetCreateForm()
|
||||
|
||||
if (createMode.value === 'online') {
|
||||
await loadPaymentMethods()
|
||||
} else {
|
||||
// 重新加载店铺列表,确保获取最新数据
|
||||
await loadShops()
|
||||
}
|
||||
|
||||
createDialogVisible.value = true
|
||||
}
|
||||
|
||||
const resetCreateForm = () => {
|
||||
createForm.amount = minimumAmountYuan.value
|
||||
createForm.payment_method = ''
|
||||
createForm.shop_id = null
|
||||
createForm.payment_voucher_key = []
|
||||
createForm.remark = ''
|
||||
onlineRequestId.value = null
|
||||
onlineCreateRetried.value = false
|
||||
voucherUploading.value = false
|
||||
uploadRef.value?.clearFiles(false)
|
||||
}
|
||||
|
||||
// 对话框关闭后的清理
|
||||
const handleCreateDialogClosed = () => {
|
||||
createFormRef.value?.resetFields()
|
||||
createForm.amount = MIN_RECHARGE_AMOUNT
|
||||
createForm.payment_method = ''
|
||||
createForm.shop_id = null
|
||||
createForm.payment_voucher_key = []
|
||||
createForm.remark = ''
|
||||
voucherUploading.value = false
|
||||
uploadRef.value?.clearFiles(false)
|
||||
resetCreateForm()
|
||||
}
|
||||
|
||||
// 加载代理在线充值可用支付方式
|
||||
const loadPaymentMethods = async () => {
|
||||
paymentMethodsLoading.value = true
|
||||
onlinePaymentMethods.value = []
|
||||
try {
|
||||
const res = await AgentRechargeService.getPaymentMethods()
|
||||
if (res.code === 0) {
|
||||
onlinePaymentMethods.value = Array.isArray(res.data?.methods) ? res.data.methods : []
|
||||
paymentMethodsBounds.min_amount = Number(res.data?.min_amount) || 10000
|
||||
paymentMethodsBounds.max_amount = Number(res.data?.max_amount) || 100000000
|
||||
createForm.amount = minimumAmountYuan.value
|
||||
} else {
|
||||
ElMessage.warning(res.msg || '当前暂无可用在线支付方式')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载在线支付方式失败:', error)
|
||||
} finally {
|
||||
paymentMethodsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 创建充值订单
|
||||
@@ -788,36 +1014,199 @@
|
||||
return
|
||||
}
|
||||
|
||||
await formRef.validate(async (valid) => {
|
||||
if (valid) {
|
||||
createLoading.value = true
|
||||
try {
|
||||
const data: CreateAgentRechargeRequest = {
|
||||
amount: Math.round(createForm.amount * 100), // 元转分
|
||||
payment_method: createForm.payment_method as AgentRechargePaymentMethod,
|
||||
shop_id: createForm.shop_id!,
|
||||
remark: createForm.remark || undefined
|
||||
}
|
||||
const valid = await formRef.validate().catch(() => false)
|
||||
if (!valid || createForm.amount == null || !createForm.payment_method) return
|
||||
|
||||
if (
|
||||
createForm.payment_method === 'offline' &&
|
||||
hasVoucherKeys(createForm.payment_voucher_key)
|
||||
) {
|
||||
data.payment_voucher_key = toVoucherKeyList(createForm.payment_voucher_key)
|
||||
}
|
||||
createLoading.value = true
|
||||
try {
|
||||
if (createMode.value === 'online') {
|
||||
await handleOnlineRechargeCreate(createForm.amount, createForm.payment_method)
|
||||
return
|
||||
}
|
||||
|
||||
await AgentRechargeService.createAgentRecharge(data)
|
||||
ElMessage.success('充值订单创建成功')
|
||||
createDialogVisible.value = false
|
||||
formRef.resetFields()
|
||||
await getTableData()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
createLoading.value = false
|
||||
if (!createForm.shop_id) {
|
||||
ElMessage.warning('请选择目标店铺')
|
||||
return
|
||||
}
|
||||
|
||||
const voucherKeys = toVoucherKeyList(createForm.payment_voucher_key)
|
||||
if (!hasVoucherKeys(voucherKeys)) {
|
||||
ElMessage.warning('请上传支付凭证')
|
||||
return
|
||||
}
|
||||
|
||||
const data: CreateAgentRechargeRequest = {
|
||||
amount: amountYuanToFen(createForm.amount),
|
||||
payment_method: 'offline',
|
||||
shop_id: createForm.shop_id,
|
||||
payment_voucher_key: voucherKeys,
|
||||
remark: createForm.remark || undefined
|
||||
}
|
||||
|
||||
const res = await AgentRechargeService.createAgentRecharge(data)
|
||||
if (res.code !== 0) {
|
||||
ElMessage.error(res.msg || '充值订单创建失败')
|
||||
return
|
||||
}
|
||||
|
||||
ElMessage.success('充值订单创建成功')
|
||||
createDialogVisible.value = false
|
||||
await getTableData()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
createLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleOnlineRechargeCreate = async (
|
||||
amountYuan: number,
|
||||
paymentMethod: AgentRechargePaymentMethod
|
||||
) => {
|
||||
if (paymentMethod === 'offline') return
|
||||
|
||||
const requestId = onlineRequestId.value || createOnlineRechargeRequestId()
|
||||
onlineRequestId.value = requestId
|
||||
onlineCreateRetried.value = false
|
||||
|
||||
const request: CreateAgentRechargeRequest = {
|
||||
amount: amountYuanToFen(amountYuan),
|
||||
payment_method: paymentMethod,
|
||||
request_id: requestId
|
||||
}
|
||||
|
||||
let attempt = 0
|
||||
while (attempt < 2) {
|
||||
try {
|
||||
const res = await AgentRechargeService.createAgentRecharge(request)
|
||||
if (res.code !== 0) {
|
||||
ElMessage.error(res.msg || '在线充值创建失败')
|
||||
onlineRequestId.value = null
|
||||
return
|
||||
}
|
||||
|
||||
if (!res.data?.qr_content) {
|
||||
ElMessage.error('支付接口未返回有效二维码,请刷新后重试')
|
||||
return
|
||||
}
|
||||
|
||||
onlineQrRecharge.value = res.data
|
||||
onlinePaymentStatus.value = null
|
||||
qrContent.value = res.data.qr_content
|
||||
onlineQrDialogVisible.value = true
|
||||
createDialogVisible.value = false
|
||||
onlineRequestId.value = null
|
||||
await getTableData()
|
||||
return
|
||||
} catch (error) {
|
||||
if (attempt === 0 && shouldRetryOnlineCreate(error)) {
|
||||
attempt += 1
|
||||
onlineCreateRetried.value = true
|
||||
continue
|
||||
}
|
||||
|
||||
const normalized = normalizeApiError(error)
|
||||
if (normalized.kind === 'validation' || normalized.kind === 'conflict') {
|
||||
ElMessage.error(normalized.message)
|
||||
} else {
|
||||
ElMessage.warning('在线充值请求结果未知,请勿重复提交;可关闭后刷新列表确认')
|
||||
}
|
||||
onlineRequestId.value = null
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const stopPaymentStatusPolling = () => {
|
||||
if (paymentStatusTimer.value) {
|
||||
clearInterval(paymentStatusTimer.value)
|
||||
paymentStatusTimer.value = null
|
||||
}
|
||||
}
|
||||
|
||||
const loadOnlinePaymentStatus = async () => {
|
||||
const rechargeId = onlineQrRecharge.value?.id
|
||||
if (!rechargeId || !onlineQrDialogVisible.value || document.visibilityState !== 'visible')
|
||||
return
|
||||
|
||||
paymentStatusLoading.value = true
|
||||
try {
|
||||
const res = await AgentRechargeService.getPaymentStatus(rechargeId)
|
||||
if (res.code !== 0) return
|
||||
|
||||
onlinePaymentStatus.value = res.data
|
||||
if (onlineQrRecharge.value) {
|
||||
onlineQrRecharge.value = {
|
||||
...onlineQrRecharge.value,
|
||||
status: res.data.status,
|
||||
status_name: res.data.status_name || onlineQrRecharge.value.status_name,
|
||||
paid_at: res.data.paid_at,
|
||||
completed_at: res.data.completed_at
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (isOnlineRechargeTerminal(res.data.status)) {
|
||||
stopPaymentStatusPolling()
|
||||
if (res.data.status === 3) {
|
||||
await Promise.all([getTableData(), refreshAgentWalletBalance()])
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('查询在线充值状态失败:', error)
|
||||
} finally {
|
||||
paymentStatusLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const startPaymentStatusPolling = () => {
|
||||
stopPaymentStatusPolling()
|
||||
void loadOnlinePaymentStatus()
|
||||
paymentStatusTimer.value = setInterval(() => {
|
||||
if (!onlineQrDialogVisible.value || document.visibilityState !== 'visible') {
|
||||
stopPaymentStatusPolling()
|
||||
return
|
||||
}
|
||||
void loadOnlinePaymentStatus()
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
const handleDocumentVisibilityChange = () => {
|
||||
if (document.visibilityState === 'visible' && onlineQrDialogVisible.value) {
|
||||
startPaymentStatusPolling()
|
||||
} else if (document.visibilityState === 'hidden') {
|
||||
stopPaymentStatusPolling()
|
||||
}
|
||||
}
|
||||
|
||||
watch(onlineQrDialogVisible, (visible) => {
|
||||
if (visible) {
|
||||
startPaymentStatusPolling()
|
||||
} else {
|
||||
stopPaymentStatusPolling()
|
||||
}
|
||||
})
|
||||
|
||||
const handleQrDialogClosed = () => {
|
||||
stopPaymentStatusPolling()
|
||||
qrContent.value = ''
|
||||
onlineQrRecharge.value = null
|
||||
onlinePaymentStatus.value = null
|
||||
onlineWalletBalance.value = null
|
||||
paymentStatusLoading.value = false
|
||||
}
|
||||
|
||||
const refreshAgentWalletBalance = async () => {
|
||||
if (!isAgentAccount.value) return
|
||||
|
||||
try {
|
||||
const res = await CommissionService.getShopFundSummary({ page: 1, page_size: 100 })
|
||||
const currentShopId = Number(userStore.info.shop_id)
|
||||
const currentShop =
|
||||
res.code === 0 ? res.data.items?.find((item) => item.shop_id === currentShopId) : null
|
||||
if (currentShop) onlineWalletBalance.value = currentShop.main_balance
|
||||
} catch (error) {
|
||||
console.error('刷新代理钱包余额失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 显示确认支付对话框
|
||||
@@ -935,4 +1324,38 @@
|
||||
.agent-recharge-page {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.online-recharge-empty-hint {
|
||||
margin-top: 8px;
|
||||
color: var(--el-color-danger);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.online-recharge-qr-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
&__summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
color: var(--el-text-color-regular);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__hint {
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
&__loading {
|
||||
color: var(--el-color-primary);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user