前言
这一篇我们来实现一下组件属性绑定变量
、修改变量
、在线执行脚本
低代码常用功能。
往期回顾
组件属性绑定变量
前言
为了能做出更复杂的页面,组件属性支持绑定变量是低代码必不可少的功能,下面我们实现一下这个功能。
定义变量
就像写代码一样,想使用变量,需要先定义变量,所以我们先实现定义变量功能。
变量数据结构
ts
interface Variable {
// 变量名
name: string,
// 变量类型
type: string,
// 默认值
defaultValue: string;
// 备注
remark: string;
}
定义一个存放变量数据的store
ts
// src/editor/stores/variable.ts
import {create} from 'zustand';
export interface Variable {
/**
* 变量名
*/
name: string;
/**
* 默认值
*/
defaultValue: string;
/**
* 备注
*/
remark: string;
}
interface State {
variables: Variable[];
}
interface Action {
/**
* 添加组件
* @param component 组件属性
* @param parentId 上级组件id
* @returns
*/
setVariables: (variables: Variable[]) => void;
}
export const useVariablesStore = create<State & Action>((set) => ({
variables: [],
setVariables: (variables) => set({variables}),
}));
这里使用antd
的Form.List
组件快速实现功能,type先写死一个string类型,后面可以拓展很多类型,比如bool、number和更高级的json和接口等。
tsx
// src/editor/layouts/header/define-variable.tsx
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Select, Space } from 'antd';
import React, { useEffect } from 'react';
import { useVariablesStore } from '../../stores/variable';
interface Props {
open: boolean,
onCancel: () => void
}
interface Variable {
// 变量名
name: string,
// 变量类型
type: string,
// 默认值
defaultValue: string;
// 备注
remark: string;
}
const DefineVariable: React.FC<Props> = ({ open, onCancel }) => {
const [form] = Form.useForm();
const { setVariables, variables } = useVariablesStore();
function onFinish(values: { variables: Variable[] }) {
setVariables(values.variables);
onCancel && onCancel();
}
useEffect(() => {
if (open) {
form.setFieldsValue({ variables });
}
}, [open])
return (
<Modal
open={open}
title="定义变量"
onCancel={onCancel}
destroyOnClose
onOk={() => { form.submit() }}
width={700}
>
<Form<{ variables: Variable[] }>
onFinish={onFinish}
autoComplete="off"
className='py-[20px]'
form={form}
initialValues={{ variables }}
>
<Form.List name="variables">
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, ...restField }) => (
<Space key={key} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
<Form.Item
{...restField}
name={[name, 'name']}
rules={[{ required: true, message: '变量名不能为空' }]}
>
<Input placeholder="变量名" />
</Form.Item>
<Form.Item
{...restField}
name={[name, 'type']}
>
<Select style={{ width: 140 }} options={[{ label: '字符串', value: 'string' }]} placeholder="类型" />
</Form.Item>
<Form.Item
{...restField}
name={[name, 'defaultValue']}
>
<Input placeholder="默认值" />
</Form.Item>
<Form.Item
{...restField}
name={[name, 'remark']}
>
<Input placeholder="备注" />
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
</Space>
))}
<Form.Item>
<Button type="dashed" onClick={() => add({ type: 'string' })} block icon={<PlusOutlined />}>
添加变量
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form>
</Modal>
)
};
export default DefineVariable;
头上工具栏再加一个定义变量的按钮控制定义变量弹框显示和隐藏。
效果展示
绑定变量
所有组件属性都需要支持绑定变量,所以需要让属性配置的表单元素都支持选择变量,我们给这些表单组件封装一下,方便使用。
下面是组件属性新数据结构,分为静态和变量两种类型。
ts
interface Value {
type: 'static' | 'variable';
value: any;
}
封装支持设置变量的输入框
tsx
// src/editor/common/setting-form-item/input.tsx
import { SettingOutlined } from '@ant-design/icons';
import { Input } from 'antd';
import { useState } from 'react';
import SelectVariableModal from '../select-variable-modal';
interface Value {
type: 'static' | 'variable';
value: any;
}
interface Props {
value?: Value,
onChange?: (value: Value) => void;
}
const SettingFormItemInput: React.FC<Props> = ({ value, onChange }) => {
const [visible, setVisible] = useState(false);
function valueChange(e: any) {
onChange && onChange({
type: 'static',
value: e?.target?.value,
});
}
function select(record: any) {
onChange && onChange({
type: 'variable',
value: record.name,
});
setVisible(false);
}
return (
<div className='flex gap-[8px]'>
<Input
disabled={value?.type === 'variable'}
value={(value?.type === 'static' || !value) ? value?.value : ''}
onChange={valueChange}
/>
<SettingOutlined
onClick={() => { setVisible(true) }}
className='cursor-pointer'
style={{ color: value?.type === 'variable' ? 'blue' : '' }}
/>
<SelectVariableModal
open={visible}
onCancel={() => { setVisible(false) }}
onSelect={select}
/>
</div>
)
}
export default SettingFormItemInput;
属性设置组件里antd
的Input
改成刚封装SettingFormItemInput
组件
效果展示
渲染的时候,根据类型处理props
效果展示
设置变量
事件再添加一个设置变量
的动作类型,选择设置变量
后,先选择一个要设置的变量,然后输入变量的值。
效果展示
先定义一个存放变量值的store,然后在点击事件里调用setData给变量设置值。
tsx
// src/editor/stores/page-data.ts
import {create} from 'zustand';
interface State {
data: any;
}
interface Action {
/**
* 设置变量值
* @param component key
* @param parentId 值
* @returns
*/
setData: (key: string, value: any) => void;
/**
* 重置数据
* @returns
*/
resetData: () => void;
}
export const usePageDataStore = create<State & Action>((set) => ({
data: {},
setData: (key, value) =>
set((state) => ({data: {...state.data, [key]: value}})),
resetData: () => set({data: {}}),
}));
事件处理那里再加一个动作类型判断,这一块其实可以用策略模式优化一下代码,但是这是demo,先不优化了。
前面渲染那里改造一下,先从data中取值,取不到再使用变量的默认值。
效果展示
动态执行脚本
前言
js中常用的动态执行脚本方式有两种,一个是使用eval
,另外一个是new Function
,这里我们使用new Function
,传参方便一点。
实战
动态执行脚本依然是由事件触发的,所以给事件添加一个执行脚本的动作类型。
脚本最好使用代码编辑器来编写,因为这里是demo,先用文本输入框代替,后面实战时会用代码编辑器的。
我们把一些常用的方法注入到ctx中,可以在脚本里调用我们注入的方法,比如设置变量值方法,和调用某个组件方法等。
封装执行脚本方法,把设置变量值方法和获取组件实例方法注入到ctx中,代码很简单。
事件那里加一个执行脚本动作
设置变量值
执行组件方法
最后
到此我们简单的实现了组件属性绑定变量
、修改变量
、在线执行脚本
低代码常用功能,下一篇我们来实现动态加载远程组件
。
demo体验地址:dbfu.github.io/lowcode-dem...
demo仓库地址:github.com/dbfu/lowcod...