pms-front/src/views/worklog/index.vue

672 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="work-log-container">
<!-- 左侧项目列表 -->
<div class="project-list" :class="{ collapsed: isCollapsed }">
<div class="list-header">
<span v-if="!isCollapsed">项目列表</span>
<div class="collapse-button-wrapper">
<el-button
type="text"
@click="toggleCollapse"
class="collapse-button"
>
{{ !isCollapsed ? "收起" : "展开" }}
</el-button>
</div>
</div>
<div class="f1">
<CustomTable
v-show="!isCollapsed"
:columns="[
{ prop: 'name', label: '项目', align: 'center' },
{ prop: 'workTime', label: '工时(天)', align: 'center' },
]"
:tableData="projectList"
:rowClick="
(row) => {
handleProjectClick(row, 1);
}
"
:show-summary="true"
:summary-method="getSummaries"
sum-text="合计工时(天)"
:showPagination="false"
:highligt="true"
ref="projectRef"
style="height: 100%"
rowKey="projectId"
/>
</div>
</div>
<!-- 右侧项目信息和日历 -->
<div class="project-info-calendar">
<h2 class="mb20 textC">工作日志</h2>
<!-- 项目信息表单 -->
<el-form
:model="projectInfo"
label-width="100px"
disabled
class="project-info-form log-form"
>
<el-form-item label="项目名称">
<el-input v-model="projectInfo.projectName"></el-input>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="项目编号">
<el-input v-model="projectInfo.projectCode"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="工时填报人">
<el-input v-model="projectInfo.nickName"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="项目开始时间">
<el-date-picker
v-model="projectInfo.startDate"
type="date"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目结束时间">
<el-date-picker
v-model="projectInfo.endDate"
type="date"
></el-date-picker>
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- 日历视图 -->
<div class="calendar-header">
<el-date-picker
v-model="selectedDate"
type="month"
format="yyyy年MM月"
:clearable="false"
@change="changeMonth"
/>
</div>
<div class="calendar-view" v-if="projectInfo">
<el-calendar v-model="selectedDate">
<template #dateCell="{ data }">
<div
:key="data.day"
:id="data.day"
@click="openLogDialog(data)"
:class="{
'date-cell': true,
'in-range': isInProjectRange(data),
'out-range': !isInProjectRange(data),
disabled: isFutureDate(data.date) && isInProjectRange(data),
}"
>
{{ data.day.split("-")[1] + "/" + data.day.split("-")[2] }}
</div>
</template>
</el-calendar>
</div>
</div>
<!-- 工作日志对话框 -->
<el-dialog
:visible="logDialogVisible"
:title="
logForm.loggerId && projectInfo.userId == $store.state.user.id
? '编辑日志'
: projectInfo.userId == $store.state.user.id
? '新增日志'
: '查看日志'
"
width="30%"
center
append-to-body
:before-close="
() => {
logDialogVisible = false;
}
"
>
<el-form :model="logForm" label-width="120px" class="log-form">
<el-form-item label="工时分配(天)">
<el-input
type="number"
v-model="logForm.workTime"
:max="logForm.max"
:placeholder="
projectInfo.userId == $store.state.user.id
? `可填报工时:${logForm.max}人天`
: ''
"
:disabled="!(projectInfo.userId == $store.state.user.id)"
:step="0.1"
min="0"
></el-input>
<el-button
type="text"
v-if="projectInfo.userId == $store.state.user.id"
@click="logForm.workTime = logForm.max"
>{{ `当天剩余可分配工时:${logForm.max}人天` }}</el-button
>
</el-form-item>
<el-form-item label="工作内容">
<el-input
v-model="logForm.workContent"
:disabled="!(projectInfo.userId == $store.state.user.id)"
type="textarea"
:rows="4"
></el-input>
</el-form-item>
</el-form>
<template #footer>
<span
class="dialog-footer"
v-if="projectInfo.userId == $store.state.user.id"
>
<el-button @click="logDialogVisible = false">取消</el-button>
<el-button v-if="logForm.loggerId" type="danger" @click="delWorkLog"
>删除</el-button
>
<el-button type="primary" @click="saveWorkLog"></el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script>
import { workLogApi, projectApi } from "@/utils/api";
import CustomTable from "@/components/CustomTable.vue";
export default {
components: {
CustomTable,
// ArrowLeft,
// ArrowRight,
},
data() {
return {
isCollapsed: false,
selectedDate: new Date(),
projectList: [],
logData: [],
logDialogVisible: false,
projectInfo: {},
logForm: {
loggerDate: "",
workContent: "",
workTime: "",
status: -1,
max: 1,
loggerId: "",
},
routeQuery: this.$route.query,
};
},
methods: {
toggleCollapse() {
this.isCollapsed = !this.isCollapsed;
},
async handleProjectClick(row, first) {
if (!row.projectId) {
return;
}
const res = await projectApi.getProjectDetail(row.projectId);
this.projectInfo.projectName = res.data.projectName;
this.projectInfo.projectId = res.data.projectId;
this.projectInfo.projectCode = res.data.projectCode;
this.projectInfo.startDate =
res.data.startDate.split(" ")[0] + " 00:00:00";
this.projectInfo.endDate = res.data.endDate.split(" ")[0] + " 23:59:59";
if (this.projectInfo.userId == this.$store.state.user.id) {
this.selectedDate = new Date();
} else {
this.selectedDate = new Date(this.projectInfo.startDate); // 设置为项目开始时间
}
const res2 = await workLogApi.getLogData({
projectId: this.projectInfo.projectId,
startDate: this.projectInfo.startDate,
endDate: this.projectInfo.endDate,
userId: this.projectInfo.userId,
});
this.logData = res2.data;
this.initDateColor(first);
},
initDateColor(first) {
let boxs = document.getElementsByClassName("date-cell");
for (var i = 0; i < boxs.length; i++) {
boxs[i].style = "";
}
this.logData.map((item) => {
var ele = document.getElementById(item.date.split(" ")[0]);
if (ele) {
if (item.state == -1) {
ele.style = "background:#ecf5ff";
} else if (item.state == 0) {
ele.style = `background:linear-gradient(to right, #409eff ${
(item.workTime || 1) * 100
}% , #ecf5ff ${(item.workTime || 1) * 100}%);color:${
item.workTime > 0.65 ? "#fff" : "#333"
};`;
}
}
});
if (document.getElementsByClassName("is-selected")[0] && first == 1) {
document.getElementsByClassName("is-selected")[0].className = document
.getElementsByClassName("is-selected")[0]
.className.replace("is-selected", "");
}
},
async openLogDialog(data) {
if (!this.projectInfo.projectId) {
this.$modal.msgWarning("请先选择一个项目");
return;
}
const clickedDate = new Date(data.day);
const currentDate = new Date();
if (
clickedDate.getTime() > currentDate.getTime() &&
clickedDate.getTime() < new Date(this.projectInfo.endDate).getTime()
) {
if (this.projectInfo.userId == this.$store.state.user.id)
this.$modal.msgWarning("不可编辑未来日期");
else this.$modal.msgWarning("不可查看未来日期");
return;
}
const date = new Date(data.day);
const start = new Date(this.projectInfo.startDate);
const end = new Date(this.projectInfo.endDate);
let flag =
date.getTime() >= start.getTime() && date.getTime() <= end.getTime();
if (!flag) {
this.$modal.msgWarning("该日期不在项目范围内");
return;
}
this.logForm.loggerDate = data.day + " 00:00:00";
let logTime =
this.logData.find((ele) => ele.date == this.logForm.loggerDate) || {};
this.logForm.status = logTime.state;
if (this.logForm.status != -1) {
const res = await workLogApi.getLogDataDetail({
userId: this.projectInfo.userId,
projectId: this.projectInfo.projectId,
loggerDate: this.logForm.loggerDate,
});
if (res.data) {
this.logForm.workTime = res.data.workTime;
this.logForm.workContent = res.data.workContent;
this.logForm.loggerId = res.data.loggerId;
} else {
this.logForm.workTime = "";
this.logForm.workContent = "";
this.logForm.loggerId = "";
}
} else {
this.logForm.workTime = "";
this.logForm.workContent = "";
this.logForm.loggerId = "";
}
const res = await workLogApi.getDayTime({
loggerDate: this.logForm.loggerDate,
});
this.logForm.max = (
Number(res.data) + (Number(this.logForm.workTime) || 0)
).toFixed(1);
this.logDialogVisible = true;
this.$nextTick(() => {
this.changeMonth();
});
},
changeMonth() {
let that = this;
this.$nextTick(async () => {
const res2 = await workLogApi.getLogData({
projectId: that.projectInfo.projectId,
startDate:
that
.moment(that.selectedDate)
.startOf("month")
.format("YYYY-MM-DD") + " 00:00:00",
endDate:
that.moment(that.selectedDate).endOf("month").format("YYYY-MM-DD") +
" 23:59:59",
userId: that.projectInfo.userId,
});
that.logData = res2.data;
that.initDateColor(1);
});
},
isInProjectRange(data) {
if (!this.projectInfo) return false;
const date = new Date(data.day);
const start = new Date(this.projectInfo.startDate);
const end = new Date(this.projectInfo.endDate);
return (
date.getTime() >= start.getTime() && date.getTime() <= end.getTime()
);
},
isFutureDate(date) {
const clickedDate = new Date(date);
const currentDate = new Date();
return clickedDate.getTime() > currentDate.getTime();
},
getSummaries(param) {
const { columns, data } = param;
const sums = [];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = "合计工时(天)";
return;
}
const values = data.map((item) => Number(item[column.property]));
if (!values.every((value) => isNaN(value))) {
sums[index] = values.reduce((prev, curr) => {
const value = Number(curr);
if (!isNaN(value)) {
return prev + curr;
} else {
return prev;
}
}, 0);
} else {
sums[index] = "N/A";
}
});
if (sums[1] != "N/A") sums[1] = Number(sums[1]).toFixed(2);
return sums;
},
async saveWorkLog() {
if (this.logForm.workTime > this.logForm.max) {
this.$modal.msgWarning("工时超过最大值");
return;
} else if (!this.logForm.workContent) {
this.$modal.msgWarning("工作日志不能为空");
return;
} else if (
(String(this.logForm.workTime).split(".")[1] || "").length > 1
) {
this.$modal.msgWarning("工时只允许一位小数");
return;
} else if (this.logForm.workTime <= 0) {
this.$modal.msgWarning("工时不能小于等于0");
return;
}
let state =
new Date(this.logForm.loggerDate).getTime() ==
new Date().setHours(0, 0, 0, 0)
? 0
: 1;
let param = {
...this.logForm,
projectId: this.projectInfo.projectId,
state,
userId: this.projectInfo.userId,
};
if (this.logForm.loggerId) {
await workLogApi.editLog(param);
} else {
await workLogApi.addLog(param);
}
this.logDialogVisible = false;
this.$modal.msgSuccess("操作成功");
const response = await workLogApi.userProject(this.projectInfo.userId);
this.projectList = response.data;
this.handleProjectClick(this.projectInfo);
},
async fetchUserProjects() {
try {
const response = await workLogApi.userProject(this.projectInfo.userId);
this.projectList = response.data;
if (this.projectList.length) {
let arr = [];
if (this.$route.query.projectId)
arr = this.projectList.filter(
(ele) => ele.projectId == this.$route.query.projectId
);
if (this.$route.query.projectId && arr.length) {
this.handleProjectClick(arr[0], 1);
this.$refs.projectRef.setCurrentRow(arr[0]);
this.projectList = arr;
} else {
this.handleProjectClick(this.projectList[0], 1);
this.$refs.projectRef.setCurrentRow(this.projectList[0]);
}
}
} catch (error) {}
},
init() {
if (this.$route.query.userId) {
this.projectInfo.userId = this.$route.query.userId;
this.projectInfo.nickName = this.$route.query.nickName;
} else {
let userInfo = this.$store.state.user;
this.projectInfo.userId = userInfo.id;
this.projectInfo.nickName = userInfo.nickName;
}
//获取项目列表
this.fetchUserProjects();
},
// 删除日志
delWorkLog() {
this.$modal.confirm(`是否确认删日志`).then(async () => {
await workLogApi.delLog(this.logForm.loggerId);
this.$modal.msgSuccess("操作成功");
this.logDialogVisible = false;
this.changeMonth();
const response = await workLogApi.userProject(this.projectInfo.userId);
this.projectList = response.data;
});
},
},
watch: {
$route(to, from) {
if (!this.$route.query.userId) {
this.init();
}
},
},
mounted() {
this.init();
},
};
</script>
<style lang="scss" scoped>
.work-log-container {
display: flex;
height: 88vh;
background-color: white;
}
.project-list {
height: 88vh;
width: 300px;
border-right: 1px solid #dcdfe6;
transition: all 0.3s;
overflow: hidden;
position: relative;
height: 100%;
display: flex;
flex-direction: column;
}
.project-list.collapsed {
width: 50px;
}
.project-list .list-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
font-size: 16px;
font-weight: bold;
border-bottom: 1px solid #dcdfe6;
position: relative;
height: 40px;
// flex: 1;
}
.project-list .collapse-button-wrapper {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 40px; /* 固定宽度 */
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
}
.project-info-calendar {
flex: 1;
padding: 20px;
overflow-y: auto;
padding-bottom: 0;
}
.project-info-calendar .calendar-picker {
margin-bottom: 0;
}
.project-info-calendar .calendar-view {
flex: 1;
padding: 0 20px;
font-size: 14px; /* 缩小字体大小 */
}
.project-info-calendar .calendar-view ::v-deep .el-calendar {
--el-calendar-cell-width: 40px; /* 缩小日历单元格宽度 */
}
.project-info-calendar .calendar-view ::v-deep .el-calendar__header {
display: none;
}
.project-info-calendar .calendar-view ::v-deep .el-calendar__body {
padding: 10px 0; /* 减小主体内边距 */
}
.project-info-calendar .calendar-view ::v-deep .el-calendar__week {
background-color: #4a4a4a; /* 深灰色背景 */
color: white; /* 白色文字 */
padding: 5px 0; /* 增加一些内边距 */
}
.project-info-calendar ::v-deep .el-calendar__body th {
height: 60px !important;
font-weight: bold !important;
font-size: 20px;
}
.project-info-calendar .calendar-view ::v-deep .el-calendar-day {
height: 70px; /* 增加日期单元格高度(原高度 + 5px */
// padding-top: 5px;
padding: 10px;
border-radius: 10px;
}
.project-info-calendar .calendar-view ::v-deep .el-calendar-day.disabled {
cursor: not-allowed;
}
.project-info-calendar .calendar-view ::v-deep table * {
border: none;
}
.project-info-calendar .calendar-view ::v-deep .date-cell {
cursor: pointer;
height: 100%;
text-align: center;
line-height: 50px;
border-radius: 10px;
}
.project-info-calendar .calendar-view ::v-deep .in-range {
background-color: #ecf5ff;
}
.project-info-calendar .calendar-view ::v-deep .out-range {
background-color: rgba(0, 0, 0, 0.1) !important;
}
.project-info-calendar .calendar-view ::v-deep .disabled {
background-color: #fff !important;
color: #c0c4cc;
cursor: not-allowed;
}
.dialog-footer {
display: flex;
justify-content: center;
margin-top: 20px;
}
::v-deep .el-table {
width: 100% !important;
}
::v-deep .log-form .el-date-editor {
width: 100% !important;
}
::v-deep .el-table__body-wrapper {
overflow-x: hidden;
}
::v-deep .el-table .cell {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
::v-deep .el-table__footer-wrapper {
background-color: #f5f7fa;
}
::v-deep .el-table__footer td {
background-color: #f5f7fa !important;
font-weight: bold;
color: #606266;
}
::v-deep .el-table__row {
cursor: pointer;
}
::v-deep tr.el-table__row:hover .el-table__cell {
background: #409eff !important;
color: #fff;
}
::v-deep tr.el-table__row.current-row .el-table__cell {
background-color: #409eff !important;
color: #fff;
}
.calendar-header {
text-align: center;
}
.custom-table {
height: 100%;
}
/* 调整合计行的样式 */
::v-deep .el-table__footer-wrapper {
bottom: 0;
position: absolute;
}
::v-deep .el-table__footer-wrapper td {
background-color: #c0c4cc !important;
}
</style>