Files
ns2.0/templates/admin_wiki_comments.html

577 lines
16 KiB
HTML
Executable File

{% extends "base.html" %}
{% block content %}
{% include 'admin_nav.html' %}
<div class="admin-container wiki-comments-review">
<div class="admin-header">
<h1>Wiki 评论审核</h1>
<div class="header-actions">
<div class="comments-filter">
<select id="status-filter" class="form-select">
<option value="all">全部状态</option>
<option value="pending">待审核</option>
<option value="approved">已通过</option>
<option value="rejected">已拒绝</option>
</select>
<select id="type-filter" class="form-select">
<option value="all">全部类型</option>
<option value="feature_request">功能建议</option>
<option value="bug_report">问题报告</option>
<option value="experience_share">新增功能</option>
</select>
</div>
</div>
</div>
<!-- <div class="debug-info">
<p>总评论数: {{ comments|length }}</p>
{% if comments %}
<p>第一条评论详情:</p>
<pre>{{ comments[0]|tojson(indent=2) }}</pre>
{% endif %}
</div> -->
<div class="comments-table-container">
<table class="admin-table comments-table">
<thead>
<tr>
<th class="text-center">ID</th>
<th>Wiki 标题</th>
<th>用户</th>
<th>评论类型</th>
<th class="content-column">内容</th>
<th>状态</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="comments-list">
{% for comment in comments %}
<tr data-comment-id="{{ comment.id }}"
data-status="{{ comment.status }}"
data-type="{{ comment.comment_type }}"
class="comment-row {% if comment.status == 'pending' %}pending-review{% endif %}">
<td class="text-center">{{ comment.id }}</td>
<td>
<div class="wiki-title-cell">
<span>{{ comment.wiki_title }}</span>
</div>
</td>
<td>
<div class="user-cell">
{% if comment.user_avatar %}
<div class="user-avatar">
<img src="{{ comment.user_avatar }}" alt="{{ comment.user_name }}">
</div>
{% endif %}
<span class="user-name">{{ comment.user_name }}</span>
</div>
</td>
<td>
<span class="comment-type-badge
{% if comment.comment_type == 'feature_request' %}badge-feature
{% elif comment.comment_type == 'bug_report' %}badge-bug
{% else %}badge-experience{% endif %}">
{% if comment.comment_type == 'feature_request' %}功能建议
{% elif comment.comment_type == 'bug_report' %}问题报告
{% else %}新增功能{% endif %}
</span>
</td>
<td class="content-column">
<div class="content-wrapper">
<div class="comment-content-preview" title="{{ comment.content }}">
{{ comment.content }}
</div>
<span class="comment-expand-btn" style="display: none;">
<i class="fas fa-chevron-right"></i>
</span>
</div>
</td>
<td class="status-cell">
{% if comment.status == 'pending' %}
<span class="badge badge-warning">待审核</span>
{% elif comment.status == 'approved' %}
<span class="badge badge-success">已通过</span>
{% else %}
<span class="badge badge-danger">已拒绝</span>
{% endif %}
</td>
<td>{{ comment.created_at }}</td>
<td>
<div class="action-buttons">
{% if comment.status == 'pending' %}
<button class="btn btn-success approve-btn" title="通过">
<i class="fas fa-check"></i>
</button>
<button class="btn btn-danger reject-btn" title="拒绝">
<i class="fas fa-times"></i>
</button>
{% endif %}
<button class="btn btn-secondary delete-btn" title="删除">
<i class="fas fa-trash"></i>
</button>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<style>
.comment-content-preview {
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-flex;
align-items: center;
vertical-align: middle;
line-height: 1.4;
position: relative;
}
.comment-expand-btn {
cursor: pointer;
margin-left: 8px;
color: var(--text-secondary);
transition: transform 0.3s ease, color 0.3s ease;
}
.comment-expand-btn:hover {
color: var(--primary-color);
transform: rotate(90deg);
}
.comment-content-full {
display: none;
width: 100%;
white-space: normal;
word-break: break-all;
margin-top: 8px;
padding: 8px;
background-color: var(--background-secondary);
border-radius: 6px;
}
.comment-content-full.show {
display: block;
}
/* 展开模式 */
.comment-content-preview.expanded {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1000;
background-color: var(--background-primary);
border: 1px solid var(--border-color);
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
max-width: 80vw;
max-height: 70vh;
width: 600px;
padding: 20px;
overflow-y: auto;
white-space: normal;
word-break: break-all;
}
/* 遮罩层 */
.comment-preview-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
z-index: 999;
display: none;
opacity: 0;
transition: opacity 0.3s ease;
}
.comment-preview-overlay.show {
display: block;
opacity: 1;
}
.expanded {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1000;
background-color: var(--background-primary);
border: 1px solid var(--border-color);
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
max-width: 80vw;
max-height: 70vh;
width: 600px;
padding: 20px;
overflow-y: auto;
opacity: 0;
transition: opacity 0.3s ease;
}
.expanded.show {
opacity: 1;
}
.expanded-comment-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
border-bottom: 1px solid var(--border-color);
padding-bottom: 10px;
}
.expanded-comment-close {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: var(--text-secondary);
transition: color 0.3s ease;
}
.expanded-comment-close:hover {
color: var(--text-primary);
}
.expanded-comment-content {
line-height: 1.6;
font-size: 16px;
color: var(--text-primary);
white-space: pre-wrap;
word-wrap: break-word;
}
/* 深色模式适配 */
[data-theme="dark"] .comment-content-preview:hover {
background-color: rgba(255,255,255,0.1);
}
[data-theme="dark"] .comment-content-preview.expanded {
background-color: var(--background-secondary);
}
.user-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
overflow: hidden;
margin-right: 8px;
}
.user-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.user-cell {
display: flex;
align-items: center;
}
.user-name {
font-size: 0.9em;
}
.wiki-comments-review {
background-color: var(--background-primary);
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
padding: 1.5rem;
}
.admin-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-color);
}
.header-actions {
display: flex;
align-items: center;
gap: 1rem;
}
.form-select {
padding: 0.5rem 1rem;
border: 1px solid var(--border-color);
border-radius: 6px;
background-color: var(--background-secondary);
color: var(--text-primary);
}
.comments-table {
width: 100%;
border-collapse: separate;
border-spacing: 0 0.5rem;
}
.comments-table thead th {
background-color: var(--background-secondary);
padding: 0.75rem;
text-align: left;
font-weight: 600;
}
.comment-row {
background-color: var(--background-secondary);
transition: all 0.3s ease;
}
.comment-row:hover {
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.pending-review {
border-left: 4px solid #ffc107;
}
.content-column {
max-width: 200px;
position: relative;
}
.content-wrapper {
display: flex;
align-items: center;
width: 100%;
}
.comment-content-preview {
flex-grow: 1;
max-width: calc(100% - 30px); /* 为箭头预留空间 */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.comment-expand-btn {
cursor: pointer;
margin-left: 8px;
color: var(--text-secondary);
transition: transform 0.3s ease, color 0.3s ease;
display: none; /* 默认隐藏 */
}
.comment-expand-btn:hover {
color: var(--primary-color);
transform: rotate(90deg);
}
.comment-content-full {
display: none;
width: 100%;
white-space: normal;
word-break: break-all;
margin-top: 8px;
padding: 8px;
background-color: var(--background-secondary);
border-radius: 6px;
}
.comment-content-full.show {
display: block;
}
.comment-type-badge {
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 500;
}
.badge-feature {
background-color: rgba(59, 130, 246, 0.1);
color: #3b82f6;
}
.badge-bug {
background-color: rgba(239, 68, 68, 0.1);
color: #ef4444;
}
.action-buttons {
display: flex;
gap: 0.5rem;
}
.action-buttons .btn {
width: 35px;
height: 35px;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
border-radius: 6px;
}
.action-buttons .btn i {
font-size: 1rem;
}
[data-theme="dark"] .wiki-comments-review {
background-color: var(--background-secondary);
box-shadow: 0 4px 6px rgba(255, 255, 255, 0.05);
}
.badge-experience {
background-color: rgba(52, 211, 153, 0.1);
color: #10b981;
}
.comments-filter {
display: flex;
gap: 10px;
}
/* 删除按钮样式 */
.action-buttons .delete-btn {
background-color: rgba(220, 53, 69, 0.1);
color: #dc3545;
margin-left: 0.5rem;
}
.action-buttons .delete-btn:hover {
background-color: rgba(220, 53, 69, 0.2);
}
[data-theme="dark"] .action-buttons .delete-btn {
background-color: rgba(220, 53, 69, 0.2);
color: #ff6b6b;
}
[data-theme="dark"] .action-buttons .delete-btn:hover {
background-color: rgba(220, 53, 69, 0.3);
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
const commentsList = document.getElementById('comments-list');
// 审核和删除操作
commentsList.addEventListener('click', function(event) {
const approveBtn = event.target.closest('.approve-btn');
const rejectBtn = event.target.closest('.reject-btn');
const deleteBtn = event.target.closest('.delete-btn');
if (approveBtn || rejectBtn || deleteBtn) {
// 找到最近的 tr 行
const row = event.target.closest('tr');
// 获取评论ID
const commentId = row.getAttribute('data-comment-id');
let url, method;
if (approveBtn) {
url = `/admin/wiki/comment/${commentId}/approve`;
method = '通过';
} else if (rejectBtn) {
url = `/admin/wiki/comment/${commentId}/reject`;
method = '拒绝';
} else if (deleteBtn) {
url = `/admin/wiki/comment/${commentId}/delete`;
method = '删除';
}
// 确认删除操作
if (deleteBtn && !confirm(`确定要${method}这条评论吗?`)) {
return;
}
// 发送请求
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(result => {
if (result.success) {
if (approveBtn) {
const statusCell = row.querySelector('.status-cell');
statusCell.innerHTML = '<span class="badge badge-success">已通过</span>';
} else if (rejectBtn) {
const statusCell = row.querySelector('.status-cell');
statusCell.innerHTML = '<span class="badge badge-danger">已拒绝</span>';
} else if (deleteBtn) {
// 删除整行
row.remove();
}
// 移除操作按钮
const actionButtons = row.querySelector('.action-buttons');
if (actionButtons && (approveBtn || rejectBtn)) {
actionButtons.remove();
}
} else {
alert(result.error || `${method}操作失败`);
}
})
.catch(error => {
console.error('Error:', error);
alert(`${method}操作失败,请重试`);
});
}
});
// 原有的内容预览和筛选代码保持不变
const contentPreviews = document.querySelectorAll('.content-wrapper');
contentPreviews.forEach(wrapper => {
const preview = wrapper.querySelector('.comment-content-preview');
const expandBtn = wrapper.querySelector('.comment-expand-btn');
const fullContent = preview.textContent.trim();
// 如果文本超过单元格宽度,显示展开箭头
if (preview.scrollWidth > preview.clientWidth) {
expandBtn.style.display = 'inline-block';
}
const previewText = fullContent.length > 50
? fullContent.substring(0, 50) + '...'
: fullContent;
// 创建完整内容容器
const fullContentDiv = document.createElement('div');
fullContentDiv.classList.add('comment-content-full');
fullContentDiv.textContent = fullContent;
wrapper.parentNode.appendChild(fullContentDiv);
// 添加展开/收起事件
expandBtn.addEventListener('click', function(e) {
e.stopPropagation();
fullContentDiv.classList.toggle('show');
// 旋转箭头
expandBtn.querySelector('i').style.transform =
fullContentDiv.classList.contains('show')
? 'rotate(90deg)'
: 'rotate(0deg)';
});
});
});
</script>
{% endblock %}