feat(web-admin): add page variant for contract form in mp webview.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
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));
|
||||
|
||||
@@ -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>
|
||||
Reference in new issue
Block a user