<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vue3</title> <script src="https://unpkg.com/vue@next"></script> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> <style> .board_container{ margin: 20px; } .v-move{ transition: transform 1s; } textarea:active, textarea:focus, input:active, input:focus, .btn:active, .btn:focus { outline: none !important; box-shadow: none !important; } </style> </head> <body> <div id="app"> <div class="board_container"> <div class="row"> <div class="col-md-10 offset-md-1 text-center"> <h2>게시판 만들기</h2> <p>json 파일 읽기</p> <input type="file" @change="loadData"> <button @click="saveBoardList">게시판 저장</button> </div> </div> </div> <my-board-list v-if="listOk" :object="boardObject" @board-read="boardRead" @board-write="boardWrite" @board-delete="boardDelete" ></my-board-list> <my-board-read v-if="readOk" :object="boardInfo" @board-list="boardList" ></my-board-read> <my-board-write v-if="writeOk" @board-list="boardList" @board-save="boardSave" ></my-board-write> </div> <script> const MyBoardList={ props:['object'], template:` <div class="board_container"> <div class="row"> <div class="col-md-10 offset-md-1"> <table id="list" class="table table-striped text-center "> <thead> <tr> <th>글번호</th> <th>제목</th> <th>조회수</th> <th></th> </tr> </thead> <tbody> <tr v-if="object.length==0"> <td colspan="4"> 등록된 글이 없습니다.</td> </tr> <tr v-else v-for="(item , index) in object" :key="item.no" > <td>{{item.no}}</td> <td @click="boardRead(item)">{{item.title}}</td> <td>{{item.view}}</td> <td> <button @click="boardDelete(item.no)" class="btn btn-danger">삭제</button> </td> </tr> </tbody> <tfoot> <tr> <td colspan="4" style="text-align: right;"> <button @click="boardWrite" class="btn btn-success">글쓰기</button> </td> </tr> </tfoot> </table> </div> </div> </div> ` , methods :{ boardRead(item){ this.$emit('board-read', item); }, boardWrite(){ this.$emit('board-write'); }, boardDelete(no){ this.$emit('board-delete', no); } } }; const MyBoardRead={ props:['object'], template:` <div class="board_container"> <div class="row"> <div class="col-md-10 offset-md-1"> <table id="list" class="table"> <tbody> <tr class="border"> <th class="border">글제목</th> <td class="text-left">{{object.title}}</td> </tr> <tr> <td colspan="2"> {{object.content}} </td> </tr> </tbody> <tfoot> <tr> <td colspan="2" style="text-align: right;border-bottom: none;"> <button @click="boardList" class="btn btn-success">목록</button> </td> </tr> </tfoot> </table> </div> </div> </div> ` , methods:{ boardList(){ this.$emit('board-list'); } } } const MyBoardWrite={ data(){ return{ title:"", content:"" } }, template:` <div class="board_container"> <div class="row"> <div class="col-md-10 offset-md-1"> <table id="list" class="table"> <tbody> <tr class="border"> <th class="border">글제목</th> <td class="text-left"> <input v-model="title" class="form-control"> </td> </tr> <tr> <td colspan="2"> <textarea v-model="content" cols="10" style="width: 100%;" rows="10" class="form-control" ></textarea> </td> </tr> </tbody> <tfoot> <tr> <td colspan="2" style="text-align: right;border-bottom: none;"> <button @click="boardList" class="btn btn-success">목록</button> <button @click="boardSave" class="btn btn-primary">저장</button> </td> </tr> </tfoot> </table> </div> </div> </div> `, methods:{ boardList(){ this.$emit('board-list'); }, boardSave(){ this.$emit('board-save', this.title, this.content); } } } const app = { data() { return { boardObject:[ // {"no":1, "title" :"첫번째글","content":"첫번째 글 내용", "view":1}, // {"no":2, "title" :"두번째글","content":"두번째 글 내용", "view":0}, // {"no":3, "title" :"세번째글","content":"세번째 글 내용", "view":3} ], boardInfo:{}, listOk:true, readOk:false, writeOk:false } }, components: { 'my-board-list':MyBoardList, 'my-board-read':MyBoardRead, 'my-board-write':MyBoardWrite }, methods: { loadData(e){ const file =e.target.files[0]; if(file){ const reader =new FileReader(); let parentThis=this; reader.onload=function(e){ const json=JSON.parse(e.target.result); parentThis.boardObject=json; }; reader.readAsText(file); } }, saveBoardList(){ let data = this.boardObject; if(data.length == 0) { alert('저장할 게시판 내용이 없습니다.') return; } const filename = 'board.json'; if(typeof data === "object"){ data = JSON.stringify(data, undefined, 4); } const blob = new Blob([data], {type: 'text/json'}), e = document.createEvent('MouseEvents'), a = document.createElement('a'); a.download = filename; a.href = window.URL.createObjectURL(blob); a.dataset.downloadurl = ['text/json', a.download, a.href].join(':'); e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); a.dispatchEvent(e); } , boardRead(boardInfo){ for(let i=0; i<this.boardObject.length; i++){ if(this.boardObject[i].no==boardInfo.no){ let updateData={ "no":this.boardObject[i].no, "title":this.boardObject[i].title, "content":this.boardObject[i].content, "view":this.boardObject[i].view+1, } this.boardObject.splice(i, 1,updateData); break; } } this.boardInfo=boardInfo; this.listOk=false; this.readOk=true; } , boardList(){ this.listOk=true; this.writeOk=false; this.readOk=false; }, boardWrite(){ this.listOk=false; this.writeOk=true; }, boardSave(title, content){ let lastNo=0; if(this.boardObject.length!=0){ //마지막 번호 가져오기 lastNo=this.boardObject[this.boardObject.length-1].no } const data={ "no":lastNo+1, "title":title, "content":content, "view":0 } this.boardObject.push(data); this.boardList(); }, boardDelete(no){ for(let i=0; i<this.boardObject.length; i++){ if(this.boardObject[i].no==no){ this.boardObject.splice(i, 1); break; } } } }, } Vue.createApp(app).mount('#app'); </script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script> </body> </html>
댓글 ( 4)
댓글 남기기