1821 lines
56 KiB
Vue
1821 lines
56 KiB
Vue
<template>
|
||
<ArtTableFullScreen>
|
||
<div class="standalone-card-list-page" id="table-full-screen">
|
||
<!-- 搜索栏 -->
|
||
<ArtSearchBar
|
||
v-model:filter="formFilters"
|
||
:items="formItems"
|
||
label-width="90"
|
||
@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
|
||
v-if="
|
||
hasAuth('iot_card:batch_recharge') ||
|
||
hasAuth('iot_card:network_distribution') ||
|
||
hasAuth('iot_card:network_recycle') ||
|
||
hasAuth('iot_card:change_package')
|
||
"
|
||
type="info"
|
||
@contextmenu.prevent="showMoreMenu"
|
||
>
|
||
更多操作
|
||
</ElButton>
|
||
</template>
|
||
</ArtTableHeader>
|
||
|
||
<!-- 表格 -->
|
||
<ArtTable
|
||
ref="tableRef"
|
||
row-key="id"
|
||
:loading="loading"
|
||
:data="cardList"
|
||
:currentPage="pagination.page"
|
||
:pageSize="pagination.pageSize"
|
||
:total="pagination.total"
|
||
:marginTop="10"
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
@selection-change="handleSelectionChange"
|
||
@row-contextmenu="handleRowContextMenu"
|
||
>
|
||
<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">
|
||
<ElSelect
|
||
v-model="allocateForm.to_shop_id"
|
||
placeholder="请选择或搜索目标店铺"
|
||
filterable
|
||
remote
|
||
reserve-keyword
|
||
:remote-method="searchTargetShops"
|
||
:loading="targetShopLoading"
|
||
clearable
|
||
style="width: 100%"
|
||
>
|
||
<ElOption
|
||
v-for="shop in targetShopList"
|
||
:key="shop.id"
|
||
:label="shop.shop_name"
|
||
:value="shop.id"
|
||
/>
|
||
</ElSelect>
|
||
</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 === 'list'" label="ICCID列表">
|
||
<div>已选择 {{ selectedCards.length }} 张卡</div>
|
||
</ElFormItem>
|
||
|
||
<ElFormItem
|
||
v-if="allocateForm.selection_type === 'range'"
|
||
label="起始ICCID"
|
||
prop="iccid_start"
|
||
>
|
||
<ElInput v-model="allocateForm.iccid_start" placeholder="请输入起始ICCID" />
|
||
</ElFormItem>
|
||
<ElFormItem
|
||
v-if="allocateForm.selection_type === 'range'"
|
||
label="结束ICCID"
|
||
prop="iccid_end"
|
||
>
|
||
<ElInput v-model="allocateForm.iccid_end" placeholder="请输入结束ICCID" />
|
||
</ElFormItem>
|
||
|
||
<ElFormItem v-if="allocateForm.selection_type === 'filter'" label="运营商">
|
||
<ElSelect
|
||
v-model="allocateForm.carrier_id"
|
||
placeholder="请选择运营商"
|
||
clearable
|
||
style="width: 100%"
|
||
>
|
||
<ElOption label="中国移动" :value="1" />
|
||
<ElOption label="中国联通" :value="2" />
|
||
<ElOption label="中国电信" :value="3" />
|
||
</ElSelect>
|
||
</ElFormItem>
|
||
<ElFormItem v-if="allocateForm.selection_type === '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 === 'filter'" label="批次号">
|
||
<ElInput v-model="allocateForm.batch_no" placeholder="请输入批次号" />
|
||
</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 === 'list'" label="ICCID列表">
|
||
<div>已选择 {{ selectedCards.length }} 张卡</div>
|
||
</ElFormItem>
|
||
|
||
<ElFormItem
|
||
v-if="recallForm.selection_type === 'range'"
|
||
label="起始ICCID"
|
||
prop="iccid_start"
|
||
>
|
||
<ElInput v-model="recallForm.iccid_start" placeholder="请输入起始ICCID" />
|
||
</ElFormItem>
|
||
<ElFormItem
|
||
v-if="recallForm.selection_type === 'range'"
|
||
label="结束ICCID"
|
||
prop="iccid_end"
|
||
>
|
||
<ElInput v-model="recallForm.iccid_end" placeholder="请输入结束ICCID" />
|
||
</ElFormItem>
|
||
|
||
<ElFormItem v-if="recallForm.selection_type === 'filter'" label="运营商">
|
||
<ElSelect
|
||
v-model="recallForm.carrier_id"
|
||
placeholder="请选择运营商"
|
||
clearable
|
||
style="width: 100%"
|
||
>
|
||
<ElOption label="中国移动" :value="1" />
|
||
<ElOption label="中国联通" :value="2" />
|
||
<ElOption label="中国电信" :value="3" />
|
||
</ElSelect>
|
||
</ElFormItem>
|
||
<ElFormItem v-if="recallForm.selection_type === 'filter'" label="批次号">
|
||
<ElInput v-model="recallForm.batch_no" placeholder="请输入批次号" />
|
||
</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="120px"
|
||
>
|
||
<ElFormItem label="已选择卡数">
|
||
<div>已选择 {{ selectedCards.length }} 张卡</div>
|
||
</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 label="清除关联" :value="0" />
|
||
<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">
|
||
<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="text-align: center; padding: 40px">
|
||
<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="卡接入号">{{
|
||
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="成本价">{{
|
||
formatCardPrice(currentCardDetail.cost_price)
|
||
}}</ElDescriptionsItem>
|
||
|
||
<ElDescriptionsItem label="分销价">{{
|
||
formatCardPrice(currentCardDetail.distribute_price)
|
||
}}</ElDescriptionsItem>
|
||
<ElDescriptionsItem label="状态">
|
||
<ElTag :type="getCardDetailStatusTagType(currentCardDetail.status)">
|
||
{{ getCardDetailStatusText(currentCardDetail.status) }}
|
||
</ElTag>
|
||
</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="一次性佣金">
|
||
<ElTag :type="currentCardDetail.first_commission_paid ? 'success' : 'info'">
|
||
{{ currentCardDetail.first_commission_paid ? '已产生' : '未产生' }}
|
||
</ElTag>
|
||
</ElDescriptionsItem>
|
||
<ElDescriptionsItem label="累计充值">{{
|
||
formatCardPrice(currentCardDetail.accumulated_recharge)
|
||
}}</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>
|
||
|
||
<!-- 流量使用查询对话框 -->
|
||
<ElDialog v-model="flowUsageDialogVisible" title="流量使用查询" width="500px">
|
||
<div v-if="flowUsageLoading" style="text-align: center; padding: 40px">
|
||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||
<div style="margin-top: 16px">查询中...</div>
|
||
</div>
|
||
|
||
<ElDescriptions v-else-if="flowUsageData" :column="1" border>
|
||
<ElDescriptionsItem label="已用流量">{{
|
||
flowUsageData.usedFlow || 0
|
||
}}</ElDescriptionsItem>
|
||
<ElDescriptionsItem label="流量单位">{{
|
||
flowUsageData.unit || 'MB'
|
||
}}</ElDescriptionsItem>
|
||
<ElDescriptionsItem v-if="flowUsageData.extend" label="扩展信息">{{
|
||
flowUsageData.extend
|
||
}}</ElDescriptionsItem>
|
||
</ElDescriptions>
|
||
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<ElButton type="primary" @click="flowUsageDialogVisible = false">关闭</ElButton>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
|
||
<!-- 实名状态查询对话框 -->
|
||
<ElDialog v-model="realnameStatusDialogVisible" title="实名认证状态" width="500px">
|
||
<div v-if="realnameStatusLoading" style="text-align: center; padding: 40px">
|
||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||
<div style="margin-top: 16px">查询中...</div>
|
||
</div>
|
||
|
||
<ElDescriptions v-else-if="realnameStatusData" :column="1" border>
|
||
<ElDescriptionsItem label="实名状态">{{
|
||
realnameStatusData.status || '未知'
|
||
}}</ElDescriptionsItem>
|
||
<ElDescriptionsItem v-if="realnameStatusData.extend" label="扩展信息">{{
|
||
realnameStatusData.extend
|
||
}}</ElDescriptionsItem>
|
||
</ElDescriptions>
|
||
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<ElButton type="primary" @click="realnameStatusDialogVisible = false">关闭</ElButton>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
|
||
<!-- 卡实时状态查询对话框 -->
|
||
<ElDialog v-model="cardStatusDialogVisible" title="卡实时状态" width="500px">
|
||
<div v-if="cardStatusLoading" style="text-align: center; padding: 40px">
|
||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||
<div style="margin-top: 16px">查询中...</div>
|
||
</div>
|
||
|
||
<ElDescriptions v-else-if="cardStatusData" :column="1" border>
|
||
<ElDescriptionsItem label="ICCID">{{
|
||
cardStatusData.iccid || '--'
|
||
}}</ElDescriptionsItem>
|
||
<ElDescriptionsItem label="卡状态">{{
|
||
cardStatusData.cardStatus || '未知'
|
||
}}</ElDescriptionsItem>
|
||
<ElDescriptionsItem v-if="cardStatusData.extend" label="扩展信息">{{
|
||
cardStatusData.extend
|
||
}}</ElDescriptionsItem>
|
||
</ElDescriptions>
|
||
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<ElButton type="primary" @click="cardStatusDialogVisible = false">关闭</ElButton>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
|
||
<!-- 实名认证链接对话框 -->
|
||
<ElDialog v-model="realnameLinkDialogVisible" title="实名认证链接" width="500px">
|
||
<div v-if="realnameLinkLoading" style="text-align: center; padding: 40px">
|
||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||
<div style="margin-top: 16px">获取中...</div>
|
||
</div>
|
||
|
||
<div v-else-if="realnameLinkData && realnameLinkData.link" style="text-align: center">
|
||
<div style="margin-bottom: 16px">
|
||
<img v-if="qrcodeDataURL" :src="qrcodeDataURL" alt="实名认证二维码" />
|
||
</div>
|
||
<ElDescriptions :column="1" border>
|
||
<ElDescriptionsItem label="实名链接">
|
||
<a
|
||
:href="realnameLinkData.link"
|
||
target="_blank"
|
||
style="color: var(--el-color-primary)"
|
||
>
|
||
{{ realnameLinkData.link }}
|
||
</a>
|
||
</ElDescriptionsItem>
|
||
<ElDescriptionsItem v-if="realnameLinkData.extend" label="扩展信息">{{
|
||
realnameLinkData.extend
|
||
}}</ElDescriptionsItem>
|
||
</ElDescriptions>
|
||
</div>
|
||
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<ElButton type="primary" @click="realnameLinkDialogVisible = false">关闭</ElButton>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
|
||
<!-- 更多操作右键菜单 -->
|
||
<ArtMenuRight
|
||
ref="moreMenuRef"
|
||
:menu-items="moreMenuItems"
|
||
:menu-width="180"
|
||
@select="handleMoreMenuSelect"
|
||
/>
|
||
|
||
<!-- 表格行操作右键菜单 -->
|
||
<ArtMenuRight
|
||
ref="cardOperationMenuRef"
|
||
:menu-items="cardOperationMenuItems"
|
||
:menu-width="160"
|
||
@select="handleCardOperationMenuSelect"
|
||
/>
|
||
</ElCard>
|
||
</div>
|
||
</ArtTableFullScreen>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { h } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { CardService, ShopService, PackageSeriesService } from '@/api/modules'
|
||
import { ElMessage, ElTag, ElIcon, ElMessageBox } from 'element-plus'
|
||
import { Loading } from '@element-plus/icons-vue'
|
||
import type { FormInstance, FormRules } from 'element-plus'
|
||
import QRCode from 'qrcode'
|
||
import type { SearchFormItem } from '@/types'
|
||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||
import { useAuth } from '@/composables/useAuth'
|
||
import { formatDateTime } from '@/utils/business/format'
|
||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||
import type {
|
||
StandaloneIotCard,
|
||
StandaloneCardStatus,
|
||
AllocateStandaloneCardsRequest,
|
||
RecallStandaloneCardsRequest,
|
||
AllocateStandaloneCardsResponse,
|
||
BatchSetCardSeriesBindingResponse
|
||
} from '@/types/api/card'
|
||
import type { PackageSeriesResponse } from '@/types/api'
|
||
|
||
defineOptions({ name: 'StandaloneCardList' })
|
||
|
||
const { hasAuth } = useAuth()
|
||
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 seriesBindingDialogVisible = ref(false)
|
||
const seriesBindingLoading = ref(false)
|
||
const seriesBindingResultDialogVisible = ref(false)
|
||
const seriesBindingFormRef = ref<FormInstance>()
|
||
const seriesLoading = ref(false)
|
||
const packageSeriesList = ref<PackageSeriesResponse[]>([])
|
||
const seriesBindingForm = reactive({
|
||
series_id: undefined as number | undefined
|
||
})
|
||
const seriesBindingRules = reactive<FormRules>({
|
||
series_id: [{ required: true, message: '请选择套餐系列', trigger: 'change' }]
|
||
})
|
||
const seriesBindingResult = ref<BatchSetCardSeriesBindingResponse>({
|
||
success_count: 0,
|
||
fail_count: 0,
|
||
failed_items: null
|
||
})
|
||
|
||
// 卡详情弹窗相关
|
||
const cardDetailDialogVisible = ref(false)
|
||
const cardDetailLoading = ref(false)
|
||
const currentCardDetail = ref<any>(null)
|
||
|
||
// IoT卡操作相关对话框
|
||
const flowUsageDialogVisible = ref(false)
|
||
const flowUsageLoading = ref(false)
|
||
const flowUsageData = ref<any>(null)
|
||
|
||
const realnameStatusDialogVisible = ref(false)
|
||
const realnameStatusLoading = ref(false)
|
||
const realnameStatusData = ref<any>(null)
|
||
|
||
const cardStatusDialogVisible = ref(false)
|
||
const cardStatusLoading = ref(false)
|
||
const cardStatusData = ref<any>(null)
|
||
|
||
const realnameLinkDialogVisible = ref(false)
|
||
const realnameLinkLoading = ref(false)
|
||
const realnameLinkData = ref<any>(null)
|
||
const qrcodeDataURL = ref<string>('')
|
||
|
||
// 更多操作右键菜单
|
||
const moreMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||
const cardOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||
const currentOperatingIccid = ref<string>('')
|
||
|
||
// 店铺相关
|
||
const targetShopLoading = ref(false)
|
||
const fromShopLoading = ref(false)
|
||
const targetShopList = ref<any[]>([])
|
||
const fromShopList = ref<any[]>([])
|
||
|
||
// 搜索表单初始值
|
||
const initialSearchState = {
|
||
status: undefined,
|
||
carrier_id: undefined,
|
||
iccid: '',
|
||
msisdn: '',
|
||
is_distributed: undefined
|
||
}
|
||
|
||
// 搜索表单
|
||
const formFilters = reactive({ ...initialSearchState })
|
||
|
||
// 批量分配表单
|
||
const allocateForm = reactive<Partial<AllocateStandaloneCardsRequest>>({
|
||
selection_type: '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 === 'range' && !value) {
|
||
callback(new Error('请输入起始ICCID'))
|
||
} else {
|
||
callback()
|
||
}
|
||
},
|
||
trigger: 'blur'
|
||
}
|
||
],
|
||
iccid_end: [
|
||
{
|
||
required: true,
|
||
validator: (rule, value, callback) => {
|
||
if (allocateForm.selection_type === 'range' && !value) {
|
||
callback(new Error('请输入结束ICCID'))
|
||
} else {
|
||
callback()
|
||
}
|
||
},
|
||
trigger: 'blur'
|
||
}
|
||
]
|
||
})
|
||
|
||
// 批量回收表单
|
||
const recallForm = reactive<Partial<RecallStandaloneCardsRequest>>({
|
||
selection_type: '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 === 'range' && !value) {
|
||
callback(new Error('请输入起始ICCID'))
|
||
} else {
|
||
callback()
|
||
}
|
||
},
|
||
trigger: 'blur'
|
||
}
|
||
],
|
||
iccid_end: [
|
||
{
|
||
required: true,
|
||
validator: (rule, value, callback) => {
|
||
if (recallForm.selection_type === 'range' && !value) {
|
||
callback(new Error('请输入结束ICCID'))
|
||
} else {
|
||
callback()
|
||
}
|
||
},
|
||
trigger: 'blur'
|
||
}
|
||
]
|
||
})
|
||
|
||
// 分页
|
||
const pagination = reactive({
|
||
page: 1,
|
||
pageSize: 20,
|
||
total: 0
|
||
})
|
||
|
||
// 搜索表单配置
|
||
const formItems: SearchFormItem[] = [
|
||
{
|
||
label: '状态',
|
||
prop: 'status',
|
||
type: 'select',
|
||
config: {
|
||
clearable: true,
|
||
placeholder: '全部'
|
||
},
|
||
options: () => [
|
||
{ label: '在库', value: 1 },
|
||
{ label: '已分销', value: 2 },
|
||
{ label: '已激活', value: 3 },
|
||
{ label: '已停用', value: 4 }
|
||
]
|
||
},
|
||
{
|
||
label: '运营商',
|
||
prop: 'carrier_id',
|
||
type: 'select',
|
||
config: {
|
||
clearable: true,
|
||
placeholder: '全部'
|
||
},
|
||
options: () => [
|
||
{ label: '中国移动', value: 1 },
|
||
{ label: '中国联通', value: 2 },
|
||
{ label: '中国电信', value: 3 }
|
||
]
|
||
},
|
||
{
|
||
label: 'ICCID',
|
||
prop: 'iccid',
|
||
type: 'input',
|
||
config: {
|
||
clearable: true,
|
||
placeholder: '请输入ICCID'
|
||
}
|
||
},
|
||
{
|
||
label: '卡接入号',
|
||
prop: 'msisdn',
|
||
type: 'input',
|
||
config: {
|
||
clearable: true,
|
||
placeholder: '请输入卡接入号'
|
||
}
|
||
},
|
||
{
|
||
label: '是否已分销',
|
||
prop: 'is_distributed',
|
||
type: 'select',
|
||
config: {
|
||
clearable: true,
|
||
placeholder: '全部'
|
||
},
|
||
options: () => [
|
||
{ label: '是', value: true },
|
||
{ label: '否', value: false }
|
||
]
|
||
}
|
||
]
|
||
|
||
// 列配置
|
||
const columnOptions = [
|
||
{ label: 'ICCID', prop: 'iccid' },
|
||
{ label: '卡接入号', prop: 'msisdn' },
|
||
{ label: '卡业务类型', prop: 'card_category' },
|
||
{ label: '运营商', prop: 'carrier_name' },
|
||
{ label: '店铺名称', prop: 'shop_name' },
|
||
{ label: '套餐系列', prop: 'series_name' },
|
||
{ label: '成本价', prop: 'cost_price' },
|
||
{ label: '分销价', prop: 'distribute_price' },
|
||
{ label: '状态', prop: 'status' },
|
||
{ label: '激活状态', prop: 'activation_status' },
|
||
{ label: '网络状态', prop: 'network_status' },
|
||
{ label: '实名状态', prop: 'real_name_status' },
|
||
{ label: '累计流量(MB)', prop: 'data_usage_mb' },
|
||
{ label: '一次性佣金', prop: 'first_commission_paid' },
|
||
{ label: '累计充值', prop: 'accumulated_recharge' },
|
||
{ label: '创建时间', prop: 'created_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 '未知'
|
||
}
|
||
}
|
||
|
||
// 打开卡详情弹窗
|
||
const goToCardDetail = async (iccid: string) => {
|
||
cardDetailDialogVisible.value = true
|
||
cardDetailLoading.value = true
|
||
currentCardDetail.value = null
|
||
|
||
try {
|
||
const res = await CardService.getIotCardDetailByIccid(iccid)
|
||
if (res.code === 0 && res.data) {
|
||
currentCardDetail.value = res.data
|
||
} else {
|
||
ElMessage.error(res.message || '查询失败')
|
||
cardDetailDialogVisible.value = false
|
||
}
|
||
} catch (error: any) {
|
||
console.error('查询卡片详情失败:', error)
|
||
ElMessage.error(error?.message || '查询失败,请检查ICCID是否正确')
|
||
cardDetailDialogVisible.value = false
|
||
} finally {
|
||
cardDetailLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 卡详情辅助函数
|
||
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 formatCardPrice = (price: number) => {
|
||
return `¥${(price / 100).toFixed(2)}`
|
||
}
|
||
|
||
// 动态列配置
|
||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||
{
|
||
prop: 'iccid',
|
||
label: 'ICCID',
|
||
minWidth: 200,
|
||
formatter: (row: StandaloneIotCard) => {
|
||
return h(
|
||
'span',
|
||
{
|
||
style: { color: 'var(--el-color-primary)', cursor: 'pointer' },
|
||
onClick: () => goToCardDetail(row.iccid)
|
||
},
|
||
row.iccid
|
||
)
|
||
}
|
||
},
|
||
{
|
||
prop: 'msisdn',
|
||
label: '卡接入号',
|
||
width: 130
|
||
},
|
||
{
|
||
prop: 'card_category',
|
||
label: '卡业务类型',
|
||
width: 100,
|
||
formatter: (row: StandaloneIotCard) => getCardCategoryText(row.card_category)
|
||
},
|
||
{
|
||
prop: 'carrier_name',
|
||
label: '运营商',
|
||
width: 150
|
||
},
|
||
{
|
||
prop: 'shop_name',
|
||
label: '店铺名称',
|
||
minWidth: 150,
|
||
formatter: (row: StandaloneIotCard) => row.shop_name || '-'
|
||
},
|
||
{
|
||
prop: 'series_name',
|
||
label: '套餐系列',
|
||
width: 150,
|
||
formatter: (row: StandaloneIotCard) => row.series_name || '-'
|
||
},
|
||
{
|
||
prop: 'cost_price',
|
||
label: '成本价',
|
||
width: 100,
|
||
formatter: (row: StandaloneIotCard) => `¥${(row.cost_price / 100).toFixed(2)}`
|
||
},
|
||
{
|
||
prop: 'distribute_price',
|
||
label: '分销价',
|
||
width: 100,
|
||
formatter: (row: StandaloneIotCard) => `¥${(row.distribute_price / 100).toFixed(2)}`
|
||
},
|
||
{
|
||
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: 'data_usage_mb',
|
||
label: '累计流量(MB)',
|
||
width: 120
|
||
},
|
||
{
|
||
prop: 'first_commission_paid',
|
||
label: '一次性佣金',
|
||
width: 100,
|
||
formatter: (row: StandaloneIotCard) => {
|
||
const type = row.first_commission_paid ? 'success' : 'info'
|
||
const text = row.first_commission_paid ? '已产生' : '未产生'
|
||
return h(ElTag, { type, size: 'small' }, () => text)
|
||
}
|
||
},
|
||
{
|
||
prop: 'accumulated_recharge',
|
||
label: '累计充值',
|
||
width: 100,
|
||
formatter: (row: StandaloneIotCard) => `¥${(row.accumulated_recharge / 100).toFixed(2)}`
|
||
},
|
||
{
|
||
prop: 'created_at',
|
||
label: '创建时间',
|
||
width: 180,
|
||
formatter: (row: StandaloneIotCard) => formatDateTime(row.created_at)
|
||
}
|
||
])
|
||
|
||
onMounted(() => {
|
||
getTableData()
|
||
})
|
||
|
||
// 获取单卡列表
|
||
const getTableData = async () => {
|
||
loading.value = true
|
||
try {
|
||
const params = {
|
||
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)
|
||
ElMessage.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 loadTargetShops = async (shopName?: string) => {
|
||
targetShopLoading.value = true
|
||
try {
|
||
const params: any = {
|
||
page: 1,
|
||
page_size: 20
|
||
}
|
||
if (shopName) {
|
||
params.shop_name = shopName
|
||
}
|
||
const res = await ShopService.getShops(params)
|
||
if (res.code === 0) {
|
||
targetShopList.value = res.data.items || []
|
||
}
|
||
} catch (error) {
|
||
console.error('获取目标店铺列表失败:', error)
|
||
} finally {
|
||
targetShopLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 搜索目标店铺
|
||
const searchTargetShops = async (query: string) => {
|
||
await loadTargetShops(query || undefined)
|
||
}
|
||
|
||
// 加载来源店铺列表
|
||
const loadFromShops = async (shopName?: string) => {
|
||
fromShopLoading.value = true
|
||
try {
|
||
const params: any = {
|
||
page: 1,
|
||
page_size: 20
|
||
}
|
||
if (shopName) {
|
||
params.shop_name = shopName
|
||
}
|
||
const res = await ShopService.getShops(params)
|
||
if (res.code === 0) {
|
||
fromShopList.value = res.data.items || []
|
||
}
|
||
} catch (error) {
|
||
console.error('获取来源店铺列表失败:', error)
|
||
} finally {
|
||
fromShopLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 搜索来源店铺
|
||
const searchFromShops = async (query: string) => {
|
||
await loadFromShops(query || undefined)
|
||
}
|
||
|
||
// 显示批量分配对话框
|
||
const showAllocateDialog = () => {
|
||
if (selectedCards.value.length === 0) {
|
||
ElMessage.warning('请先选择要分配的卡')
|
||
return
|
||
}
|
||
allocateDialogVisible.value = true
|
||
Object.assign(allocateForm, {
|
||
selection_type: 'list',
|
||
to_shop_id: undefined,
|
||
iccids: selectedCards.value.map((card) => card.iccid),
|
||
iccid_start: '',
|
||
iccid_end: '',
|
||
carrier_id: undefined,
|
||
status: undefined,
|
||
batch_no: '',
|
||
remark: ''
|
||
})
|
||
// 加载默认店铺列表
|
||
loadTargetShops()
|
||
if (allocateFormRef.value) {
|
||
allocateFormRef.value.resetFields()
|
||
}
|
||
}
|
||
|
||
// 显示批量回收对话框
|
||
const showRecallDialog = () => {
|
||
if (selectedCards.value.length === 0) {
|
||
ElMessage.warning('请先选择要回收的卡')
|
||
return
|
||
}
|
||
recallDialogVisible.value = true
|
||
Object.assign(recallForm, {
|
||
selection_type: 'list',
|
||
iccids: selectedCards.value.map((card) => card.iccid),
|
||
iccid_start: '',
|
||
iccid_end: '',
|
||
carrier_id: undefined,
|
||
batch_no: '',
|
||
remark: ''
|
||
})
|
||
if (recallFormRef.value) {
|
||
recallFormRef.value.resetFields()
|
||
}
|
||
}
|
||
|
||
// 关闭批量分配对话框
|
||
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: allocateForm.to_shop_id!,
|
||
remark: allocateForm.remark
|
||
}
|
||
|
||
if (allocateForm.selection_type === 'list') {
|
||
params.iccids = selectedCards.value.map((card) => card.iccid)
|
||
if (params.iccids.length === 0) {
|
||
ElMessage.warning('请先选择要分配的卡')
|
||
return
|
||
}
|
||
} else if (allocateForm.selection_type === 'range') {
|
||
params.iccid_start = allocateForm.iccid_start
|
||
params.iccid_end = allocateForm.iccid_end
|
||
} else if (allocateForm.selection_type === '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)
|
||
|
||
if (res.code === 0) {
|
||
allocationResult.value = res.data
|
||
resultTitle.value = '批量分配结果'
|
||
allocateDialogVisible.value = false
|
||
resultDialogVisible.value = true
|
||
// 清空选择
|
||
if (tableRef.value) {
|
||
tableRef.value.clearSelection()
|
||
}
|
||
selectedCards.value = []
|
||
// 刷新列表
|
||
getTableData()
|
||
}
|
||
} catch (error) {
|
||
console.error(error)
|
||
ElMessage.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 === 'list') {
|
||
params.iccids = selectedCards.value.map((card) => card.iccid)
|
||
if (params.iccids.length === 0) {
|
||
ElMessage.warning('请先选择要回收的卡')
|
||
return
|
||
}
|
||
} else if (recallForm.selection_type === 'range') {
|
||
params.iccid_start = recallForm.iccid_start
|
||
params.iccid_end = recallForm.iccid_end
|
||
} else if (recallForm.selection_type === '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)
|
||
|
||
if (res.code === 0) {
|
||
allocationResult.value = res.data
|
||
resultTitle.value = '批量回收结果'
|
||
recallDialogVisible.value = false
|
||
resultDialogVisible.value = true
|
||
// 清空选择
|
||
if (tableRef.value) {
|
||
tableRef.value.clearSelection()
|
||
}
|
||
selectedCards.value = []
|
||
// 刷新列表
|
||
getTableData()
|
||
}
|
||
} catch (error) {
|
||
console.error(error)
|
||
ElMessage.error('批量回收失败,请重试')
|
||
} finally {
|
||
recallLoading.value = false
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 显示套餐系列绑定对话框
|
||
const showSeriesBindingDialog = async () => {
|
||
if (selectedCards.value.length === 0) {
|
||
ElMessage.warning('请先选择要设置的卡')
|
||
return
|
||
}
|
||
|
||
// 加载套餐系列列表
|
||
await loadPackageSeriesList()
|
||
|
||
seriesBindingDialogVisible.value = true
|
||
seriesBindingForm.series_id = undefined
|
||
if (seriesBindingFormRef.value) {
|
||
seriesBindingFormRef.value.resetFields()
|
||
}
|
||
}
|
||
|
||
// 加载套餐系列列表(支持名称搜索,默认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)
|
||
ElMessage.error('获取套餐系列列表失败')
|
||
} finally {
|
||
seriesLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 搜索套餐系列
|
||
const searchPackageSeries = async (query: string) => {
|
||
await loadPackageSeriesList(query || undefined)
|
||
}
|
||
|
||
// 关闭套餐系列绑定对话框
|
||
const handleSeriesBindingDialogClose = () => {
|
||
if (seriesBindingFormRef.value) {
|
||
seriesBindingFormRef.value.resetFields()
|
||
}
|
||
}
|
||
|
||
// 执行套餐系列绑定
|
||
const handleSeriesBinding = async () => {
|
||
if (!seriesBindingFormRef.value) return
|
||
|
||
await seriesBindingFormRef.value.validate(async (valid) => {
|
||
if (valid) {
|
||
const iccids = selectedCards.value.map((card) => card.iccid)
|
||
|
||
if (iccids.length === 0) {
|
||
ElMessage.warning('请先选择要设置的卡')
|
||
return
|
||
}
|
||
|
||
seriesBindingLoading.value = true
|
||
try {
|
||
const res = await CardService.batchSetCardSeriesBinding({
|
||
iccids,
|
||
series_id: seriesBindingForm.series_id!
|
||
})
|
||
|
||
if (res.code === 0) {
|
||
seriesBindingResult.value = res.data
|
||
seriesBindingDialogVisible.value = false
|
||
seriesBindingResultDialogVisible.value = true
|
||
|
||
// 清空选择
|
||
if (tableRef.value) {
|
||
tableRef.value.clearSelection()
|
||
}
|
||
selectedCards.value = []
|
||
|
||
// 刷新列表
|
||
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 moreMenuItems = computed((): MenuItemType[] => {
|
||
const items: MenuItemType[] = []
|
||
|
||
if (hasAuth('iot_card:network_distribution')) {
|
||
items.push({
|
||
key: 'distribution',
|
||
label: '网卡分销'
|
||
})
|
||
}
|
||
|
||
if (hasAuth('iot_card:batch_recharge')) {
|
||
items.push({
|
||
key: 'recharge',
|
||
label: '批量充值'
|
||
})
|
||
}
|
||
|
||
if (hasAuth('iot_card:network_recycle')) {
|
||
items.push({
|
||
key: 'recycle',
|
||
label: '网卡回收'
|
||
})
|
||
}
|
||
|
||
items.push({
|
||
key: 'download',
|
||
label: '批量下载'
|
||
})
|
||
|
||
if (hasAuth('iot_card:change_package')) {
|
||
items.push({
|
||
key: 'changePackage',
|
||
label: '变更套餐'
|
||
})
|
||
}
|
||
|
||
return items
|
||
})
|
||
|
||
// 卡操作菜单项配置
|
||
const cardOperationMenuItems = computed((): MenuItemType[] => [
|
||
{
|
||
key: 'query-flow',
|
||
label: '查询流量'
|
||
},
|
||
{
|
||
key: 'realname-status',
|
||
label: '查询实名状态'
|
||
},
|
||
{
|
||
key: 'card-status',
|
||
label: '查询卡状态'
|
||
},
|
||
{
|
||
key: 'realname-link',
|
||
label: '获取实名链接'
|
||
},
|
||
{
|
||
key: 'start-card',
|
||
label: '启用卡片'
|
||
},
|
||
{
|
||
key: 'stop-card',
|
||
label: '停用卡片'
|
||
}
|
||
])
|
||
|
||
// 显示更多操作菜单
|
||
const showMoreMenu = (e: MouseEvent) => {
|
||
e.preventDefault()
|
||
e.stopPropagation()
|
||
moreMenuRef.value?.show(e)
|
||
}
|
||
|
||
// 处理更多操作菜单选择
|
||
const handleMoreMenuSelect = (item: MenuItemType) => {
|
||
switch (item.key) {
|
||
case 'distribution':
|
||
cardDistribution()
|
||
break
|
||
case 'recharge':
|
||
batchRecharge()
|
||
break
|
||
case 'recycle':
|
||
cardRecycle()
|
||
break
|
||
case 'download':
|
||
batchDownload()
|
||
break
|
||
case 'changePackage':
|
||
changePackage()
|
||
break
|
||
}
|
||
}
|
||
|
||
// 显示卡操作菜单
|
||
const showCardOperationMenu = (e: MouseEvent, iccid: string) => {
|
||
e.preventDefault()
|
||
e.stopPropagation()
|
||
currentOperatingIccid.value = iccid
|
||
cardOperationMenuRef.value?.show(e)
|
||
}
|
||
|
||
// 处理卡操作菜单选择
|
||
const handleCardOperationMenuSelect = (item: MenuItemType) => {
|
||
const iccid = currentOperatingIccid.value
|
||
if (!iccid) return
|
||
|
||
if (item.key === 'query-flow') {
|
||
showFlowUsageDialog(iccid)
|
||
} else {
|
||
handleCardOperation(item.key, iccid)
|
||
}
|
||
}
|
||
|
||
// 网卡分销 - 正在开发中
|
||
const cardDistribution = () => {
|
||
ElMessage.info('功能正在开发中')
|
||
}
|
||
|
||
// 批量充值 - 正在开发中
|
||
const batchRecharge = () => {
|
||
ElMessage.info('功能正在开发中')
|
||
}
|
||
|
||
// 网卡回收 - 正在开发中
|
||
const cardRecycle = () => {
|
||
ElMessage.info('功能正在开发中')
|
||
}
|
||
|
||
// 批量下载 - 正在开发中
|
||
const batchDownload = () => {
|
||
ElMessage.info('功能正在开发中')
|
||
}
|
||
|
||
// 变更套餐 - 正在开发中
|
||
const changePackage = () => {
|
||
ElMessage.info('功能正在开发中')
|
||
}
|
||
|
||
// IoT卡操作处理函数
|
||
const handleCardOperation = (command: string, iccid: string) => {
|
||
switch (command) {
|
||
case 'realname-status':
|
||
showRealnameStatusDialog(iccid)
|
||
break
|
||
case 'card-status':
|
||
showCardStatusDialog(iccid)
|
||
break
|
||
case 'realname-link':
|
||
showRealnameLinkDialog(iccid)
|
||
break
|
||
case 'start-card':
|
||
handleStartCard(iccid)
|
||
break
|
||
case 'stop-card':
|
||
handleStopCard(iccid)
|
||
break
|
||
}
|
||
}
|
||
|
||
// 查询流量使用
|
||
const showFlowUsageDialog = async (iccid: string) => {
|
||
flowUsageDialogVisible.value = true
|
||
flowUsageLoading.value = true
|
||
flowUsageData.value = null
|
||
|
||
try {
|
||
const res = await CardService.getGatewayFlow(iccid)
|
||
if (res.code === 0) {
|
||
flowUsageData.value = res.data
|
||
} else {
|
||
ElMessage.error(res.message || '查询失败')
|
||
flowUsageDialogVisible.value = false
|
||
}
|
||
} catch (error: any) {
|
||
console.error('查询流量使用失败:', error)
|
||
ElMessage.error(error?.message || '查询失败')
|
||
flowUsageDialogVisible.value = false
|
||
} finally {
|
||
flowUsageLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 查询实名认证状态
|
||
const showRealnameStatusDialog = async (iccid: string) => {
|
||
realnameStatusDialogVisible.value = true
|
||
realnameStatusLoading.value = true
|
||
realnameStatusData.value = null
|
||
|
||
try {
|
||
const res = await CardService.getGatewayRealname(iccid)
|
||
if (res.code === 0) {
|
||
realnameStatusData.value = res.data
|
||
} else {
|
||
ElMessage.error(res.message || '查询失败')
|
||
realnameStatusDialogVisible.value = false
|
||
}
|
||
} catch (error: any) {
|
||
console.error('查询实名状态失败:', error)
|
||
ElMessage.error(error?.message || '查询失败')
|
||
realnameStatusDialogVisible.value = false
|
||
} finally {
|
||
realnameStatusLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 查询卡实时状态
|
||
const showCardStatusDialog = async (iccid: string) => {
|
||
cardStatusDialogVisible.value = true
|
||
cardStatusLoading.value = true
|
||
cardStatusData.value = null
|
||
|
||
try {
|
||
const res = await CardService.getGatewayStatus(iccid)
|
||
if (res.code === 0) {
|
||
cardStatusData.value = res.data
|
||
} else {
|
||
ElMessage.error(res.message || '查询失败')
|
||
cardStatusDialogVisible.value = false
|
||
}
|
||
} catch (error: any) {
|
||
console.error('查询卡状态失败:', error)
|
||
ElMessage.error(error?.message || '查询失败')
|
||
cardStatusDialogVisible.value = false
|
||
} finally {
|
||
cardStatusLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 获取实名认证链接
|
||
const showRealnameLinkDialog = async (iccid: string) => {
|
||
realnameLinkDialogVisible.value = true
|
||
realnameLinkLoading.value = true
|
||
realnameLinkData.value = null
|
||
qrcodeDataURL.value = ''
|
||
|
||
try {
|
||
const res = await CardService.getRealnameLink(iccid)
|
||
if (res.code === 0 && res.data?.link) {
|
||
realnameLinkData.value = res.data
|
||
// 生成二维码
|
||
qrcodeDataURL.value = await QRCode.toDataURL(res.data.link, {
|
||
width: 200,
|
||
margin: 1
|
||
})
|
||
} else {
|
||
ElMessage.error(res.message || '获取失败')
|
||
realnameLinkDialogVisible.value = false
|
||
}
|
||
} catch (error: any) {
|
||
console.error('获取实名链接失败:', error)
|
||
ElMessage.error(error?.message || '获取失败')
|
||
realnameLinkDialogVisible.value = false
|
||
} finally {
|
||
realnameLinkLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 启用卡片(复机)
|
||
const handleStartCard = (iccid: string) => {
|
||
ElMessageBox.confirm('确定要启用该卡片吗?', '确认启用', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
})
|
||
.then(async () => {
|
||
try {
|
||
const res = await CardService.startCard(iccid)
|
||
if (res.code === 0) {
|
||
ElMessage.success('启用成功')
|
||
getTableData()
|
||
} else {
|
||
ElMessage.error(res.message || '启用失败')
|
||
}
|
||
} catch (error: any) {
|
||
console.error('启用卡片失败:', error)
|
||
ElMessage.error(error?.message || '启用失败')
|
||
}
|
||
})
|
||
.catch(() => {
|
||
// 用户取消
|
||
})
|
||
}
|
||
|
||
// 停用卡片(停机)
|
||
const handleStopCard = (iccid: string) => {
|
||
ElMessageBox.confirm('确定要停用该卡片吗?', '确认停用', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
})
|
||
.then(async () => {
|
||
try {
|
||
const res = await CardService.stopCard(iccid)
|
||
if (res.code === 0) {
|
||
ElMessage.success('停用成功')
|
||
getTableData()
|
||
} else {
|
||
ElMessage.error(res.message || '停用失败')
|
||
}
|
||
} catch (error: any) {
|
||
console.error('停用卡片失败:', error)
|
||
ElMessage.error(error?.message || '停用失败')
|
||
}
|
||
})
|
||
.catch(() => {
|
||
// 用户取消
|
||
})
|
||
}
|
||
|
||
// 处理表格行右键菜单
|
||
const handleRowContextMenu = (row: StandaloneIotCard, column: any, event: MouseEvent) => {
|
||
showCardOperationMenu(event, row.iccid)
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.standalone-card-list-page {
|
||
// Card list page styles
|
||
}
|
||
</style>
|