list.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. <script setup>
  2. import { ref } from 'vue'
  3. import { onShow, onReachBottom } from '@dcloudio/uni-app'
  4. import {
  5. listPendingReimburse,
  6. listPendingAdvance,
  7. listMyReimburse,
  8. listMyAdvance,
  9. listApprovedByMeReimburse,
  10. listApprovedByMeAdvance,
  11. approveReimburse,
  12. rejectReimburse,
  13. approveAdvance,
  14. rejectAdvance
  15. } from '@/api/bx'
  16. import { bxStatusLabel, formatMoney, dash } from '@/utils/bxLabels'
  17. import { PAGE_PATHS } from '@/config/api'
  18. const tabs = [
  19. { key: 'pending', label: '待审批' },
  20. { key: 'mine', label: '我的申请' },
  21. { key: 'done', label: '已办' }
  22. ]
  23. const activeTab = ref('pending')
  24. const list = ref([])
  25. const loading = ref(false)
  26. const finished = ref(false)
  27. const page = ref(1)
  28. const pageSize = 10
  29. const goBack = () => {
  30. uni.navigateBack({
  31. fail: () => uni.switchTab({ url: PAGE_PATHS.workbench })
  32. })
  33. }
  34. const goApply = () => {
  35. uni.showActionSheet({
  36. itemList: ['报销申请', '借款申请'],
  37. success: (res) => {
  38. if (res.tapIndex === 0) uni.navigateTo({ url: PAGE_PATHS.bxReimburseForm })
  39. if (res.tapIndex === 1) uni.navigateTo({ url: PAGE_PATHS.bxAdvanceForm })
  40. }
  41. })
  42. }
  43. const goDetail = (row) => {
  44. if (row._biz === 'advance') {
  45. uni.navigateTo({ url: `${PAGE_PATHS.bxAdvanceDetail}?id=${row.id}` })
  46. } else {
  47. uni.navigateTo({ url: `${PAGE_PATHS.bxReimburseDetail}?id=${row.id}` })
  48. }
  49. }
  50. const markBiz = (rows, biz) =>
  51. (rows || []).map((r) => ({
  52. ...r,
  53. _biz: biz,
  54. _title: biz === 'advance' ? `借款单 ${r.applyCode || ''}` : `报销单 ${r.applyCode || ''}`,
  55. _sub:
  56. biz === 'advance'
  57. ? dash(r.reason || r.purpose)
  58. : dash(r.expenseName || r.title)
  59. }))
  60. const loadList = async (reset = false) => {
  61. if (loading.value) return
  62. if (reset) {
  63. page.value = 1
  64. finished.value = false
  65. list.value = []
  66. }
  67. if (finished.value) return
  68. loading.value = true
  69. try {
  70. const q = { page: page.value, pageSize }
  71. let rows = []
  72. if (activeTab.value === 'pending') {
  73. const [a, b] = await Promise.all([listPendingReimburse(q), listPendingAdvance(q)])
  74. rows = markBiz(a.rows, 'reimburse').concat(markBiz(b.rows, 'advance'))
  75. rows.sort((x, y) => String(y.applyTime || '').localeCompare(String(x.applyTime || '')))
  76. finished.value = true
  77. } else if (activeTab.value === 'mine') {
  78. const [a, b] = await Promise.all([listMyReimburse(q), listMyAdvance(q)])
  79. rows = markBiz(a.rows, 'reimburse').concat(markBiz(b.rows, 'advance'))
  80. rows.sort((x, y) => String(y.applyTime || '').localeCompare(String(x.applyTime || '')))
  81. const total = (a.total || 0) + (b.total || 0)
  82. list.value = reset ? rows : list.value.concat(rows)
  83. if (list.value.length >= total || (a.rows.length < pageSize && b.rows.length < pageSize)) {
  84. finished.value = true
  85. } else page.value += 1
  86. return
  87. } else {
  88. const [a, b] = await Promise.all([
  89. listApprovedByMeReimburse(q),
  90. listApprovedByMeAdvance(q)
  91. ])
  92. rows = markBiz(a.rows, 'reimburse').concat(markBiz(b.rows, 'advance'))
  93. rows.sort((x, y) => String(y.applyTime || '').localeCompare(String(x.applyTime || '')))
  94. finished.value = true
  95. }
  96. list.value = reset ? rows : list.value.concat(rows)
  97. } catch (e) {
  98. uni.showToast({ title: e.message || '加载失败', icon: 'none' })
  99. } finally {
  100. loading.value = false
  101. }
  102. }
  103. const switchTab = (key) => {
  104. if (activeTab.value === key) return
  105. activeTab.value = key
  106. loadList(true)
  107. }
  108. const doApprove = (row, pass) => {
  109. const title = pass ? '通过' : '驳回'
  110. uni.showModal({
  111. title: `确认${title}`,
  112. editable: !pass,
  113. placeholderText: pass ? '' : '驳回意见(可选)',
  114. success: async (r) => {
  115. if (!r.confirm) return
  116. try {
  117. const opinion = r.content || (pass ? '同意' : '驳回')
  118. if (row._biz === 'advance') {
  119. if (pass) await approveAdvance(row.id, opinion)
  120. else await rejectAdvance(row.id, opinion)
  121. } else {
  122. if (pass) await approveReimburse(row.id, opinion)
  123. else await rejectReimburse(row.id, opinion)
  124. }
  125. uni.showToast({ title: `${title}成功`, icon: 'success' })
  126. loadList(true)
  127. } catch (e) {
  128. uni.showToast({ title: e.message || '操作失败', icon: 'none' })
  129. }
  130. }
  131. })
  132. }
  133. onShow(() => loadList(true))
  134. onReachBottom(() => {
  135. if (activeTab.value === 'mine') loadList(false)
  136. })
  137. </script>
  138. <template>
  139. <view class="mp-page mp-page--sub">
  140. <view class="mp-header mp-header--bx">
  141. <view class="mp-header__row">
  142. <text class="mp-subpage-back" @click="goBack">← 工作台</text>
  143. <view class="mp-header__action" @click="goApply">+ 申请</view>
  144. </view>
  145. <text class="mp-header__title" style="margin-top: 16rpx">待办任务</text>
  146. </view>
  147. <view class="mp-section" style="padding-bottom: 0">
  148. <scroll-view scroll-x class="mp-tabs">
  149. <view
  150. v-for="t in tabs"
  151. :key="t.key"
  152. class="mp-tab"
  153. :class="{ 'mp-tab--active': activeTab === t.key }"
  154. @click="switchTab(t.key)"
  155. >
  156. {{ t.label }}
  157. </view>
  158. </scroll-view>
  159. </view>
  160. <view class="mp-section">
  161. <mp-empty v-if="!list.length && !loading" title="暂无单据" />
  162. <view v-else class="mp-list">
  163. <view v-for="row in list" :key="row._biz + '-' + row.id" class="mp-card" @click="goDetail(row)">
  164. <view class="mp-card__head">
  165. <view>
  166. <text class="mp-card__title">{{ row._title }}</text>
  167. <text class="mp-card__sub">{{ row._sub }}</text>
  168. </view>
  169. <text class="mp-tag" :class="'mp-tag--' + bxStatusLabel(row.status).tag">
  170. {{ bxStatusLabel(row.status).text }}
  171. </text>
  172. </view>
  173. <view class="mp-card__row">
  174. <text>申请人</text>
  175. <text class="mp-card__row-val">{{ dash(row.applicantName) }}</text>
  176. </view>
  177. <view class="mp-card__row">
  178. <text>申请时间</text>
  179. <text class="mp-card__row-val">{{ dash(row.applyTime) }}</text>
  180. </view>
  181. <view class="mp-card__foot">
  182. <text class="mp-card__amount">{{ formatMoney(row.amount) }}</text>
  183. <view v-if="activeTab === 'pending'" style="display: flex; gap: 16rpx" @click.stop>
  184. <view class="mp-btn mp-btn--danger mp-btn--sm" @click="doApprove(row, false)">驳回</view>
  185. <view class="mp-btn mp-btn--primary mp-btn--sm" @click="doApprove(row, true)">通过</view>
  186. </view>
  187. </view>
  188. </view>
  189. </view>
  190. <view v-if="loading" class="mp-list-loading">加载中…</view>
  191. <view v-else-if="finished && list.length" class="mp-list-end">已加载全部</view>
  192. </view>
  193. </view>
  194. </template>