hookehuyr

feat(离线预约): 新增离线预约记录和详情功能

添加离线预约记录列表和详情页面,重构离线数据缓存逻辑
修改弱网页面跳转逻辑,移除旧的离线预约码功能
新增离线预约缓存管理工具函数
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:05:47 4 + * @LastEditTime: 2026-01-13 14:56:43
5 - * @FilePath: /myApp/src/app.config.js 5 + * @FilePath: /xyxBooking-weapp/src/app.config.js
6 - * @Description: 文件描述 6 + * @Description: 小程序配置文件
7 */ 7 */
8 export default { 8 export default {
9 pages: [ 9 pages: [
...@@ -26,6 +26,8 @@ export default { ...@@ -26,6 +26,8 @@ export default {
26 'pages/verificationResult/index', 26 'pages/verificationResult/index',
27 'pages/weakNetwork/index', 27 'pages/weakNetwork/index',
28 'pages/offlineBookingCode/index', 28 'pages/offlineBookingCode/index',
29 + 'pages/offlineBookingList/index',
30 + 'pages/offlineBookingDetail/index',
29 'pages/nfcTest/index', 31 'pages/nfcTest/index',
30 'pages/tailwindTest/index', 32 'pages/tailwindTest/index',
31 ], 33 ],
......
...@@ -11,29 +11,10 @@ import './utils/polyfill' ...@@ -11,29 +11,10 @@ import './utils/polyfill'
11 import './app.less' 11 import './app.less'
12 import { saveCurrentPagePath, hasAuth, silentAuth, navigateToAuth } from '@/utils/authRedirect' 12 import { saveCurrentPagePath, hasAuth, silentAuth, navigateToAuth } from '@/utils/authRedirect'
13 import Taro from '@tarojs/taro' 13 import Taro from '@tarojs/taro'
14 -import { qrcodeListAPI } from '@/api/index' 14 +import { refresh_offline_booking_cache, has_offline_booking_cache } from '@/composables/useOfflineBookingCache'
15 -import { formatDatetime } from '@/utils/tools'
16 15
17 let has_shown_network_modal = false 16 let has_shown_network_modal = false
18 17
19 -/**
20 - * 格式化支付记录,按 pay_id 分组,相同 pay_id 下的记录 sort 为 0,否则为 1
21 - * @param {*} data 支付记录数组
22 - * @returns 格式化后的支付记录数组
23 - */
24 -const formatGroup = (data) => {
25 - let lastPayId = null;
26 - for (let i = 0; i < data.length; i++) {
27 - if (data[i].pay_id !== lastPayId) {
28 - data[i].sort = 1;
29 - lastPayId = data[i].pay_id;
30 - } else {
31 - data[i].sort = 0;
32 - }
33 - }
34 - return data;
35 -}
36 -
37 const App = createApp({ 18 const App = createApp({
38 // 对应 onLaunch 19 // 对应 onLaunch
39 async onLaunch(options) { 20 async onLaunch(options) {
...@@ -51,49 +32,25 @@ const App = createApp({ ...@@ -51,49 +32,25 @@ const App = createApp({
51 } 32 }
52 33
53 /** 34 /**
54 - * 预加载二维码数据 35 + * 预加载离线预约记录数据(列表+详情)
55 - * - 仅在有网络连接时调用 36 + * - 仅在有授权且网络可用时调用
56 - * - 成功后将数据存储到本地缓存(key: OFFLINE_QR_DATA) 37 + * - 成功后将数据存储到本地缓存(key: OFFLINE_BOOKING_DATA)
57 - * - 失败则移除缓存
58 */ 38 */
59 - const preloadQrData = async () => { 39 + const preloadBookingData = async () => {
60 try { 40 try {
61 - const { code, data } = await qrcodeListAPI(); 41 + await refresh_offline_booking_cache()
62 - if (code && data) {
63 - data.forEach(item => {
64 - item.datetime = formatDatetime({ begin_time: item.begin_time, end_time: item.end_time })
65 - item.sort = 0;
66 - });
67 - const validData = data.filter(item => item.qr_code !== '');
68 - if (validData.length > 0) {
69 - const processed = formatGroup(validData);
70 - const offline_data = processed.map(item => ({
71 - name: item.name,
72 - id_number: item.id_number,
73 - qr_code: item.qr_code,
74 - begin_time: item.begin_time,
75 - end_time: item.end_time,
76 - datetime: item.datetime,
77 - pay_id: item.pay_id,
78 - sort: item.sort,
79 - }));
80 - Taro.setStorageSync('OFFLINE_QR_DATA', offline_data);
81 - } else {
82 - Taro.removeStorageSync('OFFLINE_QR_DATA');
83 - }
84 - }
85 } catch (e) { 42 } catch (e) {
86 - console.error('Preload QR failed', e); 43 + console.error('Preload booking cache failed', e)
87 } 44 }
88 - }; 45 + }
89 46
90 /** 47 /**
91 * 监听网络状态变化 48 * 监听网络状态变化
92 - * - 当网络连接且有授权时,调用 preloadQrData 预加载二维码数据 49 + * - 当网络连接且有授权时,预加载离线预约记录数据
93 */ 50 */
94 Taro.onNetworkStatusChange((res) => { 51 Taro.onNetworkStatusChange((res) => {
95 if (res.isConnected && hasAuth()) { 52 if (res.isConnected && hasAuth()) {
96 - preloadQrData() 53 + preloadBookingData()
97 } 54 }
98 }) 55 })
99 56
...@@ -120,23 +77,10 @@ const App = createApp({ ...@@ -120,23 +77,10 @@ const App = createApp({
120 } 77 }
121 78
122 /** 79 /**
123 - * 检查是否有离线预约码缓存
124 - * @returns {boolean} 如果有离线预约码缓存且有效,则返回 true;否则返回 false
125 - */
126 - const has_offline_qr_cache = () => {
127 - try {
128 - const data = Taro.getStorageSync('OFFLINE_QR_DATA')
129 - return Array.isArray(data) && data.length > 0
130 - } catch (e) {
131 - return false
132 - }
133 - }
134 -
135 - /**
136 * 处理在启动时出现的不良网络情况 80 * 处理在启动时出现的不良网络情况
137 - * - 当网络连接不良且有离线预约码缓存时,提示用户是否使用缓存预约码进入离线模式 81 + * - 当网络连接不良且有离线预约记录缓存时,提示用户是否使用缓存进入离线模式
138 - * - 当网络连接不良且无离线预约码缓存时,提示用户网络连接不畅 82 + * - 当网络连接不良且无缓存时,提示用户网络连接不畅
139 - * @returns {Promise<boolean>} 如果用户选择使用缓存预约码进入离线模式,则返回 true;否则返回 false 83 + * @returns {Promise<boolean>} 如果用户选择进入离线模式,则返回 true;否则返回 false
140 */ 84 */
141 const handle_bad_network_on_launch = async () => { 85 const handle_bad_network_on_launch = async () => {
142 /** 86 /**
...@@ -153,16 +97,16 @@ const App = createApp({ ...@@ -153,16 +97,16 @@ const App = createApp({
153 97
154 has_shown_network_modal = true 98 has_shown_network_modal = true
155 99
156 - if (has_offline_qr_cache()) { 100 + if (has_offline_booking_cache()) {
157 try { 101 try {
158 const modal_res = await Taro.showModal({ 102 const modal_res = await Taro.showModal({
159 title: '网络连接不畅', 103 title: '网络连接不畅',
160 - content: '当前网络信号较弱,可使用已缓存的预约进入离线模式', 104 + content: '当前网络信号较弱,可使用已缓存的预约记录进入离线模式',
161 - confirmText: '预约', 105 + confirmText: '预约记录',
162 cancelText: '知道了', 106 cancelText: '知道了',
163 }) 107 })
164 if (modal_res?.confirm) { 108 if (modal_res?.confirm) {
165 - await Taro.reLaunch({ url: '/pages/offlineBookingCode/index' }) 109 + await Taro.reLaunch({ url: '/pages/offlineBookingList/index' })
166 return true 110 return true
167 } 111 }
168 } catch (e) { 112 } catch (e) {
...@@ -183,10 +127,8 @@ const App = createApp({ ...@@ -183,10 +127,8 @@ const App = createApp({
183 if (should_stop) return 127 if (should_stop) return
184 128
185 /** 129 /**
186 - * 尝试在网络可用时预加载二维码数据 130 + * 尝试在网络可用时预加载离线预约记录数据
187 * - 仅在有授权时调用 131 * - 仅在有授权时调用
188 - * - 成功后将数据存储到本地缓存(key: OFFLINE_QR_DATA)
189 - * - 失败则移除缓存
190 * @returns {Promise<void>} 132 * @returns {Promise<void>}
191 */ 133 */
192 const try_preload_when_online = () => { 134 const try_preload_when_online = () => {
...@@ -194,7 +136,7 @@ const App = createApp({ ...@@ -194,7 +136,7 @@ const App = createApp({
194 Taro.getNetworkType({ 136 Taro.getNetworkType({
195 success: (res) => { 137 success: (res) => {
196 if (is_usable_network(res.networkType)) { 138 if (is_usable_network(res.networkType)) {
197 - preloadQrData() 139 + preloadBookingData()
198 } 140 }
199 } 141 }
200 }) 142 })
......
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: 2026-01-08 16:02:07 4 + * @LastEditTime: 2026-01-13 15:18:07
5 * @FilePath: /xyxBooking-weapp/src/components/qrCode.vue 5 * @FilePath: /xyxBooking-weapp/src/components/qrCode.vue
6 * @Description: 预约码卡组件 6 * @Description: 预约码卡组件
7 --> 7 -->
...@@ -187,23 +187,9 @@ const init = async () => { ...@@ -187,23 +187,9 @@ const init = async () => {
187 187
188 if (validData.length > 0) { 188 if (validData.length > 0) {
189 userList.value = formatGroup(validData); 189 userList.value = formatGroup(validData);
190 - // 缓存数据供离线模式使用
191 - const offline_data = userList.value.map(item => ({
192 - name: item.name,
193 - id_number: item.id_number,
194 - qr_code: item.qr_code,
195 - begin_time: item.begin_time,
196 - end_time: item.end_time,
197 - datetime: item.datetime,
198 - pay_id: item.pay_id,
199 - sort: item.sort,
200 - }));
201 - Taro.setStorageSync('OFFLINE_QR_DATA', offline_data);
202 refreshBtn(); 190 refreshBtn();
203 } else { 191 } else {
204 userList.value = []; 192 userList.value = [];
205 - // 清空缓存
206 - Taro.removeStorageSync('OFFLINE_QR_DATA');
207 } 193 }
208 } 194 }
209 } catch (err) { 195 } catch (err) {
......
1 <!-- 1 <!--
2 * @Date: 2024-01-24 16:38:13 2 * @Date: 2024-01-24 16:38:13
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2024-01-30 15:19:44 4 + * @LastEditTime: 2026-01-13 15:04:21
5 * @FilePath: /xyxBooking-weapp/src/components/reserveCard.vue 5 * @FilePath: /xyxBooking-weapp/src/components/reserveCard.vue
6 * @Description: 预约记录卡组件 6 * @Description: 预约记录卡组件
7 --> 7 -->
...@@ -9,7 +9,8 @@ ...@@ -9,7 +9,8 @@
9 <view class="booking-list-item" @tap="goToDetail(reserve_info)"> 9 <view class="booking-list-item" @tap="goToDetail(reserve_info)">
10 <view class="booking-list-item-header"> 10 <view class="booking-list-item-header">
11 <view>{{ reserve_info.booking_time }}</view> 11 <view>{{ reserve_info.booking_time }}</view>
12 - <view :class="[formatStatus(reserve_info.status)?.key, 'status']">{{ formatStatus(reserve_info.status)?.value }}</view> 12 + <view v-if="is_offline" class="status offline">离线</view>
13 + <view v-else :class="[status_info.key, 'status']">{{ status_info.value }}</view>
13 </view> 14 </view>
14 <view class="booking-list-item-body"> 15 <view class="booking-list-item-body">
15 <view class="booking-num"> 16 <view class="booking-num">
...@@ -39,10 +40,20 @@ const props = defineProps({ ...@@ -39,10 +40,20 @@ const props = defineProps({
39 type: Object, 40 type: Object,
40 default: () => ({}), 41 default: () => ({}),
41 }, 42 },
43 + detail_path: {
44 + type: String,
45 + default: '/bookingDetail',
46 + },
47 + is_offline: {
48 + type: Boolean,
49 + default: false,
50 + },
42 }); 51 });
43 52
44 const reserve_info = computed(() => props.data); 53 const reserve_info = computed(() => props.data);
45 54
55 +const is_offline = computed(() => props.is_offline);
56 +
46 const CodeStatus = { 57 const CodeStatus = {
47 APPLY: '1', 58 APPLY: '1',
48 PAYING: '2', 59 PAYING: '2',
...@@ -95,10 +106,14 @@ const formatStatus = (status) => { ...@@ -95,10 +106,14 @@ const formatStatus = (status) => {
95 } 106 }
96 } 107 }
97 108
109 +const status_info = computed(() => {
110 + return formatStatus(reserve_info.value?.status) || { key: '', value: '' }
111 +})
112 +
98 const goToDetail = (item) => { 113 const goToDetail = (item) => {
99 // 只有成功、已使用、已取消(退款成功)才跳转详情 114 // 只有成功、已使用、已取消(退款成功)才跳转详情
100 if (item.status === CodeStatus.SUCCESS || item.status === CodeStatus.USED || item.status === CodeStatus.CANCEL) { 115 if (item.status === CodeStatus.SUCCESS || item.status === CodeStatus.USED || item.status === CodeStatus.CANCEL) {
101 - go('/bookingDetail', { pay_id: item.pay_id }); 116 + go(props.detail_path, { pay_id: item.pay_id });
102 } 117 }
103 } 118 }
104 </script> 119 </script>
...@@ -128,6 +143,11 @@ const goToDetail = (item) => { ...@@ -128,6 +143,11 @@ const goToDetail = (item) => {
128 padding: 4rpx 12rpx; 143 padding: 4rpx 12rpx;
129 border-radius: 8rpx; 144 border-radius: 8rpx;
130 145
146 + &.offline {
147 + color: #999;
148 + background-color: #EEE;
149 + }
150 +
131 &.success { 151 &.success {
132 color: #A67939; 152 color: #A67939;
133 background-color: #FBEEDC; 153 background-color: #FBEEDC;
......
1 +/**
2 + * 刷新离线预约记录缓存
3 + * - 仅在有授权且网络可用时调用
4 + * - 成功后将数据存储到本地缓存(key: OFFLINE_BOOKING_DATA)
5 + * @param {boolean} force - 是否强制刷新,默认为 false
6 + * @returns {Promise<void>}
7 + */
8 +
9 +import Taro from '@tarojs/taro'
10 +import { billOfflineAllAPI } from '@/api/index'
11 +import { hasAuth } from '@/utils/authRedirect'
12 +import { formatDatetime } from '@/utils/tools'
13 +
14 +export const OFFLINE_BOOKING_CACHE_KEY = 'OFFLINE_BOOKING_DATA'
15 +
16 +let refresh_promise = null
17 +
18 +const is_usable_network = (network_type) => {
19 + return ['wifi', '4g', '5g', '3g'].includes(network_type)
20 +}
21 +
22 +/**
23 + * 获取当前网络类型
24 + * @returns {Promise<string>} 网络类型(wifi, 4g, 5g, 3g, unknown)
25 + */
26 +const get_network_type = async () => {
27 + try {
28 + const result = await new Promise((resolve, reject) => {
29 + Taro.getNetworkType({
30 + success: resolve,
31 + fail: reject,
32 + })
33 + })
34 + return result?.networkType || 'unknown'
35 + } catch (e) {
36 + return 'unknown'
37 + }
38 +}
39 +
40 +/**
41 + * 格式化预约记录项
42 + * @param {Object} item - 原始预约记录项
43 + * @returns {Object} 格式化后的预约记录项
44 + */
45 +const normalize_bill_item = (item) => {
46 + const data = item ? { ...item } : {}
47 +
48 + data.datetime = data.datetime || formatDatetime(data)
49 + data.booking_time = data.booking_time || data.datetime
50 + data.order_time = data.order_time || (data.created_time ? data.created_time.slice(0, -3) : '')
51 +
52 + return data
53 +}
54 +
55 +/**
56 + * 获取离线预约记录缓存
57 + * @returns {Array} 格式化后的预约记录项列表
58 + */
59 +export const get_offline_booking_cache = () => {
60 + try {
61 + const data = Taro.getStorageSync(OFFLINE_BOOKING_CACHE_KEY)
62 + return Array.isArray(data) ? data : []
63 + } catch (e) {
64 + return []
65 + }
66 +}
67 +
68 +/**
69 + * 检查是否存在离线预约记录缓存
70 + * @returns {boolean} 是否存在缓存且非空
71 + */
72 +export const has_offline_booking_cache = () => {
73 + const list = get_offline_booking_cache()
74 + return Array.isArray(list) && list.length > 0
75 +}
76 +
77 +/**
78 + * 根据支付ID获取离线预约记录
79 + * @param {*} pay_id 支付ID
80 + * @returns {Object|null} 匹配的预约记录项或 null
81 + */
82 +export const get_offline_booking_by_pay_id = (pay_id) => {
83 + const list = get_offline_booking_cache()
84 + const target_pay_id = String(pay_id || '')
85 + return list.find((item) => String(item?.pay_id || '') === target_pay_id) || null
86 +}
87 +
88 +/**
89 + * 获取预约记录中的人员列表
90 + * @param {Object} bill - 预约记录项
91 + * @returns {Array} 人员列表(包含姓名、身份证号、二维码等信息)
92 + */
93 +export const get_offline_bill_person_list = (bill) => {
94 + if (!bill) return []
95 + const candidate =
96 + bill.person_list ||
97 + bill.bill_person_list ||
98 + bill.persons ||
99 + bill.qrcode_list ||
100 + bill.qr_list ||
101 + bill.detail_list ||
102 + []
103 +
104 + return Array.isArray(candidate) ? candidate : []
105 +}
106 +
107 +/**
108 + * 构建预约记录中的二维码列表
109 + * @param {Object} bill - 预约记录项
110 + * @returns {Array} 二维码列表(包含姓名、身份证号、二维码、预约时间等信息)
111 + */
112 +export const build_offline_qr_list = (bill) => {
113 + const list = get_offline_bill_person_list(bill)
114 + const datetime = bill?.datetime || formatDatetime(bill || {})
115 +
116 + return list
117 + .filter((item) => item && item.qr_code !== '')
118 + .map((item) => {
119 + const begin_time = item.begin_time || bill?.begin_time
120 + const end_time = item.end_time || bill?.end_time
121 + return {
122 + name: item.name,
123 + id_number: item.id_number,
124 + qr_code: item.qr_code,
125 + begin_time,
126 + end_time,
127 + datetime: item.datetime || (begin_time && end_time ? formatDatetime({ begin_time, end_time }) : datetime),
128 + pay_id: bill?.pay_id,
129 + sort: 0,
130 + }
131 + })
132 +}
133 +
134 +/**
135 + * 刷新离线预约记录缓存
136 + * - 仅在有授权且网络可用时调用
137 + * - 成功后将数据存储到本地缓存(key: OFFLINE_BOOKING_DATA)
138 + * @param {boolean} force - 是否强制刷新,默认为 false
139 + * @returns {Promise<void>}
140 + */
141 +export const refresh_offline_booking_cache = async ({ force = false } = {}) => {
142 + if (!hasAuth()) return { code: 0, data: null, msg: '未授权' }
143 +
144 + if (refresh_promise && !force) return refresh_promise
145 +
146 + refresh_promise = (async () => {
147 + const network_type = await get_network_type()
148 + if (!is_usable_network(network_type)) {
149 + return { code: 0, data: null, msg: '网络不可用' }
150 + }
151 +
152 + const { code, data, msg } = await billOfflineAllAPI()
153 + if (code && Array.isArray(data)) {
154 + const normalized = data.map(normalize_bill_item).filter((item) => item && item.pay_id)
155 + if (normalized.length > 0) {
156 + Taro.setStorageSync(OFFLINE_BOOKING_CACHE_KEY, normalized)
157 + }
158 + }
159 + return { code, data, msg }
160 + })()
161 +
162 + try {
163 + return await refresh_promise
164 + } finally {
165 + refresh_promise = null
166 + }
167 +}
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: 2026-01-07 22:27:24 4 + * @LastEditTime: 2026-01-13 15:19:11
5 * @FilePath: /xyxBooking-weapp/src/pages/bookingCode/index.vue 5 * @FilePath: /xyxBooking-weapp/src/pages/bookingCode/index.vue
6 * @Description: 文件描述 6 * @Description: 文件描述
7 --> 7 -->
...@@ -41,6 +41,7 @@ import icon_3 from '@/assets/images/首页01@2x.png' ...@@ -41,6 +41,7 @@ import icon_3 from '@/assets/images/首页01@2x.png'
41 import icon_4 from '@/assets/images/二维码icon.png' 41 import icon_4 from '@/assets/images/二维码icon.png'
42 import icon_5 from '@/assets/images/我的01@2x.png' 42 import icon_5 from '@/assets/images/我的01@2x.png'
43 import { useGo } from '@/hooks/useGo' 43 import { useGo } from '@/hooks/useGo'
44 +import { has_offline_booking_cache } from '@/composables/useOfflineBookingCache'
44 45
45 const go = useGo(); 46 const go = useGo();
46 47
...@@ -49,9 +50,11 @@ useDidShow(() => { ...@@ -49,9 +50,11 @@ useDidShow(() => {
49 success: (res) => { 50 success: (res) => {
50 const isConnected = ['wifi', '4g', '5g', '3g'].includes(res.networkType); 51 const isConnected = ['wifi', '4g', '5g', '3g'].includes(res.networkType);
51 if (!isConnected) { 52 if (!isConnected) {
52 - Taro.redirectTo({ 53 + if (has_offline_booking_cache()) {
53 - url: '/pages/weakNetwork/index' 54 + Taro.redirectTo({ url: '/pages/offlineBookingList/index' })
54 - }) 55 + } else {
56 + Taro.redirectTo({ url: '/pages/weakNetwork/index' })
57 + }
55 } 58 }
56 } 59 }
57 }); 60 });
......
...@@ -6,121 +6,26 @@ ...@@ -6,121 +6,26 @@
6 * @Description: 线下预约码页面 6 * @Description: 线下预约码页面
7 --> 7 -->
8 <template> 8 <template>
9 - <view class="offline-booking-code-page"> 9 + <view class="offline-booking-code-page"></view>
10 - <view class="header-tip">
11 - <IconFont name="tips" size="15" color="#A67939" />
12 - <text>您当前处于离线模式,仅展示本地缓存的预约码</text>
13 - </view>
14 -
15 - <view style="padding: 32rpx;">
16 - <offlineQrCode :list="qrList"></offlineQrCode>
17 - <view class="warning">
18 - <view style="display: flex; align-items: center; justify-content: center;"><IconFont name="tips" /><text style="margin-left: 10rpx;">温馨提示</text></view>
19 - <view style="margin-top: 16rpx;">一人一码,扫码或识别身份证成功后进入</view>
20 - </view>
21 - </view>
22 -
23 - <view class="action-area">
24 - <button class="home-btn" @tap="toHome">返回首页</button>
25 - </view>
26 - </view>
27 </template> 10 </template>
28 11
29 <script setup> 12 <script setup>
30 -import { ref, onMounted } from 'vue' 13 +import { onMounted } from 'vue'
31 import Taro from '@tarojs/taro' 14 import Taro from '@tarojs/taro'
32 -import offlineQrCode from '@/components/offlineQrCode';
33 -import { IconFont } from '@nutui/icons-vue-taro'
34 import { useGo } from '@/hooks/useGo' 15 import { useGo } from '@/hooks/useGo'
35 16
36 const go = useGo(); 17 const go = useGo();
37 -const qrList = ref([]);
38 -
39 -const toHome = () => {
40 - Taro.reLaunch({ url: '/pages/index/index' });
41 -}
42 -
43 -// TODO: Mock Data as per requirement
44 -const getMockData = () => {
45 - return [
46 - {
47 - name: '测试用户1',
48 - id_number: '110101199003078888',
49 - qr_code: 'OFFLINE_MOCK_QR_001',
50 - datetime: '2026-01-08 08:30-10:30',
51 - sort: 0
52 - },
53 - {
54 - name: '测试用户2',
55 - id_number: '110101199205126666',
56 - qr_code: 'OFFLINE_MOCK_QR_002',
57 - datetime: '2026-01-08 08:30-10:30',
58 - sort: 0
59 - }
60 - ];
61 -}
62 18
63 onMounted(() => { 19 onMounted(() => {
64 - try { 20 + Taro.nextTick(() => {
65 - const cachedData = Taro.getStorageSync('OFFLINE_QR_DATA'); 21 + go('/pages/offlineBookingList/index')
66 - if (cachedData && cachedData.length > 0) { 22 + })
67 - qrList.value = cachedData;
68 - } else {
69 - // Requirement 4: Mock data if no data
70 - console.log('No cached data found, using mock data');
71 - qrList.value = getMockData();
72 - }
73 - } catch (e) {
74 - console.error('Read storage failed', e);
75 - qrList.value = getMockData();
76 - }
77 }); 23 });
78 24
79 </script> 25 </script>
80 26
81 <style lang="less"> 27 <style lang="less">
82 .offline-booking-code-page { 28 .offline-booking-code-page {
83 - position: relative;
84 min-height: 100vh; 29 min-height: 100vh;
85 - background-color: #F6F6F6;
86 -
87 - .header-tip {
88 - background-color: #FEF8E8;
89 - color: #A67939;
90 - padding: 20rpx 32rpx;
91 - font-size: 26rpx;
92 - display: flex;
93 - align-items: center;
94 -
95 - text {
96 - margin-left: 10rpx;
97 - }
98 - }
99 -
100 - .warning {
101 - text-align: center;
102 - color: #A67939;
103 - margin-top: 32rpx;
104 - }
105 -
106 - .action-area {
107 - position: fixed;
108 - bottom: 60rpx;
109 - left: 0;
110 - width: 100%;
111 - display: flex;
112 - justify-content: center;
113 -
114 - .home-btn {
115 - width: 600rpx;
116 - height: 88rpx;
117 - line-height: 88rpx;
118 - background: #fff;
119 - color: #A67939;
120 - border: 2rpx solid #A67939;
121 - border-radius: 44rpx;
122 - font-size: 32rpx;
123 - }
124 - }
125 } 30 }
126 </style> 31 </style>
......
1 +export default {
2 + navigationBarTitleText: '离线预约详情'
3 +}
4 +
1 +<template>
2 + <view class="offline-booking-detail-page">
3 + <view class="header-tip">
4 + <IconFont name="tips" size="15" color="#A67939" />
5 + <text>您当前处于离线模式,仅展示本地缓存的数据</text>
6 + </view>
7 +
8 + <view class="content">
9 + <offlineQrCode :list="qr_list" />
10 +
11 + <view v-if="bill_info && bill_info.pay_id" class="detail-wrapper">
12 + <view class="detail-item">
13 + <view>参访时间:</view>
14 + <view>{{ bill_info.datetime }}</view>
15 + </view>
16 + <view class="detail-item">
17 + <view>参访人数:</view>
18 + <view>{{ bill_info.total_qty }} 人</view>
19 + </view>
20 + <view class="detail-item">
21 + <view>支付金额:</view>
22 + <view>¥ {{ bill_info.total_amt }}</view>
23 + </view>
24 + <view class="detail-item">
25 + <view>下单时间:</view>
26 + <view>{{ bill_info.order_time }}</view>
27 + </view>
28 + <view class="detail-item">
29 + <view>订单编号:</view>
30 + <view>{{ bill_info.pay_id }}</view>
31 + </view>
32 + <view class="detail-item">
33 + <view>订单状态:</view>
34 + <view>{{ status_text }}</view>
35 + </view>
36 + </view>
37 + </view>
38 +
39 + <view class="action-area">
40 + <button class="back-btn" @tap="toList">返回列表</button>
41 + </view>
42 + </view>
43 +</template>
44 +
45 +<script setup>
46 +import { ref, computed } from 'vue'
47 +import Taro, { useDidShow, useRouter as useTaroRouter } from '@tarojs/taro'
48 +import { IconFont } from '@nutui/icons-vue-taro'
49 +import offlineQrCode from '@/components/offlineQrCode.vue'
50 +import { get_offline_booking_by_pay_id, build_offline_qr_list } from '@/composables/useOfflineBookingCache'
51 +
52 +const router = useTaroRouter()
53 +const bill_info = ref(null)
54 +const qr_list = ref([])
55 +
56 +const CodeStatus = {
57 + APPLY: '1',
58 + PAYING: '2',
59 + SUCCESS: '3',
60 + CANCEL: '5',
61 + CANCELED: '7',
62 + USED: '9',
63 + REFUNDING: '11'
64 +}
65 +
66 +const status_text = computed(() => {
67 + const status = bill_info.value?.status
68 + switch (status) {
69 + case CodeStatus.APPLY: return '待支付'
70 + case CodeStatus.PAYING: return '支付中'
71 + case CodeStatus.SUCCESS: return '预约成功'
72 + case CodeStatus.CANCEL: return '已取消'
73 + case CodeStatus.CANCELED: return '已取消'
74 + case CodeStatus.USED: return '已使用'
75 + case CodeStatus.REFUNDING: return '退款中'
76 + default: return '未知状态'
77 + }
78 +})
79 +
80 +const toList = () => {
81 + Taro.navigateBack({
82 + fail: () => {
83 + Taro.reLaunch({ url: '/pages/offlineBookingList/index' })
84 + }
85 + })
86 +}
87 +
88 +const load_cache = () => {
89 + const pay_id = router.params.pay_id
90 + const data = get_offline_booking_by_pay_id(pay_id)
91 + if (!data) {
92 + Taro.showToast({ title: '本地无该订单缓存', icon: 'none' })
93 + Taro.reLaunch({ url: '/pages/offlineBookingList/index' })
94 + return
95 + }
96 + bill_info.value = data
97 + qr_list.value = build_offline_qr_list(data)
98 +}
99 +
100 +useDidShow(() => {
101 + load_cache()
102 +})
103 +</script>
104 +
105 +<style lang="less">
106 +.offline-booking-detail-page {
107 + min-height: 100vh;
108 + background-color: #F6F6F6;
109 +
110 + .header-tip {
111 + display: flex;
112 + align-items: center;
113 + padding: 20rpx 32rpx;
114 + color: #A67939;
115 + font-size: 26rpx;
116 + background: #FFF;
117 + }
118 +
119 + .content {
120 + padding: 32rpx;
121 + padding-bottom: 180rpx;
122 + }
123 +
124 + .detail-wrapper {
125 + background-color: #FFF;
126 + border-radius: 16rpx;
127 + padding: 32rpx;
128 + margin-top: 32rpx;
129 + box-shadow: 0 0 29rpx 0 rgba(106,106,106,0.1);
130 +
131 + .detail-item {
132 + display: flex;
133 + justify-content: space-between;
134 + margin-bottom: 26rpx;
135 + color: #333;
136 + font-size: 30rpx;
137 +
138 + &:last-child {
139 + margin-bottom: 0;
140 + }
141 +
142 + view:first-child {
143 + color: #999;
144 + width: 160rpx;
145 + }
146 + view:last-child {
147 + flex: 1;
148 + text-align: right;
149 + }
150 + }
151 + }
152 +
153 + .action-area {
154 + position: fixed;
155 + bottom: 0;
156 + left: 0;
157 + width: 750rpx;
158 + padding: 24rpx 32rpx;
159 + background: #FFF;
160 + box-sizing: border-box;
161 + box-shadow: 0 -10rpx 8rpx 0 rgba(0,0,0,0.06);
162 +
163 + .back-btn {
164 + background-color: #A67939;
165 + color: #FFF;
166 + border-radius: 16rpx;
167 + font-size: 32rpx;
168 + padding: 22rpx 0;
169 + }
170 + }
171 +}
172 +</style>
173 +
1 +export default {
2 + navigationBarTitleText: '离线预约记录'
3 +}
4 +
1 +<template>
2 + <view class="offline-booking-list-page">
3 + <view class="header-tip">
4 + <IconFont name="tips" size="15" color="#A67939" />
5 + <text>您当前处于离线模式,仅展示本地缓存的预约记录</text>
6 + </view>
7 +
8 + <view class="list-wrapper">
9 + <view v-for="(item, index) in booking_list" :key="index">
10 + <reserveCard :data="item" detail_path="/offlineBookingDetail" is_offline />
11 + </view>
12 +
13 + <view v-if="!booking_list.length" class="empty">
14 + <text>本地无缓存预约记录</text>
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 } from 'vue'
26 +import Taro, { useDidShow } from '@tarojs/taro'
27 +import { IconFont } from '@nutui/icons-vue-taro'
28 +import reserveCard from '@/components/reserveCard.vue'
29 +import { get_offline_booking_cache } from '@/composables/useOfflineBookingCache'
30 +
31 +const booking_list = ref([])
32 +
33 +const toHome = () => {
34 + Taro.reLaunch({ url: '/pages/index/index' })
35 +}
36 +
37 +const load_cache = () => {
38 + booking_list.value = get_offline_booking_cache()
39 +}
40 +
41 +useDidShow(() => {
42 + load_cache()
43 +})
44 +</script>
45 +
46 +<style lang="less">
47 +.offline-booking-list-page {
48 + min-height: 100vh;
49 + background-color: #F6F6F6;
50 +
51 + .header-tip {
52 + display: flex;
53 + align-items: center;
54 + padding: 20rpx 32rpx;
55 + color: #A67939;
56 + font-size: 26rpx;
57 + background: #FFF;
58 + }
59 +
60 + .list-wrapper {
61 + padding: 32rpx;
62 + }
63 +
64 + .empty {
65 + padding: 120rpx 0;
66 + text-align: center;
67 + color: #A67939;
68 + font-size: 32rpx;
69 + }
70 +
71 + .action-area {
72 + position: fixed;
73 + bottom: 0;
74 + left: 0;
75 + width: 750rpx;
76 + padding: 24rpx 32rpx;
77 + background: #FFF;
78 + box-sizing: border-box;
79 + box-shadow: 0 -10rpx 8rpx 0 rgba(0,0,0,0.06);
80 +
81 + .home-btn {
82 + background-color: #A67939;
83 + color: #FFF;
84 + border-radius: 16rpx;
85 + font-size: 32rpx;
86 + padding: 22rpx 0;
87 + }
88 + }
89 +}
90 +</style>
...@@ -37,6 +37,7 @@ import { IconFont } from '@nutui/icons-vue-taro' ...@@ -37,6 +37,7 @@ import { IconFont } from '@nutui/icons-vue-taro'
37 import { useGo } from '@/hooks/useGo' 37 import { useGo } from '@/hooks/useGo'
38 import { billInfoAPI } from '@/api/index' 38 import { billInfoAPI } from '@/api/index'
39 import { formatDatetime } from '@/utils/tools'; 39 import { formatDatetime } from '@/utils/tools';
40 +import { refresh_offline_booking_cache } from '@/composables/useOfflineBookingCache'
40 41
41 const router = useTaroRouter(); 42 const router = useTaroRouter();
42 const go = useGo(); 43 const go = useGo();
...@@ -57,6 +58,7 @@ useDidShow(async () => { ...@@ -57,6 +58,7 @@ useDidShow(async () => {
57 data.datetime = data && formatDatetime(data); 58 data.datetime = data && formatDatetime(data);
58 billInfo.value = data; 59 billInfo.value = data;
59 } 60 }
61 + refresh_offline_booking_cache({ force: true })
60 }) 62 })
61 </script> 63 </script>
62 64
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
17 <view class="offline-entry" @tap="toOfflineCode"> 17 <view class="offline-entry" @tap="toOfflineCode">
18 <view class="circle-btn"> 18 <view class="circle-btn">
19 <image :src="icon_invite" style="width: 60rpx; height: 60rpx; margin-bottom: 16rpx;" /> 19 <image :src="icon_invite" style="width: 60rpx; height: 60rpx; margin-bottom: 16rpx;" />
20 - <text>预约</text> 20 + <text>预约记录</text>
21 </view> 21 </view>
22 </view> 22 </view>
23 23
...@@ -38,7 +38,7 @@ import icon_invite from '@/assets/images/二维码@2x2.png' ...@@ -38,7 +38,7 @@ import icon_invite from '@/assets/images/二维码@2x2.png'
38 const go = useGo(); 38 const go = useGo();
39 39
40 const toOfflineCode = () => { 40 const toOfflineCode = () => {
41 - go('/pages/offlineBookingCode/index'); 41 + go('/pages/offlineBookingList/index');
42 } 42 }
43 43
44 const retry = () => { 44 const retry = () => {
......