refactor: 移除域名列表接口并统一为站点列表接口

- 移除独立的 `/admin/stats/domains` 接口及相关文档
- 将原 `fetchDomainList` 重命名为 `fetchSiteList`,统一站点获取逻辑
- 优化后台界面中的站点显示,为空站点显示“默认站点 (Default)”
- 移除设置页面中的“站点管理”选项卡,简化后台功能结构
- 保持 `/admin/stats/sites` 接口功能不变,作为统一的站点数据源
This commit is contained in:
anghunk
2026-02-09 15:44:54 +08:00
parent 098d668a15
commit 3d2f1b0620
7 changed files with 55 additions and 106 deletions

View File

@@ -13,9 +13,13 @@
<div class="layout-actions-wrapper"> <div class="layout-actions-wrapper">
<div class="layout-domain-filter layout-domain-filter-header"> <div class="layout-domain-filter layout-domain-filter-header">
<select v-model="currentSiteId" class="layout-domain-select"> <select v-model="currentSiteId" class="layout-domain-select">
<option value="default">所有站点</option> <option :value="defaultSiteId">{{ getSiteLabel(defaultSiteId) }}</option>
<option v-for="item in siteOptions" :key="item.value" :value="item.value"> <option
{{ item.label }} v-for="item in siteOptions"
:key="item.value"
:value="item.value"
>
{{ getSiteLabel(item.value) }}
</option> </option>
</select> </select>
</div> </div>
@@ -71,9 +75,13 @@
> >
<div class="layout-sider-domain-filter"> <div class="layout-sider-domain-filter">
<select v-model="currentSiteId" class="layout-domain-select"> <select v-model="currentSiteId" class="layout-domain-select">
<option value="default">所有站点</option> <option :value="defaultSiteId">{{ getSiteLabel(defaultSiteId) }}</option>
<option v-for="item in siteOptions" :key="item.value" :value="item.value"> <option
{{ item.label }} v-for="item in siteOptions"
:key="item.value"
:value="item.value"
>
{{ getSiteLabel(item.value) }}
</option> </option>
</select> </select>
</div> </div>
@@ -173,7 +181,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, provide, computed } from "vue"; import { ref, onMounted, provide, computed } from "vue";
import { useRouter, useRoute } from "vue-router"; import { useRouter, useRoute } from "vue-router";
import { logoutAdmin, fetchSiteList, fetchAdminDisplaySettings } from "../../api/admin"; import { logoutAdmin, fetchAdminDisplaySettings, fetchSiteList } from "../../api/admin";
import { useTheme } from "../../composables/useTheme"; import { useTheme } from "../../composables/useTheme";
import { useSite } from "../../composables/useSite"; import { useSite } from "../../composables/useSite";
import packageJson from "../../../package.json"; import packageJson from "../../../package.json";
@@ -209,15 +217,23 @@ function cycleTheme() {
type SiteOption = { label: string; value: string }; type SiteOption = { label: string; value: string };
const siteOptions = ref<SiteOption[]>([]); const siteOptions = ref<SiteOption[]>([]);
const defaultSiteId = "";
function getSiteLabel(value: string) {
if (!value) {
return "默认站点 (Default)";
}
return value;
}
async function loadSites() { async function loadSites() {
try { try {
const res = await fetchSiteList(); const res = await fetchSiteList();
const sites = Array.isArray(res.sites) ? res.sites : []; const sites = Array.isArray(res.sites) ? res.sites : [];
const unique = Array.from(new Set(sites));
siteOptions.value = sites.map(s => ({ siteOptions.value = unique.filter((s) => s !== "").map((s) => ({
label: s === '' ? '默认站点 (Default)' : s, label: s,
value: s value: s,
})); }));
} catch { } catch {
siteOptions.value = []; siteOptions.value = [];

View File

@@ -29,7 +29,7 @@
@dragstart="onDragStart($event, domain, 'visible')" @dragstart="onDragStart($event, domain, 'visible')"
@dblclick="moveToHidden(domain)" @dblclick="moveToHidden(domain)"
> >
<span class="domain-text">{{ domain }}</span> <span class="domain-text">{{ getSiteLabel(domain) }}</span>
<button class="move-btn" @click="moveToHidden(domain)" title="移出"> <button class="move-btn" @click="moveToHidden(domain)" title="移出">
<PhArrowRight :size="16" /> <PhArrowRight :size="16" />
</button> </button>
@@ -74,7 +74,7 @@
<button class="move-btn" @click="moveToVisible(domain)" title="移入"> <button class="move-btn" @click="moveToVisible(domain)" title="移入">
<PhArrowLeft :size="16" /> <PhArrowLeft :size="16" />
</button> </button>
<span class="domain-text">{{ domain }}</span> <span class="domain-text">{{ getSiteLabel(domain) }}</span>
</div> </div>
</div> </div>
</div> </div>
@@ -90,7 +90,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed } from 'vue'; import { ref, onMounted, computed } from 'vue';
import { fetchDomainList, fetchFeatureSettings, saveFeatureSettings } from '../../../api/admin'; import { fetchSiteList, fetchFeatureSettings, saveFeatureSettings } from '../../../api/admin';
const loading = ref(false); const loading = ref(false);
const allDomains = ref<string[]>([]); const allDomains = ref<string[]>([]);
@@ -101,14 +101,21 @@ const hiddenList = computed(() => {
return allDomains.value.filter(d => !visibleSet.has(d)); return allDomains.value.filter(d => !visibleSet.has(d));
}); });
function getSiteLabel(value: string) {
if (!value) {
return '默认站点 (Default)';
}
return value;
}
async function loadData() { async function loadData() {
try { try {
const [domainRes, settingsRes] = await Promise.all([ const [siteRes, settingsRes] = await Promise.all([
fetchDomainList(), fetchSiteList(),
fetchFeatureSettings() fetchFeatureSettings()
]); ]);
allDomains.value = domainRes.domains || []; allDomains.value = siteRes.sites || [];
if (settingsRes.visibleDomains) { if (settingsRes.visibleDomains) {
visibleList.value = settingsRes.visibleDomains; visibleList.value = settingsRes.visibleDomains;
} else { } else {

View File

@@ -35,14 +35,6 @@
> >
后台显示 后台显示
</button> </button>
<button
type="button"
class="settings-tab"
:class="{ 'settings-tab-active': activeTab === 'site' }"
@click="activeTab = 'site'"
>
站点管理
</button>
<button <button
type="button" type="button"
class="settings-tab" class="settings-tab"
@@ -238,16 +230,6 @@
</div> </div>
</div> </div>
</template> </template>
<template v-else-if="activeTab === 'site'">
<div class="card">
<div class="card-header">
<div class="card-title">站点列表管理</div>
</div>
<div class="card-body">
<SiteManager />
</div>
</div>
</template>
<template v-else-if="activeTab === 'emailNotify'"> <template v-else-if="activeTab === 'emailNotify'">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
@@ -487,17 +469,15 @@ import {
fetchEmailNotifySettings, fetchEmailNotifySettings,
saveEmailNotifySettings, saveEmailNotifySettings,
sendTestEmail, sendTestEmail,
fetchFeatureSettings, fetchFeatureSettings,
saveFeatureSettings, saveFeatureSettings,
fetchAdminDisplaySettings, fetchAdminDisplaySettings,
saveAdminDisplaySettings, saveAdminDisplaySettings,
fetchTelegramSettings, fetchTelegramSettings,
saveTelegramSettings, saveTelegramSettings,
setupTelegramWebhook, setupTelegramWebhook,
sendTelegramTestMessage, sendTelegramTestMessage,
} from "../../api/admin"; } from "../../api/admin";
import SiteManager from "./components/SiteManager.vue";
import TagInput from "../../components/TagInput.vue"; import TagInput from "../../components/TagInput.vue";
const DEFAULT_REPLY_TEMPLATE = `<div style="background-color:#f4f4f5;padding:24px 0;"> const DEFAULT_REPLY_TEMPLATE = `<div style="background-color:#f4f4f5;padding:24px 0;">
@@ -606,15 +586,13 @@ type TabKey =
| "feature" | "feature"
| "display" | "display"
| "emailNotify" | "emailNotify"
| "telegramNotify" | "telegramNotify";
| "site";
const validTabs: TabKey[] = [ const validTabs: TabKey[] = [
"comment", "comment",
"feature", "feature",
"display", "display",
"emailNotify", "emailNotify",
"telegramNotify", "telegramNotify",
"site",
]; ];
const activeTab = ref<TabKey>( const activeTab = ref<TabKey>(

View File

@@ -5,7 +5,6 @@ export const getSites = async (c: Context<{ Bindings: Bindings }>) => {
try { try {
const sites = new Set<string>(); const sites = new Set<string>();
// 1. Get sites from Comment
const { results: commentRows } = await c.env.CWD_DB.prepare( const { results: commentRows } = await c.env.CWD_DB.prepare(
'SELECT DISTINCT site_id FROM Comment' 'SELECT DISTINCT site_id FROM Comment'
).all<{ site_id: string }>(); ).all<{ site_id: string }>();
@@ -16,7 +15,6 @@ export const getSites = async (c: Context<{ Bindings: Bindings }>) => {
} }
} }
// 2. Get sites from page_stats
const { results: pageRows } = await c.env.CWD_DB.prepare( const { results: pageRows } = await c.env.CWD_DB.prepare(
'SELECT DISTINCT site_id FROM page_stats' 'SELECT DISTINCT site_id FROM page_stats'
).all<{ site_id: string }>(); ).all<{ site_id: string }>();
@@ -27,16 +25,15 @@ export const getSites = async (c: Context<{ Bindings: Bindings }>) => {
} }
} }
// 3. Get sites from page_visit_daily const { results: dailyRows } = await c.env.CWD_DB.prepare(
const { results: dailyRows } = await c.env.CWD_DB.prepare( 'SELECT DISTINCT site_id FROM page_visit_daily'
'SELECT DISTINCT site_id FROM page_visit_daily' ).all<{ site_id: string }>();
).all<{ site_id: string }>();
for (const row of dailyRows) { for (const row of dailyRows) {
if (row.site_id !== undefined && row.site_id !== null) { if (row.site_id !== undefined && row.site_id !== null) {
sites.add(row.site_id); sites.add(row.site_id);
} }
} }
const list = Array.from(sites); const list = Array.from(sites);
list.sort(); list.sort();

View File

@@ -296,10 +296,9 @@ app.get('/admin/export/backup', exportBackup);
app.post('/admin/import/backup', importBackup); app.post('/admin/import/backup', importBackup);
app.put('/admin/comments/status', updateStatus); app.put('/admin/comments/status', updateStatus);
app.put('/admin/comments/update', updateComment); app.put('/admin/comments/update', updateComment);
app.get('/admin/stats/comments', getStats);
app.get('/admin/stats/sites', getSites);
app.get('/admin/analytics/overview', getVisitOverview); app.get('/admin/analytics/overview', getVisitOverview);
app.get('/admin/analytics/pages', getVisitPages); app.get('/admin/analytics/pages', getVisitPages);
app.get('/admin/stats/sites', getSites);
app.get('/admin/likes/list', listLikes); app.get('/admin/likes/list', listLikes);
app.get('/admin/likes/stats', getLikeStats); app.get('/admin/likes/stats', getLikeStats);
app.get('/admin/settings/features', getFeatureSettings); app.get('/admin/settings/features', getFeatureSettings);

View File

@@ -50,7 +50,6 @@ Token 通过登录接口获取,有效期为 48 小时。
### [统计数据相关](./admin/stats.md) ### [统计数据相关](./admin/stats.md)
- **获取评论统计数据** `GET /admin/stats/comments` - 用于管理后台「数据看板」展示评论整体统计 - **获取评论统计数据** `GET /admin/stats/comments` - 用于管理后台「数据看板」展示评论整体统计
- **获取域名列表** `GET /admin/stats/domains` - 获取所有已产生评论或访问记录的域名列表
### [访问统计相关](./admin/analytics.md) ### [访问统计相关](./admin/analytics.md)

View File

@@ -90,54 +90,7 @@ GET /admin/stats/comments
} }
``` ```
## 1.2 获取域名列表 ## 1.2 获取点赞统计数据(点赞排行榜)
```
GET /admin/stats/domains
```
用于管理后台获取所有已产生评论或访问记录的域名列表,用于域名筛选下拉框等场景。
- 方法:`GET`
- 路径:`/admin/stats/domains`
- 鉴权需要Bearer Token
**成功响应**
- 状态码:`200`
```json
{
"domains": [
"blog.example.com",
"example.com",
"www.example.com"
]
}
```
字段说明:
| 字段名 | 类型 | 说明 |
| ---------- | --------------- | ---------------------- |
| `domains` | Array\<string\> | 域名列表,按字母排序 |
说明:
- 域名从评论数据和访问统计数据中提取
- 域名自动去重并按字母顺序排序
**错误响应**
- 状态码:`500`
```json
{
"message": "获取域名列表失败"
}
```
## 1.3 获取点赞统计数据(点赞排行榜)
``` ```
GET /admin/likes/stats GET /admin/likes/stats