You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

241 lines
7.7 KiB
TypeScript

import React, { useState, useEffect, useRef } from 'react';
import styles from './style/index.module.less';
import { Button, Input, Modal, Radio, Space, Select, Message } from '@arco-design/web-react';
import { IconSearch } from '@arco-design/web-react/icon';
import CollapseList from './collapseList';
import { startStatusConstant } from '@/const/isdp/componentDeploy';
import ConfigTutorial from '@/pages/componentDevelopment/componentEnv/configTutorial';
import { getComponentClassify } from '@/api/componentClassify';
import useWebSocket from '@/hooks/useWebSocket';
import { getToken } from '@/utils/auth';
import { isJSON } from '@/utils/common';
const ComponentDeployment = () => {
const [searchKeyword, setSearchKeyword] = useState('');
const [debouncedKeyword, setDebouncedKeyword] = useState('');
const [selectedClassify, setSelectedClassify] = useState<string | undefined>(undefined);
const [classifyOptions, setClassifyOptions] = useState<{ label: string; value: string }[]>([]);
const [selectedStatus, setSelectedStatus] = useState<string | undefined>(undefined);
const [tutorialVisible, setTutorialVisible] = useState(false);
// 编译日志相关状态 - 按实例 ID 分组存储
const [compileLogsMap, setCompileLogsMap] = useState<Record<string, string>>({});
// 编译状态管理 - 按实例 ID 存储状态
const [compileStatusMap, setCompileStatusMap] = useState<Record<string, 'idle' | 'running' | 'success' | 'failed' | 'canceled'>>({});
const compileLogSequenceRef = useRef<Record<string, number>>({});
const currentCompileIdRef = useRef<string | null>(null);
// WebSocket 连接 - 用于接收编译日志
const { connect: wsConnect, disconnect: wsDisconnect, sendMessage, isConnected } = useWebSocket({
onMessage: (event) => {
const id = currentCompileIdRef.current;
if (!id) return;
try {
const currentSequence = (compileLogSequenceRef.current[id] || 0) + 1;
compileLogSequenceRef.current[id] = currentSequence;
if (isJSON(event.data)) {
const parseData = JSON.parse(event.data);
const message = parseData.message || parseData.line || event.data;
const sequenceMessage = `${currentSequence}. ${message}`;
setCompileLogsMap(prev => ({
...prev,
[id]: prev[id] ? prev[id] + '\n' + sequenceMessage : sequenceMessage
}));
}
else {
const data = `${currentSequence}. ${event.data}`;
setCompileLogsMap(prev => ({
...prev,
[id]: prev[id] ? prev[id] + '\n' + data : data
}));
}
} catch (error) {
console.error('解析 WebSocket 消息失败:', error);
}
},
onOpen: () => {
console.log('编译日志 WebSocket 连接成功');
},
onError: (error) => {
console.error('编译日志 WebSocket 错误:', error);
},
onClose: () => {
console.log('编译日志 WebSocket 连接已关闭');
}
});
// 页面挂载时连接 WebSocket
useEffect(() => {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const host = window.location.host;
let wsUrl;
if (window.location.host.includes('localhost')) {
wsUrl = `${process.env.NEXT_PUBLIC_DEV_SOCKET_HOST}/ws/v1/bpms-workbench/instance-log?Authorization=Bearer ${getToken()}`;
}
else {
wsUrl = `${protocol}//${host}/ws/v1/bpms-workbench/instance-log?Authorization=Bearer ${getToken()}`;
}
wsConnect(wsUrl);
return () => {
wsDisconnect();
};
}, []);
useEffect(() => {
const timer = setTimeout(() => setDebouncedKeyword(searchKeyword), 500);
return () => clearTimeout(timer);
}, [searchKeyword]);
useEffect(() => {
getComponentClassify('component').then((res: any) => {
if (res.code === 200) {
setClassifyOptions(res.data.map((item) => ({ label: item.classifyName, value: item.classifyName })));
}
});
}, []);
// 订阅实例编译日志
const subscribeInstanceLog = (instanceId: string) => {
currentCompileIdRef.current = instanceId;
compileLogSequenceRef.current[instanceId] = 0;
// 清空该实例之前的日志
setCompileLogsMap(prev => ({
...prev,
[instanceId]: ''
}));
// 设置编译状态为 running
setCompileStatusMap(prev => ({
...prev,
[instanceId]: 'running'
}));
if (isConnected) {
sendMessage({
instanceId,
type: 'subscribe'
});
}
else {
Message.warning('WebSocket 未连接,无法实时接收日志');
}
};
// 取消订阅实例编译日志
const unsubscribeInstanceLog = (instanceId: string) => {
if (isConnected && instanceId) {
sendMessage({
instanceId,
type: 'unsubscribe'
});
}
if (currentCompileIdRef.current === instanceId) {
currentCompileIdRef.current = null;
}
};
// 更新编译状态
const updateCompileStatus = (instanceId: string, status: 'idle' | 'running' | 'success' | 'failed' | 'canceled') => {
setCompileStatusMap(prev => ({
...prev,
[instanceId]: status
}));
};
// 状态选项配置
const statusOptions = [
{ label: '全部', value: undefined },
{ label: '启用中', value: 'RUN' },
{ label: '已下架', value: 'STOP' }
];
return (
<>
<div className={styles['component-deployment']}>
<div className={styles['header']}>
<Radio.Group
value={selectedStatus}
onChange={(value) => setSelectedStatus(value)}
name="button-radio-group"
>
{statusOptions.map((item) => {
return (
<Radio key={item.label} value={item.value}>
{({ checked }) => {
return (
<Button tabIndex={-1} key={item.label} shape="round" type={checked ? 'primary' : 'default'}>
{item.label}
</Button>
);
}}
</Radio>
);
})}
</Radio.Group>
<Space>
<Select
placeholder="组件分类"
allowClear
style={{ width: 160 }}
value={selectedClassify}
onChange={(value) => setSelectedClassify(value)}
options={classifyOptions}
/>
<Input
prefix={<IconSearch />}
placeholder={'搜索'}
style={{ width: 236 }}
value={searchKeyword}
onChange={(value) => setSearchKeyword(value)}
onPressEnter={() => {
// 触发搜索
}}
/>
<Button
type="primary"
style={{ marginLeft: 5, borderRadius: 4 }}
onClick={() => setTutorialVisible(true)}
>
</Button>
</Space>
</div>
<div className={styles['content']}>
<CollapseList
searchKeyword={debouncedKeyword}
runStatus={selectedStatus}
componentClassify={selectedClassify}
compileLogsMap={compileLogsMap}
compileStatusMap={compileStatusMap}
subscribeInstanceLog={subscribeInstanceLog}
unsubscribeInstanceLog={unsubscribeInstanceLog}
updateCompileStatus={updateCompileStatus}
isWsConnected={isConnected}
/>
</div>
</div>
<Modal
title="组件实例教程"
visible={tutorialVisible}
onCancel={() => setTutorialVisible(false)}
footer={null}
style={{ top: 20, width: '100%' }}
>
<ConfigTutorial tutorialType="compInstance" />
</Modal>
</>
);
};
export default ComponentDeployment;