Browse Source

add:移动端适配

master
tiantian 6 months ago
parent
commit
8fad22cbea
  1. 1
      frontend/package.json
  2. 353
      frontend/src/components/computer/Computer.vue
  3. 57
      frontend/src/components/desktop/DeskItem.vue
  4. 141
      frontend/src/components/desktop/Desktop.vue
  5. 4
      frontend/src/components/window/WindowNode.vue
  6. 264
      frontend/src/components/window/WindowTemplate.vue
  7. 26
      frontend/src/main.ts

1
frontend/package.json

@ -23,6 +23,7 @@
"pinia": "^2.1.7",
"pinia-plugin-persist": "^1.0.0",
"swiper": "^11.1.15",
"vant": "^4.9.15",
"vue": "^3.4.31",
"vue-i18n": "^9.13.1",
"vue-router": "^4.4.0",

353
frontend/src/components/computer/Computer.vue

@ -3,175 +3,106 @@
<div class="group">
<!-- <div class="button">文件</div> -->
<!-- <div class="button">计算机</div> -->
<div
class="button"
@click="backFolder()"
>
<div class="button" @click="backFolder()">
{{ t("back") }}
</div>
<!-- 查看 -->
<div
class="button"
@click="popoverChange()"
>
<div class="button" @click="popoverChange()">
{{ t("view") }}
</div>
<!-- <div class="button" @click="newFolder()">新建</div> -->
</div>
<div
v-if="isPopoverView"
class="up-pop"
>
<div v-if="isPopoverView" class="up-pop">
<UpPopover v-model="chosenView"></UpPopover>
</div>
<ComputerNavBar
v-model="router_url"
@backFolder="backFolder()"
@refresh="handleNavRefresh"
@search="handleNavSearch"
@changeHistory="handleHistoryChange"
></ComputerNavBar>
<ComputerNavBar v-model="router_url" @backFolder="backFolder()" @refresh="handleNavRefresh"
@search="handleNavSearch" @changeHistory="handleHistoryChange"></ComputerNavBar>
</div>
<div
class="main"
@click="handleOuterClick"
>
<div
class="left-tree"
:style="{
<div class="main" @click="handleOuterClick">
<div v-if="!isMobileDevice()" class="left-tree" :style="{
width: leftWidth + 'px',
}"
>
<div
class="disktopshow"
@click="onTreeOpen('/C/Users/Desktop')"
>
<el-icon
:size="20"
color="#137bd2"
>
}">
<div class="disktopshow" @click="onTreeOpen('/C/Users/Desktop')">
<el-icon :size="20" color="#137bd2">
<Platform />
</el-icon>
{{ t("desktop") }}
</div>
<div class="showName">{{ t("computer") }}</div>
<FileTree
:chosen-path="chosenTreePath"
mode="list"
:on-open="onTreeOpen"
:on-refresh="onListRefresh"
:file-list="rootFileList"
:key="random"
>
<FileTree :chosen-path="chosenTreePath" mode="list" :on-open="onTreeOpen" :on-refresh="onListRefresh"
:file-list="rootFileList" :key="random">
</FileTree>
<div
class="showName"
v-if="shareShow"
>
<div class="showName" v-if="shareShow">
{{ t("share") }}
</div>
<FileTree
v-if="shareShow"
:chosen-path="chosenTreePath"
mode="list"
:on-open="onTreeOpen"
:on-refresh="onListRefresh"
:file-list="shareFileList"
:key="random"
>
<FileTree v-if="shareShow" :chosen-path="chosenTreePath" mode="list" :on-open="onTreeOpen"
:on-refresh="onListRefresh" :file-list="shareFileList" :key="random">
</FileTree>
<QuickLink :on-open="onTreeOpen"></QuickLink>
<div
class="left-handle"
@mousedown="leftHandleDown"
></div>
<div class="left-handle" @mousedown="leftHandleDown"></div>
</div>
<div
class="desk-outer"
@contextmenu.self="showOuterMenu($event)"
@dragenter.prevent
@dragover.prevent
@drop.stop="dragFileToDrop($event, router_url)"
@click.self="onBackClick"
@mousedown="backgroundDown"
>
<FileList
:on-chosen="onChosen"
:on-refresh="onListRefresh"
:on-open="openFolder"
:file-list="currentList"
theme="blue"
:mode="chosenView"
>
<div class="desk-outer" @contextmenu.self="showOuterMenu($event)" @dragenter.prevent @dragover.prevent
@drop.stop="dragFileToDrop($event, router_url)" @click.self="onBackClick" @mousedown="backgroundDown">
<FileList :on-chosen="onChosen" :on-refresh="onListRefresh" :on-open="openFolder" :file-list="currentList"
theme="blue" :mode="chosenView">
</FileList>
<div
draggable="true"
class="desk-item"
v-if="creating"
>
<div draggable="true" class="desk-item" v-if="creating">
<div class="item_img">
<!-- <img draggable="false" width="50" :src="foldericon" /> -->
<svg
class="icon"
aria-hidden="true"
style="font-size: 1.2em"
>
<svg class="icon" aria-hidden="true" style="font-size: 1.2em">
<use xlink:href="#icon-folder"></use>
</svg>
</div>
<input
class="item_input"
v-model="createInput"
@blur="creatingEditEnd"
/>
<input class="item_input" v-model="createInput" @blur="creatingEditEnd" />
</div>
<Chosen></Chosen>
</div>
</div>
</template>
<script lang="ts" setup>
import { inject, onMounted, ref } from "vue";
import { useComputer } from "@/hook/useComputer";
import { useContextMenu } from "@/hook/useContextMenu";
import { useFileDrag } from "@/hook/useFileDrag";
import { Rect, useRectChosen } from "@/hook/useRectChosen";
// import { getSystemConfig } from "@/system/config";
import { emitEvent, mountEvent } from "@/system/event";
import {
import { inject, onMounted, ref } from "vue";
import { useComputer } from "@/hook/useComputer";
import { useContextMenu } from "@/hook/useContextMenu";
import { useFileDrag } from "@/hook/useFileDrag";
import { Rect, useRectChosen } from "@/hook/useRectChosen";
// import { getSystemConfig } from "@/system/config";
import { emitEvent, mountEvent } from "@/system/event";
import {
BrowserWindow,
dirname,
Notify,
OsFileWithoutContent,
t,
useSystem,
} from "@/system/index.ts";
} from "@/system/index.ts";
import { useChooseStore } from "@/stores/choose";
import { useChooseStore } from "@/stores/choose";
import { isMobileDevice } from "@/util/device";
const { choseStart, chosing, choseEnd, getRect, Chosen } = useRectChosen();
const { choseStart, chosing, choseEnd, getRect, Chosen } = useRectChosen();
const browserWindow: BrowserWindow | undefined = inject("browserWindow");
const config = browserWindow?.config;
const browserWindow: BrowserWindow | undefined = inject("browserWindow");
const config = browserWindow?.config;
const router_url = ref("");
const router_url_history = ref<Array<string>>([]);
const router_url_history_index = ref(0);
const currentList = ref<Array<OsFileWithoutContent>>([]);
const router_url = ref("");
const router_url_history = ref<Array<string>>([]);
const router_url_history_index = ref(0);
const currentList = ref<Array<OsFileWithoutContent>>([]);
const system = useSystem();
const { dragFileToDrop } = useFileDrag(system);
const { createDesktopContextMenu } = useContextMenu();
//
const props = defineProps({
const system = useSystem();
const { dragFileToDrop } = useFileDrag(system);
const { createDesktopContextMenu } = useContextMenu();
//
const props = defineProps({
translateSavePath: {
type: Function
},
componentID:{
componentID: {
}
})
const setRouter = function (path: string) {
})
const setRouter = function (path: string) {
if (props.translateSavePath && props.componentID) {
props.translateSavePath(path)
// router_url_history.value.push(path);
@ -188,10 +119,10 @@
}
router_url_history_index.value = router_url_history.value.length;
router_url_history.value.push(path);
};
const choose = useChooseStore()
};
const choose = useChooseStore()
const { refersh, createFolder, backFolder, openFolder, onComputerMount } =
const { refersh, createFolder, backFolder, openFolder, onComputerMount } =
useComputer({
setRouter: setRouter,
getRouter() {
@ -248,7 +179,7 @@
return system.fs.search(keyword);
},
});
function handleHistoryChange(offset: number) {
function handleHistoryChange(offset: number) {
if (router_url_history_index.value + offset < 0) return;
if (
router_url_history_index.value + offset >
@ -261,9 +192,9 @@
router_url_history.value[router_url_history_index.value];
refersh();
}
const leftWidth = ref(200);
function leftHandleDown(e: MouseEvent) {
}
const leftWidth = ref(200);
function leftHandleDown(e: MouseEvent) {
const startX = e.clientX;
const startWidth = leftWidth.value;
addEventListener("mousemove", leftHandleMove);
@ -278,10 +209,10 @@
removeEventListener("mousemove", leftHandleMove);
removeEventListener("mouseup", leftHandleUp);
}
}
}
const rootFileList = ref<Array<OsFileWithoutContent>>([]);
const shareFileList = ref<Array<OsFileWithoutContent>>([
const rootFileList = ref<Array<OsFileWithoutContent>>([]);
const shareFileList = ref<Array<OsFileWithoutContent>>([
{
ext: "",
isDirectory: true,
@ -299,10 +230,10 @@
atime: "",
birthtime: "",
}
]);
const random = ref(0);
const shareShow = ref(false);
onMounted(() => {
]);
const random = ref(0);
const shareShow = ref(false);
onMounted(() => {
if (config) {
router_url.value = config.path;
} else {
@ -336,32 +267,32 @@
if (pos !== -1) onTreeOpen(choose.saveFileContent[pos].defaultPath)
// console.log('',pos, choose.saveFileContent);
}
});
});
function handleOuterClick() {
function handleOuterClick() {
emitEvent("mycomputer.click");
}
}
function onListRefresh() {
function onListRefresh() {
refersh();
system.fs.readdir("/").then((file: any) => {
if (file) {
rootFileList.value = [...file];
}
});
}
}
/**------视图切换------ */
const isPopoverView = ref(false);
function popoverChange() {
/**------视图切换------ */
const isPopoverView = ref(false);
function popoverChange() {
isPopoverView.value = !isPopoverView.value;
}
}
const chosenView = ref("icon");
const chosenView = ref("icon");
/**------树状列表打开------ */
const chosenTreePath = ref("");
async function onTreeOpen(path: string) {
/**------树状列表打开------ */
const chosenTreePath = ref("");
async function onTreeOpen(path: string) {
chosenTreePath.value = path;
let file: any;
if (path.indexOf('/F') === 0) {
@ -374,29 +305,29 @@
openFolder(file);
}
router_url.value = path;
}
}
/**------框选--------- */
let chosenCallback: (rect: Rect) => void = () => {
/**------框选--------- */
let chosenCallback: (rect: Rect) => void = () => {
//
};
function onChosen(callback: (rect: Rect) => void) {
};
function onChosen(callback: (rect: Rect) => void) {
chosenCallback = callback;
}
}
function backgroundDown(e: MouseEvent) {
function backgroundDown(e: MouseEvent) {
choseStart(e);
addEventListener("mousemove", backgroundMove);
addEventListener("mouseup", backgroundUp);
}
function backgroundMove(e: MouseEvent) {
}
function backgroundMove(e: MouseEvent) {
chosing(e);
const rectValue = getRect();
if (rectValue) {
chosenCallback(rectValue);
}
}
function backgroundUp() {
}
function backgroundUp() {
choseEnd();
const rectValue = getRect();
if (rectValue) {
@ -404,31 +335,31 @@
}
removeEventListener("mousemove", backgroundMove);
removeEventListener("mouseup", backgroundUp);
}
}
/* ------------ 新建文件夹 ------------*/
const createInput = ref(t("new.folder"));
const creating = ref(false);
function creatingEditEnd() {
/* ------------ 新建文件夹 ------------*/
const createInput = ref(t("new.folder"));
const creating = ref(false);
function creatingEditEnd() {
if (creating.value) {
createFolder(createInput.value);
creating.value = false;
createInput.value = t("new.folder");
}
}
function onBackClick() {
}
function onBackClick() {
creatingEditEnd();
}
/* ------------ 新建文件夹end ---------*/
}
/* ------------ 新建文件夹end ---------*/
function showOuterMenu(e: MouseEvent) {
function showOuterMenu(e: MouseEvent) {
e.preventDefault();
createDesktopContextMenu(e, router_url.value, () => {
refersh();
});
}
/* ------------ 路径输入框 ------------*/
async function handleNavRefresh(path: string) {
}
/* ------------ 路径输入框 ------------*/
async function handleNavRefresh(path: string) {
if (path == "") return;
if (path.startsWith("search:")) {
setRouter(path);
@ -444,27 +375,29 @@
setRouter(dirname(path));
refersh();
}
}
async function handleNavSearch(path: string) {
}
async function handleNavSearch(path: string) {
setRouter("search:" + path);
refersh();
}
/* ------------ 路径输入框end ---------*/
}
/* ------------ 路径输入框end ---------*/
</script>
<style lang="scss" scoped>
.uper {
.uper {
/* height: 40px; */
background-color: rgba(255, 235, 205, 0);
font-size: 12px;
font-weight: 300;
/* border-bottom: 1px solid black; */
--button-item-height: 30px;
}
.main {
}
.main {
display: flex;
height: 100%;
position: relative;
top: 4px;
.left-tree {
position: relative;
overflow-x: hidden;
@ -474,6 +407,7 @@
background-color: rgba(255, 255, 255, 0.1);
border-right: 1px solid rgba(134, 134, 134, 0.267);
}
.left-handle {
position: absolute;
right: 0;
@ -483,6 +417,7 @@
background: rgba(0, 0, 0, 0);
cursor: ew-resize;
}
.desk-outer {
flex: 1;
height: 100%;
@ -495,9 +430,9 @@
position: relative;
overflow-x: hidden;
}
}
}
.desk-item {
.desk-item {
position: relative;
cursor: default;
box-sizing: border-box;
@ -506,29 +441,31 @@
background-color: rgba(119, 119, 119, 0);
color: white;
border: 1px solid rgba(0, 0, 0, 0);
}
.chosen {
}
.chosen {
border: 1px dashed #3bdbff3d;
background-color: #b9e3fd90;
}
.desk-item:hover {
}
.desk-item:hover {
border: 1px solid rgba(149, 149, 149, 0.233);
background-color: #b9e3fd5a;
}
}
.item_img {
.item_img {
width: 50px;
height: 40px;
overflow: hidden;
padding: 10px;
text-align: center;
}
}
.item_img img {
.item_img img {
user-select: none;
}
}
.item_name {
.item_name {
overflow: hidden;
max-width: 200px;
color: rgba(0, 0, 0, 0.664);
@ -543,20 +480,20 @@
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
}
.item_input {
.item_input {
width: 100%;
}
}
.group {
.group {
display: flex;
border-bottom: 1px solid rgba(134, 134, 134, 0.267);
user-select: none;
}
}
.button {
.button {
cursor: pointer;
text-align: center;
width: 50px;
@ -572,31 +509,37 @@
transition: 0.1s;
white-space: nowrap;
user-select: none;
}
}
.button:hover {
.button:hover {
/* background-color: #137bd2; */
background-color: #1b6bad;
color: white;
}
.showName {
}
.showName {
font-size: 11px;
text-indent: 5px;
}
.disktopshow {
}
.disktopshow {
font-size: 12px;
line-height: 20px;
max-width: 200px;
height: 30px;
display: flex;
align-items: center; /* 垂直居中对齐 */
justify-content: flex-start; /* 水平左对齐 */
align-items: center;
/* 垂直居中对齐 */
justify-content: flex-start;
/* 水平左对齐 */
user-select: none;
.el-icon {
margin-right: 3px;
}
}
.disktopshow:hover {
}
.disktopshow:hover {
background-color: #b1f1ff4c;
}
}
</style>

57
frontend/src/components/desktop/DeskItem.vue

@ -8,17 +8,30 @@
<swiper-slide class="swiper-slide">
<FileList :on-chosen="props.onChosen" :on-open="openapp" :file-list="appList"></FileList>
</swiper-slide>
<swiper-slide></swiper-slide>
<swiper-slide>
<div @click.stop="handle(item)" class="magnet-item" :style="{
animationDelay: `${Math.floor(index / 4) * 0.02}s`,
animationDuration: `${Math.floor(index / 4) * 0.04 + 0.1}s`,
}" v-for="(item, index) in menulist" v-glowing :key="basename(item.path)">
<FileIcon class="magnet-item_img" :file="item" />
<span class="magnet-item_title">{{ getName(item) }}</span>
</div>
</swiper-slide>
</swiper>
</div>
</template>
<script lang="ts" setup>
import { mountEvent } from "@/system/event";
import { useSystem } from "@/system/index.ts";
import { useSystem, BrowserWindow } from "@/system/index.ts";
import { useAppOpen } from "@/hook/useAppOpen";
import { onMounted } from "vue";
import { isMobileDevice } from "@/util/device";
import { Swiper, SwiperSlide } from 'swiper/vue'
import { basename } from "@/system/core/Path";
import { emitEvent } from "@/system/event";
import { t } from "@/i18n";
// swiper
import 'swiper/swiper-bundle.css';
// swiper
@ -26,6 +39,7 @@ import { Pagination } from 'swiper/modules'
// modules使
const modules = [Pagination]
const { openapp, appList } = useAppOpen("apps");
const { appList: menulist } = useAppOpen("menulist")
const props = defineProps({
onChosen: {
type: Function,
@ -37,6 +51,31 @@ onMounted(() => {
useSystem().initAppList();
});
});
function handle(item: any) {
emitEvent("magnet.item.click", item);
const sys = useSystem();
const winopt = sys._rootState.windowMap["Menulist"].get(item.title);
if (winopt) {
if (winopt._hasShow) {
return;
} else {
winopt._hasShow = true;
const win = new BrowserWindow(winopt.window);
win.show();
win.on("close", () => {
winopt._hasShow = false;
});
}
}
}
function getName(item: any) {
const name = basename(item.path);
if (name.endsWith(".exe")) {
return t(name.replace(".exe", ""));
} else {
return name;
}
}
</script>
<style lang="scss" scoped>
.desk-group {
@ -62,7 +101,7 @@ onMounted(() => {
.swiperBox {
width: 100vw;
padding-top: vh(20);
padding-top: vh(10);
// position: absolute;
// top:0;
// left: 0;
@ -74,4 +113,16 @@ onMounted(() => {
grid-template-rows: repeat(5, vh(100))
}
}
.magnet-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.magnet-item_img {
width: vw(40);
height: vh(40);
}
}
</style>

141
frontend/src/components/desktop/Desktop.vue

@ -19,6 +19,15 @@
<div class="bottom">
<Taskbar></Taskbar>
</div>
<div class="bottom-bar" v-if="isMobileDevice()">
<div @click.stop="handle(item)" class="magnet-item" :style="{
animationDelay: `${Math.floor(index / 4) * 0.02}s`,
animationDuration: `${Math.floor(index / 4) * 0.04 + 0.1}s`,
}" v-for="(item, index) in bottomApp" v-glowing :key="basename(item.path)">
<FileIcon class="magnet-item_img" :file="item" />
<span class="magnet-item_title">{{ getName(item) }}</span>
</div>
</div>
<ContextMenu></ContextMenu>
</div>
</template>
@ -27,10 +36,113 @@ import { emitEvent } from "@/system/event";
import { useContextMenu } from "@/hook/useContextMenu";
import { useFileDrag } from "@/hook/useFileDrag";
import { Rect, useRectChosen } from "@/hook/useRectChosen";
import { useSystem } from "@/system";
import { useSystem, BrowserWindow } from "@/system";
import { onErrorCaptured } from "vue";
import { useUpgradeStore } from '@/stores/upgrade';
import { isMobileDevice } from "@/util/device";
import { basename } from "@/system/core/Path";
import { t } from "@/i18n";
import { useAppOpen } from "@/hook/useAppOpen";
const { openapp } = useAppOpen("menulist");
function handle(item: any) {
if (item.name.includes('localchat')) {
openapp(item);
emitEvent("desktop.app.open");
} else {
emitEvent("magnet.item.click", item);
}
const sys = useSystem();
const winopt = sys._rootState.windowMap["Menulist"].get(item.title);
if (winopt) {
if (winopt._hasShow) {
return;
} else {
winopt._hasShow = true;
winopt.window.fullscreen = true
const win = new BrowserWindow(winopt.window);
win.show();
win.on("close", () => {
winopt._hasShow = false;
});
}
}
}
function getName(item: any) {
const name = basename(item.path);
if (name.endsWith(".exe")) {
return t(name.replace(".exe", ""));
} else {
return name;
}
}
const bottomApp = [
{
"isFile": true,
"isDirectory": false,
"isSymlink": false,
"size": 32,
"modTime": "2024-12-16T09:17:18.7214789+08:00",
"atime": "2024-12-16T09:17:18.7214789+08:00",
"birthtime": "2024-12-16T09:17:18.7214789+08:00",
"mtime": "2024-12-16T09:17:18.7214789+08:00",
"rdev": 0,
"mode": 511,
"name": "computer.exe",
"path": "/C/Users/Menulist/computer.exe",
"oldPath": "/C/Users/Menulist/computer.exe",
"parentPath": "/C/Users/Menulist",
"content": "link::Desktop::computer::diannao",
"ext": "exe",
"title": "computer",
"id": 1,
"isPwd": false
},
{
"isFile": true,
"isDirectory": false,
"isSymlink": false,
"size": 31,
"modTime": "2024-12-16T09:17:18.7214789+08:00",
"atime": "2024-12-16T09:17:18.7214789+08:00",
"birthtime": "2024-12-16T09:17:18.7214789+08:00",
"mtime": "2024-12-16T09:17:18.7214789+08:00",
"rdev": 0,
"mode": 511,
"name": "setting.exe",
"path": "/C/Users/Menulist/setting.exe",
"oldPath": "/C/Users/Menulist/setting.exe",
"parentPath": "/C/Users/Menulist",
"content": "link::Desktop::setting::setting",
"ext": "exe",
"title": "setting",
"id": 15,
"isPwd": false
},
{
"isFile": true,
"isDirectory": false,
"isSymlink": false,
"size": 30,
"modTime": "2024-12-16T09:17:18.7214789+08:00",
"atime": "2024-12-16T09:17:18.7214789+08:00",
"birthtime": "2024-12-16T09:17:18.7214789+08:00",
"mtime": "2024-12-16T09:17:18.7214789+08:00",
"rdev": 0,
"mode": 511,
"name": "localchat.exe",
"path": "/C/Users/Desktop/localchat.exe",
"oldPath": "/C/Users/Desktop/localchat.exe",
"parentPath": "/C/Users/Desktop",
"content": "link::Desktop::localchat::chat",
"ext": "exe",
"title": "localchat",
"id": 3,
"isPwd": false
}
];
const { createDesktopContextMenu } = useContextMenu();
const { choseStart, chosing, choseEnd, getRect, Chosen } = useRectChosen();
const system = useSystem();
@ -119,5 +231,32 @@ onErrorCaptured((err) => {
.bottom {
display: none;
}
.bottom-bar {
display: flex;
justify-content: space-evenly;
position: absolute;
width: vw(340);
left: 50%;
transform: translateX(-50%);
bottom: vh(15);
height: vh(80);
border-radius: vw(50);
color: #e5e3e3d5;
background-color: rgba(255, 255, 255, .2);
backdrop-filter: blur(15px);
.magnet-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.magnet-item_img {
width: vw(40);
height: vh(40);
}
}
}
}
</style>

4
frontend/src/components/window/WindowNode.vue

@ -1,13 +1,11 @@
<template>
<Transition name="windowanimate" appear>
<WindowTemplate v-if="window.windowInfo.isCreated && !isMobileDevice()" :browser-window="window"></WindowTemplate>
<MobileTemplate v-else></MobileTemplate>
<WindowTemplate v-if="window.windowInfo.isCreated" :browser-window="window"></WindowTemplate>
</Transition>
<WindowNode v-for="node in window.children" :key="node.id" :window="node" />
</template>
<script lang="ts" setup>
import { BrowserWindow } from "@/system/window/BrowserWindow";
import { isMobileDevice } from "@/util/device";
import { UnwrapNestedRefs } from "vue";
defineProps<{
window: UnwrapNestedRefs<BrowserWindow>;

264
frontend/src/components/window/WindowTemplate.vue

@ -1,95 +1,88 @@
<template>
<div
class="wintmp_outer dragwin"
:class="{
<div class="wintmp_outer dragwin" :class="{
topwin: istop,
max: windowInfo.state == WindowStateEnum.maximize,
min: windowInfo.state == WindowStateEnum.minimize,
fullscreen: windowInfo.state == WindowStateEnum.fullscreen,
noframe: !windowInfo.frame,
disable: windowInfo.disable,
}"
:style="customerStyle"
@touchstart.passive="onFocus"
@mousedown="onFocus"
ref="$win_outer"
v-dragable
>
}" :style="customerStyle" @touchstart.passive="onFocus" @mousedown="onFocus" ref="$win_outer" v-dragable>
<!-- 窗口标题栏 -->
<div
class="wintmp_uper"
@contextmenu.prevent
>
<div class="wintmp_uper" @contextmenu.prevent v-if="!isMobileDevice()">
<MenuBar :browser-window="browserWindow"></MenuBar>
</div>
<van-nav-bar v-else left-text="返回" left-arrow @click-left="onClickLeft" fixed>
<template #title>
<div class="title">
<FileIcon :icon="browserWindow.windowInfo.icon" />
{{ browserWindow.windowInfo.title }}
</div>
</template>
</van-nav-bar>
<div
class="wintmp_main"
:class="{ resizeing: resizemode != 'null', 'saveFileMain':browserWindow.windowInfo.footer}"
@mousedown.stop="predown"
@touchstart.stop.passive="predown"
@contextmenu.stop.prevent
>
<div
class="content-mask"
v-if="!istop && typeof browserWindow.content === 'string'"
></div>
<div class="wintmp_main"
:class="{ resizeing: resizemode != 'null', 'saveFileMain': browserWindow.windowInfo.footer }"
@mousedown.stop="predown" @touchstart.stop.passive="predown" @contextmenu.stop.prevent>
<div class="content-mask" v-if="!istop && typeof browserWindow.content === 'string'"></div>
<WindowInner :win="browserWindow"></WindowInner>
</div>
<!-- 使用 v-for 生成拖拽边界 -->
<div
v-for="border in dragBorders"
:key="border.type"
:class="[
<div v-for="border in dragBorders" :key="border.type" :class="[
border.class,
'win_drag_border',
{ isChoseMode: resizemode == border.type },
border.cursorClass,
]"
v-if="resizable"
draggable="false"
@mousedown.stop.prevent="startScale($event, border.type)"
@touchstart.stop.passive="startScale($event, border.type)"
></div>
]" v-if="resizable" draggable="false" @mousedown.stop.prevent="startScale($event, border.type)"
@touchstart.stop.passive="startScale($event, border.type)"></div>
<div class="wintmp_footer" v-if="browserWindow.windowInfo.footer">
<MenuFooter :browser-window="browserWindow" @translateSavePath="translateSavePath"></MenuFooter>
</div>
</div>
</template>
<script lang="ts" setup>
import { useSystem } from "@/system";
import { emitEvent } from "@/system/event";""
import {
import { useSystem } from "@/system";
import { emitEvent } from "@/system/event"; ""
import {
BrowserWindow,
WindowStateEnum,
} from "@/system/window/BrowserWindow";
import { ScaleElement } from "@/system/window/dom/ScaleElement";
import { vDragable } from "@/system/window/MakeDragable";
import {
} from "@/system/window/BrowserWindow";
import { ScaleElement } from "@/system/window/dom/ScaleElement";
import { vDragable } from "@/system/window/MakeDragable";
import {
computed,
onMounted,
onUnmounted,
provide,
ref,
UnwrapNestedRefs,
} from "vue";
import { useChooseStore } from "@/stores/choose";
import eventBus from '@/system/event/eventBus'
} from "vue";
import { useChooseStore } from "@/stores/choose";
import eventBus from '@/system/event/eventBus'
import { isMobileDevice } from "@/util/device";
const onClickLeft = () => {
if (choose.isExist(props.browserWindow.windowInfo.componentID)) {
choose.closeSaveFile(props.browserWindow.windowInfo.componentID)
}
props.browserWindow.destroy();
}
const sys = useSystem();
const props = defineProps<{
const sys = useSystem();
const props = defineProps<{
browserWindow: UnwrapNestedRefs<BrowserWindow>;
}>();
}>();
const browserWindow = props.browserWindow;
const windowInfo = browserWindow.windowInfo;
// const temp = reactive(browserWindow)
provide("browserWindow", browserWindow);
provide("system", sys);
const choose = useChooseStore()
const browserWindow = props.browserWindow;
if (isMobileDevice()) browserWindow.maximize()
const windowInfo = browserWindow.windowInfo;
console.log(windowInfo, 'windowInfo')
// const temp = reactive(browserWindow)
provide("browserWindow", browserWindow);
provide("system", sys);
const choose = useChooseStore()
function translateSavePath (path: string, name?: string) {
function translateSavePath(path: string, name?: string) {
if (browserWindow.windowInfo.footer) {
const pos = choose.saveFileContent.findIndex((item: any) => {
return item.componentID == browserWindow.windowInfo.componentID
@ -99,20 +92,20 @@
choose.saveFileContent[pos].filePath = path
} else if (name && name !== '') {
choose.saveFileContent[pos].fileName = name
eventBus.emit('saveFile', choose.saveFileContent[pos] )
eventBus.emit('saveFile', choose.saveFileContent[pos])
}
}
}
provide('translateSavePath', translateSavePath)
}
provide('translateSavePath', translateSavePath)
function predown() {
function predown() {
browserWindow.moveTop();
emitEvent("window.content.click", browserWindow);
}
}
const customerStyle = ref<NonNullable<unknown>>({});
const customerStyle = ref<NonNullable<unknown>>({});
function onFocus(e: MouseEvent | TouchEvent): void {
function onFocus(e: MouseEvent | TouchEvent): void {
browserWindow?.moveTop();
if (windowInfo.state === WindowStateEnum.maximize) {
if (e instanceof MouseEvent) {
@ -120,11 +113,11 @@
e.stopPropagation();
}
}
}
}
const istop = computed(() => windowInfo.istop);
const istop = computed(() => windowInfo.istop);
onMounted(() => {
onMounted(() => {
customerStyle.value = {
width: computed(() => windowInfo.width + "px"),
height: computed(() => windowInfo.height + "px"),
@ -138,13 +131,13 @@
}),
backgroundColor: computed(() => windowInfo.backgroundColor),
};
});
});
const resizable = ref(windowInfo.resizable);
const resizemode = ref("null");
let scaleAble: ScaleElement;
const resizable = ref(windowInfo.resizable);
const resizemode = ref("null");
let scaleAble: ScaleElement;
onMounted(() => {
onMounted(() => {
scaleAble = new ScaleElement(
resizemode,
windowInfo.width,
@ -165,9 +158,9 @@
);
}
);
});
});
function startScale(e: MouseEvent | TouchEvent, dire: string) {
function startScale(e: MouseEvent | TouchEvent, dire: string) {
console.log(e);
if (windowInfo.disable) {
return;
@ -180,13 +173,13 @@
windowInfo.width,
windowInfo.height
);
}
}
onUnmounted(() => {
onUnmounted(() => {
scaleAble.unMount();
});
});
const dragBorders = [
const dragBorders = [
{ type: "r", class: "right_border", cursorClass: "ew-resize" },
{ type: "b", class: "bottom_border", cursorClass: "ns-resize" },
{ type: "l", class: "left_border", cursorClass: "ew-resize" },
@ -199,17 +192,17 @@
{ type: "lb", class: "left_bottom_border", cursorClass: "nesw-resize" },
{ type: "lt", class: "left_top_border", cursorClass: "nwse-resize" },
{ type: "rt", class: "right_top_border", cursorClass: "nesw-resize" },
];
];
</script>
<style>
.dragwin {
.dragwin {
position: absolute;
width: 100%;
height: 100%;
}
}
</style>
<style scoped lang="scss">
.wintmp_outer {
.wintmp_outer {
position: absolute;
padding: 0;
margin: 0;
@ -225,6 +218,7 @@
border: var(--window-border);
display: flex;
flex-direction: column;
// box-shadow: var(--window-box-shadow);
// border-radius: var(--window-border-radius);
.wintmp_main {
@ -235,26 +229,29 @@
overflow: hidden;
contain: content;
}
.wintmp_footer {
position: relative;
}
}
.saveFileMain {
}
.saveFileMain {
:deep(.main) {
height: calc(100% - 60px);
}
}
.topwin {
}
.topwin {
// border: 1px solid #0078d7;
// box-shadow: var(--window-top-box-shadow);
}
}
.icon {
.icon {
width: 12px;
height: 12px;
}
}
.max {
.max {
position: absolute;
left: 0 !important;
top: 0 !important;
@ -262,8 +259,10 @@
height: 100% !important;
transition: left 0.1s ease-in-out, top 0.1s ease-in-out,
width 0.1s ease-in-out, height 0.1s ease-in-out;
}
.disable {
}
.disable {
.wintmp_footer,
.wintmp_uper,
.wintmp_main {
@ -271,13 +270,14 @@
user-select: none;
box-shadow: none;
}
}
.min {
}
.min {
visibility: hidden;
display: none;
}
}
.fullscreen {
.fullscreen {
//
position: fixed;
left: 0 !important;
@ -286,20 +286,22 @@
height: 100% !important;
z-index: 205 !important;
border: none;
.wintmp_uper {
display: none;
}
}
}
.noframe {
.noframe {
border: none;
box-shadow: none;
.wintmp_uper {
display: none;
}
}
}
.transparent {
.transparent {
background-color: transparent;
.wintmp_main {
@ -309,91 +311,109 @@
.wintmp_uper {
background-color: rgba(255, 255, 255, 0.774);
}
}
}
.win_drag_border {
.win_drag_border {
position: absolute;
background-color: rgba(0, 0, 0, 0);
}
}
.right_border {
.right_border {
cursor: ew-resize;
right: -12px;
width: 16px;
height: calc(100% - 4px);
}
}
.bottom_border {
.bottom_border {
cursor: ns-resize;
bottom: -12px;
width: calc(100% - 4px);
height: 16px;
}
}
.left_border {
.left_border {
cursor: ew-resize;
left: -12px;
width: 16px;
height: calc(100% - 4px);
}
}
.top_border {
.top_border {
cursor: ns-resize;
top: -12px;
width: calc(100% - 4px);
height: 16px;
}
}
.left_top_border {
.left_top_border {
cursor: nwse-resize;
left: -12px;
top: -12px;
width: 16px;
height: 16px;
}
}
.right_top_border {
.right_top_border {
cursor: nesw-resize;
right: -12px;
top: -12px;
width: 16px;
height: 16px;
}
}
.left_bottom_border {
.left_bottom_border {
cursor: nesw-resize;
left: -12px;
bottom: -12px;
width: 16px;
height: 16px;
}
}
.right_bottom_border {
.right_bottom_border {
cursor: nwse-resize;
right: -12px;
bottom: -12px;
width: 16px;
height: 16px;
}
}
.isChoseMode {
.isChoseMode {
width: 100vw;
height: 100vh;
position: fixed;
left: 0;
top: 0;
}
}
.resizeing {
.resizeing {
user-select: none;
pointer-events: none;
}
}
.content-mask {
.content-mask {
position: absolute;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0);
z-index: 100;
}
@media screen and (max-width: 768px) {
.wintmp_outer {
height: vh(100);
padding-top: vh(40);
}
.title {
height: vh(46);
display: flex;
align-items: center;
.icon {
width: 1em;
height: 1em;
}
}
}
</style>

26
frontend/src/main.ts

@ -1,24 +1,26 @@
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
import * as ElementPlusIconsVue from "@element-plus/icons-vue"
//svg插件需要配置代码
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import { createApp } from "vue";
import screenShort from "vue-web-screen-shot";
import App from "./App.vue";
import './assets/windows10.scss';
import { i18n } from './i18n/index.ts';
import pinia from './stores/index.ts';
import router from './system/router';
import ElementPlus from "element-plus"
import "element-plus/dist/index.css"
import { createApp } from "vue"
import screenShort from "vue-web-screen-shot"
import App from "./App.vue"
import "./assets/windows10.scss"
import { i18n } from "./i18n/index.ts"
import pinia from "./stores/index.ts"
import router from "./system/router"
import { NavBar } from "vant"
import "vant/lib/index.css"
const app = createApp(App)
app.use(router)
app.use(ElementPlus)
app.use(pinia)
app.use(i18n)
app.use(NavBar)
app.use(screenShort, { enableWebRtc: true })
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.mount("#app");
app.mount("#app")

Loading…
Cancel
Save