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

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
wuzilongandCursor committed 2026-10-15 17:36:29 +08:00
1 parent 796b434a28
commit a33c2d1f2e
2 files changed
+228 -18

No files matched your search

@@ -1,13 +1,20 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="formTitle"
width="calc(100vw - 48px)"
:title="isPageVariant ? undefined : formTitle"
:width="isPageVariant ? '100%' : 'calc(100vw - 48px)'"
:fullscreen="isPageVariant"
:show-close="!isPageVariant"
:modal="!isPageVariant"
:append-to-body="!isPageVariant"
align-center
class="dialog-form-layout contract-form-dialog"
:class="[
'dialog-form-layout',
'contract-form-dialog',
{ 'contract-form-dialog--page': isPageVariant }
]"
destroy-on-close
append-to-body
modal-class="order-module-dialog-mask"
:modal-class="isPageVariant ? undefined : 'order-module-dialog-mask'"
:close-on-click-modal="false"
@opened="handleOpened"
>
@@ -20,9 +27,10 @@
ref="formRef"
:model="form"
:rules="rules"
label-position="right"
label-width="120px"
:label-position="isPageVariant ? 'top' : 'right'"
:label-width="isPageVariant ? undefined : '120px'"
class="crm-dialog-form"
:class="{ 'crm-dialog-form--page': isPageVariant }"
:disabled="formLoading"
>
<!-- 合同基础信息(一行 4 列) -->
@@ -946,18 +954,29 @@ type ExtendedContractProductForm = ContractProductForm & {
priceTiers?: PriceTierRow[]
}
const props = defineProps<{
visible: boolean
editId: string | null
/** 复制来源合同(列表选中行,含 products) */
copySource?: ContractVO | null
}>()
const props = withDefaults(
defineProps<{
visible: boolean
editId: string | null
/** 复制来源合同(列表选中行,含 products) */
copySource?: ContractVO | null
/** dialog=桌面弹窗;page=小程序 WebView 全屏页 */
variant?: 'dialog' | 'page'
}>(),
{
copySource: null,
variant: 'dialog'
}
)
const emit = defineEmits<{
'update:visible': [value: boolean]
success: []
'loading-change': [loading: boolean]
}>()
const isPageVariant = computed(() => props.variant === 'page')
const dialogVisible = computed({
get: () => props.visible,
set: (val) => emit('update:visible', val)
@@ -1098,12 +1117,18 @@ watch(
(visible) => {
if (visible && (props.editId || props.copySource)) {
formLoading.value = true
emit('loading-change', true)
} else if (!visible) {
formLoading.value = false
emit('loading-change', false)
}
}
)
watch(formLoading, (loading) => {
if (props.visible) emit('loading-change', loading)
})
// 组件引用
const formRef = ref<FormInstance>()
const approvalDialogRef = ref()
@@ -1368,7 +1393,7 @@ const handleOpened = async () => {
resetForm()
const isEdit = !!props.editId
const isCopy = !!props.copySource
if (isEdit || isCopy) {
if (isEdit || isCopy || isPageVariant.value) {
formLoading.value = true
}
try {
@@ -3024,6 +3049,32 @@ const loadSupplierList = async () => {
flex-direction: column;
}
&.contract-form-dialog--page {
:deep(.el-dialog__wrapper) {
top: 0 !important;
height: 100% !important;
}
:deep(.el-dialog) {
max-height: 100%;
border-radius: 0;
box-shadow: none;
}
:deep(.el-dialog__header) {
display: none;
}
:deep(.el-dialog__body) {
padding: 8px 12px 16px;
}
:deep(.el-dialog__footer) {
padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
border-top: 1px solid #e2e8f0;
}
}
:deep(.dialog-footer) {
display: flex;
justify-content: center;
@@ -3053,6 +3104,22 @@ const loadSupplierList = async () => {
padding: 0 4px;
}
.crm-dialog-form--page {
.contract-basic-info {
grid-template-columns: 1fr;
}
:deep(.el-row) {
display: flex;
flex-direction: column;
}
:deep(.el-col) {
width: 100% !important;
max-width: 100% !important;
}
}
.contract-basic-info {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
+147 -4
View File
@@ -2,16 +2,159 @@
<MpWebviewShell>
<header class="mp-wv-header">
<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>
</header>
<div
v-if="contentLoading || bootLoading"
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>
<div v-else-if="bootError" class="mp-wv-empty">{{ bootError }}</div>
<ContractFormDialog
v-if="formReady"
v-show="!contentLoading"
:key="formKey"
variant="page"
:visible="true"
:edit-id="editId"
:copy-source="copySource"
@loading-change="onLoadingChange"
@success="onSuccess"
@update:visible="onVisibleChange"
/>
</MpWebviewShell>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { computed, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import MpWebviewShell from '@/layouts/MpWebviewShell.vue'
import ContractFormDialog from '@/views/crm/contract/components/ContractFormDialog.vue'
import { getContract, type ContractVO } from '@/api/crm/contract'
import { MP_CRM_CONTRACT_LIST_PATH } from '@/utils/mp-webview'
import '@/styles/mp-webview.scss'
const route = useRoute()
const title = computed(() => String(route.meta.title || ''))
const router = useRouter()
const contentLoading = ref(true)
const bootLoading = ref(false)
const bootError = ref('')
const formReady = ref(false)
const copySource = ref<ContractVO | null>(null)
const pageMode = computed<'add' | 'edit' | 'copy'>(() => {
if (route.name === 'MpCrmContractEdit') 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 editId = 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}-${editId.value || ''}-${copyFromId.value || ''}`
)
const loadingCopy = computed(() => {
if (pageMode.value === 'edit') {
return { title: '正在加载合同', desc: '请稍候,正在获取合同详情…' }
}
if (pageMode.value === 'copy') {
return { title: '正在复制合同', desc: '请稍候,正在预填原单数据…' }
}
return { title: '正在准备表单', desc: '请稍候,正在初始化主数据…' }
})
async function bootForm() {
formReady.value = false
bootError.value = ''
copySource.value = null
contentLoading.value = true
if (pageMode.value === 'copy' && copyFromId.value) {
bootLoading.value = true
try {
const res = await getContract(copyFromId.value)
copySource.value = ((res as any)?.data ?? res) as ContractVO
formReady.value = true
} catch (error) {
bootError.value = error instanceof Error ? error.message : '加载复制来源失败'
} finally {
bootLoading.value = false
}
return
}
formReady.value = true
}
watch(
formKey,
() => {
void bootForm()
},
{ immediate: true }
)
function onLoadingChange(loading: boolean) {
contentLoading.value = loading
}
function goList() {
void router.replace(MP_CRM_CONTRACT_LIST_PATH)
}
function onVisibleChange(visible: boolean) {
if (!visible) goList()
}
function onSuccess() {
if (pageMode.value === 'edit' && editId.value) {
void router.replace(`/m/crm/contract/${editId.value}`)
return
}
goList()
}
</script>
<style scoped lang="scss">
:deep(.contract-form-dialog--page) {
position: relative;
z-index: 1;
}
</style>