feat: 预计最终到期时间展示
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 8m8s
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 8m8s
This commit is contained in:
@@ -66,6 +66,9 @@ export interface AssetStartResponse {
|
||||
// 资产实名认证策略
|
||||
export type AssetRealnamePolicy = 'none' | 'before_order' | 'after_order'
|
||||
|
||||
// 预计最终到期时间状态
|
||||
export type ExpiryEstimateStatus = 'exact' | 'waiting_activation' | string
|
||||
|
||||
// 批量更新资产实名认证策略请求
|
||||
export interface BatchUpdateAssetRealnamePolicyRequest {
|
||||
asset_ids: number[]
|
||||
@@ -114,6 +117,10 @@ export interface AssetResolveResponse {
|
||||
virtual_used_mb: number // 展示已用量
|
||||
total_virtual_used_mb?: number | null // 所有套餐已用量(MB),未请求时为 null
|
||||
total_virtual_remaining_mb?: number | null // 所有套餐剩余量(MB),未请求时为 null
|
||||
estimated_final_expires_at?: string | null // 当前及排队主套餐接续后的预计最终到期时间
|
||||
days_until_final_expiry?: number | null // 距预计最终到期的剩余天数
|
||||
expiry_estimate_status?: ExpiryEstimateStatus // 预计最终到期时间状态
|
||||
is_expiring?: boolean // 是否临期,由后端判断
|
||||
reduction_pct: number // 展示增幅比例(小数,如 0.428571)
|
||||
device_protect_status?: DeviceProtectStatus // 保护期状态:none / stop / start(仅 device)
|
||||
activated_at: string // 激活时间
|
||||
|
||||
@@ -399,6 +399,10 @@ export interface StandaloneIotCard {
|
||||
data_usage_mb: number // 累计流量使用(MB)
|
||||
current_month_usage_mb?: number // 自然月累计流量(MB)
|
||||
current_month_start_date?: string | null // 本月开始日期
|
||||
estimated_final_expires_at?: string | null // 当前及排队主套餐接续后的预计最终到期时间
|
||||
days_until_final_expiry?: number | null // 距预计最终到期的剩余天数
|
||||
expiry_estimate_status?: string // 预计最终到期时间状态
|
||||
is_expiring?: boolean // 是否临期,由后端判断
|
||||
last_month_total_mb?: number // 上月流量总量(MB)
|
||||
last_data_check_at?: string | null // 最后流量检查时间
|
||||
last_real_name_check_at?: string | null // 最后实名检查时间
|
||||
|
||||
@@ -67,6 +67,10 @@ export interface Device {
|
||||
realname_policy?: string // 实名认证策略
|
||||
real_name_status: 0 | 1 // 实名状态 (0:未实名, 1:已实名)
|
||||
real_name_status_name: string // 实名状态名称
|
||||
estimated_final_expires_at?: string | null // 当前及排队主套餐接续后的预计最终到期时间
|
||||
days_until_final_expiry?: number | null // 距预计最终到期的剩余天数
|
||||
expiry_estimate_status?: string // 预计最终到期时间状态
|
||||
is_expiring?: boolean // 是否临期,由后端判断
|
||||
authorized_enterprise_id?: number | null // 当前有效授权企业ID
|
||||
authorized_enterprise_name?: string | null // 当前有效授权企业名称
|
||||
}
|
||||
|
||||
23
src/utils/business/expiryEstimate.ts
Normal file
23
src/utils/business/expiryEstimate.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { formatDateTime } from './format'
|
||||
|
||||
export interface ExpiryEstimate {
|
||||
estimated_final_expires_at?: string | null
|
||||
days_until_final_expiry?: number | null
|
||||
expiry_estimate_status?: string | null
|
||||
is_expiring?: boolean
|
||||
}
|
||||
|
||||
export const getExpiryEstimateText = (estimate: ExpiryEstimate): string => {
|
||||
if (estimate.expiry_estimate_status === 'exact') {
|
||||
return estimate.estimated_final_expires_at
|
||||
? formatDateTime(estimate.estimated_final_expires_at)
|
||||
: '-'
|
||||
}
|
||||
|
||||
return estimate.expiry_estimate_status ? '待激活后起算' : '-'
|
||||
}
|
||||
|
||||
export const getExpiryEstimateTooltip = (estimate: ExpiryEstimate): string => {
|
||||
if (!estimate.is_expiring || estimate.days_until_final_expiry == null) return ''
|
||||
return `剩余${estimate.days_until_final_expiry}天`
|
||||
}
|
||||
@@ -7,3 +7,4 @@ export * from './validate'
|
||||
export * from './calculate'
|
||||
export * from './voucher'
|
||||
export * from './apiRateLimit'
|
||||
export * from './expiryEstimate'
|
||||
|
||||
@@ -86,6 +86,14 @@
|
||||
<ElDescriptionsItem label="所有套餐剩余量">
|
||||
{{ formatUsageSummaryMb(cardInfo?.total_virtual_remaining_mb) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="预计套餐到期时间">
|
||||
<span
|
||||
:class="{ 'expiry-estimate--expiring': cardInfo?.is_expiring }"
|
||||
:title="getExpiryEstimateTooltip(cardInfo)"
|
||||
>
|
||||
{{ getExpiryEstimateText(cardInfo) }}
|
||||
</span>
|
||||
</ElDescriptionsItem>
|
||||
|
||||
<template v-if="cardInfo?.bound_device_id">
|
||||
<ElDescriptionsItem label="绑定设备号">
|
||||
@@ -172,6 +180,14 @@
|
||||
<ElDescriptionsItem label="所有套餐剩余量">
|
||||
{{ formatUsageSummaryMb(cardInfo?.total_virtual_remaining_mb) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="预计套餐到期时间">
|
||||
<span
|
||||
:class="{ 'expiry-estimate--expiring': cardInfo?.is_expiring }"
|
||||
:title="getExpiryEstimateTooltip(cardInfo)"
|
||||
>
|
||||
{{ getExpiryEstimateText(cardInfo) }}
|
||||
</span>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="最后同步时间">{{
|
||||
formatDateTime(deviceRealtime?.last_sync_time) || '-'
|
||||
}}</ElDescriptionsItem>
|
||||
@@ -577,6 +593,7 @@
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { getExpiryEstimateText, getExpiryEstimateTooltip } from '@/utils/business/expiryEstimate'
|
||||
|
||||
// Props
|
||||
interface BindingCard {
|
||||
@@ -624,6 +641,10 @@
|
||||
gateway_extend?: string
|
||||
total_virtual_used_mb?: number | null
|
||||
total_virtual_remaining_mb?: number | null
|
||||
estimated_final_expires_at?: string | null
|
||||
days_until_final_expiry?: number | null
|
||||
expiry_estimate_status?: string
|
||||
is_expiring?: boolean
|
||||
last_sync_time?: string | null
|
||||
last_data_check_at?: string | null
|
||||
last_real_name_check_at?: string | null
|
||||
@@ -965,6 +986,11 @@
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.expiry-estimate--expiring {
|
||||
font-weight: 500;
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
||||
@@ -140,6 +140,10 @@ export function useAssetInfo() {
|
||||
gateway_extend: data.gateway_extend ?? '',
|
||||
total_virtual_used_mb: data.total_virtual_used_mb ?? null,
|
||||
total_virtual_remaining_mb: data.total_virtual_remaining_mb ?? null,
|
||||
estimated_final_expires_at: data.estimated_final_expires_at ?? null,
|
||||
days_until_final_expiry: data.days_until_final_expiry ?? null,
|
||||
expiry_estimate_status: data.expiry_estimate_status,
|
||||
is_expiring: data.is_expiring ?? false,
|
||||
bound_device_id: data.bound_device_id,
|
||||
bound_device_no: data.bound_device_no || '',
|
||||
bound_device_name: data.bound_device_name || '',
|
||||
|
||||
@@ -35,6 +35,10 @@ export interface AssetInfo {
|
||||
gateway_extend?: string
|
||||
total_virtual_used_mb?: number | null
|
||||
total_virtual_remaining_mb?: number | null
|
||||
estimated_final_expires_at?: string | null
|
||||
days_until_final_expiry?: number | null
|
||||
expiry_estimate_status?: string
|
||||
is_expiring?: boolean
|
||||
last_sync_time?: string | null
|
||||
last_data_check_at?: string | null
|
||||
last_real_name_check_at?: string | null
|
||||
|
||||
@@ -995,6 +995,7 @@
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { getExpiryEstimateText, getExpiryEstimateTooltip } from '@/utils/business/expiryEstimate'
|
||||
import type { PackageSeriesResponse } from '@/types/api'
|
||||
import type { EnterpriseItem } from '@/types/api/enterprise'
|
||||
import type {
|
||||
@@ -1606,6 +1607,7 @@
|
||||
{ label: '设备名称', prop: 'device_name' },
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
{ label: '套餐系列', prop: 'series_name' },
|
||||
{ label: '预计套餐到期时间', prop: 'estimated_final_expires_at' },
|
||||
{ label: '设备型号', prop: 'device_model' },
|
||||
{ label: '设备类型', prop: 'device_type' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
@@ -1890,6 +1892,20 @@
|
||||
minWidth: 180,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'estimated_final_expires_at',
|
||||
label: '预计套餐到期时间',
|
||||
width: 180,
|
||||
formatter: (row: Device) =>
|
||||
h(
|
||||
'span',
|
||||
{
|
||||
class: { 'expiry-estimate--expiring': row.is_expiring },
|
||||
title: getExpiryEstimateTooltip(row)
|
||||
},
|
||||
getExpiryEstimateText(row)
|
||||
)
|
||||
},
|
||||
{
|
||||
prop: 'device_model',
|
||||
label: '设备型号',
|
||||
@@ -3201,4 +3217,9 @@
|
||||
:deep(.el-table__row.table-row-with-context-menu) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.expiry-estimate--expiring {
|
||||
font-weight: 500;
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1045,6 +1045,7 @@
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { getExpiryEstimateText, getExpiryEstimateTooltip } from '@/utils/business/expiryEstimate'
|
||||
import { formatRemainingTime, FrontendRateLimitError } from '@/utils/business/apiRateLimit'
|
||||
import type {
|
||||
StandaloneIotCard,
|
||||
@@ -1946,6 +1947,7 @@
|
||||
{ label: '绑定设备虚拟号', prop: 'device_virtual_no' },
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
{ label: '套餐系列', prop: 'series_name' },
|
||||
{ label: '预计套餐到期时间', prop: 'estimated_final_expires_at' },
|
||||
{ label: '自然月累计流量(MB)', prop: 'current_month_usage_mb' },
|
||||
{ label: '本月开始日期', prop: 'current_month_start_date' },
|
||||
{ label: '上月流量总量(MB)', prop: 'last_month_total_mb' },
|
||||
@@ -2117,6 +2119,20 @@
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: StandaloneIotCard) => row.series_name || '-'
|
||||
},
|
||||
{
|
||||
prop: 'estimated_final_expires_at',
|
||||
label: '预计套餐到期时间',
|
||||
width: 180,
|
||||
formatter: (row: StandaloneIotCard) =>
|
||||
h(
|
||||
'span',
|
||||
{
|
||||
class: { 'expiry-estimate--expiring': row.is_expiring },
|
||||
title: getExpiryEstimateTooltip(row)
|
||||
},
|
||||
getExpiryEstimateText(row)
|
||||
)
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
@@ -3027,4 +3043,9 @@
|
||||
:deep(.el-table__row.table-row-with-context-menu) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.expiry-estimate--expiring {
|
||||
font-weight: 500;
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user