Files
shanxia2.0/miniprogram/pages/bx/reimburse/form.vue
T
2026-07-17 21:59:47 +08:00

191 lines
5.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>