精华内容
下载资源
问答
  • wangEditor

    2018-04-02 10:03:07
    很好用的富文本编辑器wangEditor,设置内容、获取内容、上传图片、使用 AMD 加载、使用 textarea、onblur、onfocus、onchange、获取 JSON、配置表情、多语言、CSS-Reset
  • wangeditor

    2018-10-25 20:46:57
    使用jsp+servlet实现上传功能,并且解决Tomcat重启后,图片被删除的问题
  • wangEditor wangEditor 轻量级 web 富文本编辑器,配置方便,使用简单! 官网 · 文档 English · 在线示例 · 提交 bug · 建议增加新功能 项目介绍 wangEditor 是一款...
  • wangeditor 使用

    2021-04-19 18:11:52
    # 安装wangeditor npm i wangeditor --save # 引入 import E from "wangeditor" # 初始化 const editor = new E("#div1"); # 创建 editor.create(); # 获取元素 editor.txt.html(); 注:要注意创建的优先级,举...

    富文本编辑器官网:
    https://www.wangeditor.com
    文档地址:
    https://doc.wangeditor.com

    # 安装wangeditor
    npm i wangeditor --save
    
    # 引入
    import E from "wangeditor"
    
    # 初始化
    const editor = new E("#div1");
    
    # 创建
    editor.create();
    
    # 获取元素
    editor.txt.html();
    注:要注意创建的优先级,举个例子:如果富文本编辑器在表单中,
    首先表单要创建完成后,才能创建富文本
    

    使用案例:

    <template>
      <a-layout>
        <a-layout-content
          :style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }"
        >
          <a-row :gutter="24">
            <a-col :span="8">
              <p>
                <a-form layout="inline" :model="param">
                  <a-form-item>
                    <a-button type="primary" @click="handleQuery()">
                      查询
                    </a-button>
                  </a-form-item>
                  <a-form-item>
                    <a-button type="primary" @click="add()">
                      新增
                    </a-button>
                  </a-form-item>
                </a-form>
              </p>
              <a-table
                v-if="level1.length > 0"
                :columns="columns"
                :row-key="record => record.id"
                :data-source="level1"
                :loading="loading"
                :pagination="false"
                size="small"
                :defaultExpandAllRows="true"
              >
                <template #name="{ text, record }">
                  {{record.sort}} {{text}}
                </template>
                <template v-slot:action="{ text, record }">
                  <a-space size="small">
                    <a-button type="primary" @click="edit(record)" size="small">
                      编辑
                    </a-button>
                    <a-popconfirm
                      title="删除后不可恢复,确认删除?"
                      ok-text="是"
                      cancel-text="否"
                      @confirm="handleDelete(record.id)"
                    >
                      <a-button type="danger" size="small">
                        删除
                      </a-button>
                    </a-popconfirm>
                  </a-space>
                </template>
              </a-table>
            </a-col>
            <a-col :span="16">
              <p>
                <a-form layout="inline" :model="param">
                  <a-form-item>
                    <a-button type="primary" @click="handleSave()">
                      保存
                    </a-button>
                  </a-form-item>
                </a-form>
              </p>
              <a-form :model="doc" layout="vertical">
                <a-form-item>
                  <a-input v-model:value="doc.name" placeholder="名称"/>
                </a-form-item>
                <a-form-item>
                  <a-tree-select
                    v-model:value="doc.parent"
                    style="width: 100%"
                    :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
                    :tree-data="treeSelectData"
                    placeholder="请选择父文档"
                    tree-default-expand-all
                    :replaceFields="{title: 'name', key: 'id', value: 'id'}"
                  >
                  </a-tree-select>
                </a-form-item>
                <a-form-item>
                  <a-input v-model:value="doc.sort" placeholder="顺序"/>
                </a-form-item>
                <a-form-item>
                  <a-button type="primary" @click="handlePreviewContent()">
                    <EyeOutlined /> 内容预览
                  </a-button>
                </a-form-item>
                <a-form-item>
                  <div id="content"></div>
                </a-form-item>
              </a-form>
            </a-col>
          </a-row>
    
          <a-drawer width="900" placement="right" :closable="false" :visible="drawerVisible" @close="onDrawerClose">
            <div class="wangeditor" :innerHTML="previewHtml"></div>
          </a-drawer>
    
        </a-layout-content>
      </a-layout>
    
      <!--<a-modal-->
      <!--  title="文档表单"-->
      <!--  v-model:visible="modalVisible"-->
      <!--  :confirm-loading="modalLoading"-->
      <!--  @ok="handleModalOk"-->
      <!--&gt;-->
      <!--  -->
      <!--</a-modal>-->
    </template>
    
    <script lang="ts">
      import { defineComponent, onMounted, ref, createVNode } from 'vue';
      import axios from 'axios';
      import {message, Modal} from 'ant-design-vue';
      import {Tool} from "@/util/tool";
      import {useRoute} from "vue-router";
      import ExclamationCircleOutlined from "@ant-design/icons-vue/ExclamationCircleOutlined";
      import E from 'wangeditor'
    
      export default defineComponent({
        name: 'AdminDoc',
        setup() {
          const route = useRoute();
          console.log("路由:", route);
          console.log("route.path:", route.path);
          console.log("route.query:", route.query);
          console.log("route.param:", route.params);
          console.log("route.fullPath:", route.fullPath);
          console.log("route.name:", route.name);
          console.log("route.meta:", route.meta);
          const param = ref();
          param.value = {};
          const docs = ref();
          const loading = ref(false);
          // 因为树选择组件的属性状态,会随当前编辑的节点而变化,所以单独声明一个响应式变量
          const treeSelectData = ref();
          treeSelectData.value = [];
    
          const columns = [
            {
              title: '名称',
              dataIndex: 'name',
              slots: { customRender: 'name' }
            },
            {
              title: 'Action',
              key: 'action',
              slots: { customRender: 'action' }
            }
          ];
    
          /**
           * 一级文档树,children属性就是二级文档
           * [{
           *   id: "",
           *   name: "",
           *   children: [{
           *     id: "",
           *     name: "",
           *   }]
           * }]
           */
          const level1 = ref(); // 一级文档树,children属性就是二级文档
          level1.value = [];
    
          /**
           * 数据查询
           **/
          const handleQuery = () => {
            loading.value = true;
            // 如果不清空现有数据,则编辑保存重新加载数据后,再点编辑,则列表显示的还是编辑前的数据
            level1.value = [];
            axios.get("/doc/all/" + route.query.ebookId).then((response) => {
              loading.value = false;
              const data = response.data;
              if (data.success) {
                docs.value = data.content;
                console.log("原始数组:", docs.value);
    
                level1.value = [];
                level1.value = Tool.array2Tree(docs.value, 0);
                console.log("树形结构:", level1);
    
                // 父文档下拉框初始化,相当于点击新增
                treeSelectData.value = Tool.copy(level1.value) || [];
                // 为选择树添加一个"无"
                treeSelectData.value.unshift({id: 0, name: '无'});
              } else {
                message.error(data.message);
              }
            });
          };
    
          // -------- 表单 ---------
          const doc = ref();
          doc.value = {
            ebookId: route.query.ebookId
          };
          const modalVisible = ref(false);
          const modalLoading = ref(false);
          const editor = new E('#content');
          editor.config.zIndex = 0;
    
          const handleSave = () => {
            modalLoading.value = true;
            doc.value.content = editor.txt.html();
            axios.post("/doc/save", doc.value).then((response) => {
              modalLoading.value = false;
              const data = response.data; // data = commonResp
              if (data.success) {
                // modalVisible.value = false;
                message.success("保存成功!");
    
                // 重新加载列表
                handleQuery();
              } else {
                message.error(data.message);
              }
            });
          };
    
          /**
           * 将某节点及其子孙节点全部置为disabled
           */
          const setDisable = (treeSelectData: any, id: any) => {
            // console.log(treeSelectData, id);
            // 遍历数组,即遍历某一层节点
            for (let i = 0; i < treeSelectData.length; i++) {
              const node = treeSelectData[i];
              if (node.id === id) {
                // 如果当前节点就是目标节点
                console.log("disabled", node);
                // 将目标节点设置为disabled
                node.disabled = true;
    
                // 遍历所有子节点,将所有子节点全部都加上disabled
                const children = node.children;
                if (Tool.isNotEmpty(children)) {
                  for (let j = 0; j < children.length; j++) {
                    setDisable(children, children[j].id)
                  }
                }
              } else {
                // 如果当前节点不是目标节点,则到其子节点再找找看。
                const children = node.children;
                if (Tool.isNotEmpty(children)) {
                  setDisable(children, id);
                }
              }
            }
          };
    
          const deleteIds: Array<string> = [];
          const deleteNames: Array<string> = [];
          /**
           * 查找整根树枝
           */
          const getDeleteIds = (treeSelectData: any, id: any) => {
            // console.log(treeSelectData, id);
            // 遍历数组,即遍历某一层节点
            for (let i = 0; i < treeSelectData.length; i++) {
              const node = treeSelectData[i];
              if (node.id === id) {
                // 如果当前节点就是目标节点
                console.log("delete", node);
                // 将目标ID放入结果集ids
                // node.disabled = true;
                deleteIds.push(id);
                deleteNames.push(node.name);
    
                // 遍历所有子节点
                const children = node.children;
                if (Tool.isNotEmpty(children)) {
                  for (let j = 0; j < children.length; j++) {
                    getDeleteIds(children, children[j].id)
                  }
                }
              } else {
                // 如果当前节点不是目标节点,则到其子节点再找找看。
                const children = node.children;
                if (Tool.isNotEmpty(children)) {
                  getDeleteIds(children, id);
                }
              }
            }
          };
    
          /**
           * 内容查询
           **/
          const handleQueryContent = () => {
            axios.get("/doc/find-content/" + doc.value.id).then((response) => {
              const data = response.data;
              if (data.success) {
                editor.txt.html(data.content)
              } else {
                message.error(data.message);
              }
            });
          };
    
          /**
           * 编辑
           */
          const edit = (record: any) => {
            // 清空富文本框
            editor.txt.html("");
            modalVisible.value = true;
            doc.value = Tool.copy(record);
            handleQueryContent();
    
            // 不能选择当前节点及其所有子孙节点,作为父节点,会使树断开
            treeSelectData.value = Tool.copy(level1.value);
            setDisable(treeSelectData.value, record.id);
    
            // 为选择树添加一个"无"
            treeSelectData.value.unshift({id: 0, name: '无'});
          };
    
          /**
           * 新增
           */
          const add = () => {
            // 清空富文本框
            editor.txt.html("");
            modalVisible.value = true;
            doc.value = {
              ebookId: route.query.ebookId
            };
    
            treeSelectData.value = Tool.copy(level1.value) || [];
    
            // 为选择树添加一个"无"
            treeSelectData.value.unshift({id: 0, name: '无'});
          };
    
          const handleDelete = (id: number) => {
            // console.log(level1, level1.value, id)
            // 清空数组,否则多次删除时,数组会一直增加
            deleteIds.length = 0;
            deleteNames.length = 0;
            getDeleteIds(level1.value, id);
            Modal.confirm({
              title: '重要提醒',
              icon: createVNode(ExclamationCircleOutlined),
              content: '将删除:【' + deleteNames.join(",") + "】删除后不可恢复,确认删除?",
              onOk() {
                // console.log(ids)
                axios.delete("/doc/delete/" + deleteIds.join(",")).then((response) => {
                  const data = response.data; // data = commonResp
                  if (data.success) {
                    // 重新加载列表
                    handleQuery();
                  } else {
                    message.error(data.message);
                  }
                });
              },
            });
          };
    
          // ----------------富文本预览--------------
          const drawerVisible = ref(false);
          const previewHtml = ref();
          const handlePreviewContent = () => {
            const html = editor.txt.html();
            previewHtml.value = html;
            drawerVisible.value = true;
          };
          const onDrawerClose = () => {
            drawerVisible.value = false;
          };
    
          onMounted(() => {
            handleQuery();
    
            editor.create();
          });
    
          return {
            param,
            // docs,
            level1,
            columns,
            loading,
            handleQuery,
    
            edit,
            add,
    
            doc,
            modalVisible,
            modalLoading,
            handleSave,
    
            handleDelete,
    
            treeSelectData,
    
            drawerVisible,
            previewHtml,
            handlePreviewContent,
            onDrawerClose,
          }
        }
      });
    </script>
    
    <style scoped>
      img {
        width: 50px;
        height: 50px;
      }
    </style>
    
    
    展开全文
  • wangEditor wangEditor 轻量级 web 富文本编辑器,配置方便,使用简单! 官网 · 文档 English · 在线示例 · 提交 bug · 建议增加新功能 项目介绍 wangEditor 是一款...
  • ngx-wangeditor 的 Angular 版本。 演示 如何安装 安装 ngx-wangeditor npm install ngx-wangeditor --save 导入 ngx-wangeditor 到根模块 AppModule import { NgxWangEditorModule } from 'ngx-wangeditor'; @...
  • wangEditor-users-doc wangEditor用户文档 开发文档参考
  • wangEditor.js

    2020-07-31 16:06:39
    直接替换wangEditor.js文件,即可编辑上传图片大小,直接替换wangEditor.js文件,即可编辑上传图片大小,直接替换wangEditor.js文件,即可编辑上传图片大小,直接替换wangEditor.js文件,即可编辑上传图片大小
  • Laravel-WangEditor WangEditor3 for Laravel 5. 是一款基于javascript和css开发的html富文本编辑器,开源免费。 Install $ composer require sohenk/wangeditor:dev-master Contributing 将下面这行添加到 config/...
  • wangEditor文档

    2016-01-22 09:53:20
    wangEditor文档,描述各种功能的实现
  • wangEditor wangEditor 轻量级 web 富文本编辑器,配置方便,使用简单! · · · · 项目介绍 wangEditor 是一款使用 Typescript 开发的 Web 富文本编辑器, 轻量、简洁、易用、开源免费。 ...
  • wangEditor extension for laravel-admin 这是一个laravel-admin扩展,用来将wangEditor集成进laravel-admin的表单中 laravel-admin extension 1.x 1.x 2.x 2.x 截图 安装 // laravel-admin 1.x composer require ...
  • wangEditor源代码

    2021-03-18 12:55:55
    wangEditor是一款基于javascript和css开发的html富文本编辑器,开源免费。 wangEditor重构v1.3.13发布,全平台浏览器支持,现已支持以下自定义配置: 设置高度(固定高度、自适应高度) 初始化内容(通过javascript...
  • wangEditor-3.1.1

    2018-05-24 09:46:42
    wangEditor-3.1.1下载解压后引用release文件夹里的wangEditor.min.js或wangEditor.js到页面
  • 因为 wangEdit 内置图片上传功能,所以在创建实例的时候修改参数就行,视频上传就要修改到 wangeditor 的源码了 如果因为特殊需求需要以 js 文件形式引入 wangEditor 的,而不是通过引入 node_modules 来引入的,只...

    因为 wangEdit 内置图片上传功能,所以在创建实例的时候修改参数就行,视频上传就要修改到 wangeditor 的源码了

    Demo地址:https://download.csdn.net/download/qq_25992675/12844260

    如果因为特殊需求需要以 js 文件形式引入 wangEditor 的,而不是通过引入 node_modules 来引入的,只需要把 node_modules 中的 wangEditor 中的 release 中的 wangEditor.js 复制出来到 public 文件夹中,在 index.html中引入使用即可

    引入代码;

        <script type="text/javascript" src="./wangEditor.js"></script>
    

    调用代码:

    var E = window.wangEditor;
    ...
    ...
    // 在生命周期中初始化 editor 
    this.editor = new E(this.$refs.toolbar, this.$refs.editor);
    

    使用wangEditor

    1、安装wangEditor

    npm install wangeditor
    

    2、创建组件,通过组件调用 wangEditor

    edit是在index.html引入wangEdit的组件,importEdit是直接引入wangEdit的组件

    • edit 是在 index.html 引入 wangEditor 的组件
    • importEdit 是直接引入 wangEditor 的组件

    3、编写 importEdit.vue 组件(以下暂时都以直接引入wangEditor为例)

    视频上传片段代码需要按底下操作配置对应视频上传方法
    如果不需要视频上传,删掉视频上传代码片段,直接跳至第7步即可
    
    <template lang="html">
    	<div class="editor">
    	    <div ref="toolbar" class="toolbar"></div>
    	    <div ref="editor" class="text"></div>
    	</div>
    </template>
    
    <script>
    import E from "wangeditor";
    export default {
        name: "importEdit",
        data() {
            return {
                editor: null,
                info_: null,
            };
        },
        model: {
            prop: "value",
            event: "change",
        },
        props: {
            value: {
                type: String,
                default: "",
            },
            isClear: {
                type: Boolean,
                default: false,
            },
        },
        watch: {
            isClear(val) {
                // 触发清除文本域内容
                if (val) {
                    this.editor.txt.clear();
                    this.info_ = null;
                }
            },
            value: function (value) {
                if (value !== this.editor.txt.html()) {
                    this.editor.txt.html(this.value);
                }
            },
            //value为编辑框输入的内容,这里我监听了一下值,当父组件调用得时候,如果给value赋值了,子组件将会显示父组件赋给的值
        },
        mounted() {
            this.seteditor();
            this.editor.txt.html(this.value);
        },
        methods: {
            seteditor() {
                // http://192.168.2.125:8080/admin/storage/create
                this.editor = new E(this.$refs.toolbar, this.$refs.editor);
                this.editor.customConfig.uploadImgShowBase64 = false; // base 64 存储图片
                this.editor.customConfig.uploadImgServer =
                    "http://otp.cdinfotech.top/file/upload_images"; // 配置服务器端地址
                this.editor.customConfig.uploadImgHeaders = {}; // 自定义 header
                this.editor.customConfig.uploadFileName = "file"; // 后端接受上传文件的参数名
                this.editor.customConfig.uploadImgMaxSize = 2 * 1024 * 1024; // 将图片大小限制为 2M
                this.editor.customConfig.uploadImgMaxLength = 6; // 限制一次最多上传 3 张图片
                this.editor.customConfig.uploadImgTimeout = 3 * 60 * 1000; // 设置超时时间
    
                this.editor.customConfig.uploadImgHooks = {
                    fail: (xhr, editor, result) => {
                        // 插入图片失败回调
                    },
                    success: (xhr, editor, result) => {
                        // 图片上传成功回调
                    },
                    timeout: (xhr, editor) => {
                        // 网络超时的回调
                    },
                    error: (xhr, editor) => {
                        // 图片上传错误的回调
                    },
                    customInsert: (insertImg, result, editor) => {
                        // 图片上传成功,插入图片的回调
                        //result为上传图片成功的时候返回的数据,这里我打印了一下发现后台返回的是data:[{url:"路径的形式"},...]
                        // console.log(result.data[0].url)
                        //insertImg()为插入图片的函数
                        //循环插入图片
                        // for (let i = 0; i < 1; i++) {
                        // console.log(result)
                        let url = "http://otp.cdinfotech.top" + result.url;
                        insertImg(url);
                        // }
                    },
                };
                this.editor.customConfig.onchange = (html) => {
                    this.info_ = html; // 绑定当前逐渐地值
                    this.$emit("change", this.info_); // 将内容同步到父组件中
                };
    
                // 视频上传
                this.editor.customConfig.uploadVideoServer =
                    "http://otp.cdinfotech.top/file/upload_images"; // 上传接口
                this.editor.customConfig.uploadVideoParams = {
                    "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundarycZm1pHksXeHS6t5r"
                }
                this.editor.customConfig.uploadVideoHooks = {
                    // 上传完成处理方法
                    customInsert: function (insertVideo, result) {
                        if (result.ret === 200) {
                            (result.data || "").split(",").forEach(function (link) {
                                link && insertVideo(link);
                            });
                        } else {
                            flavrShowByTime("上传失败", null, "danger");
                        }
                    },
                };
                // 创建富文本编辑器
                this.editor.create();
            },
        },
    };
    </script>
    
    <style lang="scss" scoped>
    .editor {
        width: 100%;
        margin: 30px auto;
        position: relative;
        z-index: 0;
    
        .toolbar {
            border: 1px solid #ccc;
            min-height: 32px;
        }
    
        .text {
            overflow: auto;
            border: 1px solid #ccc;
            min-height: 500px;
            max-height: 500px;
        }
    }
    </style>
    
    

    4、在 node_modules 中找到 wangeditor 里面的 release 里面的 wangEditor.js

    搜索 function Video(editor) ,并找到 function Video(editor) 下面的原型 Video.prototype,
    更改原型里面的 _createPanel 方法
    
     _createPanel: function _createPanel() {
    
          	var _this = this;
            var editor = this.editor;
            var uploadImg = editor.uploadImg;
            var config = editor.config;
    
            // 创建 id
            // 上传视频id
            var upTriggerVideoId = getRandom('up-trigger-video');
            var upFileVideoId = getRandom('up-file-video');
    
            // 插入视频id
            var textValId = getRandom('text-val');
            var btnId = getRandom('btn');
    
            // 创建 panel
            // 一个 panel 多个 tab
            var tabsConfig = [{
                title: '上传视频或pdf',
                tpl: '<div class="w-e-up-img-container"><div id="' + upTriggerVideoId + '" class="w-e-up-btn"><i class="w-e-icon-upload2"></i></div><div style="display:none;"><input id="' + upFileVideoId + '" type="file" multiple="multiple" accept="application/pdf,video/*"/></div></div>',
                events: [{
                    // 触发选择图片
                    selector: '#' + upTriggerVideoId,
                    type: 'click',
                    fn: function fn() {
                        var $file = $('#' + upFileVideoId);
                        var fileElem = $file[0];
                        if (fileElem) {
                            fileElem.click();
                        } else {
                            // 返回 true 可关闭 panel
                            return true;
                        }
                    }
                }, {
                    // 选择图片完毕
                    selector: '#' + upFileVideoId,
                    type: 'change',
                    fn: function fn() {
                        var $file = $('#' + upFileVideoId);
                        var fileElem = $file[0];
                        if (!fileElem) {
                            // 返回 true 可关闭 panel
                            return true;
                        }
    
                        // 获取选中的 file 对象列表
                        var fileList = fileElem.files;
                        if (fileList.length) {
                            console.log(fileList);
                            uploadImg.uploadVideo(fileList);
                        }
    
                        // 返回 true 可关闭 panel
                        return true;
                    }
                }]
            }, // first tab end
            {
                // 标题
                title: '插入视频',
                // 模板
                tpl: '<div><input id="' + textValId + '" type="text" class="block" placeholder="\u683C\u5F0F\u5982\uFF1A<iframe src=... ></iframe>"/><div class="w-e-button-container"><button id="' + btnId + '" class="right">\u63D2\u5165</button></div></div>',
                // 事件绑定
                events: [{
                    selector: '#' + btnId,
                    type: 'click',
                    fn: function fn() {
                        var $text = $('#' + textValId);
                        var val = $text.val().trim();
    
                        if (val) _this._insert(val); // 插入视频
    
                        // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
                        return true;
                    }
                }]
            } // second tab end
            ] // tabs end
    
    
            // 判断 tabs 的显示
            var tabsConfigResult = [];
            if (config.uploadVideoServer) {
                // 显示“上传视频”
                tabsConfigResult.push(tabsConfig[0]);
            }
            if (config.showLinkVideo) {
                // 显示“网络视频”
                tabsConfigResult.push(tabsConfig[1]);
            }
    
            // 创建 panel
            var panel = new Panel(this, {
                width: 350,
                // 一个 panel 多个 tab
                tabs: tabsConfigResult // tabs end
            }); // panel end
    
            // 显示 panel
            panel.show();
    
            // 记录属性
            this.panel = panel;
        },
    

    5、找到上传图片的原型,搜索 UploadImg.prototype,在上传图片的方法后面插入上传视频的方法

    在这里插入图片描述
    上传视频代码

    // 上传视频
            uploadVideo: function uploadVideo(files) {
                var _this3 = this;
    
                if (!files || !files.length) {
                    return;
                }
    
                // ------------------------------ 获取配置信息 ------------------------------
                var editor = this.editor;
                var config = editor.config;
                var uploadVideoServer = config.uploadVideoServer;
    
                var maxSize = config.uploadVideoMaxSize;
                var maxSizeM = maxSize / 1024 / 1024;
                var maxLength = config.uploadVideoMaxLength || 10000;
                var uploadFileName = config.uploadFileName || '';
                var uploadVideoParams = config.uploadVideoParams || {};
                var uploadVideoParamsWithUrl = config.uploadVideoParamsWithUrl;
                var uploadVideoHeaders = config.uploadVideoHeaders || {};
                var hooks = config.uploadVideoHooks || {};
                var timeout = config.uploadVideoTimeout || 30 * 60 * 1000; // 30分钟
                var withCredentials = config.withCredentials;
                if (withCredentials == null) {
                    withCredentials = false;
                }
                var customUploadVideo = config.customUploadVideo;
    
                if (!customUploadVideo) {
                    // 没有 customUploadVideo 的情况下,需要如下两个配置才能继续进行图片上传
                    if (!uploadVideoServer) {
                        return;
                    }
                }
    
                // ------------------------------ 验证文件信息 ------------------------------
                var resultFiles = [];
                var errInfo = [];
                arrForEach(files, function (file) {
                    var name = file.name;
                    var size = file.size;
    
                    // chrome 低版本 name === undefined
                    if (!name || !size) {
                        return;
                    }
    
                    if (/\.(pdf|rm|rmvb|3gp|avi|mpeg|mpg|mkv|dat|asf|wmv|flv|mov|mp4|ogg|ogm)$/i.test(name) === false) {
                        // 后缀名不合法,不是视频
                        errInfo.push('\u3010' + name + '\u3011\u4E0D\u662F\u56FE\u7247');
                        return;
                    }
                    if (maxSize < size) {
                        // 上传视频过大
                        errInfo.push('\u3010' + name + '\u3011\u5927\u4E8E ' + maxSizeM + 'M');
                        return;
                    }
    
                    // 验证通过的加入结果列表
                    resultFiles.push(file);
                });
                // 抛出验证信息
                if (errInfo.length) {
                    this._alert('视频验证未通过: \n' + errInfo.join('\n'));
                    return;
                }
                if (resultFiles.length > maxLength) {
                    this._alert('一次最多上传' + maxLength + '个视频');
                    return;
                }
    
                // ------------------------------ 自定义上传 ------------------------------
                if (customUploadVideo && typeof customUploadVideo === 'function') {
                    customUploadVideo(resultFiles, this.insertLinkVideo.bind(this));
    
                    // 阻止以下代码执行
                    return;
                }
    
                // 添加图片数据
                var formdata = new FormData();
                arrForEach(resultFiles, function (file) {
                    var name = uploadFileName || file.name;
                    formdata.append(name, file);
                });
    
                // ------------------------------ 上传图片 ------------------------------
                if (uploadVideoServer && typeof uploadVideoServer === 'string') {
                    // 添加参数
                    var uploadVideoServerArr = uploadVideoServer.split('#');
                    uploadVideoServer = uploadVideoServerArr[0];
                    var uploadVideoServerHash = uploadVideoServerArr[1] || '';
                    objForEach(uploadVideoParams, function (key, val) {
                        // 因使用者反应,自定义参数不能默认 encode ,由 v3.1.1 版本开始注释掉
                        // val = encodeURIComponent(val)
    
                        // 第一,将参数拼接到 url 中
                        if (uploadVideoParamsWithUrl) {
                            if (uploadVideoServer.indexOf('?') > 0) {
                                uploadVideoServer += '&';
                            } else {
                                uploadVideoServer += '?';
                            }
                            uploadVideoServer = uploadVideoServer + key + '=' + val;
                        }
    
                        // 第二,将参数添加到 formdata 中
                        formdata.append(key, val);
                    });
                    if (uploadVideoServerHash) {
                        uploadVideoServer += '#' + uploadVideoServerHash;
                    }
    
                    // 定义 xhr
                    var xhr = new XMLHttpRequest();
                    xhr.open('POST', uploadVideoServer);
    
                    // 设置超时
                    xhr.timeout = timeout;
                    xhr.ontimeout = function () {
                        // hook - timeout
                        if (hooks.timeout && typeof hooks.timeout === 'function') {
                            hooks.timeout(xhr, editor);
                        }
    
                        _this3._alert('上传视频超时');
                    };
    
                    // 监控 progress
                    if (xhr.upload) {
                        xhr.upload.onprogress = function (e) {
                            var percent = void 0;
                            // 进度条
                            var progressBar = new Progress(editor);
                            if (e.lengthComputable) {
                                percent = e.loaded / e.total;
                                progressBar.show(percent);
                            }
                        };
                    }
    
                    // 返回数据
                    xhr.onreadystatechange = function () {
                        var result = void 0;
                        if (xhr.readyState === 4) {
                            if (xhr.status < 200 || xhr.status >= 300) {
                                // hook - error
                                if (hooks.error && typeof hooks.error === 'function') {
                                    hooks.error(xhr, editor);
                                }
    
                                // xhr 返回状态错误
                                _this3._alert('上传视频发生错误', '\u4E0A\u4F20\u56FE\u7247\u53D1\u751F\u9519\u8BEF\uFF0C\u670D\u52A1\u5668\u8FD4\u56DE\u72B6\u6001\u662F ' + xhr.status);
                                return;
                            }
    
                            result = xhr.responseText;
                            if ((typeof result === 'undefined' ? 'undefined' : _typeof(result)) !== 'object') {
                                try {
                                    result = JSON.parse(result);
                                } catch (ex) {
                                    // hook - fail
                                    if (hooks.fail && typeof hooks.fail === 'function') {
                                        hooks.fail(xhr, editor, result);
                                    }
    
                                    _this3._alert('上传视频失败', '上传视频返回结果错误,返回结果是: ' + result);
                                    return;
                                }
                            }
                            if (!hooks.customInsert && result.errno != '0') {
                                // hook - fail
                                if (hooks.fail && typeof hooks.fail === 'function') {
                                    hooks.fail(xhr, editor, result);
                                }
    
                                // 数据错误
                                _this3._alert('上传视频失败', '上传视频返回结果错误,返回结果 errno=' + result.errno);
                            } else {
                                if (hooks.customInsert && typeof hooks.customInsert === 'function') {
                                    // 使用者自定义插入方法
                                    hooks.customInsert(_this3.insertLinkVideo.bind(_this3), result, editor);
                                } else {
                                    // 将图片插入编辑器
                                    var data = result.data || [];
                                    data.forEach(function (link) {
                                        _this3.insertLinkVideo(link);
                                    });
                                }
    
                                // hook - success
                                if (hooks.success && typeof hooks.success === 'function') {
                                    hooks.success(xhr, editor, result);
                                }
                            }
                        }
                    };
    
                    // hook - before
                    if (hooks.before && typeof hooks.before === 'function') {
                        var beforeResult = hooks.before(xhr, editor, resultFiles);
                        if (beforeResult && (typeof beforeResult === 'undefined' ? 'undefined' : _typeof(beforeResult)) === 'object') {
                            if (beforeResult.prevent) {
                                // 如果返回的结果是 {prevent: true, msg: 'xxxx'} 则表示用户放弃上传
                                this._alert(beforeResult.msg);
                                return;
                            }
                        }
                    }
    
                    // 自定义 headers
                    objForEach(uploadVideoHeaders, function (key, val) {
                        xhr.setRequestHeader(key, val);
                    });
    
                    // 跨域传 cookie
                    xhr.withCredentials = withCredentials;
    
                    // 发送请求
                    xhr.send(formdata);
                }
            },
    

    根据链接插入视频代码

    // 根据链接插入视频
            insertLinkVideo: function insertLinkVideo(link) {
                if (!link) return;
    
                var _this2 = this;
    
                var editor = this.editor;
                var config = editor.config;
    
                // 校验格式
                var linkVideoCheck = config.linkVideoCheck;
                var checkResult = void 0;
                if (linkVideoCheck && typeof linkVideoCheck === 'function') {
                    checkResult = linkVideoCheck(link);
                    if (typeof checkResult === 'string') {
                        // 校验失败,提示信息
                        alert(checkResult);
                        return;
                    }
                }
    
                editor.cmd.do('insertHTML', '<iframe src="' + link + '" style="width:650px;height: 366px" frameborder="0"></iframe>');
            }
    

    6、找到配置信息,搜索 var config = {

    在配置信息内配置上传视频默认参数, 不配置的话也会有默认,在使用方法中都做了参数判断
    为了规范代码,这里添加一下。
    
    // 是否显示添加网络视频的 tab
    showLinkVideo: true,
    
    // 插入网络视频的回调
    linkVideoCallback: function linkVideoCallback(url) {
        // console.log(url)  // url 即插入视频的地址
    },
    
    // 默认上传视频 max size: 512M
    uploadVideoMaxSize: 512 * 1024 * 1024,
    
    // 配置一次最多上传几个视频
    uploadVideoMaxLength: 5,
    
    // 上传视频的自定义参数
    uploadVideoParams: {
        // token: 'abcdef12345'
    },
    
    // 上传视频的自定义header
    uploadVideoHeaders: {
        // 'Accept': 'text/x-json'
    },
    
    // 自定义上传视频超时时间 30分钟
    uploadVideoTimeout: 30 * 60 * 1000,
    
    // 上传视频 hook 
    uploadVideoHooks: {
        // customInsert: function (insertLinkVideo, result, editor) {
        //     console.log('customInsert')
        //     // 视频上传并返回结果,自定义插入视频的事件,而不是编辑器自动插入视频
        //     const data = result.data1 || []
        //     data.forEach(link => {
        //         insertLinkVideo(link)
        //     })
        // },
        before: function before(xhr, editor, files) {
            // 视频上传之前触发
    
            // 如果返回的结果是 {prevent: true, msg: 'xxxx'} 则表示用户放弃上传
            // return {
            //     prevent: true,
            //     msg: '放弃上传'
            // }
        },
        success: function success(xhr, editor, result) {
            // 视频上传并返回结果,视频插入成功之后触发
        },
        fail: function fail(xhr, editor, result) {
            // 视频上传并返回结果,但视频插入错误时触发
        },
        error: function error(xhr, editor) {
            // 视频上传出错时触发
        },
        timeout: function timeout(xhr, editor) {
            // 视频上传超时时触发
        }
    }
    

    7、在需要使用 wangEdit 的页面调用 组件即可

    <template>
    <div class="about">
        <editor v-model="detail" :isClear="isClear" @change="change"></editor>
    </div>
    </template>
    
    <script>
    import Editor from "../components/importEdit";
    export default {
        name: "About",
        data() {
            return {
                isClear: false,
                detail: "",
            };
        },
        methods: {
            change(val) {
                console.log(val);
            },
        },
        components: {
            Editor,
        },
    };
    </script>
    
    

    遇到的问题

    顶部弹窗优化

    因为 wangEditor 插图图片、表格、视频等的 弹窗是新增在内容区域的不是顶部菜单栏区域,如果富文本内容过长时插入图片还要滑动到顶部进行插入,所以在这里提供一个优化弹窗的方法

    在 wangEditor.js 中 搜索

     var $textContainerElem = editor.$textContainerElem;
    

    将这句代码替换成,并在当前代码块中使用到 $textContainerElem 的都替换成 $toolbarElem

    var $toolbarElem = editor.$toolbarElem;
    

    因为 wangEditor 的弹窗样式写在了内容中,所以要自己添加弹窗的样式,或则找到css源代码进行替换也行,将 .w-e-text-container .w-e-panel-container 替换成 .w-e-toolbar .w-e-panel-container

    .w-e-toolbar .w-e-panel-container {
        position: absolute;
        top: 32px;
        left: 50%;
        border: 1px solid #ccc;
        border-top: 0;
        box-shadow: 1px 1px 2px #ccc;
        color: #333;
        z-index: 999999999999999;
        background-color: #fff;
        /* 为 emotion panel 定制的样式 */
        /* 上传图片的 panel 定制样式 */
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-close {
        position: absolute;
        right: 0;
        top: 0;
        padding: 5px;
        margin: 2px 5px 0 0;
        cursor: pointer;
        color: #999;
        background: #fff;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-close:hover {
        color: #333;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-title {
        list-style: none;
        display: -webkit-box;
        display: -ms-flexbox;
        display: flex;
        font-size: 14px;
        margin: 2px 10px 0 10px;
        border-bottom: 1px solid #f1f1f1;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-title .w-e-item {
        padding: 3px 5px;
        color: #999;
        cursor: pointer;
        margin: 0 3px;
        position: relative;
        top: 1px;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-title .w-e-active {
        color: #333;
        border-bottom: 1px solid #333;
        cursor: default;
        font-weight: 700;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-content {
        padding: 10px 15px 10px 15px;
        font-size: 16px;
        /* 输入框的样式 */
        /* 按钮的样式 */
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-content input:focus,
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-content textarea:focus,
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-content button:focus {
        outline: none;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-content textarea {
        width: 100%;
        border: 1px solid #ccc;
        padding: 5px;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-content textarea:focus {
        border-color: #1e88e5;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-content input[type=text] {
        border: none;
        border-bottom: 1px solid #ccc;
        font-size: 14px;
        height: 20px;
        color: #333;
        text-align: left;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-content input[type=text].small {
        width: 30px;
        text-align: center;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-content input[type=text].block {
        display: block;
        width: 100%;
        margin: 10px 0;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-content input[type=text]:focus {
        border-bottom: 2px solid #1e88e5;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button {
        font-size: 14px;
        color: #1e88e5;
        border: none;
        padding: 5px 10px;
        background-color: #fff;
        cursor: pointer;
        border-radius: 3px;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.left {
        float: left;
        margin-right: 10px;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.right {
        float: right;
        margin-left: 10px;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.gray {
        color: #999;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.red {
        color: #c24f4a;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button:hover {
        background-color: #f1f1f1;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-panel-tab-content .w-e-button-container:after {
        content: "";
        display: table;
        clear: both;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-emoticon-container .w-e-item {
        cursor: pointer;
        font-size: 18px;
        padding: 0 3px;
        display: inline-block;
        *display: inline;
        *zoom: 1;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-up-img-container {
        text-align: center;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-up-img-container .w-e-up-btn {
        display: inline-block;
        *display: inline;
        *zoom: 1;
        color: #999;
        cursor: pointer;
        font-size: 60px;
        line-height: 1;
    }
    
    .w-e-toolbar .w-e-panel-container .w-e-up-img-container .w-e-up-btn:hover {
        color: #333;
    }
    

    在输入内容时光标会移动到内容尾端处理方法

    如果使用富文本的时候用的是 v-model 的话会有光标闪动到内容尾端的情况
    因为,editor 在接收到最新数据的时候会更新渲染视图,这个时候光标就会因为刷新移动到最尾部
    所以解决办法是拿一个新字段来存储我们更改的最新内容,在预览和提交的时候使用新字段即可。这样既能保证更新的是最新内容,也能做到不去刷新视图

    贴上 Demo

    <template>
    <div class="home">
        <button @click="submit">提交</button>
        <editor :value="detail" :isClear="isClear" @change="change"></editor>
    </div>
    </template>
    
    <script>
    import Editor from "../components/edit";
    export default {
        name: "Home",
        data() {
            return {
                isClear: false,
                detail: "",
                newDetail: "",
            };
        },
        methods: {
            change(val) {
                this.newDetail = val;
            },
            submit() {
                console.log(this.newDetail);
            }
        },
        components: {
            Editor,
        },
    };
    </script>
    
    
    展开全文
  • wangEditor.zip

    2020-05-13 11:42:49
    wangEditor相关js和css、fonts文件,不做任何商业用途,只为了给广大读者作为学习资料
  • wangeditor使用

    2020-01-14 19:11:40
    安装wangeditor npm i wangeditor -S <template> <div id='wangeditor'> <div id="wangeditor"> <div ref="editorElem" style="text-align:left;"></div> </div> ...

    安装wangeditor

    npm i wangeditor -S

    <template>
      <div id='wangeditor'>
    	<div id="wangeditor">
          <div ref="editorElem" style="text-align:left;"></div>
       </div>
        <button @click="updata">提交啊</button>
      </div>
    </template>
     
    <script>
      import E from "wangEditor";
      import 'wangeditor/release/wangEditor.min.css'
      export default {
        name:'wangeditor',
        data() {
          return {
             form: {
                    title: '',
                    ImageUrl: '',
                    content:'',
                    titleSource:'',
                    fileSource:'',
                },
          }
        },
        mounted(){
     
              this.editor = new E(this.$refs.editorElem); 
                // 编辑器的事件,每次改变会获取其html内容 
                this.editor.customConfig.onchange = html => {
                this.form.content = html; 
                //  this.catchData(this.editorContent); 
                // 把这个html通过catchData的方法传入父组件 
                }; 
    
                this.editor.customConfig.menus = [
                  // 菜单配置 
                  'head', // 标题 
                  'bold', // 粗体 
                  'fontSize', // 字号 
                  'fontName', // 字体 
                  'italic', // 斜体 
                  'underline', // 下划线 
                  'strikeThrough', // 删除线 
                  'foreColor', // 文字颜色 
                  'backColor', // 背景颜色 
                  'link', // 插入链接 
                  'list', // 列表 
                  'justify', // 对齐方式 
                  'quote', // 引用 
                  'emoticon', // 表情 
                  'image', // 插入图片 
                  'table', // 表格 
                  'code', // 插入代码 
                  'undo', // 撤销 
                  'redo' // 重复 
                  ]; 
                  
                this.editor.customConfig.pasteFilterStyle = false
               	// 手过滤word样式
                this.editor.customConfig.pasteTextHandle = function (content) {
                  // content 即粘贴过来的内容(html 或 纯文本),可进行自定义处理然后返回
                  if (content == '' && !content) return ''
                  var str = content
                  str = str.replace(/<xml>[\s\S]*?<\/xml>/ig, '')
                  str = str.replace(/<style>[\s\S]*?<\/style>/ig, '')
                  str = str.replace(/<\/?[^>]*>/g, '')
                  str = str.replace(/[ | ]*\n/g, '\n')
                  str = str.replace(/&nbsp;/ig, '')
                  console.log('****', content)
                  console.log('****', str)
                  return str
              }
                this.editor.customConfig.uploadFileName = "file";
                this.editor.customConfig.uploadImgHeaders = {
                'token':this.$store.getters.token
                }
                this.editor.customConfig.uploadImgServer = '/prod-api/file/upload' 
                // this.editor.customConfig.uploadImgServer = '/file/upload' 
                this.editor.customConfig.uploadImgTimeout = 10 * 60 * 1000 // 设置超时时间
                //自定义上传图片事件
                this.editor.customConfig.uploadImgHooks = {
                    before: function (xhr, editor, files) {
                    },
                    success: function (xhr, editor, result) {
                        console.log("上传成功");
                    },
                    fail: function (xhr, editor, result) {
                        console.log("上传失败,原因是" + result);
                        Message({
                          message: "请重新登录!",
                          type: 'error',
                          duration: 5 * 1000
                        })
                        this.$router.push({
                                path: '/login',
                            })
                    },
                    error: function (xhr, editor) {
                        console.log("上传出错");
                        Message({
                          message: "请重新登录!",
                          type: 'error',
                          duration: 5 * 1000
                        })
                        console.log(this.$router)
                        this.$router.push({
                                path: '/login',
                            })
                    },
                    timeout: function (xhr, editor) {
                      Message({
                          message: "上传超时!",
                          type: 'error',
                          duration: 5 * 1000
                        })
                        console.log("上传超时");
                    },
              customInsert: function (insertImg, result, editor) {
                console.log(result)
                var url = result.data.url;
                insertImg(url)
            }
          };
                // this.editor.customConfig.uploadImgShowBase64 = true //图片以base64形式保存
                this.editor.customConfig.uploadImgMaxSize = 1024 * 1024 * 1024; // 将图片大小限制为 3M
                this.editor.create();
            },
        },
      }
    </script>
     
    <style>
     
    </style>
    
    
    组件封装参考:https://www.bbsmax.com/A/amd0g2Vq5g/
    
    
    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,253
精华内容 901
关键字:

wangeditor