yink #17
							
								
								
									
										
											BIN
										
									
								
								src/assets/image/bofang1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/image/bofang1.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/image/yuyin1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/image/yuyin1.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB | 
| @ -15,7 +15,7 @@ const props = defineProps({ | |||||||
|   }, |   }, | ||||||
|   createdAt: { |   createdAt: { | ||||||
|     type: String, |     type: String, | ||||||
|     required: false   |     required: false | ||||||
|   }, |   }, | ||||||
|   modalTitle: { |   modalTitle: { | ||||||
|     type: String, |     type: String, | ||||||
| @ -50,13 +50,12 @@ const dropdown=ref({ | |||||||
|   options:[] as any, |   options:[] as any, | ||||||
|   item:{} as ITalkRecord, |   item:{} as ITalkRecord, | ||||||
| }) | }) | ||||||
| const onConvertText =async (data: ITalkRecord) => { | const onConvertText = async (data: ITalkRecord) => { | ||||||
|   data.is_convert_text = 1 |   data.is_convert_text = 1 | ||||||
|   const res = await voiceToText({msgId:data.msg_id,voiceUrl:data.extra.url}) |   const res = await voiceToText({ msgId: data.msg_id, voiceUrl: data.extra.url }) | ||||||
|   if(res.code == 200){ |   if (res.code == 200) { | ||||||
|     data.extra.content = res.data.convText |     data.extra.content = res.data.convText | ||||||
| 
 |   }else data.is_convert_text = 0 | ||||||
|   } |  | ||||||
| } | } | ||||||
| const onloseConvertText=(data: ITalkRecord)=>{ | const onloseConvertText=(data: ITalkRecord)=>{ | ||||||
|   data.is_convert_text = 0 |   data.is_convert_text = 0 | ||||||
| @ -87,7 +86,7 @@ const onContextMenu = (e:any,item: ITalkRecord) => { | |||||||
|   }else{ |   }else{ | ||||||
|     dropdown.value.options=[{ label: '转文字', key: 'convertText' }] |     dropdown.value.options=[{ label: '转文字', key: 'convertText' }] | ||||||
|   } |   } | ||||||
|    | 
 | ||||||
|   dropdown.value.item=item |   dropdown.value.item=item | ||||||
| } | } | ||||||
| </script> | </script> | ||||||
| @ -96,29 +95,29 @@ const onContextMenu = (e:any,item: ITalkRecord) => { | |||||||
|   <customModal   :closable="false" customCloseBtn v-model:show="isShow"   :title="title" style="width: 997px;background-color: #F9F9FD;"   :on-after-leave="onMaskClick"> |   <customModal   :closable="false" customCloseBtn v-model:show="isShow"   :title="title" style="width: 997px;background-color: #F9F9FD;"   :on-after-leave="onMaskClick"> | ||||||
|     <template #content> |     <template #content> | ||||||
|       <div class="main-box bg-#fff me-scrollbar me-scrollbar-thumb"> |       <div class="main-box bg-#fff me-scrollbar me-scrollbar-thumb"> | ||||||
|       <Loading v-if="items.length === 0" /> |         <Loading v-if="items.length === 0" /> | ||||||
| 
 | 
 | ||||||
|       <div v-for="item in items" :key="item.msg_id" class="message-item"> |         <div v-for="item in items" :key="item.msg_id" class="message-item"> | ||||||
|         <div class="left-box pointer" @click="showUserInfoModal(item.erp_user_id)"> |           <div class="left-box pointer" @click="showUserInfoModal(item.erp_user_id)"> | ||||||
|           <im-avatar :src="item.avatar" :size="38" :username="item.nickname" /> |             <im-avatar :src="item.avatar" :size="38" :username="item.nickname" /> | ||||||
|         </div> |  | ||||||
| 
 |  | ||||||
|         <div class="right-box"> |  | ||||||
|           <div class="msg-header"> |  | ||||||
|             <span class="name">{{ item.nickname }}</span> |  | ||||||
|             <span class="time"> {{ item.created_at }}</span> |  | ||||||
|           </div> |           </div> | ||||||
| 
 | 
 | ||||||
|           <component |           <div class="right-box"> | ||||||
|  |             <div class="msg-header"> | ||||||
|  |               <span class="name">{{ item.nickname }}</span> | ||||||
|  |               <span class="time"> {{ item.created_at }}</span> | ||||||
|  |             </div> | ||||||
|  | 
 | ||||||
|  |             <component | ||||||
|           @contextmenu.prevent="onContextMenu($event,item)" |           @contextmenu.prevent="onContextMenu($event,item)" | ||||||
|             :is="MessageComponents[item.msg_type] || 'unknown-message'" |               :is="MessageComponents[item.msg_type] || 'unknown-message'" | ||||||
|             :extra="item.extra" |               :extra="item.extra" | ||||||
|             :data="item" |               :data="item" | ||||||
|           /> |             /> | ||||||
|  |           </div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </div> |       <!-- 右键菜单 --> | ||||||
|      <!-- 右键菜单 --> |  | ||||||
|   <n-dropdown :show="dropdown.show" :x="dropdown.x" :y="dropdown.y" style="width: 142px;" :options="dropdown.options" |   <n-dropdown :show="dropdown.show" :x="dropdown.x" :y="dropdown.y" style="width: 142px;" :options="dropdown.options" | ||||||
|     @select="onContextMenuHandle" @clickoutside="closeDropdownMenu" /> |     @select="onContextMenuHandle" @clickoutside="closeDropdownMenu" /> | ||||||
|     </template> |     </template> | ||||||
|  | |||||||
| @ -1,7 +1,14 @@ | |||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { ref, reactive } from 'vue' | import { ref, reactive, computed } from 'vue' | ||||||
| import { PlayOne, PauseOne } from '@icon-park/vue-next' | import { PlayOne, PauseOne } from '@icon-park/vue-next' | ||||||
| import { ITalkRecordExtraAudio, ITalkRecord } from '@/types/chat' | import { ITalkRecordExtraAudio, ITalkRecord } from '@/types/chat' | ||||||
|  | import { onClickOutside } from '@vueuse/core' | ||||||
|  | import { useTemplateRef } from 'vue' | ||||||
|  | import yuyin from "@/assets/image/yuyin.png" | ||||||
|  | import yuyin1 from "@/assets/image/yuyin1.png" | ||||||
|  | import bofang from "@/assets/image/bofang.png" | ||||||
|  | import bofang1 from "@/assets/image/bofang1.png" | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
|   extra: ITalkRecordExtraAudio |   extra: ITalkRecordExtraAudio | ||||||
| @ -22,16 +29,23 @@ const state = reactive({ | |||||||
|   showText: false |   showText: false | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
|  | const target = useTemplateRef<HTMLElement>('target') | ||||||
| const onPlay = () => { | const onPlay = () => { | ||||||
|   if (state.isAudioPlay) { |   if (state.isAudioPlay) { | ||||||
|     audioRef.value.pause() |     audioRef.value.pause() | ||||||
|  |     state.isAudioPlay = false | ||||||
|   } else { |   } else { | ||||||
|     audioRef.value.play() |     audioRef.value.play() | ||||||
|  |     state.isAudioPlay = true | ||||||
|  |     onClickOutside(target, () => { | ||||||
|  |       console.log('点击了外部区域') | ||||||
|  |       audioRef.value.pause() // 关闭当前语音播放 | ||||||
|  |       state.isAudioPlay = false | ||||||
|  |     }) | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
|   state.isAudioPlay = !state.isAudioPlay |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| const onPlayEnd = () => { | const onPlayEnd = () => { | ||||||
|   state.isAudioPlay = false |   state.isAudioPlay = false | ||||||
|   state.progress = 0 |   state.progress = 0 | ||||||
| @ -70,10 +84,18 @@ const formatTime = (value: number = 0) => { | |||||||
| 
 | 
 | ||||||
|   return `${Math.floor(value)}"` |   return `${Math.floor(value)}"` | ||||||
| } | } | ||||||
|  | const right = computed(() => props.data.float === 'right') | ||||||
| </script> | </script> | ||||||
| <template> | <template> | ||||||
|   <div class="pointer w-200px bg-#F3F4FD rounded-10px px-11px"> |   <div | ||||||
|     <div class="im-message-audio h-44px"> |     class="pointer w-200px bg-#F3F4FD rounded-10px px-11px" | ||||||
|  |     :class="{ | ||||||
|  |       '!bg-[var(--im-message-right-bg-color)] !text-[var(--im-message-right-text-color)]': right | ||||||
|  |     }" | ||||||
|  |     @click.stop="onPlay" | ||||||
|  |     ref="target" | ||||||
|  |   > | ||||||
|  |     <div class="im-message-audio h-44px" :class="{ right }"> | ||||||
|       <aTrumpet :isPlay="false" color="black" :size="30"></aTrumpet> |       <aTrumpet :isPlay="false" color="black" :size="30"></aTrumpet> | ||||||
|       <audio |       <audio | ||||||
|         ref="audioRef" |         ref="audioRef" | ||||||
| @ -87,15 +109,16 @@ const formatTime = (value: number = 0) => { | |||||||
|       /> |       /> | ||||||
| 
 | 
 | ||||||
|       <div class="play"> |       <div class="play"> | ||||||
|         <div class="btn pointer" @click.stop="onPlay"> |         <div class="btn pointer"> | ||||||
|           <!-- <n-icon :size="18" :component="state.isAudioPlay ? PauseOne : PlayOne" /> --> |           <!-- <n-icon :size="18" :component="state.isAudioPlay ? PauseOne : PlayOne" /> --> | ||||||
|           <img |           <img | ||||||
|             v-if="!state.isAudioPlay" |             v-if="!state.isAudioPlay" | ||||||
|             src="@/assets/image/yuyin.png" |             :src="right ? yuyin1 : yuyin" | ||||||
|             class="w-[16px] h-[16px]" |             class="w-[16px] h-[16px]" | ||||||
|             alt="" |             alt="" | ||||||
|  |             :style="{ transform: right && 'rotate(180deg)' }" | ||||||
|           /> |           /> | ||||||
|           <img v-else src="@/assets/image/bofang.png" class="w-[16px] h-[16px]" alt="" /> |           <img v-else  :src="right ? bofang1 : bofang" class="w-[16px] h-[16px]" alt="" /> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|       <!-- <div class="desc"> |       <!-- <div class="desc"> | ||||||
| @ -129,6 +152,9 @@ const formatTime = (value: number = 0) => { | |||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| <style lang="less" scoped> | <style lang="less" scoped> | ||||||
|  | .right { | ||||||
|  |   flex-direction: row-reverse !important; | ||||||
|  | } | ||||||
| .im-message-audio { | .im-message-audio { | ||||||
|   --audio-bg-color: #f5f5f5; |   --audio-bg-color: #f5f5f5; | ||||||
|   --audio-btn-bg-color: #ffffff; |   --audio-btn-bg-color: #ffffff; | ||||||
|  | |||||||
| @ -170,7 +170,7 @@ if(newVal){ | |||||||
|         <div class="w-260px h-517px rounded-4px border-1px border-solid border-#E5E5E5 px-12px"> |         <div class="w-260px h-517px rounded-4px border-1px border-solid border-#E5E5E5 px-12px"> | ||||||
|           <div class="border-b-2px border-b-solid border-b-#FBFBFB h-35px flex items-center justify-end"> |           <div class="border-b-2px border-b-solid border-b-#FBFBFB h-35px flex items-center justify-end"> | ||||||
|             <n-button text color="#46299D" class="text-14px" @click="changeSelectType"> |             <n-button text color="#46299D" class="text-14px" @click="changeSelectType"> | ||||||
|               {{ selectType === 1 ? '多选' : '单选' }} |               {{ selectType === 1 ? '多选' : '取消多选' }} | ||||||
|             </n-button> |             </n-button> | ||||||
|           </div> |           </div> | ||||||
|           <div> |           <div> | ||||||
| @ -190,9 +190,10 @@ if(newVal){ | |||||||
|               :customStyle="{width:'42px',height:'42px'}"></avatarModule> |               :customStyle="{width:'42px',height:'42px'}"></avatarModule> | ||||||
|                     <!-- <n-image class="w-42px h-42px rounded-full" :src="item.avatar" /> --> |                     <!-- <n-image class="w-42px h-42px rounded-full" :src="item.avatar" /> --> | ||||||
|                   </div> |                   </div> | ||||||
|                   <div class="flex items-center"> |                   <div class="flex items-center overflow-hidden flex"> | ||||||
|                     <span class="text-ellipsis">{{ item.name }}</span> |                     <span class="text-ellipsis">{{ item.name }}</span> | ||||||
|                     <span v-if="item.type == 2" class="badge group ml-2">群</span> |                     <!-- <span v-if="item.type == 2" class="badge group ml-2">群</span> --> | ||||||
|  |                     <span v-if="item.talk_type === 2" class="flex-shrink-0">{{ `(${item.group_member_num})` }}</span> | ||||||
|                   </div> |                   </div> | ||||||
|                 </div> |                 </div> | ||||||
|               </template> |               </template> | ||||||
| @ -217,9 +218,10 @@ if(newVal){ | |||||||
|               :groupType="item.group_type" |               :groupType="item.group_type" | ||||||
|               :customStyle="{width:'42px',height:'42px'}"></avatarModule> |               :customStyle="{width:'42px',height:'42px'}"></avatarModule> | ||||||
|                     </div> |                     </div> | ||||||
|                     <div class="flex items-center"> |                     <div class="flex items-center overflow-hidden flex"> | ||||||
|                       <span class="text-ellipsis">{{ item.name }}</span> |                       <span class="text-ellipsis">{{ item.name }}</span> | ||||||
|                       <span v-if="item.type == 2" class="badge group ml-2">群</span> |                       <!-- <span v-if="item.type == 2" class="badge group ml-2">群</span> --> | ||||||
|  |                       <span v-if="item.talk_type === 2" class="flex-shrink-0">{{ `(${item.group_member_num})` }}</span> | ||||||
|                     </div> |                     </div> | ||||||
|                     <n-button class="ml-auto" text color="#C7C7C9" @click="onRemoveContact(item)"> |                     <n-button class="ml-auto" text color="#C7C7C9" @click="onRemoveContact(item)"> | ||||||
|                       <n-icon :component="CloseCircle" size="18" /> |                       <n-icon :component="CloseCircle" size="18" /> | ||||||
|  | |||||||
| @ -259,10 +259,16 @@ class Talk extends Base { | |||||||
|       updated_at: parseTime(new Date()) |       updated_at: parseTime(new Date()) | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|     if (this.talk_type == 1 && this.getAccountId() !== this.sender_id) { |     if (this.getAccountId() !== this.sender_id) { | ||||||
|       ServeClearTalkUnreadNum({ |       ServeClearTalkUnreadNum({ | ||||||
|         talk_type: 1, |         talk_type: this.talk_type, | ||||||
|         receiver_id: this.sender_id |         receiver_id: this.talk_type == 1 ? this.sender_id : this.receiver_id | ||||||
|  |       }).then(() => { | ||||||
|  |         useTalkStore().updateItem({ | ||||||
|  |           index_name: item.index_name, | ||||||
|  |           unread_num: 0, | ||||||
|  |           atsign_num: 0 | ||||||
|  |         }) | ||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -77,11 +77,11 @@ export function useSessionMenu() { | |||||||
|       //   key: 'delete_contact'
 |       //   key: 'delete_contact'
 | ||||||
|       // })
 |       // })
 | ||||||
|     } else { |     } else { | ||||||
|       options.push({ |       // options.push({
 | ||||||
|        |        | ||||||
|         label: '退出群聊', |       //   label: '退出群聊',
 | ||||||
|         key: 'signout_group' |       //   key: 'signout_group'
 | ||||||
|       }) |       // })
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     dropdown.options = [...options] |     dropdown.options = [...options] | ||||||
|  | |||||||
| @ -150,7 +150,7 @@ export const useUploadsStore = defineStore('uploads', { | |||||||
|         } |         } | ||||||
|       } catch (error) { |       } catch (error) { | ||||||
|         console.error("初始化分片上传失败:", error); |         console.error("初始化分片上传失败:", error); | ||||||
|         message.error("初始化上传失败,请重试") |         message.error("上传失败,请重试") | ||||||
|         this.handleUploadError(upload_id, clientUploadId) |         this.handleUploadError(upload_id, clientUploadId) | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -325,7 +325,7 @@ const onConvertText = async (data: ITalkRecord) => { | |||||||
|   const res = await voiceToText({ msgId: data.msg_id, voiceUrl: data.extra.url }) |   const res = await voiceToText({ msgId: data.msg_id, voiceUrl: data.extra.url }) | ||||||
|   if (res.code == 200) { |   if (res.code == 200) { | ||||||
|     data.extra.content = res.data.convText |     data.extra.content = res.data.convText | ||||||
|   } |   } else data.is_convert_text = 0 | ||||||
| } | } | ||||||
| const onloseConvertText = (data: ITalkRecord) => { | const onloseConvertText = (data: ITalkRecord) => { | ||||||
|   data.is_convert_text = 0 |   data.is_convert_text = 0 | ||||||
| @ -354,6 +354,10 @@ const onRowClick = (item: ITalkRecord) => { | |||||||
|     if (!isOneMonthBefore(item.created_at.split(' ')[0])) { |     if (!isOneMonthBefore(item.created_at.split(' ')[0])) { | ||||||
|       return useMessage.info('只支持转发近一个月内的消息') |       return useMessage.info('只支持转发近一个月内的消息') | ||||||
|     } |     } | ||||||
|  |     // 语音消息和群公告不支持转发 | ||||||
|  |     if ([4, 13].includes(item.msg_type)) { | ||||||
|  |       return useMessage.info('语音消息和群公告不支持转发') | ||||||
|  |     } | ||||||
|     console.log('item.msg_type', item.msg_type) |     console.log('item.msg_type', item.msg_type) | ||||||
|     if (ForwardableMessageType.includes(item.msg_type)) { |     if (ForwardableMessageType.includes(item.msg_type)) { | ||||||
|       item.isCheck = !item.isCheck |       item.isCheck = !item.isCheck | ||||||
| @ -435,12 +439,15 @@ const retry = (item: any) => { | |||||||
|   confirmBox({ |   confirmBox({ | ||||||
|     content: '确定重发吗' |     content: '确定重发吗' | ||||||
|   }).then(() => { |   }).then(() => { | ||||||
|  |     item.extra.percentage = 0 | ||||||
|     uploadsStore.retryCommonUpload(item.extra.upload_id) |     uploadsStore.retryCommonUpload(item.extra.upload_id) | ||||||
|   }) |   }) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const onContextMenuAvatar = (e: any, item: any) => { | const onContextMenuAvatar = (e: any, item: any) => { | ||||||
|   e.preventDefault() |   e.preventDefault() | ||||||
|  |   // 单聊不需要@ | ||||||
|  |   if (+props.talk_type === 1) return | ||||||
|   if (item.float !== 'right') { |   if (item.float !== 'right') { | ||||||
|     bus.emit(EditorConst.Mention, { |     bus.emit(EditorConst.Mention, { | ||||||
|       id: item.user_id, |       id: item.user_id, | ||||||
| @ -840,7 +847,7 @@ const onCustomSkipBottomEvent = () => { | |||||||
|             <!-- 近一个月外的消息多选框禁用 {{ item }} --> |             <!-- 近一个月外的消息多选框禁用 {{ item }} --> | ||||||
|             <n-checkbox |             <n-checkbox | ||||||
|               size="small" |               size="small" | ||||||
|               :disabled="!isOneMonthBefore(item.created_at.split(' ')[0])" |               :disabled="!isOneMonthBefore(item.created_at.split(' ')[0]) || [4, 13].includes(item.msg_type)" | ||||||
|               :checked="item.isCheck" |               :checked="item.isCheck" | ||||||
|               @update:checked="item.isCheck = !item.isCheck" |               @update:checked="item.isCheck = !item.isCheck" | ||||||
|             /> |             /> | ||||||
| @ -940,7 +947,13 @@ const onCustomSkipBottomEvent = () => { | |||||||
|             </div> |             </div> | ||||||
| 
 | 
 | ||||||
|             <!-- 已读回执 --> |             <!-- 已读回执 --> | ||||||
|             <div class="talk_read_num" v-if="item.user_id === props.uid"> |             <!-- item.user_id:发送这条消息的人id --> | ||||||
|  |             <!-- props.uid:当前登录人id --> | ||||||
|  |             <!-- props.receiver_id:当前消息要发送的人id --> | ||||||
|  |             <div | ||||||
|  |               class="talk_read_num" | ||||||
|  |               v-if="item.user_id === props.uid && props.uid !== props.receiver_id" | ||||||
|  |             > | ||||||
|               <span v-if="props.talk_type === 1">{{ |               <span v-if="props.talk_type === 1">{{ | ||||||
|                 item.read_total_num > 0 ? '已读' : '未读' |                 item.read_total_num > 0 ? '已读' : '未读' | ||||||
|               }}</span> |               }}</span> | ||||||
|  | |||||||
| @ -66,7 +66,7 @@ const onSendMessage = (data = {}, callBack: any) => { | |||||||
|       if (code == 200) { |       if (code == 200) { | ||||||
|         callBack(true) |         callBack(true) | ||||||
|       } else { |       } else { | ||||||
|         window['$message'].warning(message || msg) |         // window['$message'].warning(message || msg) | ||||||
|       } |       } | ||||||
|     }) |     }) | ||||||
|     .catch(() => { |     .catch(() => { | ||||||
|  | |||||||
| @ -71,11 +71,14 @@ export function useMenu() { | |||||||
|       dropdown.options.push({ label: '复制', key: 'copy' }) |       dropdown.options.push({ label: '复制', key: 'copy' }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (isOneMonthBefore(new Date(item.created_at.split(' ')[0]))) { |     if (isOneMonthBefore(new Date(item.created_at.split(' ')[0])) && ![4,13].includes(item.msg_type)) { | ||||||
|       // 根据时间判断只有近一个月内的消息才能支持多选
 |       // 根据时间判断只有近一个月内的消息才能支持多选  // 语音消息和群公告不支持转发
 | ||||||
|       dropdown.options.push({ label: '多选', key: 'multiSelect' }) |       dropdown.options.push({ label: '多选', key: 'multiSelect' }) | ||||||
|     } |     } | ||||||
|     dropdown.options.push({ label: '引用', key: 'quote' }) |     if (isOneMonthBefore(new Date(item.created_at.split(' ')[0]))) { | ||||||
|  |       // 根据时间判断只有近一个月内的消息才能支持引用
 | ||||||
|  |       dropdown.options.push({ label: '引用', key: 'quote' }) | ||||||
|  |     } | ||||||
|     if (canAddRevokeOption(uid, item, (dialogueStore.groupInfo as any).is_manager)) { |     if (canAddRevokeOption(uid, item, (dialogueStore.groupInfo as any).is_manager)) { | ||||||
|       dropdown.options.push({ label: '撤回', key: 'revoke' }); |       dropdown.options.push({ label: '撤回', key: 'revoke' }); | ||||||
|     } |     } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user