fix: 资产信息ui
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 24m28s

This commit is contained in:
luo
2026-08-21 16:56:34 +08:00
parent 4d023e7666
commit d8191f03a0
16 changed files with 661 additions and 135 deletions

View File

@@ -76,7 +76,7 @@
} }
const getEntrySignature = (html: string) => { const getEntrySignature = (html: string) => {
const matches = html.match(/(?:src|href)="[^"]*\/assets\/[^""]+\.(?:js|css)"/g) const matches = html.match(/(?:src|href)="[^"]*\/assets\/[^"]+\.(?:js|css)"/g)
return matches?.sort().join('|') || '' return matches?.sort().join('|') || ''
} }
@@ -106,7 +106,6 @@
[ [
navigator.userAgent, navigator.userAgent,
navigator.language, navigator.language,
navigator.platform,
Intl.DateTimeFormat().resolvedOptions().timeZone, Intl.DateTimeFormat().resolvedOptions().timeZone,
`${screen.width}x${screen.height}x${screen.colorDepth}` `${screen.width}x${screen.height}x${screen.colorDepth}`
].join('|') ].join('|')

View File

@@ -59,7 +59,7 @@ export const strongPasswordRules = (t: (key: string) => string): FormItemRule[]
trigger: 'blur' trigger: 'blur'
}, },
{ {
validator: (rule: any, value: any, callback: any) => { validator: (_rule: any, value: any, callback: any) => {
if (!value) { if (!value) {
callback() callback()
return return
@@ -93,7 +93,7 @@ export const confirmPasswordRules = (
trigger: 'blur' trigger: 'blur'
}, },
{ {
validator: (rule: any, value: any, callback: any) => { validator: (_rule: any, value: any, callback: any) => {
if (!value) { if (!value) {
callback() callback()
return return

View File

@@ -40,68 +40,140 @@
</div> </div>
</div> </div>
</template> </template>
<ElDescriptions :column="descriptionsColumn" direction="vertical" border size="large"> <ElDescriptions
class="asset-descriptions"
:column="descriptionsColumn"
direction="vertical"
border
size="large"
>
<!-- 卡专属字段 --> <!-- 卡专属字段 -->
<template v-if="cardInfo?.asset_type === 'card'"> <template v-if="cardInfo?.asset_type === 'card'">
<ElDescriptionsItem label="ICCID">{{ cardInfo?.iccid || '-' }}</ElDescriptionsItem> <ElDescriptionsItem
<ElDescriptionsItem label="MSISDN">{{ cardInfo?.msisdn || '-' }}</ElDescriptionsItem> label="ICCID"
<ElDescriptionsItem label="运营商账户">{{ carrierAccountDisplay }}</ElDescriptionsItem> label-class-name="asset-label asset-label--identifier-iccid"
<ElDescriptionsItem label="实名状态"> >{{ cardInfo?.iccid || '-' }}</ElDescriptionsItem
>
<ElDescriptionsItem
label="MSISDN"
label-class-name="asset-label asset-label--identifier-msisdn"
>{{ cardInfo?.msisdn || '-' }}</ElDescriptionsItem
>
<ElDescriptionsItem
label="运营商账户"
label-class-name="asset-label asset-label--operator-account"
>{{ carrierAccountDisplay }}</ElDescriptionsItem
>
<ElDescriptionsItem
label="实名状态"
label-class-name="asset-label asset-label--realname-status"
>
<ElTag :type="getRealNameStatusType(cardInfo?.real_name_status)" size="small"> <ElTag :type="getRealNameStatusType(cardInfo?.real_name_status)" size="small">
{{ getRealNameStatusName(cardInfo?.real_name_status) }} {{ getRealNameStatusName(cardInfo?.real_name_status) }}
</ElTag> </ElTag>
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem v-if="canShowSensitiveFields" label="实名认证策略"> <ElDescriptionsItem
v-if="canShowSensitiveFields"
label="实名认证策略"
label-class-name="asset-label asset-label--realname-policy"
>
{{ getRealnamePolicyName(cardInfo?.realname_policy) }} {{ getRealnamePolicyName(cardInfo?.realname_policy) }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="店铺">{{ cardInfo?.shop_name || '-' }}</ElDescriptionsItem> <ElDescriptionsItem label="店铺" label-class-name="asset-label asset-label--shop">{{
<ElDescriptionsItem label="套餐系列">{{ cardInfo?.series_name || '-' }}</ElDescriptionsItem> cardInfo?.shop_name || '-'
<ElDescriptionsItem label="虚拟号">{{ cardInfo?.virtual_no || '-' }}</ElDescriptionsItem> }}</ElDescriptionsItem>
<ElDescriptionsItem label="网络状态"> <ElDescriptionsItem
label="套餐系列"
label-class-name="asset-label asset-label--package-series"
>{{ cardInfo?.series_name || '-' }}</ElDescriptionsItem
>
<ElDescriptionsItem
label="虚拟号"
label-class-name="asset-label asset-label--virtual-number"
>{{ cardInfo?.virtual_no || '-' }}</ElDescriptionsItem
>
<ElDescriptionsItem
label="网络状态"
label-class-name="asset-label asset-label--network-status"
>
<ElTag :type="cardInfo?.network_status === 1 ? 'success' : 'danger'" size="small"> <ElTag :type="cardInfo?.network_status === 1 ? 'success' : 'danger'" size="small">
{{ cardInfo?.network_status === 1 ? '正常' : '停机' }} {{ cardInfo?.network_status === 1 ? '正常' : '停机' }}
</ElTag> </ElTag>
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem v-if="canShowCardMonthUsage" label="本月已用流量">{{ <ElDescriptionsItem
formatDataSize(cardInfo?.current_month_usage_mb || 0) v-if="canShowCardMonthUsage"
}}</ElDescriptionsItem> label="本月已用流量"
<ElDescriptionsItem v-if="canShowCardMonthUsage" label="运营商周期月内已用流量">{{ label-class-name="asset-label asset-label--usage-month"
formatDataSize(cardInfo?.last_gateway_reading_mb || 0) >{{ formatDataSize(cardInfo?.current_month_usage_mb || 0) }}</ElDescriptionsItem
}}</ElDescriptionsItem> >
<ElDescriptionsItem label="实名时间">{{ <ElDescriptionsItem
cardInfo?.real_name_at ? formatDateTime(cardInfo.real_name_at) : '-' v-if="canShowCardMonthUsage"
}}</ElDescriptionsItem> label="运营商周期月内已用流量"
<ElDescriptionsItem label="实名同步时间">{{ label-class-name="asset-label asset-label--usage-cycle"
formatDateTime(cardInfo?.last_real_name_check_at) || '-' >{{ formatDataSize(cardInfo?.last_gateway_reading_mb || 0) }}</ElDescriptionsItem
}}</ElDescriptionsItem> >
<ElDescriptionsItem
label="实名时间"
label-class-name="asset-label asset-label--realname-time"
>{{
cardInfo?.real_name_at ? formatDateTime(cardInfo.real_name_at) : '-'
}}</ElDescriptionsItem
>
<ElDescriptionsItem
label="实名同步时间"
label-class-name="asset-label asset-label--realname-sync"
>{{ formatDateTime(cardInfo?.last_real_name_check_at) || '-' }}</ElDescriptionsItem
>
<ElDescriptionsItem label="IMEI">{{ <ElDescriptionsItem
cardInfo?.gateway_card_imei || '-' label="IMEI"
}}</ElDescriptionsItem> label-class-name="asset-label asset-label--identifier-imei"
<ElDescriptionsItem label="资产状态"> >{{ cardInfo?.gateway_card_imei || '-' }}</ElDescriptionsItem
>
<ElDescriptionsItem
label="资产状态"
label-class-name="asset-label asset-label--asset-status"
>
<ElTag :type="getAssetStatusType(cardInfo?.status)" size="small"> <ElTag :type="getAssetStatusType(cardInfo?.status)" size="small">
{{ getAssetStatusName(cardInfo?.status) }} {{ getAssetStatusName(cardInfo?.status) }}
</ElTag> </ElTag>
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="卡状态同步时间">{{ <ElDescriptionsItem
formatDateTime(cardInfo?.last_card_status_check_at) || '-' label="卡状态同步时间"
}}</ElDescriptionsItem> label-class-name="asset-label asset-label--card-sync"
<ElDescriptionsItem label="流量同步时间">{{ >{{ formatDateTime(cardInfo?.last_card_status_check_at) || '-' }}</ElDescriptionsItem
formatDateTime(cardInfo?.last_data_check_at) || '-' >
}}</ElDescriptionsItem> <ElDescriptionsItem
label="流量同步时间"
label-class-name="asset-label asset-label--traffic-sync"
>{{ formatDateTime(cardInfo?.last_data_check_at) || '-' }}</ElDescriptionsItem
>
<ElDescriptionsItem label="运营商状态" :span="1"> <ElDescriptionsItem
label="运营商状态"
label-class-name="asset-label asset-label--operator-status"
:span="1"
>
<span class="gateway-extend-text">{{ cardInfo?.gateway_extend || '-' }}</span> <span class="gateway-extend-text">{{ cardInfo?.gateway_extend || '-' }}</span>
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="所有套餐已用量"> <ElDescriptionsItem
label="所有套餐已用量"
label-class-name="asset-label asset-label--package-used"
>
{{ formatUsageSummaryMb(cardInfo?.total_virtual_used_mb) }} {{ formatUsageSummaryMb(cardInfo?.total_virtual_used_mb) }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="所有套餐剩余量"> <ElDescriptionsItem
label="所有套餐剩余量"
label-class-name="asset-label asset-label--package-remaining"
>
{{ formatUsageSummaryMb(cardInfo?.total_virtual_remaining_mb) }} {{ formatUsageSummaryMb(cardInfo?.total_virtual_remaining_mb) }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="预计套餐到期时间"> <ElDescriptionsItem
label="预计套餐到期时间"
label-class-name="asset-label asset-label--package-expire-estimate"
>
<span <span
:class="getExpiryEstimateClass(cardInfo)" :class="getExpiryEstimateClass(cardInfo)"
:title="getExpiryEstimateTooltip(cardInfo)" :title="getExpiryEstimateTooltip(cardInfo)"
@@ -111,7 +183,10 @@
</ElDescriptionsItem> </ElDescriptionsItem>
<template v-if="cardInfo?.bound_device_id"> <template v-if="cardInfo?.bound_device_id">
<ElDescriptionsItem label="绑定设备号"> <ElDescriptionsItem
label="绑定设备号"
label-class-name="asset-label asset-label--bound-device-number"
>
<ElButton <ElButton
type="primary" type="primary"
link link
@@ -120,7 +195,10 @@
{{ cardInfo?.bound_device_no || '-' }} {{ cardInfo?.bound_device_no || '-' }}
</ElButton> </ElButton>
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="绑定设备名称"> <ElDescriptionsItem
label="绑定设备名称"
label-class-name="asset-label asset-label--bound-device-name"
>
{{ cardInfo?.bound_device_name || '-' }} {{ cardInfo?.bound_device_name || '-' }}
</ElDescriptionsItem> </ElDescriptionsItem>
</template> </template>
@@ -128,15 +206,31 @@
<!-- 设备专属字段 --> <!-- 设备专属字段 -->
<template v-if="cardInfo?.asset_type === 'device'"> <template v-if="cardInfo?.asset_type === 'device'">
<ElDescriptionsItem label="IMEI">{{ cardInfo?.imei || '-' }}</ElDescriptionsItem> <ElDescriptionsItem
<ElDescriptionsItem label="设备名称">{{ cardInfo?.device_name || '-' }}</ElDescriptionsItem> label="IMEI"
<ElDescriptionsItem label="店铺">{{ cardInfo?.shop_name || '-' }}</ElDescriptionsItem> label-class-name="asset-label asset-label--identifier-imei"
<ElDescriptionsItem label="设备状态"> >{{ cardInfo?.imei || '-' }}</ElDescriptionsItem
>
<ElDescriptionsItem
label="设备名称"
label-class-name="asset-label asset-label--device-name"
>{{ cardInfo?.device_name || '-' }}</ElDescriptionsItem
>
<ElDescriptionsItem label="店铺" label-class-name="asset-label asset-label--shop">{{
cardInfo?.shop_name || '-'
}}</ElDescriptionsItem>
<ElDescriptionsItem
label="设备状态"
label-class-name="asset-label asset-label--device-status"
>
<ElTag :type="getAssetStatusType(cardInfo?.status)" size="small"> <ElTag :type="getAssetStatusType(cardInfo?.status)" size="small">
{{ getAssetStatusName(cardInfo?.status) }} {{ getAssetStatusName(cardInfo?.status) }}
</ElTag> </ElTag>
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="实名状态"> <ElDescriptionsItem
label="实名状态"
label-class-name="asset-label asset-label--realname-status"
>
<ElTag <ElTag
v-if="selectedDeviceStatusCard" v-if="selectedDeviceStatusCard"
:type="getRealNameStatusType(selectedDeviceStatusCard.real_name_status)" :type="getRealNameStatusType(selectedDeviceStatusCard.real_name_status)"
@@ -146,19 +240,31 @@
</ElTag> </ElTag>
<span v-else>-</span> <span v-else>-</span>
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="激活状态"> <ElDescriptionsItem
label="激活状态"
label-class-name="asset-label asset-label--activation-status"
>
{{ cardInfo?.activation_status_name || '-' }} {{ cardInfo?.activation_status_name || '-' }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="设备型号">{{ <ElDescriptionsItem
cardInfo?.device_model || '-' label="设备型号"
}}</ElDescriptionsItem> label-class-name="asset-label asset-label--device-model"
<ElDescriptionsItem label="实名时间">{{ >{{ cardInfo?.device_model || '-' }}</ElDescriptionsItem
selectedDeviceStatusCard?.real_name_at >
? formatDateTime(selectedDeviceStatusCard.real_name_at) <ElDescriptionsItem
: '-' label="实名时间"
}}</ElDescriptionsItem> label-class-name="asset-label asset-label--realname-time"
<ElDescriptionsItem label="在线状态"> >{{
selectedDeviceStatusCard?.real_name_at
? formatDateTime(selectedDeviceStatusCard.real_name_at)
: '-'
}}</ElDescriptionsItem
>
<ElDescriptionsItem
label="在线状态"
label-class-name="asset-label asset-label--online-status"
>
<ElTag <ElTag
v-if="cardInfo?.online_status !== undefined" v-if="cardInfo?.online_status !== undefined"
:type="getOnlineStatusType(cardInfo?.online_status)" :type="getOnlineStatusType(cardInfo?.online_status)"
@@ -168,34 +274,68 @@
</ElTag> </ElTag>
<span v-else>-</span> <span v-else>-</span>
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="虚拟号">{{ cardInfo?.virtual_no || '-' }}</ElDescriptionsItem> <ElDescriptionsItem
<ElDescriptionsItem label="切卡模式">{{ label="虚拟号"
getSwitchModeName(cardInfo?.switch_mode) label-class-name="asset-label asset-label--virtual-number"
}}</ElDescriptionsItem> >{{ cardInfo?.virtual_no || '-' }}</ElDescriptionsItem
<ElDescriptionsItem label="已绑定卡数">{{ >
cardInfo?.bound_card_count || 0 <ElDescriptionsItem
}}</ElDescriptionsItem> label="切卡模式"
<ElDescriptionsItem label="最大卡槽数">{{ label-class-name="asset-label asset-label--switch-mode"
cardInfo?.max_sim_slots || '-' >{{ getSwitchModeName(cardInfo?.switch_mode) }}</ElDescriptionsItem
}}</ElDescriptionsItem> >
<ElDescriptionsItem v-if="canShowSensitiveFields" label="实名认证策略"> <ElDescriptionsItem
label="已绑定卡数"
label-class-name="asset-label asset-label--bound-card-count"
>{{ cardInfo?.bound_card_count || 0 }}</ElDescriptionsItem
>
<ElDescriptionsItem
label="最大卡槽数"
label-class-name="asset-label asset-label--max-slots"
>{{ cardInfo?.max_sim_slots || '-' }}</ElDescriptionsItem
>
<ElDescriptionsItem
v-if="canShowSensitiveFields"
label="实名认证策略"
label-class-name="asset-label asset-label--realname-policy"
>
{{ getRealnamePolicyName(cardInfo?.realname_policy) }} {{ getRealnamePolicyName(cardInfo?.realname_policy) }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="设备类型">{{ cardInfo?.device_type || '-' }}</ElDescriptionsItem> <ElDescriptionsItem
<ElDescriptionsItem v-if="canShowSensitiveFields" label="制造商">{{ label="设备类型"
cardInfo?.manufacturer || '-' label-class-name="asset-label asset-label--device-type"
>{{ cardInfo?.device_type || '-' }}</ElDescriptionsItem
>
<ElDescriptionsItem
v-if="canShowSensitiveFields"
label="制造商"
label-class-name="asset-label asset-label--manufacturer"
>{{ cardInfo?.manufacturer || '-' }}</ElDescriptionsItem
>
<ElDescriptionsItem
label="最后在线时间"
label-class-name="asset-label asset-label--last-online"
>{{ formatDateTime(cardInfo?.last_online_time) || '-' }}</ElDescriptionsItem
>
<ElDescriptionsItem label="SN" label-class-name="asset-label asset-label--identifier-sn">{{
cardInfo?.sn || '-'
}}</ElDescriptionsItem> }}</ElDescriptionsItem>
<ElDescriptionsItem label="最后在线时间">{{ <ElDescriptionsItem
formatDateTime(cardInfo?.last_online_time) || '-' label="所有套餐已用量"
}}</ElDescriptionsItem> label-class-name="asset-label asset-label--package-used"
<ElDescriptionsItem label="SN">{{ cardInfo?.sn || '-' }}</ElDescriptionsItem> >
<ElDescriptionsItem label="所有套餐已用量">
{{ formatUsageSummaryMb(cardInfo?.total_virtual_used_mb) }} {{ formatUsageSummaryMb(cardInfo?.total_virtual_used_mb) }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="所有套餐剩余量"> <ElDescriptionsItem
label="所有套餐剩余量"
label-class-name="asset-label asset-label--package-remaining"
>
{{ formatUsageSummaryMb(cardInfo?.total_virtual_remaining_mb) }} {{ formatUsageSummaryMb(cardInfo?.total_virtual_remaining_mb) }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="预计套餐到期时间"> <ElDescriptionsItem
label="预计套餐到期时间"
label-class-name="asset-label asset-label--package-expire-estimate"
>
<span <span
:class="getExpiryEstimateClass(cardInfo)" :class="getExpiryEstimateClass(cardInfo)"
:title="getExpiryEstimateTooltip(cardInfo)" :title="getExpiryEstimateTooltip(cardInfo)"
@@ -203,9 +343,11 @@
{{ getExpiryEstimateText(cardInfo) }} {{ getExpiryEstimateText(cardInfo) }}
</span> </span>
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="最后同步时间">{{ <ElDescriptionsItem
formatDateTime(deviceRealtime?.last_sync_time) || '-' label="最后同步时间"
}}</ElDescriptionsItem> label-class-name="asset-label asset-label--last-sync"
>{{ formatDateTime(deviceRealtime?.last_sync_time) || '-' }}</ElDescriptionsItem
>
</template> </template>
</ElDescriptions> </ElDescriptions>
@@ -384,6 +526,7 @@
</ElDivider> </ElDivider>
<div v-if="hasDeviceRealtimeSection" v-loading="realtimeLoading" class="realtime-section"> <div v-if="hasDeviceRealtimeSection" v-loading="realtimeLoading" class="realtime-section">
<ElDescriptions <ElDescriptions
class="asset-descriptions"
:column="descriptionsColumn" :column="descriptionsColumn"
direction="vertical" direction="vertical"
border border
@@ -393,6 +536,7 @@
v-for="item in realtimeDescriptionItems" v-for="item in realtimeDescriptionItems"
:key="item.key" :key="item.key"
:label="item.label" :label="item.label"
:label-class-name="`asset-label asset-label--${item.icon}`"
:span="item.span ?? 1" :span="item.span ?? 1"
> >
<ElTag v-if="item.tagType" :type="item.tagType" size="small"> <ElTag v-if="item.tagType" :type="item.tagType" size="small">
@@ -799,6 +943,7 @@
key: string key: string
label: string label: string
value: string value: string
icon: string
tagType?: 'primary' | 'success' | 'warning' | 'info' | 'danger' tagType?: 'primary' | 'success' | 'warning' | 'info' | 'danger'
span?: number span?: number
} }
@@ -811,11 +956,13 @@
key: 'status', key: 'status',
label: '在线状态', label: '在线状态',
value: getOnlineStatusName(realtime?.online_status), value: getOnlineStatusName(realtime?.online_status),
icon: 'online-status',
tagType: getOnlineStatusType(realtime?.online_status) tagType: getOnlineStatusType(realtime?.online_status)
}, },
{ {
key: 'battery', key: 'battery',
label: '电量', label: '电量',
icon: 'battery',
value: value:
realtime?.battery_level !== null && realtime?.battery_level !== undefined realtime?.battery_level !== null && realtime?.battery_level !== undefined
? `${realtime.battery_level}%` ? `${realtime.battery_level}%`
@@ -824,72 +971,85 @@
{ {
key: 'boot', key: 'boot',
label: '本次开机时长', label: '本次开机时长',
value: formatDuration(realtime?.run_time) value: formatDuration(realtime?.run_time),
icon: 'boot-time'
}, },
{ {
key: 'connect', key: 'connect',
label: '本次联网时长', label: '本次联网时长',
value: formatDuration(realtime?.connect_time) value: formatDuration(realtime?.connect_time),
icon: 'connection-time'
}, },
{ {
key: 'online', key: 'online',
label: '最后在线时间', label: '最后在线时间',
value: formatDateTime(realtime?.last_online_time) || '-' value: formatDateTime(realtime?.last_online_time) || '-',
icon: 'last-online'
}, },
{ {
key: 'realname', key: 'realname',
label: '实名同步时间', label: '实名同步时间',
value: formatDateTime(props.cardInfo?.last_real_name_check_at) || '-' value: formatDateTime(props.cardInfo?.last_real_name_check_at) || '-',
icon: 'realname-sync'
}, },
{ {
key: 'cardSync', key: 'cardSync',
label: '卡状态同步时间', label: '卡状态同步时间',
value: formatDateTime(props.cardInfo?.last_card_status_check_at) || '-' value: formatDateTime(props.cardInfo?.last_card_status_check_at) || '-',
icon: 'card-sync'
}, },
{ {
key: 'trafficSync', key: 'trafficSync',
label: '流量同步时间', label: '流量同步时间',
value: formatDateTime(props.cardInfo?.last_data_check_at) || '-' value: formatDateTime(props.cardInfo?.last_data_check_at) || '-',
icon: 'traffic-sync'
}, },
{ {
key: 'signal', key: 'signal',
label: '信号综合判断', label: '信号综合判断',
value: getSignalQualityText(realtime?.signal_quality), value: getSignalQualityText(realtime?.signal_quality),
icon: 'signal-quality',
tagType: getSignalQualityType(realtime?.signal_quality) tagType: getSignalQualityType(realtime?.signal_quality)
}, },
{ {
key: 'signalReason', key: 'signalReason',
label: '信号较弱原因', label: '信号较弱原因',
value: getSignalBadReasonText(realtime?.signal_bad_reason) value: getSignalBadReasonText(realtime?.signal_bad_reason),
icon: 'signal-reason'
}, },
{ {
key: 'clients', key: 'clients',
label: '已连接设备数', label: '已连接设备数',
value: `${realtime?.client_number ?? '-'} / ${realtime?.max_clients ?? '-'}` value: `${realtime?.client_number ?? '-'} / ${realtime?.max_clients ?? '-'}`,
icon: 'connected-clients'
}, },
{ {
key: 'wifi', key: 'wifi',
label: 'WiFi 状态', label: 'WiFi 状态',
value: value:
realtime?.wifi_enabled === undefined ? '-' : realtime.wifi_enabled ? '已开启' : '已关闭', realtime?.wifi_enabled === undefined ? '-' : realtime.wifi_enabled ? '已开启' : '已关闭',
icon: 'wifi-status',
tagType: realtime?.wifi_enabled ? 'success' : 'info' tagType: realtime?.wifi_enabled ? 'success' : 'info'
}, },
{ {
key: 'wifiName', key: 'wifiName',
label: 'WiFi 名称', label: 'WiFi 名称',
value: realtime?.ssid || '-' value: realtime?.ssid || '-',
icon: 'wifi-name'
}, },
{ {
key: 'wifiPassword', key: 'wifiPassword',
label: 'WiFi 密码', label: 'WiFi 密码',
value: realtime?.wifi_password || '-' value: realtime?.wifi_password || '-',
icon: 'wifi-password'
}, },
{ key: 'wan', label: 'WAN IP', value: realtime?.wan_ip || '-' }, { key: 'wan', label: 'WAN IP', value: realtime?.wan_ip || '-', icon: 'wan-ip' },
{ key: 'lan', label: 'LAN IP', value: realtime?.lan_ip || '-' }, { key: 'lan', label: 'LAN IP', value: realtime?.lan_ip || '-', icon: 'lan-ip' },
{ {
key: 'mac', key: 'mac',
label: 'MAC 地址', label: 'MAC 地址',
value: realtime?.mac_address || '-' value: realtime?.mac_address || '-',
icon: 'mac-address'
}, },
{ {
key: 'limit', key: 'limit',
@@ -899,28 +1059,34 @@
? '-' ? '-'
: realtime.limit_speed : realtime.limit_speed
? `${realtime.limit_speed} KB/s` ? `${realtime.limit_speed} KB/s`
: '不限速' : '不限速',
icon: 'speed-limit'
}, },
{ {
key: 'switchMode', key: 'switchMode',
label: '切卡模式', label: '切卡模式',
value: realtime?.switch_mode === '0' ? '自动' : realtime?.switch_mode === '1' ? '手动' : '-' value:
realtime?.switch_mode === '0' ? '自动' : realtime?.switch_mode === '1' ? '手动' : '-',
icon: 'switch-mode'
}, },
{ {
key: 'interval', key: 'interval',
label: '上报周期', label: '上报周期',
value: realtime?.sync_interval ? `${realtime.sync_interval} 秒` : '-' value: realtime?.sync_interval ? `${realtime.sync_interval} 秒` : '-',
icon: 'report-interval'
}, },
{ {
key: 'firmware', key: 'firmware',
label: '固件版本', label: '固件版本',
value: realtime?.software_version || '-', value: realtime?.software_version || '-',
icon: 'firmware-version',
span: descriptionsColumn.value >= 5 ? 2 : 1 span: descriptionsColumn.value >= 5 ? 2 : 1
}, },
{ {
key: 'updated', key: 'updated',
label: '信息更新时间', label: '信息更新时间',
value: formatDateTime(realtime?.last_update_time) || '-' value: formatDateTime(realtime?.last_update_time) || '-',
icon: 'realtime-updated'
} }
] ]
}) })
@@ -1170,6 +1336,256 @@
} }
} }
.asset-descriptions {
:deep(.el-descriptions__label.is-vertical-label) {
padding: 8px 15px;
font-size: 12px;
font-weight: 500;
line-height: 18px;
color: var(--el-text-color-secondary);
background-color: var(--el-fill-color-extra-light);
}
:deep(.asset-label::before) {
display: inline-block;
width: 16px;
height: 16px;
margin-right: 6px;
font-family: iconfont-sys, sans-serif !important;
font-size: 16px;
line-height: 1;
vertical-align: -3px;
color: var(--el-color-primary);
content: '';
opacity: 0.82;
}
:deep(.asset-label--identifier-iccid::before) {
content: '\e81a';
}
:deep(.asset-label--identifier-msisdn::before) {
content: '\e776';
}
:deep(.asset-label--operator-account::before) {
content: '\e70d';
}
:deep(.asset-label--realname-status::before) {
color: var(--el-color-success);
content: '\e88b';
}
:deep(.asset-label--realname-policy::before) {
color: var(--el-color-warning);
content: '\e75d';
}
:deep(.asset-label--shop::before) {
content: '\e737';
}
:deep(.asset-label--package-series::before) {
content: '\e762';
}
:deep(.asset-label--virtual-number::before) {
content: '\e7bd';
}
:deep(.asset-label--network-status::before) {
content: '\e757';
}
:deep(.asset-label--usage-month::before) {
content: '\e812';
}
:deep(.asset-label--usage-cycle::before) {
content: '\e82a';
}
:deep(.asset-label--realname-time::before) {
content: '\e6f7';
}
:deep(.asset-label--realname-sync::before) {
content: '\e793';
}
:deep(.asset-label--identifier-imei::before) {
content: '\e8d3';
}
:deep(.asset-label--asset-status::before) {
color: var(--el-color-success);
content: '\e88c';
}
:deep(.asset-label--card-sync::before) {
content: '\e7d3';
}
:deep(.asset-label--traffic-sync::before) {
content: '\e7e1';
}
:deep(.asset-label--operator-status::before) {
content: '\e871';
}
:deep(.asset-label--package-used::before) {
content: '\e742';
}
:deep(.asset-label--package-remaining::before) {
content: '\e7d9';
}
:deep(.asset-label--package-expire-estimate::before) {
content: '\e6f3';
}
:deep(.asset-label--bound-device-number::before) {
content: '\e851';
}
:deep(.asset-label--bound-device-name::before) {
content: '\e6ef';
}
:deep(.asset-label--device-name::before) {
content: '\e7d8';
}
:deep(.asset-label--device-status::before) {
color: var(--el-color-success);
content: '\e708';
}
:deep(.asset-label--activation-status::before) {
color: var(--el-color-warning);
content: '\e81c';
}
:deep(.asset-label--device-model::before) {
content: '\e7a7';
}
:deep(.asset-label--online-status::before) {
content: '\e6b2';
}
:deep(.asset-label--switch-mode::before) {
content: '\e72b';
}
:deep(.asset-label--bound-card-count::before) {
content: '\e730';
}
:deep(.asset-label--max-slots::before) {
content: '\e620';
}
:deep(.asset-label--device-type::before) {
content: '\e740';
}
:deep(.asset-label--manufacturer::before) {
content: '\e8a6';
}
:deep(.asset-label--last-online::before) {
content: '\e751';
}
:deep(.asset-label--identifier-sn::before) {
content: '\e72a';
}
:deep(.asset-label--last-sync::before) {
content: '\e614';
}
:deep(.asset-label--battery::before) {
color: var(--el-color-success);
content: '\e6e5';
}
:deep(.asset-label--boot-time::before) {
content: '\e827';
}
:deep(.asset-label--connection-time::before) {
content: '\e790';
}
:deep(.asset-label--signal-quality::before) {
content: '\e808';
}
:deep(.asset-label--signal-reason::before) {
color: var(--el-color-warning);
content: '\e600';
}
:deep(.asset-label--connected-clients::before) {
content: '\e608';
}
:deep(.asset-label--wifi-status::before) {
content: '\e7ee';
}
:deep(.asset-label--wifi-name::before) {
content: '\e841';
}
:deep(.asset-label--wifi-password::before) {
color: var(--el-color-warning);
content: '\e716';
}
:deep(.asset-label--wan-ip::before) {
content: '\e611';
}
:deep(.asset-label--lan-ip::before) {
content: '\e607';
}
:deep(.asset-label--mac-address::before) {
content: '\e68b';
}
:deep(.asset-label--speed-limit::before) {
content: '\e827';
}
:deep(.asset-label--report-interval::before) {
content: '\e7ac';
}
:deep(.asset-label--firmware-version::before) {
content: '\e726';
}
:deep(.asset-label--realtime-updated::before) {
content: '\e6b3';
}
:deep(.el-descriptions__content.is-vertical-content) {
padding: 8px 15px;
font-size: 14px;
font-weight: 500;
line-height: 23px;
color: var(--el-text-color-primary);
overflow-wrap: anywhere;
}
}
.binding-card-list { .binding-card-list {
display: grid; display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));

View File

@@ -181,35 +181,73 @@
</div> </div>
<!-- 套餐详情表格 --> <!-- 套餐详情表格 -->
<ElDescriptions :column="descriptionsColumn" direction="vertical" border size="large"> <ElDescriptions
<ElDescriptionsItem label="订单号"> class="asset-descriptions"
:column="descriptionsColumn"
direction="vertical"
border
size="large"
>
<ElDescriptionsItem
label="订单号"
label-class-name="asset-label asset-label--package-order"
>
{{ currentPackage.order_no || '-' }} {{ currentPackage.order_no || '-' }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="套餐名称"> <ElDescriptionsItem
label="套餐名称"
label-class-name="asset-label asset-label--package-name"
>
{{ currentPackage.package_name || '-' }} {{ currentPackage.package_name || '-' }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="套餐成本价格" v-if="isAdminOrPlatform"> <ElDescriptionsItem
label="套餐成本价格"
label-class-name="asset-label asset-label--package-cost"
v-if="isAdminOrPlatform"
>
{{ formatAmount(currentPackage.paid_amount || 0) }} {{ formatAmount(currentPackage.paid_amount || 0) }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="套餐零售价"> <ElDescriptionsItem
label="套餐零售价"
label-class-name="asset-label asset-label--package-retail"
>
{{ formatAmount(currentPackage.retail_amount || 0) }} {{ formatAmount(currentPackage.retail_amount || 0) }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="套餐类型"> <ElDescriptionsItem
label="套餐类型"
label-class-name="asset-label asset-label--package-type"
>
{{ getPackageTypeName(currentPackage.package_type) }} {{ getPackageTypeName(currentPackage.package_type) }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="开始时间"> <ElDescriptionsItem
label="开始时间"
label-class-name="asset-label asset-label--package-start"
>
{{ formatDateTime(currentPackage.start_time) || '-' }} {{ formatDateTime(currentPackage.start_time) || '-' }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="到期时间"> <ElDescriptionsItem
label="到期时间"
label-class-name="asset-label asset-label--package-expire"
>
{{ formatDateTime(currentPackage.expire_time) || '-' }} {{ formatDateTime(currentPackage.expire_time) || '-' }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="下单时间"> <ElDescriptionsItem
label="下单时间"
label-class-name="asset-label asset-label--package-created"
>
{{ formatDateTime(currentPackage.created_at) || '-' }} {{ formatDateTime(currentPackage.created_at) || '-' }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem v-if="currentPackage.duration_days" label="套餐时长"> <ElDescriptionsItem
v-if="currentPackage.duration_days"
label="套餐时长"
label-class-name="asset-label asset-label--package-duration"
>
{{ currentPackage.duration_days }} {{ currentPackage.duration_days }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="到期计时基准"> <ElDescriptionsItem
label="到期计时基准"
label-class-name="asset-label asset-label--package-base"
>
{{ getExpiryBaseLabel(currentPackage.expiry_base) }} {{ getExpiryBaseLabel(currentPackage.expiry_base) }}
</ElDescriptionsItem> </ElDescriptionsItem>
</ElDescriptions> </ElDescriptions>
@@ -318,6 +356,82 @@
<style scoped lang="scss"> <style scoped lang="scss">
.current-package-section { .current-package-section {
.asset-descriptions {
:deep(.el-descriptions__label.is-vertical-label) {
padding: 8px 15px;
font-size: 12px;
font-weight: 500;
line-height: 18px;
color: var(--el-text-color-secondary);
background-color: var(--el-fill-color-extra-light);
}
:deep(.asset-label::before) {
display: inline-block;
width: 16px;
height: 16px;
margin-right: 6px;
font-family: iconfont-sys, sans-serif !important;
font-size: 16px;
line-height: 1;
vertical-align: -3px;
color: var(--el-color-primary);
content: '';
opacity: 0.82;
}
:deep(.asset-label--package-order::before) {
content: '\e70f';
}
:deep(.asset-label--package-name::before) {
content: '\e775';
}
:deep(.asset-label--package-cost::before) {
color: var(--el-color-warning);
content: '\e815';
}
:deep(.asset-label--package-retail::before) {
color: var(--el-color-success);
content: '\e6f5';
}
:deep(.asset-label--package-type::before) {
content: '\e723';
}
:deep(.asset-label--package-start::before) {
content: '\e751';
}
:deep(.asset-label--package-expire::before) {
content: '\e76a';
}
:deep(.asset-label--package-created::before) {
content: '\e6ce';
}
:deep(.asset-label--package-duration::before) {
content: '\e7af';
}
:deep(.asset-label--package-base::before) {
content: '\e7b1';
}
:deep(.el-descriptions__content.is-vertical-content) {
padding: 8px 15px;
font-size: 14px;
font-weight: 500;
line-height: 23px;
color: var(--el-text-color-primary);
overflow-wrap: anywhere;
}
}
.info-section-divider { .info-section-divider {
display: flex; display: flex;
height: 20px; height: 20px;

View File

@@ -13,7 +13,7 @@
</div> </div>
</div> </div>
</template> </template>
<div v-loading="loading" class="package-table-wrapper"> <div v-loading="props.loading" class="package-table-wrapper">
<!-- 绑定设备提示 --> <!-- 绑定设备提示 -->
<ElEmpty v-if="props.boundDeviceId" :image-size="100"> <ElEmpty v-if="props.boundDeviceId" :image-size="100">
<template #description> <template #description>

View File

@@ -1,5 +1,5 @@
<template> <template>
<ElCard shadow="never" class="info-card wallet-info" v-loading="walletLoading"> <ElCard shadow="never" class="info-card wallet-info" v-loading="props.walletLoading">
<template #header> <template #header>
<div class="card-header wallet-header"> <div class="card-header wallet-header">
<div class="wallet-header-top"> <div class="wallet-header-top">

View File

@@ -51,13 +51,13 @@
<!-- 每日流量记录表格 --> <!-- 每日流量记录表格 -->
<ElTable :data="filteredRecords" border stripe max-height="400" v-loading="loading"> <ElTable :data="filteredRecords" border stripe max-height="400" v-loading="loading">
<ElTableColumn prop="date" label="日期" width="120" align="center" /> <ElTableColumn prop="date" label="日期" width="120" />
<ElTableColumn label="当日使用流量" width="150" align="center"> <ElTableColumn label="当日使用流量" width="150">
<template #default="{ row }"> <template #default="{ row }">
{{ formatDataSize(row.daily_usage_mb) }} {{ formatDataSize(row.daily_usage_mb) }}
</template> </template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn label="累计流量" width="150" align="center"> <ElTableColumn label="累计流量" width="150">
<template #default="{ row }"> <template #default="{ row }">
{{ formatDataSize(row.cumulative_usage_mb) }} {{ formatDataSize(row.cumulative_usage_mb) }}
</template> </template>

View File

@@ -634,7 +634,7 @@
dialogVisible.value = false dialogVisible.value = false
// 刷新列表 // 刷新列表
getTableData() await getTableData()
} catch (error) { } catch (error) {
console.error(error) console.error(error)
console.log('添加账号失败') console.log('添加账号失败')

View File

@@ -1385,7 +1385,7 @@
} }
// 重置表单验证状态 // 重置表单验证状态
nextTick(() => { await nextTick(() => {
formRef.value?.clearValidate() formRef.value?.clearValidate()
}) })
} }

View File

@@ -843,7 +843,7 @@
baseRules.one_time_commission_amount = [ baseRules.one_time_commission_amount = [
{ required: true, message: '请输入固定佣金金额', trigger: 'blur' }, { required: true, message: '请输入固定佣金金额', trigger: 'blur' },
{ {
validator: (rule, value, callback) => { validator: (_rule, value, callback) => {
if (value === undefined || value === null || value === '') { if (value === undefined || value === null || value === '') {
callback(new Error('请输入固定佣金金额')) callback(new Error('请输入固定佣金金额'))
} else if (value < 0) { } else if (value < 0) {
@@ -867,7 +867,7 @@
} else if (form.commission_type === 'tiered') { } else if (form.commission_type === 'tiered') {
baseRules.commission_tiers = [ baseRules.commission_tiers = [
{ {
validator: (rule, value, callback) => { validator: (_rule, value, callback) => {
if (!value || value.length === 0) { if (!value || value.length === 0) {
callback(new Error('请至少添加一个梯度配置')) callback(new Error('请至少添加一个梯度配置'))
return return
@@ -1288,7 +1288,7 @@
} }
// 仅在已选择目标店铺后加载该组合的授权套餐候选项 // 仅在已选择目标店铺后加载该组合的授权套餐候选项
loadGrantPackageOptions() await loadGrantPackageOptions()
} else { } else {
// 未选择系列,重置佣金类型 // 未选择系列,重置佣金类型
form.series_name = '' form.series_name = ''
@@ -1682,7 +1682,7 @@
} }
// 重置表单验证状态 // 重置表单验证状态
nextTick(() => { await nextTick(() => {
formRef.value?.clearValidate() formRef.value?.clearValidate()
}) })
} }

View File

@@ -253,7 +253,7 @@
trigger: 'blur' trigger: 'blur'
}, },
{ {
validator: (rule, value, callback) => { validator: (_rule, value, callback) => {
if (value === undefined || value === null || value === '') { if (value === undefined || value === null || value === '') {
callback(new Error('请输入成本价')) callback(new Error('请输入成本价'))
} else if (value < 0) { } else if (value < 0) {

View File

@@ -57,7 +57,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, computed, onMounted, h } from 'vue' import { ref, reactive, computed, onMounted, h } from 'vue'
import { ElMessage, ElTag } from 'element-plus' import { ElTag } from 'element-plus'
import { PollingAlertService } from '@/api/modules' import { PollingAlertService } from '@/api/modules'
import type { import type {
PollingAlertHistory, PollingAlertHistory,
@@ -264,7 +264,7 @@
loadData() loadData()
} }
const handleRowContextMenu = (row: PollingAlertHistory, column: any, event: MouseEvent) => { const handleRowContextMenu = (row: PollingAlertHistory, _column: any, event: MouseEvent) => {
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
currentClickRow.value = row currentClickRow.value = row

View File

@@ -379,7 +379,7 @@
}) })
dialogType.value = 'edit' dialogType.value = 'edit'
dialogVisible.value = true dialogVisible.value = true
nextTick(() => { await nextTick(() => {
formRef.value?.clearValidate() formRef.value?.clearValidate()
}) })
} }
@@ -398,7 +398,7 @@
if (res.code === 0) { if (res.code === 0) {
ElMessage.success('创建成功') ElMessage.success('创建成功')
dialogVisible.value = false dialogVisible.value = false
loadData() await loadData()
} }
} else { } else {
const { id, metric_type, task_type, operator, ...updateData } = form const { id, metric_type, task_type, operator, ...updateData } = form
@@ -406,7 +406,7 @@
if (res.code === 0) { if (res.code === 0) {
ElMessage.success('更新成功') ElMessage.success('更新成功')
dialogVisible.value = false dialogVisible.value = false
loadData() await loadData()
} }
} }
} catch (error) { } catch (error) {
@@ -422,7 +422,7 @@
const res = await PollingAlertService.updatePollingAlertRule(row.id, { status: newStatus }) const res = await PollingAlertService.updatePollingAlertRule(row.id, { status: newStatus })
if (res.code === 0) { if (res.code === 0) {
ElMessage.success(newStatus === 1 ? '已启用' : '已禁用') ElMessage.success(newStatus === 1 ? '已启用' : '已禁用')
loadData() await loadData()
} }
} catch (error) { } catch (error) {
console.log('操作失败') console.log('操作失败')
@@ -439,7 +439,7 @@
const res = await PollingAlertService.deletePollingAlertRule(row.id) const res = await PollingAlertService.deletePollingAlertRule(row.id)
if (res.code === 0) { if (res.code === 0) {
ElMessage.success('删除成功') ElMessage.success('删除成功')
loadData() await loadData()
} }
} catch (error) { } catch (error) {
console.log('删除失败') console.log('删除失败')

View File

@@ -611,7 +611,7 @@
card_status_check_interval: data.card_status_check_interval card_status_check_interval: data.card_status_check_interval
}) })
dialogVisible.value = true dialogVisible.value = true
nextTick(() => { await nextTick(() => {
formRef.value?.clearValidate() formRef.value?.clearValidate()
}) })
} catch { } catch {

View File

@@ -87,13 +87,10 @@
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
import type { WithdrawalSettingItem } from '@/types/api/commission' import type { WithdrawalSettingItem } from '@/types/api/commission'
import { useCheckedColumns } from '@/composables/useCheckedColumns' import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { useAuth } from '@/composables/useAuth'
import { formatDateTime, formatMoney, formatFeeRate } from '@/utils/business/format' import { formatDateTime, formatMoney, formatFeeRate } from '@/utils/business/format'
defineOptions({ name: 'WithdrawalSettings' }) defineOptions({ name: 'WithdrawalSettings' })
const { hasAuth } = useAuth()
const dialogVisible = ref(false) const dialogVisible = ref(false)
const loading = ref(false) const loading = ref(false)
const submitLoading = ref(false) const submitLoading = ref(false)
@@ -235,7 +232,7 @@
ElMessage.success('新增配置成功') ElMessage.success('新增配置成功')
dialogVisible.value = false dialogVisible.value = false
formEl.resetFields() formEl.resetFields()
loadSettingsList() await loadSettingsList()
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} finally { } finally {

View File

@@ -254,7 +254,7 @@
description: [{ max: 500, message: '描述不能超过500个字符', trigger: 'blur' }], description: [{ max: 500, message: '描述不能超过500个字符', trigger: 'blur' }],
data_reset_day: [ data_reset_day: [
{ {
validator: (rule: any, value: any, callback: any) => { validator: (_rule: any, value: any, callback: any) => {
if (value === null || value === undefined || value === '') { if (value === null || value === undefined || value === '') {
callback() // 允许为空 callback() // 允许为空
} else { } else {
@@ -277,7 +277,7 @@
realname_link_template: [ realname_link_template: [
{ {
required: true, required: true,
validator: (rule: any, value: any, callback: any) => { validator: (_rule: any, value: any, callback: any) => {
if (form.realname_link_type === 'template' && !value) { if (form.realname_link_type === 'template' && !value) {
callback(new Error('请输入实名链接模板')) callback(new Error('请输入实名链接模板'))
} else { } else {