把飞书云文档变成HTML邮件:问题挑战与解决历程

基于公司内部的飞书办公套件,早在去年6月,我们就建设了将飞书云文档转译成HTML邮件的能力,方便同学们在编写邮件文档和发送邮件时,都能有较好的体验和较高的效率。

当下问题

因此,我们对转译工具做了一次大改版和升级,对大部分常用文档块做了高度还原。

实现效果

经过我们的不懈努力,最终实现了较为不错的还原效果:

二、系统架构改版

飞书云文档结构

在展开我们如何做升级之前,先要简单了解下飞书云文档的信息结构(详情可参考官方API),在此仅做简单阐述。

TypeScript简要定义,一个平铺的文档块数组,根据block_id和parent_id确定各块的父子关系,从而形成一个树:

{/**文档块唯一标识。*/block_id:string;/**父块ID。*/parent_id:string;/**子块ID列表。*/children:string[];/**文档块类型。*/block_type:BlockType;/**页面块内容描述。*/page:{...};/**文本块内容描述。*/text:{...};/**标题1块内容描述。*/heading1:{...};/**有序列表块内容描述。*/ordered:{...};/**表格块内容描述。*/table:{...};//总计43个块定义。...}[];我们用思维导图简单举例,整个文档块的树结构大致是这样的,有些块根据缩进递进,会形成父子关系,有些块天然就会成为父块(比如表格、引用等):

旧版架构

那么我们初版转译工具是怎么做的呢,比较遗憾的是,由于当时需求的还原度诉求较低,我们的代码主要是复用现有部分实现,整体的架构设计可以用一个词概括,基本是面向过程编程:

上方的图:经过了一些抽取和封装,主流程核心代码仍有528行;下方的图:文档块核心转译渲染代码,基本没有写任何还原样式,通过Switch、Case来一个个渲染文档块。

新版架构设计

这次我们痛定思痛,势必要将转译工具的转译效果做到尽可能还原,也有了多位同学一起投入。因此首要思考和急需解决的问题来了:在老旧的架构下,如何才能做好代码扩展、多人协同、高效样式编写以及样式还原?

IoC与DI

是的,几乎一刹那,凭借过往丰富的多人协同以及项目经验,很快我们就想到了,这个事需要基于IoC的设计原则,并通过DI的方式来实现。

那么什么是IoC和DI呢,根据维基百科的解释:控制反转(InversionofControl,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度,其中最常见的方式叫做依赖注入(DependencyInjection,缩写为DI)。

这么说可能有点抽象,我们可以看下新版的架构设计,从中便能窥见其精妙:

可以看到,关键的文档块预处理和渲染器,在该架构中是反向依赖核心的createDocTranspiler了,与我们常识中的理解(文档转译渲染依赖各个块的预处理和渲染器)是相反的,这就是控制反转(IoC),通过这样的依赖倒置,我们能够把多人协同过程中,由各个同学负责开发的预处理器和渲染器的开发调试解耦出去,互不影响、互不依赖,且合码过程中基本没有代码冲突,大大提效了多人协同合作开发。同时由于实现的方式是依赖注入(DI),或者说注册,未来我们想要支持更加深水区的文档块,比如「画板」、「文档小组件」等,可以很方便地注册新的预处理器和渲染器,做增量且解耦的代码开发;如果想要取消对某一个文档块的渲染,直接unregister即可,由此也实现了文档块渲染的快速插拔和极高的可拓展性。

整个转译主干代码如下:

创建转译器,注册预处理器,注册渲染器

转译渲染,后处理,完成渲染。代码行数缩减到只有138行。

函数式编程

接下来我们将目光聚焦到核心函数createDocTranspiler中,这块是IoC架构的核心实现,根据维基百科描述,IoC是面向对象编程中的一种设计原则,那么我们真的是用面向对象的编程方式吗?

显然不是,我们是高标准的前端同学,在JavaScript编程中,面向对象编程显然不是社区推崇的设计原则,以React框架为例,早在React16.8版本,就推出了函数组件和Hooks编程,以取代较为臃肿的类组件编程,这些都是前端老生常谈的理念了,大家可以去Google深入学习函数式编程理念,在此不再赘述。

整个核心代码如下:

上方的图:内置的变量和函数,用于存储各种预处理器和渲染器,并实现文档树的递归渲染;下方的图:返回并暴露出去的函数,用于注册各种预处理器、渲染器,以及转译渲染。整个核心代码只有158行,非常精炼。

"CSS-in-JS"

然后再来说一下如此大量的样式还原工作,我们是如何实现的。由于我们要把文档树转译成最终的一个完整的HTML字符串,在模板字符串中写内联样式(style="width:100px;...")会非常痛苦,代码可读性会很差,开发调试的效率也会很低。

为了解决这个问题,我们立即想到了ReactCSSProperties的写法,并调研了一下它的源码实现,其实就是将CSSProperties中的驼峰属性名,转换成内联样式中连字符属性名,并额外处理了Webkit、ms、Moz、O等浏览器属性前缀,同时针对number类型的部分属性的值,转换时自动加上了px后缀。详细代码如下:

三、Outlook麻烦的兼容性问题

在改版系统架构后,我们先试着实现了一版有序列表和无序列表的解决方案,结果在测试中,我们得到了出乎所有人意料之外的结果:

原本文档的样子

网页版Outlook中的样子

Windows的Outlook中的样子

在网页版Outlook中,通过开发工具可以看到每一项的justify-content样式消失了,而在WindowsOutlook中,基本没什么样式还留着了。

Outlook糟糕的兼容性

我们之前从未编写过HTML邮件,也就完全没考虑过各个邮件客户端对HTML的兼容性问题。在网上找到一些资料后,我们被Outlook对HTML的兼容性之差所震惊。

首先,WindowsOutlook并没有一个自己的HTML渲染引擎,而是使用Word的渲染引擎去解析HTML。它不支持HTML5和CSS3,也就是说我们为了保证最大的兼容性,所有的飞书文档样式还原和文本解析都要用极为陈旧的技术去实现。

据官方文档所示,display、position、max-width、max-height等样式全都不兼容。

总的来说:

技术上的限制如此苛刻,就意味着在后面的开发中,我们还会遇到很多特定情况的兼容性问题。在这种情况下,为了最大限度地保证兼容性,我们决定及时止损,重新设计后面各个组件的实现方式,并将无序列表和有序列表的渲染方法推倒重来,再次编写。

四、各类型文档块的还原

首先,我们将转译工具原有的「一级标题」到「九级标题」美化为接近飞书文档的样子。我们需要梳理下将会获得的数据,来看看如何将它们转译为HTML。

标题块(heading1-9)

标题组件应该是实现难度最低的一个,一个标题组件的数据结构如下:

原版实现方式

在原版的转译工具中,我们编写了通用方法来处理文本内容的下划线、删除线、斜体、粗体、高亮色等进行处理,生成行间元素,然后在外部框上h1-h9。最终在后面加上它的子节点渲染结果。

新版实现方式

由于默认的heading样式无法满足还原度,且并没有处理对齐方式。我们将使用"div"制作heading组件,自行添加样式来还原飞书文档:

caseBlockType.HEADING1:{constblockH1=blockasHeadingBlock;constalign=blockH1.heading1.style.align;conststyles=makeHeadingStyles({type:block.block_type,align});text+=`${transpileTextElements(blockH1.block_id,blockH1.heading1.elements,isPreview,)}

`;//renderChildBlocks方法来渲染当前块的所有子节点。text+=renderChildBlocks(blockH1.block_id);break;}其中makeHeadingStyles是我们生成样式的方法,这样可以将各个组件的样式写成配置项,方便后续修改。新的样式中,我们着重对行高、行距、下划线距文字距离、对齐方式进行了调整:

//makeHeadingStyles方法的部分截取。exportfunctionmakeHeadingStyles(params:MakeHeadingStylesParams){const{type,align}=params;constbasicStyle:CSSProperties={lineHeight:1.4,letterSpacing:'-.02em',fontWeight:500,color:'#1f2329',textAlign:getTextAlignStyle(align||1),};letheadingStyles:CSSProperties={};switch(type){caseBlockType.HEADING1:headingStyles={fontSize:26,marginTop:26,marginBottom:10,...basicStyle,};break;//对Heading2-9的样式进行定义...//......//将样式对象转成行间样式字符串。returncreateInlineStyles<'headingStyles'>({headingStyles:headingStyles});}最后发邮件,测试一下生成的HTML的效果:

改版之前

改版之后

无序列表(bullet)与有序列表(ordered)

列表的数据结构与标题块大致相同,在此不再赘述。在原来的转译工具中,我们使用原生的"ul"和"li"来直接渲染无序列表,"ol""li"来渲染有序列表。我们顺序遍历兄弟节点的列表,为连续的bullet文档块的前后加上"ul""/ul",连续的ordered块前后加上"ol"和"/ol"。列表中的每一项,则渲染成"li"。

由于原生"ul"和"ol"的marker样式较丑,我们无法使用伪类元素等手段改善它的样式,为了方便,我们这次改版将自己维护列表的层级关系。

在飞书文档中,不同层级的列表,marker长得完全不同:

无序列表

有序列表

为了判断我们每个列表项要使用什么样的marker,首先我们需要对飞书给我们的数据进行预处理,为每个列表块标注它的层级和序号。

由于飞书API没有提供有序列表的序号,这个序号用户又可以随便更改,所以我们的思路是:如果有序列表中间被非空文档块以外的文本块截断,序号则重新开始计算。具体方法如下:

由于原来的方法中完全没有处理过文本块的缩进,我们根据飞书缩进的规律,为普通的文本块(表格、栅格等以外的文本块)在渲染子节点时为子节点的容器添加25px的padding-left。

接下来我们使用一个通用的方法为有序列表和无序列表渲染它们的marker。

/**渲染列表的标签。*/exportconstlistMarkRender=(type:ListType,block:DocBlock)=>{const{depth=1,order=1}=block;if(type===ListType.BULLET){conststyles=makeMarkerStyles(ListType.BULLET);letmarker:string;marker=按照深度,每三个一循环,依次为''、''、'';return`${marker}`;}else{conststyles=makeMarkerStyles(ListType.ORDERED);letmarkerGenerator:(num:number)=>number|string;markerGenerator=按照深度,每三个一循环,依次为数字、数字转小写字母、数字转罗马数字;return`${markerGenerator(order)}.`;}};对于无序列表,标号每三层一循环,顺序为''、''、''。对于有序列表,标号格式也是每三层一循环,顺序为阿拉伯数字、小写字母、罗马数字。

使用列表的标号渲染器渲染标号部分,然后简单的在"div"中将标号"span"和处理过样式的正文"span"组合。

可以看到,我们在满足使用的前提下以最高的兼容性比较完美的还原了飞书文档中的有序列表和无序列表。

待办事项

既然漂亮地还原了有序列表和无序列表,待办事项块就简单得多了。代办事项的具体的数据结构如下:

可以看到,待办事项的数据中包含了该条待办事项是否已完成的数据,从飞书文档的样式可以看出,已完成的条目会统一被划上删除线,并删除下划线样式。最终的渲染器和样式生成方法如下:

待办事项渲染器

consttodoRenderer:BlockRenderer=(block,isPreview,renderChildBlocks,_blocks)=>{consttodoBlock=blockasTodoBlock;const{align,done}=todoBlock.todo.style;constoriginTodoElements=todoBlock.todo.elements;constmarkerSrc=done'已完成标记图片地址':'未完成标记图片地址';conststyles=makeTodoStyles(align||1,done);constcheckedTodoElements=cloneDeep(originTodoElements);checkedTodoElements.forEach(element=>{为所有文本元素去掉下划线,添加删除线});lettext='';text+=` ${transpileTextElements(todoBlock.block_id,donecheckedTodoElements:originTodoElements,isPreview,)}

`;text+=renderChildBlocks(todoBlock.block_id,false);returntext;};最终呈现效果

表格(非电子表格)块

文档中另一个最重要的模块就是表格。表格是另一类比较特殊的文本块,他内部并不包含正文。整个表格实际上由三层文档块组合而成,它们的数据结构如下:

依据数据结构和我们的代码模式设计,我们需要使用嵌套的渲染器来实现表格的绘制。

表格渲染器(table块)

由于飞书API中清楚地提供了行数、列数以及列宽,我们可以较为轻松地绘制出大致的表格。这里的重点是要准确地处理合并单元格数据,将它们精准地使用在表格的每个标签上。表格渲染器的代码如下:

为了最大限度的兼容性,我们坚持能用标签属性设置的样式,就不使用CSS来设置。与列表的渲染不同,在表格中我们没有像列表渲染一样先预处理数据再生成DOM字符串,而是使用了在遍历中边处理数据边生成DOM字符串的方法。

在表格的渲染中,我们没有像之前的代码一样使用renderChildBlocks把所有子文档块都渲染出来添加进HTML字符串中,而是使用了新的renderSpecifyBlock方法,给定block_id来渲染特定的子文档块。

单元格容器渲染器(tablecell块)

单元格容器的渲染器则简单的多,他没有任何数据处理,只绘制一个容器用于承载内部的所有子节点,并在内部将单元格内的子节点渲染出来

consttableCellRenderer:BlockRenderer=(block,isPreview,renderChildBlocks,_blocks)=>{conststyles=makeTableCellStyles();return`${renderChildBlocks(block.block_id,true)}

`;};最终呈现效果

图片块

图片块理应也是一个很容易实现的文档块。但在实际处理过程中,由于飞书的API只提供图片源文件的宽高,并没有提供云文档中用户缩放过后的图片宽高,我们需要实现一个能满足绝大多数使用场景的图片缩放算法来尽可能还原文档中的图片样式。

图片块的数据结构如下:

限制图片大小

源文件的宽高一般都远大于图片在云文档中的实际宽高。我决定使用以下的方法来限制住图片在文档中的宽高:

上述算法的代码实现如下:

/**根据id找到块。*/functionfindNodeById(blocks:DocBlock[],id:string){returnblocks.find(b=>b.block_id===id);}/**检查当前块的父节点中有没有表格或栅格块。*/functioncheckIsInTable(blocks:DocBlock[],parentId:string){constparentNode=findNodeById(blocks,parentId);if(parentNode){if(WRAPPERS_LIKE_TABLE.includes(parentNode.block_type)){returntrue;}returncheckIsInTable(blocks,parentNode.parent_id);}returnfalse;}functionrestrictImageSize(width:number,height:number,maxWidth:number=820,maxHeight:number=780,):[number,number]{//宽和高按照长边缩放(高度大于宽度50px视为长图),并为缩放后的宽高向上取整。if(width>=height-50){if(width>maxWidth){return[maxWidth,Math.ceil(height*divide(maxWidth,width))];}}else{if(height>maxHeight){return[Math.ceil(width*divide(maxHeight,height)),maxHeight];}}return[width,height];}图片渲染器

constimageRenderer:BlockRenderer=(block,isPreview,_renderChildBlocks,blocks)=>{lettext='';constblockImage=blockasDocBlockImage;constalign=blockImage.image.align;constsrc=`"${isPreviewblockImage.image.base64Url:`\$\{${blockImage.block_id}\}`//实际发送时,用${block_id}作为占位符,给到服务端填充图片附件地址。}"`;const[width]=restrictImageSize(blockImage.image.width,blockImage.image.height);constisInTable=checkIsInTable(blocks,blockImage.parent_id);conststyles=makeImageStyles({width,align,isInTable});text+=`

`;returntext;};在预览的时候,我们将图片地址设为图片的base64,直接展示。最后传给后端的HTML字符串中,我们将图片地址设为一个占位符,供后端解析并转化为邮件附件地址。

使用表格来布局的几个文档块

由于WindowsOutlook对CSS的支持程度很差,我们在对一些复杂文档块进行排版布局的时候不能使用flex、grid等。且display和position属性在大多情况下也不会像预期那样正常生效。我们为了最大的兼容性只能使用表格来解决一切排版问题。代码块、高亮块、栅格等几个文档块就都遵循了这个思路,使用表格来解决排版。我们以最复杂的代码块作为代表来进行介绍。

代码块

飞书云文档中免不了会出现代码,所以较好的进行代码块的还原也是个重要的工作。代码块还原的一个难点就是数据的处理,首先介绍下代码块的数据结构:

理想的话,我们希望element中每一项为一行代码,我们挨个进行渲染即可。但实际上,element的内容和普通文本类似,只要文本的样式不变(比如设为斜体、加粗等),这些文本就都会被塞到同一个element项中。

举例说明,对于下列文档中的代码块,实际飞书API返回的代码只有两项element:

其中,最后一个大括号被单独拆成一项令人费解,不过好在代码块中,只要一项element的后面出现了另一项,那就一定意味着换行。这减少了我们的处理难度。

我们的大体思路,是将代码拆分成一个二维数组。第一维中的每一维度为一行代码,每行代码中的每一维度为拆分后零碎的代码块。我们先将所有的element中的内容根据换行符\n拆分成一个个细小的子块,同时将与HTML有关的字符替换成HTML编码,避免这些字符混入HTML字符串中被当做标签解析:

elements.forEach(element=>{consttextStyles=element.text_run.text_element_style;constelementSplit=(element.text_run.content||'').replaceAll('&','&').replaceAll('<','<').replaceAll('>','>').replaceAll('"','"').replaceAll("'",''').match(/(.*\n|.+)/g);elementSplit&&elementSplit.forEach(line=>{codeList.push({text_run:{content:line,text_element_style:textStylesasTextElementStyle,},});});});然后将这些子块按照换行符进行分组,变成我们需要的二维数组:

/**将拆分好的代码块列表按行进行分组。*/constgroupingCodeList=(list:TextElement[]=[])=>{constresult:TextElement[][]=[];letcurrentGroup:TextElement[]=[];list.forEach(item=>{//将当前字符串添加到当前分组。currentGroup.push(item);//如果字符串包含'\n',则结束当前分组,并准备开始新的分组。if(item.text_run.content.includes('\n')){result.push(currentGroup);currentGroup=[];}});//最后将currentGroup中剩余的项目加入result。if(currentGroup.length>0){result.push(currentGroup);}returnresult;};至此,我们知道了代码行数n和每行代码中的小代码块有哪些。我们要做的就是将它们放进一个n行2列的表格中

最终,代码块渲染器的代码如下。为了保证最大的兼容性,我们使用空的表格行作为内边距,尽量避免CSS解析问题:

行间公式

飞书云文档除文本外支持多种行间元素的插入,比如@文档、内联文件、内联公式等,在此我们介绍下最为复杂的内联公式是怎么处理的。

行间公式的数据位于各个文档块的内联块中,以文本块为例,具体数据如下:

我们要做的,就是将公式转换为图片,然后在邮件中将公式作为图片附件来处理。

我们将使用MathJax来将公式表达式转换为svg,用于用户预览。在发送时,我们将MathJax生成的svg通过cavans转化为png图片,上传到CDN,并将CDN地址给到后端,进行邮件附件转换。

公式的预处理方法如下:

functionallSvgsToImgThenUpload(svgObjList:SvgObj[]){//将每个SVG字符串映射到转换函数的调用上。constconversionPromises=svgObjList.map(svgObj=>svgToImgThenUpload(svgObj));//使用Promise.all等待所有图片完成转换和上传。returnPromise.all(conversionPromises);}核心的svgToImgThenUpload方法如下,它负责将svg转化为图片,并上传CDN:

/**svg转图片,并上传到OSS。*/functionsvgToImgThenUpload(svgObj:SvgObj):Promise<{id:string;url:string}>{returnnewPromise((resolve,reject)=>{const{width,height,id}=svgObj;constsvgString=svgObj.svg;if(!width||!height){reject(`公式svg大小获取失败:${id}`);return;}//生成svg的base64编码。constencodedString=encodeURIComponent(svgString).replace(/'/g,'%27').replace(/"/g,'%22');constdataUrl='data:image/svg+xml,'+encodedString;//使用canvas渲染svg并转为图片。constimage=newImage();image.onload=()=>{constcanvas=document.createElement('canvas');//为了保证图片清晰,渲染使用三倍宽高,实际大小使用两倍宽高。canvas.width=width*3;canvas.height=height*3;canvas.style.width=`${width*2}px`;canvas.style.height=`${height*2}px`;constctx=canvas.getContext('2d');ctx&&ctx.drawImage(image,0,0,width*3,height*3);//将canvas内容导出为Blob。canvas.toBlob(asyncblob=>{创建File对象并上传CDN,返回CDN链接;},'image/png');};image.onerror=reject;image.src=dataUrl;});}为了保证图片清晰,渲染使用三倍宽高,实际大小使用两倍宽高。

至此,我们让公式块带上了图片CDN地址。在发送时交给后端,转为邮件附件,即可正常显示了。

最终呈现效果

五、向前一步

好在最终我们克服了重重困难,终于来到了转译工具升级的Showcase环节。之前有提到我们有fallbackRenderer,主要用于针对未识别或者未支持的文档块,渲染其默认提示,最初我们渲染的效果只是一个简单的提示,比如:【画板暂不支持解析】这样的文案提示。

但是我们很快发现:1.这些提示并不明显,可以做一个类似AntdAlert的提示;2.在发送时要过滤掉这些提示,因为是无效信息;3.在预览时需要让用户能够看到实际的发送效果,需要有开关能隐藏这些提示;4.发送时存在这些不支持的块时,需要拦截提示用户是否去调整文档内容,以达到信息更全效果更好的发送效果。往往是这些细枝末节的体验与引导,能够真正抓住用户的心,让用户觉得这个转译工具是真的贴心、好用。

因此,我们快速增加了这些具体的引导与提示优化,具体效果如下:

六、大功告成

经过这一番波折,我们最终成功地将飞书云文档转译为兼容大多数客户端的HTML邮件。这不仅仅是一项技术上的挑战,更是一次心态和耐心的考验。

在这个过程中,我们深刻体会到在前端开发中,面对各种浏览器和客户端的不一致性时,需要的不仅仅是技术能力,还需要灵活应变和坚持不懈的精神。希望本文能为同样遇到这些问题的开发者提供一些思路和帮助。

感谢阅读!

引用:

*文/Nicolas、Asher

未经得物技术许可严禁转载,否则依法追究法律责任!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

THE END
1.飞书版本7.32.8 396万次下载 更新2024-12-05 大小284.00MB 安全认证 简介 飞书整合即时沟通、日历、音视频会议、在线文档、云盘、工作台等功能于一体,成就组织和个人,更高效、更愉悦。 更新日志 修复已知问题,优化客户端对新系统兼容性https://lestore.lenovo.com/detail/26353
2.下载飞书App及桌面客户端登录下载飞书免费试用 下载飞书客户端 同时支持 iOS,Android,iPadOS,macOS,Windows,Linux 版本 开发者:北京飞书科技有限公司应用权限隐私政策 想知道飞书更新了哪些内容?看看飞书更新日志 Windows 客户端 点击下载 macOS 客户端 点击下载 iOS & iPadOS 客户端 https://www.feishu.cn/download
3.飞书下载2024最新pc版飞书电脑版官方免费下载安装飞书电脑版软件特色 让会议更高效 告别反复沟通,会议中实时多人协同,讨论更高效 让目标更清晰 通过飞书OKR “看清”和“对齐”组织目标与关键结果, 让企业战略层层落地,让每个员工都能自驱前行。 灵活办公 无论电脑端还是手机端,都可以轻松发起或加入音视频会议,多个设备无缝切换,出差在外移动办公更便捷。 https://mip.onlinedown.net/soft/1227927.htm
4.飞书Windows客户端v7.26.6官方版飞书官方版是维境视讯推出的一款企业级IM产品,飞书官方版界面美观大方,功能强劲实用,可以连接企业已有系统,连接第三方系统,可以帮助企业快速开发稳定安全的内部应用。 软件截图 软件特点 自研内部应用 简单易用的开发环境,帮助企业快速开发稳定安全的内部应用。 https://www.puresys.net/4727.html
5.飞书官方版下载安装飞书Windows客户端v7.24.4免费版下载飞书Windows客户端是一款先进的团队办公软件,免费版的功能适用于100人以下的小型初创团队使用,拥有即时消息、云文档、视频会议、多维表格、免费邮箱等多种功能,足够团队日常的愉悦使用了。界面简洁,操作流畅不卡顿,使工作效率得到提高,适用于游戏、金融、消费等各个行业。 http://www.winwin7.com/soft/128029.html
6.飞书7.27.8Windows客户端飞书下载版本类型:Windows客户端 更新时间:2024-09-27 软件分类:办公软件 用户评分: 运行环境:WinAll 平台检测无插件360通过腾讯通过金山通过瑞星通过 很好(1) 50.0% 一般(0) 0.0% 很差(1) 50.0% 本地下载 飞书(Lark)是由字节跳动于2016年自研的新一代,一站式协作平台,是保障字节跳动全球五万人高效协作的办公工具。https://www.pc9.com/pc/info-4430.html
7.飞书云文档Windows客户端.exe软件安装包资源浏览查阅28次。飞书云文档Windows客户端.exe软件安装包飞书文档批量导出更多下载资源、学习资料请访问CSDN文库频道.https://download.csdn.net/download/qq_28802119/88355550
8.版本v6.5.8官方PC客户端,畅享高效办公体验软件下载飞书v6.5.8官方PC客户端由飞书开发商开发和维护。 支持的环境格式 飞书v6.5.8官方PC客户端支持Windows和Mac操作系统。 提供的帮助 飞书v6.5.8官方PC客户端提供了一系列功能和工具,帮助用户更高效地进行团队协作和沟通。它包括实时聊天、语音通话、视频会议、文件共享、日程安排、任务管理等功能,使团队成员能够方便地https://www.163987.com/soft/79330.html
9.Windows企业版客户端使用手册向日葵Windows企业版客户端具有多维度的安全保护机制,支持多平台远程运维,轻部署、易管理,适用于多种远控场景。 下面给大家介绍下如何使用。 1. 安装登录 (1)下载向日葵企业版客户端for Windows(点击下载) (2)下载后,双击程序安装 ①一键安装:按默认的文件路径安装企业版客户端程序,支持自定义安装路径 https://sunlogin.oray.com/news/10455.html
10.飞书电脑版下载飞书最新免费版下载飞书Windows客户端是一款智能化的企业协作办公平台,可以轻松实现企业人员的管理,一站式解决办公问题。用户在这里可以进行考勤、人力管理、互动沟通、在线会议以及云文档编辑等功能,还能自己添加日程和代办清单,工作任务清晰明了,可以极大的提高办公效率。有需要的朋友快来下载飞书Windows客户端试试吧! 飞书Windows客户端功能https://www.uzhuangji.net/dnrj/wlrj_52215.html
11.Rust移动开发与跨平台模式探究51CTO博客关于飞书使用 Rust 的公开资料很少,只有2019年字节跳动王枞在QCon分享的《Rust 跨平台客户端开发在字节跳动的实践》,从其中可以了解到,飞书使用 Rust 也是做跨平台组件。飞书客户端非 UI 部分由 Rust 跨平台实现,目前包括移动端和桌面端共 5 个平台。 https://blog.51cto.com/u_15683898/5426050
12.外接设备Zoom:Windows和Mac客户端 腾讯会议:Windows和Mac客户端 钉钉会议:Windows 客户端、Edge浏览器、Chrome浏览器;Mac客户端、Safar浏览器、Chrome浏览器 钉钉直播:Windows和Mac客户端 飞书会议:windows 客户端、Edge浏览器、Chrome浏览器;Mac客户端、Safari浏览器、 Chrome浏览器 Microsoft teams: Windows 客户端、Edge浏览器https://onlinemanual.insta360.com/ace/zh-cn/faq/compatibility/externaldevices
13.以太网客户端提示windows系统自带共享代理解决方法以太网客户端(Dr.COM)登陆出现windows系统自带共享代理,如下图: 系统win+R调出运行: 在服务中,找到Internet Connection Sharing (ICS): 右键属性,将启动方式改为禁用,并将服务状态停止,重新登陆以太网客户端就可以了。如果不行,将服务Internet Connection Sharing (ICS)的依存关系里的都关闭再试。 全部评论 推荐 最https://www.nowcoder.com/discuss/353147375764054016
14.免输密码全自动登录有道云笔记Windows客户端2024年8月22日; ; 免输入密码全自动登录有道云笔记Windows客户端 2024年8月22日 ; ; 因为有道云笔记可以批量全文搜索笔记文档的正文内容,所以我选择使用有道云笔记而不使用腾讯文档和金山文档。 ; ; 飞书也可以批量全文搜索文档文件的正文内容,但是为了集中统一管理和备份数据只能用一个笔记软件。 ; 飞书的应用宝下载量没有有道https://www.autoahk.com/archives/49477
15.如何通过网页加入飞书视频会议?* 获取会议链接后,即可通过网页轻量高速加入飞书视频会议,无需下载飞书 App。 一、功能要求 操作系统要求:仅支持 Windows、Mac、Linux Ubuntu (16+, X64) 系统浏览器要求:支持 Chrome、360、Microsoft Edge、Safari、Firefoxhttps://www.work.sany.com.cn/hc/zh-CN/articles/360049067679
16.ToDesk远程工具ToDesk Windows客户端全面支持Win11、Win10、Win8、Win7等操作系统标签:团队协作ToDesk 远程工具链接直达">手机查看 热门网址 TeamViewer远程工具 石墨文档 有道云笔记 腾讯文档 飞书云文档ToDesk远程工具首次上手使用,效果还是不错的。使用过程不太稳定,经常会有连接不上的情况发生,后来研究琢磨了下,是因为没更新软件原因https://www.eimm.cn/sites/2492.html
17.C++应用软件开发从入门到精通详解在Windows系统中,大多数客户端软件都是用C++开发的,当然也有少部分软件是用C#等语言实现的。我们日常使用的Windows桌面版的QQ、微信、腾讯会议、企业微信、字节飞书、阿里钉钉、百度网盘、迅雷、Chrome浏览器、Foxmail邮箱、远程软件向日葵/ToDesk等客户端软件,都是用C++开发实现的,C++是当前Windows平台开发应用软件的主流http://www.zsrm.cn/news/165779.html
18.马斯克又跨界了!这次是餐饮业;飞书People正式发布丨大公司动态用户可以将此前从微软应用商店中下载的程序自动安装到他们的新Windows设备上。这个功能也将帮助开发者保留他们的客户,而不必提醒客户重新下载他们的应用程序。”(财联社) 【互联网】 搜狐员工遭工资补助诈骗,企业IT系统弱点暴露,张朝阳回应 5月25日,搜狐公司董事局主席兼CEO张朝阳对外回应此事,称“事情不像大家想象那么https://www.yicai.com/news/101424115.html
19.百度输入法资源下载站官方软件下载基地抖音电脑客户端 飞书 PDF编辑器(MasterPDFEditor) PDF工具箱 PDFShaper 夸克网盘 腾讯文档 永中Office个人版 口袋系统(WinToGo) 傲梅远程桌面 品牌电脑官网驱动 荐片高清影音 AcFun直播伴侣 AcFun面捕助手 快手直播伴侣 多多云手机 PPSSPP(PSP 模拟器) 哔哩哔哩PC客户端 凤凰游戏盒子 DOSBohttps://m.039m.com/show/836.html
20.CRM管理系统APP下载CRM下载CRMAPP下载CRM系统下载悟空CRM客户端下载 适配多种设备,支持多种下载方式,为您提供高质量的使用场景 IOS版 立即下载 Android版 立即下载 小程序版 扫码打开 企业微信版 扫码打开 Windows版 V12 立即下载 Mac OS X版 V12 立即下载 支持企业微信、钉钉、飞书集成 正式支持企业微信、钉钉、飞书集成,您可以将悟空CRM的应用安装到企业微信、https://www.5kcrm.com/client/
21.remoteAPP部分程序在客户端使用异常在rdp连接下,您可以将本地文件复制到远程主机上,但不可直接粘贴到应用程序(微信、飞书、word等)内。如果您需要将文件粘贴到应用程序中,您可以将文件复制到远程主机的桌面或其他本地位置,然后再将其复制到应用程序中。 在remoteapp使用时,您可以使用ctr?c -v 的操作将文件发送到目标程序内。如果您无法通过拖拽上https://learn.microsoft.com/zh-cn/answers/questions/1514106/remote-app