2025-08-11 23:45:13 +08:00

475 lines
10 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>
<view class="record-content">
<!-- 使用 BasicListLayout 包裹记录列表 -->
<BasicListLayout @register="register" :fixed="false" class="flex-1">
<template #top>
<!-- 搜索筛选区域 -->
<view class="search-section">
<view class="search-row">
<view class="search-item">
<text class="label">班级</text>
<view class="flex-1">
<NjBjPicker
@change="changeNjBj"
icon-arrow="right"
:customStyle="{
borderRadius: '0',
padding: '0.5rem 0.625rem',
}"
/>
</view>
</view>
</view>
<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="!loading && dmRecords && dmRecords.length === 0 && hasSearched"
class="empty-state"
>
<view class="empty-icon">📋</view>
<text class="empty-text">暂无点名记录</text>
<text class="empty-tip">请选择班级和时间范围进行搜索</text>
</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="goToDetail(data)">
<view class="card-content">
<view class="info-row">
<text class="info-label">年级</text>
<text class="info-value">{{ data.njmc }}</text>
</view>
<view class="info-row">
<text class="info-label">班级</text>
<text class="info-value">{{ data.bjmc }}</text>
</view>
<view class="info-row">
<text class="info-label">就餐日期</text>
<text class="info-value">{{ formatDate(data.jcTime) }}</text>
</view>
<view class="info-row">
<text class="info-label">就餐时间</text>
<text class="info-value">{{ formatTime(data.jcTime) }}</text>
</view>
</view>
<view class="card-footer">
<text class="view-detail">查看详情 </text>
</view>
</view>
</template>
</BasicListLayout>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from "vue";
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
import NjBjPicker from "@/pages/components/NjBjPicker/index.vue";
import { getJcDmPageApi } from "@/api/base/jcApi";
import { useDataStore } from "@/store/modules/data";
const { setData } = useDataStore();
// 接收外部传入属性
const props = withDefaults(
defineProps<{
title?: string;
}>(),
{
title: "点名列表",
}
);
// 响应式数据
const loading = ref(false);
const startTime = ref("");
const endTime = ref("");
const selectedClass = ref<any>(null);
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 getJcDmPageApi(params);
console.log("API返回数据:", res); // 调试日志
// 确保数据正确赋值
if (res && res.rows) {
dmRecords.value = res.rows;
totalCount.value = res.total || 0;
} else {
dmRecords.value = [];
totalCount.value = 0;
}
return res;
} catch (error) {
console.error("获取数据失败:", error);
dmRecords.value = [];
totalCount.value = 0;
return { rows: [], total: 0 };
}
},
componentProps: {
auto: false,
},
});
// 改变了年级班级
const changeNjBj = async (nj: any, bj: any) => {
selectedClass.value = {
njId: nj.key,
bjId: bj.key,
};
};
const onTimeRangeChange = (e: any) => {
// uni-datetime-picker 返回的是一个数组,包含开始和结束时间
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 (!selectedClass.value || !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("搜索参数:", {
njId: selectedClass.value.njId,
bjId: selectedClass.value.bjId,
startTime: startTime.value + " 00:00:00",
endTime: endTime.value + " 23:59:59",
pageNo: 1,
});
// 设置搜索参数并重新加载
setParam({
njId: selectedClass.value.njId,
bjId: selectedClass.value.bjId,
startTime: startTime.value + " 00:00:00",
endTime: endTime.value + " 23:59:59",
pageNo: 1,
});
reload();
};
const resetSearch = () => {
selectedClass.value = null;
startTime.value = "";
endTime.value = "";
dmRecords.value = [];
totalCount.value = 0;
hasSearched.value = false;
};
const goToDetail = (dm: any) => {
setData(dm);
uni.navigateTo({
url: "/pages/view/routine/jc/detail",
});
};
const exportRecords = () => {
if (dmRecords.value.length === 0) {
uni.showToast({
title: "暂无数据可导出",
icon: "none",
});
return;
}
// 实现导出逻辑
uni.showToast({
title: "导出功能开发中",
icon: "none",
});
};
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 formatTime = (timeStr: string) => {
if (!timeStr) return "";
const date = new Date(timeStr);
return `${String(date.getHours()).padStart(2, "0")}:${String(
date.getMinutes()
).padStart(2, "0")}`;
};
// 生命周期
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 lang="scss" scoped>
.record-content {
display: flex;
flex: 1 0 1px;
}
.search-section {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin: 20rpx 30rpx;
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;
}
}
.export-btn {
font-size: 24rpx;
color: #007aff;
font-weight: normal;
}
.record-card {
border: 1px solid #f0f0f0;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx;
background-color: #fff;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
}
.card-footer {
text-align: right;
padding-top: 16rpx;
}
.view-detail {
font-size: 26rpx;
color: #007aff;
font-weight: normal;
}
.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;
}
.card-content {
margin-bottom: 20rpx;
padding-bottom: 16rpx;
border-bottom: 1px solid #f0f0f0;
}
.info-row {
display: flex;
align-items: center;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
}
.info-label {
font-size: 28rpx;
color: #666;
font-weight: normal;
flex: 0 0 160rpx;
}
.info-value {
font-size: 28rpx;
color: #333;
font-weight: bold;
flex: 1;
}
</style>