449 lines
14 KiB
Vue
449 lines
14 KiB
Vue
<template>
|
|
<QuesBaseItem :info="info">
|
|
<template v-slot:noMrgOption>
|
|
<a-radio-group
|
|
class="custom-radio-group"
|
|
style="width: 100%"
|
|
v-if="copyInfo.question_type === 1"
|
|
>
|
|
<draggable
|
|
v-model="optionList"
|
|
item-key="id"
|
|
handle=".mover"
|
|
chosenClass="chosen"
|
|
animation="300"
|
|
@end="sortOptionHandle"
|
|
>
|
|
<template #item="{ element, index }">
|
|
<div class="choice-option-ele">
|
|
<div
|
|
style="height: 40px; line-height: 40px"
|
|
:style="{ opacity: element.edit ? 0 : 1 }"
|
|
>
|
|
<i class="iconfont choice-option-ele-icon mover"></i>
|
|
</div>
|
|
<div class="tinymce-option" style="flex: 1">
|
|
<div
|
|
class="choice-option-radio"
|
|
:class="{ 'choice-option-radio-active': element.edit }"
|
|
>
|
|
<a-radio :value="element.id" class="choice-option-radio-item">
|
|
</a-radio>
|
|
<question-tinymce-option
|
|
v-model:html="element.option"
|
|
:ref="`tinymce_${index}`"
|
|
:preventKeyDown="true"
|
|
:hasAfter="!element.edit && element.is_other"
|
|
@keyDown="addOptionHandle(index)"
|
|
@focusChange="editChange($event, element, index)"
|
|
>
|
|
<template v-slot:content>
|
|
<div v-if="element.edit" class="choice-option-radio-btn">
|
|
<a-checkbox
|
|
:checked="Boolean(element.is_other)"
|
|
class="margin"
|
|
@change="otherChange($event, element)"
|
|
><span style="color: #434343"
|
|
>设为其他项</span
|
|
></a-checkbox
|
|
>
|
|
<div class="line" style="margin-right: 30px"></div>
|
|
<i
|
|
class="iconfont choice-option-radio-btn-remove margin"
|
|
@click="deleteOptionHandle(element.id)"
|
|
></i
|
|
>
|
|
</div>
|
|
</template>
|
|
</question-tinymce-option>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</draggable>
|
|
</a-radio-group>
|
|
<a-checkbox-group
|
|
class="custom-checkbox-group"
|
|
:value="checkGroups"
|
|
style="width: 100%"
|
|
v-else-if="copyInfo.question_type === 2"
|
|
@change="groupChange"
|
|
>
|
|
<draggable
|
|
v-model="optionList"
|
|
item-key="id"
|
|
handle=".mover"
|
|
chosenClass="chosen"
|
|
animation="300"
|
|
@end="sortOptionHandle"
|
|
>
|
|
<template #item="{ element, index }">
|
|
<div class="choice-option-ele">
|
|
<div
|
|
style="height: 40px; line-height: 40px"
|
|
:style="{ opacity: element.edit ? 0 : 1 }"
|
|
>
|
|
<i class="iconfont choice-option-ele-icon mover"></i>
|
|
</div>
|
|
<div class="tinymce-option" style="flex: 1">
|
|
<div
|
|
class="choice-option-radio"
|
|
:class="{ 'choice-option-radio-active': element.edit }"
|
|
>
|
|
<a-checkbox
|
|
class="choice-option-radio-item"
|
|
style="margin-right: 8px"
|
|
:value="element.id"
|
|
>
|
|
</a-checkbox>
|
|
<question-tinymce-option
|
|
v-model:html="element.option"
|
|
:ref="`tinymce_${index}`"
|
|
:preventKeyDown="true"
|
|
:hasAfter="!element.edit && element.is_other"
|
|
@keyDown="addOptionHandle(index)"
|
|
@focusChange="editChange($event, element, index)"
|
|
>
|
|
<template v-slot:content>
|
|
<div v-if="element.edit" class="choice-option-radio-btn">
|
|
<a-checkbox-group
|
|
v-model:value="element.other"
|
|
@change="removeOtherChange(element)"
|
|
>
|
|
<a-checkbox value="remove" class="margin"
|
|
><span style="color: #434343"
|
|
>设为排他项</span
|
|
></a-checkbox
|
|
>
|
|
<a-checkbox value="other" class="margin"
|
|
><span style="color: #434343"
|
|
>设为其他项</span
|
|
></a-checkbox
|
|
>
|
|
</a-checkbox-group>
|
|
<div class="line" style="margin-right: 30px"></div>
|
|
<i
|
|
class="iconfont choice-option-radio-btn-remove margin"
|
|
@click="deleteOptionHandle(element.id)"
|
|
></i
|
|
>
|
|
</div>
|
|
</template>
|
|
</question-tinymce-option>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</draggable>
|
|
</a-checkbox-group>
|
|
</template>
|
|
<template v-slot:footer>
|
|
<a-button
|
|
v-if="!disableUpdateBtn"
|
|
type="text"
|
|
class="custom-button"
|
|
@click="addOptionHandle(optionList.length - 1)"
|
|
>
|
|
<div class="flex-align">
|
|
<i class="iconfont"></i>
|
|
<span style="margin-left: 6px">添加选项</span>
|
|
</div>
|
|
</a-button>
|
|
<related-option v-if="!disableUpdateBtn" :info="info" />
|
|
<option-show v-if="!disableUpdateBtn" :info="info" />
|
|
<logical :info="info" />
|
|
<BatchManageOptions
|
|
v-if="!disableUpdateBtn"
|
|
:info="info"
|
|
></BatchManageOptions>
|
|
</template>
|
|
</QuesBaseItem>
|
|
</template>
|
|
|
|
<script>
|
|
import draggable from "vuedraggable";
|
|
import { computed, ref } from "@vue/reactivity";
|
|
import Logical from "../../components/Logical.vue";
|
|
import { getCurrentInstance, nextTick, watch } from "@vue/runtime-core";
|
|
import Option from "../../mode/option.js";
|
|
import QuestionTinymceOption from "../../components/QuestionTinymceOption.vue";
|
|
import QuesBaseItem from "../../fragement/QuesBaseItem.vue";
|
|
import RelatedOption from "../../components/RelatedOption.vue";
|
|
import BatchManageOptions from "../../components/BatchManageOptions.vue";
|
|
import { useStore } from "vuex";
|
|
import OptionShow from "../../components/OptionShow.vue";
|
|
import * as cheerio from "cheerio";
|
|
import { message } from "ant-design-vue";
|
|
import { getOptionName } from "../../js/util.js";
|
|
export default {
|
|
name: "Choice",
|
|
components: {
|
|
QuestionTinymceOption,
|
|
draggable,
|
|
Logical,
|
|
QuesBaseItem,
|
|
RelatedOption,
|
|
BatchManageOptions,
|
|
OptionShow,
|
|
},
|
|
props: {
|
|
info: {
|
|
type: Object,
|
|
default: () => {},
|
|
},
|
|
},
|
|
setup(props, context) {
|
|
const store = useStore();
|
|
const copyInfo = computed(() => {
|
|
const info = JSON.parse(JSON.stringify(props.info));
|
|
if (info.options[0]) {
|
|
info.options[0].forEach((x) => {
|
|
const other = [];
|
|
if (x.is_remove_other) {
|
|
other.push("remove");
|
|
}
|
|
if (x.is_other) {
|
|
other.push("other");
|
|
}
|
|
x.other = other;
|
|
x.check = false;
|
|
});
|
|
}
|
|
return info;
|
|
});
|
|
const disableUpdateBtn = computed(() => {
|
|
return props.info.permissions?.disable_update || false;
|
|
});
|
|
const optionList = ref([]);
|
|
const checkGroups = ref([]);
|
|
/** 修改选项 */
|
|
const editChange = (e, element, index) => {
|
|
element.edit = e;
|
|
if (!element.edit) {
|
|
const curUpdateOpt = props.info.options[0].find(
|
|
(x) => x.id === element.id
|
|
);
|
|
if (
|
|
curUpdateOpt.option !== element.option ||
|
|
curUpdateOpt.is_other !== element.is_other ||
|
|
curUpdateOpt.is_remove_other !== element.is_remove_other
|
|
) {
|
|
// 选项验证重复
|
|
const findIndex = optionSet(
|
|
copyInfo.value.options[0],
|
|
element,
|
|
index
|
|
);
|
|
if (findIndex !== -1) {
|
|
element.option = copyInfo.value.options[0][index].option;
|
|
return;
|
|
}
|
|
optionList.value.forEach((opt) => {
|
|
if (opt.id === element.id) {
|
|
opt.option = element.option;
|
|
}
|
|
});
|
|
emitInfo();
|
|
}
|
|
}
|
|
};
|
|
/** 多选题设为排他,其他 */
|
|
const removeOtherChange = (element) => {
|
|
element.is_remove_other = 0;
|
|
element.is_other = 0;
|
|
if (element.other.includes("remove")) {
|
|
element.is_remove_other = 1;
|
|
}
|
|
if (element.other.includes("other")) {
|
|
element.is_other = 1;
|
|
}
|
|
};
|
|
/** 单选设为其他项 */
|
|
const otherChange = (e, element) => {
|
|
element.is_other = Number(e.target.checked);
|
|
};
|
|
const instance = getCurrentInstance();
|
|
/** 新增选项 */
|
|
const addOptionHandle = (index) => {
|
|
const findIndex = optionSet(
|
|
optionList.value,
|
|
optionList.value[index],
|
|
index
|
|
);
|
|
if (findIndex !== -1) {
|
|
optionList.value[index].option =
|
|
copyInfo.value.options[0][index].option;
|
|
}
|
|
const newOption = new Option();
|
|
copyInfo.value.last_option_index++;
|
|
newOption.option = `<p>选项${getOptionName(optionList.value)}</p>`;
|
|
newOption.option_index = copyInfo.value.last_option_index;
|
|
optionList.value.splice(index + 1, 0, newOption);
|
|
// 解决在回车插入选项时,需要让新加的选项处于编辑状态,而不是还在之前位置
|
|
nextTick(() => {
|
|
const refs = instance.refs;
|
|
refs[`tinymce_${index}`]?.onClickOutsides();
|
|
refs[`tinymce_${index + 1}`]?.clickHandle();
|
|
});
|
|
emitInfo();
|
|
};
|
|
/**选项验重 */
|
|
const optionSet = (optionList, element, index) => {
|
|
const findIndex = optionList.findIndex((opt, fIndex) => {
|
|
if (fIndex !== index) {
|
|
const $old = cheerio
|
|
.load(opt.option)
|
|
.text()
|
|
.replace(/\s*/g, "")
|
|
.replaceAll("\n", "");
|
|
const $new = cheerio
|
|
.load(element.option)
|
|
.text()
|
|
.replace(/\s*/g, "")
|
|
.replaceAll("\n", "");
|
|
return $old === $new;
|
|
}
|
|
});
|
|
if (findIndex !== -1) {
|
|
message.error(
|
|
`提示:第${index + 1}个选项和第${
|
|
findIndex + 1
|
|
}个选项重复,无法自动保存!`
|
|
);
|
|
}
|
|
return findIndex;
|
|
};
|
|
/** 排序选项 */
|
|
const sortOptionHandle = () => {
|
|
emitInfo();
|
|
};
|
|
/** 删除选项 */
|
|
const deleteOptionHandle = (id) => {
|
|
optionList.value = optionList.value.filter((x) => x.id !== id);
|
|
emitInfo();
|
|
};
|
|
const emitInfo = () => {
|
|
const copyOptionList = optionList.value.map((opt) => {
|
|
const temp = {
|
|
...opt,
|
|
};
|
|
delete temp.check;
|
|
delete temp.other;
|
|
delete temp.edit;
|
|
return temp;
|
|
});
|
|
const tempCopyInfo = JSON.parse(JSON.stringify(copyInfo.value));
|
|
tempCopyInfo.options[0] = copyOptionList;
|
|
context.emit("update", tempCopyInfo);
|
|
};
|
|
const groupChange = (e) => {
|
|
if (e.length) {
|
|
const last = e[e.length - 1];
|
|
const option = optionList.value.find((option) => option.id === last);
|
|
if (option.is_remove_other) {
|
|
e = [last];
|
|
} else {
|
|
e = e.filter((value) =>
|
|
optionList.value.find(
|
|
(option) => option.id === value && !option.is_remove_other
|
|
)
|
|
);
|
|
}
|
|
}
|
|
checkGroups.value = e;
|
|
};
|
|
watch(
|
|
copyInfo,
|
|
(val) => {
|
|
optionList.value = JSON.parse(
|
|
JSON.stringify(val.options[0] ? copyInfo.value.options[0] : [])
|
|
);
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
return {
|
|
copyInfo,
|
|
disableUpdateBtn,
|
|
optionList,
|
|
checkGroups,
|
|
editChange,
|
|
otherChange,
|
|
removeOtherChange,
|
|
addOptionHandle,
|
|
deleteOptionHandle,
|
|
sortOptionHandle,
|
|
groupChange,
|
|
};
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.choice {
|
|
&-option {
|
|
&-ele {
|
|
display: flex;
|
|
margin-bottom: 18px;
|
|
&-icon {
|
|
font-size: 24px;
|
|
cursor: move;
|
|
opacity: 0;
|
|
height: 40px;
|
|
&:hover {
|
|
color: $yili-default-color;
|
|
}
|
|
}
|
|
&:hover .iconfont {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
&-radio {
|
|
display: flex;
|
|
min-height: 40px;
|
|
width: 100%;
|
|
border-radius: 5px;
|
|
border: 1px solid transparent;
|
|
padding-left: 12px;
|
|
&:hover {
|
|
border: 1px dashed #8c8c8c;
|
|
}
|
|
&-item {
|
|
height: 40px;
|
|
line-height: 40px;
|
|
}
|
|
&-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
height: 40px;
|
|
&-remove {
|
|
font-size: 16px;
|
|
cursor: pointer;
|
|
color: #bfbfbf;
|
|
&:hover {
|
|
color: $yili-default-color;
|
|
}
|
|
}
|
|
}
|
|
&-active {
|
|
background: #f5f5f5;
|
|
border: 1px solid transparent !important;
|
|
}
|
|
}
|
|
}
|
|
.line {
|
|
width: 1px;
|
|
height: 16px;
|
|
background: #d8d8d8;
|
|
opacity: 0.2;
|
|
border: 1px solid #979797;
|
|
}
|
|
.margin {
|
|
margin-right: 30px;
|
|
}
|
|
}
|
|
.chosen {
|
|
background: #ffffff;
|
|
box-shadow: 0px 4px 20px 0px rgba(0, 0, 0, 0.2);
|
|
}
|
|
</style>
|