Vue-App桌面程序列表

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桌面程序列表

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/690221.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Redis】 Redis 集成到 Spring Boot上面

文章目录 &#x1f343;前言&#x1f384;Spring Boot连接redis客户端&#x1f6a9;项目的创建&#x1f6a9;配置端⼝转发&#x1f6a9;配置 redis 服务地址&#x1f6a9;更改 Redis 配置文件&#x1f6a9;使用 StringRedisTemplate 类操作 &#x1f38d;Spring Boot操作Redis客…

FM1202,FM020和利时备品

FM1202,FM020和利时备品,统硬件设备、数据库、控制算法、图形、报表&#xff09;和相关系统参数的设置。对整个系统进行监视和控制。操作员站主要完成以下FM1202,FM020和利时备品,各种监视信息的显示、查询和打印&#xff0c;主要有工艺流程图显示、趋势显示、参数列表显示、报…

调用华为API实现语音合成

目录 1.作者介绍2.华为云语音合成2.1 语音合成介绍2.2 华为语音合成服务2.3 应用场景 3. 实验过程以及结果3.1 获取API密钥3.2 调用语音合成算法API3.3 实验代码3.4 运行结果 1.作者介绍 袁斌&#xff0c;男&#xff0c;西安工程大学电子信息学院&#xff0c;2023级研究生 研究…

【百万字详解Redis】集群

文章目录 一、集群模式概述1.1、什么是集群模式1.2、集群模式特点1.3、集群工作方式 二、集群模式的搭建2.1、搭建前的准备2.2、修改集群配置2.3、启动redis服务2.4、创建集群2.5、查看redis服务状态2.6、进入一个节点2.7、测试操作 三、集群操作3.1、主从切换3.2、从节点操作3…

重塑楼宇管理:智慧管控可视化开启高效新篇章

借助图扑智慧楼宇管控可视化技术&#xff0c;实现实时监控与智能化管理&#xff0c;快速响应潜在问题&#xff0c;确保楼宇安全、节能和高效运行。

【Mybatis】动态SQL标签3

foreach标签是使用举例 在实际应用中&#xff0c;我常常需要根据多个id批量的操作&#xff1a; 查询指定id的记录&#xff1a; 这时就可以用foreach标签&#xff1a; collection"ids" &#xff1a; 接口上传过来的数值或list集合或者map集合都可以 item"id&…

50etf期权怎么开户?期权懂有几种方式?

今天带你了解50etf期权怎么开户&#xff1f;期权懂有几种方式&#xff1f;50ETF期权开户可以通过证券公司、期权交易平台或期权交易应用进行。投资者需填写开户申请表格&#xff0c;提供身份证明和其他资料&#xff0c;完成开户手续。 50etf期权怎么开户&#xff1f; 满足资金…

linux内存缓存占用过高分析和优化

1、什么是buffer/cache &#xff1f; buffer/cache其实是作为服务器系统的文件数据缓存使用的&#xff0c;尤其是针对进程对文件存在read/write操作的时候&#xff0c;所以当你的服务进程在对文件进行读写的时候&#xff0c;Linux内核为了提高服务的读写速度&#xff0c;则将会…

亘古真知

目录 一&#xff0c;概述 二&#xff0c;个人面板 三&#xff0c;科技面板 四&#xff0c;手牌 五&#xff0c;回合 1&#xff0c;行动 &#xff08;1&#xff09;打造 &#xff08;2&#xff09;学习 &#xff08;3&#xff09;归档 &#xff08;4&#xff09;挖掘 …

网信办大模型备案全网最详细流程【附附件】

本文要点&#xff1a;大模型备案最详细说明&#xff0c;大模型备案条件有哪些&#xff0c;《算法安全自评估报告》模板&#xff0c;大模型算法备案&#xff0c;大模型上线备案&#xff0c;生成式人工智能(大语言模型)安全评估要点&#xff0c;网信办大模型备案。 大模型备案安…

用你熟悉的语言就能开发智能合约,Vara Network 以 WASM 解锁未来应用创新

Vara Network 自推出以来&#xff0c;凭借其基于 Gear Protocol 的独特架构和强大的开发工具&#xff0c;为开发者提供了一个高效、安全的智能合约构建平台。Vara Network 通过采用先进的 Actor 模型、持久内存概念和 WebAssembly 技术&#xff0c;实现了异步消息处理、并行计算…

湖南源点(市场研究咨询)如何产出更加有意义的竞品调研

湖南源点咨询认为&#xff1a;当前&#xff0c;任何项目都不能盲目开始&#xff0c;前期的准备工作必不可少。在基础架构搭建的同时&#xff0c;设计上对于前端功能、用户体验的调研就优先开始了。在这个阶段&#xff0c;大部分设计师都会分配很多调研任务&#xff0c;疯狂对竞…

YOLOv5改进总目录 | backbone、Neck、head、损失函数,注意力机制上百种改进技巧

&#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 专栏地址&#xff1a; YOLOv5改进入门——持续更新各种有效涨点方法 点击即可跳转 报错 解决Yolov5的RuntimeError: result type Float can…

Leetcode:括号生成

题目链接&#xff1a;22. 括号生成 - 力扣&#xff08;LeetCode&#xff09; 题目分析 1、括号的类型只有&#xff08;&#xff09;一种&#xff0c;没有{}或者[] 2、括号可以进行多层嵌套 3、有效的括号组合需要满足以下两个条件&#xff08;任意一个子串&#xff09;&am…

Qt5学习笔记(一):Qt Widgets Application项目初探

笔者长期使用MFC开发Windows GUI软件。随着软件向Linux平台迁移的趋势越发明朗&#xff0c;GUI程序的跨平台需求也越来越多。因此笔者计划重新抓一下Qt来实现跨平台GUI程序的实现。 0x01. 看看Qt Widgets Application项目结构 打开Qt5&#xff0c;点击“ New”按钮新建项目。…

了解Java内存模型(Java Memory Model, JMM)

了解Java内存模型&#xff08;Java Memory Model, JMM&#xff09; Java内存模型&#xff08;Java Memory Model, JMM&#xff09;是Java语言规范中规定的一组规则&#xff0c;定义了多线程程序中变量&#xff08;包括实例字段、静态字段和数组元素&#xff09;的访问方式。JM…

ChatGPT-4o, 腾讯元宝,通义千问对比测试中文文化

国内的大模型应用我选择了国内综合实力最强的两个&#xff0c;一个是腾讯元宝&#xff0c;一个是通义千问。其它的豆包&#xff0c;Kimi&#xff0c;文心一言等在某些领域也有强于竞品的表现。 问一个中文文化比较基础的问题,我满以为中文文化chatGPT不如国内的大模型。可事实…

pdf文件在线压缩网站,pdf文件在线压缩工具软件

在数字化时代的今天&#xff0c;PDF文件已经成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;随着PDF文件的广泛使用&#xff0c;其文件大小问题也日益凸显。过大的PDF文件不仅占用了大量的存储空间&#xff0c;而且在传输和共享过程中也往往面临诸多不便。因此&am…

【日记】遇到了一个 “不愿睁眼看世界也没受过社会毒打” 的逆天群友(464 字)

正文 今天坐在柜台玩了一天手机…… 手机都玩没电了快。下午在劝一个群友睁眼看世界&#xff0c;实在劝不动。他真的太逆天了&#xff0c;我不清楚这么高学历的人&#xff0c;怎么能说出这么天真的话。逆天又离谱。 晚上的时间几乎全在做家务。平时晚上都是跳舞来着&#xff0c…