Prechádzať zdrojové kódy

总览-功能逻辑

任一存 1 rok pred
rodič
commit
b5753d129a

+ 2 - 2
public/index.html

@@ -4,8 +4,8 @@
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no">
-    <link rel="icon" href="<%= BASE_URL %>logo.png">
-    <title>我是标题</title>
+    <link rel="icon" href="<%= BASE_URL %>logo.ico">
+    <title>开平碉楼与村落建筑群一张图</title>
   </head>
   <body>
     <!-- <script src="https://cdn.bootcss.com/vConsole/3.2.0/vconsole.min.js"></script> -->

BIN
public/logo.ico


BIN
public/logo.png


+ 57 - 34
src/App.vue

@@ -1,29 +1,43 @@
 <template>
   <router-view />
-  {{ $uaInfo }}
+  <Transition name="fade-out">
+    <StartupView
+      v-if="!isShowStartup"
+      class="startup"
+    />
+  </Transition>
+  <TabBar class="tab-bar" />
 </template>
 
-<script>
-// import { onClickOutside } from '@vueuse/core'
+<script setup>
+import { computed, inject, ref, } from "vue"
+import { useStore } from "vuex"
+import StartupView from "@/views/StartupView.vue"
+import TabBar from "@/components/TabBar.vue"
+const store = useStore()
+
+const isShowStartup = computed(() => {
+  return store.state.haveShownStartup
+})
 
-export default {
-  inject: ['$uaInfo'],
-  mounted() {
-  }
-}
 </script>
 
 <style lang="less">
-// html, body {
-//   overscroll-behavior: none;
-//   overflow: hidden;
-// }
+html, body {
+  // overscroll-behavior: none;
+  overflow: hidden;
+}
 
 // * {
 //   user-select: none;
 //   -webkit-touch-callout: none;
 // }
 
+#app {
+  height: 100%;
+  position: relative;
+}
+
 // // 360浏览器不支持not()
 // input, textarea {
 //   user-select: initial;
@@ -47,29 +61,29 @@ export default {
 // ::-webkit-scrollbar-thumb { background: #828a5b; border-radius: 0.15rem; }
 // ::-webkit-scrollbar-corner { background: #dddecc; }
 
-// // vue组件过渡效果
-// .fade-out-leave-active {
-//   transition: opacity 1s;
-// }
-// .fade-out-leave-to {
-//   opacity: 0;
-// }
+// vue组件过渡效果
+.fade-out-leave-active {
+  transition: opacity 1s;
+}
+.fade-out-leave-to {
+  opacity: 0;
+}
 
-// // 不断渐变显隐 animation
-// .animation-show-hide {
-//   animation: show-hide 1.8s infinite;
-// }
-// @keyframes show-hide {
-//   0% {
-//     opacity: 0;
-//   }
-//   50% {
-//     opacity: 1;
-//   }
-//   100% {
-//     opacity: 0;
-//   }
-// }
+// 不断渐变显隐 animation
+.animation-show-hide {
+  animation: show-hide 1.8s infinite;
+}
+@keyframes show-hide {
+  0% {
+    opacity: 0;
+  }
+  50% {
+    opacity: 1;
+  }
+  100% {
+    opacity: 0;
+  }
+}
 
 // // vue-viewer
 // .viewer-container {
@@ -79,4 +93,13 @@ export default {
 // .viewer-backdrop {
 //   background-color: rgba(0, 0, 0, 90%) !important;
 // }
+</style>
+
+<style scoped lang="less">
+.startup{
+  z-index: 5;
+}
+.tab-bar{
+  z-index: 4;
+}
 </style>

BIN
src/assets/images/development-protection-bg.jpg


BIN
src/assets/images/general-bg.jpg


BIN
src/assets/images/location-indicator-active.png


BIN
src/assets/images/location-indicator-normal.png


BIN
src/assets/videos/startup.mp4


+ 37 - 0
src/components/TabBar.vue

@@ -0,0 +1,37 @@
+<template>
+  <div class="tab-bar">
+    <button class="tab-item">
+      总览
+    </button>
+    <button class="tab-item">
+      村落
+    </button>
+    <button class="tab-item">
+      建筑
+    </button>
+    <button class="tab-item">
+      构件
+    </button>
+  </div>
+</template>
+
+<script setup>
+
+</script>
+
+<style lang="less" scoped>
+.tab-bar{
+  position: absolute;
+  top: 0;
+  left: 50%;
+  transform: translateX(-50%);
+  background-color: red;
+  >button.tab-item{
+    font-size: 22px;
+    font-family: Source Han Sans CN-Regular, Source Han Sans CN;
+    font-weight: 400;
+    color: #FFFFFF;
+    line-height: 30px;
+  }
+}
+</style>

+ 1 - 1
src/router/index.js

@@ -1,5 +1,5 @@
 import { createRouter, createWebHashHistory } from 'vue-router'
-import HomeView from '../views/Home.vue'
+import HomeView from '../views/HomeView.vue'
 // import store from '@/store/index.js'
 
 const routes = [

+ 4 - 4
src/store/index.js

@@ -2,14 +2,14 @@ import { createStore } from 'vuex'
 
 export default createStore({
   state: {
-    usingChinese: true,
+    haveShownStartup: process.env.VUE_APP_CLI_MODE === 'dev' ? true : false,
   },
   getters: {
   },
   mutations: {
-    setUsingChinese(state, value) {
-      state.usingChinese = value
-    },
+    recordShownStartup(state) {
+      state.haveShownStartup = true
+    }
   },
   actions: {
   },

+ 42 - 0
src/utils.js

@@ -67,4 +67,46 @@ export default {
       return fn.apply(context, args)
     }
   },
+  /**
+ * 已知某个点在设计稿中坐标、设计稿尺寸、实际视口尺寸,得到这个点在实际视口中应在位置的坐标。
+ */
+  mapPosFromDraftToWindow(posRaw, objectFit = 'cover', draftWidth = 1920, draftHeight = 1080, windowWidth = null, windowHeight = null) {
+    if (!windowWidth) {
+      windowWidth = window.innerWidth
+    }
+    if (!windowHeight) {
+      windowHeight = window.innerHeight
+    }
+    if (objectFit === 'cover') {
+      if (windowWidth / windowHeight > draftWidth / draftHeight) { // 实际窗口更宽扁,设计图与实际窗口等宽,上下被裁减
+        const scale = windowWidth / draftWidth
+        return {
+          x: posRaw.x * scale,
+          y: posRaw.y * scale - (draftHeight * scale - windowHeight) / 2,
+        }
+      } else { // false: 实际窗口更窄高,设计图与实际窗口等高,左右被裁减
+        const scale = windowHeight / draftHeight
+        return {
+          x: posRaw.x * scale - (draftWidth * scale - windowWidth) / 2,
+          y: posRaw.y * scale,
+        }
+      }
+    } else if (objectFit === 'contain') {
+      if (windowWidth / windowHeight > draftWidth / draftHeight) { // true: 实际窗口更宽扁,设计图与实际窗口等高,左右留白
+        const scale = windowHeight / draftHeight
+        return {
+          x: posRaw.x * scale + (windowWidth - draftWidth * scale) / 2,
+          y: posRaw.y * scale
+        }
+      } else { // false: 实际窗口更窄高,设计图与实际窗口等宽,上下留白
+        const scale = windowWidth / draftWidth
+        return {
+          x: posRaw.x * scale,
+          y: posRaw.y * scale + (windowHeight - draftHeight * scale) / 2,
+        }
+      }
+    } else {
+      throw (`invalid objectFit value: ${objectFit}`)
+    }
+  }
 }

+ 196 - 0
src/views/DevelopmentProtectionView.vue

@@ -0,0 +1,196 @@
+<template>
+  <div class="development-protection-view">
+    <img
+      class="bg"
+      src="@/assets/images/development-protection-bg.jpg"
+      alt=""
+      draggable="false"
+    >
+    <div
+      v-for="(item, idx) in eventList"
+      :key="item.time"
+      class="event-sign"
+      :style="{
+        left: item.posX + 'px',
+        bottom: item.posY + 'px',
+      }"
+      @click="onClickEventSign(idx)"
+    >
+      <img
+        class="location-indicator"
+        src="@/assets/images/location-indicator-normal.png"
+        alt=""
+        draggable="false"
+      >
+      <h3>{{ item.time }}</h3>
+    </div>
+
+    <article
+      v-show="isShowDesc"
+      class="event-desc"
+      :style="{
+        left: descWindowPosX + 'px',
+        bottom: descWindowPosY + 'px',
+      }"
+    >
+      <h3>历史事件</h3>
+      <p>{{ descContent }}</p>
+      <button
+        class="close"
+        @click="isShowDesc = false"
+      >
+        关闭
+      </button>
+    </article>
+  </div>
+</template>
+
+<script setup>
+import { ref, onBeforeUnmount } from "vue"
+
+const eventListRaw = [
+  {
+    time: '1983年',
+    posXInDraft: 260 + 60 / 2,
+    posYInDraft: 726,
+    desc: '2001年3月,开平市成立了开平碉楼申报世界文化遗产领导小组,市主要领导亲自挂帅,从各有关部门抽出精兵强将专职开展碉楼保护和申报工作。6月25日,开平碉楼被国务院批准列入第五批全国重点文物保护单位名单。',
+  },
+  {
+    time: '1983年3月',
+    posXInDraft: 488 + 60 / 2,
+    posYInDraft: 638,
+    desc: '2001年3月,开平市成立了开平碉楼申报世界文化遗产领导小组,市主要领导亲自挂帅,从各有关部门抽出精兵强将专职开展碉楼保护和申报工作。6月25日,开平碉楼被国务院批准列入第五批全国重点文物保护单位名单。',
+  },
+  {
+    time: '1994年12月',
+    posXInDraft: 697 + 60 / 2,
+    posYInDraft: 407,
+    desc: '2001年3月,开平市成立了开平碉楼申报世界文化遗产领导小组,市主要领导亲自挂帅,从各有关部门抽出精兵强将专职开展碉楼保护和申报工作。6月25日,开平碉楼被国务院批准列入第五批全国重点文物保护单位名单。',
+  },
+  {
+    time: '1999年3月',
+    posXInDraft: 914 + 60 / 2,
+    posYInDraft: 326,
+    desc: '2001年3月,开平市成立了开平碉楼申报世界文化遗产领导小组,市主要领导亲自挂帅,从各有关部门抽出精兵强将专职开展碉楼保护和申报工作。6月25日,开平碉楼被国务院批准列入第五批全国重点文物保护单位名单。',
+  },
+  {
+    time: '2000年11月',
+    posXInDraft: 979 + 60 / 2,
+    posYInDraft: 567,
+    desc: '2001年3月,开平市成立了开平碉楼申报世界文化遗产领导小组,市主要领导亲自挂帅,从各有关部门抽出精兵强将专职开展碉楼保护和申报工作。6月25日,开平碉楼被国务院批准列入第五批全国重点文物保护单位名单。',
+  },
+  {
+    time: '2001年3月',
+    posXInDraft: 1205 + 60 / 2,
+    posYInDraft: 686,
+    desc: '2001年3月,开平市成立了开平碉楼申报世界文化遗产领导小组,市主要领导亲自挂帅,从各有关部门抽出精兵强将专职开展碉楼保护和申报工作。6月25日,开平碉楼被国务院批准列入第五批全国重点文物保护单位名单。',
+  },
+  {
+    time: '2002年7月16日',
+    posXInDraft: 1420 + 60 / 2,
+    posYInDraft: 800,
+    desc: '2001年3月,开平市成立了开平碉楼申报世界文化遗产领导小组,市主要领导亲自挂帅,从各有关部门抽出精兵强将专职开展碉楼保护和申报工作。6月25日,开平碉楼被国务院批准列入第五批全国重点文物保护单位名单。',
+  },
+  {
+    time: '2007年6月28日',
+    posXInDraft: 1681 + 60 / 2,
+    posYInDraft: 851,
+    desc: '2001年3月,开平市成立了开平碉楼申报世界文化遗产领导小组,市主要领导亲自挂帅,从各有关部门抽出精兵强将专职开展碉楼保护和申报工作。6月25日,开平碉楼被国务院批准列入第五批全国重点文物保护单位名单。',
+  },
+]
+const eventList = ref([])
+function setEventList() {
+  eventList.value = eventListRaw.map((item) => {
+    const { x, y } = utils.mapPosFromDraftToWindow({
+      x: item.posXInDraft,
+      y: item.posYInDraft,
+    })
+    return {
+      time: item.time,
+      posX: x,
+      posY: y,
+    }
+  })
+}
+setEventList()
+window.addEventListener('resize', setEventList)
+onBeforeUnmount(() => {
+  window.removeEventListener('resize', setEventList)
+})
+
+const isShowDesc = ref(false)
+const descContent = ref('')
+const descWindowPosX = ref(0)
+const descWindowPosY = ref(0)
+function onClickEventSign(idx) {
+  descContent.value = eventListRaw[idx].desc
+  const descWindowPos = utils.mapPosFromDraftToWindow({
+    x: eventListRaw[idx].posXInDraft,
+    y: eventListRaw[idx].posYInDraft,
+  })
+  descWindowPosX.value = descWindowPos.x
+  descWindowPosY.value = descWindowPos.y
+  isShowDesc.value = true
+}
+
+</script>
+
+<style lang="less" scoped>
+.development-protection-view{
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  background-color: blue;
+  >.bg{
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+  }
+  >.event-sign{
+    position: absolute;
+    // background: red;
+    // opacity: 0.5;
+    transform: translateX(-50%);
+    width: 150px;
+    height: 60px;
+    cursor: pointer;
+    >img.location-indicator{
+      position: absolute;
+      left: 50%;
+      transform: translateX(-50%);
+      width: 56px;
+      height: 60px;
+    }
+    >h3{
+      position: absolute;
+      left: 50%;
+      bottom: 0;
+      transform: translate(-50%, 36px);
+      font-size: 20px;
+      font-family: Source Han Sans CN-Bold, Source Han Sans CN;
+      font-weight: bold;
+      color: #FFFFFF;
+      line-height: 23px;
+      text-align: center;
+      white-space: pre;
+    }
+  }
+  >article{
+    position: absolute;
+    background-color: red;
+    opacity: 0.7;
+    transform: translateX(-50%);
+    >.close{
+      position: absolute;
+      left: 50%;
+      bottom: 0;
+      transform: translate(-50%, 200%);
+    }
+  }
+}
+</style>

+ 0 - 36
src/views/Home.vue

@@ -1,36 +0,0 @@
-<template>
-  <div
-    class="home"
-  >
-    {{ $env.BASE_URL }}
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'HomeView',
-  data() {
-    return {
-    }
-  },
-  computed: {
-    ...mapState([
-    ]),
-  },
-  mounted() {
-  },
-  unmounted() {
-  },
-  methods: {
-    ...mapMutations([
-    ]),
-  },
-}
-</script>
-
-<style lang="less" scoped>
-.home {
-  width: 100%;
-  height: 100%;
-}
-</style>

+ 158 - 0
src/views/HomeView.vue

@@ -0,0 +1,158 @@
+<template>
+  <div
+    class="home"
+  >
+    <img
+      class="bg"
+      src="@/assets/images/general-bg.jpg"
+      alt=""
+      draggable="false"
+    >
+    <div
+      v-for="item in villageList"
+      :key="item.name"
+      class="village-sign"
+      :style="{
+        left: item.posX + 'px',
+        bottom: item.posY + 'px',
+      }"
+    >
+      <h3>{{ item.name }}</h3>
+      <img
+        class="location-indicator"
+        src="@/assets/images/location-indicator-normal.png"
+        alt=""
+        draggable="false"
+      >
+    </div>
+    <DevelopmentProtectionView
+      v-show="isShowDevProView"
+      class="development-protection-view"
+    />
+    <button
+      class="development-protection"
+      :class="{
+        active: isShowDevProView,
+      }"
+      @click="isShowDevProView = !isShowDevProView"
+    >
+      开发保护
+    </button>
+  </div>
+</template>
+
+<script setup>
+import { ref, onBeforeUnmount } from "vue"
+import DevelopmentProtectionView from "@/views/DevelopmentProtectionView.vue"
+
+
+const villageListRaw = [
+  {
+    name: '锦江里',
+    posXInDraft: 503 + 155 / 2,
+    posYInDraft: 406,
+  },
+  {
+    name: '百合镇马降龙村',
+    posXInDraft: 1030 + 155 / 2,
+    posYInDraft: 478,
+  },
+  {
+    name: '塘口镇自力村',
+    posXInDraft: 962 + 155 / 2,
+    posYInDraft: 764,
+  },
+  {
+    name: '赤坎古镇',
+    posXInDraft: 1132 + 155 / 2,
+    posYInDraft: 703,
+  },
+  {
+    name: '三门里',
+    posXInDraft: 1279 + 155 / 2,
+    posYInDraft: 773,
+  },
+]
+const villageList = ref([])
+function setVillageList() {
+  villageList.value = villageListRaw.map((item) => {
+    const { x, y } = utils.mapPosFromDraftToWindow({
+      x: item.posXInDraft,
+      y: item.posYInDraft,
+    })
+    return {
+      name: item.name,
+      posX: x,
+      posY: y,
+    }
+  })
+}
+setVillageList()
+window.addEventListener('resize', setVillageList)
+onBeforeUnmount(() => {
+  window.removeEventListener('resize', setVillageList)
+})
+
+const isShowDevProView = ref(false)
+
+</script>
+
+<style lang="less" scoped>
+.home {
+  position: relative;
+  height: 100%;
+  >.bg{
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+  }
+  >.village-sign{
+    position: absolute;
+    background: red;
+    opacity: 0.5;
+    width: 155px;
+    height: 120px;
+    transform: translate(-50%, 0);
+    >h3{
+      font-size: 16px;
+      font-family: Source Han Serif CN-Bold, Source Han Serif CN;
+      font-weight: bold;
+      color: #FCE9AC;
+      line-height: 19px;
+      text-align: center;
+    }
+    >img.location-indicator{
+      position: absolute;
+      bottom: 0px;
+      left: 50%;
+      transform: translateX(-50%);
+      width: 56px;
+      height: 60px;
+    }
+  }
+  >.development-protection-view{
+    z-index: 2;
+  }
+  >button.development-protection{
+    position: absolute;
+    left: 76px;
+    bottom: 68px;
+    font-size: 20px;
+    font-family: Source Han Serif CN-Bold, Source Han Serif CN;
+    font-weight: bold;
+    color: #FFFFFF;
+    line-height: 23px;
+    z-index: 3;
+    &.active{
+      font-size: 20px;
+      font-family: Source Han Serif CN-Bold, Source Han Serif CN;
+      font-weight: bold;
+      color: #403422;
+      line-height: 23px;
+    }
+  }
+}
+</style>

+ 40 - 0
src/views/StartupView.vue

@@ -0,0 +1,40 @@
+<template>
+  <div class="startup">
+    <video
+      src="@/assets/videos/startup.mp4"
+      autoplay
+      playsinline
+      webkit-playsinline="true"
+      muted
+      @ended="onVideoEnd"
+    />
+  </div>
+</template>
+
+<script setup>
+import { useStore } from "vuex"
+
+const store = useStore()
+
+function onVideoEnd() {
+  store.commit('recordShownStartup')
+}
+
+</script>
+
+<style lang="less" scoped>
+.startup{
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  >video{
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+  }
+}
+</style>

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 6432 - 0
yarn.lock