This commit is contained in:
@@ -194,6 +194,11 @@
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="实名认证策略" width="120">
|
||||
<template #default="scope">
|
||||
{{ getRealnamePolicyName(scope.row.realname_policy) }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="实名时间" width="180">
|
||||
<template #default="scope">
|
||||
{{ scope.row.real_name_at ? formatDateTime(scope.row.real_name_at) : '-' }}
|
||||
@@ -229,7 +234,7 @@
|
||||
|
||||
<!-- 设备实时信息 -->
|
||||
<ElDivider content-position="left">设备实时信息</ElDivider>
|
||||
<div v-if="deviceRealtime" style="margin-top: 16px">
|
||||
<div v-if="deviceRealtime" v-loading="realtimeLoading" style="margin-top: 16px">
|
||||
<ElDescriptions :column="4" border>
|
||||
<!-- 基本状态 -->
|
||||
<ElDescriptionsItem label="在线状态">
|
||||
@@ -522,9 +527,12 @@
|
||||
cardInfo: AssetInfo
|
||||
deviceRealtime: DeviceRealtimeInfo | null
|
||||
pollingEnabled: boolean
|
||||
realtimeLoading?: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
realtimeLoading: false
|
||||
})
|
||||
|
||||
const { hasAuth } = useAuth()
|
||||
|
||||
|
||||
@@ -2,66 +2,9 @@
|
||||
<ElCard shadow="never" class="info-card current-package-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<div class="card-header-left">
|
||||
<span class="header-title">当前生效套餐</span>
|
||||
<ElTag
|
||||
v-if="currentPackage"
|
||||
:type="getPackageStatusTagType(currentPackage.status)"
|
||||
size="small"
|
||||
>
|
||||
{{ currentPackage?.status_name || '-' }}
|
||||
</ElTag>
|
||||
</div>
|
||||
|
||||
<!-- 中间:流量进度 -->
|
||||
<div v-if="currentPackage && !props.boundDeviceId" class="header-progress">
|
||||
<div class="header-progress-info">
|
||||
<span class="progress-title">流量</span>
|
||||
<span class="progress-text">
|
||||
<template v-if="isAdminOrPlatform">
|
||||
总量 {{ formatDataSize(currentPackage.data_limit_mb || currentPackage.package_total_data || 0) }} / 实际可用
|
||||
{{ formatDataSize(currentPackage.enable_virtual_data ? (currentPackage.virtual_limit_mb || 0) : (currentPackage.data_limit_mb || currentPackage.package_total_data || 0)) }} / 剩余可用
|
||||
{{ formatDataSize(currentPackage.enable_virtual_data ? (currentPackage.virtual_remain_mb || 0) : ((currentPackage.data_limit_mb || currentPackage.package_total_data || 0) - (currentPackage.data_usage_mb || 0))) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
总量 {{ formatDataSize(currentPackage.data_limit_mb || currentPackage.package_total_data || 0) }} / 剩余可用
|
||||
{{ formatDataSize(currentPackage.enable_virtual_data ? (currentPackage.virtual_remain_mb || 0) : ((currentPackage.data_limit_mb || currentPackage.package_total_data || 0) - (currentPackage.data_usage_mb || 0))) }}
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<ElProgress
|
||||
:percentage="
|
||||
currentPackage.enable_virtual_data
|
||||
? getUsageProgress(
|
||||
currentPackage.virtual_data_used || 0,
|
||||
currentPackage.virtual_data_total || 0
|
||||
)
|
||||
: getUsageProgress(
|
||||
currentPackage.real_data_used || 0,
|
||||
currentPackage.real_data_total || 0
|
||||
)
|
||||
"
|
||||
:color="
|
||||
currentPackage.enable_virtual_data
|
||||
? getProgressColorByPercentage(
|
||||
getUsageProgress(
|
||||
currentPackage.virtual_data_used || 0,
|
||||
currentPackage.virtual_data_total || 0
|
||||
)
|
||||
)
|
||||
: getProgressColorByPercentage(
|
||||
getUsageProgress(
|
||||
currentPackage.real_data_used || 0,
|
||||
currentPackage.real_data_total || 0
|
||||
)
|
||||
)
|
||||
"
|
||||
:stroke-width="10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="currentPackage" class="card-header-right">
|
||||
<ElButton type="primary" size="small" @click="handleShowRecharge"> 套餐充值 </ElButton>
|
||||
<span class="header-title">当前生效套餐</span>
|
||||
<div class="header-actions">
|
||||
<slot name="header-actions" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -86,33 +29,80 @@
|
||||
<!-- 显示错误信息 -->
|
||||
<ElAlert v-else-if="errorMsg" :title="errorMsg" type="warning" :closable="false" />
|
||||
|
||||
<!-- 显示套餐详情 -->
|
||||
<ElDescriptions v-else-if="currentPackage" :column="3" border>
|
||||
<ElDescriptionsItem label="套餐名称">
|
||||
{{ currentPackage.package_name || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="套餐价格">
|
||||
{{ formatAmount(currentPackage.package_price) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="套餐类型">
|
||||
{{ getPackageTypeName(currentPackage.package_type) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem
|
||||
v-if="currentPackage.enable_virtual_data && isAdminOrPlatform"
|
||||
label="虚流量比例"
|
||||
>
|
||||
{{ currentPackage.virtual_ratio || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem v-if="currentPackage.duration_days" label="套餐时长" :span="3">
|
||||
{{ currentPackage.duration_days }}天
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="开始时间">
|
||||
{{ formatDateTime(currentPackage.start_time) || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="到期时间">
|
||||
{{ formatDateTime(currentPackage.expire_time) || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
<template v-else-if="currentPackage">
|
||||
<!-- 流量使用情况 - 横向进度条卡片 -->
|
||||
<div class="flow-progress-card">
|
||||
<div class="flow-stats-row">
|
||||
<div class="flow-stat-item">
|
||||
<span class="flow-stat-label">总量</span>
|
||||
<span class="flow-stat-value">
|
||||
{{ formatDataSize(currentPackage.data_limit_mb || currentPackage.package_total_data || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="isAdminOrPlatform && currentPackage.enable_virtual_data" class="flow-stat-item">
|
||||
<span class="flow-stat-label">实际可用</span>
|
||||
<span class="flow-stat-value">
|
||||
{{ formatDataSize(currentPackage.virtual_limit_mb || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flow-stat-item">
|
||||
<span class="flow-stat-label">剩余可用</span>
|
||||
<span class="flow-stat-value">
|
||||
{{
|
||||
formatDataSize(
|
||||
currentPackage.enable_virtual_data
|
||||
? currentPackage.virtual_remain_mb || 0
|
||||
: (currentPackage.data_limit_mb || currentPackage.package_total_data || 0) - (currentPackage.data_usage_mb || 0)
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flow-progress-bar">
|
||||
<ElProgress
|
||||
:percentage="
|
||||
currentPackage.enable_virtual_data
|
||||
? getUsageProgress(currentPackage.virtual_data_used || 0, currentPackage.virtual_data_total || 0)
|
||||
: getUsageProgress(currentPackage.real_data_used || 0, currentPackage.real_data_total || 0)
|
||||
"
|
||||
:color="
|
||||
currentPackage.enable_virtual_data
|
||||
? getProgressColorByPercentage(getUsageProgress(currentPackage.virtual_data_used || 0, currentPackage.virtual_data_total || 0))
|
||||
: getProgressColorByPercentage(getUsageProgress(currentPackage.real_data_used || 0, currentPackage.real_data_total || 0))
|
||||
"
|
||||
:stroke-width="14"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 套餐详情表格 -->
|
||||
<ElDescriptions :column="3" border class="package-info-table">
|
||||
<ElDescriptionsItem label="套餐名称">
|
||||
{{ currentPackage.package_name || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="套餐价格">
|
||||
{{ formatAmount(currentPackage.paid_amount || currentPackage.package_price || 0) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="套餐类型">
|
||||
{{ getPackageTypeName(currentPackage.package_type) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem v-if="isAdminOrPlatform && currentPackage.enable_virtual_data" label="虚流量比例">
|
||||
{{ currentPackage.virtual_ratio || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="开始时间">
|
||||
{{ formatDateTime(currentPackage.start_time) || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="到期时间">
|
||||
{{ formatDateTime(currentPackage.expire_time) || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="购买时间">
|
||||
{{ formatDateTime(currentPackage.created_at) || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem v-if="currentPackage.duration_days" label="套餐时长">
|
||||
{{ currentPackage.duration_days }}天
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
</template>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<ElEmpty v-else-if="!loading" :description="errorMsg || '暂无当前生效套餐'" :image-size="100" />
|
||||
@@ -122,7 +112,6 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ElCard,
|
||||
ElTag,
|
||||
ElButton,
|
||||
ElDescriptions,
|
||||
ElDescriptionsItem,
|
||||
@@ -136,21 +125,19 @@
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import type { PackageInfo } from '../types'
|
||||
|
||||
// Use formatters
|
||||
const {
|
||||
formatAmount,
|
||||
formatDataSize,
|
||||
getPackageStatusTagType,
|
||||
getUsageProgress,
|
||||
getProgressColorByPercentage
|
||||
} = useAssetFormatters()
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { info: userInfo } = storeToRefs(userStore)
|
||||
const isSuperAdmin = computed(() => userInfo.value.user_type === 1)
|
||||
const isAdminOrPlatform = computed(() => userInfo.value.user_type === 1 || userInfo.value.user_type === 2)
|
||||
const isAdminOrPlatform = computed(
|
||||
() => userInfo.value.user_type === 1 || userInfo.value.user_type === 2
|
||||
)
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
currentPackage: PackageInfo | null
|
||||
loading?: boolean
|
||||
@@ -168,18 +155,11 @@
|
||||
boundDeviceName: ''
|
||||
})
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
showRecharge: []
|
||||
navigateToDevice: [deviceNo: string]
|
||||
}>()
|
||||
|
||||
// Methods
|
||||
const handleShowRecharge = () => {
|
||||
emit('showRecharge')
|
||||
}
|
||||
|
||||
// 套餐类型名称
|
||||
const getPackageTypeName = (type?: string) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
formal: '正式套餐',
|
||||
@@ -195,52 +175,49 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
.card-header-left {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
|
||||
.header-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header-right {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
.header-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.header-progress {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 0 24px;
|
||||
.flow-progress-card {
|
||||
padding: 20px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.header-progress-info {
|
||||
.flow-stats-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
margin-bottom: 6px;
|
||||
gap: 32px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.progress-title {
|
||||
flex-shrink: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
.flow-stat-item {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
|
||||
.progress-text {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
.flow-stat-label {
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.flow-stat-value {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flow-progress-bar {
|
||||
padding: 0 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.package-info-table {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>套餐列表</span>
|
||||
<div class="card-header-right">
|
||||
<ElButton type="primary" @click="handleShowRecharge">套餐充值</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="package-table-wrapper">
|
||||
<div v-loading="loading" class="package-table-wrapper">
|
||||
<!-- 绑定设备提示 -->
|
||||
<ElEmpty v-if="props.boundDeviceId" :image-size="100">
|
||||
<template #description>
|
||||
@@ -36,7 +39,7 @@
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="套餐价格" width="120">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.package_price || 0) }}
|
||||
{{ formatAmount(scope.row.paid_amount || scope.row.package_price || 0) }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn prop="status_name" label="套餐状态" width="100">
|
||||
@@ -104,6 +107,11 @@
|
||||
{{ formatDateTime(scope.row.expires_at) }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn prop="created_at" label="创建时间" width="170" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ formatDateTime(scope.row.created_at) }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</ElTable>
|
||||
<ElPagination
|
||||
v-model:current-page="pagination.page"
|
||||
@@ -152,6 +160,7 @@
|
||||
pageSize?: number
|
||||
boundDeviceId?: number
|
||||
boundDeviceNo?: string
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -159,12 +168,14 @@
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
boundDeviceId: undefined,
|
||||
boundDeviceNo: ''
|
||||
boundDeviceNo: '',
|
||||
loading: false
|
||||
})
|
||||
|
||||
// Emits
|
||||
interface Emits {
|
||||
(e: 'showDailyRecords', payload: { packageRow: PackageInfo }): void
|
||||
(e: 'showRecharge'): void
|
||||
(e: 'page-change', page: number): void
|
||||
(e: 'size-change', size: number): void
|
||||
(e: 'navigateToDevice', deviceNo: string): void
|
||||
@@ -212,6 +223,10 @@
|
||||
emit('showDailyRecords', { packageRow })
|
||||
}
|
||||
|
||||
const handleShowRecharge = () => {
|
||||
emit('showRecharge')
|
||||
}
|
||||
|
||||
// 页码变化
|
||||
const handlePageChange = (page: number) => {
|
||||
emit('page-change', page)
|
||||
@@ -229,6 +244,13 @@
|
||||
width: 100%;
|
||||
overflow: hidden; // 防止内容溢出
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.package-table-wrapper {
|
||||
.table-scroll-container {
|
||||
overflow-x: auto;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ElCard shadow="never" class="info-card wallet-info">
|
||||
<ElCard shadow="never" class="info-card wallet-info" v-loading="walletLoading">
|
||||
<template #header>
|
||||
<div class="card-header wallet-header">
|
||||
<div class="header-left">
|
||||
|
||||
@@ -29,6 +29,8 @@ export function useAssetInfo() {
|
||||
const packageList = ref<AssetPackageUsageRecord[]>([])
|
||||
const walletInfo = ref<AssetWalletResponse | null>(null)
|
||||
const walletLoading = ref(false)
|
||||
const packageLoading = ref(false)
|
||||
const realtimeLoading = ref(false)
|
||||
|
||||
/**
|
||||
* 获取资产详情
|
||||
@@ -142,6 +144,7 @@ export function useAssetInfo() {
|
||||
*/
|
||||
const loadPackageList = async (identifier: string, page: number = 1, pageSize: number = 10) => {
|
||||
try {
|
||||
packageLoading.value = true
|
||||
const response = await AssetService.getAssetPackages(identifier, {
|
||||
page,
|
||||
page_size: pageSize
|
||||
@@ -161,6 +164,8 @@ export function useAssetInfo() {
|
||||
} catch (error) {
|
||||
console.error('获取套餐列表失败:', error)
|
||||
return { items: [], total: 0, page: 1, pageSize }
|
||||
} finally {
|
||||
packageLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,23 +178,17 @@ export function useAssetInfo() {
|
||||
currentPackageLoading.value = true
|
||||
currentPackageErrorMsg.value = ''
|
||||
|
||||
const [currentPkgResponse, packageListResponse] = await Promise.all([
|
||||
AssetService.getCurrentPackage(identifier),
|
||||
AssetService.getAssetPackages(identifier, { page: 1, page_size: 50 })
|
||||
])
|
||||
const response = await AssetService.getCurrentPackage(identifier)
|
||||
|
||||
const activePackage = packageListResponse.data?.items?.find((p: any) => p.status === 1)
|
||||
const activePaidAmount = activePackage?.paid_amount || 0
|
||||
|
||||
if (currentPkgResponse.code === 0) {
|
||||
if (currentPkgResponse.data) {
|
||||
const pkg = currentPkgResponse.data
|
||||
if (response.code === 0) {
|
||||
if (response.data) {
|
||||
const pkg = response.data
|
||||
|
||||
currentPackage.value = {
|
||||
id: pkg.package_usage_id || 0,
|
||||
package_id: pkg.package_id || 0,
|
||||
package_name: pkg.package_name || '',
|
||||
package_price: activePaidAmount,
|
||||
paid_amount: pkg.paid_amount || 0,
|
||||
package_total_data: pkg.data_limit_mb || 0,
|
||||
real_data_used: pkg.data_usage_mb || 0,
|
||||
real_data_remaining: (pkg.data_limit_mb || 0) - (pkg.data_usage_mb || 0),
|
||||
@@ -208,7 +207,8 @@ export function useAssetInfo() {
|
||||
data_limit_mb: pkg.data_limit_mb,
|
||||
virtual_limit_mb: pkg.virtual_limit_mb,
|
||||
virtual_remain_mb: pkg.virtual_remain_mb,
|
||||
data_usage_mb: pkg.data_usage_mb
|
||||
data_usage_mb: pkg.data_usage_mb,
|
||||
created_at: pkg.created_at || ''
|
||||
}
|
||||
|
||||
// 同时更新 cardInfo 中的流量显示信息(根据是否启用虚流量选择数据源)
|
||||
@@ -260,6 +260,7 @@ export function useAssetInfo() {
|
||||
*/
|
||||
const loadRealtimeStatus = async (identifier: string, assetType: string) => {
|
||||
try {
|
||||
realtimeLoading.value = true
|
||||
const response = await AssetService.getRealtimeStatus(identifier)
|
||||
if (response.code === 0 && response.data) {
|
||||
const data = response.data
|
||||
@@ -323,6 +324,8 @@ export function useAssetInfo() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取实时状态失败:', error)
|
||||
} finally {
|
||||
realtimeLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,8 +380,10 @@ export function useAssetInfo() {
|
||||
currentPackageLoading,
|
||||
currentPackageErrorMsg,
|
||||
packageList,
|
||||
packageLoading,
|
||||
walletInfo,
|
||||
walletLoading,
|
||||
realtimeLoading,
|
||||
|
||||
// Methods
|
||||
fetchAssetDetail,
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
:card-info="cardInfo"
|
||||
:device-realtime="deviceRealtime"
|
||||
v-model:polling-enabled="pollingEnabled"
|
||||
:realtime-loading="realtimeLoading"
|
||||
@enable-card="handleEnableCard"
|
||||
@disable-card="handleDisableCard"
|
||||
@manual-deactivate="handleManualDeactivate"
|
||||
@@ -56,7 +57,9 @@
|
||||
:page-size="packagePagination.pageSize"
|
||||
:bound-device-id="cardInfo.bound_device_id"
|
||||
:bound-device-no="cardInfo.bound_device_no"
|
||||
:loading="packageLoading"
|
||||
@show-daily-records="handleShowDailyRecords"
|
||||
@show-recharge="showRechargeDialog"
|
||||
@page-change="handlePackagePageChange"
|
||||
@size-change="handlePackageSizeChange"
|
||||
@navigate-to-device="handleNavigateToDevice"
|
||||
@@ -172,8 +175,10 @@
|
||||
currentPackageLoading,
|
||||
currentPackageErrorMsg,
|
||||
packageList,
|
||||
packageLoading,
|
||||
walletInfo,
|
||||
walletLoading,
|
||||
realtimeLoading,
|
||||
fetchAssetDetail,
|
||||
loadPackageList,
|
||||
loadCurrentPackage,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<ArtSearchBar
|
||||
v-model:filter="searchForm"
|
||||
:items="searchFormItems"
|
||||
label-width="100"
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
></ArtSearchBar>
|
||||
@@ -340,7 +341,11 @@
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="6">
|
||||
<ElFormItem label="搜索类型" prop="search_type">
|
||||
<ElSelect v-model="bindCardForm.search_type" placeholder="请选择搜索类型" style="width: 100%">
|
||||
<ElSelect
|
||||
v-model="bindCardForm.search_type"
|
||||
placeholder="请选择搜索类型"
|
||||
style="width: 100%"
|
||||
>
|
||||
<ElOption label="ICCID" value="iccid" />
|
||||
<ElOption label="运营商名称" value="carrier_name" />
|
||||
</ElSelect>
|
||||
@@ -655,6 +660,10 @@
|
||||
})
|
||||
const seriesBindingResult = ref<BatchSetDeviceSeriesBindingResponse | null>(null)
|
||||
|
||||
// 搜索表单店铺/系列选项
|
||||
const shopOptions = ref<any[]>([])
|
||||
const searchSeriesOptions = ref<any[]>([])
|
||||
|
||||
// 设备详情弹窗相关
|
||||
const deviceDetailDialogVisible = ref(false)
|
||||
const deviceDetailLoading = ref(false)
|
||||
@@ -734,7 +743,12 @@
|
||||
status: undefined as DeviceStatus | undefined,
|
||||
batch_no: '',
|
||||
device_type: '',
|
||||
manufacturer: ''
|
||||
manufacturer: '',
|
||||
shop_id: undefined as number | undefined,
|
||||
series_id: undefined as number | undefined,
|
||||
dateRange: [] as string[],
|
||||
created_at_start: '',
|
||||
created_at_end: ''
|
||||
}
|
||||
|
||||
// 搜索表单
|
||||
@@ -801,6 +815,54 @@
|
||||
clearable: true,
|
||||
placeholder: '请输入制造商'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '店铺名称',
|
||||
prop: 'shop_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: (query: string) => searchShops(query),
|
||||
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: (query: string) => searchSeries(query),
|
||||
loading: seriesLoading.value,
|
||||
placeholder: '请选择或搜索套餐系列'
|
||||
},
|
||||
options: () =>
|
||||
searchSeriesOptions.value.map((s) => ({
|
||||
label: s.series_name,
|
||||
value: s.id
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '起始至结束',
|
||||
prop: 'dateRange',
|
||||
type: 'date',
|
||||
config: {
|
||||
type: 'datetimerange',
|
||||
rangeSeparator: '至',
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
valueFormat: 'YYYY-MM-DDTHH:mm:ssZZ'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -814,21 +876,26 @@
|
||||
// 列配置
|
||||
const columnOptions = [
|
||||
{ label: '设备号', prop: 'virtual_no' },
|
||||
{ label: 'IMEI', prop: 'imei' },
|
||||
{ label: '设备名称', prop: 'device_name' },
|
||||
{ label: '设备型号', prop: 'device_model' },
|
||||
{ label: '设备类型', prop: 'device_type' },
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
{ label: '套餐系列', prop: 'series_name' },
|
||||
{ label: '设备型号', prop: 'device_model' },
|
||||
{ label: '设备类型', prop: 'device_type' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
{ label: '状态名称', prop: 'status_name' },
|
||||
{ label: '在线状态', prop: 'online_status' },
|
||||
{ label: '实名策略', prop: 'realname_policy' },
|
||||
{ label: '切卡模式', prop: 'switch_mode' },
|
||||
{ label: '激活时间', prop: 'activated_at' },
|
||||
{ label: '制造商', prop: 'manufacturer' },
|
||||
{ label: '软件版本', prop: 'software_version' },
|
||||
{ label: '最大插槽数', prop: 'max_sim_slots' },
|
||||
{ label: '已绑定卡数', prop: 'bound_card_count' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
{ label: '在线状态', prop: 'online_status' },
|
||||
|
||||
{ label: '切卡模式', prop: 'switch_mode' },
|
||||
{ label: '序列号', prop: 'sn' },
|
||||
{ label: '批次号', prop: 'batch_no' },
|
||||
{ label: '最后在线时间', prop: 'last_online_time' },
|
||||
{ label: '最后同步时间', prop: 'last_gateway_sync_at' },
|
||||
{ label: '批次号', prop: 'batch_no' },
|
||||
{ label: '创建时间', prop: 'created_at' },
|
||||
{ label: '操作', prop: 'operation' }
|
||||
]
|
||||
@@ -875,10 +942,7 @@
|
||||
bindCardForm.iot_card_id = undefined
|
||||
bindCardForm.slot_position = 1
|
||||
// 加载未绑定设备的卡列表
|
||||
await Promise.all([
|
||||
loadDeviceCards(device.virtual_no),
|
||||
loadDefaultIotCards()
|
||||
])
|
||||
await Promise.all([loadDeviceCards(device.virtual_no), loadDefaultIotCards()])
|
||||
}
|
||||
|
||||
// 加载设备绑定的卡列表
|
||||
@@ -1055,7 +1119,7 @@
|
||||
{
|
||||
prop: 'virtual_no',
|
||||
label: '设备号',
|
||||
minWidth: 150,
|
||||
minWidth: 170,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: Device) => {
|
||||
return h(
|
||||
@@ -1071,21 +1135,18 @@
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'imei',
|
||||
label: 'IMEI',
|
||||
minWidth: 150,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: Device) => row.imei || '-'
|
||||
},
|
||||
{
|
||||
prop: 'device_name',
|
||||
label: '设备名称',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: 'device_model',
|
||||
label: '设备型号',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: 'device_type',
|
||||
label: '设备类型',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
prop: 'shop_name',
|
||||
label: '店铺名称',
|
||||
@@ -1097,23 +1158,14 @@
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: 'manufacturer',
|
||||
label: '制造商',
|
||||
prop: 'device_model',
|
||||
label: '设备型号',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: 'max_sim_slots',
|
||||
label: '最大插槽数',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
prop: 'bound_card_count',
|
||||
label: '已绑定卡数',
|
||||
width: 110,
|
||||
formatter: (row: Device) => {
|
||||
const color = row.bound_card_count > 0 ? '#67c23a' : '#909399'
|
||||
return h('span', { style: { color, fontWeight: 'bold' } }, row.bound_card_count)
|
||||
}
|
||||
prop: 'device_type',
|
||||
label: '设备类型',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
@@ -1130,6 +1182,12 @@
|
||||
return h(ElTag, { type: status.type }, () => status.text)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'status_name',
|
||||
label: '状态名称',
|
||||
width: 100,
|
||||
formatter: (row: Device) => row.status_name || '-'
|
||||
},
|
||||
{
|
||||
prop: 'online_status',
|
||||
label: '在线状态',
|
||||
@@ -1144,6 +1202,20 @@
|
||||
return h(ElTag, { type: status.type }, () => status.text)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'realname_policy',
|
||||
label: '实名策略',
|
||||
width: 150,
|
||||
formatter: (row: Device) => {
|
||||
const policy = row.realname_policy || ''
|
||||
const policyMap: Record<string, string> = {
|
||||
none: '无需实名',
|
||||
before_order: '先实名后充值/购买',
|
||||
after_order: '先充值/购买后实名'
|
||||
}
|
||||
return policyMap[policy] || (policy ? policy : '未知')
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'switch_mode',
|
||||
label: '切卡模式',
|
||||
@@ -1157,19 +1229,51 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'realname_policy',
|
||||
label: '实名认证策略',
|
||||
width: 150,
|
||||
prop: 'activated_at',
|
||||
label: '激活时间',
|
||||
width: 180,
|
||||
formatter: (row: Device) => (row.activated_at ? formatDateTime(row.activated_at) : '-')
|
||||
},
|
||||
{
|
||||
prop: 'manufacturer',
|
||||
label: '制造商',
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
prop: 'software_version',
|
||||
label: '软件版本',
|
||||
minWidth: 180,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: Device) => row.software_version || '-'
|
||||
},
|
||||
{
|
||||
prop: 'max_sim_slots',
|
||||
label: '最大插槽数',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
prop: 'bound_card_count',
|
||||
label: '已绑定卡数',
|
||||
width: 110,
|
||||
formatter: (row: Device) => {
|
||||
const policy = row.realname_policy || ''
|
||||
const policyMap: Record<string, string> = {
|
||||
none: '无需实名',
|
||||
before_order: '先实名后充值/购买',
|
||||
after_order: '先充值/购买后实名'
|
||||
}
|
||||
return policyMap[policy] || (policy ? policy : '未知')
|
||||
const color = row.bound_card_count > 0 ? '#67c23a' : '#909399'
|
||||
return h('span', { style: { color, fontWeight: 'bold' } }, row.bound_card_count)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'sn',
|
||||
label: '序列号',
|
||||
minWidth: 120,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: Device) => row.sn || '-'
|
||||
},
|
||||
{
|
||||
prop: 'batch_no',
|
||||
label: '批次号',
|
||||
minWidth: 180,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: Device) => row.batch_no || '-'
|
||||
},
|
||||
{
|
||||
prop: 'last_online_time',
|
||||
label: '最后在线时间',
|
||||
@@ -1184,12 +1288,6 @@
|
||||
formatter: (row: Device) =>
|
||||
row.last_gateway_sync_at ? formatDateTime(row.last_gateway_sync_at) : '-'
|
||||
},
|
||||
{
|
||||
prop: 'batch_no',
|
||||
label: '批次号',
|
||||
minWidth: 180,
|
||||
formatter: (row: Device) => row.batch_no || '-'
|
||||
},
|
||||
{
|
||||
prop: 'created_at',
|
||||
label: '创建时间',
|
||||
@@ -1198,9 +1296,50 @@
|
||||
}
|
||||
])
|
||||
|
||||
// 搜索店铺(用于搜索表单)
|
||||
const searchShops = 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 searchSeries = 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)
|
||||
}
|
||||
}
|
||||
|
||||
let isFirstActivation = true
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
searchShops('')
|
||||
searchSeries('')
|
||||
})
|
||||
|
||||
// 当页面被 keep-alive 激活时自动刷新数据
|
||||
@@ -1223,7 +1362,11 @@
|
||||
status: searchForm.status,
|
||||
batch_no: searchForm.batch_no || undefined,
|
||||
device_type: searchForm.device_type || undefined,
|
||||
manufacturer: searchForm.manufacturer || undefined
|
||||
manufacturer: searchForm.manufacturer || undefined,
|
||||
shop_id: searchForm.shop_id || undefined,
|
||||
series_id: searchForm.series_id || undefined,
|
||||
created_at_start: searchForm.created_at_start || undefined,
|
||||
created_at_end: searchForm.created_at_end || undefined
|
||||
}
|
||||
const res = await DeviceService.getDevices(params)
|
||||
if (res.code === 0 && res.data) {
|
||||
@@ -1246,6 +1389,14 @@
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
// 处理日期范围
|
||||
if (searchForm.dateRange && Array.isArray(searchForm.dateRange)) {
|
||||
searchForm.created_at_start = searchForm.dateRange[0] || ''
|
||||
searchForm.created_at_end = searchForm.dateRange[1] || ''
|
||||
} else {
|
||||
searchForm.created_at_start = ''
|
||||
searchForm.created_at_end = ''
|
||||
}
|
||||
pagination.page = 1
|
||||
getTableData()
|
||||
}
|
||||
|
||||
@@ -229,6 +229,7 @@
|
||||
const searchForm = reactive({
|
||||
status: undefined,
|
||||
identifier: '',
|
||||
created_at_range: [],
|
||||
created_at_start: '',
|
||||
created_at_end: ''
|
||||
})
|
||||
@@ -306,11 +307,11 @@
|
||||
prop: 'created_at_range',
|
||||
type: 'date',
|
||||
config: {
|
||||
type: 'datetimerange',
|
||||
type: 'daterange',
|
||||
rangeSeparator: '至',
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss'
|
||||
startPlaceholder: '开始日期',
|
||||
endPlaceholder: '结束日期',
|
||||
valueFormat: 'YYYY-MM-DD'
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -417,12 +418,23 @@
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
// 处理日期范围
|
||||
if (searchForm.created_at_range && Array.isArray(searchForm.created_at_range)) {
|
||||
searchForm.created_at_start = searchForm.created_at_range[0] || ''
|
||||
searchForm.created_at_end = searchForm.created_at_range[1] || ''
|
||||
} else {
|
||||
searchForm.created_at_start = ''
|
||||
searchForm.created_at_end = ''
|
||||
}
|
||||
pagination.page = 1
|
||||
loadExchangeList()
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchForm.created_at_range = []
|
||||
searchForm.created_at_start = ''
|
||||
searchForm.created_at_end = ''
|
||||
pagination.page = 1
|
||||
loadExchangeList()
|
||||
}
|
||||
|
||||
@@ -562,7 +562,13 @@
|
||||
import { h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
import { CardService, ShopService, PackageSeriesService, AssetService, CarrierService } from '@/api/modules'
|
||||
import {
|
||||
CardService,
|
||||
ShopService,
|
||||
PackageSeriesService,
|
||||
AssetService,
|
||||
CarrierService
|
||||
} from '@/api/modules'
|
||||
import { ElMessage, ElTag, ElIcon, ElMessageBox } from 'element-plus'
|
||||
import { Loading } from '@element-plus/icons-vue'
|
||||
import RealnamePolicyDialog from '@/components/device/RealnamePolicyDialog.vue'
|
||||
@@ -659,6 +665,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 套餐系列搜索相关
|
||||
const searchSeriesOptions = ref<any[]>([])
|
||||
|
||||
// 搜索套餐系列(用于搜索栏)
|
||||
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)
|
||||
@@ -767,6 +796,7 @@
|
||||
msisdn: string
|
||||
virtual_no: string
|
||||
device_virtual_no: string
|
||||
series_id: undefined | number
|
||||
is_distributed: undefined | number
|
||||
is_standalone: undefined | boolean
|
||||
[key: string]: any
|
||||
@@ -777,6 +807,7 @@
|
||||
msisdn: '',
|
||||
virtual_no: '',
|
||||
device_virtual_no: '',
|
||||
series_id: undefined,
|
||||
is_distributed: undefined,
|
||||
is_standalone: undefined,
|
||||
is_replaced: undefined,
|
||||
@@ -952,6 +983,23 @@
|
||||
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',
|
||||
@@ -974,8 +1022,8 @@
|
||||
placeholder: '全部'
|
||||
},
|
||||
options: () => [
|
||||
{ label: '是(未绑定设备)', value: true },
|
||||
{ label: '否(已绑定设备)', value: false }
|
||||
{ label: '未绑定设备', value: true },
|
||||
{ label: '已绑定设备', value: false }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1016,18 +1064,28 @@
|
||||
{ label: 'ICCID', prop: 'iccid' },
|
||||
{ label: 'MSISDN', prop: 'msisdn' },
|
||||
{ label: '卡虚拟号', prop: 'virtual_no' },
|
||||
{ label: '绑定设备虚拟号', prop: 'device_virtual_no' },
|
||||
{ label: '卡业务类型', prop: 'card_category' },
|
||||
{ label: '运营商', prop: 'carrier_name' },
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
{ label: '套餐系列', prop: 'series_name' },
|
||||
{ label: '卡业务类型', prop: 'card_category' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
{ label: '激活状态', prop: 'activation_status' },
|
||||
{ label: '激活时间', prop: 'activated_at' },
|
||||
{ label: '网络状态', prop: 'network_status' },
|
||||
{ label: '实名状态', prop: 'real_name_status' },
|
||||
{ label: '实名认证策略', prop: 'realname_policy' },
|
||||
{ label: '绑定设备虚拟号', prop: 'device_virtual_no' },
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
{ label: '套餐系列', prop: 'series_name' },
|
||||
{ label: '自然月累计流量(MB)', prop: 'current_month_usage_mb' },
|
||||
{ label: '本月开始日期', prop: 'current_month_start_date' },
|
||||
{ label: '上月流量总量(MB)', prop: 'last_month_total_mb' },
|
||||
{ label: '累计流量(MB)', prop: 'data_usage_mb' },
|
||||
{ label: '创建时间', prop: 'created_at' }
|
||||
{ label: '最后流量检查时间', prop: 'last_data_check_at' },
|
||||
{ label: '最后实名检查时间', prop: 'last_real_name_check_at' },
|
||||
{ label: '启用轮询', prop: 'enable_polling' },
|
||||
{ label: '批次号', prop: 'batch_no' },
|
||||
{ label: '供应商', prop: 'supplier' },
|
||||
{ label: '创建时间', prop: 'created_at' },
|
||||
{ label: '更新时间', prop: 'updated_at' }
|
||||
]
|
||||
|
||||
const cardList = ref<StandaloneIotCard[]>([])
|
||||
@@ -1233,12 +1291,70 @@
|
||||
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: 'supplier',
|
||||
label: '供应商',
|
||||
width: 150,
|
||||
formatter: (row: StandaloneIotCard) => row.supplier || '-'
|
||||
},
|
||||
{
|
||||
prop: 'created_at',
|
||||
label: '创建时间',
|
||||
@@ -1289,7 +1405,6 @@
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadSearchCarrierOptions()
|
||||
getTableData()
|
||||
})
|
||||
|
||||
|
||||
@@ -59,7 +59,27 @@
|
||||
},
|
||||
{ label: '资产标识符', prop: 'asset_identifier' },
|
||||
{ label: '来源所有者', prop: 'from_owner_name' },
|
||||
{
|
||||
label: '来源类型',
|
||||
formatter: (_, data) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
platform: '平台',
|
||||
shop: '店铺'
|
||||
}
|
||||
return typeMap[data.from_owner_type] || data.from_owner_type || '-'
|
||||
}
|
||||
},
|
||||
{ label: '目标所有者', prop: 'to_owner_name' },
|
||||
{
|
||||
label: '目标类型',
|
||||
formatter: (_, data) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
platform: '平台',
|
||||
shop: '店铺'
|
||||
}
|
||||
return typeMap[data.to_owner_type] || data.to_owner_type || '-'
|
||||
}
|
||||
},
|
||||
{ label: '操作人', prop: 'operator_name' },
|
||||
{ label: '关联卡数量', prop: 'related_card_count' },
|
||||
{ label: '创建时间', prop: 'created_at', formatter: (value) => formatDateTime(value) },
|
||||
|
||||
@@ -28,28 +28,13 @@
|
||||
:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:marginTop="10"
|
||||
:row-class-name="getRowClassName"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
@row-contextmenu="handleRowContextMenu"
|
||||
@cell-mouse-enter="handleCellMouseEnter"
|
||||
@cell-mouse-leave="handleCellMouseLeave"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 鼠标悬浮提示 -->
|
||||
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||
|
||||
<!-- 右键菜单 -->
|
||||
<ArtMenuRight
|
||||
ref="contextMenuRef"
|
||||
:menu-items="contextMenuItems"
|
||||
:menu-width="120"
|
||||
@select="handleContextMenuSelect"
|
||||
/>
|
||||
</ElCard>
|
||||
</div>
|
||||
</ArtTableFullScreen>
|
||||
@@ -58,7 +43,7 @@
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { CardService } from '@/api/modules'
|
||||
import { CardService, ShopService } from '@/api/modules'
|
||||
import { ElMessage, ElTag } from 'element-plus'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
@@ -67,28 +52,14 @@
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import type { AssetAllocationRecord, AllocationTypeEnum, AssetTypeEnum } from '@/types/api/card'
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||
|
||||
defineOptions({ name: 'AssetAllocationRecords' })
|
||||
|
||||
const { hasAuth } = useAuth()
|
||||
const router = useRouter()
|
||||
|
||||
// 使用表格右键菜单功能
|
||||
const {
|
||||
showContextMenuHint,
|
||||
hintPosition,
|
||||
getRowClassName,
|
||||
handleCellMouseEnter,
|
||||
handleCellMouseLeave
|
||||
} = useTableContextMenu()
|
||||
|
||||
const loading = ref(false)
|
||||
const tableRef = ref()
|
||||
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||
const currentRow = ref<AssetAllocationRecord | null>(null)
|
||||
|
||||
// 搜索表单初始值
|
||||
const initialSearchState = {
|
||||
@@ -99,6 +70,7 @@
|
||||
from_shop_id: undefined as number | undefined,
|
||||
to_shop_id: undefined as number | undefined,
|
||||
operator_id: undefined as number | undefined,
|
||||
dateRange: [] as string[],
|
||||
created_at_start: '',
|
||||
created_at_end: ''
|
||||
}
|
||||
@@ -159,16 +131,51 @@
|
||||
placeholder: 'ICCID或设备号'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '来源店铺',
|
||||
prop: 'from_shop_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: (query: string) => searchShops(query, 'from'),
|
||||
placeholder: '请选择或搜索来源店铺'
|
||||
},
|
||||
options: () =>
|
||||
fromShopOptions.value.map((shop) => ({
|
||||
label: shop.shop_name,
|
||||
value: shop.id
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '目标店铺',
|
||||
prop: 'to_shop_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: (query: string) => searchShops(query, 'to'),
|
||||
placeholder: '请选择或搜索目标店铺'
|
||||
},
|
||||
options: () =>
|
||||
toShopOptions.value.map((shop) => ({
|
||||
label: shop.shop_name,
|
||||
value: shop.id
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'created_at_range',
|
||||
prop: 'dateRange',
|
||||
type: 'date',
|
||||
config: {
|
||||
type: 'datetimerange',
|
||||
type: 'daterange',
|
||||
clearable: true,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ'
|
||||
rangeSeparator: '至',
|
||||
startPlaceholder: '开始日期',
|
||||
endPlaceholder: '结束日期',
|
||||
valueFormat: 'YYYY-MM-DD'
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -186,6 +193,33 @@
|
||||
{ label: '创建时间', prop: 'created_at' }
|
||||
]
|
||||
|
||||
// 店铺搜索选项
|
||||
const fromShopOptions = ref<any[]>([])
|
||||
const toShopOptions = ref<any[]>([])
|
||||
|
||||
// 搜索店铺
|
||||
const searchShops = async (query: string, type: 'from' | 'to') => {
|
||||
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) {
|
||||
if (type === 'from') {
|
||||
fromShopOptions.value = res.data.items || []
|
||||
} else {
|
||||
toShopOptions.value = res.data.items || []
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索店铺失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const recordList = ref<AssetAllocationRecord[]>([])
|
||||
|
||||
// 获取分配类型标签类型
|
||||
@@ -246,12 +280,38 @@
|
||||
{
|
||||
prop: 'from_owner_name',
|
||||
label: '来源所有者',
|
||||
width: 150
|
||||
width: 150,
|
||||
formatter: (row: AssetAllocationRecord) => row.from_owner_name || '-'
|
||||
},
|
||||
{
|
||||
prop: 'from_owner_type',
|
||||
label: '来源类型',
|
||||
width: 100,
|
||||
formatter: (row: AssetAllocationRecord) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
platform: '平台',
|
||||
shop: '店铺'
|
||||
}
|
||||
return typeMap[row.from_owner_type] || row.from_owner_type || '-'
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'to_owner_name',
|
||||
label: '目标所有者',
|
||||
width: 150
|
||||
width: 150,
|
||||
formatter: (row: AssetAllocationRecord) => row.to_owner_name || '-'
|
||||
},
|
||||
{
|
||||
prop: 'to_owner_type',
|
||||
label: '目标类型',
|
||||
width: 100,
|
||||
formatter: (row: AssetAllocationRecord) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
platform: '平台',
|
||||
shop: '店铺'
|
||||
}
|
||||
return typeMap[row.to_owner_type] || row.to_owner_type || '-'
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'operator_name',
|
||||
@@ -279,6 +339,8 @@
|
||||
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
searchShops('', 'from')
|
||||
searchShops('', 'to')
|
||||
})
|
||||
|
||||
// 获取分配记录列表
|
||||
@@ -291,13 +353,6 @@
|
||||
...formFilters
|
||||
}
|
||||
|
||||
// 处理日期范围
|
||||
if ((params as any).created_at_range && (params as any).created_at_range.length === 2) {
|
||||
params.created_at_start = (params as any).created_at_range[0]
|
||||
params.created_at_end = (params as any).created_at_range[1]
|
||||
delete (params as any).created_at_range
|
||||
}
|
||||
|
||||
// 清理空值
|
||||
Object.keys(params).forEach((key) => {
|
||||
if (params[key] === '' || params[key] === undefined) {
|
||||
@@ -327,6 +382,14 @@
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
// 处理日期范围
|
||||
if (formFilters.dateRange && Array.isArray(formFilters.dateRange)) {
|
||||
formFilters.created_at_start = formFilters.dateRange[0] || ''
|
||||
formFilters.created_at_end = formFilters.dateRange[1] || ''
|
||||
} else {
|
||||
formFilters.created_at_start = ''
|
||||
formFilters.created_at_end = ''
|
||||
}
|
||||
pagination.page = 1
|
||||
getTableData()
|
||||
}
|
||||
@@ -362,40 +425,10 @@
|
||||
ElMessage.warning('您没有查看详情的权限')
|
||||
}
|
||||
}
|
||||
|
||||
// 右键菜单项配置
|
||||
const contextMenuItems = computed((): MenuItemType[] => {
|
||||
const items: MenuItemType[] = []
|
||||
|
||||
return items
|
||||
})
|
||||
|
||||
// 处理表格行右键菜单
|
||||
const handleRowContextMenu = (row: AssetAllocationRecord, column: any, event: MouseEvent) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
currentRow.value = row
|
||||
contextMenuRef.value?.show(event)
|
||||
}
|
||||
|
||||
// 处理右键菜单选择
|
||||
const handleContextMenuSelect = (item: MenuItemType) => {
|
||||
if (!currentRow.value) return
|
||||
|
||||
switch (
|
||||
item.key
|
||||
// No cases available
|
||||
) {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.asset-allocation-records-page {
|
||||
// Allocation records page styles
|
||||
}
|
||||
|
||||
:deep(.el-table__row.table-row-with-context-menu) {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { AuthorizationService } from '@/api/modules'
|
||||
import { AuthorizationService, EnterpriseService } from '@/api/modules'
|
||||
import { ElMessage, ElTag } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
@@ -125,6 +125,28 @@
|
||||
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||
const currentRow = ref<AuthorizationItem | null>(null)
|
||||
|
||||
// 企业搜索选项
|
||||
const enterpriseOptions = ref<any[]>([])
|
||||
|
||||
// 搜索企业
|
||||
const searchEnterprises = async (query: string) => {
|
||||
try {
|
||||
const params: any = {
|
||||
page: 1,
|
||||
page_size: 20
|
||||
}
|
||||
if (query) {
|
||||
params.enterprise_name = query
|
||||
}
|
||||
const res = await EnterpriseService.getEnterprises(params)
|
||||
if (res.code === 0) {
|
||||
enterpriseOptions.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索企业失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索表单初始值
|
||||
const initialSearchState = {
|
||||
enterprise_id: undefined as number | undefined,
|
||||
@@ -165,6 +187,23 @@
|
||||
placeholder: '请输入ICCID'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '企业名称',
|
||||
prop: 'enterprise_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: searchEnterprises,
|
||||
placeholder: '请选择或搜索企业'
|
||||
},
|
||||
options: () =>
|
||||
enterpriseOptions.value.map((e) => ({
|
||||
label: e.enterprise_name,
|
||||
value: e.id
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '授权人类型',
|
||||
prop: 'authorizer_type',
|
||||
@@ -309,6 +348,7 @@
|
||||
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
searchEnterprises('')
|
||||
})
|
||||
|
||||
// 获取授权记录列表
|
||||
|
||||
Reference in New Issue
Block a user