feat: 设置-流程设置 90%
This commit is contained in:
@@ -0,0 +1,551 @@
|
||||
<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>
|
||||
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
:width="430"
|
||||
:visible="visible"
|
||||
:title="isEdit ? '编辑审批流程' : '新增审批流程'"
|
||||
unmount-on-close
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
auto-label-width
|
||||
layout="vertical"
|
||||
>
|
||||
<a-form-item label="名称" field="name" required>
|
||||
<a-input
|
||||
v-model="form.name"
|
||||
:placeholder="$t('please.enter', { placeholder: '名称' })"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="审批类型" field="workflowsTypeId">
|
||||
<a-select v-model="form.workflowsTypeId" placeholder="请选择审批类型">
|
||||
<a-option
|
||||
v-for="item in approvalType"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<!-- 适用范围(部门) -->
|
||||
<a-form-item label="适用范围" field="projectIds">
|
||||
<a-select
|
||||
v-model="form.projectIds"
|
||||
multiple
|
||||
placeholder="请选择审批类型"
|
||||
>
|
||||
<a-option
|
||||
v-for="item in allProjetList"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" field="remark">
|
||||
<a-textarea
|
||||
v-model="form.remark"
|
||||
placeholder="输入详细原因"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, reactive } from 'vue';
|
||||
import type { FormInstance } from '@arco-design/web-vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { createWorkflows } from '@/api/setting';
|
||||
import { getAllProjectIdNameList, getWorkflowsTypeList } from '@/api/common';
|
||||
const allProjetList = ref<any>([]);
|
||||
const isLoading = ref<boolean>(false);
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
isEdit: boolean;
|
||||
record?: any | null;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void;
|
||||
(e: 'submitted'): void;
|
||||
}>();
|
||||
const approvalType = ref<any>([]);
|
||||
// 表单数据
|
||||
const formRef = ref<FormInstance>();
|
||||
const defaultForm: any = {
|
||||
name: '',
|
||||
workflowsTypeId: null,
|
||||
projectIds: null,
|
||||
remark: '',
|
||||
};
|
||||
let form = reactive<any>({ ...defaultForm });
|
||||
// 校验规则
|
||||
const rules = reactive({
|
||||
name: [{ required: true, message: '请输入合同编号' }],
|
||||
workflowsTypeId: [{ required: true, message: '请选择审批类型' }],
|
||||
projectIds: [{ required: true, message: '请选择适用范围' }],
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
if (val) {
|
||||
if (props.isEdit && props.record) {
|
||||
// 编辑模式:回显数据
|
||||
Object.assign(form, {
|
||||
name: props.record.name || '',
|
||||
workflowsTypeId: props.record.workflowsTypeId || null,
|
||||
projectIds: props.record.projectIds || null,
|
||||
remark: props.record.remark || '',
|
||||
});
|
||||
} else {
|
||||
// 新增模式:重置表单
|
||||
Object.assign(form, defaultForm);
|
||||
}
|
||||
if (allProjetList.value?.length === 0) getAllProjectListData();
|
||||
if (approvalType.value?.length === 0) getWorkflowsTypeListData();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const handleOk = async () => {
|
||||
if (isLoading.value) return;
|
||||
try {
|
||||
isLoading.value = true;
|
||||
const valid = await formRef.value.validate();
|
||||
if (valid) return;
|
||||
const params = {
|
||||
...form,
|
||||
// projectIds 可能是单个值或数组,统一转为数组
|
||||
projectIds: form.projectIds
|
||||
? Array.isArray(form.projectIds)
|
||||
? form.projectIds
|
||||
: [form.projectIds]
|
||||
: [],
|
||||
};
|
||||
|
||||
// 如果是编辑模式,添加 id
|
||||
if (props.isEdit && props.record?.id) {
|
||||
params.id = props.record.id;
|
||||
}
|
||||
|
||||
const res = await createWorkflows(params);
|
||||
if (res?.code === 200) {
|
||||
Message.success(res?.message);
|
||||
form = { ...defaultForm };
|
||||
emit('submitted');
|
||||
emit('update:visible', false);
|
||||
}
|
||||
} catch (err) {
|
||||
// 捕获异常,暂不处理
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
// 部门列表
|
||||
async function getAllProjectListData() {
|
||||
try {
|
||||
const { code, data } = await getAllProjectIdNameList();
|
||||
if (code === 200) {
|
||||
allProjetList.value = data?.projectsList || [];
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error('获取部门列表失败:', error);
|
||||
}
|
||||
}
|
||||
async function getWorkflowsTypeListData() {
|
||||
try {
|
||||
const { code, data } = await getWorkflowsTypeList();
|
||||
if (code === 200) {
|
||||
approvalType.value = data.workflowsTypeList;
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(label.arco-form-item-label) {
|
||||
width: 100% !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div class="approver-node" @click="handleClick">
|
||||
<!-- 输入连接点(顶部) -->
|
||||
<Handle type="target" :position="Position.Top" />
|
||||
<div class="node-content">
|
||||
<div class="node-header">
|
||||
<div class="node-icon">
|
||||
<icon-customer-service :size="20" />
|
||||
</div>
|
||||
<div class="node-label">审批</div>
|
||||
</div>
|
||||
<div v-if="approversText" class="node-desc">
|
||||
{{ approversText }}
|
||||
</div>
|
||||
<div v-if="approvalTypeText" class="node-desc">
|
||||
审批方式: {{ approvalTypeText }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 输出连接点(底部) -->
|
||||
<Handle type="source" :position="Position.Bottom" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { Handle, Position } from '@vue-flow/core';
|
||||
import { IconCustomerService } from '@arco-design/web-vue/es/icon';
|
||||
|
||||
const props = defineProps<{
|
||||
data?: {
|
||||
nodeId?: number;
|
||||
approvers?: any[];
|
||||
approvalType?: number; // 1会签,2或签
|
||||
label?: string;
|
||||
};
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'click', nodeId?: number): void;
|
||||
}>();
|
||||
|
||||
const approversText = computed(() => {
|
||||
const approvers = props.data?.approvers || [];
|
||||
if (approvers.length > 0) {
|
||||
const names = approvers.map((u: any) => u.realname || u.name).join('、');
|
||||
return names;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
const approvalTypeText = computed(() => {
|
||||
return props.data?.approvalType === 1 ? '会签' : '或签';
|
||||
});
|
||||
|
||||
const lastClickTime = ref(0);
|
||||
const DOUBLE_CLICK_DELAY = 300;
|
||||
|
||||
const handleClick = () => {
|
||||
const now = Date.now();
|
||||
// 判断是否为双击
|
||||
if (now - lastClickTime.value < DOUBLE_CLICK_DELAY) {
|
||||
// 双击事件
|
||||
emit('click', props.data?.nodeId);
|
||||
lastClickTime.value = 0;
|
||||
} else {
|
||||
lastClickTime.value = now;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.approver-node {
|
||||
position: relative;
|
||||
padding: 12px 16px;
|
||||
background: #e8ffea;
|
||||
border: 2px solid #81b337;
|
||||
border-radius: 8px;
|
||||
min-width: 140px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 2px 8px rgba(129, 179, 55, 0.3);
|
||||
}
|
||||
|
||||
.node-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.node-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.node-icon {
|
||||
color: #81b337;
|
||||
}
|
||||
|
||||
.node-label {
|
||||
font-size: 14px;
|
||||
color: #1d2129;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.node-desc {
|
||||
font-size: 12px;
|
||||
color: #86909c;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div class="cc-node" @click="handleClick">
|
||||
<!-- 输入连接点(顶部) -->
|
||||
<Handle type="target" :position="Position.Top" />
|
||||
<div class="node-content">
|
||||
<div class="node-header">
|
||||
<div class="node-icon">
|
||||
<icon-send :size="20" />
|
||||
</div>
|
||||
<div class="node-label">抄送</div>
|
||||
</div>
|
||||
<div v-if="ccText" class="node-desc">
|
||||
{{ ccText }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 输出连接点(底部) -->
|
||||
<Handle type="source" :position="Position.Bottom" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { Handle, Position } from '@vue-flow/core';
|
||||
import { IconSend } from '@arco-design/web-vue/es/icon';
|
||||
|
||||
const props = defineProps<{
|
||||
data?: {
|
||||
nodeId?: number;
|
||||
ccto?: any[];
|
||||
label?: string;
|
||||
};
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'click', nodeId?: number): void;
|
||||
}>();
|
||||
|
||||
const ccText = computed(() => {
|
||||
const ccto = props.data?.ccto || [];
|
||||
if (ccto.length > 0) {
|
||||
const names = ccto.map((u: any) => u.realname || u.name).join('、');
|
||||
return names;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
const lastClickTime = ref(0);
|
||||
const DOUBLE_CLICK_DELAY = 300;
|
||||
|
||||
const handleClick = () => {
|
||||
const now = Date.now();
|
||||
// 判断是否为双击
|
||||
if (now - lastClickTime.value < DOUBLE_CLICK_DELAY) {
|
||||
// 双击事件
|
||||
emit('click', props.data?.nodeId);
|
||||
lastClickTime.value = 0;
|
||||
} else {
|
||||
lastClickTime.value = now;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.cc-node {
|
||||
position: relative;
|
||||
padding: 12px 16px;
|
||||
background: #e8ffea;
|
||||
border: 2px solid #81b337;
|
||||
border-radius: 8px;
|
||||
min-width: 140px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 2px 8px rgba(129, 179, 55, 0.3);
|
||||
}
|
||||
|
||||
.node-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.node-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.node-icon {
|
||||
color: #81b337;
|
||||
}
|
||||
|
||||
.node-label {
|
||||
font-size: 14px;
|
||||
color: #1d2129;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.node-desc {
|
||||
font-size: 12px;
|
||||
color: #86909c;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<div class="condition-node" @click="handleClick">
|
||||
<!-- 输入连接点(顶部) -->
|
||||
<Handle type="target" :position="Position.Top" />
|
||||
<div class="node-content">
|
||||
<div class="node-header">
|
||||
<div class="node-icon">
|
||||
<icon-share-alt :size="20" />
|
||||
</div>
|
||||
<div class="node-label">{{ conditionTitle }}</div>
|
||||
</div>
|
||||
<div v-if="conditionText" class="node-desc">
|
||||
{{ conditionText }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 输出连接点(底部) -->
|
||||
<Handle type="source" :position="Position.Bottom" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { Handle, Position } from '@vue-flow/core';
|
||||
import { IconShareAlt } from '@arco-design/web-vue/es/icon';
|
||||
|
||||
const props = defineProps<{
|
||||
data?: {
|
||||
nodeId?: number;
|
||||
rules?: any[];
|
||||
label?: string;
|
||||
};
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'click', nodeId?: number): void;
|
||||
}>();
|
||||
|
||||
const fieldTypeMap: Record<number, string> = {
|
||||
1: '面积',
|
||||
2: '合同单价',
|
||||
3: '合同总价',
|
||||
4: '发起人',
|
||||
5: '免租期',
|
||||
6: '装修期',
|
||||
};
|
||||
|
||||
const operatorTypeMap: Record<number, string> = {
|
||||
1: '大于(>)',
|
||||
2: '大于等于(>=)',
|
||||
3: '小于(<)',
|
||||
4: '小于等于(<=)',
|
||||
5: '等于(=)',
|
||||
6: '区间值',
|
||||
};
|
||||
|
||||
const unitMap: Record<number, string> = {
|
||||
1: '元',
|
||||
2: '元/㎡*月',
|
||||
3: '㎡',
|
||||
};
|
||||
|
||||
const conditionTitle = computed(() => {
|
||||
const firstRule = props.data?.rules?.[0];
|
||||
if (firstRule) {
|
||||
const fieldType = fieldTypeMap[firstRule.fieldType] || '条件';
|
||||
return `条件:判断${fieldType}`;
|
||||
}
|
||||
return '条件';
|
||||
});
|
||||
|
||||
const conditionText = computed(() => {
|
||||
const firstRule = props.data?.rules?.[0];
|
||||
if (!firstRule) return '';
|
||||
|
||||
const fieldType = fieldTypeMap[firstRule.fieldType];
|
||||
if (!fieldType) return '';
|
||||
|
||||
if (firstRule.fieldType === 4) {
|
||||
// 发起人
|
||||
const approvers = firstRule.contractApprovers || [];
|
||||
if (approvers.length > 0) {
|
||||
const names = approvers
|
||||
.map((u: any) => u.realname || u.name)
|
||||
.join('、');
|
||||
return `发起人: ${names}`;
|
||||
}
|
||||
} else {
|
||||
// 其他类型
|
||||
const operator = operatorTypeMap[firstRule.operatorType] || '';
|
||||
let value = '';
|
||||
|
||||
if (firstRule.fieldType === 1) {
|
||||
// 面积
|
||||
value = `${firstRule.area || ''}${unitMap[firstRule.unit] || '㎡'}`;
|
||||
} else if (firstRule.fieldType === 2 || firstRule.fieldType === 3) {
|
||||
// 金额
|
||||
value = `${firstRule.amount || ''}元`;
|
||||
} else if (firstRule.fieldType === 5 || firstRule.fieldType === 6) {
|
||||
// 天数
|
||||
value = `${firstRule.days || ''}天`;
|
||||
}
|
||||
|
||||
if (operator && value) {
|
||||
return `${fieldType}:${operator} ${value}`;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
|
||||
const lastClickTime = ref(0);
|
||||
const DOUBLE_CLICK_DELAY = 300;
|
||||
|
||||
const handleClick = () => {
|
||||
const now = Date.now();
|
||||
// 判断是否为双击
|
||||
if (now - lastClickTime.value < DOUBLE_CLICK_DELAY) {
|
||||
// 双击事件
|
||||
emit('click', props.data?.nodeId);
|
||||
lastClickTime.value = 0;
|
||||
} else {
|
||||
lastClickTime.value = now;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.condition-node {
|
||||
position: relative;
|
||||
padding: 12px 16px;
|
||||
background: #fff7e8;
|
||||
border: 2px solid #ffbf6b;
|
||||
border-radius: 8px;
|
||||
min-width: 140px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 2px 8px rgba(255, 191, 107, 0.3);
|
||||
}
|
||||
|
||||
.node-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.node-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.node-icon {
|
||||
color: #ffbf6b;
|
||||
}
|
||||
|
||||
.node-label {
|
||||
font-size: 14px;
|
||||
color: #1d2129;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.node-desc {
|
||||
font-size: 12px;
|
||||
color: #86909c;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div class="end-node">
|
||||
<!-- 输入连接点(顶部) -->
|
||||
<Handle type="target" :position="Position.Top" />
|
||||
<div class="node-content">
|
||||
<div class="node-icon">
|
||||
<icon-check-circle-fill :size="24" />
|
||||
</div>
|
||||
<div class="node-label">结束</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Handle, Position } from '@vue-flow/core';
|
||||
import { IconCheckCircleFill } from '@arco-design/web-vue/es/icon';
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.end-node {
|
||||
position: relative;
|
||||
padding: 8px 16px;
|
||||
background: #fff;
|
||||
border: 2px solid #7298f8;
|
||||
border-radius: 8px;
|
||||
min-width: 120px;
|
||||
text-align: center;
|
||||
|
||||
.node-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.node-icon {
|
||||
color: #7298f8;
|
||||
}
|
||||
|
||||
.node-label {
|
||||
font-size: 14px;
|
||||
color: #1d2129;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div class="start-node">
|
||||
<div class="node-content">
|
||||
<div class="node-icon">
|
||||
<icon-play-circle-fill :size="24" />
|
||||
</div>
|
||||
<div class="node-label">开始</div>
|
||||
</div>
|
||||
<!-- 输出连接点(底部) -->
|
||||
<Handle type="source" :position="Position.Bottom" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Handle, Position } from '@vue-flow/core';
|
||||
import { IconPlayCircleFill } from '@arco-design/web-vue/es/icon';
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.start-node {
|
||||
position: relative;
|
||||
padding: 8px 16px;
|
||||
background: #fff;
|
||||
border: 2px solid #7298f8;
|
||||
border-radius: 8px;
|
||||
min-width: 120px;
|
||||
text-align: center;
|
||||
|
||||
.node-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.node-icon {
|
||||
color: #7298f8;
|
||||
}
|
||||
|
||||
.node-label {
|
||||
font-size: 14px;
|
||||
color: #1d2129;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user