浏览代码

feat: 评卷页面逻辑调整

chenhao 2 年之前
父节点
当前提交
516766c35d

+ 3 - 0
server.config.ts

@@ -5,6 +5,9 @@ const server: ServerOptions = {
     '^/?api/': {
       target: 'http://192.168.10.41:7201',
     },
+    '^/?file/': {
+      target: 'http://192.168.10.41:7200',
+    },
   },
 }
 

+ 4 - 0
src/App.vue

@@ -2,10 +2,14 @@
 import { ElConfigProvider } from 'element-plus'
 import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
 import useMainStore from '@/store/main'
+import useMainLayoutStore from '@/store/layout'
+
 const mainStore = useMainStore()
+const mainLayoutStore = useMainLayoutStore()
 
 if (mainStore.loginInfo) {
   mainStore.getMyUserInfo()
+  mainLayoutStore.getRenderMenuList()
 }
 </script>
 

+ 3 - 3
src/components/shared/ImagePreview.vue

@@ -22,10 +22,10 @@ const visible = useVModel(props)
   margin-left: 70%;
   .preview-content {
     width: 100%;
-    min-width: 300px;
+    max-width: 400px;
+    text-align: center;
     min-height: 400px;
-    max-width: 600px;
-    max-height: 800px;
+    max-height: 600px;
     img {
       width: 100%;
       height: 100%;

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

@@ -5,22 +5,22 @@
       <div class="flex-1 m-t-base scroll-y-auto">
         <el-tree
           ref="treeRef"
-          v-model="markerIds"
           show-checkbox
           :filter-node-method="filterTree"
           :data="markerTree"
           :props="treeProp"
+          @check-change="onCheckChange"
         ></el-tree>
       </div>
     </div>
     <div class="message-info-container">
       <div class="flex direction-column message-info">
         <div class="flex items-center p-base message-info-header">
-          <div class="flex items-center send-user">
+          <div class="flex flex-1 items-center overflow-hidden send-user">
             <span class="m-r-mini">收件人</span>
-            <span class="flex items-center justify-between radius-base user-name">
-              <span> {{ checkedUsers }}</span>
-              <el-button size="small" type="primary" @click="toggleCheckUser"> 选择收件人 </el-button>
+            <span class="flex flex-1 overflow-hidden items-center justify-between radius-base user-name">
+              <span class="flex-1 checked-users"> {{ viewCheckedUser }}</span>
+              <el-button class="m-l-base" size="small" type="primary" @click="toggleCheckUser"> 选择收件人 </el-button>
             </span>
           </div>
           <div class="grid pointer m-l-auto close-icon" @click="$emit('close')">
@@ -28,8 +28,15 @@
           </div>
         </div>
         <div class="flex-1 p-base overflow-hidden">
-          <div class="full-h radius-base p-extra-base scroll-y-auto message-info-content">
-            <el-input v-model="messageContent" type="textarea"></el-input>
+          <div class="full-h radius-base scroll-y-auto message-info-content">
+            <el-input
+              v-model="messageContent"
+              type="textarea"
+              class="full-h"
+              resize="none"
+              :maxlength="1000"
+              show-word-limit
+            ></el-input>
           </div>
         </div>
         <div class="p-base flex items-center justify-end">
@@ -42,7 +49,7 @@
 </template>
 
 <script setup lang="ts" name="MessageSend">
-import { reactive, ref, computed } from 'vue'
+import { reactive, ref, computed, watch } from 'vue'
 import { ElInput, ElButton, ElTree, ElIcon } from 'element-plus'
 import { Close } from '@element-plus/icons-vue'
 import useFetch from '@/hooks/useFetch'
@@ -57,13 +64,15 @@ defineEmits(['close'])
 const showCheckUser = ref<boolean>(false)
 
 const messageContent = ref<string>()
-const markerIds = ref<number[]>()
+
+const checkedUsers = ref<MarkerItem[]>()
 
 const filterText = ref<string>('')
+
 const treeRef = ref<InstanceType<typeof ElTree>>()
 
-const checkedUsers = computed(() => {
-  return 'xxx'
+const viewCheckedUser = computed(() => {
+  return checkedUsers?.value?.map((d) => `${d.loginName}-${d.name}`)?.join(';')
 })
 
 const toggleCheckUser = () => {
@@ -89,6 +98,10 @@ const filterTree = ((value: string, data: MarkerItem) => {
   return data.name?.includes(value) || data.loginName?.includes(value)
 }) as unknown as InstanceType<typeof ElTree>['filterNodeMethod']
 
+watch(filterText, () => {
+  treeRef?.value?.filter(filterText.value)
+})
+
 const { fetch: getUserGroup, result: userGroup } = useFetch('getUserGroup')
 
 const markerTree = computed<TreeNode[]>(() => {
@@ -103,6 +116,10 @@ const markerTree = computed<TreeNode[]>(() => {
   ] as TreeNode[]
 })
 
+const onCheckChange = () => {
+  checkedUsers.value = (treeRef?.value?.getCheckedNodes(true) as MarkerItem[]) || []
+}
+
 /** 发送当前试卷 */
 const sendCurrentPaper = () => {
   console.log('发送当前试卷')
@@ -117,9 +134,13 @@ getUserGroup()
 
 <style scoped lang="scss">
 .message-list-modal {
-  width: 880px;
   background-color: transparent;
   .tree-box {
+    width: 280px;
+    height: 446px;
+    ::v-deep(.el-tree) {
+      min-height: 100%;
+    }
   }
   .message-list {
     width: 280px;
@@ -141,6 +162,10 @@ getUserGroup()
             min-width: 320px;
             padding: 10px 12px;
             border: $OnePixelLine;
+            .checked-users {
+              overflow: hidden;
+              white-space: nowrap;
+            }
           }
         }
         .close-icon {
@@ -155,8 +180,11 @@ getUserGroup()
         }
       }
       .message-info-content {
-        border: $OnePixelLine;
+        // border: $OnePixelLine;
         font-size: $SmallFont;
+        ::v-deep(textarea.el-textarea__inner) {
+          height: 100%;
+        }
       }
     }
   }

+ 15 - 3
src/hooks/useMessageLoop.ts

@@ -1,14 +1,26 @@
-import { effectScope, onScopeDispose } from 'vue'
+import { effectScope, onScopeDispose, watch } from 'vue'
 import { useIntervalFn } from '@vueuse/core'
 import uesFetch from '@/hooks/useFetch'
+import useMainStore from '@/store/main'
 
 import type { EffectScope, ShallowRef } from 'vue'
 import type { ExtractApiResponse } from 'api-type'
-import type {} from 'global-type'
 
 const useMessageLoop = () => {
+  const mainStore = useMainStore()
   const { fetch: getUnReadMessage, result: unReadMessage } = uesFetch('getUnReadMessage')
-  useIntervalFn(getUnReadMessage, 10 * 1000)
+
+  watch(
+    () => mainStore?.myUserInfo?.role,
+    () => {
+      if (mainStore?.myUserInfo?.role && mainStore?.myUserInfo?.role !== 'ADMIN') {
+        useIntervalFn(getUnReadMessage, 10 * 1000)
+      }
+    },
+    {
+      immediate: true,
+    }
+  )
 
   return unReadMessage
 }

+ 20 - 12
src/hooks/useSetImgBg.ts

@@ -356,20 +356,28 @@ export const useSetImgBg = (option: Ref<SetImgBgOption>) => {
 
   let task: number | null = null
 
-  const updateImageData = async (image: HTMLImageElement, scale?: number) => {
+  const updateImageData = async (image?: HTMLImageElement, scale?: number) => {
     try {
+      dataUrl.value = ''
       imageData.value = null
       rawCanvas.value = null
       rawContext.value = null
-      const {
-        imageData: outPutImageData,
-        canvas,
-        context,
-      } = await getImageData({ image, canvas: option.value.canvas, scale, useNaturalSize: option.value.useNaturalSize })
-      updateCanvas(option.value.rotate, true)
-      imageData.value = outPutImageData
-      rawCanvas.value = canvas
-      rawContext.value = context
+      if (image) {
+        const {
+          imageData: outPutImageData,
+          canvas,
+          context,
+        } = await getImageData({
+          image,
+          canvas: option.value.canvas,
+          scale,
+          useNaturalSize: option.value.useNaturalSize,
+        })
+        updateCanvas(option.value.rotate, true)
+        imageData.value = outPutImageData
+        rawCanvas.value = canvas
+        rawContext.value = context
+      }
     } catch (error) {
       console.error(error)
     }
@@ -379,7 +387,7 @@ export const useSetImgBg = (option: Ref<SetImgBgOption>) => {
     () => option.value.image,
     () => {
       getImage(option.value.image).then((res) => {
-        res && (imageElement.value = res)
+        imageElement.value = res || undefined
       })
     },
     { immediate: option.value.immediate }
@@ -387,7 +395,7 @@ export const useSetImgBg = (option: Ref<SetImgBgOption>) => {
 
   /** 渲染图片 */
   watch([imageElement, () => option.value.scale], () => {
-    imageElement.value && updateImageData(imageElement.value, option.value.scale || 1)
+    updateImageData(imageElement.value, option.value.scale || 1)
   })
 
   /** 获取主色 */

+ 3 - 0
src/modules/bootstrap/login/index.vue

@@ -24,6 +24,7 @@ import { useRouter } from 'vue-router'
 import { ElButton } from 'element-plus'
 import BaseForm from '@/components/element/BaseForm.vue'
 import useMainStore from '@/store/main'
+import useMainLayoutStore from '@/store/layout'
 import useFetch from '@/hooks/useFetch'
 import useForm from '@/hooks/useForm'
 import { sessionStorage } from '@/plugins/storage'
@@ -34,6 +35,7 @@ import type { ExtractApiParams, ExtractApiResponse } from 'api-type'
 const { replace } = useRouter()
 
 const mainStore = useMainStore()
+const mainLayoutStore = useMainLayoutStore()
 
 const { loading, fetch: login } = useFetch('userLogin')
 
@@ -90,6 +92,7 @@ function loginSuccess(loginInfo: ExtractApiResponse<'userLogin'>) {
   /** pinia store 存储 login result */
   mainStore.loginInfo = loginInfo
 
+  mainLayoutStore.getRenderMenuList()
   /**
    * 超级管理员每次登录完成之后需要选择考试批次
    * 其它角色如果是首次登录,需要去设置名称.

+ 11 - 5
src/modules/marking/mark/index.vue

@@ -63,6 +63,7 @@
 /** 阅卷-正式评卷 */
 import { computed, reactive, ref, watch } from 'vue'
 import { useRouter } from 'vue-router'
+import { debounce } from 'lodash-es'
 import { ElButton, ElRadioGroup, ElRadioButton, ElRadio } from 'element-plus'
 import { useSetImgBg } from '@/hooks/useSetImgBg'
 import useFetch from '@/hooks/useFetch'
@@ -136,12 +137,14 @@ const historyTaskChange = (task: TaskInfoType) => {
   currentTaskType.value = 'remarking'
 }
 
-const getNextTask = () => {
+const debounceGetMarkingTask = debounce(getMarkingTask, 5000)
+
+const getNextTask = (force = false) => {
   currentTask.value = currentTaskPool.shift()
   currentTaskType.value =
     currentTask.value && markStatusIcon[currentTask.value.taskType] ? currentTask.value.taskType : 'FORMAL'
   if (currentTaskPool.length < 2) {
-    getMarkingTask()
+    force ? getMarkingTask() : debounceGetMarkingTask()
   }
 }
 
@@ -186,7 +189,7 @@ const onSubmit: InstanceType<typeof ScoringPanelWithConfirm>['onSubmit'] = async
       problem: false,
       taskId: currentTask.value.taskId,
     })
-    await getNextTask()
+    await getNextTask(true)
     await getMarkStatus()
   } catch (error) {
     console.error(error)
@@ -218,7 +221,8 @@ const onConfirmProblem = async () => {
         problemType: problemType.value,
         taskId: currentTask.value.taskId,
       })
-      await getMarkingTask()
+      await getNextTask(true)
+      await getMarkStatus()
     }
   } catch (error) {
     console.error(error)
@@ -293,7 +297,9 @@ watch(currentTask, () => {
 
 getMarkStatus()
 
-useFetch('clearCachedTasks').fetch().then(getNextTask)
+useFetch('clearCachedTasks')
+  .fetch()
+  .then(() => getNextTask())
 </script>
 
 <style scoped lang="scss">

+ 55 - 19
src/store/layout.ts

@@ -1,6 +1,9 @@
 import { defineStore } from 'pinia'
 import router from '@/router'
+import useFetch from '@/hooks/useFetch'
+
 import type { RouteRecordRaw } from 'vue-router'
+import type { ExtractApiResponse } from 'api-type'
 
 export function getMenuRotes() {
   function getMenuInfo(route: RouteRecordRaw): MainLayoutStore.MenuItem {
@@ -22,6 +25,18 @@ export function getMenuRotes() {
 
   const tempRoutes = router.getRoutes()
 
+  /** 给后端配置权限的菜单路由 */
+  console.log(
+    tempRoutes
+      .filter((route) => !route.path.startsWith('/example') && route.meta.menu)
+      .map((_) => ({
+        menuId: _.meta.menuId,
+        name: _.name,
+        path: _.path,
+        label: _.meta.label,
+      }))
+  )
+
   const routesMap = tempRoutes
     .map((_, i) => _.meta?.menuId + '-' + i)
     .filter((_) => !_.startsWith('undefined'))
@@ -67,27 +82,48 @@ export function getMenuRotes() {
   return objChild2Arr(menuTree, [])
 }
 
-const useMainLayoutStore = defineStore<'main-layout', MainLayoutStore.State, any, { toggleCollapse: () => void }>(
+function filterPrivilege(
+  item: MainLayoutStore.MenuItem,
+  privilege: ExtractApiResponse<'getUserPrivilege'>
+): MainLayoutStore.MenuItem | undefined {
+  if (privilege.some((d) => item.path && d.privilegeUri.startsWith(item.path))) {
+    return {
+      ...item,
+      children: item.children?.filter((child) => !!filterPrivilege(child, privilege)),
+    }
+  }
+}
+
+const useMainLayoutStore = defineStore<
   'main-layout',
-  {
-    state() {
-      return {
-        collapse: false,
-        menuList: getMenuRotes(),
-      }
-    },
-    actions: {
-      toggleCollapse() {
-        this.collapse = !this.collapse
-      },
+  MainLayoutStore.State,
+  any,
+  { toggleCollapse: () => void; getRenderMenuList: () => Promise<void> }
+>('main-layout', {
+  state() {
+    return {
+      collapse: false,
+      menuList: getMenuRotes(),
+      renderMenus: [],
+    }
+  },
+  actions: {
+    toggleCollapse() {
+      this.collapse = !this.collapse
     },
-    getters: {
-      renderMenuList: (state: MainLayoutStore.State) => {
-        console.log(state)
-        return state.menuList
-      },
+    async getRenderMenuList() {
+      try {
+        const privilege = await useFetch('getUserPrivilege').fetch()
+        this.renderMenus = this.menuList.reduce((menus, menu) => {
+          return menus.concat(filterPrivilege(menu, privilege) || [])
+        }, [] as MainLayoutStore.MenuItem[])
+
+        console.log(this.renderMenus)
+      } catch (error) {
+        console.error(error)
+      }
     },
-  }
-)
+  },
+})
 
 export default useMainLayoutStore

+ 2 - 1
src/store/main.ts

@@ -1,6 +1,7 @@
 import { defineStore } from 'pinia'
-import useFetch from '@/hooks/useFetch'
 import { sessionStorage } from '@/plugins/storage'
+import useFetch from '@/hooks/useFetch'
+
 import type { ExtractApiResponse } from 'api-type'
 interface MainStoreState {
   bootstrapTime: number

+ 1 - 1
types/api.d.ts

@@ -1137,7 +1137,7 @@ declare module 'api-type' {
       privilegeUri: string
     }
     /** 获取当前用户菜单 */
-    type GetUserPrivilege = BaseDefine<null, PrivilegeInfo>
+    type GetUserPrivilege = BaseDefine<null, PrivilegeInfo[]>
 
     /** 超管选择考试 */
     type CheckExam = BaseDefine<{ examId: number }>

+ 1 - 0
types/store.d.ts

@@ -13,5 +13,6 @@ declare namespace MainLayoutStore {
   interface State {
     collapse: boolean
     menuList: MenuItem[]
+    renderMenus: MenuItem[]
   }
 }