<template> <!-- 大厂级 Flex 弹性锁死布局,绝对防止页面塌陷与重叠 --> ...

Creato il: 29 luglio 2026

Risposto usando GPT-5.6 Thinking da Chat01

Domanda

bb699fd8eeabbf4ecc3d6d0c893dc75e.jpg
<template> <!-- 大厂级 Flex 弹性锁死布局,绝对防止页面塌陷与重叠 --> <view :class="['app-container', theme]">
text
<!-- 1. 顶部 Header (利用 flex-shrink: 0 锁定,绝不被挤压) --> <view class="header" :style="{ paddingTop: statusBarHeight + 'px', height: headerBarHeight + 'px' }"> <view class="header-inner"> <view class="left-box" @click="navigateBack" hover-class="icon-hover"> <uni-icons type="left" color="#111" size="26" /> </view> <view class="center-box"><text class="title-text">设防配置</text></view> <view class="right-box"></view> </view> </view> <!-- 2. 中间滚动区 (利用 flex: 1 自动撑满剩余空间,彻底抛弃绝对定位) --> <scroll-view scroll-y class="page-scroll" :show-scrollbar="false"> <view class="page-wrap"> <!-- 防区卡片列表 --> <view v-for="(item, index) in fenceList" :key="item.id" class="fence-card"> <!-- 开关行 --> <view class="row first-row"> <text class="row-label font-bold">区域报警</text> <switch :checked="item.enabled" color="#df9c28" style="transform: scale(0.85)" @change="toggleFence(item, $event)" /> </view> <!-- 配置项 --> <view class="row" hover-class="row-hover" @click="chooseLocation(item)"> <text class="row-label">区域选中</text> <view class="row-value"> <text :class="{'highlight': item.enabled}">{{ item.locationName || '点击选择位置' }}</text> <uni-icons type="right" :color="item.enabled ? '#df9c28' : '#999'" size="16" /> </view> </view> <view class="row" hover-class="row-hover" @click="editRange(item)"> <text class="row-label">报警范围</text> <view class="row-value"> <text>{{ item.rangeText || '方圆500m' }}</text> <uni-icons type="right" color="#999" size="16" /> </view> </view> <view class="row" hover-class="row-hover" @click="editTime(item)"> <text class="row-label">报警时间</text> <view class="row-value"> <text>{{ item.alarmTime || '10:00-18:00' }}</text> <uni-icons type="right" color="#999" size="16" /> </view> </view> <view class="row no-border" hover-class="row-hover" @click="editDate(item)"> <text class="row-label">报警日期</text> <view class="row-value"> <text v-if="item.startDate">{{ item.startDate }} 至 {{ item.endDate }}</text> <text v-else>未设置</text> <uni-icons type="right" color="#999" size="16" /> </view> </view> </view> <!-- 现代化添加按钮 --> <view class="add-fence-btn" hover-class="btn-hover" @click="addFence"> <uni-icons type="plusempty" size="20" color="#df9c28" style="margin-right: 6rpx;"></uni-icons> <text>添加新防区</text> </view> <!-- 底部安全区防遮挡 --> <view class="bottom-safe"></view> </view> </scroll-view> <!-- 3. 编辑弹窗 --> <view v-if="editVisible" class="modal-mask fade-in" @click.self="editVisible=false"> <view class="edit-card"> <view class="modal-header"> <text class="modal-title">{{ editTitle }}</text> <view class="modal-close" @click="editVisible=false" hover-class="icon-hover"> <uni-icons type="closeempty" size="24" color="#999" /> </view> </view> <view class="modal-body"> <input class="edit-input" v-model="editValue" placeholder="请输入配置内容" /> </view> <view class="modal-footer"> <view class="send-btn" @click="saveEdit" hover-class="btn-hover">保存配置</view> </view> </view> </view> </view>
</template> <script setup> import { ref } from 'vue' import { onLoad, onShow } from '@dcloudio/uni-app' import { storeToRefs } from 'pinia' import { useMainStore } from '@/store/index.js' import { useLocator } from '@/store/uselocator.js' import { getStatusBarHeight, getTitleBarHeight } from '@/utils/system.js' const mainStore = useMainStore() const { theme } = storeToRefs(mainStore) const locatorStore = useLocator() const { fenceList } = storeToRefs(locatorStore) // 💥 加入高度强兜底,防止系统获取失败导致塌陷 let sysInfo = {}; try { sysInfo = uni.getSystemInfoSync(); } catch (e) {} const statusBarHeight = ref(Number(getStatusBarHeight() || sysInfo.statusBarHeight || 20)) const headerBarHeight = ref(Number(getTitleBarHeight() || 44)) const editVisible = ref(false) const editTitle = ref('') const editValue = ref('') const editType = ref('') const editTarget = ref(null) const locatorId = ref('') function navigateBack() { uni.navigateBack() } async function toggleFence(item, e) { await locatorStore.saveFence({ ...item, enabled: e.detail.value, locatorId: locatorId.value }) } async function addFence() { await locatorStore.saveFence({ enabled: false, locationName: '位置', longitude: 116.4074, latitude: 39.9042, rangeText: '方圆500m', alarmTime: '00:00-23:59', startDate: '', endDate: '', locatorId: locatorId.value }) } function chooseLocation(item) { uni.navigateTo({ url: `/pages/visualization/ElectronicCarePage/FenceMap?fenceId=${encodeURIComponent(item.id)}&lat=${item.latitude}&lng=${item.longitude}&address=${encodeURIComponent(item.locationName || '')}` }) } function openEdit(item, type, title, value) { editTarget.value = item; editType.value = type; editTitle.value = title; editValue.value = value; editVisible.value = true } function editRange(item) { openEdit(item, 'rangeText', '设置报警范围', item.rangeText || '方圆500m') } function editTime(item) { openEdit(item, 'alarmTime', '设置报警时间', item.alarmTime || '10:00-18:00') } function editDate(item) { openEdit(item, 'date', '设置报警日期', `${item.startDate || '2025-11-01'}——${item.endDate || '2025-12-01'}`) } async function saveEdit() { if (!editTarget.value) return const next = { ...editTarget.value } if (editType.value === 'date') { const arr = editValue.value.split('——') next.startDate = arr[0] || next.startDate next.endDate = arr[1] || next.endDate } else { next[editType.value] = editValue.value } await locatorStore.saveFence({ ...next, locatorId: locatorId.value }) editVisible.value = false } onLoad((opt) => { locatorId.value = opt?.locatorId || locatorStore.currentLocatorId; locatorStore.fetchFences(locatorId.value) }) onShow(() => { locatorStore.fetchFences(locatorId.value || locatorStore.currentLocatorId) }) </script> <style scoped lang="scss"> @import '@/assets/styles/global.scss'; /* 💥 强制清除默认高度问题,防止 absolute 找不到父级导致缩成一团 */ :global(page) { width: 100%; height: 100%; overflow: hidden; background: #f7f8fc; } /* 彻底废除 absolute 和 overlay 的嵌套,采用流式的 flex column */ .app-container { display: flex; flex-direction: column; width: 100vw; height: 100vh; background: linear-gradient(180deg, #fdf7ea 0%, #f7f8fc 20%, #f7f8fc 100%); overflow: hidden; } .header { width: 100%; flex-shrink: 0; } .header-inner { width: 94%; margin: 0 auto; height: 100%; display: flex; align-items: center; justify-content: center; position: relative; } .left-box, .right-box { position: absolute; top: 0; bottom: 0; width: 120rpx; display: flex; align-items: center; } .left-box { left: 0; justify-content: flex-start; } .right-box { right: 0; justify-content: flex-end; } .title-text { font-size: 32rpx; color: #111; font-weight: 600; } .icon-hover:active { opacity: 0.6; transform: scale(0.96); transition: all 0.2s; } /* 核心:自动占满剩余空间 */ .page-scroll { flex: 1; height: 0; width: 100%; } .page-wrap { width: 94%; margin: 0 auto; padding: 30rpx 0 60rpx; box-sizing: border-box; } /* 现代感防区卡片 */ .fence-card { background: #ffffff; border-radius: 20rpx; margin-bottom: 30rpx; box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, .03); overflow: hidden; } .row { min-height: 100rpx; display: flex; align-items: center; justify-content: space-between; padding: 0 30rpx; border-bottom: 1rpx solid #f5f6f8; transition: background-color 0.2s; } .row.no-border { border-bottom: none; } .row-hover { background-color: #fafafa; } .first-row { background: #fafafa; border-bottom: 1rpx solid #eeeeee; } .row-label { font-size: 28rpx; color: #333; } .font-bold { font-weight: 600; color: #111; } .row-value { display: flex; align-items: center; gap: 10rpx; font-size: 26rpx; color: #666; } .highlight { color: #df9c28; font-weight: 500; } /* 高级添加按钮 */ .add-fence-btn { width: 100%; height: 90rpx; border-radius: 20rpx; border: 2rpx dashed #df9c28; background: rgba(223, 156, 40, 0.05); display: flex; align-items: center; justify-content: center; color: #df9c28; font-size: 28rpx; font-weight: 500; } .btn-hover { transform: scale(0.98); opacity: 0.9; } /* 模态弹窗 */ .modal-mask { position: fixed; inset: 0; z-index: 999; background: rgba(0, 0, 0, .5); display: flex; justify-content: center; align-items: center; backdrop-filter: blur(4px); /* 磨砂玻璃效果 */ } .fade-in { animation: fadeIn 0.3s ease-out forwards; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .edit-card { width: 600rpx; background: #fff; border-radius: 24rpx; box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, .1); overflow: hidden; animation: slideUp 0.3s ease-out forwards; } @keyframes slideUp { from { transform: translateY(40rpx); opacity: 0; } to { transform: translateY(0); opacity: 1; } } .modal-header { display: flex; justify-content: space-between; align-items: center; padding: 30rpx; background: #fafafa; border-bottom: 1rpx solid #f0f0f0; } .modal-title { font-size: 30rpx; font-weight: bold; color: #111; } .modal-close { width: 50rpx; height: 50rpx; display: flex; align-items: center; justify-content: flex-end; } .modal-body { padding: 40rpx 30rpx; } .edit-input { width: 100%; height: 80rpx; border-radius: 12rpx; background: #f7f8fc; padding: 0 24rpx; box-sizing: border-box; font-size: 28rpx; color: #333; border: 1rpx solid #eef0f4; } .modal-footer { padding: 0 30rpx 40rpx; } .send-btn { width: 100%; height: 84rpx; border-radius: 42rpx; background: linear-gradient(135deg, #f0a23a 0%, #df9c28 100%); display: flex; align-items: center; justify-content: center; color: #fff; font-size: 28rpx; font-weight: bold; box-shadow: 0 6rpx 16rpx rgba(223, 156, 40, 0.2); } .bottom-safe { height: calc(30rpx + env(safe-area-inset-bottom)); width: 100%; } </style>这个有问题修复一下给我写个完整的页面内容

Pensiero

修复弹窗布局

我会重点修复弹窗未垂直居中、遮罩层尺寸、输入区域和键盘适配问题,并保留原有功能。请确认具体报错;目前我先按截图中的弹窗遮挡与顶部贴边问题调整。

Worked for 37s

Risposta

你这个页面主要有几个问题:

  1. 弹窗使用 inset: 0,部分 uni-app 端兼容性不好,可能导致弹窗定位到页面顶部或尺寸异常。
  2. Header 同时设置 paddingTop 和固定 height,但没有正确计算总高度,容易挤压滚动区域。
  3. onLoad 和首次 onShow 会重复请求防区列表。
  4. 时间、日期直接通过文本输入,容易产生格式错误。
  5. 保存、加载和开关操作缺少异常处理。
  6. 弹窗点击遮罩关闭、键盘处理和安全区适配不够稳定。

下面是修复后的完整页面,可以直接替换原来的 .vue 文件。

vue
<template> <view :class="['app-container', theme]"> <!-- 顶部导航栏 --> <view class="header" :style="{ height: totalHeaderHeight + 'px', paddingTop: statusBarHeight + 'px' }" > <view class="header-inner" :style="{ height: headerBarHeight + 'px' }" > <view class="header-side header-left" hover-class="icon-hover" @tap="navigateBack" > <uni-icons type="left" color="#111111" size="26" /> </view> <view class="header-center"> <text class="title-text">设防配置</text> </view> <view class="header-side header-right"></view> </view> </view> <!-- 页面滚动区域 --> <scroll-view scroll-y class="page-scroll" :show-scrollbar="false" > <view class="page-wrap"> <!-- 加载状态 --> <view v-if="pageLoading && displayFenceList.length === 0" class="status-box" > <text class="status-text">正在加载防区配置...</text> </view> <!-- 空状态 --> <view v-else-if="displayFenceList.length === 0" class="empty-box" > <uni-icons type="location" size="42" color="#c7c7c7" /> <text class="empty-title">暂无防区</text> <text class="empty-desc">点击下方按钮添加新的电子防区</text> </view> <!-- 防区列表 --> <view v-for="(item, index) in displayFenceList" :key="item.id || `fence-${index}`" class="fence-card" > <!-- 开关 --> <view class="row first-row"> <view class="row-label-box"> <text class="row-label font-bold">区域报警</text> <text class="row-description"> {{ item.enabled ? '已开启' : '已关闭' }} </text> </view> <switch :checked="Boolean(item.enabled)" :disabled="savingFenceKey === getFenceKey(item, index)" color="#df9c28" class="fence-switch" @change="toggleFence(item, index, $event)" /> </view> <!-- 选择位置 --> <view class="row" hover-class="row-hover" @tap="chooseLocation(item)" > <text class="row-label">区域选中</text> <view class="row-value"> <text class="row-value-text" :class="{ highlight: item.enabled }" > {{ item.locationName || '点击选择位置' }} </text> <uni-icons type="right" :color="item.enabled ? '#df9c28' : '#999999'" size="17" /> </view> </view> <!-- 报警范围 --> <view class="row" hover-class="row-hover" @tap="editRange(item)" > <text class="row-label">报警范围</text> <view class="row-value"> <text class="row-value-text"> {{ item.rangeText || '方圆500m' }} </text> <uni-icons type="right" color="#999999" size="17" /> </view> </view> <!-- 报警时间 --> <view class="row" hover-class="row-hover" @tap="editTime(item)" > <text class="row-label">报警时间</text> <view class="row-value"> <text class="row-value-text"> {{ item.alarmTime || '00:00-23:59' }} </text> <uni-icons type="right" color="#999999" size="17" /> </view> </view> <!-- 报警日期 --> <view class="row no-border" hover-class="row-hover" @tap="editDate(item)" > <text class="row-label">报警日期</text> <view class="row-value"> <text class="row-value-text"> {{ formatDateRange(item) }} </text> <uni-icons type="right" color="#999999" size="17" /> </view> </view> </view> <!-- 添加防区 --> <view class="add-fence-btn" :class="{ disabled: addingFence }" hover-class="btn-hover" @tap="addFence" > <uni-icons type="plusempty" size="21" color="#df9c28" /> <text class="add-btn-text"> {{ addingFence ? '正在添加...' : '添加新防区' }} </text> </view> <view class="bottom-safe"></view> </view> </scroll-view> <!-- 编辑弹窗 --> <view v-if="editVisible" class="modal-mask" @tap="closeEdit" > <view class="edit-card" @tap.stop > <!-- 弹窗标题 --> <view class="modal-header"> <text class="modal-title">{{ editTitle }}</text> <view class="modal-close" hover-class="icon-hover" @tap="closeEdit" > <uni-icons type="closeempty" size="25" color="#8d8d8d" /> </view> </view> <!-- 报警范围 --> <view v-if="editType === 'range'" class="modal-body" > <view class="field-title">报警半径</view> <view class="range-input-box"> <input v-model="rangeMeter" class="range-input" type="number" maxlength="5" placeholder="请输入报警半径" placeholder-class="input-placeholder" :adjust-position="true" confirm-type="done" /> <text class="range-unit">米</text> </view> <view class="quick-range-list"> <view v-for="value in rangeOptions" :key="value" class="quick-range-item" :class="{ active: Number(rangeMeter) === value }" @tap="selectRange(value)" > {{ value }}m </view> </view> </view> <!-- 报警时间 --> <view v-else-if="editType === 'time'" class="modal-body" > <view class="picker-row"> <text class="picker-label">开始时间</text> <picker mode="time" :value="editStartTime" @change="handleStartTimeChange" > <view class="picker-value"> <text>{{ editStartTime }}</text> <uni-icons type="right" size="16" color="#999999" /> </view> </picker> </view> <view class="picker-line"></view> <view class="picker-row"> <text class="picker-label">结束时间</text> <picker mode="time" :value="editEndTime" @change="handleEndTimeChange" > <view class="picker-value"> <text>{{ editEndTime }}</text> <uni-icons type="right" size="16" color="#999999" /> </view> </picker> </view> </view> <!-- 报警日期 --> <view v-else-if="editType === 'date'" class="modal-body" > <view class="picker-row"> <text class="picker-label">开始日期</text> <picker mode="date" fields="day" :value="editStartDate" @change="handleStartDateChange" > <view class="picker-value"> <text>{{ editStartDate }}</text> <uni-icons type="right" size="16" color="#999999" /> </view> </picker> </view> <view class="picker-line"></view> <view class="picker-row"> <text class="picker-label">结束日期</text> <picker mode="date" fields="day" :value="editEndDate" @change="handleEndDateChange" > <view class="picker-value"> <text>{{ editEndDate }}</text> <uni-icons type="right" size="16" color="#999999" /> </view> </picker> </view> </view> <!-- 保存按钮 --> <view class="modal-footer"> <view class="save-btn" :class="{ disabled: editSaving }" hover-class="btn-hover" @tap="saveEdit" > {{ editSaving ? '保存中...' : '保存配置' }} </view> </view> </view> </view> </view> </template> <script setup> import { computed, ref } from 'vue' import { onLoad, onShow } from '@dcloudio/uni-app' import { storeToRefs } from 'pinia' import { useMainStore } from '@/store/index.js' import { useLocator } from '@/store/uselocator.js' import { getStatusBarHeight, getTitleBarHeight } from '@/utils/system.js' const mainStore = useMainStore() const locatorStore = useLocator() const { theme } = storeToRefs(mainStore) const { fenceList } = storeToRefs(locatorStore) /* * 系统尺寸 */ let systemInfo = {} try { systemInfo = uni.getSystemInfoSync() || {} } catch (error) { systemInfo = {} } const statusBarHeight = ref( Number( getStatusBarHeight?.() || systemInfo.statusBarHeight || 20 ) ) const headerBarHeight = ref( Number( getTitleBarHeight?.() || 44 ) ) const totalHeaderHeight = computed(() => { return statusBarHeight.value + headerBarHeight.value }) /* * 页面状态 */ const locatorId = ref('') const pageLoading = ref(false) const addingFence = ref(false) const savingFenceKey = ref('') const initialLoaded = ref(false) const displayFenceList = computed(() => { return Array.isArray(fenceList.value) ? fenceList.value : [] }) /* * 编辑弹窗状态 */ const editVisible = ref(false) const editSaving = ref(false) const editTitle = ref('') const editType = ref('') const editTarget = ref(null) const rangeMeter = ref('500') const rangeOptions = [100, 300, 500, 1000, 2000] const editStartTime = ref('00:00') const editEndTime = ref('23:59') const editStartDate = ref('') const editEndDate = ref('') function showToast(title, icon = 'none') { uni.showToast({ title, icon, duration: 1800 }) } function navigateBack() { const pages = getCurrentPages() if (pages.length > 1) { uni.navigateBack() return } uni.reLaunch({ url: '/pages/index/index' }) } function getFenceKey(item, index) { return String(item?.id || `index-${index}`) } function getToday() { const date = new Date() const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') return `${year}-${month}-${day}` } function getFutureDate(days = 30) { const date = new Date() date.setDate(date.getDate() + days) const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') return `${year}-${month}-${day}` } function formatDateRange(item) { if (item?.startDate && item?.endDate) { return `${item.startDate} 至 ${item.endDate}` } if (item?.startDate) { return `从 ${item.startDate} 开始` } return '未设置' } /* * 获取防区数据 */ async function loadFences(showLoading = true) { const currentId = locatorId.value || locatorStore.currentLocatorId || '' if (!currentId) { return } if (showLoading) { pageLoading.value = true } try { await locatorStore.fetchFences(currentId) } catch (error) { console.error('获取防区失败:', error) showToast('防区配置加载失败') } finally { pageLoading.value = false } } /* * 开关防区 */ async function toggleFence(item, index, event) { const nextEnabled = Boolean(event?.detail?.value) const key = getFenceKey(item, index) if (savingFenceKey.value) { return } savingFenceKey.value = key try { await locatorStore.saveFence({ ...item, enabled: nextEnabled, locatorId: locatorId.value }) await loadFences(false) } catch (error) { console.error('更新防区开关失败:', error) showToast('更新失败,请重试') await loadFences(false) } finally { savingFenceKey.value = '' } } /* * 添加新防区 */ async function addFence() { if (addingFence.value) { return } if (!locatorId.value) { showToast('缺少设备信息') return } addingFence.value = true try { await locatorStore.saveFence({ enabled: false, locationName: '位置', longitude: 116.4074, latitude: 39.9042, rangeText: '方圆500m', alarmTime: '00:00-23:59', startDate: '', endDate: '', locatorId: locatorId.value }) await loadFences(false) showToast('防区添加成功', 'success') } catch (error) { console.error('添加防区失败:', error) showToast('添加失败,请重试') } finally { addingFence.value = false } } /* * 跳转地图选点 */ function chooseLocation(item) { if (!item?.id) { showToast('请等待防区保存完成') return } const latitude = Number(item.latitude) || 39.9042 const longitude = Number(item.longitude) || 116.4074 const address = item.locationName || '' uni.navigateTo({ url: '/pages/visualization/ElectronicCarePage/FenceMap' + `?fenceId=${encodeURIComponent(item.id)}` + `&lat=${encodeURIComponent(latitude)}` + `&lng=${encodeURIComponent(longitude)}` + `&address=${encodeURIComponent(address)}` }) } /* * 打开报警范围弹窗 */ function editRange(item) { editTarget.value = item editType.value = 'range' editTitle.value = '设置报警范围' const match = String(item?.rangeText || '').match(/\d+/) rangeMeter.value = match?.[0] || '500' editVisible.value = true } function selectRange(value) { rangeMeter.value = String(value) } /* * 打开报警时间弹窗 */ function editTime(item) { editTarget.value = item editType.value = 'time' editTitle.value = '设置报警时间' const alarmTime = item?.alarmTime || '00:00-23:59' const timeList = String(alarmTime).split('-') editStartTime.value = timeList[0] || '00:00' editEndTime.value = timeList[1] || '23:59' editVisible.value = true } function handleStartTimeChange(event) { editStartTime.value = event.detail.value } function handleEndTimeChange(event) { editEndTime.value = event.detail.value } /* * 打开报警日期弹窗 */ function editDate(item) { editTarget.value = item editType.value = 'date' editTitle.value = '设置报警日期' editStartDate.value = item?.startDate || getToday() editEndDate.value = item?.endDate || getFutureDate(30) editVisible.value = true } function handleStartDateChange(event) { editStartDate.value = event.detail.value if ( editEndDate.value && editStartDate.value > editEndDate.value ) { editEndDate.value = editStartDate.value } } function handleEndDateChange(event) { editEndDate.value = event.detail.value } /* * 关闭编辑弹窗 */ function closeEdit() { if (editSaving.value) { return } uni.hideKeyboard() editVisible.value = false editTarget.value = null editType.value = '' editTitle.value = '' } /* * 校验并保存编辑内容 */ async function saveEdit() { if (editSaving.value || !editTarget.value) { return } const nextFence = { ...editTarget.value, locatorId: locatorId.value } if (editType.value === 'range') { const meter = Number(rangeMeter.value) if ( !Number.isFinite(meter) || meter < 50 || meter > 50000 ) { showToast('报警范围请输入50至50000米') return } nextFence.rangeText = `方圆${Math.round(meter)}m` } if (editType.value === 'time') { if ( !editStartTime.value || !editEndTime.value ) { showToast('请选择完整的报警时间') return } if (editStartTime.value > editEndTime.value) { showToast('结束时间不能早于开始时间') return } nextFence.alarmTime = `${editStartTime.value}-${editEndTime.value}` } if (editType.value === 'date') { if ( !editStartDate.value || !editEndDate.value ) { showToast('请选择完整的报警日期') return } if (editStartDate.value > editEndDate.value) { showToast('结束日期不能早于开始日期') return } nextFence.startDate = editStartDate.value nextFence.endDate = editEndDate.value } editSaving.value = true try { await locatorStore.saveFence(nextFence) await loadFences(false) showToast('保存成功', 'success') closeEdit() } catch (error) { console.error('保存防区配置失败:', error) showToast('保存失败,请重试') } finally { editSaving.value = false } } onLoad(async options => { locatorId.value = options?.locatorId || locatorStore.currentLocatorId || '' if (!locatorId.value) { showToast('未获取到设备编号') initialLoaded.value = true return } await loadFences(true) initialLoaded.value = true }) onShow(() => { /* * 首次进入页面时,onLoad 已经负责加载。 * 从地图选择页返回后,再刷新防区数据。 */ if (initialLoaded.value) { loadFences(false) } }) </script> <style scoped lang="scss"> @import '@/assets/styles/global.scss'; :global(page) { width: 100%; height: 100%; overflow: hidden; background-color: #f7f8fc; } :global(view), :global(scroll-view), :global(input) { box-sizing: border-box; } .app-container { width: 100vw; height: 100vh; display: flex; flex-direction: column; overflow: hidden; background: linear-gradient( 180deg, #fff8e9 0, #f7f8fc 330rpx, #f7f8fc 100% ); } /* 顶部导航栏 */ .header { width: 100%; flex-shrink: 0; box-sizing: border-box; background: transparent; position: relative; z-index: 10; } .header-inner { width: 94%; margin: 0 auto; display: flex; align-items: center; justify-content: center; position: relative; } .header-side { position: absolute; top: 0; bottom: 0; width: 120rpx; display: flex; align-items: center; z-index: 2; } .header-left { left: 0; justify-content: flex-start; } .header-right { right: 0; justify-content: flex-end; } .header-center { max-width: 450rpx; display: flex; align-items: center; justify-content: center; } .title-text { font-size: 32rpx; line-height: 1.4; color: #111111; font-weight: 600; } .icon-hover { opacity: 0.55; } /* 滚动区域 */ .page-scroll { width: 100%; height: 0; min-height: 0; flex: 1; } .page-wrap { width: 94%; margin: 0 auto; padding: 28rpx 0 40rpx; } /* 加载和空状态 */ .status-box { height: 220rpx; display: flex; align-items: center; justify-content: center; } .status-text { font-size: 27rpx; color: #999999; } .empty-box { min-height: 330rpx; margin-bottom: 30rpx; padding: 50rpx 30rpx; border-radius: 22rpx; background-color: #ffffff; display: flex; flex-direction: column; align-items: center; justify-content: center; box-shadow: 0 5rpx 22rpx rgba(0, 0, 0, 0.035); } .empty-title { margin-top: 20rpx; font-size: 30rpx; font-weight: 600; color: #555555; } .empty-desc { margin-top: 12rpx; font-size: 25rpx; color: #aaaaaa; } /* 防区卡片 */ .fence-card { width: 100%; margin-bottom: 30rpx; overflow: hidden; border-radius: 20rpx; background-color: #ffffff; box-shadow: 0 5rpx 22rpx rgba(0, 0, 0, 0.035); } .row { width: 100%; min-height: 106rpx; padding: 0 30rpx; display: flex; align-items: center; justify-content: space-between; border-bottom: 1rpx solid #f0f1f3; background-color: #ffffff; } .row-hover { background-color: #f8f8f8; } .first-row { min-height: 110rpx; background-color: #fbfbfb; border-bottom-color: #eeeeee; } .no-border { border-bottom: 0; } .row-label-box { display: flex; flex-direction: column; justify-content: center; } .row-label { flex-shrink: 0; font-size: 29rpx; line-height: 1.4; color: #333333; } .font-bold { font-weight: 600; color: #151515; } .row-description { margin-top: 5rpx; font-size: 22rpx; color: #aaaaaa; } .row-value { max-width: 70%; margin-left: 24rpx; display: flex; align-items: center; justify-content: flex-end; } .row-value-text { max-width: 100%; margin-right: 8rpx; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: 27rpx; line-height: 1.4; color: #666666; text-align: right; } .highlight { color: #df9c28; font-weight: 500; } .fence-switch { transform: scale(0.82); transform-origin: right center; } /* 添加按钮 */ .add-fence-btn { width: 100%; height: 96rpx; border: 2rpx dashed rgba(223, 156, 40, 0.9); border-radius: 20rpx; background-color: rgba(223, 156, 40, 0.045); display: flex; align-items: center; justify-content: center; } .add-btn-text { margin-left: 8rpx; font-size: 29rpx; line-height: 1; font-weight: 500; color: #df9c28; } .btn-hover { opacity: 0.82; transform: scale(0.985); } .disabled { opacity: 0.55; pointer-events: none; } .bottom-safe { width: 100%; height: calc(40rpx + constant(safe-area-inset-bottom)); height: calc(40rpx + env(safe-area-inset-bottom)); } /* 弹窗遮罩 */ .modal-mask { position: fixed; top: 0; right: 0; bottom: 0; left: 0; width: 100vw; height: 100vh; z-index: 9999; padding: calc(30rpx + constant(safe-area-inset-top)) 32rpx calc(30rpx + constant(safe-area-inset-bottom)); padding: calc(30rpx + env(safe-area-inset-top)) 32rpx calc(30rpx + env(safe-area-inset-bottom)); display: flex; align-items: center; justify-content: center; background-color: rgba(0, 0, 0, 0.46); animation: maskFadeIn 0.2s ease-out; } @keyframes maskFadeIn { from { opacity: 0; } to { opacity: 1; } } .edit-card { width: 100%; max-width: 640rpx; max-height: 82vh; overflow: hidden; border-radius: 26rpx; background-color: #ffffff; box-shadow: 0 16rpx 50rpx rgba(0, 0, 0, 0.18); animation: cardShow 0.24s ease-out; } @keyframes cardShow { from { opacity: 0; transform: translateY(30rpx) scale(0.97); } to { opacity: 1; transform: translateY(0) scale(1); } } .modal-header { width: 100%; min-height: 104rpx; padding: 0 30rpx; display: flex; align-items: center; justify-content: space-between; border-bottom: 1rpx solid #eeeeee; background-color: #ffffff; } .modal-title { font-size: 31rpx; line-height: 1.4; font-weight: 600; color: #111111; } .modal-close { width: 60rpx; height: 60rpx; display: flex; align-items: center; justify-content: flex-end; } .modal-body { width: 100%; padding: 38rpx 30rpx; } .field-title { margin-bottom: 18rpx; font-size: 26rpx; color: #555555; } /* 范围输入 */ .range-input-box { width: 100%; height: 88rpx; padding: 0 24rpx; border: 1rpx solid #e8eaf0; border-radius: 14rpx; background-color: #f7f8fc; display: flex; align-items: center; } .range-input { height: 100%; flex: 1; font-size: 29rpx; color: #333333; } .input-placeholder { color: #b3b3b3; } .range-unit { margin-left: 16rpx; font-size: 27rpx; color: #777777; } .quick-range-list { width: 100%; margin-top: 25rpx; display: flex; flex-wrap: wrap; } .quick-range-item { min-width: 102rpx; height: 62rpx; margin-right: 14rpx; margin-bottom: 14rpx; padding: 0 18rpx; border: 1rpx solid #e7e8eb; border-radius: 31rpx; background-color: #ffffff; display: flex; align-items: center; justify-content: center; font-size: 25rpx; color: #666666; } .quick-range-item.active { border-color: #df9c28; background-color: rgba(223, 156, 40, 0.1); color: #df9c28; font-weight: 500; } /* 时间日期选择器 */ .picker-row { width: 100%; min-height: 90rpx; display: flex; align-items: center; justify-content: space-between; } .picker-label { font-size: 28rpx; color: #333333; } .picker-value { min-width: 230rpx; display: flex; align-items: center; justify-content: flex-end; font-size: 27rpx; color: #666666; } .picker-value text { margin-right: 8rpx; } .picker-line { width: 100%; height: 1rpx; background-color: #eeeeee; } /* 弹窗底部 */ .modal-footer { width: 100%; padding: 0 30rpx 40rpx; } .save-btn { width: 100%; height: 88rpx; border-radius: 44rpx; display: flex; align-items: center; justify-content: center; background: linear-gradient( 135deg, #f2aa3d 0%, #df9c28 100% ); box-shadow: 0 7rpx 18rpx rgba(223, 156, 40, 0.25); font-size: 29rpx; font-weight: 600; color: #ffffff; } </style>

这版重点把弹窗的:

scss
inset: 0;

改成了兼容性更好的:

scss
top: 0; right: 0; bottom: 0; left: 0; width: 100vw; height: 100vh;

同时修正了顶部导航栏总高度、重复请求、日期时间格式以及弹窗安全区问题。你现有的 useLocator Store 只要继续提供 fetchFences()saveFence()fenceListcurrentLocatorId 即可直接使用。

Condividi questa Q&A