通信模块配置软件

This commit is contained in:
2025-12-31 15:17:05 +08:00
parent 0a1fb3eeb3
commit 267462fc3f
48 changed files with 8632 additions and 505 deletions

View File

@@ -4,6 +4,49 @@
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
/* 当屏幕宽度大于或等于3840px时 */
@media (min-width: 3840px) {
html {
font-size: 40px;
}
}
/* 当屏幕宽度在2560px到3839px之间时 */
@media (min-width: 2560px) and (max-width: 3839px) {
html {
font-size: 26px;
}
}
/* 当屏幕宽度在1920px到2559px之间时 */
@media (min-width: 1920px) and (max-width: 2559px) {
html {
font-size: 20px;
}
}
/* 当屏幕宽度在750px到1919px之间时 */
@media (min-width: 750px) and (max-width: 1919px) {
html {
font-size: 14px;
}
}
/* 当屏幕宽度在600px到749px之间时 */
@media (min-width: 600px) and (max-width: 749px) {
html {
font-size: 14px;
}
}
/* 当屏幕宽度小于600px时 */
@media (max-width: 599px) {
html {
font-size: 12px;
}
}
</style>
<title>数字星球互动体验系统</title>
</head>
<body class="w-h-full">

View File

@@ -9,28 +9,34 @@
"build": "vite build",
"serve": "vite preview",
"electron:dev": "cross-env NODE_ENV=development electron index.js",
"electron:build": "rimraf dist && vite build && tsc -p tsconfig.electron.json && electron-builder --dir"
"electron:build": "rimraf dist && vite build && tsc -p tsconfig.electron.json && electron-builder"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@types/three": "^0.178.1",
"cesium": "^1.88.0",
"global-navigation-satellite-system": "file:",
"electron-store": "^8.2.0",
"element-plus": "^2.10.4",
"three": "^0.178.0",
"vue": "^3.2.26",
"vue-cesium": "^3.0.4",
"vue-cesium": "^v3.2.8",
"vuex": "^4.0.1"
},
"devDependencies": {
"@babel/types": "^7.28.1",
"@types/node": "^24.2.1",
"@vitejs/plugin-vue": "^1.1.5",
"@vue/compiler-sfc": "^3.2.26",
"autoprefixer": "^10.2.5",
"cross-env": "^7.0.3",
"electron": "^13.6.3",
"electron-builder": "^24.4.0",
"electron": "^22.3.27",
"electron-builder": "^25.0.5",
"postcss": "^8.4.47",
"rimraf": "^3.0.2",
"rollup-plugin-copy": "^3.4.0",
"tailwindcss": "^2.1.1",
"ts-node": "^9.1.1",
"typescript": "^4.2.3",
"ts-node": "^10.9.2",
"typescript": "^5.8.3",
"vite": "^2.0.5"
},
"build": {

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 756 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -1,3 +1,3 @@
// 加密关键字
const keyword = 'BD'
export { keyword }
const keyword = 'BD';
export { keyword };

View File

@@ -1,69 +1,74 @@
import { app, globalShortcut, BrowserWindow, ipcMain, dialog } from 'electron'
import { join } from 'path'
import { keyword } from './KeyWord'
import { app, globalShortcut, BrowserWindow, ipcMain, dialog } from 'electron';
import { join } from 'path';
// const config = require('./config')
import config from './config';
import { keyword } from './KeyWord';
const child_process = require('child_process')
const crypto = require('crypto')
const child_process = require('child_process');
const crypto = require('crypto');
// 禁用硬件加速
// app.disableHardwareAcceleration();
function queryPass(passPath: string, passValue: string) {
return new Promise(function (resolve, reject) {
try {
child_process.exec(`reg query ${passPath} /v ${passValue}`, (error: Error, stdout: string, stderr: string) => {
if (error) {
reject(error)
return
reject(error);
return;
}
resolve({stdout, stderr})
resolve({stdout, stderr});
});
} catch (error) {
reject(error)
reject(error);
}
})
}
});
};
function queryKey(keyPath: string, keyValue: string) {
return new Promise(function (resolve, reject) {
try {
child_process.exec(`reg query ${keyPath} /v ${keyValue}`, (error: Error, stdout: string, stderr: string) => {
if (error) {
reject(error)
return
reject(error);
return;
}
resolve({stdout, stderr})
resolve({stdout, stderr});
})
} catch (error) {
reject(error)
reject(error);
}
})
}
});
};
function cryptMD5(GUID: string) {
let md5 = crypto.createHash('md5')
let ciphertext = md5.update(GUID).digest('hex')
return ciphertext.slice(0,8)+'-'+ciphertext.slice(8,12)+'-'+ciphertext.slice(12,16)+'-'+ciphertext.slice(16,20)+'-'+ciphertext.slice(20,32)
}
let md5 = crypto.createHash('md5');
let ciphertext = md5.update(GUID).digest('hex');
return ciphertext.slice(0,8)+'-'+ciphertext.slice(8,12)+'-'+ciphertext.slice(12,16)+'-'+ciphertext.slice(16,20)+'-'+ciphertext.slice(20,32);
};
const passPath = 'HKEY_CURRENT_USER\\SOFTWARE\\HwaSmart'
const passValue = 'BDAuthorization'
const keyPath = 'HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography'
const keyValue = 'MachineGuid'
const passPath = 'HKEY_CURRENT_USER\\SOFTWARE\\HwaSmart';
const passValue = 'BDAuthorization';
const keyPath = 'HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography';
const keyValue = 'MachineGuid';
async function checkLaunchEnv() {
try {
const passResult: any = await queryPass(passPath, passValue)
const keyResult: any = await queryKey(keyPath, keyValue)
const passResult: any = await queryPass(passPath, passValue);
const keyResult: any = await queryKey(keyPath, keyValue);
if(cryptMD5(keyResult.stdout.slice(83,119) + keyword) == passResult.stdout.slice(72,108)){
return true
return true;
}else{
return false
return false;
}
// 成功
// 查询到 有这个app启动项
} catch (error) {
// 没有查询到该app启动项目
return false
return false;
}
}
};
function createWindow() {
const win = new BrowserWindow({
@@ -71,17 +76,24 @@ function createWindow() {
frame: false,
webPreferences: {
nodeIntegration: true,
preload: join(__dirname, 'preload.js')
preload: join(__dirname, 'preload.js'),
}
})
});
if (process.env.NODE_ENV === 'development') {
win.loadURL('http://localhost:3000/')
win.webContents.openDevTools()
win.loadURL('http://localhost:3000/');
win.webContents.openDevTools();
} else {
win.loadFile('dist/render/index.html')
win.loadFile('dist/render/index.html');
// win.webContents.openDevTools();
}
ipcMain.on("APP_MOUNTED", (event) => {
const { sender } = event;
let softwareName = config.get('softwareName');
sender.send('softwareName', softwareName);
});
ipcMain.on('CLOSE', (event) => {
const res = dialog.showMessageBox({
type: 'warning',
@@ -91,49 +103,48 @@ function createWindow() {
cancelId: 1, // 按esc默认点击索引按钮
defaultId: 0, // 默认高亮的按钮下标
buttons: ['确认', '取消'], // 按钮按索引从右往左排序
})
});
res.then((data)=>{
if(data.response == 0){
win.close()
win.close();
}else{
console.log('not close software')
console.log('not close software');
}
})
})
}
});
});
};
async function main() {
// 异步代码
const checkReault: any = await checkLaunchEnv()
console.log('env right:', checkReault)
const checkReault: any = await checkLaunchEnv();
console.log('env right:', checkReault);
// 异步代码执行完毕后执行的代码
if (checkReault) {
app.whenReady().then(async () => {
createWindow()
createWindow();
// 屏蔽 F11 进入/退出全屏功能
globalShortcut.register('F11', () => {return})
globalShortcut.register('F11', () => {return});
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
app.quit();
}
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
createWindow();
}
})
} else {
dialog.showErrorBox('系统提示', '软件启动出错,请联系售后技术支持人员')
process.exit(1)
dialog.showErrorBox('系统提示', '软件启动出错,请联系售后技术支持人员');
process.exit(1);
}
}
};
main()
main();

8
src/main/config.js Normal file
View File

@@ -0,0 +1,8 @@
const Store = require('electron-store');
const store = new Store();
const set = (key, val) => store.set(key, val);
const get = (key) => store.get(key);
module.exports = { set, get };

View File

@@ -1,20 +1,20 @@
const {ipcRenderer, contextBridge} = require('electron')
const {ipcRenderer, contextBridge} = require('electron');
contextBridge.exposeInMainWorld('ipcRenderer', {
send: (channel, data) => {
// whitelist channels
let validChannels = ['toMain']
let validChannels = ['toMain'];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data)
ipcRenderer.send(channel, data);
}
ipcRenderer.send(channel, data)
ipcRenderer.send(channel, data);
},
receive: (channel, func) => {
let validChannels = ['fromMain']
let validChannels = ['fromMain'];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args))
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
ipcRenderer.on(channel, (event, ...args) => func(...args))
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
})
});

View File

@@ -1,18 +1,330 @@
<template>
<div class="w-h-full">
<EarthView />
<div class="w-h-full overflow-hidden">
<div class="w-h-full overflow-hidden" v-show="satelliteSystemName == 'custom'">
<button class="backSatelliteSystemView" @click="handleBackSatelliteSystemView"> 返回卫星系统视图 </button>
<CustomSatelliteSystem ref="customSatelliteSystemRef" />
</div>
<div class="w-h-full overflow-hidden" v-show="satelliteSystemName !== 'custom'">
<div v-show="satelliteSystemName == 'beidou'" class="RSMC">
<div class="option">
北斗三号区域短报文通信信号覆盖区域
</div>
<div class="option">
<div>单波束:</div>
<input
:checked="singleRSMCVisible"
type="checkbox"
class="w-6 h-6 cursor-pointer"
@change="showRSMC('singleBeam')"
/>
</div>
<div class="option">
<div>双波束:</div>
<input
:checked="dualRSMCVisible"
type="checkbox"
class="w-6 h-6 cursor-pointer"
@change="showRSMC('dualBeam')"
/>
</div>
</div>
<FlagView class="fixed bottom-0 right-0" />
<SatelliteSystemSelectView class="fixed top-1" />
<SatelliteTableView class="fixed left-0 right-0 bottom-0" />
<EarthView ref="earthViewRef" @showModel="showModel" />
<ShowModel
ref="showModelRef"
v-if="allowShow"
class="fixed left-0"
style="bottom: 36vh; z-index: 99"
@showModel="showModel"
/>
<FlagView class="fixed bottom-0 right-0" />
<Quiz ref="quizRef" />
<SatelliteSystemSelectView
ref="SatelliteSystemSelectViewRef"
class="fixed top-1"
@handleSatelliteSystemSelect="handleSatelliteSystemSelect"
@handleElementsFly="handleElementsFly"
/>
<SatelliteTableView ref="satelliteTableViewRef" />
<Tour @handleNext="handleNextEvent" />
</div>
</div>
</template>
<script lang="ts" setup>
import FlagView from './components/FlagView.vue'
import EarthView from './components/EarthView.vue'
import SatelliteTableView from './components/SatelliteTableView.vue'
import SatelliteSystemSelectView from './components/SatelliteSystemSelectView.vue'
</script>
<script setup>
import { ref, watch } from "vue";
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import CustomSatelliteSystem from "./components/CustomSatelliteSystem/CustomSatelliteSystem.vue";
import EarthView from "./components/SatelliteSystem/EarthView.vue";
import ShowModel from "./components/SatelliteSystem/ShowModel.vue";
import FlagView from "./components/SatelliteSystem/FlagView.vue";
import Quiz from "./components/SatelliteSystem/Quiz.vue";
import SatelliteTableView from "./components/SatelliteSystem/SatelliteTableView.vue";
import SatelliteSystemSelectView from "./components/SatelliteSystem/SatelliteSystemSelectView.vue";
import Tour from "./components/SatelliteSystem/Tour.vue";
const customSatelliteSystemRef = ref(null);
const earthViewRef = ref(null);
const showModelRef = ref(null);
const allowShow = ref(false);
const quizRef = ref(null);
const SatelliteSystemSelectViewRef = ref(null);
const satelliteTableViewRef = ref(null);
const satelliteSystemName = ref("beidou");
const singleRSMCVisible = ref(false);
const dualRSMCVisible = ref(false);
// 缓存模型对象
const modelsObj = {
BeidouGEO: {
scale: [0.3, 0.3, 0.3],
position: [0, -1.5, 0],
},
BeidouMEO: {
scale: [0.13, 0.13, 0.13],
position: [0, -1, 0],
},
BeidouIGSO: {
scale: [0.18, 0.18, 0.18],
position: [0, -1, 0],
},
"GPSII-F": {
scale: [0.35, 0.35, 0.35],
position: [0, 0, 0],
},
"GPSII-R": {
scale: [0.5, 0.5, 0.5],
position: [0, 0, 0],
},
"GPSIII-A": {
scale: [0.6, 0.6, 0.6],
position: [0, 0, 0],
},
"GPSIIR-M": {
scale: [0.5, 0.5, 0.5],
position: [0, 0, 0],
},
"GLONASSGLO-K": {
scale: [0.5, 0.5, 0.4],
position: [0, 0, -0.5],
},
"GLONASSGLO-M": {
scale: [0.5, 0.5, 0.5],
position: [0, 0, -0.5],
},
GalileoMEO: {
scale: [0.2, 0.2, 0.2],
position: [0, 0, 0],
},
};
let scene = new THREE.Scene();
class Model {
constructor(modelUrl) {
this.modelUrl = modelUrl;
this.model;
this.loadModel();
}
loadModel() {
const loader = new GLTFLoader();
const modelObj = modelsObj[this.modelUrl];
loader.load(`./models/geo/${this.modelUrl}.glb`, (gltf) => {
//设置模型大小
gltf.scene.scale.set(modelObj.scale[0], modelObj.scale[1], modelObj.scale[2]);
gltf.scene.position.set(
modelObj.position[0],
modelObj.position[1],
modelObj.position[2]
);
gltf.scene.name = this.modelUrl;
this.model = gltf.scene;
scene.add(gltf.scene);
});
}
}
const GEOModel = new Model("BeidouGEO");
const IGSOModel = new Model("BeidouIGSO");
const MEOModel = new Model("BeidouMEO");
const GPSIIFModel = new Model("GPSII-F");
const GPSIIRModel = new Model("GPSII-R");
const GPSIIIAModel = new Model("GPSIII-A");
const GPSIIRMModel = new Model("GPSIIR-M");
const GLONASSGLOKModel = new Model("GLONASSGLO-K");
const GLONASSGLOMModel = new Model("GLONASSGLO-M");
const GalileoMEOModel = new Model("GalileoMEO");
const modelCache = {
beidou: {
GEO: GEOModel,
IGSO: IGSOModel,
MEO: MEOModel
},
other: {
"II-F": GPSIIFModel,
"II-R": GPSIIRModel,
"III-A": GPSIIIAModel,
"IIR-M": GPSIIRMModel,
"GLO-K": GLONASSGLOKModel,
"GLO-M": GLONASSGLOMModel,
MEO: GalileoMEOModel
}
};
const showModel = (show, model, system) => {
allowShow.value = show;
if (model !== "") {
let systemMark = "";
if (system == "-1") {
systemMark = "other"
} else {
systemMark = "beidou"
}
if (systemMark !== "") {
setTimeout(() => {
showModelRef.value.model = modelCache[systemMark][model];
showModelRef.value.loadModel();
}, 0);
}
}
};
const handleBackSatelliteSystemView = () => {
SatelliteSystemSelectViewRef.value.selected = "beidou";
satelliteSystemName.value = "beidou";
};
const showRSMC = (model) => {
if (model == 'singleBeam') {
earthViewRef.value.singleBeamShow = !earthViewRef.value.singleBeamShow;
singleRSMCVisible.value = !singleRSMCVisible.value;
} else if (model == 'dualBeam') {
earthViewRef.value.dualBeamShow = !earthViewRef.value.dualBeamShow;
dualRSMCVisible.value = !dualRSMCVisible.value;
}
};
const handleSatelliteSystemSelect = (value) => {
satelliteSystemName.value = value;
earthViewRef.value.firstClick = true;
};
const handleElementsFly = () => {
setTimeout(() => {
earthViewRef.value.flyfunc(true);
}, 1000);
};
const handleNextEvent = (step) => {
switch (step) {
case "#step9":
satelliteTableViewRef.value.show = true;
break;
case "#step12":
satelliteTableViewRef.value.onTrackButtonClicked(
window.document.getElementById("satelliteId").innerText
);
break;
case "#step15":
satelliteTableViewRef.value.onTrackButtonClicked(
window.document.getElementById("satelliteId").innerText
);
setTimeout(() => {
window.document.getElementById("step14").click();
}, 0);
break;
case "#step22":
quizRef.value.show = true;
break;
}
};
watch(allowShow, (value) => {
earthViewRef.value.showModelTour();
});
watch(satelliteSystemName, (value) => {
if (value == "custom") {
customSatelliteSystemRef.value.animationShow = true;
earthViewRef.value.animationShow = false;
} else {
if (value !== "beidou") {
earthViewRef.value.singleBeamShow = false;
earthViewRef.value.dualBeamShow = false;
} else {
singleRSMCVisible.value = false;
dualRSMCVisible.value = false;
}
customSatelliteSystemRef.value.animationShow = false;
earthViewRef.value.animationShow = true;
}
});
</script>
<style scoped>
.backSatelliteSystemView {
font-size: 1rem;
color: #000000;
background-color: #ffffff;
width: 10rem;
height: 2rem;
border-radius: 1rem;
line-height: 2rem;
overflow: hidden;
box-shadow: 0 0px 1rem 0px rgb(165 243 252 / 1);
position: fixed;
top: 0.5rem;
left: 0.5rem;
z-index: 99;
}
.backSatelliteSystemView:hover {
background-color: rgb(165 243 252 / 1);
}
.RSMC {
font-size: 1rem;
color: #ffffff;
text-align: center;
background-color: #ffffff;
width: 1rem;
height: 8rem;
line-height: 2rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: hidden;
box-shadow: 0 0px 1rem 0px rgb(165 243 252 / 1);
position: fixed;
bottom: 24rem;
right: 0;
z-index: 99;
border-top-left-radius: 1rem;
border-bottom-left-radius: 1rem;
}
.RSMC:hover {
width: 18rem;
height: 6rem;
color: #000000;
background-color: rgb(165 243 252 / 1);
}
.option {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
</style>

View File

@@ -1,23 +1,23 @@
import store from '../store'
import store from '../store';
let entities = []
let entities = [];
const load = (value) => {
entities = value
entities = value;
// 将卫星状态信息存储到store中
const satellites = filter('Satellite')
const satellite_state_arr = satellites.map(({ id, show }) => { return { id, show } })
const satellites = filter('Satellite');
const satellite_state_arr = satellites.map(({ description, id, show }) => { return { description: description._value, id, show } });
store.dispatch('satelliteSystem/setSatellites', {
satellites: satellite_state_arr
})
}
});
};
const get = id => entities.find(entity => entity.id === id)
const get = id => entities.find(entity => entity.id === id);
const find = keyword => entities.find(entity => entity.id.startsWith(keyword))
const find = keyword => entities.find(entity => entity.id.startsWith(keyword));
const filter = keyword => entities.filter(entity => entity.id.startsWith(keyword))
const filter = keyword => entities.filter(entity => entity.id.startsWith(keyword));
export default { get, load, find }
export default { get, load, find };

BIN
src/render/assets/sky.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

View File

@@ -0,0 +1,161 @@
<template>
<table class="text-center">
<thead class="text-2xl">
<th
class="py-3 bg-blue-900 border border-gray-900"
v-for="(label, index) in ['轨道序号', '轨道名称', '显示/隐藏', '卫星列表', '操作']"
:key="label"
:style="{ width: index == 4 ? '40vw' : '20vw' }"
>
<div style="display: flex; justify-content: center; align-items: center;">
<div style="margin-right: 0.5rem;">
{{ label }}
</div>
<input
v-if="label == '显示/隐藏'"
:disabled="satelliteArr.length > 0 ? false : true"
indeterminate="false"
:checked="allShow"
type="checkbox"
class="w-6 h-6 cursor-pointer"
@change="allSatelliteShow('all')"
/>
<input
v-if="label == '操作'"
:disabled="satelliteArr.length > 0 ? false : true"
type="button"
class="buttonClass"
id="fly-btn"
@click="allClearSatellite('all')"
value="清除全部轨道"
/>
</div>
</th>
</thead>
<tbody class="text-lg odd:bg-gray-50">
<tr v-for="(entity, index) in satelliteArr" :key="index">
<td class="border border-gray-900"> {{ index + 1 }} </td>
<td class="border border-gray-900">{{ entity.orbitViewer.name }}</td>
<td class="border border-gray-900">
<div style="display: flex; justify-content: center; align-items: center;">
<input
:checked="entity.show"
type="checkbox"
class="w-6 h-6 cursor-pointer"
@change="onShowSatellite(index, 'main')"
/>
</div>
</td>
<td class="border border-gray-900">
<div style="display: flex; justify-content: center; align-items: center;">
<input
:disabled="satelliteArr[index].subSatelliteArr.length == 0"
:checked="entity.createSub"
type="checkbox"
class="w-6 h-6 cursor-pointer"
@change="showSubSatelliteTable(index)"
/>
</div>
</td>
<td class="border border-gray-900" style="display: flex; justify-content: space-around; align-items: center;">
<input
type="button"
class="buttonClass"
id="fly-btn"
@click="modifySatellitePanel(index, 'main')"
value="修改数据"
/>
<input
type="button"
class="buttonClass"
id="fly-btn"
@click="onShowSatelliteControlPanel(index, 'main')"
value="控制面板"
/>
<input
type="button"
class="buttonClass"
id="fly-btn"
@click="createSubSatellite(index)"
value="添加卫星"
/>
<input
type="button"
class="buttonClass"
id="fly-btn"
@click="clearSatellite(index, 'main')"
value="清除轨道"
/>
</td>
</tr>
</tbody>
</table>
</template>
<script setup>
// import TrackSvg from "../../TrackSvg.vue";
const props = defineProps({
satelliteArr: Array,
allShow: Boolean
});
const emit = defineEmits([
"allSatelliteShow",
"allClearSatellite",
"modifySatellitePanel",
"onShowSatelliteControlPanel",
"onShowSatellite",
// "onTrackSatellite",
"clearSatellite",
"showSubSatelliteTable",
"createSubSatellite"
]);
const allSatelliteShow = (model) => {
emit("allSatelliteShow", model);
};
const allClearSatellite = (model) => {
emit("allClearSatellite", model);
};
const modifySatellitePanel = (index, model) => {
emit("modifySatellitePanel", index, model);
};
const onShowSatelliteControlPanel = (index, model) => {
emit("onShowSatelliteControlPanel", index, model);
};
const onShowSatellite = (index, model) => {
emit("onShowSatellite", index, model);
};
// const onTrackSatellite = (index, model) => {
// emit("onTrackSatellite", index, model);
// };
const clearSatellite = (index, model) => {
emit("clearSatellite", index, model);
};
const showSubSatelliteTable = (index) => {
emit("showSubSatelliteTable", index);
};
const createSubSatellite = (index) => {
emit("createSubSatellite", index);
};
</script>
<style scoped>
.buttonClass {
padding-left: 0.5rem;
padding-right: 0.5rem;
font-size: 1rem;
cursor: pointer;
height: 2rem;
line-height: 2rem;
}
</style>

View File

@@ -0,0 +1,177 @@
<template>
<div
v-if="show"
class="text-left p-10"
style="
color: #ffffff;
background-color: rgb(55 55 55 / 0.9);
border-radius: 2rem;
box-shadow: 0 0px 1rem 0px rgb(165 243 252 / 1);
position: fixed;
top: 10vh;
left: 20vw;
width: 60vw;
height: 80vh;
font-size: 1rem;
"
>
<div
@click="close"
class="bg-gray-600 hover:bg-red-600 text-3xl absolute top-0 right-0 cursor-pointer flex justify-center items-center"
style="
width: 2rem;
height: 2rem;
border-top-right-radius: 1rem;
border-bottom-left-radius: 1rem;
"
>
<el-icon style="width: 2rem; height: 2rem">
<CloseBold color="#ffffff" />
</el-icon>
</div>
<div class="w-full text-center" style="font-size: 2rem; height: 4rem; line-height: 4rem;">
轨道控制面板
</div>
<div class="text-white text-center flex justify-center items-center" style="height: 80%; font-size: 1rem;">
<div class="option">
<label>卫星近地点颜色:</label>
<select v-model="nearPointSelected" style="cursor: pointer; color: black;">
<option v-for="(value, key) in cesiumColor" :key="key" :value="key">
{{ value }}
</option>
</select>
</div>
<div class="option">
<label>显示/隐藏卫星近地点:</label>
<input type="checkbox" v-model="nearPointShow" class="w-6 h-6 cursor-pointer" />
</div>
<div class="option">
<label>卫星远地点颜色:</label>
<select v-model="farPointSelected" style="cursor: pointer; color: black;">
<option v-for="(value, key) in cesiumColor" :key="key" :value="key">
{{ value }}
</option>
</select>
</div>
<div class="option">
<label>显示/隐藏卫星远地点:</label>
<input type="checkbox" v-model="farPointShow" class="w-6 h-6 cursor-pointer" />
</div>
<div class="option">
<label>卫星轨道颜色:</label>
<select v-model="orbitSelected" style="cursor: pointer; color: black;">
<option v-for="(value, key) in cesiumColor" :key="key" :value="key">
{{ value }}
</option>
</select>
</div>
<div class="option">
<label>显示/隐藏卫星轨道:</label>
<input type="checkbox" v-model="orbitShow" class="w-6 h-6 cursor-pointer" />
</div>
<div class="option">
<label>显示/隐藏轨道全部信息:</label>
<input type="checkbox" v-model="satelliteShow" class="w-6 h-6 cursor-pointer" />
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
import { CloseBold } from "@element-plus/icons-vue";
const emit = defineEmits([
"closeSatelliteControlPanel",
"handleSatelliteControl"
]);
const show = ref(false);
const nearPointSelected = ref("ORANGE");
const nearPointShow = ref(true);
const farPointSelected = ref("PINK");
const farPointShow = ref(true);
const orbitSelected = ref("BLUE");
const orbitShow = ref(true);
const satelliteShow = ref(true);
const cesiumColor = {
BLUE: "蓝色",
PURPLE: "紫色",
RED: "红色",
YELLOW: "黄色",
CYAN: "青色",
ORANGE: "橙色",
PINK: "粉色",
GREEN: "绿色",
WHITE: "白色",
LIGHTBLUE: "浅蓝色",
LIME: "青柠色",
MAGENTA: "品红色",
};
const data = {
nearPointSelected,
nearPointShow,
farPointSelected,
farPointShow,
orbitSelected,
orbitShow,
satelliteShow
};
const close = () => {
show.value = false;
emit("closeSatelliteControlPanel");
};
defineExpose({ show, data });
watch(nearPointSelected, (value) => {
emit("handleSatelliteControl", "nearPointSelected", value);
});
watch(nearPointShow, (value) => {
emit("handleSatelliteControl", "nearPointShow", value);
});
watch(farPointSelected, (value) => {
emit("handleSatelliteControl", "farPointSelected", value);
});
watch(farPointShow, (value) => {
emit("handleSatelliteControl", "farPointShow", value);
});
watch(orbitSelected, (value) => {
emit("handleSatelliteControl", "orbitSelected", value);
});
watch(orbitShow, (value) => {
emit("handleSatelliteControl", "orbitShow", value);
});
watch(satelliteShow, (value) => {
emit("handleSatelliteControl", "satelliteShow", value);
});
</script>
<style scoped>
.option {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.buttonClass {
padding-left: 0.5rem;
padding-right: 0.5rem;
font-size: 1rem;
cursor: pointer;
width: 12rem;
height: 2rem;
line-height: 2rem;
}
</style>

View File

@@ -0,0 +1,771 @@
<template>
<el-tooltip
class="box-item"
effect="dark"
content="
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1rem;'>
仅显示输入框,适合熟悉卫星轨道六根数原理的用户使用。
</div>
"
raw-content
placement="left"
>
<button @click="showit" class="satelliteCreate">专家模式</button>
</el-tooltip>
<div
v-if="show"
class="text-left p-10"
style="
color: #ffffff;
background-color: rgb(55 55 55 / 0.9);
border-radius: 2rem;
box-shadow: 0 0px 1rem 0px rgb(165 243 252 / 1);
position: fixed;
top: 10vh;
left: 20vw;
width: 60vw;
height: 80vh;
font-size: 1rem;
"
>
<div
@click="close"
class="bg-gray-600 hover:bg-red-600 text-3xl absolute top-0 right-0 cursor-pointer flex justify-center items-center"
style="
width: 2rem;
height: 2rem;
border-top-right-radius: 1rem;
border-bottom-left-radius: 1rem;
"
>
<el-icon style="width: 2rem; height: 2rem">
<CloseBold color="#ffffff" />
</el-icon>
</div>
<div class="w-full text-center" style="font-size: 2rem; height: 4rem; line-height: 4rem;">
添加轨道
</div>
<div class="text-white text-center flex justify-center items-center" style="height: 80%; font-size: 1rem;">
<div class="option">
<label :style="satelliteNameCheck ? '' : 'color: red;'">轨道名称:</label>
<input
type="text"
maxlength="12"
v-model="satelliteNameData"
placeholder="请输入轨道名称"
class="dataInput"
:style="satelliteNameCheck ? 'border: 2px solid black; color: black;' : 'border: 2px solid red; color: red;'"
/>
</div>
<div class="option">
<label :style="nearPointLengthCheck ? '' : 'color: red;'">近地点高度</label>
<label :style="nearPointLengthCheck ? 'color: #00efff;' : 'color: red;'">(介于200㎞~120000㎞之间,单位:㎞)</label>
<label :style="nearPointLengthCheck ? '' : 'color: red;'">:</label>
<input
type="number"
maxlength="6"
v-model="nearPointLengthData"
placeholder="请输入数据(单位:㎞)"
class="dataInput"
:style="nearPointLengthCheck ? 'border: 2px solid black; color: black;' : 'border: 2px solid red; color: red;'"
/>
<el-tooltip
class="box-item"
effect="dark"
content="
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
近地点高度就是卫星运行时,离地球最近的那个点,到地球表面的垂直距离。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
卫星的运行轨道不是正圆形,而是有点 “扁” 的椭圆。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
卫星绕着地球转时,会有一个离地球最近的位置,这个位置就是 “近地点”。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
近地点高度,就是从这个最近的点,垂直往下量到地心的距离。
</div>
"
raw-content
placement="top"
>
<el-icon style="margin-left: 0.5rem;"><QuestionFilled /></el-icon>
</el-tooltip>
</div>
<div class="option">
<label :style="farPointLengthCheck ? '' : 'color: red;'">远地点高度</label>
<label :style="farPointLengthCheck ? 'color: #00efff;' : 'color: red;'">(介于200㎞~120000㎞之间,单位:㎞)</label>
<label :style="farPointLengthCheck ? '' : 'color: red;'">:</label>
<input
type="number"
maxlength="6"
v-model="farPointLengthData"
placeholder="请输入数据(单位:㎞)"
class="dataInput"
:style="farPointLengthCheck ? 'border: 2px solid black; color: black;' : 'border: 2px solid red; color: red;'"
/>
<el-tooltip
class="box-item"
effect="dark"
content="
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
远地点高度就是卫星运行时,离地球最远的那个点,到地球表面的垂直距离。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
卫星的运行轨道不是正圆形,而是有点 “扁” 的椭圆。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
卫星绕着地球转时,会有一个离地球最远的位置,这个位置就是 “远地点”。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
远地点高度,就是从这个最远的点,垂直往下量到地心的距离。
</div>
"
raw-content
placement="top"
>
<el-icon style="margin-left: 0.5rem;"><QuestionFilled /></el-icon>
</el-tooltip>
</div>
<div class="option">
<label :style="semiMajorAxisCheck ? '' : 'color: red;'">轨道半长轴</label>
<label :style="semiMajorAxisCheck ? 'color: #00efff;' : 'color: red;'">(介于200㎞~120000㎞之间)</label>
<label :style="semiMajorAxisCheck ? '' : 'color: red;'">:</label>
<label :style="semiMajorAxisCheck ? 'color: rgb(250, 230, 164);' : 'color: red;'">{{ semiMajorAxisData === '' ? '' : Number(semiMajorAxisData).toFixed(3) }}</label>
<label :style="semiMajorAxisCheck ? 'color: #00efff;' : 'color: red;'">(单位:㎞)</label>
<el-tooltip
class="box-item"
effect="dark"
content="
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
卫星轨道为椭圆形,轨道半长轴就是 “长轴的一半长度”,也就是从椭圆(轨道)中心到椭圆最长端(远地点)的距离。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
它不是到地球表面的距离,而是从椭圆中心(不是地球球心)到轨道边缘的最长半径。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
简单说:椭圆轨道像个 “拉长的圆”,半长轴就相当于这个 “拉长圆” 的 “平均半径”—— 能反映轨道的整体大小。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
轨道半长轴决定了轨道的大小,决定卫星离地球的平均距离,决定卫星飞得高还是低。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
卫星飞得越高,速度越慢,周期越长,半径越大,轨道越大,卫星离地球越远。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
图中b为半短轴,半短轴 b 越接近半长轴 a;当偏心率=0(正圆轨道)时,b=a,此时半短轴就是圆的半径。
</div>
"
raw-content
placement="top"
>
<el-icon style="margin-left: 0.5rem;"><QuestionFilled /></el-icon>
</el-tooltip>
</div>
<div class="option">
<label :style="eccentricityCheck ? '' : 'color: red;'">轨道偏心率</label>
<label :style="eccentricityCheck ? 'color: #00efff;' : 'color: red;'">(不超过0.9)</label>
<label :style="eccentricityCheck ? '' : 'color: red;'">:</label>
<label :style="eccentricityCheck ? 'color: rgb(250, 230, 164);' : 'color: red;'">{{ eccentricityData === '' ? '' : Number(eccentricityData).toFixed(6) }}</label>
<el-tooltip
class="box-item"
effect="dark"
content="
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
轨道偏心率决定了轨道的形状,是用来描述天体轨道形状偏离圆形程度的一个数值。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
轨道偏心率是椭圆两焦点之间的距离(焦距)与长轴的比值,也可以换算为半焦距与半长轴的比值,描述椭圆扁平程度。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
用 0 到 1 之间的数字表示,偏心率越接近 0,轨道越圆;偏心率越接近 1,轨道越扁(但不会等于 1,等于 1 就不是椭圆了)。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
近地点和远地点距离差不多(轨道圆),偏心率就小。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
近地点离地球很近、远地点离地球很远(轨道扁),偏心率就大。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
用于描述轨道的形状,表征轨道椭圆的扁平程度。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
假设轨道半长轴为b,轨道焦点到轨道中心距离为a,轨道离心率为e,则:e = a ÷ b。
</div>
"
raw-content
placement="top"
>
<el-icon style="margin-left: 0.5rem;"><QuestionFilled /></el-icon>
</el-tooltip>
</div>
<div class="option">
<label>轨道周期</label>
<label style="color: #00efff;">(秒)</label>
<label>:</label>
<label style="color: rgb(250, 230, 164);">{{ orbitalPeriodData === '' ? '' : Number(orbitalPeriodData).toFixed(3) }}</label>
<label style="color: #00efff;">(单位:s)</label>
<el-tooltip
class="box-item"
effect="dark"
content="
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
轨道周期就是卫星绕中心天体(比如地球)完整转一圈的时间。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
简单说,轨道周期就是从卫星某个位置(比如近地点)出发,绕轨道飞一圈后,再回到这个位置的总时间。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
万有引力常数 G = 6.67430 × 10^⁻¹¹ 米³ / (千克 × 秒²),地球质量 M = 5.965 × 10²⁴ 千克。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
人造地球卫星运转周期T(秒)与圆轨道半径或椭圆轨道半长轴R(米)之间的关系可用下列公式计算:T = 2π × (R³ ÷ GM)½ 。
</div>
"
raw-content
placement="top"
>
<el-icon style="margin-left: 0.5rem;"><QuestionFilled /></el-icon>
</el-tooltip>
</div>
<div class="option">
<label>平均角速度</label>
<label style="color: #00efff;">(度/秒)</label>
<label>:</label>
<label style="color: rgb(250, 230, 164);">{{ angularVelocityDegData === '' ? '' : Number(angularVelocityDegData).toFixed(6) }}</label>
<label style="color: #00efff;">(单位:°/s)</label>
<el-tooltip
class="box-item"
effect="dark"
content="
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
平均角速度就是卫星绕中心天体转的时候,单位时间内转过的角度,简单说就是 “转得快不快” 的 “角度版” 描述。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
周期越短(转一圈时间少),角速度越大(转得快)。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
通俗理解:比如卫星 1 小时转了 45°,它的平均角速度就是 45°/ 小时;转得越快,单位时间内转过的角度越大,角速度就越大。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
卫星飞得越高,速度越慢,卫星飞得越低,速度越快,平均角速度是卫星在轨道上的平均运行速度。
</div>
"
raw-content
placement="top"
>
<el-icon style="margin-left: 0.5rem;"><QuestionFilled /></el-icon>
</el-tooltip>
</div>
<div class="option">
<label :style="inclinationCheck ? '' : 'color: red;'">轨道倾角</label>
<label :style="inclinationCheck ? 'color: #00efff;' : 'color: red;'">(0°~180°)</label>
<label :style="inclinationCheck ? '' : 'color: red;'">:</label>
<input
type="number"
maxlength="3"
v-model="inclinationData"
placeholder="请输入数据(0°~180°)"
class="dataInput"
:style="inclinationCheck ? 'border: 2px solid black; color: black;' : 'border: 2px solid red; color: red;'"
/>
<el-tooltip
class="box-item"
effect="dark"
content="
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
轨道倾角就是轨道相对于地球赤道的倾斜程度,是卫星绕地球转的轨道平面,和地球赤道平面之间的 “夹角”,用 0° 到 180° 的数字表示,核心是描述轨道 “歪不歪”,“往哪个方向歪”。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
地球赤道平面:想象过地球赤道画一个无限大的 “隐形圆盘”,这就是赤道平面。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
卫星轨道平面:卫星绕地球转的轨迹所在的 “隐形圆盘”,就是轨道平面。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
轨道倾角,就是这两个 “隐形圆盘” 之间夹着的角度,就像两本书打开时书页之间的夹角一样。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
也可以类比滑滑梯的角度,把地面比作赤道,卫星轨道是倾斜的滑梯,倾角就是地面和滑梯的夹角。
</div>
"
raw-content
placement="top"
>
<el-icon style="margin-left: 0.5rem;"><QuestionFilled /></el-icon>
</el-tooltip>
</div>
<div class="option">
<label :style="ascendantRightAscensionCheck ? '' : 'color: red;'">升交点赤经</label>
<label :style="ascendantRightAscensionCheck ? 'color: #00efff;' : 'color: red;'">(0°~360°)</label>
<label :style="ascendantRightAscensionCheck ? '' : 'color: red;'">:</label>
<input
type="number"
maxlength="3"
v-model="ascendantRightAscensionData"
placeholder="请输入数据(0°~360°)"
class="dataInput"
:style="ascendantRightAscensionCheck ? 'border: 2px solid black; color: black;' : 'border: 2px solid red; color: red;'"
/>
<el-tooltip
class="box-item"
effect="dark"
content="
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
轨道在空间中的方向,轨道平面在太空中的旋转角度,轨道在赤道上的起点,共同确定了轨道平面和赤道平面交点的位置,相当于给倾斜的轨道找一个基准起点。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
升交点赤经就是给卫星轨道在宇宙中 “定方向” 的参数,核心是描述轨道与地球赤道交点的 “东向位置”。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
升交点:卫星从地球南半球飞到北半球时,会穿过赤道平面,这个穿过的点就是升交点。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
春分点:太阳从地球的南半球向北半球运动穿过地球赤道平面的那个点,可以理解为太阳的升交点,每年春分(3月21日左右)的时候,太阳经过该点。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
赤经:与经度定义类似,但其参考点为春分点,且不随地球转自转运动。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
升交点赤经描述的是一个角度,这个角度就是“春分点到地心连线”与“升交点到地心连线”的夹角,表征了轨道平面相对春分点的方位。结合上一节的轨道倾角,就完全确定了轨道平面在三维空间中的方位。
</div>
"
raw-content
placement="top"
>
<el-icon style="margin-left: 0.5rem;"><QuestionFilled /></el-icon>
</el-tooltip>
</div>
<div class="option">
<label :style="argumentOfPeriapsisCheck ? '' : 'color: red;'">近地点角距</label>
<label :style="argumentOfPeriapsisCheck ? 'color: #00efff;' : 'color: red;'">(0°~360°)</label>
<label :style="argumentOfPeriapsisCheck ? '' : 'color: red;'">:</label>
<input
type="number"
maxlength="3"
v-model="argumentOfPeriapsisData"
placeholder="请输入数据(0°~360°)"
class="dataInput"
:style="argumentOfPeriapsisCheck ? 'border: 2px solid black; color: black;' : 'border: 2px solid red; color: red;'"
/>
<el-tooltip
class="box-item"
effect="dark"
content="
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
近地点角距(近地点幅角)就是从升交点开始,沿着卫星轨道往飞行方向量,到近地点的角度,核心是给近地点 “定具体位置”。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
近地点角距(近地点幅角),就是以地球球心为中心,在卫星轨道平面上,从升交点出发,顺着卫星飞行的方向,画一条线到近地点,这条线和 “升交点到地心连线” 之间的夹角(范围 0°-360°)。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
轨道在平面内的朝向,轨道椭圆在轨道平面内的旋转角度,就像钟表指针指向的方向,确定近地点在轨道上的位置,近地点相对于升交点的位置,近地点角距(近地点幅角)决定了卫星离地球最近时,指向哪个方向。
</div>
"
raw-content
placement="top"
>
<el-icon style="margin-left: 0.5rem;"><QuestionFilled /></el-icon>
</el-tooltip>
</div>
<div class="option">
<label>卫星近地点颜色:</label>
<select v-model="nearPointSelectedData" style="cursor: pointer; color: black; margin-right: 1rem;">
<option v-for="(value, key) in cesiumColor" :key="key" :value="key">
{{ value }}
</option>
</select>
<label>卫星远地点颜色:</label>
<select v-model="farPointSelectedData" style="cursor: pointer; color: black;">
<option v-for="(value, key) in cesiumColor" :key="key" :value="key">
{{ value }}
</option>
</select>
</div>
<div class="option">
<label>卫星轨道颜色:</label>
<select v-model="orbitSelectedData" style="cursor: pointer; color: black; margin-right: 1rem;">
<option v-for="(value, key) in cesiumColor" :key="key" :value="key">
{{ value }}
</option>
</select>
</div>
<div class="option">
<input
type="button"
class="buttonClass"
id="fly-btn"
@click="generateOrbit"
value="添加轨道"
/>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
import { CloseBold, QuestionFilled } from "@element-plus/icons-vue";
const props = defineProps({
satelliteArr: Array
});
const emit = defineEmits([
"selectSatelliteModel",
"generateOrbit"
]);
// 地球赤道半径 6378137 米
const equatorRadius = 6378.137;
const show = ref(false);
const satelliteNameData = ref();
const nearPointLengthData = ref();
const farPointLengthData = ref();
const semiMajorAxisData = ref();
const eccentricityData = ref();
const orbitalPeriodData = ref();
const angularVelocityRadData = ref();
const angularVelocityDegData = ref();
const inclinationData = ref();
const ascendantRightAscensionData = ref();
const argumentOfPeriapsisData = ref();
const satelliteNameCheck = ref(true);
const nearPointLengthCheck = ref(true);
const farPointLengthCheck = ref(true);
const semiMajorAxisCheck = ref(true);
const eccentricityCheck = ref(true);
const inclinationCheck = ref(true);
const ascendantRightAscensionCheck = ref(true);
const argumentOfPeriapsisCheck = ref(true);
const nearPointSelectedData = ref("ORANGE");
const farPointSelectedData = ref("PINK");
const orbitSelectedData = ref("BLUE");
const cesiumColor = {
BLUE: "蓝色",
PURPLE: "紫色",
RED: "红色",
YELLOW: "黄色",
CYAN: "青色",
ORANGE: "橙色",
PINK: "粉色",
GREEN: "绿色",
WHITE: "白色",
LIGHTBLUE: "浅蓝色",
LIME: "青柠色",
MAGENTA: "品红色",
};
const data = {
satelliteNameData,
nearPointLengthData,
farPointLengthData,
semiMajorAxisData,
eccentricityData,
orbitalPeriodData,
angularVelocityRadData,
angularVelocityDegData,
inclinationData,
ascendantRightAscensionData,
argumentOfPeriapsisData,
nearPointSelectedData,
farPointSelectedData,
orbitSelectedData,
};
const resetData = () => {
satelliteNameData.value = "";
nearPointLengthData.value = "";
farPointLengthData.value = "";
semiMajorAxisData.value = "";
eccentricityData.value = "";
orbitalPeriodData.value = "";
angularVelocityRadData.value = "";
angularVelocityDegData.value = "";
inclinationData.value = "";
ascendantRightAscensionData.value = "";
argumentOfPeriapsisData.value = "";
satelliteNameCheck.value = true;
nearPointLengthCheck.value = true;
farPointLengthCheck.value = true;
semiMajorAxisCheck.value = true;
eccentricityCheck.value = true;
inclinationCheck.value = true;
ascendantRightAscensionCheck.value = true;
argumentOfPeriapsisCheck.value = true;
nearPointSelectedData.value = "ORANGE";
farPointSelectedData.value = "PINK";
orbitSelectedData.value = "BLUE";
};
const showit = () => {
show.value = true;
};
const close = () => {
show.value = false;
setTimeout(() => {
resetData();
}, 0);
};
const generateOrbitData = () => {
if (nearPointLengthData.value != "" && nearPointLengthData.value != undefined && farPointLengthData.value != "" && farPointLengthData.value != undefined) {
semiMajorAxisData.value = (nearPointLengthData.value + equatorRadius + (farPointLengthData.value + equatorRadius)) / 2;
eccentricityData.value = (semiMajorAxisData.value - (nearPointLengthData.value + equatorRadius)) / semiMajorAxisData.value;
orbitalPeriodData.value = ((semiMajorAxisData.value * 1000) / 21613.546) ** 1.5;
angularVelocityRadData.value = (2 * Math.PI) / orbitalPeriodData.value;
angularVelocityDegData.value = angularVelocityRadData.value * (180 / Math.PI);
if (semiMajorAxisData.value === '' || semiMajorAxisData.value === undefined || semiMajorAxisData.value < 200 || semiMajorAxisData.value > 120000) {
semiMajorAxisCheck.value = false;
} else {
semiMajorAxisCheck.value = true;
}
if (eccentricityData.value === '' || eccentricityData.value === undefined || eccentricityData.value < 0 || eccentricityData.value > 0.9) {
eccentricityCheck.value = false;
} else {
eccentricityCheck.value = true;
}
}
};
watch(nearPointLengthData, (value) => {
generateOrbitData();
});
watch(farPointLengthData, (value) => {
generateOrbitData();
});
const booleanCheck = (checkArr) => {
let check = true;
checkArr.forEach(item => {
if (item == false) {
check = false;
}
});
return check;
};
const satelliteNameDataCheck = () => {
if (satelliteNameData.value === '' || satelliteNameData.value === undefined) {
satelliteNameCheck.value = false;
} else {
satelliteNameCheck.value = true;
}
props.satelliteArr.forEach((entity, index) => {
for (const element in entity) {
if (entity[element].name !== undefined) {
let name = entity[element].name;
if (satelliteNameData.value == name) {
satelliteNameCheck.value = false;
}
}
}
});
};
const nearPointLengthDataCheck = () => {
if (nearPointLengthData.value === "" || nearPointLengthData.value === undefined || nearPointLengthData.value < 200 || nearPointLengthData.value > 120000) {
nearPointLengthCheck.value = false;
} else {
nearPointLengthCheck.value = true;
}
if (nearPointLengthCheck.value == 1 && farPointLengthCheck.value == 1 && farPointLengthData.value !== "" && farPointLengthData.value !== undefined && nearPointLengthData.value > farPointLengthData.value) {
nearPointLengthCheck.value = false;
farPointLengthCheck.value = false;
}
};
const farPointLengthDataCheck = () => {
if (farPointLengthData.value === "" || farPointLengthData.value === undefined || farPointLengthData.value < 200 || farPointLengthData.value > 120000) {
farPointLengthCheck.value = false;
} else {
farPointLengthCheck.value = true;
}
if (nearPointLengthCheck.value == 1 && farPointLengthCheck.value == 1 && nearPointLengthData.value !== "" && nearPointLengthData.value !== undefined && nearPointLengthData.value > farPointLengthData.value) {
nearPointLengthCheck.value = false;
farPointLengthCheck.value = false;
}
};
const semiMajorAxisDataCheck = () => {
if (semiMajorAxisData.value === '' || semiMajorAxisData.value === undefined || semiMajorAxisData.value < 200 || semiMajorAxisData.value > 120000) {
semiMajorAxisCheck.value = false;
} else {
semiMajorAxisCheck.value = true;
}
};
const eccentricityDataCheck = () => {
if (eccentricityData.value === '' || eccentricityData.value === undefined || eccentricityData.value < 0 || eccentricityData.value > 0.9) {
eccentricityCheck.value = false;
} else {
eccentricityCheck.value = true;
}
};
const inclinationDataCheck = () => {
if (inclinationData.value === '' || inclinationData.value === undefined || inclinationData.value < 0 || inclinationData.value > 180) {
inclinationCheck.value = false;
} else {
inclinationCheck.value = true;
}
};
const ascendantRightAscensionDataCheck = () => {
if (ascendantRightAscensionData.value === '' || ascendantRightAscensionData.value === undefined || ascendantRightAscensionData.value < 0 || ascendantRightAscensionData.value > 360) {
ascendantRightAscensionCheck.value = false;
} else {
ascendantRightAscensionCheck.value = true;
}
};
const argumentOfPeriapsisDataCheck = () => {
if (argumentOfPeriapsisData.value === '' || argumentOfPeriapsisData.value === undefined || argumentOfPeriapsisData.value < 0 || argumentOfPeriapsisData.value > 360) {
argumentOfPeriapsisCheck.value = false;
} else {
argumentOfPeriapsisCheck.value = true;
}
};
watch(satelliteNameData, (value) => {
satelliteNameDataCheck();
});
watch(nearPointLengthData, (value) => {
nearPointLengthDataCheck();
});
watch(farPointLengthData, (value) => {
farPointLengthDataCheck();
});
watch(semiMajorAxisData, (value) => {
semiMajorAxisDataCheck();
});
watch(eccentricityData, (value) => {
eccentricityDataCheck();
});
watch(inclinationData, (value) => {
inclinationDataCheck();
});
watch(ascendantRightAscensionData, (value) => {
ascendantRightAscensionDataCheck();
});
watch(argumentOfPeriapsisData, (value) => {
argumentOfPeriapsisDataCheck();
});
const generateCheck = () => {
let checkArr = [];
satelliteNameDataCheck();
checkArr.push(satelliteNameCheck.value);
nearPointLengthDataCheck();
farPointLengthDataCheck();
checkArr.push(nearPointLengthCheck.value);
checkArr.push(farPointLengthCheck.value);
if (nearPointLengthData.value != "" && nearPointLengthData.value != undefined && farPointLengthData.value != "" && farPointLengthData.value != undefined) {
semiMajorAxisData.value = ((nearPointLengthData.value + equatorRadius) + (farPointLengthData.value + equatorRadius)) / 2;
eccentricityData.value = (semiMajorAxisData.value - (nearPointLengthData.value + equatorRadius)) / semiMajorAxisData.value;
orbitalPeriodData.value = ((semiMajorAxisData.value * 1000) / 21613.546) ** 1.5;
angularVelocityRadData.value = (2 * Math.PI) / orbitalPeriodData.value;
angularVelocityDegData.value = angularVelocityRadData.value * (180 / Math.PI);
}
semiMajorAxisDataCheck();
checkArr.push(semiMajorAxisCheck.value);
eccentricityDataCheck();
checkArr.push(eccentricityCheck.value);
inclinationDataCheck();
checkArr.push(inclinationCheck.value);
ascendantRightAscensionDataCheck();
checkArr.push(ascendantRightAscensionCheck.value);
argumentOfPeriapsisDataCheck();
checkArr.push(argumentOfPeriapsisCheck.value);
return booleanCheck(checkArr);
};
const generateOrbit = () => {
if (generateCheck()) {
emit("generateOrbit", "technology");
show.value = false;
setTimeout(() => {
resetData();
}, 0);
}
};
defineExpose({ data });
</script>
<style scoped>
.satelliteCreate {
font-size: 1rem;
color: #ffffff;
background-color: rgb(255 255 255 / 0.5);
width: 6rem;
height: 2rem;
line-height: 2rem;
overflow: hidden;
box-shadow: 0 0px 1rem 0px rgb(165 243 252 / 0.5);
position: fixed;
bottom: 14.5rem;
right: 1rem;
border-radius: 1rem;
}
.satelliteCreate:hover {
color: #000000;
box-shadow: 0 0px 1rem 0px rgb(165 243 252 / 1);
background-color: rgb(250, 230, 164);
}
.option {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.dataInput {
padding-left: 0.5rem;
padding-right: 0.5rem;
height: 2rem;
background: #ffffff;
border-radius: 1rem;
}
.buttonClass {
padding-left: 0.5rem;
padding-right: 0.5rem;
font-size: 1rem;
width: 8rem;
height: 2rem;
line-height: 2rem;
color: #000000;
background-color: rgb(165 243 252 / 1);
overflow: hidden;
cursor: pointer;
box-shadow: 0 0px 1rem 0px rgb(165 243 252 / 1);
border-radius: 2rem;
}
</style>

View File

@@ -0,0 +1,627 @@
<template>
<div
v-if="show"
class="text-left p-10"
style="
color: #ffffff;
background-color: rgb(55 55 55 / 0.9);
border-radius: 2rem;
box-shadow: 0 0px 1rem 0px rgb(165 243 252 / 1);
position: fixed;
top: 10vh;
left: 20vw;
width: 60vw;
height: 80vh;
font-size: 1rem;
"
>
<div
@click="close"
class="bg-gray-600 hover:bg-red-600 text-3xl absolute top-0 right-0 cursor-pointer flex justify-center items-center"
style="
width: 2rem;
height: 2rem;
border-top-right-radius: 1rem;
border-bottom-left-radius: 1rem;
"
>
<el-icon style="width: 2rem; height: 2rem">
<CloseBold color="#ffffff" />
</el-icon>
</div>
<div class="w-full text-center" style="font-size: 2rem; height: 4rem; line-height: 4rem;">修改轨道数据</div>
<div class="text-white text-center flex justify-center items-center" style="height: 80%; font-size: 1rem;">
<div class="option">
<label :style="satelliteNameCheck ? '' : 'color: red;'">轨道名称:</label>
<input
type="text"
v-model="satelliteNameModify"
placeholder="请输入卫星名称"
class="dataInput"
:style="satelliteNameCheck ? 'border: 2px solid black; color: black;' : 'border: 2px solid red; color: red;'"
/>
</div>
<div class="option">
<label :style="nearPointLengthCheck ? '' : 'color: red;'">卫星近地点高度</label>
<label :style="nearPointLengthCheck ? 'color: #00efff;' : 'color: red;'">(单位:㎞)</label>
<label :style="nearPointLengthCheck ? '' : 'color: red;'">:</label>
<input
type="number"
maxlength="6"
v-model="nearPointLengthModify"
placeholder="请输入数据(单位:㎞)"
class="dataInput"
:style="nearPointLengthCheck ? 'border: 2px solid black; color: black;' : 'border: 2px solid red; color: red;'"
/>
<el-tooltip
class="box-item"
effect="dark"
content="
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
近地点高度就是卫星运行时,离地球最近的那个点,到地球表面的垂直距离。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
卫星的运行轨道不是正圆形,而是有点 “扁” 的椭圆。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
卫星绕着地球转时,会有一个离地球最近的位置,这个位置就是 “近地点”。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
近地点高度,就是从这个最近的点,垂直往下量到地心的距离。
</div>
"
raw-content
placement="top"
>
<el-icon style="margin-left: 0.5rem;"><QuestionFilled /></el-icon>
</el-tooltip>
</div>
<div class="option">
<label :style="farPointLengthCheck ? '' : 'color: red;'">卫星远地点高度</label>
<label :style="farPointLengthCheck ? 'color: #00efff;' : 'color: red;'">(单位:㎞)</label>
<label :style="farPointLengthCheck ? '' : 'color: red;'">:</label>
<input
type="number"
maxlength="6"
v-model="farPointLengthModify"
placeholder="请输入数据(单位:㎞)"
class="dataInput"
:style="farPointLengthCheck ? 'border: 2px solid black; color: black;' : 'border: 2px solid red; color: red;'"
/>
<el-tooltip
class="box-item"
effect="dark"
content="
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
远地点高度就是卫星运行时,离地球最远的那个点,到地球表面的垂直距离。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
卫星的运行轨道不是正圆形,而是有点 “扁” 的椭圆。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
卫星绕着地球转时,会有一个离地球最远的位置,这个位置就是 “远地点”。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
远地点高度,就是从这个最远的点,垂直往下量到地心的距离。
</div>
"
raw-content
placement="top"
>
<el-icon style="margin-left: 0.5rem;"><QuestionFilled /></el-icon>
</el-tooltip>
</div>
<div class="option">
<label :style="semiMajorAxisCheck ? '' : 'color: red;'">轨道半长轴</label>
<label :style="semiMajorAxisCheck ? 'color: #00efff;' : 'color: red;'">(介于200㎞~120000㎞之间)</label>
<label :style="semiMajorAxisCheck ? '' : 'color: red;'">:</label>
<label :style="semiMajorAxisCheck ? 'color: rgb(250, 230, 164);' : 'color: red;'">{{ semiMajorAxisModify === '' ? '' :Number(semiMajorAxisModify).toFixed(3) }}</label>
<label :style="semiMajorAxisCheck ? 'color: #00efff;' : 'color: red;'">(单位:㎞)</label>
<el-tooltip
class="box-item"
effect="dark"
content="
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
卫星轨道为椭圆形,轨道半长轴就是 “长轴的一半长度”,也就是从椭圆(轨道)中心到椭圆最长端(远地点)的距离。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
它不是到地球表面的距离,而是从椭圆中心(不是地球球心)到轨道边缘的最长半径。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
简单说:椭圆轨道像个 “拉长的圆”,半长轴就相当于这个 “拉长圆” 的 “平均半径”—— 能反映轨道的整体大小。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
轨道半长轴决定了轨道的大小,决定卫星离地球的平均距离,决定卫星飞得高还是低。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
卫星飞得越高,速度越慢,周期越长,半径越大,轨道越大,卫星离地球越远。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
b为半短轴,半短轴 b 越接近半长轴 a;当偏心率=0(正圆轨道)时,b=a,此时半短轴就是圆的半径。
</div>
"
raw-content
placement="top"
>
<el-icon style="margin-left: 0.5rem;"><QuestionFilled /></el-icon>
</el-tooltip>
</div>
<div class="option">
<label :style="eccentricityCheck ? '' : 'color: red;'">轨道偏心率</label>
<label style="color: #00efff;">(不超过0.9)</label>
<label :style="eccentricityCheck ? '' : 'color: red;'">:</label>
<label :style="eccentricityCheck ? 'color: rgb(250, 230, 164);' : 'color: red;'">{{ eccentricityModify === '' ? '' : Number(eccentricityModify).toFixed(6) }}</label>
<el-tooltip
class="box-item"
effect="dark"
content="
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
轨道偏心率决定了轨道的形状,是用来描述天体轨道形状偏离圆形程度的一个数值。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
轨道偏心率是椭圆两焦点之间的距离(焦距)与长轴的比值,也可以换算为半焦距与半长轴的比值,描述椭圆扁平程度。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
用 0 到 1 之间的数字表示,偏心率越接近 0,轨道越圆;偏心率越接近 1,轨道越扁(但不会等于 1,等于 1 就不是椭圆了)。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
近地点和远地点距离差不多(轨道圆),偏心率就小。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
近地点离地球很近、远地点离地球很远(轨道扁),偏心率就大。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
用于描述轨道的形状,表征轨道椭圆的扁平程度。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
假设轨道半长轴为b,轨道焦点到轨道中心距离为a,轨道离心率为e,则:e = a ÷ b。
</div>
"
raw-content
placement="top"
>
<el-icon style="margin-left: 0.5rem;"><QuestionFilled /></el-icon>
</el-tooltip>
</div>
<div class="option">
<label>轨道周期</label>
<label style="color: #00efff;">(秒)</label>
<label>:</label>
<label style="color: rgb(250, 230, 164);">{{ orbitalPeriodModify === '' ? '' : Number(orbitalPeriodModify).toFixed(3) }}</label>
<label style="color: #00efff;">(单位:s)</label>
<el-tooltip
class="box-item"
effect="dark"
content="
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
轨道周期就是卫星绕中心天体(比如地球)完整转一圈的时间。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
简单说,轨道周期就是从卫星某个位置(比如近地点)出发,绕轨道飞一圈后,再回到这个位置的总时间。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
万有引力常数 G = 6.67430 × 10^⁻¹¹ 米³ / (千克 × 秒²),地球质量 M = 5.965 × 10²⁴ 千克。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
人造地球卫星运转周期T(秒)与圆轨道半径或椭圆轨道半长轴R(米)之间的关系可用下列公式计算:T = 2π × (R³ ÷ GM)½ 。
</div>
"
raw-content
placement="top"
>
<el-icon style="margin-left: 0.5rem;"><QuestionFilled /></el-icon>
</el-tooltip>
</div>
<div class="option">
<label>平均角速度</label>
<label style="color: #00efff;">(度/秒)</label>
<label>:</label>
<label style="color: rgb(250, 230, 164);">{{ angularVelocityDegModify === '' ? '' : Number(angularVelocityDegModify).toFixed(6) }}</label>
<label style="color: #00efff;">(单位:°/s)</label>
<el-tooltip
class="box-item"
effect="dark"
content="
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
平均角速度就是卫星绕中心天体转的时候,单位时间内转过的角度,简单说就是 “转得快不快” 的 “角度版” 描述。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
周期越短(转一圈时间少),角速度越大(转得快)。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
通俗理解:比如卫星 1 小时转了 45°,它的平均角速度就是 45°/ 小时;转得越快,单位时间内转过的角度越大,角速度就越大。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
卫星飞得越高,速度越慢,卫星飞得越低,速度越快,平均角速度是卫星在轨道上的平均运行速度。
</div>
"
raw-content
placement="top"
>
<el-icon style="margin-left: 0.5rem;"><QuestionFilled /></el-icon>
</el-tooltip>
</div>
<div class="option">
<label :style="inclinationCheck ? '' : 'color: red;'">轨道倾角</label>
<label :style="inclinationCheck ? 'color: #00efff;' : 'color: red;'">(0°~180°)</label>
<label :style="inclinationCheck ? '' : 'color: red;'">:</label>
<input
type="number"
maxlength="3"
v-model="inclinationModify"
placeholder="请输入数据(0°~180°)"
class="dataInput"
:style="inclinationCheck ? 'border: 2px solid black; color: black;' : 'border: 2px solid red; color: red;'"
/>
<el-tooltip
class="box-item"
effect="dark"
content="
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
轨道倾角就是轨道相对于地球赤道的倾斜程度,是卫星绕地球转的轨道平面,和地球赤道平面之间的 “夹角”,用 0° 到 180° 的数字表示,核心是描述轨道 “歪不歪”,“往哪个方向歪”。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
地球赤道平面:想象过地球赤道画一个无限大的 “隐形圆盘”,这就是赤道平面。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
卫星轨道平面:卫星绕地球转的轨迹所在的 “隐形圆盘”,就是轨道平面。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
轨道倾角,就是这两个 “隐形圆盘” 之间夹着的角度,就像两本书打开时书页之间的夹角一样。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
也可以类比滑滑梯的角度,把地面比作赤道,卫星轨道是倾斜的滑梯,倾角就是地面和滑梯的夹角。
</div>
"
raw-content
placement="top"
>
<el-icon style="margin-left: 0.5rem;"><QuestionFilled /></el-icon>
</el-tooltip>
</div>
<div class="option">
<label :style="ascendantRightAscensionCheck ? '' : 'color: red;'">升交点赤经</label>
<label :style="ascendantRightAscensionCheck ? 'color: #00efff;' : 'color: red;'">(0°~360°)</label>
<label :style="ascendantRightAscensionCheck ? '' : 'color: red;'">:</label>
<input
type="number"
maxlength="3"
v-model="ascendantRightAscensionModify"
placeholder="请输入数据(0°~360°)"
class="dataInput"
:style="ascendantRightAscensionCheck ? 'border: 2px solid black; color: black;' : 'border: 2px solid red; color: red;'"
/>
<el-tooltip
class="box-item"
effect="dark"
content="
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
轨道在空间中的方向,轨道平面在太空中的旋转角度,轨道在赤道上的起点,共同确定了轨道平面和赤道平面交点的位置,相当于给倾斜的轨道找一个基准起点。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
升交点赤经就是给卫星轨道在宇宙中 “定方向” 的参数,核心是描述轨道与地球赤道交点的 “东向位置”。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
升交点:卫星从地球南半球飞到北半球时,会穿过赤道平面,这个穿过的点就是升交点。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
春分点:太阳从地球的南半球向北半球运动穿过地球赤道平面的那个点,可以理解为太阳的升交点,每年春分(3月21日左右)的时候,太阳经过该点。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
赤经:与经度定义类似,但其参考点为春分点,且不随地球转自转运动。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
升交点赤经描述的是一个角度,这个角度就是“春分点到地心连线”与“升交点到地心连线”的夹角,表征了轨道平面相对春分点的方位。结合上一节的轨道倾角,就完全确定了轨道平面在三维空间中的方位。
</div>
"
raw-content
placement="top"
>
<el-icon style="margin-left: 0.5rem;"><QuestionFilled /></el-icon>
</el-tooltip>
</div>
<div class="option">
<label :style="argumentOfPeriapsisCheck ? '' : 'color: red;'">近地点角距</label>
<label :style="argumentOfPeriapsisCheck ? 'color: #00efff;' : 'color: red;'">(0°~360°)</label>
<label :style="argumentOfPeriapsisCheck ? '' : 'color: red;'">:</label>
<input
type="number"
maxlength="3"
v-model="argumentOfPeriapsisModify"
placeholder="请输入数据(0°~360°)"
class="dataInput"
:style="argumentOfPeriapsisCheck ? 'border: 2px solid black; color: black;' : 'border: 2px solid red; color: red;'"
/>
<el-tooltip
class="box-item"
effect="dark"
content="
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
近地点角距(近地点幅角)就是从升交点开始,沿着卫星轨道往飞行方向量,到近地点的角度,核心是给近地点 “定具体位置”。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
近地点角距(近地点幅角),就是以地球球心为中心,在卫星轨道平面上,从升交点出发,顺着卫星飞行的方向,画一条线到近地点,这条线和 “升交点到地心连线” 之间的夹角(范围 0°-360°)。
</div>
<div style='font-size: 1rem; color: rgb(165 243 252 / 1); line-height: 1.5rem;'>
轨道在平面内的朝向,轨道椭圆在轨道平面内的旋转角度,就像钟表指针指向的方向,确定近地点在轨道上的位置,近地点相对于升交点的位置,近地点角距(近地点幅角)决定了卫星离地球最近时,指向哪个方向。
</div>
"
raw-content
placement="top"
>
<el-icon style="margin-left: 0.5rem;"><QuestionFilled /></el-icon>
</el-tooltip>
</div>
<div class="option">
<input
type="button"
class="buttonClass"
id="fly-btn"
@click="modifySatellite"
value="修改数据"
/>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
import { CloseBold, QuestionFilled } from "@element-plus/icons-vue";
const props = defineProps({
satelliteArr: Array,
satelliteId: Number,
});
const emit = defineEmits([
"closeSatelliteModifyPanel",
"selectSatelliteModel",
"modifySatellite"
]);
// 地球赤道半径 6378137 米
const equatorRadius = 6378.137;
const show = ref(false);
const satelliteNameModify = ref();
const nearPointLengthModify = ref();
const farPointLengthModify = ref();
const semiMajorAxisModify = ref();
const eccentricityModify = ref();
const orbitalPeriodModify = ref();
const angularVelocityRadModify = ref();
const angularVelocityDegModify = ref();
const inclinationModify = ref();
const ascendantRightAscensionModify = ref();
const argumentOfPeriapsisModify = ref();
const satelliteNameCheck = ref(true);
const nearPointLengthCheck = ref(true);
const farPointLengthCheck = ref(true);
const semiMajorAxisCheck = ref(true);
const eccentricityCheck = ref(true);
const inclinationCheck = ref(true);
const ascendantRightAscensionCheck = ref(true);
const argumentOfPeriapsisCheck = ref(true);
const data = {
satelliteNameModify,
nearPointLengthModify,
farPointLengthModify,
semiMajorAxisModify,
eccentricityModify,
orbitalPeriodModify,
angularVelocityRadModify,
angularVelocityDegModify,
inclinationModify,
ascendantRightAscensionModify,
argumentOfPeriapsisModify,
};
const close = () => {
show.value = false;
emit("closeSatelliteModifyPanel");
};
const booleanCheck = (checkArr) => {
let check = true;
checkArr.forEach((item) => {
if (item == false) {
check = false;
}
});
return check;
};
const satelliteNameModifyCheck = () => {
if (satelliteNameModify.value === "" || satelliteNameModify.value === undefined) {
satelliteNameCheck.value = false;
} else {
satelliteNameCheck.value = true;
}
props.satelliteArr.forEach((entity, index) => {
for (const element in entity) {
if (entity[element].name !== undefined) {
let name = entity[element].name;
if (satelliteNameModify.value == name && index !== props.satelliteId - 1) {
satelliteNameCheck.value = false;
}
}
}
});
};
const nearPointLengthModifyCheck = () => {
if (nearPointLengthModify.value === "" || nearPointLengthModify.value === undefined || nearPointLengthModify.value < 200 || nearPointLengthModify.value > 120000) {
nearPointLengthCheck.value = false;
} else {
nearPointLengthCheck.value = true;
}
if (nearPointLengthCheck.value == 1 && farPointLengthCheck.value == 1 && farPointLengthModify.value !== "" && farPointLengthModify.value !== undefined && nearPointLengthModify.value > farPointLengthModify.value) {
nearPointLengthCheck.value = false;
farPointLengthCheck.value = false;
}
};
const farPointLengthModifyCheck = () => {
if (farPointLengthModify.value === "" || farPointLengthModify.value === undefined || farPointLengthModify.value < 200 || farPointLengthModify.value > 120000) {
farPointLengthCheck.value = false;
} else {
farPointLengthCheck.value = true;
}
if (nearPointLengthCheck.value == 1 && farPointLengthCheck.value == 1 && nearPointLengthModify.value !== "" && nearPointLengthModify.value !== undefined && nearPointLengthModify.value > farPointLengthModify.value) {
nearPointLengthCheck.value = false;
farPointLengthCheck.value = false;
}
};
const semiMajorAxisModifyCheck = () => {
if (semiMajorAxisModify.value === '' || semiMajorAxisModify.value === undefined || semiMajorAxisModify.value < 200 || semiMajorAxisModify.value > 120000) {
semiMajorAxisCheck.value = false;
} else {
semiMajorAxisCheck.value = true;
}
};
const eccentricityModifyCheck = () => {
if (eccentricityModify.value === "" || eccentricityModify.value === undefined || eccentricityModify.value < 0 || eccentricityModify.value > 0.9) {
eccentricityCheck.value = false;
} else {
eccentricityCheck.value = true;
}
};
const inclinationModifyCheck = () => {
if (inclinationModify.value === "" || inclinationModify.value === undefined || inclinationModify.value < 0 || inclinationModify.value > 180) {
inclinationCheck.value = false;
} else {
inclinationCheck.value = true;
}
};
const ascendantRightAscensionModifyCheck = () => {
if (ascendantRightAscensionModify.value === "" || ascendantRightAscensionModify.value === undefined || ascendantRightAscensionModify.value < 0 || ascendantRightAscensionModify.value > 360) {
ascendantRightAscensionCheck.value = false;
} else {
ascendantRightAscensionCheck.value = true;
}
};
const argumentOfPeriapsisModifyCheck = () => {
if (argumentOfPeriapsisModify.value === "" || argumentOfPeriapsisModify.value === undefined || argumentOfPeriapsisModify.value < 0 || argumentOfPeriapsisModify.value > 360) {
argumentOfPeriapsisCheck.value = false;
} else {
argumentOfPeriapsisCheck.value = true;
}
};
watch(satelliteNameModify, (value) => {
satelliteNameModifyCheck();
});
watch(nearPointLengthModify, (value) => {
nearPointLengthModifyCheck();
});
watch(farPointLengthModify, (value) => {
farPointLengthModifyCheck();
});
watch(semiMajorAxisModify, (value) => {
semiMajorAxisModifyCheck();
});
watch(eccentricityModify, (value) => {
eccentricityModifyCheck();
});
watch(inclinationModify, (value) => {
inclinationModifyCheck();
});
watch(ascendantRightAscensionModify, (value) => {
ascendantRightAscensionModifyCheck();
});
watch(argumentOfPeriapsisModify, (value) => {
argumentOfPeriapsisModifyCheck();
});
const generateCheck = () => {
let checkArr = [];
satelliteNameModifyCheck();
checkArr.push(satelliteNameCheck.value);
nearPointLengthModifyCheck();
farPointLengthModifyCheck();
checkArr.push(nearPointLengthCheck.value);
checkArr.push(farPointLengthCheck.value);
if (nearPointLengthModify.value != "" && nearPointLengthModify.value != undefined && farPointLengthModify.value != "" && farPointLengthModify.value != undefined) {
semiMajorAxisModify.value = (nearPointLengthModify.value + equatorRadius + (farPointLengthModify.value + equatorRadius)) / 2;
eccentricityModify.value = (semiMajorAxisModify.value - (nearPointLengthModify.value + equatorRadius)) / semiMajorAxisModify.value;
orbitalPeriodModify.value = ((semiMajorAxisModify.value * 1000) / 21613.546) ** 1.5;
angularVelocityRadModify.value = (2 * Math.PI) / orbitalPeriodModify.value;
angularVelocityDegModify.value = angularVelocityRadModify.value * (180 / Math.PI);
}
semiMajorAxisModifyCheck();
checkArr.push(semiMajorAxisCheck.value);
eccentricityModifyCheck();
checkArr.push(eccentricityCheck.value);
inclinationModifyCheck();
checkArr.push(inclinationCheck.value);
ascendantRightAscensionModifyCheck();
checkArr.push(ascendantRightAscensionCheck.value);
argumentOfPeriapsisModifyCheck();
checkArr.push(argumentOfPeriapsisCheck.value);
return booleanCheck(checkArr);
};
const modifySatellite = () => {
if (generateCheck()) {
emit("modifySatellite");
}
};
defineExpose({ show, data });
</script>
<style scoped>
.option {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.dataInput {
padding-left: 0.5rem;
padding-right: 0.5rem;
height: 2rem;
background: #ffffff;
border-radius: 1rem;
}
.buttonClass {
padding-left: 0.5rem;
padding-right: 0.5rem;
font-size: 1rem;
width: 12rem;
height: 2rem;
line-height: 2rem;
color: #000000;
background-color: rgb(165 243 252 / 1);
overflow: hidden;
cursor: pointer;
box-shadow: 0 0px 1rem 0px rgb(165 243 252 / 1);
border-radius: 2rem;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,141 @@
<template>
<table class="text-center">
<thead class="text-2xl">
<th
class="py-3 bg-blue-900 border border-gray-900"
v-for="(label, index) in ['卫星序号', '卫星名称', '显示/隐藏', '跟踪卫星', '操作']"
:key="label"
:style="{ width: index == 4 ? '40vw' : '20vw' }"
>
<div style="display: flex; justify-content: center; align-items: center;">
<div style="margin-right: 0.5rem;">
{{ label }}
</div>
<input
v-if="label == '显示/隐藏'"
:disabled="satelliteArr.length > 0 ? false : true"
indeterminate="false"
:checked="allSubShow"
type="checkbox"
class="w-6 h-6 cursor-pointer"
@change="allSatelliteShow('sub')"
/>
<input
v-if="label == '操作'"
:disabled="satelliteArr.length > 0 ? false : true"
type="button"
class="buttonClass"
id="fly-btn"
@click="allClearSatellite('sub')"
value="清除全部卫星"
/>
</div>
</th>
</thead>
<tbody class="text-lg odd:bg-gray-50">
<tr v-for="(entity, index) in satelliteArr[satelliteId - 1].subSatelliteArr" :key="index">
<td class="border border-gray-900"> {{ index + 1 }} </td>
<td class="border border-gray-900">{{ entity.satelliteModel.name }}</td>
<td class="border border-gray-900">
<div style="display: flex; justify-content: center; align-items: center;">
<input
:checked="entity.show"
type="checkbox"
class="w-6 h-6 cursor-pointer"
@change="onShowSatellite(index, 'sub')"
/>
</div>
</td>
<td class="border border-gray-900">
<div style="display: flex; justify-content: center; align-items: center;">
<button @click="onTrackSatellite(index, 'sub')">
<TrackSvg />
</button>
</div>
</td>
<td class="border border-gray-900" style="display: flex; justify-content: space-around; align-items: center;">
<input
type="button"
class="buttonClass"
id="fly-btn"
@click="modifySatellitePanel(index, 'sub')"
value="修改数据"
/>
<input
type="button"
class="buttonClass"
id="fly-btn"
@click="onShowSatelliteControlPanel(index, 'sub')"
value="控制面板"
/>
<input
type="button"
class="buttonClass"
id="fly-btn"
@click="clearSatellite(index, 'sub')"
value="清除卫星"
/>
</td>
</tr>
</tbody>
</table>
</template>
<script setup>
import TrackSvg from "../../TrackSvg.vue";
const props = defineProps({
satelliteArr: Array,
satelliteId: String,
allSubShow: Boolean
});
const emit = defineEmits([
"allSatelliteShow",
"allClearSatellite",
"modifySatellitePanel",
"onShowSatelliteControlPanel",
"onShowSatellite",
"onTrackSatellite",
"clearSatellite"
]);
const allSatelliteShow = (model) => {
emit("allSatelliteShow", model);
};
const allClearSatellite = (model) => {
emit("allClearSatellite", model);
};
const modifySatellitePanel = (index, model) => {
emit("modifySatellitePanel", index, model);
};
const onShowSatelliteControlPanel = (index, model) => {
emit("onShowSatelliteControlPanel", index, model);
};
const onShowSatellite = (index, model) => {
emit("onShowSatellite", index, model);
};
const onTrackSatellite = (index, model) => {
emit("onTrackSatellite", index, model);
};
const clearSatellite = (index, model) => {
emit("clearSatellite", index, model);
};
</script>
<style scoped>
.buttonClass {
padding-left: 0.5rem;
padding-right: 0.5rem;
font-size: 1rem;
cursor: pointer;
height: 2rem;
line-height: 2rem;
}
</style>

View File

@@ -0,0 +1,139 @@
<template>
<div
v-if="show"
class="text-left p-10"
style="
color: #ffffff;
background-color: rgb(55 55 55 / 0.9);
border-radius: 2rem;
box-shadow: 0 0px 1rem 0px rgb(165 243 252 / 1);
position: fixed;
top: 10vh;
left: 20vw;
width: 60vw;
height: 80vh;
font-size: 1rem;
"
>
<div
@click="close"
class="bg-gray-600 hover:bg-red-600 text-3xl absolute top-0 right-0 cursor-pointer flex justify-center items-center"
style="
width: 2rem;
height: 2rem;
border-top-right-radius: 1rem;
border-bottom-left-radius: 1rem;
"
>
<el-icon style="width: 2rem; height: 2rem;">
<CloseBold color="#ffffff" />
</el-icon>
</div>
<div class="w-full text-center" style="font-size: 2rem; height: 4rem; line-height: 4rem;">
卫星控制面板
</div>
<div class="text-white text-center flex justify-center items-center" style="height: 80%; font-size: 1rem;">
<div class="option">
<label>显示/隐藏卫星模型:</label>
<input type="checkbox" v-model="subSatelliteModelShow" class="w-6 h-6 cursor-pointer" />
</div>
<div class="option">
<label>卫星信号覆盖范围颜色:</label>
<select v-model="subCylinderSelected" style="cursor: pointer; color: black;">
<option v-for="(value, key) in cesiumColor" :key="key" :value="key">
{{ value }}
</option>
</select>
</div>
<div class="option">
<label>显示/隐藏卫星信号覆盖范围:</label>
<input type="checkbox" v-model="subCylinderShow" class="w-6 h-6 cursor-pointer" />
</div>
<div class="option">
<label>显示/隐藏卫星全部信息:</label>
<input type="checkbox" v-model="subSatelliteShow" class="w-6 h-6 cursor-pointer" />
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
import { CloseBold } from "@element-plus/icons-vue";
const emit = defineEmits([
"closeSubSatelliteControlPanel",
"handleSubSatelliteControl",
]);
const show = ref(false);
const subSatelliteModelShow = ref(true);
const subCylinderSelected = ref("GREEN");
const subCylinderShow = ref(true);
const subSatelliteShow = ref(true);
const cesiumColor = {
BLUE: "蓝色",
PURPLE: "紫色",
RED: "红色",
YELLOW: "黄色",
CYAN: "青色",
ORANGE: "橙色",
PINK: "粉色",
GREEN: "绿色",
WHITE: "白色",
LIGHTBLUE: "浅蓝色",
LIME: "青柠色",
MAGENTA: "品红色",
};
const data = {
subSatelliteModelShow,
subCylinderSelected,
subCylinderShow,
subSatelliteShow,
};
const close = () => {
show.value = false;
emit("closeSubSatelliteControlPanel");
};
defineExpose({ show, data });
watch(subSatelliteModelShow, (value) => {
emit("handleSubSatelliteControl", "subSatelliteModelShow", value);
});
watch(subCylinderSelected, (value) => {
emit("handleSubSatelliteControl", "subCylinderSelected", value);
});
watch(subCylinderShow, (value) => {
emit("handleSubSatelliteControl", "subCylinderShow", value);
});
watch(subSatelliteShow, (value) => {
emit("handleSubSatelliteControl", "subSatelliteShow", value);
});
</script>
<style scoped>
.option {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.buttonClass {
padding-left: 0.5rem;
padding-right: 0.5rem;
font-size: 1rem;
cursor: pointer;
width: 12rem;
height: 2rem;
line-height: 2rem;
}
</style>

View File

@@ -0,0 +1,281 @@
<template>
<div
v-if="show"
class="text-left p-10"
style="
color: #ffffff;
background-color: rgb(55 55 55 / 0.9);
border-radius: 2rem;
box-shadow: 0 0px 1rem 0px rgb(165 243 252 / 1);
position: fixed;
top: 10vh;
left: 20vw;
width: 60vw;
height: 80vh;
font-size: 1rem;
"
>
<div
@click="close"
class="bg-gray-600 hover:bg-red-600 text-3xl absolute top-0 right-0 cursor-pointer flex justify-center items-center"
style="
width: 2rem;
height: 2rem;
border-top-right-radius: 1rem;
border-bottom-left-radius: 1rem;
"
>
<el-icon style="width: 2rem; height: 2rem;">
<CloseBold color="#ffffff" />
</el-icon>
</div>
<div class="w-full text-center" style="font-size: 2rem; height: 4rem; line-height: 4rem;">
添加卫星
</div>
<div class="text-white text-center flex justify-center items-center" style="height: 80%; font-size: 1rem;">
<div class="option">
<label :style="satelliteNameCheck ? '' : 'color: red;'">卫星名称:</label>
<input
type="text"
maxlength="12"
v-model="subSatelliteNameData"
placeholder="请输入卫星名称"
class="dataInput"
:style="satelliteNameCheck ? 'border: 2px solid black; color: black;' : 'border: 2px solid red; color: red;'"
/>
</div>
<div class="option">
<label :style="trueAnomalyCheck ? '' : 'color: red;'">真近点角</label>
<label :style="trueAnomalyCheck ? 'color: #00efff;' : 'color: red;'">(0°~360°):</label>
<input
type="number"
maxlength="3"
v-model="subTrueAnomalyData"
placeholder="请输入数据(0°~360°)"
class="dataInput"
:style="trueAnomalyCheck ? 'border: 2px solid black; color: black;' : 'border: 2px solid red; color: red;'"
/>
</div>
<div class="option">
<label :style="beamWidthCheck ? '' : 'color: red;'">波束宽度</label>
<label :style="beamWidthCheck ? 'color: #00efff;' : 'color: red;'">(0.1°~22°):</label>
<input
type="number"
maxlength="4"
v-model="subBeamWidthData"
placeholder="请输入数据"
class="dataInput"
:style="beamWidthCheck ? 'border: 2px solid black; color: black;' : 'border: 2px solid red; color: red;'"
/>
</div>
<div class="option">
<label>卫星信号覆盖范围颜色:</label>
<select v-model="subCylinderSelectedData" style="cursor: pointer; color: black;">
<option v-for="(value, key) in cesiumColor" :key="key" :value="key">
{{ value }}
</option>
</select>
</div>
<div class="option">
<div>
<div>当前卫星模型:{{ satelliteName }}</div>
<input
type="button"
class="buttonClass"
id="fly-btn"
@click="selectSatelliteModel"
value="选择卫星模型"
/>
</div>
</div>
<div class="option">
<input
type="button"
class="buttonClass"
id="fly-btn"
@click="generateSubSatellite"
value="添加卫星"
/>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
import { CloseBold } from "@element-plus/icons-vue";
const props = defineProps({
satelliteArr: Array,
});
const emit = defineEmits([
"closeSubSatelliteCreatePanel",
"selectSatelliteModel",
"generateSubSatellite"
]);
const show = ref(false);
const subSatelliteNameData = ref();
const subTrueAnomalyData = ref();
const subBeamWidthData = ref();
const subCylinderSelectedData = ref("GREEN");
const satelliteNameCheck = ref(true);
const trueAnomalyCheck = ref(true);
const beamWidthCheck = ref(true);
const satelliteName = ref("BeidouGEO");
const satelliteGif = ref("/image/gif/BeidouGEO.gif");
const cesiumColor = {
BLUE: "蓝色",
PURPLE: "紫色",
RED: "红色",
YELLOW: "黄色",
CYAN: "青色",
ORANGE: "橙色",
PINK: "粉色",
GREEN: "绿色",
WHITE: "白色",
LIGHTBLUE: "浅蓝色",
LIME: "青柠色",
MAGENTA: "品红色",
};
const data = {
subSatelliteNameData,
subTrueAnomalyData,
subBeamWidthData,
subCylinderSelectedData,
};
const close = () => {
show.value = false;
emit("closeSubSatelliteCreatePanel");
};
const booleanCheck = (checkArr) => {
let check = true;
checkArr.forEach((item) => {
if (item == false) {
check = false;
}
});
return check;
};
const subSatelliteNameDataCheck = () => {
if (subSatelliteNameData.value === "" || subSatelliteNameData.value === undefined) {
satelliteNameCheck.value = false;
} else {
satelliteNameCheck.value = true;
}
props.satelliteArr.forEach((entity) => {
for (const element in entity) {
if (entity[element].name !== undefined) {
let name = entity[element].name;
if (subSatelliteNameData.value == name) {
satelliteNameCheck.value = false;
}
}
if (element == "subSatelliteArr") {
entity[element].forEach((item, index) => {
for (const subElement in item) {
if (item[subElement].name !== undefined) {
let name = item[subElement].name;
if (subSatelliteNameData.value == name) {
satelliteNameCheck.value = false;
}
}
}
});
}
}
});
};
const subTrueAnomalyDataCheck = () => {
if (subTrueAnomalyData.value === "" || subTrueAnomalyData.value === undefined || subTrueAnomalyData.value < 0 || subTrueAnomalyData.value > 360) {
trueAnomalyCheck.value = false;
} else {
trueAnomalyCheck.value = true;
}
};
const subBeamWidthDataCheck = () => {
if (subBeamWidthData.value === "" || subBeamWidthData.value === undefined || subBeamWidthData.value <= 0 || subBeamWidthData.value > 22) {
beamWidthCheck.value = false;
} else {
beamWidthCheck.value = true;
}
};
watch(subSatelliteNameData, (value) => {
subSatelliteNameDataCheck();
});
watch(subTrueAnomalyData, (value) => {
subTrueAnomalyDataCheck();
});
watch(subBeamWidthData, (value) => {
subBeamWidthDataCheck();
});
const generateCheck = () => {
let checkArr = [];
subSatelliteNameDataCheck();
checkArr.push(satelliteNameCheck.value);
subTrueAnomalyDataCheck();
checkArr.push(trueAnomalyCheck.value);
subBeamWidthDataCheck();
checkArr.push(beamWidthCheck.value);
return booleanCheck(checkArr);
};
const selectSatelliteModel = () => {
emit("selectSatelliteModel");
};
const generateSubSatellite = () => {
if (generateCheck()) {
emit("generateSubSatellite", "subSatellite");
}
};
defineExpose({ show, satelliteName, satelliteGif, data });
</script>
<style scoped>
.dataInput {
padding-left: 0.5rem;
padding-right: 0.5rem;
height: 2rem;
background: #ffffff;
border-radius: 1rem;
}
.option {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.buttonClass {
padding-left: 0.5rem;
padding-right: 0.5rem;
font-size: 1rem;
cursor: pointer;
width: 15rem;
height: 2rem;
line-height: 2rem;
}
</style>

View File

@@ -0,0 +1,256 @@
<template>
<div
v-if="show"
class="text-left p-10"
style="
color: #ffffff;
background-color: rgb(55 55 55 / 0.9);
border-radius: 2rem;
box-shadow: 0 0px 1rem 0px rgb(165 243 252 / 1);
position: fixed;
top: 10vh;
left: 20vw;
width: 60vw;
height: 80vh;
font-size: 1rem;
"
>
<div
@click="close"
class="bg-gray-600 hover:bg-red-600 text-3xl absolute top-0 right-0 cursor-pointer flex justify-center items-center"
style="
width: 2rem;
height: 2rem;
border-top-right-radius: 1rem;
border-bottom-left-radius: 1rem;
"
>
<el-icon style="width: 2rem; height: 2rem;">
<CloseBold color="#ffffff" />
</el-icon>
</div>
<div class="w-full text-center" style="font-size: 2rem; height: 4rem; line-height: 4rem;">修改卫星数据</div>
<div class="text-white text-center flex justify-center items-center" style="height: 80%; font-size: 1rem;">
<div class="option">
<label :style="satelliteNameCheck ? '' : 'color: red;'">卫星名称:</label>
<input
type="text"
v-model="subSatelliteNameModify"
placeholder="请输入卫星名称"
class="dataInput"
:style="satelliteNameCheck ? 'border: 2px solid black; color: black;' : 'border: 2px solid red; color: red;'"
/>
</div>
<div class="option">
<label :style="trueAnomalyCheck ? '' : 'color: red;'">真近点角</label>
<label :style="trueAnomalyCheck ? 'color: #00efff;' : 'color: red;'">(0°~360°):</label>
<input
type="number"
maxlength="3"
v-model="subTrueAnomalyModify"
placeholder="请输入数据(0°~360°)"
class="dataInput"
:style="trueAnomalyCheck ? 'border: 2px solid black; color: black;' : 'border: 2px solid red; color: red;'"
/>
</div>
<div class="option">
<label :style="beamWidthCheck ? '' : 'color: red;'">波束宽度</label>
<label :style="beamWidthCheck ? 'color: #00efff;' : 'color: red;'">(0.1°~22°):</label>
<input
type="number"
maxlength="4"
v-model="subBeamWidthModify"
placeholder="请输入数据"
class="dataInput"
:style="beamWidthCheck ? 'border: 2px solid black; color: black;' : 'border: 2px solid red; color: red;'"
/>
</div>
<div class="option">
<div>
<div>当前卫星模型:{{ satelliteName }}</div>
<input
type="button"
class="buttonClass"
id="fly-btn"
@click="selectSatelliteModel"
value="选择卫星模型"
/>
</div>
</div>
<div class="option">
<input
type="button"
class="buttonClass"
id="fly-btn"
@click="modifySubSatellite"
value="修改数据"
/>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
import { CloseBold } from "@element-plus/icons-vue";
const props = defineProps({
satelliteArr: Array,
subSatelliteId: String,
});
const emit = defineEmits([
"closeSubSatelliteModifyPanel",
"selectSatelliteModel",
"modifySubSatellite"
]);
const show = ref(false);
const subSatelliteNameModify = ref();
const subTrueAnomalyModify = ref();
const subBeamWidthModify = ref();
const satelliteNameCheck = ref(true);
const trueAnomalyCheck = ref(true);
const beamWidthCheck = ref(true);
const satelliteName = ref("BeidouGEO");
const satelliteGif = ref("/image/gif/BeidouGEO.gif");
const data = {
subSatelliteNameModify,
subTrueAnomalyModify,
subBeamWidthModify,
};
const close = () => {
show.value = false;
emit("closeSubSatelliteModifyPanel");
};
const booleanCheck = (checkArr) => {
let check = true;
checkArr.forEach((item) => {
if (item == false) {
check = false;
}
});
return check;
};
const subSatelliteNameModifyCheck = () => {
if (subSatelliteNameModify.value === "" || subSatelliteNameModify.value === undefined) {
satelliteNameCheck.value = false;
} else {
satelliteNameCheck.value = true;
}
props.satelliteArr.forEach((entity) => {
for (const element in entity) {
if (entity[element].name !== undefined) {
let name = entity[element].name;
if (subSatelliteNameModify.value == name) {
satelliteNameCheck.value = false;
}
}
if (element == "subSatelliteArr") {
entity[element].forEach((item, index) => {
for (const subElement in item) {
if (item[subElement].name !== undefined) {
let name = item[subElement].name;
if (subSatelliteNameModify.value == name) {
let item = Number(props.subSatelliteId.split("-")[1]) - 1;
if (index !== item) {
satelliteNameCheck.value = false;
}
}
}
}
});
}
}
});
};
const subTrueAnomalyModifyCheck = () => {
if (subTrueAnomalyModify.value === "" || subTrueAnomalyModify.value === undefined || subTrueAnomalyModify.value < 0 || subTrueAnomalyModify.value > 360) {
trueAnomalyCheck.value = false;
} else {
trueAnomalyCheck.value = true;
}
};
const subBeamWidthModifyCheck = () => {
if (subBeamWidthModify.value === "" || subBeamWidthModify.value === undefined || subBeamWidthModify.value <= 0 || subBeamWidthModify.value > 22) {
beamWidthCheck.value = false;
} else {
beamWidthCheck.value = true;
}
};
watch(subSatelliteNameModify, (value) => {
subSatelliteNameModifyCheck();
});
watch(subTrueAnomalyModify, (value) => {
subTrueAnomalyModifyCheck();
});
watch(subBeamWidthModify, (value) => {
subBeamWidthModifyCheck();
});
const generateCheck = () => {
let checkArr = [];
subSatelliteNameModifyCheck();
checkArr.push(satelliteNameCheck.value);
subTrueAnomalyModifyCheck();
checkArr.push(trueAnomalyCheck.value);
subBeamWidthModifyCheck();
checkArr.push(beamWidthCheck.value);
return booleanCheck(checkArr);
};
const selectSatelliteModel = () => {
emit("selectSatelliteModel");
};
const modifySubSatellite = () => {
if (generateCheck()) {
emit("modifySubSatellite");
}
};
defineExpose({ show, satelliteName, satelliteGif, data });
</script>
<style scoped>
.dataInput {
padding-left: 0.5rem;
padding-right: 0.5rem;
height: 2rem;
background: #ffffff;
border-radius: 1rem;
}
.option {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.buttonClass {
padding-left: 0.5rem;
padding-right: 0.5rem;
font-size: 1rem;
cursor: pointer;
width: 12rem;
height: 2rem;
line-height: 2rem;
}
</style>

View File

@@ -0,0 +1,285 @@
<template>
<button @click="showit" class="sceneControl">场景控制面板</button>
<div
v-if="show"
class="text-left p-10"
style="
color: #ffffff;
background-color: rgb(55 55 55 / 0.9);
border-radius: 2rem;
box-shadow: 0 0px 1rem 0px rgb(165 243 252 / 1);
position: fixed;
top: 10vh;
left: 20vw;
width: 60vw;
height: 80vh;
font-size: 1rem;
"
>
<div
@click="close"
class="bg-gray-600 hover:bg-red-600 text-3xl absolute top-0 right-0 cursor-pointer flex justify-center items-center"
style="width: 2rem; height: 2rem; border-top-right-radius: 1rem; border-bottom-left-radius: 1rem;"
>
<el-icon style="width: 2rem; height: 2rem">
<CloseBold color="#ffffff" />
</el-icon>
</div>
<div class="w-full text-center" style="font-size: 2rem; height: 4rem; line-height: 4rem;">
场景控制面板
</div>
<div class="text-white text-center flex justify-center items-center" style="height: 80%; font-size: 1rem;">
<div class="option">
<label>显示/隐藏地球:</label>
<input
type="checkbox"
v-model="earthShow"
class="w-6 h-6 cursor-pointer"
/>
</div>
<div class="option">
<label>显示/隐藏坐标系:</label>
<input
type="checkbox"
v-model="axesShow"
class="w-6 h-6 cursor-pointer"
/>
</div>
<div class="option">
<label>坐标轴X颜色:</label>
<select v-model="axesXSelected" style="cursor: pointer; color: black">
<option v-for="(value, key) in cesiumColor" :key="key" :value="key">
{{ value }}
</option>
</select>
</div>
<div class="option">
<label>显示/隐藏坐标轴X:</label>
<input
type="checkbox"
v-model="axesXShow"
class="w-6 h-6 cursor-pointer"
/>
</div>
<div class="option">
<label>坐标轴Y颜色:</label>
<select v-model="axesYSelected" style="cursor: pointer; color: black">
<option v-for="(value, key) in cesiumColor" :key="key" :value="key">
{{ value }}
</option>
</select>
</div>
<div class="option">
<label>显示/隐藏坐标轴Y:</label>
<input
type="checkbox"
v-model="axesYShow"
class="w-6 h-6 cursor-pointer"
/>
</div>
<div class="option">
<label>坐标轴Z颜色:</label>
<select v-model="axesZSelected" style="cursor: pointer; color: black">
<option v-for="(value, key) in cesiumColor" :key="key" :value="key">
{{ value }}
</option>
</select>
</div>
<div class="option">
<label>显示/隐藏坐标轴Z:</label>
<input
type="checkbox"
v-model="axesZShow"
class="w-6 h-6 cursor-pointer"
/>
</div>
<div class="option">
<label>赤道线颜色:</label>
<select v-model="equatorLineSelected" style="cursor: pointer; color: black">
<option v-for="(value, key) in cesiumColor" :key="key" :value="key">
{{ value }}
</option>
</select>
</div>
<div class="option">
<label>显示/隐藏赤道线:</label>
<input
type="checkbox"
v-model="equatorLineShow"
class="w-6 h-6 cursor-pointer"
/>
</div>
<div class="option">
<label>赤道面颜色:</label>
<select v-model="equatorPlaneSelected" style="cursor: pointer; color: black">
<option v-for="(value, key) in cesiumColor" :key="key" :value="key">
{{ value }}
</option>
</select>
</div>
<div class="option">
<label>显示/隐藏赤道面:</label>
<input
type="checkbox"
v-model="equatorPlaneShow"
class="w-6 h-6 cursor-pointer"
/>
</div>
<div class="option">
<input
type="button"
class="buttonClass"
id="fly-btn"
@click="resetScene"
value="重置场景"
/>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
import { CloseBold } from "@element-plus/icons-vue";
const emit = defineEmits(["handleSceneControl"]);
const show = ref(false);
const earthShow = ref(true);
const axesXSelected = ref("LIME");
const axesXShow = ref(true);
const axesYSelected = ref("PURPLE");
const axesYShow = ref(true);
const axesZSelected = ref("RED");
const axesZShow = ref(true);
const axesShow = ref(true);
const equatorLineSelected = ref("CYAN");
const equatorLineShow = ref(true);
const equatorPlaneSelected = ref("YELLOW");
const equatorPlaneShow = ref(true);
const cesiumColor = {
BLUE: "蓝色",
PURPLE: "紫色",
RED: "红色",
YELLOW: "黄色",
CYAN: "青色",
ORANGE: "橙色",
PINK: "粉色",
GREEN: "绿色",
WHITE: "白色",
LIGHTBLUE: "浅蓝色",
LIME: "青柠色",
MAGENTA: "品红色",
};
const showit = () => {
show.value = true;
};
const close = () => {
show.value = false;
};
const resetScene = () => {
earthShow.value = true;
axesXSelected.value = "LIME";
axesXShow.value = true;
axesYSelected.value = "PURPLE";
axesYShow.value = true;
axesZSelected.value = "RED";
axesZShow.value = true;
axesShow.value = true;
equatorLineSelected.value = "CYAN";
equatorLineShow.value = true;
equatorPlaneSelected.value = "YELLOW";
equatorPlaneShow.value = true;
};
watch(earthShow, (value) => {
emit("handleSceneControl", "earthShow", value);
});
watch(axesXSelected, (value) => {
emit("handleSceneControl", "axesXSelected", value);
});
watch(axesXShow, (value) => {
emit("handleSceneControl", "axesXShow", value);
});
watch(axesYSelected, (value) => {
emit("handleSceneControl", "axesYSelected", value);
});
watch(axesYShow, (value) => {
emit("handleSceneControl", "axesYShow", value);
});
watch(axesZSelected, (value) => {
emit("handleSceneControl", "axesZSelected", value);
});
watch(axesZShow, (value) => {
emit("handleSceneControl", "axesZShow", value);
});
watch(axesShow, (value) => {
emit("handleSceneControl", "axesShow", value);
});
watch(equatorLineSelected, (value) => {
emit("handleSceneControl", "equatorLineSelected", value);
});
watch(equatorLineShow, (value) => {
emit("handleSceneControl", "equatorLineShow", value);
});
watch(equatorPlaneSelected, (value) => {
emit("handleSceneControl", "equatorPlaneSelected", value);
});
watch(equatorPlaneShow, (value) => {
emit("handleSceneControl", "equatorPlaneShow", value);
});
</script>
<style scoped>
.sceneControl {
font-size: 1rem;
color: #ffffff;
background-color: rgb(255 255 255 / 0.5);
width: 8rem;
height: 2rem;
line-height: 2rem;
overflow: hidden;
box-shadow: 0 0px 1rem 0px rgb(165 243 252 / 0.5);
position: fixed;
bottom: 22rem;
right: 0;
border-radius: 1rem;
}
.sceneControl:hover {
color: #000000;
box-shadow: 0 0px 1rem 0px rgb(165 243 252 / 1);
background-color: rgb(250, 230, 164);
}
.option {
width: 50%;
display: flex;
justify-content: center;
align-items: center;
}
.buttonClass {
font-size: 1rem;
cursor: pointer;
width: 6rem;
height: 2rem;
line-height: 2rem;
}
</style>

View File

@@ -0,0 +1,283 @@
<template>
<div class="bg-white" style="width: 30vw; height: 44vh; z-index: 99; border-radius: 1rem;">
<div
class="text-3xl absolute bottom-2 right-2 cursor-pointer flex justify-center items-center"
style="width: 2rem; height: 6rem;"
>
<div
class="bg-gray-600"
style="width: 2rem; height: 2rem; border-top-left-radius: 50%; border-top-right-radius: 50%;"
>
<div
class="bg-gray-600 hover:bg-red-600"
style="width: 2rem; height: 2rem; border-radius: 50%;"
>
<el-icon @click="plus">
<Plus color="#ffffff" />
</el-icon>
</div>
</div>
<div class="bg-gray-600" style="width: 2rem; height: 2rem;">
<div
class="hover:bg-red-600"
style="width: 2rem; height: 2rem; border-radius: 50%;"
>
<el-icon @click="refresh">
<RefreshRight color="#ffffff" />
</el-icon>
</div>
</div>
<div
class="bg-gray-600"
style="width: 2rem; height: 2rem; border-bottom-left-radius: 50%; border-bottom-right-radius: 50%;"
>
<div
class="bg-gray-600 hover:bg-red-600"
style="width: 2rem; height: 2rem; border-radius: 50%;"
>
<el-icon @click="minus">
<Minus color="#ffffff" />
</el-icon>
</div>
</div>
</div>
<div
id="threeCanvas"
ref="canvasContainer"
class="canvasContainer"
>
<select v-model="selected" style="border-radius: 1rem; font-size: 1rem; width: 50%; height: 2rem; cursor: pointer; color: black; position: absolute; top: 10%; left: 25%;">
<option v-for="(value, key) in modelName" :key="key" :value="key">
{{ value }}
</option>
</select>
<div
@click="closeModel"
class="bg-gray-600 hover:bg-red-600 text-3xl absolute top-0 right-0 cursor-pointer flex justify-center items-center"
style="width: 2rem; height: 2rem; position: absolute; top: 0; right: 0; border-top-right-radius: 1rem; border-bottom-left-radius: 1rem;"
>
<el-icon style="width: 2rem; height: 2rem;">
<CloseBold color="#ffffff" />
</el-icon>
</div>
<input
type="button"
class="buttonClass"
@click="confirmModel"
value="确认"
/>
</div>
</div>
</template>
<script setup>
import { ref, watch, onMounted } from "vue";
import { CloseBold, Plus, RefreshRight, Minus } from "@element-plus/icons-vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import sky from "../../assets/sky.jpg";
const selected = ref("BeidouGEO");
const modelName = {
BeidouGEO: "北斗 GEO卫星",
BeidouIGSO: "北斗 IGSO卫星",
BeidouMEO: "北斗 MEO卫星",
"GPSII-F": "GPS II-F卫星",
"GPSII-R": "GPS II-R卫星",
"GPSIII-A": "GPS III-A卫星",
"GPSIIR-M": "GPS IIR-M卫星",
"GLONASSGLO-K": "格洛纳斯 GLO-K卫星",
"GLONASSGLO-M": "格洛纳斯 GLO-M卫星",
GalileoMEO: "伽利略 MEO卫星"
};
const satelliteModelType = ref("BeidouGEO");
const canvasContainer = ref(null);
const model = ref(null);
let scene, camera, renderer, clonedModel, controls;
// 初始化场景
const initScene = () => {
const threeCanvas = document.getElementById("threeCanvas");
// 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0x666666);
// 创建相机(透视相机)
camera = new THREE.PerspectiveCamera(
75,
threeCanvas.clientWidth / threeCanvas.clientHeight,
0.1,
1000
);
camera.position.z = 5;
// 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(threeCanvas.clientWidth, threeCanvas.clientHeight);
// 挂载到 DOM
canvasContainer.value.appendChild(renderer.domElement);
//创建鼠标控制器
controls = new OrbitControls(camera, renderer.domElement);
// 添加灯光
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 1, 1);
scene.add(light);
const light1 = new THREE.DirectionalLight(0xffffff, 1);
light1.position.set(-1, 1, 1);
scene.add(light1);
const light2 = new THREE.DirectionalLight(0xffffff, 1);
light2.position.set(1, -1, 1);
scene.add(light2);
const light3 = new THREE.DirectionalLight(0xffffff, 1);
light3.position.set(1, 1, -1);
scene.add(light3);
const light4 = new THREE.DirectionalLight(0xffffff, 1);
light4.position.set(-1, -1, 1);
scene.add(light4);
const light5 = new THREE.DirectionalLight(0xffffff, 1);
light5.position.set(1, -1, -1);
scene.add(light5);
const light6 = new THREE.DirectionalLight(0xffffff, 1);
light6.position.set(-1, 1, -1);
scene.add(light6);
const light7 = new THREE.DirectionalLight(0xffffff, 1);
light7.position.set(-1, -1, -1);
scene.add(light7);
// 创建半球体几何体
const skyGeometry = new THREE.SphereGeometry(66, 66, 66); // 半径、宽度分段、高度分段
// 加载天空纹理
const skyTextureLoader = new THREE.TextureLoader();
const skyTexture = skyTextureLoader.load(sky);
// 创建材质,并应用天空纹理
const skyMaterial = new THREE.MeshBasicMaterial({
map: skyTexture,
side: THREE.BackSide,
}); // 使用背面显示纹理
// 创建天空穹顶网格
const skyDome = new THREE.Mesh(skyGeometry, skyMaterial);
// 将天空穹顶添加到场景中
scene.add(skyDome);
};
// 动画循环
const animate = () => {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
};
// 初始球体半径
// r1 = Math.sqrt(x * x + y * y + z * z)
// 相机移动后的球体半径为 r2 ,假设相机向球心移动距离为 1 单位长度
// r1 - r2 = 1
// 调整相机移动距离 d 以获取相机移动后的球体半径 r2
// r2 = r1 - d
// 坐标值与球体半径比例是固定的,初始坐标值为 x1,相机移动后的坐标值为 x2
// x1 / r1 = x2 / r2
// x2 = x1 / r1 * r2
const plus = () => {
const moveValue = 1;
const r1 = Math.sqrt(camera.position.x * camera.position.x + camera.position.y * camera.position.y + camera.position.z * camera.position.z);
const r2 = r1 - moveValue;
const x = (camera.position.x / r1) * r2;
const y = (camera.position.y / r1) * r2;
const z = (camera.position.z / r1) * r2;
camera.position.set(x, y, z);
};
const refresh = () => {
camera.position.set(0, 0, 5);
};
const minus = () => {
const moveValue = 1;
const r1 = Math.sqrt(camera.position.x * camera.position.x + camera.position.y * camera.position.y + camera.position.z * camera.position.z);
const r2 = r1 + moveValue;
const x = (camera.position.x / r1) * r2;
const y = (camera.position.y / r1) * r2;
const z = (camera.position.z / r1) * r2;
camera.position.set(x, y, z);
};
const emit = defineEmits(["selectModel", "closeModel", "changeModel"]);
const confirmModel = () => {
emit("selectModel", satelliteModelType.value);
emit("closeModel");
};
const closeModel = () => {
emit("closeModel");
};
defineExpose({ selected, model });
watch(selected, (value) => {
satelliteModelType.value = value;
emit("changeModel", value);
setTimeout(() => {
scene.remove(clonedModel);
clonedModel = model.value.model.clone();
scene.add(clonedModel);
}, 0);
});
onMounted(() => {
initScene();
setTimeout(() => {
clonedModel = model.value.model.clone();
scene.add(clonedModel);
}, 0);
animate();
});
</script>
<style scoped>
.canvasContainer {
width: 30vw;
height: 44vh;
z-index: 99;
margin: 0;
overflow: hidden;
position: relative;
border-radius: 1rem;
}
.buttonClass {
font-size: 1rem;
width: 20%;
height: 2rem;
line-height: 2rem;
color: #000000;
background-color: rgb(165 243 252 / 1);
overflow: hidden;
cursor: pointer;
box-shadow: 0 0px 1rem 0px rgb(165 243 252 / 1);
position: absolute;
left: 40%;
bottom: 10%;
border-radius: 1rem;
}
.buttonClass:hover {
background-color: rgb(250, 230, 164);
color: #41ddee;
}
</style>

View File

@@ -1,38 +1,67 @@
<template>
<vc-viewer
class="vc-viewer"
:animation="true"
:showCredit="false"
:shouldAnimate="true"
:sceneModePicker="true"
@ready="onViewerReady"
>
<vc-layer-imagery>
<vc-provider-imagery-singletile :url="earth" />
</vc-layer-imagery>
<vc-config-provider :cesiumPath="cesiumPath">
<vc-viewer
class="vc-viewer"
:animation="true"
:showCredit="false"
:shouldAnimate="true"
:sceneModePicker="true"
@ready="onViewerReady"
>
<vc-layer-imagery>
<vc-imagery-provider-singletile :url="earth" />
</vc-layer-imagery>
<vc-navigation
:offset="[10, 40]"
:position="'top-left'"
:printOpts="false"
:locationOpts="false"
:otherOpts="false"
/>
<SatelliteSystemView />
</vc-viewer>
<vc-navigation
v-if="animationShow"
:position="'top-left'"
:printOpts="false"
:locationOpts="false"
:otherOpts="false"
/>
<SatelliteSystemView />
</vc-viewer>
</vc-config-provider>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref, watch } from "vue";
import earth from "../assets/earth.jpg";
import earth from "../../assets/earth.jpg";
// 由于组件 SatelliteSystemView 加载的数据量较大,会影响到卫星模型加载的速度,因此使用 defineAsyncComponent 来延迟加载组件 SatelliteSystemView
const SatelliteSystemView = defineAsyncComponent(
() => import("./SatelliteSystemView.vue")
);
const SatelliteSystemView = defineAsyncComponent(() => import("./SatelliteSystemView.vue"));
const cesiumPath = process.env.NODE_ENV === "development" ? "./node_modules/cesium/Build/Cesium/Cesium.js" : "./Cesium/Cesium.js";
const animationShow = ref(true);
const firstClick = ref(true);
const secondClick = ref(false);
const flyfunc = ref(null);
const singleBeamShow = ref(false);
const dualBeamShow = ref(false);
const emit = defineEmits(["showModel"]);
const showModelTour = () => {
setTimeout(() => {
window.document.getElementsByClassName("vc-navigation-controls")[0].children[0].children[0].children[0].children[1].children[0].style.fontSize = "6rem";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[0].children[0].children[0].children[1].children[0].id = "step1";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[0].children[0].children[1].children[1].children[0].style.fontSize = "2rem";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[0].children[0].children[1].children[1].children[0].id = "step2";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[0].style.marginBottom = "1rem";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[1].children[0].children[0].children[0].children[0].children[1].children[0].style.fontSize = "2rem";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[1].children[0].children[0].children[0].children[0].children[1].children[0].id = "step3";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[1].children[0].children[0].children[1].children[0].children[1].children[0].style.fontSize = "2rem";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[1].children[0].children[0].children[1].children[0].children[1].children[0].id = "step4";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[1].children[0].children[0].children[2].children[0].children[1].children[0].style.fontSize = "2rem";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[1].children[0].children[0].children[2].children[0].children[1].children[0].id = "step5";
}, 0);
};
defineExpose({ animationShow, firstClick, flyfunc, singleBeamShow, dualBeamShow, showModelTour });
// 初始化视图参数
const onViewerReady = ({ viewer }) => {
const onViewerReady = ({ Cesium, viewer }) => {
window.Cesium = Cesium;
window.viewer = viewer;
window.allowUpdate = true;
@@ -44,6 +73,15 @@ const onViewerReady = ({ viewer }) => {
// 显示FPS的控件,便于优化
// scene.debugShowFramesPerSecond = true;
// 启用内置调试工具,便于优化
// viewer.extend(Cesium.viewerCesiumInspectorMixin);
// 降低渲染质量以提高性能(可选)
viewer.scene.globe.tileLoadProgressEvent.addEventListener(() => {
viewer.scene.maximumScreenSpaceError = 12; // 默认2
viewer.scene.fxaa = false; // 禁用抗锯齿
});
// 设置单一光源,解决模型光照受时间影响问题
// DirectionalLight 表示 从无限远的地方向单一方向发射的光。
scene.light = new Cesium.DirectionalLight({
@@ -91,9 +129,10 @@ const onViewerReady = ({ viewer }) => {
if (!this.show) {
return;
}
for (var t = this._primitives, i = 0; i < t.length; ++i) {
t[i].update(frameState);
}
var t = this._primitives;
t.forEach((element) => {
element.update(frameState);
});
}
}
@@ -2303,22 +2342,22 @@ const onViewerReady = ({ viewer }) => {
e.commandList.push(item);
});
this.myinterver += 1;
var allowindex = 96;
if (viewer._clockViewModel.multiplier >= 100000) {
allowindex = 3;
} else if (50000 <= viewer._clockViewModel.multiplier < 100000) {
allowindex = 6;
} else if (25000 <= viewer._clockViewModel.multiplier < 50000) {
allowindex = 12;
} else if (12000 <= viewer._clockViewModel.multiplier < 25000) {
allowindex = 24;
} else if (6000 <= viewer._clockViewModel.multiplier < 12000) {
allowindex = 48;
} else if (3000 <= viewer._clockViewModel.multiplier < 6000) {
allowindex = 64;
} else if (1000 <= viewer._clockViewModel.multiplier < 3000) {
allowindex = 84;
}
var allowindex = 1000;
// if (viewer._clockViewModel.multiplier >= 100000) {
// allowindex = 3;
// } else if (50000 <= viewer._clockViewModel.multiplier < 100000) {
// allowindex = 6;
// } else if (25000 <= viewer._clockViewModel.multiplier < 50000) {
// allowindex = 12;
// } else if (12000 <= viewer._clockViewModel.multiplier < 25000) {
// allowindex = 24;
// } else if (6000 <= viewer._clockViewModel.multiplier < 12000) {
// allowindex = 48;
// } else if (3000 <= viewer._clockViewModel.multiplier < 6000) {
// allowindex = 64;
// } else if (1000 <= viewer._clockViewModel.multiplier < 3000) {
// allowindex = 84;
// }
if (this.myinterver == allowindex) {
this.myinterver = 0;
window.toallowUpdate = true;
@@ -2337,10 +2376,10 @@ const onViewerReady = ({ viewer }) => {
);
createBatchTable(this, i), (this._createBatchTable = !1);
}
if (this._createVertexArray || computeNewBuffersUsage(this))
if (this._createVertexArray || computeNewBuffersUsage(this)) {
createVertexArrays(this, i, r);
}
else if (this._polylinesUpdated) {
console.log("othersetp");
var a = this._polylinesToUpdate;
if (this._mode !== SceneMode$1.SCENE3D)
a.forEach((element) => {
@@ -2427,8 +2466,9 @@ const onViewerReady = ({ viewer }) => {
})),
this._batchTable.update(e),
T.render || T.pick)
)
) {
createCommandLists(this, e, this._colorCommands, C);
}
}
window.toallowUpdate = false;
}
@@ -2440,52 +2480,44 @@ const onViewerReady = ({ viewer }) => {
scene.screenSpaceCameraController.maximumZoomDistance = 200000000; // 相机高度的最大值设定为 200000000 米
const clickStatus = ref();
const clickName = ref("cesium-sceneModePicker-button3D");
const infoBox = viewer.infoBox.frame;
let initialPosition = "";
let initialDirection = "";
// 修改infobox的样式
infoBox.setAttribute(
"sandbox",
"allow-same-origin allow-scripts allow-popups allow-forms"
);
infoBox.setAttribute("sandbox", "allow-same-origin allow-scripts allow-popups allow-forms");
infoBox.setAttribute("src", ""); // 必须设置src为空 否则不会生效
infoBox.addEventListener("load", function () {
clickStatus.value = JSON.stringify(camera.position);
viewer.infoBox.container
.getElementsByClassName("cesium-infoBox-camera")[0]
.addEventListener("click", () => {
// 完成当前相机飞行并立即将相机移动到最终目的地。
camera.completeFlight();
// 在相机停止移动时再进行操作,防止相机位置的抖动
let removeChanged = camera.moveEnd.addEventListener(() => {
clickStatus.value = JSON.stringify(camera.position);
// 调整相机到初始位置后移除监听事件
removeChanged();
});
viewer.infoBox.container.getElementsByClassName("cesium-infoBox-camera")[0].addEventListener("click", () => {
const SatelliteMsgArr = viewer.infoBox._frame.contentDocument.getElementsByTagName("p");
emit("showModel", true, SatelliteMsgArr[2].textContent.slice(3), SatelliteMsgArr[SatelliteMsgArr.length - 1].textContent.indexOf("北斗"));
// 完成当前相机飞行并立即将相机移动到最终目的地。
camera.completeFlight();
// 在相机停止移动时再进行操作,防止相机位置的抖动
let removeChanged = camera.moveEnd.addEventListener(() => {
clickStatus.value = JSON.stringify(camera.position);
// 调整相机到初始位置后移除监听事件
removeChanged();
});
});
const infoBoxDescriptionElement = infoBox.contentWindow.document.getElementsByClassName(
"cesium-infoBox-description"
)[0];
const infoBoxDescriptionElement = infoBox.contentWindow.document.getElementsByClassName("cesium-infoBox-description")[0];
infoBoxDescriptionElement.style.fontSize = "larger";
infoBoxDescriptionElement.style.paddingLeft = "20px";
infoBoxDescriptionElement.style.paddingRight = "20px";
infoBoxDescriptionElement.style.paddingBottom = "10px";
infoBoxDescriptionElement.style.paddingLeft = "1.5rem";
infoBoxDescriptionElement.style.paddingRight = "1.5rem";
infoBoxDescriptionElement.style.paddingBottom = "0.5rem";
// 关闭按钮的样式
const InfoBoxClose = viewer.infoBox.container.getElementsByClassName(
"cesium-infoBox-close"
)[0];
const InfoBoxClose = viewer.infoBox.container.getElementsByClassName("cesium-infoBox-close")[0];
InfoBoxClose.style.padding = 0;
InfoBoxClose.style.width = "30px";
InfoBoxClose.style.height = "30px";
InfoBoxClose.style.position = "absolute";
InfoBoxClose.style.top = 0;
InfoBoxClose.style.left = "30px";
// InfoBoxClose.style.width = "1rem";
// InfoBoxClose.style.height = "1rem";
// InfoBoxClose.style.position = "absolute";
// InfoBoxClose.style.top = 0;
// InfoBoxClose.style.left = "2rem";
InfoBoxClose.style.zIndex = 99;
});
@@ -2521,96 +2553,68 @@ const onViewerReady = ({ viewer }) => {
// 各视图的初始化相机位置
// window.document.getElementsByClassName('cesium-toolbar-button')[1].classList[2] 可以获取到选择后当前视图按钮的名称
const elementsFly = (waitallow) => {
if (
window.document.getElementsByClassName("cesium-toolbar-button")[1].classList[2] ==
"cesium-sceneModePicker-button3D"
) {
waitit(
waitallow,
2500,
108.55,
34.32,
100000000,
0,
-90,
0,
"cesium-sceneModePicker-button3D"
);
if (window.document.getElementsByClassName("cesium-toolbar-button")[8].children[0].classList[2] == "cesium-sceneModePicker-button3D") {
waitit(waitallow, 2500, 108.55, 34.32, 100000000, 0, -90, 0, "cesium-sceneModePicker-button3D");
}
if (
window.document.getElementsByClassName("cesium-toolbar-button")[1].classList[2] ==
"cesium-sceneModePicker-button2D"
) {
waitit(
waitallow,
3500,
0,
0,
40000000,
0,
-90,
0,
"cesium-sceneModePicker-button2D"
);
if (window.document.getElementsByClassName("cesium-toolbar-button")[8].children[0].classList[2] == "cesium-sceneModePicker-button2D") {
waitit(waitallow, 3500, 0, 0, 40000000, 0, -90, 0, "cesium-sceneModePicker-button2D");
}
if (
window.document.getElementsByClassName("cesium-toolbar-button")[1].classList[2] ==
"cesium-sceneModePicker-buttonColumbusView"
) {
waitit(
waitallow,
4500,
0,
-90,
40000000,
0,
-45,
0,
"cesium-sceneModePicker-buttonColumbusView"
);
if (window.document.getElementsByClassName("cesium-toolbar-button")[8].children[0].classList[2] == "cesium-sceneModePicker-buttonColumbusView") {
waitit(waitallow, 4500, 0, -90, 40000000, 0, -45, 0, "cesium-sceneModePicker-buttonColumbusView");
}
initialPosition = JSON.stringify(camera.position);
initialDirection = JSON.stringify(camera.direction);
};
flyfunc.value = elementsFly;
const renderingOptimize = () => {
viewer.scene.primitives.show = false;
let mypolylines;
mypolylines = viewer.scene.primitives._primitives[viewer.scene.primitives._primitives.length - 1];
let polylineowner = new CustomPolyline(mypolylines);
let newprimitive = new CustomPrimitive(viewer.scene.primitives._primitives[10]);
viewer.scene.primitives._primitives[10] = newprimitive;
viewer.scene.primitives._primitives.pop();
viewer.scene.primitives._primitives.push(polylineowner);
viewer.scene.primitives.show = true;
};
const clickName = ref("cesium-sceneModePicker-button3D");
// 单击视图按钮时触发
window.document
.getElementsByClassName("cesium-toolbar-button")[0]
.addEventListener("click", () => {
if (
clickName.value !==
window.document.getElementsByClassName("cesium-toolbar-button")[1].classList[2]
) {
clickName.value = window.document.getElementsByClassName(
"cesium-toolbar-button"
)[1].classList[2];
elementsFly(true);
let mypolylines;
mypolylines =
viewer.scene.primitives._primitives[
viewer.scene.primitives._primitives.length - 1
];
if(mypolylines._mode == 2){
mypolylines._mode = 1;
window.document.getElementsByClassName("cesium-toolbar-button")[8].addEventListener("click", () => {
if (clickName.value !== window.document.getElementsByClassName("cesium-toolbar-button")[8].children[0].classList[2]) {
clickName.value = window.document.getElementsByClassName("cesium-toolbar-button")[8].children[0].classList[2];
// 解决首次3D视图转2D视图后再转哥伦布图时,卫星运动轨迹贴地的问题
if (firstClick.value && clickName.value == 'cesium-sceneModePicker-button2D') {
firstClick.value = false;
secondClick.value = true;
window.document.getElementsByClassName("cesium-toolbar-button")[8].children[3].click();
return;
} else if (clickName.value == 'cesium-sceneModePicker-buttonColumbusView') {
firstClick.value = false;
if (secondClick.value) {
setTimeout(() => {
secondClick.value = false;
window.document.getElementsByClassName("cesium-toolbar-button")[8].children[2].click();
}, 0);
return;
}
viewer.scene.primitives.show = false;
let polylineowner = new CustomPolyline(mypolylines);
let newprimitive = new CustomPrimitive(viewer.scene.primitives._primitives[10]);
viewer.scene.primitives._primitives[10] = newprimitive;
viewer.scene.primitives._primitives.pop();
viewer.scene.primitives._primitives.push(polylineowner);
viewer.scene.primitives.show = true;
}
});
if (!secondClick.value) {
elementsFly(true);
renderingOptimize();
}
}
});
// 在组件内部双击时触发
window.document
.getElementsByClassName("vc-viewer")[0]
.addEventListener("dblclick", () => {
if (initialDirection !== "") {
elementsFly(false);
}
});
window.document.getElementsByClassName("vc-viewer")[0].addEventListener("dblclick", () => {
if (initialDirection !== "") {
elementsFly(false);
}
});
// 相机位置发生变化时触发
watch(clickStatus, (value) => {
@@ -2618,7 +2622,174 @@ const onViewerReady = ({ viewer }) => {
elementsFly(false);
}
});
// 单波束
// 定义多边形的顶点数组,使用经度、纬度和高度来表示
const singleBeamPositions = Cesium.Cartesian3.fromDegreesArrayHeights([
50.0, 0.0, 0.0,
50.0, 60.0, 0.0,
160.0, 60.0, 0.0,
160.0, 0.0, 0.0
]);
// 创建一个多边形,并设置其外观为贴合地球表面
const singleBeamPolygon = viewer.entities.add({
name: "单波束",
description: `
<div style='font-size: 1rem;'>
东经 50°~东经160°
<br />
北纬0°~北纬60°
<br />
(短报文+无源定位)
</div>
`,
polygon: {
hierarchy: new Cesium.PolygonHierarchy(singleBeamPositions),
material: Cesium.Color.YELLOW.withAlpha(0.2),
outline: true, // 启用边框
outlineColor: Cesium.Color.YELLOW, // 边框颜色
// outlineWidth: 0, // 可选,设置边框宽度
// width: 0,
height: 0, // 设置高度为0,使其贴合地球表面
// extrudedHeight: 0.0, // 可选,设置多边形的拉伸高度
// extrudedHeightReference: Cesium.HeightReference.CLAMP_TO_GROUND, // 可选,使拉伸部分也贴合地面
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, // 使多边形贴合地面
show: singleBeamShow.value
}
});
// 双波束
// 定义多边形的顶点数组,使用经度、纬度和高度来表示
const dualBeamPositions = Cesium.Cartesian3.fromDegreesArrayHeights([
75.0, 10.0, 0.0,
75.0, 55.0, 0.0,
135.0, 55.0, 0.0,
135.0, 10.0, 0.0
]);
// 创建一个多边形,并设置其外观为贴合地球表面
const dualBeamPolygon = viewer.entities.add({
name: "双波束",
description: `
<div style='font-size: 1rem;'>
东经75°~东经135°
<br />
北纬10°~北纬55°
<br />
(短报文+有源/无源定位)
</div>
`,
polygon: {
hierarchy: new Cesium.PolygonHierarchy(dualBeamPositions),
material: Cesium.Color.RED.withAlpha(0.4),
outline: true, // 启用边框
outlineColor: Cesium.Color.RED, // 边框颜色
// outlineWidth: 0, // 可选,设置边框宽度
// width: 0,
height: 0, // 设置高度为0,使其贴合地球表面
// extrudedHeight: 0.0, // 可选,设置多边形的拉伸高度
// extrudedHeightReference: Cesium.HeightReference.CLAMP_TO_GROUND, // 可选,使拉伸部分也贴合地面
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, // 使多边形贴合地面
show: dualBeamShow.value
}
});
watch(singleBeamShow, (value) => {
singleBeamPolygon.polygon.show = value;
});
watch(dualBeamShow, (value) => {
dualBeamPolygon.polygon.show = value;
});
setTimeout(() => {
window.document.getElementsByClassName("cesium-toolbar-button")[8].children[0].style.width = "2rem";
window.document.getElementsByClassName("cesium-toolbar-button")[8].children[0].style.height = "2rem";
window.document.getElementsByClassName("cesium-toolbar-button")[8].children[0].id = "step19";
window.document.getElementsByClassName("cesium-toolbar-button")[8].style.right = "1rem";
window.document.getElementsByClassName("cesium-toolbar-button")[8].style.top = "1rem";
window.document.getElementsByClassName("cesium-toolbar-button")[8].children[1].style.width = "2rem";
window.document.getElementsByClassName("cesium-toolbar-button")[8].children[1].style.height = "2rem";
window.document.getElementsByClassName("cesium-toolbar-button")[8].children[2].style.width = "2rem";
window.document.getElementsByClassName("cesium-toolbar-button")[8].children[2].style.height = "2rem";
window.document.getElementsByClassName("cesium-toolbar-button")[8].children[3].style.width = "2rem";
window.document.getElementsByClassName("cesium-toolbar-button")[8].children[3].style.height = "2rem";
window.document.getElementsByClassName("cesium-infoBox")[2].style.top = "4rem";
window.document.getElementsByClassName("cesium-infoBox-title")[2].style.padding = "0";
window.document.getElementsByClassName("cesium-infoBox-title")[2].style.height = "2rem";
window.document.getElementsByClassName("cesium-infoBox-title")[2].style.fontSize = "1.5rem";
window.document.getElementsByClassName("cesium-infoBox-title")[2].style.lineHeight = "2rem";
window.document.getElementsByClassName("cesium-infoBox-title")[2].style.textAlign = "center";
window.document.getElementsByClassName("cesium-infoBox-camera")[2].style.width = "2rem";
window.document.getElementsByClassName("cesium-infoBox-camera")[2].style.height = "2rem";
window.document.getElementsByClassName("cesium-infoBox-camera")[2].style.top = "0";
window.document.getElementsByClassName("cesium-infoBox-camera")[2].style.left = "0";
window.document.getElementsByClassName("cesium-infoBox-close")[2].style.top = "0";
window.document.getElementsByClassName("cesium-infoBox-close")[2].style.right = "0";
window.document.getElementsByClassName("cesium-infoBox-close")[2].style.width = "2rem";
window.document.getElementsByClassName("cesium-infoBox-close")[2].style.height = "2rem";
window.document.getElementsByClassName("cesium-infoBox-close")[2].style.fontSize = "2rem";
window.document.getElementsByClassName("cesium-infoBox-close")[2].style.lineHeight = "2rem";
window.document.getElementsByClassName("cesium-infoBox-close")[2].style.textAlign = "center";
window.document.getElementsByClassName("cesium-viewer-infoBoxContainer")[0].children[0].id = "step13";
window.document.getElementsByClassName("cesium-viewer-infoBoxContainer")[0].children[0].style.marginTop = "2rem";
window.document.getElementsByClassName("cesium-viewer-infoBoxContainer")[0].children[0].children[1].id = "step14";
window.document.getElementsByClassName("cesium-viewer-animationContainer")[0].style.width = "12rem";
window.document.getElementsByClassName("cesium-viewer-animationContainer")[0].style.height = "8rem";
window.document.getElementsByClassName("cesium-viewer-animationContainer")[0].children[1].style.width = "12rem";
window.document.getElementsByClassName("cesium-viewer-animationContainer")[0].children[1].style.height = "8rem";
window.document.getElementsByClassName("cesium-viewer-animationContainer")[0].children[1].children[0].children[2].children[1].id = "step6";
window.document.getElementsByClassName("cesium-viewer-animationContainer")[0].children[1].children[0].children[2].children[2].id = "step8";
window.document.getElementsByClassName("cesium-viewer-animationContainer")[0].children[1].children[0].children[2].children[3].id = "step7";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[0].children[0].children[0].children[1].children[0].style.fontSize = "6rem";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[0].children[0].children[0].children[1].children[0].id = "step1";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[0].children[0].children[1].children[1].children[0].style.fontSize = "2rem";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[0].children[0].children[1].children[1].children[0].id = "step2";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[0].style.marginBottom = "1rem";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[1].children[0].children[0].children[0].children[0].children[1].children[0].style.fontSize = "2rem";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[1].children[0].children[0].children[0].children[0].children[1].children[0].id = "step3";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[1].children[0].children[0].children[1].children[0].children[1].children[0].style.fontSize = "2rem";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[1].children[0].children[0].children[1].children[0].children[1].children[0].id = "step4";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[1].children[0].children[0].children[2].children[0].children[1].children[0].style.fontSize = "2rem";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[1].children[0].children[0].children[2].children[0].children[1].children[0].id = "step5";
window.document.getElementsByClassName("vc-navigation")[0].style.top = "4rem";
window.document.getElementsByClassName("vc-navigation")[0].style.left = "2rem";
}, 0);
let buttonDisabledClock;
buttonDisabledClock = setInterval(() => {
if (window.document.getElementsByClassName("cesium-animation-buttonDisabled")[0] !== undefined) {
window.document.getElementsByClassName("cesium-animation-buttonDisabled")[0].style.display = "none";
clearInterval(buttonDisabledClock);
}
}, 100);
};
watch(animationShow, (value) => {
setTimeout(() => {
window.document.getElementsByClassName("vc-navigation-controls")[0].children[0].children[0].children[0].children[1].children[0].style.fontSize = "6rem";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[0].children[0].children[1].children[1].children[0].style.fontSize = "2rem";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[0].style.marginBottom = "1rem";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[1].children[0].children[0].children[0].children[0].children[1].children[0].style.fontSize = "2rem";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[1].children[0].children[0].children[1].children[0].children[1].children[0].style.fontSize = "2rem";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[1].children[0].children[0].children[2].children[0].children[1].children[0].style.fontSize = "2rem";
window.document.getElementsByClassName("vc-navigation")[0].style.top = "4rem";
window.document.getElementsByClassName("vc-navigation")[0].style.left = "2rem";
window.document.getElementsByClassName("cesium-animation-buttonDisabled")[0].style.display = "none";
window.document.getElementsByClassName("cesium-viewer-animationContainer")[0].style.width = "12rem";
window.document.getElementsByClassName("cesium-viewer-animationContainer")[0].style.height = "8rem";
window.document.getElementsByClassName("cesium-viewer-animationContainer")[0].children[1].style.width = "12rem";
window.document.getElementsByClassName("cesium-viewer-animationContainer")[0].children[1].style.height = "8rem";
},0);
if (value) {
setTimeout(() => {
window.document.getElementsByClassName("vc-navigation-controls")[0].children[0].children[0].children[0].children[1].children[0].id = "step1";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[0].children[0].children[1].children[1].children[0].id = "step2";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[1].children[0].children[0].children[0].children[0].children[1].children[0].id = "step3";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[1].children[0].children[0].children[1].children[0].children[1].children[0].id = "step4";
window.document.getElementsByClassName("vc-navigation-controls")[0].children[1].children[0].children[0].children[2].children[0].children[1].children[0].id = "step5";
}, 0);
}
});
</script>
<style>

View File

@@ -5,8 +5,8 @@
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
import { computed } from 'vue';
import { useStore } from 'vuex';
const store = useStore()
const src = computed(() => './image/flag/' + store.getters["satelliteSystem/name"] + '.png')
@@ -18,6 +18,6 @@ const close = () => {
<style scoped>
img {
width: 169px;
width: 10rem;
}
</style>
</style>

View File

@@ -0,0 +1,327 @@
<template>
<button id="step21" @click="showit" class="quiz"> 知识问答 </button>
<div
id="step22"
v-if="show"
class="text-center p-10"
style="
color: #ffffff;
background-color: rgb(0 0 0 / 0.6);
border-radius: 2rem;
box-shadow: 0 0px 1rem 0px rgb(165 243 252 / 1);
position: fixed;
top: 10vh;
left: 20vw;
width: 60vw;
height: 80vh;
font-size: 2rem;
"
>
<div
@click="close"
class="bg-gray-600 hover:bg-red-600 text-3xl absolute top-0 right-0 cursor-pointer flex justify-center items-center"
style="width: 2rem; height: 2rem; border-top-right-radius: 1rem; border-bottom-left-radius: 1rem;"
>
<el-icon style="width: 2rem; height: 2rem;">
<CloseBold color="#ffffff" />
</el-icon>
</div>
<div style="font-weight: bold; color: #ffffff; font-size: 2rem; text-align: left;"> 题目:{{ topic.stem }} </div>
<el-radio-group v-model="radio">
<div class="w-full">
<el-radio id="topicA" value="a" class="align-left" style="margin-right: 0px; margin-bottom: 2rem;color: #ffffff;">
<div @click="radio = 'a'" :style="!checked ? radio == 'a' ? 'font-size: 2rem; background: radial-gradient(circle, rgba(60, 133, 211, 0.9), rgba(60, 133, 211, 0.1));' : 'font-size: 2rem;' : topic.answer == 'A' ? 'font-size: 2rem; background: radial-gradient(circle, rgba(65, 184, 131, 0.9), rgba(65, 184, 131, 0.1));' : 'font-size: 2rem; background: radial-gradient(circle, rgba(204, 62, 62, 0.9), rgba(204, 62, 62, 0.1));'">
<label>A:{{ topic.A }}</label>
<label style="color: rgba(0, 0, 0, 0)">{{ labelA }}</label>
</div>
</el-radio>
</div>
<div class="w-full">
<el-radio id="topicB" value="b" class="align-left" style="margin-right: 0px; margin-bottom: 2rem; color: #ffffff;">
<div @click="radio = 'b'" :style="!checked ? radio == 'b' ? 'font-size: 2rem; background: radial-gradient(circle, rgba(60, 133, 211, 0.9), rgba(60, 133, 211, 0.1));' : 'font-size: 2rem;' : topic.answer == 'B' ? 'font-size: 2rem; background: radial-gradient(circle, rgba(65, 184, 131, 0.9), rgba(65, 184, 131, 0.1));' : 'font-size: 2rem; background: radial-gradient(circle, rgba(204, 62, 62, 0.9), rgba(204, 62, 62, 0.1));'">
<label>B:{{ topic.B }}</label>
<label style="color: rgba(0, 0, 0, 0)">{{ labelB }}</label>
</div>
</el-radio>
</div>
<div class="w-full">
<el-radio id="topicC" value="c" class="align-left" style="margin-right: 0px; margin-bottom: 2rem; color: #ffffff;">
<div @click="radio = 'c'" :style="!checked ? radio == 'c' ? 'font-size: 2rem; background: radial-gradient(circle, rgba(60, 133, 211, 0.9), rgba(60, 133, 211, 0.1));' : 'font-size: 2rem;' : topic.answer == 'C' ? 'font-size: 2rem; background: radial-gradient(circle, rgba(65, 184, 131, 0.9), rgba(65, 184, 131, 0.1));' : 'font-size: 2rem; background: radial-gradient(circle, rgba(204, 62, 62, 0.9), rgba(204, 62, 62, 0.1));'">
<label>C:{{ topic.C }}</label>
<label style="color: rgba(0, 0, 0, 0)">{{ labelC }}</label>
</div>
</el-radio>
</div>
</el-radio-group>
<br />
<button type="text" :disabled="radio == ''" style="font-size: 2rem; background: radial-gradient(circle, rgba(60, 133, 211, 0.9), rgba(60, 133, 211, 0.1));" @click="showAnswer"> 揭晓答案 </button>
<br />
<el-text v-show="checked" style="font-weight: bold; color: #ffffff; font-size: 2rem; background: radial-gradient(circle, rgba(65, 184, 131, 0.9), rgba(65, 184, 131, 0.1));">
正确答案:{{ topic.answer }}
</el-text>
<br />
<el-text v-show="checked" :style="radio.toUpperCase() == topic.answer ? 'font-weight: bold; color: #ffffff; font-size: 2rem; background: radial-gradient(circle, rgba(223, 93, 126, 0.9), rgba(223, 93, 126, 0.1));' : 'font-weight: bold; color: #ffffff; font-size: 2rem; background: radial-gradient(circle, rgba(217, 203, 48, 0.9), rgba(217, 203, 48, 0.1));'">
{{ radio.toUpperCase() == topic.answer ? "恭喜您,答对了!" : "答错了,请再接再厉!" }}
</el-text>
<br />
<button v-show="checked" type="text" style="font-size: 2rem; background: radial-gradient(circle, rgba(60, 133, 211, 0.9), rgba(60, 133, 211, 0.1));" @click="nextTopic"> 下一题 </button>
</div>
</template>
<script setup>
import { ref, reactive, watch, onMounted } from "vue";
import { useStore } from "vuex";
import { CloseBold } from "@element-plus/icons-vue";
const store = useStore();
const satellites = store.state;
const show = ref(false);
const showFrist = ref(true);
const topic = reactive({
stem: "",
A: "",
B: "",
C: "",
answer: "",
});
const radio = ref("");
const checked = ref(false);
const labelA = ref("");
const labelB = ref("");
const labelC = ref("");
const getRandomDate = () => {
// 将日期转换为时间戳
const startDate = new Date("1997-01-01").getTime();
const endDate = new Date(new Date().toISOString().split('T')[0]).getTime();
// 生成一个随机时间戳
const randomTime = Math.random() * (endDate - startDate) + startDate;
// 将时间戳转换回日期对象
const randomDate = new Date(randomTime);
// 格式化日期为YYYY-MM-DD
const year = randomDate.getFullYear();
const month = String(randomDate.getMonth() + 1).padStart(2, '0'); // 月份从0开始,需要加1
const day = String(randomDate.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
const adder = (num) => {
let placeholder = "";
for (let index = 0; index < num; index++) {
placeholder = placeholder + "0";
}
return placeholder;
};
const makeTopic = () => {
const satelliteSystem = {
beidou: "中国北斗卫星导航",
"gps-ops": "美国全球定位",
"glo-ops": "俄罗斯格洛纳斯卫星导航",
galileo: "欧盟研制伽利略卫星导航",
};
let x = Math.floor(Math.random() * satellites.satelliteSystem.satellites.length),
y = satellites.satelliteSystem.satellites[x].description.split("<p>")[2].slice(4, -4),
z = satelliteSystem[satellites.satelliteSystem.name],
topicId,
topics = [
{
topic: `NORAD ID 是指由北美防空司令部(North American Aerospace Defense Command,缩写为 NORAD) 为地球轨道上的所有人造物体分配的唯一识别编号,用于全球范围内的追踪、监测和管理。请问,PRN为${y}的${z}系统卫星的NORAD ID是多少?`,
makeAnswer: () => {
const correctAnswer = satellites.satelliteSystem.satellites[x].description.split("<p>")[1].slice(9, -4);
const uniqueNumbers = [];
while (uniqueNumbers.length < 3) {
let num = Math.floor(Math.random() * 90000) + 10000;
if (!uniqueNumbers.includes(num) && num !== correctAnswer) {
uniqueNumbers.push(num);
}
}
topic.A = uniqueNumbers[0];
topic.B = uniqueNumbers[1];
topic.C = uniqueNumbers[2];
const answerId = ["A", "B", "C"];
const answer = answerId[Math.floor(Math.random() * 3)];
topic.answer = answer;
topic[answer] = correctAnswer;
},
},
{
topic: `PRN为${y}的${z}系统卫星的发射日期是:`,
// 发射日期:2018-03-29 /1997
makeAnswer: () => {
const correctAnswer = satellites.satelliteSystem.satellites[x].description.split("<p>")[4].slice(5, -4);
const uniqueNumbers = [];
while (uniqueNumbers.length < 3) {
let num = getRandomDate();
if (!uniqueNumbers.includes(num) && num !== correctAnswer) {
uniqueNumbers.push(num);
}
}
topic.A = uniqueNumbers[0];
topic.B = uniqueNumbers[1];
topic.C = uniqueNumbers[2];
const answerId = ["A", "B", "C"];
const answer = answerId[Math.floor(Math.random() * 3)];
topic.answer = answer;
topic[answer] = correctAnswer;
},
},
{
topic: `PRN为${y}的${z}系统卫星的运行周期是:`,
makeAnswer: () => {
const correctAnswer = satellites.satelliteSystem.satellites[x].description.split("<p>")[5].slice(9, -4);
const uniqueNumbers = [];
while (uniqueNumbers.length < 3) {
let num = ((Math.random() * 1350) + 90).toFixed(2);
if (!uniqueNumbers.includes(num) && num !== correctAnswer) {
uniqueNumbers.push(num);
}
}
topic.A = uniqueNumbers[0];
topic.B = uniqueNumbers[1];
topic.C = uniqueNumbers[2];
const answerId = ["A", "B", "C"];
const answer = answerId[Math.floor(Math.random() * 3)];
topic.answer = answer;
topic[answer] = correctAnswer;
},
},
{
topic: `PRN为${y}的${z}系统卫星的轨道面倾角是多少度:`,
makeAnswer: () => {
const correctAnswer = satellites.satelliteSystem.satellites[x].description.split("<p>")[6].slice(9, -4);
const uniqueNumbers = [];
while (uniqueNumbers.length < 3) {
let num = ((Math.random() * 90)).toFixed(2);
if (!uniqueNumbers.includes(num) && num !== correctAnswer) {
uniqueNumbers.push(num);
}
}
topic.A = uniqueNumbers[0];
topic.B = uniqueNumbers[1];
topic.C = uniqueNumbers[2];
const answerId = ["A", "B", "C"];
const answer = answerId[Math.floor(Math.random() * 3)];
topic.answer = answer;
topic[answer] = correctAnswer;
},
},
{
topic: `PRN为${y}的${z}系统卫星的远地点高度是多少公里:`,
makeAnswer: () => {
const correctAnswer = satellites.satelliteSystem.satellites[x].description.split("<p>")[7].slice(10, -4);
const uniqueNumbers = [];
while (uniqueNumbers.length < 3) {
let num = Math.floor(Math.random() * 34586) + 1200;
if (!uniqueNumbers.includes(num) && num !== correctAnswer) {
uniqueNumbers.push(num);
}
}
topic.A = uniqueNumbers[0];
topic.B = uniqueNumbers[1];
topic.C = uniqueNumbers[2];
const answerId = ["A", "B", "C"];
const answer = answerId[Math.floor(Math.random() * 3)];
topic.answer = answer;
topic[answer] = correctAnswer;
},
},
{
topic: `PRN为${y}的${z}系统卫星的近地点高度是多少公里:`,
makeAnswer: () => {
const correctAnswer = satellites.satelliteSystem.satellites[x].description.split("<p>")[8].slice(10, -4);
const uniqueNumbers = [];
while (uniqueNumbers.length < 3) {
let num = Math.floor(Math.random() * 34586) + 1200;
if (!uniqueNumbers.includes(num) && num !== correctAnswer) {
uniqueNumbers.push(num);
}
}
topic.A = uniqueNumbers[0];
topic.B = uniqueNumbers[1];
topic.C = uniqueNumbers[2];
const answerId = ["A", "B", "C"];
const answer = answerId[Math.floor(Math.random() * 3)];
topic.answer = answer;
topic[answer] = correctAnswer;
},
},
];
topicId = Math.floor(Math.random() * topics.length);
topic.stem = topics[topicId].topic;
topics[topicId].makeAnswer();
topic.A = String(topic.A);
topic.B = String(topic.B);
topic.C = String(topic.C);
labelA.value = ""
labelB.value = ""
labelC.value = ""
if (topic.A.length > topic.B.length && topic.A.length > topic.C.length) {
labelB.value = adder(topic.A.length - topic.B.length);
labelC.value = adder(topic.A.length - topic.C.length);
} else if (topic.B.length > topic.A.length && topic.B.length > topic.C.length) {
labelA.value = adder(topic.B.length - topic.A.length);
labelC.value = adder(topic.B.length - topic.C.length);
} else if (topic.C.length > topic.A.length && topic.C.length > topic.B.length) {
labelA.value = adder(topic.C.length - topic.A.length);
labelB.value = adder(topic.C.length - topic.B.length);
} else if (topic.A.length < topic.B.length && topic.B.length == topic.C.length) {
labelA.value = adder(topic.B.length - topic.A.length);
} else if (topic.B.length < topic.A.length && topic.A.length == topic.C.length) {
labelB.value = adder(topic.A.length - topic.B.length);
} else if (topic.C.length < topic.A.length && topic.A.length == topic.B.length) {
labelC.value = adder(topic.A.length - topic.C.length);
}
};
const showit = () => {
if (showFrist.value) {
showFrist.value = false;
makeTopic();
}
show.value = true;
};
const showAnswer = () => {
checked.value = true;
};
const nextTopic = () => {
makeTopic();
checked.value = false;
radio.value = '';
};
const close = () => {
show.value = false;
checked.value = false;
radio.value = '';
};
defineExpose({ show });
</script>
<style scoped>
.quiz {
font-size: 1rem;
color: rgba(255, 255, 255, 0);
background-color: #ffffff;
width: 1rem;
height: 2rem;
line-height: 2rem;
overflow: hidden;
box-shadow: 0 0px 1rem 0px rgb(165 243 252 / 1);
position: fixed;
bottom: 20rem;
right: 0;
border-top-left-radius: 1rem;
border-bottom-left-radius: 1rem;
}
.quiz:hover {
width: 6rem;
color: #000000;
background-color: rgb(165 243 252 / 1);
}
</style>

View File

@@ -0,0 +1,58 @@
<template>
<div id="step20">
<select class="cesium-button" v-model="selected">
<option v-for="(value, key) in option_dic" :key="key" :value="key">
{{ value }}
</option>
</select>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted } from "vue";
import { useStore } from "vuex";
const store = useStore();
const selected = ref("");
const firstLoad = ref(true);
const emit = defineEmits(["handleSatelliteSystemSelect", "handleElementsFly"]);
defineExpose({ selected });
watch(selected, (value) => {
if (value !== "custom") {
store.dispatch("satelliteSystem/setName", { name: value });
}
window.allowUpdate = true;
emit("handleSatelliteSystemSelect", value);
if (!firstLoad.value) {
let viewName = window.document.getElementsByClassName("cesium-toolbar-button")[1]
.classList[2];
if (viewName == "cesium-sceneModePicker-button2D") {
window.document.getElementsByClassName("cesium-toolbar-button")[4].click();
setTimeout(() => {
window.document.getElementsByClassName("cesium-toolbar-button")[3].click();
emit("handleElementsFly");
}, 0);
}
}
});
onMounted(
() => (
(selected.value = Object.keys(option_dic)[0]),
setTimeout(() => {
firstLoad.value = false;
}, 0)
)
);
const option_dic = {
beidou: "中国北斗卫星导航系统",
"gps-ops": "美国全球定位系统",
"glo-ops": "俄罗斯格洛纳斯卫星导航系统",
galileo: "欧盟研制伽利略卫星导航系统",
custom: "自定义卫星导航系统",
};
</script>

View File

@@ -3,9 +3,10 @@
</template>
<script lang="ts" setup>
import { computed } from "vue";
import { ref, computed } from "vue";
import { useStore } from "vuex";
import entity from "../api/entity";
import entity from "../../api/entity.js";
// 卫星轨道数据处理
let vcViewerInstance: any = null;
@@ -63,9 +64,10 @@ const onDataSourceReady = ({ viewer, cesiumObject }) => {
if (!this.show) {
return;
}
for (var t = this._primitives, i = 0; i < t.length; ++i) {
t[i].update(frameState);
}
var t = this._primitives;
t.forEach((element) => {
element.update(frameState);
});
}
}
@@ -2227,6 +2229,55 @@ const onDataSourceReady = ({ viewer, cesiumObject }) => {
i.length = s;
}
// 视锥剔除
const isPointInViewport = (point) => {
const { planes: planceVec4 }= viewer.scene.frameState.cullingVolume;
// 获取视锥体的四个平面
const planes =[];
for (let i=0;i<planceVec4.length; i++){
const tempPlane = new Cesium.Plane(new Cesium.Cartesian3(1.0,0.0, 0.0), 0.0);
planes.push(Cesium.Plane.fromCartesian4(planceVec4[i], tempPlane));
}
// 检查点是否在每个平面内部
for(let i=0;i<planes.length; i++){
const plane = planes[i];
if(Cesium.Plane.getPointDistance(plane,point)<0){
// 点在某个平面外
return false;
}
}
//点在视口内
return true;
};
// 调整 Polyline 密度
const adjustPolylineDensity = (polyline) => {
const minSegmentLength=20000; //最小段长度设置
let positions =polyline._positions;
let newPositions=[];
for(let i=0;i<positions.length-1;i++){
let distance = Cesium.Cartesian3.distance(positions[i], positions[i + 1]);
if(distance>minSegmentLength){
if(isPointInViewport(positions[i])) {
newPositions.push(positions[i]);
}
// 如果当前段太长,可以考虑添加额外的位置点
if(distance>minSegmentLength*3){
let midPoint = Cesium.Cartesian3.lerp(positions[i], positions[i + 1], 0.5, new Cesium.Cartesian3());
if(isPointInViewport(midPoint)){
newPositions.push(midPoint);
}
}
}
}
if(positions[positions.length-1]){
if(isPointInViewport(positions[positions.length-1])){
newPositions.push(positions[positions.length-1]);
}
}
return newPositions;
};
var POSITION_SIZE_INDEX = Cesium.Polyline.POSITION_SIZE_INDEX,
MATERIAL_INDEX = Cesium.Polyline.MATERIAL_INDEX,
MATERIAL_INDEX = Cesium.Polyline.MATERIAL_INDEX,
@@ -2277,22 +2328,22 @@ const onDataSourceReady = ({ viewer, cesiumObject }) => {
e.commandList.push(item);
});
this.myinterver += 1;
var allowindex = 96;
if (viewer._clockViewModel.multiplier >= 100000) {
allowindex = 3;
} else if (50000 <= viewer._clockViewModel.multiplier < 100000) {
allowindex = 6;
} else if (25000 <= viewer._clockViewModel.multiplier < 50000) {
allowindex = 12;
} else if (12000 <= viewer._clockViewModel.multiplier < 25000) {
allowindex = 24;
} else if (6000 <= viewer._clockViewModel.multiplier < 12000) {
allowindex = 48;
} else if (3000 <= viewer._clockViewModel.multiplier < 6000) {
allowindex = 64;
} else if (1000 <= viewer._clockViewModel.multiplier < 3000) {
allowindex = 84;
}
var allowindex = 1000;
// if (viewer._clockViewModel.multiplier >= 100000) {
// allowindex = 3;
// } else if (50000 <= viewer._clockViewModel.multiplier < 100000) {
// allowindex = 6;
// } else if (25000 <= viewer._clockViewModel.multiplier < 50000) {
// allowindex = 12;
// } else if (12000 <= viewer._clockViewModel.multiplier < 25000) {
// allowindex = 24;
// } else if (6000 <= viewer._clockViewModel.multiplier < 12000) {
// allowindex = 48;
// } else if (3000 <= viewer._clockViewModel.multiplier < 6000) {
// allowindex = 64;
// } else if (1000 <= viewer._clockViewModel.multiplier < 3000) {
// allowindex = 84;
// }
if (this.myinterver == allowindex) {
this.myinterver = 0;
window.allowUpdate = true;
@@ -2314,7 +2365,6 @@ const onDataSourceReady = ({ viewer, cesiumObject }) => {
if (this._createVertexArray || computeNewBuffersUsage(this))
createVertexArrays(this, i, r);
else if (this._polylinesUpdated) {
console.log("othersetp");
var a = this._polylinesToUpdate;
if (this._mode !== SceneMode$1.SCENE3D)
a.forEach((element) => {
@@ -2417,18 +2467,22 @@ const onDataSourceReady = ({ viewer, cesiumObject }) => {
viewer.scene.primitives._primitives.splice(index, 2);
}
if (element.constructor.name == "PolylineCollection") {
if (element._mode == 2) {
element._mode = 1;
}
mypolylines = element;
}
// 打包后进行系统切换后对象名称发生改变 CustomPolyline --> xe
if(element.constructor.name == "xe"){
// 打包后进行系统切换以及先进行视图切换后进行系统切换时对象名称可能会发生改变,具体名称需要在重装或更新打包依赖库后重新获取
// 2025/12/30 打包后直接进行系统切换时对象名称发生改变 CustomPolyline --> jn
// 2025/12/30 打包后先进行视图切换(3D -> 2D)后进行系统切换时对象名称发生改变 jn --> gt
// 2025/12/30 打包后先进行视图切换(3D -> 哥伦布图)后进行系统切换时对象名称发生改变 jn --> gt
// console.log(element.constructor.name);
if (element.constructor.name == "jn" || element.constructor.name == "gt") {
viewer.scene.primitives._primitives.splice(index, 2);
}
// 打包后系统切换后对象名称发生改变 PolylineCollection --> Ie
if (element.constructor.name == "Ie") {
viewer.scene.primitives._primitives.splice(index, 1);
// 2025/12/30 打包后直接进行系统切换时对象名称未发生改变 PolylineCollection --> PolylineCollection
// 2025/12/30 打包后先进行视图切换(3D -> 2D)后进行系统切换时对象名称未发生改变 PolylineCollection --> PolylineCollection
// 2025/12/30 打包后先进行视图切换(3D -> 哥伦布图)后进行系统切换时对象名称未发生改变 PolylineCollection --> PolylineCollection
if (element.constructor.name == "PolylineCollection") {
// 4K适配版本后将造成列表第一颗卫星模型不显示的bug,故注释掉
// viewer.scene.primitives._primitives.splice(index, 1);
}
});
viewer.scene.primitives.show = false;
@@ -2440,7 +2494,7 @@ const onDataSourceReady = ({ viewer, cesiumObject }) => {
viewer.scene.primitives.show = true;
}, 0);
cesiumObject.entities.values.forEach((element) => {
cesiumObject.entities.values.forEach((element, index) => {
if (element.model !== undefined) {
// 调整卫星名称显示位置
element.label.pixelOffset._value = new Cesium.Cartesian2(12, 21);
@@ -2458,14 +2512,11 @@ const onDataSourceReady = ({ viewer, cesiumObject }) => {
element.model.imageBasedLightingFactor = new Cesium.Cartesian2(20, 1);
if (element.description._value.indexOf("II-F") !== -1) {
element.model.uri._value = GPSIIFModel.model._resource;
}
if (element.description._value.indexOf("II-R") !== -1) {
} else if (element.description._value.indexOf("II-R") !== -1) {
element.model.uri._value = GPSIIRModel.model._resource;
}
if (element.description._value.indexOf("III-A") !== -1) {
} else if (element.description._value.indexOf("III-A") !== -1) {
element.model.uri._value = GPSIIIAModel.model._resource;
}
if (element.description._value.indexOf("IIR-M") !== -1) {
} else if (element.description._value.indexOf("IIR-M") !== -1) {
element.model.uri._value = GPSIIRMModel.model._resource;
}
} else if (element.properties.satelliteType._value == "GLONASS") {
@@ -2473,8 +2524,7 @@ const onDataSourceReady = ({ viewer, cesiumObject }) => {
element.model.imageBasedLightingFactor = new Cesium.Cartesian2(10, 1);
if (element.description._value.indexOf("GLO-K") !== -1) {
element.model.uri._value = GLONASSGLOKModel.model._resource;
}
if (element.description._value.indexOf("GLO-M") !== -1) {
} else if (element.description._value.indexOf("GLO-M") !== -1) {
element.model.uri._value = GLONASSGLOMModel.model._resource;
}
} else if (element.properties.satelliteType._value == "Galileo") {
@@ -2488,10 +2538,20 @@ const onDataSourceReady = ({ viewer, cesiumObject }) => {
});
entity.load(cesiumObject.entities.values);
// 显示卫星导航系统的科普信息
viewer.selectedEntity = entity.find("Constellation");
// 当用户操作或者相机移动时调用调整 Polyline 密度的函数
viewer.camera.moveEnd.addEventListener(() => {
let entities=viewer.entities.values;
for(let i=0;i<entities.length; i++) {
if (entities[i].polyline) {
entities[i].polyline._positions = adjustPolylineDensity(entities[i]);
}
}
});
// 设置相机初始位置
const allfly = (x, y, z, h, p, r) => {
viewer.camera.flyTo({

View File

@@ -0,0 +1,183 @@
<template>
<div>
<div id="satellite-table-container" class="text-white absolute bottom-0 text-center">
<SvgButton id="step9" @click="show = !show" />
<table ref="satelliteTable" v-show="show" class="text-center">
<thead class="text-2xl">
<th
class="py-3 bg-blue-900 border border-gray-900"
v-for="(label, index) in ['序号', '卫星名称', '显示/隐藏', '跟踪']"
:key="label"
:style="{ width: index == 1 ? '37.5vw' : '12.5vw' }"
>
<div style="display: flex; justify-content: center; align-items: center;">
<div style="margin-right: 0.5rem;">
{{ label }}
</div>
<input
id="step10"
v-if="label == '显示/隐藏'"
:indeterminate="someShow"
:checked="allShow"
type="checkbox"
class="w-6 h-6 cursor-pointer"
@change="allOnShowStateCheckboxChanged"
/>
</div>
</th>
</thead>
<tbody class="text-lg odd:bg-gray-50">
<tr v-for="({ id, show }, index) in satellites" :key="id">
<td class="border border-gray-900"> {{ index + 1 }} </td>
<td id="satelliteId" class="border border-gray-900">{{ id }}</td>
<td class="border border-gray-900">
<div style="display: flex; justify-content: center; align-items: center;">
<input
id="step11"
:checked="ashow"
type="checkbox"
class="w-6 h-6 cursor-pointer"
@change="onShowStateCheckboxChanged(id)"
/>
</div>
</td>
<td class="border border-gray-900">
<button id="step12" @click="onTrackButtonClicked(id)">
<TrackSvg />
</button>
</td>
</tr>
</tbody>
</table>
<div class="text-white bg-blue-900"> {{ softwareName }} </div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, watch, onMounted } from "vue";
import { useStore } from "vuex";
import SvgButton from "../SvgButton.vue";
import TrackSvg from "../TrackSvg.vue";
const show = ref(false);
const ashow = ref(true);
const someShow = ref(false);
const allShow = ref(true);
const showarr = ref({});
const softwareName = ref("卫星科普互动展示系统V1.0");
const store = useStore();
const satellites = computed(() => store.getters["satelliteSystem/satellites"]);
const onTrackButtonClicked = (id) => {
store.dispatch("satelliteSystem/track", id);
};
const onShowStateCheckboxChanged = (id) => {
window.allowUpdate = true;
window.toallowUpdate = true;
someShow.value = true;
showarr.value[id] = !showarr.value[id];
store.dispatch("satelliteSystem/toggleShow", id);
let ashowsum = 0;
satellites.value.forEach((item, index) => {
if (showarr.value[item.id] == true) {
ashowsum = ashowsum + 1;
}
});
if (ashowsum == 0) {
someShow.value = false;
} else if (ashowsum == satellites.value.length) {
someShow.value = false;
}
};
const allOnShowStateCheckboxChanged = () => {
if (allShow.value) {
allShow.value = false;
ashow.value = false;
} else {
allShow.value = true;
ashow.value = true;
}
satellites.value.forEach((element) => {
if (showarr.value[element.id] !== ashow.value) {
onShowStateCheckboxChanged(element.id);
}
onShowStateCheckboxChanged(element.id);
});
someShow.value = false;
};
// 表格数据更新时,滚动条回到最顶部
const satelliteTable = ref(null);
watch(satellites, () => {
ashow.value = true;
someShow.value = false;
allShow.value = true;
showarr.value = {};
satellites.value.forEach((item) => {
showarr.value[item.id] = false;
});
satelliteTable.value.scrollTop = 0;
});
const changeName = (newName) => {
if (newName !== undefined) {
softwareName.value = newName;
}
}
defineExpose({ show, onTrackButtonClicked });
onMounted(() => {
if ("ipcRenderer" in window) {
ipcRenderer.receive("softwareName", changeName);
ipcRenderer.send("APP_MOUNTED");
}
});
</script>
<style scoped>
#satellite-table-container {
left: 12.5vw;
right: 12.5vw;
}
table {
display: block;
height: 18rem;
overflow: auto;
}
tr:nth-child(odd) {
background-color: rgb(17, 24, 39);
}
tr:nth-child(even) {
background-color: rgb(31, 41, 55);
}
td {
padding: 0.25rem 0;
}
::-webkit-scrollbar {
width: 0.6rem;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgb(0 0 0 / 30%);
background-color: #4fbee2;
}
::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgb(0 0 0 / 30%);
background-color: #111827;
}
</style>

View File

@@ -0,0 +1,222 @@
<template>
<div id="step15" class="bg-white" style="width: 30vw; height: 44vh; z-index: 99;">
<div
class="text-3xl absolute bottom-2 right-2 cursor-pointer flex justify-center items-center"
style="width: 2rem; height: 6rem"
>
<div
class="bg-gray-600"
style="
width: 2rem;
height: 2rem;
border-top-left-radius: 50%;
border-top-right-radius: 50%;
"
>
<div
id="step16"
class="bg-gray-600 hover:bg-red-600"
style="width: 2rem; height: 2rem; border-radius: 50%"
>
<el-icon @click="plus">
<Plus color="#ffffff" />
</el-icon>
</div>
</div>
<div class="bg-gray-600" style="width: 2rem; height: 2rem">
<div
id="step17"
class="hover:bg-red-600"
style="width: 2rem; height: 2rem; border-radius: 50%"
>
<el-icon @click="refresh">
<RefreshRight color="#ffffff" />
</el-icon>
</div>
</div>
<div
class="bg-gray-600"
style="
width: 2rem;
height: 2rem;
border-bottom-left-radius: 50%;
border-bottom-right-radius: 50%;
"
>
<div
id="step18"
class="bg-gray-600 hover:bg-red-600"
style="width: 2rem; height: 2rem; border-radius: 50%;"
>
<el-icon @click="minus">
<Minus color="#ffffff" />
</el-icon>
</div>
</div>
</div>
<div
@click="closeModel"
class="bg-gray-600 hover:bg-red-600 text-3xl absolute top-0 right-0 cursor-pointer flex justify-center items-center"
style="width: 2rem; height: 2rem"
>
<el-icon style="width: 2rem; height: 2rem">
<CloseBold color="#ffffff" />
</el-icon>
</div>
<div
id="threeCanvas"
ref="canvasContainer"
style="width: 30vw; height: 44vh; z-index: 99; margin: 0; overflow: hidden;"
></div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { CloseBold, Plus, RefreshRight, Minus } from "@element-plus/icons-vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import sky from "../../assets/sky.jpg";
const canvasContainer = ref(null);
const model = ref(null);
let scene, camera, renderer, clonedModel, controls;
// 初始化场景
const initScene = () => {
const threeCanvas = document.getElementById("threeCanvas");
// 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0x666666);
// 创建相机(透视相机)
camera = new THREE.PerspectiveCamera(75, threeCanvas.clientWidth / threeCanvas.clientHeight, 0.1, 1000);
camera.position.z = 5;
// 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(threeCanvas.clientWidth, threeCanvas.clientHeight);
// 挂载到 DOM
canvasContainer.value.appendChild(renderer.domElement);
//创建鼠标控制器
controls = new OrbitControls(camera, renderer.domElement);
// 添加灯光
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 1, 1);
scene.add(light);
const light1 = new THREE.DirectionalLight(0xffffff, 1);
light1.position.set(-1, 1, 1);
scene.add(light1);
const light2 = new THREE.DirectionalLight(0xffffff, 1);
light2.position.set(1, -1, 1);
scene.add(light2);
const light3 = new THREE.DirectionalLight(0xffffff, 1);
light3.position.set(1, 1, -1);
scene.add(light3);
const light4 = new THREE.DirectionalLight(0xffffff, 1);
light4.position.set(-1, -1, 1);
scene.add(light4);
const light5 = new THREE.DirectionalLight(0xffffff, 1);
light5.position.set(1, -1, -1);
scene.add(light5);
const light6 = new THREE.DirectionalLight(0xffffff, 1);
light6.position.set(-1, 1, -1);
scene.add(light6);
const light7 = new THREE.DirectionalLight(0xffffff, 1);
light7.position.set(-1, -1, -1);
scene.add(light7);
// 创建半球体几何体
const skyGeometry = new THREE.SphereGeometry(66, 66, 66); // 半径、宽度分段、高度分段
// 加载天空纹理
const skyTextureLoader = new THREE.TextureLoader();
const skyTexture = skyTextureLoader.load(sky);
// 创建材质,并应用天空纹理
const skyMaterial = new THREE.MeshBasicMaterial({
map: skyTexture,
side: THREE.BackSide,
}); // 使用背面显示纹理
// 创建天空穹顶网格
const skyDome = new THREE.Mesh(skyGeometry, skyMaterial);
// 将天空穹顶添加到场景中
scene.add(skyDome);
};
const loadModel = () => {
setTimeout(() => {
if (clonedModel !== undefined) {
scene.remove(clonedModel);
}
clonedModel = model.value.model.clone();
scene.add(clonedModel);
}, 0);
};
// 动画循环
const animate = () => {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
};
// 初始球体半径
// r1 = Math.sqrt(x * x + y * y + z * z)
// 相机移动后的球体半径为 r2 ,假设相机向球心移动距离为 1 单位长度
// r1 - r2 = 1
// 调整相机移动距离 d 以获取相机移动后的球体半径 r2
// r2 = r1 - d
// 坐标值与球体半径比例是固定的,初始坐标值为 x1,相机移动后的坐标值为 x2
// x1 / r1 = x2 / r2
// x2 = x1 / r1 * r2
const plus = () => {
const moveValue = 1;
const r1 = Math.sqrt(camera.position.x * camera.position.x + camera.position.y * camera.position.y + camera.position.z * camera.position.z);
const r2 = r1 - moveValue;
const x = (camera.position.x / r1) * r2;
const y = (camera.position.y / r1) * r2;
const z = (camera.position.z / r1) * r2;
camera.position.set(x, y, z);
};
const refresh = () => {
camera.position.set(0, 0, 5);
};
const minus = () => {
const moveValue = 1;
const r1 = Math.sqrt(camera.position.x * camera.position.x + camera.position.y * camera.position.y + camera.position.z * camera.position.z);
const r2 = r1 + moveValue;
const x = (camera.position.x / r1) * r2;
const y = (camera.position.y / r1) * r2;
const z = (camera.position.z / r1) * r2;
camera.position.set(x, y, z);
};
defineExpose({ model, loadModel });
onMounted(() => {
initScene();
loadModel();
animate();
});
const emit = defineEmits(["showModel"]);
const closeModel = () => {
emit("showModel", false, "", "");
};
</script>
<style scoped></style>

View File

@@ -0,0 +1,387 @@
<template>
<el-dropdown class="text-center felx justify-end items-center">
<button class="tour">
系统导航
<el-icon
style="width: 1rem; height: 1rem; font-size: 1rem"
class="el-icon--right"
><arrow-down
/></el-icon>
</button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="(step, index) in tourSteps"
@click="tour(index)"
>
<el-link
style="width: 8rem; height: 1.8rem; font-weight: bold; font-size: 1rem;"
:underline="false"
type="primary"
>
{{ step.tourName }}
</el-link>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-tour v-for="step in tourSteps" v-model="step.tourOpen">
<template #default>
<el-tour-step
v-for="(stepData, index) in step.tourData"
:target="stepData.target"
:prev-button-props="{
children: '上一步',
onClick: stepData.handlePrevClick,
}"
:next-button-props="{
children: index == step.tourData.length - 1 ? '完成' : '下一步',
onClick: stepData.handleNextClick,
}"
>
<template #header>
<div style="color: blueviolet">{{ stepData.title }}</div>
</template>
<template #default>
<div style="color: blueviolet">{{ stepData.content }}</div>
</template>
</el-tour-step>
</template>
<template #indicators="{ current, total }">
<span>{{ current + 1 }} / {{ total }}</span>
</template>
</el-tour>
</template>
<script setup>
import { ref } from "vue";
import { ArrowDown } from "@element-plus/icons-vue";
const emit = defineEmits(["handleNext"]);
const tourSteps = ref([
{
tourName: "罗盘导航",
tourOpen: false,
tourData: [
{
target: "#step1",
title: "罗盘外部的作用",
content: "用于视图的方向(北、南、东、西)控制",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
{
target: "#step2",
title: "罗盘内部的作用",
content: "用于视图的旋转控制",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
{
target: "#step3",
title: "放大按钮的作用",
content: "用于放大视图",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
{
target: "#step4",
title: "复位按钮的作用",
content: "用于定位和复位视图",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
{
target: "#step5",
title: "缩小按钮的作用",
content: "用于缩小视图",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
],
},
{
tourName: "动画控制导航",
tourOpen: false,
tourData: [
{
target: "#animation_pathPointer",
title: "速度调节指针的作用",
content: "用于控制动画速度",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
{
target: "#step6",
title: "反转按钮的作用",
content: "用于反转动画",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
{
target: "#step7",
title: "暂停按钮的作用",
content: "用于暂停动画",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
{
target: "#step8",
title: "播放按钮的作用",
content: "用于播放动画",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
],
},
{
tourName: "卫星列表导航",
tourOpen: false,
tourData: [
{
target: "#step9",
title: "卫星列表开关展示/隐藏按钮的作用",
content: "用于展示或隐藏卫星列表",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
{
target: "#step10",
title: "显示/隐藏所有卫星勾选框的作用",
content: "用于显示/隐藏所有卫星的运行轨迹和卫星模型",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
{
target: "#step11",
title: "显示/隐藏选中卫星勾选框的作用",
content: "用于显示/隐藏选中卫星的运行轨迹和卫星模型",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
// 触发查看卫星的事件
emit("handleNext", "#step12");
},
},
{
target: "#step12",
title: "查看卫星详情按钮的作用",
content: "用于查看卫星的详细信息并跟踪该卫星的视角",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
{
target: "#step13",
title: "卫星详情信息",
content: "卫星的详细信息",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
{
target: "#step14",
title: "跟踪/解除卫星视角按钮的作用",
content: "用于跟踪或解除跟随卫星运动的视角,并在左侧窗口中展示卫星的模型",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
],
},
{
tourName: "卫星模型展示导航",
tourOpen: false,
tourData: [
{
target: "#step15",
title: "卫星模型展示区域的作用",
content: "用于展示更为精细的卫星模型",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
{
target: "#step16",
title: "放大按钮的作用",
content: "用于放大视图",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
{
target: "#step17",
title: "复位按钮的作用",
content: "用于复位视图",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
{
target: "#step18",
title: "缩小按钮的作用",
content: "用于缩小视图",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
],
},
{
tourName: "视图与卫星系统切换",
tourOpen: false,
tourData: [
{
target: "#step19",
title: "视图切换按钮的作用",
content: "用于切换地球的3D,2D和哥伦布视图",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
{
target: "#step20",
title: "卫星系统切换按钮的作用",
content: "用于切换不同的卫星系统,包括北斗和其他国际卫星系统",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
],
},
{
tourName: "知识问答导航",
tourOpen: false,
tourData: [
{
target: "#step21",
title: "趣味问答按钮的作用",
content: "用于显示趣味问答区域",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
emit("handleNext", "#step22");
},
},
{
target: "#step22",
title: "趣味问答区域的作用",
content: "用于展示与卫星相关的趣味问答和相关知识",
handlePrevClick: () => {
// console.log("prevEvent");
},
handleNextClick: () => {
// console.log("nextEvent");
},
},
],
},
]);
const tour = (index) => {
emit("handleNext", tourSteps.value[index].tourData[0].target);
setTimeout(() => {
tourSteps.value[index].tourOpen = true;
}, 0);
};
</script>
<style scoped>
.tour {
font-size: 1rem;
color: rgba(255, 255, 255, 0);
background-color: #ffffff;
width: 1rem;
height: 2rem;
line-height: 2rem;
overflow: hidden;
box-shadow: 0 0px 1rem 0px rgb(165 243 252 / 1);
position: fixed;
bottom: 16rem;
right: 0;
border-top-left-radius: 1rem;
border-bottom-left-radius: 1rem;
}
.tour:hover {
width: 10rem;
color: #000000;
background-color: rgb(165 243 252 / 1);
}
</style>

View File

@@ -1,29 +0,0 @@
<template>
<div>
<select class="cesium-button" v-model="selected">
<option
v-for = "(value, key) in option_dic"
:key = "key"
:value = "key">
{{ value }}
</option>
</select>
</div>
</template>
<script lang="ts" setup>
import { useStore } from 'vuex'
import { ref, watch, onMounted } from 'vue'
const store = useStore()
const selected = ref('')
watch(selected, (value) => {store.dispatch('satelliteSystem/setName', { name: value });window.allowUpdate = true;})
onMounted(() => selected.value = Object.keys(option_dic)[0])
const option_dic = {
'beidou' : '中国北斗卫星导航系统',
'gps-ops' : '美国全球定位系统',
'glo-ops' : '俄罗斯格洛纳斯卫星导航系统',
'galileo' : '欧盟研制伽利略卫星导航系统'
}
</script>

View File

@@ -1,161 +0,0 @@
<template>
<div>
<div id="satellite-table-container" class="text-white absolute bottom-0 text-center">
<button @click="show = !show" class="rounded border-blue-900 bg-blue-900">
<svg v-if="show" xmlns="http://www.w3.org/2000/svg" width="600" height="30" fill="none" viewBox="-10 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M -200 8 l 200 10 200 -10 m -400 -6 l 200 10 200 -10">
<animate attributeName="stroke" attributeType="XML"
from="#ffffff" to="#666666"
begin="0s" dur="1.5s"
fill="remove" repeatCount="indefinite"/>
</path>
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" width="600" height="30" fill="none" viewBox="-10 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M -200 20 l 200 -10 200 10 m -400 -6 l 200 -10 200 10">
<animate attributeName="stroke" attributeType="XML"
from="#ffffff" to="#666666"
begin="0s" dur="1.5s"
fill="remove" repeatCount="indefinite"/>
</path>
</svg>
</button>
<table ref="satelliteTable" v-show="show" class="text-center">
<thead class="text-2xl">
<th class="py-3 bg-blue-900 border border-gray-900"
v-for="label, index in ['序号', '卫星名称', '显示/隐藏', '跟踪']"
:key="label"
:style="{width: index == 1 ? '37.5vw' : '12.5vw'}">
{{ label }}
<input v-if="label == '显示/隐藏'" :indeterminate="someshow" :checked="allshow" type="checkbox" class="w-6 h-6 cursor-pointer" @change="allOnShowStateCheckboxChanged" />
</th>
</thead>
<tbody class="text-lg odd:bg-gray-50">
<tr v-for="{ id, show }, index in satellites" :key="id">
<td class="border border-gray-900"> {{ index + 1 }} </td>
<td class="border border-gray-900"> {{ id }} </td>
<td class="border border-gray-900">
<input :checked="ashow" type="checkbox" class="w-6 h-6 cursor-pointer" @change="onShowStateCheckboxChanged(id)" />
</td>
<td class="border border-gray-900">
<button @click="onTrackButtonClicked(id)">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
</button>
</td>
</tr>
</tbody>
<div class="text-white bg-blue-900">
卫星科普互动展示系统V1.0
</div>
</table>
</div>
</div>
</template>
<script lang="ts" setup>
import { useStore } from 'vuex'
import { ref, computed, watch } from 'vue'
const show = ref(false)
const ashow = ref(true)
const someshow = ref(false)
const allshow = ref(true)
const showarr = ref({})
const store = useStore()
const satellites = computed(() => store.getters["satelliteSystem/satellites"])
const onTrackButtonClicked = (id) => {
store.dispatch('satelliteSystem/track', id)
}
const onShowStateCheckboxChanged = (id) => {
window.allowUpdate = true;
window.toallowUpdate = true;
someshow.value = true
showarr.value[id] = !showarr.value[id]
store.dispatch('satelliteSystem/toggleShow', id)
let ashowsum = 0
satellites.value.forEach((item,index) => {
if(showarr.value[item.id] == true){
ashowsum = ashowsum + 1
}
})
if(ashowsum == 0){
someshow.value = false
}else if(ashowsum == satellites.value.length){
someshow.value = false
}
}
const allOnShowStateCheckboxChanged = () => {
if(allshow.value){
allshow.value = false
ashow.value = false
}else{
allshow.value = true
ashow.value = true
}
satellites.value.forEach(element => {
if(showarr.value[element.id] !== ashow.value){
onShowStateCheckboxChanged(element.id)
}
onShowStateCheckboxChanged(element.id)
})
someshow.value = false
}
// 表格数据更新时,滚动条回到最顶部
const satelliteTable = ref(null)
watch(satellites, () => {
ashow.value = true
someshow.value = false
allshow.value = true
showarr.value = {}
satellites.value.forEach(item => {
showarr.value[item.id] = false
})
satelliteTable.value.scrollTop = 0
})
</script>
<style scoped>
#satellite-table-container {
left: 12.5vw;
right: 12.5vw;
}
table {
display: block;
height: 360px;
overflow: auto;
}
tr:nth-child(odd) {
background-color: rgb(17, 24, 39);
}
tr:nth-child(even) {
background-color:rgb(31, 41, 55)
}
td {
padding: 5px 0;
}
::-webkit-scrollbar
{
width: 12px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgb(0 0 0 / 30%);
background-color: #4fbee2;
}
::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgb(0 0 0 / 30%);
background-color: #111827;
}
</style>>

View File

@@ -0,0 +1,67 @@
<template>
<button @click="show = !show" class="rounded border-blue-900 bg-blue-900">
<svg
v-if="show"
xmlns="http://www.w3.org/2000/svg"
width="600"
height="30"
fill="none"
viewBox="-10 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="3"
d="M -200 8 l 200 10 200 -10 m -400 -6 l 200 10 200 -10"
>
<animate
attributeName="stroke"
attributeType="XML"
from="#ffffff"
to="#666666"
begin="0s"
dur="1.5s"
fill="remove"
repeatCount="indefinite"
/>
</path>
</svg>
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
width="600"
height="30"
fill="none"
viewBox="-10 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="3"
d="M -200 20 l 200 -10 200 10 m -400 -6 l 200 -10 200 10"
>
<animate
attributeName="stroke"
attributeType="XML"
from="#ffffff"
to="#666666"
begin="0s"
dur="1.5s"
fill="remove"
repeatCount="indefinite"
/>
</path>
</svg>
</button>
</template>
<script setup>
import { ref } from "vue";
const show = ref(false);
defineExpose({ show });
</script>
<style scoped></style>

View File

@@ -0,0 +1,21 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
/>
</svg>
</template>
<script setup></script>
<style scoped></style>

View File

@@ -1,17 +1,22 @@
import { createApp } from 'vue'
import App from './App.vue'
import { createApp } from 'vue';
import App from './App.vue';
import 'vue-cesium/dist/index.css'
import { VcViewer, VcLayerImagery, VcNavigation, VcDatasourceCzml, VcProviderImagerySingletile } from 'vue-cesium'
import 'vue-cesium/dist/index.css';
import { VcConfigProvider, VcViewer, VcLayerImagery, VcNavigation, VcDatasourceCzml, VcImageryProviderSingletile } from 'vue-cesium';
import store from './store'
import './main.css'
import store from './store';
import './main.css';
const cesiumPath = (process.env.NODE_ENV === 'development' ? './node_modules/cesium/Build/Cesium/Cesium.js': './Cesium/Cesium.js')
const app = createApp(App)
app.use(store)
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
const cesiumPath = (process.env.NODE_ENV === 'development' ? './node_modules/cesium/Build/Cesium/Cesium.js': './Cesium/Cesium.js');
const app = createApp(App);
app.use(store);
// 局部引入VueCesium
app.use(VcViewer).use(VcLayerImagery).use(VcNavigation).use(VcDatasourceCzml).use(VcProviderImagerySingletile)
app.config.globalProperties.$VueCesium = { cesiumPath }
app.mount('#app')
app.use(VcConfigProvider).use(VcViewer).use(VcLayerImagery).use(VcNavigation).use(VcDatasourceCzml).use(VcImageryProviderSingletile);
app.use(ElementPlus);
app.config.globalProperties.$VueCesium = { cesiumPath };
app.mount('#app');

View File

@@ -1,12 +1,12 @@
import { createStore } from 'vuex'
import satelliteSystem from './modules/satelliteSystem'
import { createStore } from 'vuex';
import satelliteSystem from './modules/satelliteSystem';
const debug = process.env.NODE_ENV !== 'production'
const debug = process.env.NODE_ENV !== 'production';
export default createStore({
modules: {
satelliteSystem
},
strict: debug
})
});

View File

@@ -1,13 +1,13 @@
const state = {
name: '',
satellites: []
}
};
// getters
const getters = {
name: (state) => state.name,
satellites: (state) => state.satellites
}
};
// actions
const actions = {
@@ -24,7 +24,7 @@ const actions = {
toggleShow () {
},
}
};
// mutations
const mutations = {
@@ -35,7 +35,7 @@ const mutations = {
setSatellites (state, satellites) {
state.satellites = satellites
},
}
};
export default {
namespaced: true,
@@ -43,4 +43,4 @@ export default {
getters,
actions,
mutations
}
};