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
ref="pointRef"
class="world-map-point"
:class="'world-map-point--' + (info?.type || 'default')"
:style="pointStyle"
:title="info?.label || ''"
@click="handleClick"
@ -88,5 +89,29 @@ function handleClick() {
@media screen and (max-width: 720px) {
--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>

View File

@ -37,10 +37,14 @@
import {
ref,
computed,
watch,
} from 'vue';
import validate from '@/utils/validate';
import WorldMapPoint from './world-map-point.vue';
import {
findIntersectingGroups,
} from '@/utils/world-map';
const props = defineProps({
width: {
@ -100,16 +104,75 @@ const mapStyle = computed(() => {
return style;
});
const mapPoints = computed(() => props.locations.map((i) => {
const item = {
key: i.key,
left: (computedSize.value.width / 1280) * i.x,
top: (computedSize.value.height / 621) * i.y,
size: i.size || 4,
label: i.label,
};
return item;
}));
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 = {
key: i.key,
left: (computedSize.value.width / 1280) * i.x,
top: (computedSize.value.height / 621) * i.y,
size: i.size || 4,
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;
});
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) {
style.top = `${activeTipsXY.value.y}px`;
style.left = `${activeTipsXY.value.x}px`;
style.transform = 'translate(-50%, 100%)';
style.transform = 'translate(-50%, 20px)';
} else {
style.bottom = '10px';
style.left = '50%';
@ -133,18 +196,18 @@ const tipsContentStyle = computed(() => {
}
return style;
});
let timer = null;
let handlePointTapTimer = null;
function handlePointTap(e) {
tipsContent.value = e.label;
activeTipsXY.value = {
x: e.left - (e.size / 2),
y: e.top - e.size,
x: e.left,
y: e.top - 10,
};
tipsShow.value = true;
if (timer) {
clearTimeout(timer);
if (handlePointTapTimer) {
clearTimeout(handlePointTapTimer);
}
timer = setTimeout(() => {
handlePointTapTimer = setTimeout(() => {
tipsShow.value = false;
}, 5000);
}
@ -173,6 +236,20 @@ function handlePointTap(e) {
background: rgba(#000, 0.8);
box-shadow: 1px 4px 8px rgba(#303841, 0.4);
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 {
alias2code,
locationCode2Info,
} from '@/utils/world-map-location';
} from '@/utils/world-map';
import WorldMap from '@/components/world-map/world-map.vue';
import ServerName from './components/server-detail/server-name.vue';
@ -99,6 +99,7 @@ const locations = computed(() => {
code,
size: 4,
label: `${name}`,
servers: [info.value],
});
}
return arr;

View File

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