From be55bd44fe0cf5701e01aceb8798bc7d3ecb2671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A4=E5=AD=A6=E4=BC=9A?= Date: Fri, 10 Jul 2026 10:12:03 +0800 Subject: [PATCH] fix scan routing and zip download errors --- pages/deviceQr/deviceQr.js | 11 ++- pages/index/index.js | 45 +++++------ scripts/test-erp-login-scan-routing.mjs | 54 +++++++++++++ utils/request.js | 8 +- utils/scanRouting.js | 102 ++++++++++++++++++++++++ 5 files changed, 190 insertions(+), 30 deletions(-) create mode 100644 scripts/test-erp-login-scan-routing.mjs create mode 100644 utils/scanRouting.js diff --git a/pages/deviceQr/deviceQr.js b/pages/deviceQr/deviceQr.js index 645a470..d1d7527 100644 --- a/pages/deviceQr/deviceQr.js +++ b/pages/deviceQr/deviceQr.js @@ -381,9 +381,14 @@ Page({ await this.shareZipFile(filePath, fileName) } catch (error) { wx.hideLoading() - const message = error && error.errMsg && error.errMsg.includes('cancel') - ? '已取消转发' - : (error.message || '转发失败') + let message = error.message || '转发失败' + if (error && error.errMsg && error.errMsg.includes('cancel')) { + message = '已取消转发' + } else if (error && error.statusCode === 404) { + message = 'ZIP文件不存在或已过期,请重新生成' + } else if (error && error.statusCode === 409) { + message = 'ZIP还未生成完成,请刷新后重试' + } wx.showToast({ title: message, icon: 'none' }) } }, diff --git a/pages/index/index.js b/pages/index/index.js index 62442c2..bc3a11f 100644 --- a/pages/index/index.js +++ b/pages/index/index.js @@ -1,5 +1,6 @@ const api = require('../../utils/api') const locationGuard = require('../../utils/locationGuard') +const { buildErpLoginConfirmUrlFromScanResult } = require('../../utils/scanRouting') const avatarKey = phone => `jh_wrs_avatar_${phone}` @@ -373,22 +374,14 @@ Page({ return raw }, async scanDevice() { - wx.showLoading({ title: '定位中' }) - try { - await locationGuard.ensureFactoryLocation() - } catch (error) { - wx.hideLoading() - wx.showModal({ - title: '无法扫码报工', - content: error.message || '当前位置不在允许范围内', - showCancel: false, - }) - return - } - wx.hideLoading() wx.scanCode({ onlyFromCamera: false, success: async res => { + const erpLoginUrl = buildErpLoginConfirmUrlFromScanResult(res) + if (erpLoginUrl) { + wx.navigateTo({ url: erpLoginUrl }) + return + } const candidates = [res.path, res.result, res.rawData] const moldName = candidates.reduce((found, item) => ( found || this.extractMoldName(item) @@ -398,21 +391,23 @@ Page({ return } const paramName = moldName.startsWith('mold=') ? 'scene' : 'moldName' - if (paramName === 'scene') { - try { - wx.showLoading({ title: '校验中' }) + try { + wx.showLoading({ title: paramName === 'scene' ? '校验中' : '定位中' }) + if (paramName === 'scene') { const resolved = await api.resolveMoldScene(moldName) await locationGuard.ensureFactoryLocation(resolved.attendancePointName) - } catch (error) { - wx.showModal({ - title: '无法扫码报工', - content: error.message || '当前位置不在该考勤点范围内', - showCancel: false, - }) - return - } finally { - wx.hideLoading() + } else { + await locationGuard.ensureFactoryLocation() } + } catch (error) { + wx.showModal({ + title: '无法扫码报工', + content: error.message || '当前位置不在允许范围内', + showCancel: false, + }) + return + } finally { + wx.hideLoading() } wx.navigateTo({ url: `/pages/clock/clock?${paramName}=${encodeURIComponent(moldName)}`, diff --git a/scripts/test-erp-login-scan-routing.mjs b/scripts/test-erp-login-scan-routing.mjs new file mode 100644 index 0000000..c11c8e9 --- /dev/null +++ b/scripts/test-erp-login-scan-routing.mjs @@ -0,0 +1,54 @@ +import assert from "node:assert/strict"; +import { createRequire } from "node:module"; +import { existsSync, readFileSync } from "node:fs"; + +const require = createRequire(import.meta.url); +const indexSource = readFileSync("pages/index/index.js", "utf8"); +const helperPath = "utils/scanRouting.js"; + +assert.ok(existsSync(helperPath), "scan routing helper should exist"); + +const { buildErpLoginConfirmUrlFromScanResult } = require("../utils/scanRouting.js"); + +assert.equal( + buildErpLoginConfirmUrlFromScanResult({ + path: "pages/erpLoginConfirm/erpLoginConfirm?scene=t%3Dabc123%26s%3D7", + }), + "/pages/erpLoginConfirm/erpLoginConfirm?scene=t%3Dabc123%26s%3D7", +); +assert.equal( + buildErpLoginConfirmUrlFromScanResult({ + result: "scene=t%3DcompactTicket%26s%3D9", + }), + "/pages/erpLoginConfirm/erpLoginConfirm?scene=t%3DcompactTicket%26s%3D9", +); +assert.equal( + buildErpLoginConfirmUrlFromScanResult({ + path: "pages/erpLoginConfirm/erpLoginConfirm?ticket=legacyTicket&session_id=12", + }), + "/pages/erpLoginConfirm/erpLoginConfirm?scene=t%3DlegacyTicket%26s%3D12", +); +assert.equal( + buildErpLoginConfirmUrlFromScanResult({ + result: "pages/clock/clock?scene=mold%3Dabc123", + }), + "", +); +assert.equal( + buildErpLoginConfirmUrlFromScanResult({ + result: "mold=abc123", + }), + "", +); + +const scanDeviceIndex = indexSource.indexOf("async scanDevice()"); +const erpRoutingIndex = indexSource.indexOf("buildErpLoginConfirmUrlFromScanResult", scanDeviceIndex); +const moldRoutingIndex = indexSource.indexOf("extractMoldName", scanDeviceIndex); +const locationGuardIndex = indexSource.indexOf("ensureFactoryLocation", scanDeviceIndex); + +assert.ok(scanDeviceIndex >= 0, "index page should have scanDevice"); +assert.ok(erpRoutingIndex > scanDeviceIndex, "scanDevice should check ERP login scan result"); +assert.ok(erpRoutingIndex < moldRoutingIndex, "ERP login scan should be routed before mold parsing"); +assert.ok(erpRoutingIndex < locationGuardIndex, "ERP login scan should not require report location guard"); + +console.log("erp login scan routing checks passed"); diff --git a/utils/request.js b/utils/request.js index 76236dc..0402af9 100644 --- a/utils/request.js +++ b/utils/request.js @@ -111,7 +111,9 @@ const download = options => new Promise((resolve, reject) => { if (res.statusCode === 401) { clearAuth() } - reject(new Error(`下载失败:${res.statusCode}`)) + const error = new Error(`下载失败:${res.statusCode}`) + error.statusCode = res.statusCode + reject(error) }, fail: err => { reject(new Error(err.errMsg || '下载失败')) @@ -137,7 +139,9 @@ const downloadUrl = options => new Promise((resolve, reject) => { if (res.statusCode === 401) { clearAuth() } - reject(new Error(`下载失败:${res.statusCode}`)) + const error = new Error(`下载失败:${res.statusCode}`) + error.statusCode = res.statusCode + reject(error) }, fail: err => { reject(new Error(err.errMsg || '下载失败')) diff --git a/utils/scanRouting.js b/utils/scanRouting.js new file mode 100644 index 0000000..8895aca --- /dev/null +++ b/utils/scanRouting.js @@ -0,0 +1,102 @@ +const ERP_LOGIN_PAGE = '/pages/erpLoginConfirm/erpLoginConfirm' +const ERP_LOGIN_PAGE_PATH = 'pages/erpLoginConfirm/erpLoginConfirm' + +const decodeRepeated = value => { + let text = String(value || '').trim() + for (let index = 0; index < 3; index += 1) { + try { + const decoded = decodeURIComponent(text) + if (decoded === text) { + break + } + text = decoded + } catch (error) { + break + } + } + return text +} + +const extractParam = (value, names) => { + const raw = decodeRepeated(value) + for (let index = 0; index < names.length; index += 1) { + const name = names[index] + const matched = raw.match(new RegExp(`(?:^|[?&])${name}=([^&]+)`)) + if (matched && matched[1]) { + return decodeRepeated(matched[1]) + } + } + return '' +} + +const extractRawParam = (value, names) => { + const raw = String(value || '').trim() + for (let index = 0; index < names.length; index += 1) { + const name = names[index] + const matched = raw.match(new RegExp(`(?:^|[?&])${name}=([^&]+)`)) + if (matched && matched[1]) { + return matched[1] + } + } + return '' +} + +const erpSceneFromText = value => { + const rawText = String(value || '').trim() + const rawScene = extractRawParam(rawText, ['scene']) + if (rawScene) { + const decodedScene = decodeRepeated(rawScene) + const nested = erpSceneFromText(decodedScene) + if (nested) { + return nested + } + const sceneTicket = extractParam(decodedScene, ['ticket', 't']) + const sessionId = extractParam(rawText, ['session_id', 'sessionId', 's']) + if (sceneTicket && sessionId) { + return `t=${sceneTicket}&s=${sessionId}` + } + } + const raw = decodeRepeated(value) + if (!raw) { + return '' + } + const nestedScene = extractParam(raw, ['scene']) + if (nestedScene) { + const nested = erpSceneFromText(nestedScene) + if (nested) { + return nested + } + } + const ticket = extractParam(raw, ['ticket', 't']) + const sessionId = extractParam(raw, ['session_id', 'sessionId', 's']) + if (!ticket || !sessionId) { + return '' + } + return `t=${ticket}&s=${sessionId}` +} + +const isErpLoginPath = value => decodeRepeated(value).replace(/^\/+/, '').includes(ERP_LOGIN_PAGE_PATH) + +const buildErpLoginConfirmUrlFromScanResult = scanResult => { + const candidates = [ + scanResult && scanResult.path, + scanResult && scanResult.result, + scanResult && scanResult.rawData, + ] + for (let index = 0; index < candidates.length; index += 1) { + const candidate = decodeRepeated(candidates[index]) + if (!candidate) { + continue + } + const scene = erpSceneFromText(candidate) + if (scene && (isErpLoginPath(candidate) || !extractParam(scene, ['mold']))) { + return `${ERP_LOGIN_PAGE}?scene=${encodeURIComponent(scene)}` + } + } + return '' +} + +module.exports = { + buildErpLoginConfirmUrlFromScanResult, + decodeRepeated, +}