任一存 1 year ago
parent
commit
d001e8c521

+ 1 - 0
.eslintrc.js

@@ -52,5 +52,6 @@ module.exports = {
     useSizeAdapt: true,
     defineProps: true,
     defineEmits: true,
+    configText: true,
   }
 }

+ 16 - 0
public/config.js

@@ -0,0 +1,16 @@
+var configText = {
+  homepagePaintingDesc: [
+    `古人创作墨竹的灵感来自“因观竹影而得意。”又有“学画竹者,取一枝竹,因月夜照其影于素壁之上,则竹之真形出矣”,或者“每灯下照竹枝模影写真”,再或者“折小枝,就日影视之,皆欲精到”。画家通过日光、月影和灯下竹影写成墨竹。`,
+    `我们从文献与实物综合考察分析,墨竹起源于唐、五代,北宋时以文同为集大成者,经苏轼加持,形成了中国绘画史上影响最大的“湖州竹派”。`,
+    `该派既继承传统又重视写生,技法严谨,精神内涵丰富。李衎是元代湖州竹派的代表性画家。`,
+  ],
+  homepageAuthorDesc: [
+    `李衎(1245年-1320年),字仲宾,号息斋道人,蓟丘(今北京)人。皇庆元年(1312年)任吏部尚书,拜集贤殿大学士,与赵孟頫相友善。死后追封蓟国公,谥文简。擅长画竹,墨竹初师王庭筠,继师文同,青绿设色师李颇。`,
+    `李衎喜欢在绢上作画,尤其是晚期作品,因其身为官场显贵,有财力购买好绢,与南方文人好用纸大异其趣;又因绢质能增画面清贵感,符合其地位。此外,绢画是唐宋传统,可以增加古意。`,
+    `李衎早期竹图受金代王庭筠影响,如《竹梧兰石四清图》;晚期以文同笔墨写“真竹”,竹子的造型多源于他对竹子生态的研究。他的创作理念始终以“清”为指导原则,特别重视清润,这是他最根本的美学理念,并通过画作表达出一套伦理道德观念,因此他的画有强烈的象征主义特征。"`,
+  ],
+  homepagePaintingDetailDescStem: '坡石一隅的三竿修竹,两竿近,一竿远,近低远高,近开远合,两竿向右,一竿向左,一聚一分,构图稳定,法度森严。',
+  homepagePaintingDetailDescLeaf: '竹叶的画法以“个”“介"“分”字法叠加、穿插、组合而成,叶叶不乱、有条不紊,每片都交代清楚。以书法用笔写出,看似类似,实则每一笔都有变化,细腻微妙。',
+  homepagePaintingDetailDescStone: '卧石、枯树和灌木在竹下方,用低矮、枯梗、虚淡来衬托竹的挺拔、润泽和沉着。图中弥漫着敦厚温润、不瘟不火、从容淡定的神韵。',
+  homepagePaintingSummary: '赵孟頫对李衎的墨竹评价极高:“吾友李仲宾为此君写真,冥搜极讨,盖欲尽得竹之情状,二百年来,以画竹称者,皆未必能用意精深如仲宾也。”',
+}

+ 1 - 0
public/index.html

@@ -8,6 +8,7 @@
     <title>我是标题</title>
   </head>
   <body>
+    <script src="./config.js"></script>
     <!-- <script src="https://cdn.bootcss.com/vConsole/3.2.0/vconsole.min.js"></script> -->
     <noscript>
       <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>

+ 2 - 3
src/App.vue

@@ -71,16 +71,15 @@ input, textarea {
 
 // vue组件过渡效果
 .fade-out-leave-active {
-  transition: opacity 1s;
+  transition: opacity 2s;
   pointer-events: none;
 }
 .fade-out-leave-to {
   opacity: 0;
 }
 
-// vue组件过渡效果
 .fade-in-enter-active {
-  transition: opacity 1s;
+  transition: opacity 2s;
 }
 .fade-in-enter-from {
   opacity: 0;

BIN
src/assets/images/home-painting-leaf.png


BIN
src/assets/images/home-painting-stem.png


BIN
src/assets/images/home-painting-stone.png


BIN
src/assets/images/home-painting-with-border.png


BIN
src/assets/images/home-painting.jpg


BIN
src/assets/images/home-title.png


BIN
src/assets/images/icon_hotspot.png


BIN
src/assets/images/painting-border.png


BIN
src/assets/images/size-sign-h.png


BIN
src/assets/images/size-sign-v.png


BIN
src/assets/images/start-btn-bg.png


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


BIN
src/assets/images/startup-title.png


+ 1 - 3
src/components/BtnBack.vue

@@ -1,8 +1,6 @@
 <template>
   <div class="btn-back">
-    <button
-      @click="router.go(-1)"
-    >
+    <button>
       <img
         class=""
         :src="require(`@/assets/images/icon_back_${props.color}.png`)"

+ 39 - 0
src/components/HotspotComp.vue

@@ -0,0 +1,39 @@
+<template>
+  <div class="hotspot animation-show-hide">
+    <img
+      class=""
+      src="@/assets/images/icon_hotspot.png"
+      alt=""
+      draggable="false"
+    >
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, watch, onMounted, inject } from "vue"
+import { useRoute, useRouter } from "vue-router"
+import { useStore } from "vuex"
+import useSizeAdapt from "@/useFunctions/useSizeAdapt"
+
+const {
+  windowSizeInCssForRef,
+  windowSizeWhenDesignForRef,
+} = useSizeAdapt()
+
+const route = useRoute()
+const router = useRouter()
+const store = useStore()
+
+const $env = inject('$env')
+</script>
+
+<style lang="less" scoped>
+.hotspot{
+  width: calc(33 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  height: calc(33 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  >img{
+    width: 100%;
+    height: 100%;
+  }
+}
+</style>

+ 2 - 0
src/main.js

@@ -13,6 +13,7 @@ import 'element-plus/dist/index.css'
 
 import BtnBack from '@/components/BtnBack.vue'
 import OperationTip from '@/components/OperationTip.vue'
+import HotspotComp from '@/components/HotspotComp.vue'
 
 console.log(`version: ${process.env.VUE_APP_VERSION}`)
 console.log(`Build time: ${process.env.VUE_APP_UPDATE_TIME}`)
@@ -80,6 +81,7 @@ app.use(store)
   .use(ElementPlus)
   .component('BtnBack', BtnBack)
   .component('OperationTip', OperationTip)
+  .component('HotspotComp', HotspotComp)
   .mount('#app')
 
 //  you can reset the default options at any other time

+ 14 - 0
src/store/index.js

@@ -9,6 +9,8 @@ import { createStore } from 'vuex'
 
 export default createStore({
   state: {
+    haveShownStartup: true,
+    isStartupInvisible: true,
     // loginStatus: loginStatusEnum.notLogin,
     // token: '',
     // userInfo: {
@@ -23,6 +25,12 @@ export default createStore({
     // }
   },
   mutations: {
+    setShowingStartup(state, value) {
+      state.haveShownStartup = value
+    },
+    setIsStartupInvisible(state, value) {
+      state.isStartupInvisible = value
+    }
     // setLoginStatus(state, value) {
     //   state.loginStatus = value
     // },
@@ -58,6 +66,12 @@ export default createStore({
     // }
   },
   actions: {
+    recordShownStartup({ state, commit }) {
+      commit('setShowingStartup', true)
+      setTimeout(() => {
+        commit('setIsStartupInvisible', true)
+      }, 2000)
+    },
     // recordPageVisitIfNeeded({ state, commit }, { pageId }) {
     //   let needUpdateStorage = false
 

+ 510 - 6
src/views/HomeView.vue

@@ -2,11 +2,182 @@
   <div
     class="home"
   >
+    <div class="bg-mask" />
+    <Transition name="fade-out">
+      <Startup
+        v-if="!store.state.haveShownStartup"
+        class="startup"
+      />
+    </Transition>
+    <div
+      class="title-wrap"
+      :style="{
+        opacity: titleOpacity,
+      }"
+    >
+      <img
+        class="title"
+        src="@/assets/images/home-title.png"
+        alt=""
+        draggable="false"
+      >
+      <div class="sub-text">
+        南京博物院<br>
+        绢本 墨笔<br>
+        元 李衎<br>
+      </div>
+    </div>
+    <div class="painting-wrap">
+      <div
+        class="size-sign-h"
+        :style="{
+          opacity: sizeOpacity,
+        }"
+      >
+        <img
+          class=""
+          src="@/assets/images/size-sign-h.png"
+          alt=""
+          draggable="false"
+        >
+        <span>100cm</span>
+      </div>
+      <div
+        class="size-sign-v"
+        :style="{
+          opacity: sizeOpacity,
+        }"
+      >
+        <img
+          class=""
+          src="@/assets/images/size-sign-v.png"
+          alt=""
+          draggable="false"
+        >
+        <span>152cm</span>
+      </div>
+      <img
+        class="painting-border"
+        src="@/assets/images/painting-border.png"
+        alt=""
+        draggable="false"
+      >
+      <img
+        class="painting"
+        :style="{
+          opacity: 1 - Math.max(stemOpacity * 0.5, leafOpacity * 0.5, stoneOpacity * 0.5),
+        }"
+        src="@/assets/images/home-painting.jpg"
+        alt=""
+        draggable="false"
+      >
+      <img
+        class="painting-stem"
+        :style="{
+          opacity: stemOpacity,
+        }"
+        src="@/assets/images/home-painting-stem.png"
+        alt=""
+        draggable="false"
+      >
+      <img
+        class="painting-leaf"
+        :style="{
+          opacity: leafOpacity,
+        }"
+        src="@/assets/images/home-painting-leaf.png"
+        alt=""
+        draggable="false"
+      >
+      <img
+        class="painting-stone"
+        :style="{
+          opacity: stoneOpacity,
+        }"
+        src="@/assets/images/home-painting-stone.png"
+        alt=""
+        draggable="false"
+      >
+    </div>
+    <div class="hotspot-wrap">
+      <HotspotComp
+        v-show="isShowHotspot"
+        class="hotspot-1"
+        @click="isShowHotspotDetail1 = true"
+      />
+      <HotspotComp
+        v-show="isShowHotspot"
+        class="hotspot-2"
+      />
+      <HotspotComp
+        v-show="isShowHotspot"
+        class="hotspot-3"
+      />
+    </div>
+    <div
+      class="fixed-desc detail-desc-stem"
+      :style="{
+        opacity: stemOpacity,
+      }"
+    >
+      {{ detailDescStem }}
+    </div>
+    <div
+      class="fixed-desc detail-desc-leaf"
+      :style="{
+        opacity: leafOpacity,
+      }"
+    >
+      {{ detailDescLeaf }}
+    </div>
+    <div
+      class="fixed-desc detail-desc-stone"
+      :style="{
+        opacity: stoneOpacity,
+      }"
+    >
+      {{ detailDescStone }}
+    </div>
+    <div
+      class="fixed-desc summary-desc"
+      :style="{
+        opacity: summaryOpacity,
+      }"
+    >
+      {{ summaryDesc }}
+    </div>
     <OperationTip
-      :text="`testtest`"
+      v-if="store.state.isStartupInvisible"
+      class="operation-tip"
+      text="了解作品"
       :is-show="isShowOperationTip"
     />
-    <BtnBack />
+    <div
+      ref="descEl"
+      class="desc"
+    >
+      <h3>作品简介:</h3>
+      <p
+        v-for="(item, index) in homepagePaintingDesc"
+        :key="index"
+      >
+        {{ item }}
+      </p>
+      <h3>作者简介:</h3>
+      <p
+        v-for="(item, index) in homepageAuthorDesc"
+        :key="index"
+      >
+        {{ item }}
+      </p>
+      <div class="bottom-mask" />
+    </div>
+
+    <HotspotDetail1
+      v-if="isShowHotspotDetail1"
+      class="hotspot-detail"
+      @close="isShowHotspotDetail1 = false"
+    />
   </div>
 </template>
 
@@ -14,6 +185,9 @@
 import { ref, computed, watch, onMounted, inject } from "vue"
 import { useRoute, useRouter } from "vue-router"
 import { useStore } from "vuex"
+import Startup from '@/views/StartupView.vue'
+import useSizeAdapt from "@/useFunctions/useSizeAdapt"
+import HotspotDetail1 from '@/views/HotspotDetail1.vue'
 
 const route = useRoute()
 const router = useRouter()
@@ -21,16 +195,346 @@ const store = useStore()
 
 const $env = inject('$env')
 
+const {
+  windowSizeInCssForRef,
+  windowSizeWhenDesignForRef,
+} = useSizeAdapt()
+
+const homepagePaintingDesc = configText.homepagePaintingDesc
+const homepageAuthorDesc = configText.homepageAuthorDesc
+const detailDescStem = configText.homepagePaintingDetailDescStem
+const detailDescLeaf = configText.homepagePaintingDetailDescLeaf
+const detailDescStone = configText.homepagePaintingDetailDescStone
+const summaryDesc = configText.homepagePaintingSummary
+
+const descEl = ref(null)
+const descElScrollTop = ref(0)
+onMounted(() => {
+  descEl.value.addEventListener('scroll', (e) => {
+    descElScrollTop.value = descEl.value.scrollTop
+  })
+})
+
 const isShowOperationTip = ref(true)
-setTimeout(() => {
-  isShowOperationTip.value = false
-}, 1000)
+watch(descElScrollTop, (v) => {
+  if (v > 0) {
+    isShowOperationTip.value = false
+  }
+})
+
+const isStartupInvisible = computed(() => {
+  return store.state.isStartupInvisible
+})
+
+const titleOpacity = computed(() => {
+  let ret = null
+  if (descElScrollTop.value <= window.innerHeight * 0.3) {
+    ret = 1 - (window.innerHeight * 0.3 - descElScrollTop.value) / (window.innerHeight * 0.3)
+  } else if (descElScrollTop.value > window.innerHeight * 0.3 && descElScrollTop.value < window.innerHeight * 0.5) {
+    ret = 1
+  } else {
+    ret = 1 - (descElScrollTop.value - window.innerHeight * 0.5) / (window.innerHeight * (0.75 - 0.5))
+  }
+  return ret
+})
+
+const stemOpacity = computed(() => {
+  let ret = null
+  if (descElScrollTop.value <= window.innerHeight * 2) {
+    ret = 0
+  } else if (descElScrollTop.value > window.innerHeight * 2 && descElScrollTop.value < window.innerHeight * 2.5) {
+    ret = (descElScrollTop.value - window.innerHeight * 2) / (window.innerHeight * (2.5 - 2))
+  } else if (descElScrollTop.value >= window.innerHeight * 2.5 && descElScrollTop.value <= window.innerHeight * 3) {
+    ret = 1
+  } else if (descElScrollTop.value > window.innerHeight * 3 && descElScrollTop.value < window.innerHeight * 3.5) {
+    ret = 1 - (descElScrollTop.value - window.innerHeight * 3) / (window.innerHeight * (3.5 - 3))
+  } else {
+    ret = 0
+  }
+  return ret
+})
+
+const leafOpacity = computed(() => {
+  let ret = null
+  if (descElScrollTop.value <= window.innerHeight * 4) {
+    ret = 0
+  } else if (descElScrollTop.value > window.innerHeight * 4 && descElScrollTop.value < window.innerHeight * 4.5) {
+    ret = (descElScrollTop.value - window.innerHeight * 4) / (window.innerHeight * (4.5 - 4))
+  } else if (descElScrollTop.value >= window.innerHeight * 4.5 && descElScrollTop.value <= window.innerHeight * 5) {
+    ret = 1
+  } else if (descElScrollTop.value > window.innerHeight * 5 && descElScrollTop.value < window.innerHeight * 5.5) {
+    ret = 1 - (descElScrollTop.value - window.innerHeight * 5) / (window.innerHeight * (5.5 - 5))
+  } else {
+    ret = 0
+  }
+  return ret
+})
+
+const stoneOpacity = computed(() => {
+  let ret = null
+  if (descElScrollTop.value <= window.innerHeight * 6) {
+    ret = 0
+  } else if (descElScrollTop.value > window.innerHeight * 6 && descElScrollTop.value < window.innerHeight * 6.5) {
+    ret = (descElScrollTop.value - window.innerHeight * 6) / (window.innerHeight * (6.5 - 6))
+  } else if (descElScrollTop.value >= window.innerHeight * 6.5 && descElScrollTop.value <= window.innerHeight * 7) {
+    ret = 1
+  } else if (descElScrollTop.value > window.innerHeight * 7 && descElScrollTop.value < window.innerHeight * 7.5) {
+    ret = 1 - (descElScrollTop.value - window.innerHeight * 7) / (window.innerHeight * (7.5 - 7))
+  } else {
+    ret = 0
+  }
+  return ret
+})
+
+const summaryOpacity = computed(() => {
+  let ret = null
+  if (descElScrollTop.value <= window.innerHeight * 8) {
+    ret = 0
+  } else if (descElScrollTop.value > window.innerHeight * 8 && descElScrollTop.value < window.innerHeight * 8.5) {
+    ret = (descElScrollTop.value - window.innerHeight * 8) / (window.innerHeight * (8.5 - 8))
+  } else if (descElScrollTop.value >= window.innerHeight * 6.5 && descElScrollTop.value <= window.innerHeight * 9) {
+    ret = 1
+  } else if (descElScrollTop.value > window.innerHeight * 9 && descElScrollTop.value < window.innerHeight * 9.5) {
+    ret = 1 - (descElScrollTop.value - window.innerHeight * 9) / (window.innerHeight * (9.5 - 9))
+  } else {
+    ret = 0
+  }
+  return ret
+})
+
+const sizeOpacity = computed(() => {
+  let ret = null
+  if (descElScrollTop.value <= window.innerHeight * 2) {
+    ret = 0
+  } else if (descElScrollTop.value > window.innerHeight * 2 && descElScrollTop.value < window.innerHeight * 2.5) {
+    ret = (descElScrollTop.value - window.innerHeight * 2) / (window.innerHeight * (2.5 - 2))
+  } else if (descElScrollTop.value >= window.innerHeight * 2.5 && descElScrollTop.value <= window.innerHeight * 7) {
+    ret = 1
+  } else if (descElScrollTop.value > window.innerHeight * 7 && descElScrollTop.value < window.innerHeight * 7.5) {
+    ret = 1 - (descElScrollTop.value - window.innerHeight * 7) / (window.innerHeight * (7.5 - 7))
+  } else {
+    ret = 0
+  }
+  return ret
+})
+
+const isShowHotspot = computed(() => {
+  let ret = null
+  if (descElScrollTop.value <= window.innerHeight * 2) {
+    ret = false
+  } else {
+    return true
+  }
+  return ret
+})
+
+const isShowHotspotDetail1 = ref(false)
 </script>
 
 <style lang="less" scoped>
 .home {
   width: 100%;
   height: 100%;
-  background-color: #000;
+  // 滚动条,只设置某一项可能导致不生效。
+  ::-webkit-scrollbar { width: 0; height: 0; }
+  >.bg-mask{
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    background-image: url(@/assets/images/home-painting.jpg);
+    background-size: cover;
+    background-repeat: no-repeat;
+    background-position: center center;
+    filter: grayscale(1) brightness(0.35);
+  }
+  >.startup{
+    z-index: 10;
+  }
+  >.title-wrap{
+    position: absolute;
+    left: 50%;
+    top: calc(30 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    transform: translate(-50%);
+    width: calc(43 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    height: calc(180 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    z-index: 5;
+    >img.title{
+      position: absolute;
+      left: 0;
+      top: 0;
+      width: 100%;
+      height: 100%;
+    }
+    >.sub-text{
+      position: absolute;
+      left: 110%;
+      top: 46%;
+      transform: translateY(-50%);
+      writing-mode: vertical-lr;
+      font-family: KaiTi, KaiTi;
+      font-weight: 400;
+      font-size: calc(18 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      color: #FFFFFF;
+      line-height: calc(21 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      white-space: pre;
+      letter-spacing: 0.2em;
+      text-align: center;
+    }
+  }
+  >.painting-wrap{
+    position: absolute;
+    left: 50%;
+    top: 48%;
+    transform: translate(-50%, -50%);
+    width: calc(309 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    height: calc(522 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    >.size-sign-h{
+      position: absolute;
+      left: 50%;
+      top: 0;
+      transform: translate(-50%, -105%);
+      width: calc(309 * 0.9 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      >img{
+        width: 100%;
+      }
+      >span{
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        transform: translate(-50%, -50%);
+        font-family: KaiTi, KaiTi;
+        font-weight: 400;
+        font-size: calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        color: #FFFFFF;
+        line-height: calc(23 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        text-shadow: 0px 0px calc(4 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef')) #F8DD86;
+      }
+    }
+    >.size-sign-v{
+      position: absolute;
+      right: 0;
+      top: 50%;
+      transform: translate(80%, -50%);
+      height: calc(464 * 0.9 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      >img{
+        height: 100%;
+      }
+      >span{
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        transform: translate(-50%, -50%);
+        font-family: KaiTi, KaiTi;
+        font-weight: 400;
+        font-size: calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        color: #FFFFFF;
+        line-height: calc(23 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        text-shadow: 0px 0px calc(4 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef')) #F8DD86;
+        writing-mode: vertical-lr;
+      }
+    }
+    >img.painting-border{
+      position: absolute;
+      left: 0;
+      top: 0;
+      width: 100%;
+      height: 100%;
+    }
+    >img.painting, img.painting-stem, img.painting-leaf, img.painting-stone{
+      position: absolute;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+      width: 90%;
+    }
+  }
+  >.hotspot-wrap{
+    position: absolute;
+    left: 50%;
+    top: 48%;
+    transform: translate(-50%, -50%);
+    width: calc(309 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    height: calc(522 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    z-index: 7;
+    pointer-events: none;
+    >.hotspot-1{
+      position: absolute;
+      top: calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      right: calc(0 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      pointer-events: initial;
+    }
+    >.hotspot-2{
+      position: absolute;
+      left: calc(40 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      top: calc(125 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      pointer-events: initial;
+    }
+    >.hotspot-3{
+      position: absolute;
+      bottom: calc(-10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      right: calc(-10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      pointer-events: initial;
+    }
+  }
+  >.fixed-desc{
+    position: absolute;
+    left: 50%;
+    bottom: 2%;
+    transform: translateX(-50%);
+    width: 100%;
+    height: 20%;
+    padding-left: calc(37 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    padding-right: calc(37 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    font-family: KaiTi, KaiTi;
+    color: #FFFFFF;
+    font-weight: 400;
+    font-size: calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    line-height: calc(25 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    text-align: justified;
+  }
+  >.operation-tip{
+    position: absolute;
+    left: 50%;
+    bottom: calc(77 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    transform: translateX(-50%);
+  }
+  >.desc{
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    padding-top: 100vh;
+    color: white;
+    overflow: auto;
+    padding-left: calc(37 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    padding-right: calc(37 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-family: KaiTi, KaiTi;
+    color: #FFFFFF;
+    >h3{
+      margin-top: 1em;
+      margin-bottom: 0.5em;
+      font-weight: 600;
+    }
+    >p{
+      font-weight: 400;
+      font-size: calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      line-height: calc(25 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      text-align: justified;
+      margin-bottom: 0.5em;
+    }
+    >.bottom-mask{
+      width: 100%;
+      height: 750vh;
+    }
+  }
+  >.hotspot-detail{
+    z-index: 10;
+  }
 }
 </style>

+ 46 - 0
src/views/HotspotDetail1.vue

@@ -0,0 +1,46 @@
+<template>
+  <div class="hotspot-detail-1">
+    <div class="bg-mask" />
+    <BtnBack
+      @click="emit('close')"
+    />
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, watch, onMounted, inject } from "vue"
+import { useRoute, useRouter } from "vue-router"
+import { useStore } from "vuex"
+
+const route = useRoute()
+const router = useRouter()
+const store = useStore()
+
+const $env = inject('$env')
+
+const emit = defineEmits(['close'])
+
+</script>
+
+<style lang="less" scoped>
+.hotspot-detail-1{
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  background-color: #000;
+  >.bg-mask{
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    background-image: url(@/assets/images/home-painting.jpg);
+    background-size: cover;
+    background-repeat: no-repeat;
+    background-position: center center;
+    filter: grayscale(1) brightness(0.35);
+  }
+}
+</style>

+ 78 - 0
src/views/StartupView.vue

@@ -0,0 +1,78 @@
+<template>
+  <div class="startup-view">
+    <span class="font-load-trigger">这是为了尽早触发字体文件加载</span>
+    <Transition
+      name="fade-in"
+    >
+      <button
+        v-show="isShowStartBtn"
+        class="start"
+        @click="onclickStart"
+      >
+        开始
+      </button>
+    </Transition>
+  </div>
+</template>
+
+<script setup>
+import useSizeAdapt from "@/useFunctions/useSizeAdapt"
+import { ref, computed, watch, onMounted, inject } from "vue"
+import { useRoute, useRouter } from "vue-router"
+import { useStore } from "vuex"
+
+const route = useRoute()
+const router = useRouter()
+const store = useStore()
+
+const $env = inject('$env')
+
+const {
+  windowSizeInCssForRef,
+  windowSizeWhenDesignForRef,
+} = useSizeAdapt()
+
+const isShowStartBtn = ref(false)
+setTimeout(() => {
+  isShowStartBtn.value = true
+}, 2000)
+function onclickStart() {
+  store.dispatch('recordShownStartup')
+}
+</script>
+
+<style lang="less" scoped>
+.startup-view{
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  background-image: url(@/assets/images/startup-bg.jpg);
+  background-size: cover;
+  background-repeat: no-repeat;
+  background-position: center center;
+  >.font-load-trigger{
+    pointer-events: none;
+    opacity: 0;
+    font-family: KaiTi, KaiTi;
+  }
+  >button.start{
+    position: absolute;
+    left: 50%;
+    bottom: calc(56 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    transform: translateX(-50%);
+    width: calc(71 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    height: calc(69 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    background-image: url(@/assets/images/start-btn-bg.png);
+    background-size: cover;
+    background-repeat: no-repeat;
+    background-position: center center;
+    font-family: KaiTi, KaiTi;
+    font-weight: 400;
+    font-size: calc(24 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    color: #FFFFFF;
+    line-height: calc(29 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  }
+}
+</style>