You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

824 lines
20 KiB

<template>
<view class="page">
<view class="oinfo">
<view class="yycon scell" v-if="item">
<view class="oitem">
<view class="otop">
<view class="hleft">{{ item.courseName }}</view>
<view class="hright">
{{ formatTime(item.startTime) + ' - ' + formatTime(item.endTime) }}
</view>
</view>
<view class="oscon">
<view class="osname">
<view class="osimg imgload">
<image v-if="item.pic" :src="getimgRemoteFile(item.pic)" mode="aspectFill"></image>
</view>
<view class="oscell">
<view v-if="item.claName" class="txtbg1" :style="{ color: item.claColor ? item.claColor : '#333' }">
{{ item.claName }}
</view>
<view v-if="item.courseTypeName" style="color: #888; font-weight: 500; font-size: 28rpx;">
{{ item.courseTypeName }}
</view>
<uni-rate :size="20" :readonly="true" :value="item.star ? item.star : 5" :max="5" />
</view>
<view class="oscell" v-if="item.storeName">
<uni-icons type="shop" size="18" color="#00A99A"></uni-icons>
<view class="ntip">{{ item.storeName }}</view>
</view>
<view class="oscell">
<uni-icons type="calendar" size="18" color="#00A99A"></uni-icons>
<view class="ntip">{{ item.claDate }}</view>
<view class="txt">{{ item.weekDay }}</view>
</view>
<view class="oscell">
<uni-icons type="person" size="18" color="#666"></uni-icons>
<view class="ntxt">{{ item.staffName }}</view>
</view>
<view class="oscell" v-if="item.roomName">
<uni-icons type="map-pin-ellipse" size="18" color="#00A99A"></uni-icons>
<view class="ntxt">{{ item.roomName }}</view>
</view>
<view class="oscell" v-if="item.atClassCnt">
<uni-icons type="checkbox" size="18" color="#00A99A"></uni-icons>
<view class="ntxt">已预约{{ item.bookAttendCnt }}人/限{{ item.atClassCnt }}人</view>
</view>
<view class="oscell" v-if="item.lessCnt">
<view class="txt" style="margin-left: 46rpx;">满{{ item.lessCnt }}人开课</view>
</view>
</view>
</view>
<view class="rcpm">
<!-- <button type="primary" class="orderbtn" @click="openCardSelect(item)">预约</button>-->
<button type="primary" class="orderbtn" @click="gotoorder">预约</button>
</view>
</view>
</view>
<!-- 信息 -->
<view class="yycon tcon">
<mp-html v-if="remark" :content="remark" :markdown="true" :lazy-load="true" />
</view>
</view>
<!-- ✅ 选卡弹窗 -->
<uni-popup ref="cardPopup" type="center" :mask-click="false">
<view class="select-popup">
<view class="popup-header">
<text class="popup-title">选择会员卡</text>
<view class="popup-close" @click="closeCardSelect">
<uni-icons type="closeempty" size="22" color="#999"></uni-icons>
</view>
</view>
<view class="course-info">
<view class="course-row">
<text class="course-name">{{ currentCourse.courseName }}</text>
<text class="deduct">
需扣 {{ currentCourse.deductType === 'count'
? currentCourse.deductCount + '次'
: '¥' + currentCourse.deductAmount }}
</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>
<!-- 卡片横向滑动 -->
<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>
</uni-popup>
<!-- 是否登录 -->
<openlogin ref="loginId" @getPhoneNumber="getPhoneNumber"></openlogin>
</view>
</template>
<script>
import { myCache, getRemoteFile } from '../../utils/utils.js';
import openlogin from "../components/openlogin.vue";
import MemberCardScroll from '../components/member-card-scroll.vue';
export default {
components: {
openlogin,
MemberCardScroll
},
data() {
return {
openId: "",
phone: "",
userid: "",
id: "",
item: {
courseTimeId: 1935590320322428931,
claName: "普拉提A班",
courseName: "普拉提",
storeName: "慢瑜伽-三台子店",
pic: null,
claDate: "2025-10-01",
weekDay: "星期二",
startTime: "08:00",
endTime: "09:30",
staffName: "冉洪涛老师",
studentCount: 1,
roomName: null,
claColor: "#409EFF",
claTimeStatus: "1",
bookAttendCnt: "1",
atClassCnt: "5",
lessCnt: "3"
},
remark: null,
memberCards: [],
currentCourse: {
courseName: '普拉提A班',
deductType: 'COUNT',
deductCount: 1,
deductAmount: 0
},
defaultCardId: null,
// ✅ 用户手动选中的卡(父组件持有)
selectedCard: null
};
},
computed: {
// ✅ 默认卡:后端指定的,或第一张可用卡
defaultCard() {
if (this.defaultCardId != null) {
const card = this.memberCards.find(c => c.id === this.defaultCardId);
if (card && this.isCardUsable(card)) return card;
}
return this.memberCards.find(card => this.isCardUsable(card)) || null;
},
// ✅ 有效卡:用户选中的,没选就用默认卡
effectiveCard() {
return this.selectedCard || this.defaultCard;
},
// ✅ 有效卡名称(这就是弹窗里显示的内容)
effectiveCardName() {
const card = this.effectiveCard;
return card ? card.cardName : '无可用卡';
},
// ✅ 是否手动选择了
isManuallySelected() {
return !!this.selectedCard;
}
},
onLoad(options) {
if (options.data) {
this.item = JSON.parse(decodeURIComponent(options.data));
this.id = this.item.courseTimeId;
this.getinfo();
}
},
onShow() {
this.openId = myCache('openId');
var user = myCache('user');
this.userid = user.userid ? user.userid : '';
this.phone = user.userphone;
if (this.userid == "" || this.userid == "0") {
uni.navigateTo({ url: `/pages/login/login` });
}
},
methods: {
// ✅ 判断卡片是否可用(父组件也需要)
isCardUsable(card) {
if (!card) return false;
if (card.status && card.status !== 'ACTIVE') return false;
if (card.expireDate) {
const now = new Date();
const expire = new Date(card.expireDate.replace(/-/g, '/'));
if (now > expire) return false;
}
const type = this.currentCourse.deductType;
const count = this.currentCourse.deductCount || 1;
const amount = this.currentCourse.deductAmount || 0;
if (type === 'count') {
const available = (card.remainCount || 0) - (card.frozenCount || 0);
return available >= count;
} else if (type === 'total_fee') {
const available = (card.remainAmount || 0) - (card.frozenAmount || 0);
return available >= amount;
}
},
// ✅ 子组件选中回调:更新父组件的 selectedCard
onCardSelect({ card }) {
this.selectedCard = card;
},
// ✅ 打开弹窗:重置手动选择,用默认卡
async openCardSelect(item) {
var user = myCache('userInfo');
const { data: memberCards } =
await uni.$http.post("/api/memberCard/getMemberCardListByCourseTimeId",
{visitStore:user.visitStore,
courseId:item.courseId
});
console.log("memberCards",memberCards)
//如果为空 则没有适合的卡片
this.memberCards = memberCards.data
this.selectedCard = null;
this.$refs.cardPopup.open();
},
closeCardSelect() {
this.$refs.cardPopup.close();
},
async confirmSelect() {
// ✅ 直接从父组件 computed 拿有效卡
const card = this.effectiveCard;
if (!card) {
uni.showToast({ title: '无可用会员卡', icon: 'none' });
return;
}
const { data: res } = await uni.$http.post('/api/course/bookCourse', {
courseTimeId: this.item.courseTimeId,
cardId: card.id
});
if (res && res.success) {
myCache('courserefresh', 1);
uni.showModal({
title: '提示',
content: '课程已预约!',
cancelText: '返回',
confirmText: '确定',
success: ress => {
uni.navigateBack({ delta: 1 });
}
});
} else {
uni.showModal({
title: '提示',
content: res.message ? res.message : '课程预约失败!',
cancelText: '取消',
confirmText: '确定'
});
}
},
formatTime(timeStr) {
return timeStr.slice(0, 5);
},
getimgRemoteFile(img) {
if (img) {
img = img.split(',')[0];
return getRemoteFile(img);
} else {
return require("@/static/image/theme/p1.jpg");
}
},
getPhoneNumber(e) {
if (e.phone) this.phone = e.phone;
if (e.userid) this.userid = e.userid;
},
iflogin() {
this.openId = myCache('openId');
var user = myCache('user');
this.userid = user.userid ? user.userid : '';
this.phone = user.userphone;
if (this.userid == "" || this.userid == "0" || this.phone == "") {
uni.navigateTo({ url: `/pages/login/login` });
return false;
}
return true;
},
goback() {
uni.navigateBack({ delta: 1 });
},
async getinfo() {
uni.showLoading({ title: '数据加载中...' });
const { data: res } = await uni.$http.post('/api/course/getCourseDetail', { courseTimeId: this.id });
if (res && res.data) {
this.item = res.data;
this.remark = res.data.remark;
this.$forceUpdate();
}
},
gotoorder(){
var that = this;
// 再次预约
uni.showModal({
title: '提示',
content: '确定预约吗?',
cancelText: '取消',
confirmText: '确定',
success: ress => {
if (ress.confirm) {
that.orderdodo();
}
}
});
},
// 预约
async orderdodo(){
var param={
"courseTimeId": this.item.courseTimeId
};
uni.showLoading({
title: '课程预约中...'
});
const {data: res} = await uni.$http.post('/api/course/bookCourse', param);
console.log(res)
if(res.success){
// uni.showToast({
// title: '课程已预约!',
// icon: 'success',
// duration: 2000
// });
// 刷新保存
myCache('courserefresh',1);
uni.showModal({
title: '提示',
content: '课程已预约!',
cancelText: '返回',
confirmText: '确定',
success: ress => {
if (ress.confirm) {
// 返回
uni.navigateBack({
delta: 1
});
}
else{
// 返回
uni.navigateBack({
delta: 1
});
}
}
});
}
else{
uni.showModal({
title: '提示',
content: res.message? res.message:'课程预约失败!',
cancelText: '取消',
confirmText: '确定',
success: ress => {
if (ress.confirm) {
}
}
});
}
},
}
};
</script>
<style lang="scss" scoped>
::v-deep .uni-collapse-item__title-box{
padding: 0 !important;
}
::v-deep .uni-collapse-item__title-text{
font-weight: 600;
}
.page{
padding: 0;
position: relative;
background-image: url('@/static/image/bg.jpg');
background-attachment: fixed;
background-size: cover;
background-position: center center;
min-height: calc(100vh - var(--window-top) - var(--window-bottom));
}
.ret{
position: fixed;
top:20rpx;
left:20rpx;
background: rgba(0, 0, 0, .3);
height: 60rpx;
width: 60rpx;
border-radius: 50% 50%;
text-align: center;
line-height: 60rpx;
z-index: 9999;
}
.oscon{
background-color: #f3fafa;
border-radius: 16rpx;
padding: 16rpx;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
.osimg{
margin-left: 24%;
width: 300rpx;
height: 300rpx;
margin-right: 10rpx;
>image {
width: 300rpx;
height: 300rpx;
border-radius: 10rpx;
}
}
.osrcon{
display: flex;
flex-direction: column;
flex: 1;
justify-content: flex-start;
margin-left: 20rpx;
}
.osname{
line-height: 48rpx;
font-size:32rpx;
font-weight: 600;
color:#009999;
padding-bottom: 15rpx;
}
.oscell{
display: flex;
flex-direction: row;
align-items: center;
font-size: 28rpx;
color:#333;
padding: 10rpx 0;
border-bottom: 1rpx dotted #74defb;
.txt{
display: flex; flex: 1;
}
}
}
.oinfo{
padding:20rpx;
position: relative;
margin-bottom: 20rpx;
.yycon{
display: flex;
flex-direction: column;
justify-content: center;
background-color: #FFFFFF;
padding: 20rpx 20rpx;
}
}
.tcon{
display: flex;
flex-direction: column;
justify-content: center;
background-color: #FFFFFF;
margin: 20rpx 10rpx 10rpx 10rpx;
.olist{
display: flex;
flex-direction: row;
padding: 10rpx;
width: 100%;
.ltxt{
font-size: 26rpx;
color:#747474;
width: 150rpx;
}
.rtxt{
display: flex;
flex: 1;
font-size: 26rpx;
color:#747474;
display: flex;
flex-direction: row;
}
}
}
.red{
color:#ff3d0e;
}
.green{
color:#00cd00;
}
::v-deep.uni-radio-input-checked{
background-color: #00A99A !important;
border-color: #00A99A !important;
background-clip: content-box!important;
box-sizing: border-box;
}
::v-deep.uni-radio-input-checked::before{
display: none!important;
}
.scell{
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
margin: 10rpx;
.oitem{
display: flex;
flex-direction: column;
background-color: #FFFFFF;
border-radius: 6rpx;
padding: 20rpx 20rpx 0 20rpx;
width: 100%;
margin-bottom: 20rpx;
.rcpm{
display: flex;
flex-direction: column;
width: 100%;
margin: 20rpx 0 0;
}
.orderbtn{
width: 100%;
background-color: #00A99A !important;
height: 80rpx;
line-height: 80rpx;
padding: 0 20rpx;
border:1rpx solid #00A99A;
}
.orderbtn::after{
border:0 !important;
}
.otop{
display: flex;
flex-direction: row;
padding: 10rpx 0 20rpx;
width: 100%;
border-bottom: 1rpx solid #f0f0f0;
.hleft{
display: flex;
flex:1;
align-items: center;
font-size: 30rpx;
font-weight: 600;
line-height: 32rpx;
color: #000;
}
.hright{
display: flex;
justify-content: flex-end;
align-items: center;
font-size: 28rpx;
color: #000;
font-weight: 600;
}
}
.oscon{
margin:20rpx 0 10rpx;
border-radius: 16rpx;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
.osimg{
margin-left: 24%;
width: 300rpx;
height: 300rpx;
margin-right: 10rpx;
>image {
width: 300rpx;
height: 300rpx;
border-radius: 10rpx;
}
}
.osname{
display: flex;
flex: 1;
display: flex;
flex-direction: column;
margin-left: 26rpx;
.oscell{
display: flex;
flex-direction: row;
}
.txtbg{
padding: 2rpx 15rpx;
border-radius: 30rpx;
background-color: #f3fbfb;
border: 1rpx solid #7cced6;
font-size: 26rpx;
color: #333;
margin-right: 10rpx;
}
.txtbg1{
padding: 2rpx 15rpx;
border-radius: 30rpx;
background-color: #fcf2f3;
border: 1rpx solid #f09fb6;
font-size: 26rpx;
color: #333;
margin-right: 10rpx;
}
.txtbg2{
padding: 2rpx 15rpx;
border-radius: 30rpx;
background-color: #fcf3ef;
border: 1rpx solid #e99b12;
font-size: 26rpx;
color: #333;
margin-right: 10rpx;
}
.txtbg3{
padding: 2rpx 15rpx;
border-radius: 30rpx;
background-color: #f1f8f0;
border: 1rpx solid #7dbd71;
font-size: 26rpx;
color: #333;
margin-right: 10rpx;
}
.ntip{
font-size: 26rpx;
color:#1abbc3;
line-height: 32rpx;
margin-left: 10rpx;
}
.ntxt{
font-size: 26rpx;
color:#000;
line-height: 32rpx;
margin-left: 10rpx;
}
.txt{
font-size: 24rpx;
color:#666;
margin-left: 10rpx;
}
}
}
}
.sitem {
display: flex;
flex-direction: column;
flex: 1;
background-color: #FFFFFF;
/*设置最小宽度,才会让元素排不下,导致换行排列*/
min-width: calc((100% - 20rpx) / 2);
max-width: calc((100% - 20rpx) / 2);
margin-right:5rpx;
margin-left:5rpx;
margin-bottom: 10rpx;
border-radius: 16rpx;
.simg{
width: 100%;
height: 360rpx;
.img{
width: 100%;
height: 100%;
border-radius: 16rpx 16rpx 0 0;
}
}
.sname{
font-size: 30rpx;
font-weight: 600;
color: rgb(29, 29, 29);
padding: 16rpx;
}
.sinfo{
font-size: 26rpx;
color:rgb(116, 116, 116);
padding: 0 16rpx;
height: 60rpx;
line-height: 30rpx;
overflow: hidden;
position: relative;
text-overflow: ellipsis;
display:-webkit-box;
-webkit-box-orient:vertical;
-webkit-line-clamp:2;
}
.sprices{
padding: 0 16rpx 20rpx 16rpx;
font-size: 36rpx;
color:rgb(252, 105, 0);
border-radius: 0 0 16rpx 16rpx;
.cx{
font-size: 28rpx;
color:rgb(116, 116, 116);
text-decoration: line-through;
margin-left: 20rpx;
}
}
}
}
.select-popup {
width: 660rpx;
max-height: 80vh;
background: #fff;
border-radius: 24rpx;
display: flex;
flex-direction: column;
overflow: hidden;
}
.popup-header {
position: relative;
padding: 32rpx 0;
text-align: center;
border-bottom: 1rpx solid #eee;
}
.popup-title {
font-size: 32rpx;
font-weight: 600;
color: #000;
}
.popup-close {
position: absolute;
right: 30rpx;
top: 50%;
transform: translateY(-50%);
padding: 10rpx;
}
.course-info {
padding: 20rpx 32rpx;
background: #fff;
border-bottom: 1rpx solid #f0f0f0;
}
.course-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.course-name {
font-size: 28rpx;
font-weight: 600;
color: #000;
}
.deduct {
color: #ff6b6b;
font-weight: 600;
font-size: 24rpx;
}
/* ✅ 本次使用卡片 */
.use-card-row {
display: flex;
align-items: center;
margin-top: 12rpx;
font-size: 24rpx;
}
.use-label {
color: #999;
}
.use-name {
color: #00a89b;
font-weight: 600;
&.is-default {
color: #666; /* 默认卡用灰色,区别于手动选中 */
}
}
.default-tag {
margin-left: 10rpx;
font-size: 20rpx;
color: #ff6b6b;
background: #ffeeee;
padding: 2rpx 10rpx;
border-radius: 10rpx;
}
.popup-footer {
padding: 20rpx 32rpx 32rpx;
background: #fff;
border-top: 1rpx solid #eee;
}
.confirm-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background: #00a89b;
color: #fff;
font-size: 30rpx;
border-radius: 44rpx;
border: none;
&::after { border: none; }
}
</style>