Files
X-Financial/web/src/views/TravelRequestDetailView.vue
caoxiaozhu 8b72f4e962 feat(web): update views
- AppShellRouteView.vue: update app shell route view
- AuditView.vue: update audit view
- EmployeeManagementView.vue: update employee management view
- PoliciesView.vue: update policies view
- RequestsView.vue: update requests view
- TravelReimbursementCreateView.vue: update travel form view
- TravelRequestDetailView.vue: update travel detail view
2026-05-13 03:33:11 +00:00

286 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<section class="approval-page">
<div class="approval-detail">
<div class="detail-scroll">
<article class="detail-hero panel">
<div class="applicant-card">
<div class="portrait">{{ profile.avatar }}</div>
<div>
<h2>{{ profile.name }} <span>{{ request.typeLabel }}</span></h2>
<p>{{ profile.department }} <strong>申请时间 {{ request.applyTime }}</strong></p>
</div>
</div>
<div v-for="stat in heroStats" :key="stat.label" class="hero-stat">
<span>{{ stat.label }}</span>
<strong v-if="stat.kind === 'text'">{{ stat.value }}</strong>
<b v-else :class="[stat.className, stat.tone]">{{ stat.value }}</b>
</div>
<div class="hero-summary-panel">
<div v-for="item in heroSummaryItems" :key="item.label" class="hero-summary-item">
<div class="hero-summary-label">
<span class="hero-summary-icon"><i :class="item.icon"></i></span>
<span>{{ item.label }}</span>
</div>
<strong>{{ item.value }}</strong>
</div>
</div>
<div class="progress-block">
<div class="progress-head">
<h3>{{ isTravelRequest ? '差旅进度' : '报销进度' }}</h3>
</div>
<div class="progress-line" :style="{ '--progress-columns': progressSteps.length }">
<div
v-for="step in progressSteps"
:key="step.label"
class="progress-step"
:class="{ active: step.active, current: step.current }"
>
<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>
<strong>{{ step.label }}</strong>
<small>{{ step.time }}</small>
</div>
</div>
</div>
</article>
<div class="detail-grid">
<section class="detail-left">
<article class="detail-card panel">
<div class="detail-card-head">
<div>
<h3>费用明细</h3>
<p>
{{ isTravelRequest ? '按出行时间逐笔核对票据与差旅规则。' : '按业务发生时间逐笔核对票据、用途说明与系统校验。' }}
</p>
</div>
<button class="smart-entry-btn" type="button" @click="openAiEntry">
<i class="mdi mdi-robot-outline"></i>
<span>智能录入</span>
</button>
</div>
<div class="detail-expense-table">
<table>
<thead>
<tr>
<th>时间</th>
<th>费用项目</th>
<th>说明</th>
<th>金额</th>
<th>附件材料</th>
<th>系统校验</th>
<th v-if="isDraftRequest">操作</th>
</tr>
</thead>
<tbody>
<template v-for="item in expenseItems" :key="item.id">
<tr>
<td class="expense-time">
<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>
<td class="expense-type">
<template v-if="editingExpenseId === item.id">
<div class="cell-editor">
<select v-model="expenseEditor.itemType" class="editor-select">
<option v-for="option in expenseTypeOptions" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</select>
<span>编辑费用项目</span>
</div>
</template>
<template v-else>
<strong>{{ item.name }}</strong>
<span>{{ item.category }}</span>
</template>
</td>
<td class="expense-desc">
<template v-if="editingExpenseId === item.id">
<div class="cell-editor editor-stack">
<input v-model="expenseEditor.itemReason" class="editor-input" type="text" placeholder="输入费用说明" />
<input v-model="expenseEditor.itemLocation" class="editor-input" type="text" placeholder="输入业务地点" />
</div>
</template>
<template v-else>
<strong>{{ item.desc }}</strong>
<span>{{ item.detail }}</span>
</template>
</td>
<td class="expense-amount">
<template v-if="editingExpenseId === item.id">
<div class="cell-editor">
<input v-model="expenseEditor.itemAmount" class="editor-input" type="number" min="0" step="0.01" placeholder="输入金额" />
<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>
<td class="expense-attachment">
<template v-if="editingExpenseId === item.id">
<div class="cell-editor editor-stack">
<input v-model="expenseEditor.invoiceId" class="editor-input" type="text" placeholder="输入票据标识或附件名称" />
<span>示例invoice-2026-0513.jpg</span>
</div>
</template>
<template v-else>
<div class="expense-attachment-main">
<span :class="['attachment-pill', item.attachmentTone]">{{ item.attachmentStatus }}</span>
<button
v-if="item.attachments.length"
class="inline-action"
type="button"
@click="toggleExpenseAttachments(item.id)"
>
{{ expandedExpenseId === item.id ? '收起附件' : '查看附件' }}
</button>
</div>
<span class="attachment-hint">{{ item.attachmentHint }}</span>
</template>
</td>
<td class="expense-risk">
<template v-if="showExpenseRisk(item)">
<span :class="['risk-inline-tag', resolveExpenseIssues(item).length ? 'medium' : item.riskTone]">
{{ resolveExpenseIssues(item).length ? '待补充' : item.riskLabel }}
</span>
<p>{{ resolveExpenseIssues(item).length ? resolveExpenseIssues(item).join('') : item.riskText }}</p>
</template>
</td>
<td v-if="isDraftRequest" class="expense-action-cell">
<div v-if="editingExpenseId === item.id" class="row-action-group">
<button
class="inline-action primary"
type="button"
:disabled="savingExpenseId === item.id || submitBusy || deleteBusy"
@click="saveExpenseEdit(item)"
>
{{ savingExpenseId === item.id ? '保存中' : '保存' }}
</button>
<button
class="inline-action"
type="button"
:disabled="savingExpenseId === item.id || submitBusy || deleteBusy"
@click="cancelExpenseEdit"
>
取消
</button>
</div>
<button
v-else
class="inline-action"
type="button"
:disabled="actionBusy"
@click="startExpenseEdit(item)"
>
编辑
</button>
</td>
</tr>
<tr v-if="expandedExpenseId === item.id" class="expense-expand-row">
<td :colspan="isDraftRequest ? 7 : 6">
<div class="expense-files">
<span v-for="file in item.attachments" :key="file" class="expense-file-chip">
<i class="mdi mdi-paperclip"></i>
{{ file }}
</span>
</div>
</td>
</tr>
</template>
<tr class="total-row">
<td :colspan="isDraftRequest ? 4 : 3">合计</td>
<td>{{ expenseTotal }}</td>
<td>{{ uploadedExpenseCount }} 项已关联票据</td>
<td>{{ expenseSummaryText }}</td>
<td v-if="isDraftRequest">-</td>
</tr>
</tbody>
</table>
</div>
</article>
<article v-if="isDraftRequest" class="detail-card panel validation-card">
<div class="validation-head">
<h3>提交校验</h3>
<span :class="['validation-pill', validationTone]">{{ canSubmit ? '可提交' : '待完善' }}</span>
</div>
<p class="validation-summary">{{ validationSummary }}</p>
<ul v-if="draftBlockingIssues.length" class="validation-list">
<li v-for="issue in draftBlockingIssues" :key="issue">{{ issue }}</li>
</ul>
</article>
<article class="detail-card panel">
<h3>申请说明</h3>
<div class="detail-note">{{ detailNote }}</div>
</article>
</section>
</div>
</div>
<footer class="detail-actions">
<button class="back-action" type="button" @click="emit('backToRequests')">
<i class="mdi mdi-arrow-left"></i>
<span>返回报销列表</span>
</button>
<div v-if="isDraftRequest" class="approval-action-group" aria-label="申请操作">
<button class="reject-action" type="button" :disabled="actionBusy" @click="handleDeleteDraft">
<i class="mdi mdi-trash-can-outline"></i>
{{ deleteBusy ? '删除中' : '删除草稿' }}
</button>
<button class="approve-action" type="button" :disabled="!canSubmit" @click="handleSubmit">
<i class="mdi mdi-send-circle-outline"></i>
{{ submitBusy ? '提交中' : '提交审批' }}
</button>
</div>
<p v-else class="detail-action-hint">当前单据已进入流程详情页仅展示状态与费用明细</p>
</footer>
</div>
<ConfirmDialog
:open="deleteDialogOpen"
badge="删除草稿"
badge-tone="danger"
:title="`确认删除草稿 ${request.id} 吗?`"
description="删除后该草稿及其当前费用明细将不可恢复,请确认本次操作。"
cancel-text="取消"
confirm-text="确认删除"
busy-text="删除中..."
confirm-tone="danger"
confirm-icon="mdi mdi-trash-can-outline"
:busy="deleteBusy"
@close="closeDeleteDialog"
@confirm="confirmDeleteDraft"
/>
</section>
</template>
<script src="./scripts/TravelRequestDetailView.js"></script>
<style scoped src="../assets/styles/views/travel-request-detail-view.css"></style>