containercard copy.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. <template>
  2. <div :style="{ backgroundColor: bgColor }">
  3. <q-dialog
  4. v-model="storage_dialog"
  5. transition-show="jump-down"
  6. transition-hide="jump-up"
  7. @show="prepareDialog()"
  8. >
  9. <q-card style="min-width: 1000px">
  10. <q-bar
  11. class="bg-light-blue-10 text-white rounded-borders"
  12. style="height: 50px"
  13. >
  14. <div>
  15. {{ "托盘" }}
  16. </div>
  17. <q-space></q-space>
  18. {{ containerCode }}
  19. </q-bar>
  20. <q-card-section class="q-pt-md">
  21. <q-tabs v-model="activeTab">
  22. <q-tab name="tab2" label="托盘信息" />
  23. <q-tab name="tab3" label="托盘详情" />
  24. <q-tab name="tab1" label="操作记录" />
  25. </q-tabs>
  26. </q-card-section>
  27. <q-tab-panels v-model="activeTab" animated>
  28. <q-tab-panel name="tab2" style="height: 600px">
  29. <template>
  30. <div class="text-h6 q-mb-md">{{ "托盘信息" }}</div>
  31. <q-table
  32. v-if="storage_form.length > 0"
  33. :data="storage_form"
  34. :columns="columns_batch"
  35. row-key="id"
  36. flat
  37. bordered
  38. hide-pagination
  39. class="my-sticky-table scrollable-table"
  40. :style="{ 'max-height': '400px' }"
  41. :container-style="{ height: 'auto' }"
  42. :pagination="{ rowsPerPage: 0 }"
  43. >
  44. </q-table>
  45. <div style="float: right; min-width: 100%" flow="row wrap">
  46. <q-card class="q-mb-md" bordered>
  47. <q-card-actions
  48. class="q-px-none"
  49. style="
  50. position: absolute;
  51. right: 20px;
  52. top: 10px;
  53. z-index: 100;
  54. "
  55. >
  56. <q-btn
  57. flat
  58. dense
  59. color="primary"
  60. @click="showInventoryDetails = !showInventoryDetails"
  61. >
  62. {{ showInventoryDetails ? "收起" : "展开" }}
  63. </q-btn>
  64. </q-card-actions>
  65. <q-card-section>
  66. <div class="text-h6 q-mb-md">
  67. {{ "物料详情" }}
  68. </div>
  69. <q-table
  70. v-if="showInventoryDetails"
  71. :data="results"
  72. :columns="columns_results"
  73. row-key="id"
  74. flat
  75. bordered
  76. hide-pagination
  77. class="my-sticky-table scrollable-table"
  78. :style="{ 'max-height': '400px' }"
  79. :container-style="{ height: 'auto' }"
  80. :pagination="{ rowsPerPage: 0 }"
  81. >
  82. </q-table>
  83. </q-card-section>
  84. </q-card>
  85. </div>
  86. </template>
  87. </q-tab-panel>
  88. <q-tab-panel name="tab3" style="height: 600px">
  89. <q-table
  90. :data="container_table"
  91. :columns="coloums_container"
  92. row-key="id"
  93. flat
  94. bordered
  95. hide-pagination
  96. class="my-sticky-table scrollable-table"
  97. :style="{ 'max-height': '600px' }"
  98. :container-style="{ height: 'auto' }"
  99. :pagination="{ rowsPerPage: 0 }"
  100. >
  101. <template v-slot:body-cell-actions="props">
  102. <q-td :props="props">
  103. <q-btn
  104. dense
  105. round
  106. color="primary"
  107. icon="edit"
  108. @click="handleRowEdit(props.row)"
  109. >
  110. <q-tooltip>编辑该行数据</q-tooltip>
  111. </q-btn>
  112. <q-btn
  113. dense
  114. round
  115. color="primary"
  116. icon="delete"
  117. @click="handleRowdelete(props.row)"
  118. >
  119. <q-tooltip>删除该行数据</q-tooltip>
  120. </q-btn>
  121. </q-td>
  122. </template>
  123. </q-table>
  124. </q-tab-panel>
  125. <q-tab-panel name="tab1" style="height: 600px">
  126. <div
  127. style="
  128. float: right;
  129. padding: 15px 15px 50px 15px;
  130. min-width: 100%;
  131. "
  132. flow="row wrap"
  133. >
  134. <q-card class="q-mb-md" bordered>
  135. <q-card-section>
  136. <template>
  137. <div
  138. class="text-h6 q-mb-md"
  139. style="display: flex; justify-content: space-between"
  140. >
  141. {{ "操作记录" }}
  142. </div>
  143. <q-btn
  144. class="q-ml-sm"
  145. color="primary"
  146. icon="event"
  147. @click="sortoperatedetail"
  148. >近一个月操作记录</q-btn
  149. >
  150. <template v-if="listSize > 0">
  151. <q-list bordered class="rounded-borders">
  152. <q-expansion-item
  153. v-for="(node, index) in nodeList"
  154. v-if="node.value && node.value.length > 0"
  155. :key="index"
  156. group="op-group"
  157. :caption="`操作类型: ${formatType(
  158. node.value[0].operation_type
  159. )} ----- 操作时间:${node.value[0].timestamp}`"
  160. >
  161. <q-card>
  162. <q-card-section>
  163. <q-table
  164. :data="node.value"
  165. :columns="columns_operate"
  166. row-key="id"
  167. flat
  168. bordered
  169. hide-pagination
  170. class="my-sticky-table scrollable-table"
  171. :style="{ 'max-height': '400px' }"
  172. :container-style="{ height: 'auto' }"
  173. :pagination="{ rowsPerPage: 0 }"
  174. >
  175. </q-table>
  176. </q-card-section>
  177. </q-card>
  178. </q-expansion-item>
  179. </q-list>
  180. </template>
  181. <div v-else class="text-grey-8">暂无操作记录</div>
  182. </template>
  183. </q-card-section>
  184. </q-card>
  185. </div>
  186. </q-tab-panel>
  187. </q-tab-panels>
  188. <div style="float: right; padding: 15px 15px 15px 0">
  189. <q-btn
  190. color="primary"
  191. text-color="white"
  192. style="margin-right: 25px"
  193. @click="handleEdit()"
  194. >
  195. {{
  196. can_edit_detail
  197. ? $t("stock.shelf.shelf_edit")
  198. : $t("stock.shelf.shelf_confirm")
  199. }}</q-btn
  200. >
  201. </div>
  202. </q-card>
  203. </q-dialog>
  204. </div>
  205. </template>
  206. <script>
  207. class ListNode {
  208. constructor (value) {
  209. this.value = value
  210. this.next = null
  211. }
  212. }
  213. class LinkedList {
  214. constructor () {
  215. this.head = null
  216. this.size = 0
  217. }
  218. append (value) {
  219. const newNode = new ListNode(value)
  220. if (!this.head) {
  221. this.head = newNode
  222. } else {
  223. let current = this.head
  224. while (current.next) {
  225. current = current.next
  226. }
  227. current.next = newNode
  228. }
  229. this.size++
  230. }
  231. delete (value) {
  232. if (!this.head) return
  233. if (this.head.value === value) {
  234. this.head = this.head.next
  235. this.size--
  236. return
  237. }
  238. let current = this.head
  239. while (current.next) {
  240. if (current.next.value === value) {
  241. current.next = current.next.next
  242. this.size--
  243. return
  244. }
  245. current = current.next
  246. }
  247. }
  248. toArray () {
  249. const result = []
  250. let current = this.head
  251. while (current) {
  252. result.push({ value: current.value })
  253. current = current.next
  254. }
  255. return result
  256. }
  257. clear () {
  258. this.head = null
  259. this.size = 0
  260. }
  261. }
  262. import { getauth, putauth, deleteauth } from 'boot/axios_request'
  263. import { LocalStorage } from 'quasar'
  264. export default {
  265. props: {
  266. containerCode: Number,
  267. containerNumber: Number
  268. },
  269. data () {
  270. return {
  271. pathnamecontainer: 'container/locationdetail/',
  272. pathnamecontainer_detail: 'container/containerdetail/',
  273. container_id: 123456,
  274. results: [],
  275. container_table: [],
  276. storage_form: [],
  277. showInventoryDetails: true,
  278. columns_batch: [
  279. {
  280. name: 'bound_number',
  281. label: '批次',
  282. field: (row) => row.bound_number,
  283. align: 'center'
  284. },
  285. {
  286. name: 'plan_weight',
  287. label: '当前库位容纳重量',
  288. field: (row) => row.total_batch_qty,
  289. align: 'center'
  290. }
  291. ],
  292. columns_results: [
  293. {
  294. label: '物料编码',
  295. field: (row) => row.goods_code,
  296. align: 'center'
  297. },
  298. {
  299. label: '物料名称',
  300. field: (row) => row.goods_desc,
  301. align: 'center'
  302. },
  303. {
  304. label: '物料批次',
  305. field: (row) => row.bound_number,
  306. align: 'center'
  307. },
  308. {
  309. label: '件数',
  310. field: (row) => row.group_qty,
  311. align: 'center'
  312. },
  313. {
  314. label: '每件重量',
  315. field: (row) => row.goods_qty,
  316. align: 'center'
  317. },
  318. {
  319. label: '批次计划重量',
  320. field: (row) => row.batch_total_qty,
  321. align: 'center'
  322. },
  323. {
  324. label: '在库重量',
  325. field: (row) => row.batch_total_in_qty,
  326. align: 'center'
  327. },
  328. {
  329. label: '录入时间',
  330. field: (row) => row.create_time.slice(0, 10),
  331. align: 'center'
  332. }
  333. ],
  334. coloums_container: [
  335. {
  336. label: '物料编码',
  337. field: (row) => row.goods_code,
  338. align: 'center'
  339. },
  340. {
  341. label: '物料名称',
  342. field: (row) => row.goods_desc,
  343. align: 'center'
  344. },
  345. {
  346. label: '物料批次',
  347. field: (row) => row.batch,
  348. align: 'center'
  349. },
  350. {
  351. label: '入库重量',
  352. field: (row) => row.goods_qty,
  353. align: 'center',
  354. sortable: true
  355. },
  356. {
  357. label: '出库重量',
  358. field: (row) => row.goods_out_qty,
  359. align: 'center',
  360. sortable: true
  361. },
  362. {
  363. label: '编辑',
  364. name: 'actions', // 必须与插槽名对应
  365. align: 'center',
  366. field: 'actions', // 指向数据字段(可为空)
  367. sortable: false, // 禁用排序
  368. headerStyle: 'width: 80px' // 固定列宽
  369. }
  370. ],
  371. columns_operate: [
  372. {
  373. name: 'timestamp',
  374. label: '操作时间',
  375. field: (row) => row.timestamp,
  376. align: 'center'
  377. },
  378. {
  379. name: 'operator',
  380. label: '经手人',
  381. field: (row) => row.operator,
  382. align: 'center'
  383. },
  384. {
  385. name: 'batch',
  386. label: '批次',
  387. field: (row) => row.batch.bound_number,
  388. align: 'center'
  389. },
  390. {
  391. name: 'memo',
  392. label: '备注',
  393. field: (row) => row.memo,
  394. align: 'center'
  395. },
  396. {
  397. label: '当前位置',
  398. field: (row) => row.from_location,
  399. align: 'center'
  400. },
  401. {
  402. label: '目标位置',
  403. field: (row) => row.to_location,
  404. align: 'center'
  405. }
  406. ],
  407. user_id: '',
  408. auth_id: '',
  409. can_edit_detail: false,
  410. storage_dialog: false,
  411. bgColor: 'transparent',
  412. error1: this.$t('stock.shelf.error1'),
  413. shelfLocal: '',
  414. activeTab: 'tab2',
  415. operate_detail: [],
  416. linkedList: new LinkedList()
  417. }
  418. },
  419. created () {
  420. this.handleclick()
  421. },
  422. computed: {
  423. nodeList () {
  424. return this.linkedList.toArray()
  425. },
  426. listSize () {
  427. return this.linkedList.size
  428. }
  429. },
  430. methods: {
  431. sortoperatedetail () {
  432. var _this = this
  433. console.log('近一个月操作记录', _this.nodeList)
  434. },
  435. formatType (type) {
  436. switch (type) {
  437. case 'container':
  438. return '组盘'
  439. case 'outbound':
  440. return '出库'
  441. case 'inbound':
  442. return '入库'
  443. case 'adjust':
  444. return '移库'
  445. case '5':
  446. return '调拨'
  447. case '6':
  448. return '其他'
  449. default:
  450. return '未知'
  451. }
  452. },
  453. getOperationRecord () {
  454. var _this = this
  455. _this.operate_detail = []
  456. var operate_detail_container = []
  457. _this.linkedList.clear()
  458. getauth('container/operate/?status=1&container=' + _this.containerNumber)
  459. .then((res) => {
  460. _this.operate_detail = res.results
  461. if (_this.operate_detail.length === 0) return
  462. // 初始化第一个元素
  463. operate_detail_container.push(_this.operate_detail[0])
  464. for (let i = 0; i < _this.operate_detail.length - 1; i++) {
  465. const current = _this.operate_detail[i]
  466. const next = _this.operate_detail[i + 1]
  467. if (current.operation_type === next.operation_type) {
  468. operate_detail_container.push(next)
  469. } else {
  470. _this.linkedList.append([...operate_detail_container])
  471. operate_detail_container = [next]
  472. }
  473. }
  474. // 添加最后一个分组
  475. if (operate_detail_container.length > 0) {
  476. _this.linkedList.append([...operate_detail_container])
  477. }
  478. })
  479. .catch((err) => {
  480. _this.$q.notify({
  481. message: err.detail,
  482. icon: 'close',
  483. color: 'negative'
  484. })
  485. })
  486. },
  487. handleRowdelete (rowData) {
  488. if (this.can_edit_detail === true) {
  489. console.log('当前行数据:', rowData)
  490. this.$q
  491. .dialog({
  492. title: '删除物料信息',
  493. message: `确定删除 ${rowData.goods_code} 吗?`,
  494. ok: { label: '确定', color: 'negative' },
  495. cancel: { label: '取消', color: 'primary' }
  496. })
  497. .onOk(() => {
  498. this.deleteContainerData(rowData)
  499. })
  500. } else {
  501. this.$q.notify({
  502. message: '权限不足,请联系管理员',
  503. icon: 'close',
  504. color: 'negative'
  505. })
  506. }
  507. },
  508. async deleteContainerData (rowData) {
  509. try {
  510. const res = await deleteauth(`container/detail/${rowData.id}/`)
  511. this.$q.notify({
  512. message: '删除成功',
  513. color: 'positive'
  514. })
  515. this.get_container_table() // 刷新数据
  516. } catch (err) {
  517. this.$q.notify({
  518. message: '删除失败: ' + err.message,
  519. color: 'negative'
  520. })
  521. }
  522. },
  523. handleRowEdit (rowData) {
  524. if (this.can_edit_detail === true) {
  525. console.log('当前行数据:', rowData)
  526. this.$q
  527. .dialog({
  528. title: '编辑物料信息',
  529. message: `正在编辑 ${rowData.goods_code}`,
  530. prompt: {
  531. model: rowData.goods_qty,
  532. type: 'number',
  533. label: '入库重量',
  534. isValid: (val) => val >= 0 // 验证输入
  535. },
  536. persistent: true
  537. })
  538. .onOk((value) => {
  539. // 调用API更新数据
  540. this.updateContainerData(rowData, value)
  541. })
  542. } else {
  543. this.$q.notify({
  544. message: '权限不足,请联系管理员',
  545. icon: 'close',
  546. color: 'negative'
  547. })
  548. }
  549. },
  550. async updateContainerData (rowData, newValue) {
  551. try {
  552. const res = await putauth(`container/detail/${rowData.id}/`, {
  553. goods_code: rowData.goods_code,
  554. goods_desc: rowData.goods_desc,
  555. goods_weight: 1,
  556. goods_qty: newValue
  557. })
  558. this.$q.notify({
  559. message: '更新成功',
  560. color: 'positive'
  561. })
  562. this.get_container_table() // 刷新数据
  563. } catch (err) {
  564. this.$q.notify({
  565. message: '更新失败: ' + err.message,
  566. color: 'negative'
  567. })
  568. }
  569. },
  570. prepareDialog () {
  571. this.can_edit_detail = false
  572. },
  573. handleEdit () {
  574. console.log('点击', this.can_edit_detail)
  575. if (this.can_edit_detail === false) {
  576. this.user_id = LocalStorage.getItem('login_mode')
  577. this.auth_id = LocalStorage.getItem('auth_edit')
  578. console.log(this.auth_id)
  579. if (this.auth_id === true) {
  580. this.can_edit_detail = true
  581. } else {
  582. this.can_edit_detail = false
  583. }
  584. if (this.user_id === 'Admin') {
  585. this.can_edit_detail = true
  586. }
  587. }
  588. if (this.can_edit_detail === true) {
  589. this.$q.notify({
  590. message: '开始编辑托盘信息',
  591. color: 'positive'
  592. })
  593. } else {
  594. this.$q.notify({
  595. message: '权限不足,请联系管理员',
  596. icon: 'close',
  597. color: 'negative'
  598. })
  599. }
  600. },
  601. handleclick () {
  602. this.getList()
  603. this.get_container_table()
  604. this.getOperationRecord()
  605. this.storage_dialog = true
  606. },
  607. get_container_table () {
  608. var _this = this
  609. getauth(
  610. _this.pathnamecontainer_detail + '?container=' + _this.containerNumber
  611. ).then((res) => {
  612. var data = res.data
  613. this.container_table = data
  614. })
  615. },
  616. getList () {
  617. var _this = this
  618. _this.storage_form = []
  619. _this.results = []
  620. getauth(
  621. _this.pathnamecontainer + '?container=' + _this.containerNumber
  622. ).then((res) => {
  623. var data = res.data
  624. this.storage_form = data.batch_totals
  625. this.results = data.results
  626. })
  627. }
  628. }
  629. }
  630. </script>
  631. <style scoped>
  632. :deep(.q-field__label) {
  633. margin-top: 8px;
  634. align-self: center;
  635. }
  636. :deep(.q-field__control-container) {
  637. padding-left: 50px;
  638. margin-top: -5px;
  639. }
  640. </style>