feat: 优化卫星页面加载和状态管理,引入加载动画和响应式数据处理
All checks were successful
/ depcheck (push) Successful in 2m11s
/ lint-build-and-check (push) Successful in 3m11s
/ build-and-deploy-to-vercel (push) Successful in 3m22s
/ surge (push) Successful in 2m30s
/ playwright (push) Successful in 5m13s

This commit is contained in:
严浩
2025-03-12 18:33:26 +08:00
parent 38f35328b3
commit 447907aa7c
2 changed files with 93 additions and 66 deletions

View File

@ -11,45 +11,44 @@ const { tleList, viewer } = defineProps<{
viewer: null | Viewer; viewer: null | Viewer;
}>(); }>();
// 存储卫星实体和选中状态 // 提取卫星名称的函数
interface SatelliteItem { const getSatelliteName = (tle: string) => {
entity: Entity | null; // 存储添加到viewer的实体引用 return (tle.split('\n') as [string, string, string])[0].trim();
name: string; };
selected: boolean;
tle: string;
}
// 初始化卫星数据 // 将satellites改为计算属性响应tleList的变化
const satellites = ref<SatelliteItem[]>( const satellites = computed(() => Array.from(new Set(tleList)));
tleList.map((tle) => {
const name = (tle.split('\n') as [string, string, string])[0].trim(); // 创建Map存储卫星实体
return { const satelliteEntities = ref<Map<string, Entity>>(new Map());
entity: null,
name, // 创建Set存储已选中的卫星TLE
selected: false, const selectedSatellites = ref<Set<string>>(new Set());
tle,
}; const searchText = ref('');
}),
);
const state = reactive({ const state = reactive({
checkAll: false, checkAll: false,
indeterminate: false, indeterminate: false,
searchText: '',
}); });
// 计算属性 // 计算属性
const selectedCount = computed(() => satellites.value.filter((s) => s.selected).length); const selectedCount = computed(() => selectedSatellites.value.size);
const totalCount = computed(() => satellites.value.length); const totalCount = computed(() => satellites.value.length);
// 过滤后的卫星列表 - 添加记忆化以提高性能 // 过滤后的卫星列表 - 添加记忆化以提高性能
const filteredSatellites = computed(() => { const filteredSatellites = computed(() => {
if (!state.searchText) return satellites.value; if (!searchText.value) return satellites.value;
const searchLower = state.searchText.toLowerCase(); const searchLower = searchText.value.toLowerCase();
return satellites.value.filter((s) => s.name.toLowerCase().includes(searchLower)); return satellites.value.filter((tle) => getSatelliteName(tle).toLowerCase().includes(searchLower));
}); });
// 判断卫星是否被选中
const isSatelliteSelected = (tle: string) => {
return selectedSatellites.value.has(tle);
};
// 监听选中状态变化,更新全选和半选状态 // 监听选中状态变化,更新全选和半选状态
watchEffect(() => { watchEffect(() => {
const count = selectedCount.value; const count = selectedCount.value;
@ -57,43 +56,61 @@ watchEffect(() => {
state.indeterminate = count > 0 && count < filteredCount; state.indeterminate = count > 0 && count < filteredCount;
state.checkAll = count === filteredCount && filteredCount > 0; state.checkAll = count === filteredCount && filteredCount > 0;
}); });
// 更新卫星实体(添加或移除) // 更新卫星实体(添加或移除)
const updateSatelliteEntity = (satellite: SatelliteItem) => { const updateSatelliteEntity = (tle: string, selected: boolean) => {
if (!viewer) return; if (!viewer) return;
if (satellite.selected) { const satelliteName = getSatelliteName(tle);
if (selected) {
// 添加卫星到viewer // 添加卫星到viewer
try { try {
const satelliteObj = new SatelliteEntity(satellite.tle); const satelliteObj = new SatelliteEntity(tle);
const cesiumSateEntity = satelliteObj.createSatelliteEntity(); const cesiumSateEntity = satelliteObj.createSatelliteEntity();
const result = viewer.entities.add(cesiumSateEntity); const result = viewer.entities.add(cesiumSateEntity);
satellite.entity = result; // 保存实体引用以便后续移除 satelliteEntities.value.set(tle, result);
} catch (error) { } catch (error) {
console.error(`添加卫星 ${satellite.name} 失败:`, error); console.error(`添加卫星 ${satelliteName} 失败:`, error);
} }
} else { } else {
// 从viewer中移除卫星 // 从viewer中移除卫星
if (satellite.entity) { const entity = satelliteEntities.value.get(tle);
viewer.entities.remove(satellite.entity); if (entity) {
satellite.entity = null; viewer.entities.remove(entity);
satelliteEntities.value.delete(tle);
} }
} }
}; };
// 切换卫星选中状态 // 切换卫星选中状态
const toggleSatellite = (satellite: SatelliteItem) => { const toggleSatellite = (tle: string) => {
satellite.selected = !satellite.selected; const isSelected = isSatelliteSelected(tle);
updateSatelliteEntity(satellite);
if (isSelected) {
selectedSatellites.value.delete(tle);
} else {
selectedSatellites.value.add(tle);
}
updateSatelliteEntity(tle, !isSelected);
}; };
// 全选/取消全选 // 全选/取消全选
const onCheckAllChange = (e: { target: { checked: boolean } }) => { const onCheckAllChange = (e: { target: { checked: boolean } }) => {
const checked = e.target.checked; const checked = e.target.checked;
filteredSatellites.value.forEach((satellite) => { filteredSatellites.value.forEach((tle) => {
if (satellite.selected !== checked) { const isCurrentlySelected = isSatelliteSelected(tle);
satellite.selected = checked;
updateSatelliteEntity(satellite); if (isCurrentlySelected !== checked) {
if (checked) {
selectedSatellites.value.add(tle);
} else {
selectedSatellites.value.delete(tle);
}
updateSatelliteEntity(tle, checked);
} }
}); });
}; };
@ -102,26 +119,27 @@ const onCheckAllChange = (e: { target: { checked: boolean } }) => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (viewer) { if (viewer) {
// 移除所有实体 // 移除所有实体
satellites.value.forEach((satellite) => { satelliteEntities.value.forEach((entity) => {
if (satellite.entity) { viewer?.entities.remove(entity);
viewer?.entities.remove(satellite.entity);
satellite.entity = null;
}
}); });
satelliteEntities.value.clear();
selectedSatellites.value.clear();
} }
}); });
</script> </script>
<template> <template>
<div class="absolute left-0 top-0 p-4"> <div class="SatelliteSelectorPanel">
<APopover :overlay-style="{ width: '320px' }" placement="bottomLeft" trigger="click"> <APopover :overlay-style="{ width: '320px' }" placement="bottomLeft" trigger="click">
<template #title> <template #title>
选择卫星 选择卫星
<span class="text-xs font-normal text-gray-500">{{ `(已选择: ${selectedCount}/${totalCount})` }}</span> <span class="text-xs font-normal text-gray-700 dark:text-gray-400">{{
`(已选择: ${selectedCount}/${totalCount})`
}}</span>
</template> </template>
<template #content> <template #content>
<!-- 搜索框 --> <!-- 搜索框 -->
<AInput v-model:value="state.searchText" allow-clear placeholder="搜索卫星"> <AInput v-model:value="searchText" allow-clear placeholder="搜索卫星">
<template #prefix><SearchOutlined /></template> <template #prefix><SearchOutlined /></template>
</AInput> </AInput>
@ -143,13 +161,13 @@ onBeforeUnmount(() => {
<!-- 卫星列表 --> <!-- 卫星列表 -->
<div class="satellite-list max-h-60 overflow-y-auto pr-1"> <div class="satellite-list max-h-60 overflow-y-auto pr-1">
<ACheckbox <ACheckbox
v-for="satellite in filteredSatellites" v-for="tle in filteredSatellites"
:key="satellite.tle" :key="tle"
:checked="satellite.selected" :checked="isSatelliteSelected(tle)"
class="my-2 flex w-full" class="my-2 flex w-full"
@change="() => toggleSatellite(satellite)" @change="() => toggleSatellite(tle)"
> >
{{ satellite.name }} {{ getSatelliteName(tle) }}
</ACheckbox> </ACheckbox>
</div> </div>
</template> </template>

View File

@ -6,19 +6,29 @@ import { TLE_LIST } from './cesium-helper/_TLE_DATA';
import SatelliteSelector from './components/SatelliteSelector.vue'; import SatelliteSelector from './components/SatelliteSelector.vue';
// Cesium相关 // Cesium相关
let viewer: null | Viewer = $ref(null); let viewer = $ref<null | Viewer>(null);
const tleList = ref([] as string[]);
const spinning = ref(true);
// 模拟接口获取TLE数据
const fetchTLEData = async () => {
spinning.value = true; // 开始加载设置状态为true
await new Promise((resolve) => setTimeout(resolve, 2000)); // 模拟2秒延迟
tleList.value = TLE_LIST; // 设置TLE数据
spinning.value = false; // 加载完成设置状态为false
};
// 初始化Cesium // 初始化Cesium
onMounted(() => { onMounted(async () => {
fetchTLEData();
const container = document.querySelector('#cesiumContainer'); const container = document.querySelector('#cesiumContainer');
if (!container) return; if (!container) return;
viewer = cesium_init(container); viewer = cesium_init(container);
// 开发调试用,生产环境可移除 Object.assign(globalThis, { viewer });
if (import.meta.env.DEV) {
Object.assign(globalThis, { viewer });
}
}); });
// 清理资源 // 清理资源
@ -32,13 +42,12 @@ onBeforeUnmount(() => {
</script> </script>
<template> <template>
<div id="cesiumContainer" class="z-0"> <div id="cesiumContainer" class="absolute top-0 left-0 right-0 bottom-0">
<SatelliteSelector class="z-1" :viewer="viewer" :tle-list="TLE_LIST" /> <ASpin
size="large"
v-if="spinning"
class="z-101 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
/>
<SatelliteSelector class="z-100 absolute left-0 top-0 p-4" :viewer="viewer" :tle-list />
</div> </div>
</template> </template>
<style scoped>
#cesiumContainer {
@apply absolute top-0 left-0 right-0 bottom-0;
}
</style>