- 新增 YLInput 组件 - 新增 questionSteeingList 工具文件 - 新增 BeforeRadio、BeforeCheckbox 和 BeforeCompletion 组件 - 重构 QuestionBefore 组件,支持不同题型的逻辑配置 - 优化 QuestionAction 和 ChooseQuestion组件,增加 questionsInfo 参数 - 调整 NPS 和 Design 组件,以适应新的逻辑配置方式
455 lines
13 KiB
Vue
455 lines
13 KiB
Vue
<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"
|
||
:questionsInfo="questionInfo"
|
||
@get-choose-question-id="getChooseQuestionId"
|
||
@move="emitFun.move"
|
||
@copy="emitFun.copy"
|
||
@delete="emitFun.delete"
|
||
@setting="emitFun.setting"
|
||
@logics="emitFun.logics"
|
||
>
|
||
<!-- 打分题 -->
|
||
<Rate
|
||
v-if="element.question_type === 5"
|
||
:element="computedElement(element)"
|
||
:index="index"
|
||
:active="chooseQuestionId === element.id"
|
||
@update:element="updateElement"
|
||
/>
|
||
<!-- 选择题 -->
|
||
<Choice
|
||
v-if="element.question_type === 1 || element.question_type === 2"
|
||
:index="index"
|
||
:active="chooseQuestionId === element.id"
|
||
:element="computedElement(element)"
|
||
@update:element="updateElement"
|
||
></Choice>
|
||
<!-- 填空题 -->
|
||
<Completion
|
||
v-if="element.question_type === 4"
|
||
:index="index"
|
||
:element="computedElement(element)"
|
||
:active="chooseQuestionId === element.id"
|
||
@update:element="updateElement"
|
||
></Completion>
|
||
|
||
<!-- 矩阵题 -->
|
||
<martrix-question
|
||
v-if="
|
||
element.question_type === 8 ||
|
||
element.question_type === 9 ||
|
||
element.question_type === 10
|
||
"
|
||
:element="computedElement(element)"
|
||
:index="index"
|
||
:active="chooseQuestionId === element.id"
|
||
@update:element="updateElement"
|
||
/>
|
||
|
||
<!-- 签名题 -->
|
||
<sign-question
|
||
v-if="[22].includes(element.question_type)"
|
||
:element="computedElement(element)"
|
||
:index="index"
|
||
:active="chooseQuestionId === element.id"
|
||
@update:element="updateElement"
|
||
/>
|
||
|
||
<!-- 文件上传题 -->
|
||
<file-upload
|
||
v-if="element.question_type === 18"
|
||
:element="computedElement(element)"
|
||
:index="index"
|
||
:active="chooseQuestionId === element.id"
|
||
@update:element="updateElement"
|
||
></file-upload>
|
||
|
||
<!--图文-->
|
||
<TextWithImages
|
||
v-if="element.question_type === 6"
|
||
:element="computedElement(element)"
|
||
:index="index"
|
||
:active="chooseQuestionId === element.id"
|
||
@update:element="updateElement"
|
||
/>
|
||
<!--图文-->
|
||
<NPS
|
||
v-if="element.question_type === 106"
|
||
:element="computedElement(element)"
|
||
:index="index"
|
||
:active="chooseQuestionId === element.id"
|
||
@update:element="updateElement"
|
||
/>
|
||
<!--组件底部左侧操作-->
|
||
<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>
|
||
<!-- 增加控制按钮-->
|
||
<slot name="button" :item="element"></slot>
|
||
|
||
<!-- {{ 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 * as Base64 from 'js-base64';
|
||
import { ref, onMounted, watch, computed, reactive } from 'vue';
|
||
import { useCounterStore } from '@/stores/counter';
|
||
import { storeToRefs } from 'pinia';
|
||
import { useRoute } from 'vue-router';
|
||
import { saveQuestion, sync } from '@/api/design/index.js';
|
||
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';
|
||
import { getPages } from '@/utils/public.js';
|
||
|
||
const activeIndex = ref(-1);
|
||
const route = useRoute();
|
||
// 获取所有的 question 列表内容
|
||
const { filterGap, activeId } = defineProps({
|
||
filterGap: {
|
||
type: Boolean,
|
||
required: false,
|
||
default: false
|
||
},
|
||
activeId: {
|
||
type: String,
|
||
default: ''
|
||
}
|
||
});
|
||
|
||
const computedElement = computed(() => (element) => {
|
||
return reactive({
|
||
...element,
|
||
// 添加需要响应式的属性
|
||
options: element.options.map((opt) => reactive(opt))
|
||
});
|
||
});
|
||
|
||
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 chooseQuestionIndex = ref(-1);
|
||
|
||
const questionInfo = computed(() => store.questionsInfo.value);
|
||
// 自动更新 题型
|
||
watch(
|
||
() => questionInfo.value.questions,
|
||
(newVal) => {
|
||
if (newVal) {
|
||
saveQueItem(questionInfo.value.logics, newVal); // 确保保存最新的数据
|
||
}
|
||
},
|
||
{ deep: true }
|
||
);
|
||
|
||
const emit = defineEmits(['getActiveQuestion']);
|
||
// 获取选中的题目的ID
|
||
const getChooseQuestionId = (questionItem, index) => {
|
||
chooseQuestionId.value = questionItem.id;
|
||
chooseQuestionIndex.value = index;
|
||
// 向外传出选中的题目
|
||
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;
|
||
|
||
saveQueItem(questionInfo.value.logics, [element]);
|
||
},
|
||
|
||
/**
|
||
* martrix 矩阵行数增加
|
||
* @param element {import('./components/Questions/types/martrix.js').MatrixSurveyQuestion}
|
||
*/
|
||
addMatrixRowOption: (element) => {
|
||
const optionIndex = element.last_option_index;
|
||
element.options[0].push({
|
||
cascade: [],
|
||
config: [],
|
||
is_fixed: 0,
|
||
is_other: 0,
|
||
is_remove_other: 0,
|
||
option: `<p style="text-align:center">行标签${element.options[0].length + 1}</p>`,
|
||
option_config: {
|
||
image_url: [],
|
||
title: '',
|
||
instructions: [],
|
||
option_type: 0,
|
||
limit_right_content: '<p>右极文字1</p>'
|
||
},
|
||
parent_id: 0,
|
||
type: 1,
|
||
id: uuidv4(),
|
||
option_index: optionIndex + 1
|
||
});
|
||
|
||
element.last_option_index = optionIndex + 1;
|
||
|
||
saveQueItem(questionInfo.value.logics, [element]);
|
||
},
|
||
/**
|
||
* martrix 矩阵列数增加
|
||
* @param element {import('./components/Questions/types/martrix.js').MatrixSurveyQuestion}
|
||
*/
|
||
addMatrixColumnOption: (element) => {
|
||
const optionIndex = element.last_option_index;
|
||
element.options[1].push({
|
||
cascade: [],
|
||
config: [],
|
||
is_fixed: 0,
|
||
is_other: 0,
|
||
is_remove_other: 0,
|
||
option: `<p style="text-align:center">列标签${element.options[1].length + 1}</p>`,
|
||
option_config: {
|
||
image_url: [],
|
||
title: '',
|
||
instructions: [],
|
||
option_type: 0,
|
||
limit_right_content: '<p>右极文字1</p>'
|
||
},
|
||
parent_id: 0,
|
||
type: 2,
|
||
id: uuidv4(),
|
||
option_index: optionIndex + 1
|
||
});
|
||
|
||
element.last_option_index = optionIndex + 1;
|
||
saveQueItem(questionInfo.value.logics, [element]);
|
||
}
|
||
};
|
||
|
||
// emit 事件
|
||
const saveQueItem = (logics, questions, survey) => {
|
||
// questions.map((item, index) => {
|
||
// item.title = index + 1;
|
||
// });
|
||
saveQuestion({
|
||
sn: route.query.sn,
|
||
data: {
|
||
logics: logics || [],
|
||
questions: questions || [],
|
||
survey: {
|
||
local_pages: [],
|
||
...survey,
|
||
pages: getPages(
|
||
questionInfo.value.questions,
|
||
questionInfo.value.survey.is_one_page_one_question
|
||
),
|
||
version: Base64.encode(`${new Date().getTime()}`)
|
||
}
|
||
}
|
||
}).then(() => {
|
||
sync({ sn: route.query.sn });
|
||
});
|
||
};
|
||
|
||
const emitFun = {
|
||
move: () => {
|
||
saveQueItem();
|
||
},
|
||
copy: (item) => {
|
||
saveQueItem(null, [item]);
|
||
},
|
||
delete: () => {
|
||
saveQueItem();
|
||
},
|
||
// 右下角操作
|
||
setting: (item) => {
|
||
saveQueItem(null, [item]);
|
||
},
|
||
logics: (item) => {
|
||
// console.log(questionInfo.value.logics[);
|
||
// return false;
|
||
saveQueItem(questionInfo.value.logics, [item]);
|
||
}
|
||
};
|
||
// 直接切割 变更为响应式
|
||
const updateElement = (newElement) => {
|
||
const index = questionInfo.value.questions.findIndex((q) => q.id === newElement.id);
|
||
if (index !== -1) {
|
||
questionInfo.value.questions.splice(index, 1, newElement);
|
||
}
|
||
saveQueItem(questionInfo.value.logics, [newElement]);
|
||
};
|
||
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>
|