仿真流程界面开发

This commit is contained in:
weibl
2025-10-31 18:00:51 +08:00
parent 8c5e247d59
commit 9537c4f15a
6 changed files with 629 additions and 44 deletions

View File

@@ -1,14 +1,14 @@
<template>
<div class="comp-content">
<el-dialog
<!-- <el-dialog
:title="diaTitle"
v-bind="$attrs"
>
<template v-for="(name) in Object.keys($slots)" :key="name" #[name]="scope">
<slot :name="name" v-bind="scope" />
</template>
</el-dialog>
<!-- <vxe-modal
</el-dialog> -->
<vxe-modal
:title="diaTitle"
show-zoom
resize
@@ -17,7 +17,7 @@
<template v-for="(name) in Object.keys($slots)" :key="name" #[name]="scope">
<slot :name="name" v-bind="scope" />
</template>
</vxe-modal> -->
</vxe-modal>
</div>
</template>

View File

@@ -6,4 +6,5 @@ export const tableActionsLength = {
'4-4-2': 200,
'2-2': 95,
'2-2-2': 130,
'2-2-2-2': 165,
};

View File

@@ -0,0 +1,143 @@
<template>
<div class="comp-content">
<Dialog
:loading="loadingInterface"
v-model="dialogVisible"
diaTitle="新增流程"
:width="500"
:height="500"
@close="closeFun"
show-footer
>
<TableForm ref="tableFormRef" tableName="SIMULATION_FLOW_LIB_LIST" />
<template #rightfoot>
<el-button @click="closeFun">取消</el-button>
<el-button @click="confirmFun" :loading="loadingInterface">完成</el-button>
</template>
</Dialog>
</div>
</template>
<script lang='ts' setup>
import Dialog from '@/components/common/dialog/index.vue';
// import { ElMessage } from 'element-plus';
import { computed, nextTick, ref } from 'vue';
import TableForm from '@/components/common/table/tableForm.vue';
const props = defineProps<{
showDialog: boolean;
dialogType: string;
}>();
const tableFormRef = ref();
const loadingInterface = ref(false);
const dialogVisible = computed(() => {
return props.showDialog;
});
const emits = defineEmits(['update:showDialog', 'update:currentProjectBaseInfo', 'nextPageFun', 'completeFun']);
// const projectForm = reactive<any>({
// nodeName: '',
// nodeCode: '',
// nodeSubType: '',
// memberList: [],
// beginTime: '',
// endTime: '',
// description: '',
// });
const confirmFun = async () => {
console.log('tableFormRef.value', tableFormRef.value);
if (await tableFormRef.value.validateFun()) {
loadingInterface.value = true;
if (props.dialogType === 'create') {
await createFlow();
} else {
// console.log('编辑项目', projectForm);
await editFlow();
emits('update:currentProjectBaseInfo', { ...tableFormRef.value.getFormDataFun() });
}
loadingInterface.value = false;
emits('completeFun', 'projectBasePage');
closeFun();
}
};
const createFlow = async() => {
// console.log('projectForm', projectForm);
// const memberList = projectForm.memberList.map((item: any) => {
// return JSON.parse(item);
// });
console.log('projectForm', tableFormRef.value.getFormDataFun());
// const projectForm = tableFormRef.value.getFormDataFun();
// const tagKeyMap = getTagKeyMap();
// const res:any = await addNodeApi(
// {
// addNodeList: [{ ...projectForm, pid: '', nodeType: NODE_TYPE.PROJECT, [tagKeyMap.get(NODE_TYPE.PROJECT)]: null }],
// tagMap: getTagMapList(),
// }
// );
// if (res && res.code === 200) {
// emits('update:currentProjectBaseInfo', { ...projectForm, nodeType: NODE_TYPE.PROJECT, id: res.data[0].uuid, uuid: res.data[0].uuid });
// ElMessage.success('创建项目成功');
// } else {
// ElMessage.error(res.msg || '创建项目失败');
// }
};
const editFlow = async() => {
// const projectForm = tableFormRef.value.getFormDataFun();
// const res:any = await editNodeApi({ editNodeList: [{ ...projectForm }] });
// if (res && res.code === 200) {
// ElMessage.success('编辑项目成功');
// } else {
// ElMessage.error(res.msg || '编辑项目失败');
// }
};
const closeFun = () => {
console.log('关闭弹窗');
emits('update:showDialog', false);
};
// const nextPageFun = () => {
// if (hasSameValue()) return;
// emits('update:currentProjectBaseInfo', { ...tableFormRef.value.getFormDataFun(), nodeType: NODE_TYPE.PROJECT });
// emits('nextPageFun', 'basePage');
// };
const setEditForm = (val: any) => {
console.log('val', val, tableFormRef.value);
nextTick(() => {
const memberList: number[] = [];
val?.memberList?.forEach((item: { id: number; }) => memberList.push(item.id));
tableFormRef.value.setFormDataFun({ ...val, memberList: memberList.join(',') });
});
};
const resetForm = () => {
// projectForm.nodeName = '';
// projectForm.nodeCode = '';
// projectForm.nodeSubType = '';
// projectForm.memberList = [];
// projectForm.beginTime = '';
// projectForm.endTime = '';
// projectForm.description = '';
tableFormRef.value.resetFun();
};
const setOptionsFun = (key: string, options: any[]) => {
console.log('key options', key, options);
tableFormRef.value.setOptionsFun(key, options);
};
defineExpose({
// projectForm,
setEditForm,
resetForm,
setOptionsFun,
});
</script>

View File

@@ -1,3 +1,448 @@
<template>
<div class="gl-page-content">仿真流程库</div>
<div class="gl-page-content" v-if="!showFlowDetailVisible">
<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-table-list">
<BaseTable
showIndex
ref="baseTableRef"
tableName="SIMULATION_FLOW_LIB_LIST"
:api="nodeListApi"
:searchLimitNum="3"
:listMode="viewType === 'list' ? 'default' : 'card'"
:actionsWidth="400"
>
<!-- :actionsWidth="tableActionsLength['2-2-2-2']" -->
<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">xxx</div>
<div class="project-manager">xxx</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="openDiaFun('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 #tableActions="{ row }">
<div class="gl-table-actions">
<el-link type="primary" @click="goProjectDetailFun(row.uuid, row.nodeName)">预览</el-link>
<el-link type="primary" @click="openDiaFun('edit', row)">重命名</el-link>
<el-link type="primary" @click="openDiaFun('edit', row)">复制</el-link>
<el-link type="primary" @click="openDiaFun('edit', row)">升版</el-link>
<el-link type="primary" @click="goFlowDetailFun(row)">编辑</el-link>
<el-popconfirm title="确认启用?" @confirm="deleteNodeFun(row)">
<template #reference>
<el-link type="primary">弃用</el-link>
</template>
</el-popconfirm>
<el-link type="primary" @click="openDiaFun('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>
<addDialog ref="addDiaRef" v-model:showDialog="showDialog" :dialogType="dialogType"></addDialog>
<flowCreate v-if="showFlowDetailVisible"></flowCreate>
</template>
<script setup lang="ts">
import { nextTick, reactive, ref } from 'vue';
import BaseTable from '@/components/common/table/baseTable.vue';
import { queryNodeListApi } from '@/api/project/node';
import { NODE_TYPE } from '@/utils/enum/node';
import { useDict } from '@/utils/useDict';
import dayjs from 'dayjs';
import addDialog from './components/addDialog.vue';
import flowCreate from '@/views/simulation/creation/index.vue';
const showFlowDetailVisible = ref(false);
const { PROJECT_TYPE, PROJECT_EXE_STATUS } = useDict('PROJECT_TYPE', 'PROJECT_EXE_STATUS');
const showDialog = ref(false);
const dialogType = ref('create'); // create 编辑 edit
const viewType = ref('list');
const addDiaRef = ref();
const openDiaFun = (tag: string, row?: any) => {
dialogType.value = tag;
showDialog.value = true;
if (tag === 'create') {
} else {
if (row) {
// 待优化
// setTimeout(() => {
nextTick(() => {
addDiaRef.value.setEditForm({ ...row });
});
// }, 1000);
}
}
};
const currentProject = reactive({
nodeName: '',
nodeId: '',
});
const goProjectDetailFun = (uuid: string, nodeName: string) => {
currentProject.nodeName = nodeName;
currentProject.nodeId = uuid;
// router.push({ path: '/task/projectDetail', query: { nodeId: uuid, nodeName } });
};
const goFlowDetailFun = (row: any) => {
console.log('row', row);
showFlowDetailVisible.value = true;
};
// 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 baseTableRef = ref();
const deleteNodeFun = async (row: any) => {
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>

View File

@@ -1,41 +1,10 @@
<template>
<div class="comp-content">
<!-- <el-button @click="openTaskDetailDialogFn()">
测试打开任务详情
</el-button> -->
<TreeCaseTable
ref="tableRef"
tableName="PROJECT_DETAIL_TREE_TABLE"
:data="tableList"
:actionsWidth="85"
:checkStrictly="true"
:readonly="true"
:loading="tableLoading"
>
<template #leftOptions>
<el-form label-width="60px">
<el-form-item label="阶段:">
<el-select
class="phase-select"
@change="updatePhaseUuid"
v-model="phaseUuid"
placeholder="阶段"
:options="phaseListOptions"
>
</el-select>
</el-form-item>
</el-form>
</template>
<template #rightOptions>
<el-button class="export-btn" @click="exportTaskFun">导出</el-button>
</template>
<template #tableActions="{ row }">
<div class="gl-table-actions">
<el-link v-if="row.nodeType === NODE_TYPE.TASK" type="primary" @click="openTaskDetailDialogFn(row)">查看详情</el-link>
</div>
</template>
</TreeCaseTable>
<!-- <loadcase-pro-table
<loadcase-pro-table
:loading="tableLoading"
ref="tableRef"
:columns="tableColumns"
@@ -57,24 +26,50 @@
}"
:showHeader="false"
>
</loadcase-pro-table> -->
<template #toolbar_tools_left_extra>
<el-form label-width="60px">
<el-form-item label="阶段:">
<el-select
class="phase-select"
@change="updatePhaseUuid"
v-model="phaseUuid"
placeholder="阶段"
:options="phaseListOptions"
>
</el-select>
</el-form-item>
</el-form>
</template>
<template #exportBtn>
<el-button class="export-btn" @click="exportTaskFun">导出</el-button>
</template>
<template #detail_default="{ row }">
<el-button
type="primary"
v-if="row.nodeType === NODE_TYPE.TASK"
link
@click="openTaskDetailDialogFn(row)"
>查看详情</el-button>
</template>
</loadcase-pro-table>
<taskDetail
v-if="showTaskDetailDialog"
:taskId="currentTaskInfo.id"
:current-task-info="currentTaskInfo"
@closeFn="showTaskDetailDialog = false"
/>
>
</taskDetail>
</div>
</template>
<script lang="ts" setup>
// import loadcaseProTable from '@/components/loadCaseTable/commonTable/loadcaseProTable.vue';
import loadcaseProTable from '@/components/loadCaseTable/commonTable/loadcaseProTable.vue';
import { getProjectColumn } from '@/components/loadCaseTable/utils/project';
import { nextTick, onMounted, ref } from 'vue';
import { getTaskTreeFun } from './projectApi';
import { NODE_TYPE } from '@/utils/enum/node';
import { getChildrenNodeListApi } from '@/api/project/node';
import taskDetail from './taskDetail.vue';
import TreeCaseTable from '@/components/common/treeCaseTable/index.vue';
const props = defineProps<{
projectUuid: string;
@@ -100,7 +95,7 @@ const updatePhaseUuid = async (value: string) => {
await getTaskTreeList();
console.log('getTaskTreeFun----------------tableList.value', tableList.value);
nextTick(() => {
// tableRef.value.changeLevel('全部展开');
tableRef.value.changeLevel('全部展开');
});
tableLoading.value = false;
};
@@ -130,6 +125,7 @@ const getPhaseListApi = async () => {
phaseUuid.value = phaseListOptions.value[0].value;
updatePhaseUuid(phaseListOptions.value[0].value);
}
}
} else {
phaseListOptions.value = [];

View File

@@ -303,7 +303,7 @@ const nodeListApi = async(params:any) => {
}
return item;
});
console.log('res', res);
// console.log('res', res);
return res;
}
// else {