perf: 全域数据引擎页面优化

This commit is contained in:
rd
2025-07-01 14:34:16 +08:00
parent a486d42fa5
commit 57e45d991e
22 changed files with 739 additions and 486 deletions

View File

@ -1,57 +1,47 @@
<template>
<view>
<topHeader ref="topHeaderRef" @search="search"></topHeader>
<a-space style="width: 100%; display: flex">
<a-space direction="vertical" style="background-color: #fff; padding: 24px; width: 100%;">
<a-space align="center">
<span>性别分布</span>
<a-popover position="tl">
<a-button type="primary" class="pop-btn">
<template #icon>
<icon-question-circle />
</template>
</a-button>
<template #content>
<p>基于xxx获取数据xxx一段文字描述该数据的获取方式和来源等xxx</p>
</template>
</a-popover>
</a-space>
<div class="h-360px w-100% flex mb-24px">
<!-- 1. 性别分布 -->
<div class="bg-#fff rounded-8px border-1px border-#D7D7D9 border-solid w-100% py-0 px-20px w-600px mr-24px">
<div class="title-row">
<span class="title mr-4px">性别分布</span>
<a-tooltip>
<template #content>基于社交内容平台中用户资料互动行为及语义特征进行智能识别与估算</template>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</div>
<a-space>
<div id="container" style="height: 180px; width: 180px"></div>
<div id="container" class="w-300px h-300px"></div>
<a-space direction="vertical" style="font-size: 14px">
<a-space>
<span style="width: 8px; height: 8px; background-color: #f64b31; border-radius: 50%"></span>
<span>女性</span>
<span style="width: 40px" v-if="genderData.length > 0">{{ genderData[0].rate * 100 }}%</span>
<span v-if="genderData.length > 0" style="width: 40px">{{ genderData[0].rate * 100 }}%</span>
<span>TGI</span>
<span v-if="genderData.length > 0">{{ genderData[0].tgi }}</span>
</a-space>
<a-space>
<span style="width: 8px; height: 8px; background-color: #2a59f3; border-radius: 50%"></span>
<span>男性</span>
<span style="width: 40px" v-if="genderData.length > 1">{{ genderData[1].rate * 100 }}%</span>
<span v-if="genderData.length > 1" style="width: 40px">{{ genderData[1].rate * 100 }}%</span>
<span>TGI</span>
<span v-if="genderData.length > 1">{{ genderData[1].tgi }}</span>
</a-space>
</a-space>
</a-space>
</a-space>
<a-space direction="vertical" style="background-color: #fff; padding: 24px; width: 100%;">
</div>
<!-- 2. 年龄分布 -->
<div class="bg-#fff rounded-8px border-1px border-#D7D7D9 border-solid w-100% py-0 px-20px flex-1 flex flex-col">
<a-space style="display: flex; justify-content: space-between; width: 100%; font-size: 12px">
<a-space align="center">
<span>年龄分布</span>
<a-popover position="tl">
<a-button type="primary" class="pop-btn">
<template #icon>
<icon-question-circle />
</template>
</a-button>
<template #content>
<p>基于xxx获取数据xxx一段文字描述该数据的获取方式和来源等xxx</p>
</template>
</a-popover>
</a-space>
<div class="title-row">
<span class="title mr-4px">年龄分布</span>
<a-tooltip>
<template #content>基于社交平台的公开信息内容偏好与行为模式通过算法进行年龄段归类和统计</template>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</div>
<a-space align="center">
<span style="width: 16px; height: 8px; background-color: #6d4cfe; border-radius: 2px"></span>
<span style="color: #6d4cfe">占比</span>
@ -60,32 +50,24 @@
</a-space>
</a-space>
<a-space>
<div id="age-container" style="height: 180px; width: 480px"></div>
</a-space>
</a-space>
</a-space>
<a-space direction="vertical" style="background-color: #fff; padding: 24px; flex: 1; margin-top: 24px">
<a-space align="center">
<span>地域分布</span>
<a-popover position="tl">
<a-button type="primary" class="pop-btn">
<template #icon>
<icon-question-circle />
</template>
</a-button>
<template #content>
<p>基于xxx获取数据xxx一段文字描述该数据的获取方式和来源等xxx</p>
</template>
</a-popover>
</a-space>
<a-space>
<div id="age-container" class="w-100% flex-1"></div>
</div>
</div>
<div class="bg-#fff rounded-8px border-1px border-#D7D7D9 border-solid w-100% py-0 px-20px flex-1 pb-20px">
<div class="title-row">
<span class="title mr-4px">地域分布</span>
<a-tooltip>
<template #content>基于社交平台的IP归属地位置标签内容发布地等数据推测用户活跃区域</template>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</div>
<div class="flex">
<a-space direction="vertical">
<div id="chinaMap" style="height: 416px; width: 640px"></div>
<a-space direction="vertical" style="font-size: 14px">
<span>搜索指数</span>
<span class="cts">搜索指数</span>
<a-space>
<span></span>
<span class="cts"></span>
<span
v-for="item in 5"
:key="item"
@ -98,14 +80,14 @@
margin: '0 2px',
}"
></span>
<span></span>
<span class="cts"></span>
</a-space>
</a-space>
</a-space>
<a-space>
<a-tabs default-active-key="1" @change="tabChange">
<div class="flex flex-col h-486px">
<a-tabs default-active-key="1" class="h-100%" @change="tabChange">
<a-tab-pane key="1" title="省份">
<a-table :data="geoList" :pagination="false">
<a-table :data="geoList" :pagination="false" class="h-100%" :scroll="{ y: '100%' }">
<template #columns>
<a-table-column title="排名" data-index="rank" />
<a-table-column title="省份" data-index="geo" />
@ -116,20 +98,24 @@
</a-table>
</a-tab-pane>
<a-tab-pane key="2" title="城市">
<a-table :data="geoList" :pagination="false">
<a-table :data="geoList" :pagination="false" class="h-100%" :scroll="{ y: '100%' }">
<template #columns>
<a-table-column title="排名" data-index="rank" />
<a-table-column title="城市" data-index="geo" />
<a-table-column title="分布占比" data-index="rate" />
<a-table-column title="分布占比" data-index="rate">
<template #cell="{ record }">
<span class="cts">{{ (record.rate * 100).toFixed(2) }}%</span>
</template>
</a-table-column>
<a-table-column title="TGI指数" data-index="tgi" />
</template>
</a-table>
</a-tab-pane>
</a-tabs>
</a-space>
</a-space>
</a-space>
</div>
</div>
</div>
</view>
</template>
@ -141,7 +127,7 @@ import * as echarts from 'echarts';
import chinaJson from '@/assets/maps/china.json';
echarts.registerMap('china', chinaJson);
const scope = ref(1); //地域范围1-省2-市
const scope = ref(1); // 地域范围1-省2-市
const chartInstance = (ref < echarts.ECharts) | (null > null);
const topHeaderRef = ref();
// 从topHeader获取统一的状态
@ -180,12 +166,13 @@ const getAgeDistributionsList = async () => {
return;
}
if (selectedSubCategory.value != 0) {
parms['industry_id'] = selectedSubCategory.value;
params['industry_id'] = selectedSubCategory.value;
}
const { code, data } = await fetchAgeDistributionsList(params);
if (code === 200) {
ageValueData.value = data;
drawAgeChart();
}
const res = await fetchAgeDistributionsList(params);
console.log('年龄分布:', res);
ageValueData.value = res;
drawAgeChart();
};
// 获得地理分布列表
@ -202,11 +189,12 @@ const getGeoDistributionsList = async () => {
return;
}
if (selectedSubCategory.value != 0) {
parms['industry_id'] = selectedSubCategory.value;
params['industry_id'] = selectedSubCategory.value;
}
const { code, data } = await fetchGeoDistributionsList(params);
if (code === 200) {
geoList.value = data;
}
const res = await fetchGeoDistributionsList(params);
console.log('地理分布:', res);
geoList.value = res;
};
// 获取性别分布列表
const getGenderDistributionsList = async () => {
@ -221,46 +209,78 @@ const getGenderDistributionsList = async () => {
return;
}
if (selectedSubCategory.value != 0) {
parms['industry_id'] = selectedSubCategory.value;
}
const res = await fetchGenderDistributionsList(params);
genderData.value = [];
genderData.value = [...res];
await nextTick();
genderValueData.value = [];
for (let i = 0; i < res.length; i++) {
genderValueData.value.push({
value: res[i].rate * 100,
});
params['industry_id'] = selectedSubCategory.value;
}
const { code, data } = await fetchGenderDistributionsList(params);
if (code === 200) {
genderData.value = [];
genderValueData.value = [];
drawChart();
genderData.value = [...data];
await nextTick();
genderValueData.value = data.map((item) => ({
value: item.rate * 100,
tgi: item.tgi,
name: item.gender === 1 ? '女性' : '男性',
}));
drawChart();
}
};
const drawChart = () => {
var dom = document.getElementById('container');
var myChart = echarts.init(dom, null, {
let dom = document.getElementById('container');
let myChart = echarts.init(dom, null, {
renderer: 'canvas',
useDirtyRect: false,
});
var option;
option = {
let option = {
color: ['#F64B31', '#2A59F3'],
tooltip: {
trigger: 'item',
backgroundColor: '#fff',
borderColor: 'rgba(0,0,0,0.05)',
borderWidth: 1,
textStyle: {
color: '#222',
fontSize: 14,
},
extraCssText: 'box-shadow:0 2px 8px 0 rgba(0,0,0,0.08);border-radius:8px;',
formatter: function (params) {
return `
<div class="w-140px">
<div style="font-weight:500;margin-bottom:8px;">${params.name}</div>
<div style="display:flex;align-items:center;margin-bottom:4px;">
<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:#2a59f3;margin-right:8px;"></span>
<span>占比</span>
<span style="margin-left:auto;">${params.value}%</span>
</div>
<div style="display:flex;align-items:center;">
<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:#F64B31;margin-right:8px;"></span>
<span>TGI</span>
<span style="margin-left:auto;">${params.data.tgi || '-'}</span>
</div>
</div>
`;
},
},
series: [
{
type: 'pie',
avoidLabelOverlap: false,
data: genderValueData.value,
label: {
show: false, // 关闭扇区外的文字
},
labelLine: {
show: false, // 不显示引导线
show: false,
},
radius: ['40%', '55%'],
},
],
};
if (option && typeof option === 'object') {
myChart.setOption(option);
}
myChart.setOption(option);
};
const drawAgeChart = () => {
@ -269,7 +289,7 @@ const drawAgeChart = () => {
const xAxis = ageValueData.value.map((item) => item.age);
const yAxis = ageValueData.value.map((item) => item.rate * 100);
const yAxis2 = ageValueData.value.map((item) => item.tgi);
const average = yAxis2.reduce((sum, val) => sum + val, 0) / yAxis2.length;
// const average = yAxis2.reduce((sum, val) => sum + val, 0) / yAxis2.length;
// 图表初始化强制使用2D渲染
const myChart = echarts.init(dom, null, {
@ -289,19 +309,47 @@ const drawAgeChart = () => {
animation: false, // 小尺寸下关闭动画提升性能
tooltip: {
trigger: 'axis',
confine: true, // 确保tooltip不超出容器
confine: true,
axisPointer: {
type: 'shadow',
label: {
fontSize: 12, // 调小提示标签字号
fontSize: 12,
backgroundColor: 'rgba(0,0,0,0.7)',
},
},
backgroundColor: '#fff',
borderColor: 'rgba(0,0,0,0.05)',
borderWidth: 1,
textStyle: {
color: '#333',
fontSize: 14,
},
extraCssText: 'box-shadow:0 2px 8px 0 rgba(0,0,0,0.08);border-radius:8px;',
formatter: function (params) {
const name = params[0].name;
const percent = params[0].value;
const tgi = params[1].value;
return `
<div class="w-140px">
<div style="margin-bottom: 4px;" class="color-#000">${name}岁</div>
<div style="display: flex;align-items: center;margin-bottom:2px;">
<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:#6d4cfe;margin-right:6px;"></span>
<span>占比</span>
<span style="color:#333;margin-left:auto;">${percent.toFixed(2)}%</span>
</div>
<div style="display: flex;align-items: center;">
<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:#F64B31;margin-right:6px;"></span>
<span>TGI</span>
<span style="color:#333;margin-left:auto;">${tgi}</span>
</div>
</div>
`;
},
},
grid: {
top: 10,
top: 25,
right: 30,
bottom: 25,
bottom: 40,
left: 40,
containLabel: false,
},
@ -312,8 +360,9 @@ const drawAgeChart = () => {
interval: 0,
rotate: 0,
fontSize: 12,
margin: 2,
margin: 10,
hideOverlap: true, // 自动隐藏重叠标签
color: '#939499',
},
axisTick: {
alignWithLabel: true,
@ -330,51 +379,61 @@ const drawAgeChart = () => {
// 左侧百分比轴
type: 'value',
name: '占比',
nameLocation: 'top',
nameGap: 8,
nameLocation: 'end',
nameGap: 10,
nameTextStyle: {
color: '#939499',
fontSize: 12,
padding: [0, 0, 3, 0], // 微调名称位置
padding: [0, 50, 0, 0], // 微调名称位置
},
min: 0,
max: Math.ceil(Math.max(...yAxis) / 20) * 20,
interval: calcInterval(yAxis, 20),
axisLabel: {
formatter: '{value}%',
fontSize: 8,
margin: 4,
fontSize: 12,
margin: 10,
showMinLabel: true,
showMaxLabel: true,
color: '#939499',
},
splitLine: {
lineStyle: {
type: 'dashed',
color: 'rgba(255,255,255,0.3)',
width: 0.8,
// color: 'rgba(255,255,255,0.3)',
// width: 0.8,
},
},
},
{
// 右侧TGI轴
type: 'value',
name: 'TGI指数',
nameLocation: 'top',
nameGap: 8,
name: 'TGI',
nameLocation: 'end',
nameGap: 10,
nameTextStyle: {
fontSize: 10,
color: '#F64B31',
padding: [0, 0, 3, 0],
fontSize: 12,
color: '#939499',
padding: [0, 0, 0, 20],
},
min: Math.floor(Math.min(...yAxis2) / 50) * 50,
max: Math.ceil(Math.max(...yAxis2) / 50) * 50,
interval: calcInterval(yAxis2, 50),
axisLabel: {
fontSize: 8,
fontSize: 12,
margin: 4,
color: '#F64B31',
color: '#939499',
},
splitLine: {
lineStyle: {
type: 'dashed',
// color: 'rgba(255,255,255,0.3)',
// width: 0.8,
},
},
axisLine: {
lineStyle: {
type: 'dashed',
color: '#F64B31',
width: 0.8,
},
@ -386,7 +445,7 @@ const drawAgeChart = () => {
// 柱状图
name: '占比',
type: 'bar',
barWidth: 10,
barWidth: 16,
itemStyle: {
color: '#6d4cfe',
borderRadius: [1, 1, 0, 0], // 微圆角
@ -395,7 +454,7 @@ const drawAgeChart = () => {
show: true,
position: 'top',
formatter: '{c}%',
fontSize: 8,
fontSize: 12,
color: '#fff',
distance: 1, // 减小标签与柱子的距离
},
@ -420,14 +479,15 @@ const drawAgeChart = () => {
type: 'dashed',
},
label: {
fontSize: 8,
fontSize: 12,
color: '#FFAE00',
formatter: (params) => {
// 改用回调函数
const avg = params.data.coord[1]; // 获取平均值坐标
return 'TGI:' + avg.toFixed(0); // 格式化显示
},
position: 'end',
position: 'insideEnd',
offset: [0, -10],
},
data: [{ type: 'average' }],
},
@ -498,7 +558,7 @@ onMounted(() => {
});
</script>
<style scoped>
<style scoped lang="scss">
/* 自定义样式 */
:deep(.arco-table-th) {
background-color: var(--color-fill-2);
@ -522,4 +582,41 @@ onMounted(() => {
color: #6d4cfe !important;
border-color: #6d4cfe !important;
}
.title-row {
display: flex;
height: 64px;
padding: 10px 0 2px 0;
align-items: center;
.title {
color: var(--Text-1, #211f24);
font-family: 'PuHuiTi-Medium';
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 24px; /* 150% */
}
}
.cts {
color: var(--Text-2, #3c4043);
font-family: 'PuHuiTi-Regular';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px; /* 157.143% */
&.num {
font-family: 'HarmonyOS Sans SC';
}
}
:deep(.arco-tabs) {
display: flex;
flex-direction: column;
.arco-tabs-content {
flex: 1;
.arco-tabs-content-list,
.arco-tabs-pane,
.arco-table-container {
height: 100%;
}
}
}
</style>