feat(离线模式): 添加弱网检测和离线预约码功能
实现弱网检测机制,当网络不可用时自动切换至离线模式 新增离线预约码页面和组件,支持本地缓存和二维码生成 添加网络状态监听和预约码数据预加载功能 更新文档说明离线模式的使用流程和实现细节
Showing
15 changed files
with
674 additions
and
235 deletions
| ... | @@ -15,6 +15,7 @@ declare module 'vue' { | ... | @@ -15,6 +15,7 @@ declare module 'vue' { |
| 15 | NutFormItem: typeof import('@nutui/nutui-taro')['FormItem'] | 15 | NutFormItem: typeof import('@nutui/nutui-taro')['FormItem'] |
| 16 | NutInput: typeof import('@nutui/nutui-taro')['Input'] | 16 | NutInput: typeof import('@nutui/nutui-taro')['Input'] |
| 17 | NutPopup: typeof import('@nutui/nutui-taro')['Popup'] | 17 | NutPopup: typeof import('@nutui/nutui-taro')['Popup'] |
| 18 | + OfflineQrCode: typeof import('./src/components/offlineQrCode.vue')['default'] | ||
| 18 | Picker: typeof import('./src/components/time-picker-data/picker.vue')['default'] | 19 | Picker: typeof import('./src/components/time-picker-data/picker.vue')['default'] |
| 19 | PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default'] | 20 | PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default'] |
| 20 | QrCode: typeof import('./src/components/qrCode.vue')['default'] | 21 | QrCode: typeof import('./src/components/qrCode.vue')['default'] | ... | ... |
doc/弱网用户端缓存预约码.md
0 → 100644
| 1 | +1. 如果判断是没有网络或者长时间无法加载出页面, 判定进入弱网模式. | ||
| 2 | +2. 如果是弱网模式, 用户打开一个新页面, 文字描述提示用户网络不好, 进入离线模式, 中间有一个圆形按钮文字是预约码, 用户点击进入新的离线版预约版页面. | ||
| 3 | +3. 新做一个离线版预约码组件 仿照/components/qrCode.vue, 但是功能需要调整, 因为是离线状态, 所以刷新功能是要删除掉的, 对应qrcodeStatusAPI接口查询二维码相关功能也要删除掉, 不会显示二维码的状态, billPersonAPI接口相关功能也要删除掉, 因为不会有payId传进来. 也不会有轮询的功能. 也不会有查看预约记录的显示, 当有网情况下进入时就需要把qrcodeListAPI接口数据保存到本地, 如果判断是没有网络或者长时间无法加载出页面就要跳转到, 一个结构和内容类似于bookingCode的离线版新页面, 因为没有网络不能使用'/admin?m=srv&a=get_qrcode&key=' + item.qr_code的方式获取二维码,需要找一个生成二维码的库本地生成二维码. | ||
| 4 | +4. 现在接口没有数据, 先帮我mock数据, 数据格式和qrcodeListAPI接口返回的格式一致. |
doc/针对弱网 / 无网场景的扫码核销.md
deleted
100644 → 0
| 1 | -2. 关键功能模块实现(微信小程序示例) | ||
| 2 | -以下是核心代码实现,基于微信小程序的 wx.getNetworkType 检测网络、wx.setStorageSync 本地存储、自有后端同步的方案: | ||
| 3 | -```javascript | ||
| 4 | -// pages/verify/verify.js | ||
| 5 | -// 下面是主要思路和实现, 需要修改的是扫描成功之后需要跳转到另一个页面显示核销成功/失败的提示 | ||
| 6 | -// 1. 调用 scanCodeVerify 方法开始扫码核销 | ||
| 7 | -// 2. 扫码成功后,调用 handleVerify 方法处理核销逻辑 | ||
| 8 | -// 3. 在 handleVerify 方法中,先检测网络状态 | ||
| 9 | -// 4. 如果有网,实时校验核销码并调用后端核销接口 | ||
| 10 | -// 5. 如果无网,先校验本地存储中是否有该核销码的记录 | ||
| 11 | -// 6. 如果有记录,说明之前核销过,提示用户已核销 | ||
| 12 | -// 7. 如果无记录,提示用户无网络,无法核销 | ||
| 13 | -// 8. 核销成功后,同步更新本地记录(可选) | ||
| 14 | -Page({ | ||
| 15 | - /** | ||
| 16 | - * 扫码核销核心方法 | ||
| 17 | - */ | ||
| 18 | - scanCodeVerify() { | ||
| 19 | - // 1. 调起微信扫码API | ||
| 20 | - wx.scanCode({ | ||
| 21 | - onlyFromCamera: true, // 仅允许从相机扫码 | ||
| 22 | - scanType: ['qrCode'], // 仅识别二维码 | ||
| 23 | - success: (res) => { | ||
| 24 | - const verifyCode = res.result; // 扫码获取的核销码 | ||
| 25 | - this.handleVerify(verifyCode); | ||
| 26 | - }, | ||
| 27 | - fail: (err) => { | ||
| 28 | - wx.showToast({ title: '扫码失败,请重试', icon: 'none' }); | ||
| 29 | - console.error('扫码失败:', err); | ||
| 30 | - } | ||
| 31 | - }); | ||
| 32 | - }, | ||
| 33 | - | ||
| 34 | - /** | ||
| 35 | - * 处理核销逻辑(核心) | ||
| 36 | - * @param {string} verifyCode 核销码 | ||
| 37 | - */ | ||
| 38 | - handleVerify(verifyCode) { | ||
| 39 | - wx.showLoading({ title: '核销中...' }); | ||
| 40 | - | ||
| 41 | - // 第一步:检测网络状态 | ||
| 42 | - wx.getNetworkType({ | ||
| 43 | - success: (networkRes) => { | ||
| 44 | - const networkType = networkRes.networkType; | ||
| 45 | - // 判断是否有网(wifi/4g/5g为有网,none/unknown为无网) | ||
| 46 | - if (['wifi', '4g', '5g', '3g', '2g'].includes(networkType)) { | ||
| 47 | - // 有网场景:实时校验+核销 | ||
| 48 | - this.verifyOnline(verifyCode); | ||
| 49 | - } else { | ||
| 50 | - // 无网场景:本地校验+离线核销 | ||
| 51 | - this.verifyOffline(verifyCode); | ||
| 52 | - } | ||
| 53 | - }, | ||
| 54 | - fail: () => { | ||
| 55 | - // 网络检测失败,默认走离线逻辑 | ||
| 56 | - this.verifyOffline(verifyCode); | ||
| 57 | - wx.hideLoading(); | ||
| 58 | - } | ||
| 59 | - }); | ||
| 60 | - }, | ||
| 61 | - | ||
| 62 | - /** | ||
| 63 | - * 有网核销逻辑 | ||
| 64 | - * @param {string} verifyCode 核销码 | ||
| 65 | - */ | ||
| 66 | - verifyOnline(verifyCode) { | ||
| 67 | - wx.request({ | ||
| 68 | - url: 'https://你的服务器域名/api/verify', // 后端核销接口 | ||
| 69 | - method: 'POST', | ||
| 70 | - data: { verifyCode }, | ||
| 71 | - success: (res) => { | ||
| 72 | - wx.hideLoading(); | ||
| 73 | - if (res.data.code === 200) { | ||
| 74 | - wx.showToast({ title: '核销成功', icon: 'success' }); | ||
| 75 | - // 同步本地预约状态(可选) | ||
| 76 | - this.updateLocalVerifyStatus(verifyCode, true); | ||
| 77 | - } else { | ||
| 78 | - wx.showToast({ title: res.data.msg || '核销失败', icon: 'none' }); | ||
| 79 | - } | ||
| 80 | - }, | ||
| 81 | - fail: (err) => { | ||
| 82 | - wx.hideLoading(); | ||
| 83 | - // 网络请求失败,降级到离线核销 | ||
| 84 | - wx.showToast({ title: '网络不稳定,将使用离线核销', icon: 'none' }); | ||
| 85 | - setTimeout(() => { | ||
| 86 | - this.verifyOffline(verifyCode); | ||
| 87 | - }, 1500); | ||
| 88 | - console.error('在线核销失败:', err); | ||
| 89 | - } | ||
| 90 | - }); | ||
| 91 | - }, | ||
| 92 | - | ||
| 93 | - /** | ||
| 94 | - * 无网核销逻辑 | ||
| 95 | - * @param {string} verifyCode 核销码 | ||
| 96 | - */ | ||
| 97 | - verifyOffline(verifyCode) { | ||
| 98 | - wx.hideLoading(); | ||
| 99 | - // 1. 读取本地缓存的预约数据(需提前同步到本地) | ||
| 100 | - const localAppointments = wx.getStorageSync('localAppointments') || []; | ||
| 101 | - // 2. 本地校验核销码是否有效 | ||
| 102 | - const targetAppointment = localAppointments.find(item => item.verifyCode === verifyCode); | ||
| 103 | - | ||
| 104 | - if (!targetAppointment) { | ||
| 105 | - wx.showToast({ title: '未找到该预约记录', icon: 'none' }); | ||
| 106 | - return; | ||
| 107 | - } | ||
| 108 | - if (targetAppointment.isVerified) { | ||
| 109 | - wx.showToast({ title: '该预约已核销', icon: 'none' }); | ||
| 110 | - return; | ||
| 111 | - } | ||
| 112 | - | ||
| 113 | - // 3. 本地核销:更新本地状态+记录离线操作 | ||
| 114 | - targetAppointment.isVerified = true; | ||
| 115 | - const offlineVerifyRecords = wx.getStorageSync('offlineVerifyRecords') || []; | ||
| 116 | - offlineVerifyRecords.push({ | ||
| 117 | - verifyCode, | ||
| 118 | - appointmentId: targetAppointment.id, | ||
| 119 | - verifyTime: new Date().getTime(), // 核销时间戳 | ||
| 120 | - status: 'pending' // 待同步 | ||
| 121 | - }); | ||
| 122 | - | ||
| 123 | - // 4. 保存本地修改 | ||
| 124 | - wx.setStorageSync('localAppointments', localAppointments); | ||
| 125 | - wx.setStorageSync('offlineVerifyRecords', offlineVerifyRecords); | ||
| 126 | - | ||
| 127 | - wx.showToast({ title: '离线核销成功,网络恢复后自动同步', icon: 'success' }); | ||
| 128 | - }, | ||
| 129 | - | ||
| 130 | - /** | ||
| 131 | - * 网络恢复后同步离线核销记录 | ||
| 132 | - */ | ||
| 133 | - syncOfflineRecords() { | ||
| 134 | - // 监听网络状态变化 | ||
| 135 | - wx.onNetworkStatusChange((res) => { | ||
| 136 | - if (res.isConnected) { | ||
| 137 | - const offlineRecords = wx.getStorageSync('offlineVerifyRecords') || []; | ||
| 138 | - if (offlineRecords.length === 0) return; | ||
| 139 | - | ||
| 140 | - // 批量同步离线记录到服务器 | ||
| 141 | - wx.request({ | ||
| 142 | - url: 'https://你的服务器域名/api/syncOfflineVerify', | ||
| 143 | - method: 'POST', | ||
| 144 | - data: { records: offlineRecords }, | ||
| 145 | - success: (res) => { | ||
| 146 | - if (res.data.code === 200) { | ||
| 147 | - // 同步成功:清空离线记录 | ||
| 148 | - wx.setStorageSync('offlineVerifyRecords', []); | ||
| 149 | - wx.showToast({ title: '离线核销记录已同步', icon: 'success' }); | ||
| 150 | - } | ||
| 151 | - }, | ||
| 152 | - fail: (err) => { | ||
| 153 | - console.error('离线记录同步失败:', err); | ||
| 154 | - } | ||
| 155 | - }); | ||
| 156 | - } | ||
| 157 | - }); | ||
| 158 | - }, | ||
| 159 | - | ||
| 160 | - /** | ||
| 161 | - * 提前同步预约数据到本地(页面加载时执行) | ||
| 162 | - */ | ||
| 163 | - syncAppointmentsToLocal() { | ||
| 164 | - // 页面初始化/小程序启动时,拉取预约数据到本地 | ||
| 165 | - wx.request({ | ||
| 166 | - url: 'https://你的服务器域名/api/getAppointments', | ||
| 167 | - success: (res) => { | ||
| 168 | - if (res.data.code === 200) { | ||
| 169 | - wx.setStorageSync('localAppointments', res.data.data); | ||
| 170 | - } | ||
| 171 | - }, | ||
| 172 | - fail: () => { | ||
| 173 | - // 网络失败则使用上次缓存的本地数据 | ||
| 174 | - console.log('使用本地缓存的预约数据'); | ||
| 175 | - } | ||
| 176 | - }); | ||
| 177 | - }, | ||
| 178 | - | ||
| 179 | - /** | ||
| 180 | - * 更新本地核销状态(辅助方法) | ||
| 181 | - */ | ||
| 182 | - updateLocalVerifyStatus(verifyCode, isVerified) { | ||
| 183 | - const localAppointments = wx.getStorageSync('localAppointments') || []; | ||
| 184 | - const index = localAppointments.findIndex(item => item.verifyCode === verifyCode); | ||
| 185 | - if (index > -1) { | ||
| 186 | - localAppointments[index].isVerified = isVerified; | ||
| 187 | - wx.setStorageSync('localAppointments', localAppointments); | ||
| 188 | - } | ||
| 189 | - }, | ||
| 190 | - | ||
| 191 | - /** | ||
| 192 | - * 页面加载时初始化 | ||
| 193 | - */ | ||
| 194 | - onLoad(options) { | ||
| 195 | - // 1. 同步预约数据到本地 | ||
| 196 | - this.syncAppointmentsToLocal(); | ||
| 197 | - // 2. 监听网络状态,同步离线记录 | ||
| 198 | - this.syncOfflineRecords(); | ||
| 199 | - // 3. 主动检查一次是否有未同步的离线记录 | ||
| 200 | - wx.getNetworkType({ | ||
| 201 | - success: (res) => { | ||
| 202 | - if (['wifi', '4g', '5g'].includes(res.networkType)) { | ||
| 203 | - this.syncOfflineRecords(); | ||
| 204 | - } | ||
| 205 | - } | ||
| 206 | - }); | ||
| 207 | - } | ||
| 208 | -}); | ||
| 209 | -``` | ||
| 210 | -3. 配套设计要点 | ||
| 211 | -- 本地数据预处理 | ||
| 212 | - - 小程序启动 / 核销页面加载时,主动拉取当前门店 / 账号下的待核销预约数据,缓存到本地(wx.setStorageSync 或 wx.setStorage); | ||
| 213 | - - 本地数据需包含:预约 ID、核销码、用户信息、预约时间、核销状态等核心字段,避免冗余。 | ||
| 214 | -- 防重复核销设计 | ||
| 215 | - - 本地记录核销码的核销状态(isVerified),即使无网也能避免重复核销; | ||
| 216 | - - 服务器端需做最终校验,同步离线记录时若发现已核销,返回提示并更新本地状态。 | ||
| 217 | -- 数据安全与容错 | ||
| 218 | - - 本地存储的核销记录需加简单加密(如 base64 + 时间戳),防止篡改; | ||
| 219 | - - 离线记录同步时,服务器需校验核销码有效性、时间范围(如预约有效期),避免无效核销; | ||
| 220 | - - 小程序退出 / 重启后,保留本地缓存(微信小程序本地缓存默认长期有效,可设置过期时间)。 | ||
| 221 | -- 用户体验优化 | ||
| 222 | - - 无网核销时,明确提示 “离线核销成功,网络恢复后自动同步”; | ||
| 223 | - - 网络恢复后自动同步,无需用户手动操作; | ||
| 224 | - - 提供 “离线记录查询” 入口,方便查看未同步的核销记录。 | ||
| 225 | - | ||
| 226 | - | ||
| 227 | -总结 | ||
| 228 | - 核心逻辑:无网时依赖本地缓存的预约数据完成核销,记录离线操作;有网时实时核销,网络恢复后自动同步离线记录; | ||
| 229 | - 关键保障:提前同步待核销数据到本地、本地标记核销状态防重复、服务器端做最终校验; | ||
| 230 | - 体验优化:明确的网络状态提示、自动同步机制,减少用户感知网络差异。 |
| ... | @@ -56,6 +56,7 @@ | ... | @@ -56,6 +56,7 @@ |
| 56 | "axios-miniprogram": "^2.7.2", | 56 | "axios-miniprogram": "^2.7.2", |
| 57 | "dayjs": "^1.11.19", | 57 | "dayjs": "^1.11.19", |
| 58 | "pinia": "^3.0.3", | 58 | "pinia": "^3.0.3", |
| 59 | + "qrcode": "^1.5.4", | ||
| 59 | "qs": "^6.14.1", | 60 | "qs": "^6.14.1", |
| 60 | "taro-plugin-pinia": "^1.0.0", | 61 | "taro-plugin-pinia": "^1.0.0", |
| 61 | "vue": "^3.3.0", | 62 | "vue": "^3.3.0", | ... | ... |
| ... | @@ -68,6 +68,9 @@ importers: | ... | @@ -68,6 +68,9 @@ importers: |
| 68 | pinia: | 68 | pinia: |
| 69 | specifier: ^3.0.3 | 69 | specifier: ^3.0.3 |
| 70 | version: 3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)) | 70 | version: 3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)) |
| 71 | + qrcode: | ||
| 72 | + specifier: ^1.5.4 | ||
| 73 | + version: 1.5.4 | ||
| 71 | qs: | 74 | qs: |
| 72 | specifier: ^6.14.1 | 75 | specifier: ^6.14.1 |
| 73 | version: 6.14.1 | 76 | version: 6.14.1 |
| ... | @@ -2751,6 +2754,9 @@ packages: | ... | @@ -2751,6 +2754,9 @@ packages: |
| 2751 | resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} | 2754 | resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} |
| 2752 | engines: {node: '>= 10'} | 2755 | engines: {node: '>= 10'} |
| 2753 | 2756 | ||
| 2757 | + cliui@6.0.0: | ||
| 2758 | + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} | ||
| 2759 | + | ||
| 2754 | cliui@7.0.4: | 2760 | cliui@7.0.4: |
| 2755 | resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} | 2761 | resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} |
| 2756 | 2762 | ||
| ... | @@ -3076,6 +3082,10 @@ packages: | ... | @@ -3076,6 +3082,10 @@ packages: |
| 3076 | supports-color: | 3082 | supports-color: |
| 3077 | optional: true | 3083 | optional: true |
| 3078 | 3084 | ||
| 3085 | + decamelize@1.2.0: | ||
| 3086 | + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} | ||
| 3087 | + engines: {node: '>=0.10.0'} | ||
| 3088 | + | ||
| 3079 | decimal.js@10.6.0: | 3089 | decimal.js@10.6.0: |
| 3080 | resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} | 3090 | resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} |
| 3081 | 3091 | ||
| ... | @@ -3186,6 +3196,9 @@ packages: | ... | @@ -3186,6 +3196,9 @@ packages: |
| 3186 | didyoumean@1.2.2: | 3196 | didyoumean@1.2.2: |
| 3187 | resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} | 3197 | resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} |
| 3188 | 3198 | ||
| 3199 | + dijkstrajs@1.0.3: | ||
| 3200 | + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} | ||
| 3201 | + | ||
| 3189 | dingtalk-jsapi@2.15.6: | 3202 | dingtalk-jsapi@2.15.6: |
| 3190 | resolution: {integrity: sha512-804mFz2AFV/H9ysmo7dLqMjSGOQgREsgQIuep+Xg+yNQeQtnUOYntElEzlB798Sj/691e4mMKz9mtQ7v9qdjuA==} | 3203 | resolution: {integrity: sha512-804mFz2AFV/H9ysmo7dLqMjSGOQgREsgQIuep+Xg+yNQeQtnUOYntElEzlB798Sj/691e4mMKz9mtQ7v9qdjuA==} |
| 3191 | 3204 | ||
| ... | @@ -3617,6 +3630,10 @@ packages: | ... | @@ -3617,6 +3630,10 @@ packages: |
| 3617 | resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} | 3630 | resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} |
| 3618 | engines: {node: '>=6'} | 3631 | engines: {node: '>=6'} |
| 3619 | 3632 | ||
| 3633 | + find-up@4.1.0: | ||
| 3634 | + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} | ||
| 3635 | + engines: {node: '>=8'} | ||
| 3636 | + | ||
| 3620 | find-up@5.0.0: | 3637 | find-up@5.0.0: |
| 3621 | resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} | 3638 | resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} |
| 3622 | engines: {node: '>=10'} | 3639 | engines: {node: '>=10'} |
| ... | @@ -4512,6 +4529,10 @@ packages: | ... | @@ -4512,6 +4529,10 @@ packages: |
| 4512 | resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} | 4529 | resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} |
| 4513 | engines: {node: '>=6'} | 4530 | engines: {node: '>=6'} |
| 4514 | 4531 | ||
| 4532 | + locate-path@5.0.0: | ||
| 4533 | + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} | ||
| 4534 | + engines: {node: '>=8'} | ||
| 4535 | + | ||
| 4515 | locate-path@6.0.0: | 4536 | locate-path@6.0.0: |
| 4516 | resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} | 4537 | resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} |
| 4517 | engines: {node: '>=10'} | 4538 | engines: {node: '>=10'} |
| ... | @@ -4912,6 +4933,10 @@ packages: | ... | @@ -4912,6 +4933,10 @@ packages: |
| 4912 | resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} | 4933 | resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} |
| 4913 | engines: {node: '>=6'} | 4934 | engines: {node: '>=6'} |
| 4914 | 4935 | ||
| 4936 | + p-locate@4.1.0: | ||
| 4937 | + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} | ||
| 4938 | + engines: {node: '>=8'} | ||
| 4939 | + | ||
| 4915 | p-locate@5.0.0: | 4940 | p-locate@5.0.0: |
| 4916 | resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} | 4941 | resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} |
| 4917 | engines: {node: '>=10'} | 4942 | engines: {node: '>=10'} |
| ... | @@ -5084,6 +5109,10 @@ packages: | ... | @@ -5084,6 +5109,10 @@ packages: |
| 5084 | platform@1.3.6: | 5109 | platform@1.3.6: |
| 5085 | resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} | 5110 | resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} |
| 5086 | 5111 | ||
| 5112 | + pngjs@5.0.0: | ||
| 5113 | + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} | ||
| 5114 | + engines: {node: '>=10.13.0'} | ||
| 5115 | + | ||
| 5087 | possible-typed-array-names@1.1.0: | 5116 | possible-typed-array-names@1.1.0: |
| 5088 | resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} | 5117 | resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} |
| 5089 | engines: {node: '>= 0.4'} | 5118 | engines: {node: '>= 0.4'} |
| ... | @@ -5611,6 +5640,11 @@ packages: | ... | @@ -5611,6 +5640,11 @@ packages: |
| 5611 | resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} | 5640 | resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} |
| 5612 | engines: {node: '>=6'} | 5641 | engines: {node: '>=6'} |
| 5613 | 5642 | ||
| 5643 | + qrcode@1.5.4: | ||
| 5644 | + resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} | ||
| 5645 | + engines: {node: '>=10.13.0'} | ||
| 5646 | + hasBin: true | ||
| 5647 | + | ||
| 5614 | qs@6.14.1: | 5648 | qs@6.14.1: |
| 5615 | resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} | 5649 | resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} |
| 5616 | engines: {node: '>=0.6'} | 5650 | engines: {node: '>=0.6'} |
| ... | @@ -5734,6 +5768,9 @@ packages: | ... | @@ -5734,6 +5768,9 @@ packages: |
| 5734 | resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} | 5768 | resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} |
| 5735 | engines: {node: '>=0.10.0'} | 5769 | engines: {node: '>=0.10.0'} |
| 5736 | 5770 | ||
| 5771 | + require-main-filename@2.0.0: | ||
| 5772 | + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} | ||
| 5773 | + | ||
| 5737 | requires-port@1.0.0: | 5774 | requires-port@1.0.0: |
| 5738 | resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} | 5775 | resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} |
| 5739 | 5776 | ||
| ... | @@ -5928,6 +5965,9 @@ packages: | ... | @@ -5928,6 +5965,9 @@ packages: |
| 5928 | resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} | 5965 | resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} |
| 5929 | engines: {node: '>= 0.8.0'} | 5966 | engines: {node: '>= 0.8.0'} |
| 5930 | 5967 | ||
| 5968 | + set-blocking@2.0.0: | ||
| 5969 | + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} | ||
| 5970 | + | ||
| 5931 | set-function-length@1.2.2: | 5971 | set-function-length@1.2.2: |
| 5932 | resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} | 5972 | resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} |
| 5933 | engines: {node: '>= 0.4'} | 5973 | engines: {node: '>= 0.4'} |
| ... | @@ -6655,6 +6695,9 @@ packages: | ... | @@ -6655,6 +6695,9 @@ packages: |
| 6655 | resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} | 6695 | resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} |
| 6656 | engines: {node: '>= 0.4'} | 6696 | engines: {node: '>= 0.4'} |
| 6657 | 6697 | ||
| 6698 | + which-module@2.0.1: | ||
| 6699 | + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} | ||
| 6700 | + | ||
| 6658 | which-typed-array@1.1.19: | 6701 | which-typed-array@1.1.19: |
| 6659 | resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} | 6702 | resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} |
| 6660 | engines: {node: '>= 0.4'} | 6703 | engines: {node: '>= 0.4'} |
| ... | @@ -6715,6 +6758,9 @@ packages: | ... | @@ -6715,6 +6758,9 @@ packages: |
| 6715 | xxhashjs@0.2.2: | 6758 | xxhashjs@0.2.2: |
| 6716 | resolution: {integrity: sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==} | 6759 | resolution: {integrity: sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==} |
| 6717 | 6760 | ||
| 6761 | + y18n@4.0.3: | ||
| 6762 | + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} | ||
| 6763 | + | ||
| 6718 | y18n@5.0.8: | 6764 | y18n@5.0.8: |
| 6719 | resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} | 6765 | resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} |
| 6720 | engines: {node: '>=10'} | 6766 | engines: {node: '>=10'} |
| ... | @@ -6727,10 +6773,18 @@ packages: | ... | @@ -6727,10 +6773,18 @@ packages: |
| 6727 | engines: {node: '>= 14.6'} | 6773 | engines: {node: '>= 14.6'} |
| 6728 | hasBin: true | 6774 | hasBin: true |
| 6729 | 6775 | ||
| 6776 | + yargs-parser@18.1.3: | ||
| 6777 | + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} | ||
| 6778 | + engines: {node: '>=6'} | ||
| 6779 | + | ||
| 6730 | yargs-parser@20.2.9: | 6780 | yargs-parser@20.2.9: |
| 6731 | resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} | 6781 | resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} |
| 6732 | engines: {node: '>=10'} | 6782 | engines: {node: '>=10'} |
| 6733 | 6783 | ||
| 6784 | + yargs@15.4.1: | ||
| 6785 | + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} | ||
| 6786 | + engines: {node: '>=8'} | ||
| 6787 | + | ||
| 6734 | yargs@16.2.0: | 6788 | yargs@16.2.0: |
| 6735 | resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} | 6789 | resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} |
| 6736 | engines: {node: '>=10'} | 6790 | engines: {node: '>=10'} |
| ... | @@ -9724,6 +9778,12 @@ snapshots: | ... | @@ -9724,6 +9778,12 @@ snapshots: |
| 9724 | 9778 | ||
| 9725 | cli-width@3.0.0: {} | 9779 | cli-width@3.0.0: {} |
| 9726 | 9780 | ||
| 9781 | + cliui@6.0.0: | ||
| 9782 | + dependencies: | ||
| 9783 | + string-width: 4.2.3 | ||
| 9784 | + strip-ansi: 6.0.1 | ||
| 9785 | + wrap-ansi: 6.2.0 | ||
| 9786 | + | ||
| 9727 | cliui@7.0.4: | 9787 | cliui@7.0.4: |
| 9728 | dependencies: | 9788 | dependencies: |
| 9729 | string-width: 4.2.3 | 9789 | string-width: 4.2.3 |
| ... | @@ -10065,6 +10125,8 @@ snapshots: | ... | @@ -10065,6 +10125,8 @@ snapshots: |
| 10065 | dependencies: | 10125 | dependencies: |
| 10066 | ms: 2.1.3 | 10126 | ms: 2.1.3 |
| 10067 | 10127 | ||
| 10128 | + decamelize@1.2.0: {} | ||
| 10129 | + | ||
| 10068 | decimal.js@10.6.0: {} | 10130 | decimal.js@10.6.0: {} |
| 10069 | 10131 | ||
| 10070 | decode-uri-component@0.2.2: {} | 10132 | decode-uri-component@0.2.2: {} |
| ... | @@ -10171,6 +10233,8 @@ snapshots: | ... | @@ -10171,6 +10233,8 @@ snapshots: |
| 10171 | 10233 | ||
| 10172 | didyoumean@1.2.2: {} | 10234 | didyoumean@1.2.2: {} |
| 10173 | 10235 | ||
| 10236 | + dijkstrajs@1.0.3: {} | ||
| 10237 | + | ||
| 10174 | dingtalk-jsapi@2.15.6: | 10238 | dingtalk-jsapi@2.15.6: |
| 10175 | dependencies: | 10239 | dependencies: |
| 10176 | promise-polyfill: 7.1.2 | 10240 | promise-polyfill: 7.1.2 |
| ... | @@ -10811,6 +10875,11 @@ snapshots: | ... | @@ -10811,6 +10875,11 @@ snapshots: |
| 10811 | dependencies: | 10875 | dependencies: |
| 10812 | locate-path: 3.0.0 | 10876 | locate-path: 3.0.0 |
| 10813 | 10877 | ||
| 10878 | + find-up@4.1.0: | ||
| 10879 | + dependencies: | ||
| 10880 | + locate-path: 5.0.0 | ||
| 10881 | + path-exists: 4.0.0 | ||
| 10882 | + | ||
| 10814 | find-up@5.0.0: | 10883 | find-up@5.0.0: |
| 10815 | dependencies: | 10884 | dependencies: |
| 10816 | locate-path: 6.0.0 | 10885 | locate-path: 6.0.0 |
| ... | @@ -11776,6 +11845,10 @@ snapshots: | ... | @@ -11776,6 +11845,10 @@ snapshots: |
| 11776 | p-locate: 3.0.0 | 11845 | p-locate: 3.0.0 |
| 11777 | path-exists: 3.0.0 | 11846 | path-exists: 3.0.0 |
| 11778 | 11847 | ||
| 11848 | + locate-path@5.0.0: | ||
| 11849 | + dependencies: | ||
| 11850 | + p-locate: 4.1.0 | ||
| 11851 | + | ||
| 11779 | locate-path@6.0.0: | 11852 | locate-path@6.0.0: |
| 11780 | dependencies: | 11853 | dependencies: |
| 11781 | p-locate: 5.0.0 | 11854 | p-locate: 5.0.0 |
| ... | @@ -12149,6 +12222,10 @@ snapshots: | ... | @@ -12149,6 +12222,10 @@ snapshots: |
| 12149 | dependencies: | 12222 | dependencies: |
| 12150 | p-limit: 2.3.0 | 12223 | p-limit: 2.3.0 |
| 12151 | 12224 | ||
| 12225 | + p-locate@4.1.0: | ||
| 12226 | + dependencies: | ||
| 12227 | + p-limit: 2.3.0 | ||
| 12228 | + | ||
| 12152 | p-locate@5.0.0: | 12229 | p-locate@5.0.0: |
| 12153 | dependencies: | 12230 | dependencies: |
| 12154 | p-limit: 3.1.0 | 12231 | p-limit: 3.1.0 |
| ... | @@ -12297,6 +12374,8 @@ snapshots: | ... | @@ -12297,6 +12374,8 @@ snapshots: |
| 12297 | 12374 | ||
| 12298 | platform@1.3.6: {} | 12375 | platform@1.3.6: {} |
| 12299 | 12376 | ||
| 12377 | + pngjs@5.0.0: {} | ||
| 12378 | + | ||
| 12300 | possible-typed-array-names@1.1.0: {} | 12379 | possible-typed-array-names@1.1.0: {} |
| 12301 | 12380 | ||
| 12302 | postcss-attribute-case-insensitive@7.0.1(postcss@8.5.6): | 12381 | postcss-attribute-case-insensitive@7.0.1(postcss@8.5.6): |
| ... | @@ -12858,6 +12937,12 @@ snapshots: | ... | @@ -12858,6 +12937,12 @@ snapshots: |
| 12858 | 12937 | ||
| 12859 | punycode@2.3.1: {} | 12938 | punycode@2.3.1: {} |
| 12860 | 12939 | ||
| 12940 | + qrcode@1.5.4: | ||
| 12941 | + dependencies: | ||
| 12942 | + dijkstrajs: 1.0.3 | ||
| 12943 | + pngjs: 5.0.0 | ||
| 12944 | + yargs: 15.4.1 | ||
| 12945 | + | ||
| 12861 | qs@6.14.1: | 12946 | qs@6.14.1: |
| 12862 | dependencies: | 12947 | dependencies: |
| 12863 | side-channel: 1.1.0 | 12948 | side-channel: 1.1.0 |
| ... | @@ -13004,6 +13089,8 @@ snapshots: | ... | @@ -13004,6 +13089,8 @@ snapshots: |
| 13004 | 13089 | ||
| 13005 | require-from-string@2.0.2: {} | 13090 | require-from-string@2.0.2: {} |
| 13006 | 13091 | ||
| 13092 | + require-main-filename@2.0.0: {} | ||
| 13093 | + | ||
| 13007 | requires-port@1.0.0: {} | 13094 | requires-port@1.0.0: {} |
| 13008 | 13095 | ||
| 13009 | resolve-from@4.0.0: {} | 13096 | resolve-from@4.0.0: {} |
| ... | @@ -13225,6 +13312,8 @@ snapshots: | ... | @@ -13225,6 +13312,8 @@ snapshots: |
| 13225 | transitivePeerDependencies: | 13312 | transitivePeerDependencies: |
| 13226 | - supports-color | 13313 | - supports-color |
| 13227 | 13314 | ||
| 13315 | + set-blocking@2.0.0: {} | ||
| 13316 | + | ||
| 13228 | set-function-length@1.2.2: | 13317 | set-function-length@1.2.2: |
| 13229 | dependencies: | 13318 | dependencies: |
| 13230 | define-data-property: 1.1.4 | 13319 | define-data-property: 1.1.4 |
| ... | @@ -14102,6 +14191,8 @@ snapshots: | ... | @@ -14102,6 +14191,8 @@ snapshots: |
| 14102 | is-weakmap: 2.0.2 | 14191 | is-weakmap: 2.0.2 |
| 14103 | is-weakset: 2.0.4 | 14192 | is-weakset: 2.0.4 |
| 14104 | 14193 | ||
| 14194 | + which-module@2.0.1: {} | ||
| 14195 | + | ||
| 14105 | which-typed-array@1.1.19: | 14196 | which-typed-array@1.1.19: |
| 14106 | dependencies: | 14197 | dependencies: |
| 14107 | available-typed-arrays: 1.0.7 | 14198 | available-typed-arrays: 1.0.7 |
| ... | @@ -14156,14 +14247,35 @@ snapshots: | ... | @@ -14156,14 +14247,35 @@ snapshots: |
| 14156 | dependencies: | 14247 | dependencies: |
| 14157 | cuint: 0.2.2 | 14248 | cuint: 0.2.2 |
| 14158 | 14249 | ||
| 14250 | + y18n@4.0.3: {} | ||
| 14251 | + | ||
| 14159 | y18n@5.0.8: {} | 14252 | y18n@5.0.8: {} |
| 14160 | 14253 | ||
| 14161 | yallist@3.1.1: {} | 14254 | yallist@3.1.1: {} |
| 14162 | 14255 | ||
| 14163 | yaml@2.8.2: {} | 14256 | yaml@2.8.2: {} |
| 14164 | 14257 | ||
| 14258 | + yargs-parser@18.1.3: | ||
| 14259 | + dependencies: | ||
| 14260 | + camelcase: 5.3.1 | ||
| 14261 | + decamelize: 1.2.0 | ||
| 14262 | + | ||
| 14165 | yargs-parser@20.2.9: {} | 14263 | yargs-parser@20.2.9: {} |
| 14166 | 14264 | ||
| 14265 | + yargs@15.4.1: | ||
| 14266 | + dependencies: | ||
| 14267 | + cliui: 6.0.0 | ||
| 14268 | + decamelize: 1.2.0 | ||
| 14269 | + find-up: 4.1.0 | ||
| 14270 | + get-caller-file: 2.0.5 | ||
| 14271 | + require-directory: 2.1.1 | ||
| 14272 | + require-main-filename: 2.0.0 | ||
| 14273 | + set-blocking: 2.0.0 | ||
| 14274 | + string-width: 4.2.3 | ||
| 14275 | + which-module: 2.0.1 | ||
| 14276 | + y18n: 4.0.3 | ||
| 14277 | + yargs-parser: 18.1.3 | ||
| 14278 | + | ||
| 14167 | yargs@16.2.0: | 14279 | yargs@16.2.0: |
| 14168 | dependencies: | 14280 | dependencies: |
| 14169 | cliui: 7.0.4 | 14281 | cliui: 7.0.4 | ... | ... |
| ... | @@ -24,6 +24,8 @@ export default { | ... | @@ -24,6 +24,8 @@ export default { |
| 24 | 'pages/visitorList/index', | 24 | 'pages/visitorList/index', |
| 25 | 'pages/volunteerLogin/index', | 25 | 'pages/volunteerLogin/index', |
| 26 | 'pages/verificationResult/index', | 26 | 'pages/verificationResult/index', |
| 27 | + 'pages/weakNetwork/index', | ||
| 28 | + 'pages/offlineBookingCode/index', | ||
| 27 | ], | 29 | ], |
| 28 | subpackages: [ // 配置在tabBar中的页面不能分包写到subpackages中去 | 30 | subpackages: [ // 配置在tabBar中的页面不能分包写到subpackages中去 |
| 29 | { | 31 | { | ... | ... |
| 1 | /* | 1 | /* |
| 2 | * @Date: 2025-06-28 10:33:00 | 2 | * @Date: 2025-06-28 10:33:00 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2025-06-28 11:04:17 | 4 | + * @LastEditTime: 2026-01-07 20:54:02 |
| 5 | - * @FilePath: /myApp/src/app.js | 5 | + * @FilePath: /xyxBooking-weapp/src/app.js |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | */ | 7 | */ |
| 8 | import { createApp } from 'vue' | 8 | import { createApp } from 'vue' |
| 9 | import { createPinia } from 'pinia' | 9 | import { createPinia } from 'pinia' |
| 10 | import './app.less' | 10 | import './app.less' |
| 11 | import { saveCurrentPagePath, needAuth, silentAuth, navigateToAuth } from '@/utils/authRedirect' | 11 | import { saveCurrentPagePath, needAuth, silentAuth, navigateToAuth } from '@/utils/authRedirect' |
| 12 | +import Taro from '@tarojs/taro' | ||
| 13 | +import { qrcodeListAPI } from '@/api/index' | ||
| 14 | +import { formatDatetime } from '@/utils/tools' | ||
| 15 | + | ||
| 16 | +const formatGroup = (data) => { | ||
| 17 | + let lastPayId = null; | ||
| 18 | + for (let i = 0; i < data.length; i++) { | ||
| 19 | + if (data[i].pay_id !== lastPayId) { | ||
| 20 | + data[i].sort = 1; | ||
| 21 | + lastPayId = data[i].pay_id; | ||
| 22 | + } else { | ||
| 23 | + data[i].sort = 0; | ||
| 24 | + } | ||
| 25 | + } | ||
| 26 | + return data; | ||
| 27 | +} | ||
| 12 | 28 | ||
| 13 | const App = createApp({ | 29 | const App = createApp({ |
| 14 | // 对应 onLaunch | 30 | // 对应 onLaunch |
| ... | @@ -25,12 +41,54 @@ const App = createApp({ | ... | @@ -25,12 +41,54 @@ const App = createApp({ |
| 25 | saveCurrentPagePath(full_path) | 41 | saveCurrentPagePath(full_path) |
| 26 | } | 42 | } |
| 27 | 43 | ||
| 44 | + const preloadQrData = async () => { | ||
| 45 | + try { | ||
| 46 | + const { code, data } = await qrcodeListAPI(); | ||
| 47 | + if (code && data) { | ||
| 48 | + data.forEach(item => { | ||
| 49 | + item.qr_code_url = '/admin?m=srv&a=get_qrcode&key=' + item.qr_code; | ||
| 50 | + item.datetime = formatDatetime({ begin_time: item.begin_time, end_time: item.end_time }) | ||
| 51 | + item.sort = 0; | ||
| 52 | + }); | ||
| 53 | + const validData = data.filter(item => item.qr_code !== ''); | ||
| 54 | + if (validData.length > 0) { | ||
| 55 | + const processed = formatGroup(validData); | ||
| 56 | + Taro.setStorageSync('OFFLINE_QR_DATA', processed); | ||
| 57 | + } else { | ||
| 58 | + Taro.removeStorageSync('OFFLINE_QR_DATA'); | ||
| 59 | + } | ||
| 60 | + } | ||
| 61 | + } catch (e) { | ||
| 62 | + console.error('Preload QR failed', e); | ||
| 63 | + } | ||
| 64 | + }; | ||
| 65 | + | ||
| 66 | + const checkNetworkAndPreload = () => { | ||
| 67 | + Taro.getNetworkType({ | ||
| 68 | + success: (res) => { | ||
| 69 | + const isConnected = ['wifi', '4g', '5g', '3g'].includes(res.networkType); | ||
| 70 | + if (isConnected) { | ||
| 71 | + preloadQrData(); | ||
| 72 | + } | ||
| 73 | + } | ||
| 74 | + }); | ||
| 75 | + }; | ||
| 76 | + | ||
| 77 | + Taro.onNetworkStatusChange((res) => { | ||
| 78 | + if (res.isConnected) { | ||
| 79 | + preloadQrData(); | ||
| 80 | + } | ||
| 81 | + }); | ||
| 82 | + | ||
| 83 | + checkNetworkAndPreload(); | ||
| 84 | + | ||
| 28 | if (!needAuth()) return | 85 | if (!needAuth()) return |
| 29 | 86 | ||
| 30 | if (path === 'pages/auth/index') return | 87 | if (path === 'pages/auth/index') return |
| 31 | 88 | ||
| 32 | try { | 89 | try { |
| 33 | await silentAuth() | 90 | await silentAuth() |
| 91 | + checkNetworkAndPreload(); | ||
| 34 | } catch (error) { | 92 | } catch (error) { |
| 35 | navigateToAuth(full_path || undefined) | 93 | navigateToAuth(full_path || undefined) |
| 36 | } | 94 | } | ... | ... |
src/assets/images/右1@2x.png
0 → 100644
990 Bytes
src/assets/images/左1@2x.png
0 → 100644
1011 Bytes
src/assets/images/暂无1@2x.png
0 → 100644
16.6 KB
src/components/offlineQrCode.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <view class="qr-code-page"> | ||
| 3 | + <view v-if="userList.length" class="show-qrcode"> | ||
| 4 | + <view class="qrcode-content"> | ||
| 5 | + <view class="user-info">{{ userinfo.name }} {{ userinfo.id }}</view> | ||
| 6 | + <view class="user-qrcode"> | ||
| 7 | + <view class="left" @tap="prevCode"> | ||
| 8 | + <image :src="icon_1" /> | ||
| 9 | + </view> | ||
| 10 | + <view class="center"> | ||
| 11 | + <!-- 离线模式直接显示生成的 base64 图片 --> | ||
| 12 | + <image :src="currentQrCodeUrl" mode="aspectFit" /> | ||
| 13 | + <!-- 离线模式不显示状态覆盖层,因为无法获取最新状态 --> | ||
| 14 | + </view> | ||
| 15 | + <view class="right" @tap="nextCode"> | ||
| 16 | + <image :src="icon_2" /> | ||
| 17 | + </view> | ||
| 18 | + </view> | ||
| 19 | + <view style="color: red; margin-top: 32rpx;">{{ userinfo.datetime }}</view> | ||
| 20 | + <view style="color: #999; font-size: 24rpx; margin-top: 10rpx;">(离线模式)</view> | ||
| 21 | + </view> | ||
| 22 | + <view class="user-list"> | ||
| 23 | + <view | ||
| 24 | + @tap="selectUser(index)" | ||
| 25 | + v-for="(item, index) in userList" | ||
| 26 | + :key="index" | ||
| 27 | + :class="[ | ||
| 28 | + 'user-item', | ||
| 29 | + select_index === index ? 'checked' : '', | ||
| 30 | + userList.length > 1 && item.sort ? 'border' : '', | ||
| 31 | + ]"> | ||
| 32 | + {{ item.name }} | ||
| 33 | + </view> | ||
| 34 | + </view> | ||
| 35 | + </view> | ||
| 36 | + <view v-else class="no-qrcode"> | ||
| 37 | + <image :src="icon_3" style="width: 320rpx; height: 320rpx;" /> | ||
| 38 | + <view class="no-qrcode-title">本地无缓存预约记录</view> | ||
| 39 | + </view> | ||
| 40 | + </view> | ||
| 41 | +</template> | ||
| 42 | + | ||
| 43 | +<script setup> | ||
| 44 | +import { ref, computed, watch, onMounted } from 'vue' | ||
| 45 | +import Taro from '@tarojs/taro' | ||
| 46 | +import QRCode from 'qrcode' | ||
| 47 | + | ||
| 48 | +import icon_1 from '@/assets/images/左1@2x.png' | ||
| 49 | +import icon_2 from '@/assets/images/右1@2x.png' | ||
| 50 | +import icon_3 from '@/assets/images/暂无1@2x.png' | ||
| 51 | + | ||
| 52 | +const props = defineProps({ | ||
| 53 | + list: { | ||
| 54 | + type: Array, | ||
| 55 | + default: () => [] | ||
| 56 | + } | ||
| 57 | +}); | ||
| 58 | + | ||
| 59 | +const select_index = ref(0); | ||
| 60 | +const userList = ref([]); | ||
| 61 | +const qrCodeImages = ref({}); // 存储生成的二维码图片 base64 | ||
| 62 | + | ||
| 63 | +const prevCode = () => { | ||
| 64 | + select_index.value = select_index.value - 1; | ||
| 65 | + if (select_index.value < 0) { | ||
| 66 | + select_index.value = userList.value.length - 1; | ||
| 67 | + } | ||
| 68 | +}; | ||
| 69 | +const nextCode = () => { | ||
| 70 | + select_index.value = select_index.value + 1; | ||
| 71 | + if (select_index.value > userList.value.length - 1) { | ||
| 72 | + select_index.value = 0; | ||
| 73 | + } | ||
| 74 | +}; | ||
| 75 | + | ||
| 76 | +function replaceMiddleCharacters(inputString) { | ||
| 77 | + if (!inputString || inputString.length < 15) { | ||
| 78 | + return inputString; | ||
| 79 | + } | ||
| 80 | + const start = Math.floor((inputString.length - 8) / 2); | ||
| 81 | + const end = start + 8; | ||
| 82 | + const replacement = '*'.repeat(8); | ||
| 83 | + return inputString.substring(0, start) + replacement + inputString.substring(end); | ||
| 84 | +} | ||
| 85 | + | ||
| 86 | +const formatId = (id) => replaceMiddleCharacters(id); | ||
| 87 | + | ||
| 88 | +const userinfo = computed(() => { | ||
| 89 | + return { | ||
| 90 | + name: userList.value[select_index.value]?.name, | ||
| 91 | + id: formatId(userList.value[select_index.value]?.id_number), | ||
| 92 | + datetime: userList.value[select_index.value]?.datetime, | ||
| 93 | + }; | ||
| 94 | +}); | ||
| 95 | + | ||
| 96 | +const currentQrCodeUrl = computed(() => { | ||
| 97 | + const key = userList.value[select_index.value]?.qr_code; | ||
| 98 | + return qrCodeImages.value[key] || ''; | ||
| 99 | +}) | ||
| 100 | + | ||
| 101 | +const selectUser = (index) => { | ||
| 102 | + select_index.value = index; | ||
| 103 | +} | ||
| 104 | + | ||
| 105 | +const generateQrCodes = () => { | ||
| 106 | + for (const item of userList.value) { | ||
| 107 | + if (item.qr_code && !qrCodeImages.value[item.qr_code]) { | ||
| 108 | + try { | ||
| 109 | + // 使用 create + SVG 手动生成,避免 Taro 中 Canvas 依赖问题 | ||
| 110 | + const qr = QRCode.create(item.qr_code, { errorCorrectionLevel: 'M' }); | ||
| 111 | + const size = qr.modules.size; | ||
| 112 | + let d = ''; | ||
| 113 | + for (let row = 0; row < size; row++) { | ||
| 114 | + for (let col = 0; col < size; col++) { | ||
| 115 | + if (qr.modules.get(col, row)) { | ||
| 116 | + d += `M${col},${row}h1v1h-1z`; | ||
| 117 | + } | ||
| 118 | + } | ||
| 119 | + } | ||
| 120 | + const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${size} ${size}"><path d="${d}" fill="#000"/></svg>`; | ||
| 121 | + | ||
| 122 | + // 转 Base64 | ||
| 123 | + const buffer = new ArrayBuffer(svg.length); | ||
| 124 | + const view = new Uint8Array(buffer); | ||
| 125 | + for (let i = 0; i < svg.length; i++) { | ||
| 126 | + view[i] = svg.charCodeAt(i); | ||
| 127 | + } | ||
| 128 | + const b64 = Taro.arrayBufferToBase64(buffer); | ||
| 129 | + | ||
| 130 | + qrCodeImages.value[item.qr_code] = `data:image/svg+xml;base64,${b64}`; | ||
| 131 | + } catch (err) { | ||
| 132 | + console.error('QR Gen Error', err); | ||
| 133 | + } | ||
| 134 | + } | ||
| 135 | + } | ||
| 136 | +} | ||
| 137 | + | ||
| 138 | +onMounted(() => { | ||
| 139 | + if (props.list && props.list.length > 0) { | ||
| 140 | + userList.value = props.list; | ||
| 141 | + generateQrCodes(); | ||
| 142 | + } | ||
| 143 | +}); | ||
| 144 | + | ||
| 145 | +watch(() => props.list, (newVal) => { | ||
| 146 | + if (newVal && newVal.length > 0) { | ||
| 147 | + userList.value = newVal; | ||
| 148 | + generateQrCodes(); | ||
| 149 | + } | ||
| 150 | +}, { deep: true }); | ||
| 151 | + | ||
| 152 | +</script> | ||
| 153 | + | ||
| 154 | +<style lang="less"> | ||
| 155 | +.qr-code-page { | ||
| 156 | + .qrcode-content { | ||
| 157 | + padding: 32rpx 0; | ||
| 158 | + display: flex; | ||
| 159 | + flex-direction: column; | ||
| 160 | + justify-content: center; | ||
| 161 | + align-items: center; | ||
| 162 | + background-color: #FFF; | ||
| 163 | + border-radius: 16rpx; | ||
| 164 | + box-shadow: 0 0 29rpx 0 rgba(106,106,106,0.27); | ||
| 165 | + | ||
| 166 | + .user-info { | ||
| 167 | + color: #A6A6A6; | ||
| 168 | + font-size: 37rpx; | ||
| 169 | + margin-top: 16rpx; | ||
| 170 | + margin-bottom: 16rpx; | ||
| 171 | + } | ||
| 172 | + .user-qrcode { | ||
| 173 | + display: flex; | ||
| 174 | + align-items: center; | ||
| 175 | + .left { | ||
| 176 | + image { | ||
| 177 | + width: 56rpx; height: 56rpx; margin-right: 16rpx; | ||
| 178 | + } | ||
| 179 | + } | ||
| 180 | + .center { | ||
| 181 | + border: 2rpx solid #D1D1D1; | ||
| 182 | + border-radius: 40rpx; | ||
| 183 | + padding: 16rpx; | ||
| 184 | + position: relative; | ||
| 185 | + image { | ||
| 186 | + width: 480rpx; height: 480rpx; | ||
| 187 | + } | ||
| 188 | + } | ||
| 189 | + .right { | ||
| 190 | + image { | ||
| 191 | + width: 56rpx; height: 56rpx; | ||
| 192 | + margin-left: 16rpx; | ||
| 193 | + } | ||
| 194 | + } | ||
| 195 | + } | ||
| 196 | + } | ||
| 197 | + .user-list { | ||
| 198 | + display: flex; | ||
| 199 | + padding: 32rpx; | ||
| 200 | + align-items: center; | ||
| 201 | + flex-wrap: wrap; | ||
| 202 | + .user-item { | ||
| 203 | + position: relative; | ||
| 204 | + padding: 8rpx 16rpx; | ||
| 205 | + border: 2rpx solid #A67939; | ||
| 206 | + margin: 8rpx; | ||
| 207 | + border-radius: 10rpx; | ||
| 208 | + color: #A67939; | ||
| 209 | + &.checked { | ||
| 210 | + color: #FFF; | ||
| 211 | + background-color: #A67939; | ||
| 212 | + } | ||
| 213 | + &.border { | ||
| 214 | + margin-right: 16rpx; | ||
| 215 | + &::after { | ||
| 216 | + position: absolute; | ||
| 217 | + right: -16rpx; | ||
| 218 | + top: calc(50% - 16rpx); | ||
| 219 | + content: ''; | ||
| 220 | + height: 32rpx; | ||
| 221 | + border-right: 2rpx solid #A67939; | ||
| 222 | + } | ||
| 223 | + } | ||
| 224 | + } | ||
| 225 | + } | ||
| 226 | + | ||
| 227 | + .no-qrcode { | ||
| 228 | + display: flex; | ||
| 229 | + justify-content: center; | ||
| 230 | + align-items: center; | ||
| 231 | + flex-direction: column; | ||
| 232 | + margin-bottom: 32rpx; | ||
| 233 | + | ||
| 234 | + .no-qrcode-title { | ||
| 235 | + color: #A67939; | ||
| 236 | + font-size: 34rpx; | ||
| 237 | + } | ||
| 238 | + } | ||
| 239 | +} | ||
| 240 | +</style> |
| 1 | <!-- | 1 | <!-- |
| 2 | * @Date: 2024-01-16 10:06:47 | 2 | * @Date: 2024-01-16 10:06:47 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2024-12-26 11:15:45 | 4 | + * @LastEditTime: 2026-01-07 20:54:13 |
| 5 | * @FilePath: /xyxBooking-weapp/src/components/qrCode.vue | 5 | * @FilePath: /xyxBooking-weapp/src/components/qrCode.vue |
| 6 | * @Description: 预约码卡组件 | 6 | * @Description: 预约码卡组件 |
| 7 | --> | 7 | --> |
| ... | @@ -168,7 +168,9 @@ const formatGroup = (data) => { | ... | @@ -168,7 +168,9 @@ const formatGroup = (data) => { |
| 168 | 168 | ||
| 169 | const init = async () => { | 169 | const init = async () => { |
| 170 | if (!props.type) { | 170 | if (!props.type) { |
| 171 | + try { | ||
| 171 | const { code, data } = await qrcodeListAPI(); | 172 | const { code, data } = await qrcodeListAPI(); |
| 173 | + | ||
| 172 | if (code) { | 174 | if (code) { |
| 173 | data.forEach(item => { | 175 | data.forEach(item => { |
| 174 | item.qr_code_url = '/admin?m=srv&a=get_qrcode&key=' + item.qr_code; | 176 | item.qr_code_url = '/admin?m=srv&a=get_qrcode&key=' + item.qr_code; |
| ... | @@ -180,10 +182,17 @@ const init = async () => { | ... | @@ -180,10 +182,17 @@ const init = async () => { |
| 180 | 182 | ||
| 181 | if (validData.length > 0) { | 183 | if (validData.length > 0) { |
| 182 | userList.value = formatGroup(validData); | 184 | userList.value = formatGroup(validData); |
| 185 | + // 缓存数据供离线模式使用 | ||
| 186 | + Taro.setStorageSync('OFFLINE_QR_DATA', userList.value); | ||
| 183 | refreshBtn(); | 187 | refreshBtn(); |
| 184 | } else { | 188 | } else { |
| 185 | userList.value = []; | 189 | userList.value = []; |
| 190 | + // 清空缓存 | ||
| 191 | + Taro.removeStorageSync('OFFLINE_QR_DATA'); | ||
| 192 | + } | ||
| 186 | } | 193 | } |
| 194 | + } catch (err) { | ||
| 195 | + console.error('Fetch QR List Failed:', err); | ||
| 187 | } | 196 | } |
| 188 | } else { | 197 | } else { |
| 189 | if (props.payId) { | 198 | if (props.payId) { | ... | ... |
| 1 | <!-- | 1 | <!-- |
| 2 | * @Date: 2024-01-16 10:06:47 | 2 | * @Date: 2024-01-16 10:06:47 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2024-01-30 17:48:42 | 4 | + * @LastEditTime: 2026-01-07 21:08:16 |
| 5 | * @FilePath: /xyxBooking-weapp/src/pages/bookingCode/index.vue | 5 | * @FilePath: /xyxBooking-weapp/src/pages/bookingCode/index.vue |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | --> | 7 | --> |
| ... | @@ -34,7 +34,7 @@ | ... | @@ -34,7 +34,7 @@ |
| 34 | 34 | ||
| 35 | <script setup> | 35 | <script setup> |
| 36 | import { ref } from 'vue' | 36 | import { ref } from 'vue' |
| 37 | -import Taro from '@tarojs/taro' | 37 | +import Taro, { useDidShow } from '@tarojs/taro' |
| 38 | import qrCode from '@/components/qrCode'; | 38 | import qrCode from '@/components/qrCode'; |
| 39 | import { IconFont } from '@nutui/icons-vue-taro' | 39 | import { IconFont } from '@nutui/icons-vue-taro' |
| 40 | import icon_3 from '@/assets/images/首页01@2x.png' | 40 | import icon_3 from '@/assets/images/首页01@2x.png' |
| ... | @@ -44,6 +44,17 @@ import { useGo } from '@/hooks/useGo' | ... | @@ -44,6 +44,17 @@ import { useGo } from '@/hooks/useGo' |
| 44 | 44 | ||
| 45 | const go = useGo(); | 45 | const go = useGo(); |
| 46 | 46 | ||
| 47 | +useDidShow(() => { | ||
| 48 | + Taro.getNetworkType({ | ||
| 49 | + success: (res) => { | ||
| 50 | + const isConnected = ['wifi', '4g', '5g', '3g'].includes(res.networkType); | ||
| 51 | + if (!isConnected) { | ||
| 52 | + go('/pages/weakNetwork/index'); | ||
| 53 | + } | ||
| 54 | + } | ||
| 55 | + }); | ||
| 56 | +}) | ||
| 57 | + | ||
| 47 | const toMy = () => { // 跳转到我的 | 58 | const toMy = () => { // 跳转到我的 |
| 48 | go('/pages/me/index'); | 59 | go('/pages/me/index'); |
| 49 | } | 60 | } | ... | ... |
src/pages/offlineBookingCode/index.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <view class="offline-booking-code-page"> | ||
| 3 | + <view class="header-tip"> | ||
| 4 | + <IconFont name="tips" size="15" color="#A67939" /> | ||
| 5 | + <text>您当前处于离线模式,仅展示本地缓存的预约码</text> | ||
| 6 | + </view> | ||
| 7 | + | ||
| 8 | + <view style="padding: 32rpx;"> | ||
| 9 | + <offlineQrCode :list="qrList"></offlineQrCode> | ||
| 10 | + <view class="warning"> | ||
| 11 | + <view> | ||
| 12 | + 温馨提示 | ||
| 13 | + </view> | ||
| 14 | + <view style="margin-top: 16rpx;">一人一码,扫码或识别身份证成功后进入</view> | ||
| 15 | + </view> | ||
| 16 | + </view> | ||
| 17 | + | ||
| 18 | + <view class="action-area"> | ||
| 19 | + <button class="home-btn" @tap="toHome">返回首页</button> | ||
| 20 | + </view> | ||
| 21 | + </view> | ||
| 22 | +</template> | ||
| 23 | + | ||
| 24 | +<script setup> | ||
| 25 | +import { ref, onMounted } from 'vue' | ||
| 26 | +import Taro from '@tarojs/taro' | ||
| 27 | +import offlineQrCode from '@/components/offlineQrCode'; | ||
| 28 | +import { IconFont } from '@nutui/icons-vue-taro' | ||
| 29 | +import { useGo } from '@/hooks/useGo' | ||
| 30 | + | ||
| 31 | +const go = useGo(); | ||
| 32 | +const qrList = ref([]); | ||
| 33 | + | ||
| 34 | +const toHome = () => { | ||
| 35 | + Taro.reLaunch({ url: '/pages/index/index' }); | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +// Mock Data as per requirement | ||
| 39 | +const getMockData = () => { | ||
| 40 | + return [ | ||
| 41 | + { | ||
| 42 | + name: '测试用户1', | ||
| 43 | + id_number: '110101199003078888', | ||
| 44 | + qr_code: 'OFFLINE_MOCK_QR_001', | ||
| 45 | + datetime: '2026-01-08 08:30-10:30', | ||
| 46 | + sort: 0 | ||
| 47 | + }, | ||
| 48 | + { | ||
| 49 | + name: '测试用户2', | ||
| 50 | + id_number: '110101199205126666', | ||
| 51 | + qr_code: 'OFFLINE_MOCK_QR_002', | ||
| 52 | + datetime: '2026-01-08 08:30-10:30', | ||
| 53 | + sort: 0 | ||
| 54 | + } | ||
| 55 | + ]; | ||
| 56 | +} | ||
| 57 | + | ||
| 58 | +onMounted(() => { | ||
| 59 | + try { | ||
| 60 | + const cachedData = Taro.getStorageSync('OFFLINE_QR_DATA'); | ||
| 61 | + if (cachedData && cachedData.length > 0) { | ||
| 62 | + qrList.value = cachedData; | ||
| 63 | + } else { | ||
| 64 | + // Requirement 4: Mock data if no data | ||
| 65 | + console.log('No cached data found, using mock data'); | ||
| 66 | + qrList.value = getMockData(); | ||
| 67 | + } | ||
| 68 | + } catch (e) { | ||
| 69 | + console.error('Read storage failed', e); | ||
| 70 | + qrList.value = getMockData(); | ||
| 71 | + } | ||
| 72 | +}); | ||
| 73 | + | ||
| 74 | +</script> | ||
| 75 | + | ||
| 76 | +<style lang="less"> | ||
| 77 | +.offline-booking-code-page { | ||
| 78 | + position: relative; | ||
| 79 | + min-height: 100vh; | ||
| 80 | + background-color: #F6F6F6; | ||
| 81 | + | ||
| 82 | + .header-tip { | ||
| 83 | + background-color: #FEF8E8; | ||
| 84 | + color: #A67939; | ||
| 85 | + padding: 20rpx 32rpx; | ||
| 86 | + font-size: 26rpx; | ||
| 87 | + display: flex; | ||
| 88 | + align-items: center; | ||
| 89 | + | ||
| 90 | + text { | ||
| 91 | + margin-left: 10rpx; | ||
| 92 | + } | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + .warning { | ||
| 96 | + text-align: center; | ||
| 97 | + color: #A67939; | ||
| 98 | + margin-top: 32rpx; | ||
| 99 | + } | ||
| 100 | + | ||
| 101 | + .action-area { | ||
| 102 | + position: fixed; | ||
| 103 | + bottom: 60rpx; | ||
| 104 | + left: 0; | ||
| 105 | + width: 100%; | ||
| 106 | + display: flex; | ||
| 107 | + justify-content: center; | ||
| 108 | + | ||
| 109 | + .home-btn { | ||
| 110 | + width: 600rpx; | ||
| 111 | + height: 88rpx; | ||
| 112 | + line-height: 88rpx; | ||
| 113 | + background: #fff; | ||
| 114 | + color: #A67939; | ||
| 115 | + border: 2rpx solid #A67939; | ||
| 116 | + border-radius: 44rpx; | ||
| 117 | + font-size: 32rpx; | ||
| 118 | + } | ||
| 119 | + } | ||
| 120 | +} | ||
| 121 | +</style> |
src/pages/weakNetwork/index.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <view class="weak-network-page"> | ||
| 3 | + <view class="content"> | ||
| 4 | + <view class="icon-wrapper"> | ||
| 5 | + <IconFont name="mask-close" size="120" color="#ccc" /> | ||
| 6 | + </view> | ||
| 7 | + <view class="title">网络连接不畅</view> | ||
| 8 | + <view class="desc">当前网络信号较弱,已自动为您切换至离线模式</view> | ||
| 9 | + | ||
| 10 | + <view class="offline-entry" @tap="toOfflineCode"> | ||
| 11 | + <view class="circle-btn"> | ||
| 12 | + <text>预约码</text> | ||
| 13 | + </view> | ||
| 14 | + </view> | ||
| 15 | + | ||
| 16 | + <view class="sub-action" @tap="retry"> | ||
| 17 | + <text>尝试刷新重试</text> | ||
| 18 | + </view> | ||
| 19 | + </view> | ||
| 20 | + </view> | ||
| 21 | +</template> | ||
| 22 | + | ||
| 23 | +<script setup> | ||
| 24 | +import Taro from '@tarojs/taro' | ||
| 25 | +import { IconFont } from '@nutui/icons-vue-taro' | ||
| 26 | +import { useGo } from '@/hooks/useGo' | ||
| 27 | + | ||
| 28 | +const go = useGo(); | ||
| 29 | + | ||
| 30 | +const toOfflineCode = () => { | ||
| 31 | + go('/pages/offlineBookingCode/index'); | ||
| 32 | +} | ||
| 33 | + | ||
| 34 | +const retry = () => { | ||
| 35 | + // 尝试重新加载当前页或者是返回上一页重试 | ||
| 36 | + // 这里简单做成返回首页 | ||
| 37 | + Taro.reLaunch({ url: '/pages/index/index' }); | ||
| 38 | +} | ||
| 39 | +</script> | ||
| 40 | + | ||
| 41 | +<style lang="less"> | ||
| 42 | +.weak-network-page { | ||
| 43 | + min-height: 100vh; | ||
| 44 | + background-color: #fff; | ||
| 45 | + display: flex; | ||
| 46 | + flex-direction: column; | ||
| 47 | + align-items: center; | ||
| 48 | + justify-content: center; | ||
| 49 | + | ||
| 50 | + .content { | ||
| 51 | + display: flex; | ||
| 52 | + flex-direction: column; | ||
| 53 | + align-items: center; | ||
| 54 | + margin-top: -100rpx; | ||
| 55 | + | ||
| 56 | + .icon-wrapper { | ||
| 57 | + margin-bottom: 40rpx; | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + .title { | ||
| 61 | + font-size: 40rpx; | ||
| 62 | + color: #333; | ||
| 63 | + font-weight: bold; | ||
| 64 | + margin-bottom: 20rpx; | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + .desc { | ||
| 68 | + font-size: 28rpx; | ||
| 69 | + color: #999; | ||
| 70 | + margin-bottom: 80rpx; | ||
| 71 | + text-align: center; | ||
| 72 | + padding: 0 60rpx; | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + .offline-entry { | ||
| 76 | + margin-bottom: 60rpx; | ||
| 77 | + .circle-btn { | ||
| 78 | + width: 240rpx; | ||
| 79 | + height: 240rpx; | ||
| 80 | + border-radius: 50%; | ||
| 81 | + background: linear-gradient(135deg, #A67939 0%, #C69C5C 100%); | ||
| 82 | + display: flex; | ||
| 83 | + align-items: center; | ||
| 84 | + justify-content: center; | ||
| 85 | + box-shadow: 0 10rpx 30rpx rgba(166, 121, 57, 0.4); | ||
| 86 | + | ||
| 87 | + text { | ||
| 88 | + color: #fff; | ||
| 89 | + font-size: 40rpx; | ||
| 90 | + font-weight: bold; | ||
| 91 | + letter-spacing: 2rpx; | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + &:active { | ||
| 95 | + transform: scale(0.95); | ||
| 96 | + } | ||
| 97 | + } | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + .sub-action { | ||
| 101 | + padding: 20rpx; | ||
| 102 | + text { | ||
| 103 | + color: #A67939; | ||
| 104 | + font-size: 28rpx; | ||
| 105 | + text-decoration: underline; | ||
| 106 | + } | ||
| 107 | + } | ||
| 108 | + } | ||
| 109 | +} | ||
| 110 | +</style> |
-
Please register or login to post a comment