diff --git a/.umirc.ts b/.umirc.ts index 2fe8495..aff310b 100644 --- a/.umirc.ts +++ b/.umirc.ts @@ -31,5 +31,13 @@ export default defineConfig({ changeOrigin: true, secure: false, }, + '/gateway': { + target: 'http://127.0.0.1:7888', + changeOrigin: true, + secure: false, + pathRewrite: { + '^/gateway': '', + }, + }, }, }); diff --git a/electron/bg.png b/electron/bg.png new file mode 100644 index 0000000..af3e6de Binary files /dev/null and b/electron/bg.png differ diff --git a/electron/logo.ico b/electron/logo.ico new file mode 100644 index 0000000..bb7375e Binary files /dev/null and b/electron/logo.ico differ diff --git a/electron/main.js b/electron/main.js index 176d11b..f4bed4c 100644 --- a/electron/main.js +++ b/electron/main.js @@ -1,18 +1,19 @@ -const { app, BrowserWindow, dialog, ipcMain, shell } = require('electron'); +const { + app, + BrowserWindow, + dialog, + ipcMain, + shell, + Tray, + Menu, + screen, +} = require('electron'); const path = require('path'); const url = require('url'); const { initialStorageEvents, storage } = require('./storage'); -const { initialWebsocketEvents } = require('./socket'); -const { Subject } = require('./tool'); let mainWindow; -const socketSubjects = { - onMessage: new Subject(), - onError: new Subject(), - onClose: new Subject(), -}; - function createWindow() { //创建窗口 mainWindow = new BrowserWindow({ @@ -43,70 +44,91 @@ function createWindow() { // 加载html文件 // 这里的路径是umi输出的html路径,如果没有修改过,路径和下面是一样的 mainWindow.webContents.openDevTools(); - // mainWindow.loadFile(path.join(__dirname, '../dist/index.html')); - mainWindow.loadURL( - url.format({ - pathname: path.join(__dirname, '../dist/index.html'), - protocol: 'file:', - slashes: true, - }), - ); + mainWindow.loadFile(path.join(__dirname, '../dist/index.html')); + // mainWindow.loadURL( + // url.format({ + // pathname: path.join(__dirname, '../dist/index.html'), + // protocol: 'file:', + // slashes: true, + // }), + // ); } - const onMessageReceive = (message) => { - mainWindow.webContents.send('socket:on-message', message); - }; - - const onSocketError = (...args) => { - mainWindow.webContents.send('socket:on-error', ...args); - }; - - const onSocketClose = (...args) => { - mainWindow.webContents.send('socket:on-close', ...args); - }; mainWindow.webContents.on('did-finish-load', () => { // 加载初始localStorage数据 mainWindow.webContents.send('initialStorageData', storage.getAllItem()); - socketSubjects.onMessage.add(onMessageReceive); - socketSubjects.onError.add(onSocketError); - socketSubjects.onClose.add(onSocketClose); }); mainWindow.on('closed', () => { mainWindow = null; - socketSubjects.onMessage.remove(onMessageReceive); - socketSubjects.onError.remove(onSocketError); - socketSubjects.onClose.remove(onSocketClose); + }); + + // 创建系统通知区菜单 + tray = new Tray(path.join(__dirname, 'logo.ico')); + const contextMenu = Menu.buildFromTemplate([ + { + label: '最大化', + click: () => { + mainWindow.maximize(); + }, + }, + { + label: '最小化', + click: () => { + mainWindow.minimize(); + }, + }, + { + label: '还原', + click: () => { + mainWindow.restore(); + }, + }, + { + label: '退出', + click: () => { + mainWindow.destroy(); + notifyWindow.destroy(); + }, + }, //我们需要在这里有一个真正的退出(这里直接强制退出) + ]); + tray.setToolTip('LOCKING盒子'); + tray.setContextMenu(contextMenu); + tray.on('click', () => { + //我们这里模拟桌面程序点击通知区图标实现打开关闭应用的功能 + mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show(); + mainWindow.isVisible() + ? mainWindow.setSkipTaskbar(false) + : mainWindow.setSkipTaskbar(true); }); } -// 监听必要的自定义事件 -ipcMain.handle('manipulate-window', (event, { action }) => { - // console.log('manipulate-window', event, action); - const iWindow = BrowserWindow.fromId(event.frameId); - if (!iWindow) { - return; - } - if (action === 'close') { - // 关闭窗口 - iWindow.close(); - } - if (action === 'zoom') { - // 最大化/恢复窗口 - if (iWindow.isMaximized()) { - iWindow.unmaximize(); - } else { - iWindow.maximize(); - } - } - if (action === 'minimize') { - // 最小化窗口 - iWindow.minimize(); +// 窗口操作事件 +ipcMain.handle('window:close', (event) => { + const iWindow = BrowserWindow.fromId(event.sender.id); + iWindow.hide(); + if (iWindow === mainWindow) { + iWindow.setSkipTaskbar(true); } + event.preventDefault(); + // BrowserWindow.fromId(event.sender.id)?.close(); +}); +ipcMain.handle('window:zoom', (event) => { + const iWindow = BrowserWindow.fromId(event.sender.id); + if (!iWindow) return; + iWindow.isMaximized() ? iWindow.unmaximize() : iWindow.maximize(); +}); +ipcMain.handle('window:minimize', (event) => { + BrowserWindow.fromId(event.sender.id)?.minimize(); +}); +ipcMain.handle('window:hide', (event) => { + // console.log(event.sender.id, event.senderFrame, event); + BrowserWindow.fromId(event.sender.id)?.hide(); }); + // 选择文件夹 -ipcMain.handle('project-choose-folders', async (event, args) => { +ipcMain.handle('select-folder', async (event, args) => { const res = await dialog.showOpenDialog({ - properties: ['multiSelections', 'openDirectory'], + properties: ['openDirectory'], }); return res; }); @@ -114,34 +136,80 @@ ipcMain.handle('project-choose-folders', async (event, args) => { ipcMain.handle('open-browser', (event, url) => { shell.openExternal(url); }); +// 打开文件所在位置 +ipcMain.handle('open-file-position', (event, message) => { + shell.showItemInFolder(message.absolutePath); +}); + +// 主窗口给消息窗口notifyWindow发送消息 +ipcMain.handle('notify', (event, messageObj) => { + // if (!mainWindow.isVisible()) { // 在主窗口不可见时才给消息窗口返送消息 + notifyWindow.show(); + notifyWindow.webContents.send('on-notify', messageObj); + // } +}); +// 消息窗口给主窗口发送重新同步文件的命令 +ipcMain.handle('re-sync-file', (event, messageObj) => { + mainWindow.webContents.send('request-resync-file', messageObj); +}); + +ipcMain.handle('download-file', (event, messageObj) => { + mainWindow.webContents.send('request-download-file', messageObj); +}); // 初始化electron-store相关API initialStorageEvents(ipcMain); app.on('ready', () => { + const { width: windowWidth, height: windowHeight } = + screen.getPrimaryDisplay().workAreaSize; createWindow(); - + createNotifycationWindow(windowWidth, windowHeight); app.on('activate', function () { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); }); + +app.on('second-instance', (event, commandLine, workingDirectory) => { + // 当运行第二个实例时,将会聚焦到mainWindow这个窗口 + if (mainWindow) { + if (mainWindow.isMinimized()) mainWindow.restore(); + mainWindow.focus(); + mainWindow.show(); + } +}); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); -initialWebsocketEvents( - ipcMain, - function onMessage(message) { - socketSubjects.onMessage.notify(message); - }, - function onError(...args) { - socketSubjects.onError.notify(...args); - }, - function onClose(...args) { - socketSubjects.onClose.notify(...args); - }, -); +let notifyWindow; +function createNotifycationWindow(windowWidth, windowHeight) { + if (notifyWindow) return notifyWindow; + + //创建窗口 + notifyWindow = new BrowserWindow({ + width: 504, + height: 219, // 184, + x: windowWidth - 480 - 20, + y: windowHeight - 219 + 10, + webPreferences: { + preload: path.join(__dirname, 'preload.js'), + webSecurity: false, + nodeIntegration: true, + }, + frame: false, + resizable: false, + transparent: true, + alwaysOnTop: true, + }); + // 这里的路径是umi输出的html路径,如果没有修改过,路径和下面是一样的 + notifyWindow.webContents.openDevTools(); + notifyWindow.loadFile(path.join(__dirname, 'notifycation.html')); + notifyWindow.setSkipTaskbar(true); + notifyWindow.hide(); + // return notifyWindow; +} diff --git a/electron/notifycation.html b/electron/notifycation.html new file mode 100644 index 0000000..ea74baf --- /dev/null +++ b/electron/notifycation.html @@ -0,0 +1,350 @@ + + + + + + + + Document + + + + +
+
+
+

消息通知

+
+
+ 文件名称 + 21/08/20 16:34:21 +
+
+
+ + + 同步 +
+
+ +
+ +
+
+
+
+ 该文件存在版本冲突,请先下载该文件最新版本,修改后再上传! +
+
+ 下载 +
+
+
+
+ + + + \ No newline at end of file diff --git a/electron/socket.js b/electron/socket.js index 9e2d05c..b27f69b 100644 --- a/electron/socket.js +++ b/electron/socket.js @@ -1,3 +1,5 @@ +// DEPRECATED +// 现在socket链接改在页面发起 const io = require('ws'); const config = require('./config'); @@ -11,7 +13,7 @@ function initialWebsocket(onMessage, onError, onClose) { // socket.send('1'); }); socket.on('message', (message) => { - console.log('socket message', message); + // console.log('socket message', message); onMessage(message); }); socket.on('error', (...args) => { diff --git a/src/app.ts b/src/app.ts index 40afb08..884fad9 100644 --- a/src/app.ts +++ b/src/app.ts @@ -5,7 +5,7 @@ import { firstCharToLowerCase, handleRequest } from './utils/tool'; import { isObject } from 'lodash'; import { logout, queryCurrent } from './services/user'; import storage from './utils/storage'; -import './services/socket'; +import { initialWebsocket } from './services/socket'; const codeMessage = { 200: '服务器成功返回请求的数据。', @@ -134,3 +134,6 @@ export async function getInitialState(): Promise<{ fetchUserInfo, }; } + +// 链接socket +initialWebsocket(); diff --git a/src/components/AppHeader/components/userCenter/FolderSetModal.tsx b/src/components/AppHeader/components/userCenter/FolderSetModal.tsx index 97ef1cc..2b36bdc 100644 --- a/src/components/AppHeader/components/userCenter/FolderSetModal.tsx +++ b/src/components/AppHeader/components/userCenter/FolderSetModal.tsx @@ -1,10 +1,42 @@ import React from 'react'; -import { Modal, ModalProps, Input, Button } from 'antd'; +import { Modal, ModalProps, Input, Button, message } from 'antd'; import styles from './FolderSetModal.less'; import classNames from 'classnames'; +import { useCallback } from 'react'; +import system from '@/services/system'; +import { useState } from 'react'; +import { useEffect } from 'react'; export default function FolderSetModal(props: ModalProps) { const { className, ...restProps } = props; + const [folderPath, setFolderPath] = useState(''); + const [loading, setLoading] = useState(false); + + const onSetFolderPos = useCallback(async () => { + const nextPath = await system.chooseFolder(); + console.log(nextPath); + if (nextPath) { + setFolderPath(nextPath); + } + }, []); + + useEffect(() => { + // todo: 查询当前使用的路径 + }, []); + + const updateFolderPath = useCallback(() => { + if (!folderPath) { + message.error('请选择文件夹'); + return; + } + setLoading(true); + // todo: 调接口 + setLoading(false); + if (restProps.onCancel) { + restProps.onCancel(); + } + }, [folderPath]); + return ( 「工作空间」本地盘位置 - 选择} /> + + 选择 + + } + />
-
diff --git a/src/components/FileStatus/FileStatus.less b/src/components/FileStatus/FileStatus.less index c9a9eb5..21b2482 100644 --- a/src/components/FileStatus/FileStatus.less +++ b/src/components/FileStatus/FileStatus.less @@ -2,7 +2,7 @@ display: flex; align-items: center; flex-direction: row; - background: #FFFFFF; + background: #ffffff; box-shadow: 0px 1px 2px 0px rgba(102, 110, 115, 0.3); border-radius: 4px; height: 76px; @@ -27,7 +27,11 @@ } .left { height: 100%; - .icon { width:36px; margin-right: 12px;} + .icon { + width: 36px; + margin-right: 12px; + height: 100%; + } .content { display: inline-flex; width: calc(100% - 36px - 12px); @@ -79,8 +83,12 @@ vertical-align: sub; margin-right: 8px; font-size: 16px; - &.success { color: #51DCB6; } - &.error {color: #D6243A; } + &.success { + color: #51dcb6; + } + &.error { + color: #d6243a; + } } } .time { @@ -91,11 +99,8 @@ transform: scale(0.83); } - - .textOverflow { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } - diff --git a/src/components/FileStatus/FileStatus.tsx b/src/components/FileStatus/FileStatus.tsx index 5d21855..9910cf8 100644 --- a/src/components/FileStatus/FileStatus.tsx +++ b/src/components/FileStatus/FileStatus.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames'; -import dayjs from 'dayjs'; +import dayjs, { Dayjs } from 'dayjs'; import React, { CSSProperties, useMemo } from 'react'; import { memo } from 'react'; import FileIcon from '../FileIcon'; @@ -19,8 +19,6 @@ interface FileStatusProps { export default function FileStatus(props: FileStatusProps) { const { className, style, data, ...restProps } = props; - console.log(data); - const filePath = useMemo(() => { return [data.projName, data.nodeName, data.relativePath] .filter(identity) @@ -57,11 +55,16 @@ export default function FileStatus(props: FileStatusProps) { {...restProps} /> {data.taskSyncStatus === TaskStatus.FINISH ? ( -