Files
ylst-h5/src/views/Design/Index.vue
陈昱达 8c2d426421 feat(Design): 重构题前逻辑组件
- 新增 YLInput 组件
- 新增 questionSteeingList 工具文件
- 新增 BeforeRadio、BeforeCheckbox 和 BeforeCompletion 组件
- 重构 QuestionBefore 组件,支持不同题型的逻辑配置
- 优化 QuestionAction 和 ChooseQuestion组件,增加 questionsInfo 参数
- 调整 NPS 和 Design 组件,以适应新的逻辑配置方式
2025-03-14 19:16:58 +08:00

455 lines
13 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"
: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>