This commit is contained in:
2026-08-08 21:14:45 +08:00
parent fc04029850
commit 5815649887
4 changed files with 37 additions and 52 deletions
@@ -2,7 +2,6 @@
import { computed, onMounted, reactive, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import AppDrawer from '@/shared/components/AppDrawer.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
@@ -11,7 +10,7 @@ import { getTruckAppointment, listTruckAppointmentAttachments, listTruckAppointm
import TruckAppointmentForm from '../components/TruckAppointmentForm.vue'
import TruckAttachmentDialog from '../components/TruckAttachmentDialog.vue'
import { truckAppointmentStatusLabels, type SaveTruckAppointmentRequest, type TruckAppointmentResource, type TruckAppointmentStatus, type TruckAppointmentStatusHistory } from '../types'
const route = useRoute(); const router = useRouter(); const open = ref(true); const loading = ref(false); const saving = ref(false); const statusSaving = ref(false); const attachmentOpen = ref(false); const resource = ref<TruckAppointmentResource | null>(null); const error = ref<ApiErrorDetails | null>(null); const form = reactive<SaveTruckAppointmentRequest>({ expectedVersion: 0 })
const route = useRoute(); const router = useRouter(); const loading = ref(false); const saving = ref(false); const statusSaving = ref(false); const attachmentOpen = ref(false); const resource = ref<TruckAppointmentResource | null>(null); const error = ref<ApiErrorDetails | null>(null); const form = reactive<SaveTruckAppointmentRequest>({ expectedVersion: 0 })
const history = ref<TruckAppointmentStatusHistory[]>([])
const access = useAccessPreviewStore(); const editing = computed(() => route.query.edit === '1'); const id = computed(() => String(route.params.id)); const canEdit = computed(() => access.can('platform:firstleg:truck-appointment:edit') && !['COMPLETED','CANCELLED'].includes(resource.value?.status || '') && editing.value); const canChangeStatus = computed(() => access.can('platform:firstleg:truck-appointment:operate')); const canManageAttachments = computed(() => access.can('platform:firstleg:truck-appointment:edit'))
async function loadRelated() { if (!resource.value) return; const [attachments, histories] = await Promise.all([listTruckAppointmentAttachments(id.value), listTruckAppointmentHistory(id.value)]); resource.value.attachments = attachments; history.value = histories }
@@ -28,14 +27,15 @@ async function changeStatus(status: TruckAppointmentStatus) { if (!resource.valu
onMounted(load)
</script>
<template>
<AppDrawer v-model="open" :title="resource?.appointmentNo ? `拖车预约 ${resource.appointmentNo}` : '拖车预约详情'" :loading="loading" :show-footer="false" @cancel="router.push({ name: 'trucking' })">
<LoadErrorState v-if="error && !resource" title="详情加载失败" :message="error.message" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
<template v-else-if="resource"><div class="status-bar"><span>当前状态:<strong>{{ truckAppointmentStatusLabels[resource.status] }}</strong></span><div v-if="canChangeStatus" class="status-actions"><el-button v-for="status in availableTargets" :key="status" :type="status === 'CANCELLED' ? 'danger' : status === 'EXCEPTION' ? 'warning' : 'primary'" plain :loading="statusSaving" @click="changeStatus(status)">{{ truckAppointmentStatusLabels[status] }}</el-button></div></div><ApiErrorMeta v-if="error" :details="error" /><TruckAppointmentForm v-model="form" :resource="resource" :readonly="!canEdit" :submitting="saving" @submit="submit" @attachments="attachmentOpen = true" /><section class="history-section"><h3>状态历史</h3><el-empty v-if="!history.length" description="暂无状态历史" :image-size="48" /><ol v-else><li v-for="(item, index) in history" :key="item.id || `${item.createdAt}-${index}`"><strong>{{ item.action }}</strong><span>{{ item.fromStatus || '—' }} → {{ item.toStatus || '—' }}</span><small>{{ item.operatorName || item.operatorId || '系统' }} · {{ item.createdAt || item.occurredAt || '—' }}<template v-if="item.reason"> · {{ item.reason }}</template></small></li></ol></section><TruckAttachmentDialog v-model="attachmentOpen" :appointment-id="id" :attachments="resource.attachments || []" :can-manage="canManageAttachments" @changed="loadRelated" /></template>
</AppDrawer>
<main class="business-page truck-detail-page"><div class="detail-actions"><el-button @click="router.push({ name: 'trucking' })">返回列表</el-button></div>
<LoadErrorState v-if="error && !resource" class="surface-panel" title="详情加载失败" :message="error.message" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
<template v-else-if="resource"><section class="surface-panel status-panel"><div class="status-bar"><span>当前状态:<strong>{{ truckAppointmentStatusLabels[resource.status] }}</strong></span><div v-if="canChangeStatus" class="status-actions"><el-button v-for="status in availableTargets" :key="status" :type="status === 'CANCELLED' ? 'danger' : status === 'EXCEPTION' ? 'warning' : 'primary'" plain :loading="statusSaving" @click="changeStatus(status)">{{ truckAppointmentStatusLabels[status] }}</el-button></div></div><ApiErrorMeta v-if="error" :details="error" /></section><section class="surface-panel form-panel"><TruckAppointmentForm v-model="form" :resource="resource" :readonly="!canEdit" :submitting="saving" @submit="submit" @attachments="attachmentOpen = true" /></section><section class="surface-panel history-section"><h3>状态历史</h3><el-empty v-if="!history.length" description="暂无状态历史" :image-size="48" /><ol v-else><li v-for="(item, index) in history" :key="item.id || `${item.createdAt}-${index}`"><strong>{{ item.action }}</strong><span>{{ item.fromStatus || '—' }} → {{ item.toStatus || '—' }}</span><small>{{ item.operatorName || item.operatorId || '系统' }} · {{ item.createdAt || item.occurredAt || '—' }}<template v-if="item.reason"> · {{ item.reason }}</template></small></li></ol></section><TruckAttachmentDialog v-model="attachmentOpen" :appointment-id="id" :attachments="resource.attachments || []" :can-manage="canManageAttachments" @changed="loadRelated" /></template>
</main>
</template>
<style scoped>
.status-bar { display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap; margin-bottom:16px; padding:12px; background:var(--el-fill-color-light); border-radius:6px; }
.status-actions { display:flex; flex-wrap:wrap; gap:8px; }.status-actions :deep(.el-button) { margin-left:0; }
.history-section { margin-top:20px; padding-top:16px; border-top:1px solid var(--cc-border); }.history-section h3 { margin:0 0 12px; font-size:15px; }.history-section ol { display:grid; gap:12px; margin:0; padding:0; list-style:none; }.history-section li { display:grid; grid-template-columns:120px minmax(120px, 1fr) minmax(200px, 2fr); gap:12px; padding:10px 12px; border-radius:6px; background:var(--el-fill-color-lighter); }.history-section small { color:var(--cc-muted); overflow-wrap:anywhere; }
@media (max-width: 640px) { .history-section li { grid-template-columns:1fr; gap:4px; } }
.truck-detail-page { min-width:0; }.detail-actions { display:flex; justify-content:flex-end; }.status-panel { padding:12px 16px; }.form-panel { padding:24px; }.history-section { padding:20px; }.history-section h3 { margin:0 0 12px; font-size:15px; }.history-section ol { display:grid; gap:12px; margin:0; padding:0; list-style:none; }.history-section li { display:grid; grid-template-columns:120px minmax(120px, 1fr) minmax(200px, 2fr); gap:12px; padding:10px 12px; border-radius:6px; background:var(--el-fill-color-lighter); }.history-section small { color:var(--cc-muted); overflow-wrap:anywhere; }
@media (max-width: 720px) { .form-panel { padding:16px; } }
@media (max-width: 640px) { .history-section { padding:16px; }.history-section li { grid-template-columns:1fr; gap:4px; } }
</style>