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.

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