|
|
<template> <div class="user-overview-container"> <div class="tab-header"> <div class="tab-item" :class="{ active: activeTab === 'overview' }" @click="activeTab = 'overview'" > 数据概览 </div> <div class="tab-item" :class="{ active: activeTab === 'detail' }" @click="activeTab = 'detail'" > 数据明细 </div> <div class="tab-note"> <span class="red-asterisk">*</span> 默认展示截止到今日的数据 </div> </div>
<!-- 数据概览 --> <div v-show="activeTab === 'overview'" class="tab-content overview-content"> <div class="stats-row"> <!-- 用户总数和登录总数 --> <div class="stat-card purple-gradient big-card"> <div class="card-title"> <el-icon><UserFilled /></el-icon> 用户注册总数 </div> <div class="big-card-content"> <div class="card-value">{{ overviewData.total }}</div> <div class="card-tag" :class="getGrowthClass(overviewData.total_growth)"> {{ getGrowthText(overviewData.total_growth) }} </div> </div> <div class="card-title"> <el-icon><View /></el-icon> 用户登录总数 </div> <div class="big-card-content"> <div class="card-value">{{ overviewData.total_login }}</div> <div class="card-tag" :class="getGrowthClass(overviewData.total_login_growth)"> {{ getGrowthText(overviewData.total_login_growth) }} </div> </div> </div>
<div class="right-stats-grid"> <!-- 会员总数 --> <div class="stat-card orange-gradient small-card"> <div class="small-card-content"> <div class="card-title"> <el-icon><Trophy /></el-icon> 会员登录总数 </div> <div class="card-value-small">{{ overviewData.member }}</div> <div class="card-tag-wrapper"> <div class="card-tag" :class="getGrowthClass(overviewData.member_growth)"> {{ getGrowthText(overviewData.member_growth) }} </div> </div> </div> </div> <!-- 游客总数 --> <div class="stat-card purple-gradient small-card"> <div class="small-card-content"> <div class="card-title"> <el-icon><User /></el-icon> 游客总数 </div> <div class="card-value-small">{{ overviewData.visitor }}</div> <div class="card-tag-wrapper"> <div class="card-tag" :class="getGrowthClass(overviewData.vistor_growth)"> {{ getGrowthText(overviewData.vistor_growth) }} </div> </div> </div> </div> <!-- 非网注册和登录总数 --> <div class="stat-card blue-gradient small-card full-width"> <div class="small-card-row"> <!-- 非网注册总数 --> <div class="small-card-item"> <div class="card-title"> <el-icon><User /></el-icon> 非网注册总数 </div> <div class="card-value-small">{{ overviewData.normal_register }}</div> <div class="card-tag-wrapper"> <div class="card-tag" :class="getGrowthClass(overviewData.normal_reg_growth)"> {{ getGrowthText(overviewData.normal_reg_growth) }} </div> </div> </div> <!-- 非网登录总数 --> <div class="small-card-item"> <div class="card-title"> <el-icon><View /></el-icon> 非网登录总数 </div> <div class="card-value-small">{{ overviewData.normal_login }}</div> <div class="card-tag-wrapper"> <div class="card-tag" :class="getGrowthClass(overviewData.normal_login_growth)"> {{ getGrowthText(overviewData.normal_login_growth) }} </div> </div> </div> </div> </div> </div> </div>
<!-- 登录用户构成比例 --> <div class="composition-section"> <div class="section-header"> <el-icon><PieChart /></el-icon> 登录用户构成比例 </div> <div class="charts-row"> <div class="chart-wrapper"> <div ref="chartMemberRef" class="chart-box"></div> <div class="legend-custom"> <div class="legend-item"><span class="dot green"></span>会员用户</div> <div class="legend-item"><span class="dot red"></span>非会员用户</div> </div> </div> <div class="chart-wrapper"> <div ref="chartNewOldRef" class="chart-box"></div> <div class="legend-custom"> <div class="legend-item"><span class="dot green"></span>会员用户</div> <div class="legend-item"><span class="dot red"></span>新非网数量</div> <div class="legend-item"><span class="dot blue"></span>老非网数量</div> </div> </div> </div> </div> </div>
<!-- 数据明细 --> <div v-show="activeTab === 'detail'" class="tab-content detail-content" v-loading="loading" element-loading-text="数据加载中..."> <!-- 搜索栏 --> <div class="search-bar"> <div class="search-label">时间段查询</div> <el-date-picker v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" size="default" /> <el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button> <el-button type="primary" class="reset-btn" @click="handleReset">重置</el-button> <el-button type="danger" class="export-btn" @click="handleExport">数据导出</el-button> </div>
<!-- 表格1: 用户构成明细 --> <div class="detail-section"> <div class="section-title"><el-icon><User /></el-icon> 用户构成明细</div> <el-table :data="tableData1" style="width: 100%" :header-cell-style="headerCellStyle"> <el-table-column prop="type" label="用户类型" width="180" align="center"> <template #default="scope"> <span :class="{ 'text-red': scope.row.type === '用户总数', 'text-black-bold': ['会员总数', '非会员总数'].includes(scope.row.type), 'sub-item-text': ['新非网总数', '老非网总数'].includes(scope.row.type) }">{{ scope.row.type }}</span> </template> </el-table-column> <el-table-column prop="total" label="当前总数" align="center" /> <el-table-column prop="dailyNew" label="较昨日新增" align="center"> <template #default="scope"> <span :class="getValueColorClass(scope.row.dailyNew)">{{ scope.row.dailyNew }}</span> </template> </el-table-column> <el-table-column prop="weeklyNew" label="较上周新增" align="center"> <template #default="scope"> <span :class="getValueColorClass(scope.row.weeklyNew)">{{ scope.row.weeklyNew }}</span> </template> </el-table-column> <el-table-column prop="monthlyNew" label="较上月新增" align="center"> <template #default="scope"> <span :class="getValueColorClass(scope.row.monthlyNew)">{{ scope.row.monthlyNew }}</span> </template> </el-table-column> <el-table-column prop="periodNew" label="时间段新增" align="center"> <template #default="scope"> <span :class="getValueColorClass(scope.row.periodNew)">{{ scope.row.periodNew }}</span> </template> </el-table-column> </el-table> </div>
<!-- 表格2: 新注册用户来源 --> <div class="detail-section"> <div class="section-title"><el-icon><User /></el-icon> 新非网注册数据</div> <el-table :data="tableData2" style="width: 100%" :header-cell-style="headerCellStyle"> <el-table-column prop="channel" label="来源渠道" align="center" /> <el-table-column prop="dailyNew" label="今日新增" align="center" /> <el-table-column prop="weeklyNew" label="本周新增" align="center" /> <el-table-column prop="monthlyNew" label="本月新增" align="center" /> <el-table-column prop="periodNew" label="时间段新增" align="center" /> <el-table-column prop="percent" label="占比" align="center" /> </el-table> </div>
<!-- 表格3: 老用户来源 --> <div class="detail-section"> <div class="section-title"><el-icon><User /></el-icon>老非网登录数据</div> <el-table :data="tableData3" style="width: 100%" :header-cell-style="headerCellStyle"> <el-table-column prop="channel" label="来源渠道" align="center" /> <el-table-column prop="dailyNew" label="今日新增" align="center" /> <el-table-column prop="weeklyNew" label="本周新增" align="center" /> <el-table-column prop="monthlyNew" label="本月新增" align="center" /> <el-table-column prop="periodNew" label="时间段新增" align="center" /> <el-table-column prop="percent" label="占比" align="center" /> </el-table> </div>
<!-- 图表: 用户来源渠道分布 --> <div class="detail-section chart-section-bg"> <div class="section-title"><el-icon><PieChart /></el-icon> 用户来源渠道分布(总数据)</div> <div ref="chartBarRef" class="bar-chart-box"></div> </div>
</div> <!-- 悬浮刷新时间 --> <div class="refresh-time" v-if="lastUpdateTime"> 数据刷新时间:{{ lastUpdateTime }} </div> </div></template>
<script setup>import { ref, onMounted, nextTick, watch, computed } from 'vue';import { useRoute, useRouter } from 'vue-router';import * as echarts from 'echarts';import { getUserOverviewList, getUserFullReportList, exportUserFullReport } from '../../api/platformData';import { ElMessage } from 'element-plus';
const route = useRoute();const router = useRouter();
const activeTab = ref(route.query.tab || 'overview');const dateRange = ref('');const loading = ref(false);const lastUpdateTime = ref('');
const hasDateRange = computed(() => { return dateRange.value && dateRange.value.length === 2;});
const chartMemberRef = ref(null);const chartNewOldRef = ref(null);const chartBarRef = ref(null);let chartBarInstance = null;
const overviewData = ref({ total: 0, total_growth: '0%', total_login: 0, total_login_growth: '0%', member: 0, member_growth: '0%', visitor: 0, vistor_growth: '0%', normal_register: 0, normal_reg_growth: '0%', normal_login: 0, normal_login_growth: '0%', group_member_normal: { member_val: 0, normal_login_val: 0 }, group_triple: { member_val: 0, new_normal_login_val: 0, old_normal_val: 0 }});
// 表格数据 - 使用 ref 响应式数据
const tableData1 = ref([]);const tableData2 = ref([]);const tableData3 = ref([]);
const headerCellStyle = { background: '#fff0f0', color: '#333', fontWeight: 'bold'};
// 获取增长率的样式类
const getGrowthClass = (growthStr) => { if (!growthStr) return ''; return growthStr.startsWith('-') ? 'down' : 'up';};
// 获取增长率的显示文本(添加箭头)
const getGrowthText = (growthStr) => { if (!growthStr) return ''; const isDown = growthStr.startsWith('-'); const arrow = isDown ? '↓' : '↑'; const prefix = isDown ? '较昨日减少' : '较昨日增加'; // 移除可能存在的负号,因为箭头已经表示了方向
const value = growthStr.replace('-', ''); return `${prefix}${arrow} ${value}`;};
// 获取表格数值颜色样式
const getValueColorClass = (val) => { if (!val || val === '-') return ''; return String(val).startsWith('-') ? 'text-red' : 'text-green';};
// 格式化日期
const formatDate = (date) => { if (!date) return ''; const d = new Date(date); const pad = (n) => n < 10 ? '0' + n : n; return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;};
const isOverviewLoaded = ref(false);const isDetailLoaded = ref(false);
const chartMemberInstance = ref(null);const chartNewOldInstance = ref(null);
const fetchData = async () => { try { const res = await getUserOverviewList(); console.log("获取用户概览数据响应完成:",res); // 根据用户反馈,响应拦截器直接返回data部分,不再包含code
if (res && res.list) { overviewData.value = res.list; initCharts(); isOverviewLoaded.value = true; lastUpdateTime.value = formatDate(new Date()); } } catch (error) { console.error('获取用户概览数据失败:', error); }};
const fetchDetailData = async (forceUpdate = false) => { // 决定是否显示加载状态:首次加载或强制更新时显示
const shouldShowLoading = !isDetailLoaded.value || forceUpdate;
if (shouldShowLoading) { loading.value = true; }
let params = {}; if (dateRange.value && dateRange.value.length === 2) { params.start_time = formatDate(dateRange.value[0]); params.end_time = formatDate(dateRange.value[1]); } // Check if range search is active
const isRangeSearch = !!(params.start_time && params.end_time); try { const res = await getUserFullReportList(params); console.log("获取数据明细响应:", res); // 兼容处理:如果拦截器返回了data,则直接使用res;如果没拦截,使用res.data
const data = res.composition ? res : (res.data ? res.data : null);
if (data) { // Map Table 1
if (data.composition) { tableData1.value = data.composition.map(item => ({ type: item.label, total: item.total.toLocaleString(), dailyNew: isRangeSearch ? '-' : (item.growth_day > 0 ? '+' + item.growth_day : item.growth_day), weeklyNew: isRangeSearch ? '-' : (item.growth_week > 0 ? '+' + item.growth_week : item.growth_week), monthlyNew: isRangeSearch ? '-' : (item.growth_month > 0 ? '+' + item.growth_month : item.growth_month), periodNew: !isRangeSearch ? '-' : item.growth_range })); } // Map Table 2
if (data.new_source) { tableData2.value = data.new_source.map(item => ({ channel: item.channel, dailyNew: isRangeSearch ? '-' : item.today_add, weeklyNew: isRangeSearch ? '-' : (item.week_add > 0 ? '+' + item.week_add : item.week_add), monthlyNew: isRangeSearch ? '-' : (item.month_add > 0 ? '+' + item.month_add : item.month_add), periodNew: !isRangeSearch ? '-' : item.range_add, percent: item.rate })); }
// Map Table 3
if (data.old_source) { tableData3.value = data.old_source.map(item => ({ channel: item.channel, dailyNew: isRangeSearch ? '-' : item.today_add, weeklyNew: isRangeSearch ? '-' : (item.week_add > 0 ? '+' + item.week_add : item.week_add), monthlyNew: isRangeSearch ? '-' : (item.month_add > 0 ? '+' + item.month_add : item.month_add), periodNew: !isRangeSearch ? '-' : item.range_add, percent: item.rate })); }
// Chart Data
if (data.chart) { nextTick(() => { updateBarChart(data.chart, data.new_source || [], data.old_source || []); }); } isDetailLoaded.value = true; lastUpdateTime.value = formatDate(new Date()); } } catch(e) { console.error('获取数据明细失败:', e); } finally { if (shouldShowLoading) { loading.value = false; } }};
const handleSearch = () => { fetchDetailData(true);};
const handleReset = () => { dateRange.value = ''; fetchDetailData(true);};
const handleExport = async () => { loading.value = true; let params = {}; if (dateRange.value && dateRange.value.length === 2) { params.start_time = formatDate(dateRange.value[0]); params.end_time = formatDate(dateRange.value[1]); }
try { const res = await exportUserFullReport(params); // Blob 处理
const blob = new Blob([res]); const fileName = '用户数据明细.pdf'; const link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = fileName; link.click(); window.URL.revokeObjectURL(link.href); ElMessage.success('导出成功'); } catch (e) { console.error('导出失败:', e); ElMessage.error('导出失败'); } finally { loading.value = false; }};
const initCharts = () => { if (activeTab.value === 'overview') { nextTick(() => { // Chart 1: 会员/非会员
if (chartMemberRef.value) { let chart1 = chartMemberInstance.value; if (!chart1) { chart1 = echarts.init(chartMemberRef.value); chartMemberInstance.value = chart1; } chart1.setOption({ tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' }, color: ['#ff4d4f', '#52c41a'], series: [ { type: 'pie', radius: ['50%', '70%'], avoidLabelOverlap: false, label: { show: false }, data: [ { value: overviewData.value.group_member_normal.normal_login_val, name: '非会员用户' }, { value: overviewData.value.group_member_normal.member_val, name: '会员用户' } ] } ] }); } // Chart 2: 三色环形图
if (chartNewOldRef.value) { let chart2 = chartNewOldInstance.value; if (!chart2) { chart2 = echarts.init(chartNewOldRef.value); chartNewOldInstance.value = chart2; } chart2.setOption({ tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' }, color: ['#ff4d4f', '#52c41a', '#409eff'], // 红 绿 蓝
series: [ { type: 'pie', radius: ['50%', '70%'], avoidLabelOverlap: false, label: { show: false }, data: [ { value: overviewData.value.group_triple.new_normal_login_val, name: '新非网数量' }, { value: overviewData.value.group_triple.member_val, name: '会员用户' }, { value: overviewData.value.group_triple.old_normal_val, name: '老非网数量' } ] } ] }); } }); } else if (activeTab.value === 'detail') { // 这里的初始化主要用于空状态或第一次渲染,数据更新由 updateBarChart 处理
// 如果没有数据,可以不初始化,或者初始化为空
// fetchDetailData 会被调用并初始化图表
}};
const updateBarChart = (chartData, newSources, oldSources) => { if (!chartBarRef.value) return; if (!chartBarInstance) { chartBarInstance = echarts.init(chartBarRef.value); }
const xAxisData = chartData.x_axis || []; const yAxisData = chartData.y_axis || [];
// 分离新老用户数据
const newUserData = []; const oldUserData = [];
// 提取渠道名称用于匹配
const newSourceChannels = newSources.map(s => s.channel); xAxisData.forEach((label, index) => { const val = yAxisData[index] || 0; if (newSourceChannels.includes(label)) { newUserData.push(val); oldUserData.push(0); } else { newUserData.push(0); oldUserData.push(val); } });
const option = { tooltip: { trigger: 'item', }, legend: { data: ['新用户', '老用户'], top: 'top', left: 'center' }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: [ { type: 'category', data: xAxisData, axisTick: { alignWithLabel: true } } ], yAxis: [ { type: 'value' } ], series: [ { name: '新用户', type: 'bar', barWidth: '20%', color: '#40a9ff', data: newUserData, stack: 'total' }, { name: '老用户', type: 'bar', barWidth: '20%', color: '#92CB74', data: oldUserData, stack: 'total' } ] };
chartBarInstance.setOption(option);};
watch(activeTab, (newVal) => { // 同步 tab 状态到 URL
router.replace({ query: { ...route.query, tab: newVal } }); if (newVal === 'overview') { fetchData(); } else if (newVal === 'detail') { fetchDetailData(); }});
onMounted(() => { if (activeTab.value === 'overview') { fetchData(); } else { fetchDetailData(); }});
</script>
<style scoped>.user-overview-container { background-color: #fee6e6;}
/* Tabs */.tab-header { display: flex; margin-bottom: 20px;}.tab-item { padding: 6px 16px; margin-right: 10px; background-color: #fff; border: 1px solid #ffcccc; border-radius: 4px; cursor: pointer; color: #ff4d4f; font-size: 14px;}.tab-item.active { background-color: #ff4d4f; color: #fff;}
.tab-note { margin-left: auto; display: flex; align-items: center; font-size: 14px; color: #666;}
.red-asterisk { color: #ff4d4f; font-weight: bold; margin-right: 5px;}
/* Overview Tab */.stats-row { display: flex; gap: 20px; margin-bottom: 20px;}
.big-card { flex: 1; height: auto; border-radius: 12px; padding: 24px; display: flex; flex-direction: column; justify-content: space-between; /* 空间分布 */ color: #fff; position: relative; /* 确保绝对定位相对于卡片 */}.purple-gradient { background: linear-gradient(135deg, #a18cd1 0%, #fbc2eb 100%);}.orange-gradient { background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 99%, #fecfef 100%); background: linear-gradient(to right, #ffafbd, #ffc3a0); /* approximate orange */ background: linear-gradient(90deg, #ff8c6d 0%, #ffcba4 100%);}.blue-gradient { background: linear-gradient(135deg, #9BB7FC 0%, #66a6ff 100%);}
.right-stats-grid { flex: 2; display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: auto auto; gap: 20px;}
.right-stats-grid .full-width { grid-column: 1 / -1;}
.right-stats-grid .small-card-content { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%;}
.right-stats-grid .small-card-content .card-title { margin-bottom: 15px; text-align: center;}
.right-stats-grid .small-card-content .card-value-small { margin-bottom: 10px; text-align: center;}.small-card { flex: 1; border-radius: 12px; padding: 20px; display: flex; flex-direction: column; justify-content: center; color: #fff; position: relative; /* 确保绝对定位相对于卡片 */}
.small-card-row { display: flex; flex-direction: row; justify-content: space-between; height: 100%;}
.small-card-item { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 0 10px;}
.small-card-item:first-child { border-right: 1px solid rgba(255, 255, 255, 0.3);}
.small-card-content { display: flex; flex-direction: row; justify-content: space-between; align-items: center; margin-bottom: 15px;}
.small-card-content:last-child { margin-bottom: 0;}
.left-part { display: flex; align-items: center; align-self: flex-start; /* 标题垂直居上 */}
.right-part { display: flex; flex-direction: column; align-items: center; /* 改为水平居中 */ justify-content: center; margin-right: 150px; /* 右边容器向左移 */}
.card-title { font-size: 34px; /* 字体放大 */ font-weight: bold; /* 加粗 */ display: flex; align-items: center; gap: 8px; margin-bottom: 20px; /* 增加底部间距 */ width: 100%; /* 占满宽度 */}
.small-card .card-title { width: auto; margin-bottom: 10px; text-align: center;}
.small-card-item .card-title { font-size: 24px; margin-bottom: 15px;}
.small-card-item .card-value-small { margin-bottom: 10px;}
.card-value { font-size: 64px; font-weight: bold; margin: 0; text-align: center;}.big-card-content { flex: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 10px; /* 数字和百分比之间的间距 */}.card-value-small { font-size: 64px; font-weight: bold; margin-left: auto;}
.small-card .card-value-small { margin-left: 0; line-height: 1.2;}
.top-row { display: flex; align-items: center; justify-content: space-between;}.card-tag-wrapper { display: flex; justify-content: flex-end; margin-top: 10px;}
.small-card .card-tag-wrapper { margin-top: 8px; display: flex; justify-content: center;}
.small-card-item .card-tag-wrapper { margin-top: 8px;}.card-tag { background-color: #fff; padding: 8px 16px; /* 增加高度 */ border-radius: 4px; font-weight: bold; display: inline-block;}.big-card .card-tag { font-size: 18px;}.card-tag.up { color: #52c41a;}.card-tag.down { color: #ff4d4f;}
/* Composition Section */.composition-section { background: linear-gradient(135deg, #b8c6db 0%, #f5f7fa 100%); background-color: #b8c4f9; /* Fallback */ background: linear-gradient(to right, #a18cd1, #c2e9fb); border-radius: 12px; padding: 20px; /* background-image: linear-gradient(120deg, #a1c4fd 0%, #c2e9fb 100%); */ background: #a3b1ff; /* approximate purple-ish */}.section-header { color: #fff; font-size: 24px; font-weight: bold; margin-bottom: 15px; display: flex; align-items: center; gap: 8px;}.charts-row { display: flex; gap: 20px;}.chart-wrapper { flex: 1; background: #e6e9f5; border-radius: 12px; border: 2px solid #fff; height: 220px; display: flex; align-items: center; padding: 20px;}.chart-box { flex: 1; height: 100%;}.legend-custom { display: flex; flex-direction: column; gap: 10px; min-width: 120px;}.legend-item { display: flex; align-items: center; font-size: 14px; color: #333; font-weight: bold;}.dot { width: 12px; height: 12px; border-radius: 50%; margin-right: 8px;}.dot.green { background-color: #52c41a; }.dot.red { background-color: #ff4d4f; }.dot.blue { background-color: #409eff; }
/* Detail Tab */.search-bar { background: #fff; padding: 15px; border-radius: 8px; display: flex; align-items: center; gap: 10px; margin-bottom: 20px; border: 1px solid #f0f0f0; position: sticky; top: 0; z-index: 1000; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);}.search-label { font-weight: bold; margin-right: 5px;}.search-btn, .reset-btn { width: 80px;}.search-btn { background-color: #409eff;}.reset-btn { background-color: #409eff; border-color: #409eff;}.export-btn { margin-left: auto; background-color: #ff7875; border-color: #ff7875;}
.detail-section { background: #fff; border-radius: 8px; padding: 15px; margin-bottom: 20px; border: 1px solid #f0f0f0;}.section-title { color: #409eff; font-size: 16px; font-weight: bold; margin-bottom: 15px; display: flex; align-items: center; gap: 5px;}
.text-red { color: #ff4d4f; font-weight: bold; }.text-green { color: #52c41a; font-weight: bold; }.text-black-bold { color: #333; font-weight: bold; }
.sub-item-text { font-size: 12px; padding-left: 20px; color: #606266; display: inline-block;}
.chart-section-bg { /* background: #fff; already set by detail-section */}.bar-chart-box { width: 100%; height: 350px;}
.refresh-time { position: fixed; bottom: 20px; right: 20px; background: rgba(0, 0, 0, 0.6); color: #fff; padding: 8px 15px; border-radius: 20px; font-size: 12px; z-index: 2000; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);}</style>
|