เพิ่ม Socket mark-read , notify-message
This commit is contained in:
parent
5b081878c4
commit
081c1e0589
6 changed files with 191 additions and 78 deletions
|
|
@ -4,7 +4,11 @@ export const supportIssue = `${env.API_SUPPORT_URI}/issue`;
|
|||
export const supportMessage = (id: string) =>
|
||||
`${env.API_SUPPORT_URI}/issue/${id}/message`;
|
||||
|
||||
export const supportMessageStatus = (id: string) =>
|
||||
`${env.API_SUPPORT_URI}/message-status?issueId=${id}`;
|
||||
|
||||
export default {
|
||||
supportIssue,
|
||||
supportMessage,
|
||||
supportMessageStatus,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@ import { useSupportStore } from "@/modules/00_support/store/Main.ts";
|
|||
import MessageChat from "@/modules/00_support/components/MessageChat.vue";
|
||||
|
||||
const store = useSupportStore();
|
||||
|
||||
const content = ref<string>("");
|
||||
const scrollContainerRef = ref();
|
||||
const readStatus = ref<boolean>(false);
|
||||
const readStatus = ref<boolean>(true);
|
||||
|
||||
onUpdated(() => {
|
||||
nextTick(() => {
|
||||
|
|
@ -43,75 +42,77 @@ onMounted(async () => {
|
|||
</q-avatar>
|
||||
|
||||
<q-item-section class="q-pl-sm">
|
||||
<q-item-label>{{ currentTitle }}</q-item-label>
|
||||
<!-- <q-item-label caption>
|
||||
<q-icon name="noise_control_off" color="positive" />
|
||||
ออนไลน์
|
||||
</q-item-label> -->
|
||||
<q-item-label>{{ store.currentTitle }}</q-item-label>
|
||||
</q-item-section>
|
||||
<q-space />
|
||||
|
||||
<!-- <q-btn
|
||||
flat
|
||||
round
|
||||
dense
|
||||
icon="o_videocam"
|
||||
class="q-mr-xs"
|
||||
text-color="primary"
|
||||
/> -->
|
||||
<q-btn flat round dense icon="o_info" text-color="grey" />
|
||||
</q-toolbar>
|
||||
</div>
|
||||
<div class="i3 bg-white">
|
||||
<q-list class="text-primary">
|
||||
<div v-for="data in store.issue?.result">
|
||||
<q-item
|
||||
clickable
|
||||
v-ripple
|
||||
:active="currentIssue === data.id"
|
||||
@click="
|
||||
() => {
|
||||
<q-scroll-area ref="scrollContainerRef" style="height: 400px; width: 1fr">
|
||||
<q-list class="text-primary">
|
||||
<div v-for="data in store.issue?.result">
|
||||
<q-item
|
||||
clickable
|
||||
v-ripple
|
||||
:active="store.currentIssue === data.id"
|
||||
@click="
|
||||
store.currentIssue = data.id;
|
||||
store.currentTitle = data.title;
|
||||
store.fetchMessage(data.id);
|
||||
}
|
||||
"
|
||||
active-class="my-menu-link"
|
||||
>
|
||||
<q-avatar>
|
||||
<img src="https://cdn.quasar.dev/img/avatar1.jpg" />
|
||||
</q-avatar>
|
||||
store.fetchMessageStatus(data.id);
|
||||
store.issue.result = store.issue.result.map((v) => {
|
||||
if (v.id === data.id) {
|
||||
v.unreadCount = 0;
|
||||
}
|
||||
return v;
|
||||
});
|
||||
"
|
||||
active-class="my-menu-link"
|
||||
>
|
||||
<q-avatar>
|
||||
<img src="https://cdn.quasar.dev/img/avatar1.jpg" />
|
||||
</q-avatar>
|
||||
|
||||
<q-item-section class="q-pl-sm">
|
||||
<q-item-label>{{ data.title }}</q-item-label>
|
||||
<q-item-section class="q-pl-sm">
|
||||
<q-item-label>{{ data.title }}</q-item-label>
|
||||
|
||||
<q-item-label class="flex" caption>
|
||||
{{ data.lastMessage }}
|
||||
|
||||
<q-space />
|
||||
<q-toggle v-model="readStatus" label="test" />
|
||||
<q-badge
|
||||
v-if="readStatus"
|
||||
class=""
|
||||
rounded
|
||||
color="red"
|
||||
label="1"
|
||||
/>
|
||||
<q-icon v-else size="18px" name="done_all" />
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-separator inset />
|
||||
</div>
|
||||
</q-list>
|
||||
<q-item-label class="flex" caption>
|
||||
{{ data.lastMessage }}
|
||||
<q-space />
|
||||
<q-icon
|
||||
v-if="data.lastMessage?.length === 0"
|
||||
color="green"
|
||||
size="18px"
|
||||
name="mdi-send"
|
||||
/>
|
||||
<div v-else>
|
||||
<q-badge
|
||||
v-if="data.unreadCount > 0"
|
||||
class=""
|
||||
rounded
|
||||
color="red"
|
||||
:label="data.unreadCount"
|
||||
/>
|
||||
<q-icon v-else size="18px" name="done_all" />
|
||||
</div>
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-separator inset />
|
||||
</div>
|
||||
</q-list>
|
||||
</q-scroll-area>
|
||||
</div>
|
||||
|
||||
<div class="i4 bg-grey-3">
|
||||
<!-- <div class="i4 bg-grey-3 q-pa-sm"> -->
|
||||
<q-scroll-area ref="scrollContainerRef" style="height: 400px; width: 1fr">
|
||||
<message-chat class="q-pr-md" />
|
||||
<message-chat v-if="store.currentIssue" class="q-pr-md" />
|
||||
</q-scroll-area>
|
||||
</div>
|
||||
|
||||
<div class="grid-manage bg-white"></div>
|
||||
|
||||
<div class="i5 bg-white container-input align-center">
|
||||
<div class="input-file">
|
||||
<q-btn flat>
|
||||
|
|
@ -166,7 +167,7 @@ onMounted(async () => {
|
|||
grid-template-areas:
|
||||
"search toolbar"
|
||||
"menu chat"
|
||||
"menu input";
|
||||
"manage input";
|
||||
grid-template-rows: 60px 400px 60px;
|
||||
grid-template-columns: 400px 1fr;
|
||||
}
|
||||
|
|
@ -206,6 +207,10 @@ onMounted(async () => {
|
|||
.i5 {
|
||||
grid-area: input;
|
||||
}
|
||||
|
||||
.grid-manage {
|
||||
grid-area: manage;
|
||||
}
|
||||
.container-1 {
|
||||
display: grid;
|
||||
/* grid-template-columns: 1fr 2fr; */
|
||||
|
|
|
|||
|
|
@ -7,21 +7,59 @@ import { useSupportStore } from "@/modules/00_support/store/Main";
|
|||
const store = useSupportStore();
|
||||
|
||||
onMounted(() => {});
|
||||
|
||||
function onLoad() {
|
||||
console.log("ทำงานเเล้ว");
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="bg-grey-3 q-pb-sm">
|
||||
<q-chat-message label="Sunday, 19th" />
|
||||
<q-chat-message
|
||||
v-for="(data, index) in store.message?.result.message"
|
||||
:key="index"
|
||||
:id="data.id"
|
||||
avatar="https://cdn.quasar.dev/img/avatar4.jpg"
|
||||
:text="[data.content]"
|
||||
:bg-color="data.fromUserId === store.userId ? 'primary' : 'white'"
|
||||
:text-color="data.fromUserId === store.userId ? 'white' : 'black'"
|
||||
:sent="data.fromUserId === store.userId"
|
||||
:stamp="moment(data.createdAt).fromNow()"
|
||||
/>
|
||||
<q-infinite-scroll @load="onLoad" reverse>
|
||||
<template v-slot:loading>
|
||||
<div class="row justify-center q-my-md">
|
||||
<q-spinner color="primary" name="dots" size="40px" />
|
||||
</div>
|
||||
</template>
|
||||
<q-chat-message label="Sunday, 19th" />
|
||||
<div v-for="(data, index) in store.message?.result.message">
|
||||
<q-item-section class="q-pl-sm">
|
||||
<q-item-label>
|
||||
<q-chat-message
|
||||
:key="index"
|
||||
:id="data.id"
|
||||
avatar="https://cdn.quasar.dev/img/avatar4.jpg"
|
||||
:text="[data.content]"
|
||||
:bg-color="data.fromUserId === store.userId ? 'primary' : 'white'"
|
||||
:text-color="data.fromUserId === store.userId ? 'white' : 'black'"
|
||||
:sent="data.fromUserId === store.userId"
|
||||
:stamp="moment(data.createdAt).fromNow()"
|
||||
/>
|
||||
</q-item-label>
|
||||
|
||||
<q-item-label
|
||||
v-if="data.fromUserId === store.userId"
|
||||
class="flex"
|
||||
caption
|
||||
>
|
||||
<!-- <q-space v-if="data.fromUserId === store.userId" /> -->
|
||||
<q-space />
|
||||
<div
|
||||
v-if="
|
||||
store.messageStatus.result.some(
|
||||
(v) =>
|
||||
new Date(v.lastAccessDate).getTime() >=
|
||||
new Date(data.createdAt).getTime() &&
|
||||
index === store.message?.result.message.length - 1
|
||||
)
|
||||
"
|
||||
>
|
||||
อ่านเเล้ว
|
||||
</div>
|
||||
<!-- <q-toggle v-model="readStatus" label="test" /> -->
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</div>
|
||||
</q-infinite-scroll>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,14 @@
|
|||
export interface SupportMessageStatus {
|
||||
result: SupportResult[];
|
||||
}
|
||||
|
||||
export interface SupportResult {
|
||||
fromUserId: string;
|
||||
fromUserName: string;
|
||||
lastAccessDate: string;
|
||||
issueId: string;
|
||||
}
|
||||
|
||||
export interface SupportStatusUser {
|
||||
socketId: string;
|
||||
userId: string;
|
||||
|
|
@ -59,4 +70,9 @@ export interface SupportIssueMessage {
|
|||
issueId: string;
|
||||
}
|
||||
|
||||
export type { SupportMessageResponse, SupportIssueResponse, SupportStatusUser };
|
||||
export type {
|
||||
SupportMessageStatus,
|
||||
SupportMessageResponse,
|
||||
SupportIssueResponse,
|
||||
SupportStatusUser,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
import { ref, onMounted } from "vue";
|
||||
import { io } from "socket.io-client";
|
||||
import http from "@/plugins/http";
|
||||
import config from "@/app.config";
|
||||
|
|
@ -17,6 +17,7 @@ export const useSupportStore = defineStore("supportServiceStore", () => {
|
|||
const userId = ref<string>(keycloak.subject);
|
||||
const issue = ref<SupportIssueResponse>();
|
||||
const message = ref<SupportMessageResponse>();
|
||||
const messageStatus = ref<SupportMessageStatus>();
|
||||
const statusUser = ref<SupportStatusUser>([]);
|
||||
const currentIssue = ref<string>("");
|
||||
const currentTitle = ref<string>("");
|
||||
|
|
@ -24,32 +25,56 @@ export const useSupportStore = defineStore("supportServiceStore", () => {
|
|||
const scrollContainer = ref();
|
||||
|
||||
function scrollToEnd() {
|
||||
scrollContainer.value.setScrollPosition("vertical", 10000);
|
||||
scrollContainer.value.setScrollPosition("vertical", 1000000);
|
||||
}
|
||||
|
||||
const socket = io("http://192.168.1.10:3000/", {
|
||||
auth: { token: keycloak.token },
|
||||
autoConnect: false,
|
||||
});
|
||||
|
||||
socket.on("users", (users) => {
|
||||
// console.log(users);
|
||||
socket.on("users", (data: SupportStatusUser[]) => {
|
||||
statusUser.value = data;
|
||||
});
|
||||
|
||||
socket.on("online", (r) => {
|
||||
console.log(r);
|
||||
statusUser.value.push({
|
||||
socketId: r.socketId,
|
||||
userId: r.userId,
|
||||
name: r.name,
|
||||
role: r.role,
|
||||
});
|
||||
});
|
||||
|
||||
console.log(statusUser.value);
|
||||
socket.on("offline", (socketId: string) => {
|
||||
if (socketId === socket.id) return;
|
||||
statusUser.value = statusUser.value.filter((v) => v.socketId !== socketId);
|
||||
});
|
||||
|
||||
// console.log(JSON.stringify(r, null, -2));
|
||||
socket.on("notify-message", (r) => {
|
||||
issue.value.result = issue.value.result.map((v) => {
|
||||
if (v.id === r.issueId) {
|
||||
v.unreadCount++;
|
||||
v.lastMessage = r.content;
|
||||
}
|
||||
return v;
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("read", (r) => {
|
||||
setTimeout(() => {
|
||||
messageStatus.value.result = messageStatus.value.result.map((v) => {
|
||||
if (v.issueId === r.issueId) {
|
||||
v.lastAccessDate = r.lastAccessDate;
|
||||
}
|
||||
return v;
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
|
||||
socket.on("message", (r) => {
|
||||
console.log(r);
|
||||
|
||||
message.value.result.message.push({
|
||||
id: r.id,
|
||||
fromUserId: r.fromUserId,
|
||||
|
|
@ -60,17 +85,29 @@ export const useSupportStore = defineStore("supportServiceStore", () => {
|
|||
read: r.read,
|
||||
issueId: r.issueId,
|
||||
});
|
||||
socket.emit("mark-read", { currentIssue });
|
||||
socket.emit("mark-read", { issueId: currentIssue.value });
|
||||
scrollToEnd();
|
||||
});
|
||||
|
||||
function sendMessage(content: string, to: string) {
|
||||
socket.emit("message", { to, content });
|
||||
|
||||
setTimeout(() => {
|
||||
scrollToEnd();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
async function fetchMessageStatus(issueId: string) {
|
||||
const res = await http
|
||||
.get(config.API.supportMessageStatus(issueId))
|
||||
.catch((err) => {
|
||||
messageError($q, err);
|
||||
})
|
||||
.finally(() => {});
|
||||
if (res && res.data) {
|
||||
messageStatus.value = res.data;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchMessage(issueId: string) {
|
||||
showLoader();
|
||||
const res = await http
|
||||
|
|
@ -106,7 +143,6 @@ export const useSupportStore = defineStore("supportServiceStore", () => {
|
|||
|
||||
if (res && res.data) {
|
||||
issue.value = res.data;
|
||||
// console.log(JSON.stringify(res.data, null, -2));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,11 +152,14 @@ export const useSupportStore = defineStore("supportServiceStore", () => {
|
|||
message,
|
||||
fetchIssue,
|
||||
fetchMessage,
|
||||
fetchMessageStatus,
|
||||
sendMessage,
|
||||
items,
|
||||
scrollToEnd,
|
||||
scrollContainer,
|
||||
currentIssue,
|
||||
currentTitle,
|
||||
socket,
|
||||
messageStatus,
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
import FormChat from "@/modules/00_support/components/FormChat.vue";
|
||||
import { useSupportStore } from "@/modules/00_support/store/Main.ts";
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
const store = useSupportStore();
|
||||
|
||||
onMounted(async () => {
|
||||
store.socket.connect();
|
||||
});
|
||||
|
||||
onUnmounted(async () => {
|
||||
store.socket.disconnect();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue