diff --git a/index.ts b/index.ts index 3d7e6cebe17b0fb054f4cd350ac68dd820c130b2..61cbb03f7b76684a60b01aab6adb6d7414f75ad6 100644 --- a/index.ts +++ b/index.ts @@ -1,3 +1,275 @@ -const s: string = "欢迎来到 InsCode"; -console.log(s); -tsx 函数式 返回类型 JSX.Element +/** + * 内容块类型定义(最小不可拆分单元,如段落、图片、表格等) + */ +interface ContentBlock { + id: string; // 内容块唯一标识 + type: 'paragraph' | 'image' | 'table'; // 内容块类型 + height: number; // 内容块渲染高度(px) + content: string; // 内容块实际内容(简化示例) +} + +/** + * 区域(Region)类型定义(管理连续的1-2页内容) + */ +interface Region { + id: string; // 区域唯一标识 + startPage: number; // 起始页号 + endPage: number; // 结束页号(startPage <= endPage <= startPage + 1) + contentBlocks: ContentBlock[]; // 包含的内容块 + totalHeight: number; // 区域内所有内容块总高度(px) + prevRegionId?: string; // 上一个区域ID(形成链表) + nextRegionId?: string; // 下一个区域ID +} + +/** + * 区域管理器(核心控制器,处理区域拆分、合并、页号更新等逻辑) + */ +class RegionManager { + private regions: Map; // 存储所有区域(ID -> 区域) + private pageHeight: number; // 单页高度(px,如A4约842px) + private maxPagesPerRegion: number = 2; // 每个区域最多包含的页数 + + constructor(pageHeight: number = 842) { + this.regions = new Map(); + this.pageHeight = pageHeight; + } + + /** + * 创建初始区域(文档首次加载时调用) + * @param initialBlocks 初始内容块列表 + */ + createInitialRegions(initialBlocks: ContentBlock[]): void { + let currentRegion: Region = this.createEmptyRegion(1); // 从第1页开始 + let currentTotalHeight = 0; + + for (const block of initialBlocks) { + currentTotalHeight += block.height; + currentRegion.contentBlocks.push(block); + currentRegion.totalHeight = currentTotalHeight; + + // 若当前区域总高度超过最大容量(maxPagesPerRegion页),拆分区域 + if (currentTotalHeight > this.pageHeight * this.maxPagesPerRegion) { + const newRegion = this.splitRegion(currentRegion); + this.regions.set(currentRegion.id, currentRegion); + currentRegion = newRegion; + currentTotalHeight = currentRegion.totalHeight; // 重置为新区域的初始高度 + } + } + + // 添加最后一个区域 + this.regions.set(currentRegion.id, currentRegion); + } + + /** + * 向指定区域添加内容块(模拟编辑操作) + * @param regionId 目标区域ID + * @param block 要添加的内容块 + */ + addBlockToRegion(regionId: string, block: ContentBlock): void { + const region = this.regions.get(regionId); + if (!region) throw new Error(`Region ${regionId} not found`); + + // 添加内容块并更新区域总高度 + region.contentBlocks.push(block); + region.totalHeight += block.height; + + // 检查是否溢出,若溢出则拆分 + if (region.totalHeight > this.pageHeight * this.maxPagesPerRegion) { + const newRegion = this.splitRegion(region); + this.regions.set(region.id, region); // 更新原区域 + this.regions.set(newRegion.id, newRegion); // 添加新区域 + this.updateSubsequentRegions(newRegion); // 更新后续区域页号 + } else { + this.regions.set(regionId, region); // 未溢出,直接更新 + } + } + + /** + * 拆分溢出的区域 + * @param overflowRegion 溢出的区域 + * @returns 拆分后生成的新区域 + */ + private splitRegion(overflowRegion: Region): Region { + // 1. 计算拆分点:找到第一个使累计高度 >= 单页高度的内容块 + let accumulatedHeight = 0; + let splitIndex = -1; + for (let i = 0; i < overflowRegion.contentBlocks.length; i++) { + const block = overflowRegion.contentBlocks[i]; + accumulatedHeight += block.height; + if (accumulatedHeight >= this.pageHeight) { + splitIndex = i; + break; + } + } + + if (splitIndex === -1) { + // 特殊情况:所有内容块总高度 < 单页高度(理论上不会触发,因已判断溢出) + splitIndex = overflowRegion.contentBlocks.length - 1; + } + + // 2. 拆分内容块(前半部分保留在原区域,后半部分移到新区域) + const [originalBlocks, newBlocks] = [ + overflowRegion.contentBlocks.slice(0, splitIndex + 1), + overflowRegion.contentBlocks.slice(splitIndex + 1), + ]; + + // 3. 更新原区域 + const originalHeight = originalBlocks.reduce((sum, b) => sum + b.height, 0); + overflowRegion.contentBlocks = originalBlocks; + overflowRegion.totalHeight = originalHeight; + overflowRegion.endPage = overflowRegion.startPage; // 原区域缩减为1页 + + // 4. 创建新区域(承接原区域的后续内容) + const newRegionId = `region-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; + const newRegion: Region = { + id: newRegionId, + startPage: overflowRegion.startPage + 1, // 新区域起始页为原区域起始页+1 + endPage: overflowRegion.startPage + 1, // 初始为1页,后续可能扩展 + contentBlocks: newBlocks, + totalHeight: newBlocks.reduce((sum, b) => sum + b.height, 0), + prevRegionId: overflowRegion.id, + nextRegionId: overflowRegion.nextRegionId, // 继承原区域的下一个区域 + }; + + // 5. 更新原区域的下一个区域关联 + if (overflowRegion.nextRegionId) { + const nextRegion = this.regions.get(overflowRegion.nextRegionId); + if (nextRegion) { + nextRegion.prevRegionId = newRegionId; + this.regions.set(nextRegion.id, nextRegion); + } + } + overflowRegion.nextRegionId = newRegionId; + + return newRegion; + } + + /** + * 更新后续区域的页号(因拆分导致页码偏移时调用) + * @param newRegion 新生成的区域 + */ + private updateSubsequentRegions(newRegion: Region): void { + let currentRegionId = newRegion.nextRegionId; + const pageOffset = newRegion.endPage - newRegion.startPage + 1; // 新区域占用的页数 + + while (currentRegionId) { + const region = this.regions.get(currentRegionId); + if (!region) break; + + // 后续区域的页号整体后移 + region.startPage += pageOffset; + region.endPage += pageOffset; + + currentRegionId = region.nextRegionId; // 继续处理下一个区域 + this.regions.set(region.id, region); + } + } + + /** + * 尝试合并相邻区域(当内容减少时) + * @param regionId 目标区域ID + */ + tryMergeRegions(regionId: string): void { + const currentRegion = this.regions.get(regionId); + if (!currentRegion) return; + + // 检查下一个区域是否可合并 + if (currentRegion.nextRegionId) { + const nextRegion = this.regions.get(currentRegion.nextRegionId); + if (nextRegion) { + const totalHeight = currentRegion.totalHeight + nextRegion.totalHeight; + // 若合并后总高度 <= 最大容量(2页),则合并 + if (totalHeight <= this.pageHeight * this.maxPagesPerRegion) { + this.mergeRegions(currentRegion.id, nextRegion.id); + } + } + } + } + + /** + * 合并两个相邻区域 + * @param regionAId 前一个区域ID + * @param regionBId 后一个区域ID(必须是regionA的nextRegion) + */ + private mergeRegions(regionAId: string, regionBId: string): void { + const regionA = this.regions.get(regionAId); + const regionB = this.regions.get(regionBId); + if (!regionA || !regionB || regionA.nextRegionId !== regionBId) return; + + // 1. 合并内容块和高度 + regionA.contentBlocks = [...regionA.contentBlocks, ...regionB.contentBlocks]; + regionA.totalHeight = regionA.totalHeight + regionB.totalHeight; + regionA.endPage = regionB.endPage; // 结束页更新为regionB的结束页 + regionA.nextRegionId = regionB.nextRegionId; // 继承regionB的下一个区域 + + // 2. 更新原regionB的下一个区域关联 + if (regionB.nextRegionId) { + const nextRegion = this.regions.get(regionB.nextRegionId); + if (nextRegion) { + nextRegion.prevRegionId = regionAId; + this.regions.set(nextRegion.id, nextRegion); + } + } + + // 3. 删除regionB + this.regions.delete(regionBId); + this.regions.set(regionAId, regionA); + } + + /** + * 创建空区域 + * @param startPage 起始页号 + * @returns 空区域实例 + */ + private createEmptyRegion(startPage: number): Region { + return { + id: `region-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, + startPage, + endPage: startPage, // 初始为1页 + contentBlocks: [], + totalHeight: 0, + }; + } + + /** + * 获取所有区域信息(用于调试/展示) + */ + getRegions(): Region[] { + return Array.from(this.regions.values()); + } +} + + +// ------------------------------ +// 示例使用 +// ------------------------------ +function demo() { + // 1. 初始化区域管理器(单页高度设为800px) + const manager = new RegionManager(800); + + // 2. 创建初始内容块(模拟一篇长文档,包含多个段落) + const initialBlocks: ContentBlock[] = [ + { id: 'b1', type: 'paragraph', height: 300, content: '第一段内容...' }, + { id: 'b2', type: 'paragraph', height: 400, content: '第二段内容...' }, + { id: 'b3', type: 'paragraph', height: 500, content: '第三段内容...' }, + { id: 'b4', type: 'paragraph', height: 300, content: '第四段内容...' }, + ]; + + // 3. 创建初始区域 + manager.createInitialRegions(initialBlocks); + console.log('初始区域状态:', manager.getRegions()); + + // 4. 向第一个区域添加内容块(触发拆分) + const firstRegionId = manager.getRegions()[0].id; + manager.addBlockToRegion(firstRegionId, { + id: 'b5', type: 'image', height: 700, content: '一张大图...' + }); + console.log('添加内容块后的区域状态:', manager.getRegions()); + + // 5. 尝试合并区域(模拟删除内容后触发) + manager.tryMergeRegions(firstRegionId); + console.log('尝试合并后的区域状态:', manager.getRegions()); +} + +// 执行示例 +demo();