# @ithinkdt/lowcode

## 许可证

MIT

## 安装

```bash
npm i @ithinkdt/lowcode
```

> 需要您自行安装 peer 依赖 `vue@>=3.5`、`vue-router@>=4.5`、`@ithinkdt/ui@>=4.0`、`@ithinkdt/page@>=4.0` 与 `ithinkdt-ui@>=1.8`。

## 介绍

`@ithinkdt/lowcode` 提供 iThinkDT 低代码页面搭建、运行渲染与实体建模能力，包括：

- 低代码页面 Schema 定义与运行时渲染
- 可视化页面搭建工作台
- 物料体系（容器、通用展示、表单、低代码业务组件）
- 状态定义（变量、请求、实体）
- 动作定义（脚本方法）
- 表达式、函数、字典、接口数据、图标等动态绑定
- 实体模型、字段、索引、关系定义
- 实体关系图设计器
- 根据实体字段生成表单、查询表单、详情表单的辅助工具

## 核心概念

### Schema

低代码页面由 `LcSchema` 描述：

```ts
import type { LcSchema } from '@ithinkdt/lowcode'

const schema: LcSchema = {
    name: '用户列表',
    state: [],
    methods: [],
    css: '.root { padding: 16px; }',
    css2: '.h5 .root { padding: 12px; }',
    tree: {
        id: 'root',
        type: 'RootContainer',
        label: '页面',
        container: true,
        props: {
            id: 'root',
            xScrollable: false,
        },
        css: '.root { }',
        css2: '.h5 .root { }',
        listeners: [],
        children: {
            default: [],
        },
    },
}
```

### 节点

每个页面节点由 `LcNode` 描述，核心字段包括：

| 字段 | 说明 |
|------|------|
| `id` | 节点唯一标识，也会作为默认 class |
| `type` | 物料类型，如 `Button`、`Input`、`FormContainer` |
| `label` | 工作台显示名称 |
| `props` | 组件属性，支持表达式 |
| `css` / `css2` | Web / H5 样式 |
| `listeners` | 事件绑定 |
| `children` | 插槽子节点 |
| `container` | 是否为容器 |
| `form` | 是否为表单节点 |

## 页面工作台

`LowcodeEngineWorkbench` 用于可视化搭建页面。通常项目会把平台接口、文件服务、图标选择器、物料、状态定义、动作定义等注入到工作台引擎。

```tsx
import { defineComponent, ref } from 'vue'
import { LowcodeEngineWorkbench, entity, request, script, variable } from '@ithinkdt/lowcode/engine/workbench'
import {
    base,
    button,
    flex,
    form,
    input,
    number,
    root,
    select,
    submitForm,
    text,
} from '@ithinkdt/lowcode/materials/workbench'

export default defineComponent({
    setup() {
        const terminal = ref<'WEB' | 'MOBILE'>('WEB')

        const engine = new LowcodeEngineWorkbench({
            getDictTypes: async () => [
                { label: '状态', value: 'status' },
            ],
            getApis: async () => [],
            getApi: async code => undefined,
            icons: {
                renderer: props => <IconSelect {...props} />,
            },
            file: {
                removeFile: async id => {},
                getFileInfos: async ids => [],
                previewFileUrl: id => `/api/files/${id}/preview`,
                downloadFileUrl: id => `/api/files/${id}/download`,
                uploadFile: async file => '',
            },
            materials: {
                container: {
                    label: '容器组件',
                    materials: [
                        root,
                        base,
                        flex,
                        form({ getEntities: async () => [] }),
                    ],
                },
                common: {
                    label: '通用组件',
                    materials: [
                        text,
                        button,
                    ],
                },
                form: {
                    label: '表单组件',
                    materials: [
                        input,
                        number,
                        select,
                        submitForm,
                    ],
                },
            },
            stateDefinitions: [
                variable,
                request,
                entity({ api: entityApi }),
            ],
            methodDefinitions: [
                script,
            ],
            providers: {
                msg: window.$msg,
                fetch,
            },
        })

        function save() {
            const schema = engine.toJSON()
            return fetch('/api/pages', {
                method: 'POST',
                body: JSON.stringify(schema),
            })
        }

        return () => (
            <engine.Workbench
                terminal={terminal.value}
                onSubmit={save}
                style={{ height: '100%' }}
            />
        )
    },
})
```

### 常用工作台方法

```ts
// 设置或重置 Schema
await engine.setSchema(schema)

// 导出完整 Schema
const schema = engine.toJSON()

// 创建节点并插入页面
const node = engine.createNode('Button', {
    label: '保存按钮',
    props: {
        text: '保存',
    },
})
engine.insertNode(node)

// 定义状态与动作
await engine.addState('form', 'var', '表单数据', {
    content: 'function getter() { return {} }',
})

await engine.addMethod('submit', 'script', '提交表单', {
    script: `export default async function submit() {
        await $fetch('/api/users', { method: 'POST', body: $state.form })
        $msg.success('保存成功')
    }`,
})

// 撤销、重做
engine.undo()
engine.redo()
```

## 页面渲染器

`LowcodeEngineRenderer` 用于在运行期渲染已保存的 Schema。运行期使用 `@ithinkdt/lowcode/materials/renderer` 中的组件定义。

```tsx
import { defineComponent } from 'vue'
import { LowcodeEngineRenderer, entity, request, script, variable } from '@ithinkdt/lowcode/engine/renderer'
import {
    base,
    button,
    flex,
    form,
    input,
    root,
    submitForm,
    text,
} from '@ithinkdt/lowcode/materials/renderer'

export default defineComponent({
    props: {
        schema: { type: Object, required: true },
    },
    setup(props) {
        const engine = new LowcodeEngineRenderer({
            schema: props.schema,
            components: [
                root,
                base,
                flex,
                form,
                text,
                button,
                input,
                submitForm,
            ],
            stateDefinitions: [
                variable,
                request,
                entity({ api: entityApi }),
            ],
            methodDefinitions: [
                script,
            ],
            providers: {
                msg: window.$msg,
                fetch,
            },
            getApi: async code => undefined,
        })

        return () => <engine.Renderer />
    },
})
```

### 运行期更新 Schema

```ts
await engine.setSchema(nextSchema)
```

渲染器会重新应用状态、动作、节点树与样式，并在组件卸载时自动清理运行时注入。

## 物料

`@ithinkdt/lowcode` 将物料分为工作台物料和运行期组件：

| 入口 | 用途 |
|------|------|
| `@ithinkdt/lowcode/materials/workbench` | 工作台左侧可拖拽物料与属性面板配置 |
| `@ithinkdt/lowcode/materials/renderer` | 运行期渲染组件 |
| `@ithinkdt/lowcode/materials` | 物料类型声明 |

### 内置物料

| 分类 | 物料 |
|------|------|
| 容器 | `RootContainer`、`BaseContainer`、`FlexContainer`、`FormContainer`、`FormItem`、`DialogContainer`、`DrawerContainer`、`TabsContainer` |
| 通用 | `Text`、`Button`、`Icon`、`FileBtn`、`Divider`、`Link`、`Headline`、`DataTable`、`Pagin`、`Tree`、`QrCode`、`Image`、`ImageGroup` |
| 表单 | `Input`、`Passwd`、`Textarea`、`Number`、`Switch`、`Checkbox`、`Date`、`Datetime`、`Time`、`Select`、`Transfer`、`Radios`、`Checkboxes`、`Color`、`Rate`、`Slider`、`Tags`、`Upload`、`RichText`、`TreeSelect`、`Cascader`、`EditTable`、`UserDept`、`SerialNo` |
| 低代码 | `QueryForm`、`SubmitForm` |

### 组装物料

```ts
import {
    base,
    button,
    date,
    flex,
    form,
    input,
    root,
    serialNo,
    table,
    text,
    userDept,
} from '@ithinkdt/lowcode/materials/workbench'

const materials = {
    container: {
        label: '容器组件',
        materials: [
            root,
            base,
            flex,
            form({ getEntities: async () => [] }),
        ],
    },
    common: {
        label: '通用组件',
        materials: [
            text,
            button,
            table,
        ],
    },
    form: {
        label: '表单组件',
        materials: [
            input,
            date,
            userDept({
                users,
                groups,
                depts,
                getUsersByGroup,
                getUsersByDept,
                getUsersByUsername,
                getDeptsByCode,
            }),
            serialNo({
                getSerials,
                getSerialNo,
            }),
        ],
    },
}
```

### 自定义物料

```tsx
import type { LcMaterial } from '@ithinkdt/lowcode/engine'

export const notice: LcMaterial<'Notice'> = {
    key: 'Notice',
    name: '提示',
    icon: NoticeIcon,
    component: Notice,
    props: {
        type: 'info',
        text: '提示内容',
    },
    css: () => ({
        margin: '8px 0',
    }),
    items: () => [
        { label: '类型', prop: 'type', type: 'select' },
        { label: '内容', prop: 'text', type: 'input' },
    ],
}
```

如果需要获得完整类型提示，可以扩展 `LcMaterials`：

```ts
import type { LcMaterial } from '@ithinkdt/lowcode/engine'

declare module '@ithinkdt/lowcode/materials' {
    interface LcMaterials {
        Notice: LcMaterial<'Notice'>
    }
}
```

## 表达式与脚本

节点属性支持 `{{ ... }}` 表达式。运行时会注入：

| 变量 | 说明 |
|------|------|
| `$state` | 低代码状态 |
| `$methods` | 低代码动作 |
| `$refs` | 节点引用 |
| `$MODE` | `DEV` 或 `PROD` |
| `$TERMINAL` | `WEB` 或 `H5` |
| `$<provider>` | 构造引擎时传入的 providers，如 `$msg`、`$fetch` |

常用表达式：

```ts
// 普通表达式
node.props.title = '{{ $state.form.name || "未命名" }}'

// 函数表达式
node.props.onClick = '{{ Fn: function onClick() { $methods.submit() } }}'

// 字典
node.props.options = '{{ Dict: status }}'

// 接口数据
node.props.options = '{{ ApiData: {"key":"userOptions","params":"({})","path":"data"} }}'

// 图标
node.props.icon = '{{ Icon: {"body":"data:image/svg+xml;base64,...","svg":true} }}'
```

脚本动作需要默认导出一个函数：

```ts
export default async function submit() {
    await $fetch('/api/users', {
        method: 'POST',
        body: $state.form,
    })
    $msg.success('保存成功')
}
```

## 状态与动作定义

### 内置状态

| 状态 | 工作台入口 | 渲染器入口 | 说明 |
|------|------------|------------|------|
| `variable` | `@ithinkdt/lowcode/engine/workbench` | `@ithinkdt/lowcode/engine/renderer` | 普通变量状态 |
| `request` | `@ithinkdt/lowcode/engine/workbench` | `@ithinkdt/lowcode/engine/renderer` | 请求状态 |
| `entity` | `@ithinkdt/lowcode/engine/workbench` | `@ithinkdt/lowcode/engine/renderer` | 实体数据状态 |

### 内置动作

| 动作 | 说明 |
|------|------|
| `script` | JavaScript / TypeScript 脚本动作 |

### 实体 API 适配

实体状态依赖 `EntityApi`，用于把模型 CRUD 能力接入低代码状态：

```ts
import { entity } from '@ithinkdt/lowcode/engine/renderer'

const entityState = entity({
    api: entityApi,
})
```

`EntityApi` 需要提供 `fetchOne`、`fetchList`、`fetchPage`、`postOne`、`putOne`、`deleteOne`、`deleteBatch` 等方法。

## 实体设计器

`EntityDesigner` 提供实体、字段、索引、关系的可视化设计能力。

```vue
<script setup lang="ts">
import { ref } from 'vue'
import {
    EntityDesigner,
    type EntityDesignerInst,
    type DesignerTool,
} from '@ithinkdt/lowcode/entity/designer'
import type { Entity } from '@ithinkdt/lowcode/entity'

const designer = ref<EntityDesignerInst>()
const entities = ref<Entity[]>([])

const tools: DesignerTool[] = [
    {
        title: '刷新',
        type: 'primary',
        onClick: refresh,
    },
]

async function refresh() {
    entities.value = await fetch('/api/entities').then(res => res.json())
}

async function submit(nextEntities: Entity[]) {
    await fetch('/api/entities', {
        method: 'POST',
        body: JSON.stringify(nextEntities),
    })
    designer.value?.clear()
}
</script>

<template>
    <EntityDesigner
        ref="designer"
        class="size-full"
        app-code="admin"
        :entities="entities"
        :tools="tools"
        :get-dict-types="() => Promise.resolve([])"
        :get-serials="() => Promise.resolve([])"
        :support-languages="[
            { label: '简体中文', value: 'zh-CN' },
            { label: 'English', value: 'en-US' },
        ]"
        @submit="submit"
    />
</template>
```

### 实体模型

`@ithinkdt/lowcode/entity` 导出实体相关类型与常量：

| 导出 | 说明 |
|------|------|
| `Entity` | 实体定义 |
| `EntityField` | 实体字段定义 |
| `EntityIndex` | 实体索引定义 |
| `EntityRel` | 实体关系定义 |
| `EntityFieldType` | 字段类型枚举 |
| `RelType` | 关系类型常量 |
| `patternMap` | 常用字段格式正则 |

## 根据实体生成表单状态

`createFormStateGetterByEntity` 可根据实体字段生成表单、查询表单或详情表单的初始状态脚本。

```ts
import { createFormStateGetterByEntity } from '@ithinkdt/lowcode/materials/workbench'

await engine.addState('form', 'var', '表单数据', {
    resetable: false,
    content: createFormStateGetterByEntity(entity, 'form'),
})

await engine.addState('query', 'var', '查询条件', {
    resetable: false,
    content: createFormStateGetterByEntity(entity, 'query'),
})

await engine.addState('view', 'var', '详情数据', {
    resetable: false,
    content: createFormStateGetterByEntity(entity, 'view'),
})
```

## API 参考

### 引擎

| 导出 | 说明 |
|------|------|
| `LowcodeEngineCore` | 工作台与渲染器公共基类 |
| `LowcodeEngineWorkbench` | 可视化工作台引擎 |
| `LowcodeEngineRenderer` | 运行期渲染引擎 |
| `LcSchema` | 页面 Schema |
| `LcNode` | 页面节点 |
| `LcMaterial` | 工作台物料定义 |
| `LcComponent` | 运行期组件定义 |
| `LcTemplate` | 工作台模板定义 |
| `LcStateDefinition` | 状态定义 |
| `LcMethodDefinition` | 动作定义 |
| `getFieldFormMap` | 实体字段到表单物料映射 |
| `getFieldQueryFormMap` | 实体字段到查询物料映射 |

### 工作台构造参数

| 参数 | 说明 |
|------|------|
| `materials` | 物料分组 |
| `templates` | 模板分组 |
| `getDictTypes` | 获取字典类型 |
| `getApis` / `getApi` | 获取接口列表与接口详情 |
| `icons.renderer` | 图标选择器渲染函数 |
| `file` | 文件上传、下载、预览、删除能力 |
| `stateDefinitions` | 状态定义 |
| `methodDefinitions` | 动作定义 |
| `providers` | 注入脚本运行环境的外部能力 |
| `providerDocs` | 工作台中展示的 provider 文档 |
| `mapPropItems` / `mapProItems` | 扩展或改写属性面板配置 |

### 渲染器构造参数

| 参数 | 说明 |
|------|------|
| `schema` | 要渲染的页面 Schema |
| `components` | 运行期组件列表 |
| `stateDefinitions` | 状态定义 |
| `methodDefinitions` | 动作定义 |
| `providers` | 注入脚本运行环境的外部能力 |
| `getApi` | 获取接口详情 |

## 导入路径

```ts
// 类型、实体工具、核心引擎类型
import { type LcSchema, type LcNode, type Entity } from '@ithinkdt/lowcode'

// 工作台
import { LowcodeEngineWorkbench, variable, request, entity, script } from '@ithinkdt/lowcode/engine/workbench'

// 渲染器
import {
    LowcodeEngineRenderer,
    variable as renderVariable,
    request as renderRequest,
    entity as renderEntity,
    script as renderScript,
} from '@ithinkdt/lowcode/engine/renderer'

// 物料类型
import type { LcMaterials } from '@ithinkdt/lowcode/materials'

// 工作台物料
import {
    root as wbRoot,
    base as wbBase,
    flex as wbFlex,
    form as wbForm,
    input as wbInput,
    button as wbButton,
} from '@ithinkdt/lowcode/materials/workbench'

// 运行期组件
import {
    root as renderRoot,
    base as renderBase,
    flex as renderFlex,
    form as renderForm,
    input as renderInput,
    button as renderButton,
} from '@ithinkdt/lowcode/materials/renderer'

// 实体模型
import { EntityFieldType, RelType } from '@ithinkdt/lowcode/entity'

// 实体设计器
import { EntityDesigner } from '@ithinkdt/lowcode/entity/designer'
```
