【win7Window】高仿Windows7系统窗体

特性:

  1. 任意拖拽到边界可以最大化、半屏放大
  2. 双击边界可以水平、纵向最大化
  3. 可以拖拽四边、四个顶点调整窗体尺寸
  4. 可以最大化、还原、最小化、关闭
  5. 支持双击标题栏最大化、还原
  6. 支持双击左上角图标关闭窗体

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>
相关推荐
m0_7482550215 分钟前
前端常用算法集合
前端·算法
真的很上进29 分钟前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
web1309332039835 分钟前
vue elementUI form组件动态添加el-form-item并且动态添加rules必填项校验方法
前端·vue.js·elementui
NiNg_1_2341 小时前
Echarts连接数据库,实时绘制图表详解
前端·数据库·echarts
如若1232 小时前
对文件内的文件名生成目录,方便查阅
java·前端·python
滚雪球~2 小时前
npm error code ETIMEDOUT
前端·npm·node.js
沙漏无语2 小时前
npm : 无法加载文件 D:\Nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本
前端·npm·node.js
supermapsupport2 小时前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap
brrdg_sefg2 小时前
WEB 漏洞 - 文件包含漏洞深度解析
前端·网络·安全
胡西风_foxww3 小时前
【es6复习笔记】rest参数(7)
前端·笔记·es6·参数·rest