Skip to content

Commit

Permalink
feat: 支持粘贴图片,修复更改个人名称和修改徽章,消息列表个人信息未更新,支持图片消息复制。
Browse files Browse the repository at this point in the history
  • Loading branch information
Evansy committed Jul 7, 2023
1 parent 2336abc commit 219c388
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 17 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ module.exports = {
'@typescript-eslint/no-explicit-any': 0, // 允许any类型
'no-param-reassign': 0, // 允许修改函数参数
'prefer-regex-literals': 0, // 允许使用new RegExp
'no-unused-vars': 2, // 禁止未使用的变量
},
}
25 changes: 19 additions & 6 deletions src/components/UserSettingBox/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useRequest } from 'alova'
import { ElMessage } from 'element-plus'
import { Select, CloseBold, EditPen } from '@element-plus/icons-vue'
import { useUserStore } from '@/stores/user'
import { useCachedStore } from '@/stores/cached'
import { SexEnum, IsYetEnum } from '@/enums'
import type { BadgeType } from '@/services/types'
import apis from '@/services/apis'
Expand All @@ -30,6 +31,7 @@ const editName = reactive({
})
const userStore = useUserStore()
const cachedStore = useCachedStore()
const userInfo = computed(() => userStore.userInfo)
const { send: handlerGetBadgeList, data: badgeList } = useRequest(apis.getBadgeList, {
Expand All @@ -47,12 +49,21 @@ const currentBadge = computed(() =>
badgeList.value.find((item) => item.obtain === IsYetEnum.YES && item.wearing === IsYetEnum.YES),
)
// 更新缓存里面的用户信息
const updateCurrentUserCache = (key: 'name' | 'wearingItemId', value: any) => {
const currentUser = userStore.userInfo.uid && cachedStore.userCachedList[userStore.userInfo.uid]
if (currentUser) {
currentUser[key] = value // 更新缓存里面的用户信息
}
}
// 佩戴卸下徽章
const toggleWarningBadge = async (badge: BadgeType) => {
if (!badge?.id) return
await apis.setUserBadge(badge.id).send()
handlerGetBadgeList()
badge.img && (userInfo.value.badge = badge.img)
updateCurrentUserCache('wearingItemId', badge.id) // 更新缓存里面的用户徽章
}
// 编辑用户名
Expand All @@ -73,13 +84,15 @@ const onSaveUserName = async () => {
return
}
editName.saving = true
await apis.modifyUserName(editName.tempName).send()
userStore.userInfo.name = editName.tempName
editName.saving = false
editName.isEdit = false
editName.tempName = ''
await apis.modifyUserName(editName.tempName).send() // 更改用户名
userStore.userInfo.name = editName.tempName // 更新用户信息里面的用户名
updateCurrentUserCache('name', editName.tempName) // 更新缓存里面的用户信息
// 重置状态
onCancelEditName()
// 没有更名机会就不走下去
if (!userInfo.value?.modifyNameChance || userInfo.value.modifyNameChance === 0) return
userInfo.value.modifyNameChance = userInfo.value?.modifyNameChance - 1
userInfo.value.modifyNameChance = userInfo.value?.modifyNameChance - 1 // 减少更名次数
}
// 确认保存用户名
const onCancelEditName = async () => {
Expand Down
27 changes: 27 additions & 0 deletions src/utils/copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,30 @@ export const copyToClip = (text: string) => {
}
})
}

export const handleCopyImg = (imgUrl: string) => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const img = new Image()
img.crossOrigin = 'Anonymous'
img.src = imgUrl
img.onload = () => {
if (!ctx) return
canvas.width = img.width
canvas.height = img.height
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
ctx.drawImage(img, 0, 0) // 将canvas转为blob
canvas.toBlob(async (blob) => {
if (!blob) return
const data = [new ClipboardItem({ [blob.type]: blob })] // https://w3c.github.io/clipboard-apis/#dom-clipboard-write
try {
await navigator.clipboard.write(data)
// console.log('Copied to clipboard successfully!')
} catch (error) {
// console.error('Unable to write to clipboard.')
} finally {
canvas.remove()
}
})
}
}
10 changes: 7 additions & 3 deletions src/views/Home/components/ChatBox/MsgInput/index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
// 艾特功能参考自 https://github.com/MrHGJ/at-mentions
import { ref, reactive, toRefs, watch, watchEffect, type StyleValue, inject, provide } from 'vue'
import { ref, reactive, toRefs, watch, watchEffect, type StyleValue, inject } from 'vue'
import type { IMention, INode } from './types'
import type { CacheUserItem } from '@/services/types'
import { NodeType } from './types'
Expand All @@ -14,6 +14,7 @@ import {
import { useCachedStore } from '@/stores/cached'
import VirtualList from '@/components/VirtualList'
import MentionItem from './item.vue'
import PasteImageDialog from '../PasteImageDialog/index.vue'
// 关闭透传 attrs 到组件根节点,传递到子节点 v-bind="$attrs"
defineOptions({ inheritAttrs: false })
Expand Down Expand Up @@ -353,7 +354,6 @@ const onInputKeyUp = (e: KeyboardEvent) => {
const handleArrow = (direction: 'up' | 'down') => {
if (!scrollRef.value) return
console.log(scrollRef.value.getOffset(), scrollRef.value.getClientSize())
let newIndex = 0
if (direction === 'up') {
Expand Down Expand Up @@ -518,6 +518,8 @@ const onSelectPerson = (uid: number, ignore = false) => {
// 暴露 ref 属性
defineExpose({ input: editorRef, range: editorRange, onSelectPerson })
const getKey = (item: CacheUserItem) => item.uid
</script>

<template>
Expand Down Expand Up @@ -553,11 +555,13 @@ defineExpose({ input: editorRef, range: editorRange, onSelectPerson })
dataPropName="item"
:itemProps="{ activeIndex, onSelect: onSelectPerson }"
:data="personList"
data-key="uid"
:data-key="getKey"
:item="MentionItem"
:size="20"
/>
</div>
<PasteImageDialog />
</div>
</template>
Expand Down
2 changes: 0 additions & 2 deletions src/views/Home/components/ChatBox/MsgInput/item.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ const props = defineProps({
onSelect: Function,
})
const emit = defineEmits(['select'])
const onClick = () => {
props.onSelect?.(props.item)
}
Expand Down
71 changes: 71 additions & 0 deletions src/views/Home/components/ChatBox/PasteImageDialog/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<script setup lang="ts">
import { ref, watchEffect, inject } from 'vue'
import { useEventListener } from '@vueuse/core'
import { MsgEnum } from '@/enums'
import { useUserStore } from '@/stores/user'
const imageBody = ref({ url: '' })
const pasteFile = ref<File>() // 记录input文本内容
const visible = ref(false)
const userStore = useUserStore()
const onChangeMsgType = inject<(msgType: MsgEnum) => void>('onChangeMsgType')
const onChangeFile = inject<(file: File[]) => void>('onChangeFile')
useEventListener(window, 'paste', (e) => {
e.preventDefault()
if (!userStore.isSign) return false // 未登录不支持粘贴图片交互
if (e.clipboardData && e.clipboardData.files?.length) {
const file = e.clipboardData.files[0]
// TODO 可支持粘贴文件。
if (file?.type.includes('image')) {
pasteFile.value = file
}
}
return false
})
watchEffect(() => {
if (pasteFile?.value) {
visible.value = true
imageBody.value = {
url: URL.createObjectURL(pasteFile?.value),
}
} else {
visible.value = false
}
})
const onSend = async () => {
if (!pasteFile?.value) return
// FIXME 如下逻辑可以尝试抽为 hook
onChangeMsgType?.(MsgEnum.IMAGE) // 设置上传类型为图片
await onChangeFile?.([pasteFile?.value]) // 上传文件并发送消息
visible.value = false // 关闭弹窗
URL.revokeObjectURL(imageBody.value.url)
pasteFile.value = undefined
imageBody.value = {
url: '',
}
}
</script>

<template>
<ElDialog
class="image-paste-modal"
title="粘贴图片"
v-model="visible"
:close-on-click-modal="false"
center
>
<img v-if="imageBody.url" :src="imageBody.url" />

<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="onSend"> 发送 </el-button>
</span>
</template>
</ElDialog>
</template>

<style lang="scss" src="./styles.scss" />
19 changes: 19 additions & 0 deletions src/views/Home/components/ChatBox/PasteImageDialog/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.image-paste-modal {
display: flex;
flex-direction: column;
width: 600px;
max-height: 80vh;
overflow: hidden;
text-align: center;

.el-dialog__body {
flex: 1;
max-height: 100%;
overflow-y: auto;
}

img {
max-width: 100%;
max-height: 100%;
}
}
14 changes: 12 additions & 2 deletions src/views/Home/components/ChatBox/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const onSelectPerson = (uid: number, ignoreCheck?: boolean) => {
provide('focusMsgInput', focusMsgInput)
provide('onSelectPerson', onSelectPerson)
// 发送消息
const send = (msgType: MsgEnum, body: any, roomId = 1) => {
apis
.sendMsg({ roomId, msgType, body })
Expand Down Expand Up @@ -160,17 +161,26 @@ const openFileSelect = (fileType: string) => {
open()
}
onChange((files) => {
const selectAndUploadFile = async (files?: FileList | null) => {
if (!files?.length) return
const file = files[0]
if (nowMsgType.value === MsgEnum.IMAGE) {
if (!file.type.includes('image')) {
return ElMessage.error('请选择图片文件')
}
}
uploadFile(file)
await uploadFile(file)
}
// 选中文件上传并发送消息
provide('onChangeFile', selectAndUploadFile)
// 设置消息类型
provide('onChangeMsgType', (msgType: MsgEnum) => {
nowMsgType.value = msgType
})
onChange(selectAndUploadFile)
onStart(() => {
if (!fileInfo.value) return
Expand Down
21 changes: 17 additions & 4 deletions src/views/Home/components/ChatList/ContextMenu/index.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, type PropType, inject } from 'vue'
import { ElMessage } from 'element-plus'
import apis from '@/services/apis'
import {
ContextMenu,
Expand All @@ -8,7 +9,7 @@ import {
type MenuOptions,
} from '@imengyu/vue3-context-menu'
import { useUserStore } from '@/stores/user'
import { copyToClip } from '@/utils/copy'
import { copyToClip, handleCopyImg } from '@/utils/copy'
import { useChatStore } from '@/stores/chat'
import type { MessageType } from '@/services/types'
import { MsgEnum, PowerEnum } from '@/enums'
Expand Down Expand Up @@ -52,8 +53,16 @@ const onBlockUser = async () => {
// 拷贝内容-(此版本未针对不同Body体进行处理)
const copyContent = () => {
const content = props.msg.message.body?.content
copyToClip(content)
const msg = props.msg.message
if (msg.type === MsgEnum.TEXT) {
const content = msg.body?.content
copyToClip(content)
ElMessage.success('复制成功~')
}
if (msg.type === MsgEnum.IMAGE) {
handleCopyImg(msg.body.url)
ElMessage.success('复制成功~')
}
}
// 下载
Expand Down Expand Up @@ -83,7 +92,11 @@ const onDelete = () => chatStore.deleteMsg(props.msg.message.id)
<ContextMenuItem label="艾特Ta" @click="onAtUser?.(msg.fromUser.uid, true)" v-login-show>
<template #icon> <span class="icon">@</span> </template>
</ContextMenuItem>
<ContextMenuItem v-if="msg.message.type === MsgEnum.TEXT" label="复制" @click="copyContent">
<ContextMenuItem
v-if="msg.message.type === MsgEnum.TEXT || msg.message.type === MsgEnum.IMAGE"
label="复制"
@click="copyContent"
>
<template #icon>
<Icon icon="copy" :size="13" />
</template>
Expand Down

0 comments on commit 219c388

Please sign in to comment.