Laravel+Vue 问答平台项目实战前端 - 查看问题详情

640 阅读1分钟

查看问题详情的入口是问题列表页面每条数据中的标题动态绑定的路由。在构建项目时,配置的动态路由/qustion/:id,匹配到ShowQuestion组件

问题详情页面

编辑文件src/views/ShowQuestion

<template>
  <div class="container">
    <b-card class="body" v-if="question">
        <b-card-title :title="question.title"></b-card-title>
        <b-card-sub-title class="sub-title">
          <span class="sub-title-item">{{question.author}}</span>
          <span class="sub-title-item">喜欢 {{question.like_num}}</span>
        </b-card-sub-title>
        <b-card-text>
          <MarkdownPreview :initialValue=question.content></MarkdownPreview>
        </b-card-text>


        <div>
          <b-modal centered id="showGoodTip" title="注册登陆" hide-footer>
            <div class="d-block text-center">
              <h4>欢迎来到AntFootQA</h4>
              <span style="color: #6c757d; font-size: 20px; line-height: 30px;">此平台作为演示项目存在,请先注册登陆</span>
            </div>
          </b-modal>
          <b-button v-if="token" @click="goodQuestion" variant="outline-info" class="mb-2">
            <b-icon icon="hand-thumbs-up" aria-hidden="true"></b-icon> {{question.has_like === 1 ? '已喜欢' : '喜欢'}}
          </b-button>
          <b-button v-else v-b-modal.showGoodTip variant="outline-info" class="mb-2">
            <b-icon icon="hand-thumbs-up" aria-hidden="true"></b-icon> {{question.has_like === 1 ? '已喜欢' : '喜欢'}}
          </b-button>
        </div>

        <div class="comment">
          <div class="comment-body">
            <div class="comment-header">
              <span class="comment-title">全部回答</span>
              <span class="comment-num">{{question.comment_num}}</span>
            </div>
          </div>

          <div class="comment-list">
            <div class="comment-card-list" v-for="answerItem in question.answers" :key="answerItem.id">
              <div class="comment-person">
                <span>{{answerItem.user_name}}</span>
              </div>
              <div class="comment-date">
                <span class="time">{{answerItem.created_at}}</span>
              </div>
              <div class="comment-content">
                {{answerItem.content}}
              </div>
            </div>
          </div>

          <div v-if="token">
            <b-form @submit="submitAnswer">
              <b-form-textarea
                  v-model="answer"
                  placeholder="请输入你的回答"
                  rows="3"
                  max-rows="6"
                ></b-form-textarea>
              <b-button block class="answer-btn" type="submit" variant="success" :disabled="answer === ''">我来回答</b-button>
            </b-form>
          </div>
          <b-button v-else disabled>登陆后回答问题</b-button>
        </div>
      </b-card>
  </div>
</template>

<script>
import { MarkdownPreview } from 'vue-meditor'
import { getQuestion, likeQuestion, cancelLikeQuestion, answerQuestion } from '../api/question'
import storage from  '../utils/store'

export default {
  name: 'ShowQueston',
  data () {
    return {
      question: null,
      token: '',
      answer: ''
    }
  },
  components: {
    MarkdownPreview
  },
  methods: {
    handleGetQuestion(id) {
      getQuestion({
        id: id
      }).then(res => {
        if (res.code === 0) {
          this.question = res.data.question
        }
      })
    },
    goodQuestion () {
      if (this.token && this.question) {
        let id = this.question.id
        let hasLike = this.question.has_like
        if (hasLike === 0) {
          likeQuestion({
            id: id
          }).then(res => {
            if (res.code === 0) {
             this.handleGetQuestion(id) 
            }
          })
        } else {
          cancelLikeQuestion({
            id: id
          }).then(res => {
            if (res.code === 0) {
             this.handleGetQuestion(id) 
            }
          })
        }
      }
    },
    submitAnswer (event) {
      event.preventDefault()
      let id = this.question.id
      answerQuestion({
        id: id,
        content: this.answer
      }).then(res => {
        if (res.code === 0) {
          this.handleGetQuestion(id) 
          this.answer = ''
        }
      })
    }
  },

  created () {
    let id = this.$route.params.id
    this.handleGetQuestion(id)
    this.token = storage.get()
  }
}
</script>

<style scoped>
.container {
  width: 60%;
}
.body {
  border: none;
}
.sub-title {
  margin-top: 10px;
  margin-bottom: 10px;
  color: #969696;
  font-size: 13px;
}
.sub-title-item {
  margin-right: 10px;
}
.comment {
  margin-top: 20px;
}
.comment-body {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-left: 12px;
  border-left: 4px solid #17a2b8;
  font-size: 18px;
  font-weight: 500;
  height: 20px;
  line-height: 20px;
}
.comment-header {
  display: flex;
  align-items: center;
}
.comment-title {
  font-size: 18px;
  font-weight: 500;
  line-height: 20px;
}
.comment-num {
  margin-left: 6px;
  font-size: 14px;
  font-weight: normal;  
}
.comment-list {
  margin-bottom: 30px;  
}
.comment-card-list {
  border-bottom: 1px solid #f0f0f0;
  margin: 0 0 15px;
  padding: 15px 2px 20px 0;
}
.comment-person {
display: flex;
    align-items: center;
    font-size: 15px;
    font-weight: 500;
}
.comment-date {
margin-top: 2px;
    font-size: 12px;
    color: #969696;
}
.time {
    font-size: 12px;
    color: #969696;
}
.comment-content {
    margin-top: 10px;
    font-size: 16px;
    line-height: 1.5;
    word-break: break-word;  
}
.answer-btn {
  margin-top: 20px;
}
</style>

喜欢与取消喜欢

如果当前用户还没有点击喜欢此问题,那么查看内容时候,会显示一个"喜欢"文案的按钮。否则文案展示为“已喜欢”

<b-button v-if="token" @click="goodQuestion" variant="outline-info" class="mb-2">
  <b-icon icon="hand-thumbs-up" aria-hidden="true"></b-icon> {{question.has_like === 1 ? '已喜欢' : '喜欢'}}
</b-button>
<b-button v-else v-b-modal.showGoodTip variant="outline-info" class="mb-2">
  <b-icon icon="hand-thumbs-up" aria-hidden="true"></b-icon> {{question.has_like === 1 ? '已喜欢' : '喜欢'}}
</b-button>

根据问题问题详情接口返回的数据中的has_like字段判断展示的文案。在绑定的goodQuestion方法事件中的实现

goodQuestion () {
  if (this.token && this.question) {
    let id = this.question.id
    let hasLike = this.question.has_like
    if (hasLike === 0) {
      likeQuestion({
        id: id
      }).then(res => {
        if (res.code === 0) {
          this.handleGetQuestion(id) 
        }
      })
    } else {
      cancelLikeQuestion({
        id: id
      }).then(res => {
        if (res.code === 0) {
          this.handleGetQuestion(id) 
        }
      })
    }
  }
},

也会判断当前用户是否已喜欢,来调用喜欢接口或者取消喜欢接口

回答问题

我来回答按钮,添加:disabled="answer === ''判断,如果当前输入内容为空,则将按钮禁用,否则可以点击

<b-button block class="answer-btn" type="submit" variant="success" :disabled="answer === ''">我来回答</b-button>

按钮绑定的submitAnswer函数。提交回答数据,成功之后,刷新当前页面

submitAnswer (event) {
  event.preventDefault()
  let id = this.question.id
  answerQuestion({
    id: id,
    content: this.answer
  }).then(res => {
    if (res.code === 0) {
      this.handleGetQuestion(id) 
      this.answer = ''
    }
  })
}

作者编辑已发布问题

现在需要在此页面中,新增一个编辑入口,用来问题发布人本身可以重新编辑已发布的问题内容。

添加编辑入口

如果当前登录用户就是当前发布问题的作者一致,那么他应该可以重新编辑此问题的入口,在src/views/ShowQuestions页面中,新增一个计算属性

  computed: {
    editQuestion () {
      let userInfo = this.$store.state.user.userInfo
      if (this.question && userInfo) {
        return this.question.user_id === this.$store.state.user.userInfo.id
      }
      return false
    },
  },

判断当前登录的用户userInfo是否存在,如果存在且用户id等于当前的问题发表人的user_id,则返回editQuestiontrue;否则返回false

修改页面中,新增一个编辑入口
<span class="sub-title-item" v-if="editQuestion">
	<router-link :to="`/question/edit/${question.id}`">编辑</router-link>
</span>

如果editQuestiontrue,则显示一个路由的编辑入口。最终src/views/ShowQuestions的代码如下

<template>
  <div class="container">
    <b-card class="body" v-if="question">
        <b-card-title :title="question.title"></b-card-title>
        <b-card-sub-title class="sub-title">
          <span class="sub-title-item">{{question.author}}</span>
          <span class="sub-title-item">喜欢 {{question.like_num}}</span>
          <span class="sub-title-item" v-if="editQuestion">
            <router-link :to="`/question/edit/${question.id}`">编辑</router-link>
          </span>
        </b-card-sub-title>
        <b-card-text>
          <MarkdownPreview :initialValue=question.content></MarkdownPreview>
        </b-card-text>


        <div>
          <b-modal centered id="showGoodTip" title="注册登陆" hide-footer>
            <div class="d-block text-center">
              <h4>欢迎来到AntFootQA</h4>
              <span style="color: #6c757d; font-size: 20px; line-height: 30px;">此平台作为演示项目存在,请先注册登陆</span>
            </div>
          </b-modal>
          <b-button v-if="token" @click="goodQuestion" variant="outline-info" class="mb-2">
            <b-icon icon="hand-thumbs-up" aria-hidden="true"></b-icon> {{question.has_like === 1 ? '已喜欢' : '喜欢'}}
          </b-button>
          <b-button v-else v-b-modal.showGoodTip variant="outline-info" class="mb-2">
            <b-icon icon="hand-thumbs-up" aria-hidden="true"></b-icon> {{question.has_like === 1 ? '已喜欢' : '喜欢'}}
          </b-button>
        </div>

        <div class="comment">
          <div class="comment-body">
            <div class="comment-header">
              <span class="comment-title">全部回答</span>
              <span class="comment-num">{{question.comment_num}}</span>
            </div>
          </div>

          <div class="comment-list">
            <div class="comment-card-list" v-for="answerItem in question.answers" :key="answerItem.id">
              <div class="comment-person">
                <span>{{answerItem.user_name}}</span>
              </div>
              <div class="comment-date">
                <span class="time">{{answerItem.created_at}}</span>
              </div>
              <div class="comment-content">
                {{answerItem.content}}
              </div>
            </div>
          </div>

          <div v-if="token">
            <b-form @submit="submitAnswer">
              <b-form-textarea
                  v-model="answer"
                  placeholder="请输入你的回答"
                  rows="3"
                  max-rows="6"
                ></b-form-textarea>
              <b-button block class="answer-btn" type="submit" variant="success" :disabled="answer === ''">我来回答</b-button>
            </b-form>
          </div>
          <b-button v-else disabled>登陆后回答问题</b-button>
        </div>
      </b-card>
  </div>
</template>

<script>
import { MarkdownPreview } from 'vue-meditor'
import { getQuestion, likeQuestion, cancelLikeQuestion, answerQuestion } from '../api/question'
import storage from  '../utils/store'

export default {
  name: 'ShowQueston',
  data () {
    return {
      question: null,
      token: '',
      answer: ''
    }
  },
  computed: {
    editQuestion () {
      let userInfo = this.$store.state.user.userInfo
      if (this.question && userInfo) {
        return this.question.user_id === this.$store.state.user.userInfo.id
      }
      return false
    },
  },
  components: {
    MarkdownPreview
  },
  methods: {
    handleGetQuestion(id) {
      getQuestion({
        id: id
      }).then(res => {
        if (res.code === 0) {
          this.question = res.data.question
        }
      })
    },
    goodQuestion () {
      if (this.token && this.question) {
        let id = this.question.id
        let hasLike = this.question.has_like
        if (hasLike === 0) {
          likeQuestion({
            id: id
          }).then(res => {
            if (res.code === 0) {
             this.handleGetQuestion(id) 
            }
          })
        } else {
          cancelLikeQuestion({
            id: id
          }).then(res => {
            if (res.code === 0) {
             this.handleGetQuestion(id) 
            }
          })
        }
      }
    },
    submitAnswer (event) {
      event.preventDefault()
      let id = this.question.id
      answerQuestion({
        id: id,
        content: this.answer
      }).then(res => {
        if (res.code === 0) {
          this.handleGetQuestion(id) 
          this.answer = ''
        }
      })
    }
  },

  created () {
    let id = this.$route.params.id
    this.handleGetQuestion(id)
    this.token = storage.get()
  }
}
</script>

<style scoped>
.container {
  width: 60%;
}
.body {
  border: none;
}
.sub-title {
  margin-top: 10px;
  margin-bottom: 10px;
  color: #969696;
  font-size: 13px;
}
.sub-title-item {
  margin-right: 10px;
}
.comment {
  margin-top: 20px;
}
.comment-body {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-left: 12px;
  border-left: 4px solid #17a2b8;
  font-size: 18px;
  font-weight: 500;
  height: 20px;
  line-height: 20px;
}
.comment-header {
  display: flex;
  align-items: center;
}
.comment-title {
  font-size: 18px;
  font-weight: 500;
  line-height: 20px;
}
.comment-num {
  margin-left: 6px;
  font-size: 14px;
  font-weight: normal;  
}
.comment-list {
  margin-bottom: 30px;  
}
.comment-card-list {
  border-bottom: 1px solid #f0f0f0;
  margin: 0 0 15px;
  padding: 15px 2px 20px 0;
}
.comment-person {
display: flex;
    align-items: center;
    font-size: 15px;
    font-weight: 500;
}
.comment-date {
margin-top: 2px;
    font-size: 12px;
    color: #969696;
}
.time {
    font-size: 12px;
    color: #969696;
}
.comment-content {
    margin-top: 10px;
    font-size: 16px;
    line-height: 1.5;
    word-break: break-word;  
}
.answer-btn {
  margin-top: 20px;
}
</style>
添加编辑问题路由

修改路由文件src/router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeLayout from '../layouts/HomeLayout'
import UserLayout from '../layouts/UserLayout'
import QuestionList from '../views/QuestionList'
import ShowQuestion from '../views/ShowQuestion'
import CreateQuestion from '../views/CreateQuestion'
import Login from '../views/Login'
import Register from '../views/Register'

Vue.use(VueRouter)

let routes = [
  {
    path: '/',
    redirect: '/question'
  },
  {
    path: '/question',
    component: HomeLayout,
    children: [
      {
        path: '',
        component: QuestionList
      },
      {
        path: 'create',
        component: CreateQuestion
      },
      {
        path: 'edit/:id',
        component: CreateQuestion
      },
      {
        path: ':id',
        component: ShowQuestion
      }
    ]
  },
  {
    path: '/user',
    component: UserLayout,
    children: [
      {
        path: 'login',
        component: Login
      },
      {
        path: 'register',
        component: Register
      }
    ]
  }
]

export default new VueRouter({
  mode: 'history',
  routes
})

question路由下嵌套一个path: 'edit/:id',路由,同样映射到了CreateQuestion组件。

修改创建问题页面

此改此页面,判断如果路由中包含了id参数值,则默认渲染此问题的标题和内容。

<template>
  <div class="container">
    <Alert :msg=toastMsg></Alert>
    <b-form @submit="onSubmit">
      <b-form-group>
        <b-form-input
          id="input-1"
          v-model="title"
          type="text"
          placeholder="请输入标题"
          required
        ></b-form-input>
      </b-form-group>
      <b-form-group>
          <Markdown v-model="content"></Markdown>
      </b-form-group>
      <b-button type="submit" variant="success">发布问题</b-button>
    </b-form>
  </div>
</template>

<script>
import Markdown from 'vue-meditor'
import { createQuestion, getQuestion } from '../api/question'
import Alert from '../components/Alert'
  export default {
    name: 'CreateQuestion',
    data() {
      return {
        title: '',
        content: '',
        toastMsg: '',
      }
    },
    components: {
      Alert,
      Markdown
    },
    methods: {
      initQuestion (id) {
      getQuestion({
        id: id
      }).then(res => {
        if (res.code === 0) {
          this.title = res.data.question.title
          this.content = res.data.question.content
        }
      })
      },
      onSubmit(event) {
        event.preventDefault()
        createQuestion({
          title: this.title,
          content: this.content
        }).then(res => {
          if (res.code === 0) {
            this.$router.push('/')
          }
          if (res.code === -10001) {
            this.$router.push('/user/login')
          }
          this.toastMsg = res.msg
        })
      },
    },
    created () {
      let id = this.$route.params.id
      if (id) {
        this.initQuestion(id)
      }
    }
  }
</script>

<style scoped>
.container {
  width: 60%;
}
</style>

效果

上一篇 Laravel+Vue 问答平台项目实战前端 - 提问题