Bladeren bron

添加评论相关组件

reghao 3 jaren geleden
bovenliggende
commit
f3b54c8b63
3 gewijzigde bestanden met toevoegingen van 310 en 117 verwijderingen
  1. 9 9
      src/api/comment/comment.js
  2. 234 58
      src/components/card/comment-card.vue
  3. 67 50
      src/components/comment/index.vue

+ 9 - 9
src/api/comment/comment.js

@@ -1,16 +1,16 @@
 import $axios from '../index'
 
-const searchApi = {
-  keywordSuggestApi: '/api/search/suggest',
-  videoSearchApi: '/api/search/query'
+const commentApi = {
+  videoCommentApi: '/api/comment/video',
+  childCommentApi: '/api/comment/child'
 }
 
-// 关键词建议
-export function keywordSuggest(keyword) {
-  return $axios.get(searchApi.keywordSuggestApi + '?k=' + keyword)
+// 视频评论
+export function videoComment(videoId, page) {
+  return $axios.get(commentApi.videoCommentApi + '?videoId=' + videoId + '&page=' + page)
 }
 
-// 视频搜索
-export function videoQuery(keyword) {
-  return $axios.get(searchApi.videoSearchApi + '?keyword=' + keyword)
+// 评论的子评论
+export function childComment(commentId, page) {
+  return $axios.get(commentApi.childCommentApi + '?commentId=' + commentId + '&page=' + page)
 }

+ 234 - 58
src/components/card/comment-card.vue

@@ -1,96 +1,272 @@
 <template>
-  <div>
+  <div v-infinite-scroll="loadMore" infinite-scroll-disabled="true" infinite-scroll-distance="10">
     <v-row>
       <v-col>
-        {{ commentCount }} 条评论
+        {{ video.commentCount }} 条评论
       </v-col>
-    </v-row>
-    <v-row>
-      <v-col cols="2" style="padding-top: 0px;">
-        <router-link v-if="this.$store.state.user.userInfo" :to="`/u/${this.$store.state.user.userInfo.userId}`">
-          <v-avatar size="48">
-            <v-img
-              :src="this.$store.state.user.userInfo.avatarUrl"
-              :alt="this.$store.state.user.userInfo.username"
-              :title="this.$store.state.user.userInfo.username"
-            />
-          </v-avatar>
-        </router-link>
-        <v-avatar v-if="this.$store.state.user.userInfo == null" size="48">
-          <v-img
-            src="/images/head.png"
-            alt="头像"
-            title="请登录后评论"
-          />
-        </v-avatar>
-      </v-col>
-      <v-col style="padding-top: 0px;">
-        <v-textarea
-          v-model="videoComment"
-          placeholder="发表公开评论"
-          label="评论"
-          rows="2"
-          :no-resize="true"
-        />
+      <v-col>
+        <span @click="getHotComments">最热</span>
       </v-col>
-    </v-row>
-    <v-row>
       <v-col>
-        <v-btn color="primary" style=" float:right " @click="submitComment">评论</v-btn>
+        <span @click="getNewestComments">最新</span>
       </v-col>
     </v-row>
     <v-divider />
     <v-row>
-      <v-col>
-        <v-row
-          v-for="item in replyList"
-          :key="item.replyId"
-        >
-          <reply-card :reply="item" />
-        </v-row>
-      </v-col>
+      <div ref="comment" :style="wrapStyle" class="comment-wrap">
+        <Comment
+          v-model="videoComments"
+          :user="currentUser"
+          :before-submit="submit"
+          :before-like="like"
+          :before-delete="deleteComment"
+          :upload-img="uploadImg"
+          :props="props"
+        />
+      </div>
     </v-row>
   </div>
 </template>
 
 <script>
-import ReplyCard from '@/components/card/reply-card.vue'
+import Comment from '@/components/comment'
+import { videoComment } from '@/api/comment/comment'
 
 export default {
   name: 'CommentCard',
   components: {
-    ReplyCard
+    Comment
   },
   props: {
-    count: {
-      type: Number,
-      default: 0
+    video: {
+      type: Object,
+      default: () => {}
     }
   },
   data() {
     return {
       commentCount: this.count,
-      videoComment: '',
-      replyList: [
-        { replyId: '123',
-          content: '测试一下'
-        }
-      ]
+      videoComments: [],
+      wrapStyle: '',
+      props: {
+        id: 'commentId',
+        content: 'content',
+        imgSrc: 'imgSrc',
+        children: 'childrenComments',
+        likes: 'likes',
+        liked: 'liked',
+        reply: 'reply',
+        createAt: 'createAt',
+        user: 'visitor'
+      },
+      currentUser: null,
+      busy: false,
+      page: 1
     }
   },
   created() {
-    this.getComments()
+    const userInfo = this.$store.state.user.userInfo
+    if (userInfo !== null) {
+      this.currentUser = {
+        name: userInfo.username,
+        avatar: userInfo.avatarUrl
+      }
+    }
+    this.getVideoComment(this.video.videoId, 1)
+  },
+  mounted() {
+    const header = this.$refs.header
+    this.wrapStyle = `height: calc(100vh - ${header.clientHeight + 20}px)`
   },
   methods: {
-    getComments() {
-      console.log('获取评论')
+    loadMore: function() {
+      this.busy = true
+      setTimeout(() => {
+        this.getVideoComment(this.video.videoId, this.page)
+      }, 1000)
+    },
+    getHotComments() {
+      console.log('获取热门评论')
+    },
+    getNewestComments() {
+      console.log('获取最新评论')
     },
-    submitComment() {
-      console.log('发布评论: ' + this.videoComment)
+    getVideoComment(videoId, page) {
+      videoComment(videoId, page).then(res => {
+        if (res.code === 0) {
+          this.page += 1
+          this.busy = false
+
+          for (const item of res.data.list) {
+            this.videoComments.push(item)
+          }
+          console.log('已获取的评论数量: ' + this.videoComments.length)
+        } else {
+          console.error(res.msg)
+        }
+      })
+        .catch(error => {
+          console.error(error.message)
+        })
+    },
+    // 评论相关方法
+    async submit(newComment, parent, add) {
+      const res = await new Promise((resolve) => {
+        setTimeout(() => {
+          resolve({ newComment, parent })
+        }, 300)
+      })
+      add(Object.assign(res.newComment, { _id: new Date().getTime() }))
+      console.log('addComment: ', res)
+    },
+    async like(comment) {
+      const res = await new Promise((resolve) => {
+        setTimeout(() => {
+          resolve(comment)
+        }, 0)
+      })
+
+      console.log('likeComment: ', res)
+    },
+    async uploadImg({ file, callback }) {
+      const res = await new Promise((resolve, reject) => {
+        const reader = new FileReader()
+
+        reader.readAsDataURL(file)
+
+        reader.onload = () => {
+          resolve(reader.result)
+        }
+
+        reader.onerror = () => {
+          reject(reader.error)
+        }
+      })
+
+      callback(res)
+      console.log('uploadImg: ', res)
+    },
+    async deleteComment(comment, parent) {
+      const res = await new Promise((resolve) => {
+        setTimeout(() => {
+          resolve({ comment, parent })
+        }, 300)
+      })
+      console.log('deleteComment: ', res)
     }
   }
 }
 </script>
 
-<style>
+<style lang="scss">
+  .category-link {
+    color: #999;
+  }
+  a {
+    text-decoration: none;
+  }
+  @mixin scroll-style(
+    $thumb: rgba(255, 255, 255, 0.6),
+    $track: rgba(255, 255, 255, 0)
+  ) {
+    &::-webkit-scrollbar,
+    &::-webkit-scrollbar-thumb,
+    &::-webkit-scrollbar-track {
+      border: none;
+      box-shadow: none;
+    }
+    &::-webkit-scrollbar {
+      width: 4px;
+    }
+    &::-webkit-scrollbar-thumb {
+      border-radius: 2px;
+      background: $thumb;
+    }
+    &::-webkit-scrollbar-track {
+      background: $track;
+    }
+  }
+  * {
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+  }
+
+  html {
+    font-size: 14px;
+  }
+
+  html,
+  body,
+  #app {
+    height: 100%;
+  }
+
+  @media screen and (min-width: 320px) {
+    html {
+      font-size: calc(14px + 4 * ((100vw - 320px) / (1200 - 320)));
+    }
+  }
+
+  @media screen and (min-width: 1200px) {
+    html {
+      font-size: 18px;
+    }
+  }
+
+  .change-role {
+    background: #1c2433;
+    color: #eee;
+    padding: 1rem;
+    display: flex;
+    justify-content: center;
+    align-content: center;
+    .change {
+      cursor: pointer;
+      padding: 0.4rem;
+      margin-right: 2rem;
+      font-size: 0.9rem;
+      border: 1px solid #e99210;
+      border-radius: 5px;
+      user-select: none;
+      &:hover {
+        opacity: 0.9;
+      }
+    }
+    .current-role {
+      min-width: 15rem;
+      color: #e99210;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      border: 1px dashed #e99210;
+      padding: 0 1rem;
+      img {
+        width: 1.5rem;
+        height: 1.5rem;
+        margin-right: 0.5rem;
+        border: 1px solid #eee;
+        border-radius: 50%;
+      }
+    }
+  }
+
+  .comment-wrap {
+    overflow: auto;
+    @include scroll-style(#db8f1c, #b9b9b9);
+  }
+
+  @media screen and (min-width: 760px) {
+    body {
+      margin: 0 10%;
+      border: 1px dashed #eee;
+    }
+  }
+
+  @media screen and (max-width: 500px) {
+    .change-role .current-role {
+      min-width: 5rem;
+      padding: 0 0.5rem;
+    }
+  }
 </style>

+ 67 - 50
src/components/comment/index.vue

@@ -6,7 +6,7 @@
         class="avatar"
         :src="user.avatar || ''"
         @error="(e) => e.target.classList.add('error')"
-      />
+      >
     </comment-form>
 
     <!-- 底部评论列表 -->
@@ -38,33 +38,41 @@
 
         <!-- 单条评论下的回复列表 -->
         <template #subList="{ parentId }">
-          <comment-list sub>
-            <!-- 单条回复 -->
-            <comment-item
-              v-for="(child, j) in comment.children"
-              :id="`${parentId}-${j}`"
-              :key="`${parentId}-${j}`"
-              :ref="`${parentId}-${j}`"
-              :comment="child"
-              :user="user"
-              :parent="comment"
-              @comment-reply="hasForm"
-              @comment-like="handleCommentLike"
-              @comment-delete="handleCommentDelete"
-            >
-              <!-- 单条回复的回复表单 -->
-              <comment-form
-                v-if="forms.includes(`${parentId}-${j}`)"
+          <div>
+            <comment-list sub>
+              <!-- 单条回复 -->
+              <comment-item
+                v-for="(child, j) in comment.children"
                 :id="`${parentId}-${j}`"
+                :key="`${parentId}-${j}`"
+                :ref="`${parentId}-${j}`"
                 :comment="child"
+                :user="user"
                 :parent="comment"
-                :placeholder="`回复${child.user && child.user.name}...`"
-                :upload-img="uploadImg"
-                @form-delete="deleteForm"
-                @form-submit="formSubmit"
-              />
-            </comment-item>
-          </comment-list>
+                @comment-reply="hasForm"
+                @comment-like="handleCommentLike"
+                @comment-delete="handleCommentDelete"
+              >
+                <!-- 单条回复的回复表单 -->
+                <comment-form
+                  v-if="forms.includes(`${parentId}-${j}`)"
+                  :id="`${parentId}-${j}`"
+                  :comment="child"
+                  :parent="comment"
+                  :placeholder="`回复${child.user && child.user.name}...`"
+                  :upload-img="uploadImg"
+                  @form-delete="deleteForm"
+                  @form-submit="formSubmit"
+                />
+              </comment-item>
+            </comment-list>
+            <v-pagination
+              v-model="page"
+              :length="length"
+              :total-visible="7"
+              @input="pageChange"
+            />
+          </div>
         </template>
       </comment-item>
     </comment-list>
@@ -81,51 +89,54 @@ export default {
   inheritAttrs: false,
   model: {
     prop: 'data',
-    event: 'input',
+    event: 'input'
   },
   props: {
     /* 数据 */
     data: {
       type: Array,
       default: () => [],
-      required: true,
+      required: true
     },
     /* 当前用户 */
     user: {
       type: Object,
       default: () => {},
-      required: true,
+      required: true
     },
     /* 配置属性 */
     props: {
       type: Object,
-      default: () => {},
+      default: () => {}
     },
     /* 提交表单前事件 */
     beforeSubmit: {
       type: Function,
-      required: true,
+      required: true
     },
     /* 执行点赞前事件 */
     beforeLike: {
       type: Function,
-      required: true,
+      required: true
     },
     /* 执行删除前事件 */
     beforeDelete: {
       type: Function,
-      required: true,
+      required: true
     },
     /* 上传图片 */
     uploadImg: {
       type: Function,
-      required: true,
-    },
+      required: true
+    }
   },
   data() {
     return {
       forms: [], // 显示在视图上的所有表单 id
       cacheData: [],
+      page: 1,
+      currentPage: 1,
+      length: 0
     }
   },
   computed: {
@@ -133,7 +144,7 @@ export default {
       if (!props) return null
       const entries = Object.entries(props)
       return entries.length > 0 ? entries : null
-    },
+    }
   },
   created() {
     // 监听并执行一次
@@ -162,7 +173,7 @@ export default {
         reply: null,
         createAt: null,
         user: {},
-        liked: false,
+        liked: false
       }
 
       // 赋值
@@ -185,32 +196,32 @@ export default {
     validate({ key, value }) {
       const map = {
         user: {
-          validate: function (v) {
+          validate: function(v) {
             return (
               (typeof v !== 'object' || JSON.stringify(v) === '{}') &&
               this.message
             )
           },
-          message: 'User must be an object with props.',
+          message: 'User must be an object with props.'
         },
         reply: {
-          validate: function (v) {
+          validate: function(v) {
             return typeof v !== 'object' && this.message
           },
-          message: 'Reply must be an object',
+          message: 'Reply must be an object'
         },
         children: {
-          validate: function (v) {
+          validate: function(v) {
             return !Array.isArray(v) && this.message
           },
-          message: 'Children must be an array',
+          message: 'Children must be an array'
         },
         createAt: {
-          validate: function () {
+          validate: function() {
             return new Date(value).toString() === 'Invalid Date' && this.message
           },
-          message: 'CreateAt is not a valid date.',
-        },
+          message: 'CreateAt is not a valid date.'
+        }
       }
 
       const target = map[key]
@@ -276,7 +287,7 @@ export default {
      */
     async formSubmit({
       newComment: { id, callback, ...params },
-      parent = null,
+      parent = null
     }) {
       const _params = Object.assign(params, { user: this.user })
 
@@ -297,7 +308,7 @@ export default {
       }
     },
 
-    async handleCommentLike({ id, comment: { children, ...params } }) {
+    async handleCommentLike({ id, comment: { children, ...params }}) {
       const _params = Object.assign(params, { user: this.user })
       if (typeof this.beforeLike === 'function') {
         try {
@@ -369,8 +380,8 @@ export default {
         commentIndex === 'root'
           ? this.cacheData.length - 1
           : `${commentIndex}-${
-              this.cacheData[commentIndex].children.length - 1
-            }`
+            this.cacheData[commentIndex].children.length - 1
+          }`
       this.scrollIntoView(`comment-${signal}`)
 
       // 更新外部数据
@@ -430,7 +441,13 @@ export default {
         this.$refs[ref][0].$el.scrollIntoView(false)
       })
     },
-  },
+    pageChange(page) {
+      if (page !== this.currentPage) {
+        this.currentPage = page
+        console.log('获取下一页子评论')
+      }
+    }
+  }
 }
 </script>