Files
testFlow/ui/page/stage.html
T
2025-04-23 11:21:08 +08:00

637 lines
18 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Stage管理</title>
<link rel="stylesheet" href="/ui/assets/common.css" />
<style>
body {
font-family: "Microsoft YaHei", sans-serif;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.title {
font-size: 24px;
font-weight: bold;
}
.btn {
padding: 8px 16px;
background-color: #1890ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #40a9ff;
}
.btn-danger {
background-color: #ff4d4f;
}
.btn-danger:hover {
background-color: #ff7875;
}
.table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.table th,
.table td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #e8e8e8;
}
.table th {
background-color: #fafafa;
font-weight: 500;
}
.table tr:hover {
background-color: #f5f5f5;
}
.action-cell {
display: flex;
gap: 8px;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal-content {
background-color: white;
padding: 24px;
border-radius: 4px;
width: 400px;
max-width: 90%;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.modal-title {
font-size: 18px;
font-weight: bold;
}
.close {
font-size: 24px;
cursor: pointer;
}
.form-group {
margin-bottom: 16px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-weight: 500;
}
.form-input {
width: 100%;
padding: 8px 12px;
border: 1px solid #d9d9d9;
border-radius: 4px;
box-sizing: border-box;
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 24px;
}
.loading {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 2000;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #1890ff;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.empty-state {
text-align: center;
padding: 40px;
color: #999;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1 class="title">Stage管理</h1>
<button class="btn" id="add-stage-btn">添加Stage</button>
</div>
<div id="stage-list">
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>关联树节点</th>
<!-- <th>描述</th> -->
<th>操作</th>
</tr>
</thead>
<tbody id="stage-table-body">
<!-- 数据将通过JavaScript动态加载 -->
</tbody>
</table>
<div id="empty-state" class="empty-state" style="display: none">
<p>暂无Stage数据,请添加新Stage</p>
</div>
</div>
</div>
<!-- 添加/编辑Stage的模态框 -->
<div class="modal" id="stage-modal">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="modal-title">添加Stage</h2>
<span class="close" id="close-modal">&times;</span>
</div>
<form id="stage-form">
<input type="hidden" id="stage-id" value="" />
<div class="form-group">
<label class="form-label" for="stage-name">名称</label>
<input
type="text"
class="form-input"
id="stage-name"
value=""
required
/>
</div>
<div class="form-group">
<label class="form-label" for="stage-tree-id">关联树节点</label>
<select class="form-input" id="stage-tree-id" required>
<option value="">请选择树节点</option>
<!-- 树节点选项将通过JavaScript动态加载 -->
</select>
</div>
<div class="form-actions">
<button type="button" class="btn" id="cancel-btn">取消</button>
<button type="submit" class="btn" id="save-btn">保存</button>
</div>
</form>
</div>
</div>
<!-- 确认删除的模态框 -->
<div class="modal" id="confirm-modal">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">确认删除</h2>
<span class="close" id="close-confirm-modal">&times;</span>
</div>
<p>
确定要删除此Stage吗?此操作将同时删除该Stage下的所有图形节点和连接,且不可恢复。
</p>
<div class="form-actions">
<button type="button" class="btn" id="cancel-delete-btn">取消</button>
<button type="button" class="btn btn-danger" id="confirm-delete-btn">
删除
</button>
</div>
</div>
</div>
<!-- 加载指示器 -->
<div class="loading" id="loading" style="display: none">
<div class="spinner"></div>
</div>
<script>
// 基础URL
const API_BASE_URL = "http://127.0.0.1:5002/api";
// DOM元素
const stageTableBody = document.getElementById("stage-table-body");
const emptyState = document.getElementById("empty-state");
const addStageBtn = document.getElementById("add-stage-btn");
const stageModal = document.getElementById("stage-modal");
const modalTitle = document.getElementById("modal-title");
const closeModalBtn = document.getElementById("close-modal");
const stageForm = document.getElementById("stage-form");
const stageIdInput = document.getElementById("stage-id");
const stageNameInput = document.getElementById("stage-name");
const stageTreeIdSelect = document.getElementById("stage-tree-id");
const cancelBtn = document.getElementById("cancel-btn");
const confirmModal = document.getElementById("confirm-modal");
const closeConfirmModal = document.getElementById("close-confirm-modal");
const cancelDeleteBtn = document.getElementById("cancel-delete-btn");
const confirmDeleteBtn = document.getElementById("confirm-delete-btn");
const loading = document.getElementById("loading");
const urlParams = new URLSearchParams(window.location.search);
// 当前要删除的StageID
let currentDeleteId = null;
// 存储树节点数据
let treeNodes = [];
let projectId = urlParams.get("project_id");
// 页面加载时获取Stage列表和树节点列表
function init() {
fetchTreeNodes();
fetchStages();
}
init();
// 添加Stage按钮点击事件
addStageBtn.addEventListener("click", () => {
modalTitle.textContent = "添加Stage";
stageForm.reset();
stageIdInput.value = "";
openModal(stageModal);
});
// 关闭模态框
closeModalBtn.addEventListener("click", () => closeModal(stageModal));
cancelBtn.addEventListener("click", () => closeModal(stageModal));
// 关闭确认删除模态框
closeConfirmModal.addEventListener("click", () =>
closeModal(confirmModal)
);
cancelDeleteBtn.addEventListener("click", () => closeModal(confirmModal));
// 确认删除
confirmDeleteBtn.addEventListener("click", deleteStage);
// 表单提交
stageForm.addEventListener("submit", handleFormSubmit);
// 获取所有Stage
async function fetchStages() {
showLoading();
try {
const response = await fetch(`${API_BASE_URL}/stage/${projectId}/`);
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
const result = await response.json();
if (result.code === 0 && Array.isArray(result.data)) {
renderStageList(result.data);
} else {
showError("获取Stage列表失败: " + (result.message || "未知错误"));
}
} catch (error) {
console.error("获取Stage列表错误:", error);
showError("获取Stage列表错误: " + error.message);
} finally {
hideLoading();
}
}
async function fetchTreeNodes() {
showLoading();
try {
const response = await fetch(
`${API_BASE_URL}/project/demand/${projectId}`
);
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
console.log("fetch nodes", response);
const result = await response.json();
if (result.code === 0 && Array.isArray(result.data)) {
treeNodes = result.data;
populateTreeSelect();
} else {
showError("获取树节点列表失败: " + (result.message || "未知错误"));
}
} catch (error) {
console.error("获取树节点列表错误:", error);
showError("获取树节点列表错误: " + error.message);
} finally {
hideLoading();
}
}
// 加载Stage数据用于编辑
// 填充树节点选择框
function populateTreeSelect() {
stageTreeIdSelect.innerHTML = '<option value="">请选择树节点</option>';
// 添加虚拟根节点选项
/* const rootOption = document.createElement("option");
rootOption.value = "root"; // 使用特殊值"root"表示虚拟根节点
rootOption.textContent = "所有节点 (虚拟根节点)";
stageTreeIdSelect.appendChild(rootOption);
*/
treeNodes.forEach((node) => {
const option = document.createElement("option");
option.value = node.id;
option.textContent = `${node.name} (Level: ${node.level})`;
stageTreeIdSelect.appendChild(option);
});
}
function renderStageList(stages) {
stageTableBody.innerHTML = "";
if (stages.length === 0) {
emptyState.style.display = "block";
return;
}
emptyState.style.display = "none";
stages.forEach((stage) => {
const row = document.createElement("tr");
// 截断ID以便显示
const shortId = stage.id.substring(0, 8) + "...";
// 查找关联的树节点名称
let treeNodeName = "未关联";
if (stage.tree_id) {
if (stage.tree_id === "root") {
treeNodeName = "所有节点 (虚拟根节点)";
} else {
console.log("tree_id: ", stage.tree_id);
console.log(treeNodes);
const treeNode = treeNodes.find(
(node) => node.id === stage.tree_id
);
if (treeNode) {
treeNodeName = `${treeNode.name} (Level: ${treeNode.level})`;
} else {
treeNodeName = `ID: ${stage.tree_id.substring(0, 8)}...`;
}
}
}
row.innerHTML = `
<td title="${stage.id}">${shortId}</td>
<td>${stage.name || "未命名"}</td>
<td>${treeNodeName}</td>
<td class="action-cell">
<button class="btn" data-id="${
stage.id
}" data-action="view-graph">查看Graph</button>
<button class="btn" data-id="${
stage.id
}" data-action="edit">编辑</button>
<button class="btn btn-danger" data-id="${
stage.id
}" data-action="delete">删除</button>
</td>
`;
stageTableBody.appendChild(row);
});
// 添加按钮事件监听
document.querySelectorAll("[data-action]").forEach((button) => {
button.addEventListener("click", handleActionClick);
});
}
// 处理操作按钮点击
async function handleActionClick(event) {
console.log("action click");
const action = event.target.getAttribute("data-action");
const stageId = event.target.getAttribute("data-id");
switch (action) {
case "view-graph":
// 跳转到图形页面
window.location.href = `/ui/page/stageGraph.html?id=${stageId}`;
break;
case "edit":
await loadStageForEdit(stageId);
break;
case "delete":
openDeleteConfirmation(stageId);
break;
}
}
// 加载Stage数据用于编辑
async function loadStageForEdit(stageId) {
showLoading();
try {
const response = await fetch(
`${API_BASE_URL}/stage/${project_id}/${stageId}/`
);
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
console.log("get");
const result = await response.json();
if (result.code === 0 && result.data) {
const stage = result.data.stage;
// 填充表单
console.log(stageIdInput);
stageIdInput.value = stage.id;
stageNameInput.value = stage.name || "";
stageTreeIdSelect.value = stage.tree_id || "";
//stageLevelInput.value = stage.level || 0;
//stageDescriptionInput.innerHTML = stage.description || "";
// 更新模态框标题
modalTitle.textContent = "编辑Stage";
// 打开模态框
openModal(stageModal);
} else {
showError("获取Stage详情失败: " + (result.message || "未知错误"));
}
} catch (error) {
console.error("获取Stage详情错误:", error);
showError("获取Stage详情错误: " + error.message);
} finally {
hideLoading();
}
}
async function handleFormSubmit(event) {
event.preventDefault();
const stageId = stageIdInput.value;
const isEdit = !!stageId;
const stageData = {
name: stageNameInput.value,
project_id: projectId,
tree_id: stageTreeIdSelect.value,
};
showLoading();
try {
const url = isEdit
? `${API_BASE_URL}/stage/${projectId}/${stageId}/`
: `${API_BASE_URL}/stage/${projectId}/`;
const method = isEdit ? "PATCH" : "POST";
const response = await fetch(url, {
method: method,
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(stageData),
});
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
const result = await response.json();
if (result.code === 0) {
showMessage(isEdit ? "Stage更新成功" : "Stage创建成功");
closeModal(stageModal);
fetchStages(); // 刷新列表
} else {
showError(
(isEdit ? "更新" : "创建") +
"Stage失败: " +
(result.message || "未知错误")
);
}
} catch (error) {
console.error(isEdit ? "更新Stage错误:" : "创建Stage错误:", error);
showError((isEdit ? "更新" : "创建") + "Stage错误: " + error.message);
} finally {
hideLoading();
}
}
// 打开删除确认对话框
function openDeleteConfirmation(stageId) {
currentDeleteId = stageId;
openModal(confirmModal);
}
// 删除Stage
async function deleteStage() {
if (!currentDeleteId) return;
showLoading();
try {
const response = await fetch(
`${API_BASE_URL}/stage/${project_id}/${currentDeleteId}/`,
{
method: "DELETE",
}
);
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
const result = await response.json();
if (result.code === 0) {
showMessage("Stage删除成功");
closeModal(confirmModal);
fetchStages(); // 刷新列表
} else {
showError("删除Stage失败: " + (result.message || "未知错误"));
}
} catch (error) {
console.error("删除Stage错误:", error);
showError("删除Stage错误: " + error.message);
} finally {
hideLoading();
currentDeleteId = null;
}
}
// 工具函数:打开模态框
function openModal(modal) {
modal.style.display = "flex";
}
// 工具函数:关闭模态框
function closeModal(modal) {
modal.style.display = "none";
}
// 工具函数:显示加载中
function showLoading() {
loading.style.display = "flex";
}
// 工具函数:隐藏加载中
function hideLoading() {
loading.style.display = "none";
}
// 工具函数:显示消息
function showMessage(message) {
alert(message); // 简单实现,可以替换为更友好的通知
}
// 工具函数:显示错误
function showError(message) {
alert("错误: " + message); // 简单实现,可以替换为更友好的通知
}
</script>
</body>
</html>