Toolhub / Views /Tool /Index.cshtml
unifare
Initial commit: ToolHub ASP.NET Core app
5fc700d
@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>