755 lines
20 KiB
HTML
755 lines
20 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;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.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);
|
||
}
|
||
|
||
.header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding-bottom: 15px;
|
||
border-bottom: 1px solid #eee;
|
||
font-size: 15px;
|
||
}
|
||
|
||
.header h1 {
|
||
margin: 0;
|
||
color: #333;
|
||
}
|
||
|
||
.breadcrumb {
|
||
margin-bottom: 20px;
|
||
color: #666;
|
||
}
|
||
|
||
.breadcrumb a {
|
||
color: #1890ff;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.breadcrumb a:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
button {
|
||
padding: 8px 16px;
|
||
margin-right: 8px;
|
||
background-color: #1890ff;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
transition: background-color 0.3s;
|
||
}
|
||
|
||
button:hover {
|
||
background-color: #40a9ff;
|
||
}
|
||
|
||
button.danger {
|
||
background-color: #ff4d4f;
|
||
}
|
||
|
||
button.danger:hover {
|
||
background-color: #ff7875;
|
||
}
|
||
|
||
button.secondary {
|
||
background-color: #52c41a;
|
||
}
|
||
|
||
button.secondary:hover {
|
||
background-color: #73d13d;
|
||
}
|
||
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
th,
|
||
td {
|
||
padding: 12px 15px;
|
||
text-align: left;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
|
||
th {
|
||
background-color: #fafafa;
|
||
font-weight: 600;
|
||
}
|
||
|
||
tr:hover {
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.action-buttons {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.action-buttons button {
|
||
padding: 4px 10px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.modal {
|
||
display: none;
|
||
position: fixed;
|
||
z-index: 1000;
|
||
left: 0;
|
||
top: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
}
|
||
|
||
.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);
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.modal-header h2 {
|
||
margin: 0;
|
||
}
|
||
|
||
.close {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
margin-bottom: 5px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.form-group input,
|
||
.form-group textarea,
|
||
.form-group select {
|
||
width: 100%;
|
||
padding: 8px 12px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.form-group textarea {
|
||
min-height: 100px;
|
||
resize: vertical;
|
||
}
|
||
|
||
.modal-footer {
|
||
margin-top: 20px;
|
||
text-align: right;
|
||
}
|
||
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 40px 0;
|
||
color: #999;
|
||
}
|
||
|
||
.empty-state p {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.file-upload {
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.file-upload input[type="file"] {
|
||
display: none;
|
||
}
|
||
|
||
.file-upload label {
|
||
display: inline-block;
|
||
padding: 8px 16px;
|
||
background-color: #f0f0f0;
|
||
color: #333;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
transition: background-color 0.3s;
|
||
}
|
||
|
||
.file-upload label:hover {
|
||
background-color: #e0e0e0;
|
||
}
|
||
|
||
.file-name {
|
||
margin-left: 10px;
|
||
font-size: 14px;
|
||
color: #666;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="breadcrumb">
|
||
<a href="project_list.html">项目列表</a> >
|
||
<span id="project-name">文档管理</span>
|
||
</div>
|
||
|
||
<div class="header">
|
||
<h1>文档管理</h1>
|
||
<div>
|
||
<button id="back-btn" style="display: none">返回项目列表</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-panel">
|
||
<div class="search-box">
|
||
<input type="text" id="search-input" placeholder="搜索文档..." />
|
||
</div>
|
||
<button id="add-doc-btn">添加文档</button>
|
||
<button id="refresh-btn" style="display: none">刷新</button>
|
||
</div>
|
||
|
||
<div id="docs-table-container">
|
||
<table id="docs-table">
|
||
<thead>
|
||
<tr>
|
||
<th>文档名称</th>
|
||
<th>类型</th>
|
||
<th>创建时间</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="docs-list">
|
||
<!-- 文档列表将通过JavaScript动态生成 -->
|
||
</tbody>
|
||
</table>
|
||
|
||
<div id="empty-state" class="empty-state" style="display: none">
|
||
<p>暂无文档数据</p>
|
||
<button id="create-first-doc-btn">创建第一个文档</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 添加/编辑文档的模态框 -->
|
||
<div id="doc-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="doc-form">
|
||
<input type="hidden" id="doc-id" />
|
||
<div class="form-group">
|
||
<label for="doc-name">文档名称</label>
|
||
<input type="text" id="doc-name" required />
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="doc-type">文档类型</label>
|
||
<select id="doc-type">
|
||
<option value="requirement">需求文档</option>
|
||
<option value="api">API文档</option>
|
||
|
||
<option value="design">设计文档</option>
|
||
<option value="other">其他</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="doc-description">文档描述</label>
|
||
<textarea id="doc-description"></textarea>
|
||
</div>
|
||
<div class="form-group file-upload">
|
||
<label for="doc-file">上传文件</label>
|
||
<input type="file" id="doc-file" />
|
||
<span class="file-name" id="file-name">未选择文件</span>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button id="cancel-btn">取消</button>
|
||
<button id="save-doc-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 docs = [];
|
||
let currentDocId = null;
|
||
let projectId = null;
|
||
let projectName = "";
|
||
|
||
// DOM元素
|
||
const docsListEl = document.getElementById("docs-list");
|
||
const emptyStateEl = document.getElementById("empty-state");
|
||
const searchInputEl = document.getElementById("search-input");
|
||
const docModal = document.getElementById("doc-modal");
|
||
const confirmModal = document.getElementById("confirm-modal");
|
||
const modalTitle = document.getElementById("modal-title");
|
||
const docForm = document.getElementById("doc-form");
|
||
const docIdInput = document.getElementById("doc-id");
|
||
const docNameInput = document.getElementById("doc-name");
|
||
const docTypeInput = document.getElementById("doc-type");
|
||
const docDescInput = document.getElementById("doc-description");
|
||
const docFileInput = document.getElementById("doc-file");
|
||
const fileNameEl = document.getElementById("file-name");
|
||
const projectNameEl = document.getElementById("project-name");
|
||
|
||
// 按钮
|
||
const backBtn = document.getElementById("back-btn");
|
||
const addDocBtn = document.getElementById("add-doc-btn");
|
||
const refreshBtn = document.getElementById("refresh-btn");
|
||
const createFirstDocBtn = document.getElementById("create-first-doc-btn");
|
||
const saveDocBtn = document.getElementById("save-doc-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() {
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
projectId = urlParams.get("project_id");
|
||
|
||
if (!projectId) {
|
||
alert("未指定项目ID,将返回项目列表");
|
||
window.location.href = "project_list.html";
|
||
return;
|
||
}
|
||
|
||
// 获取项目信息
|
||
fetchProjectInfo();
|
||
|
||
// 获取文档列表
|
||
fetchDocs();
|
||
|
||
// 设置事件监听器
|
||
setupEventListeners();
|
||
}
|
||
|
||
// 设置事件监听器
|
||
function setupEventListeners() {
|
||
// 返回按钮
|
||
backBtn.addEventListener("click", () => {
|
||
window.location.href = "project_list.html";
|
||
});
|
||
|
||
// 添加文档按钮
|
||
addDocBtn.addEventListener("click", () => openDocModal());
|
||
|
||
// 刷新按钮
|
||
refreshBtn.addEventListener("click", fetchDocs);
|
||
|
||
// 创建第一个文档按钮
|
||
createFirstDocBtn.addEventListener("click", () => openDocModal());
|
||
|
||
// 保存文档按钮
|
||
saveDocBtn.addEventListener("click", saveDoc);
|
||
|
||
// 取消按钮
|
||
cancelBtn.addEventListener("click", closeDocModal);
|
||
|
||
// 确认删除按钮
|
||
confirmDeleteBtn.addEventListener("click", deleteDoc);
|
||
|
||
// 取消删除按钮
|
||
cancelDeleteBtn.addEventListener("click", closeConfirmModal);
|
||
|
||
// 关闭按钮
|
||
closeButtons.forEach((button) => {
|
||
button.addEventListener("click", function () {
|
||
docModal.style.display = "none";
|
||
confirmModal.style.display = "none";
|
||
});
|
||
});
|
||
|
||
// 搜索输入框
|
||
searchInputEl.addEventListener("input", filterDocs);
|
||
|
||
// 文件选择监听
|
||
docFileInput.addEventListener("change", function () {
|
||
if (this.files.length > 0) {
|
||
fileNameEl.textContent = this.files[0].name;
|
||
if (docNameInput.value === "") {
|
||
console.log("changeName");
|
||
docNameInput.value = this.files[0].name;
|
||
}
|
||
} else {
|
||
fileNameEl.textContent = "未选择文件";
|
||
}
|
||
});
|
||
|
||
// 点击模态框外部关闭
|
||
window.addEventListener("click", function (event) {
|
||
if (event.target === docModal) {
|
||
closeDocModal();
|
||
}
|
||
if (event.target === confirmModal) {
|
||
closeConfirmModal();
|
||
}
|
||
});
|
||
}
|
||
|
||
// 获取项目信息
|
||
async function fetchProjectInfo() {
|
||
try {
|
||
const response = await fetch(
|
||
`http://127.0.0.1:5002/api/project/${projectId}`
|
||
);
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error ${response.status}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.code === 0 && result.data) {
|
||
projectName = result.data.name || "未命名项目";
|
||
projectNameEl.textContent = projectName;
|
||
document.title = `${projectName} - 文档管理`;
|
||
} else {
|
||
console.error("获取项目信息失败:", result.message || "未知错误");
|
||
}
|
||
} catch (error) {
|
||
console.error("获取项目信息错误:", error);
|
||
}
|
||
}
|
||
|
||
// 获取文档列表
|
||
async function fetchDocs() {
|
||
try {
|
||
const response = await fetch(
|
||
`http://127.0.0.1:5002/api/doc/${projectId}/`
|
||
);
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error ${response.status}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.code === 0 && Array.isArray(result.data)) {
|
||
docs = result.data.sort((a, b) => {
|
||
return new Date(b.created_at) - new Date(a.created_at);
|
||
});
|
||
//console.log("docs", docs)
|
||
renderDocs(docs);
|
||
} else {
|
||
console.error("获取文档失败:", result.message || "未知错误");
|
||
showEmptyState(true, "加载失败,请重试");
|
||
}
|
||
} catch (error) {
|
||
console.error("获取文档错误:", error);
|
||
showEmptyState(true, "加载失败,请重试");
|
||
}
|
||
}
|
||
|
||
// 渲染文档列表
|
||
function renderDocs(docsToRender) {
|
||
if (!docsToRender || docsToRender.length === 0) {
|
||
showEmptyState(true);
|
||
return;
|
||
}
|
||
|
||
showEmptyState(false);
|
||
|
||
docsListEl.innerHTML = "";
|
||
|
||
docsToRender.forEach((doc) => {
|
||
const row = document.createElement("tr");
|
||
|
||
// 格式化日期
|
||
const createdAt = new Date(doc.created_at);
|
||
const formattedDate = createdAt.toLocaleString("zh-CN", {
|
||
year: "numeric",
|
||
month: "2-digit",
|
||
day: "2-digit",
|
||
hour: "2-digit",
|
||
minute: "2-digit",
|
||
});
|
||
|
||
// 格式化文档类型
|
||
let docType = "其他";
|
||
switch (doc.type) {
|
||
case "api":
|
||
docType = "API文档";
|
||
break;
|
||
case "requirement":
|
||
docType = "需求文档";
|
||
break;
|
||
case "design":
|
||
docType = "设计文档";
|
||
break;
|
||
}
|
||
|
||
row.innerHTML = `
|
||
<td>${doc.name}</td>
|
||
<td>${docType}</td>
|
||
<td>${formattedDate}</td>
|
||
<td class="action-buttons">
|
||
<button class="view-demands-btn secondary" data-id="${doc.id}">查看需求</button>
|
||
<button class="edit-btn" data-id="${doc.id}">编辑</button>
|
||
<button class="delete-btn danger" data-id="${doc.id}">删除</button>
|
||
</td>
|
||
`;
|
||
|
||
docsListEl.appendChild(row);
|
||
});
|
||
|
||
// 添加按钮事件
|
||
document.querySelectorAll(".view-demands-btn").forEach((btn) => {
|
||
btn.addEventListener("click", function () {
|
||
const docId = this.getAttribute("data-id");
|
||
window.location.href = `demand.html?doc_id=${docId}`;
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll(".edit-btn").forEach((btn) => {
|
||
btn.addEventListener("click", function () {
|
||
const docId = this.getAttribute("data-id");
|
||
const doc = docs.find((d) => d.id === docId);
|
||
openDocModal(doc);
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll(".delete-btn").forEach((btn) => {
|
||
btn.addEventListener("click", function () {
|
||
const docId = this.getAttribute("data-id");
|
||
openConfirmModal(docId);
|
||
});
|
||
});
|
||
}
|
||
|
||
// 显示/隐藏空状态
|
||
function showEmptyState(show, message = "暂无文档数据") {
|
||
if (show) {
|
||
document.getElementById("docs-table").style.display = "none";
|
||
emptyStateEl.style.display = "block";
|
||
emptyStateEl.querySelector("p").textContent = message;
|
||
} else {
|
||
document.getElementById("docs-table").style.display = "table";
|
||
emptyStateEl.style.display = "none";
|
||
}
|
||
}
|
||
|
||
// 过滤文档
|
||
function filterDocs() {
|
||
const searchTerm = searchInputEl.value.toLowerCase();
|
||
|
||
if (!searchTerm) {
|
||
renderDocs(docs);
|
||
return;
|
||
}
|
||
|
||
const filteredDocs = docs.filter(
|
||
(doc) =>
|
||
doc.name.toLowerCase().includes(searchTerm) ||
|
||
(doc.description &&
|
||
doc.description.toLowerCase().includes(searchTerm))
|
||
);
|
||
|
||
renderDocs(filteredDocs);
|
||
}
|
||
|
||
// 打开文档模态框
|
||
function openDocModal(doc = null) {
|
||
modalTitle.textContent = doc ? "编辑文档" : "添加文档";
|
||
|
||
if (doc) {
|
||
docIdInput.value = doc.id;
|
||
docNameInput.value = doc.name || "";
|
||
docTypeInput.value = doc.type || "other";
|
||
docDescInput.value = doc.description || "";
|
||
fileNameEl.textContent = "保持原文件";
|
||
} else {
|
||
docForm.reset();
|
||
docIdInput.value = "";
|
||
fileNameEl.textContent = "未选择文件";
|
||
}
|
||
|
||
docModal.style.display = "block";
|
||
}
|
||
|
||
// 关闭文档模态框
|
||
function closeDocModal() {
|
||
docModal.style.display = "none";
|
||
docForm.reset();
|
||
fileNameEl.textContent = "未选择文件";
|
||
}
|
||
|
||
// 打开确认删除模态框
|
||
function openConfirmModal(docId) {
|
||
currentDocId = docId;
|
||
confirmModal.style.display = "block";
|
||
}
|
||
|
||
// 关闭确认删除模态框
|
||
function closeConfirmModal() {
|
||
confirmModal.style.display = "none";
|
||
currentDocId = null;
|
||
}
|
||
|
||
// 保存文档
|
||
async function saveDoc() {
|
||
const docId = docIdInput.value;
|
||
const name = docNameInput.value.trim();
|
||
const type = docTypeInput.value;
|
||
const description = docDescInput.value.trim();
|
||
|
||
if (!name) {
|
||
alert("请输入文档名称");
|
||
return;
|
||
}
|
||
|
||
// 创建FormData对象用于文件上传
|
||
const formData = new FormData();
|
||
formData.append("name", name);
|
||
formData.append("type", type);
|
||
formData.append("description", description);
|
||
formData.append("project_id", projectId);
|
||
|
||
// 如果有选择文件,添加到FormData
|
||
if (docFileInput.files.length > 0) {
|
||
formData.append("file", docFileInput.files[0]);
|
||
}
|
||
|
||
try {
|
||
let url = "http://127.0.0.1:5002/api/doc/";
|
||
let method = "POST";
|
||
|
||
if (docId) {
|
||
url += docId;
|
||
method = "PATCH";
|
||
}
|
||
|
||
const response = await fetch(url, {
|
||
method,
|
||
body: formData,
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error ${response.status}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.code === 0) {
|
||
alert(docId ? "文档更新成功" : "文档创建成功");
|
||
closeDocModal();
|
||
fetchDocs();
|
||
} else {
|
||
alert(`操作失败: ${result.message || "未知错误"}`);
|
||
}
|
||
} catch (error) {
|
||
console.error("保存文档错误:", error);
|
||
alert(`操作失败: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
// 删除文档
|
||
async function deleteDoc() {
|
||
if (!currentDocId) return;
|
||
|
||
try {
|
||
const response = await fetch(
|
||
`http://127.0.0.1:5002/api/doc/${currentDocId}`,
|
||
{
|
||
method: "DELETE",
|
||
}
|
||
);
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error ${response.status}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.code === 0) {
|
||
alert("文档删除成功");
|
||
closeConfirmModal();
|
||
fetchDocs();
|
||
} else {
|
||
alert(`删除失败: ${result.message || "未知错误"}`);
|
||
}
|
||
} catch (error) {
|
||
console.error("删除文档错误:", error);
|
||
alert(`删除失败: ${error.message}`);
|
||
}
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|