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