美文网首页前端开发
4.文章列表页和详情页

4.文章列表页和详情页

作者: Rebirth_914 | 来源:发表于2019-04-17 20:09 被阅读138次
    1.思考

    一、文章列表页的三种布局方式?
    超过三张图片:横排三张图(选最后三张图)
    图片少于3张:图文左右排列(选最后一张图)
    无图片:只显示文章内容(字数控制)
    二、文章详情页的布局?
    图文混排
    三、关于文章模块的数据库设计问题?
    1.文章表:id、u_id、title、content、create_time
    2.图片表:id、a_id、img_url
    3.评论表:id、u_id、a_id、content、comment_time
    四、各层接口的设计?

    2.数据库设计
    • t_article


      article.png
    • t_comment


      comment.png
    • t_img


      img.png
    • 自行填充一些数据
      t_article的content字段样例

    <div>
    <img src="http://hcoder.oss-cn-beijing.aliyuncs.com/public/images/xcp2.png" width="100%" />grace.hcoder.net<br />富文本可以展示html标签 _
    </div>

    3.entity
    entity.png

    其中,Article、Img、Comment类参照以前写法,自行完成

    其余vo包中的两个视图对象类如下:


    articleVo.png commentVo.png
    4.mapper
    • ArticleMapper接口,需要声明如下方法,自行实现

    List<ArticleVO> selectAll();
    ArticleVO getArticleById(int aId);

    • ImgMapper接口

    List<Img> selectImgsByAId(int aId);

    • CommentMapper接口

    List<CommentVO> selectCommentsByAId(int aId);

    5.Service接口及实现、单元测试省略
    6.Controller
    • ArticleController
    package com.soft1721.jianyue.api.controller;
    
    import com.aliyun.oss.OSSClient;
    import com.soft1721.jianyue.api.entity.Article;
    import com.soft1721.jianyue.api.entity.Img;
    import com.soft1721.jianyue.api.entity.User;
    import com.soft1721.jianyue.api.entity.vo.ArticleVO;
    import com.soft1721.jianyue.api.entity.vo.CommentVO;
    import com.soft1721.jianyue.api.service.ArticleService;
    import com.soft1721.jianyue.api.service.CommentService;
    import com.soft1721.jianyue.api.service.ImgService;
    import com.soft1721.jianyue.api.util.ResponseResult;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.multipart.MultipartFile;
    
    import javax.annotation.Resource;
    import java.io.File;
    import java.io.IOException;
    import java.net.URL;
    import java.util.*;
    
    @RestController
    @RequestMapping(value = "/api/article")
    public class ArticleController {
        @Resource
        private ArticleService articleService;
        @Resource
        private CommentService commentService;
        @Resource
        private ImgService imgService;
    
        @GetMapping(value = "/list")
        public ResponseResult getAll() {
            List<ArticleVO> articleList = articleService.selectAll();
            return ResponseResult.success(articleList);
        }
    
        @GetMapping(value = "/{aId}")
        public ResponseResult getArticleById(@PathVariable("aId") int aId) {
            ArticleVO article = articleService.getArticleById(aId);
            List<CommentVO> comments = commentService.selectCommentsByAId(aId);
            Map<String,Object> map = new HashMap<>();
            map.put("article",article);
            map.put("comments",comments);
            return ResponseResult.success(map);
        }
    
    7.swagger测试
    8.前端
    • index.vue
    <template>
        <view class="container">
            <view class="article-box">
                <view class="article" v-for="(article,index) in articles" :key="index">
                    <!-- 标题 -->
                    <text class="article-title" @tap="gotoDetail(article.id)">{{article.title}}</text>
                    <!-- 大于等于三张图片的显示方式 -->
                    <view class="" v-if="article.imgs.length >= 3">
                        <view class="thumbnail-box">
                            <view class="thumbnail-item" v-for="(item,index1) in article.imgs" :key="index1" v-if="index1<3">
                                <image :src="item.imgUrl"></image>
                            </view>
                        </view>
                    </view>
                    <!-- 小于三张图片的显示方式 -->
                    <view class="" v-else-if="article.imgs.length >= 1">
                        <view class="text-img-box">
                            <view class="left">
                                <text>{{<!-- handleContent -->article.title}}...</text>
                            </view>
                            <view class="right">
                                <image :src="article.imgs[article.imgs.length - 1].imgUrl"></image>
                            </view>
                        </view>
                    </view>
                    <!-- 没有图片的显示方式 -->
                    <view class="text-box" v-else>
                        <text>{{<!-- handleContent -->article.title}}...</text>
                    </view>
                    <!-- 文章作者等信息 -->
                    <view class="article-info">
                        <image :src="article.avatar" class="avatar small"></image>
                        <text class="info-text">{{article.nickname}}</text>
                        <text class="info-text1">{{article.createTime}}</text>
                    </view>
                </view>
            </view>
            <view>
                <navigator url="../write/write" hover-class="navigator-hover" v-if="login" @tap="islogin()">
                    <button class="btn">+</button>
                </navigator>
                <navigator url="../signin/signin" hover-class="navigator-hover" @tap="islogin()" v-else>
                    <button class="btn">+</button>
                </navigator>
            </view>
    
    
        </view>
    </template>
    
    <script>
        export default {
            data() {
                return {
                    articles: [],
                    login:false
                };
            },
            onLoad: function() {
                this.getArticles();
            },
            onShow: function() {
                const loginKey = uni.getStorageSync('login_key');
                console.log(loginKey);
                if (loginKey) {
                    this.login = true;
                } else {
                    this.login = false;
                }
            },
    
            onPullDownRefresh: function() {
                this.getArticles();
            },
            methods: {
                
                changeTab: function() {
                    uni.navigateTo({
                        url: '../write/write'
                    })
                },
                getArticles: function() {
                    var _this = this;
                    uni.request({
                        url: this.apiServer + '/article/list',
                        method: 'GET',
                        header: {
                            'content-type': 'application/x-www-form-urlencoded'
                        },
                        success: res => {
                            _this.articles = res.data.data;
                        },
                        complete: function() {
                            uni.stopPullDownRefresh();
                        }
                    });
                },
                gotoDetail: function(aId) {
                    uni.navigateTo({
                        url: '../article_detail/article_detail?aId=' + aId
                    });
                },
                handleTime: function(createTime) {
                    var date = new Date(createTime);
                    var year = date.getFullYear();
                    var month =
                        date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1;
                    var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate();
                    var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours();
                    var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes();
                    var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds();
                    // 拼接
                    return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds;
                },
                handleContent: function(content) {
                    content = content.replace(/(\n)/g, '');
                    content = content.replace(/(\t)/g, '');
                    content = content.replace(/(\r)/g, '');
                    content = content.replace(/<\/?[^>]*>/g, '');
                    content = content.replace(/\s*/g, '');
                    return content.substring(0, 50);
                },
                islogin: function() {
                    if (this.login) {
                        console.log('已登录');
                    } else {
                        console.log('未登录');
                    }
                }
    
            }
        };
    </script>
    
    <style scoped>
        
        /* 圆形按钮 */
        .btn {
            /* 阴影效果,四个参数分别是:水平阴影位置、垂直阴影位置、阴影尺寸位置大小、阴影颜色 */
            box-shadow: 2px 5px 10px #AAA;
            width: 65px;
            height: 65px;
            margin: 10px;
            border-radius: 50%;
            padding: 0;
            cursor: pointer;
            border: none;
            outline: none;
            bottom: 35px;
            right: 20px;
            background: linear-gradient(40deg, #ffd86f, #fc6262);
            color: #FFF;
            display: flex;
            justify-content: center;
            align-items: center;
            position: fixed;
            font-size: 30px;
        }
    
        .icon-text {
            color: #FFFFFF;
            font-size: 35px;
        }
    
        .article-box {
            height: auto;
            width: 100%;
        }
    
        .article-title {
            font-weight: bold;
        }
    
        .article {
            display: flex;
            flex-direction: column;
            margin-top: 10px;
            border-bottom: 1px solid #EEEEEE;
            height: 210px;
        }
    
        .thumbnail-box {
            margin-top: 10px;
            display: flex;
            height: 120px;
            width: 100%;
        }
    
        .thumbnail-item {
            flex: 1 1 33%;
            height: 110px;
            margin-right: 5px;
            margin-left: 5px;
        }
    
        .thumbnail-item image {
            width: 100%;
            height: 100%;
        }
    
        .avatar {
            width: 50px;
            height: 50px;
        }
    
        .info-text {
            margin-left: 10px;
            font-size: 18px;
        }
    
        .info-text1 {
            margin-left: 5px;
            color: #CCCCCC;
            font-size: 16px;
        }
    
        .text-img-box {
            display: flex;
            margin-top: 15px;
        }
    
        .text-box {
            margin-top: 15px;
        }
    
        .left {
            flex: 1 1 70%;
        }
    
        .right {
            flex: 1 1 40%;
            height: 110px;
        }
    
        .right image {
            width: 100%;
            height: 100%;
        }
    
        .article-info {
            display: flex;
            align-items: center;
        }
        
    </style>
    
    
    • article_detail.vue
    <template>
        <view class="container">
            <text class="article-title">{{ article.title }}</text>
            <view class="article-info">
                <image :src="article.avatar" class="avatar small"></image>
                <text style="margin-left: 10px;">{{ article.nickname }}</text>
                <text class="info-text">{{ handleTime(article.createTime)}}</text>
                <!-- 登录用户和文章作者不是同一个人,就显示关注或取消关注按钮 -->
                <button v-if="userId != article.uId && !followed" class="btn follow-btn" @tap="follow">+ 关注</button>
                <button v-if="userId != article.uId && followed" class="btn follow-btn cancel" @tap="cancelFollow">取消</button>
            </view>
    
            <view class="grace-text" style="margin-top: 10px;">
                <rich-text :nodes="article.content" bindtap="tap"></rich-text>
            </view>
            <button v-if="!liked" class="like-btn" @tap="like">收藏 </button>
            <button  v-if="liked" class="cancel-like" @tap="cancelLike">取消</button>
            <text class="info-text">评论 {{ comments.length }}</text>
            <view class="comment-item" v-for="(comment, index) in comments" :key="index">
                <view class="left">
                    <image :src="comment.avatar" class="avatar small"></image>
                </view>
                <view class="right">
                    <view class="right-content">
                        <text>{{ comment.nickname }}</text>
                        <text>{{ comment.content }}</text>
                    </view>
                    <view class="right-time">
                        <text style="margin-right: 10px;">{{ comments.length - index }}楼·{{comment.commentTime}}</text>
                        <!-- <text>{{  handleTime(comment.commentTime)}}</text> -->
                    </view>
                </view>
            </view>
            
            <input class="uni-input comment-box" type="text" placeholder="写下你的评论" v-model="content" required="required" />
            <button class="green-btn" @tap="send">提交</button>
        </view>
    </template>
    
    <script>
        export default {
            data() {
                return {
                    article: {
                        aId: 0,
                        uId: 0,
                        title: '',
                        content: '',
                        avatar: '',
                        nickname: '',
                        createTime: ''
                    },
                    comments: [],
                    content: '',
                    userId: uni.getStorageSync('login_key').userId,
                    followed: false,
                    liked:false
                };
            },
            onLoad: function(option) {
                //option为object类型,会序列化上个页面传递的参数
                this.article.aId = option.aId;
            },
            onShow: function() {
                this.getArticle();
            },
            onPullDownRefresh: function() {
                this.getArticle();
            },
            methods: {
                getArticle: function() {
                    var _this = this;
                    uni.request({
                        url: this.apiServer + '/article/' + this.article.aId,
                        method: 'GET',
                        header: {
                            'content-type': 'application/x-www-form-urlencoded'
                        },
                        data: {
                            userId: this.userId
                        },
                        success: res => {
                            // console.log(res.data.data.article);
                            _this.article.aId = res.data.data.article.id;
                            _this.article.uId = res.data.data.article.uid;
                            _this.article.title = res.data.data.article.title;
                            _this.article.content = res.data.data.article.content;
                            _this.article.nickname = res.data.data.article.nickname;
                            _this.article.avatar = res.data.data.article.avatar;
                            _this.article.createTime = res.data.data.article.createTime;
                            _this.comments = res.data.data.comments;
                            if (res.data.data.followed === '已关注') {
                                _this.followed = true;
                            }
                        },
                        complete: function() {
                            uni.stopPullDownRefresh();
                        }
                    });
                },
                handleTime: function(date) {
                    var d = new Date(date);
                    var year = d.getFullYear();
                    var month = d.getMonth() + 1;
                    var day = d.getDate() < 10 ? '0' + d.getDate() : '' + d.getDate();
                    var hour = d.getHours() < 10 ? '0' + d.getHours() : '' + d.getHours();
                    var minutes = d.getMinutes() < 10 ? '0' + d.getMinutes() : '' + d.getMinutes();
                    var seconds = d.getSeconds() < 10 ? '0' + d.getSeconds() : '' + d.getSeconds();
                    return year + '-' + month + '-' + day + ' ' + hour + ':' + minutes + ':' + seconds;
                },
                send: function() {
                    console.log('评论人编号:' + this.userId + ',文章编号:' + this.article.aId + ',评论内容:' + this.content);
                    uni.request({
                        url: this.apiServer + '/comment/add',
                        method: 'POST',
                        header: {
                            'content-type': 'application/x-www-form-urlencoded'
                        },
                        data: {
                            aId: this.article.aId,
                            uId: this.userId,
                            content: this.content
                        },
                        success: res => {
                            if (res.data.code === 0) {
                                uni.showToast({
                                    title: '评论成功'
                                });
                                this.getArticle();
                                this.content = '';
                            }
                        }
                    });
                },
                follow: function() {
                    uni.request({
                        url: this.apiServer + '/follow/add',
                        method: 'POST',
                        header: {
                            'content-type': 'application/x-www-form-urlencoded'
                        },
                        data: {
                            fromUId: this.userId,
                            toUId: this.article.uId
                        },
                        success: res => {
                            if (res.data.code === 0) {
                                uni.showToast({
                                    title: '关注成功'
                                });
                                this.followed = true;
                            }
                        }
                    });
                },
                like: function() {
                    uni.request({
                        url: this.apiServer + '/like/add',
                        method: 'POST',
                        header: {
                            'content-type': 'application/x-www-form-urlencoded'
                        },
                        data: {
                            uId: this.userId,
                            aId: this.article.aId
                        },
                        success: res => {
                            if (res.data.code === 0) {
                                uni.showToast({
                                    title: '收藏成功'
                                });
                                this.liked = true;
                            }
                        }
                    });
                },
                cancelFollow: function() {
                    uni.request({
                        url: this.apiServer + '/follow/cancel',
                        method: 'POST',
                        header: {
                            'content-type': 'application/x-www-form-urlencoded'
                        },
                        data: {
                            fromUId: this.userId,
                            toUId: this.article.uId
                        },
                        success: res => {
                            if (res.data.code === 0) {
                                uni.showToast({
                                    title: '已取消关注'
                                });
                                this.followed = false;
                            }
                        }
                    });
                },
                cancelLike: function() {
                    uni.request({
                        url: this.apiServer + '/like/cancel',
                        method: 'POST',
                        header: {
                            'content-type': 'application/x-www-form-urlencoded'
                        },
                        data: {
                            uId: this.userId,
                            aId: this.article.aId
                        },
                        success: res => {
                            if (res.data.code === 0) {
                                uni.showToast({
                                    title: '已取消收藏'
                                });
                                this.liked = false;
                            }
                        }
                    });
                }
            }
        };
    </script>
    
    <style>
        
        .link {
            cursor: pointer;
        }
        
        .article-title {
            font-weight: bold;
            padding: 10px;
            font-size: 22px;
        }
    
        .article-info {
            display: flex;
            margin-top: 20px;
            align-items: center;
        }
    
        .grace-text {
            margin-top: 10px;
        }
    
        .avatar {
            width: 60px;
            height: 60px;
            margin-left: 5px;
        }
    
        .info-text {
            margin-left: 10px;
            font-size: 18px;
            margin-top: 10px;
            display: flex;
            flex-direction: column;
        }
    
        .btn {
            margin-right: 10px;
            width: 90px;
            height: 40px;
            background: #00C777;
            display: flex;
            justify-content: center;
            align-items: center;
            color: #EEEEEE;
        }
    
        .content {
            width: 90%;
            margin: auto;
        }
    
        .comment-item {
            display: flex;
            margin-top: 5px;
        }
    
        .right {
            display: flex;
            flex-direction: column;
            margin-left: 10px;
        }
    
        .right-content {
            display: flex;
            flex-direction: column;
        }
    
        .right-time {
            margin-top: 5px;
            color: #C1C1C1;
            font-size: 15px;
        }
    
        .uni-input {
            margin-top: 10px;
            font-size: 18px;
        }
    
        .green-btn {
            margin-top: 10px;
            width: 60%;
            cursor: pointer;
            border-radius: 10px;
            background: #00EE76;
            color: white;
        }
    
        .cancel {
            background-color:#AAAAAA;
        }
        .like-btn{
            width: 90px;
            height: 40px;
            background: white;
            display: flex;
            justify-content: center;
            align-items: center;
            color:#FF7900;
            border: 1px solid #FF7900;
            border-radius: 10px;
            margin-top: 10px;
        }
        .cancel-like{
            width: 90px;
            height: 40px;
            background-color: #aaa;
            display: flex;
            justify-content: center;
            align-items: center;
            border-radius: 10px;
            margin-top: 10px;
        }
        
    </style>
    
    
    具体代码及图片请转https://github.com/wxy1027/jianyue

    相关文章

      网友评论

        本文标题:4.文章列表页和详情页

        本文链接:https://www.haomeiwen.com/subject/qcjowqtx.html