条件エディタ追加

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

@@ -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>