624 lines
16 KiB
HTML
624 lines
16 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title>项目管理</title>
|
|
<style>
|
|
body {
|
|
font-family: "Microsoft YaHei", sans-serif;
|
|
margin: 0;
|
|
padding: 20px;
|
|
background-color: #f5f5f5;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
background-color: #fff;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
h1 {
|
|
color: #333;
|
|
margin-top: 0;
|
|
padding-bottom: 15px;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
.control-panel {
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.search-box {
|
|
flex-grow: 1;
|
|
margin-right: 20px;
|
|
}
|
|
|
|
input[type="text"] {
|
|
width: 100%;
|
|
padding: 8px 12px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
body button {
|
|
padding: 8px 16px;
|
|
margin-right: 8px;
|
|
background-color: #1890ff;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
transition: background-color 0.3s;
|
|
font-size: 12px;
|
|
}
|
|
|
|
body button:hover {
|
|
background-color: #40a9ff;
|
|
}
|
|
|
|
body button.danger {
|
|
background-color: #ff4d4f;
|
|
}
|
|
|
|
body button.danger:hover {
|
|
background-color: #ff7875;
|
|
}
|
|
|
|
body table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
body th,
|
|
body td {
|
|
padding: 12px 15px;
|
|
text-align: left;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
body th {
|
|
background-color: #fafafa;
|
|
font-weight: 600;
|
|
font-size: 12px;
|
|
}
|
|
|
|
body tr:hover {
|
|
background-color: #f5f5f5;
|
|
}
|
|
|
|
body .action-buttons {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
body .action-buttons button {
|
|
padding: 4px 10px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
body .modal {
|
|
display: none;
|
|
position: fixed;
|
|
z-index: 1000;
|
|
left: 0;
|
|
top: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
body .modal-content {
|
|
background-color: #fff;
|
|
margin: 10% auto;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
width: 50%;
|
|
max-width: 500px;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
body .modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
body .modal-header h2 {
|
|
margin: 0;
|
|
}
|
|
|
|
body .close {
|
|
font-size: 24px;
|
|
font-weight: bold;
|
|
cursor: pointer;
|
|
}
|
|
|
|
body .form-group {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
body .form-group label {
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
body .form-group input,
|
|
.form-group textarea {
|
|
width: 100%;
|
|
padding: 8px 12px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
font-size: 14px;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body .form-group textarea {
|
|
min-height: 100px;
|
|
resize: vertical;
|
|
}
|
|
|
|
body .modal-footer {
|
|
margin-top: 20px;
|
|
text-align: right;
|
|
}
|
|
|
|
body .empty-state {
|
|
text-align: center;
|
|
padding: 40px 0;
|
|
color: #999;
|
|
}
|
|
|
|
body .empty-state p {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
body .description-cell {
|
|
max-width: 300px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
position: relative;
|
|
}
|
|
|
|
body .description-cell:hover {
|
|
cursor: pointer;
|
|
color: #1890ff;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="container">
|
|
<h1>项目管理</h1>
|
|
|
|
<div class="control-panel">
|
|
<div class="search-box">
|
|
<input type="text" id="search-input" placeholder="搜索项目..." />
|
|
</div>
|
|
<button id="add-project-btn">添加项目</button>
|
|
<!-- <button id="refresh-btn">刷新</button> -->
|
|
</div>
|
|
|
|
<div id="projects-table-container">
|
|
<table id="projects-table">
|
|
<thead>
|
|
<tr>
|
|
<th>项目名称</th>
|
|
<th>描述</th>
|
|
<th>创建时间</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="projects-list">
|
|
<!-- 项目列表将通过JavaScript动态生成 -->
|
|
</tbody>
|
|
</table>
|
|
|
|
<div id="empty-state" class="empty-state" style="display: none">
|
|
<p>暂无项目数据</p>
|
|
<button id="create-first-project-btn">创建第一个项目</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 添加/编辑项目的模态框 -->
|
|
<div id="project-modal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2 id="modal-title">添加项目</h2>
|
|
<span class="close">×</span>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="project-form">
|
|
<input type="hidden" id="project-id" />
|
|
<div class="form-group">
|
|
<label for="project-name">项目名称</label>
|
|
<input type="text" id="project-name" required />
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="project-description">项目描述</label>
|
|
<textarea id="project-description"></textarea>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button id="cancel-btn">取消</button>
|
|
<button id="save-project-btn">保存</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 确认删除的模态框 -->
|
|
<div id="confirm-modal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2>确认删除</h2>
|
|
<span class="close">×</span>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>确定要删除这个项目吗?此操作不可撤销。</p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button id="cancel-delete-btn">取消</button>
|
|
<button id="confirm-delete-btn" class="danger">删除</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// 全局变量
|
|
let projects = [];
|
|
let currentProjectId = null;
|
|
|
|
// DOM元素
|
|
const projectsListEl = document.getElementById("projects-list");
|
|
const emptyStateEl = document.getElementById("empty-state");
|
|
const searchInputEl = document.getElementById("search-input");
|
|
const projectModal = document.getElementById("project-modal");
|
|
const confirmModal = document.getElementById("confirm-modal");
|
|
const modalTitle = document.getElementById("modal-title");
|
|
const projectForm = document.getElementById("project-form");
|
|
const projectIdInput = document.getElementById("project-id");
|
|
const projectNameInput = document.getElementById("project-name");
|
|
const projectDescInput = document.getElementById("project-description");
|
|
|
|
// 按钮
|
|
const addProjectBtn = document.getElementById("add-project-btn");
|
|
const refreshBtn = document.getElementById("refresh-btn");
|
|
const createFirstProjectBtn = document.getElementById(
|
|
"create-first-project-btn"
|
|
);
|
|
const saveProjectBtn = document.getElementById("save-project-btn");
|
|
const cancelBtn = document.getElementById("cancel-btn");
|
|
const confirmDeleteBtn = document.getElementById("confirm-delete-btn");
|
|
const cancelDeleteBtn = document.getElementById("cancel-delete-btn");
|
|
const closeButtons = document.querySelectorAll(".close");
|
|
|
|
init();
|
|
// 初始化
|
|
function init() {
|
|
fetchProjects();
|
|
setupEventListeners();
|
|
}
|
|
|
|
// 设置事件监听器
|
|
function setupEventListeners() {
|
|
// 添加项目按钮
|
|
addProjectBtn.addEventListener("click", () => openProjectModal());
|
|
|
|
// 刷新按钮
|
|
// refreshBtn.addEventListener("click", fetchProjects);
|
|
|
|
// 创建第一个项目按钮
|
|
createFirstProjectBtn.addEventListener("click", () =>
|
|
openProjectModal()
|
|
);
|
|
|
|
// 保存项目按钮
|
|
saveProjectBtn.addEventListener("click", saveProject);
|
|
|
|
// 取消按钮
|
|
cancelBtn.addEventListener("click", closeProjectModal);
|
|
|
|
// 确认删除按钮
|
|
confirmDeleteBtn.addEventListener("click", deleteProject);
|
|
|
|
// 取消删除按钮
|
|
cancelDeleteBtn.addEventListener("click", closeConfirmModal);
|
|
|
|
// 关闭按钮
|
|
closeButtons.forEach((button) => {
|
|
button.addEventListener("click", function () {
|
|
projectModal.style.display = "none";
|
|
confirmModal.style.display = "none";
|
|
});
|
|
});
|
|
|
|
// 搜索输入框
|
|
searchInputEl.addEventListener("input", filterProjects);
|
|
|
|
// 点击模态框外部关闭
|
|
window.addEventListener("click", function (event) {
|
|
if (event.target === projectModal) {
|
|
closeProjectModal();
|
|
}
|
|
if (event.target === confirmModal) {
|
|
closeConfirmModal();
|
|
}
|
|
});
|
|
}
|
|
|
|
// 获取项目列表
|
|
async function fetchProjects() {
|
|
try {
|
|
const response = await fetch("http://127.0.0.1:5002/api/project/");
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error ${response.status}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.code === 0 && Array.isArray(result.data)) {
|
|
projects = result.data.sort((a, b) => {
|
|
return new Date(b.created_at) - new Date(a.created_at);
|
|
});
|
|
renderProjects(projects);
|
|
} else {
|
|
console.error("获取项目失败:", result.message || "未知错误");
|
|
showEmptyState(true, "加载失败,请重试");
|
|
}
|
|
} catch (error) {
|
|
console.error("获取项目错误:", error);
|
|
showEmptyState(true, "加载失败,请重试");
|
|
}
|
|
}
|
|
|
|
// 渲染项目列表
|
|
function renderProjects(projectsToRender) {
|
|
if (!projectsToRender || projectsToRender.length === 0) {
|
|
showEmptyState(true);
|
|
return;
|
|
}
|
|
|
|
showEmptyState(false);
|
|
|
|
projectsListEl.innerHTML = "";
|
|
|
|
projectsToRender.forEach((project) => {
|
|
const row = document.createElement("tr");
|
|
|
|
// 格式化日期
|
|
const createdAt = new Date(project.created_at);
|
|
const formattedDate = createdAt.toLocaleString("zh-CN", {
|
|
year: "numeric",
|
|
month: "2-digit",
|
|
day: "2-digit",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
});
|
|
const shortDesc = project.description
|
|
? project.description.length > 50
|
|
? project.description.substring(0, 50) + "..."
|
|
: project.description
|
|
: "";
|
|
|
|
row.innerHTML = `
|
|
<td>${project.name}</td>
|
|
<td class="description-cell" title="${project.description || ""
|
|
}">${shortDesc}</td>
|
|
<td>${formattedDate}</td>
|
|
<td class="action-buttons">
|
|
<button class="view-docs-btn" data-id="${project.id
|
|
}">查看文档</button>
|
|
<button class="view-demands-btn secondary" data-id="${project.id
|
|
}">查看需求树</button>
|
|
<button class="view-stages-btn secondary" data-id="${project.id
|
|
}">查看场景</button>
|
|
<button class="edit-btn" data-id="${project.id}">编辑</button>
|
|
<button class="delete-btn danger" data-id="${project.id
|
|
}">删除</button>
|
|
</td>
|
|
`;
|
|
|
|
projectsListEl.appendChild(row);
|
|
});
|
|
document.querySelectorAll(".view-demands-btn").forEach((btn) => {
|
|
btn.addEventListener("click", function () {
|
|
const docId = this.getAttribute("data-id");
|
|
window.location.href = `project/demand?project_id=${docId}`;
|
|
});
|
|
});
|
|
document.querySelectorAll(".view-stages-btn").forEach((btn) => {
|
|
btn.addEventListener("click", function () {
|
|
const projectId = this.getAttribute("data-id");
|
|
window.location.href = `project/stage?project_id=${projectId}`;
|
|
});
|
|
});
|
|
// 添加按钮事件
|
|
document.querySelectorAll(".view-docs-btn").forEach((btn) => {
|
|
btn.addEventListener("click", function () {
|
|
const projectId = this.getAttribute("data-id");
|
|
window.location.href = `doc_list?project_id=${projectId}`;
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll(".edit-btn").forEach((btn) => {
|
|
btn.addEventListener("click", function () {
|
|
const projectId = this.getAttribute("data-id");
|
|
const project = projects.find((p) => p.id === projectId);
|
|
openProjectModal(project);
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll(".delete-btn").forEach((btn) => {
|
|
btn.addEventListener("click", function () {
|
|
const projectId = this.getAttribute("data-id");
|
|
openConfirmModal(projectId);
|
|
});
|
|
});
|
|
}
|
|
|
|
// 显示/隐藏空状态
|
|
function showEmptyState(show, message = "暂无项目数据") {
|
|
if (show) {
|
|
document.getElementById("projects-table").style.display = "none";
|
|
emptyStateEl.style.display = "block";
|
|
emptyStateEl.querySelector("p").textContent = message;
|
|
} else {
|
|
document.getElementById("projects-table").style.display = "table";
|
|
emptyStateEl.style.display = "none";
|
|
}
|
|
}
|
|
|
|
// 过滤项目
|
|
function filterProjects() {
|
|
const searchTerm = searchInputEl.value.toLowerCase();
|
|
|
|
if (!searchTerm) {
|
|
renderProjects(projects);
|
|
return;
|
|
}
|
|
|
|
const filteredProjects = projects.filter(
|
|
(project) =>
|
|
project.name.toLowerCase().includes(searchTerm) ||
|
|
(project.description &&
|
|
project.description.toLowerCase().includes(searchTerm))
|
|
);
|
|
|
|
renderProjects(filteredProjects);
|
|
}
|
|
|
|
// 打开项目模态框
|
|
function openProjectModal(project = null) {
|
|
console.log("will open project modal");
|
|
modalTitle.textContent = project ? "编辑项目" : "添加项目";
|
|
console.log(project);
|
|
if (project) {
|
|
projectIdInput.value = project.id;
|
|
projectNameInput.value = project.name || "";
|
|
projectDescInput.value = project.description || "";
|
|
} else {
|
|
projectForm.reset();
|
|
projectIdInput.value = "";
|
|
}
|
|
|
|
projectModal.style.display = "block";
|
|
}
|
|
|
|
// 关闭项目模态框
|
|
function closeProjectModal() {
|
|
projectModal.style.display = "none";
|
|
projectForm.reset();
|
|
}
|
|
|
|
// 打开确认删除模态框
|
|
function openConfirmModal(projectId) {
|
|
currentProjectId = projectId;
|
|
confirmModal.style.display = "block";
|
|
}
|
|
|
|
// 关闭确认删除模态框
|
|
function closeConfirmModal() {
|
|
confirmModal.style.display = "none";
|
|
currentProjectId = null;
|
|
}
|
|
|
|
// 保存项目
|
|
async function saveProject() {
|
|
const projectId = projectIdInput.value;
|
|
const name = projectNameInput.value.trim();
|
|
const description = projectDescInput.value.trim();
|
|
|
|
if (!name) {
|
|
alert("请输入项目名称");
|
|
return;
|
|
}
|
|
|
|
const projectData = {
|
|
name,
|
|
description,
|
|
};
|
|
|
|
try {
|
|
let url = "http://127.0.0.1:5002/api/project/";
|
|
let method = "POST";
|
|
|
|
if (projectId) {
|
|
url += projectId;
|
|
method = "PATCH";
|
|
}
|
|
|
|
const response = await fetch(url, {
|
|
method,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(projectData),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error ${response.status}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.code === 0) {
|
|
alert(projectId ? "项目更新成功" : "项目创建成功");
|
|
closeProjectModal();
|
|
fetchProjects();
|
|
} else {
|
|
alert(`操作失败: ${result.message || "未知错误"}`);
|
|
}
|
|
} catch (error) {
|
|
console.error("保存项目错误:", error);
|
|
alert(`操作失败: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// 删除项目
|
|
async function deleteProject() {
|
|
if (!currentProjectId) return;
|
|
|
|
try {
|
|
const response = await fetch(
|
|
`http://127.0.0.1:5002/api/project/${currentProjectId}`,
|
|
{
|
|
method: "DELETE",
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error ${response.status}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.code === 0) {
|
|
alert("项目删除成功");
|
|
closeConfirmModal();
|
|
fetchProjects();
|
|
} else {
|
|
alert(`删除失败: ${result.message || "未知错误"}`);
|
|
}
|
|
} catch (error) {
|
|
console.error("删除项目错误:", error);
|
|
alert(`删除失败: ${error.message}`);
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
|
|
</html> |