fix:投放
This commit is contained in:
@@ -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<boolean>}
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user