fix: 套餐
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<ElDialog v-model="visible" title="套餐充值" width="600px" @closed="handleDialogClosed">
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||
<ElDialog v-model="visible" title="套餐充值" width="30%" @closed="handleDialogClosed">
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||
<ElFormItem label="资产信息">
|
||||
<span style="font-weight: bold; color: #409eff">
|
||||
{{ assetIdentifier }}
|
||||
</span>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="选择套餐" prop="package_id">
|
||||
<ElFormItem label="套餐" prop="package_id">
|
||||
<ElSelect
|
||||
v-model="form.package_id"
|
||||
placeholder="请选择套餐"
|
||||
@@ -21,9 +21,9 @@
|
||||
>
|
||||
<template v-if="!props.seriesId">
|
||||
<ElOption :value="null" disabled>
|
||||
<span style="color: var(--el-text-color-warning)"
|
||||
>该设备未关联套餐系列,无法购买套餐</span
|
||||
>
|
||||
<span style="color: var(--el-text-color-warning)">
|
||||
该设备未关联套餐系列,无法购买套餐
|
||||
</span>
|
||||
</ElOption>
|
||||
</template>
|
||||
<ElOption
|
||||
@@ -35,15 +35,38 @@
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<span>{{ pkg.package_name }}</span>
|
||||
<span style="font-size: 12px; color: var(--el-text-color-secondary)">
|
||||
¥{{ ((pkg.cost_price || 0) / 100).toFixed(2) }}
|
||||
<template v-if="pkg.is_gift">
|
||||
<el-tag type="warning" size="small">赠送</el-tag>
|
||||
</template>
|
||||
<template v-else> ¥{{ ((pkg.cost_price || 0) / 100).toFixed(2) }} </template>
|
||||
</span>
|
||||
</div>
|
||||
</ElOption>
|
||||
</ElSelect>
|
||||
<template v-if="noSeriesId">
|
||||
<div style="color: var(--el-text-color-warning); font-size: 12px; margin-top: 4px">
|
||||
该设备未关联套餐系列,无法购买套餐
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="packagesEmptyWithSeriesId">
|
||||
<div style="color: var(--el-text-color-info); font-size: 12px; margin-top: 4px">
|
||||
该系列暂无可用套餐
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="selectedPackageIsGift">
|
||||
<div style="color: var(--el-color-warning); font-size: 12px; margin-top: 4px">
|
||||
提示: 赠送套餐只能使用线下支付方式,且必须上传支付凭证
|
||||
</div>
|
||||
</template>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="支付方式" prop="payment_method">
|
||||
<ElSelect v-model="form.payment_method" placeholder="请选择支付方式" style="width: 100%">
|
||||
<ElOption label="钱包支付" value="wallet" />
|
||||
<ElSelect
|
||||
v-model="form.payment_method"
|
||||
placeholder="请选择支付方式"
|
||||
style="width: 100%"
|
||||
:disabled="selectedPackageIsGift"
|
||||
>
|
||||
<ElOption v-if="!selectedPackageIsGift" label="钱包支付" value="wallet" />
|
||||
<ElOption v-if="showOfflinePayment" label="线下支付" value="offline" />
|
||||
</ElSelect>
|
||||
<div style="margin-top: 8px; font-size: 12px; color: var(--el-text-color-secondary)">
|
||||
@@ -79,24 +102,32 @@
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="handleCancel">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleConfirm" :loading="loading"> 确认充值 </ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="handleConfirm"
|
||||
:loading="loading || voucherUploading"
|
||||
:disabled="voucherUploading"
|
||||
>
|
||||
{{ voucherUploading ? '图片上传中...' : '提交' }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { computed, nextTick, reactive, ref, watch } from 'vue'
|
||||
import {
|
||||
ElButton,
|
||||
ElDialog,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElSelect,
|
||||
ElOption,
|
||||
ElButton,
|
||||
ElIcon,
|
||||
ElMessage,
|
||||
ElUpload,
|
||||
ElIcon
|
||||
ElOption,
|
||||
ElSelect,
|
||||
ElTag,
|
||||
ElUpload
|
||||
} from 'element-plus'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import type { FormInstance, FormRules, UploadFile, UploadInstance } from 'element-plus'
|
||||
@@ -124,8 +155,11 @@
|
||||
const formRef = ref<FormInstance>()
|
||||
const uploadRef = ref<UploadInstance>()
|
||||
const loading = ref(false)
|
||||
const voucherUploading = ref(false)
|
||||
const packageSearchLoading = ref(false)
|
||||
const packageOptions = ref<PackageResponse[]>([])
|
||||
const packagesEmptyWithSeriesId = ref(false)
|
||||
let activeVoucherUploadId = 0
|
||||
|
||||
const form = reactive({
|
||||
package_id: undefined as number | undefined,
|
||||
@@ -133,83 +167,130 @@
|
||||
payment_voucher_key: undefined as string | undefined
|
||||
})
|
||||
|
||||
const rules: FormRules = {
|
||||
package_id: [{ required: true, message: '请选择套餐', trigger: 'change' }],
|
||||
payment_method: [{ required: true, message: '请选择支付方式', trigger: 'change' }]
|
||||
}
|
||||
const rules = computed<FormRules>(() => {
|
||||
const baseRules: FormRules = {
|
||||
package_id: [{ required: true, message: '请选择套餐', trigger: 'change' }],
|
||||
payment_method: [{ required: true, message: '请选择支付方式', trigger: 'change' }]
|
||||
}
|
||||
|
||||
if (form.payment_method === 'offline') {
|
||||
baseRules.payment_voucher_key = [{ required: true, message: '请上传支付凭证' }]
|
||||
}
|
||||
|
||||
return baseRules
|
||||
})
|
||||
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
})
|
||||
|
||||
// 只有平台用户(user_type 1:超级管理员, 2:平台用户)才显示线下支付选项
|
||||
const showOfflinePayment = computed(() => {
|
||||
return userStore.info.user_type === 1 || userStore.info.user_type === 2
|
||||
})
|
||||
|
||||
// 监听对话框打开,加载套餐列表
|
||||
watch(visible, async (newVal) => {
|
||||
if (newVal) {
|
||||
if (!props.seriesId) {
|
||||
packageOptions.value = []
|
||||
return
|
||||
}
|
||||
await loadPackages()
|
||||
}
|
||||
const noSeriesId = computed(() => !props.seriesId)
|
||||
|
||||
const selectedPackageIsGift = computed(() => {
|
||||
if (!form.package_id) return false
|
||||
const selectedPackage = packageOptions.value.find((pkg) => pkg.id === form.package_id)
|
||||
return selectedPackage?.is_gift || false
|
||||
})
|
||||
|
||||
watch(visible, async (newVal) => {
|
||||
if (!newVal) return
|
||||
|
||||
form.package_id = undefined
|
||||
packageOptions.value = []
|
||||
packagesEmptyWithSeriesId.value = false
|
||||
|
||||
if (!props.seriesId) {
|
||||
return
|
||||
}
|
||||
|
||||
await loadPackages()
|
||||
})
|
||||
|
||||
// 监听 seriesId 变化,重新加载套餐列表
|
||||
watch(
|
||||
() => props.seriesId,
|
||||
async (newSeriesId) => {
|
||||
if (visible.value && newSeriesId) {
|
||||
if (!visible.value) return
|
||||
|
||||
form.package_id = undefined
|
||||
packageOptions.value = []
|
||||
packagesEmptyWithSeriesId.value = false
|
||||
|
||||
if (newSeriesId) {
|
||||
await loadPackages()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => form.package_id,
|
||||
(packageId) => {
|
||||
if (!packageId) return
|
||||
|
||||
const selectedPackage = packageOptions.value.find((pkg) => pkg.id === packageId)
|
||||
if (selectedPackage?.is_gift) {
|
||||
form.payment_method = 'offline'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
let loadPackagesPromise: Promise<void> | null = null
|
||||
|
||||
const loadPackages = async () => {
|
||||
if (!props.seriesId) return
|
||||
|
||||
// 防止重复调用
|
||||
if (loadPackagesPromise) return
|
||||
if (loadPackagesPromise) return loadPackagesPromise
|
||||
|
||||
try {
|
||||
packageSearchLoading.value = true
|
||||
loadPackagesPromise = PackageManageService.getPackages({
|
||||
series_id: props.seriesId,
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
status: 1, // 只获取启用的套餐
|
||||
shelf_status: 1 // 只获取已上架的套餐
|
||||
packageSearchLoading.value = true
|
||||
packagesEmptyWithSeriesId.value = false
|
||||
|
||||
loadPackagesPromise = PackageManageService.getPackages({
|
||||
series_id: props.seriesId,
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
status: 1,
|
||||
shelf_status: 1
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.code === 0 && response.data) {
|
||||
const items = response.data.items || []
|
||||
packageOptions.value = items
|
||||
packagesEmptyWithSeriesId.value = items.length === 0
|
||||
return
|
||||
}
|
||||
|
||||
packageOptions.value = []
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.code === 0 && response.data) {
|
||||
packageOptions.value = response.data.items || []
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
packageSearchLoading.value = false
|
||||
loadPackagesPromise = null
|
||||
})
|
||||
await loadPackagesPromise
|
||||
} catch (error: any) {
|
||||
console.error('加载套餐列表失败:', error)
|
||||
console.log(error?.message || '加载套餐列表失败')
|
||||
}
|
||||
.catch((error: any) => {
|
||||
console.error('加载套餐列表失败:', error)
|
||||
packageOptions.value = []
|
||||
})
|
||||
.finally(() => {
|
||||
packageSearchLoading.value = false
|
||||
loadPackagesPromise = null
|
||||
})
|
||||
|
||||
await loadPackagesPromise
|
||||
}
|
||||
|
||||
const handlePackageSearch = async (query: string) => {
|
||||
if (!props.seriesId) return
|
||||
|
||||
if (!query) {
|
||||
await loadPackages()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
packageSearchLoading.value = true
|
||||
packagesEmptyWithSeriesId.value = false
|
||||
|
||||
const response = await PackageManageService.getPackages({
|
||||
package_name: query || undefined,
|
||||
package_name: query,
|
||||
series_id: props.seriesId,
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
@@ -227,12 +308,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 处理支付凭证文件变化
|
||||
const invalidateVoucherUploadState = (clearFileKey = false) => {
|
||||
activeVoucherUploadId += 1
|
||||
voucherUploading.value = false
|
||||
if (clearFileKey) {
|
||||
form.payment_voucher_key = undefined
|
||||
}
|
||||
}
|
||||
|
||||
const handleVoucherFileChange = async (uploadFile: UploadFile) => {
|
||||
const file = uploadFile.raw
|
||||
if (!file) return
|
||||
|
||||
// 验证文件类型
|
||||
const isImage = file.type.startsWith('image/')
|
||||
if (!isImage) {
|
||||
ElMessage.error('只能上传图片文件!')
|
||||
@@ -240,7 +327,6 @@
|
||||
return
|
||||
}
|
||||
|
||||
// 验证文件大小
|
||||
const isLt5M = file.size / 1024 / 1024 < 5
|
||||
if (!isLt5M) {
|
||||
ElMessage.error('图片大小不能超过 5MB!')
|
||||
@@ -248,11 +334,13 @@
|
||||
return
|
||||
}
|
||||
|
||||
// 开始上传
|
||||
const requestId = ++activeVoucherUploadId
|
||||
voucherUploading.value = true
|
||||
form.payment_voucher_key = undefined
|
||||
|
||||
try {
|
||||
ElMessage.info('正在上传支付凭证...')
|
||||
|
||||
// 1. 获取上传 URL
|
||||
const uploadUrlRes = await StorageService.getUploadUrl({
|
||||
file_name: file.name,
|
||||
content_type: file.type,
|
||||
@@ -260,29 +348,38 @@
|
||||
})
|
||||
|
||||
if (uploadUrlRes.code !== 0) {
|
||||
if (requestId !== activeVoucherUploadId) return
|
||||
ElMessage.error(uploadUrlRes.msg || '获取上传地址失败')
|
||||
uploadRef.value?.clearFiles()
|
||||
return
|
||||
}
|
||||
|
||||
const { upload_url, file_key } = uploadUrlRes.data
|
||||
|
||||
// 2. 上传文件到对象存储
|
||||
await StorageService.uploadFile(upload_url, file, file.type)
|
||||
|
||||
// 3. 保存 file_key
|
||||
if (requestId !== activeVoucherUploadId) return
|
||||
|
||||
form.payment_voucher_key = file_key
|
||||
ElMessage.success('支付凭证上传成功')
|
||||
ElMessage.success('上传成功')
|
||||
nextTick(() => {
|
||||
formRef.value?.validateField('payment_voucher_key')
|
||||
})
|
||||
} catch (error: any) {
|
||||
if (requestId !== activeVoucherUploadId) return
|
||||
|
||||
console.error('上传支付凭证失败:', error)
|
||||
ElMessage.error(error?.message || '上传失败')
|
||||
ElMessage.error(error?.message || '上传失败,请重试')
|
||||
form.payment_voucher_key = undefined
|
||||
uploadRef.value?.clearFiles()
|
||||
} finally {
|
||||
if (requestId === activeVoucherUploadId) {
|
||||
voucherUploading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移除支付凭证
|
||||
const handleRemoveVoucher = () => {
|
||||
form.payment_voucher_key = undefined
|
||||
invalidateVoucherUploadState(true)
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
@@ -291,38 +388,39 @@
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (!formRef.value) return
|
||||
if (voucherUploading.value) {
|
||||
ElMessage.warning('支付凭证上传中,请稍候')
|
||||
return
|
||||
}
|
||||
|
||||
const valid = await formRef.value.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
|
||||
if (!form.package_id) {
|
||||
ElMessage.error('请选择套餐')
|
||||
return
|
||||
}
|
||||
|
||||
if (form.payment_method === 'offline' && !form.payment_voucher_key) {
|
||||
ElMessage.error('线下支付必须上传支付凭证')
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
|
||||
if (!form.package_id) {
|
||||
ElMessage.error('请选择套餐')
|
||||
return
|
||||
}
|
||||
|
||||
// 线下支付时验证支付凭证
|
||||
if (form.payment_method === 'offline' && !form.payment_voucher_key) {
|
||||
ElMessage.error('线下支付需要上传支付凭证')
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
|
||||
// 创建订单(后端接收数组,前端单选转为单元素数组)
|
||||
const data: CreateOrderRequest = {
|
||||
identifier: props.assetIdentifier,
|
||||
package_ids: [form.package_id],
|
||||
payment_method: form.payment_method
|
||||
}
|
||||
|
||||
// 线下支付时,添加支付凭证
|
||||
if (form.payment_method === 'offline' && form.payment_voucher_key) {
|
||||
data.payment_voucher_key = form.payment_voucher_key
|
||||
}
|
||||
|
||||
await OrderService.createOrder(data)
|
||||
|
||||
// 根据支付方式显示不同的成功消息
|
||||
if (form.payment_method === 'wallet') {
|
||||
ElMessage.success('订单创建成功,已自动完成支付')
|
||||
} else {
|
||||
@@ -333,7 +431,6 @@
|
||||
emit('confirm')
|
||||
emit('success')
|
||||
} catch (error: any) {
|
||||
// 用户取消确认对话框
|
||||
if (error === 'cancel') {
|
||||
return
|
||||
}
|
||||
@@ -347,8 +444,9 @@
|
||||
formRef.value?.resetFields()
|
||||
form.package_id = undefined
|
||||
form.payment_method = 'wallet'
|
||||
form.payment_voucher_key = undefined
|
||||
invalidateVoucherUploadState(true)
|
||||
packageOptions.value = []
|
||||
packagesEmptyWithSeriesId.value = false
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user