Some checks failed
Lint Code / Lint Code (push) Failing after 1m40s
- 删除了项目列表中显示用户角色的文本块 - 简化了项目项的UI布局 - 减少了冗余的DOM元素和样式声明 - 保持UI整体风格一致性
859 lines
25 KiB
Vue
859 lines
25 KiB
Vue
<script setup lang="ts">
|
||
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";
|
||
|
||
import AddIcon from "~icons/ri/add-line";
|
||
import SearchIcon from "~icons/ri/search-line";
|
||
import RefreshIcon from "~icons/ri/refresh-line";
|
||
import MoreIcon from "~icons/ep/more-filled";
|
||
import DeleteIcon from "~icons/ep/delete";
|
||
import EditPenIcon from "~icons/ep/edit-pen";
|
||
import ViewIcon from "~icons/ri/eye-line";
|
||
import CalendarIcon from "~icons/ri/calendar-line";
|
||
import UserIcon from "~icons/ri/user-line";
|
||
|
||
defineOptions({
|
||
name: "Project"
|
||
});
|
||
|
||
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,
|
||
loading,
|
||
dataList,
|
||
pagination,
|
||
statistics,
|
||
activeFilter,
|
||
statusFilterButtons,
|
||
onSearch,
|
||
resetForm,
|
||
handleDelete,
|
||
handleSizeChange,
|
||
handleCurrentChange,
|
||
setFilter
|
||
} = useProject();
|
||
|
||
// 打开新建项目向导
|
||
function openWizard() {
|
||
wizardVisible.value = true;
|
||
}
|
||
|
||
// 向导成功回调
|
||
function handleWizardSuccess() {
|
||
onSearch();
|
||
}
|
||
|
||
// 查看项目详情
|
||
function handleView(row: any) {
|
||
router.push({
|
||
name: "ProjectDetail",
|
||
params: { id: row.id.toString() }
|
||
});
|
||
}
|
||
|
||
// 编辑项目
|
||
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;
|
||
}
|
||
}
|
||
|
||
// 获取状态标签类型
|
||
function getStatusType(
|
||
status?: string
|
||
): "success" | "warning" | "info" | "primary" | "danger" {
|
||
switch (status) {
|
||
case "completed":
|
||
return "success";
|
||
case "ongoing":
|
||
return "primary";
|
||
case "paused":
|
||
return "warning";
|
||
case "cancelled":
|
||
return "danger";
|
||
default:
|
||
return "info";
|
||
}
|
||
}
|
||
|
||
// 获取风险标签类型
|
||
function getRiskType(risk?: string): "success" | "warning" | "danger" {
|
||
switch (risk) {
|
||
case "low":
|
||
return "success";
|
||
case "medium":
|
||
return "warning";
|
||
case "high":
|
||
return "danger";
|
||
default:
|
||
return "success";
|
||
}
|
||
}
|
||
|
||
// 获取状态文本
|
||
function getStatusText(status?: string): string {
|
||
const statusMap: Record<string, string> = {
|
||
draft: "草稿",
|
||
planning: "规划中",
|
||
ongoing: "进行中",
|
||
paused: "已暂停",
|
||
completed: "已完成",
|
||
cancelled: "已取消"
|
||
};
|
||
return statusMap[status || ""] || status || "未知";
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="project-management w-full">
|
||
<!-- 页面标题 -->
|
||
<div class="flex-bc mb-4">
|
||
<div>
|
||
<h2 class="text-xl font-bold">项目管理</h2>
|
||
<p class="text-gray-500 text-sm mt-1">
|
||
管理所有项目的进度、资源分配和风险管控
|
||
</p>
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<el-button>
|
||
<template #icon>
|
||
<component :is="useRenderIcon('ri/download-line')" />
|
||
</template>
|
||
导出报表
|
||
</el-button>
|
||
<el-button type="primary" @click="openWizard">
|
||
<template #icon>
|
||
<component :is="useRenderIcon(AddIcon)" />
|
||
</template>
|
||
新建项目
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 统计卡片 -->
|
||
<el-row :gutter="16" class="mb-4">
|
||
<el-col :xs="24" :sm="12" :md="6">
|
||
<el-card shadow="hover" class="stat-card">
|
||
<div class="flex-bc">
|
||
<div>
|
||
<p class="text-gray-500 text-sm">项目总数</p>
|
||
<p class="text-2xl font-bold mt-1">
|
||
{{ statistics.totalCount }}
|
||
</p>
|
||
<p class="text-xs text-blue-500 mt-1">
|
||
<el-icon
|
||
><component :is="useRenderIcon('ri/folder-line')"
|
||
/></el-icon>
|
||
包含各状态项目
|
||
</p>
|
||
</div>
|
||
<div class="stat-icon bg-blue-100">
|
||
<el-icon :size="24" color="#409eff">
|
||
<component :is="useRenderIcon('ri/folders-line')" />
|
||
</el-icon>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :xs="24" :sm="12" :md="6">
|
||
<el-card shadow="hover" class="stat-card">
|
||
<div class="flex-bc">
|
||
<div>
|
||
<p class="text-gray-500 text-sm">进行中</p>
|
||
<p class="text-2xl font-bold mt-1">
|
||
{{ statistics.ongoingCount }}
|
||
</p>
|
||
<p class="text-xs text-green-500 mt-1">
|
||
<el-icon
|
||
><component :is="useRenderIcon('ri/play-circle-line')"
|
||
/></el-icon>
|
||
{{ statistics.planningCount }} 个规划中
|
||
</p>
|
||
</div>
|
||
<div class="stat-icon bg-green-100">
|
||
<el-icon :size="24" color="#67c23a">
|
||
<component :is="useRenderIcon('ri/play-circle-line')" />
|
||
</el-icon>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :xs="24" :sm="12" :md="6">
|
||
<el-card shadow="hover" class="stat-card">
|
||
<div class="flex-bc">
|
||
<div>
|
||
<p class="text-gray-500 text-sm">已完成</p>
|
||
<p class="text-2xl font-bold mt-1">
|
||
{{ statistics.completedCount }}
|
||
</p>
|
||
<p class="text-xs text-gray-400 mt-1">
|
||
{{ statistics.pausedCount }} 个已暂停 |
|
||
{{ statistics.cancelledCount }} 个已取消
|
||
</p>
|
||
</div>
|
||
<div class="stat-icon bg-purple-100">
|
||
<el-icon :size="24" color="#9b59b6">
|
||
<component :is="useRenderIcon('ri/check-double-line')" />
|
||
</el-icon>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :xs="24" :sm="12" :md="6">
|
||
<el-card shadow="hover" class="stat-card">
|
||
<div class="flex-bc">
|
||
<div>
|
||
<p class="text-gray-500 text-sm">平均进度</p>
|
||
<p class="text-2xl font-bold mt-1">
|
||
{{ Math.round(statistics.averageProgress || 0) }}%
|
||
</p>
|
||
<p class="text-xs text-orange-400 mt-1">
|
||
<el-icon
|
||
><component :is="useRenderIcon('ri/alert-line')"
|
||
/></el-icon>
|
||
{{ statistics.highRiskCount }} 个高风险项目
|
||
</p>
|
||
</div>
|
||
<div class="stat-icon bg-orange-100">
|
||
<el-icon :size="24" color="#e6a23c">
|
||
<component :is="useRenderIcon('ri/bar-chart-line')" />
|
||
</el-icon>
|
||
</div>
|
||
</div>
|
||
<el-progress
|
||
:percentage="Math.round(statistics.averageProgress || 0)"
|
||
:show-text="false"
|
||
class="mt-2"
|
||
style="width: 100%"
|
||
/>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 筛选区域 -->
|
||
<el-card shadow="never" class="mb-4 filter-card">
|
||
<div class="flex-bc flex-wrap gap-4">
|
||
<div class="flex items-center gap-2">
|
||
<el-button
|
||
v-for="btn in statusFilterButtons"
|
||
:key="btn.value"
|
||
:type="activeFilter === btn.value ? 'primary' : ''"
|
||
@click="setFilter(btn.value)"
|
||
>
|
||
{{ btn.label }}
|
||
</el-button>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<el-input
|
||
v-model="form.keyword"
|
||
placeholder="搜索项目名称..."
|
||
clearable
|
||
style="width: 200px"
|
||
@keyup.enter="onSearch"
|
||
>
|
||
<template #prefix>
|
||
<component :is="useRenderIcon(SearchIcon)" />
|
||
</template>
|
||
</el-input>
|
||
<el-select
|
||
v-model="form.status"
|
||
placeholder="状态"
|
||
clearable
|
||
style="width: 120px"
|
||
@change="onSearch"
|
||
>
|
||
<el-option label="未开始" :value="0" />
|
||
<el-option label="进行中" :value="1" />
|
||
<el-option label="已完成" :value="2" />
|
||
<el-option label="已延期" :value="3" />
|
||
</el-select>
|
||
|
||
<el-button
|
||
:icon="useRenderIcon(RefreshIcon)"
|
||
@click="resetForm(formRef)"
|
||
>
|
||
重置
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 项目列表卡片 -->
|
||
<div class="flex-bc mb-4">
|
||
<h3 class="text-lg font-medium">项目列表</h3>
|
||
<el-button type="primary" @click="openWizard">
|
||
<template #icon>
|
||
<component :is="useRenderIcon(AddIcon)" />
|
||
</template>
|
||
新建项目
|
||
</el-button>
|
||
</div>
|
||
|
||
<!-- 空状态 -->
|
||
<el-empty
|
||
v-if="!loading && dataList.length === 0"
|
||
description="暂无参与的项目"
|
||
class="py-12"
|
||
>
|
||
<template #image>
|
||
<div class="empty-icon">
|
||
<component
|
||
:is="useRenderIcon('ri/folder-open-line')"
|
||
style="font-size: 64px; color: var(--el-text-color-secondary)"
|
||
/>
|
||
</div>
|
||
</template>
|
||
<template #description>
|
||
<div class="text-center">
|
||
<p class="text-gray-500 mb-2">暂无参与的项目</p>
|
||
<p class="text-xs text-gray-400">
|
||
您还没有参与任何项目,可以创建一个新项目开始
|
||
</p>
|
||
</div>
|
||
</template>
|
||
<el-button type="primary" @click="openWizard">
|
||
<template #icon>
|
||
<component :is="useRenderIcon(AddIcon)" />
|
||
</template>
|
||
创建项目
|
||
</el-button>
|
||
</el-empty>
|
||
|
||
<el-row v-else v-loading="loading" :gutter="16">
|
||
<el-col
|
||
v-for="item in dataList"
|
||
:key="item.id"
|
||
:xs="24"
|
||
:sm="12"
|
||
:md="8"
|
||
:lg="6"
|
||
class="mb-4"
|
||
>
|
||
<el-card shadow="hover" class="project-card" @click="handleView(item)">
|
||
<div class="flex justify-between items-start mb-3" @click.stop>
|
||
<div class="flex-1 min-w-0">
|
||
<h4
|
||
class="font-medium text-base truncate"
|
||
:title="item.projectName"
|
||
>
|
||
{{ item.projectName }}
|
||
</h4>
|
||
<p class="text-xs text-gray-400 mt-1 truncate">
|
||
{{ item.projectCode || "暂无项目编号" }}
|
||
</p>
|
||
</div>
|
||
<el-dropdown @click.stop>
|
||
<el-button link @click.stop>
|
||
<component :is="useRenderIcon(MoreIcon)" />
|
||
</el-button>
|
||
<template #dropdown>
|
||
<el-dropdown-menu>
|
||
<el-dropdown-item @click="handleView(item)">
|
||
<component :is="useRenderIcon(ViewIcon)" class="mr-2" />
|
||
查看详情
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
v-if="canEditProject"
|
||
@click="handleEdit(item)"
|
||
>
|
||
<component :is="useRenderIcon(EditPenIcon)" class="mr-2" />
|
||
编辑项目
|
||
</el-dropdown-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>
|
||
</el-dropdown-menu>
|
||
</template>
|
||
</el-dropdown>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<el-tag
|
||
:type="getStatusType(item.status)"
|
||
size="small"
|
||
class="mr-2"
|
||
>
|
||
{{ getStatusText(item.status) }}
|
||
</el-tag>
|
||
<el-tag
|
||
:type="getRiskType(item.riskLevel)"
|
||
size="small"
|
||
effect="plain"
|
||
>
|
||
{{ item.riskLevel || "未知" }}风险
|
||
</el-tag>
|
||
</div>
|
||
|
||
<div class="flex items-center gap-4 text-xs text-gray-400 mb-3">
|
||
<span class="flex items-center gap-1">
|
||
<component :is="useRenderIcon(CalendarIcon)" />
|
||
{{
|
||
item.planStartDate
|
||
? dayjs(item.planStartDate).format("MM-DD")
|
||
: "--"
|
||
}}
|
||
~
|
||
{{
|
||
item.planEndDate
|
||
? dayjs(item.planEndDate).format("MM-DD")
|
||
: "--"
|
||
}}
|
||
</span>
|
||
</div>
|
||
|
||
<div class="flex-bc">
|
||
<div class="flex items-center gap-2">
|
||
<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>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<el-progress
|
||
:percentage="item.progress || 0"
|
||
:status="item.progress === 100 ? 'success' : ''"
|
||
style="width: 80px"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 分页 -->
|
||
<div class="flex justify-end mt-4">
|
||
<el-pagination
|
||
v-model:current-page="pagination.currentPage"
|
||
v-model:page-size="pagination.pageSize"
|
||
:total="pagination.total"
|
||
:page-sizes="[8, 12, 16, 20]"
|
||
layout="total, sizes, prev, pager, next"
|
||
background
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 新建项目向导 -->
|
||
<CreateProjectWizard
|
||
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>
|
||
|
||
<style scoped lang="scss">
|
||
.project-management {
|
||
padding: 16px 80px 16px 16px;
|
||
|
||
.stat-card {
|
||
.stat-icon {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 8px;
|
||
}
|
||
}
|
||
|
||
.filter-card {
|
||
:deep(.el-card__body) {
|
||
padding: 12px 16px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.project-card {
|
||
cursor: pointer;
|
||
border-radius: 12px;
|
||
transition: all 0.3s ease;
|
||
|
||
&:hover {
|
||
box-shadow: 0 8px 24px rgb(0 0 0 / 10%);
|
||
transform: translateY(-4px);
|
||
}
|
||
|
||
:deep(.el-card__body) {
|
||
padding: 16px;
|
||
}
|
||
}
|
||
|
||
:deep(.el-dropdown-menu__item i) {
|
||
margin: 0;
|
||
}
|
||
|
||
:deep(.el-button:focus-visible) {
|
||
outline: none;
|
||
}
|
||
</style>
|