Bladeren bron

'update'

main
郑州 3 jaren geleden
bovenliggende
commit
b1b8a6519c
12 gewijzigde bestanden met toevoegingen van 304 en 126 verwijderingen
  1. +11
    -18
      electron/notifycation.html
  2. +12
    -2
      src/app.ts
  3. +49
    -30
      src/components/FileStatus/FileStatus.tsx
  4. +10
    -5
      src/pages/messages/index.tsx
  5. +8
    -0
      src/pages/messages/messages.less
  6. +3
    -3
      src/pages/sync/index.tsx
  7. +1
    -1
      src/pages/sync/sync.less
  8. +11
    -1
      src/services/API.d.ts
  9. +5
    -0
      src/services/API.helper.ts
  10. +30
    -64
      src/services/socket.ts
  11. +4
    -2
      src/services/user.ts
  12. +160
    -0
      src/utils/hooks.ts

+ 11
- 18
electron/notifycation.html Bestand weergeven

@@ -265,8 +265,9 @@
window.ipcRenderer.invoke('re-sync-file', contextMsg); window.ipcRenderer.invoke('re-sync-file', contextMsg);
return; return;
} }
if (false) { // 下载
if (contextMsg.notifyType === 1) { // 下载
window.ipcRenderer.invoke('download-file', contextMsg); window.ipcRenderer.invoke('download-file', contextMsg);
return;
} }
}); });


@@ -283,22 +284,14 @@
contextMsg = message; contextMsg = message;
resetDomStatus(); resetDomStatus();
let runningMethod = noop; 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); runningMethod(message);
} }
@@ -337,7 +330,7 @@
useView(syncIncoming); useView(syncIncoming);
syncMsgFileName.innerText = fileFullName(message); syncMsgFileName.innerText = fileFullName(message);
syncMsgDate.innerText = message.taskCreateDateStr; syncMsgDate.innerText = message.taskCreateDateStr;
syncIncoming.innerText = '某某人同步了「某某文件」';
syncIncoming.innerText = message.notifyMessage;
} }
function renderConflictMsg(message) { function renderConflictMsg(message) {
useView(conflictMsgView); useView(conflictMsgView);


+ 12
- 2
src/app.ts Bestand weergeven

@@ -34,7 +34,17 @@ const errorHandler = (error: ResponseError) => {
if (response && response.status) { if (response && response.status) {
const errorText = codeMessage[response.status] || response.statusText; const errorText = codeMessage[response.status] || response.statusText;
const { status, url } = response; const { status, url } = response;

if (response.status === 401) {
notification.error({
message: '登录超时',
description: '登录状态超时,请重新登录',
});
logout(false);
return {
code: response.status,
message: errorText,
};
}
notification.error({ notification.error({
message: `请求错误 ${status}: ${url}`, message: `请求错误 ${status}: ${url}`,
description: errorText, description: errorText,
@@ -45,7 +55,7 @@ const errorHandler = (error: ResponseError) => {
}; };
} }
if (data && typeof data === 'object' && 'error' in data) return data; if (data && typeof data === 'object' && 'error' in data) return data;
// todo 报错都不走error, 改为构建成data;
// 报错都不走error, 改为构建成data;
if (!response) { if (!response) {
notification.error({ notification.error({
description: '您的网络发生异常,无法连接服务器', description: '您的网络发生异常,无法连接服务器',


+ 49
- 30
src/components/FileStatus/FileStatus.tsx Bestand weergeven

@@ -6,8 +6,8 @@ import FileIcon from '../FileIcon';
import ATooltip from '../Tooltip'; import ATooltip from '../Tooltip';
import styles from './FileStatus.less'; import styles from './FileStatus.less';
import { CloseCircleFilled, CheckCircleFilled } from '@ant-design/icons'; 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 { DATA } from '@/services/API';
import { identity } from 'lodash'; import { identity } from 'lodash';
import { useCallback } from 'react'; import { useCallback } from 'react';
@@ -39,6 +39,10 @@ export default function FileStatus(props: FileStatusProps) {
fetchLocalApi('restartTask', { taskIds: data.taskId }); fetchLocalApi('restartTask', { taskIds: data.taskId });
}, [data.taskId]); }, [data.taskId]);


const downloadFile = useCallback(() => {
fetchLocalApi('downloadFileFromMsg', { fileObject: data.originData });
}, [data.taskId]);

const checkFile = useCallback(() => { const checkFile = useCallback(() => {
system.openFilePosition(data); system.openFilePosition(data);
}, [data]); }, [data]);
@@ -60,43 +64,35 @@ export default function FileStatus(props: FileStatusProps) {
</div> </div>
</div> </div>
<div className={styles.mid}> <div className={styles.mid}>
<LoadDesc
type={data.taskType}
loadingState={data.taskSyncStatus}
progress={data.taskSyncProgress}
{...restProps}
/>
{data.taskSyncStatus === TaskStatus.FINISH ? (
{data.notifyType ? (
<span>{data.notifyMessage}</span>
) : (
<LoadDesc
type={data.taskType}
loadingState={data.taskSyncStatus}
progress={data.taskSyncProgress}
{...restProps}
/>
)}
{data.taskSyncStatus === TaskStatus.FINISH || data.notifyType ? (
<Time time={data.taskCreateDate} /> <Time time={data.taskCreateDate} />
) : null} ) : null}
</div> </div>
<div className={styles.right}> <div className={styles.right}>
{/* 查看1: 已下载 文件打开文件夹 */} {/* 查看1: 已下载 文件打开文件夹 */}
{data.taskSyncStatus === TaskStatus.FINISH ? ( {data.taskSyncStatus === TaskStatus.FINISH ? (
<Button
type="link"
className={styles.button}
size="small"
onClick={checkFile}
>
查看
</Button>
<ActionButton onClick={checkFile}>查看</ActionButton>
) : null} ) : null}
{/* 查看2: 未下载 且 已删除 文件跳转到web端 */}
{/* 下载: 未下载 且 未删除 文件 */}
{/* 重新下载: 下载失败时出现 */} {/* 重新下载: 下载失败时出现 */}
{/* 重新上传: 上传失败时出现 */} {/* 重新上传: 上传失败时出现 */}
{data.taskSyncStatus === TaskStatus.FAILED ? ( {data.taskSyncStatus === TaskStatus.FAILED ? (
<Button
type="link"
className={styles.button}
size="small"
onClick={redoTask}
>
<ActionButton onClick={redoTask}>
{data.taskType === TaskType.DOWNLOAD ? '重新下载' : '重新上传'} {data.taskType === TaskType.DOWNLOAD ? '重新下载' : '重新上传'}
</Button>
</ActionButton>
) : null}
{data.notifyType && data.notifyType === NotificationType.SYNC ? (
<ActionButton onClick={downloadFile}>下载</ActionButton>
) : null} ) : null}
{/* <Button type="link" className={styles.button} size="small">重新上传</Button> */}
{/* <Button type="link" className={styles.button} size="small">暂停</Button> */} {/* <Button type="link" className={styles.button} size="small">暂停</Button> */}
{/* <Button type="link" className={styles.button} size="small">取消</Button> */} {/* <Button type="link" className={styles.button} size="small">取消</Button> */}
</div> </div>
@@ -104,9 +100,32 @@ export default function FileStatus(props: FileStatusProps) {
); );
} }


const Time = memo((props: { time: Dayjs }) => (
<span className={styles.time}>{props.time.format('HH:mm:ss')}</span>
));
function ActionButton({ onClick, children }: ButtonProps) {
return (
<Button
type="link"
className={styles.button}
size="small"
onClick={onClick}
>
{children}
</Button>
);
}
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 <span className={styles.time}>{time.format(formatStr)}</span>;
});


interface LoadDescProps { interface LoadDescProps {
type: TaskType; type: TaskType;


+ 10
- 5
src/pages/messages/index.tsx Bestand weergeven

@@ -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() { export default function MessagesView() {
const list = useNotificationMessages();
return ( return (
<div>
消息通知界面
<div className={styles.messages}>
{list.map((data) => (
<FileStatus key={data.taskId} className={styles.item} data={data} />
))}
</div> </div>
)
}
);
}

+ 8
- 0
src/pages/messages/messages.less Bestand weergeven

@@ -0,0 +1,8 @@
.messages {
height: 100%;
padding: 0 12px;
.item {
margin-top: 12px;
margin-bottom: 8px;
}
}

+ 3
- 3
src/pages/sync/index.tsx Bestand weergeven

@@ -6,11 +6,11 @@ import syncSuccessImg from './assets/sync.success.png';
import syncErrorImg from './assets/sync.error.png'; import syncErrorImg from './assets/sync.error.png';
import classNames from 'classnames'; import classNames from 'classnames';
import SyncModal from '@/components/SyncModal'; import SyncModal from '@/components/SyncModal';
import { useSocketMessages } from '@/services/socket';
import FileStatus from '@/components/FileStatus'; import FileStatus from '@/components/FileStatus';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { TaskStatus } from '@/services/API.helper'; import { TaskStatus } from '@/services/API.helper';
import { DATA } from '@/services/API'; import { DATA } from '@/services/API';
import { useSocketMessages } from '@/utils/hooks';


enum SyncType { enum SyncType {
Syncing = 'syncing', Syncing = 'syncing',
@@ -89,9 +89,9 @@ export default function SyncView() {
))} ))}
</div> </div>
<div className={styles.right}> <div className={styles.right}>
<div className={styles.date}>
{/* <div className={styles.date}> // 由于现在消息列表没有清除功能,优先考虑使用虚拟滚动以保证页面性能
<div>2021年6月14日</div> <div>2021年6月14日</div>
</div>
</div> */}
{curretList.map((data) => ( {curretList.map((data) => (
<FileStatus key={data.taskId} className={styles.item} data={data} /> <FileStatus key={data.taskId} className={styles.item} data={data} />
))} ))}


+ 1
- 1
src/pages/sync/sync.less Bestand weergeven

@@ -54,7 +54,7 @@
flex: 1; flex: 1;
overflow: auto; overflow: auto;
padding: 0 12px; padding: 0 12px;
.item ~ .item {
.item {
margin-top: 12px; margin-top: 12px;
margin-bottom: 8px; margin-bottom: 8px;
} }


+ 11
- 1
src/services/API.d.ts Bestand weergeven

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


declare namespace API { declare namespace API {
export interface ResponseData<T> { export interface ResponseData<T> {
@@ -81,5 +81,15 @@ declare namespace DATA {
absolutePath: string; // 绝对路径 absolutePath: string; // 绝对路径
version: number; // 1, version: number; // 1,
workStatus: number; // 1 workStatus: number; // 1
/* 消息通知型数据补完 */
originData: any; // 用于消息通知
/**
* 通知类型
*/
notifyType: NotificationType;
/**
* 通知消息内容
*/
notifyMessage: string;
} }
} }

+ 5
- 0
src/services/API.helper.ts Bestand weergeven

@@ -26,3 +26,8 @@ export enum TaskType {
// 上传 // 上传
UPLOAD = 'UPLOAD', UPLOAD = 'UPLOAD',
} }
export enum NotificationType {
SYNC = 1,
UPLOAD_CONFLICT = 2,
DOWNLOAD_CONFLICT = 3,
}

+ 30
- 64
src/services/socket.ts Bestand weergeven

@@ -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 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 { enum SocketState {
CONNECTING = 0, // 连接尚未建立 CONNECTING = 0, // 连接尚未建立
@@ -60,8 +13,9 @@ enum SocketState {
} }


let taskSyncSocket: WebSocket; let taskSyncSocket: WebSocket;
let msgNotifySocket: WebSocket;
function initTaskSyncSocket() { function initTaskSyncSocket() {
taskSyncSocket = new WebSocket(skUrl);
taskSyncSocket = new WebSocket(taskSyncSkUrl);
taskSyncSocket.onopen = () => { taskSyncSocket.onopen = () => {
console.log('taskSyncSocket connection'); console.log('taskSyncSocket connection');
}; };
@@ -76,6 +30,28 @@ function initTaskSyncSocket() {
setTimeout(() => initTaskSyncSocket(), 5000); 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) { export function sendSocketMessage(message: string) {
if (taskSyncSocket && taskSyncSocket.readyState === SocketState.OPEN) { if (taskSyncSocket && taskSyncSocket.readyState === SocketState.OPEN) {
taskSyncSocket.send(message); taskSyncSocket.send(message);
@@ -83,17 +59,7 @@ export function sendSocketMessage(message: string) {
} }
export function initialWebsocket() { export function initialWebsocket() {
initTaskSyncSocket(); 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

+ 4
- 2
src/services/user.ts Bestand weergeven

@@ -66,8 +66,10 @@ export async function login(account: string, password: string) {
return res; return res;
} }


export function logout() {
storage.clear();
export function logout(clearStorage = true) {
if (clearStorage) {
storage.clear();
}
system.logout(); system.logout();
// window.location.href = '/login'; // window.location.href = '/login';
history.replace('/login'); history.replace('/login');


+ 160
- 0
src/utils/hooks.ts Bestand weergeven

@@ -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<Array<DATA.SocketFileMsg>>([]);
const fetchMessages = useCallback(
throttle(async () => {
const accountId = storage.get('accountId');
const [headList, hisList] = await Promise.all<
API.ResponseData<any[]>,
API.ResponseData<any[]>
>([
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;
}

Laden…
Annuleren
Opslaan