Files
gxtyzx-admin/src/views/approval/flow/index.vue
T

365 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 流程配置(ADR 0015):编辑节点链并发布新版本 -->
<template>
<div class="ele-body">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-split-layout
width="320px"
allow-collapse
:style="{ minHeight: 'calc(100vh - 152px)' }"
>
<a-spin :spinning="loading">
<a-list :data-source="flows" size="small">
<template #renderItem="{ item }">
<a-list-item
class="flow-item"
:class="{ 'flow-item-active': item.flowCode === flowCode }"
@click="selectFlow(item)"
>
<div class="flow-item-body">
<div class="flow-item-title">{{ item.flowName }}</div>
<div class="flow-item-meta">
{{
item.version ? '当前版本 v' + item.version : '尚未发布'
}}
</div>
</div>
</a-list-item>
</template>
</a-list>
</a-spin>
<template #content>
<div v-if="current">
<a-alert
type="warning"
show-icon
message="保存即发布新版本,仅对之后提交的单据生效;已提交的单据继续走原版本。"
style="margin-bottom: 12px"
/>
<div class="flow-toolbar">
<div class="flow-toolbar-title">
{{ current.flowName }}
<span class="ele-text-secondary flow-toolbar-meta">
{{
current.version
? 'v' + current.version + ' · ' + current.publishTime
: '尚未发布(出厂模板 v' + current.factoryVersion + ')'
}}
</span>
</div>
<a-space :size="8">
<a-button @click="loadFactory">载入出厂模板</a-button>
<a-button :disabled="!current.version" @click="openHistory">
版本历史
</a-button>
<a-button
v-permission="'approval:flow:publish'"
type="primary"
:loading="saving"
@click="publish"
>
保存并发布
</a-button>
</a-space>
</div>
<a-empty
v-if="!nodes.length"
description="该流程尚未发布,点击「载入出厂模板」开始编辑"
/>
<node-card
v-for="(node, index) in nodes"
:key="editorRevision + '-' + index + '-' + node.key"
:node="node"
:index="index"
:total="nodes.length"
:resolvers="current.resolvers || []"
:organizations="organizations"
:field-specs="current.fieldSpecs || []"
@update="updateNode(index, $event)"
@remove="removeNode(index)"
@move="moveNode(index, $event)"
/>
<a-button
v-if="nodes.length"
type="dashed"
block
class="flow-add"
@click="addNode"
>
添加节点
</a-button>
</div>
<a-empty v-else description="请选择左侧流程" />
</template>
</ele-split-layout>
</a-card>
<a-drawer v-model:open="historyVisible" title="版本历史" :width="560">
<a-spin :spinning="historyLoading">
<a-empty v-if="!history.length" description="暂无版本" />
<a-card
v-for="item in history"
:key="item.definitionId"
size="small"
class="history-card"
>
<template #title>
<span>v{{ item.version }}</span>
<span class="ele-text-secondary" style="margin-left: 8px">
{{ item.publishTime }}
</span>
</template>
<template #extra>
<a-button type="link" size="small" @click="loadVersion(item)">
以此版本为蓝本
</a-button>
</template>
<div
v-for="(node, nodeIndex) in item.nodes"
:key="nodeIndex"
class="history-node"
>
{{ nodeIndex + 1 }}. {{ node.name }}({{
nodeTypeLabel(node.type)
}})
</div>
</a-card>
</a-spin>
</a-drawer>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { Modal, message } from 'ant-design-vue';
import {
listFlowConfigs,
listFlowHistory,
publishFlowConfig
} from '@/api/approval/flow';
import { listApprovalOrganizations } from '@/api/approval/directory';
import NodeCard from './components/node-card.vue';
import { nodeTypeLabel } from './constants';
import type {
ApprovalFlow,
ApprovalFlowNode,
ApprovalFlowVersion
} from '@/api/approval/flow/model';
import type { ApprovalOrganization } from '@/api/approval/instance/model';
const loading = ref(false);
const saving = ref(false);
const flows = ref<ApprovalFlow[]>([]);
const current = ref<ApprovalFlow | null>(null);
const flowCode = ref('');
const nodes = ref<ApprovalFlowNode[]>([]);
const editorRevision = ref(0);
const organizations = ref<ApprovalOrganization[]>([]);
const historyVisible = ref(false);
const historyLoading = ref(false);
const history = ref<ApprovalFlowVersion[]>([]);
/* 深拷贝节点:编辑区不能直接改列表里的对象 */
const cloneNodes = (nodes?: ApprovalFlowNode[]) => {
return JSON.parse(JSON.stringify(nodes || [])) as ApprovalFlowNode[];
};
/* 加载编辑区:整链替换时递增 revision,强制节点卡片重新初始化 */
const applyFlow = (flow: ApprovalFlow) => {
current.value = flow;
flowCode.value = flow.flowCode;
nodes.value = cloneNodes(flow.nodes);
editorRevision.value = editorRevision.value + 1;
};
const refreshFlows = (keepCode?: string) => {
loading.value = true;
return listFlowConfigs()
.then((data) => {
loading.value = false;
flows.value = data;
const code = keepCode || flowCode.value;
const item = data.find((flow) => flow.flowCode === code);
if (item) {
applyFlow(item);
}
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
};
const selectFlow = (flow: ApprovalFlow) => {
applyFlow(flow);
};
const updateNode = (index: number, node: ApprovalFlowNode) => {
const list = [...nodes.value];
list[index] = node;
nodes.value = list;
};
const removeNode = (index: number) => {
nodes.value = nodes.value.filter((item, i) => i !== index);
};
const moveNode = (index: number, step: number) => {
const target = index + step;
if (target < 0 || target >= nodes.value.length) {
return;
}
const list = [...nodes.value];
const temp = list[index];
list[index] = list[target];
list[target] = temp;
nodes.value = list;
editorRevision.value = editorRevision.value + 1;
};
const addNode = () => {
const next: ApprovalFlowNode = {
key: 'node' + (nodes.value.length + 1),
type: 'APPROVE',
name: '新节点',
resolver: 'reporterOrgLeader'
};
nodes.value = [...nodes.value, next];
};
const loadFactory = () => {
if (!current.value) {
return;
}
Modal.confirm({
title: '载入出厂模板?',
content:
'会用代码里声明的节点覆盖当前编辑区,需要再点「保存并发布」才生效。',
okText: '载入',
cancelText: '取消',
onOk: () => {
nodes.value = cloneNodes(current.value?.factoryNodes);
editorRevision.value = editorRevision.value + 1;
}
});
};
const openHistory = () => {
if (!current.value) {
return;
}
historyVisible.value = true;
historyLoading.value = true;
listFlowHistory(current.value.flowCode)
.then((data) => {
historyLoading.value = false;
history.value = data;
})
.catch((e) => {
historyLoading.value = false;
message.error(e.message);
});
};
const loadVersion = (item: ApprovalFlowVersion) => {
nodes.value = cloneNodes(item.nodes);
editorRevision.value = editorRevision.value + 1;
historyVisible.value = false;
message.info('已载入 v' + item.version + ',确认后点「保存并发布」');
};
const publish = () => {
if (!current.value) {
return;
}
if (!nodes.value.length) {
message.warning('请先配置流程节点');
return;
}
Modal.confirm({
title: '确认发布新版本?',
content:
'保存即发布,立即对之后提交的单据生效;已提交的单据继续走原版本。',
okText: '发布',
cancelText: '取消',
onOk: () => {
saving.value = true;
return publishFlowConfig({
flowCode: current.value!.flowCode,
nodes: nodes.value
})
.then((data) => {
saving.value = false;
message.success('已发布 v' + data.version);
return refreshFlows(data.flowCode);
})
.catch((e) => {
saving.value = false;
message.error(e.message);
});
}
});
};
onMounted(() => {
refreshFlows();
listApprovalOrganizations()
.then((data) => {
organizations.value = data;
})
.catch(() => {});
});
</script>
<style lang="less" scoped>
.flow-item {
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
}
.flow-item-active {
background: rgba(24, 144, 255, 0.08);
}
.flow-item-title {
color: rgba(0, 0, 0, 0.85);
font-weight: 500;
}
.flow-item-meta {
color: rgba(0, 0, 0, 0.45);
font-size: 12px;
}
.flow-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.flow-toolbar-title {
color: rgba(0, 0, 0, 0.85);
font-size: 16px;
font-weight: 500;
}
.flow-toolbar-meta {
margin-left: 8px;
font-size: 12px;
font-weight: 400;
}
.flow-add {
margin-bottom: 12px;
}
.history-card {
margin-bottom: 12px;
}
.history-node {
color: rgba(0, 0, 0, 0.65);
line-height: 24px;
}
</style>