Files
one-pipe-system/src/views/asset-management/asset-information/components/dialogs/PackageRechargeDialog.vue
2026-04-29 11:29:53 +08:00

461 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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="null" disabled>
<span style="color: var(--el-text-color-warning)">
该设备未关联套餐系列无法购买套餐
</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="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%"
: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"
>
<ElUpload
ref="uploadRef"
:auto-upload="false"
:on-change="handleVoucherFileChange"
:on-remove="handleRemoveVoucher"
accept="image/*"
list-type="picture-card"
:limit="1"
>
<el-icon><Plus /></el-icon>
<template #tip>
<div class="el-upload__tip">支持 jpgpng 格式文件大小不超过 5MB</div>
</template>
</ElUpload>
</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, nextTick, reactive, ref, watch } from 'vue'
import {
ElButton,
ElDialog,
ElForm,
ElFormItem,
ElIcon,
ElMessage,
ElOption,
ElSelect,
ElTag,
ElUpload
} from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import type { FormInstance, FormRules, UploadFile, UploadInstance } from 'element-plus'
import { OrderService, PackageManageService, StorageService } from '@/api/modules'
import type { CreateOrderRequest, PackageResponse } from '@/types/api'
import { useUserStore } from '@/store/modules/user'
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<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,
payment_method: 'wallet' as 'wallet' | 'offline',
payment_voucher_key: undefined as string | undefined
})
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 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 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('只能上传图片文件!')
uploadRef.value?.clearFiles()
return
}
const isLt5M = file.size / 1024 / 1024 < 5
if (!isLt5M) {
ElMessage.error('图片大小不能超过 5MB!')
uploadRef.value?.clearFiles()
return
}
const requestId = ++activeVoucherUploadId
voucherUploading.value = true
form.payment_voucher_key = undefined
try {
ElMessage.info('正在上传支付凭证...')
const uploadUrlRes = await StorageService.getUploadUrl({
file_name: file.name,
content_type: file.type,
purpose: 'attachment'
})
if (uploadUrlRes.code !== 0) {
if (requestId !== activeVoucherUploadId) return
ElMessage.error(uploadUrlRes.msg || '获取上传地址失败')
uploadRef.value?.clearFiles()
return
}
const { upload_url, file_key } = uploadUrlRes.data
await StorageService.uploadFile(upload_url, file, file.type)
if (requestId !== activeVoucherUploadId) return
form.payment_voucher_key = file_key
ElMessage.success('上传成功')
nextTick(() => {
formRef.value?.validateField('payment_voucher_key')
})
} catch (error: any) {
if (requestId !== activeVoucherUploadId) return
console.error('上传支付凭证失败:', error)
ElMessage.error(error?.message || '上传失败,请重试')
form.payment_voucher_key = undefined
uploadRef.value?.clearFiles()
} finally {
if (requestId === activeVoucherUploadId) {
voucherUploading.value = false
}
}
}
const handleRemoveVoucher = () => {
invalidateVoucherUploadState(true)
}
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' && !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' && form.payment_voucher_key) {
data.payment_voucher_key = form.payment_voucher_key
}
await OrderService.createOrder(data)
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)
} finally {
loading.value = false
}
}
const handleDialogClosed = () => {
formRef.value?.resetFields()
form.package_id = undefined
form.payment_method = 'wallet'
invalidateVoucherUploadState(true)
packageOptions.value = []
packagesEmptyWithSeriesId.value = false
uploadRef.value?.clearFiles()
}
</script>
<style lang="scss" scoped>
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
</style>