chenrui 4 лет назад
Родитель
Сommit
3438d4a688

+ 2 - 1
.env

@@ -1,6 +1,7 @@
 NODE_ENV=production
 VUE_APP_PREVIEW=false
-VUE_APP_API_BASE_URL=https://it.zyucgj.com/zyit/
+VUE_APP_API_BASE_URL=http://ocs-admin.360arrow.com/ocs-admin
+VUE_APP_WX_BASE_URL=//ocs-admin.360arrow.com/ocs-admin/websocket/
 VUE_APP_VERSION=V1.0.1
 VUE_APP_PRO_NAME=箭冠运营中心系统
 VUE_APP_LOGO=@/assets/logo.png

+ 1 - 0
.env.development

@@ -1,6 +1,7 @@
 NODE_ENV=development
 VUE_APP_PREVIEW=true
 VUE_APP_API_BASE_URL=/api
+VUE_APP_WX_BASE_URL=//192.168.16.103:8602/ocs-admin/websocket/
 VUE_APP_VERSION=V1.0.1
 VUE_APP_PRO_NAME=箭冠运营中心系统-dev
 VUE_APP_LOGO=@/assets/logo.png

+ 1 - 0
.env.preview

@@ -1,6 +1,7 @@
 NODE_ENV=production
 VUE_APP_PREVIEW=true
 VUE_APP_API_BASE_URL=http://ocs-admin.360arrow.com.cn/ocs-admin
+VUE_APP_WX_BASE_URL=//ocs-admin.360arrow.com.cn/ocs-admin/websocket/
 VUE_APP_VERSION=V1.0.1
 VUE_APP_PRO_NAME=箭冠运营中心系统-pre
 VUE_APP_LOGO=@/assets/logo.png

+ 36 - 0
src/api/noticeUser.js

@@ -0,0 +1,36 @@
+import { axios } from '@/utils/request'
+
+//  公告 列表  有分页
+export const noticeUserList = (params) => {
+  const url = `/noticeUser/queryLike/${params.pageNo}/${params.pageSize}`
+  delete params.pageNo
+  delete params.pageSize
+  return axios({
+    url: url,
+    data: params,
+    method: 'post'
+  })
+}
+//  公告 详情
+export const noticeUserDetail = (params) => {
+  const url = `/notice/findById/${params.id}`
+  return axios({
+    url: url,
+    method: 'get'
+  })
+}
+
+// 获取通知消息未读数
+export const getUnreadCount = params => {
+  return axios({
+    url: 'noticeUser/queryNotReadCount',
+    method: 'post'
+  })
+}
+// 设置已读消息
+export const hasRead = id => {
+  return axios({
+    url: `noticeUser/setRead/${id}`,
+    method: 'get'
+  })
+}

+ 53 - 10
src/components/NoticeIcon/NoticeIcon.vue

@@ -1,5 +1,5 @@
 <template>
-  <a-popover
+  <!-- <a-popover
     v-model="visible"
     trigger="click"
     placement="bottomRight"
@@ -45,10 +45,16 @@
         <a-icon style="font-size: 16px; padding: 4px" type="bell" />
       </a-badge>
     </span>
-  </a-popover>
+  </a-popover> -->
+  <span @click="fetchNotice" class="header-notice" ref="noticeRef">
+    <a-badge :count="unreadCount">
+      <a-icon style="font-size: 16px; padding: 0 5px" type="bell" />
+    </a-badge>
+  </span>
 </template>
 
 <script>
+import { mapGetters, mapActions } from 'vuex'
 export default {
   name: 'HeaderNotice',
   data () {
@@ -57,17 +63,54 @@ export default {
       visible: false
     }
   },
+  computed: {
+    ...mapGetters([
+      'unreadCount'
+    ]),
+    unreadCount () {
+      return this.$store.getters.unreadCount
+    },
+    // 接受到ws消息
+    wsMessage () {
+      return this.$store.getters.wsMessageData()
+    }
+  },
+  mounted () {
+    // webSocket 连接
+    this.WEBSOCKET_INIT()
+  },
+  destroyed () {
+    this.WEBSOCKET_CLOSE()
+  },
   methods: {
+    ...mapActions([
+      'WEBSOCKET_INIT',
+      'WEBSOCKET_CLOSE',
+      'updateUnreadcount'
+    ]),
     fetchNotice () {
-      if (!this.visible) {
-        this.loading = true
-        setTimeout(() => {
-          this.loading = false
-        }, 2000)
-      } else {
-        this.loading = false
+      // if (!this.visible) {
+      //   this.loading = true
+      //   setTimeout(() => {
+      //     this.loading = false
+      //   }, 2000)
+      // } else {
+      //   this.loading = false
+      // }
+      // this.visible = !this.visible
+      this.$router.push({ path: '/notice' })
+    }
+  },
+  watch: {
+    // 监听ws消息通知
+    wsMessage (a, b) {
+      if (a !== b && a) {
+        console.log(a)
+        // 新未读消息
+        if (a.type == 'no_read_count') {
+          this.updateUnreadcount(a.data.no_read_count)
+        }
       }
-      this.visible = !this.visible
     }
   }
 }

+ 3 - 1
src/components/tools/UserMenu.vue

@@ -1,6 +1,7 @@
 <template>
   <div class="user-wrapper">
     <div class="content-box">
+		<notice-icon class="action"/>
       <!-- <a href="https://pro.loacg.com/docs/getting-started" target="_blank">
         <span class="action">
           <a-icon type="question-circle-o"></a-icon>
@@ -32,7 +33,7 @@
             <span>测试</span>
           </a-menu-item> -->
           <a-menu-divider/>
-          <a-menu-item key="3">
+          <a-menu-item key="1">
             <a href="javascript:;" @click="handleLogout">
               <a-icon type="logout"/>
               <span>退出登录</span>
@@ -67,6 +68,7 @@ export default {
       this.$confirm({
         title: '提示',
         content: '真的要注销登录吗 ?',
+        centered: true,
         onOk: () => {
           return this.Logout({}).then(() => {
             setTimeout(() => {

+ 11 - 0
src/config/router.config.js

@@ -1642,6 +1642,17 @@ export const asyncRouterMap = [
             ]
           }
         ]
+      },
+      // 公告
+      {
+        path: '/notice',
+        name: 'notice',
+        component: () => import(/* webpackChunkName: "home" */ '@/views/setting/notice/list'),
+        meta: {
+          title: '公告',
+          icon: 'bell'
+        },
+        hidden: true
       }
     ]
   },

+ 3 - 1
src/store/index.js

@@ -3,6 +3,7 @@ import Vuex from 'vuex'
 
 import app from './modules/app'
 import user from './modules/user'
+import socket from './modules/websocketStore'
 
 // default router permission control
 import permission from './modules/permission'
@@ -17,7 +18,8 @@ export default new Vuex.Store({
   modules: {
     app,
     user,
-    permission
+    permission,
+    socket
   },
   state: {
 

+ 121 - 0
src/store/modules/websocketStore.js

@@ -0,0 +1,121 @@
+// import Vue from 'vue'
+import { noticeUserList, hasRead, getUnreadCount } from '@/api/noticeUser'
+export default {
+  state: {
+    websock: null,
+    wsMessageData: null,
+    wsMessageHomeData: null,
+    unreadCount: 0,
+    updateMessage: null, // 判断是否刷新未读消息数据
+    heartInterId: null
+  },
+  getters: {
+    wsMessageData: state => {
+      return () => state.wsMessageData
+    },
+    wsMessageHomeData: state => {
+      return () => state.wsMessageHomeData
+    },
+    unreadCount: state => state.unreadCount,
+    updateMessage: state => {
+      return () => state.updateMessage
+    }
+  },
+  mutations: {
+    WEBSOCKET_INIT (state, url) {
+      const _this = this
+      const baseUrl = process.env.VUE_APP_WX_BASE_URL
+      const sid = 'pc/u' + this.getters.userInfo.userid + 'epc'
+      const protocol = process.env.NODE_ENV == 'production' ? (window.location.protocol == 'http:' ? 'ws:' : 'wss:') : 'ws:'
+      const wsUrl = protocol + baseUrl + sid
+      console.log(process.env.NODE_ENV, wsUrl)
+      state.websock = new WebSocket(wsUrl)
+      state.websock.onopen = function () {
+        console.log('连接成功!')
+        _this.dispatch('getUnreadMessageCount')
+      }
+      state.websock.onmessage = function (e) {
+        console.log('ws接收!', e.data)
+        if (e.data != 'pong') {
+          console.log('进来----------')
+          state.wsMessageData = JSON.parse(e.data)
+        }
+      }
+      state.websock.οnerrοr = function (e) { // 错误
+        console.log('ws错误!', e)
+      }
+      state.websock.onclose = function (e) { // 关闭
+        console.log('ws关闭!', e)
+      }
+
+      // 发送心跳包
+      state.heartInterId = setInterval(function () {
+        console.log('ws发送心跳!')
+        if (state.websock.readyState == 3) {
+          clearInterval(state.heartInterId)
+          _this.commit('WEBSOCKET_INIT', url)
+        }
+        state.websock.send('ping')
+      }, 10000)
+    },
+    WEBSOCKET_CLOSE (state) {
+      state.websock.close()
+      clearInterval(state.heartInterId)
+    },
+    WEBSOCKET_SEND (state, p) {
+      console.log('ws发送!', p)
+      state.websock.send(p)
+    },
+    UPDATE_HOME (state, p) {
+      state.wsMessageHomeData = p
+    },
+    setMessageCount (state, count) {
+      state.unreadCount = count
+    }
+  },
+  actions: {
+    WEBSOCKET_INIT ({ commit }, url) {
+      commit('WEBSOCKET_INIT', url)
+    },
+    WEBSOCKET_SEND ({ commit }, p) {
+      commit('WEBSOCKET_SEND', p)
+    },
+    UPDATE_HOME ({ commit }, p) {
+      commit('UPDATE_HOME', p)
+    },
+    WEBSOCKET_CLOSE ({ commit }) {
+      commit('WEBSOCKET_CLOSE')
+    },
+    // 此方法用来获取未读消息条数,接口只返回数值,不返回消息列表
+    getUnreadMessageCount ({ state, commit }) {
+      getUnreadCount().then(res => {
+        console.log(res, '---------------请求未读消息', res.data ? res.data.no_read_count : 0)
+        commit('setMessageCount', res.data ? res.data.no_read_count : 0)
+      })
+    },
+    // 获取消息列表,其中包含未读、已读、回收站三个列表
+    getMessageList ({ state, commit }, params) {
+      return new Promise((resolve, reject) => {
+        noticeUserList(params).then(res => {
+          resolve(res)
+        }).catch(error => {
+          reject(error)
+        })
+      })
+    },
+    // 把一个未读消息标记为已读
+    hasRead ({ state, commit }, { msg_id }) {
+      return new Promise((resolve, reject) => {
+        hasRead(msg_id).then((res) => {
+          resolve(res)
+        }).catch(error => {
+          reject(error)
+        })
+      })
+    },
+    // 更新未读数
+    updateUnreadcount ({ state, commit }, count) {
+      commit('setMessageCount', count)
+    }
+  }
+}

+ 192 - 0
src/views/setting/notice/detailModal.vue

@@ -0,0 +1,192 @@
+<template>
+  <a-modal
+    centered
+    class="noticeDetail-modal"
+    :footer="null"
+    :maskClosable="false"
+    title="公告详情"
+    v-model="isShow"
+    @cancle="isShow=false"
+    width="80%">
+    <div v-if="detailsData">
+      <a-descriptions bordered size="small" :column="2">
+        <a-descriptions-item label="类别">{{ typeDictValue }}</a-descriptions-item>
+        <a-descriptions-item label="可见性">{{ toAppNameDictValue }}</a-descriptions-item>
+        <a-descriptions-item label="公告标题">{{ detailsData.title||'--' }}</a-descriptions-item>
+        <a-descriptions-item label="发布时间">{{ detailsData.releaseDate||'--' }}</a-descriptions-item>
+        <a-descriptions-item label="公告内容" :span="2">
+          <div v-html="detailsData.content" class="editor-box"></div>
+        </a-descriptions-item>
+        <a-descriptions-item label="图片" :span="2">
+          <div style="display: flex;">
+            <p class="pic-box" v-for="(item,index) in imgPathsArr" :key="index">
+              <img :src="item" title="点击查看大图" class="productPic" @click="getPreview(item)" />
+            </p>
+          </div>
+          <span v-if="imgPathsArr.length == 0">--</span>
+        </a-descriptions-item>
+        <a-descriptions-item label="附件" :span="2">
+          <p style="margin: 0 0 5px;" v-for="(item,index) in attachPathsArr" :key="index">
+            <a :href="item.filePath" :download="item.fileName" class="attachLink">{{ item.fileName }}</a>
+          </p>
+          <span v-if="attachPathsArr.length == 0">--</span>
+        </a-descriptions-item>
+      </a-descriptions>
+      <div class="btn-cont">
+        <a-button id="noticeDetail-back" @click="isShow = false" style="margin-left: 15px;">关闭</a-button>
+      </div>
+    </div>
+    <a-modal :visible="previewVisible" :footer="null" centered :maskClosable="false" @cancel="previewVisible=false">
+      <img alt="example" style="width: 100%" :src="previewImage" />
+    </a-modal>
+  </a-modal>
+</template>
+
+<script>
+import { STable } from '@/components'
+import { noticeUserDetail } from '@/api/noticeUser'
+export default {
+  name: 'NoticeDetailModal',
+  components: { STable },
+  props: {
+    openModal: { //  弹框显示状态
+      type: Boolean,
+      default: false
+    },
+    itemId: {
+      type: [Number, String],
+      default: ''
+    }
+  },
+  computed: {
+    typeDictValue () { //  类别  数据字典name
+      let str = ''
+      if (this.detailsData.type == 'notity') {
+        str = '公告'
+      } else if (this.detailsData.type == 'news_company') {
+        str = '企业新闻'
+      } else if (this.detailsData.type == 'news_trade') {
+        str = '行业咨询'
+      } else {
+        str = '--'
+      }
+      return str
+    },
+    toAppNameDictValue () { //  可见性  数据字典name
+      let str = ''
+      if (this.detailsData.toAppName == '1,2') {
+        str = '所有'
+      } else if (this.detailsData.toAppName == '2') {
+        str = '经销商'
+      } else if (this.detailsData.toAppName == '1') {
+        str = '总部'
+      } else {
+        str = '--'
+      }
+      return str
+    },
+    imgPathsArr () { //  图片数组
+      return this.detailsData.imgPaths ? this.detailsData.imgPaths.split(',') : []
+    },
+    attachPathsArr () { //  附件数组
+      return this.detailsData.attachList ? this.detailsData.attachList : []
+    }
+  },
+  data () {
+    return {
+      isShow: this.openModal, //  是否打开弹框
+      detailsData: null, //  详情数据
+      previewVisible: false,
+      previewImage: ''
+    }
+  },
+  methods: {
+    //  获取详情
+    getDetail () {
+      noticeUserDetail({ id: this.itemId }).then(res => {
+        if (res.status == 200) {
+          this.detailsData = res.data
+        } else {
+          this.detailsData = null
+        }
+      })
+    },
+    // 查看大图
+    getPreview (url) {
+      this.previewImage = url
+      this.previewVisible = true
+    }
+  },
+  watch: {
+    //  父页面传过来的弹框状态
+    openModal (newValue, oldValue) {
+      this.isShow = newValue
+    },
+    //  重定义的弹框状态
+    isShow (newValue, oldValue) {
+      if (!newValue) {
+        this.detailData = null
+        this.$emit('close')
+      }
+    },
+    itemId (newValue, oldValue) {
+      if (this.isShow && newValue) {
+        this.getDetail()
+      }
+    }
+  }
+}
+</script>
+
+<style lang="less">
+  .noticeDetail-modal{
+    .ant-modal-body {
+    	padding: 40px 40px 24px;
+    }
+    .ant-descriptions-item-label.ant-descriptions-item-colon{
+      width: 90px;
+    }
+    .pic-box{
+      width: 100px;
+      height: 100px;
+      margin-right: 10px;
+      display: inline-flex;
+      align-items: center;
+      border: 1px dashed #d9d9d9;
+      border-radius: 4px;
+      padding: 3px;
+      .productPic{
+        cursor: pointer;
+        max-width: 100%;
+        max-height: 100%;
+        display: block;
+        margin: 0 auto;
+      }
+    }
+    .editor-box{  //  编辑器基本样式初始化
+      p{
+        margin: 0;
+        padding: 0;
+        ul, li{
+          list-style: none;
+          padding: 0;
+          margin: 0;
+        }
+        img{
+          max-width: 100%;
+        }
+      }
+    }
+    .btn-cont {
+      text-align: center;
+      margin: 35px 0 10px;
+    }
+    .attachLink{
+      display: inline-block;
+      color: rgba(0, 0, 0, 0.65);
+      &:hover{
+        text-decoration: underline;
+      }
+    }
+  }
+</style>

+ 196 - 0
src/views/setting/notice/list.vue

@@ -0,0 +1,196 @@
+<template>
+  <a-card size="small" :bordered="false" class="noticeList-wrap">
+    <!-- 搜索条件 -->
+    <div class="table-page-search-wrapper">
+      <a-form layout="inline" @keyup.enter.native="handelSearch(1)">
+        <a-row :gutter="15">
+          <a-col :md="6" :sm="24">
+            <a-form-item label="发布时间">
+              <rangeDate ref="rangeDate" @change="dateChange" />
+            </a-form-item>
+          </a-col>
+          <a-col :md="6" :sm="24">
+            <a-form-item label="公告名称">
+              <a-input id="noticeList-title" v-model.trim="queryParam.notice.title" allowClear placeholder="请输入公告名称"/>
+            </a-form-item>
+          </a-col>
+          <a-col :md="6" :sm="24">
+            <a-button type="primary" @click="handelSearch(1)" :disabled="disabled" id="noticeList-refresh">查询</a-button>
+            <a-button style="margin-left: 8px" @click="resetSearchForm" :disabled="disabled" id="noticeList-reset">重置</a-button>
+          </a-col>
+        </a-row>
+      </a-form>
+    </div>
+    <!-- 列表 -->
+    <a-table
+      class="sTable"
+      ref="table"
+      size="default"
+      :rowKey="(record) => record.id"
+      :columns="columns"
+      :dataSource="loadData"
+      :loading="listLoading"
+      :pagination="paginationProps"
+      bordered>
+      <!-- 操作 -->
+      <template slot="action" slot-scope="text, record">
+        <a-badge :count="record.readFlag=='0'? 1 : 0" dot>
+          <a-button size="small" type="primary" class="button-success" @click="handleDetail(record)" id="noticeList-detail-btn">详情</a-button>
+        </a-badge>
+      </template>
+    </a-table>
+    <!-- 公告详情 -->
+    <notice-detail-modal :openModal="openModal" :itemId="itemId" @close="closeModal" />
+  </a-card>
+</template>
+
+<script>
+import { STable } from '@/components'
+import rangeDate from '@/views/common/rangeDate.vue'
+import { mapActions } from 'vuex'
+import noticeDetailModal from './detailModal.vue'
+export default {
+  components: { STable, noticeDetailModal, rangeDate },
+  data () {
+    return {
+      queryParam: { //  查询条件
+        beginDate: '',
+        endDate: '',
+        notice: {
+          title: ''
+        }
+      },
+      disabled: false, //  查询、重置按钮是否可操作
+      columns: [
+        { title: '序号', dataIndex: 'no', width: 80, align: 'center' },
+        { title: '发布时间', dataIndex: 'createDate', align: 'center', customRender: function (text) { return text || '--' } },
+        { title: '公告名称', dataIndex: 'notice.title', align: 'center', customRender: function (text) { return text || '--' } },
+        { title: '操作', scopedSlots: { customRender: 'action' }, width: 200, align: 'center' }
+      ],
+      loadData: [],
+      pageNo: 1, //  分页页码
+      pageSize: 10, //  分页 每页多少条
+      paginationProps: {
+        showSizeChanger: true, //  是否可以改变 pageSize
+        total: 0, //  分页总条数
+        current: 1,
+        onShowSizeChange: (current, pageSize) => this.changePageSize(current, pageSize),
+        onChange: (current) => this.changePage(current)
+      },
+      listLoading: false,
+      openModal: false, //  查看客户详情  弹框
+      itemId: '' //  当前id
+    }
+  },
+  computed: {
+    // 接受到ws消息
+    wsMessage () {
+      return this.$store.getters.wsMessageData()
+    }
+  },
+  watch: {
+    // 监听ws消息通知
+    wsMessage (a, b) {
+      if (a !== b && a) {
+        // 新未读消息
+        if (a.type == 'no_read_count') {
+          this.resetSearchForm()
+        }
+      }
+    }
+  },
+  methods: {
+    ...mapActions([
+      'getMessageList', // 查询列表
+      'hasRead' // 设置已读
+    ]),
+    //  时间  change
+    dateChange (date) {
+      this.queryParam.beginDate = date[0]
+      this.queryParam.endDate = date[1]
+    },
+    // 查询列表
+    handelSearch (pageNo) {
+      this.pageNo = pageNo || this.pageNo
+      this.disabled = true
+      const params = {
+        pageNo: this.pageNo,
+        pageSize: this.pageSize,
+        notice: {
+          title: this.queryParam.notice.title
+        },
+        beginDate: this.queryParam.beginDate,
+        endDate: this.queryParam.endDate
+      }
+      // 开始查询
+      this.listLoading = true
+      this.getMessageList(params).then((res) => {
+        this.listLoading = false
+        if (res.status == 200) {
+          const data = res.data
+          this.paginationProps.total = Number(res.data.count) || 0
+          this.paginationProps.current = data.pageNo
+          const no = (data.pageNo - 1) * data.pageSize
+          for (var i = 0; i < data.list.length; i++) {
+            data.list[i].no = no + i + 1
+          }
+          this.loadData = data.list
+          this.disabled = false
+        } else {
+          this.paginationProps.total = 0
+          this.paginationProps.current = 1
+          this.loadData = []
+        }
+      })
+    },
+    //  分页  一页多少条change
+    changePageSize (current, pageSize) {
+      this.pageNo = current
+      this.pageSize = pageSize
+      this.handelSearch()
+    },
+    //  分页 页码change
+    changePage (current) {
+      this.pageNo = current
+      this.handelSearch()
+    },
+    //  重置
+    resetSearchForm () {
+      this.$refs.rangeDate.resetDate()
+      this.queryParam.beginDate = ''
+      this.queryParam.endDate = ''
+      this.queryParam.notice.title = undefined
+      this.pageNo = 1
+      this.pageSize = 10
+      this.paginationProps.total = 0
+      this.paginationProps.current = 1
+      this.handelSearch(1)
+    },
+    //  详情
+    handleDetail (row) {
+      this.itemId = row.notice.id
+      this.openModal = true
+      // 设置已读
+      if (row.readFlag == '0') {
+        this.hasRead({ msg_id: row.id }).then(res => {
+          // 刷新列表
+          if (res.status == 200) {
+            this.handelSearch()
+          }
+        })
+      }
+    },
+    //  关闭弹框
+    closeModal () {
+      this.itemId = ''
+      this.openModal = false
+    }
+  },
+  beforeRouteEnter (to, from, next) {
+    next(vm => {
+      vm.openModal = false
+      vm.handelSearch(1)
+    })
+  }
+}
+</script>