diff --git a/ruoyi-ui/src/views/hrm/requests/appropriation.vue b/ruoyi-ui/src/views/hrm/requests/appropriation.vue index 0fdc2d6..3021fe3 100644 --- a/ruoyi-ui/src/views/hrm/requests/appropriation.vue +++ b/ruoyi-ui/src/views/hrm/requests/appropriation.vue @@ -77,12 +77,14 @@
拨款明细 - (上传发票后自动识别,删除条目将同步移除对应文件) + (上传发票后自动识别;识别失败或无发票时可手动添加) + 手动添加条目
事由说明 金额(元) + 附件
@@ -100,6 +102,16 @@ class="col-amount" @change="recalcTotal" /> +
+ + + + + + +
@@ -258,6 +270,7 @@ import { addAppropriationReq, checkAppropriationOcrHealth, listFlowNode, listFlo import { getEmployeeByUserId } from '@/api/hrm/employee'; import { ccFlowTask } from '@/api/hrm/flow'; import FileUpload from '@/components/FileUpload'; +import { getToken } from '@/utils/auth'; import UserMultiSelect from '@/components/UserSelect/multi.vue'; import UserSelect from '@/components/UserSelect/single.vue'; @@ -280,6 +293,8 @@ export default { // 发票明细条目 // OCR服务状态:null=检测中,true=正常,false=不可用 ocrAvailable: null, + uploadFileUrl: process.env.VUE_APP_BASE_API + '/system/oss/upload', + uploadHeaders: { Authorization: 'Bearer ' + getToken() }, // 发票明细条目 invoiceItems: [], // OCR加载状态 { ossId: true/false } @@ -368,6 +383,7 @@ export default { async triggerOcr (ossId) { if (this.ocrDoneSet.has(ossId)) return + this.ocrDoneSet.add(ossId) this.$set(this.ocrLoadingMap, ossId, true) try { const res = await ocrAppropriationInvoice(ossId) @@ -396,13 +412,19 @@ export default { amount: totalAmount, sortNo: this.invoiceItems.length }) + } else { + // OCR返回无内容,添加空条目供手动填写 + this.invoiceItems.push({ ossId: ossId, itemName: '', reason: '', amount: 0, sortNo: this.invoiceItems.length }) } this.recalcTotal() + } else { + // 接口无有效数据,添加空条目供手动填写 + this.invoiceItems.push({ ossId: ossId, itemName: '', reason: '', amount: 0, sortNo: this.invoiceItems.length }) } - this.ocrDoneSet.add(ossId) } catch (e) { console.error('[OCR] 识别失败', e) this.$message.warning('发票识别失败,请手动填写事由和金额') + this.invoiceItems.push({ ossId: ossId, itemName: '', reason: '', amount: 0, sortNo: this.invoiceItems.length }) } finally { this.$set(this.ocrLoadingMap, ossId, false) } @@ -429,6 +451,21 @@ export default { this.recalcTotal() }, + addManualItem () { + this.invoiceItems.push({ ossId: null, itemName: '', reason: '', amount: 0, sortNo: this.invoiceItems.length }) + }, + + onRowFileSuccess (res, idx) { + if (res.code === 200 && res.data) { + const ossId = res.data.ossId + this.ocrDoneSet.add(ossId) + this.$set(this.invoiceItems[idx], 'ossId', ossId) + const ids = (this.form.accessoryApplyIds || '').split(',').filter(Boolean) + ids.push(ossId) + this.form.accessoryApplyIds = ids.join(',') + } + }, + recalcTotal () { const total = this.invoiceItems.reduce((sum, item) => sum + (parseFloat(item.amount) || 0), 0) this.form.amount = parseFloat(total.toFixed(2)) @@ -676,12 +713,16 @@ export default { .col-reason { flex: 1; } .col-amount { width: 140px; flex-shrink: 0; } +.col-file { width: 64px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; } .col-action { width: 32px; flex-shrink: 0; display: flex; justify-content: center; } .invoice-table-header .col-reason { flex: 1; } .invoice-table-header .col-amount { width: 140px; } +.invoice-table-header .col-file { width: 64px; } .invoice-table-header .col-action { width: 32px; } +.row-upload { display: inline-flex; } + .invoice-table-footer { display: flex; align-items: center; diff --git a/ruoyi-ui/src/views/hrm/requests/reimburse.vue b/ruoyi-ui/src/views/hrm/requests/reimburse.vue index 7811d12..f7c8f88 100644 --- a/ruoyi-ui/src/views/hrm/requests/reimburse.vue +++ b/ruoyi-ui/src/views/hrm/requests/reimburse.vue @@ -77,12 +77,14 @@
发票明细 - (上传发票后自动识别,删除条目将同步移除对应文件) + (上传发票后自动识别;识别失败或无发票时可手动添加) + 手动添加条目
事由说明 金额(元) + 附件
@@ -100,6 +102,16 @@ class="col-amount" @change="recalcTotal" /> +
+ + + + + + +
@@ -235,6 +247,7 @@ import { addReimburseReq, checkReimburseOcrHealth, listFlowNode, listFlowTemplat import { getEmployeeByUserId } from '@/api/hrm/employee'; import { ccFlowTask } from '@/api/hrm/flow'; import FileUpload from '@/components/FileUpload'; +import { getToken } from '@/utils/auth'; import UserMultiSelect from '@/components/UserSelect/multi.vue'; import UserSelect from '@/components/UserSelect/single.vue'; @@ -256,6 +269,8 @@ export default { reimburseTypeOptions: ['差旅费', '招待费', '办公费', '交通费', '通讯费', '其他'], // OCR服务状态:null=检测中,true=正常,false=不可用 ocrAvailable: null, + uploadFileUrl: process.env.VUE_APP_BASE_API + '/system/oss/upload', + uploadHeaders: { Authorization: 'Bearer ' + getToken() }, // 发票明细条目 invoiceItems: [], // OCR加载状态 { ossId: true/false } @@ -342,6 +357,7 @@ export default { async triggerOcr (ossId) { if (this.ocrDoneSet.has(ossId)) return + this.ocrDoneSet.add(ossId) this.$set(this.ocrLoadingMap, ossId, true) try { const res = await ocrReimburseInvoice(ossId) @@ -370,13 +386,19 @@ export default { amount: totalAmount, sortNo: this.invoiceItems.length }) + } else { + // OCR返回无内容,添加空条目供手动填写 + this.invoiceItems.push({ ossId: ossId, itemName: '', reason: '', amount: 0, sortNo: this.invoiceItems.length }) } this.recalcTotal() + } else { + // 接口无有效数据,添加空条目供手动填写 + this.invoiceItems.push({ ossId: ossId, itemName: '', reason: '', amount: 0, sortNo: this.invoiceItems.length }) } - this.ocrDoneSet.add(ossId) } catch (e) { console.error('[OCR] 识别失败', e) this.$message.warning('发票识别失败,请手动填写事由和金额') + this.invoiceItems.push({ ossId: ossId, itemName: '', reason: '', amount: 0, sortNo: this.invoiceItems.length }) } finally { this.$set(this.ocrLoadingMap, ossId, false) } @@ -403,6 +425,21 @@ export default { this.recalcTotal() }, + addManualItem () { + this.invoiceItems.push({ ossId: null, itemName: '', reason: '', amount: 0, sortNo: this.invoiceItems.length }) + }, + + onRowFileSuccess (res, idx) { + if (res.code === 200 && res.data) { + const ossId = res.data.ossId + this.ocrDoneSet.add(ossId) + this.$set(this.invoiceItems[idx], 'ossId', ossId) + const ids = (this.form.accessoryApplyIds || '').split(',').filter(Boolean) + ids.push(ossId) + this.form.accessoryApplyIds = ids.join(',') + } + }, + recalcTotal () { const total = this.invoiceItems.reduce((sum, item) => sum + (parseFloat(item.amount) || 0), 0) this.form.totalAmount = parseFloat(total.toFixed(2)) @@ -655,6 +692,14 @@ export default { flex-shrink: 0; } +.col-file { + width: 64px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; +} + .col-action { width: 32px; flex-shrink: 0; @@ -664,8 +709,11 @@ export default { .invoice-table-header .col-reason { flex: 1; } .invoice-table-header .col-amount { width: 140px; } +.invoice-table-header .col-file { width: 64px; } .invoice-table-header .col-action { width: 32px; } +.row-upload { display: inline-flex; } + .invoice-table-footer { display: flex; align-items: center;