美文网首页小程序前端应用
利用Editor.Js 编辑器生成多端报告页面

利用Editor.Js 编辑器生成多端报告页面

作者: 思考蛙 | 来源:发表于2021-07-14 17:03 被阅读0次
    editjs-Cover.png

    背景:

    由于我们的客户的业务中有一些生成报告的功能,报告制作需要一定的富文本支持,并且支持PC端、小程序端的展示。但报告的模板并不是固定的,会有一些个性化比如段落的样式、文字的高亮、可交互图表、表格等。这样的要求就给我们的开发产生了一些非常规要求:

    需求

    • 可视化编辑
    • PC端可预览
    • 小程序可适配UI设计模板
    • 丰富的组件展示、文本格式化
    image.png

    解决方案

    数据结构设计

    针对这个需求,我们首先考虑的是数据的格式,因为一般情况下我们做一些页面富文本展示,直接的数据格式为html就可以,但是需求上面是小程序端展示、非固定模板又可能会有一定的交互需求。一开始我们的选型是使用 meta+ markdown 方案,在小程序端去识别meta 信息处理逻辑,解析 markdown 处理格式,但这样的操作会给PC端的使用者造成困惑,要求也比较高需要他们清楚逻辑信息,这个对使用者显然很不友好。

    由于我们以前做过一些基于 block 格式数据处理的业务,所谓 block 其实逻辑上面比较简单,将内容由原来的文本或富文本,转换为 json + field 格式,很多在线定制表单的数据格式就是这样的。所以我们只需要先定义一套可以满足报告展示并能描述报告逻辑的 json 格式即可。

    思路是这样的,但还是需要解决PC端编辑器的问题,就想到了这款完全基于 block 样式的编辑器,并能直接输入 JSON格式数据,非常适合我们的需求。

    编辑器选型

    Editor.js 是一个块样式的编辑器,https://editorjs.io/,该编辑器是以块作为基本元素来的,每次编辑都是在一个块上进行编辑,主要的特点是,可以输出简洁的 JSON 而不是传统的html格式,当然也可以格式化为 html或小程序端支持的 wxml。并且可自定义插件,操作简单快捷。

    试用了下编辑和输出,的确结果是我们想要的样子:

    image.png

    剩下的事情况就相对简单了,先设计一套可以在前端展示的 blocks,可以在中间加入一些逻辑标识,然后在服务端做解析处理,PC端访问时生成 html,小程序访问时输入 wxml,以及原始的 blocks。

    服务端解析

    由于 editorjs 只是一个客户端编辑器,服务端的处理是全都交给用户自己处理的,所以我们需要一个 editorjs 输入的 json 格式解析器,并将数据进行格式化才可以满足需求。

    我们的解析器只做两件事,一个是接收数据进行解析,另一个是进行格式化。

    transform.ts 文件中主要有一些类型定义和接口

    转换器定义

    export type transforms = {
        [key: string]: any;
        delimiter(): string;
        header(block: block): string;
        paragraph(block: block): string;
        list(block: block): string;
        image(block: block): string;
        quote(block: block): string;
        code(block: block): string;
        embed(block: block): string;
        warning(block: block): string;
        keypoint(block: block): string;
        table(block: block): any;
        component(block: block): any;
        widget(block: block): any;
        chart(block: block): any;
    };
    

    block格式定义

    export type block = {
        type: string;
        data: {
            text?: string;
            level?: number;
            caption?: string;
            file?: {
                url?: string;
            };
            stretched?: boolean;
            withBackground?: boolean;
            withBorder?: boolean;
            items?: string[];
            style?: string;
            code?: string;
            service?: 'vimeo' | 'youtube';
            source?: string;
            embed?: string;
            width?: number;
            height?: number;
            title: string;
            message: any;
            content?: any;
            value?: any;
            name?: string;
            template?: string;
            props?: any;
        };
    };
    

    格式化为 html

    const transformsHtml: transforms = {
        delimiter: () => {
            return `<br/>`;
        },
    
        header: ({ data }) => {
            return `<h${data.level}>${data.text}</h${data.level}>`;
        },
    
        paragraph: ({ data }) => {
            return `<p>${data.text}</p>`;
        },
    ...
    

    格式化为 wxml

    const transformsWxml: transforms = {
        delimiter: () => {
            return `<view class="br"/>`;
        },
    
        header: ({ data }) => {
            return `<view class="h${data.level}">${data.text}</view>`;
        },
    
        paragraph: ({ data }) => {
            let text = data?.text?.replace(/<[\/]?(b)([^<>]*)>/g, (m, m1) => {
                return m.replace('b', 'text');
            });
            if (text) {
                text = text.replace(/<[\/]?(mark)([^<>]*)>/g, (m, m1) => {
                    return m.replace('mark', 'text');
                });
            }
            return `<view class="paragraph">${text}</view>`;
        },
    ...
    

    index.ts 接口实现导出
    ** 定义接口**

    type parser = {
        parse(OutputData: OutputData): string[];
        parseStrict(OutputData: OutputData): string[] | Error;
        parseBlock(block: block): string;
        validate(OutputData: OutputData): string[];
    };
    

    parseWxml

    const wxmlParser = (plugins = {}): parser => {
        const parsers = Object.assign({}, transformsWxml, plugins);
    
        return {
            parse: ({ blocks }) => {
                return blocks.map(blockItem => {
                    return parsers[blockItem.type]
                        ? parsers[blockItem.type](blockItem)
                        : ParseFunctionError(blockItem.type);
                });
            },
    
            parseBlock: blockItem => {
                return parsers[blockItem.type]
                    ? parsers[blockItem.type](blockItem)
                    : ParseFunctionError(blockItem.type);
            },
    ...
    

    parseHtml

    const htmlParser = (plugins = {}): parser => {
        const parsers = Object.assign({}, transformsHtml, plugins);
    
        return {
            parse: ({ blocks }) => {
                return blocks.map(blockItem => {
                    return parsers[blockItem.type]
                        ? parsers[blockItem.type](blockItem)
                        : ParseFunctionError(blockItem.type);
                });
            },
    
            parseBlock: blockItem => {
                return parsers[blockItem.type]
                    ? parsers[blockItem.type](blockItem)
                    : ParseFunctionError(blockItem.type);
            },
    ...
    
    export { htmlParser, wxmlParser };
    

    这样就完成了一个简单的 editorjs 解析器,我们将它打包成了一个包,在报告的具体业务类中就可以直接使用

    import { wxmlParser } from '@caixie/editorjs-parser';
    ...
    
            const parser = wxmlParser();
            const reportOutputContent = this.reportDqiReport.content;
            const reportResult = parser.parse(this.reportDqiReport.content);
            const mealTime = new Date(format(new Date(date), 'yyyy-MM-dd'));
            const source = {
                outputData: reportOutputContent,
                rawData: {
                    ...foodLog.dietAnalysis,
                },
            };
            return new UserReport({
                type: ReportType.DQI_DAILY,
                user: {
                    id: userId,
                },
                date: mealTime,
                // creator: ctx.session.user
                content: new UserReportContent(
                    reportResult,
                    { dqiScore: Math.ceil(dqiScoreValue), mealTime },
                    '评估报告',
                    source,
                ),
            });
    ... 
    

    总结

    本文介绍了一款基于块内容的 editor.js 编辑器,并利用它解决一个针对多场景(PC端、小程序端)、个性化报告需求的实现,包含 editorjs的特点以及服务端的解析处理。希望对有想用基于块内容(block)编辑需求的朋友所帮助。当前市面上 editrojs 应该是最好的基于 block 实现的编辑器。类似 notition、国产的我来这些商业的笔记服务都是基于 block 的编辑器,只是大家的方案各有不同。

    相关文章

      网友评论

        本文标题:利用Editor.Js 编辑器生成多端报告页面

        本文链接:https://www.haomeiwen.com/subject/xapypltx.html