You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

818 lines
29 KiB

4 months ago
  1. <template>
  2. <div class="mod-config">
  3. <el-row>
  4. <el-col :span="24">
  5. <el-button @click="getDataList()" type="primary">查询</el-button>
  6. <el-button @click="showAgvMonitor()" type="success">AGV监控</el-button>
  7. <el-button @click="testTuskConnection()" type="info">测试连接</el-button>
  8. <download-excel
  9. :fields="fields()"
  10. :data="exportData"
  11. type="xls"
  12. :name="exportName"
  13. :header="exportHeader"
  14. :footer="exportFooter"
  15. :defaultValue="exportDefaultValue"
  16. :fetch="createExportData"
  17. :before-generate="startDownload"
  18. :before-finish="finishDownload"
  19. worksheet="导出信息"
  20. class="el-button el-button--primary el-button--medium">
  21. 导出
  22. </download-excel>
  23. </el-col>
  24. </el-row>
  25. <!-- 查询条件 -->
  26. <el-row>
  27. <el-col :span="24">
  28. <el-form :inline="true" label-position="top">
  29. <el-form-item label="任务单号">
  30. <el-input style="width: 150px;" v-model="queryHeaderData.taskNo" placeholder="请输入任务单号"></el-input>
  31. </el-form-item>
  32. <el-form-item label="来源类型">
  33. <el-select v-model="queryHeaderData.sourceType" placeholder="请选择" style="width: 150px;">
  34. <el-option label="全部" value=""></el-option>
  35. <el-option label="领料" value="ISSUE"></el-option>
  36. <el-option label="入库" value="INBOUND"></el-option>
  37. <el-option label="出库" value="OUTBOUND"></el-option>
  38. <el-option label="移库" value="TRANSFER"></el-option>
  39. </el-select>
  40. </el-form-item>
  41. <el-form-item label="来源单号">
  42. <el-input style="width: 150px;" v-model="queryHeaderData.sourceBillNo" placeholder="请输入来源单号"></el-input>
  43. </el-form-item>
  44. <el-form-item label="物料编码">
  45. <el-input style="width: 150px;" v-model="queryHeaderData.partNo" placeholder="请输入物料编码"></el-input>
  46. </el-form-item>
  47. <el-form-item label="AGV编码">
  48. <el-input style="width: 150px;" v-model="queryHeaderData.agvCode" placeholder="请输入AGV编码"></el-input>
  49. </el-form-item>
  50. <el-form-item label="任务状态">
  51. <el-select v-model="queryHeaderData.status" placeholder="请选择" style="width: 150px;">
  52. <el-option label="全部" value=""></el-option>
  53. <el-option label="已创建" value="CREATED"></el-option>
  54. <el-option label="已下发WCS" value="SENT_TO_WCS"></el-option>
  55. <el-option label="执行中" value="EXECUTING"></el-option>
  56. <el-option label="失败" value="FAILED"></el-option>
  57. <el-option label="已取消" value="CANCELED"></el-option>
  58. </el-select>
  59. </el-form-item>
  60. </el-form>
  61. </el-col>
  62. </el-row>
  63. <!-- 数据表格 -->
  64. <el-row>
  65. <el-col :span="24">
  66. <el-table
  67. :height="height"
  68. :data="dataList"
  69. border
  70. v-loading="dataListLoading"
  71. style="width: 100%;"
  72. row-key="id"
  73. @expand-change="handleExpandChange">
  74. <!-- 展开行 -->
  75. <el-table-column type="expand">
  76. <template slot-scope="props">
  77. <div >
  78. <el-table
  79. :data="props.row.details"
  80. border
  81. style="width: 100%;"
  82. class="detail-table">
  83. <el-table-column prop="seqNo" label="序号" width="80" align="center"></el-table-column>
  84. <el-table-column prop="actionType" label="动作类型" width="120" align="center"></el-table-column>
  85. <el-table-column prop="fromLocation" label="起始位置" width="120" align="center"></el-table-column>
  86. <el-table-column prop="toLocation" label="目标位置" width="120" align="center"></el-table-column>
  87. <el-table-column prop="agvCode" label="AGV编码" width="120" align="center"></el-table-column>
  88. <el-table-column prop="status" label="状态" width="100" align="center">
  89. </el-table-column>
  90. <el-table-column prop="startTime" label="开始时间" width="160" align="center">
  91. <template slot-scope="scope">
  92. {{ scope.row.startTime | dateFormat }}
  93. </template>
  94. </el-table-column>
  95. <el-table-column prop="completeTime" label="完成时间" width="160" align="center">
  96. <template slot-scope="scope">
  97. {{ scope.row.completeTime | dateFormat }}
  98. </template>
  99. </el-table-column>
  100. <el-table-column prop="comment" label="备注" min-width="150"></el-table-column>
  101. <el-table-column prop="errorMsg" label="错误信息" min-width="200">
  102. <template slot-scope="scope">
  103. <span v-if="scope.row.errorMsg" style="color: red;">{{ scope.row.errorMsg }}</span>
  104. </template>
  105. </el-table-column>
  106. </el-table>
  107. </div>
  108. </template>
  109. </el-table-column>
  110. <el-table-column prop="taskNo" label="任务单号" width="180" align="center"></el-table-column>
  111. <el-table-column prop="sourceType" label="来源类型" width="100" align="center">
  112. <template slot-scope="scope">
  113. {{ getSourceTypeText(scope.row.sourceType) }}
  114. </template>
  115. </el-table-column>
  116. <el-table-column prop="sourceBillNo" label="来源单号" width="150" align="center"></el-table-column>
  117. <el-table-column prop="partNo" label="物料编码" width="150" align="center"></el-table-column>
  118. <el-table-column prop="qty" label="数量" width="100" align="center"></el-table-column>
  119. <el-table-column prop="batchNo" label="批次号" width="120" align="center"></el-table-column>
  120. <el-table-column prop="serialNo" label="标签号" width="150" align="center"></el-table-column>
  121. <el-table-column prop="fromLocation" label="起始库位" width="120" align="center"></el-table-column>
  122. <el-table-column prop="toLocation" label="目标库位" width="120" align="center"></el-table-column>
  123. <el-table-column prop="palletId" label="托盘ID" width="120" align="center"></el-table-column>
  124. <el-table-column prop="agvCode" label="AGV编码" width="120" align="center"></el-table-column>
  125. <el-table-column prop="priority" label="优先级" width="80" align="center"></el-table-column>
  126. <el-table-column prop="status" label="状态" width="120" align="center">
  127. <template slot-scope="scope">
  128. <el-tag :type="getStatusType(scope.row.status)">{{ getStatusText(scope.row.status) }}</el-tag>
  129. </template>
  130. </el-table-column>
  131. <el-table-column prop="wmsSendTime" label="WMS发送时间" width="160" align="center">
  132. <template slot-scope="scope">
  133. {{ scope.row.wmsSendTime | dateFormat }}
  134. </template>
  135. </el-table-column>
  136. <el-table-column prop="startTime" label="开始时间" width="160" align="center">
  137. <template slot-scope="scope">
  138. {{ scope.row.startTime | dateFormat }}
  139. </template>
  140. </el-table-column>
  141. <el-table-column prop="completeTime" label="完成时间" width="160" align="center">
  142. <template slot-scope="scope">
  143. {{ scope.row.completeTime | dateFormat }}
  144. </template>
  145. </el-table-column>
  146. <el-table-column prop="errorMsg" label="错误信息" min-width="200">
  147. <template slot-scope="scope">
  148. <span v-if="scope.row.errorMsg" style="color: red;">{{ scope.row.errorMsg }}</span>
  149. </template>
  150. </el-table-column>
  151. <el-table-column prop="createdTime" label="创建时间" width="160" align="center">
  152. <template slot-scope="scope">
  153. {{ scope.row.createdTime | dateFormat }}
  154. </template>
  155. </el-table-column>
  156. <!-- TUSK操作列 -->
  157. <el-table-column label="操作" width="150" align="center" fixed="right">
  158. <template slot-scope="scope">
  159. <a size="mini" type="primary" @click="adjustPriority(scope.row)"
  160. >调整优先级</a>
  161. <a size="mini" type="warning" @click="changeLocation(scope.row)"
  162. >变更位置</a>
  163. <a size="mini" type="danger" style="color: red" @click="cancelTuskTask(scope.row)"
  164. >取消</a>
  165. </template>
  166. </el-table-column>
  167. </el-table>
  168. <!-- 分页 -->
  169. <el-pagination
  170. @size-change="sizeChangeHandle"
  171. @current-change="currentChangeHandle"
  172. :current-page="queryHeaderData.page"
  173. :page-sizes="[10, 20, 50, 100]"
  174. :page-size="queryHeaderData.size"
  175. :total="queryHeaderData.totalCount"
  176. layout="total, sizes, prev, pager, next, jumper">
  177. </el-pagination>
  178. </el-col>
  179. </el-row>
  180. <!-- 调整优先级对话框 -->
  181. <el-dialog title="调整任务优先级" :visible.sync="priorityDialogVisible" width="240px" :close-on-click-modal="false">
  182. <el-form :model="priorityForm" label-width="100px" :inline="true" label-position="top">
  183. <el-form-item label="任务ID">
  184. <el-input v-model="priorityForm.taskId" disabled style="width: 100%"></el-input>
  185. </el-form-item>
  186. <el-form-item label="当前优先级">
  187. <el-input v-model="priorityForm.currentPriority" disabled style="width: 100%"></el-input>
  188. </el-form-item>
  189. <el-form-item label="新优先级" required>
  190. <el-input v-model="priorityForm.newPriority" style="width: 100%"></el-input>
  191. <div style="font-size: 12px; color: #999; margin-top: 5px;">优先级范围1-9数字越大优先级越高</div>
  192. </el-form-item>
  193. </el-form>
  194. <div slot="footer" class="dialog-footer" style="margin-top: 20px">
  195. <el-button @click="priorityDialogVisible = false">取消</el-button>
  196. <el-button type="primary" @click="confirmAdjustPriority" :loading="priorityLoading">确定</el-button>
  197. </div>
  198. </el-dialog>
  199. <!-- 变更位置对话框 -->
  200. <el-dialog title="变更任务位置" :visible.sync="locationDialogVisible" width="180px" :close-on-click-modal="false">
  201. <el-form :model="locationForm" label-width="100px" :inline="true" label-position="top">
  202. <el-form-item label="任务ID">
  203. <el-input v-model="locationForm.taskId" disabled></el-input>
  204. </el-form-item>
  205. <el-form-item label="当前起点">
  206. <el-input v-model="locationForm.currentFromLocation" disabled></el-input>
  207. </el-form-item>
  208. <el-form-item label="当前终点">
  209. <el-input v-model="locationForm.currentToLocation" disabled></el-input>
  210. </el-form-item>
  211. <el-form-item label="新终点" required>
  212. <el-input v-model="locationForm.newToLocation" placeholder="请输入新的终点位置"></el-input>
  213. </el-form-item>
  214. </el-form>
  215. <div slot="footer" class="dialog-footer">
  216. <el-button @click="locationDialogVisible = false">取消</el-button>
  217. <el-button type="primary" @click="confirmChangeLocation" :loading="locationLoading">确定</el-button>
  218. </div>
  219. </el-dialog>
  220. <!-- AGV监控对话框 -->
  221. <el-dialog title="AGV实时监控" :visible.sync="agvMonitorVisible" width="56%" :close-on-click-modal="false">
  222. <div>
  223. <!-- 状态统计 -->
  224. <el-row :gutter="20" style="margin-bottom: 20px;">
  225. <el-col :span="6">
  226. <el-card class="stat-card">
  227. <div class="stat-content">
  228. <span class="stat-number">{{ agvStats.online }}</span>
  229. <span class="stat-label">在线AGV</span>
  230. </div>
  231. </el-card>
  232. </el-col>
  233. <el-col :span="6">
  234. <el-card class="stat-card">
  235. <div class="stat-content">
  236. <span class="stat-number">{{ agvStats.running }}</span>
  237. <span class="stat-label">运行中</span>
  238. </div>
  239. </el-card>
  240. </el-col>
  241. <el-col :span="6">
  242. <el-card class="stat-card">
  243. <div class="stat-content">
  244. <span class="stat-number">{{ agvStats.idle }}</span>
  245. <span class="stat-label">空闲</span>
  246. </div>
  247. </el-card>
  248. </el-col>
  249. <el-col :span="6">
  250. <el-card class="stat-card error">
  251. <div class="stat-content">
  252. <span class="stat-number">{{ agvStats.error }}</span>
  253. <span class="stat-label">异常</span>
  254. </div>
  255. </el-card>
  256. </el-col>
  257. </el-row>
  258. <!-- AGV状态表格 -->
  259. <el-table :data="agvList" border style="width: 100%;" max-height="400">
  260. <el-table-column prop="id" label="AGV编号" width="100" align="center"></el-table-column>
  261. <el-table-column prop="soc" label="电量" width="120" align="center">
  262. <template slot-scope="scope">
  263. <el-progress :percentage="scope.row.soc" :color="getBatteryColor(scope.row.soc)"></el-progress>
  264. </template>
  265. </el-table-column>
  266. <el-table-column prop="agvStat" label="状态" width="120" align="center">
  267. <template slot-scope="scope">
  268. <el-tag :type="getAgvStatusType(scope.row.agvStat)">
  269. {{ getAgvStatusText(scope.row.agvStat) }}
  270. </el-tag>
  271. </template>
  272. </el-table-column>
  273. <el-table-column prop="cargo" label="载货状态" width="100" align="center">
  274. <template slot-scope="scope">
  275. <el-tag :type="scope.row.cargo ? 'success' : 'info'">
  276. {{ scope.row.cargo ? '有货' : '空载' }}
  277. </el-tag>
  278. </template>
  279. </el-table-column>
  280. <el-table-column prop="offline" label="连接状态" width="100" align="center">
  281. <template slot-scope="scope">
  282. <el-tag :type="scope.row.offline ? 'danger' : 'success'">
  283. {{ scope.row.offline ? '离线' : '在线' }}
  284. </el-tag>
  285. </template>
  286. </el-table-column>
  287. <el-table-column label="位置信息" width="200" align="center">
  288. <template slot-scope="scope">
  289. <div>X: {{ scope.row.x }}mm</div>
  290. <div>Y: {{ scope.row.y }}mm</div>
  291. <div>角度: {{ (scope.row.theta / 1000).toFixed(1) }}°</div>
  292. </template>
  293. </el-table-column>
  294. <el-table-column prop="offPlat" label="控制模式" width="100" align="center">
  295. <template slot-scope="scope">
  296. <el-tag :type="scope.row.offPlat ? 'warning' : 'primary'">
  297. {{ scope.row.offPlat ? '手动' : '自动' }}
  298. </el-tag>
  299. </template>
  300. </el-table-column>
  301. </el-table>
  302. </div>
  303. <div slot="footer" class="dialog-footer">
  304. <el-button @click="refreshAgvStatus" type="primary">刷新</el-button>
  305. <el-button @click="agvMonitorVisible = false">关闭</el-button>
  306. </div>
  307. </el-dialog>
  308. </div>
  309. </template>
  310. <script>
  311. import {
  312. getTransportTaskList,
  313. getTransportTaskDetails,
  314. adjustPriority as adjustPriorityApi,
  315. changeLocation as changeLocationApi,
  316. cancelTuskTask as cancelTuskTaskApi,
  317. getAgvStatus,
  318. testTuskConnection as testTuskConnectionApi
  319. } from '@/api/automatedWarehouse/agvTask.js'
  320. import {
  321. userFavoriteList,
  322. saveUserFavorite,
  323. removeUserFavorite,
  324. } from '@/api/userFavorite.js'
  325. export default {
  326. data() {
  327. return {
  328. queryHeaderData: {
  329. site: this.$store.state.user.site,
  330. taskNo: '',
  331. sourceType: '',
  332. sourceBillNo: '',
  333. partNo: '',
  334. agvCode: '',
  335. status: '',
  336. page: 1,
  337. size: 10,
  338. totalCount: 0
  339. },
  340. // table高度
  341. height: 450,
  342. // 是否收藏
  343. favorite: false,
  344. // 数据集
  345. dataList: [],
  346. dataListLoading: false,
  347. // 导出相关
  348. exportData: [],
  349. exportName: "AGV任务管理",
  350. exportHeader: ["AGV任务管理"],
  351. exportFooter: [],
  352. exportDefaultValue: "",
  353. // TUSK集成相关
  354. agvMonitorVisible: false,
  355. agvList: [],
  356. agvStats: {
  357. online: 0,
  358. running: 0,
  359. idle: 0,
  360. error: 0
  361. },
  362. // 调整优先级对话框
  363. priorityDialogVisible: false,
  364. priorityLoading: false,
  365. priorityForm: {
  366. taskId: '',
  367. currentPriority: '',
  368. newPriority: ''
  369. },
  370. // 变更位置对话框
  371. locationDialogVisible: false,
  372. locationLoading: false,
  373. locationForm: {
  374. taskId: '',
  375. currentFromLocation: '',
  376. currentToLocation: '',
  377. newFromLocation: '',
  378. newToLocation: ''
  379. }
  380. }
  381. },
  382. mounted() {
  383. this.$nextTick(() => {
  384. this.height = window.innerHeight - 220;
  385. })
  386. },
  387. activated() {
  388. this.getDataList()
  389. },
  390. filters: {
  391. dateFormat(value) {
  392. if (!value) return ''
  393. const date = new Date(value)
  394. return date.toLocaleString('zh-CN', {
  395. year: 'numeric',
  396. month: '2-digit',
  397. day: '2-digit',
  398. hour: '2-digit',
  399. minute: '2-digit',
  400. second: '2-digit'
  401. })
  402. }
  403. },
  404. methods: {
  405. // 获取数据列表
  406. getDataList() {
  407. this.dataListLoading = true
  408. getTransportTaskList(this.queryHeaderData).then(({data}) => {
  409. if (data && data.code === 0) {
  410. // 保存当前展开的行的明细数据
  411. const expandedDetails = {};
  412. this.dataList.forEach(item => {
  413. if (item.details) {
  414. expandedDetails[item.id] = item.details;
  415. }
  416. });
  417. // 更新数据列表
  418. this.dataList = data.page.list || [];
  419. // 恢复展开行的明细数据
  420. this.dataList.forEach(item => {
  421. if (expandedDetails[item.id]) {
  422. this.$set(item, 'details', expandedDetails[item.id]);
  423. }
  424. });
  425. this.queryHeaderData.page = data.page.currPage
  426. this.queryHeaderData.size = data.page.pageSize
  427. this.queryHeaderData.totalCount = data.page.totalCount
  428. }
  429. this.dataListLoading = false
  430. }).catch(error => {
  431. console.error('获取数据失败:', error)
  432. this.$message.error('获取数据失败')
  433. this.dataListLoading = false
  434. })
  435. },
  436. // 处理展开行变化
  437. handleExpandChange(row, expandedRows) {
  438. const isExpanded = expandedRows.some(r => r.id === row.id);
  439. if (isExpanded) {
  440. // 展开时加载明细数据
  441. if (!row.details || row.details.length === 0) {
  442. getTransportTaskDetails(row.taskNo).then(({data}) => {
  443. if (data && data.code === 0) {
  444. // 使用Vue.set确保响应式更新
  445. this.$set(row, 'details', data.details || [])
  446. // 强制更新表格
  447. this.$forceUpdate()
  448. }
  449. }).catch(error => {
  450. console.error('获取任务明细失败:', error)
  451. this.$message.error('获取任务明细失败')
  452. })
  453. }
  454. }
  455. },
  456. // 获取状态文本
  457. getStatusText(status) {
  458. const statusMap = {
  459. 'CREATED': '已创建',
  460. 'SENT_TO_WCS': '已下发WCS',
  461. 'EXECUTING': '执行中',
  462. 'COMPLETED': '已完成',
  463. 'FAILED': '失败',
  464. 'CANCELED': '已取消'
  465. }
  466. return statusMap[status] || status
  467. },
  468. // 获取状态标签类型
  469. getStatusType(status) {
  470. const typeMap = {
  471. 'CREATED': 'info',
  472. 'SENT_TO_WCS': 'warning',
  473. 'EXECUTING': 'primary',
  474. 'COMPLETED': 'success',
  475. 'FAILED': 'danger',
  476. 'CANCELED': 'info'
  477. }
  478. return typeMap[status] || 'info'
  479. },
  480. // 获取来源类型文本
  481. getSourceTypeText(sourceType) {
  482. const typeMap = {
  483. 'ISSUE': '领料',
  484. 'INBOUND': '入库',
  485. 'OUTBOUND': '出库',
  486. 'TRANSFER': '移库'
  487. }
  488. return typeMap[sourceType] || sourceType
  489. },
  490. // 每页数
  491. sizeChangeHandle(val) {
  492. this.queryHeaderData.size = val
  493. this.queryHeaderData.page = 1
  494. this.getDataList()
  495. },
  496. // 当前页
  497. currentChangeHandle(val) {
  498. this.queryHeaderData.page = val
  499. this.getDataList()
  500. },
  501. // 导出相关方法
  502. fields() {
  503. return {
  504. "任务单号": "taskNo",
  505. "来源类型": "sourceType",
  506. "来源单号": "sourceBillNo",
  507. "物料编码": "partNo",
  508. "数量": "qty",
  509. "批次号": "batchNo",
  510. "标签号": "serialNo",
  511. "起始库位": "fromLocation",
  512. "目标库位": "toLocation",
  513. "托盘ID": "palletId",
  514. "AGV编码": "agvCode",
  515. "优先级": "priority",
  516. "状态": "status",
  517. "创建时间": "createdTime"
  518. }
  519. },
  520. createExportData() {
  521. return this.dataList;
  522. },
  523. startDownload() {
  524. // 导出开始
  525. },
  526. finishDownload() {
  527. // 导出完成
  528. },
  529. // ==================== TUSK集成方法 ====================
  530. // 调整任务优先级
  531. adjustPriority(row) {
  532. this.priorityForm.taskId = row.taskNo;
  533. this.priorityForm.currentPriority = row.priority || '未设置';
  534. this.priorityForm.newPriority = row.priority || 5;
  535. this.priorityDialogVisible = true;
  536. },
  537. confirmAdjustPriority() {
  538. if (!this.priorityForm.newPriority || this.priorityForm.newPriority < 1 || this.priorityForm.newPriority > 9) {
  539. this.$message.error('请输入有效的优先级(1-9)');
  540. return;
  541. }
  542. this.priorityLoading = true;
  543. const params = {
  544. taskId: this.priorityForm.taskId,
  545. priority: this.priorityForm.newPriority
  546. };
  547. adjustPriorityApi(params).then(({data}) => {
  548. this.priorityLoading = false;
  549. if (data && data.code === 0) {
  550. this.$message.success('优先级调整成功');
  551. this.priorityDialogVisible = false;
  552. this.getDataList();
  553. } else {
  554. this.$message.error(data.msg || '优先级调整失败');
  555. }
  556. }).catch(() => {
  557. this.priorityLoading = false;
  558. this.$message.error('优先级调整异常');
  559. });
  560. },
  561. // 变更任务位置
  562. changeLocation(row) {
  563. this.locationForm.taskId = row.taskNo;
  564. this.locationForm.currentFromLocation = row.fromLocation || '未设置';
  565. this.locationForm.currentToLocation = row.toLocation || '未设置';
  566. this.locationForm.newFromLocation = row.fromLocation || '';
  567. this.locationForm.newToLocation = '';
  568. this.locationDialogVisible = true;
  569. },
  570. confirmChangeLocation() {
  571. if (!this.locationForm.newFromLocation || !this.locationForm.newToLocation) {
  572. this.$message.error('请输入新的起点和终点位置');
  573. return;
  574. }
  575. this.locationLoading = true;
  576. const params = {
  577. taskId: this.locationForm.taskId,
  578. fromLocation: this.locationForm.newFromLocation,
  579. toLocation: this.locationForm.newToLocation
  580. };
  581. changeLocationApi(params).then(({data}) => {
  582. this.locationLoading = false;
  583. if (data && data.code === 0) {
  584. this.$message.success('位置变更成功');
  585. this.locationDialogVisible = false;
  586. this.getDataList();
  587. } else {
  588. this.$message.error(data.msg || '位置变更失败');
  589. }
  590. }).catch(() => {
  591. this.locationLoading = false;
  592. this.$message.error('位置变更异常');
  593. });
  594. },
  595. // 取消TUSK任务
  596. cancelTuskTask(row) {
  597. this.$confirm('确定要取消此TUSK任务吗?取消后可能无法恢复!', '警告', {
  598. confirmButtonText: '确定',
  599. cancelButtonText: '取消',
  600. type: 'warning'
  601. }).then(() => {
  602. const params = { taskNo: row.taskNo };
  603. cancelTuskTaskApi(params).then(({data}) => {
  604. if (data.code === 0) {
  605. this.$message.success(data.msg || 'TUSK任务取消成功');
  606. this.getDataList(); // 刷新列表
  607. } else {
  608. this.$message.error(data.msg || 'TUSK任务取消失败');
  609. }
  610. }).catch(error => {
  611. this.$message.error('取消TUSK任务异常:' + error.message);
  612. });
  613. });
  614. },
  615. // 判断任务是否可以调整优先级
  616. canAdjustPriority(status) {
  617. // 可以调整优先级的状态:已创建、已下发、等待中、执行中
  618. return ['CREATED', 'SENT_TO_WCS', 'WAITING', 'IN_PROGRESS', 'EXECUTING'].includes(status);
  619. },
  620. // 判断任务是否可以取消
  621. canCancelTask(status) {
  622. return ['CREATED', 'SENT_TO_WCS', 'EXECUTING', 'WAITING', 'IN_PROGRESS'].includes(status);
  623. },
  624. // 判断任务是否可以变更位置
  625. canChangeLocation(status) {
  626. // 可以变更位置的状态:已创建、等待中(未开始执行的任务)
  627. return ['CREATED', 'WAITING'].includes(status);
  628. },
  629. // 显示AGV监控
  630. showAgvMonitor() {
  631. this.agvMonitorVisible = true;
  632. this.refreshAgvStatus();
  633. },
  634. // 刷新AGV状态
  635. refreshAgvStatus() {
  636. getAgvStatus().then(({data}) => {
  637. if (data.code === 0) {
  638. this.agvList = data.agvList || [];
  639. this.calculateAgvStats();
  640. } else {
  641. this.$message.error(data.msg || '获取AGV状态失败');
  642. }
  643. }).catch(error => {
  644. this.$message.error('获取AGV状态异常:' + error.message);
  645. });
  646. },
  647. // 计算AGV统计信息
  648. calculateAgvStats() {
  649. this.agvStats = {
  650. online: this.agvList.filter(agv => !agv.offline).length,
  651. running: this.agvList.filter(agv => agv.agvStat >= 1 && agv.agvStat <= 12).length,
  652. idle: this.agvList.filter(agv => agv.agvStat === 0).length,
  653. error: this.agvList.filter(agv => agv.agvStat >= 128).length
  654. };
  655. },
  656. // 获取AGV状态文本
  657. getAgvStatusText(status) {
  658. const statusMap = {
  659. 0: '空闲',
  660. 1: '运行中',
  661. 2: '直线运动中',
  662. 3: '旋转中',
  663. 13: '充电中',
  664. 23: '机器人暂停',
  665. 128: '异常状态',
  666. 129: '急停按钮触发',
  667. 130: '碰撞告警触发',
  668. 131: '告警'
  669. };
  670. return statusMap[status] || `未知状态(${status})`;
  671. },
  672. // 获取AGV状态标签类型
  673. getAgvStatusType(status) {
  674. if (status === 0) return 'info'; // 空闲
  675. if (status >= 1 && status <= 12) return 'primary'; // 运行中
  676. if (status === 13) return 'success'; // 充电中
  677. if (status >= 128) return 'danger'; // 异常状态
  678. return 'info';
  679. },
  680. // 获取电池颜色
  681. getBatteryColor(soc) {
  682. if (soc >= 60) return '#67c23a'; // 绿色
  683. if (soc >= 30) return '#e6a23c'; // 橙色
  684. return '#f56c6c'; // 红色
  685. },
  686. // 测试TUSK连接
  687. testTuskConnection() {
  688. this.$message.success('正在测试TUSK连接...');
  689. testTuskConnectionApi().then(({data}) => {
  690. if (data.code === 0) {
  691. this.$message.success(data.msg || 'TUSK连接正常');
  692. } else {
  693. this.$message.error(data.msg || 'TUSK连接失败');
  694. }
  695. }).catch(error => {
  696. this.$message.error('测试TUSK连接异常:' + error.message);
  697. });
  698. }
  699. },
  700. }
  701. </script>
  702. <style scoped>
  703. .sl-svg {
  704. overflow: hidden;
  705. float: right;
  706. cursor: pointer;
  707. }
  708. .el-table .el-table__expanded-cell {
  709. padding: 20px 50px;
  710. background-color: #f5f7fa;
  711. }
  712. .el-table .el-table__expanded-cell h4 {
  713. margin-bottom: 15px;
  714. color: #409EFF;
  715. font-weight: bold;
  716. }
  717. /* 明细表格表头样式 - 设置淡一些的颜色 */
  718. .detail-table >>> .el-table__header-wrapper .el-table__header {
  719. background-color: #fafbfc;
  720. }
  721. .detail-table >>> .el-table__header-wrapper th {
  722. background-color: #fafbfc !important;
  723. color: #909399;
  724. font-weight: normal;
  725. }
  726. /* AGV监控样式 */
  727. .stat-card {
  728. text-align: center;
  729. cursor: pointer;
  730. transition: all 0.3s;
  731. }
  732. .stat-card:hover {
  733. box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  734. }
  735. .stat-card.error {
  736. border-color: #f56c6c;
  737. }
  738. .stat-content {
  739. padding: 20px;
  740. }
  741. .stat-number {
  742. display: block;
  743. font-size: 32px;
  744. font-weight: bold;
  745. color: #409EFF;
  746. margin-bottom: 8px;
  747. }
  748. .stat-card.error .stat-number {
  749. color: #f56c6c;
  750. }
  751. .stat-label {
  752. display: block;
  753. font-size: 14px;
  754. color: #909399;
  755. }
  756. </style>