4 changed files with 335 additions and 2 deletions
-
6src/router/index.ts
-
1src/views/admin/AdminLayout.vue
-
229src/views/admin/CommentReviewView.vue
-
101src/views/admin/UserListView.vue
@ -0,0 +1,229 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import { ref, onMounted } from 'vue' |
||||
|
import { commentAdminApi } from '@/api/client' |
||||
|
import type { CommentResponse } from '@/generated/api' |
||||
|
|
||||
|
const comments = ref<CommentResponse[]>([]) |
||||
|
const page = ref(1) |
||||
|
const size = 10 |
||||
|
const total = ref(0) |
||||
|
const loading = ref(false) |
||||
|
const error = ref<string | null>(null) |
||||
|
|
||||
|
function formatDate(iso?: string): string { |
||||
|
if (!iso) return '' |
||||
|
const d = new Date(iso) |
||||
|
if (isNaN(d.getTime())) return iso |
||||
|
return d.toLocaleString('zh-CN', { hour12: false }) |
||||
|
} |
||||
|
|
||||
|
function statusLabel(status?: string): string { |
||||
|
switch (status) { |
||||
|
case 'PENDING': |
||||
|
return '待审核' |
||||
|
case 'APPROVED': |
||||
|
return '已通过' |
||||
|
case 'REJECTED': |
||||
|
return '已拒绝' |
||||
|
default: |
||||
|
return status ?? '-' |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function load() { |
||||
|
loading.value = true |
||||
|
error.value = null |
||||
|
try { |
||||
|
const resp = await commentAdminApi.apiAdminCommentsGet(page.value, size) |
||||
|
const data = resp.data.data |
||||
|
comments.value = (data?.list as CommentResponse[]) ?? [] |
||||
|
total.value = Number(data?.total ?? 0) |
||||
|
} catch (e) { |
||||
|
console.error(e) |
||||
|
error.value = '评论列表加载失败' |
||||
|
} finally { |
||||
|
loading.value = false |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function onApprove(id: number) { |
||||
|
try { |
||||
|
await commentAdminApi.apiAdminCommentsIdApprovePatch(id) |
||||
|
load() |
||||
|
} catch (e) { |
||||
|
console.error(e) |
||||
|
window.alert('审核通过失败') |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function onReject(id: number) { |
||||
|
try { |
||||
|
await commentAdminApi.apiAdminCommentsIdRejectPatch(id) |
||||
|
load() |
||||
|
} catch (e) { |
||||
|
console.error(e) |
||||
|
window.alert('拒绝失败') |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function onDelete(id: number) { |
||||
|
if (!window.confirm('确定删除该评论?')) return |
||||
|
try { |
||||
|
await commentAdminApi.apiAdminCommentsIdDelete(id) |
||||
|
load() |
||||
|
} catch (e) { |
||||
|
console.error(e) |
||||
|
window.alert('删除失败') |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onMounted(load) |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div> |
||||
|
<h1 class="page-title">评论审核</h1> |
||||
|
<p v-if="error" class="error-text">{{ error }}</p> |
||||
|
<p v-else-if="loading" class="hint-text">加载中...</p> |
||||
|
<p v-else-if="comments.length === 0" class="hint-text">暂无待审核评论</p> |
||||
|
|
||||
|
<table v-else class="comment-table"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th>作者</th> |
||||
|
<th>内容</th> |
||||
|
<th>状态</th> |
||||
|
<th>提交时间</th> |
||||
|
<th>操作</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<tr v-for="comment in comments" :key="comment.id"> |
||||
|
<td> |
||||
|
<div>{{ comment.authorName }}</div> |
||||
|
<div v-if="comment.authorEmail" class="sub-text">{{ comment.authorEmail }}</div> |
||||
|
</td> |
||||
|
<td class="content-cell">{{ comment.content }}</td> |
||||
|
<td> |
||||
|
<span class="status-badge" :class="(comment.status || '').toLowerCase()"> |
||||
|
{{ statusLabel(comment.status) }} |
||||
|
</span> |
||||
|
</td> |
||||
|
<td>{{ formatDate(comment.createdAt) }}</td> |
||||
|
<td> |
||||
|
<div class="actions"> |
||||
|
<button |
||||
|
v-if="comment.status !== 'APPROVED'" |
||||
|
class="btn-link" |
||||
|
@click="onApprove(comment.id)" |
||||
|
> |
||||
|
通过 |
||||
|
</button> |
||||
|
<button |
||||
|
v-if="comment.status !== 'REJECTED'" |
||||
|
class="btn-link" |
||||
|
@click="onReject(comment.id)" |
||||
|
> |
||||
|
拒绝 |
||||
|
</button> |
||||
|
<button class="btn-link danger" @click="onDelete(comment.id)">删除</button> |
||||
|
</div> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
<p class="total-hint">共 {{ total }} 条评论</p> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped> |
||||
|
.page-title { |
||||
|
margin: 0 0 16px; |
||||
|
font-size: 20px; |
||||
|
} |
||||
|
|
||||
|
.comment-table { |
||||
|
width: 100%; |
||||
|
border-collapse: collapse; |
||||
|
background: #fff; |
||||
|
border: 1px solid #e5e5ea; |
||||
|
border-radius: 8px; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.comment-table th, |
||||
|
.comment-table td { |
||||
|
padding: 10px 14px; |
||||
|
text-align: left; |
||||
|
border-bottom: 1px solid #eee; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
|
||||
|
.comment-table th { |
||||
|
background: #f7f8fa; |
||||
|
color: #5c6470; |
||||
|
} |
||||
|
|
||||
|
.content-cell { |
||||
|
max-width: 360px; |
||||
|
word-break: break-word; |
||||
|
} |
||||
|
|
||||
|
.sub-text { |
||||
|
color: #8a93a3; |
||||
|
font-size: 12px; |
||||
|
} |
||||
|
|
||||
|
.status-badge { |
||||
|
padding: 2px 10px; |
||||
|
border-radius: 12px; |
||||
|
font-size: 12px; |
||||
|
} |
||||
|
|
||||
|
.status-badge.pending { |
||||
|
background: #fff3cd; |
||||
|
color: #b26a00; |
||||
|
} |
||||
|
|
||||
|
.status-badge.approved { |
||||
|
background: #d4edda; |
||||
|
color: #1e7e34; |
||||
|
} |
||||
|
|
||||
|
.status-badge.rejected { |
||||
|
background: #f8d7da; |
||||
|
color: #8f1f2b; |
||||
|
} |
||||
|
|
||||
|
.actions { |
||||
|
display: flex; |
||||
|
gap: 10px; |
||||
|
white-space: nowrap; |
||||
|
} |
||||
|
|
||||
|
.btn-link { |
||||
|
border: none; |
||||
|
background: none; |
||||
|
color: #3b82f6; |
||||
|
cursor: pointer; |
||||
|
font-size: 13px; |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
.btn-link.danger { |
||||
|
color: #d33c3c; |
||||
|
} |
||||
|
|
||||
|
.total-hint { |
||||
|
color: #8a93a3; |
||||
|
font-size: 13px; |
||||
|
} |
||||
|
|
||||
|
.error-text { |
||||
|
color: #d33c3c; |
||||
|
} |
||||
|
|
||||
|
.hint-text { |
||||
|
color: #8a93a3; |
||||
|
} |
||||
|
</style> |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue