Files
leaser_front/src/views/setting/system/process-settings/components/NodeConfigPanel.vue
T
2026-01-10 11:39:33 +08:00

552 lines
16 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.
<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>