From 4e6f18dfa08e2f2f4f02aaa1b8e8e51852b7a9a1 Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期五, 13 三月 2026 17:46:03 +0800
Subject: [PATCH] 考点核验

---
 src/views/main/components/Signature.vue          |   29 
 src/views/h5/signup/BaiduMap.vue                 |    3 
 package-lock.json                                |   82 +++
 src/utils/UA.js                                  |   57 ++
 src/utils/tool.js                                |   15 
 src/assets/images/h5/face_tip_1.png              |    0 
 src/utils/wxjssdk.js                             |  334 ++++++++++++
 src/stores/login.js                              |    8 
 src/views/h5/verify/form.vue                     |  154 ++++-
 src/views/h5/verify/index.vue                    |   35 
 src/views/h5/signup/index.vue                    |   80 ++
 src/assets/images/h5/face_tip_4.png              |    0 
 src/views/h5/faceAuth/index.vue                  |  199 +++++++
 src/utils/axios.js                               |    6 
 vite.config.js                                   |    1 
 src/router/h5/router.js                          |   58 +
 src/views/h5/faceAuth/components/camera.vue      |  265 ++++++++++
 src/assets/images/h5/face_tip_3.png              |    0 
 src/views/h5/faceAuth/components/auditDialog.vue |  156 ++++++
 src/assets/images/h5/face_success.png            |    0 
 src/views/h5/login/index.vue                     |   18 
 src/assets/images/map/position-marker-green.png  |    0 
 src/router/index.js                              |    5 
 src/views/h5/index.vue                           |   35 +
 src/assets/images/h5/face_tip_2.png              |    0 
 src/main.js                                      |   10 
 src/assets/images/map/position-marker-red.png    |    0 
 package.json                                     |    4 
 src/views/main/components/UploadBtn.vue          |    4 
 src/config/qxueyou.js                            |    2 
 src/assets/images/h5/face_default.png            |    0 
 src/assets/images/h5/face_fail.png               |    0 
 32 files changed, 1,439 insertions(+), 121 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 546230b..106b5fd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,8 +16,10 @@
         "file-saver": "^2.0.5",
         "pdfh5": "^3.0.0",
         "pinia": "^3.0.3",
+        "pinia-plugin-persistedstate": "^4.7.1",
         "qs": "^6.14.1",
         "sass-embedded": "^1.98.0",
+        "vconsole": "^3.15.1",
         "vue": "^3.5.22",
         "vue-router": "^4.6.3",
         "xlsx": "^0.18.5",
@@ -473,6 +475,15 @@
       },
       "peerDependencies": {
         "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/runtime": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.6.tgz",
+      "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/template": {
@@ -2779,6 +2790,29 @@
         "url": "https://github.com/sponsors/mesqueeb"
       }
     },
+    "node_modules/copy-text-to-clipboard": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmmirror.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.2.tgz",
+      "integrity": "sha512-T6SqyLd1iLuqPA90J5N4cTalrtovCySh58iiZDGJ6FGznbclKh4UI+FGacQSgFzwKG77W7XT5gwbVEbd9cIH1A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/core-js": {
+      "version": "3.48.0",
+      "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.48.0.tgz",
+      "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/core-js"
+      }
+    },
     "node_modules/crc-32": {
       "version": "1.2.2",
       "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz",
@@ -2898,6 +2932,12 @@
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
       }
+    },
+    "node_modules/defu": {
+      "version": "6.1.4",
+      "resolved": "https://registry.npmmirror.com/defu/-/defu-6.1.4.tgz",
+      "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
+      "license": "MIT"
     },
     "node_modules/delayed-stream": {
       "version": "1.0.0",
@@ -4051,6 +4091,11 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/mutation-observer": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/mutation-observer/-/mutation-observer-1.0.3.tgz",
+      "integrity": "sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA=="
+    },
     "node_modules/nanoid": {
       "version": "3.3.11",
       "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
@@ -4374,6 +4419,31 @@
       },
       "peerDependenciesMeta": {
         "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/pinia-plugin-persistedstate": {
+      "version": "4.7.1",
+      "resolved": "https://registry.npmmirror.com/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.7.1.tgz",
+      "integrity": "sha512-WHOqh2esDlR3eAaknPbqXrkkj0D24h8shrDPqysgCFR6ghqP/fpFfJmMPJp0gETHsvrh9YNNg6dQfo2OEtDnIQ==",
+      "license": "MIT",
+      "dependencies": {
+        "defu": "^6.1.4"
+      },
+      "peerDependencies": {
+        "@nuxt/kit": ">=3.0.0",
+        "@pinia/nuxt": ">=0.10.0",
+        "pinia": ">=3.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@nuxt/kit": {
+          "optional": true
+        },
+        "@pinia/nuxt": {
+          "optional": true
+        },
+        "pinia": {
           "optional": true
         }
       }
@@ -5344,6 +5414,18 @@
       "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==",
       "license": "MIT"
     },
+    "node_modules/vconsole": {
+      "version": "3.15.1",
+      "resolved": "https://registry.npmmirror.com/vconsole/-/vconsole-3.15.1.tgz",
+      "integrity": "sha512-KH8XLdrq9T5YHJO/ixrjivHfmF2PC2CdVoK6RWZB4yftMykYIaXY1mxZYAic70vADM54kpMQF+dYmvl5NRNy1g==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.17.2",
+        "copy-text-to-clipboard": "^3.0.1",
+        "core-js": "^3.11.0",
+        "mutation-observer": "^1.0.3"
+      }
+    },
     "node_modules/vite": {
       "version": "7.3.1",
       "resolved": "https://registry.npmmirror.com/vite/-/vite-7.3.1.tgz",
diff --git a/package.json b/package.json
index 792cc97..757c2a8 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
 {
-  "name": "app-web-examination-platform",
+  "name": "app-web-examination-user",
   "version": "0.0.0",
   "private": true,
   "type": "module",
@@ -22,8 +22,10 @@
     "file-saver": "^2.0.5",
     "pdfh5": "^3.0.0",
     "pinia": "^3.0.3",
+    "pinia-plugin-persistedstate": "^4.7.1",
     "qs": "^6.14.1",
     "sass-embedded": "^1.98.0",
+    "vconsole": "^3.15.1",
     "vue": "^3.5.22",
     "vue-router": "^4.6.3",
     "xlsx": "^0.18.5",
diff --git a/src/assets/images/h5/face_default.png b/src/assets/images/h5/face_default.png
new file mode 100644
index 0000000..95ddd7a
--- /dev/null
+++ b/src/assets/images/h5/face_default.png
Binary files differ
diff --git a/src/assets/images/h5/face_fail.png b/src/assets/images/h5/face_fail.png
new file mode 100644
index 0000000..8803597
--- /dev/null
+++ b/src/assets/images/h5/face_fail.png
Binary files differ
diff --git a/src/assets/images/h5/face_success.png b/src/assets/images/h5/face_success.png
new file mode 100644
index 0000000..227529f
--- /dev/null
+++ b/src/assets/images/h5/face_success.png
Binary files differ
diff --git a/src/assets/images/h5/face_tip_1.png b/src/assets/images/h5/face_tip_1.png
new file mode 100644
index 0000000..600ca60
--- /dev/null
+++ b/src/assets/images/h5/face_tip_1.png
Binary files differ
diff --git a/src/assets/images/h5/face_tip_2.png b/src/assets/images/h5/face_tip_2.png
new file mode 100644
index 0000000..5f057af
--- /dev/null
+++ b/src/assets/images/h5/face_tip_2.png
Binary files differ
diff --git a/src/assets/images/h5/face_tip_3.png b/src/assets/images/h5/face_tip_3.png
new file mode 100644
index 0000000..a0b2385
--- /dev/null
+++ b/src/assets/images/h5/face_tip_3.png
Binary files differ
diff --git a/src/assets/images/h5/face_tip_4.png b/src/assets/images/h5/face_tip_4.png
new file mode 100644
index 0000000..a673e2d
--- /dev/null
+++ b/src/assets/images/h5/face_tip_4.png
Binary files differ
diff --git a/src/assets/images/map/position-marker-green.png b/src/assets/images/map/position-marker-green.png
new file mode 100644
index 0000000..f1587ef
--- /dev/null
+++ b/src/assets/images/map/position-marker-green.png
Binary files differ
diff --git a/src/assets/images/map/position-marker-red.png b/src/assets/images/map/position-marker-red.png
new file mode 100644
index 0000000..0b5a042
--- /dev/null
+++ b/src/assets/images/map/position-marker-red.png
Binary files differ
diff --git a/src/config/qxueyou.js b/src/config/qxueyou.js
index e57c416..2aef9f1 100644
--- a/src/config/qxueyou.js
+++ b/src/config/qxueyou.js
@@ -9,7 +9,7 @@
   baseUrl: serverContext,
   htmlRoot: baseDomain + htmlContext,
   serverRoot: baseDomain + serverContext,
-  upload: `${serverContext}/base/file/upload`,
+  upload: `${serverContext}/infra/file/exam/upload`,
   ACCESS_TOKEN_KEY: 'qxy-user-accessToken',
   REFRESH_TOKEN_KEY: 'qxy-user-refreshToken'
 }
\ No newline at end of file
diff --git a/src/main.js b/src/main.js
index 5707d68..13eec40 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,5 +1,6 @@
 import { createApp } from 'vue'
 import { createPinia } from 'pinia'
+import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
 import ElementPlus from 'element-plus'
 import 'element-plus/dist/index.css'
 import './assets/styles/global.css'
@@ -27,6 +28,11 @@
 for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
   app.component(key, component)
 }
+
+// import('vconsole').then((module) => {
+//   new module.default()
+// })
+
 app.config.globalProperties.$rules = ruleGenerator
 app.config.globalProperties.$property = property
 app.config.globalProperties.$qxueyou = qxueyou
@@ -47,7 +53,9 @@
 app.use(ElementPlus, {
   locale: zhCn
 })
-app.use(createPinia())
+const pinia = createPinia()
+pinia.use(piniaPluginPersistedstate)
+app.use(pinia)
 app.use(router)
 
 app.mount('#app')
diff --git a/src/router/h5/router.js b/src/router/h5/router.js
index 80f06e5..c8ebaa8 100644
--- a/src/router/h5/router.js
+++ b/src/router/h5/router.js
@@ -1,28 +1,40 @@
 const router = [
   {
-    path: '/h5/verify',
-    name: '鑰冪偣鏍搁獙',
-    component: () => import('@/views/h5/verify/index.vue'),
-  },
-  {
-    path: '/h5/verForm/:id',
-    name: '鎻愪氦鑰冪偣鏍搁獙',
-    component: () => import('@/views/h5/verify/form.vue'),
-  },
-  {
-    path: '/h5/noVerAccess',
-    name: '鏍搁獙鏃犳潈闄�',
-    component: () => import('@/views/h5/verify/noAccess.vue'),
-  },
-  {
-    path: '/h5/login',
-    name: '韬唤楠岃瘉鐧诲綍',
-    component: () => import('@/views/h5/login/index.vue'),
-  },
-  {
-    path: '/h5/signup',
-    name: '绛惧埌',
-    component: () => import('@/views/h5/signup/index.vue'),
+    path: '/h5',
+    name: 'h5椤甸潰',
+    component: () => import('@/views/h5/index.vue'),
+    children: [
+      {
+        path: 'verify',
+        name: '鑰冪偣鏍搁獙',
+        component: () => import('@/views/h5/verify/index.vue'),
+      },
+      {
+        path: 'verForm',
+        name: '鎻愪氦鑰冪偣鏍搁獙',
+        component: () => import('@/views/h5/verify/form.vue'),
+      },
+      {
+        path: 'noVerAccess',
+        name: '鏍搁獙鏃犳潈闄�',
+        component: () => import('@/views/h5/verify/noAccess.vue'),
+      },
+      {
+        path: 'login',
+        name: '韬唤楠岃瘉鐧诲綍',
+        component: () => import('@/views/h5/login/index.vue'),
+      },
+      {
+        path: 'signup',
+        name: '绛惧埌',
+        component: () => import('@/views/h5/signup/index.vue'),
+      },
+      {
+        path: 'face',
+        name: '浜鸿劯楠岃瘉',
+        component: () => import('@/views/h5/faceAuth/index.vue'),
+      },
+    ],
   },
 ]
 export default router
diff --git a/src/router/index.js b/src/router/index.js
index 899675b..3b466f4 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -3,6 +3,7 @@
 import errorPage from '@/router/error/index.js'
 import mainPage from '@/router/main/index.js'
 import h5 from '@/router/h5/router.js'
+import { useLoginStore } from '@/stores/login.js'
 
 const router = createRouter({
   history: createWebHistory(import.meta.env.BASE_URL),
@@ -10,6 +11,7 @@
 })
 
 router.beforeEach((to, from, next) => {
+  const { setLastRouteInfo } = useLoginStore()
   if (!to.matched.length) {
     if (to.path === '/') {
       next({ path: '/main/home' })
@@ -17,6 +19,9 @@
       next({ path: '/error/404', query: { errorUrl: to.path } })
     }
   } else {
+    if (from.name) {
+      setLastRouteInfo(from)
+    }
     next()
   }
 })
diff --git a/src/stores/login.js b/src/stores/login.js
index 362488f..b6c6cd3 100644
--- a/src/stores/login.js
+++ b/src/stores/login.js
@@ -2,10 +2,14 @@
 import { defineStore } from 'pinia'
 export const useLoginStore = defineStore('login', () => {
   const loginDialogVisible = ref(false)
+  const lastRouteInfo = ref({})
 
   function setLoginDialogVisible(visible) {
     loginDialogVisible.value = visible
   }
+  function setLastRouteInfo(info) {
+    lastRouteInfo.value = info
+  }
 
-  return { loginDialogVisible, setLoginDialogVisible }
-})
+  return { loginDialogVisible, setLoginDialogVisible, lastRouteInfo, setLastRouteInfo }
+}, { persist: true })
diff --git a/src/utils/UA.js b/src/utils/UA.js
new file mode 100644
index 0000000..d27de0f
--- /dev/null
+++ b/src/utils/UA.js
@@ -0,0 +1,57 @@
+const ua = window.navigator.userAgent
+const mobileAgents = ['Android', 'iPhone', 'qxyiOSApp', 'SymbianOS', 'Windows Phone', 'iPad', 'iPod', 'OpenHarmony']
+
+const isWeixin = ua.match(/MicroMessenger/i) == 'MicroMessenger'
+const isWeixinWork = ua.match(/wxwork/i) == 'wxwork'
+const isTBSX5 = ua.match(/MQQBrowser/i) == 'MQQBrowser' || ua.match(/TBS/i) == 'TBS'
+
+const isHarmony = ua.indexOf('OpenHarmony') > -1
+const isHarmonyApp = ua.indexOf('qxyHarmony') > -1
+const isAndroid = ua.indexOf('Android') > -1 || ua.indexOf('Adr') > -1
+const isAndroidApp = ua.indexOf('qxyAndroidApp') > -1
+const isiOSApp = ua.indexOf('qxyiOSApp') > -1
+const isiOS = !!ua.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/) || isiOSApp
+const isApp = isAndroidApp || isiOSApp
+const isOldVerApp = (isApp) && ua.indexOf('qxyEcpt') < 0
+
+let isMobile = false
+for (let v = 0; v < mobileAgents.length; v++) {
+  if (ua.includes(mobileAgents[v])) {
+    isMobile = true
+    break
+  }
+}
+
+let iosInputBlur = function () { // 鍏煎ios杈撳叆妗�
+  if(isiOS) { // 鍒ゆ柇鏄惁涓篒OS绯荤粺
+    setTimeout(() => {
+      const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop || 0
+      window.scrollTo(0, Math.max(scrollHeight - 1, 0))
+    }, 100)
+  }
+}
+
+let wxWorkIosScrollToTop = function() {
+  if (isWeixinWork && isiOS) {
+    setTimeout(() => {
+      window.scrollTo(1, 0)
+    }, 1000)
+  }
+}
+
+export {
+  isWeixin,
+  isWeixinWork,
+  isMobile,
+  isTBSX5,
+  isiOS,
+  isiOSApp,
+  isAndroid,
+  isAndroidApp,
+  isApp,
+  isOldVerApp,
+  iosInputBlur,
+  wxWorkIosScrollToTop,
+  isHarmony,
+  isHarmonyApp
+}
\ No newline at end of file
diff --git a/src/utils/axios.js b/src/utils/axios.js
index 293396f..1341863 100644
--- a/src/utils/axios.js
+++ b/src/utils/axios.js
@@ -138,11 +138,17 @@
           });
           refreshQueue = [];
           tokenUtils.clearTokens();
+          if (router.currentRoute._value.path.includes('/h5/')) {
+            router.push({ path: '/h5/login', query: router.currentRoute._value.query })
+          }
         } finally {
           isRefreshing = false;
         }
       } else {
         tokenUtils.clearTokens();
+        if (router.currentRoute._value.path.includes('/h5/')) {
+          router.push({ path: '/h5/login', query: router.currentRoute._value.query })
+        }
       }
     }
     
diff --git a/src/utils/tool.js b/src/utils/tool.js
index 02f842c..6b559d7 100644
--- a/src/utils/tool.js
+++ b/src/utils/tool.js
@@ -1,5 +1,7 @@
 import { useOptionItemsStore } from '@/stores/optionItems.js';
 import $qxueyou from '@/config/qxueyou.js'
+import { tokenUtils } from '@/utils/axios.js'
+import $axios from '@/utils/axios.js'
 /**
  * 鑾峰彇 assets/images 鐩綍涓嬬殑鍥剧墖URL
  * @param {string} imageName - 鍥剧墖鏂囦欢鍚嶏紙鍖呭惈鎵╁睍鍚嶏級
@@ -159,20 +161,25 @@
 
 let uploadRequest = function(blob, fileName, fileType){
   return new Promise((resolve) => {
+    const file = new File([blob], fileName, {
+      type: blob.type || 'application/octet-stream',
+      lastModified: Date.now()
+    });
     let fd = new FormData()
     let xhr = new XMLHttpRequest()
-    fd.append('image', blob, `${fileName}.${fileType}`)
+    fd.append('file', file)
     xhr.open('POST', $qxueyou.upload, true)
+    xhr.setRequestHeader('Authorization', localStorage.getItem($qxueyou.ACCESS_TOKEN_KEY));
     xhr.onreadystatechange = () => {
       if (xhr.readyState === 4 && xhr.status === 200 && xhr.responseText) {
-        let file = JSON.parse(xhr.responseText)[0] // 杩斿洖缁撴灉
-        resolve(file.path)
+        let file = JSON.parse(xhr.responseText) // 杩斿洖缁撴灉
+        resolve(file.data)
       }
     }
     xhr.onerror = (evt) => { // 涓婁紶澶辫触鍥炶皟
       store.commit("snack/error", "涓婁紶澶辫触锛�")
       console.log(JSON.stringify(evt.target))
-      resolve()
+      resolve(false)
     }
     xhr.send(fd);
   })
diff --git a/src/utils/wxjssdk.js b/src/utils/wxjssdk.js
new file mode 100644
index 0000000..083dcda
--- /dev/null
+++ b/src/utils/wxjssdk.js
@@ -0,0 +1,334 @@
+import wx from 'weixin-js-sdk'
+import axios from './axios'
+import store from '../store.js'
+import $qxueyou from '@/config/qxueyou.js'
+import utilsUA from '@/plugins/utilsUA'
+import { getUUID, qxyResImg } from '@/plugins/utils'
+
+let newFeature = false
+let oldShare = ['onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'onMenuShareQZone']
+let newShare = ['updateTimelineShareData', 'updateAppMessageShareData']
+
+let weixinFlag = utilsUA.isWeixin
+let mobileFlag = utilsUA.isMobile
+let channel = weixinFlag && mobileFlag ? 'wx_pub' : 'wx_pub_qr'
+let isWxpub = 'wx_pub'.includes(channel)
+
+/**
+ * 寰俊鑾峰彇绛惧悕
+ * @param {*} toRoute 鐩爣璺敱
+ */
+function getWxSignature(toRoute) { 
+  if (!weixinFlag) { return false }
+
+  axios.get('/wx/js/signature', {
+    params: { url: location.href } 
+  }).then(res => {
+    if (!res || !res.data) {
+      return false
+    }
+    let result = res.data.data || {}
+    wx.config({ // 寰俊閰嶇疆
+      debug: false,
+      appId: result.appId,
+      timestamp: result.timestamp,
+      nonceStr: result.nonceStr,
+      signature: result.signature,
+      jsApiList: [
+        ...(newFeature ? newShare : oldShare),
+        'chooseWXPay',
+        'chooseImage',
+        'getLocalImgData'
+      ],
+      openTagList: ['wx-open-launch-app']
+    })
+    wx.ready(function () {
+      initShareOption(toRoute)
+    })
+    wx.error(function (res) {
+      console.log(res)
+    })
+  })
+}
+
+/**
+ * 鍒濆鍖栧垎浜暟鎹�
+ * @param {*} toRoute 
+ */
+function initShareOption(toRoute) {
+  if (!weixinFlag) { return false }
+
+  let uuid = getUUID()
+  let shareOptions = getShareOptions(uuid,toRoute)
+
+  shareOptions.success = function () {
+    shareSuccess(uuid,toRoute)
+  }
+  shareOptions.cancel = function () {
+    console.log('鍙栨秷鍒嗕韩')
+  }
+  shareOptions.trigger = function () {
+    console.log('鐢ㄦ埛鐐瑰嚮鍙戦�佺粰鏈嬪弸')
+  }
+
+  // 鍚戜笅鍏煎鏃х増鍒嗕韩鎺ュ彛
+  if (!newFeature) {
+    wx.onMenuShareTimeline(shareOptions)
+    wx.onMenuShareAppMessage(shareOptions)
+  } else {
+    wx.updateAppMessageShareData(shareOptions)
+    wx.updateTimelineShareData(shareOptions)
+  }
+}
+
+/**
+ * 鑾峰彇鍒嗕韩鏁版嵁
+ * @param {*} list 
+ */
+function getShareOptions(uuid,toRoute) {
+  let result = {}
+  let customShare = store.state.share.custom
+  if (customShare.title) {
+    result.title = customShare.title
+    result.desc = customShare.desc
+  } else {
+    result.title = toRoute.name
+    result.desc = toRoute.name
+  }
+  if (customShare.imgUrl) {
+    if (customShare.imgUrl.includes('http')) {
+      result.imgUrl = customShare.imgUrl
+    } else {
+      result.imgUrl = qxyResImg(customShare.imgUrl)
+    }
+  }
+
+  if (customShare.uuidLink) {
+    let pageUrl = customShare.pageUrl || toRoute.fullPath
+    let encode = encodeURI(`${uuid},${$qxueyou.htmlRoot + pageUrl}`)
+    result.link = customShare.uuidLink + btoa(encode)
+    // result.link = customShare.uuidLink + uuid
+  } else if (customShare.link) {
+    result.link = customShare.link
+  } else {
+    result.link = location.href
+  }
+
+  return result
+}
+
+/**
+ * 鍒嗕韩鎴愬姛鍥炶皟
+ */
+function shareSuccess(uuid,toRoute) {
+  let customShare = store.state.share.custom
+  if (customShare.targetId) {
+    let pageUrl = customShare.pageUrl || toRoute.fullPath
+    axios.post('/wx/share/callback', {
+      urlId: uuid,
+      pageUrl: $qxueyou.htmlRoot + pageUrl,
+      targetId: customShare.targetId,
+      planIds: customShare.planIds
+    }).then(() => {
+      initShareOption(toRoute)
+    })
+  }
+
+  // 褰撳垎浜湁鏂规Id锛岃Е鍙戞槸鍚﹀叧娉ㄥ叕浼楀彿
+  if (customShare.planIds) {
+    store.commit('wxh5/subscribe', '鍙婃椂鑾峰彇濂栧姳鎻愰啋')
+  }
+
+  let mask = store.state.share.mask
+  if (mask.show) {
+    let text = mask.type === 'plan' ? '鍒嗕韩鎴愬姛锛岀户缁姪鍔�' : '閭�璇锋垚鍔燂紝缁х画閭�璇�'
+    let newMask = { show: true, type: mask.type, text: text, codeText: mask.codeText }
+    store.commit("share/maskText", newMask)
+  }
+}
+
+/**
+ * 缁熶竴鏀粯鎺ュ彛澶勭悊
+ * @param {*} orderId
+ * @param {*} successCallback // 鏀粯鎴愬姛鍥炶皟
+ * @param {*} showCodeCallback // 鏄剧ず浜岀淮鐮佸洖璋�
+ */
+function unipayPay(orderId, successCallback, showCodeCallback) {
+  axios.post('/wx/pay/createOrder', {
+    orderId: orderId,
+    channel: channel,
+    redirectUrl: weixinFlag ? store.state.order.paySuccessUrl : undefined,
+  }).then(res => {
+    if (!res.data.data) { return false }
+    
+    let params = res.data.data.param
+
+    if (isWxpub) { // 鍏紬鍙锋敮浠�
+      chooseWXPay(params, successCallback)
+    } else { // 鎵爜鏀粯
+      store.commit('timer/paying', true)
+      showCodeCallback && showCodeCallback(params.codeUrl)
+      checkIsPay(orderId, successCallback)
+    }
+  })
+}
+
+function chooseWXPay(result, successCallback) {
+  //璋冪敤寰俊鏀粯鎺ュ彛
+  wx.chooseWXPay({
+    timestamp: result.timeStamp, // 鏀粯绛惧悕鏃堕棿鎴筹紝娉ㄦ剰寰俊jssdk涓殑鎵�鏈変娇鐢╰imestamp瀛楁鍧囦负灏忓啓銆備絾鏈�鏂扮増鐨勬敮浠樺悗鍙扮敓鎴愮鍚嶄娇鐢ㄧ殑timeStamp瀛楁鍚嶉渶澶у啓鍏朵腑鐨凷瀛楃
+    nonceStr: result.nonceStr, // 鏀粯绛惧悕闅忔満涓诧紝涓嶉暱浜� 32 浣�
+    package: result.packageValue, // 缁熶竴鏀粯鎺ュ彛杩斿洖鐨刾repay_id鍙傛暟鍊硷紝鎻愪氦鏍煎紡濡傦細prepay_id=\*\*\*锛�
+    signType: result.signType, // 绛惧悕鏂瑰紡锛岄粯璁や负'SHA1'锛屼娇鐢ㄦ柊鐗堟敮浠橀渶浼犲叆'MD5'
+    paySign: result.paySign, // 鏀粯绛惧悕
+    success: () => {
+      successCallback && successCallback()
+    },
+    fail: (e) => {
+      store.commit('snack/error', '璇疯仈绯绘妧鏈鏈嶈В鍐�' + e.errMsg)
+    }
+  })
+}
+
+/**
+ * 鎵爜鏀粯鎴愬姛鐨勫洖璋�
+ * @param {} orderId
+ * @returns {} 
+ */
+function checkIsPay(orderId, successCallback) {
+  if (!store.state.timer.paying) {
+    return false
+  }
+  setTimeout(function () {
+    axios.get('/transact/order/payResult', {
+      params: { orderId: orderId }
+    }).then(res => {
+      if (res.data.success) {
+        successCallback && successCallback()
+        store.commit('timer/paying', false)
+      } else {
+        checkIsPay(orderId, successCallback)
+      }
+    })
+  }, 2000)
+}
+
+/**
+ * 鑾峰彇瀹氫綅
+ * @param {*} locationCallback 
+ */
+function getPosition(locationCallback){
+  wx.getLocation({
+    success: function (res) {
+      locationCallback(res)
+    }
+  })  
+}
+
+function chooseImage(){
+  return new Promise((resolve) => {
+    wx.chooseImage({
+      count: 1,
+      sizeType: ['compressed'],
+      sourceType: ['camera'],
+      success: (res) => {
+        if (res && res.localIds) {
+          getLocalImgData(res.localIds[0]).then((localData) => {
+            resolve(localData)
+          })
+        } else {
+          store.commit('snack/error', `寰俊涓婁紶鍥剧墖澶辫触锛�${res}`)
+        }
+      },
+      fail:function(e) {
+        store.commit('snack/error', `寰俊涓婁紶鍥剧墖寮傚父锛�${JSON.stringify(e)}`)
+      }
+    })
+  })
+}
+function getLocalImgData(localId){
+  return new Promise((resolve) => {
+    wx.getLocalImgData({
+      localId: localId,
+      success: function (res) {
+        resolve(res.localData)
+      },
+      fail:function(e) {
+        store.commit('snack/error', `寰俊鑾峰彇鍥剧墖寮傚父锛�${JSON.stringify(e)}`)
+      }
+    })
+  })
+}
+
+
+/**
+ * 棰勮鍥剧墖
+ * @param {*} url 
+ * @param {*} urlList 
+ */
+function previewImage(current, urls){
+  wx.previewImage({
+    current: current, // 褰撳墠鏄剧ず鍥剧墖鐨刪ttp閾炬帴
+    urls: urls ? urls : [current] // 闇�瑕侀瑙堢殑鍥剧墖http閾炬帴鍒楄〃
+  })
+}
+
+/**
+ * 杩斿洖灏忕▼搴忛〉闈�
+ * @param {*} mpRouter // 灏忕▼搴忕殑璺敱
+ * @param {*} otherCallback // 鍏朵粬鍥炶皟鎿嶄綔
+ */
+function redirectToMp(mpRouter) {
+  return new Promise(resolve => {
+    // 闈炲井淇� 鎴� 浜虹ぞ灞�娲诲姩鍏ュ彛
+    if (!weixinFlag || store.state.course.isRsjActivity) {
+      resolve(true)
+      return false
+    }
+    wx.miniProgram.getEnv(function(res) {
+      if (res.miniprogram) { // 灏忕▼搴�
+        wx.miniProgram.redirectTo({
+          url: mpRouter,
+          success: function(){
+            console.log('璺宠浆鎴愬姛')
+            setTimeout(() => { // 澶勭悊鍏朵粬鏈烘瀯鐨勫皬绋嬪簭璺宠浆鍦烘櫙
+              resolve(true);
+            }, 3000)
+          },
+          fail: function(){
+            resolve(true)
+          }
+        })
+      } else { // 闈炲皬绋嬪簭
+        resolve(true)
+      }
+    })
+  })
+}
+
+/**
+ * 鍚戝皬绋嬪簭鍙戦�佹秷鎭�
+ */
+function postMessage(data) {
+  // 闈炲井淇�
+  if (!weixinFlag) { return false }
+
+  wx.miniProgram.getEnv(function(res) {
+    if (res.miniprogram) { // 灏忕▼搴�
+      wx.miniProgram.postMessage({
+        data: data
+      })
+    }
+  })
+}
+
+export {
+  getWxSignature,
+  initShareOption,
+  unipayPay,
+  getPosition,
+  previewImage,
+  chooseImage,
+  redirectToMp,
+  postMessage
+}
diff --git a/src/views/h5/faceAuth/components/auditDialog.vue b/src/views/h5/faceAuth/components/auditDialog.vue
new file mode 100644
index 0000000..15b15e8
--- /dev/null
+++ b/src/views/h5/faceAuth/components/auditDialog.vue
@@ -0,0 +1,156 @@
+<template>
+  <el-dialog
+    v-model="dialogFlag"
+    width="80%"
+    style="max-width: 500px;"
+    align-center
+    :show-close="status!='auditing'"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+  >
+    <div class="p-7 pt-0" style="display: flex; flex-direction: column; align-items: center;">
+      <div class="image_box" v-if="['unStart', 'auditing'].includes(status)">
+        <el-image :src="base64"></el-image>
+        <div v-if="status=='auditing'" class="scan-line"></div>
+      </div>
+      <el-row justify="center" v-else-if="['success', 'fail'].includes(status)">
+        <el-image :src="$getImageUrl(`/h5/face_${status}.png`)"></el-image>
+        <el-row>
+          <el-text class="text-lg">浜鸿劯楠岃瘉{{ status=='success'?'':'涓�' }}閫氳繃</el-text>
+        </el-row>
+        <el-row class="mt-2">
+          <el-text v-if="status=='success'">绯荤粺宸叉垚鍔熷鏍告偍鐨勮韩浠�</el-text>
+          <el-text v-else-if="status=='fail'">璇烽噸鏂伴獙璇佹偍鐨勮韩浠�</el-text>
+        </el-row>
+      </el-row>
+      <el-row justify="center" class="mt-5" v-if="status=='auditing'">
+        <el-text>姝e湪瀹℃牳涓紝璇疯�愬績绛夊緟...</el-text>
+      </el-row>
+      <el-button
+        v-if="status=='unStart'"
+        @click="submitAudit" 
+        :loading="status=='auditing'" 
+        type="primary"
+        size="large" class="mt-5" 
+        style="width: 100%;"
+      >
+        鎻愪氦瀹℃牳
+      </el-button>
+      <el-button
+        v-else-if="status=='success'"
+        @click="handlerSuccess"
+        type="primary" 
+        size="large" class="mt-5" 
+        style="width: 100%;"
+      >
+        瀹屾垚楠岃瘉
+      </el-button>
+      <el-button
+        v-else-if="status=='fail'"
+        @click="dialogFlag=false"
+        type="primary" 
+        size="large" class="mt-5" 
+        style="width: 100%;"
+      >
+        纭畾
+      </el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { uploadByBase64 } from '@/utils/tool.js';
+
+export default {
+  components: {},
+  data() {
+    return {
+      dialogFlag: false,
+      status: '' 
+    }
+  },
+  props: {
+    modelValue: {
+      type: Boolean,
+      default: false
+    },
+    base64: {
+      type: String,
+      default: ''
+    }
+  },
+  computed: {
+    
+  },
+  created() {
+    
+  },
+  watch: {
+    modelValue(val) {
+      this.dialogFlag = val
+      if (val) {
+        this.status = 'unStart'
+      }
+    },
+    dialogFlag(val) {
+      this.$emit('update:modelValue', val)
+    }
+  },
+  methods: {
+    async submitAudit() {
+      this.status = 'auditing'
+      const url = await uploadByBase64(this.base64 ,'浜鸿劯鐓х墖')
+      if (!url) {
+        this.status = 'fail'
+        return
+      }
+      const params = { faceImgPath: url  }
+      this.$axios.get('/system/auth/staff/checkin/face-match', { params }).then(res => {
+        if (res.data.code == 0) {
+          // this.status = res.data.data ? 'success' : 'fail'
+          this.status = 'success'
+        } else {
+          this.status = 'fail'
+          this.$message.error(res.data.msg || "浜鸿劯姣斿澶辫触")
+        }
+      }).catch(() => {
+        this.status = 'fail'
+      })
+    },
+    handlerSuccess() {
+      this.dialogFlag = false
+      this.$emit('handlerSuccess')
+    }
+  }
+}
+</script>
+<style scoped lang="scss">
+.image_box {
+  position: relative;
+  width: 90%;
+  max-width: 300px;
+  aspect-ratio: 1/1;
+  border-radius: 50% 50%;
+  overflow: hidden;
+  display: flex;
+  justify-content: center;
+  border: 5px solid #5693f4;
+}
+/* 鎵弿绾� */
+.scan-line {
+  position: absolute;
+  left: 0px;
+  right: 0px;
+  height: 50px;
+  background: linear-gradient(0deg, #5693f4, transparent);
+  animation: scan 2s ease-in-out infinite;
+  /* 鍒濆浣嶇疆璁惧湪椤堕儴 */
+  top: -50px;
+}
+
+@keyframes scan {
+  0% { top: -50px; }
+  100% { top: 100%; } /* 绉诲姩鍒板簳閮� */
+}
+
+</style>
\ No newline at end of file
diff --git a/src/views/h5/faceAuth/components/camera.vue b/src/views/h5/faceAuth/components/camera.vue
new file mode 100644
index 0000000..530cbe0
--- /dev/null
+++ b/src/views/h5/faceAuth/components/camera.vue
@@ -0,0 +1,265 @@
+<template>
+  <div ref="cameraBox" class="camera_box" :style="cameraStyle" v-show="isCameraOpening">
+    <video ref="videoEl" :width="videoWidth" :height="videoHeight" autoplay></video>
+    <canvas ref="canvasEl" :width="videoWidth" :height="videoHeight" style="display:none;"></canvas>
+    
+    <el-row justify="center" class="btn_box">
+      <el-button plain text @click="closeCamera()">
+        <Icon icon="material-symbols:cancel" width="28" height="28"  style="color: #FA5252" />
+      </el-button>
+      <el-button plain text @click="startRecordImage()">
+        <Icon icon="ant-design:camera-filled" width="28" height="28"  style="color: #FA5252" />
+      </el-button>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { uploadByBase64 } from '@/utils/tool.js'
+export default {
+  data () {
+    return {
+      recorderOptions: { // recorder 閰嶇疆椤�
+        mimeType: 'video/webm;codecs=vp8,opus'
+      },
+      isCameraOpening: false,
+    }
+  },
+  computed: {
+    videoWidth: function () {
+      return 400
+    },
+    videoHeight: function(){
+      return (this.videoWidth * 3) / 4
+    },
+    cameraStyle: function(){
+      return {
+        width: `${this.videoWidth}px`,
+        height: `${this.videoHeight}px`
+      }
+    },
+  },
+  beforeUnmount(){
+    this.closeCamera()
+  },
+  mounted () {
+    this.initNavigatorMedia()
+    this.openCamera()
+  },
+  methods: {
+    initNavigatorMedia: function(){
+      // 鑾峰彇濯掍綋灞炴�э紝鏃х増鏈祻瑙堝櫒鍙兘涓嶆敮鎸乵ediaDevices锛岃缃竴涓┖瀵硅薄
+      if (navigator.mediaDevices === undefined) {
+        navigator.mediaDevices = {};
+      }
+      // 浣跨敤getUserMedia锛屽洜涓哄畠浼氳鐩栫幇鏈夌殑灞炴�с��
+      // 杩欓噷锛屽鏋滅己灏慻etUserMedia灞炴�э紝灏辨坊鍔犲畠銆�
+      if (navigator.mediaDevices.getUserMedia === undefined) {
+        this.initGetUserMedia()
+      }
+    },
+    initGetUserMedia: function(){
+      navigator.mediaDevices.getUserMedia = (constraints) => {
+        // 棣栧厛鑾峰彇鐜板瓨鐨刧etUserMedia(濡傛灉瀛樺湪)
+        let getUserMedia =
+          navigator.webkitGetUserMedia ||
+          navigator.mozGetUserMedia ||
+          navigator.getUserMedia;
+        // 鏈変簺娴忚鍣ㄤ笉鏀寔锛屼細杩斿洖閿欒淇℃伅
+        // 淇濇寔鎺ュ彛涓�鑷�
+        if (!getUserMedia) {//涓嶅瓨鍦ㄥ垯鎶ラ敊
+          return Promise.reject(
+            new Error("getUserMedia is not implemented in this browser")
+          );
+        }
+        // 鍚﹀垯锛屼娇鐢≒romise灏嗚皟鐢ㄥ寘瑁呭埌鏃х殑navigator.getUserMedia
+        return new Promise((resolve, reject) => {
+          getUserMedia.call(navigator, constraints, resolve, reject);
+        });
+      };
+    },
+    openCamera: function(){ // 鎵撳紑鎽勫儚澶�
+      navigator.mediaDevices.getUserMedia({
+        audio: false,
+        video: {
+          width: this.videoWidth,
+          height: this.videoHeight,
+        }
+      }).then((stream) => {
+        this.isCameraOpening = true
+        this.mediaStream = stream
+        this.initVideoSrcObject()
+      }).catch(err => {
+        console.log(`${err.name}锛�${err.message}`);
+        this.closeCamera()
+        this.mediaErrorHandler(err)
+      });
+    },
+    closeCamera() { // 鍏抽棴鎽勫儚澶�
+      if (!this.isCameraOpening) return false
+
+      this.isCameraOpening = false
+
+      if (this.mediaStream) {
+        let tracks = this.mediaStream.getTracks()
+        tracks.forEach(track => track.stop());
+      }
+
+      if (this.mediaRecorder) this.mediaRecorder.stop();
+      this.$emit('close')
+    },
+    initVideoSrcObject: function(){ // 鍒濆鍖� 瑙嗛褰曞埗 鐨� video srcObject
+      // 鏃х殑娴忚鍣ㄥ彲鑳芥病鏈塻rcObject
+      if ("srcObject" in this.$refs.videoEl) {
+        this.$refs.videoEl.srcObject = this.mediaStream;
+        this.initVideoMoveListener()
+      } else {
+        console.log('娴忚鍣ㄤ笉鏀寔')
+      }
+    },
+    initVideoMoveListener: function(){ // 鍒濆鍖� 瑙嗛绐楀彛 鐨� 绉诲姩浜嬩欢
+      let eventState = {}
+      let startMoving = (e) => { // 寮�濮嬬Щ鍔ㄧ殑鍥炶皟
+        e.preventDefault()
+        e.stopPropagation()
+        eventState = {
+          left: this.$refs.cameraBox.offsetLeft,
+          top: this.$refs.cameraBox.offsetTop,
+          x: e.clientX,
+          y: e.clientY
+        }
+        document.addEventListener('mousemove', moving)
+        document.addEventListener('mouseup', endMoving)
+      }
+      let moving = (e) => { // 绉诲姩鐨勫洖璋�
+        e.preventDefault()
+        e.stopPropagation()
+
+        let margin = 10
+        let left = e.clientX - (eventState.x - eventState.left)
+        let top = e.clientY - (eventState.y - eventState.top)
+        let maxLeft = document.documentElement.clientWidth - this.videoWidth - margin
+        let maxTop = document.documentElement.clientHeight - this.videoHeight - margin
+
+        // 闄愬埗绉诲姩鎴浘鍖哄煙涓嶈兘绉诲埌鍙鍖哄煙澶�
+        if (left < 10) left = margin
+        if (top < 10) top = margin
+        if (left > maxLeft) left = maxLeft
+        if (top > maxTop) top = maxTop
+
+        this.$refs.cameraBox.style.left = `${left}px`
+        this.$refs.cameraBox.style.top = `${top}px`
+      }
+      let endMoving = (e) => { // 缁撴潫绉诲姩鐨勫洖璋�
+        e.preventDefault()
+        document.removeEventListener('mousemove', moving)
+        document.removeEventListener('mouseup', endMoving)
+      }
+      this.$refs.cameraBox.addEventListener('mousedown', startMoving)
+    },
+    initUploadChunk: async function(blobs){ // 鍒濆鍖栦笂浼犱换鍔�
+      let tmpBlob = new Blob(blobs, { 'type': this.recorderOptions.mimeType });
+      // let file = new File(blobs, fileName, { type: 'video/webm' });
+      let file = null
+      try {
+        // 瑙e喅 Webm 瑙嗛 duration 闂
+        file = await fixWebmMetaInfo(tmpBlob)
+      } catch (error) {
+        file = tmpBlob
+        console.log(error)
+      }
+
+      let path = await uploadChunk(file, '瀛︿範瑙嗛', 'webm')
+      if (!path) return false
+
+      if (this.recordingFlag || this.recordCameraFlag) {
+        this.$store.commit('authCamera/recordVideoUrl', path)
+        this.$store.commit('authCamera/faceCaptureAll', { flag: false, imgList: this.faceCaptureAllImgList })
+      } else {
+        this.$store.commit('authCamera/biopsyVideoUrl', path)
+      }
+    },
+    startRecordImage() { // 寮�濮嬪綍鍒跺浘鍍忥紙鎷嶇収锛�
+      if (!this.$refs.canvasEl) return false
+
+      this.drawImage()
+      // this.closeCamera()
+    },
+    drawImage: function(){ // 缁樺埗鍥剧墖
+      this.$refs.canvasEl.getContext('2d').drawImage(
+        this.$refs.videoEl,
+        0,
+        0,
+        this.videoWidth,
+        this.videoHeight
+      );
+      this.uploadBase64()
+    },
+    async uploadBase64(){ // 涓婁紶鍥剧墖
+      let base64 = this.$refs.canvasEl.toDataURL("image/png", 1);
+      // const url = await uploadByBase64(base64, '鏍搁獙鐓х墖')
+      if (base64) {
+        this.$emit('handlerSuccess', base64)
+        this.closeCamera()
+      } else {
+        this.$message.error('鎷嶆憚澶辫触')
+      }
+    },
+    uploadErrorHandler: function (evt) { // 涓婁紶澶辫触鍥炶皟
+      console.log(JSON.stringify(evt.target))
+    },
+    mediaErrorHandler: function(){ // 寮�鍚憚鍍忓ご鎴栧綍鍒跺睆骞曞紓甯稿鐞�
+      this.$message.error('褰撳墠娴忚鍣ㄤ笉鏀寔锛岃鏇存崲娴忚鍣�')
+    },
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.camera_box{
+  position: fixed;
+  bottom: 10px;
+  right: 10px;
+  z-index: 300;
+}
+.status_box{
+  position: absolute;
+  left: 0;
+  right: 0;
+  padding: 6px;
+  margin-top: -38px;
+  font-size: 0.8rem;
+  color: white;
+  background: rgba(0,0,0,0.4);
+  .normal > span,
+  .abnormal > span{
+    width: 6px;
+    height: 6px;
+    border-radius: 28px;
+    display: inline-block;
+    margin-right: 5px;
+  }
+  .normal > span{
+    background-color: #00E63C;
+  }
+  .abnormal > span{
+    background-color: #FF3232;
+  }
+}
+.btn_box{
+  background: rgba(0,0,0,0.4);
+  margin-top: -63px;
+  padding-top: 10px;
+  padding-bottom: 7px;
+  position: absolute;
+  left: 0;
+  right: 0;
+}
+.duration{
+  position: absolute;
+  top: 0;
+  right: 0;
+  color: white;
+  text-shadow: 1px 1px 1px black, 1px -1px 1px black, -1px -1px 1px black, -1px 1px 1px black;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/h5/faceAuth/index.vue b/src/views/h5/faceAuth/index.vue
new file mode 100644
index 0000000..c7635b9
--- /dev/null
+++ b/src/views/h5/faceAuth/index.vue
@@ -0,0 +1,199 @@
+<template>
+  <div class="p-7 py-4 face">
+    <div>
+      <el-row class="px-7" justify="space-between">
+        <el-text class="text-lg">濮撳悕锛�<span class="font-bold">{{ userInfo.name }}</span></el-text>
+        <el-text class="text-lg">韬唤璇佸熬鍙凤細<span class="font-bold">{{ userInfo.idCard?.slice(-4) }}</span></el-text>
+      </el-row>
+      <el-row justify="center">
+        <el-image :src="$getImageUrl(`/h5/face_default.png`)" style="width: 300px;"></el-image>
+      </el-row>
+      <!-- <video id="video" width="400" height="300" autoplay></video> -->
+      <el-row justify="center">
+        <el-text class="text-xl">璇锋媿鎽勭収鐗囧畬鎴愪汉鑴歌璇�</el-text>
+      </el-row>
+      <el-row class="my-7">
+        <el-text class="text-info text-center">
+          鎻愮ず锛氫汉鑴搁獙璇佸皢浼氳繘琛岀郴缁熷鏍革紝涓虹‘淇濇偍鑳藉揩閫熼�氳繃楠岃瘉锛岃鍕胯。鐫�鏆撮湶锛岄厤鍚堢郴缁熸寚绀哄畬鎴愰獙璇併��
+        </el-text>
+      </el-row>
+      <el-row class="pt-7">
+        <el-text class="text-xl">鎷嶆憚椤荤煡</el-text>
+      </el-row>
+      <el-row justify="space-between" class="mt-3">
+        <div v-for="(tip,index) in tipItems" :key="`tip${index}`">
+          <el-image :src="$getImageUrl(`/h5/face_tip_${index+1}.png`)" style="width: 70px;"></el-image>
+        </div>
+      </el-row>
+    </div>
+    <el-row justify="center" class="mb-7">
+      <el-button @click="startCapture" type="primary" style="width: 100%;" size="large">寮�濮嬫媿鎽�</el-button>
+    </el-row>
+
+    <camera 
+      v-if="openCameraFlag"
+      @close="openCameraFlag=false"
+      @handlerSuccess="shootSuccess"
+    ></camera>
+
+    <auditDialog
+      v-model="auditDialogFlag"
+      :base64="base64"
+      @handlerSuccess="auditSuccess"
+    >
+    </auditDialog>
+  </div>
+</template>
+
+<script>
+import camera from '@/views/h5/faceAuth/components/camera.vue';
+import {isWeixin} from '@/utils/UA.js'
+import auditDialog from '@/views/h5/faceAuth/components/auditDialog.vue';
+import { useSessionStore } from '@/stores/session.js'
+import { storeToRefs } from 'pinia';
+export default {
+  components: {
+    camera,
+    auditDialog
+  },
+  setup() {
+    const { userInfo } = storeToRefs(useSessionStore()) 
+    return { userInfo } 
+  },
+  data() {
+    return {
+      tipItems: [
+        { label: '鏍囧噯鎷嶆憚', isCheck: true },
+        { label: '鏍囧噯鎷嶆憚', isCheck: true },
+        { label: '鏍囧噯鎷嶆憚', isCheck: true },
+        { label: '鏍囧噯鎷嶆憚', isCheck: true },
+      ],
+      openCameraFlag: false,
+      base64: '',
+      auditDialogFlag: false
+    }
+  },
+  computed: {
+    getSigninButtonStyle() {
+      if (this.positionStatus == 'success') {
+        return {
+          'background': '#66d06c',
+          'border-color': '#e7f7eb'
+        }
+      } else {
+        return {
+          'background': '#e1e1e1',
+          'border-color': '#f8f8f8'
+        }
+      }
+    }
+  },
+  async mounted() {
+    this.currentTimeText = this.$dayjs().format('HH:mm')
+  },
+  methods: {
+    getUserPositionStatus(evt) {
+      this.userPositionStatus = evt
+    },
+    startCapture() {
+      if (isWeixin) {
+        console.log('')
+      } else {
+        this.openCameraFlag = true
+      }
+    },
+    shootSuccess(evt) {
+      this.base64 = evt
+      if (this.base64) {
+        this.auditDialogFlag = true
+      }
+    },
+    auditSuccess() {
+      localStorage.setItem('isFace', true)
+      if (!this.getIsSignup()) {
+        this.$router.replace({ path: '/h5/signup', query: { appId: this.appId } })
+      } else {
+        this.$router.replace({ path: '/h5/verForm', query: { appId: this.appId }})
+      }
+    },
+    getIsSignup() {
+      return Boolean(localStorage.getItem('isSignup'))
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.face {
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  max-width: 700px;
+  margin: 0 auto;
+  overflow: auto;
+}
+.mapBox {
+  width: 300px;
+  height: 300px;
+  border-radius: 150px;
+}
+.mask {
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%);
+  width: 300px;
+  height: 340px;
+  background: radial-gradient(circle at center, transparent 0px, white 160px);
+  z-index: 12; /* 浣嶄簬绾㈣壊鐩掑瓙涓婃柟 */
+}
+.center-sign {
+  z-index: 13;
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%);
+}
+.text-sign {
+  z-index: 13;
+  position: absolute;
+  left: 50%;
+  top: 66%;
+  white-space: nowrap;
+  transform: translate(-50%, -50%);
+}
+.signin-button {
+  width: 140px;
+  height: 140px;
+  border-radius: 70px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  border: 16px solid 
+}
+.ripple {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  border-radius: 50%;
+  background-color: rgba(255, 255, 255, 0.5);
+  transform: scale(0);
+  animation: ripple 2s ease-out infinite;
+  pointer-events: none; /* 闃叉娉㈢汗閬尅鎸夐挳鐐瑰嚮 */
+  z-index: 14;
+}
+/* 纭繚鎸夐挳鏂囧瓧鏄剧ず鍦ㄦ尝绾逛笂鏂� */
+.signin-button .el-row {
+  position: relative;
+  z-index: 2;
+}
+@keyframes ripple {
+  to {
+    transform: scale(2);
+    opacity: 0;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/views/h5/index.vue b/src/views/h5/index.vue
new file mode 100644
index 0000000..a4c46f9
--- /dev/null
+++ b/src/views/h5/index.vue
@@ -0,0 +1,35 @@
+<template>
+  <div>
+    <router-view></router-view>
+  </div>
+</template>
+<script>
+import { useSessionStore } from '@/stores/session.js'
+export default {
+  setup() {
+    const { setUserInfo } = useSessionStore()
+    return { setUserInfo }
+  },
+  data() {
+    return {}
+  },
+  async created() {
+    await this.getUserInfo()
+  },
+  methods: {
+    getUserInfo() {
+      return new Promise((resolve) => {
+        this.$axios.get('/system/auth/staff/profile').then(res => {
+          if (res.data.code == 0) {
+            this.setUserInfo(res.data.data || {})
+          } else {
+            this.$message.error(res.data.msg || '鑾峰彇鐢ㄦ埛淇℃伅澶辫触')
+          }
+        }).finally(() => {
+          resolve()
+        })
+      })
+    },
+  }
+}
+</script>
\ No newline at end of file
diff --git a/src/views/h5/login/index.vue b/src/views/h5/login/index.vue
index befa9ae..b9cd759 100644
--- a/src/views/h5/login/index.vue
+++ b/src/views/h5/login/index.vue
@@ -1,5 +1,5 @@
 <template>
-  <div class="login">
+  <div class="login" v-if="loginType == 'mobilePhone'">
     <el-form ref="form" :model="form">
       <el-form-item :rules="[$rules.required('璇疯緭鍏ユ墜鏈哄彿') , $rules.phone()]" prop="mobile">
         <el-input v-model="form.mobile" placeholder="璇疯緭鍏ユ墜鏈哄彿" style="width: 100%" size="large" />
@@ -32,9 +32,16 @@
 </template>
 <script>
 import { tokenUtils } from '@/utils/axios.js';
+import { useLoginStore } from '@/stores/login.js'
+import { isWeixin } from '@/utils/UA.js'
 export default {
+  setup() {
+    const { lastRouteInfo } = useLoginStore()
+    return { lastRouteInfo }
+  },
   data() {
     return {
+      loginType: '', //mobile銆亀eixin
       form: {
         mobile: '',
         code: '',
@@ -47,6 +54,8 @@
   },
   created() {
     tokenUtils.clearTokens()
+    this.loginType = isWeixin ? 'weixin' : 'mobilePhone'
+    this.loginType = 'mobile'
   },
   computed: {
     appId() {
@@ -104,9 +113,12 @@
         if (res.data.code == 0) {
           const resData = res.data.data
           tokenUtils.setTokens(resData.accessToken, resData.refreshToken)
-          this.$router.replace({ path: '/h5/verify', query: { appId: this.appId } })
+          this.$message.success('鐧诲綍鎴愬姛')
+          if (this.lastRouteInfo.name) {
+            this.$router.replace(this.lastRouteInfo)
+          }
         } else {
-          this.$message.error(res.data.msg)
+          this.$message.error(res.data.msg || '鐧诲綍澶辫触')
         }
       }).finally(() => {
         this.loginLoading = false
diff --git a/src/views/h5/signup/BaiduMap.vue b/src/views/h5/signup/BaiduMap.vue
index e1930ac..bc3b59f 100644
--- a/src/views/h5/signup/BaiduMap.vue
+++ b/src/views/h5/signup/BaiduMap.vue
@@ -32,8 +32,6 @@
   });
   return baiduMapPromise;
 }
-import { getCurrentPosition } from '@/utils/tool.js'
-
 export default {
   name: 'BaiduMap',
   props: {
@@ -78,6 +76,7 @@
       map.enableScrollWheelZoom();
       this.map = map;
       this.$emit('ready', map);
+      this.$emit('getMapStatus', 'success')
       this.getUserPosition()
     },
     async getUserPosition() {
diff --git a/src/views/h5/signup/index.vue b/src/views/h5/signup/index.vue
index 3d549eb..7a6f83d 100644
--- a/src/views/h5/signup/index.vue
+++ b/src/views/h5/signup/index.vue
@@ -2,8 +2,8 @@
   <div class="p-4 signin">
     <div>
       <el-row class="px-7" justify="space-between">
-        <el-text class="text-lg">濮撳悕锛�<span class="font-bold">寮犱笁</span></el-text>
-        <el-text class="text-lg">韬唤璇佸熬鍙凤細<span class="font-bold">8888</span></el-text>
+        <el-text class="text-lg">濮撳悕锛�<span class="font-bold">{{ userInfo.name }}</span></el-text>
+        <el-text class="text-lg">韬唤璇佸熬鍙凤細<span class="font-bold">{{ userInfo.idCard?.slice(-4) }}</span></el-text>
       </el-row>
       <el-row justify="center" class="m-4">
         <div style="position: relative;">
@@ -13,7 +13,7 @@
               :center="centerPoint"
               @getUserPositionStatus="(evt) => userPositionStatus = evt"
               @getMapStatus="(evt) => mapStatus = evt"
-              @getDistance="(evt) => distance = evt"
+              @getDistance="getDistance"
             />
           </div>
           <div class="center-sign">
@@ -37,18 +37,18 @@
           </div>
         </div>
       </el-row>
-      <el-row justify="center">
+      <!-- <el-row justify="center">
         <el-text class="text-lg font-bold">{{ positionName }}</el-text>
-      </el-row>
+      </el-row> -->
       <el-row justify="center" class="mt-1">
         <el-text class="text-lg text-info">{{ positionAddress }}</el-text>
       </el-row>
     </div>
     <el-row justify="center" class="mt-7">
-      <div class="signin-button" :style="getSigninButtonStyle">
+      <div class="signin-button" :style="getSigninButtonStyle" @click="signinConfirm()">
         <span class="ripple" v-if="!positionError"></span>
         <el-row justify="center">
-          <el-text class="text-lg  text-white">绛惧埌</el-text>
+          <el-text class="text-lg text-white">绛惧埌</el-text>
         </el-row>
         <el-row justify="center" class="mt-1">
           <el-text class="text-lg text-white">{{ currentTimeText }}</el-text>
@@ -60,29 +60,37 @@
 
 <script>
 import BaiduMap from '@/views/h5/signup/BaiduMap.vue'
+import { useSessionStore } from '@/stores/session.js'
+import { storeToRefs } from 'pinia';
 export default {
   components: {
     BaiduMap
+  },
+  setup() {
+    const { userInfo } = storeToRefs(useSessionStore()) 
+    return { userInfo } 
   },
   data() {
     return {
       userPositionStatus: 'loading', // loading銆乫ail銆乻uccess
       mapStatus: 'loading', //loading銆乫ail銆乻uccess
-      distance: 0,
+      distance: null,
       positionError: false,
-      failMsg: '瓒呭嚭绛惧埌鑼冨洿锛屼笉鍙鍒�',
-      positionName: '涓囩浜戝煄',
+      positionName: '',
       positionAddress: '鍗楀北鍖烘墦鐭充簩璺崡118鍙�',
       currentTimeText: '',
       centerPoint: {
-        lat:22.580372,
+        lat: 22.580372,
         lng: 113.946530
       }
     }
   },
   computed: {
+    canSignup() {
+      return this.mapStatus == 'success' && this.userPositionStatus == 'success' && !this.positionError
+    },
     getSigninButtonStyle() {
-      if (this.positionStatus == 'success') {
+      if (this.canSignup) {
         return {
           'background': '#66d06c',
           'border-color': '#e7f7eb'
@@ -93,21 +101,67 @@
           'border-color': '#f8f8f8'
         }
       }
+    },
+    appId() {
+      return this.$route.query.appId
     }
+  },
+  created() {
+    this.getSignupAddress()
   },
   async mounted() {
     this.currentTimeText = this.$dayjs().format('HH:mm')
   },
   methods: {
+    getSignupAddress() {
+      const params = { applicationId: this.appId }
+      this.$axios.get('/exam/verify-record/get-by-application-id', { params }).then(res => {
+        if (res.data.code == 0) {
+          const resData = res.data.data || {}
+          // this.centerPoint = {
+          //   lat: resData.examSite?.locationLat,
+          //   lng: resData.examSite?.locationLng
+          // }
+          this.positionAddress = resData.examSite?.address
+        } else {
+          this.$message.error(res.data.msg)
+        }
+      })
+    },
     getUserPositionStatus(evt) {
       this.userPositionStatus = evt
+    },
+    getDistance(evt) {
+      this.distance = evt
+      if (this.distance && this.distance <= 500) {
+        this.positionError = false
+      } else {
+        this.positionError = true
+      }
+    },
+    signinConfirm() {
+      if (!this.canSignup) {
+        return
+      }
+      this.$message.success('绛惧埌鎴愬姛')
+      localStorage.setItem('isSignup', true)
+      setTimeout(() => {
+        if (this.getIsFace()) {
+          this.$router.replace({ path: '/h5/face', query: { appId: this.appId }})
+        } else {
+          this.$router.replace({ path: '/h5/verForm', query: { appId: this.appId }})
+        }
+      }, 500)
+    },
+    getIsFace() {
+      return Boolean(localStorage.getItem('isFace'))
     }
   }
 }
 </script>
 <style lang="scss" scoped>
 .signin {
-  height: 100%;
+  height: 100vh;
   display: flex;
   flex-direction: column;
   justify-content: space-between;
diff --git a/src/views/h5/verify/form.vue b/src/views/h5/verify/form.vue
index 8e2491d..8409462 100644
--- a/src/views/h5/verify/form.vue
+++ b/src/views/h5/verify/form.vue
@@ -1,34 +1,45 @@
 <template>
-  <div>
+  <div v-if="pdfUrl">
     <el-row class="p-3 m-0" justify="space-between" align="middle">
-      <el-col :span="3"></el-col>
-      <el-col :span="18">
-        <el-text class="text-lg font-bold text-center">
-          {{ title }}
-        </el-text>
+      <el-col :span="4"></el-col>
+      <el-col :span="16">
+        <el-row justify="center">
+          <el-text class="text-lg font-bold text-center">
+            {{ title }}
+          </el-text>
+        </el-row>
       </el-col>
-      <el-col :span="3">
-        <el-button text style="color: var(--el-color-primary);" :loading="saveLoading" @click="tempSave()">鏆傚瓨</el-button>
+      <el-col :span="4">
+        <el-row justify="center">
+          <el-button
+            v-if="!isVerified"
+            text style="color: var(--el-color-primary);" 
+            :loading="saveLoading" @click="tempSave()"
+            class="mx-4"
+          >
+            鏆傚瓨
+          </el-button>
+        </el-row>
       </el-col>
     </el-row>
     
     <el-divider class="m-0" style="flex-shrink: 0;"></el-divider>
-    <el-scrollbar :height="`${mainHeight}px`" class="p-2 m-0 mt-1" min-size="none">
+    <el-scrollbar :height="`${mainHeight}px`" class="p-2 m-0 mt-1" >
       <div v-if="pdfUrl" :style="{width: '100%', height: `${mainHeight - 100}px`}">
         <PdfPreview v-if="pdfUrl" :url="pdfUrl"></PdfPreview>
       </div>
       <div class="p-2 my-4">
         <el-form ref="verifyForm" :model="form">
-          <el-form-item label="浠ヤ笂鐢虫姤鍐呭鏄惁灞炲疄" prop="isVerified">
-            <el-radio-group v-model="form.isVerified">
-              <el-radio :value="true">鏄�</el-radio>
-              <el-radio :value="false">鍚�</el-radio>
+          <el-form-item label="*浠ヤ笂鐢虫姤鍐呭鏄惁灞炲疄" prop="isVerified">
+            <el-radio-group v-model="form.isContentTrue" :disabled="isVerified">
+              <el-radio :value="1">鏄�</el-radio>
+              <el-radio :value="0">鍚�</el-radio>
             </el-radio-group>
           </el-form-item>
-          <el-form-item label="璇ヨ�冪偣鏍搁獙鏄惁閫氳繃" prop="isPass">
-            <el-radio-group v-model="form.isPass">
-              <el-radio :value="true">鏄�</el-radio>
-              <el-radio :value="false">鍚�</el-radio>
+          <el-form-item label="*璇ヨ�冪偣鏍搁獙鏄惁閫氳繃" prop="isPass">
+            <el-radio-group v-model="form.isSitePass" :disabled="isVerified">
+              <el-radio :value="1">鏄�</el-radio>
+              <el-radio :value="0">鍚�</el-radio>
             </el-radio-group>
           </el-form-item>
           <el-row><el-text>涓撳璇勪及鎰忚</el-text></el-row>
@@ -38,16 +49,23 @@
               :rows="3"
               type="textarea"
               placeholder="璇峰~鍐欒瘎浼版剰瑙�"
+              :disabled="isVerified"
             />
           </el-form-item>
-          <el-row><el-text>鐜板満宸ヤ綔鐓х墖</el-text></el-row>
+          <el-row><el-text>*鐜板満宸ヤ綔鐓х墖</el-text></el-row>
           <el-row>
-            <UploadBtn v-model="form.image" :accept="['pdf', 'jpg']" :limitFileCount="10" listType="picture-card"></UploadBtn>
+            <UploadBtn v-model="form.image" :disabled="isVerified" :accept="['pdf', 'jpg']" :limitFileCount="10" listType="picture-card"></UploadBtn>
           </el-row>
           
-          <Signature v-model="form.signature"></Signature>
+          <Signature v-model="form.signatureUrl" :disabled="isVerified" :isRequire="true"></Signature>
 
-          <el-button type="primary" size="large" class="my-7" style="width: 100%;">鎻愪氦鏍搁獙缁撴灉</el-button>
+          <el-button
+            v-if="!isVerified"
+            @click="submitVerify" 
+            type="primary" size="large" 
+            class="my-7" style="width: 100%;"
+            :loading="submitLoading"
+          >鎻愪氦鏍搁獙缁撴灉</el-button>
         </el-form>
       </div>
     </el-scrollbar>
@@ -59,7 +77,6 @@
 import Signature from '@/views/main/components/Signature.vue';
 import { useSessionStore } from '@/stores/session.js'
 import { storeToRefs } from 'pinia';
-import { tokenUtils } from '@/utils/axios.js';
 
 export default {
   components: {
@@ -77,13 +94,15 @@
       pdfUrl: '',
       form: {
         id: '',
-        isVerified: false,
-        isPass: false,
+        isContentTrue: 0,
+        isSitePass: 0,
         suggestion: '',
         image: [],
-        signature: ''
+        signatureUrl: ''
       },
-      saveLoading: false
+      isVerified: false,
+      saveLoading: false,
+      submitLoading: false
     }
   },
   computed: {
@@ -91,7 +110,7 @@
       return this.pageHeight - 80
     },
     appId() {
-      return this.$route.params.id
+      return this.$route.query.appId
     }
   },
   async created() {
@@ -99,18 +118,24 @@
   },
   mounted() {
     document.title = '鑰冪偣鏍搁獙'
-    this.pdfUrl = this.$qxueyou.qxyRes + '20260304/鍖犲績瀛﹂櫌鑱屼笟鎶�鑳借瘎浠疯�冪偣鏍搁獙鐢宠琛╛1772591122177.pdf'
   },
   methods: {
     getVerifyDetail() {
-      const params = { id: this.appId }
-      this.$axios.get('/exam/verify-record/get', { params }).then(res => {
+      const params = { applicationId: this.appId }
+      this.$axios.get('/exam/verify-record/get-by-application-id', { params }).then(res => {
         if (res.data.code == 0) {
           const resData = res.data.data || {}
+          this.pdfUrl = this.$qxueyou.qxyRes + resData.examSiteVerifyFile
+          this.title = resData.organizationName + '-' + resData.examSite.siteName + '鑰冪偣鏍搁獙'
           if (resData.id) {
-            this.form = {
-              ...resData
-            }
+            this.form.isContentTrue  = resData.isContentTrue
+            this.form.isSitePass = resData.isSitePass
+            this.form.suggestion = resData.evaluationOpinion
+            resData.sitePhotos?.forEach(ele => {
+              this.form.image.push({ name: '', url: ele })
+            })
+            this.form.signatureUrl = resData.signatureUrl
+            this.isVerified = resData.isVerified
           }
         } else {
           this.$message.error('鑾峰彇鏍搁獙淇℃伅澶辫触')
@@ -119,25 +144,62 @@
     },
     tempSave() {
       const data = {
+        applicationId: this.appId,
         id: this.form.id,
-        userId: 0,
-        name: "",
-        gender: "",
-        mobile: "",
-        age: 0,
-        idNumber: "",
-        isContentTrue: 0,
-        isSitePass: 0,
-        evaluationOpinion: "",
-        sitePhotos: [],
-        status: 0
+        userId: this.userInfo.id,
+        name: this.userInfo.name,
+        mobile: this.userInfo.mobile,
+        idNumber: this.userInfo.idCard,
+        isContentTrue: this.form.isContentTrue,
+        isSitePass: this.form.isSitePass,
+        evaluationOpinion: this.form.suggestion,
+        sitePhotos: this.form.image.map(ele => ele.url),
+        signatureUrl: this.form.signatureUrl,
       }
-      this.$axios.post('/exam/verify-record/create', data).then(res => {
+      this.saveLoading = true
+      this.$axios.put(`/exam/verify-record/save`, data).then(res => {
         if (res.data.code == 0) {
-          console.log(res.data.data)
+          this.$message.success('淇濆瓨鎴愬姛')
+          this.getVerifyDetail()
         } else {
           this.$message.error(res.data.msg)
         }
+      }).finally(() => {
+        this.saveLoading = false
+      })
+    },
+    submitVerify() {
+      if (this.form.image.length==0) {
+        this.$message.error('璇蜂笂浼犵幇鍦哄伐浣滅収鐗�')
+        return
+      }
+      if (!this.form.signatureUrl) {
+        this.$message.error('璇峰~鍐欑鍚�')
+        return
+      }
+      this.$messageBox.confirm('鎻愪氦涔嬪悗涓嶅彲鍐嶇紪杈戞牳楠岋紝纭鎻愪氦鍚�', '鎻愮ず', 
+      { confirmButtonText: '纭畾', cancelButtonText: '鍙栨秷', type: 'tip' }).then(res => {
+        if (res == 'confirm') { 
+          this.submitLoading = true
+          const data = {
+            applicationId: this.appId,
+            isContentTrue: this.form.isContentTrue,
+            isSitePass: this.form.isSitePass,
+            evaluationOpinion: this.form.suggestion,
+            sitePhotos: this.form.image.map(ele => ele.url),
+            signatureUrl: this.form.signatureUrl,
+          }
+          this.$axios.post('/exam/verify-record/verify', data).then(res => {
+            if (res.data.code == 0) {
+              this.$message.success('鎻愪氦鏍搁獙鎴愬姛')
+              this.isVerified = true
+            } else {
+              this.$message.error(res.data.msg || '鎻愪氦鏍搁獙澶辫触')
+            }
+          }).finally(() => {
+            this.submitLoading = false
+          })
+        }
       })
     },
     onPagesLoaded(msg) {
diff --git a/src/views/h5/verify/index.vue b/src/views/h5/verify/index.vue
index 7d67685..02e2d30 100644
--- a/src/views/h5/verify/index.vue
+++ b/src/views/h5/verify/index.vue
@@ -1,6 +1,5 @@
 <template>
-  <div>
-  </div>
+  <div></div>
 </template>
 <script>
 import { tokenUtils } from '@/utils/axios.js';
@@ -8,9 +7,7 @@
 export default {
   components: {},
   data() {
-    return {
-      
-    }
+    return {}
   },
   computed: {
     query() {
@@ -21,17 +18,17 @@
     }
   },
   async created() {
-    if (tokenUtils.getAccessToken()) {
-      const canVerify = await this.getCanVerify()
-      if (canVerify) {
-        this.$router.replace(`/h5/verForm/${this.appId}`)
-      } 
-      // else {
-      //   this.$router.replace('/h5/noVerAccess')
-      // }
-    } else { 
-      this.$message.error('璇峰厛鐧诲綍')
-      this.$router.replace({ path: '/h5/login', query: { appId: this.appId } })
+    const canVerify = await this.getCanVerify()
+    if (canVerify) {
+      if (!this.getIsFace()) {
+        this.$router.replace({ path: '/h5/face', query: { appId: this.appId }})
+      } else if (!this.getIsSignup()) {
+        this.$router.replace({ path: '/h5/signup', query: { appId: this.appId } })
+      } else {
+        this.$router.replace({ path: '/h5/verForm', query: { appId: this.appId }})
+      }
+    } else {
+      this.$router.replace('/h5/noVerAccess')
     }
   },
   mounted() {
@@ -54,6 +51,12 @@
         })
       })
     },
+    getIsFace() {
+      return Boolean(localStorage.getItem('isFace'))
+    },
+    getIsSignup() {
+      return Boolean(localStorage.getItem('isSignup'))
+    }
   }
 }
 </script>
\ No newline at end of file
diff --git a/src/views/main/components/Signature.vue b/src/views/main/components/Signature.vue
index 7d1bf71..cf900d7 100644
--- a/src/views/main/components/Signature.vue
+++ b/src/views/main/components/Signature.vue
@@ -1,11 +1,17 @@
 <template>
   <div class="signature">
     <el-row justify="space-between">
-      <el-text>绛惧悕</el-text>
-      <el-text v-if="imageUrl" @click="imageUrl=''">娓呴櫎绛惧悕</el-text>
-      <el-text v-else @click="signatureDialog=true">鐐瑰嚮绛惧悕</el-text>
+      <el-text>{{isRequire?'*':''}}绛惧悕</el-text>
+      <template v-if="!disabled">
+        <el-text v-if="imageUrl" @click="imageUrl=''">娓呴櫎绛惧悕</el-text>
+        <el-text v-else @click="signatureDialog=true">鐐瑰嚮绛惧悕</el-text>
+      </template>
     </el-row>
-    <el-image v-if="imageUrl" :src="imageUrl"></el-image>
+    <el-image 
+      v-if="imageUrl" 
+      style="width: 100%;"
+      :src="imageUrl.includes('http') ? imageUrl : $qxueyou.qxyRes + imageUrl">
+    </el-image>
     <div v-else class="image-slot"></div>
 
     <el-dialog 
@@ -68,6 +74,14 @@
     modelValue: {
       type: String,
       default: ''
+    },
+    isRequire: {
+      type: Boolean,
+      default: false
+    },
+    disabled: {
+      type: Boolean,
+      default: false
     }
   },
   computed: {
@@ -193,9 +207,10 @@
       }
       let base64 = this.editCanvas.toDataURL('image/png', 1)
       let smallBase64 = await this.resizedataURL(base64, 240, 80)
-      // let url = await uploadByBase64(smallBase64, '绛惧悕')
-      // if (!url) return false
-      this.imageUrl = smallBase64
+      let url = await uploadByBase64(smallBase64, '绛惧悕')
+      if (!url) return false
+      this.imageUrl = url
+      this.$emit('update:modelValue', url)
       this.signatureDialog = false
     },
     resizedataURL: function(base64, wantedWidth, wantedHeight){
diff --git a/src/views/main/components/UploadBtn.vue b/src/views/main/components/UploadBtn.vue
index 0df0c52..7e92fdd 100644
--- a/src/views/main/components/UploadBtn.vue
+++ b/src/views/main/components/UploadBtn.vue
@@ -38,7 +38,7 @@
       <template v-if="listType=='picture-card'">
         <el-image
           ref="previewImg"
-          :src="file.url"
+          :src="file.url.includes('http') ? file.url : $qxueyou.qxyRes + file.url"
           :initial-index="initialPreviewImgIndex"
           :preview-src-list="filterPreviewImgList"
         >
@@ -177,7 +177,7 @@
         file: UploadRequestOptions.file,
         directory: ''
       }
-      this.$axios.post('/infra/file/upload', data, { 
+      this.$axios.post('/infra/file/exam/upload', data, { 
         headers: { 'Content-Type': "multipart/form-data" }
       }).then(res => {
         let index = this.list.findIndex(ele => ele.uid == data.file.uid)
diff --git a/vite.config.js b/vite.config.js
index d18ddc1..1837faf 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -14,6 +14,7 @@
     },
   },
   server: {
+    host: '0.0.0.0',
     proxy: {
       '/app-api': {
         target: 'http://101.43.143.75:48180', // dev

--
Gitblit v1.8.0