Forráskód Böngészése

富文本活动描述

zuy 1 éve
szülő
commit
16e67679fd

BIN
admin-web/src/assets/phone.jpeg


+ 249 - 0
admin-web/src/components/form/ExtEditor.vue

@@ -0,0 +1,249 @@
+<template>
+  <div style="border: 1px solid #ccc">
+    <Toolbar
+        style="border-bottom: 1px solid #ccc"
+        :editor="editorRef"
+        :defaultConfig="toolbarConfig"
+        mode="simple"
+    />
+    <Editor
+        style="height: 500px; overflow-y: hidden;"
+        v-model="valueHtml"
+        :defaultConfig="editorConfig"
+        mode="simple"
+        @onDestroyed="handleDestroyed"
+        @onChange="handleChange"
+        @onCreated="handleCreated"
+    />
+  </div>
+</template>
+<script lang="ts" setup>
+import '@wangeditor/editor/dist/css/style.css' // 引入 css
+import {onBeforeUnmount, onMounted, ref, shallowRef} from 'vue'
+import {Editor, Toolbar} from '@wangeditor/editor-for-vue'
+import {IEditorConfig} from '@wangeditor/editor'
+import {Session} from "/@/utils/storage";
+
+
+// 编辑器实例,必须用 shallowRef
+const editorRef = shallowRef()
+
+const emit = defineEmits(['update:modelValue'])
+
+const props = defineProps({
+  modelValue: {
+    type: String,
+    default: ''
+  }
+})
+
+// 内容 HTML
+const valueHtml = ref('<p>hello</p>')
+
+// 模拟 ajax 异步获取内容
+onMounted(() => {
+  setTimeout(() => {
+    valueHtml.value = '<p>模拟 Ajax 异步设置内容</p>'
+  }, 1500)
+})
+
+const toolbarConfig = {}
+
+const editorConfig: Partial<IEditorConfig> = {  // TS 语法
+  MENU_CONF: {},
+  placeholder: '请输入内容...'
+}
+
+let url = import.meta.env.VITE_API_URL;
+if (!url) {
+  url = `${location.origin}/admin/`;
+}
+let at = Session.get('accessToken')
+editorConfig.MENU_CONF['uploadImage'] = {
+  fieldName: 'file',
+  server: `${url}file/upload`,
+  // 自定义增加 http  header
+  headers: {
+    accessToken: at
+  },
+
+  // 上传之前触发
+  onBeforeUpload(file: File) { // TS 语法
+    // onBeforeUpload(file) {    // JS 语法
+    // file 选中的文件,格式如 { key: file }
+    return file
+
+    // 可以 return
+    // 1. return file 或者 new 一个 file ,接下来将上传
+    // 2. return false ,不上传这个 file
+  },
+
+  // 上传进度的回调函数
+  onProgress(progress: number) {  // TS 语法
+    // onProgress(progress) {       // JS 语法
+    // progress 是 0-100 的数字
+    console.log('progress', progress)
+  },
+
+  // 单个文件上传成功之后
+  onSuccess(file: File, res: any) {  // TS 语法
+    // onSuccess(file, res) {          // JS 语法
+    console.log(`${file.name} 上传成功`, res)
+  },
+
+  // 单个文件上传失败
+  onFailed(file: File, res: any) {   // TS 语法
+                                     // onFailed(file, res) {           // JS 语法
+    console.log(`${file.name} 上传失败`, res)
+  },
+
+  // 上传错误,或者触发 timeout 超时
+  onError(file: File, err: any, res: any) {  // TS 语法
+    // onError(file, err, res) {               // JS 语法
+    console.log(`${file.name} 上传出错`, err, res)
+  },
+}
+
+// 组件销毁时,也及时销毁编辑器
+onBeforeUnmount(() => {
+  const editor = editorRef.value
+  if (editor == null) return
+  editor.destroy()
+})
+
+const handleCreated = (editor) => {
+  editorRef.value = editor // 记录 editor 实例,重要!
+}
+
+const handleChange = (editor) => {
+  emit('update:modelValue', editor.getHtml())
+}
+
+const handleDestroyed = (editor) => {
+  console.log('destroyed', editor)
+}
+
+/*
+export default {
+  name: 'rich-text',
+  props: {
+    value: String
+  },
+  data() {
+    return {
+      content: '',
+      editor: {},
+      toolId: '',
+      editorId: ''
+    }
+  },
+  computed: {
+
+  },
+  watch: {
+    value(val) {
+      if (val) {
+        if (val !== this.content) {
+          this.setContent(val);
+        }
+      } else {
+        this.setContent('');
+      }
+    },
+
+    content(val) {
+      this.$emit('input', val);
+    }
+  },
+  mounted() {
+    this.toolId = this.randomString(12)
+    this.editorId = this.randomString(12)
+    this.content = this.value
+    this.$nextTick(() => {
+      this.editor = new wangEditor(this.$refs[this.toolId], this.$refs[this.editorId])
+      this.editor.customConfig.onchange = (html) => {
+        // 监控变化,同步更新到 textarea
+        this.content = html
+      }
+      //配置图片上传服务器接口
+      this.editor.customConfig.uploadImgServer = process.env.VUE_APP_BASE_API + '/file/upload/customizeDirUpload'
+      // 文件名
+      this.editor.customConfig.uploadFileName = 'file'
+      // 配置上传图片请求头部
+      // this.editor.customConfig.uploadImgHeaders = {}
+      // 上传图片钩子函数
+      this.editor.customConfig.uploadImgHooks = {
+        before: function(xhr, editor, files) {
+          // 图片上传之前触发
+          // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象,files 是选择的图片文件
+
+          // 如果返回的结果是 {prevent: true, msg: 'xxxx'} 则表示用户放弃上传
+          // return {
+          //     prevent: true,
+          //     msg: '放弃上传'
+          // }
+        },
+        success: function(xhr, editor, result) {
+          // 图片上传并返回结果,图片插入成功之后触发
+          // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象,result 是服务器端返回的结果
+          // alert('成功')
+        },
+        fail: function(xhr, editor, result) {
+          // 图片上传并返回结果,但图片插入错误时触发
+          // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象,result 是服务器端返回的结果
+        },
+        error: function(xhr, editor) {
+          // 图片上传出错时触发
+          // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象
+        },
+        timeout: function(xhr, editor) {
+          // 图片上传超时时触发
+          // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象
+        },
+
+        // 如果服务器端返回的不是 {errno:0, data: [...]} 这种格式,可使用该配置
+        // (但是,服务器端返回的必须是一个 JSON 格式字符串!!!否则会报错)
+        customInsert: function(insertImg, result, editor) {
+          // 图片上传并返回结果,自定义插入图片的事件(而不是编辑器自动插入图片!!!)
+          // insertImg 是插入图片的函数,editor 是编辑器对象,result 是服务器端返回的结果
+
+          // 举例:假如上传图片成功后,服务器端返回的是 {url:'....'} 这种格式,即可这样插入图片:
+          var url = result.data
+          insertImg(url)
+
+          // result 必须是一个 JSON 格式字符串!!!否则报错
+        }
+      }
+      this.editor.create()
+      this.editor.txt.html(this.value)
+    })
+  },
+  methods: {
+    // 生成随机字符串id
+    randomString(len) {
+      len = len || 32
+      let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678' /!** **默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****!/
+      let maxPos = $chars.length
+      let pwd = ''
+      for (let i = 0; i < len; i++) {
+        pwd += $chars.charAt(Math.floor(Math.random() * maxPos))
+      }
+      return 'a' + pwd
+    },
+    setContent(val) {
+      this.editor.txt.html(val)
+    }
+  }
+}*/
+</script>
+
+<style scoped>
+.toolbar {
+  border: 1px solid #ccc;
+}
+
+.text {
+  border: 1px solid #ccc;
+  min-height: 200px;
+}
+</style>

+ 30 - 19
admin-web/src/views/admin/activity/dialog.vue

@@ -31,16 +31,23 @@
           </el-input>
         </el-form-item>
         <el-form-item label="活动描述" prop="remark" class="w100">
-          <el-input
-              maxlength="500"
-              show-word-limit
-              type="textarea"
-              :rows="3"
-              v-model.trim="state.ruleForm.activityDesc"
-              placeholder="活动描述"
-              clearable
-              class="w100">
-          </el-input>
+          <!--          <el-input
+                        maxlength="500"
+                        show-word-limit
+                        type="textarea"
+                        :rows="3"
+                        v-model.trim="state.ruleForm.activityDesc"
+                        placeholder="活动描述"
+                        clearable
+                        class="w100">
+                    </el-input>-->
+
+
+          <ext-editor class="w100" v-model="state.ruleForm.activityDesc"></ext-editor>
+          <el-divider></el-divider>
+          <div style="width:430px;height:850px;background-image: url('/@/assets/phone.jpeg');background-repeat: no-repeat;">
+            <div style="margin: 110px 40px;    height: 600px;    overflow: auto;" v-html="state.ruleForm.activityDesc"></div>
+          </div>
         </el-form-item>
         <el-form-item label="开始时间" prop="startTime">
           <ext-date-picker
@@ -70,15 +77,15 @@
               class="wd200 ">
           </ext-d-select>
         </el-form-item>
-<!--        <el-form-item label="目标用户" prop="targetUsers" v-if="state.ruleForm.discountType==='RechargeRights'">-->
-<!--          <ext-d-select-->
-<!--              v-model="state.ruleForm.targetUsers"-->
-<!--              placeholder="目标用户"-->
-<!--              type="Activity.targetUsers"-->
-<!--              clearable-->
-<!--              class="wd200 ">-->
-<!--          </ext-d-select>-->
-<!--        </el-form-item>-->
+        <!--        <el-form-item label="目标用户" prop="targetUsers" v-if="state.ruleForm.discountType==='RechargeRights'">-->
+        <!--          <ext-d-select-->
+        <!--              v-model="state.ruleForm.targetUsers"-->
+        <!--              placeholder="目标用户"-->
+        <!--              type="Activity.targetUsers"-->
+        <!--              clearable-->
+        <!--              class="wd200 ">-->
+        <!--          </ext-d-select>-->
+        <!--        </el-form-item>-->
 
         <!--        <el-form-item label="优惠允许叠加" prop="allowStacke">-->
         <!--          <ext-d-select-->
@@ -118,6 +125,7 @@
               clearable
               class="w100">
           </el-input>
+
         </el-form-item>
 
         <div class="sub-group-bottom"> 关联站点</div>
@@ -284,6 +292,7 @@
                   clearable
                   class="w100">
               </el-input>
+
             </el-form-item>
 
 
@@ -388,6 +397,7 @@ import ExtDatePicker from "/@/components/form/ExtDatePicker.vue";
 import ExtDSelect from "/@/components/form/ExtDSelect.vue";
 import ExtSelect from "/@/components/form/ExtSelect.vue";
 import ExtInputNumber from "/@/components/form/ExtInputNumber.vue";
+import ExtEditor from "/@/components/form/ExtEditor.vue";
 
 // 定义子组件向父组件传值/事件
 const emit = defineEmits(['refresh']);
@@ -447,6 +457,7 @@ const onCancel = () => {
 };
 // 提交
 const onSubmit = () => {
+  console.log(state.ruleForm)
   let rechargeRightsList = state.ruleForm.rechargeRightsList;
   if (!u.isEmptyOrNull(rechargeRightsList)) {
     for (let i = 0; i < rechargeRightsList.length; i++) {