hookehuyr

新增寺院录入功能,新增支付完成展示订单详情页

......@@ -2,11 +2,11 @@
VITE_BASE = /
# 测试open-id
# VITE_OPENID = api-test-openid
VITE_OPENID = api-test-openid
# VITE_OPENID = o8BRf1gLDWieH3Y3JvbrI_4IjaME
# VITE_OPENID = oJLZq5t9PIKLW9tm1oSUNAuPwssA
# VITE_OPENID = oJLZq5uT_6GwIh2tQWh1F9IoHZ3U
VITE_OPENID = o5CsxuF5AfUirRn4VUwaCSNZrUoM
# VITE_OPENID = o5CsxuF5AfUirRn4VUwaCSNZrUoM
# VITE_OPENID =
# B端账号
......@@ -16,8 +16,8 @@ VITE_ID = 13761653761
VITE_PIN =
# 反向代理服务器地址
# VITE_PROXY_TARGET = https://oa-dev.onwall.cn
VITE_PROXY_TARGET = http://oa.onwall.cn
VITE_PROXY_TARGET = https://oa-dev.onwall.cn
# VITE_PROXY_TARGET = http://oa.onwall.cn
# PC端地址
VITE_MOBILE_URL = http://localhost:5173/
......
......@@ -7,7 +7,9 @@ export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
copy: typeof import('./src/components/qrCode copy.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']
......
/*
* @Date: 2023-08-24 09:42:27
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2024-01-25 14:56:16
* @LastEditTime: 2024-01-26 13:28:51
* @FilePath: /xysBooking/src/api/index.js
* @Description: 文件描述
*/
......@@ -17,12 +17,14 @@ const Api = {
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',
};
/**
......@@ -97,6 +99,13 @@ export const payCallbackAPI = (params) => fn(fetch.post(Api.PAY_CALLBACK, params
export const billInfoAPI = (params) => fn(fetch.get(Api.BILL_INFO, params));
/**
* @description: 预约单详情,参观者列表 - 免授权接口
* @param {String} bill_id 预约单id
* @returns {String}
*/
export const onAuthBillInfoAPI = (params) => fn(fetch.get(Api.ON_AUTH_BILL_INFO, params));
/**
* @description: 预约码列表
* @returns {String}
*/
......@@ -136,3 +145,10 @@ export const billPersonAPI = (params) => fn(fetch.get(Api.BILL_PREPARE, params))
* @returns {String}
*/
export const billPayStatusAPI = (params) => fn(fetch.get(Api.BILL_PAY_STATUS, params));
/**
* @description: 身份证查询预约码
* @param {String}
* @returns {String}
*/
export const queryQrCodeAPI = (params) => fn(fetch.get(Api.QUERY_QR_CODE, params));
......
<!--
* @Date: 2024-01-16 10:06:47
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2024-01-26 13:55:15
* @FilePath: /xysBooking/src/components/qrCodeSearch.vue
* @Description: 预约码卡组件
-->
<template>
<div class="qr-code-page">
<div v-if="userinfo.qr_code" class="show-qrcode">
<div class="qrcode-content">
<div class="user-info">{{ userinfo.name }}&nbsp;{{ userinfo.id }}</div>
<div class="user-qrcode">
<div class="left">
<!-- <img src="https://cdn.ipadbiz.cn/xys/booking/%E5%B7%A6@2x.png"> -->
</div>
<div class="center">
<img :src="userinfo.qr_code_url ">
<div v-if="useStatus === STATUS_CODE.CANCELED || useStatus === STATUS_CODE.USED" class="qrcode-used">
<p>二维码{{ qr_code_status[useStatus] }}</p>
</div>
</div>
<div class="right">
<!-- <img src="https://cdn.ipadbiz.cn/xys/booking/%E5%8F%B3@2x.png"> -->
</div>
</div>
<div style="color: red; margin-top: 1rem;">{{ userinfo.datetime }}</div>
</div>
</div>
<div v-else class="no-qrcode">
<img src="https://cdn.ipadbiz.cn/xys/booking/%E6%9A%82%E6%97%A0@2x.png" style="width: 10rem;">
<div class="no-qrcode-title">您还没有预约过今天参观</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Cookies, $, _, axios, storeToRefs, mainStore, Toast, useTitle } from '@/utils/generatePackage.js'
//import { } from '@/utils/generateModules.js'
//import { } from '@/utils/generateIcons.js'
//import { } from '@/composables'
import { qrcodeListAPI, qrcodeStatusAPI, billPersonAPI, queryQrCodeAPI } from '@/api/index'
const $route = useRoute();
const $router = useRouter();
useTitle($route.meta.title);
const props = defineProps({
id: {
type: String,
default: ''
},
});
const formatDatetime = (data) => { // 格式化日期
let begin_time = data.begin_time.slice(0, -3);
let end_time = data.end_time.slice(0, -3);
let str = begin_time + ' ' + end_time;
return `${str.split(' ')[0]} ${str.split(' ')[1]}-${str.split(' ')[3]}`;
}
const userinfo = ref({});
watch(
() => props.id,
async (id) => {
console.warn(id);
}
)
/**
* 生成15位身份证号中间8位替换为*号
* @param {*} inputString
*/
function replaceMiddleCharacters(inputString) {
if (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 useStatus = ref('0');
const qr_code_status = {
'1': '未激活',
'3': '待使用',
'5': '被取消',
'7': '已使用'
};
const STATUS_CODE = {
APPLY: '1',
SUCCESS: '3',
CANCELED: '5',
USED: '7'
};
const formatStatus = (status) => {
switch (status) {
case 'success':
return {
key: 'success',
value: '预约成功'
}
case 'cancel':
return {
key: 'cancel',
value: '已取消'
}
case 'used':
return {
key: 'used',
value: '已使用'
}
}
}
const selectUser = (index) => {
select_index.value = index;
}
onMounted(async () => {
if (props.id) {
// 511522190103214279
const { code, data } = await queryQrCodeAPI({ id_number: props.id });
if (code) {
userinfo.value = {
id: formatId(props.id),
name: data.name,
datetime: formatDatetime({ begin_time: data.begin_time, end_time: data.end_time }),
qr_code: data.qr_code,
qr_code_url: 'http://oa.onwall.cn/admin?m=srv&a=get_qrcode&key=' + data.qr_code,
}
const { code: status_code, data: status_data } = await qrcodeStatusAPI({ qr_code: data.qr_code });
if (status_code) {
useStatus.value = status_data.status;
}
}
}
});
// 定义轮询函数
const poll = async () => {
// 二维码未使用不停轮询接口
if (useStatus.value === STATUS_CODE.SUCCESS) {
const { code, data } = await qrcodeStatusAPI({ qr_code: userinfo.value.qr_code });
if (code) {
useStatus.value = data.status;
}
}
};
// 每秒执行一次轮询函数
const intervalId = setInterval(poll, 1000);
// 在组件卸载时清除定时器
onUnmounted(() => {
clearInterval(intervalId);
});
</script>
<style lang="less" scoped>
.qr-code-page {
.qrcode-content {
padding: 1rem 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
// width: 100%;
// height: 100%;
background-color: #FFF;
border-radius: 8px;
box-shadow: 0rem 0rem 0.92rem 0rem rgba(106,106,106,0.27);
.user-status {
.status {
font-size: 0.75rem;
padding: 5px 8px;
border-radius: 5px;
}
.success {
color: #A67939;
background-color: #FBEEDC;
}
.cancel {
color: #929292;
background-color: #E6E6E6;
}
.used {
color: #477F3D;
background-color: #E5EFE3;
}
}
.user-info {
color: #A6A6A6;
font-size: 1.15rem;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
.user-qrcode {
display: flex;
align-items: center;
.left {
img {
width: 1.75rem; margin-right: 0.5rem;
}
}
.center {
border: 1px solid #D1D1D1;
border-radius: 20px;
padding: 0.5rem;
position: relative;
img {
width: 15rem;
}
div {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(106,106,106,0.6);
// opacity: 0.49;
border-radius: 20px;
color: #FFF;
text-align: center;
p {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 1.2rem;
}
}
}
.right {
img {
width: 1.75rem;
margin-left: 0.5rem;
}
}
}
.refresh {
display: flex;
justify-content: center;
align-items: center;
margin-top: 1rem;
img {
width: 3.5rem;
}
div {
color: #A67939; font-size: 1.1rem;
}
}
}
.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;
img {
margin-top: 1rem;
margin-bottom: 1rem;
width: 10rem;
}
.no-qrcode-title {
color: #A67939;
font-size: 1.05rem;
}
}
}
</style>
/*
* @Date: 2023-06-13 13:26:46
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2024-01-19 21:59:56
* @LastEditTime: 2024-01-26 13:08:44
* @FilePath: /xysBooking/src/route.js
* @Description: 路由列表
*/
......@@ -84,6 +84,20 @@ export default [
},
},
{
path: '/callback',
component: () => import('@/views/callback.vue'),
meta: {
title: '反馈页',
},
},
{
path: '/search',
component: () => import('@/views/search.vue'),
meta: {
title: '寺院录入',
},
},
{
path: '/auth',
component: () => import('@/views/auth.vue'),
meta: {
......
<!--
* @Date: 2024-01-26 10:24:45
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2024-01-26 13:58:57
* @FilePath: /xysBooking/src/views/callback.vue
* @Description: 反馈页面
-->
<template>
<div class="callback-page">
<div style="">
<div class="text-prompts">
<img src="https://cdn.ipadbiz.cn/xys/booking/%E6%88%90%E5%8A%9F@2x.png">
<div class="text">支付完成</div>
</div>
<div class="appointment-information">
<div class="number-of-visitors">参观人数:<span>{{ billInfo?.total_qty }} 人</span></div>
<div class="visit-time">参访时间:<span>{{ billInfo?.datetime }}</span></div>
<div class="payment-amount">支付金额:<span>¥ {{ billInfo?.total_amt }}</span></div>
</div>
<!-- <div class="appointment-notice">
<p style="margin-bottom: 0.25rem;"><van-icon name="info-o" />&nbsp;温馨提示</p>
<p style="font-size: 0.85rem;">1. 一人一码,或拿身份证,扫码或识别身份证成功后进入</p>
<p style="font-size: 0.85rem;">2. 若您无法按时参观,请提前在预约记录中取消您的预约</p>
</div> -->
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { onAuthBillInfoAPI } from '@/api/index'
import { Cookies, $, _, axios, storeToRefs, mainStore, Toast, useTitle } from '@/utils/generatePackage.js'
//import { } from '@/utils/generateModules.js'
//import { } from '@/utils/generateIcons.js'
//import { } from '@/composables'
const $route = useRoute();
const $router = useRouter();
useTitle($route.meta.title);
const billInfo = ref({});
const formatDatetime = (data) => { // 格式化日期
let begin_time = data.begin_time.slice(0, -3);
let end_time = data.end_time.slice(0, -3);
let str = begin_time + ' ' + end_time;
return `${str.split(' ')[0]} ${str.split(' ')[1]}-${str.split(' ')[3]}`;
}
onMounted(async () => {
// 获取订单详情
const { code, data } = await onAuthBillInfoAPI({ pay_id: $route.query.pay_id });
if (code) {
//
data.datetime = data && formatDatetime(data);
billInfo.value = data;
}
})
</script>
<style lang="less" scoped>
.callback-page {
position: relative;
background-color: #FFF;
.text-prompts {
display: flex;
align-items: center;
justify-content: center;
height: 35vh;
flex-direction: column;
img {
width: 60vw;
}
.text {
color: #A67939;
font-size: 1.25rem;
}
}
.appointment-information {
padding: 2rem 1rem;
border-bottom: 1px dashed #A67939;
line-height: 2;
.number-of-visitors {
span {
color: #A67939;
}
}
.visit-time {
span {
color: #A67939;
}
}
.payment-amount {
span {
color: #FF1919;
}
}
}
.appointment-notice {
color: #A67939;
padding: 1rem;
line-height: 2;
}
}
</style>
<!--
* @Date: 2023-06-21 10:23:09
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2024-01-25 12:02:12
* @LastEditTime: 2024-01-26 13:09:53
* @FilePath: /xysBooking/src/views/index.vue
* @Description: 预约页首页
-->
......@@ -24,6 +24,10 @@
<van-icon size="1.5rem" color="#A67939" :name="icon_2" />
&nbsp;预约记录
</div>
<div class="search" @click="toSearch">
<van-icon size="1.5rem" color="#A67939" :name="icon_2" />
&nbsp;寺院录入
</div>
</div>
<div class="logo"></div>
</div>
......@@ -75,6 +79,9 @@ const toBooking = () => { // 跳转到预约须知
const toRecord = () => { // 跳转到预约记录
go('/bookingList');
}
const toSearch = () => { // 跳转到寺院录入
go('/search');
}
const toCode = () => { // 跳转到预约码
// go('/bookingCode');
window.location.replace(location.origin + location.pathname + '#/bookingCode');
......@@ -134,6 +141,16 @@ useClickAway(root, () => {
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;
}
}
.logo {
position: absolute;
......
<!--
* @Date: 2024-01-26 13:08:09
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2024-01-26 13:53:38
* @FilePath: /xysBooking/src/views/search.vue
* @Description: 文件描述
-->
<template>
<div class="search-page">
<div>
<div v-if="!is_search" class="input-item">
<div>证件号码</div>
<div>
<input type="text" v-model="idCode" placeholder="请输入证件号码" @blur="checkIdCode" maxlength="18" style="width: 100%;">
</div>
</div>
<div v-else>
<qrCodeSearch :id="id_number" />
</div>
<div v-if="!is_search" class="save-wrapper">
<div class="save-btn" @click="searchBtn">查询</div>
</div>
<div v-else class="success-btn">
<div @click="goToHome" class="btn-item btn-left">首页</div>
<div @click="goBack" class="btn-item btn-right">返回查询</div>
</div>
</div>
<van-toast v-model:show="show_error" style="">
<template #message>
{{ error_message }}
</template>
</van-toast>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { validateCIN } from '@code-ts/cin'
import qrCodeSearch from '@/components/qrCodeSearch';
import { Cookies, $, _, axios, storeToRefs, mainStore, Toast, useTitle } from '@/utils/generatePackage.js'
//import { } from '@/utils/generateModules.js'
//import { } from '@/utils/generateIcons.js'
//import { } from '@/composables'
import { showSuccessToast, showFailToast } from 'vant';
import { useGo } from '@/hooks/useGo'
import { queryQrCodeAPI } from '@/api/index'
const $route = useRoute();
const $router = useRouter();
useTitle($route.meta.title);
const go = useGo();
const is_search = ref(false);
const idCode = ref('');
const id_number = ref('');
const show_error = ref(false);
const error_message = ref('');
const checkIdCode = () => { // 检查身份证号是否为空
let flag = true;
if (idCode.value.length === 15) { // 15位身份证号码不校验
flag = true;
} else {
if (!validateCIN(idCode.value)) {
show_error.value = true;
error_message.value = '请检查身份证号码';
flag = false;
}
}
return flag;
}
const searchBtn = async () => {
// 查询用户信息
if (checkIdCode()) {
is_search.value = true;
id_number.value = idCode.value;
idCode.value = ''
}
}
const goBack = () => {
is_search.value = false;
}
const goToHome = () => {
go('/')
}
</script>
<style lang="less" scoped>
.search-page {
padding: 1rem;
position: relative;
.input-item {
display: flex;
align-items: center;
justify-content: space-between;
background-color: #fff;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
input {
border: 0;
text-align: right;
}
}
.save-wrapper {
background-color: #FFF;
position: fixed;
width: 100vw;
bottom: 0;
left: 0;
height: 5rem;
display: flex;
align-items: center;
justify-content: center;
.save-btn {
text-align: center;
flex-grow: 1;
background-color: #A67939;
padding: 0.8rem 0;
margin: 1rem;
color: #FFF;
border-radius: 5px;
font-size: 1.1rem;
}
}
.success-btn {
background-color: #FFF;
position: fixed;
width: 100vw;
bottom: 0;
left: 0;
height: 5rem;
display: flex;
align-items: center;
justify-content: space-around;
.btn-item {
padding: 0.7rem 4rem;
border-radius: 5px;
font-size: 1.05rem;
}
.btn-left{
background-color: #A67939;
color: #FFF;
margin-left: 0.7rem;
}
.btn-right{
border: 1px solid #A67939;
color: #A67939;
font-size: 1.05rem;
margin-right: 0.7rem;
}
}
}
</style>