From b1b8a6519c224de87eb95fee03171e23cf2314fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=91=E5=B7=9E?= Date: Thu, 15 Jul 2021 17:57:07 +0800 Subject: [PATCH] 'update' --- electron/notifycation.html | 29 ++-- src/app.ts | 14 +- src/components/FileStatus/FileStatus.tsx | 79 ++++++----- src/pages/messages/index.tsx | 15 ++- src/pages/messages/messages.less | 8 ++ src/pages/sync/index.tsx | 6 +- src/pages/sync/sync.less | 2 +- src/services/API.d.ts | 12 +- src/services/API.helper.ts | 5 + src/services/socket.ts | 94 +++++-------- src/services/user.ts | 6 +- src/utils/hooks.ts | 160 +++++++++++++++++++++++ 12 files changed, 304 insertions(+), 126 deletions(-) create mode 100644 src/pages/messages/messages.less create mode 100644 src/utils/hooks.ts diff --git a/electron/notifycation.html b/electron/notifycation.html index ea74baf..1fc529c 100644 --- a/electron/notifycation.html +++ b/electron/notifycation.html @@ -265,8 +265,9 @@ window.ipcRenderer.invoke('re-sync-file', contextMsg); return; } - if (false) { // 下载 + if (contextMsg.notifyType === 1) { // 下载 window.ipcRenderer.invoke('download-file', contextMsg); + return; } }); @@ -283,22 +284,14 @@ 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; + if(message.notifyType === 1) { + runningMethod = renderTaskIncoming; + } else if(message.notifyType === 2 || message.notifyType === 3) { + runningMethod = renderConflictMsg; + } else if(message.taskSyncStatus === 'TASK_SYNC_STATUS_FINISH') { + runningMethod = renderTaskSuccess; + } else if(message.taskSyncStatus === 'TASK_SYNC_STATUS_FAIL') { + runningMethod = renderTaskError; } runningMethod(message); } @@ -337,7 +330,7 @@ useView(syncIncoming); syncMsgFileName.innerText = fileFullName(message); syncMsgDate.innerText = message.taskCreateDateStr; - syncIncoming.innerText = '某某人同步了「某某文件」'; + syncIncoming.innerText = message.notifyMessage; } function renderConflictMsg(message) { useView(conflictMsgView); diff --git a/src/app.ts b/src/app.ts index 884fad9..aed6897 100644 --- a/src/app.ts +++ b/src/app.ts @@ -34,7 +34,17 @@ const errorHandler = (error: ResponseError) => { if (response && response.status) { const errorText = codeMessage[response.status] || response.statusText; const { status, url } = response; - + if (response.status === 401) { + notification.error({ + message: '登录超时', + description: '登录状态超时,请重新登录', + }); + logout(false); + return { + code: response.status, + message: errorText, + }; + } notification.error({ message: `请求错误 ${status}: ${url}`, description: errorText, @@ -45,7 +55,7 @@ const errorHandler = (error: ResponseError) => { }; } if (data && typeof data === 'object' && 'error' in data) return data; - // todo 报错都不走error, 改为构建成data; + // 报错都不走error, 改为构建成data; if (!response) { notification.error({ description: '您的网络发生异常,无法连接服务器', diff --git a/src/components/FileStatus/FileStatus.tsx b/src/components/FileStatus/FileStatus.tsx index ad21fa5..1f8120d 100644 --- a/src/components/FileStatus/FileStatus.tsx +++ b/src/components/FileStatus/FileStatus.tsx @@ -6,8 +6,8 @@ import FileIcon from '../FileIcon'; import ATooltip from '../Tooltip'; import styles from './FileStatus.less'; import { CloseCircleFilled, CheckCircleFilled } from '@ant-design/icons'; -import { Progress, Button } from 'antd'; -import { TaskStatus, TaskType } from '@/services/API.helper'; +import { Progress, Button, ButtonProps } from 'antd'; +import { NotificationType, TaskStatus, TaskType } from '@/services/API.helper'; import { DATA } from '@/services/API'; import { identity } from 'lodash'; import { useCallback } from 'react'; @@ -39,6 +39,10 @@ export default function FileStatus(props: FileStatusProps) { fetchLocalApi('restartTask', { taskIds: data.taskId }); }, [data.taskId]); + const downloadFile = useCallback(() => { + fetchLocalApi('downloadFileFromMsg', { fileObject: data.originData }); + }, [data.taskId]); + const checkFile = useCallback(() => { system.openFilePosition(data); }, [data]); @@ -60,43 +64,35 @@ export default function FileStatus(props: FileStatusProps) {
- - {data.taskSyncStatus === TaskStatus.FINISH ? ( + {data.notifyType ? ( + {data.notifyMessage} + ) : ( + + )} + {data.taskSyncStatus === TaskStatus.FINISH || data.notifyType ? (
{/* 查看1: 已下载 文件打开文件夹 */} {data.taskSyncStatus === TaskStatus.FINISH ? ( - + 查看 ) : null} - {/* 查看2: 未下载 且 已删除 文件跳转到web端 */} - {/* 下载: 未下载 且 未删除 文件 */} {/* 重新下载: 下载失败时出现 */} {/* 重新上传: 上传失败时出现 */} {data.taskSyncStatus === TaskStatus.FAILED ? ( - + + ) : null} + {data.notifyType && data.notifyType === NotificationType.SYNC ? ( + 下载 ) : null} - {/* */} {/* */} {/* */}
@@ -104,9 +100,32 @@ export default function FileStatus(props: FileStatusProps) { ); } -const Time = memo((props: { time: Dayjs }) => ( - {props.time.format('HH:mm:ss')} -)); +function ActionButton({ onClick, children }: ButtonProps) { + return ( + + ); +} +interface TimeProps { + time: Dayjs; +} +const Time = memo(({ time }: TimeProps) => { + const now = dayjs(); + const isToday = time.isSame(now, 'day'); + const isSameYear = time.isSame(now, 'year'); + const formatStr = useMemo(() => { + if (isToday) return 'HH:mm:ss'; + if (isSameYear) return 'MM/DD HH:mm:ss'; + return 'YYYY/MM/DD HH:mm:ss'; + }, [isToday, isSameYear]); + return {time.format(formatStr)}; +}); interface LoadDescProps { type: TaskType; diff --git a/src/pages/messages/index.tsx b/src/pages/messages/index.tsx index 3a0af27..6be86be 100644 --- a/src/pages/messages/index.tsx +++ b/src/pages/messages/index.tsx @@ -1,9 +1,14 @@ -import React from 'react' +import FileStatus from '@/components/FileStatus'; +import { useNotificationMessages } from '@/utils/hooks'; +import styles from './messages.less'; export default function MessagesView() { + const list = useNotificationMessages(); return ( -
- 消息通知界面 +
+ {list.map((data) => ( + + ))}
- ) -} \ No newline at end of file + ); +} diff --git a/src/pages/messages/messages.less b/src/pages/messages/messages.less new file mode 100644 index 0000000..b8a938a --- /dev/null +++ b/src/pages/messages/messages.less @@ -0,0 +1,8 @@ +.messages { + height: 100%; + padding: 0 12px; + .item { + margin-top: 12px; + margin-bottom: 8px; + } +} diff --git a/src/pages/sync/index.tsx b/src/pages/sync/index.tsx index 4f2344e..9e24056 100644 --- a/src/pages/sync/index.tsx +++ b/src/pages/sync/index.tsx @@ -6,11 +6,11 @@ import syncSuccessImg from './assets/sync.success.png'; import syncErrorImg from './assets/sync.error.png'; import classNames from 'classnames'; import SyncModal from '@/components/SyncModal'; -import { useSocketMessages } from '@/services/socket'; import FileStatus from '@/components/FileStatus'; import { useMemo } from 'react'; import { TaskStatus } from '@/services/API.helper'; import { DATA } from '@/services/API'; +import { useSocketMessages } from '@/utils/hooks'; enum SyncType { Syncing = 'syncing', @@ -89,9 +89,9 @@ export default function SyncView() { ))}
-
+ {/*
// 由于现在消息列表没有清除功能,优先考虑使用虚拟滚动以保证页面性能
2021年6月14日
-
+
*/} {curretList.map((data) => ( ))} diff --git a/src/pages/sync/sync.less b/src/pages/sync/sync.less index d78023d..900fced 100644 --- a/src/pages/sync/sync.less +++ b/src/pages/sync/sync.less @@ -54,7 +54,7 @@ flex: 1; overflow: auto; padding: 0 12px; - .item ~ .item { + .item { margin-top: 12px; margin-bottom: 8px; } diff --git a/src/services/API.d.ts b/src/services/API.d.ts index bbb47b6..3956851 100644 --- a/src/services/API.d.ts +++ b/src/services/API.d.ts @@ -1,5 +1,5 @@ import { Dayjs } from 'dayjs'; -import { TaskStatus, TaskType } from './API.helper'; +import { NotificationType, TaskStatus, TaskType } from './API.helper'; declare namespace API { export interface ResponseData { @@ -81,5 +81,15 @@ declare namespace DATA { absolutePath: string; // 绝对路径 version: number; // 1, workStatus: number; // 1 + /* 消息通知型数据补完 */ + originData: any; // 用于消息通知 + /** + * 通知类型 + */ + notifyType: NotificationType; + /** + * 通知消息内容 + */ + notifyMessage: string; } } diff --git a/src/services/API.helper.ts b/src/services/API.helper.ts index a1fd665..be29a0b 100644 --- a/src/services/API.helper.ts +++ b/src/services/API.helper.ts @@ -26,3 +26,8 @@ export enum TaskType { // 上传 UPLOAD = 'UPLOAD', } +export enum NotificationType { + SYNC = 1, + UPLOAD_CONFLICT = 2, + DOWNLOAD_CONFLICT = 3, +} diff --git a/src/services/socket.ts b/src/services/socket.ts index 01495af..3d46b27 100644 --- a/src/services/socket.ts +++ b/src/services/socket.ts @@ -1,56 +1,9 @@ -import { firstCharToLowerCase, Subject } from '@/utils/tool'; -import dayjs from 'dayjs'; -import { useEffect, useState } from 'react'; -import { DATA } from './API'; -import { isClient } from './system'; +import { fileMng, msgNotifySubject } from '@/utils/hooks'; +import { firstCharToLowerCase } from '@/utils/tool'; const gatewayPort = window.systemConfig?.gatewayPort || 7888; -const skUrl = `ws://127.0.0.1:${gatewayPort}/websocket/subscriptionTaskSync`; - -class FileMessageManager extends Subject { - public list: DATA.SocketFileMsg[] = []; - // 主键 taskId - public fileMap = new Map(); - - addFileMsg(message: DATA.SocketFileMsg) { - this.fileMap.set(message.taskId, message); - this.list = [message].concat(this.list); - this.notifyObservers(this.list); - } - updateFileMsg(message: DATA.SocketFileMsg) { - this.fileMap.set(message.taskId, message); - this.list = this.list.map((msg) => - msg.taskId === message.taskId ? message : msg, - ); - this.notifyObservers(this.list); - } - receiveMessage(messageStr: string) { - try { - 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); - } - } -} - -const fileMng = new FileMessageManager(); +const taskSyncSkUrl = `ws://127.0.0.1:${gatewayPort}/websocket/subscriptionTaskSync`; +const msgNotifySkUrl = `ws://127.0.0.1:${gatewayPort}/websocket/subscriptionMessageNotify`; enum SocketState { CONNECTING = 0, // 连接尚未建立 @@ -60,8 +13,9 @@ enum SocketState { } let taskSyncSocket: WebSocket; +let msgNotifySocket: WebSocket; function initTaskSyncSocket() { - taskSyncSocket = new WebSocket(skUrl); + taskSyncSocket = new WebSocket(taskSyncSkUrl); taskSyncSocket.onopen = () => { console.log('taskSyncSocket connection'); }; @@ -76,6 +30,28 @@ function initTaskSyncSocket() { setTimeout(() => initTaskSyncSocket(), 5000); }; } +function initMessageNotifySocket() { + msgNotifySocket = new WebSocket(msgNotifySkUrl); + msgNotifySocket.onopen = () => { + console.log('taskSyncSocket connection'); + }; + msgNotifySocket.onmessage = ({ data }) => { + // fileMng.receiveMessage(data); + try { + const originNotifyMessage = firstCharToLowerCase(JSON.parse(data)) as any; + msgNotifySubject.notifyObservers(originNotifyMessage); + } catch (e) { + console.error('socket notify message 解析失败:', e, data); + } + }; + msgNotifySocket.onerror = (...args) => { + console.log('taskSyncSocket error:', args); + }; + msgNotifySocket.onclose = (...args) => { + console.log('taskSyncSocket close:', args); + setTimeout(() => initTaskSyncSocket(), 5000); + }; +} export function sendSocketMessage(message: string) { if (taskSyncSocket && taskSyncSocket.readyState === SocketState.OPEN) { taskSyncSocket.send(message); @@ -83,17 +59,7 @@ export function sendSocketMessage(message: string) { } export function initialWebsocket() { initTaskSyncSocket(); + initMessageNotifySocket(); } -export function useSocketMessages() { - const [list, setList] = useState(fileMng.list); - // console.log(list); - useEffect(() => { - fileMng.addObserver(setList); - return () => { - fileMng.removeObserver(setList); - }; - }, []); - - return list; -} +// export function use diff --git a/src/services/user.ts b/src/services/user.ts index 0da42fc..785c6da 100644 --- a/src/services/user.ts +++ b/src/services/user.ts @@ -66,8 +66,10 @@ export async function login(account: string, password: string) { return res; } -export function logout() { - storage.clear(); +export function logout(clearStorage = true) { + if (clearStorage) { + storage.clear(); + } system.logout(); // window.location.href = '/login'; history.replace('/login'); diff --git a/src/utils/hooks.ts b/src/utils/hooks.ts new file mode 100644 index 0000000..4ce3156 --- /dev/null +++ b/src/utils/hooks.ts @@ -0,0 +1,160 @@ +import { useCallback, useEffect, useState } from 'react'; +import { + firstCharToLowerCase, + firstCharToUpperCase, + Subject, +} from '@/utils/tool'; +import { API, DATA } from '@/services/API'; +import dayjs from 'dayjs'; +import { isClient } from '@/services/system'; +import { throttle } from 'lodash'; +import { fetchApi } from './request'; +import storage from './storage'; + +// // let workspacePath = ''; +// // 暂时不用 待定 +// export function useWorkspacePath() { +// const ctx = useState(''); + +// // const setPath = useCallback((value: string) => { +// // setThePath(value); +// // workspacePath = value; +// // }, [setThePath]); + +// useEffect(() => { +// // 获取当前使用的路径; 这里的接口需要做一层防抖 +// }, []); + +// return ctx; +// } + +class FileMessageManager extends Subject { + public list: DATA.SocketFileMsg[] = []; + // 主键 taskId + public fileMap = new Map(); + + addFileMsg(message: DATA.SocketFileMsg) { + this.fileMap.set(message.taskId, message); + this.list = [message].concat(this.list); + this.notifyObservers(this.list); + } + updateFileMsg(message: DATA.SocketFileMsg) { + this.fileMap.set(message.taskId, message); + this.list = this.list.map((msg) => + msg.taskId === message.taskId ? message : msg, + ); + this.notifyObservers(this.list); + } + receiveMessage(messageStr: string) { + try { + 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); + } + } catch (e) { + console.error('message 解析失败:', e, messageStr); + } + } +} + +export const fileMng = new FileMessageManager(); +export const msgNotifySubject = new Subject(); + +msgNotifySubject.addObserver((upperNotifyMessage) => { + if (!isClient) { + return; + } + const message = transformNotifyMessage(upperNotifyMessage); + if (!message) { + return; + } + window.ipcRenderer.invoke('notify', message); +}); + +function transformNotifyMessage(notifyMessage: any) { + try { + if (!notifyMessage.parameter) return null; + const fileMsg = firstCharToLowerCase( + JSON.parse(notifyMessage.parameter), + ) as DATA.SocketFileMsg; + fileMsg.originData = notifyMessage.parameter; + fileMsg.notifyType = notifyMessage.type; + fileMsg.notifyMessage = notifyMessage.body; + // 数据处理 + fileMsg.taskCreateDate = dayjs((notifyMessage.unixTime || 0) * 1000); + // 给消息窗口使用 + fileMsg.taskCreateDateStr = + fileMsg.taskCreateDate.format('YY/MM/DD HH:mm:ss'); + return fileMsg; + } catch (e) { + console.error('transformNotifyMessage 解析失败:', e, notifyMessage); + return null; + } +} + +export function useSocketMessages() { + const [list, setList] = useState(fileMng.list); + useEffect(() => { + fileMng.addObserver(setList); + return () => { + fileMng.removeObserver(setList); + }; + }, []); + + return list; +} + +export function useNotificationMessages() { + const [list, setList] = useState>([]); + const fetchMessages = useCallback( + throttle(async () => { + const accountId = storage.get('accountId'); + const [headList, hisList] = await Promise.all< + API.ResponseData, + API.ResponseData + >([ + fetchApi('lockingmsg/queryLockingMsgListByFilter', { + userId: accountId, + status: 1, + }), + fetchApi('lockingmsg/queryLockingMsgListByFilter', { + userId: accountId, + status: 2, + }), + ]); + const finalList = headList.data + ?.concat(hisList.data || []) + .reduce((arr, notifyMessage) => { + const message = transformNotifyMessage(notifyMessage); + if (!message) return arr; + arr.push(message); + return arr; + }, []) as DATA.SocketFileMsg[]; + setList(finalList); + }, 500), + [], + ); + console.log(list); + useEffect(() => { + msgNotifySubject.addObserver(fetchMessages); + return () => { + msgNotifySubject.removeObserver(fetchMessages); + }; + }, []); + + useEffect(() => { + fetchMessages(); + }, []); + + return list; +}