Files
ylst-pc/src/views/planetDesign/Design/fragement/QuestionCatalog.vue
2024-10-10 11:54:27 +08:00

1502 lines
47 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="catalog">
<div
class="catalog-more"
:style="{
height: openCheckbox ? '48px' : 0
}"
>
<a-tooltip title="移动" :mouseEnterDelay="1" placement="bottom">
<i class="iconfont" @click="openBatchModal('move')">&#xe71b;</i>
</a-tooltip>
<a-tooltip title="复制" :mouseEnterDelay="1" placement="bottom">
<i class="iconfont" @click="openBatchModal('copy')">&#xe6c6;</i>
</a-tooltip>
<a-tooltip title="删除" :mouseEnterDelay="1" placement="bottom">
<i class="iconfont" @click="openBatchModal('delete')">&#xe6c5;</i>
</a-tooltip>
<a-tooltip title="题库" :mouseEnterDelay="1" placement="bottom">
<i class="iconfont" @click="openBatchModal('bank')">&#xe784;</i>
</a-tooltip>
</div>
<div style="padding: 12px">
<a-input
class="custom-input"
style="height: 32px"
allowClear
v-model:value="searchName"
placeholder="请输入题目名"
>
<template #prefix>
<i class="iconfont" style="color: #bfbfbf">&#xe6df;</i>
</template>
</a-input>
</div>
<div class="catalog-main scrollbar">
<div
class="catalog-checkall"
:style="{
overflow: 'hidden',
marginTop: openCheckbox ? '24px' : 0,
width: openCheckbox ? '70px' : 0,
height: openCheckbox ? '20px' : 0,
opacity: openCheckbox ? 1 : 0
}"
@click="toTitle('introduct')"
>
<a-checkbox
v-model:checked="checkboxAll"
class="custom-checkbox"
style="margin-right: 8px"
@change="checkboxAllHandle"
></a-checkbox>
<div style="word-break: keep-all">全选</div>
</div>
<div class="catalog-text" @click="toTitle('introduct')">
<i class="iconfont catalog-text-icon">&#xe6a7;</i>
<div v-html="surveyTitle"></div>
</div>
<div class="catalog-text" style="padding-bottom: 24px" @click="toTitle('introduct')">
<i class="iconfont catalog-text-icon">&#xe6cf;</i>
<span>介绍语</span>
</div>
<div class="catalog-page" v-for="(ques, index) in questionList" :key="index">
<div class="catalog-line"></div>
<div class="catalog-page-title">{{ ques.page }} {{ ques.first_title }}~{{ ques.last_title }}</div>
<draggable
v-model="ques.child"
item-key="id"
animation="300"
:scroll="true"
handle=".moverQues"
:group="dragGroup"
@end="dragEndHandle"
>
<template #item="{ element }">
<div class="catalog-text">
<a-checkbox
v-model:checked="element.checked"
class="custom-checkbox"
:disabled="Boolean(element.permissions?.disable_update || false)"
:style="{
marginRight: openCheckbox ? '8px' : 0,
width: openCheckbox ? '17px' : 0,
minWidth: openCheckbox ? '17px' : 0
}"
@change="checkboxHandle"
></a-checkbox>
<div class="flex-align" @click="skipToHandle(element)">
<i class="iconfont catalog-text-icon" style="margin-right: 8px" v-html="getIcon(element)"></i>
<div class="catalog-text-content" :style="{ width: `${catalogTextWidth}px` }">
<span style="word-break: keep-all">{{ element.title }}</span>
<p v-html="nodeHandle(element.stem)"></p>
</div>
<div class="catalog-text-hover" v-if="!openCheckbox && !searchName">
<i class="iconfont moverQues">&#xe71b;</i>
<i class="iconfont" v-if="!disableCopyBtn(element)" @click.stop="copyHandle(element)">&#xe6c6;</i>
<i class="iconfont" v-if="!disableDelBtn(element)" @click.stop="deleteHandle(element)">&#xe6c5;</i>
</div>
</div>
</div>
</template>
</draggable>
</div>
<div class="catalog-line"></div>
<div class="catalog-text" @click="toTitle('endLang')">
<i class="iconfont catalog-text-icon">&#xe6cf;</i>
<div>结束语</div>
</div>
</div>
<a-modal
v-model:visible="moveVisible"
title="批量移动"
class="custom-modal"
:maskClosable="false"
:destroyOnClose="true"
:width="500"
>
<div class="catalog-modal-content">
<span>移动至</span>
<a-select
v-model:value="moveQuesIndex"
placeholder="请选择问题"
class="custom-select catalog-modal-content-select"
>
<a-select-option v-for="ques in allQuesList" :key="ques.id" :value="ques.question_index">
<div class="catalog-modal-content-select-html" v-html="`${ques.title || ''}${ques.stemText || ''}`"></div
></a-select-option>
</a-select>
<a-select v-model:value="moveLocation" placeholder="请选择位置" class="custom-select" style="width: 120px">
<a-select-option :key="0" :value="0">之前</a-select-option>
<a-select-option :key="1" :value="1">之后</a-select-option>
</a-select>
</div>
<template #footer>
<div>
<a-button @click="moveVisible = false">取消</a-button>
<a-button type="primary" :disabled="moveDiabled" @click="batchMoveOk">确定</a-button>
</div>
</template>
</a-modal>
<a-modal
v-model:visible="copyVisible"
title="批量复制"
class="custom-modal"
:maskClosable="false"
:destroyOnClose="true"
:width="500"
>
<div class="catalog-modal-content">
<span>复制</span>
<a-input-number
class="catalog-modal-content-select"
v-model:value="copyNum"
:min="0"
placeholder="请输入复制次数"
/>
<span></span>
</div>
<template #footer>
<div>
<a-button @click="copyVisible = false">取消</a-button>
<a-button type="primary" :disabled="copyDiabled" @click="batchCopyOk">确定</a-button>
</div>
</template>
</a-modal>
<a-modal
v-model:visible="delVisible"
title="批量删除"
class="custom-modal"
:maskClosable="false"
:destroyOnClose="true"
:width="500"
>
<div class="catalog-modal-content">
<div>
<div class="catalog-modal-content-item" v-for="ques in checkQues" :key="ques.id">
{{ ques.title }} {{ ques.stemText }}
</div>
</div>
</div>
<template #footer>
<div>
<a-button @click="delVisible = false">取消</a-button>
<a-button type="primary" @click="batchDeleteOk">删除</a-button>
</div>
</template>
</a-modal>
<a-modal
v-model:visible="bankVisible"
title="保存到题库"
class="custom-modal"
:maskClosable="false"
:destroyOnClose="true"
:width="600"
>
<BatchSelectQuesBank
ref="batchSelectQuesBankRef"
@addclass="addclass1"
@checkGroup="
($event) => {
checkGroupInfo = $event;
}
"
/>
<template #footer>
<div class="modal-footer">
<div class="modal-footer-flex" @click="addGroupHandle">
<div
class="modal-footer-btn"
:class="{
'modal-footer-btn-disable': disableBtn
}"
>
<i class="iconfont">&#xe689;</i>
<span>新增分类</span>
</div>
</div>
<div class="modal-footer-flex">
<a-button @click="bankVisible = false">取消</a-button>
<a-button type="primary" :loading="saveLoading" @click="batchBankOk">保存</a-button>
</div>
</div>
</template>
</a-modal>
</div>
</template>
<script>
import { computed, reactive, ref } from '@vue/reactivity';
import { useStore } from 'vuex';
import Scroll from '../js/scroll.js';
import { createVNode, nextTick, watch } from '@vue/runtime-core';
import { basicQuesTypeList, quickQuesTypeList, advancedQuesTypeList } from '../../../../utils/common';
import { nodeHandle, getPageQuesByQues, saveQuesApi } from '../js/util.js';
import { loopingAvailable } from '../js/logic';
import draggable from 'vuedraggable';
import { v4 as uuidv4 } from 'uuid';
import { Modal, message } from 'ant-design-vue';
import { ExclamationCircleFilled } from '@ant-design/icons-vue';
import BatchSelectQuesBank from '../components/BatchSelectQuesBank.vue';
import { getQuesBankList, batchSaveQuesIntoBank } from '../../api/api.js';
export default {
name: 'QuestionCatalog',
components: { draggable, BatchSelectQuesBank },
props: {
openCheckbox: {
type: Boolean,
default: false
}
},
setup(props, context) {
const store = useStore();
const questionList = ref([]);
const questionGroupKeepOne = ref({});
const oldQuestionList = ref([]);
const disableBtn = ref(null);
const dragGroup = ref(`catalog_${uuidv4()}`);
const isKeepCheckStatus = ref(true);
const questionArr = computed(() => {
let tmp = [];
questionList.value.map((item) => {
console.log(123456);
console.log(item);
if (item.child) {
tmp = [...tmp, ...item.child];
}
});
return tmp;
});
const addclass1 = (e) => {
disableBtn.value = e;
};
const initList = () => {
const quesInfo = JSON.parse(JSON.stringify(store.state.common.questionInfo));
const quesList = quesInfo.questions.map((ques) => {
if (ques.stem) {
ques.stemText = nodeHandle(ques.stem);
ques.checked = false;
}
return ques;
});
allQuesList.value = quesList.filter((ques) => ques.id);
const result = [];
quesInfo.survey.pages.forEach((page, index) => {
if (page.length > 0) {
const temp = {
page: index + 1,
first_title: quesList.find((ques) => ques.question_index === page[0])?.title || '',
last_title: quesList.find((ques) => ques.question_index === page[page.length - 1])?.title || '',
child: reactive([])
};
page.forEach((childPage) => {
const ques = quesList.find((ques) => ques.question_index === childPage);
// 保留多选框的选中状态
if (isKeepCheckStatus.value && questionList.value.length > 0) {
let oldQues = null;
questionList.value.forEach((q) => {
if (q.child) {
q.child.forEach((child) => {
if (child.question_index === ques.question_index) {
oldQues = child;
}
});
}
});
ques.checked = oldQues?.checked || false;
}
if (ques) {
temp.child.push(ques);
}
});
result.push(temp);
}
});
questionList.value = result;
oldQuestionList.value = result;
isKeepCheckStatus.value = true;
};
const catalogTextWidth = ref(75);
watch(
() => props.openCheckbox,
(val) => {
if (window.document.body.clientWidth < 1280) {
catalogTextWidth.value = val ? 52 : 75;
} else {
catalogTextWidth.value = val ? 178 : 180;
}
},
{ immediate: true }
);
watch(
() => questionArr.value,
() => {
questionGroupKeepOne.value = {};
questionArr.value.map((item) => {
if (item.permissions?.delete_group_keep_one === 1 && item.other) {
let tmpGroup = questionGroupKeepOne.value[item.other];
if (!tmpGroup) {
tmpGroup = 1;
} else {
tmpGroup++;
}
questionGroupKeepOne.value[item.other] = tmpGroup;
}
});
}
);
const disableMoveBtn = (element) => {
return element.permissions?.disable_update || false;
};
const disableCopyBtn = (element) => {
return element.permissions?.disable_copy || false;
};
const disableDelBtn = (element) => {
if (questionGroupKeepOne.value[element.other] && questionGroupKeepOne.value[element.other] <= 1) {
return true;
}
return element.permissions?.disable_delete || false;
};
const {
allQuesList,
checkQues,
checkboxAll,
searchName,
skipToHandle,
toTitle,
getIcon,
checkboxHandle,
checkboxAllHandle,
openBatchModal,
delVisible,
deleteHandle,
batchDeleteOk,
copyVisible,
copyNum,
copyHandle,
batchCopyOk,
moveVisible,
moveQuesIndex,
moveLocation,
dragEndHandle,
batchMoveOk,
bankVisible,
checkGroupInfo,
saveLoading,
batchSelectQuesBankRef,
batchBankOk,
addGroupHandle
} = domEventHandle(store, context, questionList, initList, isKeepCheckStatus);
watch(
() => store.state.common.questionInfo,
(val, oldVal) => {
initList();
},
{ immediate: true }
);
watch(
searchName,
(val) => {
if (val.length > 0) {
questionList.value = JSON.parse(JSON.stringify(questionList.value))
.map((x) => {
x.child = x.child.filter((x) => `${x.title || ''}${x.stem || ''}`.includes(val));
return x;
})
.filter((x) => x.child.length > 0);
} else {
questionList.value = JSON.parse(JSON.stringify(oldQuestionList.value));
}
},
{ immediate: true }
);
const surveyTitle = computed(() => store.state.common.questionInfo.survey?.title || '');
const copyDiabled = computed(() => {
return !copyNum.value;
});
const moveDiabled = computed(() => {
return !moveQuesIndex.value;
});
return {
disableBtn,
addclass1,
catalogTextWidth,
dragGroup,
searchName,
surveyTitle,
questionList,
allQuesList,
checkQues,
checkboxAll,
getIcon,
disableMoveBtn,
disableCopyBtn,
disableDelBtn,
delVisible,
deleteHandle,
batchDeleteOk,
copyVisible,
copyNum,
copyDiabled,
copyHandle,
batchCopyOk,
moveVisible,
moveQuesIndex,
moveLocation,
moveDiabled,
dragEndHandle,
batchMoveOk,
bankVisible,
checkGroupInfo,
saveLoading,
batchSelectQuesBankRef,
batchBankOk,
addGroupHandle,
skipToHandle,
toTitle,
checkboxHandle,
checkboxAllHandle,
nodeHandle,
openBatchModal,
questionGroupKeepOne
};
}
};
/**
* 页面事件统一处理
*/
function domEventHandle(store, context, questionList, initList, isKeepCheckStatus) {
const searchName = ref('');
const checkQues = ref([]);
const checkboxAll = ref(false);
const allQuesList = ref([]);
const delVisible = ref(false);
const copyVisible = ref(false);
const copyNum = ref(1);
const moveVisible = ref(false);
const moveQuesIndex = ref(undefined);
const moveLocation = ref(1);
const bankVisible = ref(false);
const checkGroupInfo = ref(null);
const saveLoading = ref(false);
const batchSelectQuesBankRef = ref(null);
const { deleteQues, batchDeleteQues, copyQues, batchCopyQues, moveQues, batchMoveQues, batchBank } = storeUpdate(
store,
context
);
const {
deleteInterceptor,
batchDeleteInterceptor,
dragEndInterceptor,
batchMoveInterceptor,
batchBankInterceptor
} = interceptorsHandle(store, checkQues);
const skipToHandle = (element) => {
context.emit('catalogCheck', element);
nextTick(() => {
new Scroll(
document.getElementById(element.id),
undefined,
undefined,
!!store.state.common?.questionInfo?.survey?.is_one_page_one_question
).animate();
});
};
const getIcon = (item) => {
let icon = '';
if (item.config.quick_type === 0) {
icon = basicQuesTypeList?.find((x) => x?.childTypes.includes(item?.question_type))?.icon || '';
if (!icon) {
icon = quickQuesTypeList?.find((x) => x?.type === item?.question_type)?.icon || '';
}
} else {
icon = quickQuesTypeList?.find((x) => x?.quickType === item?.config?.quick_type)?.icon || '';
}
if (!icon) {
icon = advancedQuesTypeList?.find((x) => x?.type === item?.question_type)?.icon || '';
}
return icon;
};
const toTitle = (str) => {
new Scroll(document.getElementById(str)).animate();
};
/**
* 打开批量弹框
*/
const openBatchModal = (flag) => {
const temp = {
move: () => {
return {
visible: moveVisible,
msg: '请先选择要移动的节点',
done: () => {}
};
},
copy: () => {
return {
visible: copyVisible,
msg: '请先选择要复制的节点',
done: () => {}
};
},
delete: () => {
return {
visible: delVisible,
msg: '请选择要删除的题目',
done: () => {}
};
},
bank: () => {
return {
visible: bankVisible,
msg: '请选择要添加的题目',
done: () => {
nextTick(() => {
batchSelectQuesBankRef.value.refreshBankList();
});
}
};
}
};
if (checkQues.value.length === 0) {
message.warning(temp[flag].call().msg);
return;
}
temp[flag].call().visible.value = true;
temp[flag].call().done();
};
/**
* 全选题目
*/
const checkboxAllHandle = () => {
questionList.value.forEach((ques) => {
ques.child.forEach((child) => {
if (!child.permissions?.disable_update) {
child.checked = checkboxAll.value;
}
});
});
checkboxHandle();
};
/**
* 选中题目回调
*/
const checkboxHandle = () => {
const checks = [];
const quesList = [];
let childLength = 0;
questionList.value.forEach((ques) => {
const childFilter = ques.child.filter((child) => !child.permissions?.disable_update);
childLength = childLength + childFilter.length;
checks.push(...ques.child.filter((child) => child.checked && !child.permissions?.disable_update));
quesList.push(...ques.child);
});
allQuesList.value = quesList.filter((ques) => !checks.find((check) => check.question_index == ques.question_index));
checkboxAll.value = childLength === checks.length;
checkQues.value = checks;
};
/**
* 复制回调
*/
const copyHandle = (quesInfo) => {
let quesInfoCopy = JSON.parse(JSON.stringify(quesInfo));
if ([105, 201].includes(quesInfoCopy.question_type)) {
quesInfoCopy.config.design_version = 0;
quesInfoCopy.config.file_url = '';
quesInfoCopy.config.file_name = '';
}
delete quesInfoCopy.checked;
const quesSaveParam = copyQues(quesInfoCopy);
saveQuesApi(quesSaveParam, store);
};
/**
* 批量复制确认回调
*/
const batchCopyOk = () => {
const allCopyQues = [];
for (let index = 0; index < copyNum.value; index++) {
allCopyQues.push(
...checkQues.value.map((check) => {
delete check.checked;
return check;
})
);
}
try {
// 请求体超过500k则退出
var len = JSON.stringify(allCopyQues).length;
if (len > 1024 * 500) {
message.warn('复制内容过多,请分批次复制');
return;
}
} catch (e) {}
copyVisible.value = false;
const quesSaveParam = batchCopyQues(
JSON.parse(JSON.stringify(allCopyQues)),
checkQues.value[checkQues.value.length - 1]
);
checkQues.value = [];
isKeepCheckStatus.value = false;
saveQuesApi(quesSaveParam, store);
};
/**
* 删除回调
*/
const deleteHandle = (quesInfo) => {
Modal.confirm({
class: 'custom-modal',
title: '确定删除?',
content: '删除问题后将不能找回',
icon: () => createVNode(ExclamationCircleFilled),
okText: '确定',
cancelText: '取消',
onOk: () => {
const status = deleteInterceptor(quesInfo.id);
if (status) return;
const quesSaveParam = deleteQues(quesInfo.id);
saveQuesApi(quesSaveParam, store);
}
});
};
/**
* 批量删除确认回调
*/
const batchDeleteOk = () => {
Modal.confirm({
class: 'custom-modal',
title: '确定删除?',
content: '删除问题后将不能找回',
icon: () => createVNode(ExclamationCircleFilled),
okText: '确定',
cancelText: '取消',
onOk: () => {
const { canDeleteStatus, delIds } = batchDeleteInterceptor(checkQues);
if (!canDeleteStatus) {
return;
}
delVisible.value = false;
const activeQuestion = store.state.common.activeQuestion;
if (delIds.includes(activeQuestion.id)) {
context.emit('clearQuesActiveStatus');
}
const quesSaveParam = batchDeleteQues(delIds);
checkQues.value = [];
isKeepCheckStatus.value = false;
saveQuesApi(quesSaveParam, store);
}
});
};
/**
* 拖拽移动回调
*/
const dragEndHandle = (e) => {
const moveResult = [];
questionList.value.forEach((ques) => {
moveResult.push(...ques.child);
moveResult.push({
first_title: ques.first_title,
last_title: ques.last_title,
page: ques.page,
total: ques.child.length
});
});
const param = {
event: e,
quesList: JSON.parse(
JSON.stringify(
moveResult.map((m) => {
delete m.checked;
return m;
})
)
)
};
const status = dragEndInterceptor(param.event, moveResult);
if (status) return;
const quesSaveParam = moveQues(param);
saveQuesApi(quesSaveParam, store);
};
/**
* 批量移动确认回调
*/
const batchMoveOk = () => {
const { canMoveStatus, moveResultList } = batchMoveInterceptor(checkQues, moveLocation, moveQuesIndex);
if (!canMoveStatus) {
return;
}
moveVisible.value = false;
const quesSaveParam = batchMoveQues(moveResultList);
checkQues.value = [];
isKeepCheckStatus.value = false;
saveQuesApi(quesSaveParam, store);
// 取消全选
checkboxAll.value = false;
};
/**
* 批量添加题库回调
*/
const batchBankOk = () => {
const { canAddQuesInBankStatus } = batchBankInterceptor(checkQues, bankVisible);
if (!canAddQuesInBankStatus) {
return;
}
batchBank(bankVisible, saveLoading, checkQues, checkGroupInfo);
checkQues.value = [];
checkboxAll.value = false;
isKeepCheckStatus.value = false;
initList();
};
/**
* 新增分类
*/
const addGroupHandle = () => {
// console.log(checkGroupInfo.value.child.length==0)
batchSelectQuesBankRef.value.openInputHandle();
};
return {
allQuesList,
checkQues,
checkboxAll,
searchName,
skipToHandle,
toTitle,
getIcon,
checkboxHandle,
checkboxAllHandle,
openBatchModal,
delVisible,
deleteHandle,
batchDeleteOk,
copyVisible,
copyNum,
copyHandle,
batchCopyOk,
moveVisible,
moveQuesIndex,
moveLocation,
dragEndHandle,
batchMoveOk,
bankVisible,
checkGroupInfo,
saveLoading,
batchSelectQuesBankRef,
batchBankOk,
addGroupHandle
};
}
/**
* 处理更新一下store
*/
function storeUpdate(store, context) {
const copyStoreContent = (store) => {
return JSON.parse(JSON.stringify(store.state.common));
};
/** 删除题目 */
const deleteQues = (id) => {
const { questionInfo, quesSaveParam, activeQuestion } = copyStoreContent(store);
if (activeQuestion.id === id) {
store.commit('common/A_COMMON_SET_ACTIVEQUESTION', JSON.stringify({}));
context.emit('clearQuesActiveStatus');
}
questionInfo.questions = questionInfo.questions.filter((x) => x.id !== id);
const { page, resultList } = getPageQuesByQues(questionInfo.questions);
let title = Number(questionInfo.survey.last_title.substring(1));
questionInfo.questions = resultList;
questionInfo.survey.pages = page;
if (questionInfo.questions.filter((ques) => ques.id).length === 0) {
title = 0;
} else {
title--;
}
questionInfo.survey.last_title = `Q${title}`;
store.commit('common/A_COMMON_SET_QUESTIONINFO', JSON.stringify(questionInfo));
quesSaveParam.newSurvey.pages = page;
store.commit('common/A_COMMON_SET_QUESSAVEPARAM', JSON.stringify(quesSaveParam));
return quesSaveParam;
};
/** 批量删除题目 */
const batchDeleteQues = (ids) => {
const { questionInfo, activeQuestion, quesSaveParam } = copyStoreContent(store);
if (ids.includes(activeQuestion.id)) {
store.commit('common/A_COMMON_SET_ACTIVEQUESTION', JSON.stringify({}));
}
questionInfo.questions = questionInfo.questions.filter((x) => !ids.includes(x.id));
const { page, resultList } = getPageQuesByQues(questionInfo.questions);
let title = Number(questionInfo.survey.last_title.substring(1));
questionInfo.questions = resultList;
questionInfo.survey.pages = page;
if (questionInfo.questions.filter((ques) => ques.id).length === 0) {
title = 0;
} else {
title = title - ids.length;
}
questionInfo.survey.last_title = `Q${title}`;
store.commit('common/A_COMMON_SET_QUESTIONINFO', JSON.stringify(questionInfo));
quesSaveParam.newSurvey.pages = page;
store.commit('common/A_COMMON_SET_QUESSAVEPARAM', JSON.stringify(quesSaveParam));
return quesSaveParam;
};
/** 复制题目 */
const copyQues = (data) => {
const { questionInfo, quesSaveParam } = copyStoreContent(store);
const questionList = questionInfo.questions;
let activeQuestion = data;
const index = questionList.findIndex((x) => x.id === activeQuestion.id);
activeQuestion.id = uuidv4();
activeQuestion.question_index = questionInfo.survey.last_question_index + 1;
const maxTitle = questionList
.filter((ques) => ques.id)
.map((ques) => Number(ques.title.substring(1)))
.filter((til) => !isNaN(til))
.sort((a, b) => a - b);
if (maxTitle.length === 0) {
let title = Number(questionInfo.survey.last_title.substring(1));
activeQuestion.title = `Q${++title}`;
} else {
activeQuestion.title = `Q${++maxTitle[maxTitle.length - 1]}`;
}
questionList.splice(index + 1, 0, activeQuestion);
const quesData = getPageQuesByQues(questionList);
const { page, resultList } = quesData;
questionInfo.questions = resultList;
questionInfo.survey.pages = page;
questionInfo.survey.last_question_index++;
questionInfo.survey.last_title = activeQuestion.title;
store.commit('common/A_COMMON_SET_QUESTIONINFO', JSON.stringify(questionInfo));
store.commit('common/A_COMMON_SET_ACTIVEQUESTION', JSON.stringify(activeQuestion));
nextTick(() => {
new Scroll(document.getElementById(activeQuestion.id)).animate();
});
quesSaveParam.newQuestion.push(activeQuestion);
quesSaveParam.newSurvey.pages = page;
store.commit('common/A_COMMON_SET_QUESSAVEPARAM', JSON.stringify(quesSaveParam));
return quesSaveParam;
};
/** 批量复制题目 */
const batchCopyQues = (data, targetQues) => {
const { questionInfo, quesSaveParam } = copyStoreContent(store);
const questionList = questionInfo.questions;
const index = questionList.findIndex((x) => x.id === targetQues.id);
const maxTitle = questionList
.filter((ques) => ques.id)
.map((ques) => Number(ques.title.substring(1)))
.filter((til) => !isNaN(til))
.sort((a, b) => a - b);
const newData = data.map((d, index) => {
d.id = uuidv4();
d.question_index = questionInfo.survey.last_question_index + index + 1;
if (maxTitle.length === 0) {
let title = Number(questionInfo.survey.last_title.substring(1));
d.title = `Q${title + index + 1}`;
} else {
d.title = `Q${maxTitle[maxTitle.length - 1] + index + 1}`;
}
return d;
});
const activeQuestion = newData[newData.length - 1];
questionList.splice(index + 1, 0, ...newData);
const quesData = getPageQuesByQues(questionList);
const { page, resultList } = quesData;
questionInfo.questions = resultList;
questionInfo.survey.pages = page;
questionInfo.survey.last_question_index = questionInfo.survey.last_question_index + newData.length;
questionInfo.survey.last_title = activeQuestion.title;
store.commit('common/A_COMMON_SET_QUESTIONINFO', JSON.stringify(questionInfo));
store.commit('common/A_COMMON_SET_ACTIVEQUESTION', JSON.stringify(activeQuestion));
nextTick(() => {
new Scroll(document.getElementById(activeQuestion.id)).animate();
});
quesSaveParam.newQuestion.push(...newData);
quesSaveParam.newSurvey.pages = page;
store.commit('common/A_COMMON_SET_QUESSAVEPARAM', JSON.stringify(quesSaveParam));
return quesSaveParam;
};
/** 移动问题 */
const moveQues = (param) => {
const { questionInfo, quesSaveParam } = copyStoreContent(store);
const moveQues = questionInfo.questions[param.event.oldIndex];
const { page, resultList } = getPageQuesByQues(param.quesList);
questionInfo.questions = resultList;
questionInfo.survey.pages = page;
store.commit('common/A_COMMON_SET_QUESTIONINFO', JSON.stringify(questionInfo));
nextTick(() => {
new Scroll(document.getElementById(moveQues.id)).animate();
});
quesSaveParam.newSurvey.pages = page;
store.commit('common/A_COMMON_SET_QUESSAVEPARAM', JSON.stringify(quesSaveParam));
return quesSaveParam;
};
/** 批量移动 */
const batchMoveQues = (quesList) => {
const { questionInfo, quesSaveParam } = copyStoreContent(store);
const { page, resultList } = getPageQuesByQues(quesList);
questionInfo.questions = resultList;
questionInfo.survey.pages = page;
store.commit('common/A_COMMON_SET_QUESTIONINFO', JSON.stringify(questionInfo));
quesSaveParam.newSurvey.pages = page;
store.commit('common/A_COMMON_SET_QUESSAVEPARAM', JSON.stringify(quesSaveParam));
return quesSaveParam;
};
/** 批量添加到题库 */
const batchBank = async (bankVisible, saveLoading, checkQues, checkGroupInfo) => {
saveLoading.value = true;
const param = {
question_indexs: checkQues.value.map((ques) => ques.question_index),
group_id: checkGroupInfo.value.id
};
try {
await batchSaveQuesIntoBank(param);
message.success('保存成功');
const { data } = await getQuesBankList();
store.commit('common/A_COMMON_SET_BANK_LIST', data);
const { questionInfo } = copyStoreContent(store);
store.commit('common/A_COMMON_SET_QUESTIONINFO', JSON.stringify(questionInfo));
} catch (error) {
console.log(error);
}
saveLoading.value = false;
bankVisible.value = false;
};
return {
deleteQues,
batchDeleteQues,
copyQues,
batchCopyQues,
moveQues,
batchMoveQues,
batchBank
};
}
/**
* 在题目增删改查时拦截处理一些事情
*/
function interceptorsHandle(store) {
/**
* 删除题目时的特殊处理
*/
const deleteInterceptor = (delId) => {
const { questionInfo } = JSON.parse(JSON.stringify(store.state.common));
const allAssociate = [];
let delQues = {};
questionInfo.questions.forEach((ques) => {
if (ques.id && ques.id !== delId) {
allAssociate.push(...ques.associate.map((ass) => ass.question_index));
}
if (ques.id === delId) delQues = ques;
});
// 如果删除的题目被其他题关联,则不能删除
if (allAssociate.includes(delQues.question_index)) {
Modal.warning({
class: 'custom-modal',
title: () => '无法操作',
content: () => '该问题已被其他问题关联,请取消关联后再删除'
});
return true;
}
// 有循环关联
const info = store.state.common.questionInfo || {};
const movedList = JSON.parse(JSON.stringify(info.questions || [])).filter(
(i) => i.question_index !== delQues.question_index
);
const cycleStatus = loopingAvailable({
cycles: info.cycle_pages || [],
questions: movedList,
logics: info.logics || [],
isPerPage: info.survey?.is_one_page_one_question
});
if (!cycleStatus) {
return true;
}
return false;
};
/**
* 批量删除题目时对于关联的处理
*/
const batchDeleteInterceptor = (checkQues) => {
const delIds = checkQues.value.map((ques) => ques.id);
const { questionInfo } = JSON.parse(JSON.stringify(store.state.common));
let canDeleteStatus = true;
for (let index = 0; index < checkQues.value.length; index++) {
const delQues = checkQues.value[index];
const allAssociate = [];
questionInfo.questions.forEach((ques) => {
if (ques.id && ques.id !== delQues.id) {
allAssociate.push(...ques.associate.map((ass) => ass.question_index));
}
});
// 如果删除的题目被其他题关联,则不能删除
if (allAssociate.includes(delQues.question_index)) {
canDeleteStatus = false;
message.warning(`${delQues.title}题目选项已关联其他题目,不能删除,请取消关联后再删除`);
break;
}
}
// 有循环关联
const info = JSON.parse(JSON.stringify(store.state.common.questionInfo || {}));
const movedList = (info.questions || []).filter(
(i) => !checkQues.value.some((j) => j.question_index === i.question_index)
);
if (canDeleteStatus) {
canDeleteStatus = loopingAvailable({
cycles: info.cycle_pages || [],
questions: movedList,
logics: info.logics || [],
isPerPage: info.survey?.is_one_page_one_question
});
}
return {
canDeleteStatus,
delIds
};
};
/**
* 拖拽完成时的特殊处理
*/
const dragEndInterceptor = (event, moveResult) => {
const { questionInfo } = JSON.parse(JSON.stringify(store.state.common));
const copyQuesList = questionInfo.questions;
const toIndex = event.newIndex;
const fromIndex = event.oldIndex;
const toQuesInfo = copyQuesList[toIndex];
const fromQuesInfo = copyQuesList[fromIndex];
let newQues = [];
if (fromIndex < toIndex) {
newQues = copyQuesList.slice(fromIndex, toIndex + 1);
} else {
newQues = copyQuesList.slice(toIndex, fromIndex + 1);
}
// 1. 关联问题处理
const associateIndex = [];
newQues.forEach((ques) => {
if (ques.associate) {
associateIndex.push(...ques.associate);
}
});
const status = associateIndex.find(
(ass) => ass.question_index === (fromIndex < toIndex ? fromQuesInfo : toQuesInfo).question_index
);
let statusContent = '';
if (status) {
statusContent = '关联题目不能被移至被关联题目之前';
}
// 2. 解决方案处理 解决方案不能操作,不需要处理回调
// 3. 循环逻辑处理
let loopingStatus = true;
if (!status) {
loopingStatus = loopingAvailable({
cycles: questionInfo.cycle_pages || [],
questions: moveResult,
logics: questionInfo.logics || [],
isPerPage: questionInfo.survey?.is_one_page_one_question
});
}
// 处理结束,不允许排序则弹提示,否则保存
if (status || !loopingStatus) {
if (loopingStatus) {
Modal.warning({
class: 'custom-modal custom-modal-title-notice show-icon',
title: () => '无法操作',
content: () => statusContent || '禁止移动题目'
});
}
store.commit('common/A_COMMON_SET_QUESTIONINFO', JSON.stringify(questionInfo));
return true;
}
return false;
};
/**
* 批量移动时的特殊处理,判断有关联题目不能移动
*/
const batchMoveInterceptor = (checkQues, moveLocation, moveQuesIndex) => {
const { questionInfo } = JSON.parse(JSON.stringify(store.state.common));
const questions = questionInfo.questions.filter((ques) => ques.id);
const relatedLineList = [];
// 获取要移动到的目标位置
const moveQuesLocation = questions.findIndex((ques) => ques.question_index === moveQuesIndex.value) + 1;
checkQues.value.forEach((check) => {
const curCheckInfo = {
title: check.title,
questionIndex: check.question_index,
relateds: [], // 关联线
beRelateds: [] // 被关联线
};
// 递归获取选中题型的关联线
const getRelated = (quesInfo) => {
quesInfo.associate.forEach((ass) => {
const findQues = questions.find((ques) => ques.question_index === ass.question_index);
const findQuesLocation = questions.findIndex((ques) => ques.question_index === ass.question_index) + 1;
findQues.location = findQuesLocation;
curCheckInfo.relateds.push(findQues);
if (findQues.associate) {
getRelated(findQues);
}
});
};
// 递归获取选中题型的被关联线
const getBeRelated = (quesInfo) => {
questions.forEach((ques, index) => {
if (ques.associate && ques.question_index !== quesInfo.question_index) {
const findAssocicate = ques.associate.find((ass) => ass.question_index === quesInfo.question_index);
if (findAssocicate) {
const tempQues = {
...ques,
location: index + 1
};
curCheckInfo.beRelateds.push(tempQues);
getBeRelated(tempQues);
}
}
});
};
getRelated(check);
getBeRelated(check);
relatedLineList.push(curCheckInfo);
});
console.log('relatedLineList', relatedLineList);
// 用来标识是否校验成功如果已经有不能移动的题目则改为false,停止循环,减少计算时间
let canMoveStatus = true;
// 遍历上一步处理好的数组
for (let relIndex = 0; relIndex < relatedLineList.length; relIndex++) {
const rel = relatedLineList[relIndex];
// 复制一份被关联线数据防止后面调用splice方法改变原来的数据
const copyBeRelateds = JSON.parse(JSON.stringify(rel.beRelateds));
// 获取移动的目标位置,选择之后则加1之前则不变
const targetBeRelLocation = moveLocation.value ? moveQuesLocation + 1 : moveQuesLocation;
// 这个循环是拿到被关联线数组中去掉选中题型的值
// 比如q1的被关联数组是[q2,q3,q4,q5,q6],要移动的选中的题目是[q1,q2],则拿到的结果是[q3,q4,q5,q6]
checkQues.value.forEach((check) => {
const findBeRelIndex = copyBeRelateds.findIndex((beRel) => beRel.question_index === check.question_index);
if (findBeRelIndex !== -1) {
copyBeRelateds.splice(findBeRelIndex, 1);
}
});
copyBeRelateds.sort((a, b) => a.location - b.location);
// 这个循环是将上一步拿到的未选中题目数组与目标位置进行对比,如果存在小于的位置则不能移动
// 比如:上一步拿到的结果是[q3,q4,q5,q6]移动的位置是q3之后他的位置是4q3的位置是3小于4则无法移动如果移动的位置是q3之前他的位置是33不小于3则可以移动
for (let index = 0; index < copyBeRelateds.length; index++) {
const beRel = copyBeRelateds[index];
if (beRel.location < targetBeRelLocation) {
message.warning(`${rel.title}${beRel.title}之间存在关联,请取消关联后再移动`);
canMoveStatus = false;
break;
}
}
if (!canMoveStatus) {
break;
}
// 下一步是处理关联线,和被关联线的处理方式相同,只不过最后的判断不一样
const copyRelateds = JSON.parse(JSON.stringify(rel.relateds));
// 获取移动的目标位置,选择之后不变之前则需要减1
const targetRelLocation = moveLocation.value ? moveQuesLocation : moveQuesLocation - 1;
checkQues.value.forEach((check) => {
const findRelIndex = copyRelateds.findIndex((rel) => rel.question_index === check.question_index);
if (findRelIndex !== -1) {
copyRelateds.splice(findRelIndex, 1);
}
});
copyRelateds.sort((a, b) => a.location - b.location);
for (let index = 0; index < copyRelateds.length; index++) {
const element = copyRelateds[index];
if (element.location > targetRelLocation) {
message.warning(`${rel.title}${element.title}之间存在关联,请取消关联后再移动`);
canMoveStatus = false;
break;
}
}
if (!canMoveStatus) {
break;
}
}
// 判断循环逻辑
const checkedList = JSON.parse(JSON.stringify(checkQues.value || []));
checkedList.forEach((i) => (i.$isChecked = true));
const movedList = JSON.parse(JSON.stringify(questionInfo.questions)).filter(
(i) => !checkedList.some((j) => j.question_index === i.question_index)
); // 重排序后的列表
const tempIndex = movedList.findIndex((i) => i.question_index === moveQuesIndex.value);
const endIndex = tempIndex + (moveLocation.value ? 1 : 0);
movedList.splice(endIndex, 0, ...checkedList);
const loopingStatus = loopingAvailable({
cycles: questionInfo.cycle_pages || [],
questions: movedList,
logics: questionInfo.logics || [],
isPerPage: questionInfo.survey?.is_one_page_one_question
});
if (!loopingStatus) {
canMoveStatus = false;
}
if (canMoveStatus) {
questionInfo.questions = questionInfo.questions.filter(
(ques) => !checkQues.value.find((check) => check.question_index === ques.question_index)
);
const moveIndex = questionInfo.questions.findIndex((ques) => ques.question_index === moveQuesIndex.value);
const targetIndex = moveLocation.value ? moveIndex + 1 : moveIndex;
questionInfo.questions.splice(targetIndex, 0, ...checkQues.value);
}
return {
canMoveStatus,
moveResultList: JSON.parse(JSON.stringify(questionInfo.questions))
};
};
/**
* 批量添加到题库时的特殊处理,判断选中题目存在逻辑,关联,引用则不能添加
*/
const batchBankInterceptor = (checkQues, bankVisible) => {
const { questionInfo } = JSON.parse(JSON.stringify(store.state.common));
const errorQues = [];
for (let index = 0; index < checkQues.value.length; index++) {
let canAddQuesInBankStatus = true;
const quesInfo = checkQues.value[index];
const matchArr = quesInfo.stem.match(/(\[%cite\(.*?\)%\])/g) || [];
const logicArr = questionInfo.logics.filter((log) => log.question_index === quesInfo.question_index);
if (quesInfo.associate.length > 0) {
canAddQuesInBankStatus = false;
} else if (matchArr.length > 0) {
canAddQuesInBankStatus = false;
} else if (logicArr.length > 0) {
canAddQuesInBankStatus = false;
}
if (!canAddQuesInBankStatus) {
errorQues.push(quesInfo);
}
}
if (errorQues.length > 0) {
Modal.confirm({
class: 'custom-modal',
title: () => '保存失败',
icon: () => createVNode(ExclamationCircleFilled),
okText: '去修改',
cancelText: '取消',
content: () =>
`您保存的题目${errorQues.map((q) => q.title).join(',')}包含逻辑/关联/引用/随机关系,请解除关系后再试。`,
onOk: () => {
bankVisible.value = false;
nextTick(() => {
store.commit('common/A_COMMON_SET_ACTIVEQUESTION', JSON.stringify(errorQues[0]));
new Scroll(document.getElementById(errorQues[0].id)).animate();
});
}
});
}
return {
canAddQuesInBankStatus: errorQues.length === 0
};
};
return {
deleteInterceptor,
batchDeleteInterceptor,
dragEndInterceptor,
batchMoveInterceptor,
batchBankInterceptor
};
}
</script>
<style lang="scss" scoped>
.catalog {
padding-bottom: 24px;
&-main {
max-height: calc(100vh - 313px);
overflow-y: auto;
}
&-page {
padding-bottom: 24px;
&-title {
font-size: 14px;
font-weight: 500;
padding-left: 12px;
}
}
&-line {
height: 1px;
background: #f5f5f5;
margin: 0 12px 24px 0;
}
&-checkall {
display: flex;
align-items: center;
margin-top: 24px;
font-size: 13px;
font-weight: 400;
color: #434343;
cursor: pointer;
padding-left: 12px;
transition: all 0.3s;
}
&-text {
position: relative;
display: flex;
align-items: center;
margin-top: 24px;
min-height: 37px;
font-size: 13px;
font-weight: 400;
color: #434343;
cursor: pointer;
padding-left: 12px;
&-icon {
margin-right: 10px;
}
&:hover {
background: #f5f5f5;
.catalog-text-hover {
width: 90px;
}
}
&-content {
display: flex;
align-items: center;
&::v-deep p {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
text-align: center;
margin: 0;
padding: 0;
}
}
&-hover {
position: absolute;
right: 0;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: space-around;
width: 0;
height: 100%;
transition: width 0.3s;
overflow: hidden;
.iconfont {
cursor: pointer;
color: #dadada;
&:hover {
color: $yili-default-color;
}
}
}
}
&-more {
display: flex;
justify-content: space-around;
padding-top: 16px;
height: 0;
overflow: hidden;
color: #dadada;
transition: all 0.5s;
.iconfont {
font-size: 20px;
cursor: pointer;
&:hover {
color: $yili-default-color;
}
}
}
&-modal-content {
display: flex;
align-items: center;
font-size: 11px;
font-weight: 400;
color: #434343;
text-align: left;
&-select {
margin-left: 10px;
margin-right: 10px;
flex: 1;
&-html {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200px;
}
}
&-item {
&:not(:last-child) {
margin-bottom: 24px;
}
}
}
.loading {
display: flex;
justify-content: center;
padding-top: 10px;
font-size: 13px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
}
.flex-align {
display: flex;
align-items: center;
}
.custom-checkbox {
display: inline-block;
transition: all 0.3s;
height: 22px;
overflow: hidden;
}
}
.modal-footer {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 11px;
font-weight: 400;
color: #434343;
text-align: left;
&-flex {
display: flex;
align-items: center;
&-select {
margin-left: 10px;
margin-right: 10px;
&-html {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
&-btn {
display: flex;
align-items: center;
color: $yili-default-color;
cursor: pointer;
&-disable {
opacity: 0.5;
cursor: not-allowed;
}
.iconfont {
height: 27px;
}
}
}
</style>