quasar+node实现一个简单的聊天
npm install -g @quasar/cli
新建chat_data数据库
chat_data下面新建login表和message
node 初始化
npm init -y
服务器结构目录
mySql.js
let mysqlStyle = require('mysql')
let mysql = mysqlStyle.createConnection({
host: "localhost",
user: "root",
password: '',
database: "chat_data"
})
module.exports = {
mysql
}
main.js
const app = require('express')()
const httpServer = require('http').Server(app)
const io = require('socket.io')(httpServer,{ cors: true });
const bodyParser = require('body-parser')
app.use(bodyParser.json()); //body-parser 解析json格式数据
app.use(bodyParser.urlencoded({ //此项必须在 bodyParser.json 下面,为参数编码
extended: true
}));
let mysqlUserinfo = require('./mySql')
httpServer.listen(3000,()=>{
console.log('服务启动')
})
app.all("*",function(req,res,next){
//设置允许跨域的域名,*代表允许任意域名跨域
res.header("Access-Control-Allow-Origin","*");
//允许的header类型
res.header("Access-Control-Allow-Headers","content-type");
//跨域允许的请求方式
res.header("Access-Control-Allow-Methods","DELETE,PUT,POST,GET,OPTIONS");
if (req.method.toLowerCase() == 'options')
return res.send(200); //让options尝试请求快速结束
else
return next();
})
app.get('/',(req,res)=>{
res.sendFile(__dirname + '/index.html')
})
//cunnent 用来控制人数
let cunnent = 0,userId = {}
io.on('connect', socket => {
let obj = {}
socket.on('login',data=>{
let userId = data.userId
let image = data.image[0].image
let userName = data.userNmae
let id = Math.floor(Math.random()*100000000000000)
let sql = `INSERT INTO login (id,userName,image) VALUES (?,?,?)`;
mysqlUserinfo.mysql.query(sql,[id,userName,image],(err,data)=>{
cunnent++
obj = {
id,userName,image,cunnent,data,userId,
xxx: `用户${userName}进入聊天室`,ok:true // 告诉群里面的人XX加入群聊
}
socket.emit('loginDate',obj)
})
})
socket.on('request',data=>{
io.emit('name',obj)
}),
socket.on('messageValue',data=>{
console.log(data.data.message)
userId = JSON.stringify(data.data)
mysqlUserinfo.mysql.query(`INSERT INTO message VALUES ('${userId}')`,[userId],(err,result)=>{
if (result.protocol41){
let sqlDate = `SELECT * FROM message`
mysqlUserinfo.mysql.query(sqlDate,(err,data)=>{
io.emit('redirecTsenOut',data) // 告诉全部人的消息
})
}
})
})
// 离开的时候告诉全部的人谁谁离开了
socket.on('disconnect',_=>{
cunnent--
io.emit('delurlName',obj)
})
});
客户端(
npm i vue-socket.io
npm i socket.io-client
app.js
import VueSocketIO from 'vue-socket.io';
import socketio from 'socket.io-client';
Vue.use(new VueSocketIO({
debug: true,
connection: socketio('ws://localhost:3000'),
}))
import App from 'app/src/App.vue' // 先引在渲染页面
结构
index.vue文件
<template>
<div>
<div>
请输入用户名:<input type="text" placeholder="请输入用户名" v-model="userName">
</div>
<div>
<div>
请选择头像
</div>
<div class="image" v-for="(item,index) in imageList" :key="item.id" >
<img :src="item.image" alt="" :class="index === indexActive ? 'active' : ''" @click="activeBtn(item,index)">
</div>
</div>
<div>
<button class="login" @click="loginBtn">登录</button>
</div>
</div>
</template>
<script lang="ts">
import {nmaeImage} from 'components/models';
import axios from 'axios'
// import ExampleComponent from 'components/CompositionComponent.vue';
import { defineComponent, ref } from '@vue/composition-api';
interface dataObj{
indexActive:number | Boolean,
itemArray: Array,
userName:string
}
export default defineComponent({
data():dataObj{
return {
indexActive: 100,
itemArray: [],
userName:""
}
},
setup(){
const imageList = ref<nmaeImage[]>([
{id:1,image:require('../assets/images/1.png')},
{id:2,image:require('../assets/images/2.png')},
{id:3,image:require('../assets/images/3.png')},
{id:4, image:require('../assets/images/4.png')},
{id:5,image:require('../assets/images/5.png')},
])
return {imageList}
},
methods:{
activeBtn(item:object,index:number){
this.itemArray = []
if(this.indexActive === index){
this.itemArray = []
this.indexActive = !this.indexActive
} else {
this.itemArray.push(item)
this.indexActive = index
}
},
async loginBtn(){
if(this.userName === "" || this.itemArray.length === 0 ) return
const data = {
image:this.itemArray,
userNmae: this.userName,
userId:1
}
this.$socket.emit('login',data)
}
},
sockets:{
loginDate(data:any){
if(data){
this.$router.push({path:'/mainLayout',query:{params:JSON.stringify(data)}})
}
}
}
})
</script>
<style lang="less" scope>
.image{
display: inline-block;
img{
width: 100px;
height: 100px;
margin: 10px;
cursor: pointer;
}
.active{
border: 3px solid red;
}
}
.uwernam{
color:red;
}
.welcomeUsername{
color:rgb(214, 8, 8);
}
</style>
models.ts
export interface nmaeImage {
id: Number | string
image: string;
}
MainLayout.vue
<template>
<div>
<div>
当前人数 -------------------{{
cunnent < 2 ? cunnent + 1 : cunnent + 1 || 1
}}
</div>
<div>
当前用户:---------- <span class="uwernam">{{ userName.userName }}</span>
</div>
<div class="huanyingdsadas">
<div v-for="(item, index) in huanyingh" :key="index">
欢迎-----<span class="welcomeUsername">{{ item.xxx }}</span>
</div>
</div>
<div class="q-pa-md row justify-center">
<div
style="width: 100%; max-width: 600px;height:350px;overflow-y:auto"
id="scollView"
>
<div v-for="(item, index) in redirecTsenOutDateMessage" :key="item.id">
<div class="messageValue" v-if="item.id === userName.id">
<!-- 当前用户 -->
<q-chat-message
:name="item.userName"
:avatar="item.image"
:text="[item.message]"
sent
/>
</div>
<div class="redirecTsenOutDateMessage" v-else>
<q-chat-message
:name="item.userName"
:avatar="item.image"
:text="[item.message]"
/>
</div>
</div>
</div>
</div>
<div class="border-item">
<textarea v-model="message" name="" id="" cols="30" rows="10"></textarea>
<q-btn color="primary" label="发送" @click="btnClickMessageValue" />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api';
export default defineComponent({
name: 'MainLayout',
data() {
return {
userName: [],
message: '',
huanyingh: [],
cunnent: 0,
redirecTsenOutDate: {},
messageValue: [],
redirecTsenOutDateMessage: [],
redirecTsenOutDateMessageValue: false
};
},
methods: {
btnClickMessageValue() {
if (this.message === '') return;
this.messageValue.push(this.message);
this.redirecTsenOutDateMessage = [];
let result = {
data: this.userName
};
this.$socket.emit('messageValue', result);
this.message = '';
}
},
mounted() {
let params = this.$route.query.params || {};
this.userName = JSON.parse(params); // 获取当前用户
this.$socket.emit('request', 1111);
},
sockets: {
connect() {
console.log('链接成功');
},
name(data: any) {
console.log(data);
this.cunnent = data.cunnent;
this.huanyingh.push(data);
},
delurlName(data: any) {
let index = this.huanyingh.findIndex(element => element.id === data.id);
let xxx = {
xxx: `用户${data.userName}离开了群聊`
};
this.huanyingh.push(xxx);
this.huanyingh.splice(index, 1);
},
redirecTsenOut(data?: any) { // 消息
if (data) {
data.forEach((element: any) => {this.redirecTsenOutDateMessage.push(JSON.parse(element.userId));
console.log(this.redirecTsenOutDateMessage);
});
}
}
}
});
</script>
<style lang="less">
.border-item {
width: 600px;
border: 1px solid #000;
margin: 40px;
margin: 0 auto;
textarea {
width: 100%;
border: none;
resize: none;
border-bottom: 1px solid #000;
}
textarea:focus-visible {
outline: none;
}
}
.huanyingdsadas {
width: 400px;
height: 200px;
float: left;
}
</style>
注意:在测试的时候请多跑几个页面