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