zhxy-jsd/src/pages/view/analysis/xs/studentArchive.vue

584 lines
12 KiB
Vue
Raw Normal View History

2025-09-09 21:55:09 +08:00
<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>
2025-09-12 11:20:21 +08:00
<!-- 箭头图标 -->
<view class="arrow-icon-container" @click.stop="viewStudentDetail(student)">
2025-09-09 21:55:09 +08:00
<image
2025-09-12 11:20:21 +08:00
class="arrow-icon"
2025-09-09 21:55:09 +08:00
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
2025-09-12 11:20:21 +08:00
xb?: string // 性别
sfzh?: string // 身份证号
cstime?: string // 出生日期
2025-09-09 21:55:09 +08:00
}
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 '学生档案列表'
}
}
2025-09-12 11:20:21 +08:00
2025-09-09 21:55:09 +08:00
// 获取关注状态样式类
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,
2025-09-12 11:20:21 +08:00
jzxm: student.jzxm,
xb: student.xb, // 性别
sfzh: student.sfzh, // 身份证号
cstime: student.cstime // 出生日期
2025-09-09 21:55:09 +08:00
})
// 跳转到学生详情页面
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;
}
2025-09-12 11:20:21 +08:00
.arrow-icon-container {
2025-09-09 21:55:09 +08:00
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);
}
}
2025-09-12 11:20:21 +08:00
.arrow-icon {
2025-09-09 21:55:09 +08:00
width: 35rpx;
height: 35rpx;
opacity: 0.6;
transition: opacity 0.2s ease;
}
2025-09-12 11:20:21 +08:00
.arrow-icon-container:hover .arrow-icon {
2025-09-09 21:55:09 +08:00
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>