/** * 批量创建测试帖子,用于验证编辑器渲染、列表滚动、登录可见等功能 */ const BASE = 'http://localhost:8080'; const TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InRlc3R1Iiwicm9sZSI6ImFkbWluIiwiZXhwIjoxNzgyMDYyODI2LCJpYXQiOjE3ODE0NTgwMjZ9.QmsRTyj_2YqmGHw_Mw7_gwmo-WbHtTeyqkWrTRfccV4'; const longBody = Array.from({ length: 30 }, (_, i) => { const n = i + 1; return `

第 ${n} 节

虚拟滚动测试段落 ${n}:Lorem ipsum 论坛长文压测内容,包含中英文混排 Mixed Content 以及标点符号——「」、『』、…、!?。重复文本有助于观察滚动条、已读标记与列表项高度是否稳定。

`; }).join(''); const posts = [ { board_id: '1', title: '【测试】富文本排版大全', tags: '测试,排版,富文本', content: `

标题与段落

这是一段普通正文,包含 加粗斜体下划线删除线 样式。

无序列表

有序列表

  1. 注册账号
  2. 选择板块发帖
  3. 楼层式回复互动

引用块:论坛的价值在于记录,而不只是展示。

行内代码示例:npm run build,多行代码块:

func main() {
    fmt.Println("Hello Jiang13 Forum")
}

外链测试:姜十三论坛仓库


分隔线以上,排版元素应全部正常显示。

`, }, { board_id: '2', title: '【测试】登录可见区块', tags: '测试,会员专属', content: `

以下内容对游客隐藏,登录后可完整阅读。

这是会员专属段落:包含内部讨论纪要、未公开方案和敏感数据摘要。游客应看到模糊占位与登录引导按钮。

公开结尾:欢迎登录后查看上文隐藏内容。

`, }, { board_id: '1', title: '【测试】图片与图文混排', tags: '测试,图片', content: `

下图使用占位图服务,用于验证图片自适应与懒加载:

论坛测试配图

图片下方继续正文,检查间距与圆角是否正常。

小图测试

两张不同尺寸图片混排,移动端不应溢出容器。

`, }, { board_id: '2', title: '【测试】超长帖子 · 虚拟滚动压测', tags: '测试,长文', content: `

长文压测说明

本帖用于测试首页虚拟滚动与详情页渲染性能,正文重复段落以撑满屏幕。

${longBody}

长文结束,如滚动流畅则通过。

`, }, { board_id: '1', title: '【测试】短帖 · 单行标题', tags: '测试', content: '

极简短帖,仅一句话,用于测试列表项最小高度与摘要截取。

', }, { board_id: '2', title: '【测试】多级标题 H2~H6', tags: '测试,标题', content: `

二级标题 H2

H2 下方正文。

三级标题 H3

H3 下方正文。

四级标题 H4

H4 下方正文。

五级标题 H5

H5 下方正文。

六级标题 H6

H6 下方正文,六级标题字号应明显小于 H2。

`, }, { board_id: '1', title: '【测试】混合登录可见 + 富文本', tags: '测试,会员专属,排版', content: `

公开前言

所有人可见的导读部分。

隐藏区 加粗斜体

公开引用:登录后上文应展开为完整富文本。

`, }, { board_id: '2', title: '【测试】特殊字符与 Emoji', tags: '测试,编码', content: `

特殊符号:<script>alert(1)</script> 应被转义或过滤,不可执行。

Emoji:🎉 🚀 ✅ ❤️ 🔥 中日韩:姜十三论坛

数学符号:α + β = γ,箭头 → ← ↑ ↓

全角标点:,。!?;:""''【】

`, }, { board_id: '1', title: '【测试】多标签帖子', tags: 'Go,React,SQLite,部署,开源', content: `

本帖携带五个标签,用于测试标签展示、搜索与筛选。

技术栈:Go + Gin + GORM + SQLite + React + TipTap。

`, }, { board_id: '2', title: '【测试】空行与换行保留', tags: '测试,换行', content: `

第一段,后面有两个空行。

第二段,中间有
手动换行
应保留。

第三段结束。

`, }, ]; async function createPost(post) { const fd = new FormData(); fd.append('board_id', post.board_id); fd.append('title', post.title); fd.append('content', post.content); fd.append('tags', post.tags || ''); const res = await fetch(`${BASE}/api/posts`, { method: 'POST', headers: { Cookie: `jiang13_token=${TOKEN}` }, body: fd, }); const data = await res.json(); if (!res.ok || data.error) throw new Error(data.error || res.statusText); console.log(` OK post_id=${data.post_id} ${post.title}`); return data.post_id; } async function pinPost(postId) { const res = await fetch(`${BASE}/api/admin/posts/${postId}/pin`, { method: 'POST', headers: { Cookie: `jiang13_token=${TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ pinned: true }), }); const data = await res.json(); if (!res.ok || data.error) throw new Error(data.error || res.statusText); console.log(` PIN post_id=${postId}`); } async function main() { console.log(`开始创建 ${posts.length} 篇测试帖子...`); const ids = []; for (const post of posts) { ids.push(await createPost(post)); await new Promise((r) => setTimeout(r, 150)); } if (ids.length > 0) await pinPost(ids[0]); const list = await fetch(`${BASE}/api/posts?page=1&size=50`).then((r) => r.json()); console.log(`\n完成!当前帖子总数:${list.total}`); console.log(`新建帖子 ID:${ids.join(', ')}`); } main().catch((err) => { console.error('失败:', err.message); process.exit(1); });