Compare commits
31 Commits
feature-de
...
plugin-inf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3540becf6f | ||
|
|
63099eda8b | ||
|
|
65d89b0462 | ||
|
|
c6ded099fa | ||
|
|
c072233593 | ||
|
|
4e296c1555 | ||
| 016fcaab29 | |||
|
|
bebc1ec9fa | ||
|
|
f71c3d2123 | ||
|
|
d79ce8d06b | ||
|
|
fc9c3a5e81 | ||
|
|
6df72a1ae3 | ||
|
|
372dbe50f7 | ||
|
|
68fde6d490 | ||
|
|
c398dee21e | ||
|
|
f2ab310b6d | ||
|
|
ca0f24465b | ||
|
|
3cc4b65460 | ||
|
|
a6cf95b76d | ||
|
|
484ab9fdae | ||
|
|
78bba2502f | ||
|
|
c78b3cb5c0 | ||
|
|
c3b560dbc9 | ||
| 53aadfcaaa | |||
|
|
7fb3d08ccb | ||
|
|
cf4209333d | ||
|
|
61ac281134 | ||
|
|
b25c17ab53 | ||
|
|
64d2cadd82 | ||
|
|
371e2ee073 | ||
|
|
a7078b54c5 |
File diff suppressed because one or more lines are too long
@@ -2,7 +2,6 @@
|
||||
<html lang="ja-jp">
|
||||
<head>
|
||||
<title><%= productName %></title>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="description" content="<%= productDescription %>">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@quasar/extras": "^1.16.4",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"axios": "^1.4.0",
|
||||
"pinia": "^2.1.6",
|
||||
"pinia": "^2.1.7",
|
||||
"quasar": "^2.6.0",
|
||||
"uuid": "^9.0.0",
|
||||
"vue": "^3.0.0",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Router } from 'vue-router';
|
||||
import { App } from 'vue';
|
||||
|
||||
export default boot(({ app, router }: { app: App<Element>; router: Router }) => {
|
||||
document.documentElement.lang="ja-JP";
|
||||
app.config.errorHandler = (err: any, instance: any, info: string) => {
|
||||
if (err.response && err.response.status === 401) {
|
||||
// 認証エラーの場合再ログインする
|
||||
|
||||
152
frontend/src/components/AppFieldSelectBox.vue
Normal file
152
frontend/src/components/AppFieldSelectBox.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
|
||||
<div class="q-mx-md q-mb-lg">
|
||||
<div class="q-mb-xs q-ml-md text-primary">アプリ選択</div>
|
||||
|
||||
<div class="q-pa-md row" style="border: 1px solid rgba(0, 0, 0, 0.12); border-radius: 4px;">
|
||||
<div v-if="selField?.app && !showSelectApp">{{ selField.app?.name }}</div>
|
||||
<q-space />
|
||||
<div>
|
||||
<q-btn outline dense label="選 択" padding="none sm" color="primary" @click="() => {
|
||||
showSelectApp = true;
|
||||
}"></q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!showSelectApp && selField.app?.name">
|
||||
<div>
|
||||
<div class="row q-mb-md">
|
||||
<!-- <div class="col"> -->
|
||||
<div class="q-mb-xs q-ml-md text-primary">フィールド選択</div>
|
||||
<!-- </div> -->
|
||||
<q-space />
|
||||
<!-- <div class="col"> -->
|
||||
<div class="q-mr-md">
|
||||
<q-input dense debounce="300" v-model="fieldFilter" placeholder="フィールド検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<field-select ref="fieldDlg" name="フィールド" :type="selectType" :updateSelects="updateItems"
|
||||
:appId="selField.app?.id" not_page :filter="fieldFilter"
|
||||
:selFields="selField.fields"></field-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="min-width: 45vw;" v-else>
|
||||
</div>
|
||||
|
||||
|
||||
<show-dialog v-model:visible="showSelectApp" name="アプリ選択" @close="closeAppDlg">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
|
||||
<AppSelectBox ref="appDlg" name="アプリ" type="single" :filter="filter"
|
||||
:updateExternalSelectAppInfo="updateExternalSelectAppInfo"></AppSelectBox>
|
||||
</show-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watchEffect, computed ,reactive} from 'vue';
|
||||
import ShowDialog from './ShowDialog.vue';
|
||||
import FieldSelect from './FieldSelect.vue';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
import AppSelectBox from './AppSelectBox.vue';
|
||||
interface IApp {
|
||||
id: string,
|
||||
name: string
|
||||
}
|
||||
interface IField {
|
||||
name: string,
|
||||
code: string,
|
||||
type: string
|
||||
}
|
||||
|
||||
interface IAppFields {
|
||||
app?: IApp,
|
||||
fields: IField[]
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
name: 'AppFieldSelectBox',
|
||||
components: {
|
||||
ShowDialog,
|
||||
FieldSelect,
|
||||
AppSelectBox,
|
||||
},
|
||||
props: {
|
||||
selectedField: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
selectType: {
|
||||
type: String,
|
||||
default: 'single'
|
||||
},
|
||||
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const appDlg = ref();
|
||||
const fieldDlg = ref();
|
||||
const showSelectApp = ref(false);
|
||||
const selField = reactive(props.selectedField);
|
||||
console.log(props.selectedField);
|
||||
|
||||
const store = useFlowEditorStore();
|
||||
|
||||
const isSelected = computed(() => {
|
||||
return selField !== null && typeof selField === 'object' && ('app' in selField)
|
||||
});
|
||||
|
||||
|
||||
const closeAppDlg = (val: string) => {
|
||||
if (val == 'OK') {
|
||||
selField.app = appDlg.value.selected[0];
|
||||
selField.fields = [];
|
||||
showSelectApp.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const closeFieldDialog = (val: string) => {
|
||||
if (val == 'OK') {
|
||||
selField.fields = fieldDlg.value.selected;
|
||||
}
|
||||
};
|
||||
const updateExternalSelectAppInfo = (newAppinfo: IApp) => {
|
||||
selField.app = newAppinfo
|
||||
}
|
||||
|
||||
const updateItems = (newFields: IField[]) => {
|
||||
selField.fields = newFields
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selField);
|
||||
});
|
||||
|
||||
return {
|
||||
appDlg,
|
||||
fieldDlg,
|
||||
closeAppDlg,
|
||||
closeFieldDialog,
|
||||
showSelectApp,
|
||||
isSelected,
|
||||
updateExternalSelectAppInfo,
|
||||
filter: ref(),
|
||||
updateItems,
|
||||
fieldFilter: ref(),
|
||||
selField
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -21,7 +21,7 @@ import { ref, onMounted, reactive, watchEffect } from 'vue'
|
||||
import { api } from 'boot/axios';
|
||||
|
||||
export default {
|
||||
name: 'AppSelect',
|
||||
name: 'AppSelectBox',
|
||||
props: {
|
||||
name: String,
|
||||
type: String,
|
||||
@@ -1,5 +1,5 @@
|
||||
<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>
|
||||
<q-btn flat round dense icon="more_vert" >
|
||||
<q-menu auto-close anchor="bottom start">
|
||||
|
||||
@@ -6,21 +6,21 @@
|
||||
{{ selectedObject.name }}
|
||||
</q-chip>
|
||||
<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>
|
||||
</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" width="600px">
|
||||
<show-dialog v-model:visible="show" name="設定項目一覧" @close="closeDg" min-width="400px">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="200" v-model="filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
</template>
|
||||
<condition-objects ref="appDg" name="フィールド" type="single" :filter="filter" :appId="store.appInfo?.appId" :vars="vars"></condition-objects>
|
||||
</show-dialog>
|
||||
</template>
|
||||
@@ -88,9 +88,9 @@
|
||||
.condition-object{
|
||||
min-width: 200px;
|
||||
max-height: 40px;
|
||||
padding: 2px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
.selected-obj{
|
||||
margin: 0px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
</div>
|
||||
<!-- condition -->
|
||||
<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>
|
||||
<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)"
|
||||
@@ -113,7 +113,7 @@ import { finished } from 'stream';
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent,ref,reactive, computed } from 'vue';
|
||||
import { defineComponent,ref,reactive, computed, inject } from 'vue';
|
||||
import { INode,ConditionTree,GroupNode,ConditionNode, LogicalOperator,Operator,NodeType } from '../../types/Conditions';
|
||||
import ConditionObject from './ConditionObject.vue';
|
||||
export default defineComponent( {
|
||||
@@ -143,12 +143,9 @@ export default defineComponent( {
|
||||
return opts;
|
||||
});
|
||||
|
||||
const operator = inject('Operator')
|
||||
const operators =computed(()=>{
|
||||
const opts=[];
|
||||
for(const op in Operator){
|
||||
opts.push(Operator[op as keyof typeof Operator]);
|
||||
}
|
||||
return opts;
|
||||
return operator ? operator : Object.values(Operator);
|
||||
});
|
||||
const tree = reactive(props.conditionTree);
|
||||
|
||||
@@ -257,12 +254,12 @@ export default defineComponent( {
|
||||
.condition-value{
|
||||
min-width: 200px;
|
||||
max-height: 40px;
|
||||
padding: 2px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
.operator{
|
||||
min-width: 150px;
|
||||
max-height: 40px;
|
||||
padding: 2px;
|
||||
margin: 0 2px;
|
||||
text-align: center;
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<q-tab-panels v-model="tab" animated>
|
||||
<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 name="vars" >
|
||||
@@ -30,7 +30,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ref, onMounted, reactive } from 'vue'
|
||||
import { ref, onMounted, reactive, inject } from 'vue'
|
||||
import FieldList from './FieldList.vue';
|
||||
import VariableList from './VariableList.vue';
|
||||
|
||||
@@ -48,10 +48,14 @@ export default {
|
||||
filter:String
|
||||
},
|
||||
setup(props) {
|
||||
|
||||
const selected = ref([]);
|
||||
console.log(selected);
|
||||
|
||||
return {
|
||||
sourceFields : inject('sourceFields'),
|
||||
sourceApp : inject('sourceApp'),
|
||||
tab: ref('fields'),
|
||||
selected: ref([])
|
||||
selected
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<q-table flat bordered :loading="!isLoaded" row-key="name" :selection="type"
|
||||
:selected="modelValue"
|
||||
@update:selected="$emit('update:modelValue', $event)"
|
||||
:filter="filter"
|
||||
:columns="columns" :rows="rows" />
|
||||
<q-table flat bordered :loading="!isLoaded" row-key="name" :selection="type" :selected="modelValue"
|
||||
@update:selected="$emit('update:modelValue', $event)" :filter="filter" :columns="columns" :rows="rows" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref, onMounted, reactive } from 'vue'
|
||||
<script lang="ts">
|
||||
import { useAsyncState } from '@vueuse/core';
|
||||
import { api } from 'boot/axios';
|
||||
import { computed } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'FieldList',
|
||||
props: {
|
||||
fields: Array,
|
||||
name: String,
|
||||
type: String,
|
||||
appId: Number,
|
||||
modelValue:Array,
|
||||
filter:String
|
||||
},
|
||||
emits:[
|
||||
'update:modelValue'
|
||||
],
|
||||
modelValue: Array,
|
||||
filter: String
|
||||
},
|
||||
emits: [
|
||||
'update:modelValue'
|
||||
],
|
||||
setup(props) {
|
||||
const isLoaded = ref(false);
|
||||
// const rows = ref([]);
|
||||
// const isLoaded = ref(false);
|
||||
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: 'type', label: 'フィールドタイプ', align: 'left', field: 'type', sortable: true }
|
||||
]
|
||||
const rows = reactive([]);
|
||||
onMounted(async () => {
|
||||
const res = await api.get('api/v1/appfields', {
|
||||
params: {
|
||||
app: props.appId
|
||||
}
|
||||
});
|
||||
let fields = res.data.properties;
|
||||
console.log(fields);
|
||||
Object.keys(fields).forEach((key) => {
|
||||
const fld = fields[key];
|
||||
rows.push({ name: fld.label, objectType: 'field', ...fld });
|
||||
});
|
||||
isLoaded.value = true;
|
||||
});
|
||||
|
||||
const { state : rows, isReady: isLoaded, isLoading } = useAsyncState((args) => {
|
||||
if (props.fields) {
|
||||
return props.fields.map(f => ({ name: f.label, objectType: 'field', ...f }));
|
||||
} else {
|
||||
return api.get('api/v1/appfields', {
|
||||
params: {
|
||||
app: props.appId
|
||||
}
|
||||
}).then(res => {
|
||||
console.log(res);
|
||||
return Object.values(res.data.properties).map(f => ({ name: f.label, objectType: 'field', ...f }));
|
||||
});
|
||||
}
|
||||
}, [{ name: '', objectType: '', type: '', code: '', label: '' }])
|
||||
|
||||
return {
|
||||
columns,
|
||||
rows,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<!-- <div class="q-pa-md q-gutter-sm" > -->
|
||||
<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-title>{{ name }}</q-toolbar-title>
|
||||
<q-space></q-space>
|
||||
|
||||
@@ -1,43 +1,59 @@
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<q-table flat bordered row-key="name" :selection="type"
|
||||
:selected="modelValue"
|
||||
@update:selected="$emit('update:modelValue', $event)"
|
||||
:columns="columns" :rows="rows" />
|
||||
<q-table flat bordered row-key="id" :selection="type" :selected="modelValue"
|
||||
@update:selected="$emit('update:modelValue', $event)" :columns="columns" :rows="rows" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ref, reactive, PropType, compile } from 'vue';
|
||||
import {IActionNode,IActionVariable} from '../types/ActionTypes';
|
||||
import { PropType, reactive } from 'vue';
|
||||
import { IActionVariable } from '../types/ActionTypes';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export default {
|
||||
name: 'VariableList',
|
||||
props: {
|
||||
name: String,
|
||||
type: String,
|
||||
vars:{
|
||||
type:Array as PropType<IActionVariable[]>,
|
||||
reqired:true,
|
||||
default:()=>[]
|
||||
vars: {
|
||||
type: Array as PropType<IActionVariable[]>,
|
||||
reqired: true,
|
||||
default: () => []
|
||||
},
|
||||
modelValue:Array
|
||||
modelValue: Array
|
||||
},
|
||||
emits:[
|
||||
'update:modelValue'
|
||||
],
|
||||
emits: [
|
||||
'update:modelValue'
|
||||
],
|
||||
setup(props) {
|
||||
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: 'name',required: true, sortable: true }
|
||||
const variableName = (field) => {
|
||||
const name = field.name;
|
||||
return name.name;
|
||||
}
|
||||
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 {
|
||||
columns,
|
||||
rows:reactive(rows)
|
||||
rows: reactive(rows)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<AppSelect ref="appDg" name="アプリ" type="single" :filter="filter"></AppSelect>
|
||||
<AppSelectBox ref="appDg" name="アプリ" type="single" :filter="filter"></AppSelectBox>
|
||||
</ShowDialog>
|
||||
</template>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
import { defineComponent,ref } from 'vue';
|
||||
import {AppInfo} from '../../types/ActionTypes'
|
||||
import ShowDialog from '../../components/ShowDialog.vue';
|
||||
import AppSelect from '../../components/AppSelect.vue';
|
||||
import AppSelectBox from '../../components/AppSelectBox.vue';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
||||
export default defineComponent({
|
||||
@@ -47,7 +47,7 @@ export default defineComponent({
|
||||
"appSelected"
|
||||
],
|
||||
components:{
|
||||
AppSelect,
|
||||
AppSelectBox,
|
||||
ShowDialog
|
||||
},
|
||||
setup(props, context) {
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { QTree } from 'quasar';
|
||||
import { QTree, useQuasar } from 'quasar';
|
||||
import { ActionFlow, RootAction } from 'src/types/ActionTypes';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
@@ -57,6 +57,7 @@ export default defineComponent({
|
||||
FieldSelect,
|
||||
},
|
||||
setup(props, context) {
|
||||
const $q = useQuasar();
|
||||
const appDg = ref();
|
||||
const store = useFlowEditorStore();
|
||||
const showDialog = ref(false);
|
||||
@@ -101,10 +102,14 @@ export default defineComponent({
|
||||
if (!node.eventId) {
|
||||
return;
|
||||
}
|
||||
store.deleteStoreEventAndFlow(node);
|
||||
store.deleteEvent(node);
|
||||
store.selectFlow(undefined)
|
||||
console.log(store.selectedFlow);
|
||||
|
||||
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
caption: "通知",
|
||||
message: `イベント ${node.label} 削除`
|
||||
})
|
||||
}
|
||||
|
||||
const onSelected = (node: IKintoneEvent) => {
|
||||
|
||||
@@ -205,7 +205,7 @@ export default defineComponent({
|
||||
*/
|
||||
const varName =(node:IActionNode)=>{
|
||||
const prop = node.actionProps.find((prop) => prop.props.name === "verName");
|
||||
return prop?.props.modelValue;
|
||||
return prop?.props.modelValue.name;
|
||||
};
|
||||
const copyFlow=()=>{
|
||||
context.emit('copyFlow', props.actionNode);
|
||||
|
||||
@@ -18,121 +18,68 @@
|
||||
<q-separator />
|
||||
<q-card-section class="q-pa-none q-ma-none">
|
||||
<div style="">
|
||||
<div v-if="selectedField.fields && selectedField.fields.length > 0 ">
|
||||
<q-list bordered>
|
||||
<q-virtual-scroll style="max-height: 160px;" :items="selectedField.fields" separator v-slot="{ item, index }">
|
||||
<q-item :key="index" dense clickable >
|
||||
<q-item-section>
|
||||
<q-item-label>
|
||||
{{ item.label }}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-btn round flat size="sm" icon="clear" @click="removeField(index)" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-virtual-scroll>
|
||||
</q-list>
|
||||
<div v-if="selectedField.fields && selectedField.fields.length > 0">
|
||||
<q-list bordered>
|
||||
<q-virtual-scroll style="max-height: 160px;" :items="selectedField.fields" separator
|
||||
v-slot="{ item, index }">
|
||||
<q-item :key="index" dense clickable>
|
||||
<q-item-section>
|
||||
<q-item-label>
|
||||
{{ item.label }}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-btn round flat size="sm" icon="clear" @click="removeField(index)" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-virtual-scroll>
|
||||
</q-list>
|
||||
</div>
|
||||
<!-- <div v-else class="row q-mt-lg">
|
||||
</div> -->
|
||||
</div>
|
||||
<!-- <q-separator /> -->
|
||||
</q-card-section>
|
||||
<q-card-section class="q-px-none q-py-xs" v-if="selectedField.fields && selectedField.fields.length===0">
|
||||
<q-card-section class="q-px-none q-py-xs" v-if="selectedField.fields && selectedField.fields.length === 0">
|
||||
<div class="row">
|
||||
<div class="text-grey text-caption"> {{ $props.placeholder }}</div>
|
||||
<!-- <q-btn flat color="grey" label="clear" @click="clear" /> -->
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeFieldDialog" ref="fieldDlg">
|
||||
|
||||
<div class="q-mx-md q-mb-lg">
|
||||
<div class="q-mb-xs q-ml-md text-primary">アプリ選択</div>
|
||||
|
||||
<div class="q-pa-md row" style="border: 1px solid rgba(0, 0, 0, 0.12); border-radius: 4px;">
|
||||
<div v-if="!showSelectApp && selectedField.app">{{ selectedField.app?.name }}</div>
|
||||
<q-space />
|
||||
<div>
|
||||
<q-btn outline dense label="選 択" padding="none sm" color="primary" @click="() => {
|
||||
showSelectApp = true;
|
||||
}"></q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!showSelectApp && selectedField.app?.name">
|
||||
<div>
|
||||
<div class="row q-mb-md">
|
||||
<!-- <div class="col"> -->
|
||||
<div class="q-mb-xs q-ml-md text-primary">フィールド選択</div>
|
||||
<!-- </div> -->
|
||||
<q-space />
|
||||
<!-- <div class="col"> -->
|
||||
<div class="q-mr-md">
|
||||
<q-input dense debounce="300" v-model="fieldFilter" placeholder="フィールド検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<field-select ref="fieldDlg" name="フィールド" :type="selectType" :updateSelects="updateItems"
|
||||
:appId="selectedField.app?.id" not_page :filter="fieldFilter" :selectedFields="selectedField.fields"></field-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="min-width: 45vw;" v-else>
|
||||
</div>
|
||||
|
||||
</show-dialog>
|
||||
|
||||
<show-dialog v-model:visible="showSelectApp" name="アプリ選択" @close="closeAppDlg">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
|
||||
<AppSelect ref="appDlg" name="アプリ" type="single" :filter="filter"
|
||||
:updateExternalSelectAppInfo="updateExternalSelectAppInfo"></AppSelect>
|
||||
<AppFieldSelectBox v-model:selectedField="selectedField" :selectType="selectType" />
|
||||
</show-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watchEffect, computed } from 'vue';
|
||||
import { defineComponent, ref, watchEffect } from 'vue';
|
||||
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
import FieldSelect from '../FieldSelect.vue';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
import AppSelect from '../AppSelect.vue';
|
||||
interface IApp{
|
||||
id:string,
|
||||
name:string
|
||||
|
||||
export interface IApp {
|
||||
id: string,
|
||||
name: string
|
||||
}
|
||||
interface IField {
|
||||
export interface IField {
|
||||
name: string,
|
||||
code: string,
|
||||
type: string
|
||||
}
|
||||
|
||||
interface IAppFields{
|
||||
app?:IApp,
|
||||
fields:IField[]
|
||||
export interface IAppFields {
|
||||
app?: IApp,
|
||||
fields: IField[]
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs:false,
|
||||
name: 'FieldInput',
|
||||
inheritAttrs: false,
|
||||
name: 'AppFieldSelect',
|
||||
components: {
|
||||
ShowDialog,
|
||||
FieldSelect,
|
||||
AppSelect,
|
||||
AppFieldSelectBox
|
||||
},
|
||||
props: {
|
||||
displayName: {
|
||||
@@ -151,62 +98,30 @@ export default defineComponent({
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
selectType:{
|
||||
type:String,
|
||||
default:'single'
|
||||
selectType: {
|
||||
type: String,
|
||||
default: 'single'
|
||||
}
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const appDlg = ref();
|
||||
const fieldDlg = ref();
|
||||
const show = ref(false);
|
||||
const showSelectApp = ref(false);
|
||||
const selectedField = ref<IAppFields>({
|
||||
app:undefined,
|
||||
fields:[]
|
||||
});
|
||||
if(props.modelValue && "app" in props.modelValue && "fields" in props.modelValue){
|
||||
selectedField.value=props.modelValue as IAppFields;
|
||||
}
|
||||
const selectedField = ref<IAppFields>({
|
||||
app: undefined,
|
||||
fields: []
|
||||
});
|
||||
if (props.modelValue && "app" in props.modelValue && "fields" in props.modelValue) {
|
||||
selectedField.value = props.modelValue as IAppFields;
|
||||
}
|
||||
const store = useFlowEditorStore();
|
||||
|
||||
const isSelected = computed(() => {
|
||||
return selectedField.value !== null && typeof selectedField.value === 'object' && ('app' in selectedField.value)
|
||||
});
|
||||
|
||||
const showDg = () => {
|
||||
show.value = true;
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
selectedField.value ={
|
||||
fields:[]
|
||||
} ;
|
||||
selectedField.value = {
|
||||
fields: []
|
||||
};
|
||||
}
|
||||
|
||||
const closeAppDlg = (val: string) => {
|
||||
if (val == 'OK') {
|
||||
selectedField.value.app = appDlg.value.selected[0];
|
||||
selectedField.value.fields=[];
|
||||
showSelectApp.value=false;
|
||||
}
|
||||
};
|
||||
|
||||
const closeFieldDialog=(val:string)=>{
|
||||
if (val == 'OK') {
|
||||
selectedField.value.fields = fieldDlg.value.selected;
|
||||
}
|
||||
};
|
||||
const updateExternalSelectAppInfo = (newAppinfo:IApp) => {
|
||||
// selectedField.value.app = newAppinfo
|
||||
}
|
||||
|
||||
const updateItems = (newFields:IField[]) => {
|
||||
// selectedField.value.fields = newFields
|
||||
}
|
||||
|
||||
const removeField=(index:number)=>{
|
||||
selectedField.value.fields.splice(index,1);
|
||||
const removeField = (index: number) => {
|
||||
selectedField.value.fields.splice(index, 1);
|
||||
}
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedField.value);
|
||||
@@ -214,21 +129,11 @@ export default defineComponent({
|
||||
|
||||
return {
|
||||
store,
|
||||
appDlg,
|
||||
fieldDlg,
|
||||
show,
|
||||
showDg,
|
||||
closeAppDlg,
|
||||
closeFieldDialog,
|
||||
showDg: () => { show.value = true },
|
||||
selectedField,
|
||||
showSelectApp,
|
||||
isSelected,
|
||||
updateExternalSelectAppInfo,
|
||||
filter: ref(),
|
||||
updateItems,
|
||||
clear,
|
||||
fieldFilter: ref(),
|
||||
removeField
|
||||
removeField,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
93
frontend/src/components/right/AppSelect.vue
Normal file
93
frontend/src/components/right/AppSelect.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<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="selectedField.app.name">
|
||||
{{ selectedField.app.name }}
|
||||
</div>
|
||||
<div v-else>{{ placeholder }}</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
</q-field>
|
||||
</div>
|
||||
|
||||
<ShowDialog v-model:visible="dgIsShow" name="アプリ選択" @close="closeDg" min-width="50vw" min-height="50vh">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<AppSelectBox ref="appDg" name="アプリ" type="single" :filter="filter"></AppSelectBox>
|
||||
</ShowDialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, ref, watchEffect } from 'vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
import AppSelectBox from '../AppSelectBox.vue';
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
name: 'AppSelect',
|
||||
components: {
|
||||
ShowDialog,
|
||||
AppSelectBox
|
||||
},
|
||||
props: {
|
||||
context: {
|
||||
type: Array<Props>,
|
||||
default: '',
|
||||
},
|
||||
displayName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const appDg = ref()
|
||||
const dgIsShow = ref(false)
|
||||
const selectedField = props.modelValue && props.modelValue.app ? props.modelValue : reactive({app:{}});
|
||||
const closeDg = (state: string) => {
|
||||
dgIsShow.value = false;
|
||||
if (state == 'OK') {
|
||||
selectedField.app = appDg.value.selected[0];
|
||||
}
|
||||
};
|
||||
|
||||
console.log(selectedField);
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedField);
|
||||
});
|
||||
|
||||
return {
|
||||
filter: ref(''),
|
||||
dgIsShow,
|
||||
appDg,
|
||||
closeDg,
|
||||
selectedField
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-field v-model="tree" :label="displayName" labelColor="primary" stack-label >
|
||||
<template v-slot:control >
|
||||
<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-btn color="grey-3" text-color="black" :disable="btnDisable" @click="showDg()">クリックで設定:{{ isSetted ?
|
||||
'設定済み' : '未設定' }}</q-btn>
|
||||
</q-card-actions>
|
||||
<q-card-section class="text-caption" >
|
||||
<q-card-section class="text-caption">
|
||||
<div v-if="!isSetted">{{ placeholder }}</div>
|
||||
<div v-else>{{ conditionString }}</div>
|
||||
</q-card-section>
|
||||
@@ -17,82 +18,148 @@
|
||||
</div>
|
||||
</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);
|
||||
|
||||
<script lang="ts">
|
||||
import { ConditionNode, ConditionTree, Operator, OperatorListItem } from 'app/src/types/Conditions';
|
||||
import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue';
|
||||
import ConditionEditor from '../ConditionEditor/ConditionEditor.vue';
|
||||
|
||||
type Props = {
|
||||
props?: {
|
||||
name: string;
|
||||
modelValue?: {
|
||||
app: {
|
||||
id: string;
|
||||
name: string;
|
||||
},
|
||||
fields: {
|
||||
type: string;
|
||||
label: string;
|
||||
code: string;
|
||||
}[]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
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'
|
||||
},
|
||||
onlySourceSelect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
operatorList: {
|
||||
type: Array,
|
||||
}
|
||||
},
|
||||
|
||||
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') {
|
||||
provide('sourceApp', computed(() => source.props?.modelValue?.app?.id));
|
||||
}
|
||||
}
|
||||
|
||||
provide('Operator', props.operatorList);
|
||||
|
||||
const btnDisable = computed(() => {
|
||||
const onlySourceSelect = props.onlySourceSelect;
|
||||
|
||||
if (!onlySourceSelect) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isSetted=ref(props.modelValue && props.modelValue!=='');
|
||||
if (props.sourceType === 'field') {
|
||||
return source?.props?.modelValue?.fields?.length ?? 0 > 0;
|
||||
} else if (props.sourceType === 'app') {
|
||||
return source?.props?.modelValue?.app?.id ? false : true
|
||||
}
|
||||
return true;
|
||||
})
|
||||
|
||||
const conditionString = computed(()=>{
|
||||
return tree.buildConditionString(tree.root);
|
||||
});
|
||||
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({}, (props.operatorList && props.operatorList.length > 0) ? props.operatorList[0] as OperatorListItem : Operator.Equal, '', tree.root);
|
||||
tree.addNode(tree.root, newNode);
|
||||
}
|
||||
|
||||
const showDg = () => {
|
||||
show.value = true;
|
||||
};
|
||||
const isSetted = ref(props.modelValue && props.modelValue !== '');
|
||||
|
||||
const onClosed = (val:string) => {
|
||||
if (val == 'OK') {
|
||||
const conditionJson = tree.toJson();
|
||||
isSetted.value=true;
|
||||
emit('update:modelValue', conditionJson);
|
||||
}
|
||||
};
|
||||
const conditionString = computed(() => {
|
||||
return tree.buildConditionString(tree.root);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
const showDg = () => {
|
||||
show.value = true;
|
||||
};
|
||||
|
||||
const onClosed = (val: string) => {
|
||||
if (val == 'OK') {
|
||||
isSetted.value = true;
|
||||
tree.setQuery(tree.buildConditionQueryString(tree.root));
|
||||
const conditionJson = tree.toJson();
|
||||
emit('update:modelValue', conditionJson);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
appDg,
|
||||
isSetted,
|
||||
show,
|
||||
showDg,
|
||||
onClosed,
|
||||
tree,
|
||||
conditionString
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
watchEffect(() => {
|
||||
tree.setQuery(tree.buildConditionQueryString(tree.root));
|
||||
const conditionJson = tree.toJson();
|
||||
emit('update:modelValue', conditionJson);
|
||||
});
|
||||
|
||||
return {
|
||||
appDg,
|
||||
isSetted,
|
||||
show,
|
||||
showDg,
|
||||
onClosed,
|
||||
tree,
|
||||
conditionString,
|
||||
btnDisable
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
202
frontend/src/components/right/DataMapping.vue
Normal file
202
frontend/src/components/right/DataMapping.vue
Normal file
@@ -0,0 +1,202 @@
|
||||
<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" :disable="btnDisable"
|
||||
@click="() => { dgIsShow = true }">クリックで設定</q-btn>
|
||||
</q-card-actions>
|
||||
<q-card-section class="text-caption">
|
||||
<div v-if="mappingObjectsInputDisplay && mappingObjectsInputDisplay.length > 0">
|
||||
<div v-for="(item) in mappingObjectsInputDisplay" :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">
|
||||
<div class="row q-col-gutter-x-xs flex-center">
|
||||
<div class="col-5">
|
||||
<div class="q-mx-xs">From</div>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<div class="q-mx-xs">To</div>
|
||||
</div>
|
||||
<div class="col-1"><q-btn flat round dense icon="add" size="sm" @click="addMappingObject" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="q-my-sm" v-for="(item, index) in mappingProps" :key="item.id">
|
||||
<div class="row q-col-gutter-x-xs flex-center">
|
||||
<div class="col-5">
|
||||
<ConditionObject v-model="item.from" />
|
||||
</div>
|
||||
<div class="col-1">
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<q-field v-model="item.vName" type="text" outlined dense>
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" class="cursor-pointer"
|
||||
@click="() => { mappingProps[index].to.isDialogVisible = true }" />
|
||||
</template>
|
||||
<template v-slot:control>
|
||||
<div class="self-center full-width no-outline" tabindex="0"
|
||||
v-if="item.to.app?.name && item.to.fields?.length > 0 && item.to.fields[0].label">
|
||||
{{ `${item.to.app?.name} : ${item.to.fields[0].label}` }}
|
||||
</div>
|
||||
</template>
|
||||
</q-field>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<q-btn flat round dense icon="delete" size="sm" @click="() => deleteMappingObject(index)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<show-dialog v-model:visible="mappingProps[index].to.isDialogVisible" name="フィールド一覧"
|
||||
@close="closeToDg" ref="fieldDlg">
|
||||
<FieldSelect v-if="onlySourceSelect" ref="fieldDlg" name="フィールド" :appId="sourceAppId" not_page
|
||||
:selectedFields="mappingProps[index].to.fields"
|
||||
:updateSelects="(fields) => { mappingProps[index].to.fields = fields; mappingProps[index].to.app = sourceApp }">
|
||||
</FieldSelect>
|
||||
<AppFieldSelectBox v-else v-model:selectedField="mappingProps[index].to" />
|
||||
</show-dialog>
|
||||
</div>
|
||||
</div>
|
||||
</show-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { computed, defineComponent, reactive, ref, watchEffect } from 'vue';
|
||||
import ConditionObject from '../ConditionEditor/ConditionObject.vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
|
||||
import FieldSelect from '../FieldSelect.vue';
|
||||
import IAppFields from './AppFieldSelect.vue';
|
||||
|
||||
type Props = {
|
||||
props?: {
|
||||
name: string;
|
||||
modelValue?: {
|
||||
app: {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
type ValueType = {
|
||||
id: string;
|
||||
from: object;
|
||||
to: typeof IAppFields & {
|
||||
isDialogVisible: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const defaultMappingProp = () => ({ id: uuidv4(), to: { app: {}, fields: [], isDialogVisible: false } });
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DataMapping',
|
||||
inheritAttrs: false,
|
||||
components: {
|
||||
ShowDialog,
|
||||
ConditionObject,
|
||||
AppFieldSelectBox,
|
||||
FieldSelect
|
||||
},
|
||||
props: {
|
||||
context: {
|
||||
type: Array<Props>,
|
||||
default: '',
|
||||
},
|
||||
displayName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: Object as () => ValueType[],
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
onlySourceSelect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const closeDg = () => {
|
||||
emit('update:modelValue', mappingProps
|
||||
);
|
||||
}
|
||||
|
||||
const closeToDg = () => {
|
||||
emit('update:modelValue', mappingProps
|
||||
);
|
||||
}
|
||||
|
||||
const mappingProps: ValueType[] = props.modelValue
|
||||
? props.modelValue
|
||||
: reactive([defaultMappingProp()]);
|
||||
|
||||
|
||||
const deleteMappingObject = (index: number) => mappingProps.length === 1
|
||||
? mappingProps.splice(0, mappingProps.length, defaultMappingProp())
|
||||
: mappingProps.splice(index, 1);
|
||||
|
||||
const mappingObjectsInputDisplay = computed(() =>
|
||||
mappingProps ?
|
||||
mappingProps
|
||||
.filter(item => item.from?.name && item.to.fields?.length > 0)
|
||||
.map(item => {
|
||||
const name = typeof item.from?.name === 'string'
|
||||
? item.from.name
|
||||
: item.from?.name.name;
|
||||
return `[${name}] - (${item.to.app?.name} : ${item.to.fields[0].label})`;
|
||||
})
|
||||
: []
|
||||
);
|
||||
|
||||
const source = props.context.find(element => element?.props?.name === 'sources')
|
||||
|
||||
const sourceApp = computed(() => source?.props?.modelValue?.app);
|
||||
|
||||
const sourceAppId = computed(() => sourceApp.value?.id);
|
||||
|
||||
const btnDisable = computed(() => props.onlySourceSelect ? !(source?.props?.modelValue?.app?.id) : false);
|
||||
|
||||
//集計処理方法
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', mappingProps);
|
||||
});
|
||||
return {
|
||||
uuidv4,
|
||||
dgIsShow: ref(false),
|
||||
closeDg,
|
||||
toDgIsShow: ref(false),
|
||||
closeToDg,
|
||||
mappingProps,
|
||||
addMappingObject: () => mappingProps.push(defaultMappingProp()),
|
||||
deleteMappingObject,
|
||||
mappingObjectsInputDisplay,
|
||||
sourceApp,
|
||||
sourceAppId,
|
||||
btnDisable
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
229
frontend/src/components/right/DataProcessing.vue
Normal file
229
frontend/src/components/right/DataProcessing.vue
Normal 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>
|
||||
@@ -1,10 +1,7 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-input :label="displayName" v-model="inputValue" label-color="primary"
|
||||
:placeholder="placeholder" stack-label
|
||||
:rules="rulesExp"
|
||||
:maxlength="maxLength"
|
||||
>
|
||||
<q-input :label="displayName" v-model="inputValue" label-color="primary" :placeholder="placeholder" stack-label
|
||||
:rules="rulesExp" :maxlength="maxLength">
|
||||
<template v-slot:append v-if="hint !== ''">
|
||||
<q-icon name="help" size="22px" color="blue-8">
|
||||
<q-tooltip class="bg-yellow-2 text-black shadow-4" anchor="bottom right">
|
||||
@@ -18,7 +15,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { kMaxLength } from 'buffer';
|
||||
import { defineComponent, ref, watchEffect } from 'vue';
|
||||
import { defineComponent, ref, watchEffect, computed } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'InputText',
|
||||
@@ -40,27 +37,50 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
maxLength:{
|
||||
maxLength: {
|
||||
type: Number,
|
||||
default:undefined
|
||||
default: undefined
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules:{
|
||||
type:String,
|
||||
default:undefined
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
// type: Any,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const inputValue = ref(props.modelValue);
|
||||
const rulesExp = props.rules===undefined?null : eval(props.rules);
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', inputValue.value);
|
||||
const inputValue = computed({
|
||||
get: () => {
|
||||
if (props.modelValue !== null && typeof props.modelValue === 'object' && 'name' in props.modelValue) {
|
||||
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 {
|
||||
inputValue,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<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>
|
||||
</template>
|
||||
@@ -21,6 +21,9 @@ import ConditionInput from '../right/ConditionInput.vue';
|
||||
import EventSetter from '../right/EventSetter.vue';
|
||||
import ColorPicker from './ColorPicker.vue';
|
||||
import NumInput from './NumInput.vue';
|
||||
import DataProcessing from './DataProcessing.vue';
|
||||
import DataMapping from './DataMapping.vue';
|
||||
import AppSelect from './AppSelect.vue';
|
||||
import { IActionNode,IActionProperty,IProp } from 'src/types/ActionTypes';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -35,7 +38,10 @@ export default defineComponent({
|
||||
ConditionInput,
|
||||
EventSetter,
|
||||
ColorPicker,
|
||||
NumInput
|
||||
NumInput,
|
||||
DataProcessing,
|
||||
DataMapping,
|
||||
AppSelect
|
||||
},
|
||||
props: {
|
||||
nodeProps: {
|
||||
@@ -50,7 +56,7 @@ export default defineComponent({
|
||||
setup(props, context) {
|
||||
const properties=ref(props.nodeProps);
|
||||
const connectProps=(props:IProp)=>{
|
||||
const connProps:any={};
|
||||
const connProps:any={context:properties};
|
||||
if(props && "connectProps" in props && props.connectProps!=undefined){
|
||||
for(let connProp of props.connectProps){
|
||||
let targetProp = properties.value.find((prop)=>prop.props.name===connProp.propName);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,ref,watchEffect } from 'vue';
|
||||
import { defineComponent,ref,watchEffect,computed } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SelectBox',
|
||||
@@ -23,20 +24,27 @@ export default defineComponent({
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
selectType:{
|
||||
type:String,
|
||||
default:'',
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const selectedValue = ref(props.modelValue);
|
||||
|
||||
const multiple = computed(()=>{
|
||||
return props.selectType==='multiple'
|
||||
});
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedValue.value);
|
||||
});
|
||||
|
||||
return {
|
||||
selectedValue
|
||||
selectedValue,
|
||||
multiple
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -20,12 +20,13 @@
|
||||
</div>
|
||||
</q-drawer>
|
||||
</div>
|
||||
<q-btn flat dense round :icon="drawerLeft ? 'keyboard_double_arrow_left' : 'keyboard_double_arrow_right'"
|
||||
:style="[drawerLeft ? { 'left': '300px' } : { 'left': '0px' }]" @click="drawerLeft = !drawerLeft"
|
||||
class="expand" />
|
||||
<div class="q-pa-md q-gutter-sm" :style="{ minWidth: minPanelWidth }">
|
||||
<div class="flowchart" v-if="store.currentFlow" :style="[drawerLeft ? { paddingLeft: '300px' } : {}]">
|
||||
<node-item v-if="rootNode !== undefined" :key="rootNode.id" :isSelected="rootNode === store.activeNode"
|
||||
<q-btn flat dense round
|
||||
:icon="drawerLeft?'keyboard_double_arrow_left':'keyboard_double_arrow_right'"
|
||||
:style="[drawerLeft?{'left':'300px'}:{'left':'0px'}]"
|
||||
@click="drawerLeft=!drawerLeft" class="expand" />
|
||||
<div class="q-pa-md q-gutter-sm" :style="{minWidth: minPanelWidth}">
|
||||
<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"
|
||||
@deleteNode="onDeleteNode" @deleteAllNextNodes="onDeleteAllNextNodes" @copyFlow="onCopyFlow"></node-item>
|
||||
</div>
|
||||
@@ -34,13 +35,13 @@
|
||||
</q-layout>
|
||||
<ShowDialog v-model:visible="showAddAction" name="アクション" @close="closeDg" min-width="500px" min-height="500px">
|
||||
<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>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</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>
|
||||
|
||||
</q-page>
|
||||
@@ -76,20 +77,20 @@ const prevNodeIfo = ref({
|
||||
// const refFlow = ref<ActionFlow|null>(null);
|
||||
const showAddAction = ref(false);
|
||||
const drawerRight = ref(false);
|
||||
const filter = ref("");
|
||||
const filter=ref("");
|
||||
const model = ref("");
|
||||
const addActionNode = (action: IActionNode) => {
|
||||
// refFlow.value?.actionNodes.push(action);
|
||||
store.currentFlow?.actionNodes.push(action);
|
||||
}
|
||||
const rootNode = computed(() => {
|
||||
const rootNode = computed(()=>{
|
||||
return store.currentFlow?.getRoot();
|
||||
});
|
||||
const minPanelWidth = computed(() => {
|
||||
const minPanelWidth=computed(()=>{
|
||||
const root = store.currentFlow?.getRoot();
|
||||
if (store.currentFlow && root) {
|
||||
if(store.currentFlow && root){
|
||||
return store.currentFlow?.getColumns(root) * 300 + 'px';
|
||||
} else {
|
||||
}else{
|
||||
return "300px";
|
||||
}
|
||||
});
|
||||
@@ -148,7 +149,7 @@ const closeDg = (val: any) => {
|
||||
*/
|
||||
const onCopyFlow = () => {
|
||||
if (navigator.clipboard) {
|
||||
const jsonData = JSON.stringify(store.currentFlow);
|
||||
const jsonData =JSON.stringify(store.currentFlow) ;
|
||||
navigator.clipboard.writeText(jsonData).then(() => {
|
||||
console.log('Text successfully copied to clipboard');
|
||||
},
|
||||
@@ -194,22 +195,7 @@ const onDeploy = async () => {
|
||||
|
||||
const onSaveFlow = async () => {
|
||||
const targetFlow = store.selectedFlow;
|
||||
|
||||
const deleteFlows = () => {
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
caption: "通知",
|
||||
message: `削除 ${store.deleteFlowIds.length} イベント`
|
||||
});
|
||||
store.deletebackendFlow();
|
||||
}
|
||||
|
||||
if (targetFlow === undefined) {
|
||||
if (store.deleteFlowIds.length > 0) {
|
||||
deleteFlows();
|
||||
return;
|
||||
}
|
||||
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
caption: "エラー",
|
||||
@@ -218,9 +204,6 @@ const onSaveFlow = async () => {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (store.deleteFlowIds.length > 0) {
|
||||
deleteFlows();
|
||||
}
|
||||
saveLoading.value = true;
|
||||
await store.saveFlow(targetFlow);
|
||||
saveLoading.value = false;
|
||||
@@ -282,8 +265,7 @@ onMounted(() => {
|
||||
top: 50px;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.expand {
|
||||
.expand{
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
top: 50%;
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<q-btn :label="model+'選択'" color="primary" @click="showDg()" />
|
||||
<show-dialog v-model:visible="show" :name="model" @close="closeDg" width="400px">
|
||||
<template v-if="model=='アプリ'">
|
||||
<app-select ref="appDg" :name="model" type="single"></app-select>
|
||||
<app-select-box ref="appDg" :name="model" type="single"></app-select-box>
|
||||
</template>
|
||||
<template v-if="model=='フィールド'">
|
||||
<field-select ref="appDg" :name="model" type="multiple" :appId="1"></field-select>
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import ShowDialog from 'components/ShowDialog.vue';
|
||||
import AppSelect from 'components/AppSelect.vue';
|
||||
import AppSelectBox from 'components/AppSelectBox.vue';
|
||||
import FieldSelect from 'components/FieldSelect.vue';
|
||||
import ActionSelect from 'components/ActionSelect.vue';
|
||||
import { ref } from 'vue'
|
||||
|
||||
@@ -12,7 +12,6 @@ export interface FlowEditorState {
|
||||
eventTree: KintoneEventManager;
|
||||
selectedEvent: IKintoneEvent | undefined;
|
||||
expandedScreen: any[];
|
||||
deleteFlowIds: string[];
|
||||
}
|
||||
const flowCtrl = new FlowCtrl();
|
||||
const eventTree = new KintoneEventManager();
|
||||
@@ -26,7 +25,6 @@ export const useFlowEditorStore = defineStore('flowEditor', {
|
||||
eventTree: eventTree,
|
||||
selectedEvent: undefined,
|
||||
expandedScreen: [],
|
||||
deleteFlowIds: [],
|
||||
}),
|
||||
getters: {
|
||||
/**
|
||||
@@ -56,11 +54,6 @@ export const useFlowEditorStore = defineStore('flowEditor', {
|
||||
};
|
||||
},
|
||||
|
||||
findEventFrist(state) {
|
||||
return () => {
|
||||
return state.eventTree.findEventFrist();
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
@@ -76,12 +69,6 @@ export const useFlowEditorStore = defineStore('flowEditor', {
|
||||
setApp(app: AppInfo) {
|
||||
this.appInfo = app;
|
||||
},
|
||||
addDeleteFlowId(flowId: string) {
|
||||
this.deleteFlowIds.push(flowId);
|
||||
},
|
||||
clearDeleteFlowIds() {
|
||||
this.deleteFlowIds = [];
|
||||
},
|
||||
/**
|
||||
* DBからフルーを保存する
|
||||
* @returns
|
||||
@@ -125,24 +112,15 @@ export const useFlowEditorStore = defineStore('flowEditor', {
|
||||
}
|
||||
},
|
||||
|
||||
async deletebackendFlow() {
|
||||
const deletePromises = Object.values(this.deleteFlowIds).map((flowId) =>
|
||||
flowCtrl.DeleteFlow(flowId)
|
||||
);
|
||||
await Promise.all(deletePromises);
|
||||
this.clearDeleteFlowIds();
|
||||
},
|
||||
|
||||
deleteStoreEventAndFlow(event: IKintoneEvent) {
|
||||
deleteEvent(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);
|
||||
flowCtrl.DeleteFlow(flow.id)
|
||||
eventTree.deleteEvent(event, store);
|
||||
if(this.flows){
|
||||
this.flows = this.flows.filter((f) => f.id !== flow.id);
|
||||
|
||||
@@ -45,7 +45,16 @@ export interface IActionProperty {
|
||||
export interface IActionVariable{
|
||||
actionName: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[]{
|
||||
let varNames:IActionVariable[]=[];
|
||||
if(prevNode.varName!==undefined && prevNode.varName.modelValue){
|
||||
|
||||
if(prevNode.varName.modelValue ==='object'){
|
||||
console.log(prevNode);
|
||||
|
||||
}
|
||||
|
||||
varNames.unshift({
|
||||
actionName:prevNode.name,
|
||||
displayName:prevNode.varName.displayName,
|
||||
|
||||
@@ -74,6 +74,11 @@ export class GroupNode implements INode {
|
||||
|
||||
}
|
||||
|
||||
export type OperatorListItem = {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
// 条件式ノード
|
||||
export class ConditionNode implements INode {
|
||||
index: number;
|
||||
@@ -83,13 +88,13 @@ export class ConditionNode implements INode {
|
||||
return this.parent.logicalOperator;
|
||||
};
|
||||
object: any; // 比較元
|
||||
operator: Operator; // 比較子
|
||||
operator: Operator | OperatorListItem; // 比較子
|
||||
value: any;
|
||||
get header():string{
|
||||
return 'generic';
|
||||
}
|
||||
|
||||
constructor(object: any, operator: Operator, value: any, parent: GroupNode) {
|
||||
constructor(object: any, operator: Operator | OperatorListItem, value: any, parent: GroupNode) {
|
||||
this.index=0;
|
||||
this.type = NodeType.Condition;
|
||||
this.object = object;
|
||||
@@ -113,10 +118,12 @@ export class ConditionNode implements INode {
|
||||
export class ConditionTree {
|
||||
root: GroupNode;
|
||||
maxIndex:number;
|
||||
queryString:string;
|
||||
|
||||
constructor() {
|
||||
this.maxIndex=0;
|
||||
this.root = new GroupNode(LogicalOperator.AND, null);
|
||||
this.queryString='';
|
||||
}
|
||||
|
||||
// ノード追加
|
||||
@@ -198,12 +205,49 @@ export class ConditionTree {
|
||||
if(value && typeof value ==='object' && ('label' in value)){
|
||||
value =condNode.value.label;
|
||||
}
|
||||
return `${condNode.object.name} ${condNode.operator} '${value}'`;
|
||||
return `${typeof condNode.object.name === 'object' ? condNode.object.name.name : condNode.object.name} ${typeof condNode.operator === 'object' ? condNode.operator.label : condNode.operator} '${value}'`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildConditionQueryString(node:INode){
|
||||
if (node.type !== NodeType.Condition) {
|
||||
let conditionString = '';
|
||||
if(node.type !== NodeType.Root){
|
||||
conditionString = '(';
|
||||
}
|
||||
|
||||
const groupNode = node as GroupNode;
|
||||
for (let i = 0; i < groupNode.children.length; i++) {
|
||||
const childConditionString = this.buildConditionQueryString(groupNode.children[i]);
|
||||
if (childConditionString !== '') {
|
||||
conditionString += childConditionString;
|
||||
if (i < groupNode.children.length - 1) {
|
||||
conditionString += ` ${groupNode.logicalOperator.toLowerCase()} `;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(node.type !== NodeType.Root){
|
||||
conditionString += ')';
|
||||
}
|
||||
return conditionString;
|
||||
} else {
|
||||
const condNode=node as ConditionNode;
|
||||
if (condNode.object && condNode.operator ) {
|
||||
let value=condNode.value;
|
||||
if(value && typeof value ==='object' && ('label' in value)){
|
||||
value =condNode.value.label;
|
||||
}
|
||||
return `${condNode.object.code} ${typeof condNode.operator === 'object' ? condNode.operator.value : condNode.operator} "${value}"`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param node ノード移動
|
||||
@@ -325,7 +369,7 @@ export class ConditionTree {
|
||||
}
|
||||
|
||||
toJson():string{
|
||||
return JSON.stringify(this.root, (key, value) => {
|
||||
return JSON.stringify({queryString :this.queryString, ...this.root}, (key, value) => {
|
||||
if (key === 'parent') {
|
||||
return value ? value.type : null;
|
||||
}
|
||||
@@ -333,4 +377,7 @@ export class ConditionTree {
|
||||
});
|
||||
}
|
||||
|
||||
setQuery(queryString:string){
|
||||
this.queryString=queryString;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,6 @@ export class KintoneEventManager {
|
||||
this.screens = this.getKintoneEvents();
|
||||
for (const flow of flows) {
|
||||
const eventId = flow.getRoot()?.name;
|
||||
console.log('eventId:', eventId);
|
||||
if (eventId !== undefined) {
|
||||
const eventNode = this.findEventById(eventId);
|
||||
if (eventNode !== null && eventNode.header === 'EVENT') {
|
||||
@@ -101,7 +100,6 @@ export class KintoneEventManager {
|
||||
(eventNode.header === 'EVENTGROUP' || eventNode.header === 'CHANGE')
|
||||
) {
|
||||
const groupEvent = eventNode as kintoneEventGroup;
|
||||
console.log('label:', flow.getRoot()?.subTitle);
|
||||
|
||||
const newEvent = {
|
||||
label: flow.getRoot()?.subTitle || '',
|
||||
@@ -112,11 +110,6 @@ export class KintoneEventManager {
|
||||
flowData: flow,
|
||||
};
|
||||
|
||||
// new kintoneEvent(
|
||||
// flow.getRoot()?.subTitle || '',
|
||||
// eventId,
|
||||
// groupEvent.parentId
|
||||
// );
|
||||
groupEvent.events.push(newEvent);
|
||||
}
|
||||
}
|
||||
@@ -124,10 +117,6 @@ export class KintoneEventManager {
|
||||
}
|
||||
}
|
||||
|
||||
public findEventFrist() {
|
||||
return this.findEventById('app.record.create.show') as IKintoneEvent;
|
||||
}
|
||||
|
||||
public findEventById(eventId: string): IKintoneEventNode | null {
|
||||
const screen = this.findScreen(eventId);
|
||||
if (screen) {
|
||||
|
||||
2
plugin/kintone-addins/.env.dev
Normal file
2
plugin/kintone-addins/.env.dev
Normal file
@@ -0,0 +1,2 @@
|
||||
VITE_SOURCE_MAP = inline
|
||||
VITE_PORT = 4173
|
||||
2
plugin/kintone-addins/.env.production
Normal file
2
plugin/kintone-addins/.env.production
Normal file
@@ -0,0 +1,2 @@
|
||||
VITE_SOURCE_MAP = false
|
||||
VITE_PORT = 4173
|
||||
@@ -4,19 +4,28 @@
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "tsc && set \"SOURCE_MAP=true\" && vite build && vite preview",
|
||||
"build": "tsc && vite build && xcopy dist\\*.js ..\\..\\backend\\Temp\\ /E /I /Y",
|
||||
"build:dev":"tsc && set \"SOURCE_MAP=true\" && vite build && xcopy dist\\*.js ..\\..\\backend\\Temp\\ /E /I /Y",
|
||||
"preview": "vite preview",
|
||||
"ngrok":"ngrok http http://localhost:4173/",
|
||||
"vite":"vite dev"
|
||||
"dev": "run-p watch server ngrok",
|
||||
"watch": "vite build --watch --mode dev",
|
||||
"server": "vite dev --mode dev",
|
||||
"ngrok": "ngrok http 4173",
|
||||
"build": "run-s b:production copy:windows",
|
||||
"build:dev": "run-s b:dev copy:windows",
|
||||
"build:linux": "run-s b:production copy:linux",
|
||||
"build:linux-dev": "run-s b:dev copy:linux",
|
||||
"b:production": "vite build --mode production",
|
||||
"b:dev": "vite build --mode dev",
|
||||
"copy:windows": "xcopy dist\\*.js ..\\..\\backend\\Temp\\ /E /I /Y",
|
||||
"copy:linux": "cp -ur dist/*.js ../../backend/Temp"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jquery": "^3.5.24",
|
||||
"@types/node": "^20.8.9",
|
||||
"npm-run-all2": "^6.2.0",
|
||||
"sass": "^1.69.5",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.4.5"
|
||||
"vite": "^4.4.5",
|
||||
"vite-plugin-checker": "^0.6.4",
|
||||
"vite-plugin-lib-inject-css": "^2.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"jquery": "^3.7.1"
|
||||
|
||||
24
plugin/kintone-addins/src/actions/auto-numbering.css
Normal file
24
plugin/kintone-addins/src/actions/auto-numbering.css
Normal file
@@ -0,0 +1,24 @@
|
||||
.alc-button-normal {
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
padding: 0 16px;
|
||||
margin-left: 16px;
|
||||
margin-top: 8px;
|
||||
min-width: 100px;
|
||||
outline: none;
|
||||
border: 1px solid #e3e7e8;
|
||||
background-color: #f7f9fa;
|
||||
box-shadow: 1px 1px 1px #fff inset;
|
||||
color: #3498db;
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
}
|
||||
.alc-button-normal:hover {
|
||||
background-color: #c8d6dd;
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.alc-button-normal:active {
|
||||
color: #f7f9fa;
|
||||
background-color: #54b8eb;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
import { actionAddins } from ".";
|
||||
import { IField, IAction,IActionResult, IActionNode, IActionProperty, IContext } from "../types/ActionTypes";
|
||||
import { Formatter } from "../util/format";
|
||||
import "./auto-numbering.css";
|
||||
|
||||
declare global {
|
||||
interface Window { $format: any; }
|
||||
@@ -84,6 +85,7 @@ export class AutoNumbering implements IAction{
|
||||
|
||||
execEval(match:string,expr:string):string{
|
||||
console.log(match);
|
||||
// @ts-ignore
|
||||
return eval(expr);
|
||||
}
|
||||
|
||||
|
||||
0
plugin/kintone-addins/src/actions/button-add.css
Normal file
0
plugin/kintone-addins/src/actions/button-add.css
Normal file
@@ -2,6 +2,8 @@
|
||||
import { actionAddins } from ".";
|
||||
import $ from 'jquery';
|
||||
import { IAction, IActionProperty, IActionNode, IActionResult } from "../types/ActionTypes";
|
||||
import "./button-add.css";
|
||||
|
||||
/**
|
||||
* ボタン配置属性定義
|
||||
*/
|
||||
@@ -51,30 +53,7 @@ export class ButtonAddAction implements IAction {
|
||||
if(!menuSpace) return result;
|
||||
if($("style#alc-button-add").length===0){
|
||||
const css=`
|
||||
.alc-button-normal {
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
padding: 0 16px;
|
||||
margin-left: 16px;
|
||||
margin-top: 8px;
|
||||
min-width: 100px;
|
||||
outline: none;
|
||||
border: 1px solid #e3e7e8;
|
||||
background-color: #f7f9fa;
|
||||
box-shadow: 1px 1px 1px #fff inset;
|
||||
color: #3498db;
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
}
|
||||
.alc-button-normal:hover {
|
||||
background-color: #c8d6dd;
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.alc-button-normal:active {
|
||||
color: #f7f9fa;
|
||||
background-color: #54b8eb;
|
||||
}`;
|
||||
`;
|
||||
const style = $("<style id='alc-button-add'>/<style>");
|
||||
style.text(css);
|
||||
$("head").append(style);
|
||||
|
||||
182
plugin/kintone-addins/src/actions/data-mapping.ts
Normal file
182
plugin/kintone-addins/src/actions/data-mapping.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
import {
|
||||
IAction,
|
||||
IActionResult,
|
||||
IActionNode,
|
||||
IActionProperty,
|
||||
IContext,
|
||||
} from "../types/ActionTypes";
|
||||
import { actionAddins } from ".";
|
||||
|
||||
export type IApp = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
export type IField = {
|
||||
name: string;
|
||||
code: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
export type IAppFields = {
|
||||
app?: IApp;
|
||||
fields: IField[];
|
||||
};
|
||||
|
||||
type ValueType = {
|
||||
id: string;
|
||||
from: {
|
||||
objectType: "variable" | "field";
|
||||
name: { name: string };
|
||||
code: string;
|
||||
};
|
||||
to: IAppFields & {
|
||||
isDialogVisible: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
type Props = { app: IApp; field: ValueType[] };
|
||||
|
||||
export class DataMappingAction implements IAction {
|
||||
name: string;
|
||||
actionProps: IActionProperty[];
|
||||
dataMappingProps: Props;
|
||||
constructor() {
|
||||
this.name = "DataMapping";
|
||||
this.actionProps = [];
|
||||
this.dataMappingProps = {} as Props;
|
||||
this.register();
|
||||
}
|
||||
|
||||
async process(
|
||||
prop: IActionNode,
|
||||
event: any,
|
||||
context: IContext
|
||||
): Promise<IActionResult> {
|
||||
this.initActionProps(prop);
|
||||
this.initTypedActionProps();
|
||||
let result = {
|
||||
canNext: true,
|
||||
result: "",
|
||||
} as IActionResult;
|
||||
try {
|
||||
for (const item of this.dataMappingProps.field) {
|
||||
if (item.from.objectType === "variable") {
|
||||
if (
|
||||
item.from.name.name &&
|
||||
item.to.app &&
|
||||
item.to.fields &&
|
||||
item.to.fields.length > 0
|
||||
) {
|
||||
const value = getValueByPath(
|
||||
context.variables,
|
||||
item.from.name.name
|
||||
);
|
||||
if (value) {
|
||||
await kintone.api(
|
||||
kintone.api.url("/k/v1/record.json", true),
|
||||
"POST",
|
||||
{
|
||||
app: item.to.app.id,
|
||||
record: {
|
||||
[item.to.fields[0].code]: {
|
||||
value: value,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (item.from.objectType === "field") {
|
||||
if (
|
||||
item.from.code &&
|
||||
item.to.app &&
|
||||
item.to.fields &&
|
||||
item.to.fields.length > 0
|
||||
) {
|
||||
const value = await selectData(
|
||||
item.to.app.id,
|
||||
item.to.fields[0].code
|
||||
);
|
||||
if (value && value.type === context.record[item.from.code].type) {
|
||||
await kintone.api(
|
||||
kintone.api.url("/k/v1/records.json", true),
|
||||
"POST",
|
||||
{
|
||||
app: item.to.app.id,
|
||||
records: value.value.map((v) => ({
|
||||
[item.to.fields[0].code]: {
|
||||
value: v,
|
||||
},
|
||||
})),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("DataMappingAction error", error);
|
||||
result.canNext = false;
|
||||
}
|
||||
console.log("dataMappingProps", this.dataMappingProps);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private initActionProps(nodes: IActionNode) {
|
||||
this.actionProps = nodes.actionProps;
|
||||
}
|
||||
private initTypedActionProps() {
|
||||
for (const action of this.actionProps) {
|
||||
if (action.component === "DataMapping") {
|
||||
this.dataMappingProps.field = action.props.modelValue as ValueType[];
|
||||
} else if (action.component === "AppSelect") {
|
||||
this.dataMappingProps.app = action.props.modelValue.app as IApp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register(): void {
|
||||
actionAddins[this.name] = this;
|
||||
}
|
||||
}
|
||||
|
||||
new DataMappingAction();
|
||||
|
||||
const getValueByPath = (obj: any, path: string) => {
|
||||
return path.split(".").reduce((o, k) => (o || {})[k], obj);
|
||||
};
|
||||
|
||||
type Resp = { records: RespRecordType[] };
|
||||
|
||||
type RespRecordType = {
|
||||
[key: string]: {
|
||||
type: string;
|
||||
value: any;
|
||||
};
|
||||
};
|
||||
|
||||
type Result = {
|
||||
type: string;
|
||||
value: any[];
|
||||
};
|
||||
|
||||
const selectData = async (appid: string, field: string): Promise<Result> => {
|
||||
return kintone
|
||||
.api(kintone.api.url("/k/v1/records", true), "GET", {
|
||||
app: appid ?? kintone.app.getId(),
|
||||
fields: [field],
|
||||
})
|
||||
.then((resp: Resp) => {
|
||||
const result: Result = { type: "", value: [] };
|
||||
resp.records.forEach((element) => {
|
||||
for (const [key, value] of Object.entries(element)) {
|
||||
if (result.type === "") {
|
||||
result.type = value.type;
|
||||
}
|
||||
result.value.push(value.value);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
});
|
||||
};
|
||||
280
plugin/kintone-addins/src/actions/data-processing.ts
Normal file
280
plugin/kintone-addins/src/actions/data-processing.ts
Normal file
@@ -0,0 +1,280 @@
|
||||
import {
|
||||
IAction,
|
||||
IActionResult,
|
||||
IActionNode,
|
||||
IActionProperty,
|
||||
IContext,
|
||||
} from "../types/ActionTypes";
|
||||
import { actionAddins } from ".";
|
||||
|
||||
|
||||
type DataProcessingProps = {
|
||||
app: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
conditionsQuery: string;
|
||||
propcessing: {
|
||||
varRootName: string;
|
||||
fields: Field[];
|
||||
};
|
||||
};
|
||||
|
||||
type Field = {
|
||||
name: string;
|
||||
code: string;
|
||||
type: string;
|
||||
varName: string;
|
||||
operator: string;
|
||||
};
|
||||
|
||||
export class DataProcessingAction implements IAction {
|
||||
name: string;
|
||||
actionProps: IActionProperty[];
|
||||
dataProcessingProps: DataProcessingProps | null;
|
||||
constructor() {
|
||||
this.name = "データ処理";
|
||||
this.actionProps = [];
|
||||
this.dataProcessingProps = null;
|
||||
this.register();
|
||||
}
|
||||
|
||||
async process(
|
||||
nodes: IActionNode,event: any,context: IContext
|
||||
): Promise<IActionResult> {
|
||||
this.initActionProps(nodes);
|
||||
this.initTypedActionProps();
|
||||
let result = {
|
||||
canNext: true,
|
||||
result: "",
|
||||
} as IActionResult;
|
||||
try {
|
||||
if (!this.dataProcessingProps) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const data = await selectData(this.dataProcessingProps.conditionsQuery);
|
||||
console.log("data ", data);
|
||||
|
||||
context.variables[this.dataProcessingProps.propcessing.varRootName] =
|
||||
this.dataProcessingProps.propcessing.fields.reduce((acc, f) => {
|
||||
const v = calc(f, data);
|
||||
if (v) {
|
||||
acc[f.varName] = calc(f, data);
|
||||
}
|
||||
return acc;
|
||||
}, {} as Var);
|
||||
|
||||
console.log("context ", context);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
event.error=error;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
register(): void {
|
||||
actionAddins[this.name] = this;
|
||||
}
|
||||
|
||||
private initActionProps(nodes: IActionNode) {
|
||||
this.actionProps = nodes.actionProps;
|
||||
}
|
||||
|
||||
private initTypedActionProps() {
|
||||
this.dataProcessingProps = {
|
||||
app: {
|
||||
id: "",
|
||||
name: "",
|
||||
},
|
||||
conditionsQuery: "",
|
||||
propcessing: {
|
||||
varRootName: "",
|
||||
fields: [],
|
||||
},
|
||||
};
|
||||
for (const action of this.actionProps) {
|
||||
if (action.component === "AppFieldSelect") {
|
||||
this.dataProcessingProps.app.id = action.props.modelValue.app.id;
|
||||
this.dataProcessingProps.app.name = action.props.modelValue.app.name;
|
||||
} else if (action.component === "DataProcessing") {
|
||||
this.dataProcessingProps.propcessing.varRootName =
|
||||
action.props.modelValue.name;
|
||||
for (const f of action.props.modelValue.vars) {
|
||||
this.dataProcessingProps.propcessing.fields.push({
|
||||
name: f.field.name,
|
||||
code: f.field.code,
|
||||
type: f.field.type,
|
||||
varName: f.vName,
|
||||
operator: f.logicalOperator.operator,
|
||||
});
|
||||
}
|
||||
} else if (action.component === "ConditionInput") {
|
||||
this.dataProcessingProps.conditionsQuery = JSON.parse(
|
||||
action.props.modelValue
|
||||
).queryString;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new DataProcessingAction();
|
||||
|
||||
const selectData = async (query?: string) => {
|
||||
return kintone
|
||||
.api(kintone.api.url("/k/v1/records", true), "GET", {
|
||||
app: kintone.app.getId(),
|
||||
query: query,
|
||||
})
|
||||
.then((resp: Resp) => {
|
||||
const result: Result = {};
|
||||
resp.records.forEach((element) => {
|
||||
for (const [key, value] of Object.entries(element)) {
|
||||
if (!result[key]) {
|
||||
result[key] = { type: value.type, value: [] };
|
||||
}
|
||||
result[key].value.push(value.value);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
type Resp = { records: RespRecordType[] };
|
||||
|
||||
type RespRecordType = {
|
||||
[key: string]: {
|
||||
type: string;
|
||||
value: any;
|
||||
};
|
||||
};
|
||||
|
||||
type Result = {
|
||||
[key: string]: {
|
||||
type: string;
|
||||
value: any[];
|
||||
};
|
||||
};
|
||||
|
||||
type Var = {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
const ERROR_TYPE = "ERROR_TYPE";
|
||||
|
||||
const calc = (field: Field, result: Result) => {
|
||||
const type = typeCheck(field.type);
|
||||
if (!type) {
|
||||
return ERROR_TYPE;
|
||||
}
|
||||
|
||||
const fun =
|
||||
calcFunc[`${type}_${Operator[field.operator as keyof typeof Operator]}`];
|
||||
if (!fun) {
|
||||
return ERROR_TYPE;
|
||||
}
|
||||
const values = result[field.code].value;
|
||||
if (!values) {
|
||||
return null;
|
||||
}
|
||||
return fun(values);
|
||||
};
|
||||
|
||||
const typeCheck = (type: string) => {
|
||||
switch (type) {
|
||||
case "RECORD_NUMBER":
|
||||
case "NUMBER":
|
||||
return CalcType.NUMBER;
|
||||
case "SINGLE_LINE_TEXT":
|
||||
case "MULTI_LINE_TEXT":
|
||||
case "RICH_TEXT":
|
||||
return CalcType.STRING;
|
||||
case "DATE":
|
||||
return CalcType.DATE;
|
||||
case "TIME":
|
||||
return CalcType.TIME;
|
||||
case "DATETIME":
|
||||
case "UPDATED_TIME":
|
||||
return CalcType.DATETIME;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
enum Operator {
|
||||
SUM = "SUM",
|
||||
AVG = "AVG",
|
||||
MAX = "MAX",
|
||||
MIN = "MIN",
|
||||
COUNT = "COUNT",
|
||||
FIRST = "FIRST"
|
||||
}
|
||||
|
||||
enum CalcType {
|
||||
NUMBER = "number",
|
||||
STRING = "string",
|
||||
DATE = "date",
|
||||
TIME = "time",
|
||||
DATETIME = "datetime",
|
||||
}
|
||||
|
||||
const calcFunc: Record<string, (value: string[]) => string | null> = {
|
||||
[`${CalcType.NUMBER}_${Operator.COUNT}`]: (value: string[]) =>
|
||||
value.length.toString(),
|
||||
[`${CalcType.STRING}_${Operator.COUNT}`]: (value: string[]) =>
|
||||
value.length.toString(),
|
||||
[`${CalcType.DATE}_${Operator.COUNT}`]: (value: string[]) =>
|
||||
value.length.toString(),
|
||||
[`${CalcType.TIME}_${Operator.COUNT}`]: (value: string[]) =>
|
||||
value.length.toString(),
|
||||
[`${CalcType.DATETIME}_${Operator.COUNT}`]: (value: string[]) =>
|
||||
value.length.toString(),
|
||||
|
||||
[`${CalcType.NUMBER}_${Operator.SUM}`]: (value: string[]) =>
|
||||
value.reduce((acc, v) => acc + Number(v), 0).toString(),
|
||||
[`${CalcType.NUMBER}_${Operator.AVG}`]: (value: string[]) =>
|
||||
(value.reduce((acc, v) => acc + Number(v), 0) / value.length).toString(),
|
||||
[`${CalcType.NUMBER}_${Operator.MAX}`]: (value: string[]) =>
|
||||
Math.max(...value.map(Number)).toString(),
|
||||
[`${CalcType.NUMBER}_${Operator.MIN}`]: (value: string[]) =>
|
||||
Math.min(...value.map(Number)).toString(),
|
||||
|
||||
[`${CalcType.STRING}_${Operator.SUM}`]: (value: string[]) => value.join(" "),
|
||||
|
||||
[`${CalcType.DATE}_${Operator.MAX}`]: (value: string[]) =>
|
||||
value.reduce((maxDate, currentDate) =>
|
||||
maxDate > currentDate ? maxDate : currentDate
|
||||
),
|
||||
|
||||
[`${CalcType.DATE}_${Operator.MIN}`]: (value: string[]) =>
|
||||
value.reduce((minDate, currentDate) =>
|
||||
minDate < currentDate ? minDate : currentDate
|
||||
),
|
||||
|
||||
[`${CalcType.TIME}_${Operator.MAX}`]: (value: string[]) =>
|
||||
value.reduce((maxTime, currentTime) =>
|
||||
maxTime > currentTime ? maxTime : currentTime
|
||||
),
|
||||
[`${CalcType.TIME}_${Operator.MIN}`]: (value: string[]) =>
|
||||
value.reduce((minTime, currentTime) =>
|
||||
minTime < currentTime ? minTime : currentTime
|
||||
),
|
||||
|
||||
[`${CalcType.DATETIME}_${Operator.MAX}`]: (value: string[]) =>
|
||||
value.reduce((maxDateTime, currentDateTime) =>
|
||||
new Date(maxDateTime) > new Date(currentDateTime)
|
||||
? maxDateTime
|
||||
: currentDateTime
|
||||
),
|
||||
|
||||
[`${CalcType.DATETIME}_${Operator.MIN}`]: (value: string[]) =>
|
||||
value.reduce((minDateTime, currentDateTime) =>
|
||||
new Date(minDateTime) < new Date(currentDateTime)
|
||||
? minDateTime
|
||||
: currentDateTime
|
||||
),
|
||||
[`${CalcType.STRING}_${Operator.FIRST}`]:(value: string[])=>{
|
||||
return value[0];
|
||||
}
|
||||
};
|
||||
@@ -6,6 +6,8 @@ import '../actions/field-shown';
|
||||
import '../actions/error-show';
|
||||
import '../actions/button-add';
|
||||
import '../actions/condition-action';
|
||||
import '../actions/data-processing';
|
||||
import '../actions/data-mapping';
|
||||
import { ActionFlow,IActionFlow, IActionResult,IContext } from "./ActionTypes";
|
||||
|
||||
export class ActionProcess{
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
|
||||
@@ -1,16 +1,32 @@
|
||||
// vite.config.js
|
||||
import { defineConfig } from 'vite'
|
||||
const sourcemap = process.env.SOURCE_MAP==='true';
|
||||
import { defineConfig, loadEnv } from "vite";
|
||||
import checker from "vite-plugin-checker";
|
||||
import { libInjectCss } from 'vite-plugin-lib-inject-css';
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
rollupOptions: {
|
||||
input: 'src/index.ts', // entry file
|
||||
output:{
|
||||
entryFileNames:'alc_runtime.js',
|
||||
// assetFileNames:'alc_kintone_style.css'
|
||||
}
|
||||
export default ({ mode }) => {
|
||||
process.env = { ...process.env, ...loadEnv(mode, process.cwd()) };
|
||||
|
||||
return defineConfig({
|
||||
plugins: [
|
||||
checker({
|
||||
typescript: true,
|
||||
}),
|
||||
libInjectCss(),
|
||||
],
|
||||
build: {
|
||||
cssCodeSplit: false,
|
||||
rollupOptions: {
|
||||
input: "src/index.ts", // entry file
|
||||
output: {
|
||||
entryFileNames: "alc_runtime.js",
|
||||
// assetFileNames:'alc_kintone_style.css'
|
||||
},
|
||||
},
|
||||
sourcemap: process.env.VITE_SOURCE_MAP,
|
||||
},
|
||||
sourcemap:sourcemap
|
||||
}
|
||||
})
|
||||
server: {
|
||||
port: process.env.VITE_PORT,
|
||||
// open: "/dist/alc_runtime.js",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user