This commit is contained in:
@@ -206,9 +206,9 @@
|
||||
import { useRouter } from 'vue-router'
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
import { EnterpriseService, ShopService } from '@/api/modules'
|
||||
import { ElMessage, ElSwitch, ElCascader, ElTreeSelect } from 'element-plus'
|
||||
import { ElMessage, ElSwitch, ElCascader } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type { EnterpriseItem, ShopResponse } from '@/types/api'
|
||||
import type { EnterpriseItem } from '@/types/api'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
@@ -236,8 +236,6 @@
|
||||
const shopLoading = ref(false)
|
||||
const tableRef = ref()
|
||||
const currentEnterpriseId = ref<number>(0)
|
||||
const shopTreeData = ref<ShopResponse[]>([])
|
||||
|
||||
// 店铺级联选择相关
|
||||
const shopCascadeOptions = ref<any[]>([])
|
||||
const shopCascadeProps = {
|
||||
@@ -499,22 +497,6 @@
|
||||
})
|
||||
}
|
||||
|
||||
if (hasAuth('enterprise_customer:card_authorization')) {
|
||||
actions.push({
|
||||
label: '卡授权',
|
||||
handler: () => manageCards(row),
|
||||
type: 'primary'
|
||||
})
|
||||
}
|
||||
|
||||
if (hasAuth('enterprise_customer:device_authorization')) {
|
||||
actions.push({
|
||||
label: '设备授权',
|
||||
handler: () => manageDevices(row),
|
||||
type: 'primary'
|
||||
})
|
||||
}
|
||||
|
||||
if (hasAuth('enterprise_customer:edit')) {
|
||||
actions.push({
|
||||
label: '编辑',
|
||||
@@ -582,33 +564,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 构建树形数据
|
||||
const buildTreeData = (items: ShopResponse[]) => {
|
||||
const map = new Map<number, ShopResponse & { children?: ShopResponse[] }>()
|
||||
const tree: ShopResponse[] = []
|
||||
|
||||
// 先将所有项放入 map
|
||||
items.forEach((item) => {
|
||||
map.set(item.id, { ...item, children: [] })
|
||||
})
|
||||
|
||||
// 构建树形结构
|
||||
items.forEach((item) => {
|
||||
const node = map.get(item.id)!
|
||||
if (item.parent_id && map.has(item.parent_id)) {
|
||||
// 有父节点,添加到父节点的 children 中
|
||||
const parent = map.get(item.parent_id)!
|
||||
if (!parent.children) parent.children = []
|
||||
parent.children.push(node)
|
||||
} else {
|
||||
// 没有父节点或父节点不存在,作为根节点
|
||||
tree.push(node)
|
||||
}
|
||||
})
|
||||
|
||||
return tree
|
||||
}
|
||||
|
||||
// 获取企业客户列表
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
@@ -855,21 +810,6 @@
|
||||
})
|
||||
}
|
||||
|
||||
// 卡管理
|
||||
const manageCards = (row: EnterpriseItem) => {
|
||||
router.push({
|
||||
path: RoutesAlias.EnterpriseCards,
|
||||
query: { id: row.id }
|
||||
})
|
||||
}
|
||||
|
||||
const manageDevices = (row: EnterpriseItem) => {
|
||||
router.push({
|
||||
path: RoutesAlias.EnterpriseDevices,
|
||||
query: { id: row.id }
|
||||
})
|
||||
}
|
||||
|
||||
// 企业客户操作菜单项配置
|
||||
</script>
|
||||
|
||||
|
||||
@@ -297,8 +297,8 @@
|
||||
v-model="usedDataForm.data_usage"
|
||||
:min="0"
|
||||
:max="usedDataMaxValue"
|
||||
:precision="usedDataForm.unit === 'MB' ? 0 : 3"
|
||||
:step="usedDataForm.unit === 'MB' ? 1 : 0.1"
|
||||
:precision="3"
|
||||
:step="0.1"
|
||||
class="used-data-input__number"
|
||||
placeholder="请输入新的套餐真实已用量"
|
||||
/>
|
||||
@@ -447,8 +447,8 @@
|
||||
return
|
||||
}
|
||||
|
||||
if (dataUsageMb === undefined || dataUsageMb < 0 || !Number.isInteger(dataUsageMb)) {
|
||||
callback(new Error('请输入可换算为非负整数 MB 的已用量'))
|
||||
if (dataUsageMb === undefined || dataUsageMb < 0) {
|
||||
callback(new Error('请输入可换算为非负数字 MB 的已用量'))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
/>
|
||||
<ElButton type="primary" @click="handleQuery">查询</ElButton>
|
||||
<ElButton @click="handleReset()">重置</ElButton>
|
||||
<ElTag type="info" size="small">共 {{ total }} 条</ElTag>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -66,6 +65,14 @@
|
||||
|
||||
<div v-else v-loading="loading" class="wallet-table-wrapper">
|
||||
<ElTable v-if="hasTransactions" :data="transactionList" class="wallet-table" border>
|
||||
<ElTableColumn prop="reference_no" label="业务编号">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.reference_no" class="reference-no-link">
|
||||
{{ scope.row.reference_no }}
|
||||
</span>
|
||||
<span v-else>--</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="交易类型" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<ElTag :type="getTransactionTypeTag(scope.row.transaction_type)" size="small">
|
||||
@@ -103,14 +110,6 @@
|
||||
<span v-else>--</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn prop="reference_no" label="业务编号" min-width="180" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.reference_no" class="reference-no-link">
|
||||
{{ scope.row.reference_no }}
|
||||
</span>
|
||||
<span v-else>--</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn prop="remark" label="备注" min-width="150" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ scope.row.remark || '-' }}
|
||||
|
||||
@@ -44,17 +44,17 @@
|
||||
</ElOption>
|
||||
</ElSelect>
|
||||
<template v-if="noSeriesId">
|
||||
<div style=" margin-top: 4px; font-size: 12px;color: var(--el-text-color-warning)">
|
||||
<div style="margin-top: 4px; font-size: 12px; color: var(--el-text-color-warning)">
|
||||
该设备未关联套餐系列,无法购买套餐
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="packagesEmptyWithSeriesId">
|
||||
<div style=" margin-top: 4px; font-size: 12px;color: var(--el-text-color-info)">
|
||||
<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 style="margin-top: 4px; font-size: 12px; color: var(--el-color-warning)">
|
||||
提示: 赠送套餐只能使用线下支付方式,且必须上传支付凭证
|
||||
</div>
|
||||
</template>
|
||||
@@ -83,23 +83,13 @@
|
||||
label="支付凭证"
|
||||
prop="payment_voucher_key"
|
||||
>
|
||||
<ElUpload
|
||||
<VoucherUpload
|
||||
ref="uploadRef"
|
||||
class="payment-voucher-upload"
|
||||
drag
|
||||
:auto-upload="false"
|
||||
:on-change="handleVoucherFileChange"
|
||||
:on-remove="handleRemoveVoucher"
|
||||
accept="image/*"
|
||||
list-type="picture"
|
||||
:limit="1"
|
||||
>
|
||||
<el-icon class="payment-voucher-upload__icon"><UploadFilled /></el-icon>
|
||||
<div class="payment-voucher-upload__text"> 将支付凭证拖到此处,或<em>点击上传</em> </div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">支持 jpg、png 格式,文件大小不超过 5MB</div>
|
||||
</template>
|
||||
</ElUpload>
|
||||
v-model="form.payment_voucher_key"
|
||||
voucher-name="支付凭证"
|
||||
@uploading-change="voucherUploading = $event"
|
||||
@change="formRef?.validateField('payment_voucher_key')"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
@@ -111,7 +101,7 @@
|
||||
:loading="loading || voucherUploading"
|
||||
:disabled="voucherUploading"
|
||||
>
|
||||
{{ voucherUploading ? '图片上传中...' : '提交' }}
|
||||
{{ voucherUploading ? '凭证上传中...' : '提交' }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
@@ -119,24 +109,22 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, reactive, ref, watch } from 'vue'
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import {
|
||||
ElButton,
|
||||
ElDialog,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElIcon,
|
||||
ElMessage,
|
||||
ElOption,
|
||||
ElSelect,
|
||||
ElTag,
|
||||
ElUpload
|
||||
ElTag
|
||||
} from 'element-plus'
|
||||
import { UploadFilled } from '@element-plus/icons-vue'
|
||||
import type { FormInstance, FormRules, UploadFile, UploadInstance } from 'element-plus'
|
||||
import { OrderService, PackageManageService, StorageService } from '@/api/modules'
|
||||
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'
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
@@ -156,13 +144,12 @@
|
||||
|
||||
const userStore = useUserStore()
|
||||
const formRef = ref<FormInstance>()
|
||||
const uploadRef = ref<UploadInstance>()
|
||||
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)
|
||||
let activeVoucherUploadId = 0
|
||||
|
||||
const form = reactive({
|
||||
package_id: undefined as number | undefined,
|
||||
@@ -311,80 +298,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -447,10 +360,11 @@
|
||||
formRef.value?.resetFields()
|
||||
form.package_id = undefined
|
||||
form.payment_method = 'wallet'
|
||||
invalidateVoucherUploadState(true)
|
||||
form.payment_voucher_key = undefined
|
||||
voucherUploading.value = false
|
||||
packageOptions.value = []
|
||||
packagesEmptyWithSeriesId.value = false
|
||||
uploadRef.value?.clearFiles()
|
||||
uploadRef.value?.clearFiles(false)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -460,34 +374,4 @@
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.payment-voucher-upload {
|
||||
width: 100%;
|
||||
|
||||
:deep(.el-upload) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.el-upload-dragger) {
|
||||
width: 100%;
|
||||
padding: 24px 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
margin-bottom: 12px;
|
||||
font-size: 28px;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
&__text {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-regular);
|
||||
|
||||
em {
|
||||
font-style: normal;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<ArtSearchBar
|
||||
v-model:filter="searchForm"
|
||||
:items="searchFormItems"
|
||||
label-width="120"
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
></ArtSearchBar>
|
||||
@@ -41,6 +40,22 @@
|
||||
>
|
||||
批量设置套餐系列
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
:disabled="!selectedDevices.length"
|
||||
@click="showEnterpriseDeviceAuthorizeDialog"
|
||||
v-permission="'devices:authorize_enterprise'"
|
||||
>
|
||||
授权设备给企业
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="warning"
|
||||
:disabled="!selectedDevices.length"
|
||||
@click="showEnterpriseDeviceRecallDialog"
|
||||
v-permission="'devices:recall_enterprise_authorization'"
|
||||
>
|
||||
撤销设备授权
|
||||
</ElButton>
|
||||
<ElButton type="primary" @click="showExportDialog" v-permission="'devices:export'">
|
||||
导出
|
||||
</ElButton>
|
||||
@@ -677,6 +692,250 @@
|
||||
:identifier="operationLogsIdentifier"
|
||||
download-permission="device:download_log_file"
|
||||
/>
|
||||
<ElDialog
|
||||
v-model="enterpriseDeviceAuthorizeDialogVisible"
|
||||
title="授权设备给企业"
|
||||
width="620px"
|
||||
@close="handleEnterpriseDeviceAuthorizeDialogClose"
|
||||
>
|
||||
<ElForm
|
||||
ref="enterpriseDeviceAuthorizeFormRef"
|
||||
:model="enterpriseDeviceAuthorizeForm"
|
||||
:rules="enterpriseDeviceAuthorizeRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<ElFormItem label="授权企业" prop="enterprise_id">
|
||||
<ElSelect
|
||||
v-model="enterpriseDeviceAuthorizeForm.enterprise_id"
|
||||
placeholder="请选择或搜索企业"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
:remote-method="handleSearchEnterprise"
|
||||
:loading="enterpriseLoading"
|
||||
style="width: 100%"
|
||||
>
|
||||
<ElOption
|
||||
v-for="item in enterpriseOptions"
|
||||
:key="item.id"
|
||||
:label="item.enterprise_name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="选设备方式" prop="selection_type">
|
||||
<ElRadioGroup v-model="enterpriseDeviceAuthorizeForm.selection_type">
|
||||
<ElRadio label="list">设备号列表</ElRadio>
|
||||
<ElRadio label="filter">筛选条件</ElRadio>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="enterpriseDeviceAuthorizeForm.selection_type === DeviceSelectionType.LIST"
|
||||
label="设备号列表"
|
||||
>
|
||||
<div>已选择 {{ selectedDevices.length }} 台设备</div>
|
||||
</ElFormItem>
|
||||
<template
|
||||
v-if="enterpriseDeviceAuthorizeForm.selection_type === DeviceSelectionType.FILTER"
|
||||
>
|
||||
<ElFormItem label="设备号">
|
||||
<ElInput
|
||||
v-model="enterpriseDeviceAuthorizeForm.virtual_no"
|
||||
placeholder="请输入设备号"
|
||||
clearable
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="批次号">
|
||||
<ElSelect
|
||||
v-model="enterpriseDeviceAuthorizeForm.batch_no"
|
||||
placeholder="请选择或搜索批次号"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
:remote-method="handleSearchBatchNo"
|
||||
style="width: 100%"
|
||||
>
|
||||
<ElOption
|
||||
v-for="item in searchBatchNoOptions"
|
||||
:key="item.id"
|
||||
:label="item.batch_no"
|
||||
:value="item.batch_no"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="店铺名称">
|
||||
<ElCascader
|
||||
v-model="enterpriseDeviceAuthorizeForm.shop_id_path"
|
||||
:options="shopCascadeOptions"
|
||||
:props="shopCascadeProps"
|
||||
placeholder="请选择店铺名称"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
<ElFormItem label="备注">
|
||||
<ElInput
|
||||
v-model="enterpriseDeviceAuthorizeForm.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注信息"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="enterpriseDeviceAuthorizeDialogVisible = false">取消</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="handleEnterpriseDeviceAuthorize"
|
||||
:loading="enterpriseDeviceAuthorizeLoading"
|
||||
>
|
||||
确认授权
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
<ElDialog
|
||||
v-model="enterpriseDeviceRecallDialogVisible"
|
||||
title="撤销设备授权"
|
||||
width="620px"
|
||||
@close="handleEnterpriseDeviceRecallDialogClose"
|
||||
>
|
||||
<ElForm
|
||||
ref="enterpriseDeviceRecallFormRef"
|
||||
:model="enterpriseDeviceRecallForm"
|
||||
:rules="enterpriseDeviceRecallRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<ElFormItem label="授权企业" prop="enterprise_id">
|
||||
<ElSelect
|
||||
v-model="enterpriseDeviceRecallForm.enterprise_id"
|
||||
placeholder="请选择或搜索企业"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
:remote-method="handleSearchEnterprise"
|
||||
:loading="enterpriseLoading"
|
||||
style="width: 100%"
|
||||
>
|
||||
<ElOption
|
||||
v-for="item in enterpriseOptions"
|
||||
:key="item.id"
|
||||
:label="item.enterprise_name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="选设备方式" prop="selection_type">
|
||||
<ElRadioGroup v-model="enterpriseDeviceRecallForm.selection_type">
|
||||
<ElRadio label="list">设备号列表</ElRadio>
|
||||
<ElRadio label="filter">筛选条件</ElRadio>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="enterpriseDeviceRecallForm.selection_type === DeviceSelectionType.LIST"
|
||||
label="设备号列表"
|
||||
>
|
||||
<div>已选择 {{ selectedDevices.length }} 台设备</div>
|
||||
</ElFormItem>
|
||||
<template
|
||||
v-if="enterpriseDeviceRecallForm.selection_type === DeviceSelectionType.FILTER"
|
||||
>
|
||||
<ElFormItem label="设备号">
|
||||
<ElInput
|
||||
v-model="enterpriseDeviceRecallForm.virtual_no"
|
||||
placeholder="请输入设备号"
|
||||
clearable
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="批次号">
|
||||
<ElSelect
|
||||
v-model="enterpriseDeviceRecallForm.batch_no"
|
||||
placeholder="请选择或搜索批次号"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
:remote-method="handleSearchBatchNo"
|
||||
style="width: 100%"
|
||||
>
|
||||
<ElOption
|
||||
v-for="item in searchBatchNoOptions"
|
||||
:key="item.id"
|
||||
:label="item.batch_no"
|
||||
:value="item.batch_no"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="enterpriseDeviceRecallDialogVisible = false">取消</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="handleEnterpriseDeviceRecall"
|
||||
:loading="enterpriseDeviceRecallLoading"
|
||||
>
|
||||
确认撤销
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
<ElDialog
|
||||
v-model="enterpriseDeviceResultDialogVisible"
|
||||
:title="enterpriseDeviceResultTitle"
|
||||
width="700px"
|
||||
>
|
||||
<ElDescriptions :column="2" border>
|
||||
<ElDescriptionsItem label="成功数">
|
||||
<ElTag type="success">{{ enterpriseDeviceOperationResult.success_count }}</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="失败数">
|
||||
<ElTag type="danger">{{ enterpriseDeviceOperationResult.fail_count }}</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
<div
|
||||
v-if="
|
||||
enterpriseDeviceOperationResult.failed_items &&
|
||||
enterpriseDeviceOperationResult.failed_items.length > 0
|
||||
"
|
||||
style="margin-top: 20px"
|
||||
>
|
||||
<ElDivider content-position="left">失败项详情</ElDivider>
|
||||
<ElTable :data="enterpriseDeviceOperationResult.failed_items" border max-height="300">
|
||||
<ElTableColumn prop="virtual_no" label="设备号" width="180" />
|
||||
<ElTableColumn prop="reason" label="失败原因" />
|
||||
</ElTable>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
enterpriseDeviceOperationResult.authorized_devices &&
|
||||
enterpriseDeviceOperationResult.authorized_devices.length > 0
|
||||
"
|
||||
style="margin-top: 20px"
|
||||
>
|
||||
<ElDivider content-position="left">已授权设备</ElDivider>
|
||||
<ElTable
|
||||
:data="enterpriseDeviceOperationResult.authorized_devices"
|
||||
border
|
||||
max-height="220"
|
||||
>
|
||||
<ElTableColumn prop="virtual_no" label="设备号" width="180" />
|
||||
<ElTableColumn prop="card_count" label="绑定卡数" />
|
||||
</ElTable>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton type="primary" @click="enterpriseDeviceResultDialogVisible = false"
|
||||
>确定</ElButton
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
<ExportTaskCreateDialog
|
||||
v-model="exportDialogVisible"
|
||||
scene="device"
|
||||
@@ -699,7 +958,7 @@
|
||||
CardService,
|
||||
PackageSeriesService,
|
||||
AssetService,
|
||||
CarrierService
|
||||
EnterpriseService
|
||||
} from '@/api/modules'
|
||||
import { DeviceSelectionType } from '@/types/api'
|
||||
import { ElMessage, ElMessageBox, ElTag, ElIcon, ElRadioGroup, ElRadio } from 'element-plus'
|
||||
@@ -720,6 +979,11 @@
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import type { PackageSeriesResponse } from '@/types/api'
|
||||
import type { EnterpriseItem } from '@/types/api/enterprise'
|
||||
import type {
|
||||
EnterpriseAllocateDevicesResponse,
|
||||
EnterpriseRecallDevicesResponse
|
||||
} from '@/types/api/enterpriseDevice'
|
||||
import RealnamePolicyDialog from '@/components/device/RealnamePolicyDialog.vue'
|
||||
import OperationLogsDialog from '@/components/business/OperationLogsDialog.vue'
|
||||
import ExportTaskCreateDialog from '@/components/business/ExportTaskCreateDialog.vue'
|
||||
@@ -781,6 +1045,20 @@
|
||||
label: 'label',
|
||||
children: 'children'
|
||||
}
|
||||
const loadTopLevelShopCascadeOptions = async () => {
|
||||
try {
|
||||
const res = await ShopService.getShopsCascade({ parent_id: undefined })
|
||||
if (res.code === 0) {
|
||||
shopCascadeOptions.value = (res.data || []).map((item: any) => ({
|
||||
value: item.id,
|
||||
label: item.shop_name,
|
||||
leaf: !item.has_children
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载顶级店铺失败:', error)
|
||||
}
|
||||
}
|
||||
const seriesBindingShopCascadeOptions = ref<any[]>([])
|
||||
const seriesBindingShopCascadeProps = {
|
||||
lazy: true,
|
||||
@@ -815,6 +1093,27 @@
|
||||
}
|
||||
const allocateResult = ref<AllocateDevicesResponse | null>(null)
|
||||
const recallResult = ref<RecallDevicesResponse | null>(null)
|
||||
const enterpriseDeviceAuthorizeDialogVisible = ref(false)
|
||||
const enterpriseDeviceRecallDialogVisible = ref(false)
|
||||
const enterpriseDeviceResultDialogVisible = ref(false)
|
||||
const enterpriseDeviceAuthorizeLoading = ref(false)
|
||||
const enterpriseDeviceRecallLoading = ref(false)
|
||||
const enterpriseDeviceResultTitle = ref('')
|
||||
const enterpriseDeviceAuthorizeFormRef = ref<FormInstance>()
|
||||
const enterpriseDeviceRecallFormRef = ref<FormInstance>()
|
||||
const enterpriseOptions = ref<EnterpriseItem[]>([])
|
||||
const enterpriseLoading = ref(false)
|
||||
const enterpriseDeviceOperationResult = ref<
|
||||
Partial<EnterpriseAllocateDevicesResponse & EnterpriseRecallDevicesResponse> & {
|
||||
success_count: number
|
||||
fail_count: number
|
||||
}
|
||||
>({
|
||||
success_count: 0,
|
||||
fail_count: 0,
|
||||
failed_items: null,
|
||||
authorized_devices: null
|
||||
})
|
||||
|
||||
// 套餐系列绑定相关
|
||||
const seriesBindingDialogVisible = ref(false)
|
||||
@@ -840,6 +1139,22 @@
|
||||
created_at_end: ''
|
||||
})
|
||||
const seriesBindingForm = reactive(createInitialSeriesBindingState())
|
||||
const createInitialEnterpriseDeviceAuthorizeState = () => ({
|
||||
enterprise_id: undefined as number | undefined,
|
||||
selection_type: DeviceSelectionType.LIST,
|
||||
virtual_no: '',
|
||||
batch_no: '',
|
||||
shop_id_path: undefined as number[] | undefined,
|
||||
remark: ''
|
||||
})
|
||||
const createInitialEnterpriseDeviceRecallState = () => ({
|
||||
enterprise_id: undefined as number | undefined,
|
||||
selection_type: DeviceSelectionType.LIST,
|
||||
virtual_no: '',
|
||||
batch_no: ''
|
||||
})
|
||||
const enterpriseDeviceAuthorizeForm = reactive(createInitialEnterpriseDeviceAuthorizeState())
|
||||
const enterpriseDeviceRecallForm = reactive(createInitialEnterpriseDeviceRecallState())
|
||||
const parseNumericStringToBigInt = (value: string): bigint | null => {
|
||||
const normalizedValue = value.trim()
|
||||
if (!normalizedValue || !/^\d+$/.test(normalizedValue)) {
|
||||
@@ -931,6 +1246,13 @@
|
||||
}
|
||||
]
|
||||
})
|
||||
const createEnterpriseDeviceRules = () =>
|
||||
reactive<FormRules>({
|
||||
enterprise_id: [{ required: true, message: '请选择企业', trigger: 'change' }],
|
||||
selection_type: [{ required: true, message: '请选择选设备方式', trigger: 'change' }]
|
||||
})
|
||||
const enterpriseDeviceAuthorizeRules = createEnterpriseDeviceRules()
|
||||
const enterpriseDeviceRecallRules = createEnterpriseDeviceRules()
|
||||
const seriesBindingResult = ref<BatchSetDeviceSeriesBindingResponse | null>(null)
|
||||
|
||||
// 搜索表单店铺/系列选项
|
||||
@@ -999,13 +1321,15 @@
|
||||
shop_id: undefined as number | undefined,
|
||||
series_id: undefined as number | undefined,
|
||||
has_active_package: undefined as boolean | undefined,
|
||||
authorized_enterprise_id: undefined as number | undefined,
|
||||
is_authorized_to_enterprise: undefined as boolean | undefined,
|
||||
dateRange: [] as string[],
|
||||
created_at_start: '',
|
||||
created_at_end: ''
|
||||
}
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({ ...initialSearchState })
|
||||
const searchForm = reactive<Record<string, any>>({ ...initialSearchState })
|
||||
|
||||
// 搜索批次号(用于搜索表单)
|
||||
const loadSearchBatchNoOptions = async (batchNo?: string) => {
|
||||
@@ -1174,7 +1498,7 @@
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '起始至结束',
|
||||
label: '起止时间',
|
||||
prop: 'dateRange',
|
||||
type: 'date',
|
||||
config: {
|
||||
@@ -1189,7 +1513,7 @@
|
||||
|
||||
// 分页
|
||||
searchFormItems.splice(searchFormItems.length - 1, 0, {
|
||||
label: '是否有生效套餐',
|
||||
label: '有效套餐',
|
||||
prop: 'has_active_package',
|
||||
type: 'select',
|
||||
config: {
|
||||
@@ -1201,6 +1525,37 @@
|
||||
{ label: '否', value: false }
|
||||
]
|
||||
})
|
||||
searchFormItems.splice(searchFormItems.length - 1, 0, {
|
||||
label: '授权企业',
|
||||
prop: 'authorized_enterprise_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: (query: string) => handleSearchEnterprise(query),
|
||||
loading: enterpriseLoading.value,
|
||||
placeholder: '请选择或搜索企业'
|
||||
},
|
||||
options: () =>
|
||||
enterpriseOptions.value.map((enterprise) => ({
|
||||
label: enterprise.enterprise_name,
|
||||
value: enterprise.id
|
||||
}))
|
||||
})
|
||||
searchFormItems.splice(searchFormItems.length - 1, 0, {
|
||||
label: '授权状态',
|
||||
prop: 'is_authorized_to_enterprise',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '全部'
|
||||
},
|
||||
options: () => [
|
||||
{ label: '已授权', value: true },
|
||||
{ label: '未授权', value: false }
|
||||
]
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
@@ -1704,6 +2059,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearchEnterprise = async (query?: string) => {
|
||||
try {
|
||||
enterpriseLoading.value = true
|
||||
const params = {
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
enterprise_name: query || undefined,
|
||||
status: 1
|
||||
}
|
||||
const res = await EnterpriseService.getEnterprises(params)
|
||||
if (res.code === 0) {
|
||||
enterpriseOptions.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索企业失败:', error)
|
||||
} finally {
|
||||
enterpriseLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
let isFirstActivation = true
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
@@ -1735,6 +2110,8 @@
|
||||
shop_id: searchForm.shop_id || undefined,
|
||||
series_id: searchForm.series_id || undefined,
|
||||
has_active_package: searchForm.has_active_package ?? undefined,
|
||||
authorized_enterprise_id: searchForm.authorized_enterprise_id || undefined,
|
||||
is_authorized_to_enterprise: searchForm.is_authorized_to_enterprise ?? undefined,
|
||||
created_at_start: searchForm.created_at_start || undefined,
|
||||
created_at_end: searchForm.created_at_end || undefined
|
||||
}
|
||||
@@ -1763,6 +2140,8 @@
|
||||
shop_id: searchForm.shop_id || undefined,
|
||||
series_id: searchForm.series_id || undefined,
|
||||
has_active_package: searchForm.has_active_package ?? undefined,
|
||||
authorized_enterprise_id: searchForm.authorized_enterprise_id || undefined,
|
||||
is_authorized_to_enterprise: searchForm.is_authorized_to_enterprise ?? undefined,
|
||||
created_at_start: searchForm.created_at_start || undefined,
|
||||
created_at_end: searchForm.created_at_end || undefined
|
||||
}
|
||||
@@ -1993,6 +2372,160 @@
|
||||
recallForm.remark = ''
|
||||
}
|
||||
|
||||
const getInferredDeviceEnterpriseId = () => {
|
||||
const filterEnterpriseId = searchForm.authorized_enterprise_id
|
||||
if (filterEnterpriseId) return filterEnterpriseId
|
||||
|
||||
const selectedEnterpriseIds = Array.from(
|
||||
new Set(
|
||||
selectedDevices.value
|
||||
.map((device) => device.authorized_enterprise_id)
|
||||
.filter((id): id is number => typeof id === 'number')
|
||||
)
|
||||
)
|
||||
|
||||
return selectedEnterpriseIds.length === 1 ? selectedEnterpriseIds[0] : undefined
|
||||
}
|
||||
|
||||
const resetEnterpriseDeviceAuthorizeForm = () => {
|
||||
Object.assign(enterpriseDeviceAuthorizeForm, createInitialEnterpriseDeviceAuthorizeState(), {
|
||||
selection_type:
|
||||
selectedDevices.value.length > 0 ? DeviceSelectionType.LIST : DeviceSelectionType.FILTER,
|
||||
virtual_no: searchForm.virtual_no || '',
|
||||
batch_no: searchForm.batch_no || '',
|
||||
shop_id_path: undefined
|
||||
})
|
||||
}
|
||||
|
||||
const resetEnterpriseDeviceRecallForm = () => {
|
||||
Object.assign(enterpriseDeviceRecallForm, createInitialEnterpriseDeviceRecallState(), {
|
||||
enterprise_id: getInferredDeviceEnterpriseId(),
|
||||
selection_type:
|
||||
selectedDevices.value.length > 0 ? DeviceSelectionType.LIST : DeviceSelectionType.FILTER,
|
||||
virtual_no: searchForm.virtual_no || '',
|
||||
batch_no: searchForm.batch_no || ''
|
||||
})
|
||||
}
|
||||
|
||||
const showEnterpriseDeviceAuthorizeDialog = async () => {
|
||||
resetEnterpriseDeviceAuthorizeForm()
|
||||
await Promise.all([
|
||||
handleSearchEnterprise(),
|
||||
loadSearchBatchNoOptions(),
|
||||
loadTopLevelShopCascadeOptions()
|
||||
])
|
||||
enterpriseDeviceAuthorizeDialogVisible.value = true
|
||||
enterpriseDeviceAuthorizeFormRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
const showEnterpriseDeviceRecallDialog = async () => {
|
||||
resetEnterpriseDeviceRecallForm()
|
||||
await Promise.all([handleSearchEnterprise(), loadSearchBatchNoOptions()])
|
||||
enterpriseDeviceRecallDialogVisible.value = true
|
||||
enterpriseDeviceRecallFormRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
const handleEnterpriseDeviceAuthorizeDialogClose = () => {
|
||||
Object.assign(enterpriseDeviceAuthorizeForm, createInitialEnterpriseDeviceAuthorizeState())
|
||||
enterpriseDeviceAuthorizeFormRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const handleEnterpriseDeviceRecallDialogClose = () => {
|
||||
Object.assign(enterpriseDeviceRecallForm, createInitialEnterpriseDeviceRecallState())
|
||||
enterpriseDeviceRecallFormRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const buildEnterpriseDeviceParams = (form: any) => {
|
||||
const params: Record<string, unknown> = {
|
||||
selection_type: form.selection_type,
|
||||
remark: form.remark || undefined
|
||||
}
|
||||
|
||||
if (form.selection_type === DeviceSelectionType.LIST) {
|
||||
const deviceNos = selectedDevices.value.map((device) => device.virtual_no).filter(Boolean)
|
||||
if (!deviceNos.length) {
|
||||
ElMessage.warning('列表模式下请先选择设备')
|
||||
return null
|
||||
}
|
||||
params.device_nos = deviceNos
|
||||
} else {
|
||||
if (form.batch_no) params.batch_no = form.batch_no
|
||||
if (Array.isArray(form.shop_id_path) && form.shop_id_path.length > 0) {
|
||||
params.shop_id = form.shop_id_path[form.shop_id_path.length - 1]
|
||||
}
|
||||
if (form.virtual_no) params.virtual_no = form.virtual_no
|
||||
}
|
||||
|
||||
Object.keys(params).forEach((key) => {
|
||||
if (params[key] === undefined || params[key] === '') delete params[key]
|
||||
})
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
const applyEnterpriseDeviceOperationSuccess = async (
|
||||
title: string,
|
||||
result: EnterpriseAllocateDevicesResponse | EnterpriseRecallDevicesResponse
|
||||
) => {
|
||||
enterpriseDeviceOperationResult.value = result
|
||||
enterpriseDeviceResultTitle.value = title
|
||||
enterpriseDeviceResultDialogVisible.value = true
|
||||
selectedDevices.value = []
|
||||
await getTableData()
|
||||
}
|
||||
|
||||
const handleEnterpriseDeviceAuthorize = async () => {
|
||||
if (!enterpriseDeviceAuthorizeFormRef.value) return
|
||||
|
||||
await enterpriseDeviceAuthorizeFormRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
const params = buildEnterpriseDeviceParams(enterpriseDeviceAuthorizeForm)
|
||||
if (!params) return
|
||||
|
||||
enterpriseDeviceAuthorizeLoading.value = true
|
||||
try {
|
||||
const res = await EnterpriseService.allocateDevices(
|
||||
enterpriseDeviceAuthorizeForm.enterprise_id!,
|
||||
params as any
|
||||
)
|
||||
if (res.code === 0) {
|
||||
enterpriseDeviceAuthorizeDialogVisible.value = false
|
||||
await applyEnterpriseDeviceOperationSuccess('授权设备给企业结果', res.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('授权设备给企业失败:', error)
|
||||
} finally {
|
||||
enterpriseDeviceAuthorizeLoading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleEnterpriseDeviceRecall = async () => {
|
||||
if (!enterpriseDeviceRecallFormRef.value) return
|
||||
|
||||
await enterpriseDeviceRecallFormRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
const params = buildEnterpriseDeviceParams(enterpriseDeviceRecallForm)
|
||||
if (!params) return
|
||||
|
||||
enterpriseDeviceRecallLoading.value = true
|
||||
try {
|
||||
const res = await EnterpriseService.recallDevices(
|
||||
enterpriseDeviceRecallForm.enterprise_id!,
|
||||
params as any
|
||||
)
|
||||
if (res.code === 0) {
|
||||
enterpriseDeviceRecallDialogVisible.value = false
|
||||
await applyEnterpriseDeviceOperationSuccess('撤销设备授权结果', res.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('撤销设备授权失败:', error)
|
||||
} finally {
|
||||
enterpriseDeviceRecallLoading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 批量设置套餐系列
|
||||
const handleBatchSetSeries = async () => {
|
||||
seriesBindingResult.value = null
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
||||
<ArtSearchBar
|
||||
v-model:filter="formFilters"
|
||||
:items="formItems"
|
||||
label-width="120"
|
||||
:label-width="90"
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
></ArtSearchBar>
|
||||
@@ -42,6 +42,22 @@
|
||||
>
|
||||
批量设置套餐系列
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
:disabled="selectedCards.length === 0"
|
||||
@click="showEnterpriseCardAuthorizeDialog"
|
||||
v-permission="'iot_card:authorize_enterprise'"
|
||||
>
|
||||
授权卡给企业
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="warning"
|
||||
:disabled="selectedCards.length === 0"
|
||||
@click="showEnterpriseCardRecallDialog"
|
||||
v-permission="'iot_card:recall_enterprise_authorization'"
|
||||
>
|
||||
回收卡授权
|
||||
</ElButton>
|
||||
<ElButton type="primary" @click="showExportDialog" v-permission="'iot_card:export'">
|
||||
导出
|
||||
</ElButton>
|
||||
@@ -357,6 +373,350 @@
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 企业卡授权对话框 -->
|
||||
<ElDialog
|
||||
v-model="enterpriseCardAuthorizeDialogVisible"
|
||||
title="授权卡给企业"
|
||||
width="640px"
|
||||
@close="handleEnterpriseCardAuthorizeDialogClose"
|
||||
>
|
||||
<ElForm
|
||||
ref="enterpriseCardAuthorizeFormRef"
|
||||
:model="enterpriseCardAuthorizeForm"
|
||||
:rules="enterpriseCardAuthorizeRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<ElFormItem label="授权企业" prop="enterprise_id">
|
||||
<ElSelect
|
||||
v-model="enterpriseCardAuthorizeForm.enterprise_id"
|
||||
placeholder="请选择或搜索企业"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
:remote-method="handleSearchEnterprise"
|
||||
:loading="enterpriseLoading"
|
||||
style="width: 100%"
|
||||
>
|
||||
<ElOption
|
||||
v-for="item in enterpriseOptions"
|
||||
:key="item.id"
|
||||
:label="item.enterprise_name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="选卡方式" prop="selection_type">
|
||||
<ElRadioGroup v-model="enterpriseCardAuthorizeForm.selection_type">
|
||||
<ElRadio label="list">ICCID列表</ElRadio>
|
||||
<ElRadio label="range">号段范围</ElRadio>
|
||||
<ElRadio label="filter">筛选条件</ElRadio>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="enterpriseCardAuthorizeForm.selection_type === CardSelectionType.LIST"
|
||||
label="ICCID列表"
|
||||
>
|
||||
<div>已选择 {{ selectedCards.length }} 张卡</div>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="enterpriseCardAuthorizeForm.selection_type === CardSelectionType.RANGE"
|
||||
label="起始ICCID"
|
||||
prop="iccid_start"
|
||||
>
|
||||
<ElInput
|
||||
v-model="enterpriseCardAuthorizeForm.iccid_start"
|
||||
placeholder="请输入起始ICCID"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="enterpriseCardAuthorizeForm.selection_type === CardSelectionType.RANGE"
|
||||
label="结束ICCID"
|
||||
prop="iccid_end"
|
||||
>
|
||||
<ElInput
|
||||
v-model="enterpriseCardAuthorizeForm.iccid_end"
|
||||
placeholder="请输入结束ICCID"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<div
|
||||
v-if="
|
||||
enterpriseCardAuthorizeForm.selection_type === CardSelectionType.RANGE &&
|
||||
enterpriseCardAuthorizeRangeCountText
|
||||
"
|
||||
class="range-allocation-hint range-allocation-hint--card-form"
|
||||
>
|
||||
共授权{{ enterpriseCardAuthorizeRangeCountText }}张卡
|
||||
</div>
|
||||
<template
|
||||
v-if="enterpriseCardAuthorizeForm.selection_type === CardSelectionType.FILTER"
|
||||
>
|
||||
<ElFormItem label="运营商">
|
||||
<ElSelect
|
||||
v-model="enterpriseCardAuthorizeForm.carrier_id"
|
||||
placeholder="请选择或搜索运营商"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
:remote-method="handleSearchAllocateCarrier"
|
||||
:loading="allocateCarrierLoading"
|
||||
style="width: 100%"
|
||||
>
|
||||
<ElOption
|
||||
v-for="item in allocateCarrierOptions"
|
||||
:key="item.id"
|
||||
:label="item.carrier_name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="店铺">
|
||||
<ElCascader
|
||||
v-model="enterpriseCardAuthorizeForm.shop_id_path"
|
||||
:options="shopCascadeOptions"
|
||||
:props="shopCascadeProps"
|
||||
placeholder="请选择店铺"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
filterable
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="批次号">
|
||||
<ElSelect
|
||||
v-model="enterpriseCardAuthorizeForm.batch_no"
|
||||
placeholder="请选择或搜索批次号"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
:remote-method="handleSearchAllocateBatchNo"
|
||||
style="width: 100%"
|
||||
>
|
||||
<ElOption
|
||||
v-for="item in allocateBatchNoOptions"
|
||||
:key="item.id"
|
||||
:label="item.batch_no"
|
||||
:value="item.batch_no"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="ICCID">
|
||||
<ElInput v-model="enterpriseCardAuthorizeForm.iccid" placeholder="请输入ICCID" />
|
||||
</ElFormItem>
|
||||
</template>
|
||||
<ElFormItem label="备注">
|
||||
<ElInput
|
||||
v-model="enterpriseCardAuthorizeForm.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注信息"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="enterpriseCardAuthorizeDialogVisible = false">取消</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="handleEnterpriseCardAuthorize"
|
||||
:loading="enterpriseCardAuthorizeLoading"
|
||||
>
|
||||
确认授权
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 企业卡授权回收对话框 -->
|
||||
<ElDialog
|
||||
v-model="enterpriseCardRecallDialogVisible"
|
||||
title="回收卡授权"
|
||||
width="640px"
|
||||
@close="handleEnterpriseCardRecallDialogClose"
|
||||
>
|
||||
<ElForm
|
||||
ref="enterpriseCardRecallFormRef"
|
||||
:model="enterpriseCardRecallForm"
|
||||
:rules="enterpriseCardRecallRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<ElFormItem label="授权企业" prop="enterprise_id">
|
||||
<ElSelect
|
||||
v-model="enterpriseCardRecallForm.enterprise_id"
|
||||
placeholder="请选择或搜索企业"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
:remote-method="handleSearchEnterprise"
|
||||
:loading="enterpriseLoading"
|
||||
style="width: 100%"
|
||||
>
|
||||
<ElOption
|
||||
v-for="item in enterpriseOptions"
|
||||
:key="item.id"
|
||||
:label="item.enterprise_name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="选卡方式" prop="selection_type">
|
||||
<ElRadioGroup v-model="enterpriseCardRecallForm.selection_type">
|
||||
<ElRadio label="list">ICCID列表</ElRadio>
|
||||
<ElRadio label="range">号段范围</ElRadio>
|
||||
<ElRadio label="filter">筛选条件</ElRadio>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="enterpriseCardRecallForm.selection_type === CardSelectionType.LIST"
|
||||
label="ICCID列表"
|
||||
>
|
||||
<div>已选择 {{ selectedCards.length }} 张卡</div>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="enterpriseCardRecallForm.selection_type === CardSelectionType.RANGE"
|
||||
label="起始ICCID"
|
||||
prop="iccid_start"
|
||||
>
|
||||
<ElInput
|
||||
v-model="enterpriseCardRecallForm.iccid_start"
|
||||
placeholder="请输入起始ICCID"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="enterpriseCardRecallForm.selection_type === CardSelectionType.RANGE"
|
||||
label="结束ICCID"
|
||||
prop="iccid_end"
|
||||
>
|
||||
<ElInput v-model="enterpriseCardRecallForm.iccid_end" placeholder="请输入结束ICCID" />
|
||||
</ElFormItem>
|
||||
<div
|
||||
v-if="
|
||||
enterpriseCardRecallForm.selection_type === CardSelectionType.RANGE &&
|
||||
enterpriseCardRecallRangeCountText
|
||||
"
|
||||
class="range-allocation-hint range-allocation-hint--card-form"
|
||||
>
|
||||
共回收{{ enterpriseCardRecallRangeCountText }}张卡
|
||||
</div>
|
||||
<template v-if="enterpriseCardRecallForm.selection_type === CardSelectionType.FILTER">
|
||||
<ElFormItem label="运营商">
|
||||
<ElSelect
|
||||
v-model="enterpriseCardRecallForm.carrier_id"
|
||||
placeholder="请选择或搜索运营商"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
:remote-method="handleSearchAllocateCarrier"
|
||||
:loading="allocateCarrierLoading"
|
||||
style="width: 100%"
|
||||
>
|
||||
<ElOption
|
||||
v-for="item in allocateCarrierOptions"
|
||||
:key="item.id"
|
||||
:label="item.carrier_name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="批次号">
|
||||
<ElSelect
|
||||
v-model="enterpriseCardRecallForm.batch_no"
|
||||
placeholder="请选择或搜索批次号"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
:remote-method="handleSearchAllocateBatchNo"
|
||||
style="width: 100%"
|
||||
>
|
||||
<ElOption
|
||||
v-for="item in allocateBatchNoOptions"
|
||||
:key="item.id"
|
||||
:label="item.batch_no"
|
||||
:value="item.batch_no"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="ICCID">
|
||||
<ElInput v-model="enterpriseCardRecallForm.iccid" placeholder="请输入ICCID" />
|
||||
</ElFormItem>
|
||||
</template>
|
||||
<ElFormItem label="备注">
|
||||
<ElInput
|
||||
v-model="enterpriseCardRecallForm.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注信息"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="enterpriseCardRecallDialogVisible = false">取消</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="handleEnterpriseCardRecall"
|
||||
:loading="enterpriseCardRecallLoading"
|
||||
>
|
||||
确认回收
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 企业卡授权操作结果 -->
|
||||
<ElDialog
|
||||
v-model="enterpriseCardResultDialogVisible"
|
||||
:title="enterpriseCardResultTitle"
|
||||
width="700px"
|
||||
>
|
||||
<ElDescriptions :column="2" border>
|
||||
<ElDescriptionsItem label="成功数">
|
||||
<ElTag type="success">{{ enterpriseCardOperationResult.success_count }}</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="失败数">
|
||||
<ElTag type="danger">{{ enterpriseCardOperationResult.fail_count }}</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
<div
|
||||
v-if="
|
||||
enterpriseCardOperationResult.failed_items &&
|
||||
enterpriseCardOperationResult.failed_items.length > 0
|
||||
"
|
||||
style="margin-top: 20px"
|
||||
>
|
||||
<ElDivider content-position="left">失败项详情</ElDivider>
|
||||
<ElTable :data="enterpriseCardOperationResult.failed_items" border max-height="300">
|
||||
<ElTableColumn prop="iccid" label="ICCID" width="200" />
|
||||
<ElTableColumn prop="reason" label="失败原因" />
|
||||
</ElTable>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
enterpriseCardOperationResult.recalled_devices &&
|
||||
enterpriseCardOperationResult.recalled_devices.length > 0
|
||||
"
|
||||
style="margin-top: 20px"
|
||||
>
|
||||
<ElDivider content-position="left">连带回收设备</ElDivider>
|
||||
<ElTable :data="enterpriseCardOperationResult.recalled_devices" border max-height="220">
|
||||
<ElTableColumn prop="virtual_no" label="设备虚拟号" width="180" />
|
||||
<ElTableColumn prop="card_count" label="关联卡数" width="100" />
|
||||
<ElTableColumn label="ICCID列表">
|
||||
<template #default="{ row }">
|
||||
{{ row.iccids?.join(', ') || '-' }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</ElTable>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton type="primary" @click="enterpriseCardResultDialogVisible = false"
|
||||
>确定</ElButton
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 批量设置套餐系列绑定对话框 -->
|
||||
<ElDialog
|
||||
v-model="seriesBindingDialogVisible"
|
||||
@@ -652,7 +1012,8 @@
|
||||
ShopService,
|
||||
PackageSeriesService,
|
||||
AssetService,
|
||||
CarrierService
|
||||
CarrierService,
|
||||
EnterpriseService
|
||||
} from '@/api/modules'
|
||||
import { ElMessage, ElTag, ElIcon, ElMessageBox } from 'element-plus'
|
||||
import { Loading } from '@element-plus/icons-vue'
|
||||
@@ -677,6 +1038,8 @@
|
||||
} from '@/types/api/card'
|
||||
import { CardSelectionType } from '@/types/api/card'
|
||||
import type { PackageSeriesResponse } from '@/types/api'
|
||||
import type { EnterpriseItem } from '@/types/api/enterprise'
|
||||
import type { AllocateCardsResponse, RecallCardsResponse } from '@/types/api/enterpriseCard'
|
||||
import OperationLogsDialog from '@/components/business/OperationLogsDialog.vue'
|
||||
|
||||
defineOptions({ name: 'StandaloneCardList' })
|
||||
@@ -704,6 +1067,27 @@
|
||||
fail_count: 0,
|
||||
failed_items: null
|
||||
})
|
||||
const enterpriseCardAuthorizeDialogVisible = ref(false)
|
||||
const enterpriseCardRecallDialogVisible = ref(false)
|
||||
const enterpriseCardResultDialogVisible = ref(false)
|
||||
const enterpriseCardAuthorizeLoading = ref(false)
|
||||
const enterpriseCardRecallLoading = ref(false)
|
||||
const enterpriseCardResultTitle = ref('')
|
||||
const enterpriseCardAuthorizeFormRef = ref<FormInstance>()
|
||||
const enterpriseCardRecallFormRef = ref<FormInstance>()
|
||||
const enterpriseOptions = ref<EnterpriseItem[]>([])
|
||||
const enterpriseLoading = ref(false)
|
||||
const enterpriseCardOperationResult = ref<
|
||||
Partial<AllocateCardsResponse & RecallCardsResponse> & {
|
||||
success_count: number
|
||||
fail_count: number
|
||||
}
|
||||
>({
|
||||
success_count: 0,
|
||||
fail_count: 0,
|
||||
failed_items: null,
|
||||
recalled_devices: null
|
||||
})
|
||||
|
||||
// 操作审计日志弹窗
|
||||
const operationLogsDialogVisible = ref(false)
|
||||
@@ -727,6 +1111,29 @@
|
||||
batch_no: ''
|
||||
})
|
||||
const seriesBindingForm = reactive(createInitialSeriesBindingState())
|
||||
const createInitialEnterpriseCardAuthorizeState = () => ({
|
||||
enterprise_id: undefined as number | undefined,
|
||||
selection_type: CardSelectionType.LIST,
|
||||
iccid_start: '',
|
||||
iccid_end: '',
|
||||
carrier_id: undefined as number | undefined,
|
||||
shop_id_path: undefined as number[] | undefined,
|
||||
batch_no: '',
|
||||
iccid: '',
|
||||
remark: ''
|
||||
})
|
||||
const createInitialEnterpriseCardRecallState = () => ({
|
||||
enterprise_id: undefined as number | undefined,
|
||||
selection_type: CardSelectionType.LIST,
|
||||
iccid_start: '',
|
||||
iccid_end: '',
|
||||
carrier_id: undefined as number | undefined,
|
||||
batch_no: '',
|
||||
iccid: '',
|
||||
remark: ''
|
||||
})
|
||||
const enterpriseCardAuthorizeForm = reactive(createInitialEnterpriseCardAuthorizeState())
|
||||
const enterpriseCardRecallForm = reactive(createInitialEnterpriseCardRecallState())
|
||||
const parseNumericStringToBigInt = (value: string): bigint | null => {
|
||||
const normalizedValue = value.trim()
|
||||
if (!normalizedValue || !/^\d+$/.test(normalizedValue)) {
|
||||
@@ -761,9 +1168,54 @@
|
||||
const recallRangeCountText = computed(() =>
|
||||
getFormattedRangeCountText(recallForm.iccid_start, recallForm.iccid_end)
|
||||
)
|
||||
const enterpriseCardAuthorizeRangeCountText = computed(() =>
|
||||
getFormattedRangeCountText(
|
||||
enterpriseCardAuthorizeForm.iccid_start,
|
||||
enterpriseCardAuthorizeForm.iccid_end
|
||||
)
|
||||
)
|
||||
const enterpriseCardRecallRangeCountText = computed(() =>
|
||||
getFormattedRangeCountText(
|
||||
enterpriseCardRecallForm.iccid_start,
|
||||
enterpriseCardRecallForm.iccid_end
|
||||
)
|
||||
)
|
||||
const seriesBindingRangeCountText = computed(() =>
|
||||
getFormattedRangeCountText(seriesBindingForm.iccid_start, seriesBindingForm.iccid_end)
|
||||
)
|
||||
const createEnterpriseCardRules = (form: { selection_type: CardSelectionType }) =>
|
||||
reactive<FormRules>({
|
||||
enterprise_id: [{ required: true, message: '请选择企业', trigger: 'change' }],
|
||||
selection_type: [{ required: true, message: '请选择选卡方式', trigger: 'change' }],
|
||||
iccid_start: [
|
||||
{
|
||||
required: true,
|
||||
validator: (_rule, value, callback) => {
|
||||
if (form.selection_type === CardSelectionType.RANGE && !value) {
|
||||
callback(new Error('请输入起始ICCID'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
iccid_end: [
|
||||
{
|
||||
required: true,
|
||||
validator: (_rule, value, callback) => {
|
||||
if (form.selection_type === CardSelectionType.RANGE && !value) {
|
||||
callback(new Error('请输入结束ICCID'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
})
|
||||
const enterpriseCardAuthorizeRules = createEnterpriseCardRules(enterpriseCardAuthorizeForm)
|
||||
const enterpriseCardRecallRules = createEnterpriseCardRules(enterpriseCardRecallForm)
|
||||
const seriesBindingRules = reactive<FormRules>({
|
||||
selection_type: [{ required: true, message: '请选择选卡方式', trigger: 'change' }],
|
||||
series_id: [{ required: true, message: '请选择套餐系列', trigger: 'change' }],
|
||||
@@ -921,6 +1373,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearchEnterprise = async (query?: string) => {
|
||||
try {
|
||||
enterpriseLoading.value = true
|
||||
const params = {
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
enterprise_name: query || undefined,
|
||||
status: 1
|
||||
}
|
||||
const res = await EnterpriseService.getEnterprises(params)
|
||||
if (res.code === 0) {
|
||||
enterpriseOptions.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索企业失败:', error)
|
||||
} finally {
|
||||
enterpriseLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索套餐系列(用于搜索栏)
|
||||
const handleSearchSeries = async (query: string) => {
|
||||
try {
|
||||
@@ -1043,10 +1515,25 @@
|
||||
label: 'label',
|
||||
children: 'children'
|
||||
}
|
||||
const loadTopLevelShopCascadeOptions = async () => {
|
||||
try {
|
||||
const res = await ShopService.getShopsCascade({ parent_id: undefined })
|
||||
if (res.code === 0) {
|
||||
shopCascadeOptions.value = (res.data || []).map((item: any) => ({
|
||||
value: item.id,
|
||||
label: item.shop_name,
|
||||
leaf: !item.has_children
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载顶级店铺失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索表单初始值
|
||||
const initialSearchState: {
|
||||
status: undefined | number
|
||||
network_status: undefined | number
|
||||
iccid: string
|
||||
carrier_name: string
|
||||
msisdn: string
|
||||
@@ -1057,9 +1544,12 @@
|
||||
has_active_package: undefined | boolean
|
||||
is_distributed: undefined | number
|
||||
is_standalone: undefined | boolean
|
||||
authorized_enterprise_id: undefined | number
|
||||
is_authorized_to_enterprise: undefined | boolean
|
||||
[key: string]: any
|
||||
} = {
|
||||
status: undefined,
|
||||
network_status: undefined,
|
||||
iccid: '',
|
||||
carrier_name: '',
|
||||
msisdn: '',
|
||||
@@ -1070,6 +1560,8 @@
|
||||
has_active_package: undefined,
|
||||
is_distributed: undefined,
|
||||
is_standalone: undefined,
|
||||
authorized_enterprise_id: undefined,
|
||||
is_authorized_to_enterprise: undefined,
|
||||
is_replaced: undefined,
|
||||
iccid_start: '',
|
||||
iccid_end: ''
|
||||
@@ -1184,7 +1676,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '运营商名称',
|
||||
label: '运营商',
|
||||
prop: 'carrier_name',
|
||||
type: 'select',
|
||||
config: {
|
||||
@@ -1216,6 +1708,19 @@
|
||||
{ label: '已停用', value: 4 }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '网络状态',
|
||||
prop: 'network_status',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '全部'
|
||||
},
|
||||
options: () => [
|
||||
{ label: '停机', value: 0 },
|
||||
{ label: '正常', value: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '卡虚拟号',
|
||||
prop: 'virtual_no',
|
||||
@@ -1235,7 +1740,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '绑定设备虚拟号',
|
||||
label: '设备虚拟号',
|
||||
prop: 'device_virtual_no',
|
||||
type: 'input',
|
||||
config: {
|
||||
@@ -1279,7 +1784,7 @@
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '是否已分销',
|
||||
label: '分销状态',
|
||||
prop: 'is_distributed',
|
||||
type: 'select',
|
||||
config: {
|
||||
@@ -1292,7 +1797,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '是否绑定设备',
|
||||
label: '是否绑设备',
|
||||
prop: 'is_standalone',
|
||||
type: 'select',
|
||||
config: {
|
||||
@@ -1305,7 +1810,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '是否有换卡记录',
|
||||
label: '有无换卡',
|
||||
prop: 'is_replaced',
|
||||
type: 'select',
|
||||
config: {
|
||||
@@ -1318,7 +1823,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'ICCID起始号',
|
||||
label: '起始号',
|
||||
prop: 'iccid_start',
|
||||
type: 'input',
|
||||
config: {
|
||||
@@ -1327,7 +1832,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'ICCID结束号',
|
||||
label: '结束号',
|
||||
prop: 'iccid_end',
|
||||
type: 'input',
|
||||
config: {
|
||||
@@ -1339,7 +1844,7 @@
|
||||
|
||||
// 搜索表单配置
|
||||
baseFormItems.splice(8, 0, {
|
||||
label: '是否有生效套餐',
|
||||
label: '有效套餐',
|
||||
prop: 'has_active_package',
|
||||
type: 'select',
|
||||
config: {
|
||||
@@ -1351,6 +1856,37 @@
|
||||
{ label: '否', value: false }
|
||||
]
|
||||
})
|
||||
baseFormItems.splice(9, 0, {
|
||||
label: '授权企业',
|
||||
prop: 'authorized_enterprise_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: handleSearchEnterprise,
|
||||
loading: enterpriseLoading.value,
|
||||
placeholder: '请选择或搜索企业'
|
||||
},
|
||||
options: () =>
|
||||
enterpriseOptions.value.map((enterprise) => ({
|
||||
label: enterprise.enterprise_name,
|
||||
value: enterprise.id
|
||||
}))
|
||||
})
|
||||
baseFormItems.splice(10, 0, {
|
||||
label: '授权状态',
|
||||
prop: 'is_authorized_to_enterprise',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '全部'
|
||||
},
|
||||
options: () => [
|
||||
{ label: '已授权', value: true },
|
||||
{ label: '未授权', value: false }
|
||||
]
|
||||
})
|
||||
|
||||
const formItems: SearchFormItem[] = baseFormItems
|
||||
|
||||
@@ -1999,6 +2535,175 @@
|
||||
})
|
||||
}
|
||||
|
||||
const getInferredCardEnterpriseId = () => {
|
||||
const filterEnterpriseId = formFilters.authorized_enterprise_id
|
||||
if (filterEnterpriseId) return filterEnterpriseId
|
||||
|
||||
const selectedEnterpriseIds = Array.from(
|
||||
new Set(
|
||||
selectedCards.value
|
||||
.map((card) => card.authorized_enterprise_id)
|
||||
.filter((id): id is number => typeof id === 'number')
|
||||
)
|
||||
)
|
||||
|
||||
return selectedEnterpriseIds.length === 1 ? selectedEnterpriseIds[0] : undefined
|
||||
}
|
||||
|
||||
const resetEnterpriseCardAuthorizeForm = () => {
|
||||
Object.assign(enterpriseCardAuthorizeForm, createInitialEnterpriseCardAuthorizeState(), {
|
||||
selection_type:
|
||||
selectedCards.value.length > 0 ? CardSelectionType.LIST : CardSelectionType.FILTER,
|
||||
carrier_id: undefined,
|
||||
shop_id_path: undefined,
|
||||
batch_no: formFilters.batch_no || '',
|
||||
iccid: formFilters.iccid || ''
|
||||
})
|
||||
}
|
||||
|
||||
const resetEnterpriseCardRecallForm = () => {
|
||||
Object.assign(enterpriseCardRecallForm, createInitialEnterpriseCardRecallState(), {
|
||||
enterprise_id: getInferredCardEnterpriseId(),
|
||||
selection_type:
|
||||
selectedCards.value.length > 0 ? CardSelectionType.LIST : CardSelectionType.FILTER,
|
||||
carrier_id: undefined,
|
||||
batch_no: formFilters.batch_no || '',
|
||||
iccid: formFilters.iccid || ''
|
||||
})
|
||||
}
|
||||
|
||||
const showEnterpriseCardAuthorizeDialog = async () => {
|
||||
resetEnterpriseCardAuthorizeForm()
|
||||
await Promise.all([
|
||||
handleSearchEnterprise(),
|
||||
loadAllocateCarrierOptions(),
|
||||
loadAllocateBatchNoOptions(),
|
||||
loadTopLevelShopCascadeOptions()
|
||||
])
|
||||
enterpriseCardAuthorizeDialogVisible.value = true
|
||||
enterpriseCardAuthorizeFormRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
const showEnterpriseCardRecallDialog = async () => {
|
||||
resetEnterpriseCardRecallForm()
|
||||
await Promise.all([
|
||||
handleSearchEnterprise(),
|
||||
loadAllocateCarrierOptions(),
|
||||
loadAllocateBatchNoOptions()
|
||||
])
|
||||
enterpriseCardRecallDialogVisible.value = true
|
||||
enterpriseCardRecallFormRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
const handleEnterpriseCardAuthorizeDialogClose = () => {
|
||||
Object.assign(enterpriseCardAuthorizeForm, createInitialEnterpriseCardAuthorizeState())
|
||||
enterpriseCardAuthorizeFormRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const handleEnterpriseCardRecallDialogClose = () => {
|
||||
Object.assign(enterpriseCardRecallForm, createInitialEnterpriseCardRecallState())
|
||||
enterpriseCardRecallFormRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const appendEnterpriseCardFilterParams = (params: Record<string, unknown>, form: any) => {
|
||||
if (form.carrier_id) params.carrier_id = form.carrier_id
|
||||
if (Array.isArray(form.shop_id_path) && form.shop_id_path.length > 0) {
|
||||
params.shop_id = form.shop_id_path[form.shop_id_path.length - 1]
|
||||
}
|
||||
if (form.batch_no) params.batch_no = form.batch_no
|
||||
if (form.iccid) params.iccid = form.iccid
|
||||
}
|
||||
|
||||
const buildEnterpriseCardParams = (form: any) => {
|
||||
const params: Record<string, unknown> = {
|
||||
selection_type: form.selection_type,
|
||||
remark: form.remark || undefined
|
||||
}
|
||||
|
||||
if (form.selection_type === CardSelectionType.LIST) {
|
||||
const iccids = selectedCards.value.map((card) => card.iccid)
|
||||
if (!iccids.length) {
|
||||
ElMessage.warning('列表模式下请先选择卡')
|
||||
return null
|
||||
}
|
||||
params.iccids = iccids
|
||||
} else if (form.selection_type === CardSelectionType.RANGE) {
|
||||
params.iccid_start = form.iccid_start
|
||||
params.iccid_end = form.iccid_end
|
||||
} else {
|
||||
appendEnterpriseCardFilterParams(params, form)
|
||||
}
|
||||
|
||||
Object.keys(params).forEach((key) => {
|
||||
if (params[key] === undefined || params[key] === '') delete params[key]
|
||||
})
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
const applyEnterpriseCardOperationSuccess = async (
|
||||
title: string,
|
||||
result: AllocateCardsResponse | RecallCardsResponse
|
||||
) => {
|
||||
enterpriseCardOperationResult.value = result
|
||||
enterpriseCardResultTitle.value = title
|
||||
enterpriseCardResultDialogVisible.value = true
|
||||
selectedCards.value = []
|
||||
await getTableData()
|
||||
}
|
||||
|
||||
const handleEnterpriseCardAuthorize = async () => {
|
||||
if (!enterpriseCardAuthorizeFormRef.value) return
|
||||
|
||||
await enterpriseCardAuthorizeFormRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
const params = buildEnterpriseCardParams(enterpriseCardAuthorizeForm)
|
||||
if (!params) return
|
||||
|
||||
enterpriseCardAuthorizeLoading.value = true
|
||||
try {
|
||||
const res = await EnterpriseService.allocateCards(
|
||||
enterpriseCardAuthorizeForm.enterprise_id!,
|
||||
params as any
|
||||
)
|
||||
if (res.code === 0) {
|
||||
enterpriseCardAuthorizeDialogVisible.value = false
|
||||
await applyEnterpriseCardOperationSuccess('授权卡给企业结果', res.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('授权卡给企业失败:', error)
|
||||
} finally {
|
||||
enterpriseCardAuthorizeLoading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleEnterpriseCardRecall = async () => {
|
||||
if (!enterpriseCardRecallFormRef.value) return
|
||||
|
||||
await enterpriseCardRecallFormRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
const params = buildEnterpriseCardParams(enterpriseCardRecallForm)
|
||||
if (!params) return
|
||||
|
||||
enterpriseCardRecallLoading.value = true
|
||||
try {
|
||||
const res = await EnterpriseService.recallCards(
|
||||
enterpriseCardRecallForm.enterprise_id!,
|
||||
params as any
|
||||
)
|
||||
if (res.code === 0) {
|
||||
enterpriseCardRecallDialogVisible.value = false
|
||||
await applyEnterpriseCardOperationSuccess('回收卡授权结果', res.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('回收卡授权失败:', error)
|
||||
} finally {
|
||||
enterpriseCardRecallLoading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 显示套餐系列绑定对话框
|
||||
const showSeriesBindingDialog = async () => {
|
||||
Object.assign(seriesBindingForm, createInitialSeriesBindingState(), {
|
||||
@@ -2118,47 +2823,6 @@
|
||||
})
|
||||
}
|
||||
|
||||
// 清除卡套餐系列绑定
|
||||
const handleClearCardSeries = async () => {
|
||||
if (selectedCards.value.length === 0) {
|
||||
ElMessage.warning('请先选择要清除关联的卡')
|
||||
return
|
||||
}
|
||||
|
||||
ElMessageBox.confirm(
|
||||
`确定要清除所选 ${selectedCards.value.length} 张卡的套餐系列关联吗?`,
|
||||
'确认清除',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
.then(async () => {
|
||||
try {
|
||||
const res = await CardService.batchSetCardSeriesBinding({
|
||||
selection_type: CardSelectionType.LIST,
|
||||
iccids: selectedCards.value.map((card) => card.iccid),
|
||||
series_id: 0
|
||||
})
|
||||
if (res.code === 0) {
|
||||
if (res.data.fail_count === 0) {
|
||||
ElMessage.success('清除关联成功')
|
||||
} else {
|
||||
ElMessage.warning(`部分失败:${res.data.failed_items?.[0]?.reason || '未知原因'}`)
|
||||
}
|
||||
selectedCards.value = []
|
||||
await getTableData()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('清除关联失败:', error)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 用户取消
|
||||
})
|
||||
}
|
||||
|
||||
// IoT卡操作处理函数
|
||||
const handleCardOperation = (command: string, iccid: string, row?: any) => {
|
||||
switch (command) {
|
||||
@@ -2258,33 +2922,6 @@
|
||||
// 用户取消
|
||||
})
|
||||
}
|
||||
|
||||
// 手动停用 IoT 卡(资产层面的停用)
|
||||
const handleManualDeactivate = (iccid: string) => {
|
||||
ElMessageBox.confirm(
|
||||
`确定要手动停用卡片 ${iccid} 吗?此操作将在资产管理层面停用该卡。`,
|
||||
'确认手动停用',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
.then(async () => {
|
||||
try {
|
||||
const res = await AssetService.deactivateAsset(iccid)
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('手动停用成功')
|
||||
await getTableData()
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('手动停用失败:', error)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 用户取消
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -63,8 +63,8 @@
|
||||
:currentPage="commissionPagination.page"
|
||||
:pageSize="commissionPagination.pageSize"
|
||||
:total="commissionPagination.total"
|
||||
:marginTop="10"
|
||||
:height="500"
|
||||
:marginTop="0"
|
||||
:height="450"
|
||||
@size-change="handleCommissionSizeChange"
|
||||
@current-change="handleCommissionCurrentChange"
|
||||
>
|
||||
@@ -231,6 +231,45 @@
|
||||
|
||||
<!-- 预充值钱包流水 Tab -->
|
||||
<ElTabPane label="预充值钱包流水" name="mainWallet">
|
||||
<ElForm :model="mainWalletSearchForm" class="main-wallet-filter" label-position="top">
|
||||
<div class="main-wallet-filter__grid">
|
||||
<ElFormItem label="交易类型">
|
||||
<ElSelect
|
||||
v-model="mainWalletSearchForm.transaction_type"
|
||||
clearable
|
||||
placeholder="全部类型"
|
||||
>
|
||||
<ElOption label="充值入账" :value="MainWalletTransactionType.RECHARGE" />
|
||||
<ElOption label="套餐扣款" :value="MainWalletTransactionType.DEDUCT" />
|
||||
<ElOption label="退款" :value="MainWalletTransactionType.REFUND" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="交易日期">
|
||||
<ElDatePicker
|
||||
v-model="mainWalletSearchForm.date_range"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
unlink-panels
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="资产标识">
|
||||
<ElInput
|
||||
v-model="mainWalletSearchForm.asset_identifier"
|
||||
clearable
|
||||
maxlength="100"
|
||||
placeholder="请输入ICCID或设备虚拟号"
|
||||
@keyup.enter="handleMainWalletSearch"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem class="main-wallet-filter__actions">
|
||||
<ElButton type="primary" @click="handleMainWalletSearch">搜索</ElButton>
|
||||
<ElButton @click="handleMainWalletReset">重置</ElButton>
|
||||
</ElFormItem>
|
||||
</div>
|
||||
</ElForm>
|
||||
<ArtTable
|
||||
ref="mainWalletTableRef"
|
||||
row-key="id"
|
||||
@@ -384,12 +423,14 @@
|
||||
import { CommissionService } from '@/api/modules'
|
||||
import { ElMessage, ElMessageBox, ElTag } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { MainWalletTransactionType } from '@/types/api/commission'
|
||||
import type {
|
||||
ShopFundSummaryItem,
|
||||
ShopCommissionRecordItem,
|
||||
WithdrawalRequestItem,
|
||||
CommissionResolveAction,
|
||||
MainWalletTransactionItem
|
||||
MainWalletTransactionItem,
|
||||
MainWalletTransactionQueryParams
|
||||
} from '@/types/api/commission'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
@@ -491,6 +532,15 @@
|
||||
pageSize: 20,
|
||||
total: 0
|
||||
})
|
||||
const mainWalletSearchForm = reactive<{
|
||||
transaction_type: MainWalletTransactionType | undefined
|
||||
date_range: [string, string] | [] | null
|
||||
asset_identifier: string
|
||||
}>({
|
||||
transaction_type: undefined,
|
||||
date_range: [],
|
||||
asset_identifier: ''
|
||||
})
|
||||
|
||||
// 列配置
|
||||
const columnOptions = [
|
||||
@@ -713,6 +763,9 @@
|
||||
commissionPagination.page = 1
|
||||
withdrawalPagination.page = 1
|
||||
mainWalletPagination.page = 1
|
||||
mainWalletPagination.total = 0
|
||||
mainWalletRecords.value = []
|
||||
resetMainWalletSearchForm()
|
||||
|
||||
// 加载佣金明细
|
||||
loadCommissionRecords()
|
||||
@@ -841,9 +894,14 @@
|
||||
|
||||
mainWalletLoading.value = true
|
||||
try {
|
||||
const params = {
|
||||
const [startDate, endDate] = mainWalletSearchForm.date_range || []
|
||||
const params: MainWalletTransactionQueryParams = {
|
||||
page: mainWalletPagination.page,
|
||||
page_size: mainWalletPagination.pageSize
|
||||
page_size: mainWalletPagination.pageSize,
|
||||
transaction_type: mainWalletSearchForm.transaction_type,
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
asset_identifier: mainWalletSearchForm.asset_identifier.trim() || undefined
|
||||
}
|
||||
const res = await CommissionService.getMainWalletTransactions(
|
||||
currentShop.value.shop_id,
|
||||
@@ -872,6 +930,23 @@
|
||||
loadMainWalletRecords()
|
||||
}
|
||||
|
||||
const resetMainWalletSearchForm = () => {
|
||||
mainWalletSearchForm.transaction_type = undefined
|
||||
mainWalletSearchForm.date_range = []
|
||||
mainWalletSearchForm.asset_identifier = ''
|
||||
}
|
||||
|
||||
const handleMainWalletSearch = () => {
|
||||
mainWalletPagination.page = 1
|
||||
loadMainWalletRecords()
|
||||
}
|
||||
|
||||
const handleMainWalletReset = () => {
|
||||
resetMainWalletSearchForm()
|
||||
mainWalletPagination.page = 1
|
||||
loadMainWalletRecords()
|
||||
}
|
||||
|
||||
// 处理佣金修正
|
||||
const handleResolveCommission = (
|
||||
row: ShopCommissionRecordItem,
|
||||
@@ -955,6 +1030,58 @@
|
||||
}
|
||||
}
|
||||
|
||||
.main-wallet-filter {
|
||||
padding: 16px 16px 4px;
|
||||
margin-bottom: 12px;
|
||||
background: var(--el-fill-color-lighter);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 8px;
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
:deep(.el-select),
|
||||
:deep(.el-date-editor),
|
||||
:deep(.el-input) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.main-wallet-filter__grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(140px, 0.8fr) minmax(260px, 1.4fr) minmax(220px, 1fr) auto;
|
||||
gap: 12px 16px;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.main-wallet-filter__actions {
|
||||
:deep(.el-form-item__content) {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.main-wallet-filter__grid {
|
||||
grid-template-columns: repeat(2, minmax(220px, 1fr));
|
||||
}
|
||||
|
||||
.main-wallet-filter__actions {
|
||||
:deep(.el-form-item__content) {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.main-wallet-filter__grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.asset-identifier-link {
|
||||
color: var(--el-color-primary);
|
||||
text-decoration: underline;
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
</ElCard>
|
||||
|
||||
<PaymentVoucherDialog :file-key="paymentVoucherFileKey" @close="paymentVoucherFileKey = ''" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -34,7 +36,7 @@
|
||||
import { AgentRechargeService } from '@/api/modules'
|
||||
import type { AgentRecharge, AgentRechargeStatus, AgentRechargePaymentMethod } from '@/types/api'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
|
||||
|
||||
defineOptions({ name: 'AgentRechargeDetail' })
|
||||
|
||||
@@ -43,6 +45,7 @@
|
||||
|
||||
const loading = ref(false)
|
||||
const detailData = ref<AgentRecharge | null>(null)
|
||||
const paymentVoucherFileKey = ref('')
|
||||
|
||||
const pageTitle = computed(() => `充值订单详情`)
|
||||
|
||||
@@ -124,6 +127,24 @@
|
||||
prop: 'payment_transaction_id',
|
||||
formatter: (value) => value || '-',
|
||||
fullWidth: true
|
||||
},
|
||||
{
|
||||
label: '支付凭证',
|
||||
fullWidth: true,
|
||||
render: (data) =>
|
||||
data.payment_voucher_key
|
||||
? h(
|
||||
ElButton,
|
||||
{
|
||||
type: 'primary',
|
||||
link: true,
|
||||
onClick: () => {
|
||||
paymentVoucherFileKey.value = data.payment_voucher_key
|
||||
}
|
||||
},
|
||||
() => '查看支付凭证'
|
||||
)
|
||||
: h('span', '-')
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -96,32 +96,25 @@
|
||||
label="支付凭证"
|
||||
prop="payment_voucher_key"
|
||||
>
|
||||
<ElUpload
|
||||
<VoucherUpload
|
||||
ref="uploadRef"
|
||||
class="payment-voucher-upload"
|
||||
drag
|
||||
:auto-upload="false"
|
||||
:on-change="handleVoucherFileChange"
|
||||
:on-remove="handleRemoveVoucher"
|
||||
accept="image/*"
|
||||
list-type="picture"
|
||||
:limit="1"
|
||||
>
|
||||
<el-icon class="payment-voucher-upload__icon"><UploadFilled /></el-icon>
|
||||
<div class="payment-voucher-upload__text">
|
||||
将支付凭证拖到此处,或<em>点击上传</em>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">支持 jpg、png 格式,文件大小不超过 5MB</div>
|
||||
</template>
|
||||
</ElUpload>
|
||||
v-model="createForm.payment_voucher_key"
|
||||
voucher-name="支付凭证"
|
||||
@uploading-change="voucherUploading = $event"
|
||||
@change="createFormRef?.validateField('payment_voucher_key')"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="createDialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleCreateRecharge" :loading="createLoading">
|
||||
确认创建
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="handleCreateRecharge"
|
||||
:loading="createLoading || voucherUploading"
|
||||
:disabled="voucherUploading"
|
||||
>
|
||||
{{ voucherUploading ? '凭证上传中...' : '确认创建' }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
@@ -178,19 +171,16 @@
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { AgentRechargeService, ShopService, StorageService } from '@/api/modules'
|
||||
import { AgentRechargeService, ShopService } from '@/api/modules'
|
||||
import {
|
||||
ElMessage,
|
||||
ElTag,
|
||||
ElTreeSelect,
|
||||
ElButton,
|
||||
ElCascader,
|
||||
ElInputNumber,
|
||||
ElSelect,
|
||||
ElOption,
|
||||
ElUpload
|
||||
ElOption
|
||||
} from 'element-plus'
|
||||
import { UploadFilled } from '@element-plus/icons-vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type {
|
||||
AgentRecharge,
|
||||
@@ -198,25 +188,23 @@
|
||||
AgentRechargeStatus,
|
||||
AgentRechargePaymentMethod,
|
||||
CreateAgentRechargeRequest,
|
||||
ConfirmOfflinePaymentRequest,
|
||||
ShopResponse
|
||||
ConfirmOfflinePaymentRequest
|
||||
} from '@/types/api'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
|
||||
import VoucherUpload from '@/components/business/VoucherUpload.vue'
|
||||
|
||||
defineOptions({ name: 'AgentRechargeList' })
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const { hasAuth } = useAuth()
|
||||
|
||||
const loading = ref(false)
|
||||
const createLoading = ref(false)
|
||||
const voucherUploading = ref(false)
|
||||
const confirmPayLoading = ref(false)
|
||||
const tableRef = ref()
|
||||
const createDialogVisible = ref(false)
|
||||
@@ -238,8 +226,6 @@
|
||||
|
||||
// 店铺选项
|
||||
const shopOptions = ref<any[]>([])
|
||||
const shopTreeData = ref<ShopResponse[]>([])
|
||||
|
||||
// 店铺级联选择相关
|
||||
const shopCascadeOptions = ref<any[]>([])
|
||||
const shopCascadeProps = {
|
||||
@@ -340,7 +326,7 @@
|
||||
|
||||
const createFormRef = ref<FormInstance>()
|
||||
const confirmPayFormRef = ref<FormInstance>()
|
||||
const uploadRef = ref()
|
||||
const uploadRef = ref<InstanceType<typeof VoucherUpload>>()
|
||||
const MIN_RECHARGE_AMOUNT = 0.01
|
||||
|
||||
const createRules = computed<FormRules>(() => {
|
||||
@@ -523,33 +509,6 @@
|
||||
isFirstActivation = false
|
||||
})
|
||||
|
||||
// 构建树形数据
|
||||
const buildTreeData = (items: ShopResponse[]) => {
|
||||
const map = new Map<number, ShopResponse & { children?: ShopResponse[] }>()
|
||||
const tree: ShopResponse[] = []
|
||||
|
||||
// 先将所有项放入 map
|
||||
items.forEach((item) => {
|
||||
map.set(item.id, { ...item, children: [] })
|
||||
})
|
||||
|
||||
// 构建树形结构
|
||||
items.forEach((item) => {
|
||||
const node = map.get(item.id)!
|
||||
if (item.parent_id && map.has(item.parent_id)) {
|
||||
// 有父节点,添加到父节点的 children 中
|
||||
const parent = map.get(item.parent_id)!
|
||||
if (!parent.children) parent.children = []
|
||||
parent.children.push(node)
|
||||
} else {
|
||||
// 没有父节点或父节点不存在,作为根节点
|
||||
tree.push(node)
|
||||
}
|
||||
})
|
||||
|
||||
return tree
|
||||
}
|
||||
|
||||
// 加载店铺列表 - 改用级联查询
|
||||
const loadShops = async () => {
|
||||
try {
|
||||
@@ -671,60 +630,18 @@
|
||||
createForm.payment_method = ''
|
||||
createForm.shop_id = null
|
||||
createForm.payment_voucher_key = undefined
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
|
||||
// 支付凭证文件变化
|
||||
const handleVoucherFileChange = async (file: any) => {
|
||||
const isImage = file.raw.type.startsWith('image/')
|
||||
const isLt5M = file.raw.size / 1024 / 1024 < 5
|
||||
|
||||
if (!isImage) {
|
||||
ElMessage.error('只能上传图片文件')
|
||||
uploadRef.value?.clearFiles()
|
||||
return
|
||||
}
|
||||
if (!isLt5M) {
|
||||
ElMessage.error('图片大小不能超过 5MB')
|
||||
uploadRef.value?.clearFiles()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const uploadUrlRes = await StorageService.getUploadUrl({
|
||||
file_name: file.name,
|
||||
content_type: file.raw.type,
|
||||
purpose: 'attachment'
|
||||
})
|
||||
|
||||
if (uploadUrlRes.code !== 0) {
|
||||
ElMessage.error(uploadUrlRes.msg || '获取上传地址失败')
|
||||
uploadRef.value?.clearFiles()
|
||||
return
|
||||
}
|
||||
|
||||
const { upload_url, file_key } = uploadUrlRes.data
|
||||
|
||||
await StorageService.uploadFile(upload_url, file.raw, file.raw.type)
|
||||
|
||||
createForm.payment_voucher_key = file_key
|
||||
ElMessage.success('上传成功')
|
||||
} catch (error: any) {
|
||||
console.error('上传失败:', error)
|
||||
createForm.payment_voucher_key = undefined
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
}
|
||||
|
||||
// 删除支付凭证
|
||||
const handleRemoveVoucher = () => {
|
||||
createForm.payment_voucher_key = undefined
|
||||
voucherUploading.value = false
|
||||
uploadRef.value?.clearFiles(false)
|
||||
}
|
||||
|
||||
// 创建充值订单
|
||||
const handleCreateRecharge = async () => {
|
||||
const formRef = createFormRef.value
|
||||
if (!formRef) return
|
||||
if (voucherUploading.value) {
|
||||
ElMessage.warning('支付凭证上传中,请稍候')
|
||||
return
|
||||
}
|
||||
|
||||
await formRef.validate(async (valid) => {
|
||||
if (valid) {
|
||||
@@ -854,34 +771,4 @@
|
||||
.agent-recharge-page {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.payment-voucher-upload {
|
||||
width: 100%;
|
||||
|
||||
:deep(.el-upload) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.el-upload-dragger) {
|
||||
width: 100%;
|
||||
padding: 24px 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
margin-bottom: 12px;
|
||||
font-size: 28px;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
&__text {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-regular);
|
||||
|
||||
em {
|
||||
font-style: normal;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -22,13 +22,7 @@
|
||||
</div>
|
||||
</ElCard>
|
||||
|
||||
<ElImageViewer
|
||||
v-if="voucherPreviewVisible"
|
||||
:url-list="refundVoucherUrls"
|
||||
:initial-index="voucherPreviewIndex"
|
||||
hide-on-click-modal
|
||||
@close="voucherPreviewVisible = false"
|
||||
/>
|
||||
<PaymentVoucherDialog :file-key="refundVoucherFileKey" @close="refundVoucherFileKey = ''" />
|
||||
|
||||
<!-- 审批通过对话框 -->
|
||||
<ElDialog
|
||||
@@ -172,11 +166,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed, h, reactive } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElCard, ElImageViewer, ElIcon, ElMessage, ElTag } from 'element-plus'
|
||||
import { ElButton, ElCard, ElIcon, ElMessage, ElTag } from 'element-plus'
|
||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||
import DetailPage from '@/components/common/DetailPage.vue'
|
||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||
import { RefundService, StorageService } from '@/api/modules'
|
||||
import { RefundService } from '@/api/modules'
|
||||
import type {
|
||||
Refund,
|
||||
RefundStatus,
|
||||
@@ -185,6 +179,7 @@
|
||||
ResubmitRefundRequest
|
||||
} from '@/types/api'
|
||||
import { formatDateTime, yuanToFen } from '@/utils/business/format'
|
||||
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
|
||||
|
||||
defineOptions({ name: 'RefundDetail' })
|
||||
|
||||
@@ -193,9 +188,7 @@
|
||||
|
||||
const loading = ref(false)
|
||||
const refund = ref<Refund | null>(null)
|
||||
const refundVoucherUrls = ref<string[]>([])
|
||||
const voucherPreviewVisible = ref(false)
|
||||
const voucherPreviewIndex = ref(0)
|
||||
const refundVoucherFileKey = ref('')
|
||||
|
||||
const pageTitle = computed(() => `退款详情`)
|
||||
|
||||
@@ -262,14 +255,6 @@
|
||||
return statusMap[status] || '-'
|
||||
}
|
||||
|
||||
const getVoucherKeys = (data: Refund): string[] => {
|
||||
const voucherKey = data.refund_voucher_key || ''
|
||||
return voucherKey
|
||||
.split(',')
|
||||
.map((key) => key.trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
const detailSections = computed((): DetailSection[] => [
|
||||
{
|
||||
title: '退款信息',
|
||||
@@ -309,18 +294,18 @@
|
||||
{
|
||||
label: '退款凭证',
|
||||
fullWidth: true,
|
||||
render: () =>
|
||||
refundVoucherUrls.value.length
|
||||
render: (data) =>
|
||||
data.refund_voucher_key
|
||||
? h(
|
||||
'div',
|
||||
{ class: 'refund-voucher-list' },
|
||||
refundVoucherUrls.value.map((url, index) =>
|
||||
h('img', {
|
||||
class: 'refund-voucher-image',
|
||||
src: url,
|
||||
onClick: () => handlePreviewRefundVoucher(index)
|
||||
})
|
||||
)
|
||||
ElButton,
|
||||
{
|
||||
type: 'primary',
|
||||
link: true,
|
||||
onClick: () => {
|
||||
refundVoucherFileKey.value = data.refund_voucher_key
|
||||
}
|
||||
},
|
||||
() => '查看退款凭证'
|
||||
)
|
||||
: h('span', '-')
|
||||
},
|
||||
@@ -391,7 +376,6 @@
|
||||
const res = await RefundService.getRefundById(id)
|
||||
if (res.code === 0) {
|
||||
refund.value = res.data
|
||||
await loadRefundVoucherUrls(res.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -405,26 +389,6 @@
|
||||
router.back()
|
||||
}
|
||||
|
||||
const loadRefundVoucherUrls = async (data: Refund) => {
|
||||
const fileKeys = getVoucherKeys(data)
|
||||
refundVoucherUrls.value = []
|
||||
if (!fileKeys.length) return
|
||||
|
||||
try {
|
||||
const res = await StorageService.batchDownloadUrls({ file_keys: fileKeys })
|
||||
if (res.code === 0) {
|
||||
refundVoucherUrls.value = fileKeys.map((key) => res.data.urls[key]).filter(Boolean)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取退款凭证失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handlePreviewRefundVoucher = (index: number) => {
|
||||
voucherPreviewIndex.value = index
|
||||
voucherPreviewVisible.value = true
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const id = route.params.id as string
|
||||
if (id) {
|
||||
@@ -556,21 +520,5 @@
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.refund-voucher-list) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
:deep(.refund-voucher-image) {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
cursor: zoom-in;
|
||||
object-fit: cover;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<ArtSearchBar
|
||||
v-model:filter="searchForm"
|
||||
:items="searchFormItems"
|
||||
:show-expand="false"
|
||||
show-expand
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
></ArtSearchBar>
|
||||
@@ -279,7 +279,8 @@
|
||||
const initialSearchState: RefundQueryParams = {
|
||||
status: undefined,
|
||||
order_id: undefined,
|
||||
shop_id: undefined
|
||||
shop_id: undefined,
|
||||
asset_identifier: ''
|
||||
}
|
||||
|
||||
// 搜索表单
|
||||
@@ -325,6 +326,15 @@
|
||||
remoteMethod: (query: string) => searchOrdersForFilter(query)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '资产标识',
|
||||
prop: 'asset_identifier',
|
||||
type: 'input',
|
||||
placeholder: '请输入ICCID或设备虚拟号',
|
||||
config: {
|
||||
clearable: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
@@ -622,12 +632,14 @@
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const assetIdentifier = searchForm.asset_identifier?.trim()
|
||||
const params: RefundQueryParams = {
|
||||
page: pagination.page,
|
||||
page_size: pagination.page_size,
|
||||
status: searchForm.status,
|
||||
order_id: searchForm.order_id,
|
||||
shop_id: searchForm.shop_id
|
||||
shop_id: searchForm.shop_id,
|
||||
asset_identifier: assetIdentifier || undefined
|
||||
}
|
||||
const res = await RefundService.getRefunds(params)
|
||||
if (res.code === 0) {
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
<!-- 订单列表 -->
|
||||
<div v-if="detailData && detailData.items && detailData.items.length > 0">
|
||||
<ElCard style="margin-top: 20px" shadow="never">
|
||||
<div
|
||||
class="order-items-section"
|
||||
>
|
||||
<div class="order-items-section">
|
||||
<h3 class="section-title">订单列表</h3>
|
||||
<ElTable :data="detailData.items" border>
|
||||
<ElTableColumn prop="package_name" label="套餐名称" />
|
||||
@@ -57,35 +55,29 @@
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
</ElCard>
|
||||
|
||||
<PaymentVoucherDialog :file-key="paymentVoucherFileKey" @close="paymentVoucherFileKey = ''" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { h, onMounted, ref, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import {
|
||||
ElButton,
|
||||
ElCard,
|
||||
ElIcon,
|
||||
ElImage,
|
||||
ElMessage,
|
||||
ElTable,
|
||||
ElTableColumn
|
||||
} from 'element-plus'
|
||||
import { ElButton, ElCard, ElIcon, ElMessage, ElTable, ElTableColumn } from 'element-plus'
|
||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||
import DetailPage from '@/components/common/DetailPage.vue'
|
||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||
import { OrderService, StorageService } from '@/api/modules'
|
||||
import { OrderService } from '@/api/modules'
|
||||
import type {
|
||||
Order,
|
||||
OrderCommissionResult,
|
||||
OrderCommissionStatus,
|
||||
OrderOperatorType,
|
||||
OrderPaymentMethod
|
||||
OrderOperatorType
|
||||
} from '@/types/api'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
|
||||
|
||||
defineOptions({ name: 'OrderDetail' })
|
||||
|
||||
@@ -96,7 +88,7 @@
|
||||
|
||||
const loading = ref(false)
|
||||
const detailData = ref<Order | null>(null)
|
||||
const paymentVoucherUrl = ref('')
|
||||
const paymentVoucherFileKey = ref('')
|
||||
|
||||
// 格式化货币 - 将分转换为元
|
||||
const formatCurrency = (amount: number): string => {
|
||||
@@ -113,17 +105,6 @@
|
||||
return type === 'personal' ? '个人客户' : '代理商'
|
||||
}
|
||||
|
||||
// 获取支付方式文本
|
||||
const getPaymentMethodText = (method: OrderPaymentMethod): string => {
|
||||
const methodMap: Record<OrderPaymentMethod, string> = {
|
||||
wallet: '钱包支付',
|
||||
wechat: '微信支付',
|
||||
alipay: '支付宝支付',
|
||||
offline: '线下支付'
|
||||
}
|
||||
return methodMap[method] || method
|
||||
}
|
||||
|
||||
// 获取操作者类型文本
|
||||
const getOperatorTypeText = (type?: OrderOperatorType): string => {
|
||||
if (!type) return '-'
|
||||
@@ -174,17 +155,6 @@
|
||||
return roleMap[role] || role
|
||||
}
|
||||
|
||||
const loadPaymentVoucherUrl = async (fileKey?: string) => {
|
||||
paymentVoucherUrl.value = ''
|
||||
if (!fileKey || !hasAuth('orders:view_payment_voucher')) return
|
||||
|
||||
try {
|
||||
paymentVoucherUrl.value = await StorageService.getDownloadUrl(fileKey)
|
||||
} catch (error) {
|
||||
console.error('Get payment voucher failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查当前用户是否可以查看实付金额(代理账号和企业账号不能看到)
|
||||
const canViewActualPaidAmount = computed(() => {
|
||||
const userType = userStore.info.user_type
|
||||
@@ -229,21 +199,17 @@
|
||||
return h('span', '-')
|
||||
}
|
||||
|
||||
if (!paymentVoucherUrl.value) {
|
||||
return h('span', '加载中...')
|
||||
}
|
||||
|
||||
return h(ElImage, {
|
||||
src: paymentVoucherUrl.value,
|
||||
previewSrcList: [paymentVoucherUrl.value],
|
||||
fit: 'cover',
|
||||
style: {
|
||||
width: '120px',
|
||||
height: '120px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid var(--el-border-color-light)'
|
||||
}
|
||||
})
|
||||
return h(
|
||||
ElButton,
|
||||
{
|
||||
type: 'primary',
|
||||
link: true,
|
||||
onClick: () => {
|
||||
paymentVoucherFileKey.value = data.payment_voucher_key
|
||||
}
|
||||
},
|
||||
() => '查看支付凭证'
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -377,7 +343,6 @@
|
||||
const res = await OrderService.getOrderById(id)
|
||||
if (res.code === 0) {
|
||||
detailData.value = res.data
|
||||
await loadPaymentVoucherUrl(res.data.payment_voucher_key)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
@@ -199,25 +199,13 @@
|
||||
label="支付凭证"
|
||||
prop="payment_voucher_key"
|
||||
>
|
||||
<ElUpload
|
||||
<VoucherUpload
|
||||
ref="uploadRef"
|
||||
class="payment-voucher-upload"
|
||||
drag
|
||||
:auto-upload="false"
|
||||
:on-change="handleVoucherFileChange"
|
||||
:on-remove="handleRemoveVoucher"
|
||||
accept="image/*"
|
||||
list-type="picture"
|
||||
:limit="1"
|
||||
>
|
||||
<el-icon class="payment-voucher-upload__icon"><UploadFilled /></el-icon>
|
||||
<div class="payment-voucher-upload__text">
|
||||
将支付凭证拖到此处,或<em>点击上传</em>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">支持所有图片格式</div>
|
||||
</template>
|
||||
</ElUpload>
|
||||
v-model="createForm.payment_voucher_key"
|
||||
voucher-name="支付凭证"
|
||||
@uploading-change="voucherUploading = $event"
|
||||
@change="createFormRef?.validateField('payment_voucher_key')"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
@@ -229,7 +217,7 @@
|
||||
:loading="createLoading || voucherUploading"
|
||||
:disabled="voucherUploading"
|
||||
>
|
||||
{{ voucherUploading ? '图片上传中...' : '提交' }}
|
||||
{{ voucherUploading ? '凭证上传中...' : '提交' }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
@@ -255,19 +243,17 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, h, nextTick } from 'vue'
|
||||
import { computed, h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {
|
||||
OrderService,
|
||||
CardService,
|
||||
DeviceService,
|
||||
PackageManageService,
|
||||
StorageService,
|
||||
ShopService
|
||||
} from '@/api/modules'
|
||||
import { ElMessage, ElMessageBox, ElTag } from 'element-plus'
|
||||
import { UploadFilled } from '@element-plus/icons-vue'
|
||||
import type { FormInstance, FormRules, UploadInstance, UploadFile } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type {
|
||||
Order,
|
||||
OrderQueryParams,
|
||||
@@ -290,6 +276,7 @@
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
|
||||
import ExportTaskCreateDialog from '@/components/business/ExportTaskCreateDialog.vue'
|
||||
import VoucherUpload from '@/components/business/VoucherUpload.vue'
|
||||
|
||||
defineOptions({ name: 'OrderList' })
|
||||
|
||||
@@ -308,7 +295,6 @@
|
||||
const createDialogVisible = ref(false)
|
||||
const exportDialogVisible = ref(false)
|
||||
const paymentVoucherFileKey = ref('')
|
||||
let activeVoucherUploadId = 0
|
||||
|
||||
// 搜索表单初始值
|
||||
const initialSearchState: OrderSearchFormState = {
|
||||
@@ -536,82 +522,7 @@
|
||||
})
|
||||
|
||||
const createFormRef = ref<FormInstance>()
|
||||
const uploadRef = ref<UploadInstance>()
|
||||
|
||||
const invalidateVoucherUploadState = (clearFileKey = false) => {
|
||||
activeVoucherUploadId += 1
|
||||
voucherUploading.value = false
|
||||
if (clearFileKey) {
|
||||
createForm.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 requestId = ++activeVoucherUploadId
|
||||
voucherUploading.value = true
|
||||
createForm.payment_voucher_key = undefined
|
||||
|
||||
// 开始上传
|
||||
try {
|
||||
ElMessage.info('正在上传支付凭证...')
|
||||
|
||||
// 1. 获取上传 URL
|
||||
const uploadUrlRes = await StorageService.getUploadUrl({
|
||||
file_name: file.name,
|
||||
content_type: file.type,
|
||||
purpose: 'attachment'
|
||||
})
|
||||
|
||||
if (uploadUrlRes.code !== 0) {
|
||||
ElMessage.error(uploadUrlRes.msg || '获取上传地址失败')
|
||||
uploadRef.value?.clearFiles()
|
||||
return
|
||||
}
|
||||
|
||||
const { upload_url, file_key } = uploadUrlRes.data
|
||||
|
||||
// 2. 上传文件到对象存储
|
||||
await StorageService.uploadFile(upload_url, file, file.type)
|
||||
|
||||
if (requestId !== activeVoucherUploadId) return
|
||||
|
||||
// 3. 保存 file_key
|
||||
createForm.payment_voucher_key = file_key
|
||||
ElMessage.success('上传成功')
|
||||
// 触发表单验证
|
||||
await nextTick(() => {
|
||||
createFormRef.value?.validateField('payment_voucher_key')
|
||||
})
|
||||
} catch (error: any) {
|
||||
if (requestId !== activeVoucherUploadId) return
|
||||
|
||||
console.error('上传失败:', error)
|
||||
ElMessage.error(error.message || '上传失败,请重试')
|
||||
createForm.payment_voucher_key = undefined
|
||||
uploadRef.value?.clearFiles()
|
||||
} finally {
|
||||
if (requestId === activeVoucherUploadId) {
|
||||
voucherUploading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 删除支付凭证
|
||||
const handleRemoveVoucher = () => {
|
||||
invalidateVoucherUploadState(true)
|
||||
}
|
||||
const uploadRef = ref<InstanceType<typeof VoucherUpload>>()
|
||||
|
||||
const createRules = computed<FormRules>(() => {
|
||||
const baseRules: FormRules = {
|
||||
@@ -1231,10 +1142,11 @@
|
||||
createForm.iot_card_id = null
|
||||
createForm.device_id = null
|
||||
createForm.payment_method = 'wallet'
|
||||
invalidateVoucherUploadState(true)
|
||||
createForm.payment_voucher_key = undefined
|
||||
voucherUploading.value = false
|
||||
|
||||
// 清空上传文件列表
|
||||
uploadRef.value?.clearFiles()
|
||||
uploadRef.value?.clearFiles(false)
|
||||
|
||||
// 清空套餐、IoT卡和设备搜索结果
|
||||
packageOptions.value = []
|
||||
@@ -1418,36 +1330,6 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.payment-voucher-upload {
|
||||
width: 100%;
|
||||
|
||||
:deep(.el-upload) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.el-upload-dragger) {
|
||||
width: 100%;
|
||||
padding: 24px 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
margin-bottom: 12px;
|
||||
font-size: 28px;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
&__text {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-regular);
|
||||
|
||||
em {
|
||||
font-style: normal;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table__row.table-row-with-context-menu) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user