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
|
#ref: 5002c3fded585aaa69a4366135b415ea3234964e
|
||||||
path: xgen-v1.0
|
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
|
- name: Move Kun, Xun, Gou, UI, V8Go
|
||||||
run: |
|
run: |
|
||||||
mv kun ../
|
mv kun ../
|
||||||
|
|
@ -87,6 +93,7 @@ jobs:
|
||||||
mv v8go ../
|
mv v8go ../
|
||||||
mv xgen-v0.9 ../
|
mv xgen-v0.9 ../
|
||||||
mv xgen-v1.0 ../
|
mv xgen-v1.0 ../
|
||||||
|
mv yao-init ../
|
||||||
ls -l .
|
ls -l .
|
||||||
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
|
# ref: 5002c3fded585aaa69a4366135b415ea3234964e
|
||||||
path: xgen-v1.0
|
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
|
- name: Move Kun, Xun, Gou, UI, V8Go
|
||||||
run: |
|
run: |
|
||||||
mv kun ../
|
mv kun ../
|
||||||
|
|
@ -82,6 +88,7 @@ jobs:
|
||||||
mv v8go ../
|
mv v8go ../
|
||||||
mv xgen-v0.9 ../
|
mv xgen-v0.9 ../
|
||||||
mv xgen-v1.0 ../
|
mv xgen-v1.0 ../
|
||||||
|
mv yao-init ../
|
||||||
ls -l .
|
ls -l .
|
||||||
ls -l ../
|
ls -l ../
|
||||||
|
|
||||||
|
|
|
||||||
49
Makefile
49
Makefile
|
|
@ -139,12 +139,28 @@ pack: bindata fmt
|
||||||
|
|
||||||
.PHONY: bindata
|
.PHONY: bindata
|
||||||
bindata:
|
bindata:
|
||||||
|
|
||||||
|
# Setup Workdir
|
||||||
|
rm -rf .tmp/data
|
||||||
|
rm -rf .tmp/yao-init
|
||||||
mkdir -p .tmp/data
|
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/
|
||||||
|
cp -r ui .tmp/data/public
|
||||||
cp -r xgen .tmp/data/
|
cp -r xgen .tmp/data/
|
||||||
cp -r yao .tmp/data/
|
cp -r yao .tmp/data/
|
||||||
go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/...
|
go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/...
|
||||||
rm -rf .tmp/data
|
rm -rf .tmp/data
|
||||||
|
rm -rf .tmp/yao-init
|
||||||
|
|
||||||
# make artifacts-linux
|
# make artifacts-linux
|
||||||
.PHONY: artifacts-linux
|
.PHONY: artifacts-linux
|
||||||
|
|
@ -160,15 +176,23 @@ artifacts-linux: clean
|
||||||
rm -f ../xgen-v1.0/pnpm-lock.yaml
|
rm -f ../xgen-v1.0/pnpm-lock.yaml
|
||||||
echo "BASE=__yao_admin_root" > ../xgen-v1.0/packages/xgen/.env
|
echo "BASE=__yao_admin_root" > ../xgen-v1.0/packages/xgen/.env
|
||||||
cd ../xgen-v1.0 && pnpm install && pnpm run build
|
cd ../xgen-v1.0 && pnpm install && pnpm run build
|
||||||
|
|
||||||
# Setup UI
|
# Setup UI
|
||||||
cd ../xgen-v1.0/packages/setup && pnpm install && pnpm run build
|
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
|
# Packing
|
||||||
mkdir -p .tmp/data/xgen
|
mkdir -p .tmp/data/xgen
|
||||||
cp -r ./ui .tmp/data/ui
|
cp -r ./ui .tmp/data/ui
|
||||||
cp -r ../xgen-v0.9/dist .tmp/data/xgen/v0.9
|
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/setup/build .tmp/data/xgen/setup
|
||||||
cp -r ../xgen-v1.0/packages/xgen/dist .tmp/data/xgen/v1.0
|
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/
|
cp -r yao .tmp/data/
|
||||||
go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/...
|
go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/...
|
||||||
rm -rf .tmp/data
|
rm -rf .tmp/data
|
||||||
|
|
@ -206,9 +230,15 @@ artifacts-macos: clean
|
||||||
rm -f ../xgen-v1.0/pnpm-lock.yaml
|
rm -f ../xgen-v1.0/pnpm-lock.yaml
|
||||||
echo "BASE=__yao_admin_root" > ../xgen-v1.0/packages/xgen/.env
|
echo "BASE=__yao_admin_root" > ../xgen-v1.0/packages/xgen/.env
|
||||||
cd ../xgen-v1.0 && pnpm install && pnpm run build
|
cd ../xgen-v1.0 && pnpm install && pnpm run build
|
||||||
|
|
||||||
# Setup UI
|
# Setup UI
|
||||||
cd ../xgen-v1.0/packages/setup && pnpm install && pnpm run build
|
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
|
# Packing
|
||||||
mkdir -p .tmp/data/xgen
|
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-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/setup/build .tmp/data/xgen/setup
|
||||||
cp -r ../xgen-v1.0/packages/xgen/dist .tmp/data/xgen/v1.0
|
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/
|
cp -r yao .tmp/data/
|
||||||
go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/...
|
go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/...
|
||||||
rm -rf .tmp/data
|
rm -rf .tmp/data
|
||||||
|
|
@ -281,6 +312,14 @@ release: clean
|
||||||
# Setup UI
|
# Setup UI
|
||||||
cd .tmp/xgen/v1.0/packages/setup && pnpm install && pnpm run build
|
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
|
# Packing
|
||||||
mkdir -p .tmp/data/xgen
|
mkdir -p .tmp/data/xgen
|
||||||
cp -r ./ui .tmp/data/ui
|
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/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/setup/build .tmp/data/xgen/setup
|
||||||
cp -r .tmp/xgen/v1.0/packages/xgen/dist .tmp/data/xgen/v1.0
|
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/...
|
go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/...
|
||||||
rm -rf .tmp/data
|
rm -rf .tmp/data
|
||||||
rm -rf .tmp/xgen
|
rm -rf .tmp/xgen
|
||||||
|
|
@ -326,6 +366,14 @@ linux-release: clean
|
||||||
# Setup UI
|
# Setup UI
|
||||||
cd .tmp/xgen/v1.0/packages/setup && pnpm install && pnpm run build
|
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
|
# Packing
|
||||||
mkdir -p .tmp/data/xgen
|
mkdir -p .tmp/data/xgen
|
||||||
cp -r ./ui .tmp/data/ui
|
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/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/setup/build .tmp/data/xgen/setup
|
||||||
cp -r .tmp/xgen/v1.0/packages/xgen/dist .tmp/data/xgen/v1.0
|
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/...
|
go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/...
|
||||||
rm -rf .tmp/data
|
rm -rf .tmp/data
|
||||||
rm -rf .tmp/xgen
|
rm -rf .tmp/xgen
|
||||||
|
|
|
||||||
|
|
@ -43,16 +43,15 @@ func LoadInfo(root string) {
|
||||||
exception.New("解析应用失败 %s", 500, err).Throw()
|
exception.New("解析应用失败 %s", 500, err).Throw()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if has, _ := fs.Exists("/yao/icons/icon.icns"); has {
|
||||||
if fs.MustExists("/yao/icons/icon.icns") {
|
|
||||||
info.Icons.Set("icns", xfs.Encode(fs.MustReadFile("/yao/icons/icon.icns")))
|
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")))
|
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")))
|
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"
|
"strings"
|
||||||
|
|
||||||
"github.com/yaoapp/gou"
|
"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/kun/log"
|
||||||
|
"github.com/yaoapp/yao/data"
|
||||||
"github.com/yaoapp/yao/engine"
|
"github.com/yaoapp/yao/engine"
|
||||||
"github.com/yaoapp/yao/share"
|
|
||||||
"github.com/yaoapp/yao/widgets/app"
|
"github.com/yaoapp/yao/widgets/app"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -95,7 +92,7 @@ func Install(payload map[string]map[string]string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = makeDemoWidgets(root)
|
err = makeInit(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -231,897 +228,26 @@ func makeDirs(root string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeDemoWidgets(root string) error {
|
func makeInit(root string) error {
|
||||||
|
files := data.AssetNames()
|
||||||
dsl := dsl.New(root)
|
for _, file := range files {
|
||||||
|
if strings.HasPrefix(file, "init/") {
|
||||||
// make app
|
dst := filepath.Join(root, strings.TrimPrefix(file, "init/"))
|
||||||
ok, err := makeAppWidget(dsl, root)
|
content, err := data.Read(file)
|
||||||
if err != nil || !ok {
|
if err != nil {
|
||||||
return err
|
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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"chart": {
|
dir := filepath.Dir(dst)
|
||||||
"收入": {
|
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||||
"bind": "income",
|
return err
|
||||||
"link": "/x/Table/pet",
|
}
|
||||||
"out": "scripts.stat.Income",
|
|
||||||
"view": {
|
if err = os.WriteFile(dst, content, 0644); err != nil {
|
||||||
"type": "NumberChart",
|
return err
|
||||||
"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 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`), 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package setup
|
package setup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -10,3 +11,19 @@ func TestInstall(t *testing.T) {
|
||||||
// t.Fatal(err)
|
// 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 ok && name != "deleted_at" {
|
||||||
if col, has := fields.formMap[name]; has {
|
if col, has := fields.formMap[name]; has {
|
||||||
width := 12
|
width := 12
|
||||||
|
if col.Edit != nil && (col.Edit.Type == "TextArea" || col.Edit.Type == "Upload") {
|
||||||
|
width = 24
|
||||||
|
}
|
||||||
// if c, has := m.Columns[name]; has {
|
// if c, has := m.Columns[name]; has {
|
||||||
// typ := strings.ToLower(c.Type)
|
// typ := strings.ToLower(c.Type)
|
||||||
// if typ == "id" || strings.Contains(typ, "integer") || strings.Contains(typ, "float") {
|
// 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
|
name := column.Key
|
||||||
if col, has := fields.Form[name]; has && column.Bind != "deleted_at" {
|
if col, has := fields.Form[name]; has && column.Bind != "deleted_at" {
|
||||||
width := 12
|
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}})
|
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