android-midscene-automation 0.1.6 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -10
- package/package.json +3 -3
- package/server/appium-recorder/appium-runner.ts +362 -26
- package/server/appium-recorder/repository.ts +89 -12
- package/src/appium-recorder/AppiumPage.vue +504 -124
- package/src/appium-recorder/components/ComponentTree.vue +14 -1
- package/src/appium-recorder/components/NodeDetail.vue +24 -118
- package/src/appium-recorder/components/RecordedSteps.vue +898 -36
- package/src/appium-recorder/tree.ts +55 -9
- package/src/appium-recorder/types.ts +33 -1
- package/src/style.css +659 -11
|
@@ -1,24 +1,169 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { computed, reactive, shallowRef, watch } from 'vue';
|
|
3
|
+
import { Delete, Edit, FullScreen, Timer } from '@element-plus/icons-vue';
|
|
4
|
+
import type { AppiumRecordedStep, AppiumSelector } from '../types';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
type FlowKind = 'action' | 'condition' | 'assertion';
|
|
7
|
+
type BranchName = 'yes' | 'no';
|
|
8
|
+
type StepScope = 'pre' | 'main' | 'post';
|
|
9
|
+
type StepItem = { step: AppiumRecordedStep; index: number };
|
|
10
|
+
type InsertAction =
|
|
11
|
+
| 'delay'
|
|
12
|
+
| 'tap'
|
|
13
|
+
| 'input'
|
|
14
|
+
| 'clearInput'
|
|
15
|
+
| 'coordinateTap'
|
|
16
|
+
| 'longPress'
|
|
17
|
+
| 'keyBack'
|
|
18
|
+
| 'keyHome'
|
|
19
|
+
| 'keyRecent'
|
|
20
|
+
| 'keyPower'
|
|
21
|
+
| 'swipe'
|
|
22
|
+
| 'pinch'
|
|
23
|
+
| 'launchApp'
|
|
24
|
+
| 'popupCondition'
|
|
25
|
+
| 'tapIfExists'
|
|
26
|
+
| 'inputIfExists'
|
|
27
|
+
| 'clearIfExists'
|
|
28
|
+
| 'backIfExists'
|
|
29
|
+
| 'waitFor'
|
|
30
|
+
| 'assertExists'
|
|
31
|
+
| 'assertText'
|
|
32
|
+
| 'waitDisappear'
|
|
33
|
+
| 'waitActivity'
|
|
34
|
+
| 'runScript';
|
|
35
|
+
|
|
36
|
+
const props = defineProps<{
|
|
6
37
|
steps: AppiumRecordedStep[];
|
|
7
38
|
disabled?: boolean;
|
|
39
|
+
allowedLockedActions?: InsertAction[];
|
|
8
40
|
}>();
|
|
9
41
|
|
|
10
|
-
defineEmits<{
|
|
42
|
+
const emit = defineEmits<{
|
|
11
43
|
remove: [index: number];
|
|
12
44
|
addDelay: [index?: number];
|
|
45
|
+
insertAction: [index: number, action: InsertAction];
|
|
46
|
+
insertBranchAction: [index: number, branch: BranchName, action: InsertAction];
|
|
13
47
|
editInput: [index: number];
|
|
48
|
+
updateStep: [index: number, step: AppiumRecordedStep];
|
|
14
49
|
}>();
|
|
15
50
|
|
|
51
|
+
const expandedStepIndex = shallowRef<number | null>(null);
|
|
52
|
+
const flowDialogVisible = shallowRef(false);
|
|
53
|
+
const flowPan = reactive({ x: 0, y: 0 });
|
|
54
|
+
const flowScale = shallowRef(1);
|
|
55
|
+
const flowDragging = shallowRef(false);
|
|
56
|
+
const flowDragMoved = shallowRef(false);
|
|
57
|
+
let dragStart = { pointerId: 0, x: 0, y: 0, panX: 0, panY: 0 };
|
|
58
|
+
|
|
59
|
+
const stepOptions = computed(() => props.steps.map((step, index) => ({
|
|
60
|
+
label: `${index + 1}. ${step.label}`,
|
|
61
|
+
value: step.id,
|
|
62
|
+
})));
|
|
63
|
+
|
|
64
|
+
const branchTargetIds = computed(() => {
|
|
65
|
+
const ids = new Set<string>();
|
|
66
|
+
props.steps.forEach((step) => {
|
|
67
|
+
if (step.flow?.parentConditionId) ids.add(step.id);
|
|
68
|
+
if (step.flow?.yesTargetId) ids.add(step.flow.yesTargetId);
|
|
69
|
+
if (step.flow?.noTargetId) ids.add(step.flow.noTargetId);
|
|
70
|
+
});
|
|
71
|
+
return ids;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const stepItems = computed<StepItem[]>(() => props.steps
|
|
75
|
+
.map((step, index) => ({ step, index }))
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const visibleStepItems = computed(() => stepItems.value
|
|
79
|
+
.filter(({ step }) => !branchTargetIds.value.has(step.id)));
|
|
80
|
+
|
|
81
|
+
const insertActionGroups: Array<{ title: string; actions: Array<{ type: InsertAction; label: string }> }> = [
|
|
82
|
+
{
|
|
83
|
+
title: '组件操作',
|
|
84
|
+
actions: [
|
|
85
|
+
{ type: 'tap', label: '录制点击' },
|
|
86
|
+
{ type: 'input', label: '录制输入' },
|
|
87
|
+
{ type: 'clearInput', label: '清空输入' },
|
|
88
|
+
{ type: 'coordinateTap', label: '点击坐标' },
|
|
89
|
+
{ type: 'longPress', label: '长按' },
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
title: '设备操作',
|
|
94
|
+
actions: [
|
|
95
|
+
{ type: 'keyBack', label: '系统返回' },
|
|
96
|
+
{ type: 'keyHome', label: 'Home 键' },
|
|
97
|
+
{ type: 'keyRecent', label: '最近任务' },
|
|
98
|
+
{ type: 'keyPower', label: '电源键' },
|
|
99
|
+
{ type: 'swipe', label: '滑动' },
|
|
100
|
+
{ type: 'pinch', label: '双指缩放' },
|
|
101
|
+
{ type: 'launchApp', label: '启动 App' },
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
title: '等待断言',
|
|
106
|
+
actions: [
|
|
107
|
+
{ type: 'delay', label: '添加延时' },
|
|
108
|
+
{ type: 'popupCondition', label: '判断存在' },
|
|
109
|
+
{ type: 'tapIfExists', label: '存在则点击' },
|
|
110
|
+
{ type: 'inputIfExists', label: '存在则输入' },
|
|
111
|
+
{ type: 'clearIfExists', label: '存在则清空' },
|
|
112
|
+
{ type: 'backIfExists', label: '存在则返回' },
|
|
113
|
+
{ type: 'waitFor', label: '等待出现' },
|
|
114
|
+
{ type: 'assertExists', label: '断言存在' },
|
|
115
|
+
{ type: 'assertText', label: '断言文本' },
|
|
116
|
+
{ type: 'waitDisappear', label: '等待元素消失' },
|
|
117
|
+
{ type: 'waitActivity', label: '等待 Activity' },
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
title: '流程控制',
|
|
122
|
+
actions: [
|
|
123
|
+
{ type: 'runScript', label: '连接脚本' },
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
const mainActionGroups = insertActionGroups.map((group) => ({
|
|
129
|
+
...group,
|
|
130
|
+
actions: group.actions.filter((action) => action.type !== 'launchApp'),
|
|
131
|
+
}));
|
|
132
|
+
|
|
133
|
+
const boundaryActionGroups = [{
|
|
134
|
+
title: '前后置操作',
|
|
135
|
+
actions: [
|
|
136
|
+
{ type: 'launchApp' as const, label: '启动 APP' },
|
|
137
|
+
{ type: 'delay' as const, label: '添加延时' },
|
|
138
|
+
],
|
|
139
|
+
}];
|
|
140
|
+
|
|
141
|
+
function stepScope(step: AppiumRecordedStep): StepScope {
|
|
142
|
+
return step.flow?.scope || 'main';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function scopedStepOptions(source: AppiumRecordedStep) {
|
|
146
|
+
const scope = stepScope(source);
|
|
147
|
+
return stepOptions.value.filter((option) => {
|
|
148
|
+
const target = props.steps.find((step) => step.id === option.value);
|
|
149
|
+
return target && stepScope(target) === scope;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function actionGroupsForStep(step: AppiumRecordedStep) {
|
|
154
|
+
return stepScope(step) === 'main' ? mainActionGroups : boundaryActionGroups;
|
|
155
|
+
}
|
|
156
|
+
|
|
16
157
|
function stepMeta(step: AppiumRecordedStep) {
|
|
17
158
|
if (step.type === 'delay') return `${step.timeoutMs || 1000}ms`;
|
|
18
|
-
if (step.type === 'input') return `输入内容:${step.value || '空'}`;
|
|
159
|
+
if (step.type === 'input' || step.type === 'inputIfExists') return `输入内容:${step.value || '空'}`;
|
|
160
|
+
if (step.contextSelector && step.selector) {
|
|
161
|
+
return `父级 ${step.contextSelector.strategy} ${step.contextSelector.value || ''} + 子级 ${step.selector.strategy} ${step.selector.value || ''}`;
|
|
162
|
+
}
|
|
19
163
|
if (step.type === 'key') return `keyCode ${step.keyCode || ''}`;
|
|
20
164
|
if (step.type === 'waitActivity') return step.value || '';
|
|
21
165
|
if (step.type === 'launchApp') return step.value || '';
|
|
166
|
+
if (step.type === 'runScript') return step.value ? `脚本 ${step.value}` : '';
|
|
22
167
|
if (step.type === 'screenshot') return '保存当前截图';
|
|
23
168
|
if (step.type === 'swipe') {
|
|
24
169
|
const swipe = step.swipe;
|
|
@@ -30,50 +175,767 @@ function stepMeta(step: AppiumRecordedStep) {
|
|
|
30
175
|
if (step.type === 'assertText') return step.value || '';
|
|
31
176
|
return `${step.selector?.strategy || ''} ${step.selector?.value || ''}`;
|
|
32
177
|
}
|
|
178
|
+
|
|
179
|
+
function defaultKind(step: AppiumRecordedStep): FlowKind {
|
|
180
|
+
if (step.flow?.nodeKind) return step.flow.nodeKind;
|
|
181
|
+
if (step.type === 'assertExists' || step.type === 'assertText') return 'assertion';
|
|
182
|
+
return 'action';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function kindLabel(kind: FlowKind) {
|
|
186
|
+
return {
|
|
187
|
+
action: '操作',
|
|
188
|
+
condition: '判断',
|
|
189
|
+
assertion: '校验',
|
|
190
|
+
}[kind];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function typeLabel(step: AppiumRecordedStep) {
|
|
194
|
+
const labelMap: Partial<Record<AppiumRecordedStep['type'], string>> = {
|
|
195
|
+
tap: '点击',
|
|
196
|
+
input: '输入',
|
|
197
|
+
tapIfExists: '存在则点击',
|
|
198
|
+
inputIfExists: '存在则输入',
|
|
199
|
+
clearIfExists: '存在则清空',
|
|
200
|
+
backIfExists: '存在则返回',
|
|
201
|
+
clearInput: '清空',
|
|
202
|
+
waitFor: '等待出现',
|
|
203
|
+
waitDisappear: '等待消失',
|
|
204
|
+
assertExists: '断言存在',
|
|
205
|
+
assertText: '断言文本',
|
|
206
|
+
key: '按键',
|
|
207
|
+
waitActivity: '等待 Activity',
|
|
208
|
+
delay: '延时',
|
|
209
|
+
coordinateTap: '坐标点击',
|
|
210
|
+
swipe: '滑动',
|
|
211
|
+
screenshot: '截图',
|
|
212
|
+
launchApp: '启动 APP',
|
|
213
|
+
longPress: '长按',
|
|
214
|
+
pinch: '双指缩放',
|
|
215
|
+
runScript: '连接脚本',
|
|
216
|
+
};
|
|
217
|
+
return labelMap[step.type] || step.type;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function targetLabel(id?: string) {
|
|
221
|
+
if (!id) return '结束';
|
|
222
|
+
const index = props.steps.findIndex((step) => step.id === id);
|
|
223
|
+
if (index < 0) return '未找到目标';
|
|
224
|
+
return `${index + 1}. ${props.steps[index].label}`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function targetStep(id?: string) {
|
|
228
|
+
if (!id) return null;
|
|
229
|
+
const index = props.steps.findIndex((step) => step.id === id);
|
|
230
|
+
if (index < 0) return null;
|
|
231
|
+
return { step: props.steps[index], index };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function branchStepItems(step: AppiumRecordedStep, branch: BranchName) {
|
|
235
|
+
const ownedItems = stepItems.value.filter((item) => (
|
|
236
|
+
item.step.flow?.parentConditionId === step.id
|
|
237
|
+
&& item.step.flow?.parentBranch === branch
|
|
238
|
+
));
|
|
239
|
+
if (ownedItems.length) return ownedItems;
|
|
240
|
+
|
|
241
|
+
const targetId = branch === 'yes' ? step.flow?.yesTargetId : step.flow?.noTargetId;
|
|
242
|
+
const target = targetStep(targetId);
|
|
243
|
+
return target ? [target] : [];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function nextStepId(index: number) {
|
|
247
|
+
return props.steps[index + 1]?.id || '';
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function toggleExpanded(index: number) {
|
|
251
|
+
expandedStepIndex.value = expandedStepIndex.value === index ? null : index;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function patchStep(index: number, patch: Partial<AppiumRecordedStep>) {
|
|
255
|
+
const current = props.steps[index];
|
|
256
|
+
if (!current) return;
|
|
257
|
+
emit('updateStep', index, { ...current, ...patch });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function patchFlow(index: number, patch: NonNullable<AppiumRecordedStep['flow']>) {
|
|
261
|
+
const current = props.steps[index];
|
|
262
|
+
if (!current) return;
|
|
263
|
+
emit('updateStep', index, {
|
|
264
|
+
...current,
|
|
265
|
+
flow: {
|
|
266
|
+
...(current.flow || {}),
|
|
267
|
+
...patch,
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function insertAction(index: number, command: string | number | object) {
|
|
273
|
+
emit('insertAction', index, command as InsertAction);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function insertBranchAction(index: number, branch: BranchName, command: string | number | object) {
|
|
277
|
+
emit('insertBranchAction', index, branch, command as InsertAction);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function isInsertActionDisabled(action: InsertAction) {
|
|
281
|
+
return Boolean(props.disabled && !props.allowedLockedActions?.includes(action));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function canOpenInsertMenu() {
|
|
285
|
+
return !props.disabled || Boolean(props.allowedLockedActions?.length);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function isInteractiveTarget(target: EventTarget | null) {
|
|
289
|
+
return target instanceof Element && Boolean(target.closest(
|
|
290
|
+
'button,input,textarea,select,.el-select,.el-dropdown,.el-input,.el-input-number,.appium-flow-node',
|
|
291
|
+
));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function resetFlowView() {
|
|
295
|
+
flowPan.x = 0;
|
|
296
|
+
flowPan.y = 0;
|
|
297
|
+
flowScale.value = 1;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function handleFlowWheel(event: WheelEvent) {
|
|
301
|
+
if (!event.ctrlKey) return;
|
|
302
|
+
event.preventDefault();
|
|
303
|
+
const canvas = event.currentTarget as HTMLElement;
|
|
304
|
+
const bounds = canvas.getBoundingClientRect();
|
|
305
|
+
const cursorX = event.clientX - bounds.left;
|
|
306
|
+
const cursorY = event.clientY - bounds.top;
|
|
307
|
+
const previousScale = flowScale.value;
|
|
308
|
+
const direction = event.deltaY < 0 ? 1 : -1;
|
|
309
|
+
const nextScale = Math.min(2, Math.max(0.5, Math.round((previousScale + direction * 0.1) * 10) / 10));
|
|
310
|
+
if (nextScale === previousScale) return;
|
|
311
|
+
flowPan.x = cursorX - ((cursorX - flowPan.x) / previousScale) * nextScale;
|
|
312
|
+
flowPan.y = cursorY - ((cursorY - flowPan.y) / previousScale) * nextScale;
|
|
313
|
+
flowScale.value = nextScale;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function handleNodeClick(index: number) {
|
|
317
|
+
toggleExpanded(index);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function startFlowDrag(event: PointerEvent) {
|
|
321
|
+
if (isInteractiveTarget(event.target)) return;
|
|
322
|
+
flowDragging.value = true;
|
|
323
|
+
flowDragMoved.value = false;
|
|
324
|
+
dragStart = {
|
|
325
|
+
pointerId: event.pointerId,
|
|
326
|
+
x: event.clientX,
|
|
327
|
+
y: event.clientY,
|
|
328
|
+
panX: flowPan.x,
|
|
329
|
+
panY: flowPan.y,
|
|
330
|
+
};
|
|
331
|
+
(event.currentTarget as HTMLElement).setPointerCapture(event.pointerId);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function moveFlowDrag(event: PointerEvent) {
|
|
335
|
+
if (!flowDragging.value || event.pointerId !== dragStart.pointerId) return;
|
|
336
|
+
if (Math.abs(event.clientX - dragStart.x) + Math.abs(event.clientY - dragStart.y) > 4) {
|
|
337
|
+
flowDragMoved.value = true;
|
|
338
|
+
}
|
|
339
|
+
flowPan.x = dragStart.panX + event.clientX - dragStart.x;
|
|
340
|
+
flowPan.y = dragStart.panY + event.clientY - dragStart.y;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function stopFlowDrag(event: PointerEvent) {
|
|
344
|
+
if (!flowDragging.value || event.pointerId !== dragStart.pointerId) return;
|
|
345
|
+
flowDragging.value = false;
|
|
346
|
+
const target = event.currentTarget as HTMLElement;
|
|
347
|
+
if (target.hasPointerCapture(event.pointerId)) target.releasePointerCapture(event.pointerId);
|
|
348
|
+
window.setTimeout(() => {
|
|
349
|
+
flowDragMoved.value = false;
|
|
350
|
+
}, 0);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
watch(() => props.steps.length, () => {
|
|
354
|
+
resetFlowView();
|
|
355
|
+
});
|
|
33
356
|
</script>
|
|
34
357
|
|
|
35
358
|
<template>
|
|
36
359
|
<div class="appium-recorded-steps-panel">
|
|
37
|
-
<div class="appium-
|
|
38
|
-
<el-
|
|
360
|
+
<div class="appium-flow-toolbar">
|
|
361
|
+
<el-button
|
|
362
|
+
size="small"
|
|
363
|
+
:icon="FullScreen"
|
|
364
|
+
:disabled="!steps.length"
|
|
365
|
+
@click="flowDialogVisible = true"
|
|
366
|
+
>
|
|
367
|
+
放大
|
|
368
|
+
</el-button>
|
|
369
|
+
</div>
|
|
370
|
+
<div
|
|
371
|
+
class="appium-flow-canvas"
|
|
372
|
+
:class="{ 'appium-flow-canvas--dragging': flowDragging }"
|
|
373
|
+
@pointerdown="startFlowDrag"
|
|
374
|
+
@pointermove="moveFlowDrag"
|
|
375
|
+
@pointerup="stopFlowDrag"
|
|
376
|
+
@pointercancel="stopFlowDrag"
|
|
377
|
+
@pointerleave="stopFlowDrag"
|
|
378
|
+
@wheel="handleFlowWheel"
|
|
379
|
+
>
|
|
39
380
|
<div
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
:key="step.id"
|
|
43
|
-
class="appium-step-item"
|
|
381
|
+
class="appium-flow-content"
|
|
382
|
+
:style="{ transform: `translate(${flowPan.x}px, ${flowPan.y}px) scale(${flowScale})` }"
|
|
44
383
|
>
|
|
384
|
+
<div class="appium-flow-boundary appium-flow-boundary--pre">
|
|
385
|
+
<span class="appium-flow-boundary__label">前置操作</span>
|
|
386
|
+
<el-dropdown
|
|
387
|
+
trigger="click"
|
|
388
|
+
:disabled="!canOpenInsertMenu()"
|
|
389
|
+
max-height="320px"
|
|
390
|
+
popper-class="appium-action-dropdown"
|
|
391
|
+
@command="insertAction(-2, $event)"
|
|
392
|
+
>
|
|
393
|
+
<button
|
|
394
|
+
type="button"
|
|
395
|
+
class="appium-flow-insert"
|
|
396
|
+
:disabled="!canOpenInsertMenu()"
|
|
397
|
+
>
|
|
398
|
+
<el-icon><Timer /></el-icon>
|
|
399
|
+
<span>添加前置操作</span>
|
|
400
|
+
</button>
|
|
401
|
+
<template #dropdown>
|
|
402
|
+
<el-dropdown-menu>
|
|
403
|
+
<template v-for="group in boundaryActionGroups" :key="group.title">
|
|
404
|
+
<div class="appium-action-dropdown__group">{{ group.title }}</div>
|
|
405
|
+
<el-dropdown-item
|
|
406
|
+
v-for="action in group.actions"
|
|
407
|
+
:key="action.type"
|
|
408
|
+
:command="action.type"
|
|
409
|
+
:disabled="isInsertActionDisabled(action.type)"
|
|
410
|
+
>
|
|
411
|
+
{{ action.label }}
|
|
412
|
+
</el-dropdown-item>
|
|
413
|
+
</template>
|
|
414
|
+
</el-dropdown-menu>
|
|
415
|
+
</template>
|
|
416
|
+
</el-dropdown>
|
|
417
|
+
</div>
|
|
418
|
+
<div class="appium-flow-start">
|
|
419
|
+
<div class="appium-flow-start__node">开始</div>
|
|
420
|
+
<el-dropdown
|
|
421
|
+
trigger="click"
|
|
422
|
+
:disabled="!canOpenInsertMenu()"
|
|
423
|
+
max-height="320px"
|
|
424
|
+
popper-class="appium-action-dropdown"
|
|
425
|
+
@command="insertAction(-1, $event)"
|
|
426
|
+
>
|
|
427
|
+
<button
|
|
428
|
+
type="button"
|
|
429
|
+
class="appium-flow-insert"
|
|
430
|
+
:disabled="!canOpenInsertMenu()"
|
|
431
|
+
>
|
|
432
|
+
<el-icon><Timer /></el-icon>
|
|
433
|
+
<span>插入操作</span>
|
|
434
|
+
</button>
|
|
435
|
+
<template #dropdown>
|
|
436
|
+
<el-dropdown-menu>
|
|
437
|
+
<template v-for="group in mainActionGroups" :key="group.title">
|
|
438
|
+
<div class="appium-action-dropdown__group">{{ group.title }}</div>
|
|
439
|
+
<el-dropdown-item
|
|
440
|
+
v-for="action in group.actions"
|
|
441
|
+
:key="action.type"
|
|
442
|
+
:command="action.type"
|
|
443
|
+
:disabled="isInsertActionDisabled(action.type)"
|
|
444
|
+
>
|
|
445
|
+
{{ action.label }}
|
|
446
|
+
</el-dropdown-item>
|
|
447
|
+
</template>
|
|
448
|
+
</el-dropdown-menu>
|
|
449
|
+
</template>
|
|
450
|
+
</el-dropdown>
|
|
451
|
+
</div>
|
|
45
452
|
<div
|
|
46
|
-
|
|
47
|
-
:
|
|
453
|
+
v-for="{ step, index } in visibleStepItems"
|
|
454
|
+
:key="step.id"
|
|
455
|
+
class="appium-flow-item"
|
|
456
|
+
:class="[
|
|
457
|
+
`appium-flow-item--${stepScope(step)}`,
|
|
458
|
+
{ 'appium-flow-item--condition': defaultKind(step) === 'condition' },
|
|
459
|
+
]"
|
|
48
460
|
>
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
461
|
+
<div
|
|
462
|
+
class="appium-flow-node"
|
|
463
|
+
:class="[
|
|
464
|
+
`appium-flow-node--${defaultKind(step)}`,
|
|
465
|
+
{ 'appium-flow-node--expanded': expandedStepIndex === index },
|
|
466
|
+
]"
|
|
467
|
+
>
|
|
468
|
+
<button
|
|
469
|
+
type="button"
|
|
470
|
+
class="appium-flow-node__toggle"
|
|
471
|
+
:aria-expanded="expandedStepIndex === index"
|
|
472
|
+
@click.stop="handleNodeClick(index)"
|
|
473
|
+
>
|
|
474
|
+
<span class="appium-flow-node__index">{{ index + 1 }}</span>
|
|
475
|
+
<span class="appium-flow-node__body">
|
|
476
|
+
<span class="appium-flow-node__title" :title="step.label">{{ step.label }}</span>
|
|
477
|
+
<span class="appium-flow-node__meta" :title="stepMeta(step)">
|
|
478
|
+
{{ kindLabel(defaultKind(step)) }} · {{ typeLabel(step) }}
|
|
479
|
+
<template v-if="step.optional"> · 可选</template>
|
|
480
|
+
<template v-if="stepMeta(step)"> · {{ stepMeta(step) }}</template>
|
|
481
|
+
</span>
|
|
482
|
+
</span>
|
|
483
|
+
</button>
|
|
484
|
+
<span class="appium-flow-node__actions" @click.stop>
|
|
485
|
+
<el-button
|
|
486
|
+
v-if="step.type === 'input' || step.type === 'inputIfExists'"
|
|
487
|
+
text
|
|
488
|
+
size="small"
|
|
489
|
+
:icon="Edit"
|
|
490
|
+
:disabled="disabled"
|
|
491
|
+
title="修改输入内容"
|
|
492
|
+
@click="$emit('editInput', index)"
|
|
493
|
+
/>
|
|
494
|
+
<el-button
|
|
495
|
+
text
|
|
496
|
+
size="small"
|
|
497
|
+
:icon="Delete"
|
|
498
|
+
:disabled="disabled"
|
|
499
|
+
title="删除节点"
|
|
500
|
+
@click="$emit('remove', index)"
|
|
501
|
+
/>
|
|
502
|
+
</span>
|
|
503
|
+
</div>
|
|
504
|
+
|
|
505
|
+
<div v-if="defaultKind(step) === 'condition'" class="appium-flow-branches">
|
|
506
|
+
<svg class="appium-flow-branch-lines" viewBox="0 0 100 48" preserveAspectRatio="none" aria-hidden="true">
|
|
507
|
+
<path d="M50 0 C50 16 25 16 25 31 L25 48" />
|
|
508
|
+
<path d="M50 0 C50 16 75 16 75 31 L75 48" />
|
|
509
|
+
</svg>
|
|
510
|
+
<div class="appium-flow-branch appium-flow-branch--yes">
|
|
511
|
+
<strong class="appium-flow-branch__label">是</strong>
|
|
512
|
+
<div v-if="branchStepItems(step, 'yes').length" class="appium-flow-branch-steps">
|
|
513
|
+
<div
|
|
514
|
+
v-for="item in branchStepItems(step, 'yes')"
|
|
515
|
+
:key="item.step.id"
|
|
516
|
+
class="appium-flow-branch-step"
|
|
517
|
+
>
|
|
518
|
+
<span class="appium-flow-branch-step__index">
|
|
519
|
+
{{ item.index + 1 }}
|
|
520
|
+
</span>
|
|
521
|
+
<span class="appium-flow-branch-step__body">
|
|
522
|
+
<span class="appium-flow-branch-step__title">
|
|
523
|
+
{{ item.step.label }}
|
|
524
|
+
</span>
|
|
525
|
+
<span class="appium-flow-branch-step__meta">
|
|
526
|
+
{{ kindLabel(defaultKind(item.step)) }} · {{ typeLabel(item.step) }}
|
|
527
|
+
</span>
|
|
528
|
+
</span>
|
|
529
|
+
<el-button
|
|
530
|
+
class="appium-flow-branch-step__delete"
|
|
531
|
+
text
|
|
532
|
+
size="small"
|
|
533
|
+
:icon="Delete"
|
|
534
|
+
:disabled="disabled"
|
|
535
|
+
title="删除节点"
|
|
536
|
+
@click.stop="$emit('remove', item.index)"
|
|
537
|
+
/>
|
|
538
|
+
</div>
|
|
539
|
+
</div>
|
|
540
|
+
<span v-else class="appium-flow-branch__target">{{ targetLabel(step.flow?.yesTargetId) }}</span>
|
|
541
|
+
<el-dropdown
|
|
542
|
+
trigger="click"
|
|
543
|
+
:disabled="!canOpenInsertMenu()"
|
|
544
|
+
max-height="320px"
|
|
545
|
+
popper-class="appium-action-dropdown"
|
|
546
|
+
@command="insertBranchAction(index, 'yes', $event)"
|
|
547
|
+
>
|
|
548
|
+
<el-button
|
|
549
|
+
size="small"
|
|
550
|
+
class="appium-flow-branch__insert"
|
|
551
|
+
:disabled="!canOpenInsertMenu()"
|
|
552
|
+
:icon="Timer"
|
|
553
|
+
@click.stop
|
|
554
|
+
>
|
|
555
|
+
插入操作
|
|
556
|
+
</el-button>
|
|
557
|
+
<template #dropdown>
|
|
558
|
+
<el-dropdown-menu>
|
|
559
|
+
<template v-for="group in mainActionGroups" :key="group.title">
|
|
560
|
+
<div class="appium-action-dropdown__group">{{ group.title }}</div>
|
|
561
|
+
<el-dropdown-item
|
|
562
|
+
v-for="action in group.actions"
|
|
563
|
+
:key="action.type"
|
|
564
|
+
:command="action.type"
|
|
565
|
+
:disabled="isInsertActionDisabled(action.type)"
|
|
566
|
+
>
|
|
567
|
+
{{ action.label }}
|
|
568
|
+
</el-dropdown-item>
|
|
569
|
+
</template>
|
|
570
|
+
</el-dropdown-menu>
|
|
571
|
+
</template>
|
|
572
|
+
</el-dropdown>
|
|
53
573
|
</div>
|
|
54
|
-
<div class="appium-
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
574
|
+
<div class="appium-flow-branch appium-flow-branch--no">
|
|
575
|
+
<strong class="appium-flow-branch__label">否</strong>
|
|
576
|
+
<div v-if="branchStepItems(step, 'no').length" class="appium-flow-branch-steps">
|
|
577
|
+
<div
|
|
578
|
+
v-for="item in branchStepItems(step, 'no')"
|
|
579
|
+
:key="item.step.id"
|
|
580
|
+
class="appium-flow-branch-step"
|
|
581
|
+
>
|
|
582
|
+
<span class="appium-flow-branch-step__index">
|
|
583
|
+
{{ item.index + 1 }}
|
|
584
|
+
</span>
|
|
585
|
+
<span class="appium-flow-branch-step__body">
|
|
586
|
+
<span class="appium-flow-branch-step__title">
|
|
587
|
+
{{ item.step.label }}
|
|
588
|
+
</span>
|
|
589
|
+
<span class="appium-flow-branch-step__meta">
|
|
590
|
+
{{ kindLabel(defaultKind(item.step)) }} · {{ typeLabel(item.step) }}
|
|
591
|
+
</span>
|
|
592
|
+
</span>
|
|
593
|
+
<el-button
|
|
594
|
+
class="appium-flow-branch-step__delete"
|
|
595
|
+
text
|
|
596
|
+
size="small"
|
|
597
|
+
:icon="Delete"
|
|
598
|
+
:disabled="disabled"
|
|
599
|
+
title="删除节点"
|
|
600
|
+
@click.stop="$emit('remove', item.index)"
|
|
601
|
+
/>
|
|
602
|
+
</div>
|
|
603
|
+
</div>
|
|
604
|
+
<span v-else class="appium-flow-branch__target">{{ targetLabel(step.flow?.noTargetId) }}</span>
|
|
605
|
+
<el-dropdown
|
|
606
|
+
trigger="click"
|
|
607
|
+
:disabled="!canOpenInsertMenu()"
|
|
608
|
+
max-height="320px"
|
|
609
|
+
popper-class="appium-action-dropdown"
|
|
610
|
+
@command="insertBranchAction(index, 'no', $event)"
|
|
611
|
+
>
|
|
612
|
+
<el-button
|
|
613
|
+
size="small"
|
|
614
|
+
class="appium-flow-branch__insert"
|
|
615
|
+
:disabled="!canOpenInsertMenu()"
|
|
616
|
+
:icon="Timer"
|
|
617
|
+
@click.stop
|
|
618
|
+
>
|
|
619
|
+
插入操作
|
|
620
|
+
</el-button>
|
|
621
|
+
<template #dropdown>
|
|
622
|
+
<el-dropdown-menu>
|
|
623
|
+
<template v-for="group in mainActionGroups" :key="group.title">
|
|
624
|
+
<div class="appium-action-dropdown__group">{{ group.title }}</div>
|
|
625
|
+
<el-dropdown-item
|
|
626
|
+
v-for="action in group.actions"
|
|
627
|
+
:key="action.type"
|
|
628
|
+
:command="action.type"
|
|
629
|
+
:disabled="isInsertActionDisabled(action.type)"
|
|
630
|
+
>
|
|
631
|
+
{{ action.label }}
|
|
632
|
+
</el-dropdown-item>
|
|
633
|
+
</template>
|
|
634
|
+
</el-dropdown-menu>
|
|
635
|
+
</template>
|
|
636
|
+
</el-dropdown>
|
|
65
637
|
</div>
|
|
66
638
|
</div>
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
639
|
+
|
|
640
|
+
<div v-if="expandedStepIndex === index" class="appium-flow-editor">
|
|
641
|
+
<el-form label-position="top" size="small">
|
|
642
|
+
<el-form-item label="节点名称">
|
|
643
|
+
<el-input
|
|
644
|
+
:model-value="step.label"
|
|
645
|
+
:disabled="disabled"
|
|
646
|
+
@update:model-value="patchStep(index, { label: String($event) })"
|
|
647
|
+
/>
|
|
648
|
+
</el-form-item>
|
|
649
|
+
<div class="appium-flow-editor__grid">
|
|
650
|
+
<el-form-item label="节点类型">
|
|
651
|
+
<el-select
|
|
652
|
+
:model-value="defaultKind(step)"
|
|
653
|
+
:disabled="disabled"
|
|
654
|
+
@update:model-value="patchFlow(index, { nodeKind: $event as FlowKind })"
|
|
655
|
+
>
|
|
656
|
+
<el-option label="操作" value="action" />
|
|
657
|
+
<el-option label="判断" value="condition" />
|
|
658
|
+
<el-option label="校验" value="assertion" />
|
|
659
|
+
</el-select>
|
|
660
|
+
</el-form-item>
|
|
661
|
+
<el-form-item label="超时时间 ms">
|
|
662
|
+
<el-input-number
|
|
663
|
+
:model-value="step.timeoutMs || undefined"
|
|
664
|
+
:disabled="disabled"
|
|
665
|
+
:min="0"
|
|
666
|
+
:max="999999"
|
|
667
|
+
controls-position="right"
|
|
668
|
+
@update:model-value="patchStep(index, { timeoutMs: Number($event || 0) || undefined })"
|
|
669
|
+
/>
|
|
670
|
+
</el-form-item>
|
|
671
|
+
</div>
|
|
672
|
+
<el-form-item v-if="step.type === 'input' || step.type === 'inputIfExists' || step.type === 'assertText'" label="文本内容">
|
|
673
|
+
<el-input
|
|
674
|
+
:model-value="step.value || ''"
|
|
675
|
+
:disabled="disabled"
|
|
676
|
+
@update:model-value="patchStep(index, { value: String($event) })"
|
|
677
|
+
/>
|
|
678
|
+
</el-form-item>
|
|
679
|
+
<div v-if="step.selector" class="appium-flow-editor__grid">
|
|
680
|
+
<el-form-item label="Selector 类型">
|
|
681
|
+
<el-select
|
|
682
|
+
:model-value="step.selector.strategy"
|
|
683
|
+
:disabled="disabled"
|
|
684
|
+
@update:model-value="patchStep(index, { selector: { ...step.selector!, strategy: $event as AppiumSelector['strategy'] } })"
|
|
685
|
+
>
|
|
686
|
+
<el-option label="accessibility id" value="accessibilityId" />
|
|
687
|
+
<el-option label="id" value="id" />
|
|
688
|
+
<el-option label="android uiAutomator" value="androidUiAutomator" />
|
|
689
|
+
<el-option label="xpath" value="xpath" />
|
|
690
|
+
</el-select>
|
|
691
|
+
</el-form-item>
|
|
692
|
+
<el-form-item label="Selector 值">
|
|
693
|
+
<el-input
|
|
694
|
+
:model-value="step.selector.value || ''"
|
|
695
|
+
:disabled="disabled"
|
|
696
|
+
@update:model-value="patchStep(index, { selector: { ...step.selector!, value: String($event) } })"
|
|
697
|
+
/>
|
|
698
|
+
</el-form-item>
|
|
699
|
+
</div>
|
|
700
|
+
<div v-if="step.selector" class="appium-flow-editor__grid">
|
|
701
|
+
<el-form-item label="父级上下文类型">
|
|
702
|
+
<el-select
|
|
703
|
+
:model-value="step.contextSelector?.strategy || ''"
|
|
704
|
+
:disabled="disabled"
|
|
705
|
+
clearable
|
|
706
|
+
placeholder="不使用父级"
|
|
707
|
+
@update:model-value="patchStep(index, { contextSelector: $event ? { ...(step.contextSelector || { value: '' }), strategy: $event as AppiumSelector['strategy'] } : undefined })"
|
|
708
|
+
>
|
|
709
|
+
<el-option label="accessibility id" value="accessibilityId" />
|
|
710
|
+
<el-option label="id" value="id" />
|
|
711
|
+
<el-option label="android uiAutomator" value="androidUiAutomator" />
|
|
712
|
+
<el-option label="xpath" value="xpath" />
|
|
713
|
+
</el-select>
|
|
714
|
+
</el-form-item>
|
|
715
|
+
<el-form-item label="父级上下文值">
|
|
716
|
+
<el-input
|
|
717
|
+
:model-value="step.contextSelector?.value || ''"
|
|
718
|
+
:disabled="disabled || !step.contextSelector"
|
|
719
|
+
@update:model-value="patchStep(index, { contextSelector: { ...(step.contextSelector || { strategy: 'xpath' }), value: String($event) } })"
|
|
720
|
+
/>
|
|
721
|
+
</el-form-item>
|
|
722
|
+
</div>
|
|
723
|
+
<el-form-item label="可选步骤">
|
|
724
|
+
<el-switch
|
|
725
|
+
:model-value="Boolean(step.optional)"
|
|
726
|
+
:disabled="disabled"
|
|
727
|
+
active-text="找不到时跳过"
|
|
728
|
+
inactive-text="找不到时失败"
|
|
729
|
+
@update:model-value="patchStep(index, { optional: Boolean($event) })"
|
|
730
|
+
/>
|
|
731
|
+
</el-form-item>
|
|
732
|
+
<template v-if="defaultKind(step) === 'condition'">
|
|
733
|
+
<div class="appium-flow-editor__grid">
|
|
734
|
+
<el-form-item label="是,进入">
|
|
735
|
+
<el-select
|
|
736
|
+
:model-value="step.flow?.yesTargetId || ''"
|
|
737
|
+
:disabled="disabled"
|
|
738
|
+
clearable
|
|
739
|
+
placeholder="默认下一步"
|
|
740
|
+
@update:model-value="patchFlow(index, { yesTargetId: String($event || '') })"
|
|
741
|
+
>
|
|
742
|
+
<el-option v-for="option in scopedStepOptions(step)" :key="option.value" :label="option.label" :value="option.value" />
|
|
743
|
+
</el-select>
|
|
744
|
+
</el-form-item>
|
|
745
|
+
<el-form-item label="否,进入">
|
|
746
|
+
<el-select
|
|
747
|
+
:model-value="step.flow?.noTargetId || ''"
|
|
748
|
+
:disabled="disabled"
|
|
749
|
+
clearable
|
|
750
|
+
placeholder="默认结束"
|
|
751
|
+
@update:model-value="patchFlow(index, { noTargetId: String($event || '') })"
|
|
752
|
+
>
|
|
753
|
+
<el-option v-for="option in scopedStepOptions(step)" :key="option.value" :label="option.label" :value="option.value" />
|
|
754
|
+
</el-select>
|
|
755
|
+
</el-form-item>
|
|
756
|
+
</div>
|
|
757
|
+
</template>
|
|
758
|
+
<div class="appium-flow-editor__actions">
|
|
759
|
+
<el-button :icon="Timer" :disabled="disabled" @click="$emit('addDelay', index)">
|
|
760
|
+
插入延时
|
|
761
|
+
</el-button>
|
|
762
|
+
<el-button
|
|
763
|
+
v-if="defaultKind(step) !== 'condition' && stepScope(step) === 'main'"
|
|
764
|
+
:disabled="disabled"
|
|
765
|
+
@click="patchFlow(index, { nodeKind: 'condition', yesTargetId: '', noTargetId: '' })"
|
|
766
|
+
>
|
|
767
|
+
转为判断节点
|
|
768
|
+
</el-button>
|
|
769
|
+
</div>
|
|
770
|
+
</el-form>
|
|
771
|
+
</div>
|
|
772
|
+
|
|
773
|
+
<el-dropdown
|
|
774
|
+
v-if="defaultKind(step) !== 'condition'"
|
|
775
|
+
trigger="click"
|
|
776
|
+
:disabled="!canOpenInsertMenu()"
|
|
777
|
+
max-height="320px"
|
|
778
|
+
popper-class="appium-action-dropdown"
|
|
779
|
+
@command="insertAction(index, $event)"
|
|
72
780
|
>
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
781
|
+
<button
|
|
782
|
+
type="button"
|
|
783
|
+
class="appium-flow-insert"
|
|
784
|
+
:disabled="!canOpenInsertMenu()"
|
|
785
|
+
>
|
|
786
|
+
<el-icon><Timer /></el-icon>
|
|
787
|
+
<span>插入操作</span>
|
|
788
|
+
</button>
|
|
789
|
+
<template #dropdown>
|
|
790
|
+
<el-dropdown-menu>
|
|
791
|
+
<template v-for="group in actionGroupsForStep(step)" :key="group.title">
|
|
792
|
+
<div class="appium-action-dropdown__group">{{ group.title }}</div>
|
|
793
|
+
<el-dropdown-item
|
|
794
|
+
v-for="action in group.actions"
|
|
795
|
+
:key="action.type"
|
|
796
|
+
:command="action.type"
|
|
797
|
+
:disabled="isInsertActionDisabled(action.type)"
|
|
798
|
+
>
|
|
799
|
+
{{ action.label }}
|
|
800
|
+
</el-dropdown-item>
|
|
801
|
+
</template>
|
|
802
|
+
</el-dropdown-menu>
|
|
803
|
+
</template>
|
|
804
|
+
</el-dropdown>
|
|
805
|
+
</div>
|
|
806
|
+
<div class="appium-flow-end">
|
|
807
|
+
<div class="appium-flow-end__node">结束</div>
|
|
808
|
+
</div>
|
|
809
|
+
<div class="appium-flow-boundary appium-flow-boundary--post">
|
|
810
|
+
<span class="appium-flow-boundary__label">后置操作</span>
|
|
811
|
+
<el-dropdown
|
|
812
|
+
trigger="click"
|
|
813
|
+
:disabled="!canOpenInsertMenu()"
|
|
814
|
+
max-height="320px"
|
|
815
|
+
popper-class="appium-action-dropdown"
|
|
816
|
+
@command="insertAction(-3, $event)"
|
|
817
|
+
>
|
|
818
|
+
<button
|
|
819
|
+
type="button"
|
|
820
|
+
class="appium-flow-insert"
|
|
821
|
+
:disabled="!canOpenInsertMenu()"
|
|
822
|
+
>
|
|
823
|
+
<el-icon><Timer /></el-icon>
|
|
824
|
+
<span>添加后置操作</span>
|
|
825
|
+
</button>
|
|
826
|
+
<template #dropdown>
|
|
827
|
+
<el-dropdown-menu>
|
|
828
|
+
<template v-for="group in boundaryActionGroups" :key="group.title">
|
|
829
|
+
<div class="appium-action-dropdown__group">{{ group.title }}</div>
|
|
830
|
+
<el-dropdown-item
|
|
831
|
+
v-for="action in group.actions"
|
|
832
|
+
:key="action.type"
|
|
833
|
+
:command="action.type"
|
|
834
|
+
:disabled="isInsertActionDisabled(action.type)"
|
|
835
|
+
>
|
|
836
|
+
{{ action.label }}
|
|
837
|
+
</el-dropdown-item>
|
|
838
|
+
</template>
|
|
839
|
+
</el-dropdown-menu>
|
|
840
|
+
</template>
|
|
841
|
+
</el-dropdown>
|
|
842
|
+
</div>
|
|
76
843
|
</div>
|
|
77
844
|
</div>
|
|
845
|
+
<el-dialog
|
|
846
|
+
v-model="flowDialogVisible"
|
|
847
|
+
title="流程总览"
|
|
848
|
+
width="86vw"
|
|
849
|
+
class="appium-flow-dialog"
|
|
850
|
+
append-to-body
|
|
851
|
+
>
|
|
852
|
+
<div class="appium-flow-dialog-canvas">
|
|
853
|
+
<div class="appium-flow-content appium-flow-content--overview">
|
|
854
|
+
<div class="appium-flow-boundary appium-flow-boundary--pre">
|
|
855
|
+
<span class="appium-flow-boundary__label">前置操作</span>
|
|
856
|
+
</div>
|
|
857
|
+
<div class="appium-flow-start appium-flow-start--overview">
|
|
858
|
+
<div class="appium-flow-start__node">开始</div>
|
|
859
|
+
</div>
|
|
860
|
+
<div
|
|
861
|
+
v-for="{ step, index } in visibleStepItems"
|
|
862
|
+
:key="step.id"
|
|
863
|
+
class="appium-flow-item"
|
|
864
|
+
:class="[
|
|
865
|
+
`appium-flow-item--${stepScope(step)}`,
|
|
866
|
+
{ 'appium-flow-item--condition': defaultKind(step) === 'condition' },
|
|
867
|
+
]"
|
|
868
|
+
>
|
|
869
|
+
<div
|
|
870
|
+
class="appium-flow-node"
|
|
871
|
+
:class="`appium-flow-node--${defaultKind(step)}`"
|
|
872
|
+
>
|
|
873
|
+
<span class="appium-flow-node__toggle">
|
|
874
|
+
<span class="appium-flow-node__index">{{ index + 1 }}</span>
|
|
875
|
+
<span class="appium-flow-node__body">
|
|
876
|
+
<span class="appium-flow-node__title" :title="step.label">{{ step.label }}</span>
|
|
877
|
+
<span class="appium-flow-node__meta" :title="stepMeta(step)">
|
|
878
|
+
{{ kindLabel(defaultKind(step)) }} · {{ typeLabel(step) }}
|
|
879
|
+
<template v-if="step.optional"> · 可选</template>
|
|
880
|
+
<template v-if="stepMeta(step)"> · {{ stepMeta(step) }}</template>
|
|
881
|
+
</span>
|
|
882
|
+
</span>
|
|
883
|
+
</span>
|
|
884
|
+
</div>
|
|
885
|
+
|
|
886
|
+
<div v-if="defaultKind(step) === 'condition'" class="appium-flow-branches">
|
|
887
|
+
<svg class="appium-flow-branch-lines" viewBox="0 0 100 48" preserveAspectRatio="none" aria-hidden="true">
|
|
888
|
+
<path d="M50 0 C50 16 25 16 25 31 L25 48" />
|
|
889
|
+
<path d="M50 0 C50 16 75 16 75 31 L75 48" />
|
|
890
|
+
</svg>
|
|
891
|
+
<div class="appium-flow-branch appium-flow-branch--yes">
|
|
892
|
+
<strong class="appium-flow-branch__label">是</strong>
|
|
893
|
+
<div v-if="branchStepItems(step, 'yes').length" class="appium-flow-branch-steps">
|
|
894
|
+
<div
|
|
895
|
+
v-for="item in branchStepItems(step, 'yes')"
|
|
896
|
+
:key="item.step.id"
|
|
897
|
+
class="appium-flow-branch-step"
|
|
898
|
+
>
|
|
899
|
+
<span class="appium-flow-branch-step__index">{{ item.index + 1 }}</span>
|
|
900
|
+
<span class="appium-flow-branch-step__body">
|
|
901
|
+
<span class="appium-flow-branch-step__title">{{ item.step.label }}</span>
|
|
902
|
+
<span class="appium-flow-branch-step__meta">
|
|
903
|
+
{{ kindLabel(defaultKind(item.step)) }} · {{ typeLabel(item.step) }}
|
|
904
|
+
</span>
|
|
905
|
+
</span>
|
|
906
|
+
</div>
|
|
907
|
+
</div>
|
|
908
|
+
<span v-else class="appium-flow-branch__target">{{ targetLabel(step.flow?.yesTargetId) }}</span>
|
|
909
|
+
</div>
|
|
910
|
+
<div class="appium-flow-branch appium-flow-branch--no">
|
|
911
|
+
<strong class="appium-flow-branch__label">否</strong>
|
|
912
|
+
<div v-if="branchStepItems(step, 'no').length" class="appium-flow-branch-steps">
|
|
913
|
+
<div
|
|
914
|
+
v-for="item in branchStepItems(step, 'no')"
|
|
915
|
+
:key="item.step.id"
|
|
916
|
+
class="appium-flow-branch-step"
|
|
917
|
+
>
|
|
918
|
+
<span class="appium-flow-branch-step__index">{{ item.index + 1 }}</span>
|
|
919
|
+
<span class="appium-flow-branch-step__body">
|
|
920
|
+
<span class="appium-flow-branch-step__title">{{ item.step.label }}</span>
|
|
921
|
+
<span class="appium-flow-branch-step__meta">
|
|
922
|
+
{{ kindLabel(defaultKind(item.step)) }} · {{ typeLabel(item.step) }}
|
|
923
|
+
</span>
|
|
924
|
+
</span>
|
|
925
|
+
</div>
|
|
926
|
+
</div>
|
|
927
|
+
<span v-else class="appium-flow-branch__target">{{ targetLabel(step.flow?.noTargetId) }}</span>
|
|
928
|
+
</div>
|
|
929
|
+
</div>
|
|
930
|
+
</div>
|
|
931
|
+
<div class="appium-flow-end">
|
|
932
|
+
<div class="appium-flow-end__node">结束</div>
|
|
933
|
+
</div>
|
|
934
|
+
<div class="appium-flow-boundary appium-flow-boundary--post">
|
|
935
|
+
<span class="appium-flow-boundary__label">后置操作</span>
|
|
936
|
+
</div>
|
|
937
|
+
</div>
|
|
938
|
+
</div>
|
|
939
|
+
</el-dialog>
|
|
78
940
|
</div>
|
|
79
941
|
</template>
|