refactor(admin): 合并功能设置到网站设置并更新文档
将功能开关设置从独立页面合并到网站设置页面,简化导航结构 更新相关API文档和功能说明文档 移除不再使用的FeatureSettingsView组件和相关路由配置
This commit is contained in:
@@ -3,7 +3,6 @@ import LoginView from '../views/LoginView.vue';
|
|||||||
import LayoutView from '../views/LayoutView.vue';
|
import LayoutView from '../views/LayoutView.vue';
|
||||||
import CommentsView from '../views/CommentsView.vue';
|
import CommentsView from '../views/CommentsView.vue';
|
||||||
import SettingsView from '../views/SettingsView.vue';
|
import SettingsView from '../views/SettingsView.vue';
|
||||||
import FeatureSettingsView from '../views/FeatureSettingsView.vue';
|
|
||||||
import DataView from '../views/DataView.vue';
|
import DataView from '../views/DataView.vue';
|
||||||
import StatsView from '../views/StatsView.vue';
|
import StatsView from '../views/StatsView.vue';
|
||||||
import AnalyticsVisitView from '../views/AnalyticsVisitView.vue';
|
import AnalyticsVisitView from '../views/AnalyticsVisitView.vue';
|
||||||
@@ -42,11 +41,6 @@ const routes: RouteRecordRaw[] = [
|
|||||||
name: 'settings',
|
name: 'settings',
|
||||||
component: SettingsView
|
component: SettingsView
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'settings/features',
|
|
||||||
name: 'feature-settings',
|
|
||||||
component: FeatureSettingsView
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'data',
|
path: 'data',
|
||||||
name: 'data',
|
name: 'data',
|
||||||
|
|||||||
@@ -1,262 +0,0 @@
|
|||||||
<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 v-if="loading" class="page-hint">加载中...</div>
|
|
||||||
<div v-else>
|
|
||||||
<div class="card">
|
|
||||||
<h3 class="card-title">显示功能设置</h3>
|
|
||||||
|
|
||||||
<div class="form-item">
|
|
||||||
<label class="form-label">开启文章点赞功能</label>
|
|
||||||
<label class="switch">
|
|
||||||
<input v-model="enableArticleLike" type="checkbox" />
|
|
||||||
<span class="slider" />
|
|
||||||
</label>
|
|
||||||
<div class="form-hint">开启后,评论区顶部会显示文章点赞(喜欢)按钮。</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-item">
|
|
||||||
<label class="form-label">开启评论点赞功能</label>
|
|
||||||
<label class="switch">
|
|
||||||
<input v-model="enableCommentLike" type="checkbox" />
|
|
||||||
<span class="slider" />
|
|
||||||
</label>
|
|
||||||
<div class="form-hint">开启后,评论列表中的每条评论都会显示点赞按钮。</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="message && messageType === 'error'"
|
|
||||||
class="form-message form-message-error"
|
|
||||||
>
|
|
||||||
{{ message }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-actions">
|
|
||||||
<button class="card-button" :disabled="saving" @click="save">
|
|
||||||
<span v-if="saving">保存中...</span>
|
|
||||||
<span v-else>保存</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { onMounted, ref } from "vue";
|
|
||||||
import { fetchFeatureSettings, saveFeatureSettings } from "../api/admin";
|
|
||||||
|
|
||||||
const enableCommentLike = ref(true);
|
|
||||||
const enableArticleLike = ref(true);
|
|
||||||
const loading = ref(false);
|
|
||||||
const saving = ref(false);
|
|
||||||
const message = ref("");
|
|
||||||
const messageType = ref<"success" | "error">("success");
|
|
||||||
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 load() {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const res = await fetchFeatureSettings();
|
|
||||||
enableCommentLike.value = res.enableCommentLike;
|
|
||||||
enableArticleLike.value = res.enableArticleLike;
|
|
||||||
} catch (e: any) {
|
|
||||||
message.value = e.message || "加载失败";
|
|
||||||
messageType.value = "error";
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function save() {
|
|
||||||
saving.value = true;
|
|
||||||
message.value = "";
|
|
||||||
try {
|
|
||||||
const res = await saveFeatureSettings({
|
|
||||||
enableCommentLike: enableCommentLike.value,
|
|
||||||
enableArticleLike: enableArticleLike.value,
|
|
||||||
});
|
|
||||||
showToast(res.message || "保存成功", "success");
|
|
||||||
} catch (e: any) {
|
|
||||||
message.value = e.message || "保存失败";
|
|
||||||
messageType.value = "error";
|
|
||||||
} finally {
|
|
||||||
saving.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
load();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.page {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
max-width: 620px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 6px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-label {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #555555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch {
|
|
||||||
position: relative;
|
|
||||||
display: inline-flex;
|
|
||||||
width: 40px;
|
|
||||||
height: 22px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch input {
|
|
||||||
opacity: 0;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider {
|
|
||||||
position: absolute;
|
|
||||||
cursor: pointer;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: #d0d7de;
|
|
||||||
transition: 0.2s;
|
|
||||||
border-radius: 999px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider::before {
|
|
||||||
position: absolute;
|
|
||||||
content: "";
|
|
||||||
height: 16px;
|
|
||||||
width: 16px;
|
|
||||||
left: 3px;
|
|
||||||
top: 3px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
transition: 0.2s;
|
|
||||||
border-radius: 50%;
|
|
||||||
box-shadow: 0 1px 2px rgba(27, 31, 36, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch input:checked + .slider {
|
|
||||||
background-color: #0969da;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch input:checked + .slider::before {
|
|
||||||
transform: translateX(16px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-button {
|
|
||||||
padding: 8px 14px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: none;
|
|
||||||
background-color: #0969da;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-button:disabled {
|
|
||||||
opacity: 0.7;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-message {
|
|
||||||
font-size: 13px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-message-error {
|
|
||||||
color: #d1242f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-hint {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #57606a;
|
|
||||||
}
|
|
||||||
.form-hint {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #57606a;
|
|
||||||
margin-top: 4px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -97,13 +97,6 @@
|
|||||||
>
|
>
|
||||||
网站设置
|
网站设置
|
||||||
</li>
|
</li>
|
||||||
<li
|
|
||||||
class="menu-item"
|
|
||||||
:class="{ active: isRouteActive('feature-settings') }"
|
|
||||||
@click="goFeatureSettings"
|
|
||||||
>
|
|
||||||
功能开关
|
|
||||||
</li>
|
|
||||||
<li
|
<li
|
||||||
class="menu-item"
|
class="menu-item"
|
||||||
:class="{ active: isRouteActive('data') }"
|
:class="{ active: isRouteActive('data') }"
|
||||||
|
|||||||
@@ -93,6 +93,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h3 class="card-title">功能开关</h3>
|
||||||
|
<div class="form-item">
|
||||||
|
<label class="form-label">开启文章点赞功能</label>
|
||||||
|
<label class="switch">
|
||||||
|
<input v-model="enableArticleLike" type="checkbox" />
|
||||||
|
<span class="slider" />
|
||||||
|
</label>
|
||||||
|
<div class="form-hint">开启后,评论区顶部会显示文章点赞(喜欢)按钮。</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-item">
|
||||||
|
<label class="form-label">开启评论点赞功能</label>
|
||||||
|
<label class="switch">
|
||||||
|
<input v-model="enableCommentLike" type="checkbox" />
|
||||||
|
<span class="slider" />
|
||||||
|
</label>
|
||||||
|
<div class="form-hint">开启后,评论列表中的每条评论都会显示点赞按钮。</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<button class="card-button" :disabled="savingFeature" @click="saveFeature">
|
||||||
|
<span v-if="savingFeature">保存中...</span>
|
||||||
|
<span v-else>保存</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3 class="card-title">通知邮箱设置</h3>
|
<h3 class="card-title">通知邮箱设置</h3>
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
@@ -246,6 +272,8 @@ import {
|
|||||||
fetchEmailNotifySettings,
|
fetchEmailNotifySettings,
|
||||||
saveEmailNotifySettings,
|
saveEmailNotifySettings,
|
||||||
sendTestEmail,
|
sendTestEmail,
|
||||||
|
fetchFeatureSettings,
|
||||||
|
saveFeatureSettings,
|
||||||
} from "../api/admin";
|
} from "../api/admin";
|
||||||
|
|
||||||
const DEFAULT_REPLY_TEMPLATE = `<div style="background-color:#f4f4f5;padding:24px 0;">
|
const DEFAULT_REPLY_TEMPLATE = `<div style="background-color:#f4f4f5;padding:24px 0;">
|
||||||
@@ -335,12 +363,15 @@ const blockedEmails = ref("");
|
|||||||
const commentAdminKey = ref("");
|
const commentAdminKey = ref("");
|
||||||
const adminKeySet = ref(false);
|
const adminKeySet = ref(false);
|
||||||
const requireReview = ref(false);
|
const requireReview = ref(false);
|
||||||
|
const enableArticleLike = ref(true);
|
||||||
|
const enableCommentLike = ref(true);
|
||||||
const savingEmail = ref(false);
|
const savingEmail = ref(false);
|
||||||
const testingEmail = ref(false);
|
const testingEmail = ref(false);
|
||||||
const savingComment = ref(false);
|
const savingComment = ref(false);
|
||||||
|
const savingFeature = ref(false);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const message = ref("");
|
const message = ref("");
|
||||||
const messageType = ref<"success" | "error">("success");
|
const messageType messageType = ref<"success" | "error">("success");
|
||||||
const toastMessage = ref("");
|
const toastMessage = ref("");
|
||||||
const toastType = ref<"success" | "error">("success");
|
const toastType = ref<"success" | "error">("success");
|
||||||
const toastVisible = ref(false);
|
const toastVisible = ref(false);
|
||||||
@@ -379,10 +410,11 @@ function resetTemplatesToDefault() {
|
|||||||
async function load() {
|
async function load() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const [notifyRes, commentRes, emailNotifyRes] = await Promise.all([
|
const [notifyRes, commentRes, emailNotifyRes, featureRes] = await Promise.all([
|
||||||
fetchAdminEmail(),
|
fetchAdminEmail(),
|
||||||
fetchCommentSettings(),
|
fetchCommentSettings(),
|
||||||
fetchEmailNotifySettings(),
|
fetchEmailNotifySettings(),
|
||||||
|
fetchFeatureSettings(),
|
||||||
]);
|
]);
|
||||||
email.value = notifyRes.email || "";
|
email.value = notifyRes.email || "";
|
||||||
commentAdminEmail.value = commentRes.adminEmail || "";
|
commentAdminEmail.value = commentRes.adminEmail || "";
|
||||||
@@ -400,6 +432,8 @@ async function load() {
|
|||||||
adminKeySet.value = !!commentRes.adminKeySet;
|
adminKeySet.value = !!commentRes.adminKeySet;
|
||||||
requireReview.value = !!commentRes.requireReview;
|
requireReview.value = !!commentRes.requireReview;
|
||||||
emailGlobalEnabled.value = !!emailNotifyRes.globalEnabled;
|
emailGlobalEnabled.value = !!emailNotifyRes.globalEnabled;
|
||||||
|
enableArticleLike.value = featureRes.enableArticleLike;
|
||||||
|
enableCommentLike.value = featureRes.enableCommentLike;
|
||||||
|
|
||||||
if (emailNotifyRes.templates) {
|
if (emailNotifyRes.templates) {
|
||||||
templateAdmin.value = emailNotifyRes.templates.admin || DEFAULT_ADMIN_TEMPLATE;
|
templateAdmin.value = emailNotifyRes.templates.admin || DEFAULT_ADMIN_TEMPLATE;
|
||||||
@@ -516,28 +550,30 @@ async function saveComment() {
|
|||||||
savingComment.value = true;
|
savingComment.value = true;
|
||||||
message.value = "";
|
message.value = "";
|
||||||
try {
|
try {
|
||||||
const res = await saveCommentSettings({
|
const [commentRes] = await Promise.all([
|
||||||
adminEmail: commentAdminEmail.value,
|
saveCommentSettings({
|
||||||
adminBadge: commentAdminBadge.value,
|
adminEmail: commentAdminEmail.value,
|
||||||
avatarPrefix: avatarPrefix.value,
|
adminBadge: commentAdminBadge.value,
|
||||||
adminEnabled: commentAdminEnabled.value,
|
avatarPrefix: avatarPrefix.value,
|
||||||
allowedDomains: allowedDomains.value
|
adminEnabled: commentAdminEnabled.value,
|
||||||
.split(/[,,\n]/)
|
allowedDomains: allowedDomains.value
|
||||||
.map((d) => d.trim())
|
.split(/[,,\n]/)
|
||||||
.filter(Boolean),
|
.map((d) => d.trim())
|
||||||
adminKey: commentAdminKey.value || undefined,
|
.filter(Boolean),
|
||||||
requireReview: requireReview.value,
|
adminKey: commentAdminKey.value || undefined,
|
||||||
blockedIps: blockedIps.value
|
requireReview: requireReview.value,
|
||||||
.split(/[,,\n]/)
|
blockedIps: blockedIps.value
|
||||||
.map((d) => d.trim())
|
.split(/[,,\n]/)
|
||||||
.filter(Boolean),
|
.map((d) => d.trim())
|
||||||
blockedEmails: blockedEmails.value
|
.filter(Boolean),
|
||||||
.split(/[,,\n]/)
|
blockedEmails: blockedEmails.value
|
||||||
.map((d) => d.trim())
|
.split(/[,,\n]/)
|
||||||
.filter(Boolean),
|
.map((d) => d.trim())
|
||||||
});
|
.filter(Boolean),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
showToast(res.message || "保存成功", "success");
|
showToast(commentRes.message || "保存成功", "success");
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
message.value = e.message || "保存失败";
|
message.value = e.message || "保存失败";
|
||||||
messageType.value = "error";
|
messageType.value = "error";
|
||||||
@@ -546,6 +582,26 @@ async function saveComment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function saveFeature() {
|
||||||
|
savingFeature.value = true;
|
||||||
|
message.value = "";
|
||||||
|
try {
|
||||||
|
const [featureRes] = await Promise.all([
|
||||||
|
saveFeatureSettings({
|
||||||
|
enableArticleLike: enableArticleLike.value,
|
||||||
|
enableCommentLike: enableCommentLike.value,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
showToast(featureRes.message || "保存成功", "success");
|
||||||
|
} catch (e: any) {
|
||||||
|
message.value = e.message || "保存失败";
|
||||||
|
messageType.value = "error";
|
||||||
|
} finally {
|
||||||
|
savingFeature.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
load();
|
load();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export const apiSidebar = [
|
|||||||
{ text: '邮件通知', link: '/api/admin/email-notify' },
|
{ text: '邮件通知', link: '/api/admin/email-notify' },
|
||||||
{ text: '统计数据', link: '/api/admin/stats' },
|
{ text: '统计数据', link: '/api/admin/stats' },
|
||||||
{ text: '访问统计', link: '/api/admin/analytics' },
|
{ text: '访问统计', link: '/api/admin/analytics' },
|
||||||
|
{ text: '点赞开关', link: '/api/admin/feature-settings' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -55,3 +55,8 @@ Token 通过登录接口获取,有效期为 24 小时。
|
|||||||
|
|
||||||
- **获取点赞记录列表** `GET /admin/likes/list` - 获取各页面的点赞记录列表,支持按页面或用户筛选
|
- **获取点赞记录列表** `GET /admin/likes/list` - 获取各页面的点赞记录列表,支持按页面或用户筛选
|
||||||
- **获取点赞统计** `GET /admin/likes/stats` - 获取点赞 Top 页面列表,用于后台展示点赞排行榜
|
- **获取点赞统计** `GET /admin/likes/stats` - 获取点赞 Top 页面列表,用于后台展示点赞排行榜
|
||||||
|
|
||||||
|
### [功能设置相关](./admin/feature-settings.md)
|
||||||
|
|
||||||
|
- **获取功能设置** `GET /admin/settings/feature` - 获取功能开关设置(评论点赞、文章点赞)
|
||||||
|
- **更新功能设置** `PUT /admin/settings/feature` - 更新功能开关设置
|
||||||
|
|||||||
96
docs/api/admin/feature-settings.md
Normal file
96
docs/api/admin/feature-settings.md
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
# 功能设置相关
|
||||||
|
|
||||||
|
管理点赞功能开关的接口。
|
||||||
|
|
||||||
|
## 获取功能设置
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /admin/settings/feature
|
||||||
|
```
|
||||||
|
|
||||||
|
获取当前的功能开关配置。
|
||||||
|
|
||||||
|
- 方法:`GET`
|
||||||
|
- 路径:`/admin/settings/feature`
|
||||||
|
- 鉴权:需要 Bearer Token
|
||||||
|
|
||||||
|
**成功响应**
|
||||||
|
|
||||||
|
- 状态码:`200`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"enableCommentLike": true,
|
||||||
|
"enableArticleLike": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字段说明:
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 说明 |
|
||||||
|
| ------------------ | ------- | ---------------------- |
|
||||||
|
| `enableCommentLike` | boolean | 是否启用评论点赞功能 |
|
||||||
|
| `enableArticleLike` | boolean | 是否启用文章点赞功能 |
|
||||||
|
|
||||||
|
## 更新功能设置
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /admin/settings/feature
|
||||||
|
```
|
||||||
|
|
||||||
|
更新功能开关配置。
|
||||||
|
|
||||||
|
- 方法:`PUT`
|
||||||
|
- 路径:`/admin/settings/feature`
|
||||||
|
- 鉴权:需要 Bearer Token
|
||||||
|
|
||||||
|
**请求头**
|
||||||
|
|
||||||
|
| 名称 | 必填 | 示例 |
|
||||||
|
| -------------- | ---- | ------------------ |
|
||||||
|
| `Content-Type` | 是 | `application/json` |
|
||||||
|
| `Authorization` | 是 | `Bearer <token>` |
|
||||||
|
|
||||||
|
**请求体**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"enableCommentLike": true,
|
||||||
|
"enableArticleLike": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字段说明:
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 必填 | 说明 |
|
||||||
|
| ------------------ | ------- | ---- | ---------------------- |
|
||||||
|
| `enableCommentLike` | boolean | 否 | 是否启用评论点赞功能 |
|
||||||
|
| `enableArticleLike` | boolean | 否 | 是否启用文章点赞功能 |
|
||||||
|
|
||||||
|
**成功响应**
|
||||||
|
|
||||||
|
- 状态码:`200`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Saved successfully"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**错误响应**
|
||||||
|
|
||||||
|
- 状态码:`401`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Unauthorized"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 状态码:`500`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Failed to save feature settings"
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -8,6 +8,22 @@
|
|||||||
|
|
||||||
所有 API 均为 RESTful 风格,无会话 Cookie,认证全部通过 `Authorization` 请求头完成。
|
所有 API 均为 RESTful 风格,无会话 Cookie,认证全部通过 `Authorization` 请求头完成。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
### 评论点赞
|
||||||
|
|
||||||
|
- 支持对单条评论进行点赞
|
||||||
|
- 每个用户对同一条评论只能点赞一次(通过 localStorage 本地记录用户 ID 和点赞状态)
|
||||||
|
- 点赞数实时更新
|
||||||
|
- 可通过管理员后台功能设置页面开启/关闭评论点赞功能
|
||||||
|
|
||||||
|
### 文章点赞
|
||||||
|
|
||||||
|
- 支持对整篇文章进行点赞
|
||||||
|
- 每个用户对同一篇文章只能点赞一次
|
||||||
|
- 点赞数实时显示
|
||||||
|
- 可通过管理员后台功能设置页面开启/关闭文章点赞功能
|
||||||
|
|
||||||
## 版本信息
|
## 版本信息
|
||||||
部署成功后,直接访问 Base URL,成功时返回 HTML,其中包含类似文案:
|
部署成功后,直接访问 Base URL,成功时返回 HTML,其中包含类似文案:
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ GET /api/comments
|
|||||||
"postSlug": "/blog/hello-world",
|
"postSlug": "/blog/hello-world",
|
||||||
"avatar": "https://gravatar.com/avatar/...",
|
"avatar": "https://gravatar.com/avatar/...",
|
||||||
"priority": 2,
|
"priority": 2,
|
||||||
|
"likes": 5,
|
||||||
"replies": [
|
"replies": [
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
@@ -58,7 +59,8 @@ GET /api/comments
|
|||||||
"avatar": "https://gravatar.com/avatar/...",
|
"avatar": "https://gravatar.com/avatar/...",
|
||||||
"parentId": 1,
|
"parentId": 1,
|
||||||
"replyToAuthor": "张三",
|
"replyToAuthor": "张三",
|
||||||
"priority": 1
|
"priority": 1,
|
||||||
|
"likes": 2
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user