条件エディタ追加

This commit is contained in:
2024-01-22 10:52:55 +09:00
parent 276e5e9122
commit 5cd6d02f6e
29 changed files with 1209 additions and 382 deletions

View File

@@ -30,6 +30,10 @@ api.interceptors.response.use(
path:"/login",
query:{redirect:router.currentRoute.value.fullPath}
});
// router.push({
// path:"/login",
// query:{redirect:router.currentRoute.value.fullPath}
// });
}
}
)

View File

@@ -31,10 +31,10 @@ export default {
];
const rows = reactive([])
onMounted(async () => {
const res =await api.get('api/kintone/1');
const res =await api.get('api/actions');
res.data.forEach((item) =>
{
rows.push({name:item.name,desc:item.desc,content:item.content});
rows.push({name:item.name,desc:item.title,outputPoints:item.outputpoints,property:item.property});
});
isLoaded.value=true;
});

View File

@@ -75,6 +75,7 @@ export default {
width: 300px;
max-height: 60px;
max-width: 300px;
white-space: break-spaces;
}
.spinner{
min-height: 300px;

View File

@@ -0,0 +1,51 @@
<template>
<show-dialog v-model:visible="showflg" name="条件エディタ" @close="closeDg" width="60vw" height="60vh">
<NodeCondition v-model:conditionTree="tree"></NodeCondition>
</show-dialog>
</template>
<script lang="ts">
import { defineComponent, ref ,watchEffect} from 'vue';
import ShowDialog from '../../components/ShowDialog.vue';
import NodeCondition from './NodeCondition.vue';
import { ConditionTree } from '../../types/Conditions';
export default defineComponent({
name: 'ConditionObject',
components: {
ShowDialog,
NodeCondition,
},
props: {
conditionTree: {
type: ConditionTree,
default: null
},
show:{
type:Boolean,
default:false
}
},
setup(props,context) {
const appDg = ref();
const tree = ref(props.conditionTree);
const closeDg = (val:string) => {
if (val == 'OK') {
context.emit("update:conditionTree",tree.value);
}
showflg.value=false;
context.emit("update:show",false);
};
const showflg =ref(props.show);
watchEffect(() => {
showflg.value=props.show;
});
return {
tree,
appDg,
closeDg,
showflg
};
}
});
</script>

View File

@@ -0,0 +1,79 @@
<template>
<q-field v-model="selectedField" labelColor="primary" class="condition-object"
:clearable="isSelected" stack-label :dense="true" :outlined="true" >
<template v-slot:control >
<q-chip color="primary" text-color="white" v-if="isSelected" :dense="true" class="selected-obj">
{{ selectedField.name }}
</q-chip>
</template>
<template v-slot:append>
<q-icon name="search" class="cursor-pointer" @click="showDg"/>
</template>
</q-field>
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg" widht="400px">
<field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select>
</show-dialog>
</template>
<script lang="ts">
import { defineComponent, ref ,watchEffect,computed} from 'vue';
import ShowDialog from '../ShowDialog.vue';
import FieldSelect from '../FieldSelect.vue';
import { useFlowEditorStore } from '../../stores/flowEditor';
export default defineComponent({
name: 'ConditionObject',
components: {
ShowDialog,
FieldSelect,
},
props: {
modelValue: {
type: Object,
default: null
},
},
setup(props, { emit }) {
const appDg = ref();
const show = ref(false);
const selectedField = ref(props.modelValue);
const store = useFlowEditorStore();
const isSelected = computed(()=>{
return selectedField.value!==null && typeof selectedField.value === 'object' && ('name' in selectedField.value)
});
const showDg = () => {
show.value = true;
};
const closeDg = (val:string) => {
if (val == 'OK') {
selectedField.value = appDg.value.selected[0];
}
};
watchEffect(() => {
emit('update:modelValue', selectedField.value);
});
return {
store,
appDg,
show,
showDg,
closeDg,
selectedField,
isSelected
};
}
});
</script>
<style lang="scss">
.condition-object{
min-width: 200px;
max-height: 40px;
padding: 2px;
}
.selected-obj{
margin: 0px;
}
</style>

View File

@@ -0,0 +1,235 @@
<template>
<!-- <q-toolbar class="bg-grey-3" flat dense round icon="menu" aria-label="Menu" @click.stop>
<q-toolbar-title>条件エディタ</q-toolbar-title>
<q-space></q-space>
<q-btn flat round dense icon="info" color="blue" @click="showingCondition=!showingCondition"></q-btn>
</q-toolbar> -->
<div class="q-pa-md">
<q-tree :nodes="[tree.root]" node-key="index" children-key="children"
tick-strategy="strict" v-model:ticked="ticked" :expanded="expanded" default-expand-all dense color="primary" >
<template v-slot:header-root="prop">
<!-- root -->
<div class="row items-center" @click.stop>
<q-select v-model="prop.node.logicalOperator" :options="logicalOperators" filled outlined dense></q-select>
<q-btn flat round dense icon="more_horiz" size="sm" >
<q-menu auto-close anchor="top right">
<q-list>
<q-item clickable @click="addGroup(prop.node, LogicalOperator.AND)">
<q-item-section avatar><q-icon name="playlist_add" ></q-icon></q-item-section>
<q-item-section>グループの追加</q-item-section>
</q-item>
<q-item clickable @click="addCondition(prop.node)">
<q-item-section avatar><q-icon name="add_circle_outline" ></q-icon></q-item-section>
<q-item-section >条件式の追加</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
</template>
<template v-slot:header-generic="prop">
<!-- logic group -->
<div v-if="prop.node.type !== NodeType.Condition" class="row items-center" @click.stop>
<q-select v-model="prop.node.logicalOperator" :options="logicalOperators" :outlined="true" :filled="true" :dense="true"></q-select>
<q-btn flat round dense icon="more_horiz" size="sm" >
<q-menu auto-close anchor="top right">
<q-list>
<q-item clickable @click="addGroup(prop.node, LogicalOperator.AND)">
<q-item-section avatar><q-icon name="playlist_add" ></q-icon></q-item-section>
<q-item-section >グループ追加</q-item-section>
</q-item>
<q-item clickable @click="addCondition(prop.node)">
<q-item-section avatar><q-icon name="add_circle_outline" ></q-icon></q-item-section>
<q-item-section >条件式追加</q-item-section>
</q-item>
<q-separator inset/>
<q-item clickable @click="moveUp(prop.node)">
<q-item-section avatar><q-icon name="arrow_upward" ></q-icon></q-item-section>
<q-item-section >一つ上に移動</q-item-section>
</q-item>
<q-item clickable @click="moveDown(prop.node)">
<q-item-section avatar><q-icon name="arrow_downward" ></q-icon></q-item-section>
<q-item-section >一つ下に移動</q-item-section>
</q-item>
<q-separator inset/>
<q-item clickable @click="removeNode(prop.node)">
<q-item-section avatar><q-icon name="delete" color="negative"></q-icon></q-item-section>
<q-item-section >削除</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
<!-- condition -->
<div @click.stop @keypress.stop v-else >
<div class="row no-wrap items-center">
<ConditionObject v-bind="prop.node" v-model="prop.node.object" class="col-4"></ConditionObject>
<q-select v-model="prop.node.operator" :options="operators" class="operator" :outlined="true" :dense="true"></q-select>
<q-input v-if="!prop.node.object || !('options' in prop.node.object)"
v-model="prop.node.value"
class="condition-value" :outlined="true" :dense="true" ></q-input>
<q-select v-if="prop.node.object && ('options' in prop.node.object)"
v-model="prop.node.value"
:options="objectValueOptions(prop.node.object.options)"
clearable
value-key="index"
class="condition-value" :outlined="true" :dense="true" ></q-select>
<q-btn flat round dense icon="more_horiz" size="sm" >
<q-menu auto-close anchor="top right">
<q-list>
<q-item clickable @click="moveUp(prop.node)">
<q-item-section avatar><q-icon name="arrow_upward" ></q-icon></q-item-section>
<q-item-section >一つ上に移動</q-item-section>
</q-item>
<q-item clickable @click="moveDown(prop.node)">
<q-item-section avatar><q-icon name="arrow_downward" ></q-icon></q-item-section>
<q-item-section >一つ下に移動</q-item-section>
</q-item>
<q-separator inset/>
<q-item clickable @click="removeNode(prop.node)">
<q-item-section avatar><q-icon name="delete" color="negative"></q-icon></q-item-section>
<q-item-section>削除</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
</div>
</template>
</q-tree>
<!-- <q-btn @click="addCondition(tree.root)" class="q-mt-md" color="primary" icon="mdi-plus">Add Condition</q-btn> -->
<!-- <q-btn @click="getConditionString()" class="q-mt-md" color="primary" icon="mdi-plus">Show Condtion</q-btn>
<q-btn @click="getConditionJson()" class="q-mt-md" color="primary" icon="mdi-plus">Show Condtion data</q-btn>
<q-btn @click="LoadCondition()" class="q-mt-md" color="primary" icon="mdi-plus">Load Condition</q-btn> -->
<q-tooltip anchor="center middle" v-model="showingCondition" no-parent-event>
{{ conditionString }}
</q-tooltip>
</div>
</template>
<script lang="ts">
import { defineComponent,ref,reactive, computed } from 'vue';
import { INode,ConditionTree,GroupNode,ConditionNode, LogicalOperator,Operator,NodeType } from '../../types/Conditions';
import ConditionObject from './ConditionObject.vue';
export default defineComponent( {
name: 'NodeCondition',
components: {
ConditionObject
},
props:{
conditionTree: {
type: ConditionTree,
default: null
},
show:{
type:Boolean,
default:false
}
},
setup(props) {
const ticked= ref([]);
const showingCondition=ref(false);
const logicalOperators = computed(()=>{
const opts=[];
for(const op in LogicalOperator){
opts.push(LogicalOperator[op as keyof typeof LogicalOperator]);
}
return opts;
});
const operators =computed(()=>{
const opts=[];
for(const op in Operator){
opts.push(Operator[op as keyof typeof Operator]);
}
return opts;
});
const tree = reactive(props.conditionTree);
const conditionString = computed(()=>{
return tree.buildConditionString(tree.root);
});
const objectValueOptions=(options:any):any[]=>{
const opts:any[] =[];
Object.keys(options).forEach((key) =>
{
const opt=options[key];
opts.push(opt);
});
return opts;
};
const addGroup = (parent:GroupNode, logicOp:LogicalOperator) => {
if(!parent){
parent=tree.root;
}
tree.addNode(parent,new GroupNode(logicOp,parent));
};
const addCondition = (parent:GroupNode) => {
const newNode = new ConditionNode(LogicalOperator.AND,{},Operator.Equal,'',parent);
tree.addNode(parent,newNode);
};
const removeNode = (node:INode) => {
tree.removeNode(node);
};
const moveUp =(node:INode)=>{
tree.moveNode(node,'up');
}
const moveDown =(node:INode)=>{
tree.moveNode(node,'down');
}
const getConditionJson=()=>{
return tree.toJson();
}
const LoadCondition=()=>{
tree.fromJson(conditionString.value);
}
const expanded=computed(()=>tree.getGroups(tree.root));
// addCondition(tree.root);
return {
showingCondition,
conditionString,
tree,
ticked,
logicalOperators,
operators,
addGroup,
addCondition,
removeNode,
moveUp,
moveDown,
LogicalOperator,
Operator,
NodeType,
getConditionJson,
LoadCondition,
objectValueOptions,
expanded
};
},
});
</script>
<style lang="scss">
.condition-value{
min-width: 200px;
max-height: 40px;
padding: 2px;
}
.operator{
min-width: 150px;
max-height: 40px;
padding: 2px;
text-align: center;
font-size: 12pt;
}
</style>

View File

@@ -25,21 +25,23 @@ export default {
{ name: 'type', label: 'フィールドタイプ', align: 'left',field: 'type', sortable: true }
]
const rows = reactive([])
onMounted( () => {
api.get('api/v1/appfields', {
onMounted( async () => {
const res = await api.get('api/v1/appfields', {
params:{
app: props.appId
}
}).then(res =>{
let fields = res.data.properties;
console.log(fields);
Object.keys(fields).forEach((key) =>
{
rows.push({name:fields[key].label,code:fields[key].code,type:fields[key].type});
});
isLoaded.value=true;
});
});
let fields = res.data.properties;
console.log(fields);
Object.keys(fields).forEach((key) =>
{
const fld=fields[key];
// rows.push({name:fields[key].label,code:fields[key].code,type:fields[key].type});
rows.push({name:fld.label,...fld});
});
isLoaded.value=true;
});
return {
columns,
rows,

View File

@@ -1,12 +1,12 @@
<template>
<!-- <div class="q-pa-md q-gutter-sm" > -->
<q-dialog :model-value="visible" persistent>
<q-card :style="{minWidth : width }">
<q-dialog :model-value="visible" persistent bordered>
<q-card :style="{minWidth : width}" >
<q-card-section>
<div class="text-h6">{{ name }}選択</div>
<div class="text-h6">{{ name }}</div>
</q-card-section>
<q-card-section class="q-pt-none">
<q-card-section class="q-pt-none" :style="{...(height? {minHeight:height}:{}) }">
<slot></slot>
</q-card-section>
<q-card-actions align="right" class="text-primary">
@@ -24,7 +24,8 @@ export default {
props: {
name:String,
visible: Boolean,
width:String
width:String,
height:String
},
emits: [
'close'

View File

@@ -22,7 +22,7 @@
></q-btn>
</div>
</div>
<ShowDialog v-model:visible="showSelectApp" name="アプリ" @close="closeDg" width="500px">
<ShowDialog v-model:visible="showSelectApp" name="アプリ選択" @close="closeDg" width="600px" >
<AppSelect ref="appDg" name="アプリ" type="single"></AppSelect>
</ShowDialog>
</template>

View File

@@ -1,195 +0,0 @@
<template>
<div class="q-pa-md">
<q-tree :nodes="tree" node-key="num" v-if="tree.length" default-expand-all>
<template v-slot:header-root="prop">
<div class="row items-center" @click.stop>
<q-icon :name="prop.node.icon || 'share'" color="orange" size="28px" class="q-mr-sm" />
<div style="width:100px">
<q-select v-model="prop.node.logicalOperator" :options="logicalOperators" label="ロジクール"></q-select>
</div>
<q-btn @click="removeNode(prop.node)" class="q-ml-md" color="negative" icon="delete" size="5px"></q-btn>
<q-btn-dropdown class="q-ml-md" color="primary" icon="add" size="5px">
<q-list>
<q-item clickable v-close-popup @click="addGroup(prop.node, logicalOperators[0])">
<q-item-section>
<q-item-label>Group</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-close-popup @click.stop="addCondition(prop.node)">
<q-item-section>
<q-item-label>Condtion</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div>
</template>
<template v-slot:header-generic="prop" >
<div class="row items-center" @click.stop>
<q-icon :name="prop.node.icon || 'star'" color="orange" size="28px" class="q-mr-sm" />
<div v-if="prop.node.type === 'group'">
<q-icon :name="prop.node.icon || 'share'" color="orange" size="28px" class="q-mr-sm" />
<div style="width:100px">
<q-select v-model="prop.node.logicalOperator" :options="logicalOperators" label="ロジクール"></q-select>
</div>
<q-btn @click="removeNode(prop.node)" class="q-ml-md" color="negative" icon="delete" size="5px"></q-btn>
<q-btn-dropdown class="q-ml-md" color="primary" icon="add" size="5px">
<q-list>
<q-item clickable v-close-popup @click="addGroup(prop.node, logicalOperators[0])">
<q-item-section>
<q-item-label>Group</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="addCondition(prop.node)">
<q-item-section>
<q-item-label>Condtion</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div>
<div @click.stop @keypress.stop v-else >
<q-select v-model="prop.node.object" :options="objects" label="フィールド"></q-select>
<q-select v-model="prop.node.operator" :options="operators" label="オペレーター"></q-select>
<q-input v-model="prop.node.value" label="値"></q-input>
<q-btn @click="removeNode(prop.node)" class="q-ml-md" color="negative" icon="delete" size="5px"></q-btn>
</div>
</div>
</template>
</q-tree>
<q-btn @click="addGroup(null, logicalOperators[0])" class="q-mt-md" color="primary" icon="mdi-plus">Add Condition</q-btn>
<q-btn @click="getConditionString()" class="q-mt-md" color="primary" icon="mdi-plus">Show Condtion</q-btn>
<p>{{ conditionString }}</p>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const conditionString = ref('');
const tree = ref([]);
let num = 0;
const logicalOperators = [
{ label: 'AND', value: 'AND' },
{ label: 'OR', value: 'OR' },
];
const objects = [
{ label: 'Field 1', value: 'field1' },
{ label: 'Field 2', value: 'field2' },
{ label: 'Field 3', value: 'field3' },
];
const operators = [
{ label: 'Equals', value: '=' },
{ label: 'Not Equals', value: '<>' },
{ label: 'Greater Than', value: '>' },
{ label: 'Less Than', value: '<' },
];
const buildConditionString = (node) => {
if (node.type === 'group') {
let conditionString = '(';
for (let i = 0; i < node.children.length; i++) {
let childConditionString = buildConditionString(node.children[i]);
if (childConditionString !== '') {
conditionString += childConditionString;
if (i < node.children.length - 1) {
conditionString += ` ${node.logicalOperator.value} `;
}
}
}
conditionString += ')';
return conditionString;
} else {
if (node.object && node.operator && node.value) {
return `${node.object.value} ${node.operator.value} '${node.value}'`;
} else {
return '';
}
}
};
const getConditionString = () => {
conditionString.value = buildConditionString(tree.value[0]);
};
const addGroup = (node, logicalOperator = 'AND') => {
const newNode = {
header: 'generic',
num: num,
type: 'group',
logicalOperator: logicalOperator,
children: [],
parent: node,
};
num++;
const childNode = {
header: 'generic',
index: num,
type: 'condition',
logicalOperator: logicalOperator,
object: objects[0],
operator: operators[0],
value: '',
children: [],
parent: newNode,
};
newNode.children.push(childNode);
if (node === null) {
newNode.header = 'root';
tree.value.push(newNode);
} else {
node.children.push(newNode);
}
};
const addCondition = (node) => {
const newNode = {
header: 'generic',
num: num,
type: 'condition',
object: objects[0],
operator: operators[0],
value: '',
children: [],
parent: node,
};
num++;
node.children.push(newNode);
};
const removeNode = (node) => {
if (node.header === 'root') {
tree.value = [];
} else {
const index = node.parent.children.indexOf(node);
if (index != -1) {
node.parent.children.splice(index, 1);
if (node.parent.children.length == 0) {
removeNode(node.parent);
}
}
}
};
return {
conditionString,
tree,
num,
logicalOperators,
objects,
operators,
buildConditionString,
getConditionString,
addGroup,
addCondition,
removeNode,
};
},
};
</script>

View File

@@ -0,0 +1,95 @@
<template>
<q-field v-model="tree" :label="displayName" labelColor="primary" stack-label >
<template v-slot:control >
<q-card flat class="full-width">
<q-card-actions vertical>
<q-btn color="grey-3" text-color="black" @click="showDg()">クリックで設定{{ isSetted?'設定済み':'未設定' }}</q-btn>
</q-card-actions>
<q-card-section class="text-caption" >
<div v-if="!isSetted">{{ placeholder }}</div>
<div v-else>{{ conditionString }}</div>
</q-card-section>
</q-card>
</template>
</q-field>
<condition-editor v-model:show="show" v-model:conditionTree="tree"></condition-editor>
</template>
<script lang="ts">
import { defineComponent, ref ,watchEffect,computed,reactive} from 'vue';
import { ConditionTree,GroupNode,ConditionNode,LogicalOperator,Operator } from 'app/src/types/Conditions';
import ConditionEditor from '../ConditionEditor/ConditionEditor.vue'
export default defineComponent({
name: 'FieldInput',
components: {
ConditionEditor
},
props: {
displayName:{
type: String,
default: '',
},
name:{
type: String,
default: '',
},
placeholder: {
type: String,
default: '',
},
hint:{
type: String,
default: '',
},
modelValue: {
type: String,
default: null
},
},
setup(props, { emit }) {
const appDg = ref();
const show = ref(false);
const tree = reactive(new ConditionTree());
if(props.modelValue && props.modelValue!==''){
tree.fromJson(props.modelValue);
}else{
const newNode = new ConditionNode(LogicalOperator.AND,{},Operator.Equal,'',tree.root);
tree.addNode(tree.root,newNode);
}
const isSetted=ref(props.modelValue && props.modelValue!=='');
const conditionString = computed(()=>{
return tree.buildConditionString(tree.root);
});
const showDg = () => {
show.value = true;
};
const closeDg = (val:string) => {
if (val == 'OK') {
const conditionJson = tree.toJson();
isSetted.value=true;
emit('update:modelValue', conditionJson);
}
};
watchEffect(() => {
const conditionJson = tree.toJson();
emit('update:modelValue', conditionJson);
});
return {
appDg,
isSetted,
show,
showDg,
closeDg,
tree,
conditionString
};
}
});
</script>

View File

@@ -25,6 +25,10 @@ export default defineComponent({
type: String,
default: '',
},
name:{
type: String,
default: '',
},
placeholder: {
type: String,
default: '',

View File

@@ -27,6 +27,11 @@ import { defineComponent, ref ,watchEffect,computed} from 'vue';
import ShowDialog from '../ShowDialog.vue';
import FieldSelect from '../FieldSelect.vue';
import { useFlowEditorStore } from 'stores/flowEditor';
interface IField{
name:string,
code:string,
type:string
}
export default defineComponent({
name: 'FieldInput',
components: {
@@ -38,6 +43,10 @@ export default defineComponent({
type: String,
default: '',
},
name:{
type: String,
default: '',
},
placeholder: {
type: String,
default: '',

View File

@@ -12,6 +12,10 @@ export default defineComponent({
type: String,
default: '',
},
name:{
type: String,
default: '',
},
placeholder: {
type: String,
default: '',

View File

@@ -12,6 +12,10 @@ export default defineComponent({
type: String,
default: '',
},
name:{
type: String,
default: '',
},
placeholder: {
type: String,
default: '',

View File

@@ -1,6 +1,6 @@
<template>
<div>
<div v-for="(item, index) in properties" :key="index">
<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>
@@ -16,6 +16,7 @@ import SelectBox from '../right/SelectBox.vue';
import DatePicker from '../right/DatePicker.vue';
import FieldInput from '../right/FieldInput.vue';
import MuiltInputText from '../right/MuiltInputText.vue';
import ConditionInput from '../right/ConditionInput.vue';
import { IActionNode,IActionProperty } from 'src/types/ActionTypes';
export default defineComponent({
@@ -25,7 +26,8 @@ export default defineComponent({
SelectBox,
DatePicker,
FieldInput,
MuiltInputText
MuiltInputText,
ConditionInput
},
props: {
nodeProps: {
@@ -45,3 +47,5 @@ export default defineComponent({
}
});
</script>
<style lang="scss">
</style>

View File

@@ -68,6 +68,13 @@ const essentialLinks: EssentialLinkProps[] = [
link: '/#/FlowChart',
target:'_self'
},
{
title: '条件エディター',
caption: 'condition',
icon: 'tune',
link: '/#/condition',
target:'_self'
},
{
title:'',
isSeparator:true

View File

@@ -92,6 +92,9 @@ const addActionNode=(action:IActionNode)=>{
}
const addNode=(node:IActionNode,inputPoint:string)=>{
if(drawerRight.value){
drawerRight.value=false;
}
showAddAction.value=true;
prevNodeIfo.value.prevNode=node;
prevNodeIfo.value.inputPoint=inputPoint;
@@ -112,19 +115,28 @@ const onNodeEdit=(node:IActionNode)=>{
const onDeleteNode=(node:IActionNode)=>{
if(!store.currentFlow) return;
//右パネルが開いている場合、自動閉じる
if(drawerRight.value && state.activeNode.id===node.id){
drawerRight.value=false;
}
store.currentFlow?.removeNode(node);
}
const onDeleteAllNextNodes=(node:IActionNode)=>{
if(!store.currentFlow) return;
//右パネルが開いている場合、自動閉じる
if(drawerRight.value){
drawerRight.value=false;
}
store.currentFlow?.removeAllNext(node.id);
}
const closeDg=(val :any)=>{
console.log("Dialog closed->",val);
if (val == 'OK') {
const data = appDg.value.selected[0];
const actionProps=JSON.parse(data.content);
const action = new ActionNode(data.name,data.desc,"",[],actionProps);
const actionProps=JSON.parse(data.property);
const outputPoint =JSON.parse(data.outputPoints);
const action = new ActionNode(data.name,data.desc,"",outputPoint,actionProps);
store.currentFlow?.addNode(action, prevNodeIfo.value.prevNode,prevNodeIfo.value.inputPoint);
}
}

View File

@@ -1,14 +1,35 @@
<template>
<q-page>
<div class="flowchart">
<node-condition></node-condition>
<q-btn @click="showCondition()" class="q-mt-md" color="primary" icon="mdi-plus">条件エディタ表示</q-btn>
</div>
<condition-editor v-model:show="show" v-model:conditionTree="tree"></condition-editor>
<p>{{conditionString}}</p>
</q-page>
</template>
<script setup lang="ts">
import NodeCondition from 'src/components/main/NodeCondition.vue';
import {ref,reactive,computed} from 'vue';
import ConditionEditor from '../components/ConditionEditor/ConditionEditor.vue';
import { useFlowEditorStore } from 'stores/flowEditor';
import { ConditionTree,GroupNode,ConditionNode,LogicalOperator,Operator } from 'app/src/types/Conditions';
const store = useFlowEditorStore();
const tree = reactive(new ConditionTree());
const newNode = new ConditionNode(LogicalOperator.AND,{},Operator.Equal,'',tree.root);
tree.addNode(tree.root,newNode);
const show =ref(false);
const showCondition=()=>{
show.value=true;
}
const conditionString = computed(()=>{
return tree.buildConditionString(tree.root);
});
store.setApp({
appId:'146',
name:'トリトン管理部日報'
});
</script>
<style lang="scss">

View File

@@ -0,0 +1,249 @@
//ノード種別
export enum NodeType{
Root = 'root',
LogicGroup ='logicgroup',
Condition ='condition'
}
//ロジックオペレーター
export enum LogicalOperator{
AND = 'AND',
OR = 'OR'
}
//条件オペレーター
export enum Operator{
Equal = '=',
NotEqual='!=',
Greater = '>',
GreaterOrEqual = '>=',
Less = '<',
LessOrEqual = '<=',
Contains = 'contains',
NotContains = 'not contains',
StartWith = 'start With',
EndWith = 'end with',
NotStartWith = 'not start with',
NotEndWith = 'not end with'
}
// INode
export interface INode {
index:number;
type: NodeType;
header:string;
parent: INode | null;
logicalOperator:LogicalOperator
}
// ロジックノード
export class GroupNode implements INode {
index:number;
type: NodeType;
children: INode[];
parent: INode | null;
logicalOperator: LogicalOperator;
get label():string{
return this.logicalOperator;
}
get header():string{
return this.type===NodeType.Root?'root':'generic';
}
get expanded():boolean{
return this.children.length>0;
}
constructor(logicOp:LogicalOperator, parent: INode | null) {
this.index=0;
this.type = parent==null?NodeType.Root: NodeType.LogicGroup;
this.logicalOperator = logicOp;
this.parent=parent;
this.children=[];
}
static fromJSON(json: any, parent: INode | null = null): GroupNode {
const node = new GroupNode(json.logicalOperator, parent);
node.index=json.index;
node.children = json.children.map((childJson: any) => {
return childJson.type === NodeType.LogicGroup
? GroupNode.fromJSON(childJson, node)
: ConditionNode.fromJSON(childJson, node);
});
return node;
}
}
// 条件式ノード
export class ConditionNode implements INode {
index: number;
type: NodeType;
parent:INode;
get logicalOperator(): LogicalOperator{
return this.parent.logicalOperator;
};
object: any; // 比較元
operator: Operator; // 比較子
value: any;
get header():string{
return 'generic';
}
constructor(logicOp: LogicalOperator, object: any, operator: Operator, value: any, parent: GroupNode) {
this.index=0;
this.type = NodeType.Condition;
this.object = object;
this.operator = operator;
this.value = value;
this.parent=parent;
}
static fromJSON(json: any, parent: GroupNode): ConditionNode {
const node= new ConditionNode(
json.logicalOperator,
json.object,
json.operator,
json.value,
parent
);
node.index=json.index;
return node;
}
}
// 条件式の管理クラス
export class ConditionTree {
root: GroupNode;
maxIndex:number;
constructor() {
this.maxIndex=0;
this.root = new GroupNode(LogicalOperator.AND, null);
}
// ノード追加
addNode(parent: GroupNode, node: INode): void {
this.maxIndex++;
node.index=this.maxIndex;
parent.children.push(node);
}
// ノード削除
removeNode(node: INode): void {
if (node.parent === null) {
throw new Error('ルートノード削除できません');
} else {
const parent = node.parent as GroupNode;
const index = parent.children.indexOf(node);
if (index > -1) {
parent.children.splice(index, 1);
}
}
}
findByIndex(index:number):INode|undefined{
return this.findChildren(this.root,index);
}
findChildren(parent:GroupNode,index:number):INode|undefined{
if(parent.index===index){
return parent;
}
return parent.children.findLast((node:INode)=>{
if(node.index===index){
return node;
}
if(node.type!==NodeType.Condition){
return this.findChildren(node as GroupNode,index);
}
});
}
//条件式を表示する
buildConditionString(node:INode){
if (node.type !== NodeType.Condition) {
let conditionString = '(';
const groupNode = node as GroupNode;
for (let i = 0; i < groupNode.children.length; i++) {
const childConditionString = this.buildConditionString(groupNode.children[i]);
if (childConditionString !== '') {
conditionString += childConditionString;
if (i < groupNode.children.length - 1) {
conditionString += ` ${groupNode.logicalOperator} `;
}
}
}
conditionString += ')';
return conditionString;
} else {
const condNode=node as ConditionNode;
if (condNode.object && condNode.operator && condNode.value!==null) {
let value=condNode.value;
if(typeof value ==='object' && ('label' in condNode.value)){
value =condNode.value.label;
}
return `${condNode.object.name} ${condNode.operator} '${value}'`;
} else {
return '';
}
}
}
/**
*
* @param node ノード移動
* @param direction
* @returns
*/
moveNode(node:INode, direction: 'up' | 'down'): void {
if (!node || !node.parent) {
return;
}
const parent = node.parent as GroupNode;
const currentIndex = parent.children.findIndex(child => child === node);
if (currentIndex === -1) {
return;
}
const newIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1;
// 範囲外のインデックスの処理
if (newIndex >= 0 && newIndex < parent.children.length) {
// ノードの位置を入れ替える
[parent.children[currentIndex], parent.children[newIndex]] = [parent.children[newIndex], parent.children[currentIndex]];
return; // 範囲外なら移動しない
}else if(newIndex<0 && parent.parent){
this.removeNode(node);
const parentIndex = (parent.parent as GroupNode).children.findIndex(child => child === parent);
(parent.parent as GroupNode).children.splice(parentIndex, 0, node);
node.parent = parent.parent;
}
}
getGroups(parent:GroupNode):number[]{
const groups:number[]=[];
groups.push(parent.index);
parent.children.forEach((node)=>{
if(node.type!==NodeType.Condition){
groups.push(...this.getGroups(node as GroupNode));
}
});
return groups;
}
// Jsonから復元
fromJson(jsonString: string): INode {
const json = JSON.parse(jsonString);
this.root = GroupNode.fromJSON(json) as GroupNode;
return this.root;
}
toJson():string{
return JSON.stringify(this.root, (key, value) => {
if (key === 'parent') {
return value ? value.type : null;
}
return value;
});
}
}