2025-08-02 13:57:00 +08:00

764 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page">
<view class="buy-service">
<view class="back_bar"> <BackBar title="督学服务" /></view>
<view class="header">
<wd-tabs v-model="curTab" @change="onModelChange">
<block v-for="item in modelList" :key="item.name">
<wd-tab class="tabs" :title="`${item.name}`" :name="item.mode + ''">
<view v-if="item.mode === 1" class="content">
<card :is-show-title="false">
<image :src="item.url"></image>
</card>
</view>
<view v-if="item.mode === 2" class="content">
<card :is-show-title="false">
<image :src="item.url"></image>
</card>
</view>
</wd-tab>
</block>
</wd-tabs>
</view>
<view class="classDate">
<card :is-show-line="false" title="选择开课时间">
<view class="date-des">请点击日历选择12个督学日,以生成课表</view>
<custom-calender
ref="customCalenderRef"
class="custom-calender-class"
style="width: 100%; margin: 0"
:date-disabled="dateDisabled"
@selected-day="selectedDay"
>
<template #header="{ date }">
<view class="date-header">
<wd-icon name="arrow-left" size="22px" @click="prevTime"></wd-icon>
<view>{{ date.monthDate }}</view>
<wd-icon name="arrow-right" size="22px" @click="nextTime"></wd-icon>
</view>
</template>
</custom-calender>
<view class="date-state-des">
<view v-for="state in dateStateList" :key="state.name" class="item">
<view class="item-block" :style="{ backgroundColor: state.bgcolor }"></view>
<view>{{ state.name }}</view>
</view>
</view>
</card>
</view>
<view class="class-calender">
<wd-icon
v-show="curClassColl === 0 && allClassCalenderList.length > 5"
name="arrow-up"
size="22px"
class="coll-icon"
@click="onCollClick"
></wd-icon>
<wd-icon
v-show="curClassColl === 1 && allClassCalenderList.length > 5"
name="arrow-down"
size="22px"
class="coll-icon"
@click="onCollClick"
></wd-icon>
<card :is-show-line="false" title="课表">
<view v-if="!classCalenderList.length" class="no-class">
<image :src="noclassIcon"></image>
<text>您还没有选择督学日期,暂未生成课表哦~</text>
</view>
<view v-else style="width: 100%">
<view v-for="classItem in classCalenderList" :key="classItem.id" class="class-item">
<text>{{ classItem.id }}</text>
<text>{{ classItem.date }}</text>
<text>{{ classItem.time }}</text>
<text>{{ classItem.day }}</text>
</view>
</view>
</card>
</view>
<!-- //选择孩子弹窗 -->
<view class="select-pop">
<wd-popup
v-model="showSelectChild"
position="bottom"
safe-area-inset-bottom
class="select-pop"
custom-style="height: 580rpx;"
@close="onCloseSelectChild"
>
<view>
<view class="title">选择下单对象</view>
<view class="children">
<view v-for="child in childrenList" :key="child.id" class="child-item">
<view class="child-block">
<image :src="child.src"></image>
</view>
<view class="child-name">
<text class="name">{{ child.name }}</text>
<text class="expire"
>有效期至:{{ dayjs(child.expire).format('YYYY.MM.DD') }}</text
>
</view>
<view class="child-select">
<wd-checkbox v-model="child.selected" shape="square"></wd-checkbox>
</view>
</view>
</view>
<view class="ok-btn">
<button class="confirm-btn" @click="onConfirmSelectChild">确认</button>
</view>
</view>
</wd-popup>
</view>
<!-- //订单确认弹窗 -->
<view class="select-pop">
<wd-popup
v-model="showSelectOrder"
position="bottom"
safe-area-inset-bottom
class="order-pop"
custom-style="height: 290px;"
@close="onCloseSelectOrder"
>
<view>
<view class="confirm-pay-title" style="text-align: center">确认支付</view>
<view class="pay-price" style="text-align: center">
<text>¥</text>
{{ curSelectMode.discountPrice }}
</view>
<view class="content">
<view class="order-info">
<text class="order-title">订单信息</text>
<text class="order-value">督学服务</text>
</view>
<view class="order-info">
<text class="order-title">支付方式</text>
<text class="order-value">微信支付</text>
</view>
</view>
<view class="ok-btn">
<button class="confirm-btn" @click="onConfirmSelectOrder">立即付款</button>
</view>
</view>
</wd-popup>
</view>
<!-- //支付成功弹窗 -->
<view class="success-pop">
<wd-popup v-model="buySuccessPop" custom-style="padding: 30px 40px;">
<image :src="successPng"></image>
<view class="des">购买成功,将自动为你分配督学师</view>
<view class="close-btn" @click="buySuccessPop = false">确定</view>
</wd-popup>
</view>
<wd-toast />
<wd-overlay :show="payLoading">
<view style="text-align: center; height: 500px; color: #fff">
{{ tipsInfo }}
<text style="line-height: 500px"> 正在请求支付...</text>
</view>
</wd-overlay>
</view>
<view v-if="curSelectMode" class="buy-info">
<view class="price">
<view class="new-price"> <text></text>{{ curSelectMode.discountPrice }}</view>
<view class="old-price">
<text>原价</text>
<wd-text
:text="String(curSelectMode.price)"
mode="price"
decoration="line-through"
prefix="¥"
/>
</view>
</view>
<view>
<wd-button :disabled="!allClassCalenderList.length" class="buy-btn" @click="buyService">
购买
</wd-button>
</view>
</view>
</view>
</template>
<script setup lang="ts">
declare const WeixinJSBridge: any;
import { computed, reactive, ref, watchEffect } from 'vue';
import card from './components/card.vue';
import CustomCalender from '@/components/customCalender/index.vue';
import dayjs from 'dayjs';
import noclassIcon from '@/static/noclass-icon.jpg';
import successPng from '@/static/suc.png';
import { useToast } from '@/uni_modules/wot-design-uni';
import { getChildList, getModelList } from '@/api/modules/parent';
import { onLoad } from '@dcloudio/uni-app';
import { createInspectorPrepayOrder } from '@/api/global';
const toast = useToast();
const curTab = ref('1');
const modelList = ref([]);
let modes_data = []; // 后端原始数据
let curSelectMode = ref(null);
const onModelChange = value => {
curSelectMode.value = modes_data.find(m => m.mode === Number(value.name));
};
const customCalenderRef = ref();
const classCalenderList = ref([]);
const allClassCalenderList = ref([]);
const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
const selectedDay = date => {
// 根据当前日期向后选择 11 个 周二 周四 周六
const resultDay = [];
let curDay = dayjs(date.date);
let i = 0;
while (resultDay.length < 12) {
const day = curDay.add(i, 'day');
i++;
if (day.day() === 2 || day.day() === 4 || day.day() === 6) {
resultDay.push(day);
}
}
allClassCalenderList.value = resultDay.map((d, idx) => {
return {
id: `${String(idx + 1).padStart(2, '0')}`,
date: dayjs(d).format('YYYY.MM.DD'),
dateParams: dayjs(d).format('YYYY-MM-DD'),
time: '19:00-20:00',
day: weekDays[dayjs(d).day()],
};
});
if (allClassCalenderList.value.length > 5) {
classCalenderList.value = allClassCalenderList.value.slice(0, 5);
} else {
classCalenderList.value = allClassCalenderList.value;
}
};
const prevTime = () => {
customCalenderRef.value.prevMonth();
};
const nextTime = () => {
customCalenderRef.value.nextMonth();
};
const dateStateList = ref([
{ name: '可选', bgcolor: '#FFF' },
{ name: '不可选', bgcolor: '#f9f9f9' },
{ name: '已选中', bgcolor: '#E1E0FF' },
]);
// 课表更多收起
const curClassColl = ref(0);
const onCollClick = () => {
if (curClassColl.value === 0) {
classCalenderList.value = allClassCalenderList.value;
} else {
classCalenderList.value = allClassCalenderList.value.slice(0, 5);
}
curClassColl.value = curClassColl.value === 0 ? 1 : 0;
};
const buyService = () => {
if (childrenList.value.length === 0) {
uni.showToast({ title: '还没绑定孩子账号,请前往扫码绑定', icon: 'none' });
return;
}
if (childrenList.value.length === 1) {
curSelectChildren = childrenList.value;
showSelectOrder.value = true;
return;
}
showSelectChild.value = true;
};
const showSelectChild = ref(false);
const onCloseSelectChild = () => {
showSelectChild.value = false;
};
const childrenList = ref([]);
const showSelectOrder = ref(false);
const onCloseSelectOrder = () => {
showSelectOrder.value = false;
};
const getBaseInfo = async () => {
const data = await getChildList();
// console.log('data', data);
childrenList.value = data.map(child => {
return {
id: child.childId,
src: child.childAvatar,
name: child.childName,
expire: child.vipEndTime,
selected: false,
};
});
const { data: models } = await getModelList();
// console.log('models', models);
modes_data = models;
modelList.value = models.map(model => {
return {
mode: model.mode,
name: model.mode === 1 ? '1V1' : '1VN',
url: model.promotionalMaterials,
discountPrice: model.discountPrice,
price: model.price,
};
});
curSelectMode.value = modes_data.find(mode => mode.mode === 1);
};
onLoad(async (options = {}) => {
getBaseInfo();
});
let curSelectChildren = [];
const onConfirmSelectChild = () => {
curSelectChildren = childrenList.value.filter(child => child.selected);
if (curSelectChildren.length) {
showSelectChild.value = false;
showSelectOrder.value = true;
} else {
toast.show('请选择至少一个宝贝');
}
};
let timer = null;
const tipsInfo = ref();
const onConfirmSelectOrder = async () => {
// 只处理 微信浏览器 H5 端支付
const ua = navigator?.userAgent?.toLowerCase();
const isWechatBrowser = ua?.indexOf('micromessenger') !== -1;
if (!isWechatBrowser) {
toast.show('请在微信中打开进行支付');
return;
}
payLoading.value = true;
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
payLoading.value = false;
}, 30000);
const params = {
inspectorCourseList: allClassCalenderList.value.map(c => {
return c.dateParams + ' 19:00:00';
}),
inspectorModeId: curSelectMode.value.id,
money: curSelectMode.value.discountPrice,
orderType: 1,
paySubType: 'JSAPI',
userId: curSelectChildren[0].id,
};
const data = await createInspectorPrepayOrder(params);
const order = {
appId: data.prepayResponse.appId,
nonceStr: data.prepayResponse.nonceStr,
package: data.prepayResponse.packageVal,
signType: data.prepayResponse.signType,
paySign: data.prepayResponse.paySign,
timeStamp: data.prepayResponse.timeStamp,
};
// // 测试
// const o = {
// appId: 'wxd13aabeb40898e1d',
// nonceStr: 'qAQwNzkqoxXyotjWf76x466oNxq5Y97z',
// package: 'prepay_id=wx2810324759377114f4c3455c0fd7230000',
// paySign: `UMRbpAknP59VvTOOszCcUZ8Mg2JtJbU8IaXBObgwm/RTcK/h8bS9zoU8QSHLOaSZVtphbT5apy9Hn2A/2W7D/DMecJwkNk0Eczhc/8fPG3/Ato93Rl2tyOi4Ck0z5mw03E/KHSIU/8nHl7uyOet3shCxV3EiHSEVibp+rxmkWTP5VTaBJhqjvoNgECiVpjofA//iur1OnAoMdQFBznkfxqTNND1ZBm2ZLUB5plrBddhJk1WrCRmSadRx0P36ZSz+t18TJd9+n34uUhQ14Cez6oJBv1uHcEJUij+YuUx72gMO4flVVGDNZ2stWax1bupQ+N/cDTuiOOIAuet/JpltQg==`,
// signType: 'RSA',
// timeStamp: '1724812366',
// };
const payResult = await pay(order);
payLoading.value = false;
buySuccessPop.value = payResult;
showSelectOrder.value = false;
};
const buySuccessPop = ref(false);
// -----------支付相关
const payLoading = ref(false);
const pay = async order => {
return new Promise((resove, reject) => {
WeixinJSBridge.invoke(
'getBrandWCPayRequest',
{
appId: order.appId, // 公众号ID由商户传入
timeStamp: order.timeStamp, // 时间戳自1970年以来的秒数
nonceStr: order.nonceStr, // 随机串
package: order.package,
signType: order.signType, // 微信签名方式:
paySign: order.paySign,
},
res => {
if (res.err_msg === 'get_brand_wcpay_request:ok') {
// 支付成功后的操作
console.log('支付成功');
resove(true);
} else {
// 支付失败后的操作
console.log('支付失败', res.err_msg);
resove(false);
}
},
);
});
};
const dateDisabled = date => {
return (
![2, 4, 6].includes(dayjs(date.date).day()) ||
dayjs(date.date).isBefore(dayjs().subtract(1, 'day'))
);
};
</script>
<style scoped lang="scss">
.buy-info {
position: fixed;
width: 100%;
height: 126rpx;
background-color: #fff;
bottom: 0;
border-top: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 30rpx;
padding-bottom: env(safe-area-inset-bottom);
box-sizing: border-box;
.price {
display: flex;
align-items: center;
.new-price {
color: #ff8d5f;
font-weight: 700;
font-size: 42rpx;
line-height: 60rpx;
}
.old-price {
font-size: 24rpx;
margin-left: 10rpx;
line-height: 60rpx;
color: #a9aab5;
display: flex;
}
}
.buy-btn {
display: block;
width: 300rpx;
height: 90rpx;
line-height: 90rpx;
font-size: 30rpx;
overflow: hidden;
background-color: #615dff;
border-radius: 10px !important;
}
}
.buy-service {
padding: 0 12rpx 160rpx 12rpx;
.back_bar {
margin-bottom: 16rpx;
}
.classDate {
.date-des {
color: #a9aab5;
font-size: 12px;
margin-top: -10px;
}
.date-state-des {
display: flex;
width: 100%;
justify-content: center;
.item {
width: 80px;
display: flex;
align-items: center;
.item-block {
margin: 0 5px;
width: 15px;
height: 15px;
border: 1px solid #eee;
}
}
}
.date-header {
display: flex;
align-items: center;
justify-content: center;
padding: 10rpx 0 30rpx 0;
}
.custom-calender-class {
:deep(.calendar-week) {
width: 100%;
}
:deep(.calendar-inner) {
width: 100%;
}
}
}
.class-calender {
.no-class {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-bottom: 16px;
image {
display: block;
margin: auto;
width: 240px;
height: 230px;
}
text {
color: #a9aab5;
font-size: 14px;
}
}
.class-item {
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
height: 45px;
border-radius: 5px;
padding: 0 16px;
margin: 5px 0;
width: 100%;
&:nth-child(2n) {
background: linear-gradient(90deg, #e1e0ff 0%, #f5f5ff 100%);
}
&:nth-child(2n + 1) {
background: linear-gradient(90deg, #f5f5ff 0%, #f5f5ff 100%);
}
text:nth-child(1) {
width: 40px;
font-weight: 700;
}
text:nth-child(2) {
width: 100px;
}
text:nth-child(3) {
width: 100px;
}
}
position: relative;
.coll-icon {
position: absolute;
width: 15px;
height: 15px;
top: 10px;
right: 20px;
}
}
.select-pop {
::v-deep .wd-popup {
border-radius: 10px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
padding: 30rpx;
.title {
font-weight: 700;
}
.confirm-pay-title {
text-align: center;
font-weight: 700;
line-height: 40px;
border-bottom: 1px solid #eee;
}
.pay-price {
text-align: center;
color: #ff8d5f;
line-height: 60px;
height: 60px;
font-weight: 700;
font-size: 20px;
text {
margin-right: -5px;
font-size: 14px;
}
}
.content {
padding: 0px 0 16px;
.order-info {
margin: 10px 0;
border-bottom: 1px solid #eee;
padding: 10px 0;
display: flex;
justify-content: space-between;
align-items: center;
.order-title {
color: #a9aab5;
}
.order-value {
color: #333;
}
}
}
.children {
padding: 30rpx 0;
height: 320rpx;
overflow: auto;
margin-bottom: 16rpx;
.child-item {
display: flex;
align-items: center;
// margin: 10px 0;
border-bottom: 1px solid #eee;
padding: 30rpx 0;
.child-block {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
overflow: hidden;
image {
width: 100%;
height: 100%;
}
}
.child-name {
margin-left: 10px;
display: flex;
flex-direction: column;
align-items: flex-start;
.name {
font-weight: 700;
margin-bottom: 6px;
}
.expire {
color: #a9aab5;
font-size: 12px;
}
}
.child-select {
flex: 1;
text-align: right;
}
}
}
.ok-btn {
display: flex;
align-items: center;
justify-content: center;
.confirm-btn {
width: 440rpx;
height: 90rpx;
line-height: 90rpx;
line-height: 40px;
border-radius: 10px !important;
background-color: #615dff;
color: #fff;
}
}
}
}
.success-pop {
::v-deep .wd-popup {
width: 300px;
// height: 100px !important;
border-radius: 10px;
padding: 0 !important;
image {
display: block;
width: 40px;
height: 40px;
margin: auto;
margin-top: 16px;
}
.des {
width: 100%;
height: 50px;
padding-top: 10px;
border-bottom: 1px solid #eee;
text-align: center;
}
.close-btn {
height: 45px;
line-height: 45px;
text-align: center;
font-weight: 700;
}
}
}
}
::v-deep .tabs {
image {
width: 100%;
height: 360px;
border-radius: 6px;
}
}
::v-deep .wd-tabs__nav {
height: 60px;
}
::v-deep .wd-tabs__nav-container {
padding: 12px;
}
::v-deep .wd-tabs__container {
margin-top: 12px;
}
::v-deep .wd-tabs {
background-color: transparent;
}
::v-deep .wd-tabs__nav-item {
box-sizing: border-box;
margin: 0 3px;
height: 35px;
line-height: 35px;
border-radius: 5px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
&:first-child {
border-radius: 5px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
transition: none;
background-color: #f9f9fb;
}
::v-deep .wd-tabs__line {
display: none;
}
::v-deep .wd-tabs__nav-item.is-active {
background-color: #615dff;
color: #f1f1f1;
position: relative;
z-index: 1;
}
</style>