feat(web-admin): add page variant for quote form in mp webview.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
eff40b2e8f
commit
b9e44bd697
3 files changed
+308
-67
No files matched your search
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-if="!isPageVariant"
|
||||
v-model="visible"
|
||||
:title="dialogTitle"
|
||||
width="1680px"
|
||||
@@ -33,81 +34,192 @@
|
||||
</div>
|
||||
</template>
|
||||
</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>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref } from 'vue'
|
||||
import { CircleCheck, Close, DocumentCopy } from '@element-plus/icons-vue'
|
||||
import QuoteFormPanel from './QuoteFormPanel.vue'
|
||||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||
import { CircleCheck, Close, DocumentCopy } from '@element-plus/icons-vue'
|
||||
import QuoteFormPanel from './QuoteFormPanel.vue'
|
||||
|
||||
const emit = defineEmits<{
|
||||
success: []
|
||||
}>()
|
||||
const props = withDefaults(
|
||||
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 dialogMode = ref<'add' | 'edit' | 'copy'>('add')
|
||||
const editId = ref<string | number>('')
|
||||
const copyId = ref<string | number>('')
|
||||
const panelRef = ref<InstanceType<typeof QuoteFormPanel> | null>(null)
|
||||
const saveLoading = computed(() => Boolean(panelRef.value?.saveLoading))
|
||||
const dialogTitle = computed(() => {
|
||||
if (dialogMode.value === 'edit') return '编辑报价单'
|
||||
if (dialogMode.value === 'copy') return '复制新增报价单'
|
||||
return '新增报价单'
|
||||
})
|
||||
const emit = defineEmits<{
|
||||
success: []
|
||||
close: []
|
||||
'update:modelValue': [value: boolean]
|
||||
'loading-change': [loading: boolean]
|
||||
}>()
|
||||
|
||||
const handleOpened = async () => {
|
||||
await nextTick()
|
||||
if (dialogMode.value === 'edit' && editId.value) {
|
||||
await panelRef.value?.initEdit(editId.value)
|
||||
return
|
||||
const isPageVariant = computed(() => props.variant === 'page')
|
||||
const visible = ref(false)
|
||||
const dialogMode = ref<'add' | 'edit' | 'copy'>('add')
|
||||
const editId = ref<string | number>('')
|
||||
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)
|
||||
return
|
||||
|
||||
const handleSaveDraft = () => {
|
||||
panelRef.value?.handleSaveDraft()
|
||||
}
|
||||
await panelRef.value?.initNew()
|
||||
}
|
||||
|
||||
const handleSaveDraft = () => {
|
||||
panelRef.value?.handleSaveDraft()
|
||||
}
|
||||
const handleSubmit = () => {
|
||||
panelRef.value?.handleSubmit()
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
panelRef.value?.handleSubmit()
|
||||
}
|
||||
const handleSuccess = () => {
|
||||
if (!isPageVariant.value) {
|
||||
visible.value = false
|
||||
}
|
||||
emit('success')
|
||||
}
|
||||
|
||||
const handleSuccess = () => {
|
||||
visible.value = false
|
||||
emit('success')
|
||||
}
|
||||
async function bootPageMode() {
|
||||
pageBooting.value = true
|
||||
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 = () => {
|
||||
dialogMode.value = 'add'
|
||||
editId.value = ''
|
||||
copyId.value = ''
|
||||
visible.value = true
|
||||
}
|
||||
watch(
|
||||
() =>
|
||||
[
|
||||
isPageVariant.value,
|
||||
props.pageMode,
|
||||
props.quoteId,
|
||||
props.copyFromId
|
||||
] as const,
|
||||
() => {
|
||||
if (isPageVariant.value) {
|
||||
void bootPageMode()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const openEdit = (id: string | number) => {
|
||||
dialogMode.value = 'edit'
|
||||
editId.value = id
|
||||
copyId.value = ''
|
||||
visible.value = true
|
||||
}
|
||||
onMounted(() => {
|
||||
if (isPageVariant.value) {
|
||||
void bootPageMode()
|
||||
}
|
||||
})
|
||||
|
||||
const openCopy = (id: string | number) => {
|
||||
dialogMode.value = 'copy'
|
||||
copyId.value = id
|
||||
editId.value = ''
|
||||
visible.value = true
|
||||
}
|
||||
const open = () => {
|
||||
dialogMode.value = 'add'
|
||||
editId.value = ''
|
||||
copyId.value = ''
|
||||
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>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.quote-form-dialog .el-dialog__body) {
|
||||
padding-top: 8px;
|
||||
}
|
||||
:deep(.quote-form-dialog .el-dialog__body) {
|
||||
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>
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="quote-form-panel">
|
||||
<div class="quote-form-panel" :class="{ 'quote-form-panel--page': isPageVariant }">
|
||||
<el-form
|
||||
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 quote-form-body"
|
||||
scroll-to-error
|
||||
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
|
||||
@@ -672,13 +672,18 @@ const props = withDefaults(
|
||||
defineProps<{
|
||||
hideFooter?: boolean
|
||||
autoInit?: boolean
|
||||
/** 小程序 WebView 全屏页:单列布局 */
|
||||
variant?: 'dialog' | 'page'
|
||||
}>(),
|
||||
{
|
||||
hideFooter: false,
|
||||
autoInit: true
|
||||
autoInit: true,
|
||||
variant: 'dialog'
|
||||
}
|
||||
)
|
||||
|
||||
const isPageVariant = computed(() => props.variant === 'page')
|
||||
|
||||
const emit = defineEmits<{
|
||||
success: []
|
||||
close: []
|
||||
@@ -2417,6 +2422,32 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<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 {
|
||||
:deep(.el-form-item__label) {
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -2,16 +2,114 @@
|
||||
<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"
|
||||
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>
|
||||
</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 QuoteFormDialog from '@/views/crm/quote/components/QuoteFormDialog.vue'
|
||||
import { MP_CRM_QUOTE_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 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>
|
||||
Reference in new issue
Block a user