All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 7m52s
Constraint: 后端订单套餐作废接口使用 /api/admin/order-package-invalidate-tasks,上传 purpose 只能使用 attachment。 Rejected: 继续提交逗号拼接凭证字符串 | 新提交路径必须使用 string[],历史字符串只做读取归一化。 Confidence: high Scope-risk: broad Directive: 新增订单套餐作废权限时需同时配置菜单 URL 与 order_package_invalidate_task:create/detail 按钮权限。 Tested: bun run build;development 真实后端联调上传、创建、列表、详情通过;复机接口未触发。 Not-tested: 未逐一手工覆盖所有历史凭证数据、全部角色权限组合和所有文件扩展名预览
2946 lines
94 KiB
Vue
2946 lines
94 KiB
Vue
<template>
|
||
<ArtTableFullScreen>
|
||
<div class="standalone-card-list-page" id="table-full-screen">
|
||
<!-- 搜索栏 -->
|
||
<ArtSearchBar
|
||
v-model:filter="formFilters"
|
||
:items="formItems"
|
||
label-width="90px"
|
||
@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"
|
||
:disabled="selectedCards.length === 0"
|
||
@click="showSeriesBindingDialog"
|
||
v-permission="'iot_card:batch_setting'"
|
||
>
|
||
批量设置套餐系列
|
||
</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>
|
||
</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>
|
||
<div
|
||
v-if="
|
||
allocateForm.selection_type === CardSelectionType.RANGE && allocateRangeCountText
|
||
"
|
||
class="range-allocation-hint range-allocation-hint--card-form"
|
||
>
|
||
共分配{{ allocateRangeCountText }}张卡
|
||
</div>
|
||
|
||
<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>
|
||
<div
|
||
v-if="recallForm.selection_type === CardSelectionType.RANGE && recallRangeCountText"
|
||
class="range-allocation-hint range-allocation-hint--card-form"
|
||
>
|
||
共分配{{ recallRangeCountText }}张卡
|
||
</div>
|
||
|
||
<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="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"
|
||
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>
|
||
<div
|
||
v-if="
|
||
seriesBindingForm.selection_type === CardSelectionType.RANGE &&
|
||
seriesBindingRangeCountText
|
||
"
|
||
class="range-allocation-hint range-allocation-hint--card"
|
||
>
|
||
共分配{{ seriesBindingRangeCountText }}张卡
|
||
</div>
|
||
<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"
|
||
/>
|
||
<ExportTaskCreateDialog
|
||
v-model="exportDialogVisible"
|
||
scene="iot_card"
|
||
:query="exportQuery"
|
||
confirm-permission="iot_card:export"
|
||
title="导出IoT卡"
|
||
/>
|
||
</ElCard>
|
||
</div>
|
||
</ArtTableFullScreen>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, h } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { RoutesAlias } from '@/router/routesAlias'
|
||
import {
|
||
CardService,
|
||
ShopService,
|
||
PackageSeriesService,
|
||
AssetService,
|
||
CarrierService,
|
||
EnterpriseService
|
||
} 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 ExportTaskCreateDialog from '@/components/business/ExportTaskCreateDialog.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 type { EnterpriseItem } from '@/types/api/enterprise'
|
||
import type { AllocateCardsResponse, RecallCardsResponse } from '@/types/api/enterpriseCard'
|
||
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 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)
|
||
const operationLogsIdentifier = ref('')
|
||
const exportDialogVisible = ref(false)
|
||
|
||
// 套餐系列绑定相关
|
||
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 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)) {
|
||
return null
|
||
}
|
||
|
||
try {
|
||
return BigInt(normalizedValue)
|
||
} catch {
|
||
return null
|
||
}
|
||
}
|
||
const getPositiveRangeCountText = (start: bigint | null, end: bigint | null): string => {
|
||
if (start === null || end === null || end < start) {
|
||
return ''
|
||
}
|
||
|
||
return (end - start + 1n).toString()
|
||
}
|
||
const formatDigitGrouping = (value: string): string => value.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||
const getFormattedRangeCountText = (startValue?: string, endValue?: string): string => {
|
||
const countText = getPositiveRangeCountText(
|
||
parseNumericStringToBigInt(startValue ?? ''),
|
||
parseNumericStringToBigInt(endValue ?? '')
|
||
)
|
||
|
||
return countText ? formatDigitGrouping(countText) : ''
|
||
}
|
||
const allocateRangeCountText = computed(() =>
|
||
getFormattedRangeCountText(allocateForm.iccid_start, allocateForm.iccid_end)
|
||
)
|
||
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' }],
|
||
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 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 {
|
||
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 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
|
||
virtual_no: string
|
||
device_virtual_no: string
|
||
shop_id: undefined | number
|
||
series_id: undefined | number
|
||
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: '',
|
||
virtual_no: '',
|
||
device_virtual_no: '',
|
||
shop_id: undefined,
|
||
series_id: undefined,
|
||
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: ''
|
||
}
|
||
|
||
// 搜索表单
|
||
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: 'network_status',
|
||
type: 'select',
|
||
config: {
|
||
clearable: true,
|
||
placeholder: '全部'
|
||
},
|
||
options: () => [
|
||
{ label: '停机', value: 0 },
|
||
{ label: '正常', value: 1 }
|
||
]
|
||
},
|
||
{
|
||
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_replaced',
|
||
type: 'select',
|
||
config: {
|
||
clearable: true,
|
||
placeholder: '全部'
|
||
},
|
||
options: () => [
|
||
{ label: '有换卡记录', value: true },
|
||
{ label: '无换卡记录', value: false }
|
||
]
|
||
},
|
||
{
|
||
label: '起始号',
|
||
prop: 'iccid_start',
|
||
type: 'input',
|
||
config: {
|
||
clearable: true,
|
||
placeholder: '请输入ICCID起始号'
|
||
}
|
||
},
|
||
{
|
||
label: '结束号',
|
||
prop: 'iccid_end',
|
||
type: 'input',
|
||
config: {
|
||
clearable: true,
|
||
placeholder: '请输入ICCID结束号'
|
||
}
|
||
},
|
||
{
|
||
label: '是否绑设备',
|
||
prop: 'is_standalone',
|
||
type: 'select',
|
||
config: {
|
||
clearable: true,
|
||
placeholder: '全部'
|
||
},
|
||
options: () => [
|
||
{ label: '未绑定设备', value: true },
|
||
{ label: '已绑定设备', value: false }
|
||
]
|
||
}
|
||
]
|
||
|
||
// 搜索表单配置
|
||
baseFormItems.splice(8, 0, {
|
||
label: '有效套餐',
|
||
prop: 'has_active_package',
|
||
type: 'select',
|
||
config: {
|
||
clearable: true,
|
||
placeholder: '全部'
|
||
},
|
||
options: () => [
|
||
{ label: '是', value: true },
|
||
{ 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
|
||
|
||
// 列配置
|
||
const columnOptions = [
|
||
{ label: 'ICCID', prop: 'iccid' },
|
||
{ label: 'IMEI', prop: 'gateway_card_imei' },
|
||
{ label: 'MSISDN', prop: 'msisdn' },
|
||
{ label: '卡虚拟号', prop: 'virtual_no' },
|
||
{ label: '运营商', prop: 'carrier_name' },
|
||
{ label: '卡业务类型', prop: 'card_category' },
|
||
{ label: '状态', prop: 'status' },
|
||
{ label: '资产世代', prop: 'generation' },
|
||
{ label: '业务状态', prop: 'asset_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: 'gateway_card_imei',
|
||
label: 'IMEI',
|
||
width: 180,
|
||
showOverflowTooltip: true,
|
||
formatter: (row: StandaloneIotCard) => row.gateway_card_imei || '-'
|
||
},
|
||
{
|
||
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: 'generation',
|
||
label: '资产世代',
|
||
width: 100,
|
||
formatter: (row: StandaloneIotCard) => row.generation ?? '-'
|
||
},
|
||
{
|
||
prop: 'asset_status',
|
||
label: '业务状态',
|
||
width: 100,
|
||
formatter: (row: StandaloneIotCard) => {
|
||
const assetStatusMap: Record<number, { text: string; type: any }> = {
|
||
1: { text: '在库', type: 'info' },
|
||
2: { text: '已销售', type: 'success' },
|
||
3: { text: '已换货', type: 'warning' },
|
||
4: { text: '已停用', type: 'danger' }
|
||
}
|
||
if (row.asset_status == null) return '-'
|
||
const status = assetStatusMap[row.asset_status] || { text: '未知', type: 'info' }
|
||
return h(ElTag, { type: status.type }, () => status.text)
|
||
}
|
||
},
|
||
{
|
||
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
|
||
}
|
||
|
||
const exportQuery = computed(() => {
|
||
const query: Record<string, unknown> = {}
|
||
|
||
Object.entries(formFilters).forEach(([key, value]) => {
|
||
if (value === undefined || value === null || value === '') return
|
||
if (Array.isArray(value) && value.length === 0) return
|
||
query[key] = value
|
||
})
|
||
|
||
return query
|
||
})
|
||
|
||
const showExportDialog = () => {
|
||
exportDialogVisible.value = true
|
||
}
|
||
|
||
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 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(), {
|
||
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
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 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(() => {
|
||
// 用户取消
|
||
})
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.range-allocation-hint {
|
||
font-size: 13px;
|
||
line-height: 20px;
|
||
color: var(--el-text-color-secondary);
|
||
}
|
||
|
||
.range-allocation-hint--card {
|
||
margin: -6px 0 18px 100px;
|
||
}
|
||
|
||
.range-allocation-hint--card-form {
|
||
margin: -6px 0 18px 120px;
|
||
}
|
||
|
||
:deep(.el-table__row.table-row-with-context-menu) {
|
||
cursor: pointer;
|
||
}
|
||
</style>
|