feat(component): 优化 contenteditable组件功能
- 添加 showAction 控制编辑按钮显示 - 实现文本域聚焦和失焦时的编辑按钮显示和隐藏 -优化键盘弹出和收起时的编辑按钮显示逻辑 -修复文档中描述的产品问卷配置- 优化问卷设计页面的题目编辑功能
This commit is contained in:
3
components.d.ts
vendored
3
components.d.ts
vendored
@@ -14,9 +14,12 @@ declare module 'vue' {
|
||||
VanButton: typeof import('vant/es')['Button']
|
||||
VanCell: typeof import('vant/es')['Cell']
|
||||
VanCellGroup: typeof import('vant/es')['CellGroup']
|
||||
VanCheck: typeof import('vant/es')['Check']
|
||||
VanCheckbo: typeof import('vant/es')['Checkbo']
|
||||
VanCheckbox: typeof import('vant/es')['Checkbox']
|
||||
VanCheckboxGroup: typeof import('vant/es')['CheckboxGroup']
|
||||
VanCol: typeof import('vant/es')['Col']
|
||||
VanDialog: typeof import('vant/es')['Dialog']
|
||||
VanDivider: typeof import('vant/es')['Divider']
|
||||
VanField: typeof import('vant/es')['Field']
|
||||
VanIcon: typeof import('vant/es')['Icon']
|
||||
|
||||
@@ -1,39 +1,40 @@
|
||||
<template>
|
||||
<div
|
||||
ref="editor"
|
||||
contenteditable="true"
|
||||
:contenteditable="active"
|
||||
class="van-field"
|
||||
@focus="showToolbar"
|
||||
@blur="save"
|
||||
v-html="modelValue"
|
||||
></div>
|
||||
<div ref="editorAction" class="editor-action">
|
||||
<button v-for="item in actions" :key="item.name" @click="funEvent(item)">{{ item.label }}</button>
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineEmits, ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { defineEmits, ref, onMounted, watch } from 'vue';
|
||||
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const editor = ref(null);
|
||||
const editorAction = ref(null);
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
let lastHeight = window.innerHeight;
|
||||
|
||||
const save = (e) => {
|
||||
emit('update:modelValue', e.target.innerHTML);
|
||||
emit('update:modelValue', e.innerHTML);
|
||||
};
|
||||
|
||||
const functions = {
|
||||
// todo 点击按钮之后 如何判断 才能让按钮再次点击 不消失 获取重新聚焦 再次选中文本?
|
||||
boldModern: () => {
|
||||
document.execCommand('bold', false, null);
|
||||
document.execCommand('bold', true, null);
|
||||
},
|
||||
underLine: () => {
|
||||
document.execCommand('underline', false, null);
|
||||
@@ -47,6 +48,21 @@ const funEvent = (item) => {
|
||||
functions[item.fun]();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.active,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
showAction.value = true;
|
||||
} else {
|
||||
save(editor.value);
|
||||
setTimeout(() => {
|
||||
showAction.value = false;
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const showAction = ref(false);
|
||||
const actions = [
|
||||
{
|
||||
label: '加粗',
|
||||
@@ -69,31 +85,19 @@ const actions = [
|
||||
}
|
||||
];
|
||||
|
||||
const showToolbar = () => {
|
||||
editorAction.value.style.display = '';
|
||||
};
|
||||
|
||||
const handleResize = () => {
|
||||
const currentHeight = window.innerHeight;
|
||||
if (currentHeight < lastHeight) {
|
||||
// 键盘弹出
|
||||
editorAction.value.style.display = '';
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
// 键盘收起
|
||||
editorAction.value.style.display = 'none';
|
||||
}, 100);
|
||||
}
|
||||
lastHeight = currentHeight;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', handleResize);
|
||||
editor.value.addEventListener('focus', () => {
|
||||
showAction.value = true;
|
||||
});
|
||||
|
||||
document.addEventListener('resize', () => {
|
||||
showAction.value = false;
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
// onBeforeUnmount(() => {
|
||||
// editor.value.removeEventListener('resize', handleResize);
|
||||
// });
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
|
||||
src:
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix')
|
||||
format('embedded-opentype'),
|
||||
format('embedded-opentype'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont')
|
||||
format('svg');
|
||||
format('svg');
|
||||
}
|
||||
|
||||
.logo {
|
||||
|
||||
@@ -797,7 +797,7 @@ export const useCommonStore = defineStore('common', {
|
||||
stem: '<p>请问,您的实足年龄在以下哪个区间呢?指的是您上一次过生日时的年龄</p>',
|
||||
other: '',
|
||||
question_index: 27,
|
||||
question_type: 1,
|
||||
question_type: 6,
|
||||
config: {
|
||||
placeholder: '',
|
||||
version: '',
|
||||
@@ -1097,7 +1097,7 @@ export const useCommonStore = defineStore('common', {
|
||||
stem: '<p><span style="font-family: \'PingFang SC Regular\';">假设介绍中描述的产品在您平时购物的商店或网站销售的话,以下哪句话最能描述您为自己或家人<span style="text-decoration: underline;"><strong>购买</strong><strong>这款产品A的可能性</strong></span>呢?</span></p>\n<p><img style="height: 50px; max-height: 440px;" src="https://test-cxp-public-web-1302259445.cos.ap-beijing.myqcloud.com/uat-yls/packing/imgs/1725421981418_809_mao-23.jpg" /></p>',
|
||||
other: '概念诊断问卷配置概念诊断1',
|
||||
question_index: 1,
|
||||
question_type: 1,
|
||||
question_type: 6,
|
||||
config: {
|
||||
placeholder: '',
|
||||
version: '',
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
<martrix-question
|
||||
v-if="
|
||||
element.question_type === 8 ||
|
||||
element.question_type === 9 ||
|
||||
element.question_type === 10
|
||||
element.question_type === 9 ||
|
||||
element.question_type === 10
|
||||
"
|
||||
:element="element"
|
||||
:active="chooseQuestionId === element.id"
|
||||
|
||||
@@ -117,23 +117,23 @@ const openMoveModel = (item, index) => {
|
||||
// 上下移动
|
||||
const optionMove = (action) => {
|
||||
switch (action.action) {
|
||||
case 'up':
|
||||
if (activeIndex.value === 0) {
|
||||
moveShow.value = false;
|
||||
return false;
|
||||
}
|
||||
// 向上移动
|
||||
element.value.splice(activeIndex.value - 1, 0, element.value.splice(activeIndex.value, 1)[0]);
|
||||
activeIndex.value -= 1;
|
||||
break;
|
||||
case 'down':
|
||||
if (activeIndex.value === element.value.length - 1) {
|
||||
moveShow.value = false;
|
||||
return false;
|
||||
}
|
||||
element.value.splice(activeIndex.value + 1, 0, element.value.splice(activeIndex.value, 1)[0]);
|
||||
activeIndex.value += 1;
|
||||
break;
|
||||
case 'up':
|
||||
if (activeIndex.value === 0) {
|
||||
moveShow.value = false;
|
||||
return false;
|
||||
}
|
||||
// 向上移动
|
||||
element.value.splice(activeIndex.value - 1, 0, element.value.splice(activeIndex.value, 1)[0]);
|
||||
activeIndex.value -= 1;
|
||||
break;
|
||||
case 'down':
|
||||
if (activeIndex.value === element.value.length - 1) {
|
||||
moveShow.value = false;
|
||||
return false;
|
||||
}
|
||||
element.value.splice(activeIndex.value + 1, 0, element.value.splice(activeIndex.value, 1)[0]);
|
||||
activeIndex.value += 1;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -196,8 +196,8 @@ const getSkipTypeText = (skipType) => {
|
||||
const ls = [];
|
||||
logics.map((item) => {
|
||||
if (
|
||||
item.skip_type === skipType &&
|
||||
item.question_index === activeQuestion.value.question_index
|
||||
item.skip_type === skipType
|
||||
&& item.question_index === activeQuestion.value.question_index
|
||||
) {
|
||||
ls.push(item);
|
||||
}
|
||||
@@ -213,13 +213,13 @@ const getSkipTypeText = (skipType) => {
|
||||
|
||||
const questionSetting = (type) => {
|
||||
switch (type) {
|
||||
case 'before':
|
||||
questionBeforeShow.value = true;
|
||||
case 'before':
|
||||
questionBeforeShow.value = true;
|
||||
|
||||
break;
|
||||
case 'after':
|
||||
questionBeforeShow.value = true;
|
||||
break;
|
||||
break;
|
||||
case 'after':
|
||||
questionBeforeShow.value = true;
|
||||
break;
|
||||
}
|
||||
skipType.value = type === 'before' ? 1 : 0;
|
||||
};
|
||||
|
||||
@@ -108,9 +108,9 @@ function isSurplus() {
|
||||
return false;
|
||||
} else {
|
||||
return (
|
||||
parseFloat(((localConfig.value.max - localConfig.value.min) * 1000).toFixed(4, 10)) %
|
||||
parseFloat((localConfig.value.score_interval * 1000).toFixed(4, 10)) ===
|
||||
0
|
||||
parseFloat(((localConfig.value.max - localConfig.value.min) * 1000).toFixed(4, 10))
|
||||
% parseFloat((localConfig.value.score_interval * 1000).toFixed(4, 10))
|
||||
=== 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,16 @@
|
||||
label-align="top"
|
||||
class="base-select"
|
||||
>
|
||||
<template #label>
|
||||
<template #left-icon>
|
||||
<div
|
||||
class="van-filed"
|
||||
v-html="element.title"
|
||||
:contenteditable="active"
|
||||
class="van-field"
|
||||
@blur="saveStem($event, element)"
|
||||
v-html="element.stem"
|
||||
></div>
|
||||
@blur="saveStem($event, element, 'title')"
|
||||
/>
|
||||
</template>
|
||||
<template #label>
|
||||
<contenteditable v-model="element.stem" :active="active"></contenteditable>
|
||||
</template>
|
||||
<template #input>
|
||||
<template v-for="(item, index) in element.options" :key="index">
|
||||
@@ -76,7 +79,8 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import OptionAction from '@/views/Design/components/ActionCompoents/OptionAction.vue';
|
||||
import { ref } from 'vue';
|
||||
import { ref, defineAsyncComponent } from 'vue';
|
||||
const Contenteditable = defineAsyncComponent(() => import('@/components/contenteditable.vue'));
|
||||
const props = defineProps({
|
||||
element: {
|
||||
type: Object,
|
||||
@@ -96,8 +100,8 @@ const element = ref(props.element);
|
||||
const saveOption = (e, ele) => {
|
||||
ele.option = e.target.innerHTML;
|
||||
};
|
||||
const saveStem = (e, ele) => {
|
||||
ele.stem = e.target.innerHTML;
|
||||
const saveStem = (e, ele, key) => {
|
||||
ele[key] = e.target.innerHTML;
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
class="base-select"
|
||||
>
|
||||
<template #label>
|
||||
<contenteditable v-model="element.stem"></contenteditable>
|
||||
<contenteditable v-model="element.stem" :active="active"></contenteditable>
|
||||
<!-- <div v-html="element.stem" v-else></div>-->
|
||||
</template>
|
||||
</van-field>
|
||||
</div>
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
class="iconfont active-icon"
|
||||
:style="{ marginRight: isLastPage ? '0' : '16px' }"
|
||||
@click="activePage"
|
||||
></i
|
||||
>
|
||||
></i>
|
||||
<template v-if="!isLastPage">
|
||||
<i class="iconfont moverQues" style="margin-right: 16px"></i>
|
||||
<i class="iconfont" @click="deleteHandle"></i>
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
<div v-for="item in 10" :key="item" class="template">
|
||||
<img src="https://picsum.photos/131/128" width="110" height="100" alt="" />
|
||||
<span>报名/签到模板</span>
|
||||
<span style="color: rgb(127, 127, 127)"
|
||||
>报名签到 | 引用 {{ item }} 次 | 创建人: {{ '张三' }}</span
|
||||
>
|
||||
<span style="color: rgb(127, 127, 127)">报名签到 | 引用 {{ item }} 次 | 创建人: {{ '张三' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user