feat: GEO/SEO structured data + search ranking tracker
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
<el-button size="default" :type="activeTab === 'logs' ? 'primary' : ''" @click="switchTab('logs')">运行日志</el-button>
|
||||
<el-button size="default" :type="activeTab === 'assistant' ? 'primary' : ''" @click="switchTab('assistant')">AI 助手</el-button>
|
||||
<el-button size="default" :type="activeTab === 'searchproviders' ? 'primary' : ''" @click="switchTab('searchproviders')">搜索API</el-button>
|
||||
<el-button size="default" :type="activeTab === 'searchrankings' ? 'primary' : ''" @click="switchTab('searchrankings')">搜索排名</el-button>
|
||||
<el-button size="default" :type="activeTab === 'configitems' ? 'primary' : ''" @click="switchTab('configitems')">配置管理</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -607,6 +608,53 @@
|
||||
<template #footer><el-button @click="searchProviderTestVisible = false">关闭</el-button></template>
|
||||
</el-dialog>
|
||||
|
||||
<div v-if="activeTab === 'searchrankings'">
|
||||
<div class="toolbar">
|
||||
<span style="font-size:13px;color:#909399;">GEO/SEO 搜索排名追踪 — 每日 07:00 自动检查</span>
|
||||
</div>
|
||||
<div v-if="rankingsLoading" class="card-loading">加载中...</div>
|
||||
<template v-else>
|
||||
<div style="display:flex;gap:16px;margin-bottom:16px;flex-wrap:wrap;">
|
||||
<div class="stat-card" style="flex:1;min-width:140px;padding:16px;background:#f0f9ff;border-radius:8px;text-align:center;">
|
||||
<div style="font-size:28px;font-weight:700;color:#409eff;">{{ rankingStats.total_checks }}</div>
|
||||
<div style="font-size:12px;color:#909399;margin-top:4px;">累计检查关键词</div>
|
||||
</div>
|
||||
<div class="stat-card" style="flex:1;min-width:140px;padding:16px;background:#f0fdf4;border-radius:8px;text-align:center;">
|
||||
<div style="font-size:28px;font-weight:700;color:#67c23a;">{{ rankingStats.on_page }}</div>
|
||||
<div style="font-size:12px;color:#909399;margin-top:4px;">有排名关键词</div>
|
||||
</div>
|
||||
<div class="stat-card" style="flex:1;min-width:140px;padding:16px;background:#fef3f2;border-radius:8px;text-align:center;">
|
||||
<div style="font-size:28px;font-weight:700;color:#f56c6c;">{{ rankingStats.ai_cited }}</div>
|
||||
<div style="font-size:12px;color:#909399;margin-top:4px;">AI 搜索引用</div>
|
||||
</div>
|
||||
<div class="stat-card" style="flex:1;min-width:140px;padding:16px;background:#fff7e6;border-radius:8px;text-align:center;">
|
||||
<div style="font-size:28px;font-weight:700;color:#e6a23c;">{{ rankingStats.best_position ? '#' + rankingStats.best_position : '-' }}</div>
|
||||
<div style="font-size:12px;color:#909399;margin-top:4px;">最佳排名</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table :data="rankings" style="width:100%" size="small" max-height="500">
|
||||
<el-table-column prop="keyword" label="关键词" min-width="140" />
|
||||
<el-table-column prop="platform" label="平台" width="80" />
|
||||
<el-table-column prop="search_engine" label="引擎" width="70" />
|
||||
<el-table-column prop="position" label="排名" width="70">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.position && row.position <= 3 ? 'success' : row.position ? 'warning' : 'info'" size="small">
|
||||
{{ row.position ? '#' + row.position : '未上榜' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="ai_cited" label="AI引用" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.ai_cited ? 'success' : 'info'" size="small">{{ row.ai_cited ? '是' : '否' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="url_found" label="匹配 URL" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="checked_at" label="检查时间" width="160" />
|
||||
</el-table>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'configitems'">
|
||||
<div class="filter-bar">
|
||||
<el-button size="default" :type="configSubTab === 'sensitive' ? 'primary' : ''" @click="switchConfigSubTab('sensitive')">🔒 敏感词</el-button>
|
||||
@@ -634,7 +682,7 @@
|
||||
<el-tag size="small">{{ row.category || '未分类' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120">
|
||||
<el-table-column label="操作" width="150">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="editSensitiveWord(row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteSensitiveWord(row)">删除</el-button>
|
||||
@@ -682,7 +730,7 @@
|
||||
<el-tag :type="row.is_active ? 'success' : 'info'" size="small">{{ row.is_active ? '启用' : '停用' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120">
|
||||
<el-table-column label="操作" width="150">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="editCleanRule(row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteCleanRule(row)">删除</el-button>
|
||||
@@ -1270,6 +1318,20 @@ const llmConfigs = ref([]);
|
||||
return Object.entries(map).sort((a, b) => a[0].localeCompare(b[0]));
|
||||
});
|
||||
|
||||
const rankings = ref([]);
|
||||
const rankingStats = ref({ total_checks: 0, on_page: 0, ai_cited: 0, best_position: null });
|
||||
const rankingsLoading = ref(false);
|
||||
const loadRankings = async () => {
|
||||
rankingsLoading.value = true;
|
||||
try {
|
||||
const resp = await api.get('/api/seo/rankings?limit=100');
|
||||
rankings.value = resp.data || [];
|
||||
const overview = await api.get('/api/seo/rankings/overview');
|
||||
rankingStats.value = overview.data || { total_checks: 0, on_page: 0, ai_cited: 0, best_position: null };
|
||||
} catch (e) { console.error('加载排名数据失败:', e); }
|
||||
finally { rankingsLoading.value = false; }
|
||||
};
|
||||
|
||||
const contentCleanRules = ref([]);
|
||||
const ccrLoading = ref(false);
|
||||
const ccrDialogVisible = ref(false);
|
||||
@@ -1319,6 +1381,7 @@ const llmConfigs = ref([]);
|
||||
users: fetchUsers,
|
||||
logs: loadLogTypes, orgs: loadOrgs, roles: loadRoles, menus: loadMenus, assistant: loadAssistantConfig,
|
||||
searchproviders: loadSearchProviders,
|
||||
searchrankings: loadRankings,
|
||||
configitems: () => { loadSensitiveWords(); loadContentCleanRules(); },
|
||||
};
|
||||
const loadedTabs = new Set([]);
|
||||
@@ -1378,6 +1441,7 @@ const llmConfigs = ref([]);
|
||||
sensitiveWords, swLoading, swDialogVisible, swSaving, swForm, editingSwId, loadSensitiveWords, showAddSensitiveWord, editSensitiveWord, saveSensitiveWord, deleteSensitiveWord, configSubTab, switchConfigSubTab,
|
||||
contentCleanRules, ccrLoading, ccrDialogVisible, ccrDialogTitle, ccrSaving, ccrForm, editingCcrId,
|
||||
loadContentCleanRules, showAddCleanRule, editCleanRule, saveCleanRule, deleteCleanRule,
|
||||
rankings, rankingStats, rankingsLoading, loadRankings,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user