2026-05-06 11:00:38 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<section class="approval-page">
|
|
|
|
|
|
<div class="approval-detail">
|
|
|
|
|
|
<div class="detail-scroll">
|
2026-05-13 03:33:11 +00:00
|
|
|
|
<article class="detail-hero panel">
|
2026-05-14 07:10:46 +00:00
|
|
|
|
<div class="hero-banner">
|
|
|
|
|
|
<div class="hero-banner-main">
|
|
|
|
|
|
<div class="applicant-card">
|
|
|
|
|
|
<div class="portrait">
|
|
|
|
|
|
<img src="/assets/person.png" alt="" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="applicant-copy">
|
|
|
|
|
|
<div class="applicant-name-row">
|
|
|
|
|
|
<h2>{{ profile.name }}</h2>
|
|
|
|
|
|
<span class="identity-badge">{{ profile.identity }}</span>
|
|
|
|
|
|
</div>
|
2026-05-20 21:00:47 +08:00
|
|
|
|
<div class="applicant-profile-meta">
|
|
|
|
|
|
<div class="applicant-profile-meta__org">
|
|
|
|
|
|
<span class="applicant-meta-item">
|
|
|
|
|
|
<em>部门</em>
|
|
|
|
|
|
<strong>{{ profile.department }}</strong>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span class="applicant-meta-item applicant-meta-item--sub">
|
|
|
|
|
|
<em>直属上司</em>
|
|
|
|
|
|
<strong>{{ profile.manager }}</strong>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="applicant-profile-meta__role">
|
|
|
|
|
|
<span class="applicant-meta-item">
|
|
|
|
|
|
<em>职级</em>
|
|
|
|
|
|
<strong>{{ profile.grade }}</strong>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span class="applicant-meta-item">
|
|
|
|
|
|
<em>岗位</em>
|
|
|
|
|
|
<strong>{{ profile.position }}</strong>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
2026-05-13 06:56:30 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-05-13 06:51:12 +00:00
|
|
|
|
</div>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
|
2026-05-14 07:10:46 +00:00
|
|
|
|
<div class="hero-fact-grid">
|
|
|
|
|
|
<div v-for="item in heroFactItems" :key="item.key" class="hero-fact">
|
|
|
|
|
|
<div class="hero-fact-label">
|
|
|
|
|
|
<i v-if="item.icon" :class="item.icon"></i>
|
|
|
|
|
|
<span>{{ item.label }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<strong :class="item.valueClass">{{ item.value }}</strong>
|
|
|
|
|
|
</div>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
</div>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-05-14 07:10:46 +00:00
|
|
|
|
</article>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
|
2026-05-14 07:10:46 +00:00
|
|
|
|
<article class="progress-card panel">
|
2026-05-13 03:33:11 +00:00
|
|
|
|
<div class="progress-block">
|
|
|
|
|
|
<div class="progress-head">
|
2026-05-25 13:35:39 +08:00
|
|
|
|
<h3>{{ isApplicationDocument ? '申请进度' : isTravelRequest ? '差旅进度' : '报销进度' }}</h3>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="progress-line" :style="{ '--progress-columns': progressSteps.length }">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="step in progressSteps"
|
|
|
|
|
|
:key="step.label"
|
|
|
|
|
|
class="progress-step"
|
2026-05-13 06:51:12 +00:00
|
|
|
|
:class="{ active: step.active, current: step.current, done: step.done }"
|
2026-05-13 03:33:11 +00:00
|
|
|
|
>
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<i
|
|
|
|
|
|
v-if="step.current"
|
|
|
|
|
|
v-motion
|
|
|
|
|
|
class="current-progress-ring"
|
|
|
|
|
|
:initial="currentProgressRingMotion.initial"
|
|
|
|
|
|
:enter="currentProgressRingMotion.enter"
|
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
|
></i>
|
|
|
|
|
|
<i v-if="step.done" class="mdi mdi-check"></i>
|
|
|
|
|
|
<template v-else>{{ step.index }}</template>
|
|
|
|
|
|
</span>
|
2026-05-20 21:00:47 +08:00
|
|
|
|
<div class="progress-step-copy" :title="step.title || step.detail || step.time">
|
|
|
|
|
|
<strong>{{ step.label }}</strong>
|
|
|
|
|
|
<small class="progress-step-status">{{ step.time }}</small>
|
|
|
|
|
|
<em v-if="step.detail" class="progress-step-meta">{{ step.detail }}</em>
|
|
|
|
|
|
</div>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
</div>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
</article>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="detail-grid">
|
|
|
|
|
|
<section class="detail-left">
|
2026-05-26 09:15:14 +08:00
|
|
|
|
<article v-if="!isApplicationDocument" class="detail-card panel">
|
2026-05-21 10:57:06 +08:00
|
|
|
|
<div class="detail-card-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3>附加说明</h3>
|
|
|
|
|
|
<p>用于说明本次出差或办事目的,例如去哪里、拜访谁、处理什么事项。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="canEditDetailNote" class="detail-note-editor">
|
|
|
|
|
|
<textarea
|
2026-05-22 16:00:19 +08:00
|
|
|
|
v-model="detailNoteEditorView"
|
2026-05-21 10:57:06 +08:00
|
|
|
|
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>
|
2026-05-21 23:53:03 +08:00
|
|
|
|
<div v-else class="detail-note readonly">
|
|
|
|
|
|
<p>{{ detailNote }}</p>
|
|
|
|
|
|
</div>
|
2026-05-21 10:57:06 +08:00
|
|
|
|
</article>
|
|
|
|
|
|
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<article class="detail-card panel">
|
|
|
|
|
|
<div class="detail-card-head">
|
|
|
|
|
|
<div>
|
2026-05-26 09:15:14 +08:00
|
|
|
|
<h3 class="detail-card-title-with-icon">
|
|
|
|
|
|
<i v-if="isApplicationDocument" class="mdi mdi-file-document-outline"></i>
|
|
|
|
|
|
<span>{{ isApplicationDocument ? '申请详情' : '费用明细' }}</span>
|
|
|
|
|
|
</h3>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
<p>
|
2026-05-25 13:35:39 +08:00
|
|
|
|
{{
|
|
|
|
|
|
isApplicationDocument
|
2026-05-26 09:15:14 +08:00
|
|
|
|
? '展示本次申请的事实信息、职级规则测算和用户预估费用。'
|
2026-05-25 13:35:39 +08:00
|
|
|
|
: isTravelRequest
|
|
|
|
|
|
? '按出行时间逐笔核对票据与差旅规则。'
|
|
|
|
|
|
: '按业务发生时间逐笔核对票据、用途说明与 AI 识别结果。'
|
|
|
|
|
|
}}
|
2026-05-13 03:33:11 +00:00
|
|
|
|
</p>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</div>
|
2026-05-25 13:35:39 +08:00
|
|
|
|
<div v-if="!isApplicationDocument" class="detail-card-actions">
|
2026-05-20 21:00:47 +08:00
|
|
|
|
<button v-if="canOpenAiEntry" class="smart-entry-btn" type="button" @click="openAiEntry">
|
2026-05-13 06:51:12 +00:00
|
|
|
|
<i class="mdi mdi-robot-outline"></i>
|
|
|
|
|
|
<span>智能录入</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
2026-05-20 21:00:47 +08:00
|
|
|
|
v-if="isEditableRequest"
|
2026-05-13 06:51:12 +00:00
|
|
|
|
class="smart-entry-btn secondary"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:disabled="actionBusy"
|
|
|
|
|
|
@click="handleAddExpenseItem"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i class="mdi mdi-plus-circle-outline"></i>
|
|
|
|
|
|
<span>{{ creatingExpense ? '新增中' : '增加明细' }}</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</div>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
|
2026-05-26 09:15:14 +08:00
|
|
|
|
<div v-if="isApplicationDocument" class="application-detail-facts">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="item in applicationDetailFactItems"
|
|
|
|
|
|
:key="item.key"
|
|
|
|
|
|
class="application-detail-fact"
|
|
|
|
|
|
:class="{ highlight: item.highlight, emphasis: item.emphasis }"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span>{{ item.label }}</span>
|
|
|
|
|
|
<strong>{{ item.value }}</strong>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-27 17:31:27 +08:00
|
|
|
|
<TravelRequestBudgetAnalysis
|
|
|
|
|
|
v-if="showBudgetAnalysis"
|
|
|
|
|
|
:claim-id="request.claimId"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
2026-05-26 09:15:14 +08:00
|
|
|
|
<div v-if="showApplicationLeaderOpinion" class="application-leader-opinion">
|
|
|
|
|
|
<div class="application-leader-opinion-head">
|
|
|
|
|
|
<span><i class="mdi mdi-account-tie-outline"></i>领导意见</span>
|
|
|
|
|
|
<strong v-if="leaderApprovalReadonlyMeta">{{ leaderApprovalReadonlyMeta }}</strong>
|
|
|
|
|
|
</div>
|
2026-05-27 14:35:17 +08:00
|
|
|
|
<div v-if="hasLeaderApprovalEvents" class="application-leader-opinion-timeline" aria-label="领导批复事件流">
|
|
|
|
|
|
<article
|
|
|
|
|
|
v-for="event in leaderApprovalEvents"
|
|
|
|
|
|
:key="event.id"
|
|
|
|
|
|
class="application-leader-opinion-event"
|
|
|
|
|
|
:class="event.tone"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="application-leader-opinion-event-head">
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<i :class="event.type === 'returned' ? 'mdi mdi-arrow-u-left-top' : 'mdi mdi-check-circle-outline'"></i>
|
|
|
|
|
|
{{ event.title }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<time v-if="event.time">{{ event.time }}</time>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p>{{ event.opinion }}</p>
|
|
|
|
|
|
<footer>
|
|
|
|
|
|
<span>{{ event.operator }}</span>
|
|
|
|
|
|
<span v-if="event.returnCount">第 {{ event.returnCount }} 次退回</span>
|
|
|
|
|
|
</footer>
|
|
|
|
|
|
</article>
|
2026-05-26 09:15:14 +08:00
|
|
|
|
</div>
|
2026-05-25 13:35:39 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-26 09:15:14 +08:00
|
|
|
|
<div v-if="!isApplicationDocument" class="detail-expense-table">
|
2026-05-13 03:33:11 +00:00
|
|
|
|
<table>
|
|
|
|
|
|
<thead>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<tr>
|
2026-05-20 21:00:47 +08:00
|
|
|
|
<th class="col-filled-at">填写时间</th>
|
2026-05-22 16:00:19 +08:00
|
|
|
|
<th class="col-time">发生时间</th>
|
2026-05-13 06:51:12 +00:00
|
|
|
|
<th class="col-type">费用项目</th>
|
|
|
|
|
|
<th class="col-desc">说明</th>
|
|
|
|
|
|
<th class="col-amount">金额</th>
|
|
|
|
|
|
<th class="col-attachment">附件材料</th>
|
2026-05-20 14:32:35 +08:00
|
|
|
|
<th v-if="isEditableRequest" class="col-action">操作</th>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<template v-for="item in expenseItems" :key="item.id">
|
2026-05-21 10:57:06 +08:00
|
|
|
|
<tr :class="{ 'system-generated-row': item.isSystemGenerated }">
|
2026-05-22 16:00:19 +08:00
|
|
|
|
<td class="expense-filled-at col-filled-at">
|
|
|
|
|
|
<strong>{{ item.filledAt }}</strong>
|
|
|
|
|
|
<span>条款填写时间</span>
|
|
|
|
|
|
</td>
|
2026-05-21 23:53:03 +08:00
|
|
|
|
<td :class="['expense-time col-time', { 'has-major-risk': isMajorExpenseRisk(item) }]">
|
|
|
|
|
|
<i
|
|
|
|
|
|
v-if="isMajorExpenseRisk(item)"
|
|
|
|
|
|
class="mdi mdi-alert expense-risk-indicator"
|
|
|
|
|
|
:title="resolveExpenseRiskIndicatorTitle(item)"
|
|
|
|
|
|
:aria-label="resolveExpenseRiskIndicatorTitle(item)"
|
|
|
|
|
|
></i>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
<template v-if="editingExpenseId === item.id">
|
|
|
|
|
|
<div class="cell-editor">
|
|
|
|
|
|
<input v-model="expenseEditor.itemDate" class="editor-input" type="date" />
|
|
|
|
|
|
<span>{{ item.dayLabel }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<strong>{{ item.time }}</strong>
|
|
|
|
|
|
<span>{{ item.dayLabel }}</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</td>
|
2026-05-13 06:51:12 +00:00
|
|
|
|
<td class="expense-type col-type">
|
2026-05-13 03:33:11 +00:00
|
|
|
|
<template v-if="editingExpenseId === item.id">
|
|
|
|
|
|
<div class="cell-editor">
|
2026-05-27 09:17:57 +08:00
|
|
|
|
<EnterpriseSelect v-model="expenseEditor.itemType" class="editor-select" :options="expenseTypeOptions" size="small" />
|
2026-05-13 03:33:11 +00:00
|
|
|
|
<span>编辑费用项目</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<strong>{{ item.name }}</strong>
|
|
|
|
|
|
<span>{{ item.category }}</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</td>
|
2026-05-13 06:51:12 +00:00
|
|
|
|
<td class="expense-desc col-desc">
|
2026-05-13 03:33:11 +00:00
|
|
|
|
<template v-if="editingExpenseId === item.id">
|
2026-05-20 21:00:47 +08:00
|
|
|
|
<div class="cell-editor">
|
2026-05-21 14:24:51 +08:00
|
|
|
|
<input
|
|
|
|
|
|
v-model="expenseEditor.itemReason"
|
|
|
|
|
|
class="editor-input"
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
:placeholder="resolveExpenseReasonPlaceholder(expenseEditor.itemType)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span>{{ resolveExpenseReasonHelper(expenseEditor.itemType) }}</span>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<strong>{{ item.desc }}</strong>
|
|
|
|
|
|
<span>{{ item.detail }}</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</td>
|
2026-05-13 06:51:12 +00:00
|
|
|
|
<td class="expense-amount col-amount">
|
2026-05-13 03:33:11 +00:00
|
|
|
|
<template v-if="editingExpenseId === item.id">
|
|
|
|
|
|
<div class="cell-editor">
|
2026-05-13 06:51:12 +00:00
|
|
|
|
<label class="currency-editor">
|
|
|
|
|
|
<span>¥</span>
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model="expenseEditor.itemAmount"
|
|
|
|
|
|
class="editor-input"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
min="0"
|
|
|
|
|
|
step="0.01"
|
|
|
|
|
|
placeholder="输入金额"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
<span>保存后自动格式化为人民币</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<strong>{{ item.amount }}</strong>
|
|
|
|
|
|
<span v-if="item.tone !== 'ok'" :class="['over-tag', item.tone]">{{ item.status }}</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</td>
|
2026-05-13 06:51:12 +00:00
|
|
|
|
<td class="expense-attachment col-attachment">
|
2026-05-13 03:33:11 +00:00
|
|
|
|
<template v-if="editingExpenseId === item.id">
|
|
|
|
|
|
<div class="cell-editor editor-stack">
|
2026-05-13 06:51:12 +00:00
|
|
|
|
<div class="attachment-action-group">
|
2026-05-21 10:57:06 +08:00
|
|
|
|
<button
|
|
|
|
|
|
v-if="isEditableRequest && !item.invoiceId && !item.isSystemGenerated"
|
2026-05-13 06:51:12 +00:00
|
|
|
|
class="icon-action upload"
|
|
|
|
|
|
type="button"
|
2026-05-20 21:00:47 +08:00
|
|
|
|
title="上传单据"
|
|
|
|
|
|
aria-label="上传单据"
|
2026-05-13 06:51:12 +00:00
|
|
|
|
:disabled="actionBusy"
|
|
|
|
|
|
@click="triggerExpenseUpload(item)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i :class="uploadingExpenseId === item.id ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-file-upload-outline'"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-if="canPreviewAttachment(item)"
|
|
|
|
|
|
class="icon-action preview"
|
|
|
|
|
|
type="button"
|
2026-05-20 21:00:47 +08:00
|
|
|
|
:title="resolveAttachmentPreviewTitle(item)"
|
|
|
|
|
|
:aria-label="resolveAttachmentPreviewTitle(item)"
|
2026-05-13 06:51:12 +00:00
|
|
|
|
@click="openAttachmentPreview(item)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i class="mdi mdi-eye-outline"></i>
|
|
|
|
|
|
</button>
|
2026-05-21 10:57:06 +08:00
|
|
|
|
<button
|
|
|
|
|
|
v-if="isEditableRequest && item.invoiceId && !item.isSystemGenerated"
|
2026-05-13 06:51:12 +00:00
|
|
|
|
class="icon-action danger"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
title="删除附件"
|
2026-05-20 21:00:47 +08:00
|
|
|
|
aria-label="删除附件"
|
2026-05-13 06:51:12 +00:00
|
|
|
|
:disabled="deletingAttachmentId === item.id"
|
|
|
|
|
|
@click="removeExpenseAttachment(item)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i :class="deletingAttachmentId === item.id ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-close-thick'"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-else>
|
2026-05-21 10:57:06 +08:00
|
|
|
|
<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">
|
2026-05-13 06:51:12 +00:00
|
|
|
|
<button
|
2026-05-21 10:57:06 +08:00
|
|
|
|
v-if="isEditableRequest && !item.invoiceId && !item.isSystemGenerated"
|
2026-05-13 06:51:12 +00:00
|
|
|
|
class="icon-action upload"
|
|
|
|
|
|
type="button"
|
2026-05-20 21:00:47 +08:00
|
|
|
|
title="上传单据"
|
|
|
|
|
|
aria-label="上传单据"
|
2026-05-13 06:51:12 +00:00
|
|
|
|
:disabled="actionBusy"
|
|
|
|
|
|
@click="triggerExpenseUpload(item)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i :class="uploadingExpenseId === item.id ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-file-upload-outline'"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-if="canPreviewAttachment(item)"
|
|
|
|
|
|
class="icon-action preview"
|
|
|
|
|
|
type="button"
|
2026-05-20 21:00:47 +08:00
|
|
|
|
:title="resolveAttachmentPreviewTitle(item)"
|
|
|
|
|
|
:aria-label="resolveAttachmentPreviewTitle(item)"
|
2026-05-13 06:51:12 +00:00
|
|
|
|
@click="openAttachmentPreview(item)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i class="mdi mdi-eye-outline"></i>
|
|
|
|
|
|
</button>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
<button
|
2026-05-21 10:57:06 +08:00
|
|
|
|
v-if="isEditableRequest && item.invoiceId && !item.isSystemGenerated"
|
2026-05-13 06:51:12 +00:00
|
|
|
|
class="icon-action danger"
|
2026-05-13 03:33:11 +00:00
|
|
|
|
type="button"
|
2026-05-13 06:51:12 +00:00
|
|
|
|
title="删除附件"
|
2026-05-20 21:00:47 +08:00
|
|
|
|
aria-label="删除附件"
|
2026-05-13 06:51:12 +00:00
|
|
|
|
:disabled="deletingAttachmentId === item.id"
|
|
|
|
|
|
@click="removeExpenseAttachment(item)"
|
2026-05-13 03:33:11 +00:00
|
|
|
|
>
|
2026-05-13 06:51:12 +00:00
|
|
|
|
<i :class="deletingAttachmentId === item.id ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-close-thick'"></i>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</td>
|
2026-05-20 14:32:35 +08:00
|
|
|
|
<td v-if="isEditableRequest" class="expense-action-cell col-action">
|
2026-05-21 10:57:06 +08:00
|
|
|
|
<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">
|
2026-05-13 03:33:11 +00:00
|
|
|
|
<button
|
|
|
|
|
|
class="inline-action primary"
|
|
|
|
|
|
type="button"
|
2026-05-21 14:24:51 +08:00
|
|
|
|
:disabled="actionBusy"
|
2026-05-13 03:33:11 +00:00
|
|
|
|
@click="saveExpenseEdit(item)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ savingExpenseId === item.id ? '保存中' : '保存' }}
|
|
|
|
|
|
</button>
|
2026-05-13 06:51:12 +00:00
|
|
|
|
<button
|
|
|
|
|
|
class="inline-action danger"
|
|
|
|
|
|
type="button"
|
2026-05-21 14:24:51 +08:00
|
|
|
|
:disabled="actionBusy"
|
2026-05-13 06:51:12 +00:00
|
|
|
|
@click="removeExpenseItem(item)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ deletingExpenseId === item.id ? '删除中' : '删除' }}
|
|
|
|
|
|
</button>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
</div>
|
2026-05-13 06:51:12 +00:00
|
|
|
|
<div v-else class="row-action-group">
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="inline-action"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:disabled="actionBusy"
|
|
|
|
|
|
@click="startExpenseEdit(item)"
|
|
|
|
|
|
>
|
|
|
|
|
|
编辑
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="inline-action danger"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:disabled="actionBusy"
|
|
|
|
|
|
@click="removeExpenseItem(item)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ deletingExpenseId === item.id ? '删除中' : '删除' }}
|
|
|
|
|
|
</button>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</template>
|
2026-05-13 06:51:12 +00:00
|
|
|
|
<tr v-if="!expenseItems.length" class="empty-row">
|
|
|
|
|
|
<td :colspan="expenseTableColumnCount" class="empty-row-cell">
|
|
|
|
|
|
当前还没有费用明细,点击右上角“增加明细”继续补充。
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
2026-05-25 13:35:39 +08:00
|
|
|
|
<div v-if="expenseItems.length && !isApplicationDocument" class="expense-total-under-table">
|
2026-05-21 10:57:06 +08:00
|
|
|
|
<span>金额合计</span>
|
|
|
|
|
|
<strong>{{ expenseTotal }}</strong>
|
|
|
|
|
|
</div>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
</article>
|
|
|
|
|
|
|
2026-05-22 16:00:19 +08:00
|
|
|
|
<article v-if="showAiAdvicePanel" class="detail-card panel validation-card">
|
2026-05-13 03:33:11 +00:00
|
|
|
|
<div class="validation-head">
|
2026-05-13 06:51:12 +00:00
|
|
|
|
<div>
|
2026-05-22 16:00:19 +08:00
|
|
|
|
<h3>{{ aiAdviceTitle }}</h3>
|
|
|
|
|
|
<p>{{ aiAdviceHint }}</p>
|
2026-05-13 06:51:12 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<span :class="['validation-pill', aiAdvice.tone]">{{ aiAdvice.badge }}</span>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
</div>
|
2026-05-13 06:51:12 +00:00
|
|
|
|
<p class="validation-summary">{{ aiAdvice.summary }}</p>
|
2026-05-21 10:57:06 +08:00
|
|
|
|
<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}`]"
|
2026-05-20 21:00:47 +08:00
|
|
|
|
>
|
2026-05-21 10:57:06 +08:00
|
|
|
|
<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>
|
2026-05-20 21:00:47 +08:00
|
|
|
|
</div>
|
2026-05-21 10:57:06 +08:00
|
|
|
|
</section>
|
2026-05-20 21:00:47 +08:00
|
|
|
|
</div>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
</article>
|
2026-05-20 21:00:47 +08:00
|
|
|
|
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</section>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<footer class="detail-actions">
|
2026-05-13 03:33:11 +00:00
|
|
|
|
<button class="back-action" type="button" @click="emit('backToRequests')">
|
|
|
|
|
|
<i class="mdi mdi-arrow-left"></i>
|
2026-05-20 21:00:47 +08:00
|
|
|
|
<span>{{ backLabel }}</span>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
</button>
|
2026-05-20 14:32:35 +08:00
|
|
|
|
<div v-if="isEditableRequest" class="approval-action-group" aria-label="申请操作">
|
2026-05-20 14:21:56 +08:00
|
|
|
|
<button class="reject-action" type="button" :disabled="actionBusy" @click="handleDeleteRequest">
|
2026-05-13 03:33:11 +00:00
|
|
|
|
<i class="mdi mdi-trash-can-outline"></i>
|
2026-05-20 14:32:35 +08:00
|
|
|
|
{{ deleteBusy ? '删除中' : deleteActionLabel }}
|
2026-05-13 03:33:11 +00:00
|
|
|
|
</button>
|
|
|
|
|
|
<button class="approve-action" type="button" :disabled="!canSubmit" @click="handleSubmit">
|
|
|
|
|
|
<i class="mdi mdi-send-circle-outline"></i>
|
|
|
|
|
|
{{ submitBusy ? '提交中' : '提交审批' }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-05-26 09:15:14 +08:00
|
|
|
|
<div v-else-if="canReturnRequest || canApproveRequest || canDeleteRequest" class="approval-action-group" aria-label="单据管理操作">
|
2026-05-20 14:21:56 +08:00
|
|
|
|
<button
|
|
|
|
|
|
v-if="canReturnRequest"
|
|
|
|
|
|
class="return-action"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:disabled="actionBusy"
|
|
|
|
|
|
@click="handleReturnRequest"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i class="mdi mdi-undo"></i>
|
2026-05-25 13:35:39 +08:00
|
|
|
|
{{ returnBusy ? '退回中' : isApplicationDocument ? '退回申请' : '退回单据' }}
|
2026-05-20 14:21:56 +08:00
|
|
|
|
</button>
|
2026-05-20 21:00:47 +08:00
|
|
|
|
<button
|
|
|
|
|
|
v-if="canApproveRequest"
|
|
|
|
|
|
class="approve-action"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:disabled="actionBusy"
|
|
|
|
|
|
@click="handleApproveRequest"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i class="mdi mdi-check-circle-outline"></i>
|
2026-05-26 09:15:14 +08:00
|
|
|
|
{{ approveBusy ? approveBusyLabel : approveActionLabel }}
|
2026-05-20 21:00:47 +08:00
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
2026-05-26 09:15:14 +08:00
|
|
|
|
v-if="canDeleteRequest"
|
2026-05-20 21:00:47 +08:00
|
|
|
|
class="reject-action"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:disabled="actionBusy"
|
|
|
|
|
|
@click="handleDeleteRequest"
|
|
|
|
|
|
>
|
2026-05-20 14:21:56 +08:00
|
|
|
|
<i class="mdi mdi-trash-can-outline"></i>
|
2026-05-25 13:35:39 +08:00
|
|
|
|
{{ deleteBusy ? '删除中' : isApplicationDocument ? '删除申请' : '删除单据' }}
|
2026-05-20 14:21:56 +08:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-05-25 13:35:39 +08:00
|
|
|
|
<p v-else class="detail-action-hint">
|
|
|
|
|
|
{{ isApplicationDocument ? '当前申请单已进入流程,详情页仅展示状态与申请信息。' : '当前单据已进入流程,详情页仅展示状态与费用明细。' }}
|
|
|
|
|
|
</p>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</footer>
|
|
|
|
|
|
</div>
|
2026-05-13 03:33:11 +00:00
|
|
|
|
|
2026-05-13 06:51:12 +00:00
|
|
|
|
<input
|
|
|
|
|
|
ref="expenseUploadInput"
|
|
|
|
|
|
class="expense-upload-input"
|
|
|
|
|
|
type="file"
|
|
|
|
|
|
accept="image/*,.pdf"
|
|
|
|
|
|
@change="handleExpenseFileChange"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<Transition name="shared-confirm">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="attachmentPreviewOpen"
|
|
|
|
|
|
class="attachment-preview-mask"
|
|
|
|
|
|
role="presentation"
|
|
|
|
|
|
@click.self="closeAttachmentPreview"
|
|
|
|
|
|
>
|
|
|
|
|
|
<section class="attachment-preview-card" role="dialog" aria-modal="true" @click.stop>
|
|
|
|
|
|
<div class="attachment-preview-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<span class="attachment-preview-badge">附件预览</span>
|
|
|
|
|
|
<h4>{{ attachmentPreviewName || '当前附件' }}</h4>
|
|
|
|
|
|
</div>
|
2026-05-20 21:00:47 +08:00
|
|
|
|
<div class="attachment-preview-toolbar">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-if="canNavigateAttachmentPreview"
|
|
|
|
|
|
class="attachment-preview-nav"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
title="上一份附件"
|
|
|
|
|
|
:disabled="attachmentPreviewLoading"
|
|
|
|
|
|
@click="goToPreviousAttachmentPreview"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i class="mdi mdi-chevron-left"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<span v-if="attachmentPreviewIndexLabel" class="attachment-preview-count">{{ attachmentPreviewIndexLabel }}</span>
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-if="canNavigateAttachmentPreview"
|
|
|
|
|
|
class="attachment-preview-nav"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
title="下一份附件"
|
|
|
|
|
|
:disabled="attachmentPreviewLoading"
|
|
|
|
|
|
@click="goToNextAttachmentPreview"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i class="mdi mdi-chevron-right"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-05-13 06:51:12 +00:00
|
|
|
|
<button class="attachment-preview-close" type="button" @click="closeAttachmentPreview">
|
|
|
|
|
|
<i class="mdi mdi-close"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="attachment-preview-body">
|
2026-05-20 21:00:47 +08:00
|
|
|
|
<div class="attachment-source-pane">
|
|
|
|
|
|
<div v-if="attachmentPreviewLoading" class="attachment-preview-state">
|
|
|
|
|
|
<i class="mdi mdi-loading mdi-spin"></i>
|
|
|
|
|
|
<span>正在加载附件预览…</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-else-if="attachmentPreviewError" class="attachment-preview-state error">
|
|
|
|
|
|
<i class="mdi mdi-alert-circle-outline"></i>
|
|
|
|
|
|
<span>{{ attachmentPreviewError }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<img
|
|
|
|
|
|
v-else-if="attachmentPreviewUrl && attachmentPreviewMediaType.startsWith('image/')"
|
|
|
|
|
|
:src="attachmentPreviewUrl"
|
|
|
|
|
|
:alt="attachmentPreviewName || '附件图片'"
|
|
|
|
|
|
class="attachment-preview-image"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<iframe
|
|
|
|
|
|
v-else-if="attachmentPreviewUrl && attachmentPreviewMediaType === 'application/pdf'"
|
|
|
|
|
|
:src="attachmentPreviewUrl"
|
|
|
|
|
|
class="attachment-preview-frame"
|
|
|
|
|
|
title="附件预览"
|
|
|
|
|
|
></iframe>
|
|
|
|
|
|
<div v-else class="attachment-preview-state">
|
|
|
|
|
|
<i class="mdi mdi-file-outline"></i>
|
|
|
|
|
|
<span>当前附件暂不支持直接预览。</span>
|
|
|
|
|
|
</div>
|
2026-05-13 06:51:12 +00:00
|
|
|
|
</div>
|
2026-05-20 21:00:47 +08:00
|
|
|
|
|
|
|
|
|
|
<aside class="attachment-insight-pane">
|
|
|
|
|
|
<div class="attachment-insight-head">
|
|
|
|
|
|
<span>识别信息</span>
|
|
|
|
|
|
<strong>{{ currentAttachmentPreviewInsight?.documentTypeLabel || '待识别' }}</strong>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="currentAttachmentPreviewInsight" class="attachment-insight-content">
|
|
|
|
|
|
<div class="attachment-insight-pills">
|
|
|
|
|
|
<span :class="['attachment-recognition-pill', currentAttachmentPreviewInsight.requirementTone]">
|
|
|
|
|
|
{{ currentAttachmentPreviewInsight.requirementLabel }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p v-if="currentAttachmentPreviewInsight.message" class="attachment-recognition-message">
|
|
|
|
|
|
{{ currentAttachmentPreviewInsight.message }}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div v-if="currentAttachmentPreviewInsight.fields.length" class="attachment-insight-section">
|
|
|
|
|
|
<span>字段结果</span>
|
|
|
|
|
|
<ul>
|
|
|
|
|
|
<li v-for="field in currentAttachmentPreviewInsight.fields" :key="field">{{ field }}</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="currentAttachmentPreviewInsight.ruleBasis.length" class="attachment-insight-section">
|
|
|
|
|
|
<span>规则依据</span>
|
|
|
|
|
|
<ul>
|
|
|
|
|
|
<li v-for="basis in currentAttachmentPreviewInsight.ruleBasis" :key="basis">{{ basis }}</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="currentAttachmentPreviewRiskCards.length" class="attachment-insight-section risk">
|
|
|
|
|
|
<span>风险点</span>
|
|
|
|
|
|
<article
|
|
|
|
|
|
v-for="card in currentAttachmentPreviewRiskCards"
|
|
|
|
|
|
:key="card.id"
|
|
|
|
|
|
:class="['attachment-risk-card', card.tone]"
|
|
|
|
|
|
>
|
|
|
|
|
|
<strong>{{ card.risk }}</strong>
|
|
|
|
|
|
<p>{{ card.suggestion }}</p>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-else class="attachment-preview-state compact">
|
|
|
|
|
|
<i class="mdi mdi-file-search-outline"></i>
|
|
|
|
|
|
<span>预览打开后会在这里展示票据字段、规则依据和风险提示。</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</aside>
|
2026-05-13 06:51:12 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Transition>
|
|
|
|
|
|
|
2026-05-20 21:00:47 +08:00
|
|
|
|
<ConfirmDialog
|
|
|
|
|
|
:open="submitConfirmDialogOpen"
|
|
|
|
|
|
badge="提交确认"
|
|
|
|
|
|
badge-tone="warning"
|
|
|
|
|
|
:title="`确认提交 ${request.id} 吗?`"
|
2026-05-25 13:35:39 +08:00
|
|
|
|
:description="isApplicationDocument ? '请确认申请事由、预计金额和申请信息均已核对无误。确认后将提交至直属领导审批。' : '请确认报销事由、金额、费用明细和附件材料均已核对无误。确认后系统将发起 AI 预审并进入审批流程。'"
|
2026-05-20 21:00:47 +08:00
|
|
|
|
cancel-text="返回核对"
|
|
|
|
|
|
confirm-text="确认提交"
|
|
|
|
|
|
busy-text="提交中..."
|
|
|
|
|
|
confirm-tone="primary"
|
|
|
|
|
|
confirm-icon="mdi mdi-send-circle-outline"
|
|
|
|
|
|
:busy="submitBusy"
|
|
|
|
|
|
@close="closeSubmitConfirmDialog"
|
|
|
|
|
|
@confirm="confirmSubmitRequest"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="submit-confirm-summary" aria-label="提交前核对摘要">
|
|
|
|
|
|
<div class="submit-confirm-row">
|
|
|
|
|
|
<span>单据编号</span>
|
|
|
|
|
|
<strong>{{ request.documentNo || request.id }}</strong>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="submit-confirm-row">
|
2026-05-25 13:35:39 +08:00
|
|
|
|
<span>{{ isApplicationDocument ? '申请类型' : '报销类型' }}</span>
|
2026-05-20 21:00:47 +08:00
|
|
|
|
<strong>{{ request.typeLabel }}</strong>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="submit-confirm-row">
|
2026-05-25 13:35:39 +08:00
|
|
|
|
<span>{{ isApplicationDocument ? '预计金额' : '报销金额' }}</span>
|
2026-05-20 21:00:47 +08:00
|
|
|
|
<strong>{{ request.amountDisplay || expenseTotal }}</strong>
|
|
|
|
|
|
</div>
|
2026-05-25 13:35:39 +08:00
|
|
|
|
<div v-if="!isApplicationDocument" class="submit-confirm-row">
|
2026-05-20 21:00:47 +08:00
|
|
|
|
<span>费用明细</span>
|
|
|
|
|
|
<strong>{{ expenseItems.length }} 条 / {{ uploadedExpenseCount }} 张单据</strong>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</ConfirmDialog>
|
|
|
|
|
|
|
2026-05-21 23:53:03 +08:00
|
|
|
|
<ConfirmDialog
|
|
|
|
|
|
:open="riskOverrideDialogOpen"
|
|
|
|
|
|
badge="重大风险"
|
|
|
|
|
|
badge-tone="danger"
|
|
|
|
|
|
:title="`当前存在 ${submitRiskWarnings.length} 条重大风险`"
|
|
|
|
|
|
description="如仍需提交审批,请逐条填写违规或超标原因,系统会写入附加说明并用于后续风险统计。"
|
|
|
|
|
|
cancel-text="返回整改"
|
|
|
|
|
|
confirm-text="保存原因并继续"
|
|
|
|
|
|
busy-text="保存中..."
|
|
|
|
|
|
confirm-tone="danger"
|
|
|
|
|
|
confirm-icon="mdi mdi-alert-circle-outline"
|
|
|
|
|
|
:busy="riskOverrideBusy"
|
|
|
|
|
|
@close="closeRiskOverrideDialog"
|
|
|
|
|
|
@confirm="confirmRiskOverrideReasons"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div v-if="currentSubmitRiskWarning" class="risk-override-panel" aria-label="重大风险说明">
|
|
|
|
|
|
<div class="risk-override-nav">
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="risk-override-nav-btn"
|
|
|
|
|
|
:disabled="submitRiskWarnings.length <= 1 || riskOverrideBusy"
|
|
|
|
|
|
aria-label="上一条风险"
|
|
|
|
|
|
@click="goToPreviousSubmitRisk"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i class="mdi mdi-chevron-left"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<span>{{ riskOverrideIndexLabel }}</span>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="risk-override-nav-btn"
|
|
|
|
|
|
:disabled="submitRiskWarnings.length <= 1 || riskOverrideBusy"
|
|
|
|
|
|
aria-label="下一条风险"
|
|
|
|
|
|
@click="goToNextSubmitRisk"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i class="mdi mdi-chevron-right"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<article :class="['risk-override-card', currentSubmitRiskWarning.tone]">
|
|
|
|
|
|
<div class="risk-override-card-head">
|
|
|
|
|
|
<span>{{ currentSubmitRiskWarning.label }}</span>
|
|
|
|
|
|
<strong>{{ currentSubmitRiskWarning.title }}</strong>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p>{{ currentSubmitRiskWarning.risk }}</p>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="riskOverrideReasons[currentSubmitRiskWarning.id]"
|
|
|
|
|
|
maxlength="160"
|
|
|
|
|
|
placeholder="请说明为什么仍需提交,例如客户指定酒店、会议高峰、协议酒店满房等"
|
|
|
|
|
|
aria-label="违规提交原因"
|
|
|
|
|
|
></textarea>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</ConfirmDialog>
|
|
|
|
|
|
|
2026-05-27 09:17:57 +08:00
|
|
|
|
<TravelRequestDeleteDialog :open="deleteDialogOpen" :badge="deleteActionLabel" :title="deleteDialogTitle" :description="deleteDialogDescription" :busy="deleteBusy" @close="closeDeleteDialog" @confirm="confirmDeleteRequest" />
|
2026-05-20 14:21:56 +08:00
|
|
|
|
|
2026-05-27 09:17:57 +08:00
|
|
|
|
<TravelRequestApprovalDialog
|
2026-05-20 21:00:47 +08:00
|
|
|
|
:open="approveConfirmDialogOpen"
|
2026-05-21 09:28:33 +08:00
|
|
|
|
:badge="approvalConfirmBadge"
|
2026-05-26 09:15:14 +08:00
|
|
|
|
:title="approveConfirmTitle"
|
2026-05-21 09:28:33 +08:00
|
|
|
|
:description="approvalConfirmDescription"
|
2026-05-26 09:15:14 +08:00
|
|
|
|
:confirm-text="approveConfirmText"
|
|
|
|
|
|
:busy-text="approveBusyText"
|
2026-05-20 21:00:47 +08:00
|
|
|
|
:busy="approveBusy"
|
2026-05-27 09:17:57 +08:00
|
|
|
|
:opinion-title="approvalOpinionTitle"
|
2026-05-27 14:35:17 +08:00
|
|
|
|
v-model:opinion="leaderOpinion"
|
|
|
|
|
|
:opinion-placeholder="approvalOpinionPlaceholder"
|
|
|
|
|
|
:opinion-hint="approvalOpinionHint"
|
|
|
|
|
|
:opinion-required="requiresApprovalOpinion"
|
2026-05-20 21:00:47 +08:00
|
|
|
|
@close="closeApproveConfirmDialog"
|
|
|
|
|
|
@confirm="confirmApproveRequest"
|
2026-05-27 09:17:57 +08:00
|
|
|
|
/>
|
2026-05-20 21:00:47 +08:00
|
|
|
|
|
2026-05-27 09:17:57 +08:00
|
|
|
|
<TravelRequestReturnDialog
|
2026-05-20 14:21:56 +08:00
|
|
|
|
:open="returnDialogOpen"
|
|
|
|
|
|
:title="`确认退回 ${request.id} 吗?`"
|
2026-05-26 09:15:14 +08:00
|
|
|
|
:description="returnDialogDescription"
|
2026-05-20 14:21:56 +08:00
|
|
|
|
:busy="returnBusy"
|
2026-05-27 14:35:17 +08:00
|
|
|
|
:application="isApplicationDocument"
|
2026-05-20 14:21:56 +08:00
|
|
|
|
@close="closeReturnDialog"
|
|
|
|
|
|
@confirm="confirmReturnRequest"
|
2026-05-13 03:33:11 +00:00
|
|
|
|
/>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</section>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script src="./scripts/TravelRequestDetailView.js"></script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped src="../assets/styles/views/travel-request-detail-view.css"></style>
|
2026-05-21 23:53:03 +08:00
|
|
|
|
<style scoped src="../assets/styles/views/travel-request-detail-view-part2.css"></style>
|