Files
admin-web/src/modules/overseas-master-data/components/TripOperationsRecordDialog.vue
T

55 lines
6.8 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.
<script setup lang="ts">
import { onBeforeUnmount, reactive, ref, watch } from 'vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import { isFleetCommandRejected } from '../api/fleetQuotes'
import { getTripOperationsRecord, saveTripOperationsRecord, listTripOperationsRecordHistory, type TripOperationsRow, type TripOperationsRecord, type TripOperationsRecordHistory, type TripOperationsRecordSave, type TripOperationsMeasured } from '../api/tripOperations'
const open = defineModel<boolean>({ default: false })
const props = defineProps<{ row: TripOperationsRow | null; readonly?: boolean }>(), emit = defineEmits<{ saved: [] }>()
const form = reactive<TripOperationsMeasured>({ actualDistanceMiles: null, actualFuelLitres: null, remark: null }), reason = ref('')
const record = ref<TripOperationsRecord | null>(null), loading = ref(false), saving = ref(false), error = ref<ApiErrorDetails | null>(null), tab = ref('measured')
const pending = new Map<string, TripOperationsRecordSave>(), uncertain = ref(false)
const history = ref<TripOperationsRecordHistory[]>([]), total = ref(0), page = ref(1), size = ref(20)
let sequence = 0
const columns: DataTableColumn[] = [{ key: 'recordVersion', label: '版本', width: 90 }, { key: 'occurredAt', label: '更正时间', minWidth: 190 }, { key: 'distance', label: '里程(mile)', width: 130, slotName: 'distance' }, { key: 'fuel', label: '油耗(L)', width: 120, slotName: 'fuel' }, { key: 'reason', label: '更正说明', minWidth: 180 }, { key: 'remark', label: '备注', minWidth: 200, slotName: 'remark' }]
async function load() {
if (!open.value || !props.row) return
const no = props.row.tripNo, current = ++sequence; loading.value = true; error.value = null; record.value = null
try { const result = await getTripOperationsRecord(no); if (current !== sequence) return; record.value = result; const retry = pending.get(no); uncertain.value = !!retry; Object.assign(form, retry?.details || result.details); reason.value = retry?.reason || '' }
catch (e) { if (current === sequence) error.value = getApiErrorDetails(e, '实测记录读取失败') } finally { if (current === sequence) loading.value = false }
}
async function loadHistory() {
if (!open.value || !props.row || tab.value !== 'history') return
const current = ++sequence; loading.value = true; error.value = null
try { const result = await listTripOperationsRecordHistory(props.row.tripNo, page.value, size.value); if (current === sequence) { history.value = result.records; total.value = result.total } } catch (e) { if (current === sequence) error.value = getApiErrorDetails(e, '更正历史读取失败') } finally { if (current === sequence) loading.value = false }
}
async function save() {
if (!record.value || !props.row || props.readonly || saving.value) return
const no = props.row.tripNo; saving.value = true; error.value = null
const request = pending.get(no) || { recordVersion: record.value.recordVersion, idempotencyKey: crypto.randomUUID(), details: { actualDistanceMiles: form.actualDistanceMiles ?? null, actualFuelLitres: form.actualFuelLitres ?? null, remark: form.remark || null }, reason: reason.value || null }
pending.set(no, request)
try { await saveTripOperationsRecord(no, request); pending.delete(no); uncertain.value = false; open.value = false; emit('saved') }
catch (e) { error.value = getApiErrorDetails(e, '实测记录保存失败'); if (isFleetCommandRejected(e)) pending.delete(no); uncertain.value = pending.has(no) }
finally { saving.value = false }
}
watch([open, () => props.row?.tripNo], () => { sequence++; tab.value = 'measured'; history.value = []; page.value = 1; total.value = 0; if (open.value) void load() })
watch([tab, page, size], () => { if (tab.value === 'history') void loadHistory() }); onBeforeUnmount(() => { sequence++ })
</script>
<template><AppDialog v-model="open" title="出车实测记录" width="820px" class="admin-density-dialog" :close-on-click-modal="false">
<el-alert v-if="error" :title="error.message" type="error" :closable="false"><ApiErrorMeta :details="error" /><el-button @click="tab === 'history' ? loadHistory() : load()">重新读取</el-button></el-alert>
<p v-if="row">{{ row.tripNo }} · {{ row.vehiclePlate || '—' }} · 实际发车 {{ row.departedAt }} · 货物结案 {{ row.completedAt || '—' }}</p>
<el-tabs v-model="tab" class="density-element-tabs"><el-tab-pane label="实测记录" name="measured">
<p v-if="uncertain">上次保存结果未确认,将按原请求重试。</p>
<el-form label-position="top" class="density-form record-form" :disabled="readonly || !record || loading || saving || uncertain">
<el-form-item label="本趟实际里程(mile)"><el-input-number :model-value="form.actualDistanceMiles" @update:model-value="form.actualDistanceMiles = $event ?? null" :min="0" :precision="1" placeholder="选填,本趟实际英里数" /></el-form-item>
<el-form-item label="本趟实际油耗(L)"><el-input-number :model-value="form.actualFuelLitres" @update:model-value="form.actualFuelLitres = $event ?? null" :min="0" :precision="1" placeholder="选填,本趟实际用油升数" /></el-form-item>
<el-form-item label="备注" class="full"><el-input :model-value="form.remark || ''" @update:model-value="form.remark = $event || null" type="textarea" maxlength="1000" placeholder="选填,补充实测情况" /></el-form-item>
<el-form-item v-if="!readonly" label="更正说明(选填)" class="full"><el-input v-model="reason" maxlength="1000" placeholder="选填,留空使用标准更正说明" /></el-form-item>
</el-form><p>未填写与明确零值分别保存。实测记录不改车辆累计里程、货物履约或财务费用。</p>
</el-tab-pane><el-tab-pane label="更正历史" name="history"><DataTable v-model:page="page" v-model:page-size="size" :data="history" :columns="columns" :total="total" :loading="loading" row-key="recordVersion" empty-text="暂无实测更正历史"><template #distance="{ row: item }">{{ item.details.actualDistanceMiles ?? '—' }}</template><template #fuel="{ row: item }">{{ item.details.actualFuelLitres ?? '—' }}</template><template #remark="{ row: item }">{{ item.details.remark || '—' }}</template></DataTable></el-tab-pane></el-tabs>
<template #footer><el-button :disabled="saving" @click="open = false">关闭</el-button><el-button v-if="!readonly && tab === 'measured'" type="primary" :loading="saving" :disabled="!record || loading" @click="save">{{ uncertain ? '按原请求重试' : '保存记录' }}</el-button></template>
</AppDialog></template>
<style scoped>.record-form { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:0 var(--admin-space-4); }.full { grid-column:1/-1; }</style>