Files
gxtyzx-admin/src/views/approval/flow/components/node-card.vue
T

346 lines
9.8 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>
<a-card size="small" class="node-card">
<template #title>
<span>{{ index + 1 }}. {{ form.name || '未命名节点' }}</span>
<a-tag color="blue" style="margin-left: 8px">
{{ nodeTypeLabel(form.type) }}
</a-tag>
</template>
<template #extra>
<a-space :size="4">
<a-button
type="link"
size="small"
:disabled="index === 0"
@click="emit('move', -1)"
>
上移
</a-button>
<a-button
type="link"
size="small"
:disabled="index === total - 1"
@click="emit('move', 1)"
>
下移
</a-button>
<a-button type="link" danger size="small" @click="emit('remove')">
删除
</a-button>
</a-space>
</template>
<a-form layout="vertical">
<a-row :gutter="16">
<a-col :md="8" :sm="24">
<a-form-item label="节点名称">
<a-input
v-model:value="form.name"
allow-clear
:maxlength="50"
placeholder="如:报送部门负责人"
/>
</a-form-item>
</a-col>
<a-col :md="8" :sm="24">
<a-form-item label="节点标识">
<a-input
v-model:value="form.key"
allow-clear
:maxlength="40"
placeholder="同一流程内唯一,如:deptLeader"
/>
</a-form-item>
</a-col>
<a-col :md="8" :sm="24">
<a-form-item label="节点类型">
<a-select v-model:value="form.type" :options="NODE_TYPE_OPTIONS" />
</a-form-item>
</a-col>
<a-col v-if="form.type !== 'COUNTERSIGN'" :md="8" :sm="24">
<a-form-item label="审批人解析器">
<a-select
v-model:value="form.resolver"
:options="resolverOptions"
allow-clear
show-search
option-filter-prop="label"
placeholder="选择解析器"
/>
</a-form-item>
</a-col>
<a-col v-if="isAttachmentNode" :md="8" :sm="24">
<a-form-item label="通过前必须上传附件">
<a-switch v-model:checked="form.attachmentRequired" size="small" />
</a-form-item>
</a-col>
<a-col v-if="needOrgCode" :md="8" :sm="24">
<a-form-item label="组织编码">
<a-select
v-model:value="form.orgCode"
:options="orgOptions"
allow-clear
show-search
option-filter-prop="label"
placeholder="选择组织"
/>
</a-form-item>
</a-col>
</a-row>
<div v-if="form.type === 'COUNTERSIGN'" class="node-section">
<div class="node-section-title">
<span>会签组(组内或签,组间会签)</span>
<a-button type="link" size="small" @click="addGroup">
添加会签组
</a-button>
</div>
<a-row
v-for="(group, groupIndex) in form.groups"
:key="groupIndex"
:gutter="12"
>
<a-col :md="5" :sm="24">
<a-form-item label="组标识">
<a-input v-model:value="group.key" allow-clear />
</a-form-item>
</a-col>
<a-col :md="5" :sm="24">
<a-form-item label="组名称">
<a-input v-model:value="group.name" allow-clear />
</a-form-item>
</a-col>
<a-col :md="6" :sm="24">
<a-form-item label="解析器">
<a-select
v-model:value="group.resolver"
:options="resolverOptions"
option-filter-prop="label"
/>
</a-form-item>
</a-col>
<a-col v-if="isOrgResolver(group.resolver)" :md="6" :sm="24">
<a-form-item label="组织编码">
<a-select
v-model:value="group.orgCode"
:options="orgOptions"
allow-clear
show-search
option-filter-prop="label"
placeholder="选择组织"
/>
</a-form-item>
</a-col>
<a-col :md="2" :sm="24">
<a-form-item label=" ">
<a-button type="link" danger @click="removeGroup(groupIndex)">
删除
</a-button>
</a-form-item>
</a-col>
</a-row>
</div>
<div v-if="writableVisible" class="node-section">
<div class="node-section-title">
<span>回填字段</span>
<a-button type="link" size="small" @click="addField">
添加回填字段
</a-button>
</div>
<a-row
v-for="(field, fieldIndex) in form.writableFields"
:key="fieldIndex"
:gutter="12"
>
<a-col :md="8" :sm="24">
<a-form-item label="字段">
<a-select
v-model:value="field.field"
:options="fieldOptions"
show-search
option-filter-prop="label"
placeholder="选择可回填字段"
/>
</a-form-item>
</a-col>
<a-col :md="4" :sm="24">
<a-form-item label="必填">
<a-switch v-model:checked="field.required" size="small" />
</a-form-item>
</a-col>
<a-col :md="4" :sm="24">
<a-form-item label=" ">
<a-button type="link" danger @click="removeField(fieldIndex)">
删除
</a-button>
</a-form-item>
</a-col>
</a-row>
</div>
</a-form>
</a-card>
</template>
<script lang="ts" setup>
import { computed, reactive, watch } from 'vue';
import {
NODE_TYPE_OPTIONS,
ORG_RESOLVERS,
WRITABLE_TYPES,
nodeTypeLabel
} from '../constants';
import type {
ApprovalFieldSpec,
ApprovalFlowNode,
ApprovalFlowNodeGroup
} from '@/api/approval/flow/model';
import type { ApprovalOrganization } from '@/api/approval/instance/model';
const emit = defineEmits<{
(e: 'update', node: ApprovalFlowNode): void;
(e: 'remove'): void;
(e: 'move', step: number): void;
}>();
const props = defineProps<{
node: ApprovalFlowNode;
index: number;
total: number;
resolvers: string[];
organizations: ApprovalOrganization[];
fieldSpecs: ApprovalFieldSpec[];
}>();
const form = reactive<ApprovalFlowNode>({
key: props.node.key,
type: props.node.type,
resolver: props.node.resolver,
orgCode: props.node.orgCode,
name: props.node.name,
groups: (props.node.groups || []).map((group) => ({ ...group })),
writableFields: (props.node.writableFields || []).map((field) => ({
...field
})),
attachmentRequired: !!props.node.attachmentRequired
});
const isAttachmentNode = computed(() => form.type !== 'DISPATCH');
const isOrgResolver = (resolver?: string) => {
return !!resolver && ORG_RESOLVERS.indexOf(resolver) !== -1;
};
const needOrgCode = computed(() => isOrgResolver(form.resolver));
const writableVisible = computed(() => {
return (
WRITABLE_TYPES.indexOf(form.type) !== -1 && props.fieldSpecs.length > 0
);
});
const resolverOptions = computed(() => {
return props.resolvers.map((resolver) => ({
value: resolver,
label: resolver
}));
});
const orgOptions = computed(() => {
return props.organizations.map((organization) => ({
value: organization.organizationCode,
label:
organization.organizationFullName || organization.organizationName || ''
}));
});
const fieldOptions = computed(() => {
return props.fieldSpecs.map((spec) => ({
value: spec.key,
label: spec.label + '(' + spec.key + ')'
}));
});
const addGroup = () => {
const group: ApprovalFlowNodeGroup = {
key: 'group' + ((form.groups ? form.groups.length : 0) + 1),
name: '会签组' + ((form.groups ? form.groups.length : 0) + 1),
resolver: 'orgLeaderByCode'
};
form.groups = [...(form.groups || []), group];
};
const removeGroup = (index: number) => {
form.groups = (form.groups || []).filter((item, i) => i !== index);
};
const addField = () => {
if (!props.fieldSpecs.length) {
return;
}
form.writableFields = [
...(form.writableFields || []),
{ field: props.fieldSpecs[0].key, required: false }
];
};
const removeField = (index: number) => {
form.writableFields = (form.writableFields || []).filter(
(item, i) => i !== index
);
};
/* 节点类型切换时同步清理不适用的配置,避免提交被后端拦下 */
watch(
() => form.type,
(type) => {
if (type === 'COUNTERSIGN') {
form.resolver = undefined;
form.orgCode = undefined;
if (!form.groups || !form.groups.length) {
addGroup();
}
} else if (form.groups && form.groups.length) {
form.groups = [];
}
}
);
/* 换了非按组织取人的解析器,组织编码就不再适用 */
watch(
() => form.resolver,
(resolver) => {
if (!isOrgResolver(resolver)) {
form.orgCode = undefined;
}
}
);
watch(
form,
() => {
emit('update', { ...form });
},
{ deep: true }
);
</script>
<style lang="less" scoped>
.node-card {
margin-bottom: 12px;
}
.node-section {
margin-bottom: 4px;
}
.node-section-title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 4px;
color: rgba(0, 0, 0, 0.65);
font-weight: 500;
}
</style>