|
@@ -1,13 +1,65 @@
|
|
|
<script lang="ts" setup>
|
|
<script lang="ts" setup>
|
|
|
-import { computed } from 'vue'
|
|
|
|
|
-import VarSelect from '@/nodes/_base/VarSelect.vue'
|
|
|
|
|
|
|
+import { computed, watch } from 'vue'
|
|
|
import VarInput from '@/nodes/_base/VarInput.vue'
|
|
import VarInput from '@/nodes/_base/VarInput.vue'
|
|
|
|
|
+import VarSelect from '@/nodes/_base/VarSelect.vue'
|
|
|
|
|
+import { VARIABLE_TYPE_OPERATORS } from '@/constant'
|
|
|
import { useSetterModel } from '../_shared/useSetterModel'
|
|
import { useSetterModel } from '../_shared/useSetterModel'
|
|
|
|
|
|
|
|
|
|
+import type { ConditionType, NodeVariable, NodeVariableType } from '@/nodes/Interface'
|
|
|
import type { ListData } from './index'
|
|
import type { ListData } from './index'
|
|
|
-import type { NodeVariableType } from '@/nodes/Interface'
|
|
|
|
|
import type { NodeVar } from '@/types/var'
|
|
import type { NodeVar } from '@/types/var'
|
|
|
|
|
|
|
|
|
|
+type AllowedListInputType = 'array[string]' | 'array[number]' | 'array[boolean]' | 'array[file]'
|
|
|
|
|
+
|
|
|
|
|
+type FilterValueType = Extract<ConditionType['varType'], 'string' | 'number' | 'boolean'>
|
|
|
|
|
+
|
|
|
|
|
+type OperatorsType = ListData['filter_by']['conditions'][0]['comparison_operator']
|
|
|
|
|
+
|
|
|
|
|
+const ALLOWED_INPUT_TYPES: AllowedListInputType[] = [
|
|
|
|
|
+ 'array[string]',
|
|
|
|
|
+ 'array[number]',
|
|
|
|
|
+ 'array[boolean]',
|
|
|
|
|
+ 'array[file]'
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+const FILTER_VALUE_TYPE_MAP: Record<AllowedListInputType, FilterValueType> = {
|
|
|
|
|
+ 'array[string]': 'string',
|
|
|
|
|
+ 'array[number]': 'number',
|
|
|
|
|
+ 'array[boolean]': 'boolean',
|
|
|
|
|
+ 'array[file]': 'string'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const DEFAULT_OPERATOR_MAP: Record<FilterValueType, ConditionType['comparison_operator']> = {
|
|
|
|
|
+ string: 'contains',
|
|
|
|
|
+ number: '=',
|
|
|
|
|
+ boolean: 'is'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const OUTPUT_ITEM_TYPE_MAP: Record<AllowedListInputType, NodeVariableType> = {
|
|
|
|
|
+ 'array[string]': 'string',
|
|
|
|
|
+ 'array[number]': 'number',
|
|
|
|
|
+ 'array[boolean]': 'boolean',
|
|
|
|
|
+ 'array[file]': 'object'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const FILE_OPTIONS = [
|
|
|
|
|
+ { label: 'ID', value: 'id' },
|
|
|
|
|
+ { label: 'Name', value: 'name' },
|
|
|
|
|
+ { label: 'ExtensionName', value: 'extensionName' },
|
|
|
|
|
+ { label: 'Size', value: 'size' },
|
|
|
|
|
+ { label: 'Path', value: 'path' }
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+const FILE_FILTER_FIELD = {
|
|
|
|
|
+ label: 'name',
|
|
|
|
|
+ value: 'name'
|
|
|
|
|
+} as const
|
|
|
|
|
+
|
|
|
|
|
+const BOOLEAN_OPTIONS = [
|
|
|
|
|
+ { label: 'True', value: 'true' },
|
|
|
|
|
+ { label: 'False', value: 'false' }
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
const props = defineProps<{
|
|
const props = defineProps<{
|
|
|
data: ListData
|
|
data: ListData
|
|
|
}>()
|
|
}>()
|
|
@@ -44,37 +96,109 @@ const conditions = computed({
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-const handleInputVarChange = (val: { value: string; type: string }) => {
|
|
|
|
|
- if (inputVar.value) {
|
|
|
|
|
- inputVar.value.value = val.value
|
|
|
|
|
- inputVar.value.type = val.type as NodeVariableType
|
|
|
|
|
- }
|
|
|
|
|
|
|
+const currentInputType = computed<AllowedListInputType | undefined>(() => {
|
|
|
|
|
+ const type = inputVar.value?.type
|
|
|
|
|
+ return type && ALLOWED_INPUT_TYPES.includes(type as AllowedListInputType)
|
|
|
|
|
+ ? (type as AllowedListInputType)
|
|
|
|
|
+ : undefined
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const currentFilterValueType = computed<FilterValueType>(() => {
|
|
|
|
|
+ if (!currentInputType.value) return 'string'
|
|
|
|
|
+ return FILTER_VALUE_TYPE_MAP[currentInputType.value]
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const isFileInputType = computed(() => currentInputType.value === 'array[file]')
|
|
|
|
|
+
|
|
|
|
|
+const operatorOptions = computed(() => {
|
|
|
|
|
+ return VARIABLE_TYPE_OPERATORS[currentFilterValueType.value] ?? []
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const getOperatorsByFilterType = (type: FilterValueType) => {
|
|
|
|
|
+ return VARIABLE_TYPE_OPERATORS[type] ?? []
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const normalizeBooleanValue = (value?: string) => {
|
|
|
|
|
+ return value === 'false' ? 'false' : 'true'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const syncOutputs = (type?: AllowedListInputType) => {
|
|
|
|
|
+ if (!type) return
|
|
|
|
|
+
|
|
|
|
|
+ const resultType = type
|
|
|
|
|
+ const itemType = OUTPUT_ITEM_TYPE_MAP[type]
|
|
|
|
|
+ const outputTemplates: Array<Pick<NodeVariable, 'name' | 'describe' | 'type'>> = [
|
|
|
|
|
+ { name: 'result', describe: '过滤结果', type: resultType },
|
|
|
|
|
+ { name: 'first_record', describe: '第一条记录', type: itemType },
|
|
|
|
|
+ { name: 'last_record', describe: '最后一条记录', type: itemType }
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ formData.value.outputs = outputTemplates.map((template, index) => ({
|
|
|
|
|
+ ...(formData.value.outputs?.[index] || {}),
|
|
|
|
|
+ ...template
|
|
|
|
|
+ }))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const isDataVarType = (type?: string) => {
|
|
|
|
|
- if (!type) return false
|
|
|
|
|
|
|
+const syncConditionByInputType = (type?: AllowedListInputType) => {
|
|
|
|
|
+ if (!type) return
|
|
|
|
|
+
|
|
|
|
|
+ const nextVarType = FILTER_VALUE_TYPE_MAP[type]
|
|
|
|
|
+ const nextOperators = getOperatorsByFilterType(nextVarType)
|
|
|
|
|
+ const currentOperator = conditions.value?.comparison_operator
|
|
|
|
|
+ const nextOperator = nextOperators.some((item) => item.value === currentOperator)
|
|
|
|
|
+ ? currentOperator
|
|
|
|
|
+ : DEFAULT_OPERATOR_MAP[nextVarType]
|
|
|
|
|
|
|
|
- return ['string', 'number', 'boolean', 'object'].includes(type) || /^array\[[^\]]+\]$/.test(type)
|
|
|
|
|
|
|
+ conditions.value = {
|
|
|
|
|
+ ...conditions.value,
|
|
|
|
|
+ comparison_operator: nextOperator as OperatorsType,
|
|
|
|
|
+ right_value:
|
|
|
|
|
+ nextVarType === 'boolean'
|
|
|
|
|
+ ? normalizeBooleanValue(conditions.value?.right_value)
|
|
|
|
|
+ : (conditions.value?.right_value ?? ''),
|
|
|
|
|
+ left_value: type === 'array[file]' ? FILE_FILTER_FIELD.value : '',
|
|
|
|
|
+ varType: nextVarType
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+watch(
|
|
|
|
|
+ currentInputType,
|
|
|
|
|
+ (type) => {
|
|
|
|
|
+ syncOutputs(type)
|
|
|
|
|
+ syncConditionByInputType(type)
|
|
|
|
|
+ },
|
|
|
|
|
+ { immediate: true }
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
const filterVarFn = (list: NodeVar[]) => {
|
|
const filterVarFn = (list: NodeVar[]) => {
|
|
|
return list
|
|
return list
|
|
|
.map((group) => ({
|
|
.map((group) => ({
|
|
|
...group,
|
|
...group,
|
|
|
- variableList: group.variableList.filter((item) => isDataVarType(item.type))
|
|
|
|
|
|
|
+ variableList: group.variableList.filter((item) =>
|
|
|
|
|
+ ALLOWED_INPUT_TYPES.includes(item.type as AllowedListInputType)
|
|
|
|
|
+ )
|
|
|
}))
|
|
}))
|
|
|
.filter((group) => group.variableList.length)
|
|
.filter((group) => group.variableList.length)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const handleChangeFilterCondition = (val: { value: string; type: string }) => {
|
|
|
|
|
|
|
+const handleInputVarChange = (val: { value: string; type: string }) => {
|
|
|
|
|
+ const nextType = val.type as NodeVariableType
|
|
|
|
|
+ const nextInputVar: NodeVariable = {
|
|
|
|
|
+ ...(inputVar.value || { name: '' }),
|
|
|
|
|
+ value: val.value,
|
|
|
|
|
+ type: nextType
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ inputVar.value = nextInputVar
|
|
|
|
|
+ if (ALLOWED_INPUT_TYPES.includes(nextType as AllowedListInputType)) {
|
|
|
|
|
+ syncOutputs(nextType as AllowedListInputType)
|
|
|
|
|
+ syncConditionByInputType(nextType as AllowedListInputType)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleLeftChange = (val: string) => {
|
|
|
if (conditions.value) {
|
|
if (conditions.value) {
|
|
|
- conditions.value.right_value = val.value
|
|
|
|
|
- } else if (val) {
|
|
|
|
|
- conditions.value = {
|
|
|
|
|
- comparison_operator: 'contains',
|
|
|
|
|
- right_value: '',
|
|
|
|
|
- varType: 'string'
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ conditions.value.varType = val === 'size' ? 'number' : 'string'
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|
|
@@ -84,7 +208,7 @@ const handleChangeFilterCondition = (val: { value: string; type: string }) => {
|
|
|
<el-form label-width="50px">
|
|
<el-form label-width="50px">
|
|
|
<el-form-item label="" label-position="top">
|
|
<el-form-item label="" label-position="top">
|
|
|
<div class="w-full flex items-center justify-between beautify">
|
|
<div class="w-full flex items-center justify-between beautify">
|
|
|
- <label class="text-14px font-bold text-gray-700">输入</label>
|
|
|
|
|
|
|
+ <label class="text-14px font-bold text-gray-700">输入变量</label>
|
|
|
</div>
|
|
</div>
|
|
|
<VarSelect
|
|
<VarSelect
|
|
|
:model-value="inputVar?.value"
|
|
:model-value="inputVar?.value"
|
|
@@ -99,19 +223,50 @@ const handleChangeFilterCondition = (val: { value: string; type: string }) => {
|
|
|
<label class="text-14px font-bold text-gray-700">过滤条件</label>
|
|
<label class="text-14px font-bold text-gray-700">过滤条件</label>
|
|
|
<el-switch v-model="formData.filter_by.enabled" />
|
|
<el-switch v-model="formData.filter_by.enabled" />
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="w-full flex items-center gap-12px">
|
|
|
|
|
- <el-select
|
|
|
|
|
- :model-value="conditions?.comparison_operator"
|
|
|
|
|
- :options="[]"
|
|
|
|
|
- style="width: 120px"
|
|
|
|
|
- />
|
|
|
|
|
- <VarInput :model-value="conditions?.right_value!" @change="handleChangeFilterCondition" />
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="conditions && formData.filter_by.enabled" class="w-full flex flex-col gap-12px">
|
|
|
|
|
+ <div v-if="isFileInputType" class="w-full">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="conditions.left_value"
|
|
|
|
|
+ :options="FILE_OPTIONS"
|
|
|
|
|
+ @change="handleLeftChange"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="w-full flex items-center gap-12px">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="conditions.comparison_operator"
|
|
|
|
|
+ :options="operatorOptions"
|
|
|
|
|
+ style="width: 120px"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <el-radio-group
|
|
|
|
|
+ v-if="currentFilterValueType === 'boolean'"
|
|
|
|
|
+ v-model="conditions.right_value"
|
|
|
|
|
+ class="list-boolean-group"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-radio-button
|
|
|
|
|
+ v-for="option in BOOLEAN_OPTIONS"
|
|
|
|
|
+ :key="option.value"
|
|
|
|
|
+ :label="option.label"
|
|
|
|
|
+ :value="option.value"
|
|
|
|
|
+ class="list-boolean-group__item"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-radio-group>
|
|
|
|
|
+
|
|
|
|
|
+ <VarInput
|
|
|
|
|
+ v-else
|
|
|
|
|
+ v-model="conditions.right_value"
|
|
|
|
|
+ class="flex-1"
|
|
|
|
|
+ placeholder="键入 '/' 键快速插入变量"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
|
|
|
|
|
<el-form-item label="" label-position="top">
|
|
<el-form-item label="" label-position="top">
|
|
|
- <div class="w-full flex items-center justify-between beautify">
|
|
|
|
|
- <label class="text-14px font-bold text-gray-700">取第N项</label>
|
|
|
|
|
|
|
+ <div class="w-full flex items-center justify-between">
|
|
|
|
|
+ <label class="text-14px font-bold text-gray-700">取第 N 项</label>
|
|
|
<el-switch v-model="formData.extract_by.enabled" />
|
|
<el-switch v-model="formData.extract_by.enabled" />
|
|
|
</div>
|
|
</div>
|
|
|
<div class="w-full">
|
|
<div class="w-full">
|
|
@@ -120,24 +275,29 @@ const handleChangeFilterCondition = (val: { value: string; type: string }) => {
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
|
|
|
|
|
<el-form-item label="" label-position="top">
|
|
<el-form-item label="" label-position="top">
|
|
|
- <div class="w-full flex items-center justify-between beautify">
|
|
|
|
|
- <label class="text-14px font-bold text-gray-700">取前N项</label>
|
|
|
|
|
|
|
+ <div class="w-full flex items-center justify-between">
|
|
|
|
|
+ <label class="text-14px font-bold text-gray-700">取前 N 项</label>
|
|
|
<el-switch v-model="formData.limit.enabled" />
|
|
<el-switch v-model="formData.limit.enabled" />
|
|
|
</div>
|
|
</div>
|
|
|
<div class="w-full flex gap-12px">
|
|
<div class="w-full flex gap-12px">
|
|
|
<el-input-number v-model="formData.limit.size" :min="1" :max="20" />
|
|
<el-input-number v-model="formData.limit.size" :min="1" :max="20" />
|
|
|
- <div class="flex-1">
|
|
|
|
|
|
|
+ <div class="flex-1 pr-10px">
|
|
|
<el-slider v-model="formData.limit.size" :min="1" :max="20" />
|
|
<el-slider v-model="formData.limit.size" :min="1" :max="20" />
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
|
|
|
|
|
<el-form-item label="" label-position="top">
|
|
<el-form-item label="" label-position="top">
|
|
|
- <div class="w-full flex items-center justify-between beautify">
|
|
|
|
|
|
|
+ <div class="w-full flex items-center justify-between">
|
|
|
<label class="text-14px font-bold text-gray-700">排序</label>
|
|
<label class="text-14px font-bold text-gray-700">排序</label>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="w-full flex gap-12px">
|
|
<div class="w-full flex gap-12px">
|
|
|
- <VarInput v-model="formData.order_by.key" />
|
|
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-if="isFileInputType"
|
|
|
|
|
+ v-model="formData.order_by.key"
|
|
|
|
|
+ :options="FILE_OPTIONS"
|
|
|
|
|
+ style=""
|
|
|
|
|
+ />
|
|
|
<el-radio-group v-model="formData.order_by.value" size="small" class="w-160px">
|
|
<el-radio-group v-model="formData.order_by.value" size="small" class="w-160px">
|
|
|
<el-radio-button label="升序" value="asc" />
|
|
<el-radio-button label="升序" value="asc" />
|
|
|
<el-radio-button label="降序" value="desc" />
|
|
<el-radio-button label="降序" value="desc" />
|
|
@@ -147,3 +307,39 @@ const handleChangeFilterCondition = (val: { value: string; type: string }) => {
|
|
|
</el-form>
|
|
</el-form>
|
|
|
</el-scrollbar>
|
|
</el-scrollbar>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="less">
|
|
|
|
|
+:deep(.el-form-item) {
|
|
|
|
|
+ padding-bottom: 10px;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+ border-bottom: 1px solid #eee;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.list-filter-field__tag {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 6px 10px;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ color: var(--el-color-primary);
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.list-boolean-group {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.list-boolean-group__item {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.list-boolean-group .el-radio-button) {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.list-boolean-group .el-radio-button__inner) {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|