feat(web-admin): reuse plan form as a full-page webview variant.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
wuzilongandCursor committed 2026-09-14 16:47:05 +08:00
1 parent 1a5aa1c934
commit bc7a1c225a
4 files changed
+199 -26

No files matched your search

@@ -1,19 +1,13 @@
<!-- eslint-disable vue/no-v-model-argument -->
<template>
<el-dialog
v-model="innerVisible"
:title="dialogTitle"
width="100%"
align-center
destroy-on-close
append-to-body
class="dialog-form-layout pl-plan-form-dialog"
modal-class="order-module-dialog-mask"
:close-on-click-modal="false"
style="backdrop-filter: none"
<component
:is="isPageVariant ? 'div' : ElDialog"
v-bind="formShellBind"
@closed="handleClosed"
@opened="handleOpened"
@update:model-value="onFormShellVisible"
>
<h2 v-if="isPageVariant" class="mp-plan-form-title">{{ dialogTitle }}</h2>
<div class="pl-plan-form-body">
<section class="pl-plan-form-section">
<h3 class="pl-plan-form-section-title">基础信息</h3>
@@ -21,7 +15,7 @@
ref="basicFormRef"
:model="formData"
:rules="basicRules"
label-position="right"
:label-position="formLabelPosition"
label-width="100px"
class="pl-dialog-form"
>
@@ -76,7 +70,7 @@
<h3 class="pl-plan-form-section-title">运输信息</h3>
<el-form
:model="formData"
label-position="right"
:label-position="formLabelPosition"
label-width="100px"
class="pl-dialog-form"
>
@@ -198,7 +192,7 @@
<section class="pl-plan-form-section">
<h3 class="pl-plan-form-section-title">装送货信息</h3>
<el-form label-position="right" label-width="100px" class="pl-dialog-form">
<el-form :label-position="formLabelPosition" label-width="100px" class="pl-dialog-form">
<PlPlanStopsSplitShell
:plan-total-qty="planTotalQty"
:load-total-qty="loadTotalQty"
@@ -273,9 +267,9 @@
</section>
</div>
<template #footer>
<template v-if="!isPageVariant" #footer>
<div class="dialog-footer">
<el-button @click="innerVisible = false">
<el-button @click="handleCancel">
<el-icon><Close /></el-icon>取消
</el-button>
<el-button
@@ -312,6 +306,39 @@
</el-button>
</div>
</template>
<div v-else class="mp-plan-form-footer dialog-footer">
<el-button @click="handleCancel">
<el-icon><Close /></el-icon>取消
</el-button>
<el-button v-if="showDraftButton" :loading="submitLoading" @click="handleSubmit('draft')">
<el-icon><Document /></el-icon>暂存
</el-button>
<el-button
v-if="showSaveButton"
:type="showSaveAndArrangeButton ? 'default' : 'primary'"
:class="showSaveAndArrangeButton ? 'btn-ghost' : undefined"
:loading="submitLoading"
@click="handleSubmit('save')"
>
<el-icon><Check /></el-icon>{{ saveButtonLabel }}
</el-button>
<el-button
v-if="showSaveAndArrangeButton"
type="primary"
:loading="submitLoading"
@click="handleSubmit('saveAndArrange')"
>
<el-icon><Van /></el-icon>{{ saveAndArrangeButtonLabel }}
</el-button>
<el-button
v-if="showRearrangeButton"
type="primary"
:loading="submitLoading"
@click="handleSubmit('rearrange')"
>
<el-icon><Van /></el-icon>调整运输
</el-button>
</div>
<CustomerSelector
:visible="customerSelectorVisible"
@@ -342,10 +369,11 @@
mode="vehicle-bundle"
@done="handlePasteRecognizeDone"
/>
</el-dialog>
</component>
</template>
<script setup lang="ts">
import { ElDialog } from 'element-plus'
import {
Bottom,
Check,
@@ -460,11 +488,14 @@
openForArrange?: boolean
/** 归档等场景:仅保存计划,不展示暂存/安排运输(已安排计划也显示「保存」) */
planEditOnly?: boolean
/** 小程序 WebView 全屏页:不使用桌面弹窗遮罩 */
variant?: 'dialog' | 'page'
}>()
const emit = defineEmits<{
'update:visible': [value: boolean]
success: [payload?: { planId: string; arranged?: boolean }]
close: []
}>()
const userStore = useUserStore()
@@ -512,6 +543,8 @@
const isEdit = computed(() => !!props.planId)
const isCopy = computed(() => !props.planId && !!props.copyFromPlanId)
const isPageVariant = computed(() => props.variant === 'page')
const formLabelPosition = computed(() => (isPageVariant.value ? 'top' : 'right'))
const dialogTitle = computed(() => (isEdit.value ? '编辑运输计划' : '新增运输计划'))
const innerVisible = computed({
@@ -519,6 +552,28 @@
set: (value) => emit('update:visible', value)
})
function onFormShellVisible(value: boolean) {
innerVisible.value = value
}
const formShellBind = computed(() => {
if (isPageVariant.value) {
return { class: 'mp-plan-form-page pl-plan-form-dialog' }
}
return {
modelValue: innerVisible.value,
title: dialogTitle.value,
width: '100%',
alignCenter: true,
destroyOnClose: true,
appendToBody: true,
class: 'dialog-form-layout pl-plan-form-dialog',
modalClass: 'order-module-dialog-mask',
closeOnClickModal: false,
style: 'backdrop-filter: none'
}
})
const submitLoading = ref(false)
const detailLoading = ref(false)
/** 加载详情 / 重置时禁止计划产品 → 段产品自动对齐 */
@@ -1793,6 +1848,18 @@
})
}
function handleCancel() {
if (isPageVariant.value) {
emit('close')
return
}
innerVisible.value = false
}
onMounted(() => {
if (isPageVariant.value) handleOpened()
})
async function loadPlanForEdit(planId: string) {
detailLoading.value = true
suppressAddressMutex.value = true
@@ -2348,6 +2415,51 @@
</script>
<style scoped lang="scss">
.mp-plan-form-title {
margin: 0 0 12px;
font-size: 18px;
font-weight: 700;
color: #087f57;
}
.mp-plan-form-page {
:deep(.form-grid-5),
:deep(.form-grid-4),
:deep(.form-grid-3),
:deep(.form-grid-2) {
grid-template-columns: 1fr;
}
.pl-plan-transport-row--two,
.pl-plan-transport-row--three,
.pl-plan-transport-row--four,
.pl-plan-kv-grid-party {
grid-template-columns: 1fr;
}
.pl-plan-transport-row__vehicle,
.pl-plan-transport-row__driver,
.pl-plan-transport-row__escort {
width: auto;
max-width: none;
}
.pl-plan-freight-form {
min-height: 0;
}
}
.mp-plan-form-footer {
position: sticky;
bottom: 0;
z-index: 2;
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 12px 0 calc(8px + env(safe-area-inset-bottom));
background: #f5f8f7;
}
.pl-plan-transport-row {
align-items: start;
+1
View File
@@ -73,6 +73,7 @@
<style scoped lang="scss">
.mp-wv-root {
--brand: #0a9f6d;
--el-color-primary: #0a9f6d;
box-sizing: border-box;
min-height: 100vh;
max-width: 480px;
+23
View File
@@ -138,3 +138,26 @@
border-color: #0a9f6d;
font-weight: 600;
}
.mp-plan-form-page .form-grid-5,
.mp-plan-form-page .form-grid-4,
.mp-plan-form-page .form-grid-3,
.mp-plan-form-page .form-grid-2 {
grid-template-columns: 1fr;
}
.mp-print-hint {
margin: 0 0 10px;
font-size: 12px;
color: #6b7b74;
line-height: 1.5;
}
.mp-print-preview {
overflow: auto;
padding: 8px;
background: #fff;
border: 1px solid #dcebe4;
border-radius: 12px;
-webkit-overflow-scrolling: touch;
}
+46 -9
View File
@@ -1,18 +1,55 @@
<template>
<MpWebviewShell>
<p class="mp-wv-placeholder">计划表单加载中…</p>
<PlPlanFormDialog
:key="formKey"
variant="page"
:visible="true"
:plan-id="planId"
:copy-from-plan-id="copyFrom"
:open-for-arrange="openForArrange"
@success="onSuccess"
@close="onClose"
/>
</MpWebviewShell>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import MpWebviewShell from '@/layouts/MpWebviewShell.vue'
</script>
import PlPlanFormDialog from '@/components/business/pl-plan-form-dialog/index.vue'
import '@/styles/mp-webview.scss'
<style scoped>
.mp-wv-placeholder {
margin: 24px 0 0;
font-size: 14px;
color: #6b7b74;
text-align: center;
const route = useRoute()
const router = useRouter()
const planId = computed(() => {
if (route.name === 'MpPlPlanNew') return undefined
const id = String(route.params.id || '').trim()
return id || undefined
})
const copyFrom = computed(() => {
const id = String(route.query.copyFrom || '').trim()
return id || undefined
})
const openForArrange = computed(() => route.name === 'MpPlPlanArrange')
const formKey = computed(
() => `${String(route.name || '')}-${planId.value || ''}-${copyFrom.value || ''}`
)
function onSuccess(payload?: { planId: string; arranged?: boolean }) {
if (payload?.planId) {
void router.replace(`/m/pl/plan/${payload.planId}`)
return
}
void router.replace('/m/pl/plan')
}
</style>
function onClose() {
if (window.history.length > 1) {
router.back()
return
}
void router.replace('/m/pl/plan')
}
</script>