This commit is contained in:
2026-08-19 16:45:51 +08:00
parent d1de19dc82
commit 65cd9ecefa
79 changed files with 3397 additions and 38 deletions
@@ -84,4 +84,9 @@ describe('PortSchedulesView', () => {
expect(source).toContain('<el-button link type="primary" @click="loadCustomers"')
expect(source).toContain(':total="total" stripe row-key="schedule.id"')
})
it('shows query facts only inside the existing read-only schedule dialog', () => {
expect(source).toContain('CarrierScheduleQueryPanel')
expect(source).toContain('v-if="dialogReadonly && form.oceanBookingId"')
expect(source).toContain(':port-schedule-id="editingId"')
})
})
@@ -13,6 +13,7 @@ import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
import { listOceanBookings, type OceanBookingResource } from '@/modules/ocean-bookings'
import CarrierScheduleQueryPanel from '../components/CarrierScheduleQueryPanel.vue'
import { createPortSchedule, getPortSchedule, listPortSchedules, updatePortSchedule } from '../api/platformPortSchedules'
import type { PortScheduleData, PortScheduleView } from '../types'
@@ -63,7 +64,7 @@ watch([page, pageSize], () => void load()); onMounted(() => { void load(); void
<template #vessel="{ row }">{{ row.schedule.vesselName }} / {{ row.schedule.voyageNo }}</template><template #observedAt="{ row }">{{ formatDateTime(row.schedule.observedAt) }}</template><template #updatedAt="{ row }">{{ formatDateTime(row.schedule.updatedAt) }}</template><template #expired="{ row }"><el-tag :type="row.expired ? 'danger' : 'success'">{{ row.expired ? '已过期' : '有效' }}</el-tag></template>
</DataTable></section>
</ListPageLayout>
<AppDialog v-model="dialogOpen" :title="dialogTitle" width="820px" :confirm-loading="saving" :show-confirm="!dialogReadonly" :cancel-text="dialogReadonly ? '关闭' : '取消'" @confirm="save"><el-alert v-if="actionError" type="error" :closable="false"><ApiErrorMeta :details="actionError" /></el-alert><div v-if="customersError" class="selector-error"><span>{{ customersError.message }}</span><el-button link type="primary" @click="loadCustomers">重新加载客户</el-button></div><div v-if="bookingsError" class="selector-error"><span>{{ bookingsError.message }}</span><el-button link type="primary" @click="loadBookings()">重新加载订舱</el-button></div><el-form ref="formRef" :model="form" :rules="rules" :disabled="dialogReadonly" label-position="top" class="schedule-form"><el-row :gutter="16">
<AppDialog v-model="dialogOpen" :title="dialogTitle" width="820px" :confirm-loading="saving" :show-confirm="!dialogReadonly" :cancel-text="dialogReadonly ? '关闭' : '取消'" @confirm="save"><div class="schedule-dialog-body"><el-alert v-if="actionError" type="error" :closable="false"><ApiErrorMeta :details="actionError" /></el-alert><div v-if="customersError" class="selector-error"><span>{{ customersError.message }}</span><el-button link type="primary" @click="loadCustomers">重新加载客户</el-button></div><div v-if="bookingsError" class="selector-error"><span>{{ bookingsError.message }}</span><el-button link type="primary" @click="loadBookings()">重新加载订舱</el-button></div><el-form ref="formRef" :model="form" :rules="rules" :disabled="dialogReadonly" label-position="top" class="schedule-form"><el-row :gutter="16">
<el-col :xs="24" :sm="12"><el-form-item label="客户" prop="customerId"><el-select :model-value="form.customerId" filterable placeholder="请选择客户" :loading="customersLoading" no-data-text="暂无可用客户" style="width:100%" @update:model-value="handleCustomerChange(String($event || ''))"><el-option v-for="customer in selectableCustomers" :key="String(customer.id)" :label="customerLabel(customer)" :value="String(customer.id)" /></el-select></el-form-item></el-col>
<el-col :xs="24" :sm="12"><el-form-item label="订舱记录" prop="oceanBookingId"><el-select v-model="form.oceanBookingId" filterable placeholder="请先选择客户,再选择订舱记录" :loading="bookingsLoading" :disabled="!form.customerId" no-data-text="该客户暂无订舱记录" style="width:100%"><el-option v-for="booking in bookings" :key="booking.id" :label="bookingLabel(booking)" :value="booking.id" /></el-select></el-form-item></el-col>
<el-col :xs="24" :sm="12"><el-form-item label="信息来源" prop="sourceName"><el-input v-model="form.sourceName" placeholder="如美森官网" /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="来源参考" prop="sourceRef"><el-input v-model="form.sourceRef" placeholder="查询链接或参考号" /></el-form-item></el-col>
@@ -72,6 +73,6 @@ watch([page, pageSize], () => void load()); onMounted(() => { void load(); void
<el-col :xs="24" :sm="12"><el-form-item label="预计开船"><el-date-picker v-model="form.etdAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择预计开船时间" style="width:100%" /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="预计到港"><el-date-picker v-model="form.etaAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择预计到港时间" style="width:100%" /></el-form-item></el-col>
<el-col :xs="24" :sm="12"><el-form-item label="查询时间" prop="observedAt"><el-date-picker v-model="form.observedAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择本次查询时间" style="width:100%" /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="过期时间" prop="expiresAt"><el-date-picker v-model="form.expiresAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择结果过期时间" style="width:100%" /></el-form-item></el-col>
<el-col :xs="24"><el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="请输入船期补充说明(可选)" /></el-form-item></el-col>
</el-row></el-form></AppDialog></AdminPage>
</el-row></el-form><CarrierScheduleQueryPanel v-if="dialogReadonly && form.oceanBookingId" class="schedule-query-panel" :ocean-booking-id="form.oceanBookingId" :container-booking-id="form.containerBookingId" :port-schedule-id="editingId" /></div></AppDialog></AdminPage>
</template>
<style scoped>.schedule-form{max-height:65vh;overflow:auto;padding-inline:2px}.selector-error{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;padding:6px 10px;border-radius:var(--cc-radius-sm);background:var(--cc-danger-soft);color:var(--el-color-danger);font-size:12px;line-height:20px}.selector-error span{overflow-wrap:anywhere}:global(.el-dialog:has(.schedule-form)){width:min(820px,calc(100vw - 28px))!important;max-height:90vh;overflow:hidden}@media(max-width:720px){.schedule-form{max-height:68vh}}</style>
<style scoped>.schedule-dialog-body{max-height:65vh;overflow:auto;padding-inline:2px}.schedule-query-panel{margin-top:4px;padding-top:16px;border-top:1px solid var(--cc-border)}.selector-error{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;padding:6px 10px;border-radius:var(--cc-radius-sm);background:var(--cc-danger-soft);color:var(--el-color-danger);font-size:12px;line-height:20px}.selector-error span{overflow-wrap:anywhere}:global(.el-dialog:has(.schedule-form)){width:min(820px,calc(100vw - 28px))!important;max-height:90vh;overflow:hidden}@media(max-width:720px){.schedule-dialog-body{max-height:68vh}}</style>