pms-front/src/views/project/detail.vue

750 lines
21 KiB
Vue

<template>
<div class="project-management">
<el-form
ref="formRef"
:model="formData"
:rules="rules"
label-width="120px"
class="custom-form"
>
<el-row :gutter="24">
<el-col :span="24">
<el-form-item label="项目名称" prop="projectName">
<div>
<el-input
v-model="formData.projectName"
placeholder="请输入项目名称"
:disabled="isEditing"
class="full-width longInput"
/>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目编码" prop="projectCode">
<el-input
v-model="formData.projectCode"
class="full-width"
:disabled="isEditing"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目负责人" prop="projectLeader">
<el-input
v-model="formData.projectLeaderName"
placeholder="选择项目负责人"
readonly
:disabled="isEditing"
@click.native="openProjectManagerSelect"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="项目状态" prop="projectState">
<el-select
v-model="formData.projectState"
placeholder="根据时间自动生成"
disabled
class="full-width"
>
<el-option label="未启动" value="0" />
<el-option label="进行中" value="1" />
<el-option label="已完成" value="2" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="预计人天" prop="budgetDate">
<el-input
v-model.number="formData.budgetDate"
:min="0"
:disabled="isEditing"
class="full-width"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="开始日期" prop="startDate">
<el-date-picker
v-model="formData.startDate"
type="date"
placeholder="选择开始日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd"
class="full-width"
:disabled="isEditing"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="结束日期" prop="endDate">
<el-date-picker
v-model="formData.endDate"
type="date"
placeholder="选择结束日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd"
class="full-width"
:disabled="isEditing"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="form-actions jcc" v-show="!isEditing">
<el-button
type="primary"
@click="saveProject"
v-hasPermi="['project:detail:save']"
>保存</el-button
>
<el-button @click="cancelEdit">取消</el-button>
</div>
<div class="userBox">
<div class="table-actions">
<el-button
type="primary"
@click="addUser"
v-hasPermi="['project:detail:addUser']"
>新增成员</el-button
>
</div>
<div class="f1">
<CustomTable
:columns="columns"
:tableData="tableData"
:show-selection="false"
:show-index="true"
:show-pagination="false"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
style="height: 100%;"
>
<template slot="userName" slot-scope="{ row }">
<el-input
v-if="row.isNew"
v-model="row.userName"
placeholder="选择人员"
readonly
@click.native="openSelectUser(row)"
/>
<span v-else>{{ row.userName }}</span>
</template>
<template slot="post" slot-scope="{ row }">
<el-select
v-if="
(row.isEditing === true && row.teamId == row.teamId) ||
row.isNew
"
v-model="row.postId"
placeholder="请选择职位"
>
<el-option
v-for="post in postOptions"
:key="post.dictValue"
:label="post.dictLabel"
:value="post.dictValue"
/>
</el-select>
<span v-else>{{
postOptions.find((post) => post.dictValue === row.postId)
? postOptions.find((post) => post.dictValue === row.postId)
.dictLabel
: ""
}}</span>
</template>
<template slot="operation" slot-scope="{ row }">
<div class="operation-buttons">
<template v-if="row.isNew">
<el-button
text
type="text"
size="mini"
@click="confirmAddUser(row)"
>确认</el-button
>
<el-button
text
type="text"
size="mini"
@click="cancelAddUser(row)"
>取消</el-button
>
</template>
<template v-else-if="row.isEditing && row.teamId == row.teamId">
<el-button
text
type="text"
size="mini"
@click="saveUserEdit(row)"
>保存</el-button
>
<el-button
text
type="text"
size="mini"
@click="cancelUserEdit(row)"
>取消</el-button
>
</template>
<template v-else>
<el-button
type="text"
size="mini"
v-hasPermi="['project:detail:editUser']"
@click="editUser(row)"
>编辑</el-button
>
<el-button
type="text"
size="mini"
v-hasPermi="['project:detail:workLog']"
@click="showTimesheet(row)"
>工作日志</el-button
>
<el-button
type="text"
size="mini"
v-hasPermi="['project:detail:deleteUser']"
@click="confirmDelete(row)"
>删除</el-button
>
</template>
</div>
</template>
</CustomTable>
</div>
</div>
<SelectUser
:dialogVisible="showSelectUser"
:multi-select="false"
:currentSelectedUser="currentSelectedUser"
@confirm="handleUserConfirm"
@close="closeSelectUser"
/>
<SelectUser
v-if="showProjectManagerSelect"
:dialogVisible="showProjectManagerSelect"
:multi-select="false"
:selected-users="projectManagerSelectedUser"
@confirm="handleProjectManagerConfirm"
@close="closeProjectManagerSelect"
/>
</div>
</template>
<script>
import CustomTable from "@/components/CustomTable.vue";
import SelectUser from "@/components/SelectUser.vue";
import { projectApi, systemApi } from "@/utils/api";
export default {
components: {
CustomTable,
SelectUser,
},
data() {
return {
formRef: null,
isEditing: false,
originalPost: "",
showSelectUser: false,
showProjectManagerSelect: false,
formData: {
projectId: null,
projectName: "",
projectCode: "",
projectLeaderName: "",
projectLeader: "",
projectState: "",
startDate: "",
endDate: "",
budgetDate: 0,
state: 0,
},
postOptions: [],
columns: [
{ prop: "userName", label: "姓名" },
{ prop: "post", label: "项目职位" },
{ prop: "workTime", label: "工作天数" },
{ prop: "operation", label: "操作", width: "250px" },
],
tableData: [],
currentEditingRow: {},
currentSelectedUser: [],
projectManagerSelectedUser: [],
rules: {
projectName: [
{ required: true, message: "项目名称为必填", trigger: "blur" },
],
projectLeader: [
{ required: true, message: "项目负责人为必填", trigger: "change" },
],
startDate: [
{ required: true, message: "开始日期为必填", trigger: "change" },
],
endDate: [
{ required: true, message: "结束日期为必填", trigger: "change" },
{ validator: this.validateDates, trigger: "change" },
],
budgetDate: [
{ required: true, message: "预算天数为必填", trigger: "blur" },
{ validator: this.validateDates, trigger: "change" },
],
},
};
},
watch: {
// 自动更新项目状态
"formData.startDate": "updateProjectState",
"formData.endDate": "updateProjectState",
"formData.projectId": "updateIsEdit",
},
methods: {
updateProjectState() {
const now = new Date().getTime();
const start = this.formData.startDate
? new Date(this.formData.startDate).getTime()
: null;
const end = this.formData.endDate
? new Date(this.formData.endDate).getTime()
: null;
if (start && end) {
if (now < start) {
this.formData.projectState = "0"; // 未开始
} else if (now >= start && now <= end) {
this.formData.projectState = "1"; // 进行中
} else {
this.formData.projectState = "2"; // 已结束
}
}
},
updateIsEdit() {
// if (this.formData.projectId) this.isEditing = true;
// else this.isEditing = false;
},
validateDates(rule, value, callback) {
if (this.formData.startDate && this.formData.endDate) {
const start = new Date(this.formData.startDate);
const end = new Date(this.formData.endDate);
if (start > end) {
callback(new Error("开始日期不能晚于结束日期"));
} else {
callback();
}
} else {
callback();
}
},
saveProject() {
if (!this.$refs.formRef) return;
this.$refs.formRef.validate(async (valid) => {
if (valid) {
const projectDataToSave = {
...this.formData,
startDate: this.formData.startDate
? new Date(this.formData.startDate+' 00:00:00').getTime()
: null,
endDate: this.formData.endDate
? new Date(this.formData.endDate+' 23:59:59').getTime()
: null,
};
if (!this.formData.projectId) {
const res = await projectApi.addProject(projectDataToSave);
this.formData.projectId = res.data.projectId;
this.formData.projectCode = res.data.projectCode;
this.$modal.msgSuccess("操作成功");
} else {
const hasLog = await projectApi.projectHasLogData({
projectId: this.formData.projectId,
startDate: this.formData.startDate + " 00:00:00",
endDate: this.formData.endDate + " 23:59:59",
});
if (!hasLog.data) {
this.$modal
.confirm(
`检测到项目时间范围外的日志记录,修改项目时间将导致这些日志被删除。请确认是否继续修改?`
)
.then(async () => {
await projectApi.updateProject(projectDataToSave);
});
} else {
await projectApi.updateProject(projectDataToSave);
this.$modal.msgSuccess("操作成功");
}
}
} else {
this.$modal.msgError("请检查表单填写是否正确");
this.$modal.msgSuccess("操作成功");
}
});
},
cancelEdit() {
this.$router.back();
},
addUser() {
if (!this.formData.projectId) {
this.$modal.msgWarning("请先保存项目信息后再添加用户");
return;
}
let newUser = {
userName: "",
post: "",
postId: "",
workTime: 0,
isNew: true,
userId: "",
isEditing: true,
index: this.tableData.length,
};
this.currentEditingRow = newUser;
this.tableData.push(newUser);
},
confirmAddUser(row) {
if (!this.formData.projectId) {
this.$modal.msgWarning("请先保存项目信息后再添加用户");
return;
}
if (!row.userId || !row.postId) {
this.$modal.msgWarning("请选择用户并指定职位");
return;
}
const params = {
projectId: this.formData.projectId,
userId: row.userId,
postId: row.postId,
};
if (row.teamId) params.teamId = row.teamId;
projectApi
.updateProjectUser(params)
.then(() => {
this.$modal.msgSuccess("成员添加成功");
this.getProjectUser();
})
.catch((error) => {
console.error("添加成员失败", error);
this.$modal.msgError("添加成员失败");
});
},
cancelAddUser(row) {
const index = this.tableData.findIndex(
(item) => item.userId === row.userId
);
if (index !== -1) {
this.tableData.splice(index, 1);
} else {
this.tableData.splice(row.index, 1);
}
},
cancelUserEdit(row) {
row.isEditing = false;
row.postId = row.originalPost;
},
openSelectUser(row) {
this.currentEditingRow = row;
this.currentSelectedUser = row.userId
? [{ userId: row.userId, userName: row.userName }]
: [];
this.showSelectUser = true;
},
handleUserConfirm(selectedUsers) {
if (selectedUsers.length > 0) {
const selectedUser = selectedUsers[0];
this.currentEditingRow.userName = selectedUser.nickName;
this.currentEditingRow.userId = selectedUser.userId;
} else {
this.currentEditingRow.userName = "";
this.currentEditingRow.userId = "";
}
this.showSelectUser = false;
},
closeSelectUser() {
this.showSelectUser = false;
// this.currentSelectedUser = [];
},
openProjectManagerSelect() {
this.projectManagerSelectedUser = this.formData.projectLeader
? [
{
id: this.formData.projectLeaderId,
name: this.formData.projectLeader,
},
]
: [];
this.showProjectManagerSelect = true;
},
handleProjectManagerConfirm(users) {
if (users.length > 0) {
const selectedUser = users[0];
this.formData.projectLeaderName = selectedUser.nickName;
this.formData.projectLeader = selectedUser.userId;
} else {
this.formData.projectLeaderName = "";
this.formData.projectLeader = null;
}
this.showProjectManagerSelect = false;
},
closeProjectManagerSelect() {
this.showProjectManagerSelect = false;
},
handleSizeChange(val) {
console.log("每页显示条数:", val);
},
handleCurrentChange(val) {
console.log("当前页:", val);
},
showTimesheet(row) {
this.$router.push({
path: "/",
query: {
userId: row.userId,
projectId: this.formData.projectId,
nickName: row.userName,
},
});
},
confirmDelete(row) {
this.$modal.confirm(`确定要删除该成员吗?`).then(() => {
return this.deleteUser(row);
});
},
deleteUser: async function (row) {
try {
await projectApi.deleteProjectUser(row.teamId);
this.$modal.msgSuccess("成员删除成功");
this.getProjectUser();
} catch (error) {
console.error("删除成员失败", error);
this.$modal.msgError("删除成员失败");
}
},
async fetchProjectData(id) {
try {
const response = await projectApi.getProjectDetail(id);
const projectData = response.data;
let {
projectId,
projectName,
projectCode,
projectLeaderName,
projectLeader,
projectState,
budgetDate,
state,
} = response.data;
let startDate = projectData.startDate.split(" ")[0];
let endDate = projectData.endDate.split(" ")[0];
this.formData = {
projectId,
projectName,
projectCode,
projectLeaderName,
projectLeader,
projectState,
startDate,
endDate,
budgetDate,
state,
};
this.updateProjectState(); // 更新项目状态
} catch (error) {
console.error("获取项目数据失败", error);
this.$modal.msgError("获取项目数据失败");
}
},
async getDictData() {
const res = await systemApi.getDictData("business_positions");
this.postOptions = res.data;
},
async getProjectUser() {
const res = await projectApi.getProjectUser(this.formData.projectId);
this.tableData = res.data.map((ele) => {
ele.isEditing = false;
return ele;
});
},
editUser(row) {
this.currentEditingRow = row;
this.currentEditingRow.isEditing = true;
this.currentEditingRow.originalPost = row.postId; // 保存原始职位,以便取消时恢复
},
async saveUserEdit(row) {
try {
await projectApi.updateProjectUser({
projectId: this.formData.projectId,
userId: this.currentEditingRow.userId,
postId: this.currentEditingRow.postId,
teamId: this.currentEditingRow.teamId,
});
this.currentEditingRow.isEditing = false;
delete this.currentEditingRow.originalPost;
this.$modal.msgSuccess("用户信息更新成功");
this.getProjectUser();
} catch (error) {
console.error("更新用户信息失败", error);
this.$modal.msgError("更新用户信息失败");
}
},
},
mounted() {
const projectId = this.$route.params.id || this.$route.query.id;
this.formData.projectId = projectId;
this.getDictData();
if (projectId) {
this.fetchProjectData(projectId);
this.getProjectUser();
} else {
this.updateProjectState(); // 对于新项目,也要初始化状态
}
},
};
</script>
<style lang="scss" scoped>
.project-management {
padding: 30px;
background-color: white;
height: 88vh;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
}
.custom-form {
width: 100%;
margin-bottom: 20px;
}
.form-container {
width: 100%;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12);
margin-bottom: 20px;
}
.custom-form ::v-deep .el-form-item {
margin-bottom: 25px; /* 增加表单行间距 */
}
.custom-form ::v-deep .el-form-item__content {
// margin-left: auto !important;
width: 100%;
}
.custom-form ::v-deep .el-input {
height: 42px; /* 调高输入框高度 */
width: 80%;
}
::v-deep .el-form-item__label {
height: 42px; /* 调高输入框高度 */
line-height: 42px;
}
.custom-form ::v-deep .el-input__wrapper,
.custom-form ::v-deep .el-date-editor.el-input,
.custom-form ::v-deep .el-input-number {
height: 42px; /* 调高输入框高度 */
width: 80%;
}
.custom-form ::v-deep .el-select {
width: 100%;
}
//
.content-container {
width: 100%;
display: flex;
flex-direction: column;
}
.userBox {
width: 100%;
display: flex;
flex-direction: column;
flex: 1;
}
.table-actions {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.form-actions {
display: flex;
gap: 10px;
}
::v-deep .el-table {
width: 100%;
}
.operation-buttons {
display: flex;
justify-content: flex-start;
align-items: center;
}
::v-deep .operation-buttons .el-button {
padding: 4px 8px;
margin: 0 2px;
}
::v-deep .operation-column {
background-color: #fff;
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1);
}
/* 添加以下样式以使日期选择器宽度一致 */
::v-deep .el-date-editor.el-input {
width: 80%;
}
::v-deep .el-date-editor.el-input .el-input__wrapper {
width: 80%;
}
::v-deep .longInput {
width: 90% !important;
}
.el-button.is-text {
min-width: 32px !important;
}
::v-deep .el-select .el-input__wrapper {
height: 32px;
}
.delete-confirm {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.warning-icon {
width: 64px;
height: 64px;
margin-bottom: 16px;
}
</style>