Compare commits

..

7 Commits

Author SHA1 Message Date
Mouriya
c3b560dbc9 条件付きコンポーネントは'source'でappidを受け取ることができる。 2024-05-25 04:15:09 +09:00
53aadfcaaa feat:データ集計処理作成 2024-05-24 09:20:19 +09:00
Mouriya
7fb3d08ccb 細部の問題の修正 2024-05-20 03:38:27 +09:00
Mouriya
cf4209333d データ処理書き込み完了 2024-05-17 23:32:14 +09:00
Mouriya
61ac281134 verNameのラッピング・オブジェクト 2024-05-17 14:41:15 +09:00
Mouriya
64d2cadd82 2つのデータ計算コンポーネントを追加する 2024-05-13 06:56:44 +09:00
Mouriya
371e2ee073 add vueuse dependencies 2024-05-13 06:56:03 +09:00
24 changed files with 819 additions and 735 deletions

View File

@@ -2,7 +2,6 @@
<html lang="ja-jp"> <html lang="ja-jp">
<head> <head>
<title><%= productName %></title> <title><%= productName %></title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="description" content="<%= productDescription %>"> <meta name="description" content="<%= productDescription %>">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">

View File

@@ -17,8 +17,9 @@
}, },
"dependencies": { "dependencies": {
"@quasar/extras": "^1.16.4", "@quasar/extras": "^1.16.4",
"@vueuse/core": "^10.9.0",
"axios": "^1.4.0", "axios": "^1.4.0",
"pinia": "^2.1.6", "pinia": "^2.1.7",
"quasar": "^2.6.0", "quasar": "^2.6.0",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"vue": "^3.0.0", "vue": "^3.0.0",

View File

@@ -4,6 +4,7 @@ import { Router } from 'vue-router';
import { App } from 'vue'; import { App } from 'vue';
export default boot(({ app, router }: { app: App<Element>; router: Router }) => { export default boot(({ app, router }: { app: App<Element>; router: Router }) => {
document.documentElement.lang="ja-JP";
app.config.errorHandler = (err: any, instance: any, info: string) => { app.config.errorHandler = (err: any, instance: any, info: string) => {
if (err.response && err.response.status === 401) { if (err.response && err.response.status === 401) {
// 認証エラーの場合再ログインする // 認証エラーの場合再ログインする

View File

@@ -1,5 +1,5 @@
<template> <template>
<show-dialog v-model:visible="showflg" name="条件エディタ" @close="closeDg" min-width="60vw" min-height="60vh"> <show-dialog v-model:visible="showflg" name="条件エディタ" @close="closeDg" min-width="50vw" min-height="60vh">
<template v-slot:toolbar> <template v-slot:toolbar>
<q-btn flat round dense icon="more_vert" > <q-btn flat round dense icon="more_vert" >
<q-menu auto-close anchor="bottom start"> <q-menu auto-close anchor="bottom start">

View File

@@ -6,21 +6,21 @@
{{ selectedObject.name }} {{ selectedObject.name }}
</q-chip> </q-chip>
<q-chip color="info" text-color="white" v-if="isSelected && selectedObject.objectType==='variable'" :dense="true" class="selected-obj"> <q-chip color="info" text-color="white" v-if="isSelected && selectedObject.objectType==='variable'" :dense="true" class="selected-obj">
{{ selectedObject.name }} {{ selectedObject.name.name }}
</q-chip> </q-chip>
</template> </template>
<template v-slot:append> <template v-slot:append>
<q-icon name="search" class="cursor-pointer" @click="showDg"/> <q-icon name="search" class="cursor-pointer" @click="showDg"/>
</template> </template>
</q-field> </q-field>
<show-dialog v-model:visible="show" name="条件設定項目一覧" @close="closeDg" width="600px"> <show-dialog v-model:visible="show" name="設定項目一覧" @close="closeDg" min-width="400px">
<template v-slot:toolbar> <template v-slot:toolbar>
<q-input dense debounce="200" v-model="filter" placeholder="検索" clearable> <q-input dense debounce="200" v-model="filter" placeholder="検索" clearable>
<template v-slot:before> <template v-slot:before>
<q-icon name="search" /> <q-icon name="search" />
</template> </template>
</q-input> </q-input>
</template> </template>
<condition-objects ref="appDg" name="フィールド" type="single" :filter="filter" :appId="store.appInfo?.appId" :vars="vars"></condition-objects> <condition-objects ref="appDg" name="フィールド" type="single" :filter="filter" :appId="store.appInfo?.appId" :vars="vars"></condition-objects>
</show-dialog> </show-dialog>
</template> </template>
@@ -88,9 +88,9 @@
.condition-object{ .condition-object{
min-width: 200px; min-width: 200px;
max-height: 40px; max-height: 40px;
padding: 2px; margin: 0 2px;
} }
.selected-obj{ .selected-obj{
margin: 0px; margin: 0 2px;
} }
</style> </style>

View File

@@ -66,7 +66,7 @@
</div> </div>
<!-- condition --> <!-- condition -->
<div @click.stop @keypress.stop v-else > <div @click.stop @keypress.stop v-else >
<div class="row no-wrap items-center"> <div class="row no-wrap items-center q-my-xs">
<ConditionObject v-bind="prop.node" v-model="prop.node.object" class="col-4"></ConditionObject> <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-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)" <q-input v-if="!prop.node.object || !('options' in prop.node.object)"
@@ -257,12 +257,12 @@ export default defineComponent( {
.condition-value{ .condition-value{
min-width: 200px; min-width: 200px;
max-height: 40px; max-height: 40px;
padding: 2px; margin: 0 2px;
} }
.operator{ .operator{
min-width: 150px; min-width: 150px;
max-height: 40px; max-height: 40px;
padding: 2px; margin: 0 2px;
text-align: center; text-align: center;
font-size: 12pt; font-size: 12pt;
} }

View File

@@ -19,7 +19,7 @@
<q-tab-panels v-model="tab" animated> <q-tab-panels v-model="tab" animated>
<q-tab-panel name="fields"> <q-tab-panel name="fields">
<field-list v-model="selected" type="single" :filter="filter" :appId="appId"></field-list> <field-list v-model="selected" type="single" :filter="filter" :appId="sourceApp ? sourceApp :appId " :fields="sourceFields"></field-list>
</q-tab-panel> </q-tab-panel>
<q-tab-panel name="vars" > <q-tab-panel name="vars" >
@@ -30,7 +30,7 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { ref, onMounted, reactive } from 'vue' import { ref, onMounted, reactive, inject } from 'vue'
import FieldList from './FieldList.vue'; import FieldList from './FieldList.vue';
import VariableList from './VariableList.vue'; import VariableList from './VariableList.vue';
@@ -48,10 +48,14 @@ export default {
filter:String filter:String
}, },
setup(props) { setup(props) {
const selected = ref([]);
console.log(selected);
return { return {
sourceFields : inject('sourceFields'),
sourceApp : inject('sourceApp'),
tab: ref('fields'), tab: ref('fields'),
selected: ref([]) selected
} }
}, },

View File

@@ -1,50 +1,50 @@
<template> <template>
<div class="q-pa-md"> <div class="q-pa-md">
<q-table flat bordered :loading="!isLoaded" row-key="name" :selection="type" <q-table flat bordered :loading="!isLoaded" row-key="name" :selection="type" :selected="modelValue"
:selected="modelValue" @update:selected="$emit('update:modelValue', $event)" :filter="filter" :columns="columns" :rows="rows" />
@update:selected="$emit('update:modelValue', $event)"
:filter="filter"
:columns="columns" :rows="rows" />
</div> </div>
</template> </template>
<script> <script lang="ts">
import { ref, onMounted, reactive } from 'vue' import { useAsyncState } from '@vueuse/core';
import { api } from 'boot/axios'; import { api } from 'boot/axios';
import { computed } from 'vue';
export default { export default {
name: 'FieldList', name: 'FieldList',
props: { props: {
fields: Array,
name: String, name: String,
type: String, type: String,
appId: Number, appId: Number,
modelValue:Array, modelValue: Array,
filter:String filter: String
}, },
emits:[ emits: [
'update:modelValue' 'update:modelValue'
], ],
setup(props) { setup(props) {
const isLoaded = ref(false); // const rows = ref([]);
// const isLoaded = ref(false);
const columns = [ const columns = [
{ name: 'name', required: true, label: 'フィールド名', align: 'left', field: row => row.name, sortable: true }, { name: 'name', required: true, label: 'フィールド名', align: 'left', field: 'name', sortable: true },
{ name: 'code', label: 'フィールドコード', align: 'left', field: 'code', sortable: true }, { name: 'code', label: 'フィールドコード', align: 'left', field: 'code', sortable: true },
{ name: 'type', label: 'フィールドタイプ', align: 'left', field: 'type', sortable: true } { name: 'type', label: 'フィールドタイプ', align: 'left', field: 'type', sortable: true }
] ]
const rows = reactive([]);
onMounted(async () => { const { state : rows, isReady: isLoaded, isLoading } = useAsyncState((args) => {
const res = await api.get('api/v1/appfields', { if (props.fields) {
params: { return props.fields.map(f => ({ name: f.label, objectType: 'field', ...f }));
app: props.appId } else {
} return api.get('api/v1/appfields', {
}); params: {
let fields = res.data.properties; app: props.appId
console.log(fields); }
Object.keys(fields).forEach((key) => { }).then(res => {
const fld = fields[key]; console.log(res);
rows.push({ name: fld.label, objectType: 'field', ...fld }); return Object.values(res.data.properties).map(f => ({ name: f.label, objectType: 'field', ...f }));
}); });
isLoaded.value = true; }
}); }, [{ name: '', objectType: '', type: '', code: '', label: '' }])
return { return {
columns, columns,

View File

@@ -1,7 +1,7 @@
<template> <template>
<!-- <div class="q-pa-md q-gutter-sm" > --> <!-- <div class="q-pa-md q-gutter-sm" > -->
<q-dialog :model-value="visible" persistent bordered > <q-dialog :model-value="visible" persistent bordered >
<q-card :style="cardStyle" style=" min-width: 40vw; max-width: 80vw; max-height: 95vh;"> <q-card style="min-width: 40vw; max-width: 80vw; max-height: 95vh;" :style="cardStyle">
<q-toolbar class="bg-grey-4"> <q-toolbar class="bg-grey-4">
<q-toolbar-title>{{ name }}</q-toolbar-title> <q-toolbar-title>{{ name }}</q-toolbar-title>
<q-space></q-space> <q-space></q-space>

View File

@@ -1,43 +1,59 @@
<template> <template>
<div class="q-pa-md"> <div class="q-pa-md">
<q-table flat bordered row-key="name" :selection="type" <q-table flat bordered row-key="id" :selection="type" :selected="modelValue"
:selected="modelValue" @update:selected="$emit('update:modelValue', $event)" :columns="columns" :rows="rows" />
@update:selected="$emit('update:modelValue', $event)"
:columns="columns" :rows="rows" />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { ref, reactive, PropType, compile } from 'vue'; import { PropType, reactive } from 'vue';
import {IActionNode,IActionVariable} from '../types/ActionTypes'; import { IActionVariable } from '../types/ActionTypes';
import { v4 as uuidv4 } from 'uuid';
export default { export default {
name: 'VariableList', name: 'VariableList',
props: { props: {
name: String, name: String,
type: String, type: String,
vars:{ vars: {
type:Array as PropType<IActionVariable[]>, type: Array as PropType<IActionVariable[]>,
reqired:true, reqired: true,
default:()=>[] default: () => []
}, },
modelValue:Array modelValue: Array
}, },
emits:[ emits: [
'update:modelValue' 'update:modelValue'
], ],
setup(props) { setup(props) {
const columns= [ const variableName = (field) => {
{ name: 'actionName', label: 'アクション名',align: 'left',field: 'actionName',sortable: true}, const name = field.name;
{ name: 'displayName', label: '変数表示名', align: 'left',field: 'displayName', sortable: true }, return name.name;
{ name: 'name', label: '変数名', align: 'left',field: 'name',required: true, sortable: true } }
const columns = [
{ name: 'actionName', label: 'アクション名', align: 'left', field: 'actionName', sortable: true },
{ name: 'displayName', label: '変数表示名', align: 'left', field: 'displayName', sortable: true },
{ name: 'name', label: '変数名', align: 'left', field: variableName, required: true, sortable: true }
]; ];
const rows= props.vars.map((v)=>{
return {objectType:'variable',...v}; const rows = props.vars.flatMap((v) => {
if (v.name.vars && v.name.vars.length > 0) {
return v.name.vars
.filter(o => o.vName && o.logicalOperator && o.field)
.map(o => ({
id: uuidv4(),
objectType: 'variable',
name: { name: `${v.name.name}.${o.vName}` },
actionName: v.name.actionName,
displayName: v.name.displayName
}));
} else {
return [{ objectType: 'variable', ...v }];
}
}); });
return { return {
columns, columns,
rows:reactive(rows) rows: reactive(rows)
} }
} }
} }

View File

@@ -1,55 +1,48 @@
<template> <template>
<!-- <div class="q-pa-md q-gutter-sm"> --> <!-- <div class="q-pa-md q-gutter-sm"> -->
<q-tree :nodes="store.eventTree.screens" node-key="eventId" children-key="events" no-connectors <q-tree
v-model:expanded="store.expandedScreen" :dense="true" :ref="tree"> :nodes="store.eventTree.screens"
<template v-slot:header-EVENT="prop"> node-key="eventId"
<div :ref="prop.node.eventId" class="row col items-center no-wrap event-node"> children-key="events"
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px" no-connectors
class="q-mr-sm"> v-model:expanded="store.expandedScreen"
</q-icon> :dense="true"
<div class="no-wrap" @click="onSelected(prop.node)" :ref="tree"
:class="selectedEvent && prop.node.eventId === selectedEvent.eventId ? 'selected-node' : ''">{{ >
prop.node.label }}</div> <template v-slot:header-EVENT="prop">
<q-space></q-space> <div class="row col items-start no-wrap event-node" @click="onSelected(prop.node)">
<!-- <q-icon v-if="prop.node.hasFlow" name="delete" color="negative" size="16px" class="q-mr-sm"></q-icon> --> <q-icon v-if="prop.node.eventId"
</div> name="play_circle"
</template> :color="prop.node.hasFlow?'green':'grey'"
<template v-slot:header-CHANGE="prop"> size="16px" class="q-mr-sm">
<div class="row col items-center no-wrap event-node"> </q-icon>
<div class="no-wrap">{{ prop.node.label }}</div> <div class="no-wrap" :class="selectedEvent && prop.node.eventId===selectedEvent.eventId?'selected-node':''">{{ prop.node.label }}</div>
<q-space></q-space> <q-space></q-space>
<q-icon name="add_circle" color="primary" size="16px" class="q-mr-sm" <!-- <q-icon v-if="prop.node.hasFlow" name="delete" color="negative" size="16px" class="q-mr-sm"></q-icon> -->
@click="addChangeEvent(prop.node)"></q-icon>
</div>
</template>
<template v-slot:header-DELETABLE="prop">
<div class="row col items-center event-node">
<div class="row col items-center" @click="onSelected(prop.node)">
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px"
class="q-mr-sm">
</q-icon>
<div>{{ prop.node.label }}</div>
</div> </div>
<div> </template>
<q-btn class="q-mr-sm delete-btn" flat fab-mini icon="delete_forever" padding="none" color="negative" <template v-slot:header-CHANGE="prop" >
@click="deleteEvent(prop.node)"></q-btn> <div class="row col items-start no-wrap event-node" >
<div class="no-wrap">{{ prop.node.label }}</div>
<q-space></q-space>
<q-icon name="add_circle" color="primary" size="16px" class="q-mr-sm" @click="addChangeEvent(prop.node)"></q-icon>
</div> </div>
</div> </template>
</template> </q-tree>
</q-tree> <show-dialog v-model:visible="showDialog" name="フィールド選択" @close="closeDg" widht="400px">
<show-dialog v-model:visible="showDialog" name="フィールド選択" @close="closeDg"> <field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select>
<field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select> </show-dialog>
</show-dialog>
</template> </template>
<script lang="ts"> <script lang="ts">
import { QTree } from 'quasar'; import { defineComponent, computed, ref } from 'vue';
import { ActionFlow, RootAction } from 'src/types/ActionTypes'; import { IKintoneEvent ,IKintoneEventGroup, IKintoneEventNode, kintoneEvent} from '../../types/KintoneEvents';
import { storeToRefs } from 'pinia';
import { useFlowEditorStore } from 'stores/flowEditor'; import { useFlowEditorStore } from 'stores/flowEditor';
import { defineComponent, ref } from 'vue'; import { ActionFlow, ActionNode, RootAction } from 'src/types/ActionTypes';
import { IKintoneEvent, IKintoneEventGroup, IKintoneEventNode } from '../../types/KintoneEvents';
import FieldSelect from '../FieldSelect.vue';
import ShowDialog from '../ShowDialog.vue'; import ShowDialog from '../ShowDialog.vue';
import FieldSelect from '../FieldSelect.vue';
import { QTree } from 'quasar';
export default defineComponent({ export default defineComponent({
name: 'EventTree', name: 'EventTree',
components: { components: {
@@ -65,75 +58,62 @@ export default defineComponent({
// const selectedFlow = store.currentFlow; // const selectedFlow = store.currentFlow;
// const expanded=ref(); // const expanded=ref();
const selectedEvent = ref<IKintoneEvent | null>(null); const selectedEvent = ref<IKintoneEvent|null>(null);
const selectedChangeEvent = ref<IKintoneEventGroup | null>(null); const selectedChangeEvent=ref<IKintoneEventGroup|null>(null);
const isFieldChange = (node: IKintoneEventNode) => { const isFieldChange = (node:IKintoneEventNode)=>{
return node.header == 'EVENT' && node.eventId.indexOf(".change.") > -1; return node.header=='EVENT' && node.eventId.indexOf(".change.")>-1;
} }
//フィールド値変更イベント追加 //フィールド値変更イベント追加
const closeDg = (val: string) => { const closeDg = (val:string) => {
if (val == 'OK') { if (val == 'OK') {
if (!selectedChangeEvent.value) { return; } if(!selectedChangeEvent.value){return;}
const field = appDg.value.selected[0]; const field = appDg.value.selected[0];
const eventid = `${selectedChangeEvent.value.eventId}.${field.code}`; const eventid = `${selectedChangeEvent.value.eventId}.${field.code}`;
if (store.eventTree.findEventById(eventid)) { if(store.eventTree.findEventById(eventid)){
return; return;
} }
selectedChangeEvent.value?.events.push({ selectedChangeEvent.value?.events.push(
eventId: eventid, new kintoneEvent(
label: field.name, field.label,
parentId: selectedChangeEvent.value.eventId, eventid,
header: 'DELETABLE' selectedChangeEvent.value.eventId)
}); );
tree.value?.expanded?.push(selectedChangeEvent.value.eventId); tree.value?.expanded?.push(selectedChangeEvent.value.eventId);
tree.value?.expandAll(); tree.value?.expandAll();
} }
}; };
const addChangeEvent = (node: IKintoneEventGroup) => { const addChangeEvent=(node:IKintoneEventGroup)=>{
if (store.appInfo === undefined) { if(store.appInfo===undefined){
return; return;
} }
selectedChangeEvent.value = node; selectedChangeEvent.value=node;
showDialog.value = true; showDialog.value=true;
} }
const onSelected=(node:IKintoneEvent)=>{
const deleteEvent = (node: IKintoneEvent) => { if(!node.eventId){
if (!node.eventId) { return;
return; }
} selectedEvent.value=node;
store.deleteStoreEventAndFlow(node); if(store.appInfo===undefined){
store.selectFlow(undefined) return;
console.log(store.selectedFlow); }
const screen = store.eventTree.findEventById(node.parentId);
} let flow =store.findFlowByEventId(node.eventId);
let screenName=screen!==null?screen.label:"";
const onSelected = (node: IKintoneEvent) => { let nodeLabel = node.label;
if (!node.eventId) { // if(isFieldChange(node)){
return; // screenName=nodeLabel;
} // nodeLabel=`${node.label}の値を変更したとき`;
selectedEvent.value = node; // }
if (store.appInfo === undefined) { if(flow!==undefined && flow!==null ){
return; store.selectFlow(flow);
} }else{
const screen = store.eventTree.findEventById(node.parentId); const root = new RootAction(node.eventId,screenName,nodeLabel)
const flow =new ActionFlow(root);
let flow = store.findFlowByEventId(node.eventId); store.flows?.push(flow);
let screenName = screen !== null ? screen.label : ""; store.selectFlow(flow);
let nodeLabel = node.label; selectedEvent.value.flowData=flow;
// if(isFieldChange(node)){ }
// screenName=nodeLabel;
// nodeLabel=`${node.label}の値を変更したとき`;
// }
if (flow !== undefined && flow !== null) {
store.selectFlow(flow);
} else {
const root = new RootAction(node.eventId, screenName, nodeLabel)
const flow = new ActionFlow(root);
store.flows?.push(flow);
store.selectFlow(flow);
selectedEvent.value.flowData = flow;
}
}; };
return { return {
// eventTree, // eventTree,
@@ -145,7 +125,6 @@ export default defineComponent({
onSelected, onSelected,
selectedEvent, selectedEvent,
addChangeEvent, addChangeEvent,
deleteEvent,
closeDg, closeDg,
store store
} }
@@ -153,25 +132,20 @@ export default defineComponent({
}); });
</script> </script>
<style lang="scss"> <style lang="scss">
.nowrap { .nowrap{
flex-wrap: nowarp; flex-wrap:nowarp;
text-wrap: nowarp; text-wrap:nowarp;
} }
.event-node{
.event-node { cursor:pointer;
cursor: pointer;
} }
.selected-node{
.selected-node {
color: $primary; color: $primary;
font-weight: bolder; font-weight: bolder;
} }
.event-node:hover{
.event-node:hover {
background-color: $light-blue-1; background-color: $light-blue-1;
} }
.delete-btn {
margin-right: 5px;
}
</style> </style>

View File

@@ -205,7 +205,7 @@ export default defineComponent({
*/ */
const varName =(node:IActionNode)=>{ const varName =(node:IActionNode)=>{
const prop = node.actionProps.find((prop) => prop.props.name === "verName"); const prop = node.actionProps.find((prop) => prop.props.name === "verName");
return prop?.props.modelValue; return prop?.props.modelValue.name;
}; };
const copyFlow=()=>{ const copyFlow=()=>{
context.emit('copyFlow', props.actionNode); context.emit('copyFlow', props.actionNode);

View File

@@ -23,9 +23,9 @@
<q-virtual-scroll style="max-height: 160px;" :items="selectedField.fields" separator v-slot="{ item, index }"> <q-virtual-scroll style="max-height: 160px;" :items="selectedField.fields" separator v-slot="{ item, index }">
<q-item :key="index" dense clickable > <q-item :key="index" dense clickable >
<q-item-section> <q-item-section>
<q-item-label> <q-item-label>
{{ item.label }} {{ item.label }}
</q-item-label> </q-item-label>
</q-item-section> </q-item-section>
<q-item-section side> <q-item-section side>
<q-btn round flat size="sm" icon="clear" @click="removeField(index)" /> <q-btn round flat size="sm" icon="clear" @click="removeField(index)" />
@@ -128,7 +128,7 @@ interface IAppFields{
export default defineComponent({ export default defineComponent({
inheritAttrs:false, inheritAttrs:false,
name: 'FieldInput', name: 'AppFieldSelect',
components: { components: {
ShowDialog, ShowDialog,
FieldSelect, FieldSelect,

View File

@@ -1,12 +1,12 @@
<template> <template>
<div v-bind="$attrs"> <div v-bind="$attrs">
<q-field v-model="tree" :label="displayName" labelColor="primary" stack-label > <q-field v-model="tree" :label="displayName" labelColor="primary" stack-label>
<template v-slot:control > <template v-slot:control>
<q-card flat class="full-width"> <q-card flat class="full-width">
<q-card-actions vertical> <q-card-actions vertical>
<q-btn color="grey-3" text-color="black" @click="showDg()">クリックで設定{{ isSetted?'設定済み':'未設定' }}</q-btn> <q-btn color="grey-3" text-color="black" @click="showDg()">クリックで設定{{ isSetted ? '設定済み' : '未設定' }}</q-btn>
</q-card-actions> </q-card-actions>
<q-card-section class="text-caption" > <q-card-section class="text-caption">
<div v-if="!isSetted">{{ placeholder }}</div> <div v-if="!isSetted">{{ placeholder }}</div>
<div v-else>{{ conditionString }}</div> <div v-else>{{ conditionString }}</div>
</q-card-section> </q-card-section>
@@ -17,82 +17,118 @@
</div> </div>
</template> </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',
inheritAttrs:false,
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({},Operator.Equal,'',tree.root);
tree.addNode(tree.root,newNode);
}
const isSetted=ref(props.modelValue && props.modelValue!==''); <script lang="ts">
import { ConditionNode, ConditionTree, Operator } from 'app/src/types/Conditions';
import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue';
import ConditionEditor from '../ConditionEditor/ConditionEditor.vue';
const conditionString = computed(()=>{ type Props = {
return tree.buildConditionString(tree.root); props?: {
}); name: string;
modelValue?: {
const showDg = () => { fields: {
show.value = true; type: string;
}; label: string;
code: string;
const onClosed = (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,
onClosed,
tree,
conditionString
};
} }
}); }
</script> };
export default defineComponent({
name: 'FieldInput',
inheritAttrs: false,
components: {
ConditionEditor
},
props: {
context: {
type: Array<Props>,
default: '',
},
displayName: {
type: String,
default: '',
},
name: {
type: String,
default: '',
},
placeholder: {
type: String,
default: '',
},
hint: {
type: String,
default: '',
},
modelValue: {
type: String,
default: null
},
sourceType: {
type: String,
default: 'field'
}
},
setup(props, { emit }) {
const source = props.context.find(element => element?.props?.name === 'sources')
if (source) {
if(props.sourceType === 'field'){
provide('sourceFields', computed( () => source.props?.modelValue?.fields ?? []));
} else if(props.sourceType === 'app'){
console.log('sourceApp', source.props?.modelValue);
provide('sourceApp', computed( () => source.props?.modelValue?.app?.id));
}
}
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({}, 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 onClosed = (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,
onClosed,
tree,
conditionString
};
}
});
</script>

View File

@@ -0,0 +1,229 @@
<template>
<div>
<q-field :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="() => { dgIsShow = true }">クリックで設定</q-btn>
</q-card-actions>
<q-card-section class="text-caption">
<div v-if="processingObjectsInputDisplay && processingObjectsInputDisplay.length>0">
<div v-for="(item) in processingObjectsInputDisplay" :key="item">{{ item }}</div>
</div>
<div v-else>{{ placeholder }}</div>
</q-card-section>
</q-card>
</template>
</q-field>
<show-dialog v-model:visible="dgIsShow" name="集計処理" @close="closeDg" min-width="50vw" min-height="60vh">
<div class="q-mx-md q-mb-md">
<q-input v-model="processingProps.name" type="text" label-color="primary" label="集計結果の変数名"
placeholder="集計結果を格納する変数名を入力してください" stack-label />
</div>
<div class="q-mx-md">
<div class="row q-col-gutter-x-xs flex-center">
<div class="col-5">
<div class="q-mx-xs">データソース</div>
</div>
<div class="col-2">
<div class="q-mx-xs">集計計算</div>
</div>
<div class="col-4">
<div class="q-mx-xs">集計結果変数名</div>
</div>
<div class="col-1"><q-btn flat round dense icon="add" size="sm" @click="addProcessingObject" />
</div>
</div>
<div class="q-my-sm" v-for="(item, index) in processingObjects" :key="item.id">
<div class="row q-col-gutter-x-xs flex-center">
<div class="col-5">
<ConditionObject v-model="item.field" />
</div>
<div class="col-2">
<q-select v-model="item.logicalOperator" :options="logicalOperators" outlined dense></q-select>
</div>
<div class="col-4">
<q-input v-model="item.vName" type="text" outlined dense />
</div>
<div class="col-1">
<q-btn flat round dense icon="delete" size="sm" @click="() => deleteProcessingObject(index)" />
</div>
</div>
</div>
</div>
</show-dialog>
</div>
</template>
<script lang="ts">
import { v4 as uuidv4 } from 'uuid';
import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue';
import ConditionObject from '../ConditionEditor/ConditionObject.vue';
import ShowDialog from '../ShowDialog.vue';
type Props = {
props?: {
name: string;
modelValue?: {
fields: {
type: string;
label: string;
code: string;
}[]
} | string
}
};
type ProcessingObjectType = {
field?: {
name: string | {
name: string;
};
objectType: string;
type: string;
code: string;
label: string;
noLabel: boolean;
};
logicalOperator?: string;
vName?: string;
id: string;
}
type ValueType = {
name: string;
actionName: string,
displayName: string,
vars: ProcessingObjectType[];
}
export default defineComponent({
name: 'DataProcessing',
inheritAttrs: false,
components: {
ShowDialog,
ConditionObject,
},
props: {
context: {
type: Array<Props>,
default: '',
},
displayName: {
type: String,
default: '',
},
name: {
type: String,
default: '',
},
modelValue: {
type: Object as () => ValueType,
},
placeholder: {
type: String,
default: '',
},
},
setup(props, { emit }) {
const source = props.context.find(element => element?.props?.name === 'sources')
if (source) {
provide('sourceFields', computed(() => {
const modelValue = source.props?.modelValue;
if (modelValue && typeof modelValue !== 'string') {
return modelValue.fields;
}
return null;
}));
}
const actionName = props.context.find(element => element?.props?.name === 'displayName')
const processingProps: ValueType = props.modelValue && props.modelValue.vars
? props.modelValue
: reactive({
name: '',
actionName: actionName?.props?.modelValue as string,
displayName: '結果(戻り値)',
vars: [{ id: uuidv4() }]
});
const closeDg = () => {
emit('update:modelValue', processingProps
);
}
const processingObjects = processingProps.vars;
const deleteProcessingObject = (index: number) => processingObjects.length === 1
? processingObjects.splice(0, processingObjects.length, { id: uuidv4() })
: processingObjects.splice(index, 1);
const processingObjectsInputDisplay = computed(() =>
processingObjects ?
processingObjects
.filter(item => item.field && item.logicalOperator && item.vName)
.map(item => {
const name = typeof item.field?.name === 'string'
? item.field.name
: item.field?.name.name;
return item.logicalOperator.operator!==''?
`${processingProps.name}.${item.vName} = ${item.logicalOperator.operator}(${name})`
:`${processingProps.name}.${item.vName} = ${name}`
})
: []
);
//集計処理方法
const logicalOperators = ref([
{
"operator": "",
"label": "なし"
},
{
"operator": "SUM",
"label": "合計"
},
{
"operator": "AVG",
"label": "平均"
},
{
"operator": "MAX",
"label": "最大値"
},
{
"operator": "MIN",
"label": "最小値"
},
{
"operator": "COUNT",
"label": "カウント"
},
{
"operator": "FIRST",
"label": "最初の値"
}
]);
watchEffect(() => {
emit('update:modelValue', processingProps);
});
return {
uuidv4,
dgIsShow: ref(false),
closeDg,
processingObjects,
processingProps,
addProcessingObject: () => processingObjects.push({ id: uuidv4() }),
deleteProcessingObject,
logicalOperators,
processingObjectsInputDisplay,
};
},
});
</script>
<style lang="scss"></style>

View File

@@ -1,10 +1,7 @@
<template> <template>
<div v-bind="$attrs"> <div v-bind="$attrs">
<q-input :label="displayName" v-model="inputValue" label-color="primary" <q-input :label="displayName" v-model="inputValue" label-color="primary" :placeholder="placeholder" stack-label
:placeholder="placeholder" stack-label :rules="rulesExp" :maxlength="maxLength">
:rules="rulesExp"
:maxlength="maxLength"
>
<template v-slot:append v-if="hint !== ''"> <template v-slot:append v-if="hint !== ''">
<q-icon name="help" size="22px" color="blue-8"> <q-icon name="help" size="22px" color="blue-8">
<q-tooltip class="bg-yellow-2 text-black shadow-4" anchor="bottom right"> <q-tooltip class="bg-yellow-2 text-black shadow-4" anchor="bottom right">
@@ -18,7 +15,7 @@
<script lang="ts"> <script lang="ts">
import { kMaxLength } from 'buffer'; import { kMaxLength } from 'buffer';
import { defineComponent, ref, watchEffect } from 'vue'; import { defineComponent, ref, watchEffect, computed } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'InputText', name: 'InputText',
@@ -40,27 +37,50 @@ export default defineComponent({
type: String, type: String,
default: '', default: '',
}, },
maxLength:{ maxLength: {
type: Number, type: Number,
default:undefined default: undefined
}, },
//例:[val=>!!val ||'入力してください'] //例:[val=>!!val ||'入力してください']
rules:{ rules: {
type:String, type: String,
default:undefined default: undefined
}, },
modelValue: { modelValue: {
type: String, // type: Any,
default: '', default: '',
}, },
}, },
setup(props, { emit }) { setup(props, { emit }) {
const inputValue = ref(props.modelValue); const inputValue = computed({
const rulesExp = props.rules===undefined?null : eval(props.rules); get: () => {
watchEffect(() => { if (props.modelValue !== null && typeof props.modelValue === 'object' && 'name' in props.modelValue) {
emit('update:modelValue', inputValue.value); return props.modelValue.name;
} else {
return props.modelValue;
}
},
set: (val) => {
if (props.name === 'verName') {
// return props.modelValue.name;
emit('update:modelValue', { name: val });
} else {
emit('update:modelValue', val);
}
},
}); });
// const inputValue = ref(props.modelValue);
const rulesExp = props.rules === undefined ? null : eval(props.rules);
// const finalValue = computed(() => {
// return props.name !== 'verName' ? inputValue.value : {
// name: inputValue.value,
// };
// });
// watchEffect(() => {
// emit('update:modelValue', finalValue);
// });
return { return {
inputValue, inputValue,

View File

@@ -1,7 +1,7 @@
<template> <template>
<div> <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" :connectProps="connectProps(item.props)" v-model="item.props.modelValue"></component> <component :is="item.component" v-bind="item.props" :context="properties" :connectProps="connectProps(item.props)" v-model="item.props.modelValue"></component>
</div> </div>
</div> </div>
</template> </template>
@@ -21,6 +21,7 @@ import ConditionInput from '../right/ConditionInput.vue';
import EventSetter from '../right/EventSetter.vue'; import EventSetter from '../right/EventSetter.vue';
import ColorPicker from './ColorPicker.vue'; import ColorPicker from './ColorPicker.vue';
import NumInput from './NumInput.vue'; import NumInput from './NumInput.vue';
import DataProcessing from './DataProcessing.vue';
import { IActionNode,IActionProperty,IProp } from 'src/types/ActionTypes'; import { IActionNode,IActionProperty,IProp } from 'src/types/ActionTypes';
export default defineComponent({ export default defineComponent({
@@ -35,7 +36,8 @@ export default defineComponent({
ConditionInput, ConditionInput,
EventSetter, EventSetter,
ColorPicker, ColorPicker,
NumInput NumInput,
DataProcessing
}, },
props: { props: {
nodeProps: { nodeProps: {
@@ -50,7 +52,7 @@ export default defineComponent({
setup(props, context) { setup(props, context) {
const properties=ref(props.nodeProps); const properties=ref(props.nodeProps);
const connectProps=(props:IProp)=>{ const connectProps=(props:IProp)=>{
const connProps:any={}; const connProps:any={context:properties};
if(props && "connectProps" in props && props.connectProps!=undefined){ if(props && "connectProps" in props && props.connectProps!=undefined){
for(let connProp of props.connectProps){ for(let connProp of props.connectProps){
let targetProp = properties.value.find((prop)=>prop.props.name===connProp.propName); let targetProp = properties.value.find((prop)=>prop.props.name===connProp.propName);

View File

@@ -1,11 +1,12 @@
<template> <template>
<div v-bind="$attrs"> <div v-bind="$attrs">
<q-select v-model="selectedValue" :label="displayName" :options="options"/> <q-select v-model="selectedValue" :use-chips="multiple" :label="displayName" label-color="primary" :options="options" stack-label
:multiple="multiple"/>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent,ref,watchEffect } from 'vue'; import { defineComponent,ref,watchEffect,computed } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'SelectBox', name: 'SelectBox',
@@ -23,20 +24,27 @@ export default defineComponent({
type: Array, type: Array,
required: true, required: true,
}, },
selectType:{
type:String,
default:'',
},
modelValue: { modelValue: {
type: String, type: Object,
default: '', default: null,
}, },
}, },
setup(props, { emit }) { setup(props, { emit }) {
const selectedValue = ref(props.modelValue); const selectedValue = ref(props.modelValue);
const multiple = computed(()=>{
return props.selectType==='multiple'
});
watchEffect(() => { watchEffect(() => {
emit('update:modelValue', selectedValue.value); emit('update:modelValue', selectedValue.value);
}); });
return { return {
selectedValue selectedValue,
multiple
}; };
}, },
}); });

View File

@@ -1,49 +1,44 @@
import { api } from 'boot/axios'; import { api } from 'boot/axios';
import { ActionFlow } from 'src/types/ActionTypes'; import { ActionFlow } from 'src/types/ActionTypes';
export class FlowCtrl { export class FlowCtrl
async getFlows(appId: string): Promise<ActionFlow[]> { {
const flows: ActionFlow[] = [];
try {
const result = await api.get(`api/flows/${appId}`);
//console.info(result.data);
if (!result.data || !Array.isArray(result.data)) {
return [];
}
for (const flow of result.data) { async getFlows(appId:string):Promise<ActionFlow[]>
flows.push(ActionFlow.fromJSON(flow.content)); {
const flows:ActionFlow[]=[];
try{
const result = await api.get(`api/flows/${appId}`);
//console.info(result.data);
if(!result.data || !Array.isArray(result.data)){
return [];
}
for(const flow of result.data){
flows.push(ActionFlow.fromJSON(flow.content));
}
return flows;
}catch(error){
console.error(error);
return flows;
} }
return flows;
} catch (error) {
console.error(error);
return flows;
}
} }
async SaveFlow(jsonData: any): Promise<boolean> { async SaveFlow(jsonData:any):Promise<boolean>
const result = await api.post('api/flow', jsonData); {
console.info(result.data); const result = await api.post('api/flow',jsonData);
return true; console.info(result.data)
return true;
} }
/** /**
* フローを更新する * フローを更新する
* @param jsonData * @param jsonData
* @returns * @returns
*/ */
async UpdateFlow(jsonData: any): Promise<boolean> { async UpdateFlow(jsonData:any):Promise<boolean>
const result = await api.put('api/flow/' + jsonData.flowid, jsonData); {
console.info(result.data); const result = await api.put('api/flow/' + jsonData.flowid,jsonData);
return true; console.info(result.data)
}
/**
* フローを消去する
* @param flowId
* @returns
*/
async DeleteFlow(flowId: string): Promise<boolean> {
const result = await api.delete('api/flow/' + flowId);
console.info(result.data);
return true; return true;
} }
/** /**
@@ -51,9 +46,12 @@ export class FlowCtrl {
* @param appid * @param appid
* @returns * @returns
*/ */
async depoly(appid: string): Promise<boolean> { async depoly(appid:string):Promise<boolean>
{
const result = await api.post(`api/v1/createjstokintone?app=${appid}`); const result = await api.post(`api/v1/createjstokintone?app=${appid}`);
console.info(result.data); console.info(result.data);
return true; return true;
} }
} }

View File

@@ -20,12 +20,13 @@
</div> </div>
</q-drawer> </q-drawer>
</div> </div>
<q-btn flat dense round :icon="drawerLeft ? 'keyboard_double_arrow_left' : 'keyboard_double_arrow_right'" <q-btn flat dense round
:style="[drawerLeft ? { 'left': '300px' } : { 'left': '0px' }]" @click="drawerLeft = !drawerLeft" :icon="drawerLeft?'keyboard_double_arrow_left':'keyboard_double_arrow_right'"
class="expand" /> :style="[drawerLeft?{'left':'300px'}:{'left':'0px'}]"
<div class="q-pa-md q-gutter-sm" :style="{ minWidth: minPanelWidth }"> @click="drawerLeft=!drawerLeft" class="expand" />
<div class="flowchart" v-if="store.currentFlow" :style="[drawerLeft ? { paddingLeft: '300px' } : {}]"> <div class="q-pa-md q-gutter-sm" :style="{minWidth: minPanelWidth}">
<node-item v-if="rootNode !== undefined" :key="rootNode.id" :isSelected="rootNode === store.activeNode" <div class="flowchart" v-if="store.currentFlow" :style="[drawerLeft?{paddingLeft:'300px'}:{}]">
<node-item v-if="rootNode!==undefined" :key="rootNode.id" :isSelected="rootNode === store.activeNode"
:actionNode="rootNode" @addNode="addNode" @nodeSelected="onNodeSelected" @nodeEdit="onNodeEdit" :actionNode="rootNode" @addNode="addNode" @nodeSelected="onNodeSelected" @nodeEdit="onNodeEdit"
@deleteNode="onDeleteNode" @deleteAllNextNodes="onDeleteAllNextNodes" @copyFlow="onCopyFlow"></node-item> @deleteNode="onDeleteNode" @deleteAllNextNodes="onDeleteAllNextNodes" @copyFlow="onCopyFlow"></node-item>
</div> </div>
@@ -34,13 +35,13 @@
</q-layout> </q-layout>
<ShowDialog v-model:visible="showAddAction" name="アクション" @close="closeDg" min-width="500px" min-height="500px"> <ShowDialog v-model:visible="showAddAction" name="アクション" @close="closeDg" min-width="500px" min-height="500px">
<template v-slot:toolbar> <template v-slot:toolbar>
<q-input dense debounce="200" v-model="filter" placeholder="検索" clearable> <q-input dense debounce="200" v-model="filter" placeholder="検索" clearable>
<template v-slot:before> <template v-slot:before>
<q-icon name="search" /> <q-icon name="search" />
</template> </template>
</q-input> </q-input>
</template> </template>
<action-select ref="appDg" name="model" :filter="filter" type="single"></action-select> <action-select ref="appDg" name="model" :filter="filter" type="single"></action-select>
</ShowDialog> </ShowDialog>
</q-page> </q-page>
@@ -76,20 +77,20 @@ const prevNodeIfo = ref({
// const refFlow = ref<ActionFlow|null>(null); // const refFlow = ref<ActionFlow|null>(null);
const showAddAction = ref(false); const showAddAction = ref(false);
const drawerRight = ref(false); const drawerRight = ref(false);
const filter = ref(""); const filter=ref("");
const model = ref(""); const model = ref("");
const addActionNode = (action: IActionNode) => { const addActionNode = (action: IActionNode) => {
// refFlow.value?.actionNodes.push(action); // refFlow.value?.actionNodes.push(action);
store.currentFlow?.actionNodes.push(action); store.currentFlow?.actionNodes.push(action);
} }
const rootNode = computed(() => { const rootNode = computed(()=>{
return store.currentFlow?.getRoot(); return store.currentFlow?.getRoot();
}); });
const minPanelWidth = computed(() => { const minPanelWidth=computed(()=>{
const root = store.currentFlow?.getRoot(); const root = store.currentFlow?.getRoot();
if (store.currentFlow && root) { if(store.currentFlow && root){
return store.currentFlow?.getColumns(root) * 300 + 'px'; return store.currentFlow?.getColumns(root) * 300 + 'px';
} else { }else{
return "300px"; return "300px";
} }
}); });
@@ -148,7 +149,7 @@ const closeDg = (val: any) => {
*/ */
const onCopyFlow = () => { const onCopyFlow = () => {
if (navigator.clipboard) { if (navigator.clipboard) {
const jsonData = JSON.stringify(store.currentFlow); const jsonData =JSON.stringify(store.currentFlow) ;
navigator.clipboard.writeText(jsonData).then(() => { navigator.clipboard.writeText(jsonData).then(() => {
console.log('Text successfully copied to clipboard'); console.log('Text successfully copied to clipboard');
}, },
@@ -194,22 +195,7 @@ const onDeploy = async () => {
const onSaveFlow = async () => { const onSaveFlow = async () => {
const targetFlow = store.selectedFlow; const targetFlow = store.selectedFlow;
const deleteFlows = () => {
$q.notify({
type: 'positive',
caption: "通知",
message: `削除 ${store.deleteFlowIds.length} イベント`
});
store.deletebackendFlow();
}
if (targetFlow === undefined) { if (targetFlow === undefined) {
if (store.deleteFlowIds.length > 0) {
deleteFlows();
return;
}
$q.notify({ $q.notify({
type: 'negative', type: 'negative',
caption: "エラー", caption: "エラー",
@@ -218,9 +204,6 @@ const onSaveFlow = async () => {
return; return;
} }
try { try {
if (store.deleteFlowIds.length > 0) {
deleteFlows();
}
saveLoading.value = true; saveLoading.value = true;
await store.saveFlow(targetFlow); await store.saveFlow(targetFlow);
saveLoading.value = false; saveLoading.value = false;
@@ -282,8 +265,7 @@ onMounted(() => {
top: 50px; top: 50px;
z-index: 999; z-index: 999;
} }
.expand{
.expand {
position: fixed; position: fixed;
left: 0px; left: 0px;
top: 50%; top: 50%;

View File

@@ -1,164 +1,118 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { AppInfo, IActionFlow, IActionNode } from 'src/types/ActionTypes'; import { AppInfo ,IActionFlow, IActionNode} from 'src/types/ActionTypes';
import { IKintoneEvent, KintoneEventManager } from 'src/types/KintoneEvents'; import { IKintoneEvent,KintoneEventManager } from 'src/types/KintoneEvents';
import { FlowCtrl } from '../control/flowctrl'; import {FlowCtrl } from '../control/flowctrl';
export interface FlowEditorState { export interface FlowEditorState{
flowNames1: string; flowNames1:string;
appInfo?: AppInfo; appInfo?:AppInfo;
flows?: IActionFlow[]; flows?:IActionFlow[];
selectedFlow?: IActionFlow | undefined; selectedFlow?:IActionFlow|undefined;
activeNode: IActionNode | undefined; activeNode:IActionNode|undefined;
eventTree: KintoneEventManager; eventTree:KintoneEventManager;
selectedEvent: IKintoneEvent | undefined; selectedEvent:IKintoneEvent|undefined;
expandedScreen: any[]; expandedScreen:any[];
deleteFlowIds: string[];
} }
const flowCtrl = new FlowCtrl(); const flowCtrl=new FlowCtrl();
const eventTree = new KintoneEventManager(); const eventTree = new KintoneEventManager();
export const useFlowEditorStore = defineStore('flowEditor', { export const useFlowEditorStore = defineStore("flowEditor",{
state: (): FlowEditorState => ({ state: ():FlowEditorState => ({
flowNames1: '', flowNames1: '',
appInfo: undefined, appInfo:undefined,
flows: [], flows:[],
selectedFlow: undefined, selectedFlow:undefined,
activeNode: undefined, activeNode:undefined,
eventTree: eventTree, eventTree:eventTree,
selectedEvent: undefined, selectedEvent:undefined,
expandedScreen: [], expandedScreen:[]
deleteFlowIds: [], }),
}),
getters: { getters: {
/** /**
* *
* @returns 現在編集しているフロー * @returns 現在編集しているフロー
*/ */
currentFlow(): IActionFlow | undefined { currentFlow():IActionFlow|undefined{
return this.selectedFlow; return this.selectedFlow;
}, },
/** /**
* KintoneイベントIDから、バンドしているフローを検索する * KintoneイベントIDから、バンドしているフローを検索する
* @param state * @param state
* @returns * @returns
*/ */
findFlowByEventId(state) { findFlowByEventId(state){
return (eventId: string) => { return (eventId:string)=>{
return state.flows?.find((flow) => { return state.flows?.find((flow)=>{
const root = flow.getRoot(); const root=flow.getRoot();
return root?.name === eventId; return root?.name===eventId
}); });
}; }
}, }
findEventById(state) {
return (eventId: string) => {
return state.eventTree.findEventById(eventId);
};
},
findEventFrist(state) {
return () => {
return state.eventTree.findEventFrist();
};
},
}, },
actions: { actions: {
setFlows(flows: IActionFlow[]) { setFlows(flows:IActionFlow[]){
this.flows = flows; this.flows=flows;
}, },
selectFlow(flow: IActionFlow | undefined) { selectFlow(flow:IActionFlow){
this.selectedFlow = flow; this.selectedFlow=flow;
}, },
setActiveNode(node: IActionNode) { setActiveNode(node:IActionNode){
this.activeNode = node; this.activeNode=node;
}, },
setApp(app: AppInfo) { setApp(app:AppInfo){
this.appInfo = app; this.appInfo=app;
},
addDeleteFlowId(flowId: string) {
this.deleteFlowIds.push(flowId);
},
clearDeleteFlowIds() {
this.deleteFlowIds = [];
}, },
/** /**
* DBからフルーを保存する * DBからフルーを保存する
* @returns * @returns
*/ */
async loadFlow() { async loadFlow(){
if (this.appInfo === undefined) return; if(this.appInfo===undefined) return;
const actionFlows = await flowCtrl.getFlows(this.appInfo?.appId); const actionFlows = await flowCtrl.getFlows(this.appInfo?.appId);
//eventTreeにバンドする //eventTreeにバンドする
this.eventTree.bindFlows(actionFlows); this.eventTree.bindFlows(actionFlows);
if (actionFlows === undefined || actionFlows.length === 0) { if(actionFlows===undefined || actionFlows.length===0){
this.flows = []; this.flows=[];
this.selectedFlow = undefined; this.selectedFlow=undefined;
return; return;
} }
this.setFlows(actionFlows); this.setFlows(actionFlows);
if (actionFlows && actionFlows.length > 0) { if(actionFlows && actionFlows.length>0){
this.selectFlow(actionFlows[0]); this.selectFlow(actionFlows[0]);
} }
const expandNames = actionFlows.map((flow) => flow.getRoot()?.title); const expandNames = actionFlows.map(flow=>flow.getRoot()?.title);
// const expandName =actionFlows[0].getRoot()?.title; // const expandName =actionFlows[0].getRoot()?.title;
this.expandedScreen = expandNames; this.expandedScreen=expandNames;
}, },
/** /**
* フローをDBに保存及び更新する * フローをDBに保存及び更新する
*/ */
async saveFlow(flow: IActionFlow) { async saveFlow(flow:IActionFlow){
const root = flow.getRoot(); const root=flow.getRoot();
const isNew = flow.id === ''; const isNew = flow.id==='';
const jsonData = { const jsonData={
flowid: isNew ? flow.createNewId() : flow.id, flowid: isNew ? flow.createNewId():flow.id,
appid: this.appInfo?.appId, appid: this.appInfo?.appId,
eventid: root?.name, eventid: root?.name,
name: root?.subTitle, name: root?.subTitle,
content: JSON.stringify(flow), content: JSON.stringify(flow)
};
if (isNew) {
return await flowCtrl.SaveFlow(jsonData);
} else {
return await flowCtrl.UpdateFlow(jsonData);
} }
},
async deletebackendFlow() { if(isNew){
const deletePromises = Object.values(this.deleteFlowIds).map((flowId) => return await flowCtrl.SaveFlow(jsonData);
flowCtrl.DeleteFlow(flowId) }else{
); return await flowCtrl.UpdateFlow(jsonData);
await Promise.all(deletePromises);
this.clearDeleteFlowIds();
},
deleteStoreEventAndFlow(event: IKintoneEvent) {
const store = useFlowEditorStore();
if (event.flowData) {
const flow = event.flowData;
if (flow.id === '') {
return;
}
console.log('delete flow', flow);
this.addDeleteFlowId(flow.id);
eventTree.deleteEvent(event, store);
if(this.flows){
this.flows = this.flows.filter((f) => f.id !== flow.id);
}
} else {
eventTree.deleteEvent(event, store);
} }
}, },
/** /**
* デプロイする * デプロイする
*/ */
async deploy(): Promise<boolean> { async deploy():Promise<boolean>{
if (this.appInfo === undefined) { if(this.appInfo===undefined){
return false; return false;
} }
return await flowCtrl.depoly(this.appInfo?.appId); return await flowCtrl.depoly(this.appInfo?.appId);
}, }
},
}
}); });

View File

@@ -45,7 +45,16 @@ export interface IActionProperty {
export interface IActionVariable{ export interface IActionVariable{
actionName:string; actionName:string;
displayName:string; displayName:string;
name:string; name: {
name:string;
actionName:string;
displayName:string;
vars : {
vName:string;
logicalOperator:string;
field: object;
}[]
};
} }
/** /**
* アクションタイプ定義 * アクションタイプ定義
@@ -448,6 +457,12 @@ export class ActionFlow implements IActionFlow {
getPrevVarNames(prevNode:IActionNode):IActionVariable[]{ getPrevVarNames(prevNode:IActionNode):IActionVariable[]{
let varNames:IActionVariable[]=[]; let varNames:IActionVariable[]=[];
if(prevNode.varName!==undefined && prevNode.varName.modelValue){ if(prevNode.varName!==undefined && prevNode.varName.modelValue){
if(prevNode.varName.modelValue ==='object'){
console.log(prevNode);
}
varNames.unshift({ varNames.unshift({
actionName:prevNode.name, actionName:prevNode.name,
displayName:prevNode.varName.displayName, displayName:prevNode.varName.displayName,

View File

@@ -198,7 +198,7 @@ export class ConditionTree {
if(value && typeof value ==='object' && ('label' in value)){ if(value && typeof value ==='object' && ('label' in value)){
value =condNode.value.label; value =condNode.value.label;
} }
return `${condNode.object.name} ${condNode.operator} '${value}'`; return `${typeof condNode.object.name === 'object' ? condNode.object.name.name : condNode.object.name} ${condNode.operator} '${value}'`;
} else { } else {
return ''; return '';
} }

View File

@@ -1,10 +1,9 @@
import { useFlowEditorStore } from 'src/stores/flowEditor'; import {IActionFlow} from './ActionTypes';
import { IActionFlow } from './ActionTypes';
export interface IKintoneEventNode { export interface IKintoneEventNode {
label: string; label: string;
header: string; header:string;
eventId: string; eventId:string;
parentId: string; parentId:string;
} }
export interface IKintoneEvent extends IKintoneEventNode { export interface IKintoneEvent extends IKintoneEventNode {
@@ -16,64 +15,60 @@ export interface IKintoneEventGroup extends IKintoneEventNode {
events: IKintoneEventNode[]; events: IKintoneEventNode[];
} }
export class kintoneEvent implements IKintoneEvent {
export class kintoneEvent implements IKintoneEvent{
eventId: string; eventId: string;
parentId: string; parentId:string;
get hasFlow(): boolean { get hasFlow(): boolean{
return this.flowData !== undefined && this.flowData.actionNodes.length > 1; return this.flowData!==undefined && this.flowData.actionNodes.length>1
} };
flowData?: IActionFlow | undefined; flowData?: IActionFlow | undefined;
label: string; label: string;
header = 'EVENT'; get header():string{
constructor(label: string, eventId: string, parentId: string) { return "EVENT";
this.eventId = eventId; }
this.label = label; constructor(label:string,eventId:string,parentId:string){
this.parentId = parentId; this.eventId=eventId;
this.label=label;
this.parentId=parentId;
} }
} }
export class kintoneEventGroup implements IKintoneEventGroup { export class kintoneEventGroup implements IKintoneEventGroup{
eventId: string; eventId: string;
parentId: string; parentId:string;
label: string; label: string;
events: IKintoneEventNode[]; events: IKintoneEventNode[];
get header(): string { get header():string{
return 'EVENTGROUP'; return "EVENTGROUP";
} }
constructor( constructor(eventId:string,label:string,events:IKintoneEventNode[],parentId:string){
eventId: string, this.eventId=eventId;
label: string, this.label=label;
events: IKintoneEventNode[], this.events=events;
parentId: string this.parentId=parentId;
) {
this.eventId = eventId;
this.label = label;
this.events = events;
this.parentId = parentId;
} }
} }
export class kintoneEventForChange implements IKintoneEventGroup {
export class kintoneEventForChange implements IKintoneEventGroup{
eventId: string; eventId: string;
parentId: string; parentId:string;
label: string; label: string;
events: IKintoneEventNode[]; events: IKintoneEventNode[];
get header(): string { get header():string{
return 'CHANGE'; return "CHANGE";
} }
constructor( constructor(eventId:string,label:string,events:IKintoneEventNode[],parentId:string){
eventId: string, this.eventId=eventId;
label: string, this.label=label;
events: IKintoneEventNode[], this.events=events;
parentId: string this.parentId=parentId;
) {
this.eventId = eventId;
this.label = label;
this.events = events;
this.parentId = parentId;
} }
} }
export class KintoneEventManager { export class KintoneEventManager {
public screens: IKintoneEventGroup[]; public screens: IKintoneEventGroup[];
@@ -81,42 +76,28 @@ export class KintoneEventManager {
this.screens = this.getKintoneEvents(); this.screens = this.getKintoneEvents();
} }
public bindFlows(flows: IActionFlow[]) { public bindFlows(flows:IActionFlow[]){
this.screens = this.getKintoneEvents(); this.screens=this.getKintoneEvents();
for (const flow of flows) { for (const flow of flows){
const eventId = flow.getRoot()?.name; const eventId =flow.getRoot()?.name;
console.log('eventId:', eventId); if(eventId!==undefined){
if (eventId !== undefined) {
const eventNode = this.findEventById(eventId); const eventNode = this.findEventById(eventId);
if (eventNode !== null && eventNode.header === 'EVENT') { if(eventNode!==null && eventNode.header==="EVENT"){
const event = eventNode as kintoneEvent; const event =eventNode as kintoneEvent;
event.flowData = flow; event.flowData=flow;
} else { }else{
//EventGroupのIDを取得 //EventGroupのIDを取得
const lastIndex = eventId.lastIndexOf('.'); const lastIndex = eventId.lastIndexOf(".");
const groupId = eventId.substring(0, lastIndex); const groupId=eventId.substring(0,lastIndex);
const eventNode = this.findEventById(groupId); const eventNode = this.findEventById(groupId);
if ( if(eventNode && (eventNode.header==="EVENTGROUP" || eventNode.header==="CHANGE")){
eventNode && const groupEvent=eventNode as kintoneEventGroup;
(eventNode.header === 'EVENTGROUP' || eventNode.header === 'CHANGE') const newEvent =new kintoneEvent(
) { flow.getRoot()?.subTitle || "",
const groupEvent = eventNode as kintoneEventGroup; eventId,
console.log('label:', flow.getRoot()?.subTitle); groupEvent.parentId
);
const newEvent = { newEvent.flowData=flow;
label: flow.getRoot()?.subTitle || '',
eventId: eventId,
parentId: groupId,
header: 'DELETABLE',
hasFlow: true,
flowData: flow,
};
// new kintoneEvent(
// flow.getRoot()?.subTitle || '',
// eventId,
// groupEvent.parentId
// );
groupEvent.events.push(newEvent); groupEvent.events.push(newEvent);
} }
} }
@@ -124,198 +105,62 @@ export class KintoneEventManager {
} }
} }
public findEventFrist() {
return this.findEventById('app.record.create.show') as IKintoneEvent;
}
public findEventById(eventId: string): IKintoneEventNode | null { public findEventById(eventId: string): IKintoneEventNode | null {
const screen = this.findScreen(eventId); const screen=this.findScreen(eventId);
if (screen) { if(screen) {return screen;}
return screen;
}
for (const screen of this.screens) { for (const screen of this.screens) {
for (const event of screen.events) { for (const event of screen.events) {
if (event.eventId === eventId) { if (event.eventId === eventId) {
return event; return event;
} }
if (event.header === 'EVENTGROUP' || event.header === 'CHANGE') { if(event.header==="EVENTGROUP"||event.header==="CHANGE"){
const eventGroup = event as IKintoneEventGroup; const eventGroup = event as IKintoneEventGroup;
const targetEvent = eventGroup.events.find((ev) => { const targetEvent = eventGroup.events.find((ev)=>{
return ev.eventId === eventId; return ev.eventId===eventId;
}); })
if (targetEvent) { if(targetEvent){
return targetEvent; return targetEvent;
}
} }
}
} }
} }
return null; return null;
} }
public findScreen(eventId: string): IKintoneEventGroup | undefined { public findScreen(eventId:string):IKintoneEventGroup|undefined{
return this.screens.find((screen) => screen.eventId == eventId); return this.screens.find(screen=>screen.eventId==eventId);
} }
public deleteEvent( public getKintoneEvents():IKintoneEventGroup[]{
event: kintoneEvent,
store: ReturnType<typeof useFlowEditorStore>
) {
if (event.header !== 'DELETABLE') {
return;
}
const parent = store.findEventById(event.parentId);
if (parent?.header !== 'CHANGE' && parent?.header !== 'EVENTGROUP') {
return;
}
const realParent = parent as kintoneEventForChange;
const index = realParent.events.findIndex(
(e) => e.eventId === event.eventId
);
if (index !== -1) {
realParent.events.splice(index, 1);
}
}
public getKintoneEvents(): IKintoneEventGroup[] {
return [ return [
new kintoneEventGroup( new kintoneEventGroup("app.record.create","レコード追加画面",[
'app.record.create', new kintoneEvent('レコード追加画面を表示した後','app.record.create.show',"app.record.create"),
'レコード追加画面', new kintoneEvent('保存をクリックしたとき','app.record.create.submit',"app.record.create"),
[ new kintoneEvent('保存が成功したとき','app.record.create.submit.success',"app.record.create"),
new kintoneEvent( new kintoneEventForChange('app.record.create.change','フィールドの値を変更したとき',[],"app.record.create"),
'レコード追加画面を表示した', new kintoneEventGroup('app.record.create.show.customButtonClick','ボタンをクリックした',[],"app.record.create")
'app.record.create.show', ],""),
'app.record.create' new kintoneEventGroup("app.record.detail","レコード詳細画面",[
), new kintoneEvent('レコード詳細画面を表示した後','app.record.detail.show',"app.record.detail"),
new kintoneEvent( new kintoneEvent('レコードを削除するとき','app.record.detail.delete.submit',"app.record.detail"),
'保存をクリックしたとき', new kintoneEvent('プロセス管理のアクションを実行したとき','app.record.detail.process.proceed',"app.record.detail"),
'app.record.create.submit', new kintoneEventGroup('app.record.detail.show.customButtonClick','ボタンをクリックした時',[],"app.record.detail"),
'app.record.create' ],""),
), new kintoneEventGroup("app.record.edit","レコード編集画面",[
new kintoneEvent( new kintoneEvent('レコード編集画面を表示した後','app.record.edit.show',"app.record.edit"),
'保存が成功したとき', new kintoneEvent('保存をクリックしたとき','app.record.edit.submit',"app.record.edit"),
'app.record.create.submit.success', new kintoneEvent('保存が成功したとき','app.record.edit.submit.success',"app.record.edit"),
'app.record.create' new kintoneEventForChange('app.record.edit.change','フィールドの値を変更したとき',[],"app.record.edit"),
), new kintoneEventGroup('app.record.edit.show.customButtonClick','ボタンをクリックした時',[],"app.record.edit"),
new kintoneEventForChange( ],""),
'app.record.create.change', new kintoneEventGroup("app.record.index","レコード一覧画面",[
'フィールドの値を変更したとき', new kintoneEvent('一覧画面を表示した後', 'app.record.index.show',"app.record.index"),
[], new kintoneEvent('インライン編集を開始したとき','app.record.index.edit.show',"app.record.index"),
'app.record.create' new kintoneEvent('インライン編集の【保存】をクリックしたとき','app.record.index.edit.submit',"app.record.index"),
), new kintoneEvent('インライン編集の保存が成功したとき', 'app.record.index.edit.submit.success',"app.record.index"),
new kintoneEventGroup( new kintoneEventForChange('app.record.index.edit.change','インライン編集のフィールド値を変更したとき' ,[],"app.record.index"),
'app.record.create.show.customButtonClick', new kintoneEventGroup('app.record.detail.show.customButtonClick','ボタンをクリックした時',[],"app.record.index"),
'ボタンをクリックした時', ],"")
[],
'app.record.create'
),
],
''
),
new kintoneEventGroup(
'app.record.detail',
'レコード詳細画面',
[
new kintoneEvent(
'レコード詳細画面を表示した後',
'app.record.detail.show',
'app.record.detail'
),
new kintoneEvent(
'レコードを削除するとき',
'app.record.detail.delete.submit',
'app.record.detail'
),
new kintoneEvent(
'プロセス管理のアクションを実行したとき',
'app.record.detail.process.proceed',
'app.record.detail'
),
new kintoneEventGroup(
'app.record.detail.show.customButtonClick',
'ボタンをクリックした時',
[],
'app.record.detail'
),
],
''
),
new kintoneEventGroup(
'app.record.edit',
'レコード編集画面',
[
new kintoneEvent(
'レコード編集画面を表示した後',
'app.record.edit.show',
'app.record.edit'
),
new kintoneEvent(
'保存をクリックしたとき',
'app.record.edit.submit',
'app.record.edit'
),
new kintoneEvent(
'保存が成功したとき',
'app.record.edit.submit.success',
'app.record.edit'
),
new kintoneEventForChange(
'app.record.edit.change',
'フィールドの値を変更したとき',
[],
'app.record.edit'
),
new kintoneEventGroup(
'app.record.edit.show.customButtonClick',
'ボタンをクリックした時',
[],
'app.record.edit'
),
],
''
),
new kintoneEventGroup(
'app.record.index',
'レコード一覧画面',
[
new kintoneEvent(
'一覧画面を表示した後',
'app.record.index.show',
'app.record.index'
),
new kintoneEvent(
'インライン編集を開始したとき',
'app.record.index.edit.show',
'app.record.index'
),
new kintoneEvent(
'インライン編集の【保存】をクリックしたとき',
'app.record.index.edit.submit',
'app.record.index'
),
new kintoneEvent(
'インライン編集の保存が成功したとき',
'app.record.index.edit.submit.success',
'app.record.index'
),
new kintoneEventForChange(
'app.record.index.edit.change',
'インライン編集のフィールド値を変更したとき',
[],
'app.record.index'
),
new kintoneEventGroup(
'app.record.detail.show.customButtonClick',
'ボタンをクリックした時',
[],
'app.record.index'
),
],
''
),
]; ];
} }
} }