Files
one-pipe-system/src/views/asset-management/iot-card-management/index.vue
sexygoat 38cf7d12e9
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m42s
fix: 真流量虚流量权限
2026-05-14 10:52:11 +08:00

2156 lines
66 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<ArtTableFullScreen>
<div class="standalone-card-list-page" id="table-full-screen">
<!-- 搜索栏 -->
<ArtSearchBar
v-model:filter="formFilters"
:items="formItems"
label-width="120"
@reset="handleReset"
@search="handleSearch"
></ArtSearchBar>
<ElCard shadow="never" class="art-table-card">
<!-- 表格头部 -->
<ArtTableHeader
:columnList="columnOptions"
v-model:columns="columnChecks"
@refresh="handleRefresh"
>
<template #left>
<ElButton
type="success"
:disabled="selectedCards.length === 0"
@click="showAllocateDialog"
v-permission="'iot_card:batch_allocation'"
>
批量分配
</ElButton>
<ElButton
type="warning"
:disabled="selectedCards.length === 0"
@click="showRecallDialog"
v-permission="'iot_card:batch_recycle'"
>
批量回收
</ElButton>
<ElButton
type="primary"
@click="showSeriesBindingDialog"
v-permission="'iot_card:batch_setting'"
>
批量设置套餐系列
</ElButton>
</template>
</ArtTableHeader>
<!-- 表格 -->
<ArtTable
ref="tableRef"
row-key="id"
:loading="loading"
:data="cardList"
:currentPage="pagination.page"
:pageSize="pagination.pageSize"
:total="pagination.total"
:marginTop="10"
:actions="getActions"
:actionsWidth="160"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
@selection-change="handleSelectionChange"
>
<template #default>
<ElTableColumn type="selection" width="55" />
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
</template>
</ArtTable>
<!-- 批量分配对话框 -->
<ElDialog
v-model="allocateDialogVisible"
title="批量分配"
width="600px"
@close="handleAllocateDialogClose"
>
<ElForm
ref="allocateFormRef"
:model="allocateForm"
:rules="allocateRules"
label-width="120px"
>
<ElFormItem label="店铺" prop="to_shop_id">
<ElCascader
v-model="allocateForm.to_shop_id"
:options="shopCascadeOptions"
:props="shopCascadeProps"
placeholder="请选择目标店铺"
clearable
filterable
style="width: 100%"
/>
</ElFormItem>
<ElFormItem label="选卡方式" prop="selection_type">
<ElRadioGroup v-model="allocateForm.selection_type">
<ElRadio label="list">ICCID列表</ElRadio>
<ElRadio label="range">号段范围</ElRadio>
<ElRadio label="filter">筛选条件</ElRadio>
</ElRadioGroup>
</ElFormItem>
<ElFormItem
v-if="allocateForm.selection_type === CardSelectionType.LIST"
label="ICCID列表"
>
<div>已选择 {{ selectedCards.length }} 张卡</div>
</ElFormItem>
<ElFormItem
v-if="allocateForm.selection_type === CardSelectionType.RANGE"
label="起始ICCID"
prop="iccid_start"
>
<ElInput v-model="allocateForm.iccid_start" placeholder="请输入起始ICCID" />
</ElFormItem>
<ElFormItem
v-if="allocateForm.selection_type === CardSelectionType.RANGE"
label="结束ICCID"
prop="iccid_end"
>
<ElInput v-model="allocateForm.iccid_end" placeholder="请输入结束ICCID" />
</ElFormItem>
<ElFormItem
v-if="allocateForm.selection_type === CardSelectionType.FILTER"
label="运营商"
>
<ElSelect
v-model="allocateForm.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
v-if="allocateForm.selection_type === CardSelectionType.FILTER"
label="卡状态"
>
<ElSelect
v-model="allocateForm.status"
placeholder="请选择状态"
clearable
style="width: 100%"
>
<ElOption label="在库" :value="1" />
<ElOption label="已分销" :value="2" />
<ElOption label="已激活" :value="3" />
<ElOption label="已停用" :value="4" />
</ElSelect>
</ElFormItem>
<ElFormItem
v-if="
allocateForm.selection_type === CardSelectionType.FILTER && userInfo.user_type !== 3
"
label="批次号"
>
<ElSelect
v-model="allocateForm.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="备注">
<ElInput
v-model="allocateForm.remark"
type="textarea"
:rows="3"
placeholder="请输入备注信息"
/>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="allocateDialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleAllocate" :loading="allocateLoading">
确认分配
</ElButton>
</div>
</template>
</ElDialog>
<!-- 批量回收对话框 -->
<ElDialog
v-model="recallDialogVisible"
title="批量回收"
width="600px"
@close="handleRecallDialogClose"
>
<ElForm ref="recallFormRef" :model="recallForm" :rules="recallRules" label-width="120px">
<ElFormItem label="选卡方式" prop="selection_type">
<ElRadioGroup v-model="recallForm.selection_type">
<ElRadio label="list">ICCID列表</ElRadio>
<ElRadio label="range">号段范围</ElRadio>
<ElRadio label="filter">筛选条件</ElRadio>
</ElRadioGroup>
</ElFormItem>
<ElFormItem
v-if="recallForm.selection_type === CardSelectionType.LIST"
label="ICCID列表"
>
<div>已选择 {{ selectedCards.length }} 张卡</div>
</ElFormItem>
<ElFormItem
v-if="recallForm.selection_type === CardSelectionType.RANGE"
label="起始ICCID"
prop="iccid_start"
>
<ElInput v-model="recallForm.iccid_start" placeholder="请输入起始ICCID" />
</ElFormItem>
<ElFormItem
v-if="recallForm.selection_type === CardSelectionType.RANGE"
label="结束ICCID"
prop="iccid_end"
>
<ElInput v-model="recallForm.iccid_end" placeholder="请输入结束ICCID" />
</ElFormItem>
<ElFormItem
v-if="recallForm.selection_type === CardSelectionType.FILTER"
label="运营商"
>
<ElSelect
v-model="recallForm.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
v-if="
recallForm.selection_type === CardSelectionType.FILTER && userInfo.user_type !== 3
"
label="批次号"
>
<ElSelect
v-model="recallForm.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="备注">
<ElInput
v-model="recallForm.remark"
type="textarea"
:rows="3"
placeholder="请输入备注信息"
/>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="recallDialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleRecall" :loading="recallLoading">
确认回收
</ElButton>
</div>
</template>
</ElDialog>
<!-- 分配结果对话框 -->
<ElDialog v-model="resultDialogVisible" :title="resultTitle" width="700px">
<ElDescriptions :column="2" border>
<ElDescriptionsItem label="操作单号">{{
allocationResult.allocation_no
}}</ElDescriptionsItem>
<ElDescriptionsItem label="待处理总数">{{
allocationResult.total_count
}}</ElDescriptionsItem>
<ElDescriptionsItem label="成功数">
<ElTag type="success">{{ allocationResult.success_count }}</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="失败数">
<ElTag type="danger">{{ allocationResult.fail_count }}</ElTag>
</ElDescriptionsItem>
</ElDescriptions>
<div
v-if="allocationResult.failed_items && allocationResult.failed_items.length > 0"
style="margin-top: 20px"
>
<ElDivider content-position="left">失败项详情</ElDivider>
<ElTable :data="allocationResult.failed_items" border max-height="300">
<ElTableColumn prop="iccid" label="ICCID" width="200" />
<ElTableColumn prop="reason" label="失败原因" />
</ElTable>
</div>
<template #footer>
<div class="dialog-footer">
<ElButton type="primary" @click="resultDialogVisible = false">确定</ElButton>
</div>
</template>
</ElDialog>
<!-- 批量设置套餐系列绑定对话框 -->
<ElDialog
v-model="seriesBindingDialogVisible"
title="批量设置套餐系列绑定"
width="600px"
@close="handleSeriesBindingDialogClose"
>
<ElForm
ref="seriesBindingFormRef"
:model="seriesBindingForm"
:rules="seriesBindingRules"
label-width="100"
>
<ElFormItem label="选卡方式" prop="selection_type">
<ElRadioGroup v-model="seriesBindingForm.selection_type">
<ElRadio label="list">ICCID列表</ElRadio>
<ElRadio label="range">号段范围</ElRadio>
<ElRadio label="filter">筛选条件</ElRadio>
</ElRadioGroup>
</ElFormItem>
<ElFormItem
v-if="seriesBindingForm.selection_type === CardSelectionType.LIST"
label="ICCID列表"
>
<div>已选择 {{ selectedCards.length }} 张卡</div>
</ElFormItem>
<ElFormItem
v-if="seriesBindingForm.selection_type === CardSelectionType.RANGE"
label="起始ICCID"
prop="iccid_start"
>
<ElInput v-model="seriesBindingForm.iccid_start" placeholder="请输入起始ICCID" />
</ElFormItem>
<ElFormItem
v-if="seriesBindingForm.selection_type === CardSelectionType.RANGE"
label="结束ICCID"
prop="iccid_end"
>
<ElInput v-model="seriesBindingForm.iccid_end" placeholder="请输入结束ICCID" />
</ElFormItem>
<ElFormItem
v-if="seriesBindingForm.selection_type === CardSelectionType.FILTER"
label="运营商"
>
<ElSelect
v-model="seriesBindingForm.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
v-if="seriesBindingForm.selection_type === CardSelectionType.FILTER"
label="卡状态"
>
<ElSelect
v-model="seriesBindingForm.status"
placeholder="请选择状态"
clearable
style="width: 100%"
>
<ElOption label="在库" :value="1" />
<ElOption label="已分销" :value="2" />
<ElOption label="已激活" :value="3" />
<ElOption label="已停用" :value="4" />
</ElSelect>
</ElFormItem>
<ElFormItem
v-if="
seriesBindingForm.selection_type === CardSelectionType.FILTER &&
userInfo.user_type !== 3
"
label="批次号"
>
<ElSelect
v-model="seriesBindingForm.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="套餐系列" prop="series_id">
<ElSelect
v-model="seriesBindingForm.series_id"
placeholder="请选择或搜索套餐系列"
style="width: 100%"
filterable
remote
reserve-keyword
:remote-method="searchPackageSeries"
:loading="seriesLoading"
clearable
>
<ElOption
v-for="series in packageSeriesList"
:key="series.id"
:label="series.series_name"
:value="series.id"
:disabled="series.status !== 1"
/>
</ElSelect>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="seriesBindingDialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleSeriesBinding" :loading="seriesBindingLoading">
确认设置
</ElButton>
</div>
</template>
</ElDialog>
<!-- 套餐系列绑定结果对话框 -->
<ElDialog
v-model="seriesBindingResultDialogVisible"
title="设置结果"
width="700px"
@close="handleSeriesBindingResultDialogClose"
>
<ElDescriptions :column="2" border>
<ElDescriptionsItem label="成功数">
<ElTag type="success">{{ seriesBindingResult.success_count }}</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="失败数">
<ElTag type="danger">{{ seriesBindingResult.fail_count }}</ElTag>
</ElDescriptionsItem>
</ElDescriptions>
<div
v-if="seriesBindingResult.failed_items && seriesBindingResult.failed_items.length > 0"
style="margin-top: 20px"
>
<ElDivider content-position="left">失败项详情</ElDivider>
<ElTable :data="seriesBindingResult.failed_items" border max-height="300">
<ElTableColumn prop="iccid" label="ICCID" width="200" />
<ElTableColumn prop="reason" label="失败原因" />
</ElTable>
</div>
<template #footer>
<div class="dialog-footer">
<ElButton type="primary" @click="seriesBindingResultDialogVisible = false"
>确定</ElButton
>
</div>
</template>
</ElDialog>
<!-- 卡详情对话框 -->
<ElDialog v-model="cardDetailDialogVisible" title="卡片详情" width="900px">
<div v-if="cardDetailLoading" style="padding: 40px; text-align: center">
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
<div style="margin-top: 16px">加载中...</div>
</div>
<ElDescriptions v-else-if="currentCardDetail" :column="3" border>
<ElDescriptionsItem label="卡ID">{{ currentCardDetail.id }}</ElDescriptionsItem>
<ElDescriptionsItem label="ICCID" :span="2">{{
currentCardDetail.iccid
}}</ElDescriptionsItem>
<ElDescriptionsItem label="MSISDN">{{
currentCardDetail.msisdn || '--'
}}</ElDescriptionsItem>
<ElDescriptionsItem label="运营商">{{
currentCardDetail.carrier_name || '--'
}}</ElDescriptionsItem>
<ElDescriptionsItem label="运营商类型">{{
getCarrierTypeText(currentCardDetail.carrier_type)
}}</ElDescriptionsItem>
<ElDescriptionsItem label="卡业务类型">{{
getCardCategoryText(currentCardDetail.card_category)
}}</ElDescriptionsItem>
<ElDescriptionsItem label="状态">
<ElTag :type="getCardDetailStatusTagType(currentCardDetail.status)">
{{ getCardDetailStatusText(currentCardDetail.status) }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="套餐系列">{{
currentCardDetail.series_name || '--'
}}</ElDescriptionsItem>
<ElDescriptionsItem label="激活状态">
<ElTag :type="currentCardDetail.activation_status === 1 ? 'success' : 'info'">
{{ currentCardDetail.activation_status === 1 ? '已激活' : '未激活' }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="实名状态">
<ElTag :type="currentCardDetail.real_name_status === 1 ? 'success' : 'warning'">
{{ currentCardDetail.real_name_status === 1 ? '已实名' : '未实名' }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="网络状态">
<ElTag :type="currentCardDetail.network_status === 1 ? 'success' : 'danger'">
{{ currentCardDetail.network_status === 1 ? '正常' : '停机' }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="累计流量使用"
>{{ currentCardDetail.data_usage_mb }} MB</ElDescriptionsItem
>
<ElDescriptionsItem label="创建时间">{{
formatDateTime(currentCardDetail.created_at)
}}</ElDescriptionsItem>
<ElDescriptionsItem label="更新时间" :span="2">{{
formatDateTime(currentCardDetail.updated_at)
}}</ElDescriptionsItem>
</ElDescriptions>
<ElEmpty v-else description="未找到卡片信息" />
<template #footer>
<div class="dialog-footer">
<ElButton type="primary" @click="cardDetailDialogVisible = false">关闭</ElButton>
</div>
</template>
</ElDialog>
<!-- 实名认证策略对话框 -->
<RealnamePolicyDialog
v-model="realnamePolicyDialogVisible"
:asset-identifier="currentRealnamePolicyIccid"
:current-policy="currentRealnamePolicy"
@confirm="handleConfirmRealnamePolicy"
/>
<!-- 更新实名状态对话框 -->
<UpdateRealnameStatusDialog
v-model="updateRealnameStatusDialogVisible"
:asset-identifier="currentUpdateRealnameStatusIccid"
:current-realname-status="currentUpdateRealnameStatus"
@success="handleUpdateRealnameStatusSuccess"
/>
<!-- 操作审计日志弹窗 -->
<OperationLogsDialog
v-model="operationLogsDialogVisible"
:identifier="operationLogsIdentifier"
download-permission="iot_card:download_log_file"
/>
</ElCard>
</div>
</ArtTableFullScreen>
</template>
<script setup lang="ts">
import { h } from 'vue'
import { useRouter } from 'vue-router'
import { RoutesAlias } from '@/router/routesAlias'
import {
CardService,
ShopService,
PackageSeriesService,
AssetService,
CarrierService
} from '@/api/modules'
import { ElMessage, ElTag, ElIcon, ElMessageBox } from 'element-plus'
import { Loading } from '@element-plus/icons-vue'
import RealnamePolicyDialog from '@/components/device/RealnamePolicyDialog.vue'
import UpdateRealnameStatusDialog from '@/components/business/UpdateRealnameStatusDialog.vue'
import type { FormInstance, FormRules } from 'element-plus'
import type { SearchFormItem } from '@/types'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { useAuth } from '@/composables/useAuth'
import { useUserStore } from '@/store/modules/user'
import { storeToRefs } from 'pinia'
import { formatDateTime } from '@/utils/business/format'
import type {
StandaloneIotCard,
StandaloneCardStatus,
AllocateStandaloneCardsRequest,
RecallStandaloneCardsRequest,
AllocateStandaloneCardsResponse,
BatchSetCardSeriesBindingRequest,
BatchSetCardSeriesBindingResponse
} from '@/types/api/card'
import { CardSelectionType } from '@/types/api/card'
import type { PackageSeriesResponse } from '@/types/api'
import OperationLogsDialog from '@/components/business/OperationLogsDialog.vue'
defineOptions({ name: 'StandaloneCardList' })
const { hasAuth } = useAuth()
const userStore = useUserStore()
const { info: userInfo } = storeToRefs(userStore)
const router = useRouter()
const loading = ref(false)
const allocateDialogVisible = ref(false)
const allocateLoading = ref(false)
const recallDialogVisible = ref(false)
const recallLoading = ref(false)
const resultDialogVisible = ref(false)
const resultTitle = ref('')
const tableRef = ref()
const allocateFormRef = ref<FormInstance>()
const recallFormRef = ref<FormInstance>()
const selectedCards = ref<StandaloneIotCard[]>([])
const allocationResult = ref<AllocateStandaloneCardsResponse>({
allocation_no: '',
total_count: 0,
success_count: 0,
fail_count: 0,
failed_items: null
})
// 操作审计日志弹窗
const operationLogsDialogVisible = ref(false)
const operationLogsIdentifier = ref('')
// 套餐系列绑定相关
const seriesBindingDialogVisible = ref(false)
const seriesBindingLoading = ref(false)
const seriesBindingResultDialogVisible = ref(false)
const seriesBindingFormRef = ref<FormInstance>()
const seriesLoading = ref(false)
const packageSeriesList = ref<PackageSeriesResponse[]>([])
const createInitialSeriesBindingState = () => ({
selection_type: CardSelectionType.LIST,
series_id: undefined as number | undefined,
iccid_start: '',
iccid_end: '',
carrier_id: undefined as number | undefined,
status: undefined as StandaloneCardStatus | undefined,
batch_no: ''
})
const seriesBindingForm = reactive(createInitialSeriesBindingState())
const seriesBindingRules = reactive<FormRules>({
selection_type: [{ required: true, message: '请选择选卡方式', trigger: 'change' }],
series_id: [{ required: true, message: '请选择套餐系列', trigger: 'change' }],
iccid_start: [
{
required: true,
validator: (_rule, value, callback) => {
if (seriesBindingForm.selection_type === CardSelectionType.RANGE && !value) {
callback(new Error('请输入起始ICCID'))
} else {
callback()
}
},
trigger: 'blur'
}
],
iccid_end: [
{
required: true,
validator: (_rule, value, callback) => {
if (seriesBindingForm.selection_type === CardSelectionType.RANGE && !value) {
callback(new Error('请输入结束ICCID'))
} else {
callback()
}
},
trigger: 'blur'
}
]
})
const seriesBindingResult = ref<BatchSetCardSeriesBindingResponse>({
success_count: 0,
fail_count: 0,
failed_items: null
})
// 运营商搜索相关
const carrierLoading = ref(false)
const searchCarrierOptions = ref<any[]>([])
// 批量分配对话框运营商选项
const allocateCarrierOptions = ref<any[]>([])
const allocateCarrierLoading = ref(false)
// 加载搜索栏运营商选项默认加载10条
const loadSearchCarrierOptions = async (carrierName?: string) => {
try {
const params: any = {
page: 1,
page_size: 10
}
if (carrierName) {
params.carrier_name = carrierName
}
const res = await CarrierService.getCarriers(params)
if (res.code === 0) {
searchCarrierOptions.value = res.data.items
}
} catch (error) {
console.error('加载运营商选项失败:', error)
}
}
// 加载批量分配对话框运营商选项
const loadAllocateCarrierOptions = async (carrierName?: string) => {
try {
allocateCarrierLoading.value = true
const params: any = {
page: 1,
page_size: 20
}
if (carrierName) {
params.carrier_name = carrierName
}
const res = await CarrierService.getCarriers(params)
if (res.code === 0) {
allocateCarrierOptions.value = res.data.items || []
}
} catch (error) {
console.error('加载运营商选项失败:', error)
} finally {
allocateCarrierLoading.value = false
}
}
// 搜索批量分配对话框运营商
const handleSearchAllocateCarrier = (query: string) => {
if (query) {
loadAllocateCarrierOptions(query)
} else {
loadAllocateCarrierOptions()
}
}
// 搜索运营商(用于搜索栏)
const handleSearchCarrier = (query: string) => {
if (query) {
loadSearchCarrierOptions(query)
} else {
loadSearchCarrierOptions()
}
}
// 批量分配对话框批次号选项
const allocateBatchNoOptions = ref<any[]>([])
// 加载批量分配对话框批次号选项
const loadAllocateBatchNoOptions = async (batchNo?: string) => {
try {
const params: any = {
page: 1,
page_size: 20
}
if (batchNo) {
params.batch_no = batchNo
}
const res = await CardService.getIotCardImportTasks(params)
if (res.code === 0) {
allocateBatchNoOptions.value = res.data.items || []
}
} catch (error) {
console.error('加载批次号选项失败:', error)
}
}
// 搜索批量分配对话框批次号
const handleSearchAllocateBatchNo = (query: string) => {
if (query) {
loadAllocateBatchNoOptions(query)
} else {
loadAllocateBatchNoOptions()
}
}
// 搜索栏远程选项
const shopOptions = ref<any[]>([])
const searchSeriesOptions = ref<any[]>([])
// 搜索店铺(用于搜索栏)
const handleSearchShops = async (query: string) => {
try {
const params: any = {
page: 1,
page_size: 20
}
if (query) {
params.shop_name = query
}
const res = await ShopService.getShops(params)
if (res.code === 0) {
shopOptions.value = res.data.items || []
}
} catch (error) {
console.error('搜索店铺失败:', error)
}
}
// 搜索套餐系列(用于搜索栏)
const handleSearchSeries = async (query: string) => {
try {
const params: any = {
page: 1,
page_size: 20,
status: 1
}
if (query) {
params.series_name = query
}
const res = await PackageSeriesService.getPackageSeries(params)
if (res.code === 0) {
searchSeriesOptions.value = res.data.items || []
}
} catch (error) {
console.error('搜索套餐系列失败:', error)
}
}
// 卡详情弹窗相关
const cardDetailDialogVisible = ref(false)
const cardDetailLoading = ref(false)
const currentCardDetail = ref<any>(null)
// IoT卡操作相关对话框
// 实名认证策略对话框
const realnamePolicyDialogVisible = ref(false)
const currentRealnamePolicyIccid = ref('')
const currentRealnamePolicy = ref('none')
// 更新实名状态对话框
const updateRealnameStatusDialogVisible = ref(false)
const currentUpdateRealnameStatusIccid = ref('')
const currentUpdateRealnameStatus = ref<number>(0)
// 显示实名认证策略对话框
const handleShowRealnamePolicy = async (iccid: string, currentPolicy?: string) => {
currentRealnamePolicyIccid.value = iccid
if (currentPolicy !== undefined && currentPolicy !== null) {
currentRealnamePolicy.value = String(currentPolicy)
} else {
try {
const res = await AssetService.resolveAsset(iccid)
if (res.code === 0 && res.data) {
currentRealnamePolicy.value = res.data.realname_policy ?? 'none'
} else {
currentRealnamePolicy.value = 'none'
}
} catch {
currentRealnamePolicy.value = 'none'
}
}
realnamePolicyDialogVisible.value = true
}
// 确认设置实名认证策略
const handleConfirmRealnamePolicy = async (data: { realname_policy: string }) => {
if (!currentRealnamePolicyIccid.value) {
ElMessage.warning('未指定卡片')
return
}
try {
const res = await AssetService.updateRealnamePolicy(
currentRealnamePolicyIccid.value,
data.realname_policy
)
if (res.code === 0) {
ElMessage.success('设置实名认证策略成功')
realnamePolicyDialogVisible.value = false
await getTableData()
} else {
ElMessage.error(res.msg || '设置失败')
}
} catch (error) {
console.error('设置实名认证策略失败:', error)
}
}
// 显示更新实名状态对话框
const handleShowUpdateRealnameStatus = (iccid: string, realnameStatus: number) => {
currentUpdateRealnameStatusIccid.value = iccid
currentUpdateRealnameStatus.value = realnameStatus
updateRealnameStatusDialogVisible.value = true
}
// 更新实名状态成功
const handleUpdateRealnameStatusSuccess = async () => {
await getTableData()
}
// 店铺相关
const shopCascadeOptions = ref<any[]>([])
const shopCascadeProps = {
lazy: true,
checkStrictly: true,
lazyLoad: async (node: any, resolve: any) => {
const { level, value } = node
const parentId = level === 0 ? undefined : value
try {
const res = await ShopService.getShopsCascade({ parent_id: parentId })
if (res.code === 0) {
const nodes = (res.data || []).map((item: any) => ({
value: item.id,
label: item.shop_name,
leaf: !item.has_children
}))
resolve(nodes)
} else {
resolve([])
}
} catch (error) {
console.error('加载店铺级联数据失败:', error)
resolve([])
}
},
value: 'value',
label: 'label',
children: 'children'
}
// 搜索表单初始值
const initialSearchState: {
status: undefined | number
iccid: string
carrier_name: string
msisdn: string
virtual_no: string
device_virtual_no: string
shop_id: undefined | number
series_id: undefined | number
is_distributed: undefined | number
is_standalone: undefined | boolean
[key: string]: any
} = {
status: undefined,
iccid: '',
carrier_name: '',
msisdn: '',
virtual_no: '',
device_virtual_no: '',
shop_id: undefined,
series_id: undefined,
is_distributed: undefined,
is_standalone: undefined,
is_replaced: undefined,
iccid_start: '',
iccid_end: ''
}
// 搜索表单
const formFilters = reactive({ ...initialSearchState })
// 批量分配表单
const allocateForm = reactive<Partial<AllocateStandaloneCardsRequest>>({
selection_type: CardSelectionType.LIST,
to_shop_id: undefined,
iccids: [],
iccid_start: '',
iccid_end: '',
carrier_id: undefined,
status: undefined,
batch_no: '',
remark: ''
})
// 批量分配表单验证规则
const allocateRules = reactive<FormRules>({
to_shop_id: [{ required: true, message: '请选择目标店铺', trigger: 'change' }],
selection_type: [{ required: true, message: '请选择选卡方式', trigger: 'change' }],
iccid_start: [
{
required: true,
validator: (_rule, value, callback) => {
if (allocateForm.selection_type === CardSelectionType.RANGE && !value) {
callback(new Error('请输入起始ICCID'))
} else {
callback()
}
},
trigger: 'blur'
}
],
iccid_end: [
{
required: true,
validator: (_rule, value, callback) => {
if (allocateForm.selection_type === CardSelectionType.RANGE && !value) {
callback(new Error('请输入结束ICCID'))
} else {
callback()
}
},
trigger: 'blur'
}
]
})
// 批量回收表单
const recallForm = reactive<Partial<RecallStandaloneCardsRequest>>({
selection_type: CardSelectionType.LIST,
iccids: [],
iccid_start: '',
iccid_end: '',
carrier_id: undefined,
batch_no: '',
remark: ''
})
// 批量回收表单验证规则
const recallRules = reactive<FormRules>({
selection_type: [{ required: true, message: '请选择选卡方式', trigger: 'change' }],
iccid_start: [
{
required: true,
validator: (_rule, value, callback) => {
if (recallForm.selection_type === CardSelectionType.RANGE && !value) {
callback(new Error('请输入起始ICCID'))
} else {
callback()
}
},
trigger: 'blur'
}
],
iccid_end: [
{
required: true,
validator: (_rule, value, callback) => {
if (recallForm.selection_type === CardSelectionType.RANGE && !value) {
callback(new Error('请输入结束ICCID'))
} else {
callback()
}
},
trigger: 'blur'
}
]
})
// 分页
const pagination = reactive({
page: 1,
pageSize: 20,
total: 0
})
// 搜索表单配置
const baseFormItems: SearchFormItem[] = [
{
label: 'ICCID',
prop: 'iccid',
type: 'input',
config: {
clearable: true,
placeholder: '请输入ICCID'
}
},
{
label: '运营商名称',
prop: 'carrier_name',
type: 'select',
config: {
clearable: true,
filterable: true,
remote: true,
remoteMethod: handleSearchCarrier,
loading: carrierLoading.value,
placeholder: '请选择或搜索运营商名称'
},
options: () =>
searchCarrierOptions.value.map((c) => ({
label: c.carrier_name,
value: c.carrier_name
}))
},
{
label: '状态',
prop: 'status',
type: 'select',
config: {
clearable: true,
placeholder: '全部'
},
options: () => [
{ label: '在库', value: 1 },
{ label: '已分销', value: 2 },
{ label: '已激活', value: 3 },
{ label: '已停用', value: 4 }
]
},
{
label: '卡虚拟号',
prop: 'virtual_no',
type: 'input',
config: {
clearable: true,
placeholder: '请输入卡虚拟号'
}
},
{
label: 'MSISDN',
prop: 'msisdn',
type: 'input',
config: {
clearable: true,
placeholder: '请输入MSISDN'
}
},
{
label: '绑定设备虚拟号',
prop: 'device_virtual_no',
type: 'input',
config: {
clearable: true,
placeholder: '请输入绑定设备虚拟号'
}
},
{
label: '店铺名称',
prop: 'shop_id',
type: 'select',
config: {
clearable: true,
filterable: true,
remote: true,
remoteMethod: handleSearchShops,
loading: false,
placeholder: '请选择或搜索店铺名称'
},
options: () =>
shopOptions.value.map((shop) => ({
label: shop.shop_name,
value: shop.id
}))
},
{
label: '套餐系列',
prop: 'series_id',
type: 'select',
config: {
clearable: true,
filterable: true,
remote: true,
remoteMethod: handleSearchSeries,
placeholder: '请选择或搜索套餐系列'
},
options: () =>
searchSeriesOptions.value.map((s) => ({
label: s.series_name,
value: s.id
}))
},
{
label: '是否已分销',
prop: 'is_distributed',
type: 'select',
config: {
clearable: true,
placeholder: '全部'
},
options: () => [
{ label: '是', value: true },
{ label: '否', value: false }
]
},
{
label: '是否绑定设备',
prop: 'is_standalone',
type: 'select',
config: {
clearable: true,
placeholder: '全部'
},
options: () => [
{ label: '未绑定设备', value: true },
{ label: '已绑定设备', value: false }
]
},
{
label: '是否有换卡记录',
prop: 'is_replaced',
type: 'select',
config: {
clearable: true,
placeholder: '全部'
},
options: () => [
{ label: '有换卡记录', value: true },
{ label: '无换卡记录', value: false }
]
},
{
label: 'ICCID起始号',
prop: 'iccid_start',
type: 'input',
config: {
clearable: true,
placeholder: '请输入ICCID起始号'
}
},
{
label: 'ICCID结束号',
prop: 'iccid_end',
type: 'input',
config: {
clearable: true,
placeholder: '请输入ICCID结束号'
}
}
]
// 搜索表单配置
const formItems: SearchFormItem[] = baseFormItems
// 列配置
const columnOptions = [
{ label: 'ICCID', prop: 'iccid' },
{ label: 'MSISDN', prop: 'msisdn' },
{ label: '卡虚拟号', prop: 'virtual_no' },
{ label: '运营商', prop: 'carrier_name' },
{ label: '卡业务类型', prop: 'card_category' },
{ label: '状态', prop: 'status' },
{ label: '激活状态', prop: 'activation_status' },
{ label: '激活时间', prop: 'activated_at' },
{ label: '网络状态', prop: 'network_status' },
{ label: '实名状态', prop: 'real_name_status' },
{ label: '实名认证策略', prop: 'realname_policy' },
{ label: '绑定设备虚拟号', prop: 'device_virtual_no' },
{ label: '店铺名称', prop: 'shop_name' },
{ label: '套餐系列', prop: 'series_name' },
{ label: '自然月累计流量(MB)', prop: 'current_month_usage_mb' },
{ label: '本月开始日期', prop: 'current_month_start_date' },
{ label: '上月流量总量(MB)', prop: 'last_month_total_mb' },
{ label: '累计流量(MB)', prop: 'data_usage_mb' },
{ label: '最后流量检查时间', prop: 'last_data_check_at' },
{ label: '最后实名检查时间', prop: 'last_real_name_check_at' },
{ label: '启用轮询', prop: 'enable_polling' },
{ label: '批次号', prop: 'batch_no' },
{ label: '创建时间', prop: 'created_at' },
{ label: '更新时间', prop: 'updated_at' }
]
const cardList = ref<StandaloneIotCard[]>([])
// 获取状态标签类型
const getStatusType = (status: StandaloneCardStatus) => {
switch (status) {
case 1:
return 'info'
case 2:
return 'warning'
case 3:
return 'success'
case 4:
return 'danger'
default:
return 'info'
}
}
// 获取状态文本
const getStatusText = (status: StandaloneCardStatus) => {
switch (status) {
case 1:
return '在库'
case 2:
return '已分销'
case 3:
return '已激活'
case 4:
return '已停用'
default:
return '未知'
}
}
// 跳转到资产信息页面(原IoT卡详情)
const goToCardDetail = (iccid: string) => {
if (hasAuth('iot_card:view_detail')) {
router.push({
path: RoutesAlias.AssetInformation,
query: {
iccid: iccid
}
})
} else {
ElMessage.warning('您没有查看详情的权限')
}
}
// 卡详情辅助函数
const getCarrierTypeText = (type: string) => {
const typeMap: Record<string, string> = {
CMCC: '中国移动',
CUCC: '中国联通',
CTCC: '中国电信',
CBN: '中国广电'
}
return typeMap[type] || type
}
const getCardCategoryText = (category: string) => {
const categoryMap: Record<string, string> = {
normal: '普通卡',
industry: '行业卡'
}
return categoryMap[category] || category
}
const getCardDetailStatusText = (status: number) => {
const statusMap: Record<number, string> = {
1: '在库',
2: '已分销',
3: '已激活',
4: '已停用'
}
return statusMap[status] || '未知'
}
const getCardDetailStatusTagType = (status: number) => {
const typeMap: Record<number, any> = {
1: 'info',
2: 'warning',
3: 'success',
4: 'danger'
}
return typeMap[status] || 'info'
}
// 动态列配置
const { columnChecks, columns } = useCheckedColumns(() => [
{
prop: 'iccid',
label: 'ICCID',
minWidth: 200,
formatter: (row: StandaloneIotCard) => {
return h(
'span',
{
style: 'color: var(--el-color-primary); cursor: pointer; text-decoration: underline;',
onClick: (e: MouseEvent) => {
e.stopPropagation()
goToCardDetail(row.iccid)
}
},
row.iccid
)
}
},
{
prop: 'msisdn',
label: 'MSISDN',
width: 160
},
{
prop: 'virtual_no',
label: '卡虚拟号',
width: 180,
showOverflowTooltip: true,
formatter: (row: StandaloneIotCard) => row.virtual_no || '-'
},
{
prop: 'device_virtual_no',
label: '绑定设备虚拟号',
width: 180,
showOverflowTooltip: true,
formatter: (row: StandaloneIotCard) => row.device_virtual_no || '-'
},
{
prop: 'card_category',
label: '卡业务类型',
width: 100,
formatter: (row: StandaloneIotCard) => getCardCategoryText(row.card_category)
},
{
prop: 'carrier_name',
label: '运营商',
width: 150,
showOverflowTooltip: true
},
{
prop: 'shop_name',
label: '店铺名称',
minWidth: 150,
showOverflowTooltip: true,
formatter: (row: StandaloneIotCard) => row.shop_name || '-'
},
{
prop: 'series_name',
label: '套餐系列',
width: 180,
showOverflowTooltip: true,
formatter: (row: StandaloneIotCard) => row.series_name || '-'
},
{
prop: 'status',
label: '状态',
width: 100,
formatter: (row: StandaloneIotCard) => {
return h(ElTag, { type: getStatusType(row.status) }, () => getStatusText(row.status))
}
},
{
prop: 'activation_status',
label: '激活状态',
width: 100,
formatter: (row: StandaloneIotCard) => {
const type = row.activation_status === 1 ? 'success' : 'info'
const text = row.activation_status === 1 ? '已激活' : '未激活'
return h(ElTag, { type }, () => text)
}
},
{
prop: 'network_status',
label: '网络状态',
width: 100,
formatter: (row: StandaloneIotCard) => {
const type = row.network_status === 1 ? 'success' : 'danger'
const text = row.network_status === 1 ? '正常' : '停机'
return h(ElTag, { type }, () => text)
}
},
{
prop: 'real_name_status',
label: '实名状态',
width: 100,
formatter: (row: StandaloneIotCard) => {
const type = row.real_name_status === 1 ? 'success' : 'warning'
const text = row.real_name_status === 1 ? '已实名' : '未实名'
return h(ElTag, { type }, () => text)
}
},
{
prop: 'realname_policy',
label: '实名认证策略',
width: 150,
formatter: (row: StandaloneIotCard) => {
const policy = row.realname_policy || ''
const policyMap: Record<string, string> = {
none: '无需实名',
before_order: '先实名后充值/购买',
after_order: '先充值/购买后实名'
}
const text = policyMap[policy] || (policy ? policy : '未知')
return text
}
},
{
prop: 'activated_at',
label: '激活时间',
width: 180,
formatter: (row: StandaloneIotCard) =>
row.activated_at ? formatDateTime(row.activated_at) : '-'
},
{
prop: 'current_month_usage_mb',
label: '自然月累计流量(MB)',
width: 180,
formatter: (row: StandaloneIotCard) => row.current_month_usage_mb?.toLocaleString() || '0'
},
{
prop: 'current_month_start_date',
label: '本月开始日期',
width: 200,
formatter: (row: StandaloneIotCard) =>
row.current_month_start_date ? formatDateTime(row.current_month_start_date) : '-'
},
{
prop: 'last_month_total_mb',
label: '上月流量总量(MB)',
width: 150,
formatter: (row: StandaloneIotCard) => row.last_month_total_mb?.toLocaleString() || '0'
},
{
prop: 'data_usage_mb',
label: '累计流量(MB)',
width: 120,
formatter: (row: StandaloneIotCard) => row.data_usage_mb?.toLocaleString() || '0'
},
{
prop: 'last_data_check_at',
label: '最后流量检查时间',
width: 180,
formatter: (row: StandaloneIotCard) =>
row.last_data_check_at ? formatDateTime(row.last_data_check_at) : '-'
},
{
prop: 'last_real_name_check_at',
label: '最后实名检查时间',
width: 180,
formatter: (row: StandaloneIotCard) =>
row.last_real_name_check_at ? formatDateTime(row.last_real_name_check_at) : '-'
},
{
prop: 'enable_polling',
label: '启用轮询',
width: 100,
formatter: (row: StandaloneIotCard) => (row.enable_polling ? '是' : '否')
},
{
prop: 'batch_no',
label: '批次号',
width: 200,
showOverflowTooltip: true
},
{
prop: 'created_at',
label: '创建时间',
width: 180,
formatter: (row: StandaloneIotCard) => formatDateTime(row.created_at)
}
])
// 操作列配置
const getActions = (row: StandaloneIotCard) => {
const actions: any[] = []
if (row.network_status === 1) {
if (hasAuth('iot_card:stop_card')) {
actions.push({
label: '停用此卡',
handler: () => handleCardOperation('stop-card', row.iccid),
type: 'primary'
})
}
} else {
if (hasAuth('iot_card:start_card')) {
actions.push({
label: '启用此卡',
handler: () => handleCardOperation('start-card', row.iccid),
type: 'primary'
})
}
}
if (hasAuth('iot_card:realname_policy')) {
actions.push({
label: '实名认证策略',
handler: () => handleCardOperation('realname-policy', row.iccid, row),
type: 'primary'
})
}
if (hasAuth('iot_card:update_realname_status')) {
actions.push({
label: '更新实名状态',
handler: () => handleCardOperation('update-realname-status', row.iccid, row),
type: 'primary'
})
}
if (hasAuth('iot_card:clear_series')) {
actions.push({
label: '清除关联',
handler: () => handleCardOperation('clear-series', row.iccid),
type: 'primary'
})
}
if (hasAuth('iot_card:operation_logs')) {
actions.push({
label: '操作审计日志',
handler: () => {
operationLogsIdentifier.value = row.iccid
operationLogsDialogVisible.value = true
},
type: 'primary'
})
}
return actions
}
onMounted(() => {
getTableData()
})
// 获取单卡列表
const getTableData = async () => {
loading.value = true
try {
const params: Record<string, any> = {
page: pagination.page,
page_size: pagination.pageSize,
...formFilters
}
Object.keys(params).forEach((key) => {
if (params[key] === '' || params[key] === undefined) {
delete params[key]
}
})
const res = await CardService.getStandaloneIotCards(params)
if (res.code === 0) {
cardList.value = res.data.items || []
pagination.total = res.data.total || 0
}
} catch (error) {
console.error(error)
} finally {
loading.value = false
}
}
// 重置搜索
const handleReset = () => {
Object.assign(formFilters, { ...initialSearchState })
pagination.page = 1
getTableData()
}
// 搜索
const handleSearch = () => {
pagination.page = 1
getTableData()
}
// 刷新表格
const handleRefresh = () => {
getTableData()
}
// 处理表格分页变化
const handleSizeChange = (newPageSize: number) => {
pagination.pageSize = newPageSize
getTableData()
}
const handleCurrentChange = (newCurrentPage: number) => {
pagination.page = newCurrentPage
getTableData()
}
// 表格选择变化
const handleSelectionChange = (selection: StandaloneIotCard[]) => {
selectedCards.value = selection
}
// 显示批量分配对话框
const showAllocateDialog = () => {
if (selectedCards.value.length === 0) {
ElMessage.warning('请先选择要分配的卡')
return
}
allocateDialogVisible.value = true
Object.assign(allocateForm, {
selection_type: CardSelectionType.LIST,
to_shop_id: undefined,
iccids: selectedCards.value.map((card) => card.iccid),
iccid_start: '',
iccid_end: '',
carrier_id: undefined,
status: undefined,
batch_no: '',
remark: ''
})
loadAllocateCarrierOptions()
loadAllocateBatchNoOptions()
if (allocateFormRef.value) {
allocateFormRef.value.resetFields()
}
}
// 显示批量回收对话框
const showRecallDialog = () => {
if (selectedCards.value.length === 0) {
ElMessage.warning('请先选择要回收的卡')
return
}
recallDialogVisible.value = true
Object.assign(recallForm, {
selection_type: CardSelectionType.LIST,
iccids: selectedCards.value.map((card) => card.iccid),
iccid_start: '',
iccid_end: '',
carrier_id: undefined,
batch_no: '',
remark: ''
})
if (recallFormRef.value) {
recallFormRef.value.resetFields()
}
loadAllocateCarrierOptions()
loadAllocateBatchNoOptions()
}
// 关闭批量分配对话框
const handleAllocateDialogClose = () => {
if (allocateFormRef.value) {
allocateFormRef.value.resetFields()
}
}
// 关闭批量回收对话框
const handleRecallDialogClose = () => {
if (recallFormRef.value) {
recallFormRef.value.resetFields()
}
}
// 执行批量分配
const handleAllocate = async () => {
if (!allocateFormRef.value) return
await allocateFormRef.value.validate(async (valid) => {
if (valid) {
// 根据选卡方式构建请求参数
const params: Partial<AllocateStandaloneCardsRequest> = {
selection_type: allocateForm.selection_type!,
to_shop_id: Array.isArray(allocateForm.to_shop_id)
? allocateForm.to_shop_id[allocateForm.to_shop_id.length - 1]
: allocateForm.to_shop_id,
remark: allocateForm.remark
}
if (allocateForm.selection_type === CardSelectionType.LIST) {
params.iccids = selectedCards.value.map((card) => card.iccid)
if (params.iccids.length === 0) {
ElMessage.warning('请先选择要分配的卡')
return
}
} else if (allocateForm.selection_type === CardSelectionType.RANGE) {
params.iccid_start = allocateForm.iccid_start
params.iccid_end = allocateForm.iccid_end
} else if (allocateForm.selection_type === CardSelectionType.FILTER) {
if (allocateForm.carrier_id) params.carrier_id = allocateForm.carrier_id
if (allocateForm.status) params.status = allocateForm.status
if (allocateForm.batch_no) params.batch_no = allocateForm.batch_no
}
allocateLoading.value = true
try {
const res = await CardService.allocateStandaloneCards(params)
// code === 0 表示操作成功(接口调用成功),显示结果对话框
if (res.code === 0) {
allocationResult.value = res.data
resultTitle.value = '批量分配结果'
allocateDialogVisible.value = false
resultDialogVisible.value = true
// 清空选择
selectedCards.value = []
// 刷新列表
await getTableData()
}
} catch (error) {
console.error(error)
} finally {
allocateLoading.value = false
}
}
})
}
// 执行批量回收
const handleRecall = async () => {
if (!recallFormRef.value) return
await recallFormRef.value.validate(async (valid) => {
if (valid) {
// 根据选卡方式构建请求参数
const params: Partial<RecallStandaloneCardsRequest> = {
selection_type: recallForm.selection_type!,
remark: recallForm.remark
}
if (recallForm.selection_type === CardSelectionType.LIST) {
params.iccids = selectedCards.value.map((card) => card.iccid)
if (params.iccids.length === 0) {
ElMessage.warning('请先选择要回收的卡')
return
}
} else if (recallForm.selection_type === CardSelectionType.RANGE) {
params.iccid_start = recallForm.iccid_start
params.iccid_end = recallForm.iccid_end
} else if (recallForm.selection_type === CardSelectionType.FILTER) {
if (recallForm.carrier_id) params.carrier_id = recallForm.carrier_id
if (recallForm.batch_no) params.batch_no = recallForm.batch_no
}
recallLoading.value = true
try {
const res = await CardService.recallStandaloneCards(params)
// code === 0 表示操作成功(接口调用成功),显示结果对话框
if (res.code === 0) {
allocationResult.value = res.data
resultTitle.value = '批量回收结果'
recallDialogVisible.value = false
resultDialogVisible.value = true
// 清空选择
selectedCards.value = []
// 刷新列表
await getTableData()
} else {
// code !== 0 才是真正失败(接口调用失败)
ElMessage.error(res.msg || '批量回收失败,请重试')
}
} catch (error) {
console.error(error)
} finally {
recallLoading.value = false
}
}
})
}
// 显示套餐系列绑定对话框
const showSeriesBindingDialog = async () => {
Object.assign(seriesBindingForm, createInitialSeriesBindingState(), {
selection_type:
selectedCards.value.length > 0 ? CardSelectionType.LIST : CardSelectionType.FILTER
})
await Promise.all([
loadPackageSeriesList(),
loadAllocateCarrierOptions(),
loadAllocateBatchNoOptions()
])
seriesBindingDialogVisible.value = true
seriesBindingFormRef.value?.clearValidate()
}
// 加载套餐系列列表支持名称搜索默认20条
const loadPackageSeriesList = async (seriesName?: string) => {
seriesLoading.value = true
try {
const params: any = {
page: 1,
page_size: 20,
status: 1 // 只获取启用的
}
if (seriesName) {
params.series_name = seriesName
}
const res = await PackageSeriesService.getPackageSeries(params)
if (res.code === 0 && res.data.items) {
packageSeriesList.value = res.data.items
}
} catch (error) {
console.error('获取套餐系列列表失败:', error)
} finally {
seriesLoading.value = false
}
}
// 搜索套餐系列
const searchPackageSeries = async (query: string) => {
await loadPackageSeriesList(query || undefined)
}
// 关闭套餐系列绑定对话框
const handleSeriesBindingDialogClose = () => {
Object.assign(seriesBindingForm, createInitialSeriesBindingState())
if (seriesBindingFormRef.value) {
seriesBindingFormRef.value.resetFields()
}
}
// 关闭套餐系列绑定结果对话框
const handleSeriesBindingResultDialogClose = () => {
// 刷新列表
getTableData()
}
// 执行套餐系列绑定
const handleSeriesBinding = async () => {
if (!seriesBindingFormRef.value) return
await seriesBindingFormRef.value.validate(async (valid) => {
if (valid) {
seriesBindingLoading.value = true
try {
const request: BatchSetCardSeriesBindingRequest = {
selection_type: seriesBindingForm.selection_type,
series_id: seriesBindingForm.series_id!
}
if (seriesBindingForm.selection_type === CardSelectionType.LIST) {
const iccids = selectedCards.value.map((card) => card.iccid)
if (iccids.length === 0) {
ElMessage.warning('列表模式下请先选择要设置的卡')
return
}
request.iccids = iccids
} else if (seriesBindingForm.selection_type === CardSelectionType.RANGE) {
request.iccid_start = seriesBindingForm.iccid_start
request.iccid_end = seriesBindingForm.iccid_end
} else {
if (seriesBindingForm.carrier_id) request.carrier_id = seriesBindingForm.carrier_id
if (seriesBindingForm.status) request.status = seriesBindingForm.status
if (seriesBindingForm.batch_no) request.batch_no = seriesBindingForm.batch_no
}
const res = await CardService.batchSetCardSeriesBinding(request)
if (res.code === 0) {
seriesBindingResult.value = res.data
seriesBindingDialogVisible.value = false
seriesBindingResultDialogVisible.value = true
// 清空选择
selectedCards.value = []
// 立即刷新列表
await getTableData()
// 显示消息提示
if (res.data.fail_count === 0) {
ElMessage.success('套餐系列绑定设置成功')
} else if (res.data.success_count === 0) {
ElMessage.error('套餐系列绑定设置失败')
} else {
ElMessage.warning(
`部分设置成功:成功 ${res.data.success_count} 项,失败 ${res.data.fail_count} 项`
)
}
}
} catch (error) {
console.error(error)
} finally {
seriesBindingLoading.value = false
}
}
})
}
// 清除卡套餐系列绑定
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) {
case 'start-card':
handleStartCard(iccid)
break
case 'stop-card':
handleStopCard(iccid)
break
case 'realname-policy':
handleShowRealnamePolicy(iccid, row?.realname_policy)
break
case 'update-realname-status':
handleShowUpdateRealnameStatus(iccid, row?.real_name_status)
break
case 'clear-series':
handleClearSingleCardSeries(iccid)
break
}
}
// 清除单卡套餐系列绑定
const handleClearSingleCardSeries = (iccid: string) => {
ElMessageBox.confirm(`确定要清除该卡(${iccid})的套餐系列关联吗?`, '确认清除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
try {
const res = await CardService.batchSetCardSeriesBinding({
selection_type: CardSelectionType.LIST,
iccids: [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 || '未知原因'}`)
}
await getTableData()
}
} catch (error) {
console.error('清除关联失败:', error)
}
})
.catch(() => {
// 用户取消
})
}
// 启用此卡(复机)
const handleStartCard = (iccid: string) => {
ElMessageBox.confirm('确定要启用该卡片吗?', '确认启用', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
try {
// 使用统一接口:通过 identifier (ICCID) 启用资产
const res = await AssetService.startAsset(iccid)
if (res.code === 0) {
ElMessage.success('启用成功')
await getTableData()
}
} catch (error: any) {
console.error('启用此卡失败:', error)
}
})
.catch(() => {
// 用户取消
})
}
// 停用此卡(停机)
const handleStopCard = (iccid: string) => {
ElMessageBox.confirm('确定要停用该卡片吗?', '确认停用', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
try {
// 使用统一接口:通过 identifier (ICCID) 停用资产
const res = await AssetService.stopAsset(iccid)
if (res.code === 0) {
ElMessage.success('停用成功')
await getTableData()
}
} catch (error: any) {
console.error('停用此卡失败:', error)
}
})
.catch(() => {
// 用户取消
})
}
// 手动停用 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>
:deep(.el-table__row.table-row-with-context-menu) {
cursor: pointer;
}
</style>