图书馆综合管理系统
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.

605 lines
21 KiB

6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
  1. <template>
  2. <div class="app-container">
  3. <div class="venue-header dataScreening-header">
  4. <h4><i class="iconfont icon-shuju" />书架总览</h4>
  5. <div class="bookshelf-area">
  6. <!-- <span> {{ floorName }} - {{ regionName }}</span> -->
  7. <router-link :to="{ path: '/check/check/dataScreening', query: {floorTabIndex: floorTabIndex }}">
  8. {{ floorName }}
  9. </router-link>
  10. <span>/</span>
  11. <router-link :to="{ path: '/dataScreening/regions', query: {regionTabIndex: regionTabIndex }}">
  12. {{ regionName }}
  13. </router-link>
  14. <div class="double-click-btn"><span>点击左侧位置返回</span></div>
  15. </div>
  16. <p><i class="iconfont icon-gongsi" />{{ user.fonds.fondsName }}</p>
  17. </div>
  18. <div class="venue-content dataScreening-content">
  19. <crudOperation :permission="permission">
  20. <template v-slot:middle>
  21. <el-select v-model="layerVal" clearable size="small" placeholder="楼层" class="filter-item" style="width: 100px; margin-right: 10px;">
  22. <el-option v-for="item in layerOptions" :key="item.id" :label="item.name" :value="item.id" />
  23. </el-select>
  24. <el-button v-permission="permission.add" class="check-btn" size="mini" @click="toAdd(4)">
  25. <i class="iconfont icon-shengchengpandiandan" />
  26. 书架盘点
  27. </el-button>
  28. </template>
  29. <template v-slot:right>
  30. <el-button :loading="crud.downloadLoading" size="mini" @click="doExport(crud.selections)">
  31. <i class="iconfont icon-daochu" />
  32. 导出
  33. </el-button>
  34. </template>
  35. </crudOperation>
  36. <div class="venue-left">
  37. <div class="container-right tab-content">
  38. <span class="right-top-line" />
  39. <span class="left-bottom-line" />
  40. <ul class="tab-nav">
  41. <li v-for="(item,index) in tabListData" :key="index" :class="{ 'active-tab-nav': tabIndex == index }" @click="changeActiveTab(index)">{{ item.name }}<i /></li>
  42. <!-- 最右侧装饰img -->
  43. <span class="tab-right-img" />
  44. </ul>
  45. <div class="tag-info">
  46. <p class="tag-sort">错序<i class="iconfont icon-zhuangtai2" />1</p>
  47. <p class="tag-place">错架<i class="iconfont icon-zhuangtai2" />1</p>
  48. <p class="tag-all">在架<i class="iconfont icon-zhuangtai2" />20</p>
  49. </div>
  50. <!-- </div> -->
  51. <div class="shelf-top" :style="rowStyle">
  52. <p v-for="(item,index) in reversedRackNum" :key="index" :style="{width: `calc(142px)`}"><span>{{ item + '架' }}</span></p>
  53. </div>
  54. <ul v-loading="listLoading" class="data-shelf-row" :style="rowStyle">
  55. <!-- :class="{ active: i === cellIndex }" -->
  56. <!-- { active: columnIndex === i % rackNum} -->
  57. <li
  58. v-for="(cell,i) in booShelfGrid"
  59. :key="i"
  60. :class="['data-shelf-cell',{'active': isActiveColumn(i)}]"
  61. :style="cellStyle"
  62. @dblclick="handleCellCurrent(cell,i)"
  63. @mouseenter="showPopover(i)"
  64. @mouseleave="hidePopover"
  65. >
  66. <div class="mask" />
  67. <div class="tag-info">
  68. <p class="tag-sort"><i class="iconfont icon-zhuangtai2" />1</p>
  69. <p class="tag-place"><i class="iconfont icon-zhuangtai2" />1</p>
  70. <p class="tag-all"><i class="iconfont icon-zhuangtai2" />20</p>
  71. </div>
  72. <el-popover
  73. v-if="popoverIndex === i"
  74. ref="popover"
  75. :visible="popoverVisible[i]"
  76. width="400"
  77. :style="popoverStyles[i]"
  78. trigger="manual"
  79. >
  80. <div slot="reference" class="popover-content-set">
  81. <div class="tooltip-top">
  82. <h4>本架概况</h4>
  83. <i class="update-time">2024-11-28 09:46</i>
  84. </div>
  85. <ul>
  86. <li><p>架位</p><em class="percentage"><i style="color: #fff;">{{ removeAreaPrefix(cell.gridName) }}</i></em></li>
  87. <li><p>在架</p><em><i>15000</i></em></li>
  88. <li><p>错架</p><em><i>300</i></em> <em class="percentage">2.00%</em></li>
  89. <li><p>错序</p><em><i>0</i></em><em class="percentage">0.00%</em></li>
  90. </ul>
  91. </div>
  92. </el-popover>
  93. </li>
  94. </ul>
  95. </div>
  96. </div>
  97. <div class="venue-right">
  98. <div class="lib-right-item lib-info">
  99. <h4>书架概况</h4>
  100. <ul class="data-right-list">
  101. <li><p>书架</p><span><i>001</i></span></li>
  102. <li><p>规格</p><span><i>双面 6 x 5</i></span></li>
  103. </ul>
  104. </div>
  105. <div class="lib-right-item">
  106. <h4>书架盘点概况</h4>
  107. <div class="refresh-date">2024-11-28 09:46</div>
  108. <ul class="data-right-list">
  109. <li><p>在架</p><span><i>15000</i></span></li>
  110. <li><p>错架</p><span><i>300</i></span> <span class="percentage">2.00%</span></li>
  111. <li><p>错序</p><span><i>0</i></span><span class="percentage">0.00%</span></li>
  112. </ul>
  113. </div>
  114. </div>
  115. </div>
  116. <eForm ref="eform" />
  117. </div>
  118. </template>
  119. <script>
  120. import { FetchInitShelfGridByShelfId, FetchBookShelfDetails } from '@/api/shelf/index'
  121. import crudRegion from '@/api/area/index'
  122. import CRUD, { presenter, header, crud } from '@crud/crud'
  123. import crudOperation from '@crud/CRUD.operation'
  124. import { mapGetters } from 'vuex'
  125. import eForm from './module/form'
  126. export default {
  127. name: 'DataScreening',
  128. components: { crudOperation, eForm },
  129. cruds() {
  130. return CRUD({ title: '架位总览', url: 'api/libraryRegion/initLibraryRegionList', crudMethod: { ...crudRegion }, sort: [], optShow: {
  131. add: false,
  132. edit: false,
  133. del: false,
  134. download: false,
  135. group: false,
  136. reset: false
  137. },
  138. queryOnPresenterCreated: false
  139. })
  140. },
  141. mixins: [presenter(), header(), crud()],
  142. data() {
  143. const _this = this
  144. return {
  145. listLoading: false,
  146. tabIndex: 0,
  147. floorTabIndex: 0,
  148. regionTabIndex: 0,
  149. floorName: null,
  150. regionName: null,
  151. rowType: null,
  152. bookShelfDetails: null,
  153. booShelfGrid: [],
  154. cellInfo: {
  155. gridName: null,
  156. startSortmark: null,
  157. endSortmark: null,
  158. cameraId: null
  159. },
  160. callNumVisible: false,
  161. layerNum: 0,
  162. rackNum: 0,
  163. swiperActiveIndex: 0,
  164. cellIndex: null,
  165. columnIndex: null,
  166. swiperOptionContent: {
  167. slidesPerView: 'auto',
  168. on: {
  169. slideChangeTransitionStart: function() {
  170. _this.cellIndex = null
  171. _this.swiperActiveIndex = this.activeIndex
  172. _this.swiperTitle.slideTo(this.activeIndex, 500, false)
  173. }
  174. }
  175. },
  176. swiperOptionTitle: {
  177. slidesPerView: 'auto',
  178. freeMode: true
  179. },
  180. layerVal: '001排',
  181. layerOptions: [{ id: 1, name: '001排' }],
  182. tabListData: [],
  183. permission: {
  184. add: ['admin', 'floor:add'],
  185. edit: ['admin', 'floor:edit'],
  186. del: ['admin', 'floor:del']
  187. },
  188. popoverIndex: null,
  189. popoverVisible: [],
  190. popoverStyles: [],
  191. activeColumns: {}
  192. }
  193. },
  194. computed: {
  195. ...mapGetters([
  196. 'user',
  197. 'baseApi'
  198. ]),
  199. swiperContent() {
  200. return this.$refs.swiperContent.$el.swiper
  201. },
  202. swiperTitle() {
  203. return this.$refs.swiperTitle.$el.swiper
  204. },
  205. // cellStyle: function() {
  206. // // const h = '100%/' + this.layerNum
  207. // // const w = '100%/' + this.rackNum
  208. // const h = '76px'
  209. // const w = '100%/' + this.rackNum
  210. // return { width: `calc(${w} )`, height: `calc(${h})` }
  211. // },
  212. cellStyle: function() {
  213. const h = '76px'
  214. // const w = '100%/' + this.rackNum
  215. const w = '146px'
  216. return { width: `calc(${w} )`, height: `calc(${h})` }
  217. },
  218. rowStyle: function() {
  219. const w = 146 * this.rackNum + 'px'
  220. return { width: `calc(${w})` }
  221. },
  222. reversedRackNum() {
  223. if (this.booShelfGrid && this.booShelfGrid.length > 0) {
  224. if (parseInt(this.booShelfGrid[0].gridShelf) === this.rackNum) {
  225. return Array.from({ length: this.rackNum }, (_, i) => this.rackNum - i).map(x => x.toString())
  226. } else {
  227. return Array.from({ length: this.rackNum }, (_, i) => i + 1).map(x => x.toString())
  228. }
  229. } else {
  230. return []
  231. }
  232. }
  233. },
  234. async created() {
  235. if (localStorage.getItem('dataScreenFloorTableIndex')) {
  236. this.floorTabIndex = localStorage.getItem('dataScreenFloorTableIndex')
  237. }
  238. if (localStorage.getItem('dataScreenRegionTableIndex')) {
  239. this.regionTabIndex = localStorage.getItem('dataScreenRegionTableIndex')
  240. }
  241. if (localStorage.getItem('dataScreenRegion')) {
  242. const dataScreenRegion = JSON.parse(localStorage.getItem('dataScreenRegion'))
  243. this.floorName = dataScreenRegion.floorName
  244. this.regionName = dataScreenRegion.regionName
  245. this.rowType = dataScreenRegion.rowType
  246. // 单面/双面
  247. this.tabListData = dataScreenRegion.rowType === 1
  248. ? dataScreenRegion.toward === 1
  249. ? [{ name: 'A面' }]
  250. : [{ name: 'B面' }]
  251. : [{ name: 'A面' }, { name: 'B面' }]
  252. this.tabIndex = this.$route.query.tabIndex ? this.$route.query.tabIndex : 0
  253. FetchBookShelfDetails({ 'shelfId': dataScreenRegion.id }).then(res => {
  254. this
  255. this.layerNum = res.shelfFloor
  256. this.rackNum = res.shelfShelf
  257. this.bookShelfDetails = res
  258. if (this.$route.query.tabIndex) {
  259. this.getInitShelfGridByShelfId(this.$route.query.tabIndex + 1)
  260. } else {
  261. this.getInitShelfGridByShelfId(this.bookShelfDetails.toward)
  262. }
  263. }).catch(() => {
  264. })
  265. }
  266. },
  267. methods: {
  268. [CRUD.HOOK.beforeRefresh]() {
  269. },
  270. [CRUD.HOOK.afterRefresh](crud) {
  271. },
  272. // 提交前的验证
  273. [CRUD.HOOK.afterValidateCU](crud) {
  274. return true
  275. },
  276. toAdd(type) {
  277. this.$refs.eform.formVisible = true
  278. this.$refs.eform.form.shelfId = this.bookShelfDetails.id
  279. this.$refs.eform.form.stockRegion = this.floorName + this.regionName + this.bookShelfDetails.shelfName
  280. this.$refs.eform.setData(type)
  281. },
  282. removeAreaPrefix(gridNames) {
  283. return gridNames.replace(/\d*区|\d*层/g, '')
  284. },
  285. getInitShelfGridByShelfId(toward) {
  286. this.listLoading = true
  287. // rowType 1 单 2 双
  288. // toward 1 A面 2 B面
  289. // shelfType 1 '始终最左边为第1架(S型排架)'
  290. // shelfType 2 'A面最左为第1架(B面最左为最后1架)'
  291. // shelfType 3 'B面最左为第1架(A面最左为最后1架)'
  292. // floorType 1 '最顶层为第一层(从上至下)'
  293. // floorType 2 '最底层为第一层(从下至上)'
  294. FetchInitShelfGridByShelfId({ 'shelfId': this.bookShelfDetails.id, 'toward': toward }).then(res => {
  295. const sortFunction = toward === 1 ? {
  296. 1: { 1: 'sortBookshelvesLeftTop', 2: 'sortBookshelvesLeftBottom' },
  297. 2: { 1: 'sortBookshelvesLeftTop', 2: 'sortBookshelvesLeftBottom' },
  298. 3: { 1: 'sortBookshelvesRightTop', 2: 'sortBookshelvesRightBottom' }
  299. } : {
  300. 1: { 1: 'sortBookshelvesLeftTop', 2: 'sortBookshelvesLeftBottom' },
  301. 2: { 1: 'sortBookshelvesRightTop', 2: 'sortBookshelvesRightBottom' },
  302. 3: { 1: 'sortBookshelvesLeftTop', 2: 'sortBookshelvesLeftBottom' }
  303. }
  304. const shelfType = this.bookShelfDetails.shelfType
  305. const floorType = this.bookShelfDetails.floorType
  306. const sortMethod = sortFunction[shelfType][floorType]
  307. this.booShelfGrid = this[sortMethod](res)
  308. console.log('booShelfGrid', this.booShelfGrid)
  309. this.popoverVisible = Array(this.booShelfGrid.length).fill(false)
  310. setTimeout(() => {
  311. this.listLoading = false
  312. }, 1000)
  313. }).catch(() => {
  314. })
  315. },
  316. // 最左为第一架, 最顶层为第一层 从上往下
  317. sortBookshelvesLeftTop(data) {
  318. const sortedData = []
  319. const maxFloor = Math.max(...data.map(item => parseInt(item.gridFloor)))
  320. const maxShelf = Math.max(...data.map(item => parseInt(item.gridShelf.slice(-1))))
  321. for (let i = 1; i <= maxFloor; i++) {
  322. for (let j = 1; j <= maxShelf; j++) {
  323. const currentShelf = data.find(item => parseInt(item.gridFloor) === i && parseInt(item.gridShelf.slice(-1)) === j)
  324. if (currentShelf) {
  325. sortedData.push(currentShelf)
  326. }
  327. }
  328. }
  329. return sortedData
  330. },
  331. // 最右为第一架,最左为最后一架, 最顶层为第一层 从上往下
  332. sortBookshelvesRightTop(data) {
  333. const sortedData = []
  334. // 获取最大的楼层数
  335. const maxFloor = Math.max(...data.map(item => parseInt(item.gridFloor)))
  336. const maxShelf = Math.max(...data.map(item => parseInt(item.gridShelf.match(/\d+$/)[0])))
  337. for (let i = 1; i <= maxFloor; i++) {
  338. // 从最大的书架层数开始,向下排序
  339. for (let j = maxShelf; j >= 1; j--) {
  340. const currentShelf = data.find(item => parseInt(item.gridFloor) === i && parseInt(item.gridShelf.match(/\d+$/)[0]) === j)
  341. if (currentShelf) {
  342. sortedData.push(currentShelf)
  343. }
  344. }
  345. }
  346. return sortedData
  347. },
  348. // 最左为第一架, 最底层为第一层 从下往上
  349. sortBookshelvesLeftBottom(data) {
  350. const sortedData = []
  351. // 获取最大的楼层数
  352. const maxFloor = Math.max(...data.map(item => parseInt(item.gridFloor)))
  353. // 获取最大的书架层数
  354. const maxShelf = Math.max(...data.map(item => parseInt(item.gridShelf.slice(-1))))
  355. for (let i = maxFloor; i >= 1; i--) {
  356. for (let j = 1; j <= maxShelf; j++) {
  357. const currentShelf = data.find(item => parseInt(item.gridFloor) === i && parseInt(item.gridShelf.slice(-1)) === j)
  358. if (currentShelf) {
  359. sortedData.push(currentShelf)
  360. }
  361. }
  362. }
  363. return sortedData
  364. },
  365. // 最左为最后一架, 最底层为第一层 从下往上
  366. sortBookshelvesRightBottom(data) {
  367. const sortedData = []
  368. // 获取最大的楼层数
  369. const maxFloor = Math.max(...data.map(item => parseInt(item.gridFloor)))
  370. const maxShelfPerFloor = data.map(item => parseInt(item.gridShelf.match(/\d+$/)[0]))
  371. .reduce((acc, curr, index, arr) => {
  372. const floor = parseInt(data[index].gridFloor)
  373. if (!acc[floor]) acc[floor] = 1
  374. if (acc[floor] < curr) acc[floor] = curr
  375. return acc
  376. }, {})
  377. // 从最大的楼层开始向下遍历
  378. for (let i = maxFloor; i >= 1; i--) {
  379. // 从最大的书架编号开始向左遍历
  380. for (let j = maxShelfPerFloor[i] || 1; j >= 1; j--) {
  381. const currentShelf = data.find(item => parseInt(item.gridFloor) === i && parseInt(item.gridShelf.match(/\d+$/)[0]) === j)
  382. if (currentShelf) {
  383. sortedData.push(currentShelf)
  384. }
  385. }
  386. }
  387. return sortedData
  388. },
  389. changeActiveTab(index) {
  390. this.tabIndex = index
  391. this.cellIndex = null
  392. this.getInitShelfGridByShelfId(index + 1)
  393. },
  394. handleCellCurrent(item, index) {
  395. console.log('index', index)
  396. console.log('item', item)
  397. this.cellIndex = index
  398. this.cellInfo = {
  399. id: item.id,
  400. gridName: item.gridName,
  401. gridRow: item.gridRow,
  402. gridShelf: item.gridShelf,
  403. shelfId: item.shelfId,
  404. floorName: this.floorName,
  405. regionName: this.regionName,
  406. rowType: this.rowType,
  407. toward: item.toward
  408. }
  409. this.currentShelfAllGrid = this.booShelfGrid.filter(gird => gird.gridShelf === item.gridShelf)
  410. this.handleToGrids(this.cellInfo, this.currentShelfAllGrid)
  411. },
  412. handleToGrids(data, currentShelfAllGrid) {
  413. this.$router.push({ path: '/dataScreening/gird' })
  414. localStorage.setItem('dataScreenShelf', JSON.stringify(data))
  415. localStorage.setItem('dataScreenShelfAllGrid', JSON.stringify(currentShelfAllGrid))
  416. localStorage.setItem('dataScreenShelfTableIndex', this.tabIndex)
  417. },
  418. showPopover(index) {
  419. this.popoverIndex = index
  420. if (!this.popoverVisible[index]) {
  421. this.$set(this.popoverVisible, index, true)
  422. }
  423. const lastColumnIndexes = []
  424. const secondLastColumnIndexes = []
  425. for (let i = 0; i < this.booShelfGrid.length; i++) {
  426. const columnIndex = i % this.rackNum
  427. // 如果是最后一列(第5列)
  428. console.log('this.booShelfGrid.length', this.booShelfGrid.length)
  429. if (this.rackNum !== 1) {
  430. if (columnIndex === this.rackNum - 1) {
  431. lastColumnIndexes.push(i)
  432. // 更新最后一列的样式
  433. this.$set(this.popoverStyles, i, { position: 'absolute', left: '-180px', top: '20px' })
  434. }
  435. } else {
  436. this.$set(this.popoverStyles, i, { position: 'absolute', left: '60px', top: '20px' })
  437. }
  438. // 如果是倒数第二列(第4列)
  439. if (columnIndex === this.rackNum - 2) {
  440. secondLastColumnIndexes.push(i)
  441. // 更新倒数第二列的样式
  442. // this.$set(this.popoverStyles, i, { position: 'absolute', left: '-20px', top: '20px' })
  443. }
  444. }
  445. const columnIndex = index % this.rackNum
  446. this.activeColumns[columnIndex] = true
  447. },
  448. isActiveColumn(index) {
  449. const columnIndex = index % this.rackNum
  450. return this.activeColumns[columnIndex] === true
  451. },
  452. hidePopover() {
  453. this.activeColumns = {}
  454. this.popoverIndex = null
  455. this.popoverVisible.forEach((isVisible, index) => {
  456. if (isVisible) {
  457. this.$set(this.popoverVisible, index, false)
  458. }
  459. })
  460. }
  461. }
  462. }
  463. </script>
  464. <style lang="scss" scoped>
  465. .container-right{
  466. min-height: calc(100vh - 232px) !important;
  467. }
  468. .data-right-list {
  469. padding-top: 10px;
  470. li{
  471. display: flex;
  472. justify-content: flex-start;
  473. align-items: center;
  474. line-height: 36px;
  475. p{
  476. width: 80px;
  477. font-weight: bold;
  478. text-align: right;
  479. }
  480. span{
  481. width: 140px;
  482. display: block;
  483. text-align: right;
  484. i{
  485. font-style: normal;
  486. font-weight: bold;
  487. padding: 0 10px;
  488. color: #0348f3;
  489. }
  490. &.percentage{
  491. width: auto;
  492. }
  493. }
  494. }
  495. }
  496. .tag-info{
  497. position: absolute;
  498. right: 20px;
  499. top: 8px;
  500. p{
  501. margin-left: 20px;
  502. }
  503. }
  504. .shelf-top{
  505. display: flex;
  506. justify-content: space-around;
  507. align-items: center;
  508. text-align: center;
  509. margin-top: 20px;
  510. p{
  511. font-size: 14px;
  512. color: #fff;
  513. height: 36px;
  514. line-height: 30px;
  515. background: url('~@/assets/images/shelf04.png') no-repeat center top;
  516. background-size: auto 100%;
  517. }
  518. }
  519. .data-shelf-row{
  520. display: flex;
  521. flex: 1;
  522. flex-wrap: wrap;
  523. text-align: center;
  524. // margin-top: -2px;
  525. // padding-top: 40px;
  526. .data-shelf-cell{
  527. position: relative;
  528. font-size: 14px;
  529. color: #0C0E1E;
  530. background: url('~@/assets/images/shelf02.png') repeat-x left top;
  531. background-size: 20% 100%;
  532. border-radius: 3px;
  533. cursor: pointer;
  534. .tag-info{
  535. right: 12px;
  536. top: 44px;
  537. p{
  538. margin-left: 10px;
  539. }
  540. }
  541. &::before{
  542. content: "";
  543. position: absolute;
  544. left: 0;
  545. top: 0;
  546. width: 6px;
  547. height: 76px;
  548. background: url('~@/assets/images/shelf01.png') no-repeat left top;
  549. background-size: 100% 100%;
  550. }
  551. &::after{
  552. content: "";
  553. position: absolute;
  554. right: -4px;
  555. top: 0;
  556. width: 6px;
  557. height: 76px;
  558. background: url('~@/assets/images/shelf01.png') no-repeat left top;
  559. background-size: 100% 100%;
  560. }
  561. .cell-name{
  562. display: block;
  563. width: 100%;
  564. line-height: 76px;
  565. }
  566. &.active{
  567. .mask{
  568. position: absolute;
  569. top: 0;
  570. left: 6px;
  571. width: 98%;
  572. height: 100%;
  573. background-color: rgba(255,0,0,.3);
  574. }
  575. }
  576. }
  577. }
  578. ::v-deep .data-shelf-row span.el-popover__reference-wrapper{
  579. position: absolute !important;
  580. left: 60% !important;
  581. top: 48px !important;
  582. width: 250px;
  583. // height: 210px;
  584. background:rgba(0,0,0,1);
  585. color: #fff;
  586. border-radius: 6px;
  587. z-index: 99999999;
  588. }
  589. </style>