【vue2静态复古win95简历网页】模仿电脑桌面实现

文章描述了一个Vue.js应用程序中如何管理桌面图标的窗口状态,包括窗口ID、位置、内容和全屏状态等信息,使用Vuex进行状态存储和更新,通过组件动态渲染窗口内容,并处理窗口的打开、关闭和最小化操作。此外,还涉及到了窗口尺寸变化时的响应式布局调整。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

整体效果:

在这里插入图片描述
桌面图标的状态存在了store/index.js中定义的windows列表中:

    windows: [
      {
        windowId: "BiographyWindow", // 窗口ID
        windowState: "close", // 窗口状态[open, close, minimize]
        displayName: "Biography", // 展示名字(title under icon)
        windowComponent: 'window', // 窗口组件 (can be changed to use modified windows)
        windowContent: 'bio', //窗口内容(used under slots)
        windowContentPadding: {
          top: null,
          right: null,
          bottom: null,
          left: null
        }, // 窗口内容边距
        position: "absolute", //窗口位置
        positionX: "5vw", // Window Position X (when first opened)
        positionY: "5%", // Window Position Y (when first opened)
        iconImage: "bio.png", // Window Icon Image
        altText: "Biography", // Window Icon Alt Text
        fullscreen: false //窗口全屏状态 [true, false]
      },
      {
        windowId: "ResumeWindow", // Unique ID
        windowState: "close", // Window State [open, close, minimize]
        displayName: "Résumé", // Display Name (title under icon)
        windowComponent: 'window', // Window Component (can be changed to use modified windows)
        windowContent: 'resume', // Window Content (used under slots)
        windowContentPadding: {
          top: '0',
          right: '0',
          bottom: '0',
          left: '0'
        }, // Window Content Padding 
        position: "absolute", // Window Position
        positionX: "10vw", // Window Position X (when first opened)
        positionY: "15vh", // Window Position Y (when first opened)
        iconImage: "resume.png", // Window Icon Image
        altText: "Résumé", // Window Icon Alt Text
        fullscreen: false // Window Fullscreen State [true, false]
      },
      {
        windowId: "PhotosWindow", 
          windowState: "close",
          displayName: "Photos",
          windowComponent: 'window',
          windowContent: 'photos',
          windowContentPadding: {
            top: '10px',
            right: '10px',
            bottom: '10px',
            left: '10px'
          },
          position: "absolute",
          positionX: "6vw",
          positionY: "12vh",
          iconImage: "photos.png",
          altText: "Photos",
          fullscreen: false
      },
      {
        windowId: "OpenSourceWindow", 
          windowState: "close",
          displayName: "Open Source",
          windowComponent: 'window',
          windowContent: 'opensource',
          windowContentPadding: {
            top: null,
            right: '15px',
            bottom: null,
            left: '15px'
          },
          position: "absolute",
          positionX: "6vw",
          positionY: "12vh",
          iconImage: "opensource.png",
          altText: "Open Source Projects",
          fullscreen: false
      },
      {
        windowId: "MailWindow", 
          windowState: "close",
          displayName: "Mail",
          windowComponent: 'mail',
          windowContent: '',
          windowContentPadding: {
            top: '0',
            right: '0',
            bottom: '0',
            left: '0'
          },
          position: "absolute",
          positionX: "6vw",
          positionY: "12vh",
          iconImage: "mail.png",
          altText: "Mail",
          fullscreen: false
      },

在App.vue中,通过v-for循环读取windows中的窗口数据,读取窗口专属信息以便于post到窗口中

        <div 
            v-for="window in windows" 
            :key="window.key" 
            :aria-label="window.displayName"
        >
            <component
                :is="window.windowComponent" 
                :nameOfWindow="window.windowId" 
                :content_padding_bottom="window.windowContentPadding['bottom']"
                :content_padding_left="window.windowContentPadding['left']"
                :content_padding_right="window.windowContentPadding['right']"
                :content_padding_top="window.windowContentPadding['top']"
                :id="window.windowId" 
                :style="{position: window.position, left: window.positionX, top: window.positionY}" 
                v-if="windowCheck(window.windowId)"
            >
                <component 
                    :is="window.windowContent"
                    slot="content"
                >
                </component>
            </component>
            <StartMenu v-if="$store.getters.getActiveWindow=='Menu'" style="position: absolute; z-index: 9999; bottom: 0; left: 0;"></StartMenu>
        </div>

body的背景风格使用css设置,overflow: hidden来创建BFC防止高度塌陷

body {
    margin: 0;
    padding: 0;
    overflow: hidden;
    background: #018281;
}

使用component 来表示单独的图标,读取存在store中的信息v-if="windowCheck(window.windowId)" 使用v-if来选择是否展示打开的单独窗口

小图标排列

使用<app-grid></app-grid>引入小图标
在AppGrid.vue中具体实现如下:

    <li v-for="window in windows" :key="window.key">
        <button class="icon" @touchstart="openWindow(window.windowId)" @dblclick="openWindow(window.windowId)">
            <img class="icon-image" :src="require('../assets/win95Icons/' + window.iconImage)" :alt="window.altText" />
            <div class="border">
            <p class="icon-text">
                {{window.displayName}}
            </p>
            </div>
        </button>
    </li>

使用li来包裹图标,设置成button点击响应类型,绑定了touchstart事件当手指触摸屏幕时候触发,即使已经有一个手指放在屏幕上也会触发 以及 dbclick双击事件

点击后调用openwindow函数,点击按钮后将setWindowState设为open

        openWindow(windowId) {
            const payload = {
                'windowState': 'open',
                'windowID': windowId
            }
            this.$store.commit('setWindowState', payload)
        },

在存储状态的store/index.js中整个流程可以描述为:
如果WindowState设为open,那么就激活窗口。先更新窗口状态,同时执行z-index递增函数让窗口置于最前,通过pushActiveWindow将激活的窗口push进激活队列。
如果WindowState设为close,先更新窗口状态,从激活队列中删除窗口,然后取消激活窗口。
如果WindowState设为minimize,先更新窗口状态,然后取消激活窗口。此时窗口还在队列中,只是不在桌面上显示。

  mutations: {
    // Active Window Mutator
    // 激活窗口
    setActiveWindow(state, window) {
      console.log(window);
      state.activeWindow = window
    },

    // 将激活的窗口push到活动窗口队列上
    pushActiveWindow(state, window) {
      state.activeWindows.push(window)
    },

    // 从激活窗口队列中删除窗口
    popActiveWindow(state, window) {
      const windowIndex = state.activeWindows.indexOf(window);
      if (windowIndex !== -1) {
        state.activeWindows.splice(windowIndex, 1)
      }
    },

    // Z-index increment function
    // 增加z-index,让窗口浮于顶层
    zIndexIncrement(state, windowID) {
      state.zIndex += 1
      document.getElementById(windowID).style.zIndex = state.zIndex
    },

    // Set height of max-height of fullscreen window
    // 设置全屏窗口的最大高度
    setFullscreenWindowHeight(state, height) {
      state.fullscreenWindowHeight = height
    },

    updateMail(state, local) {
      state.isShownMail = local
    },
    updateMailSubject(state, local) {
      state.mailSubject = local
    },
    updateMailContent(state, local) {
      state.mailContent = local
    },
    updateMailSender(state, local) {
      state.mailSender = local
    },

    setFullscreen(state, payload) {
      function getArrItem() {
        return state.windows.find(
          (windows) => windows.windowId === payload.windowID
        );
      }
      const window = getArrItem();
      window.fullscreen = payload.fullscreen;
    },

    // Window State Mutator
    setWindowState(state, payload) {
      // payload = {'windowState': 'open', 'windowID': 'WindowOne'}

      function getArrItem() {
        return state.windows.find(
          (windows) => windows.windowId === payload.windowID
        );
      }
      const window = getArrItem();

      var preventAppendingOpenWindow = false;
      if (window.windowState == "open" || window.windowState == "minimize") {
        preventAppendingOpenWindow = true;
      }

      if (payload.windowState == "open") {
        window.windowState = payload.windowState;
        setTimeout(() => {
          this.commit("zIndexIncrement", payload.windowID);
        }, 0);
        setTimeout(() => {
          this.commit("setActiveWindow", payload.windowID);
        }, 0);
        if (preventAppendingOpenWindow == false) {
          setTimeout(() => {
            this.commit("pushActiveWindow", window);
          }, 0);
        }
      } else if (payload.windowState == "close") {
        window.windowState = payload.windowState;
        setTimeout(() => {
          this.commit("popActiveWindow", window);
        }, 0);
        setTimeout(() => {
          this.commit("setActiveWindow", "nil"); 
        }, 0);
      } else if (payload.windowState == "minimize") {
        window.windowState = payload.windowState;
        this.commit("setActiveWindow", "nil"); 
      } else {
        console.log("Error: windowState not found or invalid");
      }
    },
  },
  //通过getters获得返回值
  getters: {
    // 获取激活窗口
    getActiveWindow: (state) => {
      return state.activeWindow;
    },

    // Window Getter
    getWindowById: (state) => (id) => {
      return state.windows.find((window) => window.windowId === id);
    },

    getWindowFullscreen: (state) => (id) => {
      return state.windows.find((window) => window.windowId === id).fullscreen;
    },

    getWindows: (state) => {
      return state.windows;
    },

    getActiveWindows(state) {
      return state.activeWindows;
    },

    getFullscreenWindowHeight(state) {
      return state.fullscreenWindowHeight;
    },

    mailContent: state => {
      return state.mailContent
    },

    mailSubject: state => {
      return state.mailSubject
    },

    mailSender: state => {
      return state.mailSender
    },
  },

setTimeout设置为0的意义,简单来说就是为了让setTimeout后的代码先于setTimeout的回调函数运行

监听window尺寸变化

在这里插入图片描述在这里插入图片描述

当整体窗口尺寸变化时,需要监听到resize状态对布局的style进行调整

        window.addEventListener('resize', () => {
            let vh = window.innerHeight * 0.01; //vh单位表示根元素高度的百分比,一个vh等于视口高度的1%。
            document.documentElement.style.setProperty('--vh', `${vh}px`);
            
        });

        function resetHeight() {
            document.body.style.height = window.innerHeight + "px";
            document.html.style.height = window.innerHeight + "px";
        }
        window.addEventListener("resize", resetHeight);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值