fix: 订单详情支付凭证, 退款
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 6m34s
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 6m34s
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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 手机号
|
||||||
|
|||||||
@@ -1357,7 +1357,8 @@
|
|||||||
{
|
{
|
||||||
prop: 'series_name',
|
prop: 'series_name',
|
||||||
label: '套餐系列',
|
label: '套餐系列',
|
||||||
minWidth: 120
|
minWidth: 180,
|
||||||
|
showOverflowTooltip: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'device_model',
|
prop: 'device_model',
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user