【阿里低代码引擎实战】--- 保存为区块功能
上一篇在低代码项目中实现了表单编辑与回显功能,上篇指路:juejin.cn/post/735238...
这篇我们来实现将页面多个组件组合成一个区块,保存后可以作为新的组件重复拖拽使用的功能;
本篇完整代码github地址: github.com/JoeXu727/al...
一. 在操作辅助区添加"保存为区块"
-
期望达到的效果是在操作辅助区多一个+号按钮,点击后会出现弹框,输入一些区块的基本信息后即可保存为区块,如下图;
-
前端关键代码实现:在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);
}
-
后端关键代码实现:
inirouter.js module.exports = app => { const { router, controller } = app; router.get('/block', controller.block.listBlocks); router.post('/block', controller.block.createBlock); };
javascriptcontroller/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;
javascriptservice/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;
二. 数据库操作
-
新建 block 表,表结构如下图,其中 schemaId 取得值是 区块schema里面的 id 字段:
三. 新建区块插件
-
在 plugins 下新增
plugin-blocks
插件:typescriptplugins/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
注册一下:javascriptimport BlocksPlugin from './plugins/plugin-blocks'; import saveAsBlock from './actions/block'; // 注册保存为区块工作条 material.addBuiltinComponentAction(saveAsBlock); await plugins.register(BlocksPlugin);
四. 在区块插件面板展示已保存的区块
-
效果如下图,支持拖拽到右侧显示:
-
关键代码:
typescriptplugin-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;
typescriptplugin-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;
javascriptplugin-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...