feat: 设置-流程设置 90%

This commit is contained in:
Zzz1zz
2026-01-10 11:39:33 +08:00
14 changed files with 2964 additions and 22 deletions
@@ -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