Files
tyc-webview-v2/src/ui/CBehaviorRiskScan.vue

748 lines
26 KiB
Vue
Raw Normal View History

2026-01-22 16:03:28 +08:00
<script setup>
const props = defineProps({
data: {
type: Object,
default: () => ({})
},
apiId: {
type: String,
default: '',
},
index: {
type: Number,
default: 0,
},
notifyRiskStatus: {
type: Function,
default: () => { },
},
})
// 风险等级转换为文字描述
const riskLevelText = (level, type) => {
if (type === 'black_gray_level') {
const levels = {
'': '无风险',
1: '低风险',
2: '中等风险',
3: '高风险',
4: '极高风险',
}
return levels[level] || '未知风险'
} else if (type === 'telefraud_level') {
const levels = {
0: '无风险',
1: '极低风险',
2: '低风险',
3: '中低风险',
4: '中等风险',
5: '高风险',
6: '极高风险',
}
return levels[level] || '未知风险'
} else if (type === 'frg_list_level') {
if (level >= '3' && level <= '5') return '低风险团伙'
if (level >= '6' && level <= '7') return '中风险团伙'
if (level >= '8' && level <= '10') return '高风险团伙'
return '无风险'
} else if (type === 'risk_level') {
const levels = {
A: '无风险',
F: '低风险',
C: '中风险',
D: '中风险',
B: '高风险',
E: '高风险',
}
return levels[level] || '未知风险'
} else if (type === 'gaming') {
const levelNum = parseInt(level)
if (levelNum === 0) return '无风险'
if (levelNum > 0 && levelNum <= 20) return '极低风险'
if (levelNum > 20 && levelNum <= 40) return '低风险'
if (levelNum > 40 && levelNum <= 60) return '中等风险'
if (levelNum > 60 && levelNum <= 80) return '高风险'
if (levelNum > 80) return '极高风险'
return '未知风险'
}
return '未知风险'
}
// 风险等级转换为颜色
const riskLevelColor = (level, type) => {
if (type === 'black_gray_level') {
if (level === '' || level === '1') return 'bg-gradient-to-r from-emerald-400 to-teal-500'
if (level === '2') return 'bg-gradient-to-r from-amber-400 to-yellow-500'
if (level === '3') return 'bg-gradient-to-r from-orange-400 to-amber-600'
if (level === '4') return 'bg-gradient-to-r from-rose-400 to-red-500'
return 'bg-gradient-to-r from-gray-400 to-gray-500'
} else if (type === 'telefraud_level') {
if (level === '0') return 'bg-gradient-to-r from-emerald-400 to-teal-500'
if (level === '1' || level === '2') return 'bg-gradient-to-r from-teal-300 to-green-400'
if (level === '3' || level === '4') return 'bg-gradient-to-r from-amber-400 to-yellow-500'
if (level === '5') return 'bg-gradient-to-r from-orange-400 to-amber-600'
if (level === '6') return 'bg-gradient-to-r from-rose-400 to-red-500'
return 'bg-gradient-to-r from-gray-400 to-gray-500'
} else if (type === 'frg_list_level') {
if (level >= '3' && level <= '5') return 'bg-gradient-to-r from-emerald-400 to-teal-500'
if (level >= '6' && level <= '7') return 'bg-gradient-to-r from-amber-400 to-yellow-500'
if (level >= '8' && level <= '10') return 'bg-gradient-to-r from-rose-400 to-red-500'
return 'bg-gradient-to-r from-gray-400 to-gray-500'
} else if (type === 'risk_level') {
if (level === 'A') return 'bg-gradient-to-r from-emerald-400 to-teal-500'
if (level === 'F') return 'bg-gradient-to-r from-amber-400 to-yellow-500'
if (level === 'C' || level === 'D') return 'bg-gradient-to-r from-orange-400 to-amber-600'
if (level === 'B' || level === 'E') return 'bg-gradient-to-r from-rose-400 to-red-500'
return 'bg-gradient-to-r from-gray-400 to-gray-500'
} else if (type === 'gaming') {
const levelNum = parseInt(level)
if (levelNum === 0) return 'bg-gradient-to-r from-emerald-400 to-teal-500'
if (levelNum > 0 && levelNum <= 20) return 'bg-gradient-to-r from-teal-300 to-green-400'
if (levelNum > 20 && levelNum <= 40) return 'bg-gradient-to-r from-green-400 to-green-500'
if (levelNum > 40 && levelNum <= 60) return 'bg-gradient-to-r from-amber-400 to-yellow-500'
if (levelNum > 60 && levelNum <= 80) return 'bg-gradient-to-r from-orange-400 to-amber-600'
if (levelNum > 80) return 'bg-gradient-to-r from-rose-400 to-red-500'
return 'bg-gradient-to-r from-gray-400 to-gray-500'
}
return 'bg-gradient-to-r from-gray-400 to-gray-500'
}
// 根据风险类型获取名称
const getRiskTypeName = type => {
const types = {
110: '疑似欺诈',
130: '疑似赌博庄家',
150: '疑似赌博玩家',
170: '疑似涉赌跑分',
}
return types[type] || '未知类型'
}
// 获取团伙规模描述
const getGroupSizeDesc = code => {
const sizes = {
a: '小规模(少于50人)',
b: '中等规模(50-100人)',
c: '大规模(100-500人)',
d: '超大规模(500人以上)',
}
return sizes[code] || '未知规模'
}
// 获取风险图标
const getRiskIcon = type => {
switch (type) {
case '110':
return 'fa-exclamation-triangle'
case '130':
return 'fa-dice'
case '150':
return 'fa-gamepad'
case '170':
return 'fa-money-bill-wave'
default:
return 'fa-question-circle'
}
}
// 获取不良记录详情
const getRiskLevelDetail = level => {
switch (level) {
case 'A':
return '无任何不良记录'
case 'F':
return '涉稳、寻衅滋事'
case 'C':
case 'D':
return '吸毒、涉毒、犯罪前科'
case 'B':
case 'E':
return '涉案人员、在逃、犯罪嫌疑人'
default:
return '未知记录'
}
}
// 风险评估总结
const getRiskSummary = () => {
if (!props.data) return { text: '无法评估风险', level: 'low', color: 'text-gray-500' }
let highRiskCount = 0
let mediumRiskCount = 0
// 检查黑灰产等级
if (props.data.black_gray_level && parseInt(props.data.black_gray_level) > 2) {
highRiskCount++
} else if (props.data.black_gray_level && parseInt(props.data.black_gray_level) === 2) {
mediumRiskCount++
}
// 检查电诈风险
if (props.data.telefraud_level && parseInt(props.data.telefraud_level) > 4) {
highRiskCount++
} else if (props.data.telefraud_level && parseInt(props.data.telefraud_level) > 2) {
mediumRiskCount++
}
// 检查团伙欺诈
if (
props.data.fraud_group &&
props.data.fraud_group.frg_list_level &&
parseInt(props.data.fraud_group.frg_list_level) > 7
) {
highRiskCount++
} else if (
props.data.fraud_group &&
props.data.fraud_group.frg_list_level &&
parseInt(props.data.fraud_group.frg_list_level) > 5
) {
mediumRiskCount++
}
// 检查风险等级
if (props.data.risk_level && props.data.risk_level.risk_level) {
if (['B', 'E'].includes(props.data.risk_level.risk_level)) {
highRiskCount++
} else if (['C', 'D'].includes(props.data.risk_level.risk_level)) {
mediumRiskCount++
} else if (props.data.risk_level.risk_level === 'F') {
// 低风险,不增加计数
}
}
// 检查反诈反赌核验
if (props.data.anti_fraud_gaming) {
props.data.anti_fraud_gaming.forEach(item => {
const levelNum = parseInt(item.riskLevel)
if (levelNum > 60) {
highRiskCount++
} else if (levelNum > 40) {
mediumRiskCount++
}
})
}
if (highRiskCount > 0) {
return {
text: '该用户存在较高风险行为,建议进行进一步核实和监控',
level: 'high',
color: 'text-red-500',
}
} else if (mediumRiskCount > 0) {
return {
text: '该用户存在一定风险行为,建议提高警惕',
level: 'medium',
color: 'text-yellow-500',
}
} else {
return {
text: '该用户行为正常,风险较低',
level: 'low',
color: 'text-green-500',
}
}
}
const summary = getRiskSummary()
// 计算风险评分0-100分分数越高越安全
const riskScore = computed(() => {
// 计算总风险项数量
const totalRiskCount = Object.values(summary).reduce((sum, item) => sum + item.count, 0);
// 根据风险项数量计算评分
// 0项100分最安全
// 1-2项80分较安全
// 3-5项60分中等风险
// 6-10项40分较高风险
// 10项以上20分高风险
if (totalRiskCount === 0) return 100;
if (totalRiskCount <= 2) return 80;
if (totalRiskCount <= 5) return 60;
if (totalRiskCount <= 10) return 40;
return 20;
});
// 使用 composable 通知父组件风险评分
useRiskNotifier(props, riskScore);
// 暴露给父组件
defineExpose({
riskScore
});
</script>
<template>
<div class="card main-card">
<div v-if="!data || Object.keys(data).length === 0" class="py-4 text-center text-gray-500">
暂无风险行为扫描数据
</div>
<div v-else class="risk-content">
<!-- 风险总结 -->
<div class="summary-card" :class="{
'border-red-500 glow-red': summary.level === 'high',
'border-yellow-500 glow-yellow': summary.level === 'medium',
'border-green-500 glow-green': summary.level === 'low',
}">
<div class="flex items-center">
<div class="summary-icon" :class="summary.color">
<i class="fas" :class="summary.level === 'high'
? 'fa-exclamation-triangle'
: summary.level === 'medium'
? 'fa-exclamation-circle'
: 'fa-check-circle'
"></i>
</div>
<div class="font-bold text-lg" :class="summary.color">风险评估总结</div>
</div>
<div class="mt-1 text-gray-700">{{ summary.text }}</div>
</div>
<div class="grid-container">
<!-- 左侧列 -->
<div class="grid-left">
<!-- 黑灰产等级 -->
<!-- <div class="risk-section hover-lift">
<div class="section-title flex items-center">
<div class="title-icon bg-indigo-100 text-indigo-600">
<i class="fas fa-user-secret"></i>
</div>
<span>黑灰产等级</span>
</div>
<div class="section-content">
<div class="risk-level-indicator">
<div class="indicator-label">风险等级</div>
<div class="indicator-bar">
<div class="indicator-value" :class="riskLevelColor(data.black_gray_level || '', 'black_gray_level')"
:style="{
width: data.black_gray_level ? `${Math.min(parseInt(data.black_gray_level) * 25, 100)}%` : '0%',
}"></div>
</div>
<div class="indicator-text" :class="{
'text-green-500': (data.black_gray_level || '') === '' || (data.black_gray_level || '') === '1',
'text-yellow-500': (data.black_gray_level || '') === '2',
'text-orange-500': (data.black_gray_level || '') === '3',
'text-red-500': (data.black_gray_level || '') === '4',
}">
{{ riskLevelText(data.black_gray_level || '', 'black_gray_level') }}
</div>
</div>
<div class="description">黑灰产等级评估用户是否参与非法活动等级越高风险越大</div>
</div>
</div> -->
<!-- 电诈风险预警 -->
<!-- <div class="risk-section hover-lift">
<div class="section-title flex items-center">
<div class="title-icon bg-red-100 text-red-600">
<i class="fas fa-phone-slash"></i>
</div>
<span>电诈风险预警</span>
</div>
<div class="section-content">
<div class="risk-level-indicator">
<div class="indicator-label">风险等级</div>
<div class="indicator-bar">
<div class="indicator-value" :class="riskLevelColor(data.telefraud_level || '0', 'telefraud_level')"
:style="{ width: `${Math.min(parseInt(data.telefraud_level || '0') * 16.6, 100)}%` }"></div>
</div>
<div class="indicator-text" :class="{
'text-green-500':
(data.telefraud_level || '0') === '0' ||
(data.telefraud_level || '0') === '1' ||
(data.telefraud_level || '0') === '2',
'text-yellow-500': (data.telefraud_level || '0') === '3' || (data.telefraud_level || '0') === '4',
'text-orange-500': (data.telefraud_level || '0') === '5',
'text-red-500': (data.telefraud_level || '0') === '6',
}">
{{ riskLevelText(data.telefraud_level || '0', 'telefraud_level') }}
</div>
</div>
<div class="description">电诈风险预警评估用户是否涉及电信诈骗活动值越大风险越高</div>
</div>
</div> -->
<!-- 综合风险等级 -->
<!-- <div class="risk-section hover-lift">
<div class="section-title flex items-center">
<div class="title-icon bg-emerald-100 text-emerald-600">
<i class="fas fa-shield-alt"></i>
</div>
<span>不良个人核查</span>
</div>
<div class="section-content">
<div v-if="data.risk_level" class="flex items-center justify-center py-3">
<div
class="risk-level-badge"
:class="{
'bg-green-100 text-green-700 badge-pulse-green': data.risk_level.risk_level === 'A',
'bg-yellow-100 text-yellow-700 badge-pulse-yellow': data.risk_level.risk_level === 'F',
'bg-orange-100 text-orange-700 badge-pulse-orange': ['C', 'D'].includes(data.risk_level.risk_level),
'bg-red-100 text-red-700 badge-pulse-red': ['B', 'E'].includes(data.risk_level.risk_level),
}"
>
<span class="text-xl font-bold">{{
riskLevelText(data.risk_level.risk_level || 'A', 'risk_level')
}}</span>
</div>
<div class="ml-4 text-sm">
<div class="font-medium">详情:</div>
<div
class="mt-1"
:class="{
'text-green-600': data.risk_level.risk_level === 'A',
'text-yellow-600': data.risk_level.risk_level === 'F',
'text-orange-600': ['C', 'D'].includes(data.risk_level.risk_level),
'text-red-600': ['B', 'E'].includes(data.risk_level.risk_level),
}"
>
{{ getRiskLevelDetail(data.risk_level.risk_level || 'A') }}
</div>
</div>
</div>
<div v-else class="text-center py-2 text-gray-500">暂无不良个人核查数据</div>
<div class="description">不良个人核查评估用户的风险状况从无风险到高风险分级</div>
</div>
</div> -->
</div>
<div class="grid-right">
<!-- <div class="risk-section hover-lift">
<div class="section-title flex items-center">
<div class="title-icon bg-amber-100 text-amber-600">
<i class="fas fa-users-slash"></i>
</div>
<span>团伙欺诈排查</span>
</div>
<div class="section-content">
<div v-if="data.fraud_group" class="flex flex-col md:flex-row gap-3">
<div class="risk-level-indicator flex-1">
<div class="indicator-label">团伙风险等级</div>
<div class="indicator-bar">
<div class="indicator-value"
:class="riskLevelColor(data.fraud_group.frg_list_level || '3', 'frg_list_level')" :style="{
width: `${Math.min((parseInt(data.fraud_group.frg_list_level || '3') - 2) * 12.5, 100)}%`,
}"></div>
</div>
<div class="indicator-text" :class="{
'text-green-500': parseInt(data.fraud_group.frg_list_level || '3') <= 5,
'text-yellow-500':
parseInt(data.fraud_group.frg_list_level || '3') >= 6 &&
parseInt(data.fraud_group.frg_list_level || '3') <= 7,
'text-red-500': parseInt(data.fraud_group.frg_list_level || '3') >= 8,
}">
{{ riskLevelText(data.fraud_group.frg_list_level || '3', 'frg_list_level') }}
</div>
</div>
<div class="group-size flex-1">
<div class="font-medium text-gray-700">团伙规模</div>
<div class="mt-2 flex items-center">
<i class="fas fa-users text-blue-500 mr-2 text-xl"></i>
<span>{{ getGroupSizeDesc(data.fraud_group.frg_group_num || 'a') }}</span>
</div>
</div>
</div>
<div v-else class="text-center py-2 text-gray-500">暂无团伙欺诈数据</div>
<div class="description mt-1">团伙欺诈排查评估用户是否属于欺诈团伙及团伙规模大小</div>
</div>
</div> -->
<div class="risk-section hover-lift">
<div class="section-title flex items-center">
<div class="title-icon bg-purple-100 text-purple-600">
<i class="fas fa-dice-slash"></i>
</div>
<span>反诈反赌核验</span>
</div>
<div class="section-content">
<div v-if="data.anti_fraud_gaming && data.anti_fraud_gaming.length > 0" class="grid grid-cols-1 gap-3">
<div v-for="(item, index) in data.anti_fraud_gaming" :key="index" class="gaming-item" :class="parseInt(item.riskLevel) === 0
? 'border-green-500'
: parseInt(item.riskLevel) < 4
? 'border-green-400'
: parseInt(item.riskLevel) < 7
? 'border-yellow-500'
: 'border-red-500'
">
<div class="gaming-icon" :class="parseInt(item.riskLevel) === 0
? 'bg-green-100 text-green-500'
: parseInt(item.riskLevel) < 4
? 'bg-green-100 text-green-500'
: parseInt(item.riskLevel) < 7
? 'bg-yellow-100 text-yellow-600'
: 'bg-red-100 text-red-500'
">
<i class="fas" :class="getRiskIcon(item.riskType)"></i>
</div>
<div class="flex-1">
<div class="font-medium text-sm">{{ getRiskTypeName(item.riskType) }}</div>
<div class="flex items-center mt-2">
<div class="progress-container">
<div class="progress-bar" :class="riskLevelColor(item.riskLevel, 'gaming')"
:style="{ width: `${Math.min(parseInt(item.riskLevel), 100)}%` }"></div>
</div>
<span class="risk-level-text" :class="{
'text-green-500': parseInt(item.riskLevel) <= 20,
'text-green-600': parseInt(item.riskLevel) > 20 && parseInt(item.riskLevel) <= 40,
'text-yellow-500': parseInt(item.riskLevel) > 40 && parseInt(item.riskLevel) <= 60,
'text-orange-500': parseInt(item.riskLevel) > 60 && parseInt(item.riskLevel) <= 80,
'text-red-500': parseInt(item.riskLevel) > 80,
}">
{{ riskLevelText(item.riskLevel, 'gaming') }}
</span>
</div>
</div>
</div>
</div>
<div v-else class="text-center py-2 text-gray-500">暂无反诈反赌核验数据</div>
<div class="description mt-1">反诈反赌核验评估用户是否有涉及诈骗或赌博活动的风险</div>
</div>
</div>
<div class="security-tips hover-lift">
<div class="flex items-center">
<div class="title-icon bg-blue-100 text-blue-600 mr-2">
<i class="fas fa-lightbulb"></i>
</div>
<div class="font-bold text-blue-700">安全建议</div>
</div>
<div class="tip-list">
<div class="tip-item">
<i class="fas fa-check-circle text-green-500 mr-1"></i>
<span>定期更新密码使用复杂且不易猜测的密码</span>
</div>
<div class="tip-item">
<i class="fas fa-check-circle text-green-500 mr-1"></i>
<span>开启双因素认证提高账户安全性</span>
</div>
<div class="tip-item">
<i class="fas fa-check-circle text-green-500 mr-1"></i>
<span>不点击来源不明的链接或下载不明文件</span>
</div>
<div class="tip-item">
<i class="fas fa-check-circle text-green-500 mr-1"></i>
<span>不向陌生人透露个人敏感信息</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.main-card {
@apply bg-white shadow-md rounded-xl p-4 mb-3 border border-gray-100;
}
.risk-content {
@apply space-y-4;
}
.grid-container {
@apply grid grid-cols-1 md:grid-cols-2 gap-4;
}
.grid-left,
.grid-right {
@apply flex flex-col gap-4;
}
.summary-card {
@apply p-4 rounded-xl shadow-sm bg-gradient-to-br from-sky-50 to-indigo-100 border-l-4 transition-all duration-300;
}
.glow-red {
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.15);
border-color: rgba(239, 68, 68, 0.6);
}
.glow-yellow {
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.15);
border-color: rgba(245, 158, 11, 0.6);
}
.glow-green {
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.15);
border-color: rgba(16, 185, 129, 0.6);
}
.summary-icon {
@apply mr-2 text-xl;
}
.risk-section {
@apply bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden transition-all duration-300;
}
.hover-lift:hover {
transform: translateY(-3px);
box-shadow:
0 8px 16px -2px rgba(0, 0, 0, 0.1),
0 4px 8px -2px rgba(0, 0, 0, 0.05);
}
.section-title {
@apply bg-gradient-to-r from-gray-50 to-gray-100 px-4 py-3 font-bold text-gray-700 border-b border-gray-200 flex items-center;
}
.title-icon {
@apply w-7 h-7 rounded-full flex items-center justify-center mr-3 shadow-sm;
}
.section-content {
@apply p-4;
}
.risk-level-indicator {
@apply mb-2;
}
.indicator-label {
@apply text-gray-700 font-medium mb-1 text-sm;
}
.indicator-bar {
@apply w-full bg-gray-200 rounded-full h-3 overflow-hidden shadow-inner;
}
.indicator-value {
@apply h-3 rounded-full transition-all duration-500;
}
.indicator-text {
@apply mt-1 font-medium text-sm;
}
.description {
@apply text-xs text-gray-500 mt-2 italic;
}
.risk-level-badge {
@apply flex flex-col items-center justify-center w-20 h-20 rounded-full shadow-md border transition-transform duration-300 backdrop-blur-sm;
}
.badge-pulse-green {
background: linear-gradient(135deg, rgba(16, 185, 129, 0.15), rgba(5, 150, 105, 0.3));
border-color: rgba(5, 150, 105, 0.4);
animation: pulse-green 3s infinite;
}
.badge-pulse-yellow {
background: linear-gradient(135deg, rgba(245, 158, 11, 0.15), rgba(217, 119, 6, 0.3));
border-color: rgba(217, 119, 6, 0.4);
animation: pulse-yellow 3s infinite;
}
.badge-pulse-orange {
background: linear-gradient(135deg, rgba(249, 115, 22, 0.15), rgba(234, 88, 12, 0.3));
border-color: rgba(234, 88, 12, 0.4);
animation: pulse-orange 3s infinite;
}
.badge-pulse-red {
background: linear-gradient(135deg, rgba(239, 68, 68, 0.15), rgba(220, 38, 38, 0.3));
border-color: rgba(220, 38, 38, 0.4);
animation: pulse-red 3s infinite;
}
@keyframes pulse-green {
0% {
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.3);
}
70% {
box-shadow: 0 0 0 8px rgba(16, 185, 129, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
}
}
@keyframes pulse-yellow {
0% {
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.3);
}
70% {
box-shadow: 0 0 0 8px rgba(245, 158, 11, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0);
}
}
@keyframes pulse-orange {
0% {
box-shadow: 0 0 0 0 rgba(249, 115, 22, 0.3);
}
70% {
box-shadow: 0 0 0 8px rgba(249, 115, 22, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(249, 115, 22, 0);
}
}
@keyframes pulse-red {
0% {
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.3);
}
70% {
box-shadow: 0 0 0 8px rgba(239, 68, 68, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0);
}
}
.group-size {
@apply bg-gradient-to-br from-gray-50 to-gray-100 p-3 rounded-lg shadow-sm;
}
.gaming-item {
@apply flex items-center bg-white shadow-sm rounded-lg p-3 border-l-2 transition-all duration-300;
}
.gaming-item:hover {
@apply shadow-md;
transform: scale(1.01);
}
.gaming-icon {
@apply w-9 h-9 flex items-center justify-center rounded-full mr-3 shadow-sm;
}
.progress-container {
@apply w-full bg-gray-200 rounded-full h-3 mr-3 flex-1 shadow-inner;
}
.progress-bar {
@apply h-3 rounded-full transition-all duration-500;
}
.risk-level-text {
@apply text-xs whitespace-nowrap min-w-[3.5rem] text-right font-semibold;
}
.security-tips {
@apply bg-gradient-to-br from-sky-50 to-indigo-100 rounded-xl p-4 shadow-sm border border-blue-200;
}
.tip-list {
@apply mt-3 space-y-2;
}
.tip-item {
@apply flex items-start text-sm text-gray-700 bg-white p-2 rounded-lg shadow-sm border border-gray-100;
}
</style>