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