Pārlūkot izejas kodu

feat: 消息接收发送逻辑完成

chenhao 2 gadi atpakaļ
vecāks
revīzija
87533171da

+ 6 - 0
src/assets/styles/app.scss

@@ -100,6 +100,12 @@ a {
   }
 }
 
+.link-button {
+  background-color: transparent;
+  color: $color--primary;
+  font-size: inherit;
+}
+
 #app {
   height: 100%;
 }

+ 4 - 0
src/components/common/ContentEditAble.vue

@@ -2,9 +2,11 @@
   <component
     :is="tagName"
     ref="contenteditableEle"
+    class="contenteditable-ele"
     :contenteditable="contenteditable"
     @input="inputChange"
     @blur="inputChange"
+    @click="$emit('click', $event)"
   >
   </component>
 </template>
@@ -13,6 +15,8 @@
 import { ref, onMounted, withDefaults, watch } from 'vue'
 import useVModel from '@/hooks/useVModel'
 
+defineEmits(['click'])
+
 const props = withDefaults(
   defineProps<{
     modelValue?: string

+ 6 - 2
src/components/shared/MarkHeader.vue

@@ -17,7 +17,7 @@
 
     <div class="flex flex-1 items-center mark-header">
       <slot></slot>
-      <span class="data-item"><message></message></span>
+      <span class="data-item"><message :paper-path="props.paperPath"></message></span>
       <span class="data-item"><user-info></user-info></span>
     </div>
   </div>
@@ -88,7 +88,11 @@ const buttons: HeaderButton[] = [
   { title: '设置专家卷', type: 'bookmark' },
 ]
 
-const props = defineProps<{ excludeOperations?: ButtonType[]; includeOperations?: ButtonType[] }>()
+const props = defineProps<{
+  excludeOperations?: ButtonType[]
+  includeOperations?: ButtonType[]
+  paperPath?: string | null
+}>()
 
 const refs = reactive<Partial<Record<ButtonType, Element>>>({
   'front-color': void 0,

+ 2 - 2
src/components/shared/ScoringPanel.vue

@@ -48,9 +48,9 @@ const props = withDefaults(
     visible?: boolean
     /** 分值 */
     score: (number | string)[]
-    mainNumber: number | undefined
+    mainNumber?: number | null
   }>(),
-  { modal: false, toggleModal: true, score: void 0 }
+  { modal: false, toggleModal: true, score: () => [], mainNumber: null }
 )
 
 const emits = defineEmits(['submit', 'update:score', 'update:visible'])

+ 18 - 3
src/components/shared/message/Message.vue

@@ -41,22 +41,37 @@
         ></confirm-button>
       </div>
     </el-popover>
-    <message-window v-model="visibleMessageWindow" v-model:type="messageWindowType"></message-window>
+    <message-window
+      v-model="visibleMessageWindow"
+      v-model:type="messageWindowType"
+      :reply-user-id="props.replyUserId"
+      :paper-path="props.paperPath"
+    ></message-window>
   </message-component>
 </template>
 
 <script setup lang="ts" name="Message">
 /** 头部消息组件 */
-import { defineComponent, ref, useSlots } from 'vue'
+import { defineComponent, withDefaults, ref, useSlots } from 'vue'
 import { ElPopover } from 'element-plus'
 import dayjs from 'dayjs'
 import useVW from '@/hooks/useVW'
+import useVModel from '@/hooks/useVModel'
 import useMessageLoop from '@/hooks/useMessageLoop'
 import useMainStore from '@/store/main'
 import MessageWindow from '@/components/shared/message/MessageWindow.vue'
 import ConfirmButton from '@/components/common/ConfirmButton.vue'
 import SvgIcon from '@/components/common/SvgIcon.vue'
 
+const props = withDefaults(
+  defineProps<{
+    type?: 'view' | 'send'
+    replyUserId?: number | null
+    paperPath?: string | null
+  }>(),
+  { type: 'view', replyUserId: null, paperPath: null }
+)
+
 const MessageComponent = defineComponent({
   setup() {
     const mainStore = useMainStore()
@@ -74,7 +89,7 @@ const messageIcon = ref<HTMLDivElement>()
 
 const unReadMessages = useMessageLoop()
 
-const messageWindowType = ref<'view' | 'send'>('view')
+const messageWindowType = useVModel(props, 'type')
 
 /** 发送/查看消息Modal */
 const visibleMessageWindow = ref<boolean>(false)

+ 41 - 31
src/components/shared/message/MessageSend.vue

@@ -29,12 +29,16 @@
           </div>
         </div>
         <div class="flex-1 p-base overflow-hidden">
-          <div class="full-h radius-base scroll-y-auto message-info-content">
-            <content-edit-able v-model="messageContent" class="full-h" @click="onContentClick"></content-edit-able>
+          <div class="full-h radius-base p-base scroll-y-auto message-info-content">
+            <content-edit-able
+              v-model="messageContent"
+              class="full content-edit-able"
+              @click="onContentClick"
+            ></content-edit-able>
           </div>
         </div>
         <div class="p-base flex items-center justify-end">
-          <el-button size="small" plain @click="sendCurrentPaper">发送当前试卷</el-button>
+          <el-button v-show="showSendPaper" size="small" plain @click="sendCurrentPaper">发送当前试卷</el-button>
           <el-button size="small" type="primary" :disabled="!allowSend" :loading="loading" @click="onSendMessage">
             发送
           </el-button>
@@ -42,11 +46,11 @@
       </div>
     </div>
   </div>
-  <image-preview v-model="previewModalVisible" :url="previewPath"></image-preview>
+  <image-preview v-model="previewModalVisible" :url="props.paperPath || ''"></image-preview>
 </template>
 
 <script setup lang="tsx" name="MessageSend">
-import { ref, computed, watch, nextTick, defineComponent, createApp } from 'vue'
+import { ref, computed, watch, nextTick } from 'vue'
 import { ElInput, ElButton, ElTree, ElIcon, ElMessage } from 'element-plus'
 import { Close } from '@element-plus/icons-vue'
 import ContentEditAble from '@/components/common/ContentEditAble.vue'
@@ -60,26 +64,39 @@ type MarkerItem = ExtractArrayValue<ExtractApiResponse<'getUserGroup'>['chiffGro
 type TreeNode = ExtractArrayValue<ExtractApiResponse<'getUserGroup'>['markerGroup']> & { label: string }
 
 const props = defineProps<{
-  replyUserId: number
+  replyUserId?: number | null
+  paperPath?: string | null
 }>()
 
-const emit = defineEmits(['close'])
+/** cleat warning */
+const emit = defineEmits(['close', 'change-type', 'reply'])
 
+/** 默认的收件人ID */
 const replyUserId = useVModel(props, 'replyUserId')
 
 /** 图片预览 */
 const previewModalVisible = ref<boolean>(false)
 
-const previewPath = ref<string>()
-
+/** 显示收件人tree */
 const showCheckUser = ref<boolean>(true)
 
+/** 消息内容 */
 const messageContent = ref<string>('')
 
+/** 选中的收件人 */
 const checkedUsers = ref<MarkerItem[]>([])
 
+/** 收件人tree 筛选关键字 */
 const filterText = ref<string>('')
 
+/** 是否已发送试卷 */
+const sendedPaper = ref<boolean>(false)
+
+/** 显示发送试卷按钮 */
+const showSendPaper = computed<boolean>(() => {
+  return !!props.paperPath && !sendedPaper.value
+})
+
 const allowSend = computed<boolean>(() => {
   return !!(messageContent.value && checkedUsers.value.length)
 })
@@ -145,36 +162,26 @@ const onCheckChange = () => {
   checkedUsers.value = (treeRef?.value?.getCheckedNodes(true) as MarkerItem[]) || []
 }
 
+watch(messageContent, () => {
+  nextTick(() => {
+    const paperButton = document.querySelector(`.content-edit-able [data-path="${props.paperPath}"]`)
+    sendedPaper.value = !!paperButton
+  })
+})
+
 const onContentClick = (e: Event) => {
   const target = e.target as HTMLButtonElement
-  console.log([target])
-  const data = target.getAttribute('data')
-  console.log(data)
   const path = target.getAttribute('data-path')
   if (path) {
-    console.log(path)
+    previewModalVisible.value = true
   }
 }
 
 /** 发送当前试卷 */
 const sendCurrentPaper = () => {
-  const ImgLink = defineComponent({
-    render() {
-      return (
-        <ElButton
-          style={{ textAlign: 'left' }}
-          class="pointer inline-block full-w"
-          contenteditable={false}
-          type="primary"
-          data={'xxxxx'}
-          link
-        >
-          当前试卷
-        </ElButton>
-      )
-    },
-  })
-  messageContent.value += createApp(ImgLink).mount(document.createElement('div')).$el.outerHTML
+  if (props.paperPath) {
+    messageContent.value += ` <span class="pointer inline link-button" contenteditable="false" data-path="${props.paperPath}">当前试卷</span>`
+  }
 }
 
 const { fetch: sendMessage, loading } = useFetch('sendMessage')
@@ -182,6 +189,9 @@ const { fetch: sendMessage, loading } = useFetch('sendMessage')
 /** 发送消息 */
 const onSendMessage = async () => {
   try {
+    if (messageContent.value.length > 2000) {
+      return ElMessage.error('输入内容过长')
+    }
     await sendMessage({
       content: messageContent.value,
       receiveUserIds: checkedUsers.value.map((u) => u.id),
@@ -244,7 +254,7 @@ getUserGroup()
         }
       }
       .message-info-content {
-        // border: $OnePixelLine;
+        border: 1px solid $color--primary;
         font-size: $SmallFont;
         ::v-deep(textarea.el-textarea__inner) {
           height: 100%;

+ 6 - 3
src/components/shared/message/MessageWindow.vue

@@ -3,6 +3,7 @@
     <component
       :is="MessageWindowContent"
       :reply-user-id="replyUserId"
+      :paper-path="props.paperPath"
       @close="onClose"
       @change-type="onChangeType"
       @reply="onReply"
@@ -24,15 +25,17 @@ const props = withDefaults(
   defineProps<{
     modelValue: boolean
     type: ModalType
+    replyUserId?: number | null
+    paperPath?: string | null
   }>(),
-  { type: 'view' }
+  { type: 'view', replyUserId: null, paperPath: null }
 )
 
 const visible = useVModel(props, 'modelValue')
 
 const modalType = useVModel(props, 'type')
 
-const replyUserId = ref<number>()
+const replyUserId = useVModel(props, 'replyUserId')
 
 const MessageWindowContent = computed(() => {
   return modalType.value === 'send' ? MessageSend : MessageHistory
@@ -40,7 +43,7 @@ const MessageWindowContent = computed(() => {
 
 const onClose = () => {
   visible.value = false
-  replyUserId.value = void 0
+  replyUserId.value = null
 }
 
 const onChangeType = (type: ModalType) => {

+ 1 - 1
src/hooks/useMessageLoop.ts

@@ -15,7 +15,7 @@ const useMessageLoop = () => {
     () => {
       if (mainStore?.myUserInfo?.role && mainStore?.myUserInfo?.role !== 'ADMIN') {
         getUnReadMessage()
-        useIntervalFn(getUnReadMessage, 10 * 1000)
+        useIntervalFn(getUnReadMessage, 30 * 1000)
       }
     },
     {

+ 1 - 1
src/modules/analysis/group-monitoring-detail/index.vue

@@ -2,7 +2,7 @@
   <div class="flex direction-column full">
     <div class="flex items-center p-extra-small fill-blank header-view">
       <el-button class="m-r-auto" size="small" plain @click="back">返回</el-button>
-      <message></message>
+      <message :paper-path="current?.filePath"></message>
       <user-info></user-info>
     </div>
     <div class="flex fill-blank detail-info">

+ 5 - 1
src/modules/analysis/view-marked-detail/index.vue

@@ -1,6 +1,10 @@
 <template>
   <div class="flex direction-column full">
-    <mark-header :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']" @click="onOperationClick">
+    <mark-header
+      :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']"
+      :paper-path="current?.filePath"
+      @click="onOperationClick"
+    >
       <el-button class="m-l-base m-r-auto" size="small" type="primary" @click="onEditScore">修改给分</el-button>
     </mark-header>
     <div class="flex flex-1 overflow-hidden p-base mark-container">

+ 5 - 1
src/modules/expert/assess/index.vue

@@ -1,6 +1,10 @@
 <template>
   <div class="flex direction-column full">
-    <mark-header :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']" @click="onOperationClick">
+    <mark-header
+      :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']"
+      :paper-path="currentAssessPaper?.filePath"
+      @click="onOperationClick"
+    >
       <el-button class="m-l-base m-r-auto" size="small" type="primary" @click="onEditScore">修改给分</el-button>
     </mark-header>
     <div class="flex flex-1 overflow-hidden p-base mark-container">

+ 5 - 1
src/modules/expert/expert/index.vue

@@ -1,6 +1,10 @@
 <template>
   <div class="flex direction-column full">
-    <mark-header :exclude-operations="['remark', 'problem', 'example', 'bookmark']" @click="onOperationClick">
+    <mark-header
+      :exclude-operations="['remark', 'problem', 'example', 'bookmark']"
+      :paper-path="currentExpertPaper?.filePath"
+      @click="onOperationClick"
+    >
       <el-button class="m-l-base m-r-auto" size="small" type="primary" @click="onEditScore">修改给分</el-button>
     </mark-header>
     <div class="flex flex-1 overflow-hidden p-base mark-container">

+ 5 - 1
src/modules/expert/sample/index.vue

@@ -1,6 +1,10 @@
 <template>
   <div class="flex direction-column full">
-    <mark-header :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']" @click="onOperationClick">
+    <mark-header
+      :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']"
+      :paper-path="currentRfPaper?.filePath"
+      @click="onOperationClick"
+    >
       <el-button class="m-l-base m-r-auto" size="small" type="primary" @click="onEditScore">修改给分</el-button>
     </mark-header>
     <div class="flex flex-1 overflow-hidden p-base mark-container">

+ 1 - 1
src/modules/expert/standard/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="flex direction-column full">
-    <mark-header :include-operations="['back']" @click="onOperationClick">
+    <mark-header :include-operations="['back']" :paper-path="currentStandardPaper?.filePath" @click="onOperationClick">
       <el-button class="m-l-base m-r-auto" size="small" type="primary" @click="onTransformToSample">转为样卷</el-button>
     </mark-header>
     <div class="flex flex-1 overflow-hidden p-base mark-container">

+ 1 - 0
src/modules/expert/training/index.vue

@@ -2,6 +2,7 @@
   <div class="flex direction-column full">
     <mark-header
       :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']"
+      :paper-path="current?.filePath"
       @click="onOperationClick"
     ></mark-header>
     <div class="flex flex-1 overflow-hidden p-base mark-container">

+ 1 - 0
src/modules/marking/arbitration/index.vue

@@ -2,6 +2,7 @@
   <div class="flex direction-column full">
     <mark-header
       :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']"
+      :paper-path="currentArbitration?.filePath"
       @click="onOperationClick"
     ></mark-header>
     <div class="flex flex-1 overflow-hidden p-base mark-container">

+ 5 - 1
src/modules/marking/inquiry-result/index.vue

@@ -1,6 +1,10 @@
 <template>
   <div class="flex direction-column full">
-    <mark-header :exclude-operations="['remark', 'problem', 'example', 'delete']" @click="onOperationClick">
+    <mark-header
+      :exclude-operations="['remark', 'problem', 'example', 'delete']"
+      :paper-path="current?.filePath"
+      @click="onOperationClick"
+    >
       <el-button class="m-l-base m-r-auto" size="small" type="primary" @click="onEditScore">修改给分</el-button>
     </mark-header>
     <div class="flex flex-1 overflow-hidden p-base mark-container">

+ 1 - 1
src/modules/marking/mark/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="flex direction-column full">
-    <mark-header :exclude-operations="['delete', 'bookmark']" @click="onOperationClick">
+    <mark-header :exclude-operations="['delete', 'bookmark']" :paper-path="currentTask?.url" @click="onOperationClick">
       <div class="flex items-center m-l-auto">
         <span class="data-item">
           已评: {{ markStatus?.personCount || 0 }} /

+ 5 - 1
src/modules/marking/problem/index.vue

@@ -1,6 +1,10 @@
 <template>
   <div class="flex direction-column full">
-    <mark-header :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']" @click="onOperationClick">
+    <mark-header
+      :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']"
+      :paper-path="currentProblem?.filePath"
+      @click="onOperationClick"
+    >
       <el-button class="m-l-base m-r-auto" size="small" type="primary" @click="onSendBack">打回</el-button>
     </mark-header>
     <div class="flex flex-1 overflow-hidden p-base mark-container">

+ 5 - 1
src/modules/marking/repeat/index.vue

@@ -1,6 +1,10 @@
 <template>
   <div class="flex direction-column full">
-    <mark-header :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']" @click="onOperationClick">
+    <mark-header
+      :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']"
+      :paper-path="currentReMarkPaper?.filePath"
+      @click="onOperationClick"
+    >
       <el-button class="m-l-base m-r-auto" size="small" type="primary" @click="onConfirmReMark">确认</el-button>
     </mark-header>
     <div class="flex flex-1 overflow-hidden p-base mark-container">

+ 1 - 1
src/modules/marking/similar/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="flex direction-column full">
-    <mark-header :include-operations="['back']" @click="onOperationClick">
+    <mark-header :include-operations="['back']" :paper-path="currentSamePaper?.filePath" @click="onOperationClick">
       <el-button class="m-l-base" size="small" type="primary" @click="onConfirmSame(true)">确定雷同</el-button>
       <el-button class="m-l-base" size="small" type="primary" plain @click="onConfirmSame(false)">否定雷同</el-button>
       <el-button class="m-l-base m-r-auto" size="small" type="primary" custom-1 @click="onExport"> 导出 </el-button>

+ 1 - 0
src/modules/marking/training-record/index.vue

@@ -2,6 +2,7 @@
   <div class="flex direction-column full">
     <mark-header
       :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']"
+      :paper-path="current?.url"
       @click="onOperationClick"
     ></mark-header>
     <div class="flex flex-1 overflow-hidden p-base mark-container">

+ 1 - 0
src/modules/marking/view-sample/index.vue

@@ -2,6 +2,7 @@
   <div class="flex direction-column full">
     <mark-header
       :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']"
+      :paper-path="current?.url"
       @click="onOperationClick"
     ></mark-header>
     <div class="flex flex-1 overflow-hidden p-base mark-container">

+ 5 - 1
src/modules/monitor/system-check/index.vue

@@ -1,6 +1,10 @@
 <template>
   <div class="flex direction-column full">
-    <mark-header :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']" @click="onOperationClick">
+    <mark-header
+      :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']"
+      :paper-path="currentSystemCheckPaper?.filePath"
+      @click="onOperationClick"
+    >
       <template v-if="!hideHeaderButtons">
         <el-button class="m-l-base" size="small" type="primary" plain @click="onEditScore">修改给分</el-button>
         <el-button class="m-l-base m-r-auto" size="small" type="primary" @click="onSendBack">打回</el-button>

+ 5 - 1
src/modules/monitor/training-monitoring-detail/index.vue

@@ -1,6 +1,10 @@
 <template>
   <div class="flex direction-column full">
-    <mark-header :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']" @click="onOperationClick">
+    <mark-header
+      :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']"
+      :paper-path="current?.filePath"
+      @click="onOperationClick"
+    >
     </mark-header>
     <div class="flex flex-1 overflow-hidden p-base mark-container">
       <div

+ 5 - 1
src/modules/quality/self-check-detail/index.vue

@@ -1,6 +1,10 @@
 <template>
   <div class="flex direction-column full">
-    <mark-header :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']" @click="onOperationClick">
+    <mark-header
+      :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']"
+      :paper-path="current?.filePath"
+      @click="onOperationClick"
+    >
       <el-button class="m-l-base" size="small" type="primary" plain @click="onEditScore">修改给分</el-button>
       <el-button class="m-l-base m-r-auto" size="small" type="primary" @click="onSendBack">打回</el-button>
     </mark-header>

+ 5 - 1
src/modules/quality/subjective-check/index.vue

@@ -1,6 +1,10 @@
 <template>
   <div class="flex direction-column full">
-    <mark-header :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']" @click="onOperationClick">
+    <mark-header
+      :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']"
+      :paper-path="currentSubjectiveCheck?.filePath"
+      @click="onOperationClick"
+    >
       <el-button type="primary" size="small" class="m-l-base" plain @click="onEditScore">修改给分</el-button>
       <el-button type="primary" size="small" class="m-l-base m-r-auto" @click="onConfirm">提交确认</el-button>
     </mark-header>

+ 1 - 0
src/store/main.ts

@@ -20,6 +20,7 @@ const useMainStore = defineStore<'main', MainStoreState, Record<string, any>, Ma
       bootstrapTime: Date.now(),
       /** 用户登录信息 */
       loginInfo: (sessionStorage.get('LOGIN_RESULT') as ExtractApiResponse<'userLogin'>) ?? void 0,
+      /** 当前用户信息 */
       myUserInfo: void 0,
     }
   },