Skip to content

Commit dfa2e5d

Browse files
1myuanfit2-zhao
authored andcommitted
feat: refactor selection flow and add approval branch-group business rules
1 parent 5c4aa1b commit dfa2e5d

20 files changed

Lines changed: 420 additions & 186 deletions

File tree

frontend/packages/web/src/components/business/crm-flow/components/canvas/flowCanvas.vue

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,23 +41,30 @@
4141
BranchClickPayload,
4242
FlowGraphController,
4343
FlowGraphNodeClickPayload,
44+
FlowGraphNodeData,
4445
NodeClickPayload,
4546
} from '../../graph/types';
46-
import type { FlowSchema } from '../../types';
47+
import type { FlowSchema, NodeSelectionState } from '../../types';
4748
4849
defineOptions({
4950
name: 'FlowCanvas',
5051
});
5152
52-
const props = defineProps<{
53-
flow: FlowSchema;
54-
}>();
53+
const props = withDefaults(
54+
defineProps<{
55+
flow: FlowSchema;
56+
selection: NodeSelectionState;
57+
}>(),
58+
{}
59+
);
5560
const slots = useSlots();
5661
const hasInsertNodeContentSlot = computed(() => Boolean(slots.insertNodeContent));
5762
5863
const emit = defineEmits<{
5964
(event: 'nodeClick', payload: NodeClickPayload): void;
6065
(event: 'branchClick', payload: BranchClickPayload): void;
66+
(event: 'nodeDelete', payload: { nodeId: string }): void;
67+
(event: 'branchDelete', payload: { groupId: string; branchId: string }): void;
6168
(event: 'addConditionBranch', groupId: string): void;
6269
(event: 'blankClick'): void;
6370
}>();
@@ -145,6 +152,18 @@
145152
}
146153
}
147154
155+
function resolveSelectedState(data: FlowGraphNodeData): boolean {
156+
if (data.kind === 'condition-branch') {
157+
return props.selection.type === 'branch' && data.branchId === props.selection.id;
158+
}
159+
160+
if (data.kind === 'start' || data.kind === 'action' || data.kind === 'end') {
161+
return props.selection.type === 'node' && data.nodeId === props.selection.id;
162+
}
163+
164+
return false;
165+
}
166+
148167
function renderFlow() {
149168
if (!graphController.value) {
150169
return;
@@ -154,9 +173,59 @@
154173
cardHeight: viewMode.value === 'compact' ? 58 : 104,
155174
showNodeDescription: viewMode.value === 'detail',
156175
});
157-
graphController.value.render(cells);
176+
177+
const cellsWithSelection = (cells as any[]).map((cell) => {
178+
if (!cell?.data) {
179+
return cell;
180+
}
181+
182+
const data = cell.data as FlowGraphNodeData;
183+
return {
184+
...cell,
185+
data: {
186+
...data,
187+
selected: resolveSelectedState(data),
188+
},
189+
};
190+
});
191+
192+
graphController.value.render(cellsWithSelection);
193+
}
194+
195+
function updateRenderedSelectionState() {
196+
const graph = graphController.value?.getGraph();
197+
if (!graph) {
198+
return;
199+
}
200+
201+
graph.getNodes().forEach((node) => {
202+
const data = (node.getData?.() ?? null) as FlowGraphNodeData | null;
203+
if (!data) {
204+
return;
205+
}
206+
207+
const nextSelected = resolveSelectedState(data);
208+
if (data.selected === nextSelected) {
209+
return;
210+
}
211+
212+
node.setData({
213+
...data,
214+
selected: nextSelected,
215+
});
216+
});
158217
}
159218
219+
watch(
220+
() => props.selection,
221+
() => {
222+
updateRenderedSelectionState();
223+
},
224+
{
225+
deep: true,
226+
}
227+
);
228+
160229
function handleViewModeChange(mode: 'compact' | 'detail') {
161230
viewMode.value = mode;
162231
renderFlow();
@@ -239,6 +308,23 @@
239308
});
240309
}
241310
},
311+
onNodeDelete({ data }) {
312+
if (isPanMode.value) {
313+
return;
314+
}
315+
if (data.kind === 'condition-branch' && data.groupId && data.branchId) {
316+
emit('branchDelete', {
317+
groupId: data.groupId,
318+
branchId: data.branchId,
319+
});
320+
return;
321+
}
322+
if (data.nodeId) {
323+
emit('nodeDelete', {
324+
nodeId: data.nodeId,
325+
});
326+
}
327+
},
242328
onBlankClick() {
243329
if (isPanMode.value) {
244330
return;

frontend/packages/web/src/components/business/crm-flow/components/canvas/flowCanvasToolbar.vue

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,13 @@
4747

4848
<n-tooltip trigger="hover">
4949
<template #trigger>
50-
<CrmIcon class="flow-canvas-toolbar__icon" type="iconicon_zoom_out1" :size="16" @click="zoomOut" />
50+
<CrmIcon
51+
class="flow-canvas-toolbar__icon"
52+
:class="{ 'is-disabled': !canZoomOut }"
53+
type="iconicon_zoom_out1"
54+
:size="16"
55+
@click="zoomOut"
56+
/>
5157
</template>
5258
<span>{{ t('crmFlow.zoomOut') }}</span>
5359
</n-tooltip>
@@ -65,7 +71,13 @@
6571

6672
<n-tooltip trigger="hover">
6773
<template #trigger>
68-
<CrmIcon class="flow-canvas-toolbar__icon" type="iconicon_zoom_in1" :size="18" @click="zoomIn" />
74+
<CrmIcon
75+
class="flow-canvas-toolbar__icon"
76+
:class="{ 'is-disabled': !canZoomIn }"
77+
type="iconicon_zoom_in1"
78+
:size="18"
79+
@click="zoomIn"
80+
/>
6981
</template>
7082
<span>{{ t('crmFlow.zoomIn') }}</span>
7183
</n-tooltip>
@@ -126,6 +138,8 @@
126138
127139
const zoomPercent = ref(100);
128140
const zoomLevels = [50, 75, 100, 125, 150, 200] as const;
141+
const canZoomOut = computed(() => zoomPercent.value > 50);
142+
const canZoomIn = computed(() => zoomPercent.value < 200);
129143
130144
const selectedZoomOptionKey = computed(() => {
131145
const key = `zoom-${zoomPercent.value}`;
@@ -153,11 +167,17 @@
153167
}
154168
155169
function zoomIn() {
170+
if (!canZoomIn.value) {
171+
return;
172+
}
156173
props.graphController?.zoomIn();
157174
syncZoomPercent();
158175
}
159176
160177
function zoomOut() {
178+
if (!canZoomOut.value) {
179+
return;
180+
}
161181
props.graphController?.zoomOut();
162182
syncZoomPercent();
163183
}
@@ -249,6 +269,13 @@
249269
&.is-active {
250270
color: var(--primary-8);
251271
}
272+
&.is-disabled {
273+
color: var(--text-n6);
274+
cursor: not-allowed;
275+
&:hover {
276+
color: var(--text-n6);
277+
}
278+
}
252279
}
253280
</style>
254281

frontend/packages/web/src/components/business/crm-flow/components/nodes/actionNode.vue

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@
1212
</template>
1313

1414
<script setup lang="ts">
15-
import { computed } from 'vue';
15+
import { computed, toRef } from 'vue';
1616
1717
import BaseFlowNode from './baseFlowNode.vue';
1818
19+
import useX6NodeData from '../../composables/useX6NodeData';
1920
import type { Node } from '@antv/x6';
2021
2122
defineOptions({
@@ -42,16 +43,14 @@
4243
(event: 'delete'): void;
4344
}>();
4445
45-
const nodeData = computed(
46-
() =>
47-
(props.node?.getData?.() ?? {}) as {
48-
name?: string;
49-
description?: string;
50-
actionType?: string;
51-
showContent?: boolean;
52-
selected?: boolean;
53-
}
54-
);
46+
const { nodeData } = useX6NodeData<{
47+
name?: string;
48+
description?: string;
49+
actionType?: string;
50+
showContent?: boolean;
51+
selected?: boolean;
52+
}>(toRef(props, 'node'));
53+
5554
const iconConfig = computed<ActionIconConfig>(() => {
5655
const { actionType } = nodeData.value;
5756
return ACTION_TYPE_ICON_MAP[actionType ?? 'approval'];

frontend/packages/web/src/components/business/crm-flow/components/nodes/baseFlowNode.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
<CrmIcon
1717
type="iconicon_close"
1818
:size="16"
19-
class="cursor-pointer text-[var(--text-n4)] hover:text-[var(--primary-8)]"
20-
@click.stop="handleDelete"
19+
class="base-flow-node__delete-icon cursor-pointer text-[var(--text-n4)] hover:text-[var(--primary-8)]"
20+
@click="handleDelete"
2121
/>
2222
</template>
2323
<span> {{ t('crmFlow.deleteNode') }} </span>

frontend/packages/web/src/components/business/crm-flow/components/nodes/conditionBranchNode.vue

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515
</template>
1616

1717
<script setup lang="ts">
18-
import { computed } from 'vue';
18+
import { computed, toRef } from 'vue';
1919
2020
import BaseFlowNode from './baseFlowNode.vue';
2121
22+
import useX6NodeData from '../../composables/useX6NodeData';
2223
import type { Node } from '@antv/x6';
2324
2425
defineOptions({
@@ -33,16 +34,14 @@
3334
(event: 'delete'): void;
3435
}>();
3536
36-
const nodeData = computed(
37-
() =>
38-
(props.node?.getData?.() ?? {}) as {
39-
name?: string;
40-
description?: string;
41-
showContent?: boolean;
42-
isElse?: boolean;
43-
selected?: boolean;
44-
}
45-
);
37+
const { nodeData } = useX6NodeData<{
38+
name?: string;
39+
description?: string;
40+
showContent?: boolean;
41+
isElse?: boolean;
42+
selected?: boolean;
43+
}>(toRef(props, 'node'));
44+
4645
const displayIsElse = computed(() => nodeData.value.isElse ?? false);
4746
4847
function handleDelete() {

frontend/packages/web/src/components/business/crm-flow/components/nodes/endNode.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111
</template>
1212

1313
<script setup lang="ts">
14-
import { computed } from 'vue';
14+
import { toRef } from 'vue';
1515
1616
import BaseFlowNode from './baseFlowNode.vue';
1717
18+
import useX6NodeData from '../../composables/useX6NodeData';
1819
import type { Node } from '@antv/x6';
1920
2021
defineOptions({
@@ -25,5 +26,5 @@
2526
node?: Node;
2627
}>();
2728
28-
const nodeData = computed(() => (props.node?.getData?.() ?? {}) as { name?: string; selected?: boolean });
29+
const { nodeData } = useX6NodeData<{ name?: string; selected?: boolean }>(toRef(props, 'node'));
2930
</script>

frontend/packages/web/src/components/business/crm-flow/components/nodes/startNode.vue

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@
1313
</template>
1414

1515
<script setup lang="ts">
16-
import { computed } from 'vue';
16+
import { toRef } from 'vue';
1717
1818
import BaseFlowNode from './baseFlowNode.vue';
1919
20+
import useX6NodeData from '../../composables/useX6NodeData';
2021
import type { Node } from '@antv/x6';
2122
2223
defineOptions({
@@ -27,13 +28,10 @@
2728
node?: Node;
2829
}>();
2930
30-
const nodeData = computed(
31-
() =>
32-
(props.node?.getData?.() ?? {}) as {
33-
name?: string;
34-
description?: string;
35-
showContent?: boolean;
36-
selected?: boolean;
37-
}
38-
);
31+
const { nodeData } = useX6NodeData<{
32+
name?: string;
33+
description?: string;
34+
showContent?: boolean;
35+
selected?: boolean;
36+
}>(toRef(props, 'node'));
3937
</script>

0 commit comments

Comments
 (0)