Merged PR 83: 属性UI(プロパティ)にチェックルール設定追加

属性UI(プロパティ)にチェックルール設定追加
属性UIのJSON定義に下記のフィールドを追加しました。
1.required : boolean  入力必須かどうかを設定する
2.requiredMessage: string  未入力の場合表示するエラーメッセージを設定する
  (未設定の場合既定メッセージを表示する)
3. rules: [アロー関数]
     必須チェック以外に、入力範囲など制限したい場合下記のように指定する
     [val=>!!val ||'数値を入力してください',val=>val<=100 && val>=1 || '1-100の範囲内の数値を入力してください']

Related work items: #299
This commit is contained in:
Shohtetsu Ma
2024-08-22 09:06:17 +00:00
committed by Takuto Yoshida(タクト)
18 changed files with 468 additions and 231 deletions

View File

@@ -52,12 +52,12 @@ import { useQuasar } from 'quasar';
const tree = ref(props.conditionTree); const tree = ref(props.conditionTree);
const closeDg = (val:string) => { const closeDg = (val:string) => {
if (val == 'OK') { if (val == 'OK') {
if(tree.value.root.children.length===0){ // if(tree.value.root.children.length===0){
$q.notify({ // $q.notify({
type: 'negative', // type: 'negative',
message: `条件式を設定してください。` // message: `条件式を設定してください。`
}); // });
} // }
context.emit("update:conditionTree",tree.value); context.emit("update:conditionTree",tree.value);
} }
showflg.value=false; showflg.value=false;

View File

@@ -1,157 +1,165 @@
<template> <template>
<div class="q-my-md" v-bind="$attrs"> <div class="q-my-md" v-bind="$attrs">
<q-card flat> <q-field v-model="selectedField" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label
<q-card-section class="q-pa-none q-my-sm q-mr-md"> :rules="rulesExp" lazy-rules="ondemand" @clear="clear" ref="fieldRef">
<!-- <div class=" q-my-none ">App Field Select</div> --> <template v-slot:control>
<div class="row q-mb-xs"> {{ isSelected ? selectedField.app?.name : "(未選択)" }}
<div class="text-primary q-mb-xs text-caption">{{ $props.displayName }}</div> </template>
</div> <template v-slot:hint v-if="!isSelected">
<div class="row"> {{ placeholder }}
<div class="col"> </template>
<div class="q-mb-xs">{{ selectedField.app?.name || '未選択' }}</div> <template v-slot:append>
</div> <q-icon name="search" class="cursor-pointer" color="primary" @click="showDg" />
<div class="col-1"> </template>
<q-btn round flat size="sm" color="primary" icon="search" @click="showDg" /> </q-field>
</div> <div v-if="selectedField.fields && selectedField.fields.length > 0">
</div> <q-list bordered>
</q-card-section> <q-virtual-scroll style="max-height: 160px;" :items="selectedField.fields" separator v-slot="{ item, index }">
<q-separator /> <q-item :key="index" dense clickable>
<q-card-section class="q-pa-none q-ma-none"> <q-item-section>
<div style=""> <q-item-label>
<div v-if="selectedField.fields && selectedField.fields.length > 0"> {{ item.label }}
<q-list bordered> </q-item-label>
<q-virtual-scroll style="max-height: 160px;" :items="selectedField.fields" separator </q-item-section>
v-slot="{ item, index }"> <q-item-section side>
<q-item :key="index" dense clickable> <q-btn round flat size="sm" icon="clear" @click="removeField(index)" />
<q-item-section> </q-item-section>
<q-item-label> </q-item>
{{ item.label }} </q-virtual-scroll>
</q-item-label> </q-list>
</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">
<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="closeAFBox"> <show-dialog v-model:visible="show" name="フィールド一覧" @close="closeAFBox">
<AppFieldSelectBox v-model:selectedField="selectedField" :selectType="selectType" ref="afBox" :fieldTypes="fieldTypes"/> <AppFieldSelectBox v-model:selectedField="selectedField" :selectType="selectType" ref="afBox"
:fieldTypes="fieldTypes" />
</show-dialog> </show-dialog>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, watchEffect } from 'vue'; import { computed, defineComponent, ref, watchEffect } from 'vue';
import AppFieldSelectBox from '../AppFieldSelectBox.vue'; import AppFieldSelectBox from '../AppFieldSelectBox.vue';
import ShowDialog from '../ShowDialog.vue'; import ShowDialog from '../ShowDialog.vue';
import { useFlowEditorStore } from 'stores/flowEditor'; import { useFlowEditorStore } from 'stores/flowEditor';
export interface IApp { export interface IApp {
id: string, id: string,
name: string name: string
} }
export interface IField { export interface IField {
name: string, name: string,
code: string, code: string,
type: string, type: string,
label?:string label?: string
} }
export interface IAppFields { export interface IAppFields {
app?: IApp, app?: IApp,
fields: IField[] fields: IField[]
} }
export default defineComponent({ export default defineComponent({
inheritAttrs: false, inheritAttrs: false,
name: 'AppFieldSelect', name: 'AppFieldSelect2',
components: { components: {
ShowDialog, ShowDialog,
AppFieldSelectBox AppFieldSelectBox
},
props: {
displayName: {
type: String,
default: '',
}, },
props: { name: {
displayName: { type: String,
type: String, default: '',
default: '',
},
name: {
type: String,
default: '',
},
placeholder: {
type: String,
default: '',
},
modelValue: {
type: Object,
default: null
},
selectType: {
type: String,
default: 'single'
},
fieldTypes:{
type:Array,
default:()=>[]
}
}, },
setup(props, { emit }) { placeholder: {
const show = ref(false); type: String,
const afBox = ref(); default: '',
const selectedField = ref<IAppFields>({ },
app: undefined, modelValue: {
fields: [] type: Object,
}); default: null
if (props.modelValue && 'app' in props.modelValue && 'fields' in props.modelValue) { },
selectedField.value = props.modelValue as IAppFields; selectType: {
} type: String,
const store = useFlowEditorStore(); default: 'single'
},
const clear = () => { fieldTypes: {
selectedField.value = { type: Array,
fields: [] default: () => []
}; },
} //例:[val=>!!val ||'入力してください']
rules: {
const removeField = (index: number) => { type: String,
selectedField.value.fields.splice(index, 1); default: undefined
} },
required: {
const closeAFBox = (val: string) => { type: Boolean,
if (val == 'OK') { default: false
console.log(afBox.value); },
requiredMessage: {
selectedField.value = afBox.value.selField; type: String,
} default: ''
};
watchEffect(() => {
emit('update:modelValue', selectedField.value);
});
return {
store,
afBox,
show,
showDg: () => { show.value = true },
selectedField,
clear,
removeField,
closeAFBox,
};
} }
},
setup(props, { emit }) {
const show = ref(false);
const afBox = ref();
const fieldRef = ref();
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 clear = () => {
selectedField.value = {
fields: []
};
}
const removeField = (index: number) => {
selectedField.value.fields.splice(index, 1);
}
const closeAFBox = (val: string) => {
if (val == 'OK') {
console.log(afBox.value);
selectedField.value = afBox.value.selField;
fieldRef.value.validate();
}
};
const isSelected = computed(() => {
return !!selectedField.value.app
});
const customExp = props.rules === undefined ? [] : eval(props.rules);
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
const requiredExp = props.required ? [((val: any) => (val && val.app && val.fields && val.fields.length > 0) || errmsg)] : [];
const rulesExp = [...requiredExp, ...customExp];
watchEffect(() => {
emit('update:modelValue', selectedField.value);
});
return {
store,
afBox,
show,
showDg: () => { show.value = true },
selectedField,
clear,
removeField,
closeAFBox,
isSelected,
rulesExp,
fieldRef
};
}
}); });
</script> </script>

View File

@@ -1,14 +1,18 @@
<template> <template>
<div> <div v-bind="$attrs">
<q-field :label="displayName" labelColor="primary" stack-label> <q-field :label="displayName" labelColor="primary" stack-label
:rules="rulesExp"
lazy-rules="ondemand"
v-model="selectedApp"
ref="fieldRef">
<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="() => { dgIsShow = true }">アプリ選択</q-btn> <q-btn color="grey-3" text-color="black" @click="() => { dgIsShow = true }">アプリ選択</q-btn>
</q-card-actions> </q-card-actions>
<q-card-section class="text-caption"> <q-card-section class="text-caption">
<div v-if="selectedField.app.name"> <div v-if="selectedApp.app.name">
{{ selectedField.app.name }} {{ selectedApp.app.name }}
</div> </div>
<div v-else>{{ placeholder }}</div> <div v-else>{{ placeholder }}</div>
</q-card-section> </q-card-section>
@@ -43,10 +47,6 @@ export default defineComponent({
AppSelectBox AppSelectBox
}, },
props: { props: {
context: {
type: Array<Props>,
default: '',
},
displayName: { displayName: {
type: String, type: String,
default: '', default: '',
@@ -62,31 +62,50 @@ export default defineComponent({
modelValue: { modelValue: {
type: Object, type: Object,
default: null default: null
},
//例:[val=>!!val ||'入力してください']
rules: {
type: String,
default: undefined
},
required: {
type: Boolean,
default: false
},
requiredMessage: {
type: String,
default: ''
} }
}, },
setup(props, { emit }) { setup(props, { emit }) {
const appDg = ref() const appDg = ref();
const fieldRef=ref();
const dgIsShow = ref(false) const dgIsShow = ref(false)
const selectedField = props.modelValue && props.modelValue.app ? props.modelValue : reactive({app:{}}); const selectedApp = props.modelValue && props.modelValue.app ? props.modelValue : reactive({app:{}});
const closeDg = (state: string) => { const closeDg = (state: string) => {
dgIsShow.value = false; dgIsShow.value = false;
if (state == 'OK') { if (state == 'OK') {
selectedField.app = appDg.value.selected[0]; selectedApp.app = appDg.value.selected[0];
fieldRef.value.validate();
} }
}; };
//ルール設定
console.log(selectedField); const customExp = props.rules === undefined ? [] : eval(props.rules);
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}を選択してください。`;
const requiredExp = props.required ? [((val: any) => (!!val && !!val.app && !!val.app.name) || errmsg)] : [];
const rulesExp = [...requiredExp, ...customExp];
watchEffect(() => { watchEffect(() => {
emit('update:modelValue', selectedField); emit('update:modelValue', selectedApp);
}); });
return { return {
filter: ref(''), filter: ref(''),
dgIsShow, dgIsShow,
appDg, appDg,
fieldRef,
closeDg, closeDg,
selectedField selectedApp,
rulesExp
}; };
} }
}); });

View File

@@ -23,9 +23,6 @@ import { ConditionNode, ConditionTree, Operator, OperatorListItem } from 'app/sr
import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue'; import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue';
import ConditionEditor from '../ConditionEditor/ConditionEditor.vue'; import ConditionEditor from '../ConditionEditor/ConditionEditor.vue';
import { IActionProperty } from 'src/types/ActionTypes'; import { IActionProperty } from 'src/types/ActionTypes';
import { } from 'src/types/';
export default defineComponent({ export default defineComponent({
name: 'FieldInput', name: 'FieldInput',
@@ -132,7 +129,8 @@ export default defineComponent({
const isSetted = ref(props.modelValue && props.modelValue !== ''); const isSetted = ref(props.modelValue && props.modelValue !== '');
const conditionString = computed(() => { const conditionString = computed(() => {
return tree.buildConditionString(tree.root); const condiStr= tree.buildConditionString(tree.root);
return condiStr==='()'?'(条件なし)':condiStr;
}); });
const showDg = () => { const showDg = () => {

View File

@@ -1,6 +1,10 @@
<template> <template>
<div> <div class="q-my-md" v-bind="$attrs">
<q-field :label="displayName" labelColor="primary" stack-label> <q-field :label="displayName" labelColor="primary" stack-label
v-model="mappingProps"
:rules="rulesExp"
ref="fieldRef"
>
<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>
@@ -17,7 +21,6 @@
</template> </template>
</q-field> </q-field>
<show-dialog v-model:visible="dgIsShow" name="データマッピング" @close="closeDg" min-width="55vw" min-height="60vh"> <show-dialog v-model:visible="dgIsShow" name="データマッピング" @close="closeDg" min-width="55vw" min-height="60vh">
<div class=""> <div class="">
<div class="row q-col-gutter-x-xs flex-center"> <div class="row q-col-gutter-x-xs flex-center">
<div class="col-5"> <div class="col-5">
@@ -36,7 +39,7 @@
キー キー
</div> </div>
</div> </div>
<q-virtual-scroll style="max-height: 60vh;" :items="mappingProps" separator v-slot="{ item, index }"> <q-virtual-scroll style="max-height: 60vh;" :items="mappingProps.data" separator v-slot="{ item, index }">
<!-- <div class="q-my-sm" v-for="(item, index) in mappingProps" :key="item.id"> --> <!-- <div class="q-my-sm" v-for="(item, index) in mappingProps" :key="item.id"> -->
<div class="row q-pa-sm q-col-gutter-x-md flex-center"> <div class="row q-pa-sm q-col-gutter-x-md flex-center">
<div class="col-5"> <div class="col-5">
@@ -74,19 +77,19 @@
</div> </div>
</div> </div>
<show-dialog v-model:visible="mappingProps[index].to.isDialogVisible" name="フィールド一覧" <show-dialog v-model:visible="mappingProps.data[index].to.isDialogVisible" name="フィールド一覧"
@close="closeToDg" ref="fieldDlg"> @close="closeToDg" ref="fieldDlg">
<FieldSelect v-if="onlySourceSelect" ref="fieldDlg" name="フィールド" :appId="sourceAppId" not_page <FieldSelect v-if="onlySourceSelect" ref="fieldDlg" name="フィールド" :appId="sourceAppId" not_page
:selectedFields="mappingProps[index].to.fields" :selectedFields="mappingProps.data[index].to.fields"
:updateSelects="(fields) => { mappingProps[index].to.fields = fields; mappingProps[index].to.app = sourceApp }"> :updateSelects="(fields) => { mappingProps.data[index].to.fields = fields; mappingProps.data[index].to.app = sourceApp }">
</FieldSelect> </FieldSelect>
<AppFieldSelectBox v-else v-model:selectedField="mappingProps[index].to" /> <AppFieldSelectBox v-else v-model:selectedField="mappingProps.data[index].to" />
</show-dialog> </show-dialog>
<!-- </div> --> <!-- </div> -->
</q-virtual-scroll> </q-virtual-scroll>
<div class="q-mt-lg q-ml-md row "> <div class="q-mt-lg q-ml-md row ">
<q-checkbox size="sm" v-model="createWithNull" label="キーが存在しない場合は新規に作成され、存在する場合はデータが更新されます。" /> <q-checkbox size="sm" v-model="mappingProps.createWithNull" label="キーが存在しない場合は新規に作成され、存在する場合はデータが更新されます。" />
</div> </div>
</div> </div>
</show-dialog> </show-dialog>
@@ -115,12 +118,12 @@ type ContextProps = {
} }
}; };
type CurrentModelValueType = { interface IMappingSetting {
data: MappingValueType[]; data: IMappingValueType[];
createWithNull: boolean; createWithNull: boolean;
} }
type MappingValueType = { interface IMappingValueType {
id: string; id: string;
from: { sharedText?: string }; from: { sharedText?: string };
to: { to: {
@@ -157,7 +160,7 @@ export default defineComponent({
default: '', default: '',
}, },
modelValue: { modelValue: {
type: Object as () => CurrentModelValueType, type: Object as () => IMappingSetting,
}, },
placeholder: { placeholder: {
type: String, type: String,
@@ -171,27 +174,60 @@ export default defineComponent({
type:Array, type:Array,
default:()=>[] default:()=>[]
}, },
//例:[val=>!!val ||'入力してください']
rules: {
type: String,
default: undefined
},
required: {
type: Boolean,
default: false
},
requiredMessage: {
type: String,
default: ''
}
}, },
setup(props, { emit }) { setup(props, { emit }) {
const fieldRef=ref();
const source = props.context.find(element => element?.props?.name === 'sources') const source = props.context.find(element => element?.props?.name === 'sources')
const sourceApp = computed(() => source?.props?.modelValue?.app); const sourceApp = computed(() => source?.props?.modelValue?.app);
const sourceAppId = computed(() => sourceApp.value?.id); const sourceAppId = computed(() => sourceApp.value?.id);
//ルール設定
const checkMapping = (val:IMappingSetting)=>{
if(!val || !val.data){
return false;
}
console.log(val);
const mappingDatas = val.data.filter(item=>item.from?.sharedText && item.to.fields?.length > 0);
return mappingDatas.length>0;
}
const customExp = props.rules === undefined ? [] : eval(props.rules);
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}を選択してください。`;
const requiredExp = props.required ? [((val: any) => checkMapping(val) || errmsg)] : [];
const rulesExp = [...requiredExp, ...customExp];
// const mappingProps = ref(props.modelValue?.data ?? []);
// const createWithNull = ref(props.modelValue?.createWithNull ?? false);
const mappingProps = reactive<IMappingSetting>({
data:props.modelValue?.data ?? [],
createWithNull:props.modelValue?.createWithNull ?? false
});
const closeDg = () => { const closeDg = () => {
emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value }); fieldRef.value.validate();
emit('update:modelValue',mappingProps);
} }
const closeToDg = () => { const closeToDg = () => {
emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value }); emit('update:modelValue',mappingProps);
} }
const mappingProps = ref(props.modelValue?.data ?? []);
const createWithNull = ref(props.modelValue?.createWithNull ?? false)
// 外部ソースコンポーネントの appid をリッスンし、変更されたときに現在のコンポーネントを更新します // 外部ソースコンポーネントの appid をリッスンし、変更されたときに現在のコンポーネントを更新します
watch(() => sourceAppId.value, async (newId,) => { watch(() => sourceAppId.value, async (newId,) => {
if (!newId) return; if (!newId) return;
@@ -210,7 +246,7 @@ export default defineComponent({
.map(f => ({ name: f.label, objectType: 'field', ...f })) .map(f => ({ name: f.label, objectType: 'field', ...f }))
.map(f => { .map(f => {
// 更新前の値を求める // 更新前の値を求める
const beforeData = mappingProps.value.find(m => m.to.fields[0].code === f.code) const beforeData = mappingProps.data.find(m => m.to.fields[0].code === f.code)
return { return {
id: uuidv4(), id: uuidv4(),
from: beforeData?.from ?? {}, // 以前のデータを入力します from: beforeData?.from ?? {}, // 以前のデータを入力します
@@ -235,12 +271,12 @@ export default defineComponent({
ktAppFields.filter(f => lookupFixedField.includes(f.to.fields[0].code)).forEach(f => f.disabled = true) ktAppFields.filter(f => lookupFixedField.includes(f.to.fields[0].code)).forEach(f => f.disabled = true)
} }
mappingProps.value = ktAppFields mappingProps.data = ktAppFields
} }
const mappingObjectsInputDisplay = computed(() => const mappingObjectsInputDisplay = computed(() =>
(mappingProps.value && Array.isArray(mappingProps.value)) ? (mappingProps.data && Array.isArray(mappingProps.data)) ?
mappingProps.value mappingProps.data
.filter(item => item.from?.sharedText && item.to.fields?.length > 0) .filter(item => item.from?.sharedText && item.to.fields?.length > 0)
.map(item => { .map(item => {
return `field(${item.to.app?.id},${item.to.fields[0].label}) = ${item.from.sharedText} `; return `field(${item.to.app?.id},${item.to.fields[0].label}) = ${item.from.sharedText} `;
@@ -251,17 +287,17 @@ export default defineComponent({
const btnDisable = computed(() => props.onlySourceSelect ? !(source?.props?.modelValue?.app?.id) : false); const btnDisable = computed(() => props.onlySourceSelect ? !(source?.props?.modelValue?.app?.id) : false);
watchEffect(() => { watchEffect(() => {
emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value }); emit('update:modelValue', mappingProps);
}); });
return { return {
uuidv4, uuidv4,
dgIsShow: ref(false), dgIsShow: ref(false),
fieldRef,
closeDg, closeDg,
toDgIsShow: ref(false), toDgIsShow: ref(false),
closeToDg, closeToDg,
mappingProps, mappingProps,
createWithNull,
updateFields, updateFields,
// addMappingObject: () => mappingProps.push(defaultMappingProp()), // addMappingObject: () => mappingProps.push(defaultMappingProp()),
// deleteMappingObject, // deleteMappingObject,
@@ -269,6 +305,8 @@ export default defineComponent({
sourceApp, sourceApp,
sourceAppId, sourceAppId,
btnDisable, btnDisable,
rulesExp,
checkMapping,
config: { config: {
canInput: false, canInput: false,
buttonsConfig: [ buttonsConfig: [

View File

@@ -1,6 +1,11 @@
<template> <template>
<div> <div>
<q-field :label="displayName" labelColor="primary" stack-label> <q-field :label="displayName" labelColor="primary" stack-label
v-model="processingProps"
:rules="rulesExp"
lazy-rules="ondemand"
ref="fieldRef"
>
<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>
@@ -120,9 +125,19 @@ export default defineComponent({
type: String, type: String,
default: '', default: '',
}, },
//例:[val=>!!val ||'入力してください']
rules: {
type: String,
default: undefined
},
required: {
type: Boolean,
default: false
}
}, },
setup(props, { emit }) { setup(props, { emit }) {
const fieldRef=ref();
const source = props.context.find(element => element?.props?.name === 'sources') const source = props.context.find(element => element?.props?.name === 'sources')
if (source) { if (source) {
@@ -155,6 +170,7 @@ export default defineComponent({
}); });
const closeDg = () => { const closeDg = () => {
fieldRef.value.validate();
emit('update:modelValue', processingProps); emit('update:modelValue', processingProps);
} }
@@ -218,6 +234,24 @@ export default defineComponent({
"label": "最初の値" "label": "最初の値"
} }
]); ]);
const checkInput=(val:ValueType)=>{
if(!val){
return false;
}
if(!val.name){
return "集計結果の変数名を入力してください";
}
if(!val.vars || val.vars.length==0){
return "集計処理を設定してください";
}
if(val.vars.some((x)=>!x.vName)){
return "集計結果変数名を入力してください";
}
return true;
}
const customExp = props.rules === undefined ? [] : eval(props.rules);
const requiredExp = props.required ? [(val: any) => checkInput(val)] : [];
const rulesExp = [...requiredExp, ...customExp];
watchEffect(() => { watchEffect(() => {
emit('update:modelValue', processingProps); emit('update:modelValue', processingProps);
@@ -232,6 +266,8 @@ export default defineComponent({
deleteProcessingObject, deleteProcessingObject,
logicalOperators, logicalOperators,
processingObjectsInputDisplay, processingObjectsInputDisplay,
rulesExp,
fieldRef
}; };
}, },
}); });

View File

@@ -1,6 +1,6 @@
<template> <template>
<div v-bind="$attrs"> <div v-bind="$attrs">
<q-input v-model="selectedDate" :label="displayName" :placeholder="placeholder" label-color="primary" mask="date" :rules="['date']" stack-label> <q-input v-model="selectedDate" :label="displayName" :placeholder="placeholder" label-color="primary" mask="date" :rules="rulesExp" stack-label>
<template v-slot:append> <template v-slot:append>
<q-icon name="event" class="cursor-pointer"> <q-icon name="event" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale"> <q-popup-proxy cover transition-show="scale" transition-hide="scale">
@@ -43,16 +43,32 @@ export default defineComponent({
type: String, type: String,
default: '', default: '',
}, },
//例:[val=>!!val ||'入力してください']
rules: {
type: String,
default: undefined
},
required:{
type:Boolean,
default:false
},
requiredMessage: {
type: String,
default: ''
}
}, },
setup(props, { emit }) { setup(props, { emit }) {
const selectedDate = ref(props.modelValue); const selectedDate = ref(props.modelValue);
const customExp = props.rules === undefined ? [] : eval(props.rules);
const requiredExp = props.required?[((val:any)=>!!val||`${props.displayName}が必須です。`),'date']:['date'];
const rulesExp=[...requiredExp,...customExp];
watchEffect(() => { watchEffect(() => {
emit('update:modelValue', selectedDate.value); emit('update:modelValue', selectedDate.value);
}); });
return { return {
selectedDate selectedDate,
rulesExp
}; };
} }
}); });

View File

@@ -1,6 +1,9 @@
<template> <template>
<div v-bind="$attrs"> <div v-bind="$attrs">
<q-input :label="displayName" v-model="inputValue" label-color="primary" :placeholder="placeholder" stack-label> <q-input :label="displayName" v-model="inputValue" label-color="primary"
:placeholder="placeholder"
:rules="rulesExp"
stack-label>
<template v-slot:append> <template v-slot:append>
<q-btn round dense flat icon="add" @click="addButtonEvent()" /> <q-btn round dense flat icon="add" @click="addButtonEvent()" />
</template> </template>
@@ -40,12 +43,29 @@ export default defineComponent({
connectProps:{ connectProps:{
type:Object, type:Object,
default:undefined default:undefined
},
//例:[val=>!!val ||'入力してください']
rules: {
type: String,
default: undefined
},
required:{
type:Boolean,
default:false
},
requiredMessage: {
type: String,
default: ''
} }
}, },
setup(props , { emit }) { setup(props , { emit }) {
const inputValue = ref(props.modelValue); const inputValue = ref(props.modelValue);
const store = useFlowEditorStore(); const store = useFlowEditorStore();
const customExp = props.rules === undefined ? [] : eval(props.rules);
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}を入力してください。`;
const requiredExp = props.required ? [((val: any) => !!val || errmsg)] : [];
const rulesExp = [...requiredExp, ...customExp];
const addButtonEvent=()=>{ const addButtonEvent=()=>{
const eventId =store.currentFlow?.getRoot()?.name; const eventId =store.currentFlow?.getRoot()?.name;
if(eventId===undefined){return;} if(eventId===undefined){return;}
@@ -69,14 +89,14 @@ export default defineComponent({
}); });
} }
} }
watchEffect(() => { watchEffect(() => {
emit('update:modelValue', inputValue.value); emit('update:modelValue', inputValue.value);
}); });
return { return {
inputValue, inputValue,
addButtonEvent addButtonEvent,
rulesExp
}; };
}, },
}); });

View File

@@ -1,7 +1,9 @@
<template> <template>
<div v-bind="$attrs"> <div v-bind="$attrs">
<q-field v-model="selectedField" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label <q-field v-model="selectedField" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label
:bottom-slots="!isSelected"> :bottom-slots="!isSelected"
:rules="rulesExp"
>
<template v-slot:control> <template v-slot:control>
<q-chip color="primary" text-color="white" v-if="isSelected"> <q-chip color="primary" text-color="white" v-if="isSelected">
{{ selectedField.name }} {{ selectedField.name }}
@@ -74,6 +76,19 @@ export default defineComponent({
type: Object, type: Object,
default: null default: null
}, },
//例:[val=>!!val ||'入力してください']
rules: {
type: String,
default: undefined
},
required: {
type: Boolean,
default: false
},
requiredMessage: {
type: String,
default: ''
}
}, },
setup(props, { emit }) { setup(props, { emit }) {
@@ -85,6 +100,11 @@ export default defineComponent({
const isSelected = computed(() => { const isSelected = computed(() => {
return selectedField.value !== null && typeof selectedField.value === 'object' && ('name' in selectedField.value) return selectedField.value !== null && typeof selectedField.value === 'object' && ('name' in selectedField.value)
}); });
//ルール設定
const customExp = props.rules === undefined ? [] : eval(props.rules);
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
const requiredExp = props.required ? [((val: any) => (!!val && typeof val==='object' && !!val.name) || errmsg)] : [];
const rulesExp = [...requiredExp, ...customExp];
const showDg = () => { const showDg = () => {
show.value = true; show.value = true;
@@ -109,7 +129,8 @@ export default defineComponent({
selectedField, selectedField,
isSelected, isSelected,
filter:ref(''), filter:ref(''),
selectedFields selectedFields,
rulesExp
}; };
} }
}); });

View File

@@ -14,7 +14,6 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { kMaxLength } from 'buffer';
import { defineComponent, ref, watchEffect, computed } from 'vue'; import { defineComponent, ref, watchEffect, computed } from 'vue';
export default defineComponent({ export default defineComponent({
@@ -50,8 +49,16 @@ export default defineComponent({
type: String, type: String,
default: undefined default: undefined
}, },
required:{
type:Boolean,
default:false
},
requiredMessage: {
type: String,
default: ''
},
modelValue: { modelValue: {
// type: Any, type: null as any,
default: '', default: '',
}, },
}, },
@@ -75,8 +82,11 @@ export default defineComponent({
}, },
}); });
// const inputValue = ref(props.modelValue); // const inputValue = ref(props.modelValue);
const rulesExp = props.rules === undefined ? null : eval(props.rules);
const customExp = props.rules === undefined ? [] : eval(props.rules);
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
const requiredExp = props.required?[((val:any)=>!!val || errmsg )]:[];
const rulesExp=[...requiredExp,...customExp];
// const finalValue = computed(() => { // const finalValue = computed(() => {
// return props.name !== 'verName' ? inputValue.value : { // return props.name !== 'verName' ? inputValue.value : {
// name: inputValue.value, // name: inputValue.value,

View File

@@ -1,7 +1,10 @@
<template> <template>
<div v-bind="$attrs"> <div v-bind="$attrs">
<q-input :label="displayName" label-color="primary" v-model="inputValue" :placeholder="placeholder" autogrow <q-input :label="displayName" label-color="primary" v-model="inputValue"
stack-label /> :placeholder="placeholder"
:rules="rulesExp"
autogrow
stack-label />
</div> </div>
</template> </template>
@@ -32,17 +35,34 @@ export default defineComponent({
type: String, type: String,
default: '', default: '',
}, },
//例:[val=>!!val ||'入力してください']
rules: {
type: String,
default: undefined
},
required:{
type:Boolean,
default:false
},
requiredMessage: {
type: String,
default: ''
},
}, },
setup(props, { emit }) { setup(props, { emit }) {
const inputValue = ref(props.modelValue); const inputValue = ref(props.modelValue);
const customExp = props.rules === undefined ? [] : eval(props.rules);
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
const requiredExp = props.required?[((val:any)=>!!val || errmsg )]:[];
const rulesExp=[...requiredExp,...customExp];
watchEffect(() => { watchEffect(() => {
emit('update:modelValue', inputValue.value); emit('update:modelValue', inputValue.value);
}); });
return { return {
inputValue, inputValue,
rulesExp
}; };
}, },
}); });

View File

@@ -49,6 +49,14 @@ export default defineComponent({
type:String, type:String,
default:undefined default:undefined
}, },
required:{
type:Boolean,
default:false
},
requiredMessage: {
type: String,
default: ''
},
modelValue: { modelValue: {
type: [Number , String], type: [Number , String],
default: undefined default: undefined
@@ -57,23 +65,10 @@ export default defineComponent({
setup(props, { emit }) { setup(props, { emit }) {
const numValue = ref(props.modelValue); const numValue = ref(props.modelValue);
const rulesExp = props.rules===undefined?null : eval(props.rules); const customExp = props.rules === undefined ? [] : eval(props.rules);
const isError = computed(()=>{ const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
const val = numValue.value; const requiredExp = props.required?[((val:any)=>!!val || errmsg )]:[];
if (val === undefined) { const rulesExp=[...requiredExp,...customExp];
return false;
}
const numVal = typeof val === "string" ? parseInt(val) : val;
// Ensure parsed value is a valid number
if (isNaN(numVal)) {
return true;
}
// Check against min and max boundaries, if defined
if ((props.min !== undefined && numVal < props.min) || (props.max !== undefined && numVal > props.max)) {
return true;
}
return false;
});
watchEffect(()=>{ watchEffect(()=>{
emit("update:modelValue",numValue.value); emit("update:modelValue",numValue.value);

View File

@@ -11,6 +11,7 @@
elevated elevated
overlay overlay
> >
<q-form @submit="save" autocomplete="off" class="full-height">
<q-card class="column" style="max-width: 300px;min-height: 100%"> <q-card class="column" style="max-width: 300px;min-height: 100%">
<q-card-section> <q-card-section>
<div class="text-h6">{{ actionNode?.subTitle }}設定</div> <div class="text-h6">{{ actionNode?.subTitle }}設定</div>
@@ -21,16 +22,17 @@
<q-card-actions align="right" class="bg-white text-teal"> <q-card-actions align="right" class="bg-white text-teal">
<q-btn flat label="キャンセル" @click="cancel" outline dense padding="none sm" color="primary"/> <q-btn flat label="キャンセル" @click="cancel" outline dense padding="none sm" color="primary"/>
<q-btn flat label="更新" @click="save" outline dense padding="none sm" color="primary" /> <q-btn flat label="更新" type="submit" outline dense padding="none sm" color="primary" />
</q-card-actions> </q-card-actions>
</q-card> </q-card>
</q-form>
</q-drawer> </q-drawer>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { ref,defineComponent, PropType ,watchEffect} from 'vue' import { ref,defineComponent, PropType ,watchEffect} from 'vue'
import PropertyList from 'components/right/PropertyList.vue'; import PropertyList from 'components/right/PropertyList.vue';
import { IActionNode } from 'src/types/ActionTypes'; import { IActionNode, IActionProperty } from 'src/types/ActionTypes';
export default defineComponent({ export default defineComponent({
name: 'PropertyPanel', name: 'PropertyPanel',
components: { components: {
@@ -47,14 +49,28 @@ import { IActionNode } from 'src/types/ActionTypes';
} }
}, },
emits: [ emits: [
'update:drawerRight' 'update:drawerRight',
'saveActionProps'
], ],
setup(props,{emit}) { setup(props,{emit}) {
const showPanel =ref(props.drawerRight); const showPanel =ref(props.drawerRight);
const actionProps =ref(props.actionNode?.actionProps);
const cloneProps = (actionProps:IActionProperty[]):IActionProperty[]|null=>{
if(!actionProps){
return null;
}
const json=JSON.stringify(actionProps);
return JSON.parse(json);
}
const actionProps =ref(cloneProps(props.actionNode?.actionProps));
watchEffect(() => { watchEffect(() => {
if(showPanel.value!==undefined){
showPanel.value = props.drawerRight;
}
showPanel.value = props.drawerRight; showPanel.value = props.drawerRight;
actionProps.value= props.actionNode?.actionProps; actionProps.value= cloneProps(props.actionNode?.actionProps);
}); });
const cancel = async() =>{ const cancel = async() =>{
@@ -64,6 +80,7 @@ import { IActionNode } from 'src/types/ActionTypes';
const save = async () =>{ const save = async () =>{
showPanel.value=false; showPanel.value=false;
emit('saveActionProps', actionProps.value);
emit('update:drawerRight',false ) emit('update:drawerRight',false )
} }

View File

@@ -1,6 +1,9 @@
<template> <template>
<div v-bind="$attrs"> <div v-bind="$attrs">
<q-select v-model="selectedValue" :use-chips="multiple" :label="displayName" label-color="primary" :options="options" stack-label <q-select v-model="selectedValue" :use-chips="multiple" :label="displayName" label-color="primary"
:options="options"
stack-label
:rules="rulesExp"
:multiple="multiple"/> :multiple="multiple"/>
</div> </div>
</template> </template>
@@ -32,6 +35,19 @@ export default defineComponent({
type: [Array,String], type: [Array,String],
default: null, default: null,
}, },
//例:[val=>!!val ||'入力してください']
rules: {
type: String,
default: undefined
},
required:{
type:Boolean,
default:false
},
requiredMessage: {
type: String,
default: ''
},
}, },
setup(props, { emit }) { setup(props, { emit }) {
const selectedValue = ref(props.modelValue); const selectedValue = ref(props.modelValue);
@@ -41,10 +57,14 @@ export default defineComponent({
watchEffect(() => { watchEffect(() => {
emit('update:modelValue', selectedValue.value); emit('update:modelValue', selectedValue.value);
}); });
const customExp = props.rules === undefined ? [] : eval(props.rules);
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}を選択してください。`;
const requiredExp = props.required?[((val:any)=>!!val || errmsg )]:[];
const rulesExp=[...requiredExp,...customExp];
return { return {
selectedValue, selectedValue,
multiple multiple,
rulesExp
}; };
}, },
}); });

View File

@@ -31,7 +31,7 @@
@deleteNode="onDeleteNode" @deleteAllNextNodes="onDeleteAllNextNodes" @copyFlow="onCopyFlow"></node-item> @deleteNode="onDeleteNode" @deleteAllNextNodes="onDeleteAllNextNodes" @copyFlow="onCopyFlow"></node-item>
</div> </div>
</div> </div>
<PropertyPanel :actionNode="store.activeNode" v-model:drawerRight="drawerRight"></PropertyPanel> <PropertyPanel :actionNode="store.activeNode" v-model:drawerRight="drawerRight" @save-action-props="onSaveActionProps"></PropertyPanel>
</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>
@@ -193,6 +193,12 @@ const onDeploy = async () => {
return; return;
} }
const onSaveActionProps=(props:IActionProperty[])=>{
if(store.activeNode){
store.activeNode.actionProps=props;
}
};
const onSaveFlow = async () => { const onSaveFlow = async () => {
const targetFlow = store.selectedFlow; const targetFlow = store.selectedFlow;
if (targetFlow === undefined) { if (targetFlow === undefined) {

View File

@@ -11,7 +11,7 @@ export interface FlowEditorState {
activeNode: IActionNode | undefined; activeNode: IActionNode | undefined;
eventTree: KintoneEventManager; eventTree: KintoneEventManager;
selectedEvent: IKintoneEvent | undefined; selectedEvent: IKintoneEvent | undefined;
expandedScreen: any[]; expandedScreen: string[];
} }
const flowCtrl = new FlowCtrl(); const flowCtrl = new FlowCtrl();
const eventTree = new KintoneEventManager(); const eventTree = new KintoneEventManager();
@@ -62,6 +62,10 @@ export const useFlowEditorStore = defineStore('flowEditor', {
}, },
selectFlow(flow: IActionFlow | undefined) { selectFlow(flow: IActionFlow | undefined) {
this.selectedFlow = flow; this.selectedFlow = flow;
if(flow!==undefined){
const eventId = flow.getRoot()?.name;
this.selectedEvent=this.eventTree.findEventById(eventId) as IKintoneEvent;
}
}, },
setActiveNode(node: IActionNode) { setActiveNode(node: IActionNode) {
this.activeNode = node; this.activeNode = node;
@@ -84,15 +88,23 @@ export const useFlowEditorStore = defineStore('flowEditor', {
if (actionFlows === undefined || actionFlows.length === 0) { if (actionFlows === undefined || actionFlows.length === 0) {
this.flows = []; this.flows = [];
this.selectedFlow = undefined; this.selectedFlow = undefined;
this.expandedScreen =[];
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 expandEventIds = actionFlows.map((flow) => flow.getRoot()?.name);
const expandScreens:string[]=[];
expandEventIds.forEach((eventid)=>{
const eventNode=this.eventTree.findEventById(eventid||'');
if(eventNode){
expandScreens.push(eventNode.parentId);
}
});
// const expandName =actionFlows[0].getRoot()?.title; // const expandName =actionFlows[0].getRoot()?.title;
this.expandedScreen = expandNames; this.expandedScreen = expandScreens;
}, },
/** /**
* フローをDBに保存及び更新する * フローをDBに保存及び更新する

View File

@@ -200,12 +200,13 @@ export class ConditionTree {
return conditionString; return conditionString;
} else { } else {
const condNode=node as ConditionNode; const condNode=node as ConditionNode;
if (condNode.object && condNode.operator ) { if (condNode.object && condNode.object.sharedText && condNode.operator ) {
// let value=condNode.value; // let value=condNode.value;
// 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.sharedText} ${typeof condNode.operator === 'object' ? condNode.operator.label : condNode.operator} ${condNode.value.sharedText}`; const rightVal = condNode.value.sharedText || '""';
return `${condNode.object.sharedText} ${typeof condNode.operator === 'object' ? condNode.operator.label : condNode.operator} ${rightVal}`;
// return `${typeof condNode.object.name === 'object' ? condNode.object.name.name : condNode.object.name} ${typeof condNode.operator === 'object' ? condNode.operator.label : 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 { } else {
return ''; return '';