android-midscene-automation 0.1.29 → 0.1.30
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/CHANGELOG.md +4 -0
- package/README.md +4 -0
- package/package.json +1 -1
- package/src/appium-recorder/AppiumPage.vue +20 -8
- package/src/appium-recorder/components/FlowCanvas.vue +2 -0
- package/src/appium-recorder/components/FlowNodeCard.vue +6 -1
- package/src/appium-recorder/components/NodeDetail.vue +22 -4
- package/src/appium-recorder/components/RecordedSteps.vue +86 -40
- package/src/appium-recorder/flow-graph.ts +3 -0
- package/src/appium-recorder/flow-remove.ts +6 -0
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -1229,6 +1229,10 @@ Linux: ~/.local/share/android-midscene-automation
|
|
|
1229
1229
|
|
|
1230
1230
|
# 项目更新记录
|
|
1231
1231
|
|
|
1232
|
+
## v0.1.30
|
|
1233
|
+
|
|
1234
|
+
- 新增节点批量删除功能,支持多选节点,选中节点及所属子节点以柔和红色背景和浅色文字标记,确认后统一删除;支持普通与放大视图同步操作。
|
|
1235
|
+
|
|
1232
1236
|
## v0.1.29
|
|
1233
1237
|
|
|
1234
1238
|
- 判断节点新增“合并分支”,支持选择一侧公共流程起点,将两侧后续汇入同一段居中流程;另一侧可保留独有操作或确认删除重复后缀,支持嵌套判断及各类分支,公共节点只执行一次且不随单侧节点删除。
|
package/package.json
CHANGED
|
@@ -2095,14 +2095,11 @@ watch(
|
|
|
2095
2095
|
/>
|
|
2096
2096
|
</el-select>
|
|
2097
2097
|
</div>
|
|
2098
|
-
<
|
|
2098
|
+
<details
|
|
2099
2099
|
v-if="scriptActivityMismatch"
|
|
2100
|
-
class="appium-activity-
|
|
2101
|
-
title="当前脚本已锁定"
|
|
2102
|
-
type="warning"
|
|
2103
|
-
show-icon
|
|
2104
|
-
:closable="false"
|
|
2100
|
+
class="appium-activity-summary"
|
|
2105
2101
|
>
|
|
2102
|
+
<summary>当前页面与脚本不一致,部分录制操作不可用</summary>
|
|
2106
2103
|
<div class="appium-activity-lock-alert__details">
|
|
2107
2104
|
<div>
|
|
2108
2105
|
<strong>脚本绑定</strong>
|
|
@@ -2114,7 +2111,7 @@ watch(
|
|
|
2114
2111
|
</div>
|
|
2115
2112
|
<p>请在录制流程中添加或执行“启动 APP”节点;Activity 匹配后会自动解除编辑锁定。</p>
|
|
2116
2113
|
</div>
|
|
2117
|
-
</
|
|
2114
|
+
</details>
|
|
2118
2115
|
<NodeDetail
|
|
2119
2116
|
:node="selectedNode"
|
|
2120
2117
|
:current-activity="currentActivity"
|
|
@@ -2122,7 +2119,7 @@ watch(
|
|
|
2122
2119
|
/>
|
|
2123
2120
|
</section>
|
|
2124
2121
|
|
|
2125
|
-
<section class="appium-workbench__section">
|
|
2122
|
+
<section class="appium-workbench__section appium-workbench__flow">
|
|
2126
2123
|
<h3>录制步骤</h3>
|
|
2127
2124
|
<RecordedSteps
|
|
2128
2125
|
:key="newScriptRevision"
|
|
@@ -2403,3 +2400,18 @@ watch(
|
|
|
2403
2400
|
</el-tooltip>
|
|
2404
2401
|
</section>
|
|
2405
2402
|
</template>
|
|
2403
|
+
|
|
2404
|
+
<style scoped>
|
|
2405
|
+
.appium-activity-summary { margin: 8px 0; color: #946000; font-size: 12px; }
|
|
2406
|
+
.appium-activity-summary summary { cursor: pointer; line-height: 20px; }
|
|
2407
|
+
.appium-activity-summary[open] .appium-activity-lock-alert__details { max-height: 110px; overflow: auto; margin-top: 6px; }
|
|
2408
|
+
:deep(.appium-recorder-card--workbench > .el-card__body) { overflow: hidden; }
|
|
2409
|
+
:deep(.appium-workbench-tabs > .el-tabs__content) { overflow: hidden; }
|
|
2410
|
+
:deep(.appium-workbench-tabs .el-tab-pane) { height: 100%; overflow: auto; }
|
|
2411
|
+
.appium-workbench { height: 100%; box-sizing: border-box; grid-template-rows: auto minmax(180px, 1fr); gap: 10px; overflow: auto; }
|
|
2412
|
+
.appium-workbench__flow { display: flex; flex-direction: column; min-height: 0; }
|
|
2413
|
+
.appium-workbench__flow h3 { margin-bottom: 6px; }
|
|
2414
|
+
.appium-workbench__flow :deep(.appium-recorded-steps-panel) { display: flex; flex-direction: column; flex: 1; min-height: 0; }
|
|
2415
|
+
.appium-workbench__flow :deep(.appium-flow-toolbar) { flex: none; margin-bottom: 8px; }
|
|
2416
|
+
.appium-workbench__flow :deep(.appium-flow-canvas--vue:not(.appium-flow-canvas--dialog)) { flex: 1; height: auto; min-height: 120px; }
|
|
2417
|
+
</style>
|
|
@@ -21,6 +21,7 @@ const props = defineProps<{
|
|
|
21
21
|
steps: AppiumRecordedStep[];
|
|
22
22
|
expandedStepIndex: number | null;
|
|
23
23
|
copyMode: boolean;
|
|
24
|
+
deleteMode?: boolean;
|
|
24
25
|
selectedCopyIndexes: number[];
|
|
25
26
|
disabled?: boolean;
|
|
26
27
|
removeDisabled?: boolean;
|
|
@@ -61,6 +62,7 @@ let resetViewFrame = 0;
|
|
|
61
62
|
let pendingResetView = false;
|
|
62
63
|
|
|
63
64
|
const graph = computed(() => buildFlowGraph(props.steps, {
|
|
65
|
+
deleteMode: props.deleteMode,
|
|
64
66
|
mergeDisabled: props.mergeDisabled,
|
|
65
67
|
aiRecognitionModelConfigured: props.aiRecognitionModelConfigured,
|
|
66
68
|
expandedStepIndex: props.expandedStepIndex,
|
|
@@ -159,6 +159,7 @@ watch(() => props.data, () => {
|
|
|
159
159
|
{
|
|
160
160
|
'appium-flow-step-shell--expanded': data.expanded,
|
|
161
161
|
'appium-flow-step-shell--selected': data.selected,
|
|
162
|
+
'appium-flow-step-shell--delete-selected': data.selected && data.deleteMode,
|
|
162
163
|
'appium-flow-step-shell--copying': data.copyMode,
|
|
163
164
|
'appium-flow-step-shell--readonly': props.readonly,
|
|
164
165
|
},
|
|
@@ -181,7 +182,7 @@ watch(() => props.data, () => {
|
|
|
181
182
|
<em v-if="data.note" :title="data.note">{{ data.note }}</em>
|
|
182
183
|
</span>
|
|
183
184
|
</button>
|
|
184
|
-
<span v-if="!props.readonly" class="appium-flow-step-card__actions nodrag nopan" @click.stop>
|
|
185
|
+
<span v-if="!props.readonly && !data.deleteMode" class="appium-flow-step-card__actions nodrag nopan" @click.stop>
|
|
185
186
|
<el-tooltip v-if="data.flowKind === 'condition' && !data.copyMode" content="合并分支:将两侧后续节点汇入公共流程" placement="top" :show-after="200">
|
|
186
187
|
<span class="appium-node-action-tooltip"><el-button text size="small" :icon="Connection"
|
|
187
188
|
title="合并分支" aria-label="合并分支" :disabled="data.mergeDisabled || Boolean(data.step.flow?.successTargetId)"
|
|
@@ -263,6 +264,10 @@ watch(() => props.data, () => {
|
|
|
263
264
|
</template>
|
|
264
265
|
|
|
265
266
|
<style scoped>
|
|
267
|
+
.appium-flow-step-shell--delete-selected .appium-flow-step-card { background: #b83232; border-color: #a52a2a; }
|
|
268
|
+
.appium-flow-step-shell--delete-selected .appium-flow-step-card__content strong,
|
|
269
|
+
.appium-flow-step-shell--delete-selected .appium-flow-step-card__content em { color: #fff; }
|
|
270
|
+
.appium-flow-step-shell--delete-selected .appium-flow-step-card__content small { color: #fee2e2; }
|
|
266
271
|
.appium-node-action-tooltip { display: inline-flex; }
|
|
267
272
|
.appium-ai-model-warning { float: left; margin: 2px 6px 0 0; font-size: 16px; }
|
|
268
273
|
</style>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue';
|
|
3
|
+
import { View } from '@element-plus/icons-vue';
|
|
2
4
|
import type { AppiumNode } from '../types';
|
|
3
5
|
|
|
4
6
|
defineProps<{
|
|
@@ -6,6 +8,7 @@ defineProps<{
|
|
|
6
8
|
currentActivity?: string;
|
|
7
9
|
loading?: boolean;
|
|
8
10
|
}>();
|
|
11
|
+
const detailsVisible = ref(false);
|
|
9
12
|
</script>
|
|
10
13
|
|
|
11
14
|
<template>
|
|
@@ -14,6 +17,15 @@ defineProps<{
|
|
|
14
17
|
element-loading-text="正在检测页面变化..."
|
|
15
18
|
class="appium-node-detail"
|
|
16
19
|
>
|
|
20
|
+
<div class="appium-node-summary">
|
|
21
|
+
<div class="appium-node-summary__text" v-if="node">
|
|
22
|
+
<strong :title="node.label">{{ node.label }}</strong>
|
|
23
|
+
<span>{{ node.className.split('.').pop() }}<template v-if="node.checkable"> · {{ node.checked === undefined ? '勾选状态未知' : node.checked ? '已勾选' : '未勾选' }}</template><template v-if="node.selector.matchCount"> · {{ node.selector.matchCount > 1 ? `定位重复 ${node.selector.matchCount}` : '定位唯一' }}</template></span>
|
|
24
|
+
</div>
|
|
25
|
+
<span v-else class="appium-node-summary__empty">请选择组件</span>
|
|
26
|
+
<el-button size="small" type="primary" :icon="View" :disabled="!node" @click="detailsVisible = true">详情</el-button>
|
|
27
|
+
</div>
|
|
28
|
+
<el-drawer v-model="detailsVisible" title="组件详情" size="520px" style="max-width: 94vw" append-to-body>
|
|
17
29
|
<template v-if="node">
|
|
18
30
|
<div class="appium-node-detail__title">{{ node.label }}</div>
|
|
19
31
|
<el-descriptions :column="1" border size="small">
|
|
@@ -25,7 +37,6 @@ defineProps<{
|
|
|
25
37
|
<el-descriptions-item v-if="node.checkable" label="checked">{{ node.checked === undefined ? '-' : String(node.checked) }}</el-descriptions-item>
|
|
26
38
|
<el-descriptions-item label="当前 Activity">{{ currentActivity || '-' }}</el-descriptions-item>
|
|
27
39
|
<el-descriptions-item label="selector">
|
|
28
|
-
<span v-if="currentActivity">Activity {{ currentActivity }} · </span>
|
|
29
40
|
{{ node.selector.strategy }} {{ node.selector.value || '' }}
|
|
30
41
|
<el-tag
|
|
31
42
|
v-if="node.selector.matchCount"
|
|
@@ -37,12 +48,10 @@ defineProps<{
|
|
|
37
48
|
</el-descriptions-item>
|
|
38
49
|
<el-descriptions-item label="推荐定位">
|
|
39
50
|
<span v-if="node.contextSelector">
|
|
40
|
-
<template v-if="currentActivity">Activity {{ currentActivity }} + </template>
|
|
41
51
|
父级 {{ node.contextSelector.strategy }} {{ node.contextSelector.value || '' }}
|
|
42
52
|
+ 子级 {{ node.selector.strategy }} {{ node.selector.value || '' }}
|
|
43
53
|
</span>
|
|
44
54
|
<span v-else>
|
|
45
|
-
<template v-if="currentActivity">Activity {{ currentActivity }} + </template>
|
|
46
55
|
{{ node.selector.unique ? '当前 selector 唯一' : '使用当前 selector 或坐标兜底' }}
|
|
47
56
|
</span>
|
|
48
57
|
</el-descriptions-item>
|
|
@@ -54,7 +63,16 @@ defineProps<{
|
|
|
54
63
|
</el-descriptions-item>
|
|
55
64
|
</el-descriptions>
|
|
56
65
|
</template>
|
|
57
|
-
<
|
|
66
|
+
<p v-else>请选择组件</p>
|
|
67
|
+
</el-drawer>
|
|
58
68
|
|
|
59
69
|
</div>
|
|
60
70
|
</template>
|
|
71
|
+
|
|
72
|
+
<style scoped>
|
|
73
|
+
.appium-node-summary { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 44px; padding: 6px 0; }
|
|
74
|
+
.appium-node-summary__text { display: grid; gap: 2px; min-width: 0; font-size: 12px; }
|
|
75
|
+
.appium-node-summary__text strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
|
|
76
|
+
.appium-node-summary__text span, .appium-node-summary__empty { color: var(--el-text-color-secondary); font-size: 12px; }
|
|
77
|
+
:deep(.el-descriptions__content) { overflow-wrap: anywhere; word-break: break-word; }
|
|
78
|
+
</style>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, nextTick, shallowRef, watch } from 'vue';
|
|
3
|
-
import { ElMessage } from 'element-plus';
|
|
4
|
-
import { Aim, CopyDocument, FullScreen } from '@element-plus/icons-vue';
|
|
3
|
+
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
4
|
+
import { Aim, CopyDocument, FullScreen, Close, Delete } from '@element-plus/icons-vue';
|
|
5
|
+
import { removeFlowSteps } from '../flow-remove';
|
|
5
6
|
import type { AppiumRecordedStep } from '../types';
|
|
6
7
|
import { createFlowClipboard } from '../flow-copy';
|
|
7
8
|
import FlowCanvas from './FlowCanvas.vue';
|
|
@@ -37,6 +38,8 @@ const emit = defineEmits<{
|
|
|
37
38
|
const expandedStepIndex = shallowRef<number | null>(null);
|
|
38
39
|
const mergeConditionId = shallowRef('');
|
|
39
40
|
const copyMode = shallowRef(false);
|
|
41
|
+
const deleteMode = shallowRef(false);
|
|
42
|
+
const deleting = shallowRef(false);
|
|
40
43
|
const selectedCopyIndexes = shallowRef<number[]>([]);
|
|
41
44
|
const flowDialogVisible = shallowRef(false);
|
|
42
45
|
const mainResetViewToken = shallowRef(0);
|
|
@@ -126,7 +129,7 @@ function toggleCopySelection(index: number) {
|
|
|
126
129
|
const step = props.steps[index];
|
|
127
130
|
if (!step) return;
|
|
128
131
|
if (step.type === 'launchApp' || step.type === 'clearAppData') {
|
|
129
|
-
ElMessage.warning('App 初始化节点不能复制');
|
|
132
|
+
ElMessage.warning(deleteMode.value ? 'App 初始化节点不参与批量删除' : 'App 初始化节点不能复制');
|
|
130
133
|
return;
|
|
131
134
|
}
|
|
132
135
|
if (selectedCopyIndexes.value.includes(index)) {
|
|
@@ -144,7 +147,8 @@ function toggleCopySelection(index: number) {
|
|
|
144
147
|
try {
|
|
145
148
|
createFlowClipboard(props.steps, candidate);
|
|
146
149
|
} catch (error) {
|
|
147
|
-
|
|
150
|
+
const message = error instanceof Error ? error.message : '当前节点不能一起选择';
|
|
151
|
+
ElMessage.warning(deleteMode.value ? message.replace(/复制/g, '删除') : message);
|
|
148
152
|
return;
|
|
149
153
|
}
|
|
150
154
|
selectedCopyIndexes.value = candidate;
|
|
@@ -166,12 +170,14 @@ function handleNodeClick(index: number) {
|
|
|
166
170
|
}
|
|
167
171
|
|
|
168
172
|
function startCopyMode() {
|
|
173
|
+
deleteMode.value = false;
|
|
169
174
|
copyMode.value = true;
|
|
170
175
|
selectedCopyIndexes.value = [];
|
|
171
176
|
expandedStepIndex.value = null;
|
|
172
177
|
}
|
|
173
178
|
|
|
174
179
|
function cancelCopyMode() {
|
|
180
|
+
deleteMode.value = false;
|
|
175
181
|
copyMode.value = false;
|
|
176
182
|
selectedCopyIndexes.value = [];
|
|
177
183
|
}
|
|
@@ -182,6 +188,33 @@ function copySelectedNodes() {
|
|
|
182
188
|
cancelCopyMode();
|
|
183
189
|
}
|
|
184
190
|
|
|
191
|
+
function startDeleteMode() {
|
|
192
|
+
if (props.mergeDisabled || props.removeDisabled) return;
|
|
193
|
+
startCopyMode();
|
|
194
|
+
deleteMode.value = true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function deleteSelectedNodes() {
|
|
198
|
+
if (deleting.value || props.mergeDisabled || props.removeDisabled || !selectedCopyIndexes.value.length) return;
|
|
199
|
+
const original = props.steps;
|
|
200
|
+
const next = removeFlowSteps(original, selectedCopyIndexes.value);
|
|
201
|
+
deleting.value = true;
|
|
202
|
+
try {
|
|
203
|
+
await ElMessageBox.confirm(`确认删除选中的 ${original.length - next.length} 个节点?所属分支子节点会一起删除,公共流程保留。`, '批量删除', {
|
|
204
|
+
type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消', appendTo: 'body',
|
|
205
|
+
});
|
|
206
|
+
if (props.steps !== original || props.mergeDisabled || props.removeDisabled) {
|
|
207
|
+
ElMessage.warning('流程或运行状态已变化,请重新选择');
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
emit('replaceSteps', next);
|
|
211
|
+
cancelCopyMode();
|
|
212
|
+
} catch { /* 取消确认时保留选中状态。 */ }
|
|
213
|
+
finally { deleting.value = false; }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
watch(() => props.steps, () => { if (copyMode.value) cancelCopyMode(); });
|
|
217
|
+
|
|
185
218
|
function resetMainFlowPosition() {
|
|
186
219
|
mainResetViewToken.value += 1;
|
|
187
220
|
}
|
|
@@ -221,40 +254,43 @@ function updateStep(index: number, step: AppiumRecordedStep) {
|
|
|
221
254
|
<div class="appium-flow-toolbar">
|
|
222
255
|
<template v-if="copyMode">
|
|
223
256
|
<span class="appium-flow-toolbar__status">已选择 {{ selectedCopyIndexes.length }} 个节点</span>
|
|
224
|
-
<el-
|
|
257
|
+
<el-tooltip :content="deleteMode ? '删除选中' : '复制选中'" placement="top" :show-after="200"><span class="flow-toolbar-icon">
|
|
258
|
+
<el-button :aria-label="deleteMode ? '删除选中' : '复制选中'"
|
|
225
259
|
size="small"
|
|
226
|
-
type="primary"
|
|
227
|
-
:
|
|
228
|
-
:
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
</el-
|
|
233
|
-
<el-button size="small" @click="cancelCopyMode"
|
|
260
|
+
:type="deleteMode ? 'danger' : 'primary'"
|
|
261
|
+
:loading="deleting"
|
|
262
|
+
:disabled="!selectedCopyIndexes.length || (deleteMode && (mergeDisabled || removeDisabled))"
|
|
263
|
+
:icon="deleteMode ? Delete : CopyDocument"
|
|
264
|
+
@click="deleteMode ? deleteSelectedNodes() : copySelectedNodes()"
|
|
265
|
+
/>
|
|
266
|
+
</span></el-tooltip>
|
|
267
|
+
<el-tooltip content="取消" placement="top" :show-after="200"><span class="flow-toolbar-icon"><el-button size="small" :icon="Close" aria-label="取消" @click="cancelCopyMode" /></span></el-tooltip>
|
|
234
268
|
</template>
|
|
235
|
-
<el-
|
|
236
|
-
|
|
269
|
+
<el-tooltip v-else content="批量复制" placement="top" :show-after="200"><span class="flow-toolbar-icon">
|
|
270
|
+
<el-button aria-label="批量复制"
|
|
237
271
|
size="small"
|
|
238
272
|
:icon="CopyDocument"
|
|
239
273
|
:disabled="!steps.length"
|
|
240
274
|
@click="startCopyMode"
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
275
|
+
/>
|
|
276
|
+
</span></el-tooltip>
|
|
277
|
+
<el-tooltip v-if="!copyMode" content="批量删除" placement="top" :show-after="200"><span class="flow-toolbar-icon">
|
|
278
|
+
<el-button aria-label="批量删除" size="small" :icon="Delete" :disabled="!steps.length || mergeDisabled || removeDisabled" @click="startDeleteMode" />
|
|
279
|
+
</span></el-tooltip>
|
|
280
|
+
<el-tooltip content="还原位置" placement="top" :show-after="200"><span class="flow-toolbar-icon">
|
|
281
|
+
<el-button aria-label="还原位置"
|
|
245
282
|
size="small"
|
|
246
283
|
:icon="Aim"
|
|
247
284
|
@click="resetMainFlowPosition"
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
<el-button
|
|
285
|
+
/>
|
|
286
|
+
</span></el-tooltip>
|
|
287
|
+
<el-tooltip content="放大" placement="top" :show-after="200"><span class="flow-toolbar-icon">
|
|
288
|
+
<el-button aria-label="放大"
|
|
252
289
|
size="small"
|
|
253
290
|
:icon="FullScreen"
|
|
254
291
|
@click="flowDialogVisible = true"
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
</el-button>
|
|
292
|
+
/>
|
|
293
|
+
</span></el-tooltip>
|
|
258
294
|
</div>
|
|
259
295
|
|
|
260
296
|
<FlowCanvas
|
|
@@ -262,6 +298,7 @@ function updateStep(index: number, step: AppiumRecordedStep) {
|
|
|
262
298
|
:steps="steps"
|
|
263
299
|
:expanded-step-index="expandedStepIndex"
|
|
264
300
|
:copy-mode="copyMode"
|
|
301
|
+
:delete-mode="deleteMode"
|
|
265
302
|
:selected-copy-indexes="selectedCopyIndexes"
|
|
266
303
|
:disabled="disabled"
|
|
267
304
|
:remove-disabled="removeDisabled"
|
|
@@ -301,26 +338,29 @@ function updateStep(index: number, step: AppiumRecordedStep) {
|
|
|
301
338
|
<div class="appium-flow-toolbar appium-flow-dialog__toolbar">
|
|
302
339
|
<template v-if="copyMode">
|
|
303
340
|
<span class="appium-flow-toolbar__status">已选择 {{ selectedCopyIndexes.length }} 个节点</span>
|
|
304
|
-
<el-
|
|
341
|
+
<el-tooltip :content="deleteMode ? '删除选中' : '复制选中'" placement="top" :show-after="200"><span class="flow-toolbar-icon">
|
|
342
|
+
<el-button :aria-label="deleteMode ? '删除选中' : '复制选中'"
|
|
305
343
|
size="small"
|
|
306
|
-
type="primary"
|
|
307
|
-
:
|
|
308
|
-
:
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
</el-
|
|
313
|
-
<el-button size="small" @click="cancelCopyMode"
|
|
344
|
+
:type="deleteMode ? 'danger' : 'primary'"
|
|
345
|
+
:loading="deleting"
|
|
346
|
+
:disabled="!selectedCopyIndexes.length || (deleteMode && (mergeDisabled || removeDisabled))"
|
|
347
|
+
:icon="deleteMode ? Delete : CopyDocument"
|
|
348
|
+
@click="deleteMode ? deleteSelectedNodes() : copySelectedNodes()"
|
|
349
|
+
/>
|
|
350
|
+
</span></el-tooltip>
|
|
351
|
+
<el-tooltip content="取消" placement="top" :show-after="200"><span class="flow-toolbar-icon"><el-button size="small" :icon="Close" aria-label="取消" @click="cancelCopyMode" /></span></el-tooltip>
|
|
314
352
|
</template>
|
|
315
|
-
<el-
|
|
316
|
-
|
|
353
|
+
<el-tooltip v-else content="批量复制" placement="top" :show-after="200"><span class="flow-toolbar-icon">
|
|
354
|
+
<el-button aria-label="批量复制"
|
|
317
355
|
size="small"
|
|
318
356
|
:icon="CopyDocument"
|
|
319
357
|
:disabled="!steps.length"
|
|
320
358
|
@click="startCopyMode"
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
359
|
+
/>
|
|
360
|
+
</span></el-tooltip>
|
|
361
|
+
<el-tooltip v-if="!copyMode" content="批量删除" placement="top" :show-after="200"><span class="flow-toolbar-icon">
|
|
362
|
+
<el-button aria-label="批量删除" size="small" :icon="Delete" :disabled="!steps.length || mergeDisabled || removeDisabled" @click="startDeleteMode" />
|
|
363
|
+
</span></el-tooltip>
|
|
324
364
|
</div>
|
|
325
365
|
<FlowCanvas
|
|
326
366
|
id="appium-flow-dialog"
|
|
@@ -328,6 +368,7 @@ function updateStep(index: number, step: AppiumRecordedStep) {
|
|
|
328
368
|
:steps="steps"
|
|
329
369
|
:expanded-step-index="expandedStepIndex"
|
|
330
370
|
:copy-mode="copyMode"
|
|
371
|
+
:delete-mode="deleteMode"
|
|
331
372
|
:selected-copy-indexes="selectedCopyIndexes"
|
|
332
373
|
:disabled="disabled"
|
|
333
374
|
:remove-disabled="removeDisabled"
|
|
@@ -361,3 +402,8 @@ function updateStep(index: number, step: AppiumRecordedStep) {
|
|
|
361
402
|
@close="mergeConditionId = ''" @confirm="expandedStepIndex = null; emit('replaceSteps', $event)" />
|
|
362
403
|
</div>
|
|
363
404
|
</template>
|
|
405
|
+
|
|
406
|
+
<style scoped>
|
|
407
|
+
.flow-toolbar-icon { display: inline-flex; }
|
|
408
|
+
.flow-toolbar-icon .el-button { width: 30px; height: 30px; padding: 0; }
|
|
409
|
+
</style>
|
|
@@ -90,6 +90,7 @@ export type FlowGraphNodeData =
|
|
|
90
90
|
expanded: boolean;
|
|
91
91
|
selected: boolean;
|
|
92
92
|
copyMode: boolean;
|
|
93
|
+
deleteMode?: boolean;
|
|
93
94
|
disabled?: boolean;
|
|
94
95
|
removeDisabled?: boolean;
|
|
95
96
|
mergeDisabled?: boolean;
|
|
@@ -134,6 +135,7 @@ type BuildFlowGraphOptions = {
|
|
|
134
135
|
expandedStepIndex: number | null;
|
|
135
136
|
selectedCopyIndexes: number[];
|
|
136
137
|
copyMode: boolean;
|
|
138
|
+
deleteMode?: boolean;
|
|
137
139
|
disabled?: boolean;
|
|
138
140
|
removeDisabled?: boolean;
|
|
139
141
|
mergeDisabled?: boolean;
|
|
@@ -426,6 +428,7 @@ export function buildFlowGraph(
|
|
|
426
428
|
expanded: options.expandedStepIndex === item.index,
|
|
427
429
|
selected: options.isCopySelected(item.index),
|
|
428
430
|
copyMode: options.copyMode,
|
|
431
|
+
deleteMode: options.deleteMode,
|
|
429
432
|
disabled: item.step.type === 'launchApp' || item.step.type === 'clearAppData'
|
|
430
433
|
? options.isAppExecutionDisabled(item.step.type)
|
|
431
434
|
: item.step.type === 'aiRecognition' ? options.isInsertActionDisabled('aiRecognition') : options.disabled,
|
|
@@ -4,6 +4,12 @@ type FlowTargetKey = 'yesTargetId' | 'noTargetId' | 'successTargetId' | 'failure
|
|
|
4
4
|
|
|
5
5
|
const FLOW_TARGET_KEYS: FlowTargetKey[] = ['yesTargetId', 'noTargetId', 'successTargetId', 'failureTargetId'];
|
|
6
6
|
|
|
7
|
+
export function removeFlowSteps(steps: AppiumRecordedStep[], indexes: number[]) {
|
|
8
|
+
const ids = [...new Set(indexes)].sort((a, b) => b - a).map((index) => steps[index]?.id).filter(Boolean);
|
|
9
|
+
// 使用稳定 ID 逐个删除,避免前一次删除导致后续索引错位。
|
|
10
|
+
return ids.reduce((current, id) => removeFlowStep(current, current.findIndex((step) => step.id === id)), steps);
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
function sameBranch(step: AppiumRecordedStep, removed: AppiumRecordedStep) {
|
|
8
14
|
return Boolean(
|
|
9
15
|
removed.flow?.parentConditionId
|