본문 바로가기
💻CODING/react. vue

[vue] vue로 게시판 구현하기(ft. 검색, 게시물 추가, 편집, 삭제 기능)

by 코딩하는 갓디노 2020. 11. 29.

 

vue.js로 게시판을 만들고,
검색, 게시물 추가, 편집, 삭제하는
기능 구현입니다.

 

게시판 구현 화면

 

구현 내용:

· vue, vue material CDN 방식으로 html 문서에 설치
· api 서버 통신 대체용 임시 테스트 데이터 script 내 설정
· 게시판에 게시물 추가 - 추가 기능
· 해당 게시물 편집 - 편집 기능
· 해당 게시물 삭제 - 삭제 기능
· 게시물 제목 이용한 검색 기능

 

html 

간단한 마크업을 위해 vue, vue material을 cdn 방식으로 설치하고,
임의의 더미데이터를 삽입하여 데이터 추가, 검색, 편집, 삭제 기능을 구현한 코드입니다.

<!DOCTYPE html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="https://unpkg.com/vue-material/dist/vue-material.min.css">
    <link rel="stylesheet" href="https://unpkg.com/vue-material/dist/theme/default.css">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.1/css/all.css" 
    integrity="sha384-vp86vTRFVJgpjF9jiIGPEEqYqlDwgyBgEF109VFjmqGmIY/Y4HV4d3Gp2irVfcrp" 
    crossorigin="anonymous">
    <title>board</title>
    <style>
        .addBtn {
            position: fixed;
            bottom: 0;
            right: 0;
            font-size: 30px;
            padding: 10px;
            color: #f5b914;
        }

        i {
            cursor: pointer;
        }

        .listTitle {
            margin-bottom: 10px;
            background-color: #f5b914;
            font-weight: bold;
            padding: 5px 0;
        }

        .listInner {
            margin-bottom: 10px;
            background-color: #dedede;
            padding: 5px 0;
        }
    </style>
</head>

<body>
    <div id="app">
        <div style="max-width: 800px; margin: 10px;">
            <div class="md-layout">
                <div class="md-layout-item md-size-70">
                    <h1>Todo List</h1>
                </div>
                <div class="md-layout-item md-size-30">
                    <md-field>
                        <label for="search">Search for</label>
                        <md-input v-model="search" />
                    </md-field>
                </div>

                <div class="md-layout">
                    <div class="md-layout-item md-size-100">

                        <div class="md-layout listTitle">
                            <div class="md-layout-item md-size-30 text-info">SUBJECT</div>
                            <div class="md-layout-item md-size-50 text-info">DESCRIPTION</div>
                        </div>

                        <div class="md-layout listInner" v-for="(listItem, index) in sortedData" 
                           :key="index">
                            <div class="md-layout-item md-size-30 text-info"> {{ listItem.subject }} </div>
                            <div class="md-layout-item md-size-50 text-info"> {{ listItem.desc }} </div>

                            <div class="md-layout-item md-size-10 text-info">
                                <i class="fas fa-edit" @click="onEdit(index)"></i>
                            </div>
                            <div class="md-layout-item md-size-10 text-info">
                                <i class="fas fa-trash-alt" @click="onRemove(index)"></i>
                            </div>
                        </div>
                    </div>
                </div>
                <div>
                    <i class="fas fa-plus-circle addBtn" @click="onAdd"></i>
                </div>
            </div>

            <div>
                <md-dialog :md-active.sync="addDialog" @keydown.esc="closeDialog">
                    <md-dialog-title>Add</md-dialog-title>

                    <md-dialog-content>
                        <md-field>
                            <label for="subject">subject</label>
                            <md-input v-model="newData.subject" />
                        </md-field>

                        <md-field>
                            <label for="description">description</label>
                            <md-input v-model="newData.desc" @keydown.enter="submit" required />
                        </md-field>
                    </md-dialog-content>

                    <md-dialog-actions>
                        <md-button class="md-primary" @click="closeDialog">Close</md-button>
                        <md-button class="md-primary" @click="submit">Save</md-button>
                    </md-dialog-actions>
                </md-dialog>

                <md-dialog :md-active.sync="editDialog" @keydown.esc="closeDialog">
                    <md-dialog-title>Edit</md-dialog-title>

                    <md-dialog-content>
                        <md-field>
                            <label for="subject">subject</label>
                            <md-input v-model="selectedData.subject" />
                        </md-field>

                        <md-field>
                            <label for="description">description</label>
                            <md-input v-model="selectedData.desc" @keydown.enter="editedSubmit" 
                            required />
                        </md-field>
                    </md-dialog-content>

                    <md-dialog-actions>
                        <md-button class="md-primary" @click="closeDialog">Close</md-button>
                        <md-button class="md-primary" @click="editedSubmit">Save</md-button>
                    </md-dialog-actions>
                </md-dialog>

                <md-dialog :md-active.sync="removeDialog" @keydown.esc="closeDialog" 
                   @keydown.enter="removeData">
                    <md-dialog-title>Remove</md-dialog-title>

                    <md-dialog-content>
                        정말 삭제 하시겠습니까?
                    </md-dialog-content>

                    <md-dialog-actions>
                        <md-button class="md-primary" @click="closeDialog">Cancel</md-button>
                        <md-button class="md-primary" @click="removeData">Confirm</md-button>
                    </md-dialog-actions>
                </md-dialog>
            </div>

        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue"></script>
    <script src="https://unpkg.com/vue-material"></script>

    <script>
        Vue.use(VueMaterial.default);
        //api 통신 대체용 test data
        const testData = [{
            "id": 41,
            "subject": "Finland",
            "desc": "Calera de Tango"
        }, {
            "id": 42,
            "subject": "Lesotho",
            "desc": "Ancarano"
        }, {
            "id": 43,
            "subject": "Western Sahara",
            "desc": "Cochamó"
        }, {
            "id": 44,
            "subject": "Guernsey",
            "desc": "Villers-lez-Heest"
        }, {
            "id": 45,
            "subject": "Turkey",
            "desc": "Liedekerke"
        }, {
            "id": 46,
            "subject": "Dominica",
            "desc": "Nanterre"
        }, {
            "id": 47,
            "subject": "Saint Helena, Ascension and Tristan da Cunha",
            "desc": "Crato"
        }, {
            "id": 48,
            "subject": "Czech Republic",
            "desc": "Lossiemouth"
        }, {
            "id": 49,
            "subject": "Croatia",
            "desc": "Sale"
        }, {
            "id": 50,
            "subject": "South Georgia and The South Sandwich Islands",
            "desc": "North Shore"
        }, {
            "id": 51,
            "subject": "Malawi",
            "desc": "Newtonmore"
        }, {
            "id": 52,
            "subject": "Marshall Islands",
            "desc": "Piracicaba"
        }, {
            "id": 53,
            "subject": "Nepal",
            "desc": "Ponoka"
        }, {
            "id": 54,
            "subject": "Gibraltar",
            "desc": "Rabbi"
        }, {
            "id": 55,
            "subject": "Åland Islands",
            "desc": "Biarritz"
        }, {
            "id": 56,
            "subject": "Nepal",
            "desc": "Sant'Elia a Pianisi"
        }]

        new Vue({
            el: '#app',
            data: {
                addDialog: false,
                editDialog: false,
                removeDialog: false,
                listData: [],
                search: '',
                searchedData: [],
                curSelecdtIndex: 0, //선택한 index
                first: false,
                second: false,

                newData: {
                    id: Number,
                    subject: '',
                    desc: ''
                },
                selectedData: {
                    id: Number,
                    subject: '',
                    desc: ''
                }

            },
            created() {
                this.listData = testData;
            },

            computed: {
                //여기서 오타등의 에러가 났을 경우, toLowerCase function이 없다는 에러메시지 나옴
                sortedData() {
                    if (this.search) {
                        //listData filter 후, 검색한 데이터(결과값)를 listData에 할당
                        this.searchedData = this.listData.filter(
                            (data) => {
                                return data.subject.toLowerCase().includes(this.search.toLowerCase());
                            });

                        return this.listData = this.searchedData;
                    } else {
                        //데이터가 없을 경우, 전체 리스트 출력
                        return this.listData = testData;
                    }
                }
            },

            methods: {
                submit() {
                    const submitData = {
                        id: this.newData.id,
                        subject: this.newData.subject,
                        desc: this.newData.desc
                    }

                    //console.log(submitData);
                    this.listData.push(submitData);
                    this.closeDialog();
                    this.clearData();
                },

                editedSubmit(index) {
                    const editedData = {
                        id: this.selectedData.id,
                        subject: this.selectedData.subject,
                        desc: this.selectedData.desc,
                    }

                    //this.listData.push(editedData); 가 아님
                    //해당 위치에 데이터 추가: splice(해당 index, 갯수, 추가할 데이터)
                    this.listData.splice(this.curSelecdtIndex, 1, editedData);
                    this.closeDialog();
                },

                removeData() {
                    //데이터 삭제: splice(해당 index, 갯수)
                    this.listData.splice(this.curSelecdtIndex, 1);
                    this.removeDialog = false;
                },

                onAdd() {
                    this.addDialog = true;
                },

                onEdit(index) {
                    this.editDialog = true;

                    this.curSelecdtIndex = index;
                    this.selectedData.id = this.listData[this.curSelecdtIndex].id;
                    this.selectedData.subject = this.listData[this.curSelecdtIndex].subject;
                    this.selectedData.desc = this.listData[this.curSelecdtIndex].desc;
                },

                onRemove(index) {
                    this.removeDialog = true;
                    //현재 index번호를 보내주어야, removeDialog창에서 confirm 클릭후
                    //해당 list를 지울수 있음
                    this.curSelecdtIndex = index;
                },

                closeDialog() {
                    this.addDialog = false;
                    this.editDialog = false;
                    this.removeDialog = false;
                },

                clearData(key) {
                    for (let key in this.newData) {
                        this.newData[key] = '';
                    }
                },
            }
        })
    </script>
</body>

</html>

 

 

화면 보기(Result 클릭)

반응형

댓글