From 4dde145ee066a30b441a9687ab8a7b673c0a3ff1 Mon Sep 17 00:00:00 2001
From: long <515897141@qq.com>
Date: 星期一, 22 二月 2021 18:31:22 +0800
Subject: [PATCH] 上下架接口报错回滚状态

---
 src/components/Tinymce/index.vue                  |  247 ++++++++++++++++++++++++++++++
 src/components/Tinymce/plugins.js                 |    7 
 src/pages/demo/index.vue                          |    5 
 src/components/Tinymce/components/EditorImage.vue |  111 +++++++++++++
 src/pages/system/admin.vue                        |    2 
 src/pages/demo/form.vue                           |   13 +
 src/components/Tinymce/dynamicLoadScript.js       |   59 +++++++
 src/pages/demo/list.vue                           |    5 
 src/components/Tinymce/toolbar.js                 |    6 
 src/pages/system/banner.vue                       |    2 
 10 files changed, 450 insertions(+), 7 deletions(-)

diff --git a/src/components/Tinymce/components/EditorImage.vue b/src/components/Tinymce/components/EditorImage.vue
new file mode 100644
index 0000000..07d48e6
--- /dev/null
+++ b/src/components/Tinymce/components/EditorImage.vue
@@ -0,0 +1,111 @@
+<template>
+  <div class="upload-container">
+    <el-button :style="{background:color,borderColor:color}" icon="el-icon-upload" size="mini" type="primary" @click=" dialogVisible=true">
+      upload
+    </el-button>
+    <el-dialog :visible.sync="dialogVisible">
+      <el-upload
+        :multiple="true"
+        :file-list="fileList"
+        :show-file-list="true"
+        :on-remove="handleRemove"
+        :on-success="handleSuccess"
+        :before-upload="beforeUpload"
+        class="editor-slide-upload"
+        action="https://httpbin.org/post"
+        list-type="picture-card"
+      >
+        <el-button size="small" type="primary">
+          Click upload
+        </el-button>
+      </el-upload>
+      <el-button @click="dialogVisible = false">
+        Cancel
+      </el-button>
+      <el-button type="primary" @click="handleSubmit">
+        Confirm
+      </el-button>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+// import { getToken } from 'api/qiniu'
+
+export default {
+  name: 'EditorSlideUpload',
+  props: {
+    color: {
+      type: String,
+      default: '#1890ff'
+    }
+  },
+  data() {
+    return {
+      dialogVisible: false,
+      listObj: {},
+      fileList: []
+    }
+  },
+  methods: {
+    checkAllSuccess() {
+      return Object.keys(this.listObj).every(item => this.listObj[item].hasSuccess)
+    },
+    handleSubmit() {
+      const arr = Object.keys(this.listObj).map(v => this.listObj[v])
+      if (!this.checkAllSuccess()) {
+        this.$message('Please wait for all images to be uploaded successfully. If there is a network problem, please refresh the page and upload again!')
+        return
+      }
+      this.$emit('successCBK', arr)
+      this.listObj = {}
+      this.fileList = []
+      this.dialogVisible = false
+    },
+    handleSuccess(response, file) {
+      const uid = file.uid
+      const objKeyArr = Object.keys(this.listObj)
+      for (let i = 0, len = objKeyArr.length; i < len; i++) {
+        if (this.listObj[objKeyArr[i]].uid === uid) {
+          this.listObj[objKeyArr[i]].url = response.files.file
+          this.listObj[objKeyArr[i]].hasSuccess = true
+          return
+        }
+      }
+    },
+    handleRemove(file) {
+      const uid = file.uid
+      const objKeyArr = Object.keys(this.listObj)
+      for (let i = 0, len = objKeyArr.length; i < len; i++) {
+        if (this.listObj[objKeyArr[i]].uid === uid) {
+          delete this.listObj[objKeyArr[i]]
+          return
+        }
+      }
+    },
+    beforeUpload(file) {
+      const _self = this
+      const _URL = window.URL || window.webkitURL
+      const fileName = file.uid
+      this.listObj[fileName] = {}
+      return new Promise((resolve, reject) => {
+        const img = new Image()
+        img.src = _URL.createObjectURL(file)
+        img.onload = function() {
+          _self.listObj[fileName] = { hasSuccess: false, uid: file.uid, width: this.width, height: this.height }
+        }
+        resolve(true)
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.editor-slide-upload {
+  margin-bottom: 20px;
+  ::v-deep .el-upload--picture-card {
+    width: 100%;
+  }
+}
+</style>
diff --git a/src/components/Tinymce/dynamicLoadScript.js b/src/components/Tinymce/dynamicLoadScript.js
new file mode 100644
index 0000000..185f58d
--- /dev/null
+++ b/src/components/Tinymce/dynamicLoadScript.js
@@ -0,0 +1,59 @@
+let callbacks = []
+
+function loadedTinymce() {
+  // to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2144
+  // check is successfully downloaded script
+  return window.tinymce
+}
+
+const dynamicLoadScript = (src, callback) => {
+  const existingScript = document.getElementById(src)
+  const cb = callback || function() {}
+
+  if (!existingScript) {
+    const script = document.createElement('script')
+    script.src = src // src url for the third-party library being loaded.
+    script.id = src
+    document.body.appendChild(script)
+    callbacks.push(cb)
+    const onEnd = 'onload' in script ? stdOnEnd : ieOnEnd
+    onEnd(script)
+  }
+
+  if (existingScript && cb) {
+    if (loadedTinymce()) {
+      cb(null, existingScript)
+    } else {
+      callbacks.push(cb)
+    }
+  }
+
+  function stdOnEnd(script) {
+    script.onload = function() {
+      // this.onload = null here is necessary
+      // because even IE9 works not like others
+      this.onerror = this.onload = null
+      for (const cb of callbacks) {
+        cb(null, script)
+      }
+      callbacks = null
+    }
+    script.onerror = function() {
+      this.onerror = this.onload = null
+      cb(new Error('Failed to load ' + src), script)
+    }
+  }
+
+  function ieOnEnd(script) {
+    script.onreadystatechange = function() {
+      if (this.readyState !== 'complete' && this.readyState !== 'loaded') return
+      this.onreadystatechange = null
+      for (const cb of callbacks) {
+        cb(null, script) // there is no way to catch loading errors in IE8
+      }
+      callbacks = null
+    }
+  }
+}
+
+export default dynamicLoadScript
diff --git a/src/components/Tinymce/index.vue b/src/components/Tinymce/index.vue
new file mode 100644
index 0000000..cb6b91c
--- /dev/null
+++ b/src/components/Tinymce/index.vue
@@ -0,0 +1,247 @@
+<template>
+  <div :class="{fullscreen:fullscreen}" class="tinymce-container" :style="{width:containerWidth}">
+    <textarea :id="tinymceId" class="tinymce-textarea" />
+    <div class="editor-custom-btn-container">
+      <editorImage color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK" />
+    </div>
+  </div>
+</template>
+
+<script>
+/**
+ * docs:
+ * https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce
+ */
+import editorImage from './components/EditorImage'
+import plugins from './plugins'
+import toolbar from './toolbar'
+import load from './dynamicLoadScript'
+
+// why use this cdn, detail see https://github.com/PanJiaChen/tinymce-all-in-one
+const tinymceCDN = 'https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js'
+
+export default {
+  name: 'Tinymce',
+  components: { editorImage },
+  props: {
+    id: {
+      type: String,
+      default: function() {
+        return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
+      }
+    },
+    value: {
+      type: String,
+      default: ''
+    },
+    toolbar: {
+      type: Array,
+      required: false,
+      default() {
+        return []
+      }
+    },
+    menubar: {
+      type: String,
+      default: 'file edit insert view format table'
+    },
+    height: {
+      type: [Number, String],
+      required: false,
+      default: 360
+    },
+    width: {
+      type: [Number, String],
+      required: false,
+      default: 'auto'
+    }
+  },
+  data() {
+    return {
+      hasChange: false,
+      hasInit: false,
+      tinymceId: this.id,
+      fullscreen: false,
+      languageTypeList: {
+        'en': 'en',
+        'zh': 'zh_CN',
+        'es': 'es_MX',
+        'ja': 'ja'
+      }
+    }
+  },
+  computed: {
+    containerWidth() {
+      const width = this.width
+      if (/^[\d]+(\.[\d]+)?$/.test(width)) { // matches `100`, `'100'`
+        return `${width}px`
+      }
+      return width
+    }
+  },
+  watch: {
+    value(val) {
+      if (!this.hasChange && this.hasInit) {
+        this.$nextTick(() =>
+          window.tinymce.get(this.tinymceId).setContent(val || ''))
+      }
+    }
+  },
+  mounted() {
+    this.init()
+  },
+  activated() {
+    if (window.tinymce) {
+      this.initTinymce()
+    }
+  },
+  deactivated() {
+    this.destroyTinymce()
+  },
+  destroyed() {
+    this.destroyTinymce()
+  },
+  methods: {
+    init() {
+      // dynamic load tinymce from cdn
+      load(tinymceCDN, (err) => {
+        if (err) {
+          this.$message.error(err.message)
+          return
+        }
+        this.initTinymce()
+      })
+    },
+    initTinymce() {
+      const _this = this
+      window.tinymce.init({
+        selector: `#${this.tinymceId}`,
+        language: this.languageTypeList['en'],
+        height: this.height,
+        body_class: 'panel-body ',
+        object_resizing: false,
+        toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
+        menubar: this.menubar,
+        plugins: plugins,
+        end_container_on_empty_block: true,
+        powerpaste_word_import: 'clean',
+        code_dialog_height: 450,
+        code_dialog_width: 1000,
+        advlist_bullet_styles: 'square',
+        advlist_number_styles: 'default',
+        imagetools_cors_hosts: ['www.tinymce.com', 'codepen.io'],
+        default_link_target: '_blank',
+        link_title: false,
+        nonbreaking_force_tab: true, // inserting nonbreaking space &nbsp; need Nonbreaking Space Plugin
+        init_instance_callback: editor => {
+          if (_this.value) {
+            editor.setContent(_this.value)
+          }
+          _this.hasInit = true
+          editor.on('NodeChange Change KeyUp SetContent', () => {
+            this.hasChange = true
+            this.$emit('input', editor.getContent())
+          })
+        },
+        setup(editor) {
+          editor.on('FullscreenStateChanged', (e) => {
+            _this.fullscreen = e.state
+          })
+        },
+        // it will try to keep these URLs intact
+        // https://www.tiny.cloud/docs-3x/reference/configuration/Configuration3x@convert_urls/
+        // https://stackoverflow.com/questions/5196205/disable-tinymce-absolute-to-relative-url-conversions
+        convert_urls: false
+        // 鏁村悎涓冪墰涓婁紶
+        // images_dataimg_filter(img) {
+        //   setTimeout(() => {
+        //     const $image = $(img);
+        //     $image.removeAttr('width');
+        //     $image.removeAttr('height');
+        //     if ($image[0].height && $image[0].width) {
+        //       $image.attr('data-wscntype', 'image');
+        //       $image.attr('data-wscnh', $image[0].height);
+        //       $image.attr('data-wscnw', $image[0].width);
+        //       $image.addClass('wscnph');
+        //     }
+        //   }, 0);
+        //   return img
+        // },
+        // images_upload_handler(blobInfo, success, failure, progress) {
+        //   progress(0);
+        //   const token = _this.$store.getters.token;
+        //   getToken(token).then(response => {
+        //     const url = response.data.qiniu_url;
+        //     const formData = new FormData();
+        //     formData.append('token', response.data.qiniu_token);
+        //     formData.append('key', response.data.qiniu_key);
+        //     formData.append('file', blobInfo.blob(), url);
+        //     upload(formData).then(() => {
+        //       success(url);
+        //       progress(100);
+        //     })
+        //   }).catch(err => {
+        //     failure('鍑虹幇鏈煡闂锛屽埛鏂伴〉闈紝鎴栬�呰仈绯荤▼搴忓憳')
+        //     console.log(err);
+        //   });
+        // },
+      })
+    },
+    destroyTinymce() {
+      const tinymce = window.tinymce.get(this.tinymceId)
+      if (this.fullscreen) {
+        tinymce.execCommand('mceFullScreen')
+      }
+
+      if (tinymce) {
+        tinymce.destroy()
+      }
+    },
+    setContent(value) {
+      window.tinymce.get(this.tinymceId).setContent(value)
+    },
+    getContent() {
+      window.tinymce.get(this.tinymceId).getContent()
+    },
+    imageSuccessCBK(arr) {
+      arr.forEach(v => window.tinymce.get(this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`))
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.tinymce-container {
+  position: relative;
+  line-height: normal;
+}
+
+.tinymce-container {
+  ::v-deep {
+    .mce-fullscreen {
+      z-index: 10000;
+    }
+  }
+}
+
+.tinymce-textarea {
+  visibility: hidden;
+  z-index: -1;
+}
+
+.editor-custom-btn-container {
+  position: absolute;
+  right: 4px;
+  top: 4px;
+  /*z-index: 2005;*/
+}
+
+.fullscreen .editor-custom-btn-container {
+  z-index: 10000;
+  position: fixed;
+}
+
+.editor-upload-btn {
+  display: inline-block;
+}
+</style>
diff --git a/src/components/Tinymce/plugins.js b/src/components/Tinymce/plugins.js
new file mode 100644
index 0000000..058d2ae
--- /dev/null
+++ b/src/components/Tinymce/plugins.js
@@ -0,0 +1,7 @@
+// Any plugins you want to use has to be imported
+// Detail plugins list see https://www.tinymce.com/docs/plugins/
+// Custom builds see https://www.tinymce.com/download/custom-builds/
+
+const plugins = ['advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount']
+
+export default plugins
diff --git a/src/components/Tinymce/toolbar.js b/src/components/Tinymce/toolbar.js
new file mode 100644
index 0000000..4f8a545
--- /dev/null
+++ b/src/components/Tinymce/toolbar.js
@@ -0,0 +1,6 @@
+// Here is a list of the toolbar
+// Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols
+
+const toolbar = ['searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent  blockquote undo redo removeformat subscript superscript code codesample', 'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen']
+
+export default toolbar
diff --git a/src/pages/demo/form.vue b/src/pages/demo/form.vue
index a3cf94b..002987a 100644
--- a/src/pages/demo/form.vue
+++ b/src/pages/demo/form.vue
@@ -45,9 +45,12 @@
       <el-form-item label="鏄惁涓婃灦锛�">
         <el-switch v-model="mData.isUp" :active-value="1" :inactive-value="0" />
       </el-form-item>
-      <el-form-item label="璇︽儏锛�" prop="content">
+      <el-form-item label="瀵屾枃鏈�1锛�" prop="content">
         <!-- 瀵屾枃鏈� -->
         <WangEnduit :catchdata="catchData" :content="mData.content" :rangenum="rangenum" />
+      </el-form-item>
+      <el-form-item label="瀵屾枃鏈�2锛�" prop="content2">
+        <tinymce v-model="mData.content2" :height="300" />
       </el-form-item>
     </el-form>
 
@@ -67,6 +70,7 @@
 </template>
 
 <script>
+import Tinymce from '@/components/Tinymce'
 import WangEnduit from '@/components/WangEnduit' // 瀵屾枃鏈�
 import mixin_upload from '@/mixins/upload.js' // 閫氱敤涓婁紶鍥剧墖棰勮
 import UploadSingleImg from '@/components_simple/UploadSingleImg'
@@ -74,7 +78,7 @@
 import BackToTop from '@/components/BackToTop'
 export default {
   name: 'DemoForm',
-  components: { WangEnduit, UploadSingleImg, BackToTop },
+  components: { WangEnduit, UploadSingleImg, BackToTop, Tinymce },
   mixins: [
     mixin_upload
   ],
@@ -125,7 +129,10 @@
           required: true, message: '璇烽�夋嫨鍦板潃', trigger: 'change'
         }],
         content: [{
-          required: true, message: '璇峰~鍐欎紭鎯犲埜璇︽儏'
+          required: true, message: '璇峰~鍐欏瘜鏂囨湰1璇︽儏'
+        }],
+        content2: [{
+          required: true, message: '璇峰~鍐欏瘜鏂囨湰2璇︽儏'
         }],
         uploadImgs: [{
           required: true, message: '璇烽�夋嫨鍥剧墖'
diff --git a/src/pages/demo/index.vue b/src/pages/demo/index.vue
index 860d081..4491e16 100644
--- a/src/pages/demo/index.vue
+++ b/src/pages/demo/index.vue
@@ -67,7 +67,7 @@
     </el-table>
 
     <!-- 鏂板&缂栬緫 -->
-    <el-dialog v-el-drag-dialog :title="dialogData.type=='add'?'鏂板鍖婚櫌绉戝':'缂栬緫鍖婚櫌绉戝'" width="500px" :visible.sync="dialogVisible" append-to-body :before-close="hideDialog">
+    <el-dialog v-el-drag-dialog :title="dialogData.type=='add'?'鏂板鍖婚櫌绉戝':'缂栬緫鍖婚櫌绉戝'" width="500px" :visible.sync="dialogVisible" append-to-body :before-close="hideDialog" :close-on-click-modal="false">
       <el-form ref="refDialog" :model="dialogData" label-width="110px" :rules="rules" size="small">
         <el-form-item label="鍚嶇О" prop="name">
           <el-input v-model="dialogData.name" placeholder="璇疯緭鍏ュ悕绉�" maxlength="50" />
@@ -220,6 +220,9 @@
           }
         }, () => {
           this.$messageSuc(text + '鎴愬姛')
+        }, (res) => {
+          item.isUp = item.isUp === 1 ? 0 : 1
+          this.$messageError(res.msg)
         })
       }).catch(() => {
         item.isUp = item.isUp === 1 ? 0 : 1
diff --git a/src/pages/demo/list.vue b/src/pages/demo/list.vue
index e110d7e..b464184 100644
--- a/src/pages/demo/list.vue
+++ b/src/pages/demo/list.vue
@@ -76,7 +76,7 @@
     </el-table>
 
     <!-- 鏂板&缂栬緫 -->
-    <el-dialog v-el-drag-dialog :title="dialogData.type=='add'?'鏂板鍖婚櫌绉戝':'缂栬緫鍖婚櫌绉戝'" width="500px" :visible.sync="dialogVisible" append-to-body :before-close="hideDialog">
+    <el-dialog v-el-drag-dialog :title="dialogData.type=='add'?'鏂板鍖婚櫌绉戝':'缂栬緫鍖婚櫌绉戝'" width="500px" :visible.sync="dialogVisible" append-to-body :before-close="hideDialog" :close-on-click-modal="false">
       <el-form ref="refDialog" :model="dialogData" label-width="110px" :rules="rules" size="small">
         <el-form-item label="鍚嶇О" prop="name">
           <el-input v-model="dialogData.name" placeholder="璇疯緭鍏ュ悕绉�" maxlength="50" />
@@ -237,6 +237,9 @@
           }
         }, () => {
           this.$messageSuc(text + '鎴愬姛')
+        }, (res) => {
+          item.isUp = item.isUp === 1 ? 0 : 1
+          this.$messageError(res.msg)
         })
       }).catch(() => {
         item.isUp = item.isUp === 1 ? 0 : 1
diff --git a/src/pages/system/admin.vue b/src/pages/system/admin.vue
index 478604d..322edf3 100644
--- a/src/pages/system/admin.vue
+++ b/src/pages/system/admin.vue
@@ -86,7 +86,7 @@
     />
 
     <!-- 鏂板&缂栬緫 -->
-    <el-dialog v-el-drag-dialog :title="adminDialogData.type==='add'?'鏂板绠$悊鍛�':'缂栬緫绠$悊鍛�'" width="500px" :visible.sync="adminDialogVisible" append-to-body>
+    <el-dialog v-el-drag-dialog :title="adminDialogData.type==='add'?'鏂板绠$悊鍛�':'缂栬緫绠$悊鍛�'" width="500px" :visible.sync="adminDialogVisible" append-to-body :close-on-click-modal="false">
       <el-form ref="adminDialog" :model="adminDialogData" label-width="80px" :rules="rules" size="small">
         <el-form-item label="鍚嶇О" prop="name">
           <el-input v-model="adminDialogData.name" placeholder="璇疯緭鍏ュ悕绉�" maxlength="50" />
diff --git a/src/pages/system/banner.vue b/src/pages/system/banner.vue
index 1fb64b5..9cceb20 100644
--- a/src/pages/system/banner.vue
+++ b/src/pages/system/banner.vue
@@ -58,7 +58,7 @@
     />
 
     <!-- 鏂板&缂栬緫 -->
-    <el-dialog v-el-drag-dialog :title="dialogData.type=='add'?'鏂板杞挱鍥�':'缂栬緫杞挱鍥�'" width="500px" :visible.sync="isShowDialog" append-to-body :before-close="hideDialog">
+    <el-dialog v-el-drag-dialog :title="dialogData.type=='add'?'鏂板杞挱鍥�':'缂栬緫杞挱鍥�'" width="500px" :visible.sync="isShowDialog" append-to-body :before-close="hideDialog" :close-on-click-modal="false">
       <el-form :ref="formName" :model="dialogData" label-width="120px" :rules="rules" size="small">
         <el-form-item label="鎺掑簭鍙凤細" prop="orderNum">
           <el-input v-model="dialogData.orderNum" placeholder="璇疯緭鍏ユ帓搴忓彿" maxlength="10" />

--
Gitblit v1.8.0