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.

1945 lines
63 KiB

3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
1 month ago
3 months ago
1 month ago
3 months ago
3 months ago
1 month ago
3 months ago
3 months ago
3 months ago
1 month ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
1 month ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
1 month ago
3 months ago
1 month ago
3 months ago
  1. <template>
  2. <div>
  3. <div class="pda-container">
  4. <!-- 头部栏 -->
  5. <div class="header-bar">
  6. <div class="header-left" @click="handleBack">
  7. <i class="el-icon-arrow-left"></i>
  8. <span>人工分拣组盘</span>
  9. </div>
  10. <div class="header-right" @click="$router.push({ path: '/' })">
  11. 首页
  12. </div>
  13. </div>
  14. <div class="table-body" style="max-height: 500px; overflow-y: auto;">
  15. <div class="main-content form-section">
  16. <!-- 第一行栈板扫描 -->
  17. <div class="input-group">
  18. <label class="input-label">栈板编码</label>
  19. <div style="display: flex; gap: 8px;">
  20. <el-input
  21. v-model="palletCode"
  22. placeholder="请扫描栈板编码"
  23. class="form-input"
  24. style="flex: 0.75;"
  25. clearable
  26. @keyup.enter.native="handlePalletScan"
  27. inputmode="none"
  28. autocomplete="off"
  29. autocorrect="off"
  30. spellcheck="false"
  31. ref="palletInput"
  32. />
  33. <button class="action-btn secondary" style="flex: 0.25; margin: 0;" @click="handleFinishSorting" :disabled="finishSortingLoading || detailList.length === 0">
  34. {{ finishSortingLoading ? '处理中...' : '结束分拣' }}
  35. </button>
  36. <!-- <button-->
  37. <!-- class="action-btn secondary"-->
  38. <!-- style="flex: 0.25; margin: 0;"-->
  39. <!-- @click="handleCallPallet"-->
  40. <!-- >-->
  41. <!-- Call栈板-->
  42. <!-- </button>-->
  43. </div>
  44. </div>
  45. <!-- 第二行栈板类型和自动分拣 (扫描栈板后显示) -->
  46. <div v-if="palletScanned" class="input-group">
  47. <div style="display: flex; gap: 8px; align-items: end;">
  48. <div style="flex: 0.65;">
  49. <label class="input-label">栈板类型</label>
  50. <el-select
  51. v-model="currentPalletType"
  52. placeholder="请选择栈板类型"
  53. style="width: 100%;"
  54. :disabled="palletTypeDisabled"
  55. @change="handlePalletTypeChange"
  56. >
  57. <el-option
  58. v-for="type in palletTypeOptions"
  59. :key="type.palletType"
  60. :label="`${type.palletType} - ${type.typeDesc}`"
  61. :value="type.palletType"
  62. />
  63. </el-select>
  64. </div>
  65. <div style="flex: 0.35;">
  66. <label class="input-label">自动分拣</label>
  67. <el-select
  68. v-model="currentAutoSort"
  69. placeholder="请选择"
  70. style="width: 100%;"
  71. :disabled="autoSortDisabled"
  72. @change="handleAutoSortChange"
  73. >
  74. <el-option label="是" value="Y"></el-option>
  75. <el-option label="否" value="N"></el-option>
  76. </el-select>
  77. </div>
  78. </div>
  79. </div>
  80. <!-- 第2.5是否强制整托出库 (扫描栈板后显示) - rqrq -->
  81. <div v-if="palletScanned" class="input-group">
  82. <el-checkbox
  83. v-model="forceFullPalletOut"
  84. :disabled="palletTypeDisabled"
  85. @change="handleForceFullPalletOutChange"
  86. style="width: 100%;">
  87. 是否强制整托出库
  88. </el-checkbox>
  89. </div>
  90. <!-- 第三行当前栈板站点显示 (扫描栈板后显示) - rqrq -->
  91. <div v-if="palletScanned" class="input-group">
  92. <label class="input-label">当前站点</label>
  93. <div style="display: flex; gap: 8px;">
  94. <el-input
  95. v-model="currentPalletStation"
  96. placeholder="栈板当前站点"
  97. class="form-input"
  98. readonly
  99. style="flex: 0.75;">
  100. </el-input>
  101. <button
  102. class="action-btn secondary"
  103. style="flex: 0.25; margin: 0; white-space: nowrap;"
  104. @click="refreshTable"
  105. >
  106. 刷新
  107. </button>
  108. </div>
  109. </div>
  110. <!-- 第四行扫进/扫出选择 (扫描栈板后显示) -->
  111. <div v-if="palletScanned" class="input-group">
  112. <div style="display: flex; gap: 8px; align-items: center;">
  113. <div style="flex: 0.75;">
  114. <el-radio-group v-model="operationType" style="display: flex;">
  115. <el-radio label="in" style="margin-right: 0px; white-space: nowrap;">扫进</el-radio>
  116. <el-radio label="out" style="white-space: nowrap;">扫回</el-radio>
  117. </el-radio-group>
  118. </div>
  119. <button
  120. class="action-btn secondary"
  121. style="flex: 0.25; margin: 0; white-space: nowrap;"
  122. :disabled="currentPalletType === ''"
  123. @click="showScanModal"
  124. >
  125. 扫描条码
  126. </button>
  127. </div>
  128. </div>
  129. </div>
  130. <!-- 栈板明细表格 (扫描栈板后显示) -->
  131. <div v-if="palletScanned" class="rma-list">
  132. <div class="list-title-row" style="display: flex; gap: 8px; align-items: center; padding: 0;">
  133. <div class="list-title" style="flex: 0.75; margin: 0;">
  134. <button class="action-btn primary" style="flex: 0.25; margin: 0;" @click="showDetailModal" :disabled="completeAssemblyLoading">
  135. {{ '浏览明细' }}
  136. </button>
  137. <laber style="margin-left: 5px;">条码数:{{detailList.length}}</laber>
  138. </div>
  139. <button class="action-btn primary" style="flex: 0.25; margin: 0;" @click="handleCompleteAssembly" :disabled="completeAssemblyLoading">
  140. {{ completeAssemblyLoading ? '处理中...' : '提交分拣' }}
  141. </button>
  142. </div>
  143. <div class="detail-table">
  144. <div class="table-header">
  145. <div class="col-position">位置</div>
  146. <div class="col-layer">层数</div>
  147. <div class="col-serial">标签号</div>
  148. </div>
  149. <div
  150. v-for="(detail, index) in detailList"
  151. :key="index"
  152. class="table-row"
  153. @click="handleRowDblClick(detail, index)"
  154. >
  155. <div class="col-position">{{ detail.position }}</div>
  156. <div class="col-layer">{{ detail.layer }}</div>
  157. <div class="col-serial">{{ detail.serialNo }}</div>
  158. </div>
  159. <!-- 暂无数据提示 -->
  160. <div v-if="detailList.length === 0" class="table-row empty-row">
  161. <div class="empty-hint">暂无栈板明细数据</div>
  162. </div>
  163. </div>
  164. </div>
  165. </div>
  166. </div>
  167. <el-dialog
  168. :title="'已扫描数量:'+detailList.length"
  169. :visible.sync="detailModalVisible"
  170. width="90%"
  171. :close-on-click-modal="false"
  172. :show-close="false"
  173. :modal="true"
  174. :modal-append-to-body="true"
  175. :append-to-body="true"
  176. >
  177. <div class="table-body" style="max-height: 240px; overflow-y: auto;">
  178. <div class="detail-table">
  179. <div class="table-header">
  180. <div class="col-position">位置</div>
  181. <div class="col-layer">层数</div>
  182. <div class="col-serial">标签号</div>
  183. </div>
  184. <div
  185. v-for="(detail, index) in detailList"
  186. :key="index"
  187. class="table-row"
  188. @click="handleRowDblClick(detail, index)"
  189. >
  190. <div class="col-position">{{ detail.position }}</div>
  191. <div class="col-layer">{{ detail.layer }}</div>
  192. <div class="col-serial">{{ detail.serialNo }}</div>
  193. </div>
  194. <!-- 暂无数据提示 -->
  195. <div v-if="detailList.length === 0" class="table-row empty-row">
  196. <div class="empty-hint">暂无栈板明细数据</div>
  197. </div>
  198. </div>
  199. </div>
  200. <div slot="footer" class="dialog-footer">
  201. <button class="action-btn secondary" style="margin-left: 10px;" @click="detailModalVisible=false">取消</button>
  202. </div>
  203. </el-dialog>
  204. <!-- 扫码模态框 - rqrq -->
  205. <el-dialog
  206. title="扫描标签"
  207. :visible.sync="scanModalVisible"
  208. width="90%"
  209. :close-on-click-modal="false"
  210. :show-close="false"
  211. :modal="true"
  212. :modal-append-to-body="true"
  213. :append-to-body="true"
  214. >
  215. <div class="scan-modal-content">
  216. <!-- 扫进时显示层数选择和位置网格 - rqrq -->
  217. <div v-if="operationType === 'in'" class="modal-form">
  218. <!-- 层数选择混装托盘不显示 - rqrq -->
  219. <div v-if="!currentMixedMode && scanLayerOptions.length > 0" class="input-group">
  220. <label class="input-label">层数当前选择{{ scanLayer }}</label>
  221. <div class="layer-grid">
  222. <div
  223. v-for="layer in scanLayerOptions"
  224. :key="layer"
  225. class="layer-item"
  226. :class="{
  227. 'layer-selected': scanLayer === layer
  228. }"
  229. @click="handleLayerClick(layer)"
  230. >
  231. {{ layer }}
  232. </div>
  233. </div>
  234. </div>
  235. <!-- 位置网格选择器 - rqrq -->
  236. <div class="input-group">
  237. <label class="input-label">
  238. 位置{{ currentSelectedPosition ? `(当前选择:${currentSelectedPosition}` : '' }}
  239. <span v-if="positionGridLoading" style="color: #909399; font-size: 12px;">加载中...</span>
  240. </label>
  241. <div class="position-grid" :class="[
  242. {'position-grid-loading': positionGridLoading},
  243. getPositionGridClass
  244. ]">
  245. <div
  246. v-for="(item, index) in displayPositionGrid"
  247. :key="index"
  248. class="position-item"
  249. :class="{
  250. 'position-disabled': positionGridLoading || !availablePositions.includes(item.position),
  251. 'position-selected': currentSelectedPosition === item.position,
  252. 'position-loading': positionGridLoading,
  253. 'position-placeholder': item.isPlaceholder
  254. }"
  255. @click="handlePositionClick(item.position)"
  256. >
  257. <template v-if="!item.isPlaceholder">{{ item.position }}({{ item.count }})</template>
  258. </div>
  259. </div>
  260. </div>
  261. </div>
  262. <!-- 标签扫描 - rqrq -->
  263. <div class="input-group">
  264. <label class="input-label">标签二维码</label>
  265. <el-input
  266. v-model="scanCode"
  267. placeholder="请扫描标签二维码"
  268. class="form-input"
  269. clearable
  270. :disabled="operationType === 'in' && !currentSelectedPosition"
  271. @keyup.enter.native="handleLabelScan"
  272. inputmode="none"
  273. autocomplete="off"
  274. autocorrect="off"
  275. spellcheck="false"
  276. ref="scanInput"
  277. />
  278. </div>
  279. </div>
  280. <div slot="footer" class="dialog-footer">
  281. <button class="action-btn secondary" style="margin-left: 10px;" @click="closeScanModal">取消</button>
  282. </div>
  283. </el-dialog>
  284. <!-- 修改位置模态框 -->
  285. <el-dialog
  286. title="修改标签位置"
  287. :visible.sync="editPositionModalVisible"
  288. width="90%"
  289. :close-on-click-modal="false"
  290. :show-close="false"
  291. :modal="true"
  292. :modal-append-to-body="true"
  293. :append-to-body="true"
  294. >
  295. <div class="edit-modal-content">
  296. <!-- 标签号只读 -->
  297. <div class="input-group">
  298. <label class="input-label">标签号</label>
  299. <el-input
  300. v-model="editSerialNo"
  301. placeholder="标签号"
  302. class="form-input"
  303. readonly
  304. />
  305. </div>
  306. <!-- 位置选择 -->
  307. <div class="input-group">
  308. <label class="input-label">位置</label>
  309. <el-select
  310. v-model="editPosition"
  311. placeholder="请选择位置"
  312. style="width: 100%;"
  313. @change="handleEditPositionChange"
  314. >
  315. <el-option
  316. v-for="position in positionOptions"
  317. :key="position"
  318. :label="position"
  319. :value="position"
  320. />
  321. </el-select>
  322. </div>
  323. <!-- 层数选择 -->
  324. <div class="input-group">
  325. <label class="input-label">层数</label>
  326. <el-select
  327. v-model="editLayer"
  328. placeholder="请选择层数"
  329. style="width: 100%;"
  330. >
  331. <el-option
  332. v-for="layer in editLayerOptions"
  333. :key="layer"
  334. :label="`第${layer}层`"
  335. :value="layer"
  336. />
  337. </el-select>
  338. </div>
  339. </div>
  340. <div slot="footer" class="dialog-footer">
  341. <button class="action-btn primary" @click="confirmEditPosition" :disabled="editPositionLoading">
  342. {{ editPositionLoading ? '处理中...' : '确定' }}
  343. </button>
  344. <button class="action-btn secondary" style="margin-left: 10px;" @click="closeEditPositionModal" :disabled="editPositionLoading">取消</button>
  345. </div>
  346. </el-dialog>
  347. <!-- Call栈板模态框 -->
  348. <el-dialog
  349. title="调用空托盘"
  350. :visible.sync="callPalletModalVisible"
  351. width="90%"
  352. :close-on-click-modal="false"
  353. :show-close="false"
  354. :modal="true"
  355. :modal-append-to-body="true"
  356. :append-to-body="true"
  357. >
  358. <div class="call-modal-content">
  359. <!-- 起始站点选择 -->
  360. <div class="input-group">
  361. <label class="input-label">起始站点</label>
  362. <el-select
  363. v-model="selectedCallStartStation"
  364. placeholder="请选择起始站点"
  365. style="width: 100%;"
  366. @change="handleCallStartStationChange"
  367. >
  368. <el-option
  369. v-for="station in callStartStationOptions"
  370. :key="station.stationCode"
  371. :label="`${station.stationCode} - ${station.stationName}`"
  372. :value="station.stationCode"
  373. />
  374. </el-select>
  375. </div>
  376. <!-- 目标站点输入 -->
  377. <div class="input-group">
  378. <label class="input-label">目标站点</label>
  379. <el-input
  380. ref="callTargetStationInput"
  381. v-model="selectedCallTargetStation"
  382. placeholder="请扫描或输入目标站点"
  383. clearable
  384. style="width: 100%;"
  385. />
  386. </div>
  387. </div>
  388. <div slot="footer" class="dialog-footer">
  389. <button class="action-btn primary" @click="confirmCallPallet" :disabled="callPalletLoading">
  390. {{ callPalletLoading ? '调用中...' : '确定' }}
  391. </button>
  392. <button class="action-btn secondary" style="margin-left: 10px;" @click="closeCallPalletModal" :disabled="callPalletLoading">取消</button>
  393. </div>
  394. </el-dialog>
  395. </div>
  396. </template>
  397. <script>
  398. import {
  399. checkPalletExists,
  400. getPalletDetails,
  401. getLayersByPosition,
  402. savePalletDetail,
  403. restorePalletDetail,
  404. getLabelInfo,
  405. getLayersForEdit,
  406. updatePalletDetailPosition,
  407. getPalletInfo,
  408. getPalletTypeList,
  409. getPalletTypeAreas,
  410. updatePalletTypeAndAutoSort,
  411. getAgvStations,
  412. getAvailableAgvStations,
  413. callPalletToStation,
  414. completePalletAssemblyForFenJian,
  415. getAvailablePositionsForLayer,
  416. finishSorting,
  417. getSortingList
  418. } from '../../../api/automatedWarehouse/palletPacking'
  419. export default {
  420. data() {
  421. return {
  422. site: localStorage.getItem('site'),
  423. palletCode: '',
  424. palletScanned: false,
  425. operationType: 'in', // 'in' 或 'out'
  426. detailModalVisible:false,
  427. // 栈板类型和自动分拣
  428. currentPalletFamily: '', // 当前栈板大分类(固定不可改)
  429. currentPalletType: '', // 当前栈板类型
  430. currentAutoSort: '', // 当前是否自动分拣 Y/N
  431. forceFullPalletOut: false, // 是否强制整托出库 - rqrq
  432. palletTypeOptions: [], // 托盘类型选项列表
  433. palletTypeDisabled: false, // 栈板类型下拉框是否禁用(有明细数据时禁用)
  434. autoSortDisabled: false, // 自动分拣下拉框是否禁用
  435. currentWcsAutoSort: '', // 当前托盘类型的wcsAutoSort值
  436. currentMaxLayer: 0, // 当前托盘类型的最大层数,0=无限
  437. // 筛选条件
  438. selectedPosition: '',
  439. selectedLayer: '',
  440. positionOptions: [],
  441. layerOptions: [],
  442. currentPalletStation: '', // 当前栈板站点 - rqrq
  443. // 扫码模态框 - rqrq
  444. scanModalVisible: false,
  445. scanCode: '',
  446. scanPosition: '',
  447. scanLayer: 1,
  448. scanLayerOptions: [],
  449. needRefreshOnClose: false, // 标记是否需要在关闭模态框时刷新
  450. // 位置网格数据 - rqrq
  451. positionGrid: [], // 位置网格列表(用于显示4宫格或9宫格),格式: [{position: '1', count: 2}, ...]
  452. availablePositions: [], // 可用位置列表
  453. currentSelectedPosition: '', // 当前选中的位置
  454. currentMixedMode: false, // 是否混装模式
  455. positionGridLoading: false, // 位置网格加载中
  456. // 栈板明细
  457. detailList: [],
  458. // 分拣原始数据缓存(用于扫回恢复)- rqrq
  459. // key: serialNo, value: {palletId, position, layer, wcsFlag, partNo, createDate, createBy}
  460. originalDetailCache: {},
  461. // Call栈板模态框
  462. callPalletModalVisible: false,
  463. callStartStationOptions: [], // 起始站点选项(statusDb=1,有货)
  464. callTargetStationOptions: [], // 目标站点选项(statusDb=0,空闲)
  465. selectedCallStartStation: '',
  466. selectedCallTargetStation: '',
  467. // 修改位置模态框
  468. editPositionModalVisible: false,
  469. editSerialNo: '',
  470. editPosition: '',
  471. editLayer: '',
  472. editLayerOptions: [],
  473. editOriginalPosition: '',
  474. editOriginalLayer: '',
  475. // 按钮loading状态(防止重复点击)
  476. editPositionLoading: false, // 修改位置按钮
  477. callPalletLoading: false, // Call栈板按钮
  478. completeAssemblyLoading: false, // 完成组托按钮
  479. finishSortingLoading: false, // 结束分拣按钮 - rqrq
  480. //需要分拣的清单
  481. sortingList: [],
  482. };
  483. },
  484. computed: {
  485. // 获取位置网格的CSS类 - rqrq
  486. getPositionGridClass() {
  487. const len = this.positionGrid.length;
  488. if (len === 1) return 'position-grid-1';
  489. if (len === 2) return 'position-grid-4'; // 2宫格用4宫格布局
  490. if (len === 4) return 'position-grid-4';
  491. return 'position-grid-9';
  492. },
  493. // 生成显示用的位置网格(处理2宫格斜对角显示)- rqrq
  494. displayPositionGrid() {
  495. const len = this.positionGrid.length;
  496. // 2宫格特殊处理:用4宫格布局,1在左上(索引0),2在右下(索引3) - rqrq
  497. if (len === 2) {
  498. const pos1 = this.positionGrid.find(p => p.position === '1') || { position: '1', count: 0 };
  499. const pos2 = this.positionGrid.find(p => p.position === '2') || { position: '2', count: 0 };
  500. return [
  501. { ...pos1, isPlaceholder: false }, // 左上 - 位置1
  502. { position: '', count: 0, isPlaceholder: true }, // 右上 - 占位
  503. { position: '', count: 0, isPlaceholder: true }, // 左下 - 占位
  504. { ...pos2, isPlaceholder: false } // 右下 - 位置2
  505. ];
  506. }
  507. // 1宫格、4宫格、9宫格直接返回
  508. return this.positionGrid.map(p => ({ ...p, isPlaceholder: false }));
  509. }
  510. },
  511. methods: {
  512. // 返回上一页 - rqrq
  513. handleBack() {
  514. // 清空分拣原始数据缓存 - rqrq
  515. this.originalDetailCache = {};
  516. console.log('已清空分拣原始数据缓存 - rqrq');
  517. this.$router.back();
  518. },
  519. // 重置页面到初始状态 - rqrq
  520. resetPage() {
  521. // 清空栈板信息
  522. this.palletCode = '';
  523. this.palletScanned = false;
  524. this.operationType = 'in';
  525. // 清空栈板类型和自动分拣
  526. this.currentPalletFamily = '';
  527. this.currentPalletType = '';
  528. this.currentAutoSort = '';
  529. this.forceFullPalletOut = false; // 清空强制整托出库 - rqrq
  530. this.palletTypeOptions = [];
  531. this.palletTypeDisabled = false;
  532. this.autoSortDisabled = false;
  533. this.currentWcsAutoSort = '';
  534. this.currentMaxLayer = 0;
  535. // 清空筛选条件
  536. this.selectedPosition = '';
  537. this.selectedLayer = '';
  538. this.positionOptions = [];
  539. this.layerOptions = [];
  540. this.currentPalletStation = ''; // 清空当前站点 - rqrq
  541. // 清空栈板明细
  542. this.detailList = [];
  543. // 清空分拣原始数据缓存 - rqrq
  544. this.originalDetailCache = {};
  545. console.log('已清空分拣原始数据缓存 - rqrq');
  546. // 清空扫码模态框
  547. this.scanModalVisible = false;
  548. this.scanCode = '';
  549. this.scanPosition = '';
  550. this.scanLayer = '';
  551. this.scanLayerOptions = [];
  552. this.needRefreshOnClose = false;
  553. // 清空修改位置模态框
  554. this.editPositionModalVisible = false;
  555. this.editSerialNo = '';
  556. this.editPosition = '';
  557. this.editLayer = '';
  558. this.editLayerOptions = [];
  559. this.editOriginalPosition = '';
  560. this.editOriginalLayer = '';
  561. // 聚焦到栈板输入框
  562. this.$nextTick(() => {
  563. if (this.$refs.palletInput) {
  564. this.$refs.palletInput.focus();
  565. }
  566. });
  567. console.log('页面已重置到初始状态');
  568. },
  569. // 扫描栈板 - rqrq
  570. handlePalletScan() {
  571. if (!this.palletCode.trim()) {
  572. this.$alert('请输入栈板编码', '错误', { confirmButtonText: '确定' });
  573. return;
  574. }
  575. checkPalletExists({
  576. site: this.site,
  577. palletId: this.palletCode
  578. }).then(({ data }) => {
  579. if (data.code === 0) {
  580. this.palletCode=data.palletId
  581. let palletId=data.palletId
  582. console.log(this.palletCode)
  583. console.log(palletId)
  584. this.$nextTick(()=>{
  585. this.palletScanned = true;
  586. this.positionOptions = data.positions || [];
  587. // 扫描新栈板时清空sortingList - rqrq
  588. this.sortingList = [];
  589. console.log('扫描新栈板,已清空sortingList - rqrq');
  590. // 获取栈板详细信息(包括palletType和autoSort)
  591. this.loadPalletInfo(palletId);
  592. this.refreshTable();
  593. })
  594. } else {
  595. // 失败:弹出提示框,截取前100字符 - rqrq
  596. let errorMsg = data.msg || '栈板不存在';
  597. if (errorMsg.length > 100) {
  598. errorMsg = errorMsg.substring(0, 100) + '...';
  599. }
  600. this.$alert(errorMsg, '错误', {
  601. confirmButtonText: '确定',
  602. callback: () => {
  603. this.palletCode = '';
  604. this.$nextTick(() => {
  605. if (this.$refs.palletInput) {
  606. this.$refs.palletInput.focus();
  607. }
  608. });
  609. }
  610. });
  611. }
  612. }).catch(error => {
  613. console.error('验证栈板失败:', error);
  614. // 网络错误:弹出提示框 - rqrq
  615. let errorMsg = error.message || '验证栈板失败';
  616. if (errorMsg.length > 100) {
  617. errorMsg = errorMsg.substring(0, 100) + '...';
  618. }
  619. this.$alert(errorMsg, '错误', {
  620. confirmButtonText: '确定',
  621. callback: () => {
  622. this.palletCode = '';
  623. this.$nextTick(() => {
  624. if (this.$refs.palletInput) {
  625. this.$refs.palletInput.focus();
  626. }
  627. });
  628. }
  629. });
  630. });
  631. },
  632. // 加载栈板信息
  633. loadPalletInfo(palletId) {
  634. getPalletInfo({
  635. site: this.site,
  636. palletId: palletId
  637. }).then(({ data }) => {
  638. if (data.code === 0) {
  639. const palletInfo = data.row || {};
  640. this.currentPalletFamily = palletInfo.palletFamily || '';
  641. this.currentPalletType = palletInfo.palletType || '';
  642. this.currentAutoSort = palletInfo.autoSort || 'N';
  643. this.currentPalletStation = palletInfo.locationCode || ''; // 获取当前站点 - rqrq
  644. // 分拣界面:校验栈板站点必须是R1/R2/R3/R4 - rqrq
  645. const allowedStations = ['R1', 'R2', 'R3', 'R4'];
  646. if (!allowedStations.includes(this.currentPalletStation)) {
  647. this.$alert(
  648. `分拣功能只能在R1/R2/R3/R4站点使用,当前栈板站点为:${this.currentPalletStation || '空'}`,
  649. '站点错误',
  650. {
  651. confirmButtonText: '确定',
  652. callback: () => {
  653. this.resetPage();
  654. this.$nextTick(() => {
  655. if (this.$refs.palletInput) {
  656. this.$refs.palletInput.focus();
  657. }
  658. });
  659. }
  660. }
  661. );
  662. return;
  663. }
  664. // 第一次进页面扫描栈板查询的时候,如果sore_Type=3,那么也要显示勾选 - rqrq
  665. const soreType = palletInfo.soreType;
  666. if (soreType === 3) {
  667. this.forceFullPalletOut = true;
  668. console.log('soreType=3,自动勾选强制整托出库 - rqrq');
  669. } else {
  670. this.forceFullPalletOut = false;
  671. }
  672. // 加载托盘类型列表(根据palletFamily过滤)
  673. this.loadPalletTypeList();
  674. } else {
  675. this.$alert(data.msg || '获取栈板信息失败', '错误', { confirmButtonText: '确定' });
  676. }
  677. }).catch(error => {
  678. console.error('获取栈板信息失败:', error);
  679. this.$alert('获取栈板信息失败', '错误', { confirmButtonText: '确定' });
  680. });
  681. },
  682. // 加载托盘类型列表
  683. loadPalletTypeList() {
  684. getPalletTypeList({
  685. site: this.site,
  686. palletFamily: this.currentPalletFamily,
  687. active: 'Y'
  688. }).then(({ data }) => {
  689. if (data.code === 0) {
  690. this.palletTypeOptions = data.rows || [];
  691. // 设置当前托盘类型的wcsAutoSort值和maxLayer
  692. const currentType = this.palletTypeOptions.find(t => t.palletType === this.currentPalletType);
  693. if (currentType) {
  694. this.currentWcsAutoSort = currentType.wcsAutoSort || 'N';
  695. this.currentMaxLayer = currentType.maxLayer || 0;
  696. this.updateAutoSortControl();
  697. // 加载当前托盘类型的区域列表
  698. this.loadPalletTypeAreas();
  699. }
  700. } else {
  701. this.palletTypeOptions = [];
  702. }
  703. }).catch(error => {
  704. console.error('获取托盘类型列表失败:', error);
  705. this.palletTypeOptions = [];
  706. });
  707. },
  708. // 加载托盘类型的区域列表
  709. loadPalletTypeAreas() {
  710. getPalletTypeAreas({
  711. site: this.site,
  712. palletType: this.currentPalletType
  713. }).then(({ data }) => {
  714. if (data.code === 0) {
  715. // 从pallet_type_area获取position列表
  716. const areas = data.rows || [];
  717. this.positionOptions = areas.map(area => area.position);
  718. } else {
  719. this.positionOptions = [];
  720. }
  721. }).catch(error => {
  722. console.error('获取托盘区域列表失败:', error);
  723. this.positionOptions = [];
  724. });
  725. },
  726. // 托盘类型变更事件
  727. handlePalletTypeChange() {
  728. // 查找选中的托盘类型
  729. const selectedType = this.palletTypeOptions.find(t => t.palletType === this.currentPalletType);
  730. if (selectedType) {
  731. this.currentWcsAutoSort = selectedType.wcsAutoSort || 'N';
  732. this.currentMaxLayer = selectedType.maxLayer || 0;
  733. // 默认值:从托盘类型表取
  734. this.currentAutoSort = this.currentWcsAutoSort;
  735. // 如果栈板类型选择了A0103(手工选择后触发),那么自动勾选强制整托出库 - rqrq
  736. if (this.currentPalletType === 'A0103') {
  737. this.forceFullPalletOut = true;
  738. console.log('栈板类型选择了A0103,自动勾选强制整托出库 - rqrq');
  739. }
  740. // 更新自动分拣控制
  741. this.updateAutoSortControl();
  742. // 重新查询pallet_type_area,更新位置下拉框
  743. this.loadPalletTypeAreas();
  744. // 保存到数据库
  745. this.savePalletTypeAndAutoSort();
  746. }
  747. },
  748. // 更新自动分拣控制逻辑
  749. updateAutoSortControl() {
  750. if (this.currentWcsAutoSort === 'N') {
  751. // 不支持自动分拣,锁定为N,禁用选择
  752. this.currentAutoSort = 'N';
  753. this.autoSortDisabled = true;
  754. } else {
  755. // 支持自动分拣,可以选择Y或N
  756. this.autoSortDisabled = false;
  757. }
  758. },
  759. // 是否自动分拣变更事件
  760. handleAutoSortChange() {
  761. // 保存到数据库
  762. this.savePalletTypeAndAutoSort();
  763. },
  764. // 是否强制整托出库变更事件 - rqrq
  765. handleForceFullPalletOutChange() {
  766. console.log('是否强制整托出库变更 - rqrq,forceFullPalletOut=' + this.forceFullPalletOut);
  767. // 保存到数据库
  768. this.savePalletTypeAndAutoSort();
  769. },
  770. // 保存栈板类型、自动分拣标志和存储类型 - rqrq
  771. savePalletTypeAndAutoSort() {
  772. updatePalletTypeAndAutoSort({
  773. site: this.site,
  774. palletId: this.palletCode,
  775. palletType: this.currentPalletType,
  776. autoSort: this.currentAutoSort,
  777. forceFullPalletOut: this.forceFullPalletOut // 是否强制整托出库 - rqrq
  778. }).then(({ data }) => {
  779. if (data.code === 0) {
  780. this.$message.success('更新成功');
  781. } else {
  782. this.$alert(data.msg || '更新失败', '错误', { confirmButtonText: '确定' });
  783. }
  784. }).catch(error => {
  785. console.error('更新失败:', error);
  786. this.$alert('更新失败', '错误', { confirmButtonText: '确定' });
  787. });
  788. },
  789. // Call栈板 - 调用空托盘
  790. handleCallPallet() {
  791. this.callPalletModalVisible = true;
  792. this.selectedCallStartStation = '';
  793. this.selectedCallTargetStation = '';
  794. // 获取起始站点(有货的正式站点)
  795. getAvailableAgvStations({ statusDb: 1 }).then(({ data }) => {
  796. if (data && data.code === 0) {
  797. this.callStartStationOptions = data.stations || [];
  798. console.log('起始站点列表:', this.callStartStationOptions);
  799. } else {
  800. this.$alert(data.msg || '获取起始站点列表失败', '错误', { confirmButtonText: '确定' });
  801. }
  802. }).catch(error => {
  803. console.error('获取起始站点列表失败:', error);
  804. this.$alert('获取起始站点列表失败', '错误', { confirmButtonText: '确定' });
  805. });
  806. // 获取目标站点(空闲的正式站点)
  807. getAvailableAgvStations({ statusDb: 0 }).then(({ data }) => {
  808. if (data && data.code === 0) {
  809. this.callTargetStationOptions = data.stations || [];
  810. } else {
  811. this.$alert(data.msg || '获取目标站点列表失败', '错误', { confirmButtonText: '确定' });
  812. }
  813. }).catch(error => {
  814. console.error('获取目标站点列表失败:', error);
  815. this.$alert('获取目标站点列表失败', '错误', { confirmButtonText: '确定' });
  816. });
  817. },
  818. // 起始站点选择change事件,自动聚焦到目标站点输入框
  819. handleCallStartStationChange() {
  820. this.$nextTick(() => {
  821. if (this.$refs.callTargetStationInput) {
  822. this.$refs.callTargetStationInput.focus();
  823. }
  824. });
  825. },
  826. // 位置选择变化
  827. handlePositionChange() {
  828. if (this.selectedPosition) {
  829. getLayersByPosition({
  830. site: this.site,
  831. palletId: this.palletCode,
  832. position: this.selectedPosition
  833. }).then(({ data }) => {
  834. if (data.code === 0) {
  835. this.layerOptions = data.layers || [];
  836. }
  837. }).catch(error => {
  838. console.error('获取层数失败:', error);
  839. });
  840. } else {
  841. this.layerOptions = [];
  842. }
  843. this.selectedLayer = '';
  844. },
  845. // 刷新表格
  846. refreshTable() {
  847. getPalletDetails({
  848. site: this.site,
  849. palletId: this.palletCode,
  850. position: this.selectedPosition,
  851. layer: this.selectedLayer
  852. }).then(({ data }) => {
  853. if (data.code === 0) {
  854. this.detailList = data.details || [];
  855. // 如果栈板有明细数据,禁用栈板类型和自动分拣的修改
  856. const hasDetails = this.detailList.length > 0;
  857. this.palletTypeDisabled = hasDetails;
  858. // 如果有明细数据,自动分拣也要禁用;否则根据wcsAutoSort判断
  859. if (hasDetails) {
  860. this.autoSortDisabled = true;
  861. } else {
  862. this.updateAutoSortControl();
  863. }
  864. } else {
  865. this.detailList = [];
  866. this.palletTypeDisabled = false;
  867. this.updateAutoSortControl();
  868. }
  869. }).catch(error => {
  870. console.error('获取栈板明细失败:', error);
  871. this.detailList = [];
  872. this.palletTypeDisabled = false;
  873. this.updateAutoSortControl();
  874. });
  875. },
  876. // 显示扫码模态框 - rqrq
  877. showScanModal() {
  878. this.scanModalVisible = true;
  879. this.scanCode = '';
  880. this.currentSelectedPosition = '';
  881. this.needRefreshOnClose = false;
  882. // 初始化层数和位置网格 - rqrq
  883. if (this.operationType === 'in') {
  884. this.scanLayer = 1;
  885. // 先查询当前栈板已有的最大层数,动态生成层数选项 - rqrq
  886. this.loadLayerOptions();
  887. } else {
  888. // 扫出操作,直接聚焦到输入框 - rqrq
  889. this.$nextTick(() => {
  890. if (this.$refs.scanInput) {
  891. this.$refs.scanInput.focus();
  892. }
  893. });
  894. }
  895. },
  896. // 加载层数选项 - rqrq
  897. loadLayerOptions() {
  898. console.log('开始加载层数选项 - rqrq,maxLayer=' + this.currentMaxLayer);
  899. getPalletDetails({
  900. site: this.site,
  901. palletId: this.palletCode,
  902. position: '',
  903. layer: null
  904. }).then(({ data }) => {
  905. if (data && data.code === 0) {
  906. const details = data.details || [];
  907. // 获取当前已有的最大层数 - rqrq
  908. let maxExistingLayer = 0;
  909. if (details.length > 0) {
  910. maxExistingLayer = Math.max(...details.map(d => d.layer || 0));
  911. }
  912. // 根据maxLayer判断 - rqrq
  913. if (this.currentMaxLayer === 0) {
  914. // maxLayer=0:不限高,可选层数为已有最大层+1层 - rqrq
  915. this.currentMixedMode = false;
  916. this.scanLayerOptions = Array.from({ length: maxExistingLayer + 1 }, (_, i) => i + 1);
  917. console.log('不限高模式 - 已有最大层:' + maxExistingLayer + ',可选层数:' + this.scanLayerOptions.length + ' - rqrq');
  918. } else {
  919. // maxLayer>0:有限高,可选1~maxLayer层 - rqrq
  920. this.currentMixedMode = false;
  921. this.scanLayerOptions = Array.from({ length: this.currentMaxLayer }, (_, i) => i + 1);
  922. console.log('限高模式 - maxLayer:' + this.currentMaxLayer + ' - rqrq');
  923. }
  924. // 加载位置网格 - rqrq
  925. this.loadAvailablePositions();
  926. } else {
  927. this.$alert(data.msg || '获取栈板信息失败', '错误', { confirmButtonText: '确定' });
  928. }
  929. }).catch(error => {
  930. console.error('获取栈板信息失败:', error);
  931. this.$alert('获取栈板信息失败', '错误', { confirmButtonText: '确定' });
  932. });
  933. },
  934. // 加载可用位置 - rqrq
  935. loadAvailablePositions() {
  936. console.log('开始加载位置网格 - rqrq,layer=' + this.scanLayer);
  937. // 设置加载状态,禁用所有位置 - rqrq
  938. this.positionGridLoading = true;
  939. this.availablePositions = []; // 清空可用位置,全部禁用
  940. // 先获取当前层的所有明细数据,用于统计每个position的数量 - rqrq
  941. getPalletDetails({
  942. site: this.site,
  943. palletId: this.palletCode,
  944. position: '',
  945. layer: this.scanLayer
  946. }).then(({ data: detailData }) => {
  947. // 统计每个position的数量 - rqrq
  948. const positionCountMap = {};
  949. if (detailData && detailData.code === 0) {
  950. const details = detailData.details || [];
  951. details.forEach(detail => {
  952. const pos = detail.position;
  953. positionCountMap[pos] = (positionCountMap[pos] || 0) + 1;
  954. });
  955. console.log('当前层(' + this.scanLayer + ')各位置数量统计 - rqrq:', positionCountMap);
  956. }
  957. // 获取位置网格信息 - rqrq
  958. return getAvailablePositionsForLayer({
  959. site: this.site,
  960. palletId: this.palletCode,
  961. layer: this.scanLayer
  962. }).then(({ data }) => {
  963. if (data && data.code === 0) {
  964. const result = data.data;
  965. const positions = result.positions || [];
  966. this.availablePositions = result.availablePositions || [];
  967. // 将positions转换为包含count的对象数组 - rqrq
  968. this.positionGrid = positions.map(position => ({
  969. position: position,
  970. count: positionCountMap[position] || 0
  971. }));
  972. console.log('位置网格加载完成 - 总位置:' + this.positionGrid.length + ',可用:' + this.availablePositions.length + ' - rqrq');
  973. } else {
  974. this.$alert(data.msg || '获取位置信息失败', '错误', { confirmButtonText: '确定' });
  975. // 失败后保持全部禁用 - rqrq
  976. this.availablePositions = [];
  977. this.positionGrid = [];
  978. }
  979. });
  980. }).catch(error => {
  981. console.error('获取位置信息失败:', error);
  982. this.$alert('获取位置信息失败', '错误', { confirmButtonText: '确定' });
  983. // 失败后保持全部禁用 - rqrq
  984. this.availablePositions = [];
  985. this.positionGrid = [];
  986. }).finally(() => {
  987. // 无论成功失败,都要解除加载状态 - rqrq
  988. this.positionGridLoading = false;
  989. });
  990. },
  991. // 点击层数格子 - rqrq
  992. handleLayerClick(layer) {
  993. if (this.scanLayer === layer) {
  994. return; // 已选中,不重复处理
  995. }
  996. console.log('切换层数 - rqrq,从第' + this.scanLayer + '层切换到第' + layer + '层');
  997. this.scanLayer = layer;
  998. this.currentSelectedPosition = '';
  999. this.loadAvailablePositions();
  1000. },
  1001. // 点击位置网格 - rqrq
  1002. handlePositionClick(position) {
  1003. // 加载中或位置不可用时不处理 - rqrq
  1004. if (this.positionGridLoading) {
  1005. this.$message.warning('位置信息加载中,请稍候');
  1006. return;
  1007. }
  1008. if (!this.availablePositions.includes(position)) {
  1009. this.$message.warning('该位置不可用');
  1010. return;
  1011. }
  1012. this.currentSelectedPosition = position;
  1013. this.scanPosition = position;
  1014. console.log('选择位置 - rqrq,position=' + position);
  1015. // 聚焦到扫描输入框 - rqrq
  1016. this.$nextTick(() => {
  1017. if (this.$refs.scanInput) {
  1018. this.$refs.scanInput.focus();
  1019. }
  1020. });
  1021. },
  1022. moveFocusToScanInput(){
  1023. this.$nextTick(() => {
  1024. if (this.$refs.scanInput) {
  1025. this.$refs.scanInput.focus();
  1026. }
  1027. });
  1028. },
  1029. // 关闭扫码模态框
  1030. closeScanModal() {
  1031. this.scanModalVisible = false;
  1032. // 如果有操作成功,则在关闭时刷新外面的列表
  1033. if (this.needRefreshOnClose) {
  1034. this.refreshTable();
  1035. this.needRefreshOnClose = false;
  1036. }
  1037. },
  1038. // 处理标签扫描 - rqrq
  1039. handleLabelScan() {
  1040. if (!this.scanCode.trim()) {
  1041. this.$alert('请输入标签编码', '错误', { confirmButtonText: '确定' });
  1042. return;
  1043. }
  1044. if (this.operationType === 'in') {
  1045. // 扫进操作 - rqrq
  1046. if (!this.scanPosition) {
  1047. this.$alert('请选择位置', '错误', { confirmButtonText: '确定' });
  1048. return;
  1049. }
  1050. if (!this.scanLayer) {
  1051. this.$alert('请选择层数', '错误', { confirmButtonText: '确定' });
  1052. return;
  1053. }
  1054. // 扫进操作新增:校验标签是否在分拣明细中 - rqrq
  1055. if (this.sortingList.length === 0) {
  1056. // 如果sortingList为空,先获取分拣明细 - rqrq
  1057. console.log('sortingList为空,开始获取分拣明细 - rqrq');
  1058. getSortingList({
  1059. site: this.site,
  1060. palletId: this.palletCode
  1061. }).then(({ data }) => {
  1062. if (data && data.code === 0) {
  1063. this.sortingList = data.rfidList || [];
  1064. console.log('分拣明细获取成功 - rqrq,共' + this.sortingList.length + '条');
  1065. // 获取成功后,继续校验标签 - rqrq
  1066. this.validateAndScanIn();
  1067. } else {
  1068. // 获取失败 - rqrq
  1069. this.$alert(data.msg || '获取分拣明细失败', '错误', {
  1070. confirmButtonText: '确定',
  1071. callback: () => {
  1072. this.scanCode = '';
  1073. this.$nextTick(() => {
  1074. if (this.$refs.scanInput) {
  1075. this.$refs.scanInput.focus();
  1076. }
  1077. });
  1078. }
  1079. });
  1080. }
  1081. }).catch(error => {
  1082. console.error('获取分拣明细失败:', error);
  1083. this.$alert(error.message || '获取分拣明细失败', '错误', {
  1084. confirmButtonText: '确定',
  1085. callback: () => {
  1086. this.scanCode = '';
  1087. this.$nextTick(() => {
  1088. if (this.$refs.scanInput) {
  1089. this.$refs.scanInput.focus();
  1090. }
  1091. });
  1092. }
  1093. });
  1094. });
  1095. return;
  1096. }
  1097. // sortingList有数据,直接校验 - rqrq
  1098. this.validateAndScanIn();
  1099. } else {
  1100. // 扫回操作(不需要校验分拣明细)- rqrq
  1101. this.handleScanOut();
  1102. }
  1103. },
  1104. // 校验标签并执行扫进操作 - rqrq
  1105. validateAndScanIn() {
  1106. // 校验sortingList是否为空 - rqrq
  1107. if (this.sortingList.length === 0) {
  1108. this.$alert('当前不存在分拣任务', '提示', {
  1109. confirmButtonText: '确定',
  1110. callback: () => {
  1111. this.scanCode = '';
  1112. this.$nextTick(() => {
  1113. if (this.$refs.scanInput) {
  1114. this.$refs.scanInput.focus();
  1115. }
  1116. });
  1117. }
  1118. });
  1119. return;
  1120. }
  1121. // 校验标签是否在sortingList中 - rqrq
  1122. if (!this.sortingList.includes(this.scanCode)) {
  1123. this.$alert('该物料不在分拣明细中', '错误', {
  1124. confirmButtonText: '确定',
  1125. callback: () => {
  1126. this.scanCode = '';
  1127. this.$nextTick(() => {
  1128. if (this.$refs.scanInput) {
  1129. this.$refs.scanInput.focus();
  1130. }
  1131. });
  1132. }
  1133. });
  1134. return;
  1135. }
  1136. console.log('标签校验通过,开始扫进 - rqrq');
  1137. // 校验通过,执行原有的扫进逻辑 - rqrq
  1138. // 先查询这个标签的原始数据(扫进之前的数据)- rqrq
  1139. getLabelInfo({
  1140. site: this.site,
  1141. serialNo: this.scanCode
  1142. }).then(({ data: queryData }) => {
  1143. let originalDetail = null;
  1144. if (queryData && queryData.code === 0 && queryData.row) {
  1145. originalDetail = queryData.row;
  1146. }
  1147. // 执行扫进操作 - rqrq
  1148. savePalletDetail({
  1149. site: this.site,
  1150. palletId: this.palletCode,
  1151. position: this.scanPosition,
  1152. layer: this.scanLayer,
  1153. serialNo: this.scanCode,
  1154. sortFlag:1,//是分拣
  1155. }).then(({ data }) => {
  1156. if (data.code === 0) {
  1157. // 扫进成功后,缓存原始数据 - rqrq
  1158. if (originalDetail) {
  1159. this.originalDetailCache[this.scanCode] = {
  1160. palletId: originalDetail.palletId || '',
  1161. position: originalDetail.position || '',
  1162. layer: originalDetail.layer || 1,
  1163. wcsFlag: originalDetail.wcsFlag !== undefined ? originalDetail.wcsFlag : 1,
  1164. partNo: originalDetail.partNo || '',
  1165. createDate: originalDetail.createDate || new Date().toISOString(),
  1166. createBy: originalDetail.createBy || ''
  1167. };
  1168. console.log('扫进成功,已缓存原始数据 - rqrq,serialNo=' + this.scanCode +
  1169. ',原栈板=' + originalDetail.palletId);
  1170. }
  1171. this.$message.success('扫进成功');
  1172. this.needRefreshOnClose = true;
  1173. this.scanCode = '';
  1174. // 重新加载层数选项(可能新增了层数) - rqrq
  1175. this.loadLayerOptions();
  1176. // 保持当前位置选中,聚焦到输入框 - rqrq
  1177. this.$refs.scanInput.focus();
  1178. } else {
  1179. // 扫进失败:弹出提示框,截取前100字符 - rqrq
  1180. let errorMsg = data.msg || '扫进失败';
  1181. if (errorMsg.length > 100) {
  1182. errorMsg = errorMsg.substring(0, 100) + '...';
  1183. }
  1184. this.$alert(errorMsg, '错误', {
  1185. confirmButtonText: '确定',
  1186. callback: () => {
  1187. this.scanCode = '';
  1188. this.$nextTick(() => {
  1189. if (this.$refs.scanInput) {
  1190. this.$refs.scanInput.focus();
  1191. }
  1192. });
  1193. }
  1194. });
  1195. }
  1196. }).catch(error => {
  1197. console.error('扫进失败:', error);
  1198. // 网络错误:弹出提示框 - rqrq
  1199. let errorMsg = error.message || '扫进失败';
  1200. if (errorMsg.length > 100) {
  1201. errorMsg = errorMsg.substring(0, 100) + '...';
  1202. }
  1203. this.$alert(errorMsg, '错误', {
  1204. confirmButtonText: '确定',
  1205. callback: () => {
  1206. this.scanCode = '';
  1207. this.$nextTick(() => {
  1208. if (this.$refs.scanInput) {
  1209. this.$refs.scanInput.focus();
  1210. }
  1211. });
  1212. }
  1213. });
  1214. });
  1215. }).catch(error => {
  1216. console.error('查询标签信息失败:', error);
  1217. // 查询失败不影响扫进操作,只是无法缓存原始数据 - rqrq
  1218. console.log('无法查询标签原始信息,将不缓存 - rqrq');
  1219. });
  1220. },
  1221. // 扫回操作 - rqrq
  1222. handleScanOut() {
  1223. // 扫回操作(恢复到原栈板)- rqrq
  1224. // 检查是否有缓存的原始数据 - rqrq
  1225. const originalData = this.originalDetailCache[this.scanCode];
  1226. if (!originalData) {
  1227. this.$alert('未找到该标签的原始数据,无法扫回', '错误', { confirmButtonText: '确定' });
  1228. this.scanCode = '';
  1229. this.$nextTick(() => {
  1230. if (this.$refs.scanInput) {
  1231. this.$refs.scanInput.focus();
  1232. }
  1233. });
  1234. return;
  1235. }
  1236. // 调用恢复接口 - rqrq
  1237. restorePalletDetail({
  1238. site: this.site,
  1239. serialNo: this.scanCode,
  1240. originalPalletId: originalData.palletId,
  1241. originalPosition: originalData.position,
  1242. originalLayer: originalData.layer,
  1243. partNo: originalData.partNo,
  1244. createDate: originalData.createDate,
  1245. createBy: originalData.createBy,
  1246. wcsFlag: originalData.wcsFlag
  1247. }).then(({ data }) => {
  1248. if (data.code === 0) {
  1249. // 恢复成功后,删除缓存 - rqrq
  1250. delete this.originalDetailCache[this.scanCode];
  1251. console.log('扫回成功,已删除缓存 - rqrq,serialNo=' + this.scanCode);
  1252. this.$message.success('扫回成功');
  1253. this.needRefreshOnClose = true;
  1254. this.scanCode = '';
  1255. this.$nextTick(() => {
  1256. if (this.$refs.scanInput) {
  1257. this.$refs.scanInput.focus();
  1258. }
  1259. });
  1260. } else {
  1261. // 扫回失败:弹出提示框,截取前100字符 - rqrq
  1262. let errorMsg = data.msg || '扫回失败';
  1263. if (errorMsg.length > 100) {
  1264. errorMsg = errorMsg.substring(0, 100) + '...';
  1265. }
  1266. this.$alert(errorMsg, '错误', {
  1267. confirmButtonText: '确定',
  1268. callback: () => {
  1269. this.scanCode = '';
  1270. this.$nextTick(() => {
  1271. if (this.$refs.scanInput) {
  1272. this.$refs.scanInput.focus();
  1273. }
  1274. });
  1275. }
  1276. });
  1277. }
  1278. }).catch(error => {
  1279. console.error('扫回失败:', error);
  1280. // 网络错误:弹出提示框 - rqrq
  1281. let errorMsg = error.message || '扫回失败';
  1282. if (errorMsg.length > 100) {
  1283. errorMsg = errorMsg.substring(0, 100) + '...';
  1284. }
  1285. this.$alert(errorMsg, '错误', {
  1286. confirmButtonText: '确定',
  1287. callback: () => {
  1288. this.scanCode = '';
  1289. this.$nextTick(() => {
  1290. if (this.$refs.scanInput) {
  1291. this.$refs.scanInput.focus();
  1292. }
  1293. });
  1294. }
  1295. });
  1296. });
  1297. },
  1298. // 双击行事件 - 修改位置
  1299. handleRowDblClick(detail, index) {
  1300. this.editSerialNo = detail.serialNo;
  1301. this.editPosition = detail.position;
  1302. this.editLayer = detail.layer;
  1303. this.editOriginalPosition = detail.position;
  1304. this.editOriginalLayer = detail.layer;
  1305. // 获取当前位置的层数选项(排除自己)
  1306. this.handleEditPositionChange();
  1307. this.editPositionModalVisible = true;
  1308. },
  1309. // 编辑位置选择变化
  1310. handleEditPositionChange() {
  1311. if (this.editPosition) {
  1312. // maxLayer=0表示混装托盘,只能选第1层
  1313. if (this.currentMaxLayer === 0) {
  1314. this.editLayerOptions = [1];
  1315. this.editLayer = 1; // 自动选中第1层
  1316. return;
  1317. }
  1318. // maxLayer>0,根据已有层数和maxLayer计算可选层数
  1319. getLayersForEdit({
  1320. site: this.site,
  1321. palletId: this.palletCode,
  1322. position: this.editPosition,
  1323. excludeSerialNo: this.editSerialNo
  1324. }).then(({ data }) => {
  1325. if (data.code === 0) {
  1326. let layerOptions = data.layers || [];
  1327. // 根据maxLayer限制层数选项
  1328. if (this.currentMaxLayer > 0) {
  1329. // 过滤掉超过maxLayer的层数
  1330. layerOptions = layerOptions.filter(layer => layer <= this.currentMaxLayer);
  1331. }
  1332. this.editLayerOptions = layerOptions;
  1333. // 如果当前选择的层数不在新的选项中,清空选择
  1334. // if (!this.editLayerOptions.includes(this.editLayer)) {
  1335. // this.editLayer = '';
  1336. // }
  1337. }
  1338. }).catch(error => {
  1339. console.error('获取层数失败:', error);
  1340. this.editLayerOptions = [];
  1341. });
  1342. } else {
  1343. this.editLayerOptions = [];
  1344. this.editLayer = '';
  1345. }
  1346. },
  1347. // 确定修改位置
  1348. confirmEditPosition() {
  1349. if (!this.editPosition) {
  1350. this.$alert('请选择位置', '错误', { confirmButtonText: '确定' });
  1351. return;
  1352. }
  1353. if (!this.editLayer) {
  1354. this.$alert('请选择层数', '错误', { confirmButtonText: '确定' });
  1355. return;
  1356. }
  1357. // 检查是否有变化
  1358. if (this.editPosition === this.editOriginalPosition && this.editLayer === this.editOriginalLayer) {
  1359. this.$message.warning('位置没有变化');
  1360. return;
  1361. }
  1362. // 设置loading状态,防止重复点击
  1363. this.editPositionLoading = true;
  1364. updatePalletDetailPosition({
  1365. site: this.site,
  1366. palletId: this.palletCode,
  1367. serialNo: this.editSerialNo,
  1368. newPosition: this.editPosition,
  1369. newLayer: this.editLayer
  1370. }).then(({ data }) => {
  1371. if (data.code === 0) {
  1372. this.$message.success('位置修改成功');
  1373. this.closeEditPositionModal();
  1374. this.refreshTable();
  1375. } else {
  1376. this.$alert(data.msg || '位置修改失败', '错误', { confirmButtonText: '确定' });
  1377. }
  1378. }).catch(error => {
  1379. console.error('位置修改失败:', error);
  1380. this.$alert('位置修改失败', '错误', { confirmButtonText: '确定' });
  1381. }).finally(() => {
  1382. // 无论成功或失败,都要恢复按钮状态
  1383. this.editPositionLoading = false;
  1384. });
  1385. },
  1386. // 关闭修改位置模态框
  1387. closeEditPositionModal() {
  1388. this.editPositionModalVisible = false;
  1389. this.editSerialNo = '';
  1390. this.editPosition = '';
  1391. this.editLayer = '';
  1392. this.editLayerOptions = [];
  1393. this.editOriginalPosition = '';
  1394. this.editOriginalLayer = '';
  1395. },
  1396. // 完成组托按钮点击事件
  1397. handleCompleteAssembly() {
  1398. if (!this.palletCode) {
  1399. this.$alert('请先扫描栈板', '错误', { confirmButtonText: '确定' });
  1400. return;
  1401. }
  1402. // 检查是否有栈板明细
  1403. if (!this.detailList || this.detailList.length === 0) {
  1404. this.$alert('栈板明细为空,请先扫进物料', '错误', { confirmButtonText: '确定' });
  1405. return;
  1406. }
  1407. // 确认操作
  1408. this.$confirm('确认完成组托并推送数据到WCS系统吗?', '提示', {
  1409. confirmButtonText: '确定',
  1410. cancelButtonText: '取消',
  1411. type: 'warning'
  1412. }).then(() => {
  1413. this.doCompleteAssembly();
  1414. }).catch(() => {
  1415. // 用户取消
  1416. });
  1417. },
  1418. // 执行完成组托
  1419. doCompleteAssembly() {
  1420. // 设置loading状态,防止重复点击
  1421. this.completeAssemblyLoading = true;
  1422. completePalletAssemblyForFenJian({
  1423. site: this.site,
  1424. palletId: this.palletCode
  1425. }).then(({ data }) => {
  1426. if (data.code === 0) {
  1427. this.$message.success('数据已推送到WCS系统');
  1428. // 清空sortingList,表示本次分拣结束 - rqrq
  1429. this.sortingList = [];
  1430. console.log('提交分拣成功,已清空sortingList - rqrq');
  1431. // 清空页面数据,初始化页面
  1432. // this.resetPage();
  1433. } else {
  1434. // 失败时弹出提示框 - rqrq
  1435. this.$alert(data.msg || '本次分拣失败', '错误', {
  1436. confirmButtonText: '确定',
  1437. callback: () => {
  1438. // 点击确定后提示框消失,不做其他操作 - rqrq
  1439. }
  1440. });
  1441. }
  1442. }).catch(error => {
  1443. console.error('本次分拣失败:', error);
  1444. // 网络错误也弹出提示框 - rqrq
  1445. this.$alert(error.message || '本次分拣失败', '错误', {
  1446. confirmButtonText: '确定',
  1447. callback: () => {
  1448. // 点击确定后提示框消失,不做其他操作 - rqrq
  1449. }
  1450. });
  1451. }).finally(() => {
  1452. // 无论成功或失败,都要恢复按钮状态
  1453. this.completeAssemblyLoading = false;
  1454. });
  1455. },
  1456. // 确认Call栈板
  1457. confirmCallPallet() {
  1458. if (!this.selectedCallStartStation) {
  1459. this.$alert('请选择起始站点', '错误', { confirmButtonText: '确定' });
  1460. return;
  1461. }
  1462. if (!this.selectedCallTargetStation) {
  1463. this.$alert('请选择目标站点', '错误', { confirmButtonText: '确定' });
  1464. return;
  1465. }
  1466. // 前端验证:两个站点不能一样
  1467. if (this.selectedCallStartStation === this.selectedCallTargetStation) {
  1468. this.$alert('起始站点和目标站点不能相同', '错误', { confirmButtonText: '确定' });
  1469. return;
  1470. }
  1471. // 设置loading状态,防止重复点击
  1472. this.callPalletLoading = true;
  1473. callPalletToStation({
  1474. site: this.site,
  1475. startStation: this.selectedCallStartStation,
  1476. targetStation: this.selectedCallTargetStation
  1477. }).then(({ data }) => {
  1478. if (data.code === 0) {
  1479. this.$message.success('空托盘调用任务创建成功');
  1480. this.closeCallPalletModal();
  1481. } else {
  1482. this.$alert(data.msg || '调用空托盘失败', '错误', { confirmButtonText: '确定' });
  1483. }
  1484. }).catch(error => {
  1485. console.error('调用空托盘失败:', error);
  1486. this.$alert('异常:'+error, '错误', { confirmButtonText: '确定' });
  1487. }).finally(() => {
  1488. // 无论成功或失败,都要恢复按钮状态
  1489. this.callPalletLoading = false;
  1490. });
  1491. },
  1492. // 关闭Call栈板模态框
  1493. closeCallPalletModal() {
  1494. this.callPalletModalVisible = false;
  1495. this.selectedCallStartStation = '';
  1496. this.selectedCallTargetStation = '';
  1497. this.callStartStationOptions = [];
  1498. this.callTargetStationOptions = [];
  1499. },
  1500. showDetailModal(){
  1501. this.detailModalVisible=true;
  1502. },
  1503. // 结束分拣按钮点击事件 - rqrq
  1504. handleFinishSorting() {
  1505. if (!this.palletCode) {
  1506. this.$alert('请先扫描栈板', '错误', { confirmButtonText: '确定' });
  1507. return;
  1508. }
  1509. // 检查是否有栈板明细
  1510. // if (!this.detailList || this.detailList.length === 0) {
  1511. // this.$alert('栈板明细为空,无法结束分拣', '错误', { confirmButtonText: '确定' });
  1512. // return;
  1513. // }
  1514. // 确认操作 - rqrq
  1515. this.$confirm('确认结束分拣并将栈板送至缓存区(Z104)吗?', '提示', {
  1516. confirmButtonText: '确定',
  1517. cancelButtonText: '取消',
  1518. type: 'warning'
  1519. }).then(() => {
  1520. this.doFinishSorting();
  1521. }).catch(() => {
  1522. // 用户取消
  1523. });
  1524. },
  1525. // 执行结束分拣 - rqrq
  1526. doFinishSorting() {
  1527. // 设置loading状态,防止重复点击 - rqrq
  1528. this.finishSortingLoading = true;
  1529. finishSorting({
  1530. site: this.site,
  1531. palletId: this.palletCode
  1532. }).then(({ data }) => {
  1533. if (data.code === 0) {
  1534. this.$message.success('结束分拣成功,已创建送货任务');
  1535. // 清空页面数据,初始化页面 - rqrq
  1536. this.resetPage();
  1537. } else {
  1538. this.$alert(data.msg || '结束分拣失败', '错误', { confirmButtonText: '确定' });
  1539. }
  1540. }).catch(error => {
  1541. console.error('结束分拣失败:', error);
  1542. this.$alert('结束分拣失败', '错误', { confirmButtonText: '确定' });
  1543. }).finally(() => {
  1544. // 无论成功或失败,都要恢复按钮状态 - rqrq
  1545. this.finishSortingLoading = false;
  1546. });
  1547. },
  1548. },
  1549. mounted() {
  1550. this.$nextTick(() => {
  1551. if (this.$refs.palletInput) {
  1552. this.$refs.palletInput.focus();
  1553. }
  1554. });
  1555. }
  1556. };
  1557. </script>
  1558. <style scoped>
  1559. /* 表格样式 */
  1560. .detail-table {
  1561. background: white;
  1562. border-radius: 6px;
  1563. overflow: hidden;
  1564. border: 1px solid #e0e0e0;
  1565. }
  1566. .table-header,
  1567. .table-row {
  1568. display: flex;
  1569. align-items: center;
  1570. padding: 8px;
  1571. border-bottom: 1px solid #e0e0e0;
  1572. }
  1573. .table-header {
  1574. background: #f5f5f5;
  1575. font-weight: bold;
  1576. font-size: 14px;
  1577. }
  1578. .table-row {
  1579. font-size: 13px;
  1580. }
  1581. .table-row:last-child {
  1582. border-bottom: none;
  1583. }
  1584. .col-position {
  1585. flex: 1;
  1586. text-align: center;
  1587. }
  1588. .col-layer {
  1589. flex: 1;
  1590. text-align: center;
  1591. }
  1592. .col-serial {
  1593. flex: 4;
  1594. text-align: center;
  1595. word-break: break-all;
  1596. }
  1597. /* 空数据提示 */
  1598. .empty-hint {
  1599. text-align: center;
  1600. color: #999;
  1601. padding: 20px;
  1602. background: white;
  1603. border-radius: 6px;
  1604. margin-top: 16px;
  1605. }
  1606. /* 模态框样式 */
  1607. .scan-modal-content {
  1608. padding: 10px 0;
  1609. }
  1610. .modal-form {
  1611. margin-bottom: 16px;
  1612. }
  1613. .dialog-footer {
  1614. text-align: center;
  1615. }
  1616. /* 修复模态框层级问题 */
  1617. ::v-deep .el-dialog__wrapper {
  1618. z-index: 2000 !important;
  1619. }
  1620. ::v-deep .el-overlay {
  1621. z-index: 2000 !important;
  1622. }
  1623. /* 修复单选框样式 */
  1624. ::v-deep .el-radio {
  1625. margin-right: 8px !important;
  1626. }
  1627. ::v-deep .el-radio__label {
  1628. font-size: 14px;
  1629. }
  1630. /* 标题行样式 */
  1631. .list-title-row {
  1632. display: flex;
  1633. justify-content: space-between;
  1634. align-items: center;
  1635. margin-bottom: 8px;
  1636. }
  1637. .list-title-row .list-title {
  1638. margin: 0;
  1639. flex: 1;
  1640. }
  1641. /* 空数据行样式 */
  1642. .empty-row {
  1643. justify-content: center;
  1644. align-items: center;
  1645. padding: 20px;
  1646. border-bottom: none;
  1647. }
  1648. .empty-row .empty-hint {
  1649. text-align: center;
  1650. color: #999;
  1651. width: 100%;
  1652. }
  1653. /* 按钮禁用状态样式 */
  1654. .action-btn:disabled {
  1655. opacity: 0.6;
  1656. cursor: not-allowed;
  1657. background-color: #ccc !important;
  1658. border-color: #ccc !important;
  1659. }
  1660. /* 层数网格样式 - rqrq */
  1661. .layer-grid {
  1662. display: flex;
  1663. flex-wrap: wrap;
  1664. gap: 8px;
  1665. margin-top: 8px;
  1666. }
  1667. .layer-item {
  1668. display: flex;
  1669. align-items: center;
  1670. justify-content: center;
  1671. min-width: 50px;
  1672. height: 45px;
  1673. padding: 0 12px;
  1674. background-color: #fff;
  1675. border: 2px solid #dcdfe6;
  1676. border-radius: 6px;
  1677. font-size: 15px;
  1678. font-weight: bold;
  1679. cursor: pointer;
  1680. transition: all 0.3s;
  1681. user-select: none;
  1682. }
  1683. .layer-item:hover {
  1684. border-color: #409eff;
  1685. background-color: #ecf5ff;
  1686. }
  1687. .layer-item.layer-selected {
  1688. border-color: #409eff;
  1689. background-color: #409eff;
  1690. color: #fff;
  1691. }
  1692. /* 位置网格样式 - rqrq */
  1693. .position-grid {
  1694. display: grid;
  1695. gap: 10px;
  1696. margin-top: 8px;
  1697. position: relative;
  1698. }
  1699. /* 1宫格:单个居中 - rqrq */
  1700. .position-grid.position-grid-1 {
  1701. grid-template-columns: 1fr;
  1702. grid-template-rows: 1fr;
  1703. }
  1704. /* 4宫格:2行2列,按列排列(1,2 | 3,4)- rqrq */
  1705. /* 2宫格也使用此布局,只是1在左上,2在右下 - rqrq */
  1706. .position-grid.position-grid-4 {
  1707. grid-template-columns: repeat(2, 1fr);
  1708. grid-template-rows: repeat(2, 1fr);
  1709. grid-auto-flow: column;
  1710. }
  1711. /* 9宫格:3行3列,按行排列(1,2,3 | 4,5,6 | 7,8,9) - rqrq */
  1712. .position-grid.position-grid-9 {
  1713. grid-template-columns: repeat(3, 1fr);
  1714. grid-template-rows: repeat(3, 1fr);
  1715. grid-auto-flow: row;
  1716. }
  1717. /* 2宫格占位格子样式(灰色不可选)- rqrq */
  1718. .position-item.position-placeholder {
  1719. background-color: #f0f0f0 !important;
  1720. border: 1px dashed #d0d0d0 !important;
  1721. color: transparent !important;
  1722. cursor: not-allowed !important;
  1723. pointer-events: none !important;
  1724. }
  1725. /* 加载中的遮罩效果 - rqrq */
  1726. .position-grid.position-grid-loading {
  1727. opacity: 0.7;
  1728. pointer-events: none;
  1729. }
  1730. .position-item {
  1731. display: flex;
  1732. align-items: center;
  1733. justify-content: center;
  1734. height: 60px;
  1735. background-color: #fff;
  1736. border: 2px solid #dcdfe6;
  1737. border-radius: 6px;
  1738. font-size: 16px;
  1739. font-weight: bold;
  1740. cursor: pointer;
  1741. transition: all 0.3s;
  1742. user-select: none;
  1743. }
  1744. .position-item:hover:not(.position-disabled) {
  1745. border-color: #409eff;
  1746. background-color: #ecf5ff;
  1747. }
  1748. .position-item.position-selected {
  1749. border-color: #409eff;
  1750. background-color: #409eff;
  1751. color: #fff;
  1752. }
  1753. .position-item.position-disabled {
  1754. background-color: #f5f7fa;
  1755. color: #c0c4cc;
  1756. cursor: not-allowed;
  1757. opacity: 0.6;
  1758. }
  1759. /* 加载中的位置项样式 - rqrq */
  1760. .position-item.position-loading {
  1761. background: linear-gradient(90deg, #f5f7fa 25%, #e4e7ed 50%, #f5f7fa 75%);
  1762. background-size: 200% 100%;
  1763. animation: loading 1.5s ease-in-out infinite;
  1764. }
  1765. @keyframes loading {
  1766. 0% {
  1767. background-position: 200% 0;
  1768. }
  1769. 100% {
  1770. background-position: -200% 0;
  1771. }
  1772. }
  1773. </style>