feat(project): 添加项目编辑和状态管理功能
Some checks failed
Lint Code / Lint Code (push) Has been cancelled

- 新增 Project 类型定义,完善项目数据结构
- 新增 updateProject、updateProjectStatus 等接口封装
- 添加权限控制,实现基于角色的编辑、删除、状态更新等操作权限判断
- 实现项目编辑模态框,支持项目基本信息、预算、进度等字段的修改
- 实现项目状态更新模态框,支持项目状态的切换操作
- 实现项目经理更换模态框,支持更新项目负责人信息
- 更新项目列表操作菜单,添加编辑、状态更新、项目经理更换等功能入口
- 优化项目状态显示,新增状态中文映射文本显示
- 完善项目编辑和状态更新的保存逻辑,增加操作成功与失败提示信息
- 引入表单校验和操作Loading状态,提升交互体验
This commit is contained in:
2026-03-31 18:22:01 +08:00
parent 2735c57778
commit 4b30c1350d
3 changed files with 837 additions and 7 deletions

View File

@@ -1,8 +1,17 @@
<script setup lang="ts">
import { ref } from "vue";
import { ref, computed } from "vue";
import { useRouter } from "vue-router";
import { useProject } from "./utils/hook";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { hasPerms } from "@/utils/auth";
import { message } from "@/utils/message";
import {
updateProject,
updateProjectStatus,
updateProjectManager,
type Project,
type ProjectItem
} from "@/api/project";
import CreateProjectWizard from "./components/CreateProjectWizard.vue";
import dayjs from "dayjs";
@@ -23,6 +32,34 @@ defineOptions({
const router = useRouter();
const wizardVisible = ref(false);
// 权限控制
const canEditProject = computed(() => hasPerms("project:center:update"));
const canDeleteProject = computed(() => hasPerms("project:center:delete"));
const canUpdateProjectStatus = computed(() =>
hasPerms("project:center:update")
);
// 项目编辑模态框
const projectEditModal = ref(false);
const projectEditForm = ref<Project>({});
const projectEditLoading = ref(false);
// 状态更新模态框
const statusUpdateModal = ref(false);
const statusUpdateForm = ref({
id: "",
status: ""
});
const statusUpdateLoading = ref(false);
// 项目经理更新模态框
const managerUpdateModal = ref(false);
const managerUpdateForm = ref({
id: "",
managerName: ""
});
const managerUpdateLoading = ref(false);
const {
form,
formRef,
@@ -59,8 +96,106 @@ function handleView(row: any) {
}
// 编辑项目
function handleEdit(row: any) {
console.log("编辑项目", row);
function handleEdit(row: ProjectItem) {
projectEditForm.value = {
id: row.id,
projectCode: row.projectCode,
projectName: row.projectName,
projectType: row.projectType,
planStartDate: row.planStartDate,
planEndDate: row.planEndDate,
progress: row.progress,
status: row.status,
priority: row.priority,
riskLevel: row.riskLevel,
budget: row.budget,
cost: row.cost
};
projectEditModal.value = true;
}
/** 保存项目编辑 */
async function saveProjectEdit() {
if (!projectEditForm.value.projectName) {
message("请输入项目名称", { type: "warning" });
return;
}
projectEditLoading.value = true;
try {
await updateProject(projectEditForm.value);
message("项目更新成功", { type: "success" });
projectEditModal.value = false;
onSearch();
} catch (error) {
console.error("更新项目失败:", error);
message("更新项目失败", { type: "error" });
} finally {
projectEditLoading.value = false;
}
}
/** 打开状态更新模态框 */
function openStatusModal(row: ProjectItem) {
statusUpdateForm.value = {
id: row.id!,
status: row.status || "ongoing"
};
statusUpdateModal.value = true;
}
/** 保存状态更新 */
async function saveStatusUpdate() {
if (!statusUpdateForm.value.status) {
message("请选择状态", { type: "warning" });
return;
}
statusUpdateLoading.value = true;
try {
await updateProjectStatus(
statusUpdateForm.value.id,
statusUpdateForm.value.status
);
message("状态更新成功", { type: "success" });
statusUpdateModal.value = false;
onSearch();
} catch (error) {
console.error("更新状态失败:", error);
message("更新状态失败", { type: "error" });
} finally {
statusUpdateLoading.value = false;
}
}
/** 打开项目经理更新模态框 */
function openManagerModal(row: ProjectItem) {
managerUpdateForm.value = {
id: row.id!,
managerName: ""
};
managerUpdateModal.value = true;
}
/** 保存项目经理更新 */
async function saveManagerUpdate() {
if (!managerUpdateForm.value.managerName) {
message("请输入项目经理姓名", { type: "warning" });
return;
}
managerUpdateLoading.value = true;
try {
await updateProjectManager(
managerUpdateForm.value.id,
managerUpdateForm.value.managerName
);
message("项目经理更新成功", { type: "success" });
managerUpdateModal.value = false;
onSearch();
} catch (error) {
console.error("更新项目经理失败:", error);
message("更新项目经理失败", { type: "error" });
} finally {
managerUpdateLoading.value = false;
}
}
// 获取状态标签类型
@@ -94,6 +229,19 @@ function getRiskType(risk?: string): "success" | "warning" | "danger" {
return "success";
}
}
// 获取状态文本
function getStatusText(status?: string): string {
const statusMap: Record<string, string> = {
draft: "草稿",
planning: "规划中",
ongoing: "进行中",
paused: "已暂停",
completed: "已完成",
cancelled: "已取消"
};
return statusMap[status || ""] || status || "未知";
}
</script>
<template>
@@ -344,11 +492,38 @@ function getRiskType(risk?: string): "success" | "warning" | "danger" {
<component :is="useRenderIcon(ViewIcon)" class="mr-2" />
查看详情
</el-dropdown-item>
<el-dropdown-item @click="handleEdit(item)">
<el-dropdown-item
v-if="canEditProject"
@click="handleEdit(item)"
>
<component :is="useRenderIcon(EditPenIcon)" class="mr-2" />
编辑项目
</el-dropdown-item>
<el-dropdown-item divided @click="handleDelete(item)">
<el-dropdown-item
v-if="canUpdateProjectStatus"
@click="openStatusModal(item)"
>
<component
:is="useRenderIcon('ri/settings-3-line')"
class="mr-2"
/>
更新状态
</el-dropdown-item>
<el-dropdown-item
v-if="canEditProject"
@click="openManagerModal(item)"
>
<component
:is="useRenderIcon('ri-user-settings-line')"
class="mr-2"
/>
更换项目经理
</el-dropdown-item>
<el-dropdown-item
v-if="canDeleteProject"
divided
@click="handleDelete(item)"
>
<component :is="useRenderIcon(DeleteIcon)" class="mr-2" />
<span class="text-red-500">删除项目</span>
</el-dropdown-item>
@@ -363,7 +538,7 @@ function getRiskType(risk?: string): "success" | "warning" | "danger" {
size="small"
class="mr-2"
>
{{ item.status || "未知" }}
{{ getStatusText(item.status) }}
</el-tag>
<el-tag
:type="getRiskType(item.riskLevel)"
@@ -400,7 +575,10 @@ function getRiskType(risk?: string): "success" | "warning" | "danger" {
<div class="flex-bc">
<div class="flex items-center gap-2">
<el-avatar :size="28">
<el-avatar v-if="item.managerAvatar" :size="28">
<img :src="item.managerAvatar" alt="负责人头像" />
</el-avatar>
<el-avatar v-else :size="28">
<component :is="useRenderIcon(UserIcon)" />
</el-avatar>
<span class="text-sm">{{ item.managerName || "未分配" }}</span>
@@ -436,6 +614,207 @@ function getRiskType(risk?: string): "success" | "warning" | "danger" {
v-model:visible="wizardVisible"
@success="handleWizardSuccess"
/>
<!-- 项目编辑模态框 -->
<el-dialog
v-model="projectEditModal"
title="编辑项目"
width="600px"
destroy-on-close
>
<el-form
ref="projectFormRef"
:model="projectEditForm"
label-width="100px"
class="project-edit-form"
>
<el-row :gutter="16">
<el-col :span="24">
<el-form-item label="项目名称" prop="projectName" required>
<el-input
v-model="projectEditForm.projectName"
placeholder="请输入项目名称"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目编号">
<el-input
v-model="projectEditForm.projectCode"
placeholder="请输入项目编号"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目类型">
<el-input
v-model="projectEditForm.projectType"
placeholder="请输入项目类型"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="计划开始日期">
<el-date-picker
v-model="projectEditForm.planStartDate"
type="date"
placeholder="选择日期"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="计划结束日期">
<el-date-picker
v-model="projectEditForm.planEndDate"
type="date"
placeholder="选择日期"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="优先级">
<el-select
v-model="projectEditForm.priority"
placeholder="请选择优先级"
style="width: 100%"
>
<el-option label="关键" value="critical" />
<el-option label="高" value="high" />
<el-option label="中" value="medium" />
<el-option label="低" value="low" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="风险等级">
<el-select
v-model="projectEditForm.riskLevel"
placeholder="请选择风险等级"
style="width: 100%"
>
<el-option label="高" value="high" />
<el-option label="中" value="medium" />
<el-option label="低" value="low" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目预算">
<el-input-number
v-model="projectEditForm.budget"
:min="0"
:precision="2"
placeholder="预算"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="已花费金额">
<el-input-number
v-model="projectEditForm.cost"
:min="0"
:precision="2"
placeholder="已花费"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="项目进度">
<el-slider
v-model="projectEditForm.progress"
:max="100"
show-input
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="projectEditModal = false">取消</el-button>
<el-button
type="primary"
:loading="projectEditLoading"
@click="saveProjectEdit"
>
保存
</el-button>
</template>
</el-dialog>
<!-- 状态更新模态框 -->
<el-dialog
v-model="statusUpdateModal"
title="更新项目状态"
width="400px"
destroy-on-close
>
<el-form :model="statusUpdateForm" label-width="80px">
<el-form-item label="项目状态">
<el-select
v-model="statusUpdateForm.status"
placeholder="请选择状态"
style="width: 100%"
>
<el-option label="草稿" value="draft" />
<el-option label="规划中" value="planning" />
<el-option label="进行中" value="ongoing" />
<el-option label="已暂停" value="paused" />
<el-option label="已完成" value="completed" />
<el-option label="已取消" value="cancelled" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="statusUpdateModal = false">取消</el-button>
<el-button
type="primary"
:loading="statusUpdateLoading"
@click="saveStatusUpdate"
>
确认
</el-button>
</template>
</el-dialog>
<!-- 项目经理更新模态框 -->
<el-dialog
v-model="managerUpdateModal"
title="更换项目经理"
width="450px"
destroy-on-close
>
<el-form :model="managerUpdateForm" label-width="100px">
<el-form-item label="当前负责人">
<el-input
:model-value="managerUpdateForm.managerName"
disabled
placeholder="暂无"
/>
</el-form-item>
<el-form-item label="新项目经理" required>
<el-input
v-model="managerUpdateForm.managerName"
placeholder="请输入新项目经理的姓名"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="managerUpdateModal = false">取消</el-button>
<el-button
type="primary"
:loading="managerUpdateLoading"
@click="saveManagerUpdate"
>
确认更换
</el-button>
</template>
</el-dialog>
</div>
</template>