بنقرة واحدة
add-html-parser
为过早客论坛页面添加新的 HTML 解析函数,从论坛 HTML 中提取结构化数据。遵循项目现有的正则解析模式。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
为过早客论坛页面添加新的 HTML 解析函数,从论坛 HTML 中提取结构化数据。遵循项目现有的正则解析模式。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
代码生成后执行自动化质量检查。运行 Prettier 格式化、ESLint 检查修复和 TypeScript 类型检查。
以持续追问的方式"拷问"用户的设计方案或计划,沿决策树逐一解决每个分支的依赖关系,直到达成共识。当用户想要压力测试方案、被"盘问"设计思路,或提到"拷问我"/"grill me"/"审查方案"/"盘问方案"/"帮我梳理"等表达时使用。
添加一个新的 Content Script,注入到过早客论坛页面中。包含脚本创建、Vue 应用挂载和在主入口注册的完整流程。
为项目端到端地添加一个完整的新功能。覆盖从需求分析到代码生成、测试验证的全部流程,涉及 Content Script、Vue 组件、选项配置、国际化等多个层面。
为项目添加国际化翻译。使用 vue-i18n 管理中文和英文翻译,包含翻译 key 设计、文件更新和使用方式。
在扩展的选项页面中添加一个新的子页面。包含路由配置、Vue 页面创建和侧边栏导航集成的完整流程。
| name | add-html-parser |
| description | 为过早客论坛页面添加新的 HTML 解析函数,从论坛 HTML 中提取结构化数据。遵循项目现有的正则解析模式。 |
在执行本 Skill 前,先阅读以下知识库文件以获取必要的背景知识:
.codebuddy/knowledge/forum-domain.md - 🔴必读 - 论坛 HTML 标记结构、URL 模式、XSRF 认证、服务端响应格式.codebuddy/knowledge/design-decisions.md - 了解为什么使用正则解析 HTML、checkAlertInfo 错误处理模式项目使用正则表达式直接解析 HTML 字符串来获取论坛数据,而非使用 DOM 解析器。所有解析函数集中在 src/api/index.ts 中,命名遵循 parse* 前缀。
// src/api/index.ts
// 简单解析:单个正则提取字段
const parseUserInfo = (htmlStr: string): UserInfo => {
return {
uid: htmlStr.match(/<div class="username">([^<]+)<\/div>/)?.[1],
avatarUrl: htmlStr.match(/<a href="\/u\/[^"]+">\n\s+<img src="([^"]+)" alt="" class="avatar" \/>/)?.[1],
memberNo: htmlStr.match(/过早客第(\d+)号成员/)?.[1],
};
};
// 列表解析:split 拆分 + map 处理
const parseUserMsgList = (htmlStr: string): UserMessage[] => {
const msgList = htmlStr.split('<div class="notification-item">');
if (msgList.length < 2) {
return [];
}
const lastMsgIndex = msgList.length - 1;
msgList[lastMsgIndex] = msgList[lastMsgIndex].split('<div class="ui-footer">')[0];
msgList.shift();
return msgList.map((item): UserMessage => {
return {
uid: item.match(/<a href="\/u\/([^"]+)">/)?.[1],
topicTitle: item.match(/<a href="\/t\/[^"]+">(.+)<\/a>/)?.[1],
};
});
};
// 嵌套解析:外层 HTML 拆分为子块,各自独立解析
const parseUserTopic = (htmlStr: string): UserTopic => {
let detail: UserTopicDetail = {};
let list: UserReplyItem[] = [];
let total = htmlStr.match(/<div class="ui-header">.+?<span>共收到(\d+)条回复<\/span>/s)?.[1];
if (total === undefined) {
detail = parseTopicDetail(htmlStr);
total = '0';
} else {
const [detailHtmlStr, replyHtmlStr] = htmlStr.split('class="topic-reply');
detail = parseTopicDetail(detailHtmlStr);
list = parseTopicReplyList(replyHtmlStr);
}
return {
detail,
status: parseTopicStatus(htmlStr),
reply: { total, list },
};
};
在 src/types/index.ts 中定义解析结果的数据结构:
export interface NewData {
id?: string;
title?: string;
// ... 其他字段
}
在 src/api/index.ts 中添加解析函数:
const parseNewData = (htmlStr: string): NewData => {
return {
id: htmlStr.match(/data-id="(\d+)"/)?.[1],
title: htmlStr.match(/<h3 class="title">([^<]+)<\/h3>/)?.[1],
};
};
论坛内容可能包含 HTML 实体(如 <, >, &),使用 html-entities 解码:
import { decode } from 'html-entities';
const parseEditedTopic = (htmlStr: string): UserTopicDetail => {
const title = htmlStr.match(
/<input class="form-control" id="prependedInput" type="text" placeholder="主题" name="title" value="(.+?)">/,
)?.[1];
return {
title: decode(title), // 解码 HTML 实体
content: htmlStr.match(/<textarea [^>]*>(.+?)<\/textarea>/s)?.[1],
};
};
export const getNewData = async (id: string): Promise<NewData> => {
const data = await request(`/api/some-path/${id}`);
return parseNewData(data);
};
// 使用 checkAlertInfo 检查服务端错误提示
const checkAlertInfo = (htmlStr: string) => {
const alertHtmlStr = htmlStr.match(/<form [^>]*>.+?<ul class="alert alert-danger">(.+?)<\/ul>.+?<textarea/s)?.[1];
if (alertHtmlStr) {
const alertInfo = [...alertHtmlStr.matchAll(/<li>(.+?)<\/li>/g)].map((item) => item[1]).join(',');
throw new Error(alertInfo);
}
};
/<tag>(.+?)<\/tag>/ # 非贪婪匹配单行
/<tag>([^<]+)<\/tag>/ # 匹配不含 < 的内容
/<tag>(.+?)<\/tag>/s # 使用 s 标志匹配换行
/<div class="class">(.+)<\/div>\s+?<div class="footer">/s # 匹配到下一个标记
/<a href="([^"]+)">/ # 匹配 href 属性
/<img src="([^"]+)" alt="" class="avatar"( \/)?>/ # 匹配 img 属性
matchAll 全局匹配const timeMatches = [...item.matchAll(/<span class="time">([^<|楼主]+)<\/span>/g)];
const [timeMatch, ipMatch] = timeMatches; // 第一个和第二个匹配
const nodeList = [...item.matchAll(/<a href="\/node\/(\w+?)">(.+?)<\/a>/g)].map(
(node): TreeNode => ({
value: node[1], // 捕获组 1
label: node[2], // 捕获组 2
}),
);
const items = htmlStr.split('<div class="item">');
items.shift(); // 移除第一个空元素
return items.map(parseItem);
?. 处理匹配结果parse* 前缀?:)提高健壮性