Vue-App桌面程序列表

news2025/1/21 2:50:44

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

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

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

在线OJ项目测试(selenium+Junit5)

目录 在线OJ项目测试的思维导图 在线OJ的UI自动化测试 测试一&#xff1a;检查未登录时的页面访问以及一些未登录时的非法操作 测试二&#xff1a;测试注册界面 测试三&#xff1a;测试登录界面 测试四&#xff1a;测试题目列表界面 测试五&#xff1a;测试题目详情界面…

Android Kotlin 异步操作回调转换为挂起函数

异步接口回调是一种通过接口将任务的执行和结果处理分离开来的编程设计模式。通常用于网络请求、数据库查询等耗时操作。 挂起函数是 Kotlin 中的一个特性&#xff0c;用于简化异步编程。挂起函数是可以在协程中暂停执行并恢复的函数&#xff0c;避免了回调地狱问题&#xff0…

php质量工具系列之PHPCPD

PHPCPD 用于检测重复代码&#xff0c;直观的说就是复制粘贴再稍微改改 该工具作者已经 停止维护 安装 composer global require --dev sebastian/phpcpd执行 phpcpd --log-pmd phpcpd_result.xml ./app参数介绍 --log-pmd 将结果保存在phpcpd_result.xml 中 ./app 是phpcpd扫…

linux系统PXE自动装机和无人值守

一、PXE 1.PXE&#xff1a;c/s模式&#xff0c;允许客户端通过网络从远程服务器&#xff08;服务端&#xff09;下载引导镜像&#xff0c;加载安装文件&#xff0c;实现自动化安装操作系统。&#xff08;c/s客户端和服务端都可以是多台&#xff09; 2.PXE优点&#xff1a;规模…

【机器学习基础】Python编程08:五个实用练习题的解析与总结

Python是一种广泛使用的高级编程语言,它在机器学习领域中的重要性主要体现在以下几个方面: 简洁易学:Python语法简洁清晰,易于学习,使得初学者能够快速上手机器学习项目。 丰富的库支持:Python拥有大量的机器学习库,如scikit-learn、TensorFlow、Keras和PyTorch等,这些…

基于STM32开发的智能家居监控系统

目录 引言环境准备智能家居监控系统基础代码实现&#xff1a;实现智能家居监控系统 4.1 传感器数据读取4.2 电器设备控制4.3 实时数据监控与分析4.4 用户界面与数据可视化应用场景&#xff1a;家庭安全监控与管理问题解决方案与优化收尾与总结 1. 引言 随着智能家居技术的发…

AI绘画中的图像格式技术

在数字艺术的广阔天地里&#xff0c;AI绘画作为一种新兴的艺术形式&#xff0c;正在逐渐占据一席之地。不同于传统绘画&#xff0c;AI绘画依赖于复杂的算法和机器学习模型来生成图像&#xff0c;而这一切的背后&#xff0c;图像格式技术发挥着至关重要的作用。图像格式不仅关系…

23 二叉搜索树

本节目标 1.内容安排说明 2.二叉搜索树实现 3.应用分析 4.进阶题 1. 内容安排说明 二叉树在c数据结构已经说过了&#xff0c;本节内容是因为&#xff1a; map和set特性需要先铺垫二叉搜索树&#xff0c;而二叉搜索树也是一种树形结构二叉搜索树的特性了解&#xff0c;有助于…

LNMP与动静态网站介绍

Nginx发展 Nginx nginx http server Nginx是俄罗斯人 Igor Sysoev(伊戈尔.塞索耶夫)开发的一款高性能的HTTP和反向代理服务器。 Nginx以高效的epoll.kqueue,eventport作为网络IO模型&#xff0c;在高并发场景下&#xff0c;Nginx能够轻松支持5w并发连接数的响应&#xff0c;并…

OrangePi AIpro Ubuntu 22.04 aarch64 安装MySql 8.0

查看MySQL安装包 接下来可以使用以下命令安装MySQL服务器&#xff1a; 安装MySQL 8.0 # 安装最新版本 sudo apt install -y mysql-server # 安装指定版本 sudo apt install -y mysql-server-8.0初始化配置信息 sudo mysql_secure_installationVALIDATE PASSWORD COMPONENT ca…

算法分析与设计期末考试复习(更新ing)

重点内容&#xff1a; 绪论&#xff1a; 简单的递推方程求解 1.19(1)(2) 、 教材例题 多个函数按照阶的大小排序 1.18 分治法&#xff1a; 分治法解决芯片测试问题 计算a^n的复杂度为logn的算法&#xff08;快速幂&#xff09; 分治法解决平面最近点对问…

SecureCRT[po破] for Mac SSH终端操作工具[解] 安装教程

文章目录 效果一、准备工作二、开始安装1、双击运行软件&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2、 应用程序显示软件图标&#xff0c;表示安装成功 三、输入对应参数1、解决“软件已损坏&#xff0c;无法打开&#xff0c;要移到废纸篓”问题解决步骤…

从零开始实现自己的串口调试助手(8)-循环发送

循环发送 准备 创建槽函数 设置QSpinBox的最大值 注意&#xff1a; // 我们不能在qt的ui线程中延时&#xff0c;否则将导致页面刷新问题 //QThread::msleep(ui->spinBox->text().toInt());//设置下次发送时间间隔 定时器实现 关联信号与槽: //添加自动换行定…

【Vue】什么是props

文章目录 一、介绍二、代码示例三、props校验四、props校验完整写法五、props&data、单向数据流 一、介绍 Props 定义 组件上 注册的一些 自定义属性 Props 作用 向子组件传递数据 特点 可以 传递 任意数量 的prop可以 传递 任意类型 的prop 二、代码示例 父组件App…

实习记录2

1.flowable框架参数传递大概流程 通过传递xml&#xff0c;传递到后端&#xff0c;然后后端去解析 2.vue封装组件 在 Vue.js 中创建可复用的自定义组件是一个常见的需求&#xff0c;这样可以提高代码的复用性和可维护性。下面是一个简单的步骤指南&#xff0c;帮助你创建一个…

无锡哲讯——机械行业ERP管理系统,引领智能制造新纪元

机械行业作为现代工业的基石&#xff0c;正面临着前所未有的变革。随着智能制造的兴起&#xff0c;ERP管理系统在机械行业中的作用日益凸显。无锡哲讯智能科技有限公司&#xff0c;凭借其在ERP领域的专业实力和丰富经验&#xff0c;为机械行业客户提供定制化的ERP解决方案&…

Transformer学习之SwinTransformer

1.算法简介 本文主要参考自以下链接&#xff0c;整理成线上的形式用于备忘&#xff0c;排版太麻烦了直接贴图&#xff0c;参考的朋友慎重&#xff0c;不如直接看参考链接&#xff0c;后期有了新的理解继续更正。 参考链接1&#xff1a;Swin-Transformer网络结构详解_swin tran…

【计算机网络】对应用层协议中HTTPS协议的总结

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…

上市公司绿色并购数据+do文件(1996-2024.4)

数据简介&#xff1a;手工搜集重污染上市公司的并购公告&#xff0c;采用内容分析法&#xff0c;对每次并购的背景和目的&#xff0c;主并企业和标的企业经营范围以及该次并购对主并企业带来的影响进行综合分析&#xff0c;逐一判断该项并购事件是否为绿色并购 时间跨度&#…