This commit is contained in:
2026-09-09 16:38:00 +08:00
parent da027b4109
commit 4f6f45bae7
2 changed files with 197 additions and 21 deletions
@@ -119,3 +119,103 @@ const modal = {
}
}
Vue.prototype.$modal = modal
// H5 弹框共享一个 popstate 监听器;仅主动注册的页面会写入历史记录。
const mobilePopupHistory = (() => {
const marker = '__mobilePopupId'
const entries = []
let sequence = 0
let owners = 0
let pending = null
const session = Date.now().toString(36)
// 先移除栈项再同步界面,避免 v-model 的 watcher 再次触发历史回退。
const removeFrom = (index) => {
const removed = entries.splice(index)
removed.reverse().forEach(entry => entry.close())
}
const onPopState = (event) => {
const id = event.state && event.state[marker]
const index = entries.findIndex(entry => entry.id === id)
removeFrom(index + 1)
if (pending) {
const resolve = pending.resolve
pending = null
resolve()
}
}
const backFrom = (index) => {
if (index < 0) {
return Promise.resolve()
}
const count = entries.length - index
let resolveBack
const promise = new Promise(resolve => { resolveBack = resolve })
pending = {promise: promise, resolve: resolveBack}
removeFrom(index)
window.history.go(-count)
return promise
}
return {
/**
* 注册当前页面,无参数。返回 sync/close/clear/dispose 方法。
* sync(key, visible, close)key 为页面内唯一弹框名,visible 为显示状态,
* close 为无参关闭回调。close(key) 和 clear() 返回历史回退完成的 Promise,
* 页面跳转必须等待 clear() 完成;dispose() 用于销毁时注销回调和监听器。
*/
register() {
const owner = {}
let disposed = false
if (owners++ === 0) {
window.addEventListener('popstate', onPopState)
}
const handle = {
sync(key, visible, close) {
if (disposed) {
return
}
if (pending) {
pending.promise.then(() => handle.sync(key, visible, close))
return
}
const index = entries.findIndex(entry => entry.owner === owner && entry.key === key)
if (!visible) {
handle.close(key)
} else if (index < 0) {
const id = session + '-' + (++sequence)
const state = Object.assign({}, window.history.state)
state[marker] = id
window.history.pushState(state, '', window.location.href)
entries.push({id: id, key: key, owner: owner, close: close})
}
},
close(key) {
if (pending) {
return pending.promise.then(() => handle.close(key))
}
// 关闭下层弹框时一并关闭其上层,保持浏览器历史与可见层级一致。
return backFrom(entries.findIndex(entry => entry.owner === owner && entry.key === key))
},
clear() {
if (pending) {
return pending.promise.then(() => handle.clear())
}
return backFrom(entries.findIndex(entry => entry.owner === owner))
},
dispose() {
if (disposed) {
return
}
disposed = true
handle.clear().then(() => {
if (--owners === 0) {
window.removeEventListener('popstate', onPopState)
}
})
},
}
return handle
},
}
})()
@@ -113,6 +113,30 @@ layout("/mobile/platform.html"){
/*margin-top: 30px;*/
}
/* 标题不参与滚动,说明正文独占弹框剩余高度。 */
.checkup-desc-popup {
display: flex;
flex-direction: column;
overflow: hidden;
}
.checkup-desc-popup > .van-nav-bar {
flex-shrink: 0;
}
.checkup-desc-content {
flex: 1;
min-height: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
overscroll-behavior-y: contain;
}
.checkup-desc-content img {
max-width: 100%;
height: auto;
}
.van-dropdown-menu__bar {
display: flex;
@@ -183,7 +207,7 @@ layout("/mobile/platform.html"){
</div>
<div class="van-card-body">
<template v-if="!optionTypeList || optionTypeList.length == 0">
<van-field @click="campusVisible = true" readonly is-link label="院区" name="campus"
<van-field @click="openCampus" readonly is-link label="院区" name="campus"
placeholder="请选择体检院区" v-model="formData.campusName"
:rules="[{ required: true }]"></van-field>
</template>
@@ -260,21 +284,21 @@ layout("/mobile/platform.html"){
<van-picker
title="院区" show-toolbar
:columns="campusList" value-key="campusName"
@confirm="(value, index) => {formData.campusName = value.campusName; formData.campus = value.id; campusVisible = false}"
@cancel="campusVisible = false">
@confirm="confirmCampus"
@cancel="closeCampus">
</van-picker>
</van-popup>
<!--查看说明-->
<van-popup position="right" v-model="descVisible" :style="{ height: '100%',width: '100%'}">
<van-sticky :offset-top="0">
<van-nav-bar @click-left="descVisible=false" left-arrow
<van-popup class="checkup-desc-popup" position="right" v-model="descVisible" :style="{ height: '100%',width: '100%'}">
<van-nav-bar @click-left="closeDescription" left-arrow
title="详情"></van-nav-bar>
</van-sticky>
<div v-html="desc" class="pre_text" @click="descClick($event)"></div>
<div ref="descContent" class="checkup-desc-content">
<div v-html="desc" class="pre_text" @click="descClick($event)"></div>
</div>
</van-popup>
<van-dialog v-model="confirmShow" title="提示" show-cancel-button :before-close="onBeforeClose">
<van-dialog v-model="confirmShow" title="提示" show-cancel-button :close-on-popstate="false" :before-close="onBeforeClose">
<div class="van-dialog__message van-dialog__message--has-title">您确定要选择{{chooseOptionName}}吗?</div>
<van-radio-group v-model="formData.marry">
<van-cell-group>
@@ -322,10 +346,59 @@ layout("/mobile/platform.html"){
noticeText: '',
}
},
watch: {
descVisible(value) {
this.syncPopupHistory('descVisible', value)
},
campusVisible(value) {
this.syncPopupHistory('campusVisible', value)
},
confirmShow(value) {
this.syncPopupHistory('confirmShow', value)
},
},
methods: {
// visible 为弹框显示状态;关闭回调仅同步状态,历史回退由全局管理器负责。
syncPopupHistory(key, visible) {
this.popupHistory.sync(key, visible, () => {
this.$set(this, key, false)
})
},
closeDescription() {
this.popupHistory.close('descVisible')
},
openCampus() {
this.$set(this, 'campusVisible', true)
},
closeCampus() {
this.popupHistory.close('campusVisible')
},
// value 为院区选项,包含 campusName(显示名称)和 id(提交值)。
confirmCampus(value) {
this.$set(this.formData, 'campusName', value.campusName)
this.$set(this.formData, 'campus', value.id)
this.closeCampus()
},
// 条件校验提示也占一层历史;系统返回只关闭提示,不离开选择页面。
showConditionMessage(title, message) {
this.popupHistory.sync('conditionMessage', true, () => vant.Dialog.close())
vant.Dialog.alert({
title: title,
message: message,
confirmButtonColor: '#1867b0',
closeOnPopstate: false,
}).then(() => this.popupHistory.close('conditionMessage'))
.catch(() => this.popupHistory.close('conditionMessage'))
},
descClick(e) {
if (e.target.tagName === 'IMG') {
vant.ImagePreview([e.target.src])
// 图片预览叠加在详情之上,返回时先关闭预览,再关闭详情。
const preview = vant.ImagePreview({
images: [e.target.src],
closeOnPopstate: false,
onClose: () => this.popupHistory.close('descriptionImage'),
})
this.popupHistory.sync('descriptionImage', true, () => preview.close())
}
},
async doChangeHealthProjectSubject(item) {
@@ -333,11 +406,7 @@ layout("/mobile/platform.html"){
cndId: item.conditionStructureId,
})
if (res.code !== 0) {
vant.Dialog.alert({
title: '提示',
message: res.msg,
confirmButtonColor: '#1867b0'
})
this.showConditionMessage('提示', res.msg)
this.formData.subjectId = ''
} else {
this.$nextTick(() => {
@@ -355,6 +424,11 @@ layout("/mobile/platform.html"){
if (o.description) {
this.desc = o.description
this.descVisible = true
this.$nextTick(() => {
if (this.$refs.descContent) {
this.$refs.descContent.scrollTop = 0
}
})
} else {
vant.Toast('暂无详细说明')
}
@@ -404,7 +478,9 @@ layout("/mobile/platform.html"){
toast.clear()
}, 500)
if (resp.code === 0) {
location.href = '/mobile/healthCheckup/mine'
this.popupHistory.clear().then(() => {
location.href = '/mobile/healthCheckup/mine'
})
}
}, 1000)
},
@@ -418,11 +494,7 @@ layout("/mobile/platform.html"){
cndId: p.conditionStructureId,
})
if (res.code !== 0) {
vant.Dialog.alert({
title: '温馨提醒',
message: res.msg,
confirmButtonColor: '#1867b0'
})
this.showConditionMessage('温馨提醒', res.msg)
return
}
this.chooseOptionName = p.optionName
@@ -484,6 +556,7 @@ layout("/mobile/platform.html"){
},
},
async created() {
this.popupHistory = mobilePopupHistory.register()
this.id = GetQueryString('projectId')
this.selectId = GetQueryString('selectId')
await this.getCampus()
@@ -492,6 +565,9 @@ layout("/mobile/platform.html"){
await this.getSelectInfo()
}
},
beforeDestroy() {
this.popupHistory.dispose()
},
})
window.onload = () => {
window.addEventListener('pageshow', e => {