选课调整
This commit is contained in:
parent
073e462d71
commit
1598d8e886
19
src/api/analysis/xk.ts
Normal file
19
src/api/analysis/xk.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { get, post } from '@/utils/request'
|
||||
|
||||
// 获取选课列表
|
||||
export const getXkListApi = () => {
|
||||
return get('/api/xk/findPage', {
|
||||
page: 1,
|
||||
rows: 100,
|
||||
status: 'A'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取兴趣课选课列表
|
||||
export const getXkCourseListApi = (njId: string, bjId: string, xkId?: string) => {
|
||||
const params: any = { njId, bjId }
|
||||
if (xkId) {
|
||||
params.xkId = xkId
|
||||
}
|
||||
return get('/api/xk/getXkCourseList', params)
|
||||
}
|
||||
10
src/api/analysis/xkDm.ts
Normal file
10
src/api/analysis/xkDm.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { get } from '@/utils/request'
|
||||
|
||||
// 获取选课点名统计
|
||||
export const getXkDmStatisticsApi = (xkId: string, startTime?: string, endTime?: string) => {
|
||||
const params: any = { xkId }
|
||||
if (startTime) params.startTime = startTime
|
||||
if (endTime) params.endTime = endTime
|
||||
|
||||
return get('/api/xkDm/getXkDmStatistics', params)
|
||||
}
|
||||
10
src/api/analysis/xs.ts
Normal file
10
src/api/analysis/xs.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { get, post } from "@/utils/request";
|
||||
|
||||
// 学生档案相关API接口
|
||||
|
||||
/**
|
||||
* 根据年级ID和班级ID查询学生及家长信息(简化版)
|
||||
*/
|
||||
export function findStudentInfoByNjAndBjSimpleApi(njId: string, bjId: string) {
|
||||
return get('/api/xs/findStudentInfoByNjAndBjSimple', { njId, bjId });
|
||||
}
|
||||
@ -560,6 +560,41 @@
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/analysis/xs/studentArchive",
|
||||
"style": {
|
||||
"navigationBarTitleText": "学生档案",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/analysis/xk/xkCourse",
|
||||
"style": {
|
||||
"navigationBarTitleText": "课程明单",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/analysis/xk/xkList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选课清单",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/analysis/xk/dmStatistics",
|
||||
"style": {
|
||||
"navigationBarTitleText": "点名统计",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/analysis/xk/dmXkList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "点名选课列表",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/routine/qd/index",
|
||||
"style": {
|
||||
|
||||
@ -320,6 +320,30 @@ const sections = reactive<Section[]>([
|
||||
permissionKey: "routine-bjjl", // 发布接龙权限编码
|
||||
path: "/pages/view/notice/index",
|
||||
},
|
||||
{
|
||||
id: "hs4",
|
||||
icon: "xsda",
|
||||
text: "学生档案",
|
||||
show: true,
|
||||
permissionKey: "home-xsda", // 学生档案权限编码
|
||||
path: "/pages/view/analysis/xs/studentArchive",
|
||||
},
|
||||
{
|
||||
id: "hs5",
|
||||
icon: "xkqd",
|
||||
text: "选课清单",
|
||||
show: true,
|
||||
permissionKey: "home-xkqd", // 兴趣课选课权限编码
|
||||
path: "/pages/view/analysis/xk/xkList",
|
||||
},
|
||||
{
|
||||
id: "hs6",
|
||||
icon: "dmtj",
|
||||
text: "点名统计",
|
||||
show: true,
|
||||
permissionKey: "home-dmtj", // 点名统计权限编码
|
||||
path: "/pages/view/analysis/xk/dmXkList",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
1034
src/pages/view/analysis/xk/dmStatistics.vue
Normal file
1034
src/pages/view/analysis/xk/dmStatistics.vue
Normal file
File diff suppressed because it is too large
Load Diff
436
src/pages/view/analysis/xk/dmXkList.vue
Normal file
436
src/pages/view/analysis/xk/dmXkList.vue
Normal file
@ -0,0 +1,436 @@
|
||||
<template>
|
||||
<view class="xk-list-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<text class="page-title">选课列表</text>
|
||||
</view>
|
||||
|
||||
<!-- 选课列表 -->
|
||||
<view class="section" v-if="xkList.length > 0">
|
||||
<view class="section-title">
|
||||
选课信息 ({{ xkList.length }}个选课)
|
||||
</view>
|
||||
|
||||
<!-- 选课列表 -->
|
||||
<view class="xk-list">
|
||||
<view
|
||||
v-for="xk in xkList"
|
||||
:key="xk.id"
|
||||
class="xk-item bg-white r-md p-12"
|
||||
@click="goToXkCourse(xk)"
|
||||
>
|
||||
<view class="xk-header">
|
||||
<view class="xk-title">{{ xk.xkmc }}</view>
|
||||
<view class="xk-actions">
|
||||
<view v-if="xk.xkStatus !== '已结束'" class="xk-status" :class="getStatusClass(xk.xkStatus)">
|
||||
{{ xk.xkStatus }}
|
||||
</view>
|
||||
<view v-else class="more-btn" @click.stop="showMoreOptions(xk)">
|
||||
<image src="/static/base/view/more.png" class="more-icon" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="xk-info">
|
||||
<view class="info-row">
|
||||
<text class="info-label">选课类型:</text>
|
||||
<text class="info-value">{{ xk.xklxName || '未知' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">学期名称:</text>
|
||||
<text class="info-value">{{ xk.xqmc || '未知' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">年级范围:</text>
|
||||
<text class="info-value">{{ xk.njmc || '全部' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="!loading && xkList.length === 0 && hasSearched">
|
||||
<view class="empty-icon">📚</view>
|
||||
<view class="empty-text">暂无选课数据</view>
|
||||
<view class="empty-tip">请联系管理员添加选课信息</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-state" v-if="loading">
|
||||
<view class="loading-text">正在加载...</view>
|
||||
</view>
|
||||
|
||||
<!-- 滚动加载更多 -->
|
||||
<view class="load-more" v-if="hasMore && !loading">
|
||||
<view class="load-more-text">上拉加载更多</view>
|
||||
</view>
|
||||
|
||||
<!-- 没有更多数据 -->
|
||||
<view class="no-more" v-if="!hasMore && hasSearched && xkList.length > 0">
|
||||
<view class="no-more-text">没有更多数据了</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getXkListApi } from '@/api/analysis/xk'
|
||||
|
||||
// 定义类型接口
|
||||
interface XkInfo {
|
||||
id: string
|
||||
xkmc: string
|
||||
xklxId: string
|
||||
xklxName: string
|
||||
njId: string
|
||||
njmc: string
|
||||
xqId: string
|
||||
xqmc: string
|
||||
xkkstime: string
|
||||
xkjstime: string
|
||||
xkzt: number
|
||||
xkStatus: string
|
||||
allXkNum: number
|
||||
yfXkNum: number
|
||||
wfXkNum: number
|
||||
status: string
|
||||
createdTime: string
|
||||
}
|
||||
|
||||
// 响应式数据
|
||||
const xkList = ref<XkInfo[]>([])
|
||||
const loading = ref(false)
|
||||
const hasSearched = ref(false)
|
||||
const hasMore = ref(true)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
|
||||
// 页面加载
|
||||
onLoad(() => {
|
||||
console.log('选课列表页面加载')
|
||||
loadXkList()
|
||||
})
|
||||
|
||||
// 加载选课列表
|
||||
const loadXkList = async (isLoadMore = false) => {
|
||||
if (loading.value) return
|
||||
|
||||
loading.value = true
|
||||
hasSearched.value = true
|
||||
|
||||
try {
|
||||
// 调用后端接口查询选课列表
|
||||
const response = await getXkListApi()
|
||||
|
||||
console.log('API返回结果:', response)
|
||||
|
||||
if (response && response.rows) {
|
||||
const newData = response.rows || []
|
||||
|
||||
if (isLoadMore) {
|
||||
// 加载更多时追加数据
|
||||
xkList.value = [...xkList.value, ...newData]
|
||||
} else {
|
||||
// 首次加载或刷新时替换数据
|
||||
xkList.value = newData
|
||||
}
|
||||
|
||||
// 判断是否还有更多数据
|
||||
hasMore.value = newData.length >= pageSize.value
|
||||
} else {
|
||||
throw new Error('查询失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('查询选课列表失败:', error)
|
||||
uni.showToast({
|
||||
title: '查询失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
if (!isLoadMore) {
|
||||
xkList.value = []
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载更多数据
|
||||
const loadMore = () => {
|
||||
if (loading.value || !hasMore.value) {
|
||||
return
|
||||
}
|
||||
|
||||
currentPage.value++
|
||||
loadXkList(true)
|
||||
}
|
||||
|
||||
// 滚动到底部触发加载更多
|
||||
const onReachBottom = () => {
|
||||
loadMore()
|
||||
}
|
||||
|
||||
// 跳转到点名统计页面
|
||||
const goToXkCourse = (xk: XkInfo) => {
|
||||
console.log('跳转到点名统计:', xk)
|
||||
uni.navigateTo({
|
||||
url: `/pages/view/analysis/xk/dmStatistics?xkId=${xk.id}`
|
||||
})
|
||||
}
|
||||
|
||||
// 显示更多选项 - 直接跳转到点名统计页面
|
||||
const showMoreOptions = (xk: XkInfo) => {
|
||||
console.log('点击更多按钮,直接跳转到点名统计:', xk)
|
||||
goToXkCourse(xk)
|
||||
}
|
||||
|
||||
// 获取状态样式类
|
||||
const getStatusClass = (status: string) => {
|
||||
switch (status) {
|
||||
case '未开始':
|
||||
return 'status-pending'
|
||||
case '进行中':
|
||||
return 'status-active'
|
||||
case '已结束':
|
||||
return 'status-ended'
|
||||
default:
|
||||
return 'status-unknown'
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.xk-list-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.section {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.xk-list {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.xk-item {
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2rpx);
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.xk-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
padding-bottom: 15rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.xk-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.xk-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.xk-status {
|
||||
font-size: 24rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-weight: bold;
|
||||
|
||||
&.status-pending {
|
||||
background-color: #fff7e6;
|
||||
color: #fa8c16;
|
||||
border: 1rpx solid #ffd591;
|
||||
}
|
||||
|
||||
&.status-active {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
border: 1rpx solid #b7eb8f;
|
||||
}
|
||||
|
||||
&.status-ended {
|
||||
background-color: #fff2f0;
|
||||
color: #ff4d4f;
|
||||
border: 1rpx solid #ffccc7;
|
||||
}
|
||||
|
||||
&.status-unknown {
|
||||
background-color: #f5f5f5;
|
||||
color: #999;
|
||||
border: 1rpx solid #d9d9d9;
|
||||
}
|
||||
}
|
||||
|
||||
.more-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #f5f5f5;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #e6f7ff;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #d9d9d9;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.more-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
|
||||
.xk-info {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
margin-bottom: 12rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #666;
|
||||
margin-right: 10rpx;
|
||||
min-width: 140rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 40rpx;
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 100rpx;
|
||||
|
||||
.loading-text {
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.r-md {
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.p-12 {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
/* 滚动加载样式 */
|
||||
.load-more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
|
||||
.load-more-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.no-more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
|
||||
.no-more-text {
|
||||
font-size: 28rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
944
src/pages/view/analysis/xk/xkCourse.vue
Normal file
944
src/pages/view/analysis/xk/xkCourse.vue
Normal file
@ -0,0 +1,944 @@
|
||||
<template>
|
||||
<view class="xk-course-container">
|
||||
<!-- 班级选择器 -->
|
||||
<view class="section">
|
||||
<view class="section-title">选择班级</view>
|
||||
<BasicNjBjPicker
|
||||
v-model="classInfo"
|
||||
placeholder="请选择年级班级"
|
||||
@change="onClassChange"
|
||||
icon-arrow="right"
|
||||
:customStyle="{ backgroundColor: '#fff', borderRadius: '0', padding: '12px 15px' }"
|
||||
/>
|
||||
|
||||
<!-- 班级选择提示 -->
|
||||
<view v-if="!classInfo" class="class-tip">
|
||||
<text class="tip-icon">ℹ️</text>
|
||||
<text class="tip-text">请先选择班级</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 选课统计 -->
|
||||
<view class="section" v-if="classInfo">
|
||||
<view class="section-title">
|
||||
选课统计
|
||||
</view>
|
||||
|
||||
<!-- 统计信息 -->
|
||||
<view class="stats-container">
|
||||
<view
|
||||
class="stat-item"
|
||||
:class="{ active: selectedStatType === 'registered' }"
|
||||
@click="onStatItemClick('registered')"
|
||||
>
|
||||
<text class="stat-number registered">{{ registeredCount }}</text>
|
||||
<text class="stat-label">已报名</text>
|
||||
</view>
|
||||
<view
|
||||
class="stat-item"
|
||||
:class="{ active: selectedStatType === 'unregistered' }"
|
||||
@click="onStatItemClick('unregistered')"
|
||||
>
|
||||
<text class="stat-number unregistered">{{ unregisteredCount }}</text>
|
||||
<text class="stat-label">未报名</text>
|
||||
</view>
|
||||
<view
|
||||
class="stat-item"
|
||||
:class="{ active: selectedStatType === 'all' }"
|
||||
@click="onStatItemClick('all')"
|
||||
>
|
||||
<text class="stat-number">{{ totalStudents }}</text>
|
||||
<text class="stat-label">总人数</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 课程列表或学生列表 -->
|
||||
<view class="section" v-if="classInfo && (courseList.length > 0 || studentList.length > 0)">
|
||||
<view class="section-title">
|
||||
{{ getListTitle() }} ({{ getListCount() }})
|
||||
</view>
|
||||
|
||||
<!-- 课程列表 -->
|
||||
<view v-if="selectedStatType === 'registered'" class="course-list">
|
||||
<view
|
||||
v-for="course in filteredCourseList"
|
||||
:key="course.id"
|
||||
class="course-item bg-white r-md p-12"
|
||||
>
|
||||
<view class="course-header">
|
||||
<view class="course-title">{{ course.kcmc }}</view>
|
||||
<view class="course-student-count">{{ course.studentCount }}人</view>
|
||||
</view>
|
||||
|
||||
<view class="course-info">
|
||||
<view class="info-row">
|
||||
<text class="info-label">上课时间:</text>
|
||||
<text class="info-value">{{ course.studyTime }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">上课地点:</text>
|
||||
<text class="info-value">{{ course.kcdd }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">上课老师:</text>
|
||||
<text class="info-value">{{ course.jsName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学生列表 -->
|
||||
<view class="student-list" v-if="course.students && course.students.length > 0">
|
||||
<view class="student-list-title">报名学生:</view>
|
||||
<view class="student-tags">
|
||||
<view
|
||||
v-for="student in course.students"
|
||||
:key="student.xsId"
|
||||
class="student-tag"
|
||||
>
|
||||
{{ student.xm }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学生列表 -->
|
||||
<view v-if="selectedStatType === 'unregistered' || selectedStatType === 'all'" class="student-list">
|
||||
<view class="student-grid">
|
||||
<view
|
||||
v-for="student in filteredStudentList"
|
||||
:key="student.xsId"
|
||||
class="student-item bg-white r-md p-12"
|
||||
@click="viewStudentDetail(student)"
|
||||
>
|
||||
<view class="flex-row items-center">
|
||||
<view class="avatar-container mr-8">
|
||||
<image
|
||||
class="student-avatar"
|
||||
:src="imagUrl(student.xstx || '')"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
</view>
|
||||
<view class="flex-1 overflow-hidden">
|
||||
<view class="student-name mb-8">
|
||||
<text class="font-14 cor-333">{{ student.xsxm }}</text>
|
||||
</view>
|
||||
<view class="flex-row">
|
||||
<!-- 报名状态标签 -->
|
||||
<view
|
||||
class="status-tag"
|
||||
:class="getRegistrationStatusClass(student)"
|
||||
>
|
||||
{{ getRegistrationStatus(student) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 更多图标 -->
|
||||
<view class="more-icon-container" @click.stop="showMoreOptions(student)">
|
||||
<image
|
||||
class="more-icon"
|
||||
src="/static/base/view/more.png"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="!loading && courseList.length === 0 && hasSearched">
|
||||
<view class="empty-icon">📚</view>
|
||||
<view class="empty-text">暂无选课数据</view>
|
||||
<view class="empty-tip">请选择年级班级后查询</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-state" v-if="loading">
|
||||
<view class="loading-text">正在加载...</view>
|
||||
</view>
|
||||
|
||||
<!-- 滚动加载更多 -->
|
||||
<view class="load-more" v-if="hasMore && !loading">
|
||||
<view class="load-more-text">上拉加载更多</view>
|
||||
</view>
|
||||
|
||||
<!-- 没有更多数据 -->
|
||||
<view class="no-more" v-if="!hasMore && hasSearched && (courseList.length > 0 || studentList.length > 0)">
|
||||
<view class="no-more-text">没有更多数据了</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { BasicNjBjPicker } from '@/components/BasicNjBjPicker'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getXkCourseListApi } from '@/api/analysis/xk'
|
||||
import { findStudentInfoByNjAndBjSimpleApi } from '@/api/analysis/xs'
|
||||
import { imagUrl } from "@/utils"
|
||||
import { useDataStore } from '@/store/modules/data'
|
||||
|
||||
// 定义类型接口
|
||||
interface ClassInfo {
|
||||
njId: string
|
||||
bjId: string
|
||||
nj: any
|
||||
bj: any
|
||||
}
|
||||
|
||||
interface CourseInfo {
|
||||
id: string
|
||||
kcmc: string
|
||||
studyTime: string
|
||||
kcdd: string
|
||||
jsName: string
|
||||
totalStudents: number
|
||||
totalRegisteredCount: number
|
||||
studentCount: number
|
||||
studentNames?: string
|
||||
students: StudentInfo[]
|
||||
}
|
||||
|
||||
interface StudentInfo {
|
||||
xsId: string
|
||||
xm: string
|
||||
xsxm: string
|
||||
xstx?: string
|
||||
njId: string
|
||||
njmcId: string
|
||||
njmc: string
|
||||
bc: string
|
||||
bjId: string
|
||||
bjmc: string
|
||||
jzIds?: string
|
||||
jzxm?: string
|
||||
}
|
||||
|
||||
// 响应式数据
|
||||
const classInfo = ref<ClassInfo | null>(null)
|
||||
const courseList = ref<CourseInfo[]>([])
|
||||
const studentList = ref<StudentInfo[]>([])
|
||||
const loading = ref(false)
|
||||
const hasSearched = ref(false)
|
||||
const hasMore = ref(true)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const selectedStatType = ref<string>('registered') // 当前选中的统计类型,默认选择已报名
|
||||
const xkId = ref<string>('') // 选课ID
|
||||
const xkmc = ref<string>('') // 选课名称
|
||||
|
||||
// 使用store
|
||||
const { setXs } = useDataStore()
|
||||
|
||||
// 计算属性
|
||||
const totalStudents = computed(() => {
|
||||
// 如果课程列表不为空,取第一个课程的总人数(所有课程的总人数应该相同)
|
||||
if (courseList.value.length > 0) {
|
||||
return courseList.value[0].totalStudents || 0
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
const registeredCount = computed(() => {
|
||||
// 使用总的已报名人数(所有课程去重)
|
||||
if (courseList.value.length > 0) {
|
||||
return courseList.value[0].totalRegisteredCount || 0
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
const unregisteredCount = computed(() => {
|
||||
// 总人数 - 已报名人数 = 未报名人数
|
||||
return Math.max(0, totalStudents.value - registeredCount.value)
|
||||
})
|
||||
|
||||
// 根据选中的统计类型过滤课程列表
|
||||
const filteredCourseList = computed(() => {
|
||||
if (selectedStatType.value === 'all') {
|
||||
return courseList.value
|
||||
} else if (selectedStatType.value === 'registered') {
|
||||
return courseList.value.filter(course => course.studentCount > 0)
|
||||
} else if (selectedStatType.value === 'unregistered') {
|
||||
return courseList.value.filter(course => course.studentCount === 0)
|
||||
}
|
||||
return courseList.value
|
||||
})
|
||||
|
||||
// 根据选中的统计类型过滤学生列表
|
||||
const filteredStudentList = computed(() => {
|
||||
if (selectedStatType.value === 'all') {
|
||||
return studentList.value
|
||||
} else if (selectedStatType.value === 'unregistered') {
|
||||
// 未报名学生:所有学生中排除已报名的学生
|
||||
const registeredStudentNames = new Set()
|
||||
courseList.value.forEach(course => {
|
||||
if (course.studentNames) {
|
||||
// 将逗号分隔的学生姓名分割并添加到Set中
|
||||
const names = course.studentNames.split(',').map((name: string) => name.trim()).filter((name: string) => name)
|
||||
names.forEach((name: string) => registeredStudentNames.add(name))
|
||||
}
|
||||
})
|
||||
|
||||
console.log('已报名学生姓名:', Array.from(registeredStudentNames))
|
||||
console.log('所有学生数量:', studentList.value.length)
|
||||
|
||||
const unregisteredStudents = studentList.value.filter(student => !registeredStudentNames.has(student.xsxm))
|
||||
console.log('未报名学生数量:', unregisteredStudents.length)
|
||||
|
||||
return unregisteredStudents
|
||||
}
|
||||
return studentList.value
|
||||
})
|
||||
|
||||
// 年级班级选择变化 - 自动触发查询
|
||||
const onClassChange = async (nj: any, bj: any) => {
|
||||
console.log('年级班级选择变化:', nj, bj)
|
||||
classInfo.value = {
|
||||
njId: nj.key,
|
||||
bjId: bj.key,
|
||||
nj: nj,
|
||||
bj: bj
|
||||
}
|
||||
|
||||
// 重置分页
|
||||
currentPage.value = 1
|
||||
hasMore.value = true
|
||||
courseList.value = []
|
||||
studentList.value = []
|
||||
|
||||
// 自动触发查询
|
||||
await Promise.all([searchCourses(), searchStudents()])
|
||||
}
|
||||
|
||||
// 查询选课数据
|
||||
const searchCourses = async () => {
|
||||
if (!classInfo.value || !classInfo.value.njId || !classInfo.value.bjId) {
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
hasSearched.value = true
|
||||
|
||||
try {
|
||||
// 调用后端接口查询选课数据
|
||||
const response = await getXkCourseListApi(classInfo.value!.njId, classInfo.value!.bjId, xkId.value)
|
||||
|
||||
console.log('API返回结果:', response)
|
||||
|
||||
if (response && response.resultCode === 1) {
|
||||
courseList.value = response.result || []
|
||||
} else {
|
||||
throw new Error(response?.message || '查询失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('查询选课数据失败:', error)
|
||||
uni.showToast({
|
||||
title: '查询失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
courseList.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 查询学生数据
|
||||
const searchStudents = async () => {
|
||||
if (!classInfo.value || !classInfo.value.njId || !classInfo.value.bjId) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 调用后端接口查询学生档案
|
||||
const response = await findStudentInfoByNjAndBjSimpleApi(classInfo.value!.njId, classInfo.value!.bjId)
|
||||
|
||||
console.log('学生API返回结果:', response)
|
||||
|
||||
if (response && response.resultCode === 1) {
|
||||
studentList.value = response.result || []
|
||||
} else {
|
||||
throw new Error(response?.message || '查询学生失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('查询学生数据失败:', error)
|
||||
studentList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新课程列表
|
||||
const refreshCourseList = () => {
|
||||
currentPage.value = 1
|
||||
hasMore.value = true
|
||||
courseList.value = []
|
||||
studentList.value = []
|
||||
Promise.all([searchCourses(), searchStudents()])
|
||||
}
|
||||
|
||||
// 加载更多数据
|
||||
const loadMore = async () => {
|
||||
if (loading.value || !hasMore.value) {
|
||||
return
|
||||
}
|
||||
|
||||
currentPage.value++
|
||||
await Promise.all([searchCourses(), searchStudents()])
|
||||
}
|
||||
|
||||
// 滚动到底部触发加载更多
|
||||
const onReachBottom = () => {
|
||||
loadMore()
|
||||
}
|
||||
|
||||
// 点击统计项
|
||||
const onStatItemClick = (statType: string) => {
|
||||
selectedStatType.value = statType
|
||||
console.log('选中统计类型:', statType)
|
||||
}
|
||||
|
||||
// 获取列表标题
|
||||
const getListTitle = () => {
|
||||
switch (selectedStatType.value) {
|
||||
case 'registered':
|
||||
return '已报名课程'
|
||||
case 'unregistered':
|
||||
return '未报名学生'
|
||||
case 'all':
|
||||
return '总人数学生'
|
||||
default:
|
||||
return '已报名课程'
|
||||
}
|
||||
}
|
||||
|
||||
// 获取列表数量
|
||||
const getListCount = () => {
|
||||
switch (selectedStatType.value) {
|
||||
case 'registered':
|
||||
return `${filteredCourseList.value.length}门课程`
|
||||
case 'unregistered':
|
||||
return `${filteredStudentList.value.length}人`
|
||||
case 'all':
|
||||
return `${filteredStudentList.value.length}人`
|
||||
default:
|
||||
return `${filteredCourseList.value.length}门课程`
|
||||
}
|
||||
}
|
||||
|
||||
// 显示更多选项
|
||||
const showMoreOptions = (student: StudentInfo) => {
|
||||
console.log('显示更多选项:', student)
|
||||
|
||||
// 显示操作菜单
|
||||
uni.showActionSheet({
|
||||
itemList: ['查看详情', '编辑信息', '联系家长', '删除学生'],
|
||||
success: (res) => {
|
||||
const tapIndex = res.tapIndex
|
||||
switch (tapIndex) {
|
||||
case 0:
|
||||
// 查看详情
|
||||
viewStudentDetail(student)
|
||||
break
|
||||
case 1:
|
||||
// 编辑信息
|
||||
uni.showToast({
|
||||
title: '编辑功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
break
|
||||
case 2:
|
||||
// 联系家长
|
||||
if (student.jzxm) {
|
||||
uni.showToast({
|
||||
title: '联系家长功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '该学生暂无家长信息',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
break
|
||||
case 3:
|
||||
// 删除学生
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: `确定要删除学生 ${student.xsxm} 吗?`,
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
uni.showToast({
|
||||
title: '删除功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取报名状态
|
||||
const getRegistrationStatus = (student: StudentInfo) => {
|
||||
// 检查学生是否已报名任何课程
|
||||
const registeredStudentNames = new Set()
|
||||
courseList.value.forEach(course => {
|
||||
if (course.studentNames) {
|
||||
const names = course.studentNames.split(',').map((name: string) => name.trim()).filter((name: string) => name)
|
||||
names.forEach((name: string) => registeredStudentNames.add(name))
|
||||
}
|
||||
})
|
||||
|
||||
return registeredStudentNames.has(student.xsxm) ? '已报名' : '未报名'
|
||||
}
|
||||
|
||||
// 获取报名状态样式类
|
||||
const getRegistrationStatusClass = (student: StudentInfo) => {
|
||||
const registeredStudentNames = new Set()
|
||||
courseList.value.forEach(course => {
|
||||
if (course.studentNames) {
|
||||
const names = course.studentNames.split(',').map((name: string) => name.trim()).filter((name: string) => name)
|
||||
names.forEach((name: string) => registeredStudentNames.add(name))
|
||||
}
|
||||
})
|
||||
|
||||
return registeredStudentNames.has(student.xsxm) ? 'status-registered' : 'status-unregistered'
|
||||
}
|
||||
|
||||
// 查看学生详情
|
||||
const viewStudentDetail = (student: StudentInfo) => {
|
||||
console.log('查看学生详情:', student)
|
||||
|
||||
// 将学生信息存储到store中
|
||||
setXs({
|
||||
xsId: student.xsId,
|
||||
id: student.xsId, // 兼容字段
|
||||
xsxm: student.xsxm,
|
||||
xm: student.xsxm, // 兼容字段
|
||||
xstx: student.xstx,
|
||||
avatar: student.xstx, // 兼容字段
|
||||
njId: student.njId,
|
||||
njmc: student.njmc,
|
||||
njmcName: student.njmc, // 兼容字段
|
||||
bjId: student.bjId,
|
||||
bjmc: student.bjmc,
|
||||
jzIds: student.jzIds,
|
||||
jzxm: student.jzxm
|
||||
})
|
||||
|
||||
// 跳转到学生详情页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/view/homeSchool/parentAddressBook/detail'
|
||||
})
|
||||
}
|
||||
|
||||
// 页面加载
|
||||
onLoad((options?: any) => {
|
||||
console.log('兴趣课选课页面加载', options)
|
||||
|
||||
// 获取传入的参数
|
||||
if (options?.xkId) {
|
||||
xkId.value = options.xkId
|
||||
}
|
||||
if (options?.xkmc) {
|
||||
xkmc.value = decodeURIComponent(options.xkmc)
|
||||
}
|
||||
|
||||
// 如果有选课ID,更新页面标题
|
||||
if (xkmc.value) {
|
||||
uni.setNavigationBarTitle({
|
||||
title: xkmc.value
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.xk-course-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.section {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
font-size: 24rpx;
|
||||
color: #007aff;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 30rpx;
|
||||
padding: 20rpx;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 12rpx;
|
||||
flex-wrap: wrap;
|
||||
gap: 60rpx;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 120rpx;
|
||||
padding: 20rpx 10rpx;
|
||||
border-radius: 12rpx;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
background-color: #e6f7ff;
|
||||
border: 2rpx solid #007aff;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f0f9ff;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
|
||||
&.registered {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
&.unregistered {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.course-list {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.course-item {
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2rpx);
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.course-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
padding-bottom: 15rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.course-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.course-student-count {
|
||||
font-size: 24rpx;
|
||||
color: #007aff;
|
||||
background-color: #e6f7ff;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.course-info {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
margin-bottom: 12rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #666;
|
||||
margin-right: 10rpx;
|
||||
min-width: 140rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.student-list {
|
||||
margin-top: 20rpx;
|
||||
padding-top: 20rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.student-list-title {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.student-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.student-tag {
|
||||
background-color: #f0f9ff;
|
||||
color: #007aff;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
border: 1rpx solid #e6f7ff;
|
||||
}
|
||||
|
||||
.class-tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 20rpx;
|
||||
padding: 15rpx 20rpx;
|
||||
background-color: #fffbe6;
|
||||
border: 1rpx solid #ffe58f;
|
||||
border-radius: 12rpx;
|
||||
color: #faad14;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
|
||||
.tip-icon {
|
||||
margin-right: 10rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 40rpx;
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 100rpx;
|
||||
|
||||
.loading-text {
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.r-md {
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.p-12 {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
/* 学生列表样式 */
|
||||
.student-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.student-item {
|
||||
position: relative;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s;
|
||||
min-height: 120rpx;
|
||||
}
|
||||
|
||||
.student-item:hover {
|
||||
transform: translateY(-2rpx);
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
padding: 4rpx;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.05);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.student-avatar {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
font-size: 20rpx;
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.status-registered {
|
||||
background-color: #e6f7ff;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.status-unregistered {
|
||||
background-color: #fff2f0;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mr-8 {
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.mb-8 {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.font-14 {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.cor-333 {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.more-icon-container {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 8rpx;
|
||||
border-radius: 50%;
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #e0e0e0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.more-icon {
|
||||
width: 35rpx;
|
||||
height: 35rpx;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.more-icon-container:hover .more-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 滚动加载样式 */
|
||||
.load-more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
|
||||
.load-more-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.no-more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
|
||||
.no-more-text {
|
||||
font-size: 28rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
436
src/pages/view/analysis/xk/xkList.vue
Normal file
436
src/pages/view/analysis/xk/xkList.vue
Normal file
@ -0,0 +1,436 @@
|
||||
<template>
|
||||
<view class="xk-list-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<text class="page-title">选课列表</text>
|
||||
</view>
|
||||
|
||||
<!-- 选课列表 -->
|
||||
<view class="section" v-if="xkList.length > 0">
|
||||
<view class="section-title">
|
||||
选课信息 ({{ xkList.length }}个选课)
|
||||
</view>
|
||||
|
||||
<!-- 选课列表 -->
|
||||
<view class="xk-list">
|
||||
<view
|
||||
v-for="xk in xkList"
|
||||
:key="xk.id"
|
||||
class="xk-item bg-white r-md p-12"
|
||||
@click="goToXkCourse(xk)"
|
||||
>
|
||||
<view class="xk-header">
|
||||
<view class="xk-title">{{ xk.xkmc }}</view>
|
||||
<view class="xk-actions">
|
||||
<view v-if="xk.xkStatus !== '已结束'" class="xk-status" :class="getStatusClass(xk.xkStatus)">
|
||||
{{ xk.xkStatus }}
|
||||
</view>
|
||||
<view v-else class="more-btn" @click.stop="showMoreOptions(xk)">
|
||||
<image src="/static/base/view/more.png" class="more-icon" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="xk-info">
|
||||
<view class="info-row">
|
||||
<text class="info-label">选课类型:</text>
|
||||
<text class="info-value">{{ xk.xklxName || '未知' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">学期名称:</text>
|
||||
<text class="info-value">{{ xk.xqmc || '未知' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">年级范围:</text>
|
||||
<text class="info-value">{{ xk.njmc || '全部' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="!loading && xkList.length === 0 && hasSearched">
|
||||
<view class="empty-icon">📚</view>
|
||||
<view class="empty-text">暂无选课数据</view>
|
||||
<view class="empty-tip">请联系管理员添加选课信息</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-state" v-if="loading">
|
||||
<view class="loading-text">正在加载...</view>
|
||||
</view>
|
||||
|
||||
<!-- 滚动加载更多 -->
|
||||
<view class="load-more" v-if="hasMore && !loading">
|
||||
<view class="load-more-text">上拉加载更多</view>
|
||||
</view>
|
||||
|
||||
<!-- 没有更多数据 -->
|
||||
<view class="no-more" v-if="!hasMore && hasSearched && xkList.length > 0">
|
||||
<view class="no-more-text">没有更多数据了</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getXkListApi } from '@/api/analysis/xk'
|
||||
|
||||
// 定义类型接口
|
||||
interface XkInfo {
|
||||
id: string
|
||||
xkmc: string
|
||||
xklxId: string
|
||||
xklxName: string
|
||||
njId: string
|
||||
njmc: string
|
||||
xqId: string
|
||||
xqmc: string
|
||||
xkkstime: string
|
||||
xkjstime: string
|
||||
xkzt: number
|
||||
xkStatus: string
|
||||
allXkNum: number
|
||||
yfXkNum: number
|
||||
wfXkNum: number
|
||||
status: string
|
||||
createdTime: string
|
||||
}
|
||||
|
||||
// 响应式数据
|
||||
const xkList = ref<XkInfo[]>([])
|
||||
const loading = ref(false)
|
||||
const hasSearched = ref(false)
|
||||
const hasMore = ref(true)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
|
||||
// 页面加载
|
||||
onLoad(() => {
|
||||
console.log('选课列表页面加载')
|
||||
loadXkList()
|
||||
})
|
||||
|
||||
// 加载选课列表
|
||||
const loadXkList = async (isLoadMore = false) => {
|
||||
if (loading.value) return
|
||||
|
||||
loading.value = true
|
||||
hasSearched.value = true
|
||||
|
||||
try {
|
||||
// 调用后端接口查询选课列表
|
||||
const response = await getXkListApi()
|
||||
|
||||
console.log('API返回结果:', response)
|
||||
|
||||
if (response && response.rows) {
|
||||
const newData = response.rows || []
|
||||
|
||||
if (isLoadMore) {
|
||||
// 加载更多时追加数据
|
||||
xkList.value = [...xkList.value, ...newData]
|
||||
} else {
|
||||
// 首次加载或刷新时替换数据
|
||||
xkList.value = newData
|
||||
}
|
||||
|
||||
// 判断是否还有更多数据
|
||||
hasMore.value = newData.length >= pageSize.value
|
||||
} else {
|
||||
throw new Error('查询失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('查询选课列表失败:', error)
|
||||
uni.showToast({
|
||||
title: '查询失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
if (!isLoadMore) {
|
||||
xkList.value = []
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载更多数据
|
||||
const loadMore = () => {
|
||||
if (loading.value || !hasMore.value) {
|
||||
return
|
||||
}
|
||||
|
||||
currentPage.value++
|
||||
loadXkList(true)
|
||||
}
|
||||
|
||||
// 滚动到底部触发加载更多
|
||||
const onReachBottom = () => {
|
||||
loadMore()
|
||||
}
|
||||
|
||||
// 跳转到选课详情页面
|
||||
const goToXkCourse = (xk: XkInfo) => {
|
||||
console.log('跳转到选课详情:', xk)
|
||||
uni.navigateTo({
|
||||
url: `/pages/view/analysis/xk/xkCourse?xkId=${xk.id}&xkmc=${encodeURIComponent(xk.xkmc)}`
|
||||
})
|
||||
}
|
||||
|
||||
// 显示更多选项 - 直接跳转到课程详情页面
|
||||
const showMoreOptions = (xk: XkInfo) => {
|
||||
console.log('点击更多按钮,直接跳转到课程详情:', xk)
|
||||
goToXkCourse(xk)
|
||||
}
|
||||
|
||||
// 获取状态样式类
|
||||
const getStatusClass = (status: string) => {
|
||||
switch (status) {
|
||||
case '未开始':
|
||||
return 'status-pending'
|
||||
case '进行中':
|
||||
return 'status-active'
|
||||
case '已结束':
|
||||
return 'status-ended'
|
||||
default:
|
||||
return 'status-unknown'
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.xk-list-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.section {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.xk-list {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.xk-item {
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2rpx);
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.xk-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
padding-bottom: 15rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.xk-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.xk-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.xk-status {
|
||||
font-size: 24rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-weight: bold;
|
||||
|
||||
&.status-pending {
|
||||
background-color: #fff7e6;
|
||||
color: #fa8c16;
|
||||
border: 1rpx solid #ffd591;
|
||||
}
|
||||
|
||||
&.status-active {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
border: 1rpx solid #b7eb8f;
|
||||
}
|
||||
|
||||
&.status-ended {
|
||||
background-color: #fff2f0;
|
||||
color: #ff4d4f;
|
||||
border: 1rpx solid #ffccc7;
|
||||
}
|
||||
|
||||
&.status-unknown {
|
||||
background-color: #f5f5f5;
|
||||
color: #999;
|
||||
border: 1rpx solid #d9d9d9;
|
||||
}
|
||||
}
|
||||
|
||||
.more-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #f5f5f5;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #e6f7ff;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #d9d9d9;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.more-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
|
||||
.xk-info {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
margin-bottom: 12rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #666;
|
||||
margin-right: 10rpx;
|
||||
min-width: 140rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 40rpx;
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 100rpx;
|
||||
|
||||
.loading-text {
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.r-md {
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.p-12 {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
/* 滚动加载样式 */
|
||||
.load-more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
|
||||
.load-more-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.no-more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
|
||||
.no-more-text {
|
||||
font-size: 28rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
630
src/pages/view/analysis/xs/studentArchive.vue
Normal file
630
src/pages/view/analysis/xs/studentArchive.vue
Normal file
@ -0,0 +1,630 @@
|
||||
<template>
|
||||
<view class="student-archive-container">
|
||||
<!-- 班级选择器 -->
|
||||
<view class="section">
|
||||
<view class="section-title">选择班级</view>
|
||||
<BasicNjBjPicker
|
||||
v-model="classInfo"
|
||||
placeholder="请选择年级班级"
|
||||
@change="onClassChange"
|
||||
icon-arrow="right"
|
||||
:customStyle="{ backgroundColor: '#fff', borderRadius: '0', padding: '12px 15px' }"
|
||||
/>
|
||||
|
||||
<!-- 班级选择提示 -->
|
||||
<view v-if="!classInfo" class="class-tip">
|
||||
<text class="tip-icon">ℹ️</text>
|
||||
<text class="tip-text">请先选择班级</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学生档案统计 -->
|
||||
<view class="section" v-if="classInfo">
|
||||
<view class="section-title">
|
||||
学生档案统计
|
||||
</view>
|
||||
|
||||
<!-- 统计信息 -->
|
||||
<view class="stats-container">
|
||||
<view
|
||||
class="stat-item"
|
||||
:class="{ active: selectedStatType === 'all' }"
|
||||
@click="onStatItemClick('all')"
|
||||
>
|
||||
<text class="stat-number">{{ studentList.length }}</text>
|
||||
<text class="stat-label">总人数</text>
|
||||
</view>
|
||||
<view
|
||||
class="stat-item"
|
||||
:class="{ active: selectedStatType === 'followed' }"
|
||||
@click="onStatItemClick('followed')"
|
||||
>
|
||||
<text class="stat-number followed">{{ followedCount }}</text>
|
||||
<text class="stat-label">已关注</text>
|
||||
</view>
|
||||
<view
|
||||
class="stat-item"
|
||||
:class="{ active: selectedStatType === 'unfollowed' }"
|
||||
@click="onStatItemClick('unfollowed')"
|
||||
>
|
||||
<text class="stat-number unfollowed">{{ unfollowedCount }}</text>
|
||||
<text class="stat-label">未关注</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学生档案列表 -->
|
||||
<view class="section" v-if="classInfo && studentList.length > 0">
|
||||
<view class="section-title">
|
||||
{{ getListTitle() }} ({{ filteredStudentList.length }}人)
|
||||
</view>
|
||||
|
||||
<!-- 学生列表 - 改为card形式 -->
|
||||
<view class="student-list">
|
||||
<view class="student-grid">
|
||||
<view
|
||||
v-for="student in filteredStudentList"
|
||||
:key="student.xsId"
|
||||
class="student-item bg-white r-md p-12"
|
||||
@click="viewStudentDetail(student)"
|
||||
>
|
||||
<view class="flex-row items-center">
|
||||
<view class="avatar-container mr-8">
|
||||
<image
|
||||
class="student-avatar"
|
||||
:src="imagUrl(student.xstx || '') || '/static/images/default-avatar.png'"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
</view>
|
||||
<view class="flex-1 overflow-hidden">
|
||||
<view class="student-name mb-8">
|
||||
<text class="font-14 cor-333">{{ student.xsxm }}</text>
|
||||
</view>
|
||||
<view class="flex-row">
|
||||
<!-- 关注状态标签 -->
|
||||
<view
|
||||
class="status-tag"
|
||||
:class="getFollowStatusClass(student.jzxm)"
|
||||
>
|
||||
{{ student.jzxm ? '已关注' : '未关注' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 更多图标 -->
|
||||
<view class="more-icon-container" @click.stop="showMoreOptions(student)">
|
||||
<image
|
||||
class="more-icon"
|
||||
src="/static/base/view/more.png"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="!loading && studentList.length === 0 && hasSearched">
|
||||
<view class="empty-icon">📚</view>
|
||||
<view class="empty-text">暂无学生档案数据</view>
|
||||
<view class="empty-tip">请选择年级班级后查询</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-state" v-if="loading">
|
||||
<view class="loading-text">正在加载...</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { BasicNjBjPicker } from '@/components/BasicNjBjPicker'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { findStudentInfoByNjAndBjSimpleApi } from '@/api/analysis/xs'
|
||||
import { imagUrl } from "@/utils"
|
||||
import { useDataStore } from '@/store/modules/data'
|
||||
|
||||
// 定义类型接口
|
||||
interface ClassInfo {
|
||||
njId: string
|
||||
bjId: string
|
||||
nj: any
|
||||
bj: any
|
||||
}
|
||||
|
||||
interface StudentInfo {
|
||||
xsId: string
|
||||
xsxm: string
|
||||
xstx?: string
|
||||
njId: string
|
||||
njmcId: string
|
||||
njmc: string
|
||||
bc: string
|
||||
bjId: string
|
||||
bjmc: string
|
||||
jzIds?: string
|
||||
jzxm?: string
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
code: number
|
||||
data: StudentInfo[]
|
||||
message?: string
|
||||
}
|
||||
|
||||
// 响应式数据
|
||||
const classInfo = ref<ClassInfo | null>(null)
|
||||
const studentList = ref<StudentInfo[]>([])
|
||||
const loading = ref(false)
|
||||
const hasSearched = ref(false)
|
||||
const selectedStatType = ref<string>('all') // 当前选中的统计类型
|
||||
|
||||
// 使用store
|
||||
const { setXs } = useDataStore()
|
||||
|
||||
// 计算属性
|
||||
const followedCount = computed(() => {
|
||||
return studentList.value.filter(student => student.jzxm).length
|
||||
})
|
||||
|
||||
const unfollowedCount = computed(() => {
|
||||
return studentList.value.filter(student => !student.jzxm).length
|
||||
})
|
||||
|
||||
// 根据选中的统计类型过滤学生列表
|
||||
const filteredStudentList = computed(() => {
|
||||
if (selectedStatType.value === 'all') {
|
||||
return studentList.value
|
||||
} else if (selectedStatType.value === 'followed') {
|
||||
return studentList.value.filter(student => student.jzxm)
|
||||
} else if (selectedStatType.value === 'unfollowed') {
|
||||
return studentList.value.filter(student => !student.jzxm)
|
||||
}
|
||||
return studentList.value
|
||||
})
|
||||
|
||||
// 年级班级选择变化 - 自动触发查询
|
||||
const onClassChange = async (nj: any, bj: any) => {
|
||||
console.log('年级班级选择变化:', nj, bj)
|
||||
classInfo.value = {
|
||||
njId: nj.key,
|
||||
bjId: bj.key,
|
||||
nj: nj,
|
||||
bj: bj
|
||||
}
|
||||
|
||||
// 自动触发查询
|
||||
await searchStudents()
|
||||
}
|
||||
|
||||
// 查询学生档案
|
||||
const searchStudents = async () => {
|
||||
if (!classInfo.value || !classInfo.value.njId || !classInfo.value.bjId) {
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
hasSearched.value = true
|
||||
|
||||
try {
|
||||
// 调用后端接口查询学生档案
|
||||
const response = await findStudentInfoByNjAndBjSimpleApi(classInfo.value!.njId, classInfo.value!.bjId)
|
||||
|
||||
console.log('API返回结果:', response)
|
||||
|
||||
if (response && response.resultCode === 1) {
|
||||
studentList.value = response.result || []
|
||||
uni.showToast({
|
||||
title: `查询到${studentList.value.length}名学生`,
|
||||
icon: 'success'
|
||||
})
|
||||
} else {
|
||||
throw new Error(response?.message || '查询失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('查询学生档案失败:', error)
|
||||
uni.showToast({
|
||||
title: '查询失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
studentList.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新学生列表
|
||||
const refreshStudentList = () => {
|
||||
searchStudents()
|
||||
}
|
||||
|
||||
// 点击统计项
|
||||
const onStatItemClick = (statType: string) => {
|
||||
selectedStatType.value = statType
|
||||
console.log('选中统计类型:', statType)
|
||||
}
|
||||
|
||||
// 获取列表标题
|
||||
const getListTitle = () => {
|
||||
switch (selectedStatType.value) {
|
||||
case 'followed':
|
||||
return '已关注学生'
|
||||
case 'unfollowed':
|
||||
return '未关注学生'
|
||||
default:
|
||||
return '学生档案列表'
|
||||
}
|
||||
}
|
||||
|
||||
// 显示更多选项
|
||||
const showMoreOptions = (student: StudentInfo) => {
|
||||
console.log('显示更多选项:', student)
|
||||
|
||||
// 显示操作菜单
|
||||
uni.showActionSheet({
|
||||
itemList: ['查看详情', '编辑信息', '联系家长', '删除学生'],
|
||||
success: (res) => {
|
||||
const tapIndex = res.tapIndex
|
||||
switch (tapIndex) {
|
||||
case 0:
|
||||
// 查看详情
|
||||
viewStudentDetail(student)
|
||||
break
|
||||
case 1:
|
||||
// 编辑信息
|
||||
uni.showToast({
|
||||
title: '编辑功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
break
|
||||
case 2:
|
||||
// 联系家长
|
||||
if (student.jzxm) {
|
||||
uni.showToast({
|
||||
title: '联系家长功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '该学生暂无家长信息',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
break
|
||||
case 3:
|
||||
// 删除学生
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: `确定要删除学生 ${student.xsxm} 吗?`,
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
uni.showToast({
|
||||
title: '删除功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取关注状态样式类
|
||||
const getFollowStatusClass = (jzxm?: string) => {
|
||||
return jzxm ? 'status-followed' : 'status-unfollowed'
|
||||
}
|
||||
|
||||
// 查看学生详情
|
||||
const viewStudentDetail = (student: StudentInfo) => {
|
||||
console.log('查看学生详情:', student)
|
||||
|
||||
// 将学生信息存储到store中
|
||||
setXs({
|
||||
xsId: student.xsId,
|
||||
id: student.xsId, // 兼容字段
|
||||
xsxm: student.xsxm,
|
||||
xm: student.xsxm, // 兼容字段
|
||||
xstx: student.xstx,
|
||||
avatar: student.xstx, // 兼容字段
|
||||
njId: student.njId,
|
||||
njmc: student.njmc,
|
||||
njmcName: student.njmc, // 兼容字段
|
||||
bjId: student.bjId,
|
||||
bjmc: student.bjmc,
|
||||
jzIds: student.jzIds,
|
||||
jzxm: student.jzxm
|
||||
})
|
||||
|
||||
// 跳转到学生详情页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/view/homeSchool/parentAddressBook/detail'
|
||||
})
|
||||
}
|
||||
|
||||
// 页面加载
|
||||
onLoad(() => {
|
||||
console.log('学生档案页面加载')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.student-archive-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.section {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
font-size: 24rpx;
|
||||
color: #007aff;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 30rpx;
|
||||
padding: 20rpx;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 12rpx;
|
||||
flex-wrap: wrap;
|
||||
gap: 60rpx;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 120rpx;
|
||||
padding: 20rpx 10rpx;
|
||||
border-radius: 12rpx;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
background-color: #e6f7ff;
|
||||
border: 2rpx solid #007aff;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f0f9ff;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
|
||||
&.followed {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
&.unfollowed {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.student-list {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.student-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.student-item {
|
||||
position: relative;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s;
|
||||
min-height: 120rpx; // 确保最小高度
|
||||
}
|
||||
|
||||
.student-item:hover {
|
||||
transform: translateY(-2rpx);
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
padding: 4rpx;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.05);
|
||||
flex-shrink: 0; // 防止被压缩
|
||||
}
|
||||
|
||||
.student-avatar {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
font-size: 20rpx;
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.status-followed {
|
||||
background-color: #e6f7ff;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.status-unfollowed {
|
||||
background-color: #fff2f0;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mr-8 {
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.mb-8 {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.font-14 {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.cor-333 {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.r-md {
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.p-12 {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.more-icon-container {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 8rpx;
|
||||
border-radius: 50%;
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0; // 防止被压缩
|
||||
|
||||
&:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #e0e0e0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.more-icon {
|
||||
width: 35rpx;
|
||||
height: 35rpx;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.more-icon-container:hover .more-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.class-tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 20rpx;
|
||||
padding: 15rpx 20rpx;
|
||||
background-color: #fffbe6;
|
||||
border: 1rpx solid #ffe58f;
|
||||
border-radius: 12rpx;
|
||||
color: #faad14;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
|
||||
.tip-icon {
|
||||
margin-right: 10rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 40rpx;
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 100rpx;
|
||||
|
||||
.loading-text {
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -56,6 +56,7 @@
|
||||
v-for="jz in jzList"
|
||||
:key="jz.id"
|
||||
class="parent-card-item"
|
||||
@click="callParent(jz.jzsj)"
|
||||
>
|
||||
<view class="parent-avatar-container">
|
||||
<!-- 如果有头像则显示图片,否则显示文字头像 -->
|
||||
@ -68,7 +69,7 @@
|
||||
<view v-else class="parent-avatar-text">
|
||||
{{ jz.jzxm }}
|
||||
</view>
|
||||
<view class="call-icon-overlay" @click="callParent(jz.jzsj)">
|
||||
<view class="call-icon-overlay">
|
||||
<uni-icons
|
||||
type="phone-filled"
|
||||
size="16"
|
||||
@ -352,11 +353,18 @@ const getParentAvatar = (avatarUrl: string) => {
|
||||
padding: 8px; // 添加内边距
|
||||
border-radius: 8px; // 添加圆角
|
||||
transition: all 0.2s ease; // 添加过渡效果
|
||||
cursor: pointer; // 添加手型光标
|
||||
position: relative; // 为点击效果做准备
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(102, 126, 234, 0.05); // 悬停效果
|
||||
transform: translateY(-2px); // 轻微上移
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: rgba(102, 126, 234, 0.1); // 点击时的背景色
|
||||
transform: translateY(0); // 点击时回到原位置
|
||||
}
|
||||
}
|
||||
|
||||
.parent-avatar-container {
|
||||
@ -404,13 +412,10 @@ const getParentAvatar = (avatarUrl: string) => {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #fff; // 白色描边
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); // 添加阴影效果
|
||||
pointer-events: none; // 禁用点击事件,让父元素处理
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
// 移除 &:active 样式,因为现在由父元素处理点击
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,228 +0,0 @@
|
||||
<template>
|
||||
<BasicLayout :show-nav-bar="true" :nav-bar-props="{ title: 'API测试' }">
|
||||
<view class="api-test-page">
|
||||
<!-- 测试教师授课班级接口 -->
|
||||
<view class="test-section">
|
||||
<view class="section-title">测试教师授课班级接口</view>
|
||||
<button @click="testJsdkb" class="test-btn">
|
||||
测试获取教师授课班级
|
||||
</button>
|
||||
<view v-if="jsdkbResult" class="result-section">
|
||||
<text class="result-title">结果:</text>
|
||||
<text class="result-text">{{
|
||||
JSON.stringify(jsdkbResult, null, 2)
|
||||
}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 测试考试场次接口 -->
|
||||
<view class="test-section">
|
||||
<view class="section-title">测试考试场次接口</view>
|
||||
<input
|
||||
v-model="testBjId"
|
||||
placeholder="输入班级ID"
|
||||
class="input-field"
|
||||
/>
|
||||
<button @click="testKscc" class="test-btn">测试获取考试场次</button>
|
||||
<view v-if="ksccResult" class="result-section">
|
||||
<text class="result-title">结果:</text>
|
||||
<text class="result-text">{{
|
||||
JSON.stringify(ksccResult, null, 2)
|
||||
}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 测试成绩接口 -->
|
||||
<view class="test-section">
|
||||
<view class="section-title">测试成绩接口</view>
|
||||
<input
|
||||
v-model="testBjId2"
|
||||
placeholder="输入班级ID"
|
||||
class="input-field"
|
||||
/>
|
||||
<input
|
||||
v-model="testKsccId"
|
||||
placeholder="输入考试场次ID"
|
||||
class="input-field"
|
||||
/>
|
||||
<button @click="testCjData" class="test-btn">测试获取成绩数据</button>
|
||||
<view v-if="cjDataResult" class="result-section">
|
||||
<text class="result-title">结果:</text>
|
||||
<text class="result-text">{{
|
||||
JSON.stringify(cjDataResult, null, 2)
|
||||
}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 测试缓存机制 -->
|
||||
<view class="test-section">
|
||||
<view class="section-title">测试缓存机制</view>
|
||||
<button @click="testCache" class="test-btn">测试缓存机制</button>
|
||||
<view v-if="cacheResult" class="result-section">
|
||||
<text class="result-title">缓存测试结果:</text>
|
||||
<text class="result-text">{{ cacheResult }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</BasicLayout>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { jsdBjKscjApi, jsdJsdkbApi, jsdKsccApi } from "@/api/base/server";
|
||||
import { useCommonStore } from "@/store/modules/common";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { ref } from "vue";
|
||||
|
||||
const commonStore = useCommonStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const jsdkbResult = ref<any>(null);
|
||||
const ksccResult = ref<any>(null);
|
||||
const cjDataResult = ref<any>(null);
|
||||
const cacheResult = ref<string>("");
|
||||
|
||||
const testBjId = ref<string>("");
|
||||
const testBjId2 = ref<string>("");
|
||||
const testKsccId = ref<string>("");
|
||||
|
||||
const testJsdkb = async () => {
|
||||
try {
|
||||
const jsData = userStore.getJs();
|
||||
if (!jsData || !jsData.id) {
|
||||
uni.showToast({ title: "教师信息不存在", icon: "none" });
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await jsdJsdkbApi({ jsId: jsData.id });
|
||||
jsdkbResult.value = response;
|
||||
console.log("教师授课班级测试结果:", response);
|
||||
} catch (error) {
|
||||
console.error("测试教师授课班级接口出错:", error);
|
||||
uni.showToast({ title: "测试失败", icon: "none" });
|
||||
}
|
||||
};
|
||||
|
||||
const testKscc = async () => {
|
||||
try {
|
||||
if (!testBjId.value) {
|
||||
uni.showToast({ title: "请输入班级ID", icon: "none" });
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await jsdKsccApi({ bjId: testBjId.value });
|
||||
ksccResult.value = response;
|
||||
console.log("考试场次测试结果:", response);
|
||||
} catch (error) {
|
||||
console.error("测试考试场次接口出错:", error);
|
||||
uni.showToast({ title: "测试失败", icon: "none" });
|
||||
}
|
||||
};
|
||||
|
||||
const testCjData = async () => {
|
||||
try {
|
||||
if (!testBjId2.value || !testKsccId.value) {
|
||||
uni.showToast({ title: "请输入班级ID和考试场次ID", icon: "none" });
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await jsdBjKscjApi({
|
||||
bjId: testBjId2.value,
|
||||
ksccId: testKsccId.value,
|
||||
});
|
||||
cjDataResult.value = response;
|
||||
console.log("成绩数据测试结果:", response);
|
||||
} catch (error) {
|
||||
console.error("测试成绩数据接口出错:", error);
|
||||
uni.showToast({ title: "测试失败", icon: "none" });
|
||||
}
|
||||
};
|
||||
|
||||
const testCache = async () => {
|
||||
try {
|
||||
const jsData = userStore.getJs();
|
||||
if (!jsData || !jsData.id) {
|
||||
uni.showToast({ title: "教师信息不存在", icon: "none" });
|
||||
return;
|
||||
}
|
||||
|
||||
// 第一次调用,应该从API获取数据
|
||||
const startTime1 = Date.now();
|
||||
const response1 = await commonStore.getJsdkb({ jsId: jsData.id });
|
||||
const time1 = Date.now() - startTime1;
|
||||
|
||||
// 第二次调用,应该从缓存获取数据
|
||||
const startTime2 = Date.now();
|
||||
const response2 = await commonStore.getJsdkb({ jsId: jsData.id });
|
||||
const time2 = Date.now() - startTime2;
|
||||
|
||||
cacheResult.value = `第一次调用耗时: ${time1}ms, 第二次调用耗时: ${time2}ms, 缓存是否生效: ${
|
||||
time2 < time1
|
||||
}`;
|
||||
console.log("缓存测试结果:", cacheResult.value);
|
||||
} catch (error) {
|
||||
console.error("测试缓存机制出错:", error);
|
||||
uni.showToast({ title: "缓存测试失败", icon: "none" });
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.api-test-page {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.test-section {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.test-btn {
|
||||
background: #1890ff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx 40rpx;
|
||||
font-size: 28rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.result-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
BIN
src/static/base/home/dmtj.png
Normal file
BIN
src/static/base/home/dmtj.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
src/static/base/home/xkqd.png
Normal file
BIN
src/static/base/home/xkqd.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
BIN
src/static/base/home/xsda.png
Normal file
BIN
src/static/base/home/xsda.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
BIN
src/static/base/view/more.png
Normal file
BIN
src/static/base/view/more.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Loading…
x
Reference in New Issue
Block a user