@@ -300,27 +300,124 @@ const items = [
300300*图:构建总结流程图*
301301
302302#### 1. 我们当前的设计是将文件夹树的构建和文档的分配分离,具体来说:
303- 后端只负责构建文件夹的树形结构(通过递归查询文件夹表),前端在拿到文件夹树和文档列表(可能是单独请求的 )后,通过` buildFolderDocumentTree` 方法将文档分配到对应的文件夹节点(包括根节点) 而另一种常见的做法是:在后端构建树的时候同时查询文档,将文档作为叶子节点直接构建到树中。
303+ 后端只负责构建文件夹的树形结构(通过递归查询文件夹表),前端在拿到文件夹树和文档列表(单独请求的 )后,通过` buildFolderDocumentTree` 方法将文档分配到对应的文件夹节点(包括根节点) 而另一种常见的做法是:在后端构建树的时候同时查询文档,将文档作为叶子节点直接构建到树中。
304304#### 2. 当前设计的好处:
305- 1. 职责分离,结构清晰:
305+ 1. * 职责分离,结构清晰* :
306306- 文件夹树和文档属于不同的数据类型,分开处理使得逻辑更清晰。
307- - 文件夹树的结构相对稳定(嵌套关系),而文档可能频繁变动(如重命名、移动、删除)。 将文档处理放在前端,可以避免每次文件夹结构变动都要重新构建整个树 (包括文档)。
308- 2. 减少后端计算压力:
307+ - 将文档处理放在前端,可以避免每次文档结构变动都要重新构建整个树 (包括文档)。
308+ 2. * 减少后端计算压力* :
309309- 如果一起构建,后端需要同时处理文件夹和文档的递归,尤其是在文档数量很大的情况下,构建整个树的开销会很大。
310310- 当前设计下,后端只处理文件夹(通常数量远少于文档),文档的处理放在前端,利用浏览器的计算能力。
311- 3. 灵活性:
312- - 前端可以独立获取文档列表(例如分页、按条件过滤 ),然后根据需求动态地分配到文件夹树中。如果文档列表需要更新(比如用户搜索),我们只需要重新运行` buildFolderDocumentTree` 方法,而不需要重新请求整个文件夹树。
311+ 3. * 灵活性* :
312+ - 前端可以独立获取文档列表(例如按条件过滤 ),然后根据需求动态地分配到文件夹树中。如果文档列表需要更新(比如用户搜索),我们只需要重新运行` buildFolderDocumentTree` 方法,而不需要重新请求整个文件夹树。
313313- 如果文档和文件夹树一起构建,那么每次文档更新(比如新增一个文档)都需要重新构建整个树并返回,这在文档数量大时效率低下。
314- 4. 减少数据传输量:
315- - 假设一个场景:用户只是展开了一个文件夹,需要加载该文件夹下的文档。如果采用分离的方式,我们可以只请求该文件夹下的文档,然后动态添加到已有的树中。而如果一开始就把所有文档都构建在树中,那么初始加载的数据量会非常大。
316- 5. 支持懒加载:
317- - 当前设计可以轻松实现文档的懒加载。例如,一开始只加载文件夹树,当用户展开某个文件夹时,再去请求该文件夹下的文档。这样可以显著提高初始加载速度。
318- 6. 处理根级文档更灵活:
319- - 根级文档(没有父文件夹)在当前的分离设计中,可以通过一个特殊的` ROOT ` 键来存放,这样在展示时,前端可以自由决定如何展示根级文档(比如在文件夹树的最上方或最下方单独展示)。
320- 7. 避免重复数据:
321- - 在树形结构中,每个节点(文件夹或文档)都需要独立表示。如果文档数量庞大,那么构建的树会非常大。而分离处理允许我们只传输一次文档列表,然后在多个地方引用(例如,在文件夹树中引用文档,在最近文档列表中也可以引用同一份文档数据)。
322- 8. 更新效率:
323- - 当文档发生变化(如移动)时,我们只需要更新文档的` parentFolderIds` ,然后在前端重新运行` buildFolderDocumentTree` 即可,不需要重新构建整个文件夹树。
314+ 4. *支持文档懒加载*:
315+ - 当前设计可以轻松实现文档的懒加载。例如,一开始只加载文件夹树,当用户展开某个文件夹时,再去请求该文件夹下的文档,然后动态添加到已有的树中。这样可以显著提高初始加载速度。而如果一开始就把所有文档都构建在树中,那么初始加载的数据量会非常大。
316+
317+ #### 1.3 生成唯一默认文件名/文件夹名
318+
319+
320+ ##### 1.3.1 逻辑步骤
321+ **(1) 收集现有名称**:
322+
323+ - 创建一个 Set 集合 existingNames 存储所有不可重复的名称。
324+
325+ - 添加额外名称:遍历 additionalNames 参数,将非空名称添加到集合中。
326+
327+ - 递归查找目标文件夹
328+
329+ - 处理根目录:若 targetKey === 'root',直接查找根节点(key='root')的子项名称。
330+
331+ **(2) 生成唯一名称**:
332+
333+ - 初始化计数器 counter = 1,生成候选名称 candidateName = baseName + counter。
334+
335+ - 循环检查:若 candidateName 在 existingNames 中存在,则 counter++ 并生成新名称。
336+
337+ - 返回结果:直到找到不在集合中的名称,返回最终的 candidateName。
338+
339+ ##### 1.3.2 关键代码:
340+ ` ` ` javascript
341+ /**
342+ * 生成唯一的默认文件名/文件夹名,避免同级目录下的重复
343+ * @param {string} baseName - 基础名称,如"新建文档"或"新建文件夹"
344+ * @param {string} targetKey - 目标文件夹的key
345+ * @param {Array} additionalNames - 额外需要检查的名称列表(如后端最新数据)
346+ * @returns {string} 唯一的名称
347+ */
348+ const generateUniqueDefaultName = (
349+ baseName,
350+ targetKey,
351+ additionalNames = [],
352+ ) => {
353+ // 获取目标文件夹的现有子项
354+ const getExistingNames = () => {
355+ const existingNames = new Set ();
356+
357+ // 添加额外的名称列表(来自后端的最新数据)
358+ additionalNames .forEach (name => {
359+ if (name) existingNames .add (name);
360+ });
361+
362+ // 递归查找目标文件夹及其子项
363+ const findTargetFolderItems = (nodes , key ) => {
364+ for (const node of nodes) {
365+ if (node .key === key) {
366+ // 找到目标文件夹,收集其子项名称
367+ if (node .children ) {
368+ node .children .forEach (child => {
369+ const name = child .label ? .props ? .text || child .label ;
370+ if (name) {
371+ existingNames .add (name);
372+ }
373+ });
374+ }
375+ return true ;
376+ }
377+ if (node .children ) {
378+ if (findTargetFolderItems (node .children , key)) {
379+ return true ;
380+ }
381+ }
382+ }
383+ return false ;
384+ };
385+
386+ // 如果是根目录
387+ if (targetKey === ' root' ) {
388+ const rootFolder = folderList .find (item => item .key === ' root' );
389+ if (rootFolder && rootFolder .children ) {
390+ rootFolder .children .forEach (child => {
391+ const name = child .label ? .props ? .text || child .label ;
392+ if (name) {
393+ existingNames .add (name);
394+ }
395+ });
396+ }
397+ } else {
398+ findTargetFolderItems (folderList, targetKey);
399+ }
400+
401+ return existingNames;
402+ };
403+
404+ const existingNames = getExistingNames ();
405+
406+ console .log (' 现有名称集合:' , [... existingNames]);
407+
408+ // 从1开始尝试生成唯一名称
409+ let counter = 1 ;
410+ let candidateName = ` ${ baseName}${ counter} ` ;
411+
412+ while (existingNames .has (candidateName)) {
413+ counter++ ;
414+ candidateName = ` ${ baseName}${ counter} ` ;
415+ }
416+
417+ console .log (' 生成的唯一名称:' , candidateName);
418+ return candidateName;
419+ };
420+ ` ` `
324421
325422## 2. slate中编辑器代码块高亮
326423
0 commit comments