特性:
- 任意拖拽到边界可以最大化、半屏放大
- 双击边界可以水平、纵向最大化
- 可以拖拽四边、四个顶点调整窗体尺寸
- 可以最大化、还原、最小化、关闭
- 支持双击标题栏最大化、还原
- 支持双击左上角图标关闭窗体
win7Window源码
<template>
<div :class="$options.name" :style="style" :size="size">
<div class="titlebar" ref="titlebar" @dblclick.stop.prevent="resizeable_ ? dblclickHeader() : ''">
<div class="title">
<img :src="title.iconURL" @mousedown.stop @dblclick.stop.prevent="dblclickTitleIcon">
<label>{
{ title.label }}</label>
</div>
<div class="buttons" @mousedown.stop @dblclick.stop>
<div class="minimize" @click.stop="minimize(); $emit(`minimize`, style);" title="最小化">
<img src="static/img/desktop/Windows7/icon/minimize.png">
</div>
<div class="maximize" v-show="resizeable_" v-if="size !== 'lg'" @click.stop="maximize" title="全屏">
<img src="static/img/desktop/Windows7/icon/maximize.png">
</div>
<div class="maximize" v-show="resizeable_" v-if="size !== 'md'" @click.stop="restore_maximize" title="还原">
<img src="static/img/desktop/Windows7/icon/restore-maximize.png">
</div>
<div class="close" @click.stop="close">
<img src="static/img/desktop/Windows7/icon/close.png">
</div>
</div>
</div>
<div class="content">
<div class="path-bar" v-if="!hidPathbar">
<span class="back disabled"></span>
<div class="path">C:\root\{
{ path }}</div>
<input class="search" placeholder="输入关键词" v-model="searchValue" @keyup.enter="search">
</div>
<div class="container">
<slot></slot>
</div>
</DIV>
<!-- 拖拽到屏幕边界的透明层 -->
<div class="win7Window-opacity-bg" :style="win7WindowBgStyle" v-if="win7WindowBgStyle"></div>
<!-- 拖拽移动窗体 -->
<sgDragMove ref="sgDragMove" :data="dragMoveDoms" :grab="'default'" :grabbing="'default'" @dragStart="dragMoveStart"
@dragging="draggingMove" @dragEnd="dragMoveEnd" />
<!-- 拖拽改变窗体尺寸 -->
<sgDragSize v-if="resizeable_" :disabled="size === 'lg'" @dragStart="dragSizeStart" @dragging="draggingSize"
@dragEnd="dragSizeEnd" :minWidth="400" :minHeight="200" :taskbarHeight="taskbarHeight" />
</div>
</template>
<script>
import sgDragMove from "@/vue/components/admin/sgDragMove";
import sgDragSize from "@/vue/components/admin/sgDragSize";
export default {
name: 'win7Window',
components: {
sgDragMove,
sgDragSize,
},
data() {
return {
resizeable_: true,
searchValue: '',
dragSizeDom: null,
dragMoveDoms: [
/* {
canDragDom: elementDOM,//可以拖拽的位置元素
moveDom: elementDOM,//拖拽同步移动的元素
} */
],//可以拖拽移动的物体
default_style: {
height: '600px',
width: '1200px',
/* left: '0',
top: '0', */
},
style_bk: null,
style: {},
dragStartPoint: null,
win7WindowBgStyle: {},
size_bk: 'md',//lg全屏、md普通、mn最小(副本)
size: 'md',//lg全屏、md普通、mn最小
tbHeight: 0,
title: '',
path: '',
win7Window_opacity_bg_margin: 20,//透明放大图层(当窗体要全屏、半全屏的时候)边界距离
windowTitlebarHeight: 32,//32px是窗体标题栏高度
miniWindowSideDis: 32,//窗体出现在浏览器内侧最小宽度高度
}
},
props: [
"hidPathbar",//隐藏路径搜索栏
"initStyle",
"data",
"mini",
"taskbarHeight",//任务栏高度
"zIndex",
"resizeable",
],
watch: {
data: {
handler(newValue, oldValue) {
if (newValue && Object.keys(newValue).length) {
this.title = {
label: newValue.label,
iconURL: `static/img/desktop/Windows7/icon/${newValue.path}.png`,
};
this.path = newValue.path;
}
},
deep: true,//深度监听
immediate: true,//立即执行
},
taskbarHeight: {
handler(d) {
this.tbHeight = d || 0;
}, deep: true, immediate: true,
},
mini: {
handler(newValue, oldValue) {
if (newValue) {
this.minimize();
} else {
this.size_bk === 'lg' && this.maximize();
this.size_bk === 'md' && this.restore_maximize();
}
},
deep: true,//深度监听
immediate: true,//立即执行
},
zIndex: {
handler(newValue, oldValue) {
if (this.style && Object.keys(this.style).length) {
this.style.zIndex = newValue;
}
},
deep: true,//深度监听
immediate: true,//立即执行
},
resizeable: {
handler(newValue, oldValue) {
this.resizeable_ = typeof newValue === 'undefined' || newValue;
},
deep: true,//深度监听
immediate: true,//立即执行
},
},
created() {
},
mounted() {
this.dragSizeDom = this.$el;//拖拽设置大小的元素
this.dragMoveDoms = [
{
canDragDom: this.$refs.titlebar,//窗体标题栏可以拖拽
moveDom: this.$el,//拖拽的时候,整个窗体一起跟随移动
}
];
this.init(this.initStyle);
},
computed: {
},
methods: {
view_innerHeight() {
return innerHeight - this.tbHeight;
},
init(d) {
this.style = d;
if (d && Object.keys(d).length) {
} else {
this.style = JSON.parse(JSON.stringify(this.default_style));
}
let style = this.getInitStyle(this.style);
this.style = null;
this.$nextTick(() => {
this.style = style;
this.initSize(this.style);
});
},
getInitStyle(style) {
if (parseFloat(style.width) === innerWidth / 2 || parseFloat(style.width) === innerWidth) {
} else {
style.hasOwnProperty('left') || (style.left = `${(innerWidth - parseFloat(style.width)) / 2}px`);
}
if (parseFloat(style.height) < this.view_innerHeight()) {
style.hasOwnProperty('top') || (style.top = `${(this.view_innerHeight() - parseFloat(style.height)) / 2}px`);
}
style.zIndex = this.zIndex;
return JSON.parse(JSON.stringify(style));
},
initSize(style) {
let newValue = style;
if (newValue && Object.keys(newValue).length) {
let left = parseFloat(newValue.left);
let top = parseFloat(newValue.top);
let width = parseFloat(newValue.width);
let height = parseFloat(newValue.height);
if (left === 0 && top === 0 && width === innerWidth && height === this.view_innerHeight()) {
this.size = 'lg';
this.storeOriginStyle(this.getInitStyle(this.default_style));
} else {
this.storeOriginStyle(newValue);
}
}
},
search(d) {
this.$emit(`search`, this.searchValue);
},
dblclickHeader(d) {
switch (this.size) {
case 'lg':
this.restore_maximize();
break;
case 'md':
this.maximize();
break;
}
},
changeSize(d) {
this.size = d
switch (this.size) {
case 'lg':
this.maximize();
break;
case 'md':
this.restore_maximize();
break;
}
},
dblclickTitleIcon(d) {
this.close(d);
},
close(d) {
this.$emit(`close`, d);
},
minimize(d) {
if (this.size !== 'mn') {
this.size_bk = this.size;
this.size = 'mn';
}
},
maximize(d) {
this.storeOriginStyle();
this.size = 'lg';
this.style = null;
this.$nextTick(() => {
this.style = {
left: `0px`,
top: `0px`,
width: `${innerWidth}px`,
height: `${this.view_innerHeight()}px`,
transition: '.1s',
zIndex: this.zIndex,
};
});
},
restore_maximize(d) {
this.size = 'md';
this.style = null;
this.$nextTick(() => {
this.style = JSON.parse(JSON.stringify(this.style_bk));
this.style && (this.style.zIndex = this.zIndex);
this.dragStartPoint = null;
});
},
storeOriginStyle(style) {
// 只记录窗体在浏览器内部的位置
let rect = {};
if (style) {
rect = {
x: parseFloat(style.left),
y: parseFloat(style.top),
width: parseFloat(style.width),
height: parseFloat(style.height),
}
} else {
rect = this.$el.getBoundingClientRect();
}
if (rect.y > 0 && rect.width <= innerWidth && rect.height <= this.view_innerHeight()) {
this.style_bk = {
left: `${rect.x}px`,
top: `${rect.y}px`,
width: `${rect.width}px`,
height: `${rect.height}px`,
zIndex: this.zIndex,
}
}
},
storeCurrentStyle(style) {
// 只记录窗体在浏览器内部的位置
if (style) {
style = JSON.parse(JSON.stringify(style))
} else {
let rect = this.$el.getBoundingClientRect();
rect.x < -rect.width + this.miniWindowSideDis && (rect.x = -rect.width + this.miniWindowSideDis);//防止记录窗体顶部超出浏览器左边界
rect.x > innerWidth - this.miniWindowSideDis && (rect.x = innerWidth - this.miniWindowSideDis);//防止记录窗体顶部超出浏览器右边界
rect.y < 0 && (rect.y = 0);//防止记录窗体顶部超出浏览器上边界
rect.y > this.view_innerHeight() - this.miniWindowSideDis && (rect.y = this.view_innerHeight() - this.miniWindowSideDis);//防止记录窗体顶部超出浏览器下边界
style = {
left: `${rect.x}px`,
top: `${rect.y}px`,
width: `${rect.width}px`,
height: `${rect.height}px`,
zIndex: this.zIndex,
}
}
this.$emit(`getCurrentStyle`, style);
},
storeDragStartPoint({ x, y }) {
let rect = this.$el.getBoundingClientRect();
if (rect.y === 0 && rect.height === this.view_innerHeight()) {
this.dragStartPoint = { x };
}
},
// 拖拽修改尺寸----------------------------------------
dragSizeStart(e) {
this.$emit('dragSizeStart', e);
},
draggingSize({ e, style }) {
this.style = style;
this.style.zIndex = this.zIndex;
let { x, y } = e;
let dis = this.win7Window_opacity_bg_margin;
let rect = this.$el.getBoundingClientRect();
if (y <= 0 || y >= this.view_innerHeight()) {
// 修改窗口尺寸时,鼠标到达顶部或底部边界
this.win7WindowBgStyle = {
left: `0px`,
top: `${-rect.y}px`,
width: `${rect.width}px`,
height: `${this.view_innerHeight()}px`,
opacity: .5,
transition: 'opacity .2s,height .2s',
};
} else {
this.win7WindowBgStyle && this.win7WindowBgStyle.opacity != 0 && (this.win7WindowBgStyle = {
left: `0px`,
top: `0px`,
width: `${rect.width}px`,
height: `${rect.height}0px`,
opacity: 0,
transition: 'opacity .2s,height .2s',
})
}
},
dragSizeEnd(e) {
let { x, y } = e;
let rect = this.$el.getBoundingClientRect();
if (y <= 0 || y >= this.view_innerHeight()) {
// 结束修改窗口尺寸时,鼠标到达顶部或底部边界
this.style = null;
this.$nextTick(() => {
this.style = {
left: `${rect.x}px`,
top: `0px`,
width: `${rect.width}px`,
height: `${this.view_innerHeight()}px`,
transition: '.1s',
zIndex: this.zIndex,
};
this.storeOriginStyle(this.style);
this.storeCurrentStyle(this.style);
});
} else {
this.storeOriginStyle(this.style);
this.storeCurrentStyle(this.style);
}
this.win7WindowBgStyle = null;
},
// 拖拽移动窗体----------------------------------------
dragMoveStart(e) {
if (this.resizeable_) {
let { x, y } = e.$event;
this.storeOriginStyle();
this.storeDragStartPoint({ x, y });
}
this.$emit(`dragMoveStart`, e);
},
draggingMove(e) {
if (this.resizeable_) {
let { x, y } = e.$event;
let dis = this.win7Window_opacity_bg_margin;
let rect = this.$el.getBoundingClientRect();
if (y <= (dis / 2)) {
// 拖拽到浏览器顶部
this.win7WindowBgStyle = {
left: `${-rect.x + (dis / 2)}px`,
top: `${-rect.y + (dis / 2)}px`,
width: `${innerWidth - dis}px`,
height: `${this.view_innerHeight() - dis}px`,
opacity: .5,
transition: '.1s',
};
}
else if (x <= 0) {
// 拖拽到浏览器左侧
this.win7WindowBgStyle = {
left: `${-rect.x + (dis / 2)}px`,
top: `${-rect.y + (dis / 2)}px`,
width: `${innerWidth / 2 - dis}px`,
height: `${this.view_innerHeight() - dis}px`,
opacity: .5,
transition: '.1s',
};
}
else if (x >= innerWidth - (dis / 2)) {
// 拖拽到浏览器右侧
this.win7WindowBgStyle = {
left: `${-((innerWidth / 2) - (innerWidth - rect.x)) + (dis / 2)}px`,
top: `${-rect.y + (dis / 2)}px`,
width: `${innerWidth / 2 - dis}px`,
height: `${this.view_innerHeight() - dis}px`,
opacity: .5,
transition: '.1s',
};
} else {
// 拖拽回到浏览器中间
this.size = 'md';
if (this.style_bk) {
this.style.width = this.style_bk.width;
this.style.height = this.style_bk.height;
}
if (this.dragStartPoint) {
let leftPointDis = (this.dragStartPoint.x / innerWidth) * parseFloat(this.style.width);//计算鼠标到左上角定点的距离
this.$refs.sgDragMove.setOffset({ x: leftPointDis });
this.dragStartPoint = null;
}
this.win7WindowBgStyle && this.win7WindowBgStyle.opacity != 0 && (this.win7WindowBgStyle = {
left: `0px`,
top: `0px`,
width: `${rect.width}px`,
height: `${rect.height}0px`,
opacity: 0,
transition: '.1s',
})
}
this.storeCurrentStyle();
}
this.$emit(`draggingMove`, e);
},
dragMoveEnd(e) {
let { x, y } = e.$event;
let dis = this.win7Window_opacity_bg_margin;
if (y <= (dis / 2) && this.resizeable_) {
// 拖拽到浏览器顶部
this.maximize()
}
else if (x <= 0 && this.resizeable_) {
// 拖拽到浏览器左侧
this.style = null;
this.$nextTick(() => {
this.style = {
left: `0px`,
top: `0px`,
width: `${innerWidth / 2}px`,
height: `${this.view_innerHeight()}px`,
transition: '.1s',
zIndex: this.zIndex,
};
this.storeOriginStyle(this.style);
});
}
else if (x >= innerWidth - (dis / 2) && this.resizeable_) {
// 拖拽到浏览器右侧
this.style = null;
this.$nextTick(() => {
this.style = {
left: `${innerWidth / 2}px`,
top: `0px`,
width: `${innerWidth / 2}px`,
height: `${this.view_innerHeight()}px`,
transition: '.1s',
zIndex: this.zIndex,
};
this.storeOriginStyle(this.style);
});
} else {
// 拖拽回到浏览器中间
// 防止窗口拖拽到最底部,标题栏不见了,没办法再把窗体拖回来
this.$nextTick(() => {
let rect = this.$el.getBoundingClientRect();
let maxY = this.view_innerHeight() - this.windowTitlebarHeight;
if (rect.y > maxY) {
this.style.top = 'revet';
this.$nextTick(() => {
this.style.top = `${maxY}px`;
this.storeOriginStyle(this.style);
});
} else {
this.storeOriginStyle();
}
});
}
this.win7WindowBgStyle = null;
this.$emit(`dragMoveEnd`, e);
},
}
};
</script>
<style lang="scss" scoped>
$width: 600px;
$minWidth: 400px;
$minHeight: 200px;
// ----------------------------------------
.win7Window-bg {
user-select: none;
position: absolute;
background: linear-gradient(180deg, rgba(250, 250, 250, 0.1) 0%, rgba(255, 255, 255, 0.4) 30%, rgba(70, 70, 70, 0.2) 40%, rgba(170, 170, 170, 0.1) 100%);
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.8), 0 0 8px 3px rgba(10, 10, 10, 0.8);
backdrop-filter: blur(7px) brightness(0.9);
border-radius: 8px;
display: flex;
flex-direction: column;
min-width: $minWidth;
min-height: $minHeight;
opacity: 0.9;
backdrop-filter: blur(4px);
box-shadow:
0 0 0 1px rgba(255, 255, 255, 0.9),
0 0 8px 3px rgba(10, 10, 10, 0.4);
}
.win7Window {
@extend .win7Window-bg;
pointer-events: auto;
&[focused] {
opacity: 1;
backdrop-filter: blur(7px) brightness(0.9);
box-shadow:
0 0 0 1px rgba(255, 255, 255, 0.8),
0 0 8px 3px rgba(10, 10, 10, 0.8);
}
.titlebar {
height: 32px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 0 10px;
opacity: 1;
.title {
flex-grow: 1;
img {
width: 20px;
height: 20px;
margin-right: 5px;
transform: translateY(-2px);
vertical-align: middle;
cursor: default;
}
label {
text-shadow: 0 0 5px rgba(255, 255, 255, 1), 0 0 5px rgba(255, 255, 255, 1), 0 0 5px rgba(255, 255, 255, 1), 0 0 5px rgba(255, 255, 255, 1);
flex-grow: 1;
height: 100%;
line-height: 32px
}
}
.buttons {
flex-shrink: 0;
display: flex;
flex-direction: row;
height: 19px;
align-self: flex-start;
box-shadow: 0 0 3px rgba(255, 255, 255, 0.4), 0 0 0 1px rgba(0, 0, 0, 0.5), inset 0 0 1px 2px rgba(255, 255, 255, 0.4);
border-top: none;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
overflow: hidden;
&>* {
cursor: default;
text-align: center;
line-height: 19px;
font-size: 12px;
font-weight: bold;
color: #fff;
width: 28px;
background: linear-gradient(180deg, rgba(200, 200, 200, 0.5) 0%, rgba(200, 200, 200, 0.5) 40%, rgba(110, 110, 110, 0.4) 60%, rgba(150, 150, 150, 0.4) 90%, rgba(180, 180, 180, 0.4) 100%);
backdrop-filter: grayscale(1);
transition: .1s;
img {
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 1));
}
&:hover {
filter: brightness(1.3);
}
&:active {
filter: brightness(0.9);
}
}
.minimize {
border-right: solid 1px rgba(0, 0, 0, 0.4);
img {
height: 13px;
margin-top: 2px;
}
}
.maximize {
border-right: solid 1px rgba(0, 0, 0, 0.4);
img {
height: 11px;
margin-top: 3px;
}
}
.close {
background: linear-gradient(180deg, rgba(255, 107, 63, 0.8) 0%, rgba(212, 116, 91, 0.7) 40%, rgba(192, 55, 44, 0.9) 55%, rgba(204, 33, 33, 0.7) 80%, rgba(255, 125, 125, 0.9) 100%);
width: 46px;
img {
height: 13px;
margin-top: 2px;
}
}
}
}
.content {
flex-grow: 1;
border: solid 1px rgba(10, 10, 10, 0.5);
overflow: hidden;
margin: 0 5px 5px 5px;
max-width: calc(100% - 10px);
border: 0;
display: flex;
flex-direction: column;
.path-bar {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: nowrap;
margin-bottom: 6px;
.back {
background-image: url('/static/img/desktop/Windows7/icon/back.png');
width: 32px;
height: 32px;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
margin-right: 5px;
transition: .1s;
.disabled {
filter: grayscale(1);
}
&:hover {
filter: brightness(1.2);
}
&:active {
filter: brightness(0.8);
}
}
.path {
flex-grow: 1;
color: rgba(0, 0, 0, 1);
margin-right: 5px;
height: 24px;
line-height: 24px;
padding: 0 5px;
font-size: 14px;
border: solid 1px rgba(0, 0, 0, 0.5);
background: rgba(255, 255, 255, 1);
border-radius: 2px;
font-weight: normal;
}
.search {
height: 24px;
line-height: 24px;
padding: 0 5px;
font-size: 14px;
border: solid 1px rgba(0, 0, 0, 0.5);
background: rgba(255, 255, 255, 1);
border-radius: 2px;
}
}
.container {
position: relative;
flex-grow: 1;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: flex-start;
justify-content: flex-start;
align-content: flex-start;
width: 100%;
height: 100%;
overflow: auto;
background: rgba(255, 255, 255, 1);
flex-grow: 1;
position: relative;
overflow: hidden;
}
}
.win7Window-opacity-bg {
@extend .win7Window-bg;
position: fixed;
left: 0;
top: 0;
width: 0;
height: 0;
z-index: -1;
opacity: 0;
pointer-events: none;
background: #ffffff33;
}
&[size="lg"] {}
&[size="md"] {}
&[size="mn"] {
display: none;
}
}
</style>
该组件还需要用到以下几个自定义组件:
【sgDragMove】自定义拖拽组件,仅支持拖拽、设置吸附屏幕边界距离_你挚爱的强哥的博客-CSDN博客【sgDragMove】自定义拖拽组件,仅支持拖拽、设置吸附屏幕边界距离。https://blog.csdn.net/qq_37860634/article/details/131721634【sgDragSize】自定义拖拽修改DIV尺寸组件,适用于窗体大小调整_你挚爱的强哥的博客-CSDN博客核心原理就是在四条边、四个顶点加上透明的div,给不同方向提供按下移动鼠标监听 ,对应计算宽度高度、坐标变化。https://blog.csdn.net/qq_37860634/article/details/132347222
应用
<template>
<div :class="$options.name">
<win7Window :initStyle="initStyle" :data="data" :path="'C:\\'" focused />
</div>
</template>
<script>
import win7Window from "@/vue/components/admin/win7Window";
export default {
name: 'demoWindows7',
components: { win7Window },
data() {
return {
data: {
label: '窗体标题',
path: 'all',//窗体图标
},
// 窗体初始位置
initStyle: {
/*zIndex: 1,
height: '345px',
width: '619px',
left: '380px',
top: '133px', */
},
}
},
};
</script>
<style lang="scss" scoped>
.demoWindows7 {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
/*背景图片*/
background: transparent url("/static/img/desktop/Windows7/bg1.jpg") no-repeat center / cover;
}
</style>