选课调整
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
|
"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",
|
"path": "pages/view/routine/qd/index",
|
||||||
"style": {
|
"style": {
|
||||||
|
|||||||
@ -320,6 +320,30 @@ const sections = reactive<Section[]>([
|
|||||||
permissionKey: "routine-bjjl", // 发布接龙权限编码
|
permissionKey: "routine-bjjl", // 发布接龙权限编码
|
||||||
path: "/pages/view/notice/index",
|
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"
|
v-for="jz in jzList"
|
||||||
:key="jz.id"
|
:key="jz.id"
|
||||||
class="parent-card-item"
|
class="parent-card-item"
|
||||||
|
@click="callParent(jz.jzsj)"
|
||||||
>
|
>
|
||||||
<view class="parent-avatar-container">
|
<view class="parent-avatar-container">
|
||||||
<!-- 如果有头像则显示图片,否则显示文字头像 -->
|
<!-- 如果有头像则显示图片,否则显示文字头像 -->
|
||||||
@ -68,7 +69,7 @@
|
|||||||
<view v-else class="parent-avatar-text">
|
<view v-else class="parent-avatar-text">
|
||||||
{{ jz.jzxm }}
|
{{ jz.jzxm }}
|
||||||
</view>
|
</view>
|
||||||
<view class="call-icon-overlay" @click="callParent(jz.jzsj)">
|
<view class="call-icon-overlay">
|
||||||
<uni-icons
|
<uni-icons
|
||||||
type="phone-filled"
|
type="phone-filled"
|
||||||
size="16"
|
size="16"
|
||||||
@ -352,11 +353,18 @@ const getParentAvatar = (avatarUrl: string) => {
|
|||||||
padding: 8px; // 添加内边距
|
padding: 8px; // 添加内边距
|
||||||
border-radius: 8px; // 添加圆角
|
border-radius: 8px; // 添加圆角
|
||||||
transition: all 0.2s ease; // 添加过渡效果
|
transition: all 0.2s ease; // 添加过渡效果
|
||||||
|
cursor: pointer; // 添加手型光标
|
||||||
|
position: relative; // 为点击效果做准备
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(102, 126, 234, 0.05); // 悬停效果
|
background-color: rgba(102, 126, 234, 0.05); // 悬停效果
|
||||||
transform: translateY(-2px); // 轻微上移
|
transform: translateY(-2px); // 轻微上移
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: rgba(102, 126, 234, 0.1); // 点击时的背景色
|
||||||
|
transform: translateY(0); // 点击时回到原位置
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.parent-avatar-container {
|
.parent-avatar-container {
|
||||||
@ -404,13 +412,10 @@ const getParentAvatar = (avatarUrl: string) => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: 1px solid #fff; // 白色描边
|
border: 1px solid #fff; // 白色描边
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); // 添加阴影效果
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); // 添加阴影效果
|
||||||
|
pointer-events: none; // 禁用点击事件,让父元素处理
|
||||||
|
|
||||||
&:active {
|
// 移除 &:active 样式,因为现在由父元素处理点击
|
||||||
opacity: 0.8;
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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