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.

1798 lines
55 KiB

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