MessageSend.vue 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. <template>
  2. <div class="flex radius-base overflow-hidden message-list-modal">
  3. <div v-show="showCheckUser" class="flex direction-column p-base fill-lighter tree-box">
  4. <el-input v-model="filterText" placeholder="输入评卷员账号或名称筛选" clearable></el-input>
  5. <div class="flex-1 m-t-base scroll-y-auto">
  6. <el-tree
  7. ref="treeRef"
  8. show-checkbox
  9. node-key="id"
  10. :filter-node-method="filterTree"
  11. :data="markerTree"
  12. :props="treeProp"
  13. @check-change="onCheckChange"
  14. ></el-tree>
  15. </div>
  16. </div>
  17. <div class="message-info-container" :class="{ big: !showCheckUser }">
  18. <div class="flex direction-column message-info">
  19. <div class="flex items-center p-base message-info-header">
  20. <div class="flex flex-1 items-center overflow-hidden send-user">
  21. <span class="m-r-mini">收件人</span>
  22. <span class="flex flex-1 overflow-hidden items-center justify-between radius-base m-r-base user-name">
  23. <p class="flex-1 checked-users flex" :class="{ big: showCheckUser }">
  24. <span class="split-names">{{ viewCheckedUser }}</span>
  25. <span v-if="checkedUsers.length > 3">共计{{ checkedUsers.length }}个人员</span>
  26. </p>
  27. <el-button class="m-l-base" size="small" type="primary" @click="toggleCheckUser"> 选择收件人 </el-button>
  28. </span>
  29. </div>
  30. <div class="grid pointer m-l-auto close-icon" @click="$emit('close')">
  31. <el-icon><close /></el-icon>
  32. </div>
  33. </div>
  34. <div class="flex-1 p-base overflow-hidden">
  35. <div class="full-h radius-base p-base scroll-y-auto message-info-content">
  36. <content-edit-able
  37. v-model="messageContent"
  38. class="full content-edit-able"
  39. @click="onContentClick"
  40. ></content-edit-able>
  41. </div>
  42. </div>
  43. <div class="p-base flex items-center justify-end">
  44. <el-button v-show="showSendPaper" size="small" plain @click="sendCurrentPaper">发送当前试卷</el-button>
  45. <el-button size="small" type="primary" :disabled="!allowSend" :loading="loading" @click="onSendMessage">
  46. 发送
  47. </el-button>
  48. <el-button v-if="checkedUsers.length == 1" size="small" plain @click="toggleHistory">历史消息</el-button>
  49. </div>
  50. </div>
  51. <message-history
  52. v-if="showHistory"
  53. :send-user-id="checkedUsers.length == 1 ? checkedUsers[0].id : undefined"
  54. ></message-history>
  55. </div>
  56. </div>
  57. <image-preview v-model="previewModalVisible" :url="props.paperPath || ''"></image-preview>
  58. </template>
  59. <script setup lang="tsx" name="MessageSend">
  60. import { ref, computed, watch, nextTick } from 'vue'
  61. import { ElInput, ElButton, ElTree, ElIcon, ElMessage } from 'element-plus'
  62. import { Close } from '@element-plus/icons-vue'
  63. import ContentEditAble from '@/components/common/ContentEditAble.vue'
  64. import ImagePreview from '../ImagePreview.vue'
  65. import useFetch from '@/hooks/useFetch'
  66. import useVModel from '@/hooks/useVModel'
  67. import MessageHistory from '@/components/shared/message/MessageHistory.vue'
  68. import type { ExtractApiResponse } from '@/api/api'
  69. type MarkerItem = ExtractArrayValue<ExtractApiResponse<'getUserGroup'>['chiffGroup']>
  70. type TreeNode = ExtractArrayValue<ExtractApiResponse<'getUserGroup'>['markerGroup']> & { label: string }
  71. const props = defineProps<{
  72. replyUserId?: number | null
  73. paperPath?: string | null
  74. }>()
  75. /** cleat warning */
  76. const emit = defineEmits(['close', 'change-type', 'reply'])
  77. const showHistory = ref<boolean>(false)
  78. /** 默认的收件人ID */
  79. const replyUserId = useVModel(props, 'replyUserId')
  80. /** 图片预览 */
  81. const previewModalVisible = ref<boolean>(false)
  82. /** 显示收件人tree */
  83. const showCheckUser = ref<boolean>(false)
  84. /** 消息内容 */
  85. const messageContent = ref<string>('')
  86. /** 选中的收件人 */
  87. const checkedUsers = ref<MarkerItem[]>([])
  88. /** 收件人tree 筛选关键字 */
  89. const filterText = ref<string>('')
  90. /** 是否已发送试卷 */
  91. const sendedPaper = ref<boolean>(false)
  92. /** 显示发送试卷按钮 */
  93. const showSendPaper = computed<boolean>(() => {
  94. return !!props.paperPath && !sendedPaper.value
  95. })
  96. const allowSend = computed<boolean>(() => {
  97. return !!(messageContent.value && checkedUsers.value?.filter((v) => !!v.id)?.length)
  98. })
  99. const treeRef = ref<InstanceType<typeof ElTree>>()
  100. const viewCheckedUser = computed(() => {
  101. return checkedUsers?.value
  102. ?.filter((v) => !!v.id)
  103. ?.map((d) => `${d.loginName}-${d.name}`)
  104. ?.join(';')
  105. })
  106. const toggleCheckUser = () => {
  107. showCheckUser.value = !showCheckUser.value
  108. }
  109. function isMarker(x: any): x is MarkerItem {
  110. return !!x.loginName
  111. }
  112. const treeProp = {
  113. children: 'markers',
  114. label(treeNode: TreeNode) {
  115. if (isMarker(treeNode)) {
  116. // return treeNode.name || treeNode.loginName
  117. return `${treeNode.loginName}-${treeNode.name}`
  118. }
  119. return treeNode.label
  120. },
  121. } as unknown as InstanceType<typeof ElTree>['props']
  122. const filterTree = ((value: string, data: MarkerItem) => {
  123. if (!value) return true
  124. return data.name?.includes(value) || data.loginName?.includes(value)
  125. }) as unknown as InstanceType<typeof ElTree>['filterNodeMethod']
  126. watch(filterText, () => {
  127. treeRef?.value?.filter(filterText.value)
  128. })
  129. const { fetch: getUserGroup, result: userGroup } = useFetch('getUserGroup')
  130. const markerTree = computed<TreeNode[]>(() => {
  131. if (!userGroup?.value) {
  132. return []
  133. }
  134. const { chiffGroup, deputyGroup, markerGroup } = userGroup.value
  135. return [
  136. chiffGroup?.length ? { label: '大组长', markingGroupNumber: -1, markers: chiffGroup } : null,
  137. deputyGroup?.length ? { label: '小组长', markingGroupNumber: -1, markers: deputyGroup } : null,
  138. markerGroup?.length
  139. ? { label: '评卷小组', markers: markerGroup.map((d) => ({ ...d, label: `第${d.markingGroupNumber}组` })) }
  140. : null,
  141. ].filter(Boolean) as TreeNode[]
  142. })
  143. watch(
  144. [replyUserId, markerTree],
  145. () => {
  146. nextTick(() => {
  147. replyUserId.value && markerTree.value.length && treeRef?.value?.setCheckedKeys([replyUserId.value])
  148. })
  149. },
  150. { immediate: true }
  151. )
  152. const onCheckChange = () => {
  153. checkedUsers.value = (treeRef?.value?.getCheckedNodes(true) as MarkerItem[]).filter((v) => !!v.id) || []
  154. if (checkedUsers.value.length != 1) {
  155. showHistory.value = false
  156. }
  157. }
  158. watch(messageContent, () => {
  159. nextTick(() => {
  160. const paperButton = document.querySelector(`.content-edit-able [data-path="${props.paperPath}"]`)
  161. sendedPaper.value = !!paperButton
  162. })
  163. })
  164. const onContentClick = (e: Event) => {
  165. const target = e.target as HTMLButtonElement
  166. const path = target.getAttribute('data-path')
  167. if (path) {
  168. previewModalVisible.value = true
  169. }
  170. }
  171. /** 发送当前试卷 */
  172. const sendCurrentPaper = () => {
  173. if (props.paperPath) {
  174. messageContent.value += `<span class="pointer inline link-button" contenteditable="false" data-path="${props.paperPath}">查看试卷</span>`
  175. }
  176. }
  177. const { fetch: sendMessage, loading } = useFetch('sendMessage')
  178. /** 发送消息 */
  179. const onSendMessage = async () => {
  180. try {
  181. if (messageContent.value.length > 2000) {
  182. return ElMessage.error('输入内容过长')
  183. }
  184. await sendMessage({
  185. content: messageContent.value,
  186. receiveUserIds: checkedUsers.value?.filter(Boolean)?.map((u) => u.id),
  187. })
  188. ElMessage.success('发送成功')
  189. messageContent.value = ''
  190. // emit('close')
  191. } catch (error) {
  192. console.error(error)
  193. }
  194. }
  195. getUserGroup()
  196. const toggleHistory = () => {
  197. showHistory.value = !showHistory.value
  198. }
  199. </script>
  200. <style scoped lang="scss">
  201. .message-list-modal {
  202. background-color: transparent;
  203. width: 750px;
  204. .tree-box {
  205. width: 260px;
  206. height: 446px;
  207. ::v-deep(.el-tree) {
  208. min-height: 100%;
  209. }
  210. }
  211. .message-list {
  212. width: 260px;
  213. height: 446px;
  214. background: #fafafa;
  215. box-shadow: 0px 6px 6px 0px rgba(0, 0, 0, 0.1);
  216. }
  217. .message-info-container {
  218. // width: 600px;
  219. // flex: 1;
  220. width: calc(100% - 260px);
  221. &.big {
  222. width: 750px;
  223. }
  224. .message-info {
  225. height: 446px;
  226. background-color: $color--white;
  227. .message-info-header {
  228. border-bottom: $OnePixelLine;
  229. .send-user {
  230. font-size: $SmallFont;
  231. color: $RegularFontColor;
  232. .user-name {
  233. min-width: 320px;
  234. padding: 10px 12px;
  235. border: $OnePixelLine;
  236. .checked-users {
  237. // overflow: hidden;
  238. // white-space: nowrap;
  239. display: flex;
  240. width: 500px;
  241. &.big {
  242. width: 240px;
  243. }
  244. .split-names {
  245. flex: 1;
  246. overflow: hidden;
  247. white-space: nowrap;
  248. text-overflow: ellipsis;
  249. }
  250. }
  251. }
  252. }
  253. .close-icon {
  254. width: 20px;
  255. height: 20px;
  256. place-items: center;
  257. font-size: 18px;
  258. color: $RegularFontColor;
  259. &:hover {
  260. color: $NormalColor;
  261. }
  262. }
  263. }
  264. .message-info-content {
  265. border: 1px solid $color--primary;
  266. font-size: $SmallFont;
  267. ::v-deep(textarea.el-textarea__inner) {
  268. height: 100%;
  269. }
  270. }
  271. }
  272. }
  273. }
  274. </style>