[add] WorkFlow 定义

This commit is contained in:
Max 2021-11-26 14:22:36 +08:00
parent 98377d4a3b
commit 668b33ffb7
23 changed files with 661 additions and 39 deletions

View file

@ -27,22 +27,24 @@ type Config struct {
// XiangConfig 象传应用引擎配置
type XiangConfig struct {
Mode string `json:"mode,omitempty" env:"XIANG_MODE" envDefault:"release"` // 象传引擎模式 debug/release/test
Source string `json:"source,omitempty" env:"XIANG_SOURCE" envDefault:"fs://."` // 源码路径(用于单元测试载入数据)
Path string `json:"path,omitempty" env:"XIANG_PATH" envDefault:"bin://xiang"` // 引擎文件目录
Root string `json:"root,omitempty" env:"XIANG_ROOT" envDefault:"fs://."` // 应用文件目录
RootUI string `json:"root_ui,omitempty" env:"XIANG_ROOT_UI"` // 应用界面静态文件目录
RootDB string `json:"root_db,omitempty" env:"XIANG_ROOT_DB"` // 应用SQLite数据库目录
RootData string `json:"root_data,omitempty" env:"XIANG_ROOT_DATA"` // 应用数据文件目录
RootAPI string `json:"root_api,omitempty" env:"XIANG_ROOT_API"` // 应用API文件目录
RootModel string `json:"root_model,omitempty" env:"XIANG_ROOT_MODEL"` // 应用模型文件目录
RootFLow string `json:"root_flow,omitempty" env:"XIANG_ROOT_FLOW"` // 应用业务逻辑文件目录
RootPlugin string `json:"root_plugin,omitempty" env:"XIANG_ROOT_PLUGIN"` // 应用业务插件文件目录
RootLib string `json:"root_lib,omitempty" env:"XIANG_ROOT_LIB"` // 应用资料库文件目录
RootTable string `json:"root_table,omitempty" env:"XIANG_ROOT_TABLE"` // 应用数据表格文件目录
RootChart string `json:"root_chart,omitempty" env:"XIANG_ROOT_CHART"` // 应用分析图表文件目录
RootPage string `json:"root_page,omitempty" env:"XIANG_ROOT_PAGE"` // 应用通用页面文件目录
RootScreen string `json:"root_screen,omitempty" env:"XIANG_ROOT_SCREEN"` // 应用大屏文件目录
Mode string `json:"mode,omitempty" env:"XIANG_MODE" envDefault:"release"` // 象传引擎模式 debug/release/test
Source string `json:"source,omitempty" env:"XIANG_SOURCE" envDefault:"fs://."` // 源码路径(用于单元测试载入数据)
Path string `json:"path,omitempty" env:"XIANG_PATH" envDefault:"bin://xiang"` // 引擎文件目录
Root string `json:"root,omitempty" env:"XIANG_ROOT" envDefault:"fs://."` // 应用文件目录
RootUI string `json:"root_ui,omitempty" env:"XIANG_ROOT_UI"` // 应用界面静态文件目录
RootDB string `json:"root_db,omitempty" env:"XIANG_ROOT_DB"` // 应用SQLite数据库目录
RootData string `json:"root_data,omitempty" env:"XIANG_ROOT_DATA"` // 应用数据文件目录
RootAPI string `json:"root_api,omitempty" env:"XIANG_ROOT_API"` // 应用API文件目录
RootModel string `json:"root_model,omitempty" env:"XIANG_ROOT_MODEL"` // 应用模型文件目录
RootFLow string `json:"root_flow,omitempty" env:"XIANG_ROOT_FLOW"` // 应用业务逻辑文件目录
RootPlugin string `json:"root_plugin,omitempty" env:"XIANG_ROOT_PLUGIN"` // 应用业务插件文件目录
RootLib string `json:"root_lib,omitempty" env:"XIANG_ROOT_LIB"` // 应用资料库文件目录
RootTable string `json:"root_table,omitempty" env:"XIANG_ROOT_TABLE"` // 应用数据表格文件目录
RootChart string `json:"root_chart,omitempty" env:"XIANG_ROOT_CHART"` // 应用分析图表文件目录
RootPage string `json:"root_page,omitempty" env:"XIANG_ROOT_PAGE"` // 应用通用页面文件目录
RootScreen string `json:"root_screen,omitempty" env:"XIANG_ROOT_SCREEN"` // 应用大屏文件目录
RootForm string `json:"root_form,omitempty" env:"XIANG_ROOT_FORM"` // 表单文件目录
RootWorkFlow string `json:"root_workflow,omitempty" env:"XIANG_ROOT_WORKFLOW"` // 工作流文件目录
}
// ServiceConfig 服务配置

View file

@ -20,5 +20,5 @@ func check(t *testing.T) {
for key := range gou.Flows {
keys = append(keys, key)
}
assert.Equal(t, 2, len(keys))
assert.Equal(t, 10, len(keys))
}

View file

@ -1,8 +1,8 @@
package helper
import "github.com/yaoapp/gou"
import "github.com/yaoapp/kun/maps"
// MapValues 返回映射的数值
// MapValues 返回映射的数值
func MapValues(record map[string]interface{}) []interface{} {
values := []interface{}{}
for _, value := range record {
@ -11,7 +11,7 @@ func MapValues(record map[string]interface{}) []interface{} {
return values
}
// MapKeys 返回映射的键
// MapKeys 返回映射的键
func MapKeys(record map[string]interface{}) []string {
keys := []string{}
for key := range record {
@ -20,16 +20,8 @@ func MapKeys(record map[string]interface{}) []string {
return keys
}
// ProcessMapValues xiang.helper.MapValues 返回映射的数值
func ProcessMapValues(process *gou.Process) interface{} {
process.ValidateArgNums(1)
record := process.ArgsMap(0)
return MapValues(record)
}
// ProcessMapKeys xiang.helper.MapKeys 返回映射的键
func ProcessMapKeys(process *gou.Process) interface{} {
process.ValidateArgNums(1)
record := process.ArgsMap(0)
return MapKeys(record)
// MapGet xiang.helper.MapGet 返回映射表给定键的值
func MapGet(record map[string]interface{}, key string) interface{} {
data := maps.MapOf(record).Dot()
return data.Get(key)
}

25
helper/map.process.go Normal file
View file

@ -0,0 +1,25 @@
package helper
import "github.com/yaoapp/gou"
// ProcessMapValues xiang.helper.MapValues 返回映射表的数值
func ProcessMapValues(process *gou.Process) interface{} {
process.ValidateArgNums(1)
record := process.ArgsMap(0)
return MapValues(record)
}
// ProcessMapKeys xiang.helper.MapKeys 返回映射表的键
func ProcessMapKeys(process *gou.Process) interface{} {
process.ValidateArgNums(1)
record := process.ArgsMap(0)
return MapKeys(record)
}
// ProcessMapGet xiang.helper.MapKey 返回映射表给定键的值
func ProcessMapGet(process *gou.Process) interface{} {
process.ValidateArgNums(2)
record := process.ArgsMap(0)
key := process.ArgsString(1)
return MapGet(record, key)
}

View file

@ -12,17 +12,24 @@ func init() {
gou.RegisterProcessHandler("xiang.helper.ArrayColumn", ProcessArrayColumn)
gou.RegisterProcessHandler("xiang.helper.ArrayKeep", ProcessArrayKeep)
gou.RegisterProcessHandler("xiang.helper.ArrayTree", ProcessArrayTree)
gou.RegisterProcessHandler("xiang.helper.MapKeys", ProcessMapKeys)
gou.RegisterProcessHandler("xiang.helper.MapValues", ProcessMapValues)
gou.RegisterProcessHandler("xiang.helper.MapGet", ProcessMapGet)
gou.RegisterProcessHandler("xiang.helper.Captcha", ProcessCaptcha)
gou.RegisterProcessHandler("xiang.helper.CaptchaValidate", ProcessCaptchaValidate)
gou.RegisterProcessHandler("xiang.helper.PasswordValidate", ProcessPasswordValidate)
gou.RegisterProcessHandler("xiang.helper.JwtMake", ProcessJwtMake)
gou.RegisterProcessHandler("xiang.helper.JwtValidate", ProcessJwtValidate)
gou.RegisterProcessHandler("xiang.helper.For", ProcessFor)
gou.RegisterProcessHandler("xiang.helper.Each", ProcessEach)
gou.RegisterProcessHandler("xiang.helper.Case", ProcessCase)
gou.RegisterProcessHandler("xiang.helper.IF", ProcessIF)
gou.RegisterProcessHandler("xiang.helper.Print", ProcessPrint)
}

View file

@ -13,7 +13,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/yaoapp/kun/any"
"github.com/yaoapp/kun/maps"
"github.com/yaoapp/kun/utils"
"github.com/yaoapp/xiang/config"
"github.com/yaoapp/xiang/service"
)
@ -53,7 +52,7 @@ func TestCommandStart(t *testing.T) {
// 发送请求
request := func() (maps.MapStr, error) {
time.Sleep(time.Microsecond * 2000)
url := fmt.Sprintf("http://%s:%d/api/user/find/1?select=id,name", "local.iqka.com", config.Conf.Service.Port)
url := fmt.Sprintf("http://%s:%d/api/user/find/1?select=id,name", "127.0.0.1", config.Conf.Service.Port)
// utils.Dump(url)
resp, err := http.Get(url)
if err != nil {
@ -70,11 +69,14 @@ func TestCommandStart(t *testing.T) {
}
// 等待服务启动
time.Sleep(time.Second * 2)
times := 0
for times < 20 { // 2秒超时
times++
fmt.Printf("Trying(%d)...", times)
res, err := request()
if err != nil {
fmt.Printf(" %s\n", err.Error())
continue
}
assert.Equal(t, 1, any.Of(res.Get("id")).CInt())
@ -98,8 +100,7 @@ func TestCommandStop(t *testing.T) {
// 发送请求
request := func() (maps.MapStr, error) {
time.Sleep(time.Microsecond * 2000)
url := fmt.Sprintf("http://%s:%d/api/user/find/1?select=id,name", "local.iqka.com", config.Conf.Service.Port)
utils.Dump(url)
url := fmt.Sprintf("http://%s:%d/api/user/find/1?select=id,name", "127.0.0.1", config.Conf.Service.Port)
resp, err := http.Get(url)
if err != nil {
return nil, err
@ -115,6 +116,7 @@ func TestCommandStop(t *testing.T) {
}
// 等待服务启动
time.Sleep(time.Second * 2)
times := 0
for times < 20 { // 2秒超时
times++
@ -127,7 +129,7 @@ func TestCommandStop(t *testing.T) {
// 测试关闭
service.Stop(func() { log.Println("服务已关闭") })
time.Sleep(time.Second * 2)
time.Sleep(time.Second * 5)
_, err = request()
assert.NotNil(t, err)
return

View file

@ -19,7 +19,7 @@ var AppFileServer http.Handler = http.FileServer(http.Dir(config.Conf.RootUI))
// Middlewares 服务中间件
var Middlewares = []gin.HandlerFunc{
BindDomain,
// BindDomain,
BinStatic,
}

View file

@ -48,5 +48,5 @@ func check(t *testing.T) {
for key := range Tables {
keys = append(keys, key)
}
assert.Equal(t, 3, len(keys))
assert.Equal(t, 4, len(keys))
}

View file

@ -0,0 +1,43 @@
{
"label": "审批驳回并重置状态",
"version": "1.0.0",
"description": "审批驳回并重置状态",
"nodes": [
{
"name": "当前用户",
"process": "session.Get",
"args": ["user"]
},
{
"name": "工作流",
"process": "xiang.workflow.Save",
"args": [
"{{$res.当前用户.id}}",
"{{$name}}",
"{{$node}}",
"{{$id}}",
"{{$input}}"
]
},
{
"name": "审批驳回",
"process": "xiang.workflow.Next",
"args": [
"{{$res.当前用户.id}}",
"{{$res.工作流.id}}",
{ "审批状态": "驳回", "驳回原因": "{{$input.reason}}" }
]
},
{
"name": "重置工作流",
"process": "xiang.workflow.Goto",
"args": [
"{{$res.当前用户.id}}",
"{{$res.工作流.id}}",
"选择商务负责人",
{}
]
}
],
"output": "{{$res.重置工作流}}"
}

View file

@ -0,0 +1,38 @@
{
"label": "审批驳回并关闭流程",
"version": "1.0.0",
"description": "审批驳回并关闭流程",
"nodes": [
{
"name": "当前用户",
"process": "session.Get",
"args": ["user"]
},
{
"name": "工作流",
"process": "xiang.workflow.Save",
"args": [
"{{$res.当前用户.id}}",
"{{$name}}",
"{{$node}}",
"{{$id}}",
"{{$input}}"
]
},
{
"name": "审批驳回",
"process": "xiang.workflow.Next",
"args": [
"{{$res.当前用户.id}}",
"{{$res.工作流.id}}",
{ "审批状态": "驳回", "驳回原因": "{{$input.reason}}" }
]
},
{
"name": "关闭流程",
"process": "xiang.workflow.Status",
"args": ["{{$res.工作流.id}}", "已关闭"]
}
],
"output": "{{$res.关闭流程}}"
}

View file

@ -0,0 +1,33 @@
{
"label": "指派信息审批通过",
"version": "1.0.0",
"description": "指派信息审批通过",
"nodes": [
{
"name": "当前用户",
"process": "session.Get",
"args": ["user"]
},
{
"name": "工作流",
"process": "xiang.workflow.Save",
"args": [
"{{$res.当前用户.id}}",
"{{$name}}",
"{{$node}}",
"{{$id}}",
"{{$input}}"
]
},
{
"name": "审批通过",
"process": "xiang.workflow.Next",
"args": [
"{{$res.当前用户.id}}",
"{{$res.工作流.id}}",
{ "审批状态": "通过" }
]
}
],
"output": "{{$res.审批通过}}"
}

View file

@ -0,0 +1,24 @@
{
"label": "保存指派信息",
"version": "1.0.0",
"description": "保存指派信息",
"nodes": [
{
"name": "当前用户",
"process": "session.Get",
"args": ["user"]
},
{
"name": "工作流",
"process": "xiang.workflow.Save",
"args": [
"{{$res.当前用户.id}}",
"{{$name}}",
"{{$node}}",
"{{$id}}",
"{{$input}}"
]
}
],
"output": "{{$res.工作流}}"
}

View file

@ -0,0 +1,29 @@
{
"label": "提交指派信息",
"version": "1.0.0",
"description": "提交指派信息",
"nodes": [
{
"name": "当前用户",
"process": "session.Get",
"args": ["user"]
},
{
"name": "工作流",
"process": "xiang.workflow.Save",
"args": [
"{{$res.当前用户.id}}",
"{{$name}}",
"{{$node}}",
"{{$id}}",
"{{$input}}"
]
},
{
"name": "提交审批",
"process": "xiang.workflow.Next",
"args": ["{{$res.当前用户.id}}", "{{$res.工作流.id}}"]
}
],
"output": "{{$res.关闭流程}}"
}

View file

@ -0,0 +1,29 @@
{
"label": "关闭流程",
"version": "1.0.0",
"description": "关闭流程",
"nodes": [
{
"name": "当前用户",
"process": "session.Get",
"args": ["user"]
},
{
"name": "工作流",
"process": "xiang.workflow.Save",
"args": [
"{{$res.当前用户.id}}",
"{{$name}}",
"{{$node}}",
"{{$id}}",
"{{$input}}"
]
},
{
"name": "关闭流程",
"process": "xiang.workflow.Status",
"args": ["{{$res.当前用户.id}}", "{{$res.工作流.id}}", "已关闭"]
}
],
"output": "{{$res.关闭流程}}"
}

View file

@ -0,0 +1,18 @@
{
"label": "当前用户",
"version": "1.0.0",
"description": "读取当前用户",
"nodes": [
{
"name": "用户",
"process": "session.Get",
"args": ["user"]
},
{
"name": "ID",
"process": "xiang.helper.MapGet",
"args": ["{{$res.用户}}", "ID"]
}
],
"output": 1
}

View file

@ -0,0 +1,19 @@
{
"label": "选择管理者",
"version": "1.0.0",
"description": "选择管理者",
"nodes": [
{
"name": "负责人",
"engine": "xiang",
"query": {
"select": ["id"],
"from": "$xiang.user",
"wheres": [{ ":type": "类型", "=": "staff" }],
"first": true
},
"orders": [":RAND()"]
}
],
"output": "{{$res.负责人.id}}"
}

30
tests/libs/assign.json Normal file
View file

@ -0,0 +1,30 @@
{
"保存": {
"label": "保存",
"api": "/api/xiang/workflow/assign/save"
},
"提交": {
"label": "提交",
"api": "/api/xiang/workflow/assign/submit"
},
"同意": {
"label": "同意",
"api": "/api/xiang/workflow/assign/resolve"
},
"驳回并重填": {
"label": "驳回并重填",
"api": "/api/xiang/workflow/assign/reject/reset"
},
"驳回并关闭": {
"label": "驳回并关闭",
"api": "/api/xiang/workflow/assign/reject/reset"
},
"审批同意": {
"label": "审批同意",
"api": "/api/xiang/workflow/assign/terminal"
},
"关闭": {
"label": "关闭",
"api": "/api/xiang/workflow/terminal"
}
}

View file

@ -39,5 +39,29 @@
"format": "YYYY年MM月DD日 @hh:mm:ss"
}
}
},
"商务负责人": {
"label": "商务负责人",
"view": {
"name": "label",
"props": {
"value": ":kind.name"
}
},
"edit": {
"type": "select",
"props": {
"value": ":kind.id",
"tree": true,
"searchable": true,
"remote": {
"api": "/api/kind/tree",
"query": {
"select": ["id", "name"],
"keyword": "where_name_like"
}
}
}
}
}
}

View file

@ -0,0 +1,16 @@
{
"name": "指派商务负责人",
"version": "1.0.0",
"decription": "指派商务负责人表单(?Next:表格和表单解耦)",
"bind": { "model": "service" },
"edit": {
"primary": "id",
"layout": {
"fieldset": [
{
"columns": [{ "name": "商务负责人", "width": 24 }]
}
]
}
}
}

View file

@ -0,0 +1,83 @@
{
"label": "指派商务负责人",
"version": "1.0.0",
"description": "指派商务负责人",
"apis": {
"/save": {
"name": "保存",
"process": "xiang.assign.save"
},
"/terminal": {
"name": "关闭",
"process": "xiang.assign.terminal"
},
"/submit": {
"name": "提交审批",
"process": "flows.assign.submit"
},
"/reject/reset": {
"name": "审批驳回并重填",
"process": "flows.assign.reject.rest"
},
"/reject/terminal": {
"name": "审批驳回并关闭",
"process": "flows.assign.reject.terminal"
},
"/resolve": {
"name": "审批同意",
"process": "flows.assign.resolve"
}
},
"actions": {
"关闭": { "@": "assign.关闭" },
"保存": { "@": "assign.保存" },
"提交": { "@": "assign.提交" },
"同意": { "@": "assign.同意" },
"驳回并重填": { "@": "assign.驳回并重填" },
"驳回并关闭": { "@": "assign.驳回并关闭" }
},
"nodes": [
{
"name": "选择商务负责人",
"body": {
"type": "form",
"props": { "name": "assign" }
},
"actions": ["关闭", "保存", "提交"],
"user": { "process": "flows.current.user", "args": ["id"] }
},
{
"name": "项目负责人审批",
"body": {
"type": "iframe",
"props": { "src": "/workflows/assign/approve.html" }
},
"actions": ["驳回并重填", "驳回并关闭", "同意"],
"user": { "process": "flows.select.manager", "args": ["id"] }
},
{
"name": "审批通过",
"when": [{ "审批通过": "{{$data.审批状态}}", "=": "通过" }],
"body": {
"type": "markdown",
"props": {
"content": "审批通过, {{$data.项目名称}}商务负责人为: **{{$data.商务负责人名称}}**"
}
},
"actions": ["关闭"],
"user": { "process": "flows.current.user", "args": ["id"] }
},
{
"name": "审批驳回",
"when": [{ "审批通过": "{{$data.审批状态}}", "=": "驳回" }],
"body": {
"type": "markdown",
"props": {
"content": "您的请求被驳回, 驳回原因: **{{$data.驳回原因}}**"
}
},
"actions": ["关闭"],
"user": { "process": "flows.current.user", "args": ["id"] }
}
]
}

35
workflow/types.go Normal file
View file

@ -0,0 +1,35 @@
package workflow
import "github.com/yaoapp/xiang/share"
// WorkFlow 工作流配置结构
type WorkFlow struct {
Name string `json:"-"`
Source string `json:"-"`
Version string `json:"version"`
Label string `json:"label,omitempty"`
Decription string `json:"decription,omitempty"`
Nodes []Node `json:"nodes"`
APIs []API `json:"apis"`
}
// Node 工作流节点
type Node struct {
Name string `json:"name"`
Body share.Render `json:"body,omitempty"`
Actions []string `json:"actions,omitempty"`
User User `json:"user,omitempty"`
}
// User 工作相关用户读取条件
type User struct {
Process string `json:"process"`
Args []interface{} `json:"args"`
}
// API 工作相关API
type API struct {
Name string `json:"name"`
Process string `json:"process"`
Args []interface{} `json:"args"`
}

49
workflow/workflow.go Normal file
View file

@ -0,0 +1,49 @@
package workflow
import "github.com/yaoapp/xiang/config"
// WorkFlows 工作流列表
var WorkFlows = map[string]*WorkFlow{}
// Load 加载数据表格
func Load(cfg config.Config) {
LoadFrom(cfg.RootWorkFlow, "")
}
// LoadFrom 从特定目录加载
func LoadFrom(dir string, prefix string) {}
// Select 读取已加载图表
func Select(name string) *WorkFlow {
return WorkFlows[name]
}
// Process
// 读取工作流 xiang.workflow.Get(uid, name, data_id)
// 保存工作流 xiang.workflow.Save(uid, name, node, input)
// 进入下一个节点 xiang.workflow.Next(uid, id, input)
// 跳转到指定节点 xiang.workflow.Goto(uid, id, node, input)
// API:
// 读取工作流 GET /api/xiang/workflow/<工作流名称>/get
// 读取工作流配置 GET /api/xiang/workflow/<工作流名称>/setting
// 调用自定义API POST /api/xiang/workflow/<工作流名称>/<自定义API路由>
// Setting 返回配置信息
func (workflow *WorkFlow) Setting(id int) {}
// SetupAPIs 注册API
func (workflow *WorkFlow) SetupAPIs(id int) {}
// Get 读取当前工作流(未完成的)
func (workflow *WorkFlow) Get(uid int, name string, dataID interface{}) {}
// Save 保存工作流节点数据
func (workflow *WorkFlow) Save(uid int, name string, node string, dataID interface{}, input map[string]interface{}) {
}
// Next 下一个工作流
func (workflow *WorkFlow) Next(uid int, id int, input map[string]interface{}) {}
// Goto 工作流跳转
func (workflow *WorkFlow) Goto(uid int, id int, node string, input map[string]interface{}) {}

View file

@ -0,0 +1,124 @@
{
"name": "工作流",
"table": {
"name": "xiang_workflow",
"comment": "工作流数据表",
"engine": "InnoDB"
},
"columns": [
{ "label": "ID", "name": "id", "type": "ID" },
{
"label": "名称",
"comment": "工作流名称",
"name": "name",
"type": "string",
"length": 255,
"index": true,
"validations": [
{
"method": "typeof",
"args": ["string"],
"message": "{{input}}类型错误, {{label}}应该为字符串"
}
]
},
{
"label": "数据标识",
"comment": "关联数据标识, 关联数据模型表格ID",
"name": "data_id",
"type": "bigInteger",
"index": true,
"nullable": true
},
{
"label": "节点",
"comment": "当前节点名称",
"name": "node_name",
"type": "string",
"length": 255,
"index": true,
"nullable": true
},
{
"label": "节点状态",
"comment": "当前节点状态",
"name": "node_status",
"type": "enum",
"default": "未开始",
"option": ["未开始", "进行中", "已完成"],
"index": true,
"validations": [
{
"method": "typeof",
"args": ["string"],
"message": "{{input}}类型错误, {{label}}应该为字符串"
},
{
"method": "enum",
"args": ["未开始", "进行中", "已完成"],
"message": "{{input}}不在许可范围, {{label}}应该为 未开始/进行中/已完成"
}
]
},
{
"label": "当前处理人",
"comment": "当前处理人",
"name": "user_id",
"type": "bigInteger",
"index": true,
"nullable": true
},
{
"label": "处理人",
"comment": "处理人选取条件",
"name": "user",
"type": "json",
"nullable": true
},
{
"label": "数据",
"comment": "各节点输入数据(Key-Value)",
"name": "input",
"type": "json",
"nullable": true
},
{
"label": "处理结果",
"comment": "各节点处理结果(Key-Value)",
"name": "output",
"type": "json",
"nullable": true
},
{
"label": "操作记录",
"comment": "操作记录",
"name": "history",
"type": "json",
"nullable": true
},
{
"label": "状态",
"comment": "流程状态",
"name": "status",
"type": "enum",
"default": "未开始",
"option": ["未开始", "进行中", "已完成", "已关闭"],
"index": true,
"validations": [
{
"method": "typeof",
"args": ["string"],
"message": "{{input}}类型错误, {{label}}应该为字符串"
},
{
"method": "enum",
"args": ["未开始", "进行中", "已完成", "已关闭"],
"message": "{{input}}不在许可范围, {{label}}应该为 未开始/进行中/已完成/已关闭"
}
]
}
],
"indexes": [],
"option": { "timestamps": true, "soft_deletes": true }
}