hookehuyr

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

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