Compare commits
35 Commits
mvp-step1
...
maxz-pinia
| Author | SHA1 | Date | |
|---|---|---|---|
| 418f45f997 | |||
| 51ebe99d1c | |||
| 2f1f8a60fc | |||
| 64795a80c7 | |||
| 94a17073dd | |||
| 7f7d625fdd | |||
| 6902079866 | |||
|
|
f34dec1054 | ||
|
|
01b64f1aba | ||
|
|
3367ada343 | ||
|
|
f4ea3eaccb | ||
|
|
4adb8401d6 | ||
| df59bff6ae | |||
|
|
fce56e43c3 | ||
|
|
42618602f4 | ||
|
|
e02131846b | ||
|
|
2c3b27d9de | ||
|
|
6ccc833f7d | ||
|
|
a0ecc2eee3 | ||
| 59e6d33656 | |||
| b641c729c2 | |||
| 142cdcda38 | |||
| fc2669dabf | |||
| 8e095b51e3 | |||
| ff03490209 | |||
| 40cd9998d0 | |||
| 973ba159b4 | |||
| 063a5af822 | |||
|
|
6a06c71104 | ||
| cccff1d16d | |||
|
|
100d8de54f | ||
|
|
7c667660c0 | ||
|
|
4eb56372a5 | ||
|
|
16edd398be | ||
|
|
4e08159e6d |
@@ -2,7 +2,7 @@ from fastapi import Request,Depends, APIRouter, UploadFile,HTTPException,File
|
||||
from app.db import Base,engine
|
||||
from app.db.session import get_db
|
||||
from app.db.crud import *
|
||||
from app.db.schemas import AppBase, AppEdit, App,Kintone
|
||||
from app.db.schemas import *
|
||||
|
||||
platform_router = r = APIRouter()
|
||||
|
||||
@@ -64,4 +64,62 @@ async def kintone_data(
|
||||
db=Depends(get_db),
|
||||
):
|
||||
kintone = get_kintones(db, type)
|
||||
return kintone
|
||||
return kintone
|
||||
|
||||
@r.get(
|
||||
"/actions",
|
||||
response_model=t.List[Action],
|
||||
response_model_exclude={"id"},
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def action_data(
|
||||
request: Request,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
actions = get_actions(db)
|
||||
return actions
|
||||
|
||||
@r.get(
|
||||
"/flow/{flowid}",
|
||||
response_model=Flow,
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def flow_details(
|
||||
request: Request,
|
||||
flowid: str,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
app = get_flow(db, flowid)
|
||||
return app
|
||||
|
||||
|
||||
@r.post("/flow", response_model=Flow, response_model_exclude_none=True)
|
||||
async def flow_create(
|
||||
request: Request,
|
||||
flow: FlowBase,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
return create_flow(db, flow)
|
||||
|
||||
|
||||
@r.put(
|
||||
"/flow/{flowid}", response_model=Flow, response_model_exclude_none=True
|
||||
)
|
||||
async def flow_edit(
|
||||
request: Request,
|
||||
flow: FlowBase,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
return edit_flow(db, flow)
|
||||
|
||||
|
||||
@r.delete(
|
||||
"/flow/{flowid}", response_model=Flow, response_model_exclude_none=True
|
||||
)
|
||||
async def flow_delete(
|
||||
request: Request,
|
||||
flowid: str,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
|
||||
return delete_flow(db, flowid)
|
||||
@@ -115,4 +115,62 @@ def get_kintones(db: Session, type: int):
|
||||
kintones = db.query(models.Kintone).filter(models.Kintone.type == type).all()
|
||||
if not kintones:
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
return kintones
|
||||
return kintones
|
||||
|
||||
def get_actions(db: Session):
|
||||
actions = db.query(models.Action).all()
|
||||
if not actions:
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
return actions
|
||||
|
||||
|
||||
def create_flow(db: Session, flow: schemas.FlowBase):
|
||||
db_flow = models.Flow(
|
||||
flowid=flow.flowid,
|
||||
appid=flow.appid,
|
||||
eventid=flow.eventid,
|
||||
name=flow.name,
|
||||
content=flow.content
|
||||
)
|
||||
db.add(db_flow)
|
||||
db.commit()
|
||||
db.refresh(db_flow)
|
||||
return db_flow
|
||||
|
||||
def delete_flow(db: Session, flowid: str):
|
||||
flow = get_flow(db, flowid)
|
||||
if not flow:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Flow not found")
|
||||
db.delete(flow)
|
||||
db.commit()
|
||||
return flow
|
||||
|
||||
|
||||
def edit_flow(
|
||||
db: Session, flow: schemas.FlowBase
|
||||
) -> schemas.Flow:
|
||||
db_flow = get_flow(db, flow.flowid)
|
||||
if not db_flow:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Flow not found")
|
||||
update_data = flow.dict(exclude_unset=True)
|
||||
|
||||
for key, value in update_data.items():
|
||||
setattr(db_flow, key, value)
|
||||
|
||||
db.add(db_flow)
|
||||
db.commit()
|
||||
db.refresh(db_flow)
|
||||
return db_flow
|
||||
|
||||
|
||||
def get_flows(db: Session, flowid: str):
|
||||
flows = db.query(models.Flow).all()
|
||||
if not flows:
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
return flows
|
||||
|
||||
def get_flow(db: Session, flowid: str):
|
||||
flow = db.query(models.Flow).filter(models.Flow.flowid == flowid).first()
|
||||
if not flow:
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
return flow
|
||||
@@ -1,12 +1,16 @@
|
||||
from sqlalchemy import Boolean, Column, Integer, String
|
||||
|
||||
from .session import Base
|
||||
from sqlalchemy import Boolean, Column, Integer, String, DateTime
|
||||
from sqlalchemy.ext.declarative import as_declarative
|
||||
from datetime import datetime
|
||||
|
||||
@as_declarative()
|
||||
class Base:
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
create_time = Column(DateTime, default=datetime.now)
|
||||
update_time = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "user"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
email = Column(String(50), unique=True, index=True, nullable=False)
|
||||
first_name = Column(String(100))
|
||||
last_name = Column(String(100))
|
||||
@@ -17,15 +21,31 @@ class User(Base):
|
||||
class AppSetting(Base):
|
||||
__tablename__ = "appsetting"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
appid = Column(String(100), index=True, nullable=False)
|
||||
setting = Column(String(1000))
|
||||
|
||||
class Kintone(Base):
|
||||
__tablename__ = "kintone"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
type = Column(Integer, index=True, nullable=False)
|
||||
name = Column(String(100), nullable=False)
|
||||
desc = Column(String)
|
||||
content = Column(String)
|
||||
content = Column(String)
|
||||
|
||||
class Action(Base):
|
||||
__tablename__ = "action"
|
||||
|
||||
name = Column(String(100), index=True, nullable=False)
|
||||
title = Column(String(200))
|
||||
subtitle = Column(String(500))
|
||||
outputpoints = Column(String)
|
||||
property = Column(String)
|
||||
|
||||
class Flow(Base):
|
||||
__tablename__ = "flow"
|
||||
|
||||
flowid = Column(String(100), index=True, nullable=False)
|
||||
appid = Column(String(100), index=True, nullable=False)
|
||||
eventid = Column(String(100), index=True, nullable=False)
|
||||
name = Column(String(200))
|
||||
content = Column(String)
|
||||
@@ -1,7 +1,12 @@
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
import typing as t
|
||||
|
||||
|
||||
class Base(BaseModel):
|
||||
create_time: datetime
|
||||
update_time: datetime
|
||||
|
||||
class UserBase(BaseModel):
|
||||
email: str
|
||||
is_active: bool = True
|
||||
@@ -67,5 +72,34 @@ class Kintone(BaseModel):
|
||||
desc: str = None
|
||||
content: str = None
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class Action(BaseModel):
|
||||
id: int
|
||||
name: str = None
|
||||
title: str = None
|
||||
subtitle: str = None
|
||||
outputpoints: str = None
|
||||
property: str = None
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class FlowBase(BaseModel):
|
||||
flowid: str
|
||||
appid: str
|
||||
eventid: str
|
||||
name: str = None
|
||||
content: str = None
|
||||
|
||||
class Flow(Base):
|
||||
id: int
|
||||
flowid: str
|
||||
appid: str
|
||||
eventid: str
|
||||
name: str = None
|
||||
content: str = None
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
3
frontend/.gitignore
vendored
3
frontend/.gitignore
vendored
@@ -35,3 +35,6 @@ yarn-error.log*
|
||||
|
||||
# local .env files
|
||||
.env.local*
|
||||
|
||||
# pnpm
|
||||
pnpm-lock.yaml
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="ja-jp">
|
||||
<head>
|
||||
<title><%= productName %></title>
|
||||
|
||||
|
||||
72
frontend/package-lock.json
generated
72
frontend/package-lock.json
generated
@@ -10,13 +10,16 @@
|
||||
"dependencies": {
|
||||
"@quasar/extras": "^1.16.4",
|
||||
"axios": "^1.4.0",
|
||||
"pinia": "^2.1.6",
|
||||
"quasar": "^2.6.0",
|
||||
"uuid": "^9.0.0",
|
||||
"vue": "^3.0.0",
|
||||
"vue-router": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@quasar/app-vite": "^1.3.0",
|
||||
"@types/node": "^12.20.21",
|
||||
"@types/uuid": "^9.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
||||
"@typescript-eslint/parser": "^5.10.0",
|
||||
"autoprefixer": "^10.4.2",
|
||||
@@ -28,8 +31,9 @@
|
||||
"typescript": "^4.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18 || ^16 || ^14.19",
|
||||
"node": "^20 ||^18 || ^16 || ^14.19",
|
||||
"npm": ">= 6.13.4",
|
||||
"pnpm": ">=8.6.0",
|
||||
"yarn": ">= 1.21.1"
|
||||
}
|
||||
},
|
||||
@@ -544,6 +548,12 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/uuid": {
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.3.tgz",
|
||||
"integrity": "sha512-taHQQH/3ZyI3zP8M/puluDEIEvtQHVYcC6y3N8ijFtAd28+Ey/G4sg1u2gB01S8MwybLOKAp9/yCMu/uR5l3Ug==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "5.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz",
|
||||
@@ -4070,6 +4080,56 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pinia": {
|
||||
"version": "2.1.6",
|
||||
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.6.tgz",
|
||||
"integrity": "sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==",
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^6.5.0",
|
||||
"vue-demi": ">=0.14.5"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/posva"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.4.0",
|
||||
"typescript": ">=4.4.4",
|
||||
"vue": "^2.6.14 || ^3.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pinia/node_modules/vue-demi": {
|
||||
"version": "0.14.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
|
||||
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.25",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz",
|
||||
@@ -4946,7 +5006,7 @@
|
||||
"version": "4.9.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -5045,6 +5105,14 @@
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
|
||||
@@ -15,13 +15,16 @@
|
||||
"dependencies": {
|
||||
"@quasar/extras": "^1.16.4",
|
||||
"axios": "^1.4.0",
|
||||
"pinia": "^2.1.6",
|
||||
"quasar": "^2.6.0",
|
||||
"uuid": "^9.0.0",
|
||||
"vue": "^3.0.0",
|
||||
"vue-router": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@quasar/app-vite": "^1.3.0",
|
||||
"@types/node": "^12.20.21",
|
||||
"@types/uuid": "^9.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
||||
"@typescript-eslint/parser": "^5.10.0",
|
||||
"autoprefixer": "^10.4.2",
|
||||
@@ -33,8 +36,9 @@
|
||||
"typescript": "^4.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18 || ^16 || ^14.19",
|
||||
"node": "^20 ||^18 || ^16 || ^14.19",
|
||||
"npm": ">= 6.13.4",
|
||||
"yarn": ">= 1.21.1"
|
||||
"yarn": ">= 1.21.1",
|
||||
"pnpm": ">=8.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
export interface Rule{
|
||||
id:number;
|
||||
name:string;
|
||||
condtion:CondtionTree
|
||||
}
|
||||
|
||||
export interface CondtionTree{
|
||||
|
||||
}
|
||||
29
frontend/src/components/flowEditor/left/ControlPanelC.vue
Normal file
29
frontend/src/components/flowEditor/left/ControlPanelC.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class="q-py-md">
|
||||
<q-tree
|
||||
no-connectors
|
||||
selected-color="primary"
|
||||
default-expand-all
|
||||
:nodes="LeftDataBus.root"
|
||||
v-model:selected="flowNames1"
|
||||
node-key="label"
|
||||
>
|
||||
</q-tree>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
LeftDataBus,
|
||||
setControlPanelE,
|
||||
} from 'components/flowEditor/left/DataBus';
|
||||
import { ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
|
||||
// 应该在page中用网络请求获取值并初始化组件
|
||||
// 然后在page中执行setControlPane设置databus
|
||||
const store = useFlowEditorStore();
|
||||
const { flowNames1 } = storeToRefs(store);
|
||||
setControlPanelE();
|
||||
</script>
|
||||
72
frontend/src/components/flowEditor/left/DataBus.ts
Normal file
72
frontend/src/components/flowEditor/left/DataBus.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { reactive } from 'vue'
|
||||
|
||||
export const LeftDataBus = reactive<LeftData>({})
|
||||
|
||||
const defaultData = {
|
||||
root: [
|
||||
{
|
||||
label: 'レコードを追加画面',
|
||||
children: [
|
||||
{
|
||||
label: '追加画面表示した時',
|
||||
header: 'rg',
|
||||
value: '1-1',
|
||||
group: 'g1',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
label: '保存をクリックした時',
|
||||
header: 'rg',
|
||||
value: '1-2',
|
||||
group: 'g1',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
label: '保存成功した時',
|
||||
header: 'rg',
|
||||
value: '1-3',
|
||||
group: 'g1',
|
||||
children: []
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'レコード編集画面',
|
||||
},
|
||||
{
|
||||
label: 'レコード詳細画面',
|
||||
},
|
||||
{
|
||||
label: 'レコード一覧画面',
|
||||
},
|
||||
],
|
||||
data: new Map([['g1', '1-1']])
|
||||
}
|
||||
|
||||
export const setControlPanel = (rootData: LeftData) => {
|
||||
const { root: dr, data: dd } = defaultData
|
||||
LeftDataBus.title = rootData.title
|
||||
LeftDataBus.root = rootData.root ?? dr
|
||||
LeftDataBus.data = rootData.data ?? dd
|
||||
}
|
||||
|
||||
export const setControlPanelE = () => {
|
||||
const { root: dr, data: dd } = defaultData
|
||||
// LeftDataBus.title = rootData.title
|
||||
LeftDataBus.root = dr
|
||||
LeftDataBus.data = dd
|
||||
}
|
||||
|
||||
export interface LeftData {
|
||||
title?: string
|
||||
root?: ControlPanelData[]
|
||||
data?: Map<string, string>
|
||||
}
|
||||
|
||||
export interface ControlPanelData {
|
||||
label: string,
|
||||
header?: string,
|
||||
value?: string,
|
||||
group?: string,
|
||||
children?: ControlPanelData[]
|
||||
}
|
||||
42
frontend/src/components/flowEditor/left/ItemSelector.vue
Normal file
42
frontend/src/components/flowEditor/left/ItemSelector.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div
|
||||
class="row"
|
||||
style="
|
||||
border-radius: 2px;
|
||||
box-shadow: rgba(255, 255, 255, 0.1) 0px 0px 0px 1px inset,
|
||||
rgba(0, 0, 0, 0.3) 0px 0px 0px 1px;
|
||||
"
|
||||
>
|
||||
<q-icon
|
||||
class="self-center q-ma-sm"
|
||||
name="widgets"
|
||||
color="grey-9"
|
||||
style="font-size: 2em"
|
||||
/>
|
||||
|
||||
<div class="col-7 self-center ellipsis">
|
||||
{{ actName }}
|
||||
</div>
|
||||
|
||||
<div class="self-center">
|
||||
<q-btn
|
||||
outline
|
||||
dense
|
||||
label="変 更"
|
||||
padding="none sm"
|
||||
color="primary"
|
||||
></q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed } from 'vue';
|
||||
|
||||
export default {
|
||||
props: ['actName'],
|
||||
setup(props) {
|
||||
const actName = computed(() => props.actName);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
186
frontend/src/components/main/NodeItem.vue
Normal file
186
frontend/src/components/main/NodeItem.vue
Normal file
@@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<div class="row justify-center" :style="{ marginLeft: node.inputPoint !== '' ? '240px' : '' }" >
|
||||
<div class="row">
|
||||
<q-card class="action-node" :class="nodeStyle" :square="false" @click="onNodeClick" >
|
||||
<q-toolbar class="col" >
|
||||
<div class="text-subtitle2">{{ node.subTitle }}</div>
|
||||
<q-space></q-space>
|
||||
<q-btn flat round dense icon="more_horiz" size="sm" >
|
||||
<q-menu auto-close anchor="top right">
|
||||
<q-list>
|
||||
<q-item clickable v-if="!isRoot" @click="onEditNode">
|
||||
<q-item-section avatar><q-icon name="edit" ></q-icon></q-item-section>
|
||||
<q-item-section >編集する</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-if="!isRoot" @click="onDeleteNode">
|
||||
<q-item-section avatar><q-icon name="delete" ></q-icon></q-item-section>
|
||||
<q-item-section>削除する</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable @click="onDeleteAllNode">
|
||||
<q-item-section avatar><q-icon name="delete_sweep" ></q-icon></q-item-section>
|
||||
<q-item-section >以下すべて削除する</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
</q-toolbar>
|
||||
<q-separator />
|
||||
<q-card-section>
|
||||
<div class="text-h7">{{ node.title }}</div>
|
||||
</q-card-section>
|
||||
<template v-if="hasBranch">
|
||||
<q-separator />
|
||||
<q-card-actions align="around">
|
||||
<q-btn flat v-for="(point, index) in node.outputPoints" :key="index">
|
||||
{{ point }}
|
||||
</q-btn>
|
||||
</q-card-actions>
|
||||
</template>
|
||||
</q-card>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="hasBranch">
|
||||
<div class="row justify-center" :style="{ marginLeft: node.inputPoint !== '' ? '240px' : '' }">
|
||||
<div v-for="(point, index) in node.outputPoints" :key="index">
|
||||
<node-line :action-node="node" :mode="getMode(point)" @addNode="addNode" :input-point="point"></node-line>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="!hasBranch">
|
||||
<div class="row justify-center" :style="{ marginLeft: node.inputPoint !== '' ? '240px' : '' }">
|
||||
<node-line :action-node="node" :mode="getMode('')" @addNode="addNode" input-point=""></node-line>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref } from 'vue';
|
||||
import { IActionNode } from '../../types/ActionTypes';
|
||||
import NodeLine, { Direction } from '../main/NodeLine.vue';
|
||||
export default defineComponent({
|
||||
name: 'NodeItem',
|
||||
components: {
|
||||
NodeLine
|
||||
},
|
||||
props: {
|
||||
actionNode: {
|
||||
type: Object as () => IActionNode,
|
||||
required: true
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
'addNode',
|
||||
"nodeSelected",
|
||||
"nodeEdit",
|
||||
"deleteNode",
|
||||
"deleteAllNextNodes",
|
||||
],
|
||||
setup(props, context) {
|
||||
const hasBranch = computed(() => props.actionNode.outputPoints.length > 0);
|
||||
const nodeStyle = computed(() => {
|
||||
return {
|
||||
'root-node': props.actionNode.isRoot,
|
||||
'text-white': props.actionNode.isRoot,
|
||||
'selected': props.isSelected && !props.actionNode.isRoot
|
||||
};
|
||||
});
|
||||
const getMode = (point: string) => {
|
||||
if (point === '' || props.actionNode.outputPoints.length === 0) {
|
||||
return Direction.Default;
|
||||
}
|
||||
if (point === props.actionNode.outputPoints[0]) {
|
||||
if (props.actionNode.nextNodeIds.get(point)) {
|
||||
return Direction.Left;
|
||||
} else {
|
||||
return Direction.LeftNotNext;
|
||||
}
|
||||
} else {
|
||||
if (props.actionNode.nextNodeIds.get(point)) {
|
||||
return Direction.Right;
|
||||
} else {
|
||||
return Direction.RightNotNext;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* アクションノード追加イベントを
|
||||
* @param point 入力ポイント
|
||||
*/
|
||||
const addNode = (point: string) => {
|
||||
context.emit('addNode', props.actionNode, point);
|
||||
}
|
||||
/**
|
||||
* ノード選択状態
|
||||
*/
|
||||
const onNodeClick = () => {
|
||||
context.emit('nodeSelected', props.actionNode);
|
||||
}
|
||||
|
||||
const onEditNode=()=>{
|
||||
context.emit('nodeEdit', props.actionNode);
|
||||
}
|
||||
/**
|
||||
* ノードを削除する
|
||||
*/
|
||||
const onDeleteNode=()=>{
|
||||
context.emit('deleteNode', props.actionNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* ノードの以下すべて削除する
|
||||
*/
|
||||
const onDeleteAllNode=()=>{
|
||||
context.emit('deleteAllNextNodes', props.actionNode);
|
||||
}
|
||||
return {
|
||||
node: props.actionNode,
|
||||
isRoot: props.actionNode.isRoot,
|
||||
hasBranch,
|
||||
nodeStyle,
|
||||
getMode,
|
||||
addNode,
|
||||
onNodeClick,
|
||||
onEditNode,
|
||||
onDeleteNode,
|
||||
onDeleteAllNode
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.action-node {
|
||||
min-width: 300px !important;
|
||||
}
|
||||
|
||||
.line {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.line:after {
|
||||
content: '';
|
||||
background-color: $blue-7;
|
||||
display: block;
|
||||
width: 3px;
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
font-size: 2em;
|
||||
color: $blue-7;
|
||||
}
|
||||
|
||||
.root-node {
|
||||
background-color: $blue-7;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.action-node:not(.root-node):hover{
|
||||
background-color: $light-blue-1;
|
||||
}
|
||||
|
||||
.selected{
|
||||
background-color: $yellow-1;
|
||||
}
|
||||
</style>
|
||||
109
frontend/src/components/main/NodeLine.vue
Normal file
109
frontend/src/components/main/NodeLine.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div>
|
||||
<svg class="node-line">
|
||||
<polyline :points="points.linePoints" class="line" ></polyline>
|
||||
<text class="add-icon" @click="addNode(node)" :x="points.iconPoint.x" :y="points.iconPoint.y" font-family="Arial" font-size="25"
|
||||
text-anchor="middle" dy=".3em" style="cursor: pointer;" >
|
||||
⊕
|
||||
</text>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, defineComponent, computed, PropType } from 'vue';
|
||||
import { IActionNode, ActionNode, ActionFlow, RootAction } from '../../types/ActionTypes';
|
||||
export enum Direction {
|
||||
Default = "None",
|
||||
Left = "LEFT",
|
||||
Right = "RIGHT",
|
||||
LeftNotNext = "LEFTNOTNEXT",
|
||||
RightNotNext = "RIGHTNOTNEXT",
|
||||
}
|
||||
export default defineComponent({
|
||||
name: 'NodeLine',
|
||||
props: {
|
||||
actionNode: {
|
||||
type: Object as PropType<IActionNode>,
|
||||
required: true
|
||||
},
|
||||
mode: {
|
||||
type: String as PropType<Direction>,
|
||||
required: true
|
||||
},
|
||||
inputPoint:{
|
||||
type:String
|
||||
}
|
||||
},
|
||||
emits: ['addNode'],
|
||||
setup(props,context) {
|
||||
const hasBranch = computed(() => props.actionNode.outputPoints.length > 0);
|
||||
const points = computed(() => {
|
||||
switch (props.mode) {
|
||||
case Direction.Left:
|
||||
return {
|
||||
linePoints: '180, 0, 180, 40, 120, 40, 120, 60',
|
||||
iconPoint: { x: 180, y: 20 }
|
||||
};
|
||||
case Direction.Right:
|
||||
return {
|
||||
linePoints: '60, 0, 60, 40, 120, 40, 120, 60',
|
||||
iconPoint: { x: 60, y: 20 }
|
||||
};
|
||||
case Direction.LeftNotNext:
|
||||
return {
|
||||
linePoints: '180, 0, 180, 40',
|
||||
iconPoint: { x: 180, y: 20 }
|
||||
};
|
||||
case Direction.RightNotNext:
|
||||
return {
|
||||
linePoints: '60, 0, 60, 40',
|
||||
iconPoint: { x: 60, y: 30 }
|
||||
};
|
||||
default:
|
||||
return {
|
||||
linePoints: '120, 0, 120, 60',
|
||||
iconPoint: { x: 120, y: 30 }
|
||||
};
|
||||
}
|
||||
});
|
||||
const addNode=(prveNode:IActionNode)=>{
|
||||
context.emit('addNode',props.inputPoint);
|
||||
}
|
||||
|
||||
return {
|
||||
node: props.actionNode,
|
||||
hasBranch,
|
||||
points,
|
||||
addNode
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.node-line {
|
||||
height: 60px;
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.line {
|
||||
stroke: $blue-7;
|
||||
fill: none;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
stroke: $blue-8;
|
||||
fill: $blue-8;
|
||||
font-family: Arial;
|
||||
pointer-events: all;
|
||||
font-size: 2.0em;
|
||||
}
|
||||
|
||||
.add-icon:hover{
|
||||
stroke: $blue-8;
|
||||
fill:$blue-8;
|
||||
font-weight: bold;
|
||||
font-size: 2.4em;
|
||||
}
|
||||
</style>
|
||||
@@ -32,4 +32,3 @@ export interface AppInfo {
|
||||
creator?:User;
|
||||
modifier?:User;
|
||||
}
|
||||
|
||||
|
||||
57
frontend/src/components/right/ActionProperty.vue
Normal file
57
frontend/src/components/right/ActionProperty.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="(item, index) in componentData" :key="index">
|
||||
<component :is="item.component" v-bind="item.props" v-model="item.props.modelValue"></component>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import InputText from '../right/InputText.vue';
|
||||
import SelectBox from '../right/SelectBox.vue';
|
||||
import DatePicker from '../right/DatePicker.vue';
|
||||
import FieldInput from '../right/FieldInput.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ActionProperty',
|
||||
components: {
|
||||
InputText,
|
||||
SelectBox,
|
||||
DatePicker,
|
||||
FieldInput
|
||||
},
|
||||
props: {
|
||||
jsonData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
jsonValue:{
|
||||
type: Object,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
componentData() {
|
||||
return this.jsonData.elements.map((element: any) => {
|
||||
if(this.jsonValue != undefined )
|
||||
{
|
||||
if(this.jsonValue.hasOwnProperty(element.props.name))
|
||||
{
|
||||
element.props.modelValue = this.jsonValue[element.props.name];
|
||||
}
|
||||
else
|
||||
{
|
||||
element.props.modelValue = '';
|
||||
}
|
||||
|
||||
}
|
||||
return {
|
||||
component: element.component,
|
||||
props: element.props,
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
45
frontend/src/components/right/DatePicker.vue
Normal file
45
frontend/src/components/right/DatePicker.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
|
||||
<q-input v-model="selectedDate" :label="placeholder" mask="date" :rules="['date']">
|
||||
<template v-slot:append>
|
||||
<q-icon name="event" class="cursor-pointer">
|
||||
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
||||
<q-date v-model="selectedDate">
|
||||
<div class="row items-center justify-end">
|
||||
<q-btn v-close-popup label="Close" color="primary" flat />
|
||||
</div>
|
||||
</q-date>
|
||||
</q-popup-proxy>
|
||||
</q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref ,watchEffect} from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DatePicker',
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const selectedDate = ref(props.modelValue);
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedDate.value);
|
||||
});
|
||||
|
||||
return {
|
||||
selectedDate
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
62
frontend/src/components/right/FieldInput.vue
Normal file
62
frontend/src/components/right/FieldInput.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<q-input v-model="selectedField" :label="placeholder">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" class="cursor-pointer" @click="showDg"/>
|
||||
</template>
|
||||
</q-input>
|
||||
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg">
|
||||
<field-select ref="appDg" name="フィールド" type="single" :appId="1"></field-select>
|
||||
</show-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref ,watchEffect} from 'vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
import FieldSelect from '../FieldSelect.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FieldInput',
|
||||
components: {
|
||||
ShowDialog,
|
||||
FieldSelect,
|
||||
},
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const appDg = ref();
|
||||
const show = ref(false);
|
||||
const selectedField = ref(props.modelValue);
|
||||
|
||||
const showDg = () => {
|
||||
show.value = true;
|
||||
};
|
||||
|
||||
const closeDg = (val:string) => {
|
||||
if (val == 'OK') {
|
||||
selectedField.value = appDg.value.selected[0].name;
|
||||
}
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedField.value);
|
||||
});
|
||||
|
||||
return {
|
||||
appDg,
|
||||
show,
|
||||
showDg,
|
||||
closeDg,
|
||||
selectedField,
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
33
frontend/src/components/right/InputText.vue
Normal file
33
frontend/src/components/right/InputText.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<q-input :label="placeholder" v-model="inputValue"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,ref,watchEffect } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'InputText',
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const inputValue = ref(props.modelValue);
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', inputValue.value);
|
||||
});
|
||||
|
||||
return {
|
||||
inputValue,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
45
frontend/src/components/right/PropertyList.vue
Normal file
45
frontend/src/components/right/PropertyList.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="(item, index) in properties" :key="index">
|
||||
<component :is="item.component" v-bind="item.props" v-model="item.props.modelValue"></component>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
/**
|
||||
* プロパティ属性設定生成する
|
||||
*/
|
||||
import { PropType, defineComponent,ref } from 'vue';
|
||||
import InputText from '../right/InputText.vue';
|
||||
import SelectBox from '../right/SelectBox.vue';
|
||||
import DatePicker from '../right/DatePicker.vue';
|
||||
import FieldInput from '../right/FieldInput.vue';
|
||||
import { IActionNode,IActionProperty } from 'src/types/ActionTypes';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PropertyList',
|
||||
components: {
|
||||
InputText,
|
||||
SelectBox,
|
||||
DatePicker,
|
||||
FieldInput
|
||||
},
|
||||
props: {
|
||||
nodeProps: {
|
||||
type: Object as PropType<Array<IActionProperty>>,
|
||||
required: true,
|
||||
},
|
||||
jsonValue:{
|
||||
type: Object,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
setup(props, context) {
|
||||
const properties=ref(props.nodeProps)
|
||||
return {
|
||||
properties
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
80
frontend/src/components/right/PropertyPanel.vue
Normal file
80
frontend/src/components/right/PropertyPanel.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div class="q-pa-md q-gutter-sm">
|
||||
<q-drawer
|
||||
side="right"
|
||||
:show-if-above="false"
|
||||
bordered
|
||||
:width="301"
|
||||
:breakpoint="500"
|
||||
class="bg-grey-3"
|
||||
:model-value="showPanel"
|
||||
elevated
|
||||
overlay
|
||||
>
|
||||
<q-card class="column full-height" style="width: 300px">
|
||||
<q-card-section>
|
||||
<div class="text-h6">プロパティ</div>
|
||||
</q-card-section>
|
||||
<q-card-section class="col q-pt-none">
|
||||
<property-list :node-props="actionProps" v-if="showPanel" ></property-list>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions align="right" class="bg-white text-teal">
|
||||
<q-btn flat label="Save" @click="save"/>
|
||||
<q-btn flat label="Cancel" @click="cancel" />
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-drawer>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { reactive, ref,defineComponent, defineProps,PropType ,watchEffect} from 'vue'
|
||||
import PropertyList from 'components/right/PropertyList.vue';
|
||||
import { IActionNode } from 'src/types/ActionTypes';
|
||||
export default defineComponent({
|
||||
name: 'PropertyPanel',
|
||||
components: {
|
||||
PropertyList
|
||||
},
|
||||
props: {
|
||||
actionNode:{
|
||||
type:Object as PropType<IActionNode>,
|
||||
required:true
|
||||
},
|
||||
drawerRight:{
|
||||
type:Boolean,
|
||||
required:true
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
"update:drawerRight"
|
||||
],
|
||||
setup(props,{emit}) {
|
||||
const showPanel =ref(props.drawerRight);
|
||||
const actionProps =ref(props.actionNode.actionProps);
|
||||
watchEffect(() => {
|
||||
showPanel.value = props.drawerRight;
|
||||
actionProps.value= props.actionNode.actionProps;
|
||||
});
|
||||
|
||||
const cancel = async() =>{
|
||||
showPanel.value = false;
|
||||
emit("update:drawerRight",false )
|
||||
}
|
||||
|
||||
const save = async () =>{
|
||||
showPanel.value=false;
|
||||
emit("update:drawerRight",false )
|
||||
}
|
||||
|
||||
return {
|
||||
cancel,
|
||||
save,
|
||||
actionProps,
|
||||
showPanel
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
</style>
|
||||
36
frontend/src/components/right/SelectBox.vue
Normal file
36
frontend/src/components/right/SelectBox.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<q-select v-model="selectedValue" :label="placeholder" :options="options"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,ref,watchEffect } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SelectBox',
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const selectedValue = ref(props.modelValue);
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedValue.value);
|
||||
});
|
||||
|
||||
return {
|
||||
selectedValue
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
22
frontend/src/control/flowctrl.ts
Normal file
22
frontend/src/control/flowctrl.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { api } from 'boot/axios';
|
||||
|
||||
export class FlowCtrl
|
||||
{
|
||||
|
||||
async SaveFlow(jsonData:any):Promise<boolean>
|
||||
{
|
||||
const result = await api.post('http://127.0.0.1:8000/api/flow',jsonData);
|
||||
console.info(result.data)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
async UpdateFlow(jsonData:any):Promise<boolean>
|
||||
{
|
||||
const result = await api.put('http://127.0.0.1:8000/api/flow/' + jsonData.flowid,jsonData);
|
||||
console.info(result.data)
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -56,10 +56,17 @@ const essentialLinks: EssentialLinkProps[] = [
|
||||
target:'_self'
|
||||
},
|
||||
{
|
||||
title: 'ルールエディター',
|
||||
caption: 'rule',
|
||||
icon: 'rule',
|
||||
link: '/#/ruleEditor',
|
||||
title: 'フローエディター',
|
||||
caption: 'flowChart',
|
||||
icon: 'account_tree',
|
||||
link: '/#/flowEditor2',
|
||||
target:'_self'
|
||||
},
|
||||
{
|
||||
title: 'FlowEditor',
|
||||
caption: 'FlowEditor',
|
||||
icon: 'account_tree',
|
||||
link: '/#/flowEditor',
|
||||
target:'_self'
|
||||
},
|
||||
{
|
||||
|
||||
102
frontend/src/pages/FlowChartTest.vue
Normal file
102
frontend/src/pages/FlowChartTest.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<q-page>
|
||||
<div class="flowchart">
|
||||
<node-item v-for="(node,) in refFlow.actionNodes" :key="node.id"
|
||||
:isSelected="node===state.activeNode" :actionNode="node"
|
||||
@addNode="addNode"
|
||||
@nodeSelected="onNodeSelected"
|
||||
@nodeEdit="onNodeEdit"
|
||||
@deleteNode="onDeleteNode"
|
||||
@deleteAllNextNodes="onDeleteAllNextNodes"
|
||||
></node-item>
|
||||
</div>
|
||||
</q-page>
|
||||
<PropertyPanel :actionNode="state.activeNode" v-model:drawerRight="drawerRight"></PropertyPanel>
|
||||
<show-dialog v-model:visible="showAddAction" name="アクション" @close="closeDg">
|
||||
<action-select ref="appDg" name="アクション" type="single"></action-select>
|
||||
</show-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref,reactive,computed} from 'vue';
|
||||
import {IActionNode, ActionNode, IActionFlow, ActionFlow,RootAction, IActionProperty } from 'src/types/ActionTypes';
|
||||
import NodeItem from 'src/components/main/NodeItem.vue';
|
||||
import ShowDialog from 'components/ShowDialog.vue';
|
||||
import ActionSelect from 'components/ActionSelect.vue';
|
||||
import PropertyPanel from 'components/right/PropertyPanel.vue';
|
||||
|
||||
|
||||
const rootNode:RootAction =new RootAction("app.record.create.submit","レコード追加画面","保存するとき");
|
||||
const actionFlow: ActionFlow = new ActionFlow(rootNode);
|
||||
const saibanProps:IActionProperty[]=[{
|
||||
component:"InputText",
|
||||
props:{
|
||||
displayName:"フォーマット",
|
||||
modelValue:"",
|
||||
name:"format",
|
||||
placeholder:"フォーマットを入力してください",
|
||||
}
|
||||
},{
|
||||
component:"FieldInput",
|
||||
props:{
|
||||
displayName:"採番項目",
|
||||
modelValue:"",
|
||||
name:"field",
|
||||
placeholder:"採番項目を選択してください",
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
actionFlow.addNode(new ActionNode('自動採番','文書番号を自動採番する','',[],saibanProps));
|
||||
actionFlow.addNode(new ActionNode('入力データ取得','電話番号を取得する',''));
|
||||
const branchNode = actionFlow.addNode(new ActionNode('条件分岐','電話番号入力形式チャック','',['はい','いいえ'] ));
|
||||
// actionFlow.addNode(new ActionNode('入力データ取得','住所を取得する',''),branchNode,'はい');
|
||||
actionFlow.addNode(new ActionNode('エラー表示','エラー表示して保存しない',''),branchNode,'いいえ' );
|
||||
|
||||
// ref関数を使ってtemplateとバインド
|
||||
const state=reactive({
|
||||
activeNode:rootNode,
|
||||
})
|
||||
|
||||
const refFlow = ref(actionFlow);
|
||||
const showAddAction=ref(false);
|
||||
const drawerRight=ref(false);
|
||||
|
||||
const addActionNode=(action:IActionNode)=>{
|
||||
refFlow.value.actionNodes.push(action);
|
||||
}
|
||||
|
||||
const addNode=(node:IActionNode,inputPoint:string)=>{
|
||||
showAddAction.value=true;
|
||||
}
|
||||
|
||||
const onNodeSelected=(node:IActionNode)=>{
|
||||
//右パネルが開いている場合、自動閉じる
|
||||
if(drawerRight.value && state.activeNode.id!==node.id){
|
||||
drawerRight.value=false;
|
||||
}
|
||||
state.activeNode = node;
|
||||
}
|
||||
|
||||
const onNodeEdit=(node:IActionNode)=>{
|
||||
state.activeNode = node;
|
||||
drawerRight.value=true;
|
||||
}
|
||||
|
||||
const onDeleteNode=(node:IActionNode)=>{
|
||||
refFlow.value.removeNode(node);
|
||||
}
|
||||
|
||||
const onDeleteAllNextNodes=(node:IActionNode)=>{
|
||||
refFlow.value.removeAllNext(node.id);
|
||||
}
|
||||
const closeDg=(val :any)=>{
|
||||
console.log("Dialog closed->",val);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.flowchart{
|
||||
padding-top: 10px;
|
||||
}
|
||||
</style>
|
||||
122
frontend/src/pages/FlowEditorPage.vue
Normal file
122
frontend/src/pages/FlowEditorPage.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="q-ma-md">
|
||||
<div class="q-gutter-xs row items-start">
|
||||
<q-breadcrumbs class="q-pt-xs q-mr-sm" active-color="black">
|
||||
<q-breadcrumbs-el icon="home" />
|
||||
<q-breadcrumbs-el :label="actName" />
|
||||
<q-breadcrumbs-el
|
||||
v-for="flowName in flowNames"
|
||||
:key="flowName"
|
||||
:label="flowName"
|
||||
/>
|
||||
|
||||
<q-breadcrumbs-el :label="flowNames1" />
|
||||
</q-breadcrumbs>
|
||||
<q-separator vertical class="q-mr-xs" />
|
||||
<q-btn
|
||||
unelevated
|
||||
class="q-py-sm"
|
||||
padding="none md none sm"
|
||||
color="blue-1"
|
||||
text-color="primary"
|
||||
size="md"
|
||||
@click="drawerLeft = !drawerLeft"
|
||||
label="変 更"
|
||||
icon="expand_more"
|
||||
dense
|
||||
/>
|
||||
|
||||
<q-space />
|
||||
<q-btn
|
||||
class="q-px-sm q-mr-sm"
|
||||
color="white"
|
||||
size="sm"
|
||||
text-color="black"
|
||||
label="キャンセル"
|
||||
dense
|
||||
/>
|
||||
|
||||
<q-btn
|
||||
class="q-px-sm"
|
||||
color="primary"
|
||||
size="sm"
|
||||
label="保存する"
|
||||
dense
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<q-layout
|
||||
container
|
||||
style="height: 91.5dvb"
|
||||
class="shadow-2 rounded-borders"
|
||||
>
|
||||
<q-drawer side="left" overlay bordered v-model="drawerLeft">
|
||||
<div class="q-pa-sm fixed-right">
|
||||
<q-btn
|
||||
flat
|
||||
round
|
||||
color="primary"
|
||||
icon="close"
|
||||
@click="drawerLeft = !drawerLeft"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="q-mt-lg q-pa-sm">
|
||||
<q-card-section>
|
||||
<div class="flex-center">
|
||||
<div class="row q-pl-md">
|
||||
<p class="text-h6">アクション選択</p>
|
||||
</div>
|
||||
<ItemSelector :actName="actName" />
|
||||
</div>
|
||||
</q-card-section>
|
||||
</div>
|
||||
<q-separator />
|
||||
<div class="q-mt-md q-pa-sm">
|
||||
<q-card-section>
|
||||
<p class="text-h6 q-pl-md q-mb-none">フロー選択</p>
|
||||
<ControlPanel />
|
||||
</q-card-section>
|
||||
</div>
|
||||
<q-separator />
|
||||
<q-card-actions align="right">
|
||||
<div class="q-pa-sm">
|
||||
<q-btn
|
||||
flat
|
||||
color="primary"
|
||||
size="md"
|
||||
@click="drawerLeft = !drawerLeft"
|
||||
label="ジャンプ"
|
||||
dense
|
||||
/>
|
||||
</div>
|
||||
</q-card-actions>
|
||||
</q-drawer>
|
||||
|
||||
<FlowChartTest />
|
||||
</q-layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import FlowChartTest from 'pages/FlowChartTest.vue';
|
||||
import ControlPanel from 'components/flowEditor/left/ControlPanelC.vue';
|
||||
import ItemSelector from 'components/flowEditor/left/ItemSelector.vue';
|
||||
import { ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
|
||||
const actName = ref('勤怠管理 - 4');
|
||||
const flowNames = ref(['レコードを追加画面', '保存をクリックした時']);
|
||||
|
||||
const drawerLeft = ref(false);
|
||||
const store = useFlowEditorStore();
|
||||
const { flowNames1 } = storeToRefs(store);
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="sass"></style>
|
||||
117
frontend/src/pages/FlowEditorPage2.vue
Normal file
117
frontend/src/pages/FlowEditorPage2.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div >
|
||||
<div class="q-ma-md">
|
||||
<div class="q-gutter-xs row items-start">
|
||||
<q-btn
|
||||
size="md"
|
||||
@click="drawerLeft = !drawerLeft"
|
||||
icon="keyboard_double_arrow_right"
|
||||
round
|
||||
/>
|
||||
|
||||
<q-space />
|
||||
<q-btn
|
||||
color="white"
|
||||
size="sm"
|
||||
text-color="black"
|
||||
label="キャンセル"
|
||||
dense
|
||||
/>
|
||||
|
||||
<q-btn
|
||||
class="q-px-sm"
|
||||
color="primary"
|
||||
size="sm"
|
||||
label="保存する"
|
||||
@click="save()"
|
||||
dense
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<q-layout
|
||||
container
|
||||
class="flow-container shadow-2 rounded-borders"
|
||||
>
|
||||
<q-drawer side="left" overlay bordered v-model="drawerLeft">
|
||||
<div class="q-pa-sm fixed-right">
|
||||
<q-btn
|
||||
flat
|
||||
round
|
||||
color="primary"
|
||||
icon="close"
|
||||
@click="drawerLeft = !drawerLeft"
|
||||
/>
|
||||
</div>
|
||||
<div class="q-mt-lg q-pa-sm">
|
||||
<q-card-section>
|
||||
<div class="flex-center">
|
||||
<ItemSelector :actName="actName" />
|
||||
</div>
|
||||
</q-card-section>
|
||||
</div>
|
||||
<q-separator />
|
||||
<div class="q-mt-md q-pa-sm">
|
||||
<q-card-section>
|
||||
<ControlPanel />
|
||||
</q-card-section>
|
||||
</div>
|
||||
<q-separator />
|
||||
<q-card-actions align="right">
|
||||
<div class="q-pa-sm">
|
||||
<q-btn
|
||||
flat
|
||||
color="primary"
|
||||
size="md"
|
||||
@click="drawerLeft = !drawerLeft"
|
||||
label="ジャンプ"
|
||||
dense
|
||||
/>
|
||||
</div>
|
||||
</q-card-actions>
|
||||
</q-drawer>
|
||||
|
||||
<FlowChartTest />
|
||||
</q-layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import FlowChartTest from 'pages/FlowChartTest.vue';
|
||||
import ControlPanel from 'components/flowEditor/left/ControlPanelC.vue';
|
||||
import ItemSelector from 'components/flowEditor/left/ItemSelector.vue';
|
||||
import { ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
import { FlowCtrl } from '../control/flowctrl'
|
||||
|
||||
const flowCtrl = new FlowCtrl();
|
||||
const actName = ref('勤怠管理 - 4');
|
||||
|
||||
const drawerLeft = ref(false);
|
||||
const store = useFlowEditorStore();
|
||||
const { flowNames1 } = storeToRefs(store);
|
||||
let isNew = ref(true);
|
||||
|
||||
const save = () =>{
|
||||
|
||||
if(isNew.value)
|
||||
{
|
||||
flowCtrl.SaveFlow({appid:'1',flowid:'flow123',eventid:'event123',name:'test',content:'[]'});
|
||||
isNew.value = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
flowCtrl.UpdateFlow({appid:'1',flowid:'flow123',eventid:'event123',name:'test',content:'[{"a":"b"}]'});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.flow-toolbar{
|
||||
opacity: 50%;
|
||||
}
|
||||
.flow-container{
|
||||
height: calc(91.5dvb - 50px);
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
96
frontend/src/pages/testFlow.vue
Normal file
96
frontend/src/pages/testFlow.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div class="q-pa-md q-gutter-sm">
|
||||
<q-btn label="プロパティ" icon="keyboard_arrow_right" color="primary" @click="open('right')" />
|
||||
<!-- <q-btn label="Readプロパティ" icon="keyboard_arrow_right" color="primary" @click="write('right')" /> -->
|
||||
|
||||
<q-dialog v-model="dialog" :position="position">
|
||||
<q-card class="column full-height" style="width: 300px">
|
||||
<q-card-section>
|
||||
<div class="text-h6">プロパティ</div>
|
||||
</q-card-section>
|
||||
|
||||
|
||||
<q-card-section class="col q-pt-none">
|
||||
<ActionProperty :jsonData="jsonData" :jsonValue="jsonValue"/>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions align="right" class="bg-white text-teal">
|
||||
<q-btn flat label="Save" v-close-popup @click="save"/>
|
||||
<q-btn flat label="Cancel" v-close-popup />
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref,onMounted } from 'vue'
|
||||
import ActionProperty from 'components/right/ActionProperty.vue';
|
||||
const dialog = ref(false)
|
||||
const position = ref('top')
|
||||
|
||||
const jsonData = {
|
||||
elements: [
|
||||
{
|
||||
component: 'InputText',
|
||||
props: {
|
||||
name:'1',
|
||||
placeholder: 'Enter some text',
|
||||
modelValue: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'SelectBox',
|
||||
props: {
|
||||
name:'2',
|
||||
placeholder: 'Choose an option',
|
||||
modelValue: '',
|
||||
options: [
|
||||
'option1',
|
||||
'option2',
|
||||
'option3'
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'DatePicker',
|
||||
props: {
|
||||
name:'3',
|
||||
placeholder: 'Choose a date',
|
||||
modelValue: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'FieldInput',
|
||||
props: {
|
||||
name:'4',
|
||||
placeholder: 'Choose a field',
|
||||
modelValue: '',
|
||||
},
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
let jsonValue = {
|
||||
1:'abc',
|
||||
2:'option2',
|
||||
3:'2023/09/04',
|
||||
4:'6666'
|
||||
};
|
||||
|
||||
const open = (pos:string) => {
|
||||
position.value = pos
|
||||
dialog.value = true
|
||||
};
|
||||
|
||||
const save = async () =>{
|
||||
|
||||
jsonData.elements.forEach(property => {
|
||||
if(jsonValue != undefined)
|
||||
{
|
||||
jsonValue[property.props.name] = property.props.modelValue;
|
||||
}
|
||||
});
|
||||
console.log(jsonValue);
|
||||
|
||||
}
|
||||
</script>
|
||||
101
frontend/src/pages/testRight.vue
Normal file
101
frontend/src/pages/testRight.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div class="q-pa-md q-gutter-sm">
|
||||
<q-btn label="プロパティ" icon="keyboard_arrow_right" color="primary" @click="drawerRight = !drawerRight" />
|
||||
<!-- <q-btn label="Readプロパティ" icon="keyboard_arrow_right" color="primary" @click="write('right')" /> -->
|
||||
<q-drawer
|
||||
side="right"
|
||||
v-model="drawerRight"
|
||||
show-if-above
|
||||
bordered
|
||||
:width="301"
|
||||
:breakpoint="500"
|
||||
class="bg-grey-3"
|
||||
>
|
||||
<q-card class="column full-height" style="width: 300px">
|
||||
<q-card-section>
|
||||
<div class="text-h6">プロパティ</div>
|
||||
</q-card-section>
|
||||
|
||||
|
||||
<q-card-section class="col q-pt-none">
|
||||
<ActionProperty :jsonData="jsonData" :jsonValue="jsonValue" v-if="drawerRight"/>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions align="right" class="bg-white text-teal">
|
||||
<q-btn flat label="Save" @click="save"/>
|
||||
<q-btn flat label="Cancel" @click="cancel" />
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-drawer>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import ActionProperty from 'components/right/ActionProperty.vue';
|
||||
const drawerRight = ref(false);
|
||||
const jsonData = {
|
||||
elements: [
|
||||
{
|
||||
component: 'InputText',
|
||||
props: {
|
||||
name:'1',
|
||||
placeholder: 'Enter some text',
|
||||
modelValue: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'SelectBox',
|
||||
props: {
|
||||
name:'2',
|
||||
placeholder: 'Choose an option',
|
||||
modelValue: '',
|
||||
options: [
|
||||
'option1',
|
||||
'option2',
|
||||
'option3'
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'DatePicker',
|
||||
props: {
|
||||
name:'3',
|
||||
placeholder: 'Choose a date',
|
||||
modelValue: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'FieldInput',
|
||||
props: {
|
||||
name:'4',
|
||||
placeholder: 'Choose a field',
|
||||
modelValue: '',
|
||||
},
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
let jsonValue = {
|
||||
1:'abc',
|
||||
2:'option2',
|
||||
3:'2023/09/04',
|
||||
4:'6666'
|
||||
};
|
||||
|
||||
const cancel = async() =>{
|
||||
drawerRight.value = false;
|
||||
}
|
||||
|
||||
const save = async () =>{
|
||||
|
||||
jsonData.elements.forEach(property => {
|
||||
if(jsonValue != undefined)
|
||||
{
|
||||
jsonValue[property.props.name] = property.props.modelValue;
|
||||
}
|
||||
});
|
||||
console.log(jsonValue);
|
||||
drawerRight.value=false;
|
||||
|
||||
}
|
||||
</script>
|
||||
@@ -4,19 +4,17 @@ const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/',
|
||||
component: () => import('layouts/MainLayout.vue'),
|
||||
children: [{ path: '', component: () => import('pages/IndexPage.vue') }],
|
||||
children: [
|
||||
{ path: '', component: () => import('pages/IndexPage.vue') },
|
||||
{ path: 'ruleEditor', component: () => import('pages/RuleEditor.vue') },
|
||||
{ path: 'test', component: () => import('pages/testQursar.vue') },
|
||||
{ path: 'flow', component: () => import('pages/testFlow.vue') },
|
||||
{ path: 'flowchart', component: () => import('pages/FlowChartTest.vue') },
|
||||
{ path: 'flowEditor', component: () => import('pages/FlowEditorPage.vue') },
|
||||
{ path: 'flowEditor2', component: () => import('pages/FlowEditorPage2.vue') },
|
||||
{ path: 'right', component: () => import('pages/testRight.vue') },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/ruleEditor/',
|
||||
component: () => import('layouts/MainLayout.vue'),
|
||||
children: [{ path: '', component: () => import('pages/RuleEditor.vue') }],
|
||||
},
|
||||
{
|
||||
path: '/test/',
|
||||
component: () => import('layouts/MainLayout.vue'),
|
||||
children: [{ path: '', component: () => import('pages/testQursar.vue') }],
|
||||
},
|
||||
|
||||
// Always leave this as last one,
|
||||
// but you can also remove it
|
||||
{
|
||||
|
||||
25
frontend/src/stores/flowEditor.ts
Normal file
25
frontend/src/stores/flowEditor.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useFlowEditorStore = defineStore('flowEditor', {
|
||||
state: () => ({
|
||||
counter: 0,
|
||||
flowNames: [],
|
||||
flowNames1: ''
|
||||
}),
|
||||
|
||||
getters: {
|
||||
doubleCount(state) {
|
||||
return state.counter * 2;
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
increment() {
|
||||
this.counter++;
|
||||
},
|
||||
|
||||
setDefaultFlow() {
|
||||
this.counter++
|
||||
}
|
||||
}
|
||||
});
|
||||
32
frontend/src/stores/index.ts
Normal file
32
frontend/src/stores/index.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { store } from 'quasar/wrappers'
|
||||
import { createPinia } from 'pinia'
|
||||
import { Router } from 'vue-router';
|
||||
|
||||
/*
|
||||
* When adding new properties to stores, you should also
|
||||
* extend the `PiniaCustomProperties` interface.
|
||||
* @see https://pinia.vuejs.org/core-concepts/plugins.html#typing-new-store-properties
|
||||
*/
|
||||
declare module 'pinia' {
|
||||
export interface PiniaCustomProperties {
|
||||
readonly router: Router;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If not building with SSR mode, you can
|
||||
* directly export the Store instantiation;
|
||||
*
|
||||
* The function below can be async too; either use
|
||||
* async/await or return a Promise which resolves
|
||||
* with the Store instance.
|
||||
*/
|
||||
|
||||
export default store((/* { ssrContext } */) => {
|
||||
const pinia = createPinia()
|
||||
|
||||
// You can add Pinia plugins here
|
||||
// pinia.use(SomePiniaPlugin)
|
||||
|
||||
return pinia
|
||||
})
|
||||
10
frontend/src/stores/store-flag.d.ts
vendored
Normal file
10
frontend/src/stores/store-flag.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/* eslint-disable */
|
||||
// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
|
||||
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
|
||||
import "quasar/dist/types/feature-flag";
|
||||
|
||||
declare module "quasar/dist/types/feature-flag" {
|
||||
interface QuasarFeatureFlags {
|
||||
store: true;
|
||||
}
|
||||
}
|
||||
19
frontend/src/stores/testStore.ts
Normal file
19
frontend/src/stores/testStore.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useCounterStore = defineStore('counter', {
|
||||
state: () => ({
|
||||
counter: 0
|
||||
}),
|
||||
|
||||
getters: {
|
||||
doubleCount (state) {
|
||||
return state.counter * 2;
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
increment () {
|
||||
this.counter++;
|
||||
}
|
||||
}
|
||||
});
|
||||
358
frontend/src/types/ActionTypes.ts
Normal file
358
frontend/src/types/ActionTypes.ts
Normal file
@@ -0,0 +1,358 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
|
||||
/**
|
||||
* アクションのプロパティ定義
|
||||
*/
|
||||
export interface IActionProperty {
|
||||
component: string;
|
||||
props: {
|
||||
//プロパティ名
|
||||
name: string;
|
||||
//プロパティ表示名
|
||||
displayName:string;
|
||||
placeholder: string;
|
||||
//プロパティ設定値
|
||||
modelValue: any;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* アクションタイプ定義
|
||||
*/
|
||||
export interface IActionNode{
|
||||
id:string;
|
||||
//アクション名
|
||||
name:string;
|
||||
title:string;
|
||||
subTitle:string;
|
||||
inputPoint:string;
|
||||
//出力ポイント(条件分岐以外未使用)
|
||||
outputPoints:Array<string>;
|
||||
//ルートアクション(Kintone event)
|
||||
isRoot:boolean;
|
||||
//アクションのプロパティ定義
|
||||
actionProps:Array<IActionProperty>;
|
||||
//アクションのプロパティ設定値抽出
|
||||
ActionValue:object
|
||||
prevNodeId?: string;
|
||||
nextNodeIds: Map<string, string>;
|
||||
}
|
||||
/**
|
||||
* アクションフローの定義
|
||||
*/
|
||||
export interface IActionFlow {
|
||||
actionNodes:Array<IActionNode>
|
||||
}
|
||||
|
||||
/**
|
||||
* アクションのプロパティ定義に基づいたクラス
|
||||
*/
|
||||
class ActionProperty implements IActionProperty {
|
||||
component: string;
|
||||
props: {
|
||||
// プロパティ名
|
||||
name: string;
|
||||
// プロパティ表示名
|
||||
displayName: string;
|
||||
placeholder: string;
|
||||
// プロパティ設定値
|
||||
modelValue: any;
|
||||
};
|
||||
|
||||
static defaultProperty():IActionProperty{
|
||||
return new ActionProperty('InputText','displayName','表示名','表示を入力してください','');
|
||||
};
|
||||
|
||||
constructor(
|
||||
component: string,
|
||||
name: string,
|
||||
displayName: string,
|
||||
placeholder: string,
|
||||
modelValue: any
|
||||
) {
|
||||
this.component = component;
|
||||
this.props = {
|
||||
name: name,
|
||||
displayName: displayName,
|
||||
placeholder: placeholder,
|
||||
modelValue: modelValue
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* IActionNodeの実装、RootActionNode以外のアクション定義
|
||||
*/
|
||||
export class ActionNode implements IActionNode {
|
||||
id:string;
|
||||
name: string;
|
||||
title:string;
|
||||
get subTitle():string{
|
||||
return this.name;
|
||||
};
|
||||
inputPoint:string;
|
||||
//出力ポイント(条件分岐以外未使用)
|
||||
outputPoints:Array<string>;
|
||||
actionProps: Array<IActionProperty>;
|
||||
get isRoot(): boolean{
|
||||
return false;
|
||||
};
|
||||
get ActionValue():object{
|
||||
const propValue:any={};
|
||||
this.actionProps.forEach((value)=>{
|
||||
propValue[value.props.name]=value.props.modelValue
|
||||
});
|
||||
return propValue;
|
||||
};
|
||||
prevNodeId?: string;
|
||||
nextNodeIds: Map<string, string>;
|
||||
constructor(
|
||||
name: string,
|
||||
title:string,
|
||||
inputPoint:string,
|
||||
outputPoint: Array<string> = [],
|
||||
actionProps: Array<IActionProperty> =[ActionProperty.defaultProperty()]
|
||||
) {
|
||||
this.id=uuidv4();
|
||||
this.name = name;
|
||||
this.title=title;
|
||||
this.inputPoint=inputPoint;
|
||||
this.outputPoints = outputPoint;
|
||||
const defProp =ActionProperty.defaultProperty();
|
||||
defProp.props.displayName=title;
|
||||
this.actionProps =actionProps;
|
||||
const prop = this.actionProps.find((prop)=>prop.props.name===defProp.props.name);
|
||||
if(prop===undefined){
|
||||
this.actionProps.unshift(defProp);
|
||||
}
|
||||
this.nextNodeIds=new Map<string,string>();
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* ルートアクション定義
|
||||
*/
|
||||
export class RootAction implements IActionNode {
|
||||
id:string;
|
||||
name: string;
|
||||
title:string;
|
||||
subTitle:string;
|
||||
inputPoint:string;
|
||||
//出力ポイント(条件分岐以外未使用)
|
||||
outputPoints:Array<string>;
|
||||
isRoot: boolean;
|
||||
actionProps: Array<IActionProperty>;
|
||||
ActionValue:object;
|
||||
prevNodeId?: string = undefined;
|
||||
nextNodeIds: Map<string, string>;
|
||||
constructor(
|
||||
name: string,
|
||||
title:string,
|
||||
subTitle:string,
|
||||
) {
|
||||
this.id=uuidv4();
|
||||
this.name = name;
|
||||
this.title=title;
|
||||
this.subTitle=subTitle;
|
||||
this.inputPoint='';
|
||||
this.outputPoints = [];
|
||||
this.isRoot = true;
|
||||
this.actionProps=[];
|
||||
this.ActionValue={};
|
||||
this.nextNodeIds=new Map<string,string>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* アクションフローの定義
|
||||
*/
|
||||
export class ActionFlow implements IActionFlow {
|
||||
|
||||
actionNodes:Array<IActionNode>;
|
||||
constructor(actionNodes:Array<IActionNode>|RootAction){
|
||||
if(actionNodes instanceof Array){
|
||||
this.actionNodes=actionNodes;
|
||||
}else{
|
||||
this.actionNodes=[actionNodes];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* ノードを追加する
|
||||
* 1.ID採番する
|
||||
* 2.前のノードを関連付け
|
||||
* @param newNode 新規追加するノード
|
||||
* @param prevNode 前のノード
|
||||
* @param inputPoint 入力ポイント
|
||||
* @returns 追加されたノード
|
||||
*/
|
||||
addNode(
|
||||
newNode:IActionNode,
|
||||
prevNode?:IActionNode,
|
||||
inputPoint?:string):IActionNode
|
||||
{
|
||||
if(inputPoint!==undefined){
|
||||
newNode.inputPoint=inputPoint;
|
||||
}
|
||||
if(prevNode){
|
||||
this.connectNodes(prevNode,newNode,inputPoint||'');
|
||||
}else{
|
||||
prevNode=this.actionNodes[this.actionNodes.length-1];
|
||||
this.connectNodes(prevNode,newNode,inputPoint||'');
|
||||
}
|
||||
this.actionNodes.push(newNode);
|
||||
return newNode;
|
||||
}
|
||||
/**
|
||||
* ノードを削除する
|
||||
* @param delNode
|
||||
*/
|
||||
removeNode(targetNode :IActionNode):boolean{
|
||||
if (!targetNode ) {
|
||||
return false;
|
||||
}
|
||||
if(targetNode.isRoot){
|
||||
return false;
|
||||
}
|
||||
this.disconnectFromPrevNode(targetNode);
|
||||
this.reconnectOrRemoveNextNodes(targetNode);
|
||||
this.removeFromActionNodes(targetNode.id);
|
||||
return true;
|
||||
}
|
||||
/***
|
||||
* 目標ノードの次のノードを全部削除する
|
||||
*/
|
||||
removeAllNext(targetNodeId :string){
|
||||
if (!targetNodeId || targetNodeId==='') {
|
||||
return false;
|
||||
}
|
||||
const targetNode=this.findNodeById(targetNodeId);
|
||||
if(!targetNode){
|
||||
return false;
|
||||
}
|
||||
if(targetNode.nextNodeIds.size==0){
|
||||
return false;
|
||||
}
|
||||
for (const [, id] of targetNode.nextNodeIds) {
|
||||
this.removeAllNext(id);
|
||||
this.removeFromActionNodes(id);
|
||||
}
|
||||
}
|
||||
|
||||
// 断开与前一个节点的连接
|
||||
disconnectFromPrevNode(targetNode: IActionNode): void {
|
||||
const prevNodeId = targetNode.prevNodeId;
|
||||
if (prevNodeId) {
|
||||
const prevNode = this.findNodeById(prevNodeId);
|
||||
if (prevNode) {
|
||||
for (const [key, value] of prevNode.nextNodeIds) {
|
||||
if (value === targetNode.id) {
|
||||
prevNode.nextNodeIds.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 从 actionNodes 数组中移除节点
|
||||
private removeFromActionNodes(targetNodeId: string): void {
|
||||
const index = this.actionNodes.findIndex(node => node.id === targetNodeId);
|
||||
if (index > -1) {
|
||||
this.actionNodes.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ノード削除時、前のノードと次のノードを接続する
|
||||
* @param targetNode
|
||||
*/
|
||||
reconnectOrRemoveNextNodes(targetNode: IActionNode): void {
|
||||
if(!targetNode || !targetNode.prevNodeId ){
|
||||
return;
|
||||
}
|
||||
//前のノードを取得
|
||||
const prevNode = this.findNodeById(targetNode.prevNodeId);
|
||||
if(!prevNode) return;
|
||||
//次のノード取得
|
||||
const nextNodeIds = targetNode.nextNodeIds;
|
||||
if(nextNodeIds.size==0){
|
||||
return;
|
||||
}
|
||||
//次のノード一つの場合
|
||||
if(nextNodeIds.size==1){
|
||||
const nextNodeId = nextNodeIds.get('');
|
||||
if(!nextNodeId) return;
|
||||
const nextNode = this.findNodeById(nextNodeId) ;
|
||||
if(!nextNode) return;
|
||||
nextNode.prevNodeId=prevNode.id;
|
||||
prevNode.nextNodeIds.set(targetNode.inputPoint||'',nextNodeId);
|
||||
return;
|
||||
}
|
||||
//二つ以上の場合
|
||||
for(const [point,nextid] of nextNodeIds){
|
||||
const nextNode = this.findNodeById(nextid);
|
||||
if(!nextNode) return;
|
||||
if(!this.connectNodes(prevNode,nextNode,point)){
|
||||
this.removeAllNext(nextid);
|
||||
this.removeFromActionNodes(nextid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 二つノードを接続する
|
||||
* @param prevNode
|
||||
* @param nextNodeId
|
||||
* @param point
|
||||
* @returns
|
||||
*/
|
||||
connectNodes(prevNode:IActionNode,nextNode:IActionNode,point:string):boolean{
|
||||
if(!prevNode || !nextNode){
|
||||
return false;
|
||||
}
|
||||
if(!nextNode) return false;
|
||||
prevNode.nextNodeIds.set(point,nextNode.id);
|
||||
nextNode.prevNodeId=prevNode.id;
|
||||
nextNode.inputPoint=point;
|
||||
return true;
|
||||
}
|
||||
|
||||
resetNodeRelation(prevNode: IActionNode, newNode: IActionNode, inputPoint?: string) {
|
||||
// 设置新节点和前节点的关联
|
||||
prevNode.nextNodeIds.set(inputPoint || '', newNode.id);
|
||||
newNode.prevNodeId = prevNode.id;
|
||||
// 保存前节点原有的后节点ID
|
||||
const originalNextNodeId = prevNode.nextNodeIds.get(inputPoint || '');
|
||||
this.setNewNodeNextId(newNode,originalNextNodeId,inputPoint);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 後ノードと新ノードの関連付け
|
||||
* @param newNode
|
||||
* @param originalNextNodeId
|
||||
* @param inputPoint
|
||||
*/
|
||||
private setNewNodeNextId(newNode: IActionNode, originalNextNodeId: string | undefined, inputPoint?: string) {
|
||||
// 如果原先的后节点存在
|
||||
if (originalNextNodeId) {
|
||||
// 检查新节点的 outputPoints 是否包含该 inputPoint
|
||||
if (newNode.outputPoints.includes(inputPoint || '')) {
|
||||
newNode.nextNodeIds.set(inputPoint || '', originalNextNodeId);
|
||||
} else {
|
||||
// 如果不包含,选择新节点的一个 outputPoint
|
||||
const alternativeOutputPoint = newNode.outputPoints.length > 0 ? newNode.outputPoints[0] : '';
|
||||
newNode.nextNodeIds.set(alternativeOutputPoint, originalNextNodeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* IDでActionNodeを取得する
|
||||
*/
|
||||
findNodeById(id: string): IActionNode | undefined {
|
||||
return this.actionNodes.find((node) => node.id === id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -278,6 +278,11 @@
|
||||
"@types/mime" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/uuid@^9.0.3":
|
||||
version "9.0.3"
|
||||
resolved "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.3.tgz"
|
||||
integrity sha512-taHQQH/3ZyI3zP8M/puluDEIEvtQHVYcC6y3N8ijFtAd28+Ey/G4sg1u2gB01S8MwybLOKAp9/yCMu/uR5l3Ug==
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^5.10.0":
|
||||
version "5.61.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz"
|
||||
@@ -2144,6 +2149,14 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1:
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
pinia@^2.0.0, pinia@^2.1.6:
|
||||
version "2.1.6"
|
||||
resolved "https://registry.npmjs.org/pinia/-/pinia-2.1.6.tgz"
|
||||
integrity sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==
|
||||
dependencies:
|
||||
"@vue/devtools-api" "^6.5.0"
|
||||
vue-demi ">=0.14.5"
|
||||
|
||||
postcss-selector-parser@^6.0.9:
|
||||
version "6.0.13"
|
||||
resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz"
|
||||
@@ -2657,7 +2670,7 @@ type-is@~1.6.18:
|
||||
media-typer "0.3.0"
|
||||
mime-types "~2.1.24"
|
||||
|
||||
typescript@^4.5.4, "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta":
|
||||
typescript@^4.5.4, "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta", typescript@>=4.4.4:
|
||||
version "4.9.5"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz"
|
||||
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
|
||||
@@ -2707,6 +2720,11 @@ utils-merge@1.0.1:
|
||||
resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz"
|
||||
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
|
||||
|
||||
uuid@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz"
|
||||
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
|
||||
|
||||
vary@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz"
|
||||
@@ -2724,6 +2742,11 @@ vary@~1.1.2:
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
vue-demi@>=0.14.5:
|
||||
version "0.14.6"
|
||||
resolved "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz"
|
||||
integrity sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==
|
||||
|
||||
vue-eslint-parser@^9.3.0:
|
||||
version "9.3.1"
|
||||
resolved "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.1.tgz"
|
||||
@@ -2744,7 +2767,7 @@ vue-router@^4.0.0, vue-router@^4.0.12:
|
||||
dependencies:
|
||||
"@vue/devtools-api" "^6.5.0"
|
||||
|
||||
vue@^3.0.0, vue@^3.2.0, vue@^3.2.25, vue@^3.2.29, vue@3.3.4:
|
||||
"vue@^2.6.14 || ^3.3.0", vue@^3.0.0, "vue@^3.0.0-0 || ^2.6.0", vue@^3.2.0, vue@^3.2.25, vue@^3.2.29, vue@3.3.4:
|
||||
version "3.3.4"
|
||||
resolved "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz"
|
||||
integrity sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==
|
||||
|
||||
Reference in New Issue
Block a user