fix: 订单详情支付凭证, 退款
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 6m34s

This commit is contained in:
sexygoat
2026-05-11 18:01:22 +08:00
parent 1990a3ccba
commit 5d20d7dd3f
6 changed files with 81 additions and 40 deletions

View File

@@ -26,7 +26,7 @@
<ElFormItem label="申请退款金额" prop="requested_refund_amount"> <ElFormItem label="申请退款金额" prop="requested_refund_amount">
<ElInputNumber <ElInputNumber
v-model="formData.requested_refund_amount" v-model="formData.requested_refund_amount"
:min="0" :min="0.01"
:precision="2" :precision="2"
:step="1" :step="1"
style="width: 100%" style="width: 100%"
@@ -64,6 +64,7 @@
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
import { RefundService, OrderService } from '@/api/modules' import { RefundService, OrderService } from '@/api/modules'
import type { CreateRefundRequest, Order } from '@/types/api' import type { CreateRefundRequest, Order } from '@/types/api'
import { formatMoney, yuanToFen } from '@/utils/business/format'
interface Props { interface Props {
modelValue: boolean modelValue: boolean
@@ -89,20 +90,21 @@
const formData = reactive<{ const formData = reactive<{
order_id: number | null order_id: number | null
requested_refund_amount: number requested_refund_amount?: number
actual_received_amount: number actual_received_amount: number
refund_reason: string refund_reason: string
}>({ }>({
order_id: null, order_id: null,
requested_refund_amount: 0, requested_refund_amount: undefined,
actual_received_amount: 0, actual_received_amount: 0,
refund_reason: '' refund_reason: ''
}) })
const validateRefundAmount = (rule: any, value: number, callback: any) => { const validateRefundAmount = (rule: any, value: number, callback: any) => {
if (value === undefined || value === null) { const requestedRefundAmount = yuanToFen(value) ?? 0
callback(new Error('请输入申请退款金额')) if (value === undefined || value === null || value <= 0) {
} else if (value > formData.actual_received_amount) { callback(new Error('申请退款金额必须大于0'))
} else if (requestedRefundAmount > formData.actual_received_amount) {
callback(new Error('申请退款金额不能大于实收金额')) callback(new Error('申请退款金额不能大于实收金额'))
} else { } else {
callback() callback()
@@ -120,11 +122,9 @@
const hasInitialOrder = computed(() => !!props.initialOrderId || !!props.initialOrderNo) const hasInitialOrder = computed(() => !!props.initialOrderId || !!props.initialOrderNo)
const selectedOrderActualPaid = computed(() => { const selectedOrderActualPaid = computed(() => {
if (!formData.order_id) return 0 if (!formData.order_id) return '0.00'
const selectedOrder = orderSearchOptions.value.find((o) => o.id === formData.order_id) const selectedOrder = orderSearchOptions.value.find((o) => o.id === formData.order_id)
if (!selectedOrder?.actual_paid_amount) return 0 return formatMoney(selectedOrder?.actual_paid_amount ?? 0, false)
const amount = selectedOrder.actual_paid_amount / 100
return isNaN(amount) ? 0 : amount
}) })
const searchOrders = async (query: string) => { const searchOrders = async (query: string) => {
@@ -146,7 +146,7 @@
) )
if (matchedOrder) { if (matchedOrder) {
formData.order_id = matchedOrder.id formData.order_id = matchedOrder.id
formData.actual_received_amount = (matchedOrder.actual_paid_amount || 0) / 100 formData.actual_received_amount = matchedOrder.actual_paid_amount ?? 0
} }
} }
} }
@@ -164,14 +164,14 @@
} }
const selectedOrder = orderSearchOptions.value.find((o) => o.id === orderId) const selectedOrder = orderSearchOptions.value.find((o) => o.id === orderId)
if (selectedOrder) { if (selectedOrder) {
formData.actual_received_amount = selectedOrder.actual_paid_amount / 100 formData.actual_received_amount = selectedOrder.actual_paid_amount ?? 0
} }
} }
const handleDialogClosed = () => { const handleDialogClosed = () => {
formRef.value?.resetFields() formRef.value?.resetFields()
formData.order_id = null formData.order_id = null
formData.requested_refund_amount = 0 formData.requested_refund_amount = undefined
formData.actual_received_amount = 0 formData.actual_received_amount = 0
formData.refund_reason = '' formData.refund_reason = ''
orderSearchOptions.value = [] orderSearchOptions.value = []
@@ -186,8 +186,8 @@
try { try {
const data: CreateRefundRequest = { const data: CreateRefundRequest = {
order_id: formData.order_id!, order_id: formData.order_id!,
requested_refund_amount: formData.requested_refund_amount * 100, requested_refund_amount: yuanToFen(formData.requested_refund_amount) ?? 0,
actual_received_amount: formData.actual_received_amount * 100, actual_received_amount: formData.actual_received_amount,
refund_reason: formData.refund_reason || undefined refund_reason: formData.refund_reason || undefined
} }
await RefundService.createRefund(data) await RefundService.createRefund(data)

View File

@@ -28,6 +28,18 @@ export function formatMoney(fen: number | undefined, showSymbol = true): string
return showSymbol ? `¥${yuan}` : yuan return showSymbol ? `¥${yuan}` : yuan
} }
export function fenToYuan(fen: number | undefined | null): number {
if (fen === undefined || fen === null || Number.isNaN(fen)) return 0
return Number((fen / 100).toFixed(2))
}
export function yuanToFen(yuan: number | undefined | null): number | undefined {
if (yuan === undefined || yuan === null || Number.isNaN(yuan)) return undefined
return Math.round(yuan * 100)
}
/** /**
* 格式化手机号中间4位隐藏 * 格式化手机号中间4位隐藏
* @param phone 手机号 * @param phone 手机号

View File

@@ -1357,7 +1357,8 @@
{ {
prop: 'series_name', prop: 'series_name',
label: '套餐系列', label: '套餐系列',
minWidth: 120 minWidth: 180,
showOverflowTooltip: true
}, },
{ {
prop: 'device_model', prop: 'device_model',

View File

@@ -176,7 +176,7 @@
RejectRefundRequest, RejectRefundRequest,
ResubmitRefundRequest ResubmitRefundRequest
} from '@/types/api' } from '@/types/api'
import { formatDateTime } from '@/utils/business/format' import { formatDateTime, yuanToFen } from '@/utils/business/format'
import { RoutesAlias } from '@/router/routesAlias' import { RoutesAlias } from '@/router/routesAlias'
defineOptions({ name: 'RefundDetail' }) defineOptions({ name: 'RefundDetail' })
@@ -400,9 +400,7 @@
approveLoading.value = true approveLoading.value = true
try { try {
const data: ApproveRefundRequest = { const data: ApproveRefundRequest = {
approved_refund_amount: approveForm.approved_refund_amount approved_refund_amount: yuanToFen(approveForm.approved_refund_amount),
? approveForm.approved_refund_amount * 100
: undefined,
remark: approveForm.remark || undefined remark: approveForm.remark || undefined
} }
@@ -447,12 +445,8 @@
resubmitLoading.value = true resubmitLoading.value = true
try { try {
const data: ResubmitRefundRequest = { const data: ResubmitRefundRequest = {
requested_refund_amount: resubmitForm.requested_refund_amount requested_refund_amount: yuanToFen(resubmitForm.requested_refund_amount),
? resubmitForm.requested_refund_amount * 100 actual_received_amount: yuanToFen(resubmitForm.actual_received_amount),
: undefined,
actual_received_amount: resubmitForm.actual_received_amount
? resubmitForm.actual_received_amount * 100
: undefined,
refund_reason: resubmitForm.refund_reason || undefined refund_reason: resubmitForm.refund_reason || undefined
} }

View File

@@ -246,7 +246,7 @@
} from '@/types/api' } from '@/types/api'
import type { SearchFormItem } from '@/types' import type { SearchFormItem } from '@/types'
import { useCheckedColumns } from '@/composables/useCheckedColumns' import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { formatDateTime } from '@/utils/business/format' import { fenToYuan, formatDateTime, yuanToFen } from '@/utils/business/format'
import { RoutesAlias } from '@/router/routesAlias' import { RoutesAlias } from '@/router/routesAlias'
import { useAuth } from '@/composables/useAuth' import { useAuth } from '@/composables/useAuth'
@@ -699,9 +699,7 @@
approveLoading.value = true approveLoading.value = true
try { try {
const data: ApproveRefundRequest = { const data: ApproveRefundRequest = {
approved_refund_amount: approveForm.approved_refund_amount approved_refund_amount: yuanToFen(approveForm.approved_refund_amount),
? approveForm.approved_refund_amount * 100
: undefined,
remark: approveForm.remark || undefined remark: approveForm.remark || undefined
} }
const refundId = currentRefund.value?.id const refundId = currentRefund.value?.id
@@ -796,7 +794,7 @@
currentRefund.value = row currentRefund.value = row
// 预填充原有数据 // 预填充原有数据
resubmitForm.requested_refund_amount = row.requested_refund_amount / 100 resubmitForm.requested_refund_amount = row.requested_refund_amount / 100
resubmitForm.actual_received_amount = row.actual_received_amount / 100 resubmitForm.actual_received_amount = fenToYuan(row.actual_received_amount)
resubmitForm.refund_reason = row.refund_reason resubmitForm.refund_reason = row.refund_reason
resubmitDialogVisible.value = true resubmitDialogVisible.value = true
} }
@@ -821,12 +819,8 @@
const refundId = currentRefund.value?.id const refundId = currentRefund.value?.id
if (!refundId) return if (!refundId) return
const data: ResubmitRefundRequest = { const data: ResubmitRefundRequest = {
requested_refund_amount: resubmitForm.requested_refund_amount requested_refund_amount: yuanToFen(resubmitForm.requested_refund_amount),
? resubmitForm.requested_refund_amount * 100 actual_received_amount: yuanToFen(resubmitForm.actual_received_amount),
: undefined,
actual_received_amount: resubmitForm.actual_received_amount
? resubmitForm.actual_received_amount * 100
: undefined,
refund_reason: resubmitForm.refund_reason || undefined refund_reason: resubmitForm.refund_reason || undefined
} }

View File

@@ -60,13 +60,13 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { h, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { ElCard, ElButton, ElIcon, ElMessage, ElTable, ElTableColumn } from 'element-plus' import { ElButton, ElCard, ElIcon, ElImage, ElMessage, ElTable, ElTableColumn } from 'element-plus'
import { ArrowLeft, Loading } from '@element-plus/icons-vue' import { ArrowLeft, Loading } from '@element-plus/icons-vue'
import DetailPage from '@/components/common/DetailPage.vue' import DetailPage from '@/components/common/DetailPage.vue'
import type { DetailSection } from '@/components/common/DetailPage.vue' import type { DetailSection } from '@/components/common/DetailPage.vue'
import { OrderService } from '@/api/modules' import { OrderService, StorageService } from '@/api/modules'
import type { import type {
Order, Order,
OrderCommissionResult, OrderCommissionResult,
@@ -74,15 +74,18 @@
OrderOperatorType, OrderOperatorType,
OrderPaymentMethod OrderPaymentMethod
} from '@/types/api' } from '@/types/api'
import { useAuth } from '@/composables/useAuth'
import { formatDateTime } from '@/utils/business/format' import { formatDateTime } from '@/utils/business/format'
defineOptions({ name: 'OrderDetail' }) defineOptions({ name: 'OrderDetail' })
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const { hasAuth } = useAuth()
const loading = ref(false) const loading = ref(false)
const detailData = ref<Order | null>(null) const detailData = ref<Order | null>(null)
const paymentVoucherUrl = ref('')
// 格式化货币 - 将分转换为元 // 格式化货币 - 将分转换为元
const formatCurrency = (amount: number): string => { const formatCurrency = (amount: number): string => {
@@ -160,6 +163,17 @@
return roleMap[role] || role return roleMap[role] || role
} }
const loadPaymentVoucherUrl = async (fileKey?: string) => {
paymentVoucherUrl.value = ''
if (!fileKey || !hasAuth('orders:view_payment_voucher')) return
try {
paymentVoucherUrl.value = await StorageService.getDownloadUrl(fileKey)
} catch (error) {
console.error('Get payment voucher failed:', error)
}
}
// 详情页配置 // 详情页配置
const detailSections: DetailSection[] = [ const detailSections: DetailSection[] = [
{ {
@@ -185,6 +199,31 @@
formatter: (value) => formatter: (value) =>
value !== undefined && value !== null ? formatCurrency(value) : '-' value !== undefined && value !== null ? formatCurrency(value) : '-'
}, },
{
label: '支付凭证',
fullWidth: true,
render: (data) => {
if (!data.payment_voucher_key || !hasAuth('orders:view_payment_voucher')) {
return h('span', '-')
}
if (!paymentVoucherUrl.value) {
return h('span', '加载中...')
}
return h(ElImage, {
src: paymentVoucherUrl.value,
previewSrcList: [paymentVoucherUrl.value],
fit: 'cover',
style: {
width: '120px',
height: '120px',
borderRadius: '8px',
border: '1px solid var(--el-border-color-light)'
}
})
}
},
{ {
label: '买家类型', label: '买家类型',
formatter: (_, data) => (data.buyer_type ? getBuyerTypeText(data.buyer_type) : '-') formatter: (_, data) => (data.buyer_type ? getBuyerTypeText(data.buyer_type) : '-')
@@ -311,6 +350,7 @@
const res = await OrderService.getOrderById(id) const res = await OrderService.getOrderById(id)
if (res.code === 0) { if (res.code === 0) {
detailData.value = res.data detailData.value = res.data
await loadPaymentVoucherUrl(res.data.payment_voucher_key)
} }
} catch (error) { } catch (error) {
console.error(error) console.error(error)