【sgFloatDialog】自定义组件:浮动弹窗,支持修改尺寸、拖拽位置、最大化、还原、最小化、复位
sgFloatDialog
<template>
<div :class="$options.name" v-if="visible" :theme="theme" :size="size" :style="style">
<!-- 托盘头部 -->
<div class="header" ref="header" @dblclick.stop.prevent="dblclickHeader">
<div class="left">
<div class="title">
<i :class="titleIcon" style="margin-right: 5px" />
<span>{{ title }}</span>
</div>
</div>
<div class="right" @mousedown.stop>
<!-- 控制托盘的图标按钮 -->
<div class="tray-btns">
<div
class="icon-btn"
v-if="show_rb_btn"
v-show="![`rb`, `mn`, `lg`].includes(size)"
@dblclick.stop
@click.stop="size = `rb`"
title="回到原来的位置"
>
<i class="el-icon-bottom-right"></i>
</div>
<div
class="icon-btn"
v-if="show_mn_btn"
v-show="size !== `mn`"
@dblclick.stop
@click.stop="size = `mn`"
title="最小化"
>
<i class="el-icon-minus"></i>
</div>
<div
class="icon-btn"
v-show="[`mn`, `lg`].includes(size)"
@dblclick.stop
@click.stop="toMd()"
title="还原"
>
<i :class="size === `lg` ? `el-icon-copy-document` : `el-icon-d-caret`"></i>
</div>
<div
class="icon-btn"
v-show="size !== `lg`"
@dblclick.stop
@click.stop="size = `lg`"
title="全屏"
>
<i class="el-icon-full-screen"></i>
</div>
<div class="icon-btn" @dblclick.stop @click.stop="close">
<i class="el-icon-close"></i>
</div>
</div>
</div>
</div>
<div class="body"><slot /></div>
<!-- 拖拽移动窗体 -->
<sgDragMove
ref="sgDragMove"
:data="dragMoveDoms"
:cursor="{
grab: 'default',
grabbing: 'default',
}"
nearPadding="10"
:disabled="changeSizeStatus"
@dragging="draggingMove"
mousemoveNearSide
/>
<!-- 拖拽改变窗体尺寸 -->
<sgDragSize
v-if="resizeable"
:disabled="size === `lg`"
@dragStart="changeSizeStatus = true"
@dragging="draggingSize"
@dragEnd="changeSizeStatus = false"
:minWidth="minWidth"
:minHeight="minHeight"
/>
</div>
</template>
<script>
import sgDragMove from "@/vue/components/admin/sgDragMove";
import sgDragSize from "@/vue/components/admin/sgDragSize";
export default {
name: "sgFloatDialog",
components: {
sgDragMove,
sgDragSize,
},
data() {
return {
form: {},
theme: ``, //主题
//----------------------------------------
titleIcon: `el-icon-question`,
title: `浮动窗口标题`,
// ----------------------------------------
defaultRight: 100, //默认出现的位置
defaultBottom: 20, //默认出现的位置
defaultWidth: 400, //默认宽度
defaultHeight: 500, //默认高度
minWidth: 300, //最小宽度
minHeight: 50, //最小高度
// ----------------------------------------
style_bk: null,
style: {},
visible: false,
show_rb_btn: true, //是否显示回到右下角按钮
show_mn_btn: true, //是否显示最小化按钮
size: ``, //lg全屏最大化 lgl左侧最大化 lgr右侧最大化 md普通 mn最小 rb右下角
changeSizeStatus: false, //避免修改尺寸的时候,也在拖拽移动窗体
dragMoveDoms: [
/* {
canDragDom: elementDOM,//可以拖拽的位置元素
moveDom: elementDOM,//拖拽同步移动的元素
} */
], //可以拖拽移动的物体
resizeable: true,
};
},
props: ["data", "value"],
computed: {},
watch: {
value: {
handler(d) {
this.visible = d;
},
deep: true,
immediate: true,
},
visible(d) {
this.$emit("input", d);
},
data: {
handler(newValue, oldValue) {
//console.log(`深度监听${this.$options.name}:`, newValue, oldValue);
if (Object.keys(newValue || {}).length) {
this.form = JSON.parse(JSON.stringify(newValue));
this.$g.convertForm2ComponentParam(`theme`, this);
this.$g.convertForm2ComponentParam(`titleIcon`, this);
this.$g.convertForm2ComponentParam(`title`, this);
this.$g.convertForm2ComponentParam(`defaultRight`, this);
this.$g.convertForm2ComponentParam(`defaultBottom`, this);
this.$g.convertForm2ComponentParam(`defaultWidth`, this);
this.$g.convertForm2ComponentParam(`defaultHeight`, this);
this.$g.convertForm2ComponentParam(`minWidth`, this);
this.$g.convertForm2ComponentParam(`minHeight`, this);
this.$g.convertForm2ComponentParam(`resizeable`, this);
this.$nextTick(() => {
this.$el.style.setProperty("--defaultRight", `${this.defaultRight}px`); //js往css传递局部参数
this.$el.style.setProperty("--defaultBottom", `${this.defaultBottom}px`); //js往css传递局部参数
this.$el.style.setProperty("--defaultWidth", `${this.defaultWidth}px`); //js往css传递局部参数
this.$el.style.setProperty("--defaultHeight", `${this.defaultHeight}px`); //js往css传递局部参数
this.$el.style.setProperty("--minWidth", `${this.minWidth}px`); //js往css传递局部参数
this.$el.style.setProperty("--minHeight", `${this.minHeight}px`); //js往css传递局部参数
this.dragMoveDoms = [
{
canDragDom: this.$refs.header, //托盘的头部可以拖拽
moveDom: this.$el, //拖拽的时候,整个上传列表一起跟随移动
},
];
this.style_bk = {
right: `${this.defaultRight}px`,
bottom: `${this.defaultBottom}px`,
width: `${this.defaultWidth}px`,
height: `${this.defaultHeight}px`,
};
});
}
},
deep: true, //深度监听
immediate: true, //立即执行
},
resizeable: {
handler(newValue, oldValue) {
newValue === undefined || (this.resizeable = newValue === "" || newValue);
},
deep: true, //深度监听
immediate: true, //立即执行
},
style: {
handler(newValue, oldValue) {
if (Object.keys(newValue || {}).length) {
let { width, height, left, top } = newValue;
width = parseInt(width);
height = parseInt(height);
left = parseInt(left);
top = parseInt(top);
if (width <= this.minWidth && height <= this.minHeight) {
this.show_mn_btn = false;
} else {
this.show_mn_btn = true;
}
let right = innerWidth - left - width;
let bottom = innerHeight - top - height;
if (right <= this.defaultRight && bottom < this.defaultBottom) {
this.show_rb_btn = false;
} else {
this.show_rb_btn = true;
}
} else {
this.show_mn_btn = true;
}
},
deep: true, //深度监听
immediate: true, //立即执行
},
},
created() {},
mounted() {},
destroyed() {},
methods: {
toMd() {
this.size = null;
this.style_bk && (this.style = JSON.parse(JSON.stringify(this.style_bk)));
},
bkStyle() {
this.style && (this.style_bk = JSON.parse(JSON.stringify(this.style)));
},
fromLgToMd(e) {
let { x, y } = e.$event;
let style = JSON.parse(JSON.stringify(this.style_bk));
let leftPointDis = (x / innerWidth) * parseFloat(style.width); //计算鼠标到左上角定点的距离
this.$refs.sgDragMove.setOffset({ x: leftPointDis });
},
draggingMove(e) {
let { x, y } = e.$event;
let dis = 5; //吸附屏幕边缘
if (y <= dis) {
this.size = `lg`; // 拖拽到浏览器顶部
} else if (x <= dis) {
this.size = `lgl`; // 拖拽到浏览器左侧
} else if (x >= innerWidth - dis) {
this.size = `lgr`; // 拖拽到浏览器右侧
} else {
// 拖拽回到浏览器中间
if ((this.size || "").includes(`lg`)) {
this.fromLgToMd(e);
} else {
this.style = e.moveDomStyle;
this.bkStyle();
}
this.size = null;
}
},
draggingSize({ style }) {
this.style = style;
this.bkStyle();
this.size = null;
},
dblclickHeader(d) {
switch (this.size) {
case "lg":
this.size = "md";
break;
case "mn":
this.size = "md";
break;
case "md":
default:
this.size = "lg";
break;
}
},
// 关闭托盘
close() {
this.visible = false;
this.$emit(`close`, this.style);
},
},
};
</script>
<style lang="scss" scoped>
.sgFloatDialog {
$headerHeight: 40px; //头部高度
$defaultRight: var(--defaultRight); //托盘默认距离右下角的位置
$defaultBottom: var(--defaultBottom); //托盘默认距离右下角的位置
$defaultWidth: var(--defaultWidth); //托盘默认宽度
$defaultHeight: var(--defaultHeight); //托盘默认高度
$minWidth: var(--minWidth); //托盘最小宽度
$minHeight: var(--minHeight); //托盘最小高度
// ----------------------------------------
transition: none;
position: fixed;
z-index: 2001; //根据情况自己拿捏(太大了会遮住element的其他弹窗组件),v-loading默认是2000的z-index
user-select: none;
margin: 0;
padding: 0;
right: 100px;
bottom: 20px;
width: $defaultWidth;
height: $defaultHeight;
background-color: white;
min-width: $minWidth;
min-height: $minHeight;
box-shadow: 0 0 35px 0 rgba(0, 0, 0, 0.08);
font-size: 14px;
// 主题----------------------------------------
// 圆角主题
&[theme="radius"] {
border-radius: 8px;
box-shadow: 0 0 35px 0 rgba(0, 0, 0, 0.08), 0px -5px 0 0 #409eff;
&::before {
content: none;
}
.header {
margin-top: revert;
}
}
// 上传主题
&[theme="upload"] {
border-radius: 8px;
border: 1px solid #172d4533;
box-shadow: 0 4px 6px 0 #172d4533;
&::before {
content: none;
}
.header {
margin-top: revert;
}
}
// 右上角按钮----------------------------------------
// 最大化
&[size="lg"] {
left: 0 !important;
top: 0 !important;
width: 100vw !important;
height: 100vh !important;
}
// 最大化(左侧)
&[size="lgl"] {
left: 0 !important;
top: 0 !important;
width: calc(100vw / 2) !important;
height: 100vh !important;
}
// 最大化(右侧)
&[size="lgr"] {
left: calc(100vw / 2) !important;
top: 0 !important;
width: calc(100vw / 2) !important;
height: 100vh !important;
}
// 还原
&[size="md"] {
}
// 最小化
&[size="mn"] {
left: revert !important;
top: revert !important;
right: $defaultRight !important;
bottom: $defaultBottom !important;
width: $minWidth !important;
height: $minHeight !important;
}
// 右下角
&[size="rb"] {
left: revert !important;
top: revert !important;
right: $defaultRight !important;
bottom: $defaultBottom !important;
}
&::before {
content: "";
width: 100%;
height: 5px;
/*从左往右线性渐变背景*/
background: linear-gradient(to right, #409eff, #f56c6c);
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.header {
margin-top: 5px;
flex-shrink: 0;
font-size: 16px;
font-weight: bold;
width: 100%;
height: $headerHeight;
box-sizing: border-box;
padding: 10px 20px;
/*从上往下线性渐变背景*/
background: linear-gradient(#409eff11, white);
color: #409eff;
display: flex;
justify-content: space-between;
align-items: center;
&:hover {
background: linear-gradient(#409eff22, white);
}
.left {
display: flex;
align-items: center;
flex-grow: 1;
.title {
display: flex;
align-items: center;
flex-wrap: nowrap;
}
.icon-btns {
display: flex;
align-items: center;
flex-wrap: nowrap;
.icon-btn {
cursor: pointer;
margin-right: 5px;
&:last-of-type {
margin-right: 0;
}
i {
pointer-events: none;
}
&:hover {
opacity: 0.618;
}
}
}
}
.right {
display: flex;
align-items: center;
justify-content: flex-end;
flex-shrink: 0;
pointer-events: auto;
.icon-btn {
margin-left: 10px;
cursor: pointer;
i {
pointer-events: none;
}
&:hover {
opacity: 0.618;
}
&:first-of-type {
margin-left: 0;
}
}
.file-btns {
margin-left: 10px;
display: flex;
flex-wrap: nowrap;
justify-content: flex-end;
box-sizing: border-box;
padding: 0 10px;
border-right: 1px solid #eee;
}
.tray-btns {
margin-left: 10px;
display: flex;
flex-wrap: nowrap;
justify-content: flex-end;
}
}
}
.body {
box-sizing: border-box;
padding: 20px;
padding-top: 0;
display: flex;
flex-wrap: nowrap;
width: 100%;
height: calc(100% - #{$headerHeight});
}
}
</style>
demo
<template>
<!-- 指导帮助悬浮弹窗 -->
<sgFloatDialog
:data="data_sgFloatDialog"
v-model="show_sgFloatDialog"
v-if="show_sgFloatDialog"
@close="close"
>
<div
style="
width: 100%;
height: 100%;
overflow-y: auto;
font-size: 16px;
text-indent: 32px;
line-height: 1.6;
"
>
<p>
我一路向北,离开有你的季节,你说你好累,已无法再爱上谁。风在山路吹,过往的画面全都是不对,细数惭愧,我伤你几回。
</p>
<p>
我想我是太过依赖,在挂电话的刚才,坚持学单纯的小孩,静静看守这份爱,知道不能太依赖,怕你会把我宠坏,你的香味一直徘徊,我舍不得离开。
</p>
<p>
缓缓飘落的枫叶像思念,为何挽回要赶在冬天来之前,爱你穿越时间,两行来自秋末的眼泪,让爱渗透了地面我要的只是你在我身边。
</p>
<p>
我知道你我都没有错,只是忘了怎么退后,信誓旦旦给的承诺,全被时间扑了空。我知道我们都没有错,只是放手会比较好过,最美的爱情回忆里待续。
</p>
</div>
</sgFloatDialog>
</template>
<script>
import sgFloatDialog from "@/vue/components/admin/sgFloatDialog";
export default {
components: { sgFloatDialog },
data() {
return {
data_sgFloatDialog: {
// theme: `radius`,//主题
titleIcon: `el-icon-info`,
title: `标题`,
defaultRight: 10,
defaultBottom: 10,
defaultWidth: 400,
defaultHeight: 600,
},
show_sgFloatDialog: true,
};
},
methods: {
close(d) {
// console.log(``, d);
},
},
};
</script>
依赖
【sgDragMove】自定义组件:自定义拖拽组件,仅支持拖拽、设置吸附屏幕边界距离。_自定义拖拽位置-CSDN博客文章浏览阅读245次。sgDragMove是一个支持拖拽元素并能吸附到屏幕边界的Vue.js组件。它允许用户自定义吸附距离,可以设置拖拽行为是否禁用,以及停靠边界距离。组件在拖拽过程中提供了半透明效果,并能在不同阶段监听和处理鼠标事件以实现拖拽、吸附和停靠功能。https://blog.csdn.net/qq_37860634/article/details/131721634【sgDragSize】自定义组件:自定义拖拽修改DIV尺寸组件,适用于窗体大小调整_div拖拽调整大小-CSDN博客文章浏览阅读505次。核心原理就是在四条边、四个顶点加上透明的div,给不同方向提供按下移动鼠标监听 ,对应计算宽度高度、坐标变化。_div拖拽调整大小
https://blog.csdn.net/qq_37860634/article/details/132347222