<template>
|
<view class="training-record">
|
<!-- 使用通用页面头部组件 -->
|
<PageHeader title="培训记录"
|
@back="goBack" />
|
<!-- 搜索和筛选区域 -->
|
<view class="search-section">
|
<view class="search-bar">
|
<view class="search-input">
|
<up-input class="search-text"
|
placeholder="人员名称搜索"
|
v-model="searchForm.searchText"
|
@change="searchName"
|
clearable />
|
</view>
|
<view class="filter-button"
|
@click="searchName">
|
<u-icon name="search"
|
size="24"
|
color="#999"></u-icon>
|
</view>
|
</view>
|
</view>
|
<!-- 人员卡片列表 -->
|
<view class="user-card-list"
|
v-if="userList.length > 0">
|
<view v-for="(user, index) in userList"
|
:key="index"
|
class="user-card">
|
<!-- 卡片头部 -->
|
<view class="card-header"
|
@click="toggleUserCard(index)">
|
<view class="header-left">
|
<text class="user-name">{{ user.nickName }}</text>
|
<text class="user-dept">所属:{{ user.deptNames || '-' }}</text>
|
<text class="user-dept">联系方式:{{ user.phonenumber || '-' }}</text>
|
<!-- 培训统计信息 -->
|
</view>
|
<u-icon :name="expandedUsers[index] ? 'arrow-up' : 'arrow-down'"
|
size="20"
|
color="#999"></u-icon>
|
</view>
|
<!-- 卡片内容(折叠部分) -->
|
<view class="card-content"
|
v-if="expandedUsers[index]">
|
<!-- 年份筛选 -->
|
<view class="year-filter-section">
|
<!-- <text class="filter-label">年份筛选</text> -->
|
<view class="year-options">
|
<u-tag v-for="year in yearOptions"
|
:key="year"
|
:text="year"
|
:type="userYearFilters[user.userId] === year.toString() ? 'primary' : 'info'"
|
@click="() => {
|
userYearFilters[user.userId] = year.toString();
|
filterUserCourses(user.userId);
|
}"
|
:class="{ active: userYearFilters[user.userId] === year.toString() }"
|
style="margin-right: 8px; margin-bottom: 8px;"></u-tag>
|
</view>
|
</view>
|
<!-- 培训课程列表 -->
|
<view class="course-list"
|
v-if="userCourses[user.userId] && userCourses[user.userId].length > 0">
|
<view class="user-stats"
|
v-if="userStats[user.userId]">
|
<text class="stat-item">培训次数: {{ userStats[user.userId].total }}</text>
|
<text class="stat-item success">合格: {{ userStats[user.userId].qualified }}</text>
|
<text class="stat-item danger">不合格: {{ userStats[user.userId].unqualified }}</text>
|
</view>
|
<view v-for="(course, courseIndex) in userCourses[user.userId]"
|
:key="courseIndex">
|
<view class="course-item"
|
v-if="userYearFilters[user.userId] === '全部' || course.trainingDate.includes(userYearFilters[user.userId])">
|
<view class="course-header">
|
<text class="course-date">{{ course.trainingDate || '-' }}</text>
|
<u-tag :type="course.examinationResults === '合格' ? 'success' : 'error'">
|
{{ course.examinationResults || '-' }}
|
</u-tag>
|
</view>
|
<view class="course-info">
|
<text class="info-label">培训内容:</text>
|
<text class="info-value">{{ course.trainingContent || '-' }}</text>
|
</view>
|
<view class="course-info">
|
<text class="info-label">培训课时:</text>
|
<text class="info-value">{{ course.classHour || '-' }}</text>
|
</view>
|
</view>
|
</view>
|
<!-- 筛选后无数据 -->
|
<view v-if="userYearFilters[user.userId] === '全部' ? userCourses[user.userId].length === 0 : userCourses[user.userId].filter(c => c.trainingDate.includes(userYearFilters[user.userId])).length === 0"
|
class="empty-course">
|
<text class="empty-text">{{ userYearFilters[user.userId] === '全部' ? '暂无培训记录' : '该年份暂无培训记录' }}</text>
|
</view>
|
</view>
|
<!-- 空状态 -->
|
<view v-else
|
class="empty-course">
|
<text class="empty-text">暂无培训记录</text>
|
</view>
|
</view>
|
<!-- 导出按钮 -->
|
<!-- <view class="course-export">
|
<u-button type="primary"
|
size="small"
|
@click="exportUserRecord(user.userId)">导出记录</u-button>
|
</view> -->
|
</view>
|
</view>
|
<view v-else
|
class="empty-state">
|
<up-icon name="people"
|
size="64"
|
color="#c0c4cc"></up-icon>
|
<text class="empty-text">暂无人员数据</text>
|
</view>
|
<!-- 空状态 -->
|
<!-- 年份选择器 -->
|
<up-datetime-picker :show="yearPickerVisible"
|
mode="year"
|
@confirm="handleYearConfirm"
|
@cancel="yearPickerVisible = false"
|
title="选择年份" />
|
</view>
|
</template>
|
|
<script setup>
|
import { ref, onMounted, reactive } from "vue";
|
import { onShow } from "@dcloudio/uni-app";
|
import PageHeader from "@/components/PageHeader.vue";
|
import { safeTrainingDetailListPage } from "@/api/safeProduction/safetyTrainingAssessment";
|
import { userListNoPage } from "@/api/system/user.js";
|
import { getToken } from "@/utils/auth";
|
import config from "@/config";
|
|
// 页面状态
|
const userList = ref([]);
|
const userCourses = ref({}); // 存储每个用户的培训课程
|
const userStats = ref({}); // 存储每个用户的培训统计信息
|
const expandedUsers = ref([]); // 控制用户卡片展开状态
|
const loading = ref(false);
|
const courseLoading = ref({}); // 控制每个用户的课程加载状态
|
const userYearFilters = ref({}); // 存储每个用户的年份筛选条件
|
const yearOptions = ref([]); // 年份选项(今年和过去三年)
|
|
// 搜索表单
|
const searchForm = reactive({
|
searchText: "",
|
invoiceDate: "",
|
});
|
|
// 年份选择器状态
|
const yearPickerVisible = ref(false);
|
|
// 返回上一页
|
const goBack = () => {
|
uni.navigateBack();
|
};
|
|
// 生成年份选项
|
const generateYearOptions = () => {
|
const currentYear = new Date().getFullYear();
|
const options = [];
|
// 添加"全部"选项
|
options.push("全部");
|
for (let i = 0; i < 4; i++) {
|
options.push(currentYear - i);
|
}
|
yearOptions.value = options;
|
};
|
|
// 搜索人员名称
|
const searchName = () => {
|
getUserList();
|
};
|
|
// 显示年份选择器
|
const showYearPicker = () => {
|
yearPickerVisible.value = true;
|
};
|
|
// 处理年份选择确认
|
const handleYearConfirm = e => {
|
searchForm.invoiceDate = e.value;
|
yearPickerVisible.value = false;
|
};
|
|
// 按年份搜索
|
const searchDate = () => {
|
// 遍历所有展开的用户卡片,重新加载培训记录
|
userList.value.forEach((user, index) => {
|
if (expandedUsers.value[index]) {
|
getUserCourses(user.userId, index);
|
}
|
});
|
};
|
|
// 获取人员列表
|
const getUserList = () => {
|
loading.value = true;
|
userListNoPage()
|
.then(res => {
|
loading.value = false;
|
if (res.data && res.data.length > 0) {
|
let users = res.data;
|
// 如果有搜索关键词,进行筛选
|
if (searchForm.searchText) {
|
users = users.filter(user =>
|
user.nickName.includes(searchForm.searchText)
|
);
|
}
|
userList.value = users;
|
// 初始化所有卡片为收起状态
|
expandedUsers.value = new Array(userList.value.length).fill(false);
|
} else {
|
userList.value = [];
|
expandedUsers.value = [];
|
}
|
})
|
.catch(() => {
|
loading.value = false;
|
uni.showToast({ title: "获取人员列表失败", icon: "none" });
|
});
|
};
|
|
// 切换用户卡片展开状态
|
const toggleUserCard = index => {
|
const user = userList.value[index];
|
expandedUsers.value[index] = !expandedUsers.value[index];
|
|
// 如果展开卡片,加载培训记录
|
if (expandedUsers.value[index]) {
|
// 初始化年份筛选条件为"全部"
|
userYearFilters.value[user.userId] = "全部";
|
getUserCourses(user.userId, index);
|
}
|
};
|
|
// 筛选用户培训课程
|
const filterUserCourses = userId => {
|
if (!userYearFilters.value[userId]) return;
|
console.log("userYearFilters", userYearFilters.value);
|
const year = userYearFilters.value[userId];
|
const allCourses = userCourses.value[userId] || [];
|
|
// 筛选指定年份的培训记录
|
let filteredCourses = allCourses;
|
if (year !== "全部") {
|
filteredCourses = allCourses.filter(
|
course => course.trainingDate && course.trainingDate.includes(year)
|
);
|
}
|
console.log("filteredCourses", filteredCourses);
|
|
// 更新统计信息
|
const total = filteredCourses.length;
|
const qualified = filteredCourses.filter(
|
course => course.examinationResults === "合格"
|
).length;
|
const unqualified = filteredCourses.filter(
|
course => course.examinationResults === "不合格"
|
).length;
|
userStats.value[userId] = {
|
total,
|
qualified,
|
unqualified,
|
};
|
};
|
|
// 获取用户培训课程
|
const getUserCourses = (userId, index) => {
|
courseLoading.value[userId] = true;
|
|
const params = {
|
userId,
|
// 如果有年份筛选,添加年份参数
|
// 这里需要根据后端接口的实际参数名进行调整
|
};
|
|
safeTrainingDetailListPage(params)
|
.then(res => {
|
courseLoading.value[userId] = false;
|
if (res.data && res.data.records) {
|
let courses = res.data.records;
|
// 如果有年份筛选,进行筛选
|
if (searchForm.invoiceDate) {
|
const year = searchForm.invoiceDate.substring(0, 4);
|
courses = courses.filter(
|
course => course.trainingDate && course.trainingDate.includes(year)
|
);
|
}
|
userCourses.value[userId] = courses;
|
|
// 计算培训统计信息
|
const total = courses.length;
|
const qualified = courses.filter(
|
course => course.examinationResults === "合格"
|
).length;
|
const unqualified = courses.filter(
|
course => course.examinationResults === "不合格"
|
).length;
|
|
userStats.value[userId] = {
|
total,
|
qualified,
|
unqualified,
|
};
|
} else {
|
userCourses.value[userId] = [];
|
userStats.value[userId] = {
|
total: 0,
|
qualified: 0,
|
unqualified: 0,
|
};
|
}
|
})
|
.catch(() => {
|
courseLoading.value[userId] = false;
|
uni.showToast({ title: "获取培训记录失败", icon: "none" });
|
});
|
};
|
|
// 页面加载
|
onMounted(() => {
|
// 生成年份选项
|
generateYearOptions();
|
getUserList();
|
});
|
|
// 页面显示时刷新
|
onShow(() => {
|
// 可以在这里添加刷新逻辑
|
});
|
</script>
|
|
<style scoped lang="scss">
|
@import "../../../styles/sales-common.scss";
|
.training-record {
|
min-height: 100vh;
|
background: #f8f9fa;
|
padding-bottom: 20px;
|
}
|
|
// 年份选择器
|
.year-picker {
|
flex: 1;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
padding: 10px 15px;
|
background: #f8f9fa;
|
border-radius: 4px;
|
margin-right: 10px;
|
}
|
|
.picker-text {
|
font-size: 14px;
|
color: #333;
|
}
|
|
// 人员卡片列表
|
.user-card-list {
|
padding: 10px;
|
}
|
|
// 人员卡片
|
.user-card {
|
background: #fff;
|
border-radius: 8px;
|
margin-bottom: 12px;
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
overflow: hidden;
|
border: 1px solid #e8e8e8;
|
}
|
|
// 卡片头部
|
.card-header {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
padding: 16px;
|
background: #f5f7fa;
|
border-bottom: 1px solid #e8e8e8;
|
cursor: pointer;
|
}
|
|
.header-left {
|
display: flex;
|
flex-direction: column;
|
}
|
|
.user-name {
|
font-size: 16px;
|
font-weight: 600;
|
color: #333;
|
margin-bottom: 20rpx;
|
}
|
|
.user-dept {
|
font-size: 14px;
|
color: #666;
|
margin-bottom: 8px;
|
}
|
|
// 培训统计信息
|
.user-stats {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 10px;
|
margin-bottom: 8px;
|
}
|
|
.stat-item {
|
font-size: 12px;
|
color: #555;
|
padding: 5px 10px;
|
background: #f0f2f5;
|
border-radius: 4px;
|
border: 1px solid #e0e0e0;
|
font-weight: 500;
|
}
|
|
.stat-item.success {
|
background: #e6f7ff;
|
color: #1890ff;
|
border-color: #91d5ff;
|
}
|
|
.stat-item.danger {
|
background: #fff1f0;
|
color: #ff4d4f;
|
border-color: #ffccc7;
|
}
|
|
// 年份筛选区域
|
.year-filter-section {
|
margin-bottom: 16px;
|
padding-bottom: 12px;
|
border-bottom: 1px solid #e8e8e8;
|
}
|
|
.filter-label {
|
font-size: 14px;
|
font-weight: 500;
|
color: #333;
|
margin-bottom: 8px;
|
display: block;
|
}
|
|
.year-options {
|
display: flex;
|
flex-wrap: wrap;
|
}
|
|
// 卡片内容
|
.card-content {
|
padding: 16px;
|
}
|
|
// 培训课程列表
|
.course-list {
|
margin-bottom: 16px;
|
}
|
|
// 课程项
|
.course-item {
|
background: #fafafa;
|
border-radius: 6px;
|
padding: 14px;
|
margin-bottom: 12px;
|
border: 1px solid #e8e8e8;
|
}
|
|
// 课程头部
|
.course-header {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 12px;
|
padding-bottom: 8px;
|
border-bottom: 1px solid #f0f0f0;
|
}
|
|
.course-date {
|
font-size: 14px;
|
font-weight: 600;
|
color: #333;
|
}
|
|
// 课程信息
|
.course-info {
|
display: flex;
|
margin-bottom: 8px;
|
align-items: flex-start;
|
}
|
|
.info-label {
|
font-size: 14px;
|
color: #666;
|
width: 80px;
|
flex-shrink: 0;
|
}
|
|
.info-value {
|
font-size: 14px;
|
color: #333;
|
flex: 1;
|
line-height: 1.4;
|
}
|
|
// 课程导出按钮
|
.course-export {
|
display: flex;
|
justify-content: flex-end;
|
}
|
|
// 空状态
|
.empty-state {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
padding: 60px 0;
|
}
|
|
.empty-course {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
padding: 30px 0;
|
color: #999;
|
font-size: 14px;
|
}
|
|
.empty-text {
|
font-size: 14px;
|
color: #999;
|
margin-top: 16px;
|
}
|
:deep(.u-tag--info) {
|
background-color: #c1c3c8;
|
border-width: 1px;
|
border-color: #c1c3c8;
|
}
|
</style>
|