-
-
-
![]()
-
-
- {{ file.fileName }}
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
上传凭证
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 支持图片、视频、PDF、Word、Excel等文件
- 取消
- 确认
+
+ 取消
+
+
+
+
+ 作废
+
+
+
+
+ 确认
+
+
+
+
+
+
+
+ 确认该笔操作 ¥{{ formatAmount(currentRow.amount) }}?
+
+
+
+ 取消
+
+
+ 确认
+
@@ -320,7 +427,7 @@
+
+
diff --git a/web-admin/src/views/pilot-service/channel-service-order/ServiceOrderTxnTable.vue b/web-admin/src/views/pilot-service/channel-service-order/ServiceOrderTxnTable.vue
new file mode 100644
index 00000000..69531626
--- /dev/null
+++ b/web-admin/src/views/pilot-service/channel-service-order/ServiceOrderTxnTable.vue
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+ {{ txnTypeLabel(row.txnType) }}
+
+
+
+
+
+ ¥{{ formatAmount(row.amount) }}
+
+
+
+
+
+ {{ txnStatusLabel(row.txnStatus) }}
+
+
+
+
+
+
+
+
+ {{ row.voidByName || '—' }} · {{ row.voidTime || '—' }}
+
+ —
+
+
+
+
+
+
+
+
+
+
diff --git a/web-admin/src/views/pilot-service/channel-service-order/index.vue b/web-admin/src/views/pilot-service/channel-service-order/index.vue
index faa56e9f..9d1daa54 100644
--- a/web-admin/src/views/pilot-service/channel-service-order/index.vue
+++ b/web-admin/src/views/pilot-service/channel-service-order/index.vue
@@ -139,15 +139,24 @@
-
+
- 查看
+
+ 查看
+
+
+
操作流水
+
- 编辑
+ 编辑
- 删除
+
+ 删除
+
@@ -167,7 +178,7 @@
type="primary"
@click="openRechargeDialog(row)"
>
- 发起充值
+ 发起充值
- 申请退款
+ 申请退款
- 购买产品
+ 购买产品
- 退订产品
+ 退订产品
- 作废
+ 作废
@@ -235,58 +246,46 @@
>
-
-
-
-
-
-
- ¥{{ formatAmount(row.agreementUnitPrice) }}
-
-
-
-
-
-
+
+
+
+
+
+
+
+ ¥{{ formatAmount(row.agreementUnitPrice) }}
+
+
+
+
+
+
+
-
-
-
-
-
-
-
- {{ txnTypeLabel(row.txnType) }}
-
-
-
-
- ¥{{ formatAmount(row.amount) }}
-
-
-
-
- {{ txnStatusLabel(row.txnStatus) }}
-
-
-
-
-
-
+
+
+
+
@@ -338,17 +337,6 @@
-
-
-
-
-
@@ -445,7 +430,9 @@
- 删除
+
+ 删除
+
@@ -491,18 +478,93 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
支持图片、视频、PDF、Word、Excel 等,可上传多个
+
+
- 取消
- 确认
+
+ 取消
+
+
+ 确认
+
@@ -511,15 +573,24 @@
退款金额(元)当前余额:¥{{ formatAmount(currentOrderForTxn?.currentBalance) }}
-
+
- 取消
- 确认
+
+ 取消
+
+
+ 确认
+
@@ -549,7 +620,9 @@
- 删除
+
+ 删除
+
@@ -563,8 +636,12 @@
- 取消
- 确认
+
+ 取消
+
+
+ 确认
+
@@ -597,8 +674,12 @@
- 取消
- 确认退订
+
+ 取消
+
+
+ 确认退订
+
@@ -641,9 +722,18 @@ import {
Loading,
Download,
Delete,
- VideoPlay
+ VideoPlay,
+ View,
+ List,
+ Edit,
+ Wallet,
+ Sell,
+ ShoppingCart,
+ Remove
} from '@element-plus/icons-vue'
import SystemViewDialog, { type ViewSection } from '@/components/business/system-view-dialog/index.vue'
+import ServiceOrderTxnTable from './ServiceOrderTxnTable.vue'
+import ServiceOrderTxnDialog from './ServiceOrderTxnDialog.vue'
import {
fetchChannelServiceOrderList,
fetchChannelServiceOrderDetail,
@@ -662,27 +752,20 @@ import {
} from '@/api/admin'
import { fetchUploadAttachment } from '@/api/business'
import { downloadAttachmentFile } from '@/utils/download-attachment'
+import AmountInput from '@/components/common/amount-input/index.vue'
+import { formatMoneyDisplay } from '@/utils/money-format'
import {
channelServiceOrderTypeLabel,
channelServiceOrderTypeTagType,
channelServiceOrderStatusLabel,
channelServiceOrderStatusTagType,
- agreementTypeLabel,
- txnTypeLabel,
- txnTypeTagType,
- txnStatusLabel,
- txnStatusTagType
+ agreementTypeLabel
} from '@/utils/status-tag-registry'
defineOptions({ name: 'ChannelServiceOrderManagement' })
// ── 工具函数 ──────────────────────────────────────────────────────────
-const formatAmount = (v: any): string => {
- if (v == null || v === '') return '0.00'
- const n = Number(v)
- if (Number.isNaN(n)) return '0.00'
- return n.toFixed(2)
-}
+const formatAmount = (v: any): string => formatMoneyDisplay(v, 2)
const displayValue = (v: any): string => {
if (v == null || v === '' || v === undefined) return '-'
@@ -881,14 +964,11 @@ const viewSections = computed((): ViewSection[] => {
})
}
- // Transaction timeline
- if (d.transactions?.length) {
- sections.push({
- title: '操作记录',
- fields: [],
- slot: 'txn-timeline'
- })
- }
+ sections.push({
+ title: '操作记录',
+ fields: [],
+ slot: 'txn-timeline'
+ })
return sections
})
@@ -939,7 +1019,6 @@ const initFormData = () => ({
orderType: 'yfk',
channelId: '',
deptName: '',
- prepayAmount: null as number | null,
channelAgreementId: '',
items: [] as any[],
remark: ''
@@ -950,22 +1029,6 @@ const formData = reactive(initFormData())
const formRules: FormRules = {
orderType: [{ required: true, message: '请选择订单类型', trigger: 'change' }],
channelId: [{ required: true, message: '请选择渠道', trigger: 'change' }],
- prepayAmount: [
- {
- validator: (_rule, value, callback) => {
- if (formData.orderType !== 'yfk') {
- callback()
- return
- }
- if (value == null || Number(value) <= 0) {
- callback(new Error('请输入充值金额'))
- return
- }
- callback()
- },
- trigger: 'blur'
- }
- ],
channelAgreementId: [
{
validator: (_rule, value, callback) => {
@@ -1158,7 +1221,6 @@ const formTotalAmount = computed(() =>
)
const handleOrderTypeChange = () => {
- formData.prepayAmount = null
formData.channelAgreementId = ''
formData.items = []
agreementOptions.value = []
@@ -1189,7 +1251,6 @@ const handleEdit = async (row: any) => {
formData.orderType = detail.orderType || 'yfk'
formData.channelId = detail.channelId || ''
formData.deptName = detail.deptName || ''
- formData.prepayAmount = detail.prepayAmount ?? null
formData.channelAgreementId = detail.channelAgreementId || ''
formData.remark = detail.remark || ''
@@ -1267,7 +1328,6 @@ const handleFormSubmit = async () => {
}
if (formData.orderType === 'yfk') {
- payload.prepayAmount = formData.prepayAmount
payload.channelAgreementId = formData.channelAgreementId
} else {
payload.channelAgreementId = formData.channelAgreementId
@@ -1322,32 +1382,67 @@ const handleDelete = async (row: any) => {
// ── 流水操作 ──────────────────────────────────────────────────────────
const currentOrderForTxn = ref
(null)
+const txnDialogVisible = ref(false)
+const txnDialogOrder = ref | null>(null)
+const txnDialogRef = ref | null>(null)
+
+const openTxnDialog = (row: any) => {
+ txnDialogOrder.value = row
+ txnDialogVisible.value = true
+}
+
+const onTxnDialogRefreshed = () => {
+ loadData()
+ if (viewVisible.value && viewData.value?.id === txnDialogOrder.value?.id) {
+ handleView(txnDialogOrder.value)
+ }
+}
// ── 发起充值 ──────────────────────────────────────────────────────────
const rechargeVisible = ref(false)
const rechargeLoading = ref(false)
+const rechargeUploadRef = ref<{ clearFiles?: () => void }>()
const rechargeForm = reactive({
amount: null as number | null,
- remark: ''
+ remark: '',
+ attachments: [] as any[]
})
const openRechargeDialog = (row: any) => {
currentOrderForTxn.value = row
rechargeForm.amount = null
rechargeForm.remark = ''
+ rechargeForm.attachments = []
rechargeVisible.value = true
}
+const handleRechargeAttachmentChange = (uploadFile: any) => {
+ const file = uploadFile.raw || uploadFile
+ if (!file) return
+ uploadAttachmentItem(file, rechargeForm.attachments, rechargeUploadRef, 'channel-service-order')
+}
+
+const removeRechargeAttachment = (index: number) => {
+ rechargeForm.attachments.splice(index, 1)
+}
+
const submitRecharge = async () => {
if (!rechargeForm.amount || rechargeForm.amount <= 0) {
ElMessage.warning('请输入充值金额')
return
}
+ if (hasUploadingAttachments(rechargeForm.attachments)) {
+ ElMessage.warning('附件正在上传,请稍候')
+ return
+ }
+ const attachmentUrls = joinAttachmentUrls(rechargeForm.attachments)
+ const attachmentPayload = attachmentUrls ? { financeSubmitAttachments: attachmentUrls } : {}
rechargeLoading.value = true
try {
const res: any = await fetchCreateRechargeTxn(currentOrderForTxn.value.id, {
amount: rechargeForm.amount,
- remark: rechargeForm.remark
+ remark: rechargeForm.remark,
+ ...attachmentPayload
})
const txn = res?.data ?? res
rechargeVisible.value = false
@@ -1358,12 +1453,13 @@ const submitRecharge = async () => {
cancelButtonText: '稍后再说',
type: 'info'
})
- await fetchSubmitTxnFinance(txn.id, {})
+ await fetchSubmitTxnFinance(txn.id, attachmentPayload)
ElMessage.success('充值成功,已发起财务确认')
} catch {
ElMessage.success('充值流水已创建')
}
- loadData()
+ onTxnDialogRefreshed()
+ txnDialogRef.value?.reload()
} catch (e: any) {
ElMessage.error(e?.message || '操作失败')
} finally {
@@ -1410,7 +1506,8 @@ const submitRefundTxn = async () => {
} catch {
ElMessage.success('退款流水已创建')
}
- loadData()
+ onTxnDialogRefreshed()
+ txnDialogRef.value?.reload()
} catch (e: any) {
ElMessage.error(e?.message || '操作失败')
} finally {
@@ -1533,7 +1630,8 @@ const submitPurchase = async () => {
} catch {
ElMessage.success('购买流水已创建')
}
- loadData()
+ onTxnDialogRefreshed()
+ txnDialogRef.value?.reload()
} catch (e: any) {
ElMessage.error(e?.message || '操作失败')
} finally {
@@ -1619,7 +1717,8 @@ const submitUnsubscribe = async () => {
} catch {
ElMessage.success('退订流水已创建')
}
- loadData()
+ onTxnDialogRefreshed()
+ txnDialogRef.value?.reload()
} catch (e: any) {
ElMessage.error(e?.message || '操作失败')
} finally {
@@ -1693,13 +1792,6 @@ async function uploadAttachmentItem(
uploadRef: { value?: { clearFiles?: () => void } },
folder: string
) {
- const maxSizeMb = 20
- if (file.size / 1024 / 1024 > maxSizeMb) {
- ElMessage.error(`文件大小不能超过 ${maxSizeMb}MB`)
- uploadRef.value?.clearFiles?.()
- return
- }
-
const tempId = `temp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
list.push({
id: tempId,
@@ -1812,6 +1904,12 @@ onMounted(() => {
}
}
+.view-inner-table-wrap {
+ margin-top: 0;
+ padding: 0;
+ border: none;
+}
+
.product-detail-table-wrap {
width: 100%;
diff --git a/web-admin/src/views/pilot-service/channel/ChannelViewDialog.vue b/web-admin/src/views/pilot-service/channel/ChannelViewDialog.vue
index 2914a88e..f5e37d03 100644
--- a/web-admin/src/views/pilot-service/channel/ChannelViewDialog.vue
+++ b/web-admin/src/views/pilot-service/channel/ChannelViewDialog.vue
@@ -414,6 +414,7 @@
import { Calendar, Close, CollectionTag, OfficeBuilding, User } from '@element-plus/icons-vue'
import ProtocolAttachmentReadonly from '@/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue'
import { downloadAttachmentFile } from '@/utils/download-attachment'
+ import { formatMoneyDisplay } from '@/utils/money-format'
import {
channelTypeLabel,
channelTypeTagType,
@@ -542,12 +543,7 @@
downloadAttachmentFile(file, { fullFileUrl })
}
- function formatAmount(val: unknown) {
- if (val === null || val === undefined || val === '') return '0.00'
- const num = Number(val)
- if (isNaN(num)) return '0.00'
- return num.toFixed(2)
- }
+ const formatAmount = (v: unknown): string => formatMoneyDisplay(v as any, 2)
async function loadAccountData() {
if (!props.channelId || accountLoaded.value) return
diff --git a/web-admin/src/views/pilot-service/channel/index.vue b/web-admin/src/views/pilot-service/channel/index.vue
index 1160cf2d..386151f2 100644
--- a/web-admin/src/views/pilot-service/channel/index.vue
+++ b/web-admin/src/views/pilot-service/channel/index.vue
@@ -283,7 +283,7 @@