feat(components): 为 contenteditable组件添加错误消息显示
- 在 contenteditable.vue 中添加了 errorMessage属性和对应的样式 - 在多个组件中为 contenteditable 添加了 errorMessage 属性 - 优化了部分代码格式,如空格、换行等
This commit is contained in:
@@ -1,31 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex contenteditable align-center space-between" :class="className">
|
<div :class="className">
|
||||||
<p
|
<div class="flex contenteditable align-center space-between">
|
||||||
:id="'editor' + id"
|
<p
|
||||||
ref="editor"
|
:id="'editor' + id"
|
||||||
:contenteditable="active"
|
ref="editor"
|
||||||
class="van-field contenteditable-content"
|
:contenteditable="active"
|
||||||
:data-placeholder="placeholder"
|
class="van-field contenteditable-content"
|
||||||
@focus="onFocus"
|
:data-placeholder="placeholder"
|
||||||
@input="onChange($event.target, $event)"
|
@focus="onFocus"
|
||||||
v-html="modelValue"
|
@input="onChange($event.target, $event)"
|
||||||
></p>
|
v-html="modelValue"
|
||||||
<!-- <p-->
|
></p>
|
||||||
<!-- v-else-->
|
<!-- <p-->
|
||||||
<!-- ref="editor"-->
|
<!-- v-else-->
|
||||||
<!-- class="van-field contenteditable-content"-->
|
<!-- ref="editor"-->
|
||||||
<!-- v-html="modelValue"-->
|
<!-- class="van-field contenteditable-content"-->
|
||||||
<!-- :placeholder="placeholder"-->
|
<!-- v-html="modelValue"-->
|
||||||
<!-- ></p>-->
|
<!-- :placeholder="placeholder"-->
|
||||||
<div class="right-icon ml10">
|
<!-- ></p>-->
|
||||||
<slot name="right-icon"></slot>
|
<div class="right-icon ml10">
|
||||||
|
<slot name="right-icon"></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="showAction && active" ref="editorAction" class="editor-action">
|
||||||
|
<button v-for="item in actions" :key="item.name" @click="funEvent(item, $event)">
|
||||||
|
<van-icon class-prefix="mobilefont" :name="item.icon"></van-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="error-message">
|
||||||
|
{{ errorMessage }}
|
||||||
|
<!-- <slot name="error"></slot>-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="showAction && active" ref="editorAction" class="editor-action">
|
|
||||||
<button v-for="item in actions" :key="item.name" @click="funEvent(item, $event)">
|
|
||||||
<van-icon class-prefix="mobilefont" :name="item.icon"></van-icon>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -49,6 +54,10 @@ const modelValue = defineModel('modelValue', {
|
|||||||
default: ''
|
default: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const errorMessage = defineModel('errorMessage', {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
});
|
||||||
const active = defineModel('active', {
|
const active = defineModel('active', {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
@@ -75,7 +84,7 @@ const functions = {
|
|||||||
document.execCommand('italic', false, null);
|
document.execCommand('italic', false, null);
|
||||||
},
|
},
|
||||||
|
|
||||||
uploadImage: async() => {
|
uploadImage: async () => {
|
||||||
// 保存当前光标位置
|
// 保存当前光标位置
|
||||||
savedRange.value = saveSelection();
|
savedRange.value = saveSelection();
|
||||||
|
|
||||||
@@ -84,7 +93,7 @@ const functions = {
|
|||||||
|
|
||||||
fileInput.click();
|
fileInput.click();
|
||||||
|
|
||||||
fileInput.onchange = async(e) => {
|
fileInput.onchange = async (e) => {
|
||||||
const [file] = e.target.files;
|
const [file] = e.target.files;
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
if (file.size > 2 * 1024 * 1024) {
|
if (file.size > 2 * 1024 * 1024) {
|
||||||
@@ -169,10 +178,10 @@ const isEmptyContent = (html) => {
|
|||||||
const trimmedHtml = html.trim();
|
const trimmedHtml = html.trim();
|
||||||
// 检查是否为空字符串、仅包含 <br> 或仅包含 <p><br></p>
|
// 检查是否为空字符串、仅包含 <br> 或仅包含 <p><br></p>
|
||||||
return (
|
return (
|
||||||
trimmedHtml === ''
|
trimmedHtml === '' ||
|
||||||
|| trimmedHtml === '<br>'
|
trimmedHtml === '<br>' ||
|
||||||
|| trimmedHtml === '<p><br></p>'
|
trimmedHtml === '<p><br></p>' ||
|
||||||
|| trimmedHtml === '<p></p>'
|
trimmedHtml === '<p></p>'
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -299,7 +308,11 @@ const insertImageAtCaret = (html) => {
|
|||||||
.right-icon {
|
.right-icon {
|
||||||
color: #c4c9d4;
|
color: #c4c9d4;
|
||||||
}
|
}
|
||||||
|
.error-message {
|
||||||
|
color: red;
|
||||||
|
flex: none;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
.editor-action {
|
.editor-action {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
className="contenteditable-label"
|
className="contenteditable-label"
|
||||||
:active="active"
|
:active="active"
|
||||||
@blur="emitValue"
|
@blur="emitValue"
|
||||||
|
:errorMessage="errorMessage"
|
||||||
>
|
>
|
||||||
</contenteditable>
|
</contenteditable>
|
||||||
</template>
|
</template>
|
||||||
@@ -108,7 +109,7 @@ import { defineAsyncComponent } from 'vue';
|
|||||||
const isPreview = defineModel('isPreview', { default: false, type: Boolean });
|
const isPreview = defineModel('isPreview', { default: false, type: Boolean });
|
||||||
const choiceValue = defineModel('answer', { default: '1', type: String });
|
const choiceValue = defineModel('answer', { default: '1', type: String });
|
||||||
const value = defineModel('checkboxAnswer', { default: [1, 2], type: Array });
|
const value = defineModel('checkboxAnswer', { default: [1, 2], type: Array });
|
||||||
|
const errorMessage = defineModel('errorMessage', { default: '', type: String });
|
||||||
const Contenteditable = defineAsyncComponent(() => import('@/components/contenteditable.vue'));
|
const Contenteditable = defineAsyncComponent(() => import('@/components/contenteditable.vue'));
|
||||||
defineProps({
|
defineProps({
|
||||||
active: {
|
active: {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
v-model="element.stem"
|
v-model="element.stem"
|
||||||
:active="active"
|
:active="active"
|
||||||
@blur="emitValue"
|
@blur="emitValue"
|
||||||
|
:errorMessage="errorMessage"
|
||||||
></contenteditable>
|
></contenteditable>
|
||||||
</template>
|
</template>
|
||||||
<template #input>
|
<template #input>
|
||||||
@@ -50,6 +51,10 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
questionType: { type: [String, Number], default: 4 }
|
questionType: { type: [String, Number], default: 4 }
|
||||||
});
|
});
|
||||||
|
const errorMessage = defineModel('errorMessage', {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
});
|
||||||
// 创建一个本地副本以保存更改
|
// 创建一个本地副本以保存更改
|
||||||
const emit = defineEmits(['update:element']);
|
const emit = defineEmits(['update:element']);
|
||||||
const { element } = toRefs(props);
|
const { element } = toRefs(props);
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ const { element } = toRefs(props);
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
const errorMessage = defineModel('errorMessage', {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['update:element']);
|
const emit = defineEmits(['update:element']);
|
||||||
const emitValue = () => {
|
const emitValue = () => {
|
||||||
emit('update:element', element.value);
|
emit('update:element', element.value);
|
||||||
@@ -76,6 +81,7 @@ const emitValue = () => {
|
|||||||
v-model="element.stem"
|
v-model="element.stem"
|
||||||
:active="active"
|
:active="active"
|
||||||
@blur="emitValue"
|
@blur="emitValue"
|
||||||
|
:errorMessage="errorMessage"
|
||||||
></contenteditable>
|
></contenteditable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ const isPreview = defineModel<boolean>('isPreview', { required: false, default:
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const activeComponent = computed<Component>(() => {
|
const activeComponent = computed<Component>(() => {
|
||||||
switch (question.value.question_type) {
|
switch (question.value.question_type) {
|
||||||
case 8:
|
case 8:
|
||||||
return MatrixText;
|
return MatrixText;
|
||||||
case 9:
|
case 9:
|
||||||
return MatrixRadio;
|
return MatrixRadio;
|
||||||
case 10:
|
case 10:
|
||||||
return MatrixCheckbox;
|
return MatrixCheckbox;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -49,6 +49,10 @@ const emitValue = () => {
|
|||||||
// console.log(question.value);
|
// console.log(question.value);
|
||||||
emit('update:element', question.value);
|
emit('update:element', question.value);
|
||||||
};
|
};
|
||||||
|
const errorMessage = defineModel('errorMessage', {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -64,7 +68,12 @@ const emitValue = () => {
|
|||||||
</template>
|
</template>
|
||||||
<!-- 使用 title 插槽来自定义标题 -->
|
<!-- 使用 title 插槽来自定义标题 -->
|
||||||
<template #label>
|
<template #label>
|
||||||
<contenteditable v-model="question.stem" :active="active" @blur="emitValue" />
|
<contenteditable
|
||||||
|
v-model="question.stem"
|
||||||
|
:active="active"
|
||||||
|
@blur="emitValue"
|
||||||
|
:errorMessage="errorMessage"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #input>
|
<template #input>
|
||||||
|
|||||||
@@ -11,7 +11,12 @@
|
|||||||
{{ index + 1 }}
|
{{ index + 1 }}
|
||||||
</template>
|
</template>
|
||||||
<template #label>
|
<template #label>
|
||||||
<contenteditable v-model="element.stem" :active="active" @blur="saveStem"></contenteditable>
|
<contenteditable
|
||||||
|
v-model="element.stem"
|
||||||
|
:active="active"
|
||||||
|
@blur="saveStem"
|
||||||
|
:errorMessage="errorMessage"
|
||||||
|
></contenteditable>
|
||||||
</template>
|
</template>
|
||||||
<template #input>
|
<template #input>
|
||||||
<div
|
<div
|
||||||
@@ -43,7 +48,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { defineModel, ref } from 'vue';
|
||||||
import RateCharacter from './RateCharacter.vue';
|
import RateCharacter from './RateCharacter.vue';
|
||||||
|
|
||||||
const value = defineModel('value', { default: -1, type: Number });
|
const value = defineModel('value', { default: -1, type: Number });
|
||||||
@@ -60,7 +65,10 @@ const isPreview = defineModel('isPreview', { default: false, type: Boolean });
|
|||||||
sn: { type: String, default: '' },
|
sn: { type: String, default: '' },
|
||||||
questionType: { type: [String, Number], default: 4 }
|
questionType: { type: [String, Number], default: 4 }
|
||||||
});
|
});
|
||||||
|
const errorMessage = defineModel('errorMessage', {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
});
|
||||||
/**
|
/**
|
||||||
* element === question
|
* element === question
|
||||||
* @type {ModelRef<Object, string, Object, Object>}
|
* @type {ModelRef<Object, string, Object, Object>}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
v-model="element.stem"
|
v-model="element.stem"
|
||||||
:active="active"
|
:active="active"
|
||||||
@blur="emitValue"
|
@blur="emitValue"
|
||||||
|
:errorMessage="errorMessage"
|
||||||
></contenteditable>
|
></contenteditable>
|
||||||
</template>
|
</template>
|
||||||
<template #input>
|
<template #input>
|
||||||
@@ -49,7 +50,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { defineModel, ref } from 'vue';
|
||||||
import RateCharacter from './RateCharacter.vue';
|
import RateCharacter from './RateCharacter.vue';
|
||||||
|
|
||||||
const isPreview = defineModel('isPreview', { default: false, type: Boolean });
|
const isPreview = defineModel('isPreview', { default: false, type: Boolean });
|
||||||
@@ -65,7 +66,10 @@ defineProps({
|
|||||||
sn: { type: String, default: '' },
|
sn: { type: String, default: '' },
|
||||||
questionType: { type: [String, Number], default: 4 }
|
questionType: { type: [String, Number], default: 4 }
|
||||||
});
|
});
|
||||||
|
const errorMessage = defineModel('errorMessage', {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
});
|
||||||
const emit = defineEmits(['update:element']);
|
const emit = defineEmits(['update:element']);
|
||||||
|
|
||||||
const chooseId = ref('');
|
const chooseId = ref('');
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref, useTemplateRef, toRefs } from 'vue';
|
import { computed, onMounted, ref, useTemplateRef, toRefs } from 'vue';
|
||||||
|
import { defineModel } from 'vue/dist/vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
element: any;
|
element: any;
|
||||||
@@ -55,7 +56,10 @@ const togglePen = () => {
|
|||||||
isEraser.value = !isEraser.value;
|
isEraser.value = !isEraser.value;
|
||||||
setPenStyle();
|
setPenStyle();
|
||||||
};
|
};
|
||||||
|
const errorMessage = defineModel('errorMessage', {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
});
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!signatureCanvas.value) return;
|
if (!signatureCanvas.value) return;
|
||||||
// 将 canvas 宽度和窗口的宽度保持一致
|
// 将 canvas 宽度和窗口的宽度保持一致
|
||||||
@@ -181,7 +185,12 @@ const emitValue = () => {
|
|||||||
{{ index + 1 }}
|
{{ index + 1 }}
|
||||||
</template>
|
</template>
|
||||||
<template #label>
|
<template #label>
|
||||||
<contenteditable v-model="element.stem" :active="active" @blur="emitValue"></contenteditable>
|
<contenteditable
|
||||||
|
v-model="element.stem"
|
||||||
|
:active="active"
|
||||||
|
@blur="emitValue"
|
||||||
|
:errorMessage="errorMessage"
|
||||||
|
></contenteditable>
|
||||||
</template>
|
</template>
|
||||||
<template #input>
|
<template #input>
|
||||||
<div class="sign-question">
|
<div class="sign-question">
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
v-model="element.stem"
|
v-model="element.stem"
|
||||||
:active="active"
|
:active="active"
|
||||||
@blur="emitValue"
|
@blur="emitValue"
|
||||||
|
:errorMessage="errorMessage"
|
||||||
></contenteditable>
|
></contenteditable>
|
||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
@@ -22,7 +23,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import contenteditable from '@/components/contenteditable.vue';
|
import contenteditable from '@/components/contenteditable.vue';
|
||||||
import { toRefs } from 'vue';
|
import { defineModel, toRefs } from 'vue';
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
element: {
|
element: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -41,7 +42,10 @@ const props = defineProps({
|
|||||||
default: 0
|
default: 0
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const errorMessage = defineModel('errorMessage', {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
});
|
||||||
const { element } = toRefs(props);
|
const { element } = toRefs(props);
|
||||||
|
|
||||||
const emit = defineEmits(['update:element']);
|
const emit = defineEmits(['update:element']);
|
||||||
|
|||||||
Reference in New Issue
Block a user