242 lines
6.4 KiB
Markdown
242 lines
6.4 KiB
Markdown
# PopupDatePicker Z-Index Fix - Portal Solution
|
||
|
||
## 问题描述
|
||
|
||
在uni-app-x中,使用CSS层叠上下文(z-index)实现的弹出层组件存在显示问题:
|
||
|
||
1. **Z-Index失效**: 即使设置了极高的z-index值(2147483647),弹出层仍然无法显示在其他元素之上
|
||
2. **平台限制**: uni-app-x的渲染机制对CSS z-index的支持存在限制
|
||
3. **用户体验差**: 弹出层被遮挡导致用户无法正常操作
|
||
|
||
## 解决方案
|
||
|
||
### 1. Portal方式(推荐)
|
||
|
||
创建基于页面导航的Portal组件,通过打开新页面的方式实现日期选择,完全避免z-index问题。
|
||
|
||
#### 核心文件
|
||
|
||
- `popup-date-picker-portal.uvue` - Portal触发器组件
|
||
- `pages/common/date-picker-modal.uvue` - 专用的日期选择页面
|
||
|
||
#### 工作原理
|
||
|
||
1. 用户点击日期触发器
|
||
2. 使用 `uni.navigateTo()` 打开专用的日期选择页面
|
||
3. 在新页面中完成日期选择
|
||
4. 通过页面间通信(EventChannel)传递选择结果
|
||
5. 自动关闭选择页面并返回
|
||
|
||
#### 优势
|
||
|
||
- ✅ **完全避免z-index问题**:新页面具有独立的层叠上下文
|
||
- ✅ **用户体验优秀**:全屏显示,操作便捷
|
||
- ✅ **平台兼容性好**:利用uni-app原生页面导航
|
||
- ✅ **可扩展性强**:可以添加更多功能如快捷选择
|
||
|
||
### 2. 原有方式(存在问题)
|
||
|
||
基于CSS z-index的弹出层实现,在uni-app-x中存在显示问题。
|
||
|
||
#### 问题分析
|
||
|
||
```css
|
||
.popup-overlay {
|
||
position: fixed !important;
|
||
z-index: 2147483647 !important; /* 最大32位整数 */
|
||
top: 0 !important;
|
||
left: 0 !important;
|
||
width: 100vw !important;
|
||
height: 100vh !important;
|
||
/* 即使如此设置,仍然无法在uni-app-x中正常显示 */
|
||
}
|
||
```
|
||
|
||
## 实现细节
|
||
|
||
### Portal触发器组件
|
||
|
||
```vue
|
||
<!-- popup-date-picker-portal.uvue -->
|
||
<template>
|
||
<view class="popup-date-picker-portal">
|
||
<view class="date-trigger" @click="openDatePicker">
|
||
<text class="date-text">{{ displayText }}</text>
|
||
<simple-icon type="calendar" :size="16" />
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script lang="uts">
|
||
export default {
|
||
methods: {
|
||
openDatePicker() {
|
||
uni.navigateTo({
|
||
url: `/pages/common/date-picker-modal?currentDate=${this.currentValue}&title=${encodeURIComponent(this.title)}`,
|
||
events: {
|
||
dateSelected: (data: any) => {
|
||
this.currentValue = data.date
|
||
this.$emit('change', this.currentValue)
|
||
this.$emit('update:value', this.currentValue)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
### 日期选择页面
|
||
|
||
```vue
|
||
<!-- pages/common/date-picker-modal.uvue -->
|
||
<template>
|
||
<view class="date-picker-modal">
|
||
<view class="modal-header">
|
||
<button @click="cancel">取消</button>
|
||
<text>{{ title }}</text>
|
||
<button @click="confirm">确定</button>
|
||
</view>
|
||
|
||
<view class="modal-content">
|
||
<picker-date
|
||
:value="selectedDate"
|
||
@change="onDateChange"
|
||
/>
|
||
<!-- 快捷选择按钮 -->
|
||
<view class="quick-select">
|
||
<button @click="selectToday">今天</button>
|
||
<button @click="selectYesterday">昨天</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script lang="uts">
|
||
export default {
|
||
methods: {
|
||
confirm() {
|
||
const eventChannel = this.getOpenerEventChannel()
|
||
eventChannel.emit('dateSelected', {
|
||
date: this.selectedDate
|
||
})
|
||
uni.navigateBack()
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
### 页面配置
|
||
|
||
```json
|
||
// pages.json
|
||
{
|
||
"path": "pages/common/date-picker-modal",
|
||
"style": {
|
||
"navigationBarTitleText": "",
|
||
"navigationStyle": "custom",
|
||
"app-plus": {
|
||
"titleNView": false,
|
||
"pullToRefresh": false
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 使用方法
|
||
|
||
### 1. 引入Portal组件
|
||
|
||
```vue
|
||
<template>
|
||
<popup-date-picker-portal
|
||
v-model:value="selectedDate"
|
||
placeholder="选择日期"
|
||
title="选择训练日期"
|
||
theme="dark"
|
||
@change="onDateChange"
|
||
/>
|
||
</template>
|
||
|
||
<script lang="uts">
|
||
import PopupDatePickerPortal from '@/components/popup-date-picker/popup-date-picker-portal.uvue'
|
||
|
||
export default {
|
||
components: {
|
||
PopupDatePickerPortal
|
||
},
|
||
data() {
|
||
return {
|
||
selectedDate: ''
|
||
}
|
||
},
|
||
methods: {
|
||
onDateChange(date: string) {
|
||
console.log('选择的日期:', date)
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
### 2. 替换原有组件
|
||
|
||
将现有的 `popup-date-picker` 替换为 `popup-date-picker-portal`:
|
||
|
||
```vue
|
||
<!-- 旧方式 -->
|
||
<popup-date-picker
|
||
v-model:value="startDate"
|
||
placeholder="开始日期"
|
||
@change="onDateChange"
|
||
/>
|
||
|
||
<!-- 新方式 -->
|
||
<popup-date-picker-portal
|
||
v-model:value="startDate"
|
||
placeholder="开始日期"
|
||
@change="onDateChange"
|
||
/>
|
||
```
|
||
|
||
## 已更新的文件
|
||
|
||
### 组件文件
|
||
- ✅ `components/popup-date-picker/popup-date-picker-portal.uvue` - Portal触发器
|
||
- ✅ `pages/common/date-picker-modal.uvue` - 日期选择页面
|
||
|
||
### 页面文件
|
||
- ✅ `pages/sport/student/records.uvue` - 学生训练记录页面
|
||
- ✅ `pages/sport/teacher/records.uvue` - 教师训练记录页面
|
||
|
||
### 配置文件
|
||
- ✅ `pages.json` - 添加了日期选择页面配置
|
||
|
||
### 测试文件
|
||
- ✅ `pages/test/portal-vs-original-test.uvue` - Portal vs 原始方式对比测试
|
||
|
||
## 测试验证
|
||
|
||
1. **功能测试**: 点击日期选择器能正常打开选择页面
|
||
2. **数据传递**: 选择的日期能正确传递回父组件
|
||
3. **用户体验**: 页面跳转流畅,选择界面友好
|
||
4. **兼容性**: 在不同设备和平台上都能正常工作
|
||
|
||
## 性能优化
|
||
|
||
1. **页面预加载**: 可以考虑预加载日期选择页面
|
||
2. **缓存机制**: 缓存最近选择的日期
|
||
3. **动画效果**: 添加页面转场动画提升体验
|
||
|
||
## 后续计划
|
||
|
||
1. **时间选择器**: 基于相同原理实现时间选择Portal
|
||
2. **其他选择器**: 扩展到其他需要弹出层的组件
|
||
3. **通用Portal**: 开发通用的Portal组件框架
|
||
|
||
## 总结
|
||
|
||
Portal方式通过页面导航完美解决了uni-app-x中z-index失效的问题,提供了更好的用户体验和更稳定的技术实现。这种方案不仅解决了当前问题,还为其他类似的弹出层组件提供了参考。
|