完善抢课和取消选课

This commit is contained in:
ywyonui 2025-09-02 20:57:37 +08:00
parent 542f7878bb
commit 717f32cdf3
10 changed files with 325 additions and 162 deletions

View File

@ -12,11 +12,11 @@
mode="aspectFill"
></image>
<view class="course-details">
<view class="course-name">{{ xkqd.xkmc }}</view>
<view class="course-teacher">开课老师{{ xkqd.jsxm }}</view>
<view class="course-name">{{ xkqd.xkmc || xkqd.kcmc }}</view>
<view class="course-teacher">开课老师{{ xkqd.jsxm || xkqd.jsName }}</view>
<view class="course-location">上课地点{{ xkqd.kcdd }}</view>
<view class="course-price"
>金额<text class="price-value">¥{{ xkqd.jfje }}</text></view
>金额<text class="price-value">¥{{ xkqd.kcje || xkqd.jfje }}</text></view
>
</view>
</view>
@ -27,10 +27,10 @@
<script setup lang="ts">
import { imagUrl } from "@/utils";
import { useDataStore } from "@/store/modules/data";
const { getData } = useDataStore();
const { getQk } = useDataStore();
//
const xkqdList = computed(() => getData.xkqdList);
const xkqdList = computed(() => getQk.xkqdList);
</script>

View File

@ -105,7 +105,8 @@ const loadXkList = async () => {
} else {
const qk = getQk || {};
if (props.xsId === qk.xsId && qk.xklxId === props.xklxId && qk.xsXkStatus === "KQK") {
xkList.value = qk.xkList || [];
switchXk(xkList.value[0]);
} else {
const res = await getXsXkListApi(params);
if (res.resultCode === 1) {

View File

@ -53,11 +53,9 @@ const { setKcData } = useDataStore();
const props = withDefaults(defineProps<{
xk: any,
canSelected: boolean,
multiple: boolean,
}>(), {
xk: () => ({}),
canSelected: false,
multiple: false,
});
// emit
@ -75,16 +73,9 @@ const toggleSelection = (xkkc: any) => {
let selectedXkkcIds = uni.getStorageSync("selectedXkkcIds") || [];
if (xkkc.isSelected) {
xkkc.isSelected = false;
if (props.multiple) {
//
selectedXkkcIds = selectedXkkcIds.filter(
(id: string) => id !== xkkc.id
);
} else {
//
selectedXkkcIds = [];
}
// xkkc.hasNum--;
selectedXkkcIds = selectedXkkcIds.filter(
(id: string) => id !== xkkc.id
);
} else {
//
const maxNum = xkkc.maxNum || 0;
@ -98,9 +89,8 @@ const toggleSelection = (xkkc: any) => {
});
return;
}
//
if (xkkc.studyTime && props.multiple) {
if (xkkc.studyTime && props.xk.kxNum > 1) {
const hasTimeConflict = xkkcList.value.some((item: any) => {
//
return item.isSelected &&
@ -117,9 +107,16 @@ const toggleSelection = (xkkc: any) => {
return;
}
}
//
if (props.multiple) {
//
if (props.xk.kxNum > 1) {
if (selectedXkkcIds.length >= props.xk.kxNum) {
uni.showToast({
title: '已选课程数量已达上限!请取消其他课程再选择!',
icon: 'none',
duration: 2000
});
return;
}
//
if (!selectedXkkcIds.includes(xkkc.id)) {
selectedXkkcIds.push(xkkc.id);
@ -142,12 +139,13 @@ const toggleSelection = (xkkc: any) => {
const goToDetail = (xkkc: any) => {
setKcData(xkkc);
uni.navigateTo({
url: `/pages/base/xk/detail`,
url: `/pages/base/xk/detail`,
});
};
const switchXk = (xk: any) => {
xkkcList.value = xk.xkkcs;
xkkcList.value = xk.xkkcs || xk.xkkcList || [];
console.log('switchXk', xk)
//
xkkcList.value.sort((a: any, b: any) => {
@ -217,13 +215,13 @@ const switchXk = (xk: any) => {
//
watch(() => props.xk, (newVal) => {
if (newVal && newVal.xkkcs) {
if (newVal && (newVal.xkkcs || newVal.xkkcList)) {
switchXk(newVal);
}
});
//
if (props.xk && props.xk.xkkcs) {
if (props.xk && (props.xk.xkkcs || props.xk.xkkcList)) {
switchXk(props.xk);
}

View File

@ -30,9 +30,11 @@
</view>
<view class="action-buttons">
<view class="cancel-btn" @click="cancelRegistration">取消报名</view>
<view class="pay-btn" :class="{ 'pay-btn--disabled': isSubmitting }" @click="payNow">
{{ isSubmitting ? '支付中...' : '立即支付' }}
<view class="cancel-btn" @click="cancelRegistration">
取消报名
</view>
<view class="pay-btn" :class="{ 'pay-btn--disabled': isPaySubmitting }" @click="payNow">
{{ isPaySubmitting ? '支付中...' : '立即支付' }}
</view>
</view>
</view>
@ -42,37 +44,29 @@
<script setup lang="ts">
import XkPayXs from "@/pages/base/xk/components/XkPayXs/index.vue"
import XkPayXkqd from "@/pages/base/xk/components/XkPayXkqd/index.vue"
import { jzGetQkExpiredTime, jzXkCancelApi, jzXkFqJfjApi } from "@/api/base/xkApi";
import { jzXkCancelApi, jzXkFqJfjApi } from "@/api/base/xkApi";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
const { getCurXs, getUser } = useUserStore();
const { getData, setData } = useDataStore();
import { useDebounce } from "@/utils/debounce";
import { PageUtils } from "@/utils/pageUtil";
const { getUser, setWsCallback } = useUserStore();
const { getQk } = useDataStore();
//
const curXs = computed(() => getCurXs);
//
const xkqdList = computed(() => getData.xkqdList);
//
const { isProcessing: isPaySubmitting, debounce: payDebounce } = useDebounce(2000);
//
const { isProcessing: isCancelSubmitting, debounce: cancelDebounce } = useDebounce(2000);
const qk = ref<any>({});
//
const totalJe = computed(() => {
// xkqdList.value
if (!xkqdList.value || !xkqdList.value.length) {
return 0;
}
let total = 0;
for (let i = 0; i < xkqdList.value.length; i++) {
total += xkqdList.value[i].jfje;
}
return total;
});
const totalJe = ref(0);
//
const countdownTime = ref("1分20秒");
const countdownTime = ref("");
let timer: any = null;
let seconds = 1 * 60 + 20; // 120
//
const isSubmitting = ref(false);
//
const openPaymentPage = (payUrl: string) => {
console.log('开始打开支付页面:', payUrl);
@ -151,10 +145,14 @@ const startCountdown = () => {
seconds--;
if (seconds <= 0) {
clearInterval(timer);
jzXkCancelApi({
xsId: getData.xsId,
xkId: getData.xkId
});
try {
jzXkCancelApi({
xsId: qk.value.xsId,
xkId: qk.value.xkId
});
} catch (error) {
console.error(error);
}
uni.showModal({
title: "支付超时",
content: "支付已超时,请重新选课",
@ -165,7 +163,6 @@ const startCountdown = () => {
});
return;
}
const minutes = Math.floor(seconds / 60);
const remainSeconds = seconds % 60;
countdownTime.value = `${minutes}${remainSeconds}`;
@ -184,35 +181,33 @@ const cancelRegistration = () => {
uni.showModal({
title: "取消报名",
content: "确定要取消报名吗?",
success: async (res) => {
success: cancelDebounce(async (res) => {
if (res.confirm) {
await jzXkCancelApi({
xsId: getData.xsId,
xkId: getData.xkId
});
uni.showToast({
title: "已取消报名",
icon: "success",
});
goBack();
try {
const res = await jzXkCancelApi({
xsId: qk.value.xsId,
xkId: qk.value.xkId
});
if (res.resultCode === 1) {
uni.showLoading({ title: "取消报名中,请稍后..." });
} else {
uni.showToast({ title: res.message, icon: "none" });
goBack();
}
} catch (error) {
console.log(error);
}
}
},
}),
});
};
//
const payNow = async () => {
//
if (isSubmitting.value) {
return;
}
try {
isSubmitting.value = true;
const payNow = payDebounce(async () => {
try {
const res = await jzXkFqJfjApi({
xsId: getData.xsId,
xkId: getData.xkId,
xsId: qk.value.xsId,
xkId: qk.value.xkId,
jffs: "线上缴费", // 线线
zfptLx: "四川农信", // TODO:
jzId: getUser.jzId,
@ -224,43 +219,51 @@ const payNow = async () => {
const payUrl = res.result.cashierPayHtml;
openPaymentPage(payUrl);
/* setData({
...getData,
...res.result
});
uni.redirectTo({
url: `/pages/base/xk/pay/wait?payUrl=${encodeURIComponent(res.result.cashierPayHtml)}`
});*/
}
} catch (error: any) {
console.log(error);
uni.showToast({
title: error.message || "发起支付失败",
icon: "error",
duration: 2000,
});
// const url = "https://www.baidu.com";
// uni.redirectTo({
// url: `/pages/base/xk/pay/wait?payUrl=${encodeURIComponent(url)}`
// });
} finally {
//
setTimeout(() => {
isSubmitting.value = false;
}, 2000);
goBack();
}
};
});
onMounted(async() => {
try {
const res = await jzGetQkExpiredTime({ xsId: getCurXs.id} );
console.log('获取倒计时', res);
seconds = res.result;
qk.value = getQk || {};
const xkqdList = qk.value.xkqdList || [];
totalJe.value = 0;
for (let i = 0; i < xkqdList.length; i++) {
const xkqd = xkqdList[i];
const je = xkqd.kcje || xkqd.jfje;
totalJe.value += je;
}
const expireTime = qk.value.expireTime || new Date().getTime();
seconds = Math.floor((expireTime - new Date().getTime()) / 1000);
startCountdown();
} catch (error) {
console.log(error);
goBack();
}
//
setWsCallback((type: string, res: any) => {
// data
const dataObj = JSON.parse(res.data);
if (dataObj.action === 'qk') {
uni.hideLoading();
if (dataObj.data === "cancel") {
uni.showToast({
title: dataObj.message || '取消成功',
icon: "none",
duration: 3000
});
goBack();
}
}
});
});
onUnmounted(() => {

View File

@ -10,7 +10,7 @@
<!-- 第二行学生选择和倒计时 -->
<view class="bottom-row">
<XsPicker :is-bar="true" @change="switchXs" />
<XkCountdown :xk="curXk" @over="xkTimeOver" v-if="curXk && curXk.id" />
<XkCountdown :xk="curXk" @over="xkTimeOver" v-if="curXk && (curXk.id || curXk.xkId)" />
</view>
</view>
</view>
@ -18,15 +18,20 @@
<!-- 可滚动的内容区域 -->
<view class="scrollable-content">
<!-- 显示课程列表 -->
<XkkcList :xk="curXk" :can-selected="true" :multiple="curXk.kxNum > 1" @change="changeXkkc" />
<XkkcList :xk="curXk" :can-selected="true" @change="changeXkkc" />
</view>
<!-- 底部报名按钮 - 固定部分 -->
<view class="register-btn-container" v-if="curXk && curXk.id">
<view class="register-btn-container" v-if="curXk && (curXk.id || curXk.xkId)">
<view class="selected-count-info" v-if="selectedXkkcIds && selectedXkkcIds.length > 0">
已选 {{ selectedXkkcIds.length }} 门课程
</view>
<view class="register-btn" @click="submit">点击报名</view>
<view
class="register-btn"
:class="{ 'register-btn--disabled': isSubmitting }"
@click="submit">
{{ isSubmitting ? '报名中...' : '点击报名' }}
</view>
</view>
</view>
</template>
@ -40,9 +45,12 @@ import XkkcList from "@/pages/base/xk/components/XkkcList/index.vue"
import { jzXkQkjApi } from "@/api/base/xkApi";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
import { useDebounce } from "@/utils/debounce";
import dayjs from "dayjs";
import { PageUtils } from "@/utils/pageUtil";
import { hideLoading } from "@/utils/uniapp";
const { getCurXs, getUser } = useUserStore();
const { getCurXs, getUser, initWs, setWsCallback } = useUserStore();
const { setData, getData } = useDataStore();
const { sign_file } = getData;
@ -53,6 +61,9 @@ const curXs = computed(() => getCurXs);
const curXk = ref<any>({});
const selectedXkkcIds = ref<any>([]);
// isSubmitting useDebounce
const { isProcessing: isSubmitting, debounce } = useDebounce(2000);
const xsFlag = ref(true);
//
@ -102,8 +113,15 @@ const changeXkkc = (ids: any) => {
selectedXkkcIds.value = ids;
}
//
const goBack = () => {
uni.reLaunch({
url: '/pages/base/home/index'
});
}
//
const submit = async () => {
const submit = debounce(async () => {
//
if (selectedXkkcIds.value.length === 0) {
uni.showToast({
@ -126,50 +144,45 @@ const submit = async () => {
return;
}
}
uni.showLoading({
title: "抢课中...",
});
const params = {
xsId: curXs.value.id,
xm: curXs.value.xm,
njmc: curXs.value.njmc,
njmcId: curXs.value.njmcId,
njId: curXs.value.njId,
bc: (curXs.value.njbc || '') + (curXs.value.bjmc || ''),
xkId: curXk.value.id,
xkkcIds: selectedXkkcIds.value,
jzId: getUser.jzId,
qmFile: sign_file ? sign_file.value : "",
};
const res = await jzXkQkjApi(params);
uni.hideLoading();
if (res.resultCode === 1) {
selectedXkkcIds.value = [];
uni.setStorageSync("selectedXkkcIds", []);
setData(res.result);
xsFlag.value = true;
setTimeout(() => {
xsFlag.value = false;
}, 1000);
if (curXk.value.sfjf === 1) {
//
uni.navigateTo({
url: "/pages/base/xk/pay/index",
});
uni.showLoading({ title: "开始抢课..." });
try {
const params = {
xsId: curXs.value.id,
xkId: curXk.value.id,
xkkcIds: selectedXkkcIds.value,
jzId: getUser.jzId,
qmFile: sign_file ? sign_file.value : "",
};
const res = await jzXkQkjApi(params);
if (res.resultCode === 1) {
selectedXkkcIds.value = [];
uni.setStorageSync("selectedXkkcIds", []);
setData(res.result);
xsFlag.value = true;
setTimeout(() => {
xsFlag.value = false;
}, 1000);
hideLoading();
//
uni.showLoading({ title: "抢课中,请稍后..." });
} else {
//
uni.navigateTo({
url: "/pages/base/xk/pay/success?xklxId=" + xklxId.value,
hideLoading();
uni.showToast({
title: res.message,
icon: "none",
});
}
} else {
} catch (error: any) {
hideLoading();
console.error('报名失败:', error);
uni.showToast({
title: res.message,
title: error.message || "报名失败",
icon: "none",
});
}
}
}
});
onLoad((options:any) => {
xklxId.value = options.xklxId || '';
@ -178,8 +191,35 @@ onLoad((options:any) => {
case '816059832': { title.value = '俱乐部信息'; } break;
default: {
uni.reLaunch({ url: '/pages/base/home/index' });
return;
}
}
initWs();
//
setWsCallback((type: string, res: any) => {
// data
const dataObj = JSON.parse(res.data);
if (dataObj.action === 'qk') {
uni.hideLoading();
if (dataObj.code === 1 && dataObj.data === "qk") {
if (curXk.value.sfjf === 1) {
PageUtils.toHome(xklxId.value);
} else {
//
uni.navigateTo({
url: "/pages/base/xk/pay/success?xklxId=" + xklxId.value,
});
}
} else {
uni.showToast({
title: dataObj.message,
icon: "none",
duration: 3000
});
goBack();
}
}
});
});
</script>
@ -303,7 +343,15 @@ onLoad((options:any) => {
border-radius: 25px;
font-size: 16px;
font-weight: 500;
//
&.register-btn--disabled {
background-color: #a0cfff;
color: #ffffff;
cursor: not-allowed;
pointer-events: none;
}
}
}
</style>
</style>

View File

@ -18,7 +18,7 @@
<!-- 可滚动的内容区域 -->
<view class="scrollable-content">
<!-- 显示课程列表 -->
<XkkcList :xk="curXk" :can-selected="true" :multiple="true" @change="changeXkkc" />
<XkkcList :xk="curXk" :can-selected="true" @change="changeXkkc" />
</view>
<!-- 底部报名按钮 - 固定部分 -->

View File

@ -18,7 +18,7 @@
<!-- 可滚动的内容区域 -->
<view class="scrollable-content">
<!-- 显示课程列表 -->
<XkkcList :xk="curXk" :can-selected="true" :multiple="false" @change="changeXkkc" />
<XkkcList :xk="curXk" :can-selected="true" @change="changeXkkc" />
</view>
<!-- 底部报名按钮 - 固定部分 -->

View File

@ -106,19 +106,22 @@ export const useUserStore = defineStore({
this.wsCallback = callback;
},
initWs() {
if (this.ws) {
if (typeof this.ws.closeConnect === 'function') {
this.ws.closeConnect();
}
this.ws = null;
}
this.exitWs();
this.ws = useWebSocket(`/zhxy/webSocket/${this.userdata.userId}`, (type: string, res: any) => {
// 判断this.wsCallback是函数调用
if (typeof this.wsCallback === "function") {
this.wsCallback(type, res);
}
});
this.ws.reconnect();
this.ws.reconnect(this.token);
},
exitWs() {
if (this.ws) {
if (typeof this.ws.closeConnect === 'function') {
this.ws.closeConnect();
}
this.ws = null;
}
},
/**
* @description:
@ -218,10 +221,7 @@ export const useUserStore = defineStore({
this.setCurXs({})
this.setAuth([])
this.setXsPickerInitialized(false); // 注销时重置学生选择器状态
if (this.ws) {
this.ws.closeConnect();
this.ws = null;
}
this.exitWs();
this.wsCallback = defWsCallback;
useDicStore().setData({});
useCommonStore().setData({});

107
src/utils/debounce.ts Normal file
View File

@ -0,0 +1,107 @@
// src/utils/debounce.ts
import { ref } from 'vue';
/**
*
* @param func
* @param delay
* @param immediate
* @returns
*/
export function debounce(func: Function, wait: number, immediate: boolean = false) {
let timeout: NodeJS.Timeout | null;
return function (this: any, ...args: any[]) {
const context = this;
const later = function () {
timeout = null;
if (!immediate) func.apply(context, args);
};
const callNow = immediate && !timeout;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
/**
*
*
*/
export class DebounceManager {
private states: Map<string, boolean> = new Map();
/**
*
* @param key
* @param value
* @param duration
*/
setState(key: string, value: boolean, duration?: number): void {
this.states.set(key, value);
if (value && duration) {
setTimeout(() => {
this.states.set(key, false);
}, duration);
}
}
/**
*
* @param key
* @returns
*/
getState(key: string): boolean {
return this.states.get(key) || false;
}
/**
*
*/
reset(): void {
this.states.clear();
}
}
/**
* Vue组合式API防抖函数
* @param delay
* @returns
*/
export function useDebounce(delay: number = 1000) {
const isProcessing = ref(false);
const debounce = <T extends (...args: any[]) => Promise<any>>(
func: T
): ((...args: Parameters<T>) => Promise<ReturnType<T> | void>) => {
return async (...args: Parameters<T>): Promise<ReturnType<T> | void> => {
// 如果正在处理中,则阻止新的调用
if (isProcessing.value) {
return;
}
isProcessing.value = true;
try {
const result = await func(...args);
return result;
} finally {
// 延迟重置状态,防止快速重复点击
setTimeout(() => {
isProcessing.value = false;
}, delay);
}
};
};
const reset = () => {
isProcessing.value = false;
};
return {
isProcessing,
debounce,
reset
};
}

View File

@ -14,7 +14,7 @@ export const PageUtils = {
*
* @param lxId JC: 就餐816059832: 俱乐部962488654: 兴趣课
*/
async toHome(lxId: string) {
async toHome(lxId?: string) {
// 没有类型,则跳转首页
if (!lxId) {
uni.reLaunch({
@ -84,13 +84,19 @@ export const PageUtils = {
});
} break;
case 'QKZ': { // QKZ抢课中
uni.reLaunch({
url: "/pages/base/xk/qk/index?xklxId=" + xklxId,
});
} break;
case 'YQK': { // YQK已选课
uni.reLaunch({
url: "/pages/base/xk/pay/index?xklxId=" + xklxId,
});
} break;
case 'DZF': { // DZF待支付
uni.reLaunch({
url: "/pages/base/xk/pay/index?xklxId=" + xklxId,
});
} break;
case 'YZF': { // YZF已支付
uni.reLaunch({