feat: 增加差旅报销标准测算和财务终审流程
新增差旅报销测算接口及 Spreadsheet 规则解析,审批流程拆分 直属领导审批与财务终审两阶段并细分权限,修复 PDF 文本层 缺失时自动回退 OCR,提交后清理关联会话,前端适配审批流 交互并补充单元测试。
This commit is contained in:
@@ -121,7 +121,14 @@
|
||||
</div>
|
||||
|
||||
<div v-if="message.role === 'assistant' && !message.reviewPayload && message.meta?.length" class="message-meta-row">
|
||||
<span v-for="item in message.meta" :key="item" class="message-meta-chip">{{ item }}</span>
|
||||
<span
|
||||
v-for="item in message.meta"
|
||||
:key="item"
|
||||
class="message-meta-chip"
|
||||
:class="message.metaTone"
|
||||
>
|
||||
{{ item }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="message.role === 'assistant' && !message.reviewPayload && message.riskFlags?.length" class="message-detail-block">
|
||||
@@ -548,6 +555,72 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="travel-calculator-anchor">
|
||||
<button
|
||||
type="button"
|
||||
class="tool-btn composer-side-btn travel-calculator-trigger"
|
||||
:class="{ active: travelCalculatorOpen }"
|
||||
:disabled="submitting || reviewActionBusy || sessionSwitchBusy"
|
||||
aria-label="差旅计算器"
|
||||
title="差旅计算器"
|
||||
:aria-expanded="travelCalculatorOpen"
|
||||
@click.stop="toggleTravelCalculator"
|
||||
>
|
||||
<i class="mdi mdi-calculator"></i>
|
||||
</button>
|
||||
<div
|
||||
v-if="travelCalculatorOpen"
|
||||
class="travel-calculator-popover"
|
||||
role="dialog"
|
||||
aria-label="差旅计算器"
|
||||
@click.stop
|
||||
>
|
||||
<div class="travel-calculator-mini-head">
|
||||
<strong>差旅计算器</strong>
|
||||
<span>按规则中心差旅表测算</span>
|
||||
</div>
|
||||
<div class="travel-calculator-form">
|
||||
<label class="travel-calculator-field">
|
||||
<span>实际天数</span>
|
||||
<input
|
||||
v-model="travelCalculatorForm.days"
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
inputmode="numeric"
|
||||
:disabled="travelCalculatorBusy"
|
||||
@keydown.enter.prevent="submitTravelCalculator"
|
||||
/>
|
||||
</label>
|
||||
<label class="travel-calculator-field">
|
||||
<span>出差地点</span>
|
||||
<input
|
||||
v-model="travelCalculatorForm.location"
|
||||
type="text"
|
||||
placeholder="例如:北京、成都"
|
||||
:disabled="travelCalculatorBusy"
|
||||
@keydown.enter.prevent="submitTravelCalculator"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<p v-if="travelCalculatorError" class="travel-calculator-error">
|
||||
{{ travelCalculatorError }}
|
||||
</p>
|
||||
<div class="composer-date-popover-actions">
|
||||
<button type="button" class="composer-date-cancel-btn" :disabled="travelCalculatorBusy" @click="closeTravelCalculator">
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="composer-date-apply-btn"
|
||||
:disabled="!travelCalculatorCanSubmit"
|
||||
@click="submitTravelCalculator"
|
||||
>
|
||||
{{ travelCalculatorBusy ? '计算中...' : 'AI计算' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="composer-shell">
|
||||
@@ -783,7 +856,8 @@
|
||||
:class="{
|
||||
editable: item.editor,
|
||||
editing: reviewInlineEditorKey === item.key,
|
||||
invalid: Boolean(reviewInlineErrors[item.key])
|
||||
invalid: Boolean(reviewInlineErrors[item.key]),
|
||||
wide: item.wide
|
||||
}"
|
||||
@click="openInlineReviewEditor(item.key)"
|
||||
>
|
||||
@@ -831,6 +905,19 @@
|
||||
@keydown.enter.prevent="commitInlineReviewEditor"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="reviewInlineEditorKey === item.key && item.editor === 'textarea'">
|
||||
<textarea
|
||||
v-model="reviewInlineForm[item.modelKey]"
|
||||
class="review-inline-input review-inline-textarea"
|
||||
:class="{ invalid: Boolean(reviewInlineErrors[item.key]) }"
|
||||
:placeholder="item.placeholder"
|
||||
rows="3"
|
||||
@click.stop
|
||||
@input="clearInlineReviewFieldError(item.key)"
|
||||
@blur="commitInlineReviewEditor"
|
||||
@keydown.enter.stop
|
||||
></textarea>
|
||||
</template>
|
||||
<template v-else-if="reviewInlineEditorKey === item.key && item.editor === 'select'">
|
||||
<div class="review-inline-select-list" @click.stop>
|
||||
<button
|
||||
@@ -1091,12 +1178,9 @@
|
||||
<section class="review-side-card review-side-risk-card">
|
||||
<div class="review-side-head">
|
||||
<div class="review-side-head-copy">
|
||||
<strong>合规提醒 / 风险评分</strong>
|
||||
<p>结合本体附件要求和识别结果,集中查看当前票据风险。</p>
|
||||
<strong>差旅合规提示</strong>
|
||||
<p>结合票据识别结果与差旅规则,逐项查看需要处理的风险点。</p>
|
||||
</div>
|
||||
<span class="review-side-risk-score" :class="{ empty: reviewRiskScore === null }">
|
||||
{{ reviewRiskScore === null ? '无' : `${reviewRiskScore}/100` }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="review-side-risk-summary">{{ reviewRiskSummary }}</p>
|
||||
<div v-if="reviewRiskItems.length" class="review-side-risk-list">
|
||||
@@ -1106,9 +1190,9 @@
|
||||
type="button"
|
||||
class="review-side-risk-item"
|
||||
:class="item.level"
|
||||
@click="openReviewRiskDetail(item)"
|
||||
@click="appendReviewRiskBriefToConversation(item)"
|
||||
>
|
||||
<span class="review-side-risk-icon">
|
||||
<span class="review-side-risk-icon" :title="item.levelLabel">
|
||||
<i :class="item.icon"></i>
|
||||
</span>
|
||||
<span class="review-side-risk-copy">
|
||||
@@ -1116,7 +1200,6 @@
|
||||
<p>{{ item.summary }}</p>
|
||||
</span>
|
||||
<span class="review-side-risk-meta">
|
||||
{{ item.levelLabel }}
|
||||
<i class="mdi mdi-chevron-right"></i>
|
||||
</span>
|
||||
</button>
|
||||
@@ -1125,8 +1208,8 @@
|
||||
<span class="review-side-empty-icon">
|
||||
<i class="mdi mdi-shield-check-outline"></i>
|
||||
</span>
|
||||
<strong>暂无风险评分</strong>
|
||||
<p>当前版本还没有返回结构化风险评分结果,这里先不展示虚拟分数。</p>
|
||||
<strong>暂无风险提示</strong>
|
||||
<p>当前没有需要额外处理的结构化风险点。</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -1222,41 +1305,6 @@
|
||||
@confirm="confirmCancelReview"
|
||||
/>
|
||||
|
||||
<Transition name="assistant-modal">
|
||||
<div v-if="reviewRiskDetailDialog.open" class="assistant-overlay review-overlay">
|
||||
<section class="review-risk-detail-modal">
|
||||
<header class="review-risk-detail-head">
|
||||
<div>
|
||||
<span class="assistant-badge warning">{{ reviewRiskDetailDialog.item?.sourceLabel || 'AI预审' }}</span>
|
||||
<h3>{{ reviewRiskDetailDialog.item?.title || '风险提示' }}</h3>
|
||||
</div>
|
||||
<button class="close-btn" type="button" aria-label="关闭风险说明" @click="closeReviewRiskDetail">
|
||||
<i class="mdi mdi-close"></i>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div class="review-risk-detail-body">
|
||||
<div class="review-risk-detail-level" :class="reviewRiskDetailDialog.item?.level">
|
||||
<i :class="reviewRiskDetailDialog.item?.icon || 'mdi mdi-information-outline'"></i>
|
||||
<span>{{ reviewRiskDetailDialog.item?.levelLabel || '提示' }}</span>
|
||||
</div>
|
||||
<article class="review-risk-detail-section">
|
||||
<strong>提示情况</strong>
|
||||
<p>{{ reviewRiskDetailDialog.item?.summary }}</p>
|
||||
</article>
|
||||
<article class="review-risk-detail-section">
|
||||
<strong>详细解释</strong>
|
||||
<p>{{ reviewRiskDetailDialog.item?.detail }}</p>
|
||||
</article>
|
||||
<article class="review-risk-detail-section">
|
||||
<strong>处理建议</strong>
|
||||
<p>{{ reviewRiskDetailDialog.item?.suggestion }}</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<Transition name="assistant-modal">
|
||||
<div v-if="uploadDecisionDialogOpen" class="assistant-overlay review-overlay">
|
||||
<section class="review-confirm-modal review-upload-decision-modal">
|
||||
|
||||
Reference in New Issue
Block a user