containerlist.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  1. <!-- eslint-disable camelcase -->
  2. <template>
  3. <div style="margin-top: 42px">
  4. <transition appear enter-active-class="animated fadeIn">
  5. <q-table
  6. class="my-sticky-header-column-table shadow-24"
  7. :data="table_list"
  8. row-key="id"
  9. :separator="separator"
  10. :loading="loading"
  11. :columns="columns"
  12. hide-bottom
  13. :pagination.sync="pagination"
  14. no-data-label="No data"
  15. no-results-label="No data you want"
  16. :table-style="{ height: height }"
  17. flat
  18. bordered
  19. >
  20. <template v-slot:header-cell="props">
  21. <q-th :props="props" @dblclick="handleHeaderDblClick(props.col)">
  22. <!-- 为特定列添加下拉选择器 -->
  23. <template v-if="['bound_department'].includes(props.col.name)">
  24. <q-select
  25. dense
  26. outlined
  27. v-model="filterModels[props.col.name]"
  28. :options="getFilterOptions(props.col.name)"
  29. option-label="label"
  30. option-value="value"
  31. emit-value
  32. map-options
  33. clearable
  34. @input="handleFilterChange"
  35. style="min-width: 120px"
  36. >
  37. <template v-slot:prepend>
  38. <span class="text-caption">{{ props.col.label }}</span>
  39. </template>
  40. </q-select>
  41. </template>
  42. <template v-else>
  43. {{ props.col.label }}
  44. </template>
  45. </q-th>
  46. </template>
  47. <template v-slot:top>
  48. <q-btn-group push>
  49. <q-btn :label="'新增'" icon="add" @click="add()"></q-btn>
  50. <q-btn :label="$t('refresh')" icon="refresh" @click="reFresh()">
  51. <q-tooltip
  52. content-class="bg-amber text-black shadow-4"
  53. :offset="[10, 10]"
  54. content-style="font-size: 12px"
  55. >{{ $t("refreshtip") }}</q-tooltip
  56. >
  57. </q-btn>
  58. </q-btn-group>
  59. <q-space />
  60. <div class="flex items-center">
  61. <q-btn-group push class="q-ml-md"> </q-btn-group>
  62. <q-input
  63. outlined
  64. rounded
  65. dense
  66. debounce="300"
  67. color="primary"
  68. v-model="filter"
  69. :placeholder="$t('search')"
  70. @input="getSearchList()"
  71. @keyup.enter="getSearchList()"
  72. >
  73. <template v-slot:append>
  74. <q-icon name="search" @click="getSearchList()" />
  75. </template>
  76. </q-input>
  77. </div>
  78. </template>
  79. <template v-slot:body="props">
  80. <q-tr :props="props">
  81. <template v-if="props.row.id === editid">
  82. <q-td key="detail" :props="props">
  83. <q-btn
  84. round
  85. flat
  86. push
  87. color="black"
  88. icon="description"
  89. @click="detailData(props.row)"
  90. >
  91. <q-tooltip
  92. content-class="bg-amber text-black shadow-4"
  93. :offset="[10, 10]"
  94. content-style="font-size: 12px"
  95. >{{ "查看批次详情" }}</q-tooltip
  96. >
  97. </q-btn>
  98. </q-td>
  99. </template>
  100. <template v-else-if="props.row.id !== editid">
  101. <q-td key="detail" :props="props" style="max-width: 10px">
  102. <q-btn
  103. round
  104. flat
  105. push
  106. color="black"
  107. icon="description"
  108. @click="detailData(props.row)"
  109. >
  110. <q-tooltip
  111. content-class="bg-amber text-black shadow-4"
  112. :offset="[10, 10]"
  113. content-style="font-size: 12px"
  114. >{{ "查看批次详情" }}</q-tooltip
  115. >
  116. </q-btn>
  117. <q-btn
  118. icon="print"
  119. flat
  120. v-print="getPrintConfig()"
  121. @click="setCurrentBatch(props.row.container_code)"
  122. >
  123. <q-tooltip>打印条码</q-tooltip>
  124. </q-btn>
  125. </q-td>
  126. </template>
  127. <template v-if="props.row.id === editid">
  128. <q-td key="container_code" :props="props">
  129. <q-input
  130. dense
  131. outlined
  132. square
  133. v-model="editFormData.container_code"
  134. :label="'托盘编码'"
  135. autofocus
  136. :rules="[(val) => (val && val.length > 0) || error1]"
  137. />
  138. </q-td>
  139. </template>
  140. <template v-else-if="props.row.id !== editid">
  141. <q-td key="container_code" :props="props">{{
  142. props.row.container_code
  143. }}</q-td>
  144. </template>
  145. <template v-if="props.row.id === editid">
  146. <q-td key="current_location" :props="props">
  147. <q-input
  148. dense
  149. outlined
  150. square
  151. v-model="editFormData.current_location"
  152. :label="'当前位置'"
  153. :rules="[(val) => (val && val.length > 0) || error2]"
  154. />
  155. </q-td>
  156. </template>
  157. <template v-else-if="props.row.id !== editid">
  158. <q-td key="current_location" :props="props">{{
  159. props.row.current_location
  160. }}</q-td>
  161. </template>
  162. <template v-if="props.row.id === editid">
  163. <q-td key="target_location" :props="props">
  164. <q-input
  165. dense
  166. outlined
  167. square
  168. v-model="editFormData.target_location"
  169. :label="'目标位置'"
  170. :rules="[(val) => (val && val.length > 0) || error2]"
  171. />
  172. </q-td>
  173. </template>
  174. <template v-else-if="props.row.id !== editid">
  175. <q-td key="target_location" :props="props">{{
  176. props.row.target_location
  177. }}</q-td>
  178. </template>
  179. <template v-if="props.row.id === editid">
  180. <q-td key="status" :props="props">
  181. <q-input
  182. dense
  183. outlined
  184. square
  185. v-model="editFormData.status"
  186. :label="'状态'"
  187. :rules="[(val) => (val && val.length > 0) || error2]"
  188. />
  189. </q-td>
  190. </template>
  191. <template v-else-if="props.row.id !== editid">
  192. <q-td key="status" :props="props">{{ props.row.status }}</q-td>
  193. </template>
  194. <template v-if="props.row.id === editid">
  195. <q-td key="last_operation" :props="props">
  196. <q-input
  197. dense
  198. outlined
  199. square
  200. v-model="editFormData.last_operation"
  201. :label="'最后操作时间'"
  202. :rules="[(val) => (val && val.length > 0) || error4]"
  203. />
  204. </q-td>
  205. </template>
  206. <template v-else-if="props.row.id !== editid">
  207. <q-td key="last_operation" :props="props">{{
  208. props.row.last_operation
  209. }}</q-td>
  210. </template>
  211. </q-tr>
  212. </template>
  213. </q-table>
  214. </transition>
  215. <template>
  216. <div v-show="max !== 0" class="q-pa-lg flex flex-center">
  217. <div>{{ total }}</div>
  218. <q-pagination
  219. v-model="current"
  220. color="black"
  221. :max="max"
  222. :max-pages="6"
  223. boundary-links
  224. @click="
  225. getSearchList(current);
  226. paginationIpt = current;
  227. "
  228. />
  229. <div>
  230. <input
  231. v-model="paginationIpt"
  232. @blur="changePageEnter"
  233. @keyup.enter="changePageEnter"
  234. style="width: 60px; text-align: center"
  235. />
  236. </div>
  237. </div>
  238. <div v-show="max === 0" class="q-pa-lg flex flex-center">
  239. <q-btn flat push color="dark" :label="$t('no_data')"></q-btn>
  240. </div>
  241. </template>
  242. <div id="printBarcode" class="print-area">
  243. <div class="q-pa-md text-center" style="flex: none">
  244. <svg ref="barcodeElement" style="width: 100%; height: auto"></svg>
  245. </div>
  246. </div>
  247. <containercard
  248. v-if="showInventoryDetails"
  249. ref="containercard"
  250. :container-number="select_container_number"
  251. :container-code="select_container_code"
  252. :key="select_container_number"
  253. />
  254. </div>
  255. </template>
  256. <script>
  257. import { getauth, postauth } from 'boot/axios_request'
  258. import containercard from 'components/containercard.vue'
  259. import JsBarcode from 'jsbarcode'
  260. import { LocalStorage } from 'quasar'
  261. export default {
  262. name: 'PageContainerlist',
  263. components: {
  264. containercard
  265. },
  266. data () {
  267. return {
  268. goods_code: '',
  269. goods_desc: '',
  270. openid: '',
  271. login_name: '',
  272. authin: '0',
  273. searchUrl: '',
  274. pathname: 'container/list/',
  275. detailpathname: 'container/detail/',
  276. pathname_previous: '',
  277. pathname_next: '',
  278. separator: 'cell',
  279. loading: false,
  280. height: '',
  281. table_list: [],
  282. columns: [
  283. { name: 'detail', label: '详情', field: 'detail', align: 'center' },
  284. {
  285. name: 'container_code',
  286. required: true,
  287. label: '托盘编码',
  288. align: 'center',
  289. field: 'container_code'
  290. },
  291. {
  292. name: 'current_location',
  293. label: '当前位置',
  294. field: 'current_location',
  295. align: 'center'
  296. },
  297. {
  298. name: 'target_location',
  299. label: '目标位置',
  300. field: 'target_location',
  301. align: 'center'
  302. },
  303. { name: 'status', label: '托盘状态', field: 'status', align: 'center' },
  304. {
  305. name: 'last_operation',
  306. label: '上次操作时间',
  307. field: 'last_operation',
  308. align: 'center'
  309. }
  310. ],
  311. filter: '',
  312. pagination: {
  313. page: 1,
  314. rowsPerPage: 11
  315. },
  316. editid: 0,
  317. editFormData: {},
  318. detailForm: false,
  319. activeTab: 'tab1',
  320. table_detail: [
  321. {
  322. container: {
  323. container_code: '',
  324. current_location: ''
  325. },
  326. batch: {
  327. bound_number: '',
  328. goods_desc: '',
  329. goods_weight: '',
  330. goods_qty: '',
  331. creater: ''
  332. }
  333. }
  334. ],
  335. operate_detail: [],
  336. newValue: '',
  337. error1: this.$t('goods.view_goodslist.error1'),
  338. current: 1,
  339. max: 0,
  340. total: 0,
  341. paginationIpt: 1,
  342. onlyread: true,
  343. currentBarcode: '',
  344. select_container_number: 0,
  345. select_container_code: 0,
  346. showInventoryDetails: false,
  347. filterModels: {
  348. bound_department: null
  349. },
  350. filterdata: {},
  351. activeSearchField: '',
  352. activeSearchLabel: ''
  353. }
  354. },
  355. methods: {
  356. // 处理过滤变化
  357. handleFilterChange () {
  358. this.pagination.page = 1
  359. this.getSearchList(1)
  360. },
  361. getFilterOptions (columnName) {
  362. switch (columnName) {
  363. case 'type':
  364. return [
  365. { label: '生产入库', value: 1 },
  366. { label: '采购入库', value: 2 },
  367. { label: '其他入库', value: 3 },
  368. { label: '调拨入库', value: 4 }
  369. ]
  370. case 'bound_status':
  371. return [
  372. { label: '待审核', value: 0 },
  373. { label: '确认无误', value: 1 }
  374. ]
  375. case 'bound_department':
  376. return this.bound_department_list
  377. default:
  378. return []
  379. }
  380. },
  381. handleHeaderDblClick (column) {
  382. // 排除不需要搜索的列
  383. if (['detail', 'action'].includes(column.name)) return
  384. this.activeSearchField = column.field
  385. this.activeSearchLabel = column.label
  386. // 弹出搜索对话框
  387. this.$q
  388. .dialog({
  389. title: `搜索${column.label}`,
  390. message: `请输入${column.label}的搜索条件`,
  391. prompt: {
  392. model: '',
  393. type: 'text'
  394. },
  395. cancel: true,
  396. persistent: true
  397. })
  398. .onOk((data) => {
  399. // 执行搜索
  400. this.executeColumnSearch(column.field, data)
  401. })
  402. .onCancel(() => {
  403. this.activeSearchField = ''
  404. this.activeSearchLabel = ''
  405. })
  406. },
  407. // 执行列搜索
  408. executeColumnSearch (field, value) {
  409. // 构建搜索参数
  410. if (
  411. field === 'type' ||
  412. field === 'audit_status' ||
  413. field === 'save_status' ||
  414. field === 'bound_status'
  415. ) {
  416. const searchParams = {
  417. [field]: value
  418. }
  419. // 清除其他搜索条件
  420. this.filter = ''
  421. this.date_range = ''
  422. // 执行搜索
  423. this.getList({
  424. ...searchParams,
  425. page: 1
  426. })
  427. // 重置激活的搜索字段
  428. this.activeSearchField = ''
  429. this.activeSearchLabel = ''
  430. } else {
  431. const searchParams = {
  432. [field + '__icontains']: value
  433. }
  434. // 清除其他搜索条件
  435. this.filter = ''
  436. this.date_range = ''
  437. // 执行搜索
  438. this.getList({
  439. ...searchParams,
  440. page: 1
  441. })
  442. this.filterdata = searchParams
  443. this.$q.notify({
  444. message: `已搜索 ${this.activeSearchLabel} 含有 "${value}" 的结果`,
  445. icon: 'search',
  446. color: 'positive'
  447. })
  448. // // 重置激活的搜索字段
  449. this.activeSearchField = ''
  450. this.activeSearchLabel = ''
  451. }
  452. },
  453. setCurrentBatch (item) {
  454. this.currentBarcode = item || ''
  455. },
  456. getPrintConfig () {
  457. this.generateBarcode()
  458. return {
  459. id: 'printBarcode'
  460. }
  461. },
  462. generateBarcode () {
  463. this.$refs.barcodeElement.innerHTML = ''
  464. try {
  465. JsBarcode(this.$refs.barcodeElement, this.currentBarcode, {
  466. format: 'CODE128',
  467. displayValue: true,
  468. fontSize: 16,
  469. height: 60,
  470. margin: 10
  471. })
  472. } catch (error) {
  473. console.error('条码生成失败:', error)
  474. }
  475. },
  476. add () {
  477. postauth('/container/list/', {})
  478. .then((res) => {
  479. this.$q.notify({
  480. message: '添加成功',
  481. icon: 'done',
  482. color: 'positive'
  483. })
  484. this.reFresh()
  485. })
  486. .catch((err) => {
  487. this.$q.notify({
  488. message: err.detail,
  489. icon: 'close',
  490. color: 'negative'
  491. })
  492. })
  493. },
  494. getList (params = {}) {
  495. var _this = this
  496. _this.loading = true
  497. // 合并基础参数
  498. const baseParams = {
  499. page: _this.current,
  500. page_size: _this.pagination.rowsPerPage
  501. }
  502. // 创建URLSearchParams处理参数
  503. const queryParams = new URLSearchParams({
  504. ...baseParams,
  505. ...params
  506. })
  507. console.log(queryParams)
  508. // 过滤空值参数
  509. Array.from(queryParams.entries()).forEach(([key, value]) => {
  510. if (value === '' || value === null || value === undefined) {
  511. queryParams.delete(key)
  512. }
  513. })
  514. getauth(`${_this.pathname}?${queryParams}`)
  515. .then((res) => {
  516. _this.table_list = res.results
  517. _this.total = res.count
  518. _this.max = Math.ceil(res.count / _this.pagination.rowsPerPage) || 0
  519. _this.pathname_previous = res.previous
  520. _this.pathname_next = res.next
  521. })
  522. .catch((err) => {
  523. _this.$q.notify({
  524. message: err.detail,
  525. icon: 'close',
  526. color: 'negative'
  527. })
  528. })
  529. .finally(() => {
  530. _this.loading = false
  531. })
  532. },
  533. changePageEnter () {
  534. if (Number(this.paginationIpt) < 1) {
  535. this.current = 1
  536. this.paginationIpt = 1
  537. } else if (Number(this.paginationIpt) > this.max) {
  538. this.current = this.max
  539. this.paginationIpt = this.max
  540. } else {
  541. this.current = Number(this.paginationIpt)
  542. }
  543. this.getSearchList(this.current)
  544. },
  545. // 带搜索条件加载
  546. getSearchList (page = 1) {
  547. this.current = page
  548. this.paginationIpt = page
  549. // 构建过滤参数
  550. const filterParams = {}
  551. for (const [key, value] of Object.entries(this.filterModels)) {
  552. if (value !== null && value !== '') {
  553. filterParams[key] = value
  554. }
  555. }
  556. this.getList({
  557. number__icontains: this.filter,
  558. document_date__range: this.date_range,
  559. ...filterParams ,// 添加过滤条件
  560. ...this.filterdata, // 添加其他过滤条件
  561. })
  562. },
  563. getListPrevious () {
  564. var _this = this
  565. if (LocalStorage.has('auth')) {
  566. getauth(_this.pathname_previous, {})
  567. .then((res) => {
  568. _this.table_list = res.results
  569. _this.pathname_previous = res.previous
  570. _this.pathname_next = res.next
  571. })
  572. .catch((err) => {
  573. _this.$q.notify({
  574. message: err.detail,
  575. icon: 'close',
  576. color: 'negative'
  577. })
  578. })
  579. } else {
  580. }
  581. },
  582. getListNext () {
  583. var _this = this
  584. if (LocalStorage.has('auth')) {
  585. getauth(_this.pathname_next, {})
  586. .then((res) => {
  587. _this.table_list = res.results
  588. _this.pathname_previous = res.previous
  589. _this.pathname_next = res.next
  590. })
  591. .catch((err) => {
  592. _this.$q.notify({
  593. message: err.detail,
  594. icon: 'close',
  595. color: 'negative'
  596. })
  597. })
  598. }
  599. },
  600. reFresh () {
  601. var _this = this
  602. this.filterdata = {}
  603. this.filterModels = {
  604. bound_department: null
  605. }
  606. _this.getSearchList()
  607. },
  608. detailData (e) {
  609. var _this = this
  610. _this.detailid = e.id
  611. console.log('detail查询的id是:', _this.detailid)
  612. getauth(
  613. _this.detailpathname + '?status__lte=2&container=' + _this.detailid
  614. )
  615. .then((res) => {
  616. if (res.results.length === 0) {
  617. _this.table_detail = [
  618. {
  619. container: {
  620. container_code: '',
  621. current_location: ''
  622. },
  623. batch: {
  624. bound_number: '',
  625. goods_desc: '',
  626. goods_weight: '',
  627. goods_qty: '',
  628. creater: ''
  629. }
  630. }
  631. ]
  632. _this.$q.notify({
  633. message: '该托盘暂无物料信息',
  634. icon: 'info',
  635. color: 'positive'
  636. })
  637. return
  638. }
  639. _this.showInventoryDetails = true
  640. _this.select_container_number = e.id
  641. _this.select_container_code = e.container_code
  642. _this.$refs.containercard.handleclick()
  643. })
  644. .catch((err) => {
  645. _this.$q.notify({
  646. message: err.detail,
  647. icon: 'close',
  648. color: 'negative'
  649. })
  650. })
  651. }
  652. },
  653. created () {
  654. var _this = this
  655. if (LocalStorage.has('openid')) {
  656. _this.openid = LocalStorage.getItem('openid')
  657. } else {
  658. _this.openid = ''
  659. LocalStorage.set('openid', '')
  660. }
  661. if (LocalStorage.has('login_name')) {
  662. _this.login_name = LocalStorage.getItem('login_name')
  663. } else {
  664. _this.login_name = ''
  665. LocalStorage.set('login_name', '')
  666. }
  667. if (LocalStorage.has('auth')) {
  668. _this.authin = '1'
  669. _this.getList()
  670. } else {
  671. _this.authin = '0'
  672. }
  673. },
  674. mounted () {
  675. var _this = this
  676. if (_this.$q.platform.is.electron) {
  677. _this.height = String(_this.$q.screen.height - 290) + 'px'
  678. } else {
  679. _this.height = _this.$q.screen.height - 290 + '' + 'px'
  680. }
  681. },
  682. updated () {},
  683. destroyed () {}
  684. }
  685. </script>
  686. <style scoped>
  687. /* 添加在 <style> 中 */
  688. .q-date__calendar-item--selected {
  689. transition: all 0.3s ease;
  690. background-color: #1976d2 !important;
  691. }
  692. .q-date__range {
  693. background-color: rgba(25, 118, 210, 0.1);
  694. }
  695. </style>