commit
30b152a231
@ -0,0 +1,53 @@
|
||||
package com.stdiet.web.controller.custom;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.stdiet.common.core.controller.BaseController;
|
||||
import com.stdiet.custom.utils.WsUtils;
|
||||
import com.stdiet.custom.server.WebSocketServer;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Controller
|
||||
@EnableScheduling
|
||||
@RequestMapping("/ws/api")
|
||||
public class WebSocketController extends BaseController {
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping("/push/{cid}")
|
||||
public Map pushToWeb(@PathVariable String cid, String message) {
|
||||
if (message == null) {
|
||||
message = "我是消息44";
|
||||
}
|
||||
Map result = new HashMap();
|
||||
try {
|
||||
WebSocketServer.sendInfo(message, cid);
|
||||
result.put("code", 200);
|
||||
result.put("msg", "success");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
// @Scheduled(fixedRate = 30000)
|
||||
// public void boardCast() {
|
||||
// try {
|
||||
// JSONObject heartBeat = new JSONObject();
|
||||
// heartBeat.put("type", WsUtils.WS_TYPE_HEART_BEAT);
|
||||
// heartBeat.put("msg", "ping");
|
||||
//
|
||||
// WebSocketServer.sendInfo(heartBeat.toJSONString(), null);
|
||||
// } catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
}
|
@ -99,6 +99,11 @@
|
||||
<artifactId>ikanalyzer</artifactId>
|
||||
<version>2012_u6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
</project>
|
@ -89,4 +89,6 @@ public class SysServicesTopic {
|
||||
|
||||
List<SysServicesTopic> replys;
|
||||
|
||||
Integer count;
|
||||
|
||||
}
|
||||
|
@ -18,4 +18,6 @@ public interface SysServicesTopicMapper {
|
||||
int inserSysServicesTopicReply(SysServicesTopic topic);
|
||||
|
||||
List<SysServicesTopic> selectSysServicesTopicSessionByTopicId(String topicId);
|
||||
|
||||
List<SysServicesTopic> selectUnreadTopicCount(List<SysServicesTopic> topics);
|
||||
}
|
||||
|
@ -0,0 +1,143 @@
|
||||
package com.stdiet.custom.server;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.stdiet.common.core.domain.model.LoginUser;
|
||||
import com.stdiet.common.utils.spring.SpringUtils;
|
||||
import com.stdiet.custom.domain.SysServicesTopic;
|
||||
import com.stdiet.custom.service.ISysServicesTopicService;
|
||||
import com.stdiet.custom.utils.WsUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.websocket.*;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
@ServerEndpoint(value = "/ws")
|
||||
@Component
|
||||
@Slf4j
|
||||
public class WebSocketServer {
|
||||
// concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
|
||||
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
|
||||
|
||||
//private static ConcurrentHashMap<String,WebSocketServer> websocketList = new ConcurrentHashMap<>();
|
||||
// 与某个客户端的连接会话,需要通过它来给客户端发送数据
|
||||
private Session session;
|
||||
// 接收sid
|
||||
private String sid = "";
|
||||
|
||||
public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {
|
||||
return webSocketSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 群发自定义消息
|
||||
*/
|
||||
public static void sendInfo(String message, String sid) throws IOException {
|
||||
log.info("推送消息到窗口" + sid + ",推送内容:" + message + "目标:" + webSocketSet.size());
|
||||
for (WebSocketServer item : webSocketSet) {
|
||||
try {
|
||||
// 这里可以设定只推送给这个sid的,为null则全部推送
|
||||
if (sid == null) {
|
||||
item.sendMessage(message);
|
||||
} else if (item.sid.equals(sid)) {
|
||||
item.sendMessage(message);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// 清理断开的连接
|
||||
webSocketSet.remove(item);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// * 连接建立成功调用的方法*
|
||||
@OnOpen
|
||||
public void onOpen(Session session) {
|
||||
this.session = session;
|
||||
this.sid = String.valueOf(getUserId(session));
|
||||
|
||||
webSocketSet.add(this); // 加入set中
|
||||
|
||||
try {
|
||||
JSONObject object = new JSONObject();
|
||||
object.put("type", WsUtils.WS_TYPE_SYSTEM_MESSAGE);
|
||||
object.put("msg", "连接成功");
|
||||
sendMessage(object.toJSONString());
|
||||
} catch (IOException e) {
|
||||
log.error("websocket IO异常");
|
||||
}
|
||||
}
|
||||
|
||||
@OnClose
|
||||
public void onClose() {
|
||||
webSocketSet.remove(this); // 从set中删除
|
||||
log.info("有一连接关闭!");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param session
|
||||
* @param error
|
||||
*/
|
||||
@OnError
|
||||
public void onError(Session session, Throwable error) {
|
||||
log.error("发生错误");
|
||||
error.printStackTrace();
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现服务器主动推送
|
||||
*/
|
||||
public void sendMessage(String message) throws IOException {
|
||||
log.info("服务器消息推送:" + message);
|
||||
this.session.getBasicRemote().sendText(message);
|
||||
}
|
||||
|
||||
public Long getUserId(Session session) {
|
||||
try {
|
||||
return ((LoginUser) ((UsernamePasswordAuthenticationToken) session.getUserPrincipal()).getPrincipal()).getUser().getUserId();
|
||||
} catch (Exception e) {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
@OnMessage
|
||||
public void onMessage(String message, Session session) {
|
||||
log.info("收到来自窗口" + sid + "的信息:" + message);
|
||||
try {
|
||||
String sid = String.valueOf(getUserId(session));
|
||||
if (sid.equals("0")) {
|
||||
return;
|
||||
}
|
||||
JSONObject resultObj = new JSONObject();
|
||||
if (message.equals(WsUtils.WS_GET_UNREAD_COUNT)) {
|
||||
SysServicesTopic topic = new SysServicesTopic();
|
||||
topic.setUid(sid);
|
||||
List<SysServicesTopic> statusList = new ArrayList<>();
|
||||
statusList.add(topic);
|
||||
ISysServicesTopicService servicesTopicService = SpringUtils.getBean(ISysServicesTopicService.class);
|
||||
List<SysServicesTopic> result = servicesTopicService.selectUnreadTopicCount(statusList);
|
||||
|
||||
JSONObject dataObj = new JSONObject();
|
||||
dataObj.put("count", result.get(0).getCount());
|
||||
|
||||
resultObj.put("type", WsUtils.WS_TYPE_MESSAGE_COUNT);
|
||||
resultObj.put("msg", "未读消息数");
|
||||
resultObj.put("data", dataObj);
|
||||
|
||||
} else if (message.equals(WsUtils.WS_PING)) {
|
||||
|
||||
}
|
||||
WebSocketServer.sendInfo(resultObj.toJSONString(), sid);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -17,4 +17,6 @@ public interface ISysServicesTopicService {
|
||||
SysServicesTopic inserSysServicesTopicComment(SysServicesTopic topic);
|
||||
|
||||
List<SysServicesTopic> selectSysServicesTopicSessionByTopicId(String topicId);
|
||||
|
||||
List<SysServicesTopic> selectUnreadTopicCount(List<SysServicesTopic> topic);
|
||||
}
|
||||
|
@ -1,15 +1,19 @@
|
||||
package com.stdiet.custom.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.stdiet.common.utils.DateUtils;
|
||||
import com.stdiet.common.utils.uuid.UUID;
|
||||
import com.stdiet.custom.domain.SysCustomer;
|
||||
import com.stdiet.custom.domain.SysServicesTopic;
|
||||
import com.stdiet.custom.mapper.SysCustomerMapper;
|
||||
import com.stdiet.custom.mapper.SysServicesTopicMapper;
|
||||
import com.stdiet.custom.server.WebSocketServer;
|
||||
import com.stdiet.custom.service.ISysServicesTopicService;
|
||||
import com.stdiet.custom.utils.WsUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ -40,35 +44,58 @@ public class SysServicesTopicServiceImp implements ISysServicesTopicService {
|
||||
|
||||
List<SysServicesTopic> statusList = new ArrayList<>();
|
||||
|
||||
String customerId = String.valueOf(customer.getId());
|
||||
SysServicesTopic customerStatus = new SysServicesTopic();
|
||||
customerStatus.setUid(String.valueOf(customer.getId()));
|
||||
customerStatus.setUid(customerId);
|
||||
customerStatus.setRole("customer");
|
||||
customerStatus.setRead(1);
|
||||
customerStatus.setTopicId(topic.getTopicId());
|
||||
statusList.add(customerStatus);
|
||||
|
||||
String dieticianId = String.valueOf(customer.getMainDietitian());
|
||||
SysServicesTopic dieticianStatus = new SysServicesTopic();
|
||||
dieticianStatus.setUid(String.valueOf(customer.getMainDietitian()));
|
||||
dieticianStatus.setUid(dieticianId);
|
||||
dieticianStatus.setRole("dietician");
|
||||
dieticianStatus.setRead(0);
|
||||
dieticianStatus.setTopicId(topic.getTopicId());
|
||||
statusList.add(dieticianStatus);
|
||||
|
||||
String afterSaleId = String.valueOf(customer.getAfterDietitian());
|
||||
SysServicesTopic afterSaleStatus = new SysServicesTopic();
|
||||
afterSaleStatus.setUid(String.valueOf(customer.getAfterDietitian()));
|
||||
afterSaleStatus.setUid(afterSaleId);
|
||||
afterSaleStatus.setRole("after_sale");
|
||||
afterSaleStatus.setRead(0);
|
||||
afterSaleStatus.setTopicId(topic.getTopicId());
|
||||
statusList.add(afterSaleStatus);
|
||||
|
||||
String dieticianAssistantId = String.valueOf(customer.getAssistantDietitian());
|
||||
SysServicesTopic dieticianAssistantStatus = new SysServicesTopic();
|
||||
dieticianAssistantStatus.setUid(String.valueOf(customer.getAssistantDietitian()));
|
||||
dieticianAssistantStatus.setUid(dieticianAssistantId);
|
||||
dieticianAssistantStatus.setRole("dietician_assistant");
|
||||
dieticianAssistantStatus.setRead(0);
|
||||
dieticianAssistantStatus.setTopicId(topic.getTopicId());
|
||||
statusList.add(dieticianAssistantStatus);
|
||||
|
||||
servicesTopicMapper.insertSysServicesTopicStatus(statusList);
|
||||
int rows = servicesTopicMapper.insertSysServicesTopicStatus(statusList);
|
||||
if (rows > 0) {
|
||||
try {
|
||||
List<SysServicesTopic> counts = servicesTopicMapper.selectUnreadTopicCount(statusList);
|
||||
for (int i = 0; i < counts.size(); i++) {
|
||||
topic.setId(statusList.get(i).getId());
|
||||
JSONObject dataObj = new JSONObject();
|
||||
dataObj.put("count", counts.get(i).getCount());
|
||||
dataObj.put("data", topic);
|
||||
|
||||
JSONObject msgObj = new JSONObject();
|
||||
msgObj.put("type", WsUtils.WS_TYPE_MESSAGE_COUNT);
|
||||
msgObj.put("msg", "未读消息数");
|
||||
msgObj.put("data", dataObj);
|
||||
WebSocketServer.sendInfo(msgObj.toJSONString(), counts.get(i).getUid());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
topic.setId(customerStatus.getId());
|
||||
topic.setUid(null);
|
||||
@ -94,10 +121,13 @@ public class SysServicesTopicServiceImp implements ISysServicesTopicService {
|
||||
status.setTopicId(topic.getTopicId());
|
||||
status.setRole(topic.getRole());
|
||||
servicesTopicMapper.updateSysServicesTopicStatus(status);
|
||||
|
||||
afterReply(topic);
|
||||
}
|
||||
return topic;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SysServicesTopic inserSysServicesTopicComment(SysServicesTopic topic) {
|
||||
String uuid = java.util.UUID.randomUUID().toString().replace("-", "");
|
||||
@ -110,12 +140,51 @@ public class SysServicesTopicServiceImp implements ISysServicesTopicService {
|
||||
status.setTopicId(topic.getTopicId());
|
||||
status.setRole(topic.getRole());
|
||||
servicesTopicMapper.updateSysServicesTopicStatus(status);
|
||||
|
||||
afterReply(topic);
|
||||
}
|
||||
return topic;
|
||||
}
|
||||
|
||||
public void afterReply(SysServicesTopic topic) {
|
||||
SysCustomer customer = sysCustomerMapper.selectSysCustomerById(Long.parseLong(topic.getFromUid()));
|
||||
|
||||
List<SysServicesTopic> statusList = new ArrayList<>();
|
||||
SysServicesTopic dieticianStatus = new SysServicesTopic();
|
||||
dieticianStatus.setUid(String.valueOf(customer.getMainDietitian()));
|
||||
statusList.add(dieticianStatus);
|
||||
SysServicesTopic afterSaleStatus = new SysServicesTopic();
|
||||
afterSaleStatus.setUid(String.valueOf(customer.getAfterDietitian()));
|
||||
statusList.add(afterSaleStatus);
|
||||
SysServicesTopic dieticianAssistantStatus = new SysServicesTopic();
|
||||
dieticianAssistantStatus.setUid(String.valueOf(customer.getAssistantDietitian()));
|
||||
statusList.add(dieticianAssistantStatus);
|
||||
|
||||
try {
|
||||
List<SysServicesTopic> counts = servicesTopicMapper.selectUnreadTopicCount(statusList);
|
||||
for (int i = 0; i < counts.size(); i++) {
|
||||
JSONObject dataObj = new JSONObject();
|
||||
dataObj.put("count", counts.get(i).getCount());
|
||||
dataObj.put("topicId", topic.getTopicId());
|
||||
|
||||
JSONObject msgObj = new JSONObject();
|
||||
msgObj.put("type", WsUtils.WS_TYPE_NEW_CUSTOMER_REPLY);
|
||||
msgObj.put("msg", "新客户回复");
|
||||
msgObj.put("data", dataObj);
|
||||
WebSocketServer.sendInfo(msgObj.toJSONString(), counts.get(i).getUid());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SysServicesTopic> selectSysServicesTopicSessionByTopicId(String topicId) {
|
||||
return servicesTopicMapper.selectSysServicesTopicSessionByTopicId(topicId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SysServicesTopic> selectUnreadTopicCount(List<SysServicesTopic> statusList) {
|
||||
return servicesTopicMapper.selectUnreadTopicCount(statusList);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
package com.stdiet.custom.utils;
|
||||
|
||||
public class WsUtils {
|
||||
|
||||
public static final String WS_TYPE_HEART_BEAT = "WS_TYPE_HEART_BEAT";
|
||||
|
||||
public static final String WS_TYPE_SYSTEM_MESSAGE = "WS_TYPE_SYSTEM_MESSAGE";
|
||||
|
||||
public static final String WS_TYPE_MESSAGE_COUNT = "WS_TYPE_MESSAGE_COUNT";
|
||||
|
||||
public static final String WS_TYPE_NEW_CUSTOMER_REPLY = "WS_TYPE_NEW_CUSTOMER_REPLY";
|
||||
|
||||
public static final String WS_GET_UNREAD_COUNT = "GET_UNREAD_COUNT";
|
||||
|
||||
public static final String WS_PING = "ping";
|
||||
}
|
@ -225,5 +225,23 @@
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<resultMap type="SysServicesTopic" id="SysServicesCountResult">
|
||||
<result column="uid" property="uid"/>
|
||||
<result column="count" property="count"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectUnreadTopicCount" parameterType="java.util.List" resultMap="SysServicesCountResult">
|
||||
<foreach collection="list" item="status" index="index">
|
||||
<choose>
|
||||
<when test="index == 0">
|
||||
SELECT COUNT(*) AS count, uid FROM sys_services_topic_status WHERE `read` = 0 AND uid =
|
||||
#{status.uid}
|
||||
</when>
|
||||
<otherwise>
|
||||
UNION ALL SELECT COUNT(*) AS count, uid FROM sys_services_topic_status WHERE `read` = 0 AND uid =
|
||||
#{status.uid}
|
||||
</otherwise>
|
||||
</choose>
|
||||
</foreach>
|
||||
</select>
|
||||
</mapper>
|
@ -59,6 +59,23 @@
|
||||
<artifactId>stdiet-system</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- websocket-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -102,6 +102,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
"/custom/wxUserInfo/wx/**",
|
||||
"/custom/wxUserLog/wx/**",
|
||||
"/wx/**",
|
||||
// "/ws/**",
|
||||
"/wap/**",
|
||||
"/investigate/**",
|
||||
"/common/customerUploadFile",
|
||||
|
@ -0,0 +1,18 @@
|
||||
package com.stdiet.framework.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnWebApplication
|
||||
public class WebsocketConfig {
|
||||
|
||||
@Bean
|
||||
public ServerEndpointExporter serverEndpointExporter() {
|
||||
return new ServerEndpointExporter();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -11,26 +11,11 @@
|
||||
|
||||
<div class="right-menu">
|
||||
<template v-if="device !== 'mobile'">
|
||||
<!-- <search id="header-search" class="right-menu-item" /> -->
|
||||
|
||||
<!-- <el-tooltip content="源码地址" effect="dark" placement="bottom">
|
||||
<ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
|
||||
</el-tooltip> -->
|
||||
|
||||
<!-- <el-tooltip content="文档地址" effect="dark" placement="bottom">
|
||||
<ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />
|
||||
</el-tooltip> -->
|
||||
|
||||
<!-- <screenfull id="screenfull" class="right-menu-item hover-effect" /> -->
|
||||
|
||||
<!-- <el-tooltip content="布局大小" effect="dark" placement="bottom">
|
||||
<size-select id="size-select" class="right-menu-item hover-effect" />
|
||||
</el-tooltip> -->
|
||||
<div
|
||||
class="right-menu-item hover-effect badge_style"
|
||||
@click="handleOnMessageClick"
|
||||
>
|
||||
<el-badge :value="12">
|
||||
<el-badge :value="msgUnreadCount">
|
||||
<el-tooltip content="消息" effect="dark" placement="bottom">
|
||||
<em class="el-icon-message" :style="{ fontSize: '28px' }" />
|
||||
</el-tooltip>
|
||||
@ -66,25 +51,30 @@
|
||||
import { mapGetters, mapActions } from "vuex";
|
||||
import Breadcrumb from "@/components/Breadcrumb";
|
||||
import Hamburger from "@/components/Hamburger";
|
||||
// import Screenfull from "@/components/Screenfull";
|
||||
// import SizeSelect from "@/components/SizeSelect";
|
||||
// import Search from "@/components/HeaderSearch";
|
||||
// import RuoYiGit from "@/components/RuoYi/Git";
|
||||
// import RuoYiDoc from "@/components/RuoYi/Doc";
|
||||
import { keys } from "@/utils/websocket";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Breadcrumb,
|
||||
Hamburger,
|
||||
// Screenfull,
|
||||
// SizeSelect,
|
||||
// Search,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
msgNum: 0,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.init();
|
||||
},
|
||||
mounted() {
|
||||
window.postMessage({ type: keys.GET_UNREAD_COUNT }, window.location.origin);
|
||||
window.addEventListener("message", this.handleOnMessage);
|
||||
},
|
||||
unmounted() {
|
||||
window.removeEventListener("message", this.handleOnMessage);
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["sidebar", "avatar", "device"]),
|
||||
...mapGetters(["sidebar", "avatar", "device", "msgUnreadCount"]),
|
||||
setting: {
|
||||
get() {
|
||||
return this.$store.state.settings.showSettings;
|
||||
@ -98,7 +88,14 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(["init"]),
|
||||
...mapActions(["init", "updateUnreadCount"]),
|
||||
handleOnMessage({ data }) {
|
||||
if (data.type === keys.WS_TYPE_MESSAGE_COUNT) {
|
||||
this.updateUnreadCount({
|
||||
msgUnreadCount: data.data.count,
|
||||
});
|
||||
}
|
||||
},
|
||||
toggleSideBar() {
|
||||
this.$store.dispatch("app/toggleSideBar");
|
||||
},
|
||||
|
@ -18,12 +18,16 @@ import "./permission"; // permission control
|
||||
import { getDicts } from "@/api/system/dict/data";
|
||||
import { getConfigKey } from "@/api/system/config";
|
||||
import VueScrollTo from "vue-scrollto";
|
||||
import VueResource from "vue-resource"
|
||||
import HighchartsVue from 'highcharts-vue'
|
||||
import Highcharts from 'highcharts'
|
||||
import VueResource from "vue-resource";
|
||||
import HighchartsVue from "highcharts-vue";
|
||||
import Highcharts from "highcharts";
|
||||
import { init } from "@/utils/websocket";
|
||||
//图片导出模块
|
||||
import exportingInit from 'highcharts/modules/exporting'
|
||||
exportingInit(Highcharts)
|
||||
import exportingInit from "highcharts/modules/exporting";
|
||||
exportingInit(Highcharts);
|
||||
|
||||
// websocket 初始化
|
||||
init();
|
||||
|
||||
import {
|
||||
addDateRange,
|
||||
@ -111,7 +115,7 @@ Vue.use(VueScrollTo, {
|
||||
y: true
|
||||
});
|
||||
|
||||
Vue.use(VueResource)
|
||||
Vue.use(VueResource);
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
|
@ -1,6 +1,9 @@
|
||||
const actions = {
|
||||
async init({ dispatch }, payload) {
|
||||
dispatch("global/init", payload);
|
||||
},
|
||||
async updateUnreadCount({ commit }, payload) {
|
||||
commit("global/save", payload);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -9,12 +9,11 @@ const getters = {
|
||||
name: state => state.user.name,
|
||||
introduction: state => state.user.introduction,
|
||||
roles: state => state.user.roles,
|
||||
// roles: state => ["dietician"],
|
||||
permissions: state => state.user.permissions,
|
||||
userId: state => state.user.userId,
|
||||
// userId: state => 131,
|
||||
userRemark: state => state.user.remark,
|
||||
permission_routes: state => state.permission.routes,
|
||||
msgUnreadCount: state => state.global.msgUnreadCount,
|
||||
//
|
||||
nutritionistIdOptions: state => state.global.nutritionistIdOptions,
|
||||
nutriAssisIdOptions: state => state.global.nutriAssisIdOptions,
|
||||
|
@ -10,6 +10,7 @@ const oriState = {
|
||||
plannerAssisIdOptions: [],
|
||||
operatorIdOptions: [],
|
||||
operatorAssisIdOptions: [],
|
||||
msgUnreadCount: 0,
|
||||
pushPreSaleIdOptions:[]
|
||||
};
|
||||
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
} from "@/api/custom/message";
|
||||
|
||||
const oriState = {
|
||||
pageNum: 1,
|
||||
topicList: [],
|
||||
detailData: {},
|
||||
selTopicId: ""
|
||||
@ -28,23 +29,35 @@ const mutations = {
|
||||
};
|
||||
|
||||
const actions = {
|
||||
async init({ rootGetters, commit, dispatch }, payload) {
|
||||
async init({ dispatch }, payload) {
|
||||
dispatch("fetchTopicListApi", {});
|
||||
},
|
||||
async fetchTopicListApi({ dispatch, commit, rootGetters, state }, payload) {
|
||||
const {
|
||||
roles: [role],
|
||||
userId
|
||||
} = rootGetters;
|
||||
const result = await fetchTopicList({ role, uid: userId });
|
||||
const { detailData, pageNum, topicList } = state;
|
||||
const result = await fetchTopicList({
|
||||
role,
|
||||
uid: userId,
|
||||
pageSize: 20,
|
||||
pageNum
|
||||
});
|
||||
if (result.code === 200) {
|
||||
const [defTopic] = result.rows;
|
||||
|
||||
dispatch("fetchTopicDetailActions", {
|
||||
topicId: defTopic.topicId,
|
||||
id: defTopic.id
|
||||
});
|
||||
|
||||
commit("save", {
|
||||
topicList: result.rows
|
||||
});
|
||||
if (!detailData.topicId) {
|
||||
const [defTopic] = result.rows;
|
||||
dispatch("fetchTopicDetailActions", {
|
||||
topicId: defTopic.topicId,
|
||||
id: defTopic.id
|
||||
});
|
||||
}
|
||||
if (result.rows.length) {
|
||||
commit("save", {
|
||||
pageNum: pageNum + 1,
|
||||
topicList: [...topicList, ...result.rows]
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
async fetchTopicDetailActions({ commit }, payload) {
|
||||
|
72
stdiet-ui/src/utils/websocket.js
Normal file
72
stdiet-ui/src/utils/websocket.js
Normal file
@ -0,0 +1,72 @@
|
||||
const { protocol, hostname, origin, port } = window.location;
|
||||
const wsProtocol = protocol.startsWith("https") ? "wss" : "ws";
|
||||
const url = `${wsProtocol}://${hostname}${
|
||||
hostname === "localhost" ? ":8091" : ""
|
||||
}/ws`;
|
||||
let ws = undefined;
|
||||
let intervalRef = undefined;
|
||||
|
||||
function handleOnMessageReceive({ data = {} }) {
|
||||
if (data.type && data.type === keys.GET_UNREAD_COUNT) {
|
||||
ws.send(keys.GET_UNREAD_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
function connect() {
|
||||
try {
|
||||
ws = new WebSocket(url);
|
||||
|
||||
ws.onopen = event => {
|
||||
console.log("ws连接成功");
|
||||
intervalRef && clearInterval(intervalRef);
|
||||
|
||||
setInterval(() => {
|
||||
ws.send("ping");
|
||||
}, 30000);
|
||||
|
||||
window.addEventListener("message", handleOnMessageReceive);
|
||||
};
|
||||
|
||||
ws.onmessage = event => {
|
||||
// console.log({ event });
|
||||
|
||||
const dataObj = JSON.parse(event.data || "{}");
|
||||
|
||||
if (dataObj.type && dataObj.type !== "WS_TYPE_HEART_BEAT") {
|
||||
window.postMessage(dataObj, origin);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = event => {
|
||||
// console.log({ event });
|
||||
ws = undefined;
|
||||
window.removeEventListener("message", handleOnMessageReceive);
|
||||
};
|
||||
|
||||
ws.onclose = event => {
|
||||
ws = undefined;
|
||||
window.removeEventListener("message", handleOnMessageReceive);
|
||||
};
|
||||
} catch (error) {
|
||||
// console.log(error);
|
||||
ws = undefined;
|
||||
init();
|
||||
console.log("浏览器不支持websocket");
|
||||
}
|
||||
}
|
||||
|
||||
export function init() {
|
||||
!ws && connect();
|
||||
|
||||
!ws &&
|
||||
(intervalRef = setInterval(() => {
|
||||
console.log("尝试连接websocket");
|
||||
!ws && connect();
|
||||
}, 10000));
|
||||
}
|
||||
|
||||
export const keys = {
|
||||
GET_UNREAD_COUNT: "GET_UNREAD_COUNT",
|
||||
WS_TYPE_MESSAGE_COUNT: "WS_TYPE_MESSAGE_COUNT",
|
||||
WS_TYPE_NEW_CUSTOMER_REPLY: "WS_TYPE_NEW_CUSTOMER_REPLY"
|
||||
};
|
@ -1,28 +1,35 @@
|
||||
<template>
|
||||
<div class="message_browser_wrapper">
|
||||
<div class="topic_list">
|
||||
<div
|
||||
v-for="topic in topicList"
|
||||
:key="topic.topicId"
|
||||
:class="`topic_item ${
|
||||
selTopicId === topic.topicId ? 'topic_item_sel' : ''
|
||||
}`"
|
||||
@click="handleOnTopicClick(topic)"
|
||||
>
|
||||
<div class="topic_status topic_status_read" />
|
||||
<div class="topic_item_content">
|
||||
<div class="topic_content">{{ topic.content }}</div>
|
||||
<div class="topic_user_name">by {{ topic.name }}</div>
|
||||
</div>
|
||||
<div class="topic_info">
|
||||
<el-tag size="small">{{ topicTypeDict[topic.topicType] }}</el-tag>
|
||||
<div class="topic_time">{{ formatDate(topic.createTime) }}</div>
|
||||
<div class="topic_list" @scroll="handleOnScroll">
|
||||
<div v-if="topicList && topicList.length">
|
||||
<div
|
||||
v-for="topic in topicList"
|
||||
:key="topic.topicId"
|
||||
:class="`topic_item ${
|
||||
selTopicId === topic.topicId ? 'topic_item_sel' : ''
|
||||
}`"
|
||||
@click="handleOnTopicClick(topic)"
|
||||
>
|
||||
<div
|
||||
:class="`topic_status ${
|
||||
topic.read ? 'topic_status_read' : 'topic_status_unread'
|
||||
}`"
|
||||
/>
|
||||
<div class="topic_item_content">
|
||||
<div class="topic_content">{{ topic.content }}</div>
|
||||
<div class="topic_user_name">by {{ topic.name }}</div>
|
||||
</div>
|
||||
<div class="topic_info">
|
||||
<el-tag size="small">{{ topicTypeDict[topic.topicType] }}</el-tag>
|
||||
<div class="topic_time">{{ formatDate(topic.createTime) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="topic_list_empty">暂无消息</div>
|
||||
</div>
|
||||
<div class="topic_detail">
|
||||
<div class="topic_detail_list">
|
||||
<div class="topic_detail_title">
|
||||
<div class="topic_detail_title" v-if="!!detailData.content">
|
||||
<div>{{ detailData.content }}</div>
|
||||
<div class="content_time" :style="{ marginTop: '4px' }">
|
||||
{{ formatDate(detailData.createTime) }}
|
||||
@ -50,7 +57,12 @@
|
||||
>
|
||||
回复:{{ replyTarget }}
|
||||
</div>
|
||||
<el-input type="textarea" :rows="3" v-model="replyContent" />
|
||||
<el-input
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
v-model="replyContent"
|
||||
:disabled="!detailData.content"
|
||||
/>
|
||||
<div class="send_btn_zone">
|
||||
<el-button
|
||||
type="primary"
|
||||
@ -65,9 +77,10 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { createNamespacedHelpers } from "vuex";
|
||||
import { createNamespacedHelpers, mapActions as globalMapActions } from "vuex";
|
||||
import Comment from "./Comment";
|
||||
import dayjs from "dayjs";
|
||||
import { keys } from "@/utils/websocket";
|
||||
const {
|
||||
mapActions,
|
||||
mapState,
|
||||
@ -91,10 +104,61 @@ export default {
|
||||
created() {
|
||||
this.init();
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener("message", this.handleOnMessage);
|
||||
},
|
||||
unmounted() {
|
||||
window.removeEventListener("message", this.handleOnMessage);
|
||||
},
|
||||
computed: {
|
||||
...mapState(["topicList", "selTopicId", "detailData"]),
|
||||
},
|
||||
methods: {
|
||||
handleOnScroll({ target }) {
|
||||
if (
|
||||
target.clientHeight + parseInt(target.scrollTop) ===
|
||||
target.scrollHeight
|
||||
) {
|
||||
this.fetchTopicListApi();
|
||||
}
|
||||
},
|
||||
handleOnMessage({ data }) {
|
||||
if (data.type === keys.WS_TYPE_MESSAGE_COUNT) {
|
||||
const { data: tData } = data.data;
|
||||
const time = dayjs(tData.createTime).format("YYYY-MM-DD HH:mm:ss");
|
||||
const newTopicList = [
|
||||
{
|
||||
id: tData.id,
|
||||
content: tData.content,
|
||||
createTime: time,
|
||||
img: tData.img,
|
||||
topicId: tData.topicId,
|
||||
role: "customer",
|
||||
uid: tData.uid,
|
||||
updateTime: time,
|
||||
topicType: tData.topicType,
|
||||
read: tData.read,
|
||||
},
|
||||
...this.topicList,
|
||||
];
|
||||
this.save({
|
||||
topicList: newTopicList,
|
||||
});
|
||||
} else if (data.type === keys.WS_TYPE_NEW_CUSTOMER_REPLY) {
|
||||
const { count, topicId } = data.data;
|
||||
this.updateUnreadCount({
|
||||
msgUnreadCount: count,
|
||||
});
|
||||
if (this.selTopicId === topicId) {
|
||||
const tarTopic = this.topicList.find(
|
||||
(obj) => obj.topicId === topicId
|
||||
);
|
||||
if (tarTopic) {
|
||||
this.fetchTopicDetailActions({ topicId, id: tarTopic.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
formatDate(date) {
|
||||
return dayjs(date).format("MM-DD HH:mm");
|
||||
},
|
||||
@ -147,8 +211,14 @@ export default {
|
||||
this.$message.error("请选择回复对象");
|
||||
}
|
||||
},
|
||||
...mapActions(["init", "fetchTopicDetailActions", "postTopicReplyActions"]),
|
||||
...mapMutations(["clean"]),
|
||||
...mapActions([
|
||||
"init",
|
||||
"fetchTopicDetailActions",
|
||||
"postTopicReplyActions",
|
||||
"fetchTopicListApi",
|
||||
]),
|
||||
...mapMutations(["clean", "save"]),
|
||||
...globalMapActions(["updateUnreadCount"]),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@ -157,6 +227,7 @@ export default {
|
||||
display: flex;
|
||||
.topic_list {
|
||||
flex: 2;
|
||||
overflow: auto;
|
||||
|
||||
.topic_item {
|
||||
display: flex;
|
||||
@ -223,6 +294,13 @@ export default {
|
||||
.topic_item_sel {
|
||||
background: #dedede;
|
||||
}
|
||||
|
||||
.topic_list_empty {
|
||||
height: 100px;
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
}
|
||||
|
||||
.topic_detail {
|
||||
|
Loading…
x
Reference in New Issue
Block a user