Browse Source

始宁农业管理后台:内容管理-通知管理

panyong 2 years ago
parent
commit
f7d5e810a0

File diff suppressed because it is too large
+ 1 - 0
htmldev/shiningManage/public/lib/tinymce/tinymce.min.js


+ 112 - 0
htmldev/shiningManage/src/components/Tinymce/components/EditorImage.vue

@@ -0,0 +1,112 @@
+<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" append-to-body>
+      <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="/api/upload/img"
+        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) {
+      console.log(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.data.path
+          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>

+ 59 - 0
htmldev/shiningManage/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

+ 246 - 0
htmldev/shiningManage/src/components/Tinymce/index.vue

@@ -0,0 +1,246 @@
+<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'
+const tinymceCDN = '/lib/tinymce/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.baseURL = 'https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3'
+      window.tinymce.init({
+        selector: `#${this.tinymceId}`,
+        language: this.languageTypeList['zh'],
+        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>

+ 7 - 0
htmldev/shiningManage/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

+ 6 - 0
htmldev/shiningManage/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

+ 7 - 1
htmldev/shiningManage/src/router/modules/contentManage.js

@@ -10,9 +10,15 @@ const contentManage = {
   children: [
     {
       path: 'banner',
-      name: 'businessManageBanner',
+      name: 'contentManageBanner',
       component: () => import('@/views/contentManage/banner/index'),
       meta: { title: '轮播管理' }
+    },
+    {
+      path: 'notify',
+      name: 'contentManageNotify',
+      component: () => import('@/views/contentManage/notify/index'),
+      meta: { title: '通知管理' }
     }
   ]
 }

+ 153 - 0
htmldev/shiningManage/src/views/contentManage/notify/details.vue

@@ -0,0 +1,153 @@
+<template>
+  <div>
+    <el-dialog
+      :title="exData.id ? '编辑': '新增'"
+      :visible.sync="dialog"
+      width="900px"
+      :close-on-click-modal="false"
+      top="50px">
+      <el-form
+        ref="form"
+        :model="form"
+        :rules="formRules"
+        label-width="120px">
+        <el-form-item
+          prop="lunbo_name"
+          :rules="formRules.required"
+          label="标题:">
+          <el-input
+            v-model="form.lunbo_name"
+            placeholder="请输入标题"
+            clearable></el-input>
+        </el-form-item>
+        <el-form-item
+          :rules="formRules.required"
+          label="内容:">
+          <tinymce
+            :height="300"
+            v-model="form.content"
+            id='tinymce'></tinymce>
+        </el-form-item>
+        <el-form-item
+          prop="lunbo_status"
+          :rules="formRules.select"
+          label="状态:">
+          <el-select
+            style="width: 100%;"
+            v-model="form.lunbo_status"
+            filterable
+            clearable
+            placeholder="请选择是否显示">
+            <el-option
+              :label="item.name"
+              :value="item.value"
+              v-for="item in arrLunboStatus"
+              :key="item.value"></el-option>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer text-center">
+        <el-button @click="dialog = false">取 消</el-button>
+        <el-button type="primary" :disabled="booLock" @click="handleSubmit">确 定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import tinymce from '@/components/Tinymce'
+
+export default {
+  props: {
+    value: {
+      type: Boolean,
+      default: true
+    },
+    exData: {
+      type: Object,
+      default: function () {
+        return {}
+      }
+    }
+  },
+  components: {
+    tinymce
+  },
+  data() {
+    return {
+      dialog: !!this.value,
+      form: {
+        'lunbo_name': '', // 轮播图名称
+        'content': '',
+        'lunbo_status': '' // 状态(0下架1上架)
+      },
+      booLock: false
+    }
+  },
+  computed: {
+    arrLunboStatus() {
+      return this.$store.state.common.arrLunboStatus
+    }
+  },
+  methods: {
+    handleSubmit() {
+      const url = this.exData.id ? '/api/admin/tlunbo/modify' : '/api/admin/tlunbo/add'
+      this.$refs.form.validate(async valid => {
+        if (valid) {
+          const formData = JSON.parse(JSON.stringify(this.form))
+          const postData = {
+            ...formData
+          }
+          this.booLock = true
+          const data = await this.$fetch(url, postData)
+          this.booLock = false
+          if (data.code === 200) {
+            this.$message.success('提交成功')
+            this.$emit('success')
+            this.dialog = false
+          }
+        }
+      })
+    }
+  },
+  mounted() {
+    if (this.exData.id) {
+      this.$set(this.form, 'id', this.exData.id)
+      for (const key in this.exData) {
+        if (this.form.hasOwnProperty(key)) {
+          let value = this.exData[key]
+          if ((Array.isArray(value) && value.length >= 1) || (Object.prototype.toString.call(value) === '[object Object]') || (typeof value === 'string' && value) || typeof value === 'number') {
+            this.$set(this.form, key, value)
+          }
+        }
+      }
+    }
+  },
+  watch: {
+    dialog(val) {
+      if (!val) this.$emit('input', val)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.top-tip {
+  margin-top: -20px;
+  margin-bottom: 20px;
+}
+
+.af-put-line-radio {
+  display: flex;
+  align-items: center;
+
+  p.lalel {
+    padding: 0 10px;
+  }
+
+  p {
+    padding: 0;
+    margin: 0;
+  }
+}
+</style>

+ 166 - 0
htmldev/shiningManage/src/views/contentManage/notify/index.vue

@@ -0,0 +1,166 @@
+<template>
+  <div class="padding-20">
+    <div class="search-box">
+      <el-form ref="form"
+               :inline="true"
+               :model="searchForm"
+               clearable
+               class="mt-10">
+        <el-form-item>
+          <el-form-item label="标题:">
+            <el-input v-model="searchForm.lunbo_name" placeholder="请输入标题" clearable></el-input>
+          </el-form-item>
+          <el-form-item label="状态:">
+            <el-select
+              v-model="searchForm.lunbo_status"
+              filterable
+              clearable
+              placeholder="请选择状态">
+              <el-option
+                v-for="item in arrLunboStatus"
+                :key="item.value"
+                :label="item.name"
+                :value="item.value">
+              </el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="创建时间:">
+            <el-date-picker
+              :editable="false"
+              v-model="time"
+              @change="timearr => {timearr ? (searchForm.start_time = timearr[0] + ' 00:00:00', searchForm.end_time = timearr[1] + ' 23:59:59') : searchForm.start_time = searchForm.end_time = undefined}"
+              type="daterange"
+              value-format="yyyy-MM-dd"
+              start-placeholder="开始时间"
+              end-placeholder="结束时间"
+            ></el-date-picker>
+          </el-form-item>
+        </el-form-item>
+        <el-form-item class="ml-10">
+          <el-button icon="el-icon-search" type="primary" @click="searchSubmit">查询</el-button>
+        </el-form-item>
+        <el-form-item class="ml-10">
+          <el-button icon="el-icon-plus"
+                     type="primary"
+                     @click="add">新增
+          </el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+    <el-table
+      :data="tableData"
+      stripe
+      v-loading="tableLoading"
+      fit
+      class="marginT-10 order-table"
+      border
+      :max-height="vheight">
+      <el-table-column label="标题" prop="lunbo_name" show-overflow-tooltip></el-table-column>
+      <el-table-column label="内容" prop="lunbo_img_url" min-width="200" show-overflow-tooltip></el-table-column>
+      <el-table-column label="状态" prop="lunbo_status">
+        <template slot-scope="scope">
+          <p>{{ getLunboStatusText(scope.row.lunbo_status) }}</p>
+        </template>
+      </el-table-column>
+      <el-table-column label="发布时间" prop="created_at" min-width="160"></el-table-column>
+      <el-table-column label="操作" width="240">
+        <template slot-scope="scope">
+          <el-button type="text" @click="edit(scope.row)">编辑</el-button>
+          <el-button type="text" @click="edit(scope.row)">发布通知</el-button>
+          <el-button type="text" @click="edit(scope.row)">置顶推荐</el-button>
+          <el-button type="text" @click="del(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <el-pagination
+      class="marginT-20"
+      @size-change="handleSizeChange"
+      @current-change="handleCurrentChange"
+      :hide-on-single-page="true"
+      :current-page="page"
+      :page-size="page_size"
+      :page-sizes="[10, 20, 100, 200, 300, 400]"
+      background
+      layout="total, sizes, prev, pager, next, jumper"
+      :total="totalCount"/>
+    <detail v-if="detailsDialog.show"
+            v-model="detailsDialog.show"
+            :exData="detailsDialog.exData"
+            @success="init"></detail>
+  </div>
+</template>
+
+<script>
+import page from '@/mixin/page'
+import detail from './details'
+
+export default {
+  mixins: [page],
+  components: {
+    detail
+  },
+  data() {
+    return {
+      detailsDialog: {
+        show: false,
+        exData: {}
+      },
+      time: [],
+      searchForm: {},
+      tableData: [],
+      tableUrl: '/api/admin/tlunbo/list'
+    }
+  },
+  computed: {
+    arrLunboStatus() {
+      return this.$store.state.common.arrLunboStatus
+    },
+    arrLunboLinkType() {
+      return this.$store.state.common.arrLunboLinkType
+    }
+  },
+  methods: {
+    add() {
+      this.detailsDialog.exData = {}
+      this.detailsDialog.show = true
+    },
+    edit(row) {
+      this.detailsDialog.exData = row
+      this.detailsDialog.show = true
+    },
+    del(row) {
+      this.$confirm('确定要删除吗', '确认', {
+        type: 'warning'
+      }).then(async () => {
+        const data = await this.$fetch('/api/admin/tlunbo/delete', { id: row.id })
+        if (data.code === 200) {
+          this.$message.success('删除成功')
+          this.init()
+        }
+      }).catch(() => {
+      })
+    },
+    getLunboStatusText(value) {
+      const temp = this.arrLunboStatus.filter(item => item.value === value)
+      if (temp.length > 0) {
+        return temp[0].name
+      }
+      return ''
+    },
+    getLunboLinkTypeText(value) {
+      const temp = this.arrLunboLinkType.filter(item => item.value === value)
+      if (temp.length > 0) {
+        return temp[0].name
+      }
+      return ''
+    }
+  },
+  mounted() {
+    this.init()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

Some files were not shown because too many files changed in this diff