Spark Workbench 自定义字段类型插件开发指南
本篇面向需要二次扩展 Spark Workbench 任务字段类型 的开发者。目标是让第三方插件可以像 Spark 默认插件一样,独立交付字段类型定义、系统字段定义、字段配置逻辑、查询操作符,以及对应的前端 Vue 组件。
默认字段插件位于:
spark-plugins/spark-workbench-default-plugin/
Spark 内置的 Text、Rich Text、Markdown、User、Attachment 等字段类型,以及 Title、Assignee、Description 等系统字段,都通过这个插件注册。平台主工程只消费扩展点,不再把具体字段类型硬编码在任务设计器里。
1. 字段插件模型
一个字段类型插件通常包含两部分:
- 后端扩展点:通过 Lattice Ability 返回字段类型、系统字段、字段配置表单、支持的 TQL 操作符和过滤器协议。
- 前端组件:通过 Vue 组件渲染创建 / 编辑态控件,也可以提供查看态和任务详情态组件。
推荐结构:
my-task-field-plugin/
pom.xml
src/main/java/
.../MyTaskFieldPlugin.java
.../MyFieldRegistryExt.java
.../PriorityFieldTypeProvider.java
src/main/frontend/
package.json
vite.config.js
src/
index.js
PriorityField.vue
PriorityDetailField.vue
插件 jar 应同时包含 Java 扩展点和前端静态资源,运行期由 Spark 自动加载。
2. 业务身份
插件必须声明自己的 Lattice 业务身份:
@Business(code = "my.task.field.plugin", name = "My Task Field Plugin")
public class MyTaskFieldPlugin extends BusinessTemplate {
}
字段注册时,Spark 会遍历当前 Lattice 中所有已注册业务身份,并对每个业务身份调用字段注册 Ability。第三方插件不要把 @Realization 写到平台内置 code 上,应使用自己插件的业务身份。
3. 后端注册字段类型
字段注册扩展点为 SparkEraTaskFieldRegistryExt,通常继承空实现:
@Realization(codes = "my.task.field.plugin")
public class MyFieldRegistryExt extends BlankSparkEraTaskFieldRegistryExt {
private static final String FRONTEND_RESOURCE =
"/spark-gadgets/my-task-field-plugin/index.umd.js";
@Override
public List<SparkEraTaskCustomFieldTypeProvider> listCustomFieldTypeProviders(
SparkEraTaskFieldRegistryContext context) {
return List.of(new PriorityFieldTypeProvider(FRONTEND_RESOURCE));
}
}
字段类型 Provider 示例:
public class PriorityFieldTypeProvider implements SparkEraTaskCustomFieldTypeProvider {
private final String frontendResource;
public PriorityFieldTypeProvider(String frontendResource) {
this.frontendResource = frontendResource;
}
@Override
public String getCode() {
return "priority_score";
}
@Override
public String getTitle() {
return "Priority Score";
}
@Override
public SparkEraTaskFieldCategory getCategory() {
return SparkEraTaskFieldCategory.BASIC;
}
@Override
public String getDescription() {
return "A numeric priority score with visual emphasis.";
}
@Override
public String getComponentType() {
return "priorityScore";
}
@Override
public String getTaskFieldType() {
return "number";
}
@Override
public String getPreviewIcon() {
return "gauge";
}
@Override
public boolean isSortable() {
return true;
}
@Override
public List<String> supportOperators() {
return List.of("eq", "neq", "gt", "ge", "lt", "le");
}
@Override
public String getFrontendResource() {
return frontendResource;
}
@Override
public String getEditComponent() {
return "my$field-priority-score";
}
@Override
public String getDetailComponent() {
return "my$task-detail-priority-score-field";
}
}
关键字段说明:
code:字段类型编码,建议全局唯一。componentType:运行期控件类型语义。taskFieldType:底层值类型,影响查询、索引和展示。previewIcon:任务设计器字段调色盘图标。supportOperators:TQL 可用操作符,不应返回全集。frontendResource:插件前端 UMD 入口。editComponent:创建 / 编辑态 Vue 组件 key。readonlyComponent:普通只读表单组件 key,可为空。detailComponent:任务详情奥创协议组件 key。
4. 注册系统字段
系统字段使用 SparkEraTaskSystemFieldProvider:
@Override
public List<SparkEraTaskSystemFieldProvider> listSystemFieldProviders(
SparkEraTaskFieldRegistryContext context) {
return List.of(new EscalationLevelSystemFieldProvider(FRONTEND_RESOURCE));
}
系统字段 Provider 需要返回稳定 code、展示 label、字段类型、是否可列表展示、是否可搜索、是否可排序以及操作符。系统字段同样可以声明 frontendResource 和组件 key。
5. 前端注册组件
插件前端入口通过宿主暴露的 window.__SPARK_TASK_FIELDS__ 注册组件:
import PriorityField from "./PriorityField.vue";
import PriorityDetailField from "./PriorityDetailField.vue";
const api = typeof window === "undefined" ? null : window.__SPARK_TASK_FIELDS__;
if (api?.registerFieldComponent) {
api.registerFieldComponent("my$field-priority-score", PriorityField);
}
if (api?.registerDetailFieldComponent) {
api.registerDetailFieldComponent("my$task-detail-priority-score-field", PriorityDetailField);
}
创建 / 编辑态组件接收的主要 props:
defineProps({
modelValue: [String, Number, Boolean, Array, Object],
field: Object,
definition: Object,
placeholder: String,
disabled: Boolean,
context: Object
});
组件通过 emit("update:modelValue", value) 写回字段值。context 中包含宿主提供的工具能力,例如 t、apiFetch、openUserSelect、chooseAttachmentFiles 等;第三方组件应优先复用宿主能力,而不是重复实现平台通用交互。
任务详情态组件接入奥创渲染协议,接收 componentModel、renderContext 和 t:
<template>
<div class="priority-detail">
<span>{{ field.label }}</span>
<strong>{{ field.value }}</strong>
</div>
</template>
<script setup>
import { computed } from "vue";
const props = defineProps({
componentModel: Object
});
const field = computed(() => props.componentModel?.props?.field || {});
</script>
6. Vite 构建
字段插件前端和 Gadget 插件一样,使用 Vite library 模式,并把 Vue 设为 external:
import { resolve } from "node:path";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [vue()],
build: {
outDir: resolve(__dirname, "../../../target/classes/META-INF/resources/spark-gadgets/my-task-field-plugin"),
lib: {
entry: resolve(__dirname, "src/index.js"),
name: "MyTaskFieldPlugin",
formats: ["umd"],
fileName: () => "index.umd.js",
cssFileName: "index"
},
rollupOptions: {
external: ["vue"],
output: {
globals: {
vue: "__SPARK_VUE__"
}
}
}
}
});
构建后,运行期资源 URL 为:
/spark-gadgets/my-task-field-plugin/index.umd.js
7. 开发检查清单
- 字段类型编码、系统字段编码必须稳定。
@Realization(codes = "...")必须使用插件自己的业务身份。- 查询操作符要和字段值类型匹配,不能简单返回全集。
- 可排序能力应由字段类型明确声明,富文本、附件、长文本通常不建议排序。
- 前端组件不要打包独立 Vue 运行时。
- 组件样式使用 Spark 主题变量,避免写死单一主题颜色。
- 字段 label、description、配置项文案应通过后端多语言机制返回。