| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- <script setup>
- import { reactive, ref, onMounted } from 'vue'
- import {
- getExpenseItemTree,
- flattenExpenseLeaves,
- createReimburse,
- submitReimburse,
- generateReimburseTitle,
- bindAttachments
- } from '@/api/bx'
- import { chooseAndUploadBxAttachments } from '@/utils/bxUpload'
- import { todayStr } from '@/utils/bxLabels'
- import { PAGE_PATHS } from '@/config/api'
- const submitting = ref(false)
- const leaves = ref([])
- const photos = ref([])
- const form = reactive({
- title: '',
- reimburseDate: todayStr(),
- remark: '',
- expenseItemId: '',
- expenseLabel: '',
- amount: '',
- detailRemark: ''
- })
- const goBack = () => {
- uni.navigateBack({
- fail: () => uni.navigateTo({ url: PAGE_PATHS.bxReimburseList })
- })
- }
- const loadTree = async () => {
- try {
- const tree = await getExpenseItemTree()
- leaves.value = flattenExpenseLeaves(tree)
- } catch (e) {
- uni.showToast({ title: e.message || '费用项目加载失败', icon: 'none' })
- }
- }
- const refreshTitle = async () => {
- try {
- const t = await generateReimburseTitle(form.reimburseDate)
- if (typeof t === 'string') form.title = t
- else if (t) form.title = String(t)
- } catch {
- /* optional */
- }
- }
- const pickExpense = () => {
- if (!leaves.value.length) {
- uni.showToast({ title: '暂无费用项目,请先在 Web 配置', icon: 'none' })
- return
- }
- const names = leaves.value.map((i) => i.pathLabel).slice(0, 20)
- uni.showActionSheet({
- itemList: names,
- success: (res) => {
- const item = leaves.value[res.tapIndex]
- if (!item) return
- form.expenseItemId = item.id
- form.expenseLabel = item.pathLabel
- }
- })
- }
- const addPhotos = async () => {
- const uploaded = await chooseAndUploadBxAttachments({
- bizType: 'reimburse_apply',
- bizId: '0',
- maxCount: 6,
- currentCount: photos.value.length
- })
- photos.value = photos.value.concat(uploaded)
- }
- const removePhoto = (idx) => {
- photos.value.splice(idx, 1)
- }
- const submit = async (andSubmit) => {
- if (!form.expenseItemId) {
- uni.showToast({ title: '请选择费用项目', icon: 'none' })
- return
- }
- if (!form.amount || Number(form.amount) <= 0) {
- uni.showToast({ title: '请填写金额', icon: 'none' })
- return
- }
- if (!String(form.detailRemark || '').trim()) {
- uni.showToast({ title: '请填写明细说明', icon: 'none' })
- return
- }
- submitting.value = true
- try {
- const created = await createReimburse({
- title: form.title || undefined,
- reimburseDate: form.reimburseDate,
- remark: form.remark,
- details: [
- {
- expenseItemId: form.expenseItemId,
- amount: Number(form.amount),
- remark: String(form.detailRemark).trim()
- }
- ]
- })
- const id = created?.id
- if (id && photos.value.length) {
- await bindAttachments(
- photos.value.map((p) => p.id),
- 'reimburse_apply',
- id
- )
- }
- if (andSubmit && id) {
- await submitReimburse(id)
- uni.showToast({ title: '已提交审批', icon: 'success' })
- } else {
- uni.showToast({ title: '已保存草稿', icon: 'success' })
- }
- setTimeout(() => goBack(), 500)
- } catch (e) {
- uni.showToast({ title: e.message || '保存失败', icon: 'none' })
- } finally {
- submitting.value = false
- }
- }
- onMounted(async () => {
- await loadTree()
- await refreshTitle()
- })
- </script>
- <template>
- <view class="mp-page mp-page--sub mp-page--with-bottom">
- <view class="mp-header mp-header--bx">
- <text class="mp-subpage-back" @click="goBack">← 返回</text>
- <text class="mp-header__title" style="margin-top: 16rpx">新建报销</text>
- </view>
- <view class="mp-form-panel">
- <view class="mp-form-row">
- <text class="mp-form-label">标题</text>
- <input v-model="form.title" class="mp-form-input" placeholder="自动生成,可修改" />
- </view>
- <view class="mp-form-row">
- <text class="mp-form-label">报销日期</text>
- <picker mode="date" :value="form.reimburseDate" @change="(e) => { form.reimburseDate = e.detail.value; refreshTitle() }">
- <view class="mp-form-input">{{ form.reimburseDate }}</view>
- </picker>
- </view>
- <view class="mp-form-row" @click="pickExpense">
- <text class="mp-form-label mp-form-label--req">费用项目</text>
- <view class="mp-form-input">{{ form.expenseLabel || '点击选择' }}</view>
- </view>
- <view class="mp-form-row">
- <text class="mp-form-label mp-form-label--req">金额(元)</text>
- <input v-model="form.amount" class="mp-form-input" type="digit" placeholder="0.00" />
- </view>
- <view class="mp-form-row">
- <text class="mp-form-label mp-form-label--req">明细说明</text>
- <textarea v-model="form.detailRemark" class="mp-form-textarea" placeholder="费用事由" />
- </view>
- <view class="mp-form-row">
- <text class="mp-form-label">备注</text>
- <textarea v-model="form.remark" class="mp-form-textarea" placeholder="可选" />
- </view>
- <view class="mp-form-row">
- <text class="mp-form-label">票据照片</text>
- <view class="mp-photo-row">
- <view v-for="(p, idx) in photos" :key="p.id" class="mp-photo-item">
- <image :src="p.localPath" mode="aspectFill" />
- <view class="mp-photo-del" @click.stop="removePhoto(idx)">×</view>
- </view>
- <view v-if="photos.length < 6" class="mp-photo-add" @click="addPhotos">+ 拍照</view>
- </view>
- </view>
- </view>
- <view class="mp-bottom-bar">
- <view class="mp-btn mp-btn--ghost" @click="submit(false)">{{ submitting ? '…' : '存草稿' }}</view>
- <view class="mp-btn mp-btn--primary" @click="submit(true)">{{ submitting ? '…' : '提交审批' }}</view>
- </view>
- </view>
- </template>
|