feat: 实现多站点数据隔离和管理功能
- 新增站点管理功能,替换原有的域名管理 - 添加数据库迁移工具,为现有表增加 site_id 字段 - 创建 useSite 组合式 API 统一管理站点状态 - 修改前后端 API 以支持站点筛选参数 - 更新管理界面,将域名筛选改为站点筛选 - 优化数据导出功能,支持按站点筛选导出 - 更新文档,调整站点 ID 默认值为空字符串 - 升级 wrangler 依赖至最新版本
This commit is contained in:
@@ -23,6 +23,7 @@ export type CommentItem = {
|
||||
likes?: number;
|
||||
ua?: string | null;
|
||||
isAdmin?: boolean;
|
||||
siteId?: string;
|
||||
};
|
||||
|
||||
export type CommentListResponse = {
|
||||
@@ -115,8 +116,8 @@ export type VisitPagesResponse = {
|
||||
itemsByLatest?: VisitPageItem[];
|
||||
};
|
||||
|
||||
export type DomainListResponse = {
|
||||
domains: string[];
|
||||
export type SiteListResponse = {
|
||||
sites: string[];
|
||||
};
|
||||
|
||||
export type LikeStatsItem = {
|
||||
@@ -153,11 +154,11 @@ export function logoutAdmin(): void {
|
||||
localStorage.removeItem('cwd_admin_token');
|
||||
}
|
||||
|
||||
export function fetchComments(page: number, domain?: string): Promise<CommentListResponse> {
|
||||
export function fetchComments(page: number, siteId?: string): Promise<CommentListResponse> {
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.set('page', String(page));
|
||||
if (domain) {
|
||||
searchParams.set('domain', domain);
|
||||
if (siteId && siteId !== 'default') {
|
||||
searchParams.set('siteId', siteId);
|
||||
}
|
||||
return get<CommentListResponse>(`/admin/comments/list?${searchParams.toString()}`);
|
||||
}
|
||||
@@ -262,8 +263,13 @@ export function blockEmail(email: string): Promise<{ message: string }> {
|
||||
return post<{ message: string }>('/admin/comments/block-email', { email });
|
||||
}
|
||||
|
||||
export function exportComments(): Promise<any[]> {
|
||||
return get<any[]>('/admin/comments/export');
|
||||
export function exportComments(siteId?: string): Promise<any[]> {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (siteId && siteId !== 'default') {
|
||||
searchParams.set('siteId', siteId);
|
||||
}
|
||||
const query = searchParams.toString();
|
||||
return get<any[]>(query ? `/admin/comments/export?${query}` : '/admin/comments/export');
|
||||
}
|
||||
|
||||
export function importComments(data: any[]): Promise<{ message: string }> {
|
||||
@@ -278,8 +284,13 @@ export function importConfig(data: any[]): Promise<{ message: string }> {
|
||||
return post<{ message: string }>('/admin/import/config', data);
|
||||
}
|
||||
|
||||
export function exportStats(): Promise<any> {
|
||||
return get<any>('/admin/export/stats');
|
||||
export function exportStats(siteId?: string): Promise<any> {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (siteId && siteId !== 'default') {
|
||||
searchParams.set('siteId', siteId);
|
||||
}
|
||||
const query = searchParams.toString();
|
||||
return get<any>(query ? `/admin/export/stats?${query}` : '/admin/export/stats');
|
||||
}
|
||||
|
||||
export function importStats(data: any): Promise<{ message: string }> {
|
||||
@@ -294,30 +305,30 @@ export function importBackup(data: any): Promise<{ message: string }> {
|
||||
return post<{ message: string }>('/admin/import/backup', data);
|
||||
}
|
||||
|
||||
export function fetchCommentStats(domain?: string): Promise<CommentStatsResponse> {
|
||||
export function fetchCommentStats(siteId?: string): Promise<CommentStatsResponse> {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (domain) {
|
||||
searchParams.set('domain', domain);
|
||||
if (siteId && siteId !== 'default') {
|
||||
searchParams.set('siteId', siteId);
|
||||
}
|
||||
const query = searchParams.toString();
|
||||
const url = query ? `/admin/stats/comments?${query}` : '/admin/stats/comments';
|
||||
return get<CommentStatsResponse>(url);
|
||||
}
|
||||
|
||||
export function fetchVisitOverview(domain?: string): Promise<VisitOverviewResponse> {
|
||||
export function fetchVisitOverview(siteId?: string): Promise<VisitOverviewResponse> {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (domain) {
|
||||
searchParams.set('domain', domain);
|
||||
if (siteId && siteId !== 'default') {
|
||||
searchParams.set('siteId', siteId);
|
||||
}
|
||||
const query = searchParams.toString();
|
||||
const url = query ? `/admin/analytics/overview?${query}` : '/admin/analytics/overview';
|
||||
return get<VisitOverviewResponse>(url);
|
||||
}
|
||||
|
||||
export function fetchVisitPages(domain?: string, order?: 'pv' | 'latest'): Promise<VisitPagesResponse> {
|
||||
export function fetchVisitPages(siteId?: string, order?: 'pv' | 'latest'): Promise<VisitPagesResponse> {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (domain) {
|
||||
searchParams.set('domain', domain);
|
||||
if (siteId && siteId !== 'default') {
|
||||
searchParams.set('siteId', siteId);
|
||||
}
|
||||
if (order) {
|
||||
searchParams.set('order', order);
|
||||
@@ -327,8 +338,8 @@ export function fetchVisitPages(domain?: string, order?: 'pv' | 'latest'): Promi
|
||||
return get<VisitPagesResponse>(url);
|
||||
}
|
||||
|
||||
export function fetchDomainList(): Promise<DomainListResponse> {
|
||||
return get<DomainListResponse>('/admin/stats/domains');
|
||||
export function fetchSiteList(): Promise<SiteListResponse> {
|
||||
return get<SiteListResponse>('/admin/stats/sites');
|
||||
}
|
||||
|
||||
export function fetchLikeStats(): Promise<LikeStatsResponse> {
|
||||
|
||||
17
cwd-admin/src/composables/useSite.ts
Normal file
17
cwd-admin/src/composables/useSite.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const currentSiteId = ref(localStorage.getItem('cwd_admin_site_id') || 'default');
|
||||
|
||||
watch(currentSiteId, (val) => {
|
||||
if (val) {
|
||||
localStorage.setItem('cwd_admin_site_id', val);
|
||||
} else {
|
||||
localStorage.removeItem('cwd_admin_site_id');
|
||||
}
|
||||
});
|
||||
|
||||
export function useSite() {
|
||||
return {
|
||||
currentSiteId
|
||||
};
|
||||
}
|
||||
@@ -224,6 +224,7 @@ import {
|
||||
fetchLikeStats,
|
||||
type LikeStatsItem,
|
||||
} from "../../api/admin";
|
||||
import { useSite } from "../../composables/useSite";
|
||||
|
||||
const loading = ref(false);
|
||||
const listLoading = ref(false);
|
||||
@@ -240,6 +241,8 @@ const overview = ref<VisitOverviewResponse>({
|
||||
last30Days: [],
|
||||
});
|
||||
|
||||
const { currentSiteId } = useSite();
|
||||
|
||||
function calculateChange(current: number, previous: number) {
|
||||
if (previous === 0) {
|
||||
return current > 0 ? 100 : 0;
|
||||
@@ -271,8 +274,6 @@ const visitTab = ref<"pv" | "latest">("pv");
|
||||
const visitTabStorageKey = "cwd-analytics-visit-tab";
|
||||
const chartRangeStorageKey = "cwd-analytics-visit-chart-range";
|
||||
|
||||
const injectedDomainFilter = inject<Ref<string> | null>("domainFilter", null);
|
||||
const domainFilter = injectedDomainFilter ?? ref("");
|
||||
const last30Days = ref<{ date: string; total: number }[]>([]);
|
||||
const likeStatsItems = ref<LikeStatsItem[]>([]);
|
||||
const chartRange = ref<"7" | "30">("7");
|
||||
@@ -424,7 +425,7 @@ async function loadData() {
|
||||
listLoading.value = true;
|
||||
error.value = "";
|
||||
try {
|
||||
const domain = domainFilter.value || undefined;
|
||||
const domain = currentSiteId.value;
|
||||
const order = getVisitOrderParam();
|
||||
const [overviewRes, pagesRes, likeStatsRes] = await Promise.all([
|
||||
fetchVisitOverview(domain),
|
||||
@@ -589,7 +590,7 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
});
|
||||
|
||||
watch(domainFilter, () => {
|
||||
watch(currentSiteId, () => {
|
||||
loadData();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -240,9 +240,9 @@ import {
|
||||
updateComment,
|
||||
blockIp,
|
||||
blockEmail,
|
||||
fetchDomainList,
|
||||
} from "../../api/admin";
|
||||
import ModalEdit from "./components/ModalEdit.vue";
|
||||
import { useSite } from "../../composables/useSite";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@@ -252,8 +252,7 @@ const pagination = ref<{ page: number; total: number }>({ page: 1, total: 1 });
|
||||
const loading = ref(false);
|
||||
const error = ref("");
|
||||
const statusFilter = ref("");
|
||||
const injectedDomainFilter = inject<Ref<string> | null>("domainFilter", null);
|
||||
const domainFilter = injectedDomainFilter ?? ref("");
|
||||
const { currentSiteId } = useSite();
|
||||
const jumpPageInput = ref("");
|
||||
const editVisible = ref(false);
|
||||
const editSaving = ref(false);
|
||||
@@ -325,7 +324,7 @@ async function loadComments(page?: number) {
|
||||
loading.value = true;
|
||||
error.value = "";
|
||||
try {
|
||||
const res = await fetchComments(targetPage, domainFilter.value || undefined);
|
||||
const res = await fetchComments(targetPage, currentSiteId.value);
|
||||
comments.value = res.data;
|
||||
pagination.value = { page: res.pagination.page, total: res.pagination.total };
|
||||
} catch (e: any) {
|
||||
@@ -342,11 +341,9 @@ function updateRoutePage(page: number) {
|
||||
} else {
|
||||
query.p = String(page);
|
||||
}
|
||||
if (domainFilter.value) {
|
||||
query.domain = domainFilter.value;
|
||||
} else {
|
||||
delete query.domain;
|
||||
}
|
||||
// Removed domain from query as it's now global state
|
||||
delete query.domain;
|
||||
|
||||
router.push({ query });
|
||||
}
|
||||
|
||||
@@ -530,15 +527,11 @@ onMounted(() => {
|
||||
initialPage = Math.floor(value);
|
||||
}
|
||||
}
|
||||
const d = route.query.domain;
|
||||
if (typeof d === "string" && d.trim()) {
|
||||
domainFilter.value = d.trim();
|
||||
}
|
||||
|
||||
loadComments(initialPage);
|
||||
});
|
||||
|
||||
watch(domainFilter, () => {
|
||||
watch(currentSiteId, () => {
|
||||
updateRoutePage(1);
|
||||
loadComments(1);
|
||||
});
|
||||
|
||||
@@ -121,6 +121,7 @@ import {
|
||||
exportStats, importStats,
|
||||
exportBackup, importBackup
|
||||
} from "../../api/admin";
|
||||
import { useSite } from "../../composables/useSite";
|
||||
|
||||
const exporting = ref(false);
|
||||
const importing = ref(false);
|
||||
@@ -130,6 +131,7 @@ const toastMessage = ref("");
|
||||
const toastType = ref<"success" | "error">("success");
|
||||
const toastVisible = ref(false);
|
||||
const importLogs = ref<string[]>([]);
|
||||
const { currentSiteId } = useSite();
|
||||
|
||||
// 当前导入模式: comments | config | stats | backup
|
||||
const currentImportMode = ref<string>('comments');
|
||||
@@ -178,9 +180,9 @@ async function executeExport(apiFunc: () => Promise<any>, fileNamePrefix: string
|
||||
}
|
||||
|
||||
// 导出处理
|
||||
const handleExportComments = () => executeExport(exportComments, 'comments-export');
|
||||
const handleExportComments = () => executeExport(() => exportComments(currentSiteId.value), 'comments-export');
|
||||
const handleExportConfig = () => executeExport(exportConfig, 'cwd-config');
|
||||
const handleExportStats = () => executeExport(exportStats, 'cwd-stats');
|
||||
const handleExportStats = () => executeExport(() => exportStats(currentSiteId.value), 'cwd-stats');
|
||||
const handleExportBackup = () => executeExport(exportBackup, 'cwd-full-backup');
|
||||
|
||||
// 触发文件选择
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
<div class="layout-title">{{ layoutTitle }}</div>
|
||||
<div class="layout-actions-wrapper">
|
||||
<div class="layout-domain-filter layout-domain-filter-header">
|
||||
<select v-model="domainFilter" class="layout-domain-select">
|
||||
<option value="">全部域名</option>
|
||||
<option v-for="item in domainOptions" :key="item" :value="item">
|
||||
{{ item }}
|
||||
<select v-model="currentSiteId" class="layout-domain-select">
|
||||
<option value="default">所有站点</option>
|
||||
<option v-for="item in siteOptions" :key="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -70,10 +70,10 @@
|
||||
:class="{ 'layout-sider-mobile-open': isMobileSiderOpen }"
|
||||
>
|
||||
<div class="layout-sider-domain-filter">
|
||||
<select v-model="domainFilter" class="layout-domain-select">
|
||||
<option value="">全部域名</option>
|
||||
<option v-for="item in domainOptions" :key="item" :value="item">
|
||||
{{ item }}
|
||||
<select v-model="currentSiteId" class="layout-domain-select">
|
||||
<option value="default">所有站点</option>
|
||||
<option v-for="item in siteOptions" :key="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -171,19 +171,20 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch, provide, computed } from "vue";
|
||||
import { ref, onMounted, provide, computed } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import { logoutAdmin, fetchDomainList, fetchAdminDisplaySettings, fetchFeatureSettings } from "../../api/admin";
|
||||
import { logoutAdmin, fetchSiteList, fetchAdminDisplaySettings } from "../../api/admin";
|
||||
import { useTheme } from "../../composables/useTheme";
|
||||
import { useSite } from "../../composables/useSite";
|
||||
import packageJson from "../../../package.json";
|
||||
|
||||
const DOMAIN_STORAGE_KEY = "cwd_admin_domain_filter";
|
||||
const API_BASE_URL_KEY = "cwd_admin_api_base_url";
|
||||
const SITE_TITLE_KEY = "cwd_admin_site_title";
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const { theme, setTheme } = useTheme();
|
||||
const { currentSiteId } = useSite();
|
||||
|
||||
const isMobileSiderOpen = ref(false);
|
||||
const isActionsOpen = ref(false);
|
||||
@@ -206,34 +207,20 @@ function cycleTheme() {
|
||||
else setTheme("system");
|
||||
}
|
||||
|
||||
const storedDomain =
|
||||
typeof window !== "undefined"
|
||||
? window.localStorage.getItem(DOMAIN_STORAGE_KEY) || ""
|
||||
: "";
|
||||
const domainFilter = ref(storedDomain);
|
||||
const domainOptions = ref<string[]>([]);
|
||||
type SiteOption = { label: string; value: string };
|
||||
const siteOptions = ref<SiteOption[]>([]);
|
||||
|
||||
async function loadDomains() {
|
||||
async function loadSites() {
|
||||
try {
|
||||
const [domainRes, settingsRes] = await Promise.all([
|
||||
fetchDomainList(),
|
||||
fetchFeatureSettings().catch(() => ({ visibleDomains: undefined }))
|
||||
]);
|
||||
const res = await fetchSiteList();
|
||||
const sites = Array.isArray(res.sites) ? res.sites : [];
|
||||
|
||||
let domains = Array.isArray(domainRes.domains) ? domainRes.domains : [];
|
||||
|
||||
// 如果配置了显示域名,则仅显示配置的域名
|
||||
if (settingsRes.visibleDomains && Array.isArray(settingsRes.visibleDomains) && settingsRes.visibleDomains.length > 0) {
|
||||
domains = settingsRes.visibleDomains;
|
||||
}
|
||||
|
||||
const set = new Set(domains);
|
||||
if (domainFilter.value && !set.has(domainFilter.value)) {
|
||||
set.add(domainFilter.value);
|
||||
}
|
||||
domainOptions.value = Array.from(set);
|
||||
siteOptions.value = sites.map(s => ({
|
||||
label: s === '' ? '默认站点 (Default)' : s,
|
||||
value: s
|
||||
}));
|
||||
} catch {
|
||||
domainOptions.value = [];
|
||||
siteOptions.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,21 +274,14 @@ async function loadDisplaySettings() {
|
||||
}
|
||||
}
|
||||
|
||||
provide("domainFilter", domainFilter);
|
||||
provide("updateSiteTitle", updateTitle);
|
||||
|
||||
onMounted(() => {
|
||||
loadDomains();
|
||||
loadSites();
|
||||
loadVersion();
|
||||
loadDisplaySettings();
|
||||
});
|
||||
|
||||
watch(domainFilter, (value) => {
|
||||
if (typeof window !== "undefined") {
|
||||
window.localStorage.setItem(DOMAIN_STORAGE_KEY, value || "");
|
||||
}
|
||||
});
|
||||
|
||||
function isRouteActive(name: string) {
|
||||
return route.name === name;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div class="domain-settings">
|
||||
<div class="domain-settings-desc">
|
||||
配置后台可见的域名。左侧为后台下拉框中显示的域名,右侧为数据库中发现的所有域名。支持拖拽或点击按钮移动。
|
||||
配置后台可见的站点。左侧为后台下拉框中显示的站点,右侧为数据库中发现的所有站点。支持拖拽或点击按钮移动。
|
||||
</div>
|
||||
|
||||
<div class="domain-transfer">
|
||||
<!-- Visible Domains (Left) -->
|
||||
<div class="transfer-panel">
|
||||
<div class="transfer-header">
|
||||
后台显示域名 ({{ visibleList.length }})
|
||||
后台显示站点 ({{ visibleList.length }})
|
||||
</div>
|
||||
<div
|
||||
class="transfer-body"
|
||||
@@ -19,7 +19,7 @@
|
||||
v-if="visibleList.length === 0"
|
||||
class="transfer-empty"
|
||||
>
|
||||
无域名 (默认显示全部)
|
||||
无站点 (默认显示全部)
|
||||
</div>
|
||||
<div
|
||||
v-for="domain in visibleList"
|
||||
@@ -50,7 +50,7 @@
|
||||
<!-- Hidden/All Domains (Right) -->
|
||||
<div class="transfer-panel">
|
||||
<div class="transfer-header">
|
||||
其他域名 ({{ hiddenList.length }})
|
||||
其他站点 ({{ hiddenList.length }})
|
||||
</div>
|
||||
<div
|
||||
class="transfer-body"
|
||||
@@ -61,7 +61,7 @@
|
||||
v-if="hiddenList.length === 0"
|
||||
class="transfer-empty"
|
||||
>
|
||||
无更多域名
|
||||
无更多站点
|
||||
</div>
|
||||
<div
|
||||
v-for="domain in hiddenList"
|
||||
@@ -338,4 +338,4 @@ onMounted(() => {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -38,10 +38,10 @@
|
||||
<button
|
||||
type="button"
|
||||
class="settings-tab"
|
||||
:class="{ 'settings-tab-active': activeTab === 'domain' }"
|
||||
@click="activeTab = 'domain'"
|
||||
:class="{ 'settings-tab-active': activeTab === 'site' }"
|
||||
@click="activeTab = 'site'"
|
||||
>
|
||||
域名管理
|
||||
站点管理
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -238,13 +238,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="activeTab === 'domain'">
|
||||
<template v-else-if="activeTab === 'site'">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">域名选择管理</div>
|
||||
<div class="card-title">站点列表管理</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<DomainSettings />
|
||||
<SiteManager />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -497,7 +497,7 @@ import {
|
||||
sendTelegramTestMessage,
|
||||
} from "../../api/admin";
|
||||
|
||||
import DomainSettings from "./components/DomainSettings.vue";
|
||||
import SiteManager from "./components/SiteManager.vue";
|
||||
import TagInput from "../../components/TagInput.vue";
|
||||
|
||||
const DEFAULT_REPLY_TEMPLATE = `<div style="background-color:#f4f4f5;padding:24px 0;">
|
||||
@@ -607,14 +607,14 @@ type TabKey =
|
||||
| "display"
|
||||
| "emailNotify"
|
||||
| "telegramNotify"
|
||||
| "domain";
|
||||
| "site";
|
||||
const validTabs: TabKey[] = [
|
||||
"comment",
|
||||
"feature",
|
||||
"display",
|
||||
"emailNotify",
|
||||
"telegramNotify",
|
||||
"domain",
|
||||
"site",
|
||||
];
|
||||
|
||||
const activeTab = ref<TabKey>(
|
||||
@@ -661,11 +661,12 @@ function loadCardsExpanded() {
|
||||
feature: parsed.feature ?? false,
|
||||
email: parsed.email ?? false,
|
||||
telegram: parsed.telegram ?? false,
|
||||
site: parsed.site ?? false,
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
return { comment: true, display: false, feature: false, email: false, telegram: false };
|
||||
return { comment: true, display: false, feature: false, email: false, telegram: false, site: false };
|
||||
}
|
||||
|
||||
const cardsExpanded = ref(loadCardsExpanded());
|
||||
@@ -822,7 +823,7 @@ async function testEmail() {
|
||||
message.value = "请先在上方“评论显示配置”中设置管理员邮箱";
|
||||
messageType.value = "error";
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!smtpUser.value || !smtpPass.value) {
|
||||
message.value = "请先填写 SMTP 账号和密码";
|
||||
messageType.value = "error";
|
||||
|
||||
@@ -115,6 +115,7 @@ import { onMounted, onBeforeUnmount, ref, nextTick, watch, inject } from "vue";
|
||||
import type { Ref } from "vue";
|
||||
import * as echarts from "echarts";
|
||||
import { fetchCommentStats } from "../../api/admin";
|
||||
import { useSite } from "../../composables/useSite";
|
||||
|
||||
type DomainStat = {
|
||||
domain: string;
|
||||
@@ -137,8 +138,7 @@ const last7Days = ref<{ date: string; total: number }[]>([]);
|
||||
const chartRange = ref<"7" | "30">("7");
|
||||
const chartRangeStorageKey = "cwd-stats-chart-range";
|
||||
|
||||
const injectedDomainFilter = inject<Ref<string> | null>("domainFilter", null);
|
||||
const domainFilter = injectedDomainFilter ?? ref("");
|
||||
const { currentSiteId } = useSite();
|
||||
|
||||
const toastMessage = ref("");
|
||||
const toastType = ref<"success" | "error">("success");
|
||||
@@ -185,7 +185,7 @@ async function loadStats() {
|
||||
statsLoading.value = true;
|
||||
statsError.value = "";
|
||||
try {
|
||||
const res = await fetchCommentStats(domainFilter.value || undefined);
|
||||
const res = await fetchCommentStats(currentSiteId.value);
|
||||
statsSummary.value = {
|
||||
total: res.summary.total,
|
||||
approved: res.summary.approved,
|
||||
@@ -330,7 +330,7 @@ onMounted(() => {
|
||||
window.addEventListener("resize", handleResize);
|
||||
});
|
||||
|
||||
watch(domainFilter, () => {
|
||||
watch(currentSiteId, () => {
|
||||
loadStats();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user