fix: 聊天功能(部分),图片发送,视频发送,音频发送已完成,好友列表,聊天记录以对接
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
This commit is contained in:
@@ -1,129 +1,14 @@
|
||||
<template>
|
||||
<div class="flex items-start gap-3 mb-4" :class="isSent ? 'flex-row-reverse' : 'flex-row'">
|
||||
<!-- 头像 -->
|
||||
<div
|
||||
class="w-10 h-10 rounded-full flex items-center justify-center text-white font-bold text-sm flex-shrink-0 cursor-pointer"
|
||||
:style="avatarStyle"
|
||||
@click="showUserProfile"
|
||||
>
|
||||
{{ senderInfo.avatar }}
|
||||
</div>
|
||||
|
||||
<!-- 消息内容区域 -->
|
||||
<div class="flex flex-col max-w-xs md:max-w-md lg:max-w-lg xl:max-w-xl"
|
||||
:class="isSent ? 'items-end' : 'items-start'">
|
||||
<!-- 发送者名称 -->
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mb-1 px-2">
|
||||
{{ senderInfo.nick_name }}
|
||||
</div>
|
||||
|
||||
<!-- 消息气泡 -->
|
||||
<div
|
||||
class="rounded-2xl p-4 shadow-sm relative"
|
||||
:class="bubbleClasses"
|
||||
>
|
||||
<!-- 图片消息 -->
|
||||
<div
|
||||
v-if="message.type === 'image'"
|
||||
class="cursor-pointer relative"
|
||||
@click="handlePreviewMedia(message.content, 'image')"
|
||||
>
|
||||
<img
|
||||
:src="message.content"
|
||||
:alt="'图片消息'"
|
||||
class="rounded-lg max-w-full"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
<div
|
||||
class="absolute inset-0 flex items-center justify-center opacity-0 hover:opacity-100 transition-opacity bg-black/50 rounded-lg">
|
||||
<i class="fas fa-search-plus text-white text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视频消息 -->
|
||||
<div
|
||||
v-else-if="message.type === 'video'"
|
||||
class="cursor-pointer relative video-message"
|
||||
@click="handlePreviewMedia(message.content, 'video')"
|
||||
>
|
||||
<video
|
||||
:src="message.content"
|
||||
class="rounded-lg max-w-full video-thumbnail"
|
||||
preload="metadata"
|
||||
@error="handleVideoError"
|
||||
/>
|
||||
<div class="video-overlay">
|
||||
<div class="play-button">
|
||||
<i class="fas fa-play"></i>
|
||||
</div>
|
||||
<div class="video-duration" v-if="message.duration">
|
||||
{{ formatDuration(message.duration) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- URL链接消息 -->
|
||||
<div v-else-if="message.type === 'url'" class="url-message">
|
||||
<div class="url-preview" @click="openUrl(message.url)">
|
||||
<div class="url-favicon">
|
||||
<img :src="message.favicon" :alt="message.domain" v-if="message.favicon"/>
|
||||
<i class="fas fa-link" v-else></i>
|
||||
</div>
|
||||
<div class="url-content">
|
||||
<div class="url-title">{{ message.title || message.url }}</div>
|
||||
<div class="url-description" v-if="message.description">
|
||||
{{ message.description }}
|
||||
</div>
|
||||
<div class="url-domain">{{ message.domain }}</div>
|
||||
</div>
|
||||
<div class="url-thumbnail" v-if="message.thumbnail">
|
||||
<img :src="message.thumbnail" :alt="message.title"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="url-text" v-if="message.text">
|
||||
{{ message.text }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 音频消息 -->
|
||||
<AudioMessage v-else-if="message.type === 'audio'" :message="message" :is-sent="isSent"/>
|
||||
|
||||
<!-- 通话消息 -->
|
||||
<div v-else-if="message.type === 'video-call' || message.type === 'voice-call'"
|
||||
class="call-message">
|
||||
<div class="call-info">
|
||||
<i class="fas" :class="message.type === 'video-call' ? 'fa-video' : 'fa-phone'"></i>
|
||||
<span>{{ getCallStatusText(message.callStatus) }}</span>
|
||||
</div>
|
||||
<div class="call-duration" v-if="message.duration">
|
||||
通话时长: {{ formatDuration(message.duration) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文本消息 -->
|
||||
<div v-else class="text-base leading-relaxed whitespace-pre-wrap">
|
||||
<span v-html="formatTextWithLinks(message.content)"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 消息时间和状态 -->
|
||||
<div class="flex items-center gap-1 mt-1 text-xs text-gray-400 dark:text-gray-500 px-2">
|
||||
<span>{{ message.time }}</span>
|
||||
<i v-if="isSent && message.read" class="fas fa-check-double text-green-500"></i>
|
||||
<i v-else-if="isSent && !message.read" class="fas fa-clock text-gray-400"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {computed} from 'vue';
|
||||
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import {Avatar, Image} from 'ant-design-vue';
|
||||
|
||||
import previewMedia from '../composables/useMediaPreview.ts';
|
||||
import {useThemeStore} from '../stores/theme.ts';
|
||||
import AudioMessage from './AudioMessage.vue';
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
const props = defineProps({
|
||||
message: {
|
||||
type: Object,
|
||||
@@ -141,8 +26,12 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['show-user-profile']);
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
const avatarStyle = computed(() => ({
|
||||
background: props.senderInfo.color
|
||||
background: props.senderInfo.color,
|
||||
}));
|
||||
|
||||
const bubbleClasses = computed(() => {
|
||||
@@ -176,8 +65,11 @@ const formatDuration = (seconds) => {
|
||||
};
|
||||
|
||||
const formatTextWithLinks = (text) => {
|
||||
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
||||
return text.replace(urlRegex, '<a href="$1" target="_blank" rel="noopener noreferrer" class="text-link">$1</a>');
|
||||
const urlRegex = /(https?:\/\/\S+)/g;
|
||||
return text.replaceAll(
|
||||
urlRegex,
|
||||
'<a href="$1" target="_blank" rel="noopener noreferrer" class="text-link">$1</a>',
|
||||
);
|
||||
};
|
||||
|
||||
const openUrl = (url) => {
|
||||
@@ -186,11 +78,11 @@ const openUrl = (url) => {
|
||||
|
||||
const getCallStatusText = (status) => {
|
||||
const statusMap = {
|
||||
'invite': '发起通话',
|
||||
'accepted': '通话已接通',
|
||||
'rejected': '通话被拒绝',
|
||||
'ended': '通话已结束',
|
||||
'missed': '未接通话'
|
||||
invite: '发起通话',
|
||||
accepted: '通话已接通',
|
||||
rejected: '通话被拒绝',
|
||||
ended: '通话已结束',
|
||||
missed: '未接通话',
|
||||
};
|
||||
return statusMap[status] || '通话消息';
|
||||
};
|
||||
@@ -204,6 +96,163 @@ const handleVideoError = (event) => {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="isSent ? 'flex-row-reverse' : 'flex-row'"
|
||||
class="mb-4 flex items-start gap-3"
|
||||
>
|
||||
<!-- 头像 -->
|
||||
<!-- <div-->
|
||||
<!-- :style="avatarStyle"-->
|
||||
<!-- class="w-10 h-10 rounded-full flex items-center justify-center text-white font-bold text-sm flex-shrink-0 cursor-pointer"-->
|
||||
<!-- @click="showUserProfile"-->
|
||||
<!-- >-->
|
||||
<div
|
||||
v-if="isSent"
|
||||
:style="avatarStyle"
|
||||
class="rounded-full cursor-pointer"
|
||||
@click="showUserProfile"
|
||||
>
|
||||
<Avatar v-if="userStore.userInfo?.avatar" :size="48" :src="userStore.userInfo?.avatar" />
|
||||
<Avatar v-else :size="48">{{ userStore.userInfo?.nick_name.charAt(0) }}</Avatar>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
:style="avatarStyle"
|
||||
class="rounded-full cursor-pointer"
|
||||
@click="showUserProfile"
|
||||
>
|
||||
<Avatar v-if="senderInfo.avatar" :size="48" :src="senderInfo.avatar" />
|
||||
<Avatar v-else :size="48">{{ senderInfo.nick_name.charAt(0) }}</Avatar>
|
||||
</div>
|
||||
|
||||
<!-- 消息内容区域 -->
|
||||
<div
|
||||
:class="isSent ? 'items-end' : 'items-start'"
|
||||
class="flex flex-col max-w-xs md:max-w-md lg:max-w-lg xl:max-w-xl"
|
||||
>
|
||||
<!-- 发送者名称 -->
|
||||
<div v-if="isSent" class="mb-1 px-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ userStore.userInfo?.nick_name }}
|
||||
</div>
|
||||
<div v-else class="mb-1 px-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ senderInfo.nick_name }}
|
||||
</div>
|
||||
|
||||
<!-- 消息气泡 -->
|
||||
<div :class="bubbleClasses" class="relative rounded-2xl p-4 shadow-sm">
|
||||
<!-- 图片消息 -->
|
||||
<div
|
||||
v-if="message.type === 'image'"
|
||||
class="relative cursor-pointer"
|
||||
@click="handlePreviewMedia(message.content, 'image')"
|
||||
>
|
||||
<Image
|
||||
:src="message.content"
|
||||
alt="图片消息"
|
||||
class="rounded-lg max-w-full"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 视频消息 -->
|
||||
<div
|
||||
v-else-if="message.type === 'video'"
|
||||
class="video-message relative cursor-pointer"
|
||||
@click="handlePreviewMedia(message.content, 'video')"
|
||||
>
|
||||
<video
|
||||
:src="message.content"
|
||||
class="rounded-lg max-w-full video-thumbnail"
|
||||
preload="metadata"
|
||||
@error="handleVideoError"
|
||||
></video>
|
||||
<div class="video-overlay">
|
||||
<div class="play-button">
|
||||
<i class="fas fa-play"></i>
|
||||
</div>
|
||||
<div v-if="message.duration" class="video-duration">
|
||||
{{ formatDuration(message.duration) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- URL链接消息 -->
|
||||
<div v-else-if="message.type === 'url'" class="url-message">
|
||||
<div class="url-preview" @click="openUrl(message.url)">
|
||||
<div class="url-favicon">
|
||||
<img
|
||||
v-if="message.favicon"
|
||||
:alt="message.domain"
|
||||
:src="message.favicon"
|
||||
/>
|
||||
<i v-else class="fas fa-link"></i>
|
||||
</div>
|
||||
<div class="url-content">
|
||||
<div class="url-title">{{ message.title || message.url }}</div>
|
||||
<div v-if="message.description" class="url-description">
|
||||
{{ message.description }}
|
||||
</div>
|
||||
<div class="url-domain">{{ message.domain }}</div>
|
||||
</div>
|
||||
<div v-if="message.thumbnail" class="url-thumbnail">
|
||||
<img :alt="message.title" :src="message.thumbnail" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="message.text" class="url-text">
|
||||
{{ message.text }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 音频消息 -->
|
||||
<AudioMessage
|
||||
v-else-if="message.type === 'audio'"
|
||||
:is-sent="isSent"
|
||||
:message="message"
|
||||
/>
|
||||
|
||||
<!-- 通话消息 -->
|
||||
<div
|
||||
v-else-if="message.type === 'video-call' || message.type === 'voice-call'"
|
||||
"
|
||||
class="call-message"
|
||||
>
|
||||
<div class="call-info">
|
||||
<i
|
||||
:class="message.type === 'video-call' ? 'fa-video' : 'fa-phone'"
|
||||
class="fas"
|
||||
></i>
|
||||
<span>{{ getCallStatusText(message.callStatus) }}</span>
|
||||
</div>
|
||||
<div v-if="message.duration" class="call-duration">
|
||||
通话时长: {{ formatDuration(message.duration) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文本消息 -->
|
||||
<div v-else class="whitespace-pre-wrap text-base leading-relaxed">
|
||||
<span v-html="formatTextWithLinks(message.content)"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 消息时间和状态 -->
|
||||
<div
|
||||
class="mt-1 flex items-center gap-1 px-2 text-xs text-gray-400 dark:text-gray-500"
|
||||
>
|
||||
<span>{{ message.time }}</span>
|
||||
<i
|
||||
v-if="isSent && message.read"
|
||||
class="fas fa-check-double text-green-500"
|
||||
></i>
|
||||
<i
|
||||
v-else-if="isSent && !message.read"
|
||||
class="fas fa-clock text-gray-400"
|
||||
></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.message-bubble {
|
||||
position: relative;
|
||||
@@ -249,7 +298,11 @@ const handleVideoError = (event) => {
|
||||
.video-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(to bottom, transparent 0%, rgba(0, 0, 0, 0.3) 100%);
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent 0%,
|
||||
rgba(0, 0, 0, 0.3) 100%
|
||||
);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
Reference in New Issue
Block a user