huang
2025-11-20 366ba040d2447bacd3455299425e3166f1f992bb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
<template>
  <div class="task-orchestration">
    <div class="panel-header">
      <div>
        <h3>多设备测试编排</h3>
        <p v-if="group">当前设备组:{{ group.groupName }}({{ group.deviceCount || '-' }} 台设备)</p>
        <p v-else class="warning">请先在左侧选择一个设备组</p>
        <p v-if="group && loadDeviceName" class="sub-info">上大车设备:{{ loadDeviceName }}</p>
      </div>
      <div class="action-buttons">
        <el-button
          type="danger"
          plain
          :disabled="!group || !loadDeviceId || loadDeviceLoading"
          :loading="clearLoading"
          @click="handleClearPlc"
        >
          <el-icon><Delete /></el-icon>
          清空PLC
        </el-button>
        <el-button type="primary" :disabled="!group" :loading="loading" @click="handleSubmit">
          <el-icon><Promotion /></el-icon>
          启动测试
        </el-button>
      </div>
    </div>
 
    <el-form :model="form" label-width="120px">
      <el-form-item label="玻璃ID列表">
        <el-input
          v-model="glassIdsInput"
          type="textarea"
          :rows="4"
          placeholder="请输入玻璃条码,支持多行或逗号分隔"
        />
      </el-form-item>
      <el-form-item label="位置编码">
        <el-input v-model="form.positionCode" placeholder="例如:POS1" />
      </el-form-item>
      <el-form-item label="存储位置">
        <el-input-number v-model="form.storagePosition" :min="1" :max="200" />
      </el-form-item>
      <el-form-item label="执行间隔 (ms)">
        <el-input-number v-model="form.executionInterval" :min="100" :max="10000" />
      </el-form-item>
    </el-form>
  </div>
</template>
 
<script setup>
import { computed, reactive, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { Delete, Promotion } from '@element-plus/icons-vue'
import { multiDeviceTaskApi } from '@/api/device/multiDeviceTask'
import { deviceGroupApi, deviceInteractionApi } from '@/api/device/deviceManagement'
 
const props = defineProps({
  group: {
    type: Object,
    default: null
  }
})
 
const emit = defineEmits(['task-started'])
 
const form = reactive({
  positionCode: '',
  storagePosition: null,
  executionInterval: 1000
})
 
const glassIdsInput = ref('')
const loading = ref(false)
const clearLoading = ref(false)
const loadDeviceId = ref(null)
const loadDeviceName = ref('')
const loadDeviceLoading = ref(false)
 
watch(
  () => props.group,
  () => {
    glassIdsInput.value = ''
    fetchLoadDevice()
  }
)
 
const glassIds = computed(() => {
  if (!glassIdsInput.value) return []
  return glassIdsInput.value
    .split(/[\n,,]/)
    .map((item) => item.trim())
    .filter((item) => item.length > 0)
})
 
const fetchLoadDevice = async () => {
  loadDeviceId.value = null
  loadDeviceName.value = ''
  if (!props.group) {
    return
  }
  const groupId = props.group.id || props.group.groupId
  if (!groupId) {
    return
  }
  loadDeviceLoading.value = true
  try {
    const response = await deviceGroupApi.getGroupDevices(groupId)
    const rawList = response?.data
    const deviceList = Array.isArray(rawList)
      ? rawList
      : Array.isArray(rawList?.records)
      ? rawList.records
      : Array.isArray(rawList?.data)
      ? rawList.data
      : []
    const targetDevice =
      deviceList.find((item) => (item.deviceType || '').toUpperCase() === 'LOAD_VEHICLE') ||
      deviceList[0]
    if (targetDevice && targetDevice.id) {
      loadDeviceId.value = targetDevice.id
      loadDeviceName.value = targetDevice.deviceName || targetDevice.deviceCode || `ID: ${targetDevice.id}`
    }
  } catch (error) {
    console.error('加载设备信息失败:', error)
    ElMessage.error(error?.message || '获取设备信息失败')
  } finally {
    loadDeviceLoading.value = false
  }
}
 
const handleSubmit = async () => {
  if (!props.group) {
    ElMessage.warning('请先选择设备组')
    return
  }
  if (glassIds.value.length === 0) {
    ElMessage.warning('请至少输入一个玻璃ID')
    return
  }
  try {
    loading.value = true
    await multiDeviceTaskApi.startTask({
      groupId: props.group.id || props.group.groupId,
      parameters: {
        glassIds: glassIds.value,
        positionCode: form.positionCode || null,
        storagePosition: form.storagePosition,
        executionInterval: form.executionInterval
      }
    })
    ElMessage.success('任务已启动')
    emit('task-started')
  } catch (error) {
    ElMessage.error(error?.message || '任务启动失败')
  } finally {
    loading.value = false
  }
}
 
const handleClearPlc = async () => {
  if (!props.group) {
    ElMessage.warning('请先选择设备组')
    return
  }
  if (!loadDeviceId.value) {
    ElMessage.warning('未找到上大车设备,无法清空PLC')
    return
  }
  try {
    clearLoading.value = true
    const response = await deviceInteractionApi.executeOperation({
      deviceId: loadDeviceId.value,
      operation: 'clearGlass',
      params: {
        positionCode: form.positionCode || null
      }
    })
    if (response?.code !== 200) {
      throw new Error(response?.message || 'PLC清空失败')
    }
    const result = response?.data
    if (result?.success) {
      ElMessage.success(result?.message || 'PLC已清空')
      glassIdsInput.value = ''
    } else {
      throw new Error(result?.message || 'PLC清空失败')
    }
  } catch (error) {
    console.error('清空PLC失败:', error)
    ElMessage.error(error?.message || 'PLC清空失败')
  } finally {
    clearLoading.value = false
  }
}
</script>
 
<style scoped>
.task-orchestration {
  background: #fff;
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 8px 32px rgba(15, 18, 63, 0.08);
}
 
.panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
}
 
.panel-header h3 {
  margin: 0;
}
 
.panel-header p {
  margin: 4px 0 0;
  color: #909399;
  font-size: 13px;
}
 
.panel-header .warning {
  color: #f56c6c;
}
 
.panel-header .sub-info {
  margin-top: 4px;
  color: #606266;
  font-size: 12px;
}
 
.action-buttons {
  display: flex;
  gap: 12px;
  align-items: center;
}
</style>