2026-08-19 16:45:51 +08:00
|
|
|
|
<script setup lang="ts">
|
2026-08-21 16:04:24 +08:00
|
|
|
|
import { computed, reactive, ref, watch } from 'vue'
|
|
|
|
|
|
import { Plus } from '@lucide/vue'
|
|
|
|
|
|
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
2026-08-19 16:45:51 +08:00
|
|
|
|
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
2026-08-21 16:04:24 +08:00
|
|
|
|
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
|
|
|
|
|
import AppDialog from '@/shared/components/AppDialog.vue'
|
|
|
|
|
|
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
|
|
|
|
|
import { appendCarrierScheduleManualNode, getCarrierScheduleQuery, listCarrierScheduleQueries } from '../api/platformCarrierScheduleQueries'
|
2026-08-19 16:45:51 +08:00
|
|
|
|
import { getPortSchedule } from '../api/platformPortSchedules'
|
2026-08-21 16:04:24 +08:00
|
|
|
|
import type { CarrierScheduleExceptionFact, CarrierScheduleManualNodeRequest, CarrierScheduleNodeFact, CarrierScheduleQueryDetail, CarrierScheduleQueryView, CarrierScheduleStandardNode, PortScheduleView } from '../types'
|
2026-08-19 16:45:51 +08:00
|
|
|
|
|
|
|
|
|
|
const props = defineProps<{ oceanBookingId?: string | null; containerBookingId?: string | null; portScheduleId?: string | null }>()
|
2026-08-21 16:04:24 +08:00
|
|
|
|
const access = useAccessPreviewStore()
|
2026-08-19 16:45:51 +08:00
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
const error = ref<ApiErrorDetails | null>(null)
|
2026-08-20 09:10:36 +08:00
|
|
|
|
const queryDetailError = ref<ApiErrorDetails | null>(null)
|
|
|
|
|
|
const scheduleError = ref<ApiErrorDetails | null>(null)
|
2026-08-19 16:45:51 +08:00
|
|
|
|
const selected = ref<CarrierScheduleQueryView | null>(null)
|
|
|
|
|
|
const detail = ref<CarrierScheduleQueryDetail | null>(null)
|
|
|
|
|
|
const latestSchedule = ref<PortScheduleView | null>(null)
|
2026-08-21 16:04:24 +08:00
|
|
|
|
const manualDialogOpen = ref(false)
|
|
|
|
|
|
const manualIdempotencyKey = ref('')
|
|
|
|
|
|
const manualObservedAt = ref('')
|
|
|
|
|
|
const manualSaving = ref(false)
|
|
|
|
|
|
const manualError = ref<ApiErrorDetails | null>(null)
|
|
|
|
|
|
const manualValidationError = ref('')
|
|
|
|
|
|
const manualForm = reactive({ standardNode: '' as CarrierScheduleStandardNode | '', estimatedAt: '', actualAt: '', locationCode: '', locationName: '', reason: '' })
|
2026-08-19 16:45:51 +08:00
|
|
|
|
|
|
|
|
|
|
const query = computed(() => detail.value?.query.query || selected.value?.query || null)
|
|
|
|
|
|
const queryView = computed(() => detail.value?.query || selected.value)
|
|
|
|
|
|
const statusText = computed(() => {
|
|
|
|
|
|
if (!query.value) return '未建立'
|
2026-08-21 16:04:24 +08:00
|
|
|
|
if (query.value.status === 'STOPPED' && query.value.latestActualNode === 'EMPTY_RETURNED' && query.value.latestActualAt) return '已停止 · 已还柜'
|
|
|
|
|
|
if (query.value.status === 'STOPPED') return '任务已停止'
|
|
|
|
|
|
return ({ READY: '运行中', MANUAL_REQUIRED: '待人工处理' } as Record<string, string>)[query.value.status] || query.value.status
|
2026-08-19 16:45:51 +08:00
|
|
|
|
})
|
2026-08-20 18:02:52 +08:00
|
|
|
|
const statusType = computed<'success' | 'warning' | 'danger' | 'info'>(() => {
|
|
|
|
|
|
if (query.value?.status === 'STOPPED') return 'info'
|
|
|
|
|
|
if (queryView.value?.expired || query.value?.status === 'MANUAL_REQUIRED') return 'danger'
|
|
|
|
|
|
return 'warning'
|
|
|
|
|
|
})
|
|
|
|
|
|
const nodes = computed(() => detail.value?.nodes || [])
|
2026-08-21 16:04:24 +08:00
|
|
|
|
const exceptions = computed(() => [...(detail.value?.exceptions || [])].reverse())
|
|
|
|
|
|
const latestActualNodeFact = computed(() => [...nodes.value].reverse().find((item) => item.standardNode === query.value?.latestActualNode && item.actualAt === query.value?.latestActualAt)
|
|
|
|
|
|
|| [...nodes.value].reverse().find((item) => item.standardNode === query.value?.latestActualNode && item.actualAt)
|
|
|
|
|
|
|| null)
|
|
|
|
|
|
const latestSource = computed(() => latestActualNodeFact.value?.providerCode || latestSchedule.value?.schedule.sourceName || query.value?.carrierCode || '—')
|
|
|
|
|
|
const sourceReference = computed(() => latestActualNodeFact.value?.sourceReference || latestSchedule.value?.schedule.sourceRef || query.value?.officialUrl || '—')
|
|
|
|
|
|
const latestObservation = computed(() => latestActualNodeFact.value?.observedAt || null)
|
2026-08-19 16:45:51 +08:00
|
|
|
|
const histories = computed(() => [...(detail.value?.histories || [])].reverse())
|
2026-08-21 16:04:24 +08:00
|
|
|
|
const standardNodes: CarrierScheduleStandardNode[] = ['DEPARTED_WAREHOUSE', 'AT_ORIGIN_PORT', 'LOADED_ON_VESSEL', 'DEPARTED_ORIGIN_PORT', 'IN_TRANSIT', 'ARRIVED_DESTINATION_PORT', 'DISCHARGED_FROM_VESSEL', 'PICKUP_READY', 'PICKED_UP', 'EMPTY_RETURNED']
|
2026-08-19 16:45:51 +08:00
|
|
|
|
|
|
|
|
|
|
function formatTime(value?: string | null) { return value ? value.replace('T', ' ').slice(0, 16) : '—' }
|
2026-08-20 09:10:36 +08:00
|
|
|
|
function trackingTypeText(value: string) { return ({ BOOKING_NO: '订舱号', BILL_OF_LADING: '提单号', CONTAINER_NO: '集装箱号' } as Record<string, string>)[value] || value }
|
2026-08-20 18:02:52 +08:00
|
|
|
|
function triggerSourceText(value?: string | null) { return ({ OUTBOUND_CONFIRM: '国内出仓确认自动建立', MANUAL: '平台人工建立' } as Record<string, string>)[value || ''] || value || '—' }
|
2026-08-21 16:04:24 +08:00
|
|
|
|
function nodeText(value?: CarrierScheduleStandardNode | null) { return ({ DEPARTED_WAREHOUSE: '已离开国内仓库', AT_ORIGIN_PORT: '重箱已进入起运港', LOADED_ON_VESSEL: '已装船', DEPARTED_ORIGIN_PORT: '船舶已离开起运港', IN_TRANSIT: '海运途中', ARRIVED_DESTINATION_PORT: '船舶已到目的港', DISCHARGED_FROM_VESSEL: '已卸船、未清关', PICKUP_READY: '已清关、可提柜', PICKED_UP: '已提柜', EMPTY_RETURNED: '已还柜' } as Record<string, string>)[value || ''] || value || '—' }
|
2026-08-20 18:02:52 +08:00
|
|
|
|
function nodeLocation(item?: CarrierScheduleNodeFact | null) { return item ? [item.locationCode, item.locationName].filter(Boolean).join(' · ') || '—' : '—' }
|
2026-08-21 16:04:24 +08:00
|
|
|
|
function exceptionLocation(item: CarrierScheduleExceptionFact) { return [item.locationCode, item.locationName].filter(Boolean).join(' · ') || '—' }
|
|
|
|
|
|
function nodeTimeText(item: CarrierScheduleNodeFact) { return item.actualAt ? `实际 ${formatTime(item.actualAt)}` : item.estimatedAt ? `预计 ${formatTime(item.estimatedAt)}` : `观测 ${formatTime(item.observedAt)}` }
|
|
|
|
|
|
function exceptionText(value: CarrierScheduleExceptionFact['exceptionCode']) { return ({ CUSTOMS_HOLD: '海关扣留', TERMINAL_HOLD: '码头扣留', DOCUMENT_HOLD: '单证扣留', UNKNOWN_HOLD: '其他扣留' } as Record<string, string>)[value] || value }
|
|
|
|
|
|
function historyText(action: string) { return ({ AUTO_CREATE: '国内出仓自动建任务', CREATE: '人工建立查询任务', NODE_OBSERVED: '记录标准节点', PROVIDER_QUERY: '船司查询', MANUAL_NODE: '人工补录标准节点', STOP: '停止查询' } as Record<string, string>)[action] || action }
|
|
|
|
|
|
|
|
|
|
|
|
function resetManualForm() {
|
|
|
|
|
|
Object.assign(manualForm, { standardNode: '', estimatedAt: '', actualAt: '', locationCode: '', locationName: '', reason: '' })
|
|
|
|
|
|
manualError.value = null; manualValidationError.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
function createIdempotencyKey() { return globalThis.crypto?.randomUUID?.() || `carrier-node-${Date.now()}-${Math.random().toString(16).slice(2)}` }
|
|
|
|
|
|
function localDateTimeNow() {
|
|
|
|
|
|
const now = new Date()
|
|
|
|
|
|
return new Date(now.getTime() - now.getTimezoneOffset() * 60_000).toISOString().slice(0, 19)
|
|
|
|
|
|
}
|
|
|
|
|
|
function openManualDialog() { resetManualForm(); manualIdempotencyKey.value = createIdempotencyKey(); manualObservedAt.value = localDateTimeNow(); manualDialogOpen.value = true }
|
|
|
|
|
|
async function submitManualNode() {
|
|
|
|
|
|
manualValidationError.value = ''
|
|
|
|
|
|
if (!query.value || !manualForm.standardNode) { manualValidationError.value = '请选择标准节点'; return }
|
|
|
|
|
|
if (!manualForm.estimatedAt && !manualForm.actualAt) { manualValidationError.value = '预计时间和实际时间至少填写一项'; return }
|
|
|
|
|
|
if (!manualForm.reason.trim()) { manualValidationError.value = '请输入补录原因'; return }
|
|
|
|
|
|
const base = { expectedVersion: query.value.version, standardNode: manualForm.standardNode, observedAt: manualObservedAt.value, locationCode: manualForm.locationCode.trim() || null, locationName: manualForm.locationName.trim() || null, reason: manualForm.reason.trim() }
|
|
|
|
|
|
const request: CarrierScheduleManualNodeRequest = manualForm.actualAt
|
|
|
|
|
|
? { ...base, actualAt: manualForm.actualAt, estimatedAt: manualForm.estimatedAt || null }
|
|
|
|
|
|
: { ...base, estimatedAt: manualForm.estimatedAt, actualAt: null }
|
|
|
|
|
|
manualSaving.value = true; manualError.value = null
|
|
|
|
|
|
try {
|
|
|
|
|
|
const result = await appendCarrierScheduleManualNode(query.value.id, manualIdempotencyKey.value, request)
|
|
|
|
|
|
detail.value = result; selected.value = result.query; queryDetailError.value = null; manualDialogOpen.value = false; manualIdempotencyKey.value = ''; manualObservedAt.value = ''; resetManualForm()
|
|
|
|
|
|
} catch (cause) { manualError.value = getApiErrorDetails(cause, '人工节点补录失败') }
|
|
|
|
|
|
finally { manualSaving.value = false }
|
|
|
|
|
|
}
|
2026-08-19 16:45:51 +08:00
|
|
|
|
|
|
|
|
|
|
async function load() {
|
2026-08-20 09:10:36 +08:00
|
|
|
|
selected.value = null; detail.value = null; latestSchedule.value = null; error.value = null; queryDetailError.value = null; scheduleError.value = null
|
2026-08-19 16:45:51 +08:00
|
|
|
|
if (!props.oceanBookingId && !props.containerBookingId) return
|
|
|
|
|
|
loading.value = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const page = await listCarrierScheduleQueries({ page: 1, size: 200, oceanBookingId: props.oceanBookingId || undefined, containerBookingId: props.containerBookingId || undefined })
|
|
|
|
|
|
const matching = props.portScheduleId ? page.records.filter((item) => item.query.resultPortScheduleId === String(props.portScheduleId)) : page.records
|
2026-08-20 18:02:52 +08:00
|
|
|
|
selected.value = matching[0] || page.records[0] || null
|
2026-08-19 16:45:51 +08:00
|
|
|
|
if (!selected.value) return
|
2026-08-20 09:10:36 +08:00
|
|
|
|
const [queryResult] = await Promise.allSettled([getCarrierScheduleQuery(selected.value.query.id)])
|
2026-08-19 16:45:51 +08:00
|
|
|
|
if (queryResult.status === 'fulfilled') detail.value = queryResult.value
|
2026-08-20 09:10:36 +08:00
|
|
|
|
else queryDetailError.value = getApiErrorDetails(queryResult.reason, '查询历史加载失败')
|
|
|
|
|
|
const resultPortScheduleId = detail.value?.query.query.resultPortScheduleId || selected.value.query.resultPortScheduleId
|
|
|
|
|
|
if (resultPortScheduleId) {
|
|
|
|
|
|
try { latestSchedule.value = (await getPortSchedule(resultPortScheduleId)).portSchedule }
|
|
|
|
|
|
catch (cause) { scheduleError.value = getApiErrorDetails(cause, '最新船期结果加载失败') }
|
|
|
|
|
|
}
|
2026-08-19 16:45:51 +08:00
|
|
|
|
} catch (cause) { error.value = getApiErrorDetails(cause, '船节点查询状态加载失败') }
|
|
|
|
|
|
finally { loading.value = false }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
watch(() => [props.oceanBookingId, props.containerBookingId, props.portScheduleId], () => void load(), { immediate: true })
|
2026-08-21 16:04:24 +08:00
|
|
|
|
watch(manualDialogOpen, (open) => { if (!open && !manualSaving.value) { manualIdempotencyKey.value = ''; manualObservedAt.value = '' } })
|
2026-08-19 16:45:51 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<section class="carrier-query" aria-label="国内出仓后船节点">
|
2026-08-21 16:04:24 +08:00
|
|
|
|
<header><div><h2>国内出仓后船节点</h2><p>沿用 CarrierScheduleQuery,从确认国内出仓跟踪至已还柜;本页面不会新建轮询任务。</p></div><div class="header-actions"><el-button v-if="query && access.can('platform:firstleg:port-schedule-query:manual')" size="small" type="primary" plain @click="openManualDialog"><Plus :size="15" />人工补录节点</el-button><el-tag v-if="query" :type="statusType" effect="light">{{ statusText }}</el-tag></div></header>
|
2026-08-19 16:45:51 +08:00
|
|
|
|
<LoadErrorState v-if="error" compact title="船节点查询状态加载失败" :message="error.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
|
|
|
|
|
|
<div v-else-if="loading" class="loading-state"><el-skeleton :rows="2" animated /></div>
|
2026-08-26 11:10:48 +08:00
|
|
|
|
<div v-else-if="!query" class="compact-empty-state"><strong>尚无关联任务</strong><span>任务仅在确认国内出仓后自动建立,本页面不会创建或模拟任务。</span></div>
|
2026-08-19 16:45:51 +08:00
|
|
|
|
<template v-else>
|
|
|
|
|
|
<el-alert v-if="query.lastErrorMessage || query.status === 'MANUAL_REQUIRED'" type="error" :closable="false" show-icon :title="query.lastErrorMessage || query.manualReason || '自动查询暂不可用,需要人工补充可靠结果。'" />
|
2026-08-20 18:02:52 +08:00
|
|
|
|
<el-alert v-if="query.status === 'STOPPED'" type="info" :closable="false" show-icon :title="query.stopReason || '船节点查询已停止。'" />
|
|
|
|
|
|
<el-alert v-if="queryDetailError" type="warning" :closable="false" show-icon title="标准节点与变更历史暂不可用"><template #default><ApiErrorMeta :details="queryDetailError" /></template></el-alert>
|
2026-08-20 09:10:36 +08:00
|
|
|
|
<el-alert v-if="scheduleError" type="warning" :closable="false" show-icon title="关联船期结果暂不可用"><template #default><ApiErrorMeta :details="scheduleError" /></template></el-alert>
|
2026-08-19 16:45:51 +08:00
|
|
|
|
<dl class="query-facts">
|
2026-08-20 09:10:36 +08:00
|
|
|
|
<div><dt>查询任务</dt><dd>{{ query.queryNo || query.id }}</dd></div><div><dt>跟踪标识</dt><dd>{{ trackingTypeText(query.trackingType) }} · {{ query.trackingNo || '—' }}</dd></div>
|
2026-08-21 16:04:24 +08:00
|
|
|
|
<div><dt>任务操作状态</dt><dd>{{ statusText }}</dd></div><div><dt>建立来源</dt><dd>{{ triggerSourceText(query.triggerSource) }}</dd></div>
|
|
|
|
|
|
<div><dt>最新实际节点</dt><dd>{{ nodeText(query.latestActualNode) }}</dd></div><div><dt>实际发生时间</dt><dd>{{ formatTime(query.latestActualAt) }}</dd></div>
|
|
|
|
|
|
<div><dt>节点位置</dt><dd>{{ nodeLocation(latestActualNodeFact) }}</dd></div><div><dt>观测时间</dt><dd>{{ formatTime(latestObservation) }}</dd></div>
|
|
|
|
|
|
<div><dt>船司 / 访问方式</dt><dd>{{ query.carrierCode }} · {{ query.accessMode || '—' }}</dd></div><div><dt>最新来源</dt><dd>{{ latestSource }}</dd></div>
|
|
|
|
|
|
<div><dt>来源参考</dt><dd class="break-value">{{ sourceReference }}</dd></div><div><dt>来源更新时间</dt><dd>{{ formatTime(query.sourceUpdatedAt) }}</dd></div>
|
|
|
|
|
|
<div><dt>最近成功</dt><dd>{{ formatTime(query.lastSuccessAt) }}</dd></div><div><dt>结果有效性</dt><dd>{{ queryView?.expired ? '结果已过期' : '结果有效' }}</dd></div>
|
2026-08-20 18:02:52 +08:00
|
|
|
|
<div><dt>结果过期时间</dt><dd>{{ formatTime(query.expiresAt) }}</dd></div><div><dt>停止时间</dt><dd>{{ formatTime(query.stoppedAt) }}</dd></div>
|
2026-08-20 09:10:36 +08:00
|
|
|
|
<div v-if="query.lastErrorCode"><dt>最近错误码</dt><dd>{{ query.lastErrorCode }}</dd></div>
|
2026-08-19 16:45:51 +08:00
|
|
|
|
</dl>
|
|
|
|
|
|
<div v-if="latestSchedule" class="latest-result"><strong>{{ latestSchedule.schedule.vesselName }} / {{ latestSchedule.schedule.voyageNo }}</strong><span>{{ latestSchedule.schedule.departurePortName || latestSchedule.schedule.departurePortCode }} → {{ latestSchedule.schedule.destinationPortName || latestSchedule.schedule.destinationPortCode }}</span><span>预计开船 {{ formatTime(latestSchedule.schedule.etdAt) }} · 预计到港 {{ formatTime(latestSchedule.schedule.etaAt) }}</span></div>
|
|
|
|
|
|
<div class="history-grid">
|
2026-08-21 16:04:24 +08:00
|
|
|
|
<section><h3>标准节点历史</h3><p v-if="!nodes.length" class="empty-line">暂无标准节点</p><ol v-else><li v-for="item in nodes" :key="item.id"><span>{{ nodeTimeText(item) }}</span><strong>{{ nodeText(item.standardNode) }}</strong><small>{{ nodeLocation(item) }} · {{ item.providerCode }}<template v-if="item.sourceReference"> · {{ item.sourceReference }}</template><template v-if="item.actualAt && item.estimatedAt"> · 预计 {{ formatTime(item.estimatedAt) }}</template></small></li></ol></section>
|
|
|
|
|
|
<section><h3>异常事实</h3><p v-if="!exceptions.length" class="empty-line">暂无异常事实</p><ol v-else><li v-for="item in exceptions" :key="item.id"><span>{{ formatTime(item.observedAt) }}</span><strong>{{ exceptionText(item.exceptionCode) }} · {{ item.active ? '发生' : '已解除' }}</strong><small>{{ exceptionLocation(item) }} · {{ item.providerCode }} · {{ item.providerStatus }}<template v-if="item.sourceReference"> · {{ item.sourceReference }}</template></small></li></ol></section>
|
2026-08-20 18:02:52 +08:00
|
|
|
|
<section><h3>变更历史</h3><p v-if="!histories.length" class="empty-line">暂无变更历史</p><ol v-else><li v-for="item in histories" :key="item.id"><span>{{ formatTime(item.createdAt) }}</span><strong>{{ historyText(item.action) }}</strong><small>{{ item.operatorName || '系统' }}<template v-if="item.reason"> · {{ item.reason }}</template></small></li></ol></section>
|
2026-08-19 16:45:51 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
2026-08-21 16:04:24 +08:00
|
|
|
|
<AppDialog v-model="manualDialogOpen" title="人工补录船节点" width="min(620px, calc(100vw - 28px))" confirm-text="确认补录" :confirm-loading="manualSaving" @confirm="submitManualNode">
|
|
|
|
|
|
<el-alert v-if="manualError" type="error" :closable="false" show-icon title="补录未成功,已保留当前填写内容"><template #default><ApiErrorMeta :details="manualError" /></template></el-alert>
|
|
|
|
|
|
<el-alert v-if="manualValidationError" type="warning" :closable="false" show-icon :title="manualValidationError" />
|
|
|
|
|
|
<el-alert type="info" :closable="false" title="观测时间按提交时刻记录;预计事实不推进实际进度,既有事实不可编辑或删除。" />
|
|
|
|
|
|
<el-form label-position="top" class="manual-form">
|
|
|
|
|
|
<el-form-item label="标准节点" required><el-select v-model="manualForm.standardNode" placeholder="请选择标准节点" style="width:100%"><el-option v-for="node in standardNodes" :key="node" :label="nodeText(node)" :value="node" /></el-select></el-form-item>
|
|
|
|
|
|
<div class="manual-form-grid"><el-form-item label="预计时间"><el-date-picker v-model="manualForm.estimatedAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="可选" style="width:100%" /></el-form-item><el-form-item label="实际时间"><el-date-picker v-model="manualForm.actualAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="可选,填写后推进实际节点" style="width:100%" /></el-form-item></div>
|
|
|
|
|
|
<div class="manual-form-grid"><el-form-item label="地点编码"><el-input v-model="manualForm.locationCode" maxlength="32" placeholder="可选,如 USLAX" /></el-form-item><el-form-item label="地点名称"><el-input v-model="manualForm.locationName" maxlength="128" placeholder="可选" /></el-form-item></div>
|
|
|
|
|
|
<el-form-item label="补录原因" required><el-input v-model="manualForm.reason" type="textarea" :rows="3" maxlength="1000" show-word-limit placeholder="请输入事实来源与补录原因" /></el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</AppDialog>
|
2026-08-19 16:45:51 +08:00
|
|
|
|
</section>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
2026-08-26 11:10:48 +08:00
|
|
|
|
.carrier-query{display:grid;gap:12px;padding-top:2px}.carrier-query>header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.header-actions{display:flex;align-items:center;gap:8px}.carrier-query h2{margin:0;font-size:15px}.carrier-query header p{margin:4px 0 0;color:var(--cc-muted);font-size:12px}.loading-state{padding:8px 0}.compact-empty-state{display:flex;align-items:center;gap:10px;padding:9px 12px;border-left:3px solid var(--el-color-info);background:var(--cc-bg-subtle,#f7f8fa);font-size:12px}.compact-empty-state span{color:var(--cc-muted)}.query-facts{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px 18px;margin:0}.query-facts>div{min-width:0}.query-facts dt{color:var(--cc-muted);font-size:12px}.query-facts dd{margin:4px 0 0;font-size:13px}.break-value{overflow-wrap:anywhere}.latest-result{display:flex;flex-wrap:wrap;align-items:center;gap:8px 18px;padding:10px 12px;border-left:3px solid var(--el-color-primary);background:var(--el-color-primary-light-9);font-size:13px}.history-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}.history-grid section{min-width:0}.history-grid h3{margin:0 0 8px;font-size:13px}.history-grid ol{display:grid;gap:0;margin:0;padding:0;list-style:none}.history-grid li{display:grid;grid-template-columns:128px 120px minmax(0,1fr);gap:8px;padding:7px 0;border-top:1px solid var(--cc-border);font-size:12px}.history-grid small,.empty-line{color:var(--cc-muted);font-size:12px}.empty-line{margin:0}.manual-form{display:grid;gap:2px;margin-top:12px}.manual-form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}@media(max-width:1200px){.query-facts{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:720px){.query-facts,.history-grid,.manual-form-grid{grid-template-columns:1fr}.history-grid li{grid-template-columns:1fr}.compact-empty-state{align-items:flex-start;flex-direction:column}}
|
2026-08-19 16:45:51 +08:00
|
|
|
|
</style>
|