This commit is contained in:
@@ -0,0 +1,334 @@
|
||||
<template>
|
||||
<ArtTableFullScreen>
|
||||
<div class="bulk-purchase-detail-page" id="table-full-screen">
|
||||
<ElCard shadow="never" class="art-table-card">
|
||||
<div class="detail-header">
|
||||
<ElButton @click="goBack">
|
||||
<template #icon>
|
||||
<ElIcon><ArrowLeft /></ElIcon>
|
||||
</template>
|
||||
返回
|
||||
</ElButton>
|
||||
<h2 class="detail-title">批量订购任务详情</h2>
|
||||
<ElTag v-if="taskDetail" :type="getStatusType(taskDetail.status)">
|
||||
{{ taskDetail.status_name || getStatusName(taskDetail.status) }}
|
||||
</ElTag>
|
||||
</div>
|
||||
|
||||
<div v-if="loading && !taskDetail" class="detail-loading">
|
||||
<ElIcon class="is-loading" :size="36"><Loading /></ElIcon>
|
||||
<div>加载中...</div>
|
||||
</div>
|
||||
|
||||
<template v-else-if="taskDetail">
|
||||
<ElDescriptions :column="4" border>
|
||||
<ElDescriptionsItem label="任务号">{{ taskDetail.task_no || '-' }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="订单文件" :span="2">
|
||||
<span class="ellipsis-value" :title="taskDetail.file_name || '-'">
|
||||
{{ taskDetail.file_name || '-' }}
|
||||
</span>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="套餐名称">
|
||||
<span class="ellipsis-value" :title="taskDetail.package_name || '-'">
|
||||
{{ taskDetail.package_name || '-' }}
|
||||
</span>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="套餐编码">
|
||||
<span class="ellipsis-value" :title="taskDetail.package_code || '-'">
|
||||
{{ taskDetail.package_code || '-' }}
|
||||
</span>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="支付方式">
|
||||
{{ getPaymentMethodName(taskDetail.payment_method) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="总数">{{ taskDetail.total_count ?? 0 }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="成功数">{{
|
||||
taskDetail.success_count ?? 0
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="失败数">{{ taskDetail.fail_count ?? 0 }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="创建人">{{
|
||||
taskDetail.creator_name || '-'
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="支付凭证">
|
||||
{{
|
||||
taskDetail.voucher_keys?.length
|
||||
? `已上传 ${taskDetail.voucher_keys.length} 个`
|
||||
: '未上传'
|
||||
}}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="创建时间">{{
|
||||
formatDateTime(taskDetail.created_at)
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="开始时间">{{
|
||||
formatDateTime(taskDetail.started_at)
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="完成时间">{{
|
||||
formatDateTime(taskDetail.completed_at)
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="更新时间">{{
|
||||
formatDateTime(taskDetail.updated_at)
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem v-if="taskDetail.error_message" label="错误信息" :span="4">
|
||||
{{ taskDetail.error_message }}
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
|
||||
<ElAlert
|
||||
v-if="taskDetail.error_summary"
|
||||
class="task-error"
|
||||
type="error"
|
||||
:closable="false"
|
||||
:title="taskDetail.error_summary"
|
||||
/>
|
||||
|
||||
<template v-if="hasAuth(BULK_PURCHASE_PERMISSIONS.items)">
|
||||
<div class="items-toolbar">
|
||||
<ElSelect
|
||||
v-model="itemStatus"
|
||||
clearable
|
||||
placeholder="筛选行状态"
|
||||
style="width: 140px"
|
||||
>
|
||||
<ElOption label="成功" :value="BulkPurchaseTaskStatus.COMPLETED" />
|
||||
<ElOption label="失败" :value="BulkPurchaseTaskStatus.FAILED" />
|
||||
</ElSelect>
|
||||
<ElButton :loading="loading" @click="refreshTask">刷新结果</ElButton>
|
||||
</div>
|
||||
|
||||
<ElTable v-loading="loading" :data="paginatedItems" border>
|
||||
<ElTableColumn label="行号" width="90">
|
||||
<template #default="scope">{{ scope.row.line ?? '-' }}</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="资产" min-width="180">
|
||||
<template #default="scope">{{ getAssetIdentifier(scope.row) }}</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="状态" width="110">
|
||||
<template #default="scope">
|
||||
<ElTag :type="getItemStatusType(scope.row.status)">
|
||||
{{ scope.row.status_name || getItemStatusName(scope.row.status) }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn prop="order_no" label="订单号" min-width="180" show-overflow-tooltip />
|
||||
<ElTableColumn label="金额" width="120">
|
||||
<template #default="scope">{{ formatMoney(scope.row.amount) }}</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="处理结果" min-width="240" show-overflow-tooltip>
|
||||
<template #default="scope">{{ getItemReason(scope.row) }}</template>
|
||||
</ElTableColumn>
|
||||
</ElTable>
|
||||
|
||||
<div class="pagination-wrapper">
|
||||
<ElPagination
|
||||
v-model:current-page="itemsPage"
|
||||
v-model:page-size="itemsSize"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
:total="filteredItems.length"
|
||||
@size-change="handleItemSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<ElEmpty v-else :description="forbidden ? '暂无查看权限' : '暂无任务详情'" />
|
||||
</ElCard>
|
||||
</div>
|
||||
</ArtTableFullScreen>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import {
|
||||
ElAlert,
|
||||
ElButton,
|
||||
ElCard,
|
||||
ElDescriptions,
|
||||
ElDescriptionsItem,
|
||||
ElEmpty,
|
||||
ElIcon,
|
||||
ElMessage,
|
||||
ElOption,
|
||||
ElPagination,
|
||||
ElSelect,
|
||||
ElTable,
|
||||
ElTableColumn,
|
||||
ElTag
|
||||
} from 'element-plus'
|
||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||
import { BulkPurchaseService } from '@/api/modules'
|
||||
import type { BulkPurchaseItem, BulkPurchasePaymentMethod, BulkPurchaseTask } from '@/types/api'
|
||||
import { BulkPurchaseTaskStatus } from '@/types/api'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { useAsyncTaskPolling } from '@/composables/useAsyncTaskPolling'
|
||||
import { formatDateTime, formatMoney } from '@/utils/business/format'
|
||||
import { BULK_PURCHASE_PERMISSIONS } from '@/config/constants/bulkPurchase'
|
||||
|
||||
defineOptions({ name: 'BulkPurchaseDetail' })
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { hasAuth } = useAuth()
|
||||
const itemStatus = ref<BulkPurchaseTaskStatus | undefined>()
|
||||
const itemsPage = ref(1)
|
||||
const itemsSize = ref(20)
|
||||
const taskId = Number(route.params.id)
|
||||
|
||||
const polling = useAsyncTaskPolling<BulkPurchaseTask>({
|
||||
storageKey: `bulk-purchase-detail:${taskId}`,
|
||||
autoRestore: false,
|
||||
fetchTask: async (id) => {
|
||||
const res = await BulkPurchaseService.getTask(id)
|
||||
if (res.code === 403) {
|
||||
const error = new Error('暂无查看该批量订购任务详情的权限') as Error & { status?: number }
|
||||
error.status = 403
|
||||
throw error
|
||||
}
|
||||
if (res.code !== 0 || !res.data) {
|
||||
throw new Error(res.msg || '获取批量订购任务详情失败')
|
||||
}
|
||||
return res.data
|
||||
},
|
||||
isForbidden: (error: any) => error?.status === 403 || error?.response?.status === 403
|
||||
})
|
||||
const taskDetail = polling.task
|
||||
const loading = polling.loading
|
||||
const forbidden = polling.forbidden
|
||||
|
||||
const loadTaskDetail = async () => {
|
||||
if (!taskId) {
|
||||
ElMessage.error('缺少任务 ID 参数')
|
||||
goBack()
|
||||
return
|
||||
}
|
||||
|
||||
await polling.start(taskId)
|
||||
if (polling.error.value) ElMessage.error(polling.error.value)
|
||||
if (polling.forbidden.value) ElMessage.warning('暂无查看该批量订购任务详情的权限')
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
const refreshTask = () => {
|
||||
void polling.retry()
|
||||
}
|
||||
|
||||
const getStatusName = (status?: BulkPurchaseTaskStatus) => {
|
||||
const names: Record<BulkPurchaseTaskStatus, string> = {
|
||||
[BulkPurchaseTaskStatus.PENDING]: '待处理',
|
||||
[BulkPurchaseTaskStatus.PROCESSING]: '处理中',
|
||||
[BulkPurchaseTaskStatus.COMPLETED]: '已完成',
|
||||
[BulkPurchaseTaskStatus.FAILED]: '已失败',
|
||||
[BulkPurchaseTaskStatus.CANCELED]: '已取消'
|
||||
}
|
||||
return status ? names[status] || '-' : '-'
|
||||
}
|
||||
|
||||
const getStatusType = (status?: BulkPurchaseTaskStatus) => {
|
||||
if (status === BulkPurchaseTaskStatus.COMPLETED) return 'success'
|
||||
if (status === BulkPurchaseTaskStatus.FAILED) return 'danger'
|
||||
if (status === BulkPurchaseTaskStatus.PROCESSING) return 'warning'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
const getPaymentMethodName = (method?: BulkPurchasePaymentMethod) => {
|
||||
return method === 'offline' ? '线下支付' : method === 'wallet' ? '代理钱包' : '-'
|
||||
}
|
||||
|
||||
const getAssetIdentifier = (item: BulkPurchaseItem) =>
|
||||
item.asset_identifier || item.iccid || item.virtual_no || '-'
|
||||
|
||||
const getItemStatusName = (status?: BulkPurchaseTaskStatus) => {
|
||||
if (status === BulkPurchaseTaskStatus.COMPLETED) return '成功'
|
||||
if (status === BulkPurchaseTaskStatus.FAILED) return '失败'
|
||||
return '-'
|
||||
}
|
||||
|
||||
const getItemStatusType = (status?: BulkPurchaseTaskStatus) => {
|
||||
if (status === BulkPurchaseTaskStatus.COMPLETED) return 'success'
|
||||
if (status === BulkPurchaseTaskStatus.FAILED) return 'danger'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
const getItemReason = (item: BulkPurchaseItem) =>
|
||||
item.reason || item.error_summary || item.error_reason || '-'
|
||||
|
||||
const filteredItems = computed(() => {
|
||||
const items = taskDetail.value?.items || []
|
||||
if (itemStatus.value === undefined) return items
|
||||
return items.filter((item) => item.status === itemStatus.value)
|
||||
})
|
||||
|
||||
const paginatedItems = computed(() => {
|
||||
const start = (itemsPage.value - 1) * itemsSize.value
|
||||
return filteredItems.value.slice(start, start + itemsSize.value)
|
||||
})
|
||||
|
||||
const handleItemSizeChange = () => {
|
||||
itemsPage.value = 1
|
||||
}
|
||||
|
||||
watch(itemStatus, () => {
|
||||
itemsPage.value = 1
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
void loadTaskDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.bulk-purchase-detail-page {
|
||||
.detail-header {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
padding-bottom: 16px;
|
||||
|
||||
.detail-title {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.detail-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 220px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.ellipsis-value {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: bottom;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-error {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.items-toolbar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
margin: 20px 0 12px;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,506 @@
|
||||
<template>
|
||||
<ArtTableFullScreen>
|
||||
<div class="bulk-purchase-page" id="table-full-screen">
|
||||
<ElDrawer
|
||||
v-model="createDrawerVisible"
|
||||
title="批量订购套餐"
|
||||
direction="rtl"
|
||||
size="520px"
|
||||
:close-on-click-modal="!createDrawerBusy"
|
||||
:close-on-press-escape="!createDrawerBusy"
|
||||
@closed="handleCreateDrawerClosed"
|
||||
>
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="110px" class="create-form">
|
||||
<ElFormItem label="套餐" prop="package_id">
|
||||
<ElSelect
|
||||
v-model="form.package_id"
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
:remote-method="handlePackageSearch"
|
||||
:loading="packageLoading"
|
||||
placeholder="请输入套餐名称搜索"
|
||||
style="width: 100%"
|
||||
@visible-change="handlePackageVisibleChange"
|
||||
>
|
||||
<ElOption
|
||||
v-for="pkg in packageOptions"
|
||||
:key="pkg.id"
|
||||
:label="pkg.package_name"
|
||||
:value="pkg.id"
|
||||
>
|
||||
<span class="package-option">{{ pkg.package_name }}</span>
|
||||
</ElOption>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="支付方式" prop="payment_method">
|
||||
<ElRadioGroup v-model="form.payment_method" @change="handlePaymentMethodChange">
|
||||
<ElRadio value="wallet">代理钱包</ElRadio>
|
||||
<ElRadio value="offline">线下支付</ElRadio>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="订单文件" prop="orderFile">
|
||||
<VoucherUpload
|
||||
ref="orderUploadRef"
|
||||
v-model="orderFileKeys"
|
||||
voucher-name="订单文件"
|
||||
:max-count="1"
|
||||
purpose="batch_purchase"
|
||||
:max-size-mb="10"
|
||||
single-column-csv
|
||||
:max-csv-rows="1000"
|
||||
accept=".csv"
|
||||
tip="仅支持 UTF-8 单列 CSV,最多 1000 行,最大 10MB"
|
||||
@uploading-change="orderFileUploading = $event"
|
||||
@change="formRef?.validateField('orderFile')"
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem
|
||||
v-if="form.payment_method === 'offline'"
|
||||
label="整批支付凭证"
|
||||
prop="voucherFile"
|
||||
>
|
||||
<VoucherUpload
|
||||
ref="voucherUploadRef"
|
||||
v-model="voucherFileKeys"
|
||||
voucher-name="整批支付凭证"
|
||||
@uploading-change="voucherUploading = $event"
|
||||
@change="formRef?.validateField('voucherFile')"
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
<ElAlert
|
||||
v-if="createDrawerBusy"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
:title="
|
||||
voucherUploading
|
||||
? '支付凭证上传中,请稍候'
|
||||
: orderFileUploading
|
||||
? '订单文件上传中,请稍候'
|
||||
: '批量订购文件上传及任务创建中,请勿重复提交'
|
||||
"
|
||||
/>
|
||||
</ElForm>
|
||||
|
||||
<template #footer>
|
||||
<div class="drawer-footer">
|
||||
<ElButton :disabled="createDrawerBusy" @click="createDrawerVisible = false">
|
||||
取消
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
:loading="createDrawerBusy"
|
||||
:disabled="!hasAuth(BULK_PURCHASE_PERMISSIONS.create)"
|
||||
@click="submitTask"
|
||||
>
|
||||
创建批量订购任务
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDrawer>
|
||||
|
||||
<ElCard
|
||||
v-if="hasAuth(BULK_PURCHASE_PERMISSIONS.detail)"
|
||||
shadow="never"
|
||||
class="art-table-card task-list-card"
|
||||
>
|
||||
<template #header>
|
||||
<div class="task-list-header">
|
||||
<span>批量订购任务</span>
|
||||
<div class="task-list-toolbar">
|
||||
<ElButton
|
||||
v-if="hasAuth(BULK_PURCHASE_PERMISSIONS.template)"
|
||||
tag="a"
|
||||
href="/templates/bulk-purchase-template.csv"
|
||||
download="批量订购套餐模板.csv"
|
||||
>
|
||||
下载模板
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-if="hasAuth(BULK_PURCHASE_PERMISSIONS.create)"
|
||||
type="primary"
|
||||
@click="openCreateDrawer"
|
||||
>
|
||||
批量订购套餐
|
||||
</ElButton>
|
||||
<ElSelect
|
||||
v-model="taskListStatus"
|
||||
clearable
|
||||
placeholder="筛选任务状态"
|
||||
style="width: 150px"
|
||||
@change="handleTaskListStatusChange"
|
||||
>
|
||||
<ElOption label="待处理" :value="BulkPurchaseTaskStatus.PENDING" />
|
||||
<ElOption label="处理中" :value="BulkPurchaseTaskStatus.PROCESSING" />
|
||||
<ElOption label="已完成" :value="BulkPurchaseTaskStatus.COMPLETED" />
|
||||
<ElOption label="已失败" :value="BulkPurchaseTaskStatus.FAILED" />
|
||||
<ElOption label="已取消" :value="BulkPurchaseTaskStatus.CANCELED" />
|
||||
</ElSelect>
|
||||
<ElButton :loading="taskListLoading" @click="loadTaskList">刷新</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<ElTable v-loading="taskListLoading" :data="taskList" border>
|
||||
<ElTableColumn label="任务号" min-width="200" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<ElButton type="primary" link @click="showTask(scope.row)">
|
||||
{{ scope.row.task_no || '-' }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn prop="file_name" label="订单文件" min-width="180" show-overflow-tooltip />
|
||||
<ElTableColumn
|
||||
prop="package_name"
|
||||
label="套餐名称"
|
||||
min-width="180"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<ElTableColumn
|
||||
prop="package_code"
|
||||
label="套餐编码"
|
||||
min-width="180"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<ElTableColumn label="支付方式" width="110">
|
||||
<template #default="scope">{{
|
||||
getPaymentMethodName(scope.row.payment_method)
|
||||
}}</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="状态" width="110">
|
||||
<template #default="scope">
|
||||
<ElTag :type="getStatusType(scope.row.status)">
|
||||
{{ scope.row.status_name || getStatusName(scope.row.status) }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn prop="total_count" label="总数" width="90" />
|
||||
<ElTableColumn prop="success_count" label="成功数" width="90" />
|
||||
<ElTableColumn prop="fail_count" label="失败数" width="90" />
|
||||
<ElTableColumn label="创建时间" width="180">
|
||||
<template #default="scope">{{ formatDateTime(scope.row.created_at) }}</template>
|
||||
</ElTableColumn>
|
||||
</ElTable>
|
||||
|
||||
<div class="pagination-wrapper">
|
||||
<ElPagination
|
||||
v-model:current-page="taskListPage"
|
||||
v-model:page-size="taskListSize"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
:total="taskListTotal"
|
||||
@current-change="loadTaskList"
|
||||
@size-change="handleTaskListSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
</ArtTableFullScreen>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {
|
||||
ElAlert,
|
||||
ElButton,
|
||||
ElCard,
|
||||
ElDrawer,
|
||||
ElForm,
|
||||
ElMessage,
|
||||
ElOption,
|
||||
ElPagination,
|
||||
ElRadio,
|
||||
ElRadioGroup,
|
||||
ElSelect,
|
||||
ElTable,
|
||||
ElTableColumn,
|
||||
ElTag
|
||||
} from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import VoucherUpload from '@/components/business/VoucherUpload.vue'
|
||||
import { BulkPurchaseService, PackageManageService } from '@/api/modules'
|
||||
import type { BulkPurchasePaymentMethod, BulkPurchaseTask, PackageResponse } from '@/types/api'
|
||||
import { BulkPurchaseTaskStatus } from '@/types/api'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { BULK_PURCHASE_PERMISSIONS } from '@/config/constants/bulkPurchase'
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
|
||||
defineOptions({ name: 'BulkPurchase' })
|
||||
|
||||
const router = useRouter()
|
||||
const { hasAuth } = useAuth()
|
||||
const formRef = ref<FormInstance>()
|
||||
const orderUploadRef = ref<InstanceType<typeof VoucherUpload>>()
|
||||
const voucherUploadRef = ref<InstanceType<typeof VoucherUpload>>()
|
||||
const createDrawerVisible = ref(false)
|
||||
const submitting = ref(false)
|
||||
const orderFileKeys = ref<string[]>([])
|
||||
const orderFileUploading = ref(false)
|
||||
const voucherFileKeys = ref<string[]>([])
|
||||
const voucherUploading = ref(false)
|
||||
const packageOptions = ref<PackageResponse[]>([])
|
||||
const packageLoading = ref(false)
|
||||
const taskList = ref<BulkPurchaseTask[]>([])
|
||||
const taskListLoading = ref(false)
|
||||
const taskListPage = ref(1)
|
||||
const taskListSize = ref(20)
|
||||
const taskListTotal = ref(0)
|
||||
const taskListStatus = ref<BulkPurchaseTaskStatus | undefined>()
|
||||
const createDrawerBusy = computed(
|
||||
() => submitting.value || voucherUploading.value || orderFileUploading.value
|
||||
)
|
||||
|
||||
const form = reactive<{
|
||||
package_id?: number
|
||||
payment_method: BulkPurchasePaymentMethod
|
||||
}>({
|
||||
package_id: undefined,
|
||||
payment_method: 'wallet'
|
||||
})
|
||||
|
||||
const rules = computed<FormRules>(() => ({
|
||||
package_id: [{ required: true, message: '请选择套餐', trigger: 'change' }],
|
||||
payment_method: [{ required: true, message: '请选择支付方式', trigger: 'change' }],
|
||||
orderFile: [
|
||||
{
|
||||
validator: (_rule: unknown, _value: unknown, callback: (error?: Error) => void) => {
|
||||
if (orderFileKeys.value.length === 0) callback(new Error('请上传订单文件'))
|
||||
else callback()
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
voucherFile: [
|
||||
{
|
||||
validator: (_rule: unknown, _value: unknown, callback: (error?: Error) => void) => {
|
||||
if (form.payment_method === 'offline' && voucherFileKeys.value.length === 0) {
|
||||
callback(new Error('线下支付必须上传整批支付凭证'))
|
||||
} else callback()
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
}))
|
||||
|
||||
const loadPackages = async (query = '') => {
|
||||
packageLoading.value = true
|
||||
try {
|
||||
const res = await PackageManageService.getPackages({
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
package_name: query.trim() || undefined,
|
||||
status: 1,
|
||||
shelf_status: 1
|
||||
})
|
||||
if (res.code !== 0) {
|
||||
ElMessage.error(res.msg || '获取套餐列表失败')
|
||||
return
|
||||
}
|
||||
|
||||
packageOptions.value = (res.data?.items || []).filter(
|
||||
(item) => item.status === 1 && item.shelf_status === 1
|
||||
)
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.message || '获取套餐列表失败')
|
||||
} finally {
|
||||
packageLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handlePackageSearch = (query: string) => {
|
||||
void loadPackages(query)
|
||||
}
|
||||
|
||||
const handlePackageVisibleChange = (visible: boolean) => {
|
||||
if (visible && packageOptions.value.length === 0) void loadPackages()
|
||||
}
|
||||
|
||||
const openCreateDrawer = () => {
|
||||
createDrawerVisible.value = true
|
||||
if (packageOptions.value.length === 0) void loadPackages()
|
||||
}
|
||||
|
||||
const handleCreateDrawerClosed = () => {
|
||||
formRef.value?.resetFields()
|
||||
form.package_id = undefined
|
||||
form.payment_method = 'wallet'
|
||||
orderFileKeys.value = []
|
||||
voucherFileKeys.value = []
|
||||
orderUploadRef.value?.clearFiles(false)
|
||||
voucherUploadRef.value?.clearFiles(false)
|
||||
orderFileUploading.value = false
|
||||
voucherUploading.value = false
|
||||
}
|
||||
|
||||
const handlePaymentMethodChange = (value: string | number | boolean | undefined) => {
|
||||
if (value === 'wallet') {
|
||||
voucherFileKeys.value = []
|
||||
voucherUploadRef.value?.clearFiles()
|
||||
formRef.value?.clearValidate('voucherFile')
|
||||
}
|
||||
}
|
||||
|
||||
const submitTask = async () => {
|
||||
if (
|
||||
!hasAuth(BULK_PURCHASE_PERMISSIONS.create) ||
|
||||
submitting.value ||
|
||||
voucherUploading.value ||
|
||||
orderFileUploading.value
|
||||
)
|
||||
return
|
||||
const valid = await formRef.value?.validate().catch(() => false)
|
||||
if (!valid || !form.package_id || orderFileKeys.value.length === 0) return
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
const data = {
|
||||
file_key: orderFileKeys.value[0],
|
||||
package_id: form.package_id,
|
||||
payment_method: form.payment_method,
|
||||
...(form.payment_method === 'offline' ? { voucher_keys: voucherFileKeys.value } : {})
|
||||
}
|
||||
|
||||
const res = await BulkPurchaseService.createTask(data)
|
||||
const taskId = res.data?.task_id ?? res.data?.id
|
||||
if (res.code !== 0 || !taskId) {
|
||||
ElMessage.error(res.msg || '创建批量订购任务失败')
|
||||
return
|
||||
}
|
||||
|
||||
ElMessage.success(res.data.message || '批量订购任务已创建')
|
||||
createDrawerVisible.value = false
|
||||
await router.push(`${RoutesAlias.BulkPurchaseDetail}/${taskId}`)
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.message || '创建批量订购任务失败')
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadTaskList = async () => {
|
||||
if (!hasAuth(BULK_PURCHASE_PERMISSIONS.detail)) return
|
||||
taskListLoading.value = true
|
||||
try {
|
||||
const res = await BulkPurchaseService.getTasks({
|
||||
page: taskListPage.value,
|
||||
page_size: taskListSize.value,
|
||||
status: taskListStatus.value
|
||||
})
|
||||
if (res.code !== 0) {
|
||||
ElMessage.error(res.msg || '获取批量订购任务列表失败')
|
||||
return
|
||||
}
|
||||
|
||||
taskList.value = res.data?.items || []
|
||||
taskListTotal.value = res.data?.total || 0
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.message || '获取批量订购任务列表失败')
|
||||
} finally {
|
||||
taskListLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleTaskListStatusChange = () => {
|
||||
taskListPage.value = 1
|
||||
void loadTaskList()
|
||||
}
|
||||
|
||||
const handleTaskListSizeChange = () => {
|
||||
taskListPage.value = 1
|
||||
void loadTaskList()
|
||||
}
|
||||
|
||||
const showTask = (task: BulkPurchaseTask) => {
|
||||
const taskId = task.task_id ?? task.id
|
||||
if (!taskId) return
|
||||
|
||||
void router.push(`${RoutesAlias.BulkPurchaseDetail}/${taskId}`)
|
||||
}
|
||||
|
||||
const getStatusName = (status: BulkPurchaseTaskStatus) => {
|
||||
const names: Record<BulkPurchaseTaskStatus, string> = {
|
||||
[BulkPurchaseTaskStatus.PENDING]: '待处理',
|
||||
[BulkPurchaseTaskStatus.PROCESSING]: '处理中',
|
||||
[BulkPurchaseTaskStatus.COMPLETED]: '已完成',
|
||||
[BulkPurchaseTaskStatus.FAILED]: '已失败',
|
||||
[BulkPurchaseTaskStatus.CANCELED]: '已取消'
|
||||
}
|
||||
return names[status] || '-'
|
||||
}
|
||||
|
||||
const getStatusType = (status: BulkPurchaseTaskStatus) => {
|
||||
if (status === BulkPurchaseTaskStatus.COMPLETED) return 'success'
|
||||
if (status === BulkPurchaseTaskStatus.FAILED) return 'danger'
|
||||
if (status === BulkPurchaseTaskStatus.PROCESSING) return 'warning'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
const getPaymentMethodName = (method?: BulkPurchasePaymentMethod) => {
|
||||
return method === 'offline' ? '线下支付' : method === 'wallet' ? '代理钱包' : '-'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void loadTaskList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.bulk-purchase-page {
|
||||
.task-list-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.task-list-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.package-option {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.create-form {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.drawer-footer {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.task-list-header {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.task-list-toolbar {
|
||||
width: 100%;
|
||||
|
||||
.el-select {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user