hookehuyr

feat: 新增西园寺预约小程序功能

新增预约码、参观者管理、预约记录等功能模块
添加预约流程页面及状态管理
集成微信支付和身份证验证
优化UI设计及用户体验
更新依赖包和工具函数
Showing 74 changed files with 2979 additions and 465 deletions
...@@ -9,9 +9,19 @@ declare module 'vue' { ...@@ -9,9 +9,19 @@ declare module 'vue' {
9 export interface GlobalComponents { 9 export interface GlobalComponents {
10 NavBar: typeof import('./src/components/navBar.vue')['default'] 10 NavBar: typeof import('./src/components/navBar.vue')['default']
11 NutButton: typeof import('@nutui/nutui-taro')['Button'] 11 NutButton: typeof import('@nutui/nutui-taro')['Button']
12 - NutToast: typeof import('@nutui/nutui-taro')['Toast'] 12 + NutCheckbox: typeof import('@nutui/nutui-taro')['Checkbox']
13 + NutCheckboxGroup: typeof import('@nutui/nutui-taro')['CheckboxGroup']
14 + NutDatePicker: typeof import('@nutui/nutui-taro')['DatePicker']
15 + NutForm: typeof import('@nutui/nutui-taro')['Form']
16 + NutFormItem: typeof import('@nutui/nutui-taro')['FormItem']
17 + NutIcon: typeof import('@nutui/nutui-taro')['Icon']
18 + NutInput: typeof import('@nutui/nutui-taro')['Input']
19 + NutPopup: typeof import('@nutui/nutui-taro')['Popup']
13 Picker: typeof import('./src/components/time-picker-data/picker.vue')['default'] 20 Picker: typeof import('./src/components/time-picker-data/picker.vue')['default']
14 PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default'] 21 PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default']
22 + QrCode: typeof import('./src/components/qrCode.vue')['default']
23 + QrCodeSearch: typeof import('./src/components/qrCodeSearch.vue')['default']
24 + ReserveCard: typeof import('./src/components/reserveCard.vue')['default']
15 RouterLink: typeof import('vue-router')['RouterLink'] 25 RouterLink: typeof import('vue-router')['RouterLink']
16 RouterView: typeof import('vue-router')['RouterView'] 26 RouterView: typeof import('vue-router')['RouterView']
17 } 27 }
......
...@@ -54,9 +54,12 @@ ...@@ -54,9 +54,12 @@
54 "@tarojs/shared": "4.1.9", 54 "@tarojs/shared": "4.1.9",
55 "@tarojs/taro": "4.1.9", 55 "@tarojs/taro": "4.1.9",
56 "axios-miniprogram": "^2.7.2", 56 "axios-miniprogram": "^2.7.2",
57 + "dayjs": "^1.11.19",
57 "pinia": "^3.0.3", 58 "pinia": "^3.0.3",
59 + "qs": "^6.14.1",
58 "taro-plugin-pinia": "^1.0.0", 60 "taro-plugin-pinia": "^1.0.0",
59 - "vue": "^3.3.0" 61 + "vue": "^3.3.0",
62 + "xst-solar2lunar": "^2.1.0"
60 }, 63 },
61 "devDependencies": { 64 "devDependencies": {
62 "@babel/core": "^7.26.0", 65 "@babel/core": "^7.26.0",
......
...@@ -62,15 +62,24 @@ importers: ...@@ -62,15 +62,24 @@ importers:
62 axios-miniprogram: 62 axios-miniprogram:
63 specifier: ^2.7.2 63 specifier: ^2.7.2
64 version: 2.7.2 64 version: 2.7.2
65 + dayjs:
66 + specifier: ^1.11.19
67 + version: 1.11.19
65 pinia: 68 pinia:
66 specifier: ^3.0.3 69 specifier: ^3.0.3
67 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 + qs:
72 + specifier: ^6.14.1
73 + version: 6.14.1
68 taro-plugin-pinia: 74 taro-plugin-pinia:
69 specifier: ^1.0.0 75 specifier: ^1.0.0
70 version: 1.0.0 76 version: 1.0.0
71 vue: 77 vue:
72 specifier: ^3.3.0 78 specifier: ^3.3.0
73 version: 3.5.26(typescript@5.9.3) 79 version: 3.5.26(typescript@5.9.3)
80 + xst-solar2lunar:
81 + specifier: ^2.1.0
82 + version: 2.1.0
74 devDependencies: 83 devDependencies:
75 '@babel/core': 84 '@babel/core':
76 specifier: ^7.26.0 85 specifier: ^7.26.0
...@@ -3039,6 +3048,9 @@ packages: ...@@ -3039,6 +3048,9 @@ packages:
3039 resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} 3048 resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
3040 engines: {node: '>= 0.4'} 3049 engines: {node: '>= 0.4'}
3041 3050
3051 + dayjs@1.11.19:
3052 + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
3053 +
3042 debug@2.6.9: 3054 debug@2.6.9:
3043 resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} 3055 resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
3044 peerDependencies: 3056 peerDependencies:
...@@ -6693,6 +6705,9 @@ packages: ...@@ -6693,6 +6705,9 @@ packages:
6693 xmlchars@2.2.0: 6705 xmlchars@2.2.0:
6694 resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} 6706 resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
6695 6707
6708 + xst-solar2lunar@2.1.0:
6709 + resolution: {integrity: sha512-1X2Enk2LK1l6ZQWjSFQsLspmm6h32p8a+UGYi+HkJLEQmKiy5KSlTs2xEiyyAAG+7jTo6EOxPWS2lk3yLk/ivg==}
6710 +
6696 xtend@4.0.2: 6711 xtend@4.0.2:
6697 resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} 6712 resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
6698 engines: {node: '>=0.4'} 6713 engines: {node: '>=0.4'}
...@@ -10036,6 +10051,8 @@ snapshots: ...@@ -10036,6 +10051,8 @@ snapshots:
10036 es-errors: 1.3.0 10051 es-errors: 1.3.0
10037 is-data-view: 1.0.2 10052 is-data-view: 1.0.2
10038 10053
10054 + dayjs@1.11.19: {}
10055 +
10039 debug@2.6.9: 10056 debug@2.6.9:
10040 dependencies: 10057 dependencies:
10041 ms: 2.0.0 10058 ms: 2.0.0
...@@ -14129,6 +14146,10 @@ snapshots: ...@@ -14129,6 +14146,10 @@ snapshots:
14129 14146
14130 xmlchars@2.2.0: {} 14147 xmlchars@2.2.0: {}
14131 14148
14149 + xst-solar2lunar@2.1.0:
14150 + dependencies:
14151 + dayjs: 1.11.19
14152 +
14132 xtend@4.0.2: {} 14153 xtend@4.0.2: {}
14133 14154
14134 xxhashjs@0.2.2: 14155 xxhashjs@0.2.2:
......
1 /* 1 /*
2 * @Date: 2022-05-18 22:56:08 2 * @Date: 2022-05-18 22:56:08
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2024-05-25 22:35:00 4 + * @LastEditTime: 2026-01-06 20:56:55
5 - * @FilePath: /meihuaApp/src/api/fn.js 5 + * @FilePath: /git/xyxBooking-weapp/src/api/fn.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
8 import axios from '@/utils/request'; 8 import axios from '@/utils/request';
...@@ -17,13 +17,15 @@ import Taro from '@tarojs/taro' ...@@ -17,13 +17,15 @@ import Taro from '@tarojs/taro'
17 export const fn = (api) => { 17 export const fn = (api) => {
18 return api 18 return api
19 .then(res => { 19 .then(res => {
20 - if (res.data.code) { 20 + // 适配 H5 逻辑,code === 1 为成功
21 + if (res.data.code === 1) {
21 return res.data || true; 22 return res.data || true;
22 } else { 23 } else {
23 // tslint:disable-next-line: no-console 24 // tslint:disable-next-line: no-console
24 console.warn(res); 25 console.warn(res);
26 + if (res.data.show === false) return false;
25 Taro.showToast({ 27 Taro.showToast({
26 - title: res.data.msg, 28 + title: res.data.msg || '请求失败',
27 icon: 'none', 29 icon: 'none',
28 duration: 2000 30 duration: 2000
29 }); 31 });
...@@ -52,6 +54,7 @@ export const uploadFn = (api) => { ...@@ -52,6 +54,7 @@ export const uploadFn = (api) => {
52 } else { 54 } else {
53 // tslint:disable-next-line: no-console 55 // tslint:disable-next-line: no-console
54 console.warn(res); 56 console.warn(res);
57 + if (!res.data.show) return false;
55 Taro.showToast({ 58 Taro.showToast({
56 title: res.data.msg, 59 title: res.data.msg,
57 icon: 'none', 60 icon: 'none',
...@@ -72,7 +75,7 @@ export const uploadFn = (api) => { ...@@ -72,7 +75,7 @@ export const uploadFn = (api) => {
72 */ 75 */
73 export const fetch = { 76 export const fetch = {
74 get: function (api, params) { 77 get: function (api, params) {
75 - return axios.get(api, params) 78 + return axios.get(api, { params }) // 注意 axios-miniprogram 的 get 参数通常也是放在 config 对象中, key 为 params
76 }, 79 },
77 post: function (api, params) { 80 post: function (api, params) {
78 return axios.post(api, params) 81 return axios.post(api, params)
......
1 /* 1 /*
2 - * @Date: 2023-12-22 10:29:37 2 + * @Date: 2023-08-24 09:42:27
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2024-06-06 09:31:34 4 + * @LastEditTime: 2024-01-29 17:26:42
5 - * @FilePath: /meihuaApp/src/api/index.js 5 + * @FilePath: /xyxBooking-weapp/src/api/index.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
8 -import { fn, fetch } from './fn'; 8 +import { fn, fetch } from '@/api/fn';
9 9
10 const Api = { 10 const Api = {
11 - BIND_PHONE: '/srv/?a=room_order&t=bind_phone', 11 + CAN_RESERVE_DATE_LIST: '/srv/?a=api&t=can_reserve_date_list',
12 - SEND_SMS_CODE: '/srv/?a=room_order&t=send_sms_code', 12 + CAN_RESERVE_TIME_LIST: '/srv/?a=api&t=can_reserve_time_list',
13 - SHOW_SESSION: '/srv/?a=room_order&t=show_session', 13 + PERSON_LIST: '/srv/?a=api&t=person_list',
14 - SAVE_CUSTOMER_INFO: '/srv/?a=room_order&t=save_customer_info', 14 + ADD_PERSON: '/srv/?a=api&t=add_person',
15 - SYS_PARAM: '/srv/?a=room_order&t=sys_param', 15 + DEL_PERSON: '/srv/?a=api&t=del_person',
16 - GET_LIST: '/srv/?a=room_data&t=get_list', 16 + ADD_RESERVE: '/srv/?a=api&t=add_reserve',
17 - GET_ROOM: '/srv/?a=room_data&t=get_room', 17 + PAY_PREPARE: '/srv/?a=api&t=pay_prepare',
18 - ADD_ORDER: '/srv/?a=room_data&t=add_order', 18 + PAY_CALLBACK: '/srv/?a=api&t=pay_callback',
19 - MY_ORDER: '/srv/?a=room_data&t=my_order', 19 + BILL_INFO: '/srv/?a=api&t=bill_info',
20 - ORDER_CANCEL: '/srv/?a=room_data&t=order_cancel', 20 + ON_AUTH_BILL_INFO: '/srv/?a=no_auth_api&t=bill_info',
21 - PAY: '/srv/?a=pay', 21 + QRCODE_LIST: '/srv/?a=api&t=qrcode_list',
22 - PAY_CHECK: '/srv/?a=pay_check', 22 + QRCODE_STATUS: '/srv/?a=api&t=qrcode_status',
23 - ORDER_SUCCESS: '/srv/?a=room_data&t=order_success', 23 + BILL_LIST: '/srv/?a=api&t=bill_list',
24 - TMP_SYS_PARAM: '/srv/?a=get_item', 24 + ICBC_REFUND: '/srv/?a=icbc_refund',
25 -} 25 + BILL_PREPARE: '/srv/?a=api&t=bill_person',
26 - 26 + BILL_PAY_STATUS: '/srv/?a=api&t=bill_pay_status',
27 -/** 27 + QUERY_QR_CODE: '/srv/?a=api&t=id_number_query_qr_code',
28 - * @description: 绑定手机号(手机号登录) 28 + ICBC_ORDER_QRY: '/srv/?a=icbc_orderqry',
29 - * @param phone 手机号 29 +};
30 - * @param sms_code 验证码 30 +
31 +/**
32 + * @description: 可预约日期列表
33 + * @param {Array} month 月份,格式yyyy-mm
31 * @returns 34 * @returns
32 */ 35 */
33 -export const bindPhoneAPI = (params) => fn(fetch.post(Api.BIND_PHONE, params)); 36 +export const canReserveDateListAPI = (params) => fn(fetch.get(Api.CAN_RESERVE_DATE_LIST, params));
34 37
35 /** 38 /**
36 - * @description: 发送验证码 39 + * @description: 可预约时段列表
37 - * @param phone 手机号 40 + * @param {Array} month_date 日期,格式yyyy-mm-dd
38 * @returns 41 * @returns
39 */ 42 */
40 -export const sendSmsCodeAPI = (params) => fn(fetch.post(Api.SEND_SMS_CODE, params)); 43 +export const canReserveTimeListAPI = (params) => fn(fetch.get(Api.CAN_RESERVE_TIME_LIST, params));
41 44
42 /** 45 /**
43 - * @description: 获取我的信息 46 + * @description: 参观者列表
47 + * @param {String} reserve_date 预约日期,格式 yyyy-mm-dd,没有传入则不查询指定日期是否已预约
48 + * @param {String} begin_time 时段开始时间,格式 hh:mm
49 + * @param {String} end_time 时段结束时间,格式 hh:mm
44 * @returns 50 * @returns
45 */ 51 */
46 -export const showMyInfoAPI = (params) => fn(fetch.get(Api.SHOW_SESSION, params)); 52 +export const personListAPI = (params) => fn(fetch.get(Api.PERSON_LIST, params));
47 53
48 /** 54 /**
49 - * @description: 保存我的信息 55 + * @description: 添加参观者
50 - * @param params 56 + * @param {String} name 参观者姓名
57 + * @param {String} id_type 证件类型,1=身份证,3=其他
58 + * @param {String} id_number 证件号
51 * @returns 59 * @returns
52 */ 60 */
53 -export const saveCustomerInfoAPI = (params) => fn(fetch.post(Api.SAVE_CUSTOMER_INFO, params)); 61 +export const addPersonAPI = (params) => fn(fetch.post(Api.ADD_PERSON, params));
54 62
55 /** 63 /**
56 - * @description: 获取系统参数 64 + * @description: 删除参观者
65 + * @param {String} person_id 参观者id
57 * @returns 66 * @returns
58 */ 67 */
59 -export const sysParamAPI = (params) => fn(fetch.get(Api.SYS_PARAM, params)); 68 +export const delPersonAPI = (params) => fn(fetch.post(Api.DEL_PERSON, params));
69 +
70 +/**
71 + * @description: 提交预约
72 + * @param {String} reserve_date
73 + * @param {String} begin_time
74 + * @param {String} end_time
75 + * @param {String} person_id_list
76 + * @returns {String} bill_id 预约单id
77 + */
78 +export const addReserveAPI = (params) => fn(fetch.post(Api.ADD_RESERVE, params));
79 +
80 +/**
81 + * @description: 支付准备(模拟)
82 + * @param {String} bill_id
83 + * @returns {String} bill_id 预约单id
84 + */
85 +export const payPrepareAPI = (params) => fn(fetch.post(Api.PAY_PREPARE, params));
86 +
87 +/**
88 + * @description: 支付回调(模拟)
89 + * @param {String} pay_id 预约单支付凭证
90 + * @param {String} pay_status 支付状态,1为成功,0为失败(缺省)
91 + * @returns {String} bill_id 预约单id
92 + */
93 +export const payCallbackAPI = (params) => fn(fetch.post(Api.PAY_CALLBACK, params));
94 +
95 +/**
96 + * @description: 预约单详情,参观者列表
97 + * @param {String} bill_id 预约单id
98 + * @returns {String}
99 + */
100 +export const billInfoAPI = (params) => fn(fetch.get(Api.BILL_INFO, params));
60 101
61 /** 102 /**
62 - * @description: 获取房间列表 103 + * @description: 扫码核销二维码列表
63 - * @param start_date 入住时间
64 - * @param end_date 离店时间
65 - * @param offset 偏移量
66 - * @param limit 条数
67 * @returns 104 * @returns
68 */ 105 */
69 -export const getListAPI = (params) => fn(fetch.get(Api.GET_LIST, params)); 106 +export const qrcodeListAPI = (params) => fn(fetch.get(Api.QRCODE_LIST, params));
70 107
71 /** 108 /**
72 - * @description: 获取房间详情 109 + * @description: 扫码核销二维码状态
73 - * @param start_date 入住时间
74 - * @param end_date 离店时间
75 - * @param room_type floor/room
76 * @returns 110 * @returns
77 */ 111 */
78 -export const getRoomAPI = (params) => fn(fetch.get(Api.GET_ROOM, params)); 112 +export const qrcodeStatusAPI = (params) => fn(fetch.get(Api.QRCODE_STATUS, params));
79 113
80 /** 114 /**
81 - * @description: 预定房间 115 + * @description: 预约单列表
82 - * @param id ID
83 - * @param num 预定房间数量
84 - * @param plan_in 入住时间
85 - * @param plan_out 离店时间
86 - * @param contact_name 联系人
87 - * @param contact_phone 联系电话
88 - * @param order_remark 备注
89 - * @param room_type floor/room
90 * @returns 116 * @returns
91 */ 117 */
92 -export const addOrderAPI = (params) => fn(fetch.post(Api.ADD_ORDER, params)); 118 +export const billListAPI = (params) => fn(fetch.get(Api.BILL_LIST, params));
93 119
94 /** 120 /**
95 - * @description: 支付 121 + * @description: 退款
96 - * @param order_id 订单ID
97 * @returns 122 * @returns
98 */ 123 */
99 -export const payAPI = (params) => fn(fetch.post(Api.PAY, params)); 124 +export const icbcRefundAPI = (params) => fn(fetch.post(Api.ICBC_REFUND, params));
100 125
101 /** 126 /**
102 - * @description: 检查是否支付成功 127 + * @description: 支付前准备(获取支付参数等)
103 - * @param order_id 订单ID
104 * @returns 128 * @returns
105 */ 129 */
106 -export const payCheckAPI = (params) => fn(fetch.post(Api.PAY_CHECK, params)); 130 +export const billPrepareAPI = (params) => fn(fetch.post(Api.BILL_PREPARE, params));
107 131
108 /** 132 /**
109 - * @description: 获取我的订单列表 133 + * @description: 支付状态查询
110 - * @param pay_type
111 - * @param page
112 - * @param limit
113 * @returns 134 * @returns
114 */ 135 */
115 -export const myOrderAPI = (params) => fn(fetch.get(Api.MY_ORDER, params)); 136 +export const billPayStatusAPI = (params) => fn(fetch.get(Api.BILL_PAY_STATUS, params));
116 137
117 /** 138 /**
118 - * @description: 取消订单 139 + * @description: 证件号查询二维码
119 - * @param id
120 * @returns 140 * @returns
121 */ 141 */
122 -export const orderCancelAPI = (params) => fn(fetch.post(Api.ORDER_CANCEL, params)); 142 +export const queryQrCodeAPI = (params) => fn(fetch.get(Api.QUERY_QR_CODE, params));
123 143
124 /** 144 /**
125 - * @description: 订单成功 145 + * @description: 订单查询
126 - * @param id
127 * @returns 146 * @returns
128 */ 147 */
129 -export const orderSuccessAPI = (params) => fn(fetch.post(Api.ORDER_SUCCESS, params)); 148 +export const icbcOrderQryAPI = (params) => fn(fetch.get(Api.ICBC_ORDER_QRY, params));
130 149
131 /** 150 /**
132 - * @description: 151 + * @description: 授权前订单详情查询
133 - * @param id
134 * @returns 152 * @returns
135 */ 153 */
136 -export const tmpSysParamAPI = (params) => fn(fetch.get(Api.TMP_SYS_PARAM, params)); 154 +export const onAuthBillInfoAPI = (params) => fn(fetch.get(Api.ON_AUTH_BILL_INFO, params));
......
...@@ -9,6 +9,19 @@ export default { ...@@ -9,6 +9,19 @@ export default {
9 pages: [ 9 pages: [
10 'pages/index/index', 10 'pages/index/index',
11 'pages/auth/index', 11 'pages/auth/index',
12 + 'pages/notice/index',
13 + 'pages/booking/index',
14 + 'pages/submit/index',
15 + 'pages/addVisitor/index',
16 + 'pages/success/index',
17 + 'pages/bookingCode/index',
18 + 'pages/bookingList/index',
19 + 'pages/bookingDetail/index',
20 + 'pages/me/index',
21 + 'pages/waiting/index',
22 + 'pages/callback/index',
23 + 'pages/search/index',
24 + 'pages/visitorList/index',
12 ], 25 ],
13 subpackages: [ // 配置在tabBar中的页面不能分包写到subpackages中去 26 subpackages: [ // 配置在tabBar中的页面不能分包写到subpackages中去
14 { 27 {
...@@ -19,7 +32,7 @@ export default { ...@@ -19,7 +32,7 @@ export default {
19 window: { 32 window: {
20 backgroundTextStyle: 'light', 33 backgroundTextStyle: 'light',
21 navigationBarBackgroundColor: '#fff', 34 navigationBarBackgroundColor: '#fff',
22 - navigationBarTitleText: 'WeChat', 35 + navigationBarTitleText: '西园寺预约',
23 navigationBarTextStyle: 'black' 36 navigationBarTextStyle: 'black'
24 } 37 }
25 } 38 }
......
1 +
2 +.modify-top {
3 + z-index: 36;
4 + position: absolute;
5 + left: 0;
6 + top: 0;
7 + width: 100%;
8 + height: 10px;
9 + background-image: url('http://gyzs.onwall.cn/top-xian%402x.png');
10 + background-size: contain;
11 +}
12 +.content-bg {
13 + /**
14 + * background-color and background-image 共存,不能使用渐变色
15 + * 图片铺平当时精度提高看看效果
16 + * 直接用渐变色
17 + * 不使用渐变色背景
18 + */
19 + height: 100%;
20 + min-height: 100vh;
21 + // background-image: url('@images/bg-yellow-duan@2x.png');
22 + background-image: url('http://gyzs.onwall.cn/bg-yellow-duan%402x.png');
23 + // background-size: cover;
24 + // background: linear-gradient(360deg, #FDD347 0%, #FFED6D 100%) ;
25 +}
1 -@namespace: 'meihua'; 1 +@namespace: 'tswj';
2 2
3 /* ============ 颜色 ============ */ 3 /* ============ 颜色 ============ */
4 4
5 // 主色调 5 // 主色调
6 -@base-color: #199A74; 6 +@base-color: #11D2B1;
7 -
8 // 文字颜色 7 // 文字颜色
9 -@base-font-color: #333333; 8 +@base-font-color: #FFFFFF;
10 -@sub-font-color: #999999;
11 9
12 // 定义一个映射 10 // 定义一个映射
13 #colors() { 11 #colors() {
......
1 +<!--
2 + * @Date: 2024-01-16 10:06:47
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-12-26 11:15:45
5 + * @FilePath: /xyxBooking-weapp/src/components/qrCode.vue
6 + * @Description: 预约码卡组件
7 +-->
8 +<template>
9 + <view class="qr-code-page">
10 + <view v-if="userList.length" class="show-qrcode">
11 + <view class="qrcode-content">
12 + <view class="user-info">{{ userinfo.name }}&nbsp;{{ userinfo.id }}</view>
13 + <view class="user-qrcode">
14 + <view class="left" @tap="prevCode">
15 + <image src="https://cdn.ipadbiz.cn/xys/booking/%E5%B7%A6@2x.png" />
16 + </view>
17 + <view class="center">
18 + <image :src="currentQrCodeUrl" mode="aspectFit" />
19 + <view v-if="useStatus === STATUS_CODE.CANCELED || useStatus === STATUS_CODE.USED" class="qrcode-used">
20 + <view class="overlay"></view>
21 + <text class="status-text">二维码{{ qr_code_status[useStatus] }}</text>
22 + </view>
23 + </view>
24 + <view class="right" @tap="nextCode">
25 + <image src="https://cdn.ipadbiz.cn/xys/booking/%E5%8F%B3@2x.png" />
26 + </view>
27 + </view>
28 + <view style="color: red; margin-top: 1rem;">{{ userinfo.datetime }}</view>
29 + </view>
30 + <view class="user-list">
31 + <view
32 + @tap="selectUser(index)"
33 + v-for="(item, index) in userList"
34 + :key="index"
35 + :class="[
36 + 'user-item',
37 + select_index === index ? 'checked' : '',
38 + userList.length > 1 && item.sort ? 'border' : '',
39 + ]">
40 + {{ item.name }}
41 + </view>
42 + </view>
43 + </view>
44 + <view v-else class="no-qrcode">
45 + <image src="https://cdn.ipadbiz.cn/xys/booking/%E6%9A%82%E6%97%A0@2x.png" style="width: 10rem; height: 10rem;" />
46 + <view class="no-qrcode-title">今天没有预约记录</view>
47 + <view style="text-align: center; color: #A67939; margin-top: 0.5rem;">查看我的“<text @tap="toRecord" style="text-decoration: underline; color: #ED9820;">预约记录</text>”</view>
48 + </view>
49 + </view>
50 +</template>
51 +
52 +<script setup>
53 +import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
54 +import Taro, { useDidShow } from '@tarojs/taro'
55 +import { formatDatetime } from '@/utils/tools';
56 +import { qrcodeListAPI, qrcodeStatusAPI, billPersonAPI } from '@/api/index'
57 +import { useGo } from '@/hooks/useGo'
58 +import BASE_URL from '@/utils/config';
59 +
60 +const go = useGo();
61 +
62 +const props = defineProps({
63 + status: {
64 + type: String,
65 + default: ''
66 + },
67 + type: {
68 + type: String,
69 + default: ''
70 + },
71 + payId: { // 接收 payId
72 + type: String,
73 + default: ''
74 + }
75 +});
76 +
77 +const select_index = ref(0);
78 +const userList = ref([]);
79 +
80 +const prevCode = () => {
81 + select_index.value = select_index.value - 1;
82 + if (select_index.value < 0) {
83 + select_index.value = userList.value.length - 1;
84 + }
85 +};
86 +const nextCode = () => {
87 + select_index.value = select_index.value + 1;
88 + if (select_index.value > userList.value.length - 1) {
89 + select_index.value = 0;
90 + }
91 +};
92 +
93 +watch(
94 + () => select_index.value,
95 + (index) => {
96 + refreshBtn();
97 + }
98 +)
99 +
100 +function replaceMiddleCharacters(inputString) {
101 + if (!inputString || inputString.length < 15) {
102 + return inputString;
103 + }
104 + const start = Math.floor((inputString.length - 8) / 2);
105 + const end = start + 8;
106 + const replacement = '*'.repeat(8);
107 + return inputString.substring(0, start) + replacement + inputString.substring(end);
108 +}
109 +
110 +const formatId = (id) => replaceMiddleCharacters(id);
111 +
112 +const userinfo = computed(() => {
113 + return {
114 + name: userList.value[select_index.value]?.name,
115 + id: formatId(userList.value[select_index.value]?.id_number),
116 + datetime: userList.value[select_index.value]?.datetime,
117 + };
118 +});
119 +
120 +const currentQrCodeUrl = computed(() => {
121 + const url = userList.value[select_index.value]?.qr_code_url;
122 + if (url && url.startsWith('/')) {
123 + return BASE_URL + url;
124 + }
125 + return url;
126 +})
127 +
128 +const useStatus = ref('0');
129 +
130 +const qr_code_status = {
131 + '1': '未激活',
132 + '3': '待使用',
133 + '5': '被取消',
134 + '7': '已使用',
135 +};
136 +
137 +const STATUS_CODE = {
138 + APPLY: '1',
139 + SUCCESS: '3',
140 + CANCELED: '5',
141 + USED: '7',
142 +};
143 +
144 +const refreshBtn = async () => {
145 + if (!userList.value[select_index.value]) return;
146 + const { code, data } = await qrcodeStatusAPI({ qr_code: userList.value[select_index.value].qr_code });
147 + if (code) {
148 + useStatus.value = data.status;
149 + }
150 +}
151 +
152 +const selectUser = (index) => {
153 + select_index.value = index;
154 +}
155 +
156 +const formatGroup = (data) => {
157 + let lastPayId = null;
158 + for (let i = 0; i < data.length; i++) {
159 + if (data[i].pay_id !== lastPayId) {
160 + data[i].sort = 1;
161 + lastPayId = data[i].pay_id;
162 + } else {
163 + data[i].sort = 0;
164 + }
165 + }
166 + return data;
167 +}
168 +
169 +const init = async () => {
170 + if (!props.type) {
171 + const { code, data } = await qrcodeListAPI();
172 + if (code) {
173 + data.forEach(item => {
174 + item.qr_code_url = '/admin?m=srv&a=get_qrcode&key=' + item.qr_code;
175 + item.datetime = formatDatetime({ begin_time: item.begin_time, end_time: item.end_time })
176 + item.sort = 0;
177 + });
178 + // 剔除qr_code为空的二维码
179 + const validData = data.filter(item => item.qr_code !== '');
180 +
181 + if (validData.length > 0) {
182 + userList.value = formatGroup(validData);
183 + refreshBtn();
184 + } else {
185 + userList.value = [];
186 + }
187 + }
188 + } else {
189 + if (props.payId) {
190 + const { code, data } = await billPersonAPI({ pay_id: props.payId });
191 + if (code) {
192 + data.forEach(item => {
193 + item.qr_code_url = '/admin?m=srv&a=get_qrcode&key=' + item.qr_code;
194 + item.sort = 0;
195 + // billPersonAPI 返回的数据可能没有 datetime 字段,需要检查
196 + // 如果没有,可能需要从外部传入或者假设是当天的?
197 + // H5 代码没有处理 datetime,但在 template 里用了。
198 + // 这里暂且不做处理,如果没有 datetime 就不显示
199 + });
200 + const validData = data.filter(item => item.qr_code !== '');
201 + if (validData.length > 0) {
202 + userList.value = validData;
203 + refreshBtn();
204 + } else {
205 + userList.value = [];
206 + }
207 + }
208 + }
209 + }
210 +};
211 +
212 +onMounted(() => {
213 + init();
214 +});
215 +
216 +const poll = async () => {
217 + if (userList.value.length && useStatus.value === STATUS_CODE.SUCCESS) {
218 + if (userList.value[select_index.value]) {
219 + const { code, data } = await qrcodeStatusAPI({ qr_code: userList.value[select_index.value].qr_code });
220 + if (code) {
221 + useStatus.value = data.status;
222 + }
223 + }
224 + }
225 +};
226 +
227 +const intervalId = setInterval(poll, 3000); // 3秒轮询一次,避免过于频繁
228 +
229 +onUnmounted(() => {
230 + clearInterval(intervalId);
231 +});
232 +
233 +const toRecord = () => {
234 + go('/bookingList');
235 +}
236 +</script>
237 +
238 +<style lang="less">
239 +.qr-code-page {
240 + .qrcode-content {
241 + padding: 1rem 0;
242 + display: flex;
243 + flex-direction: column;
244 + justify-content: center;
245 + align-items: center;
246 + background-color: #FFF;
247 + border-radius: 8px;
248 + box-shadow: 0rem 0rem 0.92rem 0rem rgba(106,106,106,0.27);
249 +
250 + .user-info {
251 + color: #A6A6A6;
252 + font-size: 1.15rem;
253 + margin-top: 0.5rem;
254 + margin-bottom: 0.5rem;
255 + }
256 + .user-qrcode {
257 + display: flex;
258 + align-items: center;
259 + .left {
260 + image {
261 + width: 1.75rem; height: 1.75rem; margin-right: 0.5rem;
262 + }
263 + }
264 + .center {
265 + border: 1px solid #D1D1D1;
266 + border-radius: 20px;
267 + padding: 0.5rem;
268 + position: relative;
269 + image {
270 + width: 15rem; height: 15rem;
271 + }
272 + .qrcode-used {
273 + position: absolute;
274 + top: 0;
275 + left: 0;
276 + right: 0;
277 + bottom: 0;
278 + border-radius: 20px;
279 + overflow: hidden;
280 +
281 + .overlay {
282 + width: 100%;
283 + height: 100%;
284 + background-image: url('https://cdn.ipadbiz.cn/xys/booking/southeast.jpeg');
285 + background-size: contain;
286 + opacity: 0.9;
287 + }
288 +
289 + .status-text {
290 + color: #A67939;
291 + position: absolute;
292 + top: 50%;
293 + left: 50%;
294 + transform: translate(-50%, -50%);
295 + font-size: 1.2rem;
296 + white-space: nowrap;
297 + font-weight: bold;
298 + z-index: 10;
299 + }
300 + }
301 + }
302 + .right {
303 + image {
304 + width: 1.75rem; height: 1.75rem;
305 + margin-left: 0.5rem;
306 + }
307 + }
308 + }
309 + }
310 + .user-list {
311 + display: flex;
312 + padding: 1rem;
313 + align-items: center;
314 + flex-wrap: wrap;
315 + .user-item {
316 + position: relative;
317 + padding: 0.25rem 0.5rem;
318 + border: 1px solid #A67939;
319 + margin: 0.25rem;
320 + border-radius: 5px;
321 + color: #A67939;
322 + &.checked {
323 + color: #FFF;
324 + background-color: #A67939;
325 + }
326 + &.border {
327 + margin-right: 0.5rem;
328 + &::after {
329 + position: absolute;
330 + right: -0.5rem;
331 + top: calc(50% - 0.5rem);
332 + content: '';
333 + height: 1rem;
334 + border-right: 1px solid #A67939;
335 + }
336 + }
337 + }
338 + }
339 +
340 + .no-qrcode {
341 + display: flex;
342 + justify-content: center;
343 + align-items: center;
344 + flex-direction: column;
345 + margin-bottom: 1rem;
346 +
347 + .no-qrcode-title {
348 + color: #A67939;
349 + font-size: 1.05rem;
350 + }
351 + }
352 +}
353 +</style>
1 +<!--
2 + * @Date: 2024-01-16 10:06:47
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-12-26 12:35:01
5 + * @FilePath: /xyxBooking-weapp/src/components/qrCodeSearch.vue
6 + * @Description: 预约码卡组件
7 +-->
8 +<template>
9 + <view class="qr-code-page">
10 + <view v-if="userinfo.qr_code" class="show-qrcode">
11 + <view class="qrcode-content">
12 + <view class="user-info">{{ userinfo.name }}&nbsp;{{ userinfo.id }}</view>
13 + <view class="user-qrcode">
14 + <view class="left">
15 + <!-- <image src="https://cdn.ipadbiz.cn/xys/booking/%E5%B7%A6@2x.png"> -->
16 + </view>
17 + <view class="center">
18 + <image :src="userinfo.qr_code_url" mode="aspectFit" />
19 + <view v-if="useStatus === STATUS_CODE.CANCELED || useStatus === STATUS_CODE.USED" class="qrcode-used">
20 + <view class="overlay"></view>
21 + <text class="status-text">二维码{{ qr_code_status[useStatus] }}</text>
22 + </view>
23 + </view>
24 + <view class="right">
25 + <!-- <image src="https://cdn.ipadbiz.cn/xys/booking/%E5%8F%B3@2x.png"> -->
26 + </view>
27 + </view>
28 + <view style="color: red; margin-top: 1rem;">{{ userinfo.datetime }}</view>
29 + </view>
30 + </view>
31 + <view v-else class="no-qrcode">
32 + <image src="https://cdn.ipadbiz.cn/xys/booking/%E6%9A%82%E6%97%A0@2x.png" style="width: 10rem; height: 10rem;" />
33 + <view class="no-qrcode-title">您还没有预约过今天参观</view>
34 + </view>
35 + </view>
36 +</template>
37 +
38 +<script setup>
39 +import { ref, onMounted } from 'vue'
40 +import { formatDatetime } from '@/utils/tools';
41 +import { qrcodeStatusAPI, queryQrCodeAPI } from '@/api/index'
42 +import BASE_URL from '@/utils/config';
43 +
44 +const props = defineProps({
45 + id: {
46 + type: String,
47 + default: ''
48 + },
49 +});
50 +
51 +const userinfo = ref({});
52 +
53 +function replaceMiddleCharacters(inputString) {
54 + if (!inputString || inputString.length < 15) {
55 + return inputString;
56 + }
57 + const start = Math.floor((inputString.length - 8) / 2);
58 + const end = start + 8;
59 + const replacement = '*'.repeat(8);
60 + return inputString.substring(0, start) + replacement + inputString.substring(end);
61 +}
62 +
63 +const formatId = (id) => replaceMiddleCharacters(id);
64 +
65 +const useStatus = ref('0');
66 +
67 +const qr_code_status = {
68 + '1': '未激活',
69 + '3': '待使用',
70 + '5': '被取消',
71 + '7': '已使用',
72 +};
73 +
74 +const STATUS_CODE = {
75 + APPLY: '1',
76 + SUCCESS: '3',
77 + CANCELED: '5',
78 + USED: '7',
79 +};
80 +
81 +onMounted(async () => {
82 + if (props.id) {
83 + const { code, data } = await queryQrCodeAPI({ id_number: props.id });
84 + if (code) {
85 + // data 可能是一个对象
86 + const item = data;
87 + item.qr_code_url = BASE_URL + '/admin?m=srv&a=get_qrcode&key=' + item.qr_code;
88 + item.datetime = formatDatetime({ begin_time: item.begin_time, end_time: item.end_time });
89 + item.id = formatId(item.id_number);
90 + userinfo.value = item;
91 +
92 + const { code: status_code, data: status_data } = await qrcodeStatusAPI({ qr_code: item.qr_code });
93 + if (status_code) {
94 + useStatus.value = status_data.status;
95 + }
96 + }
97 + }
98 +})
99 +</script>
100 +
101 +<style lang="less">
102 +.qr-code-page {
103 + .qrcode-content {
104 + padding: 1rem 0;
105 + display: flex;
106 + flex-direction: column;
107 + justify-content: center;
108 + align-items: center;
109 + background-color: #FFF;
110 + border-radius: 8px;
111 + box-shadow: 0rem 0rem 0.92rem 0rem rgba(106,106,106,0.27);
112 +
113 + .user-info {
114 + color: #A6A6A6;
115 + font-size: 1.15rem;
116 + margin-top: 0.5rem;
117 + margin-bottom: 0.5rem;
118 + }
119 + .user-qrcode {
120 + display: flex;
121 + align-items: center;
122 + .center {
123 + border: 1px solid #D1D1D1;
124 + border-radius: 20px;
125 + padding: 0.5rem;
126 + position: relative;
127 + image {
128 + width: 15rem; height: 15rem;
129 + }
130 + .qrcode-used {
131 + position: absolute;
132 + top: 0;
133 + left: 0;
134 + right: 0;
135 + bottom: 0;
136 + border-radius: 20px;
137 + overflow: hidden;
138 +
139 + .overlay {
140 + width: 100%;
141 + height: 100%;
142 + background-image: url('https://cdn.ipadbiz.cn/xys/booking/southeast.jpeg');
143 + background-size: contain;
144 + opacity: 0.9;
145 + }
146 +
147 + .status-text {
148 + color: #A67939;
149 + position: absolute;
150 + top: 50%;
151 + left: 50%;
152 + transform: translate(-50%, -50%);
153 + font-size: 1.2rem;
154 + white-space: nowrap;
155 + font-weight: bold;
156 + z-index: 10;
157 + }
158 + }
159 + }
160 + }
161 + }
162 +
163 + .no-qrcode {
164 + display: flex;
165 + justify-content: center;
166 + align-items: center;
167 + flex-direction: column;
168 + margin-bottom: 1rem;
169 +
170 + .no-qrcode-title {
171 + color: #A67939;
172 + font-size: 1.05rem;
173 + }
174 + }
175 +}
176 +</style>
1 +<!--
2 + * @Date: 2024-01-24 16:38:13
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-01-30 15:19:44
5 + * @FilePath: /xyxBooking-weapp/src/components/reserveCard.vue
6 + * @Description: 预约记录卡组件
7 +-->
8 +<template>
9 + <view class="booking-list-item" @tap="goToDetail(reserve_info)">
10 + <view class="booking-list-item-header">
11 + <view>{{ reserve_info.booking_time }}</view>
12 + <view :class="[formatStatus(reserve_info.status)?.key, 'status']">{{ formatStatus(reserve_info.status)?.value }}</view>
13 + </view>
14 + <view class="booking-list-item-body">
15 + <view class="booking-num">
16 + <view class="num-body van-ellipsis">预约人数:<text>{{ reserve_info.total_qty }} 人</text>&nbsp;<text>({{ reserve_info.person_name }})</text></view>
17 + <view v-if="(reserve_info.status === CodeStatus.SUCCESS || reserve_info.status === CodeStatus.USED || reserve_info.status === CodeStatus.CANCEL)">
18 + <nut-icon name="rect-right" />
19 + </view>
20 + </view>
21 + <view class="booking-price">支付金额:<text>¥ {{ reserve_info.total_amt }}</text></view>
22 + <view class="booking-time">下单时间:<text>{{ reserve_info.order_time }}</text></view>
23 + </view>
24 + <view class="booking-list-item-footer">
25 + <!-- 倒计时逻辑省略,如果需要可添加 -->
26 + </view>
27 + </view>
28 +</template>
29 +
30 +<script setup>
31 +import { computed } from 'vue'
32 +import { useGo } from '@/hooks/useGo'
33 +
34 +const go = useGo();
35 +
36 +const props = defineProps({
37 + data: {
38 + type: Object,
39 + default: {},
40 + },
41 +});
42 +
43 +const reserve_info = computed(() => props.data);
44 +
45 +const CodeStatus = {
46 + APPLY: '1',
47 + PAYING: '2',
48 + SUCCESS: '3',
49 + CANCEL: '5',
50 + CANCELED: '7',
51 + USED: '9',
52 + REFUNDING: '11'
53 +}
54 +
55 +const formatStatus = (status) => {
56 + switch (status) {
57 + case CodeStatus.APPLY:
58 + return {
59 + key: 'cancel',
60 + value: '待支付'
61 + }
62 + case CodeStatus.PAYING:
63 + return {
64 + key: 'success',
65 + value: '支付中'
66 + }
67 + case CodeStatus.SUCCESS:
68 + return {
69 + key: 'success',
70 + value: '预约成功'
71 + }
72 + case CodeStatus.CANCEL:
73 + return {
74 + key: 'cancel',
75 + value: '已取消'
76 + }
77 + case CodeStatus.CANCELED:
78 + return {
79 + key: 'cancel',
80 + value: '已取消'
81 + }
82 + case CodeStatus.USED:
83 + return {
84 + key: 'used',
85 + value: '已使用'
86 + }
87 + case CodeStatus.REFUNDING:
88 + return {
89 + key: 'cancel',
90 + value: '退款中'
91 + }
92 + default:
93 + return { key: '', value: '' }
94 + }
95 +}
96 +
97 +const goToDetail = (item) => {
98 + // 只有成功、已使用、已取消(退款成功)才跳转详情
99 + if (item.status === CodeStatus.SUCCESS || item.status === CodeStatus.USED || item.status === CodeStatus.CANCEL) {
100 + go('/bookingDetail', { pay_id: item.pay_id });
101 + }
102 +}
103 +</script>
104 +
105 +<style lang="less">
106 +.booking-list-item {
107 + background-color: #FFF;
108 + border-radius: 8px;
109 + padding: 1rem;
110 + margin-bottom: 1rem;
111 + box-shadow: 0rem 0rem 0.92rem 0rem rgba(106,106,106,0.1);
112 +
113 + .booking-list-item-header {
114 + display: flex;
115 + justify-content: space-between;
116 + align-items: center;
117 + padding-bottom: 0.5rem;
118 + border-bottom: 1px dashed #E6E6E6;
119 + margin-bottom: 0.5rem;
120 + font-size: 1rem;
121 + font-weight: bold;
122 + color: #333;
123 +
124 + .status {
125 + font-size: 0.85rem;
126 + font-weight: normal;
127 + padding: 2px 6px;
128 + border-radius: 4px;
129 +
130 + &.success {
131 + color: #A67939;
132 + background-color: #FBEEDC;
133 + }
134 + &.cancel {
135 + color: #999;
136 + background-color: #EEE;
137 + }
138 + &.used {
139 + color: #477F3D;
140 + background-color: #E5EFE3;
141 + }
142 + }
143 + }
144 +
145 + .booking-list-item-body {
146 + .booking-num {
147 + display: flex;
148 + justify-content: space-between;
149 + align-items: center;
150 + margin-bottom: 0.5rem;
151 + color: #666;
152 +
153 + .num-body {
154 + span, text {
155 + color: #A67939;
156 + font-weight: bold;
157 + }
158 + }
159 + }
160 + .booking-price, .booking-time {
161 + color: #999;
162 + font-size: 0.9rem;
163 + margin-bottom: 0.3rem;
164 + text {
165 + color: #333;
166 + }
167 + }
168 + }
169 +}
170 +</style>
1 +import Taro from '@tarojs/taro';
2 +
3 +/**
4 + * 封装路由跳转方便行内调用
5 + * @returns
6 + */
7 +export function useGo () {
8 + function go (path, query = {}) {
9 + // 补全路径,如果是 / 开头,去掉 /
10 + let url = path.startsWith('/') ? path.substring(1) : path;
11 + // 检查是否是 tabbar 页面 (目前没有配置 tabbar,所以都是普通跳转)
12 + // 如果是页面,加上 pages/ 前缀 (假设都在 pages 下,且目录名和 path 一致)
13 + // H5 path 是 /notice,小程序是 pages/notice/index
14 + if (!url.startsWith('pages/')) {
15 + url = `pages/${url}/index`; // 适配 pages/notice/index 结构
16 + }
17 +
18 + // 构建 query string
19 + let queryString = Object.keys(query).map(key => key + '=' + query[key]).join('&');
20 + if (queryString) {
21 + url += '?' + queryString;
22 + }
23 +
24 + Taro.navigateTo({
25 + url: '/' + url,
26 + fail: (err) => {
27 + // 如果是 tabbar 页面,尝试 switchTab
28 + if (err.errMsg && err.errMsg.indexOf('tabbar') !== -1) {
29 + Taro.switchTab({ url: '/' + url });
30 + } else {
31 + console.error('Navigation failed', err);
32 + }
33 + }
34 + })
35 + }
36 + return go
37 +}
38 +
39 +export function useReplace () {
40 + function replace (path, query = {}) {
41 + let url = path.startsWith('/') ? path.substring(1) : path;
42 + if (!url.startsWith('pages/')) {
43 + url = `pages/${url}/index`;
44 + }
45 +
46 + let queryString = Object.keys(query).map(key => key + '=' + query[key]).join('&');
47 + if (queryString) {
48 + url += '?' + queryString;
49 + }
50 +
51 + Taro.redirectTo({
52 + url: '/' + url
53 + })
54 + }
55 + return replace
56 +}
1 +<!--
2 + * @Date: 2024-01-15 16:35:10
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-01-29 17:35:19
5 + * @FilePath: /xyxBooking-weapp/src/pages/addVisitor/index.vue
6 + * @Description: 添加参观者
7 +-->
8 +<template>
9 + <view class="add-visitor-page">
10 + <nut-form>
11 + <nut-form-item label="姓名">
12 + <nut-input v-model="name" placeholder="请输入参观者姓名" type="text" />
13 + </nut-form-item>
14 + <nut-form-item label="证件类型">
15 + <view style="padding: 10px 0;">身份证</view>
16 + </nut-form-item>
17 + <nut-form-item label="证件号">
18 + <nut-input v-model="id_number" placeholder="请输入参观者证件号" type="idcard" />
19 + </nut-form-item>
20 + </nut-form>
21 +
22 + <view style="padding: 1rem;">
23 + <nut-button type="primary" block color="#A67939" @click="save">保存</nut-button>
24 + </view>
25 +
26 + <view v-if="visitorList.length" class="history-list">
27 + <view class="title">历史参观者</view>
28 + <view v-for="(item, index) in visitorList" :key="index" class="item">
29 + <view class="info">
30 + <view class="name">{{ item.name }}</view>
31 + <view class="id">{{ formatId(item.id_number) }}</view>
32 + </view>
33 + <view class="action" @tap="delVisitor(item.id)">删除</view>
34 + </view>
35 + </view>
36 + </view>
37 +</template>
38 +
39 +<script setup>
40 +import { ref } from 'vue'
41 +import Taro, { useDidShow } from '@tarojs/taro'
42 +import { personListAPI, addPersonAPI, delPersonAPI } from '@/api/index'
43 +
44 +const name = ref('');
45 +const id_number = ref('');
46 +const visitorList = ref([]);
47 +
48 +// 身份证校验
49 +const checkIDCard = (idcode) => {
50 + // 简单校验
51 + return /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(idcode);
52 +}
53 +
54 +function replaceMiddleCharacters(inputString) {
55 + if (!inputString || inputString.length < 15) {
56 + return inputString;
57 + }
58 + const start = Math.floor((inputString.length - 8) / 2);
59 + const end = start + 8;
60 + const replacement = '*'.repeat(8);
61 + return inputString.substring(0, start) + replacement + inputString.substring(end);
62 +}
63 +
64 +const formatId = (id) => replaceMiddleCharacters(id);
65 +
66 +const loadList = async () => {
67 + const { code, data } = await personListAPI({});
68 + if (code) {
69 + visitorList.value = data;
70 + }
71 +}
72 +
73 +const save = async () => {
74 + if (!name.value) {
75 + Taro.showToast({ title: '请输入姓名', icon: 'none' });
76 + return;
77 + }
78 + if (!checkIDCard(id_number.value)) {
79 + Taro.showToast({ title: '请输入正确的身份证号', icon: 'none' });
80 + return;
81 + }
82 +
83 + Taro.showLoading({ title: '保存中' });
84 + const { code, msg } = await addPersonAPI({
85 + name: name.value,
86 + id_type: 1, // 身份证
87 + id_number: id_number.value
88 + });
89 + Taro.hideLoading();
90 +
91 + if (code) {
92 + Taro.showToast({ title: '添加成功' });
93 + name.value = '';
94 + id_number.value = '';
95 + loadList();
96 + // 自动返回上一页? H5 没有自动返回
97 + Taro.navigateBack();
98 + } else {
99 + Taro.showToast({ title: msg || '添加失败', icon: 'none' });
100 + }
101 +}
102 +
103 +const delVisitor = async (id) => {
104 + const { confirm } = await Taro.showModal({ title: '提示', content: '确定删除该参观者吗?' });
105 + if (confirm) {
106 + const { code, msg } = await delPersonAPI({ person_id: id });
107 + if (code) {
108 + Taro.showToast({ title: '删除成功' });
109 + loadList();
110 + } else {
111 + Taro.showToast({ title: msg || '删除失败', icon: 'none' });
112 + }
113 + }
114 +}
115 +
116 +useDidShow(() => {
117 + loadList();
118 +})
119 +</script>
120 +
121 +<style lang="less">
122 +.add-visitor-page {
123 + min-height: 100vh;
124 + background-color: #F6F6F6;
125 + padding-top: 1px;
126 +
127 + .history-list {
128 + margin-top: 1rem;
129 + background-color: #FFF;
130 + padding: 1rem;
131 +
132 + .title {
133 + font-size: 1rem;
134 + color: #333;
135 + margin-bottom: 1rem;
136 + border-left: 3px solid #A67939;
137 + padding-left: 0.5rem;
138 + }
139 +
140 + .item {
141 + display: flex;
142 + justify-content: space-between;
143 + align-items: center;
144 + padding: 1rem 0;
145 + border-bottom: 1px solid #EEE;
146 +
147 + &:last-child {
148 + border-bottom: none;
149 + }
150 +
151 + .info {
152 + .name {
153 + font-size: 1rem;
154 + color: #333;
155 + margin-bottom: 0.3rem;
156 + }
157 + .id {
158 + font-size: 0.9rem;
159 + color: #999;
160 + }
161 + }
162 +
163 + .action {
164 + color: #FF0000;
165 + font-size: 0.9rem;
166 + }
167 + }
168 + }
169 +}
170 +</style>
...@@ -2,30 +2,22 @@ ...@@ -2,30 +2,22 @@
2 * @Date: 2022-09-19 14:11:06 2 * @Date: 2022-09-19 14:11:06
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 * @LastEditTime: 2024-05-26 10:17:04 4 * @LastEditTime: 2024-05-26 10:17:04
5 - * @FilePath: /meihuaApp/src/pages/auth/index.vue 5 + * @FilePath: /xyxBooking-weapp/src/pages/auth/index.vue
6 * @Description: 文件描述 6 * @Description: 文件描述
7 --> 7 -->
8 <template> 8 <template>
9 - <div> 9 + <view class="auth-page">
10 - <!-- <button wx:if="{{canIUse}}" open-type="getUserInfo" @getuserinfo="bindGetUserInfo">授权登录</button> 10 + <view class="loading">
11 - <view @tap="auth">授权登陆</view> --> 11 + <view>正在授权登录...</view>
12 - </div> 12 + </view>
13 + </view>
13 </template> 14 </template>
14 15
15 <script setup> 16 <script setup>
16 -import Taro from '@tarojs/taro' 17 +import Taro, { useDidShow } from '@tarojs/taro'
17 -import { ref } from "vue";
18 import request from '@/utils/request'; 18 import request from '@/utils/request';
19 19
20 -</script> 20 +useDidShow(() => {
21 -
22 -<script>
23 -import "./index.less";
24 -import { getCurrentPageParam } from "@/utils/weapp";
25 -
26 -export default {
27 - name: "authPage",
28 - mounted () {
29 // 授权登陆 21 // 授权登陆
30 Taro.login({ 22 Taro.login({
31 success: function (res) { 23 success: function (res) {
...@@ -36,134 +28,61 @@ export default { ...@@ -36,134 +28,61 @@ export default {
36 }) 28 })
37 request.post('/srv/?a=openid', { 29 request.post('/srv/?a=openid', {
38 code: res.code 30 code: res.code
39 - // openid: '0002'
40 - // openid: 'o5NFZ5cFQtLRy3aVHaZMLkjHFusI'
41 - // openid: 'o5NFZ5TpgG4FwYursGCLjcUJH2ak'
42 - // openid: 'o5NFZ5cqroPYwawCp8FEOxewtgnw'
43 }) 31 })
44 .then(res => { 32 .then(res => {
45 - if (res.data.code) { 33 + if (res.data.code === 1) { // 成功
46 - var cookie = res.cookies[0]; 34 + var cookie = res.cookies ? res.cookies[0] : (res.header['Set-Cookie'] || res.header['set-cookie']);
47 - if (cookie != null) { 35 + // 有些时候 cookie 可能是一个数组,或者分号分隔的字符串
48 - wx.setStorageSync("sessionid", res.cookies[0]);//服务器返回的 Set-Cookie,保存到本地 36 + // axios-miniprogram 返回的 cookies 应该是一个数组
49 - //TAG 小程序绑定cookie 37 +
50 - // 修改请求头 38 + if (cookie) {
51 - request.defaults.headers.cookie = res.cookies[0]; 39 + // 处理 cookie 格式,如果包含分号,取第一个
52 - // if (res.data.data.avatar) { 40 + if (Array.isArray(cookie)) cookie = cookie[0];
53 - // Taro.reLaunch({ 41 + // 简单处理,通常服务器返回 PHPSESSID=xxx; path=/
54 - // url: '../../' + getCurrentPageParam().url 42 + // 我们存入 storage
55 - // }) 43 + Taro.setStorageSync("sessionid", cookie);
56 - // } else { // 头像没有设置跳转完善信息页面 44 +
57 - // Taro.redirectTo({ 45 + // 返回上一页
58 - // url: '../apxUserInfo/index'
59 - // })
60 - // }
61 - // TAG:处理分享跳转问题
62 - const params = getCurrentPageParam();
63 - if (getCurrentPageParam().url === 'pages/detail/index') { // 详情页的分享跳转处理
64 - Taro.reLaunch({
65 - url: `../../${params.url}?id=${params.id}&start_date=${params.start_date}&end_date=${params.end_date}`
66 - })
67 - } else { // 其他页面分享跳首页
68 - Taro.reLaunch({
69 - url: `/pages/index/index?first_in=${wx.getStorageSync("first_in")}`
70 - })
71 - }
72 Taro.hideLoading(); 46 Taro.hideLoading();
47 + Taro.navigateBack({
48 + fail: () => {
49 + Taro.reLaunch({ url: '/pages/index/index' })
50 + }
51 + });
52 + } else {
53 + console.warn('No cookie received');
54 + Taro.hideLoading();
55 + Taro.showToast({ title: '授权失败: 无Cookie', icon: 'none' });
73 } 56 }
74 } else { 57 } else {
75 console.warn(res.data.msg); 58 console.warn(res.data.msg);
76 Taro.hideLoading(); 59 Taro.hideLoading();
60 + Taro.showToast({ title: res.data.msg || '授权失败', icon: 'none' });
77 } 61 }
78 }) 62 })
79 .catch(err => { 63 .catch(err => {
80 console.error(err); 64 console.error(err);
81 Taro.hideLoading(); 65 Taro.hideLoading();
66 + Taro.showToast({ title: '网络错误', icon: 'none' });
82 }); 67 });
83 } else { 68 } else {
84 console.log('登录失败!' + res.errMsg) 69 console.log('登录失败!' + res.errMsg)
70 + Taro.showToast({ title: '微信登录失败', icon: 'none' });
85 } 71 }
86 } 72 }
87 }) 73 })
88 - }, 74 +})
89 - data () {
90 - return {
91 - canIUse: wx.canIUse('button.open-type.getUserInfo')
92 - }
93 - },
94 - onLoad: function() {
95 - // 查看是否授权
96 - // wx.getSetting({
97 - // success (res){
98 - // if (res.authSetting['scope.userInfo']) {
99 - // // 已经授权,可以直接调用 getUserInfo 获取头像昵称
100 - // wx.getUserInfo({
101 - // success: function(res) {
102 - // console.warn(res.userInfo)
103 - // }
104 - // })
105 - // }
106 - // }
107 - // })
108 - },
109 - methods: {
110 - bindGetUserInfo (e) {
111 - console.warn(e.detail.userInfo)
112 - },
113 - // auth () {
114 - // Taro.getSetting({
115 - // success: function (res) {
116 - // if (!res.authSetting['scope.userInfo']) {
117 - // console.warn(0);
118 - // Taro.authorize({
119 - // scope: 'scope.userInfo',
120 - // success: function () {
121 - // Taro.getUserInfo({
122 - // success: function(res) {
123 - // var userInfo = res.userInfo
124 - // console.warn(userInfo);
125 - // }
126 - // })
127 - // },
128 - // fail: function (error) {
129 - // console.error(error)
130 - // }
131 - // })
132 - // }
133 - // }
134 - // })
135 - // }
136 - auth () {
137 - // wx.getSetting({
138 - // success (res){
139 - // if (res.authSetting['scope.userInfo']) {
140 - // // 已经授权,可以直接调用 getUserInfo 获取头像昵称
141 - // wx.getUserInfo({
142 - // success: function(res) {
143 - // console.warn(res.userInfo)
144 - // }
145 - // })
146 - // }
147 - // }
148 - // })
149 - wx.getSetting({
150 - success(res) {
151 - if (!res.authSetting['scope.userInfo']) {
152 - wx.authorize({
153 - scope: 'scope.userInfo',
154 - success () {
155 - // 已经授权,可以直接调用 getUserInfo 获取头像昵称
156 - wx.getUserInfo({
157 - success: function(res) {
158 - console.warn(res.userInfo)
159 - }
160 - })
161 - }
162 - })
163 - }
164 - }
165 - })
166 - }
167 - }
168 -};
169 </script> 75 </script>
76 +
77 +<style lang="less">
78 +.auth-page {
79 + height: 100vh;
80 + display: flex;
81 + align-items: center;
82 + justify-content: center;
83 + .loading {
84 + text-align: center;
85 + color: #999;
86 + }
87 +}
88 +</style>
......
This diff is collapsed. Click to expand it.
1 +<!--
2 + * @Date: 2024-01-16 10:06:47
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-01-30 17:48:42
5 + * @FilePath: /xyxBooking-weapp/src/pages/bookingCode/index.vue
6 + * @Description: 文件描述
7 +-->
8 +<template>
9 + <view class="booking-code-page">
10 + <view style="padding: 1rem;">
11 + <qrCode></qrCode>
12 + <view class="warning">
13 + <view><nut-icon name="tips" />&nbsp;温馨提示</view>
14 + <view style="margin-top: 0.5rem;">一人一码,扫码或识别身份证成功后进入</view>
15 + <view style="height: 8rem;"></view>
16 + </view>
17 + </view>
18 + <view class="index-nav">
19 + <view class="nav-logo" @tap="toHome">
20 + <image :src="icon_3" style="width: 1.5rem; height: 1.5rem;" />
21 + 首页
22 + </view>
23 + <view class="nav-logo">
24 + <image :src="icon_4" style="width: 1.5rem; height: 1.5rem; margin-bottom: 0.1rem;" />
25 + 预约码
26 + </view>
27 + <view class="nav-logo" @tap="toMy">
28 + <image :src="icon_5" style="width: 1.5rem; height: 1.5rem;" />
29 + 我的
30 + </view>
31 + </view>
32 + </view>
33 +</template>
34 +
35 +<script setup>
36 +import { ref } from 'vue'
37 +import Taro from '@tarojs/taro'
38 +import qrCode from '@/components/qrCode';
39 +import icon_3 from '@/assets/images/首页01@2x.png'
40 +import icon_4 from '@/assets/images/二维码icon.png'
41 +import icon_5 from '@/assets/images/我的01@2x.png'
42 +import { useGo } from '@/hooks/useGo'
43 +
44 +const go = useGo();
45 +
46 +const toMy = () => { // 跳转到我的
47 + go('/pages/me/index');
48 +}
49 +const toHome = () => { // 跳转到首页
50 + go('/pages/index/index');
51 +}
52 +
53 +</script>
54 +
55 +<style lang="less">
56 +.booking-code-page {
57 + position: relative;
58 + min-height: 100vh;
59 + background-color: #F6F6F6;
60 +
61 + .warning {
62 + text-align: center;
63 + color: #A67939;
64 + margin-top: 1rem;
65 + }
66 +
67 + .index-nav {
68 + position: fixed;
69 + bottom: 0;
70 + left: 0;
71 + width: 100vw;
72 + height: 10vh;
73 + background: #FFFFFF;
74 + box-shadow: 0rem -0.33rem 0.25rem 0rem rgba(0,0,0,0.12);
75 + display: flex;
76 + align-items: center;
77 + justify-content: space-around;
78 + color: #A67939;
79 + .nav-logo {
80 + position: relative;
81 + display: flex;
82 + flex-direction: column;
83 + align-items: center;
84 + }
85 + }
86 +}
87 +</style>
1 +<!--
2 + * @Date: 2024-01-16 13:19:23
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-01-30 16:10:10
5 + * @FilePath: /xyxBooking-weapp/src/pages/bookingDetail/index.vue
6 + * @Description: 预约记录详情
7 +-->
8 +<template>
9 + <view class="booking-detail-page">
10 + <qrCode :status="qrCodeStatus" type="detail" :payId="pay_id"></qrCode>
11 + <view v-if="billInfo.pay_id" class="detail-wrapper">
12 + <view class="detail-item">
13 + <view>参访时间:</view>
14 + <view>{{ billInfo?.datetime }}</view>
15 + </view>
16 + <view class="detail-item">
17 + <view>参访人数:</view>
18 + <view>{{ billInfo?.total_qty }} 人</view>
19 + </view>
20 + <view class="detail-item">
21 + <view>支付金额:</view>
22 + <view>¥ {{ billInfo?.total_amt }}</view>
23 + </view>
24 + <view class="detail-item">
25 + <view>下单时间:</view>
26 + <view>{{ billInfo?.order_time }}</view>
27 + </view>
28 + <view class="detail-item">
29 + <view>订单编号:</view>
30 + <view>{{ billInfo?.pay_id }}</view>
31 + </view>
32 + <view class="detail-item">
33 + <view>订单状态:</view>
34 + <view>{{ qrCodeStatusText }}</view>
35 + </view>
36 + </view>
37 + <view style="height: 5rem;"></view>
38 + <view v-if="billInfo.status === CodeStatus.SUCCESS && billInfo.show_cancel_reserve === 1" class="cancel-wrapper">
39 + <view @tap="cancelBooking" class="cancel-btn ">取消预约</view>
40 + </view>
41 + </view>
42 +</template>
43 +
44 +<script setup>
45 +import { ref, computed } from 'vue'
46 +import Taro, { useDidShow, useRouter as useTaroRouter } from '@tarojs/taro'
47 +import { useGo } from '@/hooks/useGo'
48 +import qrCode from '@/components/qrCode';
49 +import { billInfoAPI, icbcRefundAPI } from '@/api/index'
50 +import { formatDatetime } from '@/utils/tools';
51 +
52 +const router = useTaroRouter();
53 +const go = useGo();
54 +
55 +const pay_id = ref('');
56 +const qrCodeStatus = ref('');
57 +const billInfo = ref({});
58 +
59 +const CodeStatus = {
60 + APPLY: '1',
61 + PAYING: '2',
62 + SUCCESS: '3',
63 + CANCEL: '5',
64 + CANCELED: '7',
65 + USED: '9',
66 + REFUNDING: '11'
67 +}
68 +
69 +const qrCodeStatusText = computed(() => {
70 + const status = billInfo.value?.status;
71 + switch (status) {
72 + case CodeStatus.SUCCESS: return '预约成功';
73 + case CodeStatus.CANCEL: return '已取消';
74 + case CodeStatus.USED: return '已使用';
75 + case CodeStatus.REFUNDING: return '退款中';
76 + default: return '未知状态';
77 + }
78 +})
79 +
80 +const cancelBooking = async () => {
81 + const { confirm } = await Taro.showModal({
82 + title: '温馨提示',
83 + content: '是否取消预约?',
84 + confirmColor: '#A67939'
85 + });
86 +
87 + if (confirm) {
88 + Taro.showLoading({ title: '取消中...' });
89 + const { code, data } = await icbcRefundAPI({ pay_id: pay_id.value });
90 + Taro.hideLoading();
91 + if (code) {
92 + Taro.showToast({ title: '取消成功' });
93 + Taro.navigateBack();
94 + } else {
95 + Taro.showToast({ title: '取消失败', icon: 'none' });
96 + }
97 + }
98 +}
99 +
100 +useDidShow(async () => {
101 + pay_id.value = router.params.pay_id;
102 + if (pay_id.value) {
103 + const { code, data } = await billInfoAPI({ pay_id: pay_id.value });
104 + if (code) {
105 + data.datetime = data && formatDatetime(data);
106 + data.order_time = data.created_time ? data.created_time.slice(0, -3) : '';
107 + billInfo.value = data;
108 + }
109 + }
110 +})
111 +</script>
112 +
113 +<style lang="less">
114 +.booking-detail-page {
115 + min-height: 100vh;
116 + background-color: #F6F6F6;
117 + padding: 1rem;
118 +
119 + .detail-wrapper {
120 + background-color: #FFF;
121 + border-radius: 8px;
122 + padding: 1rem;
123 + margin-top: 1rem;
124 + box-shadow: 0rem 0rem 0.92rem 0rem rgba(106,106,106,0.1);
125 +
126 + .detail-item {
127 + display: flex;
128 + justify-content: space-between;
129 + margin-bottom: 0.8rem;
130 + color: #333;
131 + font-size: 0.95rem;
132 +
133 + &:last-child {
134 + margin-bottom: 0;
135 + }
136 +
137 + view:first-child {
138 + color: #999;
139 + width: 5rem;
140 + }
141 + view:last-child {
142 + flex: 1;
143 + text-align: right;
144 + }
145 + }
146 + }
147 +
148 + .cancel-wrapper {
149 + position: fixed;
150 + bottom: 0;
151 + left: 0;
152 + width: 100vw;
153 + background-color: #FFF;
154 + padding: 1rem;
155 + box-sizing: border-box;
156 +
157 + .cancel-btn {
158 + background-color: #FFF;
159 + color: #A67939;
160 + border: 1px solid #A67939;
161 + text-align: center;
162 + padding: 0.8rem 0;
163 + border-radius: 8px;
164 + font-size: 1.1rem;
165 + }
166 + }
167 +}
168 +</style>
1 +<!--
2 + * @Date: 2024-01-16 11:37:10
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-01-30 15:20:12
5 + * @FilePath: /xyxBooking-weapp/src/pages/bookingList/index.vue
6 + * @Description: 预约记录列表页
7 +-->
8 +<template>
9 + <view class="booking-list-page">
10 + <view v-for="(item, index) in bookingList" :key="index">
11 + <reserveCard :data="item" />
12 + </view>
13 +
14 + <view v-if="loading" style="text-align: center; color: #999; padding: 10px;">加载中...</view>
15 + <view v-if="finished && bookingList.length > 0" style="text-align: center; color: #999; padding: 10px;">没有更多了</view>
16 +
17 + <view v-if="!bookingList.length && finished" class="no-qrcode">
18 + <image src="https://cdn.ipadbiz.cn/xys/booking/%E6%9A%82%E6%97%A0@2x.png" style="width: 10rem; height: 10rem;" />
19 + <view class="no-qrcode-title">您还没有预约过参观</view>
20 + </view>
21 + </view>
22 +</template>
23 +
24 +<script setup>
25 +import { ref } from 'vue'
26 +import Taro, { useDidShow, useReachBottom } from '@tarojs/taro'
27 +import { billListAPI } from '@/api/index'
28 +import { formatDatetime } from '@/utils/tools';
29 +import reserveCard from '@/components/reserveCard.vue'
30 +
31 +const page = ref(1);
32 +const limit = ref(5);
33 +const bookingList = ref([]);
34 +const loading = ref(false);
35 +const finished = ref(false);
36 +
37 +const loadData = async (isRefresh = false) => {
38 + if (loading.value || (finished.value && !isRefresh)) return;
39 +
40 + loading.value = true;
41 + if (isRefresh) {
42 + page.value = 1;
43 + finished.value = false;
44 + }
45 +
46 + const { code, data } = await billListAPI({ page: page.value, row_num: limit.value });
47 + loading.value = false;
48 +
49 + if (code) {
50 + data.forEach(item => {
51 + item.booking_time = item && formatDatetime(item);
52 + item.order_time = item.created_time ? item.created_time.slice(0, -3) : '';
53 + });
54 +
55 + if (isRefresh) {
56 + bookingList.value = data;
57 + } else {
58 + bookingList.value = bookingList.value.concat(data);
59 + }
60 +
61 + if (data.length < limit.value) {
62 + finished.value = true;
63 + } else {
64 + page.value++;
65 + }
66 + }
67 +}
68 +
69 +useDidShow(() => {
70 + loadData(true);
71 +});
72 +
73 +useReachBottom(() => {
74 + loadData();
75 +});
76 +</script>
77 +
78 +<style lang="less">
79 +.booking-list-page {
80 + padding: 1rem;
81 + min-height: 100vh;
82 + background-color: #F6F6F6;
83 +
84 + .no-qrcode {
85 + display: flex;
86 + justify-content: center;
87 + align-items: center;
88 + flex-direction: column;
89 + padding-top: 5rem;
90 +
91 + .no-qrcode-title {
92 + color: #A67939;
93 + font-size: 1.05rem;
94 + margin-top: 1rem;
95 + }
96 + }
97 +}
98 +</style>
1 +export default {
2 + navigationBarTitleText: '反馈页'
3 +}
1 +<template>
2 + <view class="callback-page">
3 + <view>
4 + <view v-if="pay_status === PAY_STATUS.FAIL" class="text-prompts">
5 + <image src="https://cdn.ipadbiz.cn/xys/booking/shibai.png" mode="widthFix" class="status-icon"/>
6 + <view class="text">支付失败</view>
7 + </view>
8 + <view v-else class="text-prompts">
9 + <image src="https://cdn.ipadbiz.cn/xys/booking/%E6%88%90%E5%8A%9F@2x.png?imageMogr2/thumbnail/200x/strip/quality/70" mode="widthFix" class="status-icon"/>
10 + <!-- <view class="text">支付完成</view> -->
11 + </view>
12 + <view class="appointment-information">
13 + <view class="info-item">参观人数:<text>{{ billInfo?.total_qty || 0 }} 人</text></view>
14 + <view class="info-item">参访时间:<text>{{ billInfo?.datetime || '--' }}</text></view>
15 + <view class="info-item">支付金额:<text>¥ {{ billInfo?.total_amt || 0 }}</text></view>
16 + </view>
17 + <view style="padding: 0.5rem; display: flex; justify-content: center; margin-top: 1rem;">
18 + <nut-button color="#A67939" size="small" @click="returnMerchant">返回首页</nut-button>
19 + </view>
20 + </view>
21 + </view>
22 +</template>
23 +
24 +<script setup>
25 +import { ref, onMounted } from 'vue'
26 +import Taro, { useRouter } from '@tarojs/taro'
27 +import { onAuthBillInfoAPI } from '@/api/index'
28 +import { useGo } from '@/hooks/useGo'
29 +import { formatDatetime } from '@/utils/tools'
30 +
31 +const router = useRouter()
32 +const go = useGo()
33 +
34 +const billInfo = ref({})
35 +const PAY_STATUS = {
36 + SUCCESS: '0',
37 + FAIL: '1',
38 + UNKNOWN: '2',
39 +}
40 +const pay_status = ref('0') // Default to success as per logic
41 +
42 +const out_trade_no = router.params.out_trade_no
43 +
44 +const getBillInfo = async () => {
45 + if (!out_trade_no) return
46 +
47 + try {
48 + // Get order details
49 + const { code, data } = await onAuthBillInfoAPI({ order_id: out_trade_no })
50 + if (code && data) {
51 + data.datetime = data && formatDatetime(data)
52 + billInfo.value = data
53 + } else {
54 + // Handle error if needed
55 + }
56 + } catch (e) {
57 + console.error(e)
58 + }
59 +}
60 +
61 +const returnMerchant = () => {
62 + go('/pages/index/index')
63 +}
64 +
65 +onMounted(() => {
66 + getBillInfo()
67 +})
68 +</script>
69 +
70 +<style lang="less">
71 +.callback-page {
72 + padding: 1rem;
73 + background: #fff;
74 + min-height: 100vh;
75 +
76 + .text-prompts {
77 + display: flex;
78 + flex-direction: column;
79 + align-items: center;
80 + margin-bottom: 2rem;
81 + padding-top: 2rem;
82 +
83 + .status-icon {
84 + width: 100px;
85 + height: 100px;
86 + }
87 +
88 + .text {
89 + margin-top: 1rem;
90 + font-size: 1.2rem;
91 + color: #333;
92 + }
93 + }
94 +
95 + .appointment-information {
96 + background: #f8f8f8;
97 + padding: 1rem;
98 + border-radius: 8px;
99 +
100 + .info-item {
101 + margin-bottom: 0.5rem;
102 + font-size: 0.9rem;
103 + color: #666;
104 +
105 + text {
106 + color: #333;
107 + font-weight: 500;
108 + margin-left: 0.5rem;
109 + }
110 + }
111 + }
112 +}
113 +</style>
1 <!-- 1 <!--
2 - * @Date: 2025-06-28 10:33:00 2 + * @Date: 2023-06-21 10:23:09
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-07-01 11:13:13 4 + * @LastEditTime: 2024-02-05 18:36:21
5 - * @FilePath: /myApp/src/pages/index/index.vue 5 + * @FilePath: /xysBooking/src/views/index.vue
6 - * @Description: 文件描述 6 + * @Description: 预约页首页
7 --> 7 -->
8 <template> 8 <template>
9 - <view class="index"> 9 + <view class="index-page">
10 - <nut-button type="primary" @click="onClick">按钮</nut-button> 10 + <view class="index-content">
11 - <nut-toast v-model:visible="show" msg="你成功了" /> 11 + <view style="height: 30vh;">
12 - <View className="text-[#acc855] text-[100px]">Hello world!</View> 12 + <swiper class="my-swipe" :autoplay="true" :interval="3000" indicator-dots indicator-color="white" :circular="true">
13 + <swiper-item>
14 + <image style="height: 30vh; width: 100vw;" src="https://cdn.ipadbiz.cn/xys/booking/banner.jpg" mode="aspectFill" />
15 + </swiper-item>
16 + </swiper>
17 + </view>
18 +
19 + <view ref="root" class="index-circular">
20 + <view class="booking-wrapper">
21 + <view class="booking" @tap="toBooking">
22 + <view><image :src="icon_1" style="width: 3rem; height: 3rem;" /></view>
23 + <view style="color: #FFF;">开始预约</view>
24 + </view>
25 + </view>
26 + </view>
27 + <view class="logo"></view>
28 + </view>
29 + <view class="index-nav">
30 + <view class="nav-logo">
31 + <image :src="icon_3" style="width: 1.5rem; height: 1.5rem;" />
32 + 首页
33 + </view>
34 + <view class="nav-logo" @tap="toCode">
35 + <image :src="icon_4" style="width: 5rem; height: 5rem; position: absolute; top: -4rem;" />
36 + <!-- <van-icon size="1.5rem" name="wap-home" color="#FFF" style="opacity: 0;" /> -->
37 + 预约码
38 + </view>
39 + <view class="nav-logo" @tap="toMy">
40 + <image :src="icon_5" style="width: 1.5rem; height: 1.5rem;" />
41 + 我的
42 + </view>
43 + </view>
13 </view> 44 </view>
14 </template> 45 </template>
15 46
16 <script setup> 47 <script setup>
17 -import Taro from '@tarojs/taro' 48 +import { ref } from 'vue'
18 -import '@tarojs/taro/html.css' 49 +import Taro, { useDidShow, useShareAppMessage } from '@tarojs/taro'
19 -import { ref, onMounted } from 'vue' 50 +// import { showSuccessToast, showFailToast } from 'vant'; // NutUI 或 Taro API
20 -import { useDidShow, useReady } from '@tarojs/taro' 51 +import { billListAPI } from '@/api/index';
21 -import "./index.less"; 52 +import { useGo } from '@/hooks/useGo'
53 +import icon_1 from '@/assets/images/立即预约@2x.png'
54 +import icon_2 from '@/assets/images/预约记录@2x.png'
55 +import icon_3 from '@/assets/images/首页02@2x.png'
56 +import icon_4 from '@/assets/images/二维码icon.png'
57 +import icon_5 from '@/assets/images/我的01@2x.png'
58 +import icon_6 from '@/assets/images/luru@2x.png'
22 59
23 -const show = ref(false) 60 +const go = useGo();
24 -const onClick = () => { 61 +
25 - show.value = true 62 +const toBooking = () => { // 跳转到预约须知
63 + go('/notice');
64 +}
65 +const toRecord = () => { // 跳转到预约记录
66 + go('/bookingList');
67 +}
68 +const toSearch = () => { // 跳转到寺院录入
69 + go('/search');
70 +}
71 +const toCode = () => { // 跳转到预约码
72 + go('/bookingCode');
73 +}
74 +const toMy = () => { // 跳转到我的
75 + go('/me');
26 } 76 }
27 77
28 -// 生命周期钩子 78 +useDidShow(async () => {
29 -useDidShow(() => { 79 + // TAG: 触发授权页面 (检查 session 或调用接口触发 401)
30 - console.warn('index onShow') 80 + // 小程序中,request.js 拦截器会处理 401 跳转
31 -}) 81 + await billListAPI({ page: 1, row_num: 1 });
82 +});
32 83
33 -useReady(async () => { 84 +useShareAppMessage(() => {
34 - console.warn('index onReady') 85 + return {
35 - // 版本更新检查 86 + title: '西园寺预约',
36 - if (!Taro.canIUse("getUpdateManager")) { 87 + path: '/pages/index/index'
37 - Taro.showModal({
38 - title: "提示",
39 - content: "当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试",
40 - showCancel: false,
41 - });
42 - return;
43 } 88 }
44 -
45 - // https://developers.weixin.qq.com/miniprogram/dev/api/base/update/UpdateManager.html
46 - const updateManager = Taro.getUpdateManager();
47 -
48 - updateManager.onCheckForUpdate((res) => {
49 - // 请求完新版本信息的回调
50 - if (res.hasUpdate) {
51 - updateManager.onUpdateReady(function () {
52 - Taro.showModal({
53 - title: "更新提示",
54 - content: "新版本已经准备好,是否重启应用?",
55 - success: function (res) {
56 - if (res.confirm) {
57 - // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
58 - updateManager.applyUpdate();
59 - }
60 - },
61 - });
62 - });
63 -
64 - updateManager.onUpdateFailed(function () {
65 - // 新版本下载失败
66 - Taro.showModal({
67 - title: "更新提示",
68 - content: "新版本已上线,请删除当前小程序,重新搜索打开",
69 - });
70 - });
71 - }
72 - });
73 }) 89 })
74 90
75 -onMounted(() => {
76 - console.warn('index mounted')
77 -})
78 -
79 -// 分享功能
80 -wx.showShareMenu({
81 - withShareTicket: true,
82 - menus: ['shareAppMessage', 'shareTimeline']
83 -})
84 </script> 91 </script>
85 92
86 -<script> 93 +<style lang="less">
87 -import { getCurrentPageParam } from "@/utils/weapp"; 94 +.index-page {
95 + position: relative;
96 + height: 100vh;
97 + background-image: url('https://cdn.ipadbiz.cn/xys/booking/bg.jpg');
98 + background-repeat: no-repeat;
99 + background-position: center;
100 + background-size: cover; /* 确保背景覆盖 */
101 + .index-content {
102 + height: 90vh;
103 + .index-control {
104 + position: relative;
105 + display: flex;
106 + flex-direction: column;
107 + align-items: center;
108 + justify-content: center;
109 + margin-top: 10vh;
110 + // font-weight: bold;
111 + font-size: 1.15rem;
112 + .booking {
113 + display: flex;
114 + justify-content: center;
115 + align-items: center;
116 + background-color: #A67939;
117 + border-radius: 7px;
118 + color: #FFFFFF;
119 + padding: 0.7rem 4rem;
120 + border: 1px solid #A67939;
121 + }
122 + .record {
123 + display: flex;
124 + justify-content: center;
125 + align-items: center;
126 + color: #A67939;
127 + border-radius: 7px;
128 + padding: 0.7rem 4rem;
129 + border: 1px solid #A67939;
130 + margin-top: 1.5rem;
131 + }
132 + .search {
133 + display: flex;
134 + justify-content: center;
135 + align-items: center;
136 + color: #A67939;
137 + border-radius: 7px;
138 + padding: 0.7rem 4rem;
139 + border: 1px solid #A67939;
140 + margin-top: 1.5rem;
141 + }
142 + }
143 + .index-circular {
144 + position: relative;
145 + display: flex;
146 + align-items: center;
147 + justify-content: center;
148 + margin-top: 10vh;
149 + // font-weight: bold;
150 + font-size: 1.1rem;
88 151
89 -export default { 152 + .booking-wrapper {
90 - name: "indexPage", 153 + height: 19vh;
91 - onHide () { 154 + width: 19vh;
92 - console.warn('index onHide') 155 + border-radius: 50%;
93 - }, 156 + background-color: rgba(166, 121, 57, 0.26);
94 - onShareAppMessage() { 157 + display: flex;
95 - let params = getCurrentPageParam(); 158 + align-items: center;
96 - // 设置菜单中的转发按钮触发转发事件时的转发内容 159 + justify-content: center;
97 - var shareObj = { 160 + .booking {
98 - title: "xxx", // 默认是小程序的名称(可以写slogan等) 161 + height: 17vh;
99 - path: `pages/detail/index?id=${params.id}&start_date=${params.start_date}&end_date=${params.end_date}&room_type=${params.room_type}`, // 默认是当前页面,必须是以'/'开头的完整路径 162 + width: 17vh;
100 - imageUrl: '', //自定义图片路径,可以是本地文件路径、代码包文件路径或者网络图片路径,支持PNG及JPG,不传入 imageUrl 则使用默认截图。显示图片长宽比是 5:4 163 + border-radius: 50%;
101 - success: function (res) { 164 + background-color: #A67939;
102 - // 转发成功之后的回调 165 + display: flex;
103 - if (res.errMsg == 'shareAppMessage:ok') { 166 + align-items: center;
104 - // 167 + justify-content: center;
105 - } 168 + flex-direction: column;
106 - },
107 - fail: function () {
108 - // 转发失败之后的回调
109 - if (res.errMsg == 'shareAppMessage:fail cancel') {
110 - // 用户取消转发
111 - } else if (res.errMsg == 'shareAppMessage:fail') {
112 - // 转发失败,其中 detail message 为详细失败信息
113 } 169 }
114 - },
115 - complete: function () {
116 - // 转发结束之后的回调(转发成不成功都会执行)
117 } 170 }
118 } 171 }
119 - // 来自页面内的按钮的转发 172 + .logo {
120 - // if (options.from == 'button') { 173 + position: absolute;
121 - // var eData = options.target.dataset; 174 + right: 0;
122 - // // 此处可以修改 shareObj 中的内容 175 + bottom: calc(15vh);
123 - // shareObj.path = '/pages/goods/goods?goodId=' + eData.id; 176 + height: 30vh;
124 - // } 177 + width: 20vw;
125 - // 返回shareObj 178 + background-image: url('https://cdn.ipadbiz.cn/xys/booking/logo.png');
126 - return shareObj; 179 + background-repeat: no-repeat;
180 + background-size: contain;
181 + background-position: center;
182 + }
127 } 183 }
128 -}; 184 + .my-swipe {
129 -</script> 185 + height: 30vh;
186 + swiper-item { /* Taro swiper-item 编译后 */
187 + height: 30vh;
188 + width: 100vw;
189 + background-size: cover;
190 + background-repeat: no-repeat;
191 + background-position: center;
192 + }
193 + }
194 + .index-nav {
195 + position: absolute;
196 + bottom: 0;
197 + left: 0;
198 + width: 100vw;
199 + height: 10vh;
200 + background: #FFFFFF;
201 + box-shadow: 0rem -0.33rem 0.25rem 0rem rgba(0,0,0,0.12);
202 + display: flex;
203 + align-items: center;
204 + justify-content: space-around;
205 + color: #A67939;
206 + .nav-logo {
207 + position: relative;
208 + display: flex;
209 + flex-direction: column;
210 + align-items: center;
211 + }
212 + }
213 +}
214 +</style>
......
1 +export default {
2 + navigationBarTitleText: '我的'
3 +}
1 +<template>
2 + <view class="my-page">
3 + <view v-for="(item, index) in menu_list" :key="index" class="my-item" @tap="go(item.to)">
4 + <view class="left">
5 + <image :src="item.icon" style="width: 1.2rem; height: 1.2rem; margin-right: 0.5rem;" />
6 + {{ item.name }}
7 + </view>
8 + <view>
9 + <nut-icon name="rect-right" size="1.2rem" />
10 + </view>
11 + </view>
12 + <view class="index-nav">
13 + <view class="nav-logo" @tap="toHome">
14 + <image :src="icon_3" style="width: 1.5rem; height: 1.5rem;" />
15 + 首页
16 + </view>
17 + <view class="nav-logo" @tap="toCode">
18 + <image :src="icon_4" style="width: 1.5rem; height: 1.5rem; margin-bottom: 0.1rem;" />
19 + 预约码
20 + </view>
21 + <view class="nav-logo">
22 + <image :src="icon_5" style="width: 1.5rem; height: 1.5rem;" />
23 + 我的
24 + </view>
25 + </view>
26 + </view>
27 +</template>
28 +
29 +<script setup>
30 +import { ref } from 'vue'
31 +import Taro from '@tarojs/taro'
32 +import { useGo } from '@/hooks/useGo'
33 +import icon_3 from '@/assets/images/首页01@2x.png'
34 +import icon_4 from '@/assets/images/二维码icon.png'
35 +import icon_5 from '@/assets/images/我的02@2x.png'
36 +
37 +import icon_booking from '@/assets/images/预约记录@2x.png'
38 +import icon_visitor from '@/assets/images/我的01@2x.png'
39 +import icon_invite from '@/assets/images/二维码@2x2.png'
40 +
41 +const go = useGo();
42 +
43 +const toCode = () => { // 跳转到预约码
44 + go('/pages/bookingCode/index');
45 +}
46 +const toHome = () => { // 跳转到首页
47 + go('/pages/index/index');
48 +}
49 +
50 +const menu_list = [{
51 + icon: icon_booking,
52 + name: '预约记录',
53 + to: '/pages/bookingList/index'
54 +}, {
55 + icon: icon_visitor,
56 + name: '参观者',
57 + to: '/pages/visitorList/index'
58 +}, {
59 + icon: icon_invite,
60 + name: '邀请码',
61 + to: '/pages/search/index'
62 +}]
63 +</script>
64 +
65 +<style lang="less">
66 +.my-page {
67 + position: relative;
68 + min-height: 100vh;
69 + background-color: #F6F6F6;
70 + padding: 1rem;
71 +
72 + .my-item {
73 + padding: 1rem;
74 + display: flex;
75 + justify-content:space-between;
76 + align-items: center;
77 + margin-bottom: 1rem;
78 + background-color: #FFF;
79 + border-radius: 5px;
80 + .left {
81 + color: #A67939;
82 + display: flex;
83 + align-items: center;
84 + }
85 + }
86 + .index-nav {
87 + position: fixed;
88 + bottom: 0;
89 + left: 0;
90 + width: 100vw;
91 + height: 10vh;
92 + background: #FFFFFF;
93 + box-shadow: 0rem -0.33rem 0.25rem 0rem rgba(0,0,0,0.12);
94 + display: flex;
95 + align-items: center;
96 + justify-content: space-around;
97 + color: #A67939;
98 + .nav-logo {
99 + position: relative;
100 + display: flex;
101 + flex-direction: column;
102 + align-items: center;
103 + }
104 + }
105 +}
106 +</style>
1 +<!--
2 + * @Date: 2024-01-15 11:43:01
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-02-04 22:29:54
5 + * @FilePath: /xyxBooking-weapp/src/pages/notice/index.vue
6 + * @Description: 文件描述
7 +-->
8 +<template>
9 + <view class="notice-page">
10 + <view class="content">
11 + <view style="text-align: center; font-size: 1.1rem; margin-bottom: 0.5rem;">温馨提示</view>
12 + <view>
13 + 为了您和他人的健康与安全,维护清净庄严的寺院环境,营造一个喜悦而祥和的节日氛围,请您留意并遵守以下注意事项:
14 + </view>
15 + <view v-for="(item, index) in note_text" :key="index" style="margin-top: 0.5rem;">{{ item }}</view>
16 + <view style="margin-top: 0.5rem;">谢谢您的支持与配合。祝您新春吉祥、万事如意。</view>
17 + </view>
18 + <view style="height: 8rem"></view>
19 + <view class="footer">
20 + <nut-checkbox-group v-model="checked">
21 + <nut-checkbox label="1" icon-size="1rem">
22 + <text style="color: #a67939; font-size: 1rem">我已阅读并同意以上内容</text>
23 + </nut-checkbox>
24 + </nut-checkbox-group>
25 + <view @tap="confirmBtn" class="confirm-btn">确认,下一步</view>
26 + </view>
27 + </view>
28 +</template>
29 +
30 +<script setup>
31 +import { ref } from "vue";
32 +import Taro, { useDidShow } from '@tarojs/taro'
33 +import { useGo } from "@/hooks/useGo";
34 +
35 +const go = useGo();
36 +const note_text = [
37 + '1、敬香贵在心诚,不在数量多少。三支清香,可表心诚。请带着虔诚心、恭敬心和清净心敬香礼佛。',
38 + '2、请不要自带香烛进寺院。山门殿两侧设有赠香处,凭香花券可免费领取三支清香。',
39 + '3、点燃香烛时请多加小心,以免灼伤自己与他人。',
40 + '4、请在指定燃香处燃香,禁止将香烛带入殿堂。禁止燃放烟花爆竹。',
41 + '5、请爱护公共绿地,请不要踩踏及在草坪上点烛燃香。',
42 + '6、请照看好身边的家人,以免走散。',
43 + '7、请保管好自己随身携带的钱物,以免丢失给您带来麻烦。',
44 + '8、您若有任何问题和困难,请向身边的法师或义工咨询、求助,或直接与客堂联系。电话:0512-65349545。',
45 + '9、预约如需退款,请在初七之后,到客堂办理。'
46 +];
47 +const checked = ref([]);
48 +
49 +const confirmBtn = () => {
50 + if (checked.value.includes("1")) {
51 + go("/booking");
52 + } else {
53 + Taro.showToast({ title: "请勾选同意须知", icon: "none" });
54 + }
55 +};
56 +</script>
57 +
58 +<style lang="less">
59 +.notice-page {
60 + position: relative;
61 + min-height: 100vh;
62 + background-color: #F6F6F6;
63 + padding-top: 1px; // 防止 margin collapse
64 + .content {
65 + margin: 1rem;
66 + background-color: #ffffff;
67 + border-radius: 8px;
68 + padding: 1rem;
69 + color: #333;
70 + font-size: 0.9rem;
71 + line-height: 1.5;
72 + }
73 + .footer {
74 + position: fixed;
75 + bottom: 0;
76 + width: 100vw;
77 + background-color: #FFF;
78 + display: flex;
79 + flex-direction: column;
80 + padding: 1rem;
81 + box-sizing: border-box;
82 + box-shadow: 0rem -0.33rem 0.25rem 0rem rgba(0,0,0,0.12);
83 +
84 + .confirm-btn {
85 + background-color: #A67939;
86 + color: #FFF;
87 + text-align: center;
88 + padding: 0.8rem 0;
89 + border-radius: 8px;
90 + margin-top: 1rem;
91 + font-size: 1.1rem;
92 + }
93 + }
94 +}
95 +</style>
1 +<!--
2 + * @Date: 2024-01-26 13:08:09
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-01-30 17:51:11
5 + * @FilePath: /xyxBooking-weapp/src/pages/search/index.vue
6 + * @Description: 文件描述
7 +-->
8 +<template>
9 + <view class="search-page">
10 + <view>
11 + <view v-if="!is_search">
12 + <view class="input-item">
13 + <view>证件号码</view>
14 + <view>
15 + <input type="text" v-model="idCode" placeholder="请输入证件号码" @blur="checkIdCode" maxlength="18" style="width: 100%;">
16 + </view>
17 + </view>
18 + <view style="color:#A67939; font-size: 0.95rem; text-align: center;">
19 + <view>
20 + <nut-icon name="tips" />&nbsp;温馨提示
21 + </view>
22 + <view style="margin-top: 0.5rem;">获取参观码,扫码或识别身份证成功进闸机</view>
23 + </view>
24 + </view>
25 + <view v-else>
26 + <qrCodeSearch :id="id_number" />
27 + </view>
28 + <view v-if="!is_search" class="save-wrapper">
29 + <view class="save-btn" @tap="searchBtn">查询</view>
30 + </view>
31 + <view v-else class="success-btn">
32 + <view @tap="goToHome" class="btn-item btn-left">首页</view>
33 + <view @tap="goBack" class="btn-item btn-right">返回查询</view>
34 + </view>
35 + </view>
36 + <view class="logo"></view>
37 + </view>
38 +</template>
39 +
40 +<script setup>
41 +import { ref } from 'vue'
42 +import Taro from '@tarojs/taro'
43 +import qrCodeSearch from '@/components/qrCodeSearch';
44 +import { useGo } from '@/hooks/useGo'
45 +
46 +const go = useGo();
47 +const is_search = ref(false);
48 +const idCode = ref('');
49 +const id_number = ref('');
50 +
51 +// 简单的身份证校验
52 +const validateCIN = (id) => {
53 + return /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(id);
54 +}
55 +
56 +const checkIdCode = () => { // 检查身份证号是否为空
57 + let flag = true;
58 + if (idCode.value.length === 15) { // 15位身份证号码不校验
59 + flag = true;
60 + } else {
61 + if (!validateCIN(idCode.value)) {
62 + Taro.showToast({ title: '请检查身份证号码', icon: 'none' });
63 + flag = false;
64 + }
65 + }
66 + return flag;
67 +}
68 +
69 +const searchBtn = async () => {
70 + // 查询用户信息
71 + if (checkIdCode() && idCode.value) {
72 + is_search.value = true;
73 + id_number.value = idCode.value;
74 + idCode.value = ''
75 + }
76 +}
77 +const goBack = () => {
78 + is_search.value = false;
79 +}
80 +const goToHome = () => {
81 + go('/index')
82 +}
83 +</script>
84 +
85 +<style lang="less">
86 +.search-page {
87 + padding: 1rem;
88 + min-height: 100vh;
89 + background-color: #F6F6F6;
90 +
91 + .input-item {
92 + background-color: #FFF;
93 + padding: 1rem;
94 + border-radius: 8px;
95 + margin-bottom: 1rem;
96 +
97 + view:first-child {
98 + margin-bottom: 0.5rem;
99 + font-weight: bold;
100 + }
101 + input {
102 + border-bottom: 1px solid #EEE;
103 + padding: 0.5rem 0;
104 + }
105 + }
106 +
107 + .save-wrapper {
108 + margin-top: 2rem;
109 + .save-btn {
110 + background-color: #A67939;
111 + color: #FFF;
112 + text-align: center;
113 + padding: 0.8rem 0;
114 + border-radius: 8px;
115 + font-size: 1.1rem;
116 + }
117 + }
118 +
119 + .success-btn {
120 + position: fixed;
121 + bottom: 2rem;
122 + width: 100vw;
123 + left: 0;
124 + display: flex;
125 + justify-content: space-around;
126 +
127 + .btn-item {
128 + width: 40%;
129 + text-align: center;
130 + padding: 0.8rem 0;
131 + border-radius: 8px;
132 + font-size: 1.1rem;
133 + }
134 +
135 + .btn-left {
136 + border: 1px solid #A67939;
137 + color: #A67939;
138 + background-color: #FFF;
139 + }
140 +
141 + .btn-right {
142 + background-color: #A67939;
143 + color: #FFF;
144 + }
145 + }
146 +
147 + .logo {
148 + position: absolute;
149 + right: 0;
150 + bottom: 15vh;
151 + height: 30vh;
152 + width: 20vw;
153 + background-image: url('https://cdn.ipadbiz.cn/xys/booking/logo.png');
154 + background-repeat: no-repeat;
155 + background-size: contain;
156 + background-position: center;
157 + opacity: 0.5;
158 + pointer-events: none;
159 + }
160 +}
161 +</style>
1 +<!--
2 + * @Date: 2024-01-15 16:25:51
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-01-30 15:18:58
5 + * @FilePath: /xyxBooking-weapp/src/pages/submit/index.vue
6 + * @Description: 预约人员信息
7 +-->
8 +<template>
9 + <view class="submit-page">
10 + <view @tap="goToBooking" class="visit-time">
11 + <view>参访时间</view>
12 + <view><text style="font-size: 0.95rem;">{{ date }} {{ time }}</text>&nbsp;<nut-icon name="rect-right" /></view>
13 + </view>
14 + <view @tap="goToVisitor" class="add-visitors">
15 + <view><nut-icon name="plus" /> 添加参观者</view>
16 + </view>
17 + <view v-if="visitorList.length" class="visitors-list">
18 + <view v-for="(item, index) in visitorList" :key="index" @tap="addVisitor(item)" class="visitor-item">
19 + <view style="margin-right: 1rem;">
20 + <image v-if="!checked_visitors.includes(item.id)" :src="icon_check1" style="width: 1.2rem; height: 1.2rem;" />
21 + <image v-else :src="icon_check2" style="width: 1.2rem; height: 1.2rem;" />
22 + </view>
23 + <view>
24 + <view style="color: #A67939;">{{ item.name }}</view>
25 + <view>证件号:{{ formatId(item.id_number) }}</view>
26 + <view v-if="item.is_reserve === RESERVE_STATUS.ENABLE" style="color: #9C9A9A; font-size: 0.8rem;">*已预约过{{ date }}参观,请不要重复预约</view>
27 + </view>
28 + </view>
29 + </view>
30 + <view v-else class="no-visitors-list">
31 + <image src="https://cdn.ipadbiz.cn/xys/booking/%E6%9A%82%E6%97%A0@2x.png" style="width: 10rem; height: 10rem;" />
32 + <view class="no-visitors-list-title">您还没有添加过参观者</view>
33 + </view>
34 + <view style="height: 5rem;"></view>
35 + <view class="submit-wrapper">
36 + <view class="control-wrapper">
37 + <view class="left">
38 + <view style="margin-left: 1rem; display: flex;align-items: center;">
39 + 订单金额&nbsp;&nbsp;<view style="color: #FF1919;display: inline-block;">¥<view style="font-size: 1.5rem;display: inline-block;">&nbsp;{{ total }}</view></view>
40 + </view>
41 + </view>
42 + <view @tap="submitBtn" class="right">提交订单</view>
43 + </view>
44 + <view style="font-size: 0.85rem;margin-left: 1rem;color: #FF1919; margin-bottom: 1rem;">提交后请在10分钟内完成支付</view>
45 + </view>
46 + </view>
47 +</template>
48 +
49 +<script setup>
50 +import { ref, computed } from 'vue'
51 +import Taro, { useDidShow, useRouter as useTaroRouter } from '@tarojs/taro'
52 +import { useGo } from '@/hooks/useGo'
53 +import icon_check1 from '@/assets/images/多选01@2x.png'
54 +import icon_check2 from '@/assets/images/多选02@2x.png'
55 +import { personListAPI, addReserveAPI } from '@/api/index'
56 +
57 +const router = useTaroRouter();
58 +const go = useGo();
59 +
60 +const visitorList = ref([]);
61 +const date = ref('');
62 +const time = ref('');
63 +const price = ref(0);
64 +
65 +/**
66 + * 生成15位身份证号中间8位替换为*号
67 + * @param {*} inputString
68 + */
69 +function replaceMiddleCharacters(inputString) {
70 + if (!inputString || inputString.length < 15) {
71 + return inputString; // 字符串长度不足,不进行替换
72 + }
73 +
74 + const start = Math.floor((inputString.length - 8) / 2); // 开始替换的索引位置
75 + const end = start + 8; // 结束替换的索引位置
76 +
77 + const replacement = '*'.repeat(8); // 生成包含8个*号的字符串
78 +
79 + const replacedString = inputString.substring(0, start) + replacement + inputString.substring(end);
80 + return replacedString;
81 +}
82 +
83 +const formatId = (id) => {
84 + return replaceMiddleCharacters(id);
85 +};
86 +
87 +const RESERVE_STATUS = {
88 + ENABLE: '1'
89 +}
90 +
91 +const checked_visitors = ref([]);
92 +const addVisitor = (item) => {
93 + if (item.is_reserve === RESERVE_STATUS.ENABLE) { // 今天已经预约
94 + Taro.showToast({ title: '已预约过参观,请不要重复预约', icon: 'none' })
95 + return;
96 + }
97 + if (checked_visitors.value.includes(item.id)) {
98 + checked_visitors.value = checked_visitors.value.filter((v) => v !== item.id);
99 + } else {
100 + checked_visitors.value.push(item.id);
101 + }
102 +}
103 +
104 +const total = computed(() => {
105 + return price.value * checked_visitors.value.length;
106 +})
107 +
108 +const goToBooking = () => {
109 + go('/booking');
110 +}
111 +const goToVisitor = () => {
112 + go('/addVisitor');
113 +}
114 +
115 +const submitBtn = async () => {
116 + if (!checked_visitors.value.length) {
117 + Taro.showToast({ title: '请先添加参观者', icon: 'none' })
118 + } else {
119 + // TAG: 提交订单跳转到支付页面
120 + Taro.showLoading({ title: '提交中...' });
121 + const { code, data } = await addReserveAPI({
122 + reserve_date: date.value,
123 + begin_time: time.value.split('-')[0],
124 + end_time: time.value.split('-')[1],
125 + person_id_list: JSON.stringify(checked_visitors.value)
126 + });
127 +
128 + Taro.hideLoading();
129 +
130 + if (code) {
131 + // H5 逻辑: const pay_url = `/srv/?f=reserve&a=icbc_pay&pay_id=${data.pay_id}`; location.href = pay_url;
132 + // 小程序逻辑:
133 + // 1. 如果支持小程序支付,应该调用获取支付参数接口
134 + // 2. 暂时提示不支持,或尝试模拟
135 +
136 + // 假设 payPrepareAPI 可用
137 + // const payParams = await payPrepareAPI({ bill_id: data.pay_id }); // 假设接口
138 +
139 + Taro.showModal({
140 + title: '提示',
141 + content: '订单提交成功。由于小程序支付暂未配置,请联系管理员或前往H5完成支付。',
142 + showCancel: false,
143 + success: () => {
144 + go('/bookingList');
145 + }
146 + });
147 +
148 + // 如果后端有返回支付参数,可以使用 requestPayment
149 + /*
150 + Taro.requestPayment({
151 + timeStamp: '',
152 + nonceStr: '',
153 + package: '',
154 + signType: 'MD5',
155 + paySign: '',
156 + success (res) {
157 + go('/success');
158 + },
159 + fail (res) { }
160 + })
161 + */
162 + }
163 + }
164 +}
165 +
166 +useDidShow(async () => {
167 + const params = router.params;
168 + date.value = params.date || '';
169 + time.value = params.time || '';
170 + price.value = params.price || 0;
171 +
172 + if (date.value && time.value) {
173 + const { code, data } = await personListAPI({ reserve_date: date.value, begin_time: time.value.split('-')[0], end_time: time.value.split('-')[1] });
174 + if (code) {
175 + visitorList.value = data;
176 + }
177 + }
178 +});
179 +</script>
180 +
181 +<style lang="less">
182 +.submit-page {
183 + margin: 1rem;
184 + position: relative;
185 + .visit-time {
186 + background-color: #FFF;
187 + display: flex;
188 + align-items: center;
189 + justify-content: space-between;
190 + padding: 0.75rem;
191 + border-radius: 8px;
192 + }
193 + .add-visitors {
194 + border: 1px dashed #A67939;
195 + color: #A67939;
196 + border-radius: 5px;
197 + text-align: center;
198 + padding: .65rem 0;
199 + margin: 1rem 0;
200 + font-size: 1.15rem;
201 + }
202 + .visitors-list {
203 + .visitor-item {
204 + background-color: #FFF;
205 + border-radius: 8px;
206 + padding: 1rem;
207 + margin-bottom: 1rem;
208 + display: flex;
209 + align-items: center;
210 + }
211 + }
212 + .no-visitors-list {
213 + display: flex;
214 + justify-content: center;
215 + align-items: center;
216 + flex-direction: column;
217 + img {
218 + margin-top: 1rem;
219 + margin-bottom: 1rem;
220 + width: 10rem;
221 + }
222 + .no-visitors-list-title {
223 + color: #A67939;
224 + font-size: 1.05rem;
225 + }
226 + }
227 + .submit-wrapper {
228 + position: fixed;
229 + bottom: 0;
230 + left: 0;
231 + width: 100vw;
232 + display: flex;
233 + background-color: #FFF;
234 + // padding: 1rem;
235 + justify-content: space-between;
236 + flex-direction: column;
237 + box-shadow: 0rem -0.33rem 0.25rem 0rem rgba(0,0,0,0.12);
238 +
239 + .control-wrapper {
240 + display: flex;
241 + justify-content: space-between;
242 + align-items: center;
243 + }
244 + .left {
245 + display: flex;
246 + justify-content: center;
247 + align-items: center;
248 + flex-wrap: nowrap;
249 + }
250 + .right {
251 + background-color: #A67939;
252 + color: #FFF;
253 + margin: 1rem;
254 + padding: 0.8rem 3rem;
255 + border-radius: 5px;
256 + font-size: 1.1rem;
257 + margin-bottom: 0;
258 + }
259 + }
260 +}
261 +</style>
1 +<!--
2 + * @Date: 2024-01-15 18:28:25
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-01-30 15:18:54
5 + * @FilePath: /xyxBooking-weapp/src/pages/success/index.vue
6 + * @Description: 预约成功提示页面
7 +-->
8 +<template>
9 + <view class="success-page">
10 + <view style="">
11 + <view class="text-prompts">
12 + <image src="https://cdn.ipadbiz.cn/xys/booking/%E6%88%90%E5%8A%9F@2x.png" mode="widthFix" />
13 + <view class="text">预约成功</view>
14 + </view>
15 + <view class="appointment-information">
16 + <view class="number-of-visitors">参观人数:<text>{{ billInfo?.total_qty }} 人</text></view>
17 + <view class="visit-time">参访时间:<text>{{ billInfo?.datetime }}</text></view>
18 + <view class="payment-amount">支付金额:<text>¥ {{ billInfo?.total_amt }}</text></view>
19 + </view>
20 + <view class="appointment-notice">
21 + <view style="margin-bottom: 0.25rem;"><nut-icon name="tips" />&nbsp;温馨提示</view>
22 + <view style="font-size: 0.85rem;">1. 一人一码,或拿身份证,扫码或识别身份证成功后进入</view>
23 + <view style="font-size: 0.85rem;">2. 若您无法按时参观,请提前在预约记录中取消您的预约</view>
24 + </view>
25 + </view>
26 + <view class="success-btn">
27 + <view @tap="goToHome" class="btn-item btn-left">首页</view>
28 + <view @tap="goToDetail" class="btn-item btn-right">详情</view>
29 + </view>
30 + </view>
31 +</template>
32 +
33 +<script setup>
34 +import { ref } from 'vue'
35 +import Taro, { useDidShow, useRouter as useTaroRouter } from '@tarojs/taro'
36 +import { useGo } from '@/hooks/useGo'
37 +import { billInfoAPI } from '@/api/index'
38 +import { formatDatetime } from '@/utils/tools';
39 +
40 +const router = useTaroRouter();
41 +const go = useGo();
42 +
43 +const goToHome = () => {
44 + go('/index')
45 +}
46 +const goToDetail = () => {
47 + go('/bookingDetail', { pay_id: router.params.pay_id });
48 +}
49 +
50 +const billInfo = ref({});
51 +
52 +useDidShow(async () => {
53 + // 获取订单详情
54 + const { code, data } = await billInfoAPI({ pay_id: router.params.pay_id });
55 + if (code) {
56 + data.datetime = data && formatDatetime(data);
57 + billInfo.value = data;
58 + }
59 +})
60 +</script>
61 +
62 +<style lang="less">
63 +.success-page {
64 + position: relative;
65 + background-color: #FFF;
66 + min-height: 100vh;
67 +
68 + .text-prompts {
69 + display: flex;
70 + align-items: center;
71 + justify-content: center;
72 + height: 35vh;
73 + flex-direction: column;
74 + image {
75 + width: 60vw;
76 + }
77 + .text {
78 + color: #A67939;
79 + font-size: 1.25rem;
80 + margin-top: 1rem;
81 + }
82 + }
83 + .appointment-information {
84 + padding: 2rem 1rem;
85 + border-bottom: 1px dashed #A67939;
86 + line-height: 2;
87 + .number-of-visitors {
88 + text {
89 + color: #A67939;
90 + }
91 + }
92 + .visit-time {
93 + text {
94 + color: #A67939;
95 + }
96 + }
97 + .payment-amount {
98 + text {
99 + color: #A67939;
100 + }
101 + }
102 + }
103 +
104 + .appointment-notice {
105 + padding: 1rem;
106 + color: #666;
107 + }
108 +
109 + .success-btn {
110 + position: fixed;
111 + bottom: 2rem;
112 + width: 100vw;
113 + display: flex;
114 + justify-content: space-around;
115 +
116 + .btn-item {
117 + width: 40%;
118 + text-align: center;
119 + padding: 0.8rem 0;
120 + border-radius: 8px;
121 + font-size: 1.1rem;
122 + }
123 +
124 + .btn-left {
125 + border: 1px solid #A67939;
126 + color: #A67939;
127 + }
128 +
129 + .btn-right {
130 + background-color: #A67939;
131 + color: #FFF;
132 + }
133 + }
134 +}
135 +</style>
1 +export default {
2 + navigationBarTitleText: '参观者'
3 +}
1 +<template>
2 + <view class="me-page">
3 + <view class="me-content">
4 + <view class="title">
5 + <view class="text">参观者信息</view>
6 + </view>
7 + <view @tap="() => { go('/pages/addVisitor/index') }" class="add-visitors">
8 + <view><nut-icon name="plus" /> 添加参观者</view>
9 + </view>
10 + <view v-if="visitorList.length" class="visitors-list">
11 + <view v-for="(item, index) in visitorList" :key="index" class="visitor-item">
12 + <view>
13 + <view style="color: #A67939;">{{ item.name }}</view>
14 + <view>证件号:{{ formatId(item.id_number) }}</view>
15 + </view>
16 + <view @tap="removeItem(item)" style="margin-left: 1rem;">
17 + <image src="https://cdn.ipadbiz.cn/xys/booking/%E5%88%A0%E9%99%A4@2x.png" style="width: 1.2rem; height: 1.2rem;" />
18 + </view>
19 + </view>
20 + </view>
21 + <view v-else class="no-visitors-list">
22 + <image src="https://cdn.ipadbiz.cn/xys/booking/%E6%9A%82%E6%97%A0@2x.png" style="width: 10rem; height: 10rem;" />
23 + <view class="no-visitors-list-title">您还没有添加过参观者</view>
24 + </view>
25 + </view>
26 + <view style="height: 8rem;"></view>
27 + <view class="index-nav">
28 + <view class="nav-logo" @tap="toHome">
29 + <image :src="icon_3" style="width: 1.5rem; height: 1.5rem;" />
30 + 首页
31 + </view>
32 + <view class="nav-logo" @tap="toCode">
33 + <image :src="icon_4" style="width: 1.5rem; height: 1.5rem; margin-bottom: 0.1rem;" />
34 + 预约码
35 + </view>
36 + <view class="nav-logo" @tap="toMy">
37 + <image :src="icon_5" style="width: 1.5rem; height: 1.5rem;" />
38 + 我的
39 + </view>
40 + </view>
41 + </view>
42 +</template>
43 +
44 +<script setup>
45 +import { ref } from 'vue'
46 +import Taro, { useDidShow } from '@tarojs/taro'
47 +import { useGo } from '@/hooks/useGo'
48 +import { personListAPI, delPersonAPI } from '@/api/index'
49 +import icon_3 from '@/assets/images/首页01@2x.png'
50 +import icon_4 from '@/assets/images/二维码icon.png'
51 +import icon_5 from '@/assets/images/我的02@2x.png'
52 +
53 +const go = useGo();
54 +
55 +const toCode = () => { // 跳转到预约码
56 + go('/pages/bookingCode/index');
57 +}
58 +const toHome = () => { // 跳转到首页
59 + go('/pages/index/index');
60 +}
61 +const toMy = () => { // 跳转到我的
62 + go('/pages/me/index');
63 +}
64 +
65 +const visitorList = ref([]);
66 +
67 +function replaceMiddleCharacters(inputString) {
68 + if (!inputString || inputString.length < 15) {
69 + return inputString;
70 + }
71 + const start = Math.floor((inputString.length - 8) / 2);
72 + const end = start + 8;
73 + const replacement = '*'.repeat(8);
74 + return inputString.substring(0, start) + replacement + inputString.substring(end);
75 +}
76 +
77 +const formatId = (id) => replaceMiddleCharacters(id);
78 +
79 +const loadList = async () => {
80 + const { code, data } = await personListAPI({});
81 + if (code) {
82 + visitorList.value = data;
83 + }
84 +}
85 +
86 +const removeItem = async (item) => {
87 + const { confirm } = await Taro.showModal({ title: '提示', content: '确定删除该参观者吗?' });
88 + if (confirm) {
89 + const { code, msg } = await delPersonAPI({ person_id: item.id });
90 + if (code) {
91 + Taro.showToast({ title: '删除成功' });
92 + loadList();
93 + } else {
94 + Taro.showToast({ title: msg || '删除失败', icon: 'none' });
95 + }
96 + }
97 +}
98 +
99 +useDidShow(() => {
100 + loadList();
101 +})
102 +</script>
103 +
104 +<style lang="less">
105 +.me-page {
106 + min-height: 100vh;
107 + background-color: #F6F6F6;
108 + padding: 1rem;
109 +
110 + .me-content {
111 + .title {
112 + .text {
113 + font-size: 1.1rem;
114 + font-weight: bold;
115 + margin-bottom: 1rem;
116 + border-left: 3px solid #A67939;
117 + padding-left: 0.5rem;
118 + }
119 + }
120 +
121 + .add-visitors {
122 + border: 1px dashed #A67939;
123 + color: #A67939;
124 + border-radius: 5px;
125 + text-align: center;
126 + padding: .65rem 0;
127 + margin: 1rem 0;
128 + font-size: 1.15rem;
129 + }
130 +
131 + .visitors-list {
132 + .visitor-item {
133 + background-color: #FFF;
134 + border-radius: 8px;
135 + padding: 1rem;
136 + margin-bottom: 1rem;
137 + display: flex;
138 + align-items: center;
139 + justify-content: space-between;
140 + }
141 + }
142 +
143 + .no-visitors-list {
144 + display: flex;
145 + justify-content: center;
146 + align-items: center;
147 + flex-direction: column;
148 +
149 + .no-visitors-list-title {
150 + color: #A67939;
151 + font-size: 1.05rem;
152 + margin-top: 1rem;
153 + }
154 + }
155 + }
156 +
157 + .index-nav {
158 + position: fixed;
159 + bottom: 0;
160 + left: 0;
161 + width: 100vw;
162 + height: 10vh;
163 + background: #FFFFFF;
164 + box-shadow: 0rem -0.33rem 0.25rem 0rem rgba(0,0,0,0.12);
165 + display: flex;
166 + align-items: center;
167 + justify-content: space-around;
168 + color: #A67939;
169 + .nav-logo {
170 + position: relative;
171 + display: flex;
172 + flex-direction: column;
173 + align-items: center;
174 + }
175 + }
176 +}
177 +</style>
1 +export default {
2 + navigationBarTitleText: '支付中'
3 +}
1 +<template>
2 + <view class="waiting-page">
3 + <view class="waiting-content">
4 + <view>
5 + <nut-icon name="clock" size="40" color="#A67939" />
6 + </view>
7 + <view style="margin: 1rem 0;">支付中</view>
8 + <view>{{ current.seconds }} s</view>
9 + <view style="margin: 1.5rem 0; font-size: 0.85rem; color: #A67939; text-align: center; line-height: 2;">
10 + 温馨提示:{{ pay_msg }}<br />
11 + </view>
12 + </view>
13 + <view class="go-back-wrapper">
14 + <nut-button @click="goBackBtn" color="#A67939" block>返回首页</nut-button>
15 + </view>
16 + </view>
17 +</template>
18 +
19 +<script setup>
20 +import { ref, onMounted, onUnmounted } from 'vue'
21 +import Taro, { useRouter } from '@tarojs/taro'
22 +import { billPayStatusAPI } from '@/api/index'
23 +import { useGo } from '@/hooks/useGo'
24 +
25 +const router = useRouter()
26 +const go = useGo()
27 +
28 +const remaining = ref(10)
29 +const current = ref({ seconds: 10 })
30 +
31 +// Ensure params are available. Taro.useRouter() might need time or be called in setup.
32 +// router.params is reactive in some Taro versions, or just an object.
33 +const pay_id = router.params.pay_id
34 +const pay_msg = ref('支付可能需要10s左右,请耐心等待')
35 +
36 +const PAY_STATUS = {
37 + PAY: '1',
38 + PAYING: '2',
39 + FAIL: '7',
40 + SUCCESS: '3'
41 +}
42 +
43 +let timer = null
44 +let countdownTimer = null
45 +
46 +const startCountdown = () => {
47 + countdownTimer = setInterval(() => {
48 + if (remaining.value > 0) {
49 + remaining.value--
50 + current.value.seconds = remaining.value
51 + } else {
52 + clearInterval(countdownTimer)
53 + }
54 + }, 1000)
55 +}
56 +
57 +const checkStatus = async () => {
58 + if (!pay_id) return
59 + try {
60 + const { code, data } = await billPayStatusAPI({ pay_id })
61 + // TAG:轮询支付回调
62 + if (data) {
63 + switch (data.status) {
64 + case PAY_STATUS.PAY:
65 + pay_msg.value = '订单待支付'
66 + break
67 + case PAY_STATUS.PAYING:
68 + pay_msg.value = '订单支付中'
69 + break
70 + case PAY_STATUS.SUCCESS:
71 + // 预约成功页面
72 + // Replace to avoid back button loop
73 + go(`/pages/success/index?pay_id=${pay_id}`, 'replace')
74 + break
75 + case PAY_STATUS.FAIL:
76 + pay_msg.value = '订单支付失败'
77 + break
78 + }
79 + }
80 + } catch (error) {
81 + console.error('Check status error:', error)
82 + }
83 +}
84 +
85 +onMounted(() => {
86 + startCountdown()
87 + // Immediate check
88 + checkStatus()
89 + timer = setInterval(async () => {
90 + if (remaining.value <= 0) {
91 + clearInterval(timer)
92 + }
93 + await checkStatus()
94 + }, 1000)
95 +})
96 +
97 +onUnmounted(() => {
98 + if(timer) clearInterval(timer)
99 + if(countdownTimer) clearInterval(countdownTimer)
100 +})
101 +
102 +const goBackBtn = () => {
103 + go('/pages/index/index')
104 +}
105 +</script>
106 +
107 +<style lang="less">
108 +.waiting-page {
109 + display: flex;
110 + flex-direction: column;
111 + height: 100vh;
112 + background-color: #fff;
113 + align-items: center;
114 + padding-top: 3rem;
115 +
116 + .waiting-content {
117 + display: flex;
118 + flex-direction: column;
119 + align-items: center;
120 + font-size: 1rem;
121 + color: #333;
122 + }
123 +
124 + .go-back-wrapper {
125 + width: 80%;
126 + margin-top: 2rem;
127 + }
128 +}
129 +</style>
1 +/*
2 + * @Date: 2022-04-18 15:59:42
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-01-30 15:26:30
5 + * @FilePath: /xyxBooking-weapp/src/stores/main.js
6 + * @Description: 文件描述
7 + */
8 +import { defineStore } from 'pinia';
9 +
10 +export const mainStore = defineStore('main', {
11 + state: () => {
12 + return {
13 + msg: 'Hello world',
14 + count: 0,
15 + auth: false,
16 + // keepPages: ['default'], // 小程序不支持这种 keep-alive 机制
17 + appUserInfo: [], // 缓存预约人信息
18 + };
19 + },
20 + getters: {
21 + // getKeepPages () {
22 + // return this.keepPages
23 + // },
24 + },
25 + actions: {
26 + changeState (state) {
27 + this.auth = state;
28 + },
29 + // changeKeepPages () {
30 + // this.keepPages = ['default'];
31 + // },
32 + // keepThisPage () {
33 + // // 小程序路由缓存由框架控制
34 + // },
35 + // removeThisPage () {
36 + // },
37 + changeUserInfo (info) {
38 + this.appUserInfo = info;
39 + }
40 + },
41 +});
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
2 * @Date: 2022-09-19 14:11:06 2 * @Date: 2022-09-19 14:11:06
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 * @LastEditTime: 2025-07-01 11:17:49 4 * @LastEditTime: 2025-07-01 11:17:49
5 - * @FilePath: /myApp/src/utils/request.js 5 + * @FilePath: /xyxBooking-weapp/src/utils/request.js
6 * @Description: 简单axios封装,后续按实际处理 6 * @Description: 简单axios封装,后续按实际处理
7 */ 7 */
8 // import axios from 'axios' 8 // import axios from 'axios'
...@@ -10,7 +10,7 @@ import axios from 'axios-miniprogram'; ...@@ -10,7 +10,7 @@ import axios from 'axios-miniprogram';
10 import Taro from '@tarojs/taro' 10 import Taro from '@tarojs/taro'
11 // import { strExist } from './tools' 11 // import { strExist } from './tools'
12 // import qs from 'Qs' 12 // import qs from 'Qs'
13 -import { routerStore } from '@/stores/router' 13 +import { mainStore } from '@/stores/main'
14 14
15 // import { ProgressStart, ProgressEnd } from '@/components/axios-progress/progress'; 15 // import { ProgressStart, ProgressEnd } from '@/components/axios-progress/progress';
16 // import store from '@/store' 16 // import store from '@/store'
...@@ -23,35 +23,13 @@ import BASE_URL from './config'; ...@@ -23,35 +23,13 @@ import BASE_URL from './config';
23 */ 23 */
24 const getSessionId = () => { 24 const getSessionId = () => {
25 try { 25 try {
26 - return wx.getStorageSync("sessionid") || null; 26 + return Taro.getStorageSync("sessionid") || null;
27 } catch (error) { 27 } catch (error) {
28 console.error('获取sessionid失败:', error); 28 console.error('获取sessionid失败:', error);
29 return null; 29 return null;
30 } 30 }
31 }; 31 };
32 32
33 -/**
34 - * 设置sessionid的工具函数
35 - * @param {string} sessionid - 要设置的sessionid
36 - */
37 -const setSessionId = (sessionid) => {
38 - try {
39 - wx.setStorageSync("sessionid", sessionid);
40 - } catch (error) {
41 - console.error('设置sessionid失败:', error);
42 - }
43 -};
44 -
45 -/**
46 - * 清除sessionid的工具函数
47 - */
48 -const clearSessionId = () => {
49 - try {
50 - wx.removeStorageSync("sessionid");
51 - } catch (error) {
52 - console.error('清除sessionid失败:', error);
53 - }
54 -};
55 // create an axios instance 33 // create an axios instance
56 const service = axios.create({ 34 const service = axios.create({
57 baseURL: BASE_URL, // url = base url + request url 35 baseURL: BASE_URL, // url = base url + request url
...@@ -60,8 +38,8 @@ const service = axios.create({ ...@@ -60,8 +38,8 @@ const service = axios.create({
60 }) 38 })
61 39
62 service.defaults.params = { 40 service.defaults.params = {
63 - f: 'room', 41 + f: 'reserve',
64 - client_id: '772428', 42 + client_name: '智慧西园寺',
65 }; 43 };
66 44
67 // request interceptor 45 // request interceptor
...@@ -78,6 +56,11 @@ service.interceptors.request.use( ...@@ -78,6 +56,11 @@ service.interceptors.request.use(
78 if (sessionid) { 56 if (sessionid) {
79 config.headers.cookie = sessionid; 57 config.headers.cookie = sessionid;
80 } 58 }
59 +
60 + // 增加时间戳
61 + if (config.method === 'get') {
62 + config.params = { ...config.params, timestamp: (new Date()).valueOf() }
63 + }
81 64
82 /** 65 /**
83 * POST PHP需要修改数据格式 66 * POST PHP需要修改数据格式
...@@ -106,79 +89,37 @@ service.interceptors.response.use( ...@@ -106,79 +89,37 @@ service.interceptors.response.use(
106 * You can also judge the status by HTTP Status Code 89 * You can also judge the status by HTTP Status Code
107 */ 90 */
108 response => { 91 response => {
109 - /** 92 + const res = response.data
110 - * 检查响应头中是否有新的sessionid 93 +
111 - * 如果有,则更新本地存储 94 + // 401 未授权处理
112 - */ 95 + if (res.code === 401) {
113 - const setCookieHeader = response.headers['set-cookie']; 96 + // 跳转到授权页
114 - if (setCookieHeader) { 97 + // 避免死循环,如果已经在 auth 页则不跳
115 - // 解析set-cookie头,提取sessionid 98 + const pages = Taro.getCurrentPages();
116 - const sessionidMatch = setCookieHeader.match(/sessionid=([^;]+)/); 99 + const currentPage = pages[pages.length - 1];
117 - if (sessionidMatch && sessionidMatch[1]) { 100 + if (currentPage && currentPage.route !== 'pages/auth/index') {
118 - setSessionId(sessionidMatch[1]); 101 + Taro.navigateTo({
119 - } 102 + url: '/pages/auth/index'
103 + });
104 + }
105 + return response; // 返回 response 以便业务代码处理(或者这里 reject)
120 } 106 }
121 107
122 - // wx.hideLoading(); 108 + if (['预约ID不存在'].includes(res.msg)) {
123 - // const res = response.data 109 + res.show = false;
124 - // // Toast.clear();
125 - // // if the custom code is not 20000, it is judged as an error.
126 - // if (res.code !== 100000) {
127 - // // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
128 - // if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
129 - // // to re-login
130 - // // Toast.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
131 - // // confirmButtonText: 'Re-Login',
132 - // // cancelButtonText: 'Cancel',
133 - // // type: 'warning'
134 - // // }).then(() => {
135 - // // // store.dispatch('user/resetToken').then(() => {
136 - // // // location.reload()
137 - // // // })
138 - // // })
139 - // } else {
140 - // // Toast.fail({
141 - // // message: res.message,
142 - // // duration: 1.5 * 1000
143 - // // })
144 - // // Tips.error(res.message, false)
145 - // }
146 - // return Promise.reject(new Error(res.message || 'Error'))
147 - // } else {
148 - // return res
149 - // }
150 -
151 - /**
152 - * 处理401未授权状态
153 - * 清除本地sessionid并跳转到登录页
154 - */
155 - if (response.data.code === 401) {
156 - // 清除无效的sessionid
157 - clearSessionId();
158 - /**
159 - * 未授权跳转登录页
160 - * 授权完成后 返回当前页面
161 - */
162 - setTimeout(() => {
163 - Taro.navigateTo({
164 - url: '../../pages/auth/index?url=' + routerStore().url
165 - });
166 - }, 1000);
167 } 110 }
111 +
168 return response 112 return response
169 }, 113 },
170 error => { 114 error => {
171 - // Toast.clear(); 115 + console.log('err' + error) // for debug
172 - console.error('err' + error) // for debug 116 + // Taro.showToast({
173 - // Toast.fail({ 117 + // title: error.message,
174 - // message: error.message, 118 + // icon: 'none',
175 - // duration: 1.5 * 1000 119 + // duration: 2000
176 // }) 120 // })
177 return Promise.reject(error) 121 return Promise.reject(error)
178 } 122 }
179 ) 123 )
180 124
181 -// 导出sessionid管理工具函数
182 -export { getSessionId, setSessionId, clearSessionId };
183 -
184 export default service 125 export default service
......
1 /* 1 /*
2 * @Date: 2022-04-18 15:59:42 2 * @Date: 2022-04-18 15:59:42
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2022-12-07 22:09:30 4 + * @LastEditTime: 2024-01-30 15:43:33
5 - * @FilePath: /swx/src/utils/tools.js 5 + * @FilePath: /xyxBooking-weapp/src/utils/tools.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
8 -import Taro from '@tarojs/taro' 8 +import dayjs from 'dayjs';
9 -import moment from '@/utils/moment.min.js' 9 +import Taro from '@tarojs/taro';
10 10
11 // 格式化时间 11 // 格式化时间
12 const formatDate = (date) => { 12 const formatDate = (date) => {
13 - return moment(date).format('YYYY-MM-DD HH:mm') 13 + return dayjs(date).format('YYYY-MM-DD HH:mm');
14 }; 14 };
15 15
16 /** 16 /**
17 - * @description 判断浏览器属于平台 17 + * @description 判断设备信息
18 * @returns 18 * @returns
19 */ 19 */
20 const wxInfo = () => { 20 const wxInfo = () => {
21 - let u = navigator.userAgent; 21 + const info = Taro.getSystemInfoSync();
22 - let isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1; //android终端或者uc浏览器 22 + const isAndroid = info.platform === 'android';
23 - let isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端 23 + const isiOS = info.platform === 'ios';
24 - let uAgent = navigator.userAgent.toLowerCase(); 24 + // 简单模拟
25 - let isTable = (uAgent.match(/MicroMessenger/i) == 'micromessenger') ? true : false;
26 - let isMobile = window.navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i); // 是否手机端
27 - let isWx = /micromessenger/i.test(navigator.userAgent); // 是否微信
28 - let isWxPc = isWx && !isMobile; // PC端微信
29 return { 25 return {
30 isAndroid, 26 isAndroid,
31 isiOS, 27 isiOS,
32 - isTable, 28 + isTable: false // 小程序通常不是 tablet 模式,或者可以根据 screenWidth 判断
33 - isWxPc
34 }; 29 };
35 }; 30 };
36 31
37 /** 32 /**
38 - * @description 判断多行省略文本
39 - * @param {*} id 目标dom标签
40 - * @returns
41 - */
42 -const hasEllipsis = (id) => {
43 - let oDiv = document.getElementById(id);
44 - let flag = false;
45 - if (oDiv.scrollHeight > oDiv.clientHeight) {
46 - flag = true
47 - }
48 - return flag
49 -}
50 -
51 -/**
52 * @description 解析URL参数 33 * @description 解析URL参数
53 * @param {*} url 34 * @param {*} url
54 * @returns 35 * @returns
55 */ 36 */
56 const parseQueryString = url => { 37 const parseQueryString = url => {
38 + if (!url) return {};
57 var json = {}; 39 var json = {};
58 var arr = url.indexOf('?') >= 0 ? url.substr(url.indexOf('?') + 1).split('&') : []; 40 var arr = url.indexOf('?') >= 0 ? url.substr(url.indexOf('?') + 1).split('&') : [];
59 arr.forEach(item => { 41 arr.forEach(item => {
...@@ -70,31 +52,19 @@ const parseQueryString = url => { ...@@ -70,31 +52,19 @@ const parseQueryString = url => {
70 * @returns 包含状态 52 * @returns 包含状态
71 */ 53 */
72 const strExist = (array, str) => { 54 const strExist = (array, str) => {
55 + if (!str) return false;
73 const exist = array.filter(arr => { 56 const exist = array.filter(arr => {
74 if (str.indexOf(arr) >= 0) return str; 57 if (str.indexOf(arr) >= 0) return str;
75 }) 58 })
76 return exist.length > 0 59 return exist.length > 0
77 } 60 }
78 61
79 -const randomId = (n) => { 62 +const formatDatetime = (data) => { // 格式化日期
80 - const charts = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; 63 + if (!data) return '';
81 - var res = ''; 64 + let begin_time = data?.begin_time.slice(0, -6);
82 - for (var i = 0; i < n; i++) { 65 + let end_time = data?.end_time.slice(0, -6);
83 - undefined 66 + let str = begin_time + ' ' + end_time;
84 - var id = Math.ceil(Math.random() * 35); 67 + return `${str.split(' ')[0]} ${str.split(' ')[1]}-${str.split(' ')[3]}`;
85 - res += charts[id];
86 - }
87 - return res;
88 -}
89 -
90 -/**
91 - * 获取页面query参数
92 - */
93 -const pageQuery = () => {
94 - const instance = Taro.getCurrentInstance();
95 - let $query = '';
96 - $query = JSON.stringify(instance.router.params);
97 - return JSON.parse($query)
98 } 68 }
99 69
100 -export { formatDate, wxInfo, hasEllipsis, parseQueryString, strExist, randomId, pageQuery }; 70 +export { formatDate, wxInfo, parseQueryString, strExist, formatDatetime };
......