zhxy-jsd/src/pages/view/analysis/xs/studentArchive.vue
2025-09-12 11:20:21 +08:00

584 lines
12 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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="arrow-icon-container" @click.stop="viewStudentDetail(student)">
<image
class="arrow-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
xb?: string // 性别
sfzh?: string // 身份证号
cstime?: 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 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,
xb: student.xb, // 性别
sfzh: student.sfzh, // 身份证号
cstime: student.cstime // 出生日期
})
// 跳转到学生详情页面
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;
}
.arrow-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);
}
}
.arrow-icon {
width: 35rpx;
height: 35rpx;
opacity: 0.6;
transition: opacity 0.2s ease;
}
.arrow-icon-container:hover .arrow-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>