feat: 细化差旅票据费用明细分类并自动计算出差补贴
将差旅费用明细拆分为火车票、机票、住宿票、乘车等细分类 型,根据票据字段自动生成行程/事由描述,结合规则引擎自 动计算出差补贴金额,前端适配费用明细编辑和差旅票据审 核交互,补充单元测试覆盖。
This commit is contained in:
@@ -88,6 +88,46 @@
|
||||
|
||||
<div class="detail-grid">
|
||||
<section class="detail-left">
|
||||
<article class="detail-card panel">
|
||||
<div class="detail-card-head">
|
||||
<div>
|
||||
<h3>附加说明</h3>
|
||||
<p>用于说明本次出差或办事目的,例如去哪里、拜访谁、处理什么事项。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="canEditDetailNote" class="detail-note-editor">
|
||||
<textarea
|
||||
v-model="detailNoteEditor"
|
||||
maxlength="500"
|
||||
placeholder="例如:去北京客户现场出差,拜访 XX 客户并处理项目验收事项"
|
||||
aria-label="附加说明"
|
||||
></textarea>
|
||||
<div class="detail-note-editor-meta">
|
||||
<span>仅草稿待提交状态可编辑,提交后将作为明确说明展示。</span>
|
||||
<div class="detail-note-actions">
|
||||
<button
|
||||
v-if="detailNoteDirty"
|
||||
class="inline-action"
|
||||
type="button"
|
||||
:disabled="savingDetailNote"
|
||||
@click="resetDetailNote"
|
||||
>
|
||||
恢复
|
||||
</button>
|
||||
<button
|
||||
class="inline-action primary"
|
||||
type="button"
|
||||
:disabled="!detailNoteDirty || savingDetailNote"
|
||||
@click="saveDetailNote"
|
||||
>
|
||||
{{ savingDetailNote ? '保存中' : '保存说明' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="detail-note readonly">{{ detailNote }}</div>
|
||||
</article>
|
||||
|
||||
<article class="detail-card panel">
|
||||
<div class="detail-card-head">
|
||||
<div>
|
||||
@@ -129,7 +169,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="item in expenseItems" :key="item.id">
|
||||
<tr>
|
||||
<tr :class="{ 'system-generated-row': item.isSystemGenerated }">
|
||||
<td class="expense-time col-time">
|
||||
<template v-if="editingExpenseId === item.id">
|
||||
<div class="cell-editor">
|
||||
@@ -200,8 +240,8 @@
|
||||
<template v-if="editingExpenseId === item.id">
|
||||
<div class="cell-editor editor-stack">
|
||||
<div class="attachment-action-group">
|
||||
<button
|
||||
v-if="isEditableRequest && !item.invoiceId"
|
||||
<button
|
||||
v-if="isEditableRequest && !item.invoiceId && !item.isSystemGenerated"
|
||||
class="icon-action upload"
|
||||
type="button"
|
||||
title="上传单据"
|
||||
@@ -221,8 +261,8 @@
|
||||
>
|
||||
<i class="mdi mdi-eye-outline"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="isEditableRequest && item.invoiceId"
|
||||
<button
|
||||
v-if="isEditableRequest && item.invoiceId && !item.isSystemGenerated"
|
||||
class="icon-action danger"
|
||||
type="button"
|
||||
title="删除附件"
|
||||
@@ -236,9 +276,13 @@
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="attachment-action-group">
|
||||
<div v-if="item.isSystemGenerated" class="system-attachment-note">
|
||||
<i class="mdi mdi-calculator-variant-outline"></i>
|
||||
<span>无需附件</span>
|
||||
</div>
|
||||
<div v-else class="attachment-action-group">
|
||||
<button
|
||||
v-if="isEditableRequest && !item.invoiceId"
|
||||
v-if="isEditableRequest && !item.invoiceId && !item.isSystemGenerated"
|
||||
class="icon-action upload"
|
||||
type="button"
|
||||
title="上传单据"
|
||||
@@ -259,7 +303,7 @@
|
||||
<i class="mdi mdi-eye-outline"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="isEditableRequest && item.invoiceId"
|
||||
v-if="isEditableRequest && item.invoiceId && !item.isSystemGenerated"
|
||||
class="icon-action danger"
|
||||
type="button"
|
||||
title="删除附件"
|
||||
@@ -273,7 +317,11 @@
|
||||
</template>
|
||||
</td>
|
||||
<td v-if="isEditableRequest" class="expense-action-cell col-action">
|
||||
<div v-if="editingExpenseId === item.id" class="row-action-group">
|
||||
<div v-if="item.isSystemGenerated" class="system-row-lock">
|
||||
<i class="mdi mdi-lock-outline"></i>
|
||||
<span>系统计算</span>
|
||||
</div>
|
||||
<div v-else-if="editingExpenseId === item.id" class="row-action-group">
|
||||
<button
|
||||
class="inline-action primary"
|
||||
type="button"
|
||||
@@ -328,6 +376,10 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-if="expenseItems.length" class="expense-total-under-table">
|
||||
<span>金额合计</span>
|
||||
<strong>{{ expenseTotal }}</strong>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article v-if="isEditableRequest" class="detail-card panel validation-card">
|
||||
@@ -339,39 +391,43 @@
|
||||
<span :class="['validation-pill', aiAdvice.tone]">{{ aiAdvice.badge }}</span>
|
||||
</div>
|
||||
<p class="validation-summary">{{ aiAdvice.summary }}</p>
|
||||
<div v-if="aiAdvice.riskCards.length" class="risk-advice-list">
|
||||
<article
|
||||
v-for="card in aiAdvice.riskCards"
|
||||
:key="card.id"
|
||||
:class="['risk-advice-card', card.tone]"
|
||||
<div v-if="aiAdvice.sections.length" class="validation-sections">
|
||||
<section
|
||||
v-for="section in aiAdvice.sections"
|
||||
:key="section.kind"
|
||||
:class="['validation-section', `validation-section--${section.kind}`]"
|
||||
>
|
||||
<div class="risk-advice-card-head">
|
||||
<span>{{ card.label }}</span>
|
||||
<strong>{{ card.title }}</strong>
|
||||
<h4 class="validation-section-title">{{ section.title }}</h4>
|
||||
<ul v-if="section.kind === 'completion'" class="validation-list">
|
||||
<li v-for="item in section.items" :key="item">{{ item }}</li>
|
||||
</ul>
|
||||
<div v-else class="risk-advice-list">
|
||||
<article
|
||||
v-for="card in section.items"
|
||||
:key="card.id"
|
||||
:class="['risk-advice-card', card.tone]"
|
||||
>
|
||||
<div class="risk-advice-card-head">
|
||||
<span>{{ card.label }}</span>
|
||||
<strong>{{ card.title }}</strong>
|
||||
</div>
|
||||
<p class="risk-advice-point">{{ card.risk }}</p>
|
||||
<div class="risk-advice-meta">
|
||||
<div>
|
||||
<span>规则依据</span>
|
||||
<ul>
|
||||
<li v-for="basis in card.ruleBasis" :key="basis">{{ basis }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<span>修改建议</span>
|
||||
<p>{{ card.suggestion }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<p class="risk-advice-point">{{ card.risk }}</p>
|
||||
<div class="risk-advice-meta">
|
||||
<div>
|
||||
<span>规则依据</span>
|
||||
<ul>
|
||||
<li v-for="basis in card.ruleBasis" :key="basis">{{ basis }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<span>修改建议</span>
|
||||
<p>{{ card.suggestion }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
</div>
|
||||
<ul v-if="aiAdvice.items.length" class="validation-list">
|
||||
<li v-for="item in aiAdvice.items" :key="item">{{ item }}</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article class="detail-card panel">
|
||||
<h3>附加说明</h3>
|
||||
<div class="detail-note">{{ detailNote }}</div>
|
||||
</article>
|
||||
|
||||
<article v-if="showLeaderApprovalPanel" class="detail-card panel leader-approval-card">
|
||||
|
||||
Reference in New Issue
Block a user