聊天框内容代码
<template> <div class="box"> <div class="top"> <i class="el-icon-caret-left" @click="go"></i> <span>{{ list.username }}</span> <img :src="list.image" alt="" /> </div> <ul class="center" ref="center"> <li v-for="item in messages" :key="item.id" :class="{ userId: item.user_id === userinfo.user_id }" > <img :src="list.image" alt="" v-if="item.user_id === list.user_id" /> <p> <span v-if="item.user_id === list.user_id" class="title" >{{ list.username }} <br /></span> <span class="content">{{ item.content }}</span> </p> <img :src="userinfo.image" alt="" v-if="item.user_id !== list.user_id" /> </li> </ul> <div class="bottom"> <input type="text" placeholder="请输入内容" v-model="input" @keydown.enter="send" class="inp" /> <button class="btn" @click="send">发送(S)</button> </div> </div> </template> <script> export default { data() { return { list: [], input: "", messages: [], userinfo: [], timeout: "", }; }, mounted() { this.userinfo = this.$store.state.userInfo; // console.log(this.userinfo); // console.log(this.$route.query.userid); let { userid } = this.$route.query; // console.log(userid); this.$axios({ url: `/api/my/userid?id=${userid}`, method: "get", headers: { Authorization: localStorage.getItem("token"), }, }).then(({ data }) => { // console.log(data); this.list = data.list; }); var i = 0; this.timeout = setInterval(() => { i++; this.$nextTick(() => { // console.log(this.$refs.center.scrollHeight); this.$refs.center.scrollTop = this.$refs.center.scrollHeight; }); if (i > 3) { clearInterval(this.timeout); } }, 500); }, destroyed() { clearInterval(this.timeout); }, watch: { messages: { handler() { this.gets(); }, immediate: true, }, }, methods: { go() { this.$router.go(-1); }, send() { let { userid } = this.$route.query; if (this.input !== "") { this.$axios({ url: `/api/my/publish`, method: "post", headers: { Authorization: localStorage.getItem("token"), }, data: { content: this.input, receive_id: userid, }, }).then(({ data }) => { this.messages = data.list; // console.log(data); this.input = ""; this.$nextTick(() => { this.$refs.center.scrollTop = this.$refs.center.scrollHeight; }); }); } else { this.$message({ message: "发布消息不能为空!", type: "warning", }); } }, gets() { let { userid } = this.$route.query; this.$axios({ url: `/api/my/messages?display=${userid}`, method: "get", headers: { Authorization: localStorage.getItem("token"), }, }).then(({ data }) => { // console.log(data); this.messages = data.list; }); }, }, }; </script> <style lang="scss" scoped> .box { height: 100vh; .top { position: fixed; top: 0; left: 0; width: 100%; height: 0.5rem; padding: 0.05rem 0.1rem; background-color: gainsboro; display: flex; align-items: center; justify-content: space-between; i { font-size: 0.2rem; } img { width: 0.4rem; height: 0.4rem; border-radius: 50%; } } .center { padding: 0.5rem 0 0.4rem 0; height: 100vh; overflow: auto; background-color: #f5f5f5; li { list-style: none; width: 100%; display: flex; align-items: center; padding: 0 0.05rem; p { margin: 0.1rem; .title { font-size: 0.1rem; color: gray; } .content { padding: 0.02rem 0.05rem; background-color: #fff; border-radius: 0.05rem; } } img { width: 0.3rem; height: 0.3rem; border-radius: 50%; } } .userId { justify-content: flex-end; } } .bottom { width: 100vw; position: fixed; left: 0; bottom: 0; display: flex; .inp { width: 2.55rem; padding: 0.1rem; border: 1px solid gainsboro; } .inp:focus { outline: none; } .btn { width: 1rem; padding: 0 0.1rem; box-sizing: content-box; border: none; background-color: rgb(182, 151, 151); color: rgb(0, 155, 39); margin: 0; } } } </style>
- 好友页代码
<template> <div class="box"> <ul> <li v-for="item in list" :key="item.id" @click="add(item)"> <img :src="item.image" alt="" /> <p>{{ item.username }}</p> </li> </ul> </div> </template> <script> export default { data() { return { list: [], }; }, mounted() { this.$axios({ url: "/api/my/userall", method: "get", headers: { Authorization: localStorage.getItem("token"), }, }).then(({ data }) => { console.log(data); this.list = data.list; }); }, methods: { add(item) { // console.log(item); this.$router.push({ name: "chat", query: { userid: item.user_id }, }); }, }, }; </script> <style lang="scss" scoped> .box { ul { li { list-style: none; display: flex; align-items: center; padding: 0.1rem 0.1rem; border-bottom: 1px solid gainsboro; img { width: 0.5rem; height: 0.5rem; border-radius: 50%; border: 1px solid red; margin-right: 0.1rem; color: #333; } } li:hover { background-color: gainsboro; } } } </style>
- router 路由页代码
import Vue from 'vue' import VueRouter from 'vue-router' import HomeView from '@/components/home' import axios from 'axios' import store from '@/store/index' Vue.use(VueRouter) const routes = [ { path: '/', name: 'home', redirect: 'footer', component: HomeView, meta: { isLogn:true } }, { path: '/login', name: 'login', component: () => import('@/views/login/index'), }, { path: '/footer', name: 'footer', redirect: {name:'user'}, component: () => import('@/views/home/footer.vue'), meta: { isLogn:true }, children: [ { path: 'user', name: 'user', component: () => import('@/views/user/index.vue'), meta: { isLogn:true } }, { path: 'mine', name: 'mine', component: () => import('@/views/mine/index.vue'), meta: { isLogn:true } }, ] }, { path: '/chat', name: 'chat', component: () => import('@/views/chat/index.vue'), meta: { isLogn:true } }, { path: '/setMessage', name: 'setMessage', component: () => import('@/views/mine/setMessage.vue'), } ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') const islogin = !!token // console.log(token, islogin) var data = '' axios({ url: `/api/my/myuser`, method: "get", headers: { Authorization: localStorage.getItem("token"), }, }).then(({ data }) => { store.commit('userInfo', data.list) if (to.matched.some((item) => item.meta.isLogn)) { if (data.status === 1) { localStorage.removeItem('token') window.location.href = '/login' } else { if (islogin) { next() } else { // 没有跳转至登录 window.location.href = '/login' } } } else { if (islogin && to.path === '/login') { // 跳转至首页 window.location.href = '/' return } next() } }) }) export default router
- vuex 页代码
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { userInfo: [], isRolling: false, }, getters: { isLogin(userInfo) { return !!userInfo.username } }, mutations: { addlogin(state) { state.isRolling = !state.isRolling }, userInfo(state, data) { state.userInfo = data } }, actions: { }, modules: { } })
- package.json 页代码
{ "name": "chat_vue", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build" }, "dependencies": { "axios": "^1.1.3", "core-js": "^3.8.3", "element-ui": "^2.15.12", "vue": "^2.6.14", "vue-router": "^3.5.1", "vuex": "^3.6.2" }, "devDependencies": { "@vue/cli-plugin-babel": "~5.0.0", "@vue/cli-plugin-router": "~5.0.0", "@vue/cli-plugin-vuex": "~5.0.0", "@vue/cli-service": "~5.0.0", "sass": "^1.32.7", "sass-loader": "^12.0.0", "vue-template-compiler": "^2.6.14" } }
- 移动端代码
const WIDTH = 375//如果是尺寸的设计稿在这里修改 const setView = () => { //设置html标签的fontSize document.documentElement.style.fontSize = (100 * document.documentElement.clientWidth / WIDTH) + 'px' } window.onresize = setView setView()
- main.js页面代码
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; import axios from 'axios' import '@/rem/index' Vue.prototype.$axios = axios Vue.use(ElementUI); Vue.config.productionTip = false new Vue({ router, store, render: h => h(App) }).$mount('#app')
六、MySQL 数据库创建功能展示
- 用户信息表
聊天发布内容表
七、node.js 核心代码
- 根页面
// 导入 express 模块 const express = require('express') // 创建 express 的服务器实例 const app = express() // 导入 cors 中间件 const cors = require('cors') // 将 cors 注册为全局中间件 app.use(cors()) // 配置解析表单数据的中间件 app.use(express.json()) // 解析 json 格式 // 注意:这个中间件,只能解析 application/x-www-form-urlencoded 格式的表单数据 app.use(express.urlencoded({ extended: false })) // 响应数据的中间件 app.use(function (req, res, next) { // status = 0 为成功; status = 1 为失败; 默认将 status 的值设置为 1,方便处理失败的情况 res.cc = function (err, status = 1) { res.send({ // 状态 status, // 状态描述,判断 err 是 错误对象 还是 字符串 message: err instanceof Error ? err.message : err, }) } next() }) const joi = require('@hapi/joi') // 错误中间件 app.use(function (err, req, res, next) { // 数据验证失败 if (err instanceof joi.ValidationError) return res.cc(err) // 未知错误 res.cc(err) }) // 导入配置文件 const config = require('./config') // 解析 token 的中间件 const expressJWT = require('express-jwt') // 使用 .unless({ path: [/^\/api\//] }) 指定哪些接口不需要进行 Token 的身份认证 app.use(expressJWT({ secret: config.jwtSecretKey }).unless({ path: [/^\/api\//] })) // 错误中间件 app.use(function (err, req, res, next) { // 捕获身份认证失败的错误 if (err.name === 'UnauthorizedError') return res.cc('身份认证失败!') }) // 导入并注册用户路由模块 const userRouter = require('./router/user') app.use('/api', userRouter) // 导入并使用用户信息路由模块 const userallRouter = require('./router/userall') // 注意:以 /my 开头的接口,都是有权限的接口,需要进行 Token 身份认证 app.use('/my', userallRouter) app.listen(0830, function () { console.log('api server running at http://127.0.0.1:0830') })
- 接口页面
不加密
// 导入 express 模块 const express = require('express') // 创建路由对象 const router = express.Router() const multiparty = require('multiparty') var multer = require('multer') const fs = require("fs"); // 导入用户路由处理函数模块 const userHandler = require('../router_handler/user') router.post('/reguser',userHandler.reguser) router.post('/login',userHandler.login) router.get('/uploads', userHandler.image) // 单图上传 router.post( "/upload", multer({ //设置文件存储路径 dest: "public/image", }).array("file", 1), function (req, res, next) { let files = req.files; let file = files[0]; let fileInfo = {}; let path = "public/image/" + 'image_' + file.originalname; fs.renameSync("./public/image/" + file.filename, path); //获取文件基本信息 fileInfo.type = file.mimetype; fileInfo.name = file.originalname; fileInfo.size = file.size; fileInfo.path = path; res.json({ code: 200, msg: "OK", data: fileInfo, }); console.log(fileInfo.path) } ); // 将路由对象共享出去 module.exports = router
加密:
// 导入 express const express = require('express') // 创建路由对象 const router = express.Router() // 导入用户路由处理函数模块 const userAll = require('../router_handler/userall') // 获取所有用户 router.get('/userall',userAll.getUserall) // 获取指定用户 router.get('/userid', userAll.getUserid) // 获取当前登录用户 router.get('/myuser', userAll.getMyuser) // 发布消息 router.post('/publish', userAll.setpublish) // 获取消息 router.get('/messages', userAll.getMessage) // 更改用户信息 router.post('/setmessages', userAll.setMessage) // 向外共享路由对象 module.exports = router
- 接口内容页面
不加密:
const db = require('../db/index') // 导入 bcryptjs 插件(对密码进行加密) const bcrypt = require('bcryptjs') // 导入 path 用来显示图片 var path = require('path'); // 用这个包来生成 Token 字符串 const jwt = require('jsonwebtoken') // 导入全局的配置文件 const config = require('../config') // 注册用户的处理函数 exports.reguser = (req, res,next) => { // 接收表单数据 const userinfo = req.body // 判断数据是否合法 if (!userinfo.username || !userinfo.password) { return res.send({ status: 1, message: '用户名或密码不能为空!' }) } const sql = `select * from users where username=?` db.query(sql, [userinfo.username], function (err, results) { // 执行 SQL 语句失败 if (err) { return res.send({ status: 1, message: err.message }) } // 用户名被占用 if (results.length > 0) { return res.send({ status: 1, message: '用户名被占用,请更换其他用户名!' }) } // 对用户的密码,进行 bcrype 加密,返回值是加密之后的密码字符串 userinfo.password = bcrypt.hashSync(userinfo.password, 10) const articleInfo = { // 标题、内容、状态、所属的分类Id ...req.body, // 文章封面在服务器端的存放路径 image: 'http://127.0.0.1:830/api/uploads?img=1669116817314_21.jpg', // 账号创建时间 time: new Date(), // 作者的Id user_id: Math.floor(Math.random()*(9999999999-100000)+100000) } // console.log(articleInfo) const sql = 'insert into users set ?' db.query(sql, articleInfo, function (err, results) { // 执行 SQL 语句失败 if (err) return res.send({ status: 1, message: err.message }) // SQL 语句执行成功,但影响行数不为 1 if (results.affectedRows !== 1) { return res.send({ status: 1, message: '注册用户失败,请稍后再试!' }) } // 注册成功 res.send({ status: 0, message: '注册成功!' }) }) }) } // 登录的处理函数 exports.login = (req, res) => { const userinfo = req.body const sql = `select * from users where username=?` db.query(sql, userinfo.username, function (err, results) { // 执行 SQL 语句失败 if (err) return res.cc(err) // 执行 SQL 语句成功,但是查询到数据条数不等于 1 if (results.length !== 1) return res.cc('登录失败!') // TODO:判断用户输入的登录密码是否和数据库中的密码一致 // 拿着用户输入的密码,和数据库中存储的密码进行对比 const compareResult = bcrypt.compareSync(userinfo.password, results[0].password) // 如果对比的结果等于 false, 则证明用户输入的密码错误 if (!compareResult) { return res.cc('登录失败!') } // 剔除完毕之后,user 中只保留了用户的 id, username, nickname, email 这四个属性的值 const user = { ...results[0], password: '', user_pic: '' } // 对用户的信息进行加密,生成 Token 字符串 const tokenStr = jwt.sign(user, config.jwtSecretKey, { expiresIn: config.expiresIn, // token 有效期为 72 个小时 }) res.send({ status: 0, message: '登录成功!', // 为了方便客户端使用 Token,在服务器端直接拼接上 Bearer 的前缀 token: 'Bearer ' + tokenStr, }) }) } // 显示图片 exports.image = (req, res) => { // console.log(__dirname) // res.sendFile(path.join(__dirname, 'public/image')); res.sendFile(path.join(process.cwd(), '/public/image/' + req.query.img)); // res.send('image ok') // console.log(process.cwd(),__dirname) }
加密:
const db = require('../db/index') // 获取所有用户 exports.getUserall = (req, res) => { const sql = `select * from users where user_id!=?` // console.log(req.user.user_id) db.query(sql, req.user.user_id,function (err, results) { // console.log(err, results) if(err) return res.cc(err) res.send({ status: 0, message: '获取所有用户信息成功', list: results, }) }) // res.send('ok') } // 获取指定用户 exports.getUserid = (req, res) => { const userinfo = req.url.split('=') const user_id = userinfo[userinfo.length - 1] const sql = `select * from users where user_id=?`; db.query(sql, user_id, (err, results) => { // 1. 执行 SQL 语句失败 if (err) return res.cc(err) // 2. 执行 SQL 语句成功,但是查询到的数据条数不等于 1 if (results.length !== 1) return res.cc('获取用户信息失败!') // 3. 将用户信息响应给客户端 res.send({ status: 0, message: '获取用户基本信息成功!', list: results[0], }) }) } // 获取当前用户信息 exports.getMyuser = (req, res) => { const sql = `select * from users where user_id=?`; db.query(sql, req.user.user_id, function (err, results) { if (err) return res.cc(err) if (results.length !== 1) return res.cc('获取用户信息失败!') // 将用户信息响应给客户端 res.send({ status: 0, message: '获取当前登录用户信息成功!', list: results[0], }) }) } // 发布消息 exports.setpublish = (req, res) => { // console.log(req.body.receive_id) // 接收表单数据 const articleInfo = { // 发布内容 ...req.body, // 信息发布时间 time: new Date(), // 发布者 id user_id: req.user.user_id, display_position: req.user.user_id + '' + req.body.receive_id } // console.log(articleInfo) const sql = `insert into chat_table set ?` db.query(sql, articleInfo,function (err, results) { // 执行 SQL 语句失败 if (err) return res.send({ status: 1, message: err.message }) // SQL 语句执行成功,但影响行数不为 1 if (results.affectedRows !== 1) { return res.send({ status: 1, message: '发布消息失败,请稍后再试!' }) } // 注册成功 // res.send({ status: 0, message: '发布成功!', list: req.body.content }) // console.log(req.body.receive_id,req.user.user_id) const sql = `select * from chat_table where state=0 and display_position=${req.user.user_id + '' + req.body.receive_id} or display_position=${req.body.receive_id + '' + req.user.user_id}`; db.query(sql, 0,function (err, results) { // 1. 执行 SQL 语句失败 if (err) return res.cc(err) // 3. 将用户信息响应给客户端 res.send({ status: 0, message: '发布成功!', list: results, }) }) }) } // 获取指定信息 exports.getMessage = (req, res) => { const userinfo = req.url.split('=') const user_id = userinfo[userinfo.length - 1] const sql = `select * from chat_table where state=0 and display_position=${req.user.user_id + '' + user_id} or display_position=${user_id + '' + req.user.user_id}`; db.query(sql, 0,function (err, results) { // 1. 执行 SQL 语句失败 if (err) return res.cc(err) // 3. 将用户信息响应给客户端 res.send({ status: 0, message: '获取信息成功!', list: results, }) }) } // 更改用户信息 exports.setMessage = (req, res) => { console.log(req.body,req.user.user_id) const sql = `update users set ? where user_id=?` db.query(sql, [req.body,req.user.user_id], (err, results) => { // 执行 SQL 语句失败 console.log(err) if (err) return res.cc(err) // 执行 SQL 语句成功,但影响行数不为 1 // if (results.affectedRows !== 1) return res.cc('修改用户基本信息失败!') // 修改用户信息成功 return res.cc('修改用户基本信息成功!', 0) }) }
- 验证规则页面
const joi = require('joi') /** * string() 值必须是字符串 * alphanum() 值只能是包含 a-zA-Z0-9 的字符串 * min(length) 最小长度 * max(length) 最大长度 * required() 值是必填项,不能为 undefined * pattern(正则表达式) 值必须符合正则表达式的规则 */ // 用户名的验证规则 const username = joi.string().min(1).max(10).required() // 密码的验证规则 // const password = joi.string().min(1).max(50).required() // 头像 const image = joi.string() // 用户 id 的验证规则 const user_id = joi.number().integer().min(1) // 状态 const state = joi.string().valid('0', '1') // 注册和登录表单的验证规则对象 exports.reg_login_schema = { // 表示需要对 req.body 中的数据进行验证 body: { username, // password, image, user_id, state }, }
- 链接数据库页面
// 导入 mysql 模块 const mysql = require('mysql') // 创建数据库连接对象 const db = mysql.createPool({ host: '127.0.0.1', user: 'root', password: 'wang20030830', database: 'chat', }) // 向外共享 db 数据库连接对象 module.exports = db
- 加密页面
// 这是一个全局的配置文件 module.exports = { // 加密和解密 Token 秘钥 jwtSecretKey: 'wangshihao No1. ^_^', // Token 的有效期 expiresIn: '72h' }
- package.json 页面
{ "name": "chat_node", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@escook/express-joi": "^1.1.1", "@hapi/joi": "^17.1.0", "bcryptjs": "^2.4.3", "cors": "^2.8.5", "express": "^4.17.1", "express-jwt": "^5.3.3", "fs": "^0.0.1-security", "joi": "^17.7.0", "jsonwebtoken": "^8.5.1", "multer": "^1.4.5-lts.1", "multiparty": "^4.2.3", "mysql": "^2.18.1", "path": "^0.12.7" } }
八、总结
以上就是 聊天框项目的所有功能简介和代码,不懂得可以在评论区里问我或私聊我询问,以后会持续发布一些新的功能,敬请关注。