Merge pull request #266 from trheyi/main
[change] the default application
This commit is contained in:
commit
1610ba85f5
16 changed files with 500 additions and 2459 deletions
7
.github/workflows/release-linux.yml
vendored
7
.github/workflows/release-linux.yml
vendored
|
|
@ -79,6 +79,12 @@ jobs:
|
|||
#ref: 5002c3fded585aaa69a4366135b415ea3234964e
|
||||
path: xgen-v1.0
|
||||
|
||||
- name: Checkout Yao-Init
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: yaoapp/yao-init
|
||||
path: yao-init
|
||||
|
||||
- name: Move Kun, Xun, Gou, UI, V8Go
|
||||
run: |
|
||||
mv kun ../
|
||||
|
|
@ -87,6 +93,7 @@ jobs:
|
|||
mv v8go ../
|
||||
mv xgen-v0.9 ../
|
||||
mv xgen-v1.0 ../
|
||||
mv yao-init ../
|
||||
ls -l .
|
||||
ls -l ../
|
||||
|
||||
|
|
|
|||
7
.github/workflows/release-macos.yml
vendored
7
.github/workflows/release-macos.yml
vendored
|
|
@ -74,6 +74,12 @@ jobs:
|
|||
# ref: 5002c3fded585aaa69a4366135b415ea3234964e
|
||||
path: xgen-v1.0
|
||||
|
||||
- name: Checkout Yao-Init
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: yaoapp/yao-init
|
||||
path: yao-init
|
||||
|
||||
- name: Move Kun, Xun, Gou, UI, V8Go
|
||||
run: |
|
||||
mv kun ../
|
||||
|
|
@ -82,6 +88,7 @@ jobs:
|
|||
mv v8go ../
|
||||
mv xgen-v0.9 ../
|
||||
mv xgen-v1.0 ../
|
||||
mv yao-init ../
|
||||
ls -l .
|
||||
ls -l ../
|
||||
|
||||
|
|
|
|||
49
Makefile
49
Makefile
|
|
@ -139,12 +139,28 @@ pack: bindata fmt
|
|||
|
||||
.PHONY: bindata
|
||||
bindata:
|
||||
|
||||
# Setup Workdir
|
||||
rm -rf .tmp/data
|
||||
rm -rf .tmp/yao-init
|
||||
mkdir -p .tmp/data
|
||||
|
||||
# Checkout init
|
||||
git clone https://github.com/YaoApp/yao-init.git .tmp/yao-init
|
||||
rm -rf .tmp/yao-init/.git
|
||||
rm -rf .tmp/yao-init/.gitignore
|
||||
rm -rf .tmp/yao-init/LICENSE
|
||||
rm -rf .tmp/yao-init/README.md
|
||||
|
||||
# Copy Files
|
||||
cp -r .tmp/yao-init .tmp/data/init
|
||||
cp -r ui .tmp/data/
|
||||
cp -r ui .tmp/data/public
|
||||
cp -r xgen .tmp/data/
|
||||
cp -r yao .tmp/data/
|
||||
go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/...
|
||||
rm -rf .tmp/data
|
||||
rm -rf .tmp/yao-init
|
||||
|
||||
# make artifacts-linux
|
||||
.PHONY: artifacts-linux
|
||||
|
|
@ -160,15 +176,23 @@ artifacts-linux: clean
|
|||
rm -f ../xgen-v1.0/pnpm-lock.yaml
|
||||
echo "BASE=__yao_admin_root" > ../xgen-v1.0/packages/xgen/.env
|
||||
cd ../xgen-v1.0 && pnpm install && pnpm run build
|
||||
|
||||
# Setup UI
|
||||
cd ../xgen-v1.0/packages/setup && pnpm install && pnpm run build
|
||||
|
||||
# Init Application
|
||||
cd ../yao-init rm -rf .git
|
||||
cd ../yao-init rm -rf .gitignore
|
||||
cd ../yao-init rm -rf LICENSE
|
||||
cd ../yao-init rm -rf README.md
|
||||
|
||||
# Packing
|
||||
mkdir -p .tmp/data/xgen
|
||||
cp -r ./ui .tmp/data/ui
|
||||
cp -r ../xgen-v0.9/dist .tmp/data/xgen/v0.9
|
||||
cp -r ../xgen-v1.0/packages/setup/build .tmp/data/xgen/setup
|
||||
cp -r ../xgen-v1.0/packages/xgen/dist .tmp/data/xgen/v1.0
|
||||
cp -r ../yao-init .tmp/data/init
|
||||
cp -r yao .tmp/data/
|
||||
go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/...
|
||||
rm -rf .tmp/data
|
||||
|
|
@ -206,9 +230,15 @@ artifacts-macos: clean
|
|||
rm -f ../xgen-v1.0/pnpm-lock.yaml
|
||||
echo "BASE=__yao_admin_root" > ../xgen-v1.0/packages/xgen/.env
|
||||
cd ../xgen-v1.0 && pnpm install && pnpm run build
|
||||
|
||||
# Setup UI
|
||||
cd ../xgen-v1.0/packages/setup && pnpm install && pnpm run build
|
||||
|
||||
# Init Application
|
||||
cd ../yao-init rm -rf .git
|
||||
cd ../yao-init rm -rf .gitignore
|
||||
cd ../yao-init rm -rf LICENSE
|
||||
cd ../yao-init rm -rf README.md
|
||||
|
||||
# Packing
|
||||
mkdir -p .tmp/data/xgen
|
||||
|
|
@ -216,6 +246,7 @@ artifacts-macos: clean
|
|||
cp -r ../xgen-v0.9/dist .tmp/data/xgen/v0.9
|
||||
cp -r ../xgen-v1.0/packages/setup/build .tmp/data/xgen/setup
|
||||
cp -r ../xgen-v1.0/packages/xgen/dist .tmp/data/xgen/v1.0
|
||||
cp -r ../yao-init .tmp/data/init
|
||||
cp -r yao .tmp/data/
|
||||
go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/...
|
||||
rm -rf .tmp/data
|
||||
|
|
@ -281,6 +312,14 @@ release: clean
|
|||
# Setup UI
|
||||
cd .tmp/xgen/v1.0/packages/setup && pnpm install && pnpm run build
|
||||
|
||||
|
||||
# Checkout init
|
||||
git clone https://github.com/YaoApp/yao-init.git .tmp/yao-init
|
||||
rm -rf .tmp/yao-init/.git
|
||||
rm -rf .tmp/yao-init/.gitignore
|
||||
rm -rf .tmp/yao-init/LICENSE
|
||||
rm -rf .tmp/yao-init/README.md
|
||||
|
||||
# Packing
|
||||
mkdir -p .tmp/data/xgen
|
||||
cp -r ./ui .tmp/data/ui
|
||||
|
|
@ -288,6 +327,7 @@ release: clean
|
|||
cp -r .tmp/xgen/v0.9/dist .tmp/data/xgen/v0.9
|
||||
cp -r .tmp/xgen/v1.0/packages/setup/build .tmp/data/xgen/setup
|
||||
cp -r .tmp/xgen/v1.0/packages/xgen/dist .tmp/data/xgen/v1.0
|
||||
cp -r .tmp/yao-init .tmp/data/init
|
||||
go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/...
|
||||
rm -rf .tmp/data
|
||||
rm -rf .tmp/xgen
|
||||
|
|
@ -326,6 +366,14 @@ linux-release: clean
|
|||
# Setup UI
|
||||
cd .tmp/xgen/v1.0/packages/setup && pnpm install && pnpm run build
|
||||
|
||||
|
||||
# Checkout init
|
||||
git clone https://github.com/YaoApp/yao-init.git .tmp/yao-init
|
||||
rm -rf .tmp/yao-init/.git
|
||||
rm -rf .tmp/yao-init/.gitignore
|
||||
rm -rf .tmp/yao-init/LICENSE
|
||||
rm -rf .tmp/yao-init/README.md
|
||||
|
||||
# Packing
|
||||
mkdir -p .tmp/data/xgen
|
||||
cp -r ./ui .tmp/data/ui
|
||||
|
|
@ -333,6 +381,7 @@ linux-release: clean
|
|||
cp -r .tmp/xgen/v0.9/dist .tmp/data/xgen/v0.9
|
||||
cp -r .tmp/xgen/v1.0/packages/setup/build .tmp/data/xgen/setup
|
||||
cp -r .tmp/xgen/v1.0/packages/xgen/dist .tmp/data/xgen/v1.0
|
||||
cp -r .tmp/yao-init .tmp/data/init
|
||||
go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/...
|
||||
rm -rf .tmp/data
|
||||
rm -rf .tmp/xgen
|
||||
|
|
|
|||
|
|
@ -43,16 +43,15 @@ func LoadInfo(root string) {
|
|||
exception.New("解析应用失败 %s", 500, err).Throw()
|
||||
}
|
||||
}
|
||||
|
||||
if fs.MustExists("/yao/icons/icon.icns") {
|
||||
if has, _ := fs.Exists("/yao/icons/icon.icns"); has {
|
||||
info.Icons.Set("icns", xfs.Encode(fs.MustReadFile("/yao/icons/icon.icns")))
|
||||
}
|
||||
|
||||
if fs.MustExists("/yao/icons/icon.ico") {
|
||||
if has, _ := fs.Exists("/yao/icons/icon.ico"); has {
|
||||
info.Icons.Set("ico", xfs.Encode(fs.MustReadFile("/yao/icons/icon.ico")))
|
||||
}
|
||||
|
||||
if fs.MustExists("/yao/icons/icon.png") {
|
||||
if has, _ := fs.Exists("/yao/icons/icon.png"); has {
|
||||
info.Icons.Set("png", xfs.Encode(fs.MustReadFile("/yao/icons/icon.png")))
|
||||
}
|
||||
|
||||
|
|
|
|||
457
data/bindata.go
457
data/bindata.go
File diff suppressed because one or more lines are too long
910
setup/install.go
910
setup/install.go
|
|
@ -7,12 +7,9 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/yaoapp/gou"
|
||||
"github.com/yaoapp/gou/fs"
|
||||
"github.com/yaoapp/gou/fs/dsl"
|
||||
"github.com/yaoapp/gou/fs/system"
|
||||
"github.com/yaoapp/kun/log"
|
||||
"github.com/yaoapp/yao/data"
|
||||
"github.com/yaoapp/yao/engine"
|
||||
"github.com/yaoapp/yao/share"
|
||||
"github.com/yaoapp/yao/widgets/app"
|
||||
)
|
||||
|
||||
|
|
@ -95,7 +92,7 @@ func Install(payload map[string]map[string]string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
err = makeDemoWidgets(root)
|
||||
err = makeInit(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -231,897 +228,26 @@ func makeDirs(root string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func makeDemoWidgets(root string) error {
|
||||
|
||||
dsl := dsl.New(root)
|
||||
|
||||
// make app
|
||||
ok, err := makeAppWidget(dsl, root)
|
||||
if err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
// make flows
|
||||
err = makeMenuFlowWidget(dsl, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make logins
|
||||
err = makeLoginWidget(dsl, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make models
|
||||
err = makeModelWidget(dsl, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make tables & forms
|
||||
err = makeTableFormWidget(dsl, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make chart
|
||||
err = makeChartWidget(dsl, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make scripts
|
||||
err = makeScript(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make scripts
|
||||
err = makeLang(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make index page
|
||||
err = makeIndexPage(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeAppWidget(dsl fs.FileSystem, root string) (bool, error) {
|
||||
|
||||
file := filepath.Join("app.json")
|
||||
if _, err := os.Stat(filepath.Join(root, file)); err == nil { // exists
|
||||
return false, nil
|
||||
}
|
||||
|
||||
_, err := dsl.WriteFile(file, []byte(fmt.Sprintf(`
|
||||
{
|
||||
"xgen": "1.0",
|
||||
"name": "::Demo Application",
|
||||
"short": "::Demo",
|
||||
"description": "::Another yao application",
|
||||
"version": "%s",
|
||||
"adminRoot": "admin",
|
||||
"setup": "scripts.demo.Data",
|
||||
"menu": {
|
||||
"process": "flows.app.menu",
|
||||
"args": ["demo"]
|
||||
},
|
||||
"optional": {
|
||||
"hideNotification": true,
|
||||
"hideSetting": false
|
||||
}
|
||||
}
|
||||
`, share.VERSION)), 0644)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func makeMenuFlowWidget(dsl fs.FileSystem, root string) error {
|
||||
|
||||
file := filepath.Join("flows", "app", "menu.flow.json")
|
||||
if _, err := os.Stat(filepath.Join(root, file)); err == nil { // exists
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := dsl.WriteFile(file, []byte(`
|
||||
{
|
||||
"name": "APP Menu",
|
||||
"nodes": [],
|
||||
"output": [
|
||||
{
|
||||
"blocks": 0,
|
||||
"icon": "icon-activity",
|
||||
"id": 1,
|
||||
"name": "图表",
|
||||
"parent": null,
|
||||
"path": "/x/Chart/dashboard",
|
||||
"visible_menu": 0
|
||||
},
|
||||
{
|
||||
"blocks": 0,
|
||||
"icon": "icon-book",
|
||||
"id": 2,
|
||||
"name": "表格",
|
||||
"parent": null,
|
||||
"path": "/x/Table/pet",
|
||||
"visible_menu": 1,
|
||||
"children": [
|
||||
{
|
||||
"blocks": 0,
|
||||
"icon": "icon-book",
|
||||
"name": "宠物列表",
|
||||
"id": 2010,
|
||||
"parent": 2,
|
||||
"path": "/x/Table/pet",
|
||||
"visible_menu": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"blocks": 0,
|
||||
"icon": "icon-clipboard",
|
||||
"id": 2,
|
||||
"name": "表单",
|
||||
"parent": null,
|
||||
"path": "/x/Form/pet/1/edit",
|
||||
"visible_menu": 1,
|
||||
"children": [
|
||||
{
|
||||
"blocks": 0,
|
||||
"icon": "icon-clipboard",
|
||||
"name": "编辑模式",
|
||||
"id": 2010,
|
||||
"parent": 2,
|
||||
"path": "/x/Form/pet/1/edit",
|
||||
"visible_menu": 1
|
||||
},
|
||||
{
|
||||
"blocks": 0,
|
||||
"icon": "icon-clipboard",
|
||||
"name": "查看模式",
|
||||
"id": 2010,
|
||||
"parent": 2,
|
||||
"path": "/x/Form/pet/1/view",
|
||||
"visible_menu": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
`), 0644)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeLoginWidget(dsl fs.FileSystem, root string) error {
|
||||
file := filepath.Join("logins", "admin.login.json")
|
||||
if _, err := os.Stat(filepath.Join(root, file)); err == nil { // exists
|
||||
return nil
|
||||
}
|
||||
_, err := dsl.WriteFile(file, []byte(`
|
||||
{
|
||||
"name": "::Admin Login",
|
||||
"action": {
|
||||
"process": "yao.login.Admin",
|
||||
"args": [":payload"]
|
||||
},
|
||||
"layout": {
|
||||
"entry": "/x/Chart/dashboard",
|
||||
"slogan": "::Make Your Dream With Yao App Engine",
|
||||
"site": "https://yaoapps.com?from=instance-admin-login"
|
||||
}
|
||||
}
|
||||
`), 0644)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file = filepath.Join("logins", "user.login.json")
|
||||
if _, err := os.Stat(filepath.Join(root, file)); err == nil { // exists
|
||||
return nil
|
||||
}
|
||||
_, err = dsl.WriteFile(file, []byte(`
|
||||
{
|
||||
"name": "::User Login",
|
||||
"action": {
|
||||
"process": "scripts.user.Login",
|
||||
"args": [":payload"]
|
||||
},
|
||||
"layout": {
|
||||
"entry": "/x/Table/pet",
|
||||
"slogan": "::Make Your Dream With Yao App Engine",
|
||||
"site": "https://yaoapps.com/from=instance-user-login"
|
||||
}
|
||||
}
|
||||
`), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeModelWidget(dsl fs.FileSystem, root string) error {
|
||||
file := filepath.Join("models", "pet.mod.json")
|
||||
if _, err := os.Stat(filepath.Join(root, file)); err == nil { // exists
|
||||
return nil
|
||||
}
|
||||
_, err := dsl.WriteFile(file, []byte(`
|
||||
{
|
||||
"name": "Pet",
|
||||
"table": { "name": "pet", "comment": "宠物表" },
|
||||
"columns": [
|
||||
{ "name": "id", "comment": "ID", "type": "ID" },
|
||||
{
|
||||
"name": "name",
|
||||
"comment": "昵称",
|
||||
"type": "string",
|
||||
"length": 80,
|
||||
"index": true,
|
||||
"nullable": true
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"comment": "类型",
|
||||
"type": "enum",
|
||||
"option": ["cat", "dog", "others"],
|
||||
"index": true
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"comment": "入院状态",
|
||||
"type": "enum",
|
||||
"option": ["checked", "curing", "cured"],
|
||||
"index": true
|
||||
},
|
||||
{
|
||||
"name": "mode",
|
||||
"comment": "状态",
|
||||
"type": "enum",
|
||||
"option": ["enabled", "disabled"],
|
||||
"index": true
|
||||
},
|
||||
{
|
||||
"name": "online",
|
||||
"comment": "是否在线",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"index": true
|
||||
},
|
||||
{
|
||||
"name": "curing_status",
|
||||
"comment": "治疗状态",
|
||||
"type": "enum",
|
||||
"default": "0",
|
||||
"option": ["0", "1", "2"],
|
||||
"index": true
|
||||
},
|
||||
{
|
||||
"name": "stay",
|
||||
"comment": "入院时间",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "cost",
|
||||
"comment": "花费",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "doctor_id",
|
||||
"type": "integer",
|
||||
"comment": "相关职工",
|
||||
"nullable": true
|
||||
},
|
||||
{
|
||||
"name": "images",
|
||||
"type": "json",
|
||||
"comment": "相关图片",
|
||||
"nullable": true
|
||||
},
|
||||
{
|
||||
"name": "test_string",
|
||||
"comment": "测试字段(字符串)",
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
{
|
||||
"name": "test_number",
|
||||
"comment": "测试字段(数字)",
|
||||
"type": "integer",
|
||||
"nullable": true
|
||||
},
|
||||
{
|
||||
"name": "test_array",
|
||||
"comment": "测试字段(数组)",
|
||||
"type": "json",
|
||||
"nullable": true
|
||||
}
|
||||
],
|
||||
"relations": {},
|
||||
"values": [],
|
||||
"indexes": [],
|
||||
"option": { "timestamps": true,"soft_deletes": true }
|
||||
}
|
||||
`), 0644)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeTableFormWidget(dsl fs.FileSystem, root string) error {
|
||||
file := filepath.Join("tables", "pet.tab.json")
|
||||
if _, err := os.Stat(filepath.Join(root, file)); err == nil { // exists
|
||||
return nil
|
||||
}
|
||||
_, err := dsl.WriteFile(file, []byte(`
|
||||
{
|
||||
"name": "::Pet Admin Bind Model",
|
||||
"action": {
|
||||
"bind": { "model": "pet", "option": {} }
|
||||
}
|
||||
}
|
||||
`), 0644)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file = filepath.Join("forms", "pet.form.json")
|
||||
if _, err := os.Stat(filepath.Join(root, file)); err == nil { // exists
|
||||
return nil
|
||||
}
|
||||
_, err = dsl.WriteFile(file, []byte(`
|
||||
{
|
||||
"name": "::Pet Admin Bind Table",
|
||||
"action": {
|
||||
"bind": { "table": "pet", "option": {} }
|
||||
}
|
||||
}
|
||||
`), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeChartWidget(dsl fs.FileSystem, root string) error {
|
||||
|
||||
file := filepath.Join("charts", "dashboard.chart.json")
|
||||
if _, err := os.Stat(filepath.Join(root, file)); err == nil { // exists
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := dsl.WriteFile(file, []byte(`
|
||||
{
|
||||
"name": "宠物医院数据图表",
|
||||
|
||||
"config":{"full":false},
|
||||
|
||||
"action": {
|
||||
"before:data": "scripts.stat.BeforeData",
|
||||
"data": { "process": "scripts.stat.Data", "default": ["2022-09-20"] },
|
||||
"after:data": "scripts.stat.AfterData"
|
||||
},
|
||||
|
||||
"layout": {
|
||||
"operation": {
|
||||
"actions": [
|
||||
{
|
||||
"title": "跳转至大屏",
|
||||
"icon": "icon-airplay",
|
||||
"action": { "Common.historyPush": { "pathname": "/x/Cool/demo" } }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"filter": {
|
||||
"columns": [{ "name": "时间区间", "width": 6 }]
|
||||
},
|
||||
|
||||
"chart": {
|
||||
"columns": [
|
||||
{ "name": "宠物数量", "width": 6 },
|
||||
{ "name": "宠物类型", "width": 6 },
|
||||
{ "name": "当月收入", "width": 6 },
|
||||
{ "name": "医师数量", "width": 6 },
|
||||
{ "name": "宠物数量_上月", "width": 6 },
|
||||
{ "name": "宠物类型_上月", "width": 6 },
|
||||
{ "name": "当月收入_上月", "width": 6 },
|
||||
{ "name": "医师数量_上月", "width": 6 },
|
||||
{ "name": "收入", "width": 8 },
|
||||
{ "name": "支出", "width": 8 },
|
||||
{ "name": "综合评分", "width": 8 },
|
||||
{ "name": "收入_折线图", "width": 8 },
|
||||
{ "name": "支出_折线图", "width": 8 },
|
||||
{ "name": "综合评分_折线图", "width": 8 },
|
||||
{ "name": "类型排布", "width": 12 },
|
||||
{ "name": "状态分布", "width": 12 },
|
||||
{ "name": "综合消费", "width": 24 }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"fields": {
|
||||
"filter": {
|
||||
"时间区间": {
|
||||
"bind": "range",
|
||||
"edit": { "type": "RangePicker", "props": {} }
|
||||
},
|
||||
"状态": {
|
||||
"bind": "status",
|
||||
"edit": {
|
||||
"type": "Select",
|
||||
"props": {
|
||||
"xProps": {
|
||||
"$remote": {
|
||||
"process": "models.pet.Get",
|
||||
"query": { "select": "name,status", "limit": 2 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func makeInit(root string) error {
|
||||
files := data.AssetNames()
|
||||
for _, file := range files {
|
||||
if strings.HasPrefix(file, "init/") {
|
||||
dst := filepath.Join(root, strings.TrimPrefix(file, "init/"))
|
||||
content, err := data.Read(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
},
|
||||
"chart": {
|
||||
"收入": {
|
||||
"bind": "income",
|
||||
"link": "/x/Table/pet",
|
||||
"out": "scripts.stat.Income",
|
||||
"view": {
|
||||
"type": "NumberChart",
|
||||
"props": {
|
||||
"chartHeight": 150,
|
||||
"prefix": "¥",
|
||||
"decimals": 2,
|
||||
"nameKey": "date",
|
||||
"valueKey": "value"
|
||||
}
|
||||
}
|
||||
},
|
||||
"支出": {
|
||||
"bind": "cost",
|
||||
"link": "/x/Table/pet",
|
||||
"view": {
|
||||
"type": "NumberChart",
|
||||
"props": {
|
||||
"chartHeight": 150,
|
||||
"color": "red",
|
||||
"prefix": "¥",
|
||||
"decimals": 2,
|
||||
"nameKey": "date",
|
||||
"valueKey": "value"
|
||||
}
|
||||
}
|
||||
},
|
||||
"综合评分": {
|
||||
"bind": "rate",
|
||||
"link": "/x/Table/pet",
|
||||
"view": {
|
||||
"type": "NumberChart",
|
||||
"props": {
|
||||
"chartHeight": 150,
|
||||
"color": "orange",
|
||||
"unit": "分",
|
||||
"decimals": 1,
|
||||
"nameKey": "date",
|
||||
"valueKey": "value"
|
||||
}
|
||||
}
|
||||
},
|
||||
"收入_折线图": {
|
||||
"bind": "income",
|
||||
"link": "/x/Table/pet",
|
||||
"view": {
|
||||
"type": "NumberChart",
|
||||
"props": {
|
||||
"cardStyle": { "padding": 0 },
|
||||
"type": "line",
|
||||
"chartHeight": 120,
|
||||
"prefix": "¥",
|
||||
"decimals": 2,
|
||||
"nameKey": "date",
|
||||
"valueKey": "value"
|
||||
}
|
||||
}
|
||||
},
|
||||
"支出_折线图": {
|
||||
"bind": "cost",
|
||||
"link": "/x/Table/pet",
|
||||
"view": {
|
||||
"type": "NumberChart",
|
||||
"props": {
|
||||
"cardStyle": { "padding": 0 },
|
||||
"type": "line",
|
||||
"chartHeight": 120,
|
||||
"color": "red",
|
||||
"prefix": "¥",
|
||||
"decimals": 2,
|
||||
"nameKey": "date",
|
||||
"valueKey": "value"
|
||||
}
|
||||
}
|
||||
},
|
||||
"综合评分_折线图": {
|
||||
"bind": "rate",
|
||||
"link": "/x/Table/pet",
|
||||
"view": {
|
||||
"type": "NumberChart",
|
||||
"props": {
|
||||
"cardStyle": { "padding": 0 },
|
||||
"type": "line",
|
||||
"chartHeight": 120,
|
||||
"color": "orange",
|
||||
"unit": "分",
|
||||
"decimals": 1,
|
||||
"nameKey": "date",
|
||||
"valueKey": "value"
|
||||
}
|
||||
}
|
||||
},
|
||||
"宠物数量": {
|
||||
"bind": "pet_count",
|
||||
"link": "/x/Table/pet",
|
||||
"view": { "type": "Number", "props": { "unit": "个" } }
|
||||
},
|
||||
"宠物类型": {
|
||||
"bind": "pet_type",
|
||||
"view": { "type": "Number", "props": { "unit": "种" } }
|
||||
},
|
||||
"当月收入": {
|
||||
"bind": "income_monthly",
|
||||
"view": { "type": "Number", "props": { "unit": "元" } }
|
||||
},
|
||||
"医师数量": {
|
||||
"bind": "doctor_count",
|
||||
"view": { "type": "Number", "props": { "unit": "个" } }
|
||||
},
|
||||
"宠物数量_上月": {
|
||||
"bind": "prev_pet_count",
|
||||
"link": "/x/Table/pet",
|
||||
"view": {
|
||||
"type": "Number",
|
||||
"props": {
|
||||
"cardStyle": { "padding": 0 },
|
||||
"unit": "个",
|
||||
"prev_title": "上月数据"
|
||||
}
|
||||
}
|
||||
},
|
||||
"宠物类型_上月": {
|
||||
"bind": "prev_pet_type",
|
||||
"link": "/x/Table/pet",
|
||||
"view": {
|
||||
"type": "Number",
|
||||
"props": {
|
||||
"cardStyle": { "padding": 0 },
|
||||
"unit": "种",
|
||||
"prev_title": "上月数据"
|
||||
}
|
||||
}
|
||||
},
|
||||
"当月收入_上月": {
|
||||
"bind": "prev_income_monthly",
|
||||
"link": "/x/Table/pet",
|
||||
"view": {
|
||||
"type": "Number",
|
||||
"props": {
|
||||
"cardStyle": { "padding": 0 },
|
||||
"unit": "元",
|
||||
"prev_title": "上月数据"
|
||||
}
|
||||
}
|
||||
},
|
||||
"医师数量_上月": {
|
||||
"bind": "prev_doctor_count",
|
||||
"link": "/x/Table/pet",
|
||||
"view": {
|
||||
"type": "Number",
|
||||
"props": {
|
||||
"cardStyle": { "padding": 0 },
|
||||
"unit": "个",
|
||||
"prev_title": "上月数据"
|
||||
}
|
||||
}
|
||||
},
|
||||
"类型排布": {
|
||||
"bind": "datasource_type",
|
||||
"link": "/x/Table/pet",
|
||||
"view": {
|
||||
"type": "Pie",
|
||||
"props": {
|
||||
"height": 240,
|
||||
"nameKey": "type",
|
||||
"series": [
|
||||
{
|
||||
"valueKey": "count",
|
||||
"roseType": "area",
|
||||
"radius": [10, 100],
|
||||
"center": ["60%", "50%"],
|
||||
"itemStyle": { "borderRadius": 6 }
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"refer": {
|
||||
"type": "Table",
|
||||
"props": {
|
||||
"columns": [
|
||||
{ "title": "类型", "dataIndex": "type" },
|
||||
{ "title": "数量", "dataIndex": "count" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"状态分布": {
|
||||
"bind": "datasource_status",
|
||||
"view": {
|
||||
"type": "Bar",
|
||||
"props": {
|
||||
"height": 240,
|
||||
"nameKey": "status",
|
||||
"axisLabel": { "interval": 0, "fontSize": 12 },
|
||||
"series": [
|
||||
{
|
||||
"valueKey": "count",
|
||||
"type": "bar",
|
||||
"colorBy": "data",
|
||||
"itemStyle": { "borderRadius": 6 },
|
||||
"splitLine": { "show": false },
|
||||
"axisLabel": { "show": false }
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"refer": {
|
||||
"type": "Table",
|
||||
"props": {
|
||||
"columns": [
|
||||
{ "title": "状态", "dataIndex": "status" },
|
||||
{ "title": "数量", "dataIndex": "count" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"综合消费": {
|
||||
"bind": "datasource_cost",
|
||||
"view": {
|
||||
"type": "LineBar",
|
||||
"props": {
|
||||
"height": 240,
|
||||
"nameKey": "name",
|
||||
"axisLabel": { "interval": 0, "fontSize": 12 },
|
||||
"series": [
|
||||
{
|
||||
"valueKey": "stay",
|
||||
"type": "line",
|
||||
"smooth": true,
|
||||
"symbolSize": 8,
|
||||
"itemStyle": { "borderRadius": 6 },
|
||||
"splitLine": { "show": false },
|
||||
"axisLabel": { "show": false }
|
||||
},
|
||||
{
|
||||
"valueKey": "cost",
|
||||
"type": "bar",
|
||||
"itemStyle": { "borderRadius": 6 },
|
||||
"splitLine": { "show": false },
|
||||
"axisLabel": { "show": false }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
dir := filepath.Dir(dst)
|
||||
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.WriteFile(dst, content, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`), 0644)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeScript(root string) error {
|
||||
|
||||
fs := system.New(filepath.Join(root, "scripts"))
|
||||
file := "demo.js"
|
||||
if _, err := os.Stat(filepath.Join(root, "scripts", file)); err == nil { // exists
|
||||
return nil
|
||||
}
|
||||
_, err := fs.WriteFile(file, []byte(`
|
||||
/**
|
||||
* Demo Data
|
||||
*/
|
||||
function Data() {
|
||||
return Process(
|
||||
"yao.table.Insert",
|
||||
"pet",
|
||||
["name", "type", "status", "mode", "stay", "cost", "doctor_id"],
|
||||
[
|
||||
["Cookie", "cat", "checked", "enabled", 200, 105, 1],
|
||||
["Baby", "dog", "checked", "enabled", 186, 24, 1],
|
||||
["Poo", "others", "checked", "enabled", 199, 66, 1],
|
||||
]
|
||||
);
|
||||
}
|
||||
`), 0644)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file = filepath.Join("stat.js")
|
||||
if _, err := os.Stat(filepath.Join(root, "scripts", file)); err == nil { // exists
|
||||
return nil
|
||||
}
|
||||
_, err = fs.WriteFile(file, []byte(`
|
||||
/**
|
||||
* before:data hook
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
function BeforeData(params) {
|
||||
log.Info("[chart] before data hook: %s", JSON.stringify(params));
|
||||
return [params];
|
||||
}
|
||||
|
||||
/**
|
||||
* after:data hook
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
function AfterData(data) {
|
||||
log.Info("[chart] after data hook: %s", JSON.stringify(data));
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Data
|
||||
* @param {*} params
|
||||
*/
|
||||
function Data(params) {
|
||||
log.Info("[chart] process data query: %s", JSON.stringify(params));
|
||||
return {
|
||||
income: [
|
||||
{ value: 40300, date: "2022-1-1" },
|
||||
{ value: 50800, date: "2022-2-1" },
|
||||
{ value: 31300, date: "2022-3-1" },
|
||||
{ value: 48800, date: "2022-4-1" },
|
||||
{ value: 69900, date: "2022-5-1" },
|
||||
{ value: 37800, date: "2022-6-1" },
|
||||
],
|
||||
cost: [
|
||||
{ value: 28100, date: "2022-1-1" },
|
||||
{ value: 23000, date: "2022-2-1" },
|
||||
{ value: 29300, date: "2022-3-1" },
|
||||
{ value: 26700, date: "2022-4-1" },
|
||||
{ value: 26400, date: "2022-5-1" },
|
||||
{ value: 31200, date: "2022-6-1" },
|
||||
],
|
||||
rate: [
|
||||
{ value: 8.0, date: "2022-1-1" },
|
||||
{ value: 7.6, date: "2022-2-1" },
|
||||
{ value: 9.1, date: "2022-3-1" },
|
||||
{ value: 8.4, date: "2022-4-1" },
|
||||
{ value: 6.9, date: "2022-5-1" },
|
||||
{ value: 9.0, date: "2022-6-1" },
|
||||
],
|
||||
pet_count: 54,
|
||||
pet_type: 8,
|
||||
income_monthly: 68900,
|
||||
doctor_count: 23,
|
||||
prev_pet_count: { current: 54, prev: 45 },
|
||||
prev_pet_type: { current: 8, prev: 13 },
|
||||
prev_income_monthly: { current: 68900, prev: 92000 },
|
||||
prev_doctor_count: { current: 23, prev: 27 },
|
||||
datasource_type: [
|
||||
{ type: "猫猫", count: 18 },
|
||||
{ type: "狗狗", count: 6 },
|
||||
{ type: "其他", count: 3 },
|
||||
],
|
||||
datasource_status: [
|
||||
{ status: "已查看", count: 3 },
|
||||
{ status: "治疗中", count: 12 },
|
||||
{ status: "已治愈", count: 9 },
|
||||
],
|
||||
datasource_cost: [
|
||||
{ name: "毛毛", stay: 3, cost: 2000 },
|
||||
{ name: "阿布", stay: 6, cost: 4200 },
|
||||
{ name: "咪咪", stay: 7, cost: 6000 },
|
||||
{ name: "狗蛋", stay: 1, cost: 1000 },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute out
|
||||
* @param {*} field
|
||||
* @param {*} value
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
function Income(field, value, data) {
|
||||
log.Info(
|
||||
"[chart] Income Compute: %s",
|
||||
JSON.stringify({ field: field, value: value, data: data })
|
||||
);
|
||||
return value;
|
||||
}
|
||||
`), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeLang(root string) error {
|
||||
fs := system.New(filepath.Join(root, "langs"))
|
||||
file := filepath.Join("zh-cn", "global.yml")
|
||||
if _, err := os.Stat(filepath.Join(root, "langs", file)); err == nil { // exists
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := fs.WriteFile(file, []byte(`
|
||||
Demo: 演示
|
||||
Demo Application: 示例应用
|
||||
Another yao application: 又一个 YAO 应用`), 0644)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file = filepath.Join("zh-hk", "langs", "global.yml")
|
||||
if _, err := os.Stat(filepath.Join(root, file)); err == nil { // exists
|
||||
return nil
|
||||
}
|
||||
_, err = fs.WriteFile(file, []byte(`
|
||||
Demo: 演示
|
||||
Demo Application: 示例應用
|
||||
Another yao application: 又一個YAO應用`), 0644)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeIndexPage(root string) error {
|
||||
fs := system.New(filepath.Join(root, "public"))
|
||||
file := "index.html"
|
||||
if _, err := os.Stat(filepath.Join(root, "public", file)); err == nil { // exists
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := fs.WriteFile(file, []byte(`
|
||||
It works!`), 0644)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package setup
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
@ -10,3 +11,19 @@ func TestInstall(t *testing.T) {
|
|||
// t.Fatal(err)
|
||||
// }
|
||||
}
|
||||
|
||||
func TestMakeInit(t *testing.T) {
|
||||
root := prepare(t)
|
||||
err := makeInit(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func prepare(t *testing.T) string {
|
||||
dir, err := os.MkdirTemp("", "-install")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,9 @@ func (layout *LayoutDSL) BindModel(m *gou.Model, formID string, fields *FieldsDS
|
|||
if ok && name != "deleted_at" {
|
||||
if col, has := fields.formMap[name]; has {
|
||||
width := 12
|
||||
if col.Edit != nil && (col.Edit.Type == "TextArea" || col.Edit.Type == "Upload") {
|
||||
width = 24
|
||||
}
|
||||
// if c, has := m.Columns[name]; has {
|
||||
// typ := strings.ToLower(c.Type)
|
||||
// if typ == "id" || strings.Contains(typ, "integer") || strings.Contains(typ, "float") {
|
||||
|
|
@ -137,6 +140,9 @@ func (layout *LayoutDSL) BindTable(tab *table.DSL, formID string, fields *Fields
|
|||
name := column.Key
|
||||
if col, has := fields.Form[name]; has && column.Bind != "deleted_at" {
|
||||
width := 12
|
||||
if col.Edit != nil && (col.Edit.Type == "TextArea" || col.Edit.Type == "Upload") {
|
||||
width = 24
|
||||
}
|
||||
columns = append(columns, Column{InstanceDSL: component.InstanceDSL{Name: col.Key, Width: width}})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
107
workflow/api.go
107
workflow/api.go
|
|
@ -1,107 +0,0 @@
|
|||
package workflow
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/yaoapp/gou"
|
||||
)
|
||||
|
||||
// API:
|
||||
// 读取工作流 GET /api/xiang/workflow/<工作流名称>/find/:id
|
||||
// 读取工作流 GET /api/xiang/workflow/<工作流名称>/open/:data_id
|
||||
// 读取工作流配置 GET /api/xiang/workflow/<工作流名称>/setting/:data_id
|
||||
// 调用自定义API POST /api/xiang/workflow/<工作流名称>/<自定义API路由>
|
||||
|
||||
// SetupAPIs 注册API
|
||||
func (workflow *WorkFlow) SetupAPIs() error {
|
||||
api := &gou.HTTP{
|
||||
Name: "工作流接口",
|
||||
Version: "1.0.0",
|
||||
Description: "工作流接口 API",
|
||||
Group: "xiang/workflow",
|
||||
Guard: "bearer-jwt",
|
||||
Paths: []gou.Path{},
|
||||
}
|
||||
|
||||
workflow.AddAPI(api, workflow.apiFind())
|
||||
workflow.AddAPI(api, workflow.apiOpen())
|
||||
workflow.AddAPI(api, workflow.apiSetting())
|
||||
|
||||
for name, custAPI := range workflow.APIs {
|
||||
workflow.AddAPI(api, gou.Path{
|
||||
Label: custAPI.Label,
|
||||
Description: custAPI.Description,
|
||||
Path: filepath.Join("/", workflow.Name, name),
|
||||
Method: "POST",
|
||||
Process: custAPI.Process,
|
||||
In: []string{workflow.Name, "$session.user_id", "$query.data_id", ":payload"},
|
||||
Out: gou.Out{
|
||||
Status: 200,
|
||||
Type: "application/json",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 注册API
|
||||
source, err := jsoniter.Marshal(api)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gou.LoadAPI(string(source), "xiang.workflow."+workflow.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddAPI 添加API
|
||||
func (workflow *WorkFlow) AddAPI(api *gou.HTTP, path gou.Path) {
|
||||
api.Paths = append(api.Paths, path)
|
||||
}
|
||||
|
||||
// apiFind 使用ID读取工作流
|
||||
func (workflow *WorkFlow) apiFind() gou.Path {
|
||||
return gou.Path{
|
||||
Label: "读取工作流",
|
||||
Description: "使用工作流ID读取工作流",
|
||||
Path: filepath.Join("/", workflow.Name, "find", ":id"),
|
||||
Method: "GET",
|
||||
Process: "xiang.workflow.Find",
|
||||
In: []string{workflow.Name, "$param.id"},
|
||||
Out: gou.Out{
|
||||
Status: 200,
|
||||
Type: "application/json",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// apiOpen 使用用户ID和数据ID 读取工作流
|
||||
func (workflow *WorkFlow) apiOpen() gou.Path {
|
||||
return gou.Path{
|
||||
Label: "读取工作流",
|
||||
Description: "使用数据ID读取工作流",
|
||||
Path: filepath.Join("/", workflow.Name, "open", ":data_id"),
|
||||
Method: "GET",
|
||||
Process: "xiang.workflow.Open",
|
||||
In: []string{workflow.Name, "$session.user_id", "$param.data_id"},
|
||||
Out: gou.Out{
|
||||
Status: 200,
|
||||
Type: "application/json",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// apiSetting 读取工作流配置
|
||||
func (workflow *WorkFlow) apiSetting() gou.Path {
|
||||
return gou.Path{
|
||||
Label: "读取工作流配置",
|
||||
Description: "使用数据ID读取工作流配置",
|
||||
Path: filepath.Join("/", workflow.Name, "setting", ":data_id"),
|
||||
Method: "GET",
|
||||
Process: "xiang.workflow.Setting",
|
||||
In: []string{workflow.Name, "$session.user_id", "$param.data_id"},
|
||||
Out: gou.Out{
|
||||
Status: 200,
|
||||
Type: "application/json",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package workflow
|
||||
|
||||
// Workflow will be removed from engine
|
||||
|
||||
// func TestSetupAPIs(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// assignFlow.SetupAPIs()
|
||||
|
||||
// api := gou.SelectAPI("xiang.workflow.assign")
|
||||
// assert.Equal(t, "xiang.workflow.assign", api.Name)
|
||||
// assert.Equal(t, 10, len(api.HTTP.Paths))
|
||||
// }
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
package workflow
|
||||
|
||||
import "github.com/yaoapp/gou"
|
||||
|
||||
// WARNING: the Workflow widget will be removed from yao engine
|
||||
|
||||
// Process
|
||||
// 读取工作流 xiang.workflow.Find(name, workflow_id)
|
||||
// 读取工作流 xiang.workflow.Open(name, uid, data_id)
|
||||
// 保存工作流 xiang.workflow.Save(name, uid, node_name, data_id, input, ...output)
|
||||
// 进入下一个节点 xiang.workflow.Next(name, uid, workflow_id, output)
|
||||
// 跳转到指定节点 xiang.workflow.Goto(name, uid, workflow_id, node_name, output)
|
||||
// 更新工作流状态 xiang.workflow.Status(name, uid, workflow_id, status_name, output)
|
||||
// 标记结束流程 xiang.workflow.Done(name, uid, workflow_id, output)
|
||||
// 标记关闭流程 xiang.workflow.Close(name, uid, workflow_id, output)
|
||||
// 标记重置流程 xiang.workflow.Reset(name, uid, workflow_id, output)
|
||||
|
||||
func init() {
|
||||
// 注册处理器
|
||||
gou.RegisterProcessHandler("xiang.workflow.Find", ProcessFind)
|
||||
gou.RegisterProcessHandler("xiang.workflow.Setting", ProcessSetting)
|
||||
gou.RegisterProcessHandler("xiang.workflow.Open", ProcessOpen)
|
||||
gou.RegisterProcessHandler("xiang.workflow.Save", ProcessSave)
|
||||
gou.RegisterProcessHandler("xiang.workflow.Next", ProcessNext)
|
||||
gou.RegisterProcessHandler("xiang.workflow.Goto", ProcessGoto)
|
||||
gou.RegisterProcessHandler("xiang.workflow.Status", ProcessStatus)
|
||||
gou.RegisterProcessHandler("xiang.workflow.Done", ProcessDone)
|
||||
gou.RegisterProcessHandler("xiang.workflow.Close", ProcessClose)
|
||||
gou.RegisterProcessHandler("xiang.workflow.Reset", ProcessReset)
|
||||
}
|
||||
|
||||
// ProcessFind xiang.workflow.Find 读取工作流
|
||||
//
|
||||
// args: [工作流名称*, 工作流ID*]
|
||||
//
|
||||
// return: map[string]interface{} 工作流数据记录
|
||||
func ProcessFind(process *gou.Process) interface{} {
|
||||
process.ValidateArgNums(2)
|
||||
wflow := Select(process.ArgsString(0))
|
||||
return wflow.Find(process.ArgsInt(1))
|
||||
}
|
||||
|
||||
// ProcessOpen xiang.workflow.Open 读取工作流
|
||||
//
|
||||
// args: [工作流名称*, 当前用户ID*, 关联数据ID*]
|
||||
//
|
||||
// return: map[string]interface{} 工作流数据记录
|
||||
func ProcessOpen(process *gou.Process) interface{} {
|
||||
process.ValidateArgNums(3)
|
||||
wflow := Select(process.ArgsString(0))
|
||||
return wflow.Open(process.ArgsInt(1), process.Args[2])
|
||||
}
|
||||
|
||||
// ProcessSetting xiang.workflow.Setting 读取工作流配置
|
||||
//
|
||||
// args: [工作流名称*, 当前用户ID*, 关联数据ID*]
|
||||
//
|
||||
// return: map[string]interface{} 工作流配置
|
||||
func ProcessSetting(process *gou.Process) interface{} {
|
||||
process.ValidateArgNums(3)
|
||||
wflow := Select(process.ArgsString(0))
|
||||
return wflow.Setting(process.ArgsInt(1), process.Args[2])
|
||||
}
|
||||
|
||||
// ProcessSave xiang.workflow.Save 保存工作流节点信息
|
||||
//
|
||||
// args: [工作流名称*, 当前用户ID*, 节点名称*, 关联数据ID*, 输入数据*, 输出数据] (输入数据: {"data":{}, "form":{}} data 关联数据记录信息, form 工作流body表单信息, 输出数据: {"foo":"bar"} )
|
||||
//
|
||||
// return: map[string]interface{} 工作流数据记录
|
||||
func ProcessSave(process *gou.Process) interface{} {
|
||||
process.ValidateArgNums(5)
|
||||
wflow := Select(process.ArgsString(0))
|
||||
input := InputOf(process.ArgsMap(4))
|
||||
output := map[string]interface{}{}
|
||||
if process.NumOfArgsIs(6) {
|
||||
output = process.ArgsMap(5)
|
||||
}
|
||||
return wflow.Save(process.ArgsInt(1), process.ArgsString(2), process.Args[3], input, output)
|
||||
}
|
||||
|
||||
// ProcessNext xiang.workflow.Next 进入下一个节点
|
||||
//
|
||||
// args: [工作流名称*, 当前用户ID*, 工作流ID*, 输出数据*] (输出数据: {"foo":"bar"} )
|
||||
//
|
||||
// return: map[string]interface{} 工作流数据记录
|
||||
func ProcessNext(process *gou.Process) interface{} {
|
||||
process.ValidateArgNums(4)
|
||||
wflow := Select(process.ArgsString(0))
|
||||
output := process.ArgsMap(3)
|
||||
return wflow.Next(process.ArgsInt(1), process.ArgsInt(2), output)
|
||||
}
|
||||
|
||||
// ProcessGoto xiang.workflow.Goto 跳转到指定节点
|
||||
//
|
||||
// args: [工作流名称*, 当前用户ID*, 工作流ID*, 节点名称*, 输出数据*] (输出数据: {"foo":"bar"} )
|
||||
//
|
||||
// return: map[string]interface{} 工作流数据记录
|
||||
func ProcessGoto(process *gou.Process) interface{} {
|
||||
process.ValidateArgNums(5)
|
||||
wflow := Select(process.ArgsString(0))
|
||||
output := process.ArgsMap(4)
|
||||
return wflow.Goto(process.ArgsInt(1), process.ArgsInt(2), process.ArgsString(3), output)
|
||||
}
|
||||
|
||||
// ProcessStatus xiang.workflow.Status 更新工作流状态
|
||||
//
|
||||
// args: [工作流名称*, 当前用户ID*, 工作流ID*, 状态名称*, 输出数据*] (输出数据: {"foo":"bar"} )
|
||||
//
|
||||
// return: map[string]interface{} 工作流数据记录
|
||||
func ProcessStatus(process *gou.Process) interface{} {
|
||||
process.ValidateArgNums(5)
|
||||
wflow := Select(process.ArgsString(0))
|
||||
output := process.ArgsMap(4)
|
||||
return wflow.Status(process.ArgsInt(1), process.ArgsInt(2), process.ArgsString(3), output)
|
||||
}
|
||||
|
||||
// ProcessDone xiang.workflow.Done 标记结束流程
|
||||
//
|
||||
// args: [工作流名称*, 当前用户ID*, 工作流ID*, 输出数据*] (输出数据: {"foo":"bar"} )
|
||||
//
|
||||
// return: map[string]interface{} 工作流数据记录
|
||||
func ProcessDone(process *gou.Process) interface{} {
|
||||
process.ValidateArgNums(4)
|
||||
wflow := Select(process.ArgsString(0))
|
||||
output := process.ArgsMap(3)
|
||||
return wflow.Done(process.ArgsInt(1), process.ArgsInt(2), output)
|
||||
}
|
||||
|
||||
// ProcessClose xiang.workflow.Close 标记关闭流程
|
||||
//
|
||||
// args: [工作流名称*, 当前用户ID*, 工作流ID*, 输出数据*] (输出数据: {"foo":"bar"} )
|
||||
//
|
||||
// return: map[string]interface{} 工作流数据记录
|
||||
func ProcessClose(process *gou.Process) interface{} {
|
||||
process.ValidateArgNums(4)
|
||||
wflow := Select(process.ArgsString(0))
|
||||
output := process.ArgsMap(3)
|
||||
return wflow.Close(process.ArgsInt(1), process.ArgsInt(2), output)
|
||||
}
|
||||
|
||||
// ProcessReset xiang.workflow.Reset 标记重置流程
|
||||
//
|
||||
// args: [工作流名称*, 当前用户ID*, 工作流ID*, 输出数据*] (输出数据: {"foo":"bar"} )
|
||||
//
|
||||
// return: map[string]interface{} 工作流数据记录
|
||||
func ProcessReset(process *gou.Process) interface{} {
|
||||
process.ValidateArgNums(4)
|
||||
wflow := Select(process.ArgsString(0))
|
||||
output := process.ArgsMap(3)
|
||||
return wflow.Reset(process.ArgsInt(1), process.ArgsInt(2), output)
|
||||
}
|
||||
|
|
@ -1,233 +0,0 @@
|
|||
package workflow
|
||||
|
||||
// WARNING: the Workflow widget will be removed from yao engine
|
||||
|
||||
// func init() {
|
||||
// share.DBConnect(config.Conf.DB)
|
||||
// share.Load(config.Conf)
|
||||
// model.Load(config.Conf)
|
||||
// engineModels := path.Join(os.Getenv("YAO_DEV"), "yao", "models")
|
||||
// model.LoadFrom(engineModels, "xiang.")
|
||||
// query.Load(config.Conf)
|
||||
// flow.Load(config.Conf)
|
||||
// Load(config.Conf)
|
||||
// }
|
||||
|
||||
// func TestProcessFind(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// })
|
||||
|
||||
// args := []interface{}{"assign", wflow["id"]}
|
||||
// res := gou.NewProcess("xiang.workflow.Find", args...).Run()
|
||||
|
||||
// data := any.Of(res).Map().MapStrAny.Dot()
|
||||
// assert.Equal(t, wflow["id"], data.Get("id"))
|
||||
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
||||
// func TestProcessSetting(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// })
|
||||
// id := any.Of(wflow["id"]).CInt()
|
||||
// assignFlow.Next(1, id, map[string]interface{}{
|
||||
// "项目名称": "测试项目",
|
||||
// "商务负责人名称": "林明波",
|
||||
// })
|
||||
|
||||
// args := []interface{}{"assign", 1, 1}
|
||||
// res := gou.NewProcess("xiang.workflow.Setting", args...).Run()
|
||||
// data := any.Of(res).Map().MapStrAny.Dot()
|
||||
// assert.Equal(t, 2, data.Get("nodes.2.source"))
|
||||
// assert.Equal(t, 2, data.Get("nodes.3.source"))
|
||||
// assert.Equal(t, "assign", data.Get("name"))
|
||||
// assert.Equal(t, "指派商务负责人", data.Get("label"))
|
||||
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
||||
// func TestProcessOpen(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// })
|
||||
|
||||
// args := []interface{}{"assign", 1, 1}
|
||||
// res := gou.NewProcess("xiang.workflow.Open", args...).Run()
|
||||
|
||||
// data := any.Of(res).Map().MapStrAny.Dot()
|
||||
// assert.Equal(t, wflow["id"], data.Get("id"))
|
||||
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
||||
// func TestProcessSave(t *testing.T) {
|
||||
// args := []interface{}{"assign",
|
||||
// 1, "选择商务负责人", 1,
|
||||
// map[string]interface{}{
|
||||
// "data": map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// "form": map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// },
|
||||
// map[string]interface{}{"foo": "bar"},
|
||||
// }
|
||||
// res := gou.NewProcess("xiang.workflow.Save", args...).Run()
|
||||
// data := any.Of(res).Map().MapStrAny.Dot()
|
||||
// assert.Equal(t, int64(1), data.Get("id"))
|
||||
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
||||
// func TestProcessNext(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// })
|
||||
|
||||
// args := []interface{}{"assign",
|
||||
// 1, wflow["id"], map[string]interface{}{
|
||||
// "项目名称": "测试项目",
|
||||
// "商务负责人名称": "林明波",
|
||||
// },
|
||||
// }
|
||||
// res := gou.NewProcess("xiang.workflow.Next", args...).Run()
|
||||
// data := any.Of(res).Map().MapStrAny.Dot()
|
||||
// assert.Equal(t, wflow["id"], data.Get("id"))
|
||||
// assert.Equal(t, "项目负责人审批", data.Get("node_name"))
|
||||
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
||||
// func TestProcessGoto(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// })
|
||||
// id := any.Of(wflow["id"]).CInt()
|
||||
// assignFlow.Next(1, id, map[string]interface{}{
|
||||
// "项目名称": "测试项目",
|
||||
// "商务负责人名称": "林明波",
|
||||
// })
|
||||
|
||||
// args := []interface{}{"assign",
|
||||
// 2, id, "选择商务负责人",
|
||||
// map[string]interface{}{"审批结果": "驳回"},
|
||||
// }
|
||||
// res := gou.NewProcess("xiang.workflow.Goto", args...).Run()
|
||||
// data := any.Of(res).Map().MapStrAny.Dot()
|
||||
// assert.Equal(t, wflow["id"], data.Get("id"))
|
||||
// assert.Equal(t, "选择商务负责人", data.Get("node_name"))
|
||||
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
||||
// func TestProcessStatus(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// })
|
||||
// id := any.Of(wflow["id"]).CInt()
|
||||
// assignFlow.Next(1, id, map[string]interface{}{
|
||||
// "项目名称": "测试项目",
|
||||
// "商务负责人名称": "林明波",
|
||||
// })
|
||||
|
||||
// args := []interface{}{"assign",
|
||||
// 2, id, "已完成", map[string]interface{}{"关闭原因": "测试完成"},
|
||||
// }
|
||||
// res := gou.NewProcess("xiang.workflow.Status", args...).Run()
|
||||
// data := any.Of(res).Map().MapStrAny.Dot()
|
||||
// assert.Equal(t, wflow["id"], data.Get("id"))
|
||||
// assert.Equal(t, "已完成", data.Get("status"))
|
||||
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
||||
// func TestProcessDone(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// })
|
||||
// id := any.Of(wflow["id"]).CInt()
|
||||
// assignFlow.Next(1, id, map[string]interface{}{
|
||||
// "项目名称": "测试项目",
|
||||
// "商务负责人名称": "林明波",
|
||||
// })
|
||||
|
||||
// args := []interface{}{"assign",
|
||||
// 2, id, map[string]interface{}{"关闭原因": "测试完成"},
|
||||
// }
|
||||
// res := gou.NewProcess("xiang.workflow.Done", args...).Run()
|
||||
// data := any.Of(res).Map().MapStrAny.Dot()
|
||||
// assert.Equal(t, wflow["id"], data.Get("id"))
|
||||
// assert.Equal(t, "已完成", data.Get("status"))
|
||||
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
||||
// func TestProcessClose(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// })
|
||||
// id := any.Of(wflow["id"]).CInt()
|
||||
// assignFlow.Next(1, id, map[string]interface{}{
|
||||
// "项目名称": "测试项目",
|
||||
// "商务负责人名称": "林明波",
|
||||
// })
|
||||
|
||||
// args := []interface{}{"assign",
|
||||
// 2, id, map[string]interface{}{"关闭原因": "测试关闭"},
|
||||
// }
|
||||
// res := gou.NewProcess("xiang.workflow.Close", args...).Run()
|
||||
// data := any.Of(res).Map().MapStrAny.Dot()
|
||||
// assert.Equal(t, wflow["id"], data.Get("id"))
|
||||
// assert.Equal(t, "已关闭", data.Get("status"))
|
||||
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
||||
// func TestProcessReset(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// })
|
||||
// id := any.Of(wflow["id"]).CInt()
|
||||
// assignFlow.Next(1, id, map[string]interface{}{
|
||||
// "项目名称": "测试项目",
|
||||
// "商务负责人名称": "林明波",
|
||||
// })
|
||||
|
||||
// args := []interface{}{"assign",
|
||||
// 2, id, map[string]interface{}{"驳回原因": "字段填写错误"},
|
||||
// }
|
||||
// res := gou.NewProcess("xiang.workflow.Reset", args...).Run()
|
||||
// data := any.Of(res).Map().MapStrAny.Dot()
|
||||
// assert.Equal(t, wflow["id"], data.Get("id"))
|
||||
// assert.Equal(t, "进行中", data.Get("status"))
|
||||
// assert.Equal(t, "选择商务负责人", data.Get("node_name"))
|
||||
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
package workflow
|
||||
|
||||
import (
|
||||
"github.com/yaoapp/yao/helper"
|
||||
"github.com/yaoapp/yao/share"
|
||||
)
|
||||
|
||||
// WorkFlow 工作流配置结构
|
||||
type WorkFlow struct {
|
||||
Name string `json:"-"`
|
||||
Source []byte `json:"-"`
|
||||
Version string `json:"version"`
|
||||
Label string `json:"label,omitempty"`
|
||||
Decription string `json:"decription,omitempty"`
|
||||
Nodes []Node `json:"nodes"`
|
||||
APIs map[string]API `json:"apis"`
|
||||
Actions map[string]share.Render `json:"actions"`
|
||||
}
|
||||
|
||||
// Node 工作流节点
|
||||
type Node struct {
|
||||
Name string `json:"name"`
|
||||
Body share.Render `json:"body,omitempty"`
|
||||
Actions []string `json:"actions,omitempty"`
|
||||
User User `json:"user,omitempty"`
|
||||
Next []Next `json:"next,omitempty"`
|
||||
}
|
||||
|
||||
// Next 下一个节点描述
|
||||
type Next struct {
|
||||
Conditions []helper.Condition `json:"when,omitempty"`
|
||||
Goto string `json:"goto,omitempty"`
|
||||
}
|
||||
|
||||
// User 工作流相关用户读取条件
|
||||
type User struct {
|
||||
Process string `json:"process"`
|
||||
Args []interface{} `json:"args"`
|
||||
}
|
||||
|
||||
// API 工作相关API
|
||||
type API struct {
|
||||
Label string `json:"label,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Process string `json:"process"`
|
||||
Args []interface{} `json:"args"`
|
||||
}
|
||||
|
||||
// Input 用户输入数据
|
||||
type Input struct {
|
||||
Data map[string]interface{} `json:"data"` // 记录数据
|
||||
Form map[string]interface{} `json:"form"` // 表单数据
|
||||
}
|
||||
|
|
@ -1,514 +0,0 @@
|
|||
package workflow
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/yaoapp/gou"
|
||||
gshare "github.com/yaoapp/gou/query/share"
|
||||
"github.com/yaoapp/kun/any"
|
||||
"github.com/yaoapp/kun/exception"
|
||||
"github.com/yaoapp/kun/log"
|
||||
"github.com/yaoapp/kun/maps"
|
||||
"github.com/yaoapp/kun/str"
|
||||
"github.com/yaoapp/yao/config"
|
||||
"github.com/yaoapp/yao/helper"
|
||||
"github.com/yaoapp/yao/share"
|
||||
)
|
||||
|
||||
// WARNING: the Workflow widget will be removed from yao engine
|
||||
|
||||
// Process
|
||||
// 读取工作流 xiang.workflow.Find(name, workflow_id)
|
||||
// 读取工作流 xiang.workflow.Open(name, uid, data_id)
|
||||
// 保存工作流 xiang.workflow.Save(name, uid, node_name, data_id, input, ...output)
|
||||
// 进入下一个节点 xiang.workflow.Next(name, uid, workflow_id, output)
|
||||
// 跳转到指定节点 xiang.workflow.Goto(name, uid, workflow_id, node_name, output)
|
||||
// 更新工作流状态 xiang.workflow.Status(name, uid, workflow_id, status_name, output)
|
||||
// 标记结束流程 xiang.workflow.Done(name, uid, workflow_id, output)
|
||||
// 标记关闭流程 xiang.workflow.Close(name, uid, workflow_id, output)
|
||||
// 标记重置流程 xiang.workflow.Reset(name, uid, workflow_id, output)
|
||||
|
||||
// API:
|
||||
// 读取工作流 GET /api/xiang/workflow/<工作流名称>/find/:id
|
||||
// 读取工作流 GET /api/xiang/workflow/<工作流名称>/open
|
||||
// 读取工作流配置 GET /api/xiang/workflow/<工作流名称>/setting
|
||||
// 调用自定义API POST /api/xiang/workflow/<工作流名称>/<自定义API路由>
|
||||
|
||||
// WorkFlows 工作流列表
|
||||
var WorkFlows = map[string]*WorkFlow{}
|
||||
|
||||
// Load 加载数据表格
|
||||
func Load(cfg config.Config) {
|
||||
LoadFrom(filepath.Join(cfg.Root, "workflows"), "")
|
||||
}
|
||||
|
||||
// LoadFrom 从特定目录加载
|
||||
func LoadFrom(dir string, prefix string) {
|
||||
if share.DirNotExists(dir) {
|
||||
return
|
||||
}
|
||||
share.Walk(dir, ".json", func(root, filename string) {
|
||||
name := prefix + share.SpecName(root, filename)
|
||||
content := share.ReadFile(filename)
|
||||
_, err := LoadWorkFlow(content, name)
|
||||
if err != nil {
|
||||
log.With(log.F{"root": root, "filename": filename}).Error(err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// LoadWorkFlow 载入工作流
|
||||
func LoadWorkFlow(source []byte, name string) (*WorkFlow, error) {
|
||||
workflow := WorkFlow{Name: name, Source: source}
|
||||
err := jsoniter.Unmarshal(source, &workflow)
|
||||
if err != nil {
|
||||
log.With(log.F{"name": name, "source": source}).Error("LoadWorkFlow: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
workflow.SetupAPIs()
|
||||
WorkFlows[workflow.Name] = &workflow
|
||||
return WorkFlows[workflow.Name], nil
|
||||
}
|
||||
|
||||
// Select 读取已加载图表
|
||||
func Select(name string) *WorkFlow {
|
||||
workflow, has := WorkFlows[name]
|
||||
if !has {
|
||||
exception.New(
|
||||
fmt.Sprintf("工作流:%s; 尚未加载", name),
|
||||
400,
|
||||
).Throw()
|
||||
}
|
||||
return workflow
|
||||
}
|
||||
|
||||
// Reload 重新载入工作流
|
||||
func (workflow *WorkFlow) Reload() *WorkFlow {
|
||||
new, err := LoadWorkFlow(workflow.Source, workflow.Name)
|
||||
if err != nil {
|
||||
exception.New(
|
||||
fmt.Sprintf("工作流:%s; 加载失败", workflow.Name),
|
||||
400,
|
||||
).Throw()
|
||||
}
|
||||
WorkFlows[workflow.Name] = new
|
||||
return new
|
||||
}
|
||||
|
||||
// Find 读取给定ID的工作流
|
||||
// uid 当前处理人ID, id 数据ID
|
||||
func (workflow *WorkFlow) Find(id int) map[string]interface{} {
|
||||
wflow := gou.Select("xiang.workflow")
|
||||
res := wflow.MustFind(id, gou.QueryParam{
|
||||
Select: []interface{}{
|
||||
"data_id", "id", "input", "output", "name",
|
||||
"user_id", "users",
|
||||
"node_name", "node_status",
|
||||
"status",
|
||||
"updated_at", "created_at",
|
||||
}})
|
||||
return res
|
||||
}
|
||||
|
||||
// Open 读取当前工作流(未完成的)
|
||||
// uid 当前处理人ID, id 数据ID
|
||||
func (workflow *WorkFlow) Open(uid int, id interface{}) map[string]interface{} {
|
||||
wflow := gou.Select("xiang.workflow")
|
||||
params := gou.QueryParam{
|
||||
Select: []interface{}{
|
||||
"data_id", "id", "input", "output", "name",
|
||||
"user_id", "users",
|
||||
"node_name", "node_status",
|
||||
"status",
|
||||
"updated_at", "created_at",
|
||||
},
|
||||
Wheres: []gou.QueryWhere{
|
||||
{Column: "name", Value: workflow.Name},
|
||||
{Column: "data_id", Value: id},
|
||||
{Column: "user_ids", OP: "like", Value: fmt.Sprintf("%%|%d|%%", uid)},
|
||||
{Column: "status", Value: "进行中"},
|
||||
},
|
||||
Limit: 1,
|
||||
}
|
||||
rows := wflow.MustGet(params)
|
||||
if len(rows) > 0 {
|
||||
return rows[0]
|
||||
}
|
||||
|
||||
if workflow.Len() == 0 {
|
||||
exception.New("工作流没有任何节点", 400).Throw()
|
||||
}
|
||||
|
||||
users := map[string]interface{}{}
|
||||
users[workflow.Nodes[0].Name] = uid
|
||||
return map[string]interface{}{
|
||||
"name": workflow.Name,
|
||||
"data_id": id,
|
||||
"node_name": workflow.Nodes[0].Name,
|
||||
"user_id": uid,
|
||||
"users": users,
|
||||
"status": "进行中",
|
||||
"node_status": "进行中",
|
||||
"input": map[string]interface{}{},
|
||||
"output": map[string]interface{}{},
|
||||
}
|
||||
}
|
||||
|
||||
// Setting 返回配置信息
|
||||
// uid 当前处理人ID, id 数据ID
|
||||
func (workflow *WorkFlow) Setting(uid int, id interface{}) map[string]interface{} {
|
||||
wflow := workflow.Open(1, 1)
|
||||
data := maps.MapStr{
|
||||
"$in": wflow["input"],
|
||||
"$input": wflow["input"],
|
||||
"$out": wflow["output"],
|
||||
"$outupt": wflow["output"],
|
||||
"$data": wflow["output"],
|
||||
}
|
||||
|
||||
nodes := workflow.FlowNodes(data.Dot())
|
||||
_, idx := workflow.pickNode(string(str.Of(wflow["node_name"])))
|
||||
return map[string]interface{}{
|
||||
"nodes": nodes,
|
||||
"node": nodes[idx],
|
||||
"node_index": idx,
|
||||
"read": true,
|
||||
"write": any.Of(wflow["user_id"]).CInt() == uid,
|
||||
"actions": workflow.Actions,
|
||||
"name": workflow.Name,
|
||||
"version": workflow.Version,
|
||||
"label": workflow.Label,
|
||||
"decription": workflow.Decription,
|
||||
}
|
||||
}
|
||||
|
||||
// Save 保存工作流节点数据 此版本使用Like实现
|
||||
// uid 当前处理人ID, id 数据ID
|
||||
func (workflow *WorkFlow) Save(uid int, name string, id interface{}, input Input, outputs ...map[string]interface{}) map[string]interface{} {
|
||||
wflow := gou.Select("xiang.workflow")
|
||||
params := gou.QueryParam{
|
||||
Select: []interface{}{"id", "input", "output", "users"},
|
||||
Wheres: []gou.QueryWhere{
|
||||
{Column: "name", Value: workflow.Name},
|
||||
{Column: "data_id", Value: id},
|
||||
{Column: "user_ids", OP: "like", Value: fmt.Sprintf("%%|%d|%%", uid)},
|
||||
{Column: "status", Value: "进行中"},
|
||||
},
|
||||
Limit: 1,
|
||||
}
|
||||
|
||||
rows := wflow.MustGet(params)
|
||||
data := map[string]interface{}{
|
||||
"name": workflow.Name,
|
||||
"data_id": id,
|
||||
"node_name": name,
|
||||
"user_id": uid,
|
||||
}
|
||||
users := map[string]interface{}{}
|
||||
users[name] = uid
|
||||
output := map[string]interface{}{}
|
||||
nodeInput := map[string]interface{}{}
|
||||
nodeInput[name] = input
|
||||
if len(outputs) > 0 {
|
||||
output = outputs[0]
|
||||
}
|
||||
if len(rows) > 0 {
|
||||
data["id"] = rows[0].Get("id")
|
||||
nodeInput = workflow.MergeData(rows[0].Get("input"), nodeInput)
|
||||
users = workflow.MergeUsers(rows[0].Get("users"), users)
|
||||
output = workflow.MergeData(rows[0].Get("output"), output)
|
||||
} else {
|
||||
data["status"] = "进行中"
|
||||
data["node_status"] = "进行中"
|
||||
}
|
||||
data["users"] = users
|
||||
data["user_ids"] = workflow.UserIDs(users)
|
||||
data["input"] = nodeInput
|
||||
data["output"] = output
|
||||
id = wflow.MustSave(data)
|
||||
return wflow.MustFind(id, gou.QueryParam{})
|
||||
}
|
||||
|
||||
// Done 标记完成
|
||||
// uid 当前处理人ID, id 工作流ID
|
||||
func (workflow *WorkFlow) Done(uid int, id int, output map[string]interface{}) map[string]interface{} {
|
||||
return workflow.Status(uid, id, "已完成", output)
|
||||
}
|
||||
|
||||
// Close 标记关闭
|
||||
// uid 当前处理人ID, id 工作流ID
|
||||
func (workflow *WorkFlow) Close(uid int, id int, output map[string]interface{}) map[string]interface{} {
|
||||
return workflow.Status(uid, id, "已关闭", output)
|
||||
}
|
||||
|
||||
// Status 设定状态
|
||||
// uid 当前处理人ID, id 工作流ID
|
||||
func (workflow *WorkFlow) Status(uid int, id int, status string, output map[string]interface{}) map[string]interface{} {
|
||||
wflow := workflow.Find(id)
|
||||
mod := gou.Select("xiang.workflow")
|
||||
output = workflow.MergeData(wflow["output"], output)
|
||||
mod.Save(map[string]interface{}{
|
||||
"id": wflow["id"],
|
||||
"output": output,
|
||||
"status": status,
|
||||
})
|
||||
return workflow.Find(id)
|
||||
}
|
||||
|
||||
// Reset 重新开始
|
||||
// uid 当前处理人ID, id 工作流ID
|
||||
func (workflow *WorkFlow) Reset(uid int, id int, output map[string]interface{}) map[string]interface{} {
|
||||
return workflow.Goto(uid, id, workflow.Nodes[0].Name, output)
|
||||
}
|
||||
|
||||
// Goto 工作流跳转
|
||||
func (workflow *WorkFlow) Goto(uid int, id int, name string, output map[string]interface{}) map[string]interface{} {
|
||||
wflow := workflow.Find(id)
|
||||
users := map[string]interface{}{}
|
||||
curr, _ := workflow.pickNode(wflow["node_name"].(string))
|
||||
users[curr.Name] = uid
|
||||
node, _ := workflow.pickNode(name)
|
||||
output = workflow.MergeData(wflow["output"], output)
|
||||
users = workflow.MergeUsers(wflow["users"], users)
|
||||
nodeUID := node.GetUID(users)
|
||||
mod := gou.Select("xiang.workflow")
|
||||
mod.Save(map[string]interface{}{
|
||||
"id": wflow["id"],
|
||||
"output": output,
|
||||
"node_name": name,
|
||||
"node_status": "进行中",
|
||||
"user_id": nodeUID,
|
||||
"users": users,
|
||||
"user_ids": workflow.UserIDs(users),
|
||||
})
|
||||
return workflow.Find(id)
|
||||
}
|
||||
|
||||
// Next 下一个工作流
|
||||
// uid 当前处理人ID, id 工作流ID
|
||||
func (workflow *WorkFlow) Next(uid int, id int, output map[string]interface{}) map[string]interface{} {
|
||||
wflow := workflow.Find(id)
|
||||
currNode, ok := wflow["node_name"].(string)
|
||||
if !ok {
|
||||
exception.New("流程数据异常: 当前节点信息错误", 500).Ctx(currNode).Throw()
|
||||
}
|
||||
|
||||
users := map[string]interface{}{}
|
||||
users[currNode] = uid
|
||||
output = workflow.MergeData(wflow["output"], output)
|
||||
|
||||
// 读取下一个节点
|
||||
data := map[string]interface{}{
|
||||
"$in": wflow["input"],
|
||||
"$input": wflow["input"],
|
||||
"$out": output,
|
||||
"$outupt": output,
|
||||
"$data": output,
|
||||
}
|
||||
nextNode := workflow.nextNode(currNode, data)
|
||||
nextUID := nextNode.MakeUID()
|
||||
users[nextNode.Name] = nextUID
|
||||
|
||||
// 更新数据
|
||||
users = workflow.MergeUsers(wflow["users"], users)
|
||||
mod := gou.Select("xiang.workflow")
|
||||
mod.Save(map[string]interface{}{
|
||||
"id": wflow["id"],
|
||||
"output": output,
|
||||
"node_name": nextNode.Name,
|
||||
"node_status": "进行中",
|
||||
"user_id": nextUID,
|
||||
"users": users,
|
||||
"user_ids": workflow.UserIDs(users),
|
||||
})
|
||||
return workflow.Find(id)
|
||||
}
|
||||
|
||||
// GetUID 读取节点相关人
|
||||
func (node *Node) GetUID(users interface{}) int {
|
||||
if users, ok := users.(map[string]interface{}); ok {
|
||||
if uid, has := users[node.Name]; has {
|
||||
return any.Of(uid).CInt()
|
||||
}
|
||||
}
|
||||
return node.MakeUID()
|
||||
}
|
||||
|
||||
// MakeUID 根据条件选择节点处理人
|
||||
func (node *Node) MakeUID() int {
|
||||
res := gou.NewProcess(node.User.Process, node.User.Args...).Run()
|
||||
return any.Of(res).CInt()
|
||||
}
|
||||
|
||||
func (workflow *WorkFlow) pickNode(name string) (*Node, int) {
|
||||
for i, node := range workflow.Nodes {
|
||||
if node.Name == name {
|
||||
return &node, i
|
||||
}
|
||||
}
|
||||
exception.New("流程数据异常: 未找到节点 %s", 500, name).Throw()
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// nextNode 查找下一个节点
|
||||
func (workflow *WorkFlow) nextNode(currentNode string, data map[string]interface{}) *Node {
|
||||
curr, index := workflow.pickNode(currentNode)
|
||||
nextIndex := index + 1
|
||||
if nextIndex == workflow.Len() {
|
||||
exception.New("流程数据异常: 当前节点为最后一个节点", 500).Ctx(currentNode).Throw()
|
||||
}
|
||||
|
||||
// 未声明 Next 节点, 转到下一个节点
|
||||
if curr.Next == nil {
|
||||
return &workflow.Nodes[nextIndex]
|
||||
}
|
||||
|
||||
// 声明 Next 节点, 按条件到指定节点
|
||||
data = maps.Of(data).Dot()
|
||||
for _, next := range curr.Next {
|
||||
node := workflow.GetNodeWhen(next, data)
|
||||
if node != nil {
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
||||
exception.New("流程数据异常: 未找到符合条件的工作流节点", 500).Ctx(map[string]interface{}{"current": currentNode, "data": data}).Throw()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNodeWhen 读取节点
|
||||
func (workflow *WorkFlow) GetNodeWhen(next Next, data map[string]interface{}) *Node {
|
||||
nextNode := ""
|
||||
conditions := workflow.Conditions(next.Conditions, data)
|
||||
if helper.When(conditions) {
|
||||
nextNode = next.Goto
|
||||
for i := 0; i < workflow.Len(); i++ {
|
||||
node := workflow.Nodes[i]
|
||||
if node.Name == nextNode {
|
||||
return &node
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Conditions 处理绑定参数
|
||||
func (workflow *WorkFlow) Conditions(conds []helper.Condition, data map[string]interface{}) []helper.Condition {
|
||||
conditions := []helper.Condition{}
|
||||
for _, cond := range conds {
|
||||
if left, ok := cond.Left.(string); ok {
|
||||
cond.Left = gshare.Bind(left, data)
|
||||
}
|
||||
if right, ok := cond.Right.(string); ok {
|
||||
cond.Right = gshare.Bind(right, data)
|
||||
}
|
||||
conditions = append(conditions, cond)
|
||||
}
|
||||
return conditions
|
||||
}
|
||||
|
||||
// UserIDs 读取用户ID
|
||||
func (workflow *WorkFlow) UserIDs(users map[string]interface{}) string {
|
||||
userIDs := []string{}
|
||||
for _, u := range users {
|
||||
userIDs = append(userIDs, fmt.Sprintf("|%d|", u))
|
||||
}
|
||||
userIDs = helper.ArrayStringUnique(userIDs)
|
||||
return strings.Join(userIDs, ",")
|
||||
}
|
||||
|
||||
// MergeUsers 合并数据
|
||||
func (workflow *WorkFlow) MergeUsers(data interface{}, new interface{}) map[string]interface{} {
|
||||
res, ok := data.(map[string]interface{})
|
||||
if !ok {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
if new, ok := new.(map[string]interface{}); ok {
|
||||
for name, value := range new {
|
||||
res[name] = value
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// MergeData 合并数据
|
||||
func (workflow *WorkFlow) MergeData(data interface{}, new interface{}) map[string]interface{} {
|
||||
|
||||
res, ok := data.(map[string]interface{})
|
||||
if !ok {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
if new, ok := new.(map[string]interface{}); ok {
|
||||
for key, value := range new {
|
||||
res[key] = value
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// IsLastNode 检查是否为最后一个节点
|
||||
func (workflow *WorkFlow) IsLastNode(name string) bool {
|
||||
length := workflow.Len()
|
||||
return workflow.Nodes[length-1].Name == name
|
||||
}
|
||||
|
||||
// FlowNodes 转换为 flow
|
||||
func (workflow *WorkFlow) FlowNodes(data map[string]interface{}) []map[string]interface{} {
|
||||
res := []map[string]interface{}{}
|
||||
nodes, err := jsoniter.Marshal(workflow.Nodes)
|
||||
if err != nil {
|
||||
exception.New("JSON解析错误 %s", 500, nodes).Throw()
|
||||
}
|
||||
|
||||
err = jsoniter.Unmarshal(nodes, &res)
|
||||
if err != nil {
|
||||
exception.New("JSON解析错误 %s", 500, nodes).Throw()
|
||||
}
|
||||
|
||||
nameMaps := map[string]int{}
|
||||
for i, node := range workflow.Nodes {
|
||||
nameMaps[node.Name] = i
|
||||
}
|
||||
|
||||
for i, node := range res {
|
||||
v := gshare.Bind(node, data)
|
||||
if v, ok := v.(map[string]interface{}); ok {
|
||||
res[i] = v
|
||||
}
|
||||
res[i]["id"] = i + 1
|
||||
res[i]["label"] = res[i]["name"]
|
||||
delete(res[i], "user")
|
||||
delete(res[i], "next")
|
||||
if workflow.Nodes[i].Next != nil {
|
||||
for _, next := range workflow.Nodes[i].Next {
|
||||
name := next.Goto
|
||||
if id, has := nameMaps[name]; has {
|
||||
res[id]["source"] = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Len 节点数量
|
||||
func (workflow *WorkFlow) Len() int {
|
||||
return len(workflow.Nodes)
|
||||
}
|
||||
|
||||
// InputOf 映射表转换为Input
|
||||
func InputOf(in map[string]interface{}) Input {
|
||||
input := Input{Data: map[string]interface{}{}, Form: map[string]interface{}{}}
|
||||
if data, ok := in["data"].(map[string]interface{}); ok {
|
||||
input.Data = data
|
||||
}
|
||||
if form, ok := in["form"].(map[string]interface{}); ok {
|
||||
input.Form = form
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
|
@ -1,296 +0,0 @@
|
|||
package workflow
|
||||
|
||||
// WARNING: the Workflow widget will be removed from yao engine
|
||||
|
||||
// func init() {
|
||||
// share.DBConnect(config.Conf.DB)
|
||||
// share.Load(config.Conf)
|
||||
// model.Load(config.Conf)
|
||||
// engineModels := path.Join(os.Getenv("YAO_DEV"), "xiang", "models")
|
||||
// model.LoadFrom(engineModels, "xiang.")
|
||||
// query.Load(config.Conf)
|
||||
// flow.Load(config.Conf)
|
||||
// Load(config.Conf)
|
||||
// }
|
||||
|
||||
// func TestLoad(t *testing.T) {
|
||||
// share.DBConnect(config.Conf.DB)
|
||||
// share.Load(config.Conf)
|
||||
// model.Load(config.Conf)
|
||||
// query.Load(config.Conf)
|
||||
// Load(config.Conf)
|
||||
// LoadFrom("not a path", "404.")
|
||||
// check(t)
|
||||
// }
|
||||
|
||||
// func TestSave(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// })
|
||||
|
||||
// data := maps.Of(wflow).Dot()
|
||||
// assert.Equal(t, int64(1), data.Get("id"))
|
||||
// assert.Equal(t, "assign", data.Get("name"))
|
||||
// assert.Equal(t, "选择商务负责人", data.Get("node_name"))
|
||||
// assert.Equal(t, "进行中", data.Get("node_status"))
|
||||
// assert.Equal(t, "进行中", data.Get("status"))
|
||||
// assert.Equal(t, float64(1), data.Get("input.选择商务负责人.data.id"))
|
||||
// assert.Equal(t, "云主机", data.Get("input.选择商务负责人.data.name"))
|
||||
// assert.Equal(t, float64(1), data.Get("input.选择商务负责人.form.biz_id"))
|
||||
// assert.Equal(t, "张良明", data.Get("input.选择商务负责人.form.name"))
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
||||
// func TestSaveUpdate(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// })
|
||||
|
||||
// wflow = assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云存储"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "李明博"},
|
||||
// })
|
||||
|
||||
// data := maps.Of(wflow).Dot()
|
||||
// assert.Equal(t, int64(1), data.Get("id"))
|
||||
// assert.Equal(t, "assign", data.Get("name"))
|
||||
// assert.Equal(t, "选择商务负责人", data.Get("node_name"))
|
||||
// assert.Equal(t, "进行中", data.Get("node_status"))
|
||||
// assert.Equal(t, "进行中", data.Get("status"))
|
||||
// assert.Equal(t, float64(1), data.Get("input.选择商务负责人.data.id"))
|
||||
// assert.Equal(t, "云存储", data.Get("input.选择商务负责人.data.name"))
|
||||
// assert.Equal(t, float64(1), data.Get("input.选择商务负责人.form.biz_id"))
|
||||
// assert.Equal(t, "李明博", data.Get("input.选择商务负责人.form.name"))
|
||||
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
||||
// func TestOpen(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// })
|
||||
// wflow := assignFlow.Open(1, 1)
|
||||
// data := maps.Of(wflow).Dot()
|
||||
// assert.Equal(t, int64(1), data.Get("id"))
|
||||
// assert.Equal(t, "assign", data.Get("name"))
|
||||
// assert.Equal(t, "选择商务负责人", data.Get("node_name"))
|
||||
// assert.Equal(t, "进行中", data.Get("node_status"))
|
||||
// assert.Equal(t, "进行中", data.Get("status"))
|
||||
// assert.Equal(t, int64(1), data.Get("user_id"))
|
||||
// assert.Equal(t, float64(1), data.Get("users.选择商务负责人"))
|
||||
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
||||
// func TestOpenEmpty(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// wflow := assignFlow.Open(1, 1)
|
||||
// data := maps.Of(wflow).Dot()
|
||||
// assert.Equal(t, false, data.Has("id"))
|
||||
// assert.Equal(t, "assign", data.Get("name"))
|
||||
// assert.Equal(t, "选择商务负责人", data.Get("node_name"))
|
||||
// assert.Equal(t, "进行中", data.Get("node_status"))
|
||||
// assert.Equal(t, "进行中", data.Get("status"))
|
||||
// assert.Equal(t, 1, data.Get("user_id"))
|
||||
// assert.Equal(t, 1, data.Get("users.选择商务负责人"))
|
||||
// }
|
||||
|
||||
// func TestNext(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// })
|
||||
// wflow = assignFlow.Next(1, any.Of(wflow["id"]).CInt(), map[string]interface{}{
|
||||
// "项目名称": "测试项目",
|
||||
// "商务负责人名称": "林明波",
|
||||
// })
|
||||
// data := maps.Of(wflow).Dot()
|
||||
// assert.Equal(t, int64(1), data.Get("id"))
|
||||
// assert.Equal(t, "assign", data.Get("name"))
|
||||
// assert.Equal(t, "项目负责人审批", data.Get("node_name"))
|
||||
// assert.Equal(t, "进行中", data.Get("node_status"))
|
||||
// assert.Equal(t, "进行中", data.Get("status"))
|
||||
// assert.Equal(t, int64(2), data.Get("user_id"))
|
||||
// assert.Equal(t, true, data.Has("users"))
|
||||
// assert.Equal(t, "测试项目", data.Get("output.项目名称"))
|
||||
// assert.Equal(t, "林明波", data.Get("output.商务负责人名称"))
|
||||
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
||||
// func TestNextWhen(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// })
|
||||
// id := any.Of(wflow["id"]).CInt()
|
||||
// assignFlow.Next(1, id, map[string]interface{}{
|
||||
// "项目名称": "测试项目",
|
||||
// "商务负责人名称": "林明波",
|
||||
// })
|
||||
|
||||
// // Next When
|
||||
// wflow = assignFlow.Next(2, id, map[string]interface{}{"审批结果": "通过"})
|
||||
// data := maps.Of(wflow).Dot()
|
||||
// assert.Equal(t, int64(1), data.Get("id"))
|
||||
// assert.Equal(t, "assign", data.Get("name"))
|
||||
// assert.Equal(t, "审批通过", data.Get("node_name"))
|
||||
// assert.Equal(t, "进行中", data.Get("node_status"))
|
||||
// assert.Equal(t, "进行中", data.Get("status"))
|
||||
// assert.Equal(t, int64(1), data.Get("user_id"))
|
||||
// assert.Equal(t, true, data.Has("users"))
|
||||
// assert.Equal(t, "测试项目", data.Get("output.项目名称"))
|
||||
// assert.Equal(t, "林明波", data.Get("output.商务负责人名称"))
|
||||
// assert.Equal(t, "通过", data.Get("output.审批结果"))
|
||||
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
||||
// func TestGoto(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// })
|
||||
// id := any.Of(wflow["id"]).CInt()
|
||||
// assignFlow.Next(1, id, map[string]interface{}{
|
||||
// "项目名称": "测试项目",
|
||||
// "商务负责人名称": "林明波",
|
||||
// })
|
||||
|
||||
// wflow = assignFlow.Goto(2, id, "选择商务负责人", map[string]interface{}{"审批结果": "驳回"})
|
||||
// data := maps.Of(wflow).Dot()
|
||||
// assert.Equal(t, true, data.Has("id"))
|
||||
// assert.Equal(t, "assign", data.Get("name"))
|
||||
// assert.Equal(t, "选择商务负责人", data.Get("node_name"))
|
||||
// assert.Equal(t, "进行中", data.Get("node_status"))
|
||||
// assert.Equal(t, "进行中", data.Get("status"))
|
||||
// assert.Equal(t, int64(1), data.Get("user_id"))
|
||||
// assert.Equal(t, float64(1), data.Get("users.选择商务负责人"))
|
||||
// assert.Equal(t, float64(2), data.Get("users.项目负责人审批"))
|
||||
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
||||
// func TestReset(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// })
|
||||
// id := any.Of(wflow["id"]).CInt()
|
||||
// assignFlow.Next(1, id, map[string]interface{}{
|
||||
// "项目名称": "测试项目",
|
||||
// "商务负责人名称": "林明波",
|
||||
// })
|
||||
|
||||
// wflow = assignFlow.Reset(2, id, map[string]interface{}{"审批结果": "驳回"})
|
||||
// data := maps.Of(wflow).Dot()
|
||||
// assert.Equal(t, true, data.Has("id"))
|
||||
// assert.Equal(t, "assign", data.Get("name"))
|
||||
// assert.Equal(t, "选择商务负责人", data.Get("node_name"))
|
||||
// assert.Equal(t, "进行中", data.Get("node_status"))
|
||||
// assert.Equal(t, "进行中", data.Get("status"))
|
||||
// assert.Equal(t, int64(1), data.Get("user_id"))
|
||||
// assert.Equal(t, float64(1), data.Get("users.选择商务负责人"))
|
||||
// assert.Equal(t, float64(2), data.Get("users.项目负责人审批"))
|
||||
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
||||
// func TestDone(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// })
|
||||
// id := any.Of(wflow["id"]).CInt()
|
||||
// assignFlow.Next(1, id, map[string]interface{}{
|
||||
// "项目名称": "测试项目",
|
||||
// "商务负责人名称": "林明波",
|
||||
// })
|
||||
|
||||
// wflow = assignFlow.Done(2, id, map[string]interface{}{"关闭原因": "测试完成"})
|
||||
// data := maps.Of(wflow).Dot()
|
||||
// assert.Equal(t, "已完成", data.Get("status"))
|
||||
// assert.Equal(t, "测试完成", data.Get("output.关闭原因"))
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
||||
// func TestClose(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// })
|
||||
// id := any.Of(wflow["id"]).CInt()
|
||||
// assignFlow.Next(1, id, map[string]interface{}{
|
||||
// "项目名称": "测试项目",
|
||||
// "商务负责人名称": "林明波",
|
||||
// })
|
||||
|
||||
// wflow = assignFlow.Close(2, id, map[string]interface{}{"关闭原因": "测试关闭"})
|
||||
// data := maps.Of(wflow).Dot()
|
||||
// assert.Equal(t, "已关闭", data.Get("status"))
|
||||
// assert.Equal(t, "测试关闭", data.Get("output.关闭原因"))
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
||||
// func TestSetting(t *testing.T) {
|
||||
// assignFlow := Select("assign")
|
||||
// wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
// Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
// Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
// })
|
||||
|
||||
// setting := assignFlow.Setting(1, 1)
|
||||
// data := maps.Of(setting).Dot()
|
||||
// assert.Equal(t, true, data.Get("read"))
|
||||
// assert.Equal(t, "选择商务负责人", data.Get("node.label"))
|
||||
// assert.Equal(t, true, data.Get("write"))
|
||||
|
||||
// id := any.Of(wflow["id"]).CInt()
|
||||
// assignFlow.Next(1, id, map[string]interface{}{
|
||||
// "项目名称": "测试项目",
|
||||
// "商务负责人名称": "林明波",
|
||||
// })
|
||||
|
||||
// setting = assignFlow.Setting(1, 1)
|
||||
// data = maps.Of(setting).Dot()
|
||||
// assert.Equal(t, true, data.Get("read"))
|
||||
// assert.Equal(t, false, data.Get("write"))
|
||||
// assert.Equal(t, "项目负责人审批", data.Get("node.label"))
|
||||
// assert.Equal(t, 2, data.Get("nodes.2.source"))
|
||||
// assert.Equal(t, 2, data.Get("nodes.3.source"))
|
||||
// assert.Equal(t, "assign", data.Get("name"))
|
||||
// assert.Equal(t, "指派商务负责人", data.Get("label"))
|
||||
|
||||
// // 清理数据
|
||||
// capsule.Query().From("xiang_workflow").Truncate()
|
||||
// }
|
||||
|
||||
// func check(t *testing.T) {
|
||||
// keys := []string{}
|
||||
// for key, workflow := range WorkFlows {
|
||||
// keys = append(keys, key)
|
||||
// workflow.Reload()
|
||||
// }
|
||||
// assert.Equal(t, 1, len(keys))
|
||||
// }
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
{
|
||||
"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": "处理人IDs",
|
||||
"comment": "相关处理人ID列表(用于检索)",
|
||||
"name": "user_ids",
|
||||
"type": "string",
|
||||
"length": 255,
|
||||
"index": true,
|
||||
"nullable": true
|
||||
},
|
||||
{
|
||||
"label": "处理人",
|
||||
"comment": "相关处理人",
|
||||
"name": "users",
|
||||
"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 }
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue