Phase1: 效果数据真实化
- 替换scheduler.py中假随机数metrics_sync为知乎API自动获取 - 新增 POST /api/metrics/zhihu-fetch 知乎公开数据API端点 - metrics.html新增「数据录入」tab:手动录入表单+已有数据列表+知乎自动获取 - gitignore清理已跟踪的生成文件(outlines/research/images/cache)
This commit is contained in:
@@ -4,6 +4,9 @@ from sqlalchemy import func, desc
|
||||
from typing import List, Optional
|
||||
from datetime import datetime, timedelta, date
|
||||
import logging
|
||||
import re
|
||||
import requests as http_requests
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import ContentMetrics, Topic, ContentCalendar
|
||||
@@ -302,4 +305,87 @@ def recommend_topics_from_metrics(
|
||||
return recommendations[:limit]
|
||||
except Exception as e:
|
||||
logging.getLogger(__name__).exception(f"推荐选题失败: {e}")
|
||||
return []
|
||||
return []
|
||||
|
||||
|
||||
_ZHIHU_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
|
||||
|
||||
def _extract_zhihu_post_id(url: str) -> str:
|
||||
m = re.search(r'zhuanlan\.zhihu\.com/p/(\d+)', url)
|
||||
if m:
|
||||
return m.group(1)
|
||||
m = re.search(r'zhihu\.com/question/\d+/answer/(\d+)', url)
|
||||
if m:
|
||||
return m.group(1)
|
||||
raise ValueError("无法从URL中提取知乎文章/回答ID")
|
||||
|
||||
|
||||
class ZhihuFetchRequest(BaseModel):
|
||||
topic_id: str
|
||||
zhihu_url: str
|
||||
|
||||
|
||||
@router.post("/zhihu-fetch")
|
||||
def fetch_zhihu_metrics(
|
||||
data: ZhihuFetchRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
"""从知乎专栏/回答公开API自动获取阅读/点赞/评论数据"""
|
||||
topic = db.query(Topic).filter(Topic.id == data.topic_id).first()
|
||||
if not topic:
|
||||
raise HTTPException(status_code=404, detail="选题不存在")
|
||||
|
||||
post_id = _extract_zhihu_post_id(data.zhihu_url)
|
||||
api_url = f"https://zhuanlan.zhihu.com/api/posts/{post_id}"
|
||||
|
||||
try:
|
||||
resp = http_requests.get(api_url, headers={"User-Agent": _ZHIHU_UA}, timeout=15)
|
||||
if resp.status_code == 404:
|
||||
api_url = f"https://www.zhihu.com/api/v4/answers/{post_id}"
|
||||
resp = http_requests.get(api_url, headers={"User-Agent": _ZHIHU_UA}, timeout=15)
|
||||
if resp.status_code != 200:
|
||||
raise HTTPException(status_code=502, detail=f"知乎API返回 {resp.status_code}")
|
||||
|
||||
raw = resp.json()
|
||||
platform = "zhihu"
|
||||
|
||||
existing = db.query(ContentMetrics).filter(
|
||||
ContentMetrics.topic_id == data.topic_id,
|
||||
ContentMetrics.platform == platform
|
||||
).first()
|
||||
|
||||
metric_data = {
|
||||
"views": raw.get("voteup_count", raw.get("views_count", 0)),
|
||||
"likes": raw.get("voteup_count", 0),
|
||||
"favorites": raw.get("favorite_count", 0),
|
||||
"comments": raw.get("comment_count", raw.get("comments_count", 0)),
|
||||
"shares": raw.get("share_count", 0) or raw.get("shared_count", 0),
|
||||
"publish_url": data.zhihu_url,
|
||||
"data_snapshot": raw,
|
||||
}
|
||||
|
||||
if existing:
|
||||
for k, v in metric_data.items():
|
||||
setattr(existing, k, v)
|
||||
existing.last_fetched = datetime.now()
|
||||
db.commit()
|
||||
db.refresh(existing)
|
||||
return {"ok": True, "source": "updated", "data": ContentMetricsResponse.model_validate(existing)}
|
||||
else:
|
||||
metric = ContentMetrics(
|
||||
topic_id=data.topic_id,
|
||||
platform=platform,
|
||||
**metric_data,
|
||||
last_fetched=datetime.now()
|
||||
)
|
||||
db.add(metric)
|
||||
db.commit()
|
||||
db.refresh(metric)
|
||||
return {"ok": True, "source": "created", "data": ContentMetricsResponse.model_validate(metric)}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=502, detail=f"获取知乎数据失败: {str(e)}")
|
||||
@@ -165,12 +165,12 @@ class TaskScheduler:
|
||||
logger.exception("[Scheduled] Source AI optimization failed: %s", e)
|
||||
|
||||
def _run_metrics_sync(self):
|
||||
"""定时从各平台公开API获取发布文章的效果数据(当前仅支持知乎)"""
|
||||
try:
|
||||
logger.info("[Scheduled] Starting metrics sync...")
|
||||
logger.info("[Scheduled] Starting metrics sync (zhihu auto-fetch)...")
|
||||
from ..database import SessionLocal
|
||||
from ..models import Topic, ContentMetrics, PublishRecord
|
||||
import random, math
|
||||
from datetime import date
|
||||
from ..models import Topic, ContentMetrics
|
||||
import re, requests as http_requests
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
@@ -182,58 +182,51 @@ class TaskScheduler:
|
||||
db.close()
|
||||
return
|
||||
|
||||
random.seed(42)
|
||||
multipliers = {
|
||||
"zhihu": {"v": 1.0, "l": 1.2, "f": 0.6, "c": 1.5, "s": 0.3},
|
||||
"wechat": {"v": 1.8, "l": 0.6, "f": 0.4, "c": 0.3, "s": 2.0},
|
||||
"xiaohongshu": {"v": 2.5, "l": 1.5, "f": 1.8, "c": 1.0, "s": 1.5},
|
||||
}
|
||||
ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||
count = 0
|
||||
for topic in topics:
|
||||
platforms = set()
|
||||
records = db.query(PublishRecord).filter(
|
||||
PublishRecord.topic_id == topic.id,
|
||||
PublishRecord.action == "publish",
|
||||
PublishRecord.status == "success"
|
||||
).all()
|
||||
for rec in records:
|
||||
platforms.add(rec.platform)
|
||||
if not platforms:
|
||||
platforms = {"zhihu", "wechat", "xiaohongshu"}
|
||||
days = max(1, (date.today() - (topic.published_at or date.today())).days)
|
||||
quality = (topic.compliance_score or 70) / 100.0
|
||||
for plat in platforms:
|
||||
if plat not in multipliers:
|
||||
platform_urls = topic.platform_urls or {}
|
||||
zhihu_url = platform_urls.get("zhihu", "")
|
||||
if not zhihu_url:
|
||||
continue
|
||||
m = re.search(r'zhuanlan\.zhihu\.com/p/(\d+)', zhihu_url)
|
||||
if not m:
|
||||
continue
|
||||
post_id = m.group(1)
|
||||
api_url = f"https://zhuanlan.zhihu.com/api/posts/{post_id}"
|
||||
try:
|
||||
resp = http_requests.get(api_url, headers={"User-Agent": ua}, timeout=10)
|
||||
if resp.status_code != 200:
|
||||
continue
|
||||
m = multipliers[plat]
|
||||
base = random.randint(30, 200)
|
||||
growth = 1 + math.log(days + 1, 2) * 0.5
|
||||
views = int(base * m["v"] * growth)
|
||||
likes = int(views * quality * 0.08 * m["l"])
|
||||
favs = int(likes * 0.5 * m["f"])
|
||||
comm = int(views * quality * 0.02 * m["c"])
|
||||
shar = int(views * quality * 0.03 * m["s"])
|
||||
raw = resp.json()
|
||||
existing = db.query(ContentMetrics).filter(
|
||||
ContentMetrics.topic_id == topic.id,
|
||||
ContentMetrics.platform == plat
|
||||
ContentMetrics.platform == "zhihu"
|
||||
).first()
|
||||
metric_data = {
|
||||
"views": raw.get("voteup_count", raw.get("views_count", 0)),
|
||||
"likes": raw.get("voteup_count", 0),
|
||||
"favorites": raw.get("favorite_count", 0),
|
||||
"comments": raw.get("comment_count", raw.get("comments_count", 0)),
|
||||
"shares": raw.get("share_count", 0),
|
||||
"last_fetched": datetime.now(),
|
||||
"publish_url": zhihu_url,
|
||||
"data_snapshot": raw,
|
||||
}
|
||||
if existing:
|
||||
existing.views = views
|
||||
existing.likes = likes
|
||||
existing.favorites = favs
|
||||
existing.comments = comm
|
||||
existing.shares = shar
|
||||
existing.last_fetched = datetime.now()
|
||||
for k, v in metric_data.items():
|
||||
setattr(existing, k, v)
|
||||
else:
|
||||
db.add(ContentMetrics(
|
||||
topic_id=topic.id, platform=plat,
|
||||
views=views, likes=likes, favorites=favs,
|
||||
comments=comm, shares=shar, last_fetched=datetime.now()
|
||||
))
|
||||
db.add(ContentMetrics(topic_id=topic.id, platform="zhihu", **metric_data))
|
||||
count += 1
|
||||
db.commit()
|
||||
except Exception:
|
||||
continue
|
||||
if count:
|
||||
db.commit()
|
||||
logger.info("[Scheduled] Metrics sync completed: synced %d zhihu articles", count)
|
||||
else:
|
||||
logger.info("[Scheduled] Metrics sync: no zhihu articles to sync")
|
||||
db.close()
|
||||
logger.info("[Scheduled] Metrics sync completed: %d entries for %d topics", count, len(topics))
|
||||
except Exception as e:
|
||||
logger.exception("[Scheduled] Metrics sync failed: %s", e)
|
||||
|
||||
|
||||
@@ -46,12 +46,17 @@
|
||||
</uni-nav>
|
||||
<div class="main-content">
|
||||
<main class="content-area">
|
||||
<h2 style="font-size: 24px; font-weight: 700; margin-bottom: 24px; color: #303133;"><el-icon style="vertical-align:-2px;"><IconDashboard /></el-icon> 数据分析</h2>
|
||||
<div v-if="loadingDashboard" style="text-align:center;padding:40px 0;color:#909399;">
|
||||
<el-icon style="font-size:32px;margin-bottom:12px;" class="is-loading"><IconLoading /></el-icon>
|
||||
<div>加载中...</div>
|
||||
</div>
|
||||
<div class="card page-fade">
|
||||
<div class="filter-bar" style="margin-bottom:20px;">
|
||||
<el-button size="default" :type="activeTab === 'dashboard' ? 'primary' : ''" @click="activeTab = 'dashboard'">数据概览</el-button>
|
||||
<el-button size="default" :type="activeTab === 'entry' ? 'primary' : ''" @click="activeTab = 'entry'; switchToEntry()">数据录入</el-button>
|
||||
</div>
|
||||
<div v-if="activeTab === 'dashboard'">
|
||||
<h2 style="font-size: 24px; font-weight: 700; margin-bottom: 24px; color: #303133;"><el-icon style="vertical-align:-2px;"><IconDashboard /></el-icon> 数据分析</h2>
|
||||
<div v-if="loadingDashboard" style="text-align:center;padding:40px 0;color:#909399;">
|
||||
<el-icon style="font-size:32px;margin-bottom:12px;" class="is-loading"><IconLoading /></el-icon>
|
||||
<div>加载中...</div>
|
||||
</div>
|
||||
<div class="card page-fade">
|
||||
<h3 style="font-size: 18px; margin-bottom: 16px;"><el-icon style="vertical-align:-2px;"><IconDashboard /></el-icon> 选题状态分布</h3>
|
||||
<div style="display: flex; gap: 20px; flex-wrap: wrap; align-items:center;">
|
||||
<div style="flex:1;min-width:200px;">
|
||||
@@ -169,6 +174,104 @@
|
||||
</div>
|
||||
<div v-else class="empty-state"><el-icon style="font-size:48px;color:#c0c4cc;"><IconInfo /></el-icon><div class="empty-text">暂无选题推荐</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'entry'" class="page-fade">
|
||||
<h2 style="font-size: 24px; font-weight: 700; margin-bottom: 24px; color: #303133;"><el-icon style="vertical-align:-2px;"><IconEdit /></el-icon> 数据录入</h2>
|
||||
|
||||
<div class="card" style="margin-bottom:20px;">
|
||||
<h3 style="font-size:16px;margin-bottom:16px;">手动录入</h3>
|
||||
<el-form :model="entryForm" label-width="100px" size="small">
|
||||
<el-form-item label="选题">
|
||||
<el-select v-model="entryForm.topic_id" filterable placeholder="选择选题" style="width:100%;">
|
||||
<el-option v-for="t in allTopics" :key="t.id" :label="t.id + ' - ' + t.title" :value="t.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="平台">
|
||||
<el-select v-model="entryForm.platform" placeholder="选择平台" style="width:200px;">
|
||||
<el-option label="知乎" value="zhihu"></el-option>
|
||||
<el-option label="微信公众号" value="wechat"></el-option>
|
||||
<el-option label="小红书" value="xiaohongshu"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发布链接">
|
||||
<el-input v-model="entryForm.publish_url" placeholder="https://..."></el-input>
|
||||
</el-form-item>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="6"><el-form-item label="阅读"><el-input-number v-model="entryForm.views" :min="0" style="width:100%;"></el-input-number></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="点赞"><el-input-number v-model="entryForm.likes" :min="0" style="width:100%;"></el-input-number></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="收藏"><el-input-number v-model="entryForm.favorites" :min="0" style="width:100%;"></el-input-number></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="评论"><el-input-number v-model="entryForm.comments" :min="0" style="width:100%;"></el-input-number></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="6"><el-form-item label="分享"><el-input-number v-model="entryForm.shares" :min="0" style="width:100%;"></el-input-number></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitEntry" :loading="submitting">保存</el-button>
|
||||
<el-button @click="resetEntryForm">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-bottom:20px;">
|
||||
<h3 style="font-size:16px;margin-bottom:16px;">从知乎自动获取</h3>
|
||||
<el-form :model="zhihuForm" label-width="100px" size="small" @submit.prevent="fetchZhihu">
|
||||
<el-form-item label="选题">
|
||||
<el-select v-model="zhihuForm.topic_id" filterable placeholder="选择选题" style="width:100%;">
|
||||
<el-option v-for="t in allTopics" :key="t.id" :label="t.id + ' - ' + t.title" :value="t.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="知乎链接">
|
||||
<el-input v-model="zhihuForm.zhihu_url" placeholder="https://zhuanlan.zhihu.com/p/xxxxxx"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="fetchZhihu" :loading="zhihuLoading">获取数据</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="toolbar" style="margin-bottom:12px;">
|
||||
<span style="font-size:15px;font-weight:600;">已有数据</span>
|
||||
<span style="font-size:13px;color:#909399;margin-left:8px;">共 {{ allEntries.length }} 条</span>
|
||||
<el-button size="small" style="margin-left:12px;" @click="fetchEntries">刷新</el-button>
|
||||
</div>
|
||||
<div v-if="entriesLoading" style="text-align:center;padding:20px;color:#909399;">加载中...</div>
|
||||
<template v-else-if="allEntries.length === 0">
|
||||
<div class="empty-state" style="padding:30px 0;">
|
||||
<el-icon style="font-size:48px;color:#c0c4cc;"><IconDashboard /></el-icon>
|
||||
<div class="empty-text">暂无数据,请先录入</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="metrics-table">
|
||||
<el-table :data="allEntries" stripe size="small" style="width:100%;">
|
||||
<el-table-column prop="topic_id" label="选题ID" width="80"></el-table-column>
|
||||
<el-table-column label="平台" width="100">
|
||||
<template #default="scope">{{ getPlatformName(scope.row.platform) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="views" label="阅读" width="70"></el-table-column>
|
||||
<el-table-column prop="likes" label="点赞" width="70"></el-table-column>
|
||||
<el-table-column prop="favorites" label="收藏" width="70"></el-table-column>
|
||||
<el-table-column prop="comments" label="评论" width="70"></el-table-column>
|
||||
<el-table-column prop="shares" label="分享" width="70"></el-table-column>
|
||||
<el-table-column label="互动率" width="80">
|
||||
<template #default="scope">{{ scope.row.engagement_rate || 0 }}%</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="获取时间" width="150">
|
||||
<template #default="scope">{{ formatDate(scope.row.last_fetched) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="editEntry(scope.row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteEntry(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
@@ -182,6 +285,7 @@ const MetricsApp = {
|
||||
currentUser: { username: '' },
|
||||
isAdmin: false,
|
||||
isLoggedIn: false,
|
||||
activeTab: 'dashboard',
|
||||
loadingDashboard: false,
|
||||
dashboard: { total_topics: 0, topics_by_status: {}, total_published: 0, total_views: 0, total_likes: 0, avg_engagement_rate: 0, top_topics: [], recent_metrics: [] },
|
||||
trendDays: 30,
|
||||
@@ -189,7 +293,15 @@ const MetricsApp = {
|
||||
maxViews: 1,
|
||||
platformData: [],
|
||||
recommendations: [],
|
||||
chartInstances: {}
|
||||
chartInstances: {},
|
||||
allTopics: [],
|
||||
allEntries: [],
|
||||
entriesLoading: false,
|
||||
submitting: false,
|
||||
zhihuLoading: false,
|
||||
entryForm: { topic_id: '', platform: 'zhihu', publish_url: '', views: 0, likes: 0, favorites: 0, comments: 0, shares: 0 },
|
||||
zhihuForm: { topic_id: '', zhihu_url: '' },
|
||||
editingEntryId: null,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -354,7 +466,92 @@ const MetricsApp = {
|
||||
if (res.ok) this.recommendations = await res.json();
|
||||
else { const d = await res.json().catch(() => ({})); throw new Error(d.detail || '加载失败'); }
|
||||
} catch (e) { console.error(e); this.$message.error('加载推荐失败: ' + e.message); }
|
||||
}
|
||||
},
|
||||
async fetchAllTopics() {
|
||||
try {
|
||||
const token = localStorage.getItem('authToken');
|
||||
const res = await fetch('/api/topics?limit=200', { headers: { 'Authorization': 'Bearer ' + token } });
|
||||
if (res.ok) { const data = await res.json(); this.allTopics = data.topics || data || []; }
|
||||
} catch (e) { console.error(e); }
|
||||
},
|
||||
async fetchEntries() {
|
||||
this.entriesLoading = true;
|
||||
try {
|
||||
const token = localStorage.getItem('authToken');
|
||||
const res = await fetch('/api/metrics/entries?limit=200', { headers: { 'Authorization': 'Bearer ' + token } });
|
||||
if (res.ok) this.allEntries = await res.json();
|
||||
} catch (e) { console.error(e); this.$message.error('加载数据失败'); }
|
||||
finally { this.entriesLoading = false; }
|
||||
},
|
||||
resetEntryForm() {
|
||||
this.entryForm = { topic_id: '', platform: 'zhihu', publish_url: '', views: 0, likes: 0, favorites: 0, comments: 0, shares: 0 };
|
||||
this.editingEntryId = null;
|
||||
},
|
||||
editEntry(row) {
|
||||
this.entryForm = { topic_id: row.topic_id, platform: row.platform, publish_url: row.publish_url || '', views: row.views || 0, likes: row.likes || 0, favorites: row.favorites || 0, comments: row.comments || 0, shares: row.shares || 0 };
|
||||
this.editingEntryId = row.id;
|
||||
this.$message.info('请修改后保存');
|
||||
},
|
||||
async submitEntry() {
|
||||
if (!this.entryForm.topic_id || !this.entryForm.platform) { this.$message.warning('请选择选题和平台'); return; }
|
||||
this.submitting = true;
|
||||
try {
|
||||
const token = localStorage.getItem('authToken');
|
||||
const body = { topic_id: this.entryForm.topic_id, platform: this.entryForm.platform, publish_url: this.entryForm.publish_url, views: this.entryForm.views, likes: this.entryForm.likes, favorites: this.entryForm.favorites, comments: this.entryForm.comments, shares: this.entryForm.shares };
|
||||
if (this.editingEntryId) {
|
||||
const res = await fetch('/api/metrics/entries/' + this.editingEntryId, { method: 'PUT', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
||||
if (!res.ok) throw new Error('更新失败');
|
||||
this.$message.success('已更新');
|
||||
} else {
|
||||
const res = await fetch('/api/metrics/entries', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
||||
if (!res.ok) throw new Error('保存失败');
|
||||
this.$message.success('已保存');
|
||||
}
|
||||
this.resetEntryForm();
|
||||
this.fetchEntries();
|
||||
} catch (e) { console.error(e); this.$message.error(e.message); }
|
||||
finally { this.submitting = false; }
|
||||
},
|
||||
async deleteEntry(id) {
|
||||
try {
|
||||
const token = localStorage.getItem('authToken');
|
||||
const res = await fetch('/api/metrics/entries/' + id, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token } });
|
||||
if (!res.ok) throw new Error('删除失败');
|
||||
this.$message.success('已删除');
|
||||
this.fetchEntries();
|
||||
} catch (e) { console.error(e); this.$message.error(e.message); }
|
||||
},
|
||||
async fetchZhihu() {
|
||||
if (!this.zhihuForm.topic_id || !this.zhihuForm.zhihu_url) { this.$message.warning('请选择选题并填写知乎链接'); return; }
|
||||
this.zhihuLoading = true;
|
||||
try {
|
||||
const token = localStorage.getItem('authToken');
|
||||
const res = await fetch('/api/metrics/zhihu-fetch', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify(this.zhihuForm) });
|
||||
if (!res.ok) { const d = await res.json().catch(() => ({ detail: '请求失败' })); throw new Error(d.detail); }
|
||||
const result = await res.json();
|
||||
this.$message.success('已从知乎获取数据');
|
||||
this.fetchEntries();
|
||||
if (result.data) {
|
||||
this.entryForm.topic_id = result.data.topic_id;
|
||||
this.entryForm.platform = result.data.platform;
|
||||
this.entryForm.publish_url = result.data.publish_url || '';
|
||||
this.entryForm.views = result.data.views || 0;
|
||||
this.entryForm.likes = result.data.likes || 0;
|
||||
this.entryForm.favorites = result.data.favorites || 0;
|
||||
this.entryForm.comments = result.data.comments || 0;
|
||||
this.entryForm.shares = result.data.shares || 0;
|
||||
}
|
||||
} catch (e) { console.error(e); this.$message.error(e.message); }
|
||||
finally { this.zhihuLoading = false; }
|
||||
},
|
||||
formatDate(dt) {
|
||||
if (!dt) return '-';
|
||||
try { return new Date(dt).toLocaleString('zh-CN'); } catch { return dt; }
|
||||
},
|
||||
switchToEntry() {
|
||||
this.fetchAllTopics();
|
||||
this.fetchEntries();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.checkAuth();
|
||||
|
||||
Reference in New Issue
Block a user