index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. <template>
  2. <div>
  3. <div class="edit-box div_MAX_Height" ref="myEditBox" contenteditable="true" :placeholder="placeholder" v-html="content" :key="keys" :id="keys"
  4. @blur="saveValue($event)" @click="getFocus($event)" v-on:paste="handlePaste($event)">
  5. </div>
  6. </div>
  7. </template>
  8. <script>
  9. export default {
  10. components: {},
  11. props:{
  12. value:{
  13. type:String,
  14. default:(value)=>{
  15. return ""
  16. }
  17. },
  18. placeholder:{
  19. type:String,
  20. default:()=>{
  21. return "请输入"
  22. }
  23. },
  24. keys:{
  25. type:String,
  26. default:()=>{
  27. const len = 6
  28. const codeList = []
  29. const chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz0123456789'
  30. const charsLen = chars.length
  31. for (let i = 0; i < len; i++) {
  32. codeList.push(chars.charAt(Math.floor(Math.random() * charsLen)))
  33. }
  34. return codeList.join('')
  35. }
  36. }
  37. },
  38. data() {
  39. return {
  40. content: this.value||"", // 内容
  41. };
  42. },
  43. watch: {
  44. value(val){
  45. this.content = val
  46. },
  47. },
  48. computed: {},
  49. created() {},
  50. mounted() {},
  51. methods: {
  52. //粘贴
  53. async handlePaste(e){
  54. var event = e || window.event
  55. event.preventDefault();
  56. const items = (event.clipboardData || window.clipboardData).items;
  57. let file = null;
  58. if (!items || items.length === 0) {
  59. this.$message.error("当前浏览器不支持本地");
  60. return;
  61. }
  62. for (let i = 0; i < items.length; i++) {
  63. if (items[i].type.indexOf("text") !== -1) {
  64. var text = (event.originalEvent || event).clipboardData.getData('text/plain') ||'';
  65. text = text.replace(/\n{1,}/g,'</br>')
  66. var text2 = `<span>${text}</span>`
  67. this.setCursor(event,text2,1)
  68. break;
  69. }
  70. // if(items[i].type.indexOf("text/html") !== -1){
  71. // var string = (event.originalEvent || event).clipboardData.getData('text/html')
  72. // const srcRegex = /<img\s+(?:[^>]*?\s+)?src\s*=\s*(["'])((?:[^\1"]|\\\1|.)*?)\1/g
  73. // const result = [...string.matchAll(srcRegex)]
  74. // var src = result.map(v => v[2])[0]
  75. // console.log(src)
  76. // var reader = new FileReader()
  77. // var srcBase64 = reader.readAsDataURL(src)
  78. // // var srcBase64 =await this.urlToBase64(src)
  79. // console.log(srcBase64)
  80. // continue;
  81. // }
  82. if (items[i].type.indexOf("image") !== -1) {
  83. var e = event || window.event
  84. e.preventDefault();
  85. file = items[i].getAsFile();
  86. break;
  87. }
  88. }
  89. if (!file) {
  90. return;
  91. }
  92. var str = await this.fileToBase64(file)
  93. var randNum=Math.floor(Math.random()*(9999-1000))+1000;;
  94. var new_img = '<img key="'+ randNum +'" src="' + str + '" style="width:80px;height: 80px;border: 1px solid #f9f6f675;vertical-align:middle">';
  95. // 创建新的光标对象
  96. // var range = document.createRange();
  97. // // 将光标对象的范围界定为新建的表情节点
  98. // range.selectNodeContents(new_img);
  99. // // 定位光标位置在表情节点的最大长度位置
  100. // range.setStart(new_img, new_img.length);
  101. // // 将选区折叠为一个光标
  102. // range.collapse(true);
  103. // // 清除所有光标对象
  104. // selection.removeAllRanges();
  105. // // 添加新的光标对象
  106. // selection.addRange(range);
  107. // console.log(event.target.innerHTML)
  108. var index = window.getSelection().getRangeAt(0)
  109. this.setCursor(event,new_img,2)
  110. // var indexText = this.getColumn(event.target,window.getSelection())
  111. // var index2 = 0
  112. // var str2 = content.slice(0, indexText) + new_img + content.slice(indexText);
  113. // event.target.innerHTML = str2
  114. // this.$nextTick(()=>{
  115. // var selectedText = window.getSelection();
  116. // var selectedRange = document.createRange();
  117. // try{
  118. // for(var i=0;i<event.target.childNodes.length;i++){
  119. // if(event.target.childNodes[i].outerHTML == new_img){
  120. // index2 = i
  121. // throw new Error()
  122. // }
  123. // }
  124. // }catch{
  125. // }
  126. // selectedRange.setStart(event.target,index2+1);
  127. // selectedRange.collapse(true);
  128. // selectedText.removeAllRanges();
  129. // selectedText.addRange(selectedRange);
  130. // event.target.parentNode.focus();
  131. // })
  132. // this.saveValue(event)
  133. // var selection = window.getSelection()
  134. // window.getSelection().addRange(this.index)
  135. // console.log(window.getSelection().getRangeAt(0))
  136. },
  137. /**
  138. * 根据图片的url转换对应的base64值
  139. * @param { String } imageUrl 如:http://xxxx/xxx.png
  140. * @returns base64取值
  141. */
  142. async urlToBase64(imageUrl) {
  143. return new Promise((resolve, reject) => {
  144. let canvas = document.createElement('canvas')
  145. const ctx = canvas.getContext('2d')
  146. let img = new Image()
  147. img.crossOrigin = 'Anonymous' // 解决Canvas.toDataURL 图片跨域问题
  148. img.src = imageUrl
  149. img.onload = function() {
  150. canvas.height = img.height
  151. canvas.width = img.width
  152. ctx.fillStyle = '#fff' // canvas背景填充颜色默认为黑色
  153. // ctx.fillRect(0,0,img.width,img.height)
  154. ctx.drawImage(img, 0, 0,img.width,img.height) // 参数可自定义
  155. const dataURL = canvas.toDataURL('image/jpeg', 1) // 获取Base64编码
  156. resolve(dataURL)
  157. canvas = null // 清除canvas元素
  158. img = null // 清除img元素
  159. }
  160. img.onerror = function() {
  161. reject(new Error('Could not load image at ' + imageUrl))
  162. }
  163. })
  164. },
  165. //设置光标位置
  166. setCursor(event,str,type){
  167. console.log(event)
  168. var content = event.target.innerHTML
  169. // previousSibling
  170. var indexText = this.getColumn(event.target,window.getSelection())
  171. var index2 = 0
  172. var childNodes = event.target.childNodes
  173. if(!content){
  174. if(event.target.nodeName == 'BR'){
  175. const span = document.createElement('span');
  176. span.innerHTML = str;
  177. event.target.parentNode.insertBefore(span,event.target)
  178. var selectedRange = document.createRange();
  179. selectedRange.setStart(event.target,index2);
  180. return false
  181. }else{
  182. event.target.innerHTML = str
  183. }
  184. }else{
  185. content = content.replace(/&nbsp;/g,'\r')
  186. var str2 = content.slice(0, indexText) + str + content.slice(indexText);
  187. event.target.innerHTML = str2.replace(/\r/g,'&nbsp;')
  188. }
  189. // console.log(event.currentTarget.childNodes)
  190. // if(event.target.nodeName == 'BR'){
  191. // event.target.nodeName = 'SPAN'
  192. // event.target.innerHTML = '</br>' + str2
  193. // }
  194. this.$nextTick(()=>{
  195. var selectedText = window.getSelection();
  196. var selectedRange = document.createRange();
  197. try{
  198. for(var i=0;i<childNodes.length;i++){
  199. var html = childNodes[i].outerHTML
  200. if(type == 1){
  201. if(html){
  202. html = html.replace(/\r{1,}|\n{1,}|\r\n{1,}/g,'')
  203. str = html.replace(/\r{1,}|\n{1,}|\r\n{1,}/g,'')
  204. }
  205. }
  206. if(html == str){
  207. index2 = i
  208. throw new Error()
  209. }
  210. }
  211. }catch{
  212. }
  213. selectedRange.setStart(event.target,index2+1);
  214. selectedRange.collapse(true);
  215. selectedText.removeAllRanges();
  216. selectedText.addRange(selectedRange);
  217. event.target.parentNode.focus();
  218. })
  219. },
  220. getColumn(node, selectObj) {
  221. var baseNode = node;
  222. var anchorNodePosition = this.getPosition(baseNode, selectObj.anchorNode, selectObj.anchorOffset);
  223. var focusNodePosition = this.getPosition(baseNode, selectObj.focusNode, selectObj.focusOffset);
  224. var num = Math.min(anchorNodePosition, focusNodePosition)
  225. return num;
  226. },
  227. getNodes(baseNode, path) {
  228. // 拿到所有类型的节点
  229. var temPath = path;
  230. if (baseNode != null) {
  231. temPath.push(baseNode);
  232. if (baseNode.childNodes.length > 0) {
  233. for (let i = 0; i < baseNode.childNodes.length; i++) {
  234. this.getNodes(baseNode.childNodes[i], temPath);
  235. }
  236. }
  237. }
  238. },
  239. getPosition(baseNode, node, offset) {
  240. //根据节点获取给定node中offset位置在栏位中的实际位置
  241. let path = [];
  242. this.getNodes(baseNode, path);
  243. var retIdx = 0;
  244. for (let i = 0; i < path.length; i++) {
  245. if (path[i] == node) {
  246. retIdx += offset;
  247. return retIdx;
  248. } else {
  249. if (path[i].nodeType == 3) {
  250. retIdx += path[i].nodeValue.length;
  251. }else if(i!=0 && path[i].nodeType == 1){
  252. retIdx += path[i].outerHTML.length;
  253. }
  254. }
  255. }
  256. return retIdx
  257. },
  258. // 将file文件上传转化为base64进行显示
  259. fileToBase64(file) {
  260. return new Promise((resolve, reject) => {
  261. const reader = new FileReader()
  262. //开始读文件
  263. reader.readAsDataURL(file)
  264. reader.onload = () => resolve(reader.result)
  265. // 失败返回失败的信息
  266. reader.onerror = error => reject(error)
  267. })
  268. },
  269. getFocus(event){
  270. event.target.parentNode.focus();
  271. },
  272. saveValue(event){
  273. var a = event.target.innerHTML
  274. this.$emit('input',a)
  275. }
  276. },
  277. };
  278. </script>
  279. <style lang="scss" scoped>
  280. .edit-box{
  281. overflow-x: hidden;
  282. text-overflow: ellipsis;
  283. padding: 10px 15px;
  284. // min-height:27px;
  285. line-height: 1.5;
  286. outline: #dcdfe6;
  287. border:1px solid #DCDFE6;
  288. border-radius:5px;
  289. div{
  290. height:30px !important;
  291. line-height: 30px !important;
  292. }
  293. }
  294. .edit-box:empty::before {
  295. content: attr(placeholder);
  296. // margin-left:15px;
  297. font-style: normal;
  298. white-space: nowrap;
  299. color: #cacdd4;
  300. }
  301. // .edit-box:focus::before {
  302. // content:none;
  303. // }
  304. </style>