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

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

File diff suppressed because it is too large Load Diff

View File

@@ -162,16 +162,5 @@ export default {
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
}
permissions: {}
};

View File

@@ -0,0 +1,60 @@
export default {
id: '',
title: '',
stem: '<p>请输入题目(填空)</p>',
other: '',
question_index: '',
question_type: 4,
config: {
min: '',
max: '',
version: '',
scene: null,
shelf: null,
ware: null,
placeholder: '',
is_required: 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,
left_prompt: '',
right_prompt: '',
text_type: 0,
include_mark: 0,
line_type: 1,
line_height: 3,
decimal_few: 1,
type_name: '',
float_window: 0,
float_window_content: '',
popup_window: 0,
popup_window_content: '',
is_show: [],
quick_type: 0
},
created_at: '',
created_user_id: 1180,
updated_user_id: null,
survey_id: 8721,
logic_config: {
expect: '',
order: 0,
type: 0,
stay_time: ''
},
options: [],
associate: [],
logics_has: false,
last_option_index: 0,
question_code: '',
question_value: '',
question_tag: '',
planet_id: '',
permissions: {}
};

View File

@@ -0,0 +1,54 @@
export default {
// JSON 需要修改
id: '',
question_type: 5,
question_index: '',
stem: '请完成打分',
title: '',
options: [
[
{
id: 'e46f51b1-bfd8-4d9c-becc-4fb7d175a6f4',
is_fixed: 0,
is_other: 0,
is_remove_other: 0,
option: '<p>选项1</p>',
option_config: {
image_url: [],
title: '',
instructions: [],
option_type: 0,
limit_right_content: ''
},
option_index: 1,
parent_id: 0,
type: 0,
cascade: [],
config: []
}
]
],
last_option_index: 1,
config: {
is_required: 1,
quick_type: 0,
is_show: [],
max: 5,
min: 1,
prompt_center: '',
prompt_left: '',
prompt_right: '',
score_interval: 1,
score_type: 0,
score_way: 0,
prompt_score: 2
},
associate: [],
question_code: '',
logic_config: {
order: 0,
type: 0,
expect: '',
stay_time: ''
}
};

View File

@@ -162,16 +162,5 @@ export default {
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
}
permissions: {}
};

View File

@@ -0,0 +1,53 @@
export default {
id: '',
question_type: 5,
question_index: '',
stem: '请完成打分',
title: '',
options: [
[
{
id: 'e46f51b1-bfd8-4d9c-becc-4fb7d175a6f4',
is_fixed: 0,
is_other: 0,
is_remove_other: 0,
option: '<p>选项1</p>',
option_config: {
image_url: [],
title: '',
instructions: [],
option_type: 0,
limit_right_content: ''
},
option_index: 1,
parent_id: 0,
type: 0,
cascade: [],
config: []
}
]
],
last_option_index: 1,
config: {
is_required: 1,
quick_type: 0,
is_show: [],
max: 5,
min: 1,
prompt_center: '',
prompt_left: '',
prompt_right: '',
score_interval: 1,
score_type: 0,
score_way: 0,
prompt_score: 2
},
associate: [],
question_code: '',
logic_config: {
order: 0,
type: 0,
expect: '',
stay_time: ''
}
};

View File

@@ -1,3 +1,6 @@
// 引入QuestionJsons 下的radio 并导出
export { default as radio } from './QuestionJsons/radio.js';
export { default as checkbox } from './QuestionJsons/checkbox.js';
export { default as radio } from './QuestionJsons/Radio.js';
export { default as checkbox } from './QuestionJsons/Checkbox.js';
export { default as completion } from './QuestionJsons/Completion.js';
export { default as rate } from './QuestionJsons/Rate.js';
export { default as martrixQuestion } from './QuestionJsons/MartrixQuestion.js';

View File

@@ -20,11 +20,13 @@
<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"
@@ -38,13 +40,15 @@
element.question_type === 10
"
:element="element"
:index="index"
:active="chooseQuestionId === element.id"
/>
<!-- 签名题 -->
<sign-question
v-if="[9, 10, 22].includes(element.question_type)"
v-if="[22].includes(element.question_type)"
:element="element"
:index="index"
:active="chooseQuestionId === element.id"
/>
@@ -52,6 +56,7 @@
<file-upload
v-if="element.question_type === 18"
:element="element"
:index="index"
:active="chooseQuestionId === element.id"
></file-upload>
@@ -59,6 +64,7 @@
<Rate
v-if="element.question_type === 5"
:element="element"
:index="index"
:active="chooseQuestionId === element.id"
sn="lXEBBpE2"
/>
@@ -67,12 +73,14 @@
<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"
/>
<!--组件底部左侧操作-->

View File

@@ -7,12 +7,7 @@
class="base-select"
>
<template #left-icon>
<div
class="van-filed"
:contenteditable="active"
@blur="saveStem($event, element, 'title')"
v-html="element.title"
/>
{{ index + 1 }}
</template>
<template #label>
<contenteditable v-model="element.stem" :active="active"></contenteditable>
@@ -93,6 +88,10 @@ const props = defineProps({
active: {
type: Boolean,
default: false
},
index: {
type: Number,
default: 0
}
});
@@ -100,9 +99,6 @@ const element = ref(props.element);
const saveOption = (e, ele) => {
ele.option = e.target.innerHTML;
};
const saveStem = (e, ele, key) => {
ele[key] = e.target.innerHTML;
};
</script>
<style scoped lang="scss">
.choice-html {

View File

@@ -6,6 +6,9 @@
:required="element.config.is_required === 1"
label-align="top"
>
<template #left-icon>
{{ index + 1 }}
</template>
<template #label>
<div
:contenteditable="active"
@@ -30,6 +33,10 @@ const props = defineProps({
// 补充
}
},
index: {
type: Number,
default: 0
},
active: {
type: Boolean,
default: false

View File

@@ -6,6 +6,9 @@
:required="element.config.is_required === 1"
label-align="top"
>
<template #left-icon>
{{ index + 1 }}
</template>
<template #label>
<div
:contenteditable="active"
@@ -43,6 +46,10 @@ const props = defineProps({
element: {
type: Object
},
index: {
type: Number,
default: 0
},
active: {
type: Boolean,
default: false

View File

@@ -6,6 +6,9 @@
:required="element.config.is_required === 1"
label-align="top"
>
<template #left-icon>
{{ index + 1 }}
</template>
<template #label>
<div
:contenteditable="active"
@@ -47,6 +50,10 @@ const props = defineProps({
type: Boolean,
default: false
},
index: {
type: Number,
default: 0
},
sn: { type: String, default: '' },
questionType: { type: [String, Number], default: 4 }
});

View File

@@ -7,6 +7,9 @@
label-align="top"
class="base-select"
>
<template #left-icon>
{{ index + 1 }}
</template>
<template #label>
<contenteditable v-model="element.stem" :active="active"></contenteditable>
<!-- <div v-html="element.stem" v-else></div>-->
@@ -29,11 +32,13 @@ const props = defineProps({
active: {
type: Boolean,
default: false
},
index: {
type: Number,
default: 0
}
});
const element = ref(props.element);
</script>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

View File

@@ -66,8 +66,9 @@ 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 { radio, checkbox, completion, rate, martrixQuestion } from '@/utils/importJsons';
import { useRoute } from 'vue-router';
// 获取 Store 实例
const counterStore = useCounterStore();
const store = storeToRefs(counterStore);
@@ -108,67 +109,102 @@ const quesList = ref([
{
icon: 'star-o',
name: '填空题',
question_type: '3'
},
{
icon: 'phone-o',
name: '图形打分',
question_type: '4'
question_type: '3',
json: completion
},
// {
// icon: 'phone-o',
// name: '图形打分',
// question_type: '4',
// json: rate
// },
{
icon: 'cart-o',
name: '数值打分',
question_type: '5'
question_type: '4',
json: rate
},
{
icon: 'comment-o',
name: '矩阵单选',
question_type: '6'
question_type: '8',
json: martrixQuestion
},
{
icon: 'bag-o',
name: '矩阵多选',
question_type: '7'
question_type: '9',
json: martrixQuestion
},
{
icon: 'gift-o',
name: '矩阵填空',
question_type: '8'
question_type: '10',
json: martrixQuestion
},
{
icon: 'bag-o',
name: '文件上传',
question_type: '9'
},
{
icon: 'bag-o',
name: 'NPS',
question_type: '106'
}
]);
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;
}
// switch (item.question_type) {
// // 单选 多选
// case '1':
// case '2':
//
//
// break;
// // 填空
// case '3':
// questionJson = JSON.parse(
// JSON.stringify({
// ...item.json,
// id: uuidv4(),
// question_index: questionInfo.value.survey.last_question_index + 1
// })
// );
// break;
// // 图形打分
// case '4':
// questionJson = JSON.parse(
// JSON.stringify({
// ...item.json,
// id: uuidv4(),
// question_index: questionInfo.value.survey.last_question_index + 1
// })
// );
// break
// }
questionJson = JSON.parse(
JSON.stringify({
...item.json,
id: uuidv4(),
question_type: Number(item.question_type),
question_index: questionInfo.value.survey.last_question_index + 1,
options:
item.json.options.length > 0
? item.json.options.map((item) => {
return item.map((it) => {
return {
...it,
// 主键生成
id: uuidv4()
};
});
})
: []
})
);
// 给store 装数据 判断是 push 还是 splice
if (activeQuestionIndex.value === -1) {
questionInfo.value.questions.push(questionJson);