Bladeren bron

地图页面功能

任一存 1 jaar geleden
bovenliggende
commit
161186f53c

+ 45 - 0
package-lock.json

@@ -8,6 +8,7 @@
       "name": "vue3-js-example",
       "version": "1.0.0",
       "dependencies": {
+        "@floating-ui/dom": "^1.6.5",
         "core-js": "^3.8.3",
         "dayjs": "^1.11.7",
         "vue": "^3.2.13",
@@ -1873,6 +1874,28 @@
         "node": ">=10"
       }
     },
+    "node_modules/@floating-ui/core": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.6.1.tgz",
+      "integrity": "sha512-42UH54oPZHPdRHdw6BgoBD6cg/eVTmVrFcgeRDM3jbO7uxSoipVcmcIGFcA5jmOHO5apcyvBhkSKES3fQJnu7A==",
+      "dependencies": {
+        "@floating-ui/utils": "^0.2.0"
+      }
+    },
+    "node_modules/@floating-ui/dom": {
+      "version": "1.6.5",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.6.5.tgz",
+      "integrity": "sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==",
+      "dependencies": {
+        "@floating-ui/core": "^1.0.0",
+        "@floating-ui/utils": "^0.2.0"
+      }
+    },
+    "node_modules/@floating-ui/utils": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.2.tgz",
+      "integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw=="
+    },
     "node_modules/@hapi/hoek": {
       "version": "9.3.0",
       "resolved": "https://registry.npmmirror.com/@hapi/hoek/-/hoek-9.3.0.tgz",
@@ -12593,6 +12616,28 @@
         }
       }
     },
+    "@floating-ui/core": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.6.1.tgz",
+      "integrity": "sha512-42UH54oPZHPdRHdw6BgoBD6cg/eVTmVrFcgeRDM3jbO7uxSoipVcmcIGFcA5jmOHO5apcyvBhkSKES3fQJnu7A==",
+      "requires": {
+        "@floating-ui/utils": "^0.2.0"
+      }
+    },
+    "@floating-ui/dom": {
+      "version": "1.6.5",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.6.5.tgz",
+      "integrity": "sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==",
+      "requires": {
+        "@floating-ui/core": "^1.0.0",
+        "@floating-ui/utils": "^0.2.0"
+      }
+    },
+    "@floating-ui/utils": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.2.tgz",
+      "integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw=="
+    },
     "@hapi/hoek": {
       "version": "9.3.0",
       "resolved": "https://registry.npmmirror.com/@hapi/hoek/-/hoek-9.3.0.tgz",

+ 1 - 0
package.json

@@ -9,6 +9,7 @@
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
+    "@floating-ui/dom": "^1.6.5",
     "core-js": "^3.8.3",
     "dayjs": "^1.11.7",
     "vue": "^3.2.13",

File diff suppressed because it is too large
+ 49 - 135
src/App.vue


BIN
src/assets/images/btn-back.png


BIN
src/assets/images/icon-locate-colorful.png


BIN
src/assets/images/map - 副本.png


BIN
src/assets/images/map.png


BIN
src/assets/images/poi-icon-in-map.png


BIN
src/assets/images/手绘二维地图.jpg


File diff suppressed because it is too large
+ 166 - 0
src/config.js


+ 3 - 0
src/main.js

@@ -7,6 +7,7 @@ import store from "./store";
 import packageJson from "../package.json";
 import "@/assets/style/reset.css";
 import "@/assets/style/my-reset.css";
+import { MessageCenter } from "@/message-center.js";
 
 const packageName = packageJson.name;
 const rootId = packageName + "__id";
@@ -99,3 +100,5 @@ export async function update(customProps) {
   // 假如没有相关需求,该生命周期可以删除
   console.log(`[${packageName}] app update`, customProps);
 }
+
+window.messageCenter = new MessageCenter();

+ 52 - 0
src/message-center.js

@@ -0,0 +1,52 @@
+/* eslint-disable */
+
+/**
+ * 突然想到,无论是web端、小程序还是node中,似乎都不需要这货,或者其他第三方库……因为node中有类似的API,而web端和小程序中可以通过在document上手动触发CustomEvent、注册监听器来实现,不过确实麻烦一些。Vue中还可以直接通过一个通信总线组件来实现。
+ */
+
+/**
+ * 如果想让注册的回调只调用一次就自动注销,在注册的回调中执行注销即可。
+ */
+export class MessageCenter {
+  constructor() {
+    this._recorder = {};
+  }
+
+  logInvalidParam() {
+    console.error("MessageCenter: invalid parameter.");
+  }
+
+  subscribe(message, callback) {
+    if (typeof message !== "string" || typeof callback !== "function") {
+      this.logInvalidParam();
+      return;
+    }
+
+    if (!Object.prototype.hasOwnProperty.call(this._recorder, message)) {
+      this._recorder[message] = [];
+    }
+    this._recorder[message].push(callback);
+  }
+
+  unsubscribe(message, callback) {
+    if (typeof message !== "string" || typeof callback !== "function") {
+      this.logInvalidParam();
+      return;
+    }
+
+    if (Object.prototype.hasOwnProperty.call(this._recorder, message)) {
+      const idx = this._recorder[message].indexOf(callback);
+      if (idx !== -1) {
+        this._recorder[message].splice(idx, 1);
+      }
+    }
+  }
+
+  publish(message, param) {
+    if (Object.prototype.hasOwnProperty.call(this._recorder, message)) {
+      this._recorder[message].forEach((callback) => {
+        callback(param);
+      });
+    }
+  }
+}

+ 6 - 0
src/router/index.js

@@ -1,4 +1,5 @@
 import PoiInfo from "@/views/PoiInfo.vue";
+import MapView from "@/views/MapView.vue";
 
 const routes = [
   // {
@@ -10,6 +11,11 @@ const routes = [
     name: "PoiInfo",
     component: PoiInfo,
   },
+  {
+    path: "/map",
+    name: "MapView",
+    component: MapView,
+  },
 ];
 
 export default routes;

+ 246 - 0
src/views/MapView.vue

@@ -0,0 +1,246 @@
+<template>
+  <div class="map-view">
+    <button class="back" @click="router.go(-1)"></button>
+    <button
+      ref="poiButtonElList"
+      class="poi-icon"
+      v-for="(item, idx) in poiList"
+      :key="item.name"
+      :style="{
+        left: item.posOnMapPage.left,
+        top: item.posOnMapPage.top,
+      }"
+      @click="activePoiIdx = idx"
+    ></button>
+    <transition name="fade-in">
+      <div class="poi-info-dialog" v-if="activePoiIdx !== null" ref="dialogEl">
+        <div ref="arrowEl" class="arrow" />
+        <div class="top-bar">
+          <button class="locate" @click="onClickLocateBtn">
+            <img
+              class=""
+              src="@/assets/images/icon-locate.png"
+              alt=""
+              draggable="false"
+            />
+          </button>
+          <button class="close" @click="activePoiIdx = null">
+            <img
+              class=""
+              src="@/assets/images/icon-close.png"
+              alt=""
+              draggable="false"
+            />
+          </button>
+        </div>
+        <div class="content-wrap">
+          <img
+            class="photo"
+            :src="
+              require(`@/assets/images/poiImages/${activePoiPhotoName}.jpg`)
+            "
+            alt=""
+            draggable="false"
+          />
+          <h3>{{ activePoiName }}</h3>
+          <div class="text-wrap">
+            <p
+              v-for="(item, index) in characterText"
+              :key="index"
+              v-html="item"
+            ></p>
+          </div>
+        </div>
+      </div>
+    </transition>
+  </div>
+</template>
+
+<script setup>
+import {
+  ref,
+  computed,
+  watch,
+  // onMounted,
+  // inject
+} from "vue";
+import { useRouter } from "vue-router";
+// import { useStore } from "vuex"
+import { poiList } from "@/config.js";
+import { computePosition, offset, flip, shift, arrow } from "@floating-ui/dom";
+
+// const route = useRoute()
+const router = useRouter();
+// const store = useStore()
+
+// const $env = inject('$env')
+
+const poiButtonElList = ref([]);
+const dialogEl = ref(null);
+const arrowEl = ref(null);
+
+const activePoiIdx = ref(null);
+const activePoiPhotoName = computed(() => {
+  return poiList[activePoiIdx.value]?.name;
+});
+const activePoiName = computed(() => {
+  return poiList[activePoiIdx.value]?.name;
+});
+const characterText = computed(() => {
+  return poiList[activePoiIdx.value]?.text;
+});
+watch(activePoiIdx, (v) => {
+  if (v === null) {
+    return;
+  }
+  setTimeout(() => {
+    computePosition(poiButtonElList.value[v], dialogEl.value, {
+      placement: "top",
+      middleware: [
+        offset(10),
+        flip(),
+        shift({ padding: 10 }),
+        arrow({
+          element: arrowEl.value,
+          padding: 5,
+        }),
+      ],
+    }).then(({ x, y, placement, middlewareData }) => {
+      Object.assign(dialogEl.value.style, {
+        left: `${x}px`,
+        top: `${y}px`,
+      });
+
+      const { x: arrowX } = middlewareData.arrow;
+      const arrowPlacement = {
+        top: "bottom",
+        right: "left",
+        bottom: "top",
+        left: "right",
+      }[placement.split("-")[0]];
+      const dialogPlacement2arrowRotate = {
+        top: "-135deg",
+        right: "-45deg",
+        bottom: "45deg",
+        left: "135deg",
+      };
+      Object.assign(arrowEl.value.style, {
+        top: "initial",
+        bottom: "initial",
+        left: "initial",
+        right: "initial",
+      });
+      Object.assign(arrowEl.value.style, {
+        left: arrowX != null ? `${arrowX}px` : "",
+        [arrowPlacement]: -6 + "px",
+        transform: `rotate(${dialogPlacement2arrowRotate[placement]})`,
+      });
+    });
+  }, 0);
+});
+
+function onClickLocateBtn() {
+  window.messageCenter.publish("nav-to-poi", activePoiIdx.value);
+  router.push(`/`);
+}
+</script>
+
+<style lang="less" scoped>
+.map-view {
+  pointer-events: initial;
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  background-image: url(@/assets/images/map.png);
+  background-size: 100% auto;
+  background-repeat: no-repeat;
+  background-position: center center;
+  background-color: #fff;
+  > button.back {
+    position: absolute;
+    top: 33px;
+    left: 20px;
+    width: 50px;
+    height: 50px;
+    background-image: url(@/assets/images/btn-back.png);
+    background-size: cover;
+    background-repeat: no-repeat;
+    background-position: center center;
+  }
+  > button.poi-icon {
+    position: absolute;
+    width: 30px;
+    height: 30px;
+    background-image: url(@/assets/images/poi-icon-in-map.png);
+    background-size: contain;
+    background-repeat: no-repeat;
+    background-position: center center;
+    transform: translate(-50%, -55%);
+  }
+
+  > .poi-info-dialog {
+    position: absolute;
+    width: 30.4vh;
+    height: 47.6%;
+    background-color: rgba(0, 0, 0, 0.7);
+    border-radius: 1vh;
+    padding-top: calc(0.07vh + 6.08vh + 2vh);
+    padding-left: 2vh;
+    padding-right: 2vh;
+    padding-bottom: 2vh;
+    > .arrow {
+      position: absolute;
+      width: 0;
+      height: 0;
+      border-left: solid 6px rgba(0, 0, 0, 0.7);
+      border-top: solid 6px rgba(0, 0, 0, 0.7);
+      border-right: solid 6px transparent;
+      border-bottom: solid 6px transparent;
+    }
+    > .top-bar {
+      position: absolute;
+      top: 0;
+      right: 0;
+      height: 6.08vh;
+      width: 19.02vh;
+      padding-right: 1vh;
+      display: flex;
+      justify-content: flex-end;
+      align-items: center;
+      gap: 2vh;
+      background: linear-gradient(90deg, rgba(153, 153, 153, 0) 0%, #999 100%);
+      border-radius: 3.07vh 1.18vh 0px 0px;
+      > img {
+        width: 1.2vw;
+        height: 1.2vw;
+      }
+    }
+    .content-wrap {
+      height: 100%;
+      overflow-y: auto;
+      > img.photo {
+        width: 100%;
+      }
+      > h3 {
+        font-family: Microsoft YaHei-Bold;
+        font-weight: bold;
+        text-align: center;
+        font-size: 16px;
+        line-height: 26px;
+        color: #fff;
+        margin-bottom: 0.5em;
+      }
+      > .text-wrap {
+        width: 100%;
+        font-family: Microsoft YaHei, Microsoft YaHei;
+        font-weight: 400;
+        font-size: 14px;
+        color: #fff;
+        line-height: 1.8;
+      }
+    }
+  }
+}
+</style>

File diff suppressed because it is too large
+ 6330 - 6404
yarn.lock