优化
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const source = readFileSync(resolve(process.cwd(), 'src/modules/loading-lists/pages/PreloadPlanDetailView.vue'), 'utf8')
|
||||
|
||||
describe('PreloadPlanDetailView prototype contract', () => {
|
||||
it('is a formal responsive detail page with a return action', () => {
|
||||
expect(source).toContain('<AdminPage>')
|
||||
expect(source).toContain('返回列表')
|
||||
expect(source).toContain('<ArrowLeft')
|
||||
expect(source).toContain("router.push({ name: 'loading-plans' })")
|
||||
expect(source).not.toContain('AppDrawer')
|
||||
})
|
||||
|
||||
it('requires the real warehouse context and preserves status boundaries', () => {
|
||||
expect(source).toContain('MISSING_WAREHOUSE_CONTEXT')
|
||||
expect(source).toContain("['EDITING', 'LOADING'].includes(result.summary.status)")
|
||||
expect(source).toContain('MODE_STATUS_MISMATCH')
|
||||
})
|
||||
|
||||
it('uses the real optimistic-lock transport endpoint without inventing note writes', () => {
|
||||
expect(source).toContain('updatePreloadTransport')
|
||||
expect(source).toContain('expectedVersion: detail.value.summary.version')
|
||||
expect(source).toContain('width="min(620px, calc(100vw - 28px))"')
|
||||
expect(source).not.toContain('saveMfNote')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,126 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ArrowLeft } from '@lucide/vue'
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import LoadingListDetailDrawer from '../components/LoadingListDetailDrawer.vue'
|
||||
import { getPreloadPlan, updatePreloadTransport, type LoadingDetail, type PreloadSummary } from '../api/platformLoadingLists'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const access = useAccessPreviewStore()
|
||||
const loading = ref(false)
|
||||
const detail = ref<LoadingDetail<PreloadSummary> | null>(null)
|
||||
const error = ref<ApiErrorDetails | null>(null)
|
||||
const editOpen = ref(false)
|
||||
const editSaving = ref(false)
|
||||
const editError = ref<ApiErrorDetails | null>(null)
|
||||
const editFormRef = ref<FormInstance>()
|
||||
const editForm = reactive({ containerNo: '', voyageNo: '', operationReason: '' })
|
||||
const planId = computed(() => String(route.params.planId || ''))
|
||||
const warehouseId = computed(() => typeof route.query.warehouseId === 'string' ? route.query.warehouseId : '')
|
||||
const canEdit = computed(() => access.can('platform:firstleg:preload:edit'))
|
||||
const editRules: FormRules = {
|
||||
containerNo: [{ max: 64, message: '集装箱号不能超过 64 个字符', trigger: 'blur' }],
|
||||
voyageNo: [{ max: 64, message: '航次不能超过 64 个字符', trigger: 'blur' }],
|
||||
operationReason: [{ max: 500, message: '修改原因不能超过 500 个字符', trigger: 'blur' }],
|
||||
}
|
||||
|
||||
async function loadDetail() {
|
||||
if (!planId.value || !warehouseId.value) {
|
||||
error.value = { code: 'MISSING_WAREHOUSE_CONTEXT', message: '该清单缺少所属仓库信息,无法安全查询详情,请返回列表重新进入。' }
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const result = await getPreloadPlan(planId.value, warehouseId.value)
|
||||
if (!['EDITING', 'LOADING'].includes(result.summary.status)) {
|
||||
detail.value = null
|
||||
error.value = { code: 'MODE_STATUS_MISMATCH', message: '该计划不属于预装清单状态,请从对应清单入口查看。' }
|
||||
return
|
||||
}
|
||||
detail.value = result
|
||||
} catch (cause) {
|
||||
error.value = getApiErrorDetails(cause, '预装清单详情加载失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
if (window.history.length > 1) router.back()
|
||||
else void router.push({ name: 'loading-plans' })
|
||||
}
|
||||
|
||||
function openEdit() {
|
||||
if (!detail.value || !['EDITING', 'LOADING'].includes(detail.value.summary.status)) return
|
||||
editForm.containerNo = detail.value.summary.containerNo || ''
|
||||
editForm.voyageNo = detail.value.summary.voyageNo || ''
|
||||
editForm.operationReason = ''
|
||||
editError.value = null
|
||||
editOpen.value = true
|
||||
}
|
||||
|
||||
async function saveTransport() {
|
||||
if (!detail.value || !(await editFormRef.value?.validate().catch(() => false))) return
|
||||
editSaving.value = true
|
||||
editError.value = null
|
||||
try {
|
||||
detail.value = await updatePreloadTransport(planId.value, warehouseId.value, {
|
||||
containerNo: editForm.containerNo.trim() || null,
|
||||
voyageNo: editForm.voyageNo.trim() || null,
|
||||
operationReason: editForm.operationReason.trim() || null,
|
||||
expectedVersion: detail.value.summary.version,
|
||||
})
|
||||
editOpen.value = false
|
||||
ElMessage.success('运输信息已更新')
|
||||
} catch (cause) {
|
||||
const details = getApiErrorDetails(cause, '运输信息保存失败,请刷新详情后重试')
|
||||
if (details.code === 'A0409' || /版本|并发|已被其他人修改/.test(details.message)) {
|
||||
await loadDetail()
|
||||
editError.value = { ...details, message: '运输信息已被其他人修改,系统已刷新最新版本;当前输入已保留,请核对后重新保存。' }
|
||||
} else editError.value = details
|
||||
} finally {
|
||||
editSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => void loadDetail())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminPage>
|
||||
<div class="preload-detail-page">
|
||||
<el-button link type="primary" class="back-button" @click="goBack"><ArrowLeft :size="16" />返回列表</el-button>
|
||||
<LoadErrorState v-if="error && !detail" title="预装清单详情加载失败" :message="error.message" :loading="loading" @retry="loadDetail">
|
||||
<template #details><ApiErrorMeta :details="error" /></template>
|
||||
</LoadErrorState>
|
||||
<LoadingListDetailDrawer v-else :mode="'preload'" :loading="loading" :detail="detail" :error="error" :can-edit="canEdit" @retry="loadDetail" @edit="openEdit" />
|
||||
</div>
|
||||
|
||||
<AppDialog v-model="editOpen" class="loading-transport-dialog" title="编辑预装运输信息" width="min(620px, calc(100vw - 28px))" confirm-text="保存" :confirm-loading="editSaving" :confirm-disabled="!canEdit" @confirm="saveTransport">
|
||||
<el-form ref="editFormRef" class="transport-form" :model="editForm" :rules="editRules" label-position="top">
|
||||
<el-form-item label="集装箱号" prop="containerNo"><el-input v-model="editForm.containerNo" maxlength="64" placeholder="请输入集装箱号" /></el-form-item>
|
||||
<el-form-item label="航次" prop="voyageNo"><el-input v-model="editForm.voyageNo" maxlength="64" placeholder="请输入航次" /></el-form-item>
|
||||
<el-form-item label="修改原因" prop="operationReason"><el-input v-model="editForm.operationReason" type="textarea" :rows="3" maxlength="500" placeholder="选填,填写后将记录在操作历史中" /></el-form-item>
|
||||
</el-form>
|
||||
<LoadErrorState v-if="editError" compact title="保存失败" :message="editError.message" :show-retry="false"><template #details><ApiErrorMeta :details="editError" /></template></LoadErrorState>
|
||||
</AppDialog>
|
||||
</AdminPage>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.preload-detail-page { display: grid; min-width: 0; max-width: 1180px; gap: 16px; }
|
||||
.back-button { width: fit-content; }
|
||||
.transport-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; }
|
||||
.transport-form :deep(.el-form-item:last-child) { grid-column: 1 / -1; }
|
||||
:global(.loading-transport-dialog.el-dialog) { max-height: 90vh; overflow: hidden; }
|
||||
@media (max-width: 720px) { .transport-form { grid-template-columns: 1fr; } .transport-form :deep(.el-form-item:last-child) { grid-column: auto; } }
|
||||
</style>
|
||||
Reference in New Issue
Block a user