parent
965dc89c54
commit
adec40cfc2
@ -115,7 +115,7 @@ export class RuleBuild {
|
||||
})
|
||||
}
|
||||
// 实现前置题删除校验
|
||||
findTargetsByFields(field: string) {
|
||||
findTargetsByField(field: string) {
|
||||
const nodes = this.findRulesByField(field)
|
||||
return nodes.map((item: any) => {
|
||||
return item.target
|
||||
|
@ -174,22 +174,31 @@ export class RuleMatch {
|
||||
this.rules.set(hash, rule)
|
||||
}
|
||||
|
||||
// 匹配条件规则
|
||||
// 特定目标题规则匹配
|
||||
match(target: string, scope: string, fact: Fact, comparor?: any) {
|
||||
const hash = this.calculateHash(target, scope)
|
||||
|
||||
const rule = this.rules.get(hash)
|
||||
if (rule) {
|
||||
const result = rule.match(fact, comparor)
|
||||
// this.matchCache.set(hash, result);
|
||||
return result
|
||||
} else {
|
||||
// 默认显示
|
||||
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 rule = this.rules.get(hash)
|
||||
if (rule) {
|
||||
@ -205,16 +214,7 @@ export class RuleMatch {
|
||||
// 假设哈希值计算方法为简单的字符串拼接或其他哈希算法
|
||||
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) {
|
||||
const list = [...this.rules.entries()]
|
||||
const match = list.filter(([, ruleValue]) => {
|
||||
@ -225,19 +225,8 @@ export class RuleMatch {
|
||||
})
|
||||
return res.length
|
||||
})
|
||||
console.log({ 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() {
|
||||
return Array.from(this.rules.entries()).map(([, value]) => {
|
||||
return value.toJson()
|
||||
|
@ -10,7 +10,7 @@ export const useJumpLogicInfo = (field) => {
|
||||
const hasJumpLogic = computed(() => {
|
||||
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
|
||||
return isField || isTarget
|
||||
|
@ -12,7 +12,7 @@ export const useShowLogicInfo = (field) => {
|
||||
const hasShowLogic = computed(() => {
|
||||
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
|
||||
return isField || isTarget
|
||||
|
@ -142,7 +142,14 @@ const onMoveDown = () => {
|
||||
}
|
||||
const onDelete = async () => {
|
||||
if (unref(hasShowLogic) || getShowLogicText.value) {
|
||||
ElMessageBox.alert('该问题被逻辑依赖,请先删除逻辑依赖', '提示', {
|
||||
ElMessageBox.alert('该题目被显示逻辑关联,请先清除逻辑依赖', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
type: 'warning'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (unref(hasJumpLogic)) {
|
||||
ElMessageBox.alert('该题目被跳转逻辑关联,请先清除逻辑依赖', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
type: 'warning'
|
||||
})
|
||||
|
@ -7,7 +7,7 @@ const adapter = (() => {
|
||||
const list = []
|
||||
|
||||
const exec = (questionData) => {
|
||||
return list.reduce((pre, next) => ({ ...pre, ...next(questionData, pre) }), {})
|
||||
return list.reduce((pre, next, index) => ({ ...pre, index, ...next(questionData, pre) }), {})
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -7,11 +7,12 @@ import { get as _get } from 'lodash-es'
|
||||
export default function (questionConfig) {
|
||||
let dataList = _get(questionConfig, 'dataConf.dataList')
|
||||
// 将题目列表转成对象,并且对题目类型、题目的选项做一些字段的增加和转换
|
||||
const questionData = dataList.reduce((pre, item) => {
|
||||
const questionData = dataList.reduce((pre, item, index) => {
|
||||
Object.assign(pre, {
|
||||
[item.field]: {
|
||||
indexNumber: '',
|
||||
voteTotal: 0,
|
||||
index,
|
||||
...item
|
||||
}
|
||||
})
|
||||
|
@ -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) {
|
||||
// // 去改renderData,split中间的题目
|
||||
// handleJump(field, formValues.value)
|
||||
// }
|
||||
// })
|
||||
|
||||
const validate = (callback) => {
|
||||
const length = fields.length
|
||||
|
@ -8,7 +8,7 @@
|
||||
></QuestionRuleContainer>
|
||||
</template>
|
||||
<script setup>
|
||||
import { unref, ref, computed, watch } from 'vue'
|
||||
import { unref, computed, watch } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import QuestionRuleContainer from '../../materials/questions/QuestionRuleContainer'
|
||||
import { useVoteMap } from '@/render/hooks/useVoteMap'
|
||||
@ -19,7 +19,6 @@ import { useQuestionStore } from '../stores/question'
|
||||
import { useSurveyStore } from '../stores/survey'
|
||||
|
||||
import { NORMAL_CHOICES, RATES, QUESTION_TYPE } from '@/common/typeEnum.ts'
|
||||
import { getQuestionIndexByField, findMinKeyInMap } from '@/render/utils/index.js'
|
||||
|
||||
const props = defineProps({
|
||||
indexNumber: {
|
||||
@ -31,7 +30,7 @@ const props = defineProps({
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['change'])
|
||||
const questionStore = useQuestionStore()
|
||||
@ -40,17 +39,26 @@ const surveyStore = useSurveyStore()
|
||||
const formValues = computed(() => {
|
||||
return surveyStore.formValues
|
||||
})
|
||||
const { dataConf, changeField, showLogicEngine, jumpLogicEngine } = storeToRefs(surveyStore)
|
||||
const { showLogicEngine } = storeToRefs(surveyStore)
|
||||
const {
|
||||
changeField,
|
||||
changeIndex,
|
||||
needHideFields,
|
||||
} = storeToRefs(questionStore)
|
||||
// 题型配置转换
|
||||
const questionConfig = computed(() => {
|
||||
let moduleConfig = props.moduleConfig
|
||||
const { type, field, options = [], ...rest } = cloneDeep(moduleConfig)
|
||||
// console.log(field,'这里依赖的formValue,所以change时会触发重新计算')
|
||||
let alloptions = options
|
||||
|
||||
if (type === QUESTION_TYPE.VOTE) {
|
||||
const { options, voteTotal } = useVoteMap(field)
|
||||
const voteOptions = unref(options)
|
||||
alloptions = alloptions.map((obj, index) => Object.assign(obj, voteOptions[index]))
|
||||
moduleConfig.voteTotal = unref(voteTotal)
|
||||
}
|
||||
|
||||
if (
|
||||
NORMAL_CHOICES.includes(type) &&
|
||||
options.filter((optionItem) => optionItem.others).length > 0
|
||||
@ -60,6 +68,7 @@ const questionConfig = computed(() => {
|
||||
alloptions = alloptions.map((obj, index) => Object.assign(obj, othersOptions[index]))
|
||||
moduleConfig.othersValue = unref(othersValue)
|
||||
}
|
||||
|
||||
if (
|
||||
RATES.includes(type) &&
|
||||
rest?.rangeConfig &&
|
||||
@ -78,18 +87,29 @@ const questionConfig = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const showMatch = computed(() => {
|
||||
const logicshow = computed(() => {
|
||||
// computed有计算缓存,当match有变化的时候触发重新计算
|
||||
const result = showLogicEngine.value.match(props.moduleConfig.field, 'question', formValues.value)
|
||||
return result === undefined ? true : result
|
||||
})
|
||||
|
||||
|
||||
const logicskip = computed(() => {
|
||||
return needHideFields.value.includes(props.moduleConfig.field)
|
||||
})
|
||||
const visibily = computed(() => {
|
||||
return logicshow.value && !logicskip.value
|
||||
})
|
||||
|
||||
|
||||
// 当题目被隐藏时,清空题目的选中项,实现a显示关联b,b显示关联c场景下,b隐藏不影响题目c的展示
|
||||
watch(
|
||||
() => showMatch.value,
|
||||
() => visibily.value,
|
||||
(newVal, oldVal) => {
|
||||
// 题目从显示到隐藏,需要清空值
|
||||
const { field, type, innerType } = props.moduleConfig
|
||||
if (!newVal && oldVal) {
|
||||
// 如果被隐藏题目有选中值,则需要清空选中值
|
||||
if(formValues.value[field].toString()) {
|
||||
let value = ''
|
||||
// 题型是多选,或者子题型是多选(innerType是用于投票)
|
||||
if (type === QUESTION_TYPE.CHECKBOX || innerType === QUESTION_TYPE.CHECKBOX) {
|
||||
@ -100,82 +120,10 @@ watch(
|
||||
value: value
|
||||
}
|
||||
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) => {
|
||||
@ -184,5 +132,41 @@ const handleChange = (data) => {
|
||||
if (props.moduleConfig.type === QUESTION_TYPE.VOTE) {
|
||||
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)
|
||||
// hideMap中remove被跳过的题
|
||||
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>
|
||||
|
@ -11,6 +11,11 @@ export const useQuestionStore = defineStore('question', () => {
|
||||
const questionData = ref(null)
|
||||
const questionSeq = ref([]) // 题目的顺序,因为可能会有分页的情况,所以是一个二维数组[[qid1, qid2], [qid3,qid4]]
|
||||
const pageIndex = ref(1) // 当前分页的索引
|
||||
const changeField = ref(null)
|
||||
const changeIndex = computed(() => {
|
||||
return questionData.value[changeField.value].index
|
||||
})
|
||||
const needHideFields = ref([])
|
||||
|
||||
// 题目列表
|
||||
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 {
|
||||
voteMap,
|
||||
questionData,
|
||||
@ -191,6 +212,13 @@ export const useQuestionStore = defineStore('question', () => {
|
||||
setVoteMap,
|
||||
updateVoteMapByKey,
|
||||
initVoteData,
|
||||
updateVoteData
|
||||
updateVoteData,
|
||||
changeField,
|
||||
changeIndex,
|
||||
setChangeField,
|
||||
needHideFields,
|
||||
addNeedHideFields,
|
||||
removeNeedHideFields,
|
||||
getQuestionIndexByField
|
||||
}
|
||||
})
|
||||
|
@ -41,7 +41,7 @@ export const useSurveyStore = defineStore('survey', () => {
|
||||
const formValues = ref({})
|
||||
const whiteData = ref({})
|
||||
const pageConf = ref([])
|
||||
const changeField = ref(null)
|
||||
|
||||
|
||||
const router = useRouter()
|
||||
const questionStore = useQuestionStore()
|
||||
@ -156,10 +156,10 @@ export const useSurveyStore = defineStore('survey', () => {
|
||||
// 用户输入或者选择后,更新表单数据
|
||||
const changeData = (data) => {
|
||||
let { key, value } = data
|
||||
changeField.value = key
|
||||
if (key in formValues.value) {
|
||||
formValues.value[key] = value
|
||||
}
|
||||
questionStore.setChangeField(key)
|
||||
}
|
||||
|
||||
const showLogicEngine = ref()
|
||||
@ -186,7 +186,6 @@ export const useSurveyStore = defineStore('survey', () => {
|
||||
formValues,
|
||||
whiteData,
|
||||
pageConf,
|
||||
changeField,
|
||||
initSurvey,
|
||||
changeData,
|
||||
setWhiteData,
|
||||
|
@ -30,23 +30,3 @@ export const formatLink = (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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user