최근 Vuejs에 관심이 생겨서 공부것을 바탕으로 간단한 채팅앱을 만들어 보았습니다.
이렇게 개발글 남기는건 처음이라... 어떻게 써야 잘쓰는건지 잘 모르겠지만...
좋게봐주셨으면합니다~^^
$ yarn global add @vue/cli // vue cli를 설치합니다.
$ vue create chat-front // vue 프로젝트를 생성합니다.
$ cd chat-front
$ yarn serve // 서버를 실행 시켜봅니다.
$ vue add vuetify
- 커맨드를 실행하면 main.js
import './plugins/vuetify';
자동으로 추가 되는것을 확인 할 수 있습니다.
<template>
<div class="inner-wrap" fluid fill-height>
Login Page
</div>
</template>
<script>
export default {
name: 'Login',
data() {
return {
msg: [],
name: '',
};
},
};
</script>
<template>
<v-app>
<v-content>
<!-- 라우터 -->
<v-container fluid fill-height aacontainer>
<router-view></router-view>
</v-container>
<!-- 라우터 -->
</v-content>
</v-app>
</template>
<script>
export default {
name: 'App',
components: {
},
data() {
return {
};
},
};
</script>
import Vue from 'vue';
import Router from 'vue-router';
import Login from './views/Login.vue';
Vue.use(Router);
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'Login',
component: Login,
},
],
});
<template>
<v-layout align-center justify-center>
<v-flex xs12 sm6>
<v-text-field
v-model="userName"
label="대화명"
required
v-on:keyup.enter="joinSubmit"
></v-text-field>
<div class="text-xs-center">
<v-btn @click="joinSubmit" round color="primary" dark>JOIN</v-btn>
</div>
</v-flex>
</v-layout>
</template>
<script>
export default {
name: 'LoginForm',
props: ['join'],
data() {
return {
userName: '',
};
},
methods: {
joinSubmit() {
this.$emit('joinSubmit', this.userName);
},
},
};
</script>
<template>
<div class="inner-wrap" fluid fill-height>
<Loginform-component v-on:joinSubmit="joinSubmit"></Loginform-component>
</div>
</template>
<script>
import LoginForm from '@/components/Login/LoginForm.vue';
export default {
name: 'Login',
data() {
return {
};
},
components: {
'Loginform-component': LoginForm,
},
created() {
},
methods: {
joinSubmit(userName) {
this.$router.push(`/char-room/${userName}`);
},
},
};
</script>
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
html,body{
overflow: hidden;
}
.inner-wrap {
width: 100%;
}
./src/components/Chat/MessageList.vue
./src/components/Chat/MessageForm.vue
import Vue from 'vue';
import io from 'socket.io-client';
const socket = io('http://localhost:3000');
const SocketPlugin = {
install(vue) {
vue.mixin({
});
vue.prototype.$sendMessage = ($payload) => {
socket.emit('chat', {
msg: $payload.msg,
name: $payload.name,
});
};
// 인스턴스 메소드 추가
vue.prototype.$socket = socket;
},
};
Vue.use(SocketPlugin);
import './plugins/socketPlugin';
export default {
PUSH_MSG_DATA: 'pushMsgData',
};
./src/store.js는 삭제하겠습니다.
이런 구조로 만들어줍니다..ㅋ
import Constant from '../../Constant';
const state = {
msgDatas: [],
};
// getters
const getters = {
};
// actions
const actions = {
};
// mutations
const mutations = {
[Constant.PUSH_MSG_DATA]: ($state, $payload) => {
$state.msgDatas.push($payload);
},
};
export default {
state,
getters,
actions,
mutations,
};
import Vue from 'vue';
import Vuex from 'vuex';
import socket from './modules/socket';
Vue.use(Vuex);
const debug = process.env.NODE_ENV !== 'production';
export default new Vuex.Store({
modules: {
socket,
},
strict: debug,
// plugins: debug ? [createLogger()] : []
});
<template>
<v-list v-auto-bottom="msgs">
<transition-group name="list" >
<div v-for="(msg,index) in msgs" v-bind:key="index">
<v-list-tile>
<v-list-tile-action>
<span>{{msg.from.name}}</span>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>{{msg.msg}}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-divider inset></v-divider>
</div>
</transition-group>
</v-list>
</template>
<script>
export default {
name: 'MessageList',
props: ['msgs'],
};
</script>
<style>
.list-item {
display: inline-block;
margin-right: 10px;
}
.list-enter-active, .list-leave-active {
transition: all 1s;
}
.list-enter, .list-leave-to /* .list-leave-active below version 2.1.8 */ {
opacity: 0;
transform: translateX(30px);
}
</style>
<template>
<div class="inner-wrap">
<v-text-field
v-model="msg"
label="chat"
placeholder="보낼 메세지를 입력하세요."
solo
@keyup.13="submitMessageFunc"
></v-text-field>
</div>
</template>
<script>
export default {
name: 'MessageForm',
data() {
return {
msg: '',
};
},
methods: {
submitMessageFunc() {
if (this.msg.length === 0) return false;
this.$emit('submitMessage', this.msg);
this.msg = '';
return true;
},
},
};
</script>
<template>
<div class="inner-wrap" fluid fill-height inner-wrap>
<Message-List :msgs="msgDatas" class="msg-list"></Message-List>
<Message-From v-on:submitMessage="sendMessage" class="msg-form" ></Message-From>
</div>
</template>
<script>
import { mapMutations, mapState } from 'vuex';
import MessageList from '@/components/Chat/MessageList.vue';
import MessageForm from '@/components/Chat/MessageForm.vue';
import Constant from '@/Constant';
export default {
name: 'ChatRoom',
data() {
return {
datas: [],
};
},
components: {
'Message-List': MessageList,
'Message-From': MessageForm,
},
computed: {
...mapState({
'msgDatas': state => state.socket.msgDatas,
}),
},
created() {
const $ths = this;
this.$socket.on('chat', (data) => {
this.pushMsgData(data);
$ths.datas.push(data);
});
},
methods: {
...mapMutations({
'pushMsgData': Constant.PUSH_MSG_DATA,
}),
sendMessage(msg) {
this.pushMsgData({
from: {
name: '나',
},
msg,
});
this.$sendMessage({
name: this.$route.params.username,
msg,
});
},
},
};
</script>
<style>
.msg-form {
bottom: -28px;
position: absolute;
left: 0;
right: 0;
}
.msg-list {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 60px;
overflow-x: scroll;
}
</style>
import Vue from 'vue';
import Router from 'vue-router';
import Login from './views/Login.vue';
import ChatRoom from './views/ChatRoom.vue';
Vue.use(Router);
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'Login',
component: Login,
},
{
path: '/char-room/:username',
name: 'ChatRoom',
component: ChatRoom,
},
],
});
채팅시 자동 스크롤 이동을 위해
directive를 만들어 보겠습니다.
module.exports = (Vue) => {
// dom 업데이트시 스크롤을 최하단으로 이동시킵니다.
Vue.directive('auto-bottom', {
update: (el) => {
setTimeout(() => {
el.scrollTop = el.scrollHeight;
}, 0);
},
});
};
main.js에 import해줍니다.
import Directives from './plugins/directives'; Vue.use(Directives);
$ mkdir chat-server 폴더를 생성합니다. $ yarn init $ yarn add express $ yarn add socket.io
모듈을 설치합니다.
var app = require('express')();
var server = require('http').createServer(app);
var io = require('socket.io')(server,{
pingTimeout: 1000,
});
app.all('/*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
next();
});
// localhost:3000서버에 접속하면 클라이언트로 메세지을 전송한다
app.get('/', function(req, res) {
res.sendFile('Hellow Chating App Server');
});
io.on('connection', function(socket){
// 클라이언트로부터의 메시지가 수신되면
socket.on('chat', function(data) {
console.log('Message from %s: %s', data.name, data.msg);
var msg = {
from: {
name: data.name,
},
msg: data.msg
};
// 메시지를 전송한 클라이언트를 제외한 모든 클라이언트에게 메시지를 전송한다
socket.broadcast.emit('chat', msg);
});
socket.on('disconnect', function() {
console.log('user disconnected: ' + socket.name);
});
});
server.listen(3000);
소스 깃허브
예제를 따라쳤더니, 채팅 방 관련 ui가 보이지않습니다. 에러도 없고, 무슨 문제일까 생각해보다가 "MessageList.vue"파일에 있는 css가 어느 태그도 꾸미지 않고 있음을 알게 되어
태그에 css지정을 해줬는데도 채팅 방 관련 ui가 보이지 않네요.
혹시 이 이유말고 다른 이유로 채팅 방 관련 ui가 보이지 않는 문제가 생길 수 있을까요?
깃허브에 있는 소스 받고 나서 client 에 있는거 npm install 하고 npm run serve 했는데 실행이 안됩니다. eslint configuration 에러 라는데 eslint 설정해주고 json 파일까지 만들어 진거 확인했는데도 안됩니다... 혹시 해결 한분 있으신가요
젤 초반에 로그인 페이지부터 제대로 안나오네요ㅠ [Vue warn]: Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option. 이런 에러가 뜨는데 이유가 뭘까요?ㅠㅠ
감사합니다 ;)