404 lines
16 KiB
Vue
404 lines
16 KiB
Vue
<template>
|
||
<div class="mobile-online-duration card">
|
||
<div class="verification-section mb-4">
|
||
<div class="bg-white rounded-xl border border-gray-200 p-4 relative">
|
||
<div class="flex items-center mb-3">
|
||
<div class="w-8 h-8 flex items-center justify-center mr-3">
|
||
<img src="@/assets/images/report/sjh.png" alt="手机在网时长" class="w-8 h-8 object-contain" />
|
||
</div>
|
||
<span class="font-bold text-gray-800">手机在网时长</span>
|
||
</div>
|
||
|
||
<!-- 查询结果 -->
|
||
<div class="verification-details">
|
||
<div v-if="hasData" class="space-y-4">
|
||
<!-- 运营商 -->
|
||
<div class="flex justify-between items-center p-3 bg-gray-50 rounded-lg">
|
||
<span class="text-sm text-gray-600">运营商</span>
|
||
<span class="text-sm font-bold text-gray-800">{{ operators || '-' }}</span>
|
||
</div>
|
||
|
||
<!-- 在网时长信息 -->
|
||
<div class="">
|
||
<!-- 在网时长区间 -->
|
||
<div class="flex justify-between items-center">
|
||
<span class="text-sm text-gray-600">在网时长</span>
|
||
<span class="text-sm font-bold text-gray-800">{{ friendlyDurationText }}</span>
|
||
</div>
|
||
|
||
<!-- 进度条 -->
|
||
<div v-if="showProgressBar" class="mt-16">
|
||
<div class="relative">
|
||
<!-- 当前值标签(上方) -->
|
||
<div class="absolute -top-12 left-1/2 transform -translate-x-1/2 whitespace-nowrap transition-all duration-700 ease-out z-30"
|
||
:class="progressBarTextColor" :style="{ left: progressBarWidth + '%' }">
|
||
<div class="px-3 py-1.5 rounded-lg shadow-lg backdrop-blur-sm bg-white/90 border-2 font-semibold text-sm"
|
||
:class="progressBarBorderColor">
|
||
{{ friendlyDurationText }}
|
||
</div>
|
||
<!-- 小三角指向 -->
|
||
<div class="absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-[6px] border-r-[6px] border-t-[6px] border-transparent transition-colors duration-700"
|
||
:style="{ borderTopColor: progressBarArrowBorderColor }">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 进度条容器 -->
|
||
<div
|
||
class="relative h-6 bg-gradient-to-r from-gray-50 to-gray-100 rounded-full overflow-hidden shadow-inner border-2 border-gray-200/50">
|
||
<!-- 背景分段(渐变效果) -->
|
||
<div class="absolute inset-0 flex h-full">
|
||
<div
|
||
class="w-[15%] bg-gradient-to-br from-red-50 to-red-100/50 border-r border-red-200/60">
|
||
</div>
|
||
<div
|
||
class="w-[15%] bg-gradient-to-br from-orange-50 to-orange-100/50 border-r border-orange-200/60">
|
||
</div>
|
||
<div
|
||
class="w-[20%] bg-gradient-to-br from-yellow-50 to-yellow-100/50 border-r border-yellow-200/60">
|
||
</div>
|
||
<div
|
||
class="w-[25%] bg-gradient-to-br from-green-50 to-green-100/50 border-r border-green-200/60">
|
||
</div>
|
||
<div class="flex-1 bg-gradient-to-br from-blue-50 to-indigo-100/50"></div>
|
||
</div>
|
||
|
||
<!-- 进度条填充(渐变) -->
|
||
<div class="absolute inset-0 flex items-center">
|
||
<div class="h-full transition-all duration-700 ease-out rounded-full flex items-center justify-end pr-1.5 shadow-lg"
|
||
:class="progressBarGradient" :style="{ width: progressBarWidth + '%' }">
|
||
<!-- 标记点(带光晕效果) -->
|
||
<div class="relative">
|
||
<!-- 外圈光晕 -->
|
||
<div class="absolute inset-0 rounded-full animate-ping opacity-30"
|
||
:class="progressBarColor">
|
||
</div>
|
||
<!-- 标记点主体 -->
|
||
<div class="relative w-3 h-3 rounded-full bg-white shadow-xl"
|
||
:class="progressBarBorderColor" style="border-width: 2.5px;">
|
||
<!-- 内圈高光 -->
|
||
<div class="absolute inset-0.5 rounded-full bg-white/50"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 区间分割线(更精致) -->
|
||
<div class="absolute inset-0 flex h-full pointer-events-none">
|
||
<div class="w-[15%] border-r-2 border-dashed border-gray-300/70"></div>
|
||
<div class="w-[15%] border-r-2 border-dashed border-gray-300/70"></div>
|
||
<div class="w-[20%] border-r-2 border-dashed border-gray-300/70"></div>
|
||
<div class="w-[25%] border-r-2 border-dashed border-gray-300/70"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 区间标签(底部) -->
|
||
<div class="relative mt-2 flex justify-between text-sm text-gray-600">
|
||
<span class="font-bold">0</span>
|
||
<span class="font-bold">3</span>
|
||
<span class="font-bold">6</span>
|
||
<span class="font-bold">12</span>
|
||
<span class="font-bold">24</span>
|
||
<span class="font-bold">∞</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 特殊状态说明 -->
|
||
<div v-if="isSpecialStatus" class="mt-3 p-3 rounded-lg" :class="specialStatusClass">
|
||
<div class="text-sm font-medium" :class="specialStatusTextClass">
|
||
{{ specialStatusText }}
|
||
</div>
|
||
<div class="text-sm mt-1" :class="specialStatusDescClass">
|
||
{{ specialStatusDesc }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-else class="p-8 text-center text-gray-500">
|
||
<div class="flex flex-col items-center justify-center">
|
||
<van-empty description="暂无查询结果" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed } from 'vue'
|
||
import { useRiskNotifier } from '@/composables/useRiskNotifier'
|
||
|
||
const props = defineProps({
|
||
data: {
|
||
type: Object,
|
||
required: true,
|
||
default: () => ({})
|
||
},
|
||
apiId: {
|
||
type: String,
|
||
default: '',
|
||
},
|
||
index: {
|
||
type: Number,
|
||
default: 0,
|
||
},
|
||
notifyRiskStatus: {
|
||
type: Function,
|
||
default: () => { },
|
||
},
|
||
})
|
||
|
||
// 获取数据
|
||
const reportData = computed(() => {
|
||
return props.data?.data?.data || props.data?.data || props.data || {}
|
||
})
|
||
|
||
// 运营商
|
||
const operators = computed(() => {
|
||
return reportData.value.operators || ''
|
||
})
|
||
|
||
// 在网时长
|
||
const inTime = computed(() => {
|
||
const value = reportData.value.inTime
|
||
if (value === undefined || value === null || value === '') {
|
||
return null
|
||
}
|
||
return String(value)
|
||
})
|
||
|
||
// 是否有数据
|
||
const hasData = computed(() => {
|
||
return inTime.value !== null || operators.value
|
||
})
|
||
|
||
// 是否显示进度条
|
||
const showProgressBar = computed(() => {
|
||
const value = inTime.value
|
||
return value !== null && value !== '99' && value !== '-1'
|
||
})
|
||
|
||
// 是否特殊状态
|
||
const isSpecialStatus = computed(() => {
|
||
const value = inTime.value
|
||
return value === '99' || value === '-1'
|
||
})
|
||
|
||
// 在网时长文本(原始格式)
|
||
const durationText = computed(() => {
|
||
const value = inTime.value
|
||
if (value === null) return '-'
|
||
|
||
const durationMap = {
|
||
'0': '[0, 3) 个月',
|
||
'3': '[3, 6) 个月',
|
||
'6': '[6, 12) 个月',
|
||
'12': '[12, 24) 个月',
|
||
'24': '[24, +∞) 个月',
|
||
'99': '手机号已离网/新入网/手机状态异常',
|
||
'-1': '查无记录'
|
||
}
|
||
|
||
return durationMap[value] || '-'
|
||
})
|
||
|
||
// 友好的在网时长文本
|
||
const friendlyDurationText = computed(() => {
|
||
const value = inTime.value
|
||
if (value === null) return '-'
|
||
|
||
const friendlyMap = {
|
||
'0': '0-3个月(新入网)',
|
||
'3': '3-6个月(短期在网)',
|
||
'6': '6-12个月(中期在网)',
|
||
'12': '12-24个月(长期在网)',
|
||
'24': '24个月以上(稳定在网)',
|
||
'99': '状态异常',
|
||
'-1': '查无记录'
|
||
}
|
||
|
||
return friendlyMap[value] || '-'
|
||
})
|
||
|
||
// 进度条宽度(百分比)- 精准对应区间中点
|
||
const progressBarWidth = computed(() => {
|
||
const value = inTime.value
|
||
if (!value || value === '99' || value === '-1') return 0
|
||
|
||
// 区间分布:0-3(15%), 3-6(15%), 6-12(20%), 12-24(25%), 24+(25%)
|
||
// 计算每个区间的中点位置
|
||
const widthMap = {
|
||
'0': 7.5, // [0,3) 的中点 = 15% / 2 = 7.5%
|
||
'3': 22.5, // [3,6) 的中点 = 15% + 15% / 2 = 22.5%
|
||
'6': 40, // [6,12) 的中点 = 30% + 20% / 2 = 40%
|
||
'12': 57.5, // [12,24) 的中点 = 50% + 25% / 2 = 57.5%
|
||
'24': 87.5 // [24,+) 的中点 = 75% + 25% / 2 = 87.5%
|
||
}
|
||
|
||
return widthMap[value] || 0
|
||
})
|
||
|
||
// 进度条颜色
|
||
const progressBarColor = computed(() => {
|
||
const value = inTime.value
|
||
if (!value || value === '99' || value === '-1') return ''
|
||
|
||
const colorMap = {
|
||
'0': 'bg-red-500',
|
||
'3': 'bg-orange-500',
|
||
'6': 'bg-yellow-500',
|
||
'12': 'bg-green-500',
|
||
'24': 'bg-blue-500'
|
||
}
|
||
|
||
return colorMap[value] || 'bg-gray-500'
|
||
})
|
||
|
||
// 进度条渐变效果
|
||
const progressBarGradient = computed(() => {
|
||
const value = inTime.value
|
||
if (!value || value === '99' || value === '-1') return ''
|
||
|
||
const gradientMap = {
|
||
'0': 'bg-gradient-to-r from-red-400 via-red-500 to-red-600',
|
||
'3': 'bg-gradient-to-r from-orange-400 via-orange-500 to-orange-600',
|
||
'6': 'bg-gradient-to-r from-yellow-400 via-yellow-500 to-yellow-600',
|
||
'12': 'bg-gradient-to-r from-green-400 via-green-500 to-green-600',
|
||
'24': 'bg-gradient-to-r from-blue-400 via-blue-500 to-blue-600'
|
||
}
|
||
|
||
return gradientMap[value] || 'bg-gradient-to-r from-gray-400 to-gray-600'
|
||
})
|
||
|
||
// 进度条边框颜色
|
||
const progressBarBorderColor = computed(() => {
|
||
const value = inTime.value
|
||
if (!value || value === '99' || value === '-1') return 'border-gray-300'
|
||
|
||
const colorMap = {
|
||
'0': 'border-red-400',
|
||
'3': 'border-orange-400',
|
||
'6': 'border-yellow-400',
|
||
'12': 'border-green-400',
|
||
'24': 'border-blue-400'
|
||
}
|
||
|
||
return colorMap[value] || 'border-gray-300'
|
||
})
|
||
|
||
// 进度条文本颜色
|
||
const progressBarTextColor = computed(() => {
|
||
const value = inTime.value
|
||
if (!value || value === '99' || value === '-1') return 'text-gray-700'
|
||
|
||
const colorMap = {
|
||
'0': 'text-red-600',
|
||
'3': 'text-orange-600',
|
||
'6': 'text-yellow-600',
|
||
'12': 'text-green-600',
|
||
'24': 'text-blue-600'
|
||
}
|
||
|
||
return colorMap[value] || 'text-gray-700'
|
||
})
|
||
|
||
// 箭头边框颜色(用于内联样式)
|
||
const progressBarArrowBorderColor = computed(() => {
|
||
const value = inTime.value
|
||
if (!value || value === '99' || value === '-1') return '#d1d5db'
|
||
|
||
const colorMap = {
|
||
'0': '#ef4444', // red-500
|
||
'3': '#f97316', // orange-500
|
||
'6': '#eab308', // yellow-500
|
||
'12': '#22c55e', // green-500
|
||
'24': '#3b82f6' // blue-500
|
||
}
|
||
|
||
return colorMap[value] || '#6b7280'
|
||
})
|
||
|
||
// 特殊状态样式
|
||
const specialStatusClass = computed(() => {
|
||
const value = inTime.value
|
||
if (value === '99') return 'bg-orange-50 border border-orange-200'
|
||
if (value === '-1') return 'bg-gray-50 border border-gray-200'
|
||
return ''
|
||
})
|
||
|
||
const specialStatusTextClass = computed(() => {
|
||
const value = inTime.value
|
||
if (value === '99') return 'text-orange-700'
|
||
if (value === '-1') return 'text-gray-700'
|
||
return ''
|
||
})
|
||
|
||
const specialStatusDescClass = computed(() => {
|
||
const value = inTime.value
|
||
if (value === '99') return 'text-orange-600'
|
||
if (value === '-1') return 'text-gray-600'
|
||
return ''
|
||
})
|
||
|
||
// 特殊状态文本
|
||
const specialStatusText = computed(() => {
|
||
const value = inTime.value
|
||
if (value === '99') return '状态异常'
|
||
if (value === '-1') return '查无记录'
|
||
return ''
|
||
})
|
||
|
||
// 特殊状态描述
|
||
const specialStatusDesc = computed(() => {
|
||
const value = inTime.value
|
||
if (value === '99') return '手机号可能已离网、新入网或状态异常,无法准确查询在网时长'
|
||
if (value === '-1') return '系统中未查询到该手机号的在网时长记录'
|
||
return ''
|
||
})
|
||
|
||
// 计算风险评分(在网时长越长,风险越低)
|
||
const riskScore = computed(() => {
|
||
const value = inTime.value
|
||
|
||
if (!value || value === '-1') {
|
||
return 50 // 查无记录,中等风险
|
||
}
|
||
|
||
if (value === '99') {
|
||
return 30 // 状态异常,较高风险
|
||
}
|
||
|
||
// 在网时长越长,分数越高(越安全)
|
||
const scoreMap = {
|
||
'0': 20, // [0,3) 个月 - 高风险
|
||
'3': 40, // [3,6) 个月 - 中高风险
|
||
'6': 60, // [6,12) 个月 - 中等风险
|
||
'12': 80, // [12,24) 个月 - 低风险
|
||
'24': 100 // [24,+) 个月 - 最低风险
|
||
}
|
||
|
||
return scoreMap[value] || 50
|
||
})
|
||
|
||
// 使用 composable 通知父组件风险评分
|
||
useRiskNotifier(props, riskScore)
|
||
|
||
// 暴露给父组件
|
||
defineExpose({
|
||
riskScore
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.mobile-online-duration {
|
||
@apply space-y-4;
|
||
}
|
||
|
||
.verification-section {
|
||
.verification-details {
|
||
@apply mt-3;
|
||
}
|
||
}
|
||
</style>
|