调整选课使用公共组件

This commit is contained in:
ywyonui 2025-06-29 21:31:05 +08:00
parent c12aa5c773
commit 829508ba57
8 changed files with 373 additions and 2387 deletions

View File

@ -3,343 +3,37 @@
<!-- 选课信息头部 - 固定部分 --> <!-- 选课信息头部 - 固定部分 -->
<view class="selection-header"> <view class="selection-header">
<view class="header-content"> <view class="header-content">
<view class="title-section" @click="clickShowXkSelector"> <!-- 选课类型选择部分 -->
<view class="title"> <XkPicker title="俱乐部信息" :is-xs="true" xklx-id="816059832" :xs-id="curXs.id" :nj-id="curXs.njId" @change="switchXk" />
<text v-if="xkData && xkData.xkmc">{{ xkData.xkmc }}</text>
<text v-else>选课信息</text>
</view>
<view class="switch-btn" v-if="xkList.length > 1">切换</view>
</view>
<!-- 学生选择部分 --> <!-- 学生选择部分 -->
<view class="student-selector-bar" @click="clickShowXsSelector"> <XsPicker :is-bar="true" />
<view class="user-avatar">
<image
:src="curXs.xstxUrl || '/static/base/home/11222.png'"
class="w-full h-full"
></image>
</view>
<view class="student-info">
<text class="student-name">{{ curXs.xm }}</text>
<text class="student-class"
>{{ curXs.njmc }} {{ curXs.bjmc }}</text
>
</view>
<view class="switch-btn" v-if="xsList.length > 1">切换</view>
</view>
</view> </view>
</view> </view>
<!-- 可滚动的内容区域 --> <!-- 可滚动的内容区域 -->
<view class="scrollable-content"> <view class="scrollable-content">
<!-- 课程网格列表 --> <XkkcList :xk="curXk" :can-selected="false" />
<view class="course-grid" v-if="courseListData.length > 0">
<view
v-for="(course, index) in courseListData"
:key="course.id || index"
class="course-item"
:class="{ selected: course.isSelected }"
>
<view class="course-name">{{ course.kcmc }}</view>
<view class="register-info">
<text>上课人数</text>
<text class="register-count">{{ course.ybmr }}</text>
</view>
<view class="detail-btn" @click.stop="viewCourseDetail(course)"
>详情</view
>
<view v-if="course.isSelected" class="selected-mark">
<uni-icons
type="checkbox-filled"
color="#3FBF72"
size="22"
></uni-icons>
</view>
</view>
</view>
<!-- 暂无数据提示 -->
<view v-else class="empty-course-list">
<view class="empty-icon">
<u-icon name="list" size="50" color="#C8C9CC"></u-icon>
</view>
<view class="empty-text">暂无课程数据</view>
</view>
</view> </view>
<view>
<!-- 学生选择弹窗 -->
<u-popup
:show="showXsFlag"
@close="showXsFlag = false"
mode="bottom"
round="10"
>
<view class="student-selector">
<view class="selector-header">
<text class="selector-title">选择学生</text>
<u-icon
name="close"
size="20"
@click="showXsFlag = false"
></u-icon>
</view>
<view class="student-list">
<view
v-for="(xs, index) in xsList"
:key="index"
class="student-item"
:class="{
'student-item-active': curXs.id === xs.id
}"
@click="switchXs(xs)"
>
<view class="student-avatar">
<image
:src="xs.xstxUrl || '/static/base/home/11222.png'"
class="w-full h-full"
></image>
</view>
<view class="student-info">
<text class="student-name">{{ xs.xm }}</text>
<text class="student-class"
>{{ xs.njmc }} {{ xs.bjmc }}</text
>
</view>
<u-icon
v-if="curXs.id === xs.id"
name="checkmark"
color="#409EFF"
size="20"
></u-icon>
</view>
</view>
</view>
</u-popup>
</view>
<view>
<!-- 俱乐部选择弹窗 -->
<u-popup
:show="showXkFlag"
@close="showXkFlag = false"
mode="bottom"
round="10"
>
<view class="student-selector">
<view class="selector-header">
<text class="selector-title">选择俱乐部</text>
<u-icon
name="close"
size="20"
@click="showXkFlag = false"
></u-icon>
</view>
<view class="student-list">
<view
v-for="(xk, index) in xkList"
:key="index"
class="student-item"
:class="{
'student-item-active': xkData.id === xk.id
}"
@click="switchXk(xk)"
>
<view class="student-info">
<text class="student-name">{{ xk.xkmc }}</text>
</view>
<u-icon
v-if="xkData.id === xk.id"
name="checkmark"
color="#409EFF"
size="20"
></u-icon>
</view>
</view>
</view>
</u-popup>
</view>
</view> </view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { import XsPicker from "@/pages/base/components/XsPicker/index.vue"
ref, import XkPicker from "@/pages/base/components/XkPicker/index.vue"
computed, import XkkcList from "@/pages/base/components/XkkcList/index.vue"
reactive,
onBeforeUnmount,
watch,
onMounted,
} from "vue";
import { useUserStore } from "@/store/modules/user"; import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
import { xkListApi, xsXkListApi } from "@/api/base/server";
import dayjs from "dayjs";
const { getUser, getCurXs } = useUserStore(); const { getCurXs } = useUserStore();
const { getData, setKcData, setData } = useDataStore();
const { sign_file } = getData;
// const curXs = computed(() => getCurXs);
const xsList = computed(() => {
return getUser.xsList;
});
const curXs = ref<any>({}); const curXk = ref<any>({});
//
// const switchXk = (xk: any) => {
const showXsFlag = ref(false); curXk.value = xk;
const showXkFlag = ref(false);
if (xsList.value.length > 1) {
showXsFlag.value = true;
} }
const xkList = ref<any>([]);
const xkData = ref();
const kcStatus = ref(false);
const courseInfo = ref<any>({});
onMounted(() => {
curXs.value = getCurXs;
//
uni.showLoading({
title: "加载中...",
});
loadCourseList(curXs.value);
});
//
const loadCourseList = (xs: any) => {
if (!xs) {
uni.hideLoading();
return;
}
xsXkListApi({
xsId: xs.id,
njId: xs.njId,
xklxId: "816059832",
})
.then((res) => {
if (res.resultCode == 1) {
if (res.result && res.result.length) {
xkList.value = res.result;
xkData.value = res.result[0];
} else {
xkList.value = [];
xkData.value = {};
}
uni.hideLoading();
}
})
.catch(() => {
uni.hideLoading();
});
};
//
function clickShowXsSelector() {
if (xsList.value.length > 1) {
showXsFlag.value = true;
}
}
//
function clickShowXkSelector() {
if (xkList.value.length > 1) {
showXkFlag.value = true;
}
}
//
function switchXs(xs: any) {
curXs.value = xs;
showXsFlag.value = false;
//
uni.showLoading({
title: "加载中...",
});
//
uni.showToast({
title: `已切换到${xs.xm}`,
icon: "none",
});
//
loadCourseList(xs);
}
//
function switchXk(xk: any) {
xkData.value = xk;
showXkFlag.value = false;
//
uni.showToast({
title: `已切换到${xk.xkmc}`,
icon: "none",
});
}
//
const displayCourseList = computed(() => {
if (
!xkData.value ||
!xkData.value.xkkcs ||
!Array.isArray(xkData.value.xkkcs)
) {
return [];
}
// ID
const selectedCourseIds = uni.getStorageSync("selectedCourseIds") || [];
//
return xkData.value.xkkcs.map((course: any) => ({
...course,
isSelected: selectedCourseIds.includes(course.id),
}));
});
//
const courseListData = ref<any[]>([]);
//
const selectedCoursesCount = computed(() => {
return courseListData.value.filter((course: any) => course.isSelected).length;
});
//
watch(
displayCourseList,
(newVal) => {
courseListData.value = JSON.parse(JSON.stringify(newVal));
// courseInfo
const selectedCourseIds = uni.getStorageSync("selectedCourseIds") || [];
if (selectedCourseIds.length > 0) {
const selectedCourses = newVal.filter((course: any) =>
selectedCourseIds.includes(course.id)
);
courseInfo.value = selectedCourses;
}
},
{ immediate: true }
);
//
const viewCourseDetail = (course: any) => {
setKcData(course);
uni.navigateTo({
url: `/pages/base/course-selection/detail`,
});
};
// //
onBeforeUnmount(() => { onBeforeUnmount(() => {
}); });
@ -400,108 +94,6 @@ onBeforeUnmount(() => {
flex-direction: column; flex-direction: column;
gap: 15px; gap: 15px;
.title-section {
display: flex;
align-items: center;
.title {
flex: 1 0 1px;
font-size: 24px;
font-weight: bold;
}
.subtitle {
font-size: 14px;
opacity: 0.8;
}
.switch-btn {
padding: 5px 15px;
background-color: rgba(255, 255, 255, 0.2);
color: #fff;
border-radius: 15px;
font-size: 15px;
}
}
//
.student-selector-bar {
display: flex;
align-items: center;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 10px;
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #fff;
overflow: hidden;
margin-right: 10px;
}
.student-info {
flex: 1;
display: flex;
flex-direction: column;
.student-name {
font-size: 16px;
font-weight: 500;
margin-bottom: 2px;
}
.student-class {
font-size: 12px;
opacity: 0.8;
}
}
.switch-btn {
padding: 4px 12px;
background-color: rgba(255, 255, 255, 0.2);
color: #fff;
border-radius: 20px;
font-size: 13px;
}
}
.countdown-section {
.countdown-title {
font-size: 14px;
margin-bottom: 8px;
}
.countdown-timer {
display: flex;
align-items: center;
.time-block {
background-color: rgba(255, 255, 255, 0.2);
border-radius: 6px;
min-width: 40px;
padding: 5px 8px;
text-align: center;
.time-value {
font-size: 20px;
font-weight: bold;
display: block;
}
.time-unit {
font-size: 12px;
opacity: 0.9;
}
}
.time-separator {
font-size: 20px;
font-weight: bold;
margin: 0 5px;
}
}
}
} }
} }
@ -512,211 +104,4 @@ onBeforeUnmount(() => {
-webkit-overflow-scrolling: touch; // iOS -webkit-overflow-scrolling: touch; // iOS
} }
.course-grid {
display: flex;
flex-wrap: wrap;
padding: 15px 15px 0 15px;
.course-item {
position: relative;
width: calc(50% - 10px);
margin-bottom: 15px;
background-color: #fff;
border-radius: 8px;
padding: 15px;
box-sizing: border-box;
border: 1px solid transparent;
transition: all 0.3s ease;
&:nth-child(odd) {
margin-right: 10px;
}
&:nth-child(even) {
margin-left: 10px;
}
&.selected {
border: 1px solid #3fbf72;
background-color: rgba(63, 191, 114, 0.05);
box-shadow: 0 2px 8px rgba(63, 191, 114, 0.15);
}
.course-name {
font-size: 16px;
font-weight: 500;
color: #333;
margin-bottom: 10px;
}
.register-info {
font-size: 14px;
color: #666;
margin-bottom: 12px;
.register-count {
color: #2879ff;
}
}
.detail-btn {
display: inline-block;
color: #2879ff;
font-size: 14px;
}
.selected-mark {
position: absolute;
top: -6px;
right: -6px;
}
}
}
.register-btn-container {
position: sticky;
bottom: 0;
left: 0;
right: 0;
padding: 15px;
background-color: #fff;
z-index: 10;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
.register-btn {
height: 50px;
line-height: 50px;
text-align: center;
background-color: #2879ff;
color: #fff;
border-radius: 25px;
font-size: 16px;
font-weight: 500;
}
}
/* 学生选择器弹窗样式 */
.student-selector {
background-color: #ffffff;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
padding-bottom: 20px;
.selector-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border-bottom: 1px solid #f2f2f2;
.selector-title {
font-size: 16px;
font-weight: 500;
color: #303133;
}
}
.student-list {
padding: 0 15px;
.student-item {
display: flex;
align-items: center;
padding: 15px 0;
border-bottom: 1px solid #f2f2f2;
&:last-child {
border-bottom: none;
}
&-active {
background-color: rgba(64, 158, 255, 0.05);
}
.student-avatar {
width: 45px;
height: 45px;
border-radius: 50%;
background-color: #f0f0f0;
overflow: hidden;
flex-shrink: 0;
}
.student-info {
flex: 1;
margin-left: 12px;
.student-name {
font-size: 15px;
font-weight: 500;
color: #303133;
margin-bottom: 4px;
}
.student-class {
font-size: 13px;
color: #606266;
}
}
}
}
}
/* 全局图片样式 */
.w-full {
width: 100%;
}
.h-full {
height: 100%;
}
//
.empty-course-list {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
text-align: center;
.empty-icon {
margin-bottom: 20px;
background-color: #f5f6f7;
width: 80px;
height: 80px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.empty-text {
font-size: 18px;
font-weight: 500;
color: #303133;
margin-bottom: 8px;
}
.empty-desc {
font-size: 14px;
color: #909399;
max-width: 80%;
line-height: 1.5;
}
}
/* 选课已结束样式 */
.enrollment-ended {
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 8px;
padding: 12px 15px;
font-size: 16px;
font-weight: 500;
color: #fff;
gap: 8px;
}
</style> </style>

View File

@ -83,14 +83,13 @@ const startCountdown = (endTimeStamp: number) => {
} }
// //
if (!kcStatus.value && props.xk) { if (!kcStatus.value) {
// //
kcStatus.value = true; // kcStatus.value = true; //
uni.showToast({ uni.showToast({
title: "选课已开始", title: "选课已开始",
icon: "none", icon: "none",
}); });
// //
const xkjstime = dayjs(props.xk.xkjstime).valueOf(); const xkjstime = dayjs(props.xk.xkjstime).valueOf();
startCountdown(xkjstime); startCountdown(xkjstime);
@ -103,7 +102,6 @@ const startCountdown = (endTimeStamp: number) => {
icon: "none", icon: "none",
}); });
} }
return; return;
} }
@ -162,10 +160,16 @@ onBeforeUnmount(() => {
} }
}); });
//
watch(() => props.xk, (newVal) => { watch(() => props.xk, (newVal) => {
changeXk(newVal); changeXk(newVal);
}); });
//
if (props.xk && props.xk.id) {
changeXk(props.xk);
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -204,5 +208,20 @@ watch(() => props.xk, (newVal) => {
margin: 0 5px; margin: 0 5px;
} }
} }
/* 选课已结束样式 */
.enrollment-ended {
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 8px;
padding: 12px 15px;
font-size: 16px;
font-weight: 500;
color: #fff;
gap: 8px;
}
} }
</style> </style>

View File

@ -3,7 +3,7 @@
<view class="title-section" @click="showPicker"> <view class="title-section" @click="showPicker">
<view class="title"> <view class="title">
<text v-if="curXk && curXk.xkmc">{{ curXk.xkmc }}</text> <text v-if="curXk && curXk.xkmc">{{ curXk.xkmc }}</text>
<text v-else>选课信息</text> <text v-else>{{ title }}</text>
</view> </view>
<view class="switch-btn" v-if="xkList.length > 1">切换</view> <view class="switch-btn" v-if="xkList.length > 1">切换</view>
</view> </view>
@ -31,15 +31,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import { xsXkkcListApi } from "@/api/base/server"; import { xsXkListApi, xsXkkcListApi } from "@/api/base/server";
import { useUserStore } from "@/store/modules/user"; import { useUserStore } from "@/store/modules/user";
const { getCurXs } = useUserStore(); const { getCurXs } = useUserStore();
// //
const props = defineProps<{ const props = defineProps<{
isXs: boolean, //
xsId: string, xsId: string,
njId: string, njId: string,
xklxId: string; // Id962488654 / 816059832 xklxId: string, // Id962488654 / 816059832
title: string,
}>(); }>();
// emit // emit
@ -63,15 +65,26 @@ const loadXkList = async () => {
uni.showLoading({ uni.showLoading({
title: "加载中...", title: "加载中...",
}); });
xsXkkcListApi(props) const func = props.isXs ? xsXkListApi : xsXkkcListApi;
func({
xsId: props.xsId,
njId: props.njId,
xklxId: props.xklxId,
})
.then((res) => { .then((res) => {
if (res.resultCode == 1) { if (res.resultCode == 1) {
if (res.result && res.result.length) { if (res.result && res.result.length) {
xkList.value = res.result; xkList.value = res.result;
curXk.value = res.result[0]; curXk.value = res.result[0];
} else { } else {
xkList.value = []; if (props.isXs) {
curXk.value = {}; xkList.value = [];
curXk.value = {};
} else {
uni.reLaunch({
url: "/pages/base/course-selection/notopen",
});
}
} }
switchXk(curXk.value); switchXk(curXk.value);
uni.hideLoading(); uni.hideLoading();
@ -86,11 +99,13 @@ const loadXkList = async () => {
function switchXk(xk: any) { function switchXk(xk: any) {
curXk.value = xk; curXk.value = xk;
showFlag.value = false; showFlag.value = false;
// if (xk && xk.id) {
uni.showToast({ //
title: `已切换到${xk.xkmc}`, uni.showToast({
icon: "none", title: `已切换到${xk.xkmc}`,
}); icon: "none",
});
}
emit("change", xk); emit("change", xk);
} }

View File

@ -0,0 +1,227 @@
<template>
<view>
<!-- 课程网格列表 -->
<view class="course-grid" v-if="xkkcList.length > 0">
<view
v-for="(xkkc, index) in xkkcList"
:key="xkkc.id || index"
class="course-item"
:class="{ selected: xkkc.isSelected }"
@click="toggleSelection(xkkc)"
>
<view class="course-name">{{ xkkc.kcmc }}</view>
<view class="register-info">
<text>报名情况</text>
<text class="register-count">{{ xkkc.hasNum || 0 }}</text>
<text> | {{ xkkc.maxNum || 0 }}</text>
</view>
<view class="detail-btn" @click.stop="goToDetail(xkkc)"
>详情</view
>
<view v-if="xkkc.isSelected" class="selected-mark">
<uni-icons
type="checkbox-filled"
color="#3FBF72"
size="22"
></uni-icons>
</view>
</view>
</view>
<!-- 暂无数据提示 -->
<view v-else class="empty-course-list">
<view class="empty-icon">
<u-icon name="list" size="50" color="#C8C9CC"></u-icon>
</view>
<view class="empty-text">暂无课程数据</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
import { useDataStore } from "@/store/modules/data";
const { setKcData } = useDataStore();
//
const props = defineProps<{
xk: any,
canSelected: boolean,
}>();
// emit
const emit = defineEmits(['change'])
//
const xkkcList = ref<any>([]);
//
const toggleSelection = (xkkc: any) => {
if (!props.canSelected) {
return;
}
// ID
let selectedXkkcIds = uni.getStorageSync("selectedXkkcIds") || [];
if (xkkc.isSelected) {
//
selectedXkkcIds = selectedXkkcIds.filter(
(id: string) => id !== xkkc.id
);
xkkc.isSelected = false;
// xkkc.hasNum--;
} else {
//
const maxNum = xkkc.maxNum || 0;
const hasNum = xkkc.hasNum || 0;
if (maxNum > hasNum) {
xkkc.isSelected = true;
//
if (!selectedXkkcIds.includes(xkkc.id)) {
selectedXkkcIds.push(xkkc.id);
// xkkc.hasNum++;
}
}
}
//
uni.setStorageSync("selectedXkkcIds", selectedXkkcIds);
emit("change", selectedXkkcIds);
}
const goToDetail = (xkkc: any) => {
setKcData(xkkc);
uni.navigateTo({
url: `/pages/base/course-selection/detail`,
});
};
const switchXk = (xk: any) => {
xkkcList.value = xk.xkkcs;
if (!props.canSelected) {
return;
}
// ID
let selectedXkkcIds = uni.getStorageSync("selectedXkkcIds") || [];
let newSelectedXkkcIds = [];
for (let i = 0; i < xkkcList.value.length; i++) {
const xkkc = xkkcList.value[i];
if (selectedXkkcIds.includes(xkkc.id)) {
xkkc.isSelected = true;
newSelectedXkkcIds.push(xkkc.id);
} else {
xkkc.isSelected = false;
}
}
uni.setStorageSync("selectedXkkcIds", newSelectedXkkcIds);
}
//
watch(() => props.xk, (newVal) => {
if (newVal && newVal.xkkcs) {
switchXk(newVal);
}
});
//
if (props.xk && props.xk.xkkcs) {
switchXk(props.xk);
}
</script>
<style lang="scss" scoped>
.course-grid {
display: flex;
flex-wrap: wrap;
padding: 15px 15px 0 15px;
.course-item {
position: relative;
width: calc(50% - 10px);
margin-bottom: 15px;
background-color: #fff;
border-radius: 8px;
padding: 15px;
box-sizing: border-box;
border: 1px solid transparent;
transition: all 0.3s ease;
&:nth-child(odd) {
margin-right: 10px;
}
&:nth-child(even) {
margin-left: 10px;
}
&.selected {
border: 1px solid #3fbf72;
background-color: rgba(63, 191, 114, 0.05);
box-shadow: 0 2px 8px rgba(63, 191, 114, 0.15);
}
.course-name {
font-size: 16px;
font-weight: 500;
color: #333;
margin-bottom: 10px;
}
.register-info {
font-size: 14px;
color: #666;
margin-bottom: 12px;
.register-count {
color: #2879ff;
}
}
.detail-btn {
display: inline-block;
color: #2879ff;
font-size: 14px;
}
.selected-mark {
position: absolute;
top: -6px;
right: -6px;
}
}
}
//
.empty-course-list {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
text-align: center;
.empty-icon {
margin-bottom: 20px;
background-color: #f5f6f7;
width: 80px;
height: 80px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.empty-text {
font-size: 18px;
font-weight: 500;
color: #303133;
margin-bottom: 8px;
}
.empty-desc {
font-size: 14px;
color: #909399;
max-width: 80%;
line-height: 1.5;
}
}
</style>

View File

@ -94,7 +94,7 @@ const switchXs = (xs: any) => {
} }
// bar // bar
if (props.isBar && getUser.xsList.length > 1) { if (props.isBar && getUser.xsList.length > 1 && (getCurXs === null || !getCurXs.id)) {
showPicker(); showPicker();
} }
</script> </script>

View File

@ -4,51 +4,26 @@
<view class="selection-header"> <view class="selection-header">
<view class="header-content"> <view class="header-content">
<!-- 选课类型选择部分 --> <!-- 选课类型选择部分 -->
<XkPicker xklx-id="816059832" :xs-id="curXs.id" :nj-id="curXs.njId" @change="switchXk" /> <XkPicker title="俱乐部信息" :is-xs="false" xklx-id="816059832" :xs-id="curXs.id" :nj-id="curXs.njId" @change="switchXk" />
<!-- 学生选择部分 --> <!-- 学生选择部分 -->
<XsPicker :is-bar="true" /> <XsPicker :is-bar="true" />
<!-- 倒计时--> <!-- 倒计时-->
<XkCountdown :xk="curXk" @over="xkTimeOver" /> <XkCountdown :xk="curXk" @over="xkTimeOver" v-if="curXk && curXk.id" />
</view> </view>
</view> </view>
<!-- 可滚动的内容区域 --> <!-- 可滚动的内容区域 -->
<view class="scrollable-content"> <view class="scrollable-content">
<!-- 课程网格列表 --> <XkkcList :xk="curXk" :can-selected="true" @change="changeXkkc" />
<view class="course-grid" v-if="courseListData.length > 0">
<view
v-for="(course, index) in courseListData"
:key="course.id || index"
class="course-item"
:class="{ selected: course.isSelected }"
>
<view class="course-name">{{ course.kcmc }}</view>
<view class="register-info">
<text>上课人数</text>
<text class="register-count">{{ course.ybmr }}</text>
</view>
<view class="detail-btn" @click.stop="viewCourseDetail(course)"
>详情</view
>
<view v-if="course.isSelected" class="selected-mark">
<uni-icons
type="checkbox-filled"
color="#3FBF72"
size="22"
></uni-icons>
</view>
</view>
</view>
<!-- 暂无数据提示 -->
<view v-else class="empty-course-list">
<view class="empty-icon">
<u-icon name="list" size="50" color="#C8C9CC"></u-icon>
</view>
<view class="empty-text">暂无课程数据</view>
</view>
</view> </view>
<!-- 底部报名按钮 - 固定部分 -->
<view class="register-btn-container">
<view class="selected-count-info" v-if="selectedXkkcIds && selectedXkkcIds.length > 0">
已选 {{ selectedXkkcIds.length }} 门课程
</view>
<view class="register-btn" @click="submit">点击报名</view>
</view>
</view> </view>
</template> </template>
@ -56,18 +31,14 @@
import XsPicker from "@/pages/base/components/XsPicker/index.vue" import XsPicker from "@/pages/base/components/XsPicker/index.vue"
import XkPicker from "@/pages/base/components/XkPicker/index.vue" import XkPicker from "@/pages/base/components/XkPicker/index.vue"
import XkCountdown from "@/pages/base/components/XkCountdown/index.vue" import XkCountdown from "@/pages/base/components/XkCountdown/index.vue"
import XkkcList from "@/pages/base/components/XkkcList/index.vue"
import { useUserStore } from "@/store/modules/user"; import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
const { getCurXs } = useUserStore(); const { getCurXs } = useUserStore();
const { setKcData } = useDataStore();
const curXs = computed(() => getCurXs); const curXs = computed(() => getCurXs);
const curXk = ref<any>({});
const curXk = ref(); const selectedXkkcIds = ref<any>([]);
const kcStatus = ref(false);
const courseInfo = ref<any>({});
// //
const switchXk = (xk: any) => { const switchXk = (xk: any) => {
curXk.value = xk; curXk.value = xk;
@ -78,63 +49,17 @@ const xkTimeOver = (val: any) => {
console.log(val); console.log(val);
} }
// //
const displayCourseList = computed(() => { const changeXkkc = (ids: any) => {
if ( console.log(ids);
!curXk.value || selectedXkkcIds.value = ids;
!curXk.value.xkkcs || }
!Array.isArray(curXk.value.xkkcs)
) {
return [];
}
// ID //
const selectedCourseIds = uni.getStorageSync("selectedCourseIds") || []; const submit = () => {
console.log(selectedXkkcIds.value);
}
//
return curXk.value.xkkcs.map((course: any) => ({
...course,
isSelected: selectedCourseIds.includes(course.id),
}));
});
//
const courseListData = ref<any[]>([]);
//
const selectedCoursesCount = computed(() => {
return courseListData.value.filter((course: any) => course.isSelected).length;
});
//
watch(
displayCourseList,
(newVal) => {
courseListData.value = JSON.parse(JSON.stringify(newVal));
// courseInfo
const selectedCourseIds = uni.getStorageSync("selectedCourseIds") || [];
if (selectedCourseIds.length > 0) {
const selectedCourses = newVal.filter((course: any) =>
selectedCourseIds.includes(course.id)
);
courseInfo.value = selectedCourses;
}
},
{ immediate: true }
);
//
const viewCourseDetail = (course: any) => {
setKcData(course);
uni.navigateTo({
url: `/pages/base/course-selection/detail`,
});
};
//
onBeforeUnmount(() => {
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -202,67 +127,6 @@ onBeforeUnmount(() => {
-webkit-overflow-scrolling: touch; // iOS -webkit-overflow-scrolling: touch; // iOS
} }
.course-grid {
display: flex;
flex-wrap: wrap;
padding: 15px 15px 0 15px;
.course-item {
position: relative;
width: calc(50% - 10px);
margin-bottom: 15px;
background-color: #fff;
border-radius: 8px;
padding: 15px;
box-sizing: border-box;
border: 1px solid transparent;
transition: all 0.3s ease;
&:nth-child(odd) {
margin-right: 10px;
}
&:nth-child(even) {
margin-left: 10px;
}
&.selected {
border: 1px solid #3fbf72;
background-color: rgba(63, 191, 114, 0.05);
box-shadow: 0 2px 8px rgba(63, 191, 114, 0.15);
}
.course-name {
font-size: 16px;
font-weight: 500;
color: #333;
margin-bottom: 10px;
}
.register-info {
font-size: 14px;
color: #666;
margin-bottom: 12px;
.register-count {
color: #2879ff;
}
}
.detail-btn {
display: inline-block;
color: #2879ff;
font-size: 14px;
}
.selected-mark {
position: absolute;
top: -6px;
right: -6px;
}
}
}
.register-btn-container { .register-btn-container {
position: sticky; position: sticky;
bottom: 0; bottom: 0;
@ -270,9 +134,15 @@ onBeforeUnmount(() => {
right: 0; right: 0;
padding: 15px; padding: 15px;
background-color: #fff; background-color: #fff;
z-index: 10; z-index: 1;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05); box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
.selected-count-info {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.register-btn { .register-btn {
height: 50px; height: 50px;
line-height: 50px; line-height: 50px;
@ -285,52 +155,4 @@ onBeforeUnmount(() => {
} }
} }
//
.empty-course-list {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
text-align: center;
.empty-icon {
margin-bottom: 20px;
background-color: #f5f6f7;
width: 80px;
height: 80px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.empty-text {
font-size: 18px;
font-weight: 500;
color: #303133;
margin-bottom: 8px;
}
.empty-desc {
font-size: 14px;
color: #909399;
max-width: 80%;
line-height: 1.5;
}
}
/* 选课已结束样式 */
.enrollment-ended {
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 8px;
padding: 12px 15px;
font-size: 16px;
font-weight: 500;
color: #fff;
gap: 8px;
}
</style> </style>

File diff suppressed because it is too large Load Diff

View File

@ -3,308 +3,37 @@
<!-- 选课信息头部 - 固定部分 --> <!-- 选课信息头部 - 固定部分 -->
<view class="selection-header"> <view class="selection-header">
<view class="header-content"> <view class="header-content">
<view class="title-section"> <!-- 选课类型选择部分 -->
<view class="title"> <XkPicker title="兴趣课信息" :is-xs="true" xklx-id="962488654" :xs-id="curXs.id" :nj-id="curXs.njId" @change="switchXk" />
<text v-if="xkData && xkData.xkmc">{{ xkData.xkmc }}</text>
<text v-else>选课信息</text>
</view>
<!-- <view class="subtitle">互动兴趣课程</view> -->
</view>
<!-- 学生选择部分 --> <!-- 学生选择部分 -->
<view class="student-selector-bar" @click="showStudentSelector"> <XsPicker :is-bar="true" />
<view class="user-avatar">
<image
:src="currentStudent.avatar || '/static/base/home/11222.png'"
class="w-full h-full"
></image>
</view>
<view class="student-info">
<text class="student-name">{{ currentStudent.xm }}</text>
<text class="student-class"
>{{ currentStudent.njmc }} {{ currentStudent.bjmc }}</text
>
</view>
<view class="switch-btn" v-if="studentList.length > 1">切换</view>
</view>
</view> </view>
</view> </view>
<!-- 可滚动的内容区域 --> <!-- 可滚动的内容区域 -->
<view class="scrollable-content"> <view class="scrollable-content">
<!-- 课程网格列表 --> <XkkcList :xk="curXk" :can-selected="false" />
<view class="course-grid" v-if="courseListData.length > 0">
<view
v-for="(course, index) in courseListData"
:key="course.id || index"
class="course-item"
:class="{ selected: course.isSelected }"
>
<view class="course-name">{{ course.kcmc }}</view>
<view class="register-info">
<text>上课人数</text>
<text class="register-count">{{ course.ybmr }}</text>
</view>
<view class="detail-btn" @click.stop="viewCourseDetail(course)"
>详情</view
>
<view v-if="course.isSelected" class="selected-mark">
<uni-icons
type="checkbox-filled"
color="#3FBF72"
size="22"
></uni-icons>
</view>
</view>
</view>
<!-- 暂无数据提示 -->
<view v-else class="empty-course-list">
<view class="empty-icon">
<u-icon name="list" size="50" color="#C8C9CC"></u-icon>
</view>
<view class="empty-text">暂无课程数据</view>
</view>
</view> </view>
<view> </view>
<!-- 学生选择弹窗 -->
<u-popup
:show="showSelector"
@close="showSelector = false"
mode="bottom"
round="10"
>
<view class="student-selector">
<view class="selector-header">
<text class="selector-title">选择学生</text>
<u-icon
name="close"
size="20"
@click="showSelector = false"
></u-icon>
</view>
<view class="student-list">
<view
v-for="(student, index) in studentList"
:key="index"
class="student-item"
:class="{
'student-item-active': currentStudent.id === student.id,
}"
@click="switchStudent(student)"
>
<view class="student-avatar">
<image
:src="student.avatar || '/static/base/home/11222.png'"
class="w-full h-full"
></image>
</view>
<view class="student-info">
<text class="student-name">{{ student.xm }}</text>
<text class="student-class"
>{{ student.njmc }} {{ student.bjmc }}</text
>
</view>
<u-icon
v-if="currentStudent.id === student.id"
name="checkmark"
color="#409EFF"
size="20"
></u-icon>
</view>
</view>
</view>
</u-popup>
</view>
</view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { navigateTo } from "@/utils/uniapp"; import XsPicker from "@/pages/base/components/XsPicker/index.vue"
import { import XkPicker from "@/pages/base/components/XkPicker/index.vue"
ref, import XkkcList from "@/pages/base/components/XkkcList/index.vue"
computed,
reactive,
onBeforeUnmount,
watch,
onMounted,
} from "vue";
import { useUserStore } from "@/store/modules/user"; import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
import { xkListApi, xsXkListApi } from "@/api/base/server";
import dayjs from "dayjs";
const { getUser } = useUserStore(); const { getCurXs } = useUserStore();
const { getData, setKcData, setData } = useDataStore();
const { sign_file } = getData;
// const curXs = computed(() => getCurXs);
const studentList = computed(() => {
return getUser.xsList;
});
const currentStudent = ref(); const curXk = ref<any>({});
//
// const switchXk = (xk: any) => {
const showSelector = ref(false); curXk.value = xk;
if (studentList.value.length > 1) {
showSelector.value = true;
} }
const xkData = ref();
const kcStatus = ref(false);
//
const isEnrollmentEnded = ref(false);
//
let pollTimer: number | null = null;
const courseInfo = ref({});
//
const updateEnrollmentCount = (
data: Array<{ xkkcId: string; bmrs: number }>
) => {
if (!Array.isArray(courseListData.value) || courseListData.value.length === 0)
return;
let hasUpdates = false;
courseListData.value.forEach((course) => {
const newCount = data.find((item) => item.xkkcId === course.id);
if (newCount && course.ybmr !== newCount.bmrs) {
course.ybmr = newCount.bmrs;
hasUpdates = true;
}
});
//
if (hasUpdates) {
courseListData.value = [...courseListData.value];
}
};
//
onMounted(() => {
});
//
const loadCourseList = (currentStudent: any) => {
if (!currentStudent) {
uni.hideLoading();
return;
}
xsXkListApi({
xsId: currentStudent.id,
njId: currentStudent.njId,
xklxId: "962488654",
})
.then((res) => {
if (res.resultCode == 1) {
if (res.result && res.result.length) {
xkData.value = res.result[0];
} else {
xkData.value = [];
}
uni.hideLoading();
}
})
.catch(() => {
uni.hideLoading();
});
};
if (studentList.value.length > 0 && studentList.value.length === 1) {
currentStudent.value = studentList.value[0];
//
uni.showLoading({
title: "加载中...",
});
loadCourseList(currentStudent.value);
}
//
function showStudentSelector() {
if (studentList.value.length > 1) {
showSelector.value = true;
}
}
//
function switchStudent(student: any) {
currentStudent.value = student;
showSelector.value = false;
//
uni.showLoading({
title: "加载中...",
});
//
uni.showToast({
title: `已切换到${student.xm}`,
icon: "none",
});
//
loadCourseList(student);
}
//
const displayCourseList = computed(() => {
if (
!xkData.value ||
!xkData.value.xkkcs ||
!Array.isArray(xkData.value.xkkcs)
) {
return [];
}
// ID
const selectedCourseId = uni.getStorageSync("selectedCourseId");
//
return xkData.value.xkkcs.map((course: any) => ({
...course,
isSelected: selectedCourseId && course.id === selectedCourseId,
}));
});
//
const courseListData = ref<any[]>([]);
//
watch(
displayCourseList,
(newVal) => {
courseListData.value = JSON.parse(JSON.stringify(newVal));
// courseInfo
const selectedCourseId = uni.getStorageSync("selectedCourseId");
if (selectedCourseId) {
const selectedCourse = newVal.find(
(course: any) => course.id === selectedCourseId
);
if (selectedCourse) {
courseInfo.value = selectedCourse;
}
}
},
{ immediate: true }
);
//
const viewCourseDetail = (course: any) => {
setKcData(course);
uni.navigateTo({
url: `/pages/base/course-selection/detail`,
});
};
// //
onBeforeUnmount(() => { onBeforeUnmount(() => {
}); });
@ -365,97 +94,6 @@ onBeforeUnmount(() => {
flex-direction: column; flex-direction: column;
gap: 15px; gap: 15px;
.title-section {
.title {
font-size: 24px;
font-weight: bold;
}
.subtitle {
font-size: 14px;
opacity: 0.8;
}
}
//
.student-selector-bar {
display: flex;
align-items: center;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 10px;
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #fff;
overflow: hidden;
margin-right: 10px;
}
.student-info {
flex: 1;
display: flex;
flex-direction: column;
.student-name {
font-size: 16px;
font-weight: 500;
margin-bottom: 2px;
}
.student-class {
font-size: 12px;
opacity: 0.8;
}
}
.switch-btn {
padding: 4px 12px;
background-color: rgba(255, 255, 255, 0.2);
color: #fff;
border-radius: 20px;
font-size: 13px;
}
}
.countdown-section {
.countdown-title {
font-size: 14px;
margin-bottom: 8px;
}
.countdown-timer {
display: flex;
align-items: center;
.time-block {
background-color: rgba(255, 255, 255, 0.2);
border-radius: 6px;
min-width: 40px;
padding: 5px 8px;
text-align: center;
.time-value {
font-size: 20px;
font-weight: bold;
display: block;
}
.time-unit {
font-size: 12px;
opacity: 0.9;
}
}
.time-separator {
font-size: 20px;
font-weight: bold;
margin: 0 5px;
}
}
}
} }
} }
@ -466,211 +104,4 @@ onBeforeUnmount(() => {
-webkit-overflow-scrolling: touch; // iOS -webkit-overflow-scrolling: touch; // iOS
} }
.course-grid {
display: flex;
flex-wrap: wrap;
padding: 15px 15px 0 15px;
.course-item {
position: relative;
width: calc(50% - 10px);
margin-bottom: 15px;
background-color: #fff;
border-radius: 8px;
padding: 15px;
box-sizing: border-box;
border: 1px solid transparent;
transition: all 0.3s ease;
&:nth-child(odd) {
margin-right: 10px;
}
&:nth-child(even) {
margin-left: 10px;
}
&.selected {
border: 1px solid #3fbf72;
background-color: rgba(63, 191, 114, 0.05);
box-shadow: 0 2px 8px rgba(63, 191, 114, 0.15);
}
.course-name {
font-size: 16px;
font-weight: 500;
color: #333;
margin-bottom: 10px;
}
.register-info {
font-size: 14px;
color: #666;
margin-bottom: 12px;
.register-count {
color: #2879ff;
}
}
.detail-btn {
display: inline-block;
color: #2879ff;
font-size: 14px;
}
.selected-mark {
position: absolute;
top: -6px;
right: -6px;
}
}
}
.register-btn-container {
position: sticky;
bottom: 0;
left: 0;
right: 0;
padding: 15px;
background-color: #fff;
z-index: 10;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
.register-btn {
height: 50px;
line-height: 50px;
text-align: center;
background-color: #2879ff;
color: #fff;
border-radius: 25px;
font-size: 16px;
font-weight: 500;
}
}
/* 学生选择器弹窗样式 */
.student-selector {
background-color: #ffffff;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
padding-bottom: 20px;
.selector-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border-bottom: 1px solid #f2f2f2;
.selector-title {
font-size: 16px;
font-weight: 500;
color: #303133;
}
}
.student-list {
padding: 0 15px;
.student-item {
display: flex;
align-items: center;
padding: 15px 0;
border-bottom: 1px solid #f2f2f2;
&:last-child {
border-bottom: none;
}
&-active {
background-color: rgba(64, 158, 255, 0.05);
}
.student-avatar {
width: 45px;
height: 45px;
border-radius: 50%;
background-color: #f0f0f0;
overflow: hidden;
flex-shrink: 0;
}
.student-info {
flex: 1;
margin-left: 12px;
.student-name {
font-size: 15px;
font-weight: 500;
color: #303133;
margin-bottom: 4px;
}
.student-class {
font-size: 13px;
color: #606266;
}
}
}
}
}
/* 全局图片样式 */
.w-full {
width: 100%;
}
.h-full {
height: 100%;
}
//
.empty-course-list {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
text-align: center;
.empty-icon {
margin-bottom: 20px;
background-color: #f5f6f7;
width: 80px;
height: 80px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.empty-text {
font-size: 18px;
font-weight: 500;
color: #303133;
margin-bottom: 8px;
}
.empty-desc {
font-size: 14px;
color: #909399;
max-width: 80%;
line-height: 1.5;
}
}
/* 选课已结束样式 */
.enrollment-ended {
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 8px;
padding: 12px 15px;
font-size: 16px;
font-weight: 500;
color: #fff;
gap: 8px;
}
</style> </style>