This commit is contained in:
pengxiaolong
2025-07-25 16:39:52 +08:00
parent 7116e57fc3
commit 8580cd18fa
128 changed files with 2991 additions and 411 deletions

View File

@@ -0,0 +1,4 @@
## 1.0.12021-03-12
上次demo
## 1.0.02021-03-10
仿IOS长按的悬浮菜单

View File

@@ -0,0 +1,483 @@
<template>
<view class="mask" :class="show ? 'mask-show' : ''" :style="{backgroundColor: maskBg}" @tap="tapMask">
<view class="popups" :class="[theme]" :style="popupsStyle">
<text v-if="triangle" class="triangle" :class="dynPlace" />
<scroll-view v-if="direction == 'row'" class="scroll-row" scroll-x="true" :style="{width: scrollWidth}">
<view class="scroll-row-view" v-for="(item, idx) in popData" :key="idx" @tap.stop="tapItem(item)">
<text :class="'scroll-row-text-' + theme" :disabled="item.disabled">{{item.title}}</text>
</view>
</scroll-view>
<slot></slot>
</view>
</view>
</template>
<script>
/**
* @property {String} maskBg 遮罩背景颜色,默认 rgba(0,0,0,0),不透明
* @property {Boolean} value 显示或隐藏菜单默认falsetrue-显示false-隐藏
* @property {Number} x 长按组件的中心值单位px
* @property {Number} y 长按组件的top或bootom值单位px
* @property {String} theme popup的样式默认light支持light和dark
* @property {Number} gap
* @property {Boolean} triangle 是否显示三角图标默认truetrue-显示false-隐藏
* @property {Boolean} dynamic 是否动态显示默认falsetrue-是false-否
* @property {Array} popData popup的按钮数据
* @property {String} direction 排列方向默认column支持column和row
* @property {String} placement 三角形图标相对于menu菜单的位置默认top-start
* top-start、top-end
* bottom-start、bottom-end
* top-center、bottom-center
*/
let systemInfo = uni.getSystemInfoSync();
export default {
name: "yinrh-menu-popup",
props: {
maskBg: {
type: String,
default: 'rgba(0,0,0,0)'
},
value: {
type: Boolean,
default: false
},
x: {
type: Number,
default: 0
},
y: {
type: Number,
default: 0
},
theme: {
type: String,
default: 'light'
},
gap: {
type: Number,
default: 20
},
triangle: {
type: Boolean,
default: true
},
dynamic: {
type: Boolean,
default: false
},
popData: {
type: Array,
default: () => []
},
direction: {
type: String,
default: 'column'
},
placement: {
type: String,
default: 'top-start'
}
},
watch: {
value: {
immediate: true,
handler: async function(newVal, oldVal) {
if (newVal) await this.popupsPosition()
this.show = newVal
}
},
placement: {
immediate: true,
handler(newVal, oldVal) {
this.dynPlace = newVal
}
}
},
computed: {
/**
* 根据menu列表来确定menu的宽度最大宽度为屏幕宽度的0.8倍
* 每个字符串的左右边距是10单位px
* 每个字符串的宽度 = len * 8 + 12单位px
*/
scrollWidth() {
let width = systemInfo.screenWidth * 0.8,
ret = 0;
this.popData.forEach((item, index) => {
let len = String(item.title).length;
ret += len * 8 + 12 + 20;
});
return (ret > width ? width : ret) + 'px';
}
},
data() {
return {
arrowStyle: {}, // triangle的style确定位置
dynPlace: '', // 三角形箭头的class字符串
show: false, // 是否显示遮罩true-显示false-不显示
popupsTop: '0px', // 不用
popupsLeft: '0px', // 不用
popupsStyle: {} // popups的style确定位置和方向
}
},
mounted() {
this.popupsPosition()
},
methods: {
/**
* 遮罩显示或隐藏
*/
tapMask() {
this.$emit('input', !this.value);
},
/**
* menu菜单按钮单击事件
* @param {Object} item
*/
tapItem(item) {
if (item.disabled) return;
this.$emit('tapPopup', item);
this.$emit('input', !this.value);
},
/**
* 动态获得y的值
* @param {Object} y
* @param {Object} gap
*/
dynamicGetY(y, gap) {
let h = systemInfo.windowHeight;
y = y < gap ? gap : y;
return h - y < gap ? (h - gap) : y;
},
/**
* 动态获得x的值
* @param {Object} x
* @param {Object} gap
*/
dynamicGetX(x, gap) {
let w = systemInfo.windowWidth;
x = x < gap ? gap : x;
return w - x < gap ? (w - gap) : x;
},
/**
* 静态获得x或y的值
* @param {Object} v
*/
transformRpx(v) {
return v * systemInfo.screenWidth / 375;
},
async popupsPosition() {
let promise = new Promise((resolve, reject) => {
let popups = uni.createSelectorQuery().in(this);
popups.select(".popups").fields({
size: true,
}, (data) => {
let x, y;
if (this.dynamic) { // 动态
x = this.dynamicGetX(this.x, this.gap);
y = this.dynamicGetY(this.y, this.gap);
} else { // 静态
let _v = systemInfo.screenWidth / 375;
x = this.x * _v;
y = this.y * _v;
}
let _ssw = systemInfo.screenWidth,
_rpx = uni.upx2px(20),
_top = y - _rpx - data.height,
left = (_ssw - data.width) / 2;
this.dynPlace = this.placement; // 三角形箭头
switch (this.dynPlace) {
case 'top-start': // 上左y为bottom
this.popupsStyle = {
top: `${y + _rpx}px`,
left: '15px',
flexDirection: this.direction
};
break;
case 'top-end': // 上右y为bottom
this.popupsStyle = {
top: `${y + _rpx}px`,
right: '15px',
flexDirection: this.direction
};
break;
case 'top-center': // 上中y为bottom
this.popupsStyle = {
top: `${y + _rpx}px`,
left: `${left}px`,
flexDirection: this.direction
};
break;
case 'bottom-start': // 下左y为top
this.popupsStyle = {
top: `${_top}px`,
left: '15px',
flexDirection: this.direction
};
break;
case 'bottom-end': // 下右y为top
this.popupsStyle = {
top: `${_top}px`,
right: '15px',
flexDirection: this.direction
};
break;
case 'bottom-center': // 下中y为top
this.popupsStyle = {
top: `${_top}px`,
left: `${left}px`,
flexDirection: this.direction
};
break;
}
resolve()
}).exec();
})
return promise
}
}
}
</script>
<style lang="scss" scoped>
/** mask遮罩 */
.mask {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 9999;
visibility: hidden;
transition: background 0.3s ease-in-out;
&.mask-show {
visibility: visible;
}
}
/** menu菜单弹窗 */
.popups {
position: absolute;
display: flex;
height: 45px;
border-radius: 10px;
}
/** scroll-row支持横向滑动 */
.scroll-row {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
height: 45px;
overflow: hidden;
white-space: nowrap;
box-sizing: border-box;
}
.scroll-row-view {
width: auto;
height: 25px;
display: inline-block;
margin-top: 10px;
border-right: 1px solid #CCCCCC;
}
.scroll-row-view:last-child {
border-right: 0px solid #CCCCCC;
}
/** scroll-view横向滑动dark的文本样式 */
.scroll-row-text-dark {
width: auto;
height: 25px;
line-height: 25px;
text-align: center;
color: #FFFFFF;
font-size: 30rpx;
padding-left: 10px;
padding-right: 10px;
}
/** scroll-view横向滑动dark的disabled文本样式 */
.scroll-row-text-dark[disabled] {
color: #C5C8CE !important;
}
/** scroll-view横向滑动light的文本样式 */
.scroll-row-text-light {
width: auto;
height: 25px;
line-height: 25px;
text-align: center;
color: #515A6E;
font-size: 30rpx;
padding-left: 10px;
padding-right: 10px;
}
/** scroll-view横向滑动light的disabled文本样式 */
.scroll-row-text-light[disabled] {
color: #C5C8CE !important;
}
.triangle {
width: 0px;
height: 0px;
}
/** darkpopups的theme值黑底白字 */
.dark {
background-color: #4C4C4C;
.top-start:after {
content: "";
position: absolute;
top: -18rpx;
left: 20px;
border-width: 0 20rpx 20rpx;
border-style: solid;
border-color: transparent transparent #4C4C4C;
}
.top-end:after {
content: "";
position: absolute;
top: -18rpx;
right: 20px;
border-width: 0 20rpx 20rpx;
border-style: solid;
border-color: transparent transparent #4C4C4C;
}
.top-center:after {
content: "";
position: absolute;
top: -18rpx;
left: 47%;
border-width: 0 20rpx 20rpx;
border-style: solid;
border-color: transparent transparent #4C4C4C;
}
.bottom-start:after {
content: "";
position: absolute;
bottom: -18rpx;
left: 20px;
border-width: 20rpx 20rpx 0;
border-style: solid;
border-color: #4C4C4C transparent transparent;
}
.bottom-end:after {
content: "";
position: absolute;
bottom: -18rpx;
right: 20px;
border-width: 20rpx 20rpx 0;
border-style: solid;
border-color: #4C4C4C transparent transparent;
}
.bottom-center:after {
content: "";
position: absolute;
bottom: -18rpx;
left: 47%;
border-width: 20rpx 20rpx 0;
border-style: solid;
border-color: #4C4C4C transparent transparent;
}
}
/** lightpopups的theme值白底黑字 */
.light {
background: #FFFFFF;
box-shadow: 0upx 0upx 30upx rgba(0, 0, 0, 0.2);
.top-start:after {
content: "";
position: absolute;
top: -18rpx;
left: 20px;
border-width: 0 20rpx 20rpx;
border-style: solid;
border-color: transparent transparent #FFFFFF;
}
.top-end:after {
content: "";
position: absolute;
top: -18rpx;
right: 20px;
border-width: 0 20rpx 20rpx;
border-style: solid;
border-color: transparent transparent #FFFFFF;
}
.top-center:after {
content: "";
position: absolute;
top: -18rpx;
right: 48%;
border-width: 0 20rpx 20rpx;
border-style: solid;
border-color: transparent transparent #FFFFFF;
}
.bottom-start:after {
content: "";
position: absolute;
bottom: -18rpx;
left: 20px;
border-width: 20rpx 20rpx 0;
border-style: solid;
border-color: #FFFFFF transparent transparent;
}
.bottom-end:after {
content: "";
position: absolute;
bottom: -18rpx;
right: 20px;
border-width: 20rpx 20rpx 0;
border-style: solid;
border-color: #FFFFFF transparent transparent;
}
.bottom-end:after {
content: "";
position: absolute;
bottom: -18rpx;
left: 48%;
border-width: 20rpx 20rpx 0;
border-style: solid;
border-color: #FFFFFF transparent transparent;
}
}
.popups.item:last-child {
border: none;
}
._right {
border-right: 1px solid #CCCCCC;
}
._right:last-child {
border-right: 0px solid #CCCCCC;
}
._bottom {
border-bottom: 1px solid #CCCCCC;
}
._bottom:last-child {
border-bottom: 0px solid #CCCCCC;
}
</style>

View File

@@ -0,0 +1,76 @@
{
"id": "yinrh-menu-popup",
"displayName": "yinrh-menu-popup仿IOS长按的悬浮菜单",
"version": "1.0.1",
"description": "仿IOS长按的悬浮菜单",
"keywords": [
"yinrh-menu-popup"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
}
}
}
}
}

View File

@@ -0,0 +1,49 @@
## yinrh-menu-popup
仿IOS长按的悬浮菜单
## 使用方法
### 在``template``中直接使用
```
<yinrh-menu-popup v-model="value" :pop-data="menu" dynamic :x="x" :y="y" direction="row" theme="dark"
:placement="place" @tapPopup="tapPopup" />
```
### 在``template``中,需要出现菜单的 view 上添加 id 和 click 事件
```
<text id="popup-menu" @longTap="longTap">长按出现悬浮菜单</text>
```
### 在``script``中,添加 longTap 和 tapPopup 方法
```
longTap() {
let dom = uni.createSelectorQuery().in(this);
// popup-menu 为上面添加的 id 值
dom.select('#popup-menu').boundingClientRect();
dom.exec(data => {
// 配置 place、value、x、y 的值
let isTop = data[0].top >= 60,
place = isTop ? 'bottom' : 'top';
this.place = place + '-center';
this.value = !this.value;
this.x = data[0]['width'] / 2;
this.y = data[0][isTop ? 'top' : 'bottom'];
});
}
```
```
tapPopup(e) {
console.log(e);
}
```
## 参数说明
+ maskBg遮罩背景颜色默认 rgba(0,0,0,0),不透明
+ value显示或隐藏菜单true-显示false-隐藏,默认 true
+ x长按组件的中心值单位px
+ y长按组件的 top 或 bootom 值单位px
+ theme悬浮菜单的样式支持 light 和 dark默认 light
+ gap
+ triangle悬浮菜单的箭头true-显示false-隐藏,默认 true
+ dynamic悬浮菜单的显示方式true-动态false-静态,默认 true
+ popData悬浮菜单的列表
+ direction悬浮菜单排列方向支持 column 和 row默认 column
+ placement悬浮菜单的箭头相对于菜单的位置 top-start、top-end、bottom-start、bottom-end、top-center、bottom-center默认 top-start