index.uvue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. <template>
  2. <uni-navbar-lite @rightClick="handleRight" :show-right="showRight" title="物料申请"></uni-navbar-lite>
  3. <view class="list-page">
  4. <!-- 搜索栏和状态标签 -->
  5. <view class="search-block">
  6. <view class="search-bar">
  7. <view class="search-box">
  8. <image class="search-icon" src="/static/images/workbench/list/1.png" mode="aspectFit"></image>
  9. <input class="search-input" type="text" placeholder="请输入申请单号查询" v-model="keyword" @input="handleSearch" />
  10. <view v-if="keyword.length > 0" class="clear-btn" @tap="clearKeyword">
  11. <text class="clear-btn-text">×</text>
  12. </view>
  13. <view class="search-btn" @tap="handleSearch">
  14. <text class="search-btn-text">搜索</text>
  15. </view>
  16. </view>
  17. </view>
  18. <!-- 状态标签 -->
  19. <scroll-view class="status-tabs" scroll-x="true">
  20. <view
  21. class="status-tab"
  22. :class="{ 'active': currentStatus === '' }"
  23. @tap="handleStatusChange('')"
  24. >
  25. <text class="status-tab-text" :class="{ 'active-text': currentStatus === '' }">全部</text>
  26. </view>
  27. <view
  28. class="status-tab"
  29. :class="{ 'active': currentStatus === 'PREPARE' }"
  30. @tap="handleStatusChange('PREPARE')"
  31. >
  32. <text class="status-tab-text" :class="{ 'active-text': currentStatus === 'PREPARE' }">待确认</text>
  33. </view>
  34. <view
  35. class="status-tab"
  36. :class="{ 'active': currentStatus === 'CONFIRMED' }"
  37. @tap="handleStatusChange('CONFIRMED')"
  38. >
  39. <text class="status-tab-text" :class="{ 'active-text': currentStatus === 'CONFIRMED' }">已确认</text>
  40. </view>
  41. <view
  42. class="status-tab"
  43. :class="{ 'active': currentStatus === 'WAITOUT' }"
  44. @tap="handleStatusChange('WAITOUT')"
  45. >
  46. <text class="status-tab-text" :class="{ 'active-text': currentStatus === 'WAITOUT' }">待出库</text>
  47. </view>
  48. <view
  49. class="status-tab"
  50. :class="{ 'active': currentStatus === 'FINISHED' }"
  51. @tap="handleStatusChange('FINISHED')"
  52. >
  53. <text class="status-tab-text" :class="{ 'active-text': currentStatus === 'FINISHED' }">已完成</text>
  54. </view>
  55. </scroll-view>
  56. </view>
  57. <!-- 列表内容 -->
  58. <common-list
  59. :dataList="dataList"
  60. :loading="loading"
  61. :refreshing="refreshing"
  62. :hasMore="hasMore"
  63. @refresh="handleRefresh"
  64. @loadMore="loadMore"
  65. @itemClick="handleItemClick"
  66. >
  67. <template #default="{ item, index }">
  68. <view class="list-item">
  69. <view class="item-container">
  70. <view class="item-header">
  71. <text class="item-title">{{ getApplyCode(item) }}</text>
  72. <text class="item-status" :class="'status-' + getStatus(item)">{{ getStatusText(item) }}</text>
  73. </view>
  74. <view class="item-info">
  75. <view class="info-row">
  76. <view class="info-item">
  77. <text class="info-label">申请时间</text>
  78. <text class="info-value">{{ getCreateTime(item) }}</text>
  79. </view>
  80. <view class="info-item">
  81. <text class="info-label">申请人</text>
  82. <text class="info-value">{{ getNickName(item) }}</text>
  83. </view>
  84. </view>
  85. <view class="info-row">
  86. <view class="info-item">
  87. <text class="info-label">申请数</text>
  88. <text class="info-value warning">{{ getApplyCount(item) }}</text>
  89. </view>
  90. <view class="info-item">
  91. <text class="info-label">已出库</text>
  92. <text class="info-value primary">{{ getOutCount(item) }}</text>
  93. </view>
  94. </view>
  95. <view class="info-row">
  96. <view class="info-item">
  97. <text class="info-label">申购状态</text>
  98. <text class="info-value" :class="'purchase-status-' + getPurchaseStatus(item)">{{ getPurchaseStatusText(item) }}</text>
  99. </view>
  100. </view>
  101. </view>
  102. </view>
  103. </view>
  104. </template>
  105. </common-list>
  106. </view>
  107. </template>
  108. <script setup lang="uts">
  109. import { ref, onShow } from 'vue'
  110. import { getPurchaseApplyList, getPendingReceiveApplyCount } from '../../api/apply/index'
  111. import { getUserInfo } from '../../utils/storage'
  112. let currentUserId: string = ''
  113. // 列表数据
  114. const dataList = ref<any[]>([])
  115. const keyword = ref<string>("")
  116. let searchTimer: number | null = null
  117. const currentStatus = ref<string>("")
  118. const page = ref<number>(1)
  119. const pageSize: number = 10
  120. const hasMore = ref<boolean>(true)
  121. const loading = ref<boolean>(false)
  122. const refreshing = ref<boolean>(false)
  123. const showRight = ref<boolean>(false)
  124. const pendingReceiveCount = ref<number>(0)
  125. // 加载待领取数量
  126. const loadPendingReceiveCount = (): void => {
  127. if (currentUserId.length === 0) return
  128. getPendingReceiveApplyCount(currentUserId).then((res: any) => {
  129. const result = res as UTSJSONObject
  130. const count = result['data']
  131. pendingReceiveCount.value = count != null ? parseInt(count.toString()) : 0
  132. }).catch((e) => {
  133. console.error('加载待领取数量失败:', e)
  134. })
  135. }
  136. // 获取申请单号
  137. const getApplyCode = (item: any | null): string => {
  138. if (item == null) return ''
  139. const jsonItem = item as UTSJSONObject
  140. const val = jsonItem['applyCode']
  141. return val != null ? val.toString() : ''
  142. }
  143. // 获取状态
  144. const getStatus = (item: any | null): string => {
  145. if (item == null) return ''
  146. const jsonItem = item as UTSJSONObject
  147. const val = jsonItem['status']
  148. return val != null ? val.toString() : ''
  149. }
  150. // 获取状态文本
  151. const getStatusText = (item: any | null): string => {
  152. if (item == null) return ''
  153. const jsonItem = item as UTSJSONObject
  154. const val = jsonItem['status']
  155. const status = val != null ? val.toString() : ''
  156. switch (status) {
  157. case 'PREPARE': return '草稿'
  158. case 'CONFIRMED': return '已确认'
  159. case 'FINISHED': return '已完成'
  160. case 'CANCEL': return '已取消'
  161. case 'APPROVED': return '已审批'
  162. case 'APPROVING': return '审批中'
  163. case 'WAITOUT': return '待出库'
  164. default: return status
  165. }
  166. }
  167. // 获取申购状态
  168. const getPurchaseStatus = (item: any | null): string => {
  169. if (item == null) return ''
  170. const jsonItem = item as UTSJSONObject
  171. const val = jsonItem['applyStatus']
  172. return val != null ? val.toString() : ''
  173. }
  174. // 获取申购状态文本
  175. const getPurchaseStatusText = (item: any | null): string => {
  176. if (item == null) return ''
  177. const jsonItem = item as UTSJSONObject
  178. const val = jsonItem['applyStatus']
  179. const status = val != null ? val.toString() : ''
  180. switch (status) {
  181. case '0': return '未申购'
  182. case '1': return '已申购'
  183. default: return ''
  184. }
  185. }
  186. // 获取创建时间
  187. const getCreateTime = (item: any | null): string => {
  188. if (item == null) return ''
  189. const jsonItem = item as UTSJSONObject
  190. const val = jsonItem['createTime']
  191. return val != null ? val.toString() : ''
  192. }
  193. // 获取申请人
  194. const getNickName = (item: any | null): string => {
  195. if (item == null) return ''
  196. const jsonItem = item as UTSJSONObject
  197. const val = jsonItem['nickName']
  198. return val != null ? val.toString() : ''
  199. }
  200. // 获取申请数量
  201. const getApplyCount = (item: any | null): string => {
  202. if (item == null) return '0'
  203. const jsonItem = item as UTSJSONObject
  204. const val = jsonItem['applyQuantity']
  205. return val != null ? val.toString() : '0'
  206. }
  207. // 获取已出库数量
  208. const getOutCount = (item: any | null): string => {
  209. if (item == null) return '0'
  210. const jsonItem = item as UTSJSONObject
  211. const val = jsonItem['outQuantity']
  212. return val != null ? val.toString() : '0'
  213. }
  214. // 获取已完成数量
  215. const getFinishedCount = (item: any | null): string => {
  216. if (item == null) return '0'
  217. const jsonItem = item as UTSJSONObject
  218. const val = jsonItem['finishedCount']
  219. return val != null ? val.toString() : '0'
  220. }
  221. // 获取待审核数量 (status=1)
  222. const getPendingAuditCount = (item: any | null): string => {
  223. if (item == null) return '0'
  224. const jsonItem = item as UTSJSONObject
  225. const val = jsonItem['pendingAuditCount']
  226. return val != null ? val.toString() : '0'
  227. }
  228. // 获取待领取数量 (status=2 + status=4)
  229. const getPendingReceiveCount = (item: any | null): string => {
  230. if (item == null) return '0'
  231. const jsonItem = item as UTSJSONObject
  232. const val = jsonItem['pendingReceiveCount']
  233. return val != null ? val.toString() : '0'
  234. }
  235. // 获取待采购数量 (status=3 + status=4)
  236. const getPendingPurchaseCount = (item: any | null): string => {
  237. if (item == null) return '0'
  238. const jsonItem = item as UTSJSONObject
  239. const val = jsonItem['pendingPurchaseCount']
  240. return val != null ? val.toString() : '0'
  241. }
  242. // 加载列表数据
  243. const loadData = async (isRefresh: boolean): Promise<void> => {
  244. if (loading.value) return
  245. try {
  246. loading.value = true
  247. if (isRefresh) {
  248. page.value = 1
  249. }
  250. const searchKeyword = keyword.value != null ? keyword.value : ''
  251. const isPendingReceive = currentStatus.value === 'PENDING_RECEIVE'
  252. const statusParam = isPendingReceive ? '' : (currentStatus.value != null ? currentStatus.value : '')
  253. console.log('loadData - statusParam:', statusParam, 'isPendingReceive:', isPendingReceive)
  254. const result = await getPurchaseApplyList(page.value, pageSize, searchKeyword, statusParam, currentUserId, isPendingReceive)
  255. console.log('getPurchaseApplyList result:', result)
  256. const resultObj = result as UTSJSONObject
  257. const rows = resultObj['rows']
  258. const total = resultObj['total'] as number
  259. console.log('rows:', rows, 'total:', total)
  260. if (rows != null) {
  261. const newData = rows as any[]
  262. if (isRefresh) {
  263. dataList.value = newData
  264. } else {
  265. dataList.value = [...dataList.value, ...newData]
  266. }
  267. hasMore.value = dataList.value.length < total
  268. } else {
  269. if (isRefresh) {
  270. dataList.value = []
  271. }
  272. hasMore.value = false
  273. }
  274. } catch (e) {
  275. console.error('加载失败:', e)
  276. } finally {
  277. loading.value = false
  278. refreshing.value = false
  279. }
  280. }
  281. // 下拉刷新
  282. const handleRefresh = (): void => {
  283. refreshing.value = true
  284. loadData(true)
  285. }
  286. // 加载更多
  287. const loadMore = (): void => {
  288. if (!hasMore.value || loading.value) return
  289. page.value++
  290. loadData(false)
  291. }
  292. // 搜索
  293. const handleSearch = (): void => {
  294. const timer = searchTimer
  295. if (timer != null) {
  296. clearTimeout(timer)
  297. }
  298. searchTimer = setTimeout(() => {
  299. loadData(true)
  300. }, 300)
  301. }
  302. // 清空搜索关键字
  303. const clearKeyword = (): void => {
  304. keyword.value = ''
  305. loadData(true)
  306. }
  307. // 切换状态
  308. const handleStatusChange = (status: string): void => {
  309. currentStatus.value = status
  310. loadData(true)
  311. }
  312. const handleRight = ():void=>{
  313. uni.navigateTo({
  314. url: `/pages/apply/applyNew?from=index`
  315. })
  316. }
  317. // 点击列表项
  318. const handleItemClick = (item: any | null, index: number): void => {
  319. if (item == null) return
  320. const jsonItem = item as UTSJSONObject
  321. const applyId = jsonItem['applyId']
  322. uni.navigateTo({
  323. url: `/pages/apply/applyInfo?id=${applyId}`
  324. })
  325. }
  326. // 初始化
  327. loadData(true)
  328. // 页面显示时刷新列表
  329. onShow(() => {
  330. loadData(true)
  331. loadPendingReceiveCount()
  332. })
  333. onMounted(() => {
  334. const userInfo = getUserInfo()
  335. if (userInfo != null) {
  336. const userName = userInfo['userName']
  337. currentUserId = userName != null ? userName.toString() : ''
  338. }
  339. // 获取页面参数,判断是否从index.uvue跳转过来
  340. const pages = getCurrentPages()
  341. const currentPage = pages[pages.length - 1]
  342. const options = currentPage.options
  343. if (options != null && options.from == 'new') {
  344. showRight.value = false
  345. }else{
  346. showRight.value = true
  347. }
  348. })
  349. </script>
  350. <style lang="scss">
  351. .list-page {
  352. flex: 1;
  353. background-color: #e8f0f9;
  354. }
  355. .search-block {
  356. background-color: #ffffff;
  357. }
  358. .pending-receive-bar {
  359. display: flex;
  360. flex-direction: row;
  361. align-items: center;
  362. padding: 20rpx 30rpx;
  363. background-color: #fff7e6;
  364. border-left: 6rpx solid #fa8c16;
  365. margin: 0 30rpx;
  366. border-radius: 8rpx;
  367. }
  368. .pending-receive-label {
  369. font-size: 28rpx;
  370. color: #fa8c16;
  371. font-weight: bold;
  372. }
  373. .pending-receive-count {
  374. margin-left: 16rpx;
  375. padding: 4rpx 16rpx;
  376. background-color: #fa8c16;
  377. color: #ffffff;
  378. font-size: 24rpx;
  379. border-radius: 20rpx;
  380. }
  381. .pending-receive-arrow {
  382. margin-left: auto;
  383. color: #999999;
  384. font-size: 28rpx;
  385. }
  386. .search-bar {
  387. padding: 20rpx 30rpx;
  388. }
  389. .status-tabs {
  390. display: flex;
  391. flex-direction: row;
  392. padding: 0rpx 30rpx 20rpx;
  393. background-color: #ffffff;
  394. white-space: nowrap;
  395. width: 100%;
  396. }
  397. .status-tab {
  398. display: inline-flex;
  399. flex-direction: row;
  400. align-items: center;
  401. padding: 16rpx 24rpx;
  402. text-align: center;
  403. position: relative;
  404. margin-right: 16rpx;
  405. border-radius: 8rpx;
  406. background-color: #f5f5f5;
  407. justify-content: center;
  408. align-items: center;
  409. &:last-child {
  410. margin-right: 0;
  411. }
  412. &.active {
  413. background-color: #007aff;
  414. }
  415. .status-tab-text {
  416. font-size: 26rpx;
  417. color: #666666;
  418. text-align: center;
  419. &.active-text {
  420. color: #ffffff;
  421. font-weight: bold;
  422. }
  423. }
  424. .tab-badge {
  425. margin-left: 8rpx;
  426. min-width: 32rpx;
  427. height: 32rpx;
  428. padding: 0 8rpx;
  429. background-color: #ff3b30;
  430. border-radius: 16rpx;
  431. justify-content: center;
  432. align-items: center;
  433. .tab-badge-text {
  434. font-size: 20rpx;
  435. color: #ffffff;
  436. }
  437. }
  438. }
  439. .search-box {
  440. flex-direction: row;
  441. align-items: center;
  442. height: 72rpx;
  443. padding: 0 24rpx;
  444. background-color: #f5f5f5;
  445. border-radius: 36rpx;
  446. .search-icon {
  447. width: 32rpx;
  448. height: 32rpx;
  449. margin-right: 12rpx;
  450. }
  451. .search-input {
  452. flex: 1;
  453. font-size: 28rpx;
  454. color: #333333;
  455. }
  456. .search-btn {
  457. padding: 10rpx 20rpx;
  458. background-color: #007aff;
  459. border-radius: 8rpx;
  460. margin-left: 10rpx;
  461. }
  462. .search-btn-text {
  463. color: #ffffff;
  464. font-size: 24rpx;
  465. }
  466. .clear-btn {
  467. width: 36rpx;
  468. height: 36rpx;
  469. border-radius: 18rpx;
  470. background-color: #cccccc;
  471. align-items: center;
  472. justify-content: center;
  473. margin-left: 10rpx;
  474. }
  475. .clear-btn-text {
  476. color: #ffffff;
  477. font-size: 24rpx;
  478. font-weight: bold;
  479. }
  480. }
  481. .list-item {
  482. margin: 10rpx 20rpx;
  483. background-color: #ffffff;
  484. border-radius: 16rpx;
  485. }
  486. .item-container {
  487. padding: 30rpx;
  488. }
  489. .item-header {
  490. flex-direction: row;
  491. align-items: center;
  492. margin-bottom: 20rpx;
  493. .item-title {
  494. flex: 1;
  495. font-size: 32rpx;
  496. color: #333333;
  497. font-weight: bold;
  498. }
  499. .item-status {
  500. font-size: 26rpx;
  501. padding: 6rpx 16rpx;
  502. border-radius: 6rpx;
  503. &.status-PREPARE {
  504. background-color: #f0f0f0;
  505. color: #666666;
  506. }
  507. &.status-CONFIRMED {
  508. background-color: #e6f7ff;
  509. color: #1890ff;
  510. }
  511. &.status-FINISHED {
  512. background-color: #f6ffed;
  513. color: #52c41a;
  514. }
  515. &.status-CANCEL {
  516. background-color: #fff1f0;
  517. color: #ff4d4f;
  518. }
  519. &.status-APPROVING {
  520. background-color: #d1f5f8;
  521. color: #ff007f;
  522. }
  523. &.status-WAITOUT {
  524. color: #fa8c16;
  525. }
  526. &.status-APPROVED {
  527. background-color: #ebfffd;
  528. color: #55ff00;
  529. }
  530. }
  531. }
  532. .item-info {
  533. padding: 20rpx;
  534. background-color: #f8f9fa;
  535. border-radius: 8rpx;
  536. .info-row {
  537. flex-direction: row;
  538. justify-content: space-between;
  539. margin-bottom: 16rpx;
  540. &:last-child {
  541. margin-bottom: 0;
  542. }
  543. .info-item {
  544. flex-direction: row;
  545. align-items: center;
  546. flex: 1;
  547. &:last-child {
  548. flex: 1;
  549. justify-content: flex-end;
  550. }
  551. .info-label {
  552. font-size: 26rpx;
  553. color: #666666;
  554. margin-right: 8rpx;
  555. white-space: nowrap;
  556. }
  557. .info-value {
  558. font-size: 26rpx;
  559. color: #333333;
  560. flex: 1;
  561. &.success {
  562. color: #52c41a;
  563. }
  564. &.warning {
  565. color: #faad14;
  566. }
  567. &.primary {
  568. color: #007aff;
  569. }
  570. &.purchase-status-0 {
  571. color: #faad14;
  572. }
  573. &.purchase-status-1 {
  574. color: #52c41a;
  575. }
  576. }
  577. }
  578. }
  579. }
  580. </style>