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.

1958 lines
62 KiB

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