181 lines
7.9 KiB
Vue
181 lines
7.9 KiB
Vue
<template>
|
||
<view class="page">
|
||
<fy-header :title="spec ? spec.title : '申请'" back />
|
||
<view v-if="spec" class="body">
|
||
<view class="field">
|
||
<text class="lab">标题</text>
|
||
<view class="box"><input v-model="title" /></view>
|
||
</view>
|
||
<view v-if="spec.multiInvoice">
|
||
<view v-for="(inv, i) in invoices" :key="i" class="card inv">
|
||
<text class="lab">发票 {{ i + 1 }}</text>
|
||
<picker :range="ticketTypes" @change="(e) => inv.ticketType = ticketTypes[e.detail.value]">
|
||
<view class="box">{{ inv.ticketType || '选择票种' }}</view>
|
||
</picker>
|
||
<view class="box"><input v-model="inv.amount" type="digit" placeholder="金额" /></view>
|
||
<view class="box"><input v-model="inv.invoiceNo" placeholder="发票号(选填)" /></view>
|
||
<picker mode="date" @change="(e) => inv.date = e.detail.value">
|
||
<view class="box">{{ inv.date || '发票日期(选填)' }}</view>
|
||
</picker>
|
||
<view class="box"><input v-model="inv.note" placeholder="备注" /></view>
|
||
<fy-files :files="inv.files" label="发票附件" editable kind="expense" @update:files="(rows) => inv.files = rows" @remove="(idx) => inv.files.splice(idx, 1)" />
|
||
</view>
|
||
<text class="link" @click="invoices.push({ ticketType: '', amount: '', invoiceNo: '', date: '', note: '', files: [], proxyPay: false })">+ 添加发票</text>
|
||
<view class="field">
|
||
<text class="lab">报销说明</text>
|
||
<view class="box tall"><textarea v-model="remark" /></view>
|
||
</view>
|
||
</view>
|
||
<view v-for="f in spec.fields || []" :key="f.key" class="field">
|
||
<text class="lab">{{ f.label }}</text>
|
||
<picker v-if="f.type === 'select'" :range="optionsOf(f)" @change="(e) => values[f.key] = optionsOf(f)[e.detail.value]">
|
||
<view class="box">{{ values[f.key] || '请选择' }}</view>
|
||
</picker>
|
||
<picker v-else-if="f.type === 'date'" mode="date" @change="(e) => values[f.key] = e.detail.value">
|
||
<view class="box">{{ values[f.key] || '请选择日期' }}</view>
|
||
</picker>
|
||
<picker v-else-if="f.type === 'datetime'" mode="date" @change="(e) => values[f.key] = e.detail.value">
|
||
<view class="box">{{ values[f.key] || '请选择日期' }}</view>
|
||
</picker>
|
||
<view v-else-if="f.type === 'textarea'" class="box tall"><textarea v-model="values[f.key]" :placeholder="f.placeholder" /></view>
|
||
<view v-else class="box"><input v-model="values[f.key]" :placeholder="f.placeholder" :type="f.type === 'money' || f.type === 'number' ? 'digit' : 'text'" /></view>
|
||
</view>
|
||
<fy-files v-if="!spec.multiInvoice" :files="attachments" label="附件" editable :kind="kind" @update:files="(rows) => attachments = rows" @remove="(idx) => attachments.splice(idx, 1)" />
|
||
<view v-if="!fixed" class="field">
|
||
<text class="lab">审批人</text>
|
||
<picker :range="approverNames" @change="pickApprover">
|
||
<view class="box">{{ approverLabel || '请选择审批人' }}</view>
|
||
</picker>
|
||
</view>
|
||
<view v-else class="field">
|
||
<text class="lab">审批链</text>
|
||
<text class="hint">{{ suggestedLabel || '将按固定审批链流转' }}</text>
|
||
</view>
|
||
<view class="submit">
|
||
<wd-button type="primary" block size="large" @click="submit">提交申请</wd-button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import FyHeader from '../../components/fy-header.vue'
|
||
import FyFiles from '../../components/fy-files.vue'
|
||
import { store } from '../../utils/store.js'
|
||
import { applyKindById, buildApplyPayload, isFixedChain } from '../../utils/apply.js'
|
||
import { asList, toast } from '../../utils/format.js'
|
||
import { oaGet } from '../../utils/api.js'
|
||
|
||
export default {
|
||
components: { FyHeader, FyFiles },
|
||
data() {
|
||
return {
|
||
kind: 'leave',
|
||
title: '',
|
||
values: {},
|
||
invoices: [{ ticketType: '', amount: '', invoiceNo: '', date: '', note: '', files: [], proxyPay: false }],
|
||
attachments: [],
|
||
remark: '',
|
||
approvers: [],
|
||
suggested: '',
|
||
suggestedName: '',
|
||
approver: '',
|
||
ticketTypes: ['机票', '高铁/火车', '出租车', '住宿', '餐饮招待', '办公用品', '业务招待', '过路费/油费', '其他'],
|
||
peopleOpts: []
|
||
}
|
||
},
|
||
computed: {
|
||
spec() { return applyKindById(this.kind) },
|
||
fixed() { return isFixedChain(this.kind) },
|
||
approverNames() { return this.approvers.map((a) => a.realName || a.name || a.username) },
|
||
approverLabel() {
|
||
const a = this.approvers.find((x) => x.username === this.approver)
|
||
return a ? (a.realName || a.name || a.username) : ''
|
||
},
|
||
suggestedLabel() { return this.suggestedName }
|
||
},
|
||
onLoad(q) {
|
||
this.kind = q.kind || 'leave'
|
||
const spec = this.spec
|
||
const me = store.me
|
||
if (me && spec) this.title = me.name + ' ' + spec.title
|
||
this.boot()
|
||
},
|
||
methods: {
|
||
optionsOf(f) {
|
||
if (f.key === 'targetUsername') return this.peopleOpts.map((p) => p.name)
|
||
return f.options || []
|
||
},
|
||
async boot() {
|
||
try {
|
||
const preview = await oaGet('/workflow/chain-preview?type=' + encodeURIComponent(this.kind), store.token)
|
||
this.approvers = asList(preview && preview.approvers)
|
||
const chain = asList(preview && preview.suggested)
|
||
if (chain[0]) {
|
||
this.suggested = chain[0].username || ''
|
||
this.suggestedName = chain[0].realName || chain[0].name || this.suggested
|
||
}
|
||
if (!this.fixed && !this.approver) this.approver = this.suggested || (this.approvers[0] && this.approvers[0].username) || ''
|
||
} catch (_) {}
|
||
if (this.kind === 'layoff') {
|
||
this.peopleOpts = Object.values(store.people)
|
||
}
|
||
},
|
||
pickApprover(e) {
|
||
const a = this.approvers[e.detail.value]
|
||
this.approver = a ? a.username : ''
|
||
},
|
||
async submit() {
|
||
if (!this.spec) return
|
||
if (!store.can('personal', 'apply')) return toast('没有发起申请权限')
|
||
if (!this.title.trim()) return toast('请填写标题')
|
||
if (this.spec.multiInvoice) {
|
||
const missing = this.invoices.some((r) => Number(r.amount) > 0 && !(r.files && r.files.length))
|
||
if (missing) return toast('请为有金额的发票上传附件')
|
||
}
|
||
let layoffName = ''
|
||
if (this.kind === 'layoff' && this.values.targetUsername) {
|
||
const p = this.peopleOpts.find((x) => x.name === this.values.targetUsername || x.username === this.values.targetUsername)
|
||
if (p) {
|
||
this.values.targetUsername = p.username
|
||
layoffName = p.name
|
||
}
|
||
}
|
||
const body = buildApplyPayload({
|
||
spec: this.spec,
|
||
title: this.title,
|
||
values: this.values,
|
||
invoices: this.invoices,
|
||
expenseRemark: this.remark,
|
||
approverUsername: this.approver,
|
||
suggestedUsername: this.suggested,
|
||
me: store.me,
|
||
layoffTargetName: layoffName,
|
||
attachments: this.attachments
|
||
})
|
||
try {
|
||
await store.submitApply(body)
|
||
toast('已提交', 'success')
|
||
setTimeout(() => uni.navigateBack(), 600)
|
||
} catch (e) {
|
||
toast(e.message || '提交失败')
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.body { padding: 16rpx 24rpx 48rpx; }
|
||
.field { margin-bottom: 24rpx; }
|
||
.lab { font-size: 26rpx; color: #6B7280; display: block; margin-bottom: 10rpx; }
|
||
.box { min-height: 92rpx; background: #fff; border-radius: 16rpx; padding: 22rpx 24rpx; font-size: 30rpx; color: #111827; border: 1rpx solid rgba(17,24,39,0.06); }
|
||
.box.tall { min-height: 180rpx; }
|
||
.box textarea, .box input { width: 100%; font-size: 30rpx; color: #111827; }
|
||
.inv { padding: 24rpx; margin-bottom: 16rpx; }
|
||
.inv .box { margin-top: 12rpx; background: #F5F7FA; }
|
||
.link { color: #1677FF; font-size: 28rpx; font-weight: 600; display: block; margin: 4rpx 8rpx 24rpx; }
|
||
.hint { font-size: 26rpx; color: #111827; line-height: 1.5; }
|
||
.submit { margin-top: 8rpx; }
|
||
</style>
|