| | @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; |
| | var currentPage = ViewBag.CurrentPage as int? ?? 1; |
| | var totalPages = ViewBag.TotalPages as int? ?? 1; |
| | var totalCount = ViewBag.TotalCount as int? ?? 0; |
| | var pageSize = ViewBag.PageSize as int? ?? 20; |
| | } |
| | |
| | |
| | <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> |
| | <div style="display: flex; gap: 1rem;"> |
| | <a href="@Url.Action("Index", "Tag")" class="btn btn-secondary"> |
| | <i class="fas fa-tags"></i> |
| | 标签管理 |
| | </a> |
| | <button class="btn btn-primary" onclick="openToolModal()"> |
| | <i class="fas fa-plus"></i> |
| | 添加工具 |
| | </button> |
| | </div> |
| | </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;">工具列表 (@totalCount)</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> |
| | } |
| | <button class="btn btn-outline btn-xs" onclick="manageToolTags(@tool.Id, '@tool.Name')" title="管理标签"> |
| | <i class="fas fa-tags"></i> |
| | </button> |
| | </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> |
| | <a href="@Url.Action("Index", "ToolStatistics", new { toolId = tool.Id })" class="btn btn-outline btn-sm" title="统计"> |
| | <i class="fas fa-chart-bar"></i> |
| | </a> |
| | <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> |
| | } |
| | |
| | <!-- 分页控件 --> |
| | @if (totalPages > 1) |
| | { |
| | <div style="padding: 1.5rem; border-top: 1px solid var(--light-2); display: flex; justify-content: center; align-items: center; gap: 0.5rem;"> |
| | <button class="btn btn-outline btn-sm" onclick="changePage(1)" @(currentPage == 1 ? "disabled" : "")> |
| | <i class="fas fa-angle-double-left"></i> |
| | </button> |
| | <button class="btn btn-outline btn-sm" onclick="changePage(@(currentPage - 1))" @(currentPage == 1 ? "disabled" : "")> |
| | <i class="fas fa-angle-left"></i> |
| | </button> |
| | |
| | @{ |
| | var startPage = Math.Max(1, currentPage - 2); |
| | var endPage = Math.Min(totalPages, currentPage + 2); |
| | } |
| | |
| | @for (int i = startPage; i <= endPage; i++) |
| | { |
| | <button class="btn @(i == currentPage ? "btn-primary" : "btn-outline") btn-sm" onclick="changePage(@i)"> |
| | @i |
| | </button> |
| | } |
| | |
| | <button class="btn btn-outline btn-sm" onclick="changePage(@(currentPage + 1))" @(currentPage == totalPages ? "disabled" : "")> |
| | <i class="fas fa-angle-right"></i> |
| | </button> |
| | <button class="btn btn-outline btn-sm" onclick="changePage(@totalPages)" @(currentPage == totalPages ? "disabled" : "")> |
| | <i class="fas fa-angle-double-right"></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> |
| | <div style="display: flex; gap: 0.5rem;"> |
| | <input type="text" id="toolIcon" name="icon" placeholder="如: fas fa-file-pdf" readonly style="flex: 1;" /> |
| | <button type="button" class="btn btn-outline" onclick="openIconSelector()"> |
| | <i class="fas fa-icons"></i> |
| | 选择图标 |
| | </button> |
| | </div> |
| | </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> |
| |
|
| | <!-- 标签管理模态框 --> |
| | <div id="tagModal" class="modal" style="display: none;"> |
| | <div class="modal-backdrop" onclick="closeTagModal()"></div> |
| | <div class="modal-content" style="max-width: 500px;"> |
| | <div class="modal-header"> |
| | <h3 id="tagModalTitle">管理工具标签</h3> |
| | <button onclick="closeTagModal()" style="background: none; border: none; font-size: 1.5rem; cursor: pointer;">×</button> |
| | </div> |
| | |
| | <div class="modal-body"> |
| | <div style="margin-bottom: 1rem;"> |
| | <label style="display: block; font-weight: 600; margin-bottom: 0.5rem;">当前标签</label> |
| | <div id="currentTags" style="display: flex; flex-wrap: wrap; gap: 0.5rem; min-height: 2rem; padding: 0.5rem; border: 1px solid var(--light-2); border-radius: var(--border-radius);"> |
| | <span style="color: var(--dark-2); font-style: italic;">加载中...</span> |
| | </div> |
| | </div> |
| | |
| | <div> |
| | <label style="display: block; font-weight: 600; margin-bottom: 0.5rem;">添加标签</label> |
| | <div style="display: flex; gap: 0.5rem;"> |
| | <select id="tagSelect" style="flex: 1; padding: 0.5rem; border: 1px solid var(--light-2); border-radius: var(--border-radius);"> |
| | <option value="">选择标签...</option> |
| | </select> |
| | <button class="btn btn-primary btn-sm" onclick="addToolTag()"> |
| | <i class="fas fa-plus"></i> |
| | </button> |
| | </div> |
| | </div> |
| | </div> |
| | |
| | <div class="modal-footer"> |
| | <button type="button" class="btn btn-outline" onclick="closeTagModal()">关闭</button> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | <!-- 图标选择器模态框 --> |
| | <div id="iconSelectorModal" class="modal" style="display: none;"> |
| | <div class="modal-backdrop" onclick="closeIconSelector()"></div> |
| | <div class="modal-content" style="max-width: 800px; max-height: 80vh;"> |
| | <div class="modal-header"> |
| | <h3>选择图标</h3> |
| | <button onclick="closeIconSelector()" style="background: none; border: none; font-size: 1.5rem; cursor: pointer;">×</button> |
| | </div> |
| | |
| | <div class="modal-body" style="max-height: 60vh; overflow-y: auto;"> |
| | <div style="margin-bottom: 1rem;"> |
| | <input type="text" id="iconSearch" placeholder="搜索图标..." |
| | style="width: 100%; padding: 0.75rem; border: 1px solid var(--light-2); border-radius: var(--border-radius);" |
| | onkeyup="filterIcons()" /> |
| | </div> |
| | |
| | <div id="iconGrid" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 1rem;"> |
| | |
| | </div> |
| | </div> |
| | |
| | <div class="modal-footer"> |
| | <button type="button" class="btn btn-outline" onclick="closeIconSelector()">取消</button> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | <script> |
| | let currentView = 'table'; |
| | let isEditingTool = false; |
| | let currentToolId = 0; |
| | let currentToolName = ''; |
| | |
| | function changePage(page) { |
| | const categoryFilter = document.getElementById('categoryFilter').value; |
| | const searchInput = document.getElementById('searchInput').value; |
| | |
| | let url = `@Url.Action("Index", "Tool")?page=${page}`; |
| | if (categoryFilter !== '0') { |
| | url += `&categoryId=${categoryFilter}`; |
| | } |
| | if (searchInput) { |
| | url += `&search=${encodeURIComponent(searchInput)}`; |
| | } |
| | |
| | window.location.href = url; |
| | } |
| | |
| | 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'); |
| | } |
| | } |
| | |
| | |
| | async function manageToolTags(toolId, toolName) { |
| | currentToolId = toolId; |
| | currentToolName = toolName; |
| | |
| | document.getElementById('tagModal').style.display = 'flex'; |
| | document.getElementById('tagModalTitle').textContent = `管理工具标签 - ${toolName}`; |
| | |
| | await loadToolTags(); |
| | await loadAvailableTags(); |
| | } |
| | |
| | function closeTagModal() { |
| | document.getElementById('tagModal').style.display = 'none'; |
| | } |
| | |
| | async function loadToolTags() { |
| | try { |
| | const response = await fetch(`/Tag/GetToolTags?toolId=${currentToolId}`); |
| | const tags = await response.json(); |
| | |
| | const currentTagsDiv = document.getElementById('currentTags'); |
| | if (tags.length === 0) { |
| | currentTagsDiv.innerHTML = '<span style="color: var(--dark-2); font-style: italic;">暂无标签</span>'; |
| | } else { |
| | currentTagsDiv.innerHTML = tags.map(tag => ` |
| | <span class="badge" style="background: ${tag.color || 'var(--primary)'}; color: white; display: flex; align-items: center; gap: 0.25rem;"> |
| | ${tag.name} |
| | <button onclick="removeToolTag(${tag.id})" style="background: none; border: none; color: white; cursor: pointer; font-size: 0.75rem;"> |
| | <i class="fas fa-times"></i> |
| | </button> |
| | </span> |
| | `).join(''); |
| | } |
| | } catch (error) { |
| | console.error('加载工具标签失败:', error); |
| | toolHub.showToast('加载工具标签失败', 'error'); |
| | } |
| | } |
| | |
| | async function loadAvailableTags() { |
| | try { |
| | const response = await fetch('/Tag/GetList'); |
| | const tags = await response.json(); |
| | |
| | const tagSelect = document.getElementById('tagSelect'); |
| | tagSelect.innerHTML = '<option value="">选择标签...</option>'; |
| | |
| | tags.forEach(tag => { |
| | const option = document.createElement('option'); |
| | option.value = tag.id; |
| | option.textContent = tag.name; |
| | tagSelect.appendChild(option); |
| | }); |
| | } catch (error) { |
| | console.error('加载可用标签失败:', error); |
| | toolHub.showToast('加载可用标签失败', 'error'); |
| | } |
| | } |
| | |
| | async function addToolTag() { |
| | const tagId = document.getElementById('tagSelect').value; |
| | if (!tagId) { |
| | toolHub.showToast('请选择要添加的标签', 'warning'); |
| | return; |
| | } |
| | |
| | try { |
| | const response = await fetch('/Tag/AddToolTag', { |
| | method: 'POST', |
| | headers: { |
| | 'Content-Type': 'application/json', |
| | }, |
| | body: JSON.stringify({ toolId: currentToolId, tagId: parseInt(tagId) }) |
| | }); |
| | |
| | const result = await response.json(); |
| | if (result.success) { |
| | toolHub.showToast('标签添加成功', 'success'); |
| | document.getElementById('tagSelect').value = ''; |
| | await loadToolTags(); |
| | } else { |
| | toolHub.showToast(result.message || '添加失败', 'error'); |
| | } |
| | } catch (error) { |
| | console.error('添加标签失败:', error); |
| | toolHub.showToast('添加标签失败', 'error'); |
| | } |
| | } |
| | |
| | async function removeToolTag(tagId) { |
| | if (!confirm('确定要移除这个标签吗?')) { |
| | return; |
| | } |
| | |
| | try { |
| | const response = await fetch('/Tag/RemoveToolTag', { |
| | method: 'POST', |
| | headers: { |
| | 'Content-Type': 'application/json', |
| | }, |
| | body: JSON.stringify({ toolId: currentToolId, tagId: tagId }) |
| | }); |
| | |
| | const result = await response.json(); |
| | if (result.success) { |
| | toolHub.showToast('标签移除成功', 'success'); |
| | await loadToolTags(); |
| | } else { |
| | toolHub.showToast('移除失败', 'error'); |
| | } |
| | } catch (error) { |
| | console.error('移除标签失败:', error); |
| | toolHub.showToast('移除标签失败', 'error'); |
| | } |
| | } |
| | |
| | // 图标选择器功能 |
| | const iconList = [ |
| | // 文件类型图标 |
| | { name: 'PDF文件', class: 'fas fa-file-pdf', category: '文件' }, |
| | { name: 'Word文档', class: 'fas fa-file-word', category: '文件' }, |
| | { name: 'Excel表格', class: 'fas fa-file-excel', category: '文件' }, |
| | { name: 'PowerPoint', class: 'fas fa-file-powerpoint', category: '文件' }, |
| | { name: '文本文件', class: 'fas fa-file-alt', category: '文件' }, |
| | { name: '图片文件', class: 'fas fa-file-image', category: '文件' }, |
| | { name: '压缩文件', class: 'fas fa-file-archive', category: '文件' }, |
| | { name: '代码文件', class: 'fas fa-file-code', category: '文件' }, |
| | { name: '音频文件', class: 'fas fa-file-audio', category: '文件' }, |
| | { name: '视频文件', class: 'fas fa-file-video', category: '文件' }, |
| | |
| | // 工具类图标 |
| | { name: '工具', class: 'fas fa-tools', category: '工具' }, |
| | { name: '扳手', class: 'fas fa-wrench', category: '工具' }, |
| | { name: '锤子', class: 'fas fa-hammer', category: '工具' }, |
| | { name: '螺丝刀', class: 'fas fa-screwdriver', category: '工具' }, |
| | { name: '齿轮', class: 'fas fa-cog', category: '工具' }, |
| | { name: '设置', class: 'fas fa-cogs', category: '工具' }, |
| | |
| | // 媒体图标 |
| | { name: '图片', class: 'fas fa-image', category: '媒体' }, |
| | { name: '视频', class: 'fas fa-video', category: '媒体' }, |
| | { name: '音乐', class: 'fas fa-music', category: '媒体' }, |
| | { name: '相机', class: 'fas fa-camera', category: '媒体' }, |
| | { name: '麦克风', class: 'fas fa-microphone', category: '媒体' }, |
| | { name: '播放', class: 'fas fa-play', category: '媒体' }, |
| | { name: '暂停', class: 'fas fa-pause', category: '媒体' }, |
| | { name: '停止', class: 'fas fa-stop', category: '媒体' }, |
| | |
| | // 网络图标 |
| | { name: '网络', class: 'fas fa-network-wired', category: '网络' }, |
| | { name: 'WiFi', class: 'fas fa-wifi', category: '网络' }, |
| | { name: '链接', class: 'fas fa-link', category: '网络' }, |
| | { name: '下载', class: 'fas fa-download', category: '网络' }, |
| | { name: '上传', class: 'fas fa-upload', category: '网络' }, |
| | { name: '云存储', class: 'fas fa-cloud', category: '网络' }, |
| | { name: '服务器', class: 'fas fa-server', category: '网络' }, |
| | { name: '数据库', class: 'fas fa-database', category: '网络' }, |
| | |
| | // 设计图标 |
| | { name: '画笔', class: 'fas fa-paint-brush', category: '设计' }, |
| | { name: '调色板', class: 'fas fa-palette', category: '设计' }, |
| | { name: '设计', class: 'fas fa-drafting-compass', category: '设计' }, |
| | { name: '图层', class: 'fas fa-layer-group', category: '设计' }, |
| | { name: '裁剪', class: 'fas fa-crop', category: '设计' }, |
| | { name: '滤镜', class: 'fas fa-magic', category: '设计' }, |
| | |
| | // 开发图标 |
| | { name: '代码', class: 'fas fa-code', category: '开发' }, |
| | { name: '终端', class: 'fas fa-terminal', category: '开发' }, |
| | { name: '调试', class: 'fas fa-bug', category: '开发' }, |
| | { name: 'Git', class: 'fab fa-git-alt', category: '开发' }, |
| | { name: 'GitHub', class: 'fab fa-github', category: '开发' }, |
| | { name: 'HTML', class: 'fab fa-html5', category: '开发' }, |
| | { name: 'CSS', class: 'fab fa-css3-alt', category: '开发' }, |
| | { name: 'JavaScript', class: 'fab fa-js-square', category: '开发' }, |
| | { name: 'React', class: 'fab fa-react', category: '开发' }, |
| | { name: 'Node.js', class: 'fab fa-node-js', category: '开发' }, |
| | { name: 'Python', class: 'fab fa-python', category: '开发' }, |
| | { name: 'Java', class: 'fab fa-java', category: '开发' }, |
| | { name: 'PHP', class: 'fab fa-php', category: '开发' }, |
| | |
| | // 通用图标 |
| | { name: '主页', class: 'fas fa-home', category: '通用' }, |
| | { name: '用户', class: 'fas fa-user', category: '通用' }, |
| | { name: '用户组', class: 'fas fa-users', category: '通用' }, |
| | { name: '设置', class: 'fas fa-cog', category: '通用' }, |
| | { name: '搜索', class: 'fas fa-search', category: '通用' }, |
| | { name: '编辑', class: 'fas fa-edit', category: '通用' }, |
| | { name: '删除', class: 'fas fa-trash', category: '通用' }, |
| | { name: '添加', class: 'fas fa-plus', category: '通用' }, |
| | { name: '保存', class: 'fas fa-save', category: '通用' }, |
| | { name: '关闭', class: 'fas fa-times', category: '通用' }, |
| | { name: '检查', class: 'fas fa-check', category: '通用' }, |
| | { name: '警告', class: 'fas fa-exclamation-triangle', category: '通用' }, |
| | { name: '信息', class: 'fas fa-info-circle', category: '通用' }, |
| | { name: '问号', class: 'fas fa-question-circle', category: '通用' }, |
| | { name: '星标', class: 'fas fa-star', category: '通用' }, |
| | { name: '心形', class: 'fas fa-heart', category: '通用' }, |
| | { name: '点赞', class: 'fas fa-thumbs-up', category: '通用' }, |
| | { name: '分享', class: 'fas fa-share', category: '通用' }, |
| | { name: '邮件', class: 'fas fa-envelope', category: '通用' }, |
| | { name: '电话', class: 'fas fa-phone', category: '通用' }, |
| | { name: '日历', class: 'fas fa-calendar', category: '通用' }, |
| | { name: '时钟', class: 'fas fa-clock', category: '通用' }, |
| | { name: '地图', class: 'fas fa-map-marker-alt', category: '通用' }, |
| | { name: '购物车', class: 'fas fa-shopping-cart', category: '通用' }, |
| | { name: '钱包', class: 'fas fa-wallet', category: '通用' }, |
| | { name: '信用卡', class: 'fas fa-credit-card', category: '通用' }, |
| | { name: '锁', class: 'fas fa-lock', category: '通用' }, |
| | { name: '钥匙', class: 'fas fa-key', category: '通用' }, |
| | { name: '盾牌', class: 'fas fa-shield-alt', category: '通用' }, |
| | { name: '眼睛', class: 'fas fa-eye', category: '通用' }, |
| | { name: '眼睛斜杠', class: 'fas fa-eye-slash', category: '通用' }, |
| | { name: '打印', class: 'fas fa-print', category: '通用' }, |
| | { name: '扫描', class: 'fas fa-scanner', category: '通用' }, |
| | { name: '传真', class: 'fas fa-fax', category: '通用' }, |
| | { name: '计算器', class: 'fas fa-calculator', category: '通用' }, |
| | { name: '图表', class: 'fas fa-chart-bar', category: '通用' }, |
| | { name: '饼图', class: 'fas fa-chart-pie', category: '通用' }, |
| | { name: '折线图', class: 'fas fa-chart-line', category: '通用' }, |
| | { name: '表格', class: 'fas fa-table', category: '通用' }, |
| | { name: '列表', class: 'fas fa-list', category: '通用' }, |
| | { name: '网格', class: 'fas fa-th', category: '通用' }, |
| | { name: '标签', class: 'fas fa-tags', category: '通用' }, |
| | { name: '书签', class: 'fas fa-bookmark', category: '通用' }, |
| | { name: '文件夹', class: 'fas fa-folder', category: '通用' }, |
| | { name: '文件', class: 'fas fa-file', category: '通用' }, |
| | { name: '剪贴板', class: 'fas fa-clipboard', category: '通用' }, |
| | { name: '复制', class: 'fas fa-copy', category: '通用' }, |
| | { name: '粘贴', class: 'fas fa-paste', category: '通用' }, |
| | { name: '剪切', class: 'fas fa-cut', category: '通用' }, |
| | { name: '撤销', class: 'fas fa-undo', category: '通用' }, |
| | { name: '重做', class: 'fas fa-redo', category: '通用' }, |
| | { name: '刷新', class: 'fas fa-sync', category: '通用' }, |
| | { name: '旋转', class: 'fas fa-sync-alt', category: '通用' }, |
| | { name: '箭头', class: 'fas fa-arrow-right', category: '通用' }, |
| | { name: '左箭头', class: 'fas fa-arrow-left', category: '通用' }, |
| | { name: '上箭头', class: 'fas fa-arrow-up', category: '通用' }, |
| | { name: '下箭头', class: 'fas fa-arrow-down', category: '通用' }, |
| | { name: '外部链接', class: 'fas fa-external-link-alt', category: '通用' }, |
| | { name: '返回', class: 'fas fa-arrow-circle-left', category: '通用' }, |
| | { name: '前进', class: 'fas fa-arrow-circle-right', category: '通用' }, |
| | { name: '展开', class: 'fas fa-chevron-down', category: '通用' }, |
| | { name: '收起', class: 'fas fa-chevron-up', category: '通用' }, |
| | { name: '菜单', class: 'fas fa-bars', category: '通用' }, |
| | { name: '汉堡菜单', class: 'fas fa-hamburger', category: '通用' }, |
| | { name: '更多', class: 'fas fa-ellipsis-h', category: '通用' }, |
| | { name: '垂直更多', class: 'fas fa-ellipsis-v', category: '通用' } |
| | ]; |
| | |
| | function openIconSelector() { |
| | document.getElementById('iconSelectorModal').style.display = 'flex'; |
| | loadIconGrid(); |
| | } |
| | |
| | function closeIconSelector() { |
| | document.getElementById('iconSelectorModal').style.display = 'none'; |
| | document.getElementById('iconSearch').value = ''; |
| | } |
| | |
| | function loadIconGrid() { |
| | const iconGrid = document.getElementById('iconGrid'); |
| | iconGrid.innerHTML = ''; |
| | |
| | iconList.forEach(icon => { |
| | const iconDiv = document.createElement('div'); |
| | iconDiv.className = 'icon-item'; |
| | iconDiv.style.cssText = ` |
| | display: flex; |
| | flex-direction: column; |
| | align-items: center; |
| | padding: 1rem; |
| | border: 1px solid var(--light-2); |
| | border-radius: var(--border-radius); |
| | cursor: pointer; |
| | transition: all 0.2s; |
| | background: white; |
| | `; |
| | iconDiv.onmouseover = () => { |
| | iconDiv.style.borderColor = 'var(--primary)'; |
| | iconDiv.style.backgroundColor = 'var(--light-1)'; |
| | }; |
| | iconDiv.onmouseout = () => { |
| | iconDiv.style.borderColor = 'var(--light-2)'; |
| | iconDiv.style.backgroundColor = 'white'; |
| | }; |
| | iconDiv.onclick = () => selectIcon(icon.class); |
| | |
| | iconDiv.innerHTML = ` |
| | <i class="${icon.class}" style="font-size: 2rem; color: var(--primary); margin-bottom: 0.5rem;"></i> |
| | <div style="font-size: 0.75rem; text-align: center; color: var(--dark-2);">${icon.name}</div> |
| | <div style="font-size: 0.625rem; text-align: center; color: var(--dark-3); margin-top: 0.25rem;">${icon.class}</div> |
| | `; |
| | |
| | iconGrid.appendChild(iconDiv); |
| | }); |
| | } |
| | |
| | function selectIcon(iconClass) { |
| | document.getElementById('toolIcon').value = iconClass; |
| | closeIconSelector(); |
| | toolHub.showToast('图标已选择', 'success'); |
| | } |
| | |
| | function filterIcons() { |
| | const searchTerm = document.getElementById('iconSearch').value.toLowerCase(); |
| | const iconItems = document.querySelectorAll('.icon-item'); |
| | |
| | iconItems.forEach(item => { |
| | const iconName = item.querySelector('div').textContent.toLowerCase(); |
| | const iconClass = item.querySelector('div:last-child').textContent.toLowerCase(); |
| | |
| | if (iconName.includes(searchTerm) || iconClass.includes(searchTerm)) { |
| | item.style.display = 'flex'; |
| | } else { |
| | item.style.display = 'none'; |
| | } |
| | }); |
| | } |
| | </script> |
| |
|