- 新增项目菜单项及路由配置,支持项目管理入口 - 实现项目相关API接口,包括项目列表、统计、甘特图及项目初始化接口 - 添加项目新建向导组件,支持上传文件预览及确认保存 - 实现项目管理页面,包含项目列表展示、筛选、统计卡片及新建项目操作 - 支持项目基本信息、里程碑、任务、成员及风险等多维度管理数据录入 - 优化页面交互体验,支持上传文件格式校验及数据编辑预览 - 提供状态及风险等级标签显示,辅助项目状态快速识别
This commit is contained in:
467
src/views/project/index.vue
Normal file
467
src/views/project/index.vue
Normal file
@@ -0,0 +1,467 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { useProject } from "./utils/hook";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
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 wizardVisible = 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) {
|
||||
console.log("查看项目", row);
|
||||
}
|
||||
|
||||
// 编辑项目
|
||||
function handleEdit(row: any) {
|
||||
console.log("编辑项目", row);
|
||||
}
|
||||
|
||||
// 获取状态标签类型
|
||||
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";
|
||||
}
|
||||
}
|
||||
</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.ongoingCount }}
|
||||
</p>
|
||||
<p class="text-xs text-green-500 mt-1">
|
||||
<el-icon
|
||||
><component :is="useRenderIcon('ri/arrow-up-line')"
|
||||
/></el-icon>
|
||||
较上月增加2个
|
||||
</p>
|
||||
</div>
|
||||
<div class="stat-icon bg-blue-100">
|
||||
<el-icon :size="24" color="#409eff">
|
||||
<component :is="useRenderIcon('ri/folder-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">本年度累计完成</p>
|
||||
</div>
|
||||
<div class="stat-icon bg-green-100">
|
||||
<el-icon :size="24" color="#67c23a">
|
||||
<component :is="useRenderIcon('ri/check-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 text-orange-500">
|
||||
{{ statistics.highRiskCount }}
|
||||
</p>
|
||||
<p class="text-xs text-orange-400 mt-1">需要重点关注</p>
|
||||
</div>
|
||||
<div class="stat-icon bg-orange-100">
|
||||
<el-icon :size="24" color="#e6a23c">
|
||||
<component :is="useRenderIcon('ri/alert-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.averageProgress }}%
|
||||
</p>
|
||||
<el-progress
|
||||
:percentage="statistics.averageProgress"
|
||||
:show-text="false"
|
||||
class="mt-2"
|
||||
style="width: 100px"
|
||||
/>
|
||||
</div>
|
||||
<div class="stat-icon bg-purple-100">
|
||||
<el-icon :size="24" color="#9b59b6">
|
||||
<component :is="useRenderIcon('ri/bar-chart-line')" />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
<div class="flex justify-between items-start mb-3">
|
||||
<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>
|
||||
<el-button link>
|
||||
<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 @click="handleEdit(item)">
|
||||
<component :is="useRenderIcon(EditPenIcon)" class="mr-2" />
|
||||
编辑项目
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item 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"
|
||||
>
|
||||
{{ item.status || "未知" }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
:type="getRiskType(item.riskLevel)"
|
||||
size="small"
|
||||
effect="plain"
|
||||
>
|
||||
{{ item.riskLevel || "未知" }}风险
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="text-sm text-gray-500 mb-3 line-clamp-2"
|
||||
style="min-height: 40px"
|
||||
>
|
||||
{{ item.myRole ? `我的角色: ${item.myRole}` : "暂无角色信息" }}
|
||||
</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 :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"
|
||||
/>
|
||||
</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>
|
||||
Reference in New Issue
Block a user