SpringBoot+Vue豆宝社区前后端分离项目手把手实战系列教程16---文章详情功能实现

167 阅读2分钟

豆宝社区项目实战教程简介

本项目实战教程配有免费视频教程,配套代码完全开源。手把手从零开始搭建一个目前应用最广泛的Springboot+Vue前后端分离多用户社区项目。本项目难度适中,为便于大家学习,每一集视频教程对应在Github上的每一次提交。

项目首页截图

image

代码开源地址

前端 后端

视频教程地址

视频教程

前端技术栈

Vue Vuex Vue Router Axios Bulma Buefy Element Vditor DarkReader

后端技术栈

Spring Boot Mysql Mybatis MyBatis-Plus Spring Security JWT Lombok

##前端

1.src\api\修改post.js

// 获取文章详情
export function getTopic(id) {
  return request({
    url: `/post`,
    method: 'get',
    params: {
      id: id
    }
  })
}

2.路由

  // 文章详情
  {
    name: "post-detail",
    path: "/post/:id",
    component: () => import("@/views/post/Detail"),
    meta: { title: "详情" },
  }

3.src\views\post\创建Detail.vue

<template>
  <div class="columns">
    <!--文章详情-->
    <div class="column is-three-quarters">
      <!--主题-->
      <el-card
        class="box-card"
        shadow="never"
      >
        <div
          slot="header"
          class="has-text-centered"
        >
          <p class="is-size-5 has-text-weight-bold">{{ topic.title }}</p>
          <div class="has-text-grey is-size-7 mt-3">
            <span>{{ dayjs(topic.createTime).format('YYYY/MM/DD HH:mm:ss') }}</span>
            <el-divider direction="vertical" />
            <span>发布者:{{ topicUser.alias }}</span>
            <el-divider direction="vertical" />
            <span>查看:{{ topic.view }}</span>
          </div>
        </div>

        <!--Markdown-->
        <div id="preview" />

        <!--标签-->
        <nav class="level has-text-grey is-size-7 mt-6">
          <div class="level-left">
            <p class="level-item">
              <b-taglist>
                <router-link
                  v-for="(tag, index) in tags"
                  :key="index"
                  :to="{ name: 'tag', params: { name: tag.name } }"
                >
                  <b-tag type="is-info is-light mr-1">
                    {{ "#" + tag.name }}
                  </b-tag>
                </router-link>
              </b-taglist>
            </p>
          </div>
          <div
            v-if="token && user.id === topicUser.id"
            class="level-right"
          >
            <router-link
              class="level-item"
              :to="{name:'topic-edit',params: {id:topic.id}}"
            >
              <span class="tag">编辑</span>
            </router-link>
            <a class="level-item">
              <span
                class="tag"
                @click="handleDelete(topic.id)"
              >删除</span>
            </a>
          </div>
        </nav>
      </el-card>

    </div>

    <div class="column">
      作者信息
    </div>
  </div>
</template>

<script>
import { deleteTopic, getTopic } from '@/api/post'
import { mapGetters } from 'vuex'
import Vditor from 'vditor'
import 'vditor/dist/index.css'
export default {
  name: 'TopicDetail',
  computed: {
    ...mapGetters([
      'token','user'
    ])
  },
  data() {
    return {
      flag: false,
      topic: {
        content: '',
        id: this.$route.params.id
      },
      tags: [],
      topicUser: {}
    }
  },
  mounted() {
    this.fetchTopic()
  },
  methods: {
    renderMarkdown(md) {
      Vditor.preview(document.getElementById('preview'), md, {
        hljs: { style: 'github' }
      })
    },
    // 初始化
    async fetchTopic() {
      getTopic(this.$route.params.id).then(response => {
        const { data } = response
        document.title = data.topic.title
        this.topic = data.topic
        this.tags = data.tags
        this.topicUser = data.user
        // this.comments = data.comments
        this.renderMarkdown(this.topic.content)
        this.flag = true
      })
    },
    handleDelete(id) {
      deleteTopic(id).then(value => {
        const { code, message } = value
        alert(message)
        if (code === 200) {
          setTimeout(() => {
            this.$router.push({ path: '/' })
          }, 500)
        }
      })
    }
  }
}
</script>

<style>
#preview {
  min-height: 300px;
}
</style>

文章详情后端

vo

import lombok.Data;

@Data
public class ProfileVO {

    /**
     * 用户ID
     */
    private String id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 别称
     */
    private String alias;

    /**
     * 头像
     */
    private String avatar;

    /**
     * 关注数
     */
    private Integer followCount;

    /**
     * 关注者数
     */
    private Integer followerCount;

    /**
     * 文章数
     */
    private Integer topicCount;

    /**
     * 专栏数
     */
    private Integer columns;

    /**
     * 评论数
     */
    private Integer commentCount;

}

BmsFollow

@Data
@TableName("bms_follow")
public class BmsFollow implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(type = IdType.AUTO)
    private Integer id;

    /**
     * 被关注人id
     */
    @TableField("parent_id")
    private String parentId;

    /**
     * 关注人id
     */
    @TableField("follower_id")
    private String followerId;

    public BmsFollow() {
    }

}

BmsFollowMapper

public interface BmsFollowMapper extends BaseMapper<BmsFollow> {
}

BmsPostController

/**
 * 根据id获取文章详情
 *
 * @return
 */
@GetMapping
public ApiResult getPostById(@RequestParam("id") String id) {
    Map<String, Object> map = postService.getPostById(id);
    return ApiResult.success(map);
}

BmsPostservice

public Map<String, Object> getPostById(String id) {
        HashMap<String, Object> map = new HashMap<>(16);
        // 查询文章
        BmsPost post = this.getById(id);
        Assert.notNull(post,"当前文章不存在,或被删除");
        // 查看 +1
        post.setView(post.getView()+1);
        this.updateById(post);
        // 表情转码
        post.setContent(EmojiParser.parseToUnicode(post.getContent()));
        map.put("topic",post);
        // 文章的标签
        // 标签
        QueryWrapper<BmsPostTag> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(BmsPostTag::getPostId, post.getId());
        Set<String> set = new HashSet<>();
        for (BmsPostTag articleTag : postTagService.list(wrapper)) {
            set.add(articleTag.getTagId());
        }
        List<BmsTag> tags = tagService.listByIds(set);
        map.put("tags", tags);

        // 作者
        ProfileVO user = userService.getUserProfile(post.getUserId());
        map.put("user", user);

        return map;
    }

UmsUserService

@Autowired
private BmsPostMapper postMapper;
@Autowired
private BmsFollowMapper followMapper;
public ProfileVO getUserProfile(String userId) {
    ProfileVO profile = new ProfileVO();
    UmsUser user = baseMapper.selectById(userId);
    BeanUtils.copyProperties(user, profile);
    // 用户文章数
    int count = postMapper.selectCount(new LambdaQueryWrapper<BmsPost>().eq(BmsPost::getUserId, userId));
    profile.setTopicCount(count);
    // 粉丝数
    int followers = followMapper.selectCount((new LambdaQueryWrapper<BmsFollow>().eq(BmsFollow::getParentId, userId)));
    profile.setFollowerCount(followers);
    return profile;
}