388 lines
11 KiB
Vue
388 lines
11 KiB
Vue
<template>
|
||
<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">
|
||
<ElSelect
|
||
v-model="form.package_id"
|
||
placeholder="请选择套餐"
|
||
filterable
|
||
remote
|
||
reserve-keyword
|
||
:remote-method="handlePackageSearch"
|
||
:loading="packageSearchLoading"
|
||
:disabled="!props.seriesId"
|
||
clearable
|
||
style="width: 100%"
|
||
>
|
||
<template v-if="!props.seriesId">
|
||
<ElOption value="__NO_PACKAGE__" disabled>
|
||
<span style="color: var(--el-text-color-warning)">
|
||
{{ noSeriesMessage }}
|
||
</span>
|
||
</ElOption>
|
||
</template>
|
||
<ElOption
|
||
v-for="pkg in packageOptions"
|
||
:key="pkg.id"
|
||
:label="`${pkg.package_name} (¥${((pkg.cost_price || 0) / 100).toFixed(2)})`"
|
||
:value="pkg.id"
|
||
>
|
||
<div style="display: flex; justify-content: space-between">
|
||
<span>{{ pkg.package_name }}</span>
|
||
<span style="font-size: 12px; color: var(--el-text-color-secondary)">
|
||
<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="margin-top: 4px; font-size: 12px; color: var(--el-text-color-warning)">
|
||
{{ noSeriesMessage }}
|
||
</div>
|
||
</template>
|
||
<template v-else-if="packagesEmptyWithSeriesId">
|
||
<div style="margin-top: 4px; font-size: 12px; color: var(--el-text-color-info)">
|
||
该系列暂无可用套餐
|
||
</div>
|
||
</template>
|
||
<template v-else-if="selectedPackageIsGift">
|
||
<div style="margin-top: 4px; font-size: 12px; color: var(--el-color-warning)">
|
||
提示: 赠送套餐只能使用线下支付方式,且必须上传支付凭证
|
||
</div>
|
||
</template>
|
||
</ElFormItem>
|
||
<ElFormItem label="支付方式" prop="payment_method">
|
||
<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)">
|
||
<template v-if="form.payment_method === 'wallet'">
|
||
提示: 使用钱包支付时,订单将直接完成
|
||
</template>
|
||
<template v-else-if="form.payment_method === 'offline'">
|
||
提示: 线下支付订单需要手动确认支付
|
||
</template>
|
||
</div>
|
||
</ElFormItem>
|
||
<ElFormItem
|
||
v-if="form.payment_method === 'offline'"
|
||
label="支付凭证"
|
||
prop="payment_voucher_key"
|
||
>
|
||
<VoucherUpload
|
||
ref="uploadRef"
|
||
v-model="form.payment_voucher_key"
|
||
voucher-name="支付凭证"
|
||
@uploading-change="voucherUploading = $event"
|
||
@change="formRef?.validateField('payment_voucher_key')"
|
||
/>
|
||
</ElFormItem>
|
||
</ElForm>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<ElButton @click="handleCancel">取消</ElButton>
|
||
<ElButton
|
||
type="primary"
|
||
@click="handleConfirm"
|
||
:loading="loading || voucherUploading"
|
||
:disabled="voucherUploading"
|
||
>
|
||
{{ voucherUploading ? '凭证上传中...' : '提交' }}
|
||
</ElButton>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, reactive, ref, watch } from 'vue'
|
||
import {
|
||
ElButton,
|
||
ElDialog,
|
||
ElForm,
|
||
ElFormItem,
|
||
ElMessage,
|
||
ElOption,
|
||
ElSelect,
|
||
ElTag
|
||
} from 'element-plus'
|
||
import type { FormInstance, FormRules } from 'element-plus'
|
||
import { OrderService, PackageManageService } from '@/api/modules'
|
||
import type { CreateOrderRequest, PackageResponse } from '@/types/api'
|
||
import { useUserStore } from '@/store/modules/user'
|
||
import VoucherUpload from '@/components/business/VoucherUpload.vue'
|
||
import { hasVoucherKeys, toVoucherKeyList } from '@/utils/business'
|
||
import { normalizeApiError } from '@/utils/business/apiError'
|
||
|
||
interface Props {
|
||
modelValue: boolean
|
||
assetIdentifier: string
|
||
assetType: 'card' | 'device'
|
||
seriesId?: number
|
||
}
|
||
|
||
interface Emits {
|
||
(e: 'update:modelValue', value: boolean): void
|
||
(e: 'confirm'): void
|
||
(e: 'success'): void
|
||
}
|
||
|
||
const props = defineProps<Props>()
|
||
const emit = defineEmits<Emits>()
|
||
|
||
const userStore = useUserStore()
|
||
const formRef = ref<FormInstance>()
|
||
const uploadRef = ref<InstanceType<typeof VoucherUpload>>()
|
||
const loading = ref(false)
|
||
const voucherUploading = ref(false)
|
||
const packageSearchLoading = ref(false)
|
||
const packageOptions = ref<PackageResponse[]>([])
|
||
const packagesEmptyWithSeriesId = ref(false)
|
||
|
||
const form = reactive({
|
||
package_id: undefined as number | undefined,
|
||
payment_method: 'wallet' as 'wallet' | 'offline',
|
||
payment_voucher_key: [] as string[]
|
||
})
|
||
|
||
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)
|
||
})
|
||
|
||
const showOfflinePayment = computed(() => {
|
||
return userStore.info.user_type === 1 || userStore.info.user_type === 2
|
||
})
|
||
|
||
const noSeriesId = computed(() => !props.seriesId)
|
||
const noSeriesMessage = computed(
|
||
() => `该${props.assetType === 'card' ? '卡' : '设备'}未关联套餐系列,无法购买套餐`
|
||
)
|
||
|
||
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()
|
||
})
|
||
|
||
watch(
|
||
() => props.seriesId,
|
||
async (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 loadPackagesPromise
|
||
|
||
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 = []
|
||
})
|
||
.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,
|
||
series_id: props.seriesId,
|
||
page: 1,
|
||
page_size: 20,
|
||
status: 1,
|
||
shelf_status: 1
|
||
})
|
||
|
||
if (response.code === 0 && response.data) {
|
||
packageOptions.value = response.data.items || []
|
||
}
|
||
} catch (error: any) {
|
||
console.error('搜索套餐失败:', error)
|
||
} finally {
|
||
packageSearchLoading.value = false
|
||
}
|
||
}
|
||
|
||
const handleCancel = () => {
|
||
visible.value = false
|
||
}
|
||
|
||
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' && !hasVoucherKeys(form.payment_voucher_key)) {
|
||
ElMessage.error('线下支付必须上传支付凭证')
|
||
return
|
||
}
|
||
|
||
loading.value = true
|
||
|
||
try {
|
||
const data: CreateOrderRequest = {
|
||
identifier: props.assetIdentifier,
|
||
package_ids: [form.package_id],
|
||
payment_method: form.payment_method
|
||
}
|
||
|
||
if (form.payment_method === 'offline' && hasVoucherKeys(form.payment_voucher_key)) {
|
||
data.payment_voucher_key = toVoucherKeyList(form.payment_voucher_key)
|
||
}
|
||
|
||
const response = await OrderService.createOrder(data)
|
||
if (response.code !== 0) {
|
||
ElMessage.error(response.msg || '订单创建失败')
|
||
return
|
||
}
|
||
|
||
if (form.payment_method === 'wallet') {
|
||
ElMessage.success('订单创建成功,已自动完成支付')
|
||
} else {
|
||
ElMessage.success('订单创建成功')
|
||
}
|
||
|
||
visible.value = false
|
||
emit('confirm')
|
||
emit('success')
|
||
} catch (error: any) {
|
||
if (error === 'cancel') {
|
||
return
|
||
}
|
||
console.error('创建订单失败:', error)
|
||
ElMessage.error(normalizeApiError(error).message)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
const handleDialogClosed = () => {
|
||
formRef.value?.resetFields()
|
||
form.package_id = undefined
|
||
form.payment_method = 'wallet'
|
||
form.payment_voucher_key = []
|
||
voucherUploading.value = false
|
||
packageOptions.value = []
|
||
packagesEmptyWithSeriesId.value = false
|
||
uploadRef.value?.clearFiles(false)
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.dialog-footer {
|
||
display: flex;
|
||
gap: 12px;
|
||
justify-content: flex-end;
|
||
}
|
||
</style>
|