Files
lilishop-uniapp/components/popups/popups.vue
Yer11214 349ffa4f34 refactor: 重构多个组件以支持 Vue 3 语法和功能
- 将多个组件转换为 `<script setup>` 语法,提升可读性和性能
- 优化状态管理和事件处理逻辑,简化代码结构
- 更新样式和布局以适应新组件结构
- 添加新功能和修复已知问题,提升用户体验
2026-07-08 18:37:11 +08:00

320 lines
7.7 KiB
Vue

<template>
<view class="shadow" :class="!show?'':'shadow-show'" :style="{backgroundColor:show?maskBg:'rgba(0,0,0,0)'}" @tap="tapMask">
<view class="popups" :class="[theme]" :style="{top: popupsTop ,left: popupsLeft,flexDirection:direction}">
<text :class="dynPlace" :style="{width:'0px',height:'0px'}" v-if="triangle"></text>
<view v-for="(item,index) in popData" :key="index" @tap.stop="tapItem(item)" class="itemChild view" :class="[direction=='row'?'solid-right':'solid-bottom',item.disabled?'disabledColor':'']">
<u-icon size="35" :name="item.icon" v-if="item.icon"></u-icon><span class="title">{{item.title}}</span>
</view>
<slot></slot>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, watch, onMounted, getCurrentInstance } from 'vue'
const props = withDefaults(defineProps<{
maskBg?: string
placement?: string
direction?: string
x?: number
y?: number
modelValue?: boolean
popData?: any[]
theme?: string
dynamic?: boolean
gap?: number
triangle?: boolean
}>(), {
maskBg: "rgba(0,0,0,0)",
placement: "default",
direction: "column",
x: 0,
y: 0,
modelValue: false,
popData: () => [],
theme: "light",
dynamic: false,
gap: 20,
triangle: true,
})
const emit = defineEmits(['update:modelValue', 'tapPopup'])
const instance = getCurrentInstance()
const popupsTop = ref("0rpx")
const popupsLeft = ref("0rpx")
const show = ref(false)
const dynPlace = ref("")
onMounted(() => {
popupsPosition()
})
const tapMask = () => {
emit("update:modelValue", !props.modelValue)
}
const tapItem = (item: any) => {
if (item.disabled) return
emit("tapPopup", item)
emit("update:modelValue", !props.modelValue)
}
const getStatusBar = () => {
const windowInfo = uni.getWindowInfo()
// #ifdef H5
return Promise.resolve(
(windowInfo.statusBarHeight || 0) + (windowInfo.windowTop || 0)
)
// #endif
// #ifndef H5
return Promise.resolve(windowInfo.statusBarHeight || 0)
// #endif
}
const popupsPosition = async () => {
let statusBar = await getStatusBar()
await new Promise<void>((resolve) => {
let popupsDom = uni.createSelectorQuery().in(instance!.proxy).select(".popups")
popupsDom
.fields(
{
size: true,
},
(data: any) => {
if (!data) {
resolve()
return
}
let width = data.width
let height = data.height
let y = props.dynamic
? dynamicGetY(props.y, props.gap)
: transformRpx(props.y)
let x = props.dynamic
? dynamicGetX(props.x, props.gap)
: transformRpx(props.x)
// #ifdef H5
y = props.dynamic
? props.y + statusBar
: transformRpx(props.y + statusBar)
// #endif
dynPlace.value =
props.placement == "default"
? getPlacement(x, y)
: props.placement
switch (dynPlace.value) {
case "top-start":
popupsTop.value = `${y + 9}rpx`
popupsLeft.value = `${x - 15}rpx`
break
case "top-end":
popupsTop.value = `${y + 9}rpx`
popupsLeft.value = `${x + 15 - width}rpx`
break
case "bottom-start":
popupsTop.value = `${y - 18 - height}rpx`
popupsLeft.value = `${x - 15}rpx`
break
case "bottom-end":
popupsTop.value = `${y - 9 - height}rpx`
popupsLeft.value = `${x + 15 - width}rpx`
break
}
resolve()
}
)
.exec()
})
}
const getPlacement = (x: number, y: number) => {
const { windowWidth: width, windowHeight: height } = uni.getWindowInfo()
if (x > width / 2 && y > height / 2) {
return "bottom-end"
} else if (x < width / 2 && y < height / 2) {
return "top-start"
} else if (x > width / 2 && y < height / 2) {
return "top-end"
} else if (x < width / 2 && y > height / 2) {
return "bottom-start"
} else if (x > width / 2) {
return "top-end"
} else {
return "top-start"
}
}
const dynamicGetY = (y: number, gap: number) => {
const { windowHeight: height } = uni.getWindowInfo()
y = y < gap ? gap : y
y = height - y < gap ? height - gap : y
return y
}
const dynamicGetX = (x: number, gap: number) => {
const { windowWidth: width } = uni.getWindowInfo()
x = x < gap ? gap : x
x = width - x < gap ? width - gap : x
return x
}
const transformRpx = (params: number) => {
const { screenWidth } = uni.getWindowInfo()
return (params * screenWidth) / 375
}
watch(() => props.modelValue, async (newVal) => {
if (newVal) await popupsPosition()
show.value = newVal
}, { immediate: true })
watch(() => props.placement, (newVal) => {
dynPlace.value = newVal
}, { immediate: true })
</script>
<style lang="scss" scoped>
.title {
margin-left: 20rpx;
}
.shadow {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
height: 100%;
z-index: 9999;
transition: background 0.3s ease-in-out;
visibility: hidden;
&.shadow-show {
visibility: visible;
}
}
.popups {
position: absolute;
padding: 20rpx;
border-radius: 5px;
display: flex;
.view {
display: flex;
align-items: center;
padding: 15rpx 10rpx;
font-size: 25rpx;
}
.image {
display: inline-block;
vertical-align: middle;
width: 40rpx;
height: 40rpx;
margin-right: 20rpx;
}
}
.dark {
background-color: #4c4c4c;
color: #fff;
.top-start:after {
content: "";
position: absolute;
top: -18rpx;
left: 10rpx;
border-width: 0 20rpx 20rpx;
border-style: solid;
border-color: transparent transparent #4c4c4c;
}
.top-end:after {
content: "";
position: absolute;
top: -18rpx;
right: 10rpx;
border-width: 0 20rpx 20rpx;
border-style: solid;
border-color: transparent transparent #4c4c4c;
}
.bottom-start:after {
content: "";
position: absolute;
bottom: -18rpx;
left: 10rpx;
border-width: 20rpx 20rpx 0;
border-style: solid;
border-color: #4c4c4c transparent transparent;
}
.bottom-end:after {
content: "";
position: absolute;
bottom: -18rpx;
right: 10rpx;
border-width: 20rpx 20rpx 0;
border-style: solid;
border-color: #4c4c4c transparent transparent;
}
.disabledColor {
color: #c5c8ce;
}
}
.light {
color: #515a6e;
box-shadow: 0upx 0upx 30upx rgba(0, 0, 0, 0.2);
background: #fff;
.top-start:after {
content: "";
position: absolute;
top: -18rpx;
left: 10rpx;
border-width: 0 20rpx 20rpx;
border-style: solid;
border-color: transparent transparent #fff;
}
.top-end:after {
content: "";
position: absolute;
top: -18rpx;
right: 10rpx;
border-width: 0 20rpx 20rpx;
border-style: solid;
border-color: transparent transparent #fff;
}
.bottom-start:after {
content: "";
position: absolute;
bottom: -18rpx;
left: 10rpx;
border-width: 20rpx 20rpx 0;
border-style: solid;
border-color: #fff transparent transparent;
}
.bottom-end:after {
content: "";
position: absolute;
bottom: -18rpx;
right: 10rpx;
border-width: 20rpx 20rpx 0;
border-style: solid;
border-color: #fff transparent transparent;
}
.disabledColor {
color: #c5c8ce;
}
}
.solid-bottom {
border-bottom: 1px solid #f3f5f7;
}
.solid-right {
border-right: 1px solid #ccc;
}
.popups .itemChild:last-child {
border: none;
}
</style>