This commit is contained in:
Net 2025-05-30 17:22:30 +08:00
parent d22acd005c
commit eefe1e37f6
14 changed files with 2469 additions and 862 deletions

View File

@ -19,3 +19,12 @@ export const xkXkqdApi = async (params: any) => {
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);
};

View File

@ -1,5 +1,5 @@
// const ip: string = "119.29.194.155:8893";
const ip: string = "yufangzc.com";
const ip: string = "119.29.194.155:8893";
// const ip: string = "yufangzc.com";
const fwqip: string = "yufangzc.com";
//打包服务器接口代理标识
const SERVERAGENT: string = "/jzd-api";
@ -7,11 +7,11 @@ const SERVERAGENT: string = "/jzd-api";
export const HOMEAGENT: string = "";
// 接口地址
export const BASE_URL: string =
process.env.NODE_ENV == "development" ? `https://${ip}/zhxy` : SERVERAGENT;
process.env.NODE_ENV == "development" ? `http://${ip}/zhxy` : SERVERAGENT;
// WebSocket地址
export const BASE_WS_URL: string = `wss://${ip}`;
//图片地址
export const BASE_IMAGE_URL: string = process.env.NODE_ENV == "development" ? `https://${ip}` : `https://${fwqip}`;
export const BASE_IMAGE_URL: string = process.env.NODE_ENV == "development" ? `http://${ip}` : `http://${fwqip}`;
//存token的key
export const AUTH_KEY: string = "satoken";
//token过期返回状态码

View File

@ -215,6 +215,11 @@
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#fff",
"backgroundColor": "#F8F8F8",
"orientation": "portrait"
"orientation": "portrait",
"navigationStyle": "custom",
"app-plus": {
"background": "#efeff4",
"titleView": false
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -97,10 +97,12 @@
<!-- 底部报名按钮 - 固定部分 -->
<view class="register-btn-container">
<view class="selected-count-info" v-if="getSelectedCount > 0">
已选 {{ getSelectedCount }} 门课程
<view class="register-btn" @click="submitRegistration">
<text v-if="selectedCoursesCount > 0">
点击报名 (已选{{ selectedCoursesCount }})
</text>
<text v-else>点击报名</text>
</view>
<view class="register-btn" @click="submitRegistration">点击报名</view>
</view>
<view>
@ -157,15 +159,26 @@
</template>
<script setup lang="ts">
import { navigateTo } from "@/utils/uniapp";
import { ref, computed, reactive, onBeforeUnmount, watch } from "vue";
import {
ref,
computed,
reactive,
onBeforeUnmount,
watch,
onMounted,
} from "vue";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
import { xkAddXkqdApi, xkListApi, xkXkqdApi } from "@/api/base/server";
import {
xkAddXkqdApi,
xkListApi,
xkxkbmInfoApi,
xkXkqdApi,
} from "@/api/base/server";
import dayjs from "dayjs";
const { getUser } = useUserStore();
const { getData, setKcData } = useDataStore();
const { getData, setKcData, setData } = useDataStore();
const { sign_file } = getData;
//
@ -204,6 +217,11 @@ const kcStatus = ref(false);
//
const isEnrollmentEnded = ref(false);
//
let pollTimer: number | null = null;
const courseInfo = ref({});
//
function checkStudentEnrollmentApi(student: any): Promise<boolean> {
return new Promise((resolve, reject) => {
@ -213,16 +231,32 @@ function checkStudentEnrollmentApi(student: any): Promise<boolean> {
return;
}
// kcData
if (!kcData.value || !kcData.value.id) {
console.error("课程数据未加载:", kcData.value);
resolve(false);
return;
}
//
xkXkqdApi({
njId: student.njId,
xsId: student.id,
xklxId: "962488654", // ID
xklxId: "816059832", // ID
xkId: kcData.value.id,
})
.then((res) => {
console.log(1122, res);
// result
if (res && res.resultCode === 1) {
resolve(!!res.result); //
setData({
...getData,
kcData,
studentInfo: student,
enrolledCourse: res.result,
});
resolve(res.result.length > 0); //
} else {
//
console.warn("检查报名状态接口返回错误:", res);
@ -237,14 +271,76 @@ function checkStudentEnrollmentApi(student: any): Promise<boolean> {
});
}
//
const pollEnrollmentCount = () => {
xkxkbmInfoApi({ xkId: kcData.value.id })
.then((res) => {
if (res && res.resultCode === 1 && Array.isArray(res.result)) {
updateEnrollmentCount(res.result);
}
})
.catch((error) => {
console.error("获取报名人数失败:", error);
});
};
//
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];
}
};
//
const startPolling = (interval = 5000) => {
//
stopPolling();
//
pollEnrollmentCount();
//
pollTimer = setInterval(pollEnrollmentCount, interval) as unknown as number;
};
//
const stopPolling = () => {
if (pollTimer) {
clearInterval(pollTimer);
pollTimer = null;
}
};
//
onMounted(() => {
//
watch(courseListData, (newVal) => {
if (newVal.length > 0 && !pollTimer) {
startPolling();
}
});
});
//
const checkInitialEnrollment = (currentStudent: any) => {
if (!currentStudent) return;
uni.showLoading({
title: "加载中...",
});
// 使API
checkStudentEnrollmentApi(currentStudent)
.then((isEnrolled) => {
@ -253,22 +349,14 @@ const checkInitialEnrollment = (currentStudent: any) => {
uni.reLaunch({
url: `/pages/base/course-selection/enrolled`,
});
} else {
loadCourseList(currentStudent);
}
})
.catch(() => {
uni.hideLoading();
loadCourseList(currentStudent);
//
console.log("检查学生报名状态失败,继续正常流程");
});
};
if (studentList.value.length > 0 && studentList.value.length === 1) {
currentStudent.value = studentList.value[0];
//
checkInitialEnrollment(currentStudent.value);
}
//
const loadCourseList = (currentStudent: any) => {
if (!currentStudent) {
@ -278,10 +366,9 @@ const loadCourseList = (currentStudent: any) => {
xkListApi({
njId: currentStudent.njId,
xklxId: "962488654",
xklxId: "816059832",
})
.then((res) => {
uni.hideLoading();
if (res.resultCode == 1) {
if (res.result) {
kcData.value = res.result;
@ -308,11 +395,15 @@ const loadCourseList = (currentStudent: any) => {
kcStatus.value = true;
startCountdown(xkjstime);
}
//
checkInitialEnrollment(currentStudent);
} else {
uni.reLaunch({
url: "/pages/base/course-selection/notopen",
});
}
uni.hideLoading();
}
})
.catch(() => {
@ -320,6 +411,15 @@ const loadCourseList = (currentStudent: any) => {
});
};
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) {
@ -337,38 +437,14 @@ function switchStudent(student: any) {
title: "加载中...",
});
// 使API
checkStudentEnrollmentApi(student)
.then((isEnrolled) => {
uni.hideLoading();
if (isEnrolled) {
//
uni.navigateTo({
url: `/pages/base/course-selection/enrolled?studentId=${student.id}`,
});
} else {
//
//
uni.showToast({
title: `已切换到${student.xm}`,
icon: "none",
});
//
//
loadCourseList(student);
}
})
.catch((error) => {
uni.hideLoading();
uni.showToast({
title: `已切换到${student.xm}`,
icon: "none",
});
console.error("检查学生报名状态出错:", error);
//
loadCourseList(student);
});
}
//
@ -461,11 +537,25 @@ const displayCourseList = computed(() => {
//
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 }
);
@ -490,23 +580,33 @@ const toggleSelection = (course: any) => {
courseListData.value[courseIndex].isSelected =
!courseListData.value[courseIndex].isSelected;
// ID
let selectedCourseIds = uni.getStorageSync("selectedCourseIds") || [];
// ID
const selectedCourseIds = courseListData.value
.filter((item: any) => item.isSelected)
.map((item: any) => item.id);
if (courseListData.value[courseIndex].isSelected) {
//
if (!selectedCourseIds.includes(course.id)) {
selectedCourseIds.push(course.id);
}
} else {
//
selectedCourseIds = selectedCourseIds.filter(
(id: string) => id !== course.id
);
}
//
// ID
uni.setStorageSync("selectedCourseIds", selectedCourseIds);
// courseInfo
courseInfo.value = courseListData.value.filter(
(item: any) => item.isSelected
);
//
if (courseListData.value[courseIndex].isSelected) {
uni.showToast({
title: `已选择 ${course.kcmc}`,
icon: "none",
duration: 1500,
});
} else {
uni.showToast({
title: `已取消选择 ${course.kcmc}`,
icon: "none",
duration: 1500,
});
}
};
//
@ -561,73 +661,58 @@ const submitRegistration = () => {
return;
}
//
//
const fullCourses = selectedCourses.filter(
(course) => course.ybmr >= course.maxNum
);
if (fullCourses.length > 0) {
uni.showToast({
title: `"${fullCourses[0].kcmc}"名额已满,请重新选择!`,
title: `课程"${fullCourses[0].kcmc}"名额已满,请重新选择!`,
icon: "none",
});
return;
}
//
const courseNames = selectedCourses
.map((course) => `"${course.kcmc}"`)
.join("、");
//
const courseNames = selectedCourses.map((course) => course.kcmc).join("、");
uni.showModal({
title: "确认报名",
content: `您确定要为${currentStudent.value.xm}报名以下课程吗?\n${courseNames}`,
content: `您确定要为${currentStudent.value.xm}报名以下课程吗?\n\n${courseNames}`,
success: async (res) => {
if (res.confirm) {
uni.showLoading({
title: "报名中...",
});
try {
//
for (const course of selectedCourses) {
const result = await xkAddXkqdApi({
njId: currentStudent.value.njId,
// ID
const selectedCourseIds = selectedCourses
.map((course) => course.id)
.join(",");
const res = await xkAddXkqdApi({
xsId: currentStudent.value.id,
xklxId: course.id,
sign_file,
xkkcId: selectedCourseIds,
qmFile: sign_file,
xklxId: "816059832",
});
if (result.resultCode !== 1) {
throw new Error(
`课程"${course.kcmc}"报名失败: ${result.message || "未知错误"}`
);
}
}
//
uni.hideLoading();
uni.showToast({
title: "报名成功!",
icon: "success",
duration: 2000,
if (res.resultCode == 1) {
setData({
...getData,
kcData,
studentInfo: currentStudent.value,
enrolledCourse: res.result,
});
uni.showToast({
title: "报名成功",
icon: "none",
});
//
uni.removeStorageSync("selectedCourseIds");
//
setTimeout(() => {
uni.reLaunch({
url: `/pages/base/course-selection/enrolled`,
});
}, 2000);
} catch (error: any) {
uni.hideLoading();
uni.showToast({
title: error.message || "报名失败,请稍后重试",
icon: "none",
duration: 3000,
});
}, 1500);
}
}
},
@ -640,11 +725,8 @@ onBeforeUnmount(() => {
clearInterval(countdownTimer);
countdownTimer = null;
}
});
//
const getSelectedCount = computed(() => {
return courseListData.value.filter((course) => course.isSelected).length;
stopPolling();
});
</script>
@ -875,12 +957,6 @@ const getSelectedCount = computed(() => {
z-index: 10;
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;
@ -891,12 +967,6 @@ const getSelectedCount = computed(() => {
font-size: 16px;
font-weight: 500;
}
.multi-select-tip {
font-size: 12px;
color: #909399;
text-align: center;
}
}
/* 学生选择器弹窗样式 */

View File

@ -31,73 +31,111 @@
</view>
</view>
<!-- 已报名课程信息 -->
<!-- 已报名课程列表 -->
<view class="info-card">
<view class="card-title">已报名课程</view>
<view class="card-title"
>已报名课程 ({{ enrolledCourse.length }})</view
>
<view class="divider"></view>
<view class="course-info">
<image
class="course-image"
:src="enrolledCourse.image || '/static/base/home/2211.png'"
mode="aspectFill"
></image>
<!-- 课程列表 -->
<view class="course-list">
<view
v-for="(item, index) in enrolledCourse"
:key="item.id"
class="course-item"
:class="{ 'last-item': index === enrolledCourse.length - 1 }"
>
<view class="course-header">
<view class="course-name">{{ item.title }}</view>
<view class="course-fee">¥{{ item.fee }}</view>
</view>
<view class="course-details">
<view class="course-name">{{ enrolledCourse.title }}</view>
<view class="course-teacher"
>开课老师{{ enrolledCourse.teacher }}</view
>
<view class="course-time">上课时间{{ enrolledCourse.time }}</view>
<view class="course-location"
>上课地点{{ enrolledCourse.location }}</view
>
<view class="detail-row">
<view class="detail-item">
<u-icon name="account" size="14" color="#666"></u-icon>
<text>{{ item.teacher }}</text>
</view>
<view class="detail-item">
<u-icon name="clock" size="14" color="#666"></u-icon>
<text>{{ item.time }}</text>
</view>
</view>
<!-- 报名信息 -->
<view class="enrollment-info">
<view class="info-item">
<text class="info-label">报名时间</text>
<text class="info-value">{{ enrolledCourse.enrollDate }}</text>
<view class="detail-row">
<view class="detail-item">
<u-icon name="map" size="14" color="#666"></u-icon>
<text>{{ item.location }}</text>
</view>
<view class="info-item">
<text class="info-label">课程费用</text>
<text class="info-value highlight">¥{{ enrolledCourse.fee }}</text>
</view>
<view class="info-item">
<text class="info-label">支付状态</text>
<text
class="info-value"
:class="enrolledCourse.isPaid ? 'paid' : 'unpaid'"
>
{{ enrolledCourse.isPaid ? "已支付" : "未支付" }}
</text>
<view class="detail-item">
<u-icon name="calendar" size="14" color="#666"></u-icon>
<text>{{ formatDate(item.enrollDate) }}</text>
</view>
</view>
</view>
<!-- 温馨提示 -->
<!-- <view class="notice-card">
<view class="notice-title">
<u-icon name="info-circle" size="18" color="#2879FF"></u-icon>
<text>温馨提示</text>
<view class="course-actions" v-if="!isAllPaid">
<u-button
text="取消报名"
size="mini"
:plain="true"
@click="cancelRegistration(item.id)"
></u-button>
</view>
</view>
</view>
</view>
<!-- 支付状态卡片 -->
<view
class="payment-status-card"
:class="{ paid: isAllPaid, unpaid: !isAllPaid }"
>
<view class="status-header">
<view class="status-icon">
<u-icon
:name="isAllPaid ? 'checkmark-circle' : 'clock'"
size="24"
:color="isAllPaid ? '#3FBF72' : '#FF9900'"
></u-icon>
</view>
<view class="status-text">
<text class="status-title">{{
isAllPaid ? "支付完成" : "待支付"
}}</text>
<text class="status-desc">{{
isAllPaid ? "所有课程费用已支付" : "请完成课程费用支付"
}}</text>
</view>
</view>
<view class="payment-details">
<view class="detail-item">
<text class="detail-label">课程总数</text>
<text class="detail-value">{{ enrolledCourse.length }}</text>
</view>
<view class="detail-item">
<text class="detail-label">总费用</text>
<text class="detail-value amount">¥{{ totalAmount }}</text>
</view>
<view class="detail-item" v-if="!isAllPaid">
<text class="detail-label">待支付</text>
<text class="detail-value unpaid-amount">¥{{ totalAmount }}</text>
</view>
</view>
<view class="notice-content">
<text>1. 课程一经报名成功不可取消或更换</text>
<text>2. 如有特殊情况需要请假请提前与老师联系</text>
<text>3. 请按时上课迟到将影响学习效果</text>
</view>
</view> -->
</view>
<template #bottom>
<view class="bottom-actions" v-if="!enrolledCourse.isPaid">
<u-button text="继续支付" type="primary" @click="goPay"></u-button>
<!-- <u-button
text="返回选课"
:plain="true"
@click="goBack"
></u-button> -->
<view class="bottom-actions" v-if="!isAllPaid">
<view class="payment-info">
<text class="payment-label">待支付</text>
<text class="payment-amount">¥{{ totalAmount }}</text>
</view>
<view class="payment-button">
<u-button text="立即支付" type="primary" @click="goPay"></u-button>
</view>
</view>
</template>
</BasicLayout>
@ -106,110 +144,100 @@
<script setup lang="ts">
import { ref, onMounted, computed } from "vue";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
import { xkqddeleteApi } from "@/api/base/server";
import { map } from "lodash";
import dayjs from "dayjs";
const { getUser } = useUserStore();
//
const studentId = ref("");
onMounted(() => {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
// @ts-ignore
const options = currentPage.$page?.options;
if (options && options.studentId) {
studentId.value = options.studentId;
}
});
onMounted(() => {});
const useData = useDataStore();
const { getData } = storeToRefs(useData);
//
const studentInfo = ref({
id: "",
xm: "加载中...",
njmc: "",
bjmc: "",
avatar: "",
id: getData.value.studentInfo.id,
xm: getData.value.studentInfo.xm,
njmc: getData.value.studentInfo.njmc,
bjmc: getData.value.studentInfo.bjmc,
avatar: getData.value.studentInfo.avatar,
});
//
const enrolledCourse = ref({
id: "",
title: "少儿声乐",
image: "",
teacher: "张老师",
time: "每周一 16:00-17:30",
location: "艺术楼 203教室",
enrollDate: "2023-09-15 14:30:45",
fee: 420,
isPaid: false,
const enrolledCourse = ref();
enrolledCourse.value = map(getData.value.enrolledCourse, (item) => {
return {
id: item.id,
title: item.xkmc,
image: item.image,
teacher: item.jsxm,
time: item.studyTime,
location: item.kcdd,
enrollDate: item.createdTime,
fee: item.jfje || 0,
};
});
//
const fetchStudentInfo = () => {
// ID
//
const student = getUser.xsList.find((s: any) => s.id === studentId.value);
if (student) {
studentInfo.value = student;
}
//
const isAllPaid = ref(enrolledCourse.value[0].jfzt == "B"); //
// TODO:
// const url = '/api/student/detail';
// const params = { studentId: studentId.value };
// return new Promise((resolve) => {
// // API
// setTimeout(() => {
// resolve();
// }, 500);
// });
};
//
const totalAmount = computed(() => {
return enrolledCourse.value.reduce(
(sum: number, course: any) => sum + course.fee,
0
);
});
//
const fetchEnrolledCourse = () => {
// TODO:
// const url = '/api/course/enrolled';
// const params = { studentId: studentId.value };
// return new Promise((resolve) => {
// // API
// setTimeout(() => {
// resolve();
// }, 500);
// });
//
setTimeout(() => {
enrolledCourse.value = {
id: "course123",
title: "少儿声乐",
image: "/static/base/home/2211.png",
teacher: "张老师",
time: "每周一 16:00-17:30",
location: "艺术楼 203教室",
enrollDate: "2023-09-15 14:30:45",
fee: 420,
isPaid: Math.random() > 0.5, //
};
}, 500);
//
const formatDate = (dateStr: string) => {
if (!dateStr) return "未知";
return dayjs(dateStr).format("YYYY-MM-DD HH:mm");
};
//
const goPay = () => {
uni.navigateTo({
url: `/pages/base/course-selection/payment?courseId=${enrolledCourse.value.id}&studentId=${studentId.value}`,
});
};
const goPay = () => {};
//
const goBack = () => {
uni.navigateBack();
const cancelRegistration = async (id: string) => {
uni.showModal({
title: "确认取消",
content: "确定要取消该课程的报名吗?",
success: async (res) => {
if (res.confirm) {
uni.showLoading({ title: "处理中..." });
try {
await xkqddeleteApi({
ids: id,
});
uni.hideLoading();
uni.showToast({
title: "取消成功",
icon: "success",
});
setTimeout(() => {
uni.reLaunch({
url: `/pages/base/course-selection/index`,
});
}, 1500);
} catch (error) {
uni.hideLoading();
uni.showToast({
title: "取消失败",
icon: "error",
});
}
}
},
});
};
onMounted(() => {
uni.showLoading({ title: "加载中..." });
//
Promise.all([fetchStudentInfo(), fetchEnrolledCourse()]).finally(() => {
uni.hideLoading();
});
});
</script>
@ -217,6 +245,7 @@ onMounted(() => {
.enrolled-page {
background-color: #f5f7fa;
padding: 15px;
padding-bottom: 80px; /* 为底部固定按钮留出空间 */
}
.status-card {
@ -310,44 +339,37 @@ onMounted(() => {
}
}
.course-info {
display: flex;
margin-bottom: 15px;
.course-list {
.course-item {
border-bottom: 1px solid #f0f0f0;
.course-image {
width: 100px;
height: 100px;
border-radius: 8px;
margin-right: 15px;
flex-shrink: 0;
&.last-item {
border-bottom: none;
}
.course-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
.course-name {
font-size: 16px;
font-weight: 500;
color: #333;
}
.course-fee {
font-size: 16px;
font-weight: 500;
color: #ff6b01;
}
}
.course-details {
flex: 1;
margin-bottom: 12px;
.course-name {
font-size: 18px;
font-weight: 500;
color: #333;
margin-bottom: 8px;
}
.course-teacher,
.course-time,
.course-location {
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
}
}
.enrollment-info {
background-color: #f9f9f9;
border-radius: 8px;
padding: 12px;
.info-item {
.detail-row {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
@ -356,26 +378,112 @@ onMounted(() => {
margin-bottom: 0;
}
.info-label {
.detail-item {
display: flex;
align-items: center;
flex: 1;
text {
margin-left: 5px;
font-size: 13px;
color: #666;
font-size: 14px;
}
}
}
}
.info-value {
font-size: 14px;
color: #333;
.course-actions {
display: flex;
justify-content: flex-end;
align-items: center;
&.highlight {
color: #ff6b01;
font-weight: 500;
:deep(.u-button) {
margin-left: 10px;
}
}
}
}
.payment-status-card {
background-color: #fff;
border-radius: 12px;
padding: 15px;
margin-bottom: 15px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
border-left: 4px solid #ddd;
&.paid {
color: #3fbf72;
border-left-color: #3fbf72;
background: linear-gradient(135deg, #f0f9ff, #e6f7ff);
}
&.unpaid {
border-left-color: #ff9900;
background: linear-gradient(135deg, #fff9f0, #fff2e6);
}
.status-header {
display: flex;
align-items: center;
margin-bottom: 15px;
.status-icon {
width: 24px;
height: 24px;
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.status-text {
flex: 1;
.status-title {
font-size: 16px;
font-weight: bold;
color: #333;
display: block;
margin-bottom: 4px;
}
.status-desc {
font-size: 13px;
color: #666;
display: block;
}
}
}
.payment-details {
.detail-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
.detail-label {
font-size: 14px;
color: #666;
}
.detail-value {
font-size: 14px;
color: #333;
&.amount {
font-weight: bold;
color: #333;
}
&.unpaid-amount {
color: #ff5252;
font-weight: bold;
}
}
}
}
@ -415,14 +523,62 @@ onMounted(() => {
}
.bottom-actions {
padding: 15px;
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 12px 20px;
background-color: #fff;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
border-top: 1px solid #f0f0f0;
z-index: 100;
safe-area-inset-bottom: env(safe-area-inset-bottom);
.payment-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
.payment-label {
font-size: 12px;
color: #999;
margin-bottom: 2px;
line-height: 1;
}
.payment-amount {
font-size: 18px;
font-weight: bold;
color: #ff6b01;
line-height: 1.2;
}
}
.payment-button {
flex: 0 0 auto;
margin-left: 15px;
}
:deep(.u-button) {
flex: 1;
margin: 0 5px;
width: 120px !important;
height: 40px !important;
border-radius: 20px !important;
font-size: 15px !important;
font-weight: 500 !important;
.u-button__text {
font-size: 15px !important;
font-weight: 500 !important;
}
&.u-button--primary {
background-color: #2879ff !important;
border-color: #2879ff !important;
}
}
}
</style>

View File

@ -155,14 +155,26 @@
<script setup lang="ts">
import { navigateTo } from "@/utils/uniapp";
import { ref, computed, reactive, onBeforeUnmount, watch, onMounted } from "vue";
import {
ref,
computed,
reactive,
onBeforeUnmount,
watch,
onMounted,
} from "vue";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
import { xkAddXkqdApi, xkListApi, xkXkqdApi } from "@/api/base/server";
import {
xkAddXkqdApi,
xkListApi,
xkxkbmInfoApi,
xkXkqdApi,
} from "@/api/base/server";
import dayjs from "dayjs";
const { getUser } = useUserStore();
const { getData, setKcData } = useDataStore();
const { getData, setKcData, setData } = useDataStore();
const { sign_file } = getData;
//
@ -204,6 +216,8 @@ const isEnrollmentEnded = ref(false);
//
let pollTimer: number | null = null;
const courseInfo = ref({});
//
function checkStudentEnrollmentApi(student: any): Promise<boolean> {
return new Promise((resolve, reject) => {
@ -213,16 +227,32 @@ function checkStudentEnrollmentApi(student: any): Promise<boolean> {
return;
}
// kcData
if (!kcData.value || !kcData.value.id) {
console.error("课程数据未加载:", kcData.value);
resolve(false);
return;
}
//
xkXkqdApi({
njId: student.njId,
xsId: student.id,
xklxId: "962488654", // ID
xkId: kcData.value.id,
})
.then((res) => {
console.log(1122,res);
// result
if (res && res.resultCode === 1) {
resolve(!!res.result); //
setData({
...getData,
kcData,
studentInfo: student,
enrolledCourse: res.result,
});
resolve(res.result.length > 0); //
} else {
//
console.warn("检查报名状态接口返回错误:", res);
@ -239,41 +269,30 @@ function checkStudentEnrollmentApi(student: any): Promise<boolean> {
//
const pollEnrollmentCount = () => {
//
// getEnrollmentCountApi
/*
getEnrollmentCountApi().then(res => {
xkxkbmInfoApi({ xkId: kcData.value.id })
.then((res) => {
if (res && res.resultCode === 1 && Array.isArray(res.result)) {
updateEnrollmentCount(res.result);
}
}).catch(error => {
console.error('获取报名人数失败:', error);
})
.catch((error) => {
console.error("获取报名人数失败:", error);
});
*/
// TODO:
// console.log('');
//
const mockData = [
{ id: '111B98397A08E8DA20D', ybmr: 10 },
{ id: '74ECFE7249A54FE9B98397A08E8DA20D', ybmr: 15 }
];
//
updateEnrollmentCount(mockData);
};
//
const updateEnrollmentCount = (data: Array<{id: string, ybmr: number}>) => {
if (!Array.isArray(courseListData.value) || courseListData.value.length === 0) return;
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.id === course.id);
if (newCount && course.ybmr !== newCount.ybmr) {
course.ybmr = newCount.ybmr;
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;
}
});
@ -318,10 +337,6 @@ onMounted(() => {
const checkInitialEnrollment = (currentStudent: any) => {
if (!currentStudent) return;
uni.showLoading({
title: "加载中...",
});
// 使API
checkStudentEnrollmentApi(currentStudent)
.then((isEnrolled) => {
@ -330,22 +345,14 @@ const checkInitialEnrollment = (currentStudent: any) => {
uni.reLaunch({
url: `/pages/base/course-selection/enrolled`,
});
} else {
loadCourseList(currentStudent);
}
})
.catch(() => {
uni.hideLoading();
loadCourseList(currentStudent);
//
console.log("检查学生报名状态失败,继续正常流程");
});
};
if (studentList.value.length > 0 && studentList.value.length === 1) {
currentStudent.value = studentList.value[0];
//
checkInitialEnrollment(currentStudent.value);
}
//
const loadCourseList = (currentStudent: any) => {
if (!currentStudent) {
@ -358,7 +365,6 @@ const loadCourseList = (currentStudent: any) => {
xklxId: "962488654",
})
.then((res) => {
uni.hideLoading();
if (res.resultCode == 1) {
if (res.result) {
kcData.value = res.result;
@ -385,11 +391,15 @@ const loadCourseList = (currentStudent: any) => {
kcStatus.value = true;
startCountdown(xkjstime);
}
//
checkInitialEnrollment(currentStudent);
} else {
uni.reLaunch({
url: "/pages/base/course-selection/notopen",
});
}
uni.hideLoading();
}
})
.catch(() => {
@ -397,6 +407,15 @@ const loadCourseList = (currentStudent: any) => {
});
};
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) {
@ -414,38 +433,14 @@ function switchStudent(student: any) {
title: "加载中...",
});
// 使API
checkStudentEnrollmentApi(student)
.then((isEnrolled) => {
uni.hideLoading();
if (isEnrolled) {
//
uni.navigateTo({
url: `/pages/base/course-selection/enrolled?studentId=${student.id}`,
});
} else {
//
//
uni.showToast({
title: `已切换到${student.xm}`,
icon: "none",
});
//
//
loadCourseList(student);
}
})
.catch((error) => {
uni.hideLoading();
uni.showToast({
title: `已切换到${student.xm}`,
icon: "none",
});
console.error("检查学生报名状态出错:", error);
//
loadCourseList(student);
});
}
//
@ -543,6 +538,17 @@ 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 }
);
@ -575,6 +581,7 @@ const toggleSelection = (course: any) => {
courseListData.value[courseIndex].isSelected = true;
// ID
uni.setStorageSync("selectedCourseId", course.id);
courseInfo.value = course;
} else {
//
uni.showToast({
@ -659,36 +666,29 @@ const submitRegistration = () => {
title: "报名中...",
});
const res = await xkAddXkqdApi({
njId: currentStudent.value.njId,
xsId: currentStudent.value.id,
xklxId: selectedCourse.id,
sign_file
xkkcId: selectedCourse.id,
qmFile: sign_file,
xklxId: "962488654",
});
// TODO:
//
// setTimeout(() => {
// uni.hideLoading();
// //
// uni.setStorageSync(
// "enrolledCourse",
// JSON.stringify({
// courseId: selectedCourse.id,
// courseName: selectedCourse.kcmc,
// studentId: currentStudent.value.id,
// studentName: currentStudent.value.xm,
// enrollTime: new Date().toISOString(),
// //
// fee: selectedCourse.kcje || 0,
// location: selectedCourse.kcdd || "",
// teacher: selectedCourse.jsName || "",
// studyTime: selectedCourse.studyTime || "",
// })
// );
// //
// navigateTo("/pages/base/course-selection/payment");
// }, 1500);
uni.hideLoading();
if (res.resultCode == 1) {
setData({
...getData,
kcData,
studentInfo: currentStudent.value,
enrolledCourse: res.result,
});
uni.showToast({
title: "报名成功",
icon: "none",
});
setTimeout(() => {
uni.reLaunch({
url: `/pages/base/course-selection/enrolled`,
});
}, 1500);
}
}
},
});

View File

@ -1,10 +1,10 @@
<template>
<BasicLayout>
<view class="p-15">
<view class="white-bg-color p-15 r-md">
<view class="white-bg-color p-15 r-md" v-if="notice">
<view> 各位家长</view>
<view class="notice-text">
随着素质教育的不断深入学生各项素质能力的培养越来越受到学校家庭的重视我校根据教育局的有关精神继续举办兴趣班请各位家长根据实际情况遵照"孩子自主,家长自愿"的原则选择兴趣班请点击下一步确认知晓告知内容
{{ notice }}
</view>
</view>
<BasicSign ref="signCompRef" title="签名"></BasicSign>
@ -25,13 +25,23 @@
</template>
<script lang="ts" setup>
import { xkListApi } from "@/api/base/server";
import { xkgzsApi } from "@/api/base/server";
import { useDataStore } from "@/store/modules/data";
import { useUserStore } from "@/store/modules/user";
import { showLoading } from "@/utils/uniapp";
import { onLoad } from "@dcloudio/uni-app";
const signCompRef = ref<any>(null);
const sign_file = ref<any>(null);
const { setData, getGlobal } = useDataStore();
const notice = ref("");
onLoad(async () => {
showLoading({ title: "加载中..." });
const res = await xkgzsApi({ kcLx: "兴趣课" });
notice.value = res.rows?.[0]?.content || "";
uni.hideLoading();
});
async function submit() {
//
const data = await signCompRef.value.getSyncSignature();

View File

@ -6,10 +6,10 @@
<u-icon name="clock" size="22" color="#fff"></u-icon>
</view>
<view class="countdown-text">待支付</view>
<view class="countdown-timer">
<!-- <view class="countdown-timer">
<text>剩余</text>
<text class="time-value">{{ countdownTime }}</text>
</view>
</view> -->
</view>
<!-- 学生信息卡片 -->
@ -26,18 +26,11 @@
</view>
<view class="student-details">
<view class="student-name">
{{ student.name }}
<view class="gender-icon">
<u-icon
v-if="student.gender === '男'"
name="man"
color="#2879ff"
size="18"
></u-icon>
<u-icon v-else name="woman" color="#ff6b9d" size="18"></u-icon>
{{ student.xm }}
</view>
</view>
<view class="student-class">{{ student.class }}</view>
<view class="student-class"
>{{ student.njmc }}{{ student.bjmc }}</view
>
</view>
</view>
</view>
@ -50,15 +43,15 @@
<view class="course-info">
<image
class="course-image"
:src="course.image"
:src="imagUrl(course.xkkcImg)"
mode="aspectFill"
></image>
<view class="course-details">
<view class="course-name">{{ course.title }}</view>
<view class="course-teacher">开课老师{{ course.teacher }}</view>
<view class="course-location">上课地点{{ course.location }}</view>
<view class="course-name">{{ course.kcmc }}</view>
<view class="course-teacher">开课老师{{ course.jsName }}</view>
<view class="course-location">上课地点{{ course.kcdd }}</view>
<view class="course-price"
>金额<text class="price-value">¥{{ course.price }}</text></view
>金额<text class="price-value">¥{{ course.kcje }}</text></view
>
</view>
</view>
@ -68,7 +61,7 @@
<view class="payment-footer">
<view class="total-amount">
<text>总金额</text>
<text class="amount-value">¥{{ course.price }}</text>
<text class="amount-value">¥{{ course.kcje }}</text>
</view>
<view class="action-buttons">
@ -80,52 +73,47 @@
</template>
<script setup lang="ts">
import { xkqddeleteApi } from "@/api/base/server";
import { useDataStore } from "@/store/modules/data";
import { imagUrl } from "@/utils";
import { ref, onMounted, onUnmounted } from "vue";
const useData = useDataStore();
const { getData } = storeToRefs(useData);
//
const countdownTime = ref("1分20秒");
let timer: any = null;
let seconds = 1 * 60 + 20; // 120
// const countdownTime = ref("120");
// let timer: any = null;
// let seconds = 1 * 60 + 20; // 120
//
const student = ref({
name: "何明远",
gender: "男",
class: "三年级2班",
});
const student = ref(getData.value.studentInfo);
//
const course = ref({
id: 2,
title: "机器人创客",
teacher: "叶老师",
location: "第一教学楼302",
price: 142,
image: "/static/images/robot-course.jpg",
});
const course = ref(getData.value.enrolledCourse);
console.log(course.value);
//
const startCountdown = () => {
timer = setInterval(() => {
seconds--;
if (seconds <= 0) {
clearInterval(timer);
uni.showModal({
title: "支付超时",
content: "支付已超时,请重新选课",
showCancel: false,
success: () => {
goBack();
},
});
return;
}
// const startCountdown = () => {
// timer = setInterval(() => {
// seconds--;
// if (seconds <= 0) {
// clearInterval(timer);
// uni.showModal({
// title: "",
// content: "",
// showCancel: false,
// success: () => {
// goBack();
// },
// });
// return;
// }
const minutes = Math.floor(seconds / 60);
const remainSeconds = seconds % 60;
countdownTime.value = `${minutes}${remainSeconds}`;
}, 1000);
};
// const minutes = Math.floor(seconds / 60);
// const remainSeconds = seconds % 60;
// countdownTime.value = `${minutes}${remainSeconds}`;
// }, 1000);
// };
//
const goBack = () => {
@ -137,8 +125,12 @@ const cancelRegistration = () => {
uni.showModal({
title: "取消报名",
content: "确定要取消报名吗?",
success: (res) => {
success: async (res) => {
if (res.confirm) {
await xkqddeleteApi({
id: course.value.id,
});
uni.showToast({
title: "已取消报名",
icon: "success",
@ -157,32 +149,27 @@ const payNow = () => {
title: "支付中...",
});
//
setTimeout(() => {
uni.hideLoading();
// //
// setTimeout(() => {
// uni.hideLoading();
uni.redirectTo({
url: "/pages/base/course-selection/payment-success",
});
// uni.redirectTo({
// url: "/pages/base/course-selection/payment-fail",
// url: "/pages/base/course-selection/payment-success",
// });
}, 2000);
// // uni.redirectTo({
// // url: "/pages/base/course-selection/payment-fail",
// // });
// }, 2000);
};
onMounted(() => {
startCountdown();
//
// const courseId = uni.getStorageSync('selectedCourseId');
// const studentId = uni.getStorageSync('currentStudentId');
// fetchPaymentInfo(courseId, studentId);
// startCountdown();
});
onUnmounted(() => {
if (timer) {
clearInterval(timer);
}
// if (timer) {
// clearInterval(timer);
// }
});
</script>

View File

@ -1,42 +1,47 @@
<template>
<view class="home-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar">
<text class="page-title">智慧校园</text>
</view>
<view class="content-container">
<!-- 用户信息卡片 -->
<view class="user-info-card">
<view class="user-avatar-placeholder">
<image :src="currentStudent.avatar" class="w-full h-full"></image>
<view class="user-content">
<view class="user-avatar">
<image :src="currentStudent.avatar || '/static/base/home/11222.png'" class="avatar-img"></image>
<view class="avatar-ring"></view>
</view>
<!-- <view-->
<!-- class="user-details"-->
<!-- @click="navigateTo('/pages/system/login/login')"-->
<!-- >-->
<view
class="user-details"
>
<view class="user-details">
<text class="user-name">{{ currentStudent.name }}</text>
<view class="user-class-container">
<text class="user-class"
>{{ currentStudent.grade }} {{ currentStudent.class }}</text
>
<u-icon name="edit-pen" size="14" color="#909399"></u-icon>
<view class="class-tag">
<text class="user-class">{{ currentStudent.grade }} {{ currentStudent.class }}</text>
</view>
</view>
</view>
<view class="switch-btn" @click="showStudentSelector">
<u-icon name="arrow-down" size="12" color="#fff"></u-icon>
<text>切换</text>
</view>
</view>
<view class="switch-btn" @click="showStudentSelector">切换</view>
</view>
<!-- 学校横幅 -->
<view class="school-banner">
<view class="banner-placeholder">
<image src="/static/base/home/2211.png" class="w-full h-full"></image>
<view class="banner-content">
<image src="/static/base/home/2211.png" class="banner-img"></image>
<view class="banner-overlay">
<view class="banner-text">
<text class="banner-title">智慧校园</text>
<text class="banner-subtitle">让教育更智能让成长更精彩</text>
</view>
</view>
</view>
</view>
<!-- 功能菜单 -->
<view class="menu-section">
<view class="section-title">
<text class="title-text">校园服务</text>
<view class="title-line"></view>
</view>
<view class="grid-menu">
<view
v-for="(item, index) in menuItems"
@ -44,18 +49,20 @@
class="grid-item"
@click="handleMenuClick(item)"
>
<view class="grid-icon-placeholder">
<image :src="item.icon" class="w-full h-full"></image>
<view class="grid-icon-container">
<view class="icon-background"></view>
<image :src="item.icon" class="grid-icon"></image>
</view>
<text class="grid-text">{{ item.title }}</text>
</view>
</view>
</view>
<!-- 通知公告 -->
<view class="notice-section">
<view class="section-header">
<view class="blue-dot"></view>
<text class="section-title">通知公告</text>
<view class="section-title">
<text class="title-text">通知公告</text>
<view class="title-line"></view>
</view>
<view class="notice-list">
@ -65,11 +72,18 @@
class="notice-item"
@click="navigateTo('/pages/base/home/detail')"
>
<view class="notice-img-placeholder"></view>
<view class="notice-icon">
<u-icon name="bell" size="20" color="#4A90E2"></u-icon>
</view>
<view class="notice-content">
<text class="notice-title">{{ notice.title }}</text>
<text class="notice-desc">{{ notice.description }}</text>
<view class="notice-footer">
<text class="notice-date">{{ notice.date }}</text>
<view class="notice-arrow">
<u-icon name="arrow-right" size="12" color="#C8C9CC"></u-icon>
</view>
</view>
</view>
</view>
</view>
@ -81,12 +95,14 @@
:show="showSelector"
@close="showSelector = false"
mode="bottom"
round="10"
round="20"
>
<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 class="close-btn" @click="showSelector = false">
<u-icon name="close" size="18" color="#909399"></u-icon>
</view>
</view>
<view class="student-list">
<view
@ -96,19 +112,16 @@
:class="{ 'student-item-active': currentStudent.id === student.id }"
@click="switchStudent(student)"
>
<view class="student-avatar-placeholder"></view>
<view class="student-avatar">
<image :src="student.avatar || '/static/base/home/11222.png'" class="avatar-img"></image>
</view>
<view class="student-info">
<text class="student-name">{{ student.name }}</text>
<text class="student-class"
>{{ student.grade }} {{ student.class }}</text
>
<text class="student-class">{{ student.grade }} {{ student.class }}</text>
</view>
<view class="check-icon" v-if="currentStudent.id === student.id">
<u-icon name="checkmark" color="#4A90E2" size="18"></u-icon>
</view>
<u-icon
v-if="currentStudent.id === student.id"
name="checkmark"
color="#409EFF"
size="20"
></u-icon>
</view>
</view>
</view>
@ -117,8 +130,8 @@
</template>
<script setup lang="ts">
import {navigateTo} from "@/utils/uniapp";
import {ref} from "vue";
import { navigateTo } from "@/utils/uniapp";
import { ref } from "vue";
//
const menuItems = ref([
{
@ -236,213 +249,398 @@ function switchStudent(student: any) {
<style lang="scss" scoped>
.home-page {
background-color: #f8f8f8;
min-height: 100vh;
}
/* 自定义导航栏 */
.custom-navbar {
height: 44px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
padding: 0 15px;
padding-top: var(--status-bar-height);
background-color: #ffffff;
.page-title {
font-size: 18px;
font-weight: 500;
color: #303133;
}
.navbar-right {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
height: 44px;
display: flex;
align-items: center;
}
min-height: 100vh;
position: relative;
}
.content-container {
padding: 15px;
position: relative;
z-index: 1;
}
/* 用户信息卡片 */
.user-info-card {
position: relative;
background: linear-gradient(135deg, #ffffff 0%, #f8faff 100%);
border-radius: 20px;
padding: 20px;
margin-bottom: 20px;
margin-top: 20px;
box-shadow: 0 12px 40px rgba(74, 144, 226, 0.25);
border: 1px solid rgba(255, 255, 255, 0.8);
overflow: hidden;
.user-content {
display: flex;
align-items: center;
background-color: #ffffff;
border-radius: 12px;
padding: 12px 15px;
margin-bottom: 15px;
position: relative;
z-index: 2;
.user-avatar-placeholder {
width: 60px;
height: 60px;
.user-avatar {
position: relative;
width: 70px;
height: 70px;
border-radius: 50%;
border: 1px dashed #d0d0d0;
overflow: hidden;
box-shadow: 0 4px 16px rgba(74, 144, 226, 0.3);
border: 3px solid #ffffff;
flex-shrink: 0;
.avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-ring {
position: absolute;
top: -3px;
left: -3px;
width: calc(100% + 6px);
height: calc(100% + 6px);
border-radius: 50%;
border: 2px solid rgba(74, 144, 226, 0.3);
animation: pulse 2s infinite;
}
}
.user-details {
flex: 1;
margin-left: 12px;
margin-left: 15px;
display: flex;
flex-direction: column;
justify-content: center;
.user-name {
font-size: 16px;
font-weight: 500;
font-size: 18px;
font-weight: 600;
color: #303133;
margin-bottom: 6px;
margin-bottom: 8px;
line-height: 1.2;
}
.user-class-container {
display: flex;
align-items: center;
.class-tag {
background: linear-gradient(135deg, #4A90E2 0%, #357ABD 100%);
color: #ffffff;
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
box-shadow: 0 2px 8px rgba(74, 144, 226, 0.3);
.user-class {
font-size: 13px;
color: #606266;
margin-right: 5px;
line-height: 1;
}
}
}
}
.switch-btn {
padding: 4px 12px;
background-color: rgba(64, 158, 255, 0.05);
color: #409eff;
border: 1px solid #409eff;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
padding: 8px 16px;
background: linear-gradient(135deg, #4A90E2 0%, #357ABD 100%);
color: #ffffff;
border-radius: 20px;
font-size: 13px;
font-weight: 500;
box-shadow: 0 4px 12px rgba(74, 144, 226, 0.3);
transition: all 0.3s ease;
flex-shrink: 0;
min-width: 60px;
height: 16px;
&:active {
transform: scale(0.95);
}
text {
line-height: 1;
}
}
}
}
/* 学校横幅 */
.school-banner {
margin-bottom: 15px;
border-radius: 12px;
margin-bottom: 25px;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 10px 32px rgba(0, 0, 0, 0.15);
.banner-placeholder {
.banner-content {
position: relative;
width: 100%;
height: 150px;
border: 1px dashed #d0d0d0;
height: 160px;
.banner-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.banner-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, rgba(74, 144, 226, 0.8) 0%, rgba(53, 122, 189, 0.9) 100%);
display: flex;
align-items: center;
justify-content: center;
.banner-text {
text-align: center;
.banner-title {
font-size: 26px;
font-weight: 700;
color: #ffffff;
margin-bottom: 8px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.banner-subtitle {
font-size: 14px;
color: rgba(255, 255, 255, 0.9);
font-weight: 400;
}
}
}
}
}
/* 功能菜单 */
.grid-menu {
.menu-section {
margin-bottom: 25px;
.section-title {
display: flex;
flex-wrap: wrap;
background-color: #ffffff;
border-radius: 12px;
padding: 10px;
align-items: center;
margin-bottom: 20px;
padding: 0 5px;
.title-text {
font-size: 18px;
font-weight: 600;
color: #303133;
position: relative;
padding-left: 12px;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 16px;
background: linear-gradient(135deg, #4A90E2 0%, #357ABD 100%);
border-radius: 2px;
}
}
.title-line {
flex: 1;
height: 1px;
background: linear-gradient(90deg, #e0e6ed 0%, transparent 100%);
margin-left: 20px;
}
}
.grid-menu {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0;
background: linear-gradient(135deg, #ffffff 0%, #f8faff 100%);
border-radius: 16px;
box-shadow: 0 8px 32px rgba(74, 144, 226, 0.2);
border: 1px solid rgba(255, 255, 255, 0.8);
.grid-item {
width: 33.33%;
display: flex;
flex-direction: column;
align-items: center;
padding: 15px 0;
justify-content: center;
padding: 20px 10px;
transition: all 0.3s ease;
border-radius: 12px;
position: relative;
.grid-icon-placeholder {
width: 40px;
height: 40px;
border-radius: 6px;
margin-bottom: 8px;
&:active {
transform: scale(0.95);
background-color: rgba(74, 144, 226, 0.05);
}
.grid-icon-container {
position: relative;
width: 48px;
height: 48px;
margin-bottom: 12px;
display: flex;
align-items: center;
justify-content: center;
.icon-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 12px;
background: linear-gradient(135deg, #f0f4ff 0%, #e6f0ff 100%);
box-shadow: 0 2px 8px rgba(74, 144, 226, 0.15);
}
.grid-icon {
position: relative;
z-index: 1;
width: 28px;
height: 28px;
object-fit: contain;
}
}
.grid-text {
font-size: 14px;
font-size: 13px;
font-weight: 500;
color: #303133;
text-align: center;
line-height: 1.2;
}
}
}
}
/* 通知公告 */
.notice-section {
.section-header {
.section-title {
display: flex;
align-items: center;
margin-bottom: 15px;
margin-bottom: 20px;
padding: 0 5px;
.blue-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #1976d2;
margin-right: 8px;
.title-text {
font-size: 18px;
font-weight: 600;
color: #303133;
position: relative;
padding-left: 12px;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 16px;
background: linear-gradient(135deg, #4A90E2 0%, #357ABD 100%);
border-radius: 2px;
}
}
.section-title {
font-size: 16px;
font-weight: 500;
color: #303133;
.title-line {
flex: 1;
height: 1px;
background: linear-gradient(90deg, #e0e6ed 0%, transparent 100%);
margin-left: 20px;
}
}
.notice-list {
border-radius: 12px;
overflow: hidden;
.notice-item {
display: flex;
background-color: #ffffff;
padding: 15px;
margin-bottom: 10px;
border-radius: 12px;
align-items: flex-start;
background: linear-gradient(135deg, #ffffff 0%, #f8faff 100%);
padding: 18px;
margin-bottom: 12px;
border-radius: 16px;
box-shadow: 0 8px 24px rgba(74, 144, 226, 0.18);
border: 1px solid rgba(255, 255, 255, 0.8);
transition: all 0.3s ease;
.notice-img-placeholder {
width: 80px;
height: 80px;
background-color: #f0f0f0;
border: 1px dashed #d0d0d0;
border-radius: 8px;
margin-right: 12px;
&:active {
transform: translateY(-2px);
box-shadow: 0 12px 36px rgba(74, 144, 226, 0.25);
}
.notice-icon {
width: 50px;
height: 50px;
background: linear-gradient(135deg, #f0f4ff 0%, #e6f0ff 100%);
border-radius: 12px;
margin-right: 15px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(74, 144, 226, 0.15);
flex-shrink: 0;
}
.notice-content {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
.notice-title {
font-size: 15px;
font-weight: 500;
font-weight: 600;
color: #303133;
margin-bottom: 6px;
margin-bottom: 8px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
line-height: 1.3;
}
.notice-desc {
font-size: 13px;
color: #909399;
margin-bottom: 10px;
color: #606266;
margin-bottom: 12px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
flex: 1;
line-height: 1.4;
}
.notice-footer {
display: flex;
align-items: center;
justify-content: space-between;
.notice-date {
font-size: 12px;
color: #c0c4cc;
color: #909399;
font-weight: 400;
line-height: 1;
}
.notice-arrow {
width: 20px;
height: 20px;
background-color: #f5f7fa;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
}
}
}
@ -451,68 +649,160 @@ function switchStudent(student: any) {
/* 学生选择器弹窗样式 */
.student-selector {
background-color: #ffffff;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
padding-bottom: 20px;
background: linear-gradient(135deg, #ffffff 0%, #f8faff 100%);
border-top-left-radius: 20px;
border-top-right-radius: 20px;
padding-bottom: 30px;
.selector-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border-bottom: 1px solid #f2f2f2;
padding: 20px;
border-bottom: 1px solid #f0f2f5;
.selector-title {
font-size: 16px;
font-weight: 500;
font-size: 18px;
font-weight: 600;
color: #303133;
line-height: 1;
}
.close-btn {
width: 32px;
height: 32px;
background-color: #f5f7fa;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
flex-shrink: 0;
&:active {
transform: scale(0.9);
background-color: #e6e8eb;
}
}
}
.student-list {
padding: 0 15px;
padding: 0 20px;
.student-item {
display: flex;
align-items: center;
padding: 15px 0;
border-bottom: 1px solid #f2f2f2;
padding: 16px 0;
border-bottom: 1px solid #f5f7fa;
transition: all 0.3s ease;
border-radius: 12px;
margin-bottom: 8px;
&:last-child {
border-bottom: none;
margin-bottom: 0;
}
&-active {
background-color: rgba(64, 158, 255, 0.05);
background: linear-gradient(135deg, rgba(74, 144, 226, 0.08) 0%, rgba(53, 122, 189, 0.05) 100%);
padding: 16px 12px;
}
.student-avatar-placeholder {
width: 45px;
height: 45px;
.student-avatar {
width: 50px;
height: 50px;
border-radius: 50%;
background-color: #f0f0f0;
border: 1px dashed #d0d0d0;
overflow: hidden;
flex-shrink: 0;
box-shadow: 0 2px 8px rgba(74, 144, 226, 0.15);
border: 2px solid #ffffff;
.avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.student-info {
flex: 1;
margin-left: 12px;
margin-left: 15px;
display: flex;
flex-direction: column;
justify-content: center;
min-width: 0;
.student-name {
font-size: 15px;
font-weight: 500;
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 4px;
line-height: 1.2;
}
.student-class {
font-size: 13px;
color: #606266;
font-weight: 400;
line-height: 1;
}
}
.check-icon {
width: 24px;
height: 24px;
border-radius: 50%;
background: linear-gradient(135deg, #4A90E2 0%, #357ABD 100%);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(74, 144, 226, 0.3);
flex-shrink: 0;
}
}
}
}
/* 动画效果 */
@keyframes pulse {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.05);
opacity: 0.7;
}
100% {
transform: scale(1);
opacity: 1;
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 响应式优化 */
@media (max-width: 375px) {
.content-container {
padding: 12px;
}
.user-info-card .user-content .user-avatar {
width: 60px;
height: 60px;
}
.grid-menu .grid-item {
padding: 15px 8px;
}
}
</style>

View File

@ -1,6 +1,5 @@
<template>
<view class="interest-class-page">
<u-navbar title="兴趣课" :autoBack="true"></u-navbar>
<view class="content-container">
<!-- 顶部蓝色背景横幅 -->

View File

@ -34,7 +34,6 @@ function toHome(data: any) {
}
onLoad(async (data: any) => {
console.log(data);
setGlobal(data);
if (data && data.openId) {
checkOpenId({ openId: data.openId, appCode: "JZ" })

View File

@ -112,7 +112,7 @@ import { useDataStore } from "@/store/modules/data";
const [register, { getValue }] = useForm({
formsProps: { labelWidth: 100 },
schema: [
{ title: "监人信息" },
{ title: "监人信息" },
{
field: "jzxsgxId",
label: "与学生关系",

View File

@ -1,52 +1,59 @@
import {AUTH_KEY, BASE_URL, HOMEAGENT, RESULT_CODE_NOT_LOGIN} from "@/config";
import {useUserStore} from "@/store/modules/user";
import {getLogin} from "../permission";
import {request} from "@/utils/request/request";
import {showToast} from "@/utils/uniapp";
import { AUTH_KEY, BASE_URL, HOMEAGENT, RESULT_CODE_NOT_LOGIN } from "@/config";
import { useUserStore } from "@/store/modules/user";
import { getLogin } from "../permission";
import { request } from "@/utils/request/request";
import { showToast } from "@/utils/uniapp";
let count: boolean = false;
function _loginExpiredModal() {
const store = useUserStore();
store.logout()
store.logout();
uni.showModal({
title: '提示',
content: '登录过期,请重新登录',
title: "提示",
content: "登录过期,请重新登录",
showCancel: false,
success: function (res) {
if (res.confirm) {
count = false
getLogin()
count = false;
getLogin();
} else if (res.cancel) {
count = false
count = false;
uni.navigateBack({
delta: 1,
fail: (err) => {
console.log(err)
}
})
}
console.log(err);
},
});
}
},
});
}
export const config = {
baseUrl: process.env.NODE_ENV == 'development' ? HOMEAGENT ? '/base' : BASE_URL : BASE_URL,
header: {}
}
baseUrl:
process.env.NODE_ENV == "development"
? HOMEAGENT
? "/base"
: BASE_URL
: BASE_URL,
header: {},
};
export const interceptor = {
request: (config: UniNamespace.UploadFileOption | UniNamespace.RequestOptions) => {
request: (
config: UniNamespace.UploadFileOption | UniNamespace.RequestOptions
) => {
const store = useUserStore();
if (store.getToken) {
config.header = {
[AUTH_KEY]: store.getToken
[AUTH_KEY]: store.getToken,
};
}
},
response: (response: any) => {
if (response.data && RESULT_CODE_NOT_LOGIN == response.data.resultCode) {
if (!count) {
_loginExpiredModal()
_loginExpiredModal();
}
count = true;
return;
@ -57,77 +64,126 @@ export const interceptor = {
if (response.data) {
return response.data;
} else {
console.log('接口无返回值', response)
console.log("接口无返回值", response);
}
}
}
},
};
export function get<T = any>(url: string, data?: any, options?: UniNamespace.RequestOptions): Promise<Requests<T>> {
export function get<T = any>(
url: string,
data?: any,
options?: UniNamespace.RequestOptions
): Promise<Requests<T>> {
return new Promise((resolve, reject) => {
request(Object.assign({}, {
request(
Object.assign(
{},
{
url,
data,
method: 'GET',
dataType: 'json',
responseType: 'text',
}, options)).then(res => {
method: "GET",
dataType: "json",
responseType: "text",
},
options
)
).then((res) => {
if (res.resultCode == 1) {
resolve(res)
resolve(res);
} else {
if (res.resultCode) {
if (res.resultCode != RESULT_CODE_NOT_LOGIN) {
showToast(res.message || '接口异常')
reject(res)
showToast(res.message || "接口异常");
reject(res);
}
} else {
if (res.rows) {
resolve(res)
resolve(res);
} else {
showToast(res.message || '接口异常')
reject(res)
showToast(res.message || "接口异常");
reject(res);
}
}
}
})
})
});
});
}
export function post<T = any>(url: string, data?: any, options?: UniNamespace.RequestOptions): Promise<Requests<T>> {
export function post<T = any>(
url: string,
data?: any,
options?: UniNamespace.RequestOptions
): Promise<Requests<T>> {
return new Promise((resolve, reject) => {
request(Object.assign({}, {
request(
Object.assign(
{},
{
url,
data,
method: 'POST',
dataType: 'json',
responseType: 'text',
}, options)).then(res => {
method: "POST",
dataType: "json",
responseType: "text",
},
options
)
).then((res) => {
if (res.resultCode == 1) {
resolve(res)
resolve(res);
} else {
if (res.resultCode) {
if (res.resultCode != RESULT_CODE_NOT_LOGIN) {
showToast(res.message || '接口异常')
reject(res)
showToast(res.message || "接口异常");
reject(res);
}
} else {
if (res.rows) {
resolve(res)
resolve(res);
} else {
showToast(res.message || '接口异常')
reject(res)
showToast(res.message || "接口异常");
reject(res);
}
}
}
})
})
});
});
}
export function file(url: string, data: Blob, options?: UniNamespace.UploadFileOption) {
return request(Object.assign({}, {
export function file(
url: string,
data: Blob,
options?: UniNamespace.UploadFileOption
) {
return new Promise((resolve, reject) => {
request(
Object.assign(
{},
{
url,
filePath: data,
name: 'files',
}, options), true)
name: "files",
},
options
),
true
).then((res) => {
if (res.resultCode == 1) {
resolve(res);
} else {
if (res.resultCode) {
if (res.resultCode != RESULT_CODE_NOT_LOGIN) {
showToast(res.message || "接口异常");
reject(res);
}
} else {
if (res.rows) {
resolve(res);
} else {
showToast(res.message || "接口异常");
reject(res);
}
}
}
});
});
}