feat: 联系人
parent
e5b90372a6
commit
5c1bb25237
|
@ -1,7 +1,7 @@
|
||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
|
|
||||||
export interface DeptVO {
|
export interface DeptVO {
|
||||||
id?: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
parentId: number
|
parentId: number
|
||||||
status: number
|
status: number
|
||||||
|
@ -10,6 +10,7 @@ export interface DeptVO {
|
||||||
phone: string
|
phone: string
|
||||||
email: string
|
email: string
|
||||||
createTime: Date
|
createTime: Date
|
||||||
|
children?: DeptVO[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询部门(精简)列表
|
// 查询部门(精简)列表
|
||||||
|
|
|
@ -27,6 +27,15 @@ export const getAllUser = () => {
|
||||||
return request.get({ url: '/system/user/all' })
|
return request.get({ url: '/system/user/all' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取部门成员
|
||||||
|
* @param id
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getDeptUser = (id: number) => {
|
||||||
|
return request.get({ url: '/system/user/listByDept?id='+ id })
|
||||||
|
}
|
||||||
|
|
||||||
// 查询用户详情
|
// 查询用户详情
|
||||||
export const getUser = (id: number) => {
|
export const getUser = (id: number) => {
|
||||||
return request.get({ url: '/system/user/get?id=' + id })
|
return request.get({ url: '/system/user/get?id=' + id })
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||||
import { ConversationModelType } from '@/views/chat/types/types'
|
import { ConversationModelType } from '@/views/chat/types/types'
|
||||||
import { openDB, DBSchema, IDBPDatabase } from 'idb'
|
import { openDB, DBSchema, IDBPDatabase } from 'idb'
|
||||||
|
|
||||||
|
@ -14,7 +15,9 @@ let dbPromise: Promise<IDBPDatabase<MyDB>>
|
||||||
export const initDB = () => {
|
export const initDB = () => {
|
||||||
if (!dbPromise) {
|
if (!dbPromise) {
|
||||||
try {
|
try {
|
||||||
dbPromise = openDB<MyDB>('yudao-im-indexeddb', 1, {
|
const { wsCache } = useCache()
|
||||||
|
const user = wsCache.get(CACHE_KEY.USER).user
|
||||||
|
dbPromise = openDB<MyDB>('yudao-im-indexeddb-' + user.id, 1, {
|
||||||
upgrade(db) {
|
upgrade(db) {
|
||||||
db.createObjectStore('Conversations', { keyPath: 'conversationNo' })
|
db.createObjectStore('Conversations', { keyPath: 'conversationNo' })
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="flex h-full flex-1">
|
<view class="flex h-full flex-1">
|
||||||
<ToolSection @menu-select-change="toolMenuSelectChange" />
|
<ToolSection @menu-select-change="toolMenuSelectChange" />
|
||||||
<Session v-if="bussinessType === MENU_LIST_ENUM.CONVERSATION" />
|
<Session v-if="chatStore.bussinessType === MENU_LIST_ENUM.CONVERSATION" />
|
||||||
<Friends v-if="bussinessType === MENU_LIST_ENUM.FRIENDS" />
|
<view class="flex">
|
||||||
<view v-if="bussinessType === MENU_LIST_ENUM.CONVERSATION" class="flex w-full flex-col">
|
<Department v-if="chatStore.bussinessType === MENU_LIST_ENUM.FRIENDS" />
|
||||||
|
<Friends v-if="chatStore.bussinessType === MENU_LIST_ENUM.FRIENDS" />
|
||||||
|
</view>
|
||||||
|
<view v-if="chatStore.bussinessType === MENU_LIST_ENUM.CONVERSATION" class="flex w-full flex-col">
|
||||||
<ChatHeader />
|
<ChatHeader />
|
||||||
<ChatMessage />
|
<ChatMessage />
|
||||||
<InputSection />
|
<InputSection />
|
||||||
</view>
|
</view>
|
||||||
<FriendDetail v-if="bussinessType === MENU_LIST_ENUM.FRIENDS" />
|
<FriendDetail v-if="chatStore.bussinessType === MENU_LIST_ENUM.FRIENDS && useFriendStore.currentFriend" />
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -20,22 +23,35 @@
|
||||||
import ToolSection from '../components/ToolSection/Index.vue'
|
import ToolSection from '../components/ToolSection/Index.vue'
|
||||||
import Session from '../components/Conversation/index.vue'
|
import Session from '../components/Conversation/index.vue'
|
||||||
import Friends from '../components/Friends/Index.vue'
|
import Friends from '../components/Friends/Index.vue'
|
||||||
|
import Department from '../components/Department/index.vue'
|
||||||
import ChatHeader from '../components/ChatHeader/index.vue'
|
import ChatHeader from '../components/ChatHeader/index.vue'
|
||||||
import ChatMessage from '../components/ChatMessage/index.vue'
|
import ChatMessage from '../components/ChatMessage/index.vue'
|
||||||
import InputSection from '../components/InputSection/index.vue'
|
import InputSection from '../components/InputSection/index.vue'
|
||||||
import FriendDetail from '../components/FriendDetail/Index.vue'
|
import FriendDetail from '../components/FriendDetail/Index.vue'
|
||||||
import { MENU_LIST_ENUM } from '../types/types'
|
import { MENU_LIST_ENUM } from '../types/types'
|
||||||
import { useWebSocketStore } from '../store/websocketStore'
|
import { useWebSocketStore } from '../store/websocketStore'
|
||||||
|
import { useFriendStoreWithOut } from '../store/friendstore'
|
||||||
|
import { useChatStore } from '../store/chatstore'
|
||||||
|
|
||||||
defineOptions({ name: 'ChatPage' })
|
defineOptions({ name: 'ChatPage' })
|
||||||
|
|
||||||
const bussinessType = ref(1)
|
|
||||||
const webSocketStore = useWebSocketStore();
|
const webSocketStore = useWebSocketStore();
|
||||||
|
const useFriendStore = useFriendStoreWithOut()
|
||||||
|
const { resetFriendList } = useFriendStore
|
||||||
|
const chatStore = useChatStore()
|
||||||
|
const { setBussinessType } = useChatStore()
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
webSocketStore.connect()
|
webSocketStore.connect()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(() => chatStore.bussinessType, (newVal) => {
|
||||||
|
if (newVal !== MENU_LIST_ENUM.FRIENDS) {
|
||||||
|
resetFriendList()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const toolMenuSelectChange = (value) => {
|
const toolMenuSelectChange = (value) => {
|
||||||
bussinessType.value = value
|
setBussinessType(value)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,12 +2,17 @@
|
||||||
* @Author: dylan.may@qq.com
|
* @Author: dylan.may@qq.com
|
||||||
* @Date: 2024-10-16 11:30:31
|
* @Date: 2024-10-16 11:30:31
|
||||||
* @Last Modified by: dylan.may@qq.com
|
* @Last Modified by: dylan.may@qq.com
|
||||||
* @Last Modified time: 2024-10-16 16:01:25
|
* @Last Modified time: 2024-11-28 17:32:26
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
import { ChatConversation } from '../model/ChatConversation'
|
import { ChatConversation } from '../model/ChatConversation'
|
||||||
|
|
||||||
|
interface createConversationParam {
|
||||||
|
targetId: string,
|
||||||
|
type: number
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 会话接口
|
* 会话接口
|
||||||
*/
|
*/
|
||||||
|
@ -19,4 +24,17 @@ export default class SessionApi {
|
||||||
static getSessionList(): Promise<Array<ChatConversation>> {
|
static getSessionList(): Promise<Array<ChatConversation>> {
|
||||||
return request.get({ url: '/im/conversation/list' })
|
return request.get({ url: '/im/conversation/list' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建会话
|
||||||
|
* @param data createConversationParam
|
||||||
|
* @returns Promise<ChatConversation>
|
||||||
|
*/
|
||||||
|
static createConversation(data: createConversationParam):Promise<ChatConversation> {
|
||||||
|
return request.post({
|
||||||
|
url: '/im/conversation/create',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
style="height: 60px; min-height: 60px"
|
style="height: 60px; min-height: 60px"
|
||||||
>
|
>
|
||||||
<label class="text-black text-size-xl font-medium mx-4">{{
|
<label class="text-black text-size-xl font-medium mx-4">{{
|
||||||
chatStore.currentSession?.name
|
chatStore.currentSession?.nickname || chatStore.currentSession?.name
|
||||||
}}</label>
|
}}</label>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="flex flex-col items-center h-full py-2 b-1 b-gray b-solid" style="width: 258px">
|
<view class="flex flex-col items-center h-full py-2 b-1 b-gray b-solid overflow-auto" style="width: 258px">
|
||||||
<view class="flex flex-col w-full">
|
<view class="flex flex-col w-full">
|
||||||
<SessionItem
|
<SessionItem
|
||||||
v-for="(item, index) in chatStore.sessionList"
|
v-for="(item, index) in chatStore.sessionList"
|
||||||
|
|
|
@ -53,8 +53,8 @@ const timefontColor = () => {
|
||||||
*/
|
*/
|
||||||
const lastMessage = computed(() => {
|
const lastMessage = computed(() => {
|
||||||
|
|
||||||
if (props.conversation.msgList.length === 0) {
|
if (!props.conversation.msgList || props.conversation.msgList.length === 0) {
|
||||||
return props.conversation.lastMessageDescription
|
return props.conversation.lastMessageDescription || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastIndex = props.conversation.msgList.length - 1
|
const lastIndex = props.conversation.msgList.length - 1
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
<template>
|
||||||
|
<div class="tree-node" >
|
||||||
|
<div class="node-title custom-hover" @click.stop="onClick">
|
||||||
|
<ElAvatar class="m-2" shape="square" v-if="node.children.length > 0">{{ node.name.substring(0,1) }}</ElAvatar>
|
||||||
|
<span class="mx-1 p-1">{{ node.name }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="node.children && node.children.length > 0" class="children">
|
||||||
|
<TreeNode
|
||||||
|
v-for="child in node.children"
|
||||||
|
:key="child.id"
|
||||||
|
:node="child"
|
||||||
|
@node-click="$emit('node-click', $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import TreeNode from './TreeNode.vue';
|
||||||
|
|
||||||
|
// 传递 `node` 数据作为属性
|
||||||
|
const props = defineProps({
|
||||||
|
node: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['node-click'])
|
||||||
|
|
||||||
|
const onClick = () => {
|
||||||
|
emit('node-click', props.node)
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tree-node {
|
||||||
|
padding-left: 8px;
|
||||||
|
margin-left: 16px;
|
||||||
|
border-left: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-title {
|
||||||
|
margin: 4px 0;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.children {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,28 @@
|
||||||
|
<template>
|
||||||
|
<div class="tree">
|
||||||
|
<TreeNode v-for="item in hierarchy" :key="item.id" :node="item" @node-click="$emit('tree-click', $event)"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import TreeNode from './TreeNode.vue';
|
||||||
|
|
||||||
|
// 传递 `hierarchy` 数据作为组件属性
|
||||||
|
defineProps({
|
||||||
|
hierarchy: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['tree-click'])
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tree {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,63 @@
|
||||||
|
<template>
|
||||||
|
<view class="flex flex-col items-left h-full py-2 b-1 b-gray b-solid" style="width: 248px; min-width: 248px">
|
||||||
|
<!-- <view v-for="item in departListState.list" class="w-full justify-left custom-hover border-b-gray border-1" :key="item.id" style="height: 70px;">
|
||||||
|
<ElAvatar shape="square">{{ item.name.substring(0,1) }}</ElAvatar>
|
||||||
|
<view class="text-size-sm ml-1">{{ item.name }}</view>
|
||||||
|
</view> -->
|
||||||
|
|
||||||
|
<el-skeleton animated :loading="state.loading" :throttle="{ leading: 500, initVal: true, trailing: 500 }"
|
||||||
|
>
|
||||||
|
<template #template>
|
||||||
|
<div v-for="item in 12" :key="item" class="flex flex-1 mx-2 my-3">
|
||||||
|
<el-skeleton-item animated variant="rect" style="width: 50px; height: 50px; max-width: 50px;" />
|
||||||
|
<div class="mx-2 flex flex-1 flex-col mt-2">
|
||||||
|
<el-skeleton-item animated variant="rect" style="height: 10px;" />
|
||||||
|
<el-skeleton-item animated variant="rect" style="height: 10px;" class="mt-3" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<div class="w-full h-full">
|
||||||
|
<TreeView :hierarchy="useFriendStore.departmentList" @tree-click="handleNodeClick" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-skeleton>
|
||||||
|
|
||||||
|
<view
|
||||||
|
v-if="useFriendStore.departmentList.length === 0 && !state.loading"
|
||||||
|
class="flex justify-center items-center h-full">No data</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
import TreeView from './components/TreeView.vue'
|
||||||
|
import { useFriendStoreWithOut } from '../../store/friendstore';
|
||||||
|
|
||||||
|
|
||||||
|
defineOptions({ name: 'Department' })
|
||||||
|
const useFriendStore = useFriendStoreWithOut()
|
||||||
|
const { fetchDepartment, setCurrentDepartmentId, fetchDeptUser } = useFriendStore
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
loading: true
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchDepartment()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => useFriendStore.departmentList, () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
state.loading = false
|
||||||
|
}, 1000);
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleNodeClick = (data: Tree) => {
|
||||||
|
setCurrentDepartmentId(data.id)
|
||||||
|
fetchDeptUser(data.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<view
|
<view
|
||||||
class="flex justify-center w-full border-b-1 border-b-gray border-b-solid flex-1 border-b-1 border-b-gray border-b-solid py-2"
|
class="flex flex-col items-center w-full border-b-1 border-b-gray border-b-solid flex-1 border-b-1 border-b-gray border-b-solid py-2"
|
||||||
>
|
>
|
||||||
<view class="flex mt-20" v-if="friendStore.currentFriend != null">
|
<view class="flex mt-20" v-if="friendStore.currentFriend != null">
|
||||||
<el-image
|
<el-image
|
||||||
|
@ -8,22 +8,34 @@
|
||||||
class="rounded"
|
class="rounded"
|
||||||
:src="friendStore.currentFriend.avatar"
|
:src="friendStore.currentFriend.avatar"
|
||||||
/>
|
/>
|
||||||
<view class="flex flex-col ml-2">
|
<view class="flex flex-col ml-4 mt-10">
|
||||||
<label class="font-500 text-black font-size-5">{{ friendStore.currentFriend?.name }}</label>
|
<label class="font-500 text-black font-size-5">{{ friendStore.currentFriend?.name || '无名' }}</label>
|
||||||
<label>{{ friendStore.currentFriend?.description }}</label>
|
<label class="mt-2 text-size-sm">{{ friendStore.currentFriend?.description || '--人生若只如初见' }}</label>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-else class="mt-50 flex flex-col items-center">
|
<view v-else class="mt-50 flex flex-col items-center">
|
||||||
<Icon icon="ep:coffee-cup" :size="64" />
|
<Icon icon="ep:coffee-cup" :size="64" />
|
||||||
<label>空空如也</label>
|
<label>空空如也</label>
|
||||||
</view>
|
</view>
|
||||||
|
<el-button type="primary" class="mt-10" v-if="friendStore.currentFriend != null" @click="onSend"> 发送消息</el-button>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useChatStore } from '../../store/chatstore';
|
||||||
import { useFriendStore } from '../../store/friendstore'
|
import { useFriendStore } from '../../store/friendstore'
|
||||||
|
import { CONVERSATION_TYPE } from '../../types/types';
|
||||||
|
|
||||||
defineOptions({ name: 'FriendDetail' })
|
defineOptions({ name: 'FriendDetail' })
|
||||||
|
|
||||||
const friendStore = useFriendStore()
|
const friendStore = useFriendStore()
|
||||||
|
const chatStore = useChatStore()
|
||||||
|
|
||||||
|
const onSend = () => {
|
||||||
|
const avatar = friendStore.currentFriend?.avatar || ''
|
||||||
|
const nickname = friendStore.currentFriend?.name || ''
|
||||||
|
chatStore.createConversation(friendStore.currentFriend?.id, CONVERSATION_TYPE.SINGLE, avatar, nickname)
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="flex py-2 border-b-gray-3 border-b-solid items-center px-2" :class="bgColor()">
|
<view class="flex py-2 border-b-gray-3 border-b-solid items-center px-2" :class="bgColor()">
|
||||||
<el-avatar shape="square" size="default" class="mr-2" :src="friend.avatar" />
|
<el-avatar shape="square" size="default" class="mr-2" :src="friend.avatar" />
|
||||||
<label>{{ friend.name }}</label>
|
<label :class="fontColor()">{{ friend.name }}</label>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -25,4 +25,8 @@ const friendStore = useFriendStore()
|
||||||
const bgColor = () => {
|
const bgColor = () => {
|
||||||
return props.friend.id === friendStore.currentFriend?.id ? 'bg-blue' : 'bg-white'
|
return props.friend.id === friendStore.currentFriend?.id ? 'bg-blue' : 'bg-white'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fontColor = () => {
|
||||||
|
return props.friend.id === friendStore.currentFriend?.id ? 'text-white' : 'text-black'
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<view
|
<view
|
||||||
class="flex flex-col items-center h-full py-2 b-1 b-gray b-solid"
|
class="flex flex-col items-center h-full py-2 b-1 b-gray b-solid b-l-white"
|
||||||
style="width: 248px; min-width: 248px"
|
style="width: 248px; min-width: 248px"
|
||||||
>
|
>
|
||||||
<view class="flex flex-col w-full">
|
<view class="flex flex-col w-full">
|
||||||
|
@ -24,8 +24,9 @@ import Friend from '../../model/Friend'
|
||||||
defineOptions({ name: 'Friends' })
|
defineOptions({ name: 'Friends' })
|
||||||
|
|
||||||
const friendStore = useFriendStore()
|
const friendStore = useFriendStore()
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
// set default conversation
|
// set default conversation
|
||||||
|
// await friendStore.fetchFriend()
|
||||||
})
|
})
|
||||||
|
|
||||||
const onFriendClick = (friend: Friend) => {
|
const onFriendClick = (friend: Friend) => {
|
||||||
|
|
|
@ -1,31 +1,35 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="flex flex-col items-center bg-gray h-full py-2" style="width: 80px; min-width: 80px">
|
<view class="flex flex-col items-center bg-gray-2 h-full py-2" style="width: 80px; min-width: 80px">
|
||||||
<el-avatar shape="square" />
|
<el-avatar shape="square" />
|
||||||
<icon
|
|
||||||
icon="ep:chat-line-round"
|
<div
|
||||||
:size="24"
|
class="flex flex-col items-center px-3 py-3 mt-4 rounded-2 hover:bg-white"
|
||||||
color="white"
|
:class="chatStore.bussinessType === MENU_LIST_ENUM.CONVERSATION ? 'bg-gray-3' : ''" style="width: 60px;"
|
||||||
class="px-4 py-4 mt-1 rounded-2"
|
@click="onConversatonClicked">
|
||||||
:class="selectItem === MENU_LIST_ENUM.CONVERSATION ? 'bg-red' : ''"
|
<icon icon="ep:chat-line-round" :size="24" color="#409EFF" />
|
||||||
@click="onConversatonClicked"
|
<span class="text-xs mt-1 text-gray-5">会 话</span>
|
||||||
/>
|
<span></span>
|
||||||
<icon
|
</div>
|
||||||
icon="ep:avatar"
|
|
||||||
:size="24"
|
<div
|
||||||
color="white"
|
class="flex flex-col items-center rounded-2 mt-4 p-3 hover:bg-white"
|
||||||
class="px-4 py-4 rounded-2 mt-2"
|
:class="chatStore.bussinessType === MENU_LIST_ENUM.FRIENDS ? 'bg-gray-3' : ''" style="width: 60px;" @click="onFriendsClicked">
|
||||||
:class="selectItem === MENU_LIST_ENUM.FRIENDS ? 'bg-red' : ''"
|
<icon icon="ep:avatar" :size="24" color="#409EFF" />
|
||||||
@click="onFriendsClicked"
|
<span class="text-xs mt-1 text-gray-5">联系人</span>
|
||||||
/>
|
</div>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useChatStore } from '../../store/chatstore';
|
||||||
import { MENU_LIST_ENUM } from '../../types/types'
|
import { MENU_LIST_ENUM } from '../../types/types'
|
||||||
|
|
||||||
defineOptions({ name: 'ToolSection' })
|
defineOptions({ name: 'ToolSection' })
|
||||||
|
|
||||||
const selectItem = ref(1)
|
const selectItem = ref(1)
|
||||||
|
const chatStore = useChatStore()
|
||||||
|
const { setBussinessType } = useChatStore()
|
||||||
|
|
||||||
const emit = defineEmits(['menuSelectChange'])
|
const emit = defineEmits(['menuSelectChange'])
|
||||||
watch(
|
watch(
|
||||||
|
@ -36,10 +40,10 @@ watch(
|
||||||
)
|
)
|
||||||
|
|
||||||
const onConversatonClicked = () => {
|
const onConversatonClicked = () => {
|
||||||
selectItem.value = MENU_LIST_ENUM.CONVERSATION
|
setBussinessType(MENU_LIST_ENUM.CONVERSATION)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onFriendsClicked = () => {
|
const onFriendsClicked = () => {
|
||||||
selectItem.value = MENU_LIST_ENUM.FRIENDS
|
setBussinessType(MENU_LIST_ENUM.FRIENDS)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -4,12 +4,16 @@ export default class Friend {
|
||||||
public name: string
|
public name: string
|
||||||
public description: string
|
public description: string
|
||||||
public createTime: number
|
public createTime: number
|
||||||
|
public deptId: number
|
||||||
|
public deptName: string
|
||||||
|
|
||||||
constructor(id, avatar, name, description, createTime) {
|
constructor(id, avatar, name, description, createTime, deptId, deptName) {
|
||||||
this.id = id
|
this.id = id
|
||||||
this.avatar = avatar
|
this.avatar = avatar
|
||||||
this.name = name
|
this.name = name
|
||||||
this.description = description
|
this.description = description
|
||||||
this.createTime = createTime
|
this.createTime = createTime
|
||||||
|
this.deptId = deptId
|
||||||
|
this.deptName = deptName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { store } from '@/store/index'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import BaseConversation from '../model/BaseConversation'
|
import BaseConversation from '../model/BaseConversation'
|
||||||
import BaseMessage from '../model/BaseMessage'
|
import BaseMessage from '../model/BaseMessage'
|
||||||
import { ConversationModelType, MessageRole, ContentType, SendStatus } from '../types/types'
|
import { ConversationModelType, MessageRole, ContentType, SendStatus, MENU_LIST_ENUM } from '../types/types'
|
||||||
import SessionApi from '../api/sessionApi'
|
import SessionApi from '../api/sessionApi'
|
||||||
import MessageApi, { SendMsg } from '../api/messageApi'
|
import MessageApi, { SendMsg } from '../api/messageApi'
|
||||||
import { useUserStore, useUserStoreWithOut } from '@/store/modules/user'
|
import { useUserStore, useUserStoreWithOut } from '@/store/modules/user'
|
||||||
|
@ -16,7 +16,8 @@ interface ChatStoreModel {
|
||||||
sessionList: Array<ConversationModelType>
|
sessionList: Array<ConversationModelType>
|
||||||
currentSession: ConversationModelType | null
|
currentSession: ConversationModelType | null
|
||||||
currentSessionIndex: number
|
currentSessionIndex: number
|
||||||
inputText: string
|
inputText: string,
|
||||||
|
bussinessType: number // conversation 1, friends 2
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useChatStore = defineStore('chatStore', {
|
export const useChatStore = defineStore('chatStore', {
|
||||||
|
@ -24,7 +25,8 @@ export const useChatStore = defineStore('chatStore', {
|
||||||
sessionList: [],
|
sessionList: [],
|
||||||
currentSession: null,
|
currentSession: null,
|
||||||
currentSessionIndex: 0,
|
currentSessionIndex: 0,
|
||||||
inputText: ''
|
inputText: '',
|
||||||
|
bussinessType: 1,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
|
@ -59,6 +61,10 @@ export const useChatStore = defineStore('chatStore', {
|
||||||
this.inputText = content
|
this.inputText = content
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setBussinessType(type: number) {
|
||||||
|
this.bussinessType = type
|
||||||
|
},
|
||||||
|
|
||||||
async addMessageToCurrentSession<T extends BaseMessage>(message: T): Promise<void> {
|
async addMessageToCurrentSession<T extends BaseMessage>(message: T): Promise<void> {
|
||||||
this.currentSession?.msgList.push(message)
|
this.currentSession?.msgList.push(message)
|
||||||
|
|
||||||
|
@ -109,6 +115,7 @@ export const useChatStore = defineStore('chatStore', {
|
||||||
* @param message
|
* @param message
|
||||||
*/
|
*/
|
||||||
addMessageToConversation<T extends BaseMessage>(message: T): void {
|
addMessageToConversation<T extends BaseMessage>(message: T): void {
|
||||||
|
|
||||||
// 无论是converstionNo1还是converstionNo2都都需要试一下
|
// 无论是converstionNo1还是converstionNo2都都需要试一下
|
||||||
const converstionNo1 = generateConversationNo(
|
const converstionNo1 = generateConversationNo(
|
||||||
message.senderId,
|
message.senderId,
|
||||||
|
@ -138,6 +145,11 @@ export const useChatStore = defineStore('chatStore', {
|
||||||
|
|
||||||
// 更新消息到indexeddb
|
// 更新消息到indexeddb
|
||||||
addConversation(toRaw(msgConversation) as ChatConversation )
|
addConversation(toRaw(msgConversation) as ChatConversation )
|
||||||
|
|
||||||
|
// 更新当前会话
|
||||||
|
if (conversationIndex === this.currentSessionIndex) {
|
||||||
|
this.setCurrentConversation()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -236,7 +248,60 @@ export const useChatStore = defineStore('chatStore', {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建会话
|
||||||
|
*/
|
||||||
|
async createConversation(targetId, type, avatar, nickname) {
|
||||||
|
try {
|
||||||
|
const param = {
|
||||||
|
targetId,
|
||||||
|
type
|
||||||
|
}
|
||||||
|
const res = await SessionApi.createConversation(param)
|
||||||
|
if (res) {
|
||||||
|
|
||||||
|
// 切换到聊天模式
|
||||||
|
this.bussinessType = MENU_LIST_ENUM.CONVERSATION
|
||||||
|
|
||||||
|
// 插入用户名和昵称
|
||||||
|
res.avatar = avatar;
|
||||||
|
res.nickname = nickname;
|
||||||
|
const localConversation = this.convertCoversationFromServer(res)
|
||||||
|
// 存入到数据库
|
||||||
|
addConversation(toRaw(localConversation) as ChatConversation)
|
||||||
|
// 从数据库同步到内存
|
||||||
|
await this.getConversationList()
|
||||||
|
// 设置当前的会话
|
||||||
|
const addIndex = this.sessionList.findIndex(item => item.conversationNo === localConversation.conversationNo)
|
||||||
|
this.setCurrentSessionIndex(addIndex)
|
||||||
|
this.setCurrentConversation()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
convertCoversationFromServer(item: any) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
updateTime: item.updateTime,
|
||||||
|
targetId: item.targetId,
|
||||||
|
senderId: item.userId,
|
||||||
|
conversationNo: item.no,
|
||||||
|
unreadMessagesCount: item.unreadMessagesCount,
|
||||||
|
description: item.lastMessageDescription,
|
||||||
|
avatar: item.avatar,
|
||||||
|
name: item.name,
|
||||||
|
msgList: []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,33 +1,23 @@
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
|
import { store } from '@/store/index'
|
||||||
import BaseConversation from '../model/BaseConversation'
|
import BaseConversation from '../model/BaseConversation'
|
||||||
import Friend from '../model/Friend'
|
import Friend from '../model/Friend'
|
||||||
|
import { getAllUser, getDeptUser } from '@/api/system/user'
|
||||||
|
import * as DeptApi from '@/api/system/dept'
|
||||||
|
|
||||||
interface FriendStoreModel {
|
interface FriendStoreModel {
|
||||||
friendList: Array<Friend>
|
friendList: Array<Friend>
|
||||||
currentFriend: Friend | null
|
currentFriend: Friend | null,
|
||||||
|
selectedDepartmentId: number,
|
||||||
|
departmentList: DeptApi.DeptVO[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useFriendStore = defineStore('friendStore', {
|
export const useFriendStore = defineStore('friendStore', {
|
||||||
state: (): FriendStoreModel => ({
|
state: (): FriendStoreModel => ({
|
||||||
friendList: [
|
friendList: [],
|
||||||
{
|
currentFriend: null,
|
||||||
id: '1111',
|
selectedDepartmentId: 0,
|
||||||
name: 'Elon Musk',
|
departmentList: []
|
||||||
avatar:
|
|
||||||
'https://img0.baidu.com/it/u=4211304696,1059959254&fm=253&fmt=auto&app=120&f=JPEG?w=800&h=1174',
|
|
||||||
description: 'cool boy',
|
|
||||||
createTime: 1695201147622
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2222',
|
|
||||||
name: 'Spider Man',
|
|
||||||
avatar:
|
|
||||||
'https://www.hottoys.com.cn/wp-content/uploads/2019/06/bloggerreview_spiderman_advanced_ben-9.jpg',
|
|
||||||
description: 'hero',
|
|
||||||
createTime: 1695201147622
|
|
||||||
}
|
|
||||||
],
|
|
||||||
currentFriend: null
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
|
@ -42,6 +32,82 @@ export const useFriendStore = defineStore('friendStore', {
|
||||||
},
|
},
|
||||||
setCurrentFriend(friend: Friend) {
|
setCurrentFriend(friend: Friend) {
|
||||||
this.currentFriend = friend
|
this.currentFriend = friend
|
||||||
}
|
},
|
||||||
|
setCurrentDepartmentId(id: number) {
|
||||||
|
this.selectedDepartmentId = id
|
||||||
|
},
|
||||||
|
resetFriendList() {
|
||||||
|
this.friendList = []
|
||||||
|
this.currentFriend = null
|
||||||
|
},
|
||||||
|
async fetchDepartment () {
|
||||||
|
try {
|
||||||
|
const result = await DeptApi.getSimpleDeptList()
|
||||||
|
this.departmentList = this.buildHierarchy(result)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchFriend() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getAllUser()
|
||||||
|
this.friendList = res
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchDeptUser(id) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getDeptUser(id)
|
||||||
|
if (res) {
|
||||||
|
this.friendList = res.map(item => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
name: item.nickname
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.friendList = []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
buildHierarchy(data: DeptApi.DeptVO[]): DeptApi.DeptVO[] {
|
||||||
|
const map = new Map<number, DeptApi.DeptVO>();
|
||||||
|
|
||||||
|
// 初始化 map,确保每个 id 都有一条记录
|
||||||
|
data.forEach(item => map.set(item.id, { ...item, children: [] }));
|
||||||
|
|
||||||
|
const result: DeptApi.DeptVO[] = [];
|
||||||
|
|
||||||
|
data.forEach(item => {
|
||||||
|
if (item.parentId === 0) {
|
||||||
|
// 根节点
|
||||||
|
result.push(map.get(item.id)!);
|
||||||
|
} else {
|
||||||
|
// 子节点,放入父节点的 children 数组
|
||||||
|
const parent = map.get(item.parentId);
|
||||||
|
if (parent) {
|
||||||
|
parent.children!.push(map.get(item.id)!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export const useFriendStoreWithOut = () => {
|
||||||
|
return useFriendStore(store)
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import {
|
||||||
import TextMessage from '../model/TextMessage'
|
import TextMessage from '../model/TextMessage'
|
||||||
import { debug } from 'console'
|
import { debug } from 'console'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
import BaseConversation from '../model/BaseConversation'
|
||||||
|
|
||||||
interface Message {
|
interface Message {
|
||||||
type: string
|
type: string
|
||||||
|
@ -101,6 +102,13 @@ export const useWebSocketStore = defineStore('webSocket', () => {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else if (websoketMessage.type === WEBSOCKET_MESSAGE_TYPE_ENUM.IM_CONVERSATION_ADD.toString()) {
|
||||||
|
const chatStore = useChatStore()
|
||||||
|
const conversation = JSON.parse(websoketMessage.content) as BaseConversation
|
||||||
|
chatStore.addSession(conversation)
|
||||||
|
// 同步到内存
|
||||||
|
chatStore.getConversationList()
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// TODO:[dylan]
|
// TODO:[dylan]
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,8 @@ export const enum CONVERSATION_TYPE {
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum WEBSOCKET_MESSAGE_TYPE_ENUM {
|
export enum WEBSOCKET_MESSAGE_TYPE_ENUM {
|
||||||
IM_MESSAGE_RECEIVE = 'im-message-receive'
|
IM_MESSAGE_RECEIVE = 'im-message-receive',
|
||||||
|
IM_CONVERSATION_ADD = 'im-conversation-add'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue