Files
bettafish-company/templates/workflow_editor.html
T

720 lines
30 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工作流编辑器 - 微博舆情分析系统</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.2.0/css/all.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/jsoneditor@9.5.0/dist/jsoneditor.min.css" rel="stylesheet">
<style>
:root {
--primary-color: #1890ff;
--success-color: #52c41a;
--warning-color: #faad14;
--error-color: #f5222d;
--bg-color: #f0f2f5;
--component-bg: #fafafa;
--border-color: #d9d9d9;
}
body {
background-color: var(--bg-color);
font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.navbar-brand {
font-weight: 600;
}
/* 修复顶部栏固定问题 */
.navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1030;
/* 修复banner宽度问题 */
width: 100%;
max-width: 100%;
}
/* 添加顶部导航栏高度的内边距,防止内容被遮挡 */
.container-fluid {
/* padding-top: 10px; */
}
/* 修复侧边栏样式 */
.sidebar {
position: fixed;
top: 56px;
bottom: 0;
left: 0;
z-index: 100;
padding: 20px 0;
width: 280px;
overflow-x: hidden;
/* 确保只有一个滚动条 */
overflow-y: auto;
background-color: white;
border-right: 1px solid var(--border-color);
}
/* 完全移除子面板的独立滚动 */
#componentsPanel, #templatesPanel {
height: auto;
padding: 0 15px;
/* 完全禁用独立滚动 */
overflow: visible;
}
.main-content {
margin-top: 50px;
margin-left: 280px;
padding: 20px;
}
.workflow-canvas {
background-color: white;
min-height: calc(100vh - 200px);
border-radius: 8px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
position: relative;
overflow: hidden;
}
.component-container {
padding: 15px;
border-radius: 6px;
border: 1px solid var(--border-color);
margin-bottom: 15px;
background-color: var(--component-bg);
}
.component-item {
background-color: white;
padding: 8px 12px;
border-radius: 4px;
margin: 8px 0;
border: 1px solid var(--border-color);
cursor: move;
user-select: none;
transition: all 0.3s;
}
.component-item:hover {
border-color: var(--primary-color);
box-shadow: 0 2px 5px rgba(24, 144, 255, 0.15);
}
.workflow-node {
position: absolute;
width: 200px;
min-height: 100px;
background-color: white;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 12px;
cursor: move;
z-index: 10;
transition: transform 0.3s;
}
.workflow-node .node-header {
border-bottom: 1px solid #eee;
padding-bottom: 8px;
margin-bottom: 8px;
}
.workflow-node .node-title {
font-weight: 600;
font-size: 14px;
display: block;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.workflow-node .node-type {
font-size: 12px;
color: #666;
}
.workflow-node .node-ports {
position: relative;
height: 20px;
margin-top: 15px;
}
.port {
position: absolute;
width: 12px;
height: 12px;
border-radius: 50%;
background-color: var(--primary-color);
cursor: crosshair;
}
.port-in {
top: 4px;
left: -6px;
}
.port-out {
top: 4px;
right: -6px;
}
.connection-path {
stroke: var(--primary-color);
stroke-width: 2px;
fill: none;
}
/* 修复模板项布局样式 */
.templates-wrapper {
padding: 0 15px;
max-height: calc(100vh - 180px);
overflow-y: auto;
}
.template-item {
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 15px;
margin-bottom: 15px;
background-color: white;
transition: all 0.3s;
}
.template-item:hover {
border-color: var(--primary-color);
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15);
}
.template-item .template-title {
font-weight: 600;
color: #333;
font-size: 14px;
margin-bottom: 5px;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.template-item .template-desc {
color: #666;
font-size: 12px;
margin-top: 5px;
margin-bottom: 10px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
height: 36px;
}
.active-tab {
border-bottom: 2px solid var(--primary-color);
color: var(--primary-color);
font-weight: 600;
}
.tab-content {
padding-top: 20px;
}
/* 优化组件和模板面板的滚动行为 */
#componentsPanel, #templatesPanel {
height: calc(100vh - 120px);
overflow-y: auto;
padding: 0 15px;
}
.node-entering {
animation: nodeEnter 0.3s ease;
}
@keyframes nodeEnter {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
/* 添加拖放反馈样式 */
.workflow-canvas.drag-over {
border: 2px dashed var(--primary-color);
background-color: rgba(24, 144, 255, 0.05);
}
/* 添加通知样式 */
.workflow-notification {
background-color: white;
border-radius: 6px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
padding: 12px;
margin-bottom: 10px;
transform: translateX(100%);
transition: transform 0.3s;
max-width: 320px;
}
.workflow-notification.show {
transform: translateX(0);
}
.notification-success {
border-left: 4px solid var(--success-color);
}
.notification-warning {
border-left: 4px solid var(--warning-color);
}
.notification-error {
border-left: 4px solid var(--error-color);
}
.notification-info {
border-left: 4px solid var(--primary-color);
}
/* 优化节点选中样式 */
.workflow-node.selected {
box-shadow: 0 0 0 2px var(--primary-color);
z-index: 11;
}
.port-highlight {
box-shadow: 0 0 0 3px rgba(24, 144, 255, 0.5);
transform: scale(1.2);
}
/* 媒体查询用于响应式设计 */
@media (max-width: 768px) {
.sidebar {
width: 100%;
position: static;
height: auto;
padding-bottom: 0;
}
.main-content {
margin-left: 0;
}
.properties-panel {
width: 100%;
position: fixed;
top: auto;
bottom: 0;
right: 0;
transform: translateY(100%);
border-radius: 8px 8px 0 0;
max-height: 70vh;
}
.properties-panel.open {
transform: translateY(0);
}
#componentsPanel, #templatesPanel {
height: auto;
max-height: 400px;
}
}
/* 修复属性面板样式 */
.properties-panel {
position: fixed;
top: 70px;
right: 20px;
width: 320px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
padding: 15px;
transform: translateX(calc(100% + 20px));
transition: transform 0.3s ease;
z-index: 900;
max-height: calc(100vh - 100px);
overflow-y: auto;
}
.properties-panel.open {
transform: translateX(0);
}
</style>
</head>
<body>
<!-- 导航栏保持不变 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="#">
<i class="fas fa-project-diagram me-2"></i>工作流编辑器
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link active" href="#">可视化编辑</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">模板管理</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">任务列表</a>
</li>
</ul>
<div class="d-flex">
<button id="saveWorkflowBtn" class="btn btn-success me-2">
<i class="fas fa-save me-1"></i>保存
</button>
<button id="runWorkflowBtn" class="btn btn-primary">
<i class="fas fa-play me-1"></i>运行
</button>
</div>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<!-- 侧边栏 - 修改模板面板的结构 -->
<div class="col-md-3 col-lg-2 d-md-block sidebar">
<div class="d-flex justify-content-center mb-4">
<div class="btn-group">
<button class="btn btn-outline-primary active" id="componentsTabBtn">组件</button>
<button class="btn btn-outline-primary" id="templatesTabBtn">模板</button>
</div>
</div>
<!-- 组件面板保持不变 -->
<div id="componentsPanel">
<div class="component-container">
<h6><i class="fas fa-database me-2"></i>数据源</h6>
<div class="component-list">
<div class="component-item" data-type="data_source" data-subtype="database">
<i class="fas fa-table me-2"></i>数据库
</div>
<div class="component-item" data-type="data_source" data-subtype="file">
<i class="fas fa-file-alt me-2"></i>文件
</div>
<div class="component-item" data-type="data_source" data-subtype="crawler">
<i class="fas fa-spider me-2"></i>爬虫
</div>
</div>
</div>
<div class="component-container">
<h6><i class="fas fa-filter me-2"></i>数据处理</h6>
<div class="component-list">
<div class="component-item" data-type="preprocessing" data-subtype="filter">
<i class="fas fa-filter me-2"></i>过滤
</div>
<div class="component-item" data-type="preprocessing" data-subtype="sort">
<i class="fas fa-sort me-2"></i>排序
</div>
<div class="component-item" data-type="preprocessing" data-subtype="aggregate">
<i class="fas fa-layer-group me-2"></i>聚合
</div>
</div>
</div>
<div class="component-container">
<h6><i class="fas fa-brain me-2"></i>模型分析</h6>
<div class="component-list">
<div class="component-item" data-type="model" data-subtype="sentiment">
<i class="fas fa-smile me-2"></i>情感分析
</div>
<div class="component-item" data-type="model" data-subtype="topic">
<i class="fas fa-tags me-2"></i>话题分类
</div>
<div class="component-item" data-type="model" data-subtype="keywords">
<i class="fas fa-key me-2"></i>关键词提取
</div>
<div class="component-item" data-type="model" data-subtype="summarize">
<i class="fas fa-compress-alt me-2"></i>文本摘要
</div>
</div>
</div>
<div class="component-container">
<h6><i class="fas fa-chart-bar me-2"></i>可视化</h6>
<div class="component-list">
<div class="component-item" data-type="visualization" data-subtype="chart">
<i class="fas fa-chart-line me-2"></i>图表
</div>
<div class="component-item" data-type="visualization" data-subtype="table">
<i class="fas fa-table me-2"></i>表格
</div>
<div class="component-item" data-type="visualization" data-subtype="wordcloud">
<i class="fas fa-cloud me-2"></i>词云
</div>
</div>
</div>
</div>
<!-- 模板面板 - 修改结构和样式 -->
<div id="templatesPanel" style="display: none;">
<div class="mb-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0">爬虫模板</h6>
<button class="btn btn-sm btn-outline-primary">
<i class="fas fa-plus"></i> 新建
</button>
</div>
<div class="templates-wrapper">
<div id="crawlerTemplatesList">
<!-- 爬虫模板列表将动态加载 -->
</div>
</div>
</div>
<div class="mb-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0">分析流程模板</h6>
<button class="btn btn-sm btn-outline-primary">
<i class="fas fa-plus"></i> 新建
</button>
</div>
<div class="templates-wrapper">
<div id="analysisTemplatesList">
<!-- 分析流程模板列表将动态加载 -->
</div>
</div>
</div>
</div>
</div>
<!-- 主要内容 -->
<div class="col-md-9 col-lg-10 main-content">
<!-- 添加工作流工具栏 -->
<div class="d-flex justify-content-between align-items-center mb-3" id="workflowToolbar">
<div class="btn-group">
<button id="undoBtn" class="btn btn-sm btn-outline-secondary" title="撤销">
<i class="fas fa-undo"></i>
</button>
<button id="redoBtn" class="btn btn-sm btn-outline-secondary" title="重做">
<i class="fas fa-redo"></i>
</button>
<button id="zoomInBtn" class="btn btn-sm btn-outline-secondary" title="放大">
<i class="fas fa-search-plus"></i>
</button>
<button id="zoomOutBtn" class="btn btn-sm btn-outline-secondary" title="缩小">
<i class="fas fa-search-minus"></i>
</button>
<button id="fitViewBtn" class="btn btn-sm btn-outline-secondary" title="适应视图">
<i class="fas fa-expand"></i>
</button>
</div>
<div>
<button id="validateWorkflowBtn" class="btn btn-sm btn-outline-primary" title="验证工作流">
<i class="fas fa-check-circle"></i> 验证
</button>
<button id="exportWorkflowBtn" class="btn btn-sm btn-outline-secondary" title="导出工作流">
<i class="fas fa-file-export"></i>
</button>
<button id="importWorkflowBtn" class="btn btn-sm btn-outline-secondary" title="导入工作流">
<i class="fas fa-file-import"></i>
</button>
</div>
</div>
<div class="workflow-canvas" id="workflowCanvas">
<!-- 工作流节点和连接将在这里动态创建 -->
<svg id="connectionsSvg" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none;">
<!-- 连接线将在这里动态创建 -->
</svg>
</div>
<!-- 添加工作流状态栏 -->
<div class="d-flex justify-content-between align-items-center p-2 bg-light rounded mt-3" id="workflowStatusBar" style="display: none !important;">
<div id="workflowStatusMessage" class="text-muted">
工作流就绪。拖拽左侧组件到画布创建节点。
</div>
<div class="d-flex">
<div class="me-3">节点: <span id="nodeCount">0</span></div>
<div>连接: <span id="connectionCount">0</span></div>
</div>
</div>
</div>
</div>
</div>
<!-- 属性面板 -->
<div class="properties-panel" id="propertiesPanel">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0">组件属性</h5>
<button class="btn-close" id="closePropertiesBtn"></button>
</div>
<div id="propertiesContent">
<!-- 属性内容将动态加载 -->
</div>
</div>
<!-- 模态框 -->
<div class="modal fade" id="saveTemplateModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">保存为模板</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="saveTemplateForm">
<div class="mb-3">
<label for="templateName" class="form-label">模板名称</label>
<input type="text" class="form-control" id="templateName" required>
</div>
<div class="mb-3">
<label for="templateDescription" class="form-label">描述</label>
<textarea class="form-control" id="templateDescription" rows="3"></textarea>
</div>
<div class="mb-3">
<label for="templateIcon" class="form-label">图标</label>
<select class="form-select" id="templateIcon">
<option value="chart-line">📊 图表</option>
<option value="filter">🔍 过滤</option>
<option value="spider">🕸️ 爬虫</option>
<option value="brain">🧠 AI分析</option>
<option value="database">💾 数据库</option>
<option value="cloud">☁️ 词云</option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="saveTemplateBtn">保存</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="runWorkflowModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">运行工作流</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>确认要运行当前工作流吗?</p>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="saveBeforeRun" checked>
<label class="form-check-label" for="saveBeforeRun">
运行前保存工作流
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="confirmRunBtn">运行</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="taskStatusModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">任务执行状态</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<h6 class="d-flex align-items-center">
<i class="fas fa-tasks me-2"></i>进度
<div class="ms-auto" id="taskProgressPercentage">0%</div>
</h6>
<div class="progress" style="height: 10px;">
<div id="taskProgressBar" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%"></div>
</div>
</div>
<div class="mb-3">
<h6 class="d-flex align-items-center">
<i class="fas fa-info-circle me-2"></i>状态信息
<span id="taskStatusBadge" class="ms-2 badge bg-info">等待中</span>
</h6>
<div id="taskStatusInfo" class="p-3 bg-light rounded border">
<div class="row g-2">
<div class="col-md-6">
<p class="mb-1"><strong>任务ID:</strong> <span id="taskIdDisplay">-</span></p>
<p class="mb-1"><strong>状态:</strong> <span id="taskStatusDisplay">-</span></p>
</div>
<div class="col-md-6">
<p class="mb-1"><strong>开始时间:</strong> <span id="taskStartTimeDisplay">-</span></p>
<p class="mb-1"><strong>完成时间:</strong> <span id="taskCompleteTimeDisplay">-</span></p>
</div>
</div>
<div class="mt-2" id="taskDetailsContainer">
<p class="mb-1"><strong>当前步骤:</strong> <span id="taskCurrentStepDisplay">等待开始</span></p>
<p class="mb-0"><strong>耗时:</strong> <span id="taskElapsedTimeDisplay">0秒</span></p>
</div>
</div>
</div>
<div>
<h6 class="d-flex align-items-center">
<i class="fas fa-chart-bar me-2"></i>结果预览
<button class="btn btn-sm btn-outline-secondary ms-auto" id="refreshPreviewBtn" title="刷新预览">
<i class="fas fa-sync-alt"></i>
</button>
</h6>
<div id="taskResultPreview" class="p-3 bg-light rounded border" style="max-height: 300px; overflow: auto;">
<div class="text-center py-4" id="previewLoadingIndicator">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">加载中...</span>
</div>
<p class="text-muted mt-2">任务运行中,正在准备预览数据...</p>
</div>
<div id="previewContent" style="display: none;">
<p class="text-muted">任务完成后将显示结果预览...</p>
</div>
<div id="previewError" class="alert alert-danger" style="display: none;">
<i class="fas fa-exclamation-triangle me-2"></i>
<span id="errorMessage">加载预览时发生错误</span>
</div>
</div>
<div class="mt-2 text-end">
<span class="text-muted small" id="previewUpdatedTime"></span>
</div>
</div>
</div>
<div class="modal-footer d-flex justify-content-between">
<div>
<button type="button" class="btn btn-danger" id="cancelTaskBtn">
<i class="fas fa-stop-circle me-1"></i>取消任务
</button>
</div>
<div>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
<button type="button" class="btn btn-primary" id="viewResultBtn">
<i class="fas fa-external-link-alt me-1"></i>查看完整结果
</button>
</div>
</div>
</div>
</div>
</div>
<!-- 添加通知容器 -->
<div id="notificationContainer" style="position: fixed; top: 20px; right: 20px; z-index: 1050; max-width: 350px;"></div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jsoneditor@9.5.0/dist/jsoneditor.min.js"></script>
<script src="\static\js\workflow_editor.js"></script>
</body>
</html>