testFlow/ui/page/stage.html
Wyle.Gong-巩文昕 7a1aae1e2f ui
2025-04-23 11:21:08 +08:00

637 lines
18 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>