feat(评论系统): 添加评论配置和头像显示功能
- 新增评论配置管理功能,包括博主邮箱、标签和头像前缀设置 - 在评论列表显示用户头像 - 实现服务端配置存储和获取逻辑 - 优化前端设置页面,拆分不同配置项 - 修改开发预览页面配置保存逻辑
This commit is contained in:
@@ -11,6 +11,7 @@ export type CommentItem = {
|
||||
pubDate: string;
|
||||
author: string;
|
||||
email: string;
|
||||
avatar: string;
|
||||
postSlug: string;
|
||||
url: string | null;
|
||||
ipAddress: string | null;
|
||||
@@ -32,6 +33,12 @@ export type AdminEmailResponse = {
|
||||
email: string | null;
|
||||
};
|
||||
|
||||
export type CommentSettingsResponse = {
|
||||
adminEmail: string | null;
|
||||
adminBadge: string | null;
|
||||
avatarPrefix: string | null;
|
||||
};
|
||||
|
||||
export async function loginAdmin(name: string, password: string): Promise<string> {
|
||||
const res = await post<AdminLoginResponse>('/admin/login', { name, password });
|
||||
const key = res.data.key;
|
||||
@@ -63,3 +70,14 @@ export function saveAdminEmail(email: string): Promise<{ message: string }> {
|
||||
return put<{ message: string }>('/admin/settings/email', { email });
|
||||
}
|
||||
|
||||
export function fetchCommentSettings(): Promise<CommentSettingsResponse> {
|
||||
return get<CommentSettingsResponse>('/admin/settings/comments');
|
||||
}
|
||||
|
||||
export function saveCommentSettings(data: {
|
||||
adminEmail?: string;
|
||||
adminBadge?: string;
|
||||
avatarPrefix?: string;
|
||||
}): Promise<{ message: string }> {
|
||||
return put<{ message: string }>('/admin/settings/comments', data);
|
||||
}
|
||||
|
||||
@@ -27,9 +27,19 @@
|
||||
</div>
|
||||
<div v-for="item in filteredComments" :key="item.id" class="table-row">
|
||||
<div class="table-cell table-cell-author">
|
||||
<div class="cell-author-name">{{ item.author }}</div>
|
||||
<div class="cell-author-email">{{ item.email }}</div>
|
||||
<span class="cell-time">{{ formatDate(item.pubDate) }}</span>
|
||||
<div class="cell-author-wrapper">
|
||||
<img
|
||||
v-if="item.avatar"
|
||||
:src="item.avatar"
|
||||
class="cell-avatar"
|
||||
:alt="item.author"
|
||||
/>
|
||||
<div class="cell-author-main">
|
||||
<div class="cell-author-name">{{ item.author }}</div>
|
||||
<div class="cell-author-email">{{ item.email }}</div>
|
||||
<span class="cell-time">{{ formatDate(item.pubDate) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-cell table-cell-content">
|
||||
<div class="cell-content-text">{{ item.contentText }}</div>
|
||||
@@ -291,8 +301,6 @@ onMounted(() => {
|
||||
|
||||
.table-cell-author {
|
||||
width: 180px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start !important;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -369,6 +377,24 @@ onMounted(() => {
|
||||
color: #57606a;
|
||||
}
|
||||
|
||||
.cell-author-wrapper {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.cell-author-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.cell-avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cell-status {
|
||||
padding: 3px 8px;
|
||||
border-radius: 999px;
|
||||
|
||||
@@ -19,8 +19,30 @@
|
||||
{{ message }}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<button class="card-button" :disabled="saving" @click="save">
|
||||
<span v-if="saving">保存中...</span>
|
||||
<button class="card-button" :disabled="savingEmail" @click="saveEmail">
|
||||
<span v-if="savingEmail">保存中...</span>
|
||||
<span v-else>保存</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3 class="card-title">评论显示配置</h3>
|
||||
<div class="form-item">
|
||||
<label class="form-label">评论博主邮箱(用于前台标记)</label>
|
||||
<input v-model="commentAdminEmail" class="form-input" type="email" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">博主标签文字</label>
|
||||
<input v-model="commentAdminBadge" class="form-input" type="text" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">头像前缀(Gravatar/Cravatar)</label>
|
||||
<input v-model="avatarPrefix" class="form-input" type="text" />
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<button class="card-button" :disabled="savingComment" @click="saveComment">
|
||||
<span v-if="savingComment">保存中...</span>
|
||||
<span v-else>保存</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -31,10 +53,19 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from "vue";
|
||||
import { fetchAdminEmail, saveAdminEmail } from "../api/admin";
|
||||
import {
|
||||
fetchAdminEmail,
|
||||
saveAdminEmail,
|
||||
fetchCommentSettings,
|
||||
saveCommentSettings,
|
||||
} from "../api/admin";
|
||||
|
||||
const email = ref("");
|
||||
const saving = ref(false);
|
||||
const commentAdminEmail = ref("");
|
||||
const commentAdminBadge = ref("");
|
||||
const avatarPrefix = ref("");
|
||||
const savingEmail = ref(false);
|
||||
const savingComment = ref(false);
|
||||
const loading = ref(false);
|
||||
const message = ref("");
|
||||
const messageType = ref<"success" | "error">("success");
|
||||
@@ -42,8 +73,14 @@ const messageType = ref<"success" | "error">("success");
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await fetchAdminEmail();
|
||||
email.value = res.email || "";
|
||||
const [notifyRes, commentRes] = await Promise.all([
|
||||
fetchAdminEmail(),
|
||||
fetchCommentSettings(),
|
||||
]);
|
||||
email.value = notifyRes.email || "";
|
||||
commentAdminEmail.value = commentRes.adminEmail || "";
|
||||
commentAdminBadge.value = commentRes.adminBadge || "博主";
|
||||
avatarPrefix.value = commentRes.avatarPrefix || "";
|
||||
} catch (e: any) {
|
||||
message.value = e.message || "加载失败";
|
||||
messageType.value = "error";
|
||||
@@ -52,13 +89,13 @@ async function load() {
|
||||
}
|
||||
}
|
||||
|
||||
async function save() {
|
||||
async function saveEmail() {
|
||||
if (!email.value) {
|
||||
message.value = "请输入邮箱";
|
||||
messageType.value = "error";
|
||||
return;
|
||||
}
|
||||
saving.value = true;
|
||||
savingEmail.value = true;
|
||||
message.value = "";
|
||||
try {
|
||||
const res = await saveAdminEmail(email.value);
|
||||
@@ -68,7 +105,26 @@ async function save() {
|
||||
message.value = e.message || "保存失败";
|
||||
messageType.value = "error";
|
||||
} finally {
|
||||
saving.value = false;
|
||||
savingEmail.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveComment() {
|
||||
savingComment.value = true;
|
||||
message.value = "";
|
||||
try {
|
||||
const res = await saveCommentSettings({
|
||||
adminEmail: commentAdminEmail.value,
|
||||
adminBadge: commentAdminBadge.value,
|
||||
avatarPrefix: avatarPrefix.value,
|
||||
});
|
||||
message.value = res.message || "保存成功";
|
||||
messageType.value = "success";
|
||||
} catch (e: any) {
|
||||
message.value = e.message || "保存失败";
|
||||
messageType.value = "error";
|
||||
} finally {
|
||||
savingComment.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user