低代码平台directus 实战半年-进阶篇

5,897 阅读9分钟

介绍

directus 支持extension扩展有多种方式

  • display: 用于定制后台列表页面的某一列字段显示,对应数据库某个字段,使用vue3展示
  • interface: 用于定制后台详细页面的某一字段显示,对应数据库某个字段,使用vue3展示
  • operation: 在flow里面的框框,有输入和输出。可以做更细粒度的业务封装
  • endpoint: 后台express的一个路由RESTful服务
  • hook: 监听数据变化钩子函数
  • layout: 后台列表页面的整体布局(列表或瀑布流)
  • panel: 后台报表分析的模块图像/表格化显示
  • module: 后台系统左边大菜单模块,可以定制更丰富的模块内容
  • bundle: 自定义库,可以一次性把上面所有扩展都写到一个库中,方便不同模块之间共享数据方法

git demo

github.com/mjsong07/di…

1. display

display:用于定制后台列表页面的某一列字段显示,对应数据库某个字段,使用vue3展示

我们用官方自带脚手架定制一个后台的display

cd extensions/displays #进入扩展路径
npx create-directus-extension@latest # 执行扩展安装

# 提示
Need to install the following packages:
  create-directus-extension@10.0.15
Ok to proceed? (y) y # 输入y
This utility will walk you through creating a Directus extension.
# 选择 display
? Choose the extension type 
  panel 
  hook 
  endpoint 
  operation 
  bundle 
  interface 
 ❯ display 
 
 # 定义名称
 Choose a name for the extension mytest
 # 选择语言
 ? Choose the language to use 
  javascript 
❯ typescript 


# 根据提示 我们进入进入项目运行调试或构建
To start developing, run:
        cd mytest
        npm run dev

and then to build for production, run:
        npm run build

生成的代码 image.png

为了方便调整 我们打开项目的热更新

EXTENSIONS_AUTO_RELOAD=true
EXTENSIONS_PATH="./extensions"

启动服务,发现缺少index.js 因为自定义display打包路径是mytest/dist/index.js,而directus访问的路径是mytest/index.js

[17:41:44.367] WARN: Couldn't bundle App extensions
[17:41:44.368] WARN: Could not resolve "./extensions/displays/mytest/index.js" from "virtual:entry"
    err: {
      "type": "Error",
      "message": "Could not resolve \"./extensions/displays/mytest/index.js\" from \"\u0000virtual:entry\"",

我们调整mytest的package.json输出路径

	"directus:extension": {
		"type": "display",
		"path": "dist/index.js",
		"source": "src/index.ts",
		"host": "^10.1.14"
	}, 

"path": "dist/index.js",调整为 "path": "./index.js",

再次启动,已经提示正常加载组件 image.png

测试一把

进入city表,选择name编辑 image.png 在显示拦新增了custom选项 image.png

image.png

我们对比选中自定义的name显示区别。 image.png 其实就是把字段通过自定义组件显示 image.png

代码分析

src/display.vue

生成的是vue3代码

<template>
	<div>Value: {{ value }}</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
	props: {
		value: {
			type: String,
			default: null,
		},
	},
});
</script>


src/index.ts

import { defineDisplay } from '@directus/extensions-sdk';
import DisplayComponent from './display.vue';

export default defineDisplay({
	id: 'custom',
	name: 'Custom',
	icon: 'box',
	description: 'This is my custom display!',
	component: DisplayComponent,
	options: null, //这里可以外部传入自定义的逻辑处理显示的交互
	types: ['string'], //这里对应数据字段类型,只有匹配的类型才能选择该display
});

2. interface

interface: 用于定制后台详细页面的某一字段显示,对应数据库某个字段,使用vue3展示

和display相同操作,我们创建interface

cd extensions/interfaces #进入扩展路径
npx create-directus-extension@latest # 执行扩展安装
选择 interface 》 typescript    

修改配置 我们调整mytest的package.json输出路径

    "directus:extension": {
            "type": "interface",
            "path": "./index.js",
            "source": "src/index.ts",
            "host": "^10.1.14"
    },

生成代码 image.png

测试一把

运行

 cd ./extensions/interfaces/mytest
 npm run dev

发现directus已经热加载最新interface插件 image.png

开始设置 在city -> name字段 接口栏发现 custom image.png

image.png

对比详情页面差异 image.png image.png

代码分析

src/interface.vue

生成的是vue3代码

<template>
	<input :value="value" @input="handleChange($event.target.value)" />
</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
	props: {
		value: {
			type: String,
			default: null,
		},
	},
	emits: ['input'],
	setup(props, { emit }) {
		return { handleChange };

		function handleChange(value: string): void {
			emit('input', value);
		}
	},
});
</script>



src/index.ts

import { defineInterface } from '@directus/extensions-sdk';
import InterfaceComponent from './interface.vue';

export default defineInterface({
	id: 'custom',
	name: 'Custom',
	icon: 'box',
	description: 'This is my custom interface!',
	component: InterfaceComponent,
	options: null,//这里可以外部传入自定义的逻辑处理显示的交互
	types: ['string'],//这里对应数据字段类型,只有匹配的类型才能选择该display
}); 

3.operation

operation: 就是在flow里面的框框,有输入和输出。可以做更细粒度的业务封装

和上面相同操作,我们创建operation

cd extensions/operations #进入扩展路径
npx create-directus-extension@latest # 执行扩展安装
选择 operation 》 typescript    

修改配置 我们调整mytest的package.json输出路径

    "directus:extension": {
            "type": "interface",
            "path": "./index.js",
            "source": "src/index.ts",
            "host": "^10.1.14"
    },

生成代码

image.png

测试一把

运行

 cd ./extensions/operation/mytest
 npm run dev

发现directus已经热加载最新operation插件

image.png

新建一个get请求的flow

image.png

创建一个operation,发现底部多了一个custom选项

image.png 选中并输入11111 image.png

访问get请求

http://127.0.0.1:8055/flows/trigger/ed2f22a8-ae4e-4ee8-9cbe-9b72a5195ba8

可以看到控制台输出 11111

image.png

代码分析

src/api.ts 运行时的代码

import { defineOperationApi } from '@directus/extensions-sdk';

type Options = {
	text: string;
};

export default defineOperationApi<Options>({
	id: 'custom',
	handler: ({ text }) => {
		console.log(text); //这里把接收的参数打印到控制台
	},
});


src/app.ts 用于定义operation的配置项

import { defineOperationApp } from '@directus/extensions-sdk';

export default defineOperationApp({
	id: 'custom',
	name: 'Custom1',
	icon: 'box',
	description: 'This is my custom operation!', //描述信息
	overview: ({ text }) => [
		{
			label: 'Text',
			text: text,
		},
	],
	options: [ //定义了一个文本输入框展示方式
		{
			field: 'text',
			name: 'Text',
			type: 'string',
			meta: {
				width: 'full',
				interface: 'input',
			},
		},
	],
});


4.endpoint

endpoint: 后台express的一个路由服务

和上面相同操作,我们创建endpoint

cd extensions/endpoints #进入扩展路径
npx create-directus-extension@latest # 执行扩展安装
选择 endpoint 》 typescript    

修改配置 我们调整mytest的package.json输出路径

    "directus:extension": {
            "type": "interface",
            "path": "./index.js",
            "source": "src/index.ts",
            "host": "^10.1.14"
    },

生成代码

image.png

测试一把

运行

 cd ./extensions/endpoint/mytest
 npm run dev

发现directus已经热加载最新endpoint插件

image.png

在地址栏访问 127.0.0.1:8055/mytest/

输出hello word!

image.png

代码分析

src/index.ts

import { defineEndpoint } from '@directus/extensions-sdk';

export default defineEndpoint((router) => {
	router.get('/', (_req, res) => res.send('Hello, World!'));
});

可以看到写法其实就是一个express的服务,默认路由前缀是 mytest

5.hook

hook: 监听数据变化钩子函数

和上面相同操作,我们创建hook

cd extensions/hooks #进入扩展路径
npx create-directus-extension@latest # 执行扩展安装
选择 hook 》 typescript    

修改配置 我们调整mytest的package.json输出路径

    "directus:extension": {
            "type": "interface",
            "path": "./index.js",
            "source": "src/index.ts",
            "host": "^10.1.14"
    },

生成代码

测试一把

运行

 cd ./extensions/hook/mytest
 npm run dev

发现directus已经热加载最新hook插件

image.png

测试新增一条city数据

image.png

发现控制台打印

image.png

代码分析

src/index.ts

import { defineHook } from '@directus/extensions-sdk';

export default defineHook(({ filter, action }) => {
	filter('items.create', () => {
		console.log('Creating Item!');
	});

	action('items.create', () => {
		console.log('Item created!');
	});
});

可以看到在数据发生变化的时候,监听了数据的创建过滤事件和动作事件

优化代码

我们把入参打印

import { defineHook } from '@directus/extensions-sdk';

export default defineHook(({ filter, action }) => {
	filter('items.create', (args) => {
		console.log('Creating Item!',args);
	});

	action('items.create', (args) => {
		console.log('Item created!',args);
	});
});

新建一条rrrr数据

image.png

可以获取变化数据,如id 参数 进行具体的业务逻辑处理

image.png

6.panel

panel: 后台报表分析的模块图像/表格化显示

和上面相同操作,我们创建panel

cd extensions/panels #进入扩展路径
npx create-directus-extension@latest # 执行扩展安装
选择 panel 》 typescript    

修改配置 我们调整mytest的package.json输出路径

    "directus:extension": {
            "type": "interface",
            "path": "./index.js",
            "source": "src/index.ts",
            "host": "^10.1.14"
    },

生成代码

image.png

测试一把

运行

 cd ./extensions/panel/mytest
 npm run dev

发现directus已经热加载最新panel插件

image.png

新建一个表盘

image.png

image.png image.png

image.png

image.png

image.png

我们可以新增一个省份的列表对比

image.png

image.png

代码分析

src/panel.vue

<template>
	<div class="text" :class="{ 'has-header': showHeader }">
		{{ text }}
	</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
	props: {
		showHeader: {
			type: Boolean,
			default: false,
		},
		text: {
			type: String,
			default: '',
		},
	},
});
</script>

<style scoped>
.text {
	padding: 12px;
}

.text.has-header {
	padding: 0 12px;
}
</style>

src/index.ts

import { definePanel } from '@directus/extensions-sdk';
import PanelComponent from './panel.vue';

export default definePanel({
	id: 'custom',
	name: 'Custom',
	icon: 'box',
	description: 'This is my custom panel!',
	component: PanelComponent,
	options: [
		{
			field: 'text',
			name: 'Text',
			type: 'string',
			meta: {
				interface: 'input',
				width: 'full',
			},
		},
	],
	minWidth: 12,
	minHeight: 8,
});

7.layout

layout: 后台列表页面的整体布局(列表或瀑布流)

和上面相同操作,我们创建layout

cd extensions/layouts #进入扩展路径
npx create-directus-extension@latest # 执行扩展安装
选择 layout 》 typescript    

修改配置 我们调整mytest的package.json输出路径

    "directus:extension": {
            "type": "interface",
            "path": "./index.js",
            "source": "src/index.ts",
            "host": "^10.1.14"
    },

生成代码

image.png

测试一把

运行

 cd ./extensions/layout/mytest
 npm run dev

发现directus已经热加载最新layout插件

image.png

我们在列表页面可以选择自定义的布局效果 image.png

由于我们没有重写对应列表逻辑。所以显示了默认的文本提示内容 image.png

代码分析

src/layout.vue

<template>
	<div>
		<p>Name: {{ name }}</p>
		<p>Collection: {{ collection }}</p>
	</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
	inheritAttrs: false,
	props: {
		collection: {
			type: String,
			required: true,
		},
		name: {
			type: String,
			required: true,
		},
	},
});
</script>

src/index.ts

import { ref } from 'vue';
import { defineLayout } from '@directus/extensions-sdk';
import LayoutComponent from './layout.vue';

export default defineLayout({
	id: 'custom',
	name: 'Custom',
	icon: 'box',
	component: LayoutComponent,
	slots: {
		options: () => null,
		sidebar: () => null,
		actions: () => null,
	},
	setup() {
		const name = ref('Custom Layout');

		return { name };
	},
});

8.module

module: 后台系统左边大菜单模块,可以定制更丰富的模块内容

和上面相同操作,我们创建module

cd extensions/modules #进入扩展路径
npx create-directus-extension@latest # 执行扩展安装
选择 module 》 typescript    

修改配置 我们调整mytest的package.json输出路径

    "directus:extension": {
            "type": "interface",
            "path": "./index.js",
            "source": "src/index.ts",
            "host": "^10.1.14"
    },

生成代码

image.png

测试一把

运行

 cd ./extensions/module/mytest
 npm run dev

发现directus已经热加载最新module插件

image.png

进入设置页面可以发现设置模块里面多出来一个custom image.png

勾选+保存

image.png 左侧菜单多出了对应的新菜单模块选项 image.png

9.bundle

bundle: 自定义库,可以一次性把上面所有扩展都写到一个库中,方便不同模块之间共享数据方法

和上面相同操作,我们创建bundle

mkdir bundles # 新建bundles文件夹
cd extensions/ #进入扩展路径
npx create-directus-extension@latest # 执行扩展安装 
? Choose the extension type bundle #选择 bundle 
? Choose a name for the extension directus-extension-bundle  # 名称命名为 directus-extension-bundle 

注意 : 名称必须是directus-extension-xxxxx 否则不能正常识别,并且是在extension路径下,不用嵌套两级目录

生成代码

image.png 我们看到其实只是生成的package.json和安装了一些依赖,并没有具体的代码

测试一把

运行

 cd mytest
 npm run dev 

发现directus已经热加载最新directus-extension-bundle自定义库 image.png

这里我们看到他生成的代码输出在 dist下的 app.js和api.js

image.png

image.png

分析生成代码

api.js

const hooks = [];const endpoints = [];const operations = [];
export { endpoints, hooks, operations };

app.js

const hooks = [];const endpoints = [];const operations = [];
export { endpoints, hooks, operations };

生成的api.js 和 app.js 其实就是把当前bundle里面定义的库再往外面再导出一遍,由于当前没有实际的extension代码。导出为空。directus启动的时候会去查找所有满足directus-extension-xx文件夹,并且加载dist/app.js和dist/api.js

创建子扩展代码

cd ./extensions/directus-extension-bundle/ # 进入bundle文件夹
sudo npm run add # 执行directus自带的 添加自定义库
Choose the extension type endpoint # 选择创建一个endpoint
? Choose a name for the entry myendpoint # 命名 myendpoint
? Choose the language to use typescript # 语言typescript

可以看到新增了src文件夹,并且创建了endpoint项目,默认输出hello word image.png

测试第二把

 cd directus-extension-bundle
 npm run dev  # 启动

控制台打印库已经热更新

image.png

访问路径 127.0.0.1:8055/myendpoint image.png

请求正常返回Hello, World

传送门

  1. 低代码平台directus 实战半年-入门篇 - 掘金 (juejin.cn)

  2. 低代码平台directus 实战半年-提升篇flow - 掘金 (juejin.cn)

  3. 低代码平台directus 实战半年-进阶篇 - 掘金 (juejin.cn)

  4. 低代码平台directus 实战半年-踩坑日志 - 掘金 (juejin.cn)