调整完善教师端关于选课点名相关界面
This commit is contained in:
parent
44e789f595
commit
526cdc1075
@ -35,11 +35,16 @@ export const getXkDmXsPageApi = async (params: any) => {
|
||||
return await get('/api/xkDmXs/findPage', params)
|
||||
}
|
||||
|
||||
// 选课学生列表
|
||||
// 选课待点名学生列表
|
||||
export const getWaitDmXsListApi = async (params: any) => {
|
||||
return await get("/api/xkDmXs/getWaitDmXsList", params);
|
||||
};
|
||||
|
||||
// 选课点名学生列表
|
||||
export const getDmXsListApi = async (params: any) => {
|
||||
return await get("/api/xkDmXs/getDmXsList", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 提交选课点名
|
||||
*/
|
||||
|
||||
@ -527,16 +527,16 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/routine/xk/dmRecord",
|
||||
"path": "pages/view/routine/xk/dmList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "点名记录",
|
||||
"navigationBarTitleText": "点名列表",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/routine/xk/dmXsRecord",
|
||||
"path": "pages/view/routine/xk/dmXsList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "点名学生记录",
|
||||
"navigationBarTitleText": "点名学生列表",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
|
||||
@ -54,7 +54,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="video-upload" @click="recordVideo">
|
||||
<u-icon name="videocam" size="32" color="#999"></u-icon>
|
||||
<u-icon name="camera" size="32" color="#999"></u-icon>
|
||||
<text class="upload-text">{{ videoUploadText || '点击录制' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
281
src/pages/components/dmPs/preview.vue
Normal file
281
src/pages/components/dmPs/preview.vue
Normal file
@ -0,0 +1,281 @@
|
||||
<template>
|
||||
<view class="media-preview">
|
||||
<!-- 照片预览 -->
|
||||
<view v-if="photoList.length > 0" class="media-section">
|
||||
<view class="section-title">
|
||||
<u-icon name="photo" color="#4080ff" size="16"></u-icon>
|
||||
<text class="title-text">现场照片 ({{ photoList.length }})</text>
|
||||
</view>
|
||||
<view class="photo-grid">
|
||||
<view
|
||||
v-for="(photo, index) in photoList"
|
||||
:key="index"
|
||||
class="photo-item"
|
||||
@click="previewPhoto(index)"
|
||||
>
|
||||
<image
|
||||
:src="getImageUrl(photo)"
|
||||
mode="aspectFill"
|
||||
class="photo-img"
|
||||
/>
|
||||
<view class="photo-overlay">
|
||||
<u-icon name="eye" color="white" size="16"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 视频预览 -->
|
||||
<view v-if="videoList.length > 0" class="media-section">
|
||||
<view class="section-title">
|
||||
<u-icon name="camera" color="#4080ff" size="16"></u-icon>
|
||||
<text class="title-text">现场视频 ({{ videoList.length }})</text>
|
||||
</view>
|
||||
<view class="video-list">
|
||||
<view
|
||||
v-for="(video, index) in videoList"
|
||||
:key="index"
|
||||
class="video-item"
|
||||
@click="playVideo(video)"
|
||||
>
|
||||
<view class="video-thumbnail">
|
||||
<image
|
||||
:src="getVideoThumbnail(video)"
|
||||
mode="aspectFill"
|
||||
class="video-img"
|
||||
/>
|
||||
<view class="video-play-btn">
|
||||
<u-icon name="play-right" color="white" size="20"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="video-info">
|
||||
<text class="video-title">视频 {{ index + 1 }}</text>
|
||||
<text class="video-duration">{{ getVideoDuration(video) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 无媒体文件提示 -->
|
||||
<view v-if="photoList.length === 0 && videoList.length === 0" class="empty-tip">
|
||||
<u-icon name="photo" color="#ccc" size="32"></u-icon>
|
||||
<text class="empty-text">暂无媒体文件</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
// 如果没有BASE_IMAGE_URL配置,使用默认值
|
||||
const BASE_IMAGE_URL = import.meta.env.VITE_BASE_IMAGE_URL || '';
|
||||
|
||||
// 定义组件属性
|
||||
interface Props {
|
||||
zp?: string; // 照片路径,逗号分隔
|
||||
sp?: string; // 视频路径,逗号分隔
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
zp: '',
|
||||
sp: ''
|
||||
});
|
||||
|
||||
// 解析照片列表
|
||||
const photoList = computed(() => {
|
||||
if (!props.zp) return [];
|
||||
return props.zp.split(',').filter(path => path.trim());
|
||||
});
|
||||
|
||||
// 解析视频列表
|
||||
const videoList = computed(() => {
|
||||
if (!props.sp) return [];
|
||||
return props.sp.split(',').filter(path => path.trim());
|
||||
});
|
||||
|
||||
// 获取图片完整URL
|
||||
const getImageUrl = (path: string) => {
|
||||
if (!path) return '';
|
||||
if (path.startsWith('http')) return path;
|
||||
return BASE_IMAGE_URL + path;
|
||||
};
|
||||
|
||||
// 获取视频缩略图(这里可以根据实际需求调整)
|
||||
const getVideoThumbnail = (videoPath: string) => {
|
||||
// 如果有视频缩略图,返回缩略图路径
|
||||
// 否则返回默认视频图标
|
||||
return '/static/images/video-thumbnail.png';
|
||||
};
|
||||
|
||||
// 获取视频时长(这里可以根据实际需求调整)
|
||||
const getVideoDuration = (videoPath: string) => {
|
||||
// 这里可以根据实际需求获取视频时长
|
||||
return '00:00';
|
||||
};
|
||||
|
||||
// 预览照片
|
||||
const previewPhoto = (index: number) => {
|
||||
const urls = photoList.value.map(photo => getImageUrl(photo));
|
||||
// @ts-ignore
|
||||
uni.previewImage({
|
||||
current: index,
|
||||
urls: urls
|
||||
});
|
||||
};
|
||||
|
||||
// 播放视频
|
||||
const playVideo = (videoPath: string) => {
|
||||
const videoUrl = getImageUrl(videoPath);
|
||||
// 使用uni-app的视频播放器
|
||||
// @ts-ignore
|
||||
uni.navigateTo({
|
||||
url: `/pages/components/videoPlayer/index?url=${encodeURIComponent(videoUrl)}`
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.media-preview {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.media-section {
|
||||
margin-bottom: 20px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
padding: 8px 0;
|
||||
|
||||
.title-text {
|
||||
margin-left: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.photo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.photo-item {
|
||||
position: relative;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover .photo-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.photo-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.photo-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.video-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.video-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: #e9ecef;
|
||||
}
|
||||
}
|
||||
|
||||
.video-thumbnail {
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 60px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.video-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.video-play-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.video-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.video-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.video-duration {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 20px;
|
||||
color: #999;
|
||||
|
||||
.empty-text {
|
||||
margin-top: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -94,10 +94,10 @@ import { imagUrl } from "@/utils";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { xsJzListByXsIdApi } from "@/api/base/server";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
const { getData } = useDataStore();
|
||||
const { getXs } = useDataStore();
|
||||
|
||||
const xsInfo = computed(() => {
|
||||
const xs = getData || {};
|
||||
const xs = getXs || {};
|
||||
// 适配从点名页面传递过来的学生信息字段
|
||||
const studentInfo = {
|
||||
id: xs.xsId || xs.id, // 支持两种字段名
|
||||
@ -159,7 +159,7 @@ onLoad(async (options) => {
|
||||
if (xsInfo.value && xsInfo.value.id) {
|
||||
try {
|
||||
uni.showLoading({ title: "加载中..." });
|
||||
const res = await xsJzListByXsIdApi({ xsId: xsInfo.value.id });
|
||||
const res:any = await xsJzListByXsIdApi({ xsId: xsInfo.value.id });
|
||||
if (res && res.resultCode === 1) {
|
||||
jzList.value = res.result || [];
|
||||
} else {
|
||||
|
||||
@ -56,7 +56,7 @@ import NjBjPicker from "@/pages/components/NjBjPicker/index.vue";
|
||||
import { imagUrl } from "@/utils";
|
||||
import { xsFindList } from "@/api/base/server";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
const { setData } = useDataStore();
|
||||
const { setXs } = useDataStore();
|
||||
|
||||
const xsList = ref<any>([]);
|
||||
const xsTotal = ref(0);
|
||||
@ -114,7 +114,7 @@ const getXsList = async () => {
|
||||
|
||||
// 跳转到详情页
|
||||
const goToDetail = (xs: any) => {
|
||||
setData(xs);
|
||||
setXs(xs);
|
||||
uni.navigateTo({
|
||||
url: `/pages/view/homeSchool/parentAddressBook/detail`
|
||||
});
|
||||
|
||||
@ -32,35 +32,40 @@
|
||||
</view>
|
||||
|
||||
<!-- 学生列表 -->
|
||||
<view class="student-list mb-30 white-bg-color">
|
||||
<view class="student-list mb-15 white-bg-color">
|
||||
<view class="student-grid">
|
||||
<view
|
||||
v-for="(xs, index) in xsList"
|
||||
:key="index"
|
||||
class="student-item bg-white r-md p-12"
|
||||
>
|
||||
<view class="flex-row items-center">
|
||||
<view class="avatar-container mr-8">
|
||||
<view class="student-content">
|
||||
<!-- 第一行:头像和学生信息 -->
|
||||
<view class="top-row">
|
||||
<view class="avatar-container">
|
||||
<image
|
||||
class="student-avatar"
|
||||
:src="getImageUrl(xs.tx || xs.xstx)"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
</view>
|
||||
<view class="flex-1 overflow-hidden">
|
||||
<view class="flex-row items-center mb-3">
|
||||
<text class="font-14 font-bold mr-5 text-ellipsis">{{ xs.xsXm || xs.xsxm }}</text>
|
||||
<view class="student-info">
|
||||
<text class="student-name">{{ xs.xsXm || xs.xsxm }}</text>
|
||||
<text class="student-class">{{ xs.bjmc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 第二行:状态和联系家长 -->
|
||||
<view class="bottom-row">
|
||||
<view
|
||||
class="status-tag"
|
||||
:class="getStatusClass(xs.xsZt || xs.xszt)"
|
||||
@click="openStatusPicker(xs)"
|
||||
>
|
||||
{{ getStatusText(xs.xsZt || xs.xszt) }}
|
||||
<u-icon name="arrow-down" size="10"></u-icon>
|
||||
<u-icon name="arrow-down" size="12"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<text class="font-12 cor-666">{{ xs.bjmc }}</text>
|
||||
<view class="contact-parent mt-8 flex-center" @click="contactParent(xs)">
|
||||
<view class="contact-parent" @click="contactParent(xs)">
|
||||
<text class="font-12 cor-primary">联系家长</text>
|
||||
<u-icon
|
||||
name="phone"
|
||||
@ -75,6 +80,7 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="mx-15">
|
||||
<DmPsComponent
|
||||
v-model="mediaData"
|
||||
:photo-title="'现场拍照'"
|
||||
@ -84,6 +90,7 @@
|
||||
:max-video-duration="60"
|
||||
ref="dmPsRef"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 状态选择弹窗 -->
|
||||
<u-picker
|
||||
@ -131,7 +138,7 @@ const { findByPid } = useDicStore();
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const { getJs } = useUserStore();
|
||||
const { getData, setData } = useDataStore();
|
||||
const { getData, setData, setXs } = useDataStore();
|
||||
|
||||
const js = computed(() => getJs)
|
||||
const xkkc = computed(() => getData)
|
||||
@ -355,7 +362,7 @@ const contactParent = (dmXs: any) => {
|
||||
};
|
||||
|
||||
// 设置完整的学生信息到store中,供详情页面使用
|
||||
setData(completeStudent);
|
||||
setXs(completeStudent);
|
||||
|
||||
// 跳转到家长通讯录详情页面
|
||||
uni.navigateTo({
|
||||
@ -538,13 +545,25 @@ onMounted(async () => {
|
||||
.student-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20rpx;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.student-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.student-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
@ -555,6 +574,7 @@ onMounted(async () => {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.student-avatar {
|
||||
@ -564,13 +584,51 @@ onMounted(async () => {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.student-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.student-name {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.student-class {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.bottom-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
font-size: 10px;
|
||||
padding: 1px 5px;
|
||||
font-size: 12px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
background-color: rgba(64, 128, 255, 0.1);
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.status-normal {
|
||||
@ -589,7 +647,7 @@ onMounted(async () => {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 60px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.contact-parent {
|
||||
@ -597,11 +655,14 @@ onMounted(async () => {
|
||||
border-radius: 4px;
|
||||
border: 1px solid #4080ff;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
background-color: rgba(64, 128, 255, 0.05);
|
||||
|
||||
&:active {
|
||||
background-color: rgba(64, 128, 255, 0.1);
|
||||
background-color: rgba(64, 128, 255, 0.15);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,6 +240,7 @@ const goDm = (xkkc: any) => {
|
||||
} else {
|
||||
msg = "上课时间未到,无法点名";
|
||||
}
|
||||
dmFlag = true;
|
||||
if (dmFlag) {
|
||||
setData(xkkc);
|
||||
uni.navigateTo({
|
||||
@ -259,57 +260,10 @@ const goDm = (xkkc: any) => {
|
||||
|
||||
// 跳转到点名记录
|
||||
const goRecord = (xkkc: any) => {
|
||||
const now = dayjs();
|
||||
let wDay = now.day();
|
||||
if (wDay === 0) {
|
||||
wDay = 7;
|
||||
}
|
||||
let mDay = now.date();
|
||||
const strDate = now.format('YYYY-MM-DD') + ' ';
|
||||
let recordFlag = false;
|
||||
let msg = "";
|
||||
// 判断周期
|
||||
switch (xkkc.skzqlx) {
|
||||
case '每天':
|
||||
recordFlag = true;
|
||||
break;
|
||||
case '每周':
|
||||
const daysOfWeek = xkkc.skzq.split(',').map(Number);
|
||||
recordFlag = daysOfWeek.includes(wDay);
|
||||
// 从wdNameList读取daysOfWeek对应的周几
|
||||
xkkc.skzqmc = daysOfWeek.map((day: number) => wdNameList[day - 1]).join(',');
|
||||
break;
|
||||
case '每月':
|
||||
const daysOfMonth = xkkc.skzq.split(',').map(Number);
|
||||
recordFlag = daysOfMonth.includes(mDay);
|
||||
// 从根据编号加
|
||||
xkkc.skzqmc = daysOfMonth.map((day: number) => day + "号").join(',');
|
||||
break;
|
||||
}
|
||||
// 判断日期是否合格
|
||||
if (recordFlag) {
|
||||
// xkkc.skkstime开始时间向前dmBeforeMinute分钟
|
||||
const startTime = dayjs(strDate + xkkc.skkstime).subtract(dmBeforeMinute.value, 'minute').format('YYYY-MM-DD HH:mm:ss');
|
||||
const endTime = dayjs(strDate + xkkc.skjstime, 'YYYY-MM-DD HH:mm:ss');
|
||||
recordFlag = now.isBefore(endTime) && now.isAfter(startTime)
|
||||
} else {
|
||||
msg = "上课时间未到,无法查看点名记录";
|
||||
}
|
||||
if (recordFlag) {
|
||||
setData(xkkc);
|
||||
uni.navigateTo({
|
||||
url: `/pages/view/routine/xk/dmXkkcRecord`,
|
||||
url: `/pages/view/routine/xk/dmList`,
|
||||
});
|
||||
} else {
|
||||
if (msg === "") {
|
||||
msg = "上课时间未到,无法查看点名记录";
|
||||
}
|
||||
uni.showToast({
|
||||
title: msg,
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 页面卸载前清除定时器
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<BasicLayout>
|
||||
<!-- 使用 BasicListLayout 包裹记录列表 -->
|
||||
<BasicListLayout @register="register" :fixed="false" class="flex-1">
|
||||
<template #top>
|
||||
<!-- 课程信息卡片 -->
|
||||
<view class="course-card mx-15 my-15 bg-white white-bg-color r-md p-15">
|
||||
<view class="course-card">
|
||||
<view class="flex-row items-center mb-15">
|
||||
<view class="course-icon flex-center mr-10">
|
||||
<u-icon name="calendar" color="#4080ff" size="20"></u-icon>
|
||||
@ -10,80 +12,96 @@
|
||||
<text class="font-14 cor-999 ml-10">{{ todayInfo.date }} ({{ todayInfo.weekName }})</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 点名记录统计 -->
|
||||
<view class="record-stats mx-15 mb-15">
|
||||
<view class="section-title">点名记录</view>
|
||||
<view class="records-grid">
|
||||
<view
|
||||
v-for="(record, index) in dmRecords"
|
||||
:key="record.id"
|
||||
class="record-card"
|
||||
@click="viewRecordDetail(record)"
|
||||
<!-- 搜索筛选区域 -->
|
||||
<view class="search-section">
|
||||
<view class="search-row">
|
||||
<view class="search-item">
|
||||
<text class="label">时间范围:</text>
|
||||
<uni-datetime-picker
|
||||
type="daterange"
|
||||
:value="[startTime, endTime]"
|
||||
@change="onTimeRangeChange"
|
||||
class="date-picker"
|
||||
>
|
||||
<view class="picker-text">{{ getTimeRangeText() }}</view>
|
||||
</uni-datetime-picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="search-row">
|
||||
<button class="search-btn" @click="searchRecords">搜索</button>
|
||||
<button class="reset-btn" @click="resetSearch">重置</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 记录头部信息 -->
|
||||
<view class="records-header" v-if="dmRecords && dmRecords.length > 0">
|
||||
<view class="section-title">点名记录 ({{ totalCount }}条)</view>
|
||||
</view>
|
||||
|
||||
<!-- 初始提示 -->
|
||||
<view v-if="!hasSearched" class="initial-tip">
|
||||
<view class="tip-icon">🔍</view>
|
||||
<text class="tip-text">请选择时间范围开始搜索</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<template #default="{ data }">
|
||||
<view class="record-card" @click="viewRecordDetail(data)">
|
||||
<view class="record-header">
|
||||
<view class="record-time">
|
||||
<u-icon name="clock" color="#666" size="14"></u-icon>
|
||||
<text class="time-text">{{ formatDateTime(record.dmTime) }}</text>
|
||||
</view>
|
||||
<view class="record-status" :class="getRecordStatusClass(record.status)">
|
||||
{{ getRecordStatusText(record.status) }}
|
||||
<text class="time-text">{{ formatDateTime(data.dmTime) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="record-stats">
|
||||
<view class="stat-item">
|
||||
<text class="stat-number">{{ record.zrs || 0 }}</text>
|
||||
<text class="stat-number">{{ data.zrs || 0 }}</text>
|
||||
<text class="stat-label">总人数</text>
|
||||
</view>
|
||||
<view class="stat-item present">
|
||||
<text class="stat-number">{{ record.sdRs || 0 }}</text>
|
||||
<text class="stat-number">{{ data.sdRs || 0 }}</text>
|
||||
<text class="stat-label">实到</text>
|
||||
</view>
|
||||
<view class="stat-item leave">
|
||||
<text class="stat-number">{{ record.qjRs || 0 }}</text>
|
||||
<text class="stat-number">{{ data.qjRs || 0 }}</text>
|
||||
<text class="stat-label">请假</text>
|
||||
</view>
|
||||
<view class="stat-item absent">
|
||||
<text class="stat-number">{{ record.qqRs || 0 }}</text>
|
||||
<text class="stat-number">{{ data.qqRs || 0 }}</text>
|
||||
<text class="stat-label">缺勤</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="record-footer">
|
||||
<text class="teacher-name">教师:{{ record.jsMc || '未知' }}</text>
|
||||
<text class="teacher-name">教师:{{ data.createdUserName || '未知' }}</text>
|
||||
<view class="view-detail">
|
||||
<text class="detail-text">查看详情</text>
|
||||
<u-icon name="arrow-right" color="#4080ff" size="12"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="dmRecords.length === 0" class="empty-state">
|
||||
<u-icon name="info-circle" color="#ccc" size="48"></u-icon>
|
||||
<text class="empty-text">暂无点名记录</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<template #bottom>
|
||||
<!-- 返回按钮 -->
|
||||
<view class="bottom-actions mx-15 mb-30">
|
||||
<view class="bottom-actions mx-15 mb-15">
|
||||
<button class="back-btn" @click="goBack">返回</button>
|
||||
</view>
|
||||
</BasicLayout>
|
||||
</template>
|
||||
</BasicListLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
import BasicLayout from "@/components/BasicLayout/Layout.vue";
|
||||
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
|
||||
import { getXkDmPageApi } from "@/api/base/xkApi";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const { getJs } = useUserStore();
|
||||
const { getData } = useDataStore();
|
||||
const { getData, setData } = useDataStore();
|
||||
|
||||
const js = computed(() => getJs);
|
||||
const xkkc = computed(() => getData);
|
||||
@ -100,8 +118,110 @@ const todayInfo = ref({
|
||||
weekName: wdNameList[wDay - 1],
|
||||
});
|
||||
|
||||
// 学生列表数据
|
||||
const dmRecords = ref<any[]>([]);
|
||||
// 响应式数据
|
||||
const loading = ref(false);
|
||||
const startTime = ref("");
|
||||
const endTime = ref("");
|
||||
const dmRecords = ref<any>([]);
|
||||
const totalCount = ref<any>(0);
|
||||
const hasSearched = ref(false);
|
||||
|
||||
// 使用 BasicListLayout
|
||||
const [register, { reload, setParam }] = useLayout({
|
||||
api: async (params: any) => {
|
||||
try {
|
||||
const res = await getXkDmPageApi({
|
||||
...params,
|
||||
xkkcId: xkkc.value.id,
|
||||
jsId: js.value.id,
|
||||
sidx: 'dmTime',
|
||||
sord: 'desc'
|
||||
});
|
||||
console.log("API返回数据:", res, xkkc.value);
|
||||
|
||||
if (res) {
|
||||
dmRecords.value = res?.rows || [];
|
||||
totalCount.value = res?.total || 0;
|
||||
return res;
|
||||
} else {
|
||||
dmRecords.value = [];
|
||||
totalCount.value = 0;
|
||||
return { rows: [], total: 0, page: 1, pageSize: 10 };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取数据失败:", error);
|
||||
dmRecords.value = [];
|
||||
totalCount.value = 0;
|
||||
return { rows: [], total: 0, page: 1, pageSize: 10 };
|
||||
}
|
||||
},
|
||||
componentProps: {
|
||||
auto: false,
|
||||
},
|
||||
});
|
||||
|
||||
// 时间范围选择
|
||||
const onTimeRangeChange = (e: any) => {
|
||||
if (e && Array.isArray(e)) {
|
||||
const [start, end] = e;
|
||||
startTime.value = start;
|
||||
endTime.value = end;
|
||||
} else if (e && typeof e === "string") {
|
||||
startTime.value = e;
|
||||
endTime.value = e;
|
||||
}
|
||||
};
|
||||
|
||||
const getTimeRangeText = () => {
|
||||
if (!startTime.value || !endTime.value) return "选择时间范围";
|
||||
if (startTime.value === endTime.value) {
|
||||
return formatDate(startTime.value);
|
||||
}
|
||||
return `${formatDate(startTime.value)} - ${formatDate(endTime.value)}`;
|
||||
};
|
||||
|
||||
const searchRecords = async () => {
|
||||
if (!startTime.value || !endTime.value) {
|
||||
uni.showToast({
|
||||
title: "请选择时间范围",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证开始时间不能大于结束时间
|
||||
if (startTime.value > endTime.value) {
|
||||
uni.showToast({
|
||||
title: "开始时间不能大于结束时间",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
hasSearched.value = true;
|
||||
|
||||
console.log("搜索参数:", {
|
||||
startTime: startTime.value + " 00:00:00",
|
||||
endTime: endTime.value + " 23:59:59",
|
||||
pageNo: 1,
|
||||
});
|
||||
|
||||
// 设置搜索参数并重新加载
|
||||
setParam({
|
||||
startTime: startTime.value + " 00:00:00",
|
||||
endTime: endTime.value + " 23:59:59",
|
||||
pageNo: 1,
|
||||
});
|
||||
reload();
|
||||
};
|
||||
|
||||
const resetSearch = () => {
|
||||
startTime.value = "";
|
||||
endTime.value = "";
|
||||
dmRecords.value = [];
|
||||
totalCount.value = 0;
|
||||
hasSearched.value = false;
|
||||
};
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (dateTime: string | Date) => {
|
||||
@ -109,6 +229,15 @@ const formatDateTime = (dateTime: string | Date) => {
|
||||
return dayjs(dateTime).format('MM-DD HH:mm');
|
||||
};
|
||||
|
||||
const formatDate = (dateStr: string) => {
|
||||
if (!dateStr) return "";
|
||||
const date = new Date(dateStr);
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(
|
||||
2,
|
||||
"0"
|
||||
)}-${String(date.getDate()).padStart(2, "0")}`;
|
||||
};
|
||||
|
||||
// 获取记录状态样式类
|
||||
const getRecordStatusClass = (status: string) => {
|
||||
switch (status) {
|
||||
@ -132,7 +261,6 @@ const getRecordStatusText = (status: string) => {
|
||||
// 查看记录详情
|
||||
const viewRecordDetail = (record: any) => {
|
||||
// 将记录信息存储到store中
|
||||
const { setData } = useDataStore();
|
||||
setData({
|
||||
...xkkc.value,
|
||||
dmRecord: record
|
||||
@ -149,50 +277,33 @@ const goBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
// 加载学生列表
|
||||
const loadStudentList = async () => {
|
||||
try {
|
||||
uni.showLoading({ title: '加载中...' });
|
||||
|
||||
const res = await getXkDmPageApi({
|
||||
xkkcId: xkkc.value.id,
|
||||
jsId: js.value.id,
|
||||
pageNum: 1,
|
||||
pageSize: 50,
|
||||
sidx: 'dmTime',
|
||||
sord: 'desc'
|
||||
});
|
||||
|
||||
if (res && res.resultCode === 1) {
|
||||
dmRecords.value = res.result?.rows || [];
|
||||
} else {
|
||||
dmRecords.value = [];
|
||||
uni.showToast({
|
||||
title: (res as any)?.resultMessage || '获取点名记录失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载点名记录失败:', error);
|
||||
dmRecords.value = [];
|
||||
uni.showToast({
|
||||
title: '加载点名记录失败',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
};
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadStudentList();
|
||||
// 设置默认时间范围为最近一周
|
||||
const today = new Date();
|
||||
const oneWeekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
|
||||
|
||||
startTime.value = oneWeekAgo.toISOString().split("T")[0];
|
||||
endTime.value = today.toISOString().split("T")[0];
|
||||
|
||||
console.log("组件初始化完成:", {
|
||||
startTime: startTime.value,
|
||||
endTime: endTime.value,
|
||||
dmRecords: dmRecords.value,
|
||||
totalCount: totalCount.value,
|
||||
});
|
||||
|
||||
// 不自动加载数据,等待用户搜索
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.course-card {
|
||||
border-radius: 8px;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||
margin: 30rpx;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.course-icon {
|
||||
@ -202,26 +313,89 @@ onMounted(() => {
|
||||
background-color: rgba(64, 128, 255, 0.1);
|
||||
}
|
||||
|
||||
.record-stats {
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
padding-left: 10px;
|
||||
border-left: 4px solid #4080ff;
|
||||
.search-section {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-left: 30rpx;
|
||||
margin-right: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.records-grid {
|
||||
.search-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.search-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
flex: 0 0 160rpx;
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
padding: 16rpx 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
border: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.search-btn,
|
||||
.reset-btn {
|
||||
flex: 1;
|
||||
height: 70rpx;
|
||||
border-radius: 35rpx;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
background-color: #007aff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.records-header {
|
||||
padding: 0 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.record-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
@ -340,15 +514,43 @@ onMounted(() => {
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #ccc;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
display: block;
|
||||
margin-top: 15px;
|
||||
font-size: 16px;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.initial-tip {
|
||||
text-align: center;
|
||||
padding: 50rpx 0;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.tip-icon {
|
||||
font-size: 60rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
@ -382,3 +584,4 @@ onMounted(() => {
|
||||
.font-bold { font-weight: bold; }
|
||||
.cor-999 { color: #999; }
|
||||
</style>
|
||||
|
||||
@ -11,27 +11,43 @@
|
||||
</view>
|
||||
<view class="dm-time">
|
||||
<u-icon name="clock" color="#666" size="14"></u-icon>
|
||||
<text class="time-text">{{ formatDateTime(dmRecord.dmTime) }}</text>
|
||||
<text class="time-text">{{ formatDateTime(safeDmRecord.dmTime) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 考勤统计 -->
|
||||
<view class="attendance-stats">
|
||||
<view class="stats-grid">
|
||||
<view class="stat-item total">
|
||||
<view class="stat-number">{{ dmRecord.zrs || 0 }}</view>
|
||||
<view
|
||||
class="stat-item total"
|
||||
:class="{ active: currentFilter === 'all' }"
|
||||
@click="setFilter('all')"
|
||||
>
|
||||
<view class="stat-number">{{ safeDmRecord.zrs || 0 }}</view>
|
||||
<view class="stat-label">总人数</view>
|
||||
</view>
|
||||
<view class="stat-item present">
|
||||
<view class="stat-number">{{ dmRecord.sdRs || 0 }}</view>
|
||||
<view
|
||||
class="stat-item present"
|
||||
:class="{ active: currentFilter === 'A' }"
|
||||
@click="setFilter('A')"
|
||||
>
|
||||
<view class="stat-number">{{ safeDmRecord.sdRs || 0 }}</view>
|
||||
<view class="stat-label">实到</view>
|
||||
</view>
|
||||
<view class="stat-item leave">
|
||||
<view class="stat-number">{{ dmRecord.qjRs || 0 }}</view>
|
||||
<view
|
||||
class="stat-item leave"
|
||||
:class="{ active: currentFilter === 'B' }"
|
||||
@click="setFilter('B')"
|
||||
>
|
||||
<view class="stat-number">{{ safeDmRecord.qjRs || 0 }}</view>
|
||||
<view class="stat-label">请假</view>
|
||||
</view>
|
||||
<view class="stat-item absent">
|
||||
<view class="stat-number">{{ dmRecord.qqRs || 0 }}</view>
|
||||
<view
|
||||
class="stat-item absent"
|
||||
:class="{ active: currentFilter === 'C' }"
|
||||
@click="setFilter('C')"
|
||||
>
|
||||
<view class="stat-number">{{ safeDmRecord.qqRs || 0 }}</view>
|
||||
<view class="stat-label">缺勤</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -39,97 +55,95 @@
|
||||
</view>
|
||||
|
||||
<!-- 学生列表 -->
|
||||
<view class="student-section mx-15 mb-15">
|
||||
<view class="section-header">
|
||||
<text class="section-title">学生列表</text>
|
||||
<view class="filter-tabs">
|
||||
<view class="student-list mb-15 white-bg-color">
|
||||
<view class="student-grid">
|
||||
<view
|
||||
v-for="tab in filterTabs"
|
||||
:key="tab.value"
|
||||
class="filter-tab"
|
||||
:class="{ active: currentFilter === tab.value }"
|
||||
@click="setFilter(tab.value)"
|
||||
v-for="(xs, index) in filteredStudentList"
|
||||
:key="index"
|
||||
class="student-item bg-white r-md p-12"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="student-list">
|
||||
<view
|
||||
v-for="(student, index) in filteredStudents"
|
||||
:key="student.id"
|
||||
class="student-item"
|
||||
>
|
||||
<view class="student-avatar">
|
||||
<view class="student-content">
|
||||
<!-- 第一行:头像和学生信息 -->
|
||||
<view class="top-row">
|
||||
<view class="avatar-container">
|
||||
<image
|
||||
v-if="student.tx || student.xstx"
|
||||
:src="getImageUrl(student.tx || student.xstx)"
|
||||
class="student-avatar"
|
||||
:src="getImageUrl(xs.tx || xs.xstx)"
|
||||
mode="aspectFill"
|
||||
class="avatar-img"
|
||||
/>
|
||||
<view v-else class="avatar-text">{{ (student.xsXm || student.xsxm)?.charAt(0) || '学' }}</view>
|
||||
></image>
|
||||
</view>
|
||||
<view class="student-info">
|
||||
<view class="student-name">{{ student.xsXm || student.xsxm }}</view>
|
||||
<view class="student-class">{{ student.njmc }} {{ student.bjmc }}</view>
|
||||
<text class="student-name">{{ xs.xsXm || xs.xsxm }}</text>
|
||||
<text class="student-class">{{ xs.bjmc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 第二行:状态和联系家长 -->
|
||||
<view class="bottom-row">
|
||||
<view class="status-tag" :class="getStatusClass(xs.xsZt || xs.xszt)">
|
||||
{{ getStatusText(xs.xsZt || xs.xszt) }}
|
||||
</view>
|
||||
<view class="contact-parent" @click="contactParent(xs)">
|
||||
<text class="font-12 cor-primary">联系家长</text>
|
||||
<u-icon
|
||||
name="phone"
|
||||
color="#4080ff"
|
||||
size="14"
|
||||
class="ml-2"
|
||||
></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="student-status">
|
||||
<text :class="getStatusClass(student.xsZt || student.xszt)">
|
||||
{{ getStatusText(student.xsZt || student.xszt) }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="filteredStudents.length === 0" class="empty-state">
|
||||
<u-icon name="info-circle" color="#ccc" size="48"></u-icon>
|
||||
<text class="empty-text">暂无学生数据</text>
|
||||
</view>
|
||||
<!-- 媒体预览 -->
|
||||
<view v-if="safeDmRecord.zp || safeDmRecord.sp" class="media-section mx-15 mb-15">
|
||||
<DmPsPreview
|
||||
:zp="safeDmRecord.zp"
|
||||
:sp="safeDmRecord.sp"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<template #bottom>
|
||||
<!-- 返回按钮 -->
|
||||
<view class="bottom-actions mx-15 mb-30">
|
||||
<view class="bottom-actions mx-15 mb-15">
|
||||
<button class="back-btn" @click="goBack">返回</button>
|
||||
</view>
|
||||
</template>
|
||||
</BasicLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
import BasicLayout from "@/components/BasicLayout/Layout.vue";
|
||||
import { getXkDmXsPageApi } from "@/api/base/xkApi";
|
||||
import DmPsPreview from "@/pages/components/dmPs/preview.vue";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
import { getDmXsListApi } from "@/api/base/xkApi";
|
||||
import { BASE_IMAGE_URL } from "@/config";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const { getJs } = useUserStore();
|
||||
const { getData } = useDataStore();
|
||||
const { getData, setXs } = useDataStore();
|
||||
|
||||
const js = computed(() => getJs);
|
||||
const xkkc = computed(() => getData);
|
||||
const dmRecord = computed(() => getData?.dmRecord || {});
|
||||
|
||||
// 学生列表数据
|
||||
const studentList = ref<any[]>([]);
|
||||
const currentFilter = ref<string>('all');
|
||||
// 确保 dmRecord 有默认值,避免 undefined 错误
|
||||
const safeDmRecord = computed(() => {
|
||||
return dmRecord.value || {};
|
||||
});
|
||||
|
||||
// 筛选选项
|
||||
const filterTabs = ref([
|
||||
{ label: '全部', value: 'all' },
|
||||
{ label: '正常', value: 'A' },
|
||||
{ label: '请假', value: 'B' },
|
||||
{ label: '缺勤', value: 'C' }
|
||||
]);
|
||||
// 响应式数据
|
||||
const allStudentList = ref<any[]>([]); // 存储所有学生数据
|
||||
const currentFilter = ref<string>('all'); // 默认选择总人数
|
||||
|
||||
// 筛选后的学生列表
|
||||
const filteredStudents = computed(() => {
|
||||
const filteredStudentList = computed(() => {
|
||||
if (currentFilter.value === 'all') {
|
||||
return studentList.value;
|
||||
return allStudentList.value;
|
||||
}
|
||||
return studentList.value.filter(student =>
|
||||
return allStudentList.value.filter(student =>
|
||||
(student.xsZt || student.xszt) === currentFilter.value
|
||||
);
|
||||
});
|
||||
@ -137,14 +151,24 @@ const filteredStudents = computed(() => {
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (dateTime: string | Date) => {
|
||||
if (!dateTime) return '';
|
||||
try {
|
||||
return dayjs(dateTime).format('MM-DD HH:mm');
|
||||
} catch (error) {
|
||||
console.error('日期格式化错误:', error);
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
// 获取图片完整URL
|
||||
const getImageUrl = (path: string) => {
|
||||
if (!path) return '';
|
||||
try {
|
||||
if (path.startsWith('http')) return path;
|
||||
return BASE_IMAGE_URL + path;
|
||||
} catch (error) {
|
||||
console.error('图片URL处理错误:', error);
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
// 获取状态样式类
|
||||
@ -175,46 +199,65 @@ const setFilter = (filter: string) => {
|
||||
currentFilter.value = filter;
|
||||
};
|
||||
|
||||
// 联系家长
|
||||
const contactParent = (dmXs: any) => {
|
||||
// 构建完整的学生信息,确保包含所有必要字段
|
||||
const completeStudent = {
|
||||
...dmXs,
|
||||
// 确保字段名的一致性
|
||||
id: dmXs.xsId || dmXs.id,
|
||||
xsxm: dmXs.xsXm || dmXs.xsxm || dmXs.xm,
|
||||
xstx: dmXs.tx || dmXs.xstx || dmXs.avatar,
|
||||
xb: dmXs.xb || dmXs.gender,
|
||||
sfzh: dmXs.sfzh,
|
||||
cstime: dmXs.cstime,
|
||||
njmc: dmXs.njmcName || dmXs.njmc,
|
||||
bjmc: dmXs.bjmc,
|
||||
// 如果后端返回的是njId和bjId,也保留
|
||||
njId: dmXs.njId,
|
||||
bjId: dmXs.bjId
|
||||
};
|
||||
|
||||
// 设置完整的学生信息到store中,供详情页面使用
|
||||
setXs(completeStudent);
|
||||
|
||||
// 跳转到家长通讯录详情页面
|
||||
// @ts-ignore
|
||||
uni.navigateTo({
|
||||
url: "/pages/view/homeSchool/parentAddressBook/detail"
|
||||
});
|
||||
};
|
||||
|
||||
// 返回上一页
|
||||
const goBack = () => {
|
||||
// @ts-ignore
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
// 加载学生列表
|
||||
const loadStudentList = async () => {
|
||||
try {
|
||||
uni.showLoading({ title: '加载中...' });
|
||||
|
||||
const res = await getXkDmXsPageApi({
|
||||
dmId: dmRecord.value.id,
|
||||
pageNum: 1,
|
||||
pageSize: 100,
|
||||
sidx: 'xsXm',
|
||||
sord: 'asc'
|
||||
});
|
||||
|
||||
if (res && res.resultCode === 1) {
|
||||
studentList.value = res.result?.rows || [];
|
||||
} else {
|
||||
studentList.value = [];
|
||||
uni.showToast({
|
||||
title: (res as any)?.resultMessage || '获取学生列表失败',
|
||||
icon: 'none'
|
||||
const res = await getDmXsListApi({
|
||||
dmId: safeDmRecord.value.id
|
||||
});
|
||||
if (res) {
|
||||
allStudentList.value = res?.result || [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载学生列表失败:', error);
|
||||
studentList.value = [];
|
||||
uni.showToast({
|
||||
title: '加载学生列表失败',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
console.error("获取数据失败:", error);
|
||||
allStudentList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
console.log("dmXsList 组件初始化:", {
|
||||
xkkc: xkkc.value,
|
||||
dmRecord: safeDmRecord.value,
|
||||
hasData: !!getData
|
||||
});
|
||||
|
||||
// 自动加载数据
|
||||
loadStudentList();
|
||||
});
|
||||
</script>
|
||||
@ -250,34 +293,80 @@ onMounted(() => {
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 15px;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px 15px;
|
||||
padding: 20rpx 0;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&.active {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 50rpx;
|
||||
height: 20rpx;
|
||||
background: #4080ff;
|
||||
border-top-left-radius: 50rpx;
|
||||
border-top-right-radius: 50rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&.total {
|
||||
border: 2px solid #666;
|
||||
.stat-number { color: #333; }
|
||||
|
||||
&.active {
|
||||
border-color: #4080ff;
|
||||
background: rgba(64, 128, 255, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
&.present {
|
||||
border: 2px solid #2879ff;
|
||||
.stat-number { color: #2879ff; }
|
||||
border: 2px solid #52c41a;
|
||||
.stat-number { color: #52c41a; }
|
||||
|
||||
&.active {
|
||||
border-color: #4080ff;
|
||||
background: rgba(64, 128, 255, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
&.leave {
|
||||
border: 2px solid #ff9900;
|
||||
.stat-number { color: #ff9900; }
|
||||
|
||||
&.active {
|
||||
border-color: #4080ff;
|
||||
background: rgba(64, 128, 255, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
&.absent {
|
||||
border: 2px solid #ff4d4f;
|
||||
.stat-number { color: #ff4d4f; }
|
||||
|
||||
&.active {
|
||||
border-color: #4080ff;
|
||||
background: rgba(64, 128, 255, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
@ -293,135 +382,127 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.student-section {
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
padding-left: 10px;
|
||||
border-left: 4px solid #4080ff;
|
||||
}
|
||||
|
||||
.filter-tabs {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
.filter-tab {
|
||||
padding: 6px 12px;
|
||||
border-radius: 16px;
|
||||
font-size: 12px;
|
||||
background: #f5f5f5;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.active {
|
||||
background: #4080ff;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
.media-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.student-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.student-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.student-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.student-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
border-radius: 50%;
|
||||
padding: 3px;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.student-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
margin-right: 15px;
|
||||
|
||||
.avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #4080ff;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.student-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.student-name {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.student-class {
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.student-status {
|
||||
.status-normal {
|
||||
color: #2879ff;
|
||||
background: rgba(40, 121, 255, 0.1);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
.bottom-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
font-size: 12px;
|
||||
padding: 3px 12px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: rgba(64, 128, 255, 0.1);
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.status-normal {
|
||||
color: #4080ff;
|
||||
}
|
||||
|
||||
.status-leave {
|
||||
color: #ff9900;
|
||||
background: rgba(255, 153, 0, 0.1);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.status-absent {
|
||||
color: #ff4d4f;
|
||||
background: rgba(255, 77, 79, 0.1);
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.contact-parent {
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
border: 1px solid #4080ff;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
background-color: rgba(64, 128, 255, 0.05);
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #ccc;
|
||||
|
||||
.empty-text {
|
||||
display: block;
|
||||
margin-top: 15px;
|
||||
font-size: 16px;
|
||||
}
|
||||
&:active {
|
||||
background-color: rgba(64, 128, 255, 0.15);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
@ -449,10 +530,18 @@ onMounted(() => {
|
||||
.white-bg-color { background-color: white; }
|
||||
.r-md { border-radius: 8px; }
|
||||
.p-15 { padding: 15px; }
|
||||
.p-12 { padding: 12px; }
|
||||
.flex-row { display: flex; flex-direction: row; }
|
||||
.flex-center { display: flex; align-items: center; justify-content: center; }
|
||||
.items-center { align-items: center; }
|
||||
.font-16 { font-size: 16px; }
|
||||
.font-14 { font-size: 14px; }
|
||||
.font-12 { font-size: 12px; }
|
||||
.font-bold { font-weight: bold; }
|
||||
.cor-999 { color: #999; }
|
||||
.cor-primary { color: #4080ff; }
|
||||
.cor-warning { color: #ff9900; }
|
||||
.cor-danger { color: #ff4d4f; }
|
||||
.cor-666 { color: #666; }
|
||||
.ml-2 { margin-left: 2px; }
|
||||
</style>
|
||||
|
||||
@ -8,6 +8,7 @@ export const useDataStore = defineStore({
|
||||
xxts: {}, // 添加xxts字段
|
||||
global: {},
|
||||
file: {},
|
||||
xs: {}, // 学生专用
|
||||
}),
|
||||
getters: {
|
||||
getData(): any {
|
||||
@ -25,6 +26,9 @@ export const useDataStore = defineStore({
|
||||
getFile(): any {
|
||||
return this.file;
|
||||
},
|
||||
getXs(): any {
|
||||
return this.xs;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
setData(data: any) {
|
||||
@ -42,6 +46,9 @@ export const useDataStore = defineStore({
|
||||
setFile(data: any) {
|
||||
this.file = data;
|
||||
},
|
||||
setXs(data: any) {
|
||||
this.xs = data;
|
||||
}
|
||||
},
|
||||
persist: {
|
||||
enabled: true,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user