index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. <template>
  2. <div class="commons-notice">
  3. <el-dialog title="消息通知" v-model="value" width="460px" :align-center="true" :before-close="handleClose">
  4. <div class="title">
  5. <!-- {{ data.length == 0 ? '暂无数据' : data[indexCopy].title}} -->
  6. </div>
  7. <div class="text-content" v-if="data.length > 0" style="margin-top: 10px">
  8. <h3>{{ data[index].title }}</h3>
  9. {{ data[index].businessData }}
  10. </div>
  11. <template #footer>
  12. <span class="dialog-footer">
  13. <el-button size="small" @click="index--" :disabled="data.length < 2 || index == 0">上一条</el-button>
  14. <el-button size="small" @click="index++" :disabled="data.length == index + 1">下一条</el-button>
  15. <el-button type="primary" size="small" :disabled="data.length == 0 || data[index].isRead" @click="confirm">确认已读</el-button>
  16. <!-- <span class="more" @click="moreFn">查看更多 &gt;</span> -->
  17. </span>
  18. </template>
  19. </el-dialog>
  20. <div class="notice-table-warp" :class="modelValue ? 'notice-table-warp-open' : ''" @click.stop="closeNoticeTableModal">
  21. <div class="notice-table" @click.stop v-loading="loading">
  22. <div class="tabs">
  23. <ul>
  24. <li style="padding-left: 0; border: none" @click="
  25. pushInfoReq.whetherFlow = '';
  26. getPushInfoInit();
  27. " :class="pushInfoReq.whetherFlow === '' ? 'active' : ''">
  28. 全部<span v-if="pushInfoReq.whetherFlow === ''">({{ pushInfoReq.total }})</span>
  29. </li>
  30. <li @click="
  31. pushInfoReq.whetherFlow = 1;
  32. getPushInfoInit();
  33. " :class="pushInfoReq.whetherFlow === 1 ? 'active' : ''">
  34. 流程<span v-if="pushInfoReq.whetherFlow === 1">({{ pushInfoReq.total }})</span>
  35. </li>
  36. <li @click="
  37. pushInfoReq.whetherFlow = 0;
  38. getPushInfoInit();
  39. " :class="pushInfoReq.whetherFlow === 0 ? 'active' : ''">
  40. 业务<span v-if="pushInfoReq.whetherFlow === 0">({{ pushInfoReq.total }})</span>
  41. </li>
  42. </ul>
  43. <div class="more" @click="toDealWith({ businessType: 'hisMsg' })">
  44. 查看更多&gt;
  45. </div>
  46. </div>
  47. <el-table :data="noticeData" style="width: 100%" @row-click="rowClick">
  48. <el-table-column prop="title" label="标题内容" width="250">
  49. <template #default="scope">
  50. <el-tooltip class="box-item" effect="dark" :content="scope.row.title" placement="top">
  51. <div class="noticeData-title">{{ scope.row.title }}</div>
  52. </el-tooltip>
  53. </template>
  54. </el-table-column>
  55. <el-table-column prop="businessType" label="类型" width="120">
  56. <template #default="scope">
  57. <span>{{ scope.row.businessType === 0 ? "流程" : "业务" }}</span>
  58. </template>
  59. </el-table-column>
  60. <el-table-column prop="address" label="操作">
  61. <template #default="scope">
  62. <span style="cursor: pointer; color: #0084ff" @click.stop="readFn(scope)">已读</span>
  63. </template>
  64. </el-table-column>
  65. </el-table>
  66. <div>
  67. <el-pagination style="text-align: center" :page-size="5" layout="prev, pager, next" :current-page="pushInfoReq.pageNum"
  68. :total="pushInfoReq.total" @current-change="handlePageChange" />
  69. </div>
  70. <div class="notice-btn-box" style="margin-top: 20px">
  71. <!-- <el-button plain disabled>点击清空</el-button> -->
  72. <el-button type="primary" @click="allReadFn" v-if="noticeData.length != 0">全部已读</el-button>
  73. </div>
  74. </div>
  75. </div>
  76. </div>
  77. </template>
  78. <script setup>
  79. import { ElMessageBox, ElNotification, ElMessage } from "element-plus";
  80. import { getToken } from "@/utils/auth";
  81. const { proxy } = getCurrentInstance();
  82. defineProps({
  83. modelValue: {
  84. type: Object,
  85. default: false,
  86. },
  87. });
  88. const emit = defineEmits(["update:modelValue"], "changeNum");
  89. const closeNoticeTableModal = () => {
  90. emit("update:modelValue", false);
  91. };
  92. let noticeData = ref([]);
  93. let index = ref(0);
  94. let data = ref([]);
  95. let value = ref(false);
  96. const moreFn = () => {
  97. noticeTableModal.value = true;
  98. };
  99. const rowClick = (row) => {
  100. commonRead([row.id]);
  101. toDealWith(row);
  102. };
  103. const toDealWith = (item) => {
  104. let urlConfig = {
  105. 0: "DealWith",
  106. 5: "Claim",
  107. 6: "Abnormal",
  108. hisMsg: "HisMsg",
  109. };
  110. proxy.$router.push({
  111. name: urlConfig[item.businessType],
  112. });
  113. };
  114. const allReadFn = () => {
  115. ElMessageBox.confirm(
  116. "此操作将会把所有未读消息标记为已读,是否继续?",
  117. "提示",
  118. {
  119. confirmButtonText: "确定",
  120. cancelButtonText: "取消",
  121. type: "warning",
  122. }
  123. )
  124. .then(() => {
  125. let arr = [];
  126. noticeData.value.filter((item) => {
  127. arr.push(item.id);
  128. });
  129. commonRead(arr);
  130. })
  131. .catch(() => {
  132. ElMessage({
  133. type: "info",
  134. message: "已取消操作",
  135. });
  136. });
  137. };
  138. const readFn = (item) => {
  139. commonRead([item.row.id]);
  140. };
  141. const commonRead = (ids, type) => {
  142. proxy.post("/pushInfo/read", { idList: ids }).then((res) => {
  143. console.log(res);
  144. ElMessage({
  145. message: "已读成功",
  146. type: "success",
  147. });
  148. if (type == "confirm") {
  149. data.value[index].isRead = true;
  150. } else {
  151. getPushInfo();
  152. }
  153. });
  154. };
  155. const confirm = () => {
  156. value.value = false;
  157. commonRead([data.value[index.value].id], "confirm");
  158. };
  159. // proxy.post('sendMeg/page',{
  160. // pageNum:1,
  161. // pageSize:30,
  162. // }).then(res=>{
  163. // data.value = res.rows
  164. // })
  165. const createNotification = (body, title = "通知") => {
  166. let notification = null;
  167. if (!("Notification" in window)) {
  168. // 检查浏览器是否支持通知
  169. alert("当前浏览器不支持桌面通知");
  170. } else if (Notification.permission === "granted") {
  171. // 检查是否已授予通知权限;如果是的话,创建一个通知
  172. notification = new Notification(title, {
  173. data: {
  174. originUrl: window.location.origin + "/index",
  175. },
  176. body: body, //一个表示通知正文的字符串,显示在标题下方。
  177. // tag: "aa", 一个表示通知的识别标签的字符串,默认值是一个空字符串。
  178. // image: chart, 一个包含要在通知中显示的图像的 URL 的字符串。
  179. // icon: chart, 一个包含要在通知中显示的图标的 URL 的字符串。
  180. });
  181. notification.onclick = function (event) {
  182. event.preventDefault();
  183. // 打开新的穿口
  184. // window.open("http://localhost/ehsd/dataBoard/ehsd_productAnalysis");
  185. // 打开页面
  186. window.location.href = event.currentTarget.data.originUrl;
  187. notification.close();
  188. };
  189. // …
  190. } else if (Notification.permission !== "denied") {
  191. // 我们需要征求用户的许可
  192. Notification.requestPermission().then((permission) => {
  193. // 如果用户接受,我们就创建一个通知
  194. if (permission === "granted") {
  195. notification = new Notification(title, {
  196. data: {
  197. originUrl: window.location.origin + "/index",
  198. },
  199. body: body, //一个表示通知正文的字符串,显示在标题下方。
  200. // tag: "aa", 一个表示通知的识别标签的字符串,默认值是一个空字符串。
  201. // image: chart, 一个包含要在通知中显示的图像的 URL 的字符串。
  202. // icon: chart, 一个包含要在通知中显示的图标的 URL 的字符串。
  203. });
  204. notification.onclick = function (event) {
  205. event.preventDefault();
  206. window.location.href = event.currentTarget.data.originUrl;
  207. notification.close();
  208. };
  209. }
  210. });
  211. }
  212. };
  213. const socket = ref(null);
  214. const intervalId = ref(null);
  215. const sendSocketMsg = () => {
  216. let jsonData = {
  217. cmd: "heartbeat",
  218. };
  219. socket.value.send(JSON.stringify(jsonData));
  220. console.log("Timer executed!");
  221. };
  222. const socketInit = () => {
  223. let prefix =
  224. window.location.protocol.indexOf("https") >= 0 ? "wss://" : "ws://";
  225. socket.value = new WebSocket(
  226. prefix +
  227. import.meta.env.VITE_APP_IP +
  228. import.meta.env.VITE_APP_WS_API +
  229. "/webStock/" +
  230. getToken()
  231. );
  232. //申请一个WebSocket对象,参数是服务端地址,同http协议使用http://开头一样,WebSocket协议的url使用ws://开头,另外安全的WebSocket协议使用wss://开头
  233. socket.value.onopen = function () {
  234. //当WebSocket创建成功时,触发onopen事件
  235. console.log("open");
  236. //立马发送一次心跳
  237. sendSocketMsg();
  238. // 设置定时器,每30秒执行一次myFunction
  239. intervalId.value = setInterval(sendSocketMsg, 1000 * 30);
  240. };
  241. socket.value.onmessage = function (e) {
  242. //当客户端收到服务端发来的消息时,触发onmessage事件,参数e.data包含server传递过来的数据
  243. //在data.value前面插入
  244. const res = JSON.parse(e.data);
  245. console.log(res, "socket-message");
  246. if (res.type == 1) {
  247. index.value = 0;
  248. data.value = res.list;
  249. if (res.list.length > 0) value.value = true;
  250. }
  251. if (res.type == 2 && res.count != "0") {
  252. emit("changeNum", res.count * 1);
  253. proxy.$emit("openNotice");
  254. getPushInfo(true);
  255. }
  256. if (res.type == 3) {
  257. ElNotification({
  258. title: "提示",
  259. message: res.title,
  260. position: "bottom-right",
  261. duration: 0,
  262. });
  263. // 桌面通知
  264. createNotification(res.title);
  265. }
  266. };
  267. socket.value.onclose = function (e) {
  268. //当客户端收到服务端发送的关闭连接请求时,触发onclose事件
  269. console.log("socket-close");
  270. intervalId.value && clearInterval(intervalId.value);
  271. };
  272. socket.value.onerror = function (e) {
  273. //如果出现连接、处理、接收、发送数据失败的时候触发onerror事件
  274. console.log("socket-error", e);
  275. intervalId.value && clearInterval(intervalId.value);
  276. };
  277. };
  278. let pushInfoReq = ref({
  279. pageNum: 1,
  280. pageSize: 5,
  281. pushRead: 0,
  282. type: "",
  283. total: 0,
  284. whetherFlow: "",
  285. });
  286. const handlePageChange = (val) => {
  287. pushInfoReq.value.pageNum = val;
  288. getPushInfo();
  289. };
  290. const loading = ref(false);
  291. const getPushInfoInit = () => {
  292. pushInfoReq.value.pageNum = 1;
  293. getPushInfo();
  294. };
  295. const getPushInfo = (flag) => {
  296. loading.value = true;
  297. proxy.post("/pushInfo/page", pushInfoReq.value).then((res) => {
  298. noticeData.value = res.rows;
  299. pushInfoReq.value.total = res.total;
  300. // 桌面通知
  301. if (flag && noticeData.value.length > 0) {
  302. let current = noticeData.value.find((x) => x.businessType == 0);
  303. if (current) {
  304. createNotification(current.title);
  305. index.value = 0;
  306. // current.businessData = current.title;
  307. // current.title = "流程";
  308. // data.value = [current];
  309. // value.value = true;
  310. }
  311. }
  312. setTimeout(() => {
  313. loading.value = false;
  314. proxy
  315. .post("/pushInfo/page", {
  316. pageNum: 1,
  317. pageSize: 5,
  318. pushRead: 0,
  319. type: "",
  320. total: 0,
  321. whetherFlow: "",
  322. })
  323. .then((res) => {
  324. emit("changeNum", res.total * 1);
  325. });
  326. }, 550);
  327. });
  328. };
  329. socketInit();
  330. const handleClose = () => {
  331. value.value = false;
  332. };
  333. const openBottomBar = (type) => {
  334. if (type == 1) {
  335. ElNotification({
  336. message: "您有一封新的邮件,请注意查收。",
  337. type: "warning",
  338. position: "bottom-right",
  339. });
  340. } else if (type == 2) {
  341. value.value = true;
  342. } else {
  343. noticeTableModal.value = true;
  344. }
  345. };
  346. </script>
  347. <style lang="scss">
  348. .notice-table-warp {
  349. position: fixed;
  350. right: 0;
  351. top: 0;
  352. width: 100%;
  353. height: 100%;
  354. z-index: 1000;
  355. background: rgba(0, 0, 0, 0.1);
  356. transition: all 0.3s ease-in-out;
  357. opacity: 0;
  358. display: none;
  359. }
  360. .notice-table-warp-open {
  361. opacity: 1;
  362. display: block;
  363. }
  364. .notice-table {
  365. position: fixed;
  366. right: 2px;
  367. top: 52px;
  368. padding: 0 20px 20px;
  369. width: 500px;
  370. background: #fff;
  371. box-shadow: 0px 2px 20px 1px rgba(0, 0, 0, 0.1);
  372. z-index: 20;
  373. .notice-btn-box {
  374. text-align: right;
  375. }
  376. .tabs {
  377. display: flex;
  378. justify-content: space-between;
  379. height: 60px;
  380. line-height: 60px;
  381. font-size: 14px;
  382. font-weight: 400;
  383. ul {
  384. display: flex;
  385. margin: 23px 0;
  386. padding: 0;
  387. li {
  388. list-style: none;
  389. padding: 0 20px;
  390. cursor: pointer;
  391. height: 14px;
  392. line-height: 14px;
  393. border-left: 1px solid #dcdcdc;
  394. }
  395. .active {
  396. color: #0084ff;
  397. }
  398. }
  399. .more {
  400. color: #0084ff;
  401. cursor: pointer;
  402. }
  403. }
  404. }
  405. </style>
  406. <style>
  407. .noticeData-title {
  408. width: 100%;
  409. overflow: hidden;
  410. text-overflow: ellipsis;
  411. white-space: nowrap;
  412. cursor: pointer;
  413. }
  414. .commons-notice .el-table__row {
  415. height: 50px;
  416. }
  417. .commons-notice .el-dialog__footer {
  418. text-align: left !important;
  419. margin-top: 24px;
  420. }
  421. .commons-notice .more {
  422. color: #0084ff;
  423. float: right;
  424. cursor: pointer;
  425. }
  426. .commons-notice .el-dialog__header {
  427. background: #eeeeee;
  428. }
  429. .commons-notice .el-dialog__title {
  430. position: relative;
  431. padding-left: 30px;
  432. color: #333333;
  433. font-weight: bold;
  434. }
  435. .commons-notice .el-dialog__title::before {
  436. content: " ";
  437. position: absolute;
  438. left: 0;
  439. top: 0;
  440. width: 20px;
  441. height: 20px;
  442. background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAftJREFUOE+tlT9MU1EUxr9zWwgBI/D6Boh/EnbCRiImMhBCGqNhcnDQrbgRAyxGBgYICzXYDbrB4OBENIaY4GAMrmqc3DQxMrxWNGkH0Hv83m0ptvS1EHqTm/fuO+f+zjn3nHueoGYoIFju7kGXGeTrJOd1qlwqq30HdJdzCwX7GY9+7QsX/yO4Ph66gHb0+EkiUzAyRklnrcHyugirb4jKYj/YlgUcHOlVgJrxLkLMHJVSEOmLAFV/Vt2j8SzUrsh0/ncodEDnmec9BswsYV1Vu1RD6+842zmvUR6vkRcAm0Y+vxR6Ku7MVv3bMFir65nVT/gZDNNgBw1+oM7ACe9DTy0e4GHwQnS5uxedbRs8s1t1w1S7I9O5cRfJU/8j9Ybq6ll9ieLhfdFM7w1IfDsyAacFAkXonySB3gokNhuZhNMDGcLfNIGJXWZ3pDVA+z4EfiXwaouA384GzDApEpEUlzXrgI1Dhn7B4cEITOwCYob1aK40iCYMuVlS1BLAY0GMz8ssWxMNdElpVjaaJ+RZCaJ3GbIXASyXTdPC1udAcK8E8TcJvNO4sJtePfsa/bmbDvIj8QrGTDS8ei6QsDkk/Hl2mpm6zUH0bSliGaU8bBLHQ7XA832CXLDomsORxLUvgDfGTJ2pfcGuc1+6qn1VoK1ssBXoOX8B/wDYqv5bje949AAAAABJRU5ErkJggg==);
  443. }
  444. </style>