feat(admin): 添加评论数据导出功能

- 新增数据管理页面和导出功能
- 实现后端导出接口返回所有评论数据
- 更新文档添加导出接口说明
This commit is contained in:
anghunk
2026-01-20 13:00:36 +08:00
parent 85fd16bab5
commit e6bc852945
9 changed files with 251 additions and 74 deletions

View File

@@ -124,3 +124,8 @@ export function saveCommentSettings(data: {
}): Promise<{ message: string }> {
return put<{ message: string }>('/admin/settings/comments', data);
}
export function exportComments(): Promise<any[]> {
return get<any[]>('/admin/comments/export');
}

View File

@@ -3,6 +3,7 @@ import LoginView from '../views/LoginView.vue';
import LayoutView from '../views/LayoutView.vue';
import CommentsView from '../views/CommentsView.vue';
import SettingsView from '../views/SettingsView.vue';
import DataView from '../views/DataView.vue';
const routes: RouteRecordRaw[] = [
{
@@ -27,6 +28,11 @@ const routes: RouteRecordRaw[] = [
path: 'settings',
name: 'settings',
component: SettingsView
},
{
path: 'data',
name: 'data',
component: DataView
}
]
}

View File

@@ -0,0 +1,143 @@
<template>
<div class="page">
<h2 class="page-title">数据管理</h2>
<div
v-if="toastVisible"
class="toast"
:class="toastType === 'error' ? 'toast-error' : 'toast-success'"
>
{{ toastMessage }}
</div>
<div class="card">
<h3 class="card-title">数据导出</h3>
<p class="card-desc">
将所有评论数据导出为 JSON 格式字段与数据库结构一致
</p>
<div class="card-actions">
<button class="card-button" :disabled="exporting" @click="handleExport">
<span v-if="exporting">导出中...</span>
<span v-else>导出 JSON</span>
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { exportComments } from "../api/admin";
const exporting = ref(false);
const toastMessage = ref("");
const toastType = ref<"success" | "error">("success");
const toastVisible = ref(false);
function showToast(msg: string, type: "success" | "error" = "success") {
toastMessage.value = msg;
toastType.value = type;
toastVisible.value = true;
window.setTimeout(() => {
toastVisible.value = false;
}, 2000);
}
async function handleExport() {
exporting.value = true;
try {
const data = await exportComments();
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `comments-export-${new Date().toISOString().split('T')[0]}.json`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
showToast("导出成功", "success");
} catch (e: any) {
showToast(e.message || "导出失败", "error");
} finally {
exporting.value = false;
}
}
</script>
<style scoped>
.page {
display: flex;
flex-direction: column;
gap: 12px;
max-width: 520px;
}
.page-title {
margin: 0;
font-size: 18px;
color: #24292f;
}
.card {
background-color: #ffffff;
border-radius: 6px;
border: 1px solid #d0d7de;
padding: 16px 18px;
margin-bottom: 1em;
}
.card-title {
margin: 0 0 12px;
font-size: 15px;
}
.card-desc {
font-size: 14px;
color: #57606a;
margin: 0 0 16px;
}
.card-actions {
display: flex;
justify-content: flex-end;
}
.card-button {
padding: 8px 14px;
border-radius: 4px;
border: none;
background-color: #0969da;
color: #ffffff;
font-size: 14px;
cursor: pointer;
}
.card-button:disabled {
opacity: 0.7;
cursor: default;
}
.toast {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
min-width: 220px;
max-width: 320px;
padding: 10px 14px;
border-radius: 6px;
font-size: 13px;
box-shadow: 0 8px 24px rgba(140, 149, 159, 0.2);
z-index: 1000;
}
.toast-success {
background-color: #1a7f37;
color: #ffffff;
}
.toast-error {
background-color: #d1242f;
color: #ffffff;
}
</style>

View File

@@ -30,6 +30,13 @@
>
评论管理
</li>
<li
class="menu-item"
:class="{ active: isRouteActive('data') }"
@click="goData"
>
数据管理
</li>
<li
class="menu-item"
:class="{ active: isRouteActive('settings') }"
@@ -61,6 +68,10 @@ function goComments() {
router.push({ name: "comments" });
}
function goData() {
router.push({ name: "data" });
}
function goSettings() {
router.push({ name: "settings" });
}