├── .gitignore ├── LICENSE ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html └── src ├── App.vue ├── assets └── logo.png ├── components ├── FollowNavbar.vue ├── HelloWorld.vue ├── MessageTabCom.vue ├── Navbar.vue ├── SafeCom.vue ├── SettingCom.vue ├── SettingTabCom.vue ├── UserNavbar.vue ├── chat │ ├── OppositeMsgItem.vue │ └── OwnMsgItem.vue └── msg │ ├── ChatMe.vue │ ├── FollowMe.vue │ ├── LikeMe.vue │ ├── ReplyMe.vue │ └── SystemMe.vue ├── main.js ├── plugins └── element.js ├── router └── index.js ├── store └── index.js └── views ├── Admin.vue ├── Collection.vue ├── Data.vue ├── Followees.vue ├── Followers.vue ├── Forget.vue ├── Hot.vue ├── Index.vue ├── Ken.vue ├── Login.vue ├── Message.vue ├── Notice.vue ├── NoticeDetail.vue ├── PostAdd.vue ├── PostDetail.vue ├── Profile.vue ├── Register.vue ├── Search.vue ├── Setting.vue └── UserPosts.vue /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 warson.Loong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 论坛在线交流项目 2 | 这是一个论坛项目,我称之为ken社区。它是基于前后端分离的思想,采用springboot+vue开发的。用户可以在这个论坛里发表自己的文章,与其他人一起交流。 3 | 也可以关注别人,收藏别人的文章,点赞、回复评论别人的文章/评论,也可以与别人进行聊天(类似于网页版微信),是一个让大家可以一起交流、促进学习的地方。 4 | 5 | ## 功能实现: 6 | - 发表文章(markdown格式,富文本编辑,可上传图片) 7 | - 评论文章 8 | - 回复评论 9 | - 邮件发送(注册时发送确认邮件) 10 | - 关注 11 | - 收藏 12 | - 点赞 13 | - 搜索 14 | - 聊天(类似网页版微信) 15 | 16 | ## 前端工程地址:https://github.com/KenLoong/forum_front 17 | ## 后端工程地址:https://github.com/KenLoong/forum_serve 18 | 19 | ## 后端技术栈 20 | - Springboot 21 | - Shiro 22 | - Elasticsearch 23 | - Mysql 24 | - Redis 25 | - Websocket 26 | - jwt 27 | - Mybatis 28 | 29 | ## 前端技术栈 30 | - Vue 31 | - ElementUI 32 | - vue-router 33 | - Websocket 34 | - axios 35 | - mavon-editor 36 | 37 | 38 | ## 技术实现 39 | - 采用jwt、自定义注解、拦截器组合的方式来进行登录校验 40 | - 采用shiro框架来进行权限控制(对文章进行置顶、加精) 41 | - 采用websocket技术实现即时聊天 42 | - 采用redis数据库来存储点赞、关注、收藏等信息,提高系统运行速度 43 | - 采用elasticsearch对文章进行索引,实现搜索功能 44 | - 采用线程池来实现通知消息的异步写入 45 | - 采用vue-cli来搭建前端项目,利用axios来做异步请求,vuex存储用户信息 46 | - 采用七牛云服务器来实现图片的存储 47 | - 采用element-ui,mavon-editor(富文本编辑),bootstrap来美化用户视觉 48 | 49 | ## 项目预览图 50 | 发表文章 51 | ![发表文章](https://github.com/KenLoong/img_db/blob/main/20201210205131.png) 52 | 53 | 搜索文章 54 | ![搜索文章](https://github.com/KenLoong/img_db/blob/main/20201210205429.png) 55 | 56 | 个人主页 57 | ![个人主页](https://github.com/KenLoong/img_db/blob/main/20201210210216.png) 58 | 59 | 粉丝页面 60 | ![粉丝页面](https://github.com/KenLoong/img_db/blob/main/20201211104419.png) 61 | 62 | 最热文章 63 | ![最热文章](https://github.com/KenLoong/img_db/blob/main/20201211104653.png) 64 | 65 | 最新文章 66 | ![最新文章](https://github.com/KenLoong/img_db/blob/main/20201211110409.png) 67 | 68 | 聊天 69 | ![聊天](https://github.com/KenLoong/img_db/blob/main/4505c657d808ff74a65eeadc7443ce0.png) 70 | 71 | 评论、回复 72 | ![评论](https://github.com/KenLoong/img_db/blob/main/80f2052c7750ab8b129ce8cbeea7f99.png) 73 | 74 | 设置页面 75 | ![设置页面](https://github.com/KenLoong/img_db/blob/main/8cdbf646c19a75f5c3db3c165c3706e.png) 76 | 77 | 别人的主页 78 | ![别人的主页](https://github.com/KenLoong/img_db/blob/main/db427cbf3ebf5098e84c269bae2233d.png) 79 | 80 | 消息中心 81 | ![消息中心](https://github.com/KenLoong/img_db/blob/main/f4772a46986e910e5a400d64126dc06.png) 82 | 83 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forum_vue", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build" 8 | }, 9 | "dependencies": { 10 | "axios": "^0.19.2", 11 | "bootstrap": "^4.5.2", 12 | "bootstrap-vue": "^2.16.0", 13 | "core-js": "^3.6.5", 14 | "element-ui": "^2.4.5", 15 | "markdown-it": "^12.0.2", 16 | "mavon-editor": "^2.9.0", 17 | "moment": "^2.29.1", 18 | "perfect-scrollbar": "^1.5.0", 19 | "vue": "^2.6.11", 20 | "vue-router": "^3.2.0", 21 | "vuex": "^3.4.0" 22 | }, 23 | "devDependencies": { 24 | "@vue/cli-plugin-babel": "~4.5.0", 25 | "@vue/cli-plugin-router": "~4.5.0", 26 | "@vue/cli-plugin-vuex": "~4.5.0", 27 | "@vue/cli-service": "~4.5.0", 28 | "vue-cli-plugin-element": "^1.0.1", 29 | "vue-template-compiler": "^2.6.11" 30 | }, 31 | "browserslist": [ 32 | "> 1%", 33 | "last 2 versions", 34 | "not dead" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenLoong/forum_front/1e70997961a467e66410658ad176bc23c34ef028/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%= htmlWebpackPlugin.options.title %> 10 | 11 | 12 | 16 |
17 | 18 | 19 | 24 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | 15 | 25 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KenLoong/forum_front/1e70997961a467e66410658ad176bc23c34ef028/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/FollowNavbar.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 26 | 27 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 46 | 47 | 48 | 67 | -------------------------------------------------------------------------------- /src/components/MessageTabCom.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 110 | 111 | -------------------------------------------------------------------------------- /src/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 159 | 160 | -------------------------------------------------------------------------------- /src/components/SafeCom.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 113 | 114 | -------------------------------------------------------------------------------- /src/components/SettingCom.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 105 | 106 | -------------------------------------------------------------------------------- /src/components/SettingTabCom.vue: -------------------------------------------------------------------------------- 1 | 2 | 31 | 32 | 60 | 61 | -------------------------------------------------------------------------------- /src/components/UserNavbar.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 41 | 42 | -------------------------------------------------------------------------------- /src/components/chat/OppositeMsgItem.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 89 | 90 | -------------------------------------------------------------------------------- /src/components/chat/OwnMsgItem.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 89 | 90 | -------------------------------------------------------------------------------- /src/components/msg/ChatMe.vue: -------------------------------------------------------------------------------- 1 | 86 | 87 | 140 | 141 | 142 | 180 | 181 | -------------------------------------------------------------------------------- /src/components/msg/FollowMe.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 100 | 101 | -------------------------------------------------------------------------------- /src/components/msg/LikeMe.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 101 | 102 | -------------------------------------------------------------------------------- /src/components/msg/ReplyMe.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 102 | 103 | -------------------------------------------------------------------------------- /src/components/msg/SystemMe.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 93 | 94 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | import axios from 'axios' 6 | 7 | import mavonEditor from 'mavon-editor' 8 | import 'mavon-editor/dist/css/index.css' 9 | 10 | import PerfectScrollbar from 'perfect-scrollbar' 11 | import 'perfect-scrollbar/css/perfect-scrollbar.css' 12 | 13 | import './plugins/element.js' 14 | 15 | 16 | import BootstrapVue from 'bootstrap-vue' 17 | import 'bootstrap/dist/css/bootstrap.css' 18 | import 'bootstrap-vue/dist/bootstrap-vue.css' 19 | 20 | /** 21 | * @description 自动判断该更新PerfectScrollbar还是创建它 22 | * @param {HTMLElement} el - 必填。dom元素 23 | */ 24 | const el_scrollBar = (el) => { 25 | //在元素上加点私货,名字随便取,确保不会和已有属性重复即可,我取名叫做_ps_ 26 | if (el._ps_ instanceof PerfectScrollbar) { 27 | el._ps_.update(); 28 | } else { 29 | //el上挂一份属性 30 | el._ps_ = new PerfectScrollbar(el, { 31 | // 要配什么属性自己看官网,此处不会解释任何其配置项的含义 32 | suppressScrollX: true, 33 | }); 34 | } 35 | }; 36 | 37 | //接着,自定义Vue指令,指令名你自己随便编一个,我们假定它叫scrollBar 38 | Vue.directive("scrollBar", { 39 | //使用inserted钩子函数(初次创建dom)获取使用自定义指令处的dom 40 | inserted(el, binding, vnode) { 41 | //判断其样式是否存在position 并且position为"fixed", "absolute"或"relative" 42 | //如果不符合条件,抛个错误。当然你也可以抛个警告然顺便给其position自动加上"relative" 43 | //为什么要这么做呢,因为PerfectScrollbar实现原理就是对dom注入两个div,一个是x轴一个是y轴,他们两的position都是absolute。 44 | //对css稍有常识的人都知道,absolute是相对于所有父节点里设置了position属性的最近的一个节点来定位的,为了能够正确定位,我们要给其设置position属性 45 | const rules = ["fixed", "absolute", "relative", "sticky"]; 46 | if (!rules.includes(window.getComputedStyle(el, null).position)) { 47 | console.error(`perfect-scrollbar所在的容器的position属性必须是以下之一:${rules.join("、")}`) 48 | } 49 | //el上挂一份属性 50 | el_scrollBar(el); 51 | }, 52 | //更新dom的时候 53 | componentUpdated(el, binding, vnode, oldVnode) { 54 | //vnode.context其实就是vue实例,这里其实无需实例也直接用Vue的静态方法 55 | //故而也可以写成Vue.nextTick 56 | vnode.context.$nextTick( 57 | () => { 58 | el_scrollBar(el); 59 | } 60 | ) 61 | } 62 | }) 63 | 64 | Vue.use(BootstrapVue) 65 | Vue.use(mavonEditor) 66 | 67 | Vue.config.productionTip = false 68 | 69 | //全局注册axios 70 | Vue.prototype.$axios = axios 71 | 72 | axios.defaults.baseURL = 'http://localhost:8089/forum_server' 73 | 74 | /** 75 | * axios请求拦截器,每次请求带上token 76 | */ 77 | axios.interceptors.request.use(config => { 78 | config.headers.Authorization = window.sessionStorage.getItem('JWT_TOKEN'); 79 | return config; 80 | }, error => { 81 | return Promise.reject(error) 82 | }); 83 | 84 | 85 | 86 | /** 87 | * axios响应拦截器 88 | */ 89 | axios.interceptors.response.use( 90 | function (response) { 91 | const code = response.data.code 92 | if (code == 201 || code == 202 ) { 93 | //说明token错误或者token过期 94 | const msg = response.data.msg; 95 | alert(msg); 96 | return router.push("/login"); 97 | // }else if (code == 301){ 98 | // const msg = response.data.msg; 99 | // alert(msg); 100 | // return router.push("/"); 101 | }else if(code == 200){ 102 | //更新未读消息数量 103 | return response; 104 | }else { 105 | return response; 106 | } 107 | }, 108 | function (error) { 109 | return Promise.reject(error) 110 | } 111 | ) 112 | 113 | 114 | 115 | 116 | new Vue({ 117 | router, 118 | store, 119 | render: h => h(App) 120 | }).$mount('#app') 121 | -------------------------------------------------------------------------------- /src/plugins/element.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Element from 'element-ui' 3 | import 'element-ui/lib/theme-chalk/index.css' 4 | 5 | Vue.use(Element) 6 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | 4 | Vue.use(VueRouter) 5 | 6 | const routes = [ 7 | { 8 | path: '/', 9 | name: 'Index', 10 | component: () => import( '../views/Index.vue'), 11 | }, 12 | { 13 | path: '/register', 14 | name: 'register', 15 | component: () => import( '../views/Register.vue') 16 | }, 17 | { 18 | path: '/login', 19 | name: 'login', 20 | component: () => import( '../views/Login.vue'), 21 | }, 22 | { 23 | path: '/post/add', 24 | name: 'PostAdd', 25 | component: () => import( '../views/PostAdd.vue') 26 | }, 27 | { 28 | path: '/profile/:uid', 29 | name: 'Profile', 30 | component: () => import('../views/Profile.vue') 31 | }, 32 | { 33 | path: '/post/:pid', 34 | name: 'PostDetail', 35 | component: () => import('../views/PostDetail.vue') 36 | }, 37 | /*{ 38 | path: '/letter', 39 | name: 'Letter', 40 | component: () => import('../views/Letter.vue') 41 | }, 42 | { 43 | path:'/letter/:cid', 44 | name:'LetterDetail', 45 | component: () => import('../views/LetterDetail.vue') 46 | },*/ 47 | /*{ 48 | path:'/send/:toName', 49 | name:'SendLetter', 50 | component: () => import('../views/SendLetter.vue') 51 | },*/ 52 | { 53 | path:'/followers/:userId', 54 | name:'Followers', 55 | component: () => import('../views/Followers.vue') 56 | }, 57 | { 58 | path:'/followees/:userId', 59 | name:'Followees', 60 | component: () => import('../views/Followees.vue') 61 | }, 62 | { 63 | path:'/notice', 64 | name:'Notice', 65 | component: () => import('../views/Notice.vue') 66 | }, 67 | { 68 | path:'/notice/:topic', 69 | name:'NoticeDetail', 70 | component: () => import('../views/NoticeDetail.vue') 71 | }, 72 | { 73 | path:'/search/:keyword', 74 | name:'Search', 75 | component: () => import('../views/Search.vue') 76 | }, 77 | { 78 | path:'/data', 79 | name:'Data', 80 | component: () => import('../views/Data.vue') 81 | }, 82 | { 83 | path:'/hot', 84 | name:'Hot', 85 | component: () => import('../views/Hot.vue') 86 | }, 87 | { 88 | path:'/setting', 89 | name:'Setting', 90 | component: () => import('../views/Setting.vue') 91 | }, 92 | { 93 | path:'/collect/:uid', 94 | name:'Collection', 95 | component: () => import('../views/Collection.vue') 96 | }, 97 | { 98 | path:'/userPost/:uid', 99 | name:'UserPosts', 100 | component: () => import('../views/UserPosts.vue') 101 | }, 102 | { 103 | path:'/ken', 104 | name:'Ken', 105 | component: () => import('../views/Ken.vue') 106 | }, 107 | { 108 | path:'/myAdmin', 109 | name:'Admin', 110 | component: () => import('../views/Admin.vue') 111 | }, 112 | { 113 | path:'/forget', 114 | name:'Forget', 115 | component: () => import('../views/Forget.vue') 116 | }, 117 | { 118 | path:'/message', 119 | name:'Message', 120 | component: () => import('../views/Message.vue') 121 | } 122 | 123 | ] 124 | 125 | const router = new VueRouter({ 126 | mode: 'history', 127 | base: process.env.BASE_URL, 128 | routes 129 | }) 130 | 131 | export default router 132 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import { Notification } from 'element-ui'; 4 | 5 | 6 | Vue.use(Vuex) 7 | 8 | //全局变量 9 | const store= new Vuex.Store({ 10 | state: sessionStorage.getItem('state') ? JSON.parse(sessionStorage.getItem('state')) : { 11 | userInfo:JSON.parse(sessionStorage.getItem("userInfo")) || {}, //当前登录对象 12 | isLogin: sessionStorage.getItem("isLogin") || false, //当前登录状态 13 | ws:null,//websocket连接端点 14 | userList:{},//聊天用户列表 15 | currentUser:{}, //当前聊天对象 16 | sessions:[], //聊天记录 17 | msgActiveIndex:1 18 | }, 19 | mutations:{ 20 | //弹出登录成功信息 21 | success(msg) { 22 | this.$message({ 23 | message: msg, 24 | type: 'success' 25 | }); 26 | }, 27 | fail(msg) { 28 | this.$message.error(msg); 29 | }, 30 | //改变消息页面的侧边栏的选择 31 | setMsgActiveIndex(state,i){ 32 | state.msgActiveIndex = i; 33 | }, 34 | setCurrentUser(state,user){ 35 | state.currentUser = user; 36 | 37 | }, 38 | //只有通过state的修改,vue才能实时监控到全局变量的变化! 39 | setUserInfo(state,userInfo){ 40 | sessionStorage.setItem('userInfo', JSON.stringify(userInfo));//将传递的数据先保存到localStorage中 41 | state.userInfo = userInfo;// 之后才是修改state中的状态 42 | }, 43 | setLoginState(state,flag){ 44 | sessionStorage.setItem('isLogin',flag) 45 | state.isLogin = flag; 46 | }, 47 | logout(state){ 48 | sessionStorage.clear(); 49 | state.isLogin = false; 50 | state.userInfo = {}; 51 | if (state.ws != null){ 52 | //关闭连接 53 | state.ws.close(); 54 | } 55 | //清空聊天记录 56 | state.sessions = {}; 57 | }, 58 | setWebsocket(state){ 59 | const token = window.sessionStorage.getItem('JWT_TOKEN'); 60 | let url = "ws://localhost:8089/forum_server/chat/"+token; 61 | state.ws = new WebSocket(url); 62 | }, 63 | setUserList(state,userList){ 64 | sessionStorage.setItem("userList",userList); 65 | state.userList = userList; 66 | console.log("执行了用户列表") 67 | }, 68 | //添加聊天记录到vuex中(添加自己发送的消息) 69 | addMessage(state,msg){ 70 | //获取浏览器内存中的聊天记录 71 | let message=state.sessions[state.userInfo.id+"_"+msg.toId]; 72 | if (!message){ 73 | //创建保存消息记录的数组 74 | Vue.set(state.sessions,state.userInfo.id+"_"+msg.toId,[]); 75 | } 76 | //把聊天记录放进数组中 77 | state.sessions[state.userInfo.id+"_"+msg.toId].push({ 78 | content:msg.content, 79 | createTime: msg.createTime, 80 | fromId:msg.fromId, 81 | toId:msg.toId 82 | }) 83 | }, 84 | //添加聊天记录到vuex中(添加对方发送的消息) 85 | addOtherMessage(state,msg){ 86 | //解析json字符串 87 | msg = JSON.parse(msg); 88 | //获取浏览器内存中的聊天记录 89 | let message=state.sessions[msg.toId+"_"+msg.fromId]; 90 | if (!message){ 91 | //创建保存消息记录的数组 92 | Vue.set(state.sessions,msg.toId+"_"+msg.fromId,[]); 93 | } 94 | //把聊天记录放进数组中 95 | state.sessions[msg.toId+"_"+msg.fromId].push({ 96 | content:msg.content, 97 | time: msg.time, 98 | fromId:msg.fromId, 99 | toId:msg.toId 100 | }) 101 | 102 | if (msg.fromId!=state.currentUser.id){ 103 | Notification.info({ 104 | title:'【'+msg.fromId+'号用户】发来一条消息', 105 | message:msg.content.length<8?msg.content:msg.content.substring(0,8)+"...", 106 | position:"bottom-right" 107 | }); 108 | // //默认为消息未读 109 | // Vue.set(context.state.isDot,context.state.currentUser.username+"#"+receiveMsg.from,true); 110 | } 111 | 112 | }, 113 | //获取用户列表 114 | GET_USERS(state){ 115 | const _this = this 116 | Vue.prototype.$axios.get("/message/getUsers").then(res => { 117 | if (res.data.code!=200){ 118 | _this.fail(res.data.msg); 119 | return; 120 | }else { 121 | const data = res.data.data; 122 | state.userList = data.userList; 123 | } 124 | }).catch(function(error){ 125 | console.log(error); 126 | _this.fail("网络故障,请检查网络是否正常") 127 | }); 128 | }, 129 | //获取私聊记录,若用户第一次点击,则需要向服务器请求聊天记录 130 | //否则,在vuex中获取 131 | getSession(state,current_id){ 132 | //获取vuex中的聊天记录 133 | let message=state.sessions[state.userInfo.id+"_"+current_id]; 134 | if (!message){ 135 | //向服务器请求聊天记录 136 | //提交表单 137 | Vue.prototype.$axios({ 138 | method:'post', 139 | url:'/message/getSession', 140 | data:{ 141 | toId:current_id 142 | } 143 | }).then(function(res){ 144 | if (res.data.code == 200){ 145 | 146 | const data = res.data.data; 147 | console.log(data); 148 | //创建保存消息记录的数组 149 | Vue.set(state.sessions,state.userInfo.id+"_"+current_id,data.chatList); 150 | }else{ 151 | _this.fail(res.data.msg) 152 | } 153 | console.log(res); 154 | }).catch(function(error){ 155 | console.log(error); 156 | }); 157 | } 158 | } 159 | }, 160 | actions:{ 161 | //连接服务器的websocket 162 | connect(context){ 163 | const token = window.sessionStorage.getItem('JWT_TOKEN'); 164 | let url = "ws://localhost:8089/forum_server/chat/"+token; 165 | context.state.ws = new WebSocket(url); 166 | 167 | //接收到服务端推送的消息后触发 168 | context.state.ws.onmessage = function(evt) { 169 | 170 | //获取服务端推送过来的消息 171 | var message = evt.data; 172 | //因为是对方发来的消息,from_id是对方的id 173 | //而聊天记录是以当前用户id为前缀的,所以要用另外的方法来保存 174 | //保存到聊天记录 175 | context.commit('addOtherMessage',message); 176 | 177 | } 178 | }, 179 | initData(context){ 180 | //获取用户列表 181 | context.commit('GET_USERS') 182 | } 183 | }, 184 | modules:{}, 185 | }) 186 | export default store 187 | -------------------------------------------------------------------------------- /src/views/Admin.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 117 | 118 | -------------------------------------------------------------------------------- /src/views/Collection.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 137 | 138 | -------------------------------------------------------------------------------- /src/views/Data.vue: -------------------------------------------------------------------------------- 1 | 66 | 67 | 153 | 154 | -------------------------------------------------------------------------------- /src/views/Followees.vue: -------------------------------------------------------------------------------- 1 | 66 | 67 | 166 | 167 | -------------------------------------------------------------------------------- /src/views/Followers.vue: -------------------------------------------------------------------------------- 1 | 66 | 67 | 166 | 167 | -------------------------------------------------------------------------------- /src/views/Forget.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 87 | 88 | -------------------------------------------------------------------------------- /src/views/Hot.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 53 | 97 | 98 | -------------------------------------------------------------------------------- /src/views/Index.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 91 | 92 | -------------------------------------------------------------------------------- /src/views/Ken.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 28 | 29 | -------------------------------------------------------------------------------- /src/views/Login.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 171 | 186 | -------------------------------------------------------------------------------- /src/views/Message.vue: -------------------------------------------------------------------------------- 1 | 2 | 37 | 38 | 86 | 87 | -------------------------------------------------------------------------------- /src/views/Notice.vue: -------------------------------------------------------------------------------- 1 | 2 | 129 | 130 | 180 | 181 | -------------------------------------------------------------------------------- /src/views/NoticeDetail.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 122 | 123 | -------------------------------------------------------------------------------- /src/views/PostAdd.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 273 | 274 | -------------------------------------------------------------------------------- /src/views/PostDetail.vue: -------------------------------------------------------------------------------- 1 | 213 | 214 | 488 | 489 | 490 | -------------------------------------------------------------------------------- /src/views/Profile.vue: -------------------------------------------------------------------------------- 1 | 109 | 110 | 111 | 249 | 250 | -------------------------------------------------------------------------------- /src/views/Register.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 182 | 190 | -------------------------------------------------------------------------------- /src/views/Search.vue: -------------------------------------------------------------------------------- 1 | 87 | 88 | 222 | 223 | -------------------------------------------------------------------------------- /src/views/Setting.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 66 | 67 | -------------------------------------------------------------------------------- /src/views/UserPosts.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 128 | 129 | --------------------------------------------------------------------------------