feat: GEO/SEO structured data + search ranking tracker
This commit is contained in:
@@ -8,8 +8,8 @@
|
||||
<link rel="stylesheet" href="theme-modern.css">
|
||||
<style>
|
||||
.selected-count { color: var(--color-text-secondary); font-size: var(--font-size-body); margin-left: auto; }
|
||||
.preview-dialog-custom .el-dialog__body { overflow-y: auto; max-height: calc(90vh - 120px); min-height: 400px; padding: 16px 20px; }
|
||||
.preview-dialog-custom .preview-body { max-height: calc(90vh - 180px); min-height: 300px; overflow: auto; }
|
||||
.preview-dialog-custom.el-dialog .el-dialog__body { overflow-y: auto; min-height: 400px; padding: 16px 20px; }
|
||||
.preview-dialog-custom .preview-body { min-height: 300px; overflow: auto; }
|
||||
.preview-dialog-custom .preview-body img { max-width: 100%; }
|
||||
.topic-card-list { display: none; }
|
||||
@media (max-width: 768px) {
|
||||
@@ -41,10 +41,11 @@
|
||||
<div class="page-header">
|
||||
<h2 class="page-title"><el-icon style="vertical-align:-2px;"><IconTopic /></el-icon> 选题管理</h2>
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" size="small" @click="refreshAll"><el-icon style="vertical-align:-2px;"><IconRefresh /></el-icon> 批量刷新</el-button>
|
||||
<el-button type="primary" size="small" @click="openCreateDialog"><el-icon style="vertical-align:-2px;"><IconPlus /></el-icon> 新增选题</el-button>
|
||||
<el-button size="small" @click="refreshAll"><el-icon style="vertical-align:-2px;"><IconRefresh /></el-icon> 批量刷新</el-button>
|
||||
<el-button size="small" @click="toggleSelectAll">{{ selectAllLabel }}</el-button>
|
||||
<el-button type="success" size="small" @click="triggerGenerateSelected" :disabled="selectedTopicIds.length === 0"><el-icon style="vertical-align:-2px;"><IconPlus /></el-icon> 批量创作</el-button>
|
||||
<el-button type="warning" size="small" @click="triggerReviewSelected" :disabled="selectedTopicIds.length === 0"><el-icon style="vertical-align:-2px;"><IconSearch /></el-icon> 批量审查</el-button>
|
||||
<el-button type="success" size="small" @click="triggerGenerateSelected" :disabled="selectedTopicIds.length === 0"> 批量创作</el-button>
|
||||
<el-button type="warning" size="small" @click="triggerReviewSelected" :disabled="selectedTopicIds.length === 0"> 批量审查</el-button>
|
||||
<span v-if="selectedTopicIds.length > 0" class="selected-count">已选 {{ selectedTopicIds.length }} 项</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -162,6 +163,82 @@
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<el-dialog v-model="createDialogVisible" :title="createStep === 1 ? '新增选题' : 'AI 分析结果'" width="680px" :close-on-click-modal="false" @close="resetCreateDialog">
|
||||
<template v-if="createStep === 1">
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="选题思路 / 内容描述">
|
||||
<el-input type="textarea" :rows="5" v-model="createForm.raw_input" placeholder="描述你想写的选题方向,可以是大致想法、关键词、或者一句话需求 例如:想写一篇关于旧物换补贴的实操指南,结合最近的碳普惠政策" />
|
||||
</el-form-item>
|
||||
<el-form-item label="参考链接(可选,每行一个)">
|
||||
<el-input type="textarea" :rows="3" v-model="createForm.reference_links" placeholder="https://... https://..." />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属领域(可选)">
|
||||
<el-select v-model="createForm.field_id" placeholder="选择领域" clearable style="width:100%;">
|
||||
<el-option v-for="f in fieldOptions" :key="f.id" :label="f.name" :value="f.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="选题标题">
|
||||
<el-input v-model="createResult.title" />
|
||||
</el-form-item>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="内容形式">
|
||||
<el-select v-model="createResult.format" style="width:100%;">
|
||||
<el-option label="趋势洞察" value="趋势洞察" />
|
||||
<el-option label="实操指南" value="实操指南" />
|
||||
<el-option label="对比分析" value="对比分析" />
|
||||
<el-option label="案例解读" value="案例解读" />
|
||||
<el-option label="观点讨论" value="观点讨论" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="优先级">
|
||||
<el-select v-model="createResult.priority" style="width:100%;">
|
||||
<el-option label="高" value="高" />
|
||||
<el-option label="中" value="中" />
|
||||
<el-option label="低" value="低" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="核心概念">
|
||||
<el-input v-model="createResult.core_concept" />
|
||||
</el-form-item>
|
||||
<el-form-item label="受众痛点">
|
||||
<el-input v-model="createResult.audience_pain" />
|
||||
</el-form-item>
|
||||
<el-form-item label="独特角度">
|
||||
<el-input v-model="createResult.unique_angle" />
|
||||
</el-form-item>
|
||||
<el-form-item label="标签(回车添加)">
|
||||
<el-select v-model="createResult.tags" multiple filterable allow-create default-first-option style="width:100%;" placeholder="输入标签后回车添加">
|
||||
<el-option v-for="t in createResult.tags" :key="t" :label="t" :value="t" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div style="display:flex; justify-content:space-between; width:100%;">
|
||||
<div>
|
||||
<el-button v-if="createStep === 2" size="small" @click="createStep = 1">← 返回编辑输入</el-button>
|
||||
<el-button v-else @click="createDialogVisible = false">取消</el-button>
|
||||
</div>
|
||||
<div v-if="createStep === 1">
|
||||
<el-button type="primary" @click="runAiAnalysis" :loading="analyzing">AI 智能分析</el-button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-button @click="saveCreatedTopic('list')">仅保存到选题列表</el-button>
|
||||
<el-button type="success" @click="saveCreatedTopic('create')" :loading="saving">保存并创作</el-button>
|
||||
<el-button type="warning" @click="saveCreatedTopic('review')" :loading="saving">保存并审查</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="publishDialogVisible" title="发布确认" width="420px" :close-on-click-modal="false">
|
||||
<div v-if="publishTopic">
|
||||
<div style="margin-bottom:16px;">
|
||||
@@ -253,10 +330,14 @@ const TopicsApp = {
|
||||
publishPlatforms: { zhihu: true, wechat: true, xiaohongshu: true },
|
||||
publishing: false,
|
||||
savingContent: false,
|
||||
currentPage: 1, pageSize: 10,
|
||||
sortField: 'created_at', sortOrder: 'ascending',
|
||||
showSearch: false,
|
||||
searchForm: { id: '', title: '', field: '', createdStart: null, createdEnd: null, generatedStart: null, generatedEnd: null, reviewedStart: null, reviewedEnd: null, publishedStart: null, publishedEnd: null }
|
||||
currentPage: 1, pageSize: 10,
|
||||
sortField: 'created_at', sortOrder: 'ascending',
|
||||
showSearch: false,
|
||||
searchForm: { id: '', title: '', field: '', createdStart: null, createdEnd: null, generatedStart: null, generatedEnd: null, reviewedStart: null, reviewedEnd: null, publishedStart: null, publishedEnd: null },
|
||||
createDialogVisible: false, createStep: 1, analyzing: false, saving: false,
|
||||
fieldOptions: [],
|
||||
createForm: { raw_input: '', reference_links: '', field_id: null },
|
||||
createResult: { title: '', format: '趋势洞察', core_concept: '', audience_pain: '', unique_angle: '', tags: [], priority: '中' }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -558,6 +639,76 @@ const TopicsApp = {
|
||||
this.$message.error('删除失败: ' + msg);
|
||||
}
|
||||
},
|
||||
async openCreateDialog() {
|
||||
this.createDialogVisible = true;
|
||||
this.createStep = 1;
|
||||
this.createForm = { raw_input: '', reference_links: '', field_id: null };
|
||||
this.createResult = { title: '', format: '趋势洞察', core_concept: '', audience_pain: '', unique_angle: '', tags: [], priority: '中' };
|
||||
try {
|
||||
this.fieldOptions = await this.api('/api/topic-config/fields') || [];
|
||||
} catch (e) { this.fieldOptions = []; }
|
||||
},
|
||||
resetCreateDialog() {
|
||||
this.createStep = 1; this.analyzing = false; this.saving = false;
|
||||
},
|
||||
async runAiAnalysis() {
|
||||
if (!this.createForm.raw_input.trim()) { this.$message.warning('请输入选题思路'); return; }
|
||||
this.analyzing = true;
|
||||
try {
|
||||
const refs = this.createForm.reference_links.split('\n').map(s => s.trim()).filter(Boolean);
|
||||
const data = await this.api('/api/topics/analyze', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
raw_input: this.createForm.raw_input,
|
||||
reference_links: refs,
|
||||
field_id: this.createForm.field_id || null
|
||||
})
|
||||
});
|
||||
this.createResult.title = data.title || '';
|
||||
this.createResult.format = data.format || '趋势洞察';
|
||||
this.createResult.core_concept = data.core_concept || '';
|
||||
this.createResult.audience_pain = data.audience_pain || '';
|
||||
this.createResult.unique_angle = data.unique_angle || '';
|
||||
this.createResult.tags = data.tags || [];
|
||||
this.createResult.priority = data.priority || '中';
|
||||
this.createStep = 2;
|
||||
this.$message.success('AI 分析完成,请确认选题信息');
|
||||
} catch (e) { this.$message.error('AI 分析失败: ' + (e.message || '未知错误')); }
|
||||
finally { this.analyzing = false; }
|
||||
},
|
||||
async saveCreatedTopic(action) {
|
||||
this.saving = true;
|
||||
try {
|
||||
const body = {
|
||||
title: this.createResult.title,
|
||||
field_id: this.createForm.field_id || null,
|
||||
format: this.createResult.format,
|
||||
core_concept: this.createResult.core_concept,
|
||||
audience_pain: this.createResult.audience_pain,
|
||||
unique_angle: this.createResult.unique_angle,
|
||||
tags: this.createResult.tags,
|
||||
priority: this.createResult.priority,
|
||||
};
|
||||
const topic = await this.api('/api/topics', { method: 'POST', body: JSON.stringify(body) });
|
||||
const topicId = topic.id;
|
||||
this.createDialogVisible = false;
|
||||
this.$message.success(`选题 ${topicId} 创建成功`);
|
||||
await this.fetchTopics();
|
||||
if (action === 'create') {
|
||||
try {
|
||||
await this.api('/api/tasks/run-creator?topic_id=' + topicId, { method: 'POST' });
|
||||
this.$message.success(`创作任务已启动: ${topic.title}`);
|
||||
} catch (e) { this.$message.error(`创作启动失败: ${e.message}`); }
|
||||
} else if (action === 'review') {
|
||||
try {
|
||||
await this.api('/api/system/review/run', { method: 'POST', body: JSON.stringify({ topic_ids: [topicId] }) });
|
||||
this.$message.success(`审查已启动: ${topic.title}`);
|
||||
} catch (e) { this.$message.error(`审查启动失败: ${e.message}`); }
|
||||
}
|
||||
await this.fetchTodayCount();
|
||||
} catch (e) { this.$message.error('保存选题失败: ' + (e.message || '未知错误')); }
|
||||
finally { this.saving = false; }
|
||||
},
|
||||
switchPlatform(platform) {
|
||||
if (this.editing) this.cancelEdit();
|
||||
this.previewPlatform = platform;
|
||||
|
||||
Reference in New Issue
Block a user