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