Files
leaser_front/src/views/setting/system/process-settings/components/NodeConfigPanel.vue
T

552 lines
16 KiB
Vue
Raw Normal View History

2026-01-10 11:39:33 +08:00
<template>
<div class="node-config-panel" :class="{ 'is-modal': isModal }">
<div v-if="!isModal" class="panel-header">
<div class="panel-title">{{ panelTitle }}</div>
</div>
<div class="panel-content">
<!-- 条件节点配置 -->
<template v-if="nodeType === 'condition'">
<div class="config-section">
<div class="section-title">条件设置</div>
<a-form :model="conditionForm" layout="vertical" auto-label-width>
<a-form-item label="条件类型" field="fieldType">
<a-select
v-model="conditionForm.fieldType"
placeholder="请选择条件类型"
@change="handleConditionChange"
>
<a-option
v-for="option in fieldTypeOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</a-option>
</a-select>
</a-form-item>
<a-form-item
v-if="conditionForm.fieldType === 4"
label="发起人"
field="contractApprovers"
>
<a-select
v-model="conditionForm.contractApprovers"
:options="userList"
:field-names="{ value: 'id', label: 'realname' }"
placeholder="请选择发起人"
multiple
allow-clear
/>
</a-form-item>
<a-form-item
v-if="conditionForm.fieldType !== 4"
label="条件判断"
field="operatorType"
>
<a-select
v-model="conditionForm.operatorType"
placeholder="请选择运算类型"
>
<a-option
v-for="option in operatorTypeOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</a-option>
</a-select>
</a-form-item>
<a-form-item
v-if="conditionForm.fieldType === 2"
label="使用条款类型"
field="termType"
>
<a-select
v-model="conditionForm.termType"
placeholder="请选择使用条款类型"
>
<a-option
v-for="option in termTypeOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</a-option>
</a-select>
</a-form-item>
<a-form-item
v-if="
conditionForm.fieldType === 2 || conditionForm.fieldType === 3
"
label="值"
field="amount"
>
<a-input-number
v-model="conditionForm.amount"
:precision="2"
:min="0"
placeholder="请输入金额"
style="width: 100%"
/>
</a-form-item>
<a-form-item
v-if="conditionForm.fieldType === 1"
label="值"
field="area"
>
<div class="flex items-center gap-2">
<a-input-number
v-model="conditionForm.area"
:precision="2"
:min="0"
placeholder="请输入面积"
style="flex: 1"
/>
<a-select v-model="conditionForm.unit" style="width: 80px">
<a-option :value="1">元</a-option>
<a-option :value="2">元/㎡*月</a-option>
<a-option :value="3">㎡</a-option>
</a-select>
</div>
</a-form-item>
<a-form-item
v-if="
conditionForm.fieldType === 5 || conditionForm.fieldType === 6
"
label="值"
field="days"
>
<a-input-number
v-model="conditionForm.days"
:min="0"
placeholder="请输入天数"
style="width: 100%"
/>
</a-form-item>
</a-form>
</div>
<div class="config-tip">
<div class="tip-text"
>条件设置有:
面积、合同(租赁)单价、合同总价、发起人、免租期、装修期</div
>
</div>
</template>
<!-- 审批人节点配置 -->
<template v-if="nodeType === 'approver'">
<div class="config-section">
<a-form :model="approverForm" layout="vertical" auto-label-width>
<a-form-item label="选择审批人" field="approvers">
<a-select
v-model="approverForm.approvers"
:options="userList"
:field-names="{ value: 'id', label: 'realname' }"
placeholder="请选择审批人"
multiple
allow-clear
/>
</a-form-item>
<a-form-item label="通过方式" field="approvalType">
<a-radio-group v-model="approverForm.approvalType">
<a-radio
v-for="option in approvalTypeOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="审批不通过操作" field="rejectAction">
<a-select
v-model="approverForm.rejectAction"
placeholder="请选择"
>
<a-option
v-for="option in rejectActionOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</a-option>
</a-select>
</a-form-item>
</a-form>
</div>
<!-- 如果审批人为空,显示抄送人配置选项(用于支持从审批人节点转换为抄送节点) -->
<div v-if="approverForm.approvers.length === 0" class="config-section">
<div class="section-title">或配置为抄送节点</div>
<a-form :model="ccForm" layout="vertical" auto-label-width>
<a-form-item label="选择抄送人" field="ccto">
<a-select
v-model="ccForm.ccto"
:options="userList"
:field-names="{ value: 'id', label: 'realname' }"
placeholder="请选择抄送人"
multiple
allow-clear
/>
</a-form-item>
</a-form>
</div>
</template>
<!-- 抄送节点配置 -->
<template v-if="nodeType === 'cc'">
<div class="config-section">
<a-form :model="ccForm" layout="vertical" auto-label-width>
<a-form-item label="选择抄送人" field="ccto">
<a-select
v-model="ccForm.ccto"
:options="userList"
:field-names="{ value: 'id', label: 'realname' }"
placeholder="请选择抄送人"
multiple
allow-clear
/>
</a-form-item>
</a-form>
</div>
</template>
</div>
<div v-if="!isModal" class="panel-footer">
<a-button type="primary" @click="handleSave">保存</a-button>
<a-button type="outline" status="danger" @click="handleDelete"
>删除节点</a-button
>
</div>
<div v-else class="panel-footer-modal">
<a-button type="outline" status="danger" @click="handleDelete"
>删除节点</a-button
>
<a-button type="primary" @click="handleSave">保存</a-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, watch, onMounted } from 'vue';
import type { Node } from '@vue-flow/core';
import { getCommonUserList } from '@/api/common';
const props = defineProps<{
node: Node | null;
isModal?: boolean;
}>();
const emit = defineEmits<{
(e: 'save', data: any): void;
(e: 'delete'): void;
}>();
const userList = ref<any[]>([]);
const nodeType = computed(() => props.node?.type || '');
// 条件类型选项
const fieldTypeOptions = [
{ value: 1, label: '面积' },
{ value: 2, label: '合同单价' },
{ value: 3, label: '合同总价' },
{ value: 4, label: '发起人' },
{ value: 5, label: '免租期' },
{ value: 6, label: '装修期' },
];
// 运算类型选项
const operatorTypeOptions = [
{ value: 1, label: '大于(>)' },
{ value: 2, label: '大于等于(>=)' },
{ value: 3, label: '小于(<)' },
{ value: 4, label: '小于等于(<=)' },
{ value: 5, label: '等于(=)' },
{ value: 6, label: '区间值' },
];
// 使用条款类型选项
const termTypeOptions = [
{ value: 1, label: '租赁' },
{ value: 2, label: '物业' },
];
// 单位选项
const unitOptions = [
{ value: 1, label: '元' },
{ value: 2, label: '元/㎡*月' },
{ value: 3, label: '㎡' },
];
// 审批方式选项
const approvalTypeOptions = [
{ value: 1, label: '会签' },
{ value: 2, label: '或签' },
];
// 审批不通过操作选项
const rejectActionOptions = [
{ value: 1, label: '退回发起人' },
{ value: 2, label: '终止流程' },
];
const panelTitle = computed(() => {
if (nodeType.value === 'condition') return '条件判断';
if (nodeType.value === 'approver') return '审批人';
if (nodeType.value === 'cc') return '抄送人';
return '';
});
// 条件表单
const conditionForm = ref<any>({
fieldType: undefined,
operatorType: undefined,
termType: 0,
amount: '',
area: '',
unit: 3,
days: undefined,
contractApprovers: [],
});
// 审批人表单
const approverForm = ref<any>({
approvers: [],
approvalType: 2,
rejectAction: 1,
});
// 抄送表单
const ccForm = ref<any>({
ccto: [],
});
// 监听节点变化,更新表单
watch(
() => props.node,
(node) => {
if (!node) return;
if (node.type === 'condition') {
const firstRule = node.data?.rules?.[0] || {};
// 处理数值:接口返回的可能是字符串,需要转换为数字
const amount = firstRule.amount;
const area = firstRule.area;
const days = firstRule.days;
conditionForm.value = {
fieldType: firstRule.fieldType,
operatorType: firstRule.operatorType,
termType: firstRule.termType || 0,
// 转换数值:如果是字符串且不为空,转换为数字;如果是数字,直接使用;否则为空字符串
amount:
amount !== undefined && amount !== null && amount !== ''
? typeof amount === 'string'
? parseFloat(amount) || ''
: amount
: '',
area:
area !== undefined && area !== null && area !== ''
? typeof area === 'string'
? parseFloat(area) || ''
: area
: '',
unit: firstRule.unit || 3,
days:
days !== undefined && days !== null && days !== ''
? typeof days === 'string'
? parseInt(days, 10) || undefined
: days
: undefined,
contractApprovers:
firstRule.contractApprovers?.map((u: any) => u.id) || [],
};
} else if (node.type === 'approver') {
approverForm.value = {
approvers: node.data?.approvers?.map((u: any) => u.id) || [],
approvalType: node.data?.approvalType || 2,
rejectAction: node.data?.rejectAction || 1,
};
// 如果审批人节点有 ccto 数据,也加载到表单中(用于支持从审批人节点转换为抄送节点)
if (node.data?.ccto && node.data.ccto.length > 0) {
ccForm.value = {
ccto: node.data.ccto.map((u: any) => u.id) || [],
};
}
} else if (node.type === 'cc') {
ccForm.value = {
ccto: node.data?.ccto?.map((u: any) => u.id) || [],
};
}
},
{ immediate: true }
);
const handleConditionChange = () => {
// 切换条件类型时重置相关字段
conditionForm.value.operatorType = undefined;
conditionForm.value.amount = '';
conditionForm.value.area = '';
conditionForm.value.unit = 3;
conditionForm.value.days = undefined;
conditionForm.value.contractApprovers = [];
conditionForm.value.termType = 0;
};
const handleSave = () => {
if (nodeType.value === 'condition') {
// 获取选中的用户信息
const selectedUsers = conditionForm.value.contractApprovers
.map((id: number) => userList.value.find((u) => u.id === id))
.filter(Boolean);
const ruleData = {
...conditionForm.value,
contractApprovers: selectedUsers,
workflowsSettingNodesId: props.node?.data?.nodeId,
};
emit('save', {
rules: [ruleData],
});
} else if (nodeType.value === 'approver') {
// 获取选中的用户信息
const selectedUsers = approverForm.value.approvers
.map((id: number) => userList.value.find((u) => u.id === id))
.filter(Boolean);
// 如果审批人为空,但配置了抄送人,也保存抄送人数据(用于支持从审批人节点转换为抄送节点)
const selectedCcUsers = ccForm.value.ccto
.map((id: number) => userList.value.find((u) => u.id === id))
.filter(Boolean);
emit('save', {
...approverForm.value,
approvers: selectedUsers,
// 如果审批人为空且有抄送人,保存抄送人数据
...(selectedUsers.length === 0 && selectedCcUsers.length > 0
? { ccto: selectedCcUsers }
: {}),
});
} else if (nodeType.value === 'cc') {
// 获取选中的用户信息
const selectedUsers = ccForm.value.ccto
.map((id: number) => userList.value.find((u) => u.id === id))
.filter(Boolean);
emit('save', {
...ccForm.value,
ccto: selectedUsers,
});
}
// 如果是弹窗模式,保存后自动关闭
if (props.isModal) {
// 由父组件处理关闭
}
};
const handleDelete = () => {
emit('delete');
// 删除后如果是弹窗模式,需要关闭弹窗(由父组件处理)
};
onMounted(() => {
getUserListData();
});
async function getUserListData() {
try {
const { code, data } = await getCommonUserList();
if (code === 200) {
// 兼容不同的数据结构:可能是 data.user 或 data.userList 或直接是 data
userList.value =
data?.user || data?.userList || (Array.isArray(data) ? data : []);
}
} catch (error) {
// console.error('获取用户列表失败:', error);
}
}
</script>
<style lang="less" scoped>
.node-config-panel {
height: 100%;
display: flex;
flex-direction: column;
background: #fff;
border-left: 1px solid #e6e6eb;
}
.panel-header {
padding: 16px 20px;
border-bottom: 1px solid #e6e6eb;
}
.panel-title {
font-size: 16px;
font-weight: 500;
color: #1d2129;
}
.panel-content {
flex: 1;
overflow-y: auto;
padding: 20px;
}
.config-section {
margin-bottom: 20px;
}
.section-title {
font-size: 14px;
font-weight: 500;
color: #1d2129;
margin-bottom: 16px;
}
.config-tip {
padding: 12px;
background: #f7f8fa;
border-radius: 4px;
margin-top: 20px;
}
.tip-text {
font-size: 12px;
color: #86909c;
line-height: 18px;
}
.panel-footer {
padding: 16px 20px;
border-top: 1px solid #e6e6eb;
display: flex;
gap: 12px;
}
.panel-footer .arco-btn {
flex: 1;
}
.panel-footer-modal {
padding: 16px 0 0 0;
display: flex;
justify-content: flex-end;
gap: 12px;
}
.node-config-panel.is-modal {
.panel-content {
max-height: 60vh;
overflow-y: auto;
}
}
</style>