index.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780
  1. <template>
  2. <div class="large-screen">
  3. <!-- 地图 -->
  4. <mapScene ref="mapSceneRef" v-show="state.activeIndex === '1' && !state.showVideoPlayer"></mapScene>
  5. <div class="fusion-bg" v-show="state.activeIndex === '2'">
  6. <img src="@/assets/images/昆山水文站.jpg" alt="昆山水文站" />
  7. </div>
  8. <div class="business-bg" v-show="state.activeIndex === '3'">
  9. <img src="@/assets/images/圩区.png" alt="圩区" />
  10. </div>
  11. <div class="history-bg" v-show="state.activeIndex === '4'">
  12. <video v-if="!state.showHistoryImage" ref="historyVideoRef" class="history-bg-video" loop autoplay muted>
  13. <source :src="state.historyVideoSrc" type="video/mp4">
  14. </video>
  15. <img v-else class="history-bg-image" :src="state.historyImageSrc" alt="历史背景" />
  16. </div>
  17. <div class="culture-bg" v-show="state.activeIndex === '5'">
  18. <video class="culture-bg-video" loop autoplay muted>
  19. <source src="@/assets/video/水文化.mp4" type="video/mp4">
  20. </video>
  21. </div>
  22. <div class="science-bg" v-show="state.activeIndex === '5'">
  23. <img src="@/assets/images/背景图.png" alt="背景图" />
  24. </div>
  25. <!-- 视频背景 -->
  26. <div class="video-background" v-if="state.showVideoPlayer">
  27. <video
  28. ref="videoPlayerRef"
  29. class="video-bg-player"
  30. src="@/assets/昆山水文站.mp4"
  31. autoplay
  32. loop
  33. muted
  34. @ended="closeVideoPlayer"
  35. ></video>
  36. <div class="video-close-btn" @click="closeVideoPlayer">
  37. <span>返回</span>
  38. </div>
  39. </div>
  40. <!-- 地图切换按钮 -->
  41. <div class="map-switch-btns" v-show="state.activeIndex === '1'">
  42. <div class="map-switch-btn" :class="{ active: state.currentMapMode === 'suzhou' }" @click="switchToSuzhouMap">
  43. <span>苏州地图</span>
  44. </div>
  45. <div class="map-switch-btn" :class="{ active: state.currentMapMode === 'kunshan' }" @click="switchToKunshanMap">
  46. <span>昆山地图</span>
  47. </div>
  48. </div>
  49. <div class="vignette-overlay"></div>
  50. <div class="large-screen-wrap" id="large-screen">
  51. <m-header title="水务水文融合系统" sub-text="Hydrological Visualization System">
  52. <!--左侧 天气 -->
  53. <template v-slot:left>
  54. <div class="m-header-weather"><span>小雪</span><span>-4℃</span></div>
  55. </template>
  56. <!--右侧 日期 -->
  57. <template v-slot:right>
  58. <div class="m-header-date"><span>2025-12-11</span><span>17:53:16</span></div>
  59. </template>
  60. </m-header>
  61. <!-- 顶部菜单 - 仅在昆山地图模式下显示 -->
  62. <div class="top-menu" v-show="state.currentMapMode === 'kunshan'">
  63. <mMenu :default-active="state.activeIndex" @select="handleMenuSelect">
  64. <mMenuItem index="1">区域总览</mMenuItem>
  65. <mMenuItem index="2">融合体系</mMenuItem>
  66. <mMenuItem index="3">综合业务</mMenuItem>
  67. <div class="top-menu-mid-space"></div>
  68. <mMenuItem index="4">历史沿革</mMenuItem>
  69. <mMenuItem index="5">水文科普</mMenuItem>
  70. <mMenuItem index="6">研学联建</mMenuItem>
  71. </mMenu>
  72. </div>
  73. <!-- 区域总览内容 -->
  74. <template v-if="state.activeIndex === '1'">
  75. <!-- 顶部统计卡片 -->
  76. <div class="top-count-card">
  77. <mCountCard v-for="(item, index) in state.totalView" :info="item" :key="index"></mCountCard>
  78. </div>
  79. <!-- 左边布局 图表 -->
  80. <div class="left-wrap">
  81. <div class="left-wrap-3d">
  82. <!-- 中心简介 -->
  83. <BulkCommoditySalesChart></BulkCommoditySalesChart>
  84. <!-- 近年水位变化 -->
  85. <EconomicTrendChart></EconomicTrendChart>
  86. </div>
  87. </div>
  88. <!-- 右边布局 图表 -->
  89. <div class="right-wrap">
  90. <div class="right-wrap-3d">
  91. <!-- 水利设施分布 -->
  92. <PurposeSpecialFunds> </PurposeSpecialFunds>
  93. <!-- 用水量分析 -->
  94. <ElectricityUsage></ElectricityUsage>
  95. </div>
  96. </div>
  97. <!-- 底部托盘 -->
  98. <div class="bottom-tray">
  99. <!-- svg线条动画 -->
  100. <mSvglineAnimation class="bottom-svg-line-left" :width="721" :height="57" color="#00BFFF" :strokeWidth="2"
  101. :dir="[0, 1]" :length="50"
  102. path="M1 56.6105C1 31.5123 185.586 10.0503 451.904 1.35519C458.942 1.12543 465.781 4.00883 470.505 9.22964L484.991 25.2383C487.971 28.4775 492.938 30.4201 498.254 30.4201H720.142">
  103. </mSvglineAnimation>
  104. <mSvglineAnimation class="bottom-svg-line-left bottom-svg-line-right" :width="721" :height="57" color="#00BFFF"
  105. :strokeWidth="2" :dir="[0, 1]" :length="50"
  106. path="M1 56.6105C1 31.5123 185.586 10.0503 451.904 1.35519C458.942 1.12543 465.781 4.00883 470.505 9.22964L484.991 25.2383C487.971 28.4775 492.938 30.4201 498.254 30.4201H720.142">
  107. </mSvglineAnimation>
  108. <!-- 做箭头 -->
  109. <div class="bottom-tray-arrow">
  110. <img src="@/assets/images/bottom-menu-arrow-big.svg" alt="" />
  111. <img src="@/assets/images/bottom-menu-arrow-small.svg" alt="" />
  112. </div>
  113. <!-- 底部菜单 -->
  114. <div class="bottom-menu">
  115. <div class="bottom-menu-item is-active"><span>区域总览</span></div>
  116. <div class="bottom-menu-item"><span>水质监测</span></div>
  117. <div class="bottom-menu-item"><span>水文数据</span></div>
  118. <div class="bottom-menu-item"><span>防洪预警</span></div>
  119. </div>
  120. <!-- 右箭头 -->
  121. <div class="bottom-tray-arrow is-reverse">
  122. <img src="@/assets/images/bottom-menu-arrow-big.svg" alt="" />
  123. <img src="@/assets/images/bottom-menu-arrow-small.svg" alt="" />
  124. </div>
  125. </div>
  126. <!-- 雷达 -->
  127. <div class="bottom-radar">
  128. <mRadar></mRadar>
  129. </div>
  130. </template>
  131. <!-- 融合体系内容 -->
  132. <WaterResourceContent v-if="state.activeIndex === '2'" />
  133. <!-- 水文测站内容 -->
  134. <WaterStationContent v-if="state.activeIndex === '3'" />
  135. <!-- 历史沿革内容 -->
  136. <HistoryContent v-if="state.activeIndex === '4'" @changeVideo="handleVideoChange" />
  137. <!-- 水文科普内容 -->
  138. <ScienceContent v-if="state.activeIndex === '5'" />
  139. <!-- 左右装饰线 -->
  140. <div class="large-screen-left-zsline"></div>
  141. <div class="large-screen-right-zsline"></div>
  142. </div>
  143. <!-- loading动画 -->
  144. <div class="loading">
  145. <div class="loading-text">
  146. <span style="--index: 1">L</span>
  147. <span style="--index: 2">O</span>
  148. <span style="--index: 3">A</span>
  149. <span style="--index: 4">D</span>
  150. <span style="--index: 5">I</span>
  151. <span style="--index: 6">N</span>
  152. <span style="--index: 7">G</span>
  153. </div>
  154. <div class="loading-progress">
  155. <span class="value">{{ state.progress }}</span>
  156. <span class="unit">%</span>
  157. </div>
  158. </div>
  159. </div>
  160. </template>
  161. <script setup>
  162. import { shallowRef, ref, reactive, onMounted, onBeforeUnmount, nextTick, provide } from "vue"
  163. import mapScene from "./map.vue"
  164. import mHeader from "@/components/mHeader/index.vue"
  165. import mCountCard from "@/components/mCountCard/index.vue"
  166. import mMenu from "@/components/mMenu/index.vue"
  167. import mRadar from "@/components/mRadar/index.vue"
  168. import mMenuItem from "@/components/mMenuItem/index.vue"
  169. import mSvglineAnimation from "@/components/mSvglineAnimation/index.vue"
  170. import BulkCommoditySalesChart from "./components/BulkCommoditySalesChart.vue"
  171. import YearlyEconomyTrend from "./components/YearlyEconomyTrend.vue"
  172. import EconomicTrendChart from "./components/EconomicTrendChart.vue"
  173. import DistrictEconomicIncome from "./components/DistrictEconomicIncome.vue"
  174. import PurposeSpecialFunds from "./components/PurposeSpecialFunds.vue"
  175. import ProportionPopulationConsumption from "./components/ProportionPopulationConsumption.vue"
  176. import ElectricityUsage from "./components/ElectricityUsage.vue"
  177. import QuarterlyGrowthSituation from "./components/QuarterlyGrowthSituation.vue"
  178. import WaterResourceContent from "@/views/waterResource/index.vue"
  179. import WaterStationContent from "@/views/waterStation/index.vue"
  180. import HistoryContent from "@/views/history/index.vue"
  181. import ScienceContent from "@/views/science/index.vue"
  182. import { Assets } from "./assets.js"
  183. import emitter from "@/utils/emitter"
  184. import gsap from "gsap"
  185. import autofit from "autofit.js"
  186. const assets = shallowRef(null)
  187. const mapSceneRef = ref(null)
  188. const historyVideoRef = ref(null)
  189. // 提供资源给子组件
  190. provide("assets", assets)
  191. const state = reactive({
  192. // 进度
  193. progress: 0,
  194. // 当前顶部导航索引
  195. activeIndex: "1",
  196. // 是否显示视频播放器
  197. showVideoPlayer: false,
  198. // 历史沿革视频源
  199. historyVideoSrc: new URL("@/assets/video/昆山水务水文人工.mp4", import.meta.url).href,
  200. // 历史沿革图片源
  201. historyImageSrc: new URL("@/assets/video/昆山水务水文水质监测.png", import.meta.url).href,
  202. // 是否显示历史沿革图片
  203. showHistoryImage: false,
  204. // 当前地图模式: 'kunshan' | 'suzhou',默认苏州地图
  205. currentMapMode: 'suzhou',
  206. // 卡片统计数据
  207. totalView: [
  208. {
  209. icon: "xiaoshoujine",
  210. zh: "区域总面积",
  211. value: 931,
  212. unit: "平方公里",
  213. },
  214. {
  215. icon: "zongxiaoliang",
  216. zh: "水文站数量",
  217. value: 57,
  218. unit: "个",
  219. },
  220. {
  221. icon: "xiaoshoujine",
  222. zh: "水厂数量",
  223. value: 8,
  224. unit: "座",
  225. },
  226. {
  227. icon: "zongxiaoliang",
  228. zh: "排水口数量",
  229. value: 156,
  230. unit: "个",
  231. },
  232. ],
  233. })
  234. onMounted(() => {
  235. // 监听地图播放完成,执行事件
  236. emitter.$on("mapPlayComplete", handleMapPlayComplete)
  237. // 监听水文站图标点击事件
  238. emitter.$on("waterStationClick", handleWaterStationClick)
  239. // 监听地图模式变化
  240. emitter.$on("mapModeChanged", handleMapModeChanged)
  241. // 监听切换到区域总览页面事件
  242. emitter.$on("switchToRegionOverview", handleSwitchToRegionOverview)
  243. // 自动适配
  244. assets.value = autofit.init({
  245. dh: 1080,
  246. dw: 1920,
  247. el: "#large-screen",
  248. resize: true,
  249. })
  250. // 初始化资源
  251. initAssets(async () => {
  252. // 加载地图
  253. emitter.$emit("loadMap", assets.value)
  254. // 隐藏loading
  255. await hideLoading()
  256. // 播放场景
  257. mapSceneRef.value.play()
  258. })
  259. })
  260. onBeforeUnmount(() => {
  261. emitter.$off("mapPlayComplete", handleMapPlayComplete)
  262. emitter.$off("waterStationClick", handleWaterStationClick)
  263. emitter.$off("mapModeChanged", handleMapModeChanged)
  264. emitter.$off("switchToRegionOverview", handleSwitchToRegionOverview)
  265. })
  266. // 处理地图模式变化
  267. function handleMapModeChanged(mode) {
  268. state.currentMapMode = mode
  269. }
  270. // 处理切换到区域总览页面
  271. function handleSwitchToRegionOverview() {
  272. // 切换到区域总览页面(activeIndex 为 1)
  273. state.activeIndex = '1'
  274. }
  275. // 切换到苏州地图
  276. function switchToSuzhouMap() {
  277. if (state.currentMapMode === 'suzhou') return
  278. if (mapSceneRef.value) {
  279. mapSceneRef.value.switchToSuzhou()
  280. }
  281. }
  282. // 切换到昆山地图
  283. function switchToKunshanMap() {
  284. if (state.currentMapMode === 'kunshan') return
  285. if (mapSceneRef.value) {
  286. mapSceneRef.value.switchToKunshan()
  287. }
  288. }
  289. // 处理水文站图标点击事件
  290. function handleWaterStationClick(stationInfo) {
  291. console.log('播放水文站视频:', stationInfo.name)
  292. state.showVideoPlayer = true
  293. }
  294. // 关闭视频播放器
  295. function closeVideoPlayer() {
  296. state.showVideoPlayer = false
  297. // 暂停视频播放
  298. const videoPlayer = document.querySelector('.video-player')
  299. if (videoPlayer) {
  300. videoPlayer.pause()
  301. }
  302. }
  303. // 初始化加载资源
  304. function initAssets(onLoadCallback) {
  305. assets.value = new Assets()
  306. // 资源加载进度
  307. let params = {
  308. progress: 0,
  309. }
  310. assets.value.instance.on("onProgress", (path, itemsLoaded, itemsTotal) => {
  311. let p = Math.floor((itemsLoaded / itemsTotal) * 100)
  312. gsap.to(params, {
  313. progress: p,
  314. onUpdate: () => {
  315. state.progress = Math.floor(params.progress)
  316. },
  317. })
  318. })
  319. // 资源加载完成
  320. assets.value.instance.on("onLoad", () => {
  321. onLoadCallback && onLoadCallback()
  322. })
  323. }
  324. // 隐藏loading
  325. async function hideLoading() {
  326. return new Promise((resolve, reject) => {
  327. let tl = gsap.timeline()
  328. tl.to(".loading-text span", {
  329. y: "200%",
  330. opacity: 0,
  331. ease: "power4.inOut",
  332. duration: 2,
  333. stagger: 0.2,
  334. })
  335. tl.to(".loading-progress", { opacity: 0, ease: "power4.inOut", duration: 2 }, "<")
  336. tl.to(
  337. ".loading",
  338. {
  339. opacity: 0,
  340. ease: "power4.inOut",
  341. onComplete: () => {
  342. resolve()
  343. },
  344. },
  345. "-=1"
  346. )
  347. })
  348. }
  349. function handleMenuSelect(index) {
  350. state.activeIndex = index
  351. // 切换水文站图标显示
  352. if (index === "3") {
  353. // 在水文测站页面显示图标
  354. emitter.$emit("toggleWaterStations", true)
  355. } else {
  356. // 其他页面隐藏图标
  357. emitter.$emit("toggleWaterStations", false)
  358. }
  359. nextTick(() => {
  360. if (index === "1") {
  361. gsap.to(".left-card", { x: 0, opacity: 1, duration: 0.5, stagger: 0.1 })
  362. gsap.to(".right-card", { x: 0, opacity: 1, duration: 0.5, stagger: 0.1 })
  363. gsap.to(".count-card", { y: 0, opacity: 1, duration: 0.5, stagger: 0.1 })
  364. gsap.to(".bottom-tray", { y: 0, opacity: 1, duration: 0.5 })
  365. gsap.to(".bottom-radar", { y: 0, opacity: 1, duration: 0.5 })
  366. } else if (index === "2") {
  367. gsap.to(".water-resource-content .module-card", { x: 0, opacity: 1, duration: 0.5, stagger: 0.1 })
  368. } else if (index === "3") {
  369. gsap.to(".water-station-content .station-card", { x: 0, opacity: 1, duration: 0.5 })
  370. gsap.to(".water-station-content .bottom-container", { y: 0, opacity: 1, duration: 0.5, delay: 0.2 })
  371. } else if (index === "4") {
  372. gsap.to(".history-content .event-card", { x: 0, opacity: 1, duration: 0.5 })
  373. gsap.to(".history-content .timeline-container", { y: 0, opacity: 1, duration: 0.5, delay: 0.2 })
  374. } else if (index === "5") {
  375. gsap.to(".science-content .science-card", { y: 0, opacity: 1, duration: 0.6, stagger: 0.15 })
  376. }
  377. })
  378. }
  379. function handleVideoChange(videoType) {
  380. if (videoType === "water") {
  381. state.showHistoryImage = true
  382. state.historyImageSrc = new URL("@/assets/video/昆山水务水文水质监测.png", import.meta.url).href
  383. } else {
  384. state.showHistoryImage = false
  385. if (videoType === "auto") {
  386. state.historyVideoSrc = new URL("@/assets/video/昆山水务水文自动化起步.mp4", import.meta.url).href
  387. } else if (videoType === "3d") {
  388. state.historyVideoSrc = new URL("@/assets/video/昆山水务水文三维孪生半自动.mp4", import.meta.url).href
  389. } else if (videoType === "smart") {
  390. state.historyVideoSrc = new URL("@/assets/video/昆山水务水文三维孪生智能.mp4", import.meta.url).href
  391. } else if (videoType === "wisdom") {
  392. state.historyVideoSrc = new URL("@/assets/video/智慧水务.mp4", import.meta.url).href
  393. } else {
  394. state.historyVideoSrc = new URL("@/assets/video/昆山水务水文人工.mp4", import.meta.url).href
  395. }
  396. nextTick(() => {
  397. if (historyVideoRef.value) {
  398. historyVideoRef.value.load()
  399. historyVideoRef.value.play()
  400. }
  401. })
  402. }
  403. }
  404. // 地图开始动画播放完成
  405. function handleMapPlayComplete() {
  406. let tl = gsap.timeline({ paused: false })
  407. let leftCards = gsap.utils.toArray(".left-card")
  408. let rightCards = gsap.utils.toArray(".right-card")
  409. let countCards = gsap.utils.toArray(".count-card")
  410. tl.addLabel("start", 0.5)
  411. tl.addLabel("menu", 0.5)
  412. tl.addLabel("card", 1)
  413. tl.addLabel("countCard", 3)
  414. tl.to(".m-header", { y: 0, opacity: 1, duration: 1.5, ease: "power4.out" }, "start")
  415. tl.to(".bottom-tray", { y: 0, opacity: 1, duration: 1.5, ease: "power4.out" }, "start")
  416. tl.to(
  417. ".top-menu",
  418. {
  419. y: 0,
  420. opacity: 1,
  421. duration: 1.5,
  422. ease: "power4.out",
  423. },
  424. "-=1"
  425. )
  426. tl.to(".bottom-radar", { y: 0, opacity: 1, duration: 1.5, ease: "power4.out" }, "-=2")
  427. tl.to(leftCards, { x: 0, opacity: 1, stagger: 0.2, duration: 1.5, ease: "power4.out" }, "card")
  428. tl.to(rightCards, { x: 0, opacity: 1, stagger: 0.2, duration: 1.5, ease: "power4.out" }, "card")
  429. tl.to(
  430. countCards,
  431. {
  432. y: 0,
  433. opacity: 1,
  434. stagger: 0.2,
  435. duration: 1.5,
  436. ease: "power4.out",
  437. },
  438. "card"
  439. )
  440. }
  441. </script>
  442. <style lang="scss">
  443. @use "~@/assets/style/home.scss";
  444. .vignette-overlay {
  445. position: absolute;
  446. z-index: 1;
  447. left: 0;
  448. top: 0;
  449. right: 0;
  450. bottom: 0;
  451. pointer-events: none;
  452. background: radial-gradient(
  453. ellipse 70% 70% at center,
  454. rgba(0, 20, 40, 0) 40%,
  455. rgba(0, 20, 40, 1.0) 100%
  456. );
  457. box-shadow: inset 0 0 100px rgba(0, 191, 255, 0.1);
  458. }
  459. .m-header-weather,
  460. .m-header-date {
  461. span {
  462. padding-right: 10px;
  463. color: #c4f3fe;
  464. font-size: 14px;
  465. }
  466. }
  467. .top-menu {
  468. position: absolute;
  469. left: 0px;
  470. right: 0px;
  471. top: 40px;
  472. z-index: 3;
  473. display: flex;
  474. justify-content: center;
  475. .top-menu-mid-space {
  476. width: 800px;
  477. }
  478. }
  479. .bottom-radar {
  480. position: absolute;
  481. right: 500px;
  482. bottom: 100px;
  483. z-index: 3;
  484. }
  485. .main-btn-group {
  486. display: flex;
  487. left: 50%;
  488. transform: translateX(-50%);
  489. bottom: 10px;
  490. z-index: 999;
  491. &.disabled {
  492. pointer-events: none;
  493. }
  494. .btn {
  495. margin-right: 10px;
  496. }
  497. }
  498. .bottom-svg-line-left,
  499. .bottom-svg-line-right {
  500. position: absolute;
  501. right: 50%;
  502. width: 721px;
  503. height: 57px;
  504. margin-right: -5px;
  505. bottom: -21px;
  506. }
  507. .bottom-svg-line-right {
  508. transform: scaleX(-1);
  509. left: 50%;
  510. right: inherit;
  511. margin-right: inherit;
  512. margin-left: -5px;
  513. }
  514. /* 初始化动画开始位置 */
  515. .m-header {
  516. transform: translateY(-100%);
  517. opacity: 0;
  518. }
  519. .top-menu {
  520. transform: translateY(-250%);
  521. opacity: 0;
  522. }
  523. .count-card {
  524. transform: translateY(150%);
  525. opacity: 0;
  526. }
  527. .left-card {
  528. transform: translateX(-150%);
  529. opacity: 0;
  530. }
  531. .right-card {
  532. transform: translateX(150%);
  533. opacity: 0;
  534. }
  535. .right-wrap {
  536. position: absolute;
  537. right: 32px;
  538. top: 180px;
  539. width: 398px;
  540. bottom: 50px;
  541. perspective: 500px;
  542. perspective-origin: 50% 50%;
  543. }
  544. .right-wrap-3d {
  545. position: absolute;
  546. left: 0;
  547. top: 0;
  548. right: 0;
  549. bottom: 0;
  550. display: flex;
  551. flex-direction: column;
  552. justify-content: space-between;
  553. transform: translate3d(0px, 0px, 0px) scaleX(1) scaleY(1) rotateX(0deg) rotateY(-6deg) rotateZ(0deg) skewX(0deg) skewY(0deg);
  554. z-index: 4;
  555. }
  556. .right-wrap-3d > div {
  557. margin-bottom: 16px;
  558. }
  559. .right-wrap-3d > div:last-child {
  560. margin-bottom: 0;
  561. }
  562. .bottom-tray {
  563. transform: translateY(100%);
  564. opacity: 0;
  565. }
  566. .bottom-radar {
  567. transform: translateY(100%);
  568. opacity: 0;
  569. }
  570. // 地图切换按钮样式
  571. .map-switch-btns {
  572. position: absolute;
  573. left: 50%;
  574. bottom: 100px;
  575. transform: translateX(-50%);
  576. z-index: 10;
  577. display: flex;
  578. gap: 10px;
  579. pointer-events: auto;
  580. .map-switch-btn {
  581. padding: 8px 20px;
  582. background: rgba(0, 20, 40, 0.85);
  583. border: 1px solid rgba(48, 220, 255, 0.3);
  584. border-radius: 4px;
  585. cursor: pointer;
  586. transition: all 0.3s ease;
  587. span {
  588. color: #a3dcde;
  589. font-size: 14px;
  590. }
  591. &:hover {
  592. background: rgba(48, 220, 255, 0.2);
  593. border-color: rgba(48, 220, 255, 0.6);
  594. }
  595. &.active {
  596. background: rgba(48, 220, 255, 0.3);
  597. border-color: rgba(48, 220, 255, 0.8);
  598. box-shadow: 0 0 15px rgba(48, 220, 255, 0.4);
  599. span {
  600. color: #30dcff;
  601. font-weight: bold;
  602. }
  603. }
  604. }
  605. }
  606. .fusion-bg, .business-bg, .history-bg, .culture-bg, .science-bg {
  607. position: absolute;
  608. z-index: 1;
  609. left: 0;
  610. top: 0;
  611. right: 0;
  612. bottom: 0;
  613. background-color: #000;
  614. img, video {
  615. width: 100%;
  616. height: 100%;
  617. object-fit: cover;
  618. }
  619. }
  620. .left-column {
  621. position: absolute;
  622. z-index: 4;
  623. width: 398px;
  624. left: 32px;
  625. top: 180px;
  626. bottom: 50px;
  627. perspective: 500px;
  628. perspective-origin: 50% 50%;
  629. .left-column-3d {
  630. position: absolute;
  631. left: 0;
  632. top: 0;
  633. right: 0;
  634. bottom: 0;
  635. display: flex;
  636. flex-direction: column;
  637. justify-content: space-between;
  638. transform: translate3d(0px, 0px, 0px) scaleX(1) scaleY(1) rotateX(0deg) rotateY(6deg) rotateZ(0deg) skewX(0deg) skewY(0deg);
  639. z-index: 4;
  640. }
  641. .module-card {
  642. height: 450px;
  643. background: rgba(0, 20, 40, 0.8);
  644. border: 1px solid rgba(48, 220, 255, 0.3);
  645. border-radius: 10px;
  646. box-shadow: 0 0 20px rgba(48, 220, 255, 0.1);
  647. transform: translateX(-150%);
  648. opacity: 0;
  649. }
  650. }
  651. .right-column {
  652. position: absolute;
  653. z-index: 4;
  654. width: 398px;
  655. right: 32px;
  656. top: 150px;
  657. bottom: 50px;
  658. perspective: 800px;
  659. perspective-origin: 50% 50%;
  660. .right-column-3d {
  661. position: absolute;
  662. left: 0;
  663. top: 0;
  664. right: 0;
  665. bottom: 0;
  666. display: flex;
  667. flex-direction: column;
  668. justify-content: space-between;
  669. transform: translate3d(0px, 0px, 0px) scaleX(1) scaleY(1) rotateX(0deg) rotateY(-6deg) rotateZ(0deg) skewX(0deg) skewY(0deg);
  670. }
  671. .module-card {
  672. height: 450px;
  673. background: rgba(0, 20, 40, 0.8);
  674. border: 1px solid rgba(48, 220, 255, 0.3);
  675. border-radius: 10px;
  676. box-shadow: 0 0 20px rgba(48, 220, 255, 0.1);
  677. transform: translateX(150%);
  678. opacity: 0;
  679. }
  680. }
  681. // 视频背景样式
  682. .video-background {
  683. position: fixed;
  684. top: 0;
  685. left: 0;
  686. width: 100vw;
  687. height: 100vh;
  688. z-index: 1;
  689. .video-bg-player {
  690. width: 100%;
  691. height: 100%;
  692. object-fit: cover;
  693. }
  694. .video-close-btn {
  695. position: absolute;
  696. top: 100px;
  697. right: 40px;
  698. padding: 8px 20px;
  699. background: rgba(0, 20, 40, 0.9);
  700. border: 1px solid rgba(48, 220, 255, 0.5);
  701. border-radius: 4px;
  702. display: flex;
  703. align-items: center;
  704. justify-content: center;
  705. cursor: pointer;
  706. z-index: 10;
  707. transition: all 0.3s ease;
  708. span {
  709. color: #30dcff;
  710. font-size: 14px;
  711. font-weight: bold;
  712. }
  713. &:hover {
  714. background: rgba(48, 220, 255, 0.2);
  715. border-color: rgba(48, 220, 255, 0.8);
  716. box-shadow: 0 0 20px rgba(48, 220, 255, 0.5);
  717. span {
  718. color: #00ffff;
  719. }
  720. }
  721. }
  722. }
  723. </style>