2026-07-13 16:58:51 +08:00
|
|
|
|
<template>
|
2026-07-20 10:31:27 +08:00
|
|
|
|
<section class="expense-case-timeline" aria-labelledby="expense-case-timeline-title">
|
2026-07-13 16:58:51 +08:00
|
|
|
|
<div class="expense-case-timeline-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div class="expense-case-timeline-title-row">
|
2026-07-20 10:31:27 +08:00
|
|
|
|
<h4 id="expense-case-timeline-title">费用操作记录</h4>
|
2026-07-13 16:58:51 +08:00
|
|
|
|
<span v-if="timeline" class="expense-case-stage">{{ timeline.stageLabel }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p v-if="timeline" class="expense-case-meta">
|
|
|
|
|
|
<span>{{ timeline.caseNo || '统一费用事件' }}</span>
|
|
|
|
|
|
<span>已串联 {{ timeline.linkCount }} 张业务单据</span>
|
2026-07-20 10:31:27 +08:00
|
|
|
|
<span v-if="displayedEvents.length">共 {{ displayedEvents.length }} 条记录</span>
|
|
|
|
|
|
<span v-if="displayedEvents.length">最新优先</span>
|
2026-07-13 16:58:51 +08:00
|
|
|
|
</p>
|
|
|
|
|
|
<p v-else>记录费用从申请、审批、报销到付款的真实办理轨迹。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="expense-case-refresh"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:disabled="loading"
|
|
|
|
|
|
aria-label="刷新费用事件时间线"
|
|
|
|
|
|
@click="refreshExpenseCaseTimeline"
|
|
|
|
|
|
>
|
2026-07-20 10:31:27 +08:00
|
|
|
|
<i :class="loading ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-refresh'" aria-hidden="true"></i>
|
2026-07-13 16:58:51 +08:00
|
|
|
|
<span>刷新</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-07-20 10:31:27 +08:00
|
|
|
|
<div class="expense-case-timeline-body" :aria-busy="loading">
|
2026-07-13 16:58:51 +08:00
|
|
|
|
<div v-if="loading && !timeline" class="expense-case-state loading">
|
|
|
|
|
|
<i class="mdi mdi-loading mdi-spin"></i>
|
|
|
|
|
|
<span>正在同步真实费用事件</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-else-if="errorMessage" class="expense-case-state error">
|
|
|
|
|
|
<i class="mdi mdi-alert-circle-outline"></i>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>费用事件加载失败</strong>
|
|
|
|
|
|
<span>{{ errorMessage }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button type="button" @click="refreshExpenseCaseTimeline">重新加载</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-else-if="notAvailable" class="expense-case-state empty">
|
|
|
|
|
|
<i class="mdi mdi-timeline-alert-outline"></i>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>当前单据尚未纳入统一费用事件</strong>
|
|
|
|
|
|
<span>上方进度仍可正常使用,后续业务动作写入后将自动展示办理记录。</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-else-if="timeline && !timeline.events.length" class="expense-case-state empty">
|
|
|
|
|
|
<i class="mdi mdi-timeline-clock-outline"></i>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>统一费用事件已建立</strong>
|
|
|
|
|
|
<span>暂无可展示的办理记录。</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-07-20 10:31:27 +08:00
|
|
|
|
<div v-else-if="timeline" class="expense-case-event-table">
|
|
|
|
|
|
<div class="expense-case-event-columns" aria-hidden="true">
|
|
|
|
|
|
<span>发生时间</span>
|
|
|
|
|
|
<span>事件</span>
|
|
|
|
|
|
<span>操作人</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<ol class="expense-case-event-list">
|
|
|
|
|
|
<li
|
|
|
|
|
|
v-for="event in displayedEvents"
|
|
|
|
|
|
:key="event.id"
|
|
|
|
|
|
class="expense-case-event"
|
|
|
|
|
|
:class="event.tone"
|
|
|
|
|
|
>
|
|
|
|
|
|
<time
|
|
|
|
|
|
class="expense-case-event-time"
|
|
|
|
|
|
:datetime="event.occurredAt || undefined"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ event.occurredAtLabel }}
|
|
|
|
|
|
</time>
|
|
|
|
|
|
<div class="expense-case-event-main">
|
|
|
|
|
|
<span class="expense-case-event-icon" aria-hidden="true">
|
|
|
|
|
|
<i :class="event.icon"></i>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<div class="expense-case-event-copy">
|
|
|
|
|
|
<strong>{{ event.label }}</strong>
|
|
|
|
|
|
<p>{{ event.summary }}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="expense-case-event-actor" :title="event.actorLabel">
|
|
|
|
|
|
<i class="mdi mdi-account-outline" aria-hidden="true"></i>
|
|
|
|
|
|
<span>{{ event.actorLabel }}</span>
|
2026-07-13 16:58:51 +08:00
|
|
|
|
</div>
|
2026-07-20 10:31:27 +08:00
|
|
|
|
</li>
|
|
|
|
|
|
</ol>
|
|
|
|
|
|
</div>
|
2026-07-13 16:58:51 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2026-07-20 10:31:27 +08:00
|
|
|
|
import { computed, toRef } from 'vue'
|
2026-07-13 16:58:51 +08:00
|
|
|
|
|
|
|
|
|
|
import { useExpenseCaseTimeline } from '../../composables/requests/useExpenseCaseTimeline.js'
|
2026-07-20 10:31:27 +08:00
|
|
|
|
import { orderExpenseCaseEventsForDisplay } from '../../utils/expenseCaseTimeline.js'
|
2026-07-13 16:58:51 +08:00
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
claimId: { type: String, default: '' },
|
|
|
|
|
|
refreshKey: { type: String, default: '' }
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
|
timeline,
|
|
|
|
|
|
loading,
|
|
|
|
|
|
errorMessage,
|
|
|
|
|
|
notAvailable,
|
|
|
|
|
|
refreshExpenseCaseTimeline
|
|
|
|
|
|
} = useExpenseCaseTimeline({
|
|
|
|
|
|
claimId: toRef(props, 'claimId'),
|
|
|
|
|
|
refreshKey: toRef(props, 'refreshKey')
|
|
|
|
|
|
})
|
2026-07-20 10:31:27 +08:00
|
|
|
|
|
|
|
|
|
|
const displayedEvents = computed(() => orderExpenseCaseEventsForDisplay(timeline.value?.events))
|
2026-07-13 16:58:51 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped src="../../assets/styles/components/travel-request-expense-case-timeline.css"></style>
|