diff --git a/src/assets/js/logic.js b/src/assets/js/logic.js deleted file mode 100644 index c2d4dbd..0000000 --- a/src/assets/js/logic.js +++ /dev/null @@ -1,424 +0,0 @@ -/** - * 问卷设计中涉及到的一些 【逻辑】 中公共方法 - */ -import config from '@/config'; -import { Modal } from 'ant-design-vue'; -import { advancedQuesTypeList } from './quesInfoList'; - -const advancedTypes = advancedQuesTypeList.map((i) => i.type); // 高级题型的 question_type - -const performance = { - start(label) { - if (config.currentMode === 'dev') { - console.time(`${label} Performance`); - } - }, - end(label) { - if (config.currentMode === 'dev') { - console.timeEnd(`${label} Performance`); - } - }, - log(...rest) { - if (config.currentMode === 'dev') { - console.log(...rest); - } - } -}; - -function isNullish(value) { - return value === undefined || value === null; -} - -/** - * 修改(添加、移动、删除)问卷 题目、分页,编辑(添加、删除)逻辑 时,判断循环逻辑是否满足 - * 的异步方法,感觉题多了这个方法执行会很慢。。。 - * @param rest - * @return {Promise} - */ -export async function loopingAvailableAsync(...rest) { - return Promise.resolve(loopingAvailable(...rest)); -} - -/** - * 修改(添加、移动、删除)问卷 题目、分页,编辑(添加、删除)逻辑 时,判断循环逻辑是否满足 - * @param cycles 循环逻辑列表 - * @param questions 被操作后的问题列表 - * @param logics 逻辑列表 - * @param isPerPage 是否是每页一道题 - * @param reason 提示信息 - * @return {boolean} 是否满足循环逻辑 - */ -export function loopingAvailable({ cycles, questions, logics, isPerPage, reason } = {}) { - const performanceName = 'CheckLoopingAvailable'; - performance.start(performanceName); - - let result = true; - let prompt = reason || ''; - - if (!cycles.length) { - // 没有配置循环逻辑 - performance.end(performanceName); - return result; - } - - const quizList = generateQuestionPages({ questions, logics, isPerPage }); - const cycleList = generateCyclePages(cycles, quizList); - const logicList = generateLogicPages(logics, quizList); - - const lastPage = quizList.reduce((prev, curr) => Math.max(prev, curr.$page || 0), 0); // 最后一页的页码 - - // console.log('questions', questions, quizList); - // console.log('cycles', cycles, cycleList); - // console.log('logics', logics, logicList); - // console.log('last page', lastPage); - - for (let i = 0; i < cycleList.length; i += 1) { - const { index, page, startPage, endPage } = cycleList[i] || {}; - - // 循环关联问题被删除时,才会出现这种情况 - if (!quizList.find((j) => j.question_index === index)) { - result = false; - prompt = '问题被循环关联,不能删除'; - break; - } - - if ((startPage && startPage <= page) || (endPage && endPage <= page)) { - result = false; - prompt = '问题被循环关联,不能位于循环题组后'; - break; - } - - // 循环关联问题位于自己或其它循环的循环题组分页中间 - if (page && cycleList.some((j) => j.startPage < page && page < j.endPage)) { - result = false; - prompt = '问题被循环关联,不能位于循环题组中'; - break; - } - - // 循环的循环题组,修改问题或分页的时候应该不会出现这种情况 - if (startPage && endPage && startPage > endPage) { - result = false; - prompt = '循环题组的起始分页应该小于结束分页'; - break; - } - - // 删除分页,或者删除自带分页的题,导致分页变化,校验分页是否包含了所有的循环题组分页 - if (endPage > lastPage) { - result = false; - prompt = '请勿在循环题组中编辑分页,请调整循环题组后再试'; - break; - } - - // 循环题组分页和跳转逻辑有冲突 - if ( - logicList.some((j) => { - // 跳转到这几个的时候,循环不校验,只校验带题的 - // 正常完成、提前终止、配额超限 - if ([-1, -2, -3].includes(Math.min(...j))) { - return false; - } - - return isCross(j, [startPage, endPage]); - }) - ) { - result = false; - prompt = '循环题组分页区间与跳转逻辑冲突,请修改后再试'; - break; - } - } - - if (!result && (reason || prompt)) { - Modal.warning({ - class: 'custom-modal custom-modal-title-notice hide-ant-icon', - title: '无法操作', - width: '450px', - content: reason || prompt - }); - } - - performance.end(performanceName); - return result; -} - -/** - * 给问题列表中的问题添加,问题所在的页码字段($page) - * 说明: - * 1. 需要过滤掉没有题的空白页 - * 2. 高级题型的题前后需要加分页 - * 3. 添加了逻辑的基础题型前后需要加分页 - * @param questions - * @param logics - * @param isPerPage - * @param addon 自定义额外操作,是一个方法 - * @return {array} 题目列表,包括题和分页 - */ -export function generateQuestionPages({ questions, logics, isPerPage, addon }) { - if (!questions?.length) { - return questions || []; - } - - let page = 1; - let pageObjectCount = 0; // 计数,在 questions 有几个分页的对象 - return questions.map((item, index, arr) => { - const asBlock = [...advancedTypes, 23]; // 需要自动加分页的题型:高级题型及知情同意书 - const isPrevAdv = asBlock.includes(arr[index - 1]?.question_type); // 前一道题是高级题型 - const isAdv = asBlock.includes(item?.question_type); // 当前题是高级题型 - // const isPrevAdv = advancedTypes.includes(arr[index - 1]?.question_type); // 前一道题是高级题型 - // const isAdv = advancedTypes.includes(item?.question_type); // 当前题是高级题型 - - // 前一道题配置了跳转逻辑 - const isPrevLogic = !!logics.find((logic) => logic.question_index === arr[index - 1]?.question_index); - // 当前题配置了跳转逻辑 - const isLogic = !!logics.find((logic) => logic.question_index === item.question_index); - - const isSurroundedByPage = isAdv || isLogic; // 需要在该题前后添加分页 - - const isPrevPage = !!arr[index - 1]?.page; // 前一道题是一个分页对象 - const prevIsQuestion = !arr[index - 1]?.page; // 前一道题是一道题而不是一个分页对象 - - if (!index) { - page = 1; - pageObjectCount = 0; - } - - if (item.page) { - pageObjectCount += 1; - if (index && prevIsQuestion && !isPrevLogic && !isPrevAdv) { - // 过滤掉没有题的空白页 - page += 1; - } - } else { - if (index && !isPrevPage && !isPrevAdv && !isPrevLogic && isSurroundedByPage) { - page += 1; - } - item.$page = isPerPage ? index + 1 - pageObjectCount : page; - if (isSurroundedByPage) { - page += 1; - } - } - - if (addon) { - addon(item); - } - - return item; - }); -} - -/** - * 格式化循环列表,便于后续使用 - * @param cycles - * @param questions - * @return {array} 格式化后的循环列表 - */ -export function generateCyclePages(cycles, questions) { - if (!cycles?.length) { - return cycles || []; - } - - return cycles.map((cycle) => ({ - index: cycle.question_index, - page: getPageByQuestionIndex(cycle.question_index, questions), - startPage: cycle.first_page, - endPage: cycle.last_page - })); -} - -/** - * 逻辑关联的页码,便于后续使用 - * @param logics {*[]} - * @param questions {*[]} - * @param fillRangeItem {boolean} 用数字填充满 range[index] 数组 - * @return {number[][]} 逻辑关联的页码 - */ -export function generateLogicPages(logics, questions, fillRangeItem) { - if (!logics?.length) { - return logics || []; - } - - const pages = []; - - logics.forEach((logic) => { - if (![0, 2].includes(logic.skip_type)) { - return; - } - - let page = [ - getPageByQuestionIndex(logic.logic?.[0]?.question_index, questions), - getPageByQuestionIndex(logic.question_index, questions), - getPageByQuestionIndex(logic.skip_question_index, questions) || logic.skip_question_index - ]; - page = page.filter((i) => !!i); - - if (fillRangeItem) { - const min = Math.min(...page.filter((i) => i > 0)); - const max = Math.max(...page); - page = generateRange(min, max); - } - - pages.push(page); - }); - - return pages; -} - -/** - * 通过问题的 question_index 找到问题在第几页 - * @param questionIndex - * @param questions - * @return {number|*|undefined} - */ -export function getPageByQuestionIndex(questionIndex, questions) { - return questions.find((i) => i.question_index === questionIndex)?.$page || undefined; -} - -/** - * 生成一个从 start 开始到 end 结束的数组 - * @param start {number} - * @param end {number} - * @return {number[]} - */ -export function generateRange(start, end) { - if (isNullish(start) || isNullish(end)) { - return []; - } - - if (isNaN(Number(start)) || isNaN(Number(end))) { - return []; - } - - let i = start; - let range = []; - while (i <= end) { - range.push(i); - i += 1; - } - - return range; -} - -/** - * 判断逻辑与循环分组,是否不合理 - * @param range1 {array} 跳转逻辑的分页数组 - * @param range2 {array} 循环分组的分页数组 - * @return {boolean} true 不合理, false 合理 - */ -export function isCross(range1, range2) { - if (!range1 || !range2) { - return false; - } - - const parsedRange1 = range1.slice(1); - const judge = range1[0]; - - const start1 = Math.min(...parsedRange1); - const end1 = Math.max(...parsedRange1); - const start2 = range2[0]; - const end2 = range2[1]; - - const isPlainSequence = parsedRange1[0] === start1; // 跳转逻辑的方向,true 为从前向后跳转 - - if (isNullish(start1) || isNullish(end1) || end1 < 0 || (isNullish(start2) && isNullish(end2))) { - return false; - } - - // [judge, start1, end1]; // isPlainSequence - // [start1, judge, end1]; // isPlainSequence || !isPlainSequence - // - // [start2, end2]; - - // 逻辑在循环之前 - const isLeft = isNullish(start2) ? end1 < end2 : end1 < start2; - // 逻辑在循环之后 - const isRight = isNullish(end2) ? start2 < start1 && start2 < judge : end2 < start1 && end2 < judge; - // 不相交也不包含 - const isSibling = isLeft || isRight; - - // 逻辑包含循环 - const contain = (isPlainSequence - && (((isNullish(start2) || isSequence(judge, start2, start1)) - && (isNullish(end2) || isSequence(judge, end2, start1))) - || ((isNullish(start2) || isSequence(start1, start2, end1)) - && (isNullish(end2) || isSequence(start1, end2, end1))))) - || (!isPlainSequence - && (judge < start1 - ? ((isNullish(start2) || isSequence(judge, start2, start1)) - && (isNullish(end2) || isSequence(judge, end2, start1))) - || ((isNullish(start2) || isSequence(start1, start2, end1)) - && (isNullish(end2) || isSequence(start1, end2, end1))) - : ((isNullish(start2) || isSequence(start1, start2, judge)) - && (isNullish(end2) || isSequence(start1, end2, judge))) - || ((isNullish(start2) || isSequence(judge, start2, end1)) - && (isNullish(end2) || isSequence(judge, end2, end1))))); - // 循环存在封闭区间,并且循环包含逻辑 - const contained = !isNullish(start2) - && !isNullish(end2) - // [judge, start1, end1]; - && ((isPlainSequence && start2 <= judge && end1 <= end2) - // [judge, start1, end1]; - // [start1, judge, end1]; - || (!isPlainSequence && start2 <= start1 && start2 <= judge && end1 <= end2)); - // 循环不存在封闭区间 - const unCircled = (!isNullish(start2) - && isNullish(end2) - && ((isPlainSequence && start2 === judge) || (!isPlainSequence && judge < start1) - ? start2 === judge - : start2 === start1)) - || (isNullish(start2) && !isNullish(end2) && end2 === end1); - - return !(isSibling || contain || contained || unCircled); -} - -function isSequence(s1, s2, s3, equal) { - return equal ? s1 <= s2 && s2 <= s3 : s1 < s2 && s2 < s3; -} - -/** - * 调用保存问题接口前,检查是否有问题受到循环影响需要重新保存,如果有则重新保存一下这道题,没有则不需要特殊处理 - * bugfix for : 有循环的问卷发布后,再次编辑问卷,将循环题组内的问题移除循环题组, - * 再次发布,后端不处理被移除的问题 title,导致该题的 title 仍保持上次发布时的值 - * 错误格式一般为:B3.1 正确格式一般为:B3 - * 导致作答出现错误,例如:引用找不到题 - * 从 store 里查出修改前和修改后的问题、分页、循环;比较修改前后问题是否被移除了某个循环题组;修改 quesSaveParam.newQuestion; - * @param quesSaveParam 将要保存的数据,会被此方法修改的字段:quesSaveParam.newQuestion - * @param store - */ -export function updateNewQuestionsByLoopingEffect(quesSaveParam, store) { - const { questionInfoBeforeModified = {}, questionInfo = {} } = JSON.parse(JSON.stringify(store.state.common)) || {}; - - const oldPages = questionInfoBeforeModified.survey.pages; - const newQuestions = questionInfo.questions; - const newPages = questionInfo.survey.is_one_page_one_question - ? questionInfo.questions.filter((i) => i.question_index).map((i, idx) => [i.question_index]) - : questionInfo.survey.pages; - const cycles = questionInfo.cycle_pages || []; - - if (!cycles.length) { - return; - } - - const moveOutOfCycleQuestionIndex = []; - const cyclePages = cycles.map((i) => [i.first_page, i.last_page]).filter((i) => i[0] && i[1]); - cyclePages.forEach((i) => { - const start = i[0] - 1; - const end = i[1] - 1; - - for (let j = start; j <= end; j += 1) { - if (oldPages[j]?.join(',') !== newPages[j]?.join(',')) { - oldPages[j]?.forEach((k) => { - if (!newPages?.[j]?.includes(k)) { - moveOutOfCycleQuestionIndex.push(k); - } - }); - } - } - }); - const movedOutOfCycleQuestions = newQuestions.filter((i) => moveOutOfCycleQuestionIndex.includes(i.question_index)); - - if (movedOutOfCycleQuestions.length) { - if (!quesSaveParam.newQuestion) { - quesSaveParam.newQuestion = []; - } - quesSaveParam.newQuestion.push(...movedOutOfCycleQuestions); - } -} diff --git a/src/layouts/logic.js b/src/layouts/logic.js index c2d4dbd..337537a 100644 --- a/src/layouts/logic.js +++ b/src/layouts/logic.js @@ -2,7 +2,7 @@ * 问卷设计中涉及到的一些 【逻辑】 中公共方法 */ import config from '@/config'; -import { Modal } from 'ant-design-vue'; +import { showDialog } from 'vant'; import { advancedQuesTypeList } from './quesInfoList'; const advancedTypes = advancedQuesTypeList.map((i) => i.type); // 高级题型的 question_type @@ -128,11 +128,10 @@ export function loopingAvailable({ cycles, questions, logics, isPerPage, reason } if (!result && (reason || prompt)) { - Modal.warning({ - class: 'custom-modal custom-modal-title-notice hide-ant-icon', + showDialog({ title: '无法操作', width: '450px', - content: reason || prompt + message: reason || prompt }); } @@ -288,7 +287,7 @@ export function generateRange(start, end) { } let i = start; - let range = []; + const range = []; while (i <= end) { range.push(i); i += 1; @@ -335,7 +334,7 @@ export function isCross(range1, range2) { const isSibling = isLeft || isRight; // 逻辑包含循环 - const contain = (isPlainSequence + const contain = (isPlainSequence && (((isNullish(start2) || isSequence(judge, start2, start1)) && (isNullish(end2) || isSequence(judge, end2, start1))) || ((isNullish(start2) || isSequence(start1, start2, end1)) @@ -351,7 +350,7 @@ export function isCross(range1, range2) { || ((isNullish(start2) || isSequence(judge, start2, end1)) && (isNullish(end2) || isSequence(judge, end2, end1))))); // 循环存在封闭区间,并且循环包含逻辑 - const contained = !isNullish(start2) + const contained = !isNullish(start2) && !isNullish(end2) // [judge, start1, end1]; && ((isPlainSequence && start2 <= judge && end1 <= end2) @@ -359,7 +358,7 @@ export function isCross(range1, range2) { // [start1, judge, end1]; || (!isPlainSequence && start2 <= start1 && start2 <= judge && end1 <= end2)); // 循环不存在封闭区间 - const unCircled = (!isNullish(start2) + const unCircled = (!isNullish(start2) && isNullish(end2) && ((isPlainSequence && start2 === judge) || (!isPlainSequence && judge < start1) ? start2 === judge