跳到主要内容

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、配置项文案应通过后端多语言机制返回。