Files
one-pipe-system/src/components/business/CreateRefundDialog.vue
luo 2706c52a47
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m21s
feat: 7月迭代
2026-07-28 14:00:22 +08:00

278 lines
9.0 KiB
Vue

<template>
<ElDialog v-model="dialogVisible" title="创建退款申请" width="40%" @closed="handleDialogClosed">
<ElForm ref="formRef" :model="formData" :rules="formRules" label-width="120px">
<ElFormItem label="订单号" prop="order_id">
<ElSelect
v-if="!hasInitialOrder"
v-model="formData.order_id"
filterable
remote
clearable
:remote-method="searchOrders"
:loading="orderSearchLoading"
placeholder="请输入订单号搜索"
style="width: 100%"
@change="handleOrderSelectChange"
>
<ElOption
v-for="order in orderSearchOptions"
:key="order.id"
:label="order.order_no"
:value="order.id"
/>
</ElSelect>
<ElInput v-else :model-value="props.initialOrderNo" disabled style="width: 100%" />
</ElFormItem>
<ElFormItem label="申请退款金额" prop="requested_refund_amount">
<ElInputNumber
v-model="formData.requested_refund_amount"
:min="0"
:precision="2"
:step="1"
style="width: 100%"
placeholder="请输入申请退款金额(元)"
/>
</ElFormItem>
<ElFormItem label="实收金额">
<ElInput :model-value="selectedOrderActualPaid" disabled style="width: 100%" />
</ElFormItem>
<ElFormItem label="退款原因" prop="refund_reason">
<ElInput
v-model="formData.refund_reason"
type="textarea"
:rows="3"
maxlength="1000"
show-word-limit
placeholder="请输入退款原因"
/>
</ElFormItem>
<ElFormItem label="备注">
<ElInput
v-model="formData.remark"
type="textarea"
:rows="2"
maxlength="500"
show-word-limit
placeholder="请输入备注"
/>
</ElFormItem>
<ElFormItem label="退款凭证" prop="refund_voucher_key">
<VoucherUpload
ref="uploadRef"
v-model="formData.refund_voucher_key"
voucher-name="退款凭证"
@uploading-change="voucherUploading = $event"
@change="formRef?.validateField('refund_voucher_key')"
@files-change="formData.attachments = $event"
/>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="dialogVisible = false">取消</ElButton>
<ElButton
type="primary"
@click="handleSubmit"
:loading="submitLoading || voucherUploading"
:disabled="voucherUploading"
>
{{ voucherUploading ? '凭证上传中...' : '确认创建' }}
</ElButton>
</div>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { RefundService, OrderService } from '@/api/modules'
import type { CreateRefundRequest, Order, RefundAttachment } from '@/types/api'
import { formatMoney, yuanToFen } from '@/utils/business/format'
import { getErrorMessage } from '@/utils/business'
import VoucherUpload from '@/components/business/VoucherUpload.vue'
interface Props {
modelValue: boolean
initialOrderId?: number
initialOrderNo?: string
initialPackageUsageId?: number
}
const props = defineProps<Props>()
const emit = defineEmits<{
'update:modelValue': [value: boolean]
success: []
}>()
const dialogVisible = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
const formRef = ref<FormInstance>()
const uploadRef = ref<InstanceType<typeof VoucherUpload>>()
const submitLoading = ref(false)
const voucherUploading = ref(false)
const orderSearchLoading = ref(false)
const orderSearchOptions = ref<Order[]>([])
const formData = reactive<{
order_id: number | null
package_usage_id?: number
requested_refund_amount?: number
actual_received_amount: number
refund_reason: string
remark: string
refund_voucher_key: string[]
attachments: RefundAttachment[]
}>({
order_id: null,
package_usage_id: undefined,
requested_refund_amount: undefined,
actual_received_amount: 0,
refund_reason: '',
remark: '',
refund_voucher_key: [],
attachments: []
})
const validateRefundAmount = (rule: any, value: number, callback: any) => {
const requestedRefundAmount = yuanToFen(value) ?? 0
if (value === undefined || value === null || value < 0) {
callback(new Error('申请退款金额不能小于0'))
} else if (requestedRefundAmount > formData.actual_received_amount) {
callback(new Error('申请退款金额不能大于实收金额'))
} else {
callback()
}
}
const formRules = reactive<FormRules>({
order_id: [{ required: true, message: '请选择订单', trigger: 'change' }],
requested_refund_amount: [
{ required: true, message: '请输入申请退款金额', trigger: 'blur' },
{ validator: validateRefundAmount, trigger: 'blur' }
],
refund_reason: [{ required: true, message: '请输入退款原因', trigger: 'blur' }],
refund_voucher_key: [{ required: true, message: '请上传退款凭证', trigger: 'change' }]
})
const hasInitialOrder = computed(() => !!props.initialOrderId || !!props.initialOrderNo)
const selectedOrderActualPaid = computed(() => {
if (!formData.order_id) return '0.00'
const selectedOrder = orderSearchOptions.value.find((o) => o.id === formData.order_id)
return formatMoney(selectedOrder?.actual_paid_amount ?? 0, false)
})
const searchOrders = async (query: string) => {
orderSearchLoading.value = true
try {
const params: any = {
page: 1,
page_size: 20,
payment_status: 2
}
if (query) {
params.order_no = query
}
const res = await OrderService.getOrders(params)
if (res.code === 0) {
orderSearchOptions.value = res.data.items || []
if (hasInitialOrder.value && props.initialOrderId) {
formData.order_id = props.initialOrderId
}
if (hasInitialOrder.value && props.initialOrderNo) {
const matchedOrder = orderSearchOptions.value.find(
(o) => o.order_no === props.initialOrderNo
)
if (matchedOrder) {
formData.order_id = matchedOrder.id
formData.actual_received_amount = matchedOrder.actual_paid_amount ?? 0
}
}
}
} catch (error) {
console.error('Search orders failed:', error)
} finally {
orderSearchLoading.value = false
}
}
const handleOrderSelectChange = (orderId: number | null) => {
if (!orderId) {
formData.actual_received_amount = 0
return
}
const selectedOrder = orderSearchOptions.value.find((o) => o.id === orderId)
if (selectedOrder) {
formData.actual_received_amount = selectedOrder.actual_paid_amount ?? 0
}
}
const handleDialogClosed = () => {
formRef.value?.resetFields()
voucherUploading.value = false
formData.order_id = null
formData.package_usage_id = undefined
formData.requested_refund_amount = undefined
formData.actual_received_amount = 0
formData.refund_reason = ''
formData.remark = ''
formData.refund_voucher_key = []
formData.attachments = []
orderSearchOptions.value = []
uploadRef.value?.clearFiles(false)
}
const handleSubmit = async () => {
if (!formRef.value) return
if (voucherUploading.value) {
ElMessage.warning('退款凭证上传中,请稍候')
return
}
await formRef.value.validate(async (valid) => {
if (valid) {
submitLoading.value = true
try {
const data: CreateRefundRequest = {
order_id: formData.order_id!,
requested_refund_amount: yuanToFen(formData.requested_refund_amount) ?? 0,
actual_received_amount: formData.actual_received_amount,
attachments: formData.attachments,
package_usage_id: formData.package_usage_id,
refund_reason: formData.refund_reason || undefined,
remark: formData.remark || undefined
}
await RefundService.createRefund(data)
ElMessage.success('退款申请创建成功')
dialogVisible.value = false
emit('success')
} catch (error) {
console.error(error)
ElMessage.error(getErrorMessage(error, '退款申请创建失败'))
} finally {
submitLoading.value = false
}
}
})
}
watch(dialogVisible, (val) => {
if (val) {
if (hasInitialOrder.value && props.initialOrderId) {
formData.order_id = props.initialOrderId
}
formData.package_usage_id = props.initialPackageUsageId
if (hasInitialOrder.value && props.initialOrderNo) {
searchOrders(props.initialOrderNo)
} else {
searchOrders('')
}
}
})
</script>