Vue-App桌面程序列表

文章说明

采用Vue实现PC端的桌面程序列表,采用HBuilderX将程序转化为5+App,实现移动端的适配;支持桌面打开新应用,底部导航展示当前应用列表,可切换或关闭应用

讲解视频

Vue-App桌面程序列表-系统运行演示视频

核心代码

桌面组件核心代码

<template>
  <div class="desktop-container">
    <div>
      <div v-for="item in data.appList" :key="item.id" class="single-app" @click="openApp(item)">
        <i :class="'icon-' + item.ico" :style="{ 'color' : item.color}" class="iconfont"></i>
        <div>{{ item.name }}</div>
      </div>
    </div>

    <div v-show="data.openApp.id" class="open-app">
      <div class="content">
        <iframe :src="data.openApp.path" style="height: 100%; width: 100%; border: none"></iframe>
      </div>
    </div>
  </div>
</template>

<script setup>
import {onBeforeMount, reactive} from "vue";
import {appList, BottomNavMessageType} from "@/util/constant";

const data = reactive({
  appList: [],
  openApp: {
    id: null,
    path: null,
  },
});

let bottomNavChannel;

onBeforeMount(() => {
  bottomNavChannel = new BroadcastChannel("bottomNav");
  data.appList = appList;

  bottomNavChannel.onmessage = event => {
    const transferData = event.data;
    if (transferData.type === BottomNavMessageType.activeOrMinimize) {
      data.openApp.id = transferData.content.id;
      data.openApp.path = transferData.content.path;
    } else if (transferData.type === BottomNavMessageType.toHome) {
      data.openApp.id = null;
      data.openApp.path = null;
    }
  };
});

let clickTime = 0;
const interval = 500;

function openApp(item) {
  if (new Date() - clickTime > interval) {
    clickTime = new Date();
    return;
  }

  data.openApp.id = item.id;
  data.openApp.path = item.path;

  bottomNavChannel.postMessage({
    type: BottomNavMessageType.openApp,
    content: {
      id: item.id,
      ico: item.ico,
      color: item.color,
      name: item.name,
      path: item.path,
    }
  });
  bottomNavChannel.postMessage({
    type: BottomNavMessageType.topApp,
    content: {
      id: item.id,
    }
  });
}
</script>

<style lang="scss" scoped>
.desktop-container {
  width: 100%;
  height: calc(100% - 3rem);
  background-image: url(../img/desktop.jpg);
  background-position: center center;
  background-repeat: no-repeat;
  background-attachment: fixed;
  background-size: cover;
  position: relative;
  overflow: hidden;
  padding: 0.5rem;

  .single-app {
    display: inline-flex;
    width: 5rem;
    height: 5rem;
    user-select: none;
    position: relative;

    &:hover {
      background-color: #94b5cd;
      cursor: default;
    }

    i::before {
      font-size: 3rem;
      position: absolute;
      left: 50%;
      top: 35%;
      transform: translateX(-50%) translateY(-50%);
    }

    div {
      width: 100%;
      padding: 0 0.5rem;
      text-align: center;
      font-size: 0.9rem;
      color: white;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      position: absolute;
      left: 50%;
      top: 80%;
      transform: translateX(-50%) translateY(-50%);
    }
  }

  .open-app {
    width: 100%;
    height: 100%;
    position: absolute;
    left: 0;
    top: 0;
    margin: 0;
    padding: 0;
    border: 0.1rem solid #b1b1b1;

    .content {
      position: relative;
      background-color: #ffffff;
      width: 100%;
      height: 100%;
    }
  }
}
</style>

底部导航组件核心代码

<template>
  <div class="bottom-menu-container">
    <div class="open-app-list">
      <div v-for="item in data.openAppList" :key="item.id"
           :class="data.openAppIdArray[data.openAppIdArray.length - 1] === item.id ? 'single-openApp-active' : ''"
           class="single-openApp"
           @click="activeOrMinimize(item)" @contextmenu.prevent="showCloseMenu(item)">
        <i :class="'icon-' + item.ico" :style="{ 'color' : item.color}" class="iconfont"></i>
        <div v-if="data.openAppIdArray[data.openAppIdArray.length - 1] === item.id" class="open"></div>
        <div v-if="data.openAppIdArray[data.openAppIdArray.length - 1] !== item.id" class="other"></div>

        <div v-if="item.showClose" class="close" @click.stop="closeApp(item)">关闭应用</div>
      </div>
    </div>
  </div>
</template>

<script setup>
import {onBeforeMount, reactive} from "vue";
import {BottomNavMessageType} from "@/util/constant";
import {message} from "@/util/util";

const data = reactive({
  openAppList: [],
  openAppIdArray: [],
});

let bottomNavChannel;

onBeforeMount(() => {
  listenBottomNavChannel();

  document.addEventListener("click", () => {
    for (let i = 0; i < data.openAppList.length; i++) {
      data.openAppList[i].showClose = false;
    }
  });
});

function listenBottomNavChannel() {
  bottomNavChannel = new BroadcastChannel("bottomNav");

  bottomNavChannel.onmessage = event => {
    const transferData = event.data;
    if (transferData.type === BottomNavMessageType.openApp) {
      let isOpen = false;
      for (let i = 0; i < data.openAppList.length; i++) {
        if (transferData.content.id === data.openAppList[i].id) {
          isOpen = true;
          break;
        }
      }
      if (!isOpen) {
        if (data.openAppList.length === 5) {
          message("warning", "最多打开5个应用");
          return;
        }
        data.openAppList.push(transferData.content);
      }
    } else if (transferData.type === BottomNavMessageType.topApp) {
      data.openAppIdArray = data.openAppIdArray.filter(id => id !== transferData.content.id);
      data.openAppIdArray.push(transferData.content.id);
      openCurrentApp();
    }
  };
}

function openCurrentApp() {
  if (data.openAppIdArray.length > 0) {
    const topId = data.openAppIdArray[data.openAppIdArray.length - 1];
    for (let i = 0; i < data.openAppList.length; i++) {
      if (topId === data.openAppList[i].id) {
        bottomNavChannel.postMessage({
          type: BottomNavMessageType.activeOrMinimize,
          content: {
            id: data.openAppList[i].id,
            path: data.openAppList[i].path
          }
        });
        break;
      }
    }
  } else {
    bottomNavChannel.postMessage({
      type: BottomNavMessageType.toHome
    });
  }
}

function activeOrMinimize(item) {
  if (item.id === data.openAppIdArray[data.openAppIdArray.length - 1]) {
    data.openAppIdArray = data.openAppIdArray.filter(id => id !== item.id);
    openCurrentApp();
  } else {
    data.openAppIdArray = data.openAppIdArray.filter(id => id !== item.id);
    data.openAppIdArray.push(item.id);
    openCurrentApp();
  }
}

function showCloseMenu(item) {
  for (let i = 0; i < data.openAppList.length; i++) {
    data.openAppList[i].showClose = false;
  }
  item.showClose = true;
}

function closeApp(item) {
  data.openAppIdArray = data.openAppIdArray.filter(id => id !== item.id);
  data.openAppList = data.openAppList.filter(app => app.id !== item.id);
  openCurrentApp();
}
</script>

<style lang="scss" scoped>
.bottom-menu-container {
  width: 100%;
  height: 3rem;
  background-color: #d4e0f7;
  border-top: 0.1rem solid #afb5c3;
  user-select: none;

  .open-app-list {
    margin: 0 auto;
    width: 20rem;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;

    .single-openApp {
      height: 2.5rem;
      width: 2.5rem;
      border-radius: 0.2rem;
      margin: 0 0.25rem;
      position: relative;

      &:hover {
        background-color: #ebf3fe;
        cursor: default;
      }

      .iconfont::before {
        font-size: 1.5rem;
        position: absolute;
        top: 45%;
        left: 50%;
        transform: translateX(-50%) translateY(-50%);
      }

      .open {
        position: absolute;
        bottom: 0;
        width: 1rem;
        height: 0.2rem;
        background-color: #0078d4;
        border-radius: 0.2rem;
        left: 50%;
        transform: translateX(-50%);
      }

      .other {
        position: absolute;
        bottom: 0;
        width: 0.4rem;
        height: 0.2rem;
        background-color: #777b85;
        border-radius: 0.2rem;
        left: 50%;
        transform: translateX(-50%);
      }

      .close {
        position: absolute;
        top: -2.5rem;
        left: 50%;
        width: 5rem;
        height: 2rem;
        line-height: 2rem;
        font-size: 0.8rem;
        text-align: center;
        transform: translateX(-50%);
        background-color: #d5dff1;
        color: black;
        z-index: 999;
        border: 0.1rem solid #c8c8c8;
        border-radius: 0.5rem;

        &:hover {
          background-color: #eeeeee;
        }
      }
    }

    .single-openApp-active {
      background-color: #ebf3fe;
      cursor: default;
    }
  }
}
</style>

配置的app程序列表

export const BottomNavMessageType = {
    openApp: "openApp",
    topApp: "topApp",
    activeOrMinimize: "activeOrMinimize",
    toHome: "toHome",
}

export const appList = [
    {
        id: 1,
        name: "页面1",
        ico: "market",
        color: "#1cd67c",
        path: "http://47.99.202.161:5000/index1.html",
    },
    {
        id: 2,
        name: "页面2",
        ico: "img",
        color: "#4c35d3",
        path: "http://47.99.202.161:5000/index2.html",
    },
    {
        id: 3,
        name: "页面3",
        ico: "calculator",
        color: "#1d2088",
        path: "http://47.99.202.161:5000/index3.html",
    },
];

采用媒介查询来控制rem字体大小

<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width,initial-scale=1.0">
        <title>
            Vue-App桌面程序列表
        </title>
        <style>
            @media screen and (min-width: 200px) {
                html {
                    font-size: 16px;
                }
            }

            @media screen and (min-width: 400px) {
                html {
                    font-size: 18px;
                }
            }

            @media screen and (min-width: 600px) {
                html {
                    font-size: 20px;
                }
            }

            @media screen and (min-width: 1000px) {
                html {
                    font-size: 22px;
                }
            }

            @media screen and (min-width: 1500px) {
                html {
                    font-size: 24px;
                }
            }
        </style>
    </head>

    <body>
        <div id="app"></div>
    </body>
</html>

效果展示

PC端展示效果
在这里插入图片描述

App端展示效果
在这里插入图片描述

项目链接

Vue-App桌面程序列表

  • 23
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值