介绍
directus 支持extension扩展有多种方式
- display: 前端vue页面二次开发展示,用于定制后台列表页面的显示,对应某个字段
- interface: 前端vue页面二次开发展示,用于定制后台详情页面的显示,对应某个字段
- operation: 在flow里面的框框,有输入和输出。可以做更细粒度的业务封装
- endpoint: 后台express的一个路由RESTful服务
- hook: 监听数据变化钩子函数
- layout: 后台列表页面的整体布局(列表或瀑布流)
- panel: 后台报表分析的模块图像/表格化显示
- module: 后台系统左边大菜单模块,可以定制更丰富的模块内容
1. displays
displays:前端vue页面二次开发展示,用于定制后台列表页面的显示,对应某个字段
我们用官方自带脚手架定制一个后台的displays
sh
cd extensions/displays #进入扩展路径
npx create-directus-extension@latest # 执行扩展安装
# 提示
Need to install the following packages:
[email protected]
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
生成的代码
为了方便调整 我们打开项目的热更新
js
EXTENSIONS_AUTO_RELOAD=true
EXTENSIONS_PATH="./extensions"
启动服务,发现缺少index.js 因为自定义display打包路径是mytest/dist/index.js,而directus访问的路径是mytest/index.js
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输出路径
json
"directus:extension": {
"type": "display",
"path": "dist/index.js",
"source": "src/index.ts",
"host": "^10.1.14"
},
把 "path": "dist/index.js",
调整为 "path": "./index.js",
再次启动,已经提示正常加载组件
测试一把
进入city表,选择name编辑 在显示拦新增了custom选项

我们对比选中自定义的name显示区别。 其实就是把字段通过自定义组件显示
代码分析
src/display.vue
生成的是vue3代码
html
<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
js
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. interfaces
interfaces: 前端vue页面二次开发展示,用于定制后台详情页面的显示,对应某个字段
和display相同操作,我们创建interface
sh
cd extensions/displays #进入扩展路径
npx create-directus-extension@latest # 执行扩展安装
选择 interface 》 typescript
修改配置 我们调整mytest的package.json输出路径
json
"directus:extension": {
"type": "interface",
"path": "./index.js",
"source": "src/index.ts",
"host": "^10.1.14"
},
生成代码
测试一把
运行
js
cd ./extensions/interfaces/mytest
npm run dev
发现directus已经热加载最新interface插件
开始设置 在city -> name字段 接口栏发现 custom

对比详情页面差异
代码分析
src/interface.vue
生成的是vue3代码
html
<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
js
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
sh
cd extensions/displays #进入扩展路径
npx create-directus-extension@latest # 执行扩展安装
选择 operation 》 typescript
修改配置 我们调整mytest的package.json输出路径
json
"directus:extension": {
"type": "interface",
"path": "./index.js",
"source": "src/index.ts",
"host": "^10.1.14"
},
生成代码

测试一把
运行
js
cd ./extensions/operation/mytest
npm run dev
发现directus已经热加载最新operation插件

新建一个get请求的flow

创建一个operation,发现底部多了一个custom选项
选中并输入11111
访问get请求
http://127.0.0.1:8055/flows/trigger/ed2f22a8-ae4e-4ee8-9cbe-9b72a5195ba8
可以看到控制台输出 11111

代码分析
src/api.ts 运行时的代码
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的配置项
js
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
sh
cd extensions/displays #进入扩展路径
npx create-directus-extension@latest # 执行扩展安装
选择 endpoint 》 typescript
修改配置 我们调整mytest的package.json输出路径
json
"directus:extension": {
"type": "interface",
"path": "./index.js",
"source": "src/index.ts",
"host": "^10.1.14"
},
生成代码

测试一把
运行
js
cd ./extensions/endpoint/mytest
npm run dev
发现directus已经热加载最新endpoint插件

在地址栏访问 127.0.0.1:8055/mytest/
输出hello word!

代码分析
src/index.ts
js
import { defineEndpoint } from '@directus/extensions-sdk';
export default defineEndpoint((router) => {
router.get('/', (_req, res) => res.send('Hello, World!'));
});
可以看到写法其实就是一个express的服务,默认路由前缀是 mytest
5.hook
hook: 监听数据变化钩子函数
和上面相同操作,我们创建hook
sh
cd extensions/displays #进入扩展路径
npx create-directus-extension@latest # 执行扩展安装
选择 hook 》 typescript
修改配置 我们调整mytest的package.json输出路径
json
"directus:extension": {
"type": "interface",
"path": "./index.js",
"source": "src/index.ts",
"host": "^10.1.14"
},
生成代码
测试一把
运行
js
cd ./extensions/hook/mytest
npm run dev
发现directus已经热加载最新hook插件

测试新增一条city数据

发现控制台打印

代码分析
src/index.ts
js
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!');
});
});
可以看到在数据发生变化的时候,监听了数据的创建过滤事件和动作事件
优化代码
我们把入参打印
js
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数据

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

6.panel
panel: 后台报表分析的模块图像/表格化显示
和上面相同操作,我们创建panel
sh
cd extensions/displays #进入扩展路径
npx create-directus-extension@latest # 执行扩展安装
选择 panel 》 typescript
修改配置 我们调整mytest的package.json输出路径
json
"directus:extension": {
"type": "interface",
"path": "./index.js",
"source": "src/index.ts",
"host": "^10.1.14"
},
生成代码

测试一把
运行
js
cd ./extensions/panel/mytest
npm run dev
发现directus已经热加载最新panel插件

新建一个表盘




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


代码分析
src/panel.vue
html
<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
js
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
sh
cd extensions/displays #进入扩展路径
npx create-directus-extension@latest # 执行扩展安装
选择 layout 》 typescript
修改配置 我们调整mytest的package.json输出路径
json
"directus:extension": {
"type": "interface",
"path": "./index.js",
"source": "src/index.ts",
"host": "^10.1.14"
},
生成代码

测试一把
运行
js
cd ./extensions/layout/mytest
npm run dev
发现directus已经热加载最新layout插件

我们在列表页面可以选择自定义的布局效果
由于我们没有重写对应列表逻辑。所以显示了默认的文本提示内容
代码分析
src/layout.vue
html
<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
js
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
sh
cd extensions/displays #进入扩展路径
npx create-directus-extension@latest # 执行扩展安装
选择 module 》 typescript
修改配置 我们调整mytest的package.json输出路径
json
"directus:extension": {
"type": "interface",
"path": "./index.js",
"source": "src/index.ts",
"host": "^10.1.14"
},
生成代码

测试一把
运行
js
cd ./extensions/module/mytest
npm run dev
发现directus已经热加载最新module插件

进入设置页面可以发现设置模块里面多出来一个custom
勾选+保存
左侧菜单多出了对应的新菜单模块选项