Files
anmo/business_uniapp/src/components/editor/editor.vue
2025-08-19 14:16:51 +08:00

345 lines
11 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="container">
<view class="page-body">
<view class="wrapper">
<view class="grid grid-cols-8" @tap="format">
<view :class="formats.bold ? 'ql-active' : ''" class="icon" data-name="bold">
B
</view>
<view
:class="formats.italic ? 'ql-active' : ''"
class="iconfont icon-zitixieti"
data-name="italic"
>
</view>
<view
:class="formats.italic ? 'ql-active' : ''"
class="icon"
data-name="italic"
>
I
</view>
<view
:class="formats.underline ? 'ql-active' : ''"
class="icon"
data-name="underline"
>
U
</view>
<view
:class="formats.strike ? 'ql-active' : ''"
class="icon"
data-name="strike"
>
S
</view>
<view
:class="formats.align === 'center' ? 'ql-active' : ''"
class="icon"
data-name="align"
data-value="center"
>
</view>
<view
:class="formats.align === 'right' ? 'ql-active' : ''"
class="icon"
data-name="align"
data-value="right"
>
</view>
<view
:class="formats.align === 'justify' ? 'ql-active' : ''"
class="icon"
data-name="align"
data-value="justify"
>
</view>
<view class="icon" @tap="removeFormat"></view>
<view
:class="formats.color === '#0000ff' ? 'ql-active' : ''"
class="icon"
data-name="color"
data-value="#0000ff"
>
🎨
</view>
<view
:class="formats.backgroundColor === '#00ff00' ? 'ql-active' : ''"
class="icon"
data-name="backgroundColor"
data-value="#00ff00"
>
🖌
</view>
<view class="icon" @tap="insertDate">📅</view>
<view class="icon" data-name="list" data-value="check"> </view>
<view
:class="formats.list === 'ordered' ? 'ql-active' : ''"
class="icon"
data-name="list"
data-value="ordered"
>
1.
</view>
<view
:class="formats.list === 'bullet' ? 'ql-active' : ''"
class="icon"
data-name="list"
data-value="bullet"
>
</view>
<view class="icon" @tap="undo"></view>
<view class="icon" @tap="redo"></view>
<view class="icon" data-name="indent" data-value="-1"></view>
<view class="icon" data-name="indent" data-value="+1"></view>
<view class="icon" @tap="insertDivider"></view>
<view class="icon" @tap="insertImage">🖼</view>
<view
:class="formats.header === 1 ? 'ql-active' : ''"
class="icon"
data-name="header"
:data-value="1"
>
H1
</view>
<view
:class="formats.script === 'sub' ? 'ql-active' : ''"
class="icon"
data-name="script"
data-value="sub"
>
X₂
</view>
<view
:class="formats.script === 'super' ? 'ql-active' : ''"
class="icon"
data-name="script"
data-value="super"
>
Xⁿ
</view>
<view class="icon" @tap="clear">🗑</view>
<view
:class="formats.direction === 'rtl' ? 'ql-active' : ''"
class="iconfont icon-direction-rtl"
data-name="direction"
data-value="rtl"
></view>
</view>
<view class="editor-wrapper">
<editor
id="editor"
class="ql-container"
:placeholder="placeholder"
show-img-size
show-img-toolbar
show-img-resize
@statuschange="onStatusChange"
:read-only="readOnly"
@blur="onEditorInput"
@ready="onEditorReady"
>
</editor>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
modelValue: {
type: String,
default: ''
},
placeholder: {
type: String,
default: '请输入内容...'
},
readOnly: {
type: Boolean,
default: false
}
},
data() {
return {
formats: {},
cursorPosition: { start: 0, end: 0 }, // 保存光标的起始位置
first: true
}
},
watch: {
modelValue() {
if (this.first) {
this.editorCtx.setContents({ html: this.modelValue })
this.first = false
return
}
}
},
methods: {
onEditorReady() {
// #ifdef APP-PLUS || MP-WEIXIN || H5
uni.createSelectorQuery()
.select('#editor')
.context((res) => {
this.editorCtx = res.context
this.editorCtx.setContents({ html: this.modelValue })
console.log(this.modelValue)
})
.exec()
// #endif
},
onEditorInput(e) {
const htmlContent = e.detail.html
// 在更新 modelValue 之前,保存当前光标位置
// 在更新 modelValue 之前,保存当前光标位置
this.$emit('update:modelValue', htmlContent)
// // 获取并存储当前光标位置
// this.editorCtx.getSelectionText({
// success: (res) => {
// this.cursorPosition = res.start
// }
// })
},
undo() {
this.editorCtx.undo()
},
redo() {
this.editorCtx.redo()
},
format(e) {
const { name, value } = e.target.dataset
if (!name) return
// console.log('format', name, value)
this.editorCtx.format(name, value)
},
onStatusChange(e) {
const formats = e.detail
this.formats = formats
},
insertDivider() {
this.editorCtx.insertDivider({
success: function () {
console.log('insert divider success')
}
})
},
clear() {
uni.showModal({
title: '清空编辑器',
content: '确定清空编辑器全部内容?',
success: (res) => {
if (res.confirm) {
this.editorCtx.clear({
success: function (res) {
console.log('clear success')
}
})
}
}
})
},
removeFormat() {
this.editorCtx.removeFormat()
},
insertDate() {
const date = new Date()
const formatDate = `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`
this.editorCtx.insertText({
text: formatDate
})
},
insertImage() {
uni.chooseImage({
count: 1,
success: (res) => {
const imagePath = res.tempFilePaths[0]
// 在插入图片前,保存光标位置
this.editorCtx.getSelectionText({
success: (res) => {
// 保存光标的起始位置
this.cursorPosition = { start: res.start, end: res.start }
// 插入图片
this.editorCtx.insertImage({
src: imagePath,
alt: '图片',
success: () => {
console.log('insert image success')
}
})
// 更新 modelValue 后恢复光标位置
this.$emit('update:modelValue', this.editorCtx.getContents().html)
// 恢复光标位置
if (this.cursorPosition.start !== null) {
this.editorCtx.setSelection({
start: this.cursorPosition.start,
end: this.cursorPosition.start
})
}
}
})
}
})
}
}
}
</script>
<style>
.wrapper {
height: 100%;
}
.editor-wrapper {
background: #fff;
}
.iconfont {
display: inline-block;
padding: 8px 8px;
width: 24px;
height: 24px;
cursor: pointer;
font-size: 20px;
}
.toolbar {
box-sizing: border-box;
border-bottom: 0;
font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
}
.ql-container {
box-sizing: border-box;
padding: 12px 15px;
width: 100%;
min-height: 30vh;
height: 100%;
margin-top: 20px;
font-size: 16px;
line-height: 1.5;
}
.ql-active {
color: #06c;
}
</style>