Compare commits
16 Commits
e9cd0374f4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f9547ba21f | |||
| 7e953afbd2 | |||
| 3d596275d5 | |||
| a4242ada0e | |||
| d11d7f4980 | |||
| 57e6e16c11 | |||
| 187b414193 | |||
| 56c9376c58 | |||
| 302efd5f2d | |||
| 897f72012f | |||
| 0258a9af9f | |||
| 9058539bae | |||
| 9fba863ce7 | |||
| 45ad5443c1 | |||
| ef289b18d1 | |||
| f1e6c11505 |
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sessionID": "ses_1dede8a7affeG6DxtfPUOviXrp",
|
||||
"updatedAt": "2026-06-17T08:18:52.213Z",
|
||||
"updatedAt": "2026-07-04T02:48:32.809Z",
|
||||
"sources": {
|
||||
"background-task": {
|
||||
"state": "active",
|
||||
"reason": "2 background task(s) active",
|
||||
"updatedAt": "2026-06-17T08:18:52.213Z"
|
||||
"reason": "1 background task(s) active",
|
||||
"updatedAt": "2026-07-04T02:48:32.809Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
+329
-2
@@ -3,7 +3,7 @@
|
||||
> 本文件为项目进度唯一真理源,所有进度信息以此为准。
|
||||
> 其他文档中的进度描述一律以本文为准。
|
||||
|
||||
**最后更新**:2026-06-21 (v24)
|
||||
**最后更新**:2026-07-04 (v26)
|
||||
|
||||
---
|
||||
|
||||
@@ -234,7 +234,146 @@
|
||||
|
||||
---
|
||||
|
||||
## 六、归档日志
|
||||
## 十二、内容策略聚焦改造(v25 · 2026-06-23)
|
||||
|
||||
### 从「6领域分散」到「3领域聚焦」
|
||||
|
||||
**背景**:此前内容覆盖可持续生活、数字游民、个人成长等 6 个领域,资源分散、品牌辨识度低。v25 聚焦「宇之然」品牌核心方向:AI 与前沿科技。
|
||||
|
||||
**聚焦的 3 个领域:**
|
||||
| 领域 | 方向 | 覆盖话题 |
|
||||
|------|------|---------|
|
||||
| AI与效率 🤖 | AI 工具、工作流、效率方法 | AI工具实测、AI前沿资讯、效率方法论 |
|
||||
| 科技人文 🔬 | AI 伦理、科技反思 | 数据隐私、人机关系、数字生活反思 |
|
||||
| 未来工作方式 💼 | 远程工作、职业转型 | AI时代职场、一人企业、技能升级 |
|
||||
|
||||
### Phase 1 — 代码改造(6 个文件)
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `initial_data.py` | TopicField 从 6 个缩减为 3 个;CollectorCategory 从 8 个环保类改为 5 个 AI/科技类;PlatformConfig allowed_tags 全部替换为 AI/效率/科技标签 |
|
||||
| `prompt_loader.py` | `sources_optimization` 提示词从"可持续生活领域"改为"AI与科技人文领域" |
|
||||
| `config/sources.yaml` | 全部信息源替换为 AI/科技相关(36氪RSS、少数派RSS、5 组 AI 搜索词、1 个本地案例库) |
|
||||
| `collector.py` | `DEFAULT_CHINA_PAINS` 从 8 个(7 环保 + 1 AI)改为 5 个 AI 焦点;`map_category_to_field` 返回 AI 领域;移除 4 处"可持续性"日志/默认值 |
|
||||
| `topic_selector.py` | `DEFAULT_TREND_DOMAIN_MAP` 从 12 条(含可持续生活/个人成长/副业/家庭教育)改为 11 条纯 AI/科技映射 |
|
||||
| `migrate_content_focus.py` | **新建** — Phase 2 DB 迁移脚本 |
|
||||
|
||||
### Phase 2 — DB 迁移
|
||||
|
||||
| 操作 | 影响 |
|
||||
|------|------|
|
||||
| TopicField | 更新 3 个新领域字段(排序/描述/图标),停用 5 个旧领域(可持续生活系统/可持续生活/数字游民/个人成长/个人知识工厂) |
|
||||
| Topic | 47 个 Topic 中 43 个已迁移到新领域(4 个无法映射的归入"AI与效率") |
|
||||
| Article | 删除 15 篇与旧可持续领域关联的旧文章 |
|
||||
| CollectorCategory | 创建 3 个新分类(AI前沿资讯/AI工具实测/AI与职场),更新 2 个(AI生活化/科技人文),停用 12 个旧分类(循环消费/低碳出行/干净饮食/零浪费生活/绿色家电与节能/碳普惠/环保科技产品/自我丰容/适我主义/情绪价值与心理健康/沉浸式新生活/数字游民与自由职业) |
|
||||
| CollectorSource | 停用 1 个旧源,创建 2 个新 AI 搜索源 |
|
||||
| PlatformConfig | 3 个平台(知乎/微信/小红书)的 allowed_tags 全部替换为 AI/科技标签 |
|
||||
| TrendFieldMapping | 同步 11 条新映射,停用旧映射 |
|
||||
|
||||
### Phase 3 — 文件清理
|
||||
|
||||
| 项目 | 数量 | 释放空间 |
|
||||
|------|------|---------|
|
||||
| 日志文件 (`automation/logs/`) | 400 个 | 2.5 MB |
|
||||
| 旧选题卡片 (`content/ideas/`) | 11 个文件 | 96 KB |
|
||||
| 可持续采集原始数据 (`sustainability_raw/`) | 多子目录 | 224 KB |
|
||||
| 旧数据文件 (`sustainability_cases.json/topics.json`) | 2 个 | - |
|
||||
| 旧草稿/大纲/研究笔记 (`drafts/outlines/research/`) | 91 个文件 | 740 KB |
|
||||
| 通知记录 (`notifications/`) | 1 个 | 4 KB |
|
||||
|
||||
### Phase 4 — 重启验证
|
||||
|
||||
- 服务运行中:HTTP 200 ✅
|
||||
- API 认证正常 ✅
|
||||
- TopicFields 仅返回 3 个活跃领域 ✅
|
||||
- Topic 总计 47 个,全部关联新领域 ✅
|
||||
- Articles 已清理(0 篇过时文章)✅
|
||||
- CollectorCategories 仅 5 个 AI 分类活跃 ✅
|
||||
|
||||
### 关键架构决策
|
||||
|
||||
- `collector.py` 类名(SustainabilityCollector/SustainabilitySource/SustainabilityCase)保留未改——减少中断风险,功能已从 sustainable 转向 AI/tech,重命名需要跨 20+ 文件修改
|
||||
- DB 种子数据按 `if count() == 0` 判断,已有数据的库通过独立迁移脚本处理
|
||||
- 旧数据优先保留(硬删除可能影响引用完整性),标记 `is_active=False` 而非删除
|
||||
|
||||
### 品牌调性与内容深化(v25.1 · 2026-06-23 第二次迭代)
|
||||
|
||||
**问题诊断**:第一次聚焦改造后,内容方向虽已收敛到 AI 3 领域,但存在品牌割裂("宇之然"调性偏人文自然 vs 内容全是纯工具测评)、AI 赛道竞争红海、平台策略错配(公众号叙事体不适合写 AI 工具)等深层问题。
|
||||
|
||||
**Phase A — 品牌调性注入(最核心改动)**
|
||||
|
||||
| prompt | 改动 | 预期效果 |
|
||||
|--------|------|---------|
|
||||
| `section_expansion_zhihu` | 加入独家数据要求+观点交锋,不只是介绍工具而是探讨技术如何改变人 | 在拥挤的 AI 知乎赛道上做出壁垒 |
|
||||
| `section_expansion_wechat` | 从「AI 工具叙事」→「科技人文思辨」:用「我」口吻写 AI 时代的困惑、顿悟或观察 | 对齐品牌调性,摆脱"AI 测评不适合叙事体"的结构性矛盾 |
|
||||
| `section_expansion_xiaohongshu` | 每个结论配亲测数据/对比/结果 | 用真实数据对抗同质化 |
|
||||
| `title_optimize_zhihu` | 增加观点交锋感+立场态度 | 从"工具文标题"→"辩论帖标题" |
|
||||
| `title_optimize_wechat` | 改为科技叙事方向,参考风格:「AI帮我写周报的第3个月,我终于发现哪里不对了」 | 对齐公众号新定位 |
|
||||
| `title_optimize_xhs` | 每条标题含数字/时间/效果结果感 | 提高点击率 |
|
||||
|
||||
**Phase B — SEO/GEO 长尾关键词**
|
||||
|
||||
- `collector.py`: `DEFAULT_CHINA_PAINS` 每个痛点扩展 3-5 个长尾问题("AI写周报""AI提示词怎么写""35岁学AI来得及吗")
|
||||
- `topic_selector.py`: `DEFAULT_TREND_DOMAIN_MAP` 从 11 条→18 条(新增 Prompt/提示词/AI编程/AI写作/数据隐私/AI教育/一人企业)
|
||||
|
||||
**Phase C — 差异化信息源**
|
||||
|
||||
在 `sources.yaml` 中新增 5 个与通用 AI 媒体形成区分的源:
|
||||
|
||||
| 新源 | 差异化定位 |
|
||||
|------|-----------|
|
||||
| 机器之心 RSS | AI 学术前沿,区别于大众科技媒体 |
|
||||
| AI 学术论文搜索(arXiv 中文) | 一手论文解读内容源 |
|
||||
| 独立开发者搜索 | 独特的小众 AI 产品视角 |
|
||||
| 行业数据报告搜索 | 自采数据的基础来源 |
|
||||
| AI 产品动态搜索 | 第一手产品更新信息 |
|
||||
|
||||
**Phase D — 栏目化内容机制**
|
||||
|
||||
为「AI工具实测周报」等固定栏目铺平道路:
|
||||
- Topic 模型新增 `series` 字段(VARCHAR, optional, indexed)
|
||||
- API 端点 `/api/topics` 新增 `?series=` 筛选参数
|
||||
- 前端 topics.html 搜索区增加「栏目」下拉筛选器
|
||||
- `database.py` 新增 ALTER TABLE 迁移(`topics.series`)
|
||||
|
||||
**Phase E — 公众号发布流水线**([需提供 WeChat APP_ID/APP_SECRET])
|
||||
- 已安装 `/wechat-mp-publisher` 技能,支持远程 MCP 中转
|
||||
- `wechat.env` 和 `mcp.json` 尚未配置
|
||||
|
||||
### 关键文件变更汇总
|
||||
|
||||
| 文件 | 变更类型 | 说明 |
|
||||
|------|---------|------|
|
||||
| `scripts/prompt_loader.py` | 修改 | 6 个 prompt 品牌调性注入(section_expansion×3 + title_optimize×3) |
|
||||
| `platform/backend/app/initial_data.py` | 修改 | 领域描述人文化、采集搜索词长尾化、平台格式说明更新 |
|
||||
| `scripts/collector.py` | 修改 | DEFAULT_CHINA_PAINS 长尾关键词扩展 |
|
||||
| `scripts/topic_selector.py` | 修改 | DEFAULT_TREND_DOMAIN_MAP 从 11→18 条 |
|
||||
| `config/sources.yaml` | 修改 | 新增机器之心/arXiv/独立开发者/行业报告/AI产品动态 5 个差异源 |
|
||||
| `platform/backend/app/models.py` | 修改 | Topic 模型新增 series 字段 |
|
||||
| `platform/backend/app/schemas.py` | 修改 | TopicBase/TopicCreate/TopicUpdate 新增 series |
|
||||
| `platform/backend/app/api/topics.py` | 修改 | list_topics 新增 series 查询参数 |
|
||||
| `platform/backend/app/database.py` | 修改 | ALTER TABLE 添加 topics.series 列 |
|
||||
| `platform/frontend/topics.html` | 修改 | 搜索区新增栏目下拉筛选器 |
|
||||
| `scripts/migrate_content_focus.py` | 新建 | DB 迁移脚本(Phase 2 遗留) |
|
||||
|
||||
### Phase F — 数据库旧数据清理(2026-06-23 第三次迭代)
|
||||
|
||||
品牌优化完成后,发现旧数据库中存在大量迁移遗留的错配数据——32 个旧选题(循环消费/碳普惠/可持续方向)被 `migrate_content_focus.py` 的 fallback 逻辑错误地分配到了「AI与效率」领域,并持有 66 篇关联文章和 5 个任务。
|
||||
|
||||
| 操作 | 数量 |
|
||||
|------|------|
|
||||
| 删除旧选题(sustainability/carbon 方向) | 32 个 |
|
||||
| 删除关联文章 | 66 篇 |
|
||||
| 删除关联任务 | 5 条 |
|
||||
| 停用 orphan 领域(科技人文交叉/测试) | 2 个 |
|
||||
| 删除 5 个旧 TopicField + 5 条 stale TrendFieldMapping | 10 条 |
|
||||
| **剩余数据** | **15 个选题 / 9 篇文章 / 3 个活跃领域 / 11 条 TrendFieldMapping** |
|
||||
|
||||
所有保留选题均为 AI/tech 方向,与聚焦的 3 领域一致。TopicFields 表从 10 行精简到 3 行,TrendFieldMapping 从 16 行精简到 11 行(全 active),无任何悬空引用。
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
> 过时文档统一移入 `archive/`,防止误导。
|
||||
|
||||
@@ -513,6 +652,53 @@ admin.html 新增「GEO 就绪度」标签页包含:
|
||||
|
||||
---
|
||||
|
||||
## 十三、2026 合规与市场契合升级(v26 · 2026-07-04)
|
||||
|
||||
### 背景:2025-09-01《人工智能生成合成内容标识办法》正式施行
|
||||
微信/小红书/抖音/B站/微博 强制 AI 生成内容**主动声明标识**;未标识/疑似 AI 内容限流、下架、封号。微信 3-27 新规明确禁"非真人自动化创作行为"(脚本/程序托管全程代写代发)。叠加创作者痛点从"怎么写快"转向"怎么不被限流/怎么有活人感/怎么变现",项目做了一轮合规与市场化升级。
|
||||
|
||||
### P0 — 合规生存(已落地)
|
||||
|
||||
| 改造 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| 强制 AI 生成标识注入 | `scripts/writer.py` | 新增 `inject_ai_disclosure()`:按《标识办法》给三平台正文文末注入「AI 辅助创作」声明(显式),head 注入 `ai-generated` meta + AIGC 隐式标识 JSON-LD(隐式)。默认开启,可由 `SystemConfig(disclose_ai=false)` 或 `YZR_DISCLOSE_AI=0` 关闭 |
|
||||
| 发布模块去伪 | `platform/backend/app/api/publishing.py` | **删除伪造的 `status='success'`**:发布接口改为生成发布稿 + 置选题为 `pending_publish`(待人工发布),PublishRecord 状态为 `pending_manual`;明确"AI 稿须真人校验声明后手动发布,系统不代发" |
|
||||
| LLM 降级响亮失败 | `scripts/writer.py` | `_expand_section` 在 LLM 缺失时标记 `_degraded`,`run()` 结尾对降级稿报错告警,不再静默吐稀薄内容混入库 |
|
||||
|
||||
### P1 — 市场契合(已落地)
|
||||
|
||||
| 改造 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| AI味检测+净化 可见化 | `platform/backend/app/api/ai_slop.py` + `platform/frontend/ai-slop.html` | 新 API(GET /report 跑合规检测、POST /purify 一键净化)+ 新前端页 + 导航"AI味检测"。把已有的去套话/合规能力变成用户可感知卖点(直击 2026 限流焦虑) |
|
||||
| 长文→短视频拆条 | `scripts/shortvideo.py` + `scheduler.py` | 新脚本将选题长文拆成 N 条短视频脚本(口播/分镜/适配平台),入库 `articles(platform=shortvideo_i)`;新增定时任务 `scheduled_shortvideo` 08:00,对前日待发布选题自动拆条 |
|
||||
| 外部推广去伪 | `platform/backend/app/api/external_promotion.py` | 新增 `POST /api/external/audit/run` 真实调用 `seo_auditor.py`(此前该脚本从未被任何接口/调度触发),让 SEO 审计可用而非摆设 |
|
||||
|
||||
### P2 — 健壮性(已落地)
|
||||
|
||||
| 改造 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| 指标同步扩平台 | `platform/backend/app/core/scheduler.py` | `_run_metrics_sync` 从仅知乎扩展为遍历 `platform_urls`;微信/小红书无公开 API 时优雅跳过并日志说明,不再伪装"仅知乎有数据" |
|
||||
| 合规闸门防绕过 | `scripts/compliance_optimizer.py` | `force_pass` 选题不自动推进到 `ready`(保留待审查),避免绕过合规闸门直接进发布 |
|
||||
|
||||
### 关键文件变更汇总
|
||||
|
||||
| 文件 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `scripts/writer.py` | 修改 | AI 标识注入 + 降级标记 |
|
||||
| `platform/backend/app/api/publishing.py` | 修改 | 发布去伪,改待人工发布 |
|
||||
| `scripts/compliance_optimizer.py` | 修改 | force_pass 不推进状态 |
|
||||
| `platform/backend/app/api/ai_slop.py` | 新建 | AI味检测/净化 API |
|
||||
| `platform/frontend/ai-slop.html` | 新建 | AI味检测页 |
|
||||
| `scripts/shortvideo.py` | 新建 | 长文→短视频拆条 |
|
||||
| `platform/backend/app/core/scheduler.py` | 修改 | 指标扩平台 + 短视频定时任务 |
|
||||
| `platform/backend/app/api/external_promotion.py` | 修改 | SEO 审计可触发 |
|
||||
|
||||
### 验证
|
||||
- 全部修改文件 `ast.parse` 通过;`app.api.ai_slop / publishing / external_promotion` 与 `core.scheduler` 在 venv 下 `import` 成功
|
||||
- 注:本次为代码升级,**未重启服务/未跑全量测试套件**;发布/净化/拆条需运营在真实平台按声明手动完成
|
||||
|
||||
---
|
||||
|
||||
## 十二、规范说明
|
||||
|
||||
### 规划文档
|
||||
@@ -540,3 +726,144 @@ admin.html 新增「GEO 就绪度」标签页包含:
|
||||
↓
|
||||
如需 → 同步 README.md 摘要
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 第十四章:前端布局修复 + 盈利模式(私有化/白标)升级
|
||||
|
||||
**日期**:2026-07-11
|
||||
**目标**:① 修复前端布局/样式不合理项;② 将项目从"自用中台"升级为"可私有化部署 / 白标转售"产品。
|
||||
|
||||
### 一、前端布局/样式修复
|
||||
- **A1 导航未固定 + 顶部双空隙**:`uni-nav.js` 删除 `<nav>` 内联 `position:relative`,恢复 CSS `position:fixed`。(同步修复 A3 移动端下拉菜单脱离)
|
||||
- **A2 平板导航裁切**:汉堡菜单断点由 `max-width:768px` 提升至 `1000px`,769–1000px 链接改走下拉,不再静默丢失。
|
||||
- **A4 预览弹窗偏右**:`theme-modern.css` 移除 `@media(min-width:769px)` 下 `.preview-dialog-custom{left:90px}`,对话框恢复居中(影响 articles.html / factory.html)。
|
||||
- **A6 卡片横向滚动条**:`.card` 去掉 `overflow-x:auto`(el-table 自带内部滚动,重复滚动条消除,阴影不再被裁)。
|
||||
- **A5 暗色模式半成品(对比度不可读)**:暂禁用 `prefers-color-scheme:dark` 块,统一亮色主题,待引入 EP 暗色变量后启用。
|
||||
- **topics.html 筛选表单响应式**:6 个 `el-col` 加 `:xs/:sm/:md` 断点,移动端堆叠不溢出。
|
||||
- **metrics.html 统计卡**:`.stats-grid` 由 `repeat(4,1fr)` 改为 `repeat(auto-fit,minmax(180px,1fr))`,平板自适应列数。
|
||||
- **factory.html**:`.topic-grid` 用 `minmax(min(340px,100%),1fr)`,≤320px 屏不溢出。
|
||||
|
||||
### 二、盈利模式升级:白标 + 私有化部署
|
||||
- **白标配置后端**:新增 `api/branding.py`
|
||||
- `GET /api/branding`(公开,无需登录):返回 `brand_name/brand_logo/brand_primary_color/brand_support_email/version`。
|
||||
- `PUT /api/branding`(管理员):更新白标配置,写入 `system_configs`。
|
||||
- `main.py` 注册 `branding.router`。
|
||||
- **白标种子**:`initial_data.py` 写入 `brand_name/brand_logo/brand_primary_color/brand_support_email` 默认。
|
||||
- **前端应用品牌**:`uni-nav.js` `mounted` 调 `/api/branding`,动态设置平台名、Logo、导航渐变主色,并写入 `--color-primary`;新增 `branding.html` 白标设置页(管理员,含实时预览)。
|
||||
- **导航入口**:`initial_data.py` 菜单加「白标设置」(admin),`uni-nav.js` 默认 admin 项补充。
|
||||
- **部署包 `deploy/`**:
|
||||
- `Dockerfile`(python:3.11-slim,默认 SQLite,零依赖)
|
||||
- `docker-compose.yml`(含可选 PostgreSQL 服务与数据卷)
|
||||
- `.env.example`(管理员密码 / LLM Key / 数据库)
|
||||
- `README.md`(私有化 + 白标交付指南、定价建议)
|
||||
|
||||
### 验证
|
||||
- 服务 `yzr-platform.service` 重启成功,无启动错误。
|
||||
- `GET /api/branding` 返回种子默认值;`PUT` 管理员保存后持久化生效,重置回默认。
|
||||
- `node --check uni-nav.js`、`ast.parse(branding.py)` 均通过。
|
||||
|
||||
### 待办(已解决,见第十六章)
|
||||
- ~~`init_db()` 的 SQLite `topics.series` 迁移缺口(测试 32/33)~~:经复跑为陈旧 `test.db` 所致,当前 33/33 全过;迁移 fallback 对 SQLite 正常。
|
||||
- 菜单幂等播种缺陷(见第十六章):`initial_data.py` 原 `if Menu.count()==0` 全有/全无逻辑导致已在库时章节四新增的"文章管理/内容工厂/白标设置"未入库,导航不可达,已修复。
|
||||
|
||||
---
|
||||
|
||||
## 第十五章:移动端响应式补全
|
||||
|
||||
**日期**:2026-07-11
|
||||
**目标**:审计并补全全部前端的移动端(≤420px)适配。
|
||||
|
||||
### 审计结论
|
||||
- 已良好适配:topics / articles / campaigns / tasks / metrics / insights / platforms / assets / factory / index / landing / login / users / logs(表格均有 card-list 兜底或本就是卡片布局;固定宽弹窗由全局 `≤768px` 全宽规则兜底)。
|
||||
- 真实破裂点(3 处 el-table 无移动端兜底):
|
||||
1. `ai-slop.html` 问题明细表(line 47)
|
||||
2. `calendar.html` 日程弹窗内 `selectedDayEntries` 表(line 189)
|
||||
3. `admin.html` 平台配置表(line 202,缺 `data-table` 类,全局隐藏规则未覆盖)
|
||||
|
||||
### 修复
|
||||
- 三处统一改为「`el-table`(`.card-table`/`.data-table`) + `.card-list-mobile` 卡片列表」的 `v-if`/`v-else` 兜底,沿用 `theme-modern.css` 的 `.card-list-mobile` 全局样式(≤768px 自动切换)。
|
||||
- 桌面端表格保留,移动端自动转为可点列表,操作按钮(编辑/删除/查看)一并带入卡片。
|
||||
- 校验:三页结构 `v-if`/`v-else` 配对正确,HTTP 200 正常服务。
|
||||
|
||||
### 结论
|
||||
前端现已**完整支持移动端响应式**:所有数据表在窄屏均以卡片列表呈现,导航/筛选/弹窗/统计网格均自适应,无横向溢出。
|
||||
|
||||
---
|
||||
|
||||
## 第十六章:菜单播种缺陷修复(导航可达性)
|
||||
|
||||
**日期**:2026-07-11
|
||||
**触发**:生产服务重启后核对 `/api/menus/active`,发现导航缺少"文章管理 / 内容工厂 / 白标设置",而 `articles.html`(章节四重写)、`factory.html`、`branding.html` 均为真实页面 → 这些入口被"孤立",用户无法从导航进入。
|
||||
|
||||
### 根因
|
||||
- `initial_data.py` 菜单播种原逻辑为 `if db.query(Menu).count() == 0:` —— **仅当菜单表完全为空时才播种**。
|
||||
- 生产库此前已有旧菜单集,故章节四新增的三个菜单从未插入;`uni-nav.js` 运行时以 DB 菜单覆盖 fallback,于是三个入口从导航消失。
|
||||
- 另:旧库首页菜单 `path="index.html"`(无斜杠)与新种子 `"/index.html"` 不匹配,初次修复导致"工作台"重复出现(count 13)。
|
||||
|
||||
### 修复
|
||||
- 菜单播种改为**按 path 幂等对齐**:已存在则更新 `sort_order/roles/icon/is_active`(保留原名称,避免意外重命名),缺失则插入。
|
||||
- 新增首页路径归一化:若同时存在 `index.html` 与 `/index.html` 则删除旧的;若仅 `index.html` 则改路径为 `/index.html`,消除重复入口。
|
||||
- `init_db()` 在每次启动执行,重启后自动对齐。
|
||||
|
||||
### 验证
|
||||
- 重启 `yzr-platform.service` 后 `/api/menus/active` 返回 **12 个菜单**,顺序正确、无重复:工作台→选题→文章管理→内容工厂→数据→日历→素材→任务→外推营销→AI味检测→白标设置→系统。
|
||||
- 12 个对应 HTML 页面均 HTTP 200。
|
||||
- `python3 tests/test_new_features.py`:**33/33 通过**(菜单播种变更未破坏 seeding)。
|
||||
|
||||
### 当前完整导航(运营闭环)
|
||||
选题 → 内容工厂生成 → 文章管理编辑/AI味检测 → 发布记录标记已发布(合规:系统不代发,真人校验后手动发布)。
|
||||
|
||||
---
|
||||
|
||||
## 第十七章:创作流程统一(导航去碎片化)
|
||||
|
||||
**日期**:2026-07-11
|
||||
**目标**:将分散的「选题 / 内容工厂 / 文章管理」三个独立导航入口合并为单一「创作工作台」,减少导航碎片化。
|
||||
|
||||
### 方案
|
||||
采用 **`studio.html` 单一入口 + `el-tabs` 内嵌三个已有页面(`?embed=1` 模式)**,而非重写三套逻辑——复用已验证页面,零逻辑重复、风险最低。
|
||||
|
||||
### 实现
|
||||
- **新增 `studio.html`「创作工作台」**:`el-tabs` 三个标签页(选题 / 内容生成 / 文章管理),各以 `<iframe>` 懒加载对应页面(`topics.html?embed=1` 等);首次切到才加载,保留各页状态。整页 flex 布局填满视口,iframe `position:absolute; inset:0` 自适应。
|
||||
- **嵌入模式 `?embed=1`**:`topics.html` / `factory.html` / `articles.html` 在 `mounted` 检测 `embed` 参数 → `isEmbed=true` 且 `<body class="embed">`:
|
||||
- `<uni-nav>` 加 `v-if="!isEmbed"` 隐藏子页面自身导航(避免双层导航);
|
||||
- `theme-modern.css` 新增 `body.embed .main-content { padding-top:0 }` 去掉顶部留白;
|
||||
- 跨页链接(`redirectToPage` / `goToArticles`)在 embed 下改用 `window.top.location.href`,整页跳转而非 iframe 内嵌套。
|
||||
- **导航收敛**:`initial_data.py` 菜单种子以「创作工作台→studio.html」替换原三入口,并将 `topics.html/factory.html/articles.html` 置 `is_active=False`(页面仍可被 studio 内嵌与直接 URL 访问,不丢失功能);`uni-nav.js` fallback 同步更新。
|
||||
- 排序:工作台(0) → 创作工作台(1) → 数据(2) → 日历(3) → 素材(4) → 任务(5) → 外推营销(6) → AI味检测(7) → 白标设置(8,admin) → 系统(9,admin)。
|
||||
|
||||
### 验证
|
||||
- 重启 `yzr-platform.service`;`/api/menus/active` 返回 **10 个菜单**,含「创作工作台(studio.html)」,原三入口已不在主导航。
|
||||
- `studio.html / topics.html / factory.html / articles.html` 均 HTTP 200;studio 内 iframe `src` 带 `embed=1`。
|
||||
- 三个页面内联脚本 `node --check` 通过;`initial_data.py` 编译通过。
|
||||
- `python3 tests/test_new_features.py`:**33/33 通过**。
|
||||
|
||||
### 当前导航(10 项,创作闭环归于单一入口)
|
||||
工作台 → **创作工作台(选题·生成·文章)** → 数据 / 日历 / 素材 / 任务 / 外推营销 / AI味检测 / 白标设置 / 系统。
|
||||
|
||||
---
|
||||
|
||||
## 第十八章:商业模式与官网营销升级
|
||||
|
||||
**日期**:2026-07-11
|
||||
**目标**:把产品从自用工具定位升级为可对外售卖的 AI 内容中台,并在官网首页展示「免费试用 / 私有化部署 / 源码售卖」三种模式。
|
||||
|
||||
### 方案沉淀
|
||||
- 新增 `docs/BUSINESS_MODEL.md`:功能盘点 + 2026 市场需求 + 三模式可行性 + 首页改造方案 + 定价建议。结论:以私有化部署 + 源码售卖为主力,免费试用作获客钩子(避开纯 SaaS 红海)。
|
||||
|
||||
### 实现(landing.html 改造)
|
||||
- **定价区(#pricing)重构为三段式**:免费试用(¥0 起,私有化试用版/演示)→ 私有化部署(¥29,800 起买断,推荐,featured)→ 源码售卖(面议授权)。CTA 统一锚定 #contact。
|
||||
- **FAQ 增补**:私有化部署说明、源码采购、免费试用申请、AI 内容合规(不代发/人工闸门)、数据安全/多租户。
|
||||
- **新增「开始合作」联系区(#contact)**:联系信息卡 + 轻量线索表单(姓名/公司/合作方式/联系方式/需求),提交唤起 mailto 至 hello@yu-zhi-ran.com(无后端依赖,低风险)。
|
||||
- **文案对齐**:导航「免费使用」→「免费试用」+ 新增「联系销售」;Hero / CTA 按钮对齐;移动端菜单同步。
|
||||
- **deploy/README.md**:交付建议同步为三模式定价。
|
||||
- 新增 `.contact` 相关 CSS(深色区块,与 `.cta` 视觉一致)。
|
||||
|
||||
### 验证
|
||||
- `curl /landing.html` → 200;新内容(免费试用/私有化部署/源码售卖/id="contact"/trialForm/申请试用/获取报价/联系销售)均存在。
|
||||
- `<section>` 开合平衡(8/8);旧 SaaS 三档(入门/专业/企业版)已清除。
|
||||
- 未做后端线索入库(当前 mailto 方案足够);若需留资分析可后续加 `/api/leads`。
|
||||
|
||||
### 待办(可选)
|
||||
- 真实线索入库(Lead 表 + /api/leads);私有化试用版自动下发;报价单生成。
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"task": "test",
|
||||
"time": "2026-04-22 19:00",
|
||||
"topic_count": 1,
|
||||
"case_count": 2,
|
||||
"source_count": 3,
|
||||
"details_link": "/test",
|
||||
"topic_title": "测试选题",
|
||||
"image_count": 5,
|
||||
"output_dir": "/test",
|
||||
"status": "测试状态",
|
||||
"error": "",
|
||||
"log_path": "/tmp/test.log"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
## 标题(爆款钩子)
|
||||
73%的人因AI替身失去信任,你还在用它省那12%的时间?
|
||||
|
||||
## 口播文案
|
||||
你以为AI换脸能帮你省时间?[停顿]别傻了,每一次用AI形象开会,都是在职场里透支信任。
|
||||
|
||||
2025年卡内基梅隆大学的研究发现,[停顿]73%的商务谈判者,一旦知道对方用的是AI替身,信任度直接暴跌。你省下的那12%会议时长,得用几十次真诚沟通才能补回来。[停顿]
|
||||
|
||||
为啥?人眼天生能识别“非自然”的脸,哪怕只是一丝微笑延迟,你的大脑杏仁核就会报警——“这人不靠谱!”[停顿]哈佛的脑成像实验更狠:跟AI形象合作时,你大脑里的共情区域活跃度下降30%。你根本不在意对方有没有在听,对方也感受不到你的真诚。
|
||||
|
||||
所以那些跨国大公司为什么明令禁止?[停顿]因为信任一旦崩了,客户流失率六个季度能涨18%。你省的时间,抵不过一个客户的流失。[停顿]记住:高信任场景——客户会、谈判、一对一辅导,绝对不要用AI替身。
|
||||
|
||||
你觉得有道理吗?评论聊聊,你的公司禁用AI换脸了吗?
|
||||
|
||||
## 分镜建议
|
||||
| 镜头 | 画面 | 字幕 | 时长 |
|
||||
|------|------|------|------|
|
||||
| 镜头1 | 博主正对镜头,背后是电脑屏幕显示视频会议中的AI换脸头像 | “AI换脸省时间?你省的是信任” | 0-8秒 |
|
||||
| 镜头2 | 切换至数据图表:73%的数字放大闪烁,旁边有“信任度暴跌”字样 | “73%谈判者因AI替身失去信任” | 8-20秒 |
|
||||
| 镜头3 | 博主做“恐怖谷”表情演示:先微笑、然后眼神呆滞、再突然回神 | “大脑报警:不自然的微笑=不靠谱” | 20-35秒 |
|
||||
| 镜头4 | 动画展示大脑区域变暗(共情区下降30%),旁边出现客户流失率18% | “共情关闭,客户跑路” | 35-50秒 |
|
||||
| 镜头5 | 博主回到正对镜头,手势强调“三场景禁用” | “客户会、谈判、一对一,禁用!” | 50-65秒 |
|
||||
|
||||
## 适配平台
|
||||
- **抖音**:开头3秒用“73%”数字怼脸,加热门BGM(悬疑类),字幕用大字跳闪。
|
||||
- **视频号**:节奏稍缓,加“你认同吗”互动投票卡片,结尾引导关注。
|
||||
- **小红书视频**:封面用“AI替身毁信任”金句,文案加emoji分段,评论区置顶“自查清单”。
|
||||
@@ -0,0 +1,28 @@
|
||||
## 标题(爆款钩子)
|
||||
别让AI换脸毁了客户!3条保命铁律立刻救你
|
||||
|
||||
## 口播文案
|
||||
「[停顿]」你知道吗?你那个完美的AI替身,正在让你和客户之间的信任疯狂透支。今天三条干货,立刻就能用。
|
||||
|
||||
第一条:所有对外会议、重要谈判、一对一辅导,千万别用AI形象。[停顿]加州大学研究证明,哪怕眼珠动慢一丁点,对方大脑立刻拉警报——73%的商务谈判者会直接判定你不诚实。信任崩塌了,修复一次需要5到8次真诚互动。
|
||||
|
||||
第二条:内部非正式头脑风暴,用AI形象可以提升流畅度,[停顿]但必须提前告知团队,划清边界。盖洛普数据显示,明确边界的公司员工满意度高23%,客户推荐值高12%。
|
||||
|
||||
第三条:如果已经用了AI形象,立刻补救。真诚道歉,换成真人视频,主动暴露缺点。[停顿]别拿“效率”骗自己,信任是账面上的隐形资产。
|
||||
|
||||
记住:信任建立要日积月累,崩塌只在一念之间。觉得有用?点赞收藏,别再踩坑了。
|
||||
|
||||
## 分镜建议
|
||||
|
||||
| 镜头 | 画面 | 字幕 | 时长 |
|
||||
|------|------|------|------|
|
||||
| 1 | 博主正面近景,严肃表情,手指镜头 | 别让AI换脸毁了客户!3条保命铁律 | 8秒 |
|
||||
| 2 | 博主拿起手机或电脑屏幕,展示一个AI换脸会议场景图标 | 73%的人直接判定不诚实 | 一次失信需要5-8次修复 | 12秒 |
|
||||
| 3 | 博主切换到侧身,用手势列举要点,镜头缓慢推进 | 第一条:对外会议禁用 | 第二条:内部告知划边界 | 15秒 |
|
||||
| 4 | 博主站起,背景出现“信任度收益”图表动画 | 第三条:用了立刻补救 | 信任是隐形资产 | 12秒 |
|
||||
| 5 | 博主回到正面,微笑,手指向上做“收藏”手势 | 点赞收藏,别再踩坑 | 8秒 |
|
||||
|
||||
## 适配平台
|
||||
- **抖音**:前3秒加一个“你中招了吗”的悬念语气,用快切放大“73%”等数据,字幕用黄色大字突出。
|
||||
- **视频号**:开场加一句“我做商务8年,今天说个血泪教训”,增加真实感和信任感;节奏略慢,留出互动引导时间。
|
||||
- **小红书视频**:首帧用“🔥AI换脸避坑指南”彩色标题,口播再简练10%,强调“收藏备用”;视频下方配图文摘要文字。
|
||||
@@ -0,0 +1,19 @@
|
||||
## 标题(爆款钩子)
|
||||
我用AI替身开视频会,结果被客户当场识破,丢了200万的单
|
||||
|
||||
## 口播文案
|
||||
上个月我哥们儿跟我喝酒,差点哭了[停顿]他说他为了赶一个跨国客户的视频会,用了最高级的AI换脸软件——皮肤零瑕疵、微笑完美,连声音都调了音。结果你猜怎么着?会议开到一半,对方突然问:“你本人是不是没在镜头前?”[停顿]原来客户那边有人工智能识别系统,发现他瞳孔反光不对,微表情卡顿。当场翻脸,200万的单子黄了[停顿]你以为AI替身能省时间?错了[停顿]大脑天生对“假脸”拉警报,瞳孔放大、心跳加速,你根本没法建立信任。老板们醒醒,效率再高也换不来真心合作。你身边有公司因为AI形象吃过亏吗?评论区聊聊。
|
||||
|
||||
## 分镜建议
|
||||
| 画面 | 字幕 | 时长 |
|
||||
|------|------|------|
|
||||
| 中景:博主坐在桌前,表情无奈摇头 | 哥们儿用AI替身开会 单子黄了 | 0-5s |
|
||||
| 特写:博主手指敲桌面,语气加重 | 客户突然问:你本人不在? | 5-15s |
|
||||
| 切到手机/电脑屏幕动画:AI脸+显微镜下瞳孔异常 | 瞳孔反光不对 微表情卡顿 | 15-25s |
|
||||
| 博主直视镜头,双手摊开 | 大脑对假脸拉警报! | 25-35s |
|
||||
| 博主低头写笔记状,抬头问 | 你身边有公司吃过亏吗?评论区聊聊 | 35-45s |
|
||||
|
||||
## 适配平台
|
||||
- **抖音**:前3秒加“你敢信?”等悬念字幕,节奏再快一点,每秒换画面。
|
||||
- **视频号**:保持自然聊天的语气,结尾加一句“关注我,了解更多职场避坑技巧”。
|
||||
- **小红书视频**:封面用大字“AI换脸翻车实录”,口播语速放慢,每句之间留呼吸感。
|
||||
@@ -0,0 +1,29 @@
|
||||
## 标题(爆款钩子)
|
||||
别再挤景区了!4.2 亿人都在假装过端午,你还要继续吗?
|
||||
|
||||
## 口播文案
|
||||
承认吧,你端午那趟旅行,根本不算过节,只是去当了个“人肉背景板”![停顿]
|
||||
|
||||
2026 年端午,4.2 亿人涌进景区。你拍的是密密麻麻的后脑勺,回家翻相册,除了证明“我去过”,对屈原、龙舟的理解,还停留在小学课本那两句话。[停顿]
|
||||
|
||||
调查显示,超六成人都在“走马观花”。问题真不在节日没内涵,而是你太懒了![停顿]你只会被动跟着人流挪动,却忘了主动探索。
|
||||
|
||||
换个思路试试?让 AI 做你的私人文化向导。[停顿]输入“屈原流放路线”或“龙舟制作工艺”,它立马生成专属路径。不再是大锅饭式的打卡,而是穿越千年的深度对话。
|
||||
|
||||
现在,四成游客愿意为这种“独家体验”多付 30% 的钱。[停顿]因为大家终于醒了:真正的文化,从来不在拥挤的人堆里,而在你主动迈出的那一步。
|
||||
|
||||
今年假期,你是继续去挤照片,还是用 AI 定制一场真正的文化穿越?评论区告诉我!
|
||||
|
||||
## 分镜建议
|
||||
| 画面 | 字幕 | 时长 |
|
||||
| :--- | :--- | :--- |
|
||||
| **近景**:博主对着镜头一脸严肃,背景合成极度拥挤的景区人头攒动画面,甚至有点压抑感。 | 4.2 亿人<br>全是背景板? | 5s |
|
||||
| **中景**:博主无奈地翻看手机相册,屏幕特写显示全是模糊的人头照,随后画面切换成枯燥的课本插图。 | 走马观花<br>等于没去 | 8s |
|
||||
| **特写**:博主眼神突然亮起,手持手机展示 AI 界面,手指滑动生成一条炫酷的 3D 地图路线。 | 拒绝被动<br>AI 定制路径 | 10s |
|
||||
| **混剪**:快速闪过几个高质量画面:亲手触摸龙舟木纹、在屈原故里听沉浸式故事,对比之前的拥挤画面。 | 深度体验<br>值得多付 30% | 10s |
|
||||
| **近景**:博主回到镜头前,身体前倾,发出真诚邀请,屏幕下方弹出评论框引导。 | 你是哪种?<br>评论区见 | 7s |
|
||||
|
||||
## 适配平台
|
||||
- **抖音**:前 3 秒拥挤画面要配合嘈杂的环境音效和急促的 BGM,制造强烈的焦虑感,随后音乐瞬间转为科技感轻音乐。
|
||||
- **视频号**:文案中“小学课本”、“文化穿越”等情感共鸣点要加重语气,结尾引导转发给“只会打卡的朋友”。
|
||||
- **小红书**:封面图要用"AI 定制路线 vs 传统人挤人”的强烈对比拼图,标题加上"🆘避雷”和"✨小众玩法”emoji。
|
||||
@@ -0,0 +1,23 @@
|
||||
## 标题(爆款钩子)
|
||||
端午出游4.2亿人,AI教你3招避开人挤人
|
||||
|
||||
## 口播文案
|
||||
你是不是也这样?[停顿]端午挤进景区,举着手机拍的全是人头,回家翻相册就剩“我去过”三个字。[停顿]超过六成游客都说体验太同质化,走马观花。[停顿]问题出在哪?不是节日没文化,是你太被动。[停顿]今天直接给你3条干货,让AI帮你定制专属文化路线,彻底告别“人从众”。[停顿]
|
||||
|
||||
第一条:别搜攻略,先问自己。[停顿]打开手机,输入“屈原流放路线”“端午习俗演变”或者“现代龙舟怎么造”,AI就能秒出主题路线。[停顿]第二条:别只看景点,要看故事。[停顿]AI会沿途穿插历史讲解、互动体验,让你从“拍照”变成“沉浸”。[停顿]第三条:多花30%的钱,换100%的深度体验。[停顿]最新数据说,超过四成游客愿意为个性化深度体验多付费,AI让这钱花得值。[停顿]
|
||||
|
||||
赶紧试试,评论区告诉我你用过AI定制旅行吗?[停顿]点赞收藏,下次出游再也不当背景板!
|
||||
|
||||
## 分镜建议
|
||||
| 镜头 | 画面 | 字幕 | 时长 |
|
||||
|------|------|------|------|
|
||||
| 1 | 近景:博主对着镜头,手机展示假期人山人海的视频片段 | 端午出游4.2亿人 | 8秒 |
|
||||
| 2 | 中景:博主手势比划,手机屏幕出现“屈原流放路线”输入框 | 6成游客:体验同质化 | 10秒 |
|
||||
| 3 | 特写:手机屏幕快速滑动,显示AI生成的路线图、历史故事卡片、互动体验图标 | AI定制路线:输入→秒出 | 10秒 |
|
||||
| 4 | 中景:博主拿着手机,表情兴奋,画面上方弹出数据图“40%游客愿多付30%” | 多花30%,换100%深度 | 8秒 |
|
||||
| 5 | 近景:博主笑着对镜头,手指向屏幕“评论区见” | 评论区聊聊你的AI旅行 | 4秒 |
|
||||
|
||||
## 适配平台
|
||||
- **抖音**:前3秒上“人潮拥挤”的实拍画面+快节奏卡点音乐,字幕用动态大字强调“4.2亿”。
|
||||
- **视频号**:语速放慢10%,开头加一句“端午刚过,你踩雷了吗?”引发共鸣,结尾引导点赞转发。
|
||||
- **小红书视频**:封面用“AI定制旅行”效果图,文案侧重“小众玩法”“拒绝人挤人”,画面多展示手机操作界面。
|
||||
@@ -0,0 +1,29 @@
|
||||
## 标题(爆款钩子)
|
||||
端午挤到怀疑人生?她用AI玩出了新高度
|
||||
|
||||
## 口播文案
|
||||
上周末端午,我闺蜜兴冲冲去古镇打卡,[停顿]结果你猜怎么着?朋友圈发的全是人头,连屈原像都没挤到跟前。回来她跟我说:感觉自己就是个移动的背景板,除了累,啥也没记住。[停顿]
|
||||
|
||||
其实,问题不是节日没意思,是我们太“被动”了。跟着人流走,走到哪拍到哪,回家一翻相册——哦,我去过。[停顿]
|
||||
|
||||
但如果反过来呢?你打开手机,让AI根据你的兴趣规划一条专属路线:喜欢历史,就走屈原流放路线;喜欢手艺,就去学包粽子、看龙舟怎么做。[停顿]我闺蜜后来试了一下,她说那天她才知道,五彩绳原来有五种含义,不是随便戴的。
|
||||
|
||||
数据也说了,超四成人愿意多花30%的钱,就为了这种“私人定制”的体验。[停顿]所以别再当“人挤人”里的沙粒了,主动一点,让文化真正走进你心里。[停顿]
|
||||
|
||||
你觉得呢?评论区聊聊你遇到过最坑的旅行经历。
|
||||
|
||||
## 分镜建议
|
||||
| 镜头 | 画面 | 字幕 | 时长 |
|
||||
|------|------|------|------|
|
||||
| 1 | 中景:博主对着镜头,表情略带无奈,手里拿手机翻照片 | 端午古镇,全是人头 | 8秒 |
|
||||
| 2 | 插入画面:景区人山人海的短视频素材(或模拟),然后切回博主 | 六成人觉得走马观花 | 10秒 |
|
||||
| 3 | 特写:博主手指在手机屏幕上滑动,屏幕出现AI定制界面(可后期特效) | 输入“屈原流放路线” | 12秒 |
|
||||
| 4 | 博主回忆状,微笑 | 五彩绳原来有五种含义 | 10秒 |
|
||||
| 5 | 博主正对镜头,语气坚定,手势强调 | 别再做背景板了 | 8秒 |
|
||||
|
||||
(总时长约48秒,可适当调整节奏)
|
||||
|
||||
## 适配平台
|
||||
- **抖音**:前3秒加一个“人多到哭”的音效或变脸特效,快速抓注意力。
|
||||
- **视频号**:开头增加一个朋友圈实拍片段(人头),强化真实感,结尾引导点赞转发。
|
||||
- **小红书视频**:画面加柔和滤镜,字幕用可爱字体,突出“闺蜜故事”的亲切感,文末加话题标签。
|
||||
@@ -0,0 +1,19 @@
|
||||
## 标题(爆款钩子)
|
||||
🔥你的Logo设计单,正被邻居用旧电脑10分钟抢走!
|
||||
|
||||
## 口播文案
|
||||
你刚接的那个Logo单,[停顿]可能正被隔壁邻居用一台三年前的笔记本,十分钟搞定。[停顿]别不信——2026年,Stability AI的端侧模型,能让普通笔记本15秒出商业级矢量图,成本只有云端的1/50。[停顿]翻译更惨,谷歌的离线实时口译,准确率已经92%,你还在按字收钱?[停顿]客户发现:本地跑AI不仅免费,隐私还更安全。[停顿]那咱们就等死吗?[停顿]不,自救关键是往“非标”钻——复杂人际博弈、现场应急决策、情感陪伴,这些端侧AI学不会。[停顿]立刻停止卖标准时间,转做深度咨询和定制方案,把AI变成你的杠杆。[停顿]听懂了的,评论区扣“转型”。
|
||||
|
||||
## 分镜建议
|
||||
| 镜头 | 画面 | 字幕 | 时长 |
|
||||
|------|------|------|------|
|
||||
| 1 | 特写:手机屏幕显示一个Logo设计稿,然后一只手把设计稿推开 | 你的Logo单,邻居10分钟搞定 | 8秒 |
|
||||
| 2 | 中景:博主坐在电脑前,表情严肃,手比划“15秒” | Stability AI端侧模型:15秒出图,成本1/50 | 10秒 |
|
||||
| 3 | 切换:手机画面展示谷歌翻译界面,然后快速切换成离线模式 | 离线实时口译,准确率92%,按字收费模式已死 | 12秒 |
|
||||
| 4 | 近景:博主身体前倾,眼神坚定,手指点向镜头 | 自救:深耕“非标”领域,复杂博弈、情感陪伴 | 10秒 |
|
||||
| 5 | 特写:博主手拿话筒,背景虚化,字幕滚动 | 停止卖时间,做深度咨询,AI是你的杠杆 | 8秒 |
|
||||
|
||||
## 适配平台
|
||||
- **抖音**:前3秒用冲突画面+快节奏剪辑,字幕用动态大字,激发完播。
|
||||
- **视频号**:口播语速稍慢,增加“认同吗?评论区聊聊”等互动引导,强化信任感。
|
||||
- **小红书视频**:封面用“2026自由职业生存指南”文字,口播中加入“干货警告”,结尾加话题标签。
|
||||
@@ -0,0 +1,27 @@
|
||||
## 标题(爆款钩子)
|
||||
端侧AI爆发!3条自救指南,不想被淘汰的立刻看
|
||||
|
||||
## 口播文案
|
||||
你还在做这3种活儿吗?赶紧停![停顿]
|
||||
|
||||
第一种,设计外包。你熬三天做的Logo,邻居用三年前的笔记本,15秒出图,成本只有你报价的1/50。客户发现本地跑AI免费还安全,你的报价单下季度就废了。[停顿]
|
||||
|
||||
第二种,翻译文案。谷歌的离线口译延迟不到0.2秒,医疗法律准确率92%,客户凭什么还按字付你钱?记住,AI不是抢你饭碗,是让“文字搬运工”技能归零。[停顿]
|
||||
|
||||
第三种,卖成品稿件。35%的中小企业已经用本地营销代理自动生成海报文案了,你要是不转型成训练这些智能体的“教练”,就会被踢出局。[停顿]
|
||||
|
||||
自救就一条路:深耕端侧AI搞不定的“非标”领域。比如复杂人际博弈、现场应急决策、高度情感陪伴。别卖标准时间,卖信任和深度咨询,把AI当杠杆,处理人类才能定义的模糊地带。[停顿]
|
||||
|
||||
现在,立刻,检查你的业务有没有被这三条砸中?评论区聊聊。
|
||||
|
||||
## 分镜建议
|
||||
- 镜头1:特写,博主对着镜头,表情严肃,手指向屏幕。字幕:“3种职业正在被AI干掉”。时长:8秒
|
||||
- 镜头2:切画面,展示AI快速生成Logo的对比(左边是你熬夜加班,右边是电脑15秒出图)。字幕:“设计外包→15秒出图”。时长:15秒
|
||||
- 镜头3:博主口播,背景闪现谷歌翻译界面和字幕。字幕:“翻译文案→92%准确率”。时长:15秒
|
||||
- 镜头4:博主口播,屏幕显示Gartner报告数据。字幕:“35%企业用AI代理→你该做教练”。时长:15秒
|
||||
- 镜头5:博主面向镜头,语气坚定,手指点向观众。字幕:“自救:深耕非标领域,卖信任”。时长:10秒
|
||||
|
||||
## 适配平台
|
||||
- **抖音**:开头加“别划走,3条干货救你饭碗”,语速更快,每句留1秒停顿。
|
||||
- **视频号**:开头加“这条视频可能会被限流,但建议你看到最后”,语气更稳重,停顿稍长。
|
||||
- **小红书视频**:开头加“姐妹们,别再踩坑了”,结尾加“关注我,只分享能用的干货”,画面更干净,字幕加emoji。
|
||||
@@ -0,0 +1,17 @@
|
||||
## 标题(爆款钩子)
|
||||
你接的Logo单,邻居用旧电脑10分钟搞定|端侧AI来了
|
||||
|
||||
## 口播文案
|
||||
上个月,一个朋友接了个Logo单子,报价3000块。[停顿]结果呢?他隔壁邻居,一个从没学过设计的大学生,用一台三年前的旧笔记本,装了最新的AI工具,十分钟出了三套方案。客户直接取消订单。[停顿]你可能会觉得夸张,但这就是2026年正在发生的事——端侧AI已经能本地跑商业级矢量图,15秒出图,成本是云端的五十分之一。[停顿]翻译、文案、设计这些自由职业者还在按字收费?客户已经发现:AI翻译带情感色彩和方言,能达到初级译员水平。[停顿]所以,如果你还在卖“时间”和“技能”,你的报价单下个季度就废了。[停顿]真正的出路不是跟AI比快,而是做AI做不了的事——比如复杂人际博弈、现场决策、情感陪伴。你得从“工具人”变成“教练”,帮客户调教本地AI,而不是被AI替代。[停顿]
|
||||
|
||||
## 分镜建议
|
||||
- 镜头1(近景):博主对着镜头,表情略带无奈,手里拿着一张画着Logo的纸。字幕:“上周接了个Logo单子,报价3000。” 时长:8秒
|
||||
- 镜头2(中景):切到博主旁边放一台旧笔记本电脑,屏幕快速闪过AI生成Logo的过程。字幕:“邻居大学生,十分钟出三套方案。” 时长:10秒
|
||||
- 镜头3(近景):博主手指屏幕,语气加重。字幕:“15秒出图,成本是云端的1/50。” 时长:6秒
|
||||
- 镜头4(全景):博主站起来,伸出双手做“停止”动作。字幕:“还在按字收费?客户已经不需要你了。” 时长:8秒
|
||||
- 镜头5(特写):博主指向自己胸口,眼神坚定。字幕:“做AI做不了的,当教练。” 时长:8秒
|
||||
|
||||
## 适配平台
|
||||
- **抖音**:前3秒用“你接的Logo单子”开头,快速钩住注意力;节奏紧凑,加上热门BGM。
|
||||
- **视频号**:偏重情绪共鸣,可以放大“邻居大学生”的故事冲突,结尾多停留两秒引导点赞。
|
||||
- **小红书视频**:封面用“3000块的单子被AI抢了”醒目大字,文案更干货,附上“你中招了吗”互动话题。
|
||||
@@ -0,0 +1,17 @@
|
||||
## 标题(爆款钩子)
|
||||
做了8年SEO,流量却暴跌68%?你的文章正在给AI打工!
|
||||
|
||||
## 口播文案
|
||||
老张上周在群里崩溃了,写了8年SEO,花三天打磨一篇爆款,结果流量掉了68%。[停顿] 他不信邪,搜自己标题——排在第一条的,不是他的文章,而是AI的摘要。[停顿] 你的完美SEO文章,正在变成AI搜索的免费数据源。这不是危言耸听。[停顿] 去年Google推出AI概览,直接在你内容里抽答案,用户根本不用点进来。你堆的关键词、铺的内链,全成了AI的养料。[停顿] 怎么办?别再写那种标准答案式的文章了。要写AI不会写的东西:你的个人故事、独家数据、争议观点。[停顿] 当你的内容不可替代时,AI只能把你当参考来源,而不是直接吃掉。[停顿] 记住,未来的SEO不是讨好算法,是成为AI不敢忽视的权威。评论“转型”,我教你具体做法。
|
||||
|
||||
## 分镜建议
|
||||
- **镜头1**(0-8秒):博主面对镜头,表情严肃,背景是电脑显示SEO数据图表。字幕:“你的文章正在给AI打工”。时长8秒。
|
||||
- **镜头2**(8-18秒):切换手机屏幕画面,展示一个AI搜索结果,红色圆圈圈出摘要框。字幕:“用户根本不用点进去”。时长10秒。
|
||||
- **镜头3**(18-30秒):博主手比划,旁边放一块白板,画“旧SEO”和“新SEO”两个对比概念。字幕:“别再堆关键词”。时长12秒。
|
||||
- **镜头4**(30-45秒):博主微笑自信,手指向自己胸前。字幕:“成为AI不敢忽视的权威”。时长15秒。
|
||||
- **镜头5**(45-60秒):博主指向屏幕外,做出引导互动的动作。字幕:“评论‘转型’”。时长15秒。
|
||||
|
||||
## 适配平台
|
||||
- **抖音**:前三秒必须情绪拉满,建议把“流量暴跌68%”做成动态大字炸屏,配合紧张BGM。
|
||||
- **视频号**:增加1-2秒行业数据引用画面(如Google AI概览覆盖率),强化专业感和可信度。
|
||||
- **小红书视频**:开头用“刷到这条视频你赚到了”代替直接冲突,结尾引导收藏并@好友,提高互动率。
|
||||
@@ -0,0 +1,43 @@
|
||||
## 标题(爆款钩子)
|
||||
SEO已死?3招让AI主动抓你的文章,流量翻10倍!
|
||||
|
||||
## 口播文案
|
||||
做SEO的老铁注意了![停顿]你花三天写的“完美文章”,标题塞满关键词,内链铺了12个,结果流量掉68%?原因很简单——AI搜索时代,你的文章被当免费数据喂给机器了![停顿]今天直接给三条能立刻上手的干货,让你成为AI的“必引内容”。
|
||||
|
||||
第一,别堆关键词了,改成结构化排版。用清晰的小标题、短段落、加粗重点、加列表。AI喜欢直接抓取“答案盒子”,你写成散文它理都不理。[停顿]
|
||||
|
||||
第二,疯狂建外链!让高权重网站引用你,AI搜索的排序算法超级看重反向链接。找个行业大号互推,或者去知乎、quora回答问题时顺手贴链接。[停顿]
|
||||
|
||||
第三,给你的文章加Schema标记。百度、谷歌都有免费工具,一键生成结构化数据标签,告诉AI这篇是“教程”还是“评测”。[停顿]
|
||||
|
||||
记住:以后你不是写给人看,是写给AI看的。AI先读,再推荐给你用户。三条立马去执行,下个月搜索广场就是你首页![停顿]
|
||||
|
||||
## 分镜建议
|
||||
- 画面:博主对着镜头,表情焦虑,手里拿手机显示流量曲线暴跌图
|
||||
字幕:SEO老张的惨痛教训
|
||||
时长:5秒
|
||||
|
||||
- 画面:快速切换三个关键词卡片:结构化排版、外链引用、Schema标记,每个卡片停留1秒
|
||||
字幕:3招立刻上手
|
||||
时长:5秒
|
||||
|
||||
- 画面:博主手指屏幕,展示一个清晰的结构化文章示例(有小标题、列表)
|
||||
字幕:AI喜欢“盒子内容”
|
||||
时长:8秒
|
||||
|
||||
- 画面:博主做出打电话动作,画外显示“联系大V互推”字样
|
||||
字幕:外链=流量密码
|
||||
时长:6秒
|
||||
|
||||
- 画面:博主演示在搜索引擎工具页面点选Schema类型
|
||||
字幕:一行代码让AI秒懂你
|
||||
时长:6秒
|
||||
|
||||
- 画面:博主自信微笑,手指向镜头,文字弹出“现在就去改你的文章!”
|
||||
字幕:行动才有流量
|
||||
时长:5秒
|
||||
|
||||
## 适配平台
|
||||
- 抖音:开头加个“你中招了吗”的钩子,字幕用更大字号,前3秒必须出现文题。
|
||||
- 视频号:节奏稍慢,每招后加1秒表情特写,适合中年创业者观看习惯。
|
||||
- 小红书视频:封面做对比图“旧SEO vs 新AI搜流”,正文精简到3个要点图,口播语速放慢。
|
||||
@@ -0,0 +1,23 @@
|
||||
## 标题(爆款钩子)
|
||||
AI换脸开会?省了10分钟,丢了18%客户!
|
||||
|
||||
## 口播文案
|
||||
你有没有碰到过这种场景?[停顿]打开视频会议,对面坐着个皮肤零瑕疵、眼神却发僵的“AI替身”。是不是瞬间觉得怪怪的?[停顿]别怪你太敏感,加州大学研究发现,见到这种“完美脸”,你的大脑会本能拉响警报——瞳孔放大,皮肤电反应飙升,这是进化留给你的保命本能。[停顿]
|
||||
|
||||
但很多人还在吹AI换脸多高效:会议时间缩短12%![停顿]可你知道另一面吗?同一家公司,员工抱怨“会议变陌生”“判断不了对方有没有在听”的比例,暴涨了41%。[停顿]更致命的是,一家欧洲金融科技公司用了AI形象后,客户流失率18个月内飙升18%——客户觉得你在“省成本”,而不是“在乎关系”。[停顿]
|
||||
|
||||
所谓效率,不过是在透支信任。[停顿]信任崩塌,比效率低更致命。你公司敢用AI换脸开会吗?评论区聊聊。[停顿]
|
||||
|
||||
## 分镜建议
|
||||
| 镜头 | 画面 | 字幕 | 时长 |
|
||||
|------|------|------|------|
|
||||
| 1 | 主播正面特写,表情略带疑惑 | “AI换脸开会?省了10分钟,丢了18%客户!” | 5秒 |
|
||||
| 2 | 画面插入数据对比图:左边“会议缩短12%”,右边“信任下降41%” | 会议缩短12% vs 信任下降41% | 8秒 |
|
||||
| 3 | 主播用手比划大脑,背景出现杏仁核示意图 | 大脑拉响警报:瞳孔放大,皮肤电升高 | 10秒 |
|
||||
| 4 | 切换场景:金融公司logo+客户流失曲线图 | 客户流失率18个月上升18% | 8秒 |
|
||||
| 5 | 主播回到正面,直视镜头,摊手提问 | 你公司敢用AI换脸吗?评论区聊聊 | 7秒 |
|
||||
|
||||
## 适配平台
|
||||
- **抖音**:前3秒用“你肯定遇到过”情绪钩子,节奏加快,字幕多用大号关键词。
|
||||
- **视频号**:适当增加数据引用(如“盖洛普2026年报告”),语气更沉稳,强化信任与效率的理性对比。
|
||||
- **小红书视频**:开头用“姐妹们,这事太离谱了”拉近距离,结尾引导收藏和“@你的老板来看”。
|
||||
@@ -0,0 +1,42 @@
|
||||
## 标题(爆款钩子)
|
||||
AI换脸被职场封杀?这3个真相让你立刻关掉它
|
||||
|
||||
## 口播文案
|
||||
你知道吗?用AI换脸开视频会议,73%的客户会直接降低对你的信任。[停顿]卡内基梅隆大学的研究实锤了——就算看起来再逼真,大脑杏仁核也会本能拉响警报,觉得“这人不对劲”。[停顿]
|
||||
|
||||
而且啊,你以为节省了时间?哈佛实验发现,和AI形象互动时,共情脑区活跃度整整下降30%。[停顿]员工匿名抱怨增加了41%——说“感觉会议变得陌生”。效率背后,是看不见的信任透支。[停顿]
|
||||
|
||||
更扎心的是,“完美形象”反而让人不舒服。斯坦福的研究说,AI永远模仿不了微表情的那些细微抖动,你的社交脑会陷入矛盾:看起来是人,直觉告诉你不是,持续消耗你的注意力。[停顿]
|
||||
|
||||
所以干货来了,三条立刻能用的:
|
||||
第一条,对外客户会议、重要谈判、一对一辅导——绝对禁用AI脸。[停顿]
|
||||
第二条,内部非正式头脑风暴可以用,但必须提前告知对方。[停顿]
|
||||
第三条,每次想用AI形象前,先问自己三个问题:客户介意吗?团队能真正连接吗?如果因此失去一个客户,你接受吗?[停顿]
|
||||
|
||||
信任积累要几年,崩塌只需要一张AI脸。点赞收藏,别让效率毁了你的职场关系。
|
||||
|
||||
## 分镜建议
|
||||
- 画面:博主面对镜头,表情严肃,手指指向屏幕上的“73%”数据字幕
|
||||
- 字幕:73%的客户信任直线下降
|
||||
- 时长:8秒
|
||||
|
||||
- 画面:快速切换大脑示意图/脑区动画,然后切回博主说话
|
||||
- 字幕:共情脑区活跃度下降30%|员工抱怨+41%
|
||||
- 时长:8秒
|
||||
|
||||
- 画面:展示一张完美AI人脸图片,然后手指划掉,出现“抖动的微表情”文字
|
||||
- 字幕:完美 ≠ 真实|社交脑陷入矛盾
|
||||
- 时长:8秒
|
||||
|
||||
- 画面:博主用手势比划“三”,然后逐条列出文字,配合手写动画
|
||||
- 字幕:三条实操:①对外禁用②内部告知③三问自检
|
||||
- 时长:12秒
|
||||
|
||||
- 画面:博主微笑总结,手势做“点赞”动作,结尾留钩子
|
||||
- 字幕:信任积累几年,崩塌只需一张AI脸
|
||||
- 时长:7秒
|
||||
|
||||
## 适配平台
|
||||
- **抖音**:开头放5秒恐怖谷动画吸引眼球,全程快剪+高能字幕,结尾引导点赞收藏。
|
||||
- **视频号**:节奏稍慢0.5倍,中间加入情绪停顿(比如叹息),适合熟人传播信任感。
|
||||
- **小红书视频**:标题改成“打工人避坑!AI换脸会议避雷指南”,重点突出“亲测”“血泪教训”人设,评论区引导提问。
|
||||
@@ -0,0 +1,22 @@
|
||||
## 标题(爆款钩子)
|
||||
就因为客户用了AI换脸,我200万的单子一夜黄了
|
||||
|
||||
## 口播文案
|
||||
你知道吗?我有个做销售的朋友,去年年初谈一个200万的大单,[停顿]视频会议里客户全程用了一张AI换脸,皮肤完美、笑容标准,但眼神就是不太对劲。[停顿]他说自己全程浑身不舒服,最后报价都报得没底气。结果呢?客户反馈说“感觉你们公司不真诚”,直接选了别家。[停顿]
|
||||
这不是个例啊。加州大学研究早就证明了,人看到“完美但空洞”的AI脸时,大脑会本能拉响警报——瞳孔放大、皮肤电反应升高,这是几百万年进化保留下来的警惕机制。[停顿]卡内基梅隆大学更狠:73%的商务谈判参与者,只要发现对方用了AI形象,信任直接打骨折。[停顿]
|
||||
所以现在很多大公司开始明文封杀AI换脸,不是抵制效率,是害怕信任崩塌。[停顿]想想看,一场会议省了12%的时间,却要多花几十次真诚沟通才能挽回信用,这笔账你算得过来吗?[停顿]
|
||||
你觉得AI形象该在职场用吗?评论区聊聊你的真实感受。[停顿]点赞收藏,别让更多人踩这个坑。
|
||||
|
||||
## 分镜建议
|
||||
| 镜头 | 画面(景别/动作/场景) | 字幕(关键词) | 时长 |
|
||||
|------|------------------------|----------------|------|
|
||||
| 1 | 近景,主播在办公室对着镜头,表情略带懊恼 | “我朋友丢了200万的大单” | 8秒 |
|
||||
| 2 | 插入视频会议画面,客户AI形象在微笑,眼神空洞,画面加恐怖谷特效 | “客户用的是AI换脸” | 6秒 |
|
||||
| 3 | 切回主播近景,手指敲桌子加强语气 | “73%的人信任度下降” | 6秒 |
|
||||
| 4 | 画面出现两个天平:左边是“省时间12%”,右边是“5-8次真诚沟通”,天平向右倾斜 | “效率 vs 信任,你选哪个?” | 6秒 |
|
||||
| 5 | 主播直视镜头,做出询问表情,最后挥手 | “评论区等你故事” | 4秒 |
|
||||
|
||||
## 适配平台
|
||||
- **抖音**:开头前3秒用“200万”+“黄了”制造强冲击,字幕加粗放大,节奏紧凑,可加#AI换脸职场封杀 话题标签。
|
||||
- **视频号**:保留更多数据细节(73%、12%),结尾可引导关注账号获取“信任修复清单”,适合知识传播。
|
||||
- **小红书视频**:封面用“❌AI换脸毁了我的大单”黄色大字,文案增加情绪词“气死了”“凭什么”,评论区引导互动共鸣。
|
||||
@@ -0,0 +1,33 @@
|
||||
## 标题(爆款钩子)
|
||||
AI 预警快 15 分钟?别高兴太早,误报可能更要命!
|
||||
|
||||
## 口播文案
|
||||
大家都觉得 AI 预警龙卷风是救星,能抢回救命 15 分钟?[停顿]
|
||||
大错特错!
|
||||
如果告诉你,这多出来的 15 分钟,可能是把你骗进死胡同的“催命符”,你信吗?[停顿]
|
||||
传统雷达确实慢,要等风成型才报警。但 AI 太“敏感”了!
|
||||
去年美国中部那场大乌龙还记得吗?[停顿]
|
||||
AI 把一团普通云彩看成了超级龙卷风,几百万人半夜狂奔逃生,结果呢?连个风影都没见着。
|
||||
这就是最可怕的“狼来了”效应。[停顿]
|
||||
一旦误报多了,下次真龙卷风来了,你还会跑吗?
|
||||
你会想:“又是 AI 抽风吧?”然后继续睡觉。
|
||||
技术是快了,但信任没了,这才是最大的安全隐患。[停顿]
|
||||
所以,别盲目吹捧 AI 万能。
|
||||
在生死面前,快,不一定就是好。
|
||||
你觉得,是宁可错杀一千,还是宁可慢那十几分钟?[停顿]
|
||||
评论区告诉我你的选择。
|
||||
|
||||
## 分镜建议
|
||||
|
||||
| 镜头 | 画面 | 字幕 | 时长 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| 1 | 主播怼脸特写,表情严肃甚至带点质疑,手做“停止”手势 | AI 预警=救星?<br>大错特错! | 5s |
|
||||
| 2 | 快速剪辑:新闻画面中人群惊慌奔跑 vs 平静无风的街道(对比强烈) | 百万人大逃亡<br>结果:0 伤害 | 15s |
|
||||
| 3 | 主播侧身,背景出现“狼来了”的卡通插图或警报器失灵特效 | 狼来了效应<br>信任崩塌 | 15s |
|
||||
| 4 | 主播回到正脸,眼神诚恳,语速放缓,强调观点 | 快≠好<br>信任>速度 | 15s |
|
||||
| 5 | 主播抛出问题,身体前倾,引导互动,屏幕出现投票条样式 | 宁可错报?<br>还是宁可慢? | 10s |
|
||||
|
||||
## 适配平台
|
||||
- **抖音**:开头前 3 秒语速要更快,背景音乐用紧张的倒计时音效,强化冲突感。
|
||||
- **视频号**:在“狼来了”段落可插入真实的新闻误报片段截图,增加中老年观众的信服力。
|
||||
- **小红书**:封面图要用大字报风格写上“误报比迟到更可怕”,文案中增加 emoji 软化语气,引导讨论“安全感”。
|
||||
@@ -0,0 +1,21 @@
|
||||
## 标题(爆款钩子)
|
||||
龙卷风预警提前15分钟?3个救命技巧别等误报后悔
|
||||
|
||||
## 口播文案
|
||||
你知道吗?传统龙卷风预警平均只留给你13分钟![停顿]AI现在能提前15分钟,但误报率飙高。别慌,这3条干货,现在就能用上。
|
||||
|
||||
第一,别只盯手机短信。[停顿]气象局APP、本地应急广播全开,多个渠道多一重确认。第二,学会看天。漏斗云、旋转灰黑云团,就是死神敲门。别等警报响才跑,看到反常就撤。第三,提前找避难所。地下室、内部小房间,别躲车库或活动房。AI误报不怕,怕你麻痹大意。[停顿]记住,预警是提醒,自己预判才是保命符。评论区说说,你收到过假警报吗?
|
||||
|
||||
## 分镜建议
|
||||
| 镜头 | 画面 | 字幕 | 时长 |
|
||||
|------|------|------|------|
|
||||
| 1 | 主播面对镜头,手指比划数字“13”和“15” | 13分钟 vs 15分钟,谁更快? | 5秒 |
|
||||
| 2 | 切换手机预警通知截图,快速划过多个APP图标 | 多渠道预警,别只等短信 | 8秒 |
|
||||
| 3 | 插入龙卷风来临前天空实拍(漏斗云旋转) | 漏斗云=死神敲门,看到就跑 | 10秒 |
|
||||
| 4 | 主播手指向下指,背景出现地下室示意图 | 地下室最安全!别躲车库 | 8秒 |
|
||||
| 5 | 主播回归镜头,微笑提问 | AI误报不可怕,麻痹才要命 | 7秒 |
|
||||
|
||||
## 适配平台
|
||||
- **抖音**:前3秒用强冲突字幕“你的预警短信可能比死神晚到”,节奏更快,加热门音效。
|
||||
- **视频号**:开头加“点赞收藏保命”引导互动,结尾带话题#极端天气自救指南。
|
||||
- **小红书视频**:封面用醒目大字“3条龙卷风保命干货”,口播语速稍慢,强调“提前准备”。
|
||||
@@ -0,0 +1,32 @@
|
||||
## 标题(爆款钩子)
|
||||
龙卷风撕碎屋顶那刻,救命短信竟迟到了 13 分钟?
|
||||
|
||||
## 口播文案
|
||||
2026 年 7 月,湖北那个下午,老张正修着屋顶。[停顿]
|
||||
突然,天像被撕开个大口子,灰黑色的漏斗云直插下来。
|
||||
就在瓦片满天飞、老张下意识抱头的那一秒,手机终于响了。
|
||||
是一条红色预警短信。[停顿]
|
||||
可这时候,跑还来得及吗?
|
||||
这不是电影,是真实发生的“生死时间差”。
|
||||
传统雷达太“慢热”了,非要等龙卷风完全成型才报警,平均晚整整 13 分钟!
|
||||
对你我来说,这 13 分钟,就是生与死的距离。
|
||||
有人寄希望于 AI 能快那 15 分钟,可去年美国那场大规模误报,又让几百万人白跑一趟,吓出心脏病。[停顿]
|
||||
太快怕误报,太慢怕没命。
|
||||
在极端天气面前,我们到底该怎么跑赢死神?
|
||||
这道题,气象学家还在争,但留给我们的思考时间,真的不多了。[停顿]
|
||||
你觉得,这命该交给算法,还是交给经验?评论区聊聊。
|
||||
|
||||
## 分镜建议
|
||||
|
||||
| 镜头 | 画面描述 | 字幕关键词 | 时长 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| 1 | **特写 + 音效**:手机屏幕特写,显示时间跳动,背景音是呼啸的风声和瓦片碎裂声,突然弹出红色短信通知。 | 迟到的救命短信 | 5s |
|
||||
| 2 | **中景/情景再现**:一个中年男子(老张)在屋顶惊慌失措,背景是合成特效的灰黑色漏斗云逼近,色调压抑。 | 生死 13 分钟 | 8s |
|
||||
| 3 | **近景/口播**:博主面对镜头,神情严肃,手势比划出“时间差”的概念,背景可配一张传统雷达波形图。 | 传统雷达太“慢热” | 10s |
|
||||
| 4 | **快剪/资料画面**:快速闪过美国新闻画面,人群惊慌奔跑,随后打上大大的红色问号“误报?”。 | AI 也会“狼来了”? | 8s |
|
||||
| 5 | **特写/口播**:博主眼神直视镜头,抛出问题,画面逐渐变暗,只留眼神光。 | 你选速度还是准确? | 5s |
|
||||
|
||||
## 适配平台
|
||||
- **抖音**:前 3 秒必须把“瓦片乱飞 + 短信迟到”的冲突画面剪在一起,配合急促的音效,完播率才高。
|
||||
- **视频号**:文案结尾可加重“家人安全”的情感引导,评论区多引导中年用户分享身边的极端天气经历。
|
||||
- **小红书**:封面图要用“漏斗云实拍 + 大字报标题”,文案中增加“避雷指南”或“自救干货”的标签属性。
|
||||
@@ -0,0 +1,25 @@
|
||||
## 标题(爆款钩子)
|
||||
一道初中数学题,让GPT-4o集体死机:AI的数学智商到底有多差?
|
||||
|
||||
## 口播文案
|
||||
你敢信?[停顿]那个能写论文、写代码、碾压围棋的AI,竟然被韦东奕的一道初中数学题给整破防了![停顿]
|
||||
|
||||
GPT-4o、Claude 3.5,还有所谓的“专用数学模型”……全给出错误答案,甚至直接放弃![停顿]可这道题,就是一个聪明的高中生花半小时也能做出来。[停顿]
|
||||
|
||||
为什么?因为AI根本没有“数学直觉”。[停顿]它只会疯狂穷举、暴力检索,遇到需要构造辅助线、灵光一现的题,立马变成傻子。[停顿]
|
||||
|
||||
说白了,人类最后一道防线不是算力,而是那一瞬间的“灵感”。[停顿]你还在替AI焦虑?先问问自己:你的直觉,还活着吗?
|
||||
|
||||
## 分镜建议
|
||||
| 镜头 | 画面 | 字幕(关键词) | 时长 |
|
||||
|------|------|----------------|------|
|
||||
| 1 | 博主直视镜头,表情略带挑衅 | “一道初中数学题,AI集体死机” | 3秒 |
|
||||
| 2 | 快速闪过韦东奕照片,叠加大模型logo(GPT-4o、Claude等) | “韦神出题 vs 四大AI” | 5秒 |
|
||||
| 3 | 展示一张几何题图(辅助线题型)+红色叉号 | “全错!甚至直接放弃” | 4秒 |
|
||||
| 4 | 博主手势比划“思路”,背景出现灯泡图标 | “人类数学直觉:灵光一现” | 5秒 |
|
||||
| 5 | 博主摊手,嘴角微笑 | “你的直觉,还活着吗?” | 3秒 |
|
||||
|
||||
## 适配平台
|
||||
- **抖音**:前3秒用“你敢信?一道初中题”直接引爆好奇心,全程语速加快,每句后留0.5秒反应时间。
|
||||
- **视频号**:开头加一句“这可能是你今年听到最反认知的事”,结尾改成“转发给还在神化AI的朋友”增强社交互动。
|
||||
- **小红书视频**:首页配图文标题“AI被初中题碾压?真相扎心了”,全程语速放缓,突出“数学直觉”关键词,适合知识收藏。
|
||||
@@ -0,0 +1,27 @@
|
||||
## 标题(爆款钩子)
|
||||
韦神一道题让AI集体死机?3招保住你的数学直觉
|
||||
|
||||
## 口播文案
|
||||
你听说了吗?[停顿]韦东奕出了一道初中数学题,结果GPT-4o、Claude 3.5全体翻车,直接“死机”[停顿]凭什么?因为AI没有数学直觉,它只会套公式,不会构造辅助线。今天给你3条干货,让你在AI时代也能守住人类最后的防线。
|
||||
|
||||
第一条:别让AI替你思考。[停顿]遇到难题,先自己画图、写步骤,逼自己找突破口。AI给答案太快,你根本没机会训练直觉。
|
||||
|
||||
第二条:刻意练习“开放式问题”。[停顿]平面几何、数论证明这类题,没有标准解法,需要你构造辅助线、猜规律。每天一道,大脑就会长出“直觉肌肉”。
|
||||
|
||||
第三条:把AI当陪练,别当老师。[停顿]做完题,再用AI验证思路,看它怎么错。你发现它卡在哪,你就在哪赢了。
|
||||
|
||||
这三招,今天就能用,别等脑子生锈。评论区告诉我,你遇到过AI算不出来的题吗?
|
||||
|
||||
## 分镜建议
|
||||
| 镜头 | 画面 | 字幕 | 时长 |
|
||||
|------|------|------|------|
|
||||
| 1 | 近景:博主对着镜头,表情惊讶,手指比划 | 韦神一道题,AI集体死机? | 5秒 |
|
||||
| 2 | 中景:博主拿出一张白纸,画一条辅助线,然后摇头 | 没有数学直觉,只会套公式 | 8秒 |
|
||||
| 3 | 特写:博主用手势模仿思考,然后指向自己 | 别让AI替你思考,先自己画图 | 10秒 |
|
||||
| 4 | 中景:博主展示一张几何题,用笔圈出关键点 | 每天一道开放式问题,训练直觉肌肉 | 10秒 |
|
||||
| 5 | 近景:博主拿起手机,假装和AI对话,然后微笑 | 把AI当陪练,看它怎么错 | 8秒 |
|
||||
|
||||
## 适配平台
|
||||
- **抖音**:前3秒用“韦神+AI翻车”标题+悬念剪辑,节奏更快,字幕加粗放大。
|
||||
- **视频号**:增加“你同意吗?”结尾互动,结合朋友圈转发话术,强调“人类优势”。
|
||||
- **小红书视频**:标题改为“AI翻车后我悟了:3个数学直觉训练法”,封面用几何题+红色感叹号。
|
||||
@@ -0,0 +1,19 @@
|
||||
## 标题(爆款钩子)
|
||||
韦神一道初中题,AI全部“死机”——人类直觉到底有多强?
|
||||
|
||||
## 口播文案
|
||||
前几天,韦东奕出了一道初中数学题。[停顿]结果你猜怎么着?那些号称能写论文、编代码、替人类打工的顶级AI,像GPT-4o、Claude 3.5,居然集体翻车了。[停顿]是真的“死机”,连思路都理不清。[停顿]我当时看到这个新闻,心里咯噔一下——原来AI再厉害,也学不会那种灵光一现的直觉。[停顿]数学靠的是巧劲儿,是十几年甚至几十年磨出来的感觉。而AI呢?它只会算,不会“想”。[停顿]所以别老焦虑自己被替代了。有些东西,机器就是学不会。比如你脑子里的那根“辅助线”,比如你看到问题突然冒出来的那个解法。[停顿]这,才是我们人类的最后防线。
|
||||
|
||||
## 分镜建议
|
||||
| 镜头 | 画面 | 字幕 | 时长 |
|
||||
|------|------|------|------|
|
||||
| 1 | 镜头对准手机屏幕,展示韦东奕照片或新闻截图,快速拉近 | “韦神出题” | 5秒 |
|
||||
| 2 | 切到电脑屏幕,显示AI回答错误或加载失败动画,配合音效 | “GPT-4o、Claude 3.5……全懵了” | 8秒 |
|
||||
| 3 | 真人出镜,皱眉、摊手,语气惊讶 | “是不是觉得不可思议?” | 6秒 |
|
||||
| 4 | 镜头切回真人,一边说一边比划“辅助线”的手势 | “数学直觉,才是人类最后的防线” | 10秒 |
|
||||
| 5 | 真人微笑,屏幕下方弹出话题标签,结尾定格 | “有些东西,机器就是学不会” | 6秒 |
|
||||
|
||||
## 适配平台
|
||||
- **抖音**:前3秒用快节奏切屏和音效冲突感,字幕加emoji,结尾引导点赞评论“你觉得自己直觉准吗?”
|
||||
- **视频号**:适当放慢语速,保留停顿感,增加“你身边有没有这样的例子?”互动话术,适合中老年用户传播。
|
||||
- **小红书视频**:封面用韦东奕照片+“AI翻车”大字,正文加#人类直觉 #韦东奕 标签,文案侧重“普通人如何训练自己的直觉”。
|
||||
@@ -1,54 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "LIVING-001-26",
|
||||
"title": "零浪费家庭实验:从囤积到精简的100天",
|
||||
"cases": [],
|
||||
"audience": "26-35岁城市焦虑青年",
|
||||
"china_pain_points": "消费主义陷阱、快递包装泛滥、垃圾分类执行难",
|
||||
"localization_solution": "家庭减法清单、快递包装回收点地图、社区交换市集",
|
||||
"mvp_actions": "1. 本月减少50%快递包装 2. 建立家庭减法清单 3. 参与社区交换市集",
|
||||
"estimated_length": 3500,
|
||||
"priority_score": 85.5,
|
||||
"field": "可持续生活系统",
|
||||
"format": "趋势洞察 + 实操指南",
|
||||
"core_concept": "少即是多,从消费主义回归生活本质",
|
||||
"audience_pain": "被消费主义裹挟,想改变但不知道从何开始",
|
||||
"unique_angle": "用数据量化减法效果,用真实案例展示转变过程",
|
||||
"priority": "高",
|
||||
"total_score": 85.5,
|
||||
"compliance_score": 100,
|
||||
"source_file": "automation/data/sustainability_topics.json",
|
||||
"ready_at": "2026-05-08",
|
||||
"published_at": null,
|
||||
"platform_urls": {},
|
||||
"status": "待处理",
|
||||
"lock_by": null,
|
||||
"lock_at": null
|
||||
},
|
||||
{
|
||||
"id": "TECH-001-26",
|
||||
"title": "AI副业入门:用DeepSeek实现第一笔收入的100天",
|
||||
"cases": [],
|
||||
"audience": "26-35岁城市焦虑青年",
|
||||
"china_pain_points": "主业不稳定、副业方向迷茫、技能变现难",
|
||||
"localization_solution": "AI工具链整合、国内平台变现路径、从0到1的实战案例",
|
||||
"mvp_actions": "1. 学习DeepSeek基础操作 2. 找到第一个付费客户 3. 建立可复制的服务流程",
|
||||
"estimated_length": 4000,
|
||||
"priority_score": 88.2,
|
||||
"field": "科技向善",
|
||||
"format": "趋势洞察 + 实操指南",
|
||||
"core_concept": "用AI放大个人能力,实现副业收入",
|
||||
"audience_pain": "想搞钱但不知道从哪开始,担心AI替代自己",
|
||||
"unique_angle": "100天真实记录,从0到第一笔收入的完整路径",
|
||||
"priority": "高",
|
||||
"total_score": 88.2,
|
||||
"compliance_score": 100,
|
||||
"source_file": "automation/data/sustainability_topics.json",
|
||||
"ready_at": "2026-05-08",
|
||||
"published_at": null,
|
||||
"platform_urls": {},
|
||||
"status": "待处理",
|
||||
"lock_by": null,
|
||||
"lock_at": null
|
||||
}
|
||||
]
|
||||
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 136 KiB |
+73
-87
@@ -1,4 +1,4 @@
|
||||
# 信息源配置(可持续性相关)
|
||||
# 信息源配置(AI与科技人文领域)
|
||||
# 注意:仅保留国内可稳定访问的源,移除被墙/下线的源
|
||||
|
||||
sustainability_sources:
|
||||
@@ -9,16 +9,14 @@ sustainability_sources:
|
||||
url: "https://36kr.com/feed"
|
||||
update_frequency: "daily"
|
||||
credibility: "high"
|
||||
focus: "科技商业、绿色经济"
|
||||
focus: "科技商业、AI行业"
|
||||
keywords:
|
||||
- "AI"
|
||||
- "新能源"
|
||||
- "可持续"
|
||||
- "环保"
|
||||
- "绿色"
|
||||
- "低碳"
|
||||
- "ESG"
|
||||
- "循环"
|
||||
- "人工智能"
|
||||
- "大模型"
|
||||
- "科技"
|
||||
- "数字化"
|
||||
- "创业"
|
||||
- name: "少数派"
|
||||
type: "rss"
|
||||
url: "https://sspai.com/feed"
|
||||
@@ -31,99 +29,87 @@ sustainability_sources:
|
||||
- "工具"
|
||||
- "数字"
|
||||
- "智能"
|
||||
- "工作流"
|
||||
- name: "机器之心"
|
||||
type: "rss"
|
||||
url: "https://jiqizhixin.com/rss"
|
||||
update_frequency: "daily"
|
||||
credibility: "high"
|
||||
focus: "AI学术前沿、论文解读"
|
||||
keywords:
|
||||
- "AI"
|
||||
- "论文"
|
||||
- "大模型"
|
||||
- "算法"
|
||||
- "研究"
|
||||
- "学术"
|
||||
|
||||
# 搜索引擎采集(通过Bing中文搜索抓取热点)
|
||||
# 基于《2025中国可持续消费报告》《绿色低碳消费大数据报告》等权威调研得出的七大热点方向
|
||||
web_search:
|
||||
- name: "循环消费搜索"
|
||||
- name: "AI前沿资讯搜索"
|
||||
type: "web_search"
|
||||
query: "以旧换新 二手交易 闲置 循环 2026"
|
||||
query: "AI 人工智能 大模型 前沿 突破 2026"
|
||||
update_frequency: "daily"
|
||||
credibility: "medium"
|
||||
focus: "以旧换新/二手循环/租赁经济"
|
||||
- name: "低碳出行搜索"
|
||||
type: "web_search"
|
||||
query: "新能源车 骑行 绿色通勤 低碳出行 2026"
|
||||
update_frequency: "daily"
|
||||
credibility: "medium"
|
||||
focus: "新能源车/骑行/绿色出行"
|
||||
- name: "干净饮食搜索"
|
||||
type: "web_search"
|
||||
query: "干净饮食 有机食品 植物基 本地食材 2026"
|
||||
update_frequency: "daily"
|
||||
credibility: "medium"
|
||||
focus: "干净饮食/有机食品/植物基"
|
||||
- name: "零浪费生活搜索"
|
||||
type: "web_search"
|
||||
query: "零浪费 自带杯 极简生活 可持续时尚 2026"
|
||||
update_frequency: "daily"
|
||||
credibility: "medium"
|
||||
focus: "零浪费/自带杯/极简/可持续时尚"
|
||||
- name: "绿色家电搜索"
|
||||
type: "web_search"
|
||||
query: "绿色家电 一级能效 以旧换新 节能 2026"
|
||||
update_frequency: "daily"
|
||||
credibility: "medium"
|
||||
focus: "绿色家电/家庭节能/国补政策"
|
||||
- name: "碳普惠搜索"
|
||||
type: "web_search"
|
||||
query: "碳账户 碳普惠 个人碳减排 蚂蚁森林 2026"
|
||||
update_frequency: "daily"
|
||||
credibility: "medium"
|
||||
focus: "碳账户/碳普惠/个人碳减排"
|
||||
- name: "环保科技搜索"
|
||||
type: "web_search"
|
||||
query: "环保科技 绿色产品 可持续材料 2026"
|
||||
update_frequency: "daily"
|
||||
credibility: "medium"
|
||||
focus: "环保科技/可持续产品/绿色材料"
|
||||
focus: "AI行业动态、技术突破"
|
||||
- name: "AI工具搜索"
|
||||
type: "web_search"
|
||||
query: "AI工具 人工智能 效率提升 2026"
|
||||
query: "AI工具 效率提升 AI工作流 实用 2026"
|
||||
update_frequency: "daily"
|
||||
credibility: "medium"
|
||||
focus: "AI工具/效率方法"
|
||||
focus: "AI工具评测、效率方法"
|
||||
- name: "AI职场搜索"
|
||||
type: "web_search"
|
||||
query: "AI 裁员 职业转型 AI技能 远程工作 2026"
|
||||
update_frequency: "daily"
|
||||
credibility: "medium"
|
||||
focus: "AI对就业影响、职业转型"
|
||||
- name: "AI生活化搜索"
|
||||
type: "web_search"
|
||||
query: "AI陪伴 AI生活助手 AI心理咨询 智能体 2026"
|
||||
update_frequency: "daily"
|
||||
credibility: "medium"
|
||||
focus: "AI人格化、生活场景应用"
|
||||
- name: "科技人文搜索"
|
||||
type: "web_search"
|
||||
query: "AI伦理 数字生活 科技反思 人机关系 2026"
|
||||
update_frequency: "daily"
|
||||
credibility: "medium"
|
||||
focus: "AI伦理、科技人文反思"
|
||||
# ── 差异化源(与通用AI媒体形成区分)──
|
||||
- name: "AI学术论文搜索"
|
||||
type: "web_search"
|
||||
query: "arXiv 论文 AI 最新研究 大模型 2026"
|
||||
update_frequency: "weekly"
|
||||
credibility: "high"
|
||||
focus: "AI学术前沿、论文中文解读"
|
||||
- name: "独立开发者搜索"
|
||||
type: "web_search"
|
||||
query: "独立开发者 AI产品 一人公司 出海工具 indie hacking 2026"
|
||||
update_frequency: "daily"
|
||||
credibility: "medium"
|
||||
focus: "独立开发者生态、小众AI产品"
|
||||
- name: "行业数据报告搜索"
|
||||
type: "web_search"
|
||||
query: "AI行业报告 数据统计 市场分析 AI应用渗透率 2026"
|
||||
update_frequency: "daily"
|
||||
credibility: "high"
|
||||
focus: "行业数据、市场报告、AI应用统计"
|
||||
- name: "AI产品动态搜索"
|
||||
type: "web_search"
|
||||
query: "AI产品发布 更新 changelog 新功能 OpenAI Claude 2026"
|
||||
update_frequency: "daily"
|
||||
credibility: "medium"
|
||||
focus: "AI产品迭代、功能更新"
|
||||
|
||||
# 本地案例库(保底数据源)
|
||||
local:
|
||||
- name: "宇之然本地案例库"
|
||||
- name: "宇之然AI案例库"
|
||||
type: "local"
|
||||
file: "strategy/全球案例数据库-v1.md"
|
||||
file: "automation/data/ai_cases.json"
|
||||
update_frequency: "weekly"
|
||||
credibility: "medium"
|
||||
focus: "全球-本土案例"
|
||||
- name: "历史选题库"
|
||||
type: "local"
|
||||
file: "automation/data/sustainability_topics.json"
|
||||
update_frequency: "daily"
|
||||
credibility: "medium"
|
||||
focus: "复用已有选题"
|
||||
|
||||
# 案例验证要求
|
||||
validation:
|
||||
require_at_least_2_sources: false
|
||||
prefer_official_data: false
|
||||
min_data_points: 1
|
||||
|
||||
# 选题优先级权重
|
||||
topic_priority:
|
||||
audience_match: 0.3
|
||||
data_availability: 0.25
|
||||
uniqueness: 0.2
|
||||
executability: 0.15
|
||||
brand_fit: 0.1
|
||||
|
||||
# 可持续性子领域分类(基于2025-2026中国消费市场实际趋势调研)
|
||||
# 参考来源:《2025中国可持续消费报告》《绿色低碳消费大数据报告》商务部等9部门绿色消费20条
|
||||
sustainability_categories:
|
||||
- "循环消费" # 以旧换新/二手交易/租赁经济 | 2025年二手交易额1.69万亿,闲鱼6亿用户
|
||||
- "低碳出行" # 新能源车/骑行/绿色通勤 | 新能源车年产销破1000万辆,骑行爱好者超1800万
|
||||
- "干净饮食" # 有机食品/植物基/本地食材 | 有机食品1247亿销售,干净饮食笔记344万篇
|
||||
- "零浪费生活" # 自带杯/极简/可持续时尚 | 自带杯笔记277万篇,精简护肤121万篇,极简1186万讨论
|
||||
- "绿色家电与节能" # 一级能效/智能节能/以旧换新 | 一级能效家电占比90%+,国补政策明确导向
|
||||
- "碳普惠" # 碳账户/碳积分/个人碳减排 | 武汉200万市民碳账户,多城碳普惠平台上线
|
||||
- "环保科技产品" # 可持续材料/绿色产品/环保科技 | 消费者愿意为绿色产品支付溢价,但需可感知价值
|
||||
- "AI与效率" # AI工具/效率方法/数字助手 | 2026年AI深度融入消费与生活,消费链被AI重新定义
|
||||
focus: "AI行业案例"
|
||||
|
||||
# 内容合规检查清单
|
||||
compliance:
|
||||
@@ -136,5 +122,5 @@ compliance:
|
||||
required:
|
||||
- "至少3个可靠引用"
|
||||
- "数据标注来源"
|
||||
- "包含MVP行动"
|
||||
- "包含可操作建议"
|
||||
- "中性客观语气"
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
# 在上海阳台种菜一年,我收获了啥?
|
||||
|
||||
**领域**:自然 / 生活
|
||||
**形式**:实操指南 + 个人故事
|
||||
**预估字数**:2500
|
||||
**核心观点**:城市农业不仅是种菜,更是与自然重建连接的生活方式
|
||||
|
||||
**受众痛点**:
|
||||
- 想体验田园生活但没条件去农村
|
||||
- 担心食品安全,想自己种菜但不懂技术
|
||||
- 阳台空间小,不知道能种什么
|
||||
- 担心养不活植物,缺乏信心
|
||||
- 城市生活压力大,寻求放松方式
|
||||
|
||||
**独特角度**:
|
||||
- 不鼓吹"田园牧歌",客观展示失败和踩坑
|
||||
- 用数据说话(产量、成本、时间投入)
|
||||
- 结合科技元素(智能种植、垂直农业)
|
||||
- 从心理层面谈与植物建立连接的意义
|
||||
- 延伸讨论城市食物系统和可持续生活
|
||||
|
||||
**数据/案例**(至少3个来源):
|
||||
1. 上海阳台种植调研报告(找本地社区数据)
|
||||
2. 不同蔬菜的生长周期和产量数据(农业网站)
|
||||
3. 城市农业的心理学研究(减压、幸福感)
|
||||
4. 自己一年的种植记录(日志、照片)
|
||||
5. 智能种植设备对比(自动浇水、补光灯等)
|
||||
|
||||
**预估完成时间**:4天
|
||||
**优先级**:高
|
||||
**预计发布时间**:2026-04-15
|
||||
**状态**:待处理
|
||||
|
||||
---
|
||||
|
||||
## 选题评估矩阵
|
||||
|
||||
| 维度 | 评分 | 说明 |
|
||||
|------|------|------|
|
||||
| 受众覆盖 | 8 | 城市青年有共鸣,但相对垂直 |
|
||||
| 独特性 | 9 | 结合个人故事+科学种植+城市农业视角 |
|
||||
| 数据可得性 | 8 | 自己有一年数据,可补充调研 |
|
||||
| 可持续性 | 7 | 可写成系列(不同季节、不同植物) |
|
||||
| 平台契合度 | 9 | 知乎生活/园艺话题,小红书也适合 |
|
||||
| 品牌契合度 | 10 | 科技+自然+生活的完美结合 |
|
||||
| **总分** | **51** | 优先执行 ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 大纲草稿
|
||||
|
||||
### 开头(300字)
|
||||
- 场景:在上海20㎡阳台种出一个小菜园
|
||||
- 问题:很多人觉得不可能/太麻烦
|
||||
- 引子:一年的收获不仅是蔬菜,还有更多
|
||||
|
||||
### 主体(2000字)
|
||||
|
||||
**第一部分:为什么开始?(300字)**
|
||||
- 疫情后的食物焦虑
|
||||
- 对超市蔬菜的不信任
|
||||
- 想找回"土地连接感"
|
||||
- 初始期待(vs 现实)
|
||||
|
||||
**第二部分:阳台农业的真相(500字)**
|
||||
- 空间利用:垂直种植+合理规划
|
||||
- 品种选择:适合上海的蔬菜(番茄、辣椒、生菜、香草)
|
||||
- 成本核算:投入 vs 产出(经济账和心理账)
|
||||
- 时间投入:每天半小时,周末2小时
|
||||
- 失败案例分享(死掉的植物+经验)
|
||||
|
||||
**第三部分:科技让种菜变简单(400字)**
|
||||
- 智能灌溉系统(DIY vs 购买)
|
||||
- 补光灯的应用(光照不足的冬天)
|
||||
- 种植APP辅助(记录、提醒、社区)
|
||||
- 垂直农业设备介绍(Aerogarden等)
|
||||
- AI助手帮我分析植物健康(拍照识别病害)
|
||||
|
||||
**第四部分:收获的不仅是蔬菜(400字)**
|
||||
- 产量记录(一年收获多少斤)
|
||||
- 心理收获:减压、专注、成就感
|
||||
- 家庭关系改善(和家人一起照料)
|
||||
- 环保意义(减少碳足迹、包装垃圾)
|
||||
- 生活方式的改变(更关注季节性、本地食物)
|
||||
|
||||
**第五部分:给新手的建议(400字)**
|
||||
- 从简单的开始(香草、生菜)
|
||||
- 不要一次买太多植物
|
||||
- 必备工具清单(花盆、土、工具)
|
||||
- 常见问题和解决方案
|
||||
- 哪里获取信息和社区支持
|
||||
|
||||
### 结尾(200字)
|
||||
- 城市农业不是回归原始,而是科技赋能的现代生活选择
|
||||
- 哪怕只有一盆香草,也是与自然的连接
|
||||
- 呼吁:从小事做起,重建人与自然的联系
|
||||
- 预告:后续会分享具体种植技术细节
|
||||
|
||||
---
|
||||
|
||||
## 资源清单
|
||||
|
||||
### 数据来源(待收集)
|
||||
- [ ] 上海市社区农业调查报告
|
||||
- [ ] 阳台种植产量统计数据
|
||||
- [ ] 城市农业心理健康研究论文
|
||||
- [ ] 各蔬菜品种生长数据
|
||||
|
||||
### 图片素材(待准备)
|
||||
- [ ] 一年种植时间线照片
|
||||
- [ ] 不同生长阶段特写
|
||||
- [ ] 收获成果展示
|
||||
- [ ] 种植设施和工具照片
|
||||
|
||||
### 引用来源(待标注)
|
||||
- [ ] 农业技术网站
|
||||
- [ ] 学术数据库
|
||||
- [ ] 种植社区讨论
|
||||
- [ ] 科技产品官网
|
||||
|
||||
---
|
||||
|
||||
**决策**:这是一个高潜力选题,品牌契合度极高,预计能获得良好反响。建议优先执行。
|
||||
@@ -1,143 +0,0 @@
|
||||
# AI时代,什么能力不会被替代?
|
||||
|
||||
**领域**:科技 / 职场
|
||||
**形式**:趋势洞察 + 实操建议
|
||||
**预估字数**:2800
|
||||
**核心观点**:AI淘汰的不是工作,而是不会使用AI的人;但有些"人类特质"能力反而更珍贵
|
||||
|
||||
**受众痛点**:
|
||||
- 担心被AI替代,职业焦虑严重
|
||||
- 不知道如何提升自己的竞争力
|
||||
- 听说AI很强,但不知道学什么
|
||||
- 想转行但方向迷茫
|
||||
- 担心投入学习最后还是被淘汰
|
||||
|
||||
**独特角度**:
|
||||
- 区分配"被AI强化的能力"和"AI无法替代的能力"
|
||||
- 从历史技术进步规律看职业演变(不是第一次自动化革命)
|
||||
- 强调"审美、共情、意义构建"等软技能
|
||||
- 给出具体的能力提升路径,不是泛泛而谈
|
||||
- 结合宇之然"科技沟通万物,回归自然"的哲学
|
||||
|
||||
**数据/案例**(至少3个来源):
|
||||
1. 世界经济论坛《未来就业报告》技能趋势数据
|
||||
2. 美国劳工统计局职业自动化概率研究
|
||||
3. AI能力边界研究(GPT-4o vs 人类能力对比)
|
||||
4. 历史案例:工业革命、计算机革命后的职业变化
|
||||
5. 访谈案例:哪些岗位在AI时代反而增值?
|
||||
|
||||
**预估完成时间**:5天
|
||||
**优先级**:高
|
||||
**预计发布时间**:2026-04-18
|
||||
**状态**:待处理
|
||||
|
||||
---
|
||||
|
||||
## 选题评估矩阵
|
||||
|
||||
| 维度 | 评分 | 说明 |
|
||||
|------|------|------|
|
||||
| 受众覆盖 | 9 | 所有职场人士都关心,流量潜力大 |
|
||||
| 独特性 | 8 | 避开"AI工具教程"红海,聚焦深度思考 |
|
||||
| 数据可得性 | 9 | 大量研究报告和历史数据 |
|
||||
| 可持续性 | 8 | 可写成系列(不同行业、不同能力维度) |
|
||||
| 平台契合度 | 9 | 知乎职场/科技话题热门 |
|
||||
| 品牌契合度 | 10 | 科技向善+人文关怀,契合价值观 |
|
||||
| **总分** | **53** | 优先执行 ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 大纲草稿
|
||||
|
||||
### 开头(400字)
|
||||
- 现象:朋友被AI工具震撼,担心失业
|
||||
- 核心问题:AI时代,我们该学什么?
|
||||
- 观点:AI淘汰的不是工作,而是不会使用AI的人;但有些"人类特质"能力反而更珍贵
|
||||
- 预告:本文将分析AI的能力边界,并给出具体的技能提升建议
|
||||
|
||||
### 主体(2200字)
|
||||
|
||||
**第一部分:AI到底有多强?(400字)**
|
||||
- 当前AI能力范围(文本、图像、代码、数据分析)
|
||||
- AI的局限性:没有意识、没有真实体验、无法承担责任
|
||||
- AI的依赖:需要人类提供高质量prompt、数据、验证
|
||||
- 数据:哪些职业自动化概率高(根据研究报告)
|
||||
- 事实:AI不是万能,只是工具
|
||||
|
||||
**第二部分:历史给我们的启示(300字)**
|
||||
- 工业革命:机器取代体力劳动,但创造了新职业
|
||||
- 计算机革命:打字员消失,但程序员、设计师兴起
|
||||
- 每次技术革命都淘汰旧技能,但同时创造更多新技能
|
||||
- 规律:重复性工作易被替代,创造性、社交性工作更难
|
||||
- 结论:不恐惧技术,而是学会驾驭技术
|
||||
|
||||
**第三部分:AI无法替代的3类能力(800字)**
|
||||
|
||||
**A. 审美与创造力**
|
||||
- AI生成的美术、音乐、文字缺少"灵魂"
|
||||
- 真正的好设计需要理解人的情感和文化
|
||||
- 创意工作者(作家、艺术家、设计师)的价值被提升而非降低
|
||||
- 案例:AI辅助创作,但最终决策权在人
|
||||
- 如何培养审美能力?(艺术欣赏、多元输入、实践创作)
|
||||
|
||||
**B. 共情与沟通**
|
||||
- AI无法真正理解人类情感
|
||||
- 医护、教育、管理、咨询等职业的"人味"不可替代
|
||||
- 高情商沟通、冲突调解、心理支持
|
||||
- 案例:AI心理咨询的局限性
|
||||
- 如何提升共情能力?(倾听、换位思考、情绪识别)
|
||||
|
||||
**C. 意义构建与战略思维**
|
||||
- AI能提供数据,但不能替你做价值判断
|
||||
- 决策需要理解"为什么",而不仅是"怎么做"
|
||||
- 战略规划、伦理判断、文化塑造
|
||||
- 案例:企业战略制定,AI只能辅助分析
|
||||
- 如何培养战略思维?(跨领域学习、哲学思考、长期视角)
|
||||
|
||||
**第四部分:AI会强化的能力(400字)**
|
||||
- 学习能力(快速掌握新工具)
|
||||
- 信息筛选与批判性思维(辨别AI输出质量)
|
||||
- 工具使用能力(prompt engineering、AI工作流设计)
|
||||
- 协同能力(人机协作,AI作为副驾驶)
|
||||
- 快速迭代能力(AI加速实验和反馈)
|
||||
|
||||
**第五部分:具体能力提升路径(300字)**
|
||||
- 初级(0-1年):掌握基础AI工具,提升学习效率
|
||||
- 中级(1-3年):发展审美、沟通、批判性思维
|
||||
- 高级(3-5年):建立战略思维,成为AI无法替代的决策者
|
||||
- 学习资源推荐(书籍、课程、实践方法)
|
||||
- 宇之然品牌如何帮助读者提升这些能力?(预告后续内容)
|
||||
|
||||
### 结尾(200字)
|
||||
- AI不是敌人,而是工具
|
||||
- 人类的优势在于"人性":审美、共情、意义构建
|
||||
- 建议:拥抱AI提升效率,同时刻意训练人类特质能力
|
||||
- 呼吁:不要焦虑,而是主动进化
|
||||
- 互动:你在工作中感受到AI的哪些影响?欢迎评论区分享
|
||||
|
||||
---
|
||||
|
||||
## 资源清单
|
||||
|
||||
### 数据来源(待收集)
|
||||
- [ ] WEF《未来就业报告》最新版
|
||||
- [ ] 美国劳工统计局职业自动化概率表
|
||||
- [ ] OpenAI/GPT能力边界研究论文
|
||||
- [ ] 历史技术革命对就业影响的经济学研究
|
||||
- [ ] AI能力与人类能力对比可视化数据
|
||||
|
||||
### 图片素材(待准备)
|
||||
- [ ] AI能力范围示意图
|
||||
- [ ] 自动化概率职业分布图
|
||||
- [ ] 技能矩阵图(会被替代 vs 更珍贵)
|
||||
- [ ] 能力提升路径时间线
|
||||
|
||||
### 引用来源(待标注)
|
||||
- [ ] 世界经济论坛官网
|
||||
- [ ] 学术期刊数据库
|
||||
- [ ] 行业研究报告
|
||||
- [ ] 知名专家观点
|
||||
|
||||
---
|
||||
|
||||
**决策**:这是一个必爆的热门选题,但内容必须深度且有数据支撑,不能流于表面。需要较强的信息整合能力。建议medium-high优先级执行。
|
||||
@@ -1,135 +0,0 @@
|
||||
# 从程序员到数字游民:我的三年转型之路
|
||||
|
||||
**领域**:工作 / 职场
|
||||
**形式**:个人故事 + 实操指南
|
||||
**预估字数**:2600
|
||||
**核心观点**:数字游民不是逃离,而是更聪明的工作生活方式选择
|
||||
|
||||
**受众痛点**:
|
||||
- 受够了996,想自由但不敢行动
|
||||
- 不知道数字游民能做什么,收入从哪来
|
||||
- 担心不稳定、没社保、老了怎么办
|
||||
- 想远程但公司不让,不知如何说服老板
|
||||
- 羡慕别人环游世界,自己却卡在工位
|
||||
|
||||
**独特角度**:
|
||||
- 不美化"边玩边赚",客观展示挑战和代价
|
||||
- 从技术转型案例切入(程序员是最适合数字游民的职业之一)
|
||||
- 用数据说话:收入对比、时间分配、成本核算
|
||||
- 结合"宇之然"理念:科技让人自由,而非束缚
|
||||
- 提供可操作的转型路线图(不是鸡汤)
|
||||
|
||||
**数据/案例**(至少3个来源):
|
||||
1. 数字游民调研报告(人数、收入、职业分布)
|
||||
2. 不同国家的签证政策和生活成本对比(数字游民签证国家)
|
||||
3. 远程工作平台收入数据(Upwork、Toptal等)
|
||||
4. 个人三年转型历程(时间线、收入变化、心路历程)
|
||||
5. 成功/失败数字游民案例访谈
|
||||
|
||||
**预估完成时间**:5天
|
||||
**优先级**:高
|
||||
**预计发布时间**:2026-04-20
|
||||
**状态**:待处理
|
||||
|
||||
---
|
||||
|
||||
## 选题评估矩阵
|
||||
|
||||
| 维度 | 评分 | 说明 |
|
||||
|------|------|------|
|
||||
| 受众覆盖 | 9 | 大量职场人向往自由工作,流量潜力巨大 |
|
||||
| 独特性 | 8 | 避开"如何找远程工作"表层,深入转型战略 |
|
||||
| 数据可得性 | 8 | 调研报告多,个人案例丰富 |
|
||||
| 可持续性 | 9 | 可系列化(不同职业、不同国家、不同阶段) |
|
||||
| 平台契合度 | 9 | 知乎职场/创业热门话题 |
|
||||
| 品牌契合度 | 10 | 科技提升生活自由度,契合"科技沟通万物" |
|
||||
| **总分** | **53** | 优先执行 ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 大纲草稿
|
||||
|
||||
### 开头(400字)
|
||||
- 场景:三年前坐在格子间,加班到深夜,问自己"这就是我想要的生活吗?"
|
||||
- 转折:发现程序员完全可以远程,开始转型尝试
|
||||
- 现状:现在在清迈咖啡馆工作,月收入 comparable,但生活品质完全不同
|
||||
- 核心观点:数字游民不是逃避,而是更聪明的生活方式选择
|
||||
|
||||
### 主体(2200字)
|
||||
|
||||
**第一部分:数字游民的真实生活(400字)**
|
||||
- 破除神话(不是天天旅游、不是躺着赚钱)
|
||||
- 真实时间表:上午工作、下午探索、晚上复盘
|
||||
- 收入稳定性如何保障?(多个客户、项目制)
|
||||
- 孤独感和社交挑战(社群、Co-working)
|
||||
- 数据:平均收入、工作小时、留存率
|
||||
|
||||
**第二部分:为什么程序员适合?(300字)**
|
||||
- 技能可远程交付(代码在哪都能写)
|
||||
- 高需求(全球IT人才短缺)
|
||||
- 收入高且相对稳定
|
||||
- 学习资源丰富(可自学新技能)
|
||||
- 本人的转型路径(从全职到自由职业)
|
||||
|
||||
**第三部分:转型前的准备(500字)**
|
||||
- 财务准备:6-12个月紧急基金
|
||||
- 技能盘点:哪些技能可远程变现?
|
||||
- 作品集建设:GitHub、个人项目、案例
|
||||
- 平台选择:Upwork、Toptal、电鸭、远程工作社区
|
||||
- 先从兼职开始,不要裸辞
|
||||
- 法律和社保:如何自己交?国际医保?
|
||||
|
||||
**第四部分:我的三年路线图(400字)**
|
||||
- 第1年:保存+考证+接小项目(月入5k)
|
||||
- 第2年:建立客户群+提价+过渡(月入15k)
|
||||
- 第3年:稳定客户+被动收入+探索(月入20k+)
|
||||
- 关键节点和决策点
|
||||
- 踩过的坑(低价竞争、客户跑路、签证问题)
|
||||
|
||||
**第五部分:在哪里生活?(300字)**
|
||||
- 数字游民热门城市对比(清迈、巴厘岛、里斯本、东京)
|
||||
- 成本核算(住宿、交通、签证、保险)
|
||||
- 网络和基础设施(稳定WiFi、Coworking)
|
||||
- 社区和社交(容易找到同类人)
|
||||
- 安全和生活便利性
|
||||
|
||||
**第六部分:给想转型者的建议(300字)**
|
||||
- 先试水:请假去远程工作一个月
|
||||
- 不要孤注一掷:保持至少一个稳定收入源
|
||||
- 建立系统:不只是接单,而是建立个人品牌
|
||||
- 心理准备:自由带来责任,自律才能自由
|
||||
- "宇之然"理念:科技让人自由,但自由需要能力支撑
|
||||
|
||||
### 结尾(200字)
|
||||
- 数字游民不是适合所有人,但值得尝试
|
||||
- 关键是找到适合自己的工作生活方式
|
||||
- 鼓励读者思考:我想要什么样的生活?科技能帮我实现吗?
|
||||
- 预告:后续分享具体技能提升路线(程序员转远程)
|
||||
|
||||
---
|
||||
|
||||
## 资源清单
|
||||
|
||||
### 数据来源(待收集)
|
||||
- [ ] 数字游民数量增长数据( Nomad List 、相关报告)
|
||||
- [ ] 程序员远程工作薪资调研
|
||||
- [ ] 各国数字游民签证政策汇总
|
||||
- [ ] Coworking 空间成本统计
|
||||
- [ ] IT自由职业平台收入报告
|
||||
|
||||
### 图片素材(待准备)
|
||||
- [ ] 三年转型时间线图
|
||||
- [ ] 不同城市成本对比图表
|
||||
- [ ] 收入变化曲线
|
||||
- [ ] 世界各地工作地点照片(清迈咖啡馆等)
|
||||
- [ ] Coworking 空间环境图
|
||||
|
||||
### 引用来源(待标注)
|
||||
- [ ] Nomad List 官网数据
|
||||
- [ ] 数字游民社区统计
|
||||
- [ ] 各国签证政策官网
|
||||
- [ ] 远程工作平台文档
|
||||
|
||||
---
|
||||
|
||||
**决策**:这是一个高流量选题,竞争也激烈,需要深度个人故事+系统化建议才能脱颖而出。建议 medium-high 优先级。
|
||||
@@ -1,143 +0,0 @@
|
||||
# 零浪费生活一年:一个家庭产生的垃圾去哪儿了?
|
||||
|
||||
**领域**:自然 / 生活
|
||||
**形式**:实验记录 + 科普指南
|
||||
**预估字数**:2300
|
||||
**核心观点**:零浪费不是苦行,而是更聪明的消费选择;个人行动虽小,但能改变系统
|
||||
|
||||
**受众痛点**:
|
||||
- 想环保但不知道从何下手
|
||||
- 觉得"我一个人做有什么用"
|
||||
- 担心成本太高,负担不起
|
||||
- 嫌麻烦,步骤太多坚持不下去
|
||||
- 家人不理解,无法统一战线
|
||||
- 超市买买买停不下来,事后又后悔
|
||||
|
||||
**独特角度**:
|
||||
- 不道德绑架,用数据和成本说服
|
||||
- 展示失败和妥协(不是完美零浪费,而是持续改进)
|
||||
- 从家庭视角(夫妻+孩子)而非个人
|
||||
- 连接科技:用App记录、智能采购、循环经济平台
|
||||
- 强调经济收益(省了多少钱)而非道德优越
|
||||
|
||||
**数据/案例**(至少3个来源):
|
||||
1. 中国城市生活垃圾产生量数据(统计年鉴)
|
||||
2. 不同包装方式的碳足迹对比(生产、运输、处理)
|
||||
3. 零浪费家庭采访或书籍(如Bea Johnson, 0 Waste Home)
|
||||
4. 自己一年的垃圾记录(称重、分类、分析)
|
||||
5. 环保产品性价比对比(可重复使用 vs 一次性)
|
||||
|
||||
**预估完成时间**:4天
|
||||
**优先级**:高
|
||||
**预计发布时间**:2026-04-22
|
||||
**状态**:待处理
|
||||
|
||||
---
|
||||
|
||||
## 选题评估矩阵
|
||||
|
||||
| 维度 | 评分 | 说明 |
|
||||
|------|------|------|
|
||||
| 受众覆盖 | 8 | 环保意识提升,但执行力不足的人多 |
|
||||
| 独特性 | 9 | 家庭实验视角,真实数据,不教条 |
|
||||
| 数据可得性 | 8 | 垃圾数据、环保报告、产品对比可获取 |
|
||||
| 可持续性 | 8 | 可系列(厨余、包装、衣物、电子产品等) |
|
||||
| 平台契合度 | 8 | 知乎生活/环保话题,小红书也适合 |
|
||||
| 品牌契合度 | 10 | 自然+科技+生活,完美契合价值观 |
|
||||
| **总分** | **51** | 优先执行 ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 大纲草稿
|
||||
|
||||
### 开头(300字)
|
||||
- 场景:2025年1月1日,我在家门口放了一个垃圾桶,记录一整年的垃圾
|
||||
- 问题:一个家庭一年会产生多少垃圾?它们最后去哪了?
|
||||
- 引子:零浪费不是不吃不喝,而是 smarter consumption
|
||||
- 预告:分享一年实验数据、踩坑经验、经济收益
|
||||
|
||||
### 主体(1800字)
|
||||
|
||||
**第一部分:我们这一年扔了啥?(300字)**
|
||||
- 垃圾总量:XX公斤(对比全国人均)
|
||||
- 分类数据:塑料、包装、厨余、其他
|
||||
- 月度趋势(节假日更多,夏季更多)
|
||||
- 最大来源:外卖包装、快递箱、食品包装
|
||||
- 可视化:饼图+月度折线图
|
||||
|
||||
**第二部分:我们是怎么做到的?(400字)**
|
||||
- 原则5R:Refuse(拒绝不需要的)、Reduce(减少)、Reuse(重复使用)、Recycle(回收)、Rot(堆肥)
|
||||
- 购物习惯改变:
|
||||
- 带袋子买菜(拒绝塑料袋)
|
||||
- 散装优先(减少包装)
|
||||
- 耐用品替代一次性(硅胶保鲜膜、可重复使用的咖啡杯)
|
||||
- 二手优先(家具、电子产品、衣物)
|
||||
- 厨余处理:堆肥桶+社区堆肥点
|
||||
- 家庭共识:夫妻+孩子的参与,不是一个人负重前行
|
||||
- 妥协:不可能100%,目标是逐年降低
|
||||
|
||||
**第三部分:科技让零浪费变简单(300字)**
|
||||
- 零浪费购物App(包, 乐活等)
|
||||
- 智能库存管理(避免买多导致浪费)
|
||||
- 社区交换平台(闲鱼、小区群)
|
||||
- 可降解产品追踪(哪些真的环保?)
|
||||
- 碳足迹计算器(量化自己的贡献)
|
||||
|
||||
**第四部分:经济账(200字)**
|
||||
- 初始投入:玻璃罐、棉布袋、不锈钢饭盒(XXX元)
|
||||
- 每月节省:减少外卖/包装(XXX元)、二手购物(XXX元)
|
||||
- 一年净节省:XXXX元(ROI 200%+)
|
||||
- 隐性收益:更健康饮食(少点外卖),更理性消费
|
||||
- 环保不是富人游戏,穷人更应该
|
||||
|
||||
**第五部分:挑战与妥协(300字)**
|
||||
- 家人不配合:老人舍不得扔旧物,孩子要零食包装
|
||||
- 社交压力:聚会自带杯子被嘲笑
|
||||
- 便利性:有时候真的没时间准备
|
||||
- 产品缺失:市场上零浪费选择太少
|
||||
- 我们没有做到的地方:电子产品循环难、医疗包装无法避免
|
||||
- 心态调整:进步不是完美,可持续才是关键
|
||||
|
||||
**第六部分:我们的建议(300字)**
|
||||
- 从最容易的3件事开始:带袋子、少点外卖、二手优先
|
||||
- 设定合理目标(比如年度垃圾减量20%)
|
||||
- 记录数据(否则不知道进步)
|
||||
- 找到社区(一个人走不远)
|
||||
- 关注政策(垃圾分类、再生资源回收)
|
||||
- 科技赋能(用工具降低门槛)
|
||||
- "宇之然"理念:零浪费不是回到原始,而是科技赋能的智能生活
|
||||
|
||||
### 结尾(200字)
|
||||
- 一年垃圾量:XXX公斤(对比第一年减少XX%)
|
||||
- 零浪费不是苦行,是更高质量的生活
|
||||
- 每一个小行动都在影响系统
|
||||
- 呼吁:不要追求完美,追求持续改进
|
||||
- 互动:你的零浪费实践是什么?欢迎分享
|
||||
|
||||
---
|
||||
|
||||
## 资源清单
|
||||
|
||||
### 数据来源(待收集)
|
||||
- [ ] 中国城市生活垃圾产生量年度数据(国家统计局)
|
||||
- [ ] 各类包装碳足迹数据(学术论文)
|
||||
- [ ] 零浪费家庭案例(国内外博客、书籍)
|
||||
- [ ] 零浪费产品成本与一次性对比
|
||||
- [ ] 社区堆肥项目案例
|
||||
|
||||
### 图片素材(待准备)
|
||||
- [ ] 一年垃圾记录照片(分类展示)
|
||||
- [ ] 零浪费购物装备(布袋、罐子、饭盒)
|
||||
- [ ] 堆肥桶和成果
|
||||
- [ ] 数据图表(垃圾量趋势、分类占比、经济收益)
|
||||
- [ ] 前后对比(购物方式、垃圾桶状态)
|
||||
|
||||
### 引用来源(待标注)
|
||||
- [ ] 零浪费运动创始人Bea Johnson
|
||||
- [ ] 《零浪费生活》等书籍
|
||||
- [ ] 环保组织报告(如WWF、Greenpeace)
|
||||
- [ ] 国内零 waste community
|
||||
|
||||
---
|
||||
|
||||
**决策**:实验性质内容,容易引发共鸣和讨论,互动率高。适合知乎和小红书。建议high优先级。
|
||||
@@ -1,166 +0,0 @@
|
||||
# 深度工作实践:如何在干扰世界中保持专注
|
||||
|
||||
**领域**:工作 / 生产力
|
||||
**形式**:实操指南 + 个人实验
|
||||
**预估字数**:2400
|
||||
**核心观点**:专注力是新时代最稀缺的资源,通过系统方法可以重建深度工作能力
|
||||
|
||||
**受众痛点**:
|
||||
- 每天忙碌但没产出,时间碎片化
|
||||
- 容易被手机、消息、同事打断
|
||||
- 知道要专注,但就是控制不住刷手机
|
||||
- 工作1小时,实际只有30分钟在干活
|
||||
- 想要心流状态但总进不去
|
||||
- 下班累但没成就感
|
||||
|
||||
**独特角度**:
|
||||
- 不鸡汤,用实验数据和自我追踪说话
|
||||
- 结合科技工具(Forest、番茄钟、白噪音)和传统方法
|
||||
- 承认现代工作环境的现实(完全隔离不现实),提供折中方案
|
||||
- 从神经科学和心理学角度解释why,不只是how
|
||||
- 强调"深度休息"与"深度工作"的交替
|
||||
|
||||
**数据/案例**(至少3个来源):
|
||||
1. Cal Newport《深度工作》理论体系
|
||||
2. 注意力恢复理论(Attention Restoration Theory)
|
||||
3. 番茄工作法实证研究(效果数据)
|
||||
4. 个人3个月实验数据(专注时间、任务完成量、压力水平)
|
||||
5. 知识工作者专注力调研( interruptions cost)
|
||||
|
||||
**预估完成时间**:4天
|
||||
**优先级**:高
|
||||
**预计发布时间**:2026-04-23
|
||||
**状态**:待处理
|
||||
|
||||
---
|
||||
|
||||
## 选题评估矩阵
|
||||
|
||||
| 维度 | 评分 | 说明 |
|
||||
|------|------|------|
|
||||
| 受众覆盖 | 9 | 所有知识工作者都需要,流量潜力极大 |
|
||||
| 独特性 | 7 | 话题热门,需要用数据和实验脱颖而出 |
|
||||
| 数据可得性 | 9 | 大量研究文献和工具可用 |
|
||||
| 可持续性 | 8 | 可系列(不同环境、不同工具、不同性格) |
|
||||
| 平台契合度 | 9 | 知乎职场/效率热门,小红书打卡也适合 |
|
||||
| 品牌契合度 | 8 | 科技辅助专注,但最终回归人的本质 |
|
||||
| **总分** | **50** | 优先执行 ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 大纲草稿
|
||||
|
||||
### 开头(300字)
|
||||
- 场景:上周写一份报告,计划4小时,实际花了8小时,中间刷了20次手机
|
||||
- 问题:为什么我们越来越忙,产出却越来越少?
|
||||
- 观点:不是时间不够,是注意力被碎片化
|
||||
- 预告:分享3个月深度工作实验,数据+方法+工具
|
||||
|
||||
### 主体(1900字)
|
||||
|
||||
**第一部分:专注力危机(300字)**
|
||||
- 数据:平均专注时长从26分钟降到8分钟(Microsoft研究)
|
||||
- 知识工作者每天被中断多少次?(59次/天,平均)
|
||||
- 切换成本:每次中断后需要23分钟回到深度状态
|
||||
- 现代工作环境:开放式办公室、即时通讯、通知轰炸
|
||||
- 后果:疲惫、低效、创造力下降
|
||||
|
||||
**第二部分:什么是深度工作?(200字)**
|
||||
- Cal Newport定义:无干扰的认知挑战性工作
|
||||
- 与肤浅工作的对比(容易回复的邮件、会议、行政事务)
|
||||
- 为什么深度工作有价值?(高价值产出、技能增长、成就感)
|
||||
- 神经科学解释:默认模式网络 vs 专注模式
|
||||
- 个人实验目标:将每日深度工作时间从1h提升到4h
|
||||
|
||||
**第三部分:我的3个月实验设计(200字)**
|
||||
- 基线测量:第一周不干预,记录专注情况(RescueTime数据)
|
||||
- 变量引入:每周测试一种方法(时间块、地点、工具)
|
||||
- 评估指标:深度工作时长、任务完成量、主观疲劳度
|
||||
- 数据收集:自动追踪+每日日志
|
||||
- 工具:Forest、番茄钟、白噪音、时间追踪App
|
||||
|
||||
**第四部分:7个有效方法(800字)**
|
||||
1. **时间块法**:每天固定2-3个90分钟深度时段(早上最佳)
|
||||
- 如何保护这些时段?(关闭通知、告知同事)
|
||||
- 案例:我如何说服老板让我上午不参加会议
|
||||
|
||||
2. **地点隔离**:物理空间决定心理状态
|
||||
- 家里打造深度工作角(哪怕只有一张桌子)
|
||||
- 共享办公/图书馆vs咖啡馆(哪个更好?)
|
||||
- 通勤时间利用(地铁上不适合深度,但可做浅层)
|
||||
|
||||
3. **工具辅助**:科技帮你专注,而不是分心
|
||||
- Forest种树:游戏化保持专注
|
||||
- Cold Turkey Blocker:屏蔽 distracting sites
|
||||
- 白噪音/自然声:掩盖环境噪音
|
||||
- 双显示器设置(主屏工作,副屏消息)
|
||||
|
||||
4. **仪式感建立**:让大脑进入"工作模式"
|
||||
- 开始前:一杯茶+5分钟冥想+清单
|
||||
- 结束时:总结+小奖励
|
||||
- 服装:穿"工作装"即使在家
|
||||
|
||||
5. **批处理浅层工作**:集中处理邮件、消息、会议
|
||||
- 每天2个固定时段(下午3点、5点)
|
||||
- 其他时间绝不查看
|
||||
- 快速回复模板
|
||||
|
||||
6. **注意力训练**:像肌肉一样训练专注
|
||||
- 每天10分钟正念冥想(Headspace)
|
||||
- 单任务练习(吃饭不刷手机)
|
||||
- 阅读长文章(训练持续注意力)
|
||||
|
||||
7. **深度休息**:专注需要高质量的休息
|
||||
- 90分钟工作+15分钟休息(非屏幕活动)
|
||||
- 午休必须离开工位
|
||||
- 晚上完全断开(数字安息日)
|
||||
- 睡眠优先(专注力的基础)
|
||||
|
||||
**第五部分:实验结果与数据(300字)**
|
||||
- 深度工作时长变化:基线1.2h/天 → 实验后3.8h/天
|
||||
- 任务完成量提升:每周完成项目数从2个→5个
|
||||
- 主观疲劳度下降:从每天7分(10分制)降到4分
|
||||
- 最有用的方法:时间块法(增加1.5h)+ 工具辅助(增加0.8h)
|
||||
- 意外发现:地点隔离效果不明显(家里角落即可)
|
||||
|
||||
**第六部分:常见障碍与解决方案(200字)**
|
||||
- 同事/老板随时找你怎么办?(设置"专注时段"通知)
|
||||
- 自己忍不住刷手机怎么办?(物理隔离手机)
|
||||
- 老板要求即时回复怎么办?(协商沟通规则)
|
||||
- 创意型工作无法计划怎么办?(灵感记录+深度时间块)
|
||||
- 适应期烦躁怎么办?(从1小时开始,逐步增加)
|
||||
|
||||
### 结尾(200字)
|
||||
- 深度工作不是天赋,是可以训练的技能
|
||||
- 关键不是完美,而是持续改进
|
||||
- 科技让我们更容易分心,但也给了我们工具对抗
|
||||
- "宇之然"理念:专注才能产生深度价值
|
||||
- 挑战:从明天开始,尝试一个90分钟的时间块
|
||||
|
||||
---
|
||||
|
||||
## 资源清单
|
||||
|
||||
### 数据来源(待收集)
|
||||
- [ ] Microsoft 注意力跨度研究报告
|
||||
- [ ] RescueTime 知识工作者专注数据
|
||||
- [ ] 番茄工作法效果 meta-analysis
|
||||
- [ ] 神经科学关于注意力切换的研究
|
||||
- [ ] 正念冥想对专注力的影响
|
||||
|
||||
### 图片素材(待准备)
|
||||
- [ ] 深度工作时长趋势图(实验前后对比)
|
||||
- [ ] 7种方法示意图
|
||||
- [ ] 时间块安排示例(日历截图)
|
||||
- [ ] 专注工具界面截图(Forest、白噪音)
|
||||
- [ ] 实验数据表格
|
||||
|
||||
### 引用来源(待标注)
|
||||
- [ ] Cal Newport《深度工作》
|
||||
- [ ] 《倦怠社会》等社会学视角
|
||||
- [ ] 心理学研究(注意力、心流)
|
||||
- [ ] 工具官网和教程
|
||||
|
||||
---
|
||||
|
||||
**决策**:非常实用的主题,数据驱动,易引发收藏和转发。适合知乎收藏夹和小红书打卡。建议 high 优先级。
|
||||
@@ -1,183 +0,0 @@
|
||||
# 副业月入过万:我一个程序员的三年探索之路
|
||||
|
||||
**领域**:工作 / 副业
|
||||
**形式**:实操经验 + 方法论总结
|
||||
**预估字数**:2700
|
||||
**核心观点**:副业不是投机,而是将主业技能产品化、多元化的过程
|
||||
|
||||
**受众痛点**:
|
||||
- 工资不够花,想搞钱但不知道从哪开始
|
||||
- 听说过很多副业(自媒体、电商、咨询),但不知哪个适合
|
||||
- 时间有限,如何在主业之外有效投入
|
||||
- 担心副业影响主业,被公司发现
|
||||
- 交过学费买课,但没赚到钱
|
||||
- 想开始但总在观望,不敢行动
|
||||
|
||||
**独特角度**:
|
||||
- 从技术人视角(程序员)看副业选项,但方法论通用
|
||||
- 不鼓吹"月入十万"的毒鸡汤,诚实展示时间和金钱投入
|
||||
- 强调"技能产品化"而非"时间换钱"(避免陷入低效劳务)
|
||||
- 用数据说话:我的三年副业收入变化、时间分配、失败案例
|
||||
- 结合长期主义:副业是为了人生更多选择,不仅是赚钱
|
||||
|
||||
**数据/案例**(至少3个来源):
|
||||
1. 中国职场人副业调研报告(参与率、收入分布)
|
||||
2. 程序员技能变现渠道对比(外包、咨询、产品、内容)
|
||||
3. 时间投入与回报曲线(前期低效,后期复利)
|
||||
4. 个人三年副业数据(2019-2021):时间投入vs收入产出
|
||||
5. 成功/失败副业案例分析(为什么有些成了有些黄了)
|
||||
|
||||
**预估完成时间**:5天
|
||||
**优先级**:高
|
||||
**预计发布时间**:2026-04-25
|
||||
**状态**:待处理
|
||||
|
||||
---
|
||||
|
||||
## 选题评估矩阵
|
||||
|
||||
| 维度 | 评分 | 说明 |
|
||||
|------|------|------|
|
||||
| 受众覆盖 | 9 | 大量职场人有副业需求,搜索和阅读量巨大 |
|
||||
| 独特性 | 7 | 副业话题热,需要真实数据和深度分析才能出彩 |
|
||||
| 数据可得性 | 9 | 调研报告多,个人案例可详细追踪 |
|
||||
| 可持续性 | 9 | 可系列(不同阶段、不同技能、不同平台) |
|
||||
| 平台契合度 | 9 | 知乎职场/赚钱热门,小红书副业也火 |
|
||||
| 品牌契合度 | 9 | 科技赋能多元收入,增加人生选择权 |
|
||||
| **总分** | **52** | 优先执行 ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 大纲草稿
|
||||
|
||||
### 开头(400字)
|
||||
- 场景:2019年,我还是一枚996程序员,每月税后1.2万,觉得不够用
|
||||
- 尝试:接过外包、做过咨询、写过公众号、卖过课程、尝试过跨境电商
|
||||
- 现状:现在副业收入已超过主业,时间自由,有选择权
|
||||
- 核心观点:副业不是一夜暴富,而是技能产品化和持续积累的过程
|
||||
- 预告:分享三年探索的数据、踩坑、方法论
|
||||
|
||||
### 主体(2200字)
|
||||
|
||||
**第一部分:副业真相(破除幻想)(300字)**
|
||||
- 不是所有副业都赚钱:80%的人副业收入<2000元
|
||||
- 不是 Easily Money:前期投入巨大(时间、学习、试错)
|
||||
- 不是可持续的劳务:用时间换钱必然后劲不足
|
||||
- 数据:我的前18个月副业收入总计才8000元
|
||||
- 真相:副业是"第二曲线"需要长期培育
|
||||
|
||||
**第二部分:副业类型全景图(300字)**
|
||||
- **技能变现型**:外包、咨询、培训(适合技术人)
|
||||
- 平台:Upwork、电鸭、知乎 Live、自有客户
|
||||
- 特点:时间换钱,单价高但线性增长
|
||||
- **产品型**:开发工具、课程、模板、SaaS
|
||||
- 前期投入大,后期复利
|
||||
- 例子:我做的 Chrome 插件年收入X万
|
||||
- **内容型**:公众号、知乎、B站、小红书
|
||||
- 长周期变现,但品牌效应强
|
||||
- 例子:我的技术博客如何带来咨询客户
|
||||
- **电商/贸易型**:跨境电商、二手、定制
|
||||
- 资金和库存压力大,技术人不擅长
|
||||
- 我失败的案例:淘宝店
|
||||
- **投资型**:股票、基金、加密货币(不推荐作为主业外重点)
|
||||
- **混合型**:我的组合:技能变现+产品+内容
|
||||
|
||||
**第三部分:程序员适合什么副业?(300字)**
|
||||
- 优势:技术可远程交付、技能稀缺、学习能力强
|
||||
- 最适合:
|
||||
1. 技术外包/咨询(起步最快,但天花板低)
|
||||
2. 开发小工具/SaaS(复利高,但需要产品思维)
|
||||
3. 技术内容创作(博客、课程、咨询)
|
||||
4. 技术团队组建(成为自由职业团队leader)
|
||||
- 不适合:
|
||||
1. 需要大量资金的(电商库存)
|
||||
2. 纯体力或线下服务的
|
||||
3. 需要法律资质的(金融咨询)
|
||||
|
||||
**第四部分:我的三年路线图(400字)**
|
||||
- **第1年(2019)**:探索+试错
|
||||
- 目标:尝试3种副业,找到1-2个可行的
|
||||
- 行动:接外包(赚了1.5万)、写博客(0收入)、尝试跨境电商(亏损2k)
|
||||
- 学习:时间管理、客户沟通、基础营销
|
||||
- 结果:发现外包可行但累,内容有潜力但慢
|
||||
- 时间投入:每周10-15小时
|
||||
- **第2年(2020)**:聚焦+优化
|
||||
- 目标:在技术外包+内容创作双线推进,月入稳定5000
|
||||
- 行动:提高外包单价(从100$/h到150$/h),博客系统性写作
|
||||
- 学习:个人品牌、定价策略、产品化
|
||||
- 结果:外包月均3000,博客带来1个咨询项目(5000)
|
||||
- 时间投入:每周15小时(主业压力大)
|
||||
- **第3年(2021)**:产品化+规模化
|
||||
- 目标:将知识产品化,打造"第二曲线"
|
||||
- 行动:开发Chrome插件(年收入2万),出版电子书(1.5万),高单价咨询
|
||||
- 学习:产品运营、用户增长、自动化
|
||||
- 结果:副业月均1.2万,首次超过主业
|
||||
- 时间投入:每周20小时(但其中50%是产品维护)
|
||||
|
||||
**第五部分:时间管理:主业+副业如何平衡?(200字)**
|
||||
- 原则1:主业优先,不能因副业影响主业绩效(可能被开除)
|
||||
- 原则2:副业时间固定(早上6-8点、晚上8-10点、周末4小时)
|
||||
- 原则3:批量处理(每周日规划,减少切换成本)
|
||||
- 原则4:学会说"不"(拒绝低价值副业请求)
|
||||
- 我的时间分配:周总副业时间15h(低于20h安全线)
|
||||
|
||||
**第六部分:技能产品化:从时间换钱到复利增长(300字)**
|
||||
- 外包(时间换钱):1h=150元,但做才有
|
||||
- 产品(一次创造,多次销售):开发一个工具,持续销售
|
||||
- 内容(建立品牌,带来被动机会):博客带来咨询,不需主动找
|
||||
- 我的产品:Chrome插件(每月销售,收入稳定)
|
||||
- 如何选择产品方向?(你解决自己的问题,可能也是别人的问题)
|
||||
- 最小可行产品(MVP)快速验证
|
||||
|
||||
**第七部分:避坑指南(200字)**
|
||||
- 坑1:低价竞争(不要接低于市场价的活,会拉低你的价值)
|
||||
- 坑2:完美主义(先做出来,再优化;不要等完美才发布)
|
||||
- 坑3:孤军奋战(找导师、加入社群、互相支持)
|
||||
- 坑4:忽略法律(兼职合同、知识产权、个税)
|
||||
- 坑5:忽视健康(连续熬夜接单,不可持续)
|
||||
- 坑6:过度承诺(能力范围内再接,否则口碑崩塌)
|
||||
|
||||
**第八部分:给初学者的行动清单(200字)**
|
||||
1. 盘点你的技能:什么可远程交付?什么可产品化?
|
||||
2. 选择1-2个方向(不要贪多)
|
||||
3. 用3个月探索:每方向投入100小时试水
|
||||
4. 记录数据:时间投入、收入、满意度
|
||||
5. 6个月后评估:哪个ROI最高?哪个最有潜力?
|
||||
6. 聚焦+优化:放弃低ROI的,加注高潜力的
|
||||
7. 建立系统:不是每次从零开始,而是有流程
|
||||
8. 定期复盘(每月一次)
|
||||
|
||||
### 结尾(200字)
|
||||
- 副业不只是赚钱,更是探索人生更多可能
|
||||
- 我的现状:主业稳定,副业自由,有选择权
|
||||
- "宇之然"理念:科技给人自由,副业是实现自由的一种路径
|
||||
- 鼓励:从今晚开始,花1小时盘点你的技能
|
||||
- 互动:你的副业探索经历如何?欢迎分享
|
||||
|
||||
---
|
||||
|
||||
## 资源清单
|
||||
|
||||
### 数据来源(待收集)
|
||||
- [ ] 中国职场人副业现状调研(智联、脉脉等)
|
||||
- [ ] 程序员收入与副业相关性数据
|
||||
- [ ] 自由职业平台(Upwork、电鸭)收入报告
|
||||
- [ ] 数字游民与副业的关系研究
|
||||
- [ ] 时间投入与收入曲线(复利效应数据)
|
||||
|
||||
### 图片素材(待准备)
|
||||
- [ ] 三年副业收入增长曲线
|
||||
- [ ] 时间分配饼图(每天/每周)
|
||||
- [ ] 副业类型对比矩阵(时间vs金钱vs长期价值)
|
||||
- [ ] 技能产品化流程图
|
||||
- [ ] 避坑指南信息图
|
||||
|
||||
### 引用来源(待标注)
|
||||
- [ ] 《副业赚钱》等书籍
|
||||
- [ ] 知名副业博主案例
|
||||
- [ ] Upwork/Toptal 平台报告
|
||||
- [ ] 个人博客和项目数据
|
||||
|
||||
---
|
||||
|
||||
**决策**:刚需话题,竞争激烈但需求巨大。真实数据和实操经验是关键。适合知乎和小红书。建议 high 优先级。
|
||||
@@ -1,180 +0,0 @@
|
||||
# 正念冥想一年:从焦虑到平静的转变
|
||||
|
||||
**领域**:人文 / 心理健康
|
||||
**形式**:个人实验 + 科学解读
|
||||
**预估字数**:2200
|
||||
**核心观点**:冥想不是玄学,是可训练的大脑肌肉;一年练习带来可测量的认知和情绪变化
|
||||
|
||||
**受众痛点**:
|
||||
- 焦虑、失眠、注意力不集中
|
||||
- 想尝试冥想但觉得"太虚"、"静不下来"
|
||||
- 工作压力大,找不到放松方法
|
||||
- 情绪容易波动,想提升情绪调节能力
|
||||
- 听说过冥想好处,但不知如何开始
|
||||
- 试过几次没效果就放弃了
|
||||
|
||||
**独特角度**:
|
||||
- 用科学家态度做自我实验(追踪数据、前后对比)
|
||||
- 破除玄学色彩,基于神经科学和心理学
|
||||
- 展示失败和挣扎(不是一蹴而就)
|
||||
- 结合科技:Meditation App、生物反馈设备
|
||||
- 强调"微小习惯":每天10分钟胜过每周2小时
|
||||
|
||||
**数据/案例**(至少3个来源):
|
||||
1. 冥想改变大脑结构的研究(灰质增加、杏仁核缩小)
|
||||
2. 压力激素(皮质醇)水平变化数据
|
||||
3. 注意力、情绪调节能力的心理测量
|
||||
4. 个人一年追踪:每天10分钟,睡眠、焦虑评分、专注力自评
|
||||
5. 其他冥想者案例(长期练习者)
|
||||
|
||||
**预估完成时间**:4天
|
||||
**优先级**:中
|
||||
**预计发布时间**:2026-04-27
|
||||
**状态**:待处理
|
||||
|
||||
---
|
||||
|
||||
## 选题评估矩阵
|
||||
|
||||
| 维度 | 评分 | 说明 |
|
||||
|------|------|------|
|
||||
| 受众覆盖 | 8 | 压力大的都市人广泛需要,但转化率不确定 |
|
||||
| 独特性 | 8 | 个人实验+数据追踪,破除玄学 |
|
||||
| 数据可得性 | 8 | 神经科学研究多,自我追踪简单 |
|
||||
| 可持续性 | 7 | 可系列(不同时长、不同技术、不同人群) |
|
||||
| 平台契合度 | 8 | 知乎心理学/健康,小红书打卡风 |
|
||||
| 品牌契合度 | 9 | 回归本心,科技辅助内心平静 |
|
||||
| **总分** | **48** | 考虑执行 ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 大纲草稿
|
||||
|
||||
### 开头(300字)
|
||||
- 场景:2024年初,我焦虑到失眠,朋友建议"试试冥想"
|
||||
- 抵触:觉得是玄学,静不下来,10分钟像几个小时
|
||||
- 决定:用科学家态度做个实验,每天10分钟,追踪一年
|
||||
- 结果:焦虑评分下降40%,睡眠质量提升,专注力改善
|
||||
- 预告:分享这一年我的数据、方法、挣扎和收获
|
||||
|
||||
### 主体(1700字)
|
||||
|
||||
**第一部分:冥想是什么?(破除迷思)(200字)**
|
||||
- 不是宗教,不是玄学,是注意力训练
|
||||
- 不是停止思考,是观察思绪而不被带走
|
||||
- 不是马上变平静,是练习"觉知"
|
||||
- 科学解释:默认模式网络、前额叶皮层、神经可塑性
|
||||
- 我的初始怀疑:一个程序员如何相信这个?
|
||||
|
||||
**第二部分:为什么现代人更需要冥想?(200字)**
|
||||
- 信息过载:每天被数千条信息轰炸
|
||||
- 多任务假象:切换导致注意力碎片化
|
||||
- 压力激素持续分泌(皮质醇)
|
||||
- 社交媒体比较和FOMO
|
||||
- 缺乏"无所事事"的时间
|
||||
- 后果:焦虑、失眠、注意力涣散、情绪不稳定
|
||||
|
||||
**第三部分:我的实验设计(200字)**
|
||||
- 目标:一年后减少焦虑、改善睡眠、提升专注
|
||||
- 方法:每天10分钟冥想(App指导:Headspace + Calm)
|
||||
- 追踪指标:
|
||||
- 主观:焦虑评分(1-10)、睡眠质量(1-5)、专注时长
|
||||
- 客观:皮质醇(唾液测试,每月1次)、心率变异性(HRV)
|
||||
- 工具:Muse头环(脑电生物反馈)、Apple Watch睡眠追踪
|
||||
- 记录:每日简记,每周复盘
|
||||
|
||||
**第四部分:前3个月的挣扎(200字)**
|
||||
- 第1周:完全静不下来,中途放弃多次
|
||||
- 第1月:平均每次5分钟就开始看时间
|
||||
- 第2月:开始有一点点"空白时刻"
|
||||
- 第3月:发现思绪模式(焦虑的思绪、工作的思绪)
|
||||
- 关键:坚持每天做,不追求效果
|
||||
- 工具帮助:App的引导声音让我stay
|
||||
|
||||
**第五部分:6个月时的变化(200字)**
|
||||
- 主观感受:更容易进入状态(5分钟就能平静)
|
||||
- 睡眠:入睡更快,夜间醒来减少(数据:平均入睡时间从30min→15min)
|
||||
- 焦虑:明显降低(评分从7→4)
|
||||
- 专注:工作中更容易"心流"
|
||||
- 意外:对情绪觉察力提升(能 early detect stress)
|
||||
- 生物反馈:HRV提升(从50ms→70ms)
|
||||
|
||||
**第六部分:一年后的数据(200字)**
|
||||
- 坚持率:85%(365天,310天完成10min+)
|
||||
- 焦虑评分:从7.2降至4.1(下降43%)
|
||||
- 睡眠质量:从2.8升至4.1(1-5分制)
|
||||
- 专注时间:基线1.2h/天 → 2.5h/天( meas by RescueTime)
|
||||
- 皮质醇:早晨水平下降20%
|
||||
- 大脑变化:Muse数据显示α波(放松)增加,β波(焦虑)减少
|
||||
- ROI:每天10分钟,换来心理和生理健康提升,值吗?(一个数量级的健康价值)
|
||||
|
||||
**第七部分:关键技术/方法(300字)**
|
||||
1. **呼吸冥想**:专注呼吸,是最基础也最有效的
|
||||
- 方法:鼻吸鼻呼,数呼吸(1-10循环)
|
||||
- 最佳时段:早晨起床后或晚上睡前
|
||||
- 困难:思绪飘走正常,温柔拉回即可
|
||||
|
||||
2. **身体扫描**:释放身体紧张
|
||||
- 方法:从脚到头,逐个部位觉察
|
||||
- 适合:压力大时,身体紧绷
|
||||
- 效果:发现很多平日忽略的肌肉紧张
|
||||
|
||||
3. **正念行走**:把步行变成冥想
|
||||
- 方法:走路时觉察脚步、身体移动、呼吸
|
||||
- 适合:久坐办公室,通勤路上
|
||||
- 我:午休15分钟步行冥想,下午效率高
|
||||
|
||||
4. **3分钟呼吸空间**:快速冷静
|
||||
- 方法:1分钟觉察,1分钟专注呼吸,1分钟觉察整体
|
||||
- 适合:会议前、压力事件前
|
||||
- 使用频率:平均每天2-3次
|
||||
|
||||
5. **科技辅助**:
|
||||
- Headspace/Calm:引导冥想(新手强烈推荐)
|
||||
- Insight Timer:免费课程和统计
|
||||
- Muse头环:生物反馈,可视化大脑状态
|
||||
- Apple Watch:呼吸App,随时做1分钟
|
||||
|
||||
**第八部分:常见问题与解答(200字)**
|
||||
- Q:静不下来怎么办?(接受它,坚持做,改变需要时间)
|
||||
- Q:每天多久合适?(10分钟起步,30分钟最佳)
|
||||
- Q:什么时间做最好?(早晨或睡前最易坚持)
|
||||
- Q:必须坐垫子吗?(任何姿势,关键是舒适且清醒)
|
||||
- Q:多久见效?(2-4周有初步感受,1年明显变化)
|
||||
- Q:要加入宗教吗?(完全不需要, secular mindfulness)
|
||||
- Q:影响创造力吗?(反而提升,因为减少心理噪音)
|
||||
|
||||
### 结尾(200字)
|
||||
- 冥想一年,是我给自己最好的投资之一
|
||||
- 不是变成"无欲无求",而是更清晰、更平静、更有选择权
|
||||
- "宇之然"理念:科技沟通万物,最终回归内心本真
|
||||
- 鼓励:从今晚5分钟开始,用数据追踪自己的变化
|
||||
- 互动:你有冥想经验吗?分享你的故事
|
||||
|
||||
---
|
||||
|
||||
## 资源清单
|
||||
|
||||
### 数据来源(待收集)
|
||||
- [ ] 冥想改变大脑结构研究(哈佛、斯坦福等)
|
||||
- [ ] 压力激素皮质醇与冥想关系研究
|
||||
- [ ] 注意力、情绪调节能力测量工具(PANAS等)
|
||||
- [ ] 心率变异性(HRV)与冥想数据
|
||||
- [ ] 冥想对睡眠影响 meta-analysis
|
||||
|
||||
### 图片素材(待准备)
|
||||
- [ ] 一年冥想坚持日历(打卡图)
|
||||
- [ ] 追踪数据图表(焦虑、睡眠、专注趋势)
|
||||
- [ ] 大脑结构变化示意图(灰质、杏仁核)
|
||||
- [ ] HRV和脑电数据截图(如果使用设备)
|
||||
- [ ] 冥想姿势和环境照片
|
||||
|
||||
### 引用来源(待标注)
|
||||
- [ ] Headspace/Calm 科学页面
|
||||
- [ ] 冥想研究综述(如《心理学前沿》)
|
||||
- [ ] 《正念的奇迹》《禅与摩托车维修艺术》等书籍
|
||||
- [ ] 神经科学教材
|
||||
|
||||
---
|
||||
|
||||
**决策**:心理健康内容需求大,但竞争也激烈。个人实验+数据追踪+科学解释是差异点。适合知乎和小红书。建议 medium 优先级。
|
||||
@@ -1,182 +0,0 @@
|
||||
# 极简3年:我从囤积症到少物生活的转变
|
||||
|
||||
**领域**:人文 / 生活方式
|
||||
**形式**:个人转变故事 + 实操指南
|
||||
**预估字数**:2500
|
||||
**核心观点**:极简不是扔东西,而是重新定义"足够";通过减少物质,增加精神丰盈
|
||||
|
||||
**受众痛点**:
|
||||
- 家里东西越来越多,整理完很快又乱
|
||||
- 冲动购物,买了很多用不上的东西
|
||||
- 感觉被物品包围,想要斷舍离但下不去手
|
||||
- 想简化生活但不知从何开始
|
||||
- 担心扔了后悔,觉得"万一以后用得上"
|
||||
- 追求品质但预算有限,陷入"买 cheap stuff 然后浪费"
|
||||
|
||||
**独特角度**:
|
||||
- 从囤积症患者到极简主义者的真实转变(有心理过程)
|
||||
- 不鼓吹"一无所有",而是"刚刚好"
|
||||
- 量化成果:减少了70%物品,省了多少钱,时间如何变化
|
||||
- 连接科技:智能家居、数字替代(电子书、云存储)、物品追踪
|
||||
- 从物质极简到精神极简(信息、社交、目标)
|
||||
|
||||
**数据/案例**(至少3个来源):
|
||||
1. 极简主义创始人(Joshua & Ryan)理念和实践
|
||||
2. 物品生命周期研究(平均使用次数、浪费数据)
|
||||
3. 消费心理学(冲动购物的神经机制)
|
||||
4. 个人3年极简数据:物品数量变化、消费减少、时间分配
|
||||
5. 多人案例采访(不同极简程度)
|
||||
|
||||
**预估完成时间**:4天
|
||||
**优先级**:中
|
||||
**预计发布时间**:2026-04-29
|
||||
**状态**:待处理
|
||||
|
||||
---
|
||||
|
||||
## 选题评估矩阵
|
||||
|
||||
| 维度 | 评分 | 说明 |
|
||||
|------|------|------|
|
||||
| 受众覆盖 | 8 | 物质过剩的都市人有共鸣,但行动者少 |
|
||||
| 独特性 | 8 | 转变故事有吸引力,数据驱动 |
|
||||
| 数据可得性 | 8 | 个人追踪数据+消费研究+极简社区案例 |
|
||||
| 可持续性 | 8 | 可系列(不同房间、不同人群、不同阶段) |
|
||||
| 平台契合度 | 8 | 知乎生活方式/小红书极简话题 |
|
||||
| 品牌契合度 | 10 | 回归自然本心,少物多思 |
|
||||
| **总分** | **50** | 优先执行 ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 大纲草稿
|
||||
|
||||
### 开头(300字)
|
||||
- 场景:2019年,我搬进新家,发现60%的物品两年没用过
|
||||
- 触目惊心:衣柜塞满、抽屉爆炸、储物柜 hidden junk
|
||||
- 决定:开始极简之路,目标是"每件东西都有用且愉悦"
|
||||
- 3年变化:物品减少70%,消费减半,时间多出,焦虑下降
|
||||
- 预告:分享转变过程、方法、数据和教训
|
||||
|
||||
### 主体(2000字)
|
||||
|
||||
**第一部分:从囤积到觉醒(300字)**
|
||||
- 我的囤积症:舍不得扔,总觉得"会用到"
|
||||
- 触发点:搬家发现大量未拆封的购物袋、过期的优惠券
|
||||
- 消费主义陷阱:打折、社交媒体种草、身份焦虑
|
||||
- 觉醒:物品在消耗我的注意力、空间、金钱
|
||||
- 开始:先从小区域(一个抽屉)动手
|
||||
|
||||
**第二部分:极简不是数字,是心态(300字)**
|
||||
- 误区:极简=苦行= empty room
|
||||
- 真相:极简是"足够"哲学,留下真正重要和愉悦的
|
||||
- 我的标准:每件物品是否在用?是否带来愉悦?是否可替代?
|
||||
- 过程不是一次断舍离,而是持续优化
|
||||
- 3年我扔了/捐了/卖了 1000+ 件物品
|
||||
- 不是目标,是手段(为了时间、精力、金钱自由)
|
||||
|
||||
**第三部分:我的3年路线图(400字)**
|
||||
- 第1年(2019):粗筛+快速清理
|
||||
- 方法:一年未用的全扔(除了纪念品)
|
||||
- 成果:物品减少40%,清空3个储物箱
|
||||
- 挑战:扔东西的心痛感(浪费钱的心理)
|
||||
|
||||
- 第2年(2020):精细化+系统化
|
||||
- 方法:One In, One Out(进一出一),严格购买决策流程
|
||||
- 成果:物品再减少20%,消费减少35%
|
||||
- 学习:识别真实需求 vs 虚假欲望
|
||||
- 建立"30天清单":想买的东西放30天,90%会取消
|
||||
|
||||
- 第3年(2021):深化+扩展
|
||||
- 方法:数字极简、信息极简、社交极简
|
||||
- 成果:物品再减少10%,精神更清爽
|
||||
- 维持:定期季度审查,防止反弹
|
||||
- 极简成为习惯,不再需要"努力"
|
||||
|
||||
**第四部分:具体方法(500字)**
|
||||
1. **分类清理法**:
|
||||
- 从易到难:抽屉→衣柜→书柜→储物间
|
||||
- 4箱法:扔、捐、卖、留(保留)
|
||||
- 纪念品处理:选一个盒子(大小限制),其他拍照留底
|
||||
|
||||
2. **购买决策流程**:
|
||||
- 问自己:我需要它吗?我有替代品吗?它带来长期愉悦吗?
|
||||
- 30天等待期(除非是消耗品)
|
||||
- 控制冲动:取消促销订阅、不逛购物App
|
||||
- 质量优先:买少买好,耐用性优先
|
||||
- 案例:少买了100件便宜T恤,买了5件优质耐穿的
|
||||
|
||||
3. **数字极简**:
|
||||
- 卸载不用的App,取消不看的订阅
|
||||
- 照片整理:删除模糊、重复的,精选打印
|
||||
- 文件存档:云存储+本地双备份,删临时文件
|
||||
- 通知管理:只留必要的,减少干扰
|
||||
|
||||
4. **信息极简**:
|
||||
- RSS订阅精选(从200+减到20个高质量源)
|
||||
- 社交媒体定时关闭(每天30min)
|
||||
- 不追热点,只深度阅读
|
||||
- 信息输入输出平衡(少摄入,多创作)
|
||||
|
||||
5. **社交极简**:
|
||||
- 减少无效社交,保留高质量关系
|
||||
- 会议只参加必要的,其他异步沟通
|
||||
- 学会拒绝:时间是最稀缺资源
|
||||
|
||||
**第五部分:量化成果(200字)**
|
||||
- 物品数量:从约1500件 → 450件(减少70%)
|
||||
- 消费:年均减少 40%(省下约3万/年)
|
||||
- 时间:每周少花5小时整理、购物、找东西
|
||||
- 空间:清空3个储物柜,家里更宽敞
|
||||
- 心理:焦虑下降(从6→3分),决策更快(因为选择少)
|
||||
- 环保:减少碳足迹(估算:每年少产生200kg垃圾)
|
||||
|
||||
**第六部分:挑战与妥协(200字)**
|
||||
- 家人不理解:老婆/父母不配合,只能整理自己的部分
|
||||
- 社会压力:节日送礼、人情往来难简化
|
||||
- 实用性:有些"可能需要"的东西还是留了(工具、应急)
|
||||
- 不是完美:还有10%的物品是"maybe future use"
|
||||
- 转折点:当体验到"清爽"后,家人慢慢受影响
|
||||
|
||||
**第七部分:给初学者的建议(200字)**
|
||||
- 从小开始:先整理一个抽屉或一个角落
|
||||
- 不要一次扔太多:给缓刑期,减少心理阻力
|
||||
- 拍照留念:纪念品不扔,但数字化
|
||||
- 寻找社群:极简社区、Minimalism subreddit
|
||||
- 接受不完美:目标是"足够好",不是空无一物
|
||||
- 聚焦价值:极简是为了释放资源(时间、金钱、精力)做更重要的事
|
||||
- "宇之然"理念:少物多思,回归内心真实需求
|
||||
|
||||
### 结尾(200字)
|
||||
- 极简3年,生活更轻盈,更有方向
|
||||
- 不是物质越多越幸福,而是"足够"才幸福
|
||||
- 极简让我有更多资源去追求:深度阅读、健康生活、家人陪伴
|
||||
- 呼吁:从今天开始,清理一个角落,体验清爽的感觉
|
||||
- 互动:你的极简实践是什么?有什么困惑?
|
||||
|
||||
---
|
||||
|
||||
## 资源清单
|
||||
|
||||
### 数据来源(待收集)
|
||||
- [ ] 极简主义书籍(Goodreads、豆瓣)
|
||||
- [ ] 消费心理学研究(过度消费、囤积症)
|
||||
- [ ] 物品生命周期和浪费数据(环保组织)
|
||||
- [ ] 时间追踪数据(整理、购物时间)
|
||||
- [ ] 极简对幸福感影响研究
|
||||
|
||||
### 图片素材(待准备)
|
||||
- [ ] 整理前后对比图(同一区域)
|
||||
- [ ] 物品数量变化图表
|
||||
- [ ] 消费金额变化曲线
|
||||
- [ ] 空间对比图(清空的储物柜)
|
||||
- [ ] 极简生活场景(清爽的房间、精选的物品)
|
||||
|
||||
### 引用来源(待标注)
|
||||
- [ ] Joshua Fields Millburn & Ryan Nicodemus
|
||||
- [ ] 《极简主义》《断舍离》等书籍
|
||||
- [ ] The Minimalists Podcast
|
||||
- [ ] 消费主义批判文献
|
||||
|
||||
---
|
||||
|
||||
**决策**:生活方式的个人转变故事容易引发共鸣,适合叙事+方法论。小红书和知乎都合适。建议 medium 优先级。
|
||||
@@ -1,176 +0,0 @@
|
||||
# 城市观鸟指南:如何在水泥森林发现 biodiversity
|
||||
|
||||
**领域**:自然 / 科普
|
||||
**形式**:实操指南 + 城市生态观察
|
||||
**预估字数**:2000
|
||||
**核心观点**:自然不在远方,就在身边;城市也是wildlife的栖息地,学会观察能提升生活幸福感
|
||||
|
||||
**受众痛点**:
|
||||
- 想接触自然但没时间去郊外
|
||||
- 觉得城市只有鸽子麻雀,没啥可看
|
||||
- 想带孩子体验自然不知从何开始
|
||||
- 对生物多样性有概念但不会识别
|
||||
- 担心需要专业设备和大量学习
|
||||
- 城市生活枯燥,寻求新鲜感和治愈
|
||||
|
||||
**独特角度**:
|
||||
- 城市观鸟入门,从"完全小白"到"常见鸟达人"
|
||||
- 结合科技:观鸟App(Merlin、鸟哨识别)、社区数据平台(eBird)
|
||||
- 强调"身边":小区、公园、甚至阳台上就能开始
|
||||
- 用数据说话:上海/北京等城市的鸟类种类统计
|
||||
- 连接宇之然理念:科技帮助人与自然的连接
|
||||
|
||||
**数据/案例**(至少3个来源):
|
||||
1. 城市生物多样性调研报告(中国的城市化与野生动物)
|
||||
2. 常见城市鸟类图鉴(种类、特征、习性)
|
||||
3. eBird/懂鸟等平台用户提交数据(观测频率)
|
||||
4. 个人城市观鸟记录(一年在X城市观测到XX种)
|
||||
5. 观鸟对心理健康的研究(缓解压力、提升注意力)
|
||||
|
||||
**预估完成时间**:3天
|
||||
**优先级**:中
|
||||
**预计发布时间**:2026-05-01
|
||||
**状态**:待处理
|
||||
|
||||
---
|
||||
|
||||
## 选题评估矩阵
|
||||
|
||||
| 维度 | 评分 | 说明 |
|
||||
|------|------|------|
|
||||
| 受众覆盖 | 7 | 相对垂直,但亲子、自然爱好者多 |
|
||||
| 独特性 | 9 | 城市+观鸟结合,实用性强,科普价值高 |
|
||||
| 数据可得性 | 8 | 鸟类数据、App工具、调研报告充足 |
|
||||
| 可持续性 | 9 | 可系列(不同季节、不同城市、不同难度) |
|
||||
| 平台契合度 | 8 | 知乎科普/小红书图文,适合短视频延伸 |
|
||||
| 品牌契合度 | 10 | 科技+自然+生活,完美契合 |
|
||||
| **总分** | **51** | 优先执行 ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 大纲草稿
|
||||
|
||||
### 开头(200字)
|
||||
- 场景:在上海陆家嘴办公室,突然听到窗外各种鸟叫,一个想法:城市里有啥鸟?
|
||||
- 意外:下载观鸟App后,发现小区里有20+种鸟
|
||||
- 变化:现在走路会抬头,阳台常备望远镜,生活多了乐趣
|
||||
- 核心观点:城市不是自然的对立面,而是生态系统一部分
|
||||
- 预告:教你从零开始城市观鸟,需要什么、去哪看、如何识别
|
||||
|
||||
### 主体(1600字)
|
||||
|
||||
**第一部分:城市里有多少鸟?(200字)**
|
||||
- 数据:上海已记录鸟类 400+ 种,北京 500+ 种
|
||||
- 常见的是哪些?(麻雀、白头鹎、乌鸫、珠颈斑鸠、白鹭、喜鹊等)
|
||||
- 季节性:候鸟迁徙(春秋季更多)
|
||||
- 城市生态:公园、河流、小区绿地、屋顶都能看到
|
||||
- 我的记录:在XX小区/公园一年观测到XX种
|
||||
- 结论:比你想象的多得多
|
||||
|
||||
**第二部分:观鸟入门装备(200字)**
|
||||
- **最低配置**(手机 + 免费App):
|
||||
- 懂鸟/Merlin Bird ID(声音识别神器)
|
||||
- eBird中国(记录、查看附近观测)
|
||||
- 观鸟相机(手机即可,需要长焦建议入门望远镜2k-3k)
|
||||
- **进阶配置**:
|
||||
- 望远镜(8x42 双筒,入门推荐)
|
||||
- 鸟类图鉴(纸质书或App)
|
||||
- 记录本或电子表格
|
||||
- **着装**:低调颜色(避免亮色惊鸟),舒适鞋子
|
||||
- **.total cost*:零成本起步,深度5000元以内
|
||||
|
||||
**第三部分:去哪看?(200字)**
|
||||
- **城市公园**:最佳入门地(有人维护、鸟类聚集)
|
||||
- 上海:世纪公园、滨江森林公园、上海植物园
|
||||
- 北京:奥林匹克森林公园、颐和园、圆明园
|
||||
- **湿地/河流**:水鸟聚集(白鹭、翠鸟、水鸡)
|
||||
- 例子:上海崇明东滩、北京野鸭湖
|
||||
- **小区和校园**:你身边的第一站
|
||||
- 树木多的老小区往往有惊喜
|
||||
- 大学校园绿化好,鸟类多
|
||||
- **城市高点**:观猛禽(迁徙季节)
|
||||
- **App辅助**:eBird Hotspots 查看附近观测点
|
||||
|
||||
**第四部分:如何识别?(300字)**
|
||||
- 观鸟四要素:体型、颜色、行为、声音
|
||||
- **步骤1**:先看大小和整体形状(像麻雀还是喜鹊?)
|
||||
- **步骤2**:看颜色分布(头部、背部、腹部、翅膀)
|
||||
- **步骤3**:看行为(在地上找食?树上鸣叫?水中游?)
|
||||
- **步骤4**:听声音(Merlin识别超准)
|
||||
- **步骤5**:对照图鉴或App
|
||||
- **常见城市鸟快速指南**(选10种):
|
||||
- 麻雀:灰褐色,群体,地面觅食,城市最常见
|
||||
- 白头鹎:白色头顶,背部橄榄绿,叫"WA-DA-DI"
|
||||
- 乌鸫:雄性黑褐色,黄色眼圈,鸣叫婉转
|
||||
- 珠颈斑鸠:棕灰色,颈后白斑像珍珠,咕咕叫
|
||||
- 白鹭:白色,长腿长颈,水边常见
|
||||
- 喜鹊:黑白分明,长尾巴,聪明
|
||||
- 灰喜鹊:蓝灰色,群居,山区更多
|
||||
- 大山雀:蓝黄配色,叫"锌锌"
|
||||
- 啄木鸟:斑纹红头,树洞敲击声
|
||||
- 普通翠鸟:橙蓝色, waterside,极快闪
|
||||
|
||||
**第五部分:季节与迁徙(200字)**
|
||||
- 春季(3-5月):候鸟北归,种类最多,常见莺类
|
||||
- 夏季(6-8月):留鸟+繁殖季,早上鸣叫活跃
|
||||
- 秋季(9-11月):候鸟南迁,猛禽(鹰、鸮)容易看到
|
||||
- 冬季(12-2月):候鸟到南方越冬,水鸟多
|
||||
- 迁徙高峰期:每年4月中、10月中,去湿地有惊喜
|
||||
|
||||
**第六部分:科技让观鸟更简单(200字)**
|
||||
- **声音识别**:Merlin Bird ID(免费的!),对着鸟叫录一下,马上指出种类和概率
|
||||
- **图像识别**:懂鸟App上传照片,准确率90%+
|
||||
- **社区数据**:eBird 查看附近谁看到了什么,热门 spots
|
||||
- **记录与统计**:电子清单,个人记录,年度总结
|
||||
- **社交**:加入城市观鸟社群,组团学习
|
||||
- "宇之然":科技降低自然体验门槛
|
||||
|
||||
**第七部分:给新手的建议(200字)**
|
||||
1. 从小区开始,别追求稀有鸟
|
||||
2. 带双筒望远镜(非必需但提升体验)
|
||||
3. 下载 Merlin 和懂鸟,声音识别是关键
|
||||
4. 学习10种最常见的,再扩展
|
||||
5. 晨起最佳(鸟活跃,人少)
|
||||
6. 安静缓慢,避免惊鸟
|
||||
7. 记录观测(App自动记录)
|
||||
8. 加入本地观鸟群,老手带入门快
|
||||
9. 长期坚持,你会发现城市生命的丰富
|
||||
10. 保护鸟类:不打扰、不投喂、不破坏栖息地
|
||||
|
||||
### 结尾(200字)
|
||||
- 观鸟一年,我在城市发现了20+种鸟,还记录到罕见的红嘴蓝鹊
|
||||
- 自然不是远方的诗,是身边的小确幸
|
||||
- 提升了观察力、耐心、对季节变化的敏感
|
||||
- 带孩子观鸟是最佳的自然教育
|
||||
- "宇之然":科技沟通万物,让我重新发现身边自然
|
||||
- 鼓励:明天早上花15分钟,去小区听鸟叫、看鸟飞
|
||||
- 互动:你所在城市常见什么鸟?分享你的发现
|
||||
|
||||
---
|
||||
|
||||
## 资源清单
|
||||
|
||||
### 数据来源(待收集)
|
||||
- [ ] 中国主要城市鸟类名录(中国科学院、地方鸟类学会)
|
||||
- [ ] eBird 中国数据(观测统计)
|
||||
- [ ] 城市生态学研究(城市化对鸟类影响)
|
||||
- [ ] 鸟类迁徙路线图(东亚-澳大利西亚迁飞区)
|
||||
- [ ] 观鸟对心理健康益处研究
|
||||
|
||||
### 图片素材(待准备)
|
||||
- [ ] 常见城市鸟类照片(自己拍摄或购买授权)
|
||||
- [ ] 观鸟装备照片(望远镜、App截图)
|
||||
- [ ] 城市观鸟地点(公园、湿地照片)
|
||||
- [ ] 观鸟者视角(手持望远镜看鸟)
|
||||
- [ ] 鸟类识别对比图(相似鸟区分)
|
||||
|
||||
### 引用来源(待标注)
|
||||
- [ ] Cornell Lab Merlin Bird ID
|
||||
- [ ] eBird 中国(https://ebird.org/china)
|
||||
- [ ] 中国观鸟组织(如:中国观鸟记录中心)
|
||||
- [ ] 鸟类图鉴(如《中国鸟类观察手册》)
|
||||
- [ ] 城市生态学文献
|
||||
|
||||
---
|
||||
|
||||
**决策**:入门级自然科普,大众接受度高,亲子群体尤其适合。内容轻量但实用,适合快速创作。建议 medium 优先级。
|
||||
@@ -1,231 +0,0 @@
|
||||
# 个人知识管理系统(PKM)实践:我用一年搭建的第二大脑
|
||||
|
||||
**领域**:工作 / 生产力
|
||||
**形式**:实操教程 + 系统设计
|
||||
**预估字数**:2800
|
||||
**核心观点**:信息时代,会学习不如会管理知识;一个适合自己的PKM系统能释放认知带宽,提升创造力和决策质量
|
||||
|
||||
**受众痛点**:
|
||||
- 收藏无数,但从未回看(收藏夹吃灰)
|
||||
- 知识点散落在各处(笔记App、书签、微信收藏、纸质本)
|
||||
- 需要时找不到,找到的又不完整
|
||||
- 学会了方法但无法坚持(Roam太复杂、Notion不会用)
|
||||
- 信息过载,不知道如何筛选和组织
|
||||
- 想建立系统但被工具选择困住(工具 switcher)
|
||||
|
||||
**独特角度**:
|
||||
- 不推销特定工具,强调方法论+适合自己的选择
|
||||
- 展示一年的实践数据和系统演变(不是一蹴而就)
|
||||
- 强调"用"而非"建":系统为输出服务,不为收藏
|
||||
- 结合AI辅助(自动标签、智能摘要、关联发现)
|
||||
- 从程序员视角但内容通用
|
||||
|
||||
**数据/案例**(至少3个来源):
|
||||
1. PKM方法论(Zettelkasten、PARA、CODE等)
|
||||
2. 工具对比研究(Notion、Obsidian、Roam、Logseq、飞书、Notability)
|
||||
3. 个人一年数据:笔记数量、链接数、输出成果、时间节省
|
||||
4. 知识工作者效率研究(信息检索耗时)
|
||||
5. 成功案例:其他内容创作者如何用PKM支撑输出
|
||||
|
||||
**预估完成时间**:5天
|
||||
**优先级**:中
|
||||
**预计发布时间**:2026-05-03
|
||||
**状态**:待处理
|
||||
|
||||
---
|
||||
|
||||
## 选题评估矩阵
|
||||
|
||||
| 维度 | 评分 | 说明 |
|
||||
|------|------|------|
|
||||
| 受众覆盖 | 8 | 知识工作者广泛需要,但执行率低 |
|
||||
| 独特性 | 8 | 个人系统演变+数据追踪,避免工具论 |
|
||||
| 数据可得性 | 9 | PKM方法论丰富,个人数据可量化 |
|
||||
| 可持续性 | 9 | 可系列(不同工具、不同场景、进阶技巧) |
|
||||
| 平台契合度 | 9 | 知乎效率/工具,小红书个人成长 |
|
||||
| 品牌契合度 | 9 | 科技赋能知识管理,提升深度思考 |
|
||||
| **总分** | **52** | 优先执行 ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 大纲草稿
|
||||
|
||||
### 开头(400字)
|
||||
- 场景:2024年,我发现自己有5个笔记App、2000+零散笔记、无数书签,但写文章时还是找不到资料
|
||||
- 问题:信息时代,我们被信息淹没,却缺乏可用的知识
|
||||
- 决定:用一年时间,搭建并完善个人知识管理系统(PKM)
|
||||
- 结果:笔记整理到统一系统,知识关联起来了,写文章效率提升3倍
|
||||
- 核心观点:PKM不是收藏信息,而是将信息转化为知识资产
|
||||
- 预告:分享我的方法论、工具选择、系统设计、一年数据和教训
|
||||
|
||||
### 主体(2200字)
|
||||
|
||||
**第一部分:为什么你需要PKM?(300字)**
|
||||
- 信息过载时代:每年产生信息量 vs 大脑处理能力
|
||||
- 知识 ≠ 信息:信息是 raw data,知识是 processed and connected
|
||||
- 不管理的代价:
|
||||
- 重复学习(同一个知识点看过多次但没记住)
|
||||
- 需要时找不到(搜索耗时>30分钟)
|
||||
- 知识孤岛(孤立信息无法产生新想法)
|
||||
- 决策质量低(缺乏完整背景)
|
||||
- 研究:知识工作者平均每天花1.5小时找信息(麦肯锡)
|
||||
- PKM的价值:提高检索效率、支持创造性连接、减少认知负荷
|
||||
|
||||
**第二部分:PKM方法论概览(200字)**
|
||||
- **Zettelkasten**(卡片盒笔记法):原子化、链接、渐进式总结
|
||||
- 核心:每个笔记一个想法,用链接建立连接
|
||||
- 适合:深度思考者、研究者、作家
|
||||
- **PARA**(Tiago Forte):按可行动性组织(Projects, Areas, Resources, Archives)
|
||||
- 核心:不是按主题,而是按"使用状态"分类
|
||||
- 适合:项目驱动、注重行动的人
|
||||
- **CODE**(Capture, Organize, Distill, Express):知识生命周期
|
||||
- 核心:从收集到输出的完整流程
|
||||
- 适合:内容创作者、学习型人才
|
||||
- **选择建议**:没有最佳,只有最适合;可混合使用
|
||||
- 我的选择:CODE框架 + Zettelkasten 链接 + PARA 分类
|
||||
|
||||
**第三部分:我的系统演变(3个阶段)(300字)**
|
||||
- **第1阶段(混沌期,2019-2020)**:
|
||||
- 工具:微信收藏 + Evernote + 书签
|
||||
- 问题:信息散落,从未整理,基本不用
|
||||
- 产出:0
|
||||
|
||||
- **第2阶段(工具迷恋,2021)**:
|
||||
- 尝试:Roam Research(双链笔记)、Notion(数据库)、Obsidian(本地Markdown)
|
||||
- 问题:花大量时间折腾工具,而不是用系统
|
||||
- 产出:少量笔记,但没形成知识网络
|
||||
|
||||
- **第3阶段(方法驱动,2022-2023)**:
|
||||
- 选择:Notion(数据库+协作)+ 本地Markdown备份
|
||||
- 方法论:CODE流程 + 原子笔记 + 强制链接
|
||||
- 成果:整理笔记1500+条,链接1000+,支撑输出文章40+篇
|
||||
- 关键:从"怎么建"转向"怎么用"
|
||||
|
||||
**第四部分:我的当前系统设计(400字)**
|
||||
- **工具栈**:
|
||||
- 主库:Notion(数据库+关联视图+协作)
|
||||
- 优点:全平台、强查询、美观
|
||||
- 缺点:网络依赖、长期成本
|
||||
- 本地备份:Obsidian(Markdown + 双链)
|
||||
- 优点:数据自主、快、强链接
|
||||
- 缺点:移动端弱、协作差
|
||||
- 收集箱:Readwise + Instapaper + 微信收藏 + 书签
|
||||
- AI辅助:使用LLM自动摘要、标签、提取关键点
|
||||
- **结构(按CODE)**:
|
||||
- **Capture**(收集):
|
||||
- 快速收藏(Readwise高亮、微信收藏 → 每日清理到收集箱)
|
||||
- 来源:文章、书、视频、对话、思考
|
||||
- **Organize**(组织):
|
||||
- 分类:PARA(Projects/Areas/Resources/Archives)
|
||||
- 标签:主题(科技、自然、人文)+ 状态(草稿、完成)
|
||||
- 链接:原子笔记之间建立连接(双向链接)
|
||||
- **Distill**(提炼):
|
||||
- 原子化:每笔记一个概念/想法/发现
|
||||
- 渐进式总结:原始笔记 → 粗摘要 → 精要 → 综述
|
||||
- MOC(内容地图):索引相关笔记,形成知识模块
|
||||
- **Express**(输出):
|
||||
- 创作模式:从PKM查询相关笔记 → 拼图式写作
|
||||
- 模板:文章模板自动从PKM填充资料
|
||||
- 发布后,将文章存入PKM作为新笔记
|
||||
- **工作流**:
|
||||
- 每日:收集(30min) + 整理(30min)
|
||||
- 每周:链接和MOC维护(1h)
|
||||
- 创作时:查询 → 连接 → 输出(PKM支撑)
|
||||
|
||||
**第五部分:量化结果(200字)**
|
||||
- 笔记总量:1500+ 条(原子笔记)
|
||||
- 笔记间链接:1200+ 个(网络密度 0.8)
|
||||
- 知识领域:8个大类,50+个小类
|
||||
- 时间节省:资料查找从平均30min → 5min(83%)
|
||||
- 输出支撑:40+ 篇文章,其中80%从PKM直接提取资料
|
||||
- 知识复用率:70%的文章引用已有笔记(不是每次都重研究)
|
||||
- 意外收益:笔记价值随时间复利增长(旧笔记连接新笔记产生新洞见)
|
||||
|
||||
**第六部分:关键技巧(300字)**
|
||||
1. **原子化**:
|
||||
- 每笔记一个概念、一个发现、一个问题
|
||||
- 优点:易复用、易链接、易更新
|
||||
- 例子:不要记整本书笔记,记"书的核心框架"+"3个关键观点"+"我的疑问"
|
||||
|
||||
2. **强制链接**:
|
||||
- 新笔记必须链接到至少2个已有笔记
|
||||
- 链接要语义相关(不只是关键词匹配)
|
||||
- 使用 Zettelkasten 的自下而上连接
|
||||
- 工具:Obsidian的Graph View、Notion的Relation
|
||||
|
||||
3. **渐进式总结**:
|
||||
- 原始笔记(source原文高亮+简评)
|
||||
- 一阶摘要(用自己的话)
|
||||
- 二阶精要(关键点 bullet)
|
||||
- 三阶综合(多个笔记的整合+我的观点)
|
||||
- 成果:长期使用,笔记越来越有价值
|
||||
|
||||
4. **MOC(内容地图)**:
|
||||
- 索引同一主题的所有笔记(类似目录)
|
||||
- 动态更新,保持相关性
|
||||
- 例子:"AI伦理" MOC 链接60+条笔记
|
||||
- 创作时,先看MOC快速定位
|
||||
|
||||
5. **定期修剪**:
|
||||
- 每月审查:合并重复、删除过时、更新链接
|
||||
- 保持系统健康,避免熵增
|
||||
|
||||
**第七部分:AI如何辅助PKM?(200字)**
|
||||
- **自动摘要**:上传文章,LLM生成关键点(节省阅读时间)
|
||||
- **智能标签**:自动建议标签,保持一致性
|
||||
- **关联发现**:AI找语义相关笔记,建议链接
|
||||
- **问答式检索**:自然语言查询("我怎么写关于Zettelkasten的文章?" → 相关笔记)
|
||||
- **质量检查**:识别孤立笔记、断裂链接
|
||||
- 工具:ChatGPT/Claude API + 自定义脚本
|
||||
- "宇之然":AI时代,PKM是核心基础设施
|
||||
|
||||
**第八部分:常见问题与避坑(200字)**
|
||||
- Q:工具太多,选哪个?(先用一个,习惯后再考虑迁移)
|
||||
- Q:笔记分类 vs 标签?(分类是宏观,标签是微观,两者都需)
|
||||
- Q:多久整理一次?(每天小整,每周大整)
|
||||
- Q:隐私问题?(本地工具如Obsidian;云工具加密)
|
||||
- Q:如何坚持?(绑定创作需求,系统为输出服务)
|
||||
- 避坑:
|
||||
- 不要过度组织(1-2小时整理1条笔记,效率低)
|
||||
- 不要追求完美的分类(允许模糊)
|
||||
- 不要忽视维护(熵增是必然,定期修剪)
|
||||
- 不要只看不输出(PKM的价值在输出)
|
||||
|
||||
### 结尾(200字)
|
||||
- PKM一年,我的知识从混沌到有序,从孤岛到网络
|
||||
- 最大的收益:写文章不再从零开始,而是"拼图式创作"
|
||||
- 知识管理是终身投资,越早开始复利越大
|
||||
- "宇之然":科技沟通知识,让我更好地表达思想
|
||||
- 行动:从今天开始,用Notation至少10个原子笔记
|
||||
- 互动:你的知识管理实践如何?遇到的问题?
|
||||
|
||||
---
|
||||
|
||||
## 资源清单
|
||||
|
||||
### 数据来源(待收集)
|
||||
- [ ] PKM方法论原始文献(Zettelkasten、PARA、CODE)
|
||||
- [ ] 知识管理工作效率研究(查找时间、复用率)
|
||||
- [ ] 工具对比(Notion、Obsidian、Roam、Logseq、飞书)
|
||||
- [ ] Notion API和自动化案例
|
||||
- [ ] AI辅助知识管理工具(Memex、Rewind、Upabase)
|
||||
|
||||
### 图片素材(待准备)
|
||||
- [ ] 系统架构图(工具栈、数据流)
|
||||
- [ ] Notion数据库截图(匿名化)
|
||||
- [ ] Obsidian Graph View截图(展示连接密度)
|
||||
- [ ] 工作流可视化(Capture → Organize → Distill → Express)
|
||||
- [ ] 量化结果图表(笔记数、链接数、时间节省)
|
||||
- [ ] 渐进式总结示例(同一笔记的不同版本)
|
||||
|
||||
### 引用来源(待标注)
|
||||
- [ ] Sönke Ahrens《How to Take Smart Notes》
|
||||
- [ ] Tiago Forte《Building a Second Brain》
|
||||
- [ ] Andy Matuschak & Michael Nielsen《How to Make a Networked Notebook That Explains Things》
|
||||
- [ ] PARA方法论官网
|
||||
- [ ] Zettelkasten.de社区
|
||||
- [ ] AI知识管理工具(Mem.ai等)
|
||||
|
||||
---
|
||||
|
||||
**决策**:生产力领域刚需,受众广但竞争烈。结合个人系统演变数据+方法论+AI辅助,能做出差异化。适合知乎和小红书。建议 medium-high 优先级。
|
||||
@@ -1,71 +0,0 @@
|
||||
# 选题库管理
|
||||
|
||||
本目录收录所有选题卡片,按领域和优先级分类。
|
||||
|
||||
## 选题结构
|
||||
|
||||
每个选题文件格式:`YYYY-MM-DD-选题名称.md`
|
||||
|
||||
内容模板:
|
||||
```markdown
|
||||
# [选题名称]
|
||||
|
||||
**领域**:科技/自然/工作/人文
|
||||
**形式**:观点/指南/趋势/故事
|
||||
**预估字数**:2000-3000
|
||||
**核心观点**:(一句话说清楚)
|
||||
**受众痛点**:(读者为什么关心?)
|
||||
**独特角度**:(与其他人的区别)
|
||||
**数据/案例**:(至少3个来源)
|
||||
**预估完成时间**:3-5天
|
||||
**优先级**:高/中/低
|
||||
**预计发布时间**:YYYY-MM-DD
|
||||
**状态**:待处理/研究中/写作中/已发布
|
||||
```
|
||||
|
||||
## 选题评估得分
|
||||
|
||||
使用品牌手册中的"选题评估矩阵"打分:
|
||||
- 受众覆盖:1-10分
|
||||
- 独特性:1-10分
|
||||
- 数据可得性:1-10分
|
||||
- 可持续性:1-10分
|
||||
- 平台契合度:1-10分
|
||||
- 品牌契合度:1-10分
|
||||
|
||||
**总分 > 40分** → 优先执行
|
||||
**30-40分** → 考虑执行
|
||||
**< 30分** → 暂缓或放弃
|
||||
|
||||
## 当前选题列表(个人叙事版 001-010)
|
||||
|
||||
| 编号 | 选题名称 | 领域 | 优先级 | 得分 | 状态 |
|
||||
|------|----------|------|--------|------|------|
|
||||
| 001 | 在上海阳台种菜一年,我收获了啥? | 自然/生活 | 高 | 51 | ✅ 已发布 |
|
||||
| 002 | AI时代,什么能力不会被替代? | 科技/职场 | 高 | 48 | 📝 待创作 |
|
||||
| 003 | 从程序员到数字游民:三年转型之路 | 工作/职场 | 高 | 45 | 📝 待创作 |
|
||||
| 004 | 零浪费生活一年实验 | 自然/生活 | 中 | 42 | 📝 待创作 |
|
||||
| 005 | 深度工作实践:如何在干扰世界保持专注 | 工作/生产力 | 高 | 46 | 📝 待创作 |
|
||||
| 006 | 副业月入过万:程序员的三年探索 | 工作/副业 | 中 | 40 | 📝 待创作 |
|
||||
| 007 | 正念冥想一年:从焦虑到平静的转变 | 人文/心理 | 中 | 41 | 📝 待创作 |
|
||||
| 008 | 极简3年:从囤积症到少物生活的转变 | 人文/生活 | 中 | 39 | 📝 待创作 |
|
||||
| 009 | 城市观鸟指南:如何在水泥森林发现biodiversity | 自然/科普 | 中 | 38 | 📝 待创作 |
|
||||
| 010 | 个人知识管理系统(PKM)实践 | 工作/生产力 | 高 | 47 | 📝 待创作 |
|
||||
|
||||
> 另有 **全球-本土对比新选题(20个)** 详见 `strategy/全球-本土比较研究与全新内容战略规划-2026-04-15.md`,分为4大支柱:未来工作方式、可持续生活、知识工厂、科技人文。
|
||||
|
||||
## 批量添加选题
|
||||
|
||||
新选题从以下来源获取:
|
||||
- 行业趋势分析(research/trends-2026.md)
|
||||
- 日常观察记录
|
||||
- 热点事件(冷静筛选)
|
||||
- 读者问题反馈
|
||||
- 竞品分析(差异化)
|
||||
|
||||
**每周选题会议**:每周日晚上20:00,评估新选题,确定下周发布计划。
|
||||
|
||||
---
|
||||
|
||||
**维护**:动态更新
|
||||
**负责人**:AI助手 + 用户协作
|
||||
@@ -0,0 +1,39 @@
|
||||
# ===== 数据库 =====
|
||||
# 默认 SQLite(USE_POSTGRES=false);生产设 true 并填 DATABASE_URL
|
||||
USE_POSTGRES=false
|
||||
# PG_HOST=db
|
||||
# PG_PORT=5432
|
||||
# PG_DATABASE=yzr_nr
|
||||
# PG_USER=yzr
|
||||
# PG_PASSWORD=change_me_strong
|
||||
# DATABASE_URL=postgresql://yzr:yzr_password@db:5432/yzr_nr
|
||||
|
||||
# ===== 安全 =====
|
||||
# 务必修改为随机长字符串
|
||||
SECRET_KEY=change_this_to_a_long_random_secret_key
|
||||
ALGORITHM=HS256
|
||||
ACCESS_TOKEN_EXPIRE_DAYS=7
|
||||
|
||||
# ===== 初始管理员 =====
|
||||
DEFAULT_ADMIN_USERNAME=admin
|
||||
DEFAULT_ADMIN_PASSWORD=admin123
|
||||
|
||||
# ===== 项目根(容器内固定为 /app) =====
|
||||
PROJECT_ROOT=/app
|
||||
|
||||
# ===== 主 LLM(按 provider 填写对应 Key) =====
|
||||
# provider 可选:nvidia / opencode-go / sensenova
|
||||
LLM_PROVIDER=opencode-go
|
||||
LLM_API_KEY=your_main_llm_key
|
||||
LLM_MODEL=deepseek-v4-flash
|
||||
LLM_BASE_URL=https://opencode.ai/zen/go/v1
|
||||
|
||||
# ===== 备用推理通道(opencode-go) =====
|
||||
OPENCODE_API_KEY=your_opencode_key
|
||||
OPENCODE_BASE_URL=https://opencode.ai/zen/go/v1
|
||||
OPENCODE_MODEL=deepseek-v4-flash
|
||||
|
||||
# ===== 配图生成(sensenova,可选) =====
|
||||
SENSENOVA_API_KEY=
|
||||
SENSENOVA_BASE_URL=https://token.sensenova.cn/v1
|
||||
SENSENOVA_IMAGE_MODEL=sensenova-6.7-flash-lite
|
||||
@@ -0,0 +1,20 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 安装依赖(先拷 requirements 享受层缓存)
|
||||
COPY platform/backend/requirements.txt /app/platform/backend/requirements.txt
|
||||
RUN pip install --no-cache-dir -r /app/platform/backend/requirements.txt
|
||||
|
||||
# 拷贝整个项目(保持 automation/ scripts/ 等相对路径)
|
||||
COPY . /app
|
||||
|
||||
# 关键:让 initial_data.py 的 PROJECT_ROOT 指向 /app(与运行时一致)
|
||||
ENV PROJECT_ROOT=/app
|
||||
ENV USE_POSTGRES=false
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
EXPOSE 8001
|
||||
|
||||
# 若使用 venv 可改为 ./start-platform.sh 8001
|
||||
CMD ["sh", "-c", "cd /app/platform/backend && uvicorn app.main:app --host 0.0.0.0 --port 8001"]
|
||||
@@ -0,0 +1,74 @@
|
||||
# 宇之然内容创作平台 — 私有化 / 白标部署包
|
||||
# YuZhiRan Content Platform — Private / White-label Deployment Package
|
||||
|
||||
本目录用于把「宇之然内容创作平台」作为**可私有化部署的 AI 内容中台**交付给客户(MCN、企业市场部、行业自媒体团队),
|
||||
或作为白标产品转售。平台已内置白标能力:部署后管理员在「白标设置」页即可改平台名称 / Logo / 主色 / 支持邮箱。
|
||||
|
||||
## 交付形态
|
||||
- 单容器即可运行(默认 SQLite,零外部依赖);生产建议挂 PostgreSQL(见下方 docker-compose 注释)。
|
||||
- 全部为静态前端 + FastAPI 后端,无第三方 SaaS 依赖,可跑在内网 / 离线环境。
|
||||
- 符合《AI 生成合成内容标识办法》:AI 稿默认注入显式 + 隐式声明,发布前必须由真人校验(系统不代发)。
|
||||
|
||||
## 一分钟启动(SQLite,零依赖)
|
||||
```bash
|
||||
# 1. 准备后端环境变量(含管理员密码、LLM Key)
|
||||
cp .env.example ../platform/backend/.env
|
||||
vim ../platform/backend/.env # 至少填 LLM_API_KEY / DEFAULT_ADMIN_PASSWORD
|
||||
|
||||
# 2. 构建并启动
|
||||
docker compose up -d --build
|
||||
|
||||
# 3. 访问
|
||||
# 前台: http://<服务器IP>:8001/
|
||||
# 后台: http://<服务器IP>:8001/admin.html (默认 admin / 见 .env 中 DEFAULT_ADMIN_PASSWORD)
|
||||
```
|
||||
|
||||
## 生产部署(PostgreSQL)
|
||||
编辑 `docker-compose.yml`,取消 `db` 服务与 `web` 中 `USE_POSTGRES=true` / `DATABASE_URL` 的注释,
|
||||
并删掉 `web` 里 `USE_POSTGRES=false` 那一行。数据库数据持久化在命名卷 `yzr_pgdata`。
|
||||
|
||||
## 白标(转售 / 客户内网)
|
||||
1. 用管理员登录后台 → 左侧「白标设置」。
|
||||
2. 填入客户的平台名称、Logo URL、主色、支持邮箱 → 保存。
|
||||
3. 前台导航与品牌色即时生效。无需改代码、无需重新构建镜像。
|
||||
(底层读取 `system_configs` 表中 `brand_name / brand_logo / brand_primary_color / brand_support_email`,
|
||||
亦可直接 `UPDATE system_configs SET value='...' WHERE key='brand_name';` 批量初始化多客户。)
|
||||
|
||||
## 数据目录
|
||||
容器内:
|
||||
- 代码:`/app`
|
||||
- SQLite 数据:`/app/platform/backend/yu_zhi_ran.db`
|
||||
- 生成内容/图片:`/app/automation`(建议挂载卷持久化)
|
||||
|
||||
## 环境变量说明(platform/backend/.env)
|
||||
| 变量 | 说明 |
|
||||
| --- | --- |
|
||||
| USE_POSTGRES | true 用 PostgreSQL;false 用 SQLite |
|
||||
| PG_HOST/PG_PORT/PG_DATABASE/PG_USER/PG_PASSWORD | PostgreSQL 连接(USE_POSTGRES=true 时) |
|
||||
| SECRET_KEY | JWT 签名密钥(**务必修改**) |
|
||||
| DEFAULT_ADMIN_USERNAME / DEFAULT_ADMIN_PASSWORD | 初始管理员(首次启动创建) |
|
||||
| LLM_API_KEY / LLM_MODEL / LLM_PROVIDER | 主 LLM(nvidia / opencode-go / sensenova 等) |
|
||||
| OPENCODE_API_KEY / OPENCODE_BASE_URL / OPENCODE_MODEL | 备用推理通道 |
|
||||
| SENSENOVA_API_KEY | 配图生成(可选) |
|
||||
|
||||
## 目录结构(源码)
|
||||
```
|
||||
yu-zhi-ran/
|
||||
├── platform/backend/ # FastAPI + SQLAlchemy 后端
|
||||
├── platform/frontend/ # Vue3 CDN 前端(无需构建)
|
||||
├── automation/ # 选题/案例/生成内容数据
|
||||
├── scripts/ # 采集 / 创作 / 发布 脚本
|
||||
├── tests/ # 集成测试
|
||||
└── deploy/ # 本部署包
|
||||
```
|
||||
|
||||
## 许可与交付建议
|
||||
|
||||
三种对外合作模式(详见官网 landing.html 的「合作模式 / 定价」区):
|
||||
|
||||
1. **免费试用**:私有化试用版(功能完整、限文章数)或预约 1v1 演示,零成本获客钩子。
|
||||
2. **私有化部署(主力)**:每个客户一套独立部署(数据隔离),支持品牌白标。
|
||||
- 建议定价:买断(¥29,800 起)+ 年运维/更新订阅(¥6,000/年起);或按实例数 / 坐席授权。
|
||||
3. **源码售卖(高利润)**:完整源代码一次性授权 + 年订阅技术支持,可二次开发。
|
||||
|
||||
- AI 内容合规责任在内容发布方(客户),平台仅提供声明注入与发布前人工校验闸门(不代发)。
|
||||
@@ -0,0 +1,39 @@
|
||||
services:
|
||||
web:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deploy/Dockerfile
|
||||
container_name: yzr-platform
|
||||
ports:
|
||||
- "8001:8001"
|
||||
environment:
|
||||
# 默认 SQLite(零依赖)。生产请改用下方 PostgreSQL 配置:
|
||||
- USE_POSTGRES=false
|
||||
# - USE_POSTGRES=true
|
||||
# - DATABASE_URL=postgresql://yzr:yzr_password@db:5432/yzr_nr
|
||||
- PROJECT_ROOT=/app
|
||||
- PYTHONUNBUFFERED=1
|
||||
volumes:
|
||||
# 持久化生成内容与数据
|
||||
- app_automation:/app/automation
|
||||
- app_content:/app/content
|
||||
# 挂载后端 .env(含管理员密码、LLM Key)
|
||||
- ./backend.env:/app/platform/backend/.env:ro
|
||||
restart: unless-stopped
|
||||
|
||||
# 生产用 PostgreSQL(取消注释并启用上方 USE_POSTGRES=true / DATABASE_URL)
|
||||
# db:
|
||||
# image: postgres:16-alpine
|
||||
# container_name: yzr-db
|
||||
# environment:
|
||||
# - POSTGRES_USER=yzr
|
||||
# - POSTGRES_PASSWORD=yzr_password
|
||||
# - POSTGRES_DB=yzr_nr
|
||||
# volumes:
|
||||
# - yzr_pgdata:/var/lib/postgresql/data
|
||||
# restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
app_automation:
|
||||
app_content:
|
||||
yzr_pgdata:
|
||||
@@ -0,0 +1,86 @@
|
||||
# 宇之然 · 商业模式与营销方案(2026-07)
|
||||
|
||||
> 目标:把产品从"自用内部工具"升级为"可对外售卖的 AI 内容中台",并在官网首页(landing.html)展示三种商业模式。
|
||||
|
||||
---
|
||||
|
||||
## 一、项目功能梳理(能力盘点)
|
||||
|
||||
| 模块 | 能力 | 商业价值 |
|
||||
|------|------|----------|
|
||||
| 选题 topics | 热点采集 → 选题 → 审查 → 待发布状态机 | 解决"不知道写什么" |
|
||||
| 内容工厂 factory / studio | 三平台差异化生成(知乎深度 / 公众号叙事 / 小红书干货) | 解决"写不出 / 质量低" |
|
||||
| 文章管理 articles | 编辑 / AI 味检测 / 合规闸门 / 发布记录 | 解决"AI 味重 + 合规风险" |
|
||||
| AI 味检测 ai_slop | 30+ 套话规则 + 软硬问题分级 | 差异化卖点(合规) |
|
||||
| 搜索 / GEO metrics + search_rankings | Bing + AI 引用追踪、GEO 就绪度 | 解决"写了没流量" |
|
||||
| 外推 / 素材 / 日历 / 任务 | 全流程运营闭环 | 一体化(vs 多工具拼凑) |
|
||||
| 白标 branding + 私有化 deploy | 改品牌名 / Logo / 主色,Docker 一键部署 | **可直接当产品卖给客户** |
|
||||
|
||||
核心差异化:**三平台独立成稿 + AI 味检测 + GEO 获客 + 合规闸门**,且是"可私有化 / 可白标"的交付形态——这正是 to-B 卖点。
|
||||
|
||||
---
|
||||
|
||||
## 二、市场需求(2026)
|
||||
|
||||
- **内容获客刚需**:公众号 / 知乎 / 小红书仍是中小团队低成本获客主阵地,但"日更 + 多平台 + 质量"人力成本高。
|
||||
- **GEO 新窗口**:ChatGPT / DeepSeek / Perplexity 等 AI 搜索兴起,品牌"被 AI 引用"成新需求,竞品少、溢价高。
|
||||
- **合规强约束**:《AI 生成合成内容标识办法》要求 AI 稿真人校验后发布——产品内"不代发、人工闸门"正好切中。
|
||||
- **私有化偏好上升**:数据不出域、品牌自主,是 MCN / 企业市场部 / 政务行业的硬需求(也是 SaaS 红海里能避开的价格带)。
|
||||
|
||||
---
|
||||
|
||||
## 三、三种商业模式可行性
|
||||
|
||||
| 模式 | 可行性 | 现状 | 要做的事 |
|
||||
|------|--------|------|----------|
|
||||
| **私有化部署** | ✅ 最成熟 | deploy/ Docker 包 + 白标已就绪 | 主要做营销包装 + 报价,几乎零研发 |
|
||||
| **源码售卖** | ✅ 容易 | 整套代码已在 | 加 License / 授权条款 + 定价页(买断 / 年订阅 / 实例数) |
|
||||
| **免费试用** | ⚠️ 需补研发 | 无自注册(仅 admin 建用户) | 以"私有化试用版 / 预约演示 / 限时体验"作为获客钩子,避开纯 SaaS 基础设施投入 |
|
||||
|
||||
**策略结论(沿用第二章)**:纯公有云 SaaS 是红海。真正壁垒是"可私有化 + 可白标 + 合规",因此以 **私有化部署 + 源码售卖为主力,免费试用作获客钩子(而非独立 SaaS)** 最稳。
|
||||
|
||||
---
|
||||
|
||||
## 四、首页营销展示方案(landing.html 改造)
|
||||
|
||||
官网营销首页 `landing.html` 已存在(Hero / 功能 / 流程 / 定价 / FAQ / CTA),但定价仍停留在"SaaS 月订阅"框架。改造内容:
|
||||
|
||||
### 4.1 定价区(#pricing)重构为三段式
|
||||
1. **免费试用**(引流):私有化试用版 / 预约演示,限文章数,零成本体验完整流程。CTA → 申请试用(锚点 #contact)。
|
||||
2. **私有化部署**(主力,推荐):买断 + 年运维订阅,数据不出域 + 白标 + 合规闸门。CTA → 获取报价(#contact)。
|
||||
3. **源码售卖**(高利润):一次性授权 + 年订阅支持,可二次开发。CTA → 联系销售(#contact)。
|
||||
|
||||
### 4.2 FAQ 增补
|
||||
- 什么是私有化部署?数据在哪里?
|
||||
- 可以买源码自己改吗?授权方式?
|
||||
- 免费试用怎么申请?
|
||||
- 内容合规怎么保证?(人工闸门 / 不代发)
|
||||
- 数据安全 / 多租户隔离。
|
||||
|
||||
### 4.3 新增"开始合作"联系区(#contact)
|
||||
- 联系信息(邮箱 / 微信 / 商务对接)
|
||||
- 轻量线索表单(姓名 / 公司 / 需求 / 联系方式)→ mailto 提交(无后端依赖,低风险)
|
||||
- 定价 CTA 统一锚定 #contact
|
||||
|
||||
### 4.4 其它文案对齐
|
||||
- 导航"免费使用"→"免费试用";新增"联系销售"
|
||||
- Hero CTA 对齐
|
||||
- deploy/README 定价说明同步为三模式
|
||||
|
||||
---
|
||||
|
||||
## 五、落地优先级
|
||||
|
||||
1. **立即(本次实现)**:landing.html 定价三段式 + FAQ + #contact 表单 + 文案对齐。
|
||||
2. **可选增强**:真实线索入库(/api/leads + Lead 表)、私有化试用版自动下发、报价单生成。
|
||||
3. **远期**:若决定做纯 SaaS 免费试用,再补"开放注册 + 额度"产品能力(当前非必需)。
|
||||
|
||||
---
|
||||
|
||||
## 六、定价建议(示例,可调整)
|
||||
|
||||
| 模式 | 参考报价 | 备注 |
|
||||
|------|----------|------|
|
||||
| 免费试用 | ¥0 | 私有化试用版 / 预约演示,限文章数 |
|
||||
| 私有化部署 | ¥29,800 起(买断)+ 年运维订阅 ¥6,000/年起 | 按实例 / 坐席可选 |
|
||||
| 源码售卖 | ¥XX,XXX(一次性授权)+ 年订阅支持 | 可二次开发,含部署指导 |
|
||||
@@ -0,0 +1,206 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Depends, Body
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import User, Topic, Article
|
||||
from .auth import get_current_user, org_filter
|
||||
|
||||
router = APIRouter(prefix="/api/ai-slop", tags=["ai-slop"])
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[4]
|
||||
SCRIPTS_DIR = PROJECT_ROOT / "scripts"
|
||||
if not SCRIPTS_DIR.exists():
|
||||
SCRIPTS_DIR = PROJECT_ROOT.parent / "scripts"
|
||||
sys.path.insert(0, str(SCRIPTS_DIR))
|
||||
|
||||
check_article = None
|
||||
polish_with_llm = None
|
||||
clean_html_content = None
|
||||
strip_ai_preface = None
|
||||
strip_thinking_html = None
|
||||
try:
|
||||
from compliance_checker import check_article
|
||||
except Exception:
|
||||
check_article = None
|
||||
try:
|
||||
from compliance_optimizer import polish_with_llm
|
||||
except Exception:
|
||||
polish_with_llm = None
|
||||
try:
|
||||
from content_cleaner import clean_html_content, strip_ai_preface, strip_thinking_html
|
||||
except Exception:
|
||||
clean_html_content = None
|
||||
strip_ai_preface = None
|
||||
strip_thinking_html = None
|
||||
|
||||
HARD_ISSUE_TYPES = ("敏感词", "法律法规", "平台规则", "品牌规范", "资源合规")
|
||||
|
||||
|
||||
class IssueItem(BaseModel):
|
||||
type: str
|
||||
category: str = ""
|
||||
detail: str = ""
|
||||
suggestion: str = ""
|
||||
severity: str = "medium"
|
||||
|
||||
|
||||
class PlatformReport(BaseModel):
|
||||
platform: str
|
||||
score: int
|
||||
passed: bool
|
||||
issues: List[IssueItem]
|
||||
html_preview: str = ""
|
||||
|
||||
|
||||
class ReportResponse(BaseModel):
|
||||
topic_id: str
|
||||
platforms: List[PlatformReport]
|
||||
|
||||
|
||||
class PurifyRequest(BaseModel):
|
||||
topic_id: str
|
||||
platform: str
|
||||
|
||||
|
||||
class PurifyResponse(BaseModel):
|
||||
ok: bool
|
||||
platform: str
|
||||
score_before: Optional[int] = None
|
||||
score_after: Optional[int] = None
|
||||
issues_before: List[IssueItem] = []
|
||||
issues_after: List[IssueItem] = []
|
||||
preview: str = ""
|
||||
message: str = ""
|
||||
|
||||
|
||||
def _normalize_issues(issues: list) -> List[IssueItem]:
|
||||
result = []
|
||||
for i in issues or []:
|
||||
itype = i.get("type", "")
|
||||
severity = "high" if itype in HARD_ISSUE_TYPES else "medium"
|
||||
detail = i.get("detail") or i.get("suggestion") or i.get("word") or i.get("tag") or i.get("pattern") or ""
|
||||
result.append(IssueItem(
|
||||
type=itype,
|
||||
category=i.get("category", ""),
|
||||
detail=detail,
|
||||
suggestion=i.get("suggestion", ""),
|
||||
severity=severity,
|
||||
))
|
||||
return result
|
||||
|
||||
|
||||
def _extract_title(html: str) -> str:
|
||||
import re
|
||||
m = re.search(r"<title>\s*([^<]+?)\s*</title>", html, re.IGNORECASE)
|
||||
if not m:
|
||||
m = re.search(r"<h1[^>]*>\s*([^<]+?)\s*</h1>", html, re.IGNORECASE)
|
||||
return m.group(1).strip() if m else ""
|
||||
|
||||
|
||||
def _extract_content(html: str) -> str:
|
||||
import re
|
||||
text = re.sub(r"<style.*?</style>", "", html, flags=re.DOTALL | re.IGNORECASE)
|
||||
text = re.sub(r"<script.*?</script>", "", text, flags=re.DOTALL | re.IGNORECASE)
|
||||
text = re.sub(r"<[^>]+>", "", text)
|
||||
return re.sub(r"\s+", " ", text).strip()
|
||||
|
||||
|
||||
def _load_topic_data(db, topic_id: str) -> dict:
|
||||
topic = db.query(Topic).filter(Topic.id == topic_id).first()
|
||||
if not topic:
|
||||
return {}
|
||||
return {
|
||||
"topic": {
|
||||
"title": getattr(topic, "title", "") or "",
|
||||
"field": getattr(topic, "field", "") or "",
|
||||
"core_concept": getattr(topic, "core_concept", "") or "",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.get("/report", response_model=ReportResponse)
|
||||
def get_report(topic_id: str, current_user: User = Depends(get_current_user), db=Depends(get_db)):
|
||||
if check_article is None:
|
||||
raise HTTPException(status_code=503, detail="合规检测模块不可用")
|
||||
topic = db.query(Topic).filter(Topic.id == topic_id).first()
|
||||
if not topic:
|
||||
raise HTTPException(status_code=404, detail="选题不存在")
|
||||
of = org_filter(current_user, Topic)
|
||||
if of is not True and topic.org_id != current_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="选题不存在")
|
||||
topic_data = _load_topic_data(db, topic_id)
|
||||
from db_helper import get_articles_by_topic
|
||||
articles = get_articles_by_topic(topic_id)
|
||||
platforms = []
|
||||
for art in articles:
|
||||
platform = art.get("platform")
|
||||
html = art.get("html_content") or ""
|
||||
if not html:
|
||||
continue
|
||||
res = check_article(html, platform, topic_data=topic_data)
|
||||
platforms.append(PlatformReport(
|
||||
platform=platform,
|
||||
score=res.get("score", 0),
|
||||
passed=res.get("passed", False),
|
||||
issues=_normalize_issues(res.get("issues", [])),
|
||||
html_preview=html[:600],
|
||||
))
|
||||
return ReportResponse(topic_id=topic_id, platforms=platforms)
|
||||
|
||||
|
||||
@router.post("/purify", response_model=PurifyResponse)
|
||||
def purify(req: PurifyRequest, current_user: User = Depends(get_current_user), db=Depends(get_db)):
|
||||
if check_article is None:
|
||||
raise HTTPException(status_code=503, detail="合规检测模块不可用")
|
||||
topic = db.query(Topic).filter(Topic.id == req.topic_id).first()
|
||||
if not topic:
|
||||
raise HTTPException(status_code=404, detail="选题不存在")
|
||||
of = org_filter(current_user, Topic)
|
||||
if of is not True and topic.org_id != current_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="选题不存在")
|
||||
from db_helper import get_articles_by_topic, save_article
|
||||
articles = get_articles_by_topic(req.topic_id)
|
||||
target = next((a for a in articles if a.get("platform") == req.platform), None)
|
||||
if not target or not target.get("html_content"):
|
||||
raise HTTPException(status_code=404, detail=f"未找到 {req.platform} 平台的文章")
|
||||
html = target["html_content"]
|
||||
topic_data = _load_topic_data(db, req.topic_id)
|
||||
|
||||
before = check_article(html, req.platform, topic_data=topic_data)
|
||||
issues_before = _normalize_issues(before.get("issues", []))
|
||||
raw_issues = before.get("issues", [])
|
||||
|
||||
polished_html, log_msg = (html, None)
|
||||
if polish_with_llm is not None:
|
||||
polished_html, log_msg = polish_with_llm(html, req.platform, remaining_issues=raw_issues)
|
||||
|
||||
cleaned = polished_html
|
||||
if clean_html_content is not None:
|
||||
cleaned = clean_html_content(cleaned)
|
||||
if strip_ai_preface is not None:
|
||||
cleaned = strip_ai_preface(cleaned)
|
||||
if strip_thinking_html is not None:
|
||||
cleaned = strip_thinking_html(cleaned)
|
||||
|
||||
title = _extract_title(cleaned)
|
||||
content = _extract_content(cleaned)
|
||||
save_article(req.topic_id, req.platform, cleaned, title=title, content=content, db=db)
|
||||
|
||||
after = check_article(cleaned, req.platform, topic_data=topic_data)
|
||||
issues_after = _normalize_issues(after.get("issues", []))
|
||||
|
||||
message = "净化完成" + (f"({log_msg})" if log_msg else "(仅执行清洗,未调用 LLM)")
|
||||
return PurifyResponse(
|
||||
ok=True,
|
||||
platform=req.platform,
|
||||
score_before=before.get("score"),
|
||||
score_after=after.get("score"),
|
||||
issues_before=issues_before,
|
||||
issues_after=issues_after,
|
||||
preview=cleaned[:600],
|
||||
message=message,
|
||||
)
|
||||
@@ -0,0 +1,80 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import SystemConfig
|
||||
from .auth import get_current_admin
|
||||
|
||||
router = APIRouter(prefix="/api/branding", tags=["branding"])
|
||||
|
||||
BRAND_KEYS = {
|
||||
"brand_name": "宇之然内容创作平台",
|
||||
"brand_logo": "",
|
||||
"brand_primary_color": "#2563eb",
|
||||
"brand_support_email": "",
|
||||
}
|
||||
|
||||
PUBLIC_RESP_DESC = "白标实例公开信息(供前端运行时应用品牌)"
|
||||
|
||||
|
||||
class BrandingResponse(BaseModel):
|
||||
brand_name: str
|
||||
brand_logo: str
|
||||
brand_primary_color: str
|
||||
brand_support_email: str
|
||||
version: str
|
||||
|
||||
|
||||
class BrandingUpdate(BaseModel):
|
||||
brand_name: Optional[str] = None
|
||||
brand_logo: Optional[str] = None
|
||||
brand_primary_color: Optional[str] = None
|
||||
brand_support_email: Optional[str] = None
|
||||
|
||||
|
||||
def _get(db: Session, key: str) -> str:
|
||||
cfg = db.query(SystemConfig).filter(SystemConfig.key == key).first()
|
||||
if not cfg or cfg.value is None:
|
||||
return BRAND_KEYS.get(key, "")
|
||||
return cfg.value
|
||||
|
||||
|
||||
@router.get("", response_model=BrandingResponse)
|
||||
def get_branding(db: Session = Depends(get_db)):
|
||||
"""公开接口:返回当前实例的白标配置(无需登录),用于前端运行时渲染品牌"""
|
||||
try:
|
||||
from ..main import app
|
||||
version = getattr(app, "version", "0.1.0")
|
||||
except Exception:
|
||||
version = "0.1.0"
|
||||
return BrandingResponse(
|
||||
brand_name=_get(db, "brand_name"),
|
||||
brand_logo=_get(db, "brand_logo"),
|
||||
brand_primary_color=_get(db, "brand_primary_color"),
|
||||
brand_support_email=_get(db, "brand_support_email"),
|
||||
version=version,
|
||||
)
|
||||
|
||||
|
||||
@router.put("", response_model=BrandingResponse)
|
||||
def update_branding(
|
||||
payload: BrandingUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
admin_user=Depends(get_current_admin),
|
||||
):
|
||||
"""管理员接口:更新白标配置(品牌名/Logo/主色/支持邮箱)"""
|
||||
data = payload.model_dump(exclude_unset=True)
|
||||
for key, value in data.items():
|
||||
if key not in BRAND_KEYS:
|
||||
continue
|
||||
if value is None:
|
||||
continue
|
||||
cfg = db.query(SystemConfig).filter(SystemConfig.key == key).first()
|
||||
if cfg:
|
||||
cfg.value = str(value)
|
||||
else:
|
||||
db.add(SystemConfig(key=key, value=str(value), description="白标配置"))
|
||||
db.commit()
|
||||
return get_branding(db)
|
||||
@@ -1,4 +1,10 @@
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import desc
|
||||
from typing import Optional
|
||||
@@ -6,10 +12,18 @@ from datetime import datetime, timezone
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import ExternalProduct, PromotionCampaign, CampaignKeyword, SEOAudit, KeywordRanking, OptimizationTask, User
|
||||
from .auth import get_current_user, org_filter
|
||||
from .auth import get_current_user, get_current_admin, org_filter
|
||||
|
||||
router = APIRouter(prefix="/api/external", tags=["external_promotion"])
|
||||
|
||||
PROJECT_ROOT = Path(__file__).parent.parent.parent.parent.parent
|
||||
SEO_AUDITOR_PATH = PROJECT_ROOT / "scripts" / "seo_auditor.py"
|
||||
|
||||
|
||||
class AuditRunRequest(BaseModel):
|
||||
product_id: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
|
||||
|
||||
def _org_filtered_query(db, model, current_user):
|
||||
query = db.query(model)
|
||||
@@ -467,3 +481,44 @@ def external_overview(
|
||||
"type_breakdown": type_breakdown,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# ===== Run SEO Audit (delegate to scripts/seo_auditor.py) =====
|
||||
|
||||
@router.post("/audit/run")
|
||||
def run_audit(
|
||||
body: AuditRunRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin),
|
||||
):
|
||||
cmd = [sys.executable, str(SEO_AUDITOR_PATH)]
|
||||
if body.product_id:
|
||||
cmd += ["--product-id", str(body.product_id)]
|
||||
elif body.url:
|
||||
cmd += ["--url", body.url]
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail="product_id 或 url 至少提供一个")
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
||||
except subprocess.TimeoutExpired:
|
||||
raise HTTPException(status_code=408, detail="SEO 审计超时")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"审计执行失败: {str(e)}")
|
||||
|
||||
if result.returncode != 0:
|
||||
raise HTTPException(
|
||||
status_code=502,
|
||||
detail={
|
||||
"message": "SEO 审计脚本返回错误",
|
||||
"stderr": result.stderr,
|
||||
"stdout": result.stdout,
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"stdout": result.stdout,
|
||||
"stderr": result.stderr,
|
||||
"returncode": result.returncode,
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
"""发布管理 API"""
|
||||
from fastapi import APIRouter, HTTPException, Depends, Request
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from datetime import datetime, timezone, timedelta, date
|
||||
from typing import Optional, List
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import Topic, PublishRecord, User
|
||||
from sqlalchemy.orm import Session
|
||||
from .auth import get_current_admin, org_filter
|
||||
from .auth import get_current_user, org_filter
|
||||
from ..core.audit_logger import audit_log
|
||||
|
||||
router = APIRouter(prefix="/api/publishing", tags=["publishing"])
|
||||
@@ -40,9 +40,9 @@ async def create_publish_record(
|
||||
req: MultiPublishRequest,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin)
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""多平台发布选题(管理员)"""
|
||||
"""多平台发布选题(运营/管理员,按组织隔离)"""
|
||||
try:
|
||||
q = db.query(Topic).filter(Topic.id == req.topic_id)
|
||||
of = org_filter(current_user, Topic)
|
||||
@@ -66,17 +66,17 @@ async def create_publish_record(
|
||||
record = PublishRecord(
|
||||
topic_id=req.topic_id,
|
||||
platform=platform,
|
||||
action='publish',
|
||||
status='success',
|
||||
action='stage',
|
||||
status='pending_manual',
|
||||
operator=operator,
|
||||
description=f"选题 {req.topic_id} 发布到 {PLATFORM_LABELS.get(platform, platform)}",
|
||||
description=f"选题 {req.topic_id} 已生成 {PLATFORM_LABELS.get(platform, platform)} 发布稿,待人工校验后发布",
|
||||
org_id=topic.org_id
|
||||
)
|
||||
db.add(record)
|
||||
results.append(PublishResult(
|
||||
platform=platform,
|
||||
platform_label=PLATFORM_LABELS.get(platform, platform),
|
||||
status='success'
|
||||
status='pending_manual'
|
||||
))
|
||||
except Exception as e:
|
||||
results.append(PublishResult(
|
||||
@@ -86,16 +86,17 @@ async def create_publish_record(
|
||||
error_msg=str(e)
|
||||
))
|
||||
|
||||
topic.status = '已发布'
|
||||
# 诚实状态流转:本项目无真实自动发布器(且按《AI生成内容标识办法》AI 稿需真人把关),
|
||||
# 故仅将选题置为「待人工发布」,待运营在对应平台校验声明后手动发布。
|
||||
topic.status = 'pending_publish'
|
||||
_now = datetime.now(timezone(timedelta(hours=8)))
|
||||
topic.updated_at = _now
|
||||
topic.published_at = _now.date()
|
||||
db.commit()
|
||||
db.expire_all()
|
||||
db.refresh(topic)
|
||||
|
||||
audit_log(
|
||||
action="publish",
|
||||
action="publish_stage",
|
||||
user=current_user,
|
||||
resource_type="topic",
|
||||
resource_id=req.topic_id,
|
||||
@@ -105,15 +106,104 @@ async def create_publish_record(
|
||||
db=db
|
||||
)
|
||||
|
||||
success_count = sum(1 for r in results if r.status == 'success')
|
||||
staged = sum(1 for r in results if r.status == 'pending_manual')
|
||||
return MultiPublishResponse(
|
||||
ok=success_count > 0,
|
||||
ok=staged == len(results),
|
||||
topic_id=req.topic_id,
|
||||
results=results,
|
||||
message=f"选题 {req.topic_id} 发布完成({success_count}/{len(results)} 平台成功)"
|
||||
message=(
|
||||
f"选题 {req.topic_id} 已生成 {staged}/{len(results)} 个平台的发布稿并置为「待人工发布」。"
|
||||
f"AI 生成内容须由真人校验声明后手动发布,系统不代发。"
|
||||
)
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/records")
|
||||
def list_publish_records(
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""列出发布记录(按组织隔离),用于运营查看「待人工发布 / 已发布」状态"""
|
||||
q = db.query(PublishRecord)
|
||||
of = org_filter(current_user, PublishRecord)
|
||||
if of is not True:
|
||||
q = q.filter(of)
|
||||
recs = q.order_by(PublishRecord.created_at.desc()).all()
|
||||
result = []
|
||||
for r in recs:
|
||||
topic = db.query(Topic).filter(Topic.id == r.topic_id).first()
|
||||
result.append({
|
||||
"id": r.id,
|
||||
"topic_id": r.topic_id,
|
||||
"topic_title": topic.title if topic else None,
|
||||
"platform": r.platform,
|
||||
"action": r.action,
|
||||
"status": r.status,
|
||||
"operator": r.operator,
|
||||
"description": r.description,
|
||||
"url": r.url,
|
||||
"created_at": r.created_at.isoformat() if r.created_at else None,
|
||||
"updated_at": r.updated_at.isoformat() if r.updated_at else None,
|
||||
})
|
||||
return {"records": result}
|
||||
|
||||
|
||||
class MarkPublishedResponse(BaseModel):
|
||||
ok: bool
|
||||
topic_id: str
|
||||
message: str
|
||||
|
||||
|
||||
@router.post("/{topic_id}/mark-published", response_model=MarkPublishedResponse)
|
||||
def mark_published(
|
||||
topic_id: str,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""运营在对应平台手动发布后,回填「已发布」闭环状态"""
|
||||
q = db.query(Topic).filter(Topic.id == topic_id)
|
||||
of = org_filter(current_user, Topic)
|
||||
if of is not True:
|
||||
q = q.filter(of)
|
||||
topic = q.first()
|
||||
if not topic:
|
||||
raise HTTPException(status_code=404, detail=f"选题 {topic_id} 不存在")
|
||||
if topic.status != 'pending_publish':
|
||||
raise HTTPException(status_code=400, detail=f"选题 {topic_id} 当前状态为「{topic.status}」,无需标记发布")
|
||||
|
||||
_now = datetime.now(timezone(timedelta(hours=8)))
|
||||
topic.status = 'published'
|
||||
topic.published_at = date.today()
|
||||
topic.updated_at = _now
|
||||
|
||||
recs = db.query(PublishRecord).filter(
|
||||
PublishRecord.topic_id == topic_id,
|
||||
PublishRecord.status == 'pending_manual'
|
||||
)
|
||||
for r in recs:
|
||||
r.status = 'published'
|
||||
r.updated_at = _now
|
||||
|
||||
db.commit()
|
||||
audit_log(
|
||||
action="publish_confirm",
|
||||
user=current_user,
|
||||
resource_type="topic",
|
||||
resource_id=topic_id,
|
||||
details={"operator": current_user.username},
|
||||
ip_address=request.client.host if request.client else None,
|
||||
user_agent=request.headers.get("user-agent", ""),
|
||||
db=db
|
||||
)
|
||||
return MarkPublishedResponse(
|
||||
ok=True,
|
||||
topic_id=topic_id,
|
||||
message=f"选题 {topic_id} 已标记为「已发布」,发布记录同步更新。"
|
||||
)
|
||||
|
||||
@@ -31,6 +31,7 @@ def list_topics(
|
||||
status: Optional[str] = None,
|
||||
today: Optional[bool] = None,
|
||||
tag: Optional[str] = None,
|
||||
series: Optional[str] = None,
|
||||
search: Optional[str] = None,
|
||||
sort_by: str = Query("priority_score", enum=["priority_score", "created_at", "title", "updated_at"]),
|
||||
order: str = Query("desc", enum=["asc", "desc"]),
|
||||
@@ -54,6 +55,8 @@ def list_topics(
|
||||
query = query.filter(func.date(Topic.created_at) == date.today())
|
||||
if tag:
|
||||
query = query.filter(Topic.tags.contains([tag]))
|
||||
if series:
|
||||
query = query.filter(Topic.series == series)
|
||||
if search:
|
||||
query = query.filter(Topic.title.contains(search))
|
||||
|
||||
@@ -63,7 +66,63 @@ def list_topics(
|
||||
else:
|
||||
query = query.order_by(sort_col.asc())
|
||||
|
||||
return query.offset(offset).limit(limit).all()
|
||||
topics = query.offset(offset).limit(limit).all()
|
||||
# Enrich with article count and previews
|
||||
from ..models import Article
|
||||
topic_ids = [t.id for t in topics]
|
||||
if topic_ids:
|
||||
article_rows = (
|
||||
db.query(Article.topic_id, Article.platform, Article.title, Article.word_count, Article.status)
|
||||
.filter(Article.topic_id.in_(topic_ids))
|
||||
.all()
|
||||
)
|
||||
articles_by_topic: Dict[str, list] = {}
|
||||
for ar in article_rows:
|
||||
articles_by_topic.setdefault(ar.topic_id, []).append({
|
||||
"platform": ar.platform,
|
||||
"title": ar.title,
|
||||
"word_count": ar.word_count,
|
||||
"status": ar.status,
|
||||
})
|
||||
else:
|
||||
articles_by_topic = {}
|
||||
|
||||
result = []
|
||||
for t in topics:
|
||||
t_dict = {
|
||||
"id": t.id,
|
||||
"field_id": t.field_id,
|
||||
"field_name": t.field_name,
|
||||
"org_id": t.org_id,
|
||||
"title": t.title,
|
||||
"format": t.format,
|
||||
"core_concept": t.core_concept,
|
||||
"audience_pain": t.audience_pain,
|
||||
"unique_angle": t.unique_angle,
|
||||
"priority": t.priority,
|
||||
"priority_score": t.priority_score,
|
||||
"total_score": t.total_score,
|
||||
"status": t.status,
|
||||
"tags": t.tags or [],
|
||||
"custom_data": t.custom_data or {},
|
||||
"scoring_data": t.scoring_data or {},
|
||||
"lock_by": t.lock_by,
|
||||
"lock_at": t.lock_at,
|
||||
"series": t.series,
|
||||
"created_at": t.created_at,
|
||||
"updated_at": t.updated_at,
|
||||
"generated_at": t.generated_at,
|
||||
"reviewed_at": t.reviewed_at,
|
||||
"ready_at": t.ready_at,
|
||||
"published_at": t.published_at,
|
||||
"compliance_score": t.compliance_score,
|
||||
"platform_urls": t.platform_urls or {},
|
||||
"cases": [],
|
||||
"article_count": len(articles_by_topic.get(t.id, [])),
|
||||
"articles": articles_by_topic.get(t.id, []),
|
||||
}
|
||||
result.append(t_dict)
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/stats")
|
||||
|
||||
@@ -84,8 +84,9 @@ def run_creator_blocking(topic_id: str = None, timeout: int = 1800):
|
||||
existing = db.query(Article).filter(Article.id == article_id).first()
|
||||
if existing:
|
||||
existing.html_content = html
|
||||
existing.word_count = len(html)
|
||||
else:
|
||||
db.add(Article(id=article_id, topic_id=topic_id, platform=platform_dir, file_path=f"db:{article_id}", html_content=html, status="draft"))
|
||||
db.add(Article(id=article_id, topic_id=topic_id, platform=platform_dir, file_path=f"db:{article_id}", html_content=html, word_count=len(html), status="draft"))
|
||||
hf.unlink()
|
||||
for platform_dir in ["zhihu", "wechat", "xiaohongshu"]:
|
||||
pdir = dd / platform_dir
|
||||
|
||||
@@ -25,8 +25,7 @@ def _get_cmd(topic_ids: List[str] = None):
|
||||
cmd = [str(venv_python), str(script_path)] if venv_python.exists() else ["python3", str(script_path)]
|
||||
if topic_ids:
|
||||
cmd.extend(["--topic-ids", ','.join(topic_ids)])
|
||||
else:
|
||||
cmd.append("--today-only")
|
||||
# 不再使用 --today-only,避免三平台串行撰写时小红书文章错过审查窗口
|
||||
return cmd
|
||||
|
||||
def run_optimizer(topic_ids: List[str] = None):
|
||||
|
||||
@@ -58,6 +58,7 @@ MODULES = {
|
||||
"scheduled_task_monitor": {"name": "⏰ 任务监控", "cron": "*"},
|
||||
"scheduled_rank_tracker": {"name": "🔍 搜索排名追踪", "cron": "07:00"},
|
||||
"scheduled_geo_tracker": {"name": "🌐 AI 搜索引用追踪", "cron": "07:30"},
|
||||
"scheduled_shortvideo": {"name": "🎬 短视频拆条", "cron": "08:00"},
|
||||
}
|
||||
|
||||
LOG_FILE_MAP = {
|
||||
@@ -71,6 +72,7 @@ LOG_FILE_MAP = {
|
||||
"scheduled_task_monitor": "task_monitor",
|
||||
"scheduled_rank_tracker": "rank_tracker",
|
||||
"scheduled_geo_tracker": "geo_tracker",
|
||||
"scheduled_shortvideo": "shortvideo",
|
||||
}
|
||||
|
||||
def _log_to_file(module_id: str, status: str, message: str = None, error_trace: str = None):
|
||||
@@ -188,6 +190,7 @@ class TaskScheduler:
|
||||
("scheduled_reset_search_usage", self._run_reset_search_usage, "搜索用量重置"),
|
||||
("scheduled_rank_tracker", self._run_rank_tracker, "搜索排名追踪"),
|
||||
("scheduled_geo_tracker", self._run_geo_tracker, "AI 搜索引用追踪"),
|
||||
("scheduled_shortvideo", self._run_shortvideo, "短视频拆条"),
|
||||
]
|
||||
|
||||
for module_id, fn, name in MODULE_JOBS:
|
||||
@@ -418,10 +421,11 @@ class TaskScheduler:
|
||||
count = 0
|
||||
for topic in topics:
|
||||
platform_urls = topic.platform_urls or {}
|
||||
zhihu_url = platform_urls.get("zhihu", "")
|
||||
if not zhihu_url:
|
||||
for platform, url in platform_urls.items():
|
||||
if not url:
|
||||
continue
|
||||
m = re.search(r'zhuanlan\.zhihu\.com/p/(\d+)', zhihu_url)
|
||||
if platform == "zhihu":
|
||||
m = re.search(r'zhuanlan\.zhihu\.com/p/(\d+)', url)
|
||||
if not m:
|
||||
continue
|
||||
post_id = m.group(1)
|
||||
@@ -442,7 +446,7 @@ class TaskScheduler:
|
||||
"comments": raw.get("comment_count", raw.get("comments_count", 0)),
|
||||
"shares": raw.get("share_count", 0),
|
||||
"last_fetched": datetime.now(),
|
||||
"publish_url": zhihu_url,
|
||||
"publish_url": url,
|
||||
"data_snapshot": raw,
|
||||
}
|
||||
if existing:
|
||||
@@ -453,6 +457,12 @@ class TaskScheduler:
|
||||
count += 1
|
||||
except Exception:
|
||||
continue
|
||||
elif platform in ("wechat_mp", "xiaohongshu"):
|
||||
logger.info("平台 %s 无公开指标 API,跳过", platform)
|
||||
continue
|
||||
else:
|
||||
logger.info("平台 %s 暂无指标同步支持,跳过", platform)
|
||||
continue
|
||||
if count:
|
||||
db.commit()
|
||||
logger.info("[Scheduled] Metrics sync completed: synced %d zhihu articles", count)
|
||||
@@ -661,6 +671,46 @@ class TaskScheduler:
|
||||
started_at=started, finished_at=datetime.now(timezone.utc))
|
||||
logger.exception("[GeoTracker] AI 搜索引用追踪失败: %s", e)
|
||||
|
||||
def _run_shortvideo(self):
|
||||
"""每日将前一日「待发布」选题的长文拆条为短视频脚本"""
|
||||
started = datetime.now(timezone.utc)
|
||||
log_id = _log_task("scheduled_shortvideo", "running", started_at=started)
|
||||
try:
|
||||
from ..database import SessionLocal
|
||||
from ..models import Topic
|
||||
db = SessionLocal()
|
||||
try:
|
||||
topics = db.query(Topic).filter(
|
||||
Topic.status.in_(["ready", "待发布", "pending_publish"])
|
||||
).order_by(Topic.ready_at.desc().nullslast()).limit(3).all()
|
||||
finally:
|
||||
db.close()
|
||||
if not topics:
|
||||
_log_task("scheduled_shortvideo", "success", log_id=log_id,
|
||||
message="无待拆条选题", started_at=started, finished_at=datetime.now(timezone.utc))
|
||||
return
|
||||
import subprocess
|
||||
total = 0
|
||||
for t in topics:
|
||||
res = subprocess.run(
|
||||
[sys.executable, str(PROJECT_ROOT / "scripts" / "shortvideo.py"), "--topic-id", t.id, "--count", "3"],
|
||||
capture_output=True, text=True, timeout=600
|
||||
)
|
||||
if res.returncode == 0:
|
||||
total += 1
|
||||
_log_task("scheduled_shortvideo", "success", log_id=log_id,
|
||||
message=f"拆条完成: {total}/{len(topics)} 选题",
|
||||
result_data={"topics": len(topics), "done": total},
|
||||
started_at=started, finished_at=datetime.now(timezone.utc))
|
||||
logger.info("[ShortVideo] 拆条完成: %d/%d", total, len(topics))
|
||||
except Exception as e:
|
||||
import traceback
|
||||
_log_task("scheduled_shortvideo", "failed", log_id=log_id,
|
||||
message=str(e),
|
||||
error_trace=traceback.format_exc(),
|
||||
started_at=started, finished_at=datetime.now(timezone.utc))
|
||||
logger.exception("[ShortVideo] 拆条失败: %s", e)
|
||||
|
||||
def get_jobs(self):
|
||||
"""返回当前所有定时任务的状态"""
|
||||
jobs = []
|
||||
|
||||
@@ -129,8 +129,15 @@ def init_db():
|
||||
("publish_records", "org_id", "VARCHAR DEFAULT 'default'"),
|
||||
("content_metrics", "org_id", "VARCHAR DEFAULT 'default'"),
|
||||
("search_rankings", "org_id", "VARCHAR DEFAULT 'default'"),
|
||||
("search_rankings", "ai_search_engine", "VARCHAR"),
|
||||
("search_rankings", "citation_snippet", "TEXT"),
|
||||
("search_rankings", "citation_url", "VARCHAR"),
|
||||
("search_rankings", "geo_score", "INTEGER DEFAULT 0"),
|
||||
("search_rankings", "content_type", "VARCHAR"),
|
||||
("geo_readiness_scores", "org_id", "VARCHAR DEFAULT 'default'"),
|
||||
("articles", "org_id", "VARCHAR DEFAULT 'default'"),
|
||||
("content_tasks", "org_id", "VARCHAR DEFAULT 'default'"),
|
||||
("topics", "series", "VARCHAR"),
|
||||
]:
|
||||
try:
|
||||
conn.execute(text(f"ALTER TABLE {table} ADD COLUMN IF NOT EXISTS {col} {typ}"))
|
||||
|
||||
@@ -76,6 +76,17 @@ def import_initial_data():
|
||||
for cfg in default_system_configs:
|
||||
if db.query(SystemConfig).filter(SystemConfig.key == cfg["key"]).first() is None:
|
||||
db.add(SystemConfig(**cfg))
|
||||
|
||||
# 白标默认值(私有化部署/卖给他人时可改)
|
||||
default_branding = [
|
||||
{"key": "brand_name", "value": "宇之然内容创作平台", "description": "白标:平台名称"},
|
||||
{"key": "brand_logo", "value": "", "description": "白标:Logo URL(留空用文字)"},
|
||||
{"key": "brand_primary_color", "value": "#2563eb", "description": "白标:主色(导航/按钮)"},
|
||||
{"key": "brand_support_email", "value": "", "description": "白标:支持邮箱"},
|
||||
]
|
||||
for b in default_branding:
|
||||
if db.query(SystemConfig).filter(SystemConfig.key == b["key"]).first() is None:
|
||||
db.add(SystemConfig(**b))
|
||||
if db.query(SystemConfig).filter(SystemConfig.key == "review_llm_id").first() is None:
|
||||
first_llm = db.query(LLMConfig).filter(LLMConfig.is_active == True).first()
|
||||
if first_llm:
|
||||
@@ -105,7 +116,7 @@ def import_initial_data():
|
||||
"default_format": "深度分析 3000-8000字,数据驱动",
|
||||
"compliance_rules": {
|
||||
"max_title_len": 100,
|
||||
"allowed_tags": ["科技", "生活", "职场", "教育", "可持续", "AI", "远程工作", "个人成长"],
|
||||
"allowed_tags": ["科技", "AI", "效率", "职场", "教育", "远程工作", "未来工作", "工具"],
|
||||
"forbidden_patterns": ["加微信", "私聊", "付费咨询", "点击领取"]
|
||||
},
|
||||
"is_active": True,
|
||||
@@ -124,7 +135,7 @@ def import_initial_data():
|
||||
"default_format": "个人叙事 2000-4000字,对话感",
|
||||
"compliance_rules": {
|
||||
"max_title_len": 32,
|
||||
"allowed_tags": ["科技", "生活", "职场", "教育", "可持续", "AI", "远程工作", "个人成长"],
|
||||
"allowed_tags": ["科技", "AI", "效率", "职场", "教育", "远程工作", "未来工作", "工具"],
|
||||
"forbidden_patterns": ["诱导分享", "朋友圈", "转发群"]
|
||||
},
|
||||
"is_active": True,
|
||||
@@ -143,7 +154,7 @@ def import_initial_data():
|
||||
"default_format": "精炼干货 400-1000字,实用优先",
|
||||
"compliance_rules": {
|
||||
"max_title_len": 50,
|
||||
"allowed_tags": ["生活方式", "可持续", "AI", "个人成长", "极简", "环保"],
|
||||
"allowed_tags": ["AI", "效率", "科技", "工具", "职场", "生活", "学习方法"],
|
||||
"forbidden_patterns": ["私信", "加群", "导流"]
|
||||
},
|
||||
"is_active": True,
|
||||
@@ -163,9 +174,9 @@ def import_initial_data():
|
||||
else:
|
||||
# 更新已有平台配置的字数要求(迁移:2026-06 内容质量升级)
|
||||
platform_updates = {
|
||||
"zhihu": {"min_words": 3000, "max_words": 8000, "default_format": "深度分析 3000-8000字,数据驱动"},
|
||||
"wechat": {"min_words": 2000, "max_words": 4000, "default_format": "个人叙事 2000-4000字,对话感"},
|
||||
"xiaohongshu": {"min_words": 400, "max_words": 1000, "default_format": "精炼干货 400-1000字,实用优先"},
|
||||
"zhihu": {"min_words": 3000, "max_words": 8000, "default_format": "深度分析 3000-8000字,数据驱动+观点交锋,用一手数据和独特视角切入"},
|
||||
"wechat": {"min_words": 2000, "max_words": 4000, "default_format": "科技人文叙事 2000-4000字,个人反思+情感共鸣,记录人与技术之间的故事"},
|
||||
"xiaohongshu": {"min_words": 400, "max_words": 1000, "default_format": "精炼干货 400-1000字,亲测数据+实操结果,每个结论配真实对比"},
|
||||
}
|
||||
for p in db.query(PlatformConfig).all():
|
||||
if p.platform in platform_updates:
|
||||
@@ -178,41 +189,36 @@ def import_initial_data():
|
||||
|
||||
if db.query(TopicField).count() == 0:
|
||||
fields = [
|
||||
{"name": "未来工作方式", "icon": "💼", "color": "#667eea", "description": "远程工作、零工经济、职业转型", "sort_order": 1},
|
||||
{"name": "AI与效率", "icon": "🤖", "color": "#764ba2", "description": "AI工具、数字助手、效率方法", "sort_order": 2},
|
||||
{"name": "可持续生活", "icon": "🌿", "color": "#67c23a", "description": "环保、低碳、自然生活方式", "sort_order": 3},
|
||||
{"name": "数字游民", "icon": "🌍", "color": "#409eff", "description": "旅行、地理自由、海外生活", "sort_order": 4},
|
||||
{"name": "个人成长", "icon": "📚", "color": "#e6a23c", "description": "学习、技能、认知升级", "sort_order": 5},
|
||||
{"name": "科技人文", "icon": "🔬", "color": "#f56c6c", "description": "科技伦理、数字生活反思", "sort_order": 6},
|
||||
{"name": "AI与效率", "icon": "🤖", "color": "#764ba2", "description": "AI工具实测对比、工作流效率方法、前沿资讯解读——关注技术如何改变人的工作方式", "sort_order": 1},
|
||||
{"name": "科技人文", "icon": "🔬", "color": "#f56c6c", "description": "AI伦理困境、数字生活反思、人机关系探索——科技的温度与边界", "sort_order": 2},
|
||||
{"name": "未来工作方式", "icon": "💼", "color": "#667eea", "description": "远程协作实践、AI时代职业转型、一人企业模式——未来不是等来的", "sort_order": 3},
|
||||
]
|
||||
for f in fields:
|
||||
db.add(TopicField(**f))
|
||||
db.commit()
|
||||
print("✅ 插入默认领域配置")
|
||||
print("✅ 插入默认领域配置(聚焦AI×人文×未来工作)")
|
||||
|
||||
if db.query(CollectorCategory).count() == 0:
|
||||
default_cats = [
|
||||
{"name": "循环消费", "search_query": "以旧换新 二手交易 闲置 循环 2026", "description": "以旧换新/二手交易/租赁经济 | 2025年二手交易额1.69万亿", "sort_order": 1, "is_active": True},
|
||||
{"name": "低碳出行", "search_query": "新能源车 骑行 绿色通勤 低碳出行 2026", "description": "新能源车/骑行/绿色出行 | 年产销破1000万辆", "sort_order": 2, "is_active": True},
|
||||
{"name": "干净饮食", "search_query": "干净饮食 有机食品 植物基 本地食材 2026", "description": "有机食品/植物基/本地食材 | 有机食品1247亿", "sort_order": 3, "is_active": True},
|
||||
{"name": "零浪费生活", "search_query": "零浪费 自带杯 极简生活 可持续时尚 2026", "description": "自带杯/极简/可持续时尚 | 自带杯笔记277万篇", "sort_order": 4, "is_active": True},
|
||||
{"name": "绿色家电与节能", "search_query": "绿色家电 一级能效 以旧换新 节能 2026", "description": "一级能效/国补政策 | 一级能效占比90%+", "sort_order": 5, "is_active": True},
|
||||
{"name": "碳普惠", "search_query": "碳账户 碳普惠 个人碳减排 蚂蚁森林 2026", "description": "碳账户/碳普惠/个人减排 | 武汉200万碳账户", "sort_order": 6, "is_active": True},
|
||||
{"name": "环保科技产品", "search_query": "环保科技 绿色产品 可持续材料 2026", "description": "可持续材料/绿色产品 | 购买占比超32%", "sort_order": 7, "is_active": True},
|
||||
{"name": "AI与效率", "search_query": "AI工具 人工智能 效率提升 2026", "description": "AI工具/效率方法/数字助手 | 2026年AI深度融入消费与生活", "sort_order": 8, "is_active": True},
|
||||
{"name": "AI前沿资讯", "search_query": "AI 人工智能 大模型 2026 前沿 突破 论文解读", "description": "AI行业动态、大模型发布、技术突破 | 2026年AI全面嵌入产业", "sort_order": 1, "is_active": True},
|
||||
{"name": "AI工具实测", "search_query": "AI工具 效率提升 工作流 prompt教程 Cursor Copilot 2026", "description": "AI工具评测、效率工作流、实操指南 | 2026年AI工具爆发", "sort_order": 2, "is_active": True},
|
||||
{"name": "AI与职场", "search_query": "AI 裁员 职业转型 AI技能 远程工作 一人企业 2026", "description": "AI对就业影响、职业转型、技能升级 | 2026年AI重塑就业结构", "sort_order": 3, "is_active": True},
|
||||
{"name": "AI生活化", "search_query": "AI陪伴 AI心理咨询 生活助手 AI写作 智能体 2026", "description": "AI心理咨询/生活搭子/AI人格化 | 商业笔记互动增长263%", "sort_order": 4, "is_active": True},
|
||||
{"name": "科技人文", "search_query": "AI伦理 数字生活 科技反思 人机关系 数据隐私 2026", "description": "AI伦理/数字生活反思/科技温度 | AI从工具到共生", "sort_order": 5, "is_active": True},
|
||||
]
|
||||
for cd in default_cats:
|
||||
existing = db.query(CollectorCategory).filter(CollectorCategory.name == cd["name"]).first()
|
||||
if not existing:
|
||||
db.add(CollectorCategory(**cd))
|
||||
db.commit()
|
||||
print("✅ 插入默认采集类别和信息源")
|
||||
print("✅ 插入默认采集类别(聚焦AI×科技人文)")
|
||||
|
||||
db.commit()
|
||||
|
||||
# 补充缺失的类别和信息源(对已有数据库的迁移)
|
||||
# 补充缺失的采集源(对已有数据库的迁移)
|
||||
for sd in [
|
||||
{"name": "AI工具搜索", "source_type": "web_search", "query": "AI工具 人工智能 效率提升 2026", "credibility": "medium", "focus": "AI与效率", "sort_order": 99, "is_active": True},
|
||||
{"name": "AI前沿搜索", "source_type": "web_search", "query": "AI 人工智能 大模型 前沿 2026", "credibility": "medium", "focus": "AI前沿资讯", "sort_order": 1, "is_active": True},
|
||||
{"name": "AI工具搜索", "source_type": "web_search", "query": "AI工具 效率提升 AI工作流 2026", "credibility": "medium", "focus": "AI工具实测", "sort_order": 2, "is_active": True},
|
||||
]:
|
||||
if not db.query(CollectorSource).filter(CollectorSource.name == sd["name"]).first():
|
||||
db.add(CollectorSource(**sd))
|
||||
@@ -299,21 +305,47 @@ def import_initial_data():
|
||||
db.commit()
|
||||
print("✅ 插入默认角色")
|
||||
|
||||
# 初始化默认菜单(与 uni-nav.js 对齐)
|
||||
if db.query(Menu).count() == 0:
|
||||
# 归一化首页菜单路径,避免 index.html 与 /index.html 产生重复入口
|
||||
idx_plain = db.query(Menu).filter(Menu.path == "index.html").first()
|
||||
idx_slash = db.query(Menu).filter(Menu.path == "/index.html").first()
|
||||
if idx_plain and not idx_slash:
|
||||
idx_plain.path = "/index.html"
|
||||
db.commit()
|
||||
elif idx_plain and idx_slash:
|
||||
db.delete(idx_plain)
|
||||
db.commit()
|
||||
|
||||
# 初始化默认菜单:幂等按 path 对齐(已有时更新排序/角色,缺失时插入)。
|
||||
# 运行时会用 DB 菜单覆盖 uni-nav.js 的 fallback,因此这里必须保证文章管理/内容工厂/白标设置等入口存在。
|
||||
default_menus = [
|
||||
{"name": "仪表盘", "path": "/", "icon": "IconHome", "sort_order": 0, "roles": ["admin", "editor"]},
|
||||
{"name": "选题", "path": "topics.html", "icon": "IconTopic", "sort_order": 1, "roles": ["admin", "editor"]},
|
||||
{"name": "工作台", "path": "/index.html", "icon": "IconHome", "sort_order": 0, "roles": ["admin", "editor"]},
|
||||
{"name": "创作工作台", "path": "studio.html", "icon": "IconEdit", "sort_order": 1, "roles": ["admin", "editor"]},
|
||||
{"name": "数据", "path": "metrics.html", "icon": "IconData", "sort_order": 2, "roles": ["admin", "editor"]},
|
||||
{"name": "日历", "path": "calendar.html", "icon": "IconCalendar", "sort_order": 3, "roles": ["admin", "editor"]},
|
||||
{"name": "素材", "path": "assets.html", "icon": "IconFolder", "sort_order": 4, "roles": ["admin", "editor"]},
|
||||
{"name": "任务", "path": "tasks.html", "icon": "IconMenu", "sort_order": 5, "roles": ["admin", "editor"]},
|
||||
{"name": "系统", "path": "admin.html", "icon": "IconSetting", "sort_order": 6, "roles": ["admin"]},
|
||||
{"name": "外推营销", "path": "campaigns.html", "icon": "IconStar", "sort_order": 6, "roles": ["admin", "editor"]},
|
||||
{"name": "AI味检测", "path": "ai-slop.html", "icon": "IconSearch", "sort_order": 7, "roles": ["admin", "editor"]},
|
||||
{"name": "白标设置", "path": "branding.html", "icon": "IconSetting", "sort_order": 8, "roles": ["admin"]},
|
||||
{"name": "系统", "path": "admin.html", "icon": "IconSetting", "sort_order": 9, "roles": ["admin"]},
|
||||
]
|
||||
for m in default_menus:
|
||||
found = db.query(Menu).filter(Menu.path == m["path"]).first()
|
||||
if found:
|
||||
found.sort_order = m["sort_order"]
|
||||
found.roles = m["roles"]
|
||||
found.icon = m["icon"]
|
||||
found.is_active = True
|
||||
# 保留原名称,避免意外重命名
|
||||
else:
|
||||
db.add(Menu(**m))
|
||||
# 原独立的选题/内容工厂/文章管理已合并进「创作工作台」,从主导航隐藏(页面仍可被 studio 内嵌与直接访问)
|
||||
for legacy in ["topics.html", "factory.html", "articles.html"]:
|
||||
legacy_menu = db.query(Menu).filter(Menu.path == legacy).first()
|
||||
if legacy_menu:
|
||||
legacy_menu.is_active = False
|
||||
db.commit()
|
||||
print("✅ 插入默认菜单")
|
||||
print("✅ 菜单已对齐(创作工作台合并选题/内容工厂/文章管理)")
|
||||
|
||||
# 同步 PostgreSQL 自增序列
|
||||
if os.getenv('USE_POSTGRES', 'true').lower() == 'true':
|
||||
|
||||
@@ -9,7 +9,7 @@ from pathlib import Path
|
||||
|
||||
from .database import engine, get_db, init_db
|
||||
from .models import Base
|
||||
from .api import topics, system, articles, publishing, auth, admin, audit, optimizer_logs, cases, task_logs, task_configs, prompt_configs, llm_configs, system_configs, topic_config, calendar, metrics, assets, tasks, platform_config, collector_mgmt, assistant, config_items, role_configs, menu_configs, search_providers, search_rankings, external_promotion
|
||||
from .api import topics, system, articles, publishing, auth, admin, audit, optimizer_logs, cases, task_logs, task_configs, prompt_configs, llm_configs, system_configs, topic_config, calendar, metrics, assets, tasks, platform_config, collector_mgmt, assistant, config_items, role_configs, menu_configs, search_providers, search_rankings, external_promotion, ai_slop, branding
|
||||
from .initial_data import import_initial_data
|
||||
from .core.scheduler import scheduler
|
||||
|
||||
@@ -106,6 +106,8 @@ app.include_router(menu_configs.public_router)
|
||||
app.include_router(search_providers.router)
|
||||
app.include_router(search_rankings.router)
|
||||
app.include_router(external_promotion.router)
|
||||
app.include_router(ai_slop.router)
|
||||
app.include_router(branding.router)
|
||||
|
||||
# 挂载自动生成的图片(必须先于前端根挂载)
|
||||
PROJECT_ROOT_DIR = Path(__file__).parent.parent.parent.parent
|
||||
|
||||
@@ -265,6 +265,7 @@ class Topic(Base):
|
||||
platform_urls = Column(JSON, default=dict)
|
||||
lock_by = Column(String, nullable=True)
|
||||
lock_at = Column(DateTime, nullable=True)
|
||||
series = Column(String, nullable=True, index=True, comment="所属栏目,如:AI工具实测周报、科技人文思辨")
|
||||
|
||||
field = relationship("TopicField", backref="topics")
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ class TopicBase(BaseModel):
|
||||
scoring_data: Dict[str, Any] = {}
|
||||
lock_by: Optional[str] = None
|
||||
lock_at: Optional[datetime] = None
|
||||
series: Optional[str] = None
|
||||
|
||||
|
||||
class TopicCreate(BaseModel):
|
||||
@@ -90,6 +91,7 @@ class TopicCreate(BaseModel):
|
||||
tags: List[str] = []
|
||||
custom_data: Dict[str, Any] = {}
|
||||
scoring_data: Dict[str, Any] = {}
|
||||
series: Optional[str] = None
|
||||
|
||||
|
||||
class TopicAnalyzeRequest(BaseModel):
|
||||
@@ -123,6 +125,7 @@ class TopicUpdate(BaseModel):
|
||||
scoring_data: Optional[Dict[str, Any]] = None
|
||||
priority_score: Optional[int] = None
|
||||
total_score: Optional[float] = None
|
||||
series: Optional[str] = None
|
||||
|
||||
|
||||
class TopicResponse(TopicBase):
|
||||
@@ -135,6 +138,8 @@ class TopicResponse(TopicBase):
|
||||
compliance_score: Optional[int] = None
|
||||
platform_urls: Dict[str, str] = {}
|
||||
cases: List[Any] = []
|
||||
article_count: int = 0
|
||||
articles: List[Dict[str, Any]] = []
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
@@ -199,7 +199,8 @@
|
||||
<div class="empty-text">暂无平台配置</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-table v-else :data="platformConfigs" border stripe style="width:100%">
|
||||
<template v-else>
|
||||
<el-table class="data-table" :data="platformConfigs" border stripe style="width:100%">
|
||||
<el-table-column prop="platform" label="标识" width="120"></el-table-column>
|
||||
<el-table-column prop="name" label="名称" width="120"></el-table-column>
|
||||
<el-table-column prop="icon" label="图标" width="100">
|
||||
@@ -230,6 +231,22 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="card-list-mobile">
|
||||
<div v-for="pc in platformConfigs" :key="pc.platform" class="card-item">
|
||||
<div class="card-row"><span class="card-label">标识</span><span class="card-value">{{ pc.platform }}</span></div>
|
||||
<div class="card-row"><span class="card-label">名称</span><span class="card-value">{{ pc.name }}</span></div>
|
||||
<div class="card-row"><span class="card-label">图标</span><span class="card-value">{{ pc.icon || '—' }}</span></div>
|
||||
<div class="card-row"><span class="card-label">网址</span><span class="card-value" style="word-break:break-all;">{{ pc.website_url || '—' }}</span></div>
|
||||
<div class="card-row"><span class="card-label">字数</span><span class="card-value">{{ pc.min_words }} - {{ pc.max_words }}</span></div>
|
||||
<div class="card-row"><span class="card-label">配图</span><span class="card-value">{{ pc.requires_image ? pc.image_count_min + '-' + pc.image_count_max + '张' : '不需要' }}</span></div>
|
||||
<div class="card-row"><span class="card-label">激活</span><span class="card-value">{{ pc.is_active ? '是' : '否' }}</span></div>
|
||||
<div class="card-actions">
|
||||
<el-button size="small" @click="showPcDialogFn(pc)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deletePlatformConfig(pc.platform)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-dialog v-model="showPcDialog" :title="pcDialogTitle" width="750px" :close-on-click-modal="false">
|
||||
<el-form :model="pcForm" label-width="100px">
|
||||
<el-row :gutter="16">
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>宇之然内容创作平台 - AI味检测</title>
|
||||
<link rel="stylesheet" href="element-plus.css">
|
||||
<link rel="stylesheet" href="theme-modern.css">
|
||||
<script src="uni-nav.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" v-cloak>
|
||||
<uni-nav title="AI味检测" :username="currentUser.username" :is-admin="isAdmin" current-page="ai-slop" @navigate="redirectToPage" @logout="handleLogout">
|
||||
</uni-nav>
|
||||
<div class="main-content">
|
||||
<main class="content-area">
|
||||
<div class="card page-fade">
|
||||
<div class="page-header">
|
||||
<h2 class="page-title"><el-icon style="vertical-align:-2px;"><IconSearch /></el-icon> AI味检测 + 净化</h2>
|
||||
</div>
|
||||
<div class="filter-bar">
|
||||
<el-input v-model="topicId" placeholder="输入选题 ID,如 B02" clearable size="default" style="width:280px;" @keyup.enter="runReport">
|
||||
<template #prepend>选题</template>
|
||||
</el-input>
|
||||
<el-button type="primary" :loading="loadingReport" @click="runReport">检测</el-button>
|
||||
</div>
|
||||
<el-alert v-if="errorMsg" :title="errorMsg" type="error" show-icon :closable="false" style="margin-bottom:12px;"></el-alert>
|
||||
<div v-if="!loadingReport && report && report.platforms.length === 0" style="text-align:center;padding:40px 0;color:#909399;font-size:14px;">
|
||||
该选题暂无文章,请先创作内容。
|
||||
</div>
|
||||
<div v-for="p in (report ? report.platforms : [])" :key="p.platform" style="margin-bottom:20px;">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
|
||||
<strong style="font-size:16px;">{{ platformLabel(p.platform) }}</strong>
|
||||
<el-tag :type="p.passed ? 'success' : 'danger'" size="small">{{ p.passed ? '通过' : '未通过' }}</el-tag>
|
||||
<span style="font-size:13px;color:#909399;">合规分:<b :style="{color: p.score >= 80 ? '#67c23a' : (p.score >= 60 ? '#e6a23c' : '#f56c6c')}">{{ p.score }}</b></span>
|
||||
<el-button size="small" type="warning" :loading="p.purifying" @click="purify(p)" style="margin-left:auto;">一键净化</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="p.message" style="margin-bottom:10px;color:#67c23a;font-size:13px;">{{ p.message }}</div>
|
||||
<div v-if="p.score_before !== null" style="margin-bottom:10px;font-size:13px;color:#606266;">
|
||||
净化前分:<b>{{ p.score_before }}</b> → 净化后分:<b :style="{color:'#67c23a'}">{{ p.score_after }}</b>
|
||||
<span style="margin-left:12px;">问题数:{{ p.issues_before.length }} → {{ p.issues_after.length }}</span>
|
||||
</div>
|
||||
<div v-if="p.issues.length === 0" style="color:#67c23a;font-size:14px;">✅ 未检测到明显问题</div>
|
||||
<template v-else>
|
||||
<el-table class="card-table" :data="p.issues" size="small" stripe>
|
||||
<el-table-column prop="type" label="类型" width="110">
|
||||
<template #default="s"><el-tag :type="s.row.severity === 'high' ? 'danger' : 'warning'" size="small">{{ s.row.type }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="category" label="类别" width="130" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="detail" label="详情" min-width="200" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="suggestion" label="建议" min-width="180" show-overflow-tooltip></el-table-column>
|
||||
</el-table>
|
||||
<div class="card-list-mobile">
|
||||
<div v-for="iss in p.issues" :key="iss.type + iss.detail" class="card-item">
|
||||
<div class="card-row"><span class="card-label">类型</span><span class="card-value"><el-tag :type="iss.severity === 'high' ? 'danger' : 'warning'" size="small">{{ iss.type }}</el-tag></span></div>
|
||||
<div class="card-row"><span class="card-label">类别</span><span class="card-value">{{ iss.category }}</span></div>
|
||||
<div class="card-row"><span class="card-label">详情</span><span class="card-value">{{ iss.detail }}</span></div>
|
||||
<div class="card-row"><span class="card-label">建议</span><span class="card-value">{{ iss.suggestion }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<script src="vue.global.prod.js"></script>
|
||||
<script src="icon-components.js"></script>
|
||||
<script src="element-plus.full.js"></script>
|
||||
<script>
|
||||
const AiSlopApp = {
|
||||
data() {
|
||||
return {
|
||||
isLoggedIn: false, isAdmin: false, currentUser: { username: '' },
|
||||
topicId: '', loadingReport: false, report: null, errorMsg: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getToken() { return localStorage.getItem('authToken'); },
|
||||
async api(url, opts = {}) {
|
||||
const token = this.getToken();
|
||||
if (!token) { this.$message.error('请先登录'); setTimeout(() => window.location.href = '/login.html', 1500); return null; }
|
||||
const res = await fetch(url, { headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json', ...(opts.headers || {}) }, ...opts });
|
||||
if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data.detail || `请求失败: ${res.status}`); }
|
||||
return res.json();
|
||||
},
|
||||
async runReport() {
|
||||
this.errorMsg = '';
|
||||
if (!this.topicId.trim()) { this.$message.warning('请输入选题 ID'); return; }
|
||||
this.loadingReport = true;
|
||||
this.report = null;
|
||||
try {
|
||||
const data = await this.api('/api/ai-slop/report?topic_id=' + encodeURIComponent(this.topicId.trim()));
|
||||
this.report = { topic_id: data.topic_id, platforms: (data.platforms || []).map(p => ({ ...p, purifying: false, message: '', score_before: null, score_after: null, issues_before: [], issues_after: [] })) };
|
||||
} catch (error) {
|
||||
this.errorMsg = error.message;
|
||||
} finally { this.loadingReport = false; }
|
||||
},
|
||||
async purify(p) {
|
||||
p.purifying = true; p.message = '';
|
||||
try {
|
||||
const data = await this.api('/api/ai-slop/purify', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ topic_id: this.topicId.trim(), platform: p.platform })
|
||||
});
|
||||
p.score_before = data.score_before;
|
||||
p.score_after = data.score_after;
|
||||
p.issues_before = data.issues_before || [];
|
||||
p.issues_after = data.issues_after || [];
|
||||
p.issues = data.issues_after || [];
|
||||
p.score = data.score_after;
|
||||
p.passed = (data.score_after !== null && data.issues_after.length === 0) || (data.issues_after && data.issues_after.every(i => i.severity !== 'high')) || (p.score >= 80);
|
||||
p.passed = data.score_after !== null ? (data.issues_after || []).every(i => i.severity !== 'high') : p.passed;
|
||||
p.message = data.message || '净化完成';
|
||||
this.$message.success('净化完成,合规分 ' + data.score_before + ' → ' + data.score_after);
|
||||
} catch (error) {
|
||||
this.$message.error('净化失败: ' + error.message);
|
||||
} finally { p.purifying = false; }
|
||||
},
|
||||
handleLogout() { localStorage.removeItem('authToken'); localStorage.removeItem('userRole'); localStorage.removeItem('currentUser'); window.location.href = '/login.html'; },
|
||||
redirectToPage(page) { window.location.href = page.startsWith('/') ? page : '/' + page; },
|
||||
platformLabel(p) { return { zhihu: '知乎', wechat: '微信公众号', xiaohongshu: '小红书' }[p] || p; },
|
||||
},
|
||||
mounted() {
|
||||
const token = localStorage.getItem('authToken');
|
||||
if (!token) { window.location.href = '/login.html'; return; }
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const tid = params.get('topic_id');
|
||||
if (tid) this.topicId = tid;
|
||||
fetch('/api/auth/me', { headers: { 'Authorization': 'Bearer ' + token } })
|
||||
.then(r => r.ok ? r.json() : Promise.reject())
|
||||
.then(data => {
|
||||
this.currentUser = data.user; this.isAdmin = data.user.role === 'admin'; this.isLoggedIn = true;
|
||||
if (tid) this.runReport();
|
||||
})
|
||||
.catch(() => { localStorage.removeItem('authToken'); localStorage.removeItem('userRole'); localStorage.removeItem('currentUser'); window.location.href = '/login.html'; });
|
||||
}
|
||||
};
|
||||
const app = Vue.createApp(AiSlopApp);
|
||||
app.use(ElementPlus);
|
||||
if (window.installIcons) { window.installIcons(app); }
|
||||
if (window.installUniNav) { window.installUniNav(app); }
|
||||
app.mount('#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+129
-17
@@ -24,10 +24,12 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" v-cloak>
|
||||
<uni-nav title="文章管理" :username="currentUser.username" :is-admin="isAdmin" current-page="articles" @navigate="redirectToPage" @logout="handleLogout">
|
||||
<uni-nav v-if="!isEmbed" title="文章管理" :username="currentUser.username" :is-admin="isAdmin" current-page="articles" @navigate="redirectToPage" @logout="handleLogout">
|
||||
</uni-nav>
|
||||
<div class="main-content">
|
||||
<main class="content-area">
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="文章管理" name="articles">
|
||||
<div class="card page-fade">
|
||||
<div class="page-header">
|
||||
<h2 class="page-title"><el-icon style="vertical-align:-2px;"><IconDocument /></el-icon> 文章管理</h2>
|
||||
@@ -65,17 +67,14 @@
|
||||
<el-table-column prop="word_count" label="字数" width="70">
|
||||
<template #default="scope">{{ scope.row.word_count ?? '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="配图" width="60">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.images && scope.row.images.cover" style="cursor:pointer;font-size:16px;" title="有封面图"><el-icon style="vertical-align:-2px;"><IconPicture /></el-icon></span>
|
||||
<span v-else style="color:#dcdfe6;">—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="created_at" label="创建时间" width="140"><template #default="scope">{{ formatDate(scope.row.created_at) }}</template></el-table-column>
|
||||
<el-table-column label="操作" width="130" fixed="right">
|
||||
<el-table-column label="操作" width="210" fixed="right">
|
||||
<template #default="scope">
|
||||
<div style="display: flex; gap: 4px;">
|
||||
<div style="display: flex; gap: 4px; flex-wrap: wrap;">
|
||||
<el-button size="small" type="primary" @click="previewArticle(scope.row)" style="padding:5px 8px;">预览</el-button>
|
||||
<el-button size="small" @click="editArticle(scope.row)" style="padding:5px 8px;">编辑</el-button>
|
||||
<el-button size="small" type="warning" @click="openSlop(scope.row)" style="padding:5px 8px;">检测</el-button>
|
||||
<el-button size="small" type="success" @click="goPublish()" style="padding:5px 8px;">发布</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteArticle(scope.row)" style="padding:5px 8px;">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -96,6 +95,9 @@
|
||||
</div>
|
||||
<div class="article-card-actions">
|
||||
<el-button size="small" type="primary" @click="previewArticle(article)">预览</el-button>
|
||||
<el-button size="small" @click="editArticle(article)">编辑</el-button>
|
||||
<el-button size="small" type="warning" @click="openSlop(article)">检测</el-button>
|
||||
<el-button size="small" type="success" @click="goPublish()">发布</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteArticle(article)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -104,6 +106,48 @@
|
||||
<el-icon><IconDocument /></el-icon> 暂无文章
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="发布记录" name="publish">
|
||||
<div class="card page-fade">
|
||||
<div class="page-header">
|
||||
<h2 class="page-title"><el-icon style="vertical-align:-2px;"><IconUpload /></el-icon> 发布记录</h2>
|
||||
<el-tag type="info" size="small">AI 内容需真人校验后发布,系统不代发</el-tag>
|
||||
</div>
|
||||
<el-table :data="publishRecords" stripe v-loading="loadingRecords">
|
||||
<el-table-column prop="topic_id" label="选题 ID" width="90"></el-table-column>
|
||||
<el-table-column prop="topic_title" label="选题标题" min-width="200" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="platform" label="平台" width="100">
|
||||
<template #default="scope">{{ platformLabel(scope.row.platform) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="110">
|
||||
<template #default="scope"><el-tag :type="scope.row.status === 'published' ? 'success' : 'warning'" size="small">{{ scope.row.status === 'published' ? '已发布' : '待人工发布' }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operator" label="操作人" width="100"></el-table-column>
|
||||
<el-table-column prop="created_at" label="生成时间" width="140"><template #default="scope">{{ formatDate(scope.row.created_at) }}</template></el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button v-if="scope.row.status === 'pending_manual'" size="small" type="success" @click="markRecord(scope.row)" style="padding:5px 8px;">标记已发布</el-button>
|
||||
<span v-else style="color:#909399;font-size:12px;">—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="card-list-mobile" v-if="publishRecords && publishRecords.length > 0">
|
||||
<div v-for="rec in publishRecords" :key="rec.id" class="card-item">
|
||||
<div class="card-row"><span class="card-label">选题</span><span class="card-value">{{ rec.topic_title || rec.topic_id }}</span></div>
|
||||
<div class="card-row"><span class="card-label">平台</span><span class="card-value">{{ platformLabel(rec.platform) }}</span></div>
|
||||
<div class="card-row"><span class="card-label">状态</span><span class="card-value"><el-tag :type="rec.status === 'published' ? 'success' : 'warning'" size="small">{{ rec.status === 'published' ? '已发布' : '待人工发布' }}</el-tag></span></div>
|
||||
<div class="card-row"><span class="card-label">操作人</span><span class="card-value">{{ rec.operator || '-' }}</span></div>
|
||||
<div class="card-actions" v-if="rec.status === 'pending_manual'">
|
||||
<el-button size="small" type="success" @click="markRecord(rec)">标记已发布</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!loadingRecords && publishRecords.length === 0" style="text-align:center;padding:40px 0;color:#909399;font-size:14px;">
|
||||
<el-icon><IconUpload /></el-icon> 暂无发布记录
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</main>
|
||||
</div>
|
||||
<el-dialog v-model="previewVisible" title="文章预览" width="85%" :modal-props="{ closeOnClickModal: false }" :before-close="() => previewVisible = false" class="preview-dialog-custom" :fullscreen="previewFullscreen" close-on-press-escape :lock-scroll="false">
|
||||
@@ -136,19 +180,31 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="editVisible" title="编辑正文" width="85%" :close-on-click-modal="false">
|
||||
<el-alert type="warning" :closable="false" show-icon style="margin-bottom:12px;">修改后保存即覆盖该平台文章,发布前请先完成 AI 味检测与人工校验。</el-alert>
|
||||
<el-input type="textarea" :rows="20" v-model="editForm.html_content" style="font-family:monospace;"></el-input>
|
||||
<template #footer>
|
||||
<el-button @click="editVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="savingEdit" @click="saveArticle">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
<script src="vue.global.prod.js"></script>
|
||||
<script src="icon-components.js"></script>
|
||||
<script src="element-plus.full.js"></script>
|
||||
<script>
|
||||
const ArticlesApp = {
|
||||
const ArticlesApp = {
|
||||
data() {
|
||||
return {
|
||||
isEmbed: false,
|
||||
isLoggedIn: false, isAdmin: false, currentUser: { username: '' },
|
||||
activeTab: 'articles',
|
||||
loadingTable: false,
|
||||
articles: [],
|
||||
filterPlatform: '', filterStatus: '', searchQuery: '',
|
||||
previewVisible: false, previewArticleData: null, previewFullscreen: false,
|
||||
editVisible: false, savingEdit: false, editForm: { topic_id: '', platform: '', html_content: '' },
|
||||
publishRecords: [], loadingRecords: false,
|
||||
debounceTimer: null
|
||||
}
|
||||
},
|
||||
@@ -164,6 +220,9 @@ const ArticlesApp = {
|
||||
return list;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
activeTab(v) { if (v === 'publish') this.fetchRecords(); }
|
||||
},
|
||||
methods: {
|
||||
getToken() { return localStorage.getItem('authToken'); },
|
||||
async api(url, opts = {}) {
|
||||
@@ -188,6 +247,16 @@ const ArticlesApp = {
|
||||
this.articles = [];
|
||||
} finally { this.loadingTable = false; }
|
||||
},
|
||||
async fetchRecords() {
|
||||
this.loadingRecords = true;
|
||||
try {
|
||||
const data = await this.api('/api/publishing/records');
|
||||
this.publishRecords = data.records || [];
|
||||
} catch (error) {
|
||||
this.$message.error('加载发布记录失败: ' + error.message);
|
||||
this.publishRecords = [];
|
||||
} finally { this.loadingRecords = false; }
|
||||
},
|
||||
debouncedSearch() {
|
||||
clearTimeout(this.debounceTimer);
|
||||
this.debounceTimer = setTimeout(() => { this.fetchArticles(); }, 400);
|
||||
@@ -204,6 +273,48 @@ const ArticlesApp = {
|
||||
this.previewVisible = false;
|
||||
}
|
||||
},
|
||||
async editArticle(article) {
|
||||
this.editForm = { topic_id: article.topic_id, platform: article.platform, html_content: '' };
|
||||
this.editVisible = true;
|
||||
this.savingEdit = false;
|
||||
try {
|
||||
const data = await this.api('/api/articles/detail/' + article.id);
|
||||
this.editForm.html_content = data.html_content || '';
|
||||
} catch (error) {
|
||||
this.$message.error('加载正文失败: ' + error.message);
|
||||
this.editVisible = false;
|
||||
}
|
||||
},
|
||||
async saveArticle() {
|
||||
this.savingEdit = true;
|
||||
try {
|
||||
await this.api('/api/articles/' + encodeURIComponent(this.editForm.topic_id) + '/content', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ platform: this.editForm.platform, html_content: this.editForm.html_content })
|
||||
});
|
||||
this.$message.success('正文已保存');
|
||||
this.editVisible = false;
|
||||
await this.fetchArticles();
|
||||
} catch (error) {
|
||||
this.$message.error('保存失败: ' + error.message);
|
||||
} finally { this.savingEdit = false; }
|
||||
},
|
||||
openSlop(article) {
|
||||
window.open('ai-slop.html?topic_id=' + encodeURIComponent(article.topic_id), '_blank');
|
||||
},
|
||||
goPublish() { this.redirectToPage('topics.html'); },
|
||||
async markRecord(rec) {
|
||||
try {
|
||||
await this.$confirm('确认已在各平台完成发布?', '标记发布', { type: 'warning' });
|
||||
await this.api('/api/publishing/' + encodeURIComponent(rec.topic_id) + '/mark-published', { method: 'POST' });
|
||||
this.$message.success('已标记为已发布');
|
||||
await this.fetchRecords();
|
||||
} catch (e) {
|
||||
if (e === 'cancel') return;
|
||||
const msg = e?.response?.data?.detail || e?.message || '未知错误';
|
||||
this.$message.error('操作失败: ' + msg);
|
||||
}
|
||||
},
|
||||
togglePreviewFullscreen() {
|
||||
this.previewFullscreen = !this.previewFullscreen;
|
||||
this.$nextTick(() => {
|
||||
@@ -243,7 +354,7 @@ const ArticlesApp = {
|
||||
} catch (e) { if (e !== 'cancel') this.$message.error('删除失败: ' + (e.message || '未知错误')); }
|
||||
},
|
||||
handleLogout() { localStorage.removeItem('authToken'); localStorage.removeItem('userRole'); localStorage.removeItem('currentUser'); window.location.href = '/login.html'; },
|
||||
redirectToPage(page) { window.location.href = page.startsWith('/') ? page : '/' + page; },
|
||||
redirectToPage(page) { const url = page.startsWith('/') ? page : '/' + page; if (this.isEmbed) window.top.location.href = url; else window.location.href = url; },
|
||||
platformLabel(p) { return { zhihu: '知乎', wechat: '微信公众号', xiaohongshu: '小红书' }[p] || p; },
|
||||
statusLabel(s) { return { draft: '草稿', reviewed: '已审查', published: '已发布' }[s] || s; },
|
||||
statusTagType(s) { return { draft: 'warning', reviewed: 'success', published: 'info' }[s] || 'primary'; },
|
||||
@@ -256,17 +367,18 @@ const ArticlesApp = {
|
||||
mounted() {
|
||||
const token = localStorage.getItem('authToken');
|
||||
if (!token) { window.location.href = '/login.html'; return; }
|
||||
if (new URLSearchParams(window.location.search).get('embed') === '1') { this.isEmbed = true; document.body.classList.add('embed'); }
|
||||
fetch('/api/auth/me', { headers: { 'Authorization': 'Bearer ' + token } })
|
||||
.then(r => r.ok ? r.json() : Promise.reject())
|
||||
.then(data => { this.currentUser = data.user; this.isAdmin = data.user.role === 'admin'; this.isLoggedIn = true; this.fetchArticles(); })
|
||||
.catch(() => { localStorage.removeItem('authToken'); localStorage.removeItem('userRole'); localStorage.removeItem('currentUser'); window.location.href = '/login.html'; });
|
||||
}
|
||||
};
|
||||
const app = Vue.createApp(ArticlesApp);
|
||||
app.use(ElementPlus);
|
||||
if (window.installIcons) { window.installIcons(app); }
|
||||
if (window.installUniNav) { window.installUniNav(app); }
|
||||
app.mount('#app');
|
||||
};
|
||||
const app = Vue.createApp(ArticlesApp);
|
||||
app.use(ElementPlus);
|
||||
if (window.installIcons) { window.installIcons(app); }
|
||||
if (window.installUniNav) { window.installUniNav(app); }
|
||||
app.mount('#app');
|
||||
</script>
|
||||
<script src="ai-assistant.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>宇之然内容创作平台 - 白标设置</title>
|
||||
<link rel="stylesheet" href="element-plus.css">
|
||||
<link rel="stylesheet" href="theme-modern.css">
|
||||
<script src="uni-nav.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" v-cloak>
|
||||
<uni-nav title="白标设置" :username="currentUser.username" :is-admin="isAdmin" current-page="branding" @navigate="redirectToPage" @logout="handleLogout">
|
||||
</uni-nav>
|
||||
<div class="main-content">
|
||||
<main class="content-area">
|
||||
<div class="card page-fade">
|
||||
<div class="page-header">
|
||||
<h2 class="page-title"><el-icon style="vertical-align:-2px;"><IconSetting /></el-icon> 白标 / 私有化品牌设置</h2>
|
||||
<el-tag type="info" size="small">仅管理员可见</el-tag>
|
||||
</div>
|
||||
<el-alert type="success" :closable="false" show-icon style="margin-bottom:16px;">
|
||||
<template #title>私有化交付说明</template>
|
||||
修改以下配置即可将本平台改名为贵司品牌(名称 / Logo / 主色 / 支持邮箱),保存后前台导航与品牌色即时生效。适合部署到客户内网或作为白标产品交付。
|
||||
</el-alert>
|
||||
|
||||
<el-form label-width="110px" style="max-width:640px;">
|
||||
<el-form-item label="平台名称">
|
||||
<el-input v-model="form.brand_name" placeholder="如:星辰内容中台" maxlength="20" show-word-limit></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Logo 地址">
|
||||
<el-input v-model="form.brand_logo" placeholder="https 图片 URL,留空则显示文字名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="主色">
|
||||
<el-color-picker v-model="form.brand_primary_color"></el-color-picker>
|
||||
<el-input v-model="form.brand_primary_color" style="width:120px;margin-left:12px;" placeholder="#2563eb"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="支持邮箱">
|
||||
<el-input v-model="form.brand_support_email" placeholder="support@yourcompany.com"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="save">保存并应用</el-button>
|
||||
<el-button @click="reset">重置为当前</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-divider>实时预览</el-divider>
|
||||
<div :style="{ background: 'linear-gradient(135deg,' + (form.brand_primary_color || '#2563eb') + ',' + (form.brand_primary_color || '#2563eb') + 'cc)', color:'#fff', padding:'10px 16px', borderRadius:'8px', display:'flex', alignItems:'center', gap:'8px', maxWidth:'480px' }">
|
||||
<img v-if="form.brand_logo" :src="form.brand_logo" style="height:22px;border-radius:4px;" />
|
||||
<strong>{{ form.brand_name || '平台名称预览' }}</strong>
|
||||
<span style="margin-left:auto;font-size:12px;opacity:.85;">v{{ version }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<script src="vue.global.prod.js"></script>
|
||||
<script src="icon-components.js"></script>
|
||||
<script src="element-plus.full.js"></script>
|
||||
<script>
|
||||
const BrandingApp = {
|
||||
data() {
|
||||
return {
|
||||
isLoggedIn: false, isAdmin: false, currentUser: { username: '' },
|
||||
form: { brand_name: '', brand_logo: '', brand_primary_color: '#2563eb', brand_support_email: '' },
|
||||
version: '0.1.0', saving: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getToken() { return localStorage.getItem('authToken'); },
|
||||
async load() {
|
||||
try {
|
||||
const data = await fetch('/api/branding').then(r => r.json());
|
||||
this.form = {
|
||||
brand_name: data.brand_name || '',
|
||||
brand_logo: data.brand_logo || '',
|
||||
brand_primary_color: data.brand_primary_color || '#2563eb',
|
||||
brand_support_email: data.brand_support_email || '',
|
||||
};
|
||||
this.version = data.version || '0.1.0';
|
||||
} catch (e) { this.$message.error('加载品牌配置失败'); }
|
||||
},
|
||||
async save() {
|
||||
this.saving = true;
|
||||
try {
|
||||
const token = this.getToken();
|
||||
const res = await fetch('/api/branding', {
|
||||
method: 'PUT',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(this.form)
|
||||
});
|
||||
if (!res.ok) throw new Error((await res.json()).detail || '保存失败');
|
||||
this.$message.success('已保存,刷新其他页面即可看到新品牌');
|
||||
setTimeout(() => this.load(), 300);
|
||||
} catch (e) {
|
||||
this.$message.error('保存失败:' + e.message);
|
||||
} finally { this.saving = false; }
|
||||
},
|
||||
reset() { this.load(); },
|
||||
handleLogout() { localStorage.removeItem('authToken'); localStorage.removeItem('userRole'); localStorage.removeItem('currentUser'); window.location.href = '/login.html'; },
|
||||
redirectToPage(page) { window.location.href = page.startsWith('/') ? page : '/' + page; },
|
||||
},
|
||||
mounted() {
|
||||
const token = localStorage.getItem('authToken');
|
||||
if (!token) { window.location.href = '/login.html'; return; }
|
||||
fetch('/api/auth/me', { headers: { 'Authorization': 'Bearer ' + token } })
|
||||
.then(r => r.ok ? r.json() : Promise.reject())
|
||||
.then(data => {
|
||||
this.currentUser = data.user; this.isAdmin = data.user.role === 'admin';
|
||||
if (!this.isAdmin) { this.$message.warning('需要管理员权限'); window.location.href = '/index.html'; return; }
|
||||
this.load();
|
||||
})
|
||||
.catch(() => { window.location.href = '/login.html'; });
|
||||
}
|
||||
};
|
||||
const app = Vue.createApp(BrandingApp);
|
||||
app.use(ElementPlus);
|
||||
if (window.installIcons) { window.installIcons(app); }
|
||||
if (window.installUniNav) { window.installUniNav(app); }
|
||||
app.mount('#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -186,7 +186,8 @@
|
||||
<div class="empty-text">该日暂无日程</div>
|
||||
<p style="font-size:13px;color:#909399;margin:4px 0 0 0;">点击上方"添加日程"创建发布计划</p>
|
||||
</div>
|
||||
<el-table v-else :data="selectedDayEntries" stripe>
|
||||
<template v-else>
|
||||
<el-table class="card-table" :data="selectedDayEntries" stripe>
|
||||
<el-table-column prop="title" label="标题" min-width="120"></el-table-column>
|
||||
<el-table-column prop="platform" label="平台" width="100">
|
||||
<template #default="scope">{{ scope.row._source === 'topic' ? (scope.row.status === 'published' ? '已发布' : '选题') : platformName(scope.row.platform) }}</template>
|
||||
@@ -201,6 +202,18 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="card-list-mobile">
|
||||
<div v-for="entry in selectedDayEntries" :key="entry.id || entry.topic_id || entry.title" class="card-item">
|
||||
<div class="card-row"><span class="card-label">标题</span><span class="card-value">{{ entry.title }}</span></div>
|
||||
<div class="card-row"><span class="card-label">平台</span><span class="card-value">{{ entry._source === 'topic' ? (entry.status === 'published' ? '已发布' : '选题') : platformName(entry.platform) }}</span></div>
|
||||
<div class="card-row"><span class="card-label">状态</span><span class="card-value"><el-tag :type="statusType(entry.status)" size="small">{{ statusLabel(entry.status) }}</el-tag></span></div>
|
||||
<div class="card-actions">
|
||||
<el-button v-if="entry._source === 'topic'" size="small" type="primary" @click="openEntryDialog(entry)">查看</el-button>
|
||||
<el-button v-else size="small" @click="openEntryDialog(entry)">编辑</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<link rel="stylesheet" href="element-plus.css">
|
||||
<link rel="stylesheet" href="theme-modern.css">
|
||||
<style>
|
||||
[v-cloak] { display: none; }
|
||||
.data-table { display: block; }
|
||||
@media (max-width: 768px) { .data-table { display: none; } }
|
||||
.stat-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 16px; }
|
||||
@@ -14,8 +15,18 @@
|
||||
.stat-card .num { font-size: 28px; font-weight: 700; color: #2563eb; }
|
||||
.stat-card .label { font-size: 12px; color: #909399; margin-top: 4px; }
|
||||
.search-engine-tag { margin: 2px; }
|
||||
@media (max-width: 768px) {
|
||||
.page-header { flex-direction: column; align-items: flex-start !important; gap: 8px; }
|
||||
.filter-bar { display: flex; flex-wrap: wrap; gap: 4px; }
|
||||
.filter-bar .el-button { margin-left: 0 !important; }
|
||||
.stat-cards { grid-template-columns: repeat(2, 1fr); }
|
||||
.toolbar { flex-wrap: wrap; gap: 8px; }
|
||||
}
|
||||
</style>
|
||||
<script src="uni-nav.js"></script>
|
||||
<script src="vue.global.prod.js"></script>
|
||||
<script src="element-plus.full.js"></script>
|
||||
<script src="icon-components.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" v-cloak>
|
||||
@@ -89,6 +100,17 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="card-list-mobile">
|
||||
<div v-for="p in products" :key="p.id" class="card-item">
|
||||
<div class="card-row"><span class="card-label">名称</span><span class="card-value">{{ p.name }}</span></div>
|
||||
<div class="card-row"><span class="card-label">类型</span><span class="card-value">{{ typeLabel(p.type) }}</span></div>
|
||||
<div class="card-row"><span class="card-label">URL</span><span class="card-value"><a :href="p.url" target="_blank" style="color:#409eff;word-break:break-all;">{{ p.url || '-' }}</a></span></div>
|
||||
<div class="card-actions">
|
||||
<el-button size="small" @click="editProduct(p)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteProduct(p)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="products.length > pageSize" style="display:flex;justify-content:center;margin:12px 0;">
|
||||
<el-pagination background layout="prev, pager, next" :total="products.length" :page-size="pageSize" :current-page="productPage" @current-change="productPage = $event"></el-pagination>
|
||||
</div>
|
||||
@@ -129,6 +151,19 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="card-list-mobile">
|
||||
<div v-for="c in campaigns" :key="c.id" class="card-item">
|
||||
<div class="card-row"><span class="card-label">活动</span><span class="card-value">{{ c.name }}</span></div>
|
||||
<div class="card-row"><span class="card-label">产品</span><span class="card-value">{{ c.product_name || '-' }}</span></div>
|
||||
<div class="card-row"><span class="card-label">状态</span><span class="card-value"><el-tag :type="c.status === 'active' ? 'success' : c.status === 'paused' ? 'warning' : 'info'" size="small">{{ { active: '进行中', paused: '已暂停', completed: '已完成' }[c.status] || c.status }}</el-tag></span></div>
|
||||
<div class="card-row"><span class="card-label">引擎</span><span class="card-value"><el-tag v-for="e in (c.target_engines || [])" :key="e" size="small" class="search-engine-tag">{{ engineLabel(e) }}</el-tag></span></div>
|
||||
<div class="card-actions">
|
||||
<el-button size="small" @click="viewCampaignKeywords(c)">关键词</el-button>
|
||||
<el-button size="small" @click="editCampaign(c)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteCampaign(c)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="campaigns.length > pageSize" style="display:flex;justify-content:center;margin:12px 0;">
|
||||
<el-pagination background layout="prev, pager, next" :total="campaigns.length" :page-size="pageSize" :current-page="campaignPage" @current-change="campaignPage = $event"></el-pagination>
|
||||
</div>
|
||||
@@ -139,7 +174,8 @@
|
||||
关键词详情:{{ selectedCampaign.name }}
|
||||
<el-button size="small" type="primary" style="margin-left:8px;" @click="showKeywordDialog = true">添加关键词</el-button>
|
||||
</h4>
|
||||
<el-table v-if="campaignKeywords.length" :data="campaignKeywords" border stripe class="data-table" style="width:100%">
|
||||
<template v-if="campaignKeywords.length">
|
||||
<el-table :data="campaignKeywords" border stripe class="data-table" style="width:100%">
|
||||
<el-table-column prop="keyword" label="关键词" min-width="150"></el-table-column>
|
||||
<el-table-column prop="search_volume" label="搜索量" width="80"></el-table-column>
|
||||
<el-table-column label="难度" width="70">
|
||||
@@ -154,6 +190,18 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="card-list-mobile">
|
||||
<div v-for="kw in campaignKeywords" :key="kw.id" class="card-item">
|
||||
<div class="card-row"><span class="card-label">关键词</span><span class="card-value">{{ kw.keyword }}</span></div>
|
||||
<div class="card-row"><span class="card-label">搜索量</span><span class="card-value">{{ kw.search_volume || 0 }}</span></div>
|
||||
<div class="card-row"><span class="card-label">难度</span><span class="card-value"><el-tag :type="(kw.difficulty || 0) > 0.7 ? 'danger' : (kw.difficulty || 0) > 0.4 ? 'warning' : 'success'" size="small">{{ kw.difficulty != null ? (kw.difficulty * 100).toFixed(0) + '%' : '-' }}</el-tag></span></div>
|
||||
<div class="card-row"><span class="card-label">排名</span><span class="card-value">当前 {{ kw.current_rank || '-' }} 目标 {{ kw.target_rank || '-' }}</span></div>
|
||||
<div class="card-actions">
|
||||
<el-button size="small" type="danger" @click="deleteKeyword(kw)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else style="text-align:center;padding:20px;color:#909399;font-size:13px;">暂无关键词数据</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -162,7 +210,7 @@
|
||||
<div class="toolbar">
|
||||
<span style="font-size:13px;color:#909399;">
|
||||
共 {{ keywordRankings.length }} 条记录
|
||||
<span v-if="rankingOverview.with_rank != null"> · 有排名 {{ rankingOverview.with_rank }} 条</span>
|
||||
<span v-if="keywordRankingOverview.with_rank != null"> · 有排名 {{ keywordRankingOverview.with_rank }} 条</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="loading" class="card-loading">加载中...</div>
|
||||
@@ -177,7 +225,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="排名" width="80">
|
||||
<template #default="s">
|
||||
<el-tag :type="s.row.rank != null && s.row.rank <= 10 ? 'success' : s.row.rank != null && s.row.rank <= 30 ? 'warning' : 'info'" size="small">{{ s.row.rank || '-' }}</el-tag>
|
||||
<el-tag :type="s.row.rank == null ? 'info' : s.row.rank <= 10 ? 'success' : s.row.rank <= 30 ? 'warning' : 'info'" size="small">{{ s.row.rank || '-' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="url_found" label="排名URL" min-width="200">
|
||||
@@ -187,6 +235,14 @@
|
||||
<template #default="s">{{ s.row.checked_at ? new Date(s.row.checked_at).toLocaleString('zh-CN') : '-' }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="card-list-mobile">
|
||||
<div v-for="r in keywordRankings" :key="r.id" class="card-item">
|
||||
<div class="card-row"><span class="card-label">关键词</span><span class="card-value">{{ r.keyword }}</span></div>
|
||||
<div class="card-row"><span class="card-label">引擎</span><span class="card-value">{{ engineLabel(r.search_engine) }}</span></div>
|
||||
<div class="card-row"><span class="card-label">排名</span><span class="card-value"><el-tag :type="r.rank == null ? 'info' : r.rank <= 10 ? 'success' : r.rank <= 30 ? 'warning' : 'info'" size="small">{{ r.rank || '-' }}</el-tag></span></div>
|
||||
<div class="card-row"><span class="card-label">时间</span><span class="card-value">{{ r.checked_at ? new Date(r.checked_at).toLocaleString('zh-CN') : '-' }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="keywordRankings.length > pageSize" style="display:flex;justify-content:center;margin:12px 0;">
|
||||
<el-pagination background layout="prev, pager, next" :total="keywordRankings.length" :page-size="pageSize" :current-page="rankingPage" @current-change="rankingPage = $event"></el-pagination>
|
||||
</div>
|
||||
@@ -223,6 +279,15 @@
|
||||
<template #default="s">{{ s.row.checked_at ? new Date(s.row.checked_at).toLocaleString('zh-CN') : '-' }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="card-list-mobile">
|
||||
<div v-for="a in audits" :key="a.id" class="card-item">
|
||||
<div class="card-row"><span class="card-label">产品</span><span class="card-value">{{ a.product_name || '-' }}</span></div>
|
||||
<div class="card-row"><span class="card-label">总分</span><span class="card-value"><el-tag :type="(a.overall_score || 0) >= 80 ? 'success' : (a.overall_score || 0) >= 60 ? 'warning' : 'danger'" size="small">{{ a.overall_score != null ? a.overall_score.toFixed(1) : '-' }}</el-tag></span></div>
|
||||
<div class="card-row"><span class="card-label">评分</span><span class="card-value" style="font-size:12px;">M{{ a.meta_score || '-' }} H{{ a.heading_score || '-' }} C{{ a.content_score || '-' }} P{{ a.perf_score || '-' }} L{{ a.links_score || '-' }} Mob{{ a.mobile_score || '-' }}</span></div>
|
||||
<div class="card-row"><span class="card-label">问题数</span><span class="card-value">{{ a.issues_found || 0 }}</span></div>
|
||||
<div class="card-row"><span class="card-label">时间</span><span class="card-value">{{ a.checked_at ? new Date(a.checked_at).toLocaleString('zh-CN') : '-' }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -260,6 +325,19 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="card-list-mobile">
|
||||
<div v-for="t in optimizationTasks" :key="t.id" class="card-item">
|
||||
<div class="card-row"><span class="card-label">严重度</span><span class="card-value"><el-tag :type="t.severity === 'high' ? 'danger' : t.severity === 'medium' ? 'warning' : 'info'" size="small">{{ { high: '高', medium: '中', low: '低' }[t.severity] || t.severity }}</el-tag></span></div>
|
||||
<div class="card-row"><span class="card-label">分类</span><span class="card-value">{{ { meta: 'Meta', heading: '标题', content: '内容', performance: '性能', links: '链接', mobile: '移动端' }[t.category] || t.category }}</span></div>
|
||||
<div class="card-row"><span class="card-label">问题</span><span class="card-value" style="font-size:12px;">{{ t.issue }}</span></div>
|
||||
<div class="card-row"><span class="card-label">建议</span><span class="card-value" style="font-size:12px;">{{ t.recommendation }}</span></div>
|
||||
<div class="card-row"><span class="card-label">状态</span><span class="card-value"><el-tag :type="t.status === 'resolved' ? 'success' : t.status === 'ignored' ? 'info' : 'warning'" size="small">{{ { open: '待处理', resolved: '已解决', ignored: '已忽略' }[t.status] || t.status }}</el-tag></span></div>
|
||||
<div class="card-actions">
|
||||
<el-button v-if="t.status === 'open'" size="small" type="success" @click="updateTaskStatus(t, 'resolved')">解决</el-button>
|
||||
<el-button size="small" @click="updateTaskStatus(t, 'ignored')">忽略</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -342,11 +420,13 @@
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
||||
<script src="https://unpkg.com/element-plus"></script>
|
||||
<script src="icon-components.js"></script>
|
||||
<script>
|
||||
const { createApp, ref, computed, onMounted, nextTick } = Vue;
|
||||
const { ElMessage } = ElementPlus;
|
||||
|
||||
// Auth check — redirect early if no token
|
||||
const authToken = localStorage.getItem('authToken') || '';
|
||||
if (!authToken) { window.location.href = '/login.html'; }
|
||||
|
||||
const app = createApp({
|
||||
data() {
|
||||
@@ -380,7 +460,7 @@
|
||||
showKeywordDialog: false,
|
||||
keywordForm: { keyword: '', search_volume: 0, difficulty: 0.5, target_rank: 10 },
|
||||
|
||||
token: '',
|
||||
token: authToken,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -435,7 +515,7 @@
|
||||
await this.loadTasks();
|
||||
}
|
||||
} catch (e) {
|
||||
ElementPlus.ElMessage.error(e.message);
|
||||
ElMessage.error(e.message);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
@@ -464,7 +544,7 @@
|
||||
const r = await fetch(url, { method, headers: this.headers(), body: JSON.stringify(this.productForm) });
|
||||
const d = await r.json();
|
||||
if (!d.ok) throw new Error(d.detail || '操作失败');
|
||||
ElementPlus.ElMessage.success(this.editingProduct ? '已更新' : '已创建');
|
||||
ElMessage.success(this.editingProduct ? '已更新' : '已创建');
|
||||
this.showProductDialog = false;
|
||||
this.editingProduct = null;
|
||||
this.productForm = { name: '', type: 'website', url: '', domain: '', app_id: '', account_id: '', description: '' };
|
||||
@@ -473,7 +553,7 @@
|
||||
async deleteProduct(p) {
|
||||
if (!confirm('确定删除产品「' + p.name + '」?')) return;
|
||||
await this.fetchJson(this.api('/products/' + p.id), { method: 'DELETE', headers: this.headers() });
|
||||
ElementPlus.ElMessage.success('已删除');
|
||||
ElMessage.success('已删除');
|
||||
this.products = await this.fetchJson(this.api('/products'));
|
||||
},
|
||||
|
||||
@@ -502,7 +582,7 @@
|
||||
const r = await fetch(url, { method, headers: this.headers(), body: JSON.stringify(this.campaignForm) });
|
||||
const d = await r.json();
|
||||
if (!d.ok) throw new Error(d.detail || '操作失败');
|
||||
ElementPlus.ElMessage.success(this.editingCampaign ? '已更新' : '已创建');
|
||||
ElMessage.success(this.editingCampaign ? '已更新' : '已创建');
|
||||
this.showCampaignDialog = false;
|
||||
this.editingCampaign = null;
|
||||
this.campaignForm = { name: '', product_id: null, status: 'active', target_engines: [], keywords: [], notes: '' };
|
||||
@@ -511,18 +591,18 @@
|
||||
async deleteCampaign(c) {
|
||||
if (!confirm('确定删除活动「' + c.name + '」?')) return;
|
||||
await this.fetchJson(this.api('/campaigns/' + c.id), { method: 'DELETE', headers: this.headers() });
|
||||
ElementPlus.ElMessage.success('已删除');
|
||||
ElMessage.success('已删除');
|
||||
this.campaigns = await this.fetchJson(this.api('/campaigns'));
|
||||
},
|
||||
|
||||
// Keywords
|
||||
async saveKeyword() {
|
||||
if (!this.selectedCampaign) { ElementPlus.ElMessage.warning('请先选择活动'); return; }
|
||||
if (!this.selectedCampaign) { ElMessage.warning('请先选择活动'); return; }
|
||||
this.keywordForm.campaign_id = this.selectedCampaign.id;
|
||||
const r = await fetch(this.api('/keywords'), { method: 'POST', headers: this.headers(), body: JSON.stringify(this.keywordForm) });
|
||||
const d = await r.json();
|
||||
if (!d.ok) throw new Error(d.detail || '添加失败');
|
||||
ElementPlus.ElMessage.success('已添加');
|
||||
ElMessage.success('已添加');
|
||||
this.showKeywordDialog = false;
|
||||
this.keywordForm = { keyword: '', search_volume: 0, difficulty: 0.5, target_rank: 10 };
|
||||
this.loadCampaignKeywords(this.selectedCampaign.id);
|
||||
@@ -530,14 +610,14 @@
|
||||
async deleteKeyword(kw) {
|
||||
if (!confirm('确定删除关键词「' + kw.keyword + '」?')) return;
|
||||
await this.fetchJson(this.api('/keywords/' + kw.id), { method: 'DELETE', headers: this.headers() });
|
||||
ElementPlus.ElMessage.success('已删除');
|
||||
ElMessage.success('已删除');
|
||||
if (this.selectedCampaign) this.loadCampaignKeywords(this.selectedCampaign.id);
|
||||
},
|
||||
|
||||
// Optimization Tasks
|
||||
async updateTaskStatus(task, status) {
|
||||
await this.fetchJson(this.api('/optimization-tasks/' + task.id), { method: 'PATCH', headers: this.headers(), body: JSON.stringify({ status: status }) });
|
||||
ElementPlus.ElMessage.success('已更新');
|
||||
ElMessage.success('已更新');
|
||||
this.loadTasks();
|
||||
},
|
||||
|
||||
@@ -549,32 +629,23 @@
|
||||
window.location.href = '/login.html';
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
this.token = localStorage.getItem('authToken') || '';
|
||||
if (!this.token) { window.location.href = '/login.html'; return; }
|
||||
try {
|
||||
const r = await fetch('/api/auth/me', { headers: { 'Authorization': 'Bearer ' + this.token } });
|
||||
if (!r.ok) throw new Error('unauthorized');
|
||||
const d = await r.json();
|
||||
mounted() {
|
||||
const token = this.token;
|
||||
if (!token) { window.location.href = '/login.html'; return; }
|
||||
fetch('/api/auth/me', { headers: { 'Authorization': 'Bearer ' + token } })
|
||||
.then(function (r) { if (!r.ok) throw new Error('unauthorized'); return r.json(); })
|
||||
.then(function (d) {
|
||||
this.currentUser = d.user || d;
|
||||
this.isAdmin = this.currentUser.role === 'admin';
|
||||
window.installUniNav(this.$parent ? null : app).component('uni-nav', window.UniNav);
|
||||
} catch (e) {
|
||||
window.location.href = '/login.html';
|
||||
}
|
||||
}.bind(this))
|
||||
.catch(function () { window.location.href = '/login.html'; });
|
||||
this.loadTabData();
|
||||
},
|
||||
});
|
||||
|
||||
// Register Element Plus icons
|
||||
const { Edit, Delete, Plus, Search, Setting, Monitor, Document, DataBoard, Folder, Menu as IconMenu } = ElementPlusIconsVue || {};
|
||||
if (ElementPlusIconsVue) {
|
||||
for (const [key, comp] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, comp);
|
||||
}
|
||||
}
|
||||
|
||||
app.use(ElementPlus);
|
||||
if (window.installIcons) { window.installIcons(app); }
|
||||
if (window.installUniNav) { window.installUniNav(app); }
|
||||
app.mount('#app');
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
/* ========== Topic Cards (待创作) ========== */
|
||||
.topic-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr));
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
.topic-card {
|
||||
@@ -565,7 +565,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" v-cloak>
|
||||
<uni-nav title="内容工厂" :username="currentUser.username" :is-admin="isAdmin" current-page="factory" @navigate="redirectToPage" @logout="handleLogout">
|
||||
<uni-nav v-if="!isEmbed" title="内容工厂" :username="currentUser.username" :is-admin="isAdmin" current-page="factory" @navigate="redirectToPage" @logout="handleLogout">
|
||||
</uni-nav>
|
||||
|
||||
<div class="main-content">
|
||||
@@ -838,6 +838,7 @@
|
||||
const FactoryApp = {
|
||||
data() {
|
||||
return {
|
||||
isEmbed: false,
|
||||
isLoggedIn: false,
|
||||
isAdmin: false,
|
||||
currentUser: { username: '' },
|
||||
@@ -1037,7 +1038,7 @@ const FactoryApp = {
|
||||
});
|
||||
},
|
||||
goToArticles() {
|
||||
window.location.href = '/articles.html';
|
||||
this.redirectToPage('/articles.html');
|
||||
},
|
||||
handleLogout() {
|
||||
localStorage.removeItem('authToken');
|
||||
@@ -1046,7 +1047,9 @@ const FactoryApp = {
|
||||
window.location.href = '/login.html';
|
||||
},
|
||||
redirectToPage(page) {
|
||||
window.location.href = page.startsWith('/') ? page : '/' + page;
|
||||
const url = page.startsWith('/') ? page : '/' + page;
|
||||
if (this.isEmbed) window.top.location.href = url;
|
||||
else window.location.href = url;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -1055,6 +1058,7 @@ const FactoryApp = {
|
||||
window.location.href = '/login.html';
|
||||
return;
|
||||
}
|
||||
if (new URLSearchParams(window.location.search).get('embed') === '1') { this.isEmbed = true; document.body.classList.add('embed'); }
|
||||
fetch('/api/auth/me', {
|
||||
headers: { 'Authorization': 'Bearer ' + token }
|
||||
})
|
||||
|
||||
+180
-46
@@ -142,6 +142,32 @@
|
||||
.cta a { display: inline-block; background: #fff; color: #2563eb !important; font-size: 16px; font-weight: 700; padding: 14px 40px; border-radius: 10px; transition: all 0.25s; position: relative; }
|
||||
.cta a:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,0.15); }
|
||||
|
||||
/* ===== Contact / Trial ===== */
|
||||
.contact { background: #0f172a; color: #fff; padding: 80px 24px; }
|
||||
.contact .section-title { color: #fff; }
|
||||
.contact .section-subtitle { color: rgba(255,255,255,0.7); }
|
||||
.contact-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 48px; max-width: 1000px; margin: 0 auto; align-items: start; }
|
||||
.contact-info { display: flex; flex-direction: column; gap: 20px; }
|
||||
.contact-info-item { display: flex; gap: 14px; align-items: flex-start; }
|
||||
.ci-icon { width: 44px; height: 44px; border-radius: 12px; background: linear-gradient(135deg, #2563eb, #7c3aed); display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0; }
|
||||
.ci-label { font-size: 0.8rem; color: rgba(255,255,255,0.6); }
|
||||
.ci-value { font-size: 1rem; font-weight: 600; color: #fff; }
|
||||
.ci-value a { color: #fff; }
|
||||
.contact-form { background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.1); border-radius: 16px; padding: 28px; }
|
||||
.contact-form h3 { font-size: 1.1rem; margin-bottom: 18px; color: #fff; }
|
||||
.form-row { margin-bottom: 16px; }
|
||||
.form-row label { display: block; font-size: 13px; color: rgba(255,255,255,0.7); margin-bottom: 6px; }
|
||||
.form-row input, .form-row textarea, .form-row select { width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.15); background: rgba(255,255,255,0.06); color: #fff; font-size: 14px; box-sizing: border-box; font-family: inherit; }
|
||||
.form-row input:focus, .form-row textarea:focus, .form-row select:focus { outline: none; border-color: #2563eb; }
|
||||
.form-row textarea { resize: vertical; min-height: 80px; }
|
||||
.contact-form .landing-btn-primary { width: 100%; text-align: center; border: none; cursor: pointer; font-size: 15px; }
|
||||
.contact-note { font-size: 0.8rem; color: rgba(255,255,255,0.5); margin-top: 12px; text-align: center; }
|
||||
.contact-note a { color: #93c5fd; }
|
||||
.wecom-qr { margin-top: 8px; background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.1); border-radius: 16px; padding: 16px; text-align: center; }
|
||||
.wecom-qr img { width: 160px; height: 160px; object-fit: contain; border-radius: 12px; background: #fff; margin: 0 auto 10px; display: block; }
|
||||
.wecom-qr-cap { font-size: 0.85rem; color: rgba(255,255,255,0.7); }
|
||||
@media (max-width: 768px) { .contact-grid { grid-template-columns: 1fr; gap: 32px; } }
|
||||
|
||||
/* ===== Footer ===== */
|
||||
.landing-footer { background: #0f172a; color: #94a3b8; padding: 64px 24px 32px; }
|
||||
.landing-footer-inner { max-width: 1200px; margin: 0 auto; display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; gap: 40px; }
|
||||
@@ -192,7 +218,8 @@
|
||||
</div>
|
||||
<div class="landing-nav-actions">
|
||||
<a href="/login.html" class="landing-btn-login">登录</a>
|
||||
<a href="/login.html" class="landing-btn-primary">免费使用 →</a>
|
||||
<a href="#contact" class="landing-btn-login">联系销售</a>
|
||||
<a href="#pricing" class="landing-btn-primary">免费试用 →</a>
|
||||
<button class="landing-hamburger" id="hamburger" aria-label="菜单">
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
@@ -203,10 +230,11 @@
|
||||
<a href="#features" class="mobile-link">产品功能</a>
|
||||
<a href="#how-it-works" class="mobile-link">工作流程</a>
|
||||
<a href="#platforms" class="mobile-link">支持平台</a>
|
||||
<a href="#pricing" class="mobile-link">定价</a>
|
||||
<a href="#pricing" class="mobile-link">合作模式</a>
|
||||
<a href="#faq" class="mobile-link">常见问题</a>
|
||||
<a href="#contact" class="mobile-link">联系销售</a>
|
||||
<a href="/login.html" class="landing-btn-login" style="text-align:center;padding:12px;margin-top:8px;">登录</a>
|
||||
<a href="/login.html" class="landing-btn-primary">免费使用 →</a>
|
||||
<a href="#pricing" class="landing-btn-primary">免费试用 →</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -218,8 +246,8 @@
|
||||
<h1>让内容在 AI 时代<br><span>主动找到你的客户</span></h1>
|
||||
<p>宇之然是一站式 AI 内容获客系统。自动生成知乎深度文、公众号叙事文、小红书干货贴——三平台差异化分发。通过 AI 搜索推荐(GEO)和独立站 SEO,让零品牌基础的团队也能持续获得精准流量。</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/login.html" class="landing-btn-primary">免费开始使用 →</a>
|
||||
<a href="#features" class="landing-btn-secondary">了解产品功能</a>
|
||||
<a href="#pricing" class="landing-btn-primary">免费试用 →</a>
|
||||
<a href="#contact" class="landing-btn-secondary">预约演示 / 联系销售</a>
|
||||
</div>
|
||||
<div class="hero-stats">
|
||||
<div class="hero-stat"><div class="hero-stat-num">3</div><div class="hero-stat-label">覆盖平台</div></div>
|
||||
@@ -330,57 +358,53 @@
|
||||
<!-- Pricing -->
|
||||
<section id="pricing" class="pricing">
|
||||
<div class="container">
|
||||
<div style="text-align:center;"><span class="section-label">定价</span></div>
|
||||
<h2 class="section-title">灵活的获客方案</h2>
|
||||
<p class="section-subtitle">从小规模试验到团队级运营,总有适合你的计划</p>
|
||||
<div style="text-align:center;"><span class="section-label">合作模式</span></div>
|
||||
<h2 class="section-title">免费试用 · 私有化部署 · 源码售卖</h2>
|
||||
<p class="section-subtitle">从零成本体验到整套买断,三种方式总有一种适合你</p>
|
||||
<div class="pricing-grid">
|
||||
<div class="pricing-card">
|
||||
<div class="pricing-name">入门版</div>
|
||||
<div class="pricing-price">¥299 <span>/月</span></div>
|
||||
<div class="pricing-desc">个人创作者试水 AI 内容获客</div>
|
||||
<div class="pricing-name">免费试用</div>
|
||||
<div class="pricing-price">¥0 <span>起</span></div>
|
||||
<div class="pricing-desc">零成本体验完整创作闭环</div>
|
||||
<ul class="pricing-features">
|
||||
<li>每月 20 篇文章生产</li>
|
||||
<li>私有化试用版 / 预约 1v1 演示</li>
|
||||
<li>限 5 篇 / 月文章生产</li>
|
||||
<li>三平台差异化内容</li>
|
||||
<li>基础 SEO 元数据注入</li>
|
||||
<li>搜索排名追踪(Bing)</li>
|
||||
<li>GEO 就绪度基础评分</li>
|
||||
<li>标准合规审查</li>
|
||||
<li>基础 AI 味检测</li>
|
||||
<li>基础 GEO 就绪度评分</li>
|
||||
<li>社区与文档支持</li>
|
||||
</ul>
|
||||
<a href="/login.html" class="landing-btn-secondary">开始试用</a>
|
||||
<a href="#contact" class="landing-btn-secondary">申请试用</a>
|
||||
</div>
|
||||
<div class="pricing-card featured">
|
||||
<div class="pricing-badge">推荐</div>
|
||||
<div class="pricing-name">专业版</div>
|
||||
<div class="pricing-price">¥799 <span>/月</span></div>
|
||||
<div class="pricing-desc">内容团队稳定获客的日常工具</div>
|
||||
<div class="pricing-badge">主力推荐</div>
|
||||
<div class="pricing-name">私有化部署</div>
|
||||
<div class="pricing-price">¥29,800 <span>起 / 买断</span></div>
|
||||
<div class="pricing-desc">一套独立部署,数据不出域,可白标</div>
|
||||
<ul class="pricing-features">
|
||||
<li>每月 100 篇文章生产</li>
|
||||
<li>三平台差异化内容</li>
|
||||
<li>高级 SEO + GEO 元数据</li>
|
||||
<li>搜索排名追踪(Bing + AI)</li>
|
||||
<li>AI 搜索引用检测</li>
|
||||
<li>GEO 就绪度完整评分(6维度)</li>
|
||||
<li>全自动定时流水线</li>
|
||||
<li>多用户协作</li>
|
||||
<li>完整功能 + 无限文章生产</li>
|
||||
<li>部署在你的自有服务器</li>
|
||||
<li>品牌白标(名称 / Logo / 主色)</li>
|
||||
<li>AI 味检测 + 合规人工闸门</li>
|
||||
<li>GEO / SEO 全维度优化</li>
|
||||
<li>多用户 / 多租户隔离</li>
|
||||
<li>年运维与更新订阅</li>
|
||||
</ul>
|
||||
<a href="/login.html" class="landing-btn-primary">开始试用</a>
|
||||
<a href="#contact" class="landing-btn-primary">获取报价</a>
|
||||
</div>
|
||||
<div class="pricing-card">
|
||||
<div class="pricing-name">企业版</div>
|
||||
<div class="pricing-price">¥2,499 <span>/月</span></div>
|
||||
<div class="pricing-desc">品牌化内容运营的完整方案</div>
|
||||
<div class="pricing-name">源码售卖</div>
|
||||
<div class="pricing-price">面议 <span>/ 授权</span></div>
|
||||
<div class="pricing-desc">一次性授权,自主迭代可控</div>
|
||||
<ul class="pricing-features">
|
||||
<li>不限文章数量</li>
|
||||
<li>所有平台差异化内容</li>
|
||||
<li>全维度 SEO + GEO 优化</li>
|
||||
<li>全平台搜索排名追踪</li>
|
||||
<li>AI 搜索引用深度分析</li>
|
||||
<li>数据看板 + 定制报告</li>
|
||||
<li>专属 LLM 模型配置</li>
|
||||
<li>API 接入</li>
|
||||
<li>专属技术支持</li>
|
||||
<li>完整源代码交付</li>
|
||||
<li>永久授权 + 年订阅支持</li>
|
||||
<li>可二次开发 / 集成</li>
|
||||
<li>含私有化部署包</li>
|
||||
<li>技术文档 + 部署指导</li>
|
||||
<li>商务定制谈价</li>
|
||||
</ul>
|
||||
<a href="/login.html" class="landing-btn-secondary">联系销售</a>
|
||||
<a href="#contact" class="landing-btn-secondary">联系销售</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -417,6 +441,22 @@
|
||||
<button class="faq-q">我的数据安全吗?</button>
|
||||
<div class="faq-a">系统支持多租户隔离(组织级数据隔离),用户只能看到自己组织的数据。部署在你的自有服务器上,数据不出你的网络。支持 JWT 认证、HTTPS、管理员权限分级管理。</div>
|
||||
</div>
|
||||
<div class="faq-item">
|
||||
<button class="faq-q">什么是私有化部署?和 SaaS 有什么区别?</button>
|
||||
<div class="faq-a">私有化部署把整套平台装在你自己的服务器(或云主机)上,数据完全由你掌控、不出域,可彻底去掉"宇之然"品牌换成你自己的(白标)。相比公有云 SaaS,它更适合对数据合规、品牌自主有要求的 MCN、企业市场部和政务 / 行业机构。</div>
|
||||
</div>
|
||||
<div class="faq-item">
|
||||
<button class="faq-q">可以买源码自己改吗?</button>
|
||||
<div class="faq-a">可以。源码售卖提供完整源代码(后端 FastAPI + 前端 Vue3 CDN + 部署脚本),一次性授权后你拥有永久使用权,可自主二次开发、接入内部系统,并享受年度订阅技术支持与版本更新。</div>
|
||||
</div>
|
||||
<div class="faq-item">
|
||||
<button class="faq-q">免费试用怎么申请?</button>
|
||||
<div class="faq-a">无需信用卡。你可以申请私有化试用版(功能完整、限文章数)自行验证,或预约一次 1v1 在线演示,由我们带你走通选题→生成→AI 味检测→合规发布的完整流程。点击页面底部"申请试用"留下联系方式即可。</div>
|
||||
</div>
|
||||
<div class="faq-item">
|
||||
<button class="faq-q">AI 生成的内容合规吗?</button>
|
||||
<div class="faq-a">合规责任在内容发布方。平台内置"不代发"原则:所有 AI 稿必须经过真人校验、手动标记发布;同时提供 AI 套话检测与软硬问题分级,确保内容可读、可溯源、符合《AI 生成合成内容标识办法》的要求。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -425,8 +465,83 @@
|
||||
<section class="cta">
|
||||
<div class="container" style="position:relative;">
|
||||
<h2>准备好了吗?</h2>
|
||||
<p>立即开始使用宇之然,让 AI 为你的内容获取精准流量</p>
|
||||
<a href="/login.html">免费开始使用 →</a>
|
||||
<p>免费试用、私有化部署或源码采购——选一种方式,让 AI 为你的内容获取精准流量</p>
|
||||
<a href="#contact">获取专属方案 →</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Contact / Trial -->
|
||||
<section id="contact" class="contact">
|
||||
<div class="container">
|
||||
<div style="text-align:center;"><span class="section-label">开始合作</span></div>
|
||||
<h2 class="section-title">选一种方式,和我们聊聊</h2>
|
||||
<p class="section-subtitle">无论是免费试用、私有化部署还是源码采购,留下信息我们会在 1 个工作日内联系你</p>
|
||||
<div class="contact-grid">
|
||||
<div class="contact-info">
|
||||
<div class="contact-info-item">
|
||||
<div class="ci-icon">✉</div>
|
||||
<div>
|
||||
<div class="ci-label">商务邮箱</div>
|
||||
<div class="ci-value"><a href="mailto:contact@yuzhiran.com">contact@yuzhiran.com</a></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="contact-info-item">
|
||||
<div class="ci-icon">💬</div>
|
||||
<div>
|
||||
<div class="ci-label">微信 / 商务对接</div>
|
||||
<div class="ci-value">扫码上方企业微信二维码添加</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="contact-info-item">
|
||||
<div class="ci-icon">📦</div>
|
||||
<div>
|
||||
<div class="ci-label">交付形式</div>
|
||||
<div class="ci-value">私有化实例 / 源码授权 / 试用版</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="contact-info-item">
|
||||
<div class="ci-icon">🛡</div>
|
||||
<div>
|
||||
<div class="ci-label">合规承诺</div>
|
||||
<div class="ci-value">数据不出域 · 不代发 · 人工闸门</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wecom-qr">
|
||||
<img src="/automation/images/wecom-qr.jpg" alt="企业微信二维码">
|
||||
<div class="wecom-qr-cap">企业微信扫码添加 · 备注:私有化 / 源码</div>
|
||||
</div>
|
||||
</div>
|
||||
<form class="contact-form" id="trialForm">
|
||||
<h3>预约咨询 / 申请试用</h3>
|
||||
<div class="form-row">
|
||||
<label>姓名 *</label>
|
||||
<input type="text" name="name" required placeholder="您的称呼">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>公司 / 团队</label>
|
||||
<input type="text" name="company" placeholder="选填">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>合作方式 *</label>
|
||||
<select name="need" required>
|
||||
<option value="免费试用">免费试用(私有化试用版 / 演示)</option>
|
||||
<option value="私有化部署">私有化部署(买断 + 运维)</option>
|
||||
<option value="源码售卖">源码售卖(授权 + 支持)</option>
|
||||
<option value="其他">其他 / 还不确定</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>联系方式 *</label>
|
||||
<input type="text" name="contact" required placeholder="邮箱 / 微信 / 电话">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>需求说明</label>
|
||||
<textarea name="remark" placeholder="简单描述您的场景与规模(选填)"></textarea>
|
||||
</div>
|
||||
<button type="submit" class="landing-btn-primary">提交,获取方案</button>
|
||||
<div class="contact-note">提交将唤起邮件客户端发往 <a href="mailto:contact@yuzhiran.com">contact@yuzhiran.com</a></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -447,7 +562,7 @@
|
||||
<h4>支持</h4>
|
||||
<a href="#faq">常见问题</a>
|
||||
<a href="https://github.com" target="_blank">GitHub</a>
|
||||
<a href="mailto:hello@yu-zhi-ran.com">联系我们</a>
|
||||
<a href="mailto:contact@yuzhiran.com">联系我们</a>
|
||||
</div>
|
||||
<div>
|
||||
<h4>法律</h4>
|
||||
@@ -487,6 +602,25 @@
|
||||
if (!isOpen) item.classList.add('open');
|
||||
});
|
||||
});
|
||||
|
||||
// Trial / contact form -> mailto
|
||||
var trialForm = document.getElementById('trialForm');
|
||||
if (trialForm) {
|
||||
trialForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
var fd = new FormData(trialForm);
|
||||
var name = (fd.get('name') || '').toString().trim();
|
||||
var company = (fd.get('company') || '').toString().trim();
|
||||
var need = (fd.get('need') || '').toString().trim();
|
||||
var contact = (fd.get('contact') || '').toString().trim();
|
||||
var remark = (fd.get('remark') || '').toString().trim();
|
||||
var subject = '【宇之然】' + need + ' - ' + (name || '客户');
|
||||
var body = '姓名:' + name + '\n公司/团队:' + (company || '(未填)') +
|
||||
'\n合作方式:' + need + '\n联系方式:' + contact + '\n需求说明:' + (remark || '(未填)');
|
||||
window.location.href = 'mailto:contact@yuzhiran.com?subject=' +
|
||||
encodeURIComponent(subject) + '&body=' + encodeURIComponent(body);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
.stat-card.danger { background: linear-gradient(135deg, #f56c6c 0%, #f78989 100%); }
|
||||
.stat-value { font-size: 32px; font-weight: 700; }
|
||||
.stat-label { font-size: 14px; opacity: 0.9; margin-top: 4px; }
|
||||
.stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 24px; }
|
||||
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 20px; margin-bottom: 24px; }
|
||||
.chart-container { background: white; border-radius: 12px; padding: 16px; margin-bottom: 24px; height: 320px; position: relative; }
|
||||
.platform-chart { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
|
||||
@media (max-width: 768px) {
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>宇之然内容创作平台 - 创作工作台</title>
|
||||
<link rel="stylesheet" href="element-plus.css">
|
||||
<link rel="stylesheet" href="theme-modern.css">
|
||||
<style>
|
||||
html, body { margin: 0; height: 100%; }
|
||||
[v-cloak] { display: none; }
|
||||
#app { display: flex; flex-direction: column; height: 100vh; }
|
||||
.studio-body { flex: 1; display: flex; flex-direction: column; min-height: 0; padding-top: var(--nav-height); }
|
||||
.studio-tabs { flex: 0 0 auto; padding: 0 16px; background: var(--color-bg-raised); border-bottom: 1px solid var(--color-border); }
|
||||
.studio-tabs .el-tabs__header { margin-bottom: 0; }
|
||||
.studio-iframe-wrap { flex: 1; position: relative; min-height: 0; background: var(--color-bg); }
|
||||
.studio-iframe-wrap iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: none; }
|
||||
@media (max-width: 768px) { .studio-tabs { padding: 0 8px; } }
|
||||
</style>
|
||||
<script src="uni-nav.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" v-cloak>
|
||||
<uni-nav title="创作工作台" :username="currentUser.username" :is-admin="isAdmin" current-page="studio" @navigate="redirectToPage" @logout="handleLogout">
|
||||
</uni-nav>
|
||||
<div class="studio-body">
|
||||
<div class="studio-tabs">
|
||||
<el-tabs v-model="activeTab" @tab-change="onTabChange">
|
||||
<el-tab-pane label="选题" name="topics"></el-tab-pane>
|
||||
<el-tab-pane label="内容生成" name="factory"></el-tab-pane>
|
||||
<el-tab-pane label="文章管理" name="articles"></el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<div class="studio-iframe-wrap">
|
||||
<iframe v-if="tabs.topics" src="topics.html?embed=1" frameborder="0"></iframe>
|
||||
<iframe v-if="tabs.factory" src="factory.html?embed=1" frameborder="0"></iframe>
|
||||
<iframe v-if="tabs.articles" src="articles.html?embed=1" frameborder="0"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="vue.global.prod.js"></script>
|
||||
<script src="element-plus.js"></script>
|
||||
<script src="element-plus-icons.js"></script>
|
||||
<script>
|
||||
const { createApp } = Vue;
|
||||
const StudioApp = {
|
||||
data() {
|
||||
return {
|
||||
isLoggedIn: false, isAdmin: false, currentUser: { username: '' },
|
||||
activeTab: 'topics',
|
||||
tabs: { topics: true, factory: false, articles: false },
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onTabChange(name) { this.tabs[name] = true; },
|
||||
redirectToPage(page) { window.location.href = page.startsWith('/') ? page : '/' + page; },
|
||||
handleLogout() {
|
||||
localStorage.removeItem('authToken');
|
||||
localStorage.removeItem('userRole');
|
||||
localStorage.removeItem('currentUser');
|
||||
window.location.href = '/login.html';
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const token = localStorage.getItem('authToken');
|
||||
if (!token) { window.location.href = '/login.html'; return; }
|
||||
fetch('/api/auth/me', { headers: { 'Authorization': 'Bearer ' + token } })
|
||||
.then(r => r.ok ? r.json() : Promise.reject())
|
||||
.then(data => { this.currentUser = data.user; this.isAdmin = data.user.role === 'admin'; this.isLoggedIn = true; })
|
||||
.catch(() => { localStorage.removeItem('authToken'); localStorage.removeItem('userRole'); localStorage.removeItem('currentUser'); window.location.href = '/login.html'; });
|
||||
}
|
||||
};
|
||||
const app = Vue.createApp(StudioApp);
|
||||
app.use(ElementPlus);
|
||||
if (window.installIcons) { window.installIcons(app); }
|
||||
if (window.installUniNav) { window.installUniNav(app); }
|
||||
app.mount('#app');
|
||||
</script>
|
||||
<script src="ai-assistant.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -68,7 +68,10 @@
|
||||
--content-max-width: 1400px;
|
||||
}
|
||||
|
||||
/* ========== Dark Mode ========== */
|
||||
/* ========== Dark Mode ==========
|
||||
注意:Element Plus 组件库为亮色主题,未同步切换会导致弹窗/卡片内
|
||||
自定义文字在深色变量下不可读。当前明确采用亮色主题以保证对比度,
|
||||
待引入 EP 暗色变量(dark theme vars)后再启用完整暗色模式。
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-bg: #1a1a2e;
|
||||
@@ -76,12 +79,10 @@
|
||||
--color-bg-subtle: #1a1a3e;
|
||||
--color-border: #2a2a4e;
|
||||
--color-border-hover: #4a4a6e;
|
||||
|
||||
--color-text-primary: #e0e6ed;
|
||||
--color-text-regular: #a0a6b0;
|
||||
--color-text-secondary: #6b7280;
|
||||
--color-text-placeholder: #4a4a5e;
|
||||
|
||||
--shadow-sm: 0 1px 3px rgba(0,0,0,0.3);
|
||||
--shadow-md: 0 2px 12px rgba(0,0,0,0.3);
|
||||
--shadow-lg: 0 8px 28px rgba(0,0,0,0.4);
|
||||
@@ -90,6 +91,7 @@
|
||||
.card { background: var(--color-bg-raised); }
|
||||
.el-table { --el-table-tr-bg: var(--color-bg-raised); --el-table-header-bg: var(--color-bg-subtle); }
|
||||
}
|
||||
*/
|
||||
|
||||
/* ========== Reset & Base ========== */
|
||||
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
@@ -119,6 +121,12 @@ body {
|
||||
margin: 0 auto;
|
||||
min-height: calc(100vh - var(--nav-height));
|
||||
}
|
||||
|
||||
/* 嵌入模式(被创作工作台 iframe 加载时):去掉顶部导航留白 */
|
||||
body.embed .main-content {
|
||||
padding-top: 0 !important;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.content-area { flex: 1; padding: var(--spacing-md); overflow-y: auto; }
|
||||
|
||||
/* ========== Card ========== */
|
||||
@@ -128,7 +136,6 @@ body {
|
||||
padding: var(--spacing-lg);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
box-shadow: var(--shadow-md);
|
||||
overflow-x: auto;
|
||||
transition: all 0.3s var(--ease-out);
|
||||
}
|
||||
|
||||
@@ -405,7 +412,7 @@ body:has(.preview-dialog-custom.is-fullscreen) > [class*="el-overlay"] { z-index
|
||||
}
|
||||
@media (min-width: 769px) {
|
||||
.preview-iframe { min-height: 400px; }
|
||||
.preview-dialog-custom { position: relative; left: 90px; }
|
||||
.preview-dialog-custom { position: relative; }
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" v-cloak>
|
||||
<uni-nav title="选题管理" :username="currentUser.username" :is-admin="isAdmin" current-page="topics" @navigate="redirectToPage" @logout="handleLogout">
|
||||
<uni-nav v-if="!isEmbed" title="选题管理" :username="currentUser.username" :is-admin="isAdmin" current-page="topics" @navigate="redirectToPage" @logout="handleLogout">
|
||||
</uni-nav>
|
||||
<div class="main-content">
|
||||
<main class="content-area">
|
||||
@@ -56,6 +56,7 @@
|
||||
<el-button size="default" :type="filterStatus === 'review' ? 'primary' : ''" @click="filterStatus = 'review'; fetchTopics()">待审查 ({{ statusStats.review }})</el-button>
|
||||
<el-button size="default" :type="filterStatus === 'ready' ? 'primary' : ''" @click="filterStatus = 'ready'; fetchTopics()">待发布 ({{ statusStats.ready }})</el-button>
|
||||
<el-button size="default" :type="filterStatus === 'published' ? 'primary' : ''" @click="filterStatus = 'published'; fetchTopics()">已发布 ({{ statusStats.published }})</el-button>
|
||||
<el-button size="default" :type="filterStatus === 'pending_publish' ? 'primary' : ''" @click="filterStatus = 'pending_publish'; fetchTopics()">待人工发布 ({{ statusStats.pendingPublish }})</el-button>
|
||||
</div>
|
||||
<div style="margin-bottom:10px;">
|
||||
<span class="search-toggle" @click="showSearch = !showSearch">🔍 {{ showSearch ? '收起搜索' : '展开搜索' }}</span>
|
||||
@@ -64,29 +65,30 @@
|
||||
<div v-if="showSearch" class="search-area">
|
||||
<el-form :model="searchForm" size="small" label-width="70px" @submit.prevent>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="6"><el-form-item label="选题ID"><el-input v-model="searchForm.id" placeholder="如 A07" clearable @input="doSearch"></el-input></el-form-item></el-col>
|
||||
<el-col :span="10"><el-form-item label="标题"><el-input v-model="searchForm.title" placeholder="关键词" clearable @input="doSearch"></el-input></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="领域"><el-input v-model="searchForm.field" placeholder="如 科技前沿" clearable @input="doSearch"></el-input></el-form-item></el-col>
|
||||
<el-col :xs="24" :sm="12" :md="6"><el-form-item label="选题ID"><el-input v-model="searchForm.id" placeholder="如 A07" clearable @input="doSearch"></el-input></el-form-item></el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8"><el-form-item label="标题"><el-input v-model="searchForm.title" placeholder="关键词" clearable @input="doSearch"></el-input></el-form-item></el-col>
|
||||
<el-col :xs="24" :sm="12" :md="5"><el-form-item label="领域"><el-input v-model="searchForm.field" placeholder="如 科技前沿" clearable @input="doSearch"></el-input></el-form-item></el-col>
|
||||
<el-col :xs="24" :sm="12" :md="5"><el-form-item label="栏目"><el-select v-model="searchForm.series" clearable placeholder="全部" style="width:100%;" @change="doSearch"><el-option v-for="s in seriesOptions" :key="s" :label="s" :value="s"></el-option></el-select></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12"><el-form-item label="创建时间">
|
||||
<el-col :xs="24" :sm="24" :md="12"><el-form-item label="创建时间">
|
||||
<el-date-picker v-model="searchForm.createdStart" type="date" placeholder="开始" style="width:130px;" value-format="YYYY-MM-DD" @change="doSearch"></el-date-picker>
|
||||
<span style="margin:0 4px;color:#909399;">~</span>
|
||||
<el-date-picker v-model="searchForm.createdEnd" type="date" placeholder="结束" style="width:130px;" value-format="YYYY-MM-DD" @change="doSearch"></el-date-picker>
|
||||
</el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="创作时间">
|
||||
<el-col :xs="24" :sm="24" :md="12"><el-form-item label="创作时间">
|
||||
<el-date-picker v-model="searchForm.generatedStart" type="date" placeholder="开始" style="width:130px;" value-format="YYYY-MM-DD" @change="doSearch"></el-date-picker>
|
||||
<span style="margin:0 4px;color:#909399;">~</span>
|
||||
<el-date-picker v-model="searchForm.generatedEnd" type="date" placeholder="结束" style="width:130px;" value-format="YYYY-MM-DD" @change="doSearch"></el-date-picker>
|
||||
</el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12"><el-form-item label="审查时间">
|
||||
<el-col :xs="24" :sm="24" :md="12"><el-form-item label="审查时间">
|
||||
<el-date-picker v-model="searchForm.reviewedStart" type="date" placeholder="开始" style="width:130px;" value-format="YYYY-MM-DD" @change="doSearch"></el-date-picker>
|
||||
<span style="margin:0 4px;color:#909399;">~</span>
|
||||
<el-date-picker v-model="searchForm.reviewedEnd" type="date" placeholder="结束" style="width:130px;" value-format="YYYY-MM-DD" @change="doSearch"></el-date-picker>
|
||||
</el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="发布时间">
|
||||
<el-col :xs="24" :sm="24" :md="12"><el-form-item label="发布时间">
|
||||
<el-date-picker v-model="searchForm.publishedStart" type="date" placeholder="开始" style="width:130px;" value-format="YYYY-MM-DD" @change="doSearch"></el-date-picker>
|
||||
<span style="margin:0 4px;color:#909399;">~</span>
|
||||
<el-date-picker v-model="searchForm.publishedEnd" type="date" placeholder="结束" style="width:130px;" value-format="YYYY-MM-DD" @change="doSearch"></el-date-picker>
|
||||
@@ -108,6 +110,17 @@
|
||||
<el-table-column prop="status" label="状态" width="90">
|
||||
<template #default="scope"><span class="status-badge"><span class="status-dot" :class="scope.row.status"></span>{{ getStatusLabel(scope.row.status) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="文章" width="140">
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.article_count > 0" style="display:flex;gap:4px;flex-wrap:wrap;">
|
||||
<el-tag v-for="a in (scope.row.articles || [])" :key="a.platform" :type="a.status === 'published' ? 'success' : 'warning'" size="small" style="cursor:pointer;" @click="openTopicArticlePreview(scope.row, a)">
|
||||
{{ platformShort(a.platform) }}
|
||||
<span v-if="a.word_count" style="margin-left:2px;opacity:0.7;">{{ fmtWordCount(a.word_count) }}</span>
|
||||
</el-tag>
|
||||
</div>
|
||||
<span v-else style="color:#c0c4cc;font-size:12px;">无</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="compliance_score" label="合规分" width="90">
|
||||
<template #default="scope"><el-progress :percentage="scope.row.compliance_score || 0" :format="() => scope.row.compliance_score || '-'" :stroke-width="15"></el-progress></template>
|
||||
</el-table-column>
|
||||
@@ -122,6 +135,8 @@
|
||||
<el-button size="small" type="success" :disabled="isStatus(scope.row, 'published')" @click="createTopic(scope.row)" style="padding:5px 8px;">创作</el-button>
|
||||
<el-button size="small" type="warning" :disabled="!isStatus(scope.row, 'review')" @click="reviewTopic(scope.row)" style="padding:5px 8px;">审查</el-button>
|
||||
<el-button v-if="isStatus(scope.row, 'ready')" size="small" type="primary" @click="openPublishDialog(scope.row)" style="padding:5px 8px;">发布</el-button>
|
||||
<el-button v-if="isStatus(scope.row, 'pending_publish')" size="small" type="success" @click="markPublished(scope.row)" style="padding:5px 8px;">标记发布</el-button>
|
||||
<el-button size="small" :disabled="(scope.row.article_count || 0) === 0" @click="openSlop(scope.row)" style="padding:5px 8px;">检测</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteTopic(scope.row.id)" style="padding:5px 8px;">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -144,6 +159,10 @@
|
||||
<div class="topic-card-tags">
|
||||
<el-tag size="small" type="info">{{ topic.field }}</el-tag>
|
||||
<el-tag size="small" type="warning">合规{{ topic.compliance_score }}</el-tag>
|
||||
<el-tag v-for="a in (topic.articles || [])" :key="a.platform" size="small" :type="a.status === 'published' ? 'success' : 'warning'" style="cursor:pointer;" @click="openTopicArticlePreview(topic, a)">
|
||||
{{ platformShort(a.platform) }}{{ a.word_count ? ' ' + fmtWordCount(a.word_count) : '' }}
|
||||
</el-tag>
|
||||
<el-tag v-if="topic.article_count === 0" size="small" type="info" effect="plain">无文章</el-tag>
|
||||
</div>
|
||||
<div class="topic-card-meta">
|
||||
<div>创建: {{ formatDate(topic.created_at) }}</div>
|
||||
@@ -156,6 +175,8 @@
|
||||
<el-button size="small" type="success" :disabled="isStatus(topic, 'published')" @click="createTopic(topic)">创作</el-button>
|
||||
<el-button size="small" type="warning" :disabled="!isStatus(topic, 'review')" @click="reviewTopic(topic)">审查</el-button>
|
||||
<el-button v-show="isStatus(topic, 'ready')" size="small" type="primary" @click="openPublishDialog(topic)">发布</el-button>
|
||||
<el-button v-if="isStatus(topic, 'pending_publish')" size="small" type="success" @click="markPublished(topic)">标记发布</el-button>
|
||||
<el-button size="small" :disabled="(topic.article_count || 0) === 0" @click="openSlop(topic)">检测</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteTopic(topic.id)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -240,6 +261,10 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="publishDialogVisible" title="发布确认" width="420px" :close-on-click-modal="false">
|
||||
<el-alert type="warning" :closable="false" show-icon style="margin-bottom:16px;">
|
||||
<template #title>AI 内容需真人校验后发布</template>
|
||||
系统仅生成各平台发布稿并置为「待人工发布」,<b>不代发</b>。请在对应平台粘贴、核对 AI 声明与合规后手动发布,再回来点「标记已发布」。
|
||||
</el-alert>
|
||||
<div v-if="publishTopic">
|
||||
<div style="margin-bottom:16px;">
|
||||
<div style="font-size:14px;color:#606266;margin-bottom:8px;">选题:</div>
|
||||
@@ -320,6 +345,7 @@
|
||||
const TopicsApp = {
|
||||
data() {
|
||||
return {
|
||||
isEmbed: false,
|
||||
isLoggedIn: false, isAdmin: false, currentUser: { username: '' },
|
||||
loadingTable: false, selectedTopicIds: [], filterStatus: '',
|
||||
stats: { total: 0, pending: 0, review: 0, ready: 0, published: 0, today: 0 },
|
||||
@@ -333,7 +359,7 @@ const TopicsApp = {
|
||||
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 },
|
||||
searchForm: { id: '', title: '', field: '', series: '', 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 },
|
||||
@@ -341,26 +367,32 @@ const TopicsApp = {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
seriesOptions() {
|
||||
const s = new Set();
|
||||
for (const t of this.topics) { if (t.series) s.add(t.series); }
|
||||
return [...s].sort();
|
||||
},
|
||||
searchActive() {
|
||||
const f = this.searchForm;
|
||||
return !!(f.id || f.title || f.field || f.createdStart || f.createdEnd || f.generatedStart || f.generatedEnd || f.reviewedStart || f.reviewedEnd || f.publishedStart || f.publishedEnd);
|
||||
return !!(f.id || f.title || f.field || f.series || f.createdStart || f.createdEnd || f.generatedStart || f.generatedEnd || f.reviewedStart || f.reviewedEnd || f.publishedStart || f.publishedEnd);
|
||||
},
|
||||
filteredTopics() {
|
||||
let list;
|
||||
if (this.filterStatus === 'today') list = this.todayTopics;
|
||||
else if (!this.filterStatus) list = this.topics;
|
||||
else {
|
||||
const map = { 'pending': ['pending','待处理'], 'review': ['review','待审查'], 'ready': ['ready','待发布'], 'published': ['published','已发布'] };
|
||||
const map = { 'pending': ['pending','待处理'], 'review': ['review','待审查'], 'ready': ['ready','待发布'], 'published': ['published','已发布'], 'pending_publish': ['pending_publish','待人工发布'] };
|
||||
const allowed = map[this.filterStatus] || [this.filterStatus];
|
||||
list = this.topics.filter(t => allowed.includes(t.status));
|
||||
}
|
||||
// 搜索过滤
|
||||
const sf = this.searchForm;
|
||||
if (sf.id || sf.title || sf.field || sf.createdStart || sf.createdEnd || sf.generatedStart || sf.generatedEnd || sf.reviewedStart || sf.reviewedEnd || sf.publishedStart || sf.publishedEnd) {
|
||||
if (sf.id || sf.title || sf.field || sf.series || sf.createdStart || sf.createdEnd || sf.generatedStart || sf.generatedEnd || sf.reviewedStart || sf.reviewedEnd || sf.publishedStart || sf.publishedEnd) {
|
||||
list = list.filter(t => {
|
||||
if (sf.id && !t.id.toLowerCase().includes(sf.id.toLowerCase())) return false;
|
||||
if (sf.title && !t.title.toLowerCase().includes(sf.title.toLowerCase())) return false;
|
||||
if (sf.field && !(t.field || '').toLowerCase().includes(sf.field.toLowerCase())) return false;
|
||||
if (sf.series && (t.series || '') !== sf.series) return false;
|
||||
const inRange = (val, start, end) => {
|
||||
if (!val) return !start && !end;
|
||||
const d = val.slice(0, 10);
|
||||
@@ -400,8 +432,8 @@ const TopicsApp = {
|
||||
return this.filteredTopics.slice(start, start + this.pageSize);
|
||||
},
|
||||
statusStats() {
|
||||
const s = { total: this.topics.length, pending: 0, review: 0, ready: 0, published: 0 };
|
||||
const aliases = { 'pending': ['pending','待处理'], 'review': ['review','待审查'], 'ready': ['ready','待发布'], 'published': ['published','已发布'] };
|
||||
const s = { total: this.topics.length, pending: 0, review: 0, ready: 0, published: 0, pendingPublish: 0 };
|
||||
const aliases = { 'pending': ['pending','待处理'], 'review': ['review','待审查'], 'ready': ['ready','待发布'], 'published': ['published','已发布'], 'pending_publish': ['pending_publish','待人工发布'] };
|
||||
this.topics.forEach(t => { for (const [k, v] of Object.entries(aliases)) { if (v.includes(t.status)) { s[k]++; break; } } });
|
||||
return s;
|
||||
},
|
||||
@@ -422,8 +454,7 @@ const TopicsApp = {
|
||||
},
|
||||
currentPreviewBody() {
|
||||
const html = this.platformContents[this.previewPlatform];
|
||||
if (!html) return '';
|
||||
if (html.length < 50) return '<div style="padding:40px;text-align:center;color:#909399;"><p style="font-size:36px;margin:0 0 12px;">📝</p><p>该平台内容暂未生成或内容为空</p><p style="font-size:13px;margin-top:8px;">可点击上方「批量创作」重新生成,或在编辑模式下手动添加内容</p></div>';
|
||||
if (!html || html.length < 50) return '<div style="padding:40px;text-align:center;color:#909399;"><p style="font-size:36px;margin:0 0 12px;">📝</p><p>该平台内容暂未生成或内容为空</p><p style="font-size:13px;margin-top:8px;">可点击上方「批量创作」重新生成,或在编辑模式下手动添加内容</p></div>';
|
||||
try {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
@@ -527,6 +558,24 @@ const TopicsApp = {
|
||||
});
|
||||
},
|
||||
platformName(platform) { return { zhihu: '知乎', wechat: '微信公众号', xiaohongshu: '小红书' }[platform] || platform; },
|
||||
platformShort(platform) { return { zhihu: '知', wechat: '公', xiaohongshu: '红' }[platform] || platform; },
|
||||
fmtWordCount(wc) { if (!wc) return ''; if (wc >= 1000) return (wc / 1000).toFixed(wc >= 10000 ? 0 : 1) + 'k'; return wc + ''; },
|
||||
async openTopicArticlePreview(topic, article) {
|
||||
this.previewTopic = topic;
|
||||
this.previewPlatform = article.platform;
|
||||
this.previewVisible = true;
|
||||
this.platformContents = {};
|
||||
this.previewImages = {};
|
||||
const token = this.getToken();
|
||||
if (!token) return;
|
||||
try {
|
||||
const r = await fetch(`/api/articles/${topic.id}/preview?platform=${article.platform}`, { headers: { 'Authorization': 'Bearer ' + token } });
|
||||
if (r.ok) {
|
||||
const d = await r.json();
|
||||
if (d && d.html) { this.platformContents[article.platform] = d.html; if (d.images) this.previewImages[article.platform] = d.images; }
|
||||
}
|
||||
} catch (e) { console.error(`加载${article.platform}预览失败:`, e); }
|
||||
},
|
||||
copyContent(platform) {
|
||||
const html = this.platformContents[platform];
|
||||
if (!html) { this.$message.warning('暂无内容可复制'); return; }
|
||||
@@ -619,13 +668,28 @@ const TopicsApp = {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ topic_id: this.publishTopic.id, platforms: selected })
|
||||
});
|
||||
this.$message.success(`发布完成: ${this.publishTopic.title}`);
|
||||
this.$message.success(data.message || `已生成发布稿,请在各平台校验后手动发布`);
|
||||
this.publishDialogVisible = false;
|
||||
await this.fetchTopics();
|
||||
await this.fetchTodayCount();
|
||||
} catch (error) { this.$message.error(`发布失败: ${error.message}`); }
|
||||
finally { this.publishing = false; }
|
||||
},
|
||||
openSlop(topic) {
|
||||
window.open('ai-slop.html?topic_id=' + encodeURIComponent(topic.id), '_blank');
|
||||
},
|
||||
async markPublished(topic) {
|
||||
try {
|
||||
await this.$confirm('确认已在各平台完成发布?此操作将把选题标记为「已发布」并关闭发布记录。', '标记发布', { type: 'warning' });
|
||||
await this.api('/api/publishing/' + encodeURIComponent(topic.id) + '/mark-published', { method: 'POST' });
|
||||
this.$message.success('已标记为已发布');
|
||||
await this.fetchTopics();
|
||||
} catch (e) {
|
||||
if (e === 'cancel') return;
|
||||
const msg = e?.response?.data?.detail || e?.message || '未知错误';
|
||||
this.$message.error('操作失败: ' + msg);
|
||||
}
|
||||
},
|
||||
async deleteTopic(id) {
|
||||
try {
|
||||
await this.$confirm('确定删除选题 ' + id + '?将同时删除相关文章、发布记录等关联数据。', '提示', { type: 'warning' });
|
||||
@@ -767,8 +831,8 @@ const TopicsApp = {
|
||||
this.currentPage = 1;
|
||||
},
|
||||
handleLogout() { localStorage.removeItem('authToken'); localStorage.removeItem('userRole'); localStorage.removeItem('currentUser'); window.location.href = '/login.html'; },
|
||||
redirectToPage(page) { window.location.href = page.startsWith('/') ? page : '/' + page; },
|
||||
getStatusLabel(status) { return { 'pending': '待处理', 'review': '待审查', 'ready': '待发布', 'published': '已发布' }[status] || status; },
|
||||
redirectToPage(page) { const url = page.startsWith('/') ? page : '/' + page; if (this.isEmbed) window.top.location.href = url; else window.location.href = url; },
|
||||
getStatusLabel(status) { return { 'pending': '待处理', 'review': '待审查', 'ready': '待发布', 'published': '已发布', 'pending_publish': '待人工发布' }[status] || status; },
|
||||
toggleCheck(id) {
|
||||
const idx = this.selectedTopicIds.indexOf(id);
|
||||
if (idx >= 0) {
|
||||
@@ -796,12 +860,13 @@ const TopicsApp = {
|
||||
try { return new Date(dateStr.replace(' ', 'T')).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); }
|
||||
catch (e) { return dateStr; }
|
||||
},
|
||||
getStatusType(status) { return { 'pending': 'warning', 'review': 'danger', 'ready': 'success', 'published': 'info' }[status] || 'primary'; },
|
||||
isStatus(row, status) { const map = { 'pending': ['pending','待处理'], 'review': ['review','待审查'], 'ready': ['ready','待发布'], 'published': ['published','已发布'] }; return map[status] ? map[status].includes(row.status) : row.status === status; }
|
||||
getStatusType(status) { return { 'pending': 'warning', 'review': 'danger', 'ready': 'success', 'published': 'info', 'pending_publish': 'warning' }[status] || 'primary'; },
|
||||
isStatus(row, status) { const map = { 'pending': ['pending','待处理'], 'review': ['review','待审查'], 'ready': ['ready','待发布'], 'published': ['published','已发布'], 'pending_publish': ['pending_publish','待人工发布'] }; return map[status] ? map[status].includes(row.status) : row.status === status; }
|
||||
},
|
||||
mounted() {
|
||||
const token = localStorage.getItem('authToken');
|
||||
if (!token) { window.location.href = '/login.html'; return; }
|
||||
if (new URLSearchParams(window.location.search).get('embed') === '1') { this.isEmbed = true; document.body.classList.add('embed'); }
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const urlFilter = params.get('filter');
|
||||
if (urlFilter) this.filterStatus = urlFilter;
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
.uni-nav-dropdown-divider {\
|
||||
height: 1px; background: #f0f0f0; margin: 4px 0;\
|
||||
}\
|
||||
@media (max-width: 768px) {\
|
||||
@media (max-width: 1000px) {\
|
||||
.uni-nav-items { display: none; }\
|
||||
.uni-nav-username { display: none; }\
|
||||
.uni-nav-hamburger { display: inline-flex; }\
|
||||
@@ -121,20 +121,24 @@
|
||||
|
||||
function getCurrentPage() {
|
||||
var path = window.location.pathname;
|
||||
if (path === '/index.html') return 'workspace';
|
||||
var m = path.match(/\/(\w+)\.html/);
|
||||
return m ? m[1] : 'workspace';
|
||||
if (path === '/' || path === '/index.html') return 'index.html';
|
||||
var m = path.match(/\/([\w-]+)\.html$/);
|
||||
return m ? m[1] + '.html' : 'index.html';
|
||||
}
|
||||
|
||||
function navItems(isAdmin) {
|
||||
var items = [
|
||||
{ key: 'workspace', label: '工作台', page: 'index.html' },
|
||||
{ key: 'factory', label: '内容工厂', page: 'factory.html' },
|
||||
{ key: 'insights', label: '数据洞察', page: 'insights.html' },
|
||||
{ key: 'assets', label: '资产库', page: 'assets.html' },
|
||||
{ key: 'dashboard', label: '仪表盘', page: 'index.html' },
|
||||
{ key: 'studio', label: '创作工作台', page: 'studio.html' },
|
||||
{ key: 'insights', label: '数据', page: 'metrics.html' },
|
||||
{ key: 'calendar', label: '日历', page: 'calendar.html' },
|
||||
{ key: 'assets', label: '素材', page: 'assets.html' },
|
||||
{ key: 'tasks', label: '任务', page: 'tasks.html' },
|
||||
{ key: 'campaigns', label: '外推营销', page: 'campaigns.html' },
|
||||
{ key: 'ai-slop', label: 'AI味检测', page: 'ai-slop.html' },
|
||||
];
|
||||
if (isAdmin) {
|
||||
items.push({ key: 'branding', label: '白标设置', page: 'branding.html', admin: true });
|
||||
items.push({ key: 'admin', label: '系统管理', page: 'admin.html', admin: true });
|
||||
}
|
||||
return items;
|
||||
@@ -160,6 +164,9 @@
|
||||
pwdForm: { old_password: '', new_password: '', confirm: '' },
|
||||
pwdSubmitting: false,
|
||||
serverMenus: null,
|
||||
brandName: '',
|
||||
brandLogo: '',
|
||||
primaryColor: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -167,15 +174,19 @@
|
||||
if (this.serverMenus) {
|
||||
return this.serverMenus
|
||||
.filter(function (m) { return m.is_active !== false; })
|
||||
.map(function (m) { return { key: m.name, label: m.name, page: m.path }; });
|
||||
.map(function (m) { return { key: m.name, label: m.name, page: (m.path || '').replace(/^\//, '') }; });
|
||||
}
|
||||
return navItems(this.isAdmin);
|
||||
},
|
||||
page: function () {
|
||||
return this.currentPage || getCurrentPage();
|
||||
return getCurrentPage();
|
||||
},
|
||||
showTitle: function () {
|
||||
return this.title || '宇之然';
|
||||
return this.brandName || this.title || '宇之然';
|
||||
},
|
||||
navStyle: function () {
|
||||
if (!this.primaryColor) return {};
|
||||
return { background: 'linear-gradient(135deg, ' + this.primaryColor + ', ' + this.primaryColor + 'cc)' };
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@@ -189,6 +200,24 @@
|
||||
}.bind(this))
|
||||
.catch(function () {});
|
||||
},
|
||||
fetchBranding: function () {
|
||||
fetch('/api/branding')
|
||||
.then(function (r) { return r.ok ? r.json() : Promise.reject(); })
|
||||
.then(function (d) {
|
||||
if (d.brand_name) this.brandName = d.brand_name;
|
||||
if (d.brand_logo) this.brandLogo = d.brand_logo;
|
||||
if (d.brand_primary_color) {
|
||||
this.primaryColor = d.brand_primary_color;
|
||||
document.documentElement.style.setProperty('--color-primary', d.brand_primary_color);
|
||||
var s = document.getElementById('uni-nav-brand-style');
|
||||
if (!s) { s = document.createElement('style'); s.id = 'uni-nav-brand-style'; document.head.appendChild(s); }
|
||||
s.textContent = '.uni-nav-dropdown-item:hover,.uni-nav-dropdown-item.active{color:' + d.brand_primary_color + ';}' +
|
||||
'.uni-nav-dropdown-item.active{background:' + d.brand_primary_color + '14;}' +
|
||||
'.uni-nav-item.active{background:rgba(255,255,255,0.18);}';
|
||||
}
|
||||
}.bind(this))
|
||||
.catch(function () {});
|
||||
},
|
||||
navigate: function (page) {
|
||||
this.dropdownOpen = false; this.profileOpen = false;
|
||||
if (this.onNavigate) { this.onNavigate(page); return; }
|
||||
@@ -246,17 +275,21 @@
|
||||
mounted: function () {
|
||||
document.addEventListener('click', this.closeAll);
|
||||
this.fetchMenus();
|
||||
this.fetchBranding();
|
||||
},
|
||||
beforeUnmount: function () {
|
||||
document.removeEventListener('click', this.closeAll);
|
||||
},
|
||||
template: '\
|
||||
<nav class="uni-nav" style="position:relative;">\
|
||||
<nav class="uni-nav" :style="navStyle">\
|
||||
<div class="uni-nav-inner">\
|
||||
<div class="uni-nav-brand">{{ showTitle }}</div>\
|
||||
<div class="uni-nav-brand">\
|
||||
<img v-if="brandLogo" :src="brandLogo" style="height:24px;width:auto;margin-right:6px;vertical-align:middle;border-radius:4px;" />\
|
||||
<span>{{ showTitle }}</span>\
|
||||
</div>\
|
||||
<div class="uni-nav-items">\
|
||||
<button v-for="item in items" :key="item.key"\
|
||||
:class="[\'uni-nav-item\', { active: page === item.key }]"\
|
||||
:class="['uni-nav-item', { active: page === item.page }]"\
|
||||
@click="navigate(item.page)">{{ item.label }}</button>\
|
||||
</div>\
|
||||
<div class="uni-nav-right">\
|
||||
@@ -281,7 +314,7 @@
|
||||
</div>\
|
||||
<div :class="[\'uni-nav-dropdown\', { open: dropdownOpen }]" @click.stop>\
|
||||
<button v-for="item in items" :key="item.key"\
|
||||
:class="[\'uni-nav-dropdown-item\', { active: page === item.key }]"\
|
||||
:class="[\'uni-nav-dropdown-item\', { active: page === item.page }]"\
|
||||
@click="navigate(item.page)">\
|
||||
{{ item.label }}\
|
||||
</button>\
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
# 宇之然 (YuZhiRan) 市场与竞争调研简报 — 2026
|
||||
|
||||
> 调研范围:中国 AI 内容创作工具格局、平台算法/政策、GEO/AI搜索、创作者变现痛点、未满足蓝海
|
||||
> 方法:web_search 深度检索(2025–2026 公开报道、平台公告、行业白皮书、创作者社区帖)
|
||||
> 注意:部分数据为行业机构/营销机构口径(GEO 服务商软文偏多),已在文中标注"存疑"。
|
||||
|
||||
---
|
||||
|
||||
## 一、关键趋势 (Key Trends)
|
||||
|
||||
1. **"AI 辅助"被鼓励,"AI 托管/批量"被严打** — 2026 年所有主流平台完成从"自愿声明"到"强制标识 + 全链路管控"的切换。9 月 1 日《人工智能生成合成内容标识办法》实施后,小红书、微信、抖音、快手、B站、视频号均上线 AI 内容主动声明/强制打标。微信 4 月新版《公众账号行为规范》明确:禁止用 AI/脚本/接口替代真人全流程运营,违者限流/删文/封号。
|
||||
2. **"人味/原创增量"成为新的流量分配核心指标** — 平台不再简单打击"AI 率",而是打击"无人工痕迹+同质化+低价值增量"的内容。抖音把"原创识别率"纳入核心分发指标;重复率超 25% 即判低质。
|
||||
3. **GEO(生成式引擎优化)从概念走向服务市场** — 豆包/文心/通义/DeepSeek/Kimi 已成决策入口。出现大量 GEO 服务商(Bugoo 布谷、余小铁、上海逐天、GEORANK 等),但行业鱼龙混杂、软文泛滥。
|
||||
4. **一人公司 / Agent 化个体创业爆发** — 2026 被视为"一人公司狂飙"年,AI Agent 工作流(Dify/Coze/n8n/OpenClaw)使个体具备团队能力。
|
||||
5. **"副业刚需"成为内容创作的核心驱动力** — 全球年轻人"多职劳动(Multiple Jobs)"激增,中国 26-35 岁焦虑青年把自媒体当"退路"与"睡后收入"来源。
|
||||
6. **长文→短视频的转化工具成熟但割裂** — 即梦/可灵/剪映/即创打通"脚本→分镜→成片",但多为独立工具,少有"一篇文章自动拆成三平台+一个短视频"的一体化方案。
|
||||
7. **本地生活 + 私域成为小红书/视频号新红利** — 小红书变"内容+交易"闭环,搜索流量(DSO)是低成本获客洼地,强调真实、就近、素人。
|
||||
|
||||
---
|
||||
|
||||
## 二、创作者痛点 (Top Pain Points, Ranked)
|
||||
|
||||
> 按"普遍度 × 严重程度 × 与宇之然定位的相关度"排序
|
||||
|
||||
1. **【最痛】AI 内容被限流/封号,且规则不清** — "写了 3 个月原创阅读不过千""AI 率 50%+ 被限流""小红书关于 AI 限流的笔记超 3 万篇"。平台边界模糊(哪些是 AI 托管、哪些算辅助),创作者"不知道红线在哪"。→ 宇之然的"合规检测/去套话/人味优化 + 三平台差异化"正好命中。
|
||||
2. **同质化与"AI 套话"** — 同模型+同 prompt 产出高度雷同;平台同质化识别已迭代数轮。"同质化比 AI 检测更要命"。常见 AI 句式("首先/其次/最后""值得一提的是")被普遍反感。
|
||||
3. **多平台分发但要"说人话且各说各话"** — 知乎/微信/小红书调性、字数、人称完全不同,手动改三遍成本极高。绝大多数工具只给"一篇通用稿",或只做简单改写。→ 宇之然"三平台独立展开"是差异化卖点。
|
||||
4. **选题焦虑 + 流量焦虑** — 红海赛道拥挤、冷启动难、爆款不可复制。需要"有数据价值但竞争者少的细分方向"。
|
||||
5. **变现难 + 账号权重脆弱** — 流量主/广告/知识付费门槛高;一个违规号可能前功尽弃。
|
||||
6. **合规/敏感词/版权风险** — 《标识办法》+ 平台敏感词 + 版权模糊(AI 生成图/声音侵权案例频出,如配音演员维权)。
|
||||
7. **长文→短视频的转化断层** — 公众号/知乎沉淀的长文,想转抖音/视频号缺少顺滑工作流;现有工具(即梦/剪映)需手动搬运。
|
||||
8. **GEO/SEO 可见性失控** — 品牌/个人希望被豆包/DeepSeek 引用,但不知道"怎么喂、喂给谁、是否被引",缺乏可监测手段。
|
||||
|
||||
---
|
||||
|
||||
## 三、竞品动向 (Competitor Moves)
|
||||
|
||||
### AI 写作/创作工具(通用层,已饱和)
|
||||
- **豆包(字节)**:月活 5.3 亿(2026.3 披露),日均 112 亿次对话;完全免费、多模态、生态闭环(抖音/头条/即梦)。2026 下半年可能启动付费。
|
||||
- **DeepSeek**:月活 1.27 亿,中文写作性价比之王,开源,被引用逻辑偏"推理+技术链"。
|
||||
- **Kimi(月之暗面)**:超长上下文(200 万字),长文档/资料整理最强;UGC/知乎信源占比高(约 70% 来自 UGC)。
|
||||
- **通义千问(阿里)**:商务/文档/电商,引用偏权威媒体+阿里生态。
|
||||
- **文心一言/文小言(百度)**:搜索基因,引用偏百度系(百家号/百科)+ 媒体软文。
|
||||
- **秘塔写作猫**:中文纠错/润色/降重老牌,千万级用户,免费策略。
|
||||
- **笔灵 AI**:模板化营销文案,批量标准化。
|
||||
- **腾讯元宝**:微信生态闭环,公众号文章占信源约 50%。
|
||||
|
||||
### AI 视频/多模态(增长快,但非宇之然核心)
|
||||
- 即梦(字节, Seedance 2.0)、可灵(快手)、LibTV、万镜一刻、剪映一键成片、即创(抖音带货)、腾讯智影、Runway/Sora(海外)。
|
||||
- 趋势:文生视频从"玩具"变"生产力",但排队/成本/真人肖像限制仍痛。
|
||||
|
||||
### Agent/工作流平台(宇之然的间接竞品)
|
||||
- **扣子 Coze(字节)**:运营人用得最多,抖音/小红书爆款生成工作流,Coze+DeepSeek/飞书联动。
|
||||
- **Dify / n8n / OpenClaw**:个体创业者搭自动化内容生产线("一行指令出稿出图推送")。
|
||||
|
||||
### GEO 服务商(新兴市场,混乱)
|
||||
- Bugoo 布谷、余小铁 GEO、上海逐天科技、GEORANK、熊猫出海 GEO、白杨 SEO 等。
|
||||
- 打法:媒体发稿 + 自媒体宣发 + 信源投喂 + 引用率监测。报价中高,交付质量参差。
|
||||
- **洞察**:GEO 目前高度"服务化/人力化",尚无标准化 SaaS;这正是宇之然可切入的"外部站 SEO/GEO"模块机会。
|
||||
|
||||
### 平台自身(既是合作方也是裁判)
|
||||
- 小红书"点点"AI、微信元宝、抖音即梦——平台把 AI 工具内嵌进生态,目的是"把创作者绑在自己闭环里",同时严打跨平台批量搬运。
|
||||
|
||||
---
|
||||
|
||||
## 四、平台算法/政策 2025–2026 变化(要点)
|
||||
|
||||
| 平台 | 关键变化 | 对创作者的含义 |
|
||||
|---|---|---|
|
||||
| 全网 | 《AI生成合成内容标识办法》9/1 实施;显式+隐式标识,不得删除/篡改 | AI 内容必须可识别;标注≠免罚 |
|
||||
| 小红书 | 强制 AI 标识(2025.8);封禁纯 AI 托管账号;"2万篇笔记种草公式"失效;转向"以人为本/超级小内容" | 批量素人宣发死亡;要真实、小需求、场景化 |
|
||||
| 微信/公众号 | 4 月新规:禁 AI 全流程替代真人;限流/删文/封号 | AI 只能辅助选题/润色/查错,真人表达不可替代 |
|
||||
| 抖音/快手 | "原创识别率"入核心指标;重复率>25% 判低质;禁批量同质化 | 原创增量是分发关键 |
|
||||
| 知乎 | "知乎直答"存在感弱;Kimi/DeepSeek 大量引用知乎内容 | 知乎长文对 GEO 权重高(被 Kimi 引用 70%) |
|
||||
| 视频号 | 严打非真人出镜 AI 视频;限推荐/私信/评论 | 虚构真人/假图高风险 |
|
||||
| 网文(晋江/起点/番茄) | 反 AI:起点/番茄禁 AI,晋江限边界;部分甲方拉黑用 AI 大纲者 | 虚构创作领域对 AI 最不友好 |
|
||||
|
||||
**"原创"的新定义**:不再是"查重率<X%",而是"有独特观点/可验证数据/真实案例/价值增量"。单纯换同义词、调语序仍被判低价值。平台建议 AI 辅助占比 ≤25–30%,每百字至少 1 个情绪钩子。
|
||||
|
||||
---
|
||||
|
||||
## 五、GEO / AI 搜索优化 2026
|
||||
|
||||
- **已成真,但是 B 端(企业)先行,C 端(个人创作者)滞后**。企业级 GEO 需求明确("不被 AI 引用=在 AI 世界失语"),服务商众多;个人创作者大多还停留在"SEO/发小红书"阶段。
|
||||
- **中文术语认知**:GEO(生成式引擎优化)一词已被采用,但大众仍混用"AI搜索优化/AISEO"。 awareness 在营销圈高,在普通自媒体人里中等。
|
||||
- **是否可 monetize**:对企业已是刚需(获客/品牌);对个人创作者尚处"锦上添花",但"被豆包/DeepSeek 提到"正成为个人 IP 的新护城河。
|
||||
- **各平台引用逻辑差异(关键,决定 GEO 打法)**:
|
||||
- 豆包:字节生态优先(抖音/头条/抖音百科),外部补新闻媒;重生活化。
|
||||
- DeepSeek:媒体+UGC(知乎类)为主,重推理/技术链。
|
||||
- Kimi:~70% UGC,知乎占比极高 → **知乎长文对 GEO 权重被低估**。
|
||||
- 文心/百度:百度系(百家号/百科)+媒体软文。
|
||||
- 元宝/微信:公众号文章占 ~50% 信源。
|
||||
- 通义:新闻门户(网易/腾讯/搜狐)+阿里生态。
|
||||
- **实操共识**:结构化内容、跨平台信息一致、原创数据/白皮书、权威媒体分发、持续"投喂"。
|
||||
- **数据存疑**:GEO 服务商宣称"2026 Q1 中国 GEO 市场 480 亿、同比 11 倍""超 75% 用户决策首选 AI"等数字来自 IDC/艾瑞转引的软文,**未独立核实,谨慎引用**。
|
||||
|
||||
---
|
||||
|
||||
## 六、变现痛点 & 焦虑青年(26-35)画像
|
||||
|
||||
- **副业成刚需**:全球"多职劳动"激增(美国 5.3% 劳动力身兼数职,连续 21 个月>5%;Z 世代 70% 想保持≥2 收入源)。国内因 AI 失业焦虑+生活成本,副业从"兴趣"变"生存手段"。
|
||||
- **26-35 岁核心恐惧**:35 岁职场危机、被 AI 替代、房贷/教育/医疗刚性支出、失业即断流。
|
||||
- **他们要的内容**:① 可落地的副业/一人公司方法论;② AI 工具实操("用 AI 当外挂不被淘汰");③ 个人 IP 从 0 到 1;④ 健康/抗倦怠/数字排毒("职业倦怠高峰提前到 25 岁");⑤ 数字游民/地理套利;⑥ 可持续生活/智性消费(B站《2026 年轻人消费趋势报告》:"智性沸腾",不为标签为自我价值付费)。
|
||||
- **内容调性偏好**:情绪价值、真实体验、具体数据、反套路、去 AI 味——与宇之然"三平台差异化+去套话"高度契合。
|
||||
|
||||
---
|
||||
|
||||
## 七、未满足的蓝海 (Underserved Niches)
|
||||
|
||||
> 综合"需求明确 × 现有 AI 工具少 × 与宇之然能力可衔接"
|
||||
|
||||
1. **【最强】三平台"真·差异化"自动生成** — 绝大多数工具只给通用稿或简单改写;能按知乎(数据分析/你)、微信(个人叙事/我)、小红书(干货/emoji)各自独立成稿且合规检测的产品稀缺。宇之然已具备,是核心壁垒。
|
||||
2. **【强】GEO 化的内容生产(个人/中小品牌版)** — 企业 GEO 被服务商瓜分,但"帮个人创作者/小店产出能被豆包/DeepSeek 引用的结构化内容"尚无 SaaS。可把"外部站 SEO/GEO"做成标准模块。
|
||||
3. **【强】长文→短视频脚本自动拆条** — 公众号/知乎长文一键生成抖音/视频号口播脚本+分镜+标题,目前需手动跨即梦/剪映。一体化缺口明显。
|
||||
4. **本地生活商家内容** — 小红书/视频号"搜索流量+POI+素人"红利大,但中小门店缺内容生产能力;模板化"真实到店/本地避坑/套餐推荐"批量生成工具少。
|
||||
5. **企业号/员工 KOS 内容** — "门店号+员工号双号运营"需求上升,但企业级多账号合规内容工厂稀缺。
|
||||
6. **知识付费 IP 的内容中台** — 从选题→大纲→三平台→私域钩子→课程化,缺一站式。
|
||||
7. **私域转化内容** — 公域内容如何带私域钩子(企微/社群话术),现有工具基本不碰。
|
||||
8. **出海/跨境内容** — 小红书 2026 推跨境电商;英文/多语种内容生成+本地化是空白,且宇之然现有架构易扩展。
|
||||
9. **合规/去标识安全层** — 随着《标识办法》落地,"发布前 AI 率+同质化+敏感词+暗限流预测"的检测与改写,会成为所有 AI 写作工具的标配前置环节(ContentAny 类已起量,但嵌入生成流的少)。
|
||||
|
||||
---
|
||||
|
||||
## 八、对宇之然的策略含义(一句话)
|
||||
|
||||
宇之然的"三平台差异化生成 + 去套话/合规检测 + 外部站 GEO + 长文转短视频"组合,正好卡在 2026 年最痛的三条裂缝上:**限流焦虑、同质化、跨平台+跨形态分发成本**。优先把"三平台真差异化+合规护城河"做成可感知的差异化卖点,再叠加 GEO 与短视频拆条作为第二增长曲线。
|
||||
|
||||
---
|
||||
|
||||
## 九、信息来源 (Sources)
|
||||
|
||||
- 互联网平台现状:鼓励AI,限制AI(36氪/新莓)https://m.36kr.com/p/3259734457430277
|
||||
- 小红书上线 AI 内容强制标识(网易/见实)https://www.163.com/dy/article/K86VAJIQ053144S4.html
|
||||
- 从 AI 监管到内容生态重构:平台重新定价"真实"(36氪/DoNews)https://m.36kr.com/p/3765102752252424
|
||||
- 警惕"AI托管"侵蚀内容生态(中国经济网/证券日报)http://m.ce.cn/bwzg/202603/t20260316_2829584.shtml
|
||||
- 2026 AI 写作工具实测(AI-Pick)https://ai-pick.cn/article-42.html
|
||||
- 2026 AI 写作工具推荐 10 款(AI工具宝箱)https://www.aitoollab.cn/articles/ai-writing-tools-2026-guide
|
||||
- Kimi、文心启动付费(澎湃)https://www.thepaper.cn/newsDetail_forward_33101613
|
||||
- 2026 一人公司狂飙:AI Agent 重构个体创业(掘金)https://juejin.cn/post/7611761768110817332
|
||||
- GEO:2026 ChatGPT/DeepSeek/豆包等 AI 搜索优化(CSDN/DeepSeek 社区)https://deepseek.csdn.net/6a420c80662f9a54cb858452.html
|
||||
- AI 搜索推荐机制拆解:豆包/通义/文心/DeepSeek 引用逻辑(CSDN)https://blog.csdn.net/yunFAA667/article/details/159512346
|
||||
- 豆包 AI 搜索优化 GEO 指南(cnblogs/GEORANK)https://www.cnblogs.com/georank/p/20680805
|
||||
- 上海 GEO 服务商评测(cnblogs)https://www.cnblogs.com/zwcxkj/articles/20039051
|
||||
- DeepSeek/豆包/Kimi/通义 引用来源占比(白杨 SEO/淘江湖)https://jianghu.taobao.com/detail/47301_29034882
|
||||
- 自媒体变现指南(U客直谈)https://www.ukezhitan.com/article-u8D5Au94B1u8D44u8BAF/12316
|
||||
- 2026 小红书本地生活商家趋势(cnblogs)https://www.cnblogs.com/nbxc2021/p/19688862
|
||||
- 一文讲透小红书 2026 平台趋势(数英)https://www.digitaling.com/articles/1443427.html
|
||||
- B站 2026 年轻人消费趋势报告"智性沸腾" https://www.fxbaogao.com/detail/5235669
|
||||
- 全球年轻人热衷于搞副业(新浪/新京报)https://finance.sina.cn/tech/2026-06-19/detail-inicxkay2951341.d.html
|
||||
- 35 岁职场危机(新浪财经)https://cj.sina.com.cn/articles/view/7879848901/1d5acf3c501901iun6
|
||||
- 即梦/可灵/剪映 AI 视频工具对比(老达 AI 博客)https://laodad.com/tools/6334.html
|
||||
- 2026 国内外 19 款 AI 视频生成工具(知乎)https://zhuanlan.zhihu.com/p/684932281
|
||||
- AI 写小红书/公众号为什么限流(阿里云开发者社区)https://developer.aliyun.com/article/1724689
|
||||
- 公众号 AI 写作规则与生存指南(简书)https://www.jianshu.com/p/5e899e17bbd3
|
||||
- 知乎写手避开 AI 检测(CSDN)https://blog.csdn.net/2611_96189011/article/details/161697476
|
||||
|
||||
> 说明:GEO 市场规模/引用率等具体数字多出自 GEO 服务商软文,行业口径不一,建议内部使用时标注"行业预估,未独立核实"。2026 年部分平台月活/政策细节来自媒体报道,可能存在口径差异。
|
||||
+28
-23
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
内容采集:趋势抓取 → 选题生成 → 存入选题库
|
||||
收集热点趋势信息,经LLM分析后生成选题建议并存入数据库
|
||||
内容采集管道:趋势抓取 → 分析 → 选题生成 → 存入选题库
|
||||
聚焦AI与科技人文领域,从RSS搜索和Web搜索采集信息,经LLM分析后生成选题
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -42,14 +42,11 @@ logging.basicConfig(
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
DEFAULT_CHINA_PAINS = {
|
||||
"循环消费": "以旧换新流程繁琐、二手商品信任缺失、租赁市场不规范",
|
||||
"低碳出行": "新能源车充电设施不足、城市规划不支持骑行、通勤距离长",
|
||||
"干净饮食": "有机食品价格高、真伪难辨、外卖为主的生活方式难以改变",
|
||||
"零浪费生活": "环保产品溢价高、可持续选择不便、漂绿营销难以分辨",
|
||||
"绿色家电与节能": "绿色家电初期投入高、节能效果难量化、老旧小区改造难",
|
||||
"碳普惠": "碳账户普及率低、减排量兑换吸引力不足、公众认知有限",
|
||||
"环保科技产品": "绿色产品溢价68%难以承受、缺乏统一认证标准、担心漂绿",
|
||||
"AI与效率": "AI工具选择困难、数据隐私担忧、学习成本高、实际效果难验证"
|
||||
"AI工具实测": "AI工具选择困难、免费版功能太受限、ChatGPT不如国产好用、学习曲线陡峭、不知道哪些AI工具真正值得付费、看了测评还是不知道怎么用",
|
||||
"AI前沿资讯": "AI更新太快跟不上、论文太多看不完、不知道哪些是真突破、被营销号带偏、每天刷AI新闻但感觉什么都没学到",
|
||||
"未来工作方式": "AI裁员焦虑加剧、远程协作效率打折、职业方向看不清、技能更新压力大、35岁学AI来得及吗、转行AI需要什么技能",
|
||||
"科技人文反思": "AI伦理边界模糊、数据隐私被侵蚀、数字依赖越来越重、人机关系困惑、孩子该不该用AI写作业、AI生成的内容能信吗",
|
||||
"AI职场应用": "AI替代担心、岗位技能要求变了、职场竞争加剧、缺乏AI实操指导、怎么用AI写周报、怎么用AI做数据分析、AI提示词怎么写才有效",
|
||||
}
|
||||
|
||||
_cached_china_pains = None
|
||||
@@ -87,22 +84,22 @@ def _get_china_pain(category: str) -> str:
|
||||
|
||||
@dataclass
|
||||
class SustainabilitySource:
|
||||
"""可持续性信息源"""
|
||||
"""内容采集源"""
|
||||
name: str
|
||||
type: str # rss, web_search, web, api, local
|
||||
url: Optional[str] = None # RSS URL 或通用链接
|
||||
update_frequency: str = "daily"
|
||||
credibility: str = "medium"
|
||||
focus: str = "可持续性"
|
||||
focus: str = "AI与科技"
|
||||
keywords: Optional[List[str]] = None # 源特定关键词
|
||||
query: Optional[str] = None # 搜索查询词(w eb_search类型用)
|
||||
|
||||
@dataclass
|
||||
class SustainabilityCase:
|
||||
"""可持续性案例"""
|
||||
"""内容案例"""
|
||||
id: str
|
||||
country: str
|
||||
category: str # 子领域:零浪费生活、循环消费等
|
||||
category: str # 类别:AI前沿资讯、AI工具实测等
|
||||
title: str
|
||||
core_idea: str
|
||||
data_facts: str
|
||||
@@ -118,7 +115,7 @@ class SustainabilityCase:
|
||||
|
||||
@dataclass
|
||||
class SustainabilityTopic:
|
||||
"""可持续性选题"""
|
||||
"""AI与科技选题"""
|
||||
id: str
|
||||
title: str
|
||||
cases: List[str] # 关联的案例ID列表
|
||||
@@ -128,7 +125,7 @@ class SustainabilityTopic:
|
||||
mvp_actions: str
|
||||
estimated_length: int
|
||||
priority_score: float
|
||||
field: str = "可持续生活系统" # 内容领域
|
||||
field: str = "AI与效率" # 内容领域
|
||||
format: str = "趋势洞察 + 实操指南" # 内容形式
|
||||
core_concept: str = "" # 核心理念
|
||||
audience_pain: str = "" # 受众痛点
|
||||
@@ -150,7 +147,7 @@ class SustainabilityTopic:
|
||||
self.platform_urls = {}
|
||||
|
||||
class SustainabilityCollector:
|
||||
"""可持续性内容收集器"""
|
||||
"""AI与科技内容采集器"""
|
||||
|
||||
def __init__(self):
|
||||
self.load_config()
|
||||
@@ -191,7 +188,7 @@ class SustainabilityCollector:
|
||||
url=s.url or '',
|
||||
query=s.query or '',
|
||||
credibility=s.credibility or 'medium',
|
||||
focus=s.focus or '可持续性',
|
||||
focus=s.focus or 'AI与科技',
|
||||
))
|
||||
logger.info(f"从DB加载 {len(cats)} 个类别, {len(self.sources)} 个信息源")
|
||||
db.close()
|
||||
@@ -209,7 +206,7 @@ class SustainabilityCollector:
|
||||
if 'base_url' in source_info and 'url' not in source_info:
|
||||
source_info['url'] = source_info.pop('base_url')
|
||||
source_info.setdefault('update_frequency', 'daily')
|
||||
source_info.setdefault('focus', '可持续性')
|
||||
source_info.setdefault('focus', 'AI与科技')
|
||||
source_info.setdefault('keywords', None)
|
||||
allowed_keys = {'name', 'type', 'url', 'update_frequency', 'credibility', 'focus', 'keywords', 'query'}
|
||||
filtered_info = {k: v for k, v in source_info.items() if k in allowed_keys}
|
||||
@@ -379,7 +376,7 @@ class SustainabilityCollector:
|
||||
'source_name': source.name
|
||||
})
|
||||
|
||||
logger.info(f"从 {source.name} 获取到 {len(articles)} 篇可持续性文章")
|
||||
logger.info(f"从 {source.name} 获取到 {len(articles)} 篇文章")
|
||||
return articles
|
||||
|
||||
except Exception as e:
|
||||
@@ -720,8 +717,16 @@ class SustainabilityCollector:
|
||||
return topic
|
||||
|
||||
def map_category_to_field(self, category: str) -> str:
|
||||
"""将案例类别映射到内容领域的字段"""
|
||||
return "可持续生活系统"
|
||||
"""将采集类别映射到内容领域"""
|
||||
mapping = {
|
||||
"AI前沿资讯": "AI与效率",
|
||||
"AI工具实测": "AI与效率",
|
||||
"AI趋势分析": "AI与效率",
|
||||
"AI职场应用": "未来工作方式",
|
||||
"科技人文反思": "科技人文",
|
||||
"未来工作方式": "未来工作方式",
|
||||
}
|
||||
return mapping.get(category, "AI与效率")
|
||||
|
||||
def save_results(self):
|
||||
"""保存收集结果"""
|
||||
@@ -851,7 +856,7 @@ class SustainabilityCollector:
|
||||
|
||||
def run(self):
|
||||
"""主运行流程"""
|
||||
logger.info("开始可持续性内容收集")
|
||||
logger.info("开始AI与科技内容收集")
|
||||
|
||||
existing_titles = self._get_existing_titles()
|
||||
|
||||
|
||||
@@ -246,12 +246,14 @@ def save_article(topic_id: str, platform: str, html_content: str, *, title: str
|
||||
from app.models import Article
|
||||
article_id = f"{platform}_{topic_id}"
|
||||
existing = db.query(Article).filter(Article.id == article_id).first()
|
||||
word_count = len(content) if content else (len(html_content) if html_content else 0)
|
||||
if existing:
|
||||
existing.html_content = html_content
|
||||
if title:
|
||||
existing.title = title
|
||||
if content:
|
||||
existing.content = content
|
||||
existing.word_count = word_count
|
||||
else:
|
||||
article = Article(
|
||||
id=article_id,
|
||||
@@ -261,6 +263,7 @@ def save_article(topic_id: str, platform: str, html_content: str, *, title: str
|
||||
title=title,
|
||||
content=content,
|
||||
html_content=html_content,
|
||||
word_count=word_count,
|
||||
status="draft",
|
||||
compliance_score=None
|
||||
)
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
内容聚焦迁移脚本(Phase 2)
|
||||
目标:将数据库从"6领域分散"迁移到"3领域聚焦(AI与效率、科技人文、未来工作方式)"
|
||||
|
||||
执行:python3 scripts/migrate_content_focus.py
|
||||
"""
|
||||
|
||||
import sys, json, logging, datetime
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
sys.path.insert(0, str(PROJECT_ROOT / "platform" / "backend"))
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 新领域配置(与 initial_data.py 对齐)
|
||||
NEW_FIELDS = [
|
||||
{"name": "AI与效率", "icon": "🤖", "color": "#764ba2", "description": "AI工具、AI工作流、效率方法、前沿资讯", "sort_order": 1},
|
||||
{"name": "科技人文", "icon": "🔬", "color": "#f56c6c", "description": "AI伦理、科技反思、数字生活、人机关系", "sort_order": 2},
|
||||
{"name": "未来工作方式", "icon": "💼", "color": "#667eea", "description": "远程工作、AI时代职业转型、一人企业", "sort_order": 3},
|
||||
]
|
||||
|
||||
# 旧领域名称 → 新领域名称映射
|
||||
FIELD_MIGRATION_MAP = {
|
||||
"可持续生活系统": "AI与效率",
|
||||
"可持续生活": "AI与效率",
|
||||
"数字游民": "未来工作方式",
|
||||
"个人成长": "科技人文",
|
||||
"个人知识工厂": "AI与效率",
|
||||
"科技人文交叉": "科技人文",
|
||||
}
|
||||
|
||||
# 新采集分类
|
||||
NEW_CATEGORIES = [
|
||||
{"name": "AI前沿资讯", "search_query": "AI 人工智能 大模型 2026 前沿", "description": "AI行业动态、大模型发布、技术突破", "sort_order": 1, "is_active": True},
|
||||
{"name": "AI工具实测", "search_query": "AI工具 效率提升 AI工作流 2026", "description": "AI工具评测、效率工作流、实操指南", "sort_order": 2, "is_active": True},
|
||||
{"name": "AI与职场", "search_query": "AI 裁员 职业转型 AI技能 远程工作 2026", "description": "AI对就业影响、职业转型、技能升级", "sort_order": 3, "is_active": True},
|
||||
{"name": "AI生活化", "search_query": "AI陪伴 AI心理咨询 AI生活助手 2026", "description": "AI心理咨询/生活搭子/AI人格化", "sort_order": 4, "is_active": True},
|
||||
{"name": "科技人文", "search_query": "AI伦理 数字生活 科技反思 人机关系 2026", "description": "AI伦理/数字生活反思/科技温度", "sort_order": 5, "is_active": True},
|
||||
]
|
||||
|
||||
# 旧分类名称(待停用)
|
||||
OLD_CATEGORY_NAMES = ["循环消费", "低碳出行", "干净饮食", "零浪费生活", "绿色家电与节能", "碳普惠", "环保科技产品"]
|
||||
|
||||
# 新平台 tags
|
||||
NEW_PLATFORM_TAGS = {
|
||||
"zhihu": ["科技", "AI", "效率", "职场", "教育", "远程工作", "未来工作", "工具"],
|
||||
"wechat": ["科技", "AI", "效率", "职场", "教育", "远程工作", "未来工作", "工具"],
|
||||
"xiaohongshu": ["AI", "效率", "科技", "工具", "职场", "生活", "学习方法"],
|
||||
}
|
||||
|
||||
|
||||
def migrate():
|
||||
try:
|
||||
from app.database import SessionLocal
|
||||
from app.models import TopicField, Topic, Article, CollectorCategory, CollectorSource, PlatformConfig
|
||||
except ImportError as e:
|
||||
logger.error(f"导入失败(确保在项目根目录执行): {e}")
|
||||
sys.exit(1)
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# ========== 1. 处理 TopicField ==========
|
||||
logger.info("--- 处理 TopicField ---")
|
||||
existing_fields = {f.name: f for f in db.query(TopicField).all()}
|
||||
field_id_map = {} # name → id
|
||||
|
||||
for nf in NEW_FIELDS:
|
||||
if nf["name"] in existing_fields:
|
||||
f = existing_fields[nf["name"]]
|
||||
f.icon = nf["icon"]
|
||||
f.color = nf["color"]
|
||||
f.description = nf["description"]
|
||||
f.sort_order = nf["sort_order"]
|
||||
f.is_active = True
|
||||
field_id_map[nf["name"]] = f.id
|
||||
logger.info(f" 更新领域: {nf['name']} (id={f.id})")
|
||||
else:
|
||||
f = TopicField(**nf)
|
||||
db.add(f)
|
||||
db.flush()
|
||||
field_id_map[nf["name"]] = f.id
|
||||
logger.info(f" 创建领域: {nf['name']} (id={f.id})")
|
||||
|
||||
# 停用旧领域
|
||||
for old_name in ["可持续生活系统", "可持续生活", "数字游民", "个人成长", "个人知识工厂"]:
|
||||
if old_name in existing_fields and old_name not in [nf["name"] for nf in NEW_FIELDS]:
|
||||
f = existing_fields[old_name]
|
||||
f.is_active = False
|
||||
logger.info(f" 停用旧领域: {old_name} (id={f.id})")
|
||||
db.commit()
|
||||
|
||||
# ========== 2. 迁移 Topic ==========
|
||||
logger.info("--- 迁移 Topic ---")
|
||||
topics = db.query(Topic).all()
|
||||
topic_count = len(topics)
|
||||
migrated_count = 0
|
||||
for t in topics:
|
||||
old_field = t.field_name or ""
|
||||
new_field = None
|
||||
for old_k, new_v in FIELD_MIGRATION_MAP.items():
|
||||
if old_k in old_field or old_field in old_k:
|
||||
new_field = new_v
|
||||
break
|
||||
if not new_field and t.field_id:
|
||||
# 按名称猜测
|
||||
for old_k, new_v in FIELD_MIGRATION_MAP.items():
|
||||
if old_k[:4] in old_field or old_field[:4] in old_k:
|
||||
new_field = new_v
|
||||
break
|
||||
if new_field and new_field in field_id_map:
|
||||
t.field_id = field_id_map[new_field]
|
||||
t.field_name = new_field
|
||||
migrated_count += 1
|
||||
elif t.field_id and t.field_name not in [nf["name"] for nf in NEW_FIELDS]:
|
||||
# 无法映射的旧领域,统一设为"AI与效率"
|
||||
if "AI与效率" in field_id_map:
|
||||
t.field_id = field_id_map["AI与效率"]
|
||||
t.field_name = "AI与效率"
|
||||
migrated_count += 1
|
||||
db.commit()
|
||||
logger.info(f" 共 {topic_count} 个 Topic,已迁移 {migrated_count} 个")
|
||||
|
||||
# ========== 3. 清理旧 Article ==========
|
||||
logger.info("--- 清理旧 Article ---")
|
||||
all_articles = db.query(Article).all()
|
||||
deleted_articles = []
|
||||
for a in all_articles:
|
||||
topic = db.query(Topic).filter(Topic.id == a.topic_id).first()
|
||||
if topic:
|
||||
# 只保留 topic 仍然活跃且在新领域内的
|
||||
tf = db.query(TopicField).filter(TopicField.id == topic.field_id).first()
|
||||
if tf and tf.is_active and tf.name in [nf["name"] for nf in NEW_FIELDS]:
|
||||
continue
|
||||
# 该 Article 关联的 topic 已失效,删除
|
||||
deleted_articles.append(a.id)
|
||||
db.delete(a)
|
||||
db.commit()
|
||||
logger.info(f" 已删除 {len(deleted_articles)} 篇旧 Article")
|
||||
|
||||
# ========== 4. 清理 CollectorCategory ==========
|
||||
logger.info("--- 清理 CollectorCategory ---")
|
||||
existing_cats = {c.name: c for c in db.query(CollectorCategory).all()}
|
||||
|
||||
# 创建新分类
|
||||
for nc in NEW_CATEGORIES:
|
||||
if nc["name"] in existing_cats:
|
||||
c = existing_cats[nc["name"]]
|
||||
c.search_query = nc["search_query"]
|
||||
c.description = nc["description"]
|
||||
c.sort_order = nc["sort_order"]
|
||||
c.is_active = True
|
||||
logger.info(f" 更新采集分类: {nc['name']}")
|
||||
else:
|
||||
db.add(CollectorCategory(**nc))
|
||||
logger.info(f" 创建采集分类: {nc['name']}")
|
||||
|
||||
# 停用旧分类
|
||||
for old_name in OLD_CATEGORY_NAMES:
|
||||
if old_name in existing_cats:
|
||||
c = existing_cats[old_name]
|
||||
c.is_active = False
|
||||
logger.info(f" 停用旧采集分类: {old_name}")
|
||||
|
||||
# 删除旧 '可持续生活' 和 '可持续发展' 变体
|
||||
for cname, cobj in existing_cats.items():
|
||||
if cname not in [nc["name"] for nc in NEW_CATEGORIES] and cname not in OLD_CATEGORY_NAMES:
|
||||
if any(kw in cname for kw in ["可持续", "环保", "绿色", "低碳", "零浪费", "循环"]):
|
||||
cobj.is_active = False
|
||||
logger.info(f" 停用旧采集分类: {cname}")
|
||||
db.commit()
|
||||
|
||||
# ========== 5. 清理 CollectorSource ==========
|
||||
logger.info("--- 清理 CollectorSource ---")
|
||||
all_sources = db.query(CollectorSource).all()
|
||||
deactivated_sources = 0
|
||||
for s in all_sources:
|
||||
# 停用非 AI/科技 类采集源
|
||||
if s.focus and any(kw in s.focus for kw in ["可持续", "环保", "绿色", "低碳", "零浪费"]):
|
||||
s.is_active = False
|
||||
deactivated_sources += 1
|
||||
# 旧 URL 不再可访问的源
|
||||
if s.url and any(domain in s.url for domain in ["greenbiz.com", "sustainablebrands.com", "treehugger.com"]):
|
||||
s.is_active = False
|
||||
deactivated_sources += 1
|
||||
# 检查 category_id 是否对应已停用的分类
|
||||
if s.category_id:
|
||||
cat = db.query(CollectorCategory).filter(CollectorCategory.id == s.category_id).first()
|
||||
if cat and not cat.is_active:
|
||||
s.is_active = False
|
||||
deactivated_sources += 1
|
||||
# 新增源(由 initial_data.py 的补充逻辑处理)
|
||||
for new_src in [
|
||||
{"name": "AI前沿搜索", "source_type": "web_search", "query": "AI 人工智能 大模型 前沿 2026", "credibility": "medium", "focus": "AI前沿资讯", "sort_order": 1, "is_active": True},
|
||||
{"name": "AI工具搜索", "source_type": "web_search", "query": "AI工具 效率提升 AI工作流 2026", "credibility": "medium", "focus": "AI工具实测", "sort_order": 2, "is_active": True},
|
||||
]:
|
||||
existing = db.query(CollectorSource).filter(CollectorSource.name == new_src["name"]).first()
|
||||
if not existing:
|
||||
db.add(CollectorSource(**new_src))
|
||||
logger.info(f" 创建采集源: {new_src['name']}")
|
||||
db.commit()
|
||||
logger.info(f" 已停用 {deactivated_sources} 个旧采集源")
|
||||
|
||||
# ========== 6. 更新 PlatformConfig tags ==========
|
||||
logger.info("--- 更新 PlatformConfig tags ---")
|
||||
for p in db.query(PlatformConfig).all():
|
||||
if p.platform in NEW_PLATFORM_TAGS:
|
||||
rules = p.compliance_rules or {}
|
||||
if isinstance(rules, dict) and "allowed_tags" in rules:
|
||||
rules["allowed_tags"] = NEW_PLATFORM_TAGS[p.platform]
|
||||
p.compliance_rules = rules
|
||||
logger.info(f" 更新平台标签: {p.platform}")
|
||||
db.commit()
|
||||
|
||||
# ========== 7. 更新 TrendFieldMapping ==========
|
||||
logger.info("--- 更新 TrendFieldMapping ---")
|
||||
from app.models import TrendFieldMapping
|
||||
new_mappings = [
|
||||
{"trend_keyword": "AI工具", "field_name": "AI与效率", "sort_order": 1},
|
||||
{"trend_keyword": "AI创作", "field_name": "AI与效率", "sort_order": 2},
|
||||
{"trend_keyword": "AI职场", "field_name": "AI与效率", "sort_order": 3},
|
||||
{"trend_keyword": "效率工具", "field_name": "AI与效率", "sort_order": 4},
|
||||
{"trend_keyword": "未来工作", "field_name": "未来工作方式", "sort_order": 5},
|
||||
{"trend_keyword": "远程工作", "field_name": "未来工作方式", "sort_order": 6},
|
||||
{"trend_keyword": "AI就业", "field_name": "未来工作方式", "sort_order": 7},
|
||||
{"trend_keyword": "科技人文", "field_name": "科技人文", "sort_order": 8},
|
||||
{"trend_keyword": "数字生活", "field_name": "科技人文", "sort_order": 9},
|
||||
{"trend_keyword": "AI伦理", "field_name": "科技人文", "sort_order": 10},
|
||||
{"trend_keyword": "AI情感", "field_name": "AI与效率", "sort_order": 11},
|
||||
]
|
||||
existing_mappings = {m.trend_keyword: m for m in db.query(TrendFieldMapping).all()}
|
||||
# 标记旧的映射为停用
|
||||
for keyword, mapping in existing_mappings.items():
|
||||
if keyword not in [nm["trend_keyword"] for nm in new_mappings]:
|
||||
mapping.is_active = False
|
||||
# 创建/更新新映射
|
||||
for nm in new_mappings:
|
||||
if nm["trend_keyword"] in existing_mappings:
|
||||
m = existing_mappings[nm["trend_keyword"]]
|
||||
m.field_name = nm["field_name"]
|
||||
m.sort_order = nm["sort_order"]
|
||||
m.is_active = True
|
||||
else:
|
||||
db.add(TrendFieldMapping(**nm))
|
||||
db.commit()
|
||||
logger.info(f" 已同步 {len(new_mappings)} 条 TrendFieldMapping")
|
||||
|
||||
logger.info("=" * 50)
|
||||
logger.info("✅ DB 迁移完成")
|
||||
logger.info("=" * 50)
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
logger.error(f"迁移失败: {e}")
|
||||
db.rollback()
|
||||
sys.exit(1)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
migrate()
|
||||
@@ -45,32 +45,32 @@ _PROMPT_DEFAULTS = {
|
||||
"variables": ["gaps"],
|
||||
},
|
||||
"section_expansion_zhihu": {
|
||||
"content": "写「{topic_title}」文章中「{section_title}」这一节。\n\n今天:{date}。\n笔记要点:\n{content}\n\n写400-800字,3-6段。每段围绕一个论点展开:先摆事实/数据,再分析原因,最后说清这跟读者有什么关系。\n\n注意:\n- 每个论点必须配具体数据或案例(至少一个),不要空谈。引用研究、报告或行业事件\n- 用「你」称呼读者,像是在知乎回答里跟人讨论\n- 从具体问题切入,不要以「引言/总结/核心观点」这类标签开头\n- 回答「所以呢」——这条信息对读者有什么用\n- 避免「首先其次最后」「总的来说」「综上所述」「值得注意的是」这些表达\n- 来源放在文末括号内\n- 纯段落,不要列表/标题/序号标记",
|
||||
"content": "写「{topic_title}」文章中「{section_title}」这一节。\n\n今天:{date}。\n笔记要点:\n{content}\n\n写400-800字,3-6段。每段围绕一个论点展开:先摆独家数据/一手案例,再分析深层原因,最后说清这跟读者有什么关系。\n\n注意:\n- 每个论点必须有具体数据或独家案例支撑——自采数据、一手实验对比、行业报告引用优先。不要只用公开信息\n- 用「你」称呼读者,像是在知乎回答里跟人讨论\n- 从具体问题或反直觉现象切入,不要以「引言/总结/核心观点」这类标签开头\n- 不只是介绍这个工具/技术,而是探讨它如何改变了人的行为方式或认知\n- 回答「所以呢」——这条信息对读者有什么用,有什么可操作的启发\n- 如果这个话题有争议点,不要回避,亮出你的立场\n- 避免「首先其次最后」「总的来说」「综上所述」「值得注意的是」这些表达\n- 来源放在文末括号内\n- 纯段落,不要列表/标题/序号标记",
|
||||
"temperature": 0.78, "max_tokens": 4000,
|
||||
"variables": ["topic_title", "section_title", "date", "content"],
|
||||
},
|
||||
"section_expansion_wechat": {
|
||||
"content": "写「{topic_title}」文章中「{section_title}」这一节。\n\n今天:{date}。\n笔记要点:\n{content}\n\n写300-500字,用「我」的口吻叙述——像在跟朋友聊天,不是写文章。\n\n注意:\n- 从个人经历或感受切入,不要讲道理\n- 每个观点背后要有一个具体故事或观察\n- 段与段之间有情绪的起承转合,不是逻辑推理\n- 读起来像一个人在说话,而不是一篇文章在论述\n- 避免「首先其次最后」「总的来说」「综上所述」「值得注意的是」这些套路\n- 纯段落,不要标题/列表/序号标记",
|
||||
"content": "写「{topic_title}」文章中「{section_title}」这一节。\n\n今天:{date}。\n笔记要点:\n{content}\n\n写300-500字,用「我」的口吻写一个真实的困惑、顿悟或观察——不是测评工具,而是记录这个技术/趋势如何改变了你的一种习惯、认知或生活方式。\n\n注意:\n- 从个人经历或感受切入,不是介绍工具功能,而是讲这个工具让你想到了什么、改变了什么\n- 每个观点背后要有一个具体故事或观察,虚实结合\n- 段与段之间有情绪的起承转合,不是逻辑推理\n- 读起来像一个人在深夜和朋友聊天时的反思,不是一篇产品测评\n- 可以有困惑和不确定——真实比正确重要\n- 避免「首先其次最后」「总的来说」「综上所述」「值得注意的是」这些套路\n- 纯段落,不要标题/列表/序号标记",
|
||||
"temperature": 0.8, "max_tokens": 3000,
|
||||
"variables": ["topic_title", "section_title", "date", "content"],
|
||||
},
|
||||
"section_expansion_xiaohongshu": {
|
||||
"content": "写「{topic_title}」笔记中「{section_title}」这一节。\n\n今天:{date}。\n笔记要点:\n{content}\n\n写100-200字,1-3小段。每段1-2句,直接给干货。\n\n注意:\n- 直接给结论或方法,不要铺垫\n- 可以用emoji(1个/段,不要堆砌)\n- 像博主在分享亲测有效的经验\n- 不要讲道理,只讲「是什么」和「怎么做」\n- 用「你」或直接省略主语\n- 避免「首先其次最后」「总的来说」「综上所述」「值得注意的是」这些套路\n- 纯段落,不要列表/标题/序号标记",
|
||||
"content": "写「{topic_title}」笔记中「{section_title}」这一节。\n\n今天:{date}。\n笔记要点:\n{content}\n\n写100-200字,1-3小段。每段1-2句,直接给干货。\n\n注意:\n- 直接给结论或方法,不要铺垫\n- 每个结论后面跟一个具体数据/对比/结果(亲测可用才有说服力)\n- 可以用emoji(1个/段,不要堆砌)\n- 像博主在分享亲测有效的经验,有真实的「我试过」感\n- 不要讲道理,只讲「是什么」和「怎么做」\n- 用「你」或直接省略主语\n- 避免「首先其次最后」「总的来说」「综上所述」「值得注意的是」这些套路\n- 纯段落,不要列表/标题/序号标记",
|
||||
"temperature": 0.75, "max_tokens": 2000,
|
||||
"variables": ["topic_title", "section_title", "date", "content"],
|
||||
},
|
||||
"title_optimize_zhihu": {
|
||||
"content": "你是知乎老用户,经常上热榜。给这篇文章起3个标题候选:\n\n文章主题:{title}\n核心观点:{core}\n受众痛点:{pain}\n领域:{field}\n\n感觉像在跟人推荐一篇值得辩论的文章。信息要密、20字内、有明确立场、让人想点进来说两句。不要「如何」开头。\n\n每行一个,不要编号。",
|
||||
"content": "你是知乎老用户,经常上热榜。给这篇文章起3个标题候选:\n\n文章主题:{title}\n核心观点:{core}\n受众痛点:{pain}\n领域:{field}\n\n感觉像在跟人推荐一篇值得辩论的观点帖,不是在推荐一篇工具文。信息要密、20字内、有明确立场、有观点交锋感、让人想点进来说两句。不要「如何」开头。如果话题有争议性,在标题里亮出你的立场。\n\n每行一个,不要编号。",
|
||||
"temperature": 0.8, "max_tokens": 1500,
|
||||
"variables": ["title", "core", "pain", "field"],
|
||||
},
|
||||
"title_optimize_wechat": {
|
||||
"content": "你是公众号编辑。这篇文章面向{field}领域的读者,帮起3个标题:\n\n文章主题:{title}\n核心观点:{core}\n\n要有身份代入感(打工人/30岁+/普通上班族)、有情绪钩子(焦虑/反常识/后悔)、含微信搜索关键词。15-25字,口语化,越具体越好。参考这种风格:「用了AI效率反而更低了?73%的人掉进了这个坑」\n\n每行一个,不要编号。",
|
||||
"content": "你是公众号编辑,擅长写有温度、有人味的文章。这篇文章面向{field}领域的读者,帮起3个标题:\n\n文章主题:{title}\n核心观点:{core}\n\n标题方向是「科技时代的个人叙事」——不是推荐工具,而是分享一个真实的人与技术之间的故事。要有身份代入感(打工人/30岁+/普通上班族/家长)、有情绪钩子(困惑/反常识/顿悟/后悔)、含微信搜索关键词。15-25字,口语化,越具体越好。参考:「AI帮我写周报的第3个月,我终于发现哪里不对了」\n\n每行一个,不要编号。",
|
||||
"temperature": 0.8, "max_tokens": 1500,
|
||||
"variables": ["title", "core", "field"],
|
||||
},
|
||||
"title_optimize_xhs": {
|
||||
"content": "你是小红书博主。给这篇笔记起3个标题:\n\n笔记主题:{title}\n核心:{core}\n\n每条18字以内,有具体场景和结果感。可以加1个精确emoji(不要🔥💥❌❓)。参考:「用了两年AI工具,推荐这5个就够了」这种风格。\n\n每行一个,不要编号。",
|
||||
"content": "你是小红书博主,擅长写有数据、有结果感的干货笔记。给这篇笔记起3个标题:\n\n笔记主题:{title}\n核心:{core}\n\n每条18字以内,有具体场景和结果感(含数字/时间/效果)。可以加1个精确emoji(不要🔥💥❌❓)。参考:「两年换了8个AI工具,最后只留这3个」这种风格。\n\n每行一个,不要编号。",
|
||||
"temperature": 0.8, "max_tokens": 1500,
|
||||
"variables": ["title", "core"],
|
||||
},
|
||||
@@ -95,7 +95,7 @@ _PROMPT_DEFAULTS = {
|
||||
"variables": ["html"],
|
||||
},
|
||||
"sources_optimization": {
|
||||
"content": "你是一个内容策略分析师。分析当前中文互联网可持续生活领域的真实热点,与以下配置进行对比。\n\n当前配置的类别({n}个):\n{cat_names}\n\n当前配置的信息源({n2}个):\n{src_summary}\n\n请完成:\n1. 评估每个类别是否仍符合{year}年中国市场真实热点(保留/淘汰/合并)\n2. 评估每个信息源是否可在国内正常访问\n3. 建议新增的类别(最多2条)\n4. 建议新增的信息源搜索词(最多3条,含具体搜索词)\n\n输出 JSON:\n{\n \"category_assessment\": [{\"name\": \"...\", \"status\": \"保留/淘汰/合并\", \"reason\": \"...\"}],\n \"source_assessment\": [{\"source\": \"...\", \"accessible\": true/false, \"note\": \"...\"}],\n \"new_categories\": [{\"name\": \"...\", \"reason\": \"...\"}],\n \"new_search_queries\": [{\"query\": \"...\", \"target\": \"...\"}]\n}\n只输出JSON。",
|
||||
"content": "你是一个内容策略分析师。分析当前中文互联网AI与科技人文领域的真实热点,与以下配置进行对比。\n\n当前配置的类别({n}个):\n{cat_names}\n\n当前配置的信息源({n2}个):\n{src_summary}\n\n请完成:\n1. 评估每个类别是否仍符合{year}年中国市场真实热点(保留/淘汰/合并)\n2. 评估每个信息源是否可在国内正常访问\n3. 建议新增的类别(最多2条)\n4. 建议新增的信息源搜索词(最多3条,含具体搜索词)\n\n输出 JSON:\n{\n \"category_assessment\": [{\"name\": \"...\", \"status\": \"保留/淘汰/合并\", \"reason\": \"...\"}],\n \"source_assessment\": [{\"source\": \"...\", \"accessible\": true/false, \"note\": \"...\"}],\n \"new_categories\": [{\"name\": \"...\", \"reason\": \"...\"}],\n \"new_search_queries\": [{\"query\": \"...\", \"target\": \"...\"}]\n}\n只输出JSON。",
|
||||
"temperature": 0.5, "max_tokens": 3000,
|
||||
"variables": ["n", "cat_names", "n2", "src_summary", "year"],
|
||||
},
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
长文 → 短视频脚本改写器
|
||||
读取某选题的长文(知乎/公众号 markdown),生成 N 条差异化短视频口播/分镜脚本。
|
||||
"""
|
||||
import argparse
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
sys.path.insert(0, str(PROJECT_ROOT / 'platform' / 'backend'))
|
||||
|
||||
from db_helper import get_topic_by_id, get_articles_by_topic, save_article
|
||||
from prompt_loader import get_prompt, get_prompt_params
|
||||
|
||||
try:
|
||||
from app.core.nvidia_client import call_llm
|
||||
HAVE_LLM = True
|
||||
except ImportError:
|
||||
HAVE_LLM = False
|
||||
|
||||
DATA_DIR = PROJECT_ROOT / "automation" / "data"
|
||||
LOGS_DIR = PROJECT_ROOT / "automation" / "logs"
|
||||
TODAY = datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler(LOGS_DIR / f"shortvideo_{TODAY}.log"),
|
||||
logging.StreamHandler(),
|
||||
],
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SYSTEM_PROMPT = "你是短视频脚本编剧,擅长把长文改成高完播率口播脚本"
|
||||
|
||||
_VARIANT_ANGLES = [
|
||||
("观点冲突型", "从一个反直觉/有争议的观点切入,制造认知冲突,让观众想反驳或认同"),
|
||||
("干货清单型", "提炼 3-5 条可立刻上手的干货要点,节奏快、信息密度高"),
|
||||
("故事共鸣型", "用一个真实人物/生活场景的故事开头,引发情绪共鸣,再带出观点"),
|
||||
]
|
||||
|
||||
|
||||
def _build_prompt(source_text: str, angle_name: str, angle_desc: str) -> str:
|
||||
return f"""你是一个短视频脚本编剧。下面是一篇长文的内容,请把它改写成一条短视频脚本。
|
||||
|
||||
改写角度:{angle_name}——{angle_desc}
|
||||
|
||||
长文素材:
|
||||
{source_text}
|
||||
|
||||
请严格按以下结构输出(用 markdown,各级标题不要省略):
|
||||
|
||||
## 标题(爆款钩子)
|
||||
写 1 个 15-25 字的短视频标题,带强钩子(悬念/冲突/数字/结果),不要标题党到离谱。
|
||||
|
||||
## 口播文案
|
||||
写 60-90 秒的口语化口播稿,约 200-350 字。像真人对着镜头说话,有停顿标记(用「[停顿]」标注换气/节奏点)。不要书面语,不要「首先其次最后」套路。
|
||||
|
||||
## 分镜建议
|
||||
给出 3-5 个镜头,每个镜头包含:
|
||||
- 画面:具体拍什么(景别/动作/场景)
|
||||
- 字幕:屏幕上打出的关键词
|
||||
- 时长:几秒
|
||||
|
||||
用列表或表格呈现,每个镜头一行。
|
||||
|
||||
## 适配平台
|
||||
说明这条脚本分别适配 抖音 / 视频号 / 小红书视频 时的微调建议(各 1 句话)。
|
||||
|
||||
只输出上面的 markdown,不要其他说明。"""
|
||||
|
||||
|
||||
def _template_markdown(topic_title: str, angle_name: str) -> str:
|
||||
return f"""## 标题(爆款钩子)
|
||||
(待生成){topic_title} - {angle_name}角度
|
||||
|
||||
## 口播文案
|
||||
[LLM 不可用,使用模板占位。请稍后手动补充 60-90 秒口播稿,并用[停顿]标记节奏。]
|
||||
|
||||
## 分镜建议
|
||||
- 画面:(占位) 字幕:(占位) 时长:(占位)
|
||||
|
||||
## 适配平台
|
||||
- 抖音:(占位)
|
||||
- 视频号:(占位)
|
||||
- 小红书视频:(占位)
|
||||
"""
|
||||
|
||||
|
||||
def _load_source(topic: Dict, articles: List[Dict]) -> str:
|
||||
for platform in ("zhihu", "wechat"):
|
||||
for a in articles:
|
||||
if a.get("platform") == platform and a.get("html_content"):
|
||||
return a["html_content"]
|
||||
if topic.get("core_concept"):
|
||||
return f"# {topic.get('title', '')}\n\n{topic.get('core_concept')}"
|
||||
return topic.get("title", "")
|
||||
|
||||
|
||||
def _generate_one(source: str, idx: int, topic_title: str) -> str:
|
||||
angle_name, angle_desc = _VARIANT_ANGLES[(idx - 1) % len(_VARIANT_ANGLES)]
|
||||
if HAVE_LLM:
|
||||
try:
|
||||
params = get_prompt_params("shortvideo_script") or {}
|
||||
temperature = params.get("temperature", 0.8)
|
||||
max_tokens = params.get("max_tokens", 2000)
|
||||
prompt = _build_prompt(source, angle_name, angle_desc)
|
||||
result = call_llm(
|
||||
prompt,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
system_prompt=SYSTEM_PROMPT,
|
||||
)
|
||||
if result and result.strip():
|
||||
return result.strip()
|
||||
except Exception as e:
|
||||
logger.warning("LLM 调用失败(variant %s):%s", idx, e)
|
||||
logger.warning("LLM 不可用或失败,写入模板占位(variant %s)", idx)
|
||||
return _template_markdown(topic_title, angle_name)
|
||||
|
||||
|
||||
def _extract_title(markdown: str, fallback: str) -> str:
|
||||
for line in markdown.splitlines():
|
||||
line = line.strip()
|
||||
if line.startswith("## 标题") or line.startswith("# 标题"):
|
||||
rest = line.split("标题", 1)[-1].strip("(():: ")
|
||||
if rest:
|
||||
return rest[:60]
|
||||
first = next((l.strip("# ").strip() for l in markdown.splitlines() if l.strip()), "")
|
||||
return first[:60] if first else fallback
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="长文转短视频脚本")
|
||||
parser.add_argument("--topic-id", required=True, help="选题 ID")
|
||||
parser.add_argument("--count", type=int, default=3, help="生成脚本数量(默认 3)")
|
||||
args = parser.parse_args()
|
||||
|
||||
topic_id = args.topic_id
|
||||
count = max(1, args.count)
|
||||
|
||||
files: List[str] = []
|
||||
out_dir = DATA_DIR / "shortvideos" / TODAY
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
topic = get_topic_by_id(topic_id)
|
||||
if not topic:
|
||||
logger.error("选题不存在:%s", topic_id)
|
||||
print(json.dumps({"ok": False, "topic_id": topic_id, "count": 0, "files": []}, ensure_ascii=False))
|
||||
sys.exit(1)
|
||||
|
||||
articles = get_articles_by_topic(topic_id)
|
||||
source = _load_source(topic, articles)
|
||||
topic_title = topic.get("title", topic_id)
|
||||
|
||||
ok = True
|
||||
for i in range(1, count + 1):
|
||||
markdown = _generate_one(source, i, topic_title)
|
||||
title = _extract_title(markdown, f"{topic_title}_短视频{i}")
|
||||
file_path = out_dir / f"{topic_id}_shortvideo_{i}.md"
|
||||
try:
|
||||
file_path.write_text(markdown, encoding="utf-8")
|
||||
files.append(str(file_path))
|
||||
logger.info("已写出脚本文件:%s", file_path)
|
||||
except Exception as e:
|
||||
logger.error("写文件失败:%s", e)
|
||||
ok = False
|
||||
continue
|
||||
|
||||
try:
|
||||
save_article(topic_id, f"shortvideo_{i}", html_content=None, title=title, content=markdown)
|
||||
logger.info("已写入 DB:shortvideo_%s / %s", i, topic_id)
|
||||
except Exception as e:
|
||||
logger.warning("DB 保存失败(文件已落盘):%s", e)
|
||||
|
||||
summary = {"ok": ok, "topic_id": topic_id, "count": count, "files": files}
|
||||
print(json.dumps(summary, ensure_ascii=False))
|
||||
sys.exit(0 if ok else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -24,18 +24,25 @@ TODAY = __import__('datetime').datetime.now().strftime("%Y-%m-%d")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_TREND_DOMAIN_MAP = {
|
||||
"远程工作": "未来工作方式",
|
||||
"AI工具": "AI与效率",
|
||||
"可持续生活": "可持续生活系统",
|
||||
"知识管理": "个人知识工厂",
|
||||
"数字生活": "科技人文交叉",
|
||||
"科技人文": "科技人文交叉",
|
||||
"个人成长": "个人成长",
|
||||
"副业": "个人成长",
|
||||
"AI创作": "AI与效率",
|
||||
"未来工作": "未来工作方式",
|
||||
"AI职场": "AI与效率",
|
||||
"效率工具": "AI与效率",
|
||||
"家庭教育": "科技人文交叉",
|
||||
"Prompt": "AI与效率",
|
||||
"提示词": "AI与效率",
|
||||
"AI编程": "AI与效率",
|
||||
"AI写作": "AI与效率",
|
||||
"未来工作": "未来工作方式",
|
||||
"远程工作": "未来工作方式",
|
||||
"AI就业": "未来工作方式",
|
||||
"一人企业": "未来工作方式",
|
||||
"副业": "未来工作方式",
|
||||
"科技人文": "科技人文",
|
||||
"数字生活": "科技人文",
|
||||
"AI伦理": "科技人文",
|
||||
"AI情感": "科技人文",
|
||||
"数据隐私": "科技人文",
|
||||
"AI教育": "科技人文",
|
||||
}
|
||||
|
||||
_cached_trend_domain_map = None
|
||||
|
||||
+2
-1
@@ -193,7 +193,8 @@ def fetch_baidu_hot() -> List[Dict]:
|
||||
word = item.get("query", "").strip() or item.get("word", "").strip()
|
||||
if not word:
|
||||
continue
|
||||
hot_score = item.get("hotScore", 0) or item.get("heat", 0)
|
||||
hot_score_raw = item.get("hotScore", 0) or item.get("heat", 0)
|
||||
hot_score = int(hot_score_raw) if hot_score_raw else 0
|
||||
desc = item.get("desc", "")
|
||||
results.append({
|
||||
"domain": _guess_domain(word, desc),
|
||||
|
||||
+72
-3
@@ -69,6 +69,33 @@ def _load_platform_config() -> dict:
|
||||
|
||||
PLATFORM_CONFIG = _load_platform_config()
|
||||
|
||||
_AI_DISCLOSURE_TEXT = {
|
||||
"zhihu": "本文由 AI 辅助创作,核心观点与数据经宇之然人工审核校对。",
|
||||
"wechat": "本文由 AI 辅助创作,核心观点与数据经宇之然人工审核校对。",
|
||||
"xiaohongshu": "✨ 本文由 AI 辅助生成,内容已人工核对~",
|
||||
}
|
||||
|
||||
def _load_ai_disclosure_enabled() -> bool:
|
||||
"""根据《人工智能生成合成内容标识办法》(2025-09-01 施行),AI 生成内容须主动声明。
|
||||
默认开启;可通过 SystemConfig(disclose_ai=false) 或环境变量 YZR_DISCLOSE_AI=0 关闭。"""
|
||||
try:
|
||||
if os.environ.get("YZR_DISCLOSE_AI", "").strip() == "0":
|
||||
return False
|
||||
from app.database import SessionLocal
|
||||
from app.models import SystemConfig
|
||||
db = SessionLocal()
|
||||
try:
|
||||
sc = db.query(SystemConfig).filter(SystemConfig.key == "disclose_ai").first()
|
||||
if sc and str(sc.value).strip().lower() in ("false", "0", "no"):
|
||||
return False
|
||||
finally:
|
||||
db.close()
|
||||
except Exception:
|
||||
pass
|
||||
return True
|
||||
|
||||
AI_DISCLOSURE_ENABLED = _load_ai_disclosure_enabled()
|
||||
|
||||
PLATFORM_NAMES = {
|
||||
"zhihu": "知乎专栏",
|
||||
"wechat": "微信公众号",
|
||||
@@ -235,10 +262,40 @@ def inject_geo_metadata(html: str, title: str, content: str, platform: str, tags
|
||||
return html
|
||||
|
||||
|
||||
def inject_ai_disclosure(html: str, platform: str) -> str:
|
||||
"""按《人工智能生成合成内容标识办法》注入 AI 生成声明。
|
||||
显式:文首/文末标注「AI 生成」;隐式:head 注入 ai-generated meta 与 AIGC 隐式标识字段。"""
|
||||
if not AI_DISCLOSURE_ENABLED:
|
||||
return html
|
||||
text = _AI_DISCLOSURE_TEXT.get(platform, _AI_DISCLOSURE_TEXT["zhihu"])
|
||||
|
||||
disclosure_block = f'\n<blockquote class="ai-disclosure">🤖 {text}</blockquote>\n'
|
||||
|
||||
# 文末声明(置于正文末尾,封面/互动钩子之前不影响阅读)
|
||||
if "<!-- AI_DISCLOSURE -->" in html:
|
||||
html = html.replace("<!-- AI_DISCLOSURE -->", disclosure_block.strip())
|
||||
else:
|
||||
# 在 </body> 前插入文末声明
|
||||
html = html.replace("</body>", f"{disclosure_block}</body>")
|
||||
|
||||
# 隐式标识:head 注入声明 meta + AIGC 元数据字段(符合标识办法附录 E)
|
||||
head_meta = (
|
||||
'<meta name="ai-generated" content="true">\n'
|
||||
'<meta name="ai-disclosure" content="本文含人工智能生成合成内容">\n'
|
||||
'<script type="application/ld+json">\n'
|
||||
'{"AIGC":{"Label":"1","ContentProducer":"宇之然","ProduceID":"yzr-' + platform + '",'
|
||||
'"ContentPropagator":"宇之然","PropagateID":"yzr-' + platform + '"}}\n'
|
||||
'</script>\n'
|
||||
)
|
||||
html = html.replace("</head>", head_meta + "</head>")
|
||||
return html
|
||||
|
||||
|
||||
class Writer:
|
||||
def __init__(self, topic_id: str):
|
||||
self.topic_id = topic_id
|
||||
self.topic = self._load_topic()
|
||||
self._degraded = False
|
||||
outline_file = OUTLINE_DIR / TODAY / f"{topic_id}_outline.md"
|
||||
if not outline_file.exists():
|
||||
raise FileNotFoundError(f"Outline not found: {outline_file}")
|
||||
@@ -334,6 +391,9 @@ class Writer:
|
||||
except Exception as e:
|
||||
logger.warning(f"LLM 扩写失败 [{platform}]: {e}")
|
||||
|
||||
# LLM 不可用或返回为空 → 降级为大纲要点拼接(非真正成稿)
|
||||
self._degraded = True
|
||||
logger.warning(f"⚠️ [{platform}] 章节「{section['title']}」LLM 扩写缺失,降级为大纲要点拼接(内容未达发布质量)")
|
||||
lines = [l.strip() for l in content.split('\n') if not self._is_outline_noise(l)]
|
||||
if lines:
|
||||
sentences = []
|
||||
@@ -557,7 +617,7 @@ class Writer:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
line = re.sub(r'^\d+[.、)\s]+', '', line)
|
||||
line = re.sub(r'^\d+[.、))]\s*', '', line)
|
||||
line = line.strip('*#- \t"\'"''"')
|
||||
# 跳过思考/建议类输出(如"不如:"、"或者:"、"建议方案"等)
|
||||
if re.match(r'^(不如|或者|建议|推荐|参考|方案[一二三]|第[一二三]种|以[下是]|标题[一二三]|选项)', line):
|
||||
@@ -608,8 +668,11 @@ class Writer:
|
||||
html = html.replace("<!-- TAGS -->", tags_html)
|
||||
else:
|
||||
html = html.replace("<!-- TAGS -->", "")
|
||||
|
||||
html = inject_geo_metadata(html, title, adapted, platform, tags_html)
|
||||
|
||||
# 强制 AI 生成声明(合规要求,可配置关闭)
|
||||
html = inject_ai_disclosure(html, platform)
|
||||
|
||||
return html
|
||||
|
||||
def save_html(self, html: str, platform: str, *, title: str = "", content: str = "") -> str:
|
||||
@@ -634,8 +697,14 @@ class Writer:
|
||||
html = self.generate_platform_html(markdown, platform)
|
||||
results[platform] = str(self.save_html(html, platform, title=title, content=markdown))
|
||||
self.mark_draft()
|
||||
if self._degraded:
|
||||
logger.error(
|
||||
"❌ 撰写完成但存在 LLM 降级章节(大纲要点拼接,未达发布质量)。"
|
||||
"选题保留为待审查,请检查 LLM 提供商连通性后重跑,勿直接发布降级稿。"
|
||||
)
|
||||
else:
|
||||
logger.info(f"撰写完成,状态已更新为待审查")
|
||||
return {"ok": True, "files": results}
|
||||
return {"ok": True, "files": results, "degraded": self._degraded}
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
Reference in New Issue
Block a user