bak.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. <template>
  2. <view class="page-wrap">
  3. <!-- <cu-custom :backUrl="'/pages/index/index'" :isBack="true" bgColor="bg-gradual-blue">
  4. <block slot="content">库存列表</block>
  5. </cu-custom> -->
  6. <view class="search-panel">
  7. <u-search :show-action="false" v-model="searchForm.tradeName" placeholder="搜索商品名称"
  8. @search="refreshList" @clear="refreshList"></u-search>
  9. <view class="filter-row">
  10. <view class="filter-item" @tap="openTypePicker">
  11. <text class="filter-label">入库类型</text>
  12. <text class="filter-value">{{ searchForm.wareHouseTypeName || '全部' }}</text>
  13. </view>
  14. <view class="filter-item" @tap="openSupplierPicker">
  15. <text class="filter-label">供应商</text>
  16. <text class="filter-value">{{ searchForm.supplierName || '全部' }}</text>
  17. </view>
  18. </view>
  19. <view class="action-row">
  20. <el-button class="action-button" type="primary" @click="refreshList">查询</el-button>
  21. <el-button class="action-button" plain @click="resetSearch">重置</el-button>
  22. </view>
  23. </view>
  24. <view class="summary-row">
  25. <text>共 {{ tablePage.total }} 条库存</text>
  26. <text v-if="loading">加载中...</text>
  27. </view>
  28. <view v-if="!loading && dataList.length === 0" class="empty-state">暂无库存数据</view>
  29. <view v-for="(item, index) in dataList" :key="item.id || item.tradeName + item.wareHouseType + index"
  30. class="stock-card">
  31. <view class="card-head">
  32. <view class="goods-name">{{ item.tradeName || '-' }}</view>
  33. <view class="type-badge">{{ item.wareHouseTypeName || '-' }}</view>
  34. </view>
  35. <view class="stock-main">
  36. <view class="stock-number">
  37. <text class="number-label">剩余量:</text>
  38. <text class="number-value">{{ formatNumber(item.tradeNumber) }}</text>
  39. <!-- <text class="number-unit">{{ item.company || '' }}</text> -->
  40. </view>
  41. <view class="stock-total">
  42. <text>总量: {{ formatNumber(item.allNumber) }}</text>
  43. <text>单位: {{ item.company || '-' }}</text>
  44. </view>
  45. </view>
  46. <view class="metric-grid">
  47. <view class="metric-item">
  48. <text class="metric-label">领用量</text>
  49. <text class="metric-value">{{ calcBorrowNumber(item) }}</text>
  50. </view>
  51. <view class="metric-item">
  52. <text class="metric-label">报损量</text>
  53. <text class="metric-value">{{ calcLossNumber(item) }}</text>
  54. </view>
  55. <view class="metric-item">
  56. <text class="metric-label">提醒数量</text>
  57. <u--input class="warn-input" v-model="item.warnNum" placeholder="0" @blur="changeWarnNum(item)">
  58. </u--input>
  59. </view>
  60. <view class="metric-item">
  61. <text class="metric-label">是否提醒</text>
  62. <el-switch :value="item.warnFlag === '1'" active-color="#13ce66" inactive-color="#ff4949"
  63. @change="updateWarnFlag(item)">
  64. </el-switch>
  65. </view>
  66. </view>
  67. <view class="card-actions">
  68. <el-button class="card-button" type="primary" plain @click="openHistory(item)">查看记录</el-button>
  69. </view>
  70. </view>
  71. <view v-if="dataList.length" class="load-more">
  72. <text v-if="loadingMore">加载中...</text>
  73. <text v-else-if="dataList.length >= tablePage.total">没有更多了</text>
  74. <text v-else @tap="loadMore">加载更多</text>
  75. </view>
  76. <ba-tree-picker ref="typePicker" :multiple="false" @select-change="selectTypeChange" title="类型选择"
  77. :localdata="typeTree" valueKey="value" textKey="label" childrenKey="children" />
  78. <ware-house-supplier-picker ref="supplierPicker" @selected="selectSupplier" @clear="clearSupplier">
  79. </ware-house-supplier-picker>
  80. <ware-house-history-panel ref="historyPanel"></ware-house-history-panel>
  81. <edit-trade-name-dialog ref="editTradeNameDialog" @success="refreshList"></edit-trade-name-dialog>
  82. </view>
  83. </template>
  84. <script>
  85. import baTreePicker from '@/components/ba-tree-picker/ba-tree-picker.vue'
  86. import WareHouseService from '@/api/psi/WareHouseService'
  87. import MaterialTypeService from '@/api/psi/MaterialTypeService'
  88. import WareHouseSupplierPicker from './WareHouseSupplierPicker.vue'
  89. import WareHouseHistoryPanel from './WareHouseHistoryPanel.vue'
  90. import EditTradeNameDialog from './EditTradeNameDialog.vue'
  91. export default {
  92. name: 'WareHouseSummaryList',
  93. components: {
  94. baTreePicker,
  95. WareHouseSupplierPicker,
  96. WareHouseHistoryPanel,
  97. EditTradeNameDialog
  98. },
  99. data() {
  100. return {
  101. loading: false,
  102. loadingMore: false,
  103. searchForm: {
  104. wareHouseType: '',
  105. wareHouseTypeName: '',
  106. tradeName: '',
  107. supplierId: '',
  108. supplierName: ''
  109. },
  110. dataList: [],
  111. tablePage: {
  112. total: 0,
  113. currentPage: 1,
  114. pageSize: 10,
  115. orders: []
  116. },
  117. typeTree: []
  118. }
  119. },
  120. wareHouseService: null,
  121. materialTypeService: null,
  122. created() {
  123. this.wareHouseService = new WareHouseService()
  124. this.materialTypeService = new MaterialTypeService()
  125. this.loadTypeTree()
  126. this.refreshList()
  127. },
  128. onPullDownRefresh() {
  129. this.refreshList().finally(() => {
  130. uni.stopPullDownRefresh()
  131. })
  132. },
  133. onReachBottom() {
  134. this.loadMore()
  135. },
  136. methods: {
  137. async loadTypeTree() {
  138. try {
  139. const data = await this.materialTypeService.summaryTreeData({
  140. type: 'last'
  141. })
  142. this.typeTree = this.normalizeTree(data || [])
  143. } catch (e) {
  144. try {
  145. const data = await this.materialTypeService.cgList()
  146. this.typeTree = this.buildTree(data || [])
  147. } catch (error) {
  148. this.typeTree = []
  149. }
  150. }
  151. },
  152. normalizeTree(nodes) {
  153. return (nodes || []).map(item => ({
  154. ...item,
  155. value: item.value || item.id,
  156. label: item.label || item.name,
  157. children: this.normalizeTree(item.children || [])
  158. }))
  159. },
  160. buildTree(nodes, parentId = '0') {
  161. const tree = []
  162. for (const node of nodes || []) {
  163. if (node.parentId === parentId) {
  164. const children = this.buildTree(nodes, node.id)
  165. const treeNode = {
  166. ...node,
  167. value: node.value || node.id,
  168. label: node.label || node.name
  169. }
  170. if (children.length) {
  171. treeNode.children = children
  172. }
  173. tree.push(treeNode)
  174. }
  175. }
  176. return tree
  177. },
  178. getSearchParams() {
  179. return {
  180. wareHouseType: this.searchForm.wareHouseType,
  181. tradeName: this.searchForm.tradeName,
  182. supplierId: this.searchForm.supplierId,
  183. supplierName: this.searchForm.supplierName
  184. }
  185. },
  186. async refreshList() {
  187. this.tablePage.currentPage = 1
  188. this.loading = true
  189. try {
  190. const data = await this.wareHouseService.wareHouseSummaryList({
  191. current: this.tablePage.currentPage,
  192. size: this.tablePage.pageSize,
  193. orders: this.tablePage.orders,
  194. ...this.getSearchParams()
  195. })
  196. this.dataList = (data && data.records) || []
  197. this.tablePage.total = (data && data.total) || 0
  198. } finally {
  199. this.loading = false
  200. }
  201. },
  202. async loadMore() {
  203. if (this.loading || this.loadingMore || this.dataList.length >= this.tablePage.total) {
  204. return
  205. }
  206. this.loadingMore = true
  207. try {
  208. const nextPage = this.tablePage.currentPage + 1
  209. const data = await this.wareHouseService.wareHouseSummaryList({
  210. current: nextPage,
  211. size: this.tablePage.pageSize,
  212. orders: this.tablePage.orders,
  213. ...this.getSearchParams()
  214. })
  215. this.dataList = this.dataList.concat((data && data.records) || [])
  216. this.tablePage.currentPage = nextPage
  217. this.tablePage.total = (data && data.total) || this.tablePage.total
  218. } finally {
  219. this.loadingMore = false
  220. }
  221. },
  222. resetSearch() {
  223. this.searchForm = {
  224. wareHouseType: '',
  225. wareHouseTypeName: '',
  226. tradeName: '',
  227. supplierId: '',
  228. supplierName: ''
  229. }
  230. this.refreshList()
  231. },
  232. openTypePicker() {
  233. this.$refs.typePicker._show()
  234. },
  235. selectTypeChange(ids, names) {
  236. this.searchForm.wareHouseType = (ids && ids[0]) || ''
  237. this.searchForm.wareHouseTypeName = Array.isArray(names) ? names.join('/') : names
  238. this.refreshList()
  239. },
  240. openSupplierPicker() {
  241. this.$refs.supplierPicker.open()
  242. },
  243. selectSupplier(item) {
  244. this.searchForm.supplierId = item.id || ''
  245. this.searchForm.supplierName = item.name || ''
  246. this.refreshList()
  247. },
  248. clearSupplier() {
  249. this.searchForm.supplierId = ''
  250. this.searchForm.supplierName = ''
  251. this.refreshList()
  252. },
  253. formatNumber(value) {
  254. if (value === null || value === undefined || value === '') {
  255. return '0'
  256. }
  257. return value
  258. },
  259. calcBorrowNumber(row) {
  260. if (row.notSurplusStock && row.spec) {
  261. return this.trimNumber(parseFloat(row.notSurplusStock) / parseFloat(row.spec))
  262. }
  263. return '0'
  264. },
  265. calcLossNumber(row) {
  266. if (row.lossNumber && row.spec) {
  267. return this.trimNumber(parseFloat(row.lossNumber) / parseFloat(row.spec))
  268. }
  269. return '0'
  270. },
  271. trimNumber(value) {
  272. if (Number.isNaN(value)) {
  273. return '0'
  274. }
  275. return Number.isInteger(value) ? String(value) : value.toFixed(2)
  276. },
  277. async updateWarnFlag(row) {
  278. try {
  279. await this.wareHouseService.updateWarnFlagByTradeNameAndType(row.tradeName, row.wareHouseType)
  280. uni.showToast({
  281. title: '提醒状态已更新',
  282. icon: 'success'
  283. })
  284. this.refreshList()
  285. } catch (e) {
  286. this.refreshList()
  287. }
  288. },
  289. async changeWarnNum(row) {
  290. const warnNum = row.warnNum === '' || row.warnNum === null || row.warnNum === undefined ? 0 : row.warnNum
  291. try {
  292. await this.wareHouseService.updateWarnNumByTradeNameAndType(row.tradeName, row.wareHouseType, warnNum)
  293. uni.showToast({
  294. title: '提醒数量已更新',
  295. icon: 'success'
  296. })
  297. this.refreshList()
  298. } catch (e) {
  299. this.refreshList()
  300. }
  301. },
  302. openHistory(row) {
  303. this.$refs.historyPanel.open(row, this.searchForm.supplierId)
  304. },
  305. openEditTradeName(row) {
  306. this.$refs.editTradeNameDialog.open(row)
  307. }
  308. }
  309. }
  310. </script>
  311. <style scoped>
  312. .page-wrap {
  313. min-height: 100vh;
  314. padding: 20rpx 24rpx 48rpx;
  315. background: #f6f8fb;
  316. }
  317. .search-panel {
  318. padding: 22rpx;
  319. background: #fff;
  320. border-radius: 18rpx;
  321. box-shadow: 0 8rpx 24rpx rgba(15, 23, 42, 0.05);
  322. }
  323. .filter-row {
  324. display: grid;
  325. grid-template-columns: repeat(2, minmax(0, 1fr));
  326. gap: 16rpx;
  327. margin-top: 18rpx;
  328. }
  329. .filter-item {
  330. min-width: 0;
  331. padding: 18rpx 20rpx;
  332. background: #f8fafc;
  333. border-radius: 14rpx;
  334. }
  335. .filter-label {
  336. display: block;
  337. font-size: 22rpx;
  338. color: #64748b;
  339. }
  340. .filter-value {
  341. display: block;
  342. margin-top: 8rpx;
  343. font-size: 26rpx;
  344. color: #0f172a;
  345. overflow: hidden;
  346. text-overflow: ellipsis;
  347. white-space: nowrap;
  348. }
  349. .action-row {
  350. display: flex;
  351. gap: 16rpx;
  352. margin-top: 18rpx;
  353. }
  354. .action-button {
  355. flex: 1;
  356. }
  357. .summary-row {
  358. display: flex;
  359. justify-content: space-between;
  360. padding: 22rpx 4rpx 12rpx;
  361. font-size: 24rpx;
  362. color: #64748b;
  363. }
  364. .empty-state {
  365. padding: 120rpx 0;
  366. text-align: center;
  367. font-size: 28rpx;
  368. color: #94a3b8;
  369. }
  370. .stock-card {
  371. margin-bottom: 20rpx;
  372. padding: 24rpx;
  373. background: #fff;
  374. border-radius: 18rpx;
  375. box-shadow: 0 8rpx 24rpx rgba(15, 23, 42, 0.05);
  376. }
  377. .card-head {
  378. display: flex;
  379. align-items: flex-start;
  380. justify-content: space-between;
  381. gap: 16rpx;
  382. }
  383. .goods-name {
  384. flex: 1;
  385. font-size: 31rpx;
  386. font-weight: 700;
  387. line-height: 1.4;
  388. color: #0f172a;
  389. word-break: break-all;
  390. }
  391. .type-badge {
  392. max-width: 260rpx;
  393. padding: 8rpx 14rpx;
  394. border-radius: 999rpx;
  395. font-size: 22rpx;
  396. color: #1d4ed8;
  397. background: #dbeafe;
  398. overflow: hidden;
  399. text-overflow: ellipsis;
  400. white-space: nowrap;
  401. }
  402. .stock-main {
  403. display: flex;
  404. justify-content: space-between;
  405. gap: 20rpx;
  406. margin-top: 24rpx;
  407. padding: 22rpx;
  408. background: #f8fafc;
  409. border-radius: 16rpx;
  410. }
  411. .stock-number {
  412. display: flex;
  413. align-items: baseline;
  414. gap: 8rpx;
  415. }
  416. .number-label {
  417. font-size: 22rpx;
  418. color: #64748b;
  419. }
  420. .number-value {
  421. font-size: 46rpx;
  422. font-weight: 800;
  423. color: #0f766e;
  424. }
  425. .number-unit {
  426. font-size: 24rpx;
  427. color: #475569;
  428. }
  429. .stock-total {
  430. display: flex;
  431. flex-direction: column;
  432. gap: 8rpx;
  433. text-align: right;
  434. font-size: 24rpx;
  435. color: #475569;
  436. }
  437. .metric-grid {
  438. display: grid;
  439. grid-template-columns: repeat(2, minmax(0, 1fr));
  440. gap: 16rpx;
  441. margin-top: 18rpx;
  442. }
  443. .metric-item {
  444. min-width: 0;
  445. padding: 18rpx;
  446. background: #fbfdff;
  447. border: 1rpx solid #eef2f7;
  448. border-radius: 14rpx;
  449. }
  450. .metric-label {
  451. display: block;
  452. margin-bottom: 10rpx;
  453. font-size: 22rpx;
  454. color: #64748b;
  455. }
  456. .metric-value {
  457. font-size: 28rpx;
  458. font-weight: 600;
  459. color: #0f172a;
  460. }
  461. .warn-input {
  462. width: 100%;
  463. }
  464. .card-actions {
  465. display: grid;
  466. gap: 16rpx;
  467. margin-top: 18rpx;
  468. }
  469. .card-button {
  470. width: 100%;
  471. }
  472. .load-more {
  473. padding: 24rpx 0;
  474. text-align: center;
  475. font-size: 26rpx;
  476. color: #64748b;
  477. }
  478. </style>