Explorar o código

接入wangEditor

vothin hai 1 ano
pai
achega
203a5ee76d

+ 1 - 0
languages/zh-CN.js

@@ -466,6 +466,7 @@ module.exports = {
     titleIcon: '标题图标',
     summary: '简介',
     inputSummary: '请输入简介',
+    detail: '内容'
   },
   clerkManage: {
     clerkEdit: '编辑成员信息',

+ 3 - 1
package.json

@@ -17,6 +17,8 @@
   "dependencies": {
     "@moefe/vue-aplayer": "^2.0.0-beta.5",
     "@toast-ui/editor": "^3.1.3",
+    "@wangeditor/editor": "^5.1.23",
+    "@wangeditor/editor-for-vue": "^1.0.2",
     "ag-grid-community": "^25.0.0",
     "ag-grid-vue": "^25.0.0",
     "axios": "0.18.1",
@@ -58,6 +60,7 @@
     "vue-count-to": "^1.0.13",
     "vue-draggable-resizable": "^2.3.0",
     "vue-i18n": "^8.26.1",
+    "vue-json-viewer": "^2.2.22",
     "vue-lazyload": "^1.2.6",
     "vue-property-decorator": "^9.1.2",
     "vue-qr": "^3.2.4",
@@ -65,7 +68,6 @@
     "vue-seamless-scroll": "^1.1.23",
     "vue-splitpane": "1.0.4",
     "vuedraggable": "^2.20.0",
-    "vue-json-viewer": "^2.2.22",
     "vuex": "3.1.0",
     "xlsx": "0.14.1"
   },

+ 3 - 3
public/domain.js

@@ -1,7 +1,7 @@
 const domain = {
-  serverUrl: 'http://8.129.220.143:8005',
-  DeviceUrl: 'http://8.129.220.143:8006',
-  mediaUrl: 'http://8.129.220.143:8004',
+  serverUrl: 'http://192.168.1.57:8005',
+  DeviceUrl: 'http://192.168.1.57:8006',
+  mediaUrl: 'http://192.168.1.57:8004',
   OnlineSystemUrl: 'http://api.base.wdklian.com',
   apiMode: 'dev',
   uiVersion: 1, // 1 医院版,2 月子中心版,3养老院版

+ 14 - 0
src/api/upload.js

@@ -0,0 +1,14 @@
+import request from '@/utils/request'
+export function upload(file) {
+  return request({
+    url: 'ncs/upload/uploadFile',
+    method: 'POST',
+    data: file,
+    headers: { 'Content-Type': 'multipart/form-data;' },
+    transformRequest: [
+      function() {
+        return file
+      }
+    ]
+  })
+}

+ 290 - 0
src/components/WangEdiitor/wangEditor.vue

@@ -0,0 +1,290 @@
+<template>
+  <div style="border: 1px solid #ccc;">
+    <Toolbar
+        style="border-bottom: 1px solid #ccc"
+        :editor="editor"
+        :mode="mode"
+        :default-config="toolbarConfig"
+    />
+    <Editor
+        style="height: 500px; overflow-y: hidden;"
+        v-model="content"
+        :mode="mode"
+        :default-config="editorConfig"
+        @onCreated="onCreated"
+        @onChange="onChange"
+        @onDestroyed="onDestroyed"
+        @onMaxLength="onMaxLength"
+        @onFocus="onFocus"
+        @onBlur="onBlur"
+        @customAlert="customAlert"
+        @customPaste="customPaste"
+    />
+  </div>
+</template>
+
+<script>
+import Vue from 'vue'
+import '@wangeditor/editor/dist/css/style.css'
+import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
+import * as API_Upload from "@/api/upload";
+
+const serverUrl = domain.serverUrl
+export default Vue.extend({
+  name: "wangEditor",
+  components: { Editor, Toolbar },
+  model: {
+    prop: 'value',
+    event: 'input'
+  },
+  props: {
+    value: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    return {
+      editor: null,
+      mode: 'simple', // or 'default'
+      content: '请输入内容...',
+      toolbarConfig: { },
+      editorConfig: {
+        placeholder: '请输入内容...',
+        withCredentials: true,
+        MENU_CONF: {
+          // 配置上传图片
+          uploadImage: {
+            // // 自定义上传图片的方法
+            customUpload: this.uploadImg,
+            // // 自定义插入图片的方法
+            customInsert: this.insertImg,
+            //server必须要配置正确
+            // server: serverUrl + '/ncs/upload/uploadFile',
+            // 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
+            fieldName: "file",
+            headers: {
+              // 'Content-Type': 'multipart/form-data;'
+            },
+
+            maxFileSize: 4 * 1024 * 1024, // 1M
+            // 最多可上传几个文件,默认为 100
+            maxNumberOfFiles: 100,
+            // 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
+            allowedFileTypes: [],
+            meta: {
+              //官网中把token放到了这里,但是请求的时候会看不到token
+            },
+            // 将 meta 拼接到 url 参数中,默认 false
+            metaWithUrl: false,
+            // 跨域是否传递 cookie ,默认为 false
+            withCredentials: false,
+            // 超时时间,默认为 10 秒
+            timeout: 5 * 1000, // 5 秒
+          }
+        },
+      },
+    }
+  },
+  watch: {
+    value(val) {
+      this.html = val
+    }
+  },
+  beforeDestroy() {
+    const editor = this.editor
+    if (editor == null) return
+    editor.destroy() // 组件销毁时,及时销毁编辑器
+  },
+  methods: {
+    onCreated(editor) {
+      this.editor = Object.seal(editor) // 一定要用 Object.seal() ,否则会报错
+    },
+    onChange(editor) {
+      this.$emit('input', editor.getHtml())
+    },
+    onDestroyed(editor) {
+      console.log("onDestroyed", editor);
+    },
+    onMaxLength(editor) {
+      console.log("onMaxLength", editor);
+    },
+    onFocus(editor) {
+      console.log("onFocus", editor);
+    },
+    onBlur(editor) {
+      console.log("onBlur", editor);
+    },
+    customAlert(info, type) {
+      window.alert(`customAlert in Vue demo\n${type}:\n${info}`);
+    },
+
+    //重点来了: 自定义粘贴。可阻止编辑器的默认粘贴,实现自己的粘贴逻辑。(可以实现复制粘贴 word ,有图片)
+    customPaste(editor, event, callback) {
+      console.log("ClipboardEvent 粘贴事件对象", event);
+      let html = event.clipboardData.getData("text/html"); // 获取粘贴的 html
+      // let text = event.clipboardData.getData('text/plain') // 获取粘贴的纯文本
+      let rtf = event.clipboardData.getData("text/rtf"); // 获取 rtf 数据(如从 word wsp 复制粘贴)
+      var that = this;
+      if (html && rtf) {
+
+        // 列表缩进会超出边框,直接过滤掉
+        html = html.replace(/text\-indent:\-(.*?)pt/gi, "");
+
+        // 从html内容中查找粘贴内容中是否有图片元素,并返回img标签的属性src值的集合
+        const imgSrcs = that.findAllImgSrcsFromHtml(html);
+
+        // 如果有
+        if (imgSrcs && Array.isArray(imgSrcs) && imgSrcs.length) {
+          // 从rtf内容中查找图片数据
+          const rtfImageData = that.extractImageDataFromRtf(rtf);
+
+          // 如果找到
+          if (rtfImageData.length) {
+            // TODO:此处可以将图片上传到自己的服务器上
+            this.uploadImg(rtfImageData, this.editor)
+
+            // 执行替换:将html内容中的img标签的src替换成ref中的图片数据,如果上面上传了则为图片路径
+            html = that.replaceImagesFileSourceWithInlineRepresentation(
+                html,
+                imgSrcs,
+                rtfImageData
+            );
+            editor.dangerouslyInsertHtml(html);
+          }
+        }
+
+        // 阻止默认的粘贴行为
+        event.preventDefault();
+        return false;
+      } else {
+        return true;
+      }
+    },
+
+    //自定义上传图片
+    uploadImg(file, insertFn) {
+      let imgData = new FormData();
+      imgData.append("file", file);
+      //调用上传图片接口,上传图片
+      API_Upload.upload(imgData).then((res) => {
+        // 插入后端返回的url
+        insertFn(serverUrl + '/' + res);
+        this.$message({
+          type: "success",
+          message: "上传成功",
+        });
+      }).catch((error) => {
+        this.$message("上传失败,请重新上传");
+      });
+    },
+
+    // 自定义插入图片
+    insertImg(file) {
+      console.log(file);
+    },
+    /**
+     * 从html代码中匹配返回图片标签img的属性src的值的集合
+     * @param htmlData
+     * @return Array
+     */
+    findAllImgSrcsFromHtml(htmlData) {
+      let imgReg = /<img.*?(?:>|\/>)/gi; //匹配图片中的img标签
+      let srcReg = /src=[\'\"]?([^\'\"]*)[\'\"]?/i; // 匹配图片中的src
+
+      let arr = htmlData.match(imgReg); //筛选出所有的img
+      if (!arr || (Array.isArray(arr) && !arr.length)) {
+        return false;
+      }
+
+      let srcArr = [];
+      for (let i = 0; i < arr.length; i++) {
+        let src = arr[i].match(srcReg);
+        // 获取图片地址
+        srcArr.push(src[1]);
+      }
+
+      return srcArr;
+    },
+    /**
+     * 从rtf内容中匹配返回图片数据的集合
+     * @param rtfData
+     * @return Array
+     */
+    extractImageDataFromRtf(rtfData) {
+      if (!rtfData) {
+        return [];
+      }
+
+      const regexPictureHeader =
+          /{\\pict[\s\S]+?({\\\*\\blipuid\s?[\da-fA-F]+)[\s}]*/;
+      const regexPicture = new RegExp(
+          "(?:(" + regexPictureHeader.source + "))([\\da-fA-F\\s]+)\\}",
+          "g"
+      );
+      const images = rtfData.match(regexPicture);
+      const result = [];
+
+      if (images) {
+        for (const image of images) {
+          let imageType = false;
+
+          if (image.includes("\\pngblip")) {
+            imageType = "image/png";
+          } else if (image.includes("\\jpegblip")) {
+            imageType = "image/jpeg";
+          }
+
+          if (imageType) {
+            result.push({
+              hex: image
+                  .replace(regexPictureHeader, "")
+                  .replace(/[^\da-fA-F]/g, ""),
+              type: imageType,
+            });
+          }
+        }
+      }
+
+      return result;
+    },
+    /**
+     * 将html内容中img标签的属性值替换
+     * @param htmlData html内容
+     * @param imageSrcs html中img的属性src的值的集合
+     * @param imagesHexSources rtf中图片数据的集合,与html内容中的img标签对应
+     * @param isBase64Data 是否是Base64的图片数据
+     * @return String
+     */
+    replaceImagesFileSourceWithInlineRepresentation(
+        htmlData,
+        imageSrcs,
+        imagesHexSources,
+        isBase64Data = true
+    ) {
+      if (imageSrcs.length === imagesHexSources.length) {
+        for (let i = 0; i < imageSrcs.length; i++) {
+          const newSrc = isBase64Data
+              ? `data:${
+                  imagesHexSources[i].type
+              };base64,${this._convertHexToBase64(imagesHexSources[i].hex)}`
+              : imagesHexSources[i];
+
+          htmlData = htmlData.replace(imageSrcs[i], newSrc);
+        }
+      }
+
+      return htmlData;
+    },
+    // 销毁富文本
+    beforeDestroy() {
+      const editor = this.editor;
+      if (editor == null) return;
+      editor.destroy(); // 组件销毁时,及时销毁编辑器
+    },
+  }
+})
+</script>
+
+<style>
+</style>

+ 15 - 4
src/views/ncs-device-menu-detail/index.vue

@@ -7,7 +7,7 @@
             <el-select v-model="formmodel.type"
                        :placeholder="this.$t('deviceMenuDetailManage.chooseType')"
                        filterable clearable>
-              <el-option v-for="(item,index) in deviceMenuDetialTypeTransfer" :key="index" :label="item.key" :value="item.value" />
+              <el-option v-for="(item,index) in deviceMenuDetailTypeTransfer" :key="index" :label="item.key" :value="item.value" />
             </el-select>
           </el-form-item>
         </el-row>
@@ -22,8 +22,17 @@
 
         <el-row>
           <el-col :span="24">
-            <el-form-item :label="this.$t('deviceMenuDetailManage.summary')" prop="summary" type="textarea" maxlength="200">
-              <el-input v-model="formmodel.summary" :placeholder="this.$t('deviceMenuDetailManage.inputSummary')" :maxlength="200" clearable/>
+            <el-form-item :label="this.$t('deviceMenuDetailManage.summary')" prop="summary">
+              <el-input v-model="formmodel.summary" :placeholder="this.$t('deviceMenuDetailManage.inputSummary')" type="textarea" :maxlength="200" clearable/>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row>
+          <el-col :span="24">
+            <el-form-item :label="this.$t('deviceMenuDetailManage.detail')" prop="detail">
+<!--              <wangEditor v-model="formmodel.detail" clearable/>-->
+<!--              <wangEditor/>-->
             </el-form-item>
           </el-col>
         </el-row>
@@ -69,10 +78,12 @@
 
 <script>
 import {DEVICE_MENU_DETAIL_TYPE} from "@/utils/enum/DeviceMenuDetailTypeEnum";
+// import wangEditor from "@/components/WangEdiitor/wangEditor";
 
 const serverUrl = domain.serverUrl
 export default {
   name: "index",
+  // components: { wangEditor },
   data() {
     return {
       rules: {
@@ -84,7 +95,7 @@ export default {
       titleIconUrl: '',
       titleBgUploadUrl: serverUrl + '/ncs/upload/uploadFile',
       titleBgUrl: '',
-      deviceMenuDetialTypeTransfer: DEVICE_MENU_DETAIL_TYPE.getKeyValueList(),
+      deviceMenuDetailTypeTransfer: DEVICE_MENU_DETAIL_TYPE.getKeyValueList(),
     }
   },
   methods: {