This commit is contained in:
@@ -169,6 +169,7 @@ export interface AssetRealtimeStatusResponse {
|
|||||||
real_name_status?: RealNameStatus // 实名状态(仅 card)
|
real_name_status?: RealNameStatus // 实名状态(仅 card)
|
||||||
realname_policy?: string // 实名认证策略(仅 card)
|
realname_policy?: string // 实名认证策略(仅 card)
|
||||||
current_month_usage_mb?: number // 本月已用流量 MB(仅 card)
|
current_month_usage_mb?: number // 本月已用流量 MB(仅 card)
|
||||||
|
last_gateway_reading_mb?: number // 运营商周期月内已用流量 MB(仅 card)
|
||||||
last_sync_time?: string // 最后同步时间(仅 card)
|
last_sync_time?: string // 最后同步时间(仅 card)
|
||||||
|
|
||||||
// ===== 设备专属字段 =====
|
// ===== 设备专属字段 =====
|
||||||
|
|||||||
1
src/types/auto-imports.d.ts
vendored
1
src/types/auto-imports.d.ts
vendored
@@ -7,6 +7,7 @@
|
|||||||
export {}
|
export {}
|
||||||
declare global {
|
declare global {
|
||||||
const EffectScope: typeof import('vue')['EffectScope']
|
const EffectScope: typeof import('vue')['EffectScope']
|
||||||
|
const ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
|
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
|
||||||
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
||||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
<!-- 操作按钮组 -->
|
<!-- 操作按钮组 -->
|
||||||
<div v-if="hasCardInfo" class="operation-button-group">
|
<div v-if="hasCardInfo" class="operation-button-group">
|
||||||
<ElButton @click="handleRefresh" type="primary" :icon="Refresh"> 刷新 </ElButton>
|
<ElButton @click="handleRefresh" type="primary" :icon="Refresh" :disabled="refreshDisabled"> 刷新 </ElButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
@@ -54,10 +54,12 @@
|
|||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
hasCardInfo?: boolean
|
hasCardInfo?: boolean
|
||||||
|
refreshDisabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
withDefaults(defineProps<Props>(), {
|
withDefaults(defineProps<Props>(), {
|
||||||
hasCardInfo: false
|
hasCardInfo: false,
|
||||||
|
refreshDisabled: false
|
||||||
})
|
})
|
||||||
|
|
||||||
// Emits
|
// Emits
|
||||||
|
|||||||
@@ -53,6 +53,9 @@
|
|||||||
<ElDescriptionsItem label="本月已用流量">{{
|
<ElDescriptionsItem label="本月已用流量">{{
|
||||||
formatDataSize(cardInfo?.current_month_usage_mb || 0)
|
formatDataSize(cardInfo?.current_month_usage_mb || 0)
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="运营商周期月内已用流量">{{
|
||||||
|
formatDataSize(cardInfo?.last_gateway_reading_mb || 0)
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="实名时间">{{
|
<ElDescriptionsItem label="实名时间">{{
|
||||||
cardInfo?.real_name_at ? formatDateTime(cardInfo.real_name_at) : '-'
|
cardInfo?.real_name_at ? formatDateTime(cardInfo.real_name_at) : '-'
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
@@ -480,6 +483,7 @@
|
|||||||
real_name_status?: number
|
real_name_status?: number
|
||||||
network_status?: number
|
network_status?: number
|
||||||
current_month_usage_mb?: number
|
current_month_usage_mb?: number
|
||||||
|
last_gateway_reading_mb?: number
|
||||||
device_type?: string
|
device_type?: string
|
||||||
device_name?: string
|
device_name?: string
|
||||||
device_model?: string
|
device_model?: string
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>套餐列表</span>
|
<span>套餐列表</span>
|
||||||
<div class="card-header-right">
|
<div class="card-header-right">
|
||||||
<ElButton type="primary" @click="handleShowRecharge">套餐充值</ElButton>
|
<ElButton type="primary" :disabled="props.boundDeviceId" @click="handleShowRecharge"
|
||||||
|
>套餐充值</ElButton
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -276,6 +276,9 @@ export function useAssetInfo() {
|
|||||||
if (data.current_month_usage_mb !== undefined) {
|
if (data.current_month_usage_mb !== undefined) {
|
||||||
cardInfo.value.current_month_usage_mb = data.current_month_usage_mb
|
cardInfo.value.current_month_usage_mb = data.current_month_usage_mb
|
||||||
}
|
}
|
||||||
|
if (data.last_gateway_reading_mb !== undefined) {
|
||||||
|
cardInfo.value.last_gateway_reading_mb = data.last_gateway_reading_mb
|
||||||
|
}
|
||||||
} else if (assetType === 'device') {
|
} else if (assetType === 'device') {
|
||||||
// 设备的实时状态
|
// 设备的实时状态
|
||||||
if (data.device_protect_status !== undefined) {
|
if (data.device_protect_status !== undefined) {
|
||||||
@@ -353,8 +356,9 @@ export function useAssetInfo() {
|
|||||||
* 刷新资产数据(调用refresh接口后重新加载)
|
* 刷新资产数据(调用refresh接口后重新加载)
|
||||||
* @param identifier - 资产标识符
|
* @param identifier - 资产标识符
|
||||||
* @param assetType - 资产类型(card/device)
|
* @param assetType - 资产类型(card/device)
|
||||||
|
* @returns true if refresh was successful, false if rate limited (429)
|
||||||
*/
|
*/
|
||||||
const refreshAsset = async (identifier: string, assetType: string) => {
|
const refreshAsset = async (identifier: string, assetType: string): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
await AssetService.refreshAsset(identifier)
|
await AssetService.refreshAsset(identifier)
|
||||||
ElMessage.success('刷新成功')
|
ElMessage.success('刷新成功')
|
||||||
@@ -363,8 +367,15 @@ export function useAssetInfo() {
|
|||||||
if (identifier) {
|
if (identifier) {
|
||||||
await loadRealtimeStatus(identifier, assetType)
|
await loadRealtimeStatus(identifier, assetType)
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
|
// Check if it's a 429 rate limit error
|
||||||
|
if (error?.response?.status === 429) {
|
||||||
|
ElMessage.warning('操作过于频繁,请30秒后再试')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<AssetSearchCard
|
<AssetSearchCard
|
||||||
ref="assetSearchCardRef"
|
ref="assetSearchCardRef"
|
||||||
:has-card-info="!!cardInfo"
|
:has-card-info="!!cardInfo"
|
||||||
|
:refresh-disabled="refreshDisabled"
|
||||||
@search="handleSearch"
|
@search="handleSearch"
|
||||||
@refresh="handleRefresh"
|
@refresh="handleRefresh"
|
||||||
/>
|
/>
|
||||||
@@ -266,6 +267,9 @@
|
|||||||
await updatePollingStatus(val)
|
await updatePollingStatus(val)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ========== 刷新按钮状态 ==========
|
||||||
|
const refreshDisabled = ref(false)
|
||||||
|
|
||||||
// ========== 事件处理 ==========
|
// ========== 事件处理 ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -301,7 +305,14 @@
|
|||||||
*/
|
*/
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
if (!cardInfo.value) return
|
if (!cardInfo.value) return
|
||||||
await refreshAsset(cardInfo.value.identifier, cardInfo.value.asset_type)
|
const success = await refreshAsset(cardInfo.value.identifier, cardInfo.value.asset_type)
|
||||||
|
if (!success) {
|
||||||
|
// 如果刷新失败(429错误),禁用刷新按钮30秒
|
||||||
|
refreshDisabled.value = true
|
||||||
|
setTimeout(() => {
|
||||||
|
refreshDisabled.value = false
|
||||||
|
}, 30000)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
v-model:filter="cardSearchForm"
|
v-model:filter="cardSearchForm"
|
||||||
:items="cardSearchFormItems"
|
:items="cardSearchFormItems"
|
||||||
label-width="120"
|
label-width="120"
|
||||||
:show-expand="false"
|
show-expand
|
||||||
@reset="handleCardSearchReset"
|
@reset="handleCardSearchReset"
|
||||||
@search="handleCardSearch"
|
@search="handleCardSearch"
|
||||||
></ArtSearchBar>
|
></ArtSearchBar>
|
||||||
@@ -107,6 +107,7 @@
|
|||||||
:pageSize="cardPagination.pageSize"
|
:pageSize="cardPagination.pageSize"
|
||||||
:total="cardPagination.total"
|
:total="cardPagination.total"
|
||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
|
height="40vh"
|
||||||
@size-change="handleCardPageSizeChange"
|
@size-change="handleCardPageSizeChange"
|
||||||
@current-change="handleCardPageChange"
|
@current-change="handleCardPageChange"
|
||||||
@selection-change="handleAvailableCardsSelectionChange"
|
@selection-change="handleAvailableCardsSelectionChange"
|
||||||
@@ -710,14 +711,14 @@
|
|||||||
label: '自然月累计流量(MB)',
|
label: '自然月累计流量(MB)',
|
||||||
width: 180,
|
width: 180,
|
||||||
formatter: (row: EnterpriseCardItem) =>
|
formatter: (row: EnterpriseCardItem) =>
|
||||||
((row as any).current_month_usage_mb)?.toLocaleString() || '0'
|
(row as any).current_month_usage_mb?.toLocaleString() || '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'last_month_total_mb',
|
prop: 'last_month_total_mb',
|
||||||
label: '上月流量总量(MB)',
|
label: '上月流量总量(MB)',
|
||||||
width: 150,
|
width: 150,
|
||||||
formatter: (row: EnterpriseCardItem) =>
|
formatter: (row: EnterpriseCardItem) =>
|
||||||
((row as any).last_month_total_mb)?.toLocaleString() || '0'
|
(row as any).last_month_total_mb?.toLocaleString() || '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'data_usage_mb',
|
prop: 'data_usage_mb',
|
||||||
@@ -737,7 +738,9 @@
|
|||||||
label: '最后实名检查时间',
|
label: '最后实名检查时间',
|
||||||
width: 180,
|
width: 180,
|
||||||
formatter: (row: EnterpriseCardItem) =>
|
formatter: (row: EnterpriseCardItem) =>
|
||||||
(row as any).last_real_name_check_at ? formatDateTime((row as any).last_real_name_check_at) : '-'
|
(row as any).last_real_name_check_at
|
||||||
|
? formatDateTime((row as any).last_real_name_check_at)
|
||||||
|
: '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'enable_polling',
|
prop: 'enable_polling',
|
||||||
|
|||||||
@@ -53,28 +53,28 @@
|
|||||||
:pageSize="pagination.page_size"
|
:pageSize="pagination.page_size"
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
:row-class-name="getRowClassName"
|
:actions="getActions"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
@row-contextmenu="handleRowContextMenu"
|
|
||||||
@cell-mouse-enter="handleCellMouseEnter"
|
|
||||||
@cell-mouse-leave="handleCellMouseLeave"
|
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<el-table-column v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
<el-table-column v-for="col in columns" :key="col.prop || col.type" v-bind="col">
|
||||||
|
<template v-if="col.slots?.default === 'progress'" #default="scope">
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px">
|
||||||
|
<el-progress
|
||||||
|
:percentage="getProgressPercentage(scope.row)"
|
||||||
|
stroke-width="6"
|
||||||
|
:show-text="false"
|
||||||
|
style="flex: 1"
|
||||||
|
/>
|
||||||
|
<span style="font-size: 12px; width: 50px"
|
||||||
|
>{{ getProgressPercentage(scope.row) }}%</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
<!-- 鼠标悬浮提示 -->
|
|
||||||
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
|
||||||
|
|
||||||
<!-- 右键菜单 -->
|
|
||||||
<ArtMenuRight
|
|
||||||
ref="contextMenuRef"
|
|
||||||
:menu-items="contextMenuItems"
|
|
||||||
:menu-width="120"
|
|
||||||
@select="handleContextMenuSelect"
|
|
||||||
/>
|
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<!-- 批量触发弹窗 -->
|
<!-- 批量触发弹窗 -->
|
||||||
@@ -85,91 +85,64 @@
|
|||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
@close="handleBatchDialogClose"
|
@close="handleBatchDialogClose"
|
||||||
>
|
>
|
||||||
<el-form ref="batchFormRef" :model="batchForm" :rules="batchRules" label-width="100">
|
<el-form ref="batchFormRef" :model="batchForm" :rules="batchRules" label-width="120">
|
||||||
<el-form-item label="任务类型" prop="task_type">
|
<div class="card-selection-wrapper">
|
||||||
<el-select
|
<div class="card-search-bar">
|
||||||
v-model="batchForm.task_type"
|
<ArtSearchBar
|
||||||
placeholder="请选择任务类型"
|
v-model:filter="cardSearchForm"
|
||||||
style="width: 100%"
|
:items="cardSearchFormItems"
|
||||||
>
|
label-width="100"
|
||||||
<el-option label="实名认证轮询" value="polling:realname" />
|
show-expand
|
||||||
<el-option label="卡数据轮询" value="polling:carddata" />
|
@reset="handleCardSearchReset"
|
||||||
<el-option label="套餐轮询" value="polling:package" />
|
@search="handleCardSearch"
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="选择卡">
|
|
||||||
<div class="card-selection-wrapper">
|
|
||||||
<div class="card-search-bar">
|
|
||||||
<el-input
|
|
||||||
v-model="cardSearchKeyword"
|
|
||||||
placeholder="搜索ICCID/MSISDN"
|
|
||||||
clearable
|
|
||||||
style="width: 200px"
|
|
||||||
@input="handleCardSearchInput"
|
|
||||||
/>
|
|
||||||
<el-select
|
|
||||||
v-model="cardSearchForm.card_status"
|
|
||||||
placeholder="卡状态"
|
|
||||||
clearable
|
|
||||||
style="width: 150px; margin-left: 10px"
|
|
||||||
@change="handleCardSearch"
|
|
||||||
>
|
|
||||||
<el-option label="全部" value="" />
|
|
||||||
<el-option label="已激活" :value="1" />
|
|
||||||
<el-option label="未激活" :value="0" />
|
|
||||||
<el-option label="停机" :value="2" />
|
|
||||||
<el-option label="异常" :value="3" />
|
|
||||||
</el-select>
|
|
||||||
<el-button type="primary" style="margin-left: 10px" @click="handleCardSearch">
|
|
||||||
搜索
|
|
||||||
</el-button>
|
|
||||||
<el-button @click="handleCardSearchReset">重置</el-button>
|
|
||||||
</div>
|
|
||||||
<div class="card-table-info">
|
|
||||||
<span>已选择 {{ selectedBatchCards.length }} 张卡</span>
|
|
||||||
<span style="margin-left: 20px; color: #909399">
|
|
||||||
最多选择1000张卡
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<el-table
|
|
||||||
ref="batchCardTableRef"
|
|
||||||
:data="batchCardList"
|
|
||||||
:loading="batchCardLoading"
|
|
||||||
row-key="id"
|
|
||||||
height="300"
|
|
||||||
@selection-change="handleBatchCardSelectionChange"
|
|
||||||
border
|
|
||||||
style="margin-top: 10px"
|
|
||||||
>
|
|
||||||
<el-table-column type="selection" width="55" />
|
|
||||||
<el-table-column prop="iccid" label="ICCID" min-width="180" show-overflow-tooltip />
|
|
||||||
<el-table-column prop="msisdn" label="MSISDN" min-width="120" show-overflow-tooltip />
|
|
||||||
<el-table-column prop="carrier_name" label="运营商" width="100" />
|
|
||||||
<el-table-column prop="status" label="状态" width="100">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-tag :type="getCardStatusType(row.status)" size="small">
|
|
||||||
{{ getCardStatusName(row.status) }}
|
|
||||||
</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
<el-pagination
|
|
||||||
v-model:current-page="batchCardPagination.page"
|
|
||||||
v-model:page-size="batchCardPagination.pageSize"
|
|
||||||
:total="batchCardPagination.total"
|
|
||||||
:page-sizes="[10, 20, 50, 100]"
|
|
||||||
layout="total, sizes, prev, pager, next"
|
|
||||||
style="margin-top: 10px"
|
|
||||||
small
|
|
||||||
@size-change="handleBatchCardPageSizeChange"
|
|
||||||
@current-change="handleBatchCardPageChange"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
<div class="card-table-header">
|
||||||
|
<span>已选择 {{ selectedBatchCards.length }} 张卡</span>
|
||||||
|
<el-form-item
|
||||||
|
label="任务类型"
|
||||||
|
prop="task_type"
|
||||||
|
label-width="80"
|
||||||
|
style="margin-bottom: 0; width: 300px"
|
||||||
|
>
|
||||||
|
<el-select
|
||||||
|
v-model="batchForm.task_type"
|
||||||
|
placeholder="请选择任务类型"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option label="实名认证轮询" value="polling:realname" />
|
||||||
|
<el-option label="卡数据轮询" value="polling:carddata" />
|
||||||
|
<el-option label="套餐轮询" value="polling:package" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<ArtTable
|
||||||
|
ref="batchCardTableRef"
|
||||||
|
row-key="id"
|
||||||
|
:loading="batchCardLoading"
|
||||||
|
:data="batchCardList"
|
||||||
|
:currentPage="batchCardPagination.page"
|
||||||
|
:pageSize="batchCardPagination.pageSize"
|
||||||
|
:total="batchCardPagination.total"
|
||||||
|
height="40vh"
|
||||||
|
@size-change="handleBatchCardPageSizeChange"
|
||||||
|
@current-change="handleBatchCardPageChange"
|
||||||
|
@selection-change="handleBatchCardSelectionChange"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<el-table-column type="selection" width="55" />
|
||||||
|
<el-table-column v-for="col in batchCardColumns" :key="col.prop" v-bind="col" />
|
||||||
|
</template>
|
||||||
|
</ArtTable>
|
||||||
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="batchDialogVisible = false">取消</el-button>
|
<el-button @click="batchDialogVisible = false">取消</el-button>
|
||||||
<el-button type="primary" @click="handleBatchSubmit" :disabled="selectedBatchCards.length === 0">
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="handleBatchSubmit"
|
||||||
|
:disabled="selectedBatchCards.length === 0"
|
||||||
|
>
|
||||||
确定
|
确定
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -351,8 +324,21 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, onMounted, nextTick, h } from 'vue'
|
import { ref, reactive, computed, onMounted, nextTick, h } from 'vue'
|
||||||
import { ElMessage, ElMessageBox, ElTag, type FormInstance, type FormRules } from 'element-plus'
|
import {
|
||||||
import { ManualTriggerService, CarrierService, ShopService, CardService } from '@/api/modules'
|
ElMessage,
|
||||||
|
ElMessageBox,
|
||||||
|
ElTag,
|
||||||
|
ElProgress,
|
||||||
|
type FormInstance,
|
||||||
|
type FormRules
|
||||||
|
} from 'element-plus'
|
||||||
|
import {
|
||||||
|
ManualTriggerService,
|
||||||
|
CarrierService,
|
||||||
|
ShopService,
|
||||||
|
CardService,
|
||||||
|
PackageSeriesService
|
||||||
|
} from '@/api/modules'
|
||||||
import type {
|
import type {
|
||||||
ManualTriggerLog,
|
ManualTriggerLog,
|
||||||
ManualTriggerHistoryQueryParams,
|
ManualTriggerHistoryQueryParams,
|
||||||
@@ -362,15 +348,12 @@
|
|||||||
} from '@/types/api'
|
} from '@/types/api'
|
||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
const { hasAuth } = useAuth()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const tableData = ref<ManualTriggerLog[]>([])
|
const tableData = ref<ManualTriggerLog[]>([])
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
const contextMenuRef = ref()
|
|
||||||
const currentClickRow = ref<ManualTriggerLog | null>(null)
|
|
||||||
|
|
||||||
const batchDialogVisible = ref(false)
|
const batchDialogVisible = ref(false)
|
||||||
const conditionDialogVisible = ref(false)
|
const conditionDialogVisible = ref(false)
|
||||||
@@ -409,16 +392,265 @@
|
|||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
total: 0
|
total: 0
|
||||||
})
|
})
|
||||||
const cardSearchKeyword = ref('')
|
|
||||||
const cardSearchForm = reactive({
|
|
||||||
keyword: '',
|
|
||||||
card_status: undefined as number | undefined
|
|
||||||
})
|
|
||||||
const selectedBatchCards = ref<any[]>([])
|
const selectedBatchCards = ref<any[]>([])
|
||||||
const batchCardTableRef = ref()
|
const batchCardTableRef = ref()
|
||||||
|
|
||||||
// 搜索防抖定时器
|
// 远程搜索相关
|
||||||
let cardSearchTimer: any = null
|
const carrierLoading = ref(false)
|
||||||
|
const searchCarrierOptions = ref<any[]>([])
|
||||||
|
const searchSeriesOptions = ref<any[]>([])
|
||||||
|
const seriesLoading = ref(false)
|
||||||
|
|
||||||
|
// 卡搜索表单初始值
|
||||||
|
const initialCardSearchState = {
|
||||||
|
iccid: '',
|
||||||
|
carrier_name: '',
|
||||||
|
status: undefined,
|
||||||
|
virtual_no: '',
|
||||||
|
msisdn: '',
|
||||||
|
device_virtual_no: '',
|
||||||
|
series_id: undefined,
|
||||||
|
is_distributed: undefined,
|
||||||
|
is_replaced: undefined,
|
||||||
|
iccid_start: '',
|
||||||
|
iccid_end: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const cardSearchForm = reactive({ ...initialCardSearchState })
|
||||||
|
|
||||||
|
// 卡列表搜索表单配置
|
||||||
|
const cardSearchFormItems = computed<SearchFormItem[]>(() => [
|
||||||
|
{
|
||||||
|
label: 'ICCID',
|
||||||
|
prop: 'iccid',
|
||||||
|
type: 'input',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '请输入ICCID'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '运营商名称',
|
||||||
|
prop: 'carrier_name',
|
||||||
|
type: 'select',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
filterable: true,
|
||||||
|
remote: true,
|
||||||
|
remoteMethod: handleSearchCarrier,
|
||||||
|
loading: carrierLoading.value,
|
||||||
|
placeholder: '请选择或搜索运营商名称'
|
||||||
|
},
|
||||||
|
options: () =>
|
||||||
|
searchCarrierOptions.value.map((c) => ({
|
||||||
|
label: c.carrier_name,
|
||||||
|
value: c.carrier_name
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '状态',
|
||||||
|
prop: 'status',
|
||||||
|
type: 'select',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '全部'
|
||||||
|
},
|
||||||
|
options: () => [
|
||||||
|
{ label: '在库', value: 1 },
|
||||||
|
{ label: '已分销', value: 2 },
|
||||||
|
{ label: '已激活', value: 3 },
|
||||||
|
{ label: '已停用', value: 4 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '卡虚拟号',
|
||||||
|
prop: 'virtual_no',
|
||||||
|
type: 'input',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '请输入卡虚拟号'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MSISDN',
|
||||||
|
prop: 'msisdn',
|
||||||
|
type: 'input',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '请输入MSISDN'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '绑定设备虚拟号',
|
||||||
|
prop: 'device_virtual_no',
|
||||||
|
type: 'input',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '请输入绑定设备虚拟号'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '套餐系列',
|
||||||
|
prop: '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: 'is_standalone',
|
||||||
|
type: 'select',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '全部'
|
||||||
|
},
|
||||||
|
options: () => [
|
||||||
|
{ label: '未绑定设备', value: true },
|
||||||
|
{ label: '已绑定设备', value: false }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ICCID起始号',
|
||||||
|
prop: 'iccid_start',
|
||||||
|
type: 'input',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '请输入ICCID起始号'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ICCID结束号',
|
||||||
|
prop: 'iccid_end',
|
||||||
|
type: 'input',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '请输入ICCID结束号'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// 卡列表列配置
|
||||||
|
const batchCardColumns = computed(() => [
|
||||||
|
{
|
||||||
|
prop: 'iccid',
|
||||||
|
label: 'ICCID',
|
||||||
|
minWidth: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'msisdn',
|
||||||
|
label: 'MSISDN',
|
||||||
|
width: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'virtual_no',
|
||||||
|
label: '卡虚拟号',
|
||||||
|
width: 180,
|
||||||
|
showOverflowTooltip: true,
|
||||||
|
formatter: (row: any) => row.virtual_no || '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'device_virtual_no',
|
||||||
|
label: '绑定设备虚拟号',
|
||||||
|
width: 180,
|
||||||
|
showOverflowTooltip: true,
|
||||||
|
formatter: (row: any) => row.device_virtual_no || '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'card_category',
|
||||||
|
label: '卡业务类型',
|
||||||
|
width: 100,
|
||||||
|
formatter: (row: any) => getCardCategoryText(row.card_category)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'carrier_name',
|
||||||
|
label: '运营商',
|
||||||
|
width: 150,
|
||||||
|
showOverflowTooltip: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'shop_name',
|
||||||
|
label: '店铺名称',
|
||||||
|
minWidth: 150,
|
||||||
|
formatter: (row: any) => row.shop_name || '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'series_name',
|
||||||
|
label: '套餐系列',
|
||||||
|
width: 150,
|
||||||
|
formatter: (row: any) => row.series_name || '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'status_name',
|
||||||
|
label: '状态',
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'activation_status',
|
||||||
|
label: '激活状态',
|
||||||
|
width: 100,
|
||||||
|
formatter: (row: any) => {
|
||||||
|
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: any) => {
|
||||||
|
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: any) => {
|
||||||
|
const type = row.real_name_status === 1 ? 'success' : 'warning'
|
||||||
|
const text = row.real_name_status === 1 ? '已实名' : '未实名'
|
||||||
|
return h(ElTag, { type }, () => text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
// 获取批量触发弹窗中的卡列表
|
// 获取批量触发弹窗中的卡列表
|
||||||
const getBatchCardList = async () => {
|
const getBatchCardList = async () => {
|
||||||
@@ -446,30 +678,50 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 卡搜索输入(防抖)
|
// 搜索运营商
|
||||||
const handleCardSearchInput = () => {
|
const handleSearchCarrier = async (query: string) => {
|
||||||
if (cardSearchTimer) {
|
try {
|
||||||
clearTimeout(cardSearchTimer)
|
const params: any = { page: 1, page_size: 20 }
|
||||||
|
if (query) {
|
||||||
|
params.carrier_name = query
|
||||||
|
}
|
||||||
|
const res = await CarrierService.getCarriers(params)
|
||||||
|
if (res.code === 0) {
|
||||||
|
searchCarrierOptions.value = res.data.items || []
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索套餐系列
|
||||||
|
const handleSearchSeries = async (query: string) => {
|
||||||
|
seriesLoading.value = true
|
||||||
|
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)
|
||||||
|
} finally {
|
||||||
|
seriesLoading.value = false
|
||||||
}
|
}
|
||||||
cardSearchTimer = setTimeout(() => {
|
|
||||||
cardSearchForm.keyword = cardSearchKeyword.value
|
|
||||||
batchCardPagination.page = 1
|
|
||||||
getBatchCardList()
|
|
||||||
}, 300)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 卡搜索
|
// 卡搜索
|
||||||
const handleCardSearch = () => {
|
const handleCardSearch = () => {
|
||||||
cardSearchForm.keyword = cardSearchKeyword.value
|
|
||||||
batchCardPagination.page = 1
|
batchCardPagination.page = 1
|
||||||
getBatchCardList()
|
getBatchCardList()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 卡搜索重置
|
// 卡搜索重置
|
||||||
const handleCardSearchReset = () => {
|
const handleCardSearchReset = () => {
|
||||||
cardSearchKeyword.value = ''
|
Object.assign(cardSearchForm, { ...initialCardSearchState })
|
||||||
cardSearchForm.keyword = ''
|
|
||||||
cardSearchForm.card_status = undefined
|
|
||||||
batchCardPagination.page = 1
|
batchCardPagination.page = 1
|
||||||
getBatchCardList()
|
getBatchCardList()
|
||||||
}
|
}
|
||||||
@@ -500,9 +752,7 @@
|
|||||||
// 关闭批量触发弹窗
|
// 关闭批量触发弹窗
|
||||||
const handleBatchDialogClose = () => {
|
const handleBatchDialogClose = () => {
|
||||||
selectedBatchCards.value = []
|
selectedBatchCards.value = []
|
||||||
cardSearchKeyword.value = ''
|
Object.assign(cardSearchForm, { ...initialCardSearchState })
|
||||||
cardSearchForm.keyword = ''
|
|
||||||
cardSearchForm.card_status = undefined
|
|
||||||
batchCardPagination.page = 1
|
batchCardPagination.page = 1
|
||||||
batchCardPagination.pageSize = 20
|
batchCardPagination.pageSize = 20
|
||||||
}
|
}
|
||||||
@@ -529,6 +779,15 @@
|
|||||||
return nameMap[status] || '未知'
|
return nameMap[status] || '未知'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取卡业务类型文本
|
||||||
|
const getCardCategoryText = (category: string) => {
|
||||||
|
const categoryMap: Record<string, string> = {
|
||||||
|
normal: '普通卡',
|
||||||
|
industry: '行业卡'
|
||||||
|
}
|
||||||
|
return categoryMap[category] || category
|
||||||
|
}
|
||||||
|
|
||||||
// 条件触发表单
|
// 条件触发表单
|
||||||
const conditionForm = reactive<ConditionManualTriggerRequest>({
|
const conditionForm = reactive<ConditionManualTriggerRequest>({
|
||||||
task_type: '' as TaskType | '',
|
task_type: '' as TaskType | '',
|
||||||
@@ -561,14 +820,6 @@
|
|||||||
task_type: [{ required: true, message: '请选择任务类型', trigger: 'change' }]
|
task_type: [{ required: true, message: '请选择任务类型', trigger: 'change' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
|
||||||
showContextMenuHint,
|
|
||||||
hintPosition,
|
|
||||||
getRowClassName,
|
|
||||||
handleCellMouseEnter,
|
|
||||||
handleCellMouseLeave
|
|
||||||
} = useTableContextMenu()
|
|
||||||
|
|
||||||
// 搜索表单配置
|
// 搜索表单配置
|
||||||
const searchFormItems = computed<SearchFormItem[]>(() => [
|
const searchFormItems = computed<SearchFormItem[]>(() => [
|
||||||
{
|
{
|
||||||
@@ -615,29 +866,14 @@
|
|||||||
minWidth: 120
|
minWidth: 120
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'status',
|
prop: 'status_name',
|
||||||
label: '状态',
|
label: '状态',
|
||||||
minWidth: 100,
|
minWidth: 100
|
||||||
slots: {
|
|
||||||
default: ({ row }: any) =>
|
|
||||||
h(ElTag, { type: getStatusTag(row.status) }, () => row.status_name)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '进度',
|
label: '进度',
|
||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
slots: {
|
slots: { default: 'progress' }
|
||||||
default: ({ row }: any) =>
|
|
||||||
h('div', { style: 'display: flex; align-items: center; gap: 8px;' }, [
|
|
||||||
h('span', { style: 'font-size: 12px;' }, `${row.processed_count}/${row.total_count}`),
|
|
||||||
h('el-progress', {
|
|
||||||
percentage: getProgressPercentage(row),
|
|
||||||
'stroke-width': 6,
|
|
||||||
'show-text': false,
|
|
||||||
style: 'flex: 1;'
|
|
||||||
})
|
|
||||||
])
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'success_count',
|
prop: 'success_count',
|
||||||
@@ -671,23 +907,6 @@
|
|||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
const contextMenuItems = computed<MenuItemType[]>(() => {
|
|
||||||
const items: MenuItemType[] = [{ label: '刷新', key: 'refresh' }]
|
|
||||||
|
|
||||||
if (
|
|
||||||
currentClickRow.value &&
|
|
||||||
(currentClickRow.value.status === 'pending' || currentClickRow.value.status === 'processing')
|
|
||||||
) {
|
|
||||||
items.push({
|
|
||||||
label: '取消任务',
|
|
||||||
key: 'cancel',
|
|
||||||
permission: 'polling_manual_trigger_cancel'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return items
|
|
||||||
})
|
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
@@ -719,6 +938,27 @@
|
|||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getActions = (row: ManualTriggerLog) => {
|
||||||
|
const actions: any[] = []
|
||||||
|
if (row.status === 'pending' || row.status === 'processing') {
|
||||||
|
if (hasAuth('polling_manual:trigger_cancel')) {
|
||||||
|
actions.push({
|
||||||
|
label: '取消任务',
|
||||||
|
type: 'warning',
|
||||||
|
handler: () => handleCancelTask(row)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasAuth('polling_manual:refresh')) {
|
||||||
|
actions.push({
|
||||||
|
label: '刷新',
|
||||||
|
type: 'primary',
|
||||||
|
handler: () => handleRefresh()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return actions
|
||||||
|
}
|
||||||
|
|
||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
@@ -740,12 +980,12 @@
|
|||||||
selectedBatchCards.value = []
|
selectedBatchCards.value = []
|
||||||
batchCardPagination.page = 1
|
batchCardPagination.page = 1
|
||||||
batchCardPagination.pageSize = 20
|
batchCardPagination.pageSize = 20
|
||||||
cardSearchKeyword.value = ''
|
Object.assign(cardSearchForm, { ...initialCardSearchState })
|
||||||
cardSearchForm.keyword = ''
|
|
||||||
cardSearchForm.card_status = undefined
|
|
||||||
batchDialogVisible.value = true
|
batchDialogVisible.value = true
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
batchFormRef.value?.clearValidate()
|
batchFormRef.value?.clearValidate()
|
||||||
|
handleSearchCarrier('')
|
||||||
|
handleSearchSeries('')
|
||||||
getBatchCardList()
|
getBatchCardList()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -876,26 +1116,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRowContextMenu = (row: ManualTriggerLog, _column: any, event: MouseEvent) => {
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
currentClickRow.value = row
|
|
||||||
contextMenuRef.value?.show(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleContextMenuSelect = (item: MenuItemType) => {
|
|
||||||
if (!currentClickRow.value) return
|
|
||||||
|
|
||||||
switch (item.key) {
|
|
||||||
case 'refresh':
|
|
||||||
loadData()
|
|
||||||
break
|
|
||||||
case 'cancel':
|
|
||||||
handleCancelTask(currentClickRow.value)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载运营商列表
|
// 加载运营商列表
|
||||||
const loadCarriers = async (carrierName?: string) => {
|
const loadCarriers = async (carrierName?: string) => {
|
||||||
try {
|
try {
|
||||||
@@ -965,12 +1185,14 @@
|
|||||||
|
|
||||||
.card-selection-wrapper {
|
.card-selection-wrapper {
|
||||||
.card-search-bar {
|
.card-search-bar {
|
||||||
display: flex;
|
display: block;
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-table-info {
|
.card-table-header {
|
||||||
margin-top: 10px;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 10px 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user