2025-10-24 21:29:01 +08:00

591 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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

<template>
<!-- 使用 BasicListLayout 包裹记录列表 -->
<BasicListLayout @register="register" :fixed="true" class="flex-1">
<template #top>
<!-- 课程信息卡片 -->
<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>
</view>
<text class="font-16 font-bold">{{ xkkc.xkkcMc || xkkc.kcmc }}</text>
<text class="font-14 cor-999 ml-10">{{ todayInfo.date }} ({{ todayInfo.weekName }})</text>
</view>
</view>
<!-- 搜索筛选区域 -->
<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(data.dmTime) }}</text>
</view>
</view>
<view class="record-stats">
<view class="stat-item">
<text class="stat-number">{{ data.zrs || 0 }}</text>
<text class="stat-label">总人数</text>
</view>
<view class="stat-item present">
<text class="stat-number">{{ data.sdRs || 0 }}</text>
<text class="stat-label">实到</text>
</view>
<view class="stat-item leave">
<text class="stat-number">{{ data.qjRs || 0 }}</text>
<text class="stat-label">请假</text>
</view>
<view class="stat-item absent">
<text class="stat-number">{{ data.qqRs || 0 }}</text>
<text class="stat-label">缺勤</text>
</view>
<view class="stat-item overtime">
<text class="stat-number">{{ data.cdRs || 0 }}</text>
<text class="stat-label">迟到</text>
</view>
</view>
<view class="record-footer">
<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>
</template>
<template #bottom>
<!-- 返回按钮 -->
<view class="bottom-actions mx-15 mb-15">
<button class="back-btn" @click="goBack">返回</button>
</view>
</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 { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
import { getXkDmPageApi } from "@/api/base/xkApi";
import dayjs from "dayjs";
const { getJs } = useUserStore();
const { getData, setData } = useDataStore();
const js = computed(() => getJs);
const xkkc = computed(() => getData);
// 今日信息
const now = dayjs();
let wDay = now.day();
if (wDay === 0) {
wDay = 7;
}
const wdNameList = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
const todayInfo = ref({
date: now.format("YYYY-MM-DD"),
weekName: wdNameList[wDay - 1],
});
// 响应式数据
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) => {
if (!dateTime) return '';
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) {
case 'A': return 'status-active';
case 'B': return 'status-pending';
case 'C': return 'status-cancelled';
default: return 'status-active';
}
};
// 获取记录状态文本
const getRecordStatusText = (status: string) => {
switch (status) {
case 'A': return '正常';
case 'B': return '待处理';
case 'C': return '已取消';
default: return '正常';
}
};
// 查看记录详情
const viewRecordDetail = (record: any) => {
// 将记录信息存储到store中
setData({
...xkkc.value,
dmRecord: record
});
// 跳转到详情页面
uni.navigateTo({
url: '/pages/view/routine/xk/dmXsList'
});
};
// 返回上一页
const goBack = () => {
uni.navigateBack();
};
// 生命周期
onMounted(() => {
// 设置默认时间范围为最近一周
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 {
background-color: #fff;
border-radius: 16rpx;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
margin: 30rpx;
padding: 30rpx;
}
.course-icon {
width: 30px;
height: 30px;
border-radius: 4px;
background-color: rgba(64, 128, 255, 0.1);
}
.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);
}
.search-row {
display: flex;
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;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.record-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
.record-time {
display: flex;
align-items: center;
gap: 5px;
.time-text {
font-size: 14px;
color: #666;
}
}
.record-status {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
&.status-active {
background: rgba(40, 121, 255, 0.1);
color: #2879ff;
}
&.status-pending {
background: rgba(255, 153, 0, 0.1);
color: #ff9900;
}
&.status-cancelled {
background: rgba(255, 77, 79, 0.1);
color: #ff4d4f;
}
}
}
.record-stats {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 10px;
margin-bottom: 15px;
.stat-item {
text-align: center;
padding: 10px 5px;
border-radius: 8px;
background: #f8f9fa;
&.present {
background: rgba(40, 121, 255, 0.1);
.stat-number { color: #2879ff; }
}
&.leave {
background: rgba(255, 153, 0, 0.1);
.stat-number { color: #ff9900; }
}
&.absent {
background: rgba(255, 77, 79, 0.1);
.stat-number { color: #ff4d4f; }
}
.stat-number {
font-size: 18px;
font-weight: bold;
color: #333;
display: block;
margin-bottom: 4px;
}
.stat-label {
font-size: 12px;
color: #666;
}
}
}
.record-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 15px;
border-top: 1px solid #f0f0f0;
.teacher-name {
font-size: 14px;
color: #666;
}
.view-detail {
display: flex;
align-items: center;
gap: 5px;
.detail-text {
font-size: 14px;
color: #4080ff;
}
}
}
}
.empty-state {
text-align: center;
padding: 100rpx 0;
}
.empty-icon {
font-size: 80rpx;
margin-bottom: 20rpx;
}
.empty-text {
display: block;
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 {
.back-btn {
width: 100%;
height: 44px;
background: #4080ff;
color: white;
border: none;
border-radius: 22px;
font-size: 16px;
font-weight: 500;
}
}
// 工具类
.mx-15 { margin-left: 15px; margin-right: 15px; }
.my-15 { margin-top: 15px; margin-bottom: 15px; }
.mb-15 { margin-bottom: 15px; }
.mb-30 { margin-bottom: 30px; }
.mr-10 { margin-right: 10px; }
.ml-10 { margin-left: 10px; }
.bg-white { background-color: white; }
.white-bg-color { background-color: white; }
.r-md { border-radius: 8px; }
.p-15 { padding: 15px; }
.flex-row { display: flex; flex-direction: row; }
.items-center { align-items: center; }
.font-16 { font-size: 16px; }
.font-14 { font-size: 14px; }
.font-bold { font-weight: bold; }
.cor-999 { color: #999; }
</style>