form.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. <script setup>
  2. import { reactive, ref, onMounted } from 'vue'
  3. import {
  4. getExpenseItemTree,
  5. flattenExpenseLeaves,
  6. createAdvance,
  7. submitAdvance,
  8. bindAttachments
  9. } from '@/api/bx'
  10. import { chooseAndUploadBxAttachments } from '@/utils/bxUpload'
  11. import { PAGE_PATHS } from '@/config/api'
  12. const submitting = ref(false)
  13. const leaves = ref([])
  14. const photos = ref([])
  15. const form = reactive({
  16. reason: '',
  17. purpose: '',
  18. amount: '',
  19. remark: '',
  20. expenseItemId: '',
  21. expenseLabel: ''
  22. })
  23. const goBack = () => {
  24. uni.navigateBack({
  25. fail: () => uni.navigateTo({ url: PAGE_PATHS.bxAdvanceList })
  26. })
  27. }
  28. onMounted(async () => {
  29. try {
  30. leaves.value = flattenExpenseLeaves(await getExpenseItemTree())
  31. } catch (e) {
  32. uni.showToast({ title: e.message || '费用项目加载失败', icon: 'none' })
  33. }
  34. })
  35. const pickExpense = () => {
  36. if (!leaves.value.length) {
  37. uni.showToast({ title: '暂无费用项目', icon: 'none' })
  38. return
  39. }
  40. uni.showActionSheet({
  41. itemList: leaves.value.map((i) => i.pathLabel).slice(0, 20),
  42. success: (res) => {
  43. const item = leaves.value[res.tapIndex]
  44. if (!item) return
  45. form.expenseItemId = item.id
  46. form.expenseLabel = item.pathLabel
  47. }
  48. })
  49. }
  50. const addPhotos = async () => {
  51. const uploaded = await chooseAndUploadBxAttachments({
  52. bizType: 'advance_apply',
  53. bizId: '0',
  54. maxCount: 6,
  55. currentCount: photos.value.length
  56. })
  57. photos.value = photos.value.concat(uploaded)
  58. }
  59. const removePhoto = (idx) => photos.value.splice(idx, 1)
  60. const submit = async (andSubmit) => {
  61. if (!String(form.reason).trim()) {
  62. uni.showToast({ title: '请填写借款事由', icon: 'none' })
  63. return
  64. }
  65. if (!form.amount || Number(form.amount) <= 0) {
  66. uni.showToast({ title: '请填写金额', icon: 'none' })
  67. return
  68. }
  69. submitting.value = true
  70. try {
  71. const created = await createAdvance({
  72. reason: String(form.reason).trim(),
  73. purpose: form.purpose || undefined,
  74. amount: Number(form.amount),
  75. remark: form.remark || undefined,
  76. expenseItemId: form.expenseItemId || undefined
  77. })
  78. const id = created?.id
  79. if (id && photos.value.length) {
  80. await bindAttachments(
  81. photos.value.map((p) => p.id),
  82. 'advance_apply',
  83. id
  84. )
  85. }
  86. if (andSubmit && id) {
  87. await submitAdvance(id)
  88. uni.showToast({ title: '已提交审批', icon: 'success' })
  89. } else {
  90. uni.showToast({ title: '已保存草稿', icon: 'success' })
  91. }
  92. setTimeout(() => goBack(), 500)
  93. } catch (e) {
  94. uni.showToast({ title: e.message || '保存失败', icon: 'none' })
  95. } finally {
  96. submitting.value = false
  97. }
  98. }
  99. </script>
  100. <template>
  101. <view class="mp-page mp-page--sub mp-page--with-bottom">
  102. <view class="mp-header mp-header--bx">
  103. <text class="mp-subpage-back" @click="goBack">← 返回</text>
  104. <text class="mp-header__title" style="margin-top: 16rpx">新建借款</text>
  105. </view>
  106. <view class="mp-form-panel">
  107. <view class="mp-form-row">
  108. <text class="mp-form-label mp-form-label--req">借款事由</text>
  109. <textarea v-model="form.reason" class="mp-form-textarea" placeholder="用途说明" />
  110. </view>
  111. <view class="mp-form-row">
  112. <text class="mp-form-label">用途</text>
  113. <input v-model="form.purpose" class="mp-form-input" placeholder="可选" />
  114. </view>
  115. <view class="mp-form-row">
  116. <text class="mp-form-label mp-form-label--req">金额(元)</text>
  117. <input v-model="form.amount" class="mp-form-input" type="digit" placeholder="0.00" />
  118. </view>
  119. <view class="mp-form-row" @click="pickExpense">
  120. <text class="mp-form-label">费用项目</text>
  121. <view class="mp-form-input">{{ form.expenseLabel || '可选,点击选择' }}</view>
  122. </view>
  123. <view class="mp-form-row">
  124. <text class="mp-form-label">备注</text>
  125. <textarea v-model="form.remark" class="mp-form-textarea" placeholder="可选" />
  126. </view>
  127. <view class="mp-form-row">
  128. <text class="mp-form-label">凭证照片</text>
  129. <view class="mp-photo-row">
  130. <view v-for="(p, idx) in photos" :key="p.id" class="mp-photo-item">
  131. <image :src="p.localPath" mode="aspectFill" />
  132. <view class="mp-photo-del" @click.stop="removePhoto(idx)">×</view>
  133. </view>
  134. <view v-if="photos.length < 6" class="mp-photo-add" @click="addPhotos">+ 拍照</view>
  135. </view>
  136. </view>
  137. </view>
  138. <view class="mp-bottom-bar">
  139. <view class="mp-btn mp-btn--ghost" @click="submit(false)">{{ submitting ? '…' : '存草稿' }}</view>
  140. <view class="mp-btn mp-btn--primary" @click="submit(true)">{{ submitting ? '…' : '提交审批' }}</view>
  141. </view>
  142. </view>
  143. </template>