cyy 1 неделя назад
Родитель
Сommit
c2d754d99b
3 измененных файлов с 243 добавлено и 8 удалено
  1. 66 0
      package-lock.json
  2. 1 0
      package.json
  3. 176 8
      src/views/platform/my/index.vue

+ 66 - 0
package-lock.json

@@ -1959,6 +1959,16 @@
         "@types/node": "*"
       }
     },
+    "@types/dom-webcodecs": {
+      "version": "0.1.11",
+      "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.11.tgz",
+      "integrity": "sha512-yPEZ3z7EohrmOxbk/QTAa0yonMFkNkjnVXqbGb7D4rMr+F1dGQ8ZUFxXkyLLJuiICPejZ0AZE9Rrk9wUCczx4A=="
+    },
+    "@types/emscripten": {
+      "version": "1.41.5",
+      "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz",
+      "integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q=="
+    },
     "@types/express": {
       "version": "5.0.3",
       "resolved": "https://registry.npmmirror.com/@types/express/-/express-5.0.3.tgz",
@@ -3875,6 +3885,15 @@
       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
       "dev": true
     },
+    "barcode-detector": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/barcode-detector/-/barcode-detector-2.3.1.tgz",
+      "integrity": "sha512-D9KEtrquS1tmBZduxBZl8qublIKnRrFqD8TAHDYcLCyrHQBo+vitIxmjMJ61LvXjXyAMalOlO7q0Oh/9Rl2PbQ==",
+      "requires": {
+        "@types/dom-webcodecs": "0.1.11",
+        "zxing-wasm": "1.3.4"
+      }
+    },
     "base": {
       "version": "0.11.2",
       "resolved": "https://registry.npmmirror.com/base/-/base-0.11.2.tgz",
@@ -4424,6 +4443,11 @@
         "caller-callsite": "^2.0.0"
       }
     },
+    "callforth": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/callforth/-/callforth-0.3.1.tgz",
+      "integrity": "sha512-Q2zPfqnwoKsb1DTVCr4lmhe49wKNBsMmNlbudjleu3/co+Nw1pOqFHYJHrW3VZ253ou9AAr+xauQR0C55NPdzA=="
+    },
     "callsites": {
       "version": "3.1.0",
       "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz",
@@ -15176,6 +15200,14 @@
       "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==",
       "dev": true
     },
+    "rtcpeerconnection-shim": {
+      "version": "1.2.15",
+      "resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz",
+      "integrity": "sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==",
+      "requires": {
+        "sdp": "^2.6.0"
+      }
+    },
     "run-async": {
       "version": "2.4.1",
       "resolved": "https://registry.npmmirror.com/run-async/-/run-async-2.4.1.tgz",
@@ -15411,6 +15443,11 @@
         "ajv-keywords": "^3.1.0"
       }
     },
+    "sdp": {
+      "version": "2.12.0",
+      "resolved": "https://registry.npmjs.org/sdp/-/sdp-2.12.0.tgz",
+      "integrity": "sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw=="
+    },
     "select-hose": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz",
@@ -18113,6 +18150,18 @@
       "resolved": "https://registry.npmmirror.com/vue-qr/-/vue-qr-2.5.0.tgz",
       "integrity": "sha512-lVCEqzZyhrZ49dr6n1C4dMNH/tvKJzRwJhCi9vxWYpiosYTWM0J5m5RsJ745S88XWwgTeOynKMGbyOLG9ZP20Q=="
     },
+    "vue-qrcode-reader": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/vue-qrcode-reader/-/vue-qrcode-reader-3.2.1.tgz",
+      "integrity": "sha512-TaiB5CmcShX41Fh6YfDRQFTOv2nzrg6Gh3tdnYvkLPTH8HPDUtPoRysy3MHvxI5MwlhbhTgfQr7mLd3HCOnItQ==",
+      "requires": {
+        "barcode-detector": "^2.2.4",
+        "callforth": "^0.3.1",
+        "core-js": "^3.6.5",
+        "vue": "^2.6.11",
+        "webrtc-adapter": "7.7.0"
+      }
+    },
     "vue-router": {
       "version": "3.6.5",
       "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-3.6.5.tgz",
@@ -18719,6 +18768,15 @@
         "source-map": "~0.6.1"
       }
     },
+    "webrtc-adapter": {
+      "version": "7.7.0",
+      "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-7.7.0.tgz",
+      "integrity": "sha512-7Bp9OBnx642oJRkom1tNAbeJjUadAq2rh5xLL9YXPw5hVyt2h4hHr5bcoPYDs1stp/mZHSPSQA34YISdnr0DBQ==",
+      "requires": {
+        "rtcpeerconnection-shim": "^1.2.15",
+        "sdp": "^2.12.0"
+      }
+    },
     "websocket-driver": {
       "version": "0.7.4",
       "resolved": "https://registry.npmmirror.com/websocket-driver/-/websocket-driver-0.7.4.tgz",
@@ -19181,6 +19239,14 @@
           "dev": true
         }
       }
+    },
+    "zxing-wasm": {
+      "version": "1.3.4",
+      "resolved": "https://registry.npmjs.org/zxing-wasm/-/zxing-wasm-1.3.4.tgz",
+      "integrity": "sha512-9l0QymyATF19FmI92QHe7Dayb+BUN7P7zFAt5iDgTnUf0dFWokz6GVA/W9EepjW5q8s3e89fIE/7uxpX27yqEQ==",
+      "requires": {
+        "@types/emscripten": "^1.39.13"
+      }
     }
   }
 }

+ 1 - 0
package.json

@@ -48,6 +48,7 @@
     "vue-html5-editor": "^1.1.1",
     "vue-i18n": "^8.21.0",
     "vue-qr": "^2.2.1",
+    "vue-qrcode-reader": "^3.2.1",
     "vue-router": "^3.4.3",
     "vue-video-player": "^5.0.2",
     "vuex": "^3.5.1",

+ 176 - 8
src/views/platform/my/index.vue

@@ -70,6 +70,20 @@
       <switch-environment v-if="$nodeEnv === 'development'" />
     </van-cell-group>
 
+    <!-- 扫一扫 -->
+    <van-cell-group>
+      <van-row
+        type="flex"
+        justify="center"
+        class="ibps-cell-wrapper scan-btn-wrapper"
+      >
+        <van-col span="24">
+          <van-button type="default" block :size="size" @click="scanCode">
+            扫一扫
+          </van-button>
+        </van-col>
+      </van-row>
+    </van-cell-group>
     <!-- 退出登录 -->
     <van-cell-group>
       <van-row type="flex" justify="center" class="ibps-cell-wrapper">
@@ -80,6 +94,27 @@
         </van-col>
       </van-row>
     </van-cell-group>
+    <!-- 扫一扫 -->
+    <van-popup
+      v-model="showScanPopup"
+      class="scan-popup-wrapper"
+      :style="scanPopupStyle"
+    >
+      <div class="scan-popup">
+        <!-- <van-nav-bar title="扫一扫" @click-left="closeScanPopup">
+          <template #left>
+            <van-icon name="cross" class="ibps-nav-bar__close-icon" />
+          </template>
+        </van-nav-bar> -->
+        <div class="scan-popup__reader">
+          <qrcode-stream
+            v-if="showScanPopup"
+            @decode="onScanDecode"
+            @init="onScanInit"
+          />
+        </div>
+      </div>
+    </van-popup>
     <!-- 更换主部门 -->
     <van-action-sheet
       v-model="show"
@@ -93,6 +128,7 @@
 </template>
 <script>
 import { mapState } from 'vuex'
+import { QrcodeStream } from 'vue-qrcode-reader'
 import defaultImage from '@/assets/images/logo/lc.png'
 import { getFile } from '@/utils/image'
 // import LcCard from '@/components/LcCard'
@@ -103,7 +139,8 @@ import navbar from '@/mixins/navbar'
 export default {
   components: {
     LangSelect,
-    SwitchEnvironment
+    SwitchEnvironment,
+    QrcodeStream
   },
   mixins: [navbar],
   data() {
@@ -130,7 +167,13 @@ export default {
       actions: [],
       mainPositionName: '',
       mainPosition:
-        this.info && this.info.mainPosition ? this.info.mainPosition : null
+        this.info && this.info.mainPosition ? this.info.mainPosition : null,
+      showScanPopup: false,
+      scanProcessing: false,
+      scanPopupStyle: {
+        width: '100%',
+        height: '100%'
+      }
     }
   },
   computed: {
@@ -171,17 +214,17 @@ export default {
       this.mainPositionName = mid
         ? mid.name
         : this.info && this.info.mainPosition
-          ? this.info.mainPosition.name
-          : positions.length > 0
-            ? positions[0].name
-            : null
+        ? this.info.mainPosition.name
+        : positions.length > 0
+        ? positions[0].name
+        : null
       this.mainPosition =
         mid ||
         (this.info && this.info.mainPosition
           ? this.info.mainPosition
           : positions.length > 0
-            ? positions[0]
-            : null)
+          ? positions[0]
+          : null)
 
       return pos.join(',')
     }
@@ -190,6 +233,110 @@ export default {
     userInfo() {
       this.$router.push({ name: 'userInfo' })
     },
+    scanCode() {
+      this.scanProcessing = false
+      this.showScanPopup = true
+    },
+    closeScanPopup() {
+      this.showScanPopup = false
+    },
+    onScanInit(promise) {
+      promise.catch(() => {
+        this.$toast('无法访问摄像头,请检查权限')
+        this.showScanPopup = false
+      })
+    },
+    onScanDecode(content) {
+      if (this.scanProcessing || this.$utils.isEmpty(content)) {
+        return
+      }
+      this.scanProcessing = true
+      this.showScanPopup = false
+      this.navigateByScanContent(content.trim())
+    },
+    navigateByScanContent(content) {
+      const isFullUrl = /^https?:\/\//i.test(content)
+      const isInnerPath = content.indexOf('/') === 0
+
+      if (!isFullUrl && !isInnerPath) {
+        this.$toast('无法识别的二维码内容')
+        this.scanProcessing = false
+        return
+      }
+
+      if (isFullUrl) {
+        try {
+          const url = new URL(content)
+          if (url.origin === window.location.origin) {
+            const route = this.parseScanRoute(url)
+            if (route) {
+              this.$router.push(route)
+            } else {
+              window.location.href = content
+            }
+            return
+          }
+          window.location.href = content
+        } catch (error) {
+          this.$toast('二维码链接无效')
+          this.scanProcessing = false
+        }
+        return
+      }
+
+      this.$router.push(this.parsePathToRoute(content))
+    },
+    parseScanRoute(url) {
+      let pathWithQuery = ''
+
+      // hash 路由模式:http://domain.com/#/bpmn/siginin/index?codeId=xxx
+      if (url.hash && url.hash.indexOf('#/') === 0) {
+        pathWithQuery = url.hash.slice(1)
+      } else {
+        const publicPath = process.env.VUE_APP_PUBLIC_PATH || '/'
+        let path = url.pathname
+        if (publicPath !== '/' && path.indexOf(publicPath) === 0) {
+          path = '/' + path.slice(publicPath.length).replace(/^\//, '')
+        }
+        pathWithQuery = path + url.search
+      }
+
+      if (!pathWithQuery || pathWithQuery === '/') {
+        return null
+      }
+
+      return this.parsePathToRoute(pathWithQuery)
+    },
+    parsePathToRoute(pathContent) {
+      const queryIndex = pathContent.indexOf('?')
+      if (queryIndex > -1) {
+        return {
+          path: pathContent.slice(0, queryIndex),
+          query: this.parseQueryString(pathContent.slice(queryIndex))
+        }
+      }
+      return { path: pathContent }
+    },
+    parseQueryString(search) {
+      const query = {}
+      const str = search.charAt(0) === '?' ? search.slice(1) : search
+      if (!str) {
+        return query
+      }
+      str.split('&').forEach((item) => {
+        if (!item) {
+          return
+        }
+        const eqIndex = item.indexOf('=')
+        if (eqIndex > -1) {
+          query[decodeURIComponent(item.slice(0, eqIndex))] =
+            decodeURIComponent(item.slice(eqIndex + 1))
+        } else {
+          query[decodeURIComponent(item)] = ''
+        }
+      })
+      return query
+    },
     logout() {
       this.$dialog
         .confirm({
@@ -280,4 +427,25 @@ export default {
   display: flex;
   align-items: center;
 }
+
+.scan-btn-wrapper {
+  padding-bottom: 0;
+}
+
+.scan-popup-wrapper {
+  background: #000;
+}
+
+.scan-popup {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  height: 100%;
+  background: #000;
+}
+
+.scan-popup__reader {
+  flex: 1;
+  overflow: hidden;
+}
 </style>