docs: 为工具函数、API和组件添加JSDoc注释以提升代码可读性
- 为 utils、api、stores、composables 等模块的函数和变量添加详细的 @description 注释
- 统一接口返回类型为 Promise<{code,data,msg}> 格式说明
- 为组件方法添加参数和返回值类型标注
- 更新文件头部的 @Description 字段使其更具体
- 增强代码自文档化能力,便于团队维护和IDE智能提示
Showing
32 changed files
with
607 additions
and
152 deletions
| ... | @@ -15,33 +15,38 @@ const Api = { | ... | @@ -15,33 +15,38 @@ const Api = { |
| 15 | 15 | ||
| 16 | /** | 16 | /** |
| 17 | * @description: 发送验证码 | 17 | * @description: 发送验证码 |
| 18 | - * @param {*} phone 手机号码 | 18 | + * @param {Object} params 请求参数 |
| 19 | - * @returns | 19 | + * @param {string} params.phone 手机号 |
| 20 | + * @returns {Promise<{code:number,data:any,msg:string}>} 标准返回 | ||
| 20 | */ | 21 | */ |
| 21 | export const smsAPI = (params) => fn(fetch.post(Api.SMS, params)); | 22 | export const smsAPI = (params) => fn(fetch.post(Api.SMS, params)); |
| 22 | 23 | ||
| 23 | /** | 24 | /** |
| 24 | * @description: 获取七牛token | 25 | * @description: 获取七牛token |
| 25 | - * @param {*} filename 文件名 | 26 | + * @param {Object} params 请求参数 |
| 26 | - * @param {*} file 图片base64 | 27 | + * @param {string} params.filename 文件名 |
| 27 | - * @returns | 28 | + * @param {string} params.file 图片 base64 |
| 29 | + * @returns {Promise<{code:number,data:any,msg:string}>} 标准返回(data 为上传 token 等信息) | ||
| 28 | */ | 30 | */ |
| 29 | export const qiniuTokenAPI = (params) => fn(fetch.stringifyPost(Api.TOKEN, params)); | 31 | export const qiniuTokenAPI = (params) => fn(fetch.stringifyPost(Api.TOKEN, params)); |
| 30 | 32 | ||
| 31 | /** | 33 | /** |
| 32 | * @description: 上传七牛 | 34 | * @description: 上传七牛 |
| 33 | - * @param {*} | 35 | + * @param {string} url 七牛上传地址 |
| 34 | - * @returns | 36 | + * @param {any} data 上传数据 |
| 37 | + * @param {Object} config axios 配置 | ||
| 38 | + * @returns {Promise<any|false>} 成功返回七牛响应数据,失败返回 false | ||
| 35 | */ | 39 | */ |
| 36 | export const qiniuUploadAPI = (url, data, config) => uploadFn(fetch.basePost(url, data, config)); | 40 | export const qiniuUploadAPI = (url, data, config) => uploadFn(fetch.basePost(url, data, config)); |
| 37 | 41 | ||
| 38 | /** | 42 | /** |
| 39 | * @description: 保存图片 | 43 | * @description: 保存图片 |
| 40 | - * @param {*} format | 44 | + * @param {Object} params 请求参数 |
| 41 | - * @param {*} hash | 45 | + * @param {string} params.format 文件格式 |
| 42 | - * @param {*} height | 46 | + * @param {string} params.hash 文件 hash |
| 43 | - * @param {*} width | 47 | + * @param {string|number} params.height 图片高 |
| 44 | - * @param {*} filekey | 48 | + * @param {string|number} params.width 图片宽 |
| 45 | - * @returns | 49 | + * @param {string} params.filekey 文件 key |
| 50 | + * @returns {Promise<{code:number,data:any,msg:string}>} 标准返回 | ||
| 46 | */ | 51 | */ |
| 47 | export const saveFileAPI = (params) => fn(fetch.stringifyPost(Api.SAVE_FILE, params)); | 52 | export const saveFileAPI = (params) => fn(fetch.stringifyPost(Api.SAVE_FILE, params)); | ... | ... |
| 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: 2026-01-24 12:13:14 | 4 | + * @LastEditTime: 2026-01-24 12:52:06 |
| 5 | * @FilePath: /xyxBooking-weapp/src/api/fn.js | 5 | * @FilePath: /xyxBooking-weapp/src/api/fn.js |
| 6 | - * @Description: 文件描述 | 6 | + * @Description: 统一后端返回格式(强制 { code, data, msg }) |
| 7 | */ | 7 | */ |
| 8 | import axios from '@/utils/request'; | 8 | import axios from '@/utils/request'; |
| 9 | import Taro from '@tarojs/taro' | 9 | import Taro from '@tarojs/taro' |
| 10 | import qs from 'qs' | 10 | import qs from 'qs' |
| 11 | 11 | ||
| 12 | /** | 12 | /** |
| 13 | - * 网络请求功能函数 | 13 | + * @description 统一后端返回格式(强制 { code, data, msg }) |
| 14 | - * @param {*} api 请求axios接口 | 14 | + * - code === 1 表示成功 |
| 15 | - * @returns 请求成功后,获取数据 | 15 | + * - 失败时统一 toast 提示(可通过 res.data.show=false 禁用提示) |
| 16 | + * @param {Promise<any>} api axios 请求 Promise | ||
| 17 | + * @returns {Promise<{code:number,data:any,msg:string,show?:boolean}>} 标准化后的返回对象 | ||
| 16 | */ | 18 | */ |
| 17 | export const fn = (api) => { | 19 | export const fn = (api) => { |
| 18 | return api | 20 | return api |
| 19 | .then(res => { | 21 | .then(res => { |
| 20 | - // 适配 H5 逻辑,code === 1 为成功 | 22 | + // 约定:后端 code === 1 为成功 |
| 21 | const res_data = res && res.data ? res.data : null | 23 | const res_data = res && res.data ? res.data : null |
| 22 | if (res_data && String(res_data.code) === '1') { | 24 | if (res_data && String(res_data.code) === '1') { |
| 23 | return res_data | 25 | return res_data |
| 24 | } | 26 | } |
| 25 | - // tslint:disable-next-line: no-console | 27 | + // 失败兜底:优先返回后端响应,同时做 toast 提示 |
| 26 | - console.warn('API Error:', res) | 28 | + console.warn('接口请求失败:', res) |
| 27 | if (res_data && res_data.show === false) return res_data | 29 | if (res_data && res_data.show === false) return res_data |
| 28 | Taro.showToast({ | 30 | Taro.showToast({ |
| 29 | title: (res_data && res_data.msg) ? res_data.msg : '请求失败', | 31 | title: (res_data && res_data.msg) ? res_data.msg : '请求失败', |
| ... | @@ -33,8 +35,7 @@ export const fn = (api) => { | ... | @@ -33,8 +35,7 @@ export const fn = (api) => { |
| 33 | return res_data || { code: 0, data: null, msg: '请求失败' } | 35 | return res_data || { code: 0, data: null, msg: '请求失败' } |
| 34 | }) | 36 | }) |
| 35 | .catch(err => { | 37 | .catch(err => { |
| 36 | - // tslint:disable-next-line: no-console | 38 | + console.error('接口请求异常:', err); |
| 37 | - console.error(err); | ||
| 38 | return { code: 0, data: null, msg: (err && (err.msg || err.message || err.errMsg)) ? (err.msg || err.message || err.errMsg) : '网络异常' } | 39 | return { code: 0, data: null, msg: (err && (err.msg || err.message || err.errMsg)) ? (err.msg || err.message || err.errMsg) : '网络异常' } |
| 39 | }) | 40 | }) |
| 40 | .finally(() => { // 最终执行 | 41 | .finally(() => { // 最终执行 |
| ... | @@ -42,9 +43,9 @@ export const fn = (api) => { | ... | @@ -42,9 +43,9 @@ export const fn = (api) => { |
| 42 | } | 43 | } |
| 43 | 44 | ||
| 44 | /** | 45 | /** |
| 45 | - * 七牛返回格式 | 46 | + * @description 七牛上传返回格式适配 |
| 46 | - * @param {*} api | 47 | + * @param {Promise<any>} api axios 请求 Promise |
| 47 | - * @returns | 48 | + * @returns {Promise<any|false>} 成功返回七牛响应数据,失败返回 false |
| 48 | */ | 49 | */ |
| 49 | export const uploadFn = (api) => { | 50 | export const uploadFn = (api) => { |
| 50 | return api | 51 | return api |
| ... | @@ -52,8 +53,7 @@ export const uploadFn = (api) => { | ... | @@ -52,8 +53,7 @@ export const uploadFn = (api) => { |
| 52 | if (res.statusText === 'OK') { | 53 | if (res.statusText === 'OK') { |
| 53 | return res.data || true; | 54 | return res.data || true; |
| 54 | } else { | 55 | } else { |
| 55 | - // tslint:disable-next-line: no-console | 56 | + console.warn('七牛上传失败:', res); |
| 56 | - console.warn(res); | ||
| 57 | if (!res.data.show) return false; | 57 | if (!res.data.show) return false; |
| 58 | Taro.showToast({ | 58 | Taro.showToast({ |
| 59 | title: res.data.msg, | 59 | title: res.data.msg, |
| ... | @@ -64,22 +64,42 @@ export const uploadFn = (api) => { | ... | @@ -64,22 +64,42 @@ export const uploadFn = (api) => { |
| 64 | } | 64 | } |
| 65 | }) | 65 | }) |
| 66 | .catch(err => { | 66 | .catch(err => { |
| 67 | - // tslint:disable-next-line: no-console | 67 | + console.error('七牛上传异常:', err); |
| 68 | - console.error(err); | ||
| 69 | return false; | 68 | return false; |
| 70 | }) | 69 | }) |
| 71 | } | 70 | } |
| 72 | 71 | ||
| 73 | /** | 72 | /** |
| 74 | - * 统一 GET/POST 不同传参形式 | 73 | + * @description 统一 GET/POST 传参形式 |
| 74 | + * - get/post:直接透传 axios | ||
| 75 | + * - stringifyPost:表单提交(x-www-form-urlencoded) | ||
| 76 | + * - basePost:保留自定义 config 的扩展位 | ||
| 75 | */ | 77 | */ |
| 76 | export const fetch = { | 78 | export const fetch = { |
| 79 | + /** | ||
| 80 | + * @description GET 请求 | ||
| 81 | + * @param {string} api 接口地址 | ||
| 82 | + * @param {Object} params 查询参数 | ||
| 83 | + * @returns {Promise<any>} axios Promise | ||
| 84 | + */ | ||
| 77 | get: function (api, params) { | 85 | get: function (api, params) { |
| 78 | return axios.get(api, params) | 86 | return axios.get(api, params) |
| 79 | }, | 87 | }, |
| 88 | + /** | ||
| 89 | + * @description POST 请求(JSON) | ||
| 90 | + * @param {string} api 接口地址 | ||
| 91 | + * @param {Object} params 请求体 | ||
| 92 | + * @returns {Promise<any>} axios Promise | ||
| 93 | + */ | ||
| 80 | post: function (api, params) { | 94 | post: function (api, params) { |
| 81 | return axios.post(api, params) | 95 | return axios.post(api, params) |
| 82 | }, | 96 | }, |
| 97 | + /** | ||
| 98 | + * @description POST 请求(表单序列化) | ||
| 99 | + * @param {string} api 接口地址 | ||
| 100 | + * @param {Object} params 请求体 | ||
| 101 | + * @returns {Promise<any>} axios Promise | ||
| 102 | + */ | ||
| 83 | stringifyPost: function (api, params) { | 103 | stringifyPost: function (api, params) { |
| 84 | return axios.post(api, qs.stringify(params), { | 104 | return axios.post(api, qs.stringify(params), { |
| 85 | headers: { | 105 | headers: { |
| ... | @@ -87,6 +107,13 @@ export const fetch = { | ... | @@ -87,6 +107,13 @@ export const fetch = { |
| 87 | } | 107 | } |
| 88 | }) | 108 | }) |
| 89 | }, | 109 | }, |
| 110 | + /** | ||
| 111 | + * @description POST 请求(自定义 config) | ||
| 112 | + * @param {string} url 接口地址 | ||
| 113 | + * @param {any} data 请求体 | ||
| 114 | + * @param {Object} config axios 配置 | ||
| 115 | + * @returns {Promise<any>} axios Promise | ||
| 116 | + */ | ||
| 90 | basePost: function (url, data, config) { | 117 | basePost: function (url, data, config) { |
| 91 | return axios.post(url, data, config) | 118 | return axios.post(url, data, config) |
| 92 | } | 119 | } | ... | ... |
| ... | @@ -7,6 +7,10 @@ | ... | @@ -7,6 +7,10 @@ |
| 7 | */ | 7 | */ |
| 8 | import { fn, fetch } from '@/api/fn'; | 8 | import { fn, fetch } from '@/api/fn'; |
| 9 | 9 | ||
| 10 | +/** | ||
| 11 | + * @description 预约业务 API 聚合 | ||
| 12 | + * - 所有接口统一走 fn(fetch.xxx) 保证返回 { code, data, msg } | ||
| 13 | + */ | ||
| 10 | const Api = { | 14 | const Api = { |
| 11 | CAN_RESERVE_DATE_LIST: '/srv/?a=api&t=can_reserve_date_list', | 15 | CAN_RESERVE_DATE_LIST: '/srv/?a=api&t=can_reserve_date_list', |
| 12 | CAN_RESERVE_TIME_LIST: '/srv/?a=api&t=can_reserve_time_list', | 16 | CAN_RESERVE_TIME_LIST: '/srv/?a=api&t=can_reserve_time_list', |
| ... | @@ -171,14 +175,16 @@ export const billPersonAPI = (params) => fn(fetch.get(Api.BILL_PREPARE, params)) | ... | @@ -171,14 +175,16 @@ export const billPersonAPI = (params) => fn(fetch.get(Api.BILL_PREPARE, params)) |
| 171 | 175 | ||
| 172 | /** | 176 | /** |
| 173 | * @description: 身份证查询预约码 | 177 | * @description: 身份证查询预约码 |
| 174 | - * @param {String} | 178 | + * @param {Object} params 请求参数 |
| 175 | - * @returns {String} | 179 | + * @param {string} params.id_number 身份证号 |
| 180 | + * @returns {Promise<{code:number,data:any,msg:string}>} 标准返回 | ||
| 176 | */ | 181 | */ |
| 177 | export const queryQrCodeAPI = (params) => fn(fetch.get(Api.QUERY_QR_CODE, params)); | 182 | export const queryQrCodeAPI = (params) => fn(fetch.get(Api.QUERY_QR_CODE, params)); |
| 178 | 183 | ||
| 179 | /** | 184 | /** |
| 180 | * @description: 查询订单号 | 185 | * @description: 查询订单号 |
| 181 | - * @param {String} | 186 | + * @param {Object} params 请求参数 |
| 182 | - * @returns {String} | 187 | + * @param {string} params.pay_id 支付凭证/订单号 |
| 188 | + * @returns {Promise<{code:number,data:any,msg:string}>} 标准返回 | ||
| 183 | */ | 189 | */ |
| 184 | export const icbcOrderQryAPI = (params) => fn(fetch.get(Api.ICBC_ORDER_QRY, params)); | 190 | export const icbcOrderQryAPI = (params) => fn(fetch.get(Api.ICBC_ORDER_QRY, params)); | ... | ... |
| ... | @@ -15,16 +15,21 @@ const Api = { | ... | @@ -15,16 +15,21 @@ const Api = { |
| 15 | 15 | ||
| 16 | /** | 16 | /** |
| 17 | * @description: 义工登录 | 17 | * @description: 义工登录 |
| 18 | + * @param {Object} params 请求参数 | ||
| 19 | + * @returns {Promise<{code:number,data:any,msg:string}>} 标准返回 | ||
| 18 | */ | 20 | */ |
| 19 | export const volunteerLoginAPI = (params) => fn(fetch.post(Api.REDEEM_LOGIN, params)); | 21 | export const volunteerLoginAPI = (params) => fn(fetch.post(Api.REDEEM_LOGIN, params)); |
| 20 | 22 | ||
| 21 | /** | 23 | /** |
| 22 | * @description: 检查核销权限 | 24 | * @description: 检查核销权限 |
| 23 | - * @returns {Object} { data.can_redeem: Boolean, msg: String} | 25 | + * @param {Object} params 请求参数 |
| 26 | + * @returns {Promise<{code:number,data:{can_redeem:boolean},msg:string}>} 标准返回 | ||
| 24 | */ | 27 | */ |
| 25 | export const checkRedeemPermissionAPI = (params) => fn(fetch.get(Api.REDEEM_CHECK_AUTH, params)); | 28 | export const checkRedeemPermissionAPI = (params) => fn(fetch.get(Api.REDEEM_CHECK_AUTH, params)); |
| 26 | 29 | ||
| 27 | /** | 30 | /** |
| 28 | * @description: 核销 | 31 | * @description: 核销 |
| 32 | + * @param {Object} params 请求参数 | ||
| 33 | + * @returns {Promise<{code:number,data:any,msg:string}>} 标准返回 | ||
| 29 | */ | 34 | */ |
| 30 | export const verifyTicketAPI = (params) => fn(fetch.post(Api.REDEEM_REDEEM, params)); | 35 | export const verifyTicketAPI = (params) => fn(fetch.post(Api.REDEEM_REDEEM, params)); | ... | ... |
| ... | @@ -13,8 +13,9 @@ const Api = { | ... | @@ -13,8 +13,9 @@ const Api = { |
| 13 | } | 13 | } |
| 14 | 14 | ||
| 15 | /** | 15 | /** |
| 16 | - * @description 获取微信CONFIG配置文件 | 16 | + * @description 获取微信分享配置(wx.config 所需参数) |
| 17 | - * @param {*} url | 17 | + * @param {Object} params 请求参数 |
| 18 | - * @returns {*} cfg | 18 | + * @param {string} params.url 当前页面 URL(用于签名) |
| 19 | + * @returns {Promise<{code:number,data:any,msg:string}>} 标准返回 | ||
| 19 | */ | 20 | */ |
| 20 | export const wxJsAPI = (params) => fn(fetch.get(Api.WX_JSAPI, params)); | 21 | export const wxJsAPI = (params) => fn(fetch.get(Api.WX_JSAPI, params)); | ... | ... |
| ... | @@ -5,6 +5,11 @@ | ... | @@ -5,6 +5,11 @@ |
| 5 | * @FilePath: /tswj/src/api/wx/jsApiList.js | 5 | * @FilePath: /tswj/src/api/wx/jsApiList.js |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | */ | 7 | */ |
| 8 | +/** | ||
| 9 | + * @description 微信 JSSDK 需要注入的 API 列表 | ||
| 10 | + * - 该列表用于 wx.config 的 jsApiList 字段 | ||
| 11 | + * @type {Array<string>} | ||
| 12 | + */ | ||
| 8 | export const apiList = [ | 13 | export const apiList = [ |
| 9 | "updateAppMessageShareData", | 14 | "updateAppMessageShareData", |
| 10 | "updateTimelineShareData", | 15 | "updateTimelineShareData", | ... | ... |
| ... | @@ -14,7 +14,8 @@ const Api = { | ... | @@ -14,7 +14,8 @@ const Api = { |
| 14 | 14 | ||
| 15 | /** | 15 | /** |
| 16 | * @description: 微信支付接口 | 16 | * @description: 微信支付接口 |
| 17 | - * @param {*} pay_id 预约单支付凭证 | 17 | + * @param {Object} params 请求参数 |
| 18 | - * @returns {*} 微信支付参数 | 18 | + * @param {string} params.pay_id 预约单支付凭证 |
| 19 | + * @returns {Promise<{code:number,data:any,msg:string}>} 标准返回(data 为微信支付参数) | ||
| 19 | */ | 20 | */ |
| 20 | export const wxPayAPI = (params) => fn(fetch.post(Api.WX_PAY, params)); | 21 | export const wxPayAPI = (params) => fn(fetch.post(Api.WX_PAY, params)); | ... | ... |
| ... | @@ -38,9 +38,10 @@ const App = createApp({ | ... | @@ -38,9 +38,10 @@ const App = createApp({ |
| 38 | } | 38 | } |
| 39 | 39 | ||
| 40 | /** | 40 | /** |
| 41 | - * 预加载离线预约记录数据(列表+详情) | 41 | + * @description 预加载离线预约记录数据(列表+详情) |
| 42 | * - 仅在有授权且网络可用时调用 | 42 | * - 仅在有授权且网络可用时调用 |
| 43 | * - 成功后将数据存储到本地缓存(key: OFFLINE_BOOKING_DATA) | 43 | * - 成功后将数据存储到本地缓存(key: OFFLINE_BOOKING_DATA) |
| 44 | + * @returns {Promise<void>} 无返回值 | ||
| 44 | */ | 45 | */ |
| 45 | const preloadBookingData = async () => { | 46 | const preloadBookingData = async () => { |
| 46 | try { | 47 | try { |
| ... | @@ -51,8 +52,9 @@ const App = createApp({ | ... | @@ -51,8 +52,9 @@ const App = createApp({ |
| 51 | } | 52 | } |
| 52 | 53 | ||
| 53 | /** | 54 | /** |
| 54 | - * 判断是否应该跳过网络异常提示弹窗 | 55 | + * @description 判断是否应该跳过网络异常提示弹窗 |
| 55 | * - 仅在当前页面为离线预约列表/详情/核销码页时返回 true | 56 | * - 仅在当前页面为离线预约列表/详情/核销码页时返回 true |
| 57 | + * @returns {boolean} true=跳过提示,false=不跳过 | ||
| 56 | */ | 58 | */ |
| 57 | 59 | ||
| 58 | const should_skip_network_prompt = () => { | 60 | const should_skip_network_prompt = () => { |
| ... | @@ -67,10 +69,12 @@ const App = createApp({ | ... | @@ -67,10 +69,12 @@ const App = createApp({ |
| 67 | } | 69 | } |
| 68 | 70 | ||
| 69 | /** | 71 | /** |
| 70 | - * 处理不良网络情况 | 72 | + * @description 处理不良网络情况 |
| 71 | * - 仅在当前页面未跳过提示弹窗时调用 | 73 | * - 仅在当前页面未跳过提示弹窗时调用 |
| 72 | * - 若有离线预约缓存,则展示弹窗询问是否使用缓存数据 | 74 | * - 若有离线预约缓存,则展示弹窗询问是否使用缓存数据 |
| 73 | - * - 否则展示简单提示弹窗 | 75 | + * - 否则展示简单提示 toast |
| 76 | + * @param {string} network_type 网络类型(wifi/4g/5g/3g/none/unknown) | ||
| 77 | + * @returns {Promise<boolean>} true=需要中断后续启动流程,false=继续 | ||
| 74 | */ | 78 | */ |
| 75 | 79 | ||
| 76 | const handle_bad_network = async (network_type) => { | 80 | const handle_bad_network = async (network_type) => { |
| ... | @@ -129,10 +133,10 @@ const App = createApp({ | ... | @@ -129,10 +133,10 @@ const App = createApp({ |
| 129 | }) | 133 | }) |
| 130 | 134 | ||
| 131 | /** | 135 | /** |
| 132 | - * 处理在启动时出现的不良网络情况 | 136 | + * @description 处理启动时的不良网络情况(只在启动阶段检查一次) |
| 133 | - * - 当网络连接不良且有离线预约记录缓存时,提示用户是否使用缓存进入离线模式 | 137 | + * - 网络不可用且有离线缓存:询问是否进入离线模式 |
| 134 | - * - 当网络连接不良且无缓存时,提示用户网络连接不畅 | 138 | + * - 网络不可用且无缓存:toast 提示网络不佳 |
| 135 | - * @returns {Promise<boolean>} 如果用户选择进入离线模式,则返回 true;否则返回 false | 139 | + * @returns {Promise<boolean>} true=进入离线模式并中断启动,false=继续启动 |
| 136 | */ | 140 | */ |
| 137 | const handle_bad_network_on_launch = async () => { | 141 | const handle_bad_network_on_launch = async () => { |
| 138 | /** | 142 | /** |
| ... | @@ -148,9 +152,9 @@ const App = createApp({ | ... | @@ -148,9 +152,9 @@ const App = createApp({ |
| 148 | } | 152 | } |
| 149 | 153 | ||
| 150 | /** | 154 | /** |
| 151 | - * 尝试在网络可用时预加载离线预约记录数据 | 155 | + * @description 尝试在网络可用时预加载离线预约记录数据 |
| 152 | * - 仅在有授权时调用 | 156 | * - 仅在有授权时调用 |
| 153 | - * @returns {Promise<void>} | 157 | + * @returns {void} 无返回值 |
| 154 | */ | 158 | */ |
| 155 | const try_preload_when_online = () => { | 159 | const try_preload_when_online = () => { |
| 156 | if (!hasAuth()) return | 160 | if (!hasAuth()) return |
| ... | @@ -164,9 +168,10 @@ const App = createApp({ | ... | @@ -164,9 +168,10 @@ const App = createApp({ |
| 164 | } | 168 | } |
| 165 | 169 | ||
| 166 | /** | 170 | /** |
| 167 | - * @description: 授权成功后的共用启动逻辑 | 171 | + * @description 授权成功后的共用启动逻辑 |
| 168 | * - 尝试在网络可用时预加载离线预约数据 | 172 | * - 尝试在网络可用时预加载离线预约数据 |
| 169 | * - 启动离线预约缓存轮询(会自行处理网络可用性与引用计数) | 173 | * - 启动离线预约缓存轮询(会自行处理网络可用性与引用计数) |
| 174 | + * @returns {void} 无返回值 | ||
| 170 | */ | 175 | */ |
| 171 | 176 | ||
| 172 | const bootstrap_after_auth = () => { | 177 | const bootstrap_after_auth = () => { | ... | ... |
| 1 | import Taro from '@tarojs/taro' | 1 | import Taro from '@tarojs/taro' |
| 2 | 2 | ||
| 3 | +/** | ||
| 4 | + * @description 生成随机字符串(递归补齐长度) | ||
| 5 | + * @param {number} length 目标长度 | ||
| 6 | + * @returns {string} 随机字符串 | ||
| 7 | + */ | ||
| 3 | export function randomString(length) { | 8 | export function randomString(length) { |
| 4 | let str = Math.random().toString(36).substr(2) | 9 | let str = Math.random().toString(36).substr(2) |
| 5 | if (str.length >= length) { | 10 | if (str.length >= length) { |
| ... | @@ -9,10 +14,21 @@ export function randomString(length) { | ... | @@ -9,10 +14,21 @@ export function randomString(length) { |
| 9 | return str | 14 | return str |
| 10 | } | 15 | } |
| 11 | 16 | ||
| 17 | +/** | ||
| 18 | + * @description 生成随机 id(常用于 canvasId) | ||
| 19 | + * @param {string} prefix 前缀 | ||
| 20 | + * @param {number} length 随机段长度 | ||
| 21 | + * @returns {string} 随机 id | ||
| 22 | + */ | ||
| 12 | export function getRandomId(prefix = 'canvas', length = 10) { | 23 | export function getRandomId(prefix = 'canvas', length = 10) { |
| 13 | return prefix + randomString(length) | 24 | return prefix + randomString(length) |
| 14 | } | 25 | } |
| 15 | 26 | ||
| 27 | +/** | ||
| 28 | + * @description 将 http 链接转换为 https(小程序部分场景要求 https) | ||
| 29 | + * @param {string} rawUrl 原始 url | ||
| 30 | + * @returns {string} 处理后的 url | ||
| 31 | + */ | ||
| 16 | export function mapHttpToHttps(rawUrl) { | 32 | export function mapHttpToHttps(rawUrl) { |
| 17 | if (rawUrl.indexOf(':') < 0 || rawUrl.startsWith('http://tmp')) { | 33 | if (rawUrl.indexOf(':') < 0 || rawUrl.startsWith('http://tmp')) { |
| 18 | return rawUrl | 34 | return rawUrl |
| ... | @@ -27,18 +43,40 @@ export function mapHttpToHttps(rawUrl) { | ... | @@ -27,18 +43,40 @@ export function mapHttpToHttps(rawUrl) { |
| 27 | return rawUrl | 43 | return rawUrl |
| 28 | } | 44 | } |
| 29 | 45 | ||
| 46 | +/** | ||
| 47 | + * @description 获取 rpx 与 px 的换算系数(以 750 设计稿为基准) | ||
| 48 | + * @returns {number} 系数(screenWidth / 750) | ||
| 49 | + */ | ||
| 30 | export const getFactor = () => { | 50 | export const getFactor = () => { |
| 31 | const sysInfo = Taro.getSystemInfoSync() | 51 | const sysInfo = Taro.getSystemInfoSync() |
| 32 | const { screenWidth } = sysInfo | 52 | const { screenWidth } = sysInfo |
| 33 | return screenWidth / 750 | 53 | return screenWidth / 750 |
| 34 | } | 54 | } |
| 35 | 55 | ||
| 56 | +/** | ||
| 57 | + * @description rpx 转 px | ||
| 58 | + * @param {number} rpx rpx 值 | ||
| 59 | + * @param {number} factor 换算系数 | ||
| 60 | + * @returns {number} px 值(整数) | ||
| 61 | + */ | ||
| 36 | export const toPx = (rpx, factor = getFactor()) => | 62 | export const toPx = (rpx, factor = getFactor()) => |
| 37 | parseInt(String(rpx * factor), 10) | 63 | parseInt(String(rpx * factor), 10) |
| 38 | 64 | ||
| 65 | +/** | ||
| 66 | + * @description px 转 rpx | ||
| 67 | + * @param {number} px px 值 | ||
| 68 | + * @param {number} factor 换算系数 | ||
| 69 | + * @returns {number} rpx 值(整数) | ||
| 70 | + */ | ||
| 39 | export const toRpx = (px, factor = getFactor()) => | 71 | export const toRpx = (px, factor = getFactor()) => |
| 40 | parseInt(String(px / factor), 10) | 72 | parseInt(String(px / factor), 10) |
| 41 | 73 | ||
| 74 | +/** | ||
| 75 | + * @description 下载图片到本地临时路径(避免跨域/协议限制) | ||
| 76 | + * - 已是本地路径/用户数据路径时直接返回 | ||
| 77 | + * @param {string} url 图片地址 | ||
| 78 | + * @returns {Promise<string>} 本地可用的图片路径 | ||
| 79 | + */ | ||
| 42 | export function downImage(url) { | 80 | export function downImage(url) { |
| 43 | return new Promise((resolve, reject) => { | 81 | return new Promise((resolve, reject) => { |
| 44 | const wx_user_data_path = | 82 | const wx_user_data_path = |
| ... | @@ -69,6 +107,12 @@ export function downImage(url) { | ... | @@ -69,6 +107,12 @@ export function downImage(url) { |
| 69 | }) | 107 | }) |
| 70 | } | 108 | } |
| 71 | 109 | ||
| 110 | +/** | ||
| 111 | + * @description 获取图片信息并计算裁剪参数(居中裁剪) | ||
| 112 | + * @param {Object} item 图片配置 | ||
| 113 | + * @param {number} index 渲染顺序(默认 zIndex) | ||
| 114 | + * @returns {Promise<Object>} 标准化后的图片绘制参数 | ||
| 115 | + */ | ||
| 72 | export const getImageInfo = (item, index) => | 116 | export const getImageInfo = (item, index) => |
| 73 | new Promise((resolve, reject) => { | 117 | new Promise((resolve, reject) => { |
| 74 | const { x, y, width, height, url, zIndex } = item | 118 | const { x, y, width, height, url, zIndex } = item |
| ... | @@ -112,6 +156,16 @@ export const getImageInfo = (item, index) => | ... | @@ -112,6 +156,16 @@ export const getImageInfo = (item, index) => |
| 112 | ) | 156 | ) |
| 113 | }) | 157 | }) |
| 114 | 158 | ||
| 159 | +/** | ||
| 160 | + * @description 解析 linear-gradient 字符串为 canvas 渐变色 | ||
| 161 | + * @param {CanvasRenderingContext2D} ctx canvas 上下文 | ||
| 162 | + * @param {string} color 颜色字符串(支持 linear-gradient(...)) | ||
| 163 | + * @param {number} startX 起点 x | ||
| 164 | + * @param {number} startY 起点 y | ||
| 165 | + * @param {number} w 宽度 | ||
| 166 | + * @param {number} h 高度 | ||
| 167 | + * @returns {any} 普通颜色字符串或渐变对象 | ||
| 168 | + */ | ||
| 115 | export function getLinearColor(ctx, color, startX, startY, w, h) { | 169 | export function getLinearColor(ctx, color, startX, startY, w, h) { |
| 116 | if ( | 170 | if ( |
| 117 | typeof startX !== 'number' || | 171 | typeof startX !== 'number' || |
| ... | @@ -150,6 +204,13 @@ export function getLinearColor(ctx, color, startX, startY, w, h) { | ... | @@ -150,6 +204,13 @@ export function getLinearColor(ctx, color, startX, startY, w, h) { |
| 150 | return grd | 204 | return grd |
| 151 | } | 205 | } |
| 152 | 206 | ||
| 207 | +/** | ||
| 208 | + * @description 根据 textAlign 计算文本绘制起点 x | ||
| 209 | + * @param {'left'|'center'|'right'} textAlign 对齐方式 | ||
| 210 | + * @param {number} x 原始 x | ||
| 211 | + * @param {number} width 容器宽 | ||
| 212 | + * @returns {number} 计算后的 x | ||
| 213 | + */ | ||
| 153 | export function getTextX(textAlign, x, width) { | 214 | export function getTextX(textAlign, x, width) { |
| 154 | let newX = x | 215 | let newX = x |
| 155 | if (textAlign === 'center') { | 216 | if (textAlign === 'center') { | ... | ... |
| ... | @@ -51,7 +51,7 @@ | ... | @@ -51,7 +51,7 @@ |
| 51 | 51 | ||
| 52 | <script setup> | 52 | <script setup> |
| 53 | import { ref, computed, watch, onMounted, onUnmounted } from 'vue' | 53 | import { ref, computed, watch, onMounted, onUnmounted } from 'vue' |
| 54 | -import Taro, { useDidShow } from '@tarojs/taro' | 54 | +import Taro from '@tarojs/taro' |
| 55 | import { formatDatetime } from '@/utils/tools'; | 55 | import { formatDatetime } from '@/utils/tools'; |
| 56 | import { qrcodeListAPI, qrcodeStatusAPI, billPersonAPI } from '@/api/index' | 56 | import { qrcodeListAPI, qrcodeStatusAPI, billPersonAPI } from '@/api/index' |
| 57 | import { useGo } from '@/hooks/useGo' | 57 | import { useGo } from '@/hooks/useGo' |
| ... | @@ -77,12 +77,21 @@ const props = defineProps({ | ... | @@ -77,12 +77,21 @@ const props = defineProps({ |
| 77 | const select_index = ref(0); | 77 | const select_index = ref(0); |
| 78 | const userList = ref([]); | 78 | const userList = ref([]); |
| 79 | 79 | ||
| 80 | +/** | ||
| 81 | + * @description 切换到上一张二维码(循环) | ||
| 82 | + * @returns {void} 无返回值 | ||
| 83 | + */ | ||
| 80 | const prevCode = () => { | 84 | const prevCode = () => { |
| 81 | select_index.value = select_index.value - 1; | 85 | select_index.value = select_index.value - 1; |
| 82 | if (select_index.value < 0) { | 86 | if (select_index.value < 0) { |
| 83 | select_index.value = userList.value.length - 1; | 87 | select_index.value = userList.value.length - 1; |
| 84 | } | 88 | } |
| 85 | }; | 89 | }; |
| 90 | + | ||
| 91 | +/** | ||
| 92 | + * @description 切换到下一张二维码(循环) | ||
| 93 | + * @returns {void} 无返回值 | ||
| 94 | + */ | ||
| 86 | const nextCode = () => { | 95 | const nextCode = () => { |
| 87 | select_index.value = select_index.value + 1; | 96 | select_index.value = select_index.value + 1; |
| 88 | if (select_index.value > userList.value.length - 1) { | 97 | if (select_index.value > userList.value.length - 1) { |
| ... | @@ -92,7 +101,7 @@ const nextCode = () => { | ... | @@ -92,7 +101,7 @@ const nextCode = () => { |
| 92 | 101 | ||
| 93 | watch( | 102 | watch( |
| 94 | () => select_index.value, | 103 | () => select_index.value, |
| 95 | - (index) => { | 104 | + () => { |
| 96 | refreshBtn(); | 105 | refreshBtn(); |
| 97 | } | 106 | } |
| 98 | ) | 107 | ) |
| ... | @@ -107,7 +116,12 @@ watch( | ... | @@ -107,7 +116,12 @@ watch( |
| 107 | { immediate: true } | 116 | { immediate: true } |
| 108 | ) | 117 | ) |
| 109 | 118 | ||
| 110 | -function replaceMiddleCharacters(inputString) { | 119 | +/** |
| 120 | + * @description 身份证号脱敏:中间 8 位替换为 * 号 | ||
| 121 | + * @param {string} inputString 原始身份证号 | ||
| 122 | + * @returns {string} 脱敏后的身份证号 | ||
| 123 | + */ | ||
| 124 | +function replaceMiddleCharacters (inputString) { | ||
| 111 | if (!inputString || inputString.length < 15) { | 125 | if (!inputString || inputString.length < 15) { |
| 112 | return inputString; | 126 | return inputString; |
| 113 | } | 127 | } |
| ... | @@ -117,6 +131,11 @@ function replaceMiddleCharacters(inputString) { | ... | @@ -117,6 +131,11 @@ function replaceMiddleCharacters(inputString) { |
| 117 | return inputString.substring(0, start) + replacement + inputString.substring(end); | 131 | return inputString.substring(0, start) + replacement + inputString.substring(end); |
| 118 | } | 132 | } |
| 119 | 133 | ||
| 134 | +/** | ||
| 135 | + * @description 格式化证件号展示(脱敏) | ||
| 136 | + * @param {string} id 原始证件号 | ||
| 137 | + * @returns {string} 脱敏后的证件号 | ||
| 138 | + */ | ||
| 120 | const formatId = (id) => replaceMiddleCharacters(id); | 139 | const formatId = (id) => replaceMiddleCharacters(id); |
| 121 | 140 | ||
| 122 | const userinfo = computed(() => { | 141 | const userinfo = computed(() => { |
| ... | @@ -151,6 +170,11 @@ const STATUS_CODE = { | ... | @@ -151,6 +170,11 @@ const STATUS_CODE = { |
| 151 | USED: '7', | 170 | USED: '7', |
| 152 | }; | 171 | }; |
| 153 | 172 | ||
| 173 | +/** | ||
| 174 | + * @description 刷新当前选中二维码状态 | ||
| 175 | + * - 仅在当前选中用户存在时请求 | ||
| 176 | + * @returns {Promise<void>} 无返回值 | ||
| 177 | + */ | ||
| 154 | const refreshBtn = async () => { | 178 | const refreshBtn = async () => { |
| 155 | if (!userList.value[select_index.value]) return; | 179 | if (!userList.value[select_index.value]) return; |
| 156 | const { code, data } = await qrcodeStatusAPI({ qr_code: userList.value[select_index.value].qr_code }); | 180 | const { code, data } = await qrcodeStatusAPI({ qr_code: userList.value[select_index.value].qr_code }); |
| ... | @@ -159,10 +183,21 @@ const refreshBtn = async () => { | ... | @@ -159,10 +183,21 @@ const refreshBtn = async () => { |
| 159 | } | 183 | } |
| 160 | } | 184 | } |
| 161 | 185 | ||
| 186 | +/** | ||
| 187 | + * @description 选择指定参观者的二维码 | ||
| 188 | + * @param {number} index 下标 | ||
| 189 | + * @returns {void} 无返回值 | ||
| 190 | + */ | ||
| 162 | const selectUser = (index) => { | 191 | const selectUser = (index) => { |
| 163 | select_index.value = index; | 192 | select_index.value = index; |
| 164 | } | 193 | } |
| 165 | 194 | ||
| 195 | +/** | ||
| 196 | + * @description 按 pay_id 分组标记(用于展示分隔线) | ||
| 197 | + * - 首个 pay_id 出现处标记 sort=1,其余为 sort=0 | ||
| 198 | + * @param {Array<Object>} data 二维码列表 | ||
| 199 | + * @returns {Array<Object>} 处理后的列表 | ||
| 200 | + */ | ||
| 166 | const formatGroup = (data) => { | 201 | const formatGroup = (data) => { |
| 167 | let lastPayId = null; | 202 | let lastPayId = null; |
| 168 | for (let i = 0; i < data.length; i++) { | 203 | for (let i = 0; i < data.length; i++) { |
| ... | @@ -176,6 +211,12 @@ const formatGroup = (data) => { | ... | @@ -176,6 +211,12 @@ const formatGroup = (data) => { |
| 176 | return data; | 211 | return data; |
| 177 | } | 212 | } |
| 178 | 213 | ||
| 214 | +/** | ||
| 215 | + * @description 初始化二维码列表 | ||
| 216 | + * - 不传 type:拉取“我的当日二维码列表” | ||
| 217 | + * - 传入 type + payId:按订单查询二维码人员列表 | ||
| 218 | + * @returns {Promise<void>} 无返回值 | ||
| 219 | + */ | ||
| 179 | const init = async () => { | 220 | const init = async () => { |
| 180 | if (!props.type) { | 221 | if (!props.type) { |
| 181 | try { | 222 | try { |
| ... | @@ -228,6 +269,11 @@ onMounted(() => { | ... | @@ -228,6 +269,11 @@ onMounted(() => { |
| 228 | init(); | 269 | init(); |
| 229 | }); | 270 | }); |
| 230 | 271 | ||
| 272 | +/** | ||
| 273 | + * @description 轮询刷新二维码状态 | ||
| 274 | + * - 仅在“待使用”状态下轮询,避免无意义请求 | ||
| 275 | + * @returns {Promise<void>} 无返回值 | ||
| 276 | + */ | ||
| 231 | const poll = async () => { | 277 | const poll = async () => { |
| 232 | if (userList.value.length && useStatus.value === STATUS_CODE.SUCCESS) { | 278 | if (userList.value.length && useStatus.value === STATUS_CODE.SUCCESS) { |
| 233 | if (userList.value[select_index.value]) { | 279 | if (userList.value[select_index.value]) { |
| ... | @@ -239,12 +285,17 @@ const poll = async () => { | ... | @@ -239,12 +285,17 @@ const poll = async () => { |
| 239 | } | 285 | } |
| 240 | }; | 286 | }; |
| 241 | 287 | ||
| 242 | -const intervalId = setInterval(poll, 3000); // 3秒轮询一次,避免过于频繁 | 288 | +// 3 秒轮询一次,避免过于频繁 |
| 289 | +const intervalId = setInterval(poll, 3000); | ||
| 243 | 290 | ||
| 244 | onUnmounted(() => { | 291 | onUnmounted(() => { |
| 245 | clearInterval(intervalId); | 292 | clearInterval(intervalId); |
| 246 | }); | 293 | }); |
| 247 | 294 | ||
| 295 | +/** | ||
| 296 | + * @description 跳转预约记录列表页 | ||
| 297 | + * @returns {void} 无返回值 | ||
| 298 | + */ | ||
| 248 | const toRecord = () => { | 299 | const toRecord = () => { |
| 249 | go('/bookingList'); | 300 | go('/bookingList'); |
| 250 | } | 301 | } | ... | ... |
| ... | @@ -58,6 +58,11 @@ const reserve_info = computed(() => props.data); | ... | @@ -58,6 +58,11 @@ const reserve_info = computed(() => props.data); |
| 58 | 58 | ||
| 59 | const is_offline = computed(() => props.is_offline); | 59 | const is_offline = computed(() => props.is_offline); |
| 60 | 60 | ||
| 61 | +/** | ||
| 62 | + * @description 预约码状态枚举(与后端约定) | ||
| 63 | + * - 1=待支付;2=支付中;3=预约成功;5/7=已取消;9=已使用;11=退款中 | ||
| 64 | + * @readonly | ||
| 65 | + */ | ||
| 61 | const CodeStatus = { | 66 | const CodeStatus = { |
| 62 | APPLY: '1', | 67 | APPLY: '1', |
| 63 | PAYING: '2', | 68 | PAYING: '2', |
| ... | @@ -69,7 +74,7 @@ const CodeStatus = { | ... | @@ -69,7 +74,7 @@ const CodeStatus = { |
| 69 | } | 74 | } |
| 70 | 75 | ||
| 71 | /** | 76 | /** |
| 72 | - * 是否支付待处理状态 | 77 | + * @description 是否支付待处理状态 |
| 73 | */ | 78 | */ |
| 74 | 79 | ||
| 75 | const is_pay_pending = computed(() => { | 80 | const is_pay_pending = computed(() => { |
| ... | @@ -79,6 +84,11 @@ const is_pay_pending = computed(() => { | ... | @@ -79,6 +84,11 @@ const is_pay_pending = computed(() => { |
| 79 | 84 | ||
| 80 | const countdown_seconds = ref(0) | 85 | const countdown_seconds = ref(0) |
| 81 | 86 | ||
| 87 | +/** | ||
| 88 | + * @description 将数字格式化为两位字符串(不足补 0) | ||
| 89 | + * @param {number|string} n 数字 | ||
| 90 | + * @returns {string} 两位字符串 | ||
| 91 | + */ | ||
| 82 | const format_two_digits = (n) => { | 92 | const format_two_digits = (n) => { |
| 83 | const num = Number(n) || 0 | 93 | const num = Number(n) || 0 |
| 84 | return num < 10 ? `0${num}` : String(num) | 94 | return num < 10 ? `0${num}` : String(num) |
| ... | @@ -91,6 +101,11 @@ const countdown_text = computed(() => { | ... | @@ -91,6 +101,11 @@ const countdown_text = computed(() => { |
| 91 | return `${format_two_digits(minutes)}:${format_two_digits(remain)}` | 101 | return `${format_two_digits(minutes)}:${format_two_digits(remain)}` |
| 92 | }) | 102 | }) |
| 93 | 103 | ||
| 104 | +/** | ||
| 105 | + * @description 解析 created_time 为毫秒时间戳(兼容 iOS Date 解析) | ||
| 106 | + * @param {string} created_time 创建时间字符串 | ||
| 107 | + * @returns {number} 毫秒时间戳;解析失败返回 0 | ||
| 108 | + */ | ||
| 94 | const parse_created_time_ms = (created_time) => { | 109 | const parse_created_time_ms = (created_time) => { |
| 95 | const raw = String(created_time || '') | 110 | const raw = String(created_time || '') |
| 96 | if (!raw) return 0 | 111 | if (!raw) return 0 |
| ... | @@ -102,7 +117,8 @@ const parse_created_time_ms = (created_time) => { | ... | @@ -102,7 +117,8 @@ const parse_created_time_ms = (created_time) => { |
| 102 | 117 | ||
| 103 | let countdown_timer = null | 118 | let countdown_timer = null |
| 104 | /** | 119 | /** |
| 105 | - * 停止倒计时 | 120 | + * @description 停止倒计时(清理定时器) |
| 121 | + * @returns {void} 无返回值 | ||
| 106 | */ | 122 | */ |
| 107 | 123 | ||
| 108 | const stop_countdown = () => { | 124 | const stop_countdown = () => { |
| ... | @@ -112,6 +128,10 @@ const stop_countdown = () => { | ... | @@ -112,6 +128,10 @@ const stop_countdown = () => { |
| 112 | } | 128 | } |
| 113 | } | 129 | } |
| 114 | 130 | ||
| 131 | +/** | ||
| 132 | + * @description 更新倒计时剩余秒数(默认 10 分钟支付窗口) | ||
| 133 | + * @returns {void} 无返回值 | ||
| 134 | + */ | ||
| 115 | const update_countdown = () => { | 135 | const update_countdown = () => { |
| 116 | const start_ms = parse_created_time_ms(reserve_info.value?.created_time) | 136 | const start_ms = parse_created_time_ms(reserve_info.value?.created_time) |
| 117 | if (!start_ms) { | 137 | if (!start_ms) { |
| ... | @@ -130,6 +150,10 @@ const update_countdown = () => { | ... | @@ -130,6 +150,10 @@ const update_countdown = () => { |
| 130 | } | 150 | } |
| 131 | } | 151 | } |
| 132 | 152 | ||
| 153 | +/** | ||
| 154 | + * @description 启动倒计时(每秒更新) | ||
| 155 | + * @returns {void} 无返回值 | ||
| 156 | + */ | ||
| 133 | const start_countdown = () => { | 157 | const start_countdown = () => { |
| 134 | stop_countdown() | 158 | stop_countdown() |
| 135 | update_countdown() | 159 | update_countdown() |
| ... | @@ -138,6 +162,11 @@ const start_countdown = () => { | ... | @@ -138,6 +162,11 @@ const start_countdown = () => { |
| 138 | } | 162 | } |
| 139 | 163 | ||
| 140 | let is_showing_pay_modal = false | 164 | let is_showing_pay_modal = false |
| 165 | +/** | ||
| 166 | + * @description 支付失败提示弹窗(防并发) | ||
| 167 | + * @param {string} content 弹窗内容 | ||
| 168 | + * @returns {Promise<boolean>} true=继续支付,false=取消 | ||
| 169 | + */ | ||
| 141 | const show_pay_modal = async (content) => { | 170 | const show_pay_modal = async (content) => { |
| 142 | if (is_showing_pay_modal) return false | 171 | if (is_showing_pay_modal) return false |
| 143 | is_showing_pay_modal = true | 172 | is_showing_pay_modal = true |
| ... | @@ -156,7 +185,8 @@ const show_pay_modal = async (content) => { | ... | @@ -156,7 +185,8 @@ const show_pay_modal = async (content) => { |
| 156 | } | 185 | } |
| 157 | 186 | ||
| 158 | /** | 187 | /** |
| 159 | - * 重新支付 | 188 | + * @description 重新支付(循环支付直到成功或用户取消) |
| 189 | + * @returns {Promise<void>} 无返回值 | ||
| 160 | */ | 190 | */ |
| 161 | 191 | ||
| 162 | const onRepay = async () => { | 192 | const onRepay = async () => { |
| ... | @@ -178,6 +208,11 @@ const onRepay = async () => { | ... | @@ -178,6 +208,11 @@ const onRepay = async () => { |
| 178 | } | 208 | } |
| 179 | } | 209 | } |
| 180 | 210 | ||
| 211 | +/** | ||
| 212 | + * @description 订单状态展示映射 | ||
| 213 | + * @param {string} status 订单状态码 | ||
| 214 | + * @returns {{key:string,value:string}} 展示状态(key 用于 class) | ||
| 215 | + */ | ||
| 181 | const formatStatus = (status) => { | 216 | const formatStatus = (status) => { |
| 182 | switch (status) { | 217 | switch (status) { |
| 183 | case CodeStatus.APPLY: | 218 | case CodeStatus.APPLY: |
| ... | @@ -224,6 +259,12 @@ const status_info = computed(() => { | ... | @@ -224,6 +259,12 @@ const status_info = computed(() => { |
| 224 | return formatStatus(reserve_info.value?.status) || { key: '', value: '' } | 259 | return formatStatus(reserve_info.value?.status) || { key: '', value: '' } |
| 225 | }) | 260 | }) |
| 226 | 261 | ||
| 262 | +/** | ||
| 263 | + * @description 跳转预约详情 | ||
| 264 | + * - 只有成功/已使用/已取消才允许进入详情 | ||
| 265 | + * @param {Object} item 预约记录 | ||
| 266 | + * @returns {void} 无返回值 | ||
| 267 | + */ | ||
| 227 | const goToDetail = (item) => { | 268 | const goToDetail = (item) => { |
| 228 | // 只有成功、已使用、已取消(退款成功)才跳转详情 | 269 | // 只有成功、已使用、已取消(退款成功)才跳转详情 |
| 229 | if (item.status === CodeStatus.SUCCESS || item.status === CodeStatus.USED || item.status === CodeStatus.CANCEL) { | 270 | if (item.status === CodeStatus.SUCCESS || item.status === CodeStatus.USED || item.status === CodeStatus.CANCEL) { |
| ... | @@ -232,7 +273,9 @@ const goToDetail = (item) => { | ... | @@ -232,7 +273,9 @@ const goToDetail = (item) => { |
| 232 | } | 273 | } |
| 233 | 274 | ||
| 234 | /** | 275 | /** |
| 235 | - * 监听支付待处理状态变化 | 276 | + * @description 监听支付待处理状态变化 |
| 277 | + * - 进入待支付:启动倒计时 | ||
| 278 | + * - 退出待支付:清空倒计时 | ||
| 236 | */ | 279 | */ |
| 237 | 280 | ||
| 238 | watch(is_pay_pending, (val) => { | 281 | watch(is_pay_pending, (val) => { | ... | ... |
| ... | @@ -16,6 +16,12 @@ export const OFFLINE_BOOKING_CACHE_KEY = 'OFFLINE_BOOKING_DATA' | ... | @@ -16,6 +16,12 @@ export const OFFLINE_BOOKING_CACHE_KEY = 'OFFLINE_BOOKING_DATA' |
| 16 | 16 | ||
| 17 | let refresh_promise = null | 17 | let refresh_promise = null |
| 18 | 18 | ||
| 19 | +/** | ||
| 20 | + * @description 兼容不同后端结构:从预约记录中提取可用数据载荷 | ||
| 21 | + * - 部分接口会把字段塞到 bill.list 对象里,这里做一次展开合并 | ||
| 22 | + * @param {Object} bill 原始预约记录 | ||
| 23 | + * @returns {Object} 扁平化后的预约记录对象 | ||
| 24 | + */ | ||
| 19 | const extract_bill_payload = (bill) => { | 25 | const extract_bill_payload = (bill) => { |
| 20 | if (!bill) return {} | 26 | if (!bill) return {} |
| 21 | 27 | ||
| ... | @@ -29,6 +35,13 @@ const extract_bill_payload = (bill) => { | ... | @@ -29,6 +35,13 @@ const extract_bill_payload = (bill) => { |
| 29 | return data | 35 | return data |
| 30 | } | 36 | } |
| 31 | 37 | ||
| 38 | +/** | ||
| 39 | + * @description 从预约记录中提取人员列表 | ||
| 40 | + * - 兼容不同字段名(person_list/bill_person_list/persons/qrcode_list/qr_list/detail_list) | ||
| 41 | + * - 保证返回数组类型 | ||
| 42 | + * @param {Object} bill 预约记录 | ||
| 43 | + * @returns {Array} 人员列表 | ||
| 44 | + */ | ||
| 32 | const extract_person_list = (bill) => { | 45 | const extract_person_list = (bill) => { |
| 33 | if (!bill) return [] | 46 | if (!bill) return [] |
| 34 | 47 | ||
| ... | @@ -52,8 +65,8 @@ const extract_person_list = (bill) => { | ... | @@ -52,8 +65,8 @@ const extract_person_list = (bill) => { |
| 52 | } | 65 | } |
| 53 | 66 | ||
| 54 | /** | 67 | /** |
| 55 | - * 格式化预约记录项 | 68 | + * @description 格式化预约记录项(统一字段与展示用时间) |
| 56 | - * @param {Object} item - 原始预约记录项 | 69 | + * @param {Object} item 原始预约记录项 |
| 57 | * @returns {Object} 格式化后的预约记录项 | 70 | * @returns {Object} 格式化后的预约记录项 |
| 58 | */ | 71 | */ |
| 59 | const normalize_bill_item = (item) => { | 72 | const normalize_bill_item = (item) => { | ... | ... |
| ... | @@ -8,14 +8,17 @@ | ... | @@ -8,14 +8,17 @@ |
| 8 | import Taro from '@tarojs/taro'; | 8 | import Taro from '@tarojs/taro'; |
| 9 | 9 | ||
| 10 | /** | 10 | /** |
| 11 | - * 封装路由跳转方便行内调用 | 11 | + * @description 获取页面跳转方法(navigateTo) |
| 12 | - * @returns | 12 | + * - 支持短路径:notice / /notice |
| 13 | + * - 自动补全:pages/notice/index | ||
| 14 | + * @returns {(path:string, query?:Object)=>void} go 跳转函数 | ||
| 13 | */ | 15 | */ |
| 14 | export function useGo () { | 16 | export function useGo () { |
| 15 | /** | 17 | /** |
| 16 | - * 路由跳转 | 18 | + * @description 路由跳转 |
| 17 | - * @param {string} path - 目标页面路径,支持 / 开头 | 19 | + * @param {string} path 目标页面路径,支持 / 开头与短路径 |
| 18 | - * @param {Object} query - 查询参数,键值对形式 | 20 | + * @param {Object} query 查询参数(键值对) |
| 21 | + * @returns {void} 无返回值 | ||
| 19 | */ | 22 | */ |
| 20 | function go (path, query = {}) { | 23 | function go (path, query = {}) { |
| 21 | // 补全路径,如果是 / 开头,去掉 / | 24 | // 补全路径,如果是 / 开头,去掉 / |
| ... | @@ -40,7 +43,7 @@ export function useGo () { | ... | @@ -40,7 +43,7 @@ export function useGo () { |
| 40 | if (err.errMsg && err.errMsg.indexOf('tabbar') !== -1) { | 43 | if (err.errMsg && err.errMsg.indexOf('tabbar') !== -1) { |
| 41 | Taro.switchTab({ url: '/' + url }); | 44 | Taro.switchTab({ url: '/' + url }); |
| 42 | } else { | 45 | } else { |
| 43 | - console.error('Navigation failed', err); | 46 | + console.error('页面跳转失败:', err); |
| 44 | } | 47 | } |
| 45 | } | 48 | } |
| 46 | }) | 49 | }) |
| ... | @@ -49,14 +52,17 @@ export function useGo () { | ... | @@ -49,14 +52,17 @@ export function useGo () { |
| 49 | } | 52 | } |
| 50 | 53 | ||
| 51 | /** | 54 | /** |
| 52 | - * 封装路由替换方便行内调用 | 55 | + * @description 获取页面替换方法(redirectTo) |
| 53 | - * @returns | 56 | + * - 支持短路径:notice / /notice |
| 57 | + * - 自动补全:pages/notice/index | ||
| 58 | + * @returns {(path:string, query?:Object)=>void} replace 替换函数 | ||
| 54 | */ | 59 | */ |
| 55 | export function useReplace () { | 60 | export function useReplace () { |
| 56 | /** | 61 | /** |
| 57 | - * 路由替换 | 62 | + * @description 路由替换 |
| 58 | - * @param {string} path - 目标页面路径,支持 / 开头 | 63 | + * @param {string} path 目标页面路径,支持 / 开头与短路径 |
| 59 | - * @param {Object} query - 查询参数,键值对形式 | 64 | + * @param {Object} query 查询参数(键值对) |
| 65 | + * @returns {void} 无返回值 | ||
| 60 | */ | 66 | */ |
| 61 | function replace (path, query = {}) { | 67 | function replace (path, query = {}) { |
| 62 | let url = path.startsWith('/') ? path.substring(1) : path; | 68 | let url = path.startsWith('/') ? path.substring(1) : path; | ... | ... |
| 1 | <!-- | 1 | <!-- |
| 2 | * @Date: 2024-01-16 13:19:23 | 2 | * @Date: 2024-01-16 13:19:23 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2026-01-13 18:36:58 | 4 | + * @LastEditTime: 2026-01-24 12:46:06 |
| 5 | * @FilePath: /xyxBooking-weapp/src/pages/bookingDetail/index.vue | 5 | * @FilePath: /xyxBooking-weapp/src/pages/bookingDetail/index.vue |
| 6 | * @Description: 预约记录详情 | 6 | * @Description: 预约记录详情 |
| 7 | --> | 7 | --> |
| ... | @@ -44,19 +44,21 @@ | ... | @@ -44,19 +44,21 @@ |
| 44 | <script setup> | 44 | <script setup> |
| 45 | import { ref, computed } from 'vue' | 45 | import { ref, computed } from 'vue' |
| 46 | import Taro, { useDidShow, useRouter as useTaroRouter } from '@tarojs/taro' | 46 | import Taro, { useDidShow, useRouter as useTaroRouter } from '@tarojs/taro' |
| 47 | -import { useGo } from '@/hooks/useGo' | ||
| 48 | import qrCode from '@/components/qrCode'; | 47 | import qrCode from '@/components/qrCode'; |
| 49 | import { billInfoAPI, icbcRefundAPI } from '@/api/index' | 48 | import { billInfoAPI, icbcRefundAPI } from '@/api/index' |
| 50 | import { formatDatetime } from '@/utils/tools'; | 49 | import { formatDatetime } from '@/utils/tools'; |
| 51 | import { refresh_offline_booking_cache } from '@/composables/useOfflineBookingCache' | 50 | import { refresh_offline_booking_cache } from '@/composables/useOfflineBookingCache' |
| 52 | 51 | ||
| 53 | const router = useTaroRouter(); | 52 | const router = useTaroRouter(); |
| 54 | -const go = useGo(); | ||
| 55 | 53 | ||
| 56 | const pay_id = ref(''); | 54 | const pay_id = ref(''); |
| 57 | const qrCodeStatus = ref(''); | 55 | const qrCodeStatus = ref(''); |
| 58 | const billInfo = ref({}); | 56 | const billInfo = ref({}); |
| 59 | 57 | ||
| 58 | +/** | ||
| 59 | + * @description 预约码状态枚举(与后端约定) | ||
| 60 | + * @readonly | ||
| 61 | + */ | ||
| 60 | const CodeStatus = { | 62 | const CodeStatus = { |
| 61 | APPLY: '1', | 63 | APPLY: '1', |
| 62 | PAYING: '2', | 64 | PAYING: '2', |
| ... | @@ -67,6 +69,10 @@ const CodeStatus = { | ... | @@ -67,6 +69,10 @@ const CodeStatus = { |
| 67 | REFUNDING: '11' | 69 | REFUNDING: '11' |
| 68 | } | 70 | } |
| 69 | 71 | ||
| 72 | +/** | ||
| 73 | + * @description 订单状态文案 | ||
| 74 | + * @returns {string} 状态文案 | ||
| 75 | + */ | ||
| 70 | const qrCodeStatusText = computed(() => { | 76 | const qrCodeStatusText = computed(() => { |
| 71 | const status = billInfo.value?.status; | 77 | const status = billInfo.value?.status; |
| 72 | switch (status) { | 78 | switch (status) { |
| ... | @@ -78,6 +84,11 @@ const qrCodeStatusText = computed(() => { | ... | @@ -78,6 +84,11 @@ const qrCodeStatusText = computed(() => { |
| 78 | } | 84 | } |
| 79 | }) | 85 | }) |
| 80 | 86 | ||
| 87 | +/** | ||
| 88 | + * @description 取消预约 | ||
| 89 | + * - 成功后刷新离线缓存(保证弱网/离线模式数据一致) | ||
| 90 | + * @returns {Promise<void>} 无返回值 | ||
| 91 | + */ | ||
| 81 | const cancelBooking = async () => { | 92 | const cancelBooking = async () => { |
| 82 | const { confirm } = await Taro.showModal({ | 93 | const { confirm } = await Taro.showModal({ |
| 83 | title: '温馨提示', | 94 | title: '温馨提示', | ... | ... |
| 1 | <!-- | 1 | <!-- |
| 2 | * @Date: 2024-01-16 11:37:10 | 2 | * @Date: 2024-01-16 11:37:10 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2026-01-16 19:41:35 | 4 | + * @LastEditTime: 2026-01-24 12:45:49 |
| 5 | * @FilePath: /xyxBooking-weapp/src/pages/bookingList/index.vue | 5 | * @FilePath: /xyxBooking-weapp/src/pages/bookingList/index.vue |
| 6 | * @Description: 预约记录列表页 | 6 | * @Description: 预约记录列表页 |
| 7 | --> | 7 | --> |
| ... | @@ -23,7 +23,7 @@ | ... | @@ -23,7 +23,7 @@ |
| 23 | 23 | ||
| 24 | <script setup> | 24 | <script setup> |
| 25 | import { ref } from 'vue' | 25 | import { ref } from 'vue' |
| 26 | -import Taro, { useDidShow, useReachBottom } from '@tarojs/taro' | 26 | +import { useDidShow, useReachBottom } from '@tarojs/taro' |
| 27 | import { billListAPI } from '@/api/index' | 27 | import { billListAPI } from '@/api/index' |
| 28 | import { formatDatetime } from '@/utils/tools'; | 28 | import { formatDatetime } from '@/utils/tools'; |
| 29 | import reserveCard from '@/components/reserveCard.vue' | 29 | import reserveCard from '@/components/reserveCard.vue' |
| ... | @@ -35,8 +35,9 @@ const loading = ref(false); | ... | @@ -35,8 +35,9 @@ const loading = ref(false); |
| 35 | const finished = ref(false); | 35 | const finished = ref(false); |
| 36 | 36 | ||
| 37 | /** | 37 | /** |
| 38 | - * 加载预约记录列表 | 38 | + * @description 加载预约记录列表(分页) |
| 39 | - * @param isRefresh 是否刷新,默认 false | 39 | + * @param {boolean} isRefresh 是否刷新(刷新会重置 page 并覆盖列表) |
| 40 | + * @returns {Promise<void>} 无返回值 | ||
| 40 | */ | 41 | */ |
| 41 | const loadData = async (isRefresh = false) => { | 42 | const loadData = async (isRefresh = false) => { |
| 42 | if (loading.value || (finished.value && !isRefresh)) return; | 43 | if (loading.value || (finished.value && !isRefresh)) return; | ... | ... |
| ... | @@ -3,7 +3,7 @@ | ... | @@ -3,7 +3,7 @@ |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | * @LastEditTime: 2025-07-01 10:56:38 | 4 | * @LastEditTime: 2025-07-01 10:56:38 |
| 5 | * @FilePath: /myApp/src/pages/demo/index.vue | 5 | * @FilePath: /myApp/src/pages/demo/index.vue |
| 6 | - * @Description: 文件描述 | 6 | + * @Description: demo 页面(开发调试用) |
| 7 | --> | 7 | --> |
| 8 | <template> | 8 | <template> |
| 9 | <div class="red">{{ str }}</div> | 9 | <div class="red">{{ str }}</div> | ... | ... |
| ... | @@ -3,7 +3,7 @@ | ... | @@ -3,7 +3,7 @@ |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | * @LastEditTime: 2026-01-08 20:16:02 | 4 | * @LastEditTime: 2026-01-08 20:16:02 |
| 5 | * @FilePath: /xyxBooking-weapp/src/pages/notice/index.vue | 5 | * @FilePath: /xyxBooking-weapp/src/pages/notice/index.vue |
| 6 | - * @Description: 文件描述 | 6 | + * @Description: 参访须知确认页 |
| 7 | --> | 7 | --> |
| 8 | <template> | 8 | <template> |
| 9 | <view class="notice-page"> | 9 | <view class="notice-page"> |
| ... | @@ -46,6 +46,11 @@ const note_text = [ | ... | @@ -46,6 +46,11 @@ const note_text = [ |
| 46 | ]; | 46 | ]; |
| 47 | const checked = ref([]); | 47 | const checked = ref([]); |
| 48 | 48 | ||
| 49 | +/** | ||
| 50 | + * @description 点击确认进入下一步 | ||
| 51 | + * - 必须勾选“我已阅读并同意” | ||
| 52 | + * @returns {void} 无返回值 | ||
| 53 | + */ | ||
| 49 | const confirmBtn = () => { | 54 | const confirmBtn = () => { |
| 50 | if (checked.value.includes("1")) { | 55 | if (checked.value.includes("1")) { |
| 51 | go("/booking"); | 56 | go("/booking"); | ... | ... |
| 1 | <!-- | 1 | <!-- |
| 2 | * @Date: 2024-01-15 16:25:51 | 2 | * @Date: 2024-01-15 16:25:51 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2026-01-16 20:38:48 | 4 | + * @LastEditTime: 2026-01-24 12:44:25 |
| 5 | * @FilePath: /xyxBooking-weapp/src/pages/submit/index.vue | 5 | * @FilePath: /xyxBooking-weapp/src/pages/submit/index.vue |
| 6 | * @Description: 预约人员信息 | 6 | * @Description: 预约人员信息 |
| 7 | --> | 7 | --> |
| ... | @@ -76,8 +76,9 @@ const price = ref(0); | ... | @@ -76,8 +76,9 @@ const price = ref(0); |
| 76 | const period_type = ref(''); | 76 | const period_type = ref(''); |
| 77 | 77 | ||
| 78 | /** | 78 | /** |
| 79 | - * 生成15位身份证号中间8位替换为*号 | 79 | + * @description 身份证号脱敏:中间 8 位替换为 * |
| 80 | - * @param {*} inputString | 80 | + * @param {string} inputString 身份证号 |
| 81 | + * @returns {string} 脱敏后的身份证号 | ||
| 81 | */ | 82 | */ |
| 82 | function replaceMiddleCharacters(inputString) { | 83 | function replaceMiddleCharacters(inputString) { |
| 83 | if (!inputString || inputString.length < 15) { | 84 | if (!inputString || inputString.length < 15) { |
| ... | @@ -93,16 +94,33 @@ function replaceMiddleCharacters(inputString) { | ... | @@ -93,16 +94,33 @@ function replaceMiddleCharacters(inputString) { |
| 93 | return replacedString; | 94 | return replacedString; |
| 94 | } | 95 | } |
| 95 | 96 | ||
| 97 | +/** | ||
| 98 | + * @description 格式化身份证号展示 | ||
| 99 | + * @param {string} id 身份证号 | ||
| 100 | + * @returns {string} 脱敏后的身份证号 | ||
| 101 | + */ | ||
| 96 | const formatId = (id) => { | 102 | const formatId = (id) => { |
| 97 | return replaceMiddleCharacters(id); | 103 | return replaceMiddleCharacters(id); |
| 98 | }; | 104 | }; |
| 99 | 105 | ||
| 106 | +/** | ||
| 107 | + * @description 当天预约标记 | ||
| 108 | + * - ENABLE 表示该参观者今天已预约,不允许重复预约 | ||
| 109 | + * @readonly | ||
| 110 | + */ | ||
| 100 | const RESERVE_STATUS = { | 111 | const RESERVE_STATUS = { |
| 101 | ENABLE: '1' | 112 | ENABLE: '1' |
| 102 | } | 113 | } |
| 103 | 114 | ||
| 104 | const checked_visitors = ref([]); | 115 | const checked_visitors = ref([]); |
| 105 | const is_submitting = ref(false); // 是否正在提交订单 | 116 | const is_submitting = ref(false); // 是否正在提交订单 |
| 117 | + | ||
| 118 | +/** | ||
| 119 | + * @description 选择/取消选择参观者 | ||
| 120 | + * - 已预约过当天参观的参观者不允许选择 | ||
| 121 | + * @param {Object} item 参观者数据 | ||
| 122 | + * @returns {void} 无返回值 | ||
| 123 | + */ | ||
| 106 | const addVisitor = (item) => { | 124 | const addVisitor = (item) => { |
| 107 | if (item.is_reserve === RESERVE_STATUS.ENABLE) { // 今天已经预约 | 125 | if (item.is_reserve === RESERVE_STATUS.ENABLE) { // 今天已经预约 |
| 108 | Taro.showToast({ title: '已预约过参观,请不要重复预约', icon: 'none' }) | 126 | Taro.showToast({ title: '已预约过参观,请不要重复预约', icon: 'none' }) |
| ... | @@ -119,9 +137,18 @@ const total = computed(() => { | ... | @@ -119,9 +137,18 @@ const total = computed(() => { |
| 119 | return price.value * checked_visitors.value.length; | 137 | return price.value * checked_visitors.value.length; |
| 120 | }) | 138 | }) |
| 121 | 139 | ||
| 140 | +/** | ||
| 141 | + * @description 返回重新选择参访时间页 | ||
| 142 | + * @returns {void} 无返回值 | ||
| 143 | + */ | ||
| 122 | const goToBooking = () => { | 144 | const goToBooking = () => { |
| 123 | go('/booking'); | 145 | go('/booking'); |
| 124 | } | 146 | } |
| 147 | + | ||
| 148 | +/** | ||
| 149 | + * @description 跳转新增参观者页 | ||
| 150 | + * @returns {void} 无返回值 | ||
| 151 | + */ | ||
| 125 | const goToVisitor = () => { | 152 | const goToVisitor = () => { |
| 126 | go('/addVisitor'); | 153 | go('/addVisitor'); |
| 127 | } | 154 | } |
| ... | @@ -132,7 +159,10 @@ const pending_pay_id = ref(null); | ... | @@ -132,7 +159,10 @@ const pending_pay_id = ref(null); |
| 132 | const pending_need_pay = ref(null); | 159 | const pending_need_pay = ref(null); |
| 133 | 160 | ||
| 134 | /** | 161 | /** |
| 135 | - * 刷新参观者列表 | 162 | + * @description 刷新参观者列表(并同步“当天已预约”标记) |
| 163 | + * @param {Object} options 选项 | ||
| 164 | + * @param {boolean} options.reset_checked 是否清空已勾选参观者 | ||
| 165 | + * @returns {Promise<void>} 无返回值 | ||
| 136 | */ | 166 | */ |
| 137 | 167 | ||
| 138 | const refreshVisitorList = async (options) => { | 168 | const refreshVisitorList = async (options) => { |
| ... | @@ -152,6 +182,12 @@ const refreshVisitorList = async (options) => { | ... | @@ -152,6 +182,12 @@ const refreshVisitorList = async (options) => { |
| 152 | } | 182 | } |
| 153 | 183 | ||
| 154 | let is_showing_pay_modal = false; | 184 | let is_showing_pay_modal = false; |
| 185 | + | ||
| 186 | +/** | ||
| 187 | + * @description 支付未完成弹窗(防并发) | ||
| 188 | + * @param {string} content 弹窗内容 | ||
| 189 | + * @returns {Promise<boolean>} true=继续支付,false=离开 | ||
| 190 | + */ | ||
| 155 | const showPayErrorModal = async (content) => { | 191 | const showPayErrorModal = async (content) => { |
| 156 | if (is_showing_pay_modal) return; | 192 | if (is_showing_pay_modal) return; |
| 157 | is_showing_pay_modal = true; | 193 | is_showing_pay_modal = true; |
| ... | @@ -169,6 +205,12 @@ const showPayErrorModal = async (content) => { | ... | @@ -169,6 +205,12 @@ const showPayErrorModal = async (content) => { |
| 169 | } | 205 | } |
| 170 | } | 206 | } |
| 171 | 207 | ||
| 208 | +/** | ||
| 209 | + * @description 提交订单 | ||
| 210 | + * - 先创建预约单拿 pay_id(支持“待支付订单”复用) | ||
| 211 | + * - need_pay=1 时循环拉起微信支付,直到成功或用户取消 | ||
| 212 | + * @returns {Promise<void>} 无返回值 | ||
| 213 | + */ | ||
| 172 | const submitBtn = async () => { | 214 | const submitBtn = async () => { |
| 173 | if (is_submitting.value) return; | 215 | if (is_submitting.value) return; |
| 174 | if (!checked_visitors.value.length) { | 216 | if (!checked_visitors.value.length) { | ... | ... |
| ... | @@ -3,7 +3,7 @@ | ... | @@ -3,7 +3,7 @@ |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | * @LastEditTime: 2026-01-12 22:53:55 | 4 | * @LastEditTime: 2026-01-12 22:53:55 |
| 5 | * @FilePath: /xyxBooking-weapp/src/pages/tailwindTest/index.config.js | 5 | * @FilePath: /xyxBooking-weapp/src/pages/tailwindTest/index.config.js |
| 6 | - * @Description: 文件描述 | 6 | + * @Description: Tailwind 测试页配置(仅开发环境) |
| 7 | */ | 7 | */ |
| 8 | export default { | 8 | export default { |
| 9 | navigationBarTitleText: 'Tailwind 测试' | 9 | navigationBarTitleText: 'Tailwind 测试' | ... | ... |
| ... | @@ -70,7 +70,12 @@ const on_nav_select = (key) => { | ... | @@ -70,7 +70,12 @@ const on_nav_select = (key) => { |
| 70 | 70 | ||
| 71 | const visitorList = ref([]); | 71 | const visitorList = ref([]); |
| 72 | 72 | ||
| 73 | -function replaceMiddleCharacters(inputString) { | 73 | +/** |
| 74 | + * @description 身份证号脱敏:中间 8 位替换为 * 号 | ||
| 75 | + * @param {string} inputString 原始身份证号 | ||
| 76 | + * @returns {string} 脱敏后的身份证号 | ||
| 77 | + */ | ||
| 78 | +function replaceMiddleCharacters (inputString) { | ||
| 74 | if (!inputString || inputString.length < 15) { | 79 | if (!inputString || inputString.length < 15) { |
| 75 | return inputString; | 80 | return inputString; |
| 76 | } | 81 | } |
| ... | @@ -80,8 +85,17 @@ function replaceMiddleCharacters(inputString) { | ... | @@ -80,8 +85,17 @@ function replaceMiddleCharacters(inputString) { |
| 80 | return inputString.substring(0, start) + replacement + inputString.substring(end); | 85 | return inputString.substring(0, start) + replacement + inputString.substring(end); |
| 81 | } | 86 | } |
| 82 | 87 | ||
| 88 | +/** | ||
| 89 | + * @description 格式化证件号展示(脱敏) | ||
| 90 | + * @param {string} id 原始证件号 | ||
| 91 | + * @returns {string} 脱敏后的证件号 | ||
| 92 | + */ | ||
| 83 | const formatId = (id) => replaceMiddleCharacters(id); | 93 | const formatId = (id) => replaceMiddleCharacters(id); |
| 84 | 94 | ||
| 95 | +/** | ||
| 96 | + * @description 加载参观者列表 | ||
| 97 | + * @returns {Promise<void>} 无返回值 | ||
| 98 | + */ | ||
| 85 | const loadList = async () => { | 99 | const loadList = async () => { |
| 86 | try { | 100 | try { |
| 87 | const { code, data } = await personListAPI({}); | 101 | const { code, data } = await personListAPI({}); |
| ... | @@ -94,6 +108,11 @@ const loadList = async () => { | ... | @@ -94,6 +108,11 @@ const loadList = async () => { |
| 94 | } | 108 | } |
| 95 | } | 109 | } |
| 96 | 110 | ||
| 111 | +/** | ||
| 112 | + * @description 删除参观者 | ||
| 113 | + * @param {Object} item 参观者对象 | ||
| 114 | + * @returns {Promise<void>} 无返回值 | ||
| 115 | + */ | ||
| 97 | const removeItem = async (item) => { | 116 | const removeItem = async (item) => { |
| 98 | const { confirm } = await Taro.showModal({ title: '提示', content: '确定删除该参观者吗?' }); | 117 | const { confirm } = await Taro.showModal({ title: '提示', content: '确定删除该参观者吗?' }); |
| 99 | if (confirm) { | 118 | if (confirm) { | ... | ... |
| 1 | -// https://pinia.esm.dev/introduction.html | 1 | +// Pinia 入门文档:https://pinia.vuejs.org/introduction.html |
| 2 | import { defineStore } from 'pinia' | 2 | import { defineStore } from 'pinia' |
| 3 | 3 | ||
| 4 | +/** | ||
| 5 | + * @description 计数器示例 Store(模板保留) | ||
| 6 | + */ | ||
| 4 | export const useCounterStore = defineStore('counter', { | 7 | export const useCounterStore = defineStore('counter', { |
| 5 | state: () => { | 8 | state: () => { |
| 6 | return { count: 0 } | 9 | return { count: 0 } |
| 7 | }, | 10 | }, |
| 8 | - // could also be defined as | 11 | + // 也可以写成:state: () => ({ count: 0 }) |
| 9 | - // state: () => ({ count: 0 }) | ||
| 10 | actions: { | 12 | actions: { |
| 13 | + /** | ||
| 14 | + * @description 计数 +1 | ||
| 15 | + * @returns {void} 无返回值 | ||
| 16 | + */ | ||
| 11 | increment() { | 17 | increment() { |
| 12 | this.count++ | 18 | this.count++ |
| 13 | }, | 19 | }, |
| 14 | }, | 20 | }, |
| 15 | }) | 21 | }) |
| 16 | 22 | ||
| 17 | -// You can even use a function (similar to a component setup()) to define a Store for more advanced use cases: | 23 | +// 也可以用函数式(类似组件 setup)定义 Store,适合更复杂场景: |
| 18 | // export const useCounterStore = defineStore('counter', () => { | 24 | // export const useCounterStore = defineStore('counter', () => { |
| 19 | // const count = ref(0) | 25 | // const count = ref(0) |
| 20 | // | 26 | // |
| ... | @@ -23,4 +29,4 @@ export const useCounterStore = defineStore('counter', { | ... | @@ -23,4 +29,4 @@ export const useCounterStore = defineStore('counter', { |
| 23 | // } | 29 | // } |
| 24 | // | 30 | // |
| 25 | // return {count, increment} | 31 | // return {count, increment} |
| 26 | -// }) | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 32 | +// }) | ... | ... |
| ... | @@ -7,6 +7,10 @@ | ... | @@ -7,6 +7,10 @@ |
| 7 | */ | 7 | */ |
| 8 | import { defineStore } from 'pinia' | 8 | import { defineStore } from 'pinia' |
| 9 | 9 | ||
| 10 | +/** | ||
| 11 | + * @description 主办方相关缓存 | ||
| 12 | + * - 用于保存主办方 id / join_id 等页面间共享参数 | ||
| 13 | + */ | ||
| 10 | export const hostStore = defineStore('host', { | 14 | export const hostStore = defineStore('host', { |
| 11 | state: () => { | 15 | state: () => { |
| 12 | return { | 16 | return { |
| ... | @@ -15,9 +19,19 @@ export const hostStore = defineStore('host', { | ... | @@ -15,9 +19,19 @@ export const hostStore = defineStore('host', { |
| 15 | } | 19 | } |
| 16 | }, | 20 | }, |
| 17 | actions: { | 21 | actions: { |
| 22 | + /** | ||
| 23 | + * @description 设置主办方 id | ||
| 24 | + * @param {string} id 主办方 id | ||
| 25 | + * @returns {void} 无返回值 | ||
| 26 | + */ | ||
| 18 | add (id) { | 27 | add (id) { |
| 19 | this.id = id | 28 | this.id = id |
| 20 | }, | 29 | }, |
| 30 | + /** | ||
| 31 | + * @description 设置 join_id | ||
| 32 | + * @param {string} id join_id | ||
| 33 | + * @returns {void} 无返回值 | ||
| 34 | + */ | ||
| 21 | addJoin (id) { | 35 | addJoin (id) { |
| 22 | this.join_id = id | 36 | this.join_id = id |
| 23 | }, | 37 | }, | ... | ... |
| ... | @@ -7,6 +7,10 @@ | ... | @@ -7,6 +7,10 @@ |
| 7 | */ | 7 | */ |
| 8 | import { defineStore } from 'pinia'; | 8 | import { defineStore } from 'pinia'; |
| 9 | 9 | ||
| 10 | +/** | ||
| 11 | + * @description 全局主状态 | ||
| 12 | + * - 存储用户信息与授权态等全局数据 | ||
| 13 | + */ | ||
| 10 | export const mainStore = defineStore('main', { | 14 | export const mainStore = defineStore('main', { |
| 11 | state: () => { | 15 | state: () => { |
| 12 | return { | 16 | return { |
| ... | @@ -18,12 +22,20 @@ export const mainStore = defineStore('main', { | ... | @@ -18,12 +22,20 @@ export const mainStore = defineStore('main', { |
| 18 | }; | 22 | }; |
| 19 | }, | 23 | }, |
| 20 | getters: { | 24 | getters: { |
| 21 | - // 判断是否为义工 | 25 | + /** |
| 26 | + * @description 是否具备义工核销权限 | ||
| 27 | + * @returns {boolean} true=义工,false=非义工 | ||
| 28 | + */ | ||
| 22 | isVolunteer: (state) => { | 29 | isVolunteer: (state) => { |
| 23 | return !!(state.appUserInfo && (state.appUserInfo.can_redeem === true)); | 30 | return !!(state.appUserInfo && (state.appUserInfo.can_redeem === true)); |
| 24 | }, | 31 | }, |
| 25 | }, | 32 | }, |
| 26 | actions: { | 33 | actions: { |
| 34 | + /** | ||
| 35 | + * @description 更新授权状态 | ||
| 36 | + * @param {boolean} state 是否已授权 | ||
| 37 | + * @returns {void} 无返回值 | ||
| 38 | + */ | ||
| 27 | changeState (state) { | 39 | changeState (state) { |
| 28 | this.auth = state; | 40 | this.auth = state; |
| 29 | }, | 41 | }, |
| ... | @@ -38,6 +50,11 @@ export const mainStore = defineStore('main', { | ... | @@ -38,6 +50,11 @@ export const mainStore = defineStore('main', { |
| 38 | // }, | 50 | // }, |
| 39 | // removeThisPage () { | 51 | // removeThisPage () { |
| 40 | // }, | 52 | // }, |
| 53 | + /** | ||
| 54 | + * @description 更新全局用户信息 | ||
| 55 | + * @param {Object|null} info 用户信息对象 | ||
| 56 | + * @returns {void} 无返回值 | ||
| 57 | + */ | ||
| 41 | changeUserInfo (info) { | 58 | changeUserInfo (info) { |
| 42 | this.appUserInfo = info; | 59 | this.appUserInfo = info; |
| 43 | } | 60 | } | ... | ... |
| ... | @@ -7,6 +7,11 @@ | ... | @@ -7,6 +7,11 @@ |
| 7 | */ | 7 | */ |
| 8 | import { defineStore } from 'pinia' | 8 | import { defineStore } from 'pinia' |
| 9 | 9 | ||
| 10 | +/** | ||
| 11 | + * @description 路由回跳信息(用于授权完成后的返回) | ||
| 12 | + * - authRedirect.saveCurrentPagePath 会写入 url | ||
| 13 | + * - authRedirect.returnToOriginalPage 会消费并清空 url | ||
| 14 | + */ | ||
| 10 | export const routerStore = defineStore('router', { | 15 | export const routerStore = defineStore('router', { |
| 11 | state: () => { | 16 | state: () => { |
| 12 | return { | 17 | return { |
| ... | @@ -14,9 +19,18 @@ export const routerStore = defineStore('router', { | ... | @@ -14,9 +19,18 @@ export const routerStore = defineStore('router', { |
| 14 | } | 19 | } |
| 15 | }, | 20 | }, |
| 16 | actions: { | 21 | actions: { |
| 22 | + /** | ||
| 23 | + * @description 记录回跳路径 | ||
| 24 | + * @param {string} path 页面路径(可带 query) | ||
| 25 | + * @returns {void} 无返回值 | ||
| 26 | + */ | ||
| 17 | add (path) { | 27 | add (path) { |
| 18 | this.url = path | 28 | this.url = path |
| 19 | }, | 29 | }, |
| 30 | + /** | ||
| 31 | + * @description 清空回跳路径 | ||
| 32 | + * @returns {void} 无返回值 | ||
| 33 | + */ | ||
| 20 | remove () { | 34 | remove () { |
| 21 | this.url = '' | 35 | this.url = '' |
| 22 | }, | 36 | }, | ... | ... |
| 1 | /* | 1 | /* |
| 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: 2026-01-24 12:28:14 | 4 | + * @LastEditTime: 2026-01-24 12:40:28 |
| 5 | * @FilePath: /xyxBooking-weapp/src/utils/config.js | 5 | * @FilePath: /xyxBooking-weapp/src/utils/config.js |
| 6 | * @Description: 服务器环境配置 | 6 | * @Description: 服务器环境配置 |
| 7 | */ | 7 | */ |
| 8 | // TAG:服务器环境配置 | 8 | // TAG:服务器环境配置 |
| 9 | // const isH5Dev = process.env.TARO_ENV === 'h5' && process.env.NODE_ENV === 'development'; | 9 | // const isH5Dev = process.env.TARO_ENV === 'h5' && process.env.NODE_ENV === 'development'; |
| 10 | 10 | ||
| 11 | +/** | ||
| 12 | + * @description 接口基础域名 | ||
| 13 | + * - 小程序端由 taro 构建时注入 NODE_ENV | ||
| 14 | + * - 线上/测试环境按需切换 | ||
| 15 | + * @type {string} | ||
| 16 | + */ | ||
| 11 | const BASE_URL = process.env.NODE_ENV === 'production' | 17 | const BASE_URL = process.env.NODE_ENV === 'production' |
| 12 | // ? 'https://oa.onwall.cn' | 18 | // ? 'https://oa.onwall.cn' |
| 13 | ? 'https://oa-dev.onwall.cn' | 19 | ? 'https://oa-dev.onwall.cn' | ... | ... |
| 1 | /* | 1 | /* |
| 2 | * @Date: 2022-10-13 22:36:08 | 2 | * @Date: 2022-10-13 22:36:08 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2023-12-22 18:23:08 | 4 | + * @LastEditTime: 2026-01-24 12:53:30 |
| 5 | - * @FilePath: /meihuaApp/src/utils/mixin.js | 5 | + * @FilePath: /xyxBooking-weapp/src/utils/mixin.js |
| 6 | - * @Description: 文件描述 | 6 | + * @Description: 全局 mixin(兼容保留) |
| 7 | */ | 7 | */ |
| 8 | import { getSessionId, setSessionId, clearSessionId } from './request'; | 8 | import { getSessionId, setSessionId, clearSessionId } from './request'; |
| 9 | 9 | ||
| 10 | /** | 10 | /** |
| 11 | - * 全局mixin,提供sessionid管理功能 | 11 | + * @description 全局 mixin(兼容保留) |
| 12 | - * 注意:sessionid现在由request.js自动管理,无需手动设置 | 12 | + * - 早期版本用于手动管理 sessionid |
| 13 | + * - 当前项目 sessionid 由 request.js 拦截器自动注入 | ||
| 14 | + * - 该文件主要作为“旧代码兼容层”保留 | ||
| 13 | */ | 15 | */ |
| 14 | export default { | 16 | export default { |
| 15 | - // 初始化设置 | 17 | + // 初始化入口(如需全局混入逻辑可写在这里) |
| 16 | init: { | 18 | init: { |
| 17 | created () { | 19 | created () { |
| 18 | - // sessionid现在由request.js的拦截器自动管理 | 20 | + // 说明:sessionid 现在由 request.js 的拦截器自动管理 |
| 19 | - // 这里可以添加其他初始化逻辑 | 21 | + // 如需在组件创建时做通用初始化,可在此补充 |
| 20 | } | 22 | } |
| 21 | } | 23 | } |
| 22 | }; | 24 | }; |
| 23 | 25 | ||
| 24 | -// 导出sessionid管理工具函数,供其他组件使用 | 26 | +/** |
| 27 | + * @description 导出 sessionid 管理工具(供极端场景手动处理) | ||
| 28 | + * - 正常业务不建议直接调用 | ||
| 29 | + */ | ||
| 25 | export { getSessionId, setSessionId, clearSessionId } | 30 | export { getSessionId, setSessionId, clearSessionId } | ... | ... |
| 1 | +/* | ||
| 2 | + * @Date: 2026-01-07 21:19:46 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2026-01-24 12:44:01 | ||
| 5 | + * @FilePath: /xyxBooking-weapp/src/utils/polyfill.js | ||
| 6 | + * @Description: 文件描述 | ||
| 7 | + */ | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * @description 小程序环境 Polyfill:TextEncoder/TextDecoder | ||
| 11 | + * - 部分运行时(尤其是低版本基础库)可能缺少 TextEncoder/TextDecoder | ||
| 12 | + * - NFC/二维码等场景会用到 TextDecoder/TextEncoder(例如解析 NDEF) | ||
| 13 | + */ | ||
| 1 | 14 | ||
| 2 | if (typeof TextEncoder === 'undefined') { | 15 | if (typeof TextEncoder === 'undefined') { |
| 3 | class TextEncoder { | 16 | class TextEncoder { |
| 17 | + /** | ||
| 18 | + * @description 将字符串编码为 UTF-8 字节数组 | ||
| 19 | + * @param {string} str 待编码字符串 | ||
| 20 | + * @returns {Uint8Array} UTF-8 字节数组 | ||
| 21 | + */ | ||
| 4 | encode(str) { | 22 | encode(str) { |
| 5 | const len = str.length; | 23 | const len = str.length; |
| 6 | const res = []; | 24 | const res = []; |
| ... | @@ -40,7 +58,14 @@ if (typeof TextEncoder === 'undefined') { | ... | @@ -40,7 +58,14 @@ if (typeof TextEncoder === 'undefined') { |
| 40 | 58 | ||
| 41 | if (typeof TextDecoder === 'undefined') { | 59 | if (typeof TextDecoder === 'undefined') { |
| 42 | class TextDecoder { | 60 | class TextDecoder { |
| 61 | + /** | ||
| 62 | + * @description 将字节数据解码为字符串(简化 UTF-8 解码) | ||
| 63 | + * @param {ArrayBuffer|Uint8Array} view 字节数据 | ||
| 64 | + * @param {Object} options 预留参数(与标准接口对齐) | ||
| 65 | + * @returns {string} 解码后的字符串 | ||
| 66 | + */ | ||
| 43 | decode(view, options) { | 67 | decode(view, options) { |
| 68 | + void options; | ||
| 44 | if (!view) { | 69 | if (!view) { |
| 45 | return ''; | 70 | return ''; |
| 46 | } | 71 | } | ... | ... |
| ... | @@ -21,10 +21,12 @@ import { parseQueryString } from './tools' | ... | @@ -21,10 +21,12 @@ import { parseQueryString } from './tools' |
| 21 | import BASE_URL, { REQUEST_DEFAULT_PARAMS } from './config'; | 21 | import BASE_URL, { REQUEST_DEFAULT_PARAMS } from './config'; |
| 22 | 22 | ||
| 23 | /** | 23 | /** |
| 24 | - * 获取sessionid的工具函数 | 24 | + * @description 获取 sessionid 的工具函数 |
| 25 | + * - sessionid 由 authRedirect.refreshSession 写入 | ||
| 26 | + * - 每次请求前动态读取,避免旧会话导致的 401 | ||
| 25 | * @returns {string|null} sessionid或null | 27 | * @returns {string|null} sessionid或null |
| 26 | */ | 28 | */ |
| 27 | -const getSessionId = () => { | 29 | +export const getSessionId = () => { |
| 28 | try { | 30 | try { |
| 29 | return Taro.getStorageSync("sessionid") || null; | 31 | return Taro.getStorageSync("sessionid") || null; |
| 30 | } catch (error) { | 32 | } catch (error) { |
| ... | @@ -33,12 +35,44 @@ const getSessionId = () => { | ... | @@ -33,12 +35,44 @@ const getSessionId = () => { |
| 33 | } | 35 | } |
| 34 | }; | 36 | }; |
| 35 | 37 | ||
| 38 | +/** | ||
| 39 | + * @description 设置 sessionid(一般不需要手动调用) | ||
| 40 | + * - 正常情况下由 authRedirect.refreshSession 写入 | ||
| 41 | + * - 保留该方法用于极端场景的手动修复/兼容旧逻辑 | ||
| 42 | + * @param {string} sessionid cookie 字符串 | ||
| 43 | + * @returns {void} 无返回值 | ||
| 44 | + */ | ||
| 45 | +export const setSessionId = (sessionid) => { | ||
| 46 | + try { | ||
| 47 | + if (!sessionid) return | ||
| 48 | + Taro.setStorageSync('sessionid', sessionid) | ||
| 49 | + } catch (error) { | ||
| 50 | + console.error('设置sessionid失败:', error) | ||
| 51 | + } | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +/** | ||
| 55 | + * @description 清空 sessionid(一般不需要手动调用) | ||
| 56 | + * @returns {void} 无返回值 | ||
| 57 | + */ | ||
| 58 | +export const clearSessionId = () => { | ||
| 59 | + try { | ||
| 60 | + Taro.removeStorageSync('sessionid') | ||
| 61 | + } catch (error) { | ||
| 62 | + console.error('清空sessionid失败:', error) | ||
| 63 | + } | ||
| 64 | +} | ||
| 65 | + | ||
| 36 | // const isPlainObject = (value) => { | 66 | // const isPlainObject = (value) => { |
| 37 | // if (value === null || typeof value !== 'object') return false | 67 | // if (value === null || typeof value !== 'object') return false |
| 38 | // return Object.prototype.toString.call(value) === '[object Object]' | 68 | // return Object.prototype.toString.call(value) === '[object Object]' |
| 39 | // } | 69 | // } |
| 40 | 70 | ||
| 41 | -// create an axios instance | 71 | +/** |
| 72 | + * @description axios 实例(axios-miniprogram) | ||
| 73 | + * - 统一 baseURL / timeout | ||
| 74 | + * - 通过拦截器处理:默认参数、cookie 注入、401 自动续期、弱网降级 | ||
| 75 | + */ | ||
| 42 | const service = axios.create({ | 76 | const service = axios.create({ |
| 43 | baseURL: BASE_URL, // url = base url + request url | 77 | baseURL: BASE_URL, // url = base url + request url |
| 44 | // withCredentials: true, // send cookies when cross-domain requests | 78 | // withCredentials: true, // send cookies when cross-domain requests |
| ... | @@ -52,9 +86,9 @@ const service = axios.create({ | ... | @@ -52,9 +86,9 @@ const service = axios.create({ |
| 52 | let has_shown_timeout_modal = false | 86 | let has_shown_timeout_modal = false |
| 53 | 87 | ||
| 54 | /** | 88 | /** |
| 55 | - * 判断是否为超时错误 | 89 | + * @description 判断是否为超时错误 |
| 56 | - * @param {Error} error - 请求错误对象 | 90 | + * @param {Error} error 请求错误对象 |
| 57 | - * @returns {boolean} 是否为超时错误 | 91 | + * @returns {boolean} true=超时,false=非超时 |
| 58 | */ | 92 | */ |
| 59 | 93 | ||
| 60 | const is_timeout_error = (error) => { | 94 | const is_timeout_error = (error) => { |
| ... | @@ -64,9 +98,9 @@ const is_timeout_error = (error) => { | ... | @@ -64,9 +98,9 @@ const is_timeout_error = (error) => { |
| 64 | } | 98 | } |
| 65 | 99 | ||
| 66 | /** | 100 | /** |
| 67 | - * 判断是否为网络错误(断网/弱网/请求失败等) | 101 | + * @description 判断是否为网络错误(断网/弱网/请求失败等) |
| 68 | - * @param {Error} error - 请求错误对象 | 102 | + * @param {Error} error 请求错误对象 |
| 69 | - * @returns {boolean} 是否为网络错误 | 103 | + * @returns {boolean} true=网络错误,false=非网络错误 |
| 70 | */ | 104 | */ |
| 71 | const is_network_error = (error) => { | 105 | const is_network_error = (error) => { |
| 72 | const msg = String(error?.message || error?.errMsg || '') | 106 | const msg = String(error?.message || error?.errMsg || '') |
| ... | @@ -89,11 +123,11 @@ const is_network_error = (error) => { | ... | @@ -89,11 +123,11 @@ const is_network_error = (error) => { |
| 89 | } | 123 | } |
| 90 | 124 | ||
| 91 | /** | 125 | /** |
| 92 | - * 是否需要触发弱网/断网降级逻辑 | 126 | + * @description 是否需要触发弱网/断网降级逻辑 |
| 93 | * - 超时:直接触发 | 127 | * - 超时:直接触发 |
| 94 | * - 网络错误:直接触发(避免 wifi 但无网场景漏判) | 128 | * - 网络错误:直接触发(避免 wifi 但无网场景漏判) |
| 95 | - * @param {Error} error - 请求错误对象 | 129 | + * @param {Error} error 请求错误对象 |
| 96 | - * @returns {Promise<boolean>} 是否需要触发降级 | 130 | + * @returns {Promise<boolean>} true=需要降级,false=不需要 |
| 97 | */ | 131 | */ |
| 98 | const should_handle_bad_network = async (error) => { | 132 | const should_handle_bad_network = async (error) => { |
| 99 | if (is_timeout_error(error)) return true | 133 | if (is_timeout_error(error)) return true |
| ... | @@ -101,10 +135,10 @@ const should_handle_bad_network = async (error) => { | ... | @@ -101,10 +135,10 @@ const should_handle_bad_network = async (error) => { |
| 101 | } | 135 | } |
| 102 | 136 | ||
| 103 | /** | 137 | /** |
| 104 | - * 处理请求超时错误 | 138 | + * @description 处理请求超时/弱网错误 |
| 105 | - * - 显示超时提示 modal | 139 | + * - 优先:若存在离线预约记录缓存,直接跳转离线预约列表页 |
| 106 | - * - 若有离线预约记录缓存,则跳转至离线预约列表页 | 140 | + * - 否则:弹出弱网提示(统一文案由 uiText 管理) |
| 107 | - * - 否则提示用户检查网络连接 | 141 | + * @returns {Promise<void>} 无返回值 |
| 108 | */ | 142 | */ |
| 109 | const handle_request_timeout = async () => { | 143 | const handle_request_timeout = async () => { |
| 110 | if (has_shown_timeout_modal) return | 144 | if (has_shown_timeout_modal) return |
| ... | @@ -133,7 +167,7 @@ const handle_request_timeout = async () => { | ... | @@ -133,7 +167,7 @@ const handle_request_timeout = async () => { |
| 133 | } | 167 | } |
| 134 | } | 168 | } |
| 135 | 169 | ||
| 136 | -// request interceptor | 170 | +// 请求拦截器:合并默认参数 / 注入 cookie |
| 137 | service.interceptors.request.use( | 171 | service.interceptors.request.use( |
| 138 | config => { | 172 | config => { |
| 139 | // console.warn(config) | 173 | // console.warn(config) |
| ... | @@ -155,11 +189,13 @@ service.interceptors.request.use( | ... | @@ -155,11 +189,13 @@ service.interceptors.request.use( |
| 155 | } | 189 | } |
| 156 | 190 | ||
| 157 | /** | 191 | /** |
| 158 | - * 动态获取sessionid并设置到请求头 | 192 | + * 动态获取 sessionid 并设置到请求头 |
| 159 | - * 确保每个请求都带上最新的sessionid | 193 | + * - 确保每个请求都带上最新的 sessionid |
| 194 | + * - 注意:axios-miniprogram 的 headers 可能不存在,需要先兜底 | ||
| 160 | */ | 195 | */ |
| 161 | const sessionid = getSessionId(); | 196 | const sessionid = getSessionId(); |
| 162 | if (sessionid) { | 197 | if (sessionid) { |
| 198 | + config.headers = config.headers || {} | ||
| 163 | config.headers.cookie = sessionid; | 199 | config.headers.cookie = sessionid; |
| 164 | } | 200 | } |
| 165 | 201 | ||
| ... | @@ -187,13 +223,12 @@ service.interceptors.request.use( | ... | @@ -187,13 +223,12 @@ service.interceptors.request.use( |
| 187 | return config | 223 | return config |
| 188 | }, | 224 | }, |
| 189 | error => { | 225 | error => { |
| 190 | - // do something with request error | 226 | + console.error('请求拦截器异常:', error) |
| 191 | - console.error(error, 'err') // for debug | ||
| 192 | return Promise.reject(error) | 227 | return Promise.reject(error) |
| 193 | } | 228 | } |
| 194 | ) | 229 | ) |
| 195 | 230 | ||
| 196 | -// response interceptor | 231 | +// 响应拦截器:401 自动续期 / 弱网降级 |
| 197 | service.interceptors.response.use( | 232 | service.interceptors.response.use( |
| 198 | /** | 233 | /** |
| 199 | * 响应拦截器说明 | 234 | * 响应拦截器说明 | ... | ... |
| ... | @@ -2,7 +2,16 @@ | ... | @@ -2,7 +2,16 @@ |
| 2 | * 系统参数 | 2 | * 系统参数 |
| 3 | */ | 3 | */ |
| 4 | 4 | ||
| 5 | -const DEFAULT_HOST_TYPE = ['首次参与', '老用户']; // 主办方默认用户类型 | 5 | +/** |
| 6 | -const DEFAULT_HOST_STATUS = ['跟踪', '引导']; // 主办方默认用户状态 | 6 | + * @description 主办方默认用户类型 |
| 7 | + * @type {Array<string>} | ||
| 8 | + */ | ||
| 9 | +const DEFAULT_HOST_TYPE = ['首次参与', '老用户']; | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * @description 主办方默认用户状态 | ||
| 13 | + * @type {Array<string>} | ||
| 14 | + */ | ||
| 15 | +const DEFAULT_HOST_STATUS = ['跟踪', '引导']; | ||
| 7 | 16 | ||
| 8 | export { DEFAULT_HOST_TYPE, DEFAULT_HOST_STATUS } | 17 | export { DEFAULT_HOST_TYPE, DEFAULT_HOST_STATUS } | ... | ... |
| ... | @@ -9,7 +9,11 @@ import dayjs from 'dayjs'; | ... | @@ -9,7 +9,11 @@ import dayjs from 'dayjs'; |
| 9 | import Taro from '@tarojs/taro'; | 9 | import Taro from '@tarojs/taro'; |
| 10 | import BASE_URL, { REQUEST_DEFAULT_PARAMS } from './config' | 10 | import BASE_URL, { REQUEST_DEFAULT_PARAMS } from './config' |
| 11 | 11 | ||
| 12 | -// 格式化时间 | 12 | +/** |
| 13 | + * @description 格式化时间 | ||
| 14 | + * @param {string|number|Date} date 时间入参 | ||
| 15 | + * @returns {string} 格式化后的时间字符串(YYYY-MM-DD HH:mm) | ||
| 16 | + */ | ||
| 13 | const formatDate = (date) => { | 17 | const formatDate = (date) => { |
| 14 | return dayjs(date).format('YYYY-MM-DD HH:mm'); | 18 | return dayjs(date).format('YYYY-MM-DD HH:mm'); |
| 15 | }; | 19 | }; |
| ... | @@ -22,7 +26,7 @@ const wxInfo = () => { | ... | @@ -22,7 +26,7 @@ const wxInfo = () => { |
| 22 | const info = Taro.getSystemInfoSync(); | 26 | const info = Taro.getSystemInfoSync(); |
| 23 | const isAndroid = info.platform === 'android'; | 27 | const isAndroid = info.platform === 'android'; |
| 24 | const isiOS = info.platform === 'ios'; | 28 | const isiOS = info.platform === 'ios'; |
| 25 | - // 简单模拟 | 29 | + // 说明:当前项目只用到 Android/iOS 区分;平板能力按需补充 |
| 26 | return { | 30 | return { |
| 27 | isAndroid, | 31 | isAndroid, |
| 28 | isiOS, | 32 | isiOS, |
| ... | @@ -31,9 +35,9 @@ const wxInfo = () => { | ... | @@ -31,9 +35,9 @@ const wxInfo = () => { |
| 31 | }; | 35 | }; |
| 32 | 36 | ||
| 33 | /** | 37 | /** |
| 34 | - * @description 解析URL参数 | 38 | + * @description 解析 URL 参数 |
| 35 | - * @param {*} url | 39 | + * @param {string} url 完整 URL 或带 query 的路径 |
| 36 | - * @returns {Object} URL参数对象,键值对形式存储参数名和参数值 | 40 | + * @returns {Object} URL 参数对象(键值对) |
| 37 | */ | 41 | */ |
| 38 | const parseQueryString = url => { | 42 | const parseQueryString = url => { |
| 39 | if (!url) return {}; | 43 | if (!url) return {}; |
| ... | @@ -47,10 +51,10 @@ const parseQueryString = url => { | ... | @@ -47,10 +51,10 @@ const parseQueryString = url => { |
| 47 | } | 51 | } |
| 48 | 52 | ||
| 49 | /** | 53 | /** |
| 50 | - * 字符串包含字符数组中字符的状态 | 54 | + * @description 判断字符串是否包含数组中的任意子串 |
| 51 | - * @param {*} array 字符数组 | 55 | + * @param {Array<string>} array 子串数组 |
| 52 | - * @param {*} str 字符串 | 56 | + * @param {string} str 目标字符串 |
| 53 | - * @returns 包含状态 | 57 | + * @returns {boolean} true=包含任意一个子串,false=都不包含 |
| 54 | */ | 58 | */ |
| 55 | const strExist = (array, str) => { | 59 | const strExist = (array, str) => { |
| 56 | if (!str) return false; | 60 | if (!str) return false; |
| ... | @@ -114,13 +118,10 @@ const formatDatetime = (data) => { | ... | @@ -114,13 +118,10 @@ const formatDatetime = (data) => { |
| 114 | }; | 118 | }; |
| 115 | 119 | ||
| 116 | /** | 120 | /** |
| 117 | - * @description 构建 API 请求 URL | 121 | + * @description 构建 API 请求 URL(带默认公共参数) |
| 118 | - * @param {string} action - API 动作名称(如 'get_user_info') | 122 | + * @param {string} action 接口动作名称(例如:openid_wxapp) |
| 119 | - * @param {Object} [params={}] - 请求参数对象,默认空对象 | 123 | + * @param {Object} [params={}] 额外 query 参数 |
| 120 | - * @returns {string} 完整的 API 请求 URL,包含基础路径、动作参数、默认参数和查询字符串 | 124 | + * @returns {string} 完整请求 URL(BASE_URL + /srv/?a=...&f=...&client_name=...) |
| 121 | - * @example | ||
| 122 | - * buildApiUrl('get_user_info', { user_id: 123 }); | ||
| 123 | - * 返回 "/srv/?a=get_user_info&f=json&client_name=xyxBooking&user_id=123" | ||
| 124 | */ | 125 | */ |
| 125 | const buildApiUrl = (action, params = {}) => { | 126 | const buildApiUrl = (action, params = {}) => { |
| 126 | const queryParams = new URLSearchParams({ | 127 | const queryParams = new URLSearchParams({ | ... | ... |
| ... | @@ -5,6 +5,10 @@ | ... | @@ -5,6 +5,10 @@ |
| 5 | * @FilePath: /xyxBooking-weapp/src/utils/uiText.js | 5 | * @FilePath: /xyxBooking-weapp/src/utils/uiText.js |
| 6 | * @Description: 弱网络提示文本 | 6 | * @Description: 弱网络提示文本 |
| 7 | */ | 7 | */ |
| 8 | +/** | ||
| 9 | + * @description 弱网/断网统一文案 | ||
| 10 | + * - toast/modal/banner 等入口统一引用,避免多处硬编码 | ||
| 11 | + */ | ||
| 8 | export const weak_network_text = { | 12 | export const weak_network_text = { |
| 9 | title: '网络连接不畅', | 13 | title: '网络连接不畅', |
| 10 | toast_title: '网络连接不畅', | 14 | toast_title: '网络连接不畅', | ... | ... |
| 1 | -/*获取当前页url*/ | 1 | +/** |
| 2 | + * @description 获取当前页路由(不含 query) | ||
| 3 | + * @returns {string} 当前页 route,例如:pages/index/index | ||
| 4 | + */ | ||
| 2 | const getCurrentPageUrl = () => { | 5 | const getCurrentPageUrl = () => { |
| 3 | - let pages = getCurrentPages() //获取加载的页面 | 6 | + // 获取加载的页面栈 |
| 4 | - let currentPage = pages[pages.length - 1] //获取当前页面的对象 | 7 | + let pages = getCurrentPages() |
| 5 | - let url = currentPage.route //当前页面url | 8 | + // 获取当前页面对象 |
| 9 | + let currentPage = pages[pages.length - 1] | ||
| 10 | + // 当前页面 route(不含 query) | ||
| 11 | + let url = currentPage.route | ||
| 6 | return url | 12 | return url |
| 7 | } | 13 | } |
| 8 | -/*获取当前页参数*/ | 14 | +/** |
| 15 | + * @description 获取当前页 query 参数 | ||
| 16 | + * @returns {Object} 当前页 options(query 参数对象) | ||
| 17 | + */ | ||
| 9 | const getCurrentPageParam = () => { | 18 | const getCurrentPageParam = () => { |
| 10 | - let pages = getCurrentPages() //获取加载的页面 | 19 | + // 获取加载的页面栈 |
| 11 | - let currentPage = pages[pages.length - 1] //获取当前页面的对象 | 20 | + let pages = getCurrentPages() |
| 12 | - let options = currentPage.options //如果要获取url中所带的参数可以查看options | 21 | + // 获取当前页面对象 |
| 22 | + let currentPage = pages[pages.length - 1] | ||
| 23 | + // 当前页面 query 参数对象 | ||
| 24 | + let options = currentPage.options | ||
| 13 | return options | 25 | return options |
| 14 | } | 26 | } |
| 15 | 27 | ... | ... |
-
Please register or login to post a comment