This commit is contained in:
2026-03-20 09:56:14 +03:00
parent bc704b03cd
commit 09028e0ced
10 changed files with 76 additions and 57 deletions

View File

@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { type Chat, useChatsStore } from '@/stores/chats.ts' import { type Chat, useChatsStore } from '@/stores/chats.ts'
import { computed } from 'vue' import { computed } from 'vue'
import { useMessagesStore } from '@/stores/messages.ts'
interface Props { interface Props {
chat: Chat chat: Chat
@@ -9,45 +10,36 @@ interface Props {
const { chat } = defineProps<Props>() const { chat } = defineProps<Props>()
const chatsStore = useChatsStore() const chatsStore = useChatsStore()
const chatName = computed(() => { const chatInfo = computed(() => {
return chatsStore.getChatInfo(chat).name return chatsStore.getChatInfo(chat)
}) })
const lastMessage = computed(() => { const convertDate = (dateString: string) => {
return chat.message?.message ?? '' const date = new Date(dateString)
})
const avatarText = computed(() => {
return chatName.value.slice(0, 1).toUpperCase()
})
const lastMessageCreatedAt = computed(() => {
if (!chat.message) return ''
const date = new Date(chat.message.createdAt)
return date.toLocaleTimeString('ru-RU', { timeStyle: 'short' }) return date.toLocaleTimeString('ru-RU', { timeStyle: 'short' })
}) }
</script> </script>
<template> <template>
<VListItem :value="chat.id"> <VListItem :value="chat.id">
<template v-slot:prepend> <template v-slot:prepend>
<VAvatar color="primary" :text="avatarText" /> <VAvatar color="primary" :text="chatInfo?.name.slice(0, 1).toUpperCase()" />
</template> </template>
<template #title> <template #title>
<div class="flex justify-between"> <div class="flex justify-between">
<div class="font-medium truncate"> <div class="font-medium truncate">
{{ chatName }} {{ chatInfo.name }}
</div> </div>
<div class="text-xs"> <div v-if="chat.message" class="text-xs">
{{ lastMessageCreatedAt }} {{ convertDate(chat.message.createdAt) }}
</div> </div>
</div> </div>
</template> </template>
<template #subtitle> <template v-if="chat.message" #subtitle>
<div class="flex justify-between"> <div class="flex justify-between">
<div>{{ lastMessage }}</div> <div>{{ chat.message.message }}</div>
<div class="text-xs"> <div class="text-xs">
<!-- <VChip v-show="true" size="small" text="0" />--> <!-- <VChip v-show="true" size="small" text="0" />-->
</div> </div>

View File

@@ -6,23 +6,23 @@ import { useChatsStore } from '@/stores/chats.ts'
const socketsStore = useSocketsStore() const socketsStore = useSocketsStore()
const chatsStore = useChatsStore() const chatsStore = useChatsStore()
const text = ref('') const message = ref('')
const sendMessage = () => { const sendMessage = () => {
if (text.value.trim()) { if (message.value && chatsStore.selectedChat) {
socketsStore.send({ socketsStore.send({
type: SocketDataReq.CREATE_MESSAGE, type: SocketDataReq.CREATE_MESSAGE,
data: { data: {
chat_id: chatsStore.selected, chatId: chatsStore.selectedChat.id,
text: text.value, message: message.value.trim().slice(0, 200),
}, },
}) })
} }
text.value = '' message.value = ''
} }
const isEmptyText = computed(() => { const isEmptyText = computed(() => {
return !text.value return !message.value
}) })
</script> </script>
@@ -32,7 +32,7 @@ const isEmptyText = computed(() => {
prepend-inner-icon="mdi-emoticon-outline" prepend-inner-icon="mdi-emoticon-outline"
append-inner-icon="mdi-paperclip" append-inner-icon="mdi-paperclip"
bg-color="white" bg-color="white"
v-model="text" v-model="message"
placeholder="message" placeholder="message"
density="default" density="default"
@keyup.enter="sendMessage" @keyup.enter="sendMessage"

View File

@@ -1,23 +1,27 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
const props = withDefaults( interface Props {
defineProps<{ createdAt?: string; username?: string; onRightSide?: boolean; message?: string }>(), createdAt?: string
{ username?: string
my: false, onRightSide?: boolean
message: 'foobar', message?: string
username: 'robot', }
},
)
const createdAt = computed(() => { const { message, username, createdAt } = withDefaults(defineProps<Props>(), {
return props.createdAt my: false,
? new Date(props.createdAt).toLocaleTimeString('ru-RU', { timeStyle: 'short' }) message: 'foobar',
username: 'robot',
})
const messageDate = computed(() => {
return createdAt
? new Date(createdAt).toLocaleTimeString('ru-RU', { timeStyle: 'short' })
: new Date().toLocaleTimeString('ru-RU', { timeStyle: 'short' }) : new Date().toLocaleTimeString('ru-RU', { timeStyle: 'short' })
}) })
const avatarLetter = computed(() => { const avatarLetter = computed(() => {
return props.username.slice(0, 1).toUpperCase() return username.slice(0, 1).toUpperCase()
}) })
</script> </script>
@@ -35,15 +39,15 @@ const avatarLetter = computed(() => {
<!-- </div>--> <!-- </div>-->
<!-- </div>--> <!-- </div>-->
<div class="flex gap-2" :class="{ 'flex-row-reverse': props.onRightSide }"> <div class="flex gap-2" :class="{ 'flex-row-reverse': onRightSide }">
<!-- <v-avatar size="36" color="deep-purple-lighten-4">--> <!-- <v-avatar size="36" color="deep-purple-lighten-4">-->
<!-- <span class="text-deep-purple-darken-2">{{ avatarLetter }}</span>--> <!-- <span class="text-deep-purple-darken-2">{{ avatarLetter }}</span>-->
<!-- </v-avatar>--> <!-- </v-avatar>-->
<!-- :class="props.my ? 'message-shaped-right' : 'message-shaped'"--> <!-- :class="props.my ? 'message-shaped-right' : 'message-shaped'"-->
<div class="pa-4" :class="props.onRightSide ? 'bg-blue-400' : 'bg-white'"> <div class="flex gap-1 pa-4" :class="onRightSide ? 'bg-blue-400' : 'bg-white'">
<span class="">{{ props.message }}</span> <span class="">{{ message }}</span>
<span class="">{{ createdAt }}</span> <span class="text-xs self-end">{{ messageDate }}</span>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
interface Props { interface Props {
name: string name?: string
image?: string image?: string
} }
@@ -9,7 +9,7 @@ const { name, image } = defineProps<Props>()
<template> <template>
<VToolbar theme="dark" class="px-2"> <VToolbar theme="dark" class="px-2">
<VAvatar text="A" color="primary" /> <VAvatar :text="name?.slice(0, 1).toUpperCase()" color="primary" />
<VToolbarTitle :text="name" /> <VToolbarTitle :text="name" />
</VToolbar> </VToolbar>
</template> </template>

View File

@@ -3,8 +3,10 @@ import { computed, nextTick, ref, useTemplateRef, watch } from 'vue'
import { useScroll } from '@vueuse/core' import { useScroll } from '@vueuse/core'
import type { User } from '@/stores/users.ts' import type { User } from '@/stores/users.ts'
import { useChatsStore } from '@/stores/chats.ts' import { useChatsStore } from '@/stores/chats.ts'
import { useMessagesStore } from '@/stores/messages.ts'
const chatsStore = useChatsStore() const chatsStore = useChatsStore()
const messagesStore = useMessagesStore()
// const area = useTemplateRef('messageArea') // const area = useTemplateRef('messageArea')
// const { y, arrivedState } = useScroll(area) // const { y, arrivedState } = useScroll(area)
@@ -16,16 +18,23 @@ const chatsStore = useChatsStore()
// if (area.value) y.value = area.value?.scrollHeight // if (area.value) y.value = area.value?.scrollHeight
// } // }
const chatInfo = computed(() => {
if (chatsStore.selectedChat) {
return chatsStore.getChatInfo(chatsStore.selectedChat)
}
return null
})
const messages = ref([]) const messages = ref([])
</script> </script>
<template> <template>
<div class="flex h-full flex-col overflow-hidden"> <div class="flex h-full flex-col overflow-hidden">
<div class="grow-0" v-if="chatsStore.selectedChat"> <div class="grow-0" v-if="chatsStore.selectedChat">
<slot name="toolbar" :toolBarData="chatsStore.getChatInfo(chatsStore.selectedChat)" /> <slot name="toolbar" :info="chatInfo" />
</div> </div>
<div class="px-8 gap-2 grow flex flex-col-reverse overflow-y-auto" ref="messageArea"> <div class="px-8 gap-2 grow flex flex-col-reverse overflow-y-auto" ref="messageArea">
<slot :messages="messages" /> <slot :messages="messagesStore.messages" />
</div> </div>
<div class="grow-0"> <div class="grow-0">
<slot name="input" /> <slot name="input" />

View File

@@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import MessagesForm from '@/components/Messages/MessagesForm.vue'
import MessageToolbar from '@/components/Messages/MessageToolbar.vue' import MessageToolbar from '@/components/Messages/MessageToolbar.vue'
import MessageInput from '@/components/Messages/MessageInput.vue' import MessageInput from '@/components/Messages/MessageInput.vue'
import MessagesList from '@/components/Messages/MessagesList.vue'
import { useChatsStore } from '@/stores/chats.ts' import { useChatsStore } from '@/stores/chats.ts'
import MessageItem from '@/components/Messages/MessageItem.vue'
import MessagesWrapper from '@/components/Messages/MessagesWrapper.vue'
const chatsStore = useChatsStore() const chatsStore = useChatsStore()
</script> </script>
@@ -11,17 +11,17 @@ const chatsStore = useChatsStore()
<template> <template>
<div class="h-full bg-gray-100"> <div class="h-full bg-gray-100">
<div class="flex flex-col h-full"> <div class="flex flex-col h-full">
<MessagesForm v-if="chatsStore.selected.length"> <MessagesWrapper v-if="chatsStore.selectedChat">
<template #toolbar="{ toolBarData }"> <template #toolbar="{ info }">
<MessageToolbar :name="toolBarData.name" /> <MessageToolbar v-bind="info" />
</template> </template>
<template #default="{ messages }"> <template #default="{ messages }">
<MessagesList :messages /> <MessageItem v-for="message in messages" :key="message.id" v-bind="message" />
</template> </template>
<template #input> <template #input>
<MessageInput /> <MessageInput />
</template> </template>
</MessagesForm> </MessagesWrapper>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -10,6 +10,7 @@ export interface Chat {
name: string name: string
users: User[] users: User[]
message?: Message message?: Message
image?: string
} }
export const useChatsStore = defineStore('chats', () => { export const useChatsStore = defineStore('chats', () => {
@@ -18,6 +19,8 @@ export const useChatsStore = defineStore('chats', () => {
const selected = ref<string[]>([]) const selected = ref<string[]>([])
const selectedChat = computed(() => { const selectedChat = computed(() => {
if (!selected.value.length) return
return chats.value.find((chat: Chat) => chat.id === selected.value[0]) return chats.value.find((chat: Chat) => chat.id === selected.value[0])
}) })
@@ -35,5 +38,9 @@ export const useChatsStore = defineStore('chats', () => {
} }
} }
function getChatLastMessage(chat: Chat) {
return chats.value.find((el) => el.id === chat.id)
}
return { chats, selected, selectedChat, getChatInfo } return { chats, selected, selectedChat, getChatInfo }
}) })

View File

@@ -1,5 +1,6 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref } from 'vue' import { computed, ref } from 'vue'
import { useChatsStore } from '@/stores/chats.ts'
export interface Message { export interface Message {
id: number id: number
@@ -9,7 +10,13 @@ export interface Message {
} }
export const useMessagesStore = defineStore('messages', () => { export const useMessagesStore = defineStore('messages', () => {
const chatsStore = useChatsStore()
const messages = ref<Message[]>([]) const messages = ref<Message[]>([])
const message = ref<Message>()
return { messages } const lastMessage = computed(() => {
return message.value ?? chatsStore.selectedChat?.message ?? null
})
return { messages, lastMessage }
}) })

View File

@@ -76,7 +76,6 @@ export const useSocketsStore = defineStore('sockets', () => {
const idx = chatsStore.chats.findIndex((chat) => chat.id === data.id) const idx = chatsStore.chats.findIndex((chat) => chat.id === data.id)
if (idx < 0) chatsStore.chats.push(data) if (idx < 0) chatsStore.chats.push(data)
console.log(data.id)
menuStore.selected = ['chats'] menuStore.selected = ['chats']
chatsStore.selected = [data.id] chatsStore.selected = [data.id]
} }

View File

@@ -5,6 +5,7 @@ export interface User {
id: number id: number
email: string email: string
name: string name: string
image?: string
} }
export const useUsersStore = defineStore('users', () => { export const useUsersStore = defineStore('users', () => {