Compare commits
15 Commits
fang
...
maxz-new-s
| Author | SHA1 | Date | |
|---|---|---|---|
| df59bff6ae | |||
| 59e6d33656 | |||
| b641c729c2 | |||
| 142cdcda38 | |||
| fc2669dabf | |||
| 8e095b51e3 | |||
| ff03490209 | |||
| 40cd9998d0 | |||
| 973ba159b4 | |||
|
|
6a06c71104 | ||
|
|
100d8de54f | ||
|
|
7c667660c0 | ||
|
|
4eb56372a5 | ||
|
|
16edd398be | ||
|
|
4e08159e6d |
3
frontend/.gitignore
vendored
3
frontend/.gitignore
vendored
@@ -35,3 +35,6 @@ yarn-error.log*
|
|||||||
|
|
||||||
# local .env files
|
# local .env files
|
||||||
.env.local*
|
.env.local*
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm-lock.yaml
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="ja-jp">
|
||||||
<head>
|
<head>
|
||||||
<title><%= productName %></title>
|
<title><%= productName %></title>
|
||||||
|
|
||||||
|
|||||||
19
frontend/package-lock.json
generated
19
frontend/package-lock.json
generated
@@ -11,12 +11,14 @@
|
|||||||
"@quasar/extras": "^1.16.4",
|
"@quasar/extras": "^1.16.4",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"quasar": "^2.6.0",
|
"quasar": "^2.6.0",
|
||||||
|
"uuid": "^9.0.0",
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
"vue-router": "^4.0.0"
|
"vue-router": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@quasar/app-vite": "^1.3.0",
|
"@quasar/app-vite": "^1.3.0",
|
||||||
"@types/node": "^12.20.21",
|
"@types/node": "^12.20.21",
|
||||||
|
"@types/uuid": "^9.0.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
||||||
"@typescript-eslint/parser": "^5.10.0",
|
"@typescript-eslint/parser": "^5.10.0",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
@@ -28,8 +30,9 @@
|
|||||||
"typescript": "^4.5.4"
|
"typescript": "^4.5.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18 || ^16 || ^14.19",
|
"node": "^20 ||^18 || ^16 || ^14.19",
|
||||||
"npm": ">= 6.13.4",
|
"npm": ">= 6.13.4",
|
||||||
|
"pnpm": ">=8.6.0",
|
||||||
"yarn": ">= 1.21.1"
|
"yarn": ">= 1.21.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -544,6 +547,12 @@
|
|||||||
"@types/node": "*"
|
"@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": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "5.61.0",
|
"version": "5.61.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz",
|
||||||
@@ -5045,6 +5054,14 @@
|
|||||||
"node": ">= 0.4.0"
|
"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": {
|
"node_modules/vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
|||||||
@@ -16,12 +16,14 @@
|
|||||||
"@quasar/extras": "^1.16.4",
|
"@quasar/extras": "^1.16.4",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"quasar": "^2.6.0",
|
"quasar": "^2.6.0",
|
||||||
|
"uuid": "^9.0.0",
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
"vue-router": "^4.0.0"
|
"vue-router": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@quasar/app-vite": "^1.3.0",
|
"@quasar/app-vite": "^1.3.0",
|
||||||
"@types/node": "^12.20.21",
|
"@types/node": "^12.20.21",
|
||||||
|
"@types/uuid": "^9.0.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
||||||
"@typescript-eslint/parser": "^5.10.0",
|
"@typescript-eslint/parser": "^5.10.0",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
@@ -33,8 +35,9 @@
|
|||||||
"typescript": "^4.5.4"
|
"typescript": "^4.5.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18 || ^16 || ^14.19",
|
"node": "^20 ||^18 || ^16 || ^14.19",
|
||||||
"npm": ">= 6.13.4",
|
"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{
|
|
||||||
|
|
||||||
}
|
|
||||||
73
frontend/src/components/flowEditor/left/ControlPanel.vue
Normal file
73
frontend/src/components/flowEditor/left/ControlPanel.vue
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<template>
|
||||||
|
<div class="q-py-md">
|
||||||
|
<q-list>
|
||||||
|
<q-expansion-item
|
||||||
|
group="somegroup"
|
||||||
|
label="レコードを追加画面"
|
||||||
|
default-opened
|
||||||
|
>
|
||||||
|
<q-card-section>
|
||||||
|
<q-checkbox v-model="setting.v1" label="追加画面表示した時" />
|
||||||
|
<q-checkbox v-model="setting.v2" label="保存をクリックした時" />
|
||||||
|
<q-checkbox v-model="setting.v3" label="保存成功した時" />
|
||||||
|
</q-card-section>
|
||||||
|
</q-expansion-item>
|
||||||
|
|
||||||
|
<q-expansion-item group="somegroup" label="レコード編集画面">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quidem,
|
||||||
|
eius reprehenderit eos corrupti commodi magni quaerat ex numquam,
|
||||||
|
dolorum officiis modi facere maiores architecto suscipit iste
|
||||||
|
eveniet doloribus ullam aliquid.
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
|
||||||
|
<q-expansion-item group="somegroup" label="レコード詳細画面">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quidem,
|
||||||
|
eius reprehenderit eos corrupti commodi magni quaerat ex numquam,
|
||||||
|
dolorum officiis modi facere maiores architecto suscipit iste
|
||||||
|
eveniet doloribus ullam aliquid.
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
|
||||||
|
<q-expansion-item group="somegroup" label="レコード一覧画面">
|
||||||
|
<q-card class="bg-teal-2">
|
||||||
|
<q-card-section>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quidem,
|
||||||
|
eius reprehenderit eos corrupti commodi magni quaerat ex numquam,
|
||||||
|
dolorum officiis modi facere maiores architecto suscipit iste
|
||||||
|
eveniet doloribus ullam aliquid.
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
</q-list>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<q-btn @click="clear" label="clear"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, Ref } from 'vue';
|
||||||
|
interface Setting {
|
||||||
|
v1: boolean;
|
||||||
|
v2: boolean;
|
||||||
|
v3: boolean;
|
||||||
|
}
|
||||||
|
const setting: Ref<Setting> = ref({
|
||||||
|
v1: true,
|
||||||
|
v2: true,
|
||||||
|
v3: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let clear = () => {
|
||||||
|
setting.value.v1 = false
|
||||||
|
setting.value.v2 = false
|
||||||
|
setting.value.v3 = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
24
frontend/src/components/flowEditor/left/ControlPanelC.vue
Normal file
24
frontend/src/components/flowEditor/left/ControlPanelC.vue
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<template>
|
||||||
|
<div class="q-py-md">
|
||||||
|
<q-tree :nodes="LeftDataBus.root" node-key="label">
|
||||||
|
<template #header-rg="p">
|
||||||
|
<ControlPanelTreeRadio
|
||||||
|
:node="p.node"
|
||||||
|
:dataBus="LeftDataBus"
|
||||||
|
></ControlPanelTreeRadio>
|
||||||
|
</template>
|
||||||
|
</q-tree>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
LeftDataBus,
|
||||||
|
setControlPanelE,
|
||||||
|
} from 'components/flowEditor/left/DataBus';
|
||||||
|
import ControlPanelTreeRadio from './ControlPanelTreeRadio.vue';
|
||||||
|
|
||||||
|
// 应该在page中用网络请求获取值并初始化组件
|
||||||
|
// 然后在page中执行setControlPane设置databus
|
||||||
|
setControlPanelE();
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<template>
|
||||||
|
<q-radio v-model="model" :val="node.value" :label="node.label" />
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { LeftData, ControlPanelData } from 'components/flowEditor/left/DataBus';
|
||||||
|
|
||||||
|
const props = defineProps(['node', 'dataBus']);
|
||||||
|
|
||||||
|
const node = computed(() => props.node as ControlPanelData);
|
||||||
|
|
||||||
|
const model = computed({
|
||||||
|
get() {
|
||||||
|
return (props.dataBus as LeftData).data?.get(node.value.group ?? 'n');
|
||||||
|
},
|
||||||
|
set(newValue) {
|
||||||
|
(props.dataBus as LeftData).data?.set(
|
||||||
|
node.value.group ?? 'n',
|
||||||
|
newValue ?? ''
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</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[]
|
||||||
|
}
|
||||||
36
frontend/src/components/flowEditor/left/ItemSelector.vue
Normal file
36
frontend/src/components/flowEditor/left/ItemSelector.vue
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<div class="ItemSelector q-pa-sm">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto">
|
||||||
|
<q-icon name="widgets" color="primary" size="2.5em" />
|
||||||
|
</div>
|
||||||
|
<div class="col flex">
|
||||||
|
<div class="q-pa-sm flex" style="align-items: center">{{title}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto flex">
|
||||||
|
<div class="flex" style="align-items: center">
|
||||||
|
<q-btn
|
||||||
|
class="q-px-sm"
|
||||||
|
color="white"
|
||||||
|
size="sm"
|
||||||
|
text-color="black"
|
||||||
|
label="変 更"
|
||||||
|
dense
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const title = ref('勤怠管理')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
.ItemSelector
|
||||||
|
border: 0.15em solid rgba(#999, .4)
|
||||||
|
border-radius: 0.4em
|
||||||
|
</style>
|
||||||
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;
|
creator?:User;
|
||||||
modifier?:User;
|
modifier?:User;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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>
|
||||||
@@ -56,10 +56,17 @@ const essentialLinks: EssentialLinkProps[] = [
|
|||||||
target:'_self'
|
target:'_self'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'ルールエディター',
|
title: 'フローエディター',
|
||||||
caption: 'rule',
|
caption: 'flowChart',
|
||||||
icon: 'rule',
|
icon: 'account_tree',
|
||||||
link: '/#/ruleEditor',
|
link: '/#/flowChart',
|
||||||
|
target:'_self'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'FlowEditor',
|
||||||
|
caption: 'FlowEditor',
|
||||||
|
icon: 'account_tree',
|
||||||
|
link: '/#/flowEditor',
|
||||||
target:'_self'
|
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>
|
||||||
37
frontend/src/pages/FlowEditorPage.vue
Normal file
37
frontend/src/pages/FlowEditorPage.vue
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<q-page>
|
||||||
|
<div class="q-pa-md">
|
||||||
|
<div class="q-gutter-sm row items-start">
|
||||||
|
<q-breadcrumbs>
|
||||||
|
<q-breadcrumbs-el icon="home" to="/" />
|
||||||
|
<q-breadcrumbs-el :label="title" icon="rule" />
|
||||||
|
</q-breadcrumbs>
|
||||||
|
</div>
|
||||||
|
<div class="q-pa-md">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-2 column">
|
||||||
|
<ItemSelector />
|
||||||
|
<div class="col-auto"><ControlPanel /></div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="col">
|
||||||
|
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ItemSelector from 'components/flowEditor/left/ItemSelector.vue';
|
||||||
|
import ControlPanel from 'components/flowEditor/left/ControlPanelC.vue';
|
||||||
|
interface FlowEditorPageProps {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<FlowEditorPageProps>(), {
|
||||||
|
title: 'FlowEditor',
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass"></style>
|
||||||
@@ -4,30 +4,16 @@ const routes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
component: () => import('layouts/MainLayout.vue'),
|
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: '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') }],
|
|
||||||
},
|
|
||||||
,
|
|
||||||
{
|
|
||||||
path: '/flow/',
|
|
||||||
component: () => import('layouts/MainLayout.vue'),
|
|
||||||
children: [{ path: '', component: () => import('pages/testFlow.vue') }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/right/',
|
|
||||||
component: () => import('layouts/MainLayout.vue'),
|
|
||||||
children: [{ path: '', component: () => import('pages/testRight.vue') }],
|
|
||||||
},
|
|
||||||
|
|
||||||
// Always leave this as last one,
|
// Always leave this as last one,
|
||||||
// but you can also remove it
|
// but you can also remove it
|
||||||
{
|
{
|
||||||
|
|||||||
357
frontend/src/types/ActionTypes.ts
Normal file
357
frontend/src/types/ActionTypes.ts
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
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(!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/mime" "*"
|
||||||
"@types/node" "*"
|
"@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":
|
"@typescript-eslint/eslint-plugin@^5.10.0":
|
||||||
version "5.61.0"
|
version "5.61.0"
|
||||||
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz"
|
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz"
|
||||||
@@ -2707,6 +2712,11 @@ utils-merge@1.0.1:
|
|||||||
resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz"
|
resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz"
|
||||||
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
|
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:
|
vary@~1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz"
|
resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz"
|
||||||
|
|||||||
Reference in New Issue
Block a user