Optimize Conditional Logic Handling and Introduce Demo Component
- Refactored ConditionalLogicEngine.vue to optimize conditional logic script generation by grouping handlers for watched fields, reducing duplicate event listeners and improving performance. - Added helper functions for generating condition checks and action codes, enhancing code readability and maintainability. - Introduced ConditionalLogicTestDemo.vue to demonstrate the benefits of optimization, showcasing before and after examples of conditional logic handling. - Updated FormBuilderFieldSettingsModal.vue to include notes on optimization when multiple fields watch the same trigger field, improving user awareness of performance enhancements. - Enhanced ComponentPreview.vue and workflow pages to support preview mode for conditional logic, ensuring consistent behavior across the application.
This commit is contained in:
parent
8a6f87ebf1
commit
577128a799
@ -26,8 +26,8 @@
|
||||
<FormKit v-else-if="isInputType" :id="`preview-${component.id}`" :type="component.type" :name="component.props.name"
|
||||
:label="component.props.label" :help="component.props.help" :placeholder="component.props.placeholder"
|
||||
:validation="component.props.validation" :validation-visibility="isPreview ? 'live' : 'blur'"
|
||||
:readonly="component.props.readonly || isPreview"
|
||||
:disabled="isPreview || (component.props.readonly && ['select', 'checkbox', 'radio'].includes(component.type))"
|
||||
:readonly="component.props.readonly || !isPreview"
|
||||
:disabled="!isPreview || (component.props.readonly && ['select', 'checkbox', 'radio'].includes(component.type))"
|
||||
:options="component.props.options || undefined" :value="component.props.value || undefined"
|
||||
:accept="component.props.accept || undefined" :max="component.props.max || undefined"
|
||||
:mask="component.props.mask || undefined" :digits="component.props.digits || undefined"
|
||||
@ -144,7 +144,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Default group preview (in edit mode) -->
|
||||
<div v-if="isPreview" class="repeating-groups space-y-4">
|
||||
<div v-if="!isPreview" class="repeating-groups space-y-4">
|
||||
<div class="group-item border border-gray-200 rounded-md p-3 bg-gray-50">
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<h4 class="text-sm font-medium text-gray-700">Item 1</h4>
|
||||
@ -168,7 +168,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Functional groups (in form view) -->
|
||||
<!-- Functional groups (in preview mode) -->
|
||||
<div v-else class="repeating-groups space-y-4">
|
||||
<div v-for="(group, groupIndex) in (safeGetField(component.props.name, previewFormData) || [])" :key="groupIndex"
|
||||
class="group-item border border-gray-200 rounded-md p-3 bg-gray-50">
|
||||
@ -250,7 +250,7 @@
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div v-if="isPreview" class="list-items space-y-2">
|
||||
<div v-if="!isPreview" class="list-items space-y-2">
|
||||
<div v-for="(item, index) in component.props.defaultItems" :key="index" class="flex items-center">
|
||||
<input :type="component.props.itemType || 'text'" :value="item" :placeholder="component.props.placeholder"
|
||||
disabled
|
||||
@ -263,7 +263,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Add button for preview -->
|
||||
<!-- Add button for edit mode -->
|
||||
<button type="button"
|
||||
class="inline-flex items-center px-3 py-1.5 border border-blue-600 text-blue-600 bg-white hover:bg-blue-50 rounded text-sm">
|
||||
<Icon name="material-symbols:add-circle-outline" class="w-4 h-4 mr-1" />
|
||||
@ -464,7 +464,12 @@
|
||||
<component-preview :component="childComponent" :is-preview="true" />
|
||||
</template>
|
||||
</div>
|
||||
<!-- No placeholder in preview mode when empty -->
|
||||
<!-- Show subtle indication for empty sections in preview mode (optional) -->
|
||||
<div v-else-if="component.props.showEmptyMessage !== false" class="empty-section-preview">
|
||||
<p class="text-sm text-gray-400 text-center py-4">
|
||||
{{ component.props.emptyMessage || 'This section is empty' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- In form builder mode, show drop zone -->
|
||||
@ -728,7 +733,7 @@ watch(() => props.component.props.minItems, (newMinItems, oldMinItems) => {
|
||||
|
||||
// Repeating group and dynamic list functionality
|
||||
const addGroupItem = () => {
|
||||
if (props.isPreview) return;
|
||||
if (!props.isPreview) return;
|
||||
|
||||
const groupName = props.component.props.name;
|
||||
if (!groupName) return;
|
||||
@ -758,7 +763,7 @@ const addGroupItem = () => {
|
||||
};
|
||||
|
||||
const removeGroupItem = (index) => {
|
||||
if (props.isPreview) return;
|
||||
if (!props.isPreview) return;
|
||||
|
||||
const groupName = props.component.props.name;
|
||||
if (!groupName) return;
|
||||
@ -785,7 +790,7 @@ const removeGroupItem = (index) => {
|
||||
};
|
||||
|
||||
const addListItem = () => {
|
||||
if (props.isPreview) return;
|
||||
if (!props.isPreview) return;
|
||||
|
||||
const listName = props.component.props.name;
|
||||
if (!listName) return;
|
||||
@ -802,7 +807,7 @@ const addListItem = () => {
|
||||
};
|
||||
|
||||
const removeListItem = (index) => {
|
||||
if (props.isPreview) return;
|
||||
if (!props.isPreview) return;
|
||||
|
||||
const listName = props.component.props.name;
|
||||
if (!listName) return;
|
||||
@ -1060,7 +1065,7 @@ const showImportModal = (listName) => {
|
||||
|
||||
// Handle button click
|
||||
const handleButtonClick = () => {
|
||||
if (props.isPreview) return;
|
||||
if (!props.isPreview) return;
|
||||
|
||||
// Execute custom onClick code if provided
|
||||
if (props.component.props.onClick) {
|
||||
@ -1159,12 +1164,7 @@ const handleImageClick = (event) => {
|
||||
|
||||
// Compute style based on grid properties
|
||||
const componentStyle = computed(() => {
|
||||
// Only apply grid styles in the non-preview mode (actual form)
|
||||
if (props.isPreview) {
|
||||
return {}; // Styling is handled by parent in canvas mode
|
||||
}
|
||||
|
||||
// Apply grid column in preview mode
|
||||
// Apply grid column styling in both preview and edit modes
|
||||
const gridColumn = props.component.props.gridColumn || 'span 12';
|
||||
|
||||
return {
|
||||
@ -1634,6 +1634,12 @@ const getButtonSizeClass = (size) => {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.empty-section-preview {
|
||||
border: 1px dashed #e5e7eb;
|
||||
border-radius: 0.375rem;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.section-drop-zone {
|
||||
padding: 1rem;
|
||||
min-height: 120px;
|
||||
|
@ -41,6 +41,56 @@ const getJavaScriptOperator = (operator) => {
|
||||
// Generate conditional logic script from form components
|
||||
const generateConditionalLogicScript = () => {
|
||||
const scriptsArray = []
|
||||
const fieldWatchers = new Map() // Group by watched field to avoid duplicates
|
||||
const initialEvaluations = []
|
||||
|
||||
// Helper function to generate condition check
|
||||
const generateConditionCheck = (condition) => {
|
||||
const { field, operator: condOp, value } = condition
|
||||
|
||||
switch (condOp) {
|
||||
case 'equals':
|
||||
return `getField('${field}') === '${value}'`
|
||||
case 'not_equals':
|
||||
return `getField('${field}') !== '${value}'`
|
||||
case 'contains':
|
||||
return `String(getField('${field}') || '').toLowerCase().includes('${value}'.toLowerCase())`
|
||||
case 'not_contains':
|
||||
return `!String(getField('${field}') || '').toLowerCase().includes('${value}'.toLowerCase())`
|
||||
case 'is_empty':
|
||||
return `!getField('${field}') || getField('${field}') === ''`
|
||||
case 'is_not_empty':
|
||||
return `getField('${field}') && getField('${field}') !== ''`
|
||||
case 'greater_than':
|
||||
return `Number(getField('${field}')) > ${Number(value) || 0}`
|
||||
case 'less_than':
|
||||
return `Number(getField('${field}')) < ${Number(value) || 0}`
|
||||
default:
|
||||
return `getField('${field}') === '${value}'`
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to generate action code
|
||||
const generateActionCode = (action, fieldName) => {
|
||||
switch (action) {
|
||||
case 'show': return `showField('${fieldName}')`
|
||||
case 'hide': return `hideField('${fieldName}')`
|
||||
case 'enable': return `enableField('${fieldName}')`
|
||||
case 'disable': return `disableField('${fieldName}')`
|
||||
default: return `showField('${fieldName}')`
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to generate inverse action code
|
||||
const generateInverseActionCode = (action, fieldName) => {
|
||||
switch (action) {
|
||||
case 'show': return `hideField('${fieldName}')`
|
||||
case 'hide': return `showField('${fieldName}')`
|
||||
case 'enable': return `disableField('${fieldName}')`
|
||||
case 'disable': return `enableField('${fieldName}')`
|
||||
default: return `hideField('${fieldName}')`
|
||||
}
|
||||
}
|
||||
|
||||
// Process each component that has conditional logic enabled
|
||||
props.formComponents.forEach(component => {
|
||||
@ -56,73 +106,81 @@ const generateConditionalLogicScript = () => {
|
||||
if (!fieldName) return
|
||||
|
||||
// Generate condition checks
|
||||
const conditionChecks = conditions.map(condition => {
|
||||
const { field, operator: condOp, value } = condition
|
||||
|
||||
switch (condOp) {
|
||||
case 'equals':
|
||||
return `getField('${field}') === '${value}'`
|
||||
case 'not_equals':
|
||||
return `getField('${field}') !== '${value}'`
|
||||
case 'contains':
|
||||
return `String(getField('${field}') || '').toLowerCase().includes('${value}'.toLowerCase())`
|
||||
case 'not_contains':
|
||||
return `!String(getField('${field}') || '').toLowerCase().includes('${value}'.toLowerCase())`
|
||||
case 'is_empty':
|
||||
return `!getField('${field}') || getField('${field}') === ''`
|
||||
case 'is_not_empty':
|
||||
return `getField('${field}') && getField('${field}') !== ''`
|
||||
case 'greater_than':
|
||||
return `Number(getField('${field}')) > ${Number(value) || 0}`
|
||||
case 'less_than':
|
||||
return `Number(getField('${field}')) < ${Number(value) || 0}`
|
||||
default:
|
||||
return `getField('${field}') === '${value}'`
|
||||
}
|
||||
}).join(` ${getJavaScriptOperator(operator)} `)
|
||||
const conditionChecks = conditions.map(generateConditionCheck).join(` ${getJavaScriptOperator(operator)} `)
|
||||
|
||||
// Generate action functions
|
||||
const actionCode = action === 'show' ? `showField('${fieldName}')` :
|
||||
action === 'hide' ? `hideField('${fieldName}')` :
|
||||
action === 'enable' ? `enableField('${fieldName}')` :
|
||||
action === 'disable' ? `disableField('${fieldName}')` :
|
||||
`showField('${fieldName}')`
|
||||
const actionCode = generateActionCode(action, fieldName)
|
||||
const inverseActionCode = generateInverseActionCode(action, fieldName)
|
||||
|
||||
const inverseActionCode = action === 'show' ? `hideField('${fieldName}')` :
|
||||
action === 'hide' ? `showField('${fieldName}')` :
|
||||
action === 'enable' ? `disableField('${fieldName}')` :
|
||||
action === 'disable' ? `enableField('${fieldName}')` :
|
||||
`hideField('${fieldName}')`
|
||||
|
||||
// Generate field change listeners
|
||||
// Get all watched fields for this component
|
||||
const watchedFields = [...new Set(conditions.map(c => c.field).filter(Boolean))]
|
||||
|
||||
// Group logic by watched field to avoid duplicates
|
||||
watchedFields.forEach(watchField => {
|
||||
const script = `
|
||||
// Conditional logic for field: ${fieldName}
|
||||
onFieldChange('${watchField}', function() {
|
||||
if (${conditionChecks}) {
|
||||
${actionCode};
|
||||
} else {
|
||||
${inverseActionCode};
|
||||
}
|
||||
});`
|
||||
scriptsArray.push(script)
|
||||
if (!fieldWatchers.has(watchField)) {
|
||||
fieldWatchers.set(watchField, {
|
||||
fieldName: watchField,
|
||||
logicBlocks: []
|
||||
})
|
||||
}
|
||||
|
||||
// Add this component's logic to the watched field
|
||||
fieldWatchers.get(watchField).logicBlocks.push({
|
||||
targetField: fieldName,
|
||||
conditionChecks,
|
||||
actionCode,
|
||||
inverseActionCode,
|
||||
componentType: component.type
|
||||
})
|
||||
})
|
||||
|
||||
// Also add initial evaluation
|
||||
const initialScript = `
|
||||
// Initial evaluation for field: ${fieldName}
|
||||
(function() {
|
||||
if (${conditionChecks}) {
|
||||
${actionCode};
|
||||
} else {
|
||||
${inverseActionCode};
|
||||
}
|
||||
})();`
|
||||
scriptsArray.push(initialScript)
|
||||
// Add initial evaluation
|
||||
initialEvaluations.push({
|
||||
targetField: fieldName,
|
||||
conditionChecks,
|
||||
actionCode,
|
||||
inverseActionCode,
|
||||
componentType: component.type
|
||||
})
|
||||
})
|
||||
|
||||
// Generate optimized field change handlers (one per watched field)
|
||||
fieldWatchers.forEach((watcher, watchField) => {
|
||||
const logicBlocks = watcher.logicBlocks.map(block => `
|
||||
// Logic for ${block.componentType} field: ${block.targetField}
|
||||
if (${block.conditionChecks}) {
|
||||
${block.actionCode};
|
||||
} else {
|
||||
${block.inverseActionCode};
|
||||
}`).join('')
|
||||
|
||||
const script = `
|
||||
// Optimized field change handler for: ${watchField}
|
||||
// Handles ${watcher.logicBlocks.length} dependent field(s): ${watcher.logicBlocks.map(b => b.targetField).join(', ')}
|
||||
onFieldChange('${watchField}', function() {${logicBlocks}
|
||||
});`
|
||||
|
||||
scriptsArray.push(script)
|
||||
})
|
||||
|
||||
// Generate initial evaluations
|
||||
if (initialEvaluations.length > 0) {
|
||||
const initialLogicBlocks = initialEvaluations.map(block => `
|
||||
// Initial evaluation for ${block.componentType} field: ${block.targetField}
|
||||
if (${block.conditionChecks}) {
|
||||
${block.actionCode};
|
||||
} else {
|
||||
${block.inverseActionCode};
|
||||
}`).join('')
|
||||
|
||||
const initialScript = `
|
||||
// Initial conditional logic evaluation
|
||||
(function() {${initialLogicBlocks}
|
||||
})();`
|
||||
|
||||
scriptsArray.push(initialScript)
|
||||
}
|
||||
|
||||
return scriptsArray.join('\n\n')
|
||||
}
|
||||
|
||||
|
108
components/ConditionalLogicTestDemo.vue
Normal file
108
components/ConditionalLogicTestDemo.vue
Normal file
@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<div class="conditional-logic-demo p-6 bg-gray-50 rounded-lg">
|
||||
<h3 class="text-lg font-semibold mb-4">Conditional Logic Optimization Demo</h3>
|
||||
|
||||
<!-- Before Optimization Example -->
|
||||
<div class="mb-6">
|
||||
<h4 class="text-md font-medium text-red-600 mb-2">❌ Before Optimization (Duplicate Handlers)</h4>
|
||||
<div class="bg-red-50 p-4 rounded border border-red-200">
|
||||
<pre class="text-sm text-red-800 whitespace-pre-wrap">{{ beforeOptimization }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- After Optimization Example -->
|
||||
<div class="mb-6">
|
||||
<h4 class="text-md font-medium text-green-600 mb-2">✅ After Optimization (Grouped Handlers)</h4>
|
||||
<div class="bg-green-50 p-4 rounded border border-green-200">
|
||||
<pre class="text-sm text-green-800 whitespace-pre-wrap">{{ afterOptimization }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Benefits -->
|
||||
<div class="bg-blue-50 p-4 rounded border border-blue-200">
|
||||
<h4 class="text-md font-medium text-blue-800 mb-2">🚀 Benefits of Optimization</h4>
|
||||
<ul class="text-sm text-blue-700 space-y-1">
|
||||
<li>• <strong>Reduced Memory Usage:</strong> Only one handler per watched field instead of multiple duplicates</li>
|
||||
<li>• <strong>Better Performance:</strong> Fewer function calls when field values change</li>
|
||||
<li>• <strong>Cleaner Code:</strong> Grouped logic is easier to read and maintain</li>
|
||||
<li>• <strong>No Conflicts:</strong> Eliminates potential conflicts between duplicate handlers</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
// Example scenario: 3 fields (field2, field3, field4) all depend on field1
|
||||
const beforeOptimization = computed(() => `// BEFORE: Multiple duplicate handlers for the same field
|
||||
// This creates performance issues and potential conflicts
|
||||
|
||||
// Conditional logic for field: field2
|
||||
onFieldChange('field1', function() {
|
||||
if (getField('field1') === 'show') {
|
||||
showField('field2');
|
||||
} else {
|
||||
hideField('field2');
|
||||
}
|
||||
});
|
||||
|
||||
// Conditional logic for field: field3
|
||||
onFieldChange('field1', function() {
|
||||
if (getField('field1') === 'show') {
|
||||
showField('field3');
|
||||
} else {
|
||||
hideField('field3');
|
||||
}
|
||||
});
|
||||
|
||||
// Conditional logic for field: field4
|
||||
onFieldChange('field1', function() {
|
||||
if (getField('field1') === 'show') {
|
||||
showField('field4');
|
||||
} else {
|
||||
hideField('field4');
|
||||
}
|
||||
});
|
||||
|
||||
// Problem: 3 duplicate handlers listening to the same field!`)
|
||||
|
||||
const afterOptimization = computed(() => `// AFTER: Single optimized handler for all dependent fields
|
||||
// Much more efficient and prevents conflicts
|
||||
|
||||
// Optimized field change handler for: field1
|
||||
// Handles 3 dependent field(s): field2, field3, field4
|
||||
onFieldChange('field1', function() {
|
||||
// Logic for text field: field2
|
||||
if (getField('field1') === 'show') {
|
||||
showField('field2');
|
||||
} else {
|
||||
hideField('field2');
|
||||
}
|
||||
|
||||
// Logic for text field: field3
|
||||
if (getField('field1') === 'show') {
|
||||
showField('field3');
|
||||
} else {
|
||||
hideField('field3');
|
||||
}
|
||||
|
||||
// Logic for text field: field4
|
||||
if (getField('field1') === 'show') {
|
||||
showField('field4');
|
||||
} else {
|
||||
hideField('field4');
|
||||
}
|
||||
});
|
||||
|
||||
// Solution: Only 1 handler managing all dependent fields!`)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
pre {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
overflow-x: auto;
|
||||
}
|
||||
</style>
|
@ -2787,8 +2787,22 @@ const generateConditionalLogicCode = () => {
|
||||
const { conditions, action, operator } = configModel.value.conditionalLogic
|
||||
const fieldName = configModel.value.name || 'this_field'
|
||||
|
||||
// Generate condition checks
|
||||
const conditionChecks = conditions.map(condition => {
|
||||
// Helper function to map operators to valid JavaScript
|
||||
const getJavaScriptOperator = (operator) => {
|
||||
switch (operator) {
|
||||
case 'and':
|
||||
case 'AND':
|
||||
return '&&'
|
||||
case 'or':
|
||||
case 'OR':
|
||||
return '||'
|
||||
default:
|
||||
return '&&' // Default to AND if unknown operator
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to generate condition check
|
||||
const generateConditionCheck = (condition) => {
|
||||
const { field, operator: condOp, value } = condition
|
||||
|
||||
switch (condOp) {
|
||||
@ -2797,9 +2811,9 @@ const generateConditionalLogicCode = () => {
|
||||
case 'not_equals':
|
||||
return `getField('${field}') !== '${value}'`
|
||||
case 'contains':
|
||||
return `String(getField('${field}')).includes('${value}')`
|
||||
return `String(getField('${field}') || '').toLowerCase().includes('${value}'.toLowerCase())`
|
||||
case 'not_contains':
|
||||
return `!String(getField('${field}')).includes('${value}')`
|
||||
return `!String(getField('${field}') || '').toLowerCase().includes('${value}'.toLowerCase())`
|
||||
case 'is_empty':
|
||||
return `!getField('${field}') || getField('${field}') === ''`
|
||||
case 'is_not_empty':
|
||||
@ -2811,32 +2825,56 @@ const generateConditionalLogicCode = () => {
|
||||
default:
|
||||
return `getField('${field}') === '${value}'`
|
||||
}
|
||||
}).join(` ${operator} `)
|
||||
}
|
||||
|
||||
// Generate action
|
||||
const actionCode = action === 'show' ? `showField('${fieldName}')` :
|
||||
action === 'hide' ? `hideField('${fieldName}')` :
|
||||
action === 'enable' ? `enableField('${fieldName}')` :
|
||||
action === 'disable' ? `disableField('${fieldName}')` :
|
||||
`showField('${fieldName}')`
|
||||
// Helper function to generate action code
|
||||
const generateActionCode = (action, fieldName) => {
|
||||
switch (action) {
|
||||
case 'show': return `showField('${fieldName}')`
|
||||
case 'hide': return `hideField('${fieldName}')`
|
||||
case 'enable': return `enableField('${fieldName}')`
|
||||
case 'disable': return `disableField('${fieldName}')`
|
||||
default: return `showField('${fieldName}')`
|
||||
}
|
||||
}
|
||||
|
||||
// Generate field change listeners
|
||||
// Helper function to generate inverse action code
|
||||
const generateInverseActionCode = (action, fieldName) => {
|
||||
switch (action) {
|
||||
case 'show': return `hideField('${fieldName}')`
|
||||
case 'hide': return `showField('${fieldName}')`
|
||||
case 'enable': return `disableField('${fieldName}')`
|
||||
case 'disable': return `enableField('${fieldName}')`
|
||||
default: return `hideField('${fieldName}')`
|
||||
}
|
||||
}
|
||||
|
||||
// Generate condition checks
|
||||
const conditionChecks = conditions.map(generateConditionCheck).join(` ${getJavaScriptOperator(operator)} `)
|
||||
|
||||
// Generate action functions
|
||||
const actionCode = generateActionCode(action, fieldName)
|
||||
const inverseActionCode = generateInverseActionCode(action, fieldName)
|
||||
|
||||
// Generate field change listeners - note about optimization
|
||||
const watchedFields = [...new Set(conditions.map(c => c.field).filter(Boolean))]
|
||||
|
||||
// Add helpful comment about optimization when multiple fields watch the same condition
|
||||
const optimizationNote = watchedFields.length > 1 ?
|
||||
`// NOTE: When multiple fields use the same trigger field, the FormBuilder automatically\n// optimizes by grouping these handlers to avoid duplicate onFieldChange listeners.\n\n` :
|
||||
''
|
||||
|
||||
const listenerCode = watchedFields.map(field =>
|
||||
`onFieldChange('${field}', function() {
|
||||
if (${conditionChecks}) {
|
||||
${actionCode};
|
||||
} else {
|
||||
${action === 'show' ? `hideField('${fieldName}')` :
|
||||
action === 'hide' ? `showField('${fieldName}')` :
|
||||
action === 'enable' ? `disableField('${fieldName}')` :
|
||||
action === 'disable' ? `enableField('${fieldName}')` :
|
||||
`hideField('${fieldName}')`};
|
||||
${inverseActionCode};
|
||||
}
|
||||
});`
|
||||
).join('\n\n')
|
||||
|
||||
return listenerCode || '// Invalid configuration'
|
||||
return optimizationNote + (listenerCode || '// Invalid configuration')
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
|
@ -296,7 +296,7 @@
|
||||
v-for="(component, index) in formStore.formComponents"
|
||||
:key="index"
|
||||
>
|
||||
<component-preview :component="component" :is-preview="false" />
|
||||
<component-preview :component="component" :is-preview="true" />
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@ -904,6 +904,20 @@
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<!-- Conditional Logic Optimization -->
|
||||
<div class="mb-4 p-3 bg-amber-50 rounded border border-amber-200">
|
||||
<details>
|
||||
<summary class="text-sm font-medium text-amber-800 cursor-pointer">⚡ Conditional Logic Optimization</summary>
|
||||
<div class="mt-2 text-xs text-amber-700 space-y-1">
|
||||
<div><strong>Automatic Optimization:</strong> When multiple fields use <code>onFieldChange</code> with the same trigger field:</div>
|
||||
<div>• The form builder automatically groups them into a single handler</div>
|
||||
<div>• No need to worry about duplicate event listeners</div>
|
||||
<div>• Better performance and memory usage</div>
|
||||
<div>• Use field settings UI for visual condition setup, or write custom logic here</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<!-- Utility Functions -->
|
||||
<div class="mb-4 p-3 bg-purple-50 rounded border border-purple-200">
|
||||
<details>
|
||||
@ -980,6 +994,20 @@
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<!-- Performance Optimization Notice -->
|
||||
<div class="mb-4 p-3 bg-blue-50 rounded border border-blue-200">
|
||||
<details>
|
||||
<summary class="text-sm font-medium text-blue-800 cursor-pointer">⚡ Performance Optimization</summary>
|
||||
<div class="mt-2 text-xs text-blue-700 space-y-1">
|
||||
<div><strong>Smart Field Watching:</strong> The form builder automatically optimizes conditional logic</div>
|
||||
<div>• Groups multiple field conditions that watch the same trigger field</div>
|
||||
<div>• Prevents duplicate <code>onFieldChange</code> handlers</div>
|
||||
<div>• Improves performance and reduces memory usage</div>
|
||||
<div>• Example: If 5 fields depend on "customer_type", only 1 handler is created</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<RsCodeMirror
|
||||
|
@ -1730,7 +1730,7 @@ const getWorkflowSubmitButtonStyle = () => {
|
||||
<template v-for="component in currentForm.formComponents" :key="component.id || component.props.name">
|
||||
<ComponentPreview
|
||||
:component="component"
|
||||
:is-preview="false"
|
||||
:is-preview="true"
|
||||
:field-states="fieldStates"
|
||||
@form-data-updated="handleFormDataUpdate"
|
||||
/>
|
||||
|
@ -83,44 +83,7 @@ export default defineEventHandler(async (event) => {
|
||||
|
||||
console.log('Case instance created:', caseInstance.caseUUID);
|
||||
|
||||
// Get the process definition
|
||||
const processDefinition = process.processDefinition || {};
|
||||
const nodes = processDefinition.nodes || [];
|
||||
const edges = processDefinition.edges || [];
|
||||
|
||||
// Find all form nodes
|
||||
const formNodes = nodes.filter(node => node.type === 'form');
|
||||
|
||||
if (formNodes.length === 0) {
|
||||
console.log('No form nodes found in process');
|
||||
return {
|
||||
success: false,
|
||||
error: 'Process does not contain any forms'
|
||||
};
|
||||
}
|
||||
|
||||
console.log(`Found ${formNodes.length} form nodes`);
|
||||
|
||||
// Create tasks for all forms
|
||||
const tasks = [];
|
||||
for (const formNode of formNodes) {
|
||||
console.log('Creating task for form:', formNode.data?.label);
|
||||
const task = await prisma.task.create({
|
||||
data: {
|
||||
taskUUID: uuidv4(),
|
||||
caseID: caseInstance.caseID,
|
||||
taskName: formNode.data?.label || 'Complete Form',
|
||||
taskType: 'form',
|
||||
taskStatus: 'pending',
|
||||
taskAssignedTo: currentUser.userID,
|
||||
taskFormID: formNode.data?.formId,
|
||||
taskCreatedDate: new Date(),
|
||||
taskModifiedDate: new Date()
|
||||
}
|
||||
});
|
||||
tasks.push(task);
|
||||
console.log('Task created:', task.taskUUID);
|
||||
}
|
||||
console.log('Process started successfully - tasks will be created dynamically during workflow execution');
|
||||
|
||||
// Add to case timeline
|
||||
console.log('Adding to case timeline...');
|
||||
@ -143,14 +106,7 @@ export default defineEventHandler(async (event) => {
|
||||
name: caseInstance.caseName,
|
||||
status: caseInstance.caseStatus,
|
||||
startedAt: caseInstance.caseCreatedDate
|
||||
},
|
||||
tasks: tasks.map(task => ({
|
||||
id: task.taskUUID,
|
||||
name: task.taskName,
|
||||
type: task.taskType,
|
||||
formId: task.taskFormID,
|
||||
status: task.taskStatus
|
||||
}))
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user