Browse Source

feat: 应用初始化窗口完善

kindring 10 months ago
parent
commit
2fa429291a

+ 3 - 10
src/App.vue

@@ -1,22 +1,15 @@
 <script setup lang="ts">
-import HelloWorld from './components/HelloWorld.vue'
+// import HelloWorld from './components/HelloWorld.vue'
 
 import { onMounted } from "vue";
+import MacWindow from "./components/window/macWindow.vue";
 onMounted(() => {
 
 });
 </script>
 
 <template>
-  <div>
-    <a href="https://vitejs.dev" target="_blank">
-      <img src="/vite.svg" class="logo" alt="Vite logo" />
-    </a>
-    <a href="https://vuejs.org/" target="_blank">
-      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
-    </a>
-  </div>
-  <HelloWorld msg="Vite + Vue" />
+  <mac-window :title="'test'"/>
 </template>
 
 <style scoped>

+ 94 - 0
src/assets/base.css

@@ -0,0 +1,94 @@
+.drag{
+    -webkit-app-region: drag;
+}
+.no-drag{
+    -webkit-app-region: no-drag;
+}
+.window{
+    width: 100%;
+    height: 100%;
+    background: #fff;
+    border-radius: 3px;
+    box-shadow: 0 0 3px #000;
+    position: relative;
+    overflow: hidden;
+}
+.top-bar{
+    width: 100%;
+    height: 30px;
+    background: #eee;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+}
+.top-bar .drag{
+    padding-left: 5px;
+    display: flex;
+    width: 100%;
+    /*    运行拖拽*/
+}
+/*.top-bar*/
+.control-box{
+    width: auto;
+    height: 100%;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    flex-shrink: 1;
+}
+.control-box .btn{
+    width: 20px;
+    height: 20px;
+    margin: 0 5px;
+    border-radius: 50%;
+    cursor: pointer;
+    position: relative;
+}
+.control-box .ding{
+    background-color: #f1c40f;
+}
+.control-box .ding-is{
+    background-color: #f39c12;
+}
+.control-box .min{
+    background-color: #2ecc71;
+}
+.control-box .full{
+    background-color: #3498db;
+}
+.control-box .full-is{
+    background-color: #2980b9;
+}
+.control-box .close{
+    background-color: #e74c3c;
+}
+.control-box .btn .showTip{
+    position: absolute;
+    display: block;
+    width: 90px;
+    top: 20px;
+    right: -100%;
+    height: 20px;
+    padding: 0 5px;
+    box-shadow: 1px 0 4px #000;
+    line-height: 20px;
+    text-align: center;
+    font-size: 12px;
+    color: #fff;
+    background-color: #000;
+    border-radius: 5px;
+    opacity: 0;
+    transition: all 0.3s;
+}
+.control-box .btn:hover .showTip{
+    top: 30px;
+    opacity: 1;
+}
+
+.window-content{
+    width: 100%;
+    height: calc(100% - 30px);
+    overflow: hidden;
+    position: relative;
+    /*    允许被点击*/
+}

+ 1 - 0
src/common/appConfig.ts

@@ -4,6 +4,7 @@ import Path from "path";
 import Logger from '../util/logger';
 import fs from "fs";
 import path from "path";
+import {AppConfig} from "@/types/appConfig.ts";
 
 
 let logger = Logger.logger('config', 'info');

+ 64 - 0
src/components/window/macWindow.vue

@@ -0,0 +1,64 @@
+<script setup lang="ts">
+import {ComponentInternalInstance, getCurrentInstance, Ref, ref, UnwrapRef} from "vue";
+import {actionMap, IpcAction, windowAction} from "../../tools/IpcCmd.ts";
+
+import "@/assets/base.css"
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance
+defineProps<{
+  title: string,
+}>()
+const isDing: Ref<UnwrapRef<boolean>> = ref(false)
+const isFull: Ref<UnwrapRef<boolean>> = ref(false)
+
+// 定义函数
+const switchDingHandle = () => {
+  console.log("置顶切换")
+  proxy?.$winHandle(isDing.value? windowAction.unDing : windowAction.ding)
+  isDing.value = !isDing.value
+}
+const switchFullHandle = () => {
+  console.log("全屏切换")
+  proxy?.$winHandle(isFull.value? windowAction.unMax : windowAction.max)
+  isFull.value = !isFull.value
+
+}
+const btnClickHandel = (action: IpcAction) => {
+  proxy?.$winHandle(action)
+}
+</script>
+
+<template>
+  <div class="window">
+    <div class="top-bar">
+      <div class="drag flex items-center" >
+<!--        <svg-icon :icon-class="icon"/>-->
+        <span class="ml-1.5 ">{{title}}</span>
+
+      </div>
+
+      <div class="control-box">
+        <slot name="top"></slot>
+        <div :class="`no-drag btn ding ${isDing?'ding-is':''}`"  @click="switchDingHandle">
+          <span class="showTip">{{isDing?"取消置顶":"置顶"}}</span>
+        </div>
+        <div class="no-drag btn min" @click="btnClickHandel(windowAction.min)">
+          <span class="showTip">最小化</span>
+        </div>
+        <div :class="`no-drag btn full ${isFull?'full-is':''}`"  @click="switchFullHandle">
+          <span class="showTip">{{isFull?'取消全屏':'全屏'}}</span>
+        </div>
+        <div class="no-drag btn close"  @click="btnClickHandel(windowAction.close)">
+          <span class="showTip">关闭窗口</span>
+        </div>
+      </div>
+    </div>
+    <div class="window-content">
+      <slot></slot>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+
+</style>

+ 127 - 57
src/main/AppControl.ts

@@ -1,14 +1,17 @@
 import Logger from "../util/logger";
-import {BrowserWindow} from "electron";
+import { BrowserWindow, globalShortcut, Menu, Tray} from "electron";
 import {CustomScheme} from "./CustomScheme";
 import {getAvailablePort} from "./tools/port.ts";
 import {FcServer, startServer} from "./server/httpServer.ts";
-import {AppWindow, AppConfig} from "../types/appConfig.ts";
+import {AppWindow, AppConfig, HotKeyConfig} from "../types/appConfig.ts";
 import {randomId} from "../util/random.ts";
 import {actionMap} from "../tools/IpcCmd.ts";
 import BrowserWindowConstructorOptions = Electron.BrowserWindowConstructorOptions;
 import {initIpc} from "./tools/ipcInit.ts";
 import {initHook} from "./tools/hookInit.ts";
+import hook from "@/util/hook.ts";
+import path from "path";
+import Path from "path";
 
 let logger = Logger.logger('controlWindow', 'info');
 
@@ -21,6 +24,28 @@ let _appConfig: AppConfig;
 let _winArr: AppWindow[] = [];
 let checkTimer: NodeJS.Timeout;
 let isExitAppTask = false;// 是否处于退出任务中
+
+const defaultWin : AppWindow = {
+    id: '',
+    sign: '',
+    parentSign: '',
+    type: '',
+    title: '未知窗口',
+    description: '窗口描述文件',
+    win: null,
+    isMain: false,
+    timer: null,// 等待销毁计时器
+    hide: false,// 是否隐藏
+    isConnected: false,// 是否已经建立连接
+    isUsed: false,// 是否被使用中,用于复用窗口
+    destroyWait: 30,
+    style: {
+        width: 0,
+        height: 0,
+        x: 0,
+        y: 0
+    }
+}
 function _generate_unique_window_id(){
     let id = randomId();
     let ind = -1;
@@ -111,29 +136,8 @@ function winTryConnect(): void {
 function registerApp(newApp: Electron.App) {
     _app = newApp;
 }
-
 function registerWin(windowConfig: AppWindow): AppWindow{
-    let defaultWin : AppWindow = {
-        id: '',
-        sign: '',
-        parentSign: '',
-        type: '',
-        title: '未知窗口',
-        description: '窗口描述文件',
-        win: null,
-        isMain: false,
-        timer: null,// 等待销毁计时器
-        hide: false,// 是否隐藏
-        isConnected: false,// 是否已经建立连接
-        isUsed: false,// 是否被使用中,用于复用窗口
-        destroyWait: 30,
-        style: {
-            width: 0,
-            height: 0,
-            x: 0,
-            y: 0
-        }
-    }
+
     let finalWindow = {...defaultWin, ...windowConfig }
     finalWindow.id = _generate_unique_window_id();
     finalWindow.sign = `${finalWindow.type}:${finalWindow.id}`;
@@ -149,10 +153,62 @@ function registerWin(windowConfig: AppWindow): AppWindow{
 
     winTryConnect();
 
-
-
     return finalWindow;
 }
+function registerHotKey(hotKey: HotKeyConfig, mainWin: AppWindow){
+    if(mainWin.win == null){
+        logger.error(`注册快捷键失败,窗口${mainWin.sign}不存在`);
+        return false;
+    }
+    // 注册快捷键
+    logger.info(`注册显示窗口快捷键 ${hotKey.show}`)
+    globalShortcut.register(hotKey.show, function() {
+        // 隐藏,与显示窗口
+        if(mainWin.win?.isVisible()){
+            hook.runHook(actionMap.hide.code, mainWin.sign).then(r => r)
+        }else{
+            hook.runHook(actionMap.show.code, mainWin.sign).then(r => r)
+        }
+    })
+    logger.info(`最小化窗口快捷键 ${hotKey.min}`)
+    globalShortcut.register(hotKey.min, function() {
+        // 最小化窗口和恢复窗口
+        if (mainWin.win) {
+            if (!mainWin.win.isMinimized()) {
+                logger.info(`最小化窗口 ${mainWin.sign}`);
+                hook.runHook(actionMap.min.code, mainWin.sign).then(r => r)
+            } else {
+                hook.runHook(actionMap.restore.code, mainWin.sign).then(r => r)
+            }
+        }
+    })
+    return true
+}
+function _createTray(app: Electron.App, mainWin: AppWindow){
+    const appPath = app.isPackaged ? path.dirname(app.getPath('exe')) : app.getAppPath();
+
+    logger.info(`[托盘挂载] appPath:${appPath}`);
+    // 创建系统托盘
+    const iconPath = Path.resolve( appPath,`/logo.ico`);
+    mainWin.tray = new Tray(iconPath);
+    mainWin.tray.setToolTip('fc-ele');
+    const contextMenu = Menu.buildFromTemplate([
+        {
+            label: '显示主窗口',
+            click: () => {
+                hook.runHook(actionMap.show.code, mainWin.sign).then(r => r)
+            }
+        },
+        {
+            label: '退出',
+            click: () => {
+                hook.runHook(actionMap.exitApp.code,mainWin.sign).then(r => r)
+            }
+        }
+    ]);
+    // 载入托盘菜单
+    mainWin.tray.setContextMenu(contextMenu);
+}
 
 function _createMainWindow(){
     let MainUrl : string = `app://index.html`
@@ -169,7 +225,7 @@ function _createMainWindow(){
         height: 620,
         frame: false, //任务栏是否显示
         show: true,
-        transparent: false, //无边框
+        transparent: true, //无边框
         resizable: false,// 禁止 重新设置窗口大小
         maximizable: false, //禁止最大化
         webPreferences: {
@@ -187,6 +243,39 @@ function _createMainWindow(){
     mainWindow.webContents.openDevTools();
     return mainWindow;
 }
+
+
+async function exit(){
+    logger.info(`[应用退出] 应用退出中....`);
+    if(!_app){
+        logger.error(`[应用退出] 无法找到主应用. 非常离奇的情况, 按理说不应该这样的`);
+        return 0;
+    }
+    isExitAppTask = true;
+    // fixme: 修复退出软件时,窗口不会关闭的问题. 以及引用问题
+    while(_winArr.length > 0){
+        let winObj = _winArr.pop() as AppWindow;
+        if(!winObj.win){
+            logger.error(`[应用退出] 无法找到窗口对象, 需要修复呢`);
+            continue;
+        }
+        // if(winObj.type === 'top'){
+        //     // 从topWinArr 中移除窗口对象
+        //     exitTopWin(winObj);
+        // }
+        // 移除所有win的监听事件
+        winObj.win.removeAllListeners();
+        winObj.win.close();
+        winObj.win.destroy();
+        winObj.win = null;
+    }
+    console.log(`退出软件`);
+    // 清理窗口
+    _app.quit();
+    return 0;
+}
+
+
 export async function initApp(appConfig: AppConfig, app: Electron.App) : Promise<AppWindow | null>{
     logger.info('start init control window');
     let mainWindow : BrowserWindow = _createMainWindow();
@@ -215,49 +304,30 @@ export async function initApp(appConfig: AppConfig, app: Electron.App) : Promise
 
     // 创建主窗口
     let mainWin = registerWin({
+        ...defaultWin,
         type: 'main',
         title: '主进程窗口',
         win: mainWindow,
         isMain: true,
+
     });
 
     // 绑定主进程
     registerApp(app)
 
+    // 注册托盘
+    _createTray(app, mainWin)
 
+    // 注册快捷键
+    if(registerHotKey(appConfig.hotKey, mainWin)){
+        logger.info(`[应用初始化] 快捷键注册完成`);
+    }
 
-    return mainWin;
-}
 
 
-async function exit(){
-    logger.info(`[应用退出] 应用退出中....`);
-    if(!_app){
-        logger.error(`[应用退出] 无法找到主应用. 非常离奇的情况, 按理说不应该这样的`);
-        return 0;
-    }
-    isExitAppTask = true;
-    // fixme: 修复退出软件时,窗口不会关闭的问题. 以及引用问题
-    while(_winArr.length > 0){
-        let winObj = _winArr.pop() as AppWindow;
-        if(!winObj.win){
-            logger.error(`[应用退出] 无法找到窗口对象, 需要修复呢`);
-            continue;
-        }
-        // if(winObj.type === 'top'){
-        //     // 从topWinArr 中移除窗口对象
-        //     exitTopWin(winObj);
-        // }
-        // 移除所有win的监听事件
-        winObj.win.removeAllListeners();
-        winObj.win.close();
-        winObj.win.destroy();
-        winObj.win = null;
-    }
-    console.log(`退出软件`);
-    // 清理窗口
-    _app.quit();
-    return 0;
+    logger.info(`[应用初始化] 初始化完成`);
+
+    return mainWin;
 }
 
 

+ 4 - 1
src/main/mainEntry.ts

@@ -56,7 +56,10 @@ function handle_ready(){
         // }
     }
     logger.info("[fc-ele] app ready")
-    startApp();
+    setTimeout(() => {
+        startApp();
+    }, 100);
+    // startApp();
 }
 
 async function startApp(){

+ 10 - 6
src/main/tools/doWindowAction.ts

@@ -94,14 +94,14 @@ export function maxWin(sign: string): Promise<boolean> {
         try {
             if (!winObj.win.isMaximizable()) {
                 let size = winObj.win.getSize();
-                winObj.style = {
-                    width: size[0],
-                    height: size[1]
-                }
                 // 获取位置
                 let pos = winObj.win.getPosition();
+                winObj.style.width = size[0];
+                winObj.style.height = size[1];
                 winObj.style.x = pos[0];
                 winObj.style.y = pos[1];
+                console.log(winObj.style)
+                // 修改窗口状态
                 winObj.win.maximize();
                 resolve(true)
             } else {
@@ -130,8 +130,12 @@ export function unMaxWin(sign: string): Promise<boolean> {
                     y: 0,
                 }
             }
-            winObj.win.setContentSize(winObj.style.width??1000, winObj.style.height??700);
-            winObj.win.setPosition(winObj.style.x??0, winObj.style.y??0);
+            // 如果是未启用无边框窗口则恢复窗口尺寸
+            // restoreWin(sign);
+
+            // 如果是启用了 无边框 以及透明窗口则设置窗口尺寸
+            winObj.win.setContentSize(winObj.style.width, winObj.style.height);
+            winObj.win.setPosition(winObj.style.x, winObj.style.y);
             // winObj.win.center(); // 窗口居中
             resolve(true);
         } catch (error) {

+ 25 - 21
src/style.css

@@ -5,13 +5,30 @@
 
   color-scheme: light dark;
   color: rgba(255, 255, 255, 0.87);
-  background-color: #242424;
+  /*background-color: #242424;*/
 
   font-synthesis: none;
   text-rendering: optimizeLegibility;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  -webkit-text-size-adjust: 100%;
+}
+
+*{
+  /*box-size: border-box;*/
+  margin: 0;
+  padding: 0;
+}
+html , body{
+  font-size: 16px;
+  width: 100%;
+  height: 100%;
+  /* electron no frame */
+  overflow: hidden;
+}
+
+#app {
+  width: calc( 100% - 10px);
+  height:  calc( 100% - 10px);
+  box-sizing: border-box;
+  margin: 5px;
 }
 
 a {
@@ -23,13 +40,7 @@ a:hover {
   color: #535bf2;
 }
 
-body {
-  margin: 0;
-  display: flex;
-  place-items: center;
-  min-width: 320px;
-  min-height: 100vh;
-}
+
 
 h1 {
   font-size: 3.2em;
@@ -55,21 +66,14 @@ button:focus-visible {
   outline: 4px auto -webkit-focus-ring-color;
 }
 
-.card {
-  padding: 2em;
-}
 
-#app {
-  max-width: 1280px;
-  margin: 0 auto;
-  padding: 2rem;
-  text-align: center;
-}
+
+
 
 @media (prefers-color-scheme: light) {
   :root {
     color: #213547;
-    background-color: #ffffff;
+    /*background-color: #ffffff;*/
   }
   a:hover {
     color: #747bff;

+ 7 - 7
src/types/appConfig.ts

@@ -1,6 +1,6 @@
 import {BrowserWindow} from "electron";
 
-interface HotKeyConfig {
+export interface HotKeyConfig {
     show: string;
     min: string;
 }
@@ -49,13 +49,13 @@ export interface AppWindow {
     isUsed?: boolean;// 是否被使用中,用于复用窗口
     destroyWait?: number;
     isQueryClose?: boolean;// 窗口是否在询问关闭中
-    style?: {
-        width?: number;
-        height?: number;
-        x?: number;
-        y?: number;
+    tray?: Electron.Tray | null;
+    style: {
+        width: number;
+        height: number;
+        x: number;
+        y: number;
     }
-
 }
 
 

+ 10 - 0
src/types/exVueType.ts

@@ -0,0 +1,10 @@
+import vue from 'vue'; // eslint-disable-line no-unused-vars
+import {IpcAction} from "../tools/IpcCmd.ts";
+
+declare module '@vue/runtime-core' {
+    // 扩展全局变量的接口内容,需要扩展ComponentCustomProperties这个接口,不要乱改成别的
+
+    interface ComponentCustomProperties {
+        $winHandle: (action: IpcAction) => void,
+    }
+}

BIN
static/logo.ico


+ 8 - 1
tsconfig.json

@@ -18,7 +18,14 @@
     "strict": true,
     "noUnusedLocals": true,
     "noUnusedParameters": true,
-    "noFallthroughCasesInSwitch": true
+    "noFallthroughCasesInSwitch": true,
+
+    // alias
+    "paths": {
+      "@/*": [
+        "./src/*"
+      ]
+    }
   },
   "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "plugins/*.ts", "plugins/**/*.ts"],
   "references": [{ "path": "./tsconfig.node.json" }]

+ 10 - 5
vite.config.ts

@@ -6,16 +6,21 @@ import {buildPlugin} from "./plugins/buildPlugin";
 import { devPlugin, getReplacer } from "./plugins/devPlugin";
 
 export default defineConfig({
-  plugins: [
-      optimizer(getReplacer()),
-      devPlugin(),
-      vue()
-  ],
+      plugins: [
+          optimizer(getReplacer()),
+          devPlugin(),
+          vue()
+      ],
     build: {
         rollupOptions: {
             plugins: [buildPlugin()],
         },
     },
+    resolve: {
+        alias: {
+            "@": "./src",
+        },
+    },
 
     server: {
         host: '127.0.0.1',