调整完善教师端关于选课点名相关界面
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)
|
return await get('/api/xkDmXs/findPage', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选课学生列表
|
// 选课待点名学生列表
|
||||||
export const getWaitDmXsListApi = async (params: any) => {
|
export const getWaitDmXsListApi = async (params: any) => {
|
||||||
return await get("/api/xkDmXs/getWaitDmXsList", params);
|
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": {
|
"style": {
|
||||||
"navigationBarTitleText": "点名记录",
|
"navigationBarTitleText": "点名列表",
|
||||||
"enablePullDownRefresh": false
|
"enablePullDownRefresh": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/view/routine/xk/dmXsRecord",
|
"path": "pages/view/routine/xk/dmXsList",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "点名学生记录",
|
"navigationBarTitleText": "点名学生列表",
|
||||||
"enablePullDownRefresh": false
|
"enablePullDownRefresh": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -54,7 +54,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="video-upload" @click="recordVideo">
|
<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>
|
<text class="upload-text">{{ videoUploadText || '点击录制' }}</text>
|
||||||
</view>
|
</view>
|
||||||
</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 { onLoad } from "@dcloudio/uni-app";
|
||||||
import { xsJzListByXsIdApi } from "@/api/base/server";
|
import { xsJzListByXsIdApi } from "@/api/base/server";
|
||||||
import { useDataStore } from "@/store/modules/data";
|
import { useDataStore } from "@/store/modules/data";
|
||||||
const { getData } = useDataStore();
|
const { getXs } = useDataStore();
|
||||||
|
|
||||||
const xsInfo = computed(() => {
|
const xsInfo = computed(() => {
|
||||||
const xs = getData || {};
|
const xs = getXs || {};
|
||||||
// 适配从点名页面传递过来的学生信息字段
|
// 适配从点名页面传递过来的学生信息字段
|
||||||
const studentInfo = {
|
const studentInfo = {
|
||||||
id: xs.xsId || xs.id, // 支持两种字段名
|
id: xs.xsId || xs.id, // 支持两种字段名
|
||||||
@ -159,7 +159,7 @@ onLoad(async (options) => {
|
|||||||
if (xsInfo.value && xsInfo.value.id) {
|
if (xsInfo.value && xsInfo.value.id) {
|
||||||
try {
|
try {
|
||||||
uni.showLoading({ title: "加载中..." });
|
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) {
|
if (res && res.resultCode === 1) {
|
||||||
jzList.value = res.result || [];
|
jzList.value = res.result || [];
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -56,7 +56,7 @@ import NjBjPicker from "@/pages/components/NjBjPicker/index.vue";
|
|||||||
import { imagUrl } from "@/utils";
|
import { imagUrl } from "@/utils";
|
||||||
import { xsFindList } from "@/api/base/server";
|
import { xsFindList } from "@/api/base/server";
|
||||||
import { useDataStore } from "@/store/modules/data";
|
import { useDataStore } from "@/store/modules/data";
|
||||||
const { setData } = useDataStore();
|
const { setXs } = useDataStore();
|
||||||
|
|
||||||
const xsList = ref<any>([]);
|
const xsList = ref<any>([]);
|
||||||
const xsTotal = ref(0);
|
const xsTotal = ref(0);
|
||||||
@ -114,7 +114,7 @@ const getXsList = async () => {
|
|||||||
|
|
||||||
// 跳转到详情页
|
// 跳转到详情页
|
||||||
const goToDetail = (xs: any) => {
|
const goToDetail = (xs: any) => {
|
||||||
setData(xs);
|
setXs(xs);
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/view/homeSchool/parentAddressBook/detail`
|
url: `/pages/view/homeSchool/parentAddressBook/detail`
|
||||||
});
|
});
|
||||||
|
|||||||
@ -32,35 +32,40 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 学生列表 -->
|
<!-- 学生列表 -->
|
||||||
<view class="student-list mb-30 white-bg-color">
|
<view class="student-list mb-15 white-bg-color">
|
||||||
<view class="student-grid">
|
<view class="student-grid">
|
||||||
<view
|
<view
|
||||||
v-for="(xs, index) in xsList"
|
v-for="(xs, index) in xsList"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="student-item bg-white r-md p-12"
|
class="student-item bg-white r-md p-12"
|
||||||
>
|
>
|
||||||
<view class="flex-row items-center">
|
<view class="student-content">
|
||||||
<view class="avatar-container mr-8">
|
<!-- 第一行:头像和学生信息 -->
|
||||||
|
<view class="top-row">
|
||||||
|
<view class="avatar-container">
|
||||||
<image
|
<image
|
||||||
class="student-avatar"
|
class="student-avatar"
|
||||||
:src="getImageUrl(xs.tx || xs.xstx)"
|
:src="getImageUrl(xs.tx || xs.xstx)"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
></image>
|
></image>
|
||||||
</view>
|
</view>
|
||||||
<view class="flex-1 overflow-hidden">
|
<view class="student-info">
|
||||||
<view class="flex-row items-center mb-3">
|
<text class="student-name">{{ xs.xsXm || xs.xsxm }}</text>
|
||||||
<text class="font-14 font-bold mr-5 text-ellipsis">{{ xs.xsXm || xs.xsxm }}</text>
|
<text class="student-class">{{ xs.bjmc }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 第二行:状态和联系家长 -->
|
||||||
|
<view class="bottom-row">
|
||||||
<view
|
<view
|
||||||
class="status-tag"
|
class="status-tag"
|
||||||
:class="getStatusClass(xs.xsZt || xs.xszt)"
|
:class="getStatusClass(xs.xsZt || xs.xszt)"
|
||||||
@click="openStatusPicker(xs)"
|
@click="openStatusPicker(xs)"
|
||||||
>
|
>
|
||||||
{{ getStatusText(xs.xsZt || xs.xszt) }}
|
{{ 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>
|
||||||
</view>
|
<view class="contact-parent" @click="contactParent(xs)">
|
||||||
<text class="font-12 cor-666">{{ xs.bjmc }}</text>
|
|
||||||
<view class="contact-parent mt-8 flex-center" @click="contactParent(xs)">
|
|
||||||
<text class="font-12 cor-primary">联系家长</text>
|
<text class="font-12 cor-primary">联系家长</text>
|
||||||
<u-icon
|
<u-icon
|
||||||
name="phone"
|
name="phone"
|
||||||
@ -75,6 +80,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view class="mx-15">
|
||||||
<DmPsComponent
|
<DmPsComponent
|
||||||
v-model="mediaData"
|
v-model="mediaData"
|
||||||
:photo-title="'现场拍照'"
|
:photo-title="'现场拍照'"
|
||||||
@ -84,6 +90,7 @@
|
|||||||
:max-video-duration="60"
|
:max-video-duration="60"
|
||||||
ref="dmPsRef"
|
ref="dmPsRef"
|
||||||
/>
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 状态选择弹窗 -->
|
<!-- 状态选择弹窗 -->
|
||||||
<u-picker
|
<u-picker
|
||||||
@ -131,7 +138,7 @@ const { findByPid } = useDicStore();
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
const { getJs } = useUserStore();
|
const { getJs } = useUserStore();
|
||||||
const { getData, setData } = useDataStore();
|
const { getData, setData, setXs } = useDataStore();
|
||||||
|
|
||||||
const js = computed(() => getJs)
|
const js = computed(() => getJs)
|
||||||
const xkkc = computed(() => getData)
|
const xkkc = computed(() => getData)
|
||||||
@ -355,7 +362,7 @@ const contactParent = (dmXs: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 设置完整的学生信息到store中,供详情页面使用
|
// 设置完整的学生信息到store中,供详情页面使用
|
||||||
setData(completeStudent);
|
setXs(completeStudent);
|
||||||
|
|
||||||
// 跳转到家长通讯录详情页面
|
// 跳转到家长通讯录详情页面
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
@ -538,13 +545,25 @@ onMounted(async () => {
|
|||||||
.student-grid {
|
.student-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
gap: 20rpx;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.student-item {
|
.student-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.student-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.avatar-container {
|
.avatar-container {
|
||||||
width: 46px;
|
width: 46px;
|
||||||
height: 46px;
|
height: 46px;
|
||||||
@ -555,6 +574,7 @@ onMounted(async () => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.student-avatar {
|
.student-avatar {
|
||||||
@ -564,13 +584,51 @@ onMounted(async () => {
|
|||||||
background-color: #f5f5f5;
|
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 {
|
.status-tag {
|
||||||
font-size: 10px;
|
font-size: 12px;
|
||||||
padding: 1px 5px;
|
padding: 3px 8px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
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 {
|
.status-normal {
|
||||||
@ -589,7 +647,7 @@ onMounted(async () => {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
max-width: 60px;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-parent {
|
.contact-parent {
|
||||||
@ -597,11 +655,14 @@ onMounted(async () => {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid #4080ff;
|
border: 1px solid #4080ff;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
background-color: rgba(64, 128, 255, 0.05);
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background-color: rgba(64, 128, 255, 0.1);
|
background-color: rgba(64, 128, 255, 0.15);
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -240,6 +240,7 @@ const goDm = (xkkc: any) => {
|
|||||||
} else {
|
} else {
|
||||||
msg = "上课时间未到,无法点名";
|
msg = "上课时间未到,无法点名";
|
||||||
}
|
}
|
||||||
|
dmFlag = true;
|
||||||
if (dmFlag) {
|
if (dmFlag) {
|
||||||
setData(xkkc);
|
setData(xkkc);
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
@ -259,57 +260,10 @@ const goDm = (xkkc: any) => {
|
|||||||
|
|
||||||
// 跳转到点名记录
|
// 跳转到点名记录
|
||||||
const goRecord = (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);
|
setData(xkkc);
|
||||||
uni.navigateTo({
|
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>
|
<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="flex-row items-center mb-15">
|
||||||
<view class="course-icon flex-center mr-10">
|
<view class="course-icon flex-center mr-10">
|
||||||
<u-icon name="calendar" color="#4080ff" size="20"></u-icon>
|
<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>
|
<text class="font-14 cor-999 ml-10">{{ todayInfo.date }} ({{ todayInfo.weekName }})</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<!-- 搜索筛选区域 -->
|
||||||
<!-- 点名记录统计 -->
|
<view class="search-section">
|
||||||
<view class="record-stats mx-15 mb-15">
|
<view class="search-row">
|
||||||
<view class="section-title">点名记录</view>
|
<view class="search-item">
|
||||||
<view class="records-grid">
|
<text class="label">时间范围:</text>
|
||||||
<view
|
<uni-datetime-picker
|
||||||
v-for="(record, index) in dmRecords"
|
type="daterange"
|
||||||
:key="record.id"
|
:value="[startTime, endTime]"
|
||||||
class="record-card"
|
@change="onTimeRangeChange"
|
||||||
@click="viewRecordDetail(record)"
|
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-header">
|
||||||
<view class="record-time">
|
<view class="record-time">
|
||||||
<u-icon name="clock" color="#666" size="14"></u-icon>
|
<u-icon name="clock" color="#666" size="14"></u-icon>
|
||||||
<text class="time-text">{{ formatDateTime(record.dmTime) }}</text>
|
<text class="time-text">{{ formatDateTime(data.dmTime) }}</text>
|
||||||
</view>
|
|
||||||
<view class="record-status" :class="getRecordStatusClass(record.status)">
|
|
||||||
{{ getRecordStatusText(record.status) }}
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="record-stats">
|
<view class="record-stats">
|
||||||
<view class="stat-item">
|
<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>
|
<text class="stat-label">总人数</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat-item present">
|
<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>
|
<text class="stat-label">实到</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat-item leave">
|
<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>
|
<text class="stat-label">请假</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat-item absent">
|
<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>
|
<text class="stat-label">缺勤</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="record-footer">
|
<view class="record-footer">
|
||||||
<text class="teacher-name">教师:{{ record.jsMc || '未知' }}</text>
|
<text class="teacher-name">教师:{{ data.createdUserName || '未知' }}</text>
|
||||||
<view class="view-detail">
|
<view class="view-detail">
|
||||||
<text class="detail-text">查看详情</text>
|
<text class="detail-text">查看详情</text>
|
||||||
<u-icon name="arrow-right" color="#4080ff" size="12"></u-icon>
|
<u-icon name="arrow-right" color="#4080ff" size="12"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</template>
|
||||||
|
|
||||||
<!-- 空状态 -->
|
|
||||||
<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 #bottom>
|
||||||
<!-- 返回按钮 -->
|
<!-- 返回按钮 -->
|
||||||
<view class="bottom-actions mx-15 mb-30">
|
<view class="bottom-actions mx-15 mb-15">
|
||||||
<button class="back-btn" @click="goBack">返回</button>
|
<button class="back-btn" @click="goBack">返回</button>
|
||||||
</view>
|
</view>
|
||||||
</BasicLayout>
|
</template>
|
||||||
|
</BasicListLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from "vue";
|
import { ref, computed, onMounted } from "vue";
|
||||||
import { useUserStore } from "@/store/modules/user";
|
import { useUserStore } from "@/store/modules/user";
|
||||||
import { useDataStore } from "@/store/modules/data";
|
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 { getXkDmPageApi } from "@/api/base/xkApi";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
const { getJs } = useUserStore();
|
const { getJs } = useUserStore();
|
||||||
const { getData } = useDataStore();
|
const { getData, setData } = useDataStore();
|
||||||
|
|
||||||
const js = computed(() => getJs);
|
const js = computed(() => getJs);
|
||||||
const xkkc = computed(() => getData);
|
const xkkc = computed(() => getData);
|
||||||
@ -100,8 +118,110 @@ const todayInfo = ref({
|
|||||||
weekName: wdNameList[wDay - 1],
|
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) => {
|
const formatDateTime = (dateTime: string | Date) => {
|
||||||
@ -109,6 +229,15 @@ const formatDateTime = (dateTime: string | Date) => {
|
|||||||
return dayjs(dateTime).format('MM-DD HH:mm');
|
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) => {
|
const getRecordStatusClass = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
@ -132,7 +261,6 @@ const getRecordStatusText = (status: string) => {
|
|||||||
// 查看记录详情
|
// 查看记录详情
|
||||||
const viewRecordDetail = (record: any) => {
|
const viewRecordDetail = (record: any) => {
|
||||||
// 将记录信息存储到store中
|
// 将记录信息存储到store中
|
||||||
const { setData } = useDataStore();
|
|
||||||
setData({
|
setData({
|
||||||
...xkkc.value,
|
...xkkc.value,
|
||||||
dmRecord: record
|
dmRecord: record
|
||||||
@ -149,50 +277,33 @@ const goBack = () => {
|
|||||||
uni.navigateBack();
|
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(() => {
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.course-card {
|
.course-card {
|
||||||
border-radius: 8px;
|
background-color: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||||
|
margin: 30rpx;
|
||||||
|
padding: 30rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.course-icon {
|
.course-icon {
|
||||||
@ -202,26 +313,89 @@ onMounted(() => {
|
|||||||
background-color: rgba(64, 128, 255, 0.1);
|
background-color: rgba(64, 128, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.record-stats {
|
.search-section {
|
||||||
.section-title {
|
background-color: #fff;
|
||||||
font-size: 16px;
|
border-radius: 16rpx;
|
||||||
font-weight: bold;
|
padding: 30rpx;
|
||||||
color: #333;
|
margin-left: 30rpx;
|
||||||
margin-bottom: 15px;
|
margin-right: 30rpx;
|
||||||
padding-left: 10px;
|
margin-bottom: 20rpx;
|
||||||
border-left: 4px solid #4080ff;
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.records-grid {
|
.search-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
gap: 20rpx;
|
||||||
gap: 15px;
|
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 {
|
.record-card {
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -340,15 +514,43 @@ onMounted(() => {
|
|||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 60px 20px;
|
padding: 100rpx 0;
|
||||||
color: #ccc;
|
}
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
font-size: 80rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.empty-text {
|
.empty-text {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 15px;
|
font-size: 32rpx;
|
||||||
font-size: 16px;
|
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 {
|
.bottom-actions {
|
||||||
@ -382,3 +584,4 @@ onMounted(() => {
|
|||||||
.font-bold { font-weight: bold; }
|
.font-bold { font-weight: bold; }
|
||||||
.cor-999 { color: #999; }
|
.cor-999 { color: #999; }
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@ -11,27 +11,43 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="dm-time">
|
<view class="dm-time">
|
||||||
<u-icon name="clock" color="#666" size="14"></u-icon>
|
<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>
|
</view>
|
||||||
|
|
||||||
<!-- 考勤统计 -->
|
<!-- 考勤统计 -->
|
||||||
<view class="attendance-stats">
|
<view class="attendance-stats">
|
||||||
<view class="stats-grid">
|
<view class="stats-grid">
|
||||||
<view class="stat-item total">
|
<view
|
||||||
<view class="stat-number">{{ dmRecord.zrs || 0 }}</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 class="stat-label">总人数</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat-item present">
|
<view
|
||||||
<view class="stat-number">{{ dmRecord.sdRs || 0 }}</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 class="stat-label">实到</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat-item leave">
|
<view
|
||||||
<view class="stat-number">{{ dmRecord.qjRs || 0 }}</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 class="stat-label">请假</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat-item absent">
|
<view
|
||||||
<view class="stat-number">{{ dmRecord.qqRs || 0 }}</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 class="stat-label">缺勤</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -39,97 +55,95 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 学生列表 -->
|
<!-- 学生列表 -->
|
||||||
<view class="student-section mx-15 mb-15">
|
<view class="student-list mb-15 white-bg-color">
|
||||||
<view class="section-header">
|
<view class="student-grid">
|
||||||
<text class="section-title">学生列表</text>
|
|
||||||
<view class="filter-tabs">
|
|
||||||
<view
|
<view
|
||||||
v-for="tab in filterTabs"
|
v-for="(xs, index) in filteredStudentList"
|
||||||
:key="tab.value"
|
:key="index"
|
||||||
class="filter-tab"
|
class="student-item bg-white r-md p-12"
|
||||||
:class="{ active: currentFilter === tab.value }"
|
|
||||||
@click="setFilter(tab.value)"
|
|
||||||
>
|
>
|
||||||
{{ tab.label }}
|
<view class="student-content">
|
||||||
</view>
|
<!-- 第一行:头像和学生信息 -->
|
||||||
</view>
|
<view class="top-row">
|
||||||
</view>
|
<view class="avatar-container">
|
||||||
|
|
||||||
<view class="student-list">
|
|
||||||
<view
|
|
||||||
v-for="(student, index) in filteredStudents"
|
|
||||||
:key="student.id"
|
|
||||||
class="student-item"
|
|
||||||
>
|
|
||||||
<view class="student-avatar">
|
|
||||||
<image
|
<image
|
||||||
v-if="student.tx || student.xstx"
|
class="student-avatar"
|
||||||
:src="getImageUrl(student.tx || student.xstx)"
|
:src="getImageUrl(xs.tx || xs.xstx)"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
class="avatar-img"
|
></image>
|
||||||
/>
|
|
||||||
<view v-else class="avatar-text">{{ (student.xsXm || student.xsxm)?.charAt(0) || '学' }}</view>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="student-info">
|
<view class="student-info">
|
||||||
<view class="student-name">{{ student.xsXm || student.xsxm }}</view>
|
<text class="student-name">{{ xs.xsXm || xs.xsxm }}</text>
|
||||||
<view class="student-class">{{ student.njmc }} {{ student.bjmc }}</view>
|
<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>
|
||||||
<view class="student-status">
|
|
||||||
<text :class="getStatusClass(student.xsZt || student.xszt)">
|
|
||||||
{{ getStatusText(student.xsZt || student.xszt) }}
|
|
||||||
</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 空状态 -->
|
<!-- 媒体预览 -->
|
||||||
<view v-if="filteredStudents.length === 0" class="empty-state">
|
<view v-if="safeDmRecord.zp || safeDmRecord.sp" class="media-section mx-15 mb-15">
|
||||||
<u-icon name="info-circle" color="#ccc" size="48"></u-icon>
|
<DmPsPreview
|
||||||
<text class="empty-text">暂无学生数据</text>
|
:zp="safeDmRecord.zp"
|
||||||
</view>
|
:sp="safeDmRecord.sp"
|
||||||
|
/>
|
||||||
</view>
|
</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>
|
<button class="back-btn" @click="goBack">返回</button>
|
||||||
</view>
|
</view>
|
||||||
|
</template>
|
||||||
</BasicLayout>
|
</BasicLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from "vue";
|
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 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 { BASE_IMAGE_URL } from "@/config";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
const { getJs } = useUserStore();
|
const { getData, setXs } = useDataStore();
|
||||||
const { getData } = useDataStore();
|
|
||||||
|
|
||||||
const js = computed(() => getJs);
|
|
||||||
const xkkc = computed(() => getData);
|
const xkkc = computed(() => getData);
|
||||||
const dmRecord = computed(() => getData?.dmRecord || {});
|
const dmRecord = computed(() => getData?.dmRecord || {});
|
||||||
|
|
||||||
// 学生列表数据
|
// 确保 dmRecord 有默认值,避免 undefined 错误
|
||||||
const studentList = ref<any[]>([]);
|
const safeDmRecord = computed(() => {
|
||||||
const currentFilter = ref<string>('all');
|
return dmRecord.value || {};
|
||||||
|
});
|
||||||
|
|
||||||
// 筛选选项
|
// 响应式数据
|
||||||
const filterTabs = ref([
|
const allStudentList = ref<any[]>([]); // 存储所有学生数据
|
||||||
{ label: '全部', value: 'all' },
|
const currentFilter = ref<string>('all'); // 默认选择总人数
|
||||||
{ label: '正常', value: 'A' },
|
|
||||||
{ label: '请假', value: 'B' },
|
|
||||||
{ label: '缺勤', value: 'C' }
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 筛选后的学生列表
|
// 筛选后的学生列表
|
||||||
const filteredStudents = computed(() => {
|
const filteredStudentList = computed(() => {
|
||||||
if (currentFilter.value === 'all') {
|
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
|
(student.xsZt || student.xszt) === currentFilter.value
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -137,14 +151,24 @@ const filteredStudents = computed(() => {
|
|||||||
// 格式化日期时间
|
// 格式化日期时间
|
||||||
const formatDateTime = (dateTime: string | Date) => {
|
const formatDateTime = (dateTime: string | Date) => {
|
||||||
if (!dateTime) return '';
|
if (!dateTime) return '';
|
||||||
|
try {
|
||||||
return dayjs(dateTime).format('MM-DD HH:mm');
|
return dayjs(dateTime).format('MM-DD HH:mm');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('日期格式化错误:', error);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取图片完整URL
|
// 获取图片完整URL
|
||||||
const getImageUrl = (path: string) => {
|
const getImageUrl = (path: string) => {
|
||||||
if (!path) return '';
|
if (!path) return '';
|
||||||
|
try {
|
||||||
if (path.startsWith('http')) return path;
|
if (path.startsWith('http')) return path;
|
||||||
return BASE_IMAGE_URL + path;
|
return BASE_IMAGE_URL + path;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('图片URL处理错误:', error);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取状态样式类
|
// 获取状态样式类
|
||||||
@ -175,46 +199,65 @@ const setFilter = (filter: string) => {
|
|||||||
currentFilter.value = filter;
|
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 = () => {
|
const goBack = () => {
|
||||||
|
// @ts-ignore
|
||||||
uni.navigateBack();
|
uni.navigateBack();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载学生列表
|
// 加载学生列表
|
||||||
const loadStudentList = async () => {
|
const loadStudentList = async () => {
|
||||||
try {
|
try {
|
||||||
uni.showLoading({ title: '加载中...' });
|
const res = await getDmXsListApi({
|
||||||
|
dmId: safeDmRecord.value.id
|
||||||
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'
|
|
||||||
});
|
});
|
||||||
|
if (res) {
|
||||||
|
allStudentList.value = res?.result || [];
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载学生列表失败:', error);
|
console.error("获取数据失败:", error);
|
||||||
studentList.value = [];
|
allStudentList.value = [];
|
||||||
uni.showToast({
|
|
||||||
title: '加载学生列表失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
uni.hideLoading();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 生命周期
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
console.log("dmXsList 组件初始化:", {
|
||||||
|
xkkc: xkkc.value,
|
||||||
|
dmRecord: safeDmRecord.value,
|
||||||
|
hasData: !!getData
|
||||||
|
});
|
||||||
|
|
||||||
|
// 自动加载数据
|
||||||
loadStudentList();
|
loadStudentList();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -250,34 +293,80 @@ onMounted(() => {
|
|||||||
.stats-grid {
|
.stats-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
gap: 15px;
|
gap: 20rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-item {
|
.stat-item {
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 20px 15px;
|
padding: 20rpx 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
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 {
|
&.total {
|
||||||
border: 2px solid #666;
|
border: 2px solid #666;
|
||||||
.stat-number { color: #333; }
|
.stat-number { color: #333; }
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: #4080ff;
|
||||||
|
background: rgba(64, 128, 255, 0.05);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.present {
|
&.present {
|
||||||
border: 2px solid #2879ff;
|
border: 2px solid #52c41a;
|
||||||
.stat-number { color: #2879ff; }
|
.stat-number { color: #52c41a; }
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: #4080ff;
|
||||||
|
background: rgba(64, 128, 255, 0.05);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.leave {
|
&.leave {
|
||||||
border: 2px solid #ff9900;
|
border: 2px solid #ff9900;
|
||||||
.stat-number { color: #ff9900; }
|
.stat-number { color: #ff9900; }
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: #4080ff;
|
||||||
|
background: rgba(64, 128, 255, 0.05);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.absent {
|
&.absent {
|
||||||
border: 2px solid #ff4d4f;
|
border: 2px solid #ff4d4f;
|
||||||
.stat-number { color: #ff4d4f; }
|
.stat-number { color: #ff4d4f; }
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: #4080ff;
|
||||||
|
background: rgba(64, 128, 255, 0.05);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-number {
|
.stat-number {
|
||||||
@ -293,135 +382,127 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.student-section {
|
.media-section {
|
||||||
.section-header {
|
background: white;
|
||||||
display: flex;
|
border-radius: 8px;
|
||||||
justify-content: space-between;
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||||
align-items: center;
|
overflow: hidden;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.student-list {
|
.student-list {
|
||||||
display: flex;
|
padding: 0 15px;
|
||||||
flex-direction: column;
|
}
|
||||||
|
|
||||||
|
.student-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.student-item {
|
.student-item {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.student-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 15px;
|
gap: 12px;
|
||||||
background: white;
|
}
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
.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 {
|
.student-avatar {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
overflow: hidden;
|
background-color: #f5f5f5;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.student-info {
|
.student-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.student-name {
|
.student-name {
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: bold;
|
||||||
color: #333;
|
color: #333;
|
||||||
margin-bottom: 4px;
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.student-class {
|
.student-class {
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.student-status {
|
.bottom-row {
|
||||||
.status-normal {
|
display: flex;
|
||||||
color: #2879ff;
|
justify-content: space-between;
|
||||||
background: rgba(40, 121, 255, 0.1);
|
align-items: center;
|
||||||
padding: 4px 8px;
|
gap: 5px;
|
||||||
border-radius: 4px;
|
}
|
||||||
|
|
||||||
|
.status-tag {
|
||||||
font-size: 12px;
|
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 {
|
.status-leave {
|
||||||
color: #ff9900;
|
color: #ff9900;
|
||||||
background: rgba(255, 153, 0, 0.1);
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-absent {
|
.status-absent {
|
||||||
color: #ff4d4f;
|
color: #ff4d4f;
|
||||||
background: rgba(255, 77, 79, 0.1);
|
}
|
||||||
padding: 4px 8px;
|
|
||||||
|
.contact-parent {
|
||||||
|
padding: 3px 8px;
|
||||||
border-radius: 4px;
|
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 {
|
&:active {
|
||||||
text-align: center;
|
background-color: rgba(64, 128, 255, 0.15);
|
||||||
padding: 60px 20px;
|
transform: scale(0.95);
|
||||||
color: #ccc;
|
|
||||||
|
|
||||||
.empty-text {
|
|
||||||
display: block;
|
|
||||||
margin-top: 15px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -449,10 +530,18 @@ onMounted(() => {
|
|||||||
.white-bg-color { background-color: white; }
|
.white-bg-color { background-color: white; }
|
||||||
.r-md { border-radius: 8px; }
|
.r-md { border-radius: 8px; }
|
||||||
.p-15 { padding: 15px; }
|
.p-15 { padding: 15px; }
|
||||||
|
.p-12 { padding: 12px; }
|
||||||
.flex-row { display: flex; flex-direction: row; }
|
.flex-row { display: flex; flex-direction: row; }
|
||||||
|
.flex-center { display: flex; align-items: center; justify-content: center; }
|
||||||
.items-center { align-items: center; }
|
.items-center { align-items: center; }
|
||||||
.font-16 { font-size: 16px; }
|
.font-16 { font-size: 16px; }
|
||||||
.font-14 { font-size: 14px; }
|
.font-14 { font-size: 14px; }
|
||||||
|
.font-12 { font-size: 12px; }
|
||||||
.font-bold { font-weight: bold; }
|
.font-bold { font-weight: bold; }
|
||||||
.cor-999 { color: #999; }
|
.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>
|
</style>
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export const useDataStore = defineStore({
|
|||||||
xxts: {}, // 添加xxts字段
|
xxts: {}, // 添加xxts字段
|
||||||
global: {},
|
global: {},
|
||||||
file: {},
|
file: {},
|
||||||
|
xs: {}, // 学生专用
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
getData(): any {
|
getData(): any {
|
||||||
@ -25,6 +26,9 @@ export const useDataStore = defineStore({
|
|||||||
getFile(): any {
|
getFile(): any {
|
||||||
return this.file;
|
return this.file;
|
||||||
},
|
},
|
||||||
|
getXs(): any {
|
||||||
|
return this.xs;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setData(data: any) {
|
setData(data: any) {
|
||||||
@ -42,6 +46,9 @@ export const useDataStore = defineStore({
|
|||||||
setFile(data: any) {
|
setFile(data: any) {
|
||||||
this.file = data;
|
this.file = data;
|
||||||
},
|
},
|
||||||
|
setXs(data: any) {
|
||||||
|
this.xs = data;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
persist: {
|
persist: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user