基于uview-pro的u-dropdown扩展自己的dropdown组件

作者:LC同学47981日期:2026/2/6

基于uview-pro的u-dropdown扩展自己的dropdown组件

uview-pro的u-dropdown只能是菜单,且只能向下展开,当前组件采用它的核心逻辑,去除多余逻辑,兼容上/下展开,以及自定义展示的内容,不再局限于菜单形式

e4043c3d-df06-4a4f-9d61-237d8254cf54.png

1import type { ExtractPropTypes, PropType } from 'vue';
2import { baseProps } from 'uview-pro/components/common/props';
3
4/**
5 * u-dropdown 下拉菜单 Props
6 * @description 该组件一般用于向下展开菜单,同时可切换多个选项卡的场景
7 */
8export const DropdownProps = {
9  ...baseProps,
10  /** 点击遮罩是否关闭菜单 */
11  closeOnClickMask: { type: Boolean, default: true },
12  /** 过渡时间 */
13  duration: { type: [Number, String] as PropType<number | string>, default: 300 },
14  /** 下拉出来的内容部分的圆角值 */
15  borderRadius: { type: [Number, String] as PropType<number | string>, default: 20 },
16  /** 展开方向 down/up */
17  direction: { type: String as PropType<'down' | 'up'>, default: 'up' },
18  /** 弹出层最大高度 */
19  maxHeight: { type: String as PropType<`${number}rpx` | `${number}vh`>, default: '80vh' },
20  /** 弹出层最小高度 */
21  minHeight: { type: String as PropType<`${number}rpx` | `${number}vh`>, default: '0rpx' },
22  /** 是否隐藏关闭按钮 */
23  hiddenClose: { type: Boolean, default: false },
24  /** 弹出层标题 */
25  title: { type: String, default: '' }
26};
27
28export type DropdownProps = ExtractPropTypes<typeof DropdownProps>;
29
30
1<template>
2  <view class="u-dropdown" :style="$u.toStyle(styles, customStyle)" :class="customClass">
3    <view class="u-dropdown__menu">
4      <slot></slot>
5    </view>
6    <view
7      class="u-dropdown__content"
8      :style="[
9        contentStyle,
10        {
11          transition: [`opacity ${Number(duration) / 1000}s linear`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.s.md),
12          [currentDirection === 'down' ? 'top' : 'bottom']: menuHeight + 'px',
13          height: contentHeight + 'px'
14        }
15      ]"
16      @tap="maskClick"
17      @touchmove.stop.prevent>
18      <view @tap.stop.prevent class="u-dropdown__content__popup" :style="[popupStyle]">
19        <slot name="close" v-if="!hiddenClose">
20          <view class="u-dropdown__content__popup__close" @click="close">
21            <u-icon name="close" size="48" custom-prefix="custom-icon" />
22          </view>
23        </slot>
24
25        <slot name="header">
26          <view class="u-dropdown__content__popup__header" v-if="title"> {{ title }} </view>
27        </slot>
28
29        <view class="u-dropdown__content__popup__body">
30          <scroll-view scroll-y class="u-dropdown__content__popup__scroll-view">
31            <slot name="content"></slot>
32          </scroll-view>
33        </view>
34        <view class="u-dropdown__content__popup__footer">
35          <slot name="footer"></slot>
36        </view>
37      </view>
38      <view class="u-dropdown__content__mask"></view>
39    </view>
40  </view>
41</template>
42
43<script lang="ts">
44  export default {
45    name: 'hj-dropdown',
46    options: {
47      addGlobalClass: true,
48      // #ifndef MP-TOUTIAO
49      virtualHost: true,
50      // #endif
51      styleIsolation: 'shared'
52    }
53  };
54</script>
55
56<script setup lang="ts">
57  import { ref, computed, onMounted, getCurrentInstance, watch, type CSSProperties } from 'vue';
58  import { $u } from 'uview-pro';
59  import { DropdownProps } from './types';
60
61  /**
62   * dropdown 下拉菜单
63   * @description 该组件一般用于向下展开菜单,同时可切换多个选项卡的场景
64   * @tutorial https://uviewpro.cn/zh/components/dropdown.html
65   * @property {Boolean} close-on-click-mask 点击遮罩是否关闭菜单(默认true)
66   * @property {String | Number} duration 选项卡展开和收起的过渡时间,单位ms(默认300)
67   * @property {String | Number} border-radius 菜单展开内容下方的圆角值,单位任意(默认20)
68   * @property {String} direction 展开方向 down/up(默认up)
69   * @property {String} max-height 弹出层最大高度(默认80vh)
70   * @property {String} min-height 弹出层最小高度
71   * @property {Boolean} hidden-close 是否隐藏关闭按钮(默认false)
72   * @property {String} title 弹出层标题
73   * @property {Boolean} show 是否显示下拉菜单(默认false)
74   * @event {Function} open 下拉菜单被打开时触发
75   * @event {Function} close 下拉菜单被关闭时触发
76   * @example <hj-dropdown></hj-dropdown>
77   */
78
79  const props = defineProps(DropdownProps);
80  const emit = defineEmits(['open', 'close']);
81
82  // 展开状态
83  const active = ref(false);
84  // 外层内容样式
85  const contentStyle = ref<CSSProperties>({
86    zIndex: -1,
87    opacity: 0
88  });
89  // 下拉内容高度
90  const contentHeight = ref<number>(0);
91  // 菜单实际高度
92  const menuHeight = ref<number>(0);
93  // 当前展开方向
94  const currentDirection = ref<'down' | 'up'>(props.direction);
95  // 子组件引用
96  const instance = getCurrentInstance();
97
98  const vShow = defineModel('show', {
99    type: Boolean,
100    default: false
101  });
102
103  watch(vShow, val => {
104    if (val === active.value) return;
105    if (val) {
106      open();
107    } else {
108      close();
109    }
110  });
111
112  // 监听方向变化
113  watch(
114    () => props.direction,
115    val => {
116      currentDirection.value = val;
117    }
118  );
119
120  // 兼容头条样式
121  const styles = computed<CSSProperties>(() => {
122    const style: CSSProperties = {};
123    // #ifdef MP-TOUTIAO
124    style.width = '100vw';
125    // #endif
126    return style;
127  });
128
129  // 下拉出来部分的样式
130  const popupStyle = computed<CSSProperties>(() => {
131    const style: CSSProperties = {};
132    const isDown = currentDirection.value === 'down';
133    const hiddenTransformLate = isDown ? '-100%' : '100%';
134
135    style.maxHeight = props.maxHeight;
136    style.minHeight = props.minHeight;
137    style.transform = `translateY(${active.value ? 0 : hiddenTransformLate})`;
138    style[isDown ? 'top' : 'bottom'] = 0;
139    // 进行Y轴位移,展开状态时,恢复原位。收起状态时,往上位移100%(或下),进行隐藏
140    style.transitionDuration = [`${Number(props.duration) / 1000}s`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.s.md);
141
142    if (isDown) {
143      style.borderRadius = [`0 0 ${$u.addUnit(props.borderRadius)} ${$u.addUnit(props.borderRadius)}`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.u.md);
144    } else {
145      style.borderRadius = [`${$u.addUnit(props.borderRadius)} ${$u.addUnit(props.borderRadius)} 0 0`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.u.md);
146    }
147    return style;
148  });
149
150  // 生命周期
151  onMounted(() => {
152    getContentHeight();
153  });
154
155  /**
156   * 打开下拉菜单
157   * @param direction 展开方向 'down' | 'up'
158   */
159  function open(direction?: 'down' | 'up') {
160    currentDirection.value = direction || props.direction;
161
162    // 重新计算高度,因为方向可能改变
163    getContentHeight();
164
165    // 设置展开状态
166    active.value = true;
167
168    // 展开时,设置下拉内容的样式
169    contentStyle.value = {
170      zIndex: 11,
171      opacity: 1
172    };
173    vShow.value = true;
174    emit('open');
175  }
176
177  /**
178   * 关闭下拉菜单
179   */
180  function close() {
181    // 下拉内容的样式进行调整,不透明度设置为0
182    active.value = false;
183    contentStyle.value = {
184      ...contentStyle.value,
185      opacity: 0
186    };
187
188    // 等待过渡动画结束后隐藏 z-index
189    vShow.value = false;
190    setTimeout(() => {
191      contentStyle.value = {
192        zIndex: -1,
193        opacity: 0
194      };
195      emit('close');
196    }, Number(props.duration));
197  }
198
199  /**
200   * 点击遮罩
201   */
202  function maskClick() {
203    if (!props.closeOnClickMask) return;
204    close();
205  }
206
207  /**
208   * 获取下拉菜单内容的高度
209   * @description
210   * dropdown组件是相对定位的,下拉内容必须给定高度,
211   * 才能让遮罩占满菜单以下直到屏幕底部的高度。
212   */
213  function getContentHeight() {
214    const windowHeight = $u.sys().windowHeight;
215
216    $u.getRect('.u-dropdown__menu', instance).then((res: any) => {
217      // 获取菜单实际高度
218      menuHeight.value = res.height;
219
220      /**
221       * 尺寸计算说明:
222       * 在H5端,uniapp获取尺寸存在已知问题:
223       * 元素尺寸的top值为导航栏底部到元素的上边沿的距离
224       * 但元素的bottom值却是导航栏顶部到元素底部的距离
225       * 为避免页面滚动,此处取菜单栏的bottom值进行计算
226       */
227      if (currentDirection.value === 'up') {
228        contentHeight.value = res.top;
229      } else {
230        contentHeight.value = windowHeight - res.bottom;
231      }
232    });
233  }
234
235  // 暴露方法
236  defineExpose({
237    close,
238    open
239  });
240</script>
241
242<style scoped lang="scss">
243  @import 'uview-pro/libs/css/style.components';
244
245  .u-dropdown {
246    flex: 1;
247    width: 100%;
248    position: relative;
249    background-color: #fff;
250
251    &__content {
252      position: absolute;
253      z-index: 8;
254      width: 100%;
255      left: 0;
256      overflow: hidden;
257
258      &__mask {
259        position: absolute;
260        z-index: 9;
261        background: rgba(0, 0, 0, 0.3);
262        width: 100%;
263        left: 0;
264        top: 0;
265        bottom: 0;
266      }
267
268      &__popup {
269        position: absolute;
270        width: 100%;
271        z-index: 10;
272        transition: all 0.3s;
273        transform: translate3D(0, -100%, 0);
274        overflow: hidden;
275        background-color: var(--gray-2);
276
277        &__close {
278          width: 40rpx;
279          height: 40rpx;
280          display: flex;
281          align-items: center;
282          justify-content: center;
283          position: absolute;
284          right: 24rpx;
285          top: 30rpx;
286          z-index: 9;
287        }
288
289        &__header {
290          display: flex;
291          color: var(--title-1);
292          font-size: var(--ft-32);
293          font-weight: 500;
294          line-height: 44rpx;
295          padding: 30rpx 24rpx;
296        }
297
298        &__body {
299          flex: 1;
300          overflow: hidden;
301          display: flex;
302        }
303
304        &__scroll-view {
305          flex: 1;
306        }
307
308        &__footer {
309          display: flex;
310        }
311      }
312    }
313  }
314</style>
315
316

基于uview-pro的u-dropdown扩展自己的dropdown组件》 是转载文章,点击查看原文


相关推荐


🔥别再用递归了!WeakMap 的影子索引“让树不再是树!”
vilan_微澜2026/1/28

一、前言 大家好,我是微澜。今天来分享一个基于 WeakMap 实现的快速对树形结构数据进行增删改查操作的useTree hook函数,它是基于JavaScript 的 WeakMap 特性,在不改动原始数据的前提下,实现了一套 O(1) 查找的影子索引结构,这个影子其实就是对象的引用地址,让树形数据操作像操作数组一样简单! 二、为什么选择 WeakMap? 1. 非侵入性 (Non-invasive) 通过 WeakMap 在内存中构建了一套 Node -> Parent 的映射。原始数据对象


【Python爬虫实战】用 Flet 把爬虫做成手机 App
MoonPointer-Byte2026/1/18

有没有想过,把你写的爬虫装进手机里? 比如: 想听歌时,后台自动爬取音乐的资源并播放; 想搜图时,后台自动爬取 高清图接口并下载; 想看人时,一键聚合搜索社交用户数据。 今天我们将实战一个MoonMusic。它的核心不是 UI,而是强大的异步数据采集层。 🔧 核心技术栈 数据采集 (Crawler): httpx (异步 HTTP 请求), BeautifulSoup4 (HTML 解析) 并发控制 (Concurrency): asyncio (协程调度) 数据可视


Java是当今最优雅的开发语言
richxu202510012026/1/10

我认为Java是当今最优雅的开发语言! 天然成熟的生态 !! 项目内部代码都各种积木化(模块化) (离不开spring boot的加持) 我也曾用过Delphi ,C#,Python 开发 ! 随感而发,不喜勿喷        #嵌入式 #电子信息 #编程 #软件设计与开发 #找工作 #后端开发 #单片机 #小红书#Java


程序员副业 | 2025年12月复盘
嘟嘟MD2026/1/2

本文首发于公众号:嘟爷创业日记 。 我已经坚持日更600天+,欢迎过来追剧~ 大家好,我是嘟嘟MD,一个10年程序员,现在离职创业,有700天了,我每个月都会写一篇总结复盘,让大家可以近距离看看一个离职程序员都在干什么,今天这篇是十二月份的总结,大概2000字,略长,有空的可以翻翻,希望对大家有一丢丢的借鉴作用! 一、月度大事 今天先把12月的复盘写了, 改天再把25年的复盘整理哈,这一年还是经历了很多事情,需要好好总结复盘 1:公众号运营+B站视频运营 公众号和B站视频运营目前还是最高


OSPF协议
Suchadar2025/12/23

一、OSPF 协议概述         OSPF(Open Shortest Path First,开放式最短路径优先协议)是一种链路状态路由协议,隶属于内部网关协议(IGP,Interior Gateway Protocol)范畴,核心功能是实现自治系统(AS,Autonomous System)内部路由器之间的路由信息交换,为数据传输提供最优路径指引。         分层网络架构:通过 “区域(Area)” 和 “特殊角色路由器” 划分网络层级,有效解决单区域网络中路由信息泛滥、设备资


基于 Gradio 构建神经网络 GUI 实验平台:感知器 / BP/Hopfield/AlexNet/VGG/ResNet 一站式实现
晞微2025/12/15

引言 神经网络实验往往需要编写大量代码、调试参数、手动可视化结果,对于初学者而言门槛较高。传统的命令行式实验方式不仅操作繁琐,还难以直观展示算法效果。本文基于Gradio框架,构建了一个集经典神经网络(感知器、BP、Hopfield) 和深度学习模型(AlexNet/VGG/ResNet) 于一体的可视化 GUI 实验平台,无需编写代码,仅通过界面交互即可完成各类神经网络实验,大幅降低了实验门槛。 本平台支持以下核心实验: 感知器二分类实验BP 神经网络非线性函数拟合实验Hopfield


手写 EventEmitter:深入理解发布订阅模式
1024肥宅2025/12/7

引言 在 JavaScript 开发中,事件驱动编程是构建可维护、可扩展应用的核心范式之一。从浏览器 DOM 事件到 Node.js 的异步 I/O,从 Vue 的组件通信到 React 的状态管理,发布订阅模式无处不在。 通过手写一个符合 Node.js EventEmitter 标准的实现,我们不仅能深入理解事件驱动架构的设计原理,还能掌握 JavaScript 中闭包、内存管理、设计模式等核心概念。更重要的是,这是面试中常见的高级题目,能体现你对JavaScript设计模式的理解深度。 本


C#小案例-->让两个线程交替打印偶数和奇数值(并发)
MM_MS2025/11/28

方法一: 编写代码实现切换逻辑 using System; using System.Threading; namespace 让两个线程交替打印偶数和奇数值_并发_ { internal class Program { // ===================== 共享资源与同步工具 ===================== // 1. 偶数线程的当前值(初始为0,每次+2,打印偶数) private static i


Flutter三方库适配OpenHarmony【apple_product_name】异步调用与错误处理
淼学派对2026/2/14

前言 欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net 本文将围绕 apple_product_name 的实际 API,从 Future 基础到全局错误兜底,给出一套完整的异步调用与错误处理方案。 先给出结论式摘要: 所有 API 返回 Future:getMachineId()、getProductName()、lookup() 都是异步的,必须 await 或 .then()三类异常要分层捕获:PlatformExc


AI 系统架构
lizhongxuan2026/2/23

AI 系统看起来很复杂,但核心可以压缩成三句话: 尽量少搬数据:很多时候不是算不动,而是数据搬运太慢。 尽量提高有效计算密度:让硬件更多时间在做有价值的乘加计算。 尽量重叠计算与通信:训练和推理都要避免“设备空等”。 换句话说,AI 性能问题本质上是 计算(Compute)+ 访存(Memory)+ 通信(Communication) 的协同问题。 1. AI 系统栈 层级主要职责典型问

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 XYZ博客