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.

1708 lines
52 KiB

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