corrad-bp/components/ConditionalLogicEngine.vue
Afiq 577128a799 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.
2025-08-06 18:31:56 +08:00

222 lines
6.4 KiB
Vue

<template>
<div class="conditional-logic-engine">
<!-- This component handles conditional logic execution -->
</div>
</template>
<script setup>
import { ref, onMounted, watch, nextTick } from 'vue'
const props = defineProps({
formComponents: {
type: Array,
required: true
},
formData: {
type: Object,
required: true
}
})
const emit = defineEmits(['script-generated'])
// Internal state
const generatedScript = ref('')
const isInitialized = ref(false)
// 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
}
}
// 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 => {
const conditionalLogic = component.props.conditionalLogic
if (!conditionalLogic || !conditionalLogic.enabled || !conditionalLogic.conditions.length) {
return
}
const { conditions, action, operator } = conditionalLogic
const fieldName = component.props.name
if (!fieldName) return
// Generate condition checks
const conditionChecks = conditions.map(generateConditionCheck).join(` ${getJavaScriptOperator(operator)} `)
// Generate action functions
const actionCode = generateActionCode(action, fieldName)
const inverseActionCode = generateInverseActionCode(action, fieldName)
// 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 => {
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
})
})
// 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')
}
// Update generated script when components change
watch(() => props.formComponents, () => {
if (isInitialized.value) {
updateConditionalLogic()
}
}, { deep: true })
// Update conditional logic
const updateConditionalLogic = () => {
const newScript = generateConditionalLogicScript()
generatedScript.value = newScript
// Emit the generated script to parent component
emit('script-generated', newScript)
}
// Initialize on mount
onMounted(() => {
nextTick(() => {
updateConditionalLogic()
isInitialized.value = true
})
})
// Expose the generated script for debugging
defineExpose({
generatedScript,
updateConditionalLogic
})
</script>
<style scoped>
.conditional-logic-engine {
display: none; /* This component is purely functional */
}
</style>