重构前端逻辑

This commit is contained in:
dengqichen 2025-11-10 17:11:09 +08:00
parent a682d5718b
commit c285d224e4
3 changed files with 94 additions and 381 deletions

View File

@ -1,23 +0,0 @@
你是一名java高级开发工程师对你有以下要求
1. 缺陷修正:
- 在提出修复建议前,应充分分析问题
- 提供精准、有针对性的解决方案
- 解释bug的根本原因
2. 保持简单:
- 优先考虑可读性和可维护性
- 避免过度工程化的解决方案
- 尽可能使用标准库和模式
- 遵循正确、最佳实践、DRY原则、无错误、功能齐全的代码编写原则
3. 代码更改:
- 在做出改变之前提出一个清晰的计划
- 一次将所有修改应用于单个文件
- 请勿修改不相关的文件
4. 新增或者修改初始化数据
- 新增或者修改数据库表需在V1.0.0__init_schema.sql、V1.0.1__init_data.sql中补充表结构和初始化数据不要随意删除
记住要始终考虑每个项目的背景和特定需求。

View File

@ -1,291 +0,0 @@
# 前端接口对接文档
## 通用说明
### 1. 接口响应格式
所有接口统一返回以下格式:
```typescript
interface Response<T> {
success: boolean; // 请求是否成功
code: number; // 状态码200表示成功其他表示失败
message: string; // 提示信息
data?: T; // 响应数据,可选
}
```
### 2. 分页请求参数
```typescript
interface PageQuery {
pageNum: number; // 页码从1开始
pageSize: number; // 每页大小
sortField?: string; // 排序字段,可选
sortOrder?: 'asc' | 'desc'; // 排序方式,可选
}
```
### 3. 分页响应格式
```typescript
interface PageResponse<T> {
content: T[]; // 数据列表
totalElements: number;// 总记录数
totalPages: number; // 总页数
size: number; // 每页大小
number: number; // 当前页码从0开始
first: boolean; // 是否第一页
last: boolean; // 是否最后一页
empty: boolean; // 是否为空
}
```
## 通用接口
### 1. 基础CRUD接口
所有实体都支持以下基础操作接口:
#### 1.1 分页查询
```typescript
GET /api/v1/{module}/page
请求参数PageQuery & {
// 其他查询条件,根据具体模块定义
}
响应结果Response<PageResponse<T>>
```
#### 1.2 列表查询
```typescript
GET /api/v1/{module}/list
请求参数:{
// 查询条件,根据具体模块定义
}
响应结果Response<T[]>
```
#### 1.3 获取详情
```typescript
GET /api/v1/{module}/{id}
响应结果Response<T>
```
#### 1.4 创建
```typescript
POST /api/v1/{module}
请求参数:{
// 创建参数,根据具体模块定义
}
响应结果Response<T>
```
#### 1.5 更新
```typescript
PUT /api/v1/{module}/{id}
请求参数:{
// 更新参数,根据具体模块定义
}
响应结果Response<T>
```
#### 1.6 删除
```typescript
DELETE /api/v1/{module}/{id}
响应结果Response<void>
```
#### 1.7 批量删除
```typescript
DELETE /api/v1/{module}/batch
请求参数:{
ids: number[]; // ID列表
}
响应结果Response<void>
```
#### 1.8 导出数据
```typescript
GET /api/v1/{module}/export
请求参数:{
// 查询条件,根据具体模块定义
}
响应结果:二进制文件流
```
### 2. 树形结构接口
对于树形结构的数据(如部门、菜单等),还支持以下接口:
#### 2.1 获取树形数据
```typescript
GET /api/v1/{module}/tree
响应结果Response<TreeNode[]>
interface TreeNode {
id: number;
parentId: number | null;
children: TreeNode[];
// 其他字段根据具体模块定义
}
```
### 3. 状态管理接口
对于需要状态管理的数据(如租户、用户等),还支持以下接口:
#### 3.1 获取状态
```typescript
GET /api/v1/{module}/{id}/enabled
响应结果Response<boolean>
```
#### 3.2 更新状态
```typescript
PUT /api/v1/{module}/{id}/enabled
请求参数:
enabled=true // 是否启用
响应结果Response<void>
```
## 错误处理
### 1. 错误码说明
- 200成功
- 400请求参数错误
- 401未认证
- 403无权限
- 404资源不存在
- 500服务器内部错误
### 2. 错误响应示例
```json
{
"success": false,
"code": 400,
"message": "请求参数错误",
"data": {
"field": "name",
"message": "名称不能为空"
}
}
```
## 接口调用示例
### TypeScript 示例
```typescript
// 定义接口返回类型
interface User {
id: number;
username: string;
enabled: boolean;
}
// 分页查询
async function getUserList(query: PageQuery & { username?: string }) {
const response = await axios.get<Response<PageResponse<User>>>('/api/v1/user/page', {
params: query
});
return response.data;
}
// 创建用户
async function createUser(user: Omit<User, 'id'>) {
const response = await axios.post<Response<User>>('/api/v1/user', user);
return response.data;
}
// 更新状态
async function updateUserStatus(id: number, enabled: boolean) {
const response = await axios.put<Response<void>>(`/api/v1/user/${id}/enabled`, null, {
params: { enabled }
});
return response.data;
}
```
### Vue3 + TypeScript 示例
```typescript
// 在组件中使用
import { ref, onMounted } from 'vue';
export default defineComponent({
setup() {
const userList = ref<User[]>([]);
const total = ref(0);
const loading = ref(false);
const queryList = async (query: PageQuery) => {
try {
loading.value = true;
const response = await getUserList(query);
if (response.success) {
userList.value = response.data.content;
total.value = response.data.totalElements;
}
} finally {
loading.value = false;
}
};
onMounted(() => {
queryList({
pageNum: 1,
pageSize: 10
});
});
return {
userList,
total,
loading,
queryList
};
}
});
```
## 注意事项
1. 请求头要求
```typescript
{
'Content-Type': 'application/json',
'Authorization': 'Bearer ${token}' // JWT认证token
}
```
2. 日期时间格式
- 请求参数使用ISO 8601格式YYYY-MM-DDTHH:mm:ss.sssZ
- 响应数据统一返回ISO 8601格式
3. 文件上传
- 使用multipart/form-data格式
- 文件大小限制10MB
4. 接口版本
- 所有接口统一使用v1版本
- URL格式/api/v1/{module}/{resource}
5. 安全性
- 所有接口都需要JWT认证
- Token过期时间2小时
- 需要定期刷新Token
6. 错误处理
- 统一使用axios拦截器处理错误
- 401错误跳转到登录页
- 其他错误统一提示

View File

@ -71,16 +71,17 @@ const getLayoutedElements = (
const dagreGraph = new dagre.graphlib.Graph(); const dagreGraph = new dagre.graphlib.Graph();
dagreGraph.setDefaultEdgeLabel(() => ({})); dagreGraph.setDefaultEdgeLabel(() => ({}));
const nodeWidth = 180; const nodeWidth = 200;
const nodeHeight = 120; const nodeHeight = 150;
const isHorizontal = direction === 'LR'; const isHorizontal = direction === 'LR';
dagreGraph.setGraph({ dagreGraph.setGraph({
rankdir: direction, rankdir: direction,
nodesep: 50, nodesep: isHorizontal ? 80 : 100, // 同一层节点间距横向80纵向100
ranksep: 80, ranksep: isHorizontal ? 150 : 120, // 层级间距横向150纵向120
marginx: 20, marginx: 40,
marginy: 20, marginy: 40,
align: 'UL', // 对齐方式
}); });
nodes.forEach((node) => { nodes.forEach((node) => {
@ -137,57 +138,73 @@ const CustomFlowNode: React.FC<any> = ({ data }) => {
const nodeContent = ( const nodeContent = (
<div <div
className={cn( className={cn(
'px-3 py-2 rounded-md min-w-[160px] transition-all', 'px-4 py-3 rounded-lg w-[190px] min-h-[140px] transition-all duration-200 relative overflow-hidden flex flex-col',
isNotStarted && 'border-2 border-dashed', isNotStarted && 'border-2 border-dashed bg-gradient-to-br from-slate-50 to-gray-50',
!isNotStarted && 'border-2 border-solid shadow-sm', !isNotStarted && 'border-2 border-solid shadow-lg hover:shadow-2xl bg-white',
isRunning && 'animate-pulse', isRunning && 'ring-2 ring-blue-400 ring-opacity-60 shadow-blue-200',
canViewLog && 'cursor-pointer hover:shadow-md hover:scale-[1.02]' // 可查看日志的节点增加交互效果 canViewLog && 'cursor-pointer hover:shadow-2xl hover:scale-105 active:scale-100'
)} )}
style={{ style={{
borderColor: statusColor, borderColor: statusColor,
backgroundColor: isNotStarted ? '#f9fafb' : '#ffffff', borderWidth: '3px',
}} }}
> >
{/* 顶部状态指示条 */}
{!isNotStarted && (
<div
className="absolute top-0 left-0 right-0 h-1"
style={{ backgroundColor: statusColor, opacity: 0.6 }}
/>
)}
{/* 节点名称 */} {/* 节点名称 */}
<div className="flex items-center justify-between gap-2 mb-1"> <div className="flex items-center justify-between gap-2 mb-2 mt-0.5">
<div className="font-medium text-sm truncate">{nodeName}</div> <div className="font-semibold text-sm truncate text-gray-900">{nodeName}</div>
{canViewLog && ( {canViewLog && (
<FileText className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" /> <FileText className="h-4 w-4 text-blue-500 flex-shrink-0" />
)} )}
</div> </div>
{/* 节点状态 */} {/* 节点状态 - 使用徽章样式 */}
<div <div
className="text-xs font-medium mb-1" className={cn(
style={{ color: statusColor }} "inline-flex items-center px-2 py-0.5 rounded-full text-xs font-bold mb-2",
isRunning && "animate-pulse"
)}
style={{
backgroundColor: `${statusColor}20`,
color: statusColor,
border: `1.5px solid ${statusColor}`
}}
> >
{getNodeStatusText(status)} {getNodeStatusText(status)}
</div> </div>
{/* 时间信息 */} {/* 时间信息 - 更紧凑的显示 */}
{!isNotStarted && ( {!isNotStarted && displayDuration && (
<div className="text-xs text-muted-foreground space-y-0.5"> <div className="text-xs text-muted-foreground mb-1">
{displayDuration && ( <div className="font-medium text-blue-600">
<div className="font-medium"> {displayDuration}
{isRunning ? '运行: ' : '时长: '} </div>
{displayDuration}
</div>
)}
</div> </div>
)} )}
{/* 错误提示 */} {/* 弹性空间,让底部提示始终在底部 */}
<div className="flex-1 min-h-[4px]" />
{/* 错误提示 - 增强样式 */}
{hasFailed && errorMessage && ( {hasFailed && errorMessage && (
<div className="mt-1 flex items-center gap-1 text-red-600"> <div className="flex items-center gap-1.5 px-2 py-1 rounded bg-red-50 border border-red-200">
<AlertCircle className="h-3 w-3" /> <AlertCircle className="h-3.5 w-3.5 text-red-600 flex-shrink-0" />
<span className="text-xs"></span> <span className="text-xs font-medium text-red-700"></span>
</div> </div>
)} )}
{/* 查看日志提示 */} {/* 查看日志提示 - 增强样式 */}
{canViewLog && ( {canViewLog && !hasFailed && (
<div className="mt-1 text-xs text-blue-600 flex items-center gap-1"> <div className="flex items-center gap-1.5 px-2 py-1 rounded bg-blue-50 border border-blue-200">
<span></span> <FileText className="h-3.5 w-3.5 text-blue-600 flex-shrink-0" />
<span className="text-xs font-medium text-blue-700"></span>
</div> </div>
)} )}
</div> </div>
@ -298,35 +315,39 @@ const ProgressCard: React.FC<{ flowData: DeployRecordFlowGraph }> = ({ flowData
</CardHeader> </CardHeader>
<CardContent className="space-y-3"> <CardContent className="space-y-3">
<div> <div>
<div className="flex justify-between text-sm mb-2"> <div className="flex justify-between items-center mb-3">
<span className="text-muted-foreground"></span> <span className="text-sm font-semibold text-blue-600"></span>
<span className="font-medium"> <span className="text-xl font-bold text-blue-600">
{executedNodeCount} / {totalNodeCount} {executedNodeCount} / {totalNodeCount}
</span> </span>
</div> </div>
<Progress value={progress} className="h-2" /> <div className="relative">
<div className="text-xs text-muted-foreground mt-1 text-right"> <Progress value={progress} className="h-3 bg-blue-100" />
{Math.round(progress)}% <div className="absolute inset-0 flex items-center justify-end pr-2">
<span className="text-xs font-bold text-white drop-shadow">
{Math.round(progress)}%
</span>
</div>
</div> </div>
</div> </div>
<Separator /> <Separator />
<div className="grid grid-cols-3 gap-2 text-xs"> <div className="grid grid-cols-3 gap-3 text-xs">
<div className="flex flex-col items-center p-2 rounded-md bg-green-50"> <div className="flex flex-col items-center p-3 rounded-lg bg-gradient-to-br from-green-50 to-emerald-50 border border-green-100">
<CheckCircle2 className="h-4 w-4 text-green-600 mb-1" /> <CheckCircle2 className="h-5 w-5 text-green-600 mb-1.5" />
<span className="text-muted-foreground"></span> <span className="text-muted-foreground text-[10px]"></span>
<span className="font-medium text-green-600">{successNodeCount}</span> <span className="font-bold text-lg text-green-600">{successNodeCount}</span>
</div> </div>
<div className="flex flex-col items-center p-2 rounded-md bg-red-50"> <div className="flex flex-col items-center p-3 rounded-lg bg-gradient-to-br from-red-50 to-rose-50 border border-red-100">
<XCircle className="h-4 w-4 text-red-600 mb-1" /> <XCircle className="h-5 w-5 text-red-600 mb-1.5" />
<span className="text-muted-foreground"></span> <span className="text-muted-foreground text-[10px]"></span>
<span className="font-medium text-red-600">{failedNodeCount}</span> <span className="font-bold text-lg text-red-600">{failedNodeCount}</span>
</div> </div>
<div className="flex flex-col items-center p-2 rounded-md bg-blue-50"> <div className="flex flex-col items-center p-3 rounded-lg bg-gradient-to-br from-blue-50 to-cyan-50 border border-blue-100">
<Loader2 className="h-4 w-4 text-blue-600 mb-1" /> <Loader2 className={cn("h-5 w-5 text-blue-600 mb-1.5", runningNodeCount > 0 && "animate-spin")} />
<span className="text-muted-foreground"></span> <span className="text-muted-foreground text-[10px]"></span>
<span className="font-medium text-blue-600">{runningNodeCount}</span> <span className="font-bold text-lg text-blue-600">{runningNodeCount}</span>
</div> </div>
</div> </div>
</CardContent> </CardContent>
@ -433,7 +454,7 @@ const MonitoringAlert: React.FC<{ flowData: DeployRecordFlowGraph }> = ({ flowDa
*/ */
const DeployInfoPanel: React.FC<{ flowData: DeployRecordFlowGraph }> = ({ flowData }) => { const DeployInfoPanel: React.FC<{ flowData: DeployRecordFlowGraph }> = ({ flowData }) => {
return ( return (
<div className="w-80 flex-shrink-0 border-r bg-muted/30 p-4 space-y-4 overflow-y-auto"> <div className="w-80 flex-shrink-0 border-r-2 border-border bg-gradient-to-b from-muted/20 to-muted/40 p-4 space-y-4 overflow-y-auto shadow-[2px_0_8px_rgba(0,0,0,0.05)]">
<MonitoringAlert flowData={flowData} /> <MonitoringAlert flowData={flowData} />
<BusinessInfoCard flowData={flowData} /> <BusinessInfoCard flowData={flowData} />
<ProgressCard flowData={flowData} /> <ProgressCard flowData={flowData} />
@ -581,7 +602,7 @@ export const DeployFlowGraphModal: React.FC<DeployFlowGraphModalProps> = ({
return { return {
id: node.id, id: node.id,
type: 'custom', type: 'custom',
position: node.position || { x: 0, y: 0 }, // 使用设计器中保存的坐标 position: { x: 0, y: 0 }, // 忽略设计器坐标使用dagre自动布局
data: { data: {
nodeName: node.nodeName, nodeName: node.nodeName,
nodeType: instance?.nodeType || node.nodeType, // 优先使用运行时的 nodeType nodeType: instance?.nodeType || node.nodeType, // 优先使用运行时的 nodeType
@ -617,32 +638,37 @@ export const DeployFlowGraphModal: React.FC<DeployFlowGraphModalProps> = ({
const targetStatus = targetInstance?.status || 'NOT_STARTED'; const targetStatus = targetInstance?.status || 'NOT_STARTED';
// 根据节点状态确定边的样式 // 根据节点状态确定边的样式
let strokeColor = '#d1d5db'; // 默认灰色 let strokeColor = '#cbd5e1'; // 默认浅灰色
let strokeWidth = 2; let strokeWidth = 3; // 增加连线粗细
let animated = false; let animated = false;
let strokeDasharray: string | undefined = undefined; let strokeDasharray: string | undefined = undefined;
// 源节点已完成 + 目标节点也已完成/运行中 = 绿色实线 // 源节点已完成 + 目标节点也已完成/运行中 = 绿色实线
if (sourceStatus === 'COMPLETED' && (targetStatus === 'COMPLETED' || targetStatus === 'RUNNING')) { if (sourceStatus === 'COMPLETED' && (targetStatus === 'COMPLETED' || targetStatus === 'RUNNING')) {
strokeColor = '#10b981'; // 绿色 strokeColor = '#10b981'; // 绿色
strokeWidth = 3.5;
} }
// 源节点 TERMINATED = 橙色实线 // 源节点 TERMINATED = 橙色实线
else if (sourceStatus === 'TERMINATED') { else if (sourceStatus === 'TERMINATED') {
strokeColor = '#f59e0b'; // 橙色 strokeColor = '#f59e0b'; // 橙色
strokeWidth = 3.5;
} }
// 源节点失败 = 红色实线 // 源节点失败 = 红色实线
else if (sourceStatus === 'FAILED') { else if (sourceStatus === 'FAILED') {
strokeColor = '#ef4444'; // 红色 strokeColor = '#ef4444'; // 红色
strokeWidth = 3.5;
} }
// 源节点运行中 = 蓝色动画 // 源节点运行中 = 蓝色动画
else if (sourceStatus === 'RUNNING') { else if (sourceStatus === 'RUNNING') {
strokeColor = '#3b82f6'; // 蓝色 strokeColor = '#3b82f6'; // 蓝色
strokeWidth = 4; // 运行中的线更粗
animated = true; animated = true;
} }
// 源节点完成 + 目标节点未开始 = 虚线(即将执行的路径) // 源节点完成 + 目标节点未开始 = 虚线(即将执行的路径)
else if ((sourceStatus === 'COMPLETED' || sourceStatus === 'TERMINATED') && targetStatus === 'NOT_STARTED') { else if ((sourceStatus === 'COMPLETED' || sourceStatus === 'TERMINATED') && targetStatus === 'NOT_STARTED') {
strokeColor = '#9ca3af'; // 浅灰色 strokeColor = '#94a3b8'; // 稍深的灰色
strokeDasharray = '5,5'; strokeDasharray = '8,4';
strokeWidth = 2.5;
} }
return { return {
@ -659,8 +685,8 @@ export const DeployFlowGraphModal: React.FC<DeployFlowGraphModalProps> = ({
markerEnd: { markerEnd: {
type: 'arrowclosed', type: 'arrowclosed',
color: strokeColor, color: strokeColor,
width: 20, width: 24,
height: 20, height: 24,
}, },
}; };
}); });
@ -773,16 +799,17 @@ export const DeployFlowGraphModal: React.FC<DeployFlowGraphModalProps> = ({
> >
<Background <Background
variant={BackgroundVariant.Dots} variant={BackgroundVariant.Dots}
gap={16} gap={20}
size={1} size={1.5}
className="opacity-30" color="#cbd5e1"
className="opacity-40"
/> />
<Controls <Controls
position="bottom-right" position="bottom-right"
showZoom={true} showZoom={true}
showFitView={true} showFitView={true}
showInteractive={false} showInteractive={false}
className="!shadow-lg !border !border-border" className="!shadow-xl !border-2 !border-border !rounded-lg !overflow-hidden"
/> />
<MiniMap <MiniMap
position="bottom-left" position="bottom-left"