特性:
- 任意拖拽到边界可以最大化、半屏放大
- 双击边界可以水平、纵向最大化
- 可以拖拽四边、四个顶点调整窗体尺寸
- 可以最大化、还原、最小化、关闭
- 支持双击标题栏最大化、还原
- 支持双击左上角图标关闭窗体
win7Window源码
html
<template>
<div :class="$options.name" :style="style" :size="size">
<div class="titlebar" ref="titlebar" @dblclick.stop.prevent="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" title="最小化">
<img src="static/img/desktop/icon/minimize.png">
</div>
<div class="maximize" v-if="size !== 'lg'" @click.stop="maximize" title="全屏">
<img src="static/img/desktop/icon/maximize.png">
</div>
<div class="maximize" v-if="size !== 'md'" @click.stop="restore_maximize" title="还原">
<img src="static/img/desktop/icon/restore-maximize.png">
</div>
<div class="close" @click.stop="close">
<img src="static/img/desktop/icon/close.png">
</div>
</div>
</div>
<div class="content">
<div class="path-bar">
<span class="back disabled"></span>
<div class="path">{{ path }}</div>
<input class="search" placeholder="输入关键词" v-model="searchValue" @keyup.enter="search">
</div>
<div class="files-container">
</div>
</DIV>
<!-- 拖拽到屏幕边界的透明层 -->
<div class="win7Window-opacity-bg" :style="win7WindowBgStyle" v-if="win7WindowBgStyle"></div>
<!-- 拖拽移动窗体 -->
<sgDragMove ref="sgDragMove" :data="dragMoveDoms" :grab="'default'" :grabbing="'move'" @dragStart="dragMoveStart"
@dragging="draggingMove" @dragEnd="dragMoveEnd" />
<!-- 拖拽改变窗体尺寸 -->
<sgDragSize :disabled="size === 'lg'" @dragging="d => style = d" :minWidth="400" :minHeight="200" />
</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 {
searchValue: '',
dragSizeDom: null,
dragMoveDoms: [
/* {
canDragDom: elementDOM,//可以拖拽的位置元素
moveDom: elementDOM,//拖拽同步移动的元素
} */
],//可以拖拽移动的物体
style_bk: null,
style: {
zIndex: 1,
height: '500px',
width: '800px',
left: '0',
top: '0',
},
dragStartPoint: null,
win7WindowBgStyle: {},
size: 'md',//lg全屏、md普通、mn最小
}
},
props: [
"initStyle",
"title",
"path",
],
watch: {
initStyle: {
handler(d) {
if (d && Object.keys(d).length) {
this.style = d;
} else {
this.style.left = `${(innerWidth - parseFloat(this.style.width)) / 2}px`;
this.style.top = `${(innerHeight - parseFloat(this.style.height)) / 2}px`;
}
}, deep: true, immediate: true,
},
size: {
handler(d) {
if (d === 'md') {
this.$nextTick(() => {
let rect = this.$el.getBoundingClientRect();
if (this.style
&& this.style_bk
&& (rect.x < 0 || rect.y < 0)
) {
this.style = null;
this.$nextTick(() => {
this.style = JSON.parse(JSON.stringify(this.style_bk));
});
}
});
}
}, deep: true, immediate: true,
},
},
created() {
},
mounted() {
this.dragSizeDom = this.$el;//拖拽设置大小的元素
this.dragMoveDoms = [
{
canDragDom: this.$refs.titlebar,//窗体标题栏可以拖拽
moveDom: this.$el,//拖拽的时候,整个窗体一起跟随移动
}
];
},
computed: {
},
methods: {
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) {
this.size = 'mn';
},
maximize(d) {
this.size = 'lg';
this.style = null;
this.storeOriginStyle();
this.$nextTick(() => {
this.style = {
left: `0px`,
top: `0px`,
width: `${innerWidth}px`,
height: `${innerHeight}px`,
transition: '.1s',
};
});
},
restore_maximize(d) {
this.size = 'md';
this.style = null;
this.$nextTick(() => {
this.style = JSON.parse(JSON.stringify(this.style_bk))
});
},
storeOriginStyle() {
let rect = this.$el.getBoundingClientRect();
if (rect.y > 0 && rect.width < innerWidth && rect.height < innerHeight) {
this.style_bk = {
left: `${rect.x}px`,
top: `${rect.y}px`,
width: `${rect.width}px`,
height: `${rect.height}px`,
}
}
},
storeDragStartPoint({ x, y }) {
let rect = this.$el.getBoundingClientRect();
if (rect.width >= innerWidth || rect.height >= innerHeight) {
this.dragStartPoint = { x };
}
},
dragMoveStart({ $event: { x, y } }) {
this.storeOriginStyle();
this.storeDragStartPoint({ x, y });
},
draggingMove({ $event: { x, y } }) {
let dis = 20;
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: `${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: `${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: `${innerHeight - dis}px`,
opacity: .5,
transition: '.1s',
};
} else {
// 拖拽回到浏览器中间
this.size = 'md';
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 = {
left: `0px`,
top: `0px`,
width: `${rect.width}px`,
height: `${rect.height}0px`,
opacity: 0,
transition: 'none',
})
}
},
dragMoveEnd({ $event: { x, y } }) {
let dis = 20;
if (y <= (dis / 2)) {
// 拖拽到浏览器顶部
this.maximize()
}
else if (x <= 0) {
// 拖拽到浏览器左侧
this.style = null;
this.$nextTick(() => {
this.style = {
left: `0px`,
top: `0px`,
width: `${innerWidth / 2}px`,
height: `${innerHeight}px`,
transition: '.1s',
};
});
}
else if (x >= innerWidth - (dis / 2)) {
// 拖拽到浏览器右侧
this.style = null;
this.$nextTick(() => {
this.style = {
left: `${innerWidth / 2}px`,
top: `0px`,
width: `${innerWidth / 2}px`,
height: `${innerHeight}px`,
transition: '.1s',
};
});
} else {
// 拖拽回到浏览器中间
}
// 防止窗口拖拽到最底部,标题栏不见了,没办法再把窗体拖回来
this.$nextTick(() => {
let rect = this.$el.getBoundingClientRect();
let maxY = innerHeight - 32;
if (rect.y > maxY) {
this.style.top = 'revet';
this.$nextTick(() => {
this.style.top = `${maxY}px`;
});
}
});
this.win7WindowBgStyle = null;
},
emit(d) {
this.$emit(`input`, d);
},
}
};
</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;
font-size: 15px;
min-width: $minWidth;
min-height: $minHeight;
opacity: 0.9;
}
.win7Window {
@extend .win7Window-bg;
// resize: both;
transition: none;
&[focused] {
opacity: 1;
}
.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;
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/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: 15px;
border: solid 1px rgba(0, 0, 0, 0.5);
background: rgba(255, 255, 255, 1);
border-radius: 2px;
}
.search {
height: 24px;
line-height: 24px;
padding: 0 5px;
font-size: 15px;
border: solid 1px rgba(0, 0, 0, 0.5);
background: rgba(255, 255, 255, 1);
border-radius: 2px;
}
}
.files-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;
}
}
.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>
应用
html
<template>
<div :class="$options.name">
<win7Window :initStyle="initStyle" :title="title" focused />
</div>
</template>
<script>
import win7Window from "@/vue/components/admin/win7Window";
export default {
name: 'demo',
components: {
win7Window
},
data() {
return {
title: {
label: '窗体标题',
iconURL: 'static/img/desktop/icon/computer.png',//窗体图标
},
// 窗体初始位置
initStyle: {
/*zIndex: 1,
height: '345px',
width: '619px',
left: '380px',
top: '133px', */
},
}
},
props: ["data", "value"],
watch: {
value: {
handler(d) {
}, deep: true, immediate: true,
},
},
created() {
},
mounted() {
},
computed: {
},
methods: {
emit(d) {
this.$emit(`input`, d);
},
}
};
</script>
<style lang="scss" scoped>
.demo {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
/*背景图片*/
background: transparent url("/static/img/desktop/bg1.jpg") no-repeat center / cover;
}
</style>