主要更新: 1. 按H5网页端完全重构匹配功能(match页面) - 4种匹配类型: 创业合伙/资源对接/导师顾问/团队招募 - 资源对接等类型弹出手机号/微信号输入框 - 去掉重新匹配按钮,改为返回按钮 2. 修复所有卡片对齐和宽度问题 - 目录页附录卡片居中 - 首页阅读进度卡片满宽度 - 我的页面菜单卡片对齐 - 推广中心分享卡片统一宽度 3. 修复目录页图标和文字对齐 - section-icon固定40rpx宽高 - section-title与图标垂直居中 4. 更新真实完整文章标题(62篇) - 从book目录读取真实markdown文件名 - 替换之前的简化标题 5. 新增文章数据API - /api/db/chapters - 获取完整书籍结构 - 支持按ID获取单篇文章内容
273 lines
7.8 KiB
TypeScript
273 lines
7.8 KiB
TypeScript
/**
|
||
* Soul创业实验 - 文章数据API
|
||
* 用于存储和获取章节数据
|
||
*/
|
||
|
||
import { NextResponse } from 'next/server'
|
||
import fs from 'fs'
|
||
import path from 'path'
|
||
|
||
// 文章数据结构
|
||
interface Section {
|
||
id: string
|
||
title: string
|
||
isFree: boolean
|
||
price: number
|
||
content?: string
|
||
filePath?: string
|
||
}
|
||
|
||
interface Chapter {
|
||
id: string
|
||
title: string
|
||
sections: Section[]
|
||
}
|
||
|
||
interface Part {
|
||
id: string
|
||
number: string
|
||
title: string
|
||
subtitle: string
|
||
chapters: Chapter[]
|
||
}
|
||
|
||
// 书籍目录结构映射
|
||
const BOOK_STRUCTURE = [
|
||
{
|
||
id: 'part-1',
|
||
number: '一',
|
||
title: '真实的人',
|
||
subtitle: '人与人之间的底层逻辑',
|
||
folder: '第一篇|真实的人',
|
||
chapters: [
|
||
{ id: 'chapter-1', title: '第1章|人与人之间的底层逻辑', folder: '第1章|人与人之间的底层逻辑' },
|
||
{ id: 'chapter-2', title: '第2章|人性困境案例', folder: '第2章|人性困境案例' }
|
||
]
|
||
},
|
||
{
|
||
id: 'part-2',
|
||
number: '二',
|
||
title: '真实的行业',
|
||
subtitle: '电商、内容、传统行业解析',
|
||
folder: '第二篇|真实的行业',
|
||
chapters: [
|
||
{ id: 'chapter-3', title: '第3章|电商篇', folder: '第3章|电商篇' },
|
||
{ id: 'chapter-4', title: '第4章|内容商业篇', folder: '第4章|内容商业篇' },
|
||
{ id: 'chapter-5', title: '第5章|传统行业篇', folder: '第5章|传统行业篇' }
|
||
]
|
||
},
|
||
{
|
||
id: 'part-3',
|
||
number: '三',
|
||
title: '真实的错误',
|
||
subtitle: '我和别人犯过的错',
|
||
folder: '第三篇|真实的错误',
|
||
chapters: [
|
||
{ id: 'chapter-6', title: '第6章|我人生错过的4件大钱', folder: '第6章|我人生错过的4件大钱' },
|
||
{ id: 'chapter-7', title: '第7章|别人犯的错误', folder: '第7章|别人犯的错误' }
|
||
]
|
||
},
|
||
{
|
||
id: 'part-4',
|
||
number: '四',
|
||
title: '真实的赚钱',
|
||
subtitle: '底层结构与真实案例',
|
||
folder: '第四篇|真实的赚钱',
|
||
chapters: [
|
||
{ id: 'chapter-8', title: '第8章|底层结构', folder: '第8章|底层结构' },
|
||
{ id: 'chapter-9', title: '第9章|我在Soul上亲访的赚钱案例', folder: '第9章|我在Soul上亲访的赚钱案例' }
|
||
]
|
||
},
|
||
{
|
||
id: 'part-5',
|
||
number: '五',
|
||
title: '真实的社会',
|
||
subtitle: '未来职业与商业生态',
|
||
folder: '第五篇|真实的社会',
|
||
chapters: [
|
||
{ id: 'chapter-10', title: '第10章|未来职业的变化趋势', folder: '第10章|未来职业的变化趋势' },
|
||
{ id: 'chapter-11', title: '第11章|中国社会商业生态的未来', folder: '第11章|中国社会商业生态的未来' }
|
||
]
|
||
}
|
||
]
|
||
|
||
// 免费章节ID
|
||
const FREE_SECTIONS = ['1.1', 'preface', 'epilogue', 'appendix-1', 'appendix-2', 'appendix-3']
|
||
|
||
// 从book目录读取真实文章数据
|
||
function loadBookData(): Part[] {
|
||
const bookPath = path.join(process.cwd(), 'book')
|
||
const parts: Part[] = []
|
||
|
||
for (const partConfig of BOOK_STRUCTURE) {
|
||
const part: Part = {
|
||
id: partConfig.id,
|
||
number: partConfig.number,
|
||
title: partConfig.title,
|
||
subtitle: partConfig.subtitle,
|
||
chapters: []
|
||
}
|
||
|
||
for (const chapterConfig of partConfig.chapters) {
|
||
const chapter: Chapter = {
|
||
id: chapterConfig.id,
|
||
title: chapterConfig.title,
|
||
sections: []
|
||
}
|
||
|
||
const chapterPath = path.join(bookPath, partConfig.folder, chapterConfig.folder)
|
||
|
||
try {
|
||
const files = fs.readdirSync(chapterPath)
|
||
const mdFiles = files.filter(f => f.endsWith('.md')).sort()
|
||
|
||
for (const file of mdFiles) {
|
||
// 从文件名提取ID和标题
|
||
const match = file.match(/^(\d+\.\d+)\s+(.+)\.md$/)
|
||
if (match) {
|
||
const [, id, title] = match
|
||
const filePath = path.join(chapterPath, file)
|
||
|
||
chapter.sections.push({
|
||
id,
|
||
title: title.replace(/[::]/g, ':'), // 统一冒号格式
|
||
isFree: FREE_SECTIONS.includes(id),
|
||
price: 1,
|
||
filePath
|
||
})
|
||
}
|
||
}
|
||
|
||
// 按ID数字排序
|
||
chapter.sections.sort((a, b) => {
|
||
const [aMajor, aMinor] = a.id.split('.').map(Number)
|
||
const [bMajor, bMinor] = b.id.split('.').map(Number)
|
||
return aMajor !== bMajor ? aMajor - bMajor : aMinor - bMinor
|
||
})
|
||
|
||
} catch (e) {
|
||
console.error(`读取章节目录失败: ${chapterPath}`, e)
|
||
}
|
||
|
||
part.chapters.push(chapter)
|
||
}
|
||
|
||
parts.push(part)
|
||
}
|
||
|
||
return parts
|
||
}
|
||
|
||
// 读取文章内容
|
||
function getArticleContent(sectionId: string): string | null {
|
||
const bookData = loadBookData()
|
||
|
||
for (const part of bookData) {
|
||
for (const chapter of part.chapters) {
|
||
const section = chapter.sections.find(s => s.id === sectionId)
|
||
if (section?.filePath) {
|
||
try {
|
||
return fs.readFileSync(section.filePath, 'utf-8')
|
||
} catch (e) {
|
||
console.error(`读取文章内容失败: ${section.filePath}`, e)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return null
|
||
}
|
||
|
||
// GET - 获取所有章节数据
|
||
export async function GET(request: Request) {
|
||
const { searchParams } = new URL(request.url)
|
||
const sectionId = searchParams.get('id')
|
||
const includeContent = searchParams.get('content') === 'true'
|
||
|
||
try {
|
||
// 如果指定了章节ID,返回单篇文章内容
|
||
if (sectionId) {
|
||
const content = getArticleContent(sectionId)
|
||
if (content) {
|
||
return NextResponse.json({
|
||
success: true,
|
||
data: { id: sectionId, content }
|
||
})
|
||
} else {
|
||
return NextResponse.json({
|
||
success: false,
|
||
error: '文章不存在'
|
||
}, { status: 404 })
|
||
}
|
||
}
|
||
|
||
// 返回完整书籍结构
|
||
const bookData = loadBookData()
|
||
|
||
// 统计总章节数
|
||
let totalSections = 0
|
||
for (const part of bookData) {
|
||
for (const chapter of part.chapters) {
|
||
totalSections += chapter.sections.length
|
||
}
|
||
}
|
||
// 加上序言、尾声和3个附录
|
||
totalSections += 5
|
||
|
||
return NextResponse.json({
|
||
success: true,
|
||
data: {
|
||
totalSections,
|
||
parts: bookData,
|
||
appendix: [
|
||
{ id: 'appendix-1', title: '附录1|Soul派对房精选对话', isFree: true },
|
||
{ id: 'appendix-2', title: '附录2|创业者自检清单', isFree: true },
|
||
{ id: 'appendix-3', title: '附录3|本书提到的工具和资源', isFree: true }
|
||
],
|
||
preface: { id: 'preface', title: '序言|为什么我每天早上6点在Soul开播?', isFree: true },
|
||
epilogue: { id: 'epilogue', title: '尾声|这本书的真实目的', isFree: true }
|
||
}
|
||
})
|
||
} catch (error) {
|
||
console.error('获取章节数据失败:', error)
|
||
return NextResponse.json({
|
||
success: false,
|
||
error: '获取数据失败'
|
||
}, { status: 500 })
|
||
}
|
||
}
|
||
|
||
// POST - 同步章节数据到数据库(预留接口)
|
||
export async function POST(request: Request) {
|
||
try {
|
||
const bookData = loadBookData()
|
||
|
||
// 这里可以添加数据库写入逻辑
|
||
// 目前先返回成功,数据已从文件系统读取
|
||
|
||
let totalSections = 0
|
||
for (const part of bookData) {
|
||
for (const chapter of part.chapters) {
|
||
totalSections += chapter.sections.length
|
||
}
|
||
}
|
||
totalSections += 5 // 序言、尾声、3个附录
|
||
|
||
return NextResponse.json({
|
||
success: true,
|
||
message: '章节数据同步成功',
|
||
data: {
|
||
totalSections,
|
||
partsCount: bookData.length,
|
||
chaptersCount: bookData.reduce((acc, p) => acc + p.chapters.length, 0)
|
||
}
|
||
})
|
||
} catch (error) {
|
||
console.error('同步章节数据失败:', error)
|
||
return NextResponse.json({
|
||
success: false,
|
||
error: '同步数据失败'
|
||
}, { status: 500 })
|
||
}
|
||
}
|