从0到1搭建自己的OA系统(四)

除了第三篇中的弹窗消息外,本次计划在弹窗消息的基础上实现点对点的聊天窗口,即可以在线聊天,效果如图:

即在“即时消息” 页面,新增“聊天窗口”的按钮,点击相应的人员对应的此按钮后,即可打开一个临时的点对点聊天窗口,实现类似于微信或QQ的临时对话交流。

最终测试效果已经部署到本人个人的服务器,有兴趣的可以去测试和体验一下,体验地址:

怎知后台管理系统icon-default.png?t=N7T8https://www.zenknow.cn/

  • 体验账号1:test1      密码:123456   (公告发起者账号)
  • 体验账号2:test2      密码:123456   (流程审核者账号)

5.1 功能策划

 本次聊天窗口和弹窗功能的改造,需要满足以下功能:

  • 发送消息时,如果接收消息的用户已经打开聊天窗口,则直接在窗口显示消息,不弹窗
  • 发送消息时,如果接受消息的用户未打开聊天窗口,则以弹窗形式提醒
  • 从聊天窗口发送消息时,发送者的消息一方面显示在本窗口右侧,同时发送到接收者
  • 打开不同接收者的聊天窗口,只显示是对应的与该人员的聊天记录
  • 记录以用户本地浏览器缓存的形式保存,服务器只转发,不存储

5.2 后端初步改造

5.2.1 消息对象改造

原弹窗发送消息只传递了fromUser(发送者)、msg(消息)两个信息,为了实现对话框功能还需要传递用户头像、用户id等字段,故对原弹窗消息后端的对象字段改造如下:

SysUserOnlineUserOnlineDTO中分别增加:

/**
   * 用户ID
   */
   private String userId;
/**
   * 头像
   */
    private String avatar;

5.2.2 后端消息处理改造

后端收到消息后,给users发正式消息时,携带上userId和avatar

 @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        log.info("发送文本消息");
        // 获得客户端传来的消息
        JSONObject json = JSONUtil.parseObj(message.getPayload());
        if(json != null) {
            List<String> users = (List<String>) json.get("users");
            String msg  = (String) json.get("msg");
            String fromNick = (String) json.get("fromNick");
            String fromAvatar = (String) json.get("fromAvatar");
            String fromUserId = String.valueOf( json.get("fromUserId"));
            if(fromNick == null){
                fromNick ="后台系统";
            }
            if(users.isEmpty()){
                //向发送者反馈发送信息
                JSONObject sendResult = new JSONObject();
                sendResult.set("fromUser", "系统发送反馈");
                sendResult.set("msg", "消息发送失败,您未勾选接收人");
                if(session != null) {
                    session.sendMessage(new TextMessage(sendResult.toString()));
                }
            } else {
                for(String user : users) {
                    WebSocketSession userSession = WsSessionManager.get(user);
                    System.out.println(userSession);
                    if (userSession != null) {
                        //向消息者发送消息
                        JSONObject jsonObject = new JSONObject();
                        jsonObject.set("fromUser", fromNick);
                        jsonObject.set("msg", msg);
                        jsonObject.set("fromAvatar", fromAvatar);
                        jsonObject.set("fromUserId", fromUserId);
                        userSession.sendMessage(new TextMessage(jsonObject.toString()));
                        //向发送者反馈发送信息
                        JSONObject sendResult = new JSONObject();
                        sendResult.set("fromUser", "系统发送反馈");
                        sendResult.set("msg", "消息发送成功");
                        if(session != null) {
                            session.sendMessage(new TextMessage(sendResult.toString()));
                        }
                    } else {
                        //向发送者反馈发送信息
                        JSONObject sendResult = new JSONObject();
                        sendResult.set("fromUser", "系统发送反馈");
                        sendResult.set("msg", "消息发送失败,可能是对方已经下线,请刷新用户列表");
                        if(session != null) {
                            session.sendMessage(new TextMessage(sendResult.toString()));
                        }
                    }
                }
            }

        }
    }

5.3 前端改造

5.3.1 前端界面改造

本次聊天对话框使用JWchat插件,具体使用文档可以参考其官网文档:

https://codegi.gitee.io/jwchatdoc/component/chatIndex.html

这次是直接在对话框中使用JWchat-index插件,并将右侧和左侧隐藏,只保留聊天窗口。其中列表中首先增加一列按钮

<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button
            size="big"
            type="text"
            @click="openChatDialog(scope.row.userNick,scope.row.userId,scope.row.avatar,scope.row.deptName)"
          >聊天窗口</el-button>           
        </template>
      </el-table-column>

对话框及聊天窗口:

<el-dialog title="聊天窗口" :visible.sync="openChat" width="800px" append-to-body @close="closeChat">
      <div style="display: flex; align-items: center; justify-content: center;">
        <JwChat-index 
      :taleList="taleList" 
      :showRightBox="false"
      :config="config"
      width="750px"
      scrollType="scroll"
      @enter="bindEnter" 
      v-model="inputMsg" 
      :toolConfig="tool" /> 
      </div>
    </el-dialog> 

5.3.2 相关配置数据 

 在data中配置JWchat-index 

//配置对话框头部的头像和名字部门的
config:{
        img: '',
        name: '',
        dept: '',
      },
//聊天窗口中输入位置的信息
inputMsg:"",
//配置聊天窗口输入上方的工具栏的
tool:{
        // file img video 现在只配置了三个图标
        show: ['file', 'img'],
        callback: this.toolEvent
      },
//以下三个变量为了和导航栏的弹窗消息能够共享,需改造为vuex存储为全局变量,见下节
//用于存储当前打开的窗口的聊天对象的昵称
dialogActiveUserNick:"",
//用于存储当前打开的窗口的聊天对象的id
dialogActiveUserId:"",
//用于监听是否发来了新信息
dialogDetailChange:false,

//聊天记录
taleList:[
          {
              "date": "",
              "text": { "text": "" },
              "mine": false,
              "name": "",
              "img": "i"
          }
      ],
//是否打开聊天窗口,由新增的列表中的按钮触发
openChat:false,
//要发送的整体信息,新增fromAvatar、fromUserId
usersMessage: {
        fromNick:"",
        fromAvatar:"",
        fromUserId:"",
        users:[],
        msg:""
      },

其中,dialogActiveUserNick等三个变量是用来监听对话框聊天对象信息的,为了与Navbar.vue中的弹窗消息能够通讯,需要设置为共享形式,此处仅为占位。将这三个信息设置为vuex:

在store的moudles中新建chat.js

const chat = {
  state: {
    chatDialogActiveUserNick:"",
    chatDialogActiveUserId:"",
    chatCurGetInfoChanged:false,
  },

  mutations: {
    SET_CHAT_DIA_ACT_USER_NICK: (state, chatDialogActiveUserNick) => {
      state.chatDialogActiveUserNick = chatDialogActiveUserNick
    },
    SET_CHAT_DIA_ACT_USER_ID: (state, chatDialogActiveUserId) => {
      state.chatDialogActiveUserId = chatDialogActiveUserId
    },
    SET_CHAT_CUR_GETINFO_CHANGED: (state, chatCurGetInfoChanged) => {
      state.chatCurGetInfoChanged = chatCurGetInfoChanged
    },
  },

  actions: {},

}

export default chat

在getters中设置:

const getters = {
  ....

  chatDialogActiveUserNick:state => state.chat.chatDialogActiveUserNick,
  chatDialogActiveUserId:state => state.chat.chatDialogActiveUserId,
  chatCurGetInfoChanged:state => state.chat.chatCurGetInfoChanged,
}
export default getters

store中的index中添加:

....
import chat from './modules/chat'

Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
    ...
    chat
  },
  getters
})

export default store

 5.3.3 针对界面对应的方法

created() {
    this.getList();
//此处为当窗口整体关闭时,防止store中的共享变量状态未能及时清空所做的操作
    window.addEventListener('beforeunload', e => this.closeChat());
    window.addEventListener('unload', e => this.this.closeChat());
  },
此处为当窗口整体关闭时,防止store中的共享变量状态未能及时清空所做的操作
  destroyed() {
      window.removeEventListener('beforeunload', e => this.this.closeChat());
      window.removeEventListener('unload', e => this.this.closeChat());
  },

computed: {
//引入store中共享的表明对话框聊天对象的信息的变量,以便于watch监听
    ...mapGetters([
      'chatCurGetInfoChanged',
      'chatDialogActiveUserNick',
    ]),
  },

watch:{
//用于监听store中共享的,表明新增了消息的变量,此变量在true和false之间轮转变化
    chatCurGetInfoChanged:{
      handler(newVal,oldVal){
        var curTaleList = localStorage.getItem(store.getters.chatDialogActiveUserNick) == null ? JSON.stringify([]) : localStorage.getItem(store.getters.chatDialogActiveUserNick) ;
        this.taleList = JSON.parse(curTaleList);
      },
      immediate:true
    },
  },

methods: {

//外部群发弹窗的按钮动作,增加了
//1.发送者的userID
//2.发送者的头像avatar
    send() {
      if (store.getters.ws) {
        this.usersMessage.fromNick = store.getters.nick;
        this.usersMessage.fromUserId = store.getters.userId;
        this.usersMessage.fromAvatar = store.getters.avatar;
        this.usersMessage.users = this.toUsers;
        this.usersMessage.msg = this.message;        
        console.log(JSON.stringify(this.usersMessage));
        store.getters.ws.send(JSON.stringify(this.usersMessage));
      } else {
        alert("未连接到服务器");
      }
    },

//打开聊天窗口时的方法,主要执行了:
//1.设置头部的聊天对象的头像、名字、部门
//2.显示对话框
//3.设置STORE中的聊天对象共享变量为聊天对象昵称和id
//4.从本地缓存中读取此聊天对象对应的聊天记录
    openChatDialog(userNick,userId,userAvatar,userDept){
      this.config.img = userAvatar;
      this.config.dept = userDept;
      this.config.name = userNick;
      this.openChat=true;      
      this.$store.commit('SET_CHAT_DIA_ACT_USER_NICK',userNick);
      this.$store.commit('SET_CHAT_DIA_ACT_USER_ID',userId);
      var curTaleList = localStorage.getItem(store.getters.chatDialogActiveUserNick) == null ? JSON.stringify([]) : localStorage.getItem(store.getters.chatDialogActiveUserNick) ;
        this.taleList = JSON.parse(curTaleList);
    },

//关闭临时对话框的方法,主要执行了:
//1.隐藏对话框
//2.将Store中共享的,表明聊天对象的变量信息重置为空
//3.将聊天记录重置为空
    closeChat() {      
      this.openChat=false; 
      this.$store.commit('SET_CHAT_DIA_ACT_USER_NICK',"");
      this.$store.commit('SET_CHAT_DIA_ACT_USER_ID',"");   
      this.taleList = [];
    },

//在聊天窗口中点击发送按钮后的方法,主要执行了
//1.在聊天窗口的聊天记录中添加mine为true的自己发送的消息,并存储到本地缓存
//2.向服务器端请求发送给聊天对象发送的消息
    bindEnter (e) {
      const msg = this.inputMsg    
      if (!msg) return;
      var nowDate = moment(new Date().getTime()).format("YYYY/MM/DD HH:mm:ss");
      const msgObj = {
        "date": nowDate,
        "text": { "text": msg },
        "mine": true,
        "name": store.getters.nick,
        "img": store.getters.avatar
      }
      this.taleList.push(msgObj)
      var curTaleList = localStorage.getItem(store.getters.chatDialogActiveUserNick) == null ? JSON.stringify([]) : localStorage.getItem(store.getters.chatDialogActiveUserNick) ;
      var getCurTaleList = JSON.parse(curTaleList);
      getCurTaleList.push(msgObj);
      localStorage.setItem(store.getters.chatDialogActiveUserNick,JSON.stringify(getCurTaleList));
      if (store.getters.ws) {
        this.usersMessage.fromNick = store.getters.nick;
        this.usersMessage.fromUserId = store.getters.userId;
        this.usersMessage.fromAvatar = store.getters.avatar;
        this.usersMessage.users = [store.getters.chatDialogActiveUserId];
        this.usersMessage.msg = msg; 
        store.getters.ws.send(JSON.stringify(this.usersMessage));
      } else {
        alert("未连接到服务器");
      }
    },
  },

5.3.4 导航栏收到消息时的操作,Navbar.vue

this.ws.onmessage = function (event) {
      console.log("收到消息!!!");
      //self.text_content = event.data + "\n";
      // 判断是推动预警消息的时候
      var fromUser = JSON.parse(event.data).fromUser;
      var msg = JSON.parse(event.data).msg;
        // 预警消息包含预警id的时候
        if(fromUser == "系统发送反馈"){
          if(msg.includes("成功")){
            self.$modal.msgSuccess(msg);
          } else {
            self.$modal.msgError(msg);
          }
        } else if(fromUser == "流程中心") {
          Notification.info({
            title: "来自" + fromUser + "的消息 " + moment(new Date().getTime()).format(
                "HH:mm:ss"
              ),
            dangerouslyUseHTMLString: true,
            message: msg,
            duration: 3000,
            position: "bottom-right",
            onClick: function () {
              self.flowDetail(); //自定义回调,message为传的参数
            },
          }); 

        } else {

//如果发送来的消息,对应的对话框窗口是打开的,那么
//1.消息存储到聊天对象的本地缓存中
//2.设置store中共享的变量,表明有新增消息
          if(store.getters.chatDialogActiveUserNick==fromUser){
            var fromAvatar = JSON.parse(event.data).fromAvatar;
            var fromUserId = JSON.parse(event.data).fromUserId;
            var nowDate = moment(new Date().getTime()).format("YYYY/MM/DD HH:mm:ss");
            var taleListOne = {
              "date":nowDate,
              "text":{"text":msg},
              "mine":false,
              "name":fromUser,
              "img":fromAvatar
            };
            var getCurTaleList = localStorage.getItem(fromUser) == null ? JSON.stringify([]) : localStorage.getItem(fromUser);
            var curTaleList = JSON.parse(getCurTaleList);
            curTaleList.push(taleListOne);
            localStorage.setItem(fromUser,JSON.stringify(curTaleList));
            var changed = store.getters.chatCurGetInfoChanged ? false : true
            console.log("changed:" + changed);
            store.commit("SET_CHAT_CUR_GETINFO_CHANGED",changed);
          } else {

//如果发送来的消息,对应的对话框窗口是关闭的,那么
//1.消息存储到聊天对象的本地缓存中
//2.生成弹窗消息显示
            Notification.info({
            title: "来自" + fromUser + "的消息 " + moment(new Date().getTime()).format(
                "HH:mm:ss"
              ),
            dangerouslyUseHTMLString: true,
            message: msg,
            duration: 3000,
            position: "bottom-right",
            onClick: function () {//预留跳转
              //self.warnDetailByWarnid(messageBody.warnId); //自定义回调,message为传的参数
              // 点击跳转的页面
            },
          }); 

          var fromAvatar = JSON.parse(event.data).fromAvatar;
            var fromUserId = JSON.parse(event.data).fromUserId;
            var nowDate = moment(new Date().getTime()).format("YYYY/MM/DD HH:mm:ss");
            var taleListOne = {
              "date":nowDate,
              "text":{"text":msg},
              "mine":false,
              "name":fromUser,
              "img":fromAvatar
            };
            var getCurTaleList = localStorage.getItem(fromUser) == null ? JSON.stringify([]) : localStorage.getItem(fromUser);            
            var curTaleList = JSON.parse(getCurTaleList);            
            curTaleList.push(taleListOne);
            localStorage.setItem(fromUser,JSON.stringify(curTaleList));
            
          }
        }
    };

5.4 最终效果  

最终测试效果已经部署到本人个人的服务器,有兴趣的可以去测试和体验一下,体验地址:

怎知后台管理系统icon-default.png?t=N7T8https://www.zenknow.cn/

  • 体验账号1:test1      密码:123456   (公告发起者账号)
  • 体验账号2:test2      密码:123456   (流程审核者账号)

  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值