Pārlūkot izejas kodu

消息功能完成

yuwenjun 4 gadi atpakaļ
vecāks
revīzija
521a79e418

+ 6 - 3
.env.development

@@ -5,9 +5,12 @@ ENV = 'development'
 VUE_APP_BASE_API = 'https://zplma-b.caimei365.com'
 # VUE_APP_BASE_API = 'http://192.168.2.68:8012'
 
-
 # 文件上传 API接口地址
 VUE_APP_UPLOAD_API='https://zplma-b.caimei365.com'
 
-#二维码生成链接location
-VUE_APP_BASE_SERVER = 'https://www-b.caimei365.com'
+# 二维码生成链接location
+VUE_APP_BASE_SERVER = 'https://www-b.caimei365.com'
+
+# 消息接口地址 WebSocket
+VUE_APP_SOCKET_SERVER = 'wss://zplma-b.caimei365.com/websocket?sessionSource=zplm_admin'
+# VUE_APP_SOCKET_SERVER = 'ws://192.168.2.68:8012/websocket?sessionSource=zplm_admin'

+ 9 - 0
src/api/message.js

@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+export function getMessage(data) {
+  return request({
+    url: '/message/admin/list',
+    method: 'get',
+    data
+  })
+}

+ 19 - 20
src/layout/components/Navbar.vue

@@ -1,17 +1,21 @@
 <template>
   <div class="navbar">
-    <hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
+    <hamburger
+      id="hamburger-container"
+      :is-active="sidebar.opened"
+      class="hamburger-container"
+      @toggleClick="toggleSideBar"
+    />
 
     <breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
 
     <div class="right-menu">
-      <template v-if="device!=='mobile'">
+      <template v-if="device !== 'mobile'">
         <!-- <search id="header-search" class="right-menu-item" /> -->
-        <!-- <notice-todo /> -->
+        <notice-todo v-if="userIdentity === 1" />
         <!-- <error-log class="errLog-container right-menu-item hover-effect" /> -->
 
         <screenfull id="screenfull" class="right-menu-item hover-effect" />
-
       </template>
 
       <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
@@ -40,24 +44,19 @@ import Hamburger from '@/components/Hamburger'
 // import ErrorLog from '@/components/ErrorLog'
 import Screenfull from '@/components/Screenfull'
 // import Search from '@/components/HeaderSearch'
-// import NoticeTodo from './NoticeTodo'
+import NoticeTodo from './NoticeTodo'
 
 export default {
   components: {
     Breadcrumb,
     Hamburger,
     // ErrorLog,
-    Screenfull
-    // NoticeTodo
+    Screenfull,
+    NoticeTodo
     // Search
   },
   computed: {
-    ...mapGetters([
-      'sidebar',
-      'device',
-      'name',
-      'proxyInfo'
-    ]),
+    ...mapGetters(['sidebar', 'device', 'name', 'proxyInfo', 'userIdentity']),
     userName() {
       return this.name
     }
@@ -83,18 +82,18 @@ export default {
   overflow: hidden;
   position: relative;
   background: #fff;
-  box-shadow: 0 1px 4px rgba(0,21,41,.08);
+  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
 
   .hamburger-container {
     line-height: 46px;
     height: 100%;
     float: left;
     cursor: pointer;
-    transition: background .3s;
-    -webkit-tap-highlight-color:transparent;
+    transition: background 0.3s;
+    -webkit-tap-highlight-color: transparent;
 
     &:hover {
-      background: rgba(0, 0, 0, .025)
+      background: rgba(0, 0, 0, 0.025);
     }
   }
 
@@ -126,10 +125,10 @@ export default {
 
       &.hover-effect {
         cursor: pointer;
-        transition: background .3s;
+        transition: background 0.3s;
 
         &:hover {
-          background: rgba(0, 0, 0, .025)
+          background: rgba(0, 0, 0, 0.025);
         }
       }
     }
@@ -148,7 +147,7 @@ export default {
           border-radius: 10px;
           vertical-align: middle;
         }
-        span{
+        span {
           margin-left: 10px;
         }
         .el-icon-caret-bottom {

+ 94 - 31
src/layout/components/NoticeTodo/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="notice-container">
-    <div class="notice-btn">
+    <div class="notice-btn " :class="{msg:hasMessage}">
       <span class="el-icon-message-solid" @click="drawer = true" />
     </div>
     <el-drawer
@@ -13,35 +13,48 @@
       <!-- 侧边展开 -->
       <div class="drawer-content">
         <el-tabs v-model="activeName" :stretch="true" @tab-click="handleClick">
+          <!-- 全部 -->
           <el-tab-pane label="全部" name="first">
-            <ul>
-              <li class="dot">
-                <time>2021/05/28</time>
-                <span>XX新增了品牌授权信息</span>
-                <a href="#">去审核</a>
-              </li>
-              <li>
-                <time>2021/05/28</time>
-                <span>XX新增了品牌授权信息</span>
-                <a href="#">去审核</a>
-              </li>
-              <li>
-                <time>2021/05/28</time>
-                <span>XX新增了品牌授权信息</span>
-                <a href="#">已审核</a>
-              </li>
-            </ul>
+            <div class="scroll-box">
+              <ul>
+                <li v-for="(msg,index) in todoList" :key="index" :class="{dot:msg.status===0,isDone:msg.status===1}">
+                  <time>{{ msg.createDate }}</time>
+                  <span>{{ msg.content }}</span>
+                  <a href="#" @click.prevent="goReviewPage(msg)">{{ msg.status === 0 ? '去审核' : '已审核' }}</a>
+                </li>
+              </ul>
+            </div>
           </el-tab-pane>
+          <!-- 已处理 -->
           <el-tab-pane label="已处理" name="second">
-            <div class="no-notice">
-              <span class="el-icon-message-solid" />
-              <p>暂时没有任何消息</p>
+            <div class="scroll-box">
+              <ul v-if="todoList.length>0">
+                <li v-for="(msg,index) in todoList" :key="index" class="isDone">
+                  <time>{{ msg.createDate }}</time>
+                  <span>{{ msg.content }}</span>
+                  <a href="#">已审核</a>
+                </li>
+              </ul>
+              <div v-else class="no-notice">
+                <span class="el-icon-message-solid" />
+                <p>暂时没有任何消息</p>
+              </div>
             </div>
           </el-tab-pane>
-          <el-tab-pane label="未处理(99+)" class="untreated" name="third">
-            <div class="no-notice">
-              <span class="el-icon-message-solid" />
-              <p>暂时没有任何消息</p>
+          <!-- 未处理 -->
+          <el-tab-pane :label="count" class="untreated" name="third">
+            <div class="scroll-box">
+              <ul v-if="todoList.length>0">
+                <li v-for="(msg,index) in todoList" :key="index" class="dot">
+                  <time>{{ msg.createDate }}</time>
+                  <span>{{ msg.content }}</span>
+                  <a href="#">去审核</a>
+                </li>
+              </ul>
+              <div v-else class="no-notice">
+                <span class="el-icon-message-solid" />
+                <p>暂时没有任何消息</p>
+              </div>
             </div>
           </el-tab-pane>
         </el-tabs>
@@ -49,8 +62,8 @@
         <div class="dot-list">
           <span v-show="false" class="dot" />
           <span v-show="false" class="dot" />
-          <span class="dot" />
-          <span class="dot" />
+          <span v-show="hasMessage" class="dot" />
+          <span v-show="hasMessage" class="dot" />
         </div>
       </div>
     </el-drawer>
@@ -58,7 +71,7 @@
 </template>
 
 <script>
-
+import { mapGetters } from 'vuex'
 export default {
   data() {
     return {
@@ -66,8 +79,43 @@ export default {
       drawer: false
     }
   },
+  computed: {
+    ...mapGetters(['messageList']),
+    todoList() {
+      if (this.activeName === 'first') {
+        return this.messageList
+      } else if (this.activeName === 'second') {
+        return this.messageList.filter(item => item.status === 1)
+      } else {
+        return this.messageList.filter(item => item.status === 0)
+      }
+    },
+    count() {
+      const length = this.messageList.filter(item => item.status === 0).length
+      if (length > 99) {
+        return `未处理(99+)`
+      } else if (length === 0) {
+        return `未处理`
+      } else {
+        return `未处理(${length}+)`
+      }
+    },
+    hasMessage() {
+      return this.messageList.filter(item => item.status === 0).length > 0
+    }
+  },
   methods: {
-    handleClick() {}
+    handleClick() {},
+    // 跳转到审核页面
+    goReviewPage(msg) {
+      if (msg.status !== 0) return
+      if (msg.type === 1) {
+        this.$router.push(`/review/auth-list?authUserId=${msg.authUserId}`)
+      } else {
+        this.$router.push(`/review/shop-list?authId=${msg.authId}`)
+      }
+      this.drawer = false
+    }
   }
 }
 </script>
@@ -84,7 +132,8 @@ export default {
   cursor: pointer;
   font-size: 24px;
   color: #666;
-  &::before{
+  &.msg{
+    &::before{
     content: "";
     display: block;
     position: absolute;
@@ -95,6 +144,7 @@ export default {
     top: 10px;
     right: 0;
   }
+  }
 }
 .drawer-content{
   line-height: initial;
@@ -133,6 +183,13 @@ export default {
           background-color: red;
         }
       }
+      &.isDone{
+        color: #ccc !important;
+        a{
+          color: #ccc !important;
+          text-decoration: none !important;
+        }
+      }
       span{
         display: inline-block;
         vertical-align: middle;
@@ -180,5 +237,11 @@ export default {
   }
 
 }
-
+.scroll-box{
+  height: 82vh;
+  overflow-y: scroll;
+  &::-webkit-scrollbar{
+    display: none;
+  }
+}
 </style>

+ 8 - 3
src/main.js

@@ -13,15 +13,13 @@ import '@/styles/index.scss' // global css
 import App from './App'
 import store from './store'
 import router from './router'
-
 import './icons' // icon
 import './permission' // permission control
 import './utils/error-log' // error log
 
 import * as filters from './filters' // global filters
-
+// import WebSocket from './utils/WebSocketUtil'
 import './mixin/base' // 公共方法
-
 /**
  * If you don't want to use mock-server
  * you want to use MockJs for mock api
@@ -35,6 +33,13 @@ import './mixin/base' // 公共方法
 //   mockXHR()
 // }
 
+// 开启会话
+// if (store.getters.userIdentity === 1) {
+//   Vue.use(WebSocket, {
+//     namespaced: 'webSocket'
+//   })
+// }
+
 Vue.use(Element, {
   size: Cookies.get('size') || 'medium' // set element-ui default size
   // locale: enLang // 如果使用中文,无需设置,请删除

+ 11 - 5
src/permission.js

@@ -18,15 +18,16 @@ router.beforeEach(async(to, from, next) => {
 
   if (hasToken) {
     /**
-         * 满足要求则关闭所有标签,防止代理数据冲突
-         * 条件:1、sotre中存有代理数据 即 proxyInfo !== null
-         *      2、即将跳转的页面为 /supplier/list 或者 /supplier
-         */
+     * 满足要求则关闭所有标签,防止代理数据冲突
+     * 条件:1、sotre中存有代理数据 即 proxyInfo !== null
+     *      2、即将跳转的页面为 /supplier/list 或者 /supplier
+     */
     if (toSupplier.includes(to.path) && store.getters.proxyInfo !== null) {
       store.dispatch('tagsView/delAllProxyView')
       store.commit('user/SET_PROXY_INFO', null)
       console.log('关闭其他的标签')
     }
+
     if (to.path === '/login') {
       // 在登录状态下访问login页面,直接跳转到首页
       next()
@@ -44,9 +45,14 @@ router.beforeEach(async(to, from, next) => {
           // 通过
           const accessRoutes = await store.dispatch('permission/generateRoutes', store.getters.roles)
 
-          // dynamically add accessible routes
+          // 设置路由
           router.addRoutes(accessRoutes)
 
+          // 开启会话
+          if (!store.getters.socketState && store.getters.userIdentity === 1) {
+            store.dispatch('webSocket/initMessage')
+          }
+
           // hack method to ensure that addRoutes is complete
           // set the replace: true, so the navigation will not leave a history record
           next({ ...to, replace: true })

+ 3 - 1
src/store/getters.js

@@ -16,6 +16,8 @@ const getters = {
   initRouter: state => state.permission.initRouter,
   proxyInfo: state => state.user.proxyInfo,
   shopType: state => state.user.shopType,
-  brandId: state => state.user.brandId
+  brandId: state => state.user.brandId,
+  socketState: state => state.webSocket.socketState,
+  messageList: state => state.webSocket.messageList
 }
 export default getters

+ 1 - 1
src/store/modules/user.js

@@ -58,7 +58,7 @@ const mutations = {
 
 const actions = {
   // 登录操作
-  login({ commit }, userInfo) {
+  login({ commit, action }, userInfo) {
     const { username, password } = userInfo
     return new Promise((resolve, reject) => {
       login({ mobileOrName: username.trim(), password: password })

+ 61 - 0
src/store/modules/webSocket.js

@@ -0,0 +1,61 @@
+import { closeWebSocket, createSoceket } from '@/utils/WebSocketUtil'
+import { getMessage } from '@/api/message'
+
+const state = {
+  messageList: [],
+  socketState: false
+}
+const mutations = {
+  INIT_SOCKET: (state, commit) => {
+    createSoceket({ commit })
+  },
+  UPDATE_MESSAGE: (state, message) => {
+    state.messageList.unshift(message)
+  },
+  INIT_MESSAGE: (state, list) => {
+    state.messageList = list
+    console.log(state.messageList)
+  },
+  SET_SCOKET_STATE: (state, flag) => {
+    state.socketState = flag
+  },
+  SET_MESSAGE_STATE: (state, info) => {
+    console.log(info)
+    if (info.type === 1) {
+      state.messageList = state.messageList.map(v => {
+        if (v.authId === info.id) {
+          v.status = 1
+        }
+        return v
+      })
+    } else {
+      state.messageList = state.messageList.map(v => {
+        if (v.authProductId === info.id) {
+          v.status = 1
+        }
+        return v
+      })
+    }
+  }
+}
+const actions = {
+  initMessage({ commit }) {
+    getMessage().then(res => {
+      if (res.code !== 0) {
+        return
+      }
+      commit('INIT_MESSAGE', res.data)
+      commit('INIT_SOCKET', commit)
+    })
+  },
+  colseSocket() {
+    closeWebSocket()
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}

+ 62 - 0
src/utils/WebSocketUtil.js

@@ -0,0 +1,62 @@
+import store from '../store'
+let websocket = null
+let commit = null
+
+export function createSoceket(options) {
+  if (options) {
+    commit = options.commit
+  }
+  // 判断当前浏览器是否支持WebSocket
+  if ('WebSocket' in window) {
+    if (websocket === null) {
+      websocket = new WebSocket(process.env.VUE_APP_SOCKET_SERVER)
+      init()
+    }
+  } else {
+    alert('Not support websocket')
+  }
+}
+
+export function init() {
+  // 连接发生错误的回调方法
+  websocket.onerror = function() {
+    commit(`SET_SCOKET_STATE`, false)
+    console.log('socket connect with error !')
+  }
+
+  // 连接成功建立的回调方法
+  websocket.onopen = function(event) {
+    commit(`SET_SCOKET_STATE`, true)
+    console.log('socket is open !')
+  }
+
+  // 接收到消息的回调方法
+  websocket.onmessage = function(event) {
+    console.log(event.data)
+    commit(`UPDATE_MESSAGE`, JSON.parse(event.data))
+    console.log(store.getters.messageList)
+  }
+
+  // 连接关闭的回调方法
+  websocket.onclose = function() {
+    commit(`SET_SCOKET_STATE`, false)
+    console.log('socket is closed !')
+  }
+}
+
+// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
+window.onbeforeunload = function() {
+  websocket.close()
+}
+
+// 关闭连接
+export function closeWebSocket() {
+  websocket.close()
+}
+
+// 发送消息
+export function send() {
+  var message = document.getElementById('text').value
+  websocket.send(message)
+}
+

+ 24 - 14
src/views/login/index.vue

@@ -1,7 +1,13 @@
 <template>
   <div class="login-container">
-    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" autocomplete="on" label-position="left">
-
+    <el-form
+      ref="loginForm"
+      :model="loginForm"
+      :rules="loginRules"
+      class="login-form"
+      autocomplete="on"
+      label-position="left"
+    >
       <div class="title-container">
         <h3 class="title">正品联盟管理系统</h3>
       </div>
@@ -45,14 +51,17 @@
           </span>
         </el-form-item>
       </el-tooltip>
-
-      <el-button :loading="loading" type="primary" style="width:100%;margin-bottom:44px;" @click.native.prevent="handleLogin">登录</el-button>
+      <el-button
+        :loading="loading"
+        type="primary"
+        style="width:100%;margin-bottom:44px;"
+        @click.native.prevent="handleLogin"
+      >登录</el-button>
     </el-form>
   </div>
 </template>
 
 <script>
-
 export default {
   name: 'Login',
 
@@ -106,7 +115,7 @@ export default {
   methods: {
     checkCapslock(e) {
       const { key } = e
-      this.capsTooltip = key && key.length === 1 && (key >= 'A' && key <= 'Z')
+      this.capsTooltip = key && key.length === 1 && key >= 'A' && key <= 'Z'
     },
     showPwd() {
       if (this.passwordType === 'password') {
@@ -122,7 +131,8 @@ export default {
       this.$refs.loginForm.validate(valid => {
         if (valid) {
           this.loading = true
-          this.$store.dispatch('user/login', this.loginForm)
+          this.$store
+            .dispatch('user/login', this.loginForm)
             .then(() => {
               const isAdmin = this.$store.getters.userIdentity === 1
               isAdmin ? this.$router.push('/supplier/list') : this.$router.push('/auth/list')
@@ -147,7 +157,7 @@ export default {
       }, {})
     },
     handleInput() {
-      this.loginForm.username = this.loginForm.username.replace(/[^\w\.\/]/ig, '')
+      this.loginForm.username = this.loginForm.username.replace(/[^\w\.\/]/gi, '')
     }
     // afterQRScan() {
     //   if (e.key === 'x-admin-oauth-code') {
@@ -175,8 +185,8 @@ export default {
 /* 修复input 背景不协调 和光标变色 */
 /* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
 
-$bg:#283443;
-$light_gray:#fff;
+$bg: #283443;
+$light_gray: #fff;
 $cursor: #fff;
 
 @supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
@@ -219,9 +229,9 @@ $cursor: #fff;
 </style>
 
 <style lang="scss" scoped>
-$bg:#2d3a4b;
-$dark_gray:#889aa4;
-$light_gray:#eee;
+$bg: #2d3a4b;
+$dark_gray: #889aa4;
+$light_gray: #eee;
 
 .login-container {
   min-height: 100%;
@@ -262,7 +272,7 @@ $light_gray:#eee;
     position: relative;
 
     .title {
-      font-size:36px;
+      font-size: 36px;
       color: $light_gray;
       margin: 0px auto 40px auto;
       text-align: center;

+ 1 - 2
src/views/supplier/auth/index.vue

@@ -104,7 +104,7 @@
       <el-table-column label="操作" align="center" width="240px" class-name="small-padding fixed-width">
         <template slot-scope="{row}">
           <template v-if="userIdentity === 2|| proxyInfo!==null">
-            <el-button type="info" size="mini" @click="handleShowEditDialog('添加品牌授权',row)">
+            <el-button type="info" size="mini" @click="handleShowEditDialog('编辑品牌授权',row)">
               编辑
             </el-button>
             <el-button type="danger" size="mini" @click="handleRemoveAuth(row)">
@@ -237,7 +237,6 @@ export default {
     // Audit failed 审核未通过
     checkAuditFailedList(data) {
       this.auditFailedList = data.filter(item => item.auditStatus === 0)
-      console.log(this.auditFailedList)
       if (this.auditFailedList.length > 0 && this.auditNoticeFlag && (this.userIdentity !== 1 || this.proxyInfo !== null)) {
         this.$notify.info({
           title: '消息通知',

+ 0 - 1
src/views/supplier/product/index.vue

@@ -256,7 +256,6 @@ export default {
     // Audit failed 审核未通过
     checkAuditFailedList(data) {
       this.auditFailedList = data.filter(item => item.auditStatus === 0)
-      console.log(this.auditFailedList)
       if (this.auditFailedList.length > 0 && this.auditNoticeFlag && (this.userIdentity !== 1 || this.proxyInfo !== null)) {
         this.$notify.info({
           title: '消息通知',

+ 3 - 1
src/views/supplier/review/authList.vue

@@ -146,7 +146,7 @@
 import Pagination from '@/components/Pagination'
 import { fecthAuthList, auditAuth } from '@/api/auth'
 import { formatDate } from '@/utils'
-import { mapGetters } from 'vuex'
+import { mapGetters, mapMutations } from 'vuex'
 export default {
   components: { Pagination },
   filters: {
@@ -199,6 +199,7 @@ export default {
     this.getList()
   },
   methods: {
+    ...mapMutations({ setMessageState: 'webSocket/SET_MESSAGE_STATE' }),
     // 获取授权列表
     getList() {
       this.listLoading = true
@@ -221,6 +222,7 @@ export default {
           auditAuth(this.dialogData).then(res => {
             if (res.code !== 0) return
             this.$message.success(res.data)
+            this.setMessageState({ id: this.dialogData.authId, type: 1 })
           }).finally(() => {
             this.dialogVisible = false
             this.getList()

+ 3 - 1
src/views/supplier/review/shopDetail.vue

@@ -51,7 +51,7 @@
 
 <script>
 import { getProductById, auditProduct } from '@/api/product'
-import { mapGetters } from 'vuex'
+import { mapGetters, mapMutations } from 'vuex'
 export default {
   data() {
     return {
@@ -79,6 +79,7 @@ export default {
     this.getDetail()
   },
   methods: {
+    ...mapMutations({ setMessageState: 'webSocket/SET_MESSAGE_STATE' }),
     // 获取商品详情
     getDetail() {
       this.isLoading = true
@@ -104,6 +105,7 @@ export default {
             this.$message.success(res.data)
             this.$store.dispatch('tagsView/delView', this.$route)
             this.$router.replace(`/review/shop-list?authId=${this.authId}`)
+            this.setMessageState({ id: parseInt(this.formData.productId), type: 2 })
           }).finally(() => {
             this.isLoading = false
           })