Przeglądaj źródła

feat: 封装图形验证为公共组件

zhouyuhao 1 rok temu
rodzic
commit
08edc83c95

+ 1 - 0
src/components/VSliderVerification/index.ts

@@ -0,0 +1 @@
+export { default } from './src/VSliderVerification.vue'

+ 236 - 0
src/components/VSliderVerification/src/VSliderVerification.vue

@@ -0,0 +1,236 @@
+<script lang="ts" setup>
+//滑动图片验证码部分
+import Img01 from './image/verification-img-1.png'
+import Img02 from './image/verification-img-2.png'
+import Img03 from './image/verification-img-3.png'
+import Img04 from './image/verification-img-4.png'
+import { ref } from 'vue'
+//引入'vue3-puzzle-vcode'插件
+import Vcode from 'vue3-puzzle-vcode'
+
+const openDialog = () => {
+  isShow.value = true
+  nextTick(() => {
+    addElement()
+    onSuccess()
+    updateSliderBackground('success')
+  })
+}
+// 添加自定义元素
+const addElement = () => {
+  addTipsNode()
+  addSliderBtnNode('start')
+  updateRefreshImg()
+}
+
+const updateRefreshImg = () => {
+  const targetNode: any = document.querySelector('img.reset_')
+  if (targetNode) {
+    // 使用 import.meta.url 结合 new URL 获取图片的正确路径
+    targetNode.src = new URL('./image/icon_refresh_bold_b.png', import.meta.url).href
+  }
+}
+const addTipsNode = () => {
+  const childNode = document.createElement('div')
+  childNode.className = 'tips'
+  childNode.innerHTML = `
+    <p>Please drag the slider below to complete the</p>
+    <p>verification to ensure normal access</p>
+  `
+  const parentNode = document.querySelector('.vue-auth-box_')
+  if (parentNode.firstChild) {
+    parentNode.insertBefore(childNode, parentNode.firstChild)
+  } else {
+    parentNode.appendChild(childNode)
+  }
+}
+
+const styleMap = {
+  start: {
+    thumbColor: 'var(--color-neutral-1)',
+    thumbIcon: 'icon-icon_drag__line_b',
+    trackBackground: 'var(--color-success)'
+  },
+  success: {
+    thumbColor: '#fff',
+    thumbIcon: 'icon-icon_confirm_b',
+    trackBackground: 'var(--color-success)'
+  },
+  error: {
+    thumbColor: '#fff',
+    thumbIcon: 'icon-icon_reject_b',
+    trackBackground: '#c7353f'
+  }
+}
+
+const addSliderBtnNode = (state: string) => {
+  const parentNode = document.querySelector('.range-btn')
+  if (state === 'start') {
+    parentNode.innerHTML = `
+      <span style="color: ${styleMap[state].thumbColor}" class="font_family ${styleMap[state].thumbIcon}"></span>
+  `
+  } else {
+    parentNode.innerHTML = `
+    <div class="icon-border" style="background: ${styleMap[state].trackBackground}">
+      <span style="color: ${styleMap[state].thumbColor}" class="font_family ${styleMap[state].thumbIcon}"></span>
+    </div>
+  `
+  }
+}
+
+const isShow = ref(false)
+
+// 根据不同的状态,设置已滑动区域的背景颜色
+const updateSliderBackground = (state: string) => {
+  const targetNode: any = document.querySelector('.range-slider')
+  console.log(targetNode, styleMap[state].trackBackground)
+  targetNode.style.background = styleMap[state].trackBackground
+}
+
+const emit = defineEmits<{
+  close: []
+}>()
+// 监听验证成功事件,因为这里库中的成功事件有0.8秒的延迟,所以这里手动监听验证成功事件
+const onSuccess = () => {
+  // 选择要监听的目标元素
+  const targetNode = document.querySelector('.info-box_') // 将此选择器替换为你的实际目标
+  if (targetNode) {
+    const observer = new MutationObserver((mutationsList) => {
+      mutationsList.forEach((mutation) => {
+        if (mutation.type === 'childList') {
+          if ((mutation.target as any).innerHTML === 'Verification successful') {
+            updateSliderBackground('success')
+            addSliderBtnNode('success')
+            setTimeout(() => {
+              emit('close')
+              isShow.value = false
+            }, 500)
+          }
+        }
+      })
+    })
+
+    // 配置 MutationObserver,监听子节点的变化
+    const config = { childList: true, subtree: true }
+
+    // 启动监听
+    observer.observe(targetNode, config)
+  }
+}
+const close = () => {
+  isShow.value = true
+}
+const fail = () => {
+  updateSliderBackground('error')
+  addSliderBtnNode('error')
+  setTimeout(() => {
+    updateSliderBackground('start')
+    addSliderBtnNode('start')
+  }, 800)
+}
+
+defineExpose({
+  openDialog
+})
+</script>
+<template>
+  <Vcode
+    :show="isShow"
+    @close="close"
+    @fail="fail"
+    :canvasWidth="320"
+    :canvasHeight="180"
+    sliderText="Swipe to verify"
+    :sliderSize="38"
+    successText="Verification successful"
+    failText="Verification failed"
+    :range="5"
+    :imgs="[Img01, Img02, Img03, Img04]"
+  ></Vcode>
+</template>
+<style lang="scss" scoped>
+.slider-verification {
+  width: 400px;
+  height: 373px;
+  padding: 40px;
+  .tips {
+    text-align: center;
+  }
+}
+</style>
+<style lang="scss">
+// 整体框架
+.vue-auth-box_ {
+  width: 400px;
+  height: 373px;
+  padding: 40px;
+  .tips {
+    margin-bottom: 16px;
+    text-align: center;
+  }
+  .icon-border {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: 16px !important;
+    height: 16px !important;
+    border-radius: 50%;
+    border: none !important;
+    span {
+      font-size: 14px !important;
+    }
+  }
+}
+
+div.vue-puzzle-vcode {
+  background-color: rgba(43, 47, 54, 0.7);
+}
+
+.vue-auth-box_ .auth-control_ div.range-box {
+  background-color: #87909e;
+  box-shadow: none;
+}
+.vue-auth-box_ div.auth-body_ {
+  border-radius: 6px;
+}
+// 已滑动区域的样式
+.vue-auth-box_ .auth-control_ .range-box div.range-slider {
+  background-color: var(--color-success);
+}
+
+.vue-auth-box_ .auth-body_ div.loading-box_.hide_ {
+  display: none;
+}
+
+// 成功的提示
+.vue-auth-box_ .auth-body_ div.info-box_ {
+  background: var(--color-success);
+}
+// 失败的提示
+.vue-auth-box_ .auth-body_ div.info-box_.fail {
+  background: #c7353f;
+}
+
+.vue-auth-box_ .auth-control_ .range-box .range-slider {
+  border-radius: 6px;
+}
+
+.vue-auth-box_ .auth-control_ div.range-box {
+  border-radius: 6px;
+}
+// 滑块
+.vue-auth-box_ .auth-control_ .range-box .range-slider .range-btn {
+  border-radius: 6px;
+}
+
+.vue-auth-box_ .auth-body_ .reset_ {
+  width: 20px;
+  height: 20px;
+  top: 10px;
+  right: 10px;
+}
+
+.vue-auth-box_ .auth-control_ .range-box .range-text {
+  color: #fff;
+}
+</style>

BIN
src/components/VSliderVerification/src/image/icon_refresh_bold_b.png


BIN
src/components/VSliderVerification/src/image/verification-img-1.png


BIN
src/components/VSliderVerification/src/image/verification-img-2.png


+ 2 - 15
src/stores/modules/breadCrumb.ts

@@ -18,22 +18,10 @@ export const useBreadCrumb = defineStore('breadCrumb', {
   getters: {},
   actions: {
     setRouteList(toRoute: any) {
-      if (toRoute.name === 'Reset Password') {
-        this.routeList = [
-          {
-            label: toRoute.name,
-            path: toRoute.path,
-            query: toRoute.query
-          }
-        ]
-      }
-
       const index = this.routeList.findIndex((item) => item.label === toRoute.name)
       if (index !== -1) {
         this.routeList.splice(index + 1)
-        return
-      }
-      if (toRoute.name === 'Public Tracking Detail') {
+      } else if (toRoute.name === 'Public Tracking Detail') {
         this.routeList = [
           {
             label: 'Public Tracking',
@@ -46,8 +34,7 @@ export const useBreadCrumb = defineStore('breadCrumb', {
             query: toRoute.query
           }
         ]
-      }
-      if (toRoute.name && whiteList.includes(toRoute.name)) {
+      } else if (toRoute.name && whiteList.includes(toRoute.name)) {
         this.routeList.push({
           label: toRoute.name,
           path: toRoute.path,

+ 19 - 9
src/views/Tracking/src/components/PublicTracking/src/PublicTrackingSearch.vue

@@ -57,7 +57,11 @@ const handleSearchNo = () => {
       } else if (res.code === 400) {
         const { data } = res
         if (data.msg === 'visit limit') {
-          slideVerifyRef.value?.openDialog()
+          console.log('visit limit', sliderVerificationRef.value)
+          isShowSliderVerification.value = true
+          nextTick(() => {
+            sliderVerificationRef.value?.openDialog()
+          })
         }
       }
     })
@@ -67,7 +71,14 @@ const handleSearchNo = () => {
     })
 }
 
-const slideVerifyRef = ref<InstanceType<typeof SlideVerify> | null>(null)
+const sliderVerificationRef = ref()
+const isShowSliderVerification = ref(false)
+const confirmVerification = () => {
+  const pwd = dayjs().unix()
+  confirmVerifyStatus.value = encryptPassword(pwd)
+  handleSearchNo()
+}
+
 const secretKey = 'fT5!R1k$7Mv@4Q9X'
 const fixedIv = '1234567890123456' // 固定的初始化向量,长度为16字符
 
@@ -78,12 +89,6 @@ const encryptPassword = (password) => {
   const encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(password), key, { iv: iv })
   return encrypted.toString()
 }
-
-// 验证滑块成功
-const confirmVerify = () => {
-  const pwd = dayjs().unix()
-  confirmVerifyStatus.value = encryptPassword(pwd)
-}
 </script>
 
 <template>
@@ -126,7 +131,12 @@ const confirmVerify = () => {
         </VEmpty>
       </div>
     </div>
-    <SlideVerify ref="slideVerifyRef" @verify-success="confirmVerify"></SlideVerify>
+    <!-- <SlideVerify ref="slideVerifyRef" @verify-success="confirmVerify"></SlideVerify> -->
+    <VSliderVerification
+      v-if="isShowSliderVerification"
+      @close="confirmVerification"
+      ref="sliderVerificationRef"
+    ></VSliderVerification>
   </div>
 </template>