Files
ylst-h5/src/views/Design/Index.vue
陈昱达 4d34c293b8 feat(survey): 优化问卷创建功能
- 新增保存设置功能
- 优化题目列表渲染逻辑
- 添加断点续答、IP限制等设置项
-修复部分组件样式问题
2025-03-11 20:11:15 +08:00

319 lines
8.9 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="design-create">
<draggable
v-model:data="questionInfo.questions"
item-key="id"
handle=".moverQues"
chosenClass="chosen"
animation="300"
:scroll="true"
>
<template #item="{ element, index }">
<choose-question
:element="element"
:questions="questionInfo.questions"
:index="index"
:chooseQuestionId="chooseQuestionId"
@get-choose-question-id="getChooseQuestionId"
>
<!-- 选择题 -->
<Choice
v-if="element.question_type === 1 || element.question_type === 2"
:element="element"
:index="index"
:active="chooseQuestionId === element.id"
></Choice>
<!-- 填空题 -->
<Completion
v-if="element.question_type === 4"
:index="index"
:element="element"
:active="chooseQuestionId === element.id"
sn="lXEBBpE2"
></Completion>
<!-- 矩阵题 -->
<martrix-question
v-if="
element.question_type === 8 ||
element.question_type === 9 ||
element.question_type === 10
"
:element="element"
:index="index"
:active="chooseQuestionId === element.id"
/>
<!-- 签名题 -->
<sign-question
v-if="[22].includes(element.question_type)"
:element="element"
:index="index"
:active="chooseQuestionId === element.id"
/>
<!-- 文件上传题 -->
<file-upload
v-if="element.question_type === 18"
:element="element"
:index="index"
:active="chooseQuestionId === element.id"
></file-upload>
<!-- 打分题 -->
<Rate
v-if="element.question_type === 5"
:element="element"
:index="index"
:active="chooseQuestionId === element.id"
sn="lXEBBpE2"
/>
<!--图文-->
<TextWithImages
v-if="element.question_type === 6"
:element="element"
:index="index"
:active="chooseQuestionId === element.id"
/>
<!--图文-->
<NPS
v-if="element.question_type === 106"
:element="element"
:index="index"
:active="chooseQuestionId === element.id"
/>
<!--组件底部左侧操作-->
<template #action="{ element: el }">
<div class="flex slot-actions">
<template v-for="(item, optionIndex) in actionOptions">
<div
v-if="item.question_type.includes(el.question_type)"
:key="optionIndex"
class="flex"
>
<template v-for="(act, actIndex) in item.actions" :key="actIndex">
<div class="flex align-center action-item" @click="actionEvent(act, el)">
<van-icon :name="act.icon"></van-icon>
<span class="ml10">{{ act.label }}</span>
</div>
</template>
</div>
</template>
</div>
</template>
</choose-question>
<!-- {{ element.question_type }}-->
<!-- {{questionInfo.survey.pages.length}}-->
<div v-if="!filterGap">
<paging
v-if="!element.question_type && questionInfo.survey.pages.length > 1"
:info="element"
:index="index"
:active="pageIsActive(activeIndex, questionInfo.questions, element.page)"
@click.stop=""
/>
</div>
</template>
</draggable>
</div>
</template>
<script setup>
import { v4 as uuidv4 } from 'uuid';
import { ref, onMounted, watch, computed } from 'vue';
import { useCounterStore } from '@/stores/counter';
import { storeToRefs } from 'pinia';
import Draggable from './components/Draggable.vue';
import Choice from './components/Questions/Choice.vue';
import ChooseQuestion from './components/ChooseQuestion.vue';
import Paging from './components/Questions/paging/Paging.vue';
import Completion from './components/Questions/Completion.vue';
import MartrixQuestion from './components/Questions/MartrixQuestion.vue';
import Rate from './components/Questions/Rate.vue';
import TextWithImages from '@/views/Design/components/Questions/TextWithImages.vue';
import SignQuestion from './components/Questions/SignQuestion.vue';
import FileUpload from './components/Questions/FileUpload.vue';
import NPS from '@/views/Design/components/Questions/NPS.vue';
const activeIndex = ref(-1);
// 获取所有的 question 列表内容
const { filterGap, activeId } = defineProps({
filterGap: {
type: Boolean,
required: false,
default: false
},
activeId: {
type: String,
default: ''
}
});
watch(
() => activeId,
(newVal) => {
chooseQuestionId.value = newVal;
}
);
/**
* 工具函数
*/
function util() {
/** 通过id找到数组中对应的下标 */
const getIndexById = (arr, id) => arr.findIndex((i) => i.id === id);
/** 通过分页找到数组中对应的下标 */
const getIndexByPage = (arr, page) => arr.findIndex((i) => i.page === page);
const quesIsActive = (activeIndex, questionList, eleId) => {
return activeIndex === getIndexById(questionList, eleId);
};
const pageIsActive = (activeIndex, questionList, elePage) => {
return activeIndex === getIndexByPage(questionList, elePage);
};
const quesIsDisabled = (questionIndex, disabledQuestionIndex) => {
return (disabledQuestionIndex || []).includes(questionIndex);
};
const pagingDisabled = (index, question, disabledQuestionIndex) => {
if (disabledQuestionIndex.includes(question?.[index - 1]?.questionIndex)) {
return true;
}
for (let i = 1; i < question.length - 1; i++) {
if (question?.[index + i]?.page) {
continue;
}
if (disabledQuestionIndex.includes(question?.[index + i]?.questionIndex)) {
return true;
}
}
return false;
};
/** 工具函数复制store中的数据避免直接更改store */
const copyStoreContent = (store) => {
return JSON.parse(JSON.stringify(store.state.common));
};
return {
getIndexById,
getIndexByPage,
quesIsActive,
pageIsActive,
quesIsDisabled,
pagingDisabled,
copyStoreContent
};
}
const { pageIsActive } = util();
// 获取 Store 实例
const counterStore = useCounterStore();
const store = storeToRefs(counterStore);
const chooseQuestionId = ref('');
const questionInfo = computed(() => store.questionsInfo.value);
const emit = defineEmits(['getActiveQuestion']);
// 获取选中的题目的ID
const getChooseQuestionId = (questionItem) => {
chooseQuestionId.value = questionItem.id;
// 向外传出选中的题目
emit('getActiveQuestion', questionItem);
};
// 组件对应的操作
const actionOptions = [
{
question_type: [1, 2, 5],
actions: [
{
label: '添加选项',
icon: 'add',
fun: 'radioAddOption'
}
]
},
{
question_type: [8, 9, 10],
// 矩阵问卷逻辑处理
actions: [
{
label: '添加行标签',
icon: 'add',
fun: 'addMatrixRowOption'
},
{
label: '添加列标签',
icon: 'add',
fun: 'addMatrixColumnOption'
}
]
}
];
// 事件分发
const actionEvent = (item, el) => {
actionFun[item.fun](el);
};
// 总事件注册
const actionFun = {
// 单选事件 添加选项
radioAddOption: (element) => {
element.options.map((item) => {
item.push({
id: uuidv4(),
option: `选项${item.length + 1}`,
option_config: {
image_url: [],
title: '',
instructions: [],
option_type: 0,
limit_right_content: ''
},
option_index: element.last_option_index + 1,
parent_id: 0,
type: 0,
cascade: [],
config: []
});
});
element.last_option_index += 1;
},
/**
* martrix 矩阵行数增加
* @param element {import('./components/Questions/types/martrix.js').MatrixSurveyQuestion}
*/
addMatrixRowOption: (element) => {
element.options[0].push({
option: '新增行'
});
},
/**
* martrix 矩阵列数增加
* @param element {import('./components/Questions/types/martrix.js').MatrixSurveyQuestion}
*/
addMatrixColumnOption: (element) => {
element.options[1].push({ option: '新增列' });
}
};
onMounted(() => {
questionInfo.value = store.questionsInfo.value;
});
</script>
<style scoped lang="scss">
.ml10 {
margin-left: 5px;
}
.design-create {
//min-height: calc(100vh);
background-color: #e9eef3;
color: #333;
.slot-actions {
& .action-item + .action-item {
margin-left: 10px;
}
}
}
</style>