revert 修改预约相关功能
main
xiaoning 10 hours ago
parent 0067a6a3c1
commit 1bce468198

@ -51,8 +51,8 @@
</view> </view>
</view> </view>
<view class="rcpm"> <view class="rcpm">
<button type="primary" class="orderbtn" @click="openCardSelect(item)">预约</button> <!-- <button type="primary" class="orderbtn" @click="openCardSelect(item)">预约</button>-->
<!-- <button type="primary" class="orderbtn" @click="gotoorder">预约</button>--> <button type="primary" class="orderbtn" @click="gotoorder">预约</button>
</view> </view>
</view> </view>
</view> </view>
@ -73,53 +73,46 @@
</view> </view>
</view> </view>
<!-- 卡片为空提示 --> <view class="course-info">
<view v-if="!memberCards || memberCards.length === 0" class="empty-tip"> <view class="course-row">
无对应会员卡可使用 <text class="course-name">{{ currentCourse.courseName }}</text>
</view> <text class="deduct">
<view v-else> 需扣 {{ currentCourse.deductType === 'count'
<view class="course-info"> ? currentCourse.deductCount + '次'
<view class="course-row"> : '¥' + currentCourse.deductAmount }}
<text class="course-name">{{ item.courseName}}</text> </text>
<text class="deduct" v-if="selectedCard && selectedCard.memberChargeType !='days'">
需扣 {{ selectedCard.memberChargeType === 'total_fee'
? '¥' + selectedCard.chargeFee
: selectedCard.chargeCnt + '次'}}
</text>
</view>
<!-- ✅ 本次使用卡片 -->
<view class="use-card-row">
<text class="use-label">本次使用卡片:</text>
<text class="use-name" :class="{ 'is-default': !isManuallySelected }">
{{ effectiveCardName }}
</text>
<text v-if="!isManuallySelected && effectiveCard" class="default-tag">默认</text>
</view>
</view> </view>
<!-- ✅ 本次使用卡片 -->
<!-- 卡片横向滑动 --> <view class="use-card-row">
<member-card-scroll <text class="use-label">本次使用卡片:</text>
v-if="memberCards && memberCards.length > 0" <text class="use-name" :class="{ 'is-default': !isManuallySelected }">
ref="cardScroll" {{ effectiveCardName }}
:cards="memberCards" </text>
size="small" <text v-if="!isManuallySelected && effectiveCard" class="default-tag">默认</text>
:selectable="true"
:center="false"
:auto-select="false"
:default-card-id="defaultCardId"
@select="onCardSelect"
/>
<view v-if="memberCards && memberCards.length > 0" class="popup-footer">
<button class="confirm-btn" @click="confirmSelect">确认选择</button>
</view> </view>
</view> </view>
<!-- 卡片横向滑动 -->
<member-card-scroll
ref="cardScroll"
:cards="memberCards"
size="small"
:selectable="true"
:center="false"
:auto-select="false"
:default-card-id="defaultCardId"
:deduct-type="currentCourse.deductType"
:deduct-count="currentCourse.deductCount"
:deduct-amount="currentCourse.deductAmount"
@select="onCardSelect"
/>
<view class="popup-footer">
<button class="confirm-btn" @click="confirmSelect">确认选择</button>
</view>
</view> </view>
</uni-popup> </uni-popup>
<u-toast ref="uToast" />
<!-- 是否登录 --> <!-- 是否登录 -->
<openlogin ref="loginId" @getPhoneNumber="getPhoneNumber"></openlogin> <openlogin ref="loginId" @getPhoneNumber="getPhoneNumber"></openlogin>
</view> </view>
@ -142,27 +135,31 @@ export default {
userid: "", userid: "",
id: "", id: "",
item: { item: {
courseTimeId: null, courseTimeId: 1935590320322428931,
claName: "", claName: "普拉提A班",
courseName: "", courseName: "普拉提",
storeName: "", storeName: "慢瑜伽-三台子店",
pic: null, pic: null,
claDate: "", claDate: "2025-10-01",
weekDay: "", weekDay: "星期二",
startTime: "", startTime: "08:00",
endTime: "", endTime: "09:30",
staffName: "", staffName: "冉洪涛老师",
studentCount: null, studentCount: 1,
roomName: null, roomName: null,
claColor: "", claColor: "#409EFF",
claTimeStatus: "", claTimeStatus: "1",
bookAttendCnt: "", bookAttendCnt: "1",
atClassCnt: "", atClassCnt: "5",
lessCnt: "" lessCnt: "3"
}, },
remark: null, remark: null,
memberCards: [], memberCards: [],
currentCourse: { currentCourse: {
courseName: '普拉提A班',
deductType: 'COUNT',
deductCount: 1,
deductAmount: 0
}, },
defaultCardId: null, defaultCardId: null,
// ✅ 用户手动选中的卡(父组件持有) // ✅ 用户手动选中的卡(父组件持有)
@ -213,9 +210,9 @@ export default {
isCardUsable(card) { isCardUsable(card) {
if (!card) return false; if (!card) return false;
if (card.status && card.status !== 'ACTIVE') return false; if (card.status && card.status !== 'ACTIVE') return false;
if (card.expiryDate) { if (card.expireDate) {
const now = new Date(); const now = new Date();
const expire = new Date(card.expiryDate.replace(/-/g, '/')); const expire = new Date(card.expireDate.replace(/-/g, '/'));
if (now > expire) return false; if (now > expire) return false;
} }
const type = this.currentCourse.deductType; const type = this.currentCourse.deductType;
@ -238,19 +235,15 @@ export default {
// ✅ 打开弹窗:重置手动选择,用默认卡 // ✅ 打开弹窗:重置手动选择,用默认卡
async openCardSelect(item) { async openCardSelect(item) {
var user = myCache('userInfo'); var user = myCache('userInfo');
item.visitStore=user.visitStore;
console.log('item',item)
const { data: memberCards } = const { data: memberCards } =
await uni.$http.post("/api/memberCard/getMemberCardListByCourseTimeId", await uni.$http.post("/api/memberCard/getMemberCardListByCourseTimeId",
item); {visitStore:user.visitStore,
courseId:item.courseId
});
console.log("memberCards",memberCards)
//如果为空 则没有适合的卡片 //如果为空 则没有适合的卡片
this.memberCards = memberCards.data this.memberCards = memberCards.data
console.log('this.memberCards',this.memberCards) this.selectedCard = null;
if(this.memberCards&&this.memberCards.length > 0){
this.selectedCard = this.memberCards[0];
}else{
this.selectedCard = null;
}
this.$refs.cardPopup.open(); this.$refs.cardPopup.open();
}, },
@ -259,53 +252,37 @@ export default {
}, },
async confirmSelect() { async confirmSelect() {
// ✅ 直接从父组件 computed 拿有效卡
const card = this.effectiveCard; const card = this.effectiveCard;
if (!card) { if (!card) {
uni.showToast({ title: '无可用会员卡', icon: 'none' }); uni.showToast({ title: '无可用会员卡', icon: 'none' });
return; return;
} }
// 开启loading const { data: res } = await uni.$http.post('/api/course/bookCourse', {
uni.showLoading({ courseTimeId: this.item.courseTimeId,
title: '预约中...', cardId: card.id
mask: true // 遮罩,防止重复点击
}); });
try { if (res && res.success) {
const { data: res } = await uni.$http.post('/api/course/bookCourse', { myCache('courserefresh', 1);
courseTimeId: this.item.courseTimeId, uni.showModal({
memberCardId: card.memberCardId, title: '提示',
memberChargeType: this.selectedCard.memberChargeType, content: '课程已预约!',
chargeCnt: this.selectedCard.chargeCnt, cancelText: '返回',
chargeFee: this.selectedCard.chargeFee confirmText: '确定',
success: ress => {
uni.navigateBack({ delta: 1 });
}
}); });
if (res && res.success) { } else {
myCache('courserefresh', 1); uni.showModal({
this.closeCardSelect(); title: '提示',
this.$refs.uToast.show({ content: res.message ? res.message : '课程预约失败!',
title: '课程预约成功,请到 我的预约-预约中 查看', cancelText: '取消',
type: 'success', confirmText: '确定'
duration: 2000
});
} else {
this.$refs.uToast.show({
title: res.message ? res.message : '课程预约失败!',
type: 'error',
duration: 2000
});
}
} catch (err) {
// 网络异常捕获
this.$refs.uToast.show({
title: '预约请求异常,请稍后重试',
type: 'error',
duration: 2000
}); });
} finally {
// 不管成功失败、报错,都关闭loading
uni.hideLoading();
} }
}, },
formatTime(timeStr) { formatTime(timeStr) {
return timeStr.slice(0, 5); return timeStr.slice(0, 5);
}, },
@ -842,11 +819,5 @@ export default {
border: none; border: none;
&::after { border: none; } &::after { border: none; }
} }
.empty-tip {
padding: 60rpx 20rpx;
text-align: center;
font-size: 28rpx;
color: #999;
}
</style> </style>

@ -23,24 +23,19 @@
</view> </view>
<view class="oscell"> <view class="oscell">
预约状态: 预约状态:
<view v-if="info.bookStatus==1" class="ztip">预约中</view> <view v-if="info.bookStatus==1" class="ztip">教练确认</view>
<view v-else-if="info.bookStatus==0" class="jtip">预约中</view> <view v-else-if="info.bookStatus==0" class="jtip">预约中</view>
<view v-else-if="info.bookStatus==2" class="htip">预约成功,待上课</view> <view v-else-if="info.bookStatus==2" class="htip">待上课</view>
<view v-else-if="info.bookStatus==4" class="wtip">已完成</view> <view v-else-if="info.bookStatus==4" class="wtip">已完成</view>
<view v-else-if="info.bookStatus==3" class="ctip">已取消</view> <view v-else-if="info.bookStatus==3" class="ctip">已取消</view>
</view> </view>
<view class="oscell">
签到状态:
<view v-if="info.checkIn==1" class="ztip">已签到</view>
<view v-else-if="info.checkIn==0" class="jtip">未签到</view>
</view>
<view class="oscell"> <view class="oscell">
授课教练: 授课教练:
<text class="txt">{{info.teacherName }}</text> <text class="txt">{{info.teacherName }}</text>
</view> </view>
<view class="oscell"> <view class="oscell">
上课教室: 上课教室:
<text class="txt">{{info.roomName?info.roomName:'待定'}}</text> <text class="txt">{{info.roomName }}</text>
</view> </view>
<view class="oscell"> <view class="oscell">
上课时间: 上课时间:

@ -19,13 +19,11 @@
<view class="hright"> <view class="hright">
<!-- 1:待上课,2:预约中,3:待评价 ,4:已完成 ,5:已取消 --> <!-- 1:待上课,2:预约中,3:待评价 ,4:已完成 ,5:已取消 -->
<!-- 描述:0->预约中;1->教练确认;2->店长确认/预约成功/待上课 3->已取消;4->已完成/待评价; --> <!-- 描述:0->预约中;1->教练确认;2->店长确认/预约成功/待上课 3->已取消;4->已完成/待评价; -->
<view v-if="item.bookStatus==1" class="ztip">教练确认</view>
<view v-if="item.bookStatus==1" class="ztip">预约中</view>
<view v-else-if="item.bookStatus==0" class="jtip">预约中</view> <view v-else-if="item.bookStatus==0" class="jtip">预约中</view>
<view v-else-if="item.bookStatus==2" class="htip">预约成功,待上课</view> <view v-else-if="item.bookStatus==2" class="htip">待上课</view>
<view v-else-if="item.bookStatus==4" class="wtip">已完成</view> <view v-else-if="item.bookStatus==4" class="wtip">已完成</view>
<view v-else-if="item.bookStatus==3" class="ctip">已取消</view> <view v-else-if="item.bookStatus==3" class="ctip">已取消</view>
<view v-else-if="item.bookStatus==5" class="ctip">预约失败</view>
</view> </view>
</view> </view>
@ -37,8 +35,7 @@
<text class="ntxt">{{item.courseName}} {{item.claName}} </text> <text class="ntxt">{{item.courseName}} {{item.claName}} </text>
<text class="txt"> 教练:{{item.teacherName || '-'}}</text> <text class="txt"> 教练:{{item.teacherName || '-'}}</text>
<text class="txt"> 上课时间:{{ item.claDate+' '+item.startTime+'-'+item.endTime}}</text> <text class="txt"> 上课时间:{{ item.claDate+' '+item.startTime+'-'+item.endTime}}</text>
<text class="txt"> 教室:{{item.roomName?item.roomName:'待定'}}</text> <text class="txt"> 教室:{{item.roomName}}</text>
<text class="txt"> 使用会员卡号:{{item.cardNo}}</text>
</view> </view>
</view> </view>
@ -52,7 +49,7 @@
<!-- 描述:0->预约中;1->教练确认;2->店长确认/预约成功/待上课 3->已取消;4->已完成/待评价; --> <!-- 描述:0->预约中;1->教练确认;2->店长确认/预约成功/待上课 3->已取消;4->已完成/待评价; -->
<button class="conbtn" @click="gototeacher(item)">联系教练</button> <button class="conbtn" @click="gototeacher(item)">联系教练</button>
<!-- <button v-if="item.bookStatus==2||item.bookStatus==1" type="primary" class="cancelbtn" :data-id="item.courseTimeId" @click="cancelkcdo">取消课程</button>--> <!-- <button v-if="item.bookStatus==2||item.bookStatus==1" type="primary" class="cancelbtn" :data-id="item.courseTimeId" @click="cancelkcdo">取消课程</button>-->
<button v-if="'0,1,2'.includes(item.bookStatus) && item.checkIn!=1" class="cancelbtn" :data-id="item.courseTimeId" @click="cancelyydo(item)">取消预约</button> <button v-if="'0,1,2'.includes(item.bookStatus)" class="cancelbtn" :data-id="item.courseTimeId" @click="cancelyydo">取消预约</button>
<!-- <button v-if="item.bookStatus==0" type="primary" class="paybtn" :data-id="item.courseTimeId" @click="checkdo($event,item)">确认上课</button>--> <!-- <button v-if="item.bookStatus==0" type="primary" class="paybtn" :data-id="item.courseTimeId" @click="checkdo($event,item)">确认上课</button>-->
<button v-if="item.bookStatus==4" class="viewbtn" :data-id="item.courseTimeId" @click="orderdo($event,item)">再次预约</button> <button v-if="item.bookStatus==4" class="viewbtn" :data-id="item.courseTimeId" @click="orderdo($event,item)">再次预约</button>
<!-- <button v-if="item.bookStatus==4" type="primary" class="gzbtn" :data-id="item.courseTimeId" @click="gotoevaluate($event,item)">评价</button>--> <!-- <button v-if="item.bookStatus==4" type="primary" class="gzbtn" :data-id="item.courseTimeId" @click="gotoevaluate($event,item)">评价</button>-->
@ -680,7 +677,7 @@ import { mapState, mapGetters, mapActions } from 'vuex';
// 取消预约 // 取消预约
cancelyydo(e){ cancelyydo(e){
console.log(e); console.log(e);
// var id= e.currentTarget.dataset.id; var id= e.currentTarget.dataset.id;
var that = this; var that = this;
//取消预约 //取消预约
uni.showModal({ uni.showModal({
@ -690,14 +687,16 @@ import { mapState, mapGetters, mapActions } from 'vuex';
confirmText: '确定', confirmText: '确定',
success: ress => { success: ress => {
if (ress.confirm) { if (ress.confirm) {
that.delyydo(e); that.delyydo(id);
} }
} }
}); });
}, },
// 取消预约 // 取消预约
async delyydo(e){ async delyydo(id){
var param=e; var param={
"courseTimeId":id
};
uni.showToast({ uni.showToast({
title: '取消中...', title: '取消中...',
icon: 'success', icon: 'success',

@ -12,12 +12,10 @@
<text style="margin-left: 30rpx;">性别:{{item.sex==2?'男':(item.sex==1?'女':'未知')}}</text> <text style="margin-left: 30rpx;">性别:{{item.sex==2?'男':(item.sex==1?'女':'未知')}}</text>
</view> </view>
<text>电话:{{item.phone?item.phone:'-'}}</text> <text>电话:{{item.phone?item.phone:'-'}}</text>
<text>会员卡号:{{item.cardNo?item.cardNo:'-'}}</text>
</view> </view>
<view class="hright"> <view class="hright">
<!-- 1:待上课,2:预约中,3:待评价 ,4:已完成 ,5:已取消 --> <!-- 1:待上课,2:预约中,3:待评价 ,4:已完成 ,5:已取消 -->
<!-- 描述:0->预约中;1->教练确认;2->店长确认/预约成功/待上课 3->已取消;4->已完成/待评价; --> <!-- 描述:0->预约中;1->教练确认;2->店长确认/预约成功/待上课 3->已取消;4->已完成/待评价; -->
<view v-if="item.bookStatus==1" class="ztip">教练确认</view> <view v-if="item.bookStatus==1" class="ztip">教练确认</view>
<view v-else-if="item.bookStatus==0" class="jtip">预约中</view> <view v-else-if="item.bookStatus==0" class="jtip">预约中</view>
<view v-else-if="item.bookStatus==2" class="htip">待上课</view> <view v-else-if="item.bookStatus==2" class="htip">待上课</view>
@ -141,7 +139,6 @@
// 全部 // 全部
this.loadStatus="noMore"; this.loadStatus="noMore";
this.list=res.data; this.list=res.data;
console.log('this.list',this.list)
this.$forceUpdate(); this.$forceUpdate();
} }
else{ else{

@ -8,6 +8,16 @@
<view class="uid">ID:{{ userInfo.id || userInfo.userId || '-' }}</view> <view class="uid">ID:{{ userInfo.id || userInfo.userId || '-' }}</view>
</view> </view>
<!-- 黑名单开关行 仿微信 -->
<!-- <view class="black-row">-->
<!-- <text class="black-label">加入黑名单</text>-->
<!-- <switch-->
<!-- :checked="isBlacked"-->
<!-- active-color="#00a89b"-->
<!-- @change="onBlackSwitchChange"-->
<!-- />-->
<!-- </view>-->
<view class="black-row"> <view class="black-row">
<text class="black-label">加入黑名单</text> <text class="black-label">加入黑名单</text>
<view class="custom-switch" :class="{ on: isBlacked }" @tap="handleSwitchTap"> <view class="custom-switch" :class="{ on: isBlacked }" @tap="handleSwitchTap">
@ -74,7 +84,45 @@ export default {
console.error('查询黑名单状态失败', e) console.error('查询黑名单状态失败', e)
} }
}, },
/**
* switch点击,不直接改状态,弹出确认弹窗
*/
onBlackSwitchChange(e) {
// e.detail.value 是用户想要切换到的目标状态
const targetStatus = e.detail.value
const title = targetStatus ? '加入黑名单' : '取消黑名单'
const content = targetStatus
? '加入黑名单后,对方将无法给你发送消息,确定?'
: '确定将该用户移出黑名单?'
uni.showModal({
title,
content,
success: async (res) => {
if (!res.confirm) {
// 用户点取消,不做任何操作,switch会自动回弹回旧状态,无需手动赋值
return
}
try {
const url = targetStatus ? '/api/blacklist/add?targetUid=' : '/api/blacklist/cancel?targetUid='
await uni.$http.post(`${url}${this.userId}`)
// 接口成功才更新本地状态
this.isBlacked = targetStatus
uni.showToast({
title: this.isBlacked ? '已加入黑名单' : '已取消黑名单',
icon: 'none'
})
} catch (err) {
uni.showToast({ title: '操作失败', icon: 'none' })
}
}
})
},
onSendMessage() { onSendMessage() {
// if (this.isBlacked) {
// uni.showToast({ title: '你已将对方加入黑名单', icon: 'none' })
// return
// }
let chat = { let chat = {
type: 'PRIVATE', type: 'PRIVATE',
targetId: this.userId, targetId: this.userId,
@ -142,7 +190,7 @@ export default {
onBackPress(e) { onBackPress(e) {
// 来源是左上角返回 / 安卓返回键 // 来源是左上角返回 / 安卓返回键
if(e.from === 'backbutton'){ if(e.from === 'backbutton'){
if(this.isBlacked && this.chatIndex){ if(this.isBlacked){
// 拉黑成功,自定义跳转chat // 拉黑成功,自定义跳转chat
this.goBackChat() this.goBackChat()
return true // return true 阻止原生默认返回 return true // return true 阻止原生默认返回

@ -1,7 +1,7 @@
<template> <template>
<view class="member-card-scroll"> <view class="member-card-scroll">
<scroll-view <scroll-view
class="card-scroll-wrap" class="card-scroll"
scroll-x="true" scroll-x="true"
scroll-with-animation scroll-with-animation
:show-scrollbar="false" :show-scrollbar="false"
@ -22,7 +22,7 @@
/> />
<!-- v-if="selectable && !isCardUsable(card)"--> <!-- v-if="selectable && !isCardUsable(card)"-->
<view class="unusable-tip"> <view class="unusable-tip">
<text v-if=" isCardUsable(card)" style="color:#00a89b;" >正常</text> <text v-if=" card.status == 'ACTIVE'" style="color:#00a89b;" >正常</text>
<text v-else style="color: #ff6b6b;">{{ getUnusableReason(card) }}</text> <text v-else style="color: #ff6b6b;">{{ getUnusableReason(card) }}</text>
</view> </view>
</view> </view>
@ -99,20 +99,15 @@ export default {
}, },
// 动态计算首尾 padding // 动态计算首尾 padding
innerStyle() { innerStyle() {
// ✅ 只要卡片总数=1,不管外部center参数传啥,自动触发居中计算 if (!this.center) {
const isSingleCard = this.cards && this.cards.length === 1;
if (!this.center && !isSingleCard) {
// 多卡正常:保留原来的20rpx边距
return { paddingLeft: '20rpx', paddingRight: '20rpx' }; return { paddingLeft: '20rpx', paddingRight: '20rpx' };
} }
// 居中计算:左右padding = (屏幕宽度750rpx - 卡片宽度) / 2,刚好把卡片推到正中间
const padding = Math.max(0, (this.screenWidthRpx - this.cardWidthRpx) / 2); const padding = Math.max(0, (this.screenWidthRpx - this.cardWidthRpx) / 2);
return { return {
paddingLeft: padding-30 + 'rpx', paddingLeft: padding + 'rpx',
paddingRight: padding + 'rpx' paddingRight: padding + 'rpx'
}; };
} }
}, },
watch: { watch: {
cards: { cards: {
@ -188,9 +183,9 @@ export default {
if (!card) return false; if (!card) return false;
// ✅ 只有 ACTIVE 才可用 // ✅ 只有 ACTIVE 才可用
if (card.status && card.status !== 'ACTIVE') return false; if (card.status && card.status !== 'ACTIVE') return false;
if (card.expiryDate) { if (card.expireDate) {
const now = new Date(); const now = new Date();
const expire = new Date(card.expiryDate.replace(/-/g, '/')); const expire = new Date(card.expireDate.replace(/-/g, '/'));
if (now > expire) return false; if (now > expire) return false;
} }
if (card.type === 'count') { if (card.type === 'count') {
@ -219,9 +214,9 @@ export default {
if (card.status && card.status == 'DEPLETED') { if (card.status && card.status == 'DEPLETED') {
return '卡片额度已用完'; return '卡片额度已用完';
} }
if (card.expiryDate) { if (card.expireDate) {
const now = new Date(); const now = new Date();
const expire = new Date(card.expiryDate.replace(/-/g, '/')); const expire = new Date(card.expireDate.replace(/-/g, '/'));
if (now > expire) return '卡片已过期'; if (now > expire) return '卡片已过期';
} }
if (card.type === 'count') { if (card.type === 'count') {
@ -304,17 +299,4 @@ export default {
background: #00a89b; background: #00a89b;
} }
} }
.card-scroll-wrap {
width: 100%;
overflow-x: auto;
padding: 20rpx 0;
/* 多卡时默认左对齐横向滚动 */
display: flex;
gap: 20rpx;
}
</style> </style>

@ -21,28 +21,22 @@
<view class="card-middle"> <view class="card-middle">
<view class="balance-box"> <view class="balance-box">
<text class="balance-label"> <text class="balance-label">
{{ card.chargeType === 'count' ? '剩余次数' : card.chargeType ==='days'?'剩余天数':'可用余额' }} {{ card.chargeType === 'count' ? '剩余次数' : card.chargeType ==='days'?'剩余天数':'卡内余额' }}
</text> </text>
<view class="balance-value-box"> <view class="balance-value-box">
<text v-if="card.chargeType == 'total_fee'" class="unit">¥</text> <text v-if="card.chargeType == 'total_fee'" class="unit">¥</text>
<text class="balance-value"> <text class="balance-value">
{{ {{ card.chargeType === 'count' ? (card.remainingCount || 0) : card.chargeType ==='days'?card.remainingDays: formatAmount(card.remainingTotalFee) }}
card.chargeType === 'count'
? Math.max((card.remainingCount || 0) - (card.freezeCount || 0), 0)
: card.chargeType === 'days'
? (card.remainingDays || 0)
: formatAmount( Math.max( (Number(card.remainingTotalFee)||0) - (Number(card.freezeAmount)||0), 0 ) )
}}
</text> </text>
<text v-if="card.chargeType === 'count'" class="unit">次</text> <text v-if="card.chargeType === 'count'" class="unit">次</text>
<text v-if="card.chargeType === 'days'" class="unit">天</text> <text v-if="card.chargeType === 'days'" class="unit">天</text>
</view> </view>
</view> </view>
<!-- 冻结信息(可选) --> <!-- 冻结信息(可选) -->
<view v-if="card.chargeType !== 'days' || card.freezeCount > 0 || card.freezeAmount > 0" class="frozen-box"> <view v-if="card.chargeType !== 'days' || card.frozenCount > 0 || card.frozenAmount > 0" class="frozen-box">
<text class="frozen-label">预约占用</text> <text class="frozen-label">预约占用</text>
<text class="frozen-value"> <text class="frozen-value">
{{ card.type === 'count' ? card.freezeCount + '次' : '¥' + formatAmount(card.freezeAmount) }} {{ card.type === 'count' ? card.frozenCount + '次' : '¥' + formatAmount(card.frozenAmount) }}
</text> </text>
</view> </view>
</view> </view>
@ -51,7 +45,7 @@
<view class="card-bottom"> <view class="card-bottom">
<text class="card-no">NO. {{ card.cardNo || '--' }}</text> <text class="card-no">NO. {{ card.cardNo || '--' }}</text>
<text class="card-expire"> <text class="card-expire">
{{ card.expiryDate ? '有效期至 ' + card.expiryDate : '长期有效' }} {{ card.expireDate ? '有效期至 ' + card.expireDate : '长期有效' }}
</text> </text>
</view> </view>
</view> </view>

@ -74,7 +74,7 @@
</view> </view>
<view class="info-row"> <view class="info-row">
<text class="info-label">有效期至</text> <text class="info-label">有效期至</text>
<text class="info-value">{{ card.expiryDate || '长期有效' }}</text> <text class="info-value">{{ card.expireDate || '长期有效' }}</text>
</view> </view>
<view class="info-row"> <view class="info-row">
<text class="info-label">状态</text> <text class="info-label">状态</text>

@ -56,12 +56,11 @@
<!-- <view v-if="item.claName=='团课'" class="txtbg">{{item.claName}}</view> <!-- <view v-if="item.claName=='团课'" class="txtbg">{{item.claName}}</view>
<view v-else class="txtbg1">{{item.claName}}</view> --> <view v-else class="txtbg1">{{item.claName}}</view> -->
<!-- <view v-if="item.claName" class="txtbg1">{{item.claName}}</view>--> <!-- <view v-if="item.claName" class="txtbg1">{{item.claName}}</view>-->
<uni-rate :size="15" :readonly="true" :value="item.star?item.star:0" :max="5"/>
</view> </view>
<view class="oscell" v-if="item.storeName"> <view class="oscell" v-if="item.storeName">
<uni-icons type="paperclip" size="18" color="#00A99A"></uni-icons> <uni-icons type="paperclip" size="18" color="#00A99A"></uni-icons>
<view class="ntip" style="margin-right: 10rpx;">{{item.courseName}}</view> <view class="ntip">{{item.courseName}}</view>
<uni-rate :size="15" :readonly="true" :value="item.star?item.star:0" :max="5"/>
</view> </view>
<view class="oscell"> <view class="oscell">
<uni-icons type="calendar" size="18" color="#00A99A"></uni-icons> <uni-icons type="calendar" size="18" color="#00A99A"></uni-icons>
@ -75,7 +74,7 @@
</view> </view>
<view class="oscell"> <view class="oscell">
<uni-icons type="map-pin-ellipse" size="18" color="#00A99A"></uni-icons> <uni-icons type="map-pin-ellipse" size="18" color="#00A99A"></uni-icons>
<view class="ntxt">{{item.roomName?item.roomName:'教室待定'}}</view> <view class="ntxt">{{item.roomName}}</view>
</view> </view>
<view class="oscell"> <view class="oscell">
<uni-icons type="checkbox" size="18" color="#00A99A"></uni-icons> <uni-icons type="checkbox" size="18" color="#00A99A"></uni-icons>

@ -8,11 +8,10 @@
<view class="otop"> <view class="otop">
<view class="hleft"> <view class="hleft">
{{item.courseName}} &nbsp; {{item.courseName}}
<uni-rate :size="20" :readonly="true" :value="item.star" :max="item.star"/>
</view> </view>
<view class="hright"> <view class="hright">
{{formatTime(item.startTime)+'-'+formatTime(item.endTime)}} {{item.startTime+'-'+item.endTime}}
</view> </view>
</view> </view>
@ -22,10 +21,9 @@
</view> </view>
<view class="osname"> <view class="osname">
<view class="oscell"> <view class="oscell">
{{item.courseTypeName}}
<view v-if="item.courseTypeName=='团课'" class="txtbg">{{item.courseTypeName}}</view> <view v-if="item.courseTypeName=='团课'" class="txtbg">{{item.courseTypeName}}</view>
<view v-else-if="item.courseTypeName" class="txtbg1">{{item.courseTypeName}}</view> <view v-else-if="item.courseTypeName" class="txtbg1">{{item.courseTypeName}}</view>
<uni-rate :size="20" :readonly="true" :value="item.star" :max="item.star"/>
</view> </view>
<view class="oscell" v-if="item.storeName"> <view class="oscell" v-if="item.storeName">
<uni-icons type="shop" size="18" color="#00A99A"></uni-icons> <uni-icons type="shop" size="18" color="#00A99A"></uni-icons>
@ -51,17 +49,12 @@
</view> </view>
<view class="oscell" v-if="item.lessCnt"> <view class="oscell" v-if="item.lessCnt">
<uni-icons type="staff" size="18" color="#00A99A"></uni-icons> <uni-icons type="staff" size="18" color="#00A99A"></uni-icons>
<view class="ntxt" >满{{item.lessCnt}}人开课</view> <view class="ntxt" >预约人数{{item.lessCnt}}人</view>
</view> </view>
<view class="oscell" v-if="item.claTimeStatus">
<uni-icons type="wallet" size="18" color="#00A99A"></uni-icons>
<view class="ntxt" v-if="item.claTimeStatus=='1'"> 未上课 </view>
<view class="ntxt" v-else>已确认上课 </view>
</view>
</view> </view>
</view> </view>
<view class="rcpm" > <view class="rcpm">
<button type="primary" class="orderbtn" v-if="item.claTimeStatus=='1'" @click="gotoorder">确认上课</button> <button type="primary" class="orderbtn" @click="gotoorder">确认上课</button>
</view> </view>
</view> </view>
</view> </view>
@ -69,16 +62,11 @@
<!-- 信息 --> <!-- 信息 -->
<view class="yycon tcon"> <view class="yycon tcon">
<view class="stitle">学员名单</view> <view class="stitle">学员名单</view>
<view class="batch-bar" v-if="checkableList.length > 0">
<view class="batch-tip">可签到 {{ checkableList.length }} 人</view>
<button class="batch-btn" @click="openBatch">批量签到</button>
</view>
<view class="hcon"> <view class="hcon">
<view class="hcell">学员姓名</view> <view class="hcell">学员姓名</view>
<view class="hcell">性别</view> <view class="hcell">性别</view>
<view class="hcell">电话</view> <view class="hcell">电话</view>
<view class="hcell">预约状态</view> <view class="hcell">预约状态</view>
<view class="hcell">是否签到</view>
</view> </view>
<view class="stcell" v-if="studentlist.length>0"> <view class="stcell" v-if="studentlist.length>0">
<view v-for="(item, index) in studentlist" :key="index" class="oitem"> <view v-for="(item, index) in studentlist" :key="index" class="oitem">
@ -86,7 +74,7 @@
{{item.studentName}} {{item.studentName}}
</view> </view>
<view class="ocell"> <view class="ocell">
{{item.sex==2?'男':(item.sex==1?'女':'未知')}} {{item.sex=1?'女':(item.sex=2?'男':'未知')}}
</view> </view>
<view class="ocell"> <view class="ocell">
{{item.phone}} {{item.phone}}
@ -94,65 +82,12 @@
<view class="ocell"> <view class="ocell">
{{ retstr(item.bookStatus)}} {{ retstr(item.bookStatus)}}
</view> </view>
<view class="ocell">
{{ item.checkIn=='1'?'已签到':'未签到'}}
</view>
</view> </view>
</view> </view>
<uni-load-more v-if="studentlist.length<1" iconType="circle" :status="loadStatus" /> <uni-load-more v-if="studentlist.length<1" iconType="circle" :status="loadStatus" />
</view> </view>
</view> </view>
<!-- 批量签到弹窗 -->
<view class="batch-mask" v-if="batchVisible" @click="closeBatch">
<view class="batch-dialog" @click.stop>
<view class="bd-title">批量签到</view>
<view class="bd-sub">已选 {{ selectedIds.length }} / {{ checkableList.length }} 人</view>
<!-- 全选 -->
<view class="bd-all" @click="toggleAll">
<view class="cb" :class="{ checked: isAllSelected }">
<text v-if="isAllSelected" class="cb-icon">✓</text>
</view>
<view class="bd-info">
<view class="bd-name">全选</view>
</view>
</view>
<!-- 可签到名单 -->
<scroll-view scroll-y class="bd-list">
<view
v-for="stu in checkableList"
:key="getBookId(stu)"
class="bd-item"
@click="toggleOne(stu)"
>
<view class="cb" :class="{ checked: isChecked(stu) }">
<text v-if="isChecked(stu)" class="cb-icon">✓</text>
</view>
<view class="bd-info">
<view class="bd-name">{{ stu.studentName }}</view>
<view class="bd-meta">{{ stu.phone }} · {{ retstr(stu.bookStatus) }}</view>
</view>
</view>
<view v-if="checkableList.length === 0" class="bd-empty">暂无可签到学员</view>
</scroll-view>
<view class="bd-btns">
<button class="bd-cancel" @click="closeBatch">取消</button>
<button
class="bd-confirm"
:disabled="selectedIds.length === 0 || submitting"
@click="doBatchCheckIn"
>
{{ submitting ? '提交中...' : '确认签到(' + selectedIds.length + ')' }}
</button>
</view>
</view>
</view>
<!-- 是否登录 --> <!-- 是否登录 -->
<openlogin ref="loginId" @getPhoneNumber="getPhoneNumber"></openlogin> <openlogin ref="loginId" @getPhoneNumber="getPhoneNumber"></openlogin>
@ -215,24 +150,9 @@
name3:"年卡", name3:"年卡",
name4:"3390", name4:"3390",
}, },
], ]
batchVisible: false, // 弹窗开关
selectedIds: [], // 已勾选的 bookId
submitting: false // 防重复提交
}; };
}, },
computed: {
// 可签到数据:bookStatus ∈ [0,1,2] 且 checkIn === 0
checkableList() {
return (this.studentlist || []).filter(stu => this.isCheckable(stu));
},
// 是否全选
isAllSelected() {
return this.checkableList.length > 0 &&
this.selectedIds.length === this.checkableList.length;
}
},
onLoad(options) { onLoad(options) {
// 课程详情 // 课程详情
if(options.id){ if(options.id){
@ -253,19 +173,11 @@
} }
}, },
methods: { methods: {
formatTime(timeStr) {
// 假设 timeStr 格式为 "HH:MM:SS"
return timeStr.slice(0, 5); // 取前5位 "HH:MM"
},
retstr(val){ retstr(val){
// 描述:0->预约中;1->教练确认;2->店长确认/预约成功/待上课 3->已取消;4->已完成/待评价; // 描述:0->预约中;1->教练确认;2->店长确认/预约成功/待上课 3->已取消;4->已完成/待评价;
if(val=='0'){ if(val=='0'){
return '预约中'; return '预约中';
} }
else if (val == '1')
{
return '教练确认';
}
else if(val=='2'){ else if(val=='2'){
return '待上课 '; return '待上课 ';
} }
@ -330,18 +242,15 @@
uni.showLoading({ uni.showLoading({
title: '数据加载中...' title: '数据加载中...'
}); });
try { const {data: res} = await uni.$http.post('/api/my/courseTimeDetail', {courseTimeId:this.id});
const { data: res } = await uni.$http.post('/api/my/courseTimeDetail', { courseTimeId: this.id }); if(res&&res.data){
if (res && res.data) { var data=res.data;
var data = res.data; // 信息
this.item = data.courseDetail; this.item=data.courseDetail;
this.studentlist = data.studentList; this.studentlist=data.studentList;
this.loadStatus = 'noMore'; this.loadStatus='noMore';
this.$forceUpdate(); this.$forceUpdate();
} }
} finally {
uni.hideLoading(); // ← 关键
}
}, },
gotoorder(){ gotoorder(){
var that = this; var that = this;
@ -371,8 +280,7 @@
duration: 2000 duration: 2000
}); });
const {data: res} = await uni.$http.post('/api/my/confirmClass', param); const {data: res} = await uni.$http.post('/api/my/confirmClass', param);
console.log(res) if(res.msg=="successed"){
if(res.success=="true"){
uni.showToast({ uni.showToast({
title: '已确定上课!', title: '已确定上课!',
icon: 'success', icon: 'success',
@ -387,115 +295,14 @@
} }
else{ else{
uni.showToast({ uni.showToast({
title: res.message? res.message:'确定上课失败!', title: res.msg? res.msg:'确定上课失败!',
icon: 'none', icon: 'error',
duration: 2000 duration: 2000
}); });
} }
}, },
// 取预约记录主键(兼容 bookId / id 两种字段名) }
getBookId(stu) {
return stu.bookId || stu.id;
},
// 是否可签到
isCheckable(stu) {
if (!stu) return false;
const rawStatus = stu.bookStatus;
if (rawStatus === null || rawStatus === undefined || rawStatus === '') return false;
const status = Number(rawStatus);
const checkIn = Number(stu.checkIn === null || stu.checkIn === undefined || stu.checkIn === '' ? 0 : stu.checkIn);
return [0, 1, 2].includes(status) && checkIn === 0;
},
// 是否已勾选
isChecked(stu) {
return this.selectedIds.includes(this.getBookId(stu));
},
// 打开弹窗(默认全选)
openBatch() {
if (this.checkableList.length === 0) {
uni.showToast({ title: '暂无可签到学员', icon: 'none' });
return;
}
this.selectedIds = this.checkableList.map(stu => this.getBookId(stu));
this.batchVisible = true;
},
// 关闭弹窗
closeBatch() {
if (this.submitting) return;
this.batchVisible = false;
},
// 全选/取消全选
toggleAll() {
if (this.isAllSelected) {
this.selectedIds = [];
} else {
this.selectedIds = this.checkableList.map(stu => this.getBookId(stu));
}
},
// 单选切换
toggleOne(stu) {
const id = this.getBookId(stu);
const idx = this.selectedIds.indexOf(id);
if (idx > -1) {
this.selectedIds.splice(idx, 1);
} else {
this.selectedIds.push(id);
}
},
// 提交批量签到
async doBatchCheckIn() {
if (this.submitting) return;
if (this.selectedIds.length === 0) {
uni.showToast({ title: '请至少勾选一名学员', icon: 'none' });
return;
}
const submitCount = this.selectedIds.length;
const bookIds = this.selectedIds.slice();
this.submitting = true;
uni.showLoading({ title: '提交中...', mask: true });
try {
const { data: res } = await uni.$http.post('/api/my/batchCheckIn', {
bookIds: bookIds
});
if (res && (res.success === true || res.success === 'true')) {
this.batchVisible = false;
this.selectedIds = [];
myCache('courserefresh', 1);
uni.showToast({
title: `已签到 ${submitCount} 人`.trim() || '签到成功',
icon: 'success',
duration: 2000
});
// 刷新名单
await this.getinfo();
} else {
uni.showToast({
title: (res && res.msg) ? res.msg : '批量签到失败!',
icon: 'none',
duration: 2500
});
}
} catch (e) {
uni.showToast({ title: '签到请求异常,请稍后重试', icon: 'none', duration: 2500 });
} finally {
uni.hideLoading();
this.submitting = false;
}
}
}
} }
</script> </script>
@ -863,149 +670,6 @@
} }
} }
} }
/* ===== 批量签到入口 ===== */
.batch-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10rpx 16rpx;
}
.batch-tip {
font-size: 24rpx;
color: #999;
}
.batch-btn {
margin: 0;
height: 64rpx;
line-height: 64rpx;
padding: 0 30rpx;
font-size: 26rpx;
color: #fff;
background-color: #00A99A !important;
border-radius: 32rpx;
border: none;
}
.batch-btn::after {
border: none !important;
}
/* ===== 批量签到弹窗 ===== */
.batch-mask {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
display: flex;
align-items: center;
justify-content: center;
}
.batch-dialog {
width: 640rpx;
max-height: 72vh;
background: #fff;
border-radius: 20rpx;
display: flex;
flex-direction: column;
overflow: hidden;
}
.bd-title {
text-align: center;
font-size: 32rpx;
font-weight: 600;
color: #000;
padding: 30rpx 0 8rpx;
}
.bd-sub {
text-align: center;
font-size: 24rpx;
color: #999;
padding-bottom: 16rpx;
}
.bd-all,
.bd-item {
display: flex;
align-items: center;
padding: 22rpx 30rpx;
border-top: 1rpx solid #f2f2f2;
}
.cb {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
border: 2rpx solid #c8c8c8;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
flex-shrink: 0;
box-sizing: border-box;
}
.cb.checked {
background: #00A99A;
border-color: #00A99A;
}
.cb-icon {
color: #fff;
font-size: 22rpx;
line-height: 1;
}
.bd-list {
max-height: 460rpx;
}
.bd-info {
flex: 1;
}
.bd-name {
font-size: 28rpx;
color: #333;
}
.bd-meta {
font-size: 24rpx;
color: #999;
margin-top: 6rpx;
}
.bd-empty {
text-align: center;
font-size: 26rpx;
color: #999;
padding: 80rpx 0;
}
.bd-btns {
display: flex;
padding: 20rpx 30rpx 30rpx;
}
.bd-cancel {
flex: 1;
margin: 0 20rpx 0 0;
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
color: #666;
background: #f5f5f5 !important;
border-radius: 40rpx;
border: none;
}
.bd-confirm {
flex: 1;
margin: 0;
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
color: #fff;
background: #00A99A !important;
border-radius: 40rpx;
border: none;
}
.bd-cancel::after,
.bd-confirm::after {
border: none !important;
}
.bd-confirm[disabled] {
opacity: 0.5;
}
</style> </style>

@ -13,27 +13,20 @@
</view> </view>
</view> </view>
<view class="s-list" > <!-- <view class="s-list" >-->
<view class="s-header"> <!-- <view class="s-header">-->
<view class="tit-box"> <!-- <view class="tit-box">-->
<text class="tit">我的会员卡</text> <!-- <text class="tit">我的会员卡</text>-->
</view> <!-- </view>-->
</view> <!-- </view>-->
<!-- 卡片为空提示 --> <!-- &lt;!&ndash; ✅ 会员卡横向滑动(不可选,纯展示) &ndash;&gt;-->
<view v-if="!memberCards || memberCards.length === 0" class="empty-tip"> <!-- <member-card-scroll-->
暂无会员卡 <!-- :cards="memberCards"-->
</view> <!-- size="small"-->
<!-- :selectable="false"-->
<view v-else> <!-- @card-click="onCardClick"-->
<!-- ✅ 会员卡横向滑动(不可选,纯展示) --> <!-- />-->
<member-card-scroll <!-- </view>-->
:cards="memberCards"
size="small"
:selectable="false"
@card-click="onCardClick"
/>
</view>
</view>
<!-- 菜单1 --> <!-- 菜单1 -->
<!-- <view class="s-list"> <!-- <view class="s-list">
<view class="s-header"> <view class="s-header">
@ -122,7 +115,7 @@
<view class="title">{{servers[13].name}}</view> <view class="title">{{servers[13].name}}</view>
</view> </view>
<!-- 学员签到 教练/顾问 --> <!-- 学员签到 教练/顾问 -->
<view v-if="ifview('103,104,105')" class="product-item" @click="gotoServe(14)"> <view v-if="ifview('103,104')" class="product-item" @click="gotoServe(14)">
<view class="image-wrapper"> <view class="image-wrapper">
<image class="imgicon" :src="servers[14].img" mode="aspectFill" lazy-load></image> <image class="imgicon" :src="servers[14].img" mode="aspectFill" lazy-load></image>
</view> </view>
@ -226,7 +219,77 @@
avatar:"../../static/image/logo.png", avatar:"../../static/image/logo.png",
roleId:[], roleId:[],
memberCards: [ memberCards: [
// 1. 次卡:正常,余额充足
{
id: 1001,
cardNo: '88880001',
chargeType: 'count',//收费模式 count:课时 days:时间 total_fee:储值
cardName: '次卡',
remainingCount: 8,
frozenCount: 1,
remainingTotalFee: 0,
frozenAmount: 0,
expireDate: '2027-12-31',
status: 'ACTIVE'
},
// 2. 次卡:剩余次数不足(只剩1次)
{
id: 1002,
type: 'COUNT',
cardName: '次卡',
name: '体验次卡',
cardNo: '88880002',
remainingCount: 1,
frozenCount: 0,
remainAmount: 0,
frozenAmount: 0,
expireDate: '2026-12-31',
status: 'NORMAL'
},
// 3. 储值卡:余额充足
{
id: 1003,
type: 'AMOUNT',
cardName: '储值卡',
name: '瑜伽储值卡',
cardNo: '88880003',
remainingCount: 0,
frozenCount: 0,
remainAmount: 1280.50,
frozenAmount: 200.00,
expireDate: '2028-06-30',
status: 'NORMAL'
},
// 4. 储值卡:余额不足(只剩50元)
{
id: 1004,
type: 'AMOUNT',
cardName: '储值卡',
name: '小额储值卡',
cardNo: '88880004',
remainingCount: 0,
frozenCount: 0,
remainAmount: 50.00,
frozenAmount: 0,
expireDate: '2027-01-01',
status: 'NORMAL'
},
// 5. 已过期的次卡
{
id: 1005,
type: 'COUNT',
cardName: '次卡',
name: '过期次卡',
cardNo: '88880005',
remainingCount: 5,
frozenCount: 0,
remainAmount: 0,
frozenAmount: 0,
expireDate: '2025-01-01', // 已过期
status: 'NORMAL'
}
], ],
favoritesnum:0,//我的收藏数量 favoritesnum:0,//我的收藏数量
menus: [ menus: [
@ -362,7 +425,7 @@
this.signature = user.signature?user.signature:this.signature; this.signature = user.signature?user.signature:this.signature;
this.roleId=user.roleId?user.roleId:[]; this.roleId=user.roleId?user.roleId:[];
this.getmember(); this.getmember();
this.getMemberCards();
// if(myCache("ifuserEdit")) // if(myCache("ifuserEdit"))
// { // {
// myCache("ifuserEdit",false) // myCache("ifuserEdit",false)
@ -1175,10 +1238,5 @@
align-items: center; align-items: center;
} }
} }
.empty-tip {
padding: 60rpx 20rpx;
text-align: center;
font-size: 28rpx;
color: #999;
}
</style> </style>

Loading…
Cancel
Save