551 lines
16 KiB
Vue
551 lines
16 KiB
Vue
<template>
|
|
<div class="gl-page-content" v-if="!showProjectDetailVisible">
|
|
<div class="display-mode">
|
|
<el-radio-group v-model="viewType">
|
|
<!-- <el-radio-button label="甘特图" value="gantt" /> -->
|
|
<el-radio-button label="卡片" value="card" />
|
|
<el-radio-button label="列表" value="list" />
|
|
</el-radio-group>
|
|
</div>
|
|
<!-- <div class="project-car-list" v-show="viewType === 'card'"> -->
|
|
|
|
<div class="project-table-list">
|
|
<BaseTable
|
|
showIndex
|
|
ref="baseTableRef"
|
|
tableName="NODE_LIST_LEVEL1"
|
|
:api="nodeListApi"
|
|
:searchLimitNum="3"
|
|
:actionsWidth="tableActionsLength['2-2-2']"
|
|
:listMode="viewType === 'list'?'default':'card'"
|
|
>
|
|
<template #cardTemplate="{tableData}">
|
|
<div class="project-card-box" >
|
|
<div class="projects-grid">
|
|
<div v-for="project in tableData" :key="project.id" class="project-card">
|
|
<div class="card-header">
|
|
<img class="project-icon" src="@/assets/imgs/projectTree/project-icon.png" alt="">
|
|
<div class="title-section">
|
|
<div class="project-title">{{ project.nodeName }}</div>
|
|
<div class="project-manager">{{ disposeMemberList(project) }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="project-info">
|
|
<div class="info-row">
|
|
<span class="info-label">状态</span>
|
|
<span class="info-value">
|
|
<span :class="['status-badge', 'status-' + (project.exeStatus?'':'no-start')]">
|
|
{{ PROJECT_EXE_STATUS.O[project.exeStatus ] }}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
|
|
<div class="date-row">
|
|
<span>{{ project.beginTime }}</span>
|
|
<span>至</span>
|
|
<span>{{ project.endTime }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="progress-section">
|
|
<div class="progress-header">
|
|
<span class="progress-label">时间进度</span>
|
|
<span class="progress-value">{{ project.progress }}%</span>
|
|
</div>
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" :style="{ width: project.progress + '%' }"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #leftOptions>
|
|
<el-button
|
|
icon="plus"
|
|
@click="openProjectInfoDiaFun('create')"
|
|
type="primary"
|
|
>
|
|
{{ $t('项目列表.新增') }}
|
|
</el-button>
|
|
</template>
|
|
<template #nodeSubType="{row}">
|
|
{{ PROJECT_TYPE.O[row.nodeSubType] }}
|
|
</template>
|
|
<template #exeStatus="{row}">
|
|
{{ PROJECT_EXE_STATUS.O[row.exeStatus] }}
|
|
</template>
|
|
<template #memberList="{row}">
|
|
{{ disposeMemberList(row) }}
|
|
</template>
|
|
<template #tableActions="{ row }">
|
|
<div class="gl-table-actions">
|
|
<el-link type="primary" @click="goProjectDetailFun(row.uuid,row.nodeName)">查看</el-link>
|
|
<el-link type="primary" @click="openProjectInfoDiaFun('edit',row)">编辑</el-link>
|
|
<el-popconfirm
|
|
title="确认取消关注吗?"
|
|
@confirm="deleteNodeFun(row)"
|
|
>
|
|
<template #reference>
|
|
<el-link type="danger">删除</el-link>
|
|
</template>
|
|
</el-popconfirm>
|
|
</div>
|
|
</template>
|
|
</BaseTable>
|
|
</div>
|
|
</div>
|
|
<projectInfoDialog
|
|
ref="basePageRef"
|
|
v-model="showProjectInfoDialog"
|
|
:projectId="currentProjectBaseInfo.uuid"
|
|
:nodeLevel1List="baseTableRef?.tableData||[]"
|
|
@update:currentProjectBaseInfo="updateCurrentProjectBaseInfoFun"
|
|
@nextPageFun="nextPageFun"
|
|
@completeFun="completeFun"
|
|
/>
|
|
<projectDetail
|
|
v-if="showProjectDetailVisible"
|
|
:projectName="currentProject.nodeName"
|
|
:projectUuid="currentProject.nodeId"
|
|
@goBack="showProjectDetailVisible = false"
|
|
></projectDetail>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { reactive, ref } from 'vue';
|
|
import BaseTable from '@/components/common/table/baseTable.vue';
|
|
import projectInfoDialog from '@/components/project/projectInfoDialog.vue';
|
|
// import { useRouter } from 'vue-router';
|
|
import { deleteNodeApi, queryNodeListApi } from '@/api/project/node';
|
|
import { ElMessage } from 'element-plus';
|
|
import { NODE_TYPE } from '@/utils/enum/node';
|
|
import { disposeMemberList } from '../projectDetail/components/project';
|
|
import { useDict } from '@/utils/useDict';
|
|
import { tableActionsLength } from '@/utils/common';
|
|
import dayjs from 'dayjs';
|
|
import projectDetail from '../projectDetail/index.vue';
|
|
|
|
export interface IUserInfo {
|
|
id: number;
|
|
company: string | null;
|
|
creator: string;
|
|
createTime: string;
|
|
updater: string | null;
|
|
updateTime: string | null;
|
|
tenantId: string | null;
|
|
nodeId: string;
|
|
identity: string;
|
|
name: string;
|
|
}
|
|
|
|
interface IProjectInfo {
|
|
[key:string]: string;
|
|
id: string;
|
|
uuid: string;
|
|
nodeName: string;
|
|
nodeCode: string;
|
|
nodeType: string;
|
|
nodeSubType: string;
|
|
memberList: any;
|
|
progressStatus: string;
|
|
currentNode: string;
|
|
beginTime: string;
|
|
endTime: string;
|
|
actualStartTime: string;
|
|
actualEndTime: string;
|
|
description: string;
|
|
}
|
|
|
|
// const router = useRouter();
|
|
const basePageRef = ref();
|
|
const nodePageRef = ref();
|
|
|
|
const { PROJECT_TYPE, PROJECT_EXE_STATUS } = useDict('PROJECT_TYPE', 'PROJECT_EXE_STATUS');
|
|
|
|
const showProjectInfoDialog = ref(false);
|
|
const showNodeInfoDialog = ref(false);
|
|
const showTaskDialog = ref(false);
|
|
|
|
const showProjectDetailVisible = ref(false);
|
|
|
|
const dialogType = ref('create'); // create 编辑 edit
|
|
const currentProjectBaseInfo = reactive<any>({
|
|
id: '',
|
|
exeStatus: '',
|
|
uuid: '',
|
|
nodeName: '',
|
|
nodeCode: '',
|
|
nodeType: '',
|
|
nodeSubType: '',
|
|
progressStatus: '',
|
|
currentNode: '',
|
|
beginTime: '',
|
|
endTime: '',
|
|
actualStartTime: '',
|
|
actualEndTime: '',
|
|
description: '',
|
|
memberList: [],
|
|
});
|
|
|
|
const viewType = ref('list');
|
|
|
|
const currentRow = ref();
|
|
|
|
const openProjectInfoDiaFun = (tag:string, row?:IProjectInfo) => {
|
|
dialogType.value = tag;
|
|
showProjectInfoDialog.value = true;
|
|
currentRow.value = row;
|
|
|
|
if (tag === 'edit' && row) {
|
|
Object.keys(currentProjectBaseInfo).forEach(key => {
|
|
(currentProjectBaseInfo as any)[key] = (row as any)[key];
|
|
});
|
|
} else {
|
|
Object.keys(currentProjectBaseInfo).forEach(key => {
|
|
(currentProjectBaseInfo as any)[key] = '';
|
|
});
|
|
}
|
|
};
|
|
|
|
const currentProject = reactive({
|
|
nodeName: '',
|
|
nodeId: '',
|
|
});
|
|
const goProjectDetailFun = (uuid:string, nodeName:string) => {
|
|
showProjectDetailVisible.value = true;
|
|
currentProject.nodeName = nodeName;
|
|
currentProject.nodeId = uuid;
|
|
|
|
// router.push({ path: '/task/projectDetail', query: { nodeId: uuid, nodeName } });
|
|
};
|
|
|
|
// NODE_LIST_LEVEL1
|
|
// const headData = ref<any[]>( [
|
|
// { title: '项目名称', key: 'nodeName', isShow: true, inputMode: 'input', type: 1, inForm: false, required: true, width: 200 },
|
|
// { title: '项目代号', key: 'nodeCode', isShow: true, inputMode: 'input', type: 1, inForm: false, required: true, width: 120 },
|
|
// { title: '项目类型', key: 'nodeSubType', isShow: true, inputMode: 'input', type: 1, inForm: false, required: true, width: 120 },
|
|
// { title: '进度状态', key: 'progressStatusValue', isShow: true, inputMode: 'input', type: 1, inForm: false, required: true, width: 120 },
|
|
// { title: '计划开始时间', key: 'beginTime', isShow: true, inputMode: 'input', type: 1, inForm: false, required: true, width: 180 },
|
|
// { title: '计划结束时间', key: 'endTime', isShow: true, inputMode: 'input', type: 1, inForm: false, required: true, width: 180 },
|
|
// { title: '实际完成时间', key: 'finishTime', isShow: true, inputMode: 'input', type: 1, inForm: false, required: true, width: 180 },
|
|
// { title: '项目经理', key: 'memberList', isShow: true, inputMode: 'input', type: 1, inForm: false, required: true, width: 120 },
|
|
// { title: '创建人', key: 'creator', isShow: true, inputMode: 'input', type: 1, inForm: false, required: true, width: 120 },
|
|
// { title: '创建时间', key: 'createTime', isShow: true, inputMode: 'input', type: 1, inForm: false, required: true, width: 180 },
|
|
// { title: '描述', key: 'description', isShow: true, inputMode: 'input', type: 1, inForm: false, required: true, width: 200 },
|
|
// { title: '操作', key: 'tableActions', isShow: true, inputMode: 'input', type: 1, inForm: false, required: true, width: 150, fixed: 'right' },
|
|
// ]);
|
|
|
|
const nodeListApi = async(params:any) => {
|
|
const res:any = await queryNodeListApi({ ...params, nodeType: NODE_TYPE.PROJECT });
|
|
if (res && res.code === 200) {
|
|
res.data.data = res.data.data.map((item:any) => {
|
|
if (item.beginTime && item.endTime) {
|
|
const now = new Date();
|
|
if (dayjs(now).isBefore(item.beginTime)) {
|
|
item.progress = 0;
|
|
}
|
|
else if (dayjs(item.endTime).isBefore(now)) {
|
|
item.progress = 100;
|
|
} else {
|
|
const progress = dayjs(now).diff(item.beginTime) / dayjs(item.endTime).diff(item.beginTime);
|
|
// console.log('progress', progress);
|
|
item.progress = Number(progress.toFixed(4)) * 100;
|
|
// console.log('item.progress', item.progress);
|
|
}
|
|
} else {
|
|
item.progress = 0;
|
|
}
|
|
return item;
|
|
});
|
|
// console.log('res', res);
|
|
return res;
|
|
}
|
|
// else {
|
|
// ElMessage.error('不能提交空数据');
|
|
// }
|
|
};
|
|
|
|
const updateCurrentProjectBaseInfoFun = (info:any) => {
|
|
for (const key in currentProjectBaseInfo) {
|
|
currentProjectBaseInfo[key] = info[key];
|
|
}
|
|
console.log('currentProjectBaseInfo', currentProjectBaseInfo);
|
|
};
|
|
|
|
const nextPageFun = (step:string) => {
|
|
console.log('nodePageRef?.nodeTableList', nodePageRef.value?.nodeTableList);
|
|
if (step === 'basePage') {
|
|
showProjectInfoDialog.value = false;
|
|
showNodeInfoDialog.value = true;
|
|
}
|
|
if (step === 'nodePage') {
|
|
showNodeInfoDialog.value = false;
|
|
showTaskDialog.value = true;
|
|
}
|
|
};
|
|
|
|
// const prePageFun = (step:string) => {
|
|
// if (step === 'nodePage') {
|
|
// showNodeInfoDialog.value = false;
|
|
// showProjectInfoDialog.value = true;
|
|
// }
|
|
// if (step === 'taskPage') {
|
|
// showTaskDialog.value = false;
|
|
// showNodeInfoDialog.value = true;
|
|
// }
|
|
// };
|
|
|
|
const baseTableRef = ref();
|
|
|
|
const completeFun = async (page:string) => {
|
|
if (page === 'nodePage') {
|
|
await basePageRef.value.createProject();
|
|
console.log('currentProjectBaseInfo', currentProjectBaseInfo);
|
|
nodePageRef.value.addNodeDisposeFun(nodePageRef.value.nodeTableList, currentProjectBaseInfo.id);
|
|
}
|
|
baseTableRef.value.resetFun({});
|
|
};
|
|
|
|
const deleteNodeFun = async(row:IProjectInfo) => {
|
|
console.log('删除节点', row);
|
|
// 调用删除接口
|
|
const res:any = await deleteNodeApi({ deleteNodeIdList: [ row.uuid ] });
|
|
if (res && res.code === 200) {
|
|
ElMessage.success('删除项目成功');
|
|
} else {
|
|
ElMessage.error(res.msg || '删除项目失败');
|
|
}
|
|
baseTableRef.value.resetFun({});
|
|
};
|
|
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.gl-page-content {
|
|
.display-mode {
|
|
text-align: right;
|
|
}
|
|
.project-car-list {
|
|
width: 100%;
|
|
// // overflow-y: auto;
|
|
// // height: calc(100% - 16px);
|
|
// // margin-top: 16px;
|
|
// display: flex;
|
|
// flex-wrap: wrap;
|
|
// gap: @DEFAULT_PADDING;
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 12px;
|
|
// padding-right: 5px;
|
|
.filter-btn {
|
|
text-align: right;
|
|
margin: 10px 0;
|
|
}
|
|
.project {
|
|
// width: calc(25% - 9px);
|
|
height: 156px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 12px;
|
|
border-radius: 6px;
|
|
position: relative;
|
|
|
|
overflow: hidden;
|
|
border: 1px solid red;
|
|
.progress {
|
|
width: 40px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
}
|
|
|
|
.activeProject {
|
|
// background-color: @DEFAULT_THEME_BACKGROUND_COLOR;
|
|
border: 2px solid red;
|
|
// color: #fff;
|
|
box-shadow: 0 0 5px 2px #d3d0f0;
|
|
|
|
.projectCenter {
|
|
.projectName {
|
|
font-size: 14px;
|
|
// color: #fff;
|
|
}
|
|
|
|
.phase {
|
|
// font-size: 12px;
|
|
// color: #fff;
|
|
}
|
|
}
|
|
|
|
.name {
|
|
font-weight: 700;
|
|
}
|
|
}
|
|
|
|
.circle {
|
|
display: inline-block;
|
|
width: 10px;
|
|
height: 10px;
|
|
position: absolute;
|
|
top: 6px;
|
|
right: 6px;
|
|
margin-right: 0;
|
|
}
|
|
}
|
|
}
|
|
.projects-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
gap: 20px;
|
|
}
|
|
|
|
.project-card {
|
|
background: white;
|
|
border-radius: 12px;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
|
padding: 18px;
|
|
transition: all 0.3s ease;
|
|
border: 1px solid #e2e8f0;
|
|
}
|
|
|
|
.project-card:hover {
|
|
transform: translateY(-3px);
|
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
|
|
border-color: #cbd5e1;
|
|
}
|
|
|
|
.card-header {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.project-icon {
|
|
width: 36px;
|
|
height: 36px;
|
|
background-color: #ef4444;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: white;
|
|
font-size: 14px;
|
|
font-weight: bold;
|
|
margin-right: 12px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.title-section {
|
|
flex-grow: 1;
|
|
}
|
|
|
|
.project-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: #1e293b;
|
|
margin-bottom: 2px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.project-manager {
|
|
font-size: 12px;
|
|
color: #64748b;
|
|
}
|
|
|
|
.project-info {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.info-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 10px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.info-label {
|
|
color: #64748b;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.info-value {
|
|
color: #1e293b;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.status-badge {
|
|
padding: 4px 10px;
|
|
border-radius: 12px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
display: inline-block;
|
|
}
|
|
|
|
.status-planning {
|
|
background-color: #eff6ff;
|
|
color: #3b82f6;
|
|
}
|
|
|
|
.status-progress {
|
|
background-color: #ecfdf5;
|
|
color: #10b981;
|
|
}
|
|
|
|
.status-delayed {
|
|
background-color: #fef2f2;
|
|
color: #ef4444;
|
|
}
|
|
|
|
.status-no-start {
|
|
background-color: #f8fafc;
|
|
color: #64748b;
|
|
}
|
|
|
|
.progress-section {
|
|
margin-top: 18px;
|
|
}
|
|
|
|
.progress-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 6px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.progress-label {
|
|
color: #64748b;
|
|
}
|
|
|
|
.progress-value {
|
|
color: #3b82f6;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.progress-bar {
|
|
height: 6px;
|
|
background-color: #e2e8f0;
|
|
border-radius: 3px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.progress-fill {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, #3b82f6, #60a5fa);
|
|
border-radius: 3px;
|
|
transition: width 0.5s ease;
|
|
}
|
|
|
|
.date-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-top: 4px;
|
|
font-size: 12px;
|
|
color: #64748b;
|
|
}
|
|
</style>
|