346 lines
9.8 KiB
Vue
346 lines
9.8 KiB
Vue
<!-- 流程节点编辑卡片(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>
|