Browse Source

'大致完成消息窗口的整体开发;调整了socket的整体设计,改为主窗口发起socket;'

main
郑州 3 years ago
parent
commit
1b307299ec
16 changed files with 676 additions and 122 deletions
  1. +8
    -0
      .umirc.ts
  2. BIN
      electron/bg.png
  3. BIN
      electron/logo.ico
  4. +139
    -71
      electron/main.js
  5. +350
    -0
      electron/notifycation.html
  6. +3
    -1
      electron/socket.js
  7. +4
    -1
      src/app.ts
  8. +48
    -3
      src/components/AppHeader/components/userCenter/FolderSetModal.tsx
  9. +12
    -7
      src/components/FileStatus/FileStatus.less
  10. +9
    -6
      src/components/FileStatus/FileStatus.tsx
  11. +16
    -4
      src/pages/sync/index.tsx
  12. +5
    -0
      src/services/API.d.ts
  13. +46
    -12
      src/services/socket.ts
  14. +30
    -10
      src/services/system.ts
  15. +3
    -5
      src/services/user.ts
  16. +3
    -2
      src/utils/request.ts

+ 8
- 0
.umirc.ts View File

@@ -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': '',
},
},
},
});

BIN
electron/bg.png View File

Before After
Width: 504  |  Height: 219  |  Size: 8.6 KiB

BIN
electron/logo.ico View File

Before After

+ 139
- 71
electron/main.js View File

@@ -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;
}

+ 350
- 0
electron/notifycation.html View File

@@ -0,0 +1,350 @@
<!DOCTYPE html>
<html lang="en">

<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>Document</title>
<style>
* {
margin: 0;
padding: 0;
}

#board {
position: relative;
width: 504px;
height: 219px;
padding: 9px 12px 15px;
background: url(./bg.png) no-repeat;
box-sizing: border-box;
}

.inner {
position: relative;
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
}

#close-icon {
position: absolute;
width: 24px;
height: 24px;
top: 24px;
right: 32px;
cursor: pointer;
z-index: 1;
}

.close-icon:before,
.close-icon:after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
width: 16px;
height: 2px;
background-color: #fff;
}

.close-icon:before {
transform: rotate(45deg);
}

.close-icon:after {
transform: rotate(-45deg);
}

h3 {
flex: none;
height: 52px;
line-height: 52px;
color: #fff;
font-size: 20px;
padding-left: 20px;
}

#syncMsg {
display: flex;
flex-direction: column;
justify-content: space-evenly;
flex: 1;
}

.line {
display: flex;
font-size: 14px;
line-height: 22px;
flex-direction: row;
justify-content: space-between;
padding: 0 20px;
}

.primary-btn {
font-size: 16px;
color: #7850FF;
cursor: pointer;
}

.err-icon {
position: relative;
border-radius: 50%;
display: inline-block;
vertical-align: top;
height: 24px;
width: 24px;
background-color: #FF3F17;
}

.succ-icon {
position: relative;
border-radius: 50%;
display: inline-block;
vertical-align: top;
height: 24px;
width: 24px;
background: #51DCB6;

}

.succ-icon:before,
.succ-icon:after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
width: 16px;
height: 2px;
background-color: #fff;
}

.succ-icon:before {
width: 7px;
transform: translate(-3px, 2px) rotate(41deg);
}

.succ-icon:after {
width: 10px;
transform: translateX(2px) rotate(-60deg);
}

.label {
margin-left: 8px;
}

#conflictMsg {
display: flex;
flex-direction: row;
/* justify-content: space-evenly; */
flex: 1;
padding: 0 20px;
align-items: center;
}

#conflictMsg .right {
flex: none;
margin-left: 72px;
}

#conflictMsg .left {
flex: 1;
font-size: 20px;
color: rgba(0, 0, 0, 0.85);
}

.hide {
display: none !important;
}

#syncMsg.success .err-icon {
display: none;
}

#syncMsg.success .label:after {
content: '成功!';
}

#syncMsg.success .primary-btn:after {
content: '查看';
}

#syncMsg.error .succ-icon {
display: none;
}

#syncMsg.error .label:after {
content: '失败';
}

#syncMsg.error .primary-btn:after {
content: '重新同步';
}

#syncMsg.incomming #syncIncoming {
font-size: 20px;
color: rgba(0, 0, 0, 0.85);
}

#syncMsg.incomming .primary-btn:after {
content: '下载';
}
</style>
</head>

<body>
<div id="board">
<div id="close-icon" class="close-icon"></div>
<div class="inner">
<h3>消息通知</h3>
<div id="syncMsg">
<div class="line">
<span id="syncMsgFileName">文件名称</span>
<span id="syncMsgDate">21/08/20 16:34:21</span>
</div>
<div class="line">
<div id="syncResult">
<span class="err-icon close-icon"></span>
<span class="succ-icon"></span>
<span class="label">同步</span>
</div>
<div id="syncIncoming">

</div>
<span id="syncMsgBtn" class="primary-btn"></span>
</div>
</div>
<div id="conflictMsg">
<div class="left" id="conflictMsgText">
该文件存在版本冲突,请先下载该文件最新版本,修改后再上传!
</div>
<div class="right">
<span id="conflictMsgBtn" class="primary-btn">下载</span>
</div>
</div>
</div>
</div>
<script type="text/javascript">
let contextMsg;
const noop = () => { };
const domId = document.getElementById.bind(document);
const closeBtn = domId('close-icon');

const syncMsgView = domId('syncMsg');
const syncMsgBtn = domId('syncMsgBtn');
const syncMsgFileName = domId('syncMsgFileName');
const syncMsgDate = domId('syncMsgDate');
const syncResult = domId('syncResult');
const syncIncoming = domId('syncIncoming');

const conflictMsgView = domId('conflictMsg');
const conflictMsgBtn = domId('conflictMsgBtn');
const conflictMsgText = domId('conflictMsgText');

syncMsgView.classList.add('hide');
conflictMsgView.classList.add('hide');
syncResult.classList.add('hide');
syncIncoming.classList.add('hide');

closeBtn.addEventListener('click', function () { window.ipcRenderer.invoke('window:hide'); });

syncMsgBtn.addEventListener('click', function () {
if (contextMsg.taskSyncStatus === 'TASK_SYNC_STATUS_FINISH') { // 查看
window.ipcRenderer.invoke('open-file-position', contextMsg);
return;
}
if (contextMsg.taskSyncStatus === 'TASK_SYNC_STATUS_FAIL') { // 重新同步
window.ipcRenderer.invoke('re-sync-file', contextMsg);
return;
}
if (false) { // 下载
window.ipcRenderer.invoke('download-file', contextMsg);
}
});

conflictMsgBtn.addEventListener('click', function () {
window.ipcRenderer.invoke('download-file', contextMsg);
});

window.addIpcRendererListener('on-notify', function (_, message) { // message: DATA.SocketFileMsg
console.log('comming message: ', message);
render(message);
});

function render(message) {
contextMsg = message;
resetDomStatus();
let runningMethod = noop;
// todo
switch (message.taskSyncStatus) {
case 'success':
runningMethod = renderTaskSuccess;
break;
case 'error':
runningMethod = renderTaskError;
break;
case 'incomming':
runningMethod = renderTaskError;
break;
case 'conflict':
runningMethod = renderTaskError;
break;
default:
break;
}
runningMethod(message);
}
function useView(dom) { dom.classList.remove('hide'); };
function fileFullName(message) {
const extension = message.extension;
return `${message.archName}${extension ? '.' + extension : ''}`;
}
function resetDomStatus() {
syncMsgView.classList.add('hide');
syncMsgView.classList.remove('success');
syncMsgView.classList.remove('error');
syncMsgView.classList.remove('incomming');
syncResult.classList.add('hide');
syncIncoming.classList.add('hide');
conflictMsgView.classList.add('hide');
}

function renderTaskSuccess(message) {
useView(syncMsgView);
syncMsgView.classList.add('success');
useView(syncResult);
syncMsgFileName.innerText = fileFullName(message);
syncMsgDate.innerText = message.taskCreateDateStr;
}
function renderTaskError(message) {
useView(syncMsgView);
syncMsgView.classList.add('error');
syncResult.classList.remove('hide');
syncMsgFileName.innerText = fileFullName(message);
syncMsgDate.innerText = message.taskCreateDateStr;
}
function renderTaskIncoming(message) {
useView(syncMsgView);
syncMsgView.classList.add('incomming');
useView(syncIncoming);
syncMsgFileName.innerText = fileFullName(message);
syncMsgDate.innerText = message.taskCreateDateStr;
syncIncoming.innerText = '某某人同步了「某某文件」';
}
function renderConflictMsg(message) {
useView(conflictMsgView);
conflictMsgText.innerText = `「${fileFullName(message)}」存在版本冲突,请先下载该文件最新版本,修改后再上传!`;
}

</script>
</body>

</html>

+ 3
- 1
electron/socket.js View File

@@ -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) => {


+ 4
- 1
src/app.ts View File

@@ -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();

+ 48
- 3
src/components/AppHeader/components/userCenter/FolderSetModal.tsx View File

@@ -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 (
<Modal
title="同步设置"
@@ -15,10 +47,23 @@ export default function FolderSetModal(props: ModalProps) {
maskClosable={false}
>
<span className={styles.label}>「工作空间」本地盘位置</span>
<Input size="small" suffix={<Button type="link">选择</Button>} />
<Input
value={folderPath}
size="small"
suffix={
<Button type="link" onClick={onSetFolderPos}>
选择
</Button>
}
/>

<div className={styles.btnGroup}>
<Button className={styles.btn} type="primary">
<Button
loading={loading}
className={styles.btn}
type="primary"
onClick={updateFolderPath}
>
更新资料
</Button>
</div>


+ 12
- 7
src/components/FileStatus/FileStatus.less View File

@@ -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;
}


+ 9
- 6
src/components/FileStatus/FileStatus.tsx View File

@@ -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 ? (
<Time time={'2020-01-01 18:24:56'} />
<Time time={data.taskCreateDate} />
) : null}
</div>
<div className={styles.right}>
{/* 查看1: 已下载 文件打开文件夹 */}
{data.taskSyncStatus === TaskStatus.FINISH ? (
<Button type="link" className={styles.button} size="small">
查看
</Button>
) : null}
{/* 查看2: 未下载 且 已删除 文件跳转到web端 */}
{/* 下载: 未下载 且 未删除 文件 */}
{/* 重新下载: 下载失败时出现 */}
@@ -74,8 +77,8 @@ export default function FileStatus(props: FileStatusProps) {
);
}

const Time = memo((props: { time: string }) => (
<span className={styles.time}>{dayjs(props.time).format('HH:mm:ss')}</span>
const Time = memo((props: { time: Dayjs }) => (
<span className={styles.time}>{props.time.format('HH:mm:ss')}</span>
));

interface LoadDescProps {


+ 16
- 4
src/pages/sync/index.tsx View File

@@ -13,9 +13,9 @@ import { TaskStatus } from '@/services/API.helper';
import { DATA } from '@/services/API';

enum SyncType {
Syncing,
SyncSuccess,
SyncError,
Syncing = 'syncing',
SyncSuccess = 'syncSuccess',
SyncError = 'syncError',
}

const syncTypeList = [
@@ -49,6 +49,14 @@ export default function SyncView() {
);
}, [originList]);

const mapSyncTypeToCount = useMemo(() => {
return {
[SyncType.Syncing]: syncingList.length,
[SyncType.SyncSuccess]: succList.length,
[SyncType.SyncError]: failList.length,
};
}, [syncingList, succList, failList]);

const curretList = useMemo(() => {
if (type === SyncType.SyncSuccess) return succList;
if (type === SyncType.SyncError) return failList;
@@ -72,7 +80,11 @@ export default function SyncView() {
>
<img className={styles.icon} src={icon} alt="" />
<span className={styles.label}>{label}</span>
<span className={styles.count}>99+</span>
<span className={styles.count}>
{mapSyncTypeToCount[iType] > 99
? '99+'
: mapSyncTypeToCount[iType] || ''}
</span>
</div>
))}
</div>


+ 5
- 0
src/services/API.d.ts View File

@@ -1,3 +1,4 @@
import { Dayjs } from 'dayjs';
import { TaskStatus, TaskType } from './API.helper';

declare namespace API {
@@ -74,6 +75,10 @@ declare namespace DATA {
taskSyncProgress: 100;
taskSyncStatus: TaskStatus; // "TASK_SYNC_STATUS_FINISH",
taskType: TaskType; // "DOWNLOAD",
taskCreateUnixTime: number; // 秒级时间
taskCreateDate: Dayjs; // 前端根据taskCreateUnixTime生成的时间对象
taskCreateDateStr: string; // 前端根据taskCreateUnixTime生成的时间字符串 供给消息窗口使用, 格式: YY/MM/DD HH:mm:ss
absolutePath: string; // 绝对路径
version: number; // 1,
workStatus: number; // 1
}


+ 46
- 12
src/services/socket.ts View File

@@ -1,8 +1,11 @@
import { firstCharToLowerCase, Subject } from '@/utils/tool';
import dayjs from 'dayjs';
import { useEffect, useState } from 'react';
import { DATA } from './API';
import { isClient } from './system';

export const isClient = !!window.ipcRenderer; // process.env.IS_CLIENT;
const gatewayPort = window.systemConfig?.gatewayPort || 7888;
const skUrl = `ws://127.0.0.1:${gatewayPort}/websocket/subscriptionTaskSync`;

class FileMessageManager extends Subject {
public list: DATA.SocketFileMsg[] = [];
@@ -10,6 +13,7 @@ class FileMessageManager extends Subject {
public fileMap = new Map();

addFileMsg(message: DATA.SocketFileMsg) {
this.fileMap.set(message.taskId, message);
this.list = [message].concat(this.list);
this.notifyObservers(this.list);
}
@@ -25,12 +29,21 @@ class FileMessageManager extends Subject {
const message = firstCharToLowerCase(
JSON.parse(messageStr),
) as DATA.SocketFileMsg;
// 数据处理
message.taskCreateDate = dayjs((message.taskCreateUnixTime || 0) * 1000);
// 给消息窗口使用
message.taskCreateDateStr =
message.taskCreateDate.format('YY/MM/DD HH:mm:ss');
const existMsg = this.fileMap.get(message.taskId);
if (existMsg) {
this.updateFileMsg(message);
} else {
this.addFileMsg(message);
}
// todo: 临时测试
if (isClient) {
window.ipcRenderer.invoke('notify', message);
}
} catch (e) {
console.error('message 解析失败:', e, messageStr);
}
@@ -39,21 +52,42 @@ class FileMessageManager extends Subject {

const fileMng = new FileMessageManager();

if (isClient) {
window.addIpcRendererListener('socket:on-message', (_, message) => {
console.log('receive electron socket message:', message);
fileMng.receiveMessage(message);
});
window.addIpcRendererListener('socket:on-error', (_, ...args) => {
console.log('receive electron socket error:', args);
});
window.addIpcRendererListener('socket:on-close', (_, ...args) => {
console.log('receive electron socket close:', args);
});
enum SocketState {
CONNECTING = 0, // 连接尚未建立
OPEN, // WebSocket的链接已经建立
CLOSING, // 连接正在关闭
CLOSED, // 连接已经关闭或不可用
}

let taskSyncSocket: WebSocket;
function initTaskSyncSocket() {
taskSyncSocket = new WebSocket(skUrl);
taskSyncSocket.onopen = () => {
console.log('taskSyncSocket connection');
};
taskSyncSocket.onmessage = ({ data }) => {
fileMng.receiveMessage(data);
};
taskSyncSocket.onerror = (...args) => {
console.log('taskSyncSocket error:', args);
};
taskSyncSocket.onclose = (...args) => {
console.log('taskSyncSocket close:', args);
setTimeout(() => initTaskSyncSocket(), 5000);
};
}
export function sendSocketMessage(message: string) {
if (taskSyncSocket && taskSyncSocket.readyState === SocketState.OPEN) {
taskSyncSocket.send(message);
}
}
export function initialWebsocket() {
initTaskSyncSocket();
}

export function useSocketMessages() {
const [list, setList] = useState(fileMng.list);
// console.log(list);
useEffect(() => {
fileMng.addObserver(setList);
return () => {


+ 30
- 10
src/services/system.ts View File

@@ -1,4 +1,6 @@
import { fetchLocalApi } from '@/utils/request';
import { DATA } from './API';
import { sendSocketMessage } from './socket';

export const isClient = !!window.ipcRenderer; // process.env.IS_CLIENT;

@@ -9,13 +11,13 @@ const ipcRenderer = window.ipcRenderer;

const system = {
closeWindow() {
ipcRenderer.invoke('manipulate-window', { action: 'close' });
ipcRenderer.invoke('window:close');
},
zoomWindow() {
ipcRenderer.invoke('manipulate-window', { action: 'zoom' });
ipcRenderer.invoke('window:zoom');
},
minimizeWindow() {
ipcRenderer.invoke('manipulate-window', { action: 'minimize' });
ipcRenderer.invoke('window:minimize');
},
openUrl(url: string) {
if (!isClient) {
@@ -24,6 +26,13 @@ const system = {
}
ipcRenderer.invoke('open-browser', url);
},
// 选择文件
chooseFolder: safeCall(async () => {
const res = await ipcRenderer.invoke('select-folder');
const { canceled, filePaths } = res;
if (canceled) return null;
return filePaths[0] as string;
}),

storage: {
set: safeCall((key: string, value: any) => {
@@ -35,25 +44,36 @@ const system = {
},
/* 发往网关的请求 */
// 登陆
login: safeCall(async (userId: string, userPhone: string) => {
login: async (userId: string, userPhone: string) => {
const res = await fetchLocalApi<null>(
'login',
{ userId, userPhone },
{ silent: true },
);
ipcRenderer.invoke('socket:send-message', `login:${userPhone}`);
sendSocketMessage(`login:${userPhone}`);
return res;
}),
},
// 退出登陆
logout: safeCall(async () => {
logout: async () => {
return await fetchLocalApi<null>('logout');
}),
},
// 发送下载同步文件夹的指令
syncProjects: safeCall((projectIds: string[]) => {
syncProjects: (projectIds: string[]) => {
return fetchLocalApi<null>('syncFolderToWorkSpace', {
ids: projectIds.join(','),
});
}),
},
};

if (isClient) {
window.addIpcRendererListener(
'request-resync-file',
(e, msg: DATA.SocketFileMsg) => {},
);
window.addIpcRendererListener(
'request-download-file',
(e, msg: DATA.SocketFileMsg) => {},
);
}

export default system;

+ 3
- 5
src/services/user.ts View File

@@ -34,11 +34,9 @@ export async function login(account: string, password: string) {
return errorReponse('该账号没有访问权限');
}

if (isClient) {
const systemLoginRes = await system.login(res.data.id, res.data.phone);
if (!isReqSuccess(systemLoginRes)) {
return errorReponse('本地网管通讯失败');
}
const systemLoginRes = await system.login(res.data.id, res.data.phone);
if (!isReqSuccess(systemLoginRes)) {
return errorReponse('本地网管通讯失败');
}

// const companyInfoRes = await fetchApi('company/queryFrontDeskCompanyById', { id: userData.companyId }, { silent: true });


+ 3
- 2
src/utils/request.ts View File

@@ -1,10 +1,11 @@
import { isClient } from '@/services/system';
import { message } from 'antd';
import { request } from 'umi';
import { parseRequest } from './request.config';
import { errorReponse, firstCharToLowerCase, handleRequest } from './tool';

const remoteUrl = window.systemConfig?.remoteUrl || '';
const gatewayPort = window.systemConfig?.gatewayPort || 0;
const gatewayPort = window.systemConfig?.gatewayPort || 7888;

export async function fetchApi<T = any>(
path: string,
@@ -41,7 +42,7 @@ export async function fetchLocalApi<T = any>(
}
const { silent, method = 'GET', ...restOptions } = options;
const res = await request<API.ResponseData<T>>(
`http://127.0.0.1:${gatewayPort}/api/${path}`,
`${isClient ? `http://127.0.0.1:${gatewayPort}` : '/gateway'}/api/${path}`,
{ method, [method === 'GET' ? 'params' : 'data']: params, ...restOptions },
);
if (!silent) {


Loading…
Cancel
Save