mirror of
https://github.com/hi2shark/nazhua.git
synced 2026-01-12 07:10:43 +08:00
fix: 优化世界地图点的计算逻辑,添加分组支持并重构相关样式
This commit is contained in:
parent
b825894796
commit
20281a1848
@ -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>
|
||||
|
||||
@ -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%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
61
src/utils/world-map.js
Normal 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;
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user