fix: 跳转逻辑优化 (#397)

* fix: 跳转逻辑优化

* fix: processJumpSkip逻辑放在题目组件中
This commit is contained in:
dayou 2024-08-14 16:30:11 +08:00 committed by GitHub
parent 965dc89c54
commit adec40cfc2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 134 additions and 176 deletions

View File

@ -115,7 +115,7 @@ export class RuleBuild {
}) })
} }
// 实现前置题删除校验 // 实现前置题删除校验
findTargetsByFields(field: string) { findTargetsByField(field: string) {
const nodes = this.findRulesByField(field) const nodes = this.findRulesByField(field)
return nodes.map((item: any) => { return nodes.map((item: any) => {
return item.target return item.target

View File

@ -174,22 +174,31 @@ export class RuleMatch {
this.rules.set(hash, rule) this.rules.set(hash, rule)
} }
// 匹配条件规则 // 特定目标题规则匹配
match(target: string, scope: string, fact: Fact, comparor?: any) { match(target: string, scope: string, fact: Fact, comparor?: any) {
const hash = this.calculateHash(target, scope) const hash = this.calculateHash(target, scope)
const rule = this.rules.get(hash) const rule = this.rules.get(hash)
if (rule) { if (rule) {
const result = rule.match(fact, comparor) const result = rule.match(fact, comparor)
// this.matchCache.set(hash, result);
return result return result
} else { } else {
// 默认显示 // 默认显示
return true return true
} }
} }
/* 获取条件题关联的多个目标题匹配情况 */
getResult(target: string, scope: string) { getResultsByField(field: string, fact: Fact) {
const rules = this.findRulesByField(field)
return rules.map(([, rule]) => {
return {
target: rule.target,
result: this.match(rule.target, 'question', fact, 'or')
}
})
}
/* 获取目标题的规则是否匹配 */
getResultByTarget(target: string, scope: string) {
const hash = this.calculateHash(target, scope) const hash = this.calculateHash(target, scope)
const rule = this.rules.get(hash) const rule = this.rules.get(hash)
if (rule) { if (rule) {
@ -205,16 +214,7 @@ export class RuleMatch {
// 假设哈希值计算方法为简单的字符串拼接或其他哈希算法 // 假设哈希值计算方法为简单的字符串拼接或其他哈希算法
return target + scope return target + scope
} }
findTargetsByField(field: string) { // 查找条件题的规则
const rules = new Map(
[...this.rules.entries()].filter(([, value]) => {
return [...value.conditions.entries()].filter(([, value]) => {
return value.field === field
}).length
})
)
return [...rules.values()].map((obj) => obj.target)
}
findRulesByField(field: string) { findRulesByField(field: string) {
const list = [...this.rules.entries()] const list = [...this.rules.entries()]
const match = list.filter(([, ruleValue]) => { const match = list.filter(([, ruleValue]) => {
@ -225,19 +225,8 @@ export class RuleMatch {
}) })
return res.length return res.length
}) })
console.log({ match })
return match return match
} }
findFieldsByTarget(target: string) {
const rules = new Map(
[...this.rules.entries()].filter(([, value]) => {
return value.target === target
})
)
return [...rules.values()].map((obj) =>
[...obj.conditions.entries()].map(([, value]) => value.field)
)
}
toJson() { toJson() {
return Array.from(this.rules.entries()).map(([, value]) => { return Array.from(this.rules.entries()).map(([, value]) => {
return value.toJson() return value.toJson()

View File

@ -10,7 +10,7 @@ export const useJumpLogicInfo = (field) => {
const hasJumpLogic = computed(() => { const hasJumpLogic = computed(() => {
const logicEngine = jumpLogicEngine.value const logicEngine = jumpLogicEngine.value
// 判断该题是否作为了跳转逻辑条件 // 判断该题是否作为了跳转逻辑条件
const isField = logicEngine?.findTargetsByFields(field)?.length > 0 const isField = logicEngine?.findTargetsByField(field)?.length > 0
// 判断该题是否作为了跳转目标 // 判断该题是否作为了跳转目标
const isTarget = logicEngine?.findConditionByTarget(field)?.length > 0 const isTarget = logicEngine?.findConditionByTarget(field)?.length > 0
return isField || isTarget return isField || isTarget

View File

@ -12,7 +12,7 @@ export const useShowLogicInfo = (field) => {
const hasShowLogic = computed(() => { const hasShowLogic = computed(() => {
const logicEngine = showLogicEngine.value const logicEngine = showLogicEngine.value
// 判断该题是否作为了显示逻辑前置题 // 判断该题是否作为了显示逻辑前置题
const isField = logicEngine?.findTargetsByFields(field)?.length > 0 const isField = logicEngine?.findTargetsByField(field)?.length > 0
// 判断该题是否作为了显示逻辑目标题 // 判断该题是否作为了显示逻辑目标题
const isTarget = logicEngine?.findConditionByTarget(field)?.length > 0 const isTarget = logicEngine?.findConditionByTarget(field)?.length > 0
return isField || isTarget return isField || isTarget

View File

@ -142,7 +142,14 @@ const onMoveDown = () => {
} }
const onDelete = async () => { const onDelete = async () => {
if (unref(hasShowLogic) || getShowLogicText.value) { if (unref(hasShowLogic) || getShowLogicText.value) {
ElMessageBox.alert('该问题被逻辑依赖,请先删除逻辑依赖', '提示', { ElMessageBox.alert('该题目被显示逻辑关联,请先清除逻辑依赖', '提示', {
confirmButtonText: '确定',
type: 'warning'
})
return
}
if (unref(hasJumpLogic)) {
ElMessageBox.alert('该题目被跳转逻辑关联,请先清除逻辑依赖', '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
type: 'warning' type: 'warning'
}) })

View File

@ -7,7 +7,7 @@ const adapter = (() => {
const list = [] const list = []
const exec = (questionData) => { const exec = (questionData) => {
return list.reduce((pre, next) => ({ ...pre, ...next(questionData, pre) }), {}) return list.reduce((pre, next, index) => ({ ...pre, index, ...next(questionData, pre) }), {})
} }
return { return {

View File

@ -7,11 +7,12 @@ import { get as _get } from 'lodash-es'
export default function (questionConfig) { export default function (questionConfig) {
let dataList = _get(questionConfig, 'dataConf.dataList') let dataList = _get(questionConfig, 'dataConf.dataList')
// 将题目列表转成对象,并且对题目类型、题目的选项做一些字段的增加和转换 // 将题目列表转成对象,并且对题目类型、题目的选项做一些字段的增加和转换
const questionData = dataList.reduce((pre, item) => { const questionData = dataList.reduce((pre, item, index) => {
Object.assign(pre, { Object.assign(pre, {
[item.field]: { [item.field]: {
indexNumber: '', indexNumber: '',
voteTotal: 0, voteTotal: 0,
index,
...item ...item
} }
}) })

View File

@ -69,36 +69,6 @@ onBeforeMount(() => {
} }
}) })
}) })
// const visible = computed(() => {
// return (index) => {
// const field = props.renderData[index].field
// const jumpRely = flatten(ruleEngine.findFieldsByTarget(field))
// const jumpRelyIndexs = jumpRely.map(item => props.renderData.map(i => i.field).findIndex(i=> i === item))
// const jumpRelyIndex = Math.max(...jumpRelyIndexs)
// const jumpTargetIndex = 5
// // props.renderData.map(i => i.field).findIndex(i=> i === item)
// const jumpTargetMatch = ruleEngine.getResult(field, 'question')
// console.log({index, field, jumpRely, jumpRelyIndexs, jumpRelyIndex, jumpTargetMatch})
// if(jumpTargetMatch) {
// return !(jumpRelyIndex < index && index < jumpTargetIndex)
// } else {
// return true
// }
// }
// const jumpMatch = computed(() => {
// return (field) => {
// return ruleEngine.match(field, 'question', formValues.value)
// }
// })
// watch(()=> jumpMatch,
// (newVal, oldVal) => {
// if(newVal) {
// // renderDatasplit
// handleJump(field, formValues.value)
// }
// })
const validate = (callback) => { const validate = (callback) => {
const length = fields.length const length = fields.length

View File

@ -8,7 +8,7 @@
></QuestionRuleContainer> ></QuestionRuleContainer>
</template> </template>
<script setup> <script setup>
import { unref, ref, computed, watch } from 'vue' import { unref, computed, watch } from 'vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import QuestionRuleContainer from '../../materials/questions/QuestionRuleContainer' import QuestionRuleContainer from '../../materials/questions/QuestionRuleContainer'
import { useVoteMap } from '@/render/hooks/useVoteMap' import { useVoteMap } from '@/render/hooks/useVoteMap'
@ -19,7 +19,6 @@ import { useQuestionStore } from '../stores/question'
import { useSurveyStore } from '../stores/survey' import { useSurveyStore } from '../stores/survey'
import { NORMAL_CHOICES, RATES, QUESTION_TYPE } from '@/common/typeEnum.ts' import { NORMAL_CHOICES, RATES, QUESTION_TYPE } from '@/common/typeEnum.ts'
import { getQuestionIndexByField, findMinKeyInMap } from '@/render/utils/index.js'
const props = defineProps({ const props = defineProps({
indexNumber: { indexNumber: {
@ -31,7 +30,7 @@ const props = defineProps({
default: () => { default: () => {
return {} return {}
} }
} },
}) })
const emit = defineEmits(['change']) const emit = defineEmits(['change'])
const questionStore = useQuestionStore() const questionStore = useQuestionStore()
@ -40,17 +39,26 @@ const surveyStore = useSurveyStore()
const formValues = computed(() => { const formValues = computed(() => {
return surveyStore.formValues return surveyStore.formValues
}) })
const { dataConf, changeField, showLogicEngine, jumpLogicEngine } = storeToRefs(surveyStore) const { showLogicEngine } = storeToRefs(surveyStore)
const {
changeField,
changeIndex,
needHideFields,
} = storeToRefs(questionStore)
//
const questionConfig = computed(() => { const questionConfig = computed(() => {
let moduleConfig = props.moduleConfig let moduleConfig = props.moduleConfig
const { type, field, options = [], ...rest } = cloneDeep(moduleConfig) const { type, field, options = [], ...rest } = cloneDeep(moduleConfig)
// console.log(field,'formValuechange')
let alloptions = options let alloptions = options
if (type === QUESTION_TYPE.VOTE) { if (type === QUESTION_TYPE.VOTE) {
const { options, voteTotal } = useVoteMap(field) const { options, voteTotal } = useVoteMap(field)
const voteOptions = unref(options) const voteOptions = unref(options)
alloptions = alloptions.map((obj, index) => Object.assign(obj, voteOptions[index])) alloptions = alloptions.map((obj, index) => Object.assign(obj, voteOptions[index]))
moduleConfig.voteTotal = unref(voteTotal) moduleConfig.voteTotal = unref(voteTotal)
} }
if ( if (
NORMAL_CHOICES.includes(type) && NORMAL_CHOICES.includes(type) &&
options.filter((optionItem) => optionItem.others).length > 0 options.filter((optionItem) => optionItem.others).length > 0
@ -60,6 +68,7 @@ const questionConfig = computed(() => {
alloptions = alloptions.map((obj, index) => Object.assign(obj, othersOptions[index])) alloptions = alloptions.map((obj, index) => Object.assign(obj, othersOptions[index]))
moduleConfig.othersValue = unref(othersValue) moduleConfig.othersValue = unref(othersValue)
} }
if ( if (
RATES.includes(type) && RATES.includes(type) &&
rest?.rangeConfig && rest?.rangeConfig &&
@ -78,18 +87,29 @@ const questionConfig = computed(() => {
} }
}) })
const showMatch = computed(() => { const logicshow = computed(() => {
// computedmatch // computedmatch
const result = showLogicEngine.value.match(props.moduleConfig.field, 'question', formValues.value) const result = showLogicEngine.value.match(props.moduleConfig.field, 'question', formValues.value)
return result === undefined ? true : result return result === undefined ? true : result
}) })
const logicskip = computed(() => {
return needHideFields.value.includes(props.moduleConfig.field)
})
const visibily = computed(() => {
return logicshow.value && !logicskip.value
})
// abbcbc
watch( watch(
() => showMatch.value, () => visibily.value,
(newVal, oldVal) => { (newVal, oldVal) => {
//
const { field, type, innerType } = props.moduleConfig const { field, type, innerType } = props.moduleConfig
if (!newVal && oldVal) { if (!newVal && oldVal) {
//
if(formValues.value[field].toString()) {
let value = '' let value = ''
// innerType // innerType
if (type === QUESTION_TYPE.CHECKBOX || innerType === QUESTION_TYPE.CHECKBOX) { if (type === QUESTION_TYPE.CHECKBOX || innerType === QUESTION_TYPE.CHECKBOX) {
@ -100,82 +120,10 @@ watch(
value: value value: value
} }
surveyStore.changeData(data) surveyStore.changeData(data)
processJumpSkip()
} }
} }
)
const jumpSkip = ref(false)
const visibily = computed(() => {
return showMatch.value && !jumpSkip.value
})
// formValues
watch(
() => formValues,
(newVal) => {
const currentIndex = getQuestionIndexByField(dataConf.value.dataList, questionConfig.value.field)
const changeIndex = getQuestionIndexByField(dataConf.value.dataList, changeField.value)
//
if (currentIndex < changeIndex) {
return
} }
//
const rules = jumpLogicEngine.value.findRulesByField(changeField.value)
// change
if (!rules.length) {
return
}
//
const targetsResult = new Map()
//
if (rules.length === 1) {
rules.forEach(([, rule]) => {
const index = getQuestionIndexByField(dataConf.value.dataList, rule.target)
targetsResult.set(
index,
jumpLogicEngine.value.match(rule.target, 'question', newVal.value, 'or')
)
})
} else {
// ,
const optionJump = rules.filter(([, rule]) => {
//
const conditionhash = `${changeField.value}neq`
return !rule.conditions.get(conditionhash)
})
if (optionJump.length) {
optionJump.forEach(([, rule]) => {
const index = getQuestionIndexByField(dataConf.value.dataList, rule.target)
targetsResult.set(
index,
jumpLogicEngine.value.match(rule.target, 'question', newVal.value, 'or')
)
})
} else {
const answerJump = rules.find(([, rule]) => {
const conditionhash = `${changeField.value}neq`
return rule.conditions.get(conditionhash)
})
const index = getQuestionIndexByField(dataConf.value.dataList, answerJump[1].target)
targetsResult.set(
index,
jumpLogicEngine.value.match(answerJump[1].target, 'question', newVal.value, 'or')
)
}
}
const jumpFitMinIndex = findMinKeyInMap(targetsResult, true)
const jumpQuestion = currentIndex < jumpFitMinIndex
const jumpEnd = jumpFitMinIndex === -1 && rules.map(([, rule]) => rule.target).includes('end')
if (changeIndex < currentIndex && (jumpQuestion || jumpEnd)) {
jumpSkip.value = true
} else {
jumpSkip.value = false
}
},
{ deep: true }
) )
const handleChange = (data) => { const handleChange = (data) => {
@ -184,5 +132,41 @@ const handleChange = (data) => {
if (props.moduleConfig.type === QUESTION_TYPE.VOTE) { if (props.moduleConfig.type === QUESTION_TYPE.VOTE) {
questionStore.updateVoteData(data) questionStore.updateVoteData(data)
} }
processJumpSkip()
} }
const processJumpSkip = () => {
const targetResult = surveyStore.jumpLogicEngine
.getResultsByField(changeField.value, surveyStore.formValues)
.map(item => {
//
const index = item.target === 'end' ? surveyStore.dataConf.dataList.length : questionStore.getQuestionIndexByField(item.target)
return {
index,
...item
}
})
const notMatchedFields = targetResult.filter(item => !item.result)
const matchedFields = targetResult.filter(item => item.result)
//
if (notMatchedFields.length) {
notMatchedFields.forEach(element => {
const endIndex = element.index
const fields = surveyStore.dataConf.dataList.slice(changeIndex.value + 1, endIndex).map(item => item.field)
// hideMapremove
questionStore.removeNeedHideFields(fields)
});
}
if (!matchedFields.length) return
//
const maxIndexQuestion =
matchedFields.filter(item => item.result).sort((a, b) => b.index - a.index)[0].index
//
const skipKey = surveyStore.dataConf.dataList
.slice(changeIndex.value + 1, maxIndexQuestion).map(item => item.field)
questionStore.addNeedHideFields(skipKey)
}
</script> </script>

View File

@ -11,6 +11,11 @@ export const useQuestionStore = defineStore('question', () => {
const questionData = ref(null) const questionData = ref(null)
const questionSeq = ref([]) // 题目的顺序,因为可能会有分页的情况,所以是一个二维数组[[qid1, qid2], [qid3,qid4]] const questionSeq = ref([]) // 题目的顺序,因为可能会有分页的情况,所以是一个二维数组[[qid1, qid2], [qid3,qid4]]
const pageIndex = ref(1) // 当前分页的索引 const pageIndex = ref(1) // 当前分页的索引
const changeField = ref(null)
const changeIndex = computed(() => {
return questionData.value[changeField.value].index
})
const needHideFields = ref([])
// 题目列表 // 题目列表
const questionList = computed(() => { const questionList = computed(() => {
@ -177,6 +182,22 @@ export const useQuestionStore = defineStore('question', () => {
}) })
} }
const setChangeField = (field) => {
changeField.value = field
}
const getQuestionIndexByField = (field) => {
return questionData.value[field].index
}
const addNeedHideFields = (fields) => {
fields.forEach(field => {
if(!needHideFields.value.includes(field)) {
needHideFields.value.push(field)
}
})
}
const removeNeedHideFields = (fields) => {
needHideFields.value = needHideFields.value.filter(field => !fields.includes(field))
}
return { return {
voteMap, voteMap,
questionData, questionData,
@ -191,6 +212,13 @@ export const useQuestionStore = defineStore('question', () => {
setVoteMap, setVoteMap,
updateVoteMapByKey, updateVoteMapByKey,
initVoteData, initVoteData,
updateVoteData updateVoteData,
changeField,
changeIndex,
setChangeField,
needHideFields,
addNeedHideFields,
removeNeedHideFields,
getQuestionIndexByField
} }
}) })

View File

@ -41,7 +41,7 @@ export const useSurveyStore = defineStore('survey', () => {
const formValues = ref({}) const formValues = ref({})
const whiteData = ref({}) const whiteData = ref({})
const pageConf = ref([]) const pageConf = ref([])
const changeField = ref(null)
const router = useRouter() const router = useRouter()
const questionStore = useQuestionStore() const questionStore = useQuestionStore()
@ -156,10 +156,10 @@ export const useSurveyStore = defineStore('survey', () => {
// 用户输入或者选择后,更新表单数据 // 用户输入或者选择后,更新表单数据
const changeData = (data) => { const changeData = (data) => {
let { key, value } = data let { key, value } = data
changeField.value = key
if (key in formValues.value) { if (key in formValues.value) {
formValues.value[key] = value formValues.value[key] = value
} }
questionStore.setChangeField(key)
} }
const showLogicEngine = ref() const showLogicEngine = ref()
@ -186,7 +186,6 @@ export const useSurveyStore = defineStore('survey', () => {
formValues, formValues,
whiteData, whiteData,
pageConf, pageConf,
changeField,
initSurvey, initSurvey,
changeData, changeData,
setWhiteData, setWhiteData,

View File

@ -30,23 +30,3 @@ export const formatLink = (url) => {
} }
return `http://${url}` return `http://${url}`
} }
export const getQuestionIndexByField = (questionList, field) => {
const arr = questionList.map((item) => item.field)
const index = arr.findIndex((item) => item === field)
return index
}
export function findMinKeyInMap(map, hit) {
let minKey = null
for (const [key, value] of map) {
if (value === hit) {
if (minKey === null || key < minKey) {
minKey = key
}
}
}
return minKey
}