跳至主要内容

Spark Workbench 自訂欄位類型插件開發指南

本篇面向需要二次擴展 Spark Workbench 任務欄位類型 的開發者。目標是讓第三方插件可以像 Spark 預設插件一樣,獨立交付欄位類型定義、系統欄位定義、欄位配置邏輯、TQL 操作符,以及對應的前端 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 等;第三方組件應優先複用宿主能力,而不是重複實作平台通用互動。

任務詳情態組件接入 Spark 渲染協議,接收 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、配置項文案應透過後端多語言機制返回。