refactor: 移除域名列表接口并统一为站点列表接口
- 移除独立的 `/admin/stats/domains` 接口及相关文档 - 将原 `fetchDomainList` 重命名为 `fetchSiteList`,统一站点获取逻辑 - 优化后台界面中的站点显示,为空站点显示“默认站点 (Default)” - 移除设置页面中的“站点管理”选项卡,简化后台功能结构 - 保持 `/admin/stats/sites` 接口功能不变,作为统一的站点数据源
This commit is contained in:
@@ -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 = [];
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>(
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user