Files
fad_oa/ruoyi-ui/src/views/oa/attendance/index.vue
2025-01-02 13:42:09 +08:00

771 lines
22 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>
<div class="app-container" v-loading="loading">
<el-tabs v-model="activeIndex" @tab-click="handleClickTab">
<el-tab-pane label="人员签到表" name="1">
<el-row :gutter="20">
<el-col :span="16">
<el-header class="sign-in-header">
<div class="header-container">
<div class="header-title-section">
<i class="el-icon-date header-icon"></i>
<h2 class="header-title">签到表</h2>
</div>
<el-date-picker
v-model="queryParams.selectTime"
value-format="yyyy-MM-dd"
type="month"
@change="getList()"
placeholder="选择月份"
class="custom-date-picker"
></el-date-picker>
</div>
</el-header>
<div class="table-container">
<table class="attendance-table">
<thead>
<tr>
<th class="name-column">姓名</th>
<th
v-for="(item, index) in dateLength"
:key="index"
:class="selectHead === index + 1 ? 'selected-column' : ''"
@click="selectMany(index + 1)"
>
{{ index + 1 }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(user, userIndex) in userList" :key="userIndex">
<td class="user-name"
:style="{backgroundColor: user.userId===selectUser.userId&&!selectAll?'#e3f2fd':''}">
{{ user.nickName }}
</td>
<td
v-for="(day, dayIndex) in dateLength"
:key="dayIndex"
:style="getCellStyle(user, dayIndex + 1)"
@click="selectAttendDay(user, userIndex, dayIndex + 1)"
>
{{
user.attendances.length > 0 &&
user.attendances.findIndex(i => i.attendanceDay === dayIndex + 1) > -1 &&
(user.attendances[user.attendances.findIndex(i => i.attendanceDay === dayIndex + 1)].projectId === 0 || user.attendances[user.attendances.findIndex(i => i.attendanceDay === dayIndex + 1)].projectId === 1)
? user.attendances[user.attendances.findIndex(i => i.attendanceDay === dayIndex + 1)].projectId === 0 ? '出差' : '请假' :
''
}}
</td>
</tr>
</tbody>
</table>
</div>
<div class="legend">
<div class="legend-item">
<div class="legend-color" style="background-color: #fdf6e4;"></div>
出差
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #f3ff52;"></div>
当前选中
</div>
</div>
</el-col>
<el-col :span="8">
<div>
<el-header class="header-title"
style="display: flex;gap: 10px;flex-direction: column;justify-content: center">
<div>
<i class="el-icon-edit header-icon"></i>
操作栏
</div>
</el-header>
<el-card class="box-card">
<el-button @click="removeAttendance" type="danger" plain>取消操作</el-button>
<el-button @click="toTravel" type="warning" plain>出差</el-button>
<el-button @click="toFree" type="info" plain>请假</el-button>
</el-card>
<el-card class="box-card">
<div slot="" class="">
<span><i class="el-icon-s-order"></i> 项目列表</span>
<!-- <el-button style="float: right; padding: 3px 0" type="text">操作按钮</el-button>-->
</div>
<div style="height: 250px;overflow: auto">
<div v-for="(item,index) in sortedProjects" style="display: flex;justify-content: space-between;">
<el-button class="text" :style="{backgroundColor:item.color===''?'':item.color}"
@click="signIn(item)">
{{ item.projectName }}
</el-button>
<el-color-picker class="color-picker" v-model="item.color"
@change="changeItemColor(item)"></el-color-picker>
</div>
</div>
</el-card>
<el-card class="box-card">
<div slot="" class="">
<span><i class="el-icon-timer"></i>工作时长</span>
<div style="margin: 20px 0 ">
<el-radio-group v-model="timeFlag">
<el-radio :label="0">天计</el-radio>
<el-radio :label="1">小时计</el-radio>
</el-radio-group>
</div>
<el-select v-if="timeFlag===0" v-model="form.dayLength" placeholder="请选择工作时长">
<el-option
v-for="dict in dict.type.work_time_length"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
<el-select v-if="timeFlag===1" v-model="form.hour" placeholder="请选择工作时长">
<el-option
v-for="dict in dict.type.work_time_length_hour"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</div>
</el-card>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="数据分析" name="2">
<el-row :gutter="20" class="mb8">
<el-col :span="2">
<span class="demonstration">计算月份:</span>
</el-col>
<el-col :span="6">
<div class="block">
<el-date-picker
v-model="date"
type="month"
placeholder="选择日期">
</el-date-picker>
</div>
</el-col>
<el-col :span="1.5">
<el-button @click="calcWork"
element-loading-text="正在计算请稍等"
v-loading.fullscreen.lock="fullscreenLoading"
>计算</el-button>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-table
:data="userList"
stripe
style="width: 100%">
<el-table-column
type="index"
label="序号"
width="180">
</el-table-column>
<el-table-column
prop="nickName"
label="员工姓名"
width="180">
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
v-if="calcFlag"
@click="handleCalc(scope.row)"
v-hasPermi="['oa:oaWarehouse:edit']"
>查看考勤结果
</el-button>
<el-button
size="mini"
type="text"
v-else
disabled
icon="el-icon-edit"
@click="handleCalc(scope.row)"
v-hasPermi="['oa:oaWarehouse:edit']"
>请选择日期计算
</el-button>
</template>
</el-table-column>
</el-table>
</el-col>
<el-col :span="12">
<bar-chart :projectList="projects" ref="barChart"></bar-chart>
</el-col>
</el-row>
<el-dialog
title="计算结果"
:visible.sync="showCalc"
width="70%"
>
<div class="container">
<h1>员工个人财务与签到报告 - {{ calcUser.nickName }}</h1>
<!-- 员工基本信息 -->
<div class="employee-info">
<p><strong>员工姓名</strong> {{ calcUser.nickName }}</p>
<p><strong>职位</strong> 员工</p>
</div>
<!-- 月度工作情况表格 -->
<h2>{{ date.getMonth() }} 月度工作签到情况</h2>
<el-descriptions class="margin-top" title="报告详情" :column="3" :size="size" border>
<template slot="extra">
<div>总工作时长{{ calcResultItem.workTimes }}</div>
</template>
<el-descriptions-item>
<template slot="label">
<i class="el-icon-user"></i>
员工姓名
</template>
{{ calcUser.nickName }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">
<i class="el-icon-mobile-phone"></i>
人力成本
</template>
{{ calcUser.laborCost }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">
当月估计未计算小时()
</template>
{{ calcUser.laborCost * calcResultItem.workTimes }}
</el-descriptions-item>
</el-descriptions>
<el-table
:data="calcResultAttendances"
stripe
style="width: 100%">
<el-table-column
prop="projectName"
label="项目名"
width="180">
<template slot-scope="scope">
<div>{{
scope.row.projectId === 0 ? '出差' : scope.row.projectId === 1 ? '请假' : scope.row.projectName
}}
</div>
</template>
</el-table-column>
<el-table-column
prop="count"
label="签到(天)"
width="180">
</el-table-column>
<el-table-column
prop="workTimes"
label="工作时长(天)">
</el-table-column>
<el-table-column
prop="hourWorkTimes"
label="工作时长(小时计)">
</el-table-column>
</el-table>
<div class="footer">
<p style="color: red">小时计与天计为单独记录计算</p>
<p>© 2024 财务与签到报告 | 由公司财务部门生成</p>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="showCalc = false">关闭</el-button>
</span>
</el-dialog>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import {listWorker} from "@/api/system/user";
import {listProject, updateProject} from "@/api/oa/project";
import {listOaAttendance} from "@/api/oa/oaAttendance";
import {
addBatchOaAttendance,
addOaAttendance,
delOaAttendance,
getDateLength,
workCalc,
delOaAttendanceAll
} from "../../../api/oa/oaAttendance";
import item from "../../../layout/components/Sidebar/Item.vue";
import BarChart from "../../dashboard/BarChart.vue";
import {projectData} from "../../../api/oa/project";
export default {
name: "Project",
components: {BarChart},
dicts: ['work_time_length', 'work_time_length_hour'],
data() {
return {
// 选择索引
selectIndex: new Date().getDate(),
// 用户列表
userList: [],
// 计算结果
calcFlag: false,
// 项目列表
projectList: [],
loading: true,
selectHead: new Date().getDate(),
// 当前月的天数
dateLength: 31,
// 计算提交月份
date: new Date(),
// 提交表单
form: {},
showCalc: false,
// 查询参数
userQueryParams: {
pageNum: 1,
pageSize: 10,
userName: undefined,
phonenumber: undefined,
status: undefined,
deptId: undefined
},
calcUser: {},
queryParams: {
pageNum: 1,
pageSize: 50,
selectTime: new Date(),
},
selectArrayIndex: new Date().getDate(),
timeFlag: 0,
// 项目查询参数
projectQueryParams: {
pageNum: 1,
pageSize: 99,
projectStatus: 0,
projectName: undefined,
projectNum: undefined,
beginTime: undefined,
finishTime: undefined
},
selectUser: {},
// 日期范围
dateRange: [],
selectAll: true,
calcResult: [],
calcResultItem: {},
activeIndex: '1',
calcResultUser: {},
calcResultAttendances: [],
calcResultProject: {},
projects: [],
fullscreenLoading:false,
}
},
mounted() {
},
created() {
this.getDate();
this.getProjectData();
this.getList();
},
computed: {
sortedProjects() {
let projectList = [...this.projectList];
projectList.sort((a, b) => {
if (a.color && !b.color) return -1;
if (!a.color && b.color) return 1;
return 0;
});
return projectList;
}
},
watch: {
activeIndex(newVal) {
if (newVal === '2') { // 数据分析 Tab
this.$nextTick(() => {
this.$refs.barChart && this.$refs.barChart.resize();
});
}
}
},
methods: {
// // tag切换方法
// handleClickTab(tag,event){
// if (tag.index===1){
// this.$nextTick(() => {
// console.log("1284u0")
// this.$refs.barChart && this.$refs.barChart.resize(); // 假设你的 BarChart 组件提供了 resize 方法
// });
// }
// },
// 获取单元格的样式
getCellStyle(user, dayIndex) {
const attendanceIndex = user.attendances.findIndex(
(i) => i.attendanceDay === dayIndex
);
if (attendanceIndex > -1) {
const attendance = user.attendances[attendanceIndex];
// 如果存在签到记录,使用对应项目的颜色
return {
backgroundColor: attendance.color || '#fdf6e4',
color: '#000', // 确保文字可读性
};
}
// 如果是当前选中的用户或列,应用高亮样式
if (
this.selectIndex === dayIndex &&
(user.userId === this.selectUser.userId || this.selectAll)
) {
return {
backgroundColor: '#f3ff52',
};
}
// 默认单元格样式
return {
backgroundColor: '#fff',
};
},
// 获取条形图数据
getProjectData() {
projectData().then(res => {
this.projects = res.data;
})
},
// 出差标记
toTravel() {
if (!this.selectAll) {
this.form = {
projectId: 0,
userId: this.selectUser.userId,
attendanceDay: this.selectIndex,
selectTime:this.queryParams.selectTime
}
addOaAttendance(this.form).then(res => {
this.getList()
this.selectUser = this.selectArrayIndex >= this.userList.length - 1 ? this.selectUser : this.userList[this.selectArrayIndex + 1]
})
} else {
this.form = {
projectId: 0,
attendanceDay: this.selectIndex,
selectTime:this.queryParams.selectTime
}
// 集体赋予状态
addBatchOaAttendance(this.form).then(res => {
this.getList()
})
}
},
toFree() {
if (!this.selectAll) {
this.form = {
projectId: 1,
userId: this.selectUser.userId,
attendanceDay: this.selectIndex,
selectTime:this.queryParams.selectTime
}
addOaAttendance(this.form).then(res => {
this.getList()
this.selectUser = this.selectArrayIndex >= this.userList.length - 1 ? this.selectUser : this.userList[this.selectArrayIndex + 1]
})
} else {
this.form = {
projectId: 1,
attendanceDay: this.selectIndex,
selectTime:this.queryParams.selectTime
}
// 集体赋予状态
addBatchOaAttendance(this.form).then(res => {
this.getList()
})
}
},
// 删除签到
removeAttendance() {
if (!this.selectAll) {
const attendanceId = this.selectUser.attendances.find(item => item.attendanceDay === this.selectIndex).id
delOaAttendance(attendanceId).then(res => {
this.$modal.msgSuccess("操作成功");
this.getList();
})
} else {
delOaAttendanceAll(this.selectIndex).then(res => {
this.$modal.msgSuccess("操作成功");
this.getList();
})
}
},
// 查看计算结果
handleCalc(row) {
this.showCalc = true;
this.calcUser = row
this.calcResultItem = this.calcResult.filter(item => item.sysUser.userId === this.calcUser.userId)[0]
this.calcResultAttendances = this.calcResultItem.attendances
},
// 计算
calcWork() {
this.fullscreenLoading = true;
workCalc(this.date).then(res => {
this.calcResult = res.data;
this.calcFlag = true;
this.fullscreenLoading = false;
})
},
getDate() {
getDateLength().then(res => {
this.dateLength = res.data;
})
},
selectAttendDay(item, index, index2) {
this.selectIndex = index2;
this.selectAll = false;
this.selectUser = item;
this.selectHead = index2
this.selectArrayIndex = index
},
selectMany(index) {
this.selectAll = true;
this.selectIndex = index;
this.selectHead = index
},
/** 查询用户列表 */
getList() {
this.loading = true;
listOaAttendance(this.queryParams).then(res => {
this.userList = res.rows;
console.log(this.userList);
this.dateLength = res.total;
this.loading = false;
});
listProject(this.projectQueryParams).then(response => {
this.projectList = response.rows;
this.total = response.total;
})
},
changeItemColor(item) {
updateProject(item).then(res => {
this.getList();
})
},
signIn(project) {
// 一个签到
if (this.form.dayLength === undefined && this.timeFlag === 0) {
this.$message({message: '请选择工作时长', type: 'warning'})
return;
}
if (this.form.hour === undefined && this.timeFlag === 1) {
this.$message({message: '请选择工作时长', type: 'warning'})
return;
}
if (!this.selectAll) {
this.form = {
projectId: project.projectId,
userId: this.selectUser.userId,
attendanceDay: this.selectIndex,
dayLength: this.timeFlag === 0 ? this.form.dayLength : undefined,
hour: this.timeFlag === 1 ? this.form.hour : undefined,
selectTime:this.queryParams.selectTime
}
addOaAttendance(this.form).then(res => {
this.selectUser = this.selectArrayIndex >= this.userList.length - 1 ? this.selectUser : this.userList[this.selectArrayIndex + 1]
this.getList()
})
} else {
this.form = {
projectId: project.projectId,
attendanceDay: this.selectIndex,
dayLength: this.timeFlag === 0 ? this.form.dayLength : undefined,
hour: this.timeFlag === 1 ? this.form.hour : undefined,
selectTime:this.queryParams.selectTime
}
// 集体赋予状态
addBatchOaAttendance(this.form).then(res => {
this.getList()
})
}
},
}
};
</script>
<style scoped>
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
}
.header-title-section {
display: flex;
align-items: center;
gap: 10px;
}
.header-title {
color: #000;
font-size: 22px; /* 更小字体 */
font-weight: 600;
}
.custom-date-picker {
background-color: #ffffff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: background-color 0.3s ease, box-shadow 0.3s ease;
}
.custom-date-picker:hover {
background-color: #f7f7f7;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.table-container {
background: #f9f9f9;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin: 0 auto; /* 居中对齐 */
}
.attendance-table {
width: 100%; /* 表格铺满父容器 */
border-collapse: collapse;
table-layout: fixed; /* 固定表格布局 */
font-size: 12px; /* 调整字体大小 */
}
.attendance-table th,
.attendance-table td {
padding: 3px; /* 缩小单元格内边距 */
border: 1px solid #ddd;
white-space: nowrap;
text-align: center;
overflow: hidden; /* 不隐藏内容 */
font-size: clamp(10px, 0.7vw, 16px); /* 字体动态调整 */
}
.attendance-table th:first-child,
.attendance-table td:first-child {
font-size: 14px; /* 调整字体大小 */
width: 50px; /* 固定名字列宽度 */
white-space: normal; /* 允许换行 */
text-align: left; /* 左对齐方便查看 */
overflow: visible; /* 不隐藏内容 */
}
.name-column {
background-color: #f3f3f3;
font-weight: bold;
}
.selected-column {
background-color: #e3f2fd;
}
.user-name {
background-color: #f0f0f0;
font-weight: bold;
}
/* 鼠标悬停效果 */
.attendance-table td:hover {
background-color: #e8f4ff;
cursor: pointer;
}
/* 出差标记字体颜色 */
.attendance-table td span {
font-size: 12px;
font-weight: bold;
}
/* 图例区域 */
.legend {
margin-top: 10px;
display: flex;
justify-content: flex-start;
gap: 20px;
}
.legend-item {
display: flex;
align-items: center;
}
.legend-color {
width: 16px;
height: 16px;
margin-right: 8px;
border-radius: 3px;
}
.text {
width: 70%;
overflow: hidden;
text-overflow: ellipsis;
}
</style>