调整选课页面跳转逻辑

This commit is contained in:
ywyonui 2025-08-06 20:29:45 +08:00
parent a27e4e8a40
commit 55c62cc033
23 changed files with 1788 additions and 259 deletions

View File

@ -4,89 +4,68 @@ import { get, post } from "@/utils/request";
*
*/
export const jcGetJcBzListApi = async (params: any) => {
return await get("/mobile/jc/getJcBzList", params);
return await get("/mobile/jz/jc/getJcBzList", params);
};
/**
*
*/
export const jcGetJcBzDetailApi = async (params: any) => {
return await get("/mobile/jc/getJcBzDetail", params);
return await get("/mobile/jz/jc/getJcBzDetail", params);
};
/**
*
*/
export const jcBmJcBzApi = async (params: any) => {
return await post("/mobile/jc/bmJcBz", params);
return await post("/mobile/jz/jc/bmJcBz", params);
};
/**
*
*/
export const jcGetXsBmJcBzListApi = async (params: any) => {
return await get("/mobile/jc/getXsBmJcBzList", params);
return await get("/mobile/jz/jc/getXsBmJcBzList", params);
};
/**
*
*/
export const jcCancelBmJcBzApi = async (params: any) => {
return await post("/mobile/jc/cancelBmJcBz", params);
return await post("/mobile/jz/jc/cancelBmJcBz", params);
};
/**
*
*/
export const jcGetBmExpiredTimeApi = async (params: any) => {
return await get("/mobile/jc/getBmExpiredTime", params);
return await get("/mobile/jz/jc/getBmExpiredTime", params);
};
/**
*
*/
export const jcFqJcBzJfjApi = async (params: any) => {
return await post("/mobile/jc/fqJcBzJfj", params);
return await post("/mobile/jz/jc/fqJcBzJfj", params);
};
/**
*
*/
export const jcJcBzJfCxjApi = async (params: any) => {
return await post("/mobile/jc/jcBzJfcx", params);
return await post("/mobile/jz/jc/jcBzJfcx", params);
};
/**
*
*/
export const jcGetJcBzBmXsListApi = async (params: any) => {
return await get("/mobile/jc/getJcBzBmXsList", params);
};
/**
*
*/
export const jcGetJcBzTjInfoApi = async (params: any) => {
return await get("/mobile/jc/getJcBzTjInfo", params);
};
/**
*
*/
export const jcGetDqXqJcBzConfigApi = async () => {
return await get("/mobile/jc/getDqXqJcBzConfig");
return await get("/mobile/jz/jc/getJcBzBmXsList", params);
};
/**
*
*/
export const jcGetXsJcJlApi = async (params: any) => {
return await get("/mobile/jc/getXsJcJl", params);
};
/**
*
*/
export const jcGetJcBzPayExpiredTimeApi = async (params: any) => {
return await get("/mobile/jc/getJcBzPayExpiredTime", params);
return await get("/mobile/jz/jc/getXsJcJl", params);
};

View File

@ -11,28 +11,12 @@ export const glxsApi = async (params: any) => {
return await post("/open/login/glxs", params);
};
export const xkAddXkqdApi = async (params: any) => {
return await post("/mobile/xk/addXkqd", params);
};
export const xkListApi = async (params: any) => {
return await get("/mobile/xk/list", params);
};
export const xkXkqdApi = async (params: any) => {
return await get("/mobile/xk/xkqd", params);
};
export const kcjhFindKcjhByKcIdApi = async (params: any) => {
return await get("/api/kcjh/findKcjhByKcId", params);
};
export const xkgzsApi = async (params: any) => {
return await get("/api/gzs/findPage", params);
};
export const xkxkbmInfoApi = async (params: any) => {
return await get("/mobile/xk/xkbmInfo", params);
};
export const xkqddeleteApi = async (params: any) => {
return await post("/api/xkqd/delete?ids=" + params.ids);
};
/**
*
@ -67,20 +51,6 @@ export const drpkkbApi = async (params: any) => {
return await get("/mobile/jz/pkkb/drpkkb", params);
};
/**
*
*/
export const xsKxApi = async (params: any) => {
return await get("/mobile/jz/xkkc/list", params);
};
/**
*
*/
export const xsYxListApi = async (params: any) => {
return await get("/mobile/jz/xsxk/list", params);
};
/**
*
*/
@ -94,46 +64,6 @@ export const xsKsccApi = async (params: any) => {
export const xsKscjApi = async (params: any) => {
return await get("/mobile/jz/kscj", params);
};
/**
*
*/
export const jzXkQkjApi = async (params: any) => {
return await post("/mobile/jz/xk/qk", params);
};
/**
*
*/
export const jzGetQkExpiredTime = async (params: any) => {
return await get("/mobile/jz/xk/getQkExpiredTime", params);
};
/**
*
*/
export const jzXkFqJfjApi = async (params: any) => {
return await post("/mobile/jz/xk/fqjf", params);
};
/**
*
*/
export const jzXkCancelApi = async (params: any) => {
return await post("/mobile/jz/xk/cancel", params);
};
/**
* 退
*/
export const jzXkTkjApi = async (params: any) => {
return await post("/mobile/jz/xk/tk", params);
};
/**
*
*/
export const jzXkJfCxjApi = async (params: any) => {
return await post("/mobile/jz/xk/jfcx", params);
};
/**
*
*/

95
src/api/base/xkApi.ts Normal file
View File

@ -0,0 +1,95 @@
// 选课相关接口
import { get, post } from "@/utils/request";
/**
*
*/
export const xsKxApi = async (params: any) => {
return await get("/mobile/jz/xkkc/list", params);
};
/**
*
*/
export const xsYxListApi = async (params: any) => {
return await get("/mobile/jz/xsxk/list", params);
};
/**
*
*/
export const jzXkQkjApi = async (params: any) => {
return await post("/mobile/jz/xk/qk", params);
};
/**
*
*/
export const jzGetQkExpiredTime = async (params: any) => {
return await get("/mobile/jz/xk/getQkExpiredTime", params);
};
/**
*
*/
export const jzXkFqJfjApi = async (params: any) => {
return await post("/mobile/jz/xk/fqjf", params);
};
/**
*
*/
export const jzXkCancelApi = async (params: any) => {
return await post("/mobile/jz/xk/cancel", params);
};
/**
* 退
*/
export const jzXkTkjApi = async (params: any) => {
return await post("/mobile/jz/xk/tk", params);
};
/**
*
*/
export const jzXkJfCxjApi = async (params: any) => {
return await post("/mobile/jz/xk/jfcx", params);
};
/**
*
*/
export const checkXsXkByTypeApi = async (params: any) => {
return await get("/mobile/jz/xk/checkXsXkByType", params);
};
/**
*
*/
export const getXsXkStatusApi = async (params: any) => {
return await get("/mobile/jz/xkkc/list", params);
};
/**
*
*/
export const xkAddXkqdApi = async (params: any) => {
return await post("/mobile/xk/addXkqd", params);
};
export const xkListApi = async (params: any) => {
return await get("/mobile/xk/list", params);
};
export const xkXkqdApi = async (params: any) => {
return await get("/mobile/xk/xkqd", params);
};
export const xkxkbmInfoApi = async (params: any) => {
return await get("/mobile/xk/xkbmInfo", params);
};
export const xkqddeleteApi = async (params: any) => {
return await post("/api/xkqd/delete?ids=" + params.ids);
};

View File

@ -1,5 +1,5 @@
// const ip: string = "127.0.0.1:8897";
const ip: string = "lzcxsx.cn";
const ip: string = "127.0.0.1:8897";
// const ip: string = "lzcxsx.cn";
const fwqip: string = "lzcxsx.cn";
//const ip: string = "zhxy.yufangzc.com";
//const fwqip: string = "zhxy.yufangzc.com";

View File

@ -192,6 +192,13 @@
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/home/xsXz",
"style": {
"navigationBarTitleText": "学生选择",
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/xk/qk/xqk",
"style": {
@ -271,6 +278,72 @@
"navigationBarTitleText": "俱乐部告知书",
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/jc/index",
"style": {
"navigationBarTitleText": "检查",
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/jc/detail",
"style": {
"navigationBarTitleText": "检查详情",
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/jc/bm",
"style": {
"navigationBarTitleText": "报名",
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/jc/bm-detail",
"style": {
"navigationBarTitleText": "报名详情",
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/jc/record",
"style": {
"navigationBarTitleText": "记录",
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/jc/pay/index",
"style": {
"navigationBarTitleText": "支付",
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/jc/pay/wait",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false,
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"backgroundColor": "#ffffff"
}
},
{
"path": "pages/base/jc/pay/success",
"style": {
"navigationBarTitleText": "支付成功",
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/jc/pay/fail",
"style": {
"navigationBarTitleText": "支付失败",
"enablePullDownRefresh": false
}
}
],
"globalStyle": {

View File

@ -0,0 +1,229 @@
<template>
<view class="jc-bz-detail-card">
<view class="card-header">
<view class="card-title">{{ title || '就餐标准详情' }}</view>
</view>
<view class="jc-bz-list">
<view
v-for="(jcBz, index) in jcBzList"
:key="jcBz.id || index"
class="jc-bz-item"
>
<view class="jc-bz-info">
<image
class="jc-bz-image"
:src="getImageUrl(jcBz.lxtp)"
mode="aspectFill"
></image>
<view class="jc-bz-content">
<view class="jc-bz-name">{{ jcBz.bzmc || '暂无标准名称' }}</view>
<view class="jc-bz-price">
价格<text class="price-value">¥{{ jcBz.jfje || 0 }}</text>
</view>
<view class="jc-bz-desc">{{ jcBz.bzms || '暂无描述' }}</view>
<view class="jc-bz-time">有效期{{ jcBz.yxq || '暂无有效期信息' }}</view>
</view>
</view>
<!-- 可选的详情展开按钮 -->
<view v-if="showDetailBtn" class="detail-btn" @click="goToDetail(jcBz)">
<text>查看详情</text>
</view>
</view>
</view>
<!-- 汇总信息 -->
<view v-if="showSummary" class="summary-info">
<view class="summary-item">
<text class="label">已选择</text>
<text class="value">{{ jcBzList.length }} 个就餐标准</text>
</view>
<view class="summary-item">
<text class="label">总金额</text>
<text class="value total-price">¥{{ totalPrice }}</text>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { computed } from "vue";
import { imagUrl } from "@/utils";
//
interface JcBzData {
id?: string;
bzmc?: string;
jfje?: number;
bzms?: string;
yxq?: string;
lxtp?: string;
[key: string]: any;
}
interface Props {
jcBzList: JcBzData[];
title?: string;
showDetailBtn?: boolean;
showSummary?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
jcBzList: () => [],
title: '就餐标准详情',
showDetailBtn: false,
showSummary: true
});
const emit = defineEmits(['detail']);
//
const totalPrice = computed(() => {
let total = 0;
props.jcBzList.forEach(jcBz => {
total += jcBz.jfje || 0;
});
return total;
});
// URL
const getImageUrl = (imagePath: string) => {
return imagUrl(imagePath || '');
};
//
const goToDetail = (jcBz: JcBzData) => {
emit('detail', jcBz);
};
</script>
<style lang="scss" scoped>
.jc-bz-detail-card {
background-color: #fff;
border-radius: 8px;
margin: 15px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
overflow: hidden;
}
.card-header {
padding: 15px 15px 10px;
border-bottom: 1px solid #f0f0f0;
.card-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
}
.jc-bz-list {
.jc-bz-item {
padding: 15px;
border-bottom: 1px solid #f0f0f0;
display: flex;
align-items: flex-start;
&:last-child {
border-bottom: none;
}
.jc-bz-info {
flex: 1;
display: flex;
.jc-bz-image {
width: 80px;
height: 80px;
border-radius: 6px;
margin-right: 12px;
}
.jc-bz-content {
flex: 1;
.jc-bz-name {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 8px;
line-height: 1.3;
}
.jc-bz-price {
font-size: 14px;
color: #666;
margin-bottom: 6px;
.price-value {
color: #ff6b00;
font-weight: bold;
}
}
.jc-bz-desc {
font-size: 13px;
color: #999;
margin-bottom: 6px;
line-height: 1.4;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.jc-bz-time {
font-size: 12px;
color: #999;
}
}
}
.detail-btn {
margin-left: 10px;
padding: 6px 12px;
background-color: #2879ff;
color: #fff;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
}
}
}
.summary-info {
padding: 15px;
background-color: #f8f9fa;
border-top: 1px solid #f0f0f0;
.summary-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
.label {
font-size: 14px;
color: #666;
}
.value {
font-size: 14px;
color: #333;
font-weight: 500;
&.total-price {
color: #ff6b00;
font-weight: bold;
font-size: 16px;
}
}
}
}
</style>

View File

@ -60,7 +60,7 @@ const props = withDefaults(defineProps<{
});
// emit
const emit = defineEmits(['change'])
const emit = defineEmits(['change', 'selectedData'])
//
const jcBzList = ref<any>([]);
@ -89,6 +89,12 @@ const initSelectedStatus = () => {
jcBzList.value.forEach((jcBz: any) => {
jcBz.isSelected = selectedJcBzIds.includes(jcBz.id);
});
//
const selectedData = jcBzList.value.filter((item: any) =>
selectedJcBzIds.includes(item.id)
);
emit("selectedData", selectedData);
};
//
@ -128,6 +134,12 @@ const toggleSelection = (jcBz: any) => {
//
uni.setStorageSync("selectedJcBzIds", selectedJcBzIds);
emit("change", selectedJcBzIds);
//
const selectedData = jcBzList.value.filter((item: any) =>
selectedJcBzIds.includes(item.id)
);
emit("selectedData", selectedData);
}
const goToDetail = (jcBz: any) => {

View File

@ -0,0 +1,318 @@
<template>
<view class="jc-bz-picker">
<view class="picker-header" @click="showPicker">
<view class="picker-label">
<text class="label-text">{{ label }}</text>
<text class="required" v-if="required">*</text>
</view>
<view class="picker-value" :class="{ placeholder: !selectedText }">
{{ selectedText || placeholder }}
</view>
<view class="picker-arrow">
<uni-icons type="right" size="16" color="#999"></uni-icons>
</view>
</view>
<!-- 选择器弹窗 -->
<uni-popup ref="popup" type="bottom" :mask-click="false">
<view class="popup-content">
<view class="popup-header">
<view class="popup-title">{{ title }}</view>
<view class="popup-actions">
<view class="cancel-btn" @click="cancelPicker">取消</view>
<view class="confirm-btn" @click="confirmPicker">确定</view>
</view>
</view>
<view class="popup-body">
<view class="jc-bz-list">
<view
v-for="(jcBz, index) in jcBzList"
:key="jcBz.id || index"
class="jc-bz-item"
:class="{ selected: isSelected(jcBz) }"
@click="toggleSelection(jcBz)"
>
<view class="jc-bz-info">
<view class="jc-bz-name">{{ jcBz.bzmc }}</view>
<view class="jc-bz-price">¥{{ jcBz.jfje }}</view>
</view>
<view v-if="isSelected(jcBz)" class="selected-icon">
<uni-icons type="checkmarkempty" color="#2879ff" size="20"></uni-icons>
</view>
</view>
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from "vue";
import { jcGetJcBzListApi } from "@/api/base/jcApi";
interface JcBzData {
id: string;
bzmc: string;
jfje: number;
bzms?: string;
yxq?: string;
lxtp?: string;
[key: string]: any;
}
interface Props {
xsId: string;
label?: string;
placeholder?: string;
title?: string;
required?: boolean;
multiple?: boolean;
value?: string | string[];
}
const props = withDefaults(defineProps<Props>(), {
xsId: '',
label: '就餐标准',
placeholder: '请选择就餐标准',
title: '选择就餐标准',
required: false,
multiple: false,
value: ''
});
const emit = defineEmits(['change', 'update:value']);
const popup = ref();
const jcBzList = ref<JcBzData[]>([]);
const selectedIds = ref<string[]>([]);
//
const selectedText = computed(() => {
if (!selectedIds.value.length) return '';
if (props.multiple) {
const selectedItems = jcBzList.value.filter(item =>
selectedIds.value.includes(item.id)
);
return selectedItems.map(item => item.bzmc).join('、');
} else {
const selectedItem = jcBzList.value.find(item =>
selectedIds.value.includes(item.id)
);
return selectedItem?.bzmc || '';
}
});
//
const isSelected = (jcBz: JcBzData) => {
return selectedIds.value.includes(jcBz.id);
};
//
const getJcBzList = async () => {
try {
const res = await jcGetJcBzListApi({
xsId: props.xsId
});
if (res.resultCode === 1) {
//
const result = res.result;
if (result && result.jcBzList) {
jcBzList.value = result.jcBzList || [];
} else {
jcBzList.value = [];
}
}
} catch (error) {
console.error('获取就餐标准列表失败:', error);
}
};
//
const showPicker = () => {
popup.value.open();
};
//
const cancelPicker = () => {
popup.value.close();
};
//
const confirmPicker = () => {
const result = props.multiple ? selectedIds.value : selectedIds.value[0] || '';
emit('change', result);
emit('update:value', result);
popup.value.close();
};
//
const toggleSelection = (jcBz: JcBzData) => {
if (props.multiple) {
const index = selectedIds.value.indexOf(jcBz.id);
if (index > -1) {
selectedIds.value.splice(index, 1);
} else {
selectedIds.value.push(jcBz.id);
}
} else {
selectedIds.value = [jcBz.id];
}
};
//
watch(() => props.value, (newVal) => {
if (newVal) {
if (props.multiple && Array.isArray(newVal)) {
selectedIds.value = [...newVal];
} else if (!props.multiple && typeof newVal === 'string') {
selectedIds.value = [newVal];
}
} else {
selectedIds.value = [];
}
}, { immediate: true });
// ID
watch(() => props.xsId, (newVal) => {
if (newVal) {
getJcBzList();
}
});
onMounted(() => {
if (props.xsId) {
getJcBzList();
}
});
</script>
<style lang="scss" scoped>
.jc-bz-picker {
.picker-header {
display: flex;
align-items: center;
padding: 15px;
background-color: #fff;
border-radius: 8px;
border: 1px solid #e5e5e5;
.picker-label {
display: flex;
align-items: center;
margin-right: 10px;
.label-text {
font-size: 14px;
color: #333;
}
.required {
color: #ff4757;
margin-left: 2px;
}
}
.picker-value {
flex: 1;
font-size: 14px;
color: #333;
&.placeholder {
color: #999;
}
}
.picker-arrow {
margin-left: 10px;
}
}
}
.popup-content {
background-color: #fff;
border-radius: 12px 12px 0 0;
max-height: 70vh;
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid #f0f0f0;
.popup-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.popup-actions {
display: flex;
gap: 15px;
.cancel-btn,
.confirm-btn {
font-size: 14px;
padding: 5px 10px;
}
.cancel-btn {
color: #666;
}
.confirm-btn {
color: #2879ff;
font-weight: bold;
}
}
}
.popup-body {
max-height: 60vh;
overflow-y: auto;
.jc-bz-list {
padding: 0 20px;
.jc-bz-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
&.selected {
background-color: rgba(40, 121, 255, 0.05);
}
.jc-bz-info {
flex: 1;
.jc-bz-name {
font-size: 16px;
color: #333;
margin-bottom: 5px;
}
.jc-bz-price {
font-size: 14px;
color: #ff6b00;
font-weight: bold;
}
}
.selected-icon {
margin-left: 10px;
}
}
}
}
}
</style>

View File

@ -26,13 +26,11 @@
class="xk-item"
:class="{
'xk-item-active': curXk.id === xk.id,
'xk-item-selected': xk.selected,
}"
@click="switchXk(xk)"
>
<view class="xk-info">
<text class="xk-name">{{ xk.xkmc }}</text>
<text v-if="xk.selected" class="xk-selected-tip">已选择</text>
</view>
<u-icon
v-if="curXk.id === xk.id"
@ -49,7 +47,7 @@
<script setup lang="ts">
import { ref, watch } from "vue";
import { xsKxApi, xsYxListApi } from "@/api/base/server";
import { xsKxApi, xsYxListApi } from "@/api/base/xkApi";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
@ -223,11 +221,6 @@ if (props.xsId) {
background-color: rgba(64, 158, 255, 0.05);
}
&-selected {
background-color: rgba(64, 158, 255, 0.1);
border-left: 3px solid #409eff;
}
.xk-info {
flex: 1;
margin-left: 12px;
@ -239,14 +232,6 @@ if (props.xsId) {
margin-bottom: 4px;
display: block;
}
.xk-selected-tip {
font-size: 12px;
color: #409eff;
background-color: rgba(64, 158, 255, 0.1);
padding: 2px 6px;
border-radius: 4px;
}
}
}
}

View File

@ -50,7 +50,7 @@ async function submit() {
sign_file: sign_file.value,
});
uni.reLaunch({
url: "/pages/base/xk/qk/jlb",
url: "/pages/base/jc/bm",
});
}
</script>

View File

@ -0,0 +1,62 @@
<template>
<view class="interest-course">
<!-- 选课信息头部 - 固定部分 -->
<view class="selection-header">
<view class="header-content">
<!-- 学生选择部分 -->
<XsPicker :is-bar="true" @change="switchXs" />
</view>
</view>
</view>
</template>
<script setup lang="ts">
import XsPicker from "@/pages/base/components/XsPicker/index.vue"
import { useDataStore } from "@/store/modules/data";
import { useUserStore } from "@/store/modules/user";
const { getGlobal } = useDataStore();
const { checkXqk, checkJlb } = useUserStore();
const switchXs = (xs: any) => {
if (getGlobal.type == 1) {
checkXqk();
} else if (getGlobal.type == 2) {
checkJlb();
} else {
}
}
</script>
<style lang="scss" scoped>
.interest-course {
min-height: 100%;
background-color: #f5f7fa;
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.selection-header {
background: linear-gradient(135deg, #4a90e2, #2879ff);
padding: 20px 15px;
color: #fff;
border-radius: 0 0 15px 15px;
box-shadow: 0 4px 12px rgba(40, 121, 255, 0.2);
position: sticky;
top: 0;
left: 0;
right: 0;
z-index: 10;
.header-content {
display: flex;
flex-direction: column;
gap: 15px;
}
}
</style>

View File

@ -0,0 +1,78 @@
# JC模块 - 就餐标准管理
## 功能概述
JC模块是用于管理学生就餐标准的前端模块包含以下主要功能
### 1. 就餐标准列表 (`/pages/base/jc/index.vue`)
- 显示当前学期可用的就餐标准
- 支持学生选择器切换不同学生
- 展示就餐标准的基本信息(名称、价格、描述)
### 2. 就餐标准详情 (`/pages/base/jc/detail.vue`)
- 显示就餐标准的详细信息
- 包含标准描述、使用说明、注意事项等
- 支持图片展示
### 3. 就餐标准报名 (`/pages/base/jc/bm.vue`)
- 支持多选就餐标准
- 实时计算总金额
- 确认报名后跳转到支付页面
### 4. 支付流程 (`/pages/base/jc/pay/`)
- 支付页面 (`index.vue`):显示报名信息和支付倒计时
- 支付等待页面 (`wait.vue`):处理支付回调
- 支付成功页面 (`success.vue`):支付成功提示
- 支付失败页面 (`fail.vue`):支付失败处理
### 5. 就餐记录 (`/pages/base/jc/record.vue`)
- 显示学生的就餐记录历史
- 包含就餐日期、状态、地点等信息
## 组件结构
### 核心组件
- `JcBzList` (`/pages/base/components/JcBzList/index.vue`):就餐标准列表组件
- `JcRecordList` (`/pages/base/components/JcRecordList/index.vue`):就餐记录列表组件
### API接口
所有JC相关的API接口都集中在 `jcApi.ts` 文件中,包括:
- 获取就餐标准列表
- 获取就餐标准详情
- 学生报名就餐标准
- 取消报名
- 发起支付
- 查询支付状态
- 获取就餐记录等
## 数据流
1. **报名流程**
- 学生选择就餐标准 → 确认报名 → 跳转支付页面 → 完成支付
2. **数据存储**
- 使用 Pinia store 管理状态
- 本地存储保存选中状态
- 支付数据通过 store 传递
## 技术特点
- 采用 Vue 3 Composition API
- 使用 TypeScript 进行类型检查
- 响应式设计,支持移动端
- 模块化组件设计
- 统一的错误处理机制
## 注意事项
1. 支付倒计时功能需要后端提供倒计时接口
2. 多选功能需要合理处理数据同步
3. 支付状态查询需要定时轮询
4. 图片资源需要正确的路径处理
## 扩展建议
1. 可以添加就餐标准筛选功能
2. 支持按时间范围查询就餐记录
3. 添加就餐统计功能
4. 支持批量操作就餐标准

View File

@ -0,0 +1,295 @@
<template>
<view class="bm-detail-page">
<!-- 页面头部 -->
<view class="page-header">
<view class="header-title">报名详情</view>
</view>
<!-- 学生信息 -->
<view class="info-card">
<view class="card-title">学生信息</view>
<view class="student-info">
<view class="info-item">
<text class="label">姓名</text>
<text class="value">{{ curXs.xm }}</text>
</view>
<view class="info-item">
<text class="label">班级</text>
<text class="value">{{ curXs.bjmc }}</text>
</view>
</view>
</view>
<!-- 报名信息 -->
<view class="info-card">
<view class="card-title">报名信息</view>
<view class="bm-info">
<view class="info-item">
<text class="label">报名时间</text>
<text class="value">{{ bmInfo.bmTime || '暂无' }}</text>
</view>
<view class="info-item">
<text class="label">报名状态</text>
<text class="value status-success">已报名</text>
</view>
</view>
</view>
<!-- 就餐标准详情 -->
<JcBzDetailCard
v-if="bmJcBzList.length > 0"
:jc-bz-list="bmJcBzList"
title="已报名的就餐标准"
:show-detail-btn="true"
:show-summary="false"
@detail="goToDetail"
/>
<!-- 支付信息 -->
<view class="info-card">
<view class="card-title">支付信息</view>
<view class="payment-info">
<view class="info-item">
<text class="label">缴费金额</text>
<text class="value total-price">¥{{ totalAmount }}</text>
</view>
<view class="info-item">
<text class="label">支付状态</text>
<text class="value" :class="paymentStatusClass">{{ paymentStatusText }}</text>
</view>
</view>
</view>
<!-- 底部操作 -->
<view class="bottom-actions">
<view class="action-btn primary" @click="goToPay" v-if="paymentStatus === 'unpaid'">
立即支付
</view>
<view class="action-btn secondary" @click="goBack">
返回
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from "vue";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
import JcBzDetailCard from "@/pages/base/components/JcBzDetailCard/index.vue";
import { jcGetXsBmJcBzListApi } from "@/api/base/jcApi";
const { getCurXs } = useUserStore();
const { getData, setData } = useDataStore();
const curXs = computed(() => getCurXs);
//
const bmInfo = ref<any>({});
//
const bmJcBzList = ref<any[]>([]);
//
const paymentStatus = ref<string>('unpaid'); // unpaid, paid, expired
//
const totalAmount = computed(() => {
let total = 0;
bmJcBzList.value.forEach(jcBz => {
total += jcBz.jfje || 0;
});
return total;
});
//
const paymentStatusClass = computed(() => {
switch (paymentStatus.value) {
case 'paid':
return 'status-success';
case 'expired':
return 'status-error';
default:
return 'status-warning';
}
});
//
const paymentStatusText = computed(() => {
switch (paymentStatus.value) {
case 'paid':
return '已支付';
case 'expired':
return '已过期';
default:
return '待支付';
}
});
//
const getBmInfo = async () => {
try {
const res = await jcGetXsBmJcBzListApi({
xsId: curXs.value.id
});
if (res.resultCode === 1) {
bmJcBzList.value = res.result || [];
//
bmInfo.value = {
bmTime: new Date().toLocaleString(),
//
};
}
} catch (error) {
console.error('获取报名信息失败:', error);
}
};
//
const goToDetail = (jcBz: any) => {
setData({
...getData,
jcBzData: jcBz
});
uni.navigateTo({
url: '/pages/base/jc/detail'
});
};
//
const goToPay = () => {
// store
setData({
...getData,
xsId: curXs.value.id,
jcBzIds: bmJcBzList.value.map(item => item.id),
jcBzList: bmJcBzList.value,
totalJe: totalAmount.value,
});
uni.redirectTo({
url: '/pages/base/jc/pay/index'
});
};
//
const goBack = () => {
uni.navigateBack();
};
onMounted(() => {
getBmInfo();
});
</script>
<style lang="scss" scoped>
.bm-detail-page {
min-height: 100vh;
background-color: #f5f7fa;
padding-bottom: 100px;
}
.page-header {
background: linear-gradient(135deg, #4a90e2, #2879ff);
padding: 20px 15px;
color: #fff;
text-align: center;
.header-title {
font-size: 18px;
font-weight: bold;
}
}
.info-card {
margin: 15px;
background-color: #fff;
border-radius: 8px;
padding: 15px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
.card-title {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 15px;
border-bottom: 1px solid #f0f0f0;
padding-bottom: 10px;
}
.student-info,
.bm-info,
.payment-info {
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
.label {
font-size: 14px;
color: #666;
}
.value {
font-size: 14px;
color: #333;
font-weight: 500;
&.total-price {
color: #ff6b00;
font-weight: bold;
font-size: 16px;
}
&.status-success {
color: #52c41a;
}
&.status-warning {
color: #faad14;
}
&.status-error {
color: #ff4d4f;
}
}
}
}
}
.bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 15px;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
display: flex;
gap: 15px;
.action-btn {
flex: 1;
height: 44px;
line-height: 44px;
text-align: center;
border-radius: 22px;
font-size: 16px;
&.primary {
background-color: #2879ff;
color: #fff;
}
&.secondary {
background-color: #fff;
color: #333;
border: 1px solid #ddd;
}
}
}
</style>

View File

@ -10,18 +10,38 @@
<!-- 可滚动的内容区域 -->
<view class="scrollable-content">
<JcBzList :xs-id="curXs.id" :can-selected="true" :multiple="true" @change="onJcBzChange" />
<!-- 就餐标准选择器 -->
<view class="form-section">
<JcBzPicker
:xs-id="curXs.id"
label="就餐标准"
placeholder="请选择就餐标准"
:multiple="false"
:required="true"
v-model="selectedJcBzId"
@change="onJcBzChange"
/>
</view>
<!-- 选中标准的详情展示 -->
<JcBzDetailCard
v-if="selectedJcBz"
:jc-bz-list="[selectedJcBz]"
title="已选择的就餐标准"
:show-detail-btn="true"
:show-summary="false"
@detail="goToDetail"
/>
</view>
<!-- 底部操作区域 -->
<view class="bottom-action">
<view class="selected-info">
<text>已选择{{ selectedCount }} 个就餐标准</text>
<text class="total-price">总金额¥{{ totalPrice }}</text>
<text class="total-price">缴费金额¥{{ totalPrice }}</text>
</view>
<view class="action-buttons">
<view class="cancel-btn" @click="goBack">取消</view>
<view class="confirm-btn" @click="confirmBm" :class="{ disabled: selectedCount === 0 }">确认报名</view>
<view class="confirm-btn" @click="confirmBm" :class="{ disabled: !selectedJcBzId }">确认报名</view>
</view>
</view>
</view>
@ -29,46 +49,72 @@
<script setup lang="ts">
import XsPicker from "@/pages/base/components/XsPicker/index.vue"
import JcBzList from "@/pages/base/components/JcBzList/index.vue"
import JcBzPicker from "@/pages/base/components/JcBzPicker/index.vue"
import JcBzDetailCard from "@/pages/base/components/JcBzDetailCard/index.vue"
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
import { jcBmJcBzApi } from "@/api/base/jcApi";
import { jcBmJcBzApi, jcGetJcBzListApi } from "@/api/base/jcApi";
const { getCurXs, getUser } = useUserStore();
const { getData, setData } = useDataStore();
const curXs = computed(() => getCurXs);
// ID
const selectedJcBzIds = ref<string[]>([]);
//
const selectedJcBzList = ref<any[]>([]);
// ID
const selectedJcBzId = ref<string>('');
//
const selectedJcBz = ref<any>(null);
//
const selectedCount = computed(() => selectedJcBzIds.value.length);
//
//
const totalPrice = computed(() => {
let total = 0;
selectedJcBzList.value.forEach(jcBz => {
total += jcBz.jfje || 0;
});
return total;
return selectedJcBz.value?.jfje || 0;
});
//
const onJcBzChange = (ids: string[]) => {
selectedJcBzIds.value = ids;
// ID
//
updateSelectedJcBzList();
const onJcBzChange = (id: string) => {
selectedJcBzId.value = id;
// ID
updateSelectedJcBz(id);
};
//
const updateSelectedJcBzList = () => {
// selectedJcBzIds
// 使
selectedJcBzList.value = [];
//
const updateSelectedJcBz = async (id: string) => {
if (!id) {
selectedJcBz.value = null;
return;
}
try {
//
const res = await jcGetJcBzListApi({
xsId: curXs.value.id
});
if (res.resultCode === 1) {
//
const result = res.result;
const allJcBzList = result && result.jcBzList ? result.jcBzList : [];
//
selectedJcBz.value = allJcBzList.find((jcBz: any) =>
jcBz.id === id
) || null;
}
} catch (error) {
console.error('获取就餐标准详情失败:', error);
}
};
//
const goToDetail = (jcBz: any) => {
// store
setData({
...getData,
jcBzData: jcBz
});
//
uni.navigateTo({
url: '/pages/base/jc/detail'
});
};
//
@ -78,7 +124,7 @@ const goBack = () => {
//
const confirmBm = async () => {
if (selectedCount.value === 0) {
if (!selectedJcBzId.value) {
uni.showToast({
title: "请选择就餐标准",
icon: "none",
@ -89,9 +135,13 @@ const confirmBm = async () => {
try {
const res = await jcBmJcBzApi({
xsId: curXs.value.id,
jcBzIds: selectedJcBzIds.value,
bzId: selectedJcBzId.value,
jzId: getUser.jzId,
userId: getUser.userId,
njId: curXs.value.njId,
njmcId: curXs.value.njmcId,
njmc: curXs.value.njmc,
bc: curXs.value.bc,
xm: curXs.value.xsxm,
});
if (res.resultCode === 1) {
@ -104,8 +154,8 @@ const confirmBm = async () => {
setData({
...getData,
xsId: curXs.value.id,
jcBzIds: selectedJcBzIds.value,
jcBzList: selectedJcBzList.value,
jcBzIds: [selectedJcBzId.value],
jcBzList: [selectedJcBz.value],
totalJe: totalPrice.value,
});
@ -114,10 +164,29 @@ const confirmBm = async () => {
url: '/pages/base/jc/pay/index'
});
} else {
uni.showToast({
title: res.result?.message || "报名失败",
icon: "none",
});
//
if (res.result?.message && res.result.message.includes('已报名')) {
uni.showModal({
title: '提示',
content: '您已经报名了该就餐标准,是否查看报名详情?',
success: (modalRes) => {
if (modalRes.confirm) {
//
uni.redirectTo({
url: '/pages/base/jc/bm-detail'
});
} else {
//
uni.navigateBack();
}
}
});
} else {
uni.showToast({
title: res.result?.message || "报名失败",
icon: "none",
});
}
}
} catch (error) {
console.error('报名失败:', error);
@ -169,6 +238,14 @@ onBeforeUnmount(() => {
-webkit-overflow-scrolling: touch; // iOS
}
.form-section {
padding: 15px;
background-color: #fff;
margin: 15px;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
}
.bottom-action {
background-color: #fff;
padding: 15px;
@ -176,7 +253,7 @@ onBeforeUnmount(() => {
.selected-info {
display: flex;
justify-content: space-between;
justify-content: center;
align-items: center;
margin-bottom: 15px;
font-size: 14px;
@ -185,7 +262,7 @@ onBeforeUnmount(() => {
.total-price {
color: #ff6b00;
font-weight: bold;
font-size: 16px;
font-size: 18px;
}
}

View File

@ -17,7 +17,7 @@
<view class="success-text">恭喜你报名成功</view>
</view>
<!-- 这里显示学生信息和报名的就餐标准信息 -->
<!-- TODO: 这里显示学生信息和报名的就餐标准信息 -->
</view>
<template #bottom>

View File

@ -40,7 +40,7 @@
<script setup lang="ts">
import XkPayXs from "@/pages/base/components/XkPayXs/index.vue"
import XkPayXkqd from "@/pages/base/components/XkPayXkqd/index.vue"
import { jzGetQkExpiredTime, jzXkCancelApi, jzXkFqJfjApi } from "@/api/base/server";
import { jzGetQkExpiredTime, jzXkCancelApi, jzXkFqJfjApi } from "@/api/base/xkApi";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
const { getCurXs, getUser } = useUserStore();

View File

@ -46,7 +46,7 @@
<script lang="ts" setup>
import { onLoad } from "@dcloudio/uni-app";
import { jzGetQkExpiredTime, jzXkCancelApi, jzXkJfCxjApi } from "@/api/base/server";
import { jzGetQkExpiredTime, jzXkCancelApi, jzXkJfCxjApi } from "@/api/base/xkApi";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
const { getCurXs, initWs, setWsCallback } = useUserStore();

View File

@ -17,19 +17,12 @@
<!-- 可滚动的内容区域 -->
<view class="scrollable-content">
<!-- 如果当前选课已被选择显示提示信息 -->
<view v-if="curXk && curXk.selected" class="selected-notice">
<view class="notice-content">
<u-icon name="info-circle" color="#409EFF" size="24"></u-icon>
<text class="notice-text">{{ curXk.message || '您已经选择了该选课下的课程,如果需要重新选课,请联系教师进行处理' }}</text>
</view>
</view>
<!-- 如果当前选课未被选择显示课程列表 -->
<XkkcList v-else :xk="curXk" :can-selected="true" :multiple="true" @change="changeXkkc" />
<!-- 显示课程列表 -->
<XkkcList :xk="curXk" :can-selected="true" :multiple="true" @change="changeXkkc" />
</view>
<!-- 底部报名按钮 - 固定部分 -->
<view class="register-btn-container" v-if="curXk && !curXk.selected">
<view class="register-btn-container" v-if="curXk && curXk.id">
<view class="selected-count-info" v-if="selectedXkkcIds && selectedXkkcIds.length > 0">
已选 {{ selectedXkkcIds.length }} 门课程
</view>
@ -43,7 +36,7 @@ import XsPicker from "@/pages/base/components/XsPicker/index.vue"
import XkPicker from "@/pages/base/components/XkPicker/index.vue"
import XkCountdown from "@/pages/base/components/XkCountdown/index.vue"
import XkkcList from "@/pages/base/components/XkkcList/index.vue"
import { jzXkQkjApi } from "@/api/base/server";
import { jzXkQkjApi } from "@/api/base/xkApi";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
import dayjs from "dayjs";
@ -116,15 +109,6 @@ const submit = async () => {
return;
}
//
if (curXk.value && curXk.value.selected) {
uni.showToast({
title: "您已经选择了该选课下的课程,如需重新选课,请联系教师进行处理",
icon: "none",
});
return;
}
//
if (curXk.value && curXk.value.xkkstime) {
const now = dayjs().valueOf();
@ -281,28 +265,6 @@ const submit = async () => {
-webkit-overflow-scrolling: touch; // iOS
}
//
.selected-notice {
padding: 20px 15px;
.notice-content {
display: flex;
align-items: flex-start;
gap: 12px;
background: linear-gradient(135deg, #e3f2fd, #f3e5f5);
padding: 16px;
border-radius: 12px;
border-left: 4px solid #409EFF;
.notice-text {
flex: 1;
font-size: 14px;
line-height: 1.5;
color: #333;
}
}
}
.register-btn-container {
position: sticky;
bottom: 0;

View File

@ -3,40 +3,171 @@
<!-- 选课信息头部 - 固定部分 -->
<view class="selection-header">
<view class="header-content">
<!-- 选课类型选择部分 -->
<XkPicker title="兴趣课信息" :is-qk="false" xklx-id="962488654" :xs-id="curXs.id" @change="switchXk" />
<!-- 学生选择部分 -->
<XsPicker :is-bar="true" />
<!-- 第一行选课类型选择 -->
<view class="top-row" v-if="!xsFlag">
<XkPicker title="兴趣课信息" :is-qk="true" xklx-id="962488654" :xs-id="curXs.id" @change="switchXk" />
</view>
<!-- 第二行学生选择和倒计时 -->
<view class="bottom-row">
<XsPicker :is-bar="true" @change="switchXs" />
<XkCountdown :xk="curXk" @over="xkTimeOver" v-if="curXk && curXk.id" />
</view>
</view>
</view>
<!-- 可滚动的内容区域 -->
<view class="scrollable-content">
<XkkcList :xk="curXk" />
<!-- 显示课程列表 -->
<XkkcList :xk="curXk" :can-selected="true" :multiple="false" @change="changeXkkc" />
</view>
<!-- 底部报名按钮 - 固定部分 -->
<view class="register-btn-container" v-if="curXk && curXk.id">
<view class="selected-count-info" v-if="selectedXkkcIds && selectedXkkcIds.length > 0">
已选 {{ selectedXkkcIds.length }} 门课程
</view>
<view class="register-btn" @click="submit">点击报名</view>
</view>
</view>
</template>
<script setup lang="ts">
import XsPicker from "@/pages/base/components/XsPicker/index.vue"
import XkPicker from "@/pages/base/components/XkPicker/index.vue"
import XkCountdown from "@/pages/base/components/XkCountdown/index.vue"
import XkkcList from "@/pages/base/components/XkkcList/index.vue"
import { jzXkQkjApi } from "@/api/base/xkApi";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
import dayjs from "dayjs";
const { getCurXs } = useUserStore();
const { getCurXs, getUser } = useUserStore();
const { setData, getData } = useDataStore();
const { sign_file } = getData;
const curXs = computed(() => getCurXs);
const curXk = ref<any>({});
const selectedXkkcIds = ref<any>([]);
const xsFlag = ref(true);
//
const checkEnrollmentStatus = (xk: any) => {
if (!xk || !xk.xkjstime) return;
const now = new Date().getTime();
const endTime = new Date(xk.xkjstime).getTime();
if (now > endTime) {
//
const courseInfo = encodeURIComponent(JSON.stringify(xk));
uni.navigateTo({
url: `/pages/base/xk/qk/yjz?courseInfo=${courseInfo}`
});
return true;
}
return false;
};
//
const switchXk = (xk: any) => {
curXk.value = xk;
//
selectedXkkcIds.value = [];
uni.setStorageSync("selectedXkkcIds", []);
//
if (checkEnrollmentStatus(xk)) {
return;
}
}
const switchXs = (xs: any) => {
xsFlag.value = false;
}
//
const xkTimeOver = (val: any) => {
console.log('选课时间结束:', val);
//
//
}
//
const changeXkkc = (ids: any) => {
selectedXkkcIds.value = ids;
}
//
const submit = async () => {
//
if (selectedXkkcIds.value.length === 0) {
uni.showToast({
title: "请选择课程!",
icon: "none",
});
return;
}
//
if (curXk.value && curXk.value.xkkstime) {
const now = dayjs().valueOf();
const startTime = dayjs(curXk.value.xkkstime).valueOf();
if (now < startTime) {
uni.showToast({
title: "选课还未开始,请耐心等待!",
icon: "none",
});
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", []);
res.result.backUrl = "/pages/base/xk/qk/xqk";
setData(res.result);
xsFlag.value = true;
setTimeout(() => {
xsFlag.value = false;
}, 1000);
if (curXk.value.sfjf === 1) {
//
uni.navigateTo({
url: "/pages/base/xk/pay/index",
});
} else {
//
uni.navigateTo({
url: "/pages/base/xk/pay/success",
});
}
} else {
uni.showToast({
title: res.message,
icon: "none",
});
}
}
//
onBeforeUnmount(() => {
});
</script>
<style lang="scss" scoped>
@ -94,6 +225,36 @@ onBeforeUnmount(() => {
flex-direction: column;
gap: 15px;
.top-row {
display: flex;
align-items: center;
:deep(.title-section) {
width: 100%;
}
}
.bottom-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 15px;
//
:deep(.xs-bar) {
flex: 1;
min-width: 0;
}
//
:deep(.countdown-section) {
flex-shrink: 0;
background: rgba(255, 255, 255, 0.1);
border-radius: 6px;
padding: 6px 10px;
backdrop-filter: blur(10px);
}
}
}
}
@ -104,4 +265,32 @@ onBeforeUnmount(() => {
-webkit-overflow-scrolling: touch; // iOS
}
.register-btn-container {
position: sticky;
bottom: 0;
left: 0;
right: 0;
padding: 15px;
background-color: #fff;
z-index: 1;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
.selected-count-info {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.register-btn {
height: 50px;
line-height: 50px;
text-align: center;
background-color: #2879ff;
color: #fff;
border-radius: 25px;
font-size: 16px;
font-weight: 500;
}
}
</style>

View File

@ -19,25 +19,9 @@ import { checkOpenId } from "@/api/system/login";
import { refreshPermissionCache } from "@/utils/permission";
const { setGlobal } = useDataStore();
const { afterLoginAction } = useUserStore();
const userStore = useUserStore();
const isShow = ref(true);
function toHome(data: any) {
if (data.type == 1) {
uni.reLaunch({
url: "/pages/base/gzs/xkXqk",
});
} if (data.type == 2) {
uni.reLaunch({
url: "/pages/base/gzs/xkJlb",
});
} else {
uni.reLaunch({
url: "/pages/base/home/index",
});
}
}
onLoad(async (data: any) => {
setGlobal(data);
if (data && data.openId) {
@ -46,20 +30,18 @@ onLoad(async (data: any) => {
if (res.resultCode == 1 && res.result) {
//
afterLoginAction(res.result);
userStore.afterLoginAction(res.result);
// changeTime
if (data.changeTime) {
const userStore = useUserStore();
const currentPermissions = userStore.getAuth;
if (currentPermissions && currentPermissions.length > 0) {
refreshPermissionCache(currentPermissions, data.changeTime);
}
}
//
toHome(data);
userStore.toHome(data.type);
} else {
uni.reLaunch({
url: "/pages/system/login/login",

View File

@ -266,24 +266,8 @@ function removeStudent(index: number) {
}
}
function toHome() {
if (getGlobal.type == 1) {
uni.reLaunch({
url: "/pages/base/gzs/xkXqk",
});
} else if (getGlobal.type == 2) {
uni.reLaunch({
url: "/pages/base/gzs/xkJlb",
});
} else {
uni.reLaunch({
url: "/pages/base/home/index",
});
}
}
const { getGlobal, getAppCode } = useDataStore();
const { afterLoginAction } = useUserStore();
const userStore = useUserStore();
async function submit() {
for (const student of students.value) {
if (!student.xstx) {
@ -313,19 +297,16 @@ async function submit() {
});
hideLoading();
if (res.resultCode == 1) {
afterLoginAction(res.result);
userStore.afterLoginAction(res.result);
// changeTime
if (res.result && res.result.changeTime) {
const userStore = useUserStore();
const currentPermissions = userStore.getAuth;
if (currentPermissions && currentPermissions.length > 0) {
refreshPermissionCache(currentPermissions, res.result.changeTime);
}
}
toHome();
userStore.toHome(getGlobal.type);
} else {
showToast({ title: res.message || "提交失败", icon: "none" });
}

View File

@ -1,5 +1,6 @@
import { defineStore } from "pinia";
import { authenticationApi, loginCode, loginPass, weChatLogin, checkOpenId } from "@/api/system/login";
import { checkXsXkByTypeApi, xsKxApi } from "@/api/base/xkApi";
import { AUTH_KEY } from "@/config";
import { imagUrl } from "@/utils";
import { useWebSocket } from '@/utils/webSocket/webSocket'
@ -194,6 +195,192 @@ export const useUserStore = defineStore({
}
})
},
/**
*
* @param type
*/
async toHome(type: number) {
if (type == 1) {
// 兴趣课逻辑: 判断当前学生列表
if (this.userdata.xsList && this.userdata.xsList.length === 1) {
this.checkXqk();
} else if (this.userdata.xsList && this.userdata.xsList.length > 1) {
uni.reLaunch({
url: "/pages/base/home/xsXz",
});
}
} else if (type == 2) {
// 俱乐部逻辑: 判断当前学生列表
if (this.userdata.xsList && this.userdata.xsList.length == 1) {
this.checkJlb();
} else if (this.userdata.xsList && this.userdata.xsList.length > 1) {
uni.reLaunch({
url: "/pages/base/home/xsXz",
});
}
} else {
uni.reLaunch({
url: "/pages/base/home/index",
});
}
},
// 校验兴趣课 - 优化版本
async checkXqk() {
try {
// 使用getXsKx接口获取详细选课信息
const res = await xsKxApi({
xsId: this.curXs.id,
njmcId: this.curXs.njmcId,
xklxId: "962488654" // 兴趣课类型ID
});
if (res.resultCode === 1) {
const data = res.result;
const type = data.type;
switch (type) {
case 1: // 待支付
uni.reLaunch({
url: "/pages/base/xk/pay/index",
});
return;
case 2: // 可选课列表 - 跳转到抢课页面
uni.reLaunch({
url: "/pages/base/xk/qk/xqk",
});
return;
case 3: // 已支付 - 跳转到详情页
uni.reLaunch({
url: "/pages/base/xk/xqk",
});
return;
default:
// 默认跳转到告知书页面
uni.reLaunch({
url: "/pages/base/gzs/xqk",
});
return;
}
}
// 接口调用失败,使用备用方案
this.checkXqkFallback();
} catch (error) {
console.error("查询兴趣课状态失败:", error);
// 异常情况,使用备用方案
this.checkXqkFallback();
}
},
// 兴趣课备用检查方案
async checkXqkFallback() {
try {
const res = await checkXsXkByTypeApi({
xsId: this.curXs.id,
xklxId: "962488654" // 兴趣课类型ID
});
if (res.resultCode === 1) {
const checkResult = res.result;
if (checkResult === 1) {
uni.reLaunch({
url: "/pages/base/xk/pay/index",
});
return;
} else if (checkResult === 2) {
uni.reLaunch({
url: "/pages/base/xk/xqk",
});
return;
}
}
uni.reLaunch({
url: "/pages/base/gzs/xqk",
});
} catch (error) {
console.error("备用方案也失败:", error);
uni.reLaunch({
url: "/pages/base/gzs/xqk",
});
}
},
// 校验俱乐部 - 优化版本
async checkJlb() {
try {
// 使用getXsKx接口获取详细选课信息
const res = await xsKxApi({
xsId: this.curXs.id,
njmcId: this.curXs.njmcId,
xklxId: "816059832" // 俱乐部类型ID
});
if (res.resultCode === 1) {
const data = res.result;
const type = data.type;
switch (type) {
case 1: // 待支付
uni.reLaunch({
url: "/pages/base/xk/pay/index",
});
return;
case 2: // 可选课列表 - 跳转到抢课页面
uni.reLaunch({
url: "/pages/base/xk/qk/jlb",
});
return;
case 3: // 已支付 - 跳转到详情页
uni.reLaunch({
url: "/pages/base/xk/jlb",
});
return;
default:
// 默认跳转到告知书页面
uni.reLaunch({
url: "/pages/base/gzs/jlb",
});
return;
}
}
// 接口调用失败,使用备用方案
this.checkJlbFallback();
} catch (error) {
console.error("查询俱乐部状态失败:", error);
// 异常情况,使用备用方案
this.checkJlbFallback();
}
},
// 俱乐部备用检查方案
async checkJlbFallback() {
try {
const res = await checkXsXkByTypeApi({
xsId: this.curXs.id,
xklxId: "816059832" // 俱乐部类型ID
});
if (res.resultCode === 1) {
const checkResult = res.result;
if (checkResult === 1) {
uni.reLaunch({
url: "/pages/base/xk/pay/index",
});
return;
} else if (checkResult === 2) {
uni.reLaunch({
url: "/pages/base/xk/jlb",
});
return;
}
}
uni.reLaunch({
url: "/pages/base/gzs/jlb",
});
} catch (error) {
console.error("备用方案也失败:", error);
uni.reLaunch({
url: "/pages/base/gzs/jlb",
});
}
},
/**
* @description:
*/

95
src/utils/xkUtils.ts Normal file
View File

@ -0,0 +1,95 @@
import { getXsXkStatusApi } from "@/api/base/xkApi";
import { useUserStore } from "@/store/modules/user";
/**
*
*/
export class XkUtils {
/**
*
* @param xklxId ID
* @returns
*/
static async checkXkStatus(xklxId: string) {
const userStore = useUserStore();
const curXs = userStore.getCurXs;
try {
const res = await getXsXkStatusApi({
xsId: curXs.id,
njmcId: curXs.njmcId,
xklxId: xklxId
});
if (res.resultCode === 1) {
return res.result;
}
return null;
} catch (error) {
console.error("检查选课状态失败:", error);
return null;
}
}
/**
*
* @param type
* @returns
*/
static getStatusDescription(type: number): string {
switch (type) {
case 1:
return "有待支付的选课,请先完成支付";
case 2:
return "可以选择的课程";
case 3:
return "已选择并完成支付的选课";
default:
return "暂无可选课程";
}
}
/**
*
* @param xkList
* @returns
*/
static canQk(xkList: any[]): boolean {
if (!xkList || xkList.length === 0) {
return false;
}
// 检查是否有可选的课程
return xkList.some((xk: any) => {
return !xk.selected && xk.xkkcs && xk.xkkcs.length > 0;
});
}
/**
*
* @param xkList
* @returns
*/
static getAvailableXkList(xkList: any[]): any[] {
if (!xkList || xkList.length === 0) {
return [];
}
return xkList.filter((xk: any) => {
return !xk.selected && xk.xkkcs && xk.xkkcs.length > 0;
});
}
/**
*
* @param xkList
* @returns
*/
static getSelectedXk(xkList: any[]): any | null {
if (!xkList || xkList.length === 0) {
return null;
}
return xkList.find((xk: any) => xk.selected) || null;
}
}