This commit is contained in:
net 2024-02-16 16:20:18 +07:00 committed by Net
parent f25a439804
commit 0a98d96d38
9 changed files with 6862 additions and 9 deletions

View file

@ -0,0 +1,184 @@
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { useSupportStore } from "@/modules/00_support/store/Main.ts";
import MessageChat from "@/modules/00_support/components/MessageChat.vue";
const store = useSupportStore();
const currentIssue = ref<string>("");
const content = ref<string>("");
onMounted(async () => {
store.fetchIssue();
});
</script>
<template>
<p class="text-h6 text-weight-medium">ถาม - ตอบ</p>
<div class="container">
<div class="i1 bg-white align-center">
<q-toolbar>
<q-item-section>
<q-input dense rounded outlined label="ค้นหาข้อความ...">
<template v-slot:prepend>
<q-icon name="search" />
</template>
</q-input>
</q-item-section>
</q-toolbar>
</div>
<div class="i2 bg-white align-center">
<q-toolbar>
<q-avatar>
<img src="https://cdn.quasar.dev/img/avatar3.jpg" />
</q-avatar>
<q-item-section class="q-pl-sm">
<q-item-label>มล คงสวสด</q-item-label>
<q-item-label caption>
<q-icon name="noise_control_off" color="positive" />
ออนไลน
</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="
() => {
currentIssue = data.id;
store.fetchMessage(data.id);
}
"
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-label caption>
{{ data.lastMessage }}
</q-item-label>
</q-item-section>
</q-item>
<q-separator inset />
</div>
</q-list>
</div>
<div class="i4 bg-grey-3 q-pa-sm" style="overflow-y: auto">
<!-- <div class="i4 bg-grey-3 q-pa-sm"> -->
<message-chat v-if="currentIssue" />
</div>
<div class="i5 bg-white container-input align-center">
<div class="input-file">
<q-btn flat>
<q-icon name="attach_file" style="transform: rotate(-125deg)" />
</q-btn>
</div>
<div class="input-chat">
<q-input
dense
class="q-pa-xs"
standout="bg-teal text-white"
label="Aa"
v-model="content"
/>
</div>
<div class="btn-chat">
<q-btn
@click="
() => {
store.sendMessage(content, currentIssue);
}
"
flat
color="primary"
label="ส่งข้อความ"
/>
</div>
</div>
</div>
</template>
<style scoped>
.align-center {
display: flex;
align-items: center;
justify-content: center;
}
.container {
display: grid;
grid-template-areas:
"search toolbar"
"menu chat"
"menu input";
grid-template-rows: 60px 400px 60px;
grid-template-columns: 400px 1fr;
}
.container-input {
display: grid;
grid-template-areas: "file input-chat btn";
grid-template-rows: 1fr;
grid-template-columns: 60px 1fr 100px;
}
.input-file {
grid-area: file;
}
.input-chat {
grid-area: input-chat;
}
.btn-chat {
grid-area: btn;
}
.i1 {
grid-area: search;
}
.i2 {
grid-area: toolbar;
}
.i3 {
grid-area: menu;
}
.i4 {
grid-area: chat;
}
.i5 {
grid-area: input;
}
.container-1 {
display: grid;
/* grid-template-columns: 1fr 2fr; */
grid: 50px 450px / 1fr 2fr;
}
.container-2 {
display: grid;
/* grid-template-columns: 1fr 2fr; */
grid: 50px 450px / 1fr 2fr;
}
</style>

View file

@ -0,0 +1,29 @@
<script setup lang="ts">
import "moment/dist/locale/th";
import moment from "moment";
import { useSupportStore } from "@/modules/00_support/store/Main";
import { ref, onMounted } from "vue";
const store = useSupportStore();
moment.locale("th");
console.log(moment.locale());
</script>
<template>
<q-infinite-scroll reverse>
<div class="bg-grey-3 q-pb-sm">
<q-chat-message label="Sunday, 19th" />
<q-chat-message
v-if="store.message"
v-for="data in store.message?.result.message.reverse()"
: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()"
/>
</div>
</q-infinite-scroll>
</template>
<style scoped></style>

View file

@ -1,8 +1,54 @@
interface DataOption {
export interface SupportIssueResponse {
result: SupportIssue[];
page: number;
pageSize: number;
total: number;
}
export interface SupportIssue {
id: string;
createdByUserId: string;
createdByUserName: string;
createdAt: string;
updatedAt: string;
title: string;
status: string;
category: SupportIssueCategory;
unreadCount: number;
}
export interface SupportIssueCategory {
id: string;
name: string;
}
export type {
DataOption,
};
export interface SupportMessageResponse {
result: SupportIssueWithMessage;
page: number;
pageSize: number;
total: number;
}
export interface SupportIssueWithMessage {
id: string;
createdByUserId: string;
createdByUserName: string;
createdAt: string;
updatedAt: string;
title: string;
status: string;
message: SupportIssueMessage[];
}
export interface SupportIssueMessage {
id: string;
fromUserId: string;
fromUserName: string;
createdAt: string;
updatedAt: string;
content: string;
read: boolean;
issueId: string;
}
export type { SupportMessageResponse, SupportIssueResponse };

View file

@ -1,10 +1,86 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { io } from "socket.io-client";
import http from "@/plugins/http";
import config from "@/app.config";
import { useCounterMixin } from "@/stores/mixin";
import type {
SupportMessageResponse,
SupportIssueResponse,
} from "@/modules/00_support/interface/index/Main";
import keycloak from "@/plugins/keycloak";
export const useEvalutuonStore = defineStore("supportServiceStore", () => {
const index = ref<number>(0);
export const useSupportStore = defineStore("supportServiceStore", () => {
const socket = io("http://192.168.1.10:3000/", {
auth: { token: keycloak.token },
});
socket.on("users", (users) => {
// console.log(users);
});
socket.on("message", (r) => {
console.log(r);
});
const { showLoader, hideLoader } = useCounterMixin();
const userId = ref<string>(keycloak.subject);
const issue = ref<SupportIssueResponse>();
const message = ref<SupportMessageResponse>();
const items = ref<string>([{}, {}, {}, {}, {}, {}, {}]);
const scrollTargetRef = ref();
function sendMessage(content: string, to: string) {
console.log(content);
console.log(to);
socket.emit("message", { to, content });
}
async function fetchMessage(issueId: string) {
showLoader();
const res = await http
.get(config.API.supportMessage(issueId))
.catch((err) => {
messageError($q, err);
})
.finally(() => {
hideLoader();
});
if (res && res.data) {
message.value = res.data;
console.log(res.data);
socket.emit("join-issue", { issueId });
socket.emit("mark-read", { issueId });
}
}
async function fetchIssue() {
showLoader();
const res = await http
.get(config.API.supportIssue)
.catch((err) => {
messageError($q, err);
})
.finally(() => {
hideLoader();
});
if (res && res.data) {
issue.value = res.data;
console.log(JSON.stringify(res.data, null, -2));
}
}
return {
index,
userId,
issue,
message,
fetchIssue,
fetchMessage,
sendMessage,
scrollTargetRef,
items,
};
});

View file

@ -1,7 +1,9 @@
<script setup lang="ts"></script>
<script setup lang="ts">
import FormChat from "@/modules/00_support/components/FormChat.vue";
</script>
<template>
<div>support user</div>
<form-Chat />
</template>
<style scoped></style>