fix: 优化世界地图点的计算逻辑,添加分组支持并重构相关样式

This commit is contained in:
hi2hi 2024-12-08 16:39:02 +00:00
parent b825894796
commit 20281a1848
6 changed files with 189 additions and 59 deletions

View File

@ -2,6 +2,7 @@
<div <div
ref="pointRef" ref="pointRef"
class="world-map-point" class="world-map-point"
:class="'world-map-point--' + (info?.type || 'default')"
:style="pointStyle" :style="pointStyle"
:title="info?.label || ''" :title="info?.label || ''"
@click="handleClick" @click="handleClick"
@ -88,5 +89,29 @@ function handleClick() {
@media screen and (max-width: 720px) { @media screen and (max-width: 720px) {
--map-point-scale: 0.5; --map-point-scale: 0.5;
} }
&--group {
.point-block {
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: calc(var(--map-point-size) * var(--map-point-scale) + (16px * var(--map-point-scale)));
height: calc(var(--map-point-size) * var(--map-point-scale) + (16px * var(--map-point-scale)));
transform: translate(-50%, -50%);
border: calc(2px * var(--map-point-scale)) solid var(--world-map-point-color);
border-radius: 50%;
opacity: 0.7;
transition: opacity 0.3s;
}
&:hover {
&::after {
opacity: 1;
}
}
}
}
} }
</style> </style>

View File

@ -37,10 +37,14 @@
import { import {
ref, ref,
computed, computed,
watch,
} from 'vue'; } from 'vue';
import validate from '@/utils/validate'; import validate from '@/utils/validate';
import WorldMapPoint from './world-map-point.vue'; import WorldMapPoint from './world-map-point.vue';
import {
findIntersectingGroups,
} from '@/utils/world-map';
const props = defineProps({ const props = defineProps({
width: { width: {
@ -100,16 +104,75 @@ const mapStyle = computed(() => {
return style; return style;
}); });
const mapPoints = computed(() => props.locations.map((i) => { const mapPoints = ref([]);
let computeMapPointsTimer = null;
function computeMapPoints() {
if (computeMapPointsTimer) {
clearTimeout(computeMapPointsTimer);
}
if (props.locations.length === 0) {
mapPoints.value = [];
return;
}
computeMapPointsTimer = setTimeout(() => {
const points = props.locations.map((i) => {
const item = { const item = {
key: i.key, key: i.key,
left: (computedSize.value.width / 1280) * i.x, left: (computedSize.value.width / 1280) * i.x,
top: (computedSize.value.height / 621) * i.y, top: (computedSize.value.height / 621) * i.y,
size: i.size || 4, size: i.size || 4,
label: i.label, label: i.label,
servers: i.servers,
type: 'single',
};
const halfSize = (item.size + 8) / 2;
item.topLeft = {
left: item.left - halfSize,
top: item.top - halfSize,
};
item.bottomRight = {
left: item.left + halfSize,
top: item.top + halfSize,
}; };
return item; return item;
})); });
const groups = findIntersectingGroups(points);
Object.entries(groups).forEach(([key, group]) => {
const item = points.find((i) => i.key === key);
if (item.parent) {
return;
}
item.size = 4;
item.type = 'group';
item.children = group;
let label = item.label || '';
let servers = [...(item.servers || [])];
group.forEach((i) => {
if (!i.parent && !i.children) {
i.parent = item;
label += `\n${i.label}`;
servers = servers.concat((i.servers || []));
}
});
item.label = label;
item.servers = servers;
});
mapPoints.value = points.filter((i) => !i.parent);
}, 100);
}
watch(() => props.locations, () => {
computeMapPoints();
}, {
immediate: true,
});
watch(() => computedSize.value, () => {
computeMapPoints();
}, {
immediate: true,
deep: true,
});
/** /**
* 提示框 * 提示框
@ -125,7 +188,7 @@ const tipsContentStyle = computed(() => {
if (window.innerWidth > 500) { if (window.innerWidth > 500) {
style.top = `${activeTipsXY.value.y}px`; style.top = `${activeTipsXY.value.y}px`;
style.left = `${activeTipsXY.value.x}px`; style.left = `${activeTipsXY.value.x}px`;
style.transform = 'translate(-50%, 100%)'; style.transform = 'translate(-50%, 20px)';
} else { } else {
style.bottom = '10px'; style.bottom = '10px';
style.left = '50%'; style.left = '50%';
@ -133,18 +196,18 @@ const tipsContentStyle = computed(() => {
} }
return style; return style;
}); });
let timer = null; let handlePointTapTimer = null;
function handlePointTap(e) { function handlePointTap(e) {
tipsContent.value = e.label; tipsContent.value = e.label;
activeTipsXY.value = { activeTipsXY.value = {
x: e.left - (e.size / 2), x: e.left,
y: e.top - e.size, y: e.top - 10,
}; };
tipsShow.value = true; tipsShow.value = true;
if (timer) { if (handlePointTapTimer) {
clearTimeout(timer); clearTimeout(handlePointTapTimer);
} }
timer = setTimeout(() => { handlePointTapTimer = setTimeout(() => {
tipsShow.value = false; tipsShow.value = false;
}, 5000); }, 5000);
} }
@ -173,6 +236,20 @@ function handlePointTap(e) {
background: rgba(#000, 0.8); background: rgba(#000, 0.8);
box-shadow: 1px 4px 8px rgba(#303841, 0.4); box-shadow: 1px 4px 8px rgba(#303841, 0.4);
z-index: 100; z-index: 100;
white-space: pre;
//
&::before {
content: '';
position: absolute;
bottom: 100%;
left: 50%;
width: 0;
height: 0;
border: 5px solid transparent;
border-bottom-color: rgba(#000, 0.8);
transform: translateX(-50%);
}
} }
} }

View File

@ -1,35 +0,0 @@
import config from '@/config';
import CODE_MAPS, {
countryCodeMapping,
aliasMapping,
} from '@/data/code-maps';
export const ALIAS_CODE = {
...aliasMapping,
...countryCodeMapping,
};
export const alias2code = (code) => ALIAS_CODE[code];
export const locationCode2Info = (code) => {
const maps = {
...CODE_MAPS,
...(config.nazhua.customCodeMap || {}),
};
let info = maps[code];
const aliasCode = aliasMapping[code];
if (!info && aliasCode) {
info = maps[aliasCode];
}
return info;
};
export const count2size = (count) => {
if (count < 3) {
return 4;
}
if (count < 5) {
return 6;
}
return 8;
};

61
src/utils/world-map.js Normal file
View File

@ -0,0 +1,61 @@
import config from '@/config';
import CODE_MAPS, {
countryCodeMapping,
aliasMapping,
} from '@/data/code-maps';
export const ALIAS_CODE = {
...aliasMapping,
...countryCodeMapping,
};
export const alias2code = (code) => ALIAS_CODE[code];
export const locationCode2Info = (code) => {
const maps = {
...CODE_MAPS,
...(config.nazhua.customCodeMap || {}),
};
let info = maps[code];
const aliasCode = aliasMapping[code];
if (!info && aliasCode) {
info = maps[aliasCode];
}
return info;
};
export const count2size = (count) => {
if (count < 3) {
return 4;
}
if (count < 5) {
return 6;
}
return 8;
};
export function findIntersectingGroups(coordinates) {
const groups = {};
coordinates.forEach((coordinate, index) => {
const intersects = [];
const n = 2;
coordinates.forEach((otherCoordinate, otherIndex) => {
if (index !== otherIndex) {
if (
coordinate.topLeft.top - otherCoordinate.bottomRight.top < n
&& coordinate.topLeft.left - otherCoordinate.bottomRight.left < n
&& coordinate.bottomRight.top - otherCoordinate.topLeft.top > -n
&& coordinate.bottomRight.left - otherCoordinate.topLeft.left > -n
) {
intersects.push(otherCoordinate);
}
}
});
if (intersects.length > 0) {
groups[coordinate.key] = intersects;
}
});
return groups;
}

View File

@ -53,7 +53,7 @@ import config from '@/config';
import { import {
alias2code, alias2code,
locationCode2Info, locationCode2Info,
} from '@/utils/world-map-location'; } from '@/utils/world-map';
import WorldMap from '@/components/world-map/world-map.vue'; import WorldMap from '@/components/world-map/world-map.vue';
import ServerName from './components/server-detail/server-name.vue'; import ServerName from './components/server-detail/server-name.vue';
@ -99,6 +99,7 @@ const locations = computed(() => {
code, code,
size: 4, size: 4,
label: `${name}`, label: `${name}`,
servers: [info.value],
}); });
} }
return arr; return arr;

View File

@ -64,7 +64,7 @@ import {
alias2code, alias2code,
locationCode2Info, locationCode2Info,
count2size, count2size,
} from '@/utils/world-map-location'; } from '@/utils/world-map';
import uuid from '@/utils/uuid'; import uuid from '@/utils/uuid';
import WorldMap from '@/components/world-map/world-map.vue'; import WorldMap from '@/components/world-map/world-map.vue';
@ -149,13 +149,13 @@ const serverLocations = computed(() => {
const code = alias2code(aliasCode) || locationCode; const code = alias2code(aliasCode) || locationCode;
if (code) { if (code) {
if (!locationMap[code]) { if (!locationMap[code]) {
locationMap[code] = 0; locationMap[code] = [];
} }
locationMap[code] += 1; locationMap[code].push(i);
} }
}); });
const locations = []; const locations = [];
Object.entries(locationMap).forEach(([code, count]) => { Object.entries(locationMap).forEach(([code, servers]) => {
const { const {
x, x,
y, y,
@ -167,8 +167,9 @@ const serverLocations = computed(() => {
x, x,
y, y,
code, code,
size: count2size(count), size: count2size(servers.length),
label: `${name},${count}`, label: `${name},${servers.length}`,
servers,
}); });
} }
}); });