feat(web-admin): add page variant for quote form in mp webview.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
wuzilongandCursor committed 2026-10-15 16:03:19 +08:00
1 parent eff40b2e8f
commit b9e44bd697
3 files changed
+308 -67

No files matched your search

@@ -1,5 +1,6 @@
<template> <template>
<el-dialog <el-dialog
v-if="!isPageVariant"
v-model="visible" v-model="visible"
:title="dialogTitle" :title="dialogTitle"
width="1680px" width="1680px"
@@ -33,81 +34,192 @@
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
<div v-else class="mp-quote-form-page">
<QuoteFormPanel
ref="panelRef"
variant="page"
hide-footer
:auto-init="false"
@success="handleSuccess"
@close="emit('close')"
/>
<div class="mp-plan-form-footer">
<el-button class="btn-ghost" @click="emit('close')">
<el-icon><Close /></el-icon>取消
</el-button>
<el-button class="btn-ghost" :loading="saveLoading" @click="handleSaveDraft">
<el-icon><DocumentCopy /></el-icon>保存草稿
</el-button>
<el-button class="btn-main" type="primary" @click="handleSubmit">
<el-icon><CircleCheck /></el-icon>提交审批
</el-button>
</div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, nextTick, ref } from 'vue' import { computed, nextTick, onMounted, ref, watch } from 'vue'
import { CircleCheck, Close, DocumentCopy } from '@element-plus/icons-vue' import { CircleCheck, Close, DocumentCopy } from '@element-plus/icons-vue'
import QuoteFormPanel from './QuoteFormPanel.vue' import QuoteFormPanel from './QuoteFormPanel.vue'
const emit = defineEmits<{ const props = withDefaults(
success: [] defineProps<{
}>() modelValue?: boolean
variant?: 'dialog' | 'page'
pageMode?: 'add' | 'edit' | 'copy'
quoteId?: string | number | null
copyFromId?: string | number | null
}>(),
{
modelValue: false,
variant: 'dialog',
pageMode: 'add',
quoteId: null,
copyFromId: null
}
)
const visible = ref(false) const emit = defineEmits<{
const dialogMode = ref<'add' | 'edit' | 'copy'>('add') success: []
const editId = ref<string | number>('') close: []
const copyId = ref<string | number>('') 'update:modelValue': [value: boolean]
const panelRef = ref<InstanceType<typeof QuoteFormPanel> | null>(null) 'loading-change': [loading: boolean]
const saveLoading = computed(() => Boolean(panelRef.value?.saveLoading)) }>()
const dialogTitle = computed(() => {
if (dialogMode.value === 'edit') return '编辑报价单'
if (dialogMode.value === 'copy') return '复制新增报价单'
return '新增报价单'
})
const handleOpened = async () => { const isPageVariant = computed(() => props.variant === 'page')
await nextTick() const visible = ref(false)
if (dialogMode.value === 'edit' && editId.value) { const dialogMode = ref<'add' | 'edit' | 'copy'>('add')
await panelRef.value?.initEdit(editId.value) const editId = ref<string | number>('')
return const copyId = ref<string | number>('')
const panelRef = ref<InstanceType<typeof QuoteFormPanel> | null>(null)
const pageBooting = ref(false)
const saveLoading = computed(() => Boolean(panelRef.value?.saveLoading))
const dialogTitle = computed(() => {
if (dialogMode.value === 'edit') return '编辑报价单'
if (dialogMode.value === 'copy') return '复制新增报价单'
return '新增报价单'
})
const handleOpened = async () => {
await nextTick()
if (dialogMode.value === 'edit' && editId.value) {
await panelRef.value?.initEdit(editId.value)
return
}
if (dialogMode.value === 'copy' && copyId.value) {
await panelRef.value?.initCopy(copyId.value)
return
}
await panelRef.value?.initNew()
} }
if (dialogMode.value === 'copy' && copyId.value) {
await panelRef.value?.initCopy(copyId.value) const handleSaveDraft = () => {
return panelRef.value?.handleSaveDraft()
} }
await panelRef.value?.initNew()
}
const handleSaveDraft = () => { const handleSubmit = () => {
panelRef.value?.handleSaveDraft() panelRef.value?.handleSubmit()
} }
const handleSubmit = () => { const handleSuccess = () => {
panelRef.value?.handleSubmit() if (!isPageVariant.value) {
} visible.value = false
}
emit('success')
}
const handleSuccess = () => { async function bootPageMode() {
visible.value = false pageBooting.value = true
emit('success') emit('loading-change', true)
} await nextTick()
try {
if (props.pageMode === 'edit' && props.quoteId) {
await panelRef.value?.initEdit(props.quoteId)
} else if (props.pageMode === 'copy' && props.copyFromId) {
await panelRef.value?.initCopy(props.copyFromId)
} else {
await panelRef.value?.initNew()
}
} finally {
pageBooting.value = false
emit('loading-change', false)
}
}
const open = () => { watch(
dialogMode.value = 'add' () =>
editId.value = '' [
copyId.value = '' isPageVariant.value,
visible.value = true props.pageMode,
} props.quoteId,
props.copyFromId
] as const,
() => {
if (isPageVariant.value) {
void bootPageMode()
}
}
)
const openEdit = (id: string | number) => { onMounted(() => {
dialogMode.value = 'edit' if (isPageVariant.value) {
editId.value = id void bootPageMode()
copyId.value = '' }
visible.value = true })
}
const openCopy = (id: string | number) => { const open = () => {
dialogMode.value = 'copy' dialogMode.value = 'add'
copyId.value = id editId.value = ''
editId.value = '' copyId.value = ''
visible.value = true visible.value = true
} }
defineExpose({ open, openEdit, openCopy }) const openEdit = (id: string | number) => {
dialogMode.value = 'edit'
editId.value = id
copyId.value = ''
visible.value = true
}
const openCopy = (id: string | number) => {
dialogMode.value = 'copy'
copyId.value = id
editId.value = ''
visible.value = true
}
defineExpose({ open, openEdit, openCopy })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
:deep(.quote-form-dialog .el-dialog__body) { :deep(.quote-form-dialog .el-dialog__body) {
padding-top: 8px; padding-top: 8px;
} }
.mp-quote-form-page {
display: flex;
flex-direction: column;
min-height: calc(100vh - 120px);
padding: 0 12px 72px;
}
.mp-plan-form-footer {
position: sticky;
bottom: 0;
z-index: 12;
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 0 -12px;
padding: 10px 12px calc(10px + env(safe-area-inset-bottom, 0px));
background: #fff;
border-top: 1px solid #dcebe4;
.el-button {
flex: 1 1 0;
min-width: 0;
}
}
</style> </style>
@@ -1,11 +1,11 @@
<template> <template>
<div class="quote-form-panel"> <div class="quote-form-panel" :class="{ 'quote-form-panel--page': isPageVariant }">
<el-form <el-form
ref="formRef" ref="formRef"
:model="form" :model="form"
:rules="rules" :rules="rules"
label-position="right" :label-position="isPageVariant ? 'top' : 'right'"
label-width="120px" :label-width="isPageVariant ? undefined : '120px'"
class="crm-dialog-form quote-form-body" class="crm-dialog-form quote-form-body"
scroll-to-error scroll-to-error
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }" :scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
@@ -672,13 +672,18 @@ const props = withDefaults(
defineProps<{ defineProps<{
hideFooter?: boolean hideFooter?: boolean
autoInit?: boolean autoInit?: boolean
/** 小程序 WebView 全屏页:单列布局 */
variant?: 'dialog' | 'page'
}>(), }>(),
{ {
hideFooter: false, hideFooter: false,
autoInit: true autoInit: true,
variant: 'dialog'
} }
) )
const isPageVariant = computed(() => props.variant === 'page')
const emit = defineEmits<{ const emit = defineEmits<{
success: [] success: []
close: [] close: []
@@ -2417,6 +2422,32 @@ onMounted(async () => {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.quote-form-panel--page {
.quote-form-body {
:deep(.form-grid-4),
:deep(.form-grid-2) {
display: grid;
grid-template-columns: 1fr;
gap: 12px;
}
:deep(.form-col-span-2),
:deep([class*='form-col-span']) {
grid-column: 1 / -1;
}
}
:deep(.dialog-list-panel),
:deep(.list-body),
:deep(.el-table) {
max-width: 100%;
}
:deep(.dialog-list-panel) {
overflow-x: auto;
}
}
.quote-form-body { .quote-form-body {
:deep(.el-form-item__label) { :deep(.el-form-item__label) {
white-space: nowrap; white-space: nowrap;
+102 -4
View File
@@ -2,16 +2,114 @@
<MpWebviewShell> <MpWebviewShell>
<header class="mp-wv-header"> <header class="mp-wv-header">
<div class="mp-wv-header__row"> <div class="mp-wv-header__row">
<h1 class="mp-wv-header__title">{{ title }}</h1> <button class="mp-wv-header__back" type="button" aria-label="返回列表" @click="goList">
←
</button>
<h1 class="mp-wv-header__title">{{ pageTitle }}</h1>
</div> </div>
</header> </header>
<div
v-if="contentLoading"
class="mp-plan-form-loading mp-plan-form-loading--page"
role="status"
aria-live="polite"
aria-busy="true"
>
<div class="mp-plan-form-loading__orb" aria-hidden="true">
<span class="mp-plan-form-loading__dot" />
</div>
<p class="mp-plan-form-loading__title">{{ loadingCopy.title }}</p>
<p class="mp-plan-form-loading__desc">{{ loadingCopy.desc }}</p>
<div class="mp-plan-form-loading__skel" aria-hidden="true">
<div class="mp-wv-skel mp-wv-skel--block" />
<div class="mp-wv-skel mp-wv-skel--mid" />
<div class="mp-wv-skel mp-wv-skel--line" />
<div class="mp-wv-skel mp-wv-skel--short" />
</div>
</div>
<QuoteFormDialog
v-show="!contentLoading"
:key="formKey"
variant="page"
:model-value="true"
:page-mode="pageMode"
:quote-id="quoteId"
:copy-from-id="copyFromId"
@loading-change="onLoadingChange"
@success="onSuccess"
@close="goList"
/>
</MpWebviewShell> </MpWebviewShell>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed, ref, watch } from 'vue'
import { useRoute } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import MpWebviewShell from '@/layouts/MpWebviewShell.vue' import MpWebviewShell from '@/layouts/MpWebviewShell.vue'
import QuoteFormDialog from '@/views/crm/quote/components/QuoteFormDialog.vue'
import { MP_CRM_QUOTE_LIST_PATH } from '@/utils/mp-webview'
import '@/styles/mp-webview.scss' import '@/styles/mp-webview.scss'
const route = useRoute() const route = useRoute()
const title = computed(() => String(route.meta.title || '')) const router = useRouter()
const contentLoading = ref(true)
const pageMode = computed<'add' | 'edit' | 'copy'>(() => {
if (route.name === 'MpCrmQuoteEdit') return 'edit'
const copyFrom = String(route.query.copyFrom || '').trim()
if (copyFrom) return 'copy'
return 'add'
})
const pageTitle = computed(() => {
if (pageMode.value === 'copy') return '复制新增报价单'
if (pageMode.value === 'edit') return '编辑报价单'
return String(route.meta.title || '新增报价单')
})
const quoteId = computed(() => {
if (pageMode.value !== 'edit') return null
const id = String(route.params.id || '').trim()
return id || null
})
const copyFromId = computed(() => {
if (pageMode.value !== 'copy') return null
const id = String(route.query.copyFrom || '').trim()
return id || null
})
const formKey = computed(
() => `${String(route.name || '')}-${pageMode.value}-${quoteId.value || ''}-${copyFromId.value || ''}`
)
const loadingCopy = computed(() => {
if (pageMode.value === 'edit') {
return { title: '正在加载报价单', desc: '请稍候,正在获取报价单详情…' }
}
if (pageMode.value === 'copy') {
return { title: '正在复制报价单', desc: '请稍候,正在预填原单数据…' }
}
return { title: '正在准备表单', desc: '请稍候,正在初始化主数据…' }
})
watch(formKey, () => {
contentLoading.value = true
})
function onLoadingChange(loading: boolean) {
contentLoading.value = loading
}
function goList() {
void router.replace(MP_CRM_QUOTE_LIST_PATH)
}
function onSuccess() {
if (pageMode.value === 'edit' && quoteId.value) {
void router.replace(`/m/crm/quote/${quoteId.value}`)
return
}
goList()
}
</script> </script>