vue小案例--简易评论区

一、小案例(评论区)

1、流程

(1)分析静态页面。(vue项目创建参考https://www.cnblogs.com/l-y-h/p/11241503.html)
(2)拆分静态页面,变成一个个组件。
(3)对组件编码,生成动态页面。

2、静态页面

参考来源:https://www.bilibili.com/video/av49099807/?p=22&t=1223

【举例:】
<!DOCTYPE html>
<html>

    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1.0">
        <!--此处如果bootstrap选用 4.3.1的版本,样式会无效(没去研究)-->
        <link href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet">
        <script src="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/js/bootstrap.js"></script>
        <title>vue_demo</title>
    </head>

    <body>
        <div id="app">
            <div>
                <!--头部-->
                <header class="site-header jumbotron">
                    <div class="container">
                        <div class="row">
                            <div class="col-xs-12">
                                <h1>欢迎来到吐槽大厅</h1>
                            </div>
                        </div>
                    </div>
                </header>

                <!--主体部分-->
                <!--bootstrap将页面分为12格,此处拆分为左4格,右8格-->
                <div class="container">
                    <div class="col-md-4">
                        <form action="form-horizontal">
                            <div class="form-group">
                                <label>用户名</label>
                                <input type="text" class="form-control" placeholder="用户名">
                            </div>
                            <div class="form-group">
                                <label>吐槽内容</label>
                                <textarea type="text" class="form-control" placeholder="吐槽内容"></textarea>
                            </div>
                            <div class="form-group">
                                <div class="col-sm-offset-2 col-sm-10">
                                    <button type="button" class="btn btn-default pull-right">提交</button>
                                </div>
                            </div>
                        </form>
                    </div>
                    <!--md4 for Add end    -->

                    <div class="col-md-8">
                        <h3 class="reply">吐槽回复:</h3>
                        <h2>暂无吐槽,点击左侧添加吐槽吧!</h2>
                        <ul class="list-group">
                            <li class="list-group-item">
                                <div class="handle col-sm-offset-2 col-sm-10">
                                    <a class="pull-right">删除</a>
                                </div>
                                <p class="user"><span>Tom</span><span>说:</span></p>
                            </li>
                            <li class="list-group-item">
                                <div class="handle col-sm-offset-2 col-sm-10">
                                    <a class="pull-right">删除</a>
                                </div>
                                <p class="user"><span>Tom</span><span>说:</span></p>
                            </li>
                        </ul>
                    </div>
                    <!--md8 for List end -->
                </div>
            </div>
        </div>
        <!--app -->
    </body>

</html>

 

页面截图:

 

 

 

3、拆分静态页面,

拆分静态页面,使其变成一个个静态组件。

Step1:是一个大的组件(App),里面包含各种组件。

Step2:页面内容可以拆分成 提交吐槽组件(Comment),吐槽回复组件(Comments)。

Step3:吐槽回复组件里面 可以对 每一条吐槽进行拆分,即每个吐槽为一个组件(Item)。

  

文件结构如下:

 

【主要文件与文件夹:】
index.html    主页面,所有组件操作均为其服务,在此处引入css、js文件
main.js       vue入口文件,从此处启动vue
App.vue       App.vue组件,项目的入口组件
components      里面保存各个小组件


【index.html】
<!DOCTYPE html>
<html lang="en">
  <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">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <!--所有组件都是为index.html服务的,所以在此处引入css、js文件-->
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet">
    <title>vuedemo</title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but vuedemo doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>


【main.js】
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')


【App.vue】
<template>
    <div>
        <!--头部-->
        <header class="site-header jumbotron">
            <div class="container">
                <div class="row">
                    <div class="col-xs-12">
                        <h1>欢迎来到吐槽大厅</h1>
                    </div>
                </div>
            </div>
        </header>

        <!--主体部分-->
        <!--bootstrap将页面分为12格,此处拆分为左4格,右8格-->
        <div class="container">
            <!--使用各组件-->
            <Comment></Comment>
            <Comments></Comments>
        </div>
    </div>
    <!--App -->
</template>

<script>
    // 引入各组件
    import Comment from './components/Comment.vue'
    import Comments from './components/Comments.vue'

    export default {
        name: 'app',
        // 注册各组件
        components: {
            Comment,
            Comments
        }
    }
</script>

<style>
</style>


【Comment.vue】
<template>
    <div class="col-md-4">
        <form action="form-horizontal">
            <div class="form-group">
                <label>用户名</label>
                <input type="text" class="form-control" placeholder="用户名">
            </div>
            <div class="form-group">
                <label>吐槽内容</label>
                <textarea type="text" class="form-control" placeholder="吐槽内容"></textarea>
            </div>
            <div class="form-group">
                <div class="col-sm-offset-2 col-sm-10">
                    <button type="button" class="btn btn-default pull-right">提交</button>
                </div>
            </div>
        </form>
    </div>
    <!--Comment -->
</template>

<script>
    export default{
        name: 'comment'
    }
</script>

<style>

</style>


【Comments.vue】
<template>
    <div class="col-md-8">
        <h3 class="reply">吐槽回复:</h3>
        <h2>暂无吐槽,点击左侧添加吐槽吧!</h2>
        <ul class="list-group">
            <Item></Item>
        </ul>
    </div>
    <!--md8 for List end -->
</template>

<script>
    import Item from './Item.vue'

    export default{
        name: 'comments',
        components: {
            Item
        }
    }
</script>

<style>

</style>


【Item.vue】
<template>
    <!--注意,需要使用div包裹,否则会报错-->
    <div>
        <li class="list-group-item">
            <div class="handle col-sm-offset-2 col-sm-10">
                <a class="pull-right">删除</a>
            </div>
            <p class="user"><span>Tom</span><span>说:</span></p>
        </li>
        <li class="list-group-item">
            <div class="handle col-sm-offset-2 col-sm-10">
                <a class="pull-right">删除</a>
            </div>
            <p class="user"><span>Tom</span><span>说:</span></p>
        </li>
    </div>
</template>

<script>
    export default{
        name: 'item'
    }
</script>

<style>

</style>

 

拆分后效果与原静态页面一致。

 

 

 

4、组件间值的传递(组件间通信)

  静态页面上吐槽区的内容不会是写好的,是动态生成的,那么如何生成,就涉及到组件间的值的传递。通过props 来声明属性,使用data来传递数据(属性值),使用 v-bind 绑定属性。

【对上面代码进行修改】
App.vue              获取数据,将数据往吐槽区(Comments.vue)传
Comments.vue         接收App.vue传来的数据,将每条数据往Item.vue传
Item.vue             接收Comments.vue传来的数据并显示

要是一眼看不出来,可以下载个Bcompare软件,自行比较一下代码间的区别。

【App.vue】
<template>
    <div>
        <!--头部-->
        <header class="site-header jumbotron">
            <div class="container">
                <div class="row">
                    <div class="col-xs-12">
                        <h1>欢迎来到吐槽大厅</h1>
                    </div>
                </div>
            </div>
        </header>

        <!--主体部分-->
        <!--bootstrap将页面分为12格,此处拆分为左4格,右8格-->
        <div class="container">
            <!--使用各组件-->
            <Comment></Comment>
                   <!--需使用v-bind绑定属性-->
            <Comments :contents="contents"></Comments>
        </div>
    </div>
    <!--App -->
</template>

<script>
    // 引入各组件
    import Comment from './components/Comment.vue'
    import Comments from './components/Comments.vue'

    export default {
        name: 'app',
        // 注册各组件
        components: {
            Comment,
            Comments
        },

        // 传递数据
        data(){
            return {
                contents:[
                    {name: 'tom', content: '妈妈,我想吃烤山药'},
                    {name: 'jarry', content: '吃,吃大块的'},
                    {name: 'jarry', content: '两块够不'},
                    {name: 'tom', content: '够了,妈妈真好,谢谢妈妈'},
                ]
            }
        }
    }
</script>

<style>
</style>


【Comments.vue】
<template>
    <div class="col-md-8">
        <h3 class="reply">吐槽回复:</h3>
        <ul class="list-group">
            <Item v-for="(content, index) in contents" :key="index" :content="content"></Item>
        </ul>
    </div>
    <!--md8 for List end -->
</template>

<script>
    import Item from './Item.vue'

    export default{
        name: 'comments',
        // 声明接收属性,此属性可以在该组件中使用
        props: ['contents'],  // 只指定属性名

        // 注册组件
        components: {
            Item
        }
    }
</script>

<style>

</style>


【Item.vue】
<template>
    <!--注意,需要使用div包裹,否则会报错-->
    <div>
        <li class="list-group-item">
            <div class="handle col-sm-offset-2 col-sm-10">
                <a class="pull-right">删除</a>
            </div>
            <p class="user"><span style="font-size: 18px;">{{content.name}}</span><span style="font-size: 18px;">说:</span>{{content.content}}</p>
        </li>
    </div>
</template>

<script>
    export default{
        name: 'item',
        props: {
            // 指定属性名以及属性值的类型
            content : Object
         }
    }
</script>

<style>

</style>

效果如下图:

 

 

5、动态交互--添加

  实现添加吐槽操作。

  使用v-on 绑定事件,使用v-model 实现数据的双向绑定,方法也可以使用 v-bind 绑定 并进行组件通信。

【对上面代码进行修改】
App.vue            定义增加吐槽的方法,并作为属性传递给Comment.vue组件
Comment.vue        接收属性,并定义添加数据的事件

【App.vue】
<template>
    <div>
        <!--头部-->
        <header class="site-header jumbotron">
            <div class="container">
                <div class="row">
                    <div class="col-xs-12">
                        <h1>欢迎来到吐槽大厅</h1>
                    </div>
                </div>
            </div>
        </header>

        <!--主体部分-->
        <!--bootstrap将页面分为12格,此处拆分为左4格,右8格-->
        <div class="container">
            <!--使用各组件-->
            <Comment :addComment="addComment"></Comment>
            <!--需使用v-bind绑定属性-->
            <Comments :contents="contents"></Comments>
        </div>
    </div>
    <!--App -->
</template>

<script>
    // 引入各组件
    import Comment from './components/Comment.vue'
    import Comments from './components/Comments.vue'

    export default {
        name: 'app',
        // 注册各组件
        components: {
            Comment,
            Comments
        },

        // 传递数据
        data(){
            return {
                contents:[
                    {name: 'tom', content: '妈妈,我想吃烤山药'},
                    {name: 'jarry', content: '吃,吃大块的'},
                    {name: 'jarry', content: '两块够不'},
                    {name: 'tom', content: '够了,妈妈真好,谢谢妈妈'},
                ]
            }
        },

        // 操作数据的方法
        methods: {
            addComment(comment){
                // 在数组头部插入数据
                this.contents.unshift(comment);
            }
        }
    }
</script>

<style>
</style>


【Comment.vue】
<template>
    <div class="col-md-4">
        <form action="form-horizontal">
            <div class="form-group">
                <label>用户名</label>
                <input type="text" class="form-control" placeholder="用户名" v-model="name">
            </div>
            <div class="form-group">
                <label>吐槽内容</label>
                <textarea type="text" class="form-control" placeholder="吐槽内容" v-model="content"></textarea>
            </div>
            <div class="form-group">
                <div class="col-sm-offset-2 col-sm-10">
                    <button type="button" class="btn btn-default pull-right" @click="add">提交</button>
                </div>
            </div>
        </form>
    </div>
    <!--Comment -->
</template>

<script>
    export default{
        name: 'comment',
        data(){
            return {
                name : '',
                content : ''
            }
        },
        props:{
            // 定义属性类型、属性值类型、必须性
            addComment: {
                type: Function,
                required: true
            }
        },
        methods: {
            add(){
                // step1:进行合法性检验
                const name = this.name.trim();
                const content = this.content.trim();
                if(!name || !content){
                    alert("输入内容不能为空");
                    return;
                }

                // step2:将name,content封装成一个comment(吐槽)对象
                const comment = {name, content};

                console.log(comment);
                // step3: 将comment 加入到 comments(吐槽区)
                this.addComment(comment);

                // step4:清空输入框
                this.name = '';
                this.content = '';
            }
        }
    }
</script>

<style>

</style>

效果:

 

 

 

 

 

 

6、动态交互--删除

  进行删除操作。

  类似于添加操作。

【对上面文件进行修改】
App.vue             定义删除数据的方法,并将其作为属性传递给Comments.vue
Comments.vue        作为一个中间的组件,传递index以及删除方法
Item.vue            接收Comments.vue传递的属性,并定义删除事件


【App.vue】
<template>
    <div>
        <!--头部-->
        <header class="site-header jumbotron">
            <div class="container">
                <div class="row">
                    <div class="col-xs-12">
                        <h1>欢迎来到吐槽大厅</h1>
                    </div>
                </div>
            </div>
        </header>

        <!--主体部分-->
        <!--bootstrap将页面分为12格,此处拆分为左4格,右8格-->
        <div class="container">
            <!--使用各组件-->
            <Comment :addComment="addComment"></Comment>
            <!--需使用v-bind绑定属性-->
            <Comments :contents="contents" :deleteComment="deleteComment"></Comments>
        </div>
    </div>
    <!--App -->
</template>

<script>
    // 引入各组件
    import Comment from './components/Comment.vue'
    import Comments from './components/Comments.vue'

    export default {
        name: 'app',
        // 注册各组件
        components: {
            Comment,
            Comments
        },

        // 传递数据
        data(){
            return {
                contents:[
                    {name: 'tom', content: '妈妈,我想吃烤山药'},
                    {name: 'jarry', content: '吃,吃大块的'},
                    {name: 'jarry', content: '两块够不'},
                    {name: 'tom', content: '够了,妈妈真好,谢谢妈妈'},
                ]
            }
        },

        // 操作数据的方法
        methods: {
            addComment(comment){
                // 在数组头部插入数据
                this.contents.unshift(comment);
            },
            deleteComment(index){
                // 删除指定下标的数据
                this.contents.splice(index, 1);
            }
        }
    }
</script>

<style>
</style>


【Comments.vue】
<template>
    <div class="col-md-8">
        <h3 class="reply">吐槽回复:</h3>
        <h3 v-show="contents.length === 0">暂无吐槽,点击左侧提交吐槽!!!</h3>
        <ul class="list-group">
            <Item v-for="(content, index) in contents" :key="index" :content="content" :deleteComment="deleteComment" :index="index"></Item>
        </ul>
    </div>
    <!--md8 for List end -->
</template>

<script>
    import Item from './Item.vue'

    export default {
        name: 'comments',
        // 声明接收属性,此属性可以在该组件中使用
        props: ['contents', 'deleteComment'], // 只指定属性名

        // 注册组件
        components: {
            Item
        }
    }
</script>

<style>

</style>


【Item.vue】
<template>
    <!--注意,需要使用div包裹,否则会报错-->
    <div>
        <li class="list-group-item">
            <div class="handle col-sm-offset-2 col-sm-10">
                <a class="pull-right" @click="deleteItem">删除</a>
            </div>
            <p class="user"><span style="font-size: 18px;">{{content.name}}</span><span style="font-size: 18px;">说:</span>{{content.content}}</p>
        </li>
    </div>
</template>

<script>
    export default{
        name: 'item',
        props: {
            // 指定属性名以及属性值的类型
            content : Object,
            deleteComment : Function,
            index : Number
         },
         methods: {
             deleteItem() {
                 const {content, deleteComment, index} = this;
                 // 使用  反引号 + ${}, ES6模板字符串
                 if(window.confirm(`确定删除${content.name}的评论吗?`)){
                     deleteComment(index);
                 }
             }
         }
    }
</script>

<style>

</style>

 

运行结果:

 

 

 

 

 

 

7、完整代码

(1)项目结构以及修改的文件

 

 

(2)代码

【index.html】
<!DOCTYPE html>
<html lang="en">
  <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">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <!--所有组件都是为index.html服务的,所以在此处引入css、js文件-->
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet">
    <title>vuedemo</title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but vuedemo doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>


【main.js】
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')


【App.vue】
<template>
    <div>
        <!--头部-->
        <header class="site-header jumbotron">
            <div class="container">
                <div class="row">
                    <div class="col-xs-12">
                        <h1>欢迎来到吐槽大厅</h1>
                    </div>
                </div>
            </div>
        </header>

        <!--主体部分-->
        <!--bootstrap将页面分为12格,此处拆分为左4格,右8格-->
        <div class="container">
            <!--使用各组件-->
            <Comment :addComment="addComment"></Comment>
            <!--需使用v-bind绑定属性-->
            <Comments :contents="contents" :deleteComment="deleteComment"></Comments>
        </div>
    </div>
    <!--App -->
</template>

<script>
    // 引入各组件
    import Comment from './components/Comment.vue'
    import Comments from './components/Comments.vue'

    export default {
        name: 'app',
        // 注册各组件
        components: {
            Comment,
            Comments
        },

        // 传递数据
        data(){
            return {
                contents:[
                    {name: 'tom', content: '妈妈,我想吃烤山药'},
                    {name: 'jarry', content: '吃,吃大块的'},
                    {name: 'jarry', content: '两块够不'},
                    {name: 'tom', content: '够了,妈妈真好,谢谢妈妈'},
                ]
            }
        },

        // 操作数据的方法
        methods: {
            addComment(comment){
                // 在数组头部插入数据
                this.contents.unshift(comment);
            },
            deleteComment(index){
                // 删除指定下标的数据
                this.contents.splice(index, 1);
            }
        }
    }
</script>

<style>
</style>


【Comment.vue】
<template>
    <div class="col-md-4">
        <form action="form-horizontal">
            <div class="form-group">
                <label>用户名</label>
                <input type="text" class="form-control" placeholder="用户名" v-model="name">
            </div>
            <div class="form-group">
                <label>吐槽内容</label>
                <textarea type="text" class="form-control" placeholder="吐槽内容" v-model="content"></textarea>
            </div>
            <div class="form-group">
                <div class="col-sm-offset-2 col-sm-10">
                    <button type="button" class="btn btn-default pull-right" @click="add">提交</button>
                </div>
            </div>
        </form>
    </div>
    <!--Comment -->
</template>

<script>
    export default{
        name: 'comment',
        data(){
            return {
                name : '',
                content : ''
            }
        },
        props:{
            // 定义属性类型、属性值类型、必须性
            addComment: {
                type: Function,
                required: true
            }
        },
        methods: {
            add(){
                // step1:进行合法性检验
                const name = this.name.trim();
                const content = this.content.trim();
                if(!name || !content){
                    alert("输入内容不能为空");
                    return;
                }

                // step2:将name,content封装成一个comment(吐槽)对象
                const comment = {name, content};

                console.log(comment);
                // step3: 将comment 加入到 comments(吐槽区)
                this.addComment(comment);

                // step4:清空输入框
                this.name = '';
                this.content = '';
            }
        }
    }
</script>

<style>

</style>


【Comments.vue】
<template>
    <div class="col-md-8">
        <h3 class="reply">吐槽回复:</h3>
        <h3 v-show="contents.length === 0">暂无吐槽,点击左侧提交吐槽!!!</h3>
        <ul class="list-group">
            <Item v-for="(content, index) in contents" :key="index" :content="content" :deleteComment="deleteComment" :index="index"></Item>
        </ul>
    </div>
    <!--md8 for List end -->
</template>

<script>
    import Item from './Item.vue'

    export default {
        name: 'comments',
        // 声明接收属性,此属性可以在该组件中使用
        props: ['contents', 'deleteComment'], // 只指定属性名

        // 注册组件
        components: {
            Item
        }
    }
</script>

<style>

</style>


【Item.vue】
<template>
    <!--注意,需要使用div包裹,否则会报错-->
    <div>
        <li class="list-group-item">
            <div class="handle col-sm-offset-2 col-sm-10">
                <a class="pull-right" @click="deleteItem">删除</a>
            </div>
            <p class="user"><span style="font-size: 18px;">{{content.name}}</span><span style="font-size: 18px;">说:</span>{{content.content}}</p>
        </li>
    </div>
</template>

<script>
    export default{
        name: 'item',
        props: {
            // 指定属性名以及属性值的类型
            content : Object,
            deleteComment : Function,
            index : Number
         },
         methods: {
             deleteItem() {
                 const {content, deleteComment, index} = this;
                 // 使用  反引号 + ${}, ES6模板字符串
                 if(window.confirm(`确定删除${content.name}的评论吗?`)){
                     deleteComment(index);
                 }
             }
         }
    }
</script>

<style>

</style>

 

运行结果此处不再重复截图,与上述截图相同。

posted @ 2021-02-05 14:53  壮灬哥  阅读(439)  评论(0编辑  收藏  举报