Files
one-pipe-system/src/views/asset-management/asset-information/components/dialogs/PackageRechargeDialog.vue
luo 261f5f2853
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 5m0s
fix: id兼容, 通知入口
2026-07-28 15:19:29 +08:00

388 lines
11 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="__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>