整体效果:
桌面图标的状态存在了store/index.js中定义的windows列表中:
windows: [
{
windowId: "BiographyWindow", // 窗口ID
windowState: "close", // 窗口状态[open, close, minimize]
displayName: "Biography", // 展示名字(title under icon)
windowComponent: 'window', // 窗口组件 (can be changed to use modified windows)
windowContent: 'bio', //窗口内容(used under slots)
windowContentPadding: {
top: null,
right: null,
bottom: null,
left: null
}, // 窗口内容边距
position: "absolute", //窗口位置
positionX: "5vw", // Window Position X (when first opened)
positionY: "5%", // Window Position Y (when first opened)
iconImage: "bio.png", // Window Icon Image
altText: "Biography", // Window Icon Alt Text
fullscreen: false //窗口全屏状态 [true, false]
},
{
windowId: "ResumeWindow", // Unique ID
windowState: "close", // Window State [open, close, minimize]
displayName: "Résumé", // Display Name (title under icon)
windowComponent: 'window', // Window Component (can be changed to use modified windows)
windowContent: 'resume', // Window Content (used under slots)
windowContentPadding: {
top: '0',
right: '0',
bottom: '0',
left: '0'
}, // Window Content Padding
position: "absolute", // Window Position
positionX: "10vw", // Window Position X (when first opened)
positionY: "15vh", // Window Position Y (when first opened)
iconImage: "resume.png", // Window Icon Image
altText: "Résumé", // Window Icon Alt Text
fullscreen: false // Window Fullscreen State [true, false]
},
{
windowId: "PhotosWindow",
windowState: "close",
displayName: "Photos",
windowComponent: 'window',
windowContent: 'photos',
windowContentPadding: {
top: '10px',
right: '10px',
bottom: '10px',
left: '10px'
},
position: "absolute",
positionX: "6vw",
positionY: "12vh",
iconImage: "photos.png",
altText: "Photos",
fullscreen: false
},
{
windowId: "OpenSourceWindow",
windowState: "close",
displayName: "Open Source",
windowComponent: 'window',
windowContent: 'opensource',
windowContentPadding: {
top: null,
right: '15px',
bottom: null,
left: '15px'
},
position: "absolute",
positionX: "6vw",
positionY: "12vh",
iconImage: "opensource.png",
altText: "Open Source Projects",
fullscreen: false
},
{
windowId: "MailWindow",
windowState: "close",
displayName: "Mail",
windowComponent: 'mail',
windowContent: '',
windowContentPadding: {
top: '0',
right: '0',
bottom: '0',
left: '0'
},
position: "absolute",
positionX: "6vw",
positionY: "12vh",
iconImage: "mail.png",
altText: "Mail",
fullscreen: false
},
在App.vue中,通过v-for循环读取windows中的窗口数据,读取窗口专属信息以便于post到窗口中
<div
v-for="window in windows"
:key="window.key"
:aria-label="window.displayName"
>
<component
:is="window.windowComponent"
:nameOfWindow="window.windowId"
:content_padding_bottom="window.windowContentPadding['bottom']"
:content_padding_left="window.windowContentPadding['left']"
:content_padding_right="window.windowContentPadding['right']"
:content_padding_top="window.windowContentPadding['top']"
:id="window.windowId"
:style="{position: window.position, left: window.positionX, top: window.positionY}"
v-if="windowCheck(window.windowId)"
>
<component
:is="window.windowContent"
slot="content"
>
</component>
</component>
<StartMenu v-if="$store.getters.getActiveWindow=='Menu'" style="position: absolute; z-index: 9999; bottom: 0; left: 0;"></StartMenu>
</div>
body的背景风格使用css设置,overflow: hidden来创建BFC防止高度塌陷
body {
margin: 0;
padding: 0;
overflow: hidden;
background: #018281;
}
使用component 来表示单独的图标,读取存在store中的信息v-if="windowCheck(window.windowId)"
使用v-if来选择是否展示打开的单独窗口
小图标排列
使用<app-grid></app-grid>
引入小图标
在AppGrid.vue中具体实现如下:
<li v-for="window in windows" :key="window.key">
<button class="icon" @touchstart="openWindow(window.windowId)" @dblclick="openWindow(window.windowId)">
<img class="icon-image" :src="require('../assets/win95Icons/' + window.iconImage)" :alt="window.altText" />
<div class="border">
<p class="icon-text">
{{window.displayName}}
</p>
</div>
</button>
</li>
使用li来包裹图标,设置成button点击响应类型,绑定了touchstart事件,当手指触摸屏幕时候触发,即使已经有一个手指放在屏幕上也会触发 以及 dbclick双击事件
点击后调用openwindow函数,点击按钮后将setWindowState设为open
openWindow(windowId) {
const payload = {
'windowState': 'open',
'windowID': windowId
}
this.$store.commit('setWindowState', payload)
},
在存储状态的store/index.js中整个流程可以描述为:
如果WindowState设为open,那么就激活窗口。先更新窗口状态,同时执行z-index递增函数让窗口置于最前,通过pushActiveWindow将激活的窗口push进激活队列。
如果WindowState设为close,先更新窗口状态,从激活队列中删除窗口,然后取消激活窗口。
如果WindowState设为minimize,先更新窗口状态,然后取消激活窗口。此时窗口还在队列中,只是不在桌面上显示。
mutations: {
// Active Window Mutator
// 激活窗口
setActiveWindow(state, window) {
console.log(window);
state.activeWindow = window
},
// 将激活的窗口push到活动窗口队列上
pushActiveWindow(state, window) {
state.activeWindows.push(window)
},
// 从激活窗口队列中删除窗口
popActiveWindow(state, window) {
const windowIndex = state.activeWindows.indexOf(window);
if (windowIndex !== -1) {
state.activeWindows.splice(windowIndex, 1)
}
},
// Z-index increment function
// 增加z-index,让窗口浮于顶层
zIndexIncrement(state, windowID) {
state.zIndex += 1
document.getElementById(windowID).style.zIndex = state.zIndex
},
// Set height of max-height of fullscreen window
// 设置全屏窗口的最大高度
setFullscreenWindowHeight(state, height) {
state.fullscreenWindowHeight = height
},
updateMail(state, local) {
state.isShownMail = local
},
updateMailSubject(state, local) {
state.mailSubject = local
},
updateMailContent(state, local) {
state.mailContent = local
},
updateMailSender(state, local) {
state.mailSender = local
},
setFullscreen(state, payload) {
function getArrItem() {
return state.windows.find(
(windows) => windows.windowId === payload.windowID
);
}
const window = getArrItem();
window.fullscreen = payload.fullscreen;
},
// Window State Mutator
setWindowState(state, payload) {
// payload = {'windowState': 'open', 'windowID': 'WindowOne'}
function getArrItem() {
return state.windows.find(
(windows) => windows.windowId === payload.windowID
);
}
const window = getArrItem();
var preventAppendingOpenWindow = false;
if (window.windowState == "open" || window.windowState == "minimize") {
preventAppendingOpenWindow = true;
}
if (payload.windowState == "open") {
window.windowState = payload.windowState;
setTimeout(() => {
this.commit("zIndexIncrement", payload.windowID);
}, 0);
setTimeout(() => {
this.commit("setActiveWindow", payload.windowID);
}, 0);
if (preventAppendingOpenWindow == false) {
setTimeout(() => {
this.commit("pushActiveWindow", window);
}, 0);
}
} else if (payload.windowState == "close") {
window.windowState = payload.windowState;
setTimeout(() => {
this.commit("popActiveWindow", window);
}, 0);
setTimeout(() => {
this.commit("setActiveWindow", "nil");
}, 0);
} else if (payload.windowState == "minimize") {
window.windowState = payload.windowState;
this.commit("setActiveWindow", "nil");
} else {
console.log("Error: windowState not found or invalid");
}
},
},
//通过getters获得返回值
getters: {
// 获取激活窗口
getActiveWindow: (state) => {
return state.activeWindow;
},
// Window Getter
getWindowById: (state) => (id) => {
return state.windows.find((window) => window.windowId === id);
},
getWindowFullscreen: (state) => (id) => {
return state.windows.find((window) => window.windowId === id).fullscreen;
},
getWindows: (state) => {
return state.windows;
},
getActiveWindows(state) {
return state.activeWindows;
},
getFullscreenWindowHeight(state) {
return state.fullscreenWindowHeight;
},
mailContent: state => {
return state.mailContent
},
mailSubject: state => {
return state.mailSubject
},
mailSender: state => {
return state.mailSender
},
},
setTimeout设置为0的意义,简单来说就是为了让setTimeout后的代码先于setTimeout的回调函数运行
监听window尺寸变化
当整体窗口尺寸变化时,需要监听到resize状态对布局的style进行调整
window.addEventListener('resize', () => {
let vh = window.innerHeight * 0.01; //vh单位表示根元素高度的百分比,一个vh等于视口高度的1%。
document.documentElement.style.setProperty('--vh', `${vh}px`);
});
function resetHeight() {
document.body.style.height = window.innerHeight + "px";
document.html.style.height = window.innerHeight + "px";
}
window.addEventListener("resize", resetHeight);