<!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 rel="stylesheet" href="css/style.css">
</head>
<body>
<header class="header">
<div class="container header-content">
<div class="header-left">
<a href="index.html" class="logo">
<div class="logo-icon">⚡</div>
<span class="logo-text">算子开发效率平台</span>
</a>
<nav class="nav">
<a href="index.html" class="nav-link">首页</a>
<a href="deployment.html" class="nav-link">环境部署</a>
<a href="learning.html" class="nav-link">学习共享</a>
<a href="query.html" class="nav-link active">材料查询</a>
</nav>
</div>
<div class="header-right">
<a href="index.html" class="btn btn-ghost">返回首页</a>
</div>
</div>
</header>
<main class="main">
<div class="container">
<div class="section-header">
<div>
<h2 class="section-title">材料查询</h2>
<p class="section-subtitle">按业务范围快速查询开发资料</p>
</div>
<button class="btn btn-primary" id="btn-add-item">
<span>+</span> 新增内容
</button>
</div>
<div class="search-box" style="margin-bottom: var(--space-6);">
<span class="search-box-icon">🔍</span>
<input type="text" id="search-input" class="form-control" placeholder="搜索查询内容...">
</div>
<div id="category-tabs" class="tabs">
</div>
<div id="content-grid" class="grid grid-3" style="margin-top: var(--space-6);">
</div>
<div id="content-empty" class="empty-state" style="display: none;">
<div class="empty-state-icon">📋</div>
<p class="empty-state-text">暂无查询内容</p>
</div>
</div>
</main>
<div id="item-modal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title" id="modal-title">新增查询内容</h3>
<button class="modal-close" data-action="close">×</button>
</div>
<div class="modal-body">
<div class="form">
<input type="hidden" id="edit-id">
<input type="hidden" id="edit-category-id">
<div class="form-group">
<label class="form-label required">所属分类</label>
<select id="item-category" class="form-control">
</select>
</div>
<div class="form-group">
<label class="form-label required">标题</label>
<input type="text" id="item-title" class="form-control" placeholder="输入查询内容标题">
</div>
<div class="form-group">
<label class="form-label required">描述</label>
<textarea id="item-desc" class="form-control" rows="4" placeholder="简要描述查询内容"></textarea>
</div>
<div class="form-group">
<label class="form-label required">Wiki 链接</label>
<input type="text" id="item-wiki" class="form-control" placeholder="https://wiki.example.com/...">
<div class="form-helper">点击内容卡片将跳转到此链接</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-action="cancel">取消</button>
<button class="btn btn-primary" data-action="confirm">保存</button>
</div>
</div>
</div>
<div id="toast-container" class="toast-container"></div>
<script src="js/storage.js"></script>
<script src="js/common.js"></script>
<script>
const state = {
categories: [],
activeCategoryId: null,
searchQuery: '',
isEditing: false
};
* 初始化
*/
function init() {
loadCategories();
renderCategories();
renderContent();
fillCategoryOptions();
bindEvents();
if (state.categories.length > 0) {
setActiveCategory(state.categories[0].id);
}
}
* 加载分类数据
*/
function loadCategories() {
QueryAPI.getCategories()
.then(data => {
state.categories = data.categories || [];
renderCategories();
fillCategoryOptions();
})
.catch(() => {
state.categories = Storage.get('queryCategories', []);
renderCategories();
fillCategoryOptions();
});
}
* 保存分类数据
*/
function saveCategories() {
Storage.set('queryCategories', state.categories);
}
* 渲染分类 Tab
*/
function renderCategories() {
const tabsContainer = document.getElementById('category-tabs');
if (state.categories.length === 0) {
tabsContainer.innerHTML = '<div class="text-tertiary text-sm">暂无分类</div>';
return;
}
tabsContainer.innerHTML = state.categories.map(cat => {
const itemCount = cat.items?.length || 0;
return `
<button class="tab ${cat.id === state.activeCategoryId ? 'active' : ''}"
data-category-id="${cat.id}">
${cat.icon || '📦'} ${cat.name}
<span class="text-tertiary text-xs">(${itemCount})</span>
</button>
`;
}).join('');
}
* 渲染内容卡片
*/
function renderContent() {
const grid = document.getElementById('content-grid');
const empty = document.getElementById('content-empty');
const category = state.categories.find(c => c.id === state.activeCategoryId);
if (!category || !category.items || category.items.length === 0) {
grid.innerHTML = '';
empty.style.display = 'flex';
return;
}
let items = category.items;
if (state.searchQuery) {
items = Utils.filterItems(items, state.searchQuery, ['title', 'desc']);
}
if (items.length === 0) {
grid.innerHTML = '';
empty.style.display = 'flex';
empty.querySelector('.empty-state-text').textContent = state.searchQuery ? '未找到匹配的内容' : '暂无查询内容';
return;
}
empty.style.display = 'none';
grid.innerHTML = items.map(item => `
<div class="content-card">
<div class="flex-between" style="margin-bottom: var(--space-2);">
<div class="content-icon" data-wiki-url="${item.wikiUrl}" title="点击跳转 Wiki">
🔍
</div>
<div class="flex gap-1">
<button class="btn btn-sm btn-icon btn-ghost" data-action="edit" data-item-id="${item.id}" title="编辑">
✏️
</button>
<button class="btn btn-sm btn-icon btn-ghost" data-action="delete" data-item-id="${item.id}" title="删除">
🗑️
</button>
</div>
</div>
<h3 class="content-title">${item.title}</h3>
<p class="content-desc">${item.desc}</p>
</div>
`).join('');
}
* 填充分类选项
*/
function fillCategoryOptions() {
const select = document.getElementById('item-category');
select.innerHTML = state.categories.map(cat =>
`<option value="${cat.id}">${cat.icon || '📦'} ${cat.name}</option>`
).join('');
}
* 设置活动分类
* @param {number} categoryId - 分类ID
*/
function setActiveCategory(categoryId) {
state.activeCategoryId = categoryId;
renderCategories();
renderContent();
}
* 打开新增弹窗
*/
function openAddModal() {
state.isEditing = false;
document.getElementById('modal-title').textContent = '新增查询内容';
document.getElementById('edit-id').value = '';
document.getElementById('edit-category-id').value = state.activeCategoryId || '';
document.getElementById('item-category').value = state.activeCategoryId || '';
document.getElementById('item-title').value = '';
document.getElementById('item-desc').value = '';
document.getElementById('item-wiki').value = '';
const modal = document.getElementById('item-modal');
modal.classList.add('active');
}
* 打开编辑弹窗
* @param {number} itemId - 内容ID
*/
function openEditModal(itemId) {
const category = state.categories.find(c => c.id === state.activeCategoryId);
const item = category?.items?.find(i => i.id === itemId);
if (!item) {
Toast.error('未找到内容');
return;
}
state.isEditing = true;
document.getElementById('modal-title').textContent = '编辑查询内容';
document.getElementById('edit-id').value = itemId;
document.getElementById('edit-category-id').value = state.activeCategoryId;
document.getElementById('item-category').value = state.activeCategoryId;
document.getElementById('item-title').value = item.title;
document.getElementById('item-desc').value = item.desc;
document.getElementById('item-wiki').value = item.wikiUrl;
const modal = document.getElementById('item-modal');
modal.classList.add('active');
}
* 关闭弹窗
*/
function closeModal() {
document.getElementById('item-modal').classList.remove('active');
}
* 保存内容
*/
function saveItem() {
const categoryId = parseInt(document.getElementById('item-category').value);
const title = document.getElementById('item-title').value.trim();
const desc = document.getElementById('item-desc').value.trim();
const wikiUrl = document.getElementById('item-wiki').value.trim();
if (!title) {
Toast.error('请输入标题');
return;
}
if (!desc) {
Toast.error('请输入描述');
return;
}
if (!wikiUrl) {
Toast.error('请输入 Wiki 链接');
return;
}
if (!Validator.url(wikiUrl).valid) {
Toast.error('Wiki 链接格式不正确');
return;
}
const categoryIndex = state.categories.findIndex(c => c.id === categoryId);
if (categoryIndex === -1) {
Toast.error('未找到分类');
return;
}
const category = state.categories[categoryIndex];
if (state.isEditing) {
const itemId = parseInt(document.getElementById('edit-id').value);
const itemIndex = category.items?.findIndex(i => i.id === itemId) ?? -1;
if (itemIndex !== -1) {
category.items[itemIndex].title = title;
category.items[itemIndex].desc = desc;
category.items[itemIndex].wikiUrl = wikiUrl;
}
} else {
if (!category.items) {
category.items = [];
}
category.items.push({
id: Date.now(),
title,
desc,
wikiUrl
});
}
state.categories[categoryIndex] = category;
saveCategories();
closeModal();
setActiveCategory(categoryId);
Toast.success(state.isEditing ? '保存成功' : '新增成功');
}
* 删除内容
* @param {number} itemId - 内容ID
*/
function deleteItem(itemId) {
Modal.confirm('确定要删除这个查询内容吗?此操作不可恢复。', () => {
const categoryIndex = state.categories.findIndex(c => c.id === state.activeCategoryId);
if (categoryIndex === -1) return;
const category = state.categories[categoryIndex];
category.items = category.items?.filter(item => item.id !== itemId) || [];
state.categories[categoryIndex] = category;
saveCategories();
renderCategories();
renderContent();
Toast.success('删除成功');
}, '删除内容');
}
* 绑定事件
*/
function bindEvents() {
document.getElementById('btn-add-item').addEventListener('click', openAddModal);
document.getElementById('category-tabs').addEventListener('click', (e) => {
const tab = e.target.closest('[data-category-id]');
if (tab) {
const categoryId = parseInt(tab.dataset.categoryId);
setActiveCategory(categoryId);
}
});
document.getElementById('content-grid').addEventListener('click', (e) => {
const wikiIcon = e.target.closest('[data-wiki-url]');
if (wikiIcon) {
const url = wikiIcon.dataset.wikiUrl;
window.open(url, '_blank');
return;
}
const editBtn = e.target.closest('[data-action="edit"]');
if (editBtn) {
e.stopPropagation();
const itemId = parseInt(editBtn.dataset.itemId);
openEditModal(itemId);
return;
}
const deleteBtn = e.target.closest('[data-action="delete"]');
if (deleteBtn) {
e.stopPropagation();
const itemId = parseInt(deleteBtn.dataset.itemId);
deleteItem(itemId);
return;
}
const card = e.target.closest('.content-card');
if (card) {
const wikiIcon = card.querySelector('[data-wiki-url]');
if (wikiIcon) {
window.open(wikiIcon.dataset.wikiUrl, '_blank');
}
}
});
const modal = document.getElementById('item-modal');
modal.addEventListener('click', (e) => {
const action = e.target.dataset.action;
if (action === 'close' || e.target === modal) {
closeModal();
} else if (action === 'cancel') {
closeModal();
} else if (action === 'confirm') {
saveItem();
}
});
const searchInput = document.getElementById('search-input');
searchInput.addEventListener('input', Utils.debounce((e) => {
state.searchQuery = e.target.value.trim();
renderContent();
}, 300));
const currentFile = window.location.pathname.split('/').pop() || 'query.html';
document.querySelectorAll('.nav-link').forEach(link => {
link.classList.remove('active');
const hrefFile = link.getAttribute('href').split('/').pop();
if (hrefFile === currentFile) {
link.classList.add('active');
}
});
}
document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>