| | |
| | | "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", |
| | |
| | | }, |
| | | "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": { |
| | |
| | | "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", |
| | |
| | | "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", |
| | |
| | | "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", |
| | |
| | | }, |
| | | "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 |
| | | } |
| | | } |
| | |
| | | "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", |
| | |
| | | { |
| | | "name": "app-web-examination-platform", |
| | | "name": "app-web-examination-user", |
| | | "version": "0.0.0", |
| | | "private": true, |
| | | "type": "module", |
| | |
| | | "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", |
| | |
| | | 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' |
| | | } |
| | |
| | | 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' |
| | |
| | | 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 |
| | |
| | | app.use(ElementPlus, { |
| | | locale: zhCn |
| | | }) |
| | | app.use(createPinia()) |
| | | const pinia = createPinia() |
| | | pinia.use(piniaPluginPersistedstate) |
| | | app.use(pinia) |
| | | app.use(router) |
| | | |
| | | app.mount('#app') |
| | |
| | | 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 |
| | |
| | | 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), |
| | |
| | | }) |
| | | |
| | | router.beforeEach((to, from, next) => { |
| | | const { setLastRouteInfo } = useLoginStore() |
| | | if (!to.matched.length) { |
| | | if (to.path === '/') { |
| | | next({ path: '/main/home' }) |
| | |
| | | next({ path: '/error/404', query: { errorUrl: to.path } }) |
| | | } |
| | | } else { |
| | | if (from.name) { |
| | | setLastRouteInfo(from) |
| | | } |
| | | next() |
| | | } |
| | | }) |
| | |
| | | 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 }) |
| New file |
| | |
| | | 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) { // 判断是否为IOS系统 |
| | | 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 |
| | | } |
| | |
| | | }); |
| | | 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 }) |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | 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 - 图片文件名(包含扩展名) |
| | |
| | | |
| | | 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); |
| | | }) |
| New file |
| | |
| | | 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中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符 |
| | | nonceStr: result.nonceStr, // 支付签名随机串,不长于 32 位 |
| | | package: result.packageValue, // 统一支付接口返回的prepay_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, // 当前显示图片的http链接 |
| | | 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 |
| | | } |
| New file |
| | |
| | | <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>正在审核中,请耐心等待...</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> |
| New file |
| | |
| | | <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(){ |
| | | // 获取媒体属性,旧版本浏览器可能不支持mediaDevices,设置一个空对象 |
| | | if (navigator.mediaDevices === undefined) { |
| | | navigator.mediaDevices = {}; |
| | | } |
| | | // 使用getUserMedia,因为它会覆盖现有的属性。 |
| | | // 这里,如果缺少getUserMedia属性,就添加它。 |
| | | if (navigator.mediaDevices.getUserMedia === undefined) { |
| | | this.initGetUserMedia() |
| | | } |
| | | }, |
| | | initGetUserMedia: function(){ |
| | | navigator.mediaDevices.getUserMedia = (constraints) => { |
| | | // 首先获取现存的getUserMedia(如果存在) |
| | | let getUserMedia = |
| | | navigator.webkitGetUserMedia || |
| | | navigator.mozGetUserMedia || |
| | | navigator.getUserMedia; |
| | | // 有些浏览器不支持,会返回错误信息 |
| | | // 保持接口一致 |
| | | if (!getUserMedia) {//不存在则报错 |
| | | return Promise.reject( |
| | | new Error("getUserMedia is not implemented in this browser") |
| | | ); |
| | | } |
| | | // 否则,使用Promise将调用包装到旧的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 |
| | | // 旧的浏览器可能没有srcObject |
| | | 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 { |
| | | // 解决 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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| | |
| | | <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" /> |
| | |
| | | </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、weixin |
| | | form: { |
| | | mobile: '', |
| | | code: '', |
| | |
| | | }, |
| | | created() { |
| | | tokenUtils.clearTokens() |
| | | this.loginType = isWeixin ? 'weixin' : 'mobilePhone' |
| | | this.loginType = 'mobile' |
| | | }, |
| | | computed: { |
| | | appId() { |
| | |
| | | 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 |
| | |
| | | }); |
| | | return baiduMapPromise; |
| | | } |
| | | import { getCurrentPosition } from '@/utils/tool.js' |
| | | |
| | | export default { |
| | | name: 'BaiduMap', |
| | | props: { |
| | |
| | | map.enableScrollWheelZoom(); |
| | | this.map = map; |
| | | this.$emit('ready', map); |
| | | this.$emit('getMapStatus', 'success') |
| | | this.getUserPosition() |
| | | }, |
| | | async getUserPosition() { |
| | |
| | | <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;"> |
| | |
| | | :center="centerPoint" |
| | | @getUserPositionStatus="(evt) => userPositionStatus = evt" |
| | | @getMapStatus="(evt) => mapStatus = evt" |
| | | @getDistance="(evt) => distance = evt" |
| | | @getDistance="getDistance" |
| | | /> |
| | | </div> |
| | | <div class="center-sign"> |
| | |
| | | </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> |
| | |
| | | |
| | | <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、fail、success |
| | | mapStatus: 'loading', //loading、fail、success |
| | | 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' |
| | |
| | | '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; |
| | |
| | | <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> |
| | |
| | | :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> |
| | |
| | | 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: { |
| | |
| | | pdfUrl: '', |
| | | form: { |
| | | id: '', |
| | | isVerified: false, |
| | | isPass: false, |
| | | isContentTrue: 0, |
| | | isSitePass: 0, |
| | | suggestion: '', |
| | | image: [], |
| | | signature: '' |
| | | signatureUrl: '' |
| | | }, |
| | | saveLoading: false |
| | | isVerified: false, |
| | | saveLoading: false, |
| | | submitLoading: false |
| | | } |
| | | }, |
| | | computed: { |
| | |
| | | return this.pageHeight - 80 |
| | | }, |
| | | appId() { |
| | | return this.$route.params.id |
| | | return this.$route.query.appId |
| | | } |
| | | }, |
| | | async created() { |
| | |
| | | }, |
| | | 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('获取核验信息失败') |
| | |
| | | }, |
| | | 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) { |
| | |
| | | <template> |
| | | <div> |
| | | </div> |
| | | <div></div> |
| | | </template> |
| | | <script> |
| | | import { tokenUtils } from '@/utils/axios.js'; |
| | |
| | | export default { |
| | | components: {}, |
| | | data() { |
| | | return { |
| | | |
| | | } |
| | | return {} |
| | | }, |
| | | computed: { |
| | | query() { |
| | |
| | | } |
| | | }, |
| | | 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() { |
| | |
| | | }) |
| | | }) |
| | | }, |
| | | getIsFace() { |
| | | return Boolean(localStorage.getItem('isFace')) |
| | | }, |
| | | getIsSignup() { |
| | | return Boolean(localStorage.getItem('isSignup')) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | |
| | | <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 |
| | |
| | | modelValue: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | isRequire: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | disabled: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | }, |
| | | computed: { |
| | |
| | | } |
| | | 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){ |
| | |
| | | <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" |
| | | > |
| | |
| | | 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) |
| | |
| | | }, |
| | | }, |
| | | server: { |
| | | host: '0.0.0.0', |
| | | proxy: { |
| | | '/app-api': { |
| | | target: 'http://101.43.143.75:48180', // dev |