750 lines
21 KiB
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>
|