Vue-App桌面程序列表

作者 : admin 本文共7452个字,预计阅读时间需要19分钟 发布时间: 2024-06-16 共1人阅读

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端展示效果
Vue-App桌面程序列表插图

App端展示效果
Vue-App桌面程序列表插图(1)

项目链接

Vue-App桌面程序列表

本站无任何商业行为
个人在线分享 » Vue-App桌面程序列表
E-->