明树Git Lab

Commit 330f85bb authored by zhanghan's avatar zhanghan

初版修改完毕

parent c3408065
Pipeline #111244 passed with stage
in 20 seconds
<template>
<div class="flow-chart">
<div class="flow-title">{{ flowTitle }}</div>
<div class="flow-body" ref="bodyRef">
<div class="flow-nodes">
<div
v-for="(node, index) in activeNodes"
:key="index"
class="flow-node-wrap"
:ref="el => nodeEls[index] = el"
>
<div
class="flow-node"
:class="[node.type || 'step', getNodeStatus(index)]"
>
<span class="node-label">{{ node.label }}</span>
</div>
<div v-if="node.annotation" class="flow-annotation">{{ node.annotation }}</div>
<div v-if="index < activeNodes.length - 1" class="flow-arrow">
<div class="arrow-line"></div>
<div class="arrow-head"></div>
</div>
</div>
</div>
<svg
v-if="returnPaths.length"
class="return-svg"
:width="svgWidth"
:height="svgHeight"
>
<defs>
<marker id="arrowMarker" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
<path d="M0,0 L8,3 L0,6 Z" fill="#909399" />
</marker>
</defs>
<g v-for="(path, i) in returnPaths" :key="i">
<polyline
:points="path.points"
fill="none"
stroke="#909399"
stroke-width="1.5"
stroke-dasharray="4,3"
marker-end="url(#arrowMarker)"
/>
<text
:x="path.textX"
:y="path.textY"
font-size="11"
fill="#909399"
text-anchor="middle"
dominant-baseline="middle"
:transform="`rotate(-90, ${path.textX}, ${path.textY})`"
>退回修改</text>
</g>
</svg>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, nextTick, watch } from "vue";
const props = defineProps({
flowType: {
type: String,
default: "complex",
},
currentState: {
type: Number,
default: 0,
},
});
const complexNodes = [
{ label: "开始", type: "terminal" },
{ label: "所属单位员工发起" },
{ label: "所属单位部门长核准", annotation: "提交/退回" },
{ label: "投管部正/副职指定经办人" },
{ label: "投管部经办人初审" },
{ label: "投管部正副职审核", returnTo: 2 },
{ label: "结束", type: "terminal" },
];
const simpleNodes = [
{ label: "开始", type: "terminal" },
{ label: "投管部员工发起" },
{ label: "投管部正副职审核", annotation: "提交/退回", returnTo: 1 },
{ label: "结束", type: "terminal" },
];
const complexLevelMap = {
1: 2, 2: 2, 7: 2,
3: 4,
4: 6, 8: 6,
5: 7, 9: 7, 13: 7, 14: 7, 15: 7,
};
const simpleLevelMap = {
1: 2, 2: 2,
3: 3, 4: 3,
5: 4,
};
const activeNodes = computed(() => {
return props.flowType === "simple" ? simpleNodes : complexNodes;
});
const highlightLevel = computed(() => {
const map = props.flowType === "simple" ? simpleLevelMap : complexLevelMap;
return map[props.currentState] || 0;
});
const flowTitle = computed(() => {
if (props.flowType === "simple") return "立项审批流程";
return props.currentState >= 7 ? "决策审批流程" : "立项审批流程";
});
const getNodeStatus = (index) => {
const level = highlightLevel.value;
const total = activeNodes.value.length;
if (level === 0 || index >= level) return "";
if (level >= total) return "completed";
if (index === level - 1) return "active";
return "completed";
};
const bodyRef = ref(null);
const nodeEls = ref([]);
const svgWidth = ref(0);
const svgHeight = ref(0);
const returnPaths = ref([]);
const calcReturnPaths = () => {
const body = bodyRef.value;
if (!body) return;
const bodyRect = body.getBoundingClientRect();
svgWidth.value = bodyRect.width;
svgHeight.value = bodyRect.height;
const paths = [];
const nodes = activeNodes.value;
let offsetX = 0;
nodes.forEach((node, index) => {
if (node.returnTo === undefined) return;
const sourceEl = nodeEls.value[index];
const targetEl = nodeEls.value[node.returnTo];
if (!sourceEl || !targetEl) return;
const sourceNode = sourceEl.querySelector(".flow-node");
const targetNode = targetEl.querySelector(".flow-node");
if (!sourceNode || !targetNode) return;
const sourceRect = sourceNode.getBoundingClientRect();
const targetRect = targetNode.getBoundingClientRect();
const sourceY = sourceRect.top + sourceRect.height / 2 - bodyRect.top;
const targetY = targetRect.top + targetRect.height / 2 - bodyRect.top;
const nodeLeft = sourceRect.left - bodyRect.left;
offsetX += 20;
const lineX = nodeLeft - offsetX;
paths.push({
points: `${nodeLeft},${sourceY} ${lineX},${sourceY} ${lineX},${targetY} ${nodeLeft},${targetY}`,
textX: lineX - 8,
textY: (sourceY + targetY) / 2,
});
});
returnPaths.value = paths;
};
onMounted(() => {
nextTick(calcReturnPaths);
});
watch(
() => [props.flowType, props.currentState],
() => {
nextTick(() => nextTick(calcReturnPaths));
}
);
</script>
<style scoped lang="scss">
.flow-chart {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px 12px;
min-width: 200px;
}
.flow-title {
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 16px;
}
.flow-body {
position: relative;
width: 100%;
padding-left: 90px;
}
.flow-nodes {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.return-svg {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
overflow: visible;
}
.flow-node-wrap {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
width: 100%;
}
.flow-node {
min-width: 200px;
max-width: 280px;
padding: 10px 24px;
text-align: center;
border: 2px solid #dcdfe6;
background: #fff;
font-size: 13px;
color: #606266;
transition: all 0.3s;
&.terminal {
border-radius: 20px;
background: #f5f7fa;
border-color: #c0c4cc;
color: #909399;
}
&.step {
border-radius: 4px;
}
&.active {
border-color: #409eff;
background: #409eff;
color: #fff;
font-weight: 600;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.4);
}
&.completed {
border-color: #409eff;
background: #ecf5ff;
color: #409eff;
}
&.terminal.completed {
background: #ecf5ff;
border-color: #409eff;
color: #409eff;
}
&.terminal.active {
background: #409eff;
border-color: #409eff;
color: #fff;
}
}
.node-label {
white-space: nowrap;
}
.flow-annotation {
font-size: 11px;
color: #909399;
margin-top: 2px;
}
.flow-arrow {
display: flex;
flex-direction: column;
align-items: center;
height: 28px;
.arrow-line {
width: 2px;
flex: 1;
background: #c0c4cc;
}
.arrow-head {
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 6px solid #c0c4cc;
}
}
</style>
...@@ -48,36 +48,41 @@ ...@@ -48,36 +48,41 @@
:close-on-click-modal="false" :close-on-click-modal="false"
:close-on-press-escape="true" :close-on-press-escape="true"
> >
<div class="process-dialog-content"> <div class="process-dialog-content process-layout">
<div class="process-project-name" v-if="processInfo.projectName"> <div class="process-left">
项目名称:<span class="name">{{ processInfo.projectName }}</span> <div class="process-project-name" v-if="processInfo.projectName">
项目名称:<span class="name">{{ processInfo.projectName }}</span>
</div>
<el-table
:data="processData"
border
style="width: 100%"
class="process-table"
:header-cell-style="{
background: '#f5f7fa',
color: '#333',
textAlign: 'center',
fontWeight: 600,
}"
:cell-style="{ textAlign: 'center' }"
>
<el-table-column type="index" label="序号" width="60" />
<el-table-column prop="actionName" label="操作" min-width="120" />
<el-table-column label="操作人" min-width="100">
<template #default="{ row }">{{ row.creator?.name }}</template>
</el-table-column>
<el-table-column prop="actionName" label="项目步骤" min-width="120" />
<el-table-column
prop="approvalMessage"
label="审核意见"
min-width="120"
/>
<el-table-column prop="createdAt" label="时间" min-width="160" />
</el-table>
</div>
<div class="process-right">
<ProcessFlowChart :flow-type="flowType" :current-state="currentState" />
</div> </div>
<el-table
:data="processData"
border
style="width: 100%"
class="process-table"
:header-cell-style="{
background: '#f5f7fa',
color: '#333',
textAlign: 'center',
fontWeight: 600,
}"
:cell-style="{ textAlign: 'center' }"
>
<el-table-column type="index" label="序号" width="80" />
<el-table-column prop="actionName" label="操作" min-width="150" />
<el-table-column label="操作人" min-width="120">
<template #default="{ row }">{{ row.creator?.name }}</template>
</el-table-column>
<el-table-column prop="actionName" label="项目步骤" min-width="150" />
<el-table-column
prop="approvalMessage"
label="审核意见"
min-width="150"
/>
<el-table-column prop="createdAt" label="时间" min-width="180" />
</el-table>
</div> </div>
<template #footer> <template #footer>
<el-button @click="processDialogVisible = false">关闭</el-button> <el-button @click="processDialogVisible = false">关闭</el-button>
...@@ -88,6 +93,7 @@ ...@@ -88,6 +93,7 @@
<script setup> <script setup>
import { ref } from "vue"; import { ref } from "vue";
import { useRouter, useRoute } from "vue-router"; import { useRouter, useRoute } from "vue-router";
import ProcessFlowChart from "./ProcessFlowChart.vue";
const props = defineProps({ const props = defineProps({
showSave: { showSave: {
...@@ -126,6 +132,14 @@ const props = defineProps({ ...@@ -126,6 +132,14 @@ const props = defineProps({
type: Array, type: Array,
default: () => [], default: () => [],
}, },
flowType: {
type: String,
default: "complex",
},
currentState: {
type: Number,
default: 0,
},
}); });
const emit = defineEmits(["save", "back", "export", "process"]); const emit = defineEmits(["save", "back", "export", "process"]);
...@@ -187,6 +201,26 @@ const handleProcess = () => { ...@@ -187,6 +201,26 @@ const handleProcess = () => {
} }
} }
.process-layout {
display: flex;
gap: 16px;
}
.process-left {
flex: 1;
min-width: 0;
overflow: auto;
}
.process-right {
width: 420px;
flex-shrink: 0;
border-left: 1px solid #ebeef5;
display: flex;
align-items: flex-start;
justify-content: center;
}
.process-project-name { .process-project-name {
font-size: 14px; font-size: 14px;
margin-bottom: 12px; margin-bottom: 12px;
......
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
:show-process="!!projectId" :show-process="!!projectId"
:process-info="processInfo" :process-info="processInfo"
:process-data="processTableData" :process-data="processTableData"
:flow-type="flowType"
:current-state="currentFlowState"
@save="() => saveClick('save')" @save="() => saveClick('save')"
@process="getProcessData" @process="getProcessData"
></routerBack> ></routerBack>
...@@ -219,6 +221,17 @@ userInfo.roles.map((item) => { ...@@ -219,6 +221,17 @@ userInfo.roles.map((item) => {
} }
}); });
const isTouGuanBu = userInfo.departs?.some(d => d.name === '投资管理部门');
const currentFlowState = computed(() => {
const lxState = Number(formData.projectLzType) || 0;
const jcState = Number(jcFormData.projectLzType) || 0;
return jcState >= 7 ? jcState : lxState;
});
const flowType = computed(() => {
if (isTouGuanBu && currentFlowState.value >= 1 && currentFlowState.value <= 6) return 'simple';
return 'complex';
});
// tab相关 // tab相关
const pageActiveName = ref("立项填报"); const pageActiveName = ref("立项填报");
const lxTabActiveName = ref("基本信息"); const lxTabActiveName = ref("基本信息");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment