123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 |
- <template>
- <div class="flex radius-base overflow-hidden message-list-modal">
- <template v-if="!!replyUserId">
- <slot></slot>
- </template>
- <div class="flex direction-column p-base fill-lighter tree-box">
- <el-input v-model="filterText" placeholder="输入评卷员账号或名称筛选" clearable></el-input>
- <div class="flex-1 m-t-base scroll-y-auto">
- <el-tree
- ref="treeRef"
- show-checkbox
- node-key="id"
- :filter-node-method="filterTree"
- :data="markerTree"
- :props="treeProp"
- @check-change="onCheckChange"
- ></el-tree>
- </div>
- </div>
- <div class="message-info-container" :class="{ big: !showCheckUser && !replyUserId }">
- <div class="flex direction-column message-info">
- <div class="flex items-center p-base message-info-header">
- <div class="flex flex-1 items-center overflow-hidden send-user">
- <span class="m-r-mini">收件人</span>
- <span class="flex flex-1 overflow-hidden items-center justify-between radius-base m-r-base user-name">
- <p class="flex-1 checked-users flex" :class="{ big: showCheckUser }">
- <span class="split-names">{{ viewCheckedUser }}</span>
- <span v-if="checkedUsers.length > 3">共计{{ checkedUsers.length }}个人员</span>
- </p>
- <!-- <el-button v-if="!replyUserId" 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')">
- <el-icon><close /></el-icon>
- </div>
- </div>
- <div class="flex-1 p-base overflow-hidden">
- <div class="full-h radius-base p-base message-info-content">
- <div style="height: calc(100% - 18px)" class="scroll-y-auto">
- <content-edit-able
- v-model="messageContent"
- class="full content-edit-able"
- @click="onContentClick"
- ></content-edit-able>
- </div>
- <div class="limit-tip">{{ messageContent.length }} / 2000</div>
- </div>
- </div>
- <div class="p-base flex items-center justify-end">
- <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>
- <el-button v-if="checkedUsers.length == 1" size="small" plain @click="toggleHistory">历史消息</el-button>
- </div>
- </div>
- <message-history
- v-if="showHistory"
- :send-user-id="checkedUsers.length == 1 ? checkedUsers[0].id : undefined"
- ></message-history>
- </div>
- </div>
- <image-preview
- v-model="previewModalVisible"
- resize-key="can-resize22"
- :url="props.paperPath || ''"
- :is-big="true"
- ></image-preview>
- </template>
- <script setup lang="tsx" name="MessageSend">
- 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'
- import ImagePreview from '../ImagePreview.vue'
- import useFetch from '@/hooks/useFetch'
- import useVModel from '@/hooks/useVModel'
- import MessageHistory from '@/components/shared/message/MessageHistory.vue'
- import { transHtmlContent } from '@/utils/common'
- import type { ExtractApiResponse } from '@/api/api'
- type MarkerItem = ExtractArrayValue<ExtractApiResponse<'getUserGroup'>['chiffGroup']>
- type TreeNode = ExtractArrayValue<ExtractApiResponse<'getUserGroup'>['markerGroup']> & { label: string }
- const props = defineProps<{
- replyUserId?: number | null
- paperPath?: string | null
- replyUserName?: string | null
- }>()
- const customNodeClass = (data: any, node: any) => {
- if (data.online === true) {
- return 'is-online'
- } else if (data.online === false) {
- return 'is-offline'
- }
- return null
- }
- /** cleat warning */
- const emit = defineEmits(['close', 'change-type', 'reply'])
- const showHistory = ref<boolean>(false)
- /** 默认的收件人ID */
- const replyUserId = useVModel(props, 'replyUserId')
- const replyUserName = useVModel(props, 'replyUserName')
- /** 图片预览 */
- const previewModalVisible = ref<boolean>(false)
- /** 显示收件人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?.filter((v) => !!v.id)?.length) ||
- (replyUserId.value && messageContent.value)
- )
- })
- const treeRef = ref<InstanceType<typeof ElTree>>()
- const viewCheckedUser = computed(() => {
- let arr = checkedUsers?.value?.filter((v) => !!v.id)?.map((d) => `${d.loginName}-${d.name}`)
- if (!!replyUserName.value && !arr.includes(replyUserName.value)) {
- arr.unshift(replyUserName.value)
- }
- return arr.join(';')
- // return checkedUsers?.value
- // ?.filter((v) => !!v.id)
- // ?.map((d) => `${d.loginName}-${d.name}`)
- // ?.join(';')
- })
- const toggleCheckUser = () => {
- showCheckUser.value = !showCheckUser.value
- }
- function isMarker(x: any): x is MarkerItem {
- return !!x.loginName
- }
- const treeProp = {
- children: 'markers',
- label(treeNode: TreeNode) {
- if (isMarker(treeNode)) {
- // return treeNode.name || treeNode.loginName
- return `${treeNode.loginName}-${treeNode.name}`
- }
- return treeNode.label
- },
- class: customNodeClass,
- } as unknown as InstanceType<typeof ElTree>['props']
- const filterTree = ((value: string, data: MarkerItem) => {
- if (!value) return true
- 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[]>(() => {
- if (!userGroup?.value) {
- return []
- }
- const { chiffGroup, deputyGroup, markerGroup } = userGroup.value
- return [
- chiffGroup?.length ? { label: '大组长', markingGroupNumber: -1, markers: chiffGroup } : null,
- deputyGroup?.length ? { label: '小组长', markingGroupNumber: -1, markers: deputyGroup } : null,
- markerGroup?.length
- ? { label: '评卷小组', markers: markerGroup.map((d) => ({ ...d, label: `第${d.markingGroupNumber}组` })) }
- : null,
- ].filter(Boolean) as TreeNode[]
- })
- watch(
- [replyUserId, markerTree],
- () => {
- nextTick(() => {
- replyUserId.value && markerTree.value.length && treeRef?.value?.setCheckedKeys([replyUserId.value])
- })
- },
- { immediate: true }
- )
- const onCheckChange = () => {
- checkedUsers.value = (treeRef?.value?.getCheckedNodes(true) as MarkerItem[]).filter((v) => !!v.id) || []
- if (checkedUsers.value.length != 1) {
- showHistory.value = false
- }
- }
- watch(messageContent, () => {
- // let reg = /<span class=\"pointer inline link-button\" contenteditable=\"false\" data-path=.*>查看试卷<\/span>/g
- // let arr = messageContent.value.match(reg)
- // let text = messageContent.value.replace(reg, '【查看试卷临时替换字符】')
- // text = transHtmlContent(text)
- // if (arr && arr.length) {
- // text = text.replace('【查看试卷临时替换字符】', arr[0])
- // }
- // messageContent.value = text
- 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
- const path = target.getAttribute('data-path')
- if (path) {
- previewModalVisible.value = true
- }
- }
- /** 发送当前试卷 */
- const sendCurrentPaper = () => {
- if (props.paperPath) {
- messageContent.value += `<span class="pointer inline link-button" contenteditable="false" data-path="${props.paperPath}">查看试卷</span>`
- }
- }
- const { fetch: sendMessage, loading } = useFetch('sendMessage')
- /** 发送消息 */
- const onSendMessage = async () => {
- try {
- if (messageContent.value.length > 2000) {
- return ElMessage.error('输入内容过长')
- }
- let ids: any = checkedUsers.value?.filter(Boolean)?.map((u) => u.id)
- if (replyUserId?.value && ids.indexOf(replyUserId?.value) == -1) {
- ids.unshift(replyUserId.value)
- }
- await sendMessage({
- content: messageContent.value,
- receiveUserIds: ids,
- })
- ElMessage.success('发送成功')
- messageContent.value = ''
- // emit('close')
- } catch (error) {
- console.error(error)
- }
- }
- getUserGroup()
- const toggleHistory = () => {
- showHistory.value = !showHistory.value
- }
- </script>
- <style scoped lang="scss">
- .message-list-modal {
- background-color: transparent;
- width: 750px;
- .tree-box {
- width: 260px;
- height: 446px;
- ::v-deep(.el-tree) {
- min-height: 100%;
- .is-online,
- .is-offline {
- .el-tree-node__label {
- padding-left: 14px;
- position: relative;
- &:before {
- content: '';
- display: block;
- position: absolute;
- left: 0;
- top: 5px;
- width: 8px;
- height: 8px;
- border-radius: 4px;
- background-color: #67c23a;
- z-index: 10;
- }
- }
- }
- .is-offline {
- .el-tree-node__label {
- &:before {
- background-color: #ccc !important;
- }
- }
- }
- }
- }
- // .message-list {
- // width: 260px;
- // height: 446px;
- // background: #fafafa;
- // box-shadow: 0px 6px 6px 0px rgba(0, 0, 0, 0.1);
- // }
- .message-info-container {
- // width: 600px;
- // flex: 1;
- width: calc(100% - 260px);
- &.big {
- width: 750px;
- }
- .message-info {
- height: 446px;
- background-color: $color--white;
- .message-info-header {
- border-bottom: $OnePixelLine;
- .send-user {
- font-size: $SmallFont;
- color: $RegularFontColor;
- .user-name {
- min-width: 320px;
- padding: 10px 12px;
- border: $OnePixelLine;
- .checked-users {
- // overflow: hidden;
- // white-space: nowrap;
- display: flex;
- width: 500px;
- &.big {
- width: 240px;
- }
- .split-names {
- flex: 1;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
- }
- }
- }
- .close-icon {
- width: 20px;
- height: 20px;
- place-items: center;
- font-size: 18px;
- color: $RegularFontColor;
- &:hover {
- color: $NormalColor;
- }
- }
- }
- .message-info-content {
- border: 1px solid $color--primary;
- font-size: $SmallFont;
- ::v-deep(textarea.el-textarea__inner) {
- height: 100%;
- }
- .limit-tip {
- text-align: right;
- height: 18px;
- padding-top: 3px;
- }
- }
- }
- }
- }
- </style>
|