vue

 

yarn 으로 시작하기

 

1. yarn 설치

https://pakss328.medium.com/yarn이란-b4e8edf1638b

 

 

2. yarn global add @vue/cli-init

 

3.  vue init webpack bootstrap-bbs

$ vue init webpack bootstrap-bbs
 
? Project name vue-board
? Project description A Vue.js project
? Author junhochoi 
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? No
? Set up unit tests Yes
? Pick a test runner jest
? Setup e2e tests with Nightwatch? No
? Should we run `npm install` for you after the project has been created? (recommended) yarn
 
   vue-cli ·· Generated "vue-board".

 

 

main.js

 

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'


import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

// Make BootstrapVue available throughout your project
Vue.use(BootstrapVue)
// Optionally install the BootstrapVue icon components plugin
Vue.use(IconsPlugin)




Vue.config.productionTip = false



/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

 

 

App.vue

<template>
  <div id="app">
    <Header />
    <!-- <img src="./assets/logo.png"> -->
    <router-view/>
  </div>
</template>

<script>
import Header from '@/components/Header'

export default {
  name: 'App',
  components:{
    Header,
  }

}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
</style>

 

 

router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Board from '@/components/Board';
import ContentDetail from '@/components/ContentDetail';
import Create from  '@/components/Create';


Vue.use(Router)

export default new Router({
  mode:"history",
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: HelloWorld
    },
    {
      path: '/board/free',
      name: 'Board',
      component: Board
    },
    {
      path: '/board/free/detail/:contentId',
      name: 'ContentDetail',
      component: ContentDetail
    },
    {
      path: '/board/free/create/:contentId?',
      name: 'Create',
      component: Create
    },

  ]
})

 

 

 

components/Header.vue

<template>
  <div>
    <b-navbar toggleable="lg" type="dark" variant="info">
      <b-navbar-brand href="#">Vue.js로 게시판 만들기</b-navbar-brand>

      <b-navbar-toggle target="nav_collapse"/>

      <b-collapse is-nav id="nav_collapse">
        <b-navbar-nav>
          <b-nav-item href="#">공지사항</b-nav-item>
          <b-nav-item to="/board/free">자유게시판</b-nav-item>
          <b-nav-item href="#">구인구직</b-nav-item>
        </b-navbar-nav>
      </b-collapse>
    </b-navbar>
  </div>
</template>

<script>
export default {
  name: "Header",
  data() {
    return {};
  }
};
</script>

 

 

components/Board.vue

<template>
  <div>
    <b-table striped hover :items="items"  :fields="fields" @row-clicked="rowClick"></b-table>
    <b-button  @click="writeContent">글쓰기</b-button>
  </div>
</template>

<script>
import data from  '@/data';

export default {
    data() {
      let items=data.Content.sort((a,b)=>{ return b.content_id-a.content_id})
      items=items.map(contentItem=>{ return {...contentItem, user_name:data.User.filter(userItem=>userItem.user_id===contentItem.user_id)[0].name}})
     return {
  
        fields:[
          {
            key: 'content_id',
            label: '글번호',      
          },
          {
            key: 'title',
            label: '제목',      
          },
          {
            key: 'created_at',
            label: '작성일',      
          },
          {
            key: 'user_name',
            label: '글쓴',      
          }          
        ],
        items:items
      }
    },

    methods:{

       rowClick(item, index, e){
         this.$router.push({
           path: `/board/free/detail/${item.content_id}`
         });         
       },

       writeContent(){
         this.$router.push({
           path:'/board/free/create'
         });
       }

    }



  }
</script>

 

 

components/Create.vue

<template>
	
<b-container fluid class="mt-2 p-5">
  <b-row class="mt-2">
	  <b-col sm="12" >
		<b-input v-model="subject" placeholder="제목을 입력해 주세요" ></b-input>
	  </b-col>
	  <b-col sm="12" class="mt-2">
		<b-form-textarea
			v-model="context"
			placeholder="내용을 입력해 주세요"
			rows="3"
			max-rows="10"
		></b-form-textarea>
	   </b-col>
	 

	<b-col sm="12" class="mt-2">	
		<b-button @click="updateMode?  updateContent():uploadContent() ">저장</b-button>
		<b-button @click="cancle">취소</b-button>
	 </b-col>
	</b-row>
	</b-container>
		
</template>

<script>
import data from  '@/data';

export default {
	name:"Create",
	data(){

		return {
			now: "00:00:00",
			subject:"",
			context:"",
			userId:1,
			createdAt:"",
			updatedAt:null,
			updateObject:null,
			updateMode:this.$route.params.contentId>0?true:false
		}
	},

	created(){
		if(this.$route.params.contentId>0){
			const contentId=Number(this.$route.params.contentId);
			this.updateObject=data.Content.filter(item=>item.content_id===contentId)[0];
			this.subject=this.updateObject.title;
			this.context=this.updateObject.context;
			this.createdAt=this.updateObject.created_at;
		}
	},

	methods:{

		uploadContent(){

				let items=data.Content.sort((a,b)=>{return b.content_id-a.content_id});
				const content_id=items[0].content_id+1;
				data.Content.push({
					    content_id: content_id,
						user_id: this.userId,
						title: this.subject,
						context: this.context,
						created_at: this.currentTime(),
						updated_at: null
				});

				this.$router.push({
						path:'/board/free/'
				});
		},



		updateContent(){
			this.updateObject.title=this.subject;
			this.updateObject.context=this.context;
			this.updateObject.updated_at=this.currentTime();
			this.$router.push({
				 path:'/board/free/'
			});
		},


		cancle(){
			this.$router.push({
				path:'/board/free/'
			});
		},


		currentTime(){
			let today = new Date();  
			let year = today.getFullYear(); // 년도
			let month = today.getMonth() + 1;  // 월
			let date = today.getDate();  // 날짜
			let day = today.getDay();  // 요일
			
			month=this.dateFormat(month);
			date=this.dateFormat(date);		
			let currentDay=year + '-' + month + '-' + date;

			this.now = " "+this.dateFormat(today.getHours()) + ":" + this.dateFormat(today.getMinutes()) + ":" +this.dateFormat(today.getSeconds());
			console.log(currentDay+this.now);
			return 	currentDay+this.now;		
		 },

		 dateFormat(str){
			str= "00"+str;
			return str=str.slice(-2);
		 }

	}



}
</script>

<style>

</style>

 

 

 

components/ContentDetail.vue

<template>
<b-container fluid class="mt-5 p-5">
  <b-row class="mt-2">
    <b-col sm="2">
      <label for="textarea-small">글번호:</label>
    </b-col>
    <b-col sm="10">
       <b-form-input v-model="contentId" readonly ></b-form-input>
    </b-col>
  </b-row>

  <b-row class="mt-2">
    <b-col sm="2">
      <label for="textarea-small">글쓴이:</label>
    </b-col>
    <b-col sm="10">
       <b-form-input v-model="user" readonly ></b-form-input>
    </b-col>
  </b-row>

  <b-row class="mt-2">
    <b-col sm="2">
      <label for="textarea-default">등록일:</label>
    </b-col>
    <b-col sm="10">
      <b-form-input v-model="created" readonly ></b-form-input>
    </b-col>
  </b-row>


 <b-row class="mt-2">
    <b-col sm="2">
      <label for="textarea-default">수정일:</label>
    </b-col>
    <b-col sm="10">
       <b-form-input v-model="updatedAt" readonly ></b-form-input>
    </b-col>
  </b-row>
   

  <b-row class="mt-2">
    <b-col sm="2">
      <label for="textarea-large">내용:</label>
    </b-col>
    <b-col sm="10">
      <b-form-textarea      
        size="lg"
        rows="10"
        v-model="context"
      ></b-form-textarea>
    </b-col>
  </b-row>


  <b-row class="mt-2">
      <b-col sm="12" class="text-center">
         <b-button variant="primary" @click="updateData">수정</b-button>
         <b-button variant="success" @click="deleteData">삭제</b-button>
      </b-col>
  </b-row> 

<b-row class="mt-5">
<b-col sm="12 text-left"><span>댓글</span></b-col>
</b-row>
<b-row class="mb-5 comment">
    <b-col sm="12">      
      <CommentList :contentId="contentId"/>
    </b-col>   
  </b-row>

</b-container>
</template>



<script>
import data from '@/data';
import CommentList from '@/components/CommentList';

export default{

  name:"ContentDetail",
  components:{
    CommentList
  },

  data(){
    const contentId=Number(this.$route.params.contentId);
    const contentData=data.Content.filter(item=>item.content_id===contentId)[0]
    return{
      contentId:contentId,
      title:contentData.title,
      context:contentData.context,
      user:data.User.filter(item=>item.user_id===contentData.user_id)[0].name,
      created:contentData.created_at,
      updatedAt:contentData.updated_at
    }
  },

  methods:{
      updateData(){
          this.$router.push({
             path: `/board/free/create/${this.contentId}`
          })
      },

      deleteData(){
         const content_index = data.Content.findIndex(item => item.content_id === this.contentId);
          data.Content.splice(content_index, 1)
          this.$router.push({
            path: '/board/free'
          });
      }


  }

}
</script>
<style scoped>
.comment{
  border: 1px solid black;
  margin-top: 1rem;
  padding: 2rem;
}
</style>

 

 

 

 

components/CommentList.vue

<template>
  <div>		
	  <div  v-for="(item, index)  in comments" :key="item.comment_id"> 
	  	<div>
			  this index : {{index}}
			  <CommentListItem  :commentObject="item"  :reloadComment="reloadComment"  :commentIndex="index" />
		</div>
	  </div>

	

	<div class="mb-5">
		<CommentCreate :contentId="contentId" :reloadComment="reloadComment" />
	 </div>	  




 

 
	</div>  
</template>

<script>
import data from  '@/data';
import CommentListItem from "@/components/CommentListItem";
import CommentCreate from "@/components/CommentCreate";


export default {
	name:"CommentList",
	props:{
		contentId:Number,
	},

	components:{
		CommentListItem,
		CommentCreate,

	},
	data(){
		return {
			comments:data.Comment.filter(item => item.content_id === this.contentId)
		}		
	},


	methods:{
		reloadComment(){
			this.comments=data.Comment.filter(item => item.content_id === this.contentId)
		}
	}


}
</script>

<style>

</style>

 

 

 

 

components/CommentCreate.vue

<template>
  <div>
	<b-input-group :prepend="name" class="mt-3">
    <b-form-textarea
      v-model="context"
      placeholder="코멘트를 달아 주세"
      rows="3"
      max-rows="6"
    ></b-form-textarea>
	
	<b-input-group-append>
		<b-button variant="info" @click="isSubComment ?   createSubComment():createComment()">작성하기</b-button>	
	</b-input-group-append>
	
	</b-input-group>
  </div>	  
</template>

<script>
import data from  "@/data";

export default {
  name:"CommentCreate",
  props:{
    contentId:Number,
    reloadComment:Function,
    commentId:Number,
    isSubComment:Boolean,
    reloadSubComments:Function,
    subCommentToggle:Function
  },

	data(){
		return {
			name:"르라나",   
      context:''
		}
  },
  
  methods:{
    createComment(){
       data.Comment.push(
        {
          comment_id:data.Comment[data.Comment.length-1].comment_id+1,
          user_id:1,
          content_id:this.contentId,
          context:this.context,
          created_at:this.currentTime(),
          updated_at:null
        });
        this.reloadComment();
        this.context="";
    },

    createSubComment(){
       
        let subCommentobj={
          subcomment_id:data.SubComment[data.SubComment.length-1].subcomment_id+1,
          user_id:1,
          comment_id:this.commentId,
          context:this.context,
          created_at:this.currentTime(),
          updated_at:null
        }

        data.SubComment.push(
          subCommentobj
        );
        this.reloadSubComments();
        this.context="";
        this.subCommentToggle();
    },

    currentTime(){
        let today = new Date();  
        let year = today.getFullYear(); // 년도
        let month = today.getMonth() + 1;  // 월
        let date = today.getDate();  // 날짜
        let day = today.getDay();  // 요일
        
        month=this.dateFormat(month);
        date=this.dateFormat(date);		
        let currentDay=year + '-' + month + '-' + date;

        this.now = " "+this.dateFormat(today.getHours()) + ":" + this.dateFormat(today.getMinutes()) + ":" +this.dateFormat(today.getSeconds());
        console.log(currentDay+this.now);
        return 	currentDay+this.now;		
    },

    dateFormat(str){
      str= "00"+str;
      return str=str.slice(-2);
    }

  }
	

}


</script>

<style>

</style>

 

 

 

 

components/CommentListItem.vue

<template>
 <div>
    <div class="comment-list-item">
      <div class="comment-list-item-name">
        <div>{{name}}</div>
        <div>댓글 번호: {{commentId}}</div>
        <div>{{commentObject.created_at}}</div>
      </div>
      <div class="comment-list-item-context">{{commentObject.context}}</div>
      <div class="comment-list-item-button">
        <b-button variant="info"  @click="modalCommentBtn(commentObject)">수정</b-button>
        <b-button variant="info" @click="commentDelete">삭제</b-button>
        <b-button variant="info" @click="subCommentToggle">덧글 달기</b-button>
      </div>
      
	</div>




  <template v-if="subCommentCreateToggle" >
    <SubCommentCreate class="mb-3" :commentId="commentId" :isSubComment="true"  :reloadSubComments="reloadSubComments"  :subCommentToggle="subCommentToggle"  />
  </template>  

  <template v-if="subCommentList.length >0">
      <div v-for="item in subCommentList" :key="item.subcomment_id" class="comment-list-item-subcomment-list ">                
          <div class="comment-list-item">
            <div class="comment-list-item-name">
              <div>{{item.user_name}}</div>
              <div>댓글 번호: {{item.comment_id}}</div>
              <div>서브 댓글 번호: {{item.subcomment_id}}</div>
              <div>{{item.created_at}}</div>
            </div>
            <div class="comment-list-item-context">{{item.context}}</div>
            <div class="comment-list-item-button">
              <b-button variant="info" @click="modalSubCommentBtn(item)" >수정</b-button>
              <b-button variant="info"  @click="commentSubDelete(item.subcomment_id)">삭제</b-button>            
            </div>
          </div>
      </div>
  </template>


  <div>
    <CommentUpdateModal  ref="commentUpdateModal"  :reloadComment="reloadComment" :commentObject="commentObject"  :commentIndex="commentIndex" />   
  </div>

 </div>	
</template>



<script>
import data from "@/data";
import SubCommentCreate from "@/components/CommentCreate";
import CommentUpdateModal from "@/Components/CommentUpdateModal";


export default {
	name:"CommentListItem",
	props:{
    commentObject:Object,
    reloadComment:Function,
    commentIndex:Number,
	},

  components:{
    SubCommentCreate,
    CommentUpdateModal
  },

	data(){

		return{
       commentId:this.commentObject.comment_id,
       modalCommentObject:this.commentObject,
			// userId:this.commentObject.user_id,
			// contentId:this.commentObject.content_id,
      // updatedAt:this.commentObject.updated_at,
      
      name:data.User.filter(item=> item.user_id===this.commentObject.user_id)[0].name,
      
      subCommentList:data.SubComment.filter(
        item=>item.comment_id===this.commentObject.comment_id
      ).map(subCommentItem =>{return {
        ...subCommentItem, user_name:data.User.filter(item=> item.user_id===subCommentItem.user_id)[0].name,
      }}),

     subCommentCreateToggle:false,

		}

  },
  
  methods:{
    subCommentToggle(){
      this.subCommentCreateToggle=!this.subCommentCreateToggle;
    },

    reloadSubComments(){
      this.subCommentList=data.SubComment.filter(
        item=>item.comment_id===this.commentObject.comment_id
      ).map(subCommentItem =>{return {
        ...subCommentItem, user_name:data.User.filter(item=> item.user_id===subCommentItem.user_id)[0].name,
      }})
    },

    commentDelete(){
      if(confirm("정말 삭제 하시겠습니까?")){
        
        let commentId=this.commentObject.comment_id;
        let findItem=data.Comment.find(function(item){ return item.comment_id===commentId});
        let idx=data.Comment.indexOf(findItem);
        
        console.log("findItem:");
        console.dir(findItem);       
        console.log("idx:"+idx);

        data.Comment.splice(idx, 1);               
        this.reloadComment();
      }
    },

    commentSubDelete(subcommentId){
       if(confirm("정말 삭제 하시겠습니까?")){ 
         let findItem=data.SubComment.find(function(item){ return item.subcomment_id==subcommentId});
         let idx=data.SubComment.indexOf(findItem);

         data.SubComment.splice(idx, 1);
         this.reloadSubComments();
      }
    },

    modalCommentBtn(commentObject){
      this.$refs.commentUpdateModal.$refs['my-modal'].hide();
      this.$refs.commentUpdateModal.$refs['my-modal'].show();
    },



  }



}
</script>

<style scoped>
.comment-list-item {
  display: flex;
  justify-content: space-between;
  padding-bottom: 1em;
}
.comment-list-item-name {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  border: 0.5px solid black;
  padding: 1em;
}
.comment-list-item-context {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 50em;
  border: 0.5px solid black;
}
.comment-list-item-button {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  border: 0.5px solid black;
  padding: 1em;
}
.btn {
  margin-bottom: 1em;
}
.comment-list-item-subcomment-list {
  display: flex;
  justify-content: space-between;
  padding-bottom: 1em;
  margin-left: 10em;
} 
</style>

 

 

 

components/CommentUpdateModal.vue

<template>
  <div>
    <b-modal
      id="commentUpdate-modal"
      ref="my-modal"
      title="수정 하기"
      @show="resetModal"
      @hidden="resetModal"
      @ok="handleOk"
    >
      <form ref="form" @submit.stop.prevent="handleSubmit">
        <b-form-input
            ref="modalCommentId"
            v-model="commentId"
            :state="commentIdState"
            required
            hidden
          ></b-form-input>

          <b-form-textarea
            ref="modalContext"
            v-model="context"
            :state="contextState"
            required
          ></b-form-textarea>

      </form>
    </b-modal>


    <b-modal
      id="commentSubUpdate-modal"
      ref="my-sub-modal"
      title="서브 댓글 수정 하기"
      @show="resetModal"
      @hidden="resetModal"
      @ok="handleOk"
    >
      <form ref="form" @submit.stop.prevent="handleSubSubmit">     
          <b-form-textarea
            ref="subContext"
            id="sub_context"
            v-model="context"          
            required
          ></b-form-textarea>
      </form>
    </b-modal> 

  </div>
</template>


<script>
import data from "@/data";


 export default {
    name:"CommentUpdateModal",

    props:{
       reloadComment:Function,     
       commentObject:Object, 
       commentIndex:Number  ,
       reloadSubComments:Function 
    },


    data() {
      return {
        context: this.commentObject.context,
        commentId:this.commentObject.comment_id,
        contextState: null,
        commentIdState:null,
      }

    },

  
     methods: {
      checkFormValidity() {
        const valid = this.$refs.form.checkValidity()
        this.contextState = valid
        return valid
      },
      resetModal() {
       
      },
      handleOk(bvModalEvt) {
        // Prevent modal from closing
        bvModalEvt.preventDefault()
        // Trigger submit handler
        this.handleSubmit()
      },

      handleSubmit() {
        // Exit when the form isn't valid
        if (!this.checkFormValidity()) {
          alert("댓글을 작성해 주세요.");          
          this.$refs.modalContext.focus;
          return
        }

        //데이타 업데이트                
        let commentId=this.commentId;
        data.Comment.filter(item=>item.comment_id===commentId)[0].context=this.context;
        this.reloadComment();


        //모달 닫
        this.$nextTick(() => {
          this.$bvModal.hide('commentUpdate-modal');
        });    
      },


      handleSubSubmit() {
        // Exit when the form isn't valid
        if (!this.checkFormValidity()) {
          alert("댓글을 작성해 주세요.");          
          this.$refs.modalContext.focus;
          return
        }


      },


    }
  }
</script>

 

 

 

about author

PHRASE

Level 60  라이트

100가지 중에서 하나라도 가능성이 있다면 거기에 대해서 그야말로 만전을 기하는 것, 이것이 국방이다. -박정희

댓글 ( 6)

댓글 남기기

작성