Files
ylst-h5/src/components/contenteditable.vue
Huangzhe 283e07a022 feat: 修复 eslint 报错, 删除多余生成文件
- 删除 vite.config.ts 自动生成的 vite.config.ts.timestamp...
- eslint 报错修复
2025-03-17 10:47:49 +08:00

251 lines
5.3 KiB
Vue

<template>
<div class="flex contenteditable align-center space-between" :class="className">
<p
:id="'editor' + id" ref="editor" :contenteditable="active" class="van-field contenteditable-content"
@focus="onFocus" v-html="modelValue"
></p>
<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>
</template>
<script setup>
import { defineEmits, ref, onMounted, watch } from 'vue';
import { v4 as uuidv4 } from 'uuid';
import CommonApi from '@/api/common.js';
const props = defineProps({
modelValue: {
type: String,
default: ''
},
className: {
type: String,
default: ''
},
active: {
type: Boolean,
default: false
}
});
const id = ref(uuidv4());
const editor = ref(null);
const editorAction = ref(null);
const emit = defineEmits(['update:modelValue', 'blur']);
const save = (e) => {
emit('update:modelValue', e.innerHTML);
};
const savedRange = ref(null);
const functions = {
boldModern: () => {
document.execCommand('bold', true, null);
},
underLine: () => {
document.execCommand('underline', false, null);
},
italic: () => {
document.execCommand('italic', false, null);
},
uploadImage: async() => {
// 保存当前光标位置
savedRange.value = saveSelection();
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.click();
fileInput.onchange = async(e) => {
const [file] = e.target.files;
if (!file) return;
if (file.size > 2 * 1024 * 1024) {
// console.error('文件大小不能超过2M');
return;
}
const data = await CommonApi.cosUpload(file);
const img = document.createElement('img');
img.src = data.url;
img.onload = () => {
const scale = img.naturalHeight / 50;
const width = (img.naturalWidth / scale).toFixed(0);
const height = 50;
const maxWidth = img.naturalWidth;
const maxHeight = img.naturalHeight;
const style = `style="width:${width}px;height:${height}px;max-width:${maxWidth}px;max-height:${maxHeight}px"`;
insertImageAtCaret(`<img src=${data.url} ${style}/>`);
// 恢复光标位置
restoreSelection(savedRange.value);
};
};
}
};
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: '加粗',
fun: 'boldModern',
icon: 'jiacu'
},
{
label: '下划线',
fun: 'underLine',
icon: 'xiahuaxian'
},
{
label: '图片上传',
fun: 'uploadImage',
icon: 'tupian'
},
{
label: '倾斜',
fun: 'italic',
icon: 'qingxie'
}
];
const checkContains = (element, target) => {
try {
return element?.contains(target) ?? false;
} catch (e) {
return false;
}
};
onMounted(() => {
editor.value.addEventListener('focus', () => {
showAction.value = true;
});
document.addEventListener('click', (e) => {
if (!editor.value || !editorAction.value) return;
const target = e.composedPath?.()?.[0] || e.target;
const isEditor = checkContains(editor.value, target);
const isActionBar = checkContains(editorAction.value, target);
if (!isEditor && !isActionBar) {
showAction.value = false;
save(editor.value);
emit('blur', editor.value);
}
});
document.addEventListener('resize', () => {
showAction.value = false;
});
});
const onFocus = (e) => {
// 阻止
e.preventDefault();
e.stopPropagation();
// console.log('Editor focused');
};
// 保存当前光标位置
const saveSelection = () => {
const selection = window.getSelection();
if (selection.rangeCount === 0) return null;
return selection.getRangeAt(0);
};
// 恢复光标位置
const restoreSelection = (range) => {
if (!range) return;
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
};
// 在光标位置插入图片
const insertImageAtCaret = (html) => {
const selection = window.getSelection();
if (selection.rangeCount === 0) return;
const range = selection.getRangeAt(0);
range.deleteContents();
const div = document.createElement('div');
div.innerHTML = html;
const frag = document.createDocumentFragment();
for (let child = div.firstChild; child; child = div.firstChild) {
frag.appendChild(child);
}
range.insertNode(frag);
range.setStartAfter(frag);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
};
</script>
<style scoped lang="scss">
.contenteditable-content {
width: 100%;
}
.right-icon {
color: #c4c9d4;
}
.editor-action {
position: fixed;
bottom: 0;
left: 0;
z-index: 2008;
display: flex;
width: 100%;
height: 40px;
padding: 0 10px;
background: #fff;
line-height: 40px;
& button {
border: none;
background: #fff;
color: #000;
outline: none;
}
button+button {
margin-left: 10px;
}
}
</style>