任一存 преди 2 години
родител
ревизия
ea5f1aa408
променени са 13 файла, в които са добавени 675 реда и са изтрити 13 реда
  1. 2 1
      .env.dev
  2. 2 1
      .env.localdeploy
  3. 1 0
      .env.test
  4. 7 3
      package.json
  5. 4 0
      src/App.vue
  6. 21 0
      src/api.js
  7. 5 3
      src/main.js
  8. 7 1
      src/router/index.js
  9. 106 0
      src/utils/pass.js
  10. 66 0
      src/utils/to.js
  11. 336 0
      src/views/gui/LandmarkEditor.vue
  12. 69 3
      src/views/gui/LandmarkList.vue
  13. 49 1
      yarn.lock

+ 2 - 1
.env.dev

@@ -1,4 +1,5 @@
 CLI_MODE=dev
 NODE_ENV=development
 PUBLIC_PATH=./
-VUE_APP_G_PREFIX=https://super.4dage.com/
+VUE_APP_G_PREFIX=https://super.4dage.com/
+VUE_APP_API_PREFIX=http://192.168.20.55:8048

+ 2 - 1
.env.localdeploy

@@ -1,4 +1,5 @@
 CLI_MODE=localdeploy
 NODE_ENV=production
 PUBLIC_PATH=./
-VUE_APP_G_PREFIX=http://localhost/
+VUE_APP_G_PREFIX=http://localhost/
+VUE_APP_API_PREFIX=http://192.168.20.55:8048

+ 1 - 0
.env.test

@@ -2,3 +2,4 @@ CLI_MODE=test
 NODE_ENV=production
 PUBLIC_PATH=./
 VUE_APP_G_PREFIX=https://super.4dage.com/
+VUE_APP_API_PREFIX=http://192.168.20.55:8048

+ 7 - 3
package.json

@@ -10,17 +10,21 @@
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
+    "alloyfinger": "^0.1.16",
     "axios": "^1.1.3",
     "core-js": "^3.8.3",
+    "css3transform": "^1.2.3",
     "dayjs": "^1.11.7",
     "element-ui": "^2.15.6",
     "html2canvas": "^1.3.3",
     "install": "^0.13.0",
+    "js-base64": "^3.7.5",
     "swiper": "^8.4.4",
     "v-viewer": "^1.6.4",
+    "vant": "^2.12.54",
+    "vue": "^2.6.14",
     "vue-lazyload": "^1.3.3",
     "vue-router": "^3.5.1",
-    "vue": "^2.6.14",
     "vuex": "^3.6.2"
   },
   "devDependencies": {
@@ -31,10 +35,10 @@
     "@vue/cli-plugin-router": "~5.0.0",
     "@vue/cli-plugin-vuex": "~5.0.0",
     "@vue/cli-service": "~5.0.0",
-    "eslint-plugin-vue": "^8.0.3",
     "eslint": "^7.32.0",
-    "less-loader": "^5.0.0",
+    "eslint-plugin-vue": "^8.0.3",
     "less": "^3.0.4",
+    "less-loader": "^5.0.0",
     "vue-template-compiler": "^2.6.14"
   }
 }

+ 4 - 0
src/App.vue

@@ -145,4 +145,8 @@ export default {
 #closepop {
   display: initial;
 }
+
+.element-ui-loading {
+  z-index: 9999 !important;
+}
 </style>

+ 21 - 0
src/api.js

@@ -1,10 +1,31 @@
 import axios from "axios"
 import { goodsData as rawData } from "@/assets/data/data.js"
+import { encodeStr } from "@/utils/pass.js"
+import { Base64 } from "js-base64"
 
 export default {
   fetchRelicList(pageNum = 0, pageSize = 20, keyword = '') {
     return rawData.filter((item) => {
       return item.name.includes(keyword) || keyword === ''
     }).slice(pageNum * pageSize, pageNum * pageSize + pageSize)
+  },
+  async getPersonInImage(file) {
+    const data = new FormData()
+    data.append('file', file)
+    data.append('type', 'img')
+
+    const res = await axios({
+      method: 'post',
+      url: `${process.env.VUE_APP_API_PREFIX}/api/cms/cut/upload`,
+      headers: {
+        token: encodeStr(Base64.encode(Date.now())),
+      },
+      data,
+    })
+    if (res?.data?.code === 0) {
+      return res.data.data
+    } else {
+      throw ('人像抠图失败:', res)
+    }
   }
 }

+ 5 - 3
src/main.js

@@ -4,6 +4,8 @@ import 'viewerjs/dist/viewer.css'
 import App from './App.vue'
 import clickOutside from "@/directives/v-click-outside.js"
 import ElementUI from 'element-ui'
+import Popup from 'vant/lib/popup'
+import 'vant/lib/popup/style'
 import router from './router'
 import Viewer from 'v-viewer'
 import Vue from 'vue'
@@ -19,6 +21,8 @@ if (!browser.mobile) {
   location.replace(`${location.origin}/gzswc/pc/index.html#/`)
 }
 
+Vue.config.productionTip = false
+
 Vue.use(Viewer, {
   defaultOptions: {
     navbar: false,
@@ -28,12 +32,10 @@ Vue.use(Viewer, {
     title: false
   }
 })
-
-
-Vue.config.productionTip = false
 Vue.use(clickOutside)
 Vue.use(ElementUI)
 Vue.use(VueLazyload)
+Vue.use(Popup)
 
 new Vue({
   router,

+ 7 - 1
src/router/index.js

@@ -5,6 +5,7 @@ import Cover from '@/views/gui/Cover.vue'
 import RelicsList from '@/views/gui/RelicsList.vue'
 import RelicDetail from "@/views/gui/RelicDetail.vue"
 import LandmarkList from "@/views/gui/LandmarkList.vue"
+import LandmarkEditor from "@/views/gui/LandmarkEditor.vue"
 
 const originalPush = VueRouter.prototype.push
 VueRouter.prototype.push = function push (location) {
@@ -39,7 +40,12 @@ const routes = [
         path: 'landmark-list',
         name: 'LandmarkList',
         component: LandmarkList,
-      }
+      },
+      {
+        path: 'landmark-editor',
+        name: 'LandmarkEditor',
+        component: LandmarkEditor,
+      },
     ]
   }
 ]

+ 106 - 0
src/utils/pass.js

@@ -0,0 +1,106 @@
+/* eslint-disable */
+function NoToChinese (num) {
+  num = String(num)
+  var chnNumChar = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
+  if (num == 0) {
+    return chnNumChar[0]
+  }
+  let tmp = ''
+  for (let i = 0; i < num.length; i++) {
+    const ele = num.charAt(i)
+    tmp += chnNumChar[ele]
+  }
+
+  return tmp
+}
+
+function randomWord (randomFlag, min, max) {
+  let str = ''
+  let range = min
+  const arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
+  // 随机产生
+  if (randomFlag) {
+    range = Math.round(Math.random() * (max - min)) + min
+  }
+  for (var i = 0; i < range; i++) {
+    const pos = Math.round(Math.random() * (arr.length - 1))
+    str += arr[pos]
+  }
+  return str
+}
+
+module.exports = {
+  formatDate: function (time) {
+    var weekArr = ['日', '一', '二', '三', '四', '五', '六']
+    var date = new Date(time)
+    var year = date.getFullYear()
+    var month = date.getMonth() + 1
+    var day = date.getDate()
+    var week = '星期' + weekArr[date.getDay()]
+    if (window.innerWidth < 1700) {
+      return (
+        year +
+        '年' +
+        (String(month).length > 1 ? month : '0' + month) +
+        '月' +
+        (String(day).length > 1 ? day : '0' + day) +
+        '日' +
+        '<br/>' +
+        week
+      )
+    }
+    return (
+      year +
+      '年' +
+      (String(month).length > 1 ? month : '0' + month) +
+      '月' +
+      (String(day).length > 1 ? day : '0' + day) +
+      '日' +
+      ' ' +
+      week
+    )
+  },
+  smoothscrollpos: function (domName) {
+    if (domName == '/') {
+      return window.scrollTo(0, 0)
+    }
+    const smoothscroll = () => {
+      const dom = document.getElementById(domName)
+      // window.scrollTo({
+      //   top:dom.offsetTop - 100,
+      //   left:0,
+      //   behavior: "smooth"
+      // })
+      dom && window.scrollTo(0, dom.offsetTop - 120)
+    }
+    smoothscroll()
+  },
+
+  formatTime: function (time, fan = false) {
+    let t1 = time.split(' ')[0].split('-')
+    if (fan) {
+      t1 = t1.map((item) => {
+        const t = NoToChinese(item)
+        return t
+      })
+    }
+    return t1
+  },
+  encodeStr: function (str, strv = '') {
+    const NUM = 2
+    const front = randomWord(false, 8)
+    const middle = randomWord(false, 8)
+    const end = randomWord(false, 8)
+
+    const str1 = str.substring(0, NUM)
+    const str2 = str.substring(NUM)
+
+    if (strv) {
+      const strv1 = strv.substring(0, NUM)
+      const strv2 = strv.substring(NUM)
+      return [front + str2 + middle + str1 + end, front + strv2 + middle + strv1 + end]
+    }
+
+    return front + str2 + middle + str1 + end
+  }
+}

+ 66 - 0
src/utils/to.js

@@ -0,0 +1,66 @@
+(function () {
+  var lastTime = 0
+  var vendors = ['webkit', 'moz']
+  for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
+    window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']
+    window.cancelAnimationFrame =
+          window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame']
+  }
+
+  if (!window.requestAnimationFrame)
+    window.requestAnimationFrame = function (callback) {
+      var currTime = new Date().getTime()
+      var timeToCall = Math.max(0, 16 - (currTime - lastTime))
+      var id = window.setTimeout(function () { callback(currTime + timeToCall) },
+        timeToCall)
+      lastTime = currTime + timeToCall
+      return id
+    }
+
+  if (!window.cancelAnimationFrame)
+    window.cancelAnimationFrame = function (id) {
+      clearTimeout(id)
+    }
+}())
+
+var To = function (el, property, value, time, ease, onEnd, onChange ) {
+  var current = el[property]
+  var dv = value - current
+  var beginTime = new Date()
+  var self = this
+  var currentEase = ease || function(a) {return a }
+  this.tickID = null
+  var toTick = function () {
+    var dt = new Date() - beginTime
+    if (dt >= time) {
+      el[property] = value
+      onChange && onChange(value)
+      onEnd && onEnd(value)
+      cancelAnimationFrame(self.tickID)
+      self.toTick = null
+      return
+    }
+    el[property] = dv * currentEase(dt / time) + current
+    self.tickID = requestAnimationFrame(toTick)
+    //self.tickID = requestAnimationFrame(toTick);
+    //cancelAnimationFrame������ tickID = requestAnimationFrame(toTick);���
+    onChange && onChange(el[property])
+  }
+  toTick()
+  To.List.push(this)
+}
+
+To.List = []
+
+To.stopAll = function() {
+  for (var i = 0, len = To.List.length; i < len; i++) {
+    cancelAnimationFrame(To.List[i].tickID)
+  }
+  To.List.length = 0
+}
+
+To.stop = function(to) {
+  cancelAnimationFrame(to.tickID)
+}
+
+module.exports = To

+ 336 - 0
src/views/gui/LandmarkEditor.vue

@@ -0,0 +1,336 @@
+<template>
+  <div class="landmark-editor">
+    <div class="group-photo-wrapper">
+      <img
+        src="@/assets/images/entries/wen-wu-shang-xi.png"
+        alt=""
+        class="bg"
+        draggable="false"
+      >
+      <img
+        ref="person"
+        class="person"
+        :src="personImgUrl"
+        alt=""
+        draggable="false"
+      >
+    </div>
+
+    <div class="button-wrapper">
+      <div class="line1">
+        <button
+          class="give-up"
+          @click="$router.go(-2)"
+        >
+          放弃
+        </button>
+        <button
+          class="redo"
+          @click="onClickRedo"
+        >
+          重拍
+        </button>
+      </div>
+      <button
+        class="save"
+        @click="onClickSave"
+      >
+        保存
+      </button>
+    </div>
+    <a
+      v-show="false"
+      ref="for-download"
+      :href="aDownloadHref"
+      download="photo.jpg"
+    />
+    <van-popup
+      v-model="isShowPopup"
+      class="popup-bottom"
+      round
+      position="bottom"
+    >
+      <button @click="$router.go(-1)">
+        重选场景
+      </button>
+      <button @click="onClickReshot">
+        重新拍摄
+      </button>
+      <button @click="isShowPopup = false">
+        取消
+      </button>
+    </van-popup>
+    <input
+      id="input"
+      ref="input"
+      type="file"
+      accept="image/*"
+      capture="user"
+      @input="onInput"
+    >
+  </div>
+</template>
+
+<script>
+const AlloyFinger = require("alloyfinger")
+const Transform = require("css3transform")
+const To = require("@/utils/to.js")
+import html2canvas from "html2canvas"
+
+export default {
+  data() {
+    return {
+      aDownloadHref: '',
+      isShowPopup: false,
+    }
+  },
+  computed: {
+    personImgUrl() {
+      return decodeURI(this.$route.query.personImgUrl)
+    }
+  },
+  mounted() {
+    function ease(x) {
+      return Math.sqrt(1 - Math.pow(x - 1, 2))
+    }
+
+    var initScale = 1
+
+    const target = this.$refs.person
+
+    Transform(target)
+
+    new AlloyFinger(target, {
+    // 多点触摸时重置状态
+      multipointStart: function() {
+        To.stopAll()
+        initScale = target.scaleX
+      },
+      // 旋转
+      rotate: function(evt) {
+        target.rotateZ += evt.angle
+      },
+      // 缩放
+      pinch: function(evt) {
+        target.scaleX = target.scaleY = initScale * evt.zoom
+      },
+      // 触摸结束时的动画
+      multipointEnd: function() {
+        // 使用To.js来管理js开启的动画
+        To.stopAll()
+
+        // // 最小缩放到0.5倍
+        // if (target.scaleX < 0.5) {
+        //   new To(target, "scaleX", 0.5, 500, ease)
+        //   new To(target, "scaleY", 0.5, 500, ease)
+        // }
+        // // 最大2倍
+        // if (target.scaleX > 2) {
+        //   new To(target, "scaleX", 2, 500, ease)
+        //   new To(target, "scaleY", 2, 500, ease)
+        // }
+
+        // 取旋转角度
+        var rotation = target.rotateZ % 360
+
+        if (rotation < 0) rotation = 360 + rotation
+        target.rotateZ = rotation
+
+        // // 角度回弹
+        // if (rotation > 0 && rotation < 45) {
+        //   new To(target, "rotateZ", 0, 500, ease)
+        // } else if (rotation >= 315) {
+        //   new To(target, "rotateZ", 360, 500, ease)
+        // } else if (rotation >= 45 && rotation < 135) {
+        //   new To(target, "rotateZ", 90, 500, ease)
+        // } else if (rotation >= 135 && rotation < 225) {
+        //   new To(target, "rotateZ", 180, 500, ease)
+        // } else if (rotation >= 225 && rotation < 315) {
+        //   new To(target, "rotateZ", 270, 500, ease)
+        // }
+      },
+      // 拖拽
+      pressMove: function(evt) {
+        target.translateX += evt.deltaX
+        target.translateY += evt.deltaY
+        evt.preventDefault()
+      }
+    })
+  },
+  methods: {
+    onClickRedo() {
+      this.isShowPopup = true
+    },
+    garenteeValid(originalFile) { // 确保格式为jpg
+      return new Promise((resolve) => {
+        // 选中的图片以dataUrl形式存入FileReader,
+        let imgFile = new FileReader()
+        imgFile.readAsDataURL(originalFile)
+        imgFile.onload = function (loadEvent) {
+          // 把FileReader中的dataUrl转换成一个Image对象
+          let image = new Image()
+          image.src = loadEvent.target.result
+          image.onload = function () {
+            // 计算理想尺寸
+            let newWidth = image.width
+            let newHeight = image.height
+            if (image.width >= 2000 || image.height >= 2000) {
+              if (image.width > image.height) {
+                newWidth = 1999
+                newHeight = image.height / image.width * newWidth
+              } else {
+                newHeight = 1999
+                newWidth = image.width / image.height * newHeight
+              }
+            }
+
+            // 把Image画到canvas上
+            let canvas = document.createElement("canvas")
+            // document.body.append(canvas)
+            // canvas.style.position = 'absolute'
+            // canvas.style.top = 0
+            // canvas.style.left = 0
+            // canvas.style.zIndex = 999999999
+            canvas.width = newWidth
+            canvas.height = newHeight
+            canvas.getContext("2d").drawImage(image, 0, 0, newWidth, newHeight)
+            // canvas转为jpg格式的blob
+            canvas.toBlob((blob) => {
+              // Blob转为File
+              const fileToSend = new File([blob], `1.jpg`, { type: blob.type })
+              resolve(fileToSend)
+            }, 'image/jpeg')
+          }
+        }
+      })
+    },
+    onClickReshot() {
+      this.$refs.input.click()
+    },
+    onInput(e) {
+      if (!e.target.value) {
+        return
+      }
+      const loadingHandler = this.$loading({
+        lock: true,
+        text: 'Loading',
+        spinner: 'el-icon-loading',
+        background: 'rgba(0, 0, 0, 0.7)',
+        customClass: 'element-ui-loading'
+      })
+      this.garenteeValid(e.target.files[0]).then((jpgFile) => {
+        return globalApi.getPersonInImage(jpgFile)
+      }).then((url) => {
+        loadingHandler.close()
+        // 不能直接该query.personImgUrl的值,会不生效。
+        this.$router.replace({
+          name: this.$route.name,
+          query: {
+            personImgUrl: encodeURI(url)
+          }
+        })
+        this.isShowPopup = false
+      }).catch((err) => {
+        loadingHandler.close()
+        console.error(err)
+        window.alert('请上传包含人物的图片')
+      })
+    },
+    onClickSave() {
+      html2canvas(document.querySelector('.group-photo-wrapper'), {
+        useCORS: true, // 【重要】开启跨域配置
+        scale: 1,
+        allowTaint: true, // 允许跨域图片
+        preserveDrawingBuffer: true,
+      }).then((canvas) => {
+        this.aDownloadHref = canvas.toDataURL('image/jpeg', 1.0)
+        this.$nextTick(() => {
+          this.$refs['for-download'].click()
+        })
+      })
+    },
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.landmark-editor {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 9999;
+  background: #E7DDD6;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-evenly;
+  > .group-photo-wrapper {
+    width: 92.4vw;
+    height: fit-content;
+    overflow: hidden;
+    position: relative;
+    > img.bg {
+      width: 100%;
+    }
+    > img.person {
+      position: absolute;
+      left: 60%;
+      top: 40%;
+      width: 30vw;
+    }
+  }
+  > .button-wrapper {
+    > .line1 {
+      display: flex;
+      justify-content: space-between;
+      button.give-up {
+        width: 42.7vw;
+        height: 16vw;
+        border-radius: 8vw;
+        border: 0.3vw solid #A33328;
+        font-size: 4.3vw;
+        color: #A33328;
+      }
+      button.redo {
+        width: 42.7vw;
+        height: 16vw;
+        border-radius: 8vw;
+        border: 0.3vw solid #A33328;
+        font-size: 4.3vw;
+        color: #A33328;
+      }
+    }
+    > .save {
+      margin-top: 4.5vw;
+      width: 91.1vw;
+      height: 16vw;
+      background: #A33328;
+      border-radius: 8vw;
+      font-size: 4.3vw;
+      color: #fff;
+    }
+  }
+  > .popup-bottom {
+    height: 63.1vw;
+    > button {
+      height: 33.333%;
+      width: 100%;
+      border-bottom: 1px solid #ccc;
+      color: black;
+      &:last-of-type {
+        border-bottom: none;
+      }
+    }
+  }
+  > #input {
+    display: none;
+  }
+}
+</style>
+
+http://vibktprfx-prod-prod-damo-eas-cn-shanghai.oss-cn-shanghai.aliyuncs.com/segment-body/2023-05-17/a22f4727-a9e8-47f0-bd9c-ad71bfb0d237/image.png?Expires=1684313991&OSSAccessKeyId=LTAI4FoLmvQ9urWXgSRpDvh1&Signature=galFAcHHalE%2Bm39ckgeyep4OD6o%3D
+http%3A%2F%2Fvibktprfx-prod-prod-damo-eas-cn-shanghai.oss-cn-shanghai.aliyuncs.com%2Fsegment-body%2F2023-05-17%2F082bc112-522a-40e6-90c7-aeca0baf899d%2Fimage.png%3FExpires%3D1684313986%26OSSAccessKeyId%3DLTAI4FoLmvQ9urWXgSRpDvh1%26Signature%3DCgwrX3t%25252BllmZxlZHfCPoxnZNJK4%25253D

+ 69 - 3
src/views/gui/LandmarkList.vue

@@ -39,7 +39,6 @@
     </label>
     <input
       id="input"
-      ref="input"
       type="file"
       accept="image/*"
       capture="user"
@@ -95,6 +94,7 @@ export default {
         },
       ],
       selectedIdx: null,
+      isLoading: false,
     }
   },
   mounted() {
@@ -107,9 +107,75 @@ export default {
         this.selectedIdx = index
       }
     },
+    garenteeValid(originalFile) { // 确保格式为jpg
+      return new Promise((resolve) => {
+        // 选中的图片以dataUrl形式存入FileReader,
+        let imgFile = new FileReader()
+        imgFile.readAsDataURL(originalFile)
+        imgFile.onload = function (loadEvent) {
+          // 把FileReader中的dataUrl转换成一个Image对象
+          let image = new Image()
+          image.src = loadEvent.target.result
+          image.onload = function () {
+            // 计算理想尺寸
+            let newWidth = image.width
+            let newHeight = image.height
+            if (image.width >= 2000 || image.height >= 2000) {
+              if (image.width > image.height) {
+                newWidth = 1999
+                newHeight = image.height / image.width * newWidth
+              } else {
+                newHeight = 1999
+                newWidth = image.width / image.height * newHeight
+              }
+            }
+
+            // 把Image画到canvas上
+            let canvas = document.createElement("canvas")
+            // document.body.append(canvas)
+            // canvas.style.position = 'absolute'
+            // canvas.style.top = 0
+            // canvas.style.left = 0
+            // canvas.style.zIndex = 999999999
+            canvas.width = newWidth
+            canvas.height = newHeight
+            canvas.getContext("2d").drawImage(image, 0, 0, newWidth, newHeight)
+            // canvas转为jpg格式的blob
+            canvas.toBlob((blob) => {
+              // Blob转为File
+              const fileToSend = new File([blob], `1.jpg`, { type: blob.type })
+              resolve(fileToSend)
+            }, 'image/jpeg')
+          }
+        }
+      })
+    },
     onInput(e) {
-      console.log(e)
-      console.log(this.$refs.input.value)
+      if (!e.target.value) {
+        return
+      }
+      const loadingHandler = this.$loading({
+        lock: true,
+        text: 'Loading',
+        spinner: 'el-icon-loading',
+        background: 'rgba(0, 0, 0, 0.7)',
+        customClass: 'element-ui-loading'
+      })
+      this.garenteeValid(e.target.files[0]).then((jpgFile) => {
+        return globalApi.getPersonInImage(jpgFile)
+      }).then((url) => {
+        loadingHandler.close()
+        this.$router.push({
+          name: 'LandmarkEditor',
+          query: {
+            personImgUrl: encodeURI(url)
+          }
+        })
+      }).catch((err) => {
+        loadingHandler.close()
+        console.error(err)
+        window.alert('请上传包含人物的图片')
+      })
     }
   },
 }

+ 49 - 1
yarn.lock

@@ -931,6 +931,13 @@
   resolved "https://registry.npmmirror.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz"
   integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
 
+"@babel/runtime@7.x":
+  version "7.21.5"
+  resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200"
+  integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==
+  dependencies:
+    regenerator-runtime "^0.13.11"
+
 "@babel/runtime@^7.12.13", "@babel/runtime@^7.8.4":
   version "7.21.0"
   resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.21.0.tgz"
@@ -1309,7 +1316,17 @@
   dependencies:
     "@types/node" "*"
 
-"@vue/babel-helper-vue-jsx-merge-props@^1.4.0":
+"@vant/icons@^1.7.1":
+  version "1.8.0"
+  resolved "https://registry.npmmirror.com/@vant/icons/-/icons-1.8.0.tgz#36b13f2e628b533f6523a93a168cf02f07056674"
+  integrity sha512-sKfEUo2/CkQFuERxvkuF6mGQZDKu3IQdj5rV9Fm0weJXtchDSSQ+zt8qPCNUEhh9Y8shy5PzxbvAfOOkCwlCXg==
+
+"@vant/popperjs@^1.1.0":
+  version "1.3.0"
+  resolved "https://registry.npmmirror.com/@vant/popperjs/-/popperjs-1.3.0.tgz#e0eff017124b5b2352ef3b36a6df06277f4400f2"
+  integrity sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw==
+
+"@vue/babel-helper-vue-jsx-merge-props@^1.0.0", "@vue/babel-helper-vue-jsx-merge-props@^1.4.0":
   version "1.4.0"
   resolved "https://registry.npmmirror.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.4.0.tgz"
   integrity sha512-JkqXfCkUDp4PIlFdDQ0TdXoIejMtTHP67/pvxlgeY+u5k3LEdKuWZ3LK6xkxo52uDoABIVyRwqVkfLQJhk7VBA==
@@ -1799,6 +1816,11 @@ ajv@^8.0.0, ajv@^8.0.1, ajv@^8.8.0:
     require-from-string "^2.0.2"
     uri-js "^4.2.2"
 
+alloyfinger@^0.1.16:
+  version "0.1.16"
+  resolved "https://registry.npmmirror.com/alloyfinger/-/alloyfinger-0.1.16.tgz#8d5d46073cf1fedba5f446e666bc4b3925b8b8b5"
+  integrity sha512-AfsLALs929WQsjSk1pbysoiVU3bgm/4k1wdZDtMQ7uI7b8XweqCCnUiBYqqdp8uPZ1fBq/+LCJhgUlhd90FssQ==
+
 ansi-colors@^4.1.1:
   version "4.1.3"
   resolved "https://registry.npmmirror.com/ansi-colors/-/ansi-colors-4.1.3.tgz"
@@ -2551,6 +2573,11 @@ css-what@^6.0.1:
   resolved "https://registry.npmmirror.com/css-what/-/css-what-6.1.0.tgz"
   integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
 
+css3transform@^1.2.3:
+  version "1.2.3"
+  resolved "https://registry.npmmirror.com/css3transform/-/css3transform-1.2.3.tgz#dbb6c7f706224bbf29239ca6625877c4bf75b68d"
+  integrity sha512-6VmJ0s6X+mUuYh0xAxqhSi0i3vt52gAu3lL1mBoXLYPGfPHOm2pjYNRDYD6pdrBrszU/4njk5a9t6K2JjwX1Yg==
+
 cssesc@^3.0.0:
   version "3.0.0"
   resolved "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz"
@@ -3871,6 +3898,11 @@ joi@^17.4.0:
     "@sideway/formula" "^3.0.1"
     "@sideway/pinpoint" "^2.0.0"
 
+js-base64@^3.7.5:
+  version "3.7.5"
+  resolved "https://registry.npmmirror.com/js-base64/-/js-base64-3.7.5.tgz#21e24cf6b886f76d6f5f165bfcd69cc55b9e3fca"
+  integrity sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==
+
 js-message@1.0.7:
   version "1.0.7"
   resolved "https://registry.npmmirror.com/js-message/-/js-message-1.0.7.tgz"
@@ -5937,6 +5969,17 @@ validate-npm-package-license@^3.0.1:
     spdx-correct "^3.0.0"
     spdx-expression-parse "^3.0.0"
 
+vant@^2.12.54:
+  version "2.12.54"
+  resolved "https://registry.npmmirror.com/vant/-/vant-2.12.54.tgz#0bc52d80414422987cdb9b7e7c101a66d3647d8d"
+  integrity sha512-t7DCiLxNosDrg0Jm5EY9p0A5cAMo5OadmizbYtPEc0ru+OJKEa3kcfxtKIK5on7ZPqoOkyYJt8e6BQ1VDMPsrg==
+  dependencies:
+    "@babel/runtime" "7.x"
+    "@vant/icons" "^1.7.1"
+    "@vant/popperjs" "^1.1.0"
+    "@vue/babel-helper-vue-jsx-merge-props" "^1.0.0"
+    vue-lazyload "1.2.3"
+
 vary@~1.1.2:
   version "1.1.2"
   resolved "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz"
@@ -5965,6 +6008,11 @@ vue-hot-reload-api@^2.3.0:
   resolved "https://registry.npmmirror.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz"
   integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==
 
+vue-lazyload@1.2.3:
+  version "1.2.3"
+  resolved "https://registry.npmmirror.com/vue-lazyload/-/vue-lazyload-1.2.3.tgz#901f9ec15c7e6ca78781a2bae4a343686bdedb2c"
+  integrity sha512-DC0ZwxanbRhx79tlA3zY5OYJkH8FYp3WBAnAJbrcuoS8eye1P73rcgAZhyxFSPUluJUTelMB+i/+VkNU/qVm7g==
+
 vue-lazyload@^1.3.3:
   version "1.3.5"
   resolved "https://registry.npmmirror.com/vue-lazyload/-/vue-lazyload-1.3.5.tgz#eb36d299a519167d987fdf0ebfdc9c6dd1bf1ef0"