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>
|
<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;
|
||||||
|
|||||||
@@ -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>
|
||||||
Reference in new issue
Block a user