【阿里低代码引擎实战】— 保存为区块功能

【阿里低代码引擎实战】--- 保存为区块功能

上一篇在低代码项目中实现了表单编辑与回显功能,上篇指路:juejin.cn/post/735238...

这篇我们来实现将页面多个组件组合成一个区块,保存后可以作为新的组件重复拖拽使用的功能;

本篇完整代码github地址: github.com/JoeXu727/al...

github.com/JoeXu727/lo...

一. 在操作辅助区添加"保存为区块"

  1. 期望达到的效果是在操作辅助区多一个+号按钮,点击后会出现弹框,输入一些区块的基本信息后即可保存为区块,如下图;

  2. 前端关键代码实现:在src下新建 actions/block.tsx:

typescript 复制代码
    src/actions/block.tsx
    ​
    import * as React from 'react';
    import { default as html2canvas } from 'html2canvas';
    import { Node } from '@alilc/lowcode-engine';
    import { Dialog, Form, Input } from '@alifd/next';
    import { createBlock } from '../services/mockService';
    ​
    import './index.scss';
    ​
    const FormItem = Form.Item;
    ​
    interface SaveAsBlockProps {
        node: Node;
    }
    ​
    const SaveAsBlock = (props: SaveAsBlockProps) => {
        const { node } = props;
        const [src, setSrc] = React.useState();
        React.useEffect(() => {
            const generateImage = async () => {
                let dom2 = node.getDOMNode();
                const canvas = await html2canvas?.(dom2, { scale: 0.5 });
                const dataUrl = canvas.toDataURL();
                setSrc(dataUrl);
            };
    ​
            generateImage();
        }, []);
    ​
        const save = async (values) => {
            const { name, title } = values;
            const { schema } = node;
    ​
            if (!name || !title) {
                return;
            }
    ​
            await createBlock({
                schemaId: schema.id,
                name,
                title,
                schema: JSON.stringify(schema),
                screenshot: src,
            })
        }
    ​
        return <div style={{ width: '450px' }}>
            <Form colon>
                <FormItem
                    name="name"
                    label="名称"
                    labelCol={{ span: 4 }}
                    wrapperCol={{ span: 16 }}
                    required
                    requiredMessage='please input name'>
                    <Input />
                </FormItem>
                <FormItem
                    name="title"
                    label="简介"
                    labelCol={{ span: 4 }}
                    wrapperCol={{ span: 16 }}
                    required
                    requiredMessage='please input title'>
                    <Input />
                </FormItem>
                <FormItem
                    name="screenshot"
                    label="缩略图"
                >
                    <div className='block-screenshot'>
                        <img src={src} />
                    </div>
                    <Input value={src} style={{ display: 'none' }} />
                </FormItem>
                <FormItem
                    label="" colon={false}>
                    <Form.Submit
                        type="primary"
                        validate
                        onClick={save}
                        style={{ marginLeft: 26 }}>
                        保存
                    </Form.Submit>
                </FormItem>
    ​
            </Form>
        </div>
    }
    ​
    export default {
        name: 'SaveAsBlock',
        content: {
            icon: {
                type: 'add',
                size: 'xs'
            },
            title: '保存为区块',
            action(node: Node) {
                Dialog.show({
                    v2: true,
                    title: '是否保存为区块?',
                    content: <SaveAsBlock node={node} />,
                    footer: false
                })
    ​
            }
        },
        important: true,
    }

<!---->

    src/services/mockService.ts
    ​
    export const createBlock = async (block: any) => {
      const url = `http://127.0.0.1:7001/block`;
    ​
      const res = await (await fetch(url, {
        method: 'post',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ block })
      })).json()
    ​
      if (res.code) {
        console.error('create block failed: ', res);
        return;
      }
      Message.success(res.message);
    }
  1. 后端关键代码实现:

    ini 复制代码
    router.js
    ​
    module.exports = app => {
      const { router, controller } = app;
      router.get('/block', controller.block.listBlocks);
      router.post('/block', controller.block.createBlock);
    };
    javascript 复制代码
    controller/block.js
    ​
    'use strict';
    ​
    const Controller = require('egg').Controller;
    ​
    class BlockController extends Controller {
    ​
        async listBlocks() {
            const { ctx } = this;
            const blocks = await ctx.service.block.listBlock();
            ctx.body = {
                code: 0,
                data: blocks,
            };
        }
    ​
        // 新增or更新区块
        async createBlock() {
            const { ctx } = this;
            const { block } = ctx.request.body;
            const result = await ctx.service.block.createOrUpdateBlock(block);
            ctx.logger.info('create block result: ', result);
            ctx.body = {
                code: 0,
                message: '保存区块成功',
            };
        }
    }
    ​
    module.exports = BlockController;
    javascript 复制代码
    service/block.js
    ​
    'use strict';
    ​
    const { Service } = require('egg');
    ​
    class BlockService extends Service {
    ​
        // 查询区块列表
        async listBlock() {
            try {
                return this.app.mysql.select('block');
            } catch (error) {
                console.log(error);
            }
        }
    ​
        // 新增or更新区块
        async createOrUpdateBlock(params) {
            try {
                const query = await this.app.mysql.get('block', { schemaId: params.schemaId });
                return query
                    ? await this.app.mysql.update('block', params, { where: { schemaId: params.schemaId } })
                    : await this.app.mysql.insert('block', params)
            } catch (error) {
                console.log(error);
            }
        }
    }
    ​
    module.exports = BlockService;

二. 数据库操作

  1. 新建 block 表,表结构如下图,其中 schemaId 取得值是 区块schema里面的 id 字段:

三. 新建区块插件

  1. 在 plugins 下新增 plugin-blocks 插件:

    typescript 复制代码
    plugins/plugin-blocks/index.tsx
    ​
    import * as React from 'react';
    import { IPublicModelPluginContext } from '@alilc/lowcode-types';
    ​
    import { default as BlockPane } from './pane';
    import { listBlocks } from '../../apis/block';
    ​
    import './index.scss';
    ​
    const LowcodePluginPluginBlocks = (ctx: IPublicModelPluginContext) => {
        return {
            // 插件对外暴露的数据和方法
            exports() {
                return {
                    data: '你可以把插件的数据这样对外暴露',
                    func: () => {
                        console.log('方法也是一样');
                    },
                };
            },
            // 插件的初始化函数,在引擎初始化之后会立刻调用
            async init() {
                // 你可以拿到其他插件暴露的方法和属性
                // const { data, func } = ctx.plugins.pluginA;
                // func();
    ​
                // console.log(options.name);
    ​
                let config: any = {
                    area: 'leftArea',
                    name: 'LowcodePluginPluginBlocksPane',
                    type: 'PanelDock',
                    props: {
                        icon: 'smile',
                        description: '区块',
                    },
                    contentProps: {
                        api: {
                            listBlocks
                        }
                    },
                    content: BlockPane,
                };
                // 往引擎增加面板
                ctx.skeleton.add(config);
    ​
                ctx.logger.log('打个日志');
            },
        };
    };
    ​
    // 插件名,注册环境下唯一
    LowcodePluginPluginBlocks.pluginName = 'LowcodePluginPluginBlocks';
    LowcodePluginPluginBlocks.meta = {
        // 依赖的插件(插件名数组)
        dependencies: [],
        engines: {
            lowcodeEngine: '^1.0.0', // 插件需要配合 ^1.0.0 的引擎才可运行
        },
    };
    ​
    export default LowcodePluginPluginBlocks;

    记得在 src/index.ts 注册一下:

    javascript 复制代码
    import BlocksPlugin from './plugins/plugin-blocks';
    import saveAsBlock from './actions/block';
    ​
    // 注册保存为区块工作条
    material.addBuiltinComponentAction(saveAsBlock);
    ​
    await plugins.register(BlocksPlugin);

四. 在区块插件面板展示已保存的区块

  1. 效果如下图,支持拖拽到右侧显示:

  2. 关键代码:

    typescript 复制代码
    plugin-blocks/card/index.tsx
    ​
    import * as React from 'react';
    ​
    import './index.scss';
    ​
    const { useState, useEffect } = React;
    ​
    interface BlockCardProps {
        id: string,
        name: string,
        screenshot: string,
    }
    ​
    const BlockCard = (props: BlockCardProps) => {
        const { id, name, screenshot } = props;
    ​
        return <div className='block-card snippets' data-id={id}>
            <div className='block-card-screenshot'>
                <img src={screenshot} />
            </div>
            <span>
                {name}
            </span>
        </div>;
    }
    ​
    export default BlockCard;
    typescript 复制代码
    plugin-blocks/pane/index.tsx
    ​
    import * as React from 'react';
    import { common, project, config, event } from '@alilc/lowcode-engine';
    ​
    import { Loading, Box, Divider } from '@alifd/next';
    ​
    import { default as BlockCard } from '../card';
    import { default as store } from '../store';
    ​
    const { useState, useEffect } = React;
    ​
    export interface Block {
    ​
    }
    ​
    export interface BlockResponse {
        code: number;
        data: Block[];
    }
    ​
    export interface BlockPaneAPI {
        listBlocks: () => BlockResponse;
    }
    ​
    export interface BlockPaneProps {
        api: BlockPaneAPI;
    }
    ​
    export const BlockPane = (props: BlockPaneProps) => {
    ​
        const { api } = props;
        const [blocks, setBlocks] = useState();
        const { listBlocks } = api;
    ​
        useEffect(() => {
            const fetchBlocks = async () => {
                const res = await listBlocks();
                if (res?.code) {
                    console.error('list block failed: ', res);
                    return;
                }
                store.init(res);
                setBlocks(res);
            };
    ​
            fetchBlocks();
        }, []);
    ​
        // 拖拽逻辑
        const registerAdditive = (shell: HTMLDivElement | null) => {
            if (!shell || shell.dataset.registered) {
                return;
            }
    ​
            function getSnippetId(elem: any) {
                if (!elem) {
                    return null;
                }
                while (shell !== elem) {
                    if (elem.classList.contains('snippets')) {
                        return elem.dataset.id;
                    }
                    elem = elem.parentNode;
                }
                return null;
            }
    ​
            const _dragon = common.designerCabin.dragon
            if (!_dragon) {
                return;
            }
    ​
            // eslint-disable-next-line
            const click = (e: Event) => { };
    ​
            shell.addEventListener('click', click);
    ​
            _dragon.from(shell, (e: Event) => {
                const doc = project.getCurrentDocument();
                const id = getSnippetId(e.target);
                if (!doc || !id) {
                    return false;
                }
    ​
                const dragTarget = {
                    type: 'nodedata',
                    data: store.get(id),
                };
    ​
                return dragTarget;
            });
    ​
            shell.dataset.registered = 'true';
        };
    ​
        if (!blocks?.length) {
            return <div className='block-pane-loading'><Loading /></div>
        }
    ​
        return <div className='block-pane' ref={registerAdditive}><Box direction='row' wrap>
            {
                blocks.map(item => <BlockCard id={item.id} name={item.name} screenshot={item.screenshot} />)
            }
        </Box>
        </div>;
    ​
    }
    ​
    export default BlockPane;
    javascript 复制代码
    plugin-blocks/store/index.ts
    ​
    class BlockStore {
    ​
        store;
    ​
        constructor() {
            this.store = new Map();
        }
    ​
        init(blocks) {
            blocks.forEach(block => {
                const { id, schema } = block;
                this.store.set(`${id}`, JSON.parse(schema));
            });
        }
    ​
        set(id, snippets) {
            this.store.set(id, snippets);
        }
    ​
        get(id) {
            return this.store.get(id);
        }
    ​
    }
    ​
    const singleton = new BlockStore();
    ​
    export default singleton;

结果演示

新增一个区块666,查看效果:

================================================================= 以上为个人工作学习笔记总结,供学习参考交流,未经允许禁止转载或商用。

本篇参考金蝉大佬:www.bilibili.com/video/BV1YF...

github.com/alibaba/low...

相关推荐
wenzhangli723 分钟前
低代码开发中 DDD 领域驱动的页面权限控制
低代码
程序猿online32 分钟前
nvm安装使用,控制node版本
开发语言·前端·学习
web Rookie42 分钟前
React 中 createContext 和 useContext 的深度应用与优化实战
前端·javascript·react.js
男孩121 小时前
react高阶组件及hooks
前端·javascript·react.js
m0_748251721 小时前
DataOps驱动数据集成创新:Apache DolphinScheduler & SeaTunnel on Amazon Web Services
前端·apache
珊珊来吃1 小时前
EXCEL中给某一列数据加上双引号
java·前端·excel
onejason2 小时前
深度解析:利用Python爬虫获取亚马逊商品详情
前端·python
胡西风_foxww2 小时前
【ES6复习笔记】Spread 扩展运算符(8)
前端·笔记·es6·扩展·运算符·spread
小林爱2 小时前
【Compose multiplatform教程08】【组件】Text组件
android·java·前端·ui·前端框架·kotlin·android studio
树上有只程序猿2 小时前
低代码应用场景大赏——多领域深度赋能
低代码