powershell
复制代码
<!--
* 手机预览
* @Author: Hanyang
* @Date: 2022-12-09 09:13:00
* @LastEditors: Hanyang
* @LastEditTime: 2023-01-12 15:37:00
-->
<template>
<div
class="public-preview-mobile"
ref="previewMobileRef"
:class="showMobile ? 'animation-show-mobile' : 'animation-hide-mobile'"
:style="{ ...mobileStyle, height: props.height }">
<div
v-show="showMask"
class="priveiew-mask"
ref="previewMaskRef"
@mousedown="showMask = true"
@mouseup="maskDisapear"
@mouseleave="maskDisapear"
@mousemove="maskMoveFn ? maskMoveFn($event) : () => {}"></div>
<div
class="switch"
:title="showMobile ? '点击收起手机' : '点击显示手机'"
@click="switchMobile"></div>
<div class="liu-hair-wrap">
<div
class="liu-hair"
@mousedown="(showMask = true), (maskMoveFn = mobileMouseMove)"></div>
</div>
<div
class="mobile-fixed-container"
:class="{
'fixed-container': props.isFixedContainer,
}"
:style="{ width: props.width }">
<div
class="mobile-wrap"
@scroll="scrollMobile"
:class="{
'hidden-scroll': !canScroll,
}">
<div
class="liu-hair-head"
:style="getStatusBarStyle(isImmersive, statusBarBg)"></div>
<slot></slot>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
type MobileStatusInfo = "on" | "off";
const props = defineProps({
isOn: {
//手机是否显示
type: Boolean,
default: true,
},
isImmersive: {
//是否是沉浸式状态栏
type: Boolean,
default: false,
},
statusBarBg: {
type: String,
default: "#fff",
},
canScroll: {
type: Boolean,
default: true,
},
isFixedContainer: {
//是否将手机内屏作为position:fixed;的屏幕视口
type: Boolean,
default: true,
},
width: {
//手机宽度
type: String,
default: "300px",
},
height: {
//手机高度
type: String,
default: "600px",
},
});
const emit = defineEmits(["scroll"]);
const previewMobileRef = ref();
const showMask = ref(false);
const maskMoveFn = ref<any>(null);
const showMobile = ref(true);
const mobileStyle = ref<any>(null);
const getStatusBarStyle = (isImmersive: boolean, statusBarBg: string) => {
const s: any = {};
if (isImmersive == false) {
s.background = statusBarBg;
}
return s;
};
const maskDisapear = () => {
showMask.value = false;
maskMoveFn.value = null;
};
const mobileMouseMove = (e: MouseEvent) => {
const dom = previewMobileRef.value;
let x_dis = parseFloat(window.getComputedStyle(dom).right) - e.movementX;
let y_dis = parseFloat(window.getComputedStyle(dom).top) + e.movementY;
(previewMobileRef.value as any).style.right = x_dis + "px";
(previewMobileRef.value as any).style.top = y_dis + "px";
showMobile.value = true;
};
const switchMobile = () => {
showMobile.value = !showMobile.value;
};
//组件触发事件:删除事件
const scrollMobile = (e: any) => {
emit("scroll", e);
};
/**
* 外部调用方法:用于组件内鼠标按住移动所要执行的动作
* maskMoveFn:执行的动作
*/
const maskApear = (fn?: (event: MouseEvent) => void) => {
showMask.value = true;
if (fn) maskMoveFn.value = fn;
};
/**
* 外部调用方法:获取手机状态 on-开机 off-关机
*/
const getMobileStatus = (): MobileStatusInfo => {
return showMobile.value ? "on" : "off";
};
/**
* 外部调用方法:切换手机状态
*/
const switchMobileStatus = (status: MobileStatusInfo, style?: any) => {
return new Promise((resolve, reject) => {
try {
if (
(status === "on" && showMobile.value) ||
(status === "off" && !showMobile.value)
) {
resolve(1);
} else {
const dom = previewMobileRef.value as any;
dom?.addEventListener(
"animationend",
() => {
resolve(1);
},
{
once: true,
}
);
switch (status) {
case "on":
showMobile.value = true;
break;
case "off":
showMobile.value = false;
break;
default:
showMobile.value = true;
}
if (style) {
mobileStyle.value = style;
}
}
} catch (e) {
reject(e);
}
});
};
watch(
() => props.isOn,
(val: Boolean) => {
showMobile.value = !!val;
},
{ immediate: true }
);
</script>
<style lang="scss">
@use "sass:math";
.public-preview-mobile {
cursor: pointer;
position: fixed;
display: block;
top: 180px;
right: 10px;
z-index: 10;
$LH_H: 20px; //刘海高度
$M_B: 4px; //手机边框
border: $M_B solid #000;
border-radius: 12px;
height: 600px;
@keyframes hide-mobile {
from {
// top: 0px;
}
to {
top: 97vh;
}
}
@keyframes show-mobile {
from {
// top: 0px;
}
to {
top: 97vh;
}
}
&.animation-hide-mobile {
animation-name: hide-mobile;
animation-duration: 0.2s;
animation-fill-mode: forwards;
filter: brightness(0.2);
}
&.animation-show-mobile {
animation-name: show-mobile;
animation-duration: 0.2s;
animation-fill-mode: forwards;
animation-direction: reverse;
}
.priveiew-mask {
position: fixed;
cursor: pointer;
z-index: 4023;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.switch {
width: 60px;
height: 25px;
background: pink;
position: absolute;
right: 20px;
top: -7px;
border-radius: 6px;
background: #000;
transition: all 0.2s;
&::before {
content: "";
position: absolute;
top: -16px;
left: -8px;
right: -8px;
bottom: 0px;
z-index: 12;
}
&:hover {
top: -9px;
}
&:active {
top: -6px;
}
}
.liu-hair-wrap {
position: absolute;
height: 20px;
width: 36%;
overflow: hidden;
z-index: 4013;
left: 50%;
transform: translateX(-50%);
margin-top: 0px;
.liu-hair {
cursor: move;
display: inline-block;
height: $LH_H;
width: 100%;
background: #000;
border-radius: math.div($LH_H, 2);
transform: translateY(-50%);
}
}
.mobile-fixed-container {
width: 300px;
height: inherit;
display: inline-block;
overflow: hidden;
user-select: none;
background: transparent;
box-sizing: border-box;
&.fixed-container {
transform: scale(1);
}
.mobile-wrap {
position: relative;
width: inherit;
height: inherit;
display: inline-block;
border: $M_B solid #000;
border-radius: 12px;
overflow: auto;
background: #fff;
user-select: none;
margin: -$M_B;
&::-webkit-scrollbar {
width: 2px;
/*滚动条宽度*/
height: 2px;
/*滚动条高度*/
cursor: pointer;
}
scrollbar-width: none;
&.hidden-scroll {
overflow-y: hidden !important;
}
.liu-hair-head {
position: sticky;
top: 0;
left: 0px;
right: 0px;
z-index: 11;
height: $LH_H;
}
}
}
}
</style>