aamin hace 1 año
padre
commit
a0c5049a79
Se han modificado 61 ficheros con 1126 adiciones y 1499 borrados
  1. 1 0
      components.d.ts
  2. 1 1
      index.html
  3. 12 8
      src/App.vue
  4. BIN
      src/assets/font/MicrosoftYaHei.ttf
  5. BIN
      src/assets/font/SourceHanSansCN-Bold.otf
  6. BIN
      src/assets/font/SourceHanSansCN-Heavy.otf
  7. BIN
      src/assets/font/SourceHanSansCN-Regular.otf
  8. BIN
      src/assets/font/MicrosoftYaHei-Bold.ttf
  9. BIN
      src/assets/img/bg.png
  10. BIN
      src/assets/img/contentBg.png
  11. BIN
      src/assets/img/down.png
  12. BIN
      src/assets/img/home-icon.png
  13. BIN
      src/assets/img/home/0.png
  14. BIN
      src/assets/img/home/1.png
  15. BIN
      src/assets/img/home/2.png
  16. BIN
      src/assets/img/home/welcome.png
  17. BIN
      src/assets/img/left.png
  18. BIN
      src/assets/img/overview/close.png
  19. BIN
      src/assets/img/overview/develop.png
  20. BIN
      src/assets/img/overview/line.png
  21. BIN
      src/assets/img/overview/location.png
  22. BIN
      src/assets/img/overview/map.png
  23. BIN
      src/assets/img/overview/riverBg.png
  24. BIN
      src/assets/img/overview/timeIcon.png
  25. BIN
      src/assets/img/overview/titleBg.png
  26. BIN
      src/assets/img/part/label@2x.png
  27. BIN
      src/assets/img/part/part1.png
  28. BIN
      src/assets/img/pause.png
  29. BIN
      src/assets/img/play.png
  30. BIN
      src/assets/img/right.png
  31. BIN
      src/assets/img/search/right.png
  32. BIN
      src/assets/img/search/searchBoxBg.png
  33. BIN
      src/assets/img/search/searchBtnBg.png
  34. BIN
      src/assets/img/search/searchIcon.png
  35. BIN
      src/assets/img/tabbar/activedian.png
  36. BIN
      src/assets/img/tabbar/itemBg.png
  37. BIN
      src/assets/img/tabbar/menu.png
  38. BIN
      src/assets/img/tabbar/menu_active.png
  39. BIN
      src/assets/img/tabbar/search.png
  40. BIN
      src/assets/img/tabbar/tabbarBg.png
  41. BIN
      src/assets/img/up.png
  42. BIN
      src/assets/img/villages/border.png
  43. BIN
      src/assets/img/villages/borderAc.png
  44. BIN
      src/assets/img/villages/building.jpg
  45. 0 281
      src/components/AudioBox.vue
  46. 366 0
      src/components/Tabbar.vue
  47. 0 110
      src/components/progress.vue
  48. 118 535
      src/data/data.ts
  49. 28 9
      src/routes/index.ts
  50. 1 28
      src/store/index.ts
  51. 0 262
      src/view/audioDetail/index.vue
  52. 11 0
      src/view/building/index.vue
  53. 0 187
      src/view/exhibitionList/index.vue
  54. 0 78
      src/view/home/index.vue
  55. 157 0
      src/view/overview/components/incident.vue
  56. 75 0
      src/view/overview/components/local.vue
  57. 92 0
      src/view/overview/index.vue
  58. 67 0
      src/view/part/index.vue
  59. 11 0
      src/view/search/index.vue
  60. 164 0
      src/view/village/index.vue
  61. 22 0
      src/view/welcome/index.vue

+ 1 - 0
components.d.ts

@@ -11,5 +11,6 @@ declare module 'vue' {
     Progress: typeof import('./src/components/progress.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
+    Tabbar: typeof import('./src/components/Tabbar.vue')['default']
   }
 }

+ 1 - 1
index.html

@@ -4,7 +4,7 @@
     <meta charset="UTF-8" />
     <link rel="icon" type="image/svg+xml" href="/vite.svg" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>岳阳博物馆</title>
+    <title>开平碉楼建筑群</title>
   </head>
   <body>
     <div id="app"></div>

+ 12 - 8
src/App.vue

@@ -22,19 +22,23 @@ onMounted(() => {
 
 <style scoped>
 @font-face {
-  font-family: "Microsoft YaHei-Bold"; /* 字体名称 */
-  src: url("../src/assets/font/MicrosoftYaHei-Bold.ttf"); /* 字体文件相对路径 */
+  font-family: "Source Han Sans CN-Bold"; /* 字体名称 */
+  src: url("./assets/font/SourceHanSansCN-Bold.otf"); /* 字体文件相对路径 */
 }
 @font-face {
-  font-family: "Microsoft YaHei"; /* 字体名称 */
-  src: url("../src/assets/font/MicrosoftYaHei.ttf"); /* 字体文件相对路径 */
+  font-family: "Source Han Sans CN"; /* 字体名称 */
+  src: url("./assets/font/Source Han Sans CN.otf"); /* 字体文件相对路径 */
+}
+@font-face {
+  font-family: "Source Han Sans CN-Regular"; /* 字体名称 */
+  src: url("./assets/font/SourceHanSansCN-Regular.otf"); /* 字体文件相对路径 */
 }
 .all {
-  width: 100%;
-  height: calc(var(--vh, 1vh) * 100);
-  background: url(/src\assets\img\bg.png);
-  background-size: 100% 100%;
+  width: 100vw;
+  height: 100vh;
   overflow: hidden;
+  background: url(\src/assets/img/overview/map.png);
+  background-size: 100% 100%;
 }
 * {
   font-family: "Microsoft YaHei-Bold, Microsoft YaHei";

BIN
src/assets/font/MicrosoftYaHei.ttf


BIN
src/assets/font/SourceHanSansCN-Bold.otf


BIN
src/assets/font/SourceHanSansCN-Heavy.otf


BIN
src/assets/font/SourceHanSansCN-Regular.otf


BIN
src/assets/font/MicrosoftYaHei-Bold.ttf


BIN
src/assets/img/bg.png


BIN
src/assets/img/contentBg.png


BIN
src/assets/img/down.png


BIN
src/assets/img/home-icon.png


BIN
src/assets/img/home/0.png


BIN
src/assets/img/home/1.png


BIN
src/assets/img/home/2.png


BIN
src/assets/img/home/welcome.png


BIN
src/assets/img/left.png


BIN
src/assets/img/overview/close.png


BIN
src/assets/img/overview/develop.png


BIN
src/assets/img/overview/line.png


BIN
src/assets/img/overview/location.png


BIN
src/assets/img/overview/map.png


BIN
src/assets/img/overview/riverBg.png


BIN
src/assets/img/overview/timeIcon.png


BIN
src/assets/img/overview/titleBg.png


BIN
src/assets/img/part/label@2x.png


BIN
src/assets/img/part/part1.png


BIN
src/assets/img/pause.png


BIN
src/assets/img/play.png


BIN
src/assets/img/right.png


BIN
src/assets/img/search/right.png


BIN
src/assets/img/search/searchBoxBg.png


BIN
src/assets/img/search/searchBtnBg.png


BIN
src/assets/img/search/searchIcon.png


BIN
src/assets/img/tabbar/activedian.png


BIN
src/assets/img/tabbar/itemBg.png


BIN
src/assets/img/tabbar/menu.png


BIN
src/assets/img/tabbar/menu_active.png


BIN
src/assets/img/tabbar/search.png


BIN
src/assets/img/tabbar/tabbarBg.png


BIN
src/assets/img/up.png


BIN
src/assets/img/villages/border.png


BIN
src/assets/img/villages/borderAc.png


BIN
src/assets/img/villages/building.jpg


+ 0 - 281
src/components/AudioBox.vue

@@ -1,281 +0,0 @@
-<script setup lang="ts">
-import { stat } from "fs";
-import { kill } from "process";
-import { showToast } from "vant";
-import wx from "weixin-js-sdk";
-
-const route = useRoute()
-
-// 播放状态
-const state = ref(false);
-const stateChange = (stateNew: boolean) => {
-  console.log("ooo");
-  var audio = document.getElementById("music1");
-  if (stateNew) {
-    audio!.play();
-  } else {
-    audio!.pause();
-  }
-  state.value = stateNew;
-};
-const getAssetURL = (image: string) => {
-  return new URL(`../assets/img/${image}`, import.meta.url).href;
-};
-
-const props = defineProps({
-  audioUrl: {
-    default: "",
-    type: String,
-  },
-});
-
-const allTime = ref("");
-const durationRef = ref(null);
-
-// 拖拽进度条
-const handleMousedown = (e: any) => {
-  const progressBg = document.getElementById("progressBg");
-
-  // 改变播放状态
-  state.value = false;
-  // 计算进度条距离
-  let moveMin = progressBg?.offsetParent?.offsetLeft + progressBg?.offsetLeft;
-  let moveMax =
-    progressBg?.offsetParent?.offsetLeft +
-    progressBg?.offsetLeft +
-    progressBg?.clientWidth;
-
-  if (e.targetTouches[0].pageX >= moveMax) {
-    return;
-  } else if (e.targetTouches[0].pageX <= moveMin) {
-    return;
-  }
-  // circle!.style.left = e.targetTouches[0].pageX - moveMin - circleWidth + "px";
-  updateProgress2(e.targetTouches[0].pageX - moveMin);
-};
-
-// 更新进度条(拖动进度条)
-const updateProgress2 = (moveX: number) => {
-  const progressBg = document.getElementById("progressBg");
-  const progressBar = document.getElementById("progressBar");
-  var audio = document.getElementById("music1");
-  //当前移动的位置 = 当前移动的位置 / 当前进度条的可视长度    //this.$refs.progress.clientWidth 注意一定要拿总长度 否则会拿进度条已经走过的长度
-  let clickProgress = moveX / progressBg!.clientWidth;
-  //设置播放的时间 = 总时长 * 当前点击的长度
-  audio!.currentTime = audio!.duration * clickProgress;
-
-  //设置移动的位置
-  progressBar!.style.width = moveX + "px";
-
-  var audio = document.getElementById("music1");
-  setTimeout(() => {
-    audio!.play();
-    state.value = true;
-  }, 200);
-};
-
-// 更新进度条(播放中)
-const updateProgress = (e: any) => {
-  e.target.autoplay = true;
-  const progressBar = document.getElementById("progressBar");
-
-  var value = e.target.currentTime / e.target.duration;
-  if (progressBar) {
-    progressBar!.style.width = value * 100 + "%";
-    if (e.target.currentTime === e.target.duration) {
-      state.value = false;
-    }
-  } else {
-    state.value = true;
-  }
-  durationRef.value =
-    e.target.duration - e.target.currentTime <= 1
-      ? timeToMinute(e.target.duration - e.target.currentTime)
-      : "-" + timeToMinute(e.target.duration - e.target.currentTime);
-};
-
-const formatePlayerDuration = (node: any) => {
-  node.onloadedmetadata = (e: any) => {
-    const audio = e.target;
-    const audioDuration = audio.duration;
-    if (audioDuration === Infinity) {
-      audio.currentTime = 1e101;
-      audio.ontimeupdate = () => {
-        audio.ontimeupdate = () => {
-          return;
-        };
-        // 不重新设置currtTime,会直接触发audio的ended事件,因为之前将currentTime设置成了一个比音频时长还大的值。所以要将currentTime重置为初始状态。
-        // 注: 这里有一个问题,直接设置为0 是不起作用的。需要重新设置一下audio.currentTime = 1e101;然后再设置为
-        audio.currentTime = 1e101;
-        audio.currentTime = 0;
-      };
-    }
-  };
-};
-
-// 计算音频的时长
-const countAudioTime = async () => {
-  var audio = document.getElementById("music1");
-
-  while (isNaN(audio!.duration) || audio!.duration === Infinity) {
-    // 延迟一会 不然网页都卡死
-    await new Promise((resolve) => setTimeout(resolve, 200));
-    // 设置随机播放时间,模拟调进度条
-    // audio!.currentTime = 10000000 * Math.random();
-    durationRef.value = "-" + timeToMinute(audio!.duration);
-  }
-  // showToast(audio!.duration);
-  // console.log("音频的总时长:", audio.duration);
-};
-
-const getDuration = () => {
-  countAudioTime();
-  // formatePlayerDuration(audio)
-};
-const timeToMinute = (times: any) => {
-  var t = "";
-  if (times > -1) {
-    var min = Math.floor(times / 60) % 60;
-    var sec = times % 60;
-    if (min < 10) {
-      t += "0";
-    }
-    t += min + ":";
-    if (sec < 10) {
-      t += "0";
-    }
-    t += sec.toFixed(2);
-  }
-  t = t?.substring(0, t.length - 3);
-  return t;
-};
-
-const clear = () => {
-  const progressBar = document.getElementById("progressBar");
-  progressBar!.style.width = 0 + "px";
-  var audio = document.getElementById("music1");
-  audio!.play();
-  state.value = true;
-};
-
-//暴露state和play方法
-defineExpose({
-  clear,
-});
-
-const autoPlayFn = () => {
-  const IS_IOS = !/(Android)/i.test(navigator.userAgent); //ios终端
-  if (IS_IOS) {
-    wx.config({
-      // 配置信息, 即使不正确也能使用 wx.ready
-      debug: false,
-      appId: "",
-      timestamp: 1,
-      nonceStr: "",
-      signature: "",
-      jsApiList: [],
-    });
-    wx.ready(function () {
-      document.getElementById("music1")!.load();
-      document.getElementById("music1")!.play();
-      state.value = true;
-    });
-  }
-};
-
-
-
-onMounted(() => {
-  getDuration();
-  clear();
-  autoPlayFn();
-});
-</script>
-
-<template>
-  <audio
-    id="music1"
-    :src="props.audioUrl"
-    @timeupdate="updateProgress"
-    controls
-    ref="audioRef"
-    style="display: none"
-    @canplay="getDuration"
-    preload="meta"
-  ></audio>
-  <div class="audio-box">
-    <img
-      @click="stateChange(!state)"
-      :src="state ? getAssetURL('pause.png') : getAssetURL('play.png')"
-    />
-    <div class="progress-box">
-      <div class="progress-bg" id="progressBg">
-        <div class="progress-active" id="progressBar">
-          <div
-            class="active-o"
-            id="progressBtn"
-            @touchmove="handleMousedown"
-          ></div>
-        </div>
-      </div>
-    </div>
-    <div class="time">{{ durationRef }}</div>
-  </div>
-</template>
-
-<style lang="less" scoped>
-.audio-box {
-  width: 100%;
-  height: 100%;
-  padding: 0;
-  border-radius: 50px;
-  background: rgba(0, 0, 0, 0.3);
-  box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.6);
-  border: 1px solid rgba(0, 0, 0, 0.4);
-  display: flex;
-  justify-content: space-between;
-
-  .progress-box {
-    width: 85%;
-    height: 100%;
-    display: flex;
-    position: relative;
-    align-items: center;
-    justify-content: right;
-    .progress-bg {
-      width: 90%;
-      height: 2px;
-      background: rgba(255, 255, 255, 0.6);
-      position: absolute;
-    }
-    .progress-active {
-      width: 0;
-      height: 2px;
-      background: #3c7e70;
-      position: relative;
-
-      .active-o {
-        width: 13px;
-        height: 13px;
-        border-radius: 100%;
-        background: #3c7e70;
-        position: absolute;
-        right: 0;
-        transform: translate(50%, -45%);
-        z-index: 10000;
-      }
-    }
-  }
-  .time {
-    width: 20%;
-    height: 100%;
-    font-size: 14px;
-    color: rgba(255, 255, 255, 0.493);
-    text-align: center;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    margin-left: 5px;
-  }
-}
-</style>

+ 366 - 0
src/components/Tabbar.vue

@@ -0,0 +1,366 @@
+<script setup lang="ts">
+import searchIcon from "@/assets/img/tabbar/search.png";
+import toolIconAc from "@/assets/img/tabbar/menu_active.png";
+import toolIcon from "@/assets/img/tabbar/menu.png";
+import dian from "@/assets/img/tabbar/activedian.png";
+import { useStore } from "@/store/index";
+
+const isHide = ref(false);
+const store = useStore();
+const router = useRouter();
+
+const changePage = (index: number) => {
+  store.currentModelIndex = index;
+};
+
+const upDateRoute = (index: number) => {
+  let path = "";
+  switch (index) {
+    case 1:
+      path = "/overview";
+      break;
+    case 2:
+      path = "/village";
+      break;
+    case 3:
+      path = "/building";
+      break;
+    case 4:
+      path = "/part";
+      break;
+  }
+  router.push({
+    path: path,
+  });
+};
+
+// const goSearch = () => {
+//   router.replace("/search");
+// };
+
+// 搜索部分
+const isShowSearch = ref(false);
+
+store.$subscribe((mutation: any, state: any) => {
+  upDateRoute(state.currentModelIndex);
+});
+
+const resList = ref([
+  {
+    name: "山花",
+    type: "构件",
+  },
+  {
+    name: "碉楼",
+    type: "构件",
+  },
+  {
+    name: "别墅",
+    type: "建筑",
+  },
+  {
+    name: "自立村",
+    type: "村落",
+  },
+]);
+
+onMounted(() => {
+  upDateRoute(store.currentModelIndex);
+});
+</script>
+
+<template>
+  <div class="tabbar" v-show="!isHide">
+    <div class="box">
+      <div
+        class="box-item"
+        :class="store.currentModelIndex == 1 ? 'active' : ''"
+        @click="changePage(1)"
+      >
+        总览<img v-show="store.currentModelIndex == 1" :src="dian" alt="" />
+      </div>
+      <div
+        class="box-item"
+        :class="store.currentModelIndex == 2 ? 'active' : ''"
+        @click="changePage(2)"
+      >
+        村落<img v-show="store.currentModelIndex == 2" :src="dian" alt="" />
+      </div>
+    </div>
+    <div class="box">
+      <div
+        class="box-item"
+        :class="store.currentModelIndex == 3 ? 'active' : ''"
+        @click="changePage(3)"
+      >
+        建筑<img v-show="store.currentModelIndex == 3" :src="dian" alt="" />
+      </div>
+      <div
+        class="box-item"
+        :class="store.currentModelIndex == 4 ? 'active' : ''"
+        @click="changePage(4)"
+      >
+        构件<img v-show="store.currentModelIndex == 4" :src="dian" alt="" />
+      </div>
+    </div>
+    <div class="tool-box">
+      <img
+        @click="
+          () => {
+            isShowSearch = !isShowSearch;
+          }
+        "
+        :src="searchIcon"
+        alt=""
+      />
+      <img
+        @click="
+          () => {
+            isHide = true;
+          }
+        "
+        :src="toolIconAc"
+        alt=""
+      />
+    </div>
+  </div>
+  <div
+    v-if="isHide"
+    class="openBtn"
+    @click="
+      () => {
+        isHide = false;
+      }
+    "
+  >
+    <div>
+      <img :src="toolIcon" alt="" />
+    </div>
+  </div>
+
+  <!-- 搜索部分 -->
+  <div class="search-box" v-show="isShowSearch">
+    <div class="search-title">近代建筑一张图</div>
+    <div class="search-dics">开平碉楼与村落及赤坎旧镇</div>
+    <div class="box">
+      <img src="../assets/img/search/searchIcon.png" alt="" />
+      <input type="text" placeholder="请输入关键字" />
+      <div class="searcBtn">搜索</div>
+    </div>
+    <div class="list-box">
+      <div class="list-item" v-for="(item, index) in resList" :style="{borderBottom: index == resList.length - 1 ? '' : '2px dashed #B19C7D'}">
+        <div class="item-left">
+          <div class="item-name">{{ item.name }}</div>
+          <div class="item-type">{{ item.type }}</div>
+        </div>
+        <div class="item-right">
+          查看<img src="../assets/img/search/right.png" alt="" />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="less" scoped>
+@font-face {
+  font-family: "SourceHanSerifCN-Bold"; /* 字体名称 */
+  src: url("../assets/font/SourceHanSerifCN-Bold.otf"); /* 字体文件相对路径 */
+}
+.tabbar {
+  width: 80%;
+  margin: auto;
+  height: 85px;
+  background: url(\src/assets/img/tabbar/tabbarBg.png);
+  background-size: 100% 100%;
+  display: flex;
+  justify-content: space-between;
+  padding: 0 8%;
+  position: relative;
+  z-index: 10000;
+
+  .box {
+    width: 38%;
+    height: 48px;
+    display: flex;
+    justify-content: space-evenly;
+    margin-top: 16px;
+
+    &-item {
+      width: 30%;
+      height: 100%;
+      text-align: center;
+      font-size: 20px;
+      // background: green;
+      color: white;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      font-family: "Source Han Sans CN, Source Han Sans CN-Regular";
+      cursor: pointer;
+      position: relative;
+      img {
+        position: absolute;
+        width: 85%;
+      }
+    }
+    .active {
+      color: #fce9ac;
+      font-weight: bold;
+      font-family: "Source Han Sans CN, Source Han Sans CN-Bold";
+      background: url(\src/assets/img/tabbar/itemBg.png);
+      background-size: 100% 100%;
+    }
+  }
+
+  .tool-box {
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+    right: 10px;
+    img {
+      width: 20px;
+      margin-right: 30px;
+      cursor: pointer;
+    }
+  }
+}
+.openBtn {
+  width: 45px;
+  height: 45px;
+  border-radius: 50%;
+  // background: #a28b5f;
+  border: 1px solid #a28b5f;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  position: absolute;
+  right: 11%;
+  top: 45px;
+  z-index: 10000;
+
+  div {
+    width: 40px;
+    height: 40px;
+    border-radius: 50%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    background: #a28b5f;
+    img {
+      width: 20px;
+    }
+  }
+}
+
+.search-box {
+  width: 100%;
+  height: 100%;
+  padding-top: 30px;
+  background: #fff;
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 5;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  .search-title {
+    font-size: 48px;
+    font-weight: bold;
+    color: #403422;
+    font-family: "SourceHanSerifCN-Bold";
+  }
+  .search-dics {
+    font-size: 18px;
+    font-weight: bold;
+    color: #403422;
+    font-family: "SourceHanSerifCN-Bold";
+    margin-top: 5px;
+  }
+  .box {
+    width: 35%;
+    height: 60px;
+    background: url(\src/assets/img/search/searchBoxBg.png);
+    background-size: 100% 100%;
+    padding-left: 30px;
+    padding-right: 20px;
+    display: flex;
+    align-items: center;
+    justify-content: start;
+    margin: 30px;
+    img {
+      width: 25px;
+      margin-right: 20px;
+    }
+    input {
+      // border: none;
+      height: 30px;
+      width: 75%;
+      border: none;
+      font-size: 18px;
+      color: #403422;
+    }
+    .searcBtn {
+      width: 15%;
+      height: 65%;
+      background: url(\src/assets/img/search/searchBtnBg.png);
+      background-size: 100% 100%;
+      text-align: center;
+      font-size: 18px;
+      // font-weight: bold;
+      letter-spacing: 2px;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      color: #fff;
+      cursor: pointer;
+    }
+  }
+  .list-box {
+    margin-top: 30px;
+    width: 35%;
+    height: 35%;
+    overflow: auto;
+    .list-item {
+      width: 100%;
+      height: 50px;
+      display: flex;
+      justify-content: space-between;
+      margin-top: 10px;
+      .item-left {
+        display: flex;
+        justify-content: start;
+        align-items: center;
+        .item-name {
+          font-size: 16px;
+          color: #2f281e;
+        }
+        .item-type {
+          font-size: 12px;
+          color: #403422;
+          width: 40px;
+          line-height: 20px;
+          background: #fce9ac;
+          border-radius: 5px;
+          border: 1px solid #b19c7d;
+          text-align: center;
+          margin-left: 10px;
+        }
+      }
+      .item-right {
+        display: flex;
+        justify-content: end;
+        align-items: center;
+        font-size: 16px;
+        color: #b19c7d;
+        font-family: "Source Han Sans CN-Regular, Source Han Sans CN";
+        img {
+          width: 20px;
+          margin-left: 10px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 0 - 110
src/components/progress.vue

@@ -1,110 +0,0 @@
-<template>
-  <div class="progress-bar" @click="clickProgress" ref="progressBar" :style="{background:props.bgColor}">
-    <div class="progress" :style="{ width: progressWidth,background:props.activeColor }"></div>
-    <div class="progress-handle" @mousedown="startDrag" :style="{ background:props.activeColor }">{{ progress }}</div>
-  </div>
-</template>
-
-<script setup>
-import {ref, computed, watch, getCurrentInstance} from 'vue';
-
-const {proxy} = getCurrentInstance()
-const props = defineProps({
-  // 背景色
-  bgColor: {
-    type: String,
-    default: '#f0f0f0'
-  },
-  // 前景色
-  activeColor: {
-    type: String,
-    default: '#4caf50'
-  },
-  //当前进度
-  percentage: {
-    type: Number,
-    default: 0
-  }
-})
-
-const progress = ref(0);
-const progressBar = ref(null);
-let dragging = false;
-let dragStartX = 0;
-
-
-watch(
-    () => [progress, props.percentage],
-    ([progress, percentage]) => {
-      if (percentage) {
-        // progress.value = percentage.toFixed(2)
-      }
-    },
-    {deep: true}
-)
-
-const updateProgress = (clientX) => {
-  const progressBarRect = progressBar.value.getBoundingClientRect();
-  const progressWidthPercentage = ((clientX - progressBarRect.left) / progressBarRect.width) * 100;
-  progress.value = Math.max(0, Math.min(progressWidthPercentage, 100)).toFixed(2);
-  proxy.$emit('valueChange', progress.value)
-};
-
-const startDrag = (event) => {
-  dragging = true;
-  dragStartX = event.clientX;
-  document.addEventListener('mousemove', handleDrag);
-  document.addEventListener('mouseup', endDrag);
-};
-
-const handleDrag = (event) => {
-  if (dragging) {
-    updateProgress(event.clientX);
-  }
-};
-
-const endDrag = () => {
-  dragging = false;
-  document.removeEventListener('mousemove', handleDrag);
-  document.removeEventListener('mouseup', endDrag);
-};
-
-const clickProgress = (event) => {
-  updateProgress(event.clientX);
-};
-
-const progressWidth = computed(() => {
-  return progress.value + '%';
-});
-
-</script>
-
-<style scoped>
-.progress-bar {
-  width: 99%;
-  height: 20px;
-  border-radius: 10px;
-  display: flex;
-  justify-content: start;
-  position: relative;
-  cursor: pointer;
-}
-
-.progress {
-  height: 100%;
-  border-radius: 10px;
-  transition: width 0.2s;
-}
-
-.progress-handle {
-  width: 40px;
-  height: 40px;
-  border-radius: 50%;
-  margin-top: -10px;
-  margin-left: -10px;
-  line-height: 40px;
-  color: #fff;
-  text-align: center;
-}
-</style>
-

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 118 - 535
src/data/data.ts


+ 28 - 9
src/routes/index.ts

@@ -5,20 +5,39 @@ let routes = [
   {
     path: "/",
     name: "home",
-    component: () => import("@/view/home/index.vue"),
+    component: () => import("@/view/welcome/index.vue"),
   },
-  // 展馆 -> 音频列表
+  // 总览首页
   {
-    path: "/exhibitionList",
-    name: "exhibitionList",
-    component: () => import("@/view/exhibitionList/index.vue"),
+    path: "/overview",
+    name: "overview",
+    component: () => import("@/view/overview/index.vue"),
   },
-  // 展馆 -> 音频详情
+  // 村落
   {
-    path: "/audioDetail",
-    name: "audioDetail",
-    component: () => import("@/view/audioDetail/index.vue"),
+    path: "/village",
+    name: "village",
+    component: () => import("@/view/village/index.vue"),
   },
+  // 建筑
+  {
+    path: "/building",
+    name: "building",
+    component: () => import("@/view/building/index.vue"),
+  },
+  // 构件
+  {
+    path: "/part",
+    name: "part",
+    component: () => import("@/view/part/index.vue"),
+  },
+   // 搜索
+   {
+    path: "/search",
+    name: "search",
+    component: () => import("@/view/search/index.vue"),
+  },
+  
 ];
 // 路由
 const router = createRouter({

+ 1 - 28
src/store/index.ts

@@ -1,43 +1,16 @@
 
 import { defineStore } from "pinia";
 
-export type Paragraph = {
-  paragraphName: string;
-  contentText: string;
-};
-
-export type Part = {
-  partName: string;
-  paragraphs: Paragraph[];
-};
-
-export type Exhibition = {
-  id: number;
-  name: string;
-  parts: Part[];
-};
-
 export const useStore = defineStore("home", {
   // 相当于data
   state: () => {
     return {
-      currentScene: {} as Exhibition,
-      currentParagraph: {} as Paragraph,
-      currentPart:{} as Part
+      currentModelIndex: 1
     };
   },
   // 相当于计算属性
   getters: {},
   // 相当于vuex的 mutation + action,可以同时写同步和异步的代码
   actions: {
-    setCurrentScene(currentScene: Exhibition) {
-      this.currentScene = currentScene;
-    },
-    setCurrentParagraph(currentP: Paragraph) {
-      this.currentParagraph = currentP;
-    },
-    setCurrentPart(currentPart: Part) {
-      this.currentPart = currentPart;
-    },
   },
 });

+ 0 - 262
src/view/audioDetail/index.vue

@@ -1,262 +0,0 @@
-<script setup lang="ts">
-import dataList from "@/data/data";
-import { Exhibition, Paragraph, Part, useStore } from "@/store/index";
-import { showToast } from "vant";
-import AudioBoxVue from "@/components/AudioBox.vue";
-
-const route = useRoute();
-const router = useRouter();
-const store = useStore();
-
-const currentP = ref();
-const getAssetURL = (image: string) => {
-  return new URL(`../../assets/img/${image}`, import.meta.url).href;
-};
-
-// 查看全部
-const goBack = () => {
-  router.push({
-    path: "/exhibitionList",
-  });
-};
-
-// 上一个
-const prev = () => {
-  // 当前段落下标
-  let indexPP = store.currentPart.paragraphs.findIndex((item: Paragraph) => {
-    return item.paragraphName == currentP.value.paragraphName;
-  });
-  // 当前Part的下标
-  let indexP = store.currentScene.parts.findIndex((item: Part) => {
-    return item.partName == store.currentPart.partName;
-  });
-  //console.log(indexPP, indexP);
-  if (indexPP === 0 && indexP == 0) {
-    showToast("暂无更多");
-  } else if (indexPP === 0 && indexP != 0) {
-    //console.log("切换");
-    // 当前段落为part第一章,则part - 1, 段落为part - 1的最后一个段落
-    indexP -= 1;
-    indexPP = store.currentScene.parts[indexP].paragraphs.length - 1;
-    //console.log(indexP, indexPP);
-  } else {
-    // 当前章节的非第一段落
-    indexPP -= 1;
-  }
-  // 更新仓库章节
-  store.currentPart = store.currentScene.parts[indexP];
-  // 更新仓库段落
-  store.currentParagraph = store.currentPart.paragraphs[indexPP];
-  currentP.value = store.currentParagraph;
-};
-
-// 下一个
-const next = () => {
-  // 当前段落下标
-  let indexPP = store.currentPart.paragraphs.findIndex((item: Paragraph) => {
-    return item.paragraphName == currentP.value.paragraphName;
-  });
-  // 当前Part的下标
-  let indexP = store.currentScene.parts.findIndex((item: Part) => {
-    return item.partName == store.currentPart.partName;
-  });
-  //console.log(indexPP, indexP);
-  if (
-    indexPP === store.currentPart.paragraphs.length - 1 &&
-    indexP == store.currentScene.parts.length - 1
-  ) {
-    showToast("暂无更多");
-  } else if (
-    indexPP === store.currentPart.paragraphs.length - 1 &&
-    indexP != store.currentScene.parts.length - 1
-  ) {
-    //console.log("切换");
-    // 当前段落为part最后一章,则part + 1, 段落为part + 1的第一个段落
-    indexP += 1;
-    indexPP = 0;
-    //console.log(indexP, indexPP);
-  } else {
-    // 当前章节的非第一段落
-    indexPP += 1;
-  }
-  // 更新仓库章节
-  store.currentPart = store.currentScene.parts[indexP];
-  // 更新仓库段落
-  store.currentParagraph = store.currentPart.paragraphs[indexPP];
-  currentP.value = store.currentParagraph;
-};
-
-const audiobox = ref(null);
-watch(currentP, () => {
-  console.log(audiobox.value);
-  if (audiobox.value == null) {
-    return;
-  } else {
-    audiobox!.value.clear();
-  }
-});
-
-onMounted(() => {
-  // 跳转进入(传入段落名和段落编号)
-  const sName = route.query.sName;
-  const pNumb = route.query.pNum;
-  console.log(route.query.sName, route.query.pNum);
-  if (sName != "" && pNumb) {
-    const s = dataList.find((item: Exhibition) => {
-      // return item.name == sName;
-      return item.name.indexOf(sName) != -1 || sName.indexOf(item.name) != -1;
-    });
-    // console.log(dataList);
-    // console.log(s);
-    // 遍历所有的part
-    s?.parts.forEach((item: Part) => {
-      const res = item.paragraphs.find((p: Paragraph) => {
-        return p.paragraphName.indexOf(pNumb) != -1;
-      });
-      if (res) {
-        store.currentParagraph = res;
-        store.currentPart = item;
-        store.currentScene = s;
-        return;
-      }
-    });
-  }
-  currentP.value = store.currentParagraph;
-});
-
-const htmlD = ref("<div>献给所有为了梦想狂奔的人</div><br />你好");
-</script>
-
-<template>
-  <div class="all" v-if="currentP">
-    <div class="content">
-      <div class="top-img">
-        <img :src="getAssetURL(`home/${store.currentScene.coverImg}`)" alt="" />
-      </div>
-      <div class="content-box">
-        <div class="content-title">{{ currentP.paragraphName }}</div>
-        <div class="p">
-          <div v-html="currentP.contentText"></div>
-        </div>
-      </div>
-      <div class="control-box">
-        <!-- <audio :src="currentP.audioUrl"></audio> -->
-        <div class="control-top">
-          <div class="top-audio">
-            <!-- <audio id="audiom" :src="currentP.audioUrl" preload="meta"></audio> -->
-            <AudioBox :audioUrl="currentP.audioUrl" ref="audiobox" />
-            <!-- <audioPlayer :audioUrl="currentP.audioUrl" /> -->
-          </div>
-        </div>
-        <div class="control-bottom">
-          <div class="bottom-left" @click.stop="prev()">
-            <img :src="getAssetURL('left.png')" alt="" />
-          </div>
-          <div class="bottom-center" @click.stop="goBack()">查看全部</div>
-          <div class="bottom-right" @click.stop="next()">
-            <img :src="getAssetURL('right.png')" alt="" />
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<style lang="less" scoped>
-.all {
-  width: 100%;
-  height: 100%;
-  padding: 5% 5% 0 5%;
-  overflow: hidden;
-
-  .content {
-    width: 100%;
-    height: 100%;
-    background: url(/src\assets\img\contentBg.png);
-    background-size: 100% 100%;
-    border-radius: 8px 8px 0 0;
-    position: relative;
-    .top-img {
-      width: 100%;
-      // height: 30vh;
-      img {
-        width: 100%;
-        height: 100%;
-        object-fit: cover;
-        box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
-      }
-    }
-    .content-box {
-      width: 100%;
-      height: 55vh;
-      overflow: auto;
-      padding: 5%;
-      .content-title {
-        text-align: center;
-        font-size: 1.1rem;
-        font-weight: bold;
-        text-shadow: 0px 3px 5px rgba(0, 0, 0, 0.25);
-      }
-      .p {
-        font-size: 1rem;
-        line-height: 1.5rem;
-        letter-spacing: 6px;
-        font-weight: 500;
-        white-space: pre-wrap;
-        margin-top: 10px;
-
-        .html-box {
-          div {
-            text-indent: 2em;
-          }
-        }
-      }
-    }
-
-    .control-box {
-      width: 100%;
-      position: absolute;
-      bottom: 0;
-
-      .control-top {
-        height: 7vh;
-        background: rgba(0, 0, 0, 0.8);
-        display: flex;
-        justify-content: center;
-        align-content: center;
-        position: relative;
-        z-index: 10;
-
-        .top-audio {
-          width: 90%;
-          height: 60%;
-          margin: auto;
-        }
-      }
-      .control-bottom {
-        width: 100%;
-        display: flex;
-        justify-content: space-between;
-        box-shadow: 0px 0px 3px 0px rgba(0, 0, 0, 0.6);
-
-        div {
-          width: 33%;
-          height: 60px;
-          background: rgba(0, 0, 0, 0.8);
-          display: flex;
-          justify-content: center;
-          align-items: center;
-          font-size: 1rem;
-          color: white;
-          font-weight: bold;
-          letter-spacing: 2px;
-
-          img {
-            width: 7vw;
-          }
-        }
-      }
-    }
-  }
-}
-</style>

+ 11 - 0
src/view/building/index.vue

@@ -0,0 +1,11 @@
+<script setup lang="ts">
+import Tabbar from "@/components/Tabbar.vue";
+</script>
+
+<template>
+  <div class="">
+    <Tabbar />
+  </div>
+</template>
+
+<style lang="less" scoped></style>

+ 0 - 187
src/view/exhibitionList/index.vue

@@ -1,187 +0,0 @@
-<script setup lang="ts">
-import homeIcon from "@/assets/img/home-icon.png";
-import dataList from "@/data/data";
-import { useStore } from "@/store/index";
-
-const route = useRoute();
-const router = useRouter();
-const store = useStore();
-
-const goBack = (id: string) => {
-  router.push({
-    path: "/",
-  });
-};
-
-const goDetail = (item: string, index: number) => {
-  store.currentPart = currentScene.value.parts[index];
-  store.currentParagraph = item;
-  router.push({
-    path: "/audioDetail",
-  });
-};
-const currentPart = ref(-1);
-const open = (partIndex: number) => {
-  if (currentPart.value == partIndex) {
-    currentPart.value = 0;
-  } else {
-    currentPart.value = partIndex;
-  }
-};
-
-const getAssetURL = (image: string) => {
-  return new URL(`../../assets/img/${image}`, import.meta.url).href;
-};
-
-const currentScene = ref();
-
-onMounted(() => {
-  if (route.query.id) {
-    currentScene.value = dataList.find((item) => {
-      return item.id == Number(route.query.id);
-    });
-    store.currentScene = currentScene.value;
-  } else {
-    currentScene.value = store.currentScene;
-  }
-  currentPart.value = 1;
-});
-</script>
-
-<template>
-  <div class="all" v-if="currentScene">
-    <div class="content">
-      <div class="top-img">
-        <img :src="getAssetURL(`home/${currentScene.coverImg}`)" alt="" />
-      </div>
-      <div class="content-box">
-        <div
-          class="content-box-item"
-          v-for="(item, index) in currentScene.parts"
-          :key="item.partName"
-        >
-          <div class="item-title" @click="open(index)">
-            {{ item.partName }}
-            <img
-              v-show="index != 0"
-              :src="
-                currentPart == index
-                  ? getAssetURL('up.png')
-                  : getAssetURL('down.png')
-              "
-              alt=""
-            />
-          </div>
-          <div class="item-list" v-if="currentPart == index || index == 0">
-            <div
-              class="item-list-item"
-              v-for="(p, i) in item.paragraphs"
-              @click="goDetail(p, index)"
-            >
-              {{ p.paragraphName }}
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-    <!-- 回到主页按钮 -->
-    <img :src="homeIcon" alt="" class="home-icon" @click="goBack" />
-  </div>
-</template>
-
-<style lang="less" scoped>
-.all {
-  width: 100%;
-  height: 100%;
-  padding: 5%;
-  overflow: hidden;
-
-  .content {
-    width: 100%;
-    height: 110%;
-    background: url(/src\assets\img\contentBg.png);
-    background-size: 100% 100%;
-    border-radius: 8px 8px 0 0;
-    .top-img {
-      width: 100%;
-      // height: 30vh;
-      img {
-        width: 100%;
-        height: 100%;
-        object-fit: cover;
-        box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
-      }
-    }
-    .content-box {
-      width: 100%;
-      height: 70vh;
-      overflow: auto;
-      padding: 5%;
-
-      &-item {
-        width: 100%;
-        margin-bottom: 15px;
-
-        .item-title {
-          width: 100%;
-          height: 8vh;
-          background: rgba(44, 44, 44, 0.8);
-          border-radius: 12px 12px 0px 0px;
-          color: white;
-          font-size: 16px;
-          font-weight: bold;
-          text-align: center;
-          line-height: 8vh;
-          position: relative;
-          img {
-            width: 6vw;
-            height: 6vw;
-            opacity: 0.6;
-            position: absolute;
-            top: 50%;
-            right: 5%;
-            transform: translateY(-50%);
-          }
-        }
-
-        .item-list {
-          transition: height 2s;
-          &-item {
-            width: 100%;
-            height: 8vh;
-            // height: 10vh;
-            color: white;
-            background: rgba(60, 126, 112, 0.6);
-            box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.2);
-            margin-top: 2px;
-            font-size: 15px;
-            text-align: center;
-            line-height: 8vh;
-            font-weight: bold;
-            // white-space: nowrap;
-            // overflow: hidden;
-            // text-overflow: ellipsis;
-          }
-        }
-      }
-
-      // background: url(/src\assets\img\contentBg.png);
-      // background-size: 100% 100%;
-    }
-    .content-box::-webkit-scrollbar-track {
-      /*滚动条里面轨道*/
-      -webkit-box-shadow: inset 0 0 5px transparent;
-      border-radius: 10px;
-      background: transparent;
-    }
-  }
-  .home-icon {
-    width: 15vw;
-    height: 15vw;
-    position: fixed;
-    right: 20px;
-    bottom: 30px;
-    opacity: 0.7;
-  }
-}
-</style>

+ 0 - 78
src/view/home/index.vue

@@ -1,78 +0,0 @@
-<script setup lang="ts">
-import dataList from "@/data/data";
-
-const getAssetURL = (image: string) => {
-  return new URL(`../../assets/img/home/${image}`, import.meta.url).href;
-};
-
-const router = useRouter();
-const goDetail = (id: string) => {
-
-  router.push(
-    {
-      path: "/exhibitionList",
-      query: {
-        id: id,
-      },
-    },
-    5000
-  );
-};
-onMounted(() => {
-  //console.log(dataList);
-});
-</script>
-
-<template>
-  <div class="all">
-    <div
-      v-for="(item, index) in dataList"
-      :key="item.id"
-      class="box"
-      :style="{
-        marginBottom: index == dataList.length - 1 ? '' : '20px',
-      }"
-      @click="goDetail(item.id)"
-    >
-      <img :src="getAssetURL(item.coverImg)" alt="" />
-      <div class="title-box">{{ item.name }}</div>
-    </div>
-  </div>
-</template>
-
-<style lang="less" scoped>
-.all {
-  width: 100%;
-  height: 100%;
-  padding: 8%;
-  overflow: auto;
-  .box {
-    width: 100%;
-    height: 31%;
-    position: relative;
-    border-radius: 8px;
-    box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
-
-    img {
-      width: 100%;
-      height: 100%;
-      border-radius: 8px;
-    }
-    .title-box {
-      width: 100%;
-      height: 2.5rem;
-      background: #2c2c2c;
-      position: absolute;
-      bottom: 0px;
-      border-radius: 0 0 8px 8px;
-      font-family: "Microsoft YaHei-Bold, Microsoft YaHei";
-      font-size: 14px;
-      color: #ffffff;
-      line-height: 2.5rem;
-      letter-spacing: 2px;
-      font-weight: bold;
-      padding-left: 5%;
-    }
-  }
-}
-</style>

+ 157 - 0
src/view/overview/components/incident.vue

@@ -0,0 +1,157 @@
+<script setup lang="ts">
+import TimeIcon from "@/assets/img/overview/timeIcon.png";
+import lineImg from "@/assets/img/overview/line.png";
+
+const props = defineProps({
+  isHightLight: {
+    type: Boolean,
+    default: false,
+  },
+  time: {
+    type: String,
+    default: "",
+  },
+  content: {
+    type: String,
+    default: "",
+  },
+  isContentShow: {
+    type: Boolean,
+    default: false,
+  },
+  top: {
+    type: String,
+    default: "0",
+  },
+  left: {
+    type: String,
+    default: "0",
+  },
+});
+const isShow = ref(false);
+onMounted(() => {
+  isShow.value = props.isContentShow;
+});
+</script>
+
+<template>
+  <div
+    class="incident-box"
+    :style="{
+      top: props.top + 'px',
+      left: props.left + 'px',
+    }"
+  >
+    <img
+      :src="TimeIcon"
+      alt=""
+      @click="
+        () => {
+          isShow = !isShow;
+        }
+      "
+    />
+    <div
+      class="time"
+      @click="
+        () => {
+          isShow = !isShow;
+        }
+      "
+    >
+      {{ props.time }}
+    </div>
+    <div class="content-box" v-show="isShow">
+      <div>
+        <div class="content-top">历史事件</div>
+        <img src="../../../assets/img/overview/line.png" alt="" />
+        <div class="content-span">{{ props.content }}</div>
+      </div>
+    </div>
+    <img
+      class="close-btn"
+      v-show="isShow"
+      src="../../../assets/img/overview/close.png"
+      alt=""
+      @click="
+        () => {
+          isShow = false;
+        }
+      "
+    />
+  </div>
+</template>
+
+<style lang="less" scoped>
+@font-face {
+  font-family: "SourceHanSerifCN-Bold"; /* 字体名称 */
+  src: url("../../../assets/font/SourceHanSerifCN-Bold.otf"); /* 字体文件相对路径 */
+}
+.incident-box {
+  width: 200px;
+  position: absolute;
+  img {
+    width: 60px;
+    cursor: pointer;
+  }
+  .time {
+    color: #fff;
+    font-size: 14px;
+    cursor: pointer;
+  }
+  .content-box {
+    width: 100%;
+    border-radius: 5px;
+    background: linear-gradient(
+      180deg,
+      #92865f91 0%,
+      rgba(252, 233, 172, 0.295) 100%
+    );
+    // filter: blur(122px);
+    padding: 10px;
+    div {
+      width: 100%;
+      border-radius: 5px;
+      border: 1px solid #ffedb7;
+      padding: 0 10px;
+      .content-top {
+        width: 100%;
+        height: 40px;
+        // background: red;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        color: #fce9ac;
+        font-weight: bold;
+        border: none;
+        font-size: 16px;
+        font-family: "SourceHanSerifCN-Bold";
+      }
+      img {
+        width: 100%;
+      }
+      .content-span {
+        width: 100%;
+        // overflow: auto;
+        text-indent: 2em;
+        font-size: 12px;
+        font-weight: 480;
+        color: #eddfb275;
+        line-height: 20px;
+        border: none;
+        margin: 5px 0;
+      }
+    }
+  }
+  .close-btn {
+    width: 30px;
+    height: 30px;
+    position: absolute;
+    left: 50%;
+    transform: translateX(-50%);
+    margin-top: 10px;
+    cursor: pointer;
+    font-family: "SourceHanSerifCN-Bold";
+  }
+}
+</style>

+ 75 - 0
src/view/overview/components/local.vue

@@ -0,0 +1,75 @@
+<script setup lang="ts">
+import localIcon from "@/assets/img/overview/location.png";
+let props = defineProps({
+  top: {
+    type: String,
+    default: '0',
+  },
+  bottom: {
+    type: String,
+    default: '0',
+  },
+  left: {
+    type: String,
+    default: '0',
+  },
+  right: {
+    type: String,
+    default: '0',
+  },
+  title: {
+    type: String,
+    default: "",
+  },
+});
+</script>
+
+<template>
+  <div
+    class="local-box"
+    :style="{
+      top: props.top + 'px',
+      bottom: props.bottom + 'px',
+      left: props.left + 'px',
+      right: props.right + 'px',
+    }"
+  >
+    <div class="title">{{ props.title }}</div>
+    <div style="position: relative">
+      <img :src="localIcon" alt="" />
+      <div class="guang"></div>
+    </div>
+  </div>
+</template>
+
+<style lang="less" scoped>
+.local-box {
+  width: 100%;
+  position: absolute;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  .title {
+    margin-bottom: 20px;
+    background: url(\src/assets/img/overview/titleBg.png);
+    background-size: 100% 100%;
+    text-align: center;
+    color: #fce9ac;
+    font-size: 16px;
+    padding: 5px 20px;
+  }
+  img {
+    width: 60px;
+  }
+
+  .guang {
+    width: 60px;
+    height: 60px;
+    position: absolute;
+    border-radius: 50%;
+    left: -4px;
+    bottom: -19px;
+    background: radial-gradient(circle at center, #ffeeb7c0, #ffeeb70c, #ffeeb700);
+  }
+}
+</style>

+ 92 - 0
src/view/overview/index.vue

@@ -0,0 +1,92 @@
+<script setup lang="ts">
+import Tabbar from "@/components/Tabbar.vue";
+import Local from "./components/local.vue";
+import Incident from "./components/incident.vue";
+
+import villages from "@/data/data";
+import { historys } from "@/data/data";
+
+// 窗口控制  1-原始首页窗口  2-历史河流窗口
+const windowIndex = ref(1);
+
+const openHistory = () => {
+  if (windowIndex.value == 1) {
+    windowIndex.value = 2;
+  } else if (windowIndex.value == 2) {
+    windowIndex.value = 1;
+  }
+};
+</script>
+
+<template>
+  <div class="all">
+    <Tabbar />
+    <!-- 总览1 -->
+    <div v-show="windowIndex == 1">
+      <!-- 村落定位 -->
+      <div class="local-box" v-for="(item, index) in villages">
+        <Local :top="item.top" :left="item.left" :title="item.name" />
+      </div>
+    </div>
+    <!-- 开发保护 -->
+    <div
+      class="develop-bh"
+      @click="openHistory"
+      :style="{ color: windowIndex == 2 ? 'black' : 'white' }"
+    >
+      开发保护
+    </div>
+    <!-- 总览时间轴 -->
+    <div class="river-box" v-if="windowIndex == 2">
+      <div v-for="(item, index) in historys">
+        <Incident
+          :time="item.time"
+          :content="item.content"
+          :top="item.top"
+          :left="item.left"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="less" scoped>
+.all {
+  width: 100%;
+  height: 100%;
+  background: url(\src/assets/img/overview/map.png);
+  background-size: 100% 100%;
+  padding-top: 30px;
+
+  .local-box {
+    min-width: 80px;
+    max-width: 400px;
+    height: 80px;
+  }
+
+  .develop-bh {
+    max-width: 150px;
+    position: absolute;
+    left: 100px;
+    bottom: 80px;
+    background: url(\src/assets/img/overview/develop.png);
+    background-size: 100% 100%;
+    text-align: center;
+    color: #fff;
+    font-size: 14px;
+    padding: 5px 20px;
+    z-index: 2;
+    cursor: pointer;
+  }
+
+  .river-box {
+    width: 100%;
+    height: 100%;
+    position: absolute;
+    top: 0;
+    left: 0;
+    background: url(\src/assets/img/overview/riverBg.png);
+    background-size: 100% 100%;
+  }
+}
+</style>

+ 67 - 0
src/view/part/index.vue

@@ -0,0 +1,67 @@
+<script setup lang="ts">
+import Tabbar from "@/components/Tabbar.vue";
+import { parts } from "@/data/data";
+const getAssetURL = (image: string) => {
+  return new URL(`../../assets/img/part/${image}`, import.meta.url).href;
+};
+</script>
+
+<template>
+  <div class="all">
+    <Tabbar />
+    <div class="content-list">
+      <div class="content-list-item" v-for="(item, index) in parts">
+        <div class="item-name">{{ item.name }}</div>
+        <img class="item-img" :src="getAssetURL(item.img)" alt="" />
+        <!-- <div class="item-right-boder"></div> -->
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="less" scoped>
+.all {
+  width: 100%;
+  height: 100%;
+  background: #b8a393;
+  padding: 30px;
+  .content-list {
+    height: 90%;
+    // background: #fff;
+    // overflow: auto;
+    // white-space: nowrap;
+    overflow-x: auto;
+    // display: flex;
+    // align-items: center;
+    &-item {
+      width: 80vw;
+      height: 100%;
+      position: relative;
+      .item-name {
+        width: 10px;
+        position: absolute;
+        bottom: 10px;
+        left: 40px;
+      }
+      .item-img {
+        width: 70%;
+        position: absolute;
+        bottom: 10px;
+        right: 0;
+      }
+      .item-right-boder {
+        width: 1px;
+        height: 100%;
+        background: linear-gradient(
+          to bottom,
+          #403422 0%,
+          #403422 50%,
+          #403422 100%,
+          #403422 50%,
+          #b19c7d 0%
+        );
+      }
+    }
+  }
+}
+</style>

+ 11 - 0
src/view/search/index.vue

@@ -0,0 +1,11 @@
+<script setup lang="ts">
+import Tabbar from "@/components/Tabbar.vue";
+</script>
+
+<template>
+  <div class="">
+    <Tabbar />
+  </div>
+</template>
+
+<style lang="less" scoped></style>

+ 164 - 0
src/view/village/index.vue

@@ -0,0 +1,164 @@
+<script setup lang="ts">
+import Tabbar from "@/components/Tabbar.vue";
+import villages from "@/data/data";
+const router = useRouter();
+const goBack = () => {
+  isShowAll.value = false;
+};
+
+const getAssetURL = (image: string) => {
+  return new URL(`../../assets/img/villages/${image}`, import.meta.url).href;
+};
+const currentHoverIndex = ref(2);
+const handleMouseover = (index: number) => {
+  currentHoverIndex.value = index;
+};
+
+// 村落选择页
+const isShowAll = ref(false);
+</script>
+
+<template>
+  <div class="all">
+    <Tabbar />
+    <!-- 村庄选择 -->
+    <div class="village" v-show="isShowAll">
+      <div class="village-content">
+        <div
+          class="village-content-item"
+          v-for="(item, index) in villages"
+          :style="{
+            width: currentHoverIndex == index ? '18%' : '17%',
+            height: currentHoverIndex == index ? '105%' : '100%',
+          }"
+          @mouseover="handleMouseover(index)"
+          @click="goBack"
+        >
+          <!-- 普通图片 -->
+          <img class="normal-img" :src="getAssetURL(item.imgUrl)" alt="" />
+          <!-- 边框图片 -->
+          <img
+            class="border-img img-hover"
+            :src="getAssetURL('border.png')"
+            alt=""
+          />
+          <!-- 遮罩(信息) -->
+          <div
+            class="info-box"
+            :style="{
+              background:
+                currentHoverIndex == index ? 'none' : 'rgba(0, 0, 0, 0.288)',
+            }"
+          >
+            <div class="detail-title">{{ item.sName }}</div>
+            <div class="detail-box" v-show="currentHoverIndex == index">
+              {{ item.introduce }}
+            </div>
+          </div>
+        </div>
+      </div>
+      <img
+        @click="goBack"
+        class="close-btn"
+        src="../../assets/img/overview/close.png"
+        alt=""
+      />
+    </div>
+    <iframe
+      width="100vw"
+      height="100vh"
+      src="https://www.4dkankan.com/panorama/show.html?id=WK1706844323821158400&vr=fd720_SQrfQP1Pm&lang=zh"
+      frameborder="0"
+    ></iframe>
+  </div>
+</template>
+
+<style lang="less" scoped>
+.all {
+  .village {
+    width: 100%;
+    height: 100%;
+    background: rgba(28, 21, 12, 0.5);
+    backdrop-filter: blur(10px);
+    padding-top: 30px;
+    &-content {
+      width: 100%;
+      height: 80%;
+      // background: red;
+      margin-top: 20px;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 40px;
+      &-item {
+        width: 17%;
+        height: 100%;
+        position: relative;
+        cursor: pointer;
+        // clip-path: polygon(50% 0%, 100% 4%, 100% 96%, 50% 100%, 0 96%, 0 4%);
+        overflow: hidden;
+        img {
+          width: 100%;
+          height: 100%;
+          position: absolute;
+        }
+        .normal-img {
+          box-shadow: 0 0 22px #fce9ac;
+          clip-path: polygon(50% 0%, 100% 4%, 100% 96%, 50% 100%, 0 96%, 0 4%);
+        }
+        .border-img {
+        }
+        .info-box {
+          width: 100%;
+          height: 100%;
+          clip-path: polygon(50% 0%, 100% 4%, 100% 96%, 50% 100%, 0 96%, 0 4%);
+          background: rgba(0, 0, 0, 0.288);
+          color: #fff;
+          font-size: 22px;
+          font-family: "Source Han Sans CN, Source Han Sans CN-Regular";
+          padding: 40px;
+          position: relative;
+          .detail-title {
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+          }
+          .detail-box {
+            width: 80%;
+            // height: 20%;
+            margin-bottom: 20px;
+            font-size: 14px;
+            text-indent: 2em;
+            letter-spacing: 2px;
+            position: absolute;
+            top: 80%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            display: -webkit-box;
+            -webkit-box-orient: vertical;
+            overflow: hidden;
+            -webkit-line-clamp: 5;
+          }
+        }
+      }
+    }
+    .close-btn {
+      width: 30px;
+      height: 30px;
+      position: absolute;
+      left: 50%;
+      transform: translateX(-50%);
+      margin-top: 10px;
+      cursor: pointer;
+    }
+  }
+  iframe {
+    width: 100vw;
+    height: 100vh;
+    position: absolute;
+    top: 0;
+    left: 0;
+  }
+}
+</style>

+ 22 - 0
src/view/welcome/index.vue

@@ -0,0 +1,22 @@
+<script setup lang="ts">
+const router = useRouter();
+const goOverview = () => {
+  router.push({
+    path: "overview",
+  });
+};
+</script>
+
+<template>
+  <div class="all" @click="goOverview"></div>
+</template>
+
+<style lang="less" scoped>
+.all {
+  width: 100%;
+  height: 100%;
+  background: url(../../assets/img/home/welcome.png);
+  background-size: 100% 100%;
+  cursor: pointer;
+}
+</style>