通信模块配置软件

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
<template>
<div v-show="!!src">
<img :src="src" @dblclick="close">
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { useStore } from 'vuex';
const store = useStore()
const src = computed(() => './image/flag/' + store.getters["satelliteSystem/name"] + '.png')
const close = () => {
window.ipcRenderer.send('CLOSE')
}
</script>
<style scoped>
img {
width: 10rem;
}
</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>

File diff suppressed because it is too large Load Diff

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>