123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477 |
- <template>
- <div class="myRichText">
- <div class="edit-box div_MAX_Height" ref="myEditBox" data-type="myEditBox" contenteditable="true" :placeholder="placeholder" v-html="content" :key="keys" :id="keys"
- @blur="saveValue($event)" @change="change($event)" @focus="focus($event)" @click="getFocus($event)" v-on:paste="handlePaste($event)">
- </div>
- <div id="customMenu" ref="customMenu" >
- <template v-if="show">
- <div v-for="item in this.menu" :key="item.name" :class="item.chose?'chose':''" >
- <div @click="clickBtn(item.method)">{{ item.name }}</div>
- </div>
- </template>
-
- </div>
- </div>
- </template>
- <script>
- import mixins from './mixins.js';
- export default {
- mixins:[mixins],
- components: {},
- props:{
- value:{
- type:[String,Function],
- default:(value)=>{
- return ""
- }
- },
- placeholder:{
- type:String,
- default:()=>{
- return "请输入"
- }
- },
- autoFocus:{
- type:Boolean,
- default:false
- },
- keys:{
- type:String,
- default:()=>{
- const len = 6
- const codeList = []
- const chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz0123456789'
- const charsLen = chars.length
- for (let i = 0; i < len; i++) {
- codeList.push(chars.charAt(Math.floor(Math.random() * charsLen)))
- }
- return codeList.join('')
- }
- }
- },
- data() {
- return {
- content: this.value||"", // 内容,
- beforeChange:'',
- menu :[
- {
- name: "高亮",
- method: "highlight",
- },
- // {
- // name: "取消全部高亮",
- // method: "cancel",
- // }
- ],
- selectObj:null,
- show:false,
- };
- },
- watch: {
- value(val){
- this.content = val
- },
- },
- computed: {},
- created() {},
- mounted() {
- var that = this
- this.$refs.myEditBox.addEventListener('contextmenu', function(event) {
- event.preventDefault(); // 取消默认的右击菜单显示行为
- that.showCustomMenu(event)
- });
- // 当点击任意地方或者按下ESC键时,隐藏菜单
- // window.onclick = this.hideCustomMenu
- window.addEventListener('click',function(event){
- var customMenu = that.$refs.customMenu
- if (customMenu && event.target != customMenu && !customMenu.contains(event.target)) {
- customMenu.style.display = "none";
- }
-
- })
- window.onkeydown = this.checkKeyPress
- if(this.autoFocus){
- this.$refs.myEditBox.focus()
- }
- },
- methods: {
- clickBtn(method){
-
- var SplitHtmls = this.SplitHtmlTag1(this.$refs.myEditBox.innerHTML)
- var html = this.$refs.myEditBox.innerHTML
- if(method == 'cancel'){
- html = html.replace(/<highlight>/g,'').replace(/<\/highlight>/g,'')
- }else{
- html = this.changeHtml(SplitHtmls,method)
- }
- // var html = this.addTag(SplitHtmls,method)
- this.content = html
- this.show = false
- this.$emit('input',html)
- // this.$emit('blur',html)
- this.$emit('change',html)
- this.close()
- },
-
- showCustomMenu(event) {
- var obj = document.getSelection()
- if(obj.anchorOffset == 0 && obj.extentOffset == 0){
- return false
- }
- this.selectObj = {
- anchorNode: obj.anchorNode,
- anchorOffset: obj.anchorOffset,
- extentNode: obj.extentNode,
- extentOffset: obj.extentOffset,
- focusNode:obj.focusNode,
- focusOffset:obj.focusOffset,
- isCollapsed: obj.isCollapsed,
- rangeCount: obj.rangeCount,
- type: obj.type,
- text:obj.toString()
- }
- //判断是否已经突出显示
- if (this.selectObj.anchorNode.isEqualNode(this.selectObj.extentNode)) {
- var tags = this.getParentNode(this.selectObj)
- if(tags.length != 0){
- for(var i = 0;i<tags.length;i++){
- var index = this.menu.findIndex(item=>{
- item.chose = false
- return item.method == tags[i].tagName.toLowerCase()
- })
- if(index!=-1){
- this.menu[index].chose = true
- break
- }else{
-
- }
- }
-
- }
- } else {
- // console.log("两个DOM元素不相同");
- }
- this.show = false
- var { startIndex,endIndex } = this.getLocation(this.$refs.myEditBox,this.selectObj)
- this.selectObj.startIndex = startIndex
- this.selectObj.endIndex = endIndex
- this.$nextTick(()=>{
- this.show = true
- var customMenu = this.$refs.customMenu
- var xPos = event.layerX + "px";
- var yPos = event.layerY + "px";
- customMenu.style.display = "block";
- customMenu.style.left = xPos;
- customMenu.style.top = yPos;
- })
-
- },
- hideCustomMenu(event) {
- var customMenu = this.$refs.customMenu
- if (event.target != customMenu && !customMenu.contains(event.target)) {
- customMenu.style.display = "none";
- }
- },
- close(){
- var customMenu = this.$refs.customMenu
- customMenu.style.display = 'none';
- },
- checkKeyPress(e) {
- var customMenu = this.$refs.customMenu
- e = e || window.event;
- if (e.keyCode == 27) { // ESC键的keycode值为27
- customMenu.style.display = "none";
- }
- if (e.keyCode === 13) {
- e.preventDefault(); // 阻止默认操作
- document.execCommand("insertHTML", false, "<br><br>"); // 插入换行
- }
- },
- //粘贴
- async handlePaste(e){
- var event = e || window.event
- event.preventDefault();
- const items = (event.clipboardData || window.clipboardData).items;
- let file = null;
- if (!items || items.length === 0) {
- this.$message.error("当前浏览器不支持本地");
- return;
- }
- for (let i = 0; i < items.length; i++) {
- if (items[i].type.indexOf("text") !== -1) {
- var text = (event.originalEvent || event).clipboardData.getData('text/plain') ||'';
- // text = text.replace(/\n{1,}/g,'<br>').replace(/</g,'<').replace(/>/g,'>')
- text = text.replace(/\n{1,}/g,'<br>')
- text = this.parsedText(text)
- document.execCommand('insertHTML', true, text)
- // var text2 = `<span>${text}</span>`
- // this.setCursor(event,text2,1)
- break;
- }
- // if(items[i].type.indexOf("text/html") !== -1){
- // var string = (event.originalEvent || event).clipboardData.getData('text/html')
- // const srcRegex = /<img\s+(?:[^>]*?\s+)?src\s*=\s*(["'])((?:[^\1"]|\\\1|.)*?)\1/g
- // const result = [...string.matchAll(srcRegex)]
- // var src = result.map(v => v[2])[0]
- // console.log(src)
- // var reader = new FileReader()
- // var srcBase64 = reader.readAsDataURL(src)
- // // var srcBase64 =await this.urlToBase64(src)
- // console.log(srcBase64)
- // continue;
- // }
- if (items[i].type.indexOf("image") !== -1) {
- var e = event || window.event
- e.preventDefault();
- file = items[i].getAsFile();
- break;
- }
- }
- if (!file) {
- return;
- }
- var str = await this.fileToBase64(file)
- var randNum=Math.floor(Math.random()*(9999-1000))+1000;;
- var new_img = '<img key="'+ randNum +'" src="' + str + '" onClick="vm.checkImg(event)" style="width:80px;height: 80px;border: 1px solid #f9f6f675;vertical-align:middle">';
- document.execCommand('insertText', false, new_img)
- var alltext = this.$refs.myEditBox.innerHTML
- // var alltext = this.parsedText(this.$refs.myEditBox.innerHTML)
- this.$refs.myEditBox.innerHTML = alltext
- // 创建新的光标对象
- // var range = document.createRange();
- // // 将光标对象的范围界定为新建的表情节点
- // range.selectNodeContents(new_img);
- // // 定位光标位置在表情节点的最大长度位置
- // range.setStart(new_img, new_img.length);
- // // 将选区折叠为一个光标
- // range.collapse(true);
- // // 清除所有光标对象
- // selection.removeAllRanges();
- // // 添加新的光标对象
- // selection.addRange(range);
- // console.log(event.target.innerHTML)
- // var index = window.getSelection().getRangeAt(0)
- // this.setCursor(event,new_img,2)
- // var indexText = this.getColumn(event.target,window.getSelection())
- // var index2 = 0
- // var str2 = content.slice(0, indexText) + new_img + content.slice(indexText);
- // event.target.innerHTML = str2
- // this.$nextTick(()=>{
- // var selectedText = window.getSelection();
- // var selectedRange = document.createRange();
- // try{
- // for(var i=0;i<event.target.childNodes.length;i++){
- // if(event.target.childNodes[i].outerHTML == new_img){
- // index2 = i
- // throw new Error()
- // }
- // }
- // }catch{
- // }
- // selectedRange.setStart(event.target,index2+1);
- // selectedRange.collapse(true);
- // selectedText.removeAllRanges();
- // selectedText.addRange(selectedRange);
- // event.target.parentNode.focus();
- // })
-
- // this.saveValue(event)
- // var selection = window.getSelection()
- // window.getSelection().addRange(this.index)
- // console.log(window.getSelection().getRangeAt(0))
-
- },
- /**
- * 根据图片的url转换对应的base64值
- * @param { String } imageUrl 如:http://xxxx/xxx.png
- * @returns base64取值
- */
- async urlToBase64(imageUrl) {
- return new Promise((resolve, reject) => {
- let canvas = document.createElement('canvas')
- const ctx = canvas.getContext('2d')
- let img = new Image()
- img.crossOrigin = 'Anonymous' // 解决Canvas.toDataURL 图片跨域问题
- img.src = imageUrl
- img.onload = function() {
- canvas.height = img.height
- canvas.width = img.width
- ctx.fillStyle = '#fff' // canvas背景填充颜色默认为黑色
- // ctx.fillRect(0,0,img.width,img.height)
- ctx.drawImage(img, 0, 0,img.width,img.height) // 参数可自定义
- const dataURL = canvas.toDataURL('image/jpeg', 1) // 获取Base64编码
- resolve(dataURL)
- canvas = null // 清除canvas元素
- img = null // 清除img元素
- }
- img.onerror = function() {
- reject(new Error('Could not load image at ' + imageUrl))
- }
- })
- },
- //设置光标位置
- setCursor(event,str,type){
- // console.log(event)
- var content = event.target.innerHTML
- // previousSibling
- var indexText = this.getColumn(event.target,window.getSelection())
- var index2 = 0
- var childNodes = event.target.childNodes
- if(!content){
- if(event.target.nodeName == 'BR'){
- const span = document.createElement('span');
- span.innerHTML = str;
- event.target.parentNode.insertBefore(span,event.target)
- var selectedRange = document.createRange();
- selectedRange.setStart(event.target,index2);
- return false
- }else{
- event.target.innerHTML = str
- }
- }else{
- content = content.replace(/ /g,'\r')
- var str2 = content.slice(0, indexText) + str + content.slice(indexText);
- event.target.innerHTML = str2.replace(/\r/g,' ')
- }
-
- // console.log(event.currentTarget.childNodes)
- // if(event.target.nodeName == 'BR'){
- // event.target.nodeName = 'SPAN'
- // event.target.innerHTML = '</br>' + str2
- // }
- this.$nextTick(()=>{
- var selectedText = window.getSelection();
- var selectedRange = document.createRange();
- try{
- for(var i=0;i<childNodes.length;i++){
- var html = childNodes[i].outerHTML
- if(type == 1){
- if(html){
- html = html.replace(/\r{1,}|\n{1,}|\r\n{1,}/g,'')
- str = html.replace(/\r{1,}|\n{1,}|\r\n{1,}/g,'')
- }
- }
- if(html == str){
- index2 = i
- throw new Error()
- }
-
- }
- }catch{
- }
- selectedRange.setStart(event.target,index2+1);
- selectedRange.collapse(true);
- selectedText.removeAllRanges();
- selectedText.addRange(selectedRange);
- event.target.parentNode.focus();
- })
- },
-
- // 将file文件上传转化为base64进行显示
- fileToBase64(file) {
- return new Promise((resolve, reject) => {
- const reader = new FileReader()
- //开始读文件
- reader.readAsDataURL(file)
- reader.onload = () => resolve(reader.result)
- // 失败返回失败的信息
- reader.onerror = error => reject(error)
- })
- },
- getFocus(event){
- event.target.parentNode.focus();
- },
- saveValue(event){
- if(this.show){
- return
- }
- var a = event.target.innerHTML
- this.$emit('input',a)
- this.$emit('blur',a)
- if(a != this.beforeChange){
- this.$emit('change',a)
- }
- },
- // input(event){
- // var a = event.target.innerHTML
- // this.$emit('input',a)
- // },
- focus(event){
- this.beforeChange = event.target.innerHTML
- },
- },
- };
- </script>
- <style lang="scss" scoped>
- .myRichText{
- position: relative;
- }
- #customMenu {
- user-select:none;
- background: #fff;
- color: #34495e;
- box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.2);
- min-width: 100px;
- text-align: center;
- z-index: 999;
- display: none; /* 初始状态下不显示 */
- position: absolute;
- &>div{
- padding:10px;
- line-height: 25px;
- border-bottom: 1px solid #E4E7ED;
- cursor: pointer;
- }
- &>div:last-child{
- border-bottom: none;
- }
- &>div:hover{
- background: #edf6ff;
- }
- .chose{
- background: #edf6ff;
- }
- }
- .edit-box{
- overflow-x: hidden;
- text-overflow: ellipsis;
- text-wrap: wrap;
- text-align: left;
- padding: 10px 15px;
- // min-height:27px;
- line-height: 1.5;
- outline: #dcdfe6;
- border:1px solid var(--bg);
- border-radius:5px;
- div{
- height:30px !important;
- line-height: 30px !important;
- }
- }
- .edit-box:empty::before {
- content: attr(placeholder);
- // margin-left:15px;
- font-style: normal;
- white-space: nowrap;
- color: #cacdd4;
- }
- // .edit-box:focus::before {
- // content:none;
- // }
- </style>
|