This commit is contained in:
zpc 2025-09-28 00:13:15 +08:00
parent d91eac13d1
commit e0ba4f5d5b
20 changed files with 1101 additions and 244 deletions

View File

@ -6,11 +6,11 @@
// 开发环境配置
const development = {
// API基础URL
// baseUrl: 'https://ydsapi.zpc-xy.com',
baseUrl: 'https://sqqp.zpc-xy.com',
// baseUrl: 'http://1.15.21.245:2401',
// host: ['http://1.15.21.245:2401'],
baseUrl: 'http://localhost:2015',
host: ['http://localhost:2015'],
host: ['https://sqqp.zpc-xy.com'],
// baseUrl: 'http://192.168.1.21:2016',
// host: ['http://192.168.1.21:2016'],
imageUrl: 'https://guyu-1308826010.cos.ap-shanghai.myqcloud.com',
};

View File

@ -23,7 +23,7 @@ export const getConfig = async () => {
}
return null;
}
/**
* 接口测试反馈
* @returns {Promise<any>}
@ -36,6 +36,18 @@ export const interFaceTest = async () => {
return null;
}
/**
* 获取服务与说明数据
* @returns {Promise<any>}
*/
export const getServiceDescription = async () => {
const res = await request.post("Common/GetServiceDescription");
if (res.code == 0) {
return res.data;
}
return null;
}
/**
* 上传附件通用接口
* @param {FormData} formData 上传的表单数据

View File

@ -1,5 +1,21 @@
import request from '@/common/system/request';
/**
* 用户是否可以创建预约仅校验不创建
* @param {Object} reservationData 创建预约所需参数
* @returns {Promise<{canCreate:boolean,message?:string,code?:number}>}
*/
export const canCreateSQReservation = async (reservationData) => {
const res = await request.post("sq/CanCreateSQReservation", reservationData);
if (res && typeof res.code !== 'undefined') {
if (res.code === 0) {
return { canCreate: true, code: 0 };
}
return { canCreate: false, message: res.msg || '不可创建', code: res.code };
}
return { canCreate: false, message: '网络异常', code: 500 };
}
/**
* 获取预约记录
* @param {number} index
@ -95,6 +111,23 @@ export const getReputationByUser = async (pageIndex = 1, pageSize = 20) => {
return null;
}
/**
* 获取支付记录
* @param {number} pageIndex 起始页
* @param {number} pageSize 页大小
* @returns {Promise<any>} 返回支付记录数据
*/
export const getPaymentRecords = async (pageIndex = 1, pageSize = 20) => {
const res = await request.get("sq/GetPaymentRecords", {
pageIndex: pageIndex,
pageSize: pageSize
});
if (res.code == 0) {
return res.data;
}
return null;
}
/**
* 获取可预约的房间列表
* @param {number} startTime 开始时间 时间戳()
@ -181,18 +214,45 @@ export const cancelReservation = async (reservation_id, cancel_reason) => {
};
}
/**
* 预约签到接口仅发起者可操作且只能签到一次
* @param {Object} checkInData 签到数据
* @param {number} checkInData.reservation_id 预约ID
* @param {Array} checkInData.attendeds 参会名单不包含发起者
* @param {number} checkInData.attendeds[].user_id 用户ID
* @param {boolean} checkInData.attendeds[].isAttended 是否到场
* @returns {Promise<any>} 返回签到结果
*/
export const checkInReservation = async (checkInData) => {
console.log("checkInReservation", checkInData);
const res = await request.post("sq/CheckInReservation", checkInData);
if (res.code == 0) {
return {
success: true,
message: res.msg || '签到成功'
};
}
return {
success: false,
message: res.msg || '签到失败'
};
}
export const sqInterface = {
canCreateSQReservation,
getReservationList,
getMyReservation,
getMyUseReservation,
getEvaluateServices,
addEvaluateServices,
getReputationByUser,
getPaymentRecords,
getReservationRoomList,
addSQReservation,
joinReservation,
cancelReservation
cancelReservation,
checkInReservation
}

View File

@ -31,4 +31,5 @@ export const navigateToAccountLogin = (page = "") => {
export const navigateToAgreement = (type) => {
navigateTo(`/pages/other/agreement?type=${type}`);
};
};

1
components.d.ts vendored
View File

@ -17,6 +17,7 @@ declare module 'vue' {
NoData: typeof import('./components/com/page/no-data.vue')['default']
NoEmpty: typeof import('./components/com/index/NoEmpty.vue')['default']
PickerData: typeof import('./components/com/appointment/picker-data.vue')['default']
QiandaoPopup: typeof import('./components/com/page/qiandao-popup.vue')['default']
RadioSelect: typeof import('./components/com/appointment/radio-select.vue')['default']
ReservationEvaluate: typeof import('./components/com/page/reservation-evaluate.vue')['default']
ReservationItem: typeof import('./components/com/page/reservation-item.vue')['default']

View File

@ -1,21 +1,19 @@
<template>
<uni-popup ref="popup" type="center">
<view class="column center"
style="width: 680rpx; background-color: white; border-radius: 10rpx; padding: 20rpx;">
<view class="column center" style="width: 680rpx; background: #F7F7F7; border-radius: 10rpx; padding: 20rpx;">
<text style="">预约信息</text>
<view class="column"
style="width: 100%; height: 180rpx; background-color: #E3E2E2; margin-top: 20rpx; border-radius: 10rpx;">
<view class="row" style="margin-top: 20rpx; align-items: center; margin-left: 20rpx;">
<text style="font-size: 22rpx;">发起者</text>
<view class="column card" style="width: 100%; height: 180rpx;">
<view class="row lable" style="margin-top: 20rpx; align-items: center; margin-left: 20rpx;">
<text>发起者</text>
<image :src="initiatorInfo.avatarImage || ''"
style="width: 50rpx; height: 50rpx; background-color: antiquewhite; border-radius: 50%; margin-left: 20rpx;"
mode=""></image>
<text style="font-size: 20rpx; margin-left: 15rpx;">{{ initiatorInfo.userName || '' }}</text>
<text style="margin-left: 15rpx;">{{ initiatorInfo.userName || '' }}</text>
</view>
<view class="row" style="margin-top: 30rpx; align-items: center; margin-left: 20rpx;">
<text style="font-size: 22rpx;">参与者</text>
<view class="row lable" style="margin-top: 30rpx; align-items: center; margin-left: 20rpx;">
<text>参与者</text>
<view class="row" v-for="(item, index) in participantList" :key="index"
style="align-items: center;">
@ -30,10 +28,6 @@
<text style="font-size: 10rpx;">黑名单</text>
</view>
</view>
<text
style="font-size: 20rpx; margin-left: 30rpx; width: 100rpx; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">{{
item.userName || '' }}</text>
</view>
</view>
@ -41,25 +35,23 @@
</view>
<view class="column"
style="width: 100%; height: 180rpx; background-color: #E3E2E2; margin-top: 20rpx; border-radius: 10rpx;font-size: 22rpx;">
<view class="column card lable" style="width: 100%;padding-bottom:20rpx;">
<view class="row" style="justify-content: space-between; margin: 20rpx;">
<view class="row lable" style="justify-content: space-between; margin: 20rpx;">
<text>开始时间</text>
<text>{{ formatTime(reservationData.start_time) }}</text>
</view>
<view class="row" style="justify-content: space-between; margin: 0 20rpx;">
<view class="row lable" style="justify-content: space-between; margin: 0 20rpx;">
<text>结束时间</text>
<text>{{ formatTime(reservationData.end_time) }}</text>
</view>
<text style="margin: 30rpx 20rpx 0;">合计{{ formatDuration(reservationData.duration_minutes) }}</text>
<text style="margin:20rpx 20rpx 0;">合计{{
formatDuration(reservationData.duration_minutes) }}</text>
</view>
<view class="column"
style="width: 100%; background-color: #E3E2E2; margin-top: 20rpx; border-radius: 10rpx; font-size: 22rpx;">
<text style="margin: 20rpx 20rpx 10rpx; font-size: 24rpx;">房间号{{ reservationData.room_name || ''
<view class="column card lable" style="width: 100%;">
<text style="margin: 20rpx 20rpx 10rpx;">房间号{{ reservationData.room_name || ''
}}</text>
<text style="margin: 10rpx 20rpx;">人数{{ reservationData.player_count || 0 }}</text>
<text style="margin: 10rpx 20rpx;">玩法类型{{ reservationData.game_type || '' }}</text>
@ -67,17 +59,17 @@
<text style="margin: 10rpx 20rpx 20rpx;">补充信息{{ reservationData.extra_info || '无' }}</text>
</view>
<view class="column"
style="width: 100%; background-color: #E3E2E2; margin-top: 20rpx; border-radius: 10rpx; font-size: 22rpx;">
<text style="margin: 20rpx 20rpx 10rpx; font-size: 24rpx;">是否禁烟{{
<view class="column card lable" style="width: 100%; ">
<text style="margin: 20rpx 20rpx 10rpx;">是否禁烟{{
getSmokingText(reservationData.is_smoking) }}</text>
<text style="margin: 10rpx 20rpx;">性别{{ getGenderText(reservationData.gender_limit) }}</text>
<text style="margin: 10rpx 20rpx;">年龄范围{{
getAgeRangeText(reservationData.min_age, reservationData.max_age) }}</text>
<text style="margin: 10rpx 20rpx 20rpx;">信誉{{ reservationData.credit_limit || 0 }}</text>
</view>
<view class="column"
style="width: 100%; background-color: #E3E2E2; margin-top: 20rpx; border-radius: 10rpx; font-size: 22rpx;">
<text style="margin: 20rpx 20rpx 10rpx; font-size: 24rpx;">鸽子费{{ reservationData.deposit_fee || 0
<view class="column card lable" style="width: 100%; ">
<text style="margin: 20rpx 20rpx 10rpx; ">鸽子费{{ reservationData.deposit_fee || 0
}}</text>
<text
style="margin: 10rpx 20rpx 20rpx; color: #9F9F9F;">组局成功后若有牌友未赴约其鸽子费平均分给其他牌友组局成功或失败后鸽子费将全额返还</text>
@ -113,6 +105,13 @@
<text style="font-size: 24rpx; font-weight: 600; color: white;">取消组局</text>
</view>
</view>
<view class="row" v-if="isAddhandleJoin == 3" style="width: 100%; margin-top: 30rpx;">
<view class="center" @click="close"
style="height: 80rpx; flex: 1; background-color: #9F9F9F; border-radius: 10rpx;">
<text style="font-size: 24rpx; font-weight: 600; color: white;">关闭</text>
</view>
</view>
</view>
</uni-popup>
</template>
@ -150,7 +149,9 @@ const participantList = computed(() => {
var isAddhandleJoin = ref(0);
//
const show = (item) => {
console.log("itemitemitemitem", item)
reservationData.value = item || {}
isAddhandleJoin.value = 0;
if (userInfo.value != null) {
var _initiatorInfo = item.participants.find(p => p.role === 1);
if (_initiatorInfo != null) {
@ -158,6 +159,12 @@ const show = (item) => {
isAddhandleJoin.value = 2;
}
}
var _joinInfo = item.participants.find(p => p.role === 0);
if (_joinInfo != null) {
if (userInfo.value.id == _joinInfo.user_id) {
isAddhandleJoin.value = 1;
}
}
}
popup.value.open()
}
@ -209,7 +216,24 @@ const getGenderText = (genderLimit) => {
return '不限'
}
}
const getAgeRangeText = (minAge, maxAge) => {
if (minAge == 0 && maxAge == 0) {
return "不限";
}
let s = "";
if (minAge == 0) {
s = "不限";
} else {
s = minAge + "岁";
}
s += " ~ ";
if (maxAge == 0) {
s += "不限";
} else {
s += maxAge + "岁";
}
return s;
}
//
const handleJoin = async () => {
//
@ -424,4 +448,17 @@ defineExpose({
justify-content: center;
align-items: center;
}
.card {
background: #FFFFFF;
box-shadow: 0rpx 0rpx 10rpx 4rpx rgba(0, 0, 0, 0.25);
border-radius: 30rpx 30rpx 30rpx 30rpx;
margin-top: 20rpx;
}
.lable {
font-family: PingFang SC, PingFang SC;
font-size: 22rpx;
}
</style>

View File

@ -2,6 +2,7 @@
<view>
<slot></slot>
<reservation-evaluate ref="_baseEvaluatePop" />
<qiandao-popup ref="_qianDaoPopup" />
<!-- 预约信息弹窗组件 -->
<ReservationPopup ref="_reservationPopup" />
<up-datetime-picker :show="_upDatesTimePicker.show" :filter="_upDatesTimePicker.filter"
@ -16,9 +17,11 @@
import { ref, onMounted, provide } from 'vue'
import ReservationEvaluate from '@/components/com/page/reservation-evaluate.vue'
import ReservationPopup from '@/components/com/index/ReservationPopup.vue'
import QiandaoPopup from '@/components/com/page/qiandao-popup.vue'
import dayjs from 'dayjs';
const _baseEvaluatePop = ref(null)
const _reservationPopup = ref(null)
const _qianDaoPopup = ref(null);
const openEvaluatePop = async (reservation) => {
_baseEvaluatePop.value && _baseEvaluatePop.value.show(reservation)
@ -27,6 +30,10 @@ const openReservationPopup = async (reservation) => {
console.log("openReservationPopup", reservation)
_reservationPopup.value && _reservationPopup.value.show(reservation)
}
const openQianDaoPop = async (reservation) => {
console.log("openQianDaoPop", reservation)
_qianDaoPopup.value && _qianDaoPopup.value.show(reservation)
}
const _upDatesTimePicker = ref({
refId: null,
show: false,
@ -105,7 +112,7 @@ const openUpDatesTimePicker = (value, minDate, title) => {
})
}
defineExpose({ openEvaluatePop, openReservationPopup, openUpDatesTimePicker })
defineExpose({ openEvaluatePop, openReservationPopup, openUpDatesTimePicker,openQianDaoPop })
// onMounted(() => {

View File

@ -0,0 +1,329 @@
<template>
<uni-popup ref="_qiandaoPopupRef" type="center">
<view class="popup-container">
<view class="column popup-inner">
<view class="popup-title">到场确认</view>
<view style="height:40rpx;"></view>
<view class="popup-h1">请确认预约参与者是否已到场</view>
<view v-if="currentEvaluate.length > 0" class="participants-list">
<view v-for="(item, index) in currentEvaluate" :key="index" class="participant-item">
<view class="participant-info">
<image :src="item.avatarImage || '/static/default-avatar.png'" class="participant-avatar"
mode="aspectFill" />
<text class="participant-name">{{ item.userName }}</text>
</view>
<view class="attendance-btn" :class="{ 'attended': item.isAttended }"
@click="toggleAttendance(item, index)">
<text class="attendance-text">
{{ item.isAttended ? '已到场' : '未到场' }}
</text>
</view>
</view>
</view>
</view>
<view style="height: 5rpx;"></view>
<view style="display: flex;justify-content: center;align-items: center;">
<view
style="background: #00AC4E;box-shadow: 0rpx 0rpx 10rpx 2rpx rgba(0,0,0,0.25);border-radius: 16rpx 16rpx 16rpx 16rpx;width: 478rpx;height: 70rpx;display: flex;justify-content: center;align-items: center;"
@click="submit">
<text
style="font-family: PingFang SC, PingFang SC;font-weight: 500;font-size: 28rpx;color: #FFFFFF;font-style: normal;text-transform: none;">提交</text>
</view>
</view>
</view>
</uni-popup>
</template>
<script setup>
import {
ref,
computed
} from 'vue'
import {
sqInterface
} from '@/common/server/interface/sq.js'
import {
showModalConfirm
} from '@/common/utils'
import {
userInfo
} from '@/common/server/user.js'
const _qiandaoPopupRef = ref(null)
const evaluateList = ref([])
const currentReservation = ref(null)
const currentEvaluate = ref([])
//
const isAllAttended = computed(() => {
if (currentEvaluate.value.length === 0) return false
return currentEvaluate.value.every(item => item.isAttended)
})
const show = async (reservation) => {
currentReservation.value = reservation
//
if (reservation && reservation.participants) {
currentEvaluate.value = reservation.participants
.filter(item => item.user_id != userInfo.value.id)
.map(item => ({
...item,
isAttended: item.isAttended || true //
}))
} else {
currentEvaluate.value = []
}
evaluateList.value = []
_qiandaoPopupRef.value.open()
}
const close = () => {
_qiandaoPopupRef.value.close()
}
//
const toggleAttendance = (participant, index) => {
participant.isAttended = !participant.isAttended
console.log('切换签到状态:', participant.userName, participant.isAttended)
}
//
const setAllAttended = () => {
currentEvaluate.value.forEach(item => {
item.isAttended = true
})
console.log('设置全部到场')
}
const submit = async () => {
var isSuccess = await showModalConfirm("签到提示", "确定提交签到数据吗?签到后将无法更改数据!");
if (!isSuccess) {
return;
}
uni.showLoading({
title: '签到提交中...'
})
const payload = {
reservation_id: currentReservation.value.id,
attendeds: currentEvaluate.value.map(item => {
return {
user_id: item.user_id,
isAttended: item.isAttended
}
})
}
console.log('签到数据:', payload)
const result = await sqInterface.checkInReservation(payload)
uni.hideLoading()
if (result.success) {
uni.showToast({
title: result.message || '签到提交成功',
icon: 'success'
})
close()
} else {
uni.showToast({
title: result.message || '签到提交失败',
icon: 'none'
})
}
}
defineExpose({
show,
close
})
</script>
<style lang="scss" scoped>
.popup-container {
width: 700rpx;
background-color: #FFFFFF;
border-radius: 20rpx;
height: 830rpx;
}
.popup-inner {
width: 90%;
height: 700rpx;
margin: 0 auto 0;
}
.popup-title {
font-size: 26rpx;
margin-top: 30rpx;
text-align: center;
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 40rpx;
color: #000000;
font-style: normal;
text-transform: none;
}
.popup-h1 {
width: 476rpx;
height: 44rpx;
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 32rpx;
color: #000000;
text-align: left;
font-style: normal;
text-transform: none;
}
.panel-box {
width: 100%;
background-color: #F2F3F5;
border-radius: 10rpx;
padding-top: 20rpx;
padding-bottom: 20rpx;
margin-top: 20rpx;
}
.down-icon {
width: 40rpx;
height: 40rpx;
margin-left: auto;
margin-right: 20rpx;
}
.font-24 {
font-size: 24rpx;
}
.action-row {
margin-top: 50rpx;
height: 80rpx;
color: #FFFFFF;
font-size: 28rpx;
}
.action-btn {
flex: 1;
background-color: #1989FA;
border-radius: 10rpx;
}
.info-tip {
font-size: 24rpx;
text-align: center;
margin-top: 20rpx;
margin-bottom: 20rpx;
}
.row-center {
align-items: center;
}
.mt-20 {
margin-top: 20rpx;
}
.mt-10 {
margin-top: 10rpx;
}
.ml-20 {
margin-left: 20rpx;
}
/* 参与者列表样式 */
.participants-list {
margin-top: 20rpx;
max-height: 300rpx;
overflow-y: auto;
}
.participant-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.participant-item:last-child {
border-bottom: none;
}
.participant-info {
display: flex;
align-items: center;
flex: 1;
}
.participant-avatar {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
margin-right: 20rpx;
}
.participant-name {
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 28rpx;
color: #333333;
}
.attendance-btn {
width: 120rpx;
height: 50rpx;
border-radius: 25rpx;
display: flex;
justify-content: center;
align-items: center;
background-color: #f0f0f0;
border: 2rpx solid #ddd;
transition: all 0.3s ease;
}
.attendance-btn.attended {
background-color: #00AC4E;
border-color: #00AC4E;
}
.attendance-text {
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 24rpx;
color: #666666;
}
.attendance-btn.attended .attendance-text {
color: #FFFFFF;
}
/* 全部到场按钮样式 */
.all-attended-btn {
background: #6BB08A;
box-shadow: 0rpx 0rpx 10rpx 2rpx rgba(0, 0, 0, 0.25);
border-radius: 16rpx;
width: 180rpx;
height: 70rpx;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.3s ease;
}
.all-attended-btn.all-attended {
background: #00AC4E;
}
.all-attended-text {
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 28rpx;
color: #FFFFFF;
font-style: normal;
text-transform: none;
}
</style>

View File

@ -1,12 +1,13 @@
<template>
<uni-popup ref="popupRef" type="center">
<view class="popup-container">
<view class="column popup-inner">
<text class="popup-title">牌友评价</text>
<text class="popup-desc">请对每位牌友进行评价</text>
<view class="row panel-box" v-if="currentEvaluate" @click="index = 0, showPicker = true">
<view class="row panel-box" style="padding-left: 10rpx;" v-if="currentEvaluate"
@click="index = 0, showPicker = true">
{{ currentEvaluate.userName }}
<image src="/static/down.png" class="down-icon" mode=""></image>
</view>
@ -180,6 +181,7 @@ defineExpose({ show, close })
.mt-20 {
margin-top: 20rpx;
}
.mt-10 {
margin-top: 10rpx;
}

View File

@ -20,9 +20,12 @@
<text class="row-text ml-20">已约牌友</text>
<image v-for="(participant, pIndex) in reservation.participants" :key="pIndex"
:src="participant.avatarImage || '/static/default-avatar.png'" class="avatar" mode="aspectFill" />
<view class="center evaluate-btn" v-if="reservation.status == 2" @click.stop="onEvaluate()">
<view class="center evaluate-btn" v-if="reservation.status == 3" @click.stop="onEvaluate()">
<text class="evaluate-btn-text">牌友评价</text>
</view>
<view class="center evaluate-btn" v-else-if="isQianDaoVisible" @click.stop="onQianDao()">
<text class="evaluate-btn-text">签到</text>
</view>
</view>
<view class="mt-20" style="height: 10rpx;"></view>
</view>
@ -33,7 +36,7 @@
<script setup>
//
import { inject } from 'vue'
import { inject, computed } from 'vue'
const props = defineProps({
reservation: {
type: Object,
@ -41,11 +44,59 @@ const props = defineProps({
}
})
const emit = defineEmits(['evaluate', 'click'])
const onEvaluate = () => {
emit('evaluate', props.reservation)
// openEvaluatePop?.(props.reservation)
}
const onQianDao = () => {
console.log(" props.reservation", props.reservation);
emit('qianDao', props.reservation)
// TODO:
}
//
const isQianDaoVisible = computed(() => {
const item = props.reservation;
console.log("计算签到按钮显示状态", item);
// 1-2-
if (item.status !== 1) {
return false;
}
// role1
if (item.role !== 1) {
return false;
}
//
if (!item.start_time) {
return false;
}
//
if (!item.end_time) {
return false;
}
const now = new Date();
const startTime = new Date(item.start_time);
const endTime = new Date(item.end_time);
const tenMinutes = 10 * 60 * 1000; // 10
//
const timeToStart = startTime.getTime() - now.getTime();
const timeToEnd = endTime.getTime() - now.getTime();
//
// 1. 10
// 2.
// 3.
const canSignBeforeStart = timeToStart <= tenMinutes && timeToStart > 0; // 10
const canSignDuringEvent = timeToStart <= 0 && timeToEnd > 0; //
return canSignBeforeStart || canSignDuringEvent;
})
const formatTimeRange = (startTime, endTime) => {
if (!startTime || !endTime) return '时间未设置'
@ -57,11 +108,11 @@ const formatTimeRange = (startTime, endTime) => {
}
const getStatusText = (status) => {
const map = { 0: '待开始', 1: '进行中', 2: '已结束', 3: "已取消" }
const map = { 0: '组局中', 1: '待开始', 2: '进行中', 3: "已结束", 4: "已取消" }
return map[status] || '未知状态'
}
const getStatusStyle = (status) => {
const map = { 0: 'color: #1989FA', 1: 'color: #999', 2: 'color: #999', 3: 'color: #999' }
const map = { 0: 'color: #999', 1: 'color: #1989FA', 2: 'color: #1989FA', 3: 'color: #999', 4: 'color: #999' }
return map[status] || 'color: #999'
}
const openReservationPopup = () => {

View File

@ -137,13 +137,13 @@ const statusName = computed(() => {
const count = joinPerson.length
if (status === 0) {
return personCount === count ? "待开始" : "组局中..."
return "组局中..."
} else if (status === 1) {
return "进行中"
return "待开始"
} else if (status === 2) {
return "已结束"
return "进行中"
} else if (status === 3) {
return "取消"
return "已结束"
}
return "其它"
})

View File

@ -1,6 +1,6 @@
{
"name" : "mahjong_group",
"appid" : "__UNI__60D9924",
"appid" : "__UNI__6A23109",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",

View File

@ -85,6 +85,18 @@
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "pages/other/payment-records",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/other/faq",
"style": {
"navigationStyle": "custom"
}
}
],
"globalStyle": {

View File

@ -145,7 +145,7 @@ import TagSelect from '@/components/com/appointment/tag-select.vue'
import ComAppointmentPickerData from '@/components/com/appointment/picker-data.vue'
import ComAppointmentRadioSelect from '@/components/com/appointment/radio-select.vue'
import {
getReservationRoomList, addSQReservation, cancelReservation
getReservationRoomList, addSQReservation, cancelReservation, canCreateSQReservation
} from '@/common/server/interface/sq'
const _containerBase = ref(null)
//
@ -497,6 +497,23 @@ const submitReservation = async () => {
//
submitData.game_type = getGameTypeText(submitData.game_type);
submitData.game_rule = getGameRuleText(submitData.game_rule);
var important_data = "";
if (subscribeMessage.result != null) {
important_data = JSON.stringify(subscribeMessage.result);
}
submitData.important_data = important_data;
//
const canCreateRes = await canCreateSQReservation(submitData)
if (!canCreateRes || canCreateRes.canCreate !== true) {
uni.hideLoading()
uni.showToast({
title: (canCreateRes && canCreateRes.message) ? canCreateRes.message : '当前条件不可创建预约',
icon: 'none'
})
return
}
var resPay = null;
if (submitData.deposit_fee > 0) {
@ -509,13 +526,12 @@ const submitReservation = async () => {
return;
}
subscribeMessage.result.paymentId = resPay.paymentId;
if (subscribeMessage.result != null) {
important_data = JSON.stringify(subscribeMessage.result);
}
submitData.important_data = important_data;
}
console.log('提交预约数据:', submitData)
var important_data = "";
if (subscribeMessage.result != null) {
important_data = JSON.stringify(subscribeMessage.result);
}
submitData.important_data = important_data;
// TODO: API
// API
const result = await addSQReservation(submitData)

View File

@ -183,7 +183,9 @@ const queryList = (pageNo, pageSize) => {
//
const refreshData = () => {
pagePaging.value.reload()
if (pagePaging.value != null) {
pagePaging.value.reload()
}
}
//
@ -226,7 +228,9 @@ onMounted(() => {
//
})
onShow(async () => {
refreshData();
});
onLoad(async () => {
if (!homeData.value) preloadHomeData();
if (!configData.value) preloadConfigData();

View File

@ -44,7 +44,7 @@
<view class="column " style="width: 90%;margin: 0 auto;" v-for="(item, index) in myUseReservation"
:key="index">
<reservation-item :reservation="item" @evaluate="_containerBase.openEvaluatePop"
@click="_containerBase.openReservationPopup" />
@click="_containerBase.openReservationPopup" @qianDao="_containerBase.openQianDaoPop" />
</view>
</view>
<view v-if="userInfo != null" class="column"
@ -99,128 +99,19 @@
<text style="font-size: 30rpx; margin-top: 26rpx; margin-left: 36rpx;">常用功能</text>
<view class="row" style=" justify-content: space-between; margin: 40rpx;">
<view class="column" @click="toAppointment()" style="align-items: center;">
<image src="@@:app/static/appoin_record.png" style="width: 50rpx; height: 50rpx;" mode="">
</image>
<text style="font-size: 24rpx; margin-top: 20rpx;">预约记录</text>
<view class="row" style="flex-wrap: wrap; justify-content: flex-start; margin: 40rpx;">
<view class="column" v-for="item in commonActions" :key="item.label"
@click="handleCommonAction(item)"
style="width: 25%; align-items: center; margin-bottom: 30rpx;">
<image :src="item.icon" style="width: 50rpx; height: 50rpx;" mode=""></image>
<text style="font-size: 24rpx; margin-top: 20rpx;">{{ item.label }}</text>
</view>
<view class="column" style="align-items: center;">
<image src="@@:app/static/problem.png" style="width: 50rpx; height: 50rpx;" mode=""></image>
<text style="font-size: 24rpx; margin-top: 20rpx;">常见问题</text>
</view>
<view class="column" style="align-items: center;">
<image src="@@:app/static/customer_s.png" style="width: 50rpx; height: 50rpx;" mode=""></image>
<text style="font-size: 24rpx; margin-top: 20rpx;">联系我们</text>
</view>
<view class="column" @click="toBlacklist()" style="align-items: center;">
<image src="@@:app/static/blacklist.png" style="width: 50rpx; height: 50rpx;" mode=""></image>
<text style="font-size: 24rpx; margin-top: 20rpx;">黑名单</text>
</view>
</view>
</view>
</view>
</container-base>
<uni-popup ref="infoPop" type="center">
<view class="column center"
style="width: 680rpx; background-color: white; border-radius: 10rpx; padding: 20rpx;">
<text style="">预约信息</text>
<view class="column"
style="width: 100%; height: 180rpx; background-color: #E3E2E2; margin-top: 20rpx; border-radius: 10rpx;">
<view class="row" style="margin-top: 20rpx; align-items: center; margin-left: 20rpx;">
<text style="font-size: 22rpx;">发起者</text>
<image :src="appointmentDetail.organizer.avatar"
style="width: 50rpx; height: 50rpx; background-color: antiquewhite; border-radius: 50%; margin-left: 20rpx;"
mode=""></image>
<text style="font-size: 20rpx; margin-left: 15rpx;">{{ appointmentDetail.organizer.name }}</text>
</view>
<view class="row" style="margin-top: 30rpx; align-items: center; margin-left: 20rpx;">
<text style="font-size: 22rpx;">参与者</text>
<view class="row" v-for="(participant, index) in appointmentDetail.participants" :key="index"
style="align-items: center;">
<view class="" style="position: relative; width: 50rpx; height: 50rpx; display: flex;">
<image :src="participant.avatar" @click="openUserPop()"
style="width: 50rpx; height: 50rpx; background-color: antiquewhite; border-radius: 50%; margin-left: 20rpx; position: absolute;"
mode=""></image>
<view v-if="participant.isBlacklisted" class="center"
style="width: 50rpx; height: 20rpx; background-color: #FFB7B7; position: absolute; left: 40%; top: -10rpx;">
<text style="font-size: 10rpx;">黑名单</text>
</view>
</view>
<text
style="font-size: 20rpx; margin-left: 30rpx; width: 100rpx; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">{{
participant.name }}</text>
</view>
</view>
</view>
<view class="column"
style="width: 100%; height: 180rpx; background-color: #E3E2E2; margin-top: 20rpx; border-radius: 10rpx;font-size: 22rpx;">
<view class="row" style="justify-content: space-between; margin: 20rpx;">
<text>开始时间</text>
<text>{{ appointmentDetail.timeInfo.startTime }}</text>
</view>
<view class="row" style="justify-content: space-between; margin: 0 20rpx;">
<text>结束时间</text>
<text>{{ appointmentDetail.timeInfo.endTime }}</text>
</view>
<text style="margin: 30rpx 20rpx 0;">合计{{ appointmentDetail.timeInfo.duration }}</text>
</view>
<view class="column"
style="width: 100%; background-color: #E3E2E2; margin-top: 20rpx; border-radius: 10rpx; font-size: 22rpx;">
<text style="margin: 20rpx 20rpx 10rpx; font-size: 24rpx;">房间号{{ appointmentDetail.roomInfo.roomNumber
}}{{ appointmentDetail.roomInfo.price }}</text>
<text style="margin: 10rpx 20rpx;">人数{{ appointmentDetail.roomInfo.playerCount }}</text>
<text style="margin: 10rpx 20rpx;">玩法类型{{ appointmentDetail.roomInfo.gameType }}</text>
<text style="margin: 10rpx 20rpx;">具体规则{{ appointmentDetail.roomInfo.rules }}</text>
<text style="margin: 10rpx 20rpx 20rpx;">补充信息{{ appointmentDetail.roomInfo.additionalInfo }}</text>
</view>
<view class="column"
style="width: 100%; background-color: #E3E2E2; margin-top: 20rpx; border-radius: 10rpx; font-size: 22rpx;">
<text style="margin: 20rpx 20rpx 10rpx; font-size: 24rpx;">是否禁烟{{
appointmentDetail.requirements.smoking
}}</text>
<text style="margin: 10rpx 20rpx;">性别{{ appointmentDetail.requirements.gender }}</text>
<text style="margin: 10rpx 20rpx 20rpx;">信誉{{ appointmentDetail.requirements.reputation }}</text>
</view>
<view class="column"
style="width: 100%; background-color: #E3E2E2; margin-top: 20rpx; border-radius: 10rpx; font-size: 22rpx;">
<text style="margin: 20rpx 20rpx 10rpx; font-size: 24rpx;">鸽子费{{ appointmentDetail.pigeonFee }}</text>
<text
style="margin: 10rpx 20rpx 20rpx; color: #9F9F9F;">组局成功后若有牌友未赴约其鸽子费平均分给其他牌友组局成功或失败后鸽子费将全额返还</text>
</view>
<view class="row" style="width: 100%; margin-top: 30rpx;">
<view class="center" @click="clasePop()"
style="height: 80rpx; flex: 1; background-color: #9F9F9F; border-radius: 10rpx;">
<text style="font-size: 24rpx; font-weight: 600; color: white;">关闭</text>
</view>
</view>
</view>
</uni-popup>
</template>
@ -231,6 +122,7 @@ import {
loadUserInfo,
isLogin
} from '@/common/server/user'
import { navigateToAgreement } from '@/common/system/router'
import { sqInterface } from '@/common/server/interface/sq'
import ReservationEvaluate from '@/components/com/page/reservation-evaluate.vue'
import ContainerBase from '@/components/com/page/container-base.vue'
@ -242,7 +134,6 @@ import {
import ReservationItem from '@/components/com/page/reservation-item.vue'
//
const rateValue = ref(4.5)
const infoPop = ref(null)
const loading = ref(false)
const _containerBase = ref(null)
@ -250,61 +141,58 @@ const _containerBase = ref(null)
//
const currentAppointment = ref(null)
//
const appointmentDetail = reactive({
organizer: {
name: '苏家辉',
avatar: '',
isBlacklisted: false
},
participants: [{
name: '树下的胖子',
avatar: '',
isBlacklisted: true
},
{
name: '张三',
avatar: '',
isBlacklisted: false
},
{
name: '李四',
avatar: '',
isBlacklisted: false
// -
const commonActions = [
{ label: '预约记录', icon: '@@:app/static/me/wodd.png', action: 'appointment' },
{ label: '订单记录', icon: '@@:app/static/me/wodd.png', action: 'payment' },
{ label: '常见问题', icon: '@@:app/static/me/cjwt.png', action: 'faq' },
{ label: '黑名单', icon: '@@:app/static/me/hmd.png', action: 'blacklist' },
{ label: '联系我们', icon: '@@:app/static/me/lxwm.png', action: 'contact' },
]
const handleCommonAction = (item) => {
switch (item.action) {
case 'appointment':
toAppointment();
break;
case 'blacklist':
toBlacklist();
break;
case 'faq':
if (userInfo.value == null) {
uni.navigateTo({
url: '/pages/me/login'
});
return;
}
uni.navigateTo({
url: '/pages/other/faq'
});
break;
case 'payment':
if (userInfo.value == null) {
uni.navigateTo({
url: '/pages/me/login'
});
return;
}
uni.navigateTo({
url: '/pages/other/payment-records'
});
break;
case 'contact':
uni.navigateTo({
url: '/pages/other/agreement?type=about'
});
break;
default:
uni.showToast({ title: '敬请期待', icon: 'none' });
break;
}
],
timeInfo: {
startTime: '2025/08/27 1530',
endTime: '2025/08/27 1730',
duration: '2小时'
},
roomInfo: {
roomNumber: '304号-大包',
price: '30元/小时',
playerCount: 3,
gameType: '扑克',
rules: '斗地主',
additionalInfo: '无'
},
requirements: {
smoking: '禁烟',
gender: '不限',
reputation: '≧4.0'
},
pigeonFee: '0元'
})
//
const openPop = () => {
infoPop.value.open();
}
const clasePop = () => {
infoPop.value.close();
}
const openReservationPopup = (item) => {
console.log("openReservationPopup", item)
// _containerBase.value.openReservationPopup(item)
}
const toAppointment = () => {
@ -380,8 +268,10 @@ const loadCurrentAppointment = async () => {
var res = await sqInterface.getMyUseReservation();
console.log("getMyUseReservation", res);
if (res != null) {
myUseReservation.value = myUseReservation.value.splice(0, myUseReservation.value.length);
myUseReservation.value.push(...res);
// console.log("myUseReservation.value.length", myUseReservation.value.length);
// myUseReservation.value = myUseReservation.value.splice(0, myUseReservation.value.length);
// console.log("myUseReservation.value.length", myUseReservation.value.length);
myUseReservation.value = res;
}
@ -390,19 +280,16 @@ const loadCurrentAppointment = async () => {
}
}
//
onMounted(() => {
loadCurrentAppointment()
})
//
onShow(() => {
onShow(async () => {
//
// getUserInfoData();
loadCurrentAppointment();
await loadUserInfo();
})
onLoad(async () => {
await loadUserInfo();
//loadCurrentAppointment();
});
</script>

View File

@ -1,6 +1,5 @@
<template>
<view class="content column">
<z-paging ref="pagePaging" v-model="repList" @query="queryList" :refresher-enabled="true"
:loading-more-enabled="true" :auto="true" :empty-view-text="'暂无记录'"
:empty-view-img="'@@:app/static/index/no.png'" style="flex: 1; width: 100%; margin-top: 30rpx;">
@ -25,7 +24,7 @@
:key="index">
<view class="column reservation-inner">
<view class="row title title-row">
<view class="title">{{ item.title || '记录' }}</view>reservation-box
<view class="title">{{ item.title || '记录' }}</view>
<view
:class="['value-text', Number(item.reputation_value) >= 0 ? 'value-pos' : 'value-neg']">
{{ formatValue(item.reputation_value) }}

View File

@ -2,19 +2,29 @@
<view>
<com-page-container :title="title" showBack>
<view class="agreement-container">
<rich-text v-if="article != null" :nodes="article.contentBody" class="agreement-content"></rich-text>
<rich-text v-if="article != null" :nodes="safeHtml" class="agreement-content"></rich-text>
</view>
</com-page-container>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ref, reactive, computed } from 'vue'
import { getArticleDetail } from '@/common/server/interface/article'
import { getConfigData } from '@/common/server/config'
let title = ref('')
const article = ref(null)
let configData = null;
// rich-text
const safeHtml = computed(() => {
const html = article.value?.contentBody || ''
return html
.replace(/<figure\b[^>]*>/gi, '<div>')
.replace(/<\/figure>/gi, '</div>')
// style img
.replace(/<img(?![^>]*\bstyle=)([^>]*?)>/gi, '<img$1 style="max-width:100%;height:auto;display:block;">')
})
onLoad(async (option) => {
console.log(option);
if (configData == null) {
@ -24,11 +34,14 @@ onLoad(async (option) => {
if (option.type != null) {
if (option.type == 'userAgreement') {
title = "用户协议";
title.value = "用户协议";
article.value = await getArticleDetail(configData.config.userAgreementId)
} else if (option.type == 'privacyPolicy') {
title = "隐私协议";
title.value = "隐私协议";
article.value = await getArticleDetail(configData.config.privacyPolicyId)
} else if (option.type == 'about') {
title.value = "关于我们";
article.value = await getArticleDetail(configData.config.aboutArticleId)
}
}
})

232
pages/other/faq.vue Normal file
View File

@ -0,0 +1,232 @@
<template>
<view class="content column">
<view class="row header-row" :style="{ marginTop: statusBarHeight + 'px' }">
<image src="/static/back.png" class="back-icon" @click="goBack()" mode=""></image>
<text class="page-title">常见问题</text>
<view class="spacer-40"></view>
</view>
<view class="list-wrapper">
<view v-if="loading" class="tip-text">加载中...</view>
<view v-else>
<view v-if="errorMsg" class="tip-text">{{ errorMsg }}</view>
<template v-else>
<view v-if="commonList.length" class="section">
<view class="section-title">常见问题</view>
<view class="column list-inner">
<view class="reservation-item reservation-box" v-for="(item, index) in commonList" :key="item.id || index">
<view class="column reservation-inner">
<view class="row title title-row">
<view class="title">{{ item.title }}</view>
</view>
<view class="row row-text row-center mt-20">
<text class="ml-20">{{ item.description }}</text>
</view>
</view>
</view>
</view>
</view>
<view v-if="serviceList.length" class="section">
<view class="section-title">服务问题</view>
<view class="column list-inner">
<view class="reservation-item reservation-box" v-for="(item, index) in serviceList" :key="item.id || index">
<view class="column reservation-inner">
<view class="row title title-row">
<view class="title">{{ item.title }}</view>
</view>
<view class="row row-text row-center mt-20">
<text class="ml-20">{{ item.description }}</text>
</view>
</view>
</view>
</view>
</view>
<view v-if="deliveryList.length" class="section">
<view class="section-title">订单问题</view>
<view class="column list-inner">
<view class="reservation-item reservation-box" v-for="(item, index) in deliveryList" :key="item.id || index">
<view class="column reservation-inner">
<view class="row title title-row">
<view class="title">{{ item.title }}</view>
</view>
<view class="row row-text row-center mt-20">
<text class="ml-20">{{ item.description }}</text>
</view>
</view>
</view>
</view>
</view>
<view v-if="!commonList.length && !serviceList.length && !deliveryList.length" class="tip-text">暂无内容</view>
</template>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getServiceDescription } from '@/common/server/interface/common.js'
const statusBarHeight = ref(uni.getSystemInfoSync().statusBarHeight);
const loading = ref(true)
const errorMsg = ref('')
const commonList = ref([])
const serviceList = ref([])
const deliveryList = ref([])
const fetchData = async () => {
try {
loading.value = true
errorMsg.value = ''
const data = await getServiceDescription()
if (data) {
const filterSort = (arr = []) => arr
.filter(i => i && i.isShow)
.sort((a, b) => Number(a.sortId || 0) - Number(b.sortId || 0))
commonList.value = filterSort(data.commonQuestion || [])
serviceList.value = filterSort(data.service || [])
deliveryList.value = filterSort(data.delivery || [])
} else {
errorMsg.value = '获取失败'
}
} catch (e) {
console.error('获取服务与说明失败:', e)
errorMsg.value = '获取失败'
} finally {
loading.value = false
}
}
onMounted(fetchData)
const goBack = () => {
uni.navigateBack({
delta: 1
})
}
</script>
<style lang="scss" scoped>
.content {
width: 100%;
height: 100vh;
background: #F7F7F7;
}
.header-row {
width: 90%;
margin: 100rpx auto 0;
justify-content: space-between;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.page-title {
font-size: 30rpx;
}
.spacer-40 {
width: 40rpx;
}
.list-wrapper {
width: 100%;
overflow-y: auto;
margin-top: 30rpx;
}
.list-inner {
width: 90%;
margin: 0 auto 0;
font-size: 24rpx;
}
.section {
width: 100%;
margin-bottom: 20rpx;
}
.section-title {
width: 90%;
margin: 0 auto 20rpx;
font-size: 28rpx;
color: #322A2A;
font-weight: 600;
}
.title {
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 35rpx;
color: #322A2A;
text-align: left;
font-style: normal;
text-transform: none;
}
.title-row {
justify-content: space-between;
}
.row-text {
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 30rpx;
color: #575757;
text-align: left;
font-style: normal;
text-transform: none;
word-break: break-all;
white-space: pre-wrap;
}
.reservation-item {
background: linear-gradient(180deg, #D7F0DD 0%, #FFFFFF 100%);
box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(0, 0, 0, 0.25);
border-radius: 46rpx 46rpx 46rpx 46rpx;
transition: transform 0.2s;
&:active {
transform: scale(0.98);
}
}
.reservation-box {
width: 100%;
border-radius: 10rpx;
background-color: #F2F3F5;
margin-bottom: 40rpx;
}
.reservation-inner {
width: 95%;
margin: 20rpx auto 20rpx;
}
.row-center {
align-items: center;
}
.mt-20 {
margin-top: 20rpx;
}
.ml-20 {
margin-left: 20rpx;
}
.tip-text {
width: 100%;
text-align: center;
color: #999;
font-size: 26rpx;
margin-top: 60rpx;
}
</style>

View File

@ -0,0 +1,194 @@
<template>
<view class="content column">
<z-paging
ref="pagePaging"
v-model="orderList"
@query="queryList"
:refresher-enabled="true"
:loading-more-enabled="true"
:auto="true"
:empty-view-text="'暂无订单记录'"
:empty-view-img="'@@:app/static/index/no.png'"
style="flex: 1; width: 100%; margin-top: 30rpx;"
>
<template #top>
<view class="row header-row" :style="{ marginTop: statusBarHeight + 'px' }">
<image src="/static/back.png" class="back-icon" @click="goBack()" mode=""></image>
<text class="page-title">订单记录</text>
<view class="spacer-40"></view>
</view>
</template>
<view class="list-wrapper">
<view class="column list-inner">
<view class="reservation-item reservation-box" v-for="(item, index) in orderList" :key="index">
<view class="column reservation-inner">
<view class="row title title-row">
<view class="title">{{ item.title || '订单' }}</view>
<view :class="['status-text', statusClass(item)]">{{ item.is_refund_text || '' }}</view>
</view>
<view class="row row-text row-center mt-20">
<text class="label">订单号</text>
<text class="value">{{ item.paymentId || '' }}</text>
</view>
<view class="row row-text row-center mt-10">
<text class="label">加入时间</text>
<text class="value">{{ item.join_time || '' }}</text>
</view>
<view class="row row-text row-center mt-10" v-if="item.status === 1">
<text class="label">预约状态</text>
<text class="value status-quit">已退出</text>
</view>
</view>
</view>
</view>
</view>
</z-paging>
</view>
</template>
<script setup>
import { ref } from 'vue'
import zPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { sqInterface } from '@/common/server/interface/sq.js'
const statusBarHeight = ref(uni.getSystemInfoSync().statusBarHeight)
const orderList = ref([])
const pagePaging = ref(null)
const queryList = async (pageNo, pageSize) => {
try {
const data = await sqInterface.getPaymentRecords(pageNo, pageSize) || []
pagePaging.value.complete(data)
} catch (error) {
console.error('获取订单记录失败:', error)
pagePaging.value.complete(false)
uni.showToast({
title: '获取订单记录失败',
icon: 'none'
})
}
}
const goBack = () => {
uni.navigateBack({
delta: 1
})
}
const statusClass = (item) => {
switch (Number(item?.is_refund)) {
case 1: return 'status-wait-pay'
case 2: return 'status-paid'
case 3: return 'status-wait-refund'
case 4: return 'status-refunded'
default: return ''
}
}
</script>
<style lang="scss" scoped>
.content {
width: 100%;
height: 100vh;
background: #F7F7F7;
}
.header-row {
width: 90%;
margin: 100rpx auto 0;
justify-content: space-between;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.page-title {
font-size: 30rpx;
}
.spacer-40 {
width: 40rpx;
}
.list-wrapper {
width: 100%;
overflow-y: auto;
margin-top: 30rpx;
}
.list-inner {
width: 90%;
margin: 0 auto 0;
font-size: 24rpx;
}
.title {
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 35rpx;
color: #322A2A;
text-align: left;
font-style: normal;
text-transform: none;
}
.title-row {
justify-content: space-between;
}
.row-text {
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 30rpx;
color: #575757;
text-align: left;
font-style: normal;
text-transform: none;
}
.reservation-item {
background: linear-gradient(180deg, #D7F0DD 0%, #FFFFFF 100%);
box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(0, 0, 0, 0.25);
border-radius: 46rpx 46rpx 46rpx 46rpx;
transition: transform 0.2s;
&:active {
transform: scale(0.98);
}
}
.reservation-box {
width: 100%;
border-radius: 10rpx;
background-color: #F2F3F5;
margin-bottom: 40rpx;
}
.reservation-inner {
width: 95%;
margin: 20rpx auto 20rpx;
}
.row-center {
align-items: center;
}
.mt-10 { margin-top: 10rpx; }
.mt-20 { margin-top: 20rpx; }
.label { color: #888; }
.value { color: #333; }
.status-text { font-size: 26rpx; }
.status-wait-pay { color: #e67e22; }
.status-paid { color: #2ecc71; }
.status-wait-refund { color: #3498db; }
.status-refunded { color: #7f8c8d; }
.status-quit { color: #e74c3c; }
</style>