feat(component): 优化 contenteditable组件功能

- 添加 showAction 控制编辑按钮显示
- 实现文本域聚焦和失焦时的编辑按钮显示和隐藏
-优化键盘弹出和收起时的编辑按钮显示逻辑
-修复文档中描述的产品问卷配置- 优化问卷设计页面的题目编辑功能
This commit is contained in:
陈昱达
2025-03-07 16:56:25 +08:00
parent ae04b89a62
commit 8f903e3869
11 changed files with 930 additions and 67 deletions

1
components.d.ts vendored
View File

@@ -23,6 +23,7 @@ declare module 'vue' {
VanDivider: typeof import('vant/es')['Divider'] VanDivider: typeof import('vant/es')['Divider']
VanField: typeof import('vant/es')['Field'] VanField: typeof import('vant/es')['Field']
VanIcon: typeof import('vant/es')['Icon'] VanIcon: typeof import('vant/es')['Icon']
VanNavBar: typeof import('vant/es')['NavBar']
VanPopup: typeof import('vant/es')['Popup'] VanPopup: typeof import('vant/es')['Popup']
VanRadio: typeof import('vant/es')['Radio'] VanRadio: typeof import('vant/es')['Radio']
VanRadioGroup: typeof import('vant/es')['RadioGroup'] VanRadioGroup: typeof import('vant/es')['RadioGroup']

View File

@@ -1,12 +1,9 @@
<template> <template>
<div <div ref="editor" :contenteditable="active" class="van-field" v-html="modelValue"></div>
ref="editor"
:contenteditable="active"
class="van-field"
v-html="modelValue"
></div>
<div v-if="showAction && active" ref="editorAction" class="editor-action"> <div v-if="showAction && active" ref="editorAction" class="editor-action">
<button v-for="item in actions" :key="item.name" @click="funEvent(item,$event)">{{ item.label }}</button> <button v-for="item in actions" :key="item.name" @click="funEvent(item, $event)">
{{ item.label }}
</button>
</div> </div>
</template> </template>
@@ -52,7 +49,7 @@ watch(
() => props.active, () => props.active,
(newVal) => { (newVal) => {
if (newVal) { if (newVal) {
showAction.value = true; // showAction.value = true;
} else { } else {
save(editor.value); save(editor.value);
setTimeout(() => { setTimeout(() => {
@@ -105,7 +102,7 @@ onMounted(() => {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
left: 0; left: 0;
z-index: 3000; z-index: 2008;
width: 100%; width: 100%;
height: 40px; height: 40px;
padding: 0 10px; padding: 0 10px;

View File

@@ -0,0 +1,177 @@
export default {
title: '',
stem: '<p>请输入题目(多选)</p>',
other: '',
question_index: '',
question_type: 2,
config: {
placeholder: '',
version: '',
scene: null,
shelf: null,
ware: null,
option_groups: null,
is_required: 1,
select_random: 0,
each_number: 1,
is_three_dimensions: 0,
material_sn: '',
scene_information: null,
simple_scene_information: null,
is_behavior: 0,
is_price_tag: 0,
is_brand: 0,
is_initialize: 0,
is_default_perspective: 0,
is_binding_goods: 0,
float_window: 0,
float_window_content: '',
popup_window: 0,
popup_window_content: '',
is_show: [],
quick_type: 0,
option_group_random_inside: null,
option_group_random_outside: null
},
created_at: '',
created_user_id: '',
updated_user_id: null,
survey_id: '',
logic_config: {
expect: '',
order: 0,
type: 0,
stay_time: ''
},
options: [
[
{
option: '<span >选项1</span>',
id: '',
type: 0,
is_other: 0,
is_fixed: 0,
is_remove_other: 0,
created_at: null,
created_user_id: null,
parent_id: null,
option_index: 1,
option_code: '',
option_config: {
title: '',
instructions: [],
price: 0,
gradient: '',
image_url: null,
option_type: 0,
type: 0,
limit_right_content: '',
child_area: null,
binding_goods_id: ''
},
disable_option_update: 1,
cascade: []
},
{
option: '<span >选项2</span>',
id: '',
type: 0,
is_other: 0,
is_fixed: 0,
is_remove_other: 0,
created_at: null,
created_user_id: null,
parent_id: null,
option_index: 2,
option_code: '',
option_config: {
title: '',
instructions: [],
price: 0,
gradient: '',
image_url: null,
option_type: 0,
type: 0,
limit_right_content: '',
child_area: null,
binding_goods_id: ''
},
disable_option_update: 1,
cascade: []
},
{
option: '<span >选项3</span>',
id: '',
type: 0,
is_other: 0,
is_fixed: 0,
is_remove_other: 0,
created_at: null,
created_user_id: null,
parent_id: null,
option_index: 3,
option_code: '',
option_config: {
title: '',
instructions: [],
price: 0,
gradient: '',
image_url: null,
option_type: 0,
type: 0,
limit_right_content: '',
child_area: null,
binding_goods_id: ''
},
disable_option_update: 1,
cascade: []
},
{
option: '<span >选项4</span>',
id: '',
type: 0,
is_other: 0,
is_fixed: 0,
is_remove_other: 0,
created_at: null,
created_user_id: null,
parent_id: null,
option_index: 4,
option_code: '',
option_config: {
title: '',
instructions: [],
price: 0,
gradient: '',
image_url: null,
option_type: 0,
type: 0,
limit_right_content: '',
child_area: null,
binding_goods_id: ''
},
disable_option_update: 1,
cascade: []
}
]
],
associate: [],
logics_has: false,
last_option_index: 4,
question_code: '',
question_value: '',
question_tag: '',
planet_id: '',
permissions: {
id: 9032,
question_id: 17835006,
parent_question_id: 0,
disable_update: 1,
disable_option_update: 1,
disable_copy: 1,
disable_delete: 1,
delete_group_keep_one: 1,
disable_copy_delete: 1,
disable_copy_update: 1
}
};

View File

@@ -0,0 +1,177 @@
export default {
title: '',
stem: '<p>请输入题目(单选)</p>',
other: '',
question_index: '',
question_type: 1,
config: {
placeholder: '',
version: '',
scene: null,
shelf: null,
ware: null,
option_groups: null,
is_required: 1,
select_random: 0,
each_number: 1,
is_three_dimensions: 0,
material_sn: '',
scene_information: null,
simple_scene_information: null,
is_behavior: 0,
is_price_tag: 0,
is_brand: 0,
is_initialize: 0,
is_default_perspective: 0,
is_binding_goods: 0,
float_window: 0,
float_window_content: '',
popup_window: 0,
popup_window_content: '',
is_show: [],
quick_type: 0,
option_group_random_inside: null,
option_group_random_outside: null
},
created_at: '',
created_user_id: '',
updated_user_id: null,
survey_id: '',
logic_config: {
expect: '',
order: 0,
type: 0,
stay_time: ''
},
options: [
[
{
option: '<span >选项1</span>',
id: '',
type: 0,
is_other: 0,
is_fixed: 0,
is_remove_other: 0,
created_at: null,
created_user_id: null,
parent_id: null,
option_index: 1,
option_code: '',
option_config: {
title: '',
instructions: [],
price: 0,
gradient: '',
image_url: null,
option_type: 0,
type: 0,
limit_right_content: '',
child_area: null,
binding_goods_id: ''
},
disable_option_update: 1,
cascade: []
},
{
option: '<span >选项2</span>',
id: '',
type: 0,
is_other: 0,
is_fixed: 0,
is_remove_other: 0,
created_at: null,
created_user_id: null,
parent_id: null,
option_index: 2,
option_code: '',
option_config: {
title: '',
instructions: [],
price: 0,
gradient: '',
image_url: null,
option_type: 0,
type: 0,
limit_right_content: '',
child_area: null,
binding_goods_id: ''
},
disable_option_update: 1,
cascade: []
},
{
option: '<span >选项3</span>',
id: '',
type: 0,
is_other: 0,
is_fixed: 0,
is_remove_other: 0,
created_at: null,
created_user_id: null,
parent_id: null,
option_index: 3,
option_code: '',
option_config: {
title: '',
instructions: [],
price: 0,
gradient: '',
image_url: null,
option_type: 0,
type: 0,
limit_right_content: '',
child_area: null,
binding_goods_id: ''
},
disable_option_update: 1,
cascade: []
},
{
option: '<span >选项4</span>',
id: '',
type: 0,
is_other: 0,
is_fixed: 0,
is_remove_other: 0,
created_at: null,
created_user_id: null,
parent_id: null,
option_index: 4,
option_code: '',
option_config: {
title: '',
instructions: [],
price: 0,
gradient: '',
image_url: null,
option_type: 0,
type: 0,
limit_right_content: '',
child_area: null,
binding_goods_id: ''
},
disable_option_update: 1,
cascade: []
}
]
],
associate: [],
logics_has: false,
last_option_index: 4,
question_code: '',
question_value: '',
question_tag: '',
planet_id: '',
permissions: {
id: 9032,
question_id: 17835006,
parent_question_id: 0,
disable_update: 1,
disable_option_update: 1,
disable_copy: 1,
disable_delete: 1,
delete_group_keep_one: 1,
disable_copy_delete: 1,
disable_copy_update: 1
}
};

331
src/utils/common.js Normal file
View File

@@ -0,0 +1,331 @@
const basicQuesTypeList = [
{
name: '选择题',
icon: '&#xe6d3;',
check: false,
type: 1,
childTypes: [1, 2]
},
{
name: '级联题',
icon: '&#xe6b6;',
check: false,
type: 3,
childTypes: [3]
},
{
name: '填空题',
icon: '&#xe6c2;',
check: false,
type: 4,
childTypes: [4]
},
{
name: '多项填空题',
icon: '&#xe850;',
check: false,
type: 27,
childTypes: [27]
},
{
name: '打分题',
icon: '&#xe6c1;',
check: false,
type: 5,
childTypes: [5]
},
{
name: '矩阵题',
icon: '&#xe71c;',
check: false,
type: 9,
childTypes: [8, 9, 10, 11]
},
{
name: '图片题',
icon: '&#xe71e;',
check: false,
type: 13,
childTypes: [12, 13, 14]
},
{
name: '分类题',
icon: '&#xe700;',
check: false,
type: 15,
childTypes: [15]
},
{
name: '排序题',
icon: '&#xe71d;',
check: false,
type: 16,
childTypes: [16]
},
{
name: '图文说明题',
icon: '&#xe6d0;',
check: false,
type: 6,
childTypes: [6]
},
{
name: '日期/时间',
icon: '&#xe6ac;',
check: false,
type: 7,
childTypes: [7]
},
{
name: '恒定总和题',
icon: '&#xe6ff;',
check: false,
type: 17,
childTypes: [17]
},
{
name: '文件上传题',
icon: '&#xe6f9;',
check: false,
type: 18,
childTypes: [18]
},
{
name: '热区题',
icon: '&#xe834;',
check: false,
type: 25,
childTypes: [25, 56]
},
{
name: 'NPS',
icon: '&#xe833;',
check: false,
type: 106,
childTypes: [106]
}
];
const quickQuesTypeList = [
{
name: '姓名',
icon: '&#xe713;',
check: false,
type: 4,
quickType: 6
},
{
name: '性别',
icon: '&#xe716;',
check: false,
type: 1,
quickType: 7
},
{
name: '手机号',
icon: '&#xe638;',
check: false,
type: 20
},
{
name: '身份证号',
icon: '&#xe6ba;',
check: false,
type: 4,
quickType: 2
},
{
name: '邮箱',
icon: '&#xe70b;',
check: false,
type: 4,
quickType: 8
},
{
name: '年龄',
icon: '&#xe701;',
check: false,
type: 4,
quickType: 9
},
{
name: '年龄段',
icon: '&#xe6fb;',
check: false,
type: 1,
quickType: 10
},
{
name: '生日',
icon: '&#xe729;',
check: false,
type: 7,
quickType: 11
},
{
name: '学历',
icon: '&#xe712;',
check: false,
type: 1,
quickType: 12
},
{
name: '院校',
icon: '&#xe718;',
check: false,
type: 3,
quickType: 13
},
{
name: '专业',
icon: '&#xe705;',
check: false,
type: 3,
quickType: 14
},
{
name: '行业',
icon: '&#xe715;',
check: false,
type: 1,
quickType: 15
},
{
name: '地理位置',
icon: '&#xe6fd;',
check: false,
type: 19,
quickType: 16
},
{
name: '省份',
icon: '&#xe6b8;',
check: false,
type: 3,
quickType: 3
},
{
name: '省市',
icon: '&#xe6bd;',
check: false,
type: 3,
quickType: 4
},
{
name: '省市区/县',
icon: '&#xe6cb;',
check: false,
type: 3,
quickType: 5
},
// {
// type: 21,
// quickType: 21,
// name: "密码",
// icon: "&#xe639;",
// check: false,
// },
{
type: 22,
name: '签名题',
icon: '&#xe637;',
check: false
},
{
type: 23,
name: '知情同意书',
icon: '&#xe63a;',
check: false
}
];
const advancedQuesTypeList = [
{
name: 'Maxdiff',
icon: '&#xe70c;',
check: false,
type: 105
},
// {
// name: "CBC",
// icon: "&#xe70d;",
// check: false,
// type: 103,
// },
// // {
// // name: "BPTO",
// // icon: "&#xe70d;",
// // check: false,
// // type: 104,
// // },
{
name: 'PSM',
icon: '&#xe708;',
check: false,
type: 101
},
{
name: 'KANO',
icon: '&#xe710;',
check: false,
type: 102
}
];
const d3QuestypeList = [
{
name: 'ID test',
icon: '&#xe762;',
check: false,
type: 200
},
{
name: 'Experiment',
icon: '&#xe765;',
check: false,
type: 201
}
];
const uxSimuatorQuestypeList = [
{
name: 'UI',
icon: '&#xe764;',
check: false,
type: 202
},
{
name: 'Prototype',
icon: '&#xe763;',
check: false,
type: 203
},
{
name: 'UE',
icon: '&#xe761;',
check: false,
type: 204
}
];
function 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;
}
module.exports = {
basicQuesTypeList,
quickQuesTypeList,
advancedQuesTypeList,
d3QuestypeList,
uxSimuatorQuestypeList,
getIcon
};

3
src/utils/importJsons.js Normal file
View File

@@ -0,0 +1,3 @@
// 引入QuestionJsons 下的radio 并导出
export { default as radio } from './QuestionJsons/radio.js';
export { default as checkbox } from './QuestionJsons/checkbox.js';

View File

@@ -1,22 +1,32 @@
<template> <template>
<div class="design-create"> <div class="design-create">
<draggable <draggable
v-model:data="questionInfo.questions" item-key="id" handle=".moverQues" chosenClass="chosen" v-model:data="questionInfo.questions"
animation="300" :scroll="true" item-key="id"
handle=".moverQues"
chosenClass="chosen"
animation="300"
:scroll="true"
> >
<template #item="{ element, index }"> <template #item="{ element, index }">
<choose-question <choose-question
:element="element" :questions="questionInfo.questions" :index="index" :element="element"
:chooseQuestionId="chooseQuestionId" @get-choose-question-id="getChooseQuestionId" :questions="questionInfo.questions"
:index="index"
:chooseQuestionId="chooseQuestionId"
@get-choose-question-id="getChooseQuestionId"
> >
<!-- 选择题 --> <!-- 选择题 -->
<Choice <Choice
v-if="element.question_type === 1 || element.question_type === 2" :element="element" v-if="element.question_type === 1 || element.question_type === 2"
:element="element"
:active="chooseQuestionId === element.id" :active="chooseQuestionId === element.id"
></Choice> ></Choice>
<!-- 填空题 --> <!-- 填空题 -->
<Completion <Completion
v-if="element.question_type === 4" :element="element" :active="chooseQuestionId === element.id" v-if="element.question_type === 4"
:element="element"
:active="chooseQuestionId === element.id"
sn="lXEBBpE2" sn="lXEBBpE2"
></Completion> ></Completion>
@@ -26,37 +36,54 @@
element.question_type === 8 || element.question_type === 8 ||
element.question_type === 9 || element.question_type === 9 ||
element.question_type === 10 element.question_type === 10
" :element="element" :active="chooseQuestionId === element.id" "
:element="element"
:active="chooseQuestionId === element.id"
/> />
<!-- 签名题 --> <!-- 签名题 -->
<sign-question <sign-question
v-if="element.question_type === 22" :element="element" v-if="[9, 10, 22].includes(element.question_type)"
:element="element"
:active="chooseQuestionId === element.id" :active="chooseQuestionId === element.id"
></sign-question> />
<!-- 文件上传题 --> <!-- 文件上传题 -->
<file-upload <file-upload
v-if="element.question_type === 18" :element="element" :active="chooseQuestionId === element.id" v-if="element.question_type === 18"
:element="element"
:active="chooseQuestionId === element.id"
></file-upload> ></file-upload>
<!-- 打分题 --> <!-- 打分题 -->
<Rate <Rate
v-if="element.question_type === 5" :element="element" :active="chooseQuestionId === element.id" v-if="element.question_type === 5"
:element="element"
:active="chooseQuestionId === element.id"
sn="lXEBBpE2" sn="lXEBBpE2"
/> />
<!--图文--> <!--图文-->
<TextWithImages <TextWithImages
v-if="element.question_type === 6" :element="element" v-if="element.question_type === 6"
:element="element"
:active="chooseQuestionId === element.id"
/>
<!--图文-->
<NPS
v-if="element.question_type === 106"
:element="element"
:active="chooseQuestionId === element.id" :active="chooseQuestionId === element.id"
/> />
<!--组件底部左侧操作--> <!--组件底部左侧操作-->
<template #action="{ element: el }"> <template #action="{ element: el }">
<div class="flex slot-actions"> <div class="flex slot-actions">
<template v-for="(item, optionIndex) in actionOptions"> <template v-for="(item, optionIndex) in actionOptions">
<div v-if="item.question_type.includes(el.question_type)" :key="optionIndex" class="flex"> <div
v-if="item.question_type.includes(el.question_type)"
:key="optionIndex"
class="flex"
>
<template v-for="(act, actIndex) in item.actions" :key="actIndex"> <template v-for="(act, actIndex) in item.actions" :key="actIndex">
<div class="flex align-center action-item" @click="actionEvent(act, el)"> <div class="flex align-center action-item" @click="actionEvent(act, el)">
<van-icon :name="act.icon"></van-icon> <van-icon :name="act.icon"></van-icon>
@@ -72,8 +99,11 @@
<!-- {{ element.question_type }}--> <!-- {{ element.question_type }}-->
<!-- {{questionInfo.survey.pages.length}}--> <!-- {{questionInfo.survey.pages.length}}-->
<paging <paging
v-if="!element.question_type && questionInfo.survey.pages.length > 1" :info="element" :index="index" v-if="!element.question_type && questionInfo.survey.pages.length > 1"
:active="pageIsActive(activeIndex, questionInfo.questions, element.page)" @click.stop="" :info="element"
:index="index"
:active="pageIsActive(activeIndex, questionInfo.questions, element.page)"
@click.stop=""
/> />
</template> </template>
</draggable> </draggable>
@@ -94,6 +124,8 @@ import Rate from './components/Questions/Rate.vue';
import TextWithImages from '@/views/Design/components/Questions/TextWithImages.vue'; import TextWithImages from '@/views/Design/components/Questions/TextWithImages.vue';
import SignQuestion from './components/Questions/SignQuestion.vue'; import SignQuestion from './components/Questions/SignQuestion.vue';
import FileUpload from './components/Questions/FileUpload.vue'; import FileUpload from './components/Questions/FileUpload.vue';
import NPS from '@/views/Design/components/Questions/NPS.vue';
const activeIndex = ref(-1); const activeIndex = ref(-1);
/** /**
* 工具函数 * 工具函数
@@ -148,9 +180,12 @@ const store = storeToRefs(counterStore);
const chooseQuestionId = ref(''); const chooseQuestionId = ref('');
const questionInfo = ref(store.questionsInfo.value); const questionInfo = ref(store.questionsInfo.value);
const emit = defineEmits(['getActiveQuestion']);
// 获取选中的题目的ID // 获取选中的题目的ID
const getChooseQuestionId = (questionItem) => { const getChooseQuestionId = (questionItem) => {
chooseQuestionId.value = questionItem.id; chooseQuestionId.value = questionItem.id;
// 向外传出选中的题目
emit('getActiveQuestion', questionItem);
}; };
// 组件对应的操作 // 组件对应的操作
const actionOptions = [ const actionOptions = [
@@ -238,12 +273,12 @@ onMounted(() => {
} }
.design-create { .design-create {
min-height: calc(100vh); //min-height: calc(100vh);
background-color: #e9eef3; background-color: #e9eef3;
color: #333; color: #333;
.slot-actions { .slot-actions {
& .action-item+.action-item { & .action-item + .action-item {
margin-left: 10px; margin-left: 10px;
} }
} }

View File

@@ -22,7 +22,11 @@
></van-switch> ></van-switch>
</template> </template>
</van-cell> </van-cell>
<van-cell title="选项随机" :border="false"> <van-cell
v-if="[1, 2].includes(activeQuestion.question_type)"
title="选项随机"
:border="false"
>
<template #right-icon> <template #right-icon>
<van-switch <van-switch
v-model="activeQuestion.config.select_random" v-model="activeQuestion.config.select_random"
@@ -95,13 +99,13 @@
</van-popup> </van-popup>
</template> </template>
<script setup> <script setup>
import checkboxQuestionAction from './components/QuestionItemAction/checkboxQuestionAction.vue'; import checkboxQuestionAction from './components/QuestionItemAction/CheckboxQuestionAction.vue';
import { showConfirmDialog } from 'vant'; import { showConfirmDialog } from 'vant';
import { ref } from 'vue'; import { ref } from 'vue';
import { useCounterStore } from '@/stores/counter'; import { useCounterStore } from '@/stores/counter';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import QuestionBefore from '@/views/Design/components/ActionCompoents/components/QuestionItemAction/QuestionBefore.vue'; import QuestionBefore from '@/views/Design/components/ActionCompoents/components/QuestionItemAction/QuestionBefore.vue';
import RateAction from './components/RateAction.vue'; import RateAction from './components/OptionItemAction/RateAction.vue';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
const store = useCounterStore(); const store = useCounterStore();
const { questionsInfo } = storeToRefs(store); const { questionsInfo } = storeToRefs(store);

View File

@@ -0,0 +1,77 @@
<template>
<div class="content">
<van-field
v-model="element.stem"
:label="element.stem"
:required="element.config.is_required === 1"
label-align="top"
>
<template #label>
<div
:contenteditable="active"
class="van-field"
@blur="saveStem($event, element)"
v-html="element.stem"
></div>
</template>
<template #input>
<div v-for="(optionItem, optionItemIndex) in element.options" :key="optionItemIndex">
<div v-for="(item, index) in optionItem" :key="index" @click="chooseOption(item)">
<!-- <div-->
<!-- :contenteditable="item.id === chooseId"-->
<!-- class="van-field"-->
<!-- v-html="item.option"-->
<!-- ></div>-->
<RateCharacter :config="element.config"></RateCharacter>
<div class="tips">
<p>{{ element.config.prompt_left }}</p>
<p>{{ element.config.prompt_center }}</p>
<p>{{ element.config.prompt_right }}</p>
</div>
</div>
</div>
</template>
</van-field>
</div>
</template>
<script setup>
import { ref } from 'vue';
import RateCharacter from './RateCharacter.vue';
const props = defineProps({
element: {
type: Object
},
active: {
type: Boolean,
default: false
},
sn: { type: String, default: '' },
questionType: { type: [String, Number], default: 4 }
});
const element = ref(props.element);
const chooseId = ref('');
// 创建一个本地副本以保存更改
const localElement = ref({ ...props.element });
const saveStem = (e) => {
localElement.value.stem = e.target.innerHTML;
};
const chooseOption = (item) => {
chooseId.value = item.id;
};
</script>
<style scoped lang="scss">
.content {
background-color: #fff;
}
.tips {
display: flex;
justify-content: space-between;
color: #bfbfbf;
}
</style>

View File

@@ -15,27 +15,32 @@
</div> </div>
<button @click="show = true">添加题目</button> <button @click="show = true">添加题目</button>
<div class="ques">
<van-popup v-model:show="show" round position="bottom" :style="{ height: '50%' }">
<van-row class="ques_title">
<van-col :span="6">添加题目</van-col>
<van-col :span="6" :offset="12" @click="show = false">
<van-icon name="close" />
</van-col>
</van-row>
<ul>
<li v-for="item in quesList" :key="item.type">
<div>
<van-icon :name="item.icon" size="20" />
<p>{{ item.name }}</p>
</div>
</li>
</ul>
</van-popup>
</div>
</div> </div>
</van-cell-group> </van-cell-group>
<div class="ques">
<!-- 题目-->
<Design @get-active-question="getActiveQuestion"></Design>
<van-button @click="show = true">添加题目</van-button>
<!-- 弹出的新增题目弹窗-->
<van-popup v-model:show="show" round position="bottom" :style="{ height: '50%' }">
<van-row class="ques_title">
<van-col :span="6">添加题目</van-col>
<van-col :span="6" :offset="12" @click="show = false">
<van-icon name="close" />
</van-col>
</van-row>
<ul>
<li v-for="item in quesList" :key="item.type" @click="questionEvent(item)">
<div>
<van-icon :name="item.icon" size="20" />
<p>{{ item.name }}</p>
</div>
</li>
</ul>
</van-popup>
</div>
<van-cell-group inset class="thanks-cell"> <van-cell-group inset class="thanks-cell">
<div>您已完成本次调研感谢您的参与</div> <div>您已完成本次调研感谢您的参与</div>
</van-cell-group> </van-cell-group>
@@ -57,71 +62,127 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import Design from '@/views/Design/Index.vue';
import { useCounterStore } from '@/stores/counter';
import { storeToRefs } from 'pinia';
import { v4 as uuidv4 } from 'uuid';
import { radio, checkbox } from '@/utils/importJsons';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
// 获取 Store 实例
const counterStore = useCounterStore();
const store = storeToRefs(counterStore);
const chooseQuestionId = ref('');
const questionInfo = ref(store.questionsInfo.value);
const activeQuestionIndex = ref(-1);
const route = useRoute(); const route = useRoute();
const surveyTitle = route.meta.title as string; const surveyTitle = route.meta.title as string;
const show = ref(false); const show = ref(false);
// 获取选中的题目
const getActiveQuestion = (activeQues) => {
chooseQuestionId.value = activeQues.id;
// 在questions 里 查找index 给 activeQuestionIndex
questionInfo.value.questions.forEach((item, index) => {
if (item.id === activeQues.id) {
activeQuestionIndex.value = index;
}
});
};
const quesList = ref([ const quesList = ref([
{ {
icon: 'location-o', icon: 'location-o',
name: '单选题', name: '单选题',
type: '1' question_type: '1',
json: radio
}, },
{ {
icon: 'like-o', icon: 'like-o',
name: '多选题', name: '多选题',
type: '2' question_type: '2',
json: checkbox
}, },
{ {
icon: 'star-o', icon: 'star-o',
name: '填空题', name: '填空题',
type: '3' question_type: '3'
}, },
{ {
icon: 'phone-o', icon: 'phone-o',
name: '图形打分', name: '图形打分',
type: '4' question_type: '4'
}, },
{ {
icon: 'cart-o', icon: 'cart-o',
name: '数值打分', name: '数值打分',
type: '5' question_type: '5'
}, },
{ {
icon: 'comment-o', icon: 'comment-o',
name: '矩阵单选', name: '矩阵单选',
type: '6' question_type: '6'
}, },
{ {
icon: 'bag-o', icon: 'bag-o',
name: '矩阵多选', name: '矩阵多选',
type: '7' question_type: '7'
}, },
{ {
icon: 'gift-o', icon: 'gift-o',
name: '矩阵填空', name: '矩阵填空',
type: '8' question_type: '8'
}, },
{ {
icon: 'bag-o', icon: 'bag-o',
name: '文件上传', name: '文件上传',
type: '9' question_type: '9'
} }
]); ]);
const questionEvent = (item) => {
let questionJson = {};
switch (item.question_type) {
// 单选
case '1':
case '2':
questionJson = JSON.parse(
JSON.stringify({
...item.json,
id: uuidv4(),
question_index: questionInfo.value.survey.last_question_index + 1,
options: item.json.options.map((item) => {
return item.map((it) => {
return {
...it,
// 主键生成
id: uuidv4()
};
});
})
})
);
questionInfo.value.questions.splice(activeQuestionIndex.value + 1, 0);
break;
}
// 给store 装数据 判断是 push 还是 splice
if (activeQuestionIndex.value === -1) {
questionInfo.value.questions.push(questionJson);
} else {
questionInfo.value.questions.splice(activeQuestionIndex.value + 1, 0, questionJson);
}
// 更新题目索引
questionInfo.value.survey.last_question_index += 1;
};
const init = () => { const init = () => {
// event.detail 为当前输入的值 // event.detail 为当前输入的值
show.value = true; show.value = true;
}; };
// const handleInputIntro = (e: Event) => {
// // event.detail 为当前输入的值
// console.log(e);
// surveyIntro.value = e.target.value;
// };
defineExpose({ init }); defineExpose({ init });
</script> </script>
@@ -137,7 +198,7 @@ defineExpose({ init });
} }
* { * {
font-size: 12px; //font-size: 12px;
} }
} }
@@ -145,13 +206,13 @@ defineExpose({ init });
margin: 10px 5px; margin: 10px 5px;
padding: 5px; padding: 5px;
&>div { & > div {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
text-align: center; text-align: center;
&>button { & > button {
margin: 20px; margin: 20px;
border-radius: 10px; border-radius: 10px;
background-color: lightgreen; background-color: lightgreen;