blockFactory.ts•12.4 kB
import { Logger } from '../utils/logger.js';
/**
* 块类型接口
*/
export interface FeishuBlock {
block_type: number;
[key: string]: any;
}
/**
* 文本样式接口
*/
export interface TextElementStyle {
bold?: boolean; // 是否加粗
italic?: boolean; // 是否斜体
underline?: boolean; // 是否下划线
strikethrough?: boolean; // 是否删除线
inline_code?: boolean; // 是否行内代码
text_color?: number; // 文本颜色
}
/**
* 文本内容接口
*/
export interface TextContent {
text: string; // 文本内容
style?: TextElementStyle; // 文本样式
}
/**
* 公式内容接口
*/
export interface EquationContent {
equation: string; // 公式内容
style?: TextElementStyle; // 文本样式
}
/**
* 文本元素类型 - 可以是普通文本或公式
*/
export type TextElement = TextContent | EquationContent;
/**
* 文本块接口
*/
export interface TextBlock extends FeishuBlock {
block_type: 2; // 文本块类型固定为2
text: {
elements: Array<{
text_run: {
content: string;
text_element_style: TextElementStyle;
}
}>;
style: {
align: number; // 对齐方式:1左对齐,2居中,3右对齐
}
};
}
/**
* 代码块接口
*/
export interface CodeBlock extends FeishuBlock {
block_type: 14; // 代码块类型固定为14
code: {
elements: Array<{
text_run: {
content: string;
text_element_style: TextElementStyle;
}
}>;
style: {
language: number; // 语言类型代码
wrap: boolean; // 是否自动换行
}
};
}
/**
* 标题块接口
*/
export interface HeadingBlock extends FeishuBlock {
block_type: number; // 标题块类型:3-11(对应标题级别1-9)
[headingKey: string]: any; // 动态属性名,如heading1, heading2等
}
/**
* 块类型枚举
*/
export enum BlockType {
TEXT = 'text',
CODE = 'code',
HEADING = 'heading',
LIST = 'list',
IMAGE = 'image',
MERMAID = 'mermaid'
}
/**
* 对齐方式枚举
*/
export enum AlignType {
LEFT = 1,
CENTER = 2,
RIGHT = 3
}
/**
* 块工厂类
* 提供统一接口创建不同类型的块内容
*/
export class BlockFactory {
private static instance: BlockFactory;
private constructor() {}
/**
* 获取块工厂实例
* @returns 块工厂实例
*/
public static getInstance(): BlockFactory {
if (!BlockFactory.instance) {
BlockFactory.instance = new BlockFactory();
}
return BlockFactory.instance;
}
/**
* 获取默认的文本元素样式
* @returns 默认文本元素样式
*/
public static getDefaultTextElementStyle(): TextElementStyle {
return {
bold: false,
inline_code: false,
italic: false,
strikethrough: false,
underline: false
};
}
/**
* 应用默认文本样式
* @param style 已有样式(可选)
* @returns 合并后的样式
*/
public static applyDefaultTextStyle(style?: TextElementStyle): TextElementStyle {
const defaultStyle = BlockFactory.getDefaultTextElementStyle();
return style ? { ...defaultStyle, ...style } : defaultStyle;
}
/**
* 创建块内容
* @param type 块类型
* @param options 块选项
* @returns 块内容对象
*/
public createBlock(type: BlockType, options: any): FeishuBlock {
switch (type) {
case BlockType.TEXT:
return this.createTextBlock(options);
case BlockType.CODE:
return this.createCodeBlock(options);
case BlockType.HEADING:
return this.createHeadingBlock(options);
case BlockType.LIST:
return this.createListBlock(options);
case BlockType.IMAGE:
return this.createImageBlock(options);
case BlockType.MERMAID:
return this.createMermaidBlock(options);
default:
Logger.error(`不支持的块类型: ${type}`);
throw new Error(`不支持的块类型: ${type}`);
}
}
/**
* 创建文本块内容
* @param options 文本块选项
* @returns 文本块内容对象
*/
public createTextBlock(options: {
textContents: Array<TextElement>,
align?: AlignType
}): FeishuBlock {
const { textContents, align = AlignType.LEFT } = options;
return {
block_type: 2, // 2表示文本块
text: {
elements: textContents.map(content => {
// 检查是否是公式元素
if ('equation' in content) {
return {
equation: {
content: content.equation,
text_element_style: BlockFactory.applyDefaultTextStyle(content.style)
}
};
} else {
// 普通文本元素
return {
text_run: {
content: content.text,
text_element_style: BlockFactory.applyDefaultTextStyle(content.style)
}
};
}
}),
style: {
align: align, // 1 居左,2 居中,3 居右
folded: false
}
}
};
}
/**
* 创建代码块内容
* @param options 代码块选项
* @returns 代码块内容对象
*/
public createCodeBlock(options: {
code: string,
language?: number,
wrap?: boolean
}): FeishuBlock {
const { code, language = 0, wrap = false } = options;
// 校验 language 合法性,飞书API只允许1~75
const safeLanguage = language >= 1 && language <= 75 ? language : 1;
return {
block_type: 14, // 14表示代码块
code: {
elements: [
{
text_run: {
content: code,
text_element_style: BlockFactory.getDefaultTextElementStyle()
}
}
],
style: {
language: safeLanguage,
wrap: wrap
}
}
};
}
/**
* 创建标题块内容
* @param options 标题块选项
* @returns 标题块内容对象
*/
public createHeadingBlock(options: {
text: string,
level?: number,
align?: AlignType
}): FeishuBlock {
const { text, level = 1, align = AlignType.LEFT } = options;
// 确保标题级别在有效范围内(1-9)
const safeLevel = Math.max(1, Math.min(9, level));
// 根据标题级别设置block_type和对应的属性名
// 飞书API中,一级标题的block_type为3,二级标题为4,以此类推
const blockType = 2 + safeLevel; // 一级标题为3,二级标题为4,以此类推
const headingKey = `heading${safeLevel}`; // heading1, heading2, ...
// 构建块内容
const blockContent: any = {
block_type: blockType
};
// 设置对应级别的标题属性
blockContent[headingKey] = {
elements: [
{
text_run: {
content: text,
text_element_style: BlockFactory.getDefaultTextElementStyle()
}
}
],
style: {
align: align,
folded: false
}
};
return blockContent;
}
/**
* 创建列表块内容(有序或无序)
* @param options 列表块选项
* @returns 列表块内容对象
*/
public createListBlock(options: {
text: string,
isOrdered?: boolean,
align?: AlignType
}): FeishuBlock {
const { text, isOrdered = false, align = AlignType.LEFT } = options;
// 有序列表是 block_type: 13,无序列表是 block_type: 12
const blockType = isOrdered ? 13 : 12;
const propertyKey = isOrdered ? "ordered" : "bullet";
// 构建块内容
const blockContent: any = {
block_type: blockType
};
// 设置列表属性
blockContent[propertyKey] = {
elements: [
{
text_run: {
content: text,
text_element_style: BlockFactory.getDefaultTextElementStyle()
}
}
],
style: {
align: align,
folded: false
}
};
return blockContent;
}
/**
* 创建图片块内容(空图片块,需要后续设置图片资源)
* @param options 图片块选项
* @returns 图片块内容对象
*/
public createImageBlock(options: {
width?: number,
height?: number
} = {}): FeishuBlock {
const { width = 100, height = 100 } = options;
return {
block_type: 27, // 27表示图片块
image: {
width: width,
height: height,
token: "" // 空token,需要后续通过API设置
}
};
}
/**
* 创建Mermaid
* @param options Mermaid块选项
* @returns Mermaid块内容对象
*/
public createMermaidBlock(
options: {
code?: string;
} = {},
): FeishuBlock {
const { code } = options;
return {
block_type: 40,
add_ons: {
component_id: '',
component_type_id: 'blk_631fefbbae02400430b8f9f4',
record: JSON.stringify({
data: code,
}),
},
};
}
/**
* 创建表格块
* @param options 表格块选项
* @returns 表格块内容对象
*/
public createTableBlock(options: {
columnSize: number;
rowSize: number;
cells?: Array<{
coordinate: { row: number; column: number };
content: any;
}>;
}): any {
const { columnSize, rowSize, cells = [] } = options;
// 生成表格ID
const tableId = `table_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const imageBlocks= Array<{
coordinate: { row: number; column: number };
localBlockId: string;
}>()
// 创建表格单元格
const tableCells = [];
const descendants = [];
for (let row = 0; row < rowSize; row++) {
for (let col = 0; col < columnSize; col++) {
const cellId = `table_cell${row}_${col}`;
// 查找是否有配置的单元格内容
const cellConfigs = cells.filter(cell =>
cell.coordinate.row === row && cell.coordinate.column === col
);
// 创建单元格内容
const cellContentBlocks = [];
const cellContentIds = [];
if (cellConfigs.length > 0) {
// 处理多个内容块
cellConfigs.forEach((cellConfig, index) => {
const cellContentId = `${cellId}_child_${index}`;
const cellContentBlock = {
block_id: cellContentId,
...cellConfig.content,
children: []
};
cellContentBlocks.push(cellContentBlock);
cellContentIds.push(cellContentId);
Logger.info(`处理块:${JSON.stringify(cellConfig)} ${index}`)
if (cellConfig.content.block_type === 27) {
//把图片块保存起来,用于后续获取该图片块的token
imageBlocks.push({
coordinate: cellConfig.coordinate,
localBlockId: cellContentId,
});
}
});
} else {
// 创建空的文本块
const cellContentId = `${cellId}_child`;
const cellContentBlock = {
block_id: cellContentId,
...this.createTextBlock({
textContents: [{ text: "" }]
}),
children: []
};
cellContentBlocks.push(cellContentBlock);
cellContentIds.push(cellContentId);
}
// 创建表格单元格块
const tableCell = {
block_id: cellId,
block_type: 32, // 表格单元格类型
table_cell: {},
children: cellContentIds
};
tableCells.push(cellId);
descendants.push(tableCell);
descendants.push(...cellContentBlocks);
}
}
// 创建表格主体
const tableBlock = {
block_id: tableId,
block_type: 31, // 表格块类型
table: {
property: {
row_size: rowSize,
column_size: columnSize
}
},
children: tableCells
};
descendants.unshift(tableBlock);
// 过滤并记录 block_type 为 27 的元素
Logger.info(`发现 ${imageBlocks.length} 个图片块 (block_type: 27): ${JSON.stringify(imageBlocks)}`);
return {
children_id: [tableId],
descendants: descendants,
imageBlocks:imageBlocks
};
}
}