Parcourir la source

feat: 私人评论功能开放

ChenYangfan il y a 6 ans
Parent
commit
7e9e411561

+ 1 - 1
src/components/Basic/GlobalMessage.vue

@@ -65,7 +65,7 @@ export default {
   position: fixed;
   top: 20px;
   margin: 0 auto;
-  z-index: 5;
+  z-index: 100;
   animation: show 5s ease-out;
 }
 

+ 54 - 27
src/components/Discussion/CComment.vue

@@ -1,27 +1,41 @@
 <template>
-  <div class="comment c-comment">
+  <div class="c-comment">
     <div>
-      <div class="comment-title" :class="isNew ? 'new-comment' : ''">
-        <c-icon :left="-2" :right="4" :size="18" v-if="isNew" theme="green" title="这条评论属于实时更新的内容" bold>autorenew
-        </c-icon>
-        <c-icon :left="-2" :right="4" :size="20" v-if="comment.topNumber" theme="blue" title="这条评论属于置顶评论">publish
-        </c-icon>
-        {{comment.title}}
+      <div class="vertical-center">
+        <div class="comment-wrapper">
+          <div class="comment-title" :class="isNew ? 'new-comment' : ''">
+            <c-icon :left="-2" :right="6" :size="16" v-if="isNew" theme="green" title="实时更新的讨论" bold>autorenew
+            </c-icon>
+            <c-icon :left="-2" :right="6" :size="16" v-else-if="personal" theme="orange" title="笔记">lock
+            </c-icon>
+            <c-icon :left="-2" :right="6" :size="16" v-else-if="comment.topNumber" theme="red" title="置顶讨论">publish
+            </c-icon>
+            <c-icon :left="-2" :right="6" :size="16" v-else theme="blue" title="公共讨论">watch_later
+            </c-icon>
+            {{comment.title}}
+          </div>
+          <div class="comment-content">{{comment.content}}</div>
+          <div class="comment-author">{{ createTime }} | 作者:{{comment.userName}}
+            <c-tag :theme="comment.userRole === userType.teacher ? 'red' : 'blue'">{{userTypeNaming[comment.userRole]}}
+            </c-tag>
+          </div>
+        </div>
+        <icon-button :left="12" :size="40" @click="showActions = !showActions" class="more-button">
+          {{showActions ? 'expand_less' : 'expand_more'}}
+        </icon-button>
       </div>
-      <div class="comment-content">{{comment.content}}</div>
-      <div class="comment-author">{{ createTime }} | 作者:{{comment.userName}}
-        <c-tag :theme="comment.userRole === userType.teacher ? 'red' : 'blue'">{{userTypeNaming[comment.userRole]}}</c-tag>
-      </div>
-      <comment-tool-bar
-        :top-number="comment.topNumber || ''"
-        :user-id="comment.userId" :has-reply="comment.reply || ''"
-        @reply="handleReply"
-        @delete-reply="handleDeleteReply"
-        @delete-comment="handleDeleteComment"
-        @to-top="handleToTop"
-        @cancel-to-top="handleCancelToTop"
-      >
-      </comment-tool-bar>
+      <show-transition-small>
+        <comment-tool-bar
+          v-if="showActions"
+          :top-number="comment.topNumber || ''"
+          :user-id="comment.userId" :has-reply="comment.reply || ''"
+          @reply="handleReply"
+          @delete-reply="handleDeleteReply"
+          @delete-comment="handleDeleteComment"
+          @to-top="handleToTop"
+          @cancel-to-top="handleCancelToTop"
+        />
+      </show-transition-small>
       <div v-if="comment.reply" class="comment-reply">
         <div>教师回复:{{comment.reply.content}}</div>
         <div class="comment-reply-time">{{replyTime}}</div>
@@ -39,13 +53,20 @@ import CIcon from '@/components/Basic/CIcon'
 import momentFromNow from '@/utils/moment-from-now'
 import { userTypeNaming, userType } from '@/enums/user-type'
 import CTag from '@/components/Basic/CTag'
+import IconButton from '@/components/Basic/IconButton'
+import ShowTransitionSmall from '@/animations/ShowTransitionSmall'
 
 export default {
-  components: { CTag, CIcon, CommentToolBar },
-  props: ['comment', 'isNew'],
+  components: { ShowTransitionSmall, IconButton, CTag, CIcon, CommentToolBar },
+  props: {
+    comment: Object,
+    isNew: Boolean,
+    personal: Boolean
+  },
   data () {
     return {
       replyContent: '',
+      showActions: false,
 
       userType,
       userTypeNaming
@@ -116,16 +137,22 @@ export default {
 <style lang="scss" scoped>
 @import '~@/style/theme.scss';
 
-.comment {
+.c-comment {
   position: relative;
   width: 100%;
   margin: 0 -12px;
-  padding: 0 14px 8px 14px;
+  padding: 4px 14px 12px 14px;
+  word-break: break-word;
 }
+
+.comment-wrapper {
+  flex: 1;
+}
+
 .comment-title {
   padding: 6px 6px 0 6px;
-  margin: 0 -6px 6px -6px;
-  line-height: 1.8;
+  margin: 0 -6px 2px -6px;
+  line-height: 1.7;
   display: flex;
   align-items: center;
 }

+ 75 - 19
src/components/Discussion/DiscussionArea.vue

@@ -1,16 +1,31 @@
 <template>
   <div class="discussion-area">
     <h1 class="sub-title vertical-center">
-      本页留言板
+      讨论区
       <c-button theme="green" :left="12" :icon="focus ? 'unfold_less' : 'edit'" text @click="handleFocusButtonClick">
         {{focus ? '隐藏编辑器' : '发帖'}}
       </c-button>
+      <c-button v-if="focus" :no-bold="personal" text :theme="personal ? 'blue' : ''"
+                :icon="personal ? 'check_box' : 'check_box_outline_blank'"
+                @click="handleSetPersonal">私人笔记
+      </c-button>
     </h1>
     <div class="discussion-area-content">
       <show-transition>
-        <write-new-comment v-show="focus" class="mb-8" ref="writeComment" @comment-submit="handleCommentSubmit"
+        <write-new-comment :show="personal" v-show="focus" class="mb-8" ref="writeComment"
+                           @comment-submit="handleCommentSubmit"
                            :placeholder="writingAreaPlaceholder"></write-new-comment>
       </show-transition>
+      <div v-show="notes.length">
+        <div class="line"><span>私人笔记</span></div>
+        <template v-for="comment of notes">
+          <c-comment :key="'note' + comment.id" :comment="comment" personal
+                     @need-refresh="updateComments"
+          >
+          </c-comment>
+        </template>
+      </div>
+      <div class="line"><span>公共讨论区</span></div>
       <div class="no-content-warning" v-if="comments === null">
         <div class="no-content-warning-tip">加载中...</div>
       </div>
@@ -18,7 +33,7 @@
         本页还没有讨论
       </div>
       <template v-for="comment of wsComments">
-        <c-comment v-if="comment.pageNumber === currentPage" :key="comment.id" :comment="comment" is-new="true"
+        <c-comment v-if="comment.pageNumber === currentPage" :key="comment.id" :comment="comment" is-new
                    @need-refresh="updateComments"
         >
         </c-comment>
@@ -37,35 +52,33 @@
 import CComment from '@/components/Discussion/CComment'
 import WriteNewComment from '@/components/Discussion/WriteNewComment'
 import { mapState } from 'vuex'
-import { createComment, getComments } from '@/server/comment'
+import { createComment, getComments, getSelfCommentsBySlide } from '@/server/comment'
 import WebsocketPublisher from '@/server/websocket/ws'
 import { useCommentWsServer } from '@/config/server-info'
 import CButton from '@/components/Basic/CButton'
 import ShowTransition from '@/animations/ShowTransition'
-import { FOCUS_ON_INPUT, USE_FOCUS } from '@/store/types/pdf-types'
+import { FOCUS_ON_INPUT, SET_PERSONAL, USE_FOCUS } from '@/store/types/pdf-types'
 
 export default {
   components: { ShowTransition, CButton, WriteNewComment, CComment },
   data () {
     return {
       comments: null,
+      notes: [],
       wsComments: [],
       slideId: this.$route.params.slideId
     }
   },
   mounted () {
     this.initWs()
-    if (!this.userInfo) {
-      this.$setInfoMessage('需要登录本系统才可以浏览课程内容')
-      this.$router.push('login')
-    }
     this.updateComments()
   },
   computed: {
     ...mapState({
       userInfo: state => state.main.userInfo,
       currentPage: state => state.pdf.currentPage,
-      focus: state => state.pdf.focus
+      focus: state => state.pdf.focus,
+      personal: state => state.pdf.personal
     }),
     writingAreaPlaceholder () {
       return '主题'
@@ -95,6 +108,7 @@ export default {
     },
     updateComments () {
       this.getComments(1000)
+      this.getNote(1000)
     },
     getComments (size) {
       return getComments({
@@ -109,12 +123,24 @@ export default {
         this.$setErrorMessage('获取本页评论区的过程中:' + err.response.data.msg)
       })
     },
+    getNote (size) {
+      this.notes = []
+      return getSelfCommentsBySlide({
+        slideId: this.slideId,
+        pageNumber: this.currentPage,
+        show: false,
+        size: 1000
+      })
+        .then(res => (this.notes = res.data))
+        .catch(err => this.$setErrorMessage(err.response.data.msg))
+    },
     handleCommentSubmit (comment) {
       createComment({
         slideId: this.slideId,
         pageNumber: this.currentPage,
         title: comment.title,
-        content: comment.content
+        content: comment.content,
+        show: !this.personal
       }).then(res => {
         this.$refs.writeComment.resetContent()
         this.$setSuccessMessage('您的讨论帖发表成功')
@@ -124,6 +150,9 @@ export default {
     },
     handleFocusButtonClick () {
       this.focus ? this.$store.commit(USE_FOCUS) : this.$store.commit(FOCUS_ON_INPUT)
+    },
+    handleSetPersonal () {
+      this.$store.commit(SET_PERSONAL, { personal: !this.personal })
     }
   }
 }
@@ -142,21 +171,21 @@ export default {
   overflow-x: hidden;
   bottom: 0;
   right: 0;
-  background-color: rgba(250, 250, 250, 0.8);
-  /*background-color: rgba(255, 255, 255, 0.8);*/
-  backdrop-filter: saturate(150%) blur(20px);
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+  background-color: white;
+  border: 1px solid #e8e8e8;
 }
 
 .no-content-warning {
   height: 50px;
 }
 
-.c-comment:nth-child(2n) {
-  background-color: rgba(130, 130, 130, 0.05);
+.c-comment:nth-child(2n-1) {
+  background-color: #f9f9f9;
 }
 
-.c-comment:nth-child(2n-1) {
-  background-color: rgba(130, 130, 130, 0.1);
+.c-comment:nth-child(2n) {
+  background-color: white;
 }
 
 @media (max-width: 500px) {
@@ -165,7 +194,34 @@ export default {
     max-width: unset;
     left: 0;
     right: 0;
-    background-color: rgba(255, 255, 255, 0.8);
+    background-color: rgba(255, 255, 255, 1);
+  }
+}
+
+.line {
+  position: relative;
+  font-size: 12px;
+  font-weight: normal;
+  text-align: center;
+  line-height: 0;
+  margin: 4px 0;
+
+  & > span {
+    color: #5a6a7a;
+    display: inline-block;
+    background-color: white;
+    padding: 0 12px;
+    line-height: 12px;
+  }
+
+  &:before {
+    z-index: -1;
+    content: '';
+    width: 100%;
+    position: absolute;
+    left: 0;
+    top: 6px;
+    border-bottom: 1px solid #aabaca;
   }
 }
 </style>

+ 3 - 1
src/components/Discussion/WriteNewComment.vue

@@ -15,7 +15,9 @@ import IconButton from '@/components/Basic/IconButton'
 import { mapState } from 'vuex'
 
 export default {
-  props: ['placeholder'],
+  props: {
+    placeholder: String
+  },
   components: { IconButton },
   computed: {
     ...mapState({

+ 6 - 4
src/components/PDF/Pdf.vue

@@ -114,10 +114,12 @@ export default {
 }
 
 .focus-mask {
-  background: rgba(202, 218, 234, 0.3);
-  box-shadow: 0 0 0 1000px rgba(202, 218, 234, 0.2);
-  color: rgba(90, 106, 122, 0.5);
-  text-shadow: 2px 2px 4px rgba(90, 106, 122, 0.2);
+  font-size: 24px;
+  background: rgba(255, 255, 255, 0.5);
+  box-shadow: 0 0 0 1000px rgba(255, 255, 255, 0.5);
+  color: #3a4a5a;
+  top: 30vh;
+  text-shadow: 2px 2px 6px rgba(255, 255, 255, 1);
 }
 
 .pdf-canvas {

+ 2 - 2
src/components/PDF/PointerCanvas.vue

@@ -113,8 +113,8 @@ export default {
       // 框选
       this._ctx.lineWidth = 4
       this._ctx.lineJoin = 'round'
-      this._ctx.strokeStyle = `rgba(211, 47, 47, .8)`
-      this._ctx.fillStyle = `rgba(211, 47, 47, .05)`
+      this._ctx.strokeStyle = `rgba(85, 136, 229, 1)`
+      this._ctx.fillStyle = `rgba(85, 136, 229, .05)`
       this.rects.forEach(rect => {
         if (rect) {
           this._ctx.beginPath()

+ 40 - 2
src/server/comment.js

@@ -3,12 +3,13 @@ import axios from '@/plugins/axios'
 import generateSearchParams from '@/server/utils/generate-search-params'
 
 // 发表评论
-export function createComment ({ slideId, pageNumber, title, content }) {
+export function createComment ({ slideId, pageNumber, title, content, show = true }) {
   const data = {
     slideId,
     pageNumber,
     title,
-    content
+    content,
+    show
   }
   return axios
     .post(useCommentServer(), data)
@@ -47,8 +48,45 @@ export function getComments ({ slideId, pageNumber, key = '', page = 1, size = 1
     .then(res => res.data)
 }
 
+// 取得某一评论
 export function getOneComment ({ commentId }) {
   return axios
     .get(useCommentServer(`/${commentId}`))
     .then(res => res.data)
 }
+
+// 取得自己某一页的评论
+export function getSelfCommentsBySlide ({
+  slideId,
+  pageNumber = 1,
+  show = true,
+  key = '',
+  page = 1,
+  size = 10,
+  sort = ''
+}) {
+  const params = generateSearchParams({ key, page, size, sort })
+  params.append('pageNumber', pageNumber)
+  params.append('show', show)
+
+  return axios
+    .get(useCommentServer(`/slide/${slideId}/self`), {
+      params
+    })
+    .then(res => res.data)
+}
+
+// 修改评论的展示状态
+export function modifyCommentShow ({ commentId, show }) {
+  return axios
+    .put(useCommentServer(`/${commentId}/show`), { show })
+}
+
+// 获取自己所有的评论
+export function getSelfComments ({ key, page, size, sort }) {
+  const params = generateSearchParams({ key, page, size, sort })
+
+  return axios
+    .get(useCommentServer('self'), { params })
+    .then(res => res)
+}

+ 10 - 0
src/server/ml-recommend.js

@@ -0,0 +1,10 @@
+import axios from '@/plugins/axios'
+
+export function recommendKnowledge ({ text }) {
+  const data = {
+    text
+  }
+
+  return axios
+    .post('http://ml.seec.seecoder.cn/api/graph', data)
+}

+ 7 - 2
src/store/modules/pdf.js

@@ -3,7 +3,7 @@ import {
   NEXT_PAGE,
   PREVIOUS_PAGE,
   RESET,
-  SET_CURRENT_PAGE, SET_SLIDE_INFO,
+  SET_CURRENT_PAGE, SET_PERSONAL, SET_SLIDE_INFO,
   SET_SRC, SET_TOTAL_PAGES, TOGGLE_SHOW_DISCUSSION, UNSET_SLIDE_INFO, USE_FOCUS
 } from '@/store/types/pdf-types'
 
@@ -14,7 +14,8 @@ export default {
     totalPages: 0,
     currentPage: 1,
     showDiscussion: true,
-    focus: false
+    focus: false,
+    personal: localStorage.getItem('personal') || false
   },
 
   mutations: {
@@ -48,6 +49,10 @@ export default {
     [USE_FOCUS] (state) {
       state.focus = false
     },
+    [SET_PERSONAL] (state, payload) {
+      state.personal = payload.personal
+      localStorage.setItem('personal', state.personal)
+    },
     [RESET] (state) {
       state.src = ''
       state.totalPages = 0

+ 1 - 0
src/store/types/pdf-types.js

@@ -9,3 +9,4 @@ export const SET_SLIDE_INFO = 'setSlideInfo'
 export const UNSET_SLIDE_INFO = 'unsetSlideInfo'
 export const FOCUS_ON_INPUT = 'focusOnInput'
 export const USE_FOCUS = 'useFocus'
+export const SET_PERSONAL = 'setPersonal'

+ 16 - 2
src/views/quiz/components/QuestionEditorFactory.vue

@@ -12,8 +12,10 @@
     <div v-else class="mt-24">知识点加载中...</div>
     <slide-in-same-transition>
       <keep-alive>
-        <choice-question-editor v-if="kind === QuestionKind.choice" :initValue="initValue" @submit="handleSubmit"/>
-        <true-false-question-editor v-else-if="kind === QuestionKind.trueOrFalse" :initValue="initValue"
+        <choice-question-editor @input="handleInput" v-if="kind === QuestionKind.choice" :initValue="initValue"
+                                @submit="handleSubmit"/>
+        <true-false-question-editor @input="handleInput" v-else-if="kind === QuestionKind.trueOrFalse"
+                                    :initValue="initValue"
                                     @submit="handleSubmit"/>
       </keep-alive>
     </slide-in-same-transition>
@@ -29,6 +31,8 @@ import { getKnowledgeNodes } from '@/server/knowledge'
 import Treeselect from '@riophae/vue-treeselect'
 import '@riophae/vue-treeselect/dist/vue-treeselect.css'
 import { constructTree } from '@/utils/construct-tree'
+import debounce from '@/utils/debounce'
+import { recommendKnowledge } from '@/server/ml-recommend'
 
 export default {
   name: 'QuestionEditorFactory',
@@ -50,6 +54,12 @@ export default {
     return {
       knowledgeTree: null,
       selectedKnowledgeIdList: this.initValue.knowledgeId,
+      stem: null,
+
+      debounceRecommend: debounce(() => {
+        recommendKnowledge({ text: JSON.stringify(this.stem) })
+          .then(res => console.log(res))
+      }, 500),
       QuestionKind
     }
   },
@@ -68,6 +78,10 @@ export default {
     },
     handleSubmit (event) {
       this.$emit('submit', { ...this.initValue, ...event, knowledgeId: this.selectedKnowledgeIdList })
+    },
+    handleInput (event) {
+      this.stem = event
+      this.debounceRecommend()
     }
   }
 }

+ 8 - 4
src/views/reader/PdfReader.vue

@@ -4,8 +4,8 @@
     <pdf-viewer></pdf-viewer>
     <pointer-canvas :no-pointer="!pointer" :no-box="!box"></pointer-canvas>
     <div class="pointer-actions">
-      <c-button icon="gesture" :theme="pointer ? 'red' : ''" @click="pointer = !pointer">指针</c-button>
-      <c-button icon="crop_square" :theme="box ? 'green' : ''" @click="box = !box" :left="12">框选</c-button>
+      <c-button text icon="gesture" :theme="pointer ? 'red' : ''" @click="pointer = !pointer">指针</c-button>
+      <c-button text icon="crop_square" :left="8" :theme="box ? 'blue' : ''" @click="box = !box">框选</c-button>
     </div>
     <discussion-area
       :class="showDiscussion ? 'show-discussion' : 'hide-discussion'"
@@ -131,7 +131,11 @@ export default {
 
 .pointer-actions {
   position: fixed;
-  left: 12px;
-  bottom: 12px;
+  left: 6px;
+  bottom: 8px;
+
+  .c-button {
+    background-color: white;
+  }
 }
 </style>