| @model List<ToolHub.Models.Tool> |
| @{ |
| ViewData["Title"] = "工具管理"; |
| Layout = "_AdminLayout"; |
| var categories = ViewBag.Categories as List<ToolHub.Models.Category> ?? new List<ToolHub.Models.Category>(); |
| var currentCategory = ViewBag.CurrentCategory as int? ?? 0; |
| } |
| |
| |
| <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;"> |
| <div> |
| <h2 style="font-size: 1.5rem; font-weight: 700; margin: 0; color: var(--dark);">工具管理</h2> |
| <p style="color: var(--dark-2); margin: 0.5rem 0 0;">管理平台上的所有工具,添加、编辑或删除工具</p> |
| </div> |
| <button class="btn btn-primary" onclick="openToolModal()"> |
| <i class="fas fa-plus"></i> |
| 添加工具 |
| </button> |
| </div> |
| |
| |
| <div style="background: white; padding: 1.5rem; border-radius: var(--border-radius); box-shadow: var(--shadow-sm); margin-bottom: 2rem;"> |
| <div style="display: grid; grid-template-columns: 1fr 1fr 1fr auto; gap: 1rem; align-items: end;"> |
| <div> |
| <label style="display: block; font-weight: 600; margin-bottom: 0.5rem; font-size: 0.875rem;">分类筛选</label> |
| <select id="categoryFilter" onchange="filterTools()" style="width: 100%; padding: 0.75rem; border: 1px solid var(--light-2); border-radius: var(--border-radius);"> |
| <option value="0">全部分类</option> |
| @foreach (var category in categories) |
| { |
| <option value="@category.Id" selected="@(currentCategory == category.Id)">@category.Name</option> |
| } |
| </select> |
| </div> |
| |
| <div> |
| <label style="display: block; font-weight: 600; margin-bottom: 0.5rem; font-size: 0.875rem;">状态筛选</label> |
| <select id="statusFilter" onchange="filterTools()" style="width: 100%; padding: 0.75rem; border: 1px solid var(--light-2); border-radius: var(--border-radius);"> |
| <option value="">全部状态</option> |
| <option value="active">启用</option> |
| <option value="inactive">禁用</option> |
| <option value="hot">热门</option> |
| <option value="new">新品</option> |
| <option value="recommended">推荐</option> |
| </select> |
| </div> |
| |
| <div> |
| <label style="display: block; font-weight: 600; margin-bottom: 0.5rem; font-size: 0.875rem;">搜索工具</label> |
| <input type="text" id="searchInput" placeholder="输入工具名称..." onkeyup="filterTools()" |
| style="width: 100%; padding: 0.75rem; border: 1px solid var(--light-2); border-radius: var(--border-radius);" /> |
| </div> |
| |
| <button class="btn btn-outline" onclick="clearFilters()"> |
| <i class="fas fa-undo"></i> |
| 重置 |
| </button> |
| </div> |
| </div> |
| |
| |
| <div class="data-table"> |
| <div style="padding: 1rem 1.5rem; border-bottom: 1px solid var(--light-2); display: flex; justify-content: space-between; align-items: center;"> |
| <span style="font-weight: 600;">工具列表 (@Model.Count)</span> |
| <div style="display: flex; gap: 0.5rem;"> |
| <button class="btn btn-outline btn-sm" onclick="toggleView('table')" id="tableViewBtn"> |
| <i class="fas fa-list"></i> 表格 |
| </button> |
| <button class="btn btn-outline btn-sm" onclick="toggleView('grid')" id="gridViewBtn"> |
| <i class="fas fa-th"></i> 网格 |
| </button> |
| </div> |
| </div> |
| |
| |
| <div id="tableView"> |
| <table> |
| <thead> |
| <tr> |
| <th>工具信息</th> |
| <th>分类</th> |
| <th>统计</th> |
| <th>标签</th> |
| <th>状态</th> |
| <th>创建时间</th> |
| <th>操作</th> |
| </tr> |
| </thead> |
| <tbody id="toolsTableBody"> |
| @foreach (var tool in Model) |
| { |
| <tr data-category="@tool.CategoryId" data-status="@(tool.IsActive ? "active" : "inactive")" |
| data-hot="@tool.IsHot.ToString().ToLower()" data-new="@tool.IsNew.ToString().ToLower()" data-recommended="@tool.IsRecommended.ToString().ToLower()"> |
| <td> |
| <div style="display: flex; align-items: center;"> |
| @if (!string.IsNullOrEmpty(tool.Image)) |
| { |
| <img src="@tool.Image" alt="@tool.Name" style="width: 40px; height: 40px; border-radius: var(--border-radius); margin-right: 0.75rem; object-fit: cover;" /> |
| } |
| else |
| { |
| <div style="width: 40px; height: 40px; background: linear-gradient(135deg, var(--primary), var(--secondary)); border-radius: var(--border-radius); display: flex; align-items: center; justify-content: center; margin-right: 0.75rem;"> |
| <i class="@tool.Icon" style="color: white;"></i> |
| </div> |
| } |
| <div> |
| <strong class="tool-name">@tool.Name</strong> |
| @if (!string.IsNullOrEmpty(tool.Description)) |
| { |
| <br><small style="color: var(--dark-2);">@(tool.Description.Length > 50 ? tool.Description.Substring(0, 50) + "..." : tool.Description)</small> |
| } |
| </div> |
| </div> |
| </td> |
| <td> |
| <span class="badge badge-primary">@tool.Category?.Name</span> |
| </td> |
| <td> |
| <div style="font-size: 0.875rem;"> |
| <div style="margin-bottom: 0.25rem;"> |
| <i class="fas fa-eye" style="color: var(--primary); margin-right: 0.25rem;"></i> |
| @(tool.ViewCount > 1000 ? (tool.ViewCount / 1000).ToString("F0") + "K" : tool.ViewCount.ToString()) |
| </div> |
| @if (tool.Rating > 0) |
| { |
| <div> |
| <i class="fas fa-star" style="color: var(--warning); margin-right: 0.25rem;"></i> |
| @tool.Rating.ToString("F1") |
| </div> |
| } |
| </div> |
| </td> |
| <td> |
| <div style="display: flex; flex-wrap: wrap; gap: 0.25rem;"> |
| @if (tool.IsHot) |
| { |
| <span class="badge badge-danger">热门</span> |
| } |
| @if (tool.IsNew) |
| { |
| <span class="badge badge-warning">新品</span> |
| } |
| @if (tool.IsRecommended) |
| { |
| <span class="badge badge-success">推荐</span> |
| } |
| </div> |
| </td> |
| <td> |
| @if (tool.IsActive) |
| { |
| <span class="badge badge-success">启用</span> |
| } |
| else |
| { |
| <span class="badge badge-secondary">禁用</span> |
| } |
| </td> |
| <td style="color: var(--dark-2); font-size: 0.875rem;"> |
| @tool.CreatedAt.ToString("yyyy-MM-dd") |
| </td> |
| <td> |
| <div style="display: flex; gap: 0.5rem;"> |
| <button class="btn btn-outline btn-sm" onclick="editTool(@tool.Id)" title="编辑"> |
| <i class="fas fa-edit"></i> |
| </button> |
| <button class="btn btn-outline btn-sm" onclick="toggleToolStatus(@tool.Id, @tool.IsActive.ToString().ToLower())" |
| style="color: @(tool.IsActive ? "var(--warning)" : "var(--success)");" title="@(tool.IsActive ? "禁用" : "启用")"> |
| <i class="fas fa-@(tool.IsActive ? "pause" : "play")"></i> |
| </button> |
| <button class="btn btn-outline btn-sm" onclick="deleteTool(@tool.Id, '@tool.Name')" |
| style="color: var(--danger);" title="删除"> |
| <i class="fas fa-trash"></i> |
| </button> |
| </div> |
| </td> |
| </tr> |
| } |
| </tbody> |
| </table> |
| </div> |
| |
| @if (!Model.Any()) |
| { |
| <div style="padding: 3rem; text-align: center; color: var(--dark-2);"> |
| <i class="fas fa-tools" style="font-size: 3rem; margin-bottom: 1rem; display: block; opacity: 0.3;"></i> |
| <h3 style="margin-bottom: 0.5rem;">暂无工具</h3> |
| <p style="margin-bottom: 1.5rem;">还没有添加任何工具</p> |
| <button class="btn btn-primary" onclick="openToolModal()"> |
| <i class="fas fa-plus"></i> |
| 添加第一个工具 |
| </button> |
| </div> |
| } |
| </div> |
|
|
| <!-- 工具模态框 --> |
| <div id="toolModal" class="modal" style="display: none;"> |
| <div class="modal-backdrop" onclick="closeToolModal()"></div> |
| <div class="modal-content" style="max-width: 600px;"> |
| <div class="modal-header"> |
| <h3 id="toolModalTitle">添加工具</h3> |
| <button onclick="closeToolModal()" style="background: none; border: none; font-size: 1.5rem; cursor: pointer;">×</button> |
| </div> |
| |
| <form id="toolForm" onsubmit="submitTool(event)"> |
| <div class="modal-body"> |
| <input type="hidden" id="toolId" name="id" value="0" /> |
| |
| <div class="form-group"> |
| <label>工具名称 *</label> |
| <input type="text" id="toolName" name="name" required placeholder="请输入工具名称" /> |
| </div> |
| |
| <div class="form-group"> |
| <label>工具描述</label> |
| <textarea id="toolDescription" name="description" rows="3" placeholder="请输入工具描述"></textarea> |
| </div> |
| |
| <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;"> |
| <div class="form-group"> |
| <label>所属分类 *</label> |
| <select id="toolCategory" name="categoryId" required> |
| <option value="">请选择分类</option> |
| @foreach (var category in categories) |
| { |
| <option value="@category.Id">@category.Name</option> |
| } |
| </select> |
| </div> |
| |
| <div class="form-group"> |
| <label>图标类名</label> |
| <input type="text" id="toolIcon" name="icon" placeholder="如: fas fa-file-pdf" /> |
| </div> |
| </div> |
| |
| <div class="form-group"> |
| <label>工具链接</label> |
| <input type="url" id="toolUrl" name="url" placeholder="https://example.com" /> |
| </div> |
| |
| <div class="form-group"> |
| <label>工具图片链接</label> |
| <input type="url" id="toolImage" name="image" placeholder="https://example.com/image.jpg" /> |
| </div> |
| |
| <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem;"> |
| <div class="form-group"> |
| <label style="display: flex; align-items: center; gap: 0.5rem;"> |
| <input type="checkbox" id="toolIsHot" name="isHot" /> |
| <span>热门工具</span> |
| </label> |
| </div> |
| |
| <div class="form-group"> |
| <label style="display: flex; align-items: center; gap: 0.5rem;"> |
| <input type="checkbox" id="toolIsNew" name="isNew" /> |
| <span>新品工具</span> |
| </label> |
| </div> |
| |
| <div class="form-group"> |
| <label style="display: flex; align-items: center; gap: 0.5rem;"> |
| <input type="checkbox" id="toolIsRecommended" name="isRecommended" /> |
| <span>推荐工具</span> |
| </label> |
| </div> |
| </div> |
| |
| <div class="form-group"> |
| <label>排序顺序</label> |
| <input type="number" id="toolSort" name="sortOrder" min="0" value="0" placeholder="数字越小排序越靠前" /> |
| </div> |
| </div> |
| |
| <div class="modal-footer"> |
| <button type="button" class="btn btn-outline" onclick="closeToolModal()">取消</button> |
| <button type="submit" class="btn btn-primary"> |
| <span id="toolSubmitText">保存</span> |
| </button> |
| </div> |
| </form> |
| </div> |
| </div> |
|
|
| <script> |
| let currentView = 'table'; |
| let isEditingTool = false; |
| |
| function toggleView(view) { |
| currentView = view; |
| document.getElementById('tableView').style.display = view === 'table' ? 'block' : 'none'; |
| |
| document.getElementById('tableViewBtn').classList.toggle('btn-primary', view === 'table'); |
| document.getElementById('tableViewBtn').classList.toggle('btn-outline', view !== 'table'); |
| } |
| |
| function filterTools() { |
| const categoryFilter = document.getElementById('categoryFilter').value; |
| const statusFilter = document.getElementById('statusFilter').value; |
| const searchInput = document.getElementById('searchInput').value.toLowerCase(); |
| |
| const tableRows = document.querySelectorAll('#toolsTableBody tr'); |
| |
| tableRows.forEach(row => { |
| const category = row.getAttribute('data-category'); |
| const status = row.getAttribute('data-status'); |
| const isHot = row.getAttribute('data-hot') === 'true'; |
| const isNew = row.getAttribute('data-new') === 'true'; |
| const isRecommended = row.getAttribute('data-recommended') === 'true'; |
| |
| const toolName = row.querySelector('.tool-name').textContent.toLowerCase(); |
| |
| let showRow = true; |
| |
| // 分类筛选 |
| if (categoryFilter !== '0' && category !== categoryFilter) { |
| showRow = false; |
| } |
| |
| |
| if (statusFilter) { |
| switch(statusFilter) { |
| case 'active': |
| if (status !== 'active') showRow = false; |
| break; |
| case 'inactive': |
| if (status !== 'inactive') showRow = false; |
| break; |
| case 'hot': |
| if (!isHot) showRow = false; |
| break; |
| case 'new': |
| if (!isNew) showRow = false; |
| break; |
| case 'recommended': |
| if (!isRecommended) showRow = false; |
| break; |
| } |
| } |
| |
| |
| if (searchInput && !toolName.includes(searchInput)) { |
| showRow = false; |
| } |
| |
| row.style.display = showRow ? '' : 'none'; |
| }); |
| } |
| |
| function clearFilters() { |
| document.getElementById('categoryFilter').value = '0'; |
| document.getElementById('statusFilter').value = ''; |
| document.getElementById('searchInput').value = ''; |
| filterTools(); |
| } |
| |
| function openToolModal(editMode = false) { |
| document.getElementById('toolModal').style.display = 'flex'; |
| document.getElementById('toolModalTitle').textContent = editMode ? '编辑工具' : '添加工具'; |
| document.getElementById('toolSubmitText').textContent = editMode ? '更新' : '保存'; |
| isEditingTool = editMode; |
| |
| if (!editMode) { |
| document.getElementById('toolForm').reset(); |
| document.getElementById('toolId').value = '0'; |
| } |
| } |
| |
| function closeToolModal() { |
| document.getElementById('toolModal').style.display = 'none'; |
| } |
| |
| async function editTool(id) { |
| try { |
| const response = await fetch(`/Tool/Get?id=${id}`); |
| if (response.ok) { |
| const tool = await response.json(); |
| openToolModal(true); |
| |
| document.getElementById('toolId').value = tool.id; |
| document.getElementById('toolName').value = tool.name; |
| document.getElementById('toolDescription').value = tool.description || ''; |
| document.getElementById('toolCategory').value = tool.categoryId; |
| document.getElementById('toolIcon').value = tool.icon || ''; |
| document.getElementById('toolUrl').value = tool.url || ''; |
| document.getElementById('toolImage').value = tool.image || ''; |
| document.getElementById('toolIsHot').checked = tool.isHot; |
| document.getElementById('toolIsNew').checked = tool.isNew; |
| document.getElementById('toolIsRecommended').checked = tool.isRecommended; |
| document.getElementById('toolSort').value = tool.sortOrder; |
| } |
| } catch (error) { |
| console.error('获取工具信息失败:', error); |
| toolHub.showToast('获取工具信息失败', 'error'); |
| } |
| } |
| |
| async function submitTool(event) { |
| event.preventDefault(); |
| |
| const formData = new FormData(event.target); |
| const data = Object.fromEntries(formData.entries()); |
| |
| // 处理复选框 |
| data.isHot = document.getElementById('toolIsHot').checked; |
| data.isNew = document.getElementById('toolIsNew').checked; |
| data.isRecommended = document.getElementById('toolIsRecommended').checked; |
| |
| try { |
| const response = await fetch('/Tool/Save', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify(data) |
| }); |
| |
| if (response.ok) { |
| toolHub.showToast(isEditingTool ? '工具更新成功' : '工具添加成功', 'success'); |
| closeToolModal(); |
| setTimeout(() => location.reload(), 1000); |
| } else { |
| toolHub.showToast('操作失败,请重试', 'error'); |
| } |
| } catch (error) { |
| console.error('提交失败:', error); |
| toolHub.showToast('操作失败,请重试', 'error'); |
| } |
| } |
| |
| async function toggleToolStatus(id, currentStatus) { |
| try { |
| const response = await fetch('/Tool/ToggleStatus', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ id: id, isActive: !currentStatus }) |
| }); |
| |
| if (response.ok) { |
| toolHub.showToast('状态更新成功', 'success'); |
| setTimeout(() => location.reload(), 1000); |
| } else { |
| toolHub.showToast('操作失败,请重试', 'error'); |
| } |
| } catch (error) { |
| console.error('操作失败:', error); |
| toolHub.showToast('操作失败,请重试', 'error'); |
| } |
| } |
| |
| async function deleteTool(id, name) { |
| if (!confirm(`确定要删除工具"${name}"吗?此操作不可恢复。`)) { |
| return; |
| } |
| |
| try { |
| const response = await fetch('/Tool/Delete', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ id: id }) |
| }); |
| |
| if (response.ok) { |
| toolHub.showToast('工具删除成功', 'success'); |
| setTimeout(() => location.reload(), 1000); |
| } else { |
| toolHub.showToast('删除失败,请重试', 'error'); |
| } |
| } catch (error) { |
| console.error('删除失败:', error); |
| toolHub.showToast('删除失败,请重试', 'error'); |
| } |
| } |
| </script> |
|
|