feat(im):antd 的 im 迁移进一步对齐
parent
0929ab9409
commit
24813f00f5
|
|
@ -70,8 +70,5 @@
|
|||
"vue3-print-nb": "catalog:",
|
||||
"vue3-signature": "catalog:",
|
||||
"vuedraggable": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "catalog:"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,24 @@
|
|||
// TODO @AI:api 的风格,要和 vben 保持一致;
|
||||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
// 用户端能看到的频道素材详情
|
||||
export interface ImChannelMaterialRespVO {
|
||||
id: number
|
||||
channelId: number
|
||||
type: number
|
||||
title: string
|
||||
coverUrl?: string
|
||||
summary?: string
|
||||
content?: string
|
||||
url?: string
|
||||
export namespace ImChannelMaterialApi {
|
||||
/** 用户端能看到的频道素材详情 */
|
||||
export interface Material {
|
||||
id: number;
|
||||
channelId: number;
|
||||
type: number;
|
||||
title: string;
|
||||
coverUrl?: string;
|
||||
summary?: string;
|
||||
content?: string;
|
||||
url?: string;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取频道素材详情;用于客户端点击图文卡片渲染详情页
|
||||
export const getChannelMaterial = (id: number) => {
|
||||
return requestClient.get<ImChannelMaterialRespVO>('/im/channel/material/get', { params: { id } })
|
||||
|
||||
/** 获取频道素材详情;用于客户端点击图文卡片渲染详情页 */
|
||||
export function getChannelMaterial(id: number) {
|
||||
return requestClient.get<ImChannelMaterialApi.Material>(
|
||||
'/im/channel/material/get',
|
||||
{ params: { id } },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,25 @@
|
|||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
// IM 会话读位置 Response VO
|
||||
export interface ImConversationReadRespVO {
|
||||
id: number // 读位置编号(增量拉取游标用)
|
||||
conversationType: number // 会话类型,参见 ImConversationType
|
||||
targetId: number // 会话目标编号
|
||||
messageId: number // 最大已读消息编号
|
||||
updateTime?: number // 最近更新时间(毫秒时间戳,增量拉取游标用)
|
||||
export namespace ImConversationReadApi {
|
||||
/** IM 会话读位置 Response VO */
|
||||
export interface ConversationReadRespVO {
|
||||
id: number; // 读位置编号(增量拉取游标用)
|
||||
conversationType: number; // 会话类型,参见 ImConversationType
|
||||
targetId: number; // 会话目标编号
|
||||
messageId: number; // 最大已读消息编号
|
||||
updateTime?: number; // 最近更新时间(毫秒时间戳,增量拉取游标用)
|
||||
}
|
||||
}
|
||||
|
||||
// 增量拉取当前用户的会话读位置(重连 / 离线补偿)
|
||||
export const pullMyConversationReadList = (params: {
|
||||
lastId?: number
|
||||
lastUpdateTime?: number
|
||||
limit: number
|
||||
}) => {
|
||||
return requestClient.get<ImConversationReadRespVO[]>('/im/conversation-read/pull', { params })
|
||||
|
||||
/** 增量拉取当前用户的会话读位置(重连 / 离线补偿) */
|
||||
export function pullMyConversationReadList(params: {
|
||||
lastId?: number;
|
||||
lastUpdateTime?: number;
|
||||
limit: number;
|
||||
}) {
|
||||
return requestClient.get<ImConversationReadApi.ConversationReadRespVO[]>(
|
||||
'/im/conversation-read/pull',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,26 @@
|
|||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
// 用户端表情包项(精简版)
|
||||
export interface ImFacePackUserItemVO {
|
||||
id: number
|
||||
url: string
|
||||
name?: string
|
||||
width: number
|
||||
height: number
|
||||
export namespace ImFacePackApi {
|
||||
/** 用户端表情包项(精简版) */
|
||||
export interface FacePackUserItem {
|
||||
id: number;
|
||||
url: string;
|
||||
name?: string;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
/** 用户端表情包 + 嵌套 items */
|
||||
export interface FacePackUser {
|
||||
id: number;
|
||||
name: string;
|
||||
icon?: string;
|
||||
items: FacePackUserItem[];
|
||||
}
|
||||
}
|
||||
|
||||
// 用户端表情包 + 嵌套 items
|
||||
export interface ImFacePackUserVO {
|
||||
id: number
|
||||
name: string
|
||||
icon?: string
|
||||
items: ImFacePackUserItemVO[]
|
||||
}
|
||||
|
||||
// 拉取所有启用的系统表情包(含表情列表)
|
||||
export const getFacePackList = () => {
|
||||
return requestClient.get<ImFacePackUserVO[]>('/im/face-pack/list')
|
||||
/** 拉取所有启用的系统表情包(含表情列表) */
|
||||
export function getFacePackList() {
|
||||
return requestClient.get<ImFacePackApi.FacePackUser[]>('/im/face-pack/list');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,38 @@
|
|||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
// 个人表情
|
||||
export interface ImFaceUserItemVO {
|
||||
id: number
|
||||
url: string
|
||||
name?: string
|
||||
width: number
|
||||
height: number
|
||||
export namespace ImFaceUserItemApi {
|
||||
/** 个人表情 */
|
||||
export interface FaceUserItem {
|
||||
id: number;
|
||||
url: string;
|
||||
name?: string;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
/** 添加个人表情请求 */
|
||||
export interface FaceUserItemSaveReqVO {
|
||||
url: string;
|
||||
name?: string;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加个人表情请求
|
||||
export interface ImFaceUserItemSaveReqVO {
|
||||
url: string
|
||||
name?: string
|
||||
width: number
|
||||
height: number
|
||||
|
||||
/** 获取我的个人表情列表 */
|
||||
export function getFaceUserItemList() {
|
||||
return requestClient.get<ImFaceUserItemApi.FaceUserItem[]>('/im/face-user-item/list');
|
||||
}
|
||||
|
||||
// 获取我的个人表情列表
|
||||
export const getFaceUserItemList = () => {
|
||||
return requestClient.get<ImFaceUserItemVO[]>('/im/face-user-item/list')
|
||||
/** 添加个人表情 */
|
||||
export function createFaceUserItem(data: ImFaceUserItemApi.FaceUserItemSaveReqVO) {
|
||||
return requestClient.post<number>('/im/face-user-item/create', data);
|
||||
}
|
||||
|
||||
// 添加个人表情
|
||||
export const createFaceUserItem = (data: ImFaceUserItemSaveReqVO) => {
|
||||
return requestClient.post<number>('/im/face-user-item/create', data)
|
||||
}
|
||||
|
||||
// 删除个人表情
|
||||
export const deleteFaceUserItem = (id: number) => {
|
||||
return requestClient.delete('/im/face-user-item/delete', { params: { id } })
|
||||
/** 删除个人表情 */
|
||||
export function deleteFaceUserItem(id: number) {
|
||||
return requestClient.delete<boolean>('/im/face-user-item/delete', {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,65 +1,78 @@
|
|||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
// IM 好友 Response VO
|
||||
export interface ImFriendRespVO {
|
||||
id: number // 关系记录编号
|
||||
friendUserId: number // 好友的用户编号
|
||||
silent?: boolean // 是否免打扰
|
||||
displayName?: string // 好友展示备注(仅自己可见)
|
||||
displayNamePinyin?: string // 备注的拼音(小写无空格,前端按首字母分桶 / 拼音搜索)
|
||||
addSource?: number // 添加来源;参见 ImFriendAddSourceEnum
|
||||
pinned?: boolean // 是否置顶联系人
|
||||
blocked?: boolean // 是否拉黑
|
||||
status?: number // 好友状态(0=正常,1=已删除)
|
||||
addTime?: string // 添加好友时间
|
||||
deleteTime?: string // 删除好友时间
|
||||
updateTime?: number // 最近更新时间(毫秒时间戳,增量拉取游标用)
|
||||
// 聚合字段(自 AdminUser)
|
||||
nickname?: string // 好友昵称
|
||||
nicknamePinyin?: string // 昵称的拼音(小写无空格,前端按首字母分桶 / 拼音搜索)
|
||||
avatar?: string // 好友头像
|
||||
export namespace ImFriendApi {
|
||||
/** IM 好友 Response VO */
|
||||
export interface FriendRespVO {
|
||||
id: number; // 关系记录编号
|
||||
friendUserId: number; // 好友的用户编号
|
||||
silent?: boolean; // 是否免打扰
|
||||
displayName?: string; // 好友展示备注(仅自己可见)
|
||||
displayNamePinyin?: string; // 备注的拼音(小写无空格,前端按首字母分桶 / 拼音搜索)
|
||||
addSource?: number; // 添加来源;参见 ImFriendAddSourceEnum
|
||||
pinned?: boolean; // 是否置顶联系人
|
||||
blocked?: boolean; // 是否拉黑
|
||||
status?: number; // 好友状态(0=正常,1=已删除)
|
||||
addTime?: string; // 添加好友时间
|
||||
deleteTime?: string; // 删除好友时间
|
||||
updateTime?: number; // 最近更新时间(毫秒时间戳,增量拉取游标用)
|
||||
nickname?: string; // 好友昵称
|
||||
nicknamePinyin?: string; // 昵称的拼音(小写无空格,前端按首字母分桶 / 拼音搜索)
|
||||
avatar?: string; // 好友头像
|
||||
}
|
||||
|
||||
/** IM 好友更新 Request VO */
|
||||
export interface FriendUpdateReqVO {
|
||||
friendUserId: number; // 好友的用户编号
|
||||
silent?: boolean; // 是否免打扰
|
||||
displayName?: string; // 好友展示备注
|
||||
pinned?: boolean; // 是否置顶联系人
|
||||
}
|
||||
}
|
||||
|
||||
// IM 好友更新 Request VO
|
||||
export interface ImFriendUpdateReqVO {
|
||||
friendUserId: number // 好友的用户编号
|
||||
silent?: boolean // 是否免打扰
|
||||
displayName?: string // 好友展示备注
|
||||
pinned?: boolean // 是否置顶联系人
|
||||
|
||||
/** 获得当前登录用户的好友列表 */
|
||||
export function getMyFriendList() {
|
||||
return requestClient.get<ImFriendApi.FriendRespVO[]>('/im/friend/list');
|
||||
}
|
||||
|
||||
// 获得当前登录用户的好友列表
|
||||
export const getMyFriendList = () => {
|
||||
return requestClient.get<ImFriendRespVO[]>('/im/friend/list')
|
||||
/** 增量拉取当前用户的好友关系(重连 / 离线补偿) */
|
||||
export function pullMyFriendList(params: {
|
||||
lastId?: number;
|
||||
lastUpdateTime?: number;
|
||||
limit: number;
|
||||
}) {
|
||||
return requestClient.get<ImFriendApi.FriendRespVO[]>('/im/friend/pull', { params });
|
||||
}
|
||||
|
||||
// 增量拉取当前用户的好友关系(重连 / 离线补偿)
|
||||
export const pullMyFriendList = (params: { lastId?: number; lastUpdateTime?: number; limit: number }) => {
|
||||
return requestClient.get<ImFriendRespVO[]>('/im/friend/pull', { params })
|
||||
/** 获得好友详情 */
|
||||
export function getFriend(friendUserId: number | string) {
|
||||
return requestClient.get<ImFriendApi.FriendRespVO>('/im/friend/get', {
|
||||
params: { friendUserId },
|
||||
});
|
||||
}
|
||||
|
||||
// 获得好友详情
|
||||
export const getFriend = (friendUserId: number | string) => {
|
||||
return requestClient.get<ImFriendRespVO>('/im/friend/get', { params: { friendUserId } })
|
||||
/** 删除好友(单向软删除) */
|
||||
export function deleteFriend(friendUserId: number | string, clear: boolean) {
|
||||
return requestClient.delete<boolean>('/im/friend/delete', {
|
||||
params: { friendUserId, clear },
|
||||
});
|
||||
}
|
||||
|
||||
// 删除好友(单向软删除)
|
||||
export const deleteFriend = (friendUserId: number | string, clear: boolean) => {
|
||||
return requestClient.delete<boolean>('/im/friend/delete', { params: { friendUserId, clear } })
|
||||
/** 更新好友信息(备注 / 免打扰 / 联系人置顶) */
|
||||
export function updateFriend(data: ImFriendApi.FriendUpdateReqVO) {
|
||||
return requestClient.put<boolean>('/im/friend/update', data);
|
||||
}
|
||||
|
||||
// 更新好友信息(备注 / 免打扰 / 联系人置顶)
|
||||
export const updateFriend = (data: ImFriendUpdateReqVO) => {
|
||||
return requestClient.put<boolean>('/im/friend/update', data)
|
||||
/** 拉黑好友(必须先是好友;单边屏蔽对方私聊消息) */
|
||||
export function blockFriend(friendUserId: number | string) {
|
||||
return requestClient.put<boolean>('/im/friend/block', undefined, {
|
||||
params: { friendUserId },
|
||||
});
|
||||
}
|
||||
|
||||
// 拉黑好友(必须先是好友;单边屏蔽对方私聊消息)
|
||||
export const blockFriend = (friendUserId: number | string) => {
|
||||
return requestClient.put<boolean>('/im/friend/block', undefined, { params: { friendUserId } })
|
||||
/** 移出黑名单 */
|
||||
export function unblockFriend(friendUserId: number | string) {
|
||||
return requestClient.put<boolean>('/im/friend/unblock', undefined, {
|
||||
params: { friendUserId },
|
||||
});
|
||||
}
|
||||
|
||||
// 移出黑名单
|
||||
export const unblockFriend = (friendUserId: number | string) => {
|
||||
return requestClient.put<boolean>('/im/friend/unblock', undefined, { params: { friendUserId } })
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,66 +1,84 @@
|
|||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
// IM 好友申请 Response VO
|
||||
export interface ImFriendRequestRespVO {
|
||||
id: number // 申请编号
|
||||
fromUserId: number // 发起方用户编号
|
||||
toUserId: number // 接收方用户编号
|
||||
handleResult: number // 处理结果;0=未处理;1=同意;2=拒绝
|
||||
applyContent?: string // 申请理由
|
||||
handleContent?: string // 处理理由(接收方拒绝时可选填)
|
||||
addSource?: number // 添加来源;参见 ImFriendAddSourceEnum
|
||||
handleTime?: string // 处理时间
|
||||
createTime: string // 申请创建时间
|
||||
updateTime?: number // 最近更新时间(毫秒时间戳,增量拉取游标用)
|
||||
// 聚合字段(自 AdminUser)
|
||||
fromNickname?: string // 发起方昵称
|
||||
fromAvatar?: string // 发起方头像
|
||||
toNickname?: string // 接收方昵称
|
||||
toAvatar?: string // 接收方头像
|
||||
}
|
||||
|
||||
// IM 好友申请发起 Request VO
|
||||
export interface ImFriendRequestApplyReqVO {
|
||||
toUserId: number // 接收方用户编号
|
||||
applyContent?: string // 申请理由
|
||||
displayName?: string // 对接收方的备注(仅自己可见)
|
||||
addSource?: number // 添加来源
|
||||
}
|
||||
|
||||
// 发起好友申请
|
||||
export const applyFriendRequest = (data: ImFriendRequestApplyReqVO) => {
|
||||
return requestClient.post<null | number>('/im/friend-request/apply', data)
|
||||
}
|
||||
|
||||
// 同意好友申请
|
||||
export const agreeFriendRequest = (id: number | string) => {
|
||||
return requestClient.put<boolean>('/im/friend-request/agree', undefined, { params: { id } })
|
||||
}
|
||||
|
||||
// 拒绝好友申请
|
||||
export const refuseFriendRequest = (id: number | string, handleContent?: string) => {
|
||||
return requestClient.put<boolean>('/im/friend-request/refuse', undefined, { params: { id, handleContent } })
|
||||
}
|
||||
|
||||
// 查询「我相关」的好友申请列表(游标分页:传 maxId 加载更多)
|
||||
export const getMyFriendRequestList = (limit: number, maxId?: number) => {
|
||||
const params: Record<string, number> = { limit }
|
||||
if (maxId != null) {
|
||||
params.maxId = maxId
|
||||
export namespace ImFriendRequestApi {
|
||||
/** IM 好友申请 Response VO */
|
||||
export interface FriendRequestRespVO {
|
||||
id: number; // 申请编号
|
||||
fromUserId: number; // 发起方用户编号
|
||||
toUserId: number; // 接收方用户编号
|
||||
handleResult: number; // 处理结果;0=未处理;1=同意;2=拒绝
|
||||
applyContent?: string; // 申请理由
|
||||
handleContent?: string; // 处理理由(接收方拒绝时可选填)
|
||||
addSource?: number; // 添加来源;参见 ImFriendAddSourceEnum
|
||||
handleTime?: string; // 处理时间
|
||||
createTime: string; // 申请创建时间
|
||||
updateTime?: number; // 最近更新时间(毫秒时间戳,增量拉取游标用)
|
||||
fromNickname?: string; // 发起方昵称
|
||||
fromAvatar?: string; // 发起方头像
|
||||
toNickname?: string; // 接收方昵称
|
||||
toAvatar?: string; // 接收方头像
|
||||
}
|
||||
|
||||
/** IM 好友申请发起 Request VO */
|
||||
export interface FriendRequestApplyReqVO {
|
||||
toUserId: number; // 接收方用户编号
|
||||
applyContent?: string; // 申请理由
|
||||
displayName?: string; // 对接收方的备注(仅自己可见)
|
||||
addSource?: number; // 添加来源
|
||||
}
|
||||
return requestClient.get<ImFriendRequestRespVO[]>('/im/friend-request/list', { params })
|
||||
}
|
||||
|
||||
// 增量拉取「我相关」的好友申请变更(重连 / 离线补偿)
|
||||
export const pullMyFriendRequestList = (params: {
|
||||
lastId?: number
|
||||
lastUpdateTime?: number
|
||||
limit: number
|
||||
}) => {
|
||||
return requestClient.get<ImFriendRequestRespVO[]>('/im/friend-request/pull', { params })
|
||||
|
||||
/** 发起好友申请 */
|
||||
export function applyFriendRequest(data: ImFriendRequestApi.FriendRequestApplyReqVO) {
|
||||
return requestClient.post<null | number>('/im/friend-request/apply', data);
|
||||
}
|
||||
|
||||
// 按 id 单查「我相关」的申请记录(带越权过滤;WebSocket 通知到达后用)
|
||||
export const getMyFriendRequest = (id: number) => {
|
||||
return requestClient.get<ImFriendRequestRespVO | null>('/im/friend-request/get', { params: { id } })
|
||||
/** 同意好友申请 */
|
||||
export function agreeFriendRequest(id: number | string) {
|
||||
return requestClient.put<boolean>('/im/friend-request/agree', undefined, {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
/** 拒绝好友申请 */
|
||||
export function refuseFriendRequest(
|
||||
id: number | string,
|
||||
handleContent?: string,
|
||||
) {
|
||||
return requestClient.put<boolean>('/im/friend-request/refuse', undefined, {
|
||||
params: { id, handleContent },
|
||||
});
|
||||
}
|
||||
|
||||
/** 查询「我相关」的好友申请列表(游标分页:传 maxId 加载更多) */
|
||||
export function getMyFriendRequestList(limit: number, maxId?: number) {
|
||||
const params: Record<string, number> = { limit };
|
||||
if (maxId != null) {
|
||||
params.maxId = maxId;
|
||||
}
|
||||
return requestClient.get<ImFriendRequestApi.FriendRequestRespVO[]>(
|
||||
'/im/friend-request/list',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 增量拉取「我相关」的好友申请变更(重连 / 离线补偿) */
|
||||
export function pullMyFriendRequestList(params: {
|
||||
lastId?: number;
|
||||
lastUpdateTime?: number;
|
||||
limit: number;
|
||||
}) {
|
||||
return requestClient.get<ImFriendRequestApi.FriendRequestRespVO[]>(
|
||||
'/im/friend-request/pull',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 按 id 单查「我相关」的申请记录(带越权过滤;WebSocket 通知到达后用) */
|
||||
export function getMyFriendRequest(id: number) {
|
||||
return requestClient.get<ImFriendRequestApi.FriendRequestRespVO | null>(
|
||||
'/im/friend-request/get',
|
||||
{ params: { id } },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,139 +1,144 @@
|
|||
import type { ImGroupMessageRespVO } from '#/api/im/message/group'
|
||||
import type { ImGroupMessageApi } from '#/api/im/message/group';
|
||||
|
||||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
// 群 Response VO
|
||||
export interface ImGroupRespVO {
|
||||
id: number // 编号
|
||||
name: string // 群名称
|
||||
ownerUserId: number // 群主用户编号
|
||||
avatar?: string // 群头像
|
||||
notice?: string // 群公告
|
||||
banned?: boolean // 是否封禁
|
||||
mutedAll?: boolean // 是否全群禁言
|
||||
joinApproval?: boolean // 进群是否需群主 / 管理员审批
|
||||
bannedTime?: string // 封禁时间
|
||||
status: number // 群状态(0=正常,1=已解散)
|
||||
dissolvedTime?: string // 解散时间
|
||||
createTime?: string // 创建时间
|
||||
pinnedMessages?: ImGroupMessageRespVO[] // 群置顶消息列表(后端关联回填,仅当登录用户是群成员时非空)
|
||||
joinStatus?: number // 当前登录用户在该群的成员状态(参见 CommonStatusEnum:0 在群 / 1 已退群);历史退群群仍返回,供展示离线消息的群名 / 头像
|
||||
export namespace ImGroupApi {
|
||||
/** 群 Response VO */
|
||||
export interface GroupRespVO {
|
||||
id: number; // 编号
|
||||
name: string; // 群名称
|
||||
ownerUserId: number; // 群主用户编号
|
||||
avatar?: string; // 群头像
|
||||
notice?: string; // 群公告
|
||||
banned?: boolean; // 是否封禁
|
||||
mutedAll?: boolean; // 是否全群禁言
|
||||
joinApproval?: boolean; // 进群是否需群主 / 管理员审批
|
||||
bannedTime?: string; // 封禁时间
|
||||
status: number; // 群状态(0=正常,1=已解散)
|
||||
dissolvedTime?: string; // 解散时间
|
||||
createTime?: string; // 创建时间
|
||||
pinnedMessages?: ImGroupMessageApi.GroupMessageRespVO[]; // 群置顶消息列表(后端关联回填,仅当登录用户是群成员时非空)
|
||||
joinStatus?: number; // 当前登录用户在该群的成员状态(参见 CommonStatusEnum:0 在群 / 1 已退群);历史退群群仍返回,供展示离线消息的群名 / 头像
|
||||
}
|
||||
|
||||
/** 群消息置顶 / 取消置顶 Request VO */
|
||||
export interface GroupMessagePinReqVO {
|
||||
id: number; // 群编号
|
||||
messageId: number; // 消息编号
|
||||
}
|
||||
|
||||
/** 群创建 Request VO */
|
||||
export interface GroupCreateReqVO {
|
||||
name: string; // 群名称
|
||||
memberUserIds?: number[]; // 初始成员用户编号列表(建群同时邀请的好友,不含创建者自己)
|
||||
joinApproval?: boolean; // 进群是否需审批;不传默认 false 自由进群
|
||||
}
|
||||
|
||||
/** 群更新 Request VO */
|
||||
export interface GroupUpdateReqVO {
|
||||
id: number; // 群编号
|
||||
name?: string; // 群名称
|
||||
avatar?: string; // 群头像
|
||||
notice?: string; // 群公告
|
||||
joinApproval?: boolean; // 进群是否需审批
|
||||
}
|
||||
|
||||
/** 添加 / 撤销群管理员 Request VO */
|
||||
export interface GroupAdminReqVO {
|
||||
id: number; // 群编号
|
||||
userIds: number[]; // 目标用户编号列表
|
||||
}
|
||||
|
||||
/** 群主转让 Request VO */
|
||||
export interface GroupTransferOwnerReqVO {
|
||||
id: number; // 群编号
|
||||
newOwnerUserId: number; // 新群主用户编号
|
||||
}
|
||||
|
||||
/** 全群禁言 / 取消 Request VO */
|
||||
export interface GroupMuteAllReqVO {
|
||||
id: number; // 群编号
|
||||
mutedAll: boolean; // 是否全群禁言
|
||||
}
|
||||
|
||||
/** 成员禁言 Request VO */
|
||||
export interface GroupMuteMemberReqVO {
|
||||
id: number; // 群编号
|
||||
userId: number; // 被禁言的用户编号
|
||||
mutedSeconds: number; // 禁言时长(秒),0 表示永久禁言
|
||||
}
|
||||
|
||||
/** 取消成员禁言 Request VO */
|
||||
export interface GroupCancelMuteMemberReqVO {
|
||||
id: number; // 群编号
|
||||
userId: number; // 被取消禁言的用户编号
|
||||
}
|
||||
}
|
||||
|
||||
// 群消息置顶 / 取消置顶 Request VO
|
||||
export interface ImGroupMessagePinReqVO {
|
||||
id: number // 群编号
|
||||
messageId: number // 消息编号
|
||||
|
||||
/** 获得当前登录用户的群列表 */
|
||||
export function getMyGroupList() {
|
||||
return requestClient.get<ImGroupApi.GroupRespVO[]>('/im/group/list');
|
||||
}
|
||||
|
||||
// 群创建 Request VO
|
||||
export interface ImGroupCreateReqVO {
|
||||
name: string // 群名称
|
||||
memberUserIds?: number[] // 初始成员用户编号列表(建群同时邀请的好友,不含创建者自己)
|
||||
joinApproval?: boolean // 进群是否需审批;不传默认 false 自由进群
|
||||
/** 获得群详情 */
|
||||
export function getGroup(id: number | string) {
|
||||
return requestClient.get<ImGroupApi.GroupRespVO>('/im/group/get', { params: { id } });
|
||||
}
|
||||
|
||||
// 群更新 Request VO
|
||||
export interface ImGroupUpdateReqVO {
|
||||
id: number // 群编号
|
||||
name?: string // 群名称
|
||||
avatar?: string // 群头像
|
||||
notice?: string // 群公告
|
||||
joinApproval?: boolean // 进群是否需审批
|
||||
/** 创建群 */
|
||||
export function createGroup(data: ImGroupApi.GroupCreateReqVO) {
|
||||
return requestClient.post<ImGroupApi.GroupRespVO>('/im/group/create', data);
|
||||
}
|
||||
|
||||
// 添加 / 撤销群管理员 Request VO
|
||||
export interface ImGroupAdminReqVO {
|
||||
id: number // 群编号
|
||||
userIds: number[] // 目标用户编号列表
|
||||
/** 更新群 */
|
||||
export function updateGroup(data: ImGroupApi.GroupUpdateReqVO) {
|
||||
return requestClient.put<ImGroupApi.GroupRespVO>('/im/group/update', data);
|
||||
}
|
||||
|
||||
// 群主转让 Request VO
|
||||
export interface ImGroupTransferOwnerReqVO {
|
||||
id: number // 群编号
|
||||
newOwnerUserId: number // 新群主用户编号
|
||||
/** 解散群 */
|
||||
export function dissolveGroup(id: number | string) {
|
||||
return requestClient.delete<boolean>('/im/group/dissolve', {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
// 全群禁言 / 取消 Request VO
|
||||
export interface ImGroupMuteAllReqVO {
|
||||
id: number // 群编号
|
||||
mutedAll: boolean // 是否全群禁言
|
||||
/** 添加群管理员(仅群主可调) */
|
||||
export function addGroupAdmin(data: ImGroupApi.GroupAdminReqVO) {
|
||||
return requestClient.put<boolean>('/im/group/add-admin', data);
|
||||
}
|
||||
|
||||
// 成员禁言 Request VO
|
||||
export interface ImGroupMuteMemberReqVO {
|
||||
id: number // 群编号
|
||||
userId: number // 被禁言的用户编号
|
||||
mutedSeconds: number // 禁言时长(秒),0 表示永久禁言
|
||||
/** 撤销群管理员(仅群主可调) */
|
||||
export function removeGroupAdmin(data: ImGroupApi.GroupAdminReqVO) {
|
||||
return requestClient.put<boolean>('/im/group/remove-admin', data);
|
||||
}
|
||||
|
||||
// 取消成员禁言 Request VO
|
||||
export interface ImGroupCancelMuteMemberReqVO {
|
||||
id: number // 群编号
|
||||
userId: number // 被取消禁言的用户编号
|
||||
/** 转让群主(仅老群主可调;旧群主转让后降为普通成员) */
|
||||
export function transferGroupOwner(data: ImGroupApi.GroupTransferOwnerReqVO) {
|
||||
return requestClient.put<boolean>('/im/group/transfer-owner', data);
|
||||
}
|
||||
|
||||
// 获得当前登录用户的群列表
|
||||
export const getMyGroupList = () => {
|
||||
return requestClient.get<ImGroupRespVO[]>('/im/group/list')
|
||||
/** 置顶群消息(仅群主 / 管理员可调) */
|
||||
export function pinGroupMessage(data: ImGroupApi.GroupMessagePinReqVO) {
|
||||
return requestClient.put<boolean>('/im/group/pin-message', data);
|
||||
}
|
||||
|
||||
// 获得群详情
|
||||
export const getGroup = (id: number | string) => {
|
||||
return requestClient.get<ImGroupRespVO>('/im/group/get', { params: { id } })
|
||||
/** 取消置顶群消息(仅群主 / 管理员可调) */
|
||||
export function unpinGroupMessage(data: ImGroupApi.GroupMessagePinReqVO) {
|
||||
return requestClient.put<boolean>('/im/group/unpin-message', data);
|
||||
}
|
||||
|
||||
// 创建群
|
||||
export const createGroup = (data: ImGroupCreateReqVO) => {
|
||||
return requestClient.post<ImGroupRespVO>('/im/group/create', data)
|
||||
/** 全群禁言 / 取消(仅群主 / 管理员可调) */
|
||||
export function muteAll(data: ImGroupApi.GroupMuteAllReqVO) {
|
||||
return requestClient.put<boolean>('/im/group/mute-all', data);
|
||||
}
|
||||
|
||||
// 更新群
|
||||
export const updateGroup = (data: ImGroupUpdateReqVO) => {
|
||||
return requestClient.put<ImGroupRespVO>('/im/group/update', data)
|
||||
/** 禁言成员 */
|
||||
export function muteMember(data: ImGroupApi.GroupMuteMemberReqVO) {
|
||||
return requestClient.put<boolean>('/im/group/mute-member', data);
|
||||
}
|
||||
|
||||
// 解散群
|
||||
export const dissolveGroup = (id: number | string) => {
|
||||
return requestClient.delete<boolean>('/im/group/dissolve', { params: { id } })
|
||||
}
|
||||
|
||||
// 添加群管理员(仅群主可调)
|
||||
export const addGroupAdmin = (data: ImGroupAdminReqVO) => {
|
||||
return requestClient.put<boolean>('/im/group/add-admin', data)
|
||||
}
|
||||
|
||||
// 撤销群管理员(仅群主可调)
|
||||
export const removeGroupAdmin = (data: ImGroupAdminReqVO) => {
|
||||
return requestClient.put<boolean>('/im/group/remove-admin', data)
|
||||
}
|
||||
|
||||
// 转让群主(仅老群主可调;旧群主转让后降为普通成员)
|
||||
export const transferGroupOwner = (data: ImGroupTransferOwnerReqVO) => {
|
||||
return requestClient.put<boolean>('/im/group/transfer-owner', data)
|
||||
}
|
||||
|
||||
// 置顶群消息(仅群主 / 管理员可调)
|
||||
export const pinGroupMessage = (data: ImGroupMessagePinReqVO) => {
|
||||
return requestClient.put<boolean>('/im/group/pin-message', data)
|
||||
}
|
||||
|
||||
// 取消置顶群消息(仅群主 / 管理员可调)
|
||||
export const unpinGroupMessage = (data: ImGroupMessagePinReqVO) => {
|
||||
return requestClient.put<boolean>('/im/group/unpin-message', data)
|
||||
}
|
||||
|
||||
// 全群禁言 / 取消(仅群主 / 管理员可调)
|
||||
export const muteAll = (data: ImGroupMuteAllReqVO) => {
|
||||
return requestClient.put<boolean>('/im/group/mute-all', data)
|
||||
}
|
||||
|
||||
// 禁言成员
|
||||
export const muteMember = (data: ImGroupMuteMemberReqVO) => {
|
||||
return requestClient.put<boolean>('/im/group/mute-member', data)
|
||||
}
|
||||
|
||||
// 取消成员禁言
|
||||
export const cancelMuteMember = (data: ImGroupCancelMuteMemberReqVO) => {
|
||||
return requestClient.put<boolean>('/im/group/cancel-mute-member', data)
|
||||
/** 取消成员禁言 */
|
||||
export function cancelMuteMember(data: ImGroupApi.GroupCancelMuteMemberReqVO) {
|
||||
return requestClient.put<boolean>('/im/group/cancel-mute-member', data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,70 +1,78 @@
|
|||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
// 群成员 Response VO
|
||||
export interface ImGroupMemberRespVO {
|
||||
id: number // 编号
|
||||
groupId: number // 群编号
|
||||
userId: number // 用户编号
|
||||
displayUserName?: string // 组内显示名(群主设置的备注)
|
||||
groupRemark?: string // 群备注(当前用户对群的备注)
|
||||
silent?: boolean // 是否免打扰
|
||||
status?: number // 成员状态(0=在群,1=退群)
|
||||
role?: number // 成员角色,参见 ImGroupMemberRole 枚举
|
||||
joinTime?: string // 入群时间
|
||||
quitTime?: string // 退群时间
|
||||
muteEndTime?: string // 禁言到期时间
|
||||
createTime?: string // 创建时间
|
||||
// 聚合字段(自 AdminUser)
|
||||
nickname?: string // 用户昵称
|
||||
avatar?: string // 用户头像
|
||||
export namespace ImGroupMemberApi {
|
||||
/** 群成员 Response VO */
|
||||
export interface GroupMemberRespVO {
|
||||
id: number; // 编号
|
||||
groupId: number; // 群编号
|
||||
userId: number; // 用户编号
|
||||
displayUserName?: string; // 组内显示名(群主设置的备注)
|
||||
groupRemark?: string; // 群备注(当前用户对群的备注)
|
||||
silent?: boolean; // 是否免打扰
|
||||
status?: number; // 成员状态(0=在群,1=退群)
|
||||
role?: number; // 成员角色,参见 ImGroupMemberRole 枚举
|
||||
joinTime?: string; // 入群时间
|
||||
quitTime?: string; // 退群时间
|
||||
muteEndTime?: string; // 禁言到期时间
|
||||
createTime?: string; // 创建时间
|
||||
nickname?: string; // 用户昵称
|
||||
avatar?: string; // 用户头像
|
||||
}
|
||||
|
||||
/** 群成员邀请 Request VO */
|
||||
export interface GroupMemberInviteReqVO {
|
||||
groupId: number; // 群编号
|
||||
memberUserIds: number[]; // 被邀请的用户编号列表
|
||||
}
|
||||
|
||||
/** 群成员移除 Request VO */
|
||||
export interface GroupMemberRemoveReqVO {
|
||||
groupId: number; // 群编号
|
||||
memberUserIds: number[]; // 被移除的用户编号列表
|
||||
}
|
||||
|
||||
/** 群成员更新 Request VO */
|
||||
export interface GroupMemberUpdateReqVO {
|
||||
groupId: number; // 群编号
|
||||
displayUserName?: string; // 群内昵称
|
||||
groupRemark?: string; // 群备注
|
||||
silent?: boolean; // 是否免打扰
|
||||
}
|
||||
}
|
||||
|
||||
// 群成员邀请 Request VO
|
||||
export interface ImGroupMemberInviteReqVO {
|
||||
groupId: number // 群编号
|
||||
memberUserIds: number[] // 被邀请的用户编号列表
|
||||
|
||||
/** 邀请用户加入群 */
|
||||
export function inviteGroupMember(data: ImGroupMemberApi.GroupMemberInviteReqVO) {
|
||||
return requestClient.post<boolean>('/im/group/invite', data);
|
||||
}
|
||||
|
||||
// 群成员移除 Request VO
|
||||
export interface ImGroupMemberRemoveReqVO {
|
||||
groupId: number // 群编号
|
||||
memberUserIds: number[] // 被移除的用户编号列表
|
||||
/** 退出群 */
|
||||
export function quitGroup(groupId: number | string) {
|
||||
return requestClient.delete<boolean>('/im/group/quit', {
|
||||
params: { groupId },
|
||||
});
|
||||
}
|
||||
|
||||
// 群成员更新 Request VO
|
||||
export interface ImGroupMemberUpdateReqVO {
|
||||
groupId: number // 群编号
|
||||
displayUserName?: string // 群内昵称
|
||||
groupRemark?: string // 群备注
|
||||
silent?: boolean // 是否免打扰
|
||||
/** 移除群成员 */
|
||||
export function removeGroupMember(data: ImGroupMemberApi.GroupMemberRemoveReqVO) {
|
||||
return requestClient.delete<boolean>('/im/group/kicking', { data });
|
||||
}
|
||||
|
||||
// 邀请用户加入群
|
||||
export const inviteGroupMember = (data: ImGroupMemberInviteReqVO) => {
|
||||
return requestClient.post<boolean>('/im/group/invite', data)
|
||||
/** 获得群成员详情 */
|
||||
export function getGroupMember(groupId: number, userId: number) {
|
||||
return requestClient.get<ImGroupMemberApi.GroupMemberRespVO>('/im/group-member/get', {
|
||||
params: { groupId, userId },
|
||||
});
|
||||
}
|
||||
|
||||
// 退出群
|
||||
export const quitGroup = (groupId: number | string) => {
|
||||
return requestClient.delete<boolean>('/im/group/quit', { params: { groupId } })
|
||||
/** 获得指定群的成员列表(聚合 AdminUser 昵称 / 头像) */
|
||||
export function getGroupMemberList(groupId: number | string) {
|
||||
return requestClient.get<ImGroupMemberApi.GroupMemberRespVO[]>('/im/group-member/list', {
|
||||
params: { groupId },
|
||||
});
|
||||
}
|
||||
|
||||
// 移除群成员
|
||||
export const removeGroupMember = (data: ImGroupMemberRemoveReqVO) => {
|
||||
return requestClient.delete<boolean>('/im/group/kicking', { data })
|
||||
}
|
||||
|
||||
// 获得群成员详情
|
||||
export const getGroupMember = (groupId: number, userId: number) => {
|
||||
return requestClient.get<ImGroupMemberRespVO>('/im/group-member/get', { params: { groupId, userId } })
|
||||
}
|
||||
|
||||
// 获得指定群的成员列表(聚合 AdminUser 昵称 / 头像)
|
||||
export const getGroupMemberList = (groupId: number | string) => {
|
||||
return requestClient.get<ImGroupMemberRespVO[]>('/im/group-member/list', { params: { groupId } })
|
||||
}
|
||||
|
||||
// 更新群成员
|
||||
export const updateGroupMember = (data: ImGroupMemberUpdateReqVO) => {
|
||||
return requestClient.put<boolean>('/im/group-member/update', data)
|
||||
/** 更新群成员 */
|
||||
export function updateGroupMember(data: ImGroupMemberApi.GroupMemberUpdateReqVO) {
|
||||
return requestClient.put<boolean>('/im/group-member/update', data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,66 +1,90 @@
|
|||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
// IM 加群申请 Response VO
|
||||
export interface ImGroupRequestRespVO {
|
||||
id: number // 申请编号
|
||||
groupId: number // 群编号
|
||||
userId: number // 申请人 / 被邀请人用户编号
|
||||
inviterUserId?: number // 邀请人;NULL 表示用户主动申请
|
||||
handleResult: number // 处理结果;0=未处理;1=同意;2=拒绝
|
||||
applyContent?: string // 申请理由
|
||||
handleContent?: string // 处理理由(拒绝时可选填)
|
||||
handleUserId?: number // 处理人用户编号
|
||||
addSource?: number // 加入来源;参见 ImGroupAddSourceEnum
|
||||
handleTime?: string // 处理时间
|
||||
createTime: string // 申请创建时间
|
||||
updateTime?: number // 最近更新时间(毫秒时间戳,增量拉取游标用)
|
||||
// 聚合字段
|
||||
userNickname?: string // 申请人 / 被邀请人昵称
|
||||
userAvatar?: string // 申请人 / 被邀请人头像
|
||||
inviterNickname?: string // 邀请人昵称
|
||||
inviterAvatar?: string // 邀请人头像
|
||||
groupName?: string // 群名称
|
||||
groupAvatar?: string // 群头像
|
||||
export namespace ImGroupRequestApi {
|
||||
/** IM 加群申请 Response VO */
|
||||
export interface GroupRequestRespVO {
|
||||
id: number; // 申请编号
|
||||
groupId: number; // 群编号
|
||||
userId: number; // 申请人 / 被邀请人用户编号
|
||||
inviterUserId?: number; // 邀请人;NULL 表示用户主动申请
|
||||
handleResult: number; // 处理结果;0=未处理;1=同意;2=拒绝
|
||||
applyContent?: string; // 申请理由
|
||||
handleContent?: string; // 处理理由(拒绝时可选填)
|
||||
handleUserId?: number; // 处理人用户编号
|
||||
addSource?: number; // 加入来源;参见 ImGroupAddSourceEnum
|
||||
handleTime?: string; // 处理时间
|
||||
createTime: string; // 申请创建时间
|
||||
updateTime?: number; // 最近更新时间(毫秒时间戳,增量拉取游标用)
|
||||
userNickname?: string; // 申请人 / 被邀请人昵称
|
||||
userAvatar?: string; // 申请人 / 被邀请人头像
|
||||
inviterNickname?: string; // 邀请人昵称
|
||||
inviterAvatar?: string; // 邀请人头像
|
||||
groupName?: string; // 群名称
|
||||
groupAvatar?: string; // 群头像
|
||||
}
|
||||
|
||||
/** IM 加群申请发起 Request VO */
|
||||
export interface GroupRequestApplyReqVO {
|
||||
groupId: number; // 群编号
|
||||
applyContent?: string; // 申请理由
|
||||
addSource?: number; // 加入来源
|
||||
}
|
||||
}
|
||||
|
||||
// IM 加群申请发起 Request VO
|
||||
export interface ImGroupRequestApplyReqVO {
|
||||
groupId: number // 群编号
|
||||
applyContent?: string // 申请理由
|
||||
addSource?: number // 加入来源
|
||||
|
||||
/** 申请加群 */
|
||||
export function applyJoinGroup(data: ImGroupRequestApi.GroupRequestApplyReqVO) {
|
||||
return requestClient.post<null | number>('/im/group-request/apply', data);
|
||||
}
|
||||
|
||||
// 申请加群
|
||||
export const applyJoinGroup = (data: ImGroupRequestApplyReqVO) => {
|
||||
return requestClient.post<null | number>('/im/group-request/apply', data)
|
||||
/** 同意加群申请(群主或管理员) */
|
||||
export function agreeGroupRequest(id: number | string) {
|
||||
return requestClient.put<boolean>('/im/group-request/agree', undefined, {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
// 同意加群申请(群主或管理员)
|
||||
export const agreeGroupRequest = (id: number | string) => {
|
||||
return requestClient.put<boolean>('/im/group-request/agree', undefined, { params: { id } })
|
||||
/** 拒绝加群申请(群主或管理员) */
|
||||
export function refuseGroupRequest(
|
||||
id: number | string,
|
||||
handleContent?: string,
|
||||
) {
|
||||
return requestClient.put<boolean>('/im/group-request/refuse', undefined, {
|
||||
params: { id, handleContent },
|
||||
});
|
||||
}
|
||||
|
||||
// 拒绝加群申请(群主或管理员)
|
||||
export const refuseGroupRequest = (id: number | string, handleContent?: string) => {
|
||||
return requestClient.put<boolean>('/im/group-request/refuse', undefined, { params: { id, handleContent } })
|
||||
/** 查询「我管理的所有群」下的未处理加群申请列表(不分页);前端 store 据此派生横幅红点 + Drawer 列表 */
|
||||
export function getUnhandledRequestList() {
|
||||
return requestClient.get<ImGroupRequestApi.GroupRequestRespVO[]>(
|
||||
'/im/group-request/unhandled-list',
|
||||
);
|
||||
}
|
||||
|
||||
// 查询「我管理的所有群」下的未处理加群申请列表(不分页);前端 store 据此派生横幅红点 + Drawer 列表
|
||||
export const getUnhandledRequestList = () => {
|
||||
return requestClient.get<ImGroupRequestRespVO[]>('/im/group-request/unhandled-list')
|
||||
/** 查询指定群下的全部加群申请(含已处理);仅群主 / 管理员可查 */
|
||||
export function getGroupRequestListByGroupId(groupId: number) {
|
||||
return requestClient.get<ImGroupRequestApi.GroupRequestRespVO[]>(
|
||||
'/im/group-request/list-by-group',
|
||||
{ params: { groupId } },
|
||||
);
|
||||
}
|
||||
|
||||
// 查询指定群下的全部加群申请(含已处理);仅群主 / 管理员可查
|
||||
export const getGroupRequestListByGroupId = (groupId: number) => {
|
||||
return requestClient.get<ImGroupRequestRespVO[]>('/im/group-request/list-by-group', { params: { groupId } })
|
||||
/** 按 id 单查申请记录(带越权过滤;WebSocket 通知到达后用) */
|
||||
export function getMyGroupRequest(id: number) {
|
||||
return requestClient.get<ImGroupRequestApi.GroupRequestRespVO | null>(
|
||||
'/im/group-request/get',
|
||||
{ params: { id } },
|
||||
);
|
||||
}
|
||||
|
||||
// 按 id 单查申请记录(带越权过滤;WebSocket 通知到达后用)
|
||||
export const getMyGroupRequest = (id: number) => {
|
||||
return requestClient.get<ImGroupRequestRespVO | null>('/im/group-request/get', { params: { id } })
|
||||
}
|
||||
|
||||
// 增量拉取我管理的所有群下加群申请变更(重连 / 离线补偿)
|
||||
export const pullMyGroupRequestList = (params: { lastId?: number; lastUpdateTime?: number; limit: number }) => {
|
||||
return requestClient.get<ImGroupRequestRespVO[]>('/im/group-request/pull', { params })
|
||||
/** 增量拉取我管理的所有群下加群申请变更(重连 / 离线补偿) */
|
||||
export function pullMyGroupRequestList(params: {
|
||||
lastId?: number;
|
||||
lastUpdateTime?: number;
|
||||
limit: number;
|
||||
}) {
|
||||
return requestClient.get<ImGroupRequestApi.GroupRequestRespVO[]>(
|
||||
'/im/group-request/pull',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +1,56 @@
|
|||
import type { PageParam } from '@vben/request'
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export interface ImManagerChannelVO {
|
||||
id: number
|
||||
code: string
|
||||
name: string
|
||||
avatar?: string
|
||||
sort: number
|
||||
status: number
|
||||
createTime?: Date
|
||||
export namespace ImManagerChannelApi {
|
||||
/** 频道 */
|
||||
export interface Channel {
|
||||
id: number;
|
||||
code: string;
|
||||
name: string;
|
||||
avatar?: string;
|
||||
sort: number;
|
||||
status: number;
|
||||
createTime?: Date;
|
||||
}
|
||||
}
|
||||
|
||||
// 获得频道分页
|
||||
export const getManagerChannelPage = (params: PageParam) => {
|
||||
return requestClient.get('/im/manager/channel/page', { params })
|
||||
|
||||
/** 获得频道分页 */
|
||||
export function getManagerChannelPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<ImManagerChannelApi.Channel>>(
|
||||
'/im/manager/channel/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
// 获得频道详情
|
||||
export const getManagerChannel = (id: number) => {
|
||||
return requestClient.get('/im/manager/channel/get', { params: { id } })
|
||||
/** 获得频道详情 */
|
||||
export function getManagerChannel(id: number) {
|
||||
return requestClient.get<ImManagerChannelApi.Channel>('/im/manager/channel/get', {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
// 新增频道
|
||||
export const createManagerChannel = (data: ImManagerChannelVO) => {
|
||||
return requestClient.post('/im/manager/channel/create', data)
|
||||
/** 新增频道 */
|
||||
export function createManagerChannel(data: ImManagerChannelApi.Channel) {
|
||||
return requestClient.post<number>('/im/manager/channel/create', data);
|
||||
}
|
||||
|
||||
// 修改频道
|
||||
export const updateManagerChannel = (data: ImManagerChannelVO) => {
|
||||
return requestClient.put('/im/manager/channel/update', data)
|
||||
/** 修改频道 */
|
||||
export function updateManagerChannel(data: ImManagerChannelApi.Channel) {
|
||||
return requestClient.put<boolean>('/im/manager/channel/update', data);
|
||||
}
|
||||
|
||||
// 删除频道
|
||||
export const deleteManagerChannel = (id: number) => {
|
||||
return requestClient.delete('/im/manager/channel/delete', { params: { id } })
|
||||
/** 删除频道 */
|
||||
export function deleteManagerChannel(id: number) {
|
||||
return requestClient.delete<boolean>('/im/manager/channel/delete', {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
// 获得启用的频道精简列表(表单选择用)
|
||||
export const getSimpleChannelList = () => {
|
||||
return requestClient.get<ImManagerChannelVO[]>('/im/manager/channel/simple-list')
|
||||
/** 获得启用的频道精简列表(表单选择用) */
|
||||
export function getSimpleChannelList() {
|
||||
return requestClient.get<ImManagerChannelApi.Channel[]>(
|
||||
'/im/manager/channel/simple-list',
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,46 +1,68 @@
|
|||
import type { PageParam } from '@vben/request'
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export interface ImManagerChannelMaterialVO {
|
||||
id: number
|
||||
channelId: number
|
||||
channelName?: string
|
||||
type: number
|
||||
title: string
|
||||
coverUrl?: string
|
||||
summary?: string
|
||||
content?: string
|
||||
url?: string
|
||||
createTime?: Date
|
||||
export namespace ImManagerChannelMaterialApi {
|
||||
/** 频道素材 */
|
||||
export interface Material {
|
||||
id: number;
|
||||
channelId: number;
|
||||
channelName?: string;
|
||||
type: number;
|
||||
title: string;
|
||||
coverUrl?: string;
|
||||
summary?: string;
|
||||
content?: string;
|
||||
url?: string;
|
||||
createTime?: Date;
|
||||
}
|
||||
}
|
||||
|
||||
// 获得素材分页
|
||||
export const getManagerChannelMaterialPage = (params: PageParam) => {
|
||||
return requestClient.get('/im/manager/channel-material/page', { params })
|
||||
|
||||
/** 获得素材分页 */
|
||||
export function getManagerChannelMaterialPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<ImManagerChannelMaterialApi.Material>>(
|
||||
'/im/manager/channel-material/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
// 获得指定频道下的素材精简列表
|
||||
export const getSimpleManagerChannelMaterialList = (channelId: number) => {
|
||||
return requestClient.get('/im/manager/channel-material/simple-list', { params: { channelId } })
|
||||
/** 获得指定频道下的素材精简列表 */
|
||||
export function getSimpleManagerChannelMaterialList(channelId: number) {
|
||||
return requestClient.get<ImManagerChannelMaterialApi.Material[]>(
|
||||
'/im/manager/channel-material/simple-list',
|
||||
{ params: { channelId } },
|
||||
);
|
||||
}
|
||||
|
||||
// 获得素材详情
|
||||
export const getManagerChannelMaterial = (id: number) => {
|
||||
return requestClient.get('/im/manager/channel-material/get', { params: { id } })
|
||||
/** 获得素材详情 */
|
||||
export function getManagerChannelMaterial(id: number) {
|
||||
return requestClient.get<ImManagerChannelMaterialApi.Material>(
|
||||
'/im/manager/channel-material/get',
|
||||
{ params: { id } },
|
||||
);
|
||||
}
|
||||
|
||||
// 新增素材
|
||||
export const createManagerChannelMaterial = (data: ImManagerChannelMaterialVO) => {
|
||||
return requestClient.post('/im/manager/channel-material/create', data)
|
||||
/** 新增素材 */
|
||||
export function createManagerChannelMaterial(
|
||||
data: ImManagerChannelMaterialApi.Material,
|
||||
) {
|
||||
return requestClient.post<number>('/im/manager/channel-material/create', data);
|
||||
}
|
||||
|
||||
// 修改素材
|
||||
export const updateManagerChannelMaterial = (data: ImManagerChannelMaterialVO) => {
|
||||
return requestClient.put('/im/manager/channel-material/update', data)
|
||||
/** 修改素材 */
|
||||
export function updateManagerChannelMaterial(
|
||||
data: ImManagerChannelMaterialApi.Material,
|
||||
) {
|
||||
return requestClient.put<boolean>(
|
||||
'/im/manager/channel-material/update',
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
// 删除素材
|
||||
export const deleteManagerChannelMaterial = (id: number) => {
|
||||
return requestClient.delete('/im/manager/channel-material/delete', { params: { id } })
|
||||
/** 删除素材 */
|
||||
export function deleteManagerChannelMaterial(id: number) {
|
||||
return requestClient.delete<boolean>('/im/manager/channel-material/delete', {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,48 @@
|
|||
import type { PageParam } from '@vben/request'
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export interface ImManagerChannelMessageVO {
|
||||
id: number
|
||||
channelId: number
|
||||
channelName?: string
|
||||
materialId: number
|
||||
materialTitle?: string
|
||||
materialCoverUrl?: string
|
||||
type: number
|
||||
content?: string
|
||||
receiverUserIds?: number[]
|
||||
sendTime?: Date
|
||||
export namespace ImManagerChannelMessageApi {
|
||||
/** 频道消息 */
|
||||
export interface ChannelMessage {
|
||||
id: number;
|
||||
channelId: number;
|
||||
channelName?: string;
|
||||
materialId: number;
|
||||
materialTitle?: string;
|
||||
materialCoverUrl?: string;
|
||||
type: number;
|
||||
content?: string;
|
||||
receiverUserIds?: number[];
|
||||
sendTime?: Date;
|
||||
}
|
||||
|
||||
/** 频道消息发送请求 */
|
||||
export interface ChannelMessageSendReqVO {
|
||||
materialId: number;
|
||||
receiverUserIds?: number[];
|
||||
}
|
||||
}
|
||||
|
||||
export interface ImManagerChannelMessageSendReqVO {
|
||||
materialId: number
|
||||
receiverUserIds?: number[]
|
||||
|
||||
/** 立即推送频道消息 */
|
||||
export function sendManagerChannelMessage(
|
||||
data: ImManagerChannelMessageApi.ChannelMessageSendReqVO,
|
||||
) {
|
||||
return requestClient.post<number>('/im/manager/channel-message/send', data);
|
||||
}
|
||||
|
||||
// 立即推送频道消息
|
||||
export const sendManagerChannelMessage = (data: ImManagerChannelMessageSendReqVO) => {
|
||||
return requestClient.post('/im/manager/channel-message/send', data)
|
||||
/** 删除频道消息 */
|
||||
export function deleteManagerChannelMessage(id: number) {
|
||||
return requestClient.delete<boolean>('/im/manager/channel-message/delete', {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
// 删除频道消息
|
||||
export const deleteManagerChannelMessage = (id: number) => {
|
||||
return requestClient.delete('/im/manager/channel-message/delete', { params: { id } })
|
||||
}
|
||||
|
||||
// 获得频道消息分页
|
||||
export const getManagerChannelMessagePage = (params: PageParam) => {
|
||||
return requestClient.get('/im/manager/channel-message/page', { params })
|
||||
/** 获得频道消息分页 */
|
||||
export function getManagerChannelMessagePage(params: PageParam) {
|
||||
return requestClient.get<PageResult<ImManagerChannelMessageApi.ChannelMessage>>(
|
||||
'/im/manager/channel-message/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,47 +1,63 @@
|
|||
import type { PageParam } from '@vben/request'
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export interface ImManagerFacePackItemVO {
|
||||
id: number
|
||||
packId: number
|
||||
url: string
|
||||
name?: string
|
||||
width: number
|
||||
height: number
|
||||
sort: number
|
||||
status: number
|
||||
createTime?: Date
|
||||
export namespace ImManagerFacePackItemApi {
|
||||
/** 表情项 */
|
||||
export interface FacePackItem {
|
||||
id: number;
|
||||
packId: number;
|
||||
url: string;
|
||||
name?: string;
|
||||
width: number;
|
||||
height: number;
|
||||
sort: number;
|
||||
status: number;
|
||||
createTime?: Date;
|
||||
}
|
||||
}
|
||||
|
||||
// 获得表情分页
|
||||
export const getManagerFacePackItemPage = (params: PageParam) => {
|
||||
return requestClient.get('/im/manager/face-pack-item/page', { params })
|
||||
|
||||
/** 获得表情分页 */
|
||||
export function getManagerFacePackItemPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<ImManagerFacePackItemApi.FacePackItem>>(
|
||||
'/im/manager/face-pack-item/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
// 获得表情详情
|
||||
export const getManagerFacePackItem = (id: number) => {
|
||||
return requestClient.get('/im/manager/face-pack-item/get', { params: { id } })
|
||||
/** 获得表情详情 */
|
||||
export function getManagerFacePackItem(id: number) {
|
||||
return requestClient.get<ImManagerFacePackItemApi.FacePackItem>(
|
||||
'/im/manager/face-pack-item/get',
|
||||
{ params: { id } },
|
||||
);
|
||||
}
|
||||
|
||||
// 新增表情
|
||||
export const createManagerFacePackItem = (data: ImManagerFacePackItemVO) => {
|
||||
return requestClient.post('/im/manager/face-pack-item/create', data)
|
||||
/** 新增表情 */
|
||||
export function createManagerFacePackItem(data: ImManagerFacePackItemApi.FacePackItem) {
|
||||
return requestClient.post<number>('/im/manager/face-pack-item/create', data);
|
||||
}
|
||||
|
||||
// 修改表情
|
||||
export const updateManagerFacePackItem = (data: ImManagerFacePackItemVO) => {
|
||||
return requestClient.put('/im/manager/face-pack-item/update', data)
|
||||
/** 修改表情 */
|
||||
export function updateManagerFacePackItem(data: ImManagerFacePackItemApi.FacePackItem) {
|
||||
return requestClient.put<boolean>(
|
||||
'/im/manager/face-pack-item/update',
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
// 删除表情
|
||||
export const deleteManagerFacePackItem = (id: number) => {
|
||||
return requestClient.delete('/im/manager/face-pack-item/delete', { params: { id } })
|
||||
/** 删除表情 */
|
||||
export function deleteManagerFacePackItem(id: number) {
|
||||
return requestClient.delete<boolean>('/im/manager/face-pack-item/delete', {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
// 批量删除表情
|
||||
export const deleteManagerFacePackItemList = (ids: number[]) => {
|
||||
return requestClient.delete('/im/manager/face-pack-item/delete-list', {
|
||||
params: { ids: ids.join(',') }
|
||||
})
|
||||
/** 批量删除表情 */
|
||||
export function deleteManagerFacePackItemList(ids: number[]) {
|
||||
return requestClient.delete<boolean>(
|
||||
'/im/manager/face-pack-item/delete-list',
|
||||
{ params: { ids: ids.join(',') } },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,55 @@
|
|||
import type { PageParam } from '@vben/request'
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export interface ImManagerFacePackVO {
|
||||
id: number
|
||||
name: string
|
||||
icon?: string
|
||||
sort: number
|
||||
status: number
|
||||
createTime?: Date
|
||||
export namespace ImManagerFacePackApi {
|
||||
/** 表情包 */
|
||||
export interface FacePack {
|
||||
id: number;
|
||||
name: string;
|
||||
icon?: string;
|
||||
sort: number;
|
||||
status: number;
|
||||
createTime?: Date;
|
||||
}
|
||||
}
|
||||
|
||||
// 获得表情包分页
|
||||
export const getManagerFacePackPage = (params: PageParam) => {
|
||||
return requestClient.get('/im/manager/face-pack/page', { params })
|
||||
|
||||
/** 获得表情包分页 */
|
||||
export function getManagerFacePackPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<ImManagerFacePackApi.FacePack>>(
|
||||
'/im/manager/face-pack/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
// 获得表情包详情
|
||||
export const getManagerFacePack = (id: number) => {
|
||||
return requestClient.get('/im/manager/face-pack/get', { params: { id } })
|
||||
/** 获得表情包详情 */
|
||||
export function getManagerFacePack(id: number) {
|
||||
return requestClient.get<ImManagerFacePackApi.FacePack>('/im/manager/face-pack/get', {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
// 新增表情包
|
||||
export const createManagerFacePack = (data: ImManagerFacePackVO) => {
|
||||
return requestClient.post('/im/manager/face-pack/create', data)
|
||||
/** 新增表情包 */
|
||||
export function createManagerFacePack(data: ImManagerFacePackApi.FacePack) {
|
||||
return requestClient.post<number>('/im/manager/face-pack/create', data);
|
||||
}
|
||||
|
||||
// 修改表情包
|
||||
export const updateManagerFacePack = (data: ImManagerFacePackVO) => {
|
||||
return requestClient.put('/im/manager/face-pack/update', data)
|
||||
/** 修改表情包 */
|
||||
export function updateManagerFacePack(data: ImManagerFacePackApi.FacePack) {
|
||||
return requestClient.put<boolean>('/im/manager/face-pack/update', data);
|
||||
}
|
||||
|
||||
// 删除表情包
|
||||
export const deleteManagerFacePack = (id: number) => {
|
||||
return requestClient.delete('/im/manager/face-pack/delete', { params: { id } })
|
||||
/** 删除表情包 */
|
||||
export function deleteManagerFacePack(id: number) {
|
||||
return requestClient.delete<boolean>('/im/manager/face-pack/delete', {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
// 批量删除表情包
|
||||
export const deleteManagerFacePackList = (ids: number[]) => {
|
||||
return requestClient.delete('/im/manager/face-pack/delete-list', {
|
||||
params: { ids: ids.join(',') }
|
||||
})
|
||||
/** 批量删除表情包 */
|
||||
export function deleteManagerFacePackList(ids: number[]) {
|
||||
return requestClient.delete<boolean>('/im/manager/face-pack/delete-list', {
|
||||
params: { ids: ids.join(',') },
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,33 @@
|
|||
import type { PageParam } from '@vben/request'
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export interface ImManagerFaceUserItemVO {
|
||||
id: number
|
||||
userId: number
|
||||
userNickname?: string
|
||||
url: string
|
||||
name?: string
|
||||
width?: number
|
||||
height?: number
|
||||
createTime?: Date
|
||||
export namespace ImManagerFaceUserItemApi {
|
||||
/** 用户表情 */
|
||||
export interface FaceUserItem {
|
||||
id: number;
|
||||
userId: number;
|
||||
userNickname?: string;
|
||||
url: string;
|
||||
name?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
createTime?: Date;
|
||||
}
|
||||
}
|
||||
|
||||
// 获得用户表情分页
|
||||
export const getManagerFaceUserItemPage = (params: PageParam) => {
|
||||
return requestClient.get('/im/manager/face-user-item/page', { params })
|
||||
|
||||
/** 获得用户表情分页 */
|
||||
export function getManagerFaceUserItemPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<ImManagerFaceUserItemApi.FaceUserItem>>(
|
||||
'/im/manager/face-user-item/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
// 删除用户表情
|
||||
export const deleteManagerFaceUserItem = (id: number) => {
|
||||
return requestClient.delete('/im/manager/face-user-item/delete', { params: { id } })
|
||||
/** 删除用户表情 */
|
||||
export function deleteManagerFaceUserItem(id: number) {
|
||||
return requestClient.delete<boolean>('/im/manager/face-user-item/delete', {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,32 @@
|
|||
import type { PageParam } from '@vben/request'
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export interface ImManagerFriendVO {
|
||||
id: number
|
||||
userId: number
|
||||
userNickname?: string
|
||||
friendUserId: number
|
||||
friendNickname?: string
|
||||
displayName?: string
|
||||
addSource?: number
|
||||
silent: boolean
|
||||
pinned: boolean
|
||||
blocked: boolean
|
||||
status: number
|
||||
addTime?: Date
|
||||
deleteTime?: Date
|
||||
createTime: Date
|
||||
export namespace ImManagerFriendApi {
|
||||
/** 好友关系 */
|
||||
export interface Friend {
|
||||
id: number;
|
||||
userId: number;
|
||||
userNickname?: string;
|
||||
friendUserId: number;
|
||||
friendNickname?: string;
|
||||
displayName?: string;
|
||||
addSource?: number;
|
||||
silent: boolean;
|
||||
pinned: boolean;
|
||||
blocked: boolean;
|
||||
status: number;
|
||||
addTime?: Date;
|
||||
deleteTime?: Date;
|
||||
createTime: Date;
|
||||
}
|
||||
}
|
||||
|
||||
// 获得好友关系分页
|
||||
export const getManagerFriendPage = (params: PageParam) => {
|
||||
return requestClient.get('/im/manager/friend/page', { params })
|
||||
|
||||
/** 获得好友关系分页 */
|
||||
export function getManagerFriendPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<ImManagerFriendApi.Friend>>(
|
||||
'/im/manager/friend/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,30 @@
|
|||
import type { PageParam } from '@vben/request'
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export interface ImManagerFriendRequestVO {
|
||||
id: number
|
||||
fromUserId: number
|
||||
fromNickname?: string
|
||||
toUserId: number
|
||||
toNickname?: string
|
||||
applyContent?: string
|
||||
displayName?: string
|
||||
addSource?: number
|
||||
handleResult: number
|
||||
handleContent?: string
|
||||
handleTime?: Date
|
||||
createTime: Date
|
||||
export namespace ImManagerFriendRequestApi {
|
||||
/** 好友申请 */
|
||||
export interface FriendRequest {
|
||||
id: number;
|
||||
fromUserId: number;
|
||||
fromNickname?: string;
|
||||
toUserId: number;
|
||||
toNickname?: string;
|
||||
applyContent?: string;
|
||||
displayName?: string;
|
||||
addSource?: number;
|
||||
handleResult: number;
|
||||
handleContent?: string;
|
||||
handleTime?: Date;
|
||||
createTime: Date;
|
||||
}
|
||||
}
|
||||
|
||||
// 获得好友申请分页
|
||||
export const getManagerFriendRequestPage = (params: PageParam) => {
|
||||
return requestClient.get('/im/manager/friend-request/page', { params })
|
||||
|
||||
/** 获得好友申请分页 */
|
||||
export function getManagerFriendRequestPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<ImManagerFriendRequestApi.FriendRequest>>(
|
||||
'/im/manager/friend-request/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,64 +1,87 @@
|
|||
import type { PageParam } from '@vben/request'
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export interface ImManagerGroupVO {
|
||||
id: number
|
||||
name: string
|
||||
avatar?: string
|
||||
notice?: string
|
||||
ownerUserId: number
|
||||
ownerNickname?: string
|
||||
memberCount?: number
|
||||
status: number
|
||||
banned: boolean
|
||||
mutedAll?: boolean
|
||||
bannedReason?: string
|
||||
bannedTime?: Date
|
||||
dissolvedTime?: Date
|
||||
createTime: Date
|
||||
export namespace ImManagerGroupApi {
|
||||
/** 群 */
|
||||
export interface Group {
|
||||
id: number;
|
||||
name: string;
|
||||
avatar?: string;
|
||||
notice?: string;
|
||||
ownerUserId: number;
|
||||
ownerNickname?: string;
|
||||
memberCount?: number;
|
||||
status: number;
|
||||
banned: boolean;
|
||||
mutedAll?: boolean; // 是否全群禁言
|
||||
bannedReason?: string;
|
||||
bannedTime?: Date;
|
||||
dissolvedTime?: Date;
|
||||
createTime: Date;
|
||||
}
|
||||
|
||||
/** 群成员 */
|
||||
export interface GroupMember {
|
||||
userId: number;
|
||||
nickname?: string;
|
||||
avatar?: string;
|
||||
displayUserName?: string;
|
||||
groupRemark?: string;
|
||||
silent?: boolean;
|
||||
status: number;
|
||||
role?: number; // 成员角色,参见 ImGroupMemberRole 枚举
|
||||
joinTime?: Date;
|
||||
quitTime?: Date;
|
||||
muteEndTime?: Date; // 禁言到期时间
|
||||
}
|
||||
|
||||
/** 群封禁请求 */
|
||||
export interface GroupBanReqVO {
|
||||
id: number;
|
||||
reason: string;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ImManagerGroupMemberVO {
|
||||
userId: number
|
||||
nickname?: string
|
||||
avatar?: string
|
||||
displayUserName?: string
|
||||
groupRemark?: string
|
||||
silent?: boolean
|
||||
status: number
|
||||
role?: number
|
||||
joinTime?: Date
|
||||
quitTime?: Date
|
||||
muteEndTime?: Date
|
||||
|
||||
/** 获得群分页 */
|
||||
export function getManagerGroupPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<ImManagerGroupApi.Group>>(
|
||||
'/im/manager/group/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
// 获得群分页
|
||||
export const getManagerGroupPage = (params: PageParam) => {
|
||||
return requestClient.get('/im/manager/group/page', { params })
|
||||
/** 获得群详情 */
|
||||
export function getManagerGroup(id: number) {
|
||||
return requestClient.get<ImManagerGroupApi.Group>('/im/manager/group/get', {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
// 获得群详情
|
||||
export const getManagerGroup = (id: number) => {
|
||||
return requestClient.get('/im/manager/group/get', { params: { id } })
|
||||
/** 封禁群 */
|
||||
export function banManagerGroup(data: ImManagerGroupApi.GroupBanReqVO) {
|
||||
return requestClient.put<boolean>('/im/manager/group/ban', data);
|
||||
}
|
||||
|
||||
// 封禁群
|
||||
export const banManagerGroup = (data: { id: number; reason: string }) => {
|
||||
return requestClient.put('/im/manager/group/ban', data)
|
||||
/** 解封群 */
|
||||
export function unbanManagerGroup(id: number) {
|
||||
return requestClient.put<boolean>('/im/manager/group/unban', undefined, {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
// 解封群
|
||||
export const unbanManagerGroup = (id: number) => {
|
||||
return requestClient.put('/im/manager/group/unban', undefined, { params: { id } })
|
||||
/** 解散群 */
|
||||
export function dissolveManagerGroup(id: number) {
|
||||
return requestClient.delete<boolean>('/im/manager/group/dissolve', {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
// 解散群
|
||||
export const dissolveManagerGroup = (id: number) => {
|
||||
return requestClient.delete('/im/manager/group/dissolve', { params: { id } })
|
||||
}
|
||||
|
||||
// 获得群成员列表
|
||||
export const getManagerGroupMemberList = (groupId: number) => {
|
||||
return requestClient.get('/im/manager/group/member/list', { params: { groupId } })
|
||||
/** 获得群成员列表(含已退群成员,由前端按需过滤) */
|
||||
export function getManagerGroupMemberList(groupId: number) {
|
||||
return requestClient.get<ImManagerGroupApi.GroupMember[]>(
|
||||
'/im/manager/group/member/list',
|
||||
{ params: { groupId } },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,33 @@
|
|||
import type { PageParam } from '@vben/request'
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export interface ImManagerGroupRequestVO {
|
||||
id: number
|
||||
groupId: number
|
||||
groupName?: string
|
||||
userId: number
|
||||
userNickname?: string
|
||||
inviterUserId?: number
|
||||
inviterNickname?: string
|
||||
applyContent?: string
|
||||
addSource?: number
|
||||
handleResult: number
|
||||
handleUserId?: number
|
||||
handleNickname?: string
|
||||
handleContent?: string
|
||||
handleTime?: Date
|
||||
createTime: Date
|
||||
export namespace ImManagerGroupRequestApi {
|
||||
/** 加群申请 */
|
||||
export interface GroupRequest {
|
||||
id: number;
|
||||
groupId: number;
|
||||
groupName?: string;
|
||||
userId: number;
|
||||
userNickname?: string;
|
||||
inviterUserId?: number;
|
||||
inviterNickname?: string;
|
||||
applyContent?: string;
|
||||
addSource?: number;
|
||||
handleResult: number;
|
||||
handleUserId?: number;
|
||||
handleNickname?: string;
|
||||
handleContent?: string;
|
||||
handleTime?: Date;
|
||||
createTime: Date;
|
||||
}
|
||||
}
|
||||
|
||||
// 获得加群申请分页
|
||||
export const getManagerGroupRequestPage = (params: PageParam) => {
|
||||
return requestClient.get('/im/manager/group-request/page', { params })
|
||||
|
||||
/** 获得加群申请分页 */
|
||||
export function getManagerGroupRequestPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<ImManagerGroupRequestApi.GroupRequest>>(
|
||||
'/im/manager/group-request/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,40 @@
|
|||
import type { PageParam } from '@vben/request'
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export interface ImManagerGroupMessageVO {
|
||||
id: number
|
||||
clientMessageId?: string
|
||||
groupId: number
|
||||
groupName?: string
|
||||
senderId: number
|
||||
senderNickname?: string
|
||||
type: number
|
||||
content: string
|
||||
status: number
|
||||
atUserIds?: number[]
|
||||
atUserNicknames?: (null | string)[]
|
||||
receiptStatus: number
|
||||
sendTime: Date
|
||||
createTime: Date
|
||||
export namespace ImManagerGroupMessageApi {
|
||||
/** 群聊消息 */
|
||||
export interface GroupMessage {
|
||||
id: number;
|
||||
clientMessageId?: string;
|
||||
groupId: number;
|
||||
groupName?: string;
|
||||
senderId: number;
|
||||
senderNickname?: string;
|
||||
type: number;
|
||||
content: string;
|
||||
status: number;
|
||||
atUserIds?: number[];
|
||||
atUserNicknames?: (null | string)[];
|
||||
receiptStatus: number;
|
||||
sendTime: Date;
|
||||
createTime: Date;
|
||||
}
|
||||
}
|
||||
|
||||
// 获得群聊消息分页
|
||||
export const getManagerGroupMessagePage = (params: PageParam) => {
|
||||
return requestClient.get('/im/manager/message/group/page', { params })
|
||||
|
||||
/** 获得群聊消息分页 */
|
||||
export function getManagerGroupMessagePage(params: PageParam) {
|
||||
return requestClient.get<PageResult<ImManagerGroupMessageApi.GroupMessage>>(
|
||||
'/im/manager/message/group/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
// 获得群聊消息详情
|
||||
export const getManagerGroupMessage = (id: number) => {
|
||||
return requestClient.get('/im/manager/message/group/get', { params: { id } })
|
||||
/** 获得群聊消息详情 */
|
||||
export function getManagerGroupMessage(id: number) {
|
||||
return requestClient.get<ImManagerGroupMessageApi.GroupMessage>(
|
||||
'/im/manager/message/group/get',
|
||||
{ params: { id } },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,37 @@
|
|||
import type { PageParam } from '@vben/request'
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export interface ImManagerPrivateMessageVO {
|
||||
id: number
|
||||
clientMessageId?: string
|
||||
senderId: number
|
||||
senderNickname?: string
|
||||
receiverId: number
|
||||
receiverNickname?: string
|
||||
type: number
|
||||
content: string
|
||||
status: number
|
||||
receiptStatus: number
|
||||
sendTime: Date
|
||||
createTime: Date
|
||||
export namespace ImManagerPrivateMessageApi {
|
||||
/** 私聊消息 */
|
||||
export interface PrivateMessage {
|
||||
id: number;
|
||||
clientMessageId?: string;
|
||||
senderId: number;
|
||||
senderNickname?: string;
|
||||
receiverId: number;
|
||||
receiverNickname?: string;
|
||||
type: number;
|
||||
content: string;
|
||||
status: number;
|
||||
sendTime: Date;
|
||||
createTime: Date;
|
||||
}
|
||||
}
|
||||
|
||||
// 获得私聊消息分页
|
||||
export const getManagerPrivateMessagePage = (params: PageParam) => {
|
||||
return requestClient.get('/im/manager/message/private/page', { params })
|
||||
|
||||
/** 获得私聊消息分页 */
|
||||
export function getManagerPrivateMessagePage(params: PageParam) {
|
||||
return requestClient.get<PageResult<ImManagerPrivateMessageApi.PrivateMessage>>(
|
||||
'/im/manager/message/private/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
// 获得私聊消息详情
|
||||
export const getManagerPrivateMessage = (id: number) => {
|
||||
return requestClient.get('/im/manager/message/private/get', { params: { id } })
|
||||
/** 获得私聊消息详情 */
|
||||
export function getManagerPrivateMessage(id: number) {
|
||||
return requestClient.get<ImManagerPrivateMessageApi.PrivateMessage>(
|
||||
'/im/manager/message/private/get',
|
||||
{ params: { id } },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +1,53 @@
|
|||
import type { PageParam } from '@vben/request'
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export interface ImManagerRtcCallVO {
|
||||
id: number
|
||||
room: string
|
||||
conversationType: number
|
||||
mediaType: number
|
||||
inviterUserId: number
|
||||
inviterNickname?: string
|
||||
groupId?: number
|
||||
groupName?: string
|
||||
status: number
|
||||
endReason?: number
|
||||
startTime: Date
|
||||
acceptTime?: Date
|
||||
endTime?: Date
|
||||
createTime: Date
|
||||
export namespace ImManagerRtcApi {
|
||||
/** RTC 通话 */
|
||||
export interface RtcCall {
|
||||
id: number;
|
||||
room: string;
|
||||
conversationType: number;
|
||||
mediaType: number;
|
||||
inviterUserId: number;
|
||||
inviterNickname?: string;
|
||||
groupId?: number;
|
||||
groupName?: string;
|
||||
status: number;
|
||||
endReason?: number;
|
||||
startTime: Date;
|
||||
acceptTime?: Date;
|
||||
endTime?: Date;
|
||||
createTime: Date;
|
||||
}
|
||||
|
||||
/** RTC 通话参与者 */
|
||||
export interface RtcParticipant {
|
||||
id: number;
|
||||
callId: number;
|
||||
userId: number;
|
||||
userNickname?: string;
|
||||
role: number;
|
||||
status: number;
|
||||
inviteTime: Date;
|
||||
acceptTime?: Date;
|
||||
leaveTime?: Date;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ImManagerRtcParticipantVO {
|
||||
id: number
|
||||
callId: number
|
||||
userId: number
|
||||
userNickname?: string
|
||||
role: number
|
||||
status: number
|
||||
inviteTime: Date
|
||||
acceptTime?: Date
|
||||
leaveTime?: Date
|
||||
|
||||
/** 获得通话记录分页 */
|
||||
export function getManagerRtcCallPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<ImManagerRtcApi.RtcCall>>(
|
||||
'/im/manager/rtc/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
// 获得通话记录分页
|
||||
export const getManagerRtcCallPage = (params: PageParam) => {
|
||||
return requestClient.get('/im/manager/rtc/page', { params })
|
||||
}
|
||||
|
||||
// 获得通话参与者列表
|
||||
export const getManagerRtcCallParticipantList = (id: number) => {
|
||||
return requestClient.get('/im/manager/rtc/participant-list', { params: { id } })
|
||||
/** 获得通话参与者列表 */
|
||||
export function getManagerRtcCallParticipantList(id: number) {
|
||||
return requestClient.get<ImManagerRtcApi.RtcParticipant[]>(
|
||||
'/im/manager/rtc/participant-list',
|
||||
{ params: { id } },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,60 @@
|
|||
import type { PageParam } from '@vben/request'
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export interface ImManagerSensitiveWordVO {
|
||||
id: number
|
||||
word: string
|
||||
status: number
|
||||
creator?: string
|
||||
creatorName?: string
|
||||
createTime?: Date
|
||||
export namespace ImManagerSensitiveWordApi {
|
||||
/** 敏感词 */
|
||||
export interface SensitiveWord {
|
||||
id: number;
|
||||
word: string;
|
||||
status: number;
|
||||
creator?: string;
|
||||
creatorName?: string;
|
||||
createTime?: Date;
|
||||
}
|
||||
}
|
||||
|
||||
// 获得敏感词分页
|
||||
export const getManagerSensitiveWordPage = (params: PageParam) => {
|
||||
return requestClient.get('/im/manager/sensitive-word/page', { params })
|
||||
|
||||
/** 获得敏感词分页 */
|
||||
export function getManagerSensitiveWordPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<ImManagerSensitiveWordApi.SensitiveWord>>(
|
||||
'/im/manager/sensitive-word/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
// 获得敏感词详情
|
||||
export const getManagerSensitiveWord = (id: number) => {
|
||||
return requestClient.get('/im/manager/sensitive-word/get', { params: { id } })
|
||||
/** 获得敏感词详情 */
|
||||
export function getManagerSensitiveWord(id: number) {
|
||||
return requestClient.get<ImManagerSensitiveWordApi.SensitiveWord>(
|
||||
'/im/manager/sensitive-word/get',
|
||||
{ params: { id } },
|
||||
);
|
||||
}
|
||||
|
||||
// 新增敏感词
|
||||
export const createManagerSensitiveWord = (data: ImManagerSensitiveWordVO) => {
|
||||
return requestClient.post('/im/manager/sensitive-word/create', data)
|
||||
/** 新增敏感词 */
|
||||
export function createManagerSensitiveWord(data: ImManagerSensitiveWordApi.SensitiveWord) {
|
||||
return requestClient.post<number>('/im/manager/sensitive-word/create', data);
|
||||
}
|
||||
|
||||
// 修改敏感词
|
||||
export const updateManagerSensitiveWord = (data: ImManagerSensitiveWordVO) => {
|
||||
return requestClient.put('/im/manager/sensitive-word/update', data)
|
||||
/** 修改敏感词 */
|
||||
export function updateManagerSensitiveWord(data: ImManagerSensitiveWordApi.SensitiveWord) {
|
||||
return requestClient.put<boolean>(
|
||||
'/im/manager/sensitive-word/update',
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
// 删除敏感词
|
||||
export const deleteManagerSensitiveWord = (id: number) => {
|
||||
return requestClient.delete('/im/manager/sensitive-word/delete', { params: { id } })
|
||||
/** 删除敏感词 */
|
||||
export function deleteManagerSensitiveWord(id: number) {
|
||||
return requestClient.delete<boolean>('/im/manager/sensitive-word/delete', {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
// 批量删除敏感词
|
||||
export const deleteManagerSensitiveWordList = (ids: number[]) => {
|
||||
return requestClient.delete('/im/manager/sensitive-word/delete-list', {
|
||||
params: { ids: ids.join(',') }
|
||||
})
|
||||
/** 批量删除敏感词 */
|
||||
export function deleteManagerSensitiveWordList(ids: number[]) {
|
||||
return requestClient.delete<boolean>(
|
||||
'/im/manager/sensitive-word/delete-list',
|
||||
{ params: { ids: ids.join(',') } },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,66 +1,88 @@
|
|||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export interface ImStatisticsOverviewVO {
|
||||
totalUser: number
|
||||
newUserToday: number
|
||||
totalGroup: number
|
||||
newGroupToday: number
|
||||
activeUserDaily: number
|
||||
activeUserWeekly: number
|
||||
activeUserMonthly: number
|
||||
privateMessageToday: number
|
||||
groupMessageToday: number
|
||||
privateMessageYesterday: number
|
||||
groupMessageYesterday: number
|
||||
export namespace ImManagerStatisticsApi {
|
||||
/** 统计概览 */
|
||||
export interface Overview {
|
||||
totalUser: number;
|
||||
newUserToday: number;
|
||||
totalGroup: number;
|
||||
newGroupToday: number;
|
||||
activeUserDaily: number;
|
||||
activeUserWeekly: number;
|
||||
activeUserMonthly: number;
|
||||
privateMessageToday: number;
|
||||
groupMessageToday: number;
|
||||
privateMessageYesterday: number;
|
||||
groupMessageYesterday: number;
|
||||
}
|
||||
|
||||
/** 趋势数据 */
|
||||
export interface Trend {
|
||||
dates: string[];
|
||||
series: Record<string, number[]>;
|
||||
}
|
||||
|
||||
/** 消息类型分布 */
|
||||
export interface MessageType {
|
||||
type: number; // 参见 ImContentTypeEnum 枚举类,由前端按 DICT_TYPE.IM_CONTENT_TYPE 翻译
|
||||
value: number;
|
||||
}
|
||||
|
||||
/** 群规模分布 */
|
||||
export interface GroupSize {
|
||||
range: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
/** 消息发送排行 */
|
||||
export interface TopSender {
|
||||
userId: number;
|
||||
nickname: string;
|
||||
messageCount: number;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ImStatisticsTrendVO {
|
||||
dates: string[]
|
||||
series: Record<string, number[]>
|
||||
|
||||
/** 获得 KPI 概览 */
|
||||
export function getStatisticsOverview() {
|
||||
return requestClient.get<ImManagerStatisticsApi.Overview>(
|
||||
'/im/manager/statistics/overview',
|
||||
);
|
||||
}
|
||||
|
||||
export interface ImStatisticsMessageTypeVO {
|
||||
type: number
|
||||
value: number
|
||||
/** 获得消息趋势(私聊 + 群聊双线) */
|
||||
export function getMessageTrend(days: number) {
|
||||
return requestClient.get<ImManagerStatisticsApi.Trend>(
|
||||
'/im/manager/statistics/message-trend',
|
||||
{ params: { days } },
|
||||
);
|
||||
}
|
||||
|
||||
export interface ImStatisticsGroupSizeVO {
|
||||
range: string
|
||||
count: number
|
||||
/** 获得用户趋势(新增注册 + 日活双线) */
|
||||
export function getUserTrend(days: number) {
|
||||
return requestClient.get<ImManagerStatisticsApi.Trend>(
|
||||
'/im/manager/statistics/user-trend',
|
||||
{ params: { days } },
|
||||
);
|
||||
}
|
||||
|
||||
export interface ImStatisticsTopSenderVO {
|
||||
userId: number
|
||||
nickname: string
|
||||
messageCount: number
|
||||
/** 获得内容类型分布(最近 30 天) */
|
||||
export function getMessageTypeDistribution() {
|
||||
return requestClient.get<ImManagerStatisticsApi.MessageType[]>(
|
||||
'/im/manager/statistics/message-type-distribution',
|
||||
);
|
||||
}
|
||||
|
||||
// 获得 KPI 概览
|
||||
export const getStatisticsOverview = (): Promise<ImStatisticsOverviewVO> => {
|
||||
return requestClient.get('/im/manager/statistics/overview')
|
||||
/** 获得群规模分布 */
|
||||
export function getGroupSizeDistribution() {
|
||||
return requestClient.get<ImManagerStatisticsApi.GroupSize[]>(
|
||||
'/im/manager/statistics/group-size-distribution',
|
||||
);
|
||||
}
|
||||
|
||||
// 获得消息趋势
|
||||
export const getMessageTrend = (days: number): Promise<ImStatisticsTrendVO> => {
|
||||
return requestClient.get('/im/manager/statistics/message-trend', { params: { days } })
|
||||
}
|
||||
|
||||
// 获得用户趋势
|
||||
export const getUserTrend = (days: number): Promise<ImStatisticsTrendVO> => {
|
||||
return requestClient.get('/im/manager/statistics/user-trend', { params: { days } })
|
||||
}
|
||||
|
||||
// 获得内容类型分布
|
||||
export const getMessageTypeDistribution = (): Promise<ImStatisticsMessageTypeVO[]> => {
|
||||
return requestClient.get('/im/manager/statistics/message-type-distribution')
|
||||
}
|
||||
|
||||
// 获得群规模分布
|
||||
export const getGroupSizeDistribution = (): Promise<ImStatisticsGroupSizeVO[]> => {
|
||||
return requestClient.get('/im/manager/statistics/group-size-distribution')
|
||||
}
|
||||
|
||||
// 获得消息 TOP 发送者
|
||||
export const getTopSenders = (): Promise<ImStatisticsTopSenderVO[]> => {
|
||||
return requestClient.get('/im/manager/statistics/top-senders')
|
||||
/** 获得消息 TOP 发送者(最近 30 天) */
|
||||
export function getTopSenders() {
|
||||
return requestClient.get<ImManagerStatisticsApi.TopSender[]>(
|
||||
'/im/manager/statistics/top-senders',
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,34 @@
|
|||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export interface ImChannelMessageRespVO {
|
||||
id: number
|
||||
clientMessageId?: string
|
||||
channelId: number
|
||||
materialId: number
|
||||
type: number
|
||||
content: string
|
||||
receiptStatus?: number
|
||||
sendTime: string
|
||||
export namespace ImChannelMessageApi {
|
||||
/** 频道消息 */
|
||||
export interface ChannelMessageRespVO {
|
||||
id: number;
|
||||
clientMessageId?: string;
|
||||
channelId: number;
|
||||
materialId: number;
|
||||
type: number;
|
||||
content: string;
|
||||
receiptStatus?: number;
|
||||
sendTime: string;
|
||||
}
|
||||
}
|
||||
|
||||
// 拉取当前用户应收的频道消息(离线增量);按 minId 游标分页
|
||||
export const pullChannelMessages = (params: { minId: number; size?: number }, signal?: AbortSignal) => {
|
||||
return requestClient.get<ImChannelMessageRespVO[]>('/im/channel/message/pull', { params, signal })
|
||||
|
||||
/** 拉取当前用户应收的频道消息(离线增量);按 minId 游标分页 */
|
||||
export function pullChannelMessages(
|
||||
params: { minId: number; size?: number },
|
||||
signal?: AbortSignal,
|
||||
) {
|
||||
return requestClient.get<ImChannelMessageApi.ChannelMessageRespVO[]>(
|
||||
'/im/channel/message/pull',
|
||||
{ params, signal },
|
||||
);
|
||||
}
|
||||
|
||||
// 上报频道消息已读位置;切到频道会话或拉到新消息后调
|
||||
export const readChannelMessages = (channelId: number, messageId: number) => {
|
||||
return requestClient.put('/im/channel/message/read', undefined, { params: { channelId, messageId } })
|
||||
/** 上报频道消息已读位置;切到频道会话或拉到新消息后调 */
|
||||
export function readChannelMessages(channelId: number, messageId: number) {
|
||||
return requestClient.put<boolean>('/im/channel/message/read', undefined, {
|
||||
params: { channelId, messageId },
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,70 +1,92 @@
|
|||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
// 群聊消息 Response VO
|
||||
export interface ImGroupMessageRespVO {
|
||||
id: number // 消息编号
|
||||
clientMessageId: string // 客户端消息编号
|
||||
senderId: number // 发送人编号
|
||||
groupId: number // 群编号
|
||||
type: number // 内容类型
|
||||
content: string // 消息内容(JSON 格式)
|
||||
status: number // 消息状态
|
||||
sendTime: string // 发送时间
|
||||
atUserIds?: number[] // @ 目标用户编号列表
|
||||
receiverUserIds?: number[] // 定向接收用户编号列表
|
||||
receiptStatus?: number // 回执状态
|
||||
readCount?: number // 已读人数(回执消息、且发送人为当前用户时有值)
|
||||
export namespace ImGroupMessageApi {
|
||||
/** 群聊消息 Response VO */
|
||||
export interface GroupMessageRespVO {
|
||||
id: number; // 消息编号
|
||||
clientMessageId: string; // 客户端消息编号
|
||||
senderId: number; // 发送人编号
|
||||
groupId: number; // 群编号
|
||||
type: number; // 内容类型
|
||||
content: string; // 消息内容(JSON 格式)
|
||||
status: number; // 消息状态
|
||||
sendTime: string; // 发送时间
|
||||
atUserIds?: number[]; // @ 目标用户编号列表
|
||||
receiverUserIds?: number[]; // 定向接收用户编号列表
|
||||
receiptStatus?: number; // 回执状态
|
||||
readCount?: number; // 已读人数(回执消息、且发送人为当前用户时有值)
|
||||
}
|
||||
|
||||
/** 群聊消息发送 Request VO */
|
||||
export interface GroupMessageSendReqVO {
|
||||
clientMessageId: string; // 客户端消息编号
|
||||
groupId: number; // 群编号
|
||||
type: number; // 内容类型
|
||||
content: string; // 消息内容(JSON 格式)
|
||||
atUserIds?: number[]; // @ 目标用户编号列表
|
||||
receipt?: boolean; // 是否需要回执
|
||||
}
|
||||
|
||||
/** 群聊历史消息列表 Request VO */
|
||||
export interface GroupMessageListReqVO {
|
||||
groupId: number | string; // 群编号
|
||||
maxId?: number | string; // 起始消息编号(不含),为空则从最新消息开始
|
||||
limit: number; // 拉取数量(1 ~ 200)
|
||||
}
|
||||
}
|
||||
|
||||
// 群聊消息发送 Request VO
|
||||
export interface ImGroupMessageSendReqVO {
|
||||
clientMessageId: string // 客户端消息编号
|
||||
groupId: number // 群编号
|
||||
type: number // 内容类型
|
||||
content: string // 消息内容(JSON 格式)
|
||||
atUserIds?: number[] // @ 目标用户编号列表
|
||||
receipt?: boolean // 是否需要回执
|
||||
|
||||
/** 发送群聊消息 */
|
||||
export function sendGroupMessage(data: ImGroupMessageApi.GroupMessageSendReqVO) {
|
||||
return requestClient.post<ImGroupMessageApi.GroupMessageRespVO>(
|
||||
'/im/message/group/send',
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
// 群聊历史消息列表 Request VO
|
||||
export interface ImGroupMessageListReqVO {
|
||||
groupId: number | string // 群编号
|
||||
maxId?: number | string // 起始消息编号(不含),为空则从最新消息开始
|
||||
limit: number // 拉取数量(1 ~ 200)
|
||||
}
|
||||
|
||||
// 发送群聊消息
|
||||
export const sendGroupMessage = (data: ImGroupMessageSendReqVO) => {
|
||||
return requestClient.post<ImGroupMessageRespVO>('/im/message/group/send', data)
|
||||
}
|
||||
|
||||
// 拉取群聊消息(增量)
|
||||
export const pullGroupMessages = (
|
||||
/** 拉取群聊消息(增量) */
|
||||
export function pullGroupMessages(
|
||||
params: { minId: number | string; size: number },
|
||||
signal?: AbortSignal
|
||||
) => {
|
||||
return requestClient.get<ImGroupMessageRespVO[]>('/im/message/group/pull', { params, signal })
|
||||
signal?: AbortSignal,
|
||||
) {
|
||||
return requestClient.get<ImGroupMessageApi.GroupMessageRespVO[]>(
|
||||
'/im/message/group/pull',
|
||||
{ params, signal },
|
||||
);
|
||||
}
|
||||
|
||||
// 查询群聊历史消息
|
||||
export const getGroupMessageList = (params: ImGroupMessageListReqVO) => {
|
||||
return requestClient.get<ImGroupMessageRespVO[]>('/im/message/group/list', { params })
|
||||
/** 查询群聊历史消息 */
|
||||
export function getGroupMessageList(params: ImGroupMessageApi.GroupMessageListReqVO) {
|
||||
return requestClient.get<ImGroupMessageApi.GroupMessageRespVO[]>(
|
||||
'/im/message/group/list',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
// 标记群聊消息已读
|
||||
export const readGroupMessages = (groupId: number | string, messageId: number | string) => {
|
||||
return requestClient.put<boolean>('/im/message/group/read', undefined, { params: { groupId, messageId } })
|
||||
/** 标记群聊消息已读 */
|
||||
export function readGroupMessages(
|
||||
groupId: number | string,
|
||||
messageId: number | string,
|
||||
) {
|
||||
return requestClient.put<boolean>('/im/message/group/read', undefined, {
|
||||
params: { groupId, messageId },
|
||||
});
|
||||
}
|
||||
|
||||
// 撤回群聊消息
|
||||
export const recallGroupMessage = (id: number | string) => {
|
||||
return requestClient.delete<ImGroupMessageRespVO>('/im/message/group/recall', { params: { id } })
|
||||
/** 撤回群聊消息 */
|
||||
export function recallGroupMessage(id: number | string) {
|
||||
return requestClient.delete<ImGroupMessageApi.GroupMessageRespVO>(
|
||||
'/im/message/group/recall',
|
||||
{ params: { id } },
|
||||
);
|
||||
}
|
||||
|
||||
// 获取群消息已读用户列表
|
||||
export const getGroupReadUsers = (params: {
|
||||
groupId: number | string
|
||||
messageId: number | string
|
||||
}) => {
|
||||
return requestClient.get<number[]>('/im/message/group/get-read-user-ids', { params })
|
||||
/** 获取群消息已读用户列表 */
|
||||
export function getGroupReadUsers(params: {
|
||||
groupId: number | string;
|
||||
messageId: number | string;
|
||||
}) {
|
||||
return requestClient.get<number[]>('/im/message/group/get-read-user-ids', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,63 +1,89 @@
|
|||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
// 私聊消息 Response VO
|
||||
export interface ImPrivateMessageRespVO {
|
||||
id: number // 消息编号
|
||||
clientMessageId: string // 客户端消息编号
|
||||
senderId: number // 发送人编号
|
||||
receiverId: number // 接收人编号
|
||||
type: number // 内容类型
|
||||
content: string // 消息内容(JSON 格式)
|
||||
status: number // 消息状态(正常 / 已撤回)
|
||||
receiptStatus?: number // 回执状态(不需要 / 待完成 / 已完成),对齐 ImMessageReceiptStatus
|
||||
sendTime: string // 发送时间
|
||||
export namespace ImPrivateMessageApi {
|
||||
/** 私聊消息 Response VO */
|
||||
export interface PrivateMessageRespVO {
|
||||
id: number; // 消息编号
|
||||
clientMessageId: string; // 客户端消息编号
|
||||
senderId: number; // 发送人编号
|
||||
receiverId: number; // 接收人编号
|
||||
type: number; // 内容类型
|
||||
content: string; // 消息内容(JSON 格式)
|
||||
status: number; // 消息状态(正常 / 已撤回)
|
||||
receiptStatus?: number; // 回执状态(不需要 / 待完成 / 已完成),对齐 ImMessageReceiptStatus
|
||||
sendTime: string; // 发送时间
|
||||
}
|
||||
|
||||
/** 私聊消息发送 Request VO */
|
||||
export interface PrivateMessageSendReqVO {
|
||||
clientMessageId: string; // 客户端消息编号
|
||||
receiverId: number; // 接收人编号
|
||||
type: number; // 内容类型
|
||||
content: string; // 消息内容(JSON 格式)
|
||||
receipt?: boolean; // 是否需要回执;不传后端默认 true(普通私聊用户消息)
|
||||
}
|
||||
|
||||
/** 私聊历史消息列表 Request VO */
|
||||
export interface PrivateMessageListReqVO {
|
||||
receiverId: number | string; // 接收人编号(对方)
|
||||
maxId?: number | string; // 起始消息编号(不含),为空则从最新消息开始
|
||||
limit: number; // 拉取数量(1 ~ 200)
|
||||
}
|
||||
}
|
||||
|
||||
// 私聊消息发送 Request VO
|
||||
export interface ImPrivateMessageSendReqVO {
|
||||
clientMessageId: string // 客户端消息编号
|
||||
receiverId: number // 接收人编号
|
||||
type: number // 内容类型
|
||||
content: string // 消息内容(JSON 格式)
|
||||
receipt?: boolean // 是否需要回执;不传后端默认 true(普通私聊用户消息)
|
||||
|
||||
/** 发送私聊消息 */
|
||||
export function sendPrivateMessage(data: ImPrivateMessageApi.PrivateMessageSendReqVO) {
|
||||
return requestClient.post<ImPrivateMessageApi.PrivateMessageRespVO>(
|
||||
'/im/message/private/send',
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
// 私聊历史消息列表 Request VO
|
||||
export interface ImPrivateMessageListReqVO {
|
||||
receiverId: number | string // 接收人编号(对方)
|
||||
maxId?: number | string // 起始消息编号(不含),为空则从最新消息开始
|
||||
limit: number // 拉取数量(1 ~ 200)
|
||||
}
|
||||
|
||||
// 发送私聊消息
|
||||
export const sendPrivateMessage = (data: ImPrivateMessageSendReqVO) => {
|
||||
return requestClient.post<ImPrivateMessageRespVO>('/im/message/private/send', data)
|
||||
}
|
||||
|
||||
// 拉取私聊消息(增量)
|
||||
export const pullPrivateMessages = (
|
||||
/** 拉取私聊消息(增量) */
|
||||
export function pullPrivateMessages(
|
||||
params: { minId: number | string; size: number },
|
||||
signal?: AbortSignal
|
||||
) => {
|
||||
return requestClient.get<ImPrivateMessageRespVO[]>('/im/message/private/pull', { params, signal })
|
||||
signal?: AbortSignal,
|
||||
) {
|
||||
return requestClient.get<ImPrivateMessageApi.PrivateMessageRespVO[]>(
|
||||
'/im/message/private/pull',
|
||||
{ params, signal },
|
||||
);
|
||||
}
|
||||
|
||||
// 查询私聊历史消息
|
||||
export const getPrivateMessageList = (params: ImPrivateMessageListReqVO) => {
|
||||
return requestClient.get<ImPrivateMessageRespVO[]>('/im/message/private/list', { params })
|
||||
/** 查询私聊历史消息 */
|
||||
export function getPrivateMessageList(params: ImPrivateMessageApi.PrivateMessageListReqVO) {
|
||||
return requestClient.get<ImPrivateMessageApi.PrivateMessageRespVO[]>(
|
||||
'/im/message/private/list',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
// 标记私聊消息已读
|
||||
export const readPrivateMessages = (receiverId: number | string, messageId: number | string) => {
|
||||
return requestClient.put<boolean>('/im/message/private/read', undefined, { params: { receiverId, messageId } })
|
||||
/** 标记私聊消息已读 */
|
||||
export function readPrivateMessages(
|
||||
receiverId: number | string,
|
||||
messageId: number | string,
|
||||
) {
|
||||
return requestClient.put<boolean>('/im/message/private/read', undefined, {
|
||||
params: { receiverId, messageId },
|
||||
});
|
||||
}
|
||||
|
||||
// 查询对方已读到我发的最大消息 id(多端 / 离线后用于补齐已读状态)
|
||||
export const getPrivateMaxReadMessageId = (peerId: number | string, signal?: AbortSignal) => {
|
||||
return requestClient.get<null | number>('/im/message/private/max-read-message-id', { params: { peerId }, signal })
|
||||
/** 查询对方已读到我发的最大消息 id(多端 / 离线后用于补齐已读状态) */
|
||||
export function getPrivateMaxReadMessageId(
|
||||
peerId: number | string,
|
||||
signal?: AbortSignal,
|
||||
) {
|
||||
return requestClient.get<null | number>(
|
||||
'/im/message/private/max-read-message-id',
|
||||
{ params: { peerId }, signal },
|
||||
);
|
||||
}
|
||||
|
||||
// 撤回私聊消息
|
||||
export const recallPrivateMessage = (id: number | string) => {
|
||||
return requestClient.delete<ImPrivateMessageRespVO>('/im/message/private/recall', { params: { id } })
|
||||
/** 撤回私聊消息 */
|
||||
export function recallPrivateMessage(id: number | string) {
|
||||
return requestClient.delete<ImPrivateMessageApi.PrivateMessageRespVO>(
|
||||
'/im/message/private/recall',
|
||||
{ params: { id } },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,85 +1,105 @@
|
|||
import { requestClient } from '#/api/request'
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
// 创建新通话请求 VO
|
||||
export interface ImRtcCallCreateReqVO {
|
||||
conversationType: number
|
||||
mediaType: number
|
||||
groupId?: number
|
||||
inviteeIds: number[] // 被邀请的用户编号集合;私聊必传 1 个对端,群聊必传至少 1 人
|
||||
export namespace ImRtcApi {
|
||||
/** 创建新通话请求 VO */
|
||||
export interface RtcCallCreateReqVO {
|
||||
conversationType: number;
|
||||
mediaType: number;
|
||||
groupId?: number;
|
||||
inviteeIds: number[]; // 被邀请的用户编号集合;私聊必传 1 个对端,群聊必传至少 1 人
|
||||
}
|
||||
|
||||
/** 通话中追加邀请请求 VO;仅群通话可用 */
|
||||
export interface RtcCallInviteReqVO {
|
||||
room: string;
|
||||
inviteeIds: number[];
|
||||
}
|
||||
|
||||
/** 通话会话 VO;create / join / accept / refreshToken 共用 */
|
||||
export interface RtcCallRespVO {
|
||||
room: string; // 业务通话编号(同时作为 LiveKit 房间名)
|
||||
livekitUrl: string;
|
||||
token?: string; // ENDED 状态时为 null(无需 connect LiveKit)
|
||||
conversationType: number;
|
||||
mediaType: number;
|
||||
status: number;
|
||||
endReason?: number; // 结束原因;仅 status=ENDED 时有值
|
||||
inviterId: number;
|
||||
groupId?: number;
|
||||
inviteeIds?: number[];
|
||||
joinedUserIds?: number[];
|
||||
}
|
||||
|
||||
/** 群活跃通话查询响应;不含 token */
|
||||
export interface RtcGroupCallRespVO {
|
||||
room: string;
|
||||
groupId: number;
|
||||
mediaType: number;
|
||||
inviterId: number;
|
||||
joinedUserIds?: number[];
|
||||
inviteeIds?: number[];
|
||||
}
|
||||
}
|
||||
|
||||
// 通话中追加邀请请求 VO;仅群通话可用
|
||||
export interface ImRtcCallInviteReqVO {
|
||||
room: string
|
||||
inviteeIds: number[]
|
||||
|
||||
/** 创建新通话;私聊或群聊根据 conversationType 区分 */
|
||||
export function createCall(data: ImRtcApi.RtcCallCreateReqVO) {
|
||||
return requestClient.post<ImRtcApi.RtcCallRespVO>('/im/rtc/create', data);
|
||||
}
|
||||
|
||||
// 通话会话 VO;create / join / accept / refreshToken 共用
|
||||
export interface ImRtcCallRespVO {
|
||||
room: string // 业务通话编号(同时作为 LiveKit 房间名)
|
||||
livekitUrl: string
|
||||
token?: string // ENDED 状态时为 null(无需 connect LiveKit)
|
||||
conversationType: number
|
||||
mediaType: number
|
||||
status: number
|
||||
endReason?: number // 结束原因;仅 status=ENDED 时有值
|
||||
inviterId: number
|
||||
groupId?: number
|
||||
inviteeIds?: number[]
|
||||
joinedUserIds?: number[]
|
||||
/** 通话中追加邀请;仅群通话可用 */
|
||||
export function inviteCall(data: ImRtcApi.RtcCallInviteReqVO) {
|
||||
return requestClient.post<boolean>('/im/rtc/invite', data);
|
||||
}
|
||||
|
||||
// 群活跃通话查询响应;不含 token
|
||||
export interface ImRtcGroupCallRespVO {
|
||||
room: string
|
||||
groupId: number
|
||||
mediaType: number
|
||||
inviterId: number
|
||||
joinedUserIds?: number[]
|
||||
inviteeIds?: number[]
|
||||
/** 加入已有群通话;用于胶囊条「加入」按钮 */
|
||||
export function joinCall(room: string) {
|
||||
return requestClient.post<ImRtcApi.RtcCallRespVO>('/im/rtc/join', undefined, {
|
||||
params: { room },
|
||||
});
|
||||
}
|
||||
|
||||
// 创建新通话;私聊或群聊根据 conversationType 区分
|
||||
export const createCall = (data: ImRtcCallCreateReqVO) => {
|
||||
return requestClient.post<ImRtcCallRespVO>('/im/rtc/create', data)
|
||||
/** 接听通话 */
|
||||
export function acceptCall(room: string) {
|
||||
return requestClient.post<ImRtcApi.RtcCallRespVO>('/im/rtc/accept', undefined, {
|
||||
params: { room },
|
||||
});
|
||||
}
|
||||
|
||||
// 通话中追加邀请;仅群通话可用
|
||||
export const inviteCall = (data: ImRtcCallInviteReqVO) => {
|
||||
return requestClient.post<boolean>('/im/rtc/invite', data)
|
||||
/** 拒绝通话 */
|
||||
export function rejectCall(room: string) {
|
||||
return requestClient.post<boolean>('/im/rtc/reject', undefined, {
|
||||
params: { room },
|
||||
});
|
||||
}
|
||||
|
||||
// 加入已有群通话;用于胶囊条「加入」按钮
|
||||
export const joinCall = (room: string) => {
|
||||
return requestClient.post<ImRtcCallRespVO>('/im/rtc/join', undefined, { params: { room } })
|
||||
/** 取消邀请;主叫接通前调用 */
|
||||
export function cancelCall(room: string) {
|
||||
return requestClient.post<boolean>('/im/rtc/cancel', undefined, {
|
||||
params: { room },
|
||||
});
|
||||
}
|
||||
|
||||
// 接听通话
|
||||
export const acceptCall = (room: string) => {
|
||||
return requestClient.post<ImRtcCallRespVO>('/im/rtc/accept', undefined, { params: { room } })
|
||||
/** 离开通话;接通后调用 */
|
||||
export function leaveCall(room: string) {
|
||||
return requestClient.post<boolean>('/im/rtc/leave', undefined, {
|
||||
params: { room },
|
||||
});
|
||||
}
|
||||
|
||||
// 拒绝通话
|
||||
export const rejectCall = (room: string) => {
|
||||
return requestClient.post<boolean>('/im/rtc/reject', undefined, { params: { room } })
|
||||
/** 振铃超时检查;RUNNING 端 timer 兜底,触发后端立即扫描该 room 的超时 INVITING(接口静默) */
|
||||
export function noAnswerCallCheck(room: string) {
|
||||
return requestClient.post<boolean>(
|
||||
'/im/rtc/no-answer-call-check',
|
||||
undefined,
|
||||
{ params: { room } },
|
||||
);
|
||||
}
|
||||
|
||||
// 取消邀请;主叫接通前调用
|
||||
export const cancelCall = (room: string) => {
|
||||
return requestClient.post<boolean>('/im/rtc/cancel', undefined, { params: { room } })
|
||||
}
|
||||
|
||||
// 离开通话;接通后调用
|
||||
export const leaveCall = (room: string) => {
|
||||
return requestClient.post<boolean>('/im/rtc/leave', undefined, { params: { room } })
|
||||
}
|
||||
|
||||
// 振铃超时检查;RUNNING 端 timer 兜底,触发后端立即扫描该 room 的超时 INVITING(接口静默)
|
||||
export const noAnswerCallCheck = (room: string) => {
|
||||
return requestClient.post<boolean>('/im/rtc/no-answer-call-check', undefined, { params: { room } })
|
||||
}
|
||||
|
||||
// 查询当前进行中的通话;目前仅群聊场景(胶囊条),返回 null 表示无活跃通话
|
||||
export const getActiveCall = (groupId: number) => {
|
||||
return requestClient.get<ImRtcGroupCallRespVO | null>('/im/rtc/get-active-call', { params: { groupId } })
|
||||
/** 查询当前进行中的通话;目前仅群聊场景(胶囊条),返回 null 表示无活跃通话 */
|
||||
export function getActiveCall(groupId: number) {
|
||||
return requestClient.get<ImRtcApi.RtcGroupCallRespVO | null>(
|
||||
'/im/rtc/get-active-call',
|
||||
{ params: { groupId } },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,16 +9,23 @@ export namespace SystemUserApi {
|
|||
username: string;
|
||||
nickname: string;
|
||||
deptId: number;
|
||||
deptName?: string;
|
||||
postIds: string[];
|
||||
email: string;
|
||||
mobile: string;
|
||||
sex: number;
|
||||
avatar: string;
|
||||
loginIp: string;
|
||||
loginDate?: Date;
|
||||
status: number;
|
||||
remark: string;
|
||||
createTime?: Date;
|
||||
}
|
||||
|
||||
/** 用户精简信息 */
|
||||
export interface UserSimple extends User {
|
||||
id: number;
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询用户管理列表 */
|
||||
|
|
@ -86,3 +93,17 @@ export function updateUserStatus(id: number, status: number) {
|
|||
export function getSimpleUserList() {
|
||||
return requestClient.get<SystemUserApi.User[]>('/system/user/simple-list');
|
||||
}
|
||||
|
||||
/** 按用户编号查询用户精简信息 */
|
||||
export function getSimpleUser(id: number | string) {
|
||||
return requestClient.get<SystemUserApi.UserSimple>('/system/user/get-simple', {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
/** 按昵称模糊搜索用户 */
|
||||
export function getSimpleUserListByNickname(nickname: string) {
|
||||
return requestClient.get<SystemUserApi.UserSimple[]>('/system/user/list-by-nickname', {
|
||||
params: { nickname },
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,18 +21,20 @@ const conversationStore = useConversationStore()
|
|||
const friendStore = useFriendStore()
|
||||
const uiStore = useImUiStore()
|
||||
|
||||
const totalUnread = computed(() => conversationStore.getTotalUnreadCount) // 消息 Tab 的红点:所有非免打扰会话的未读总和
|
||||
const unhandledRequestCount = computed(() => friendStore.getUnhandledRequestCount) // 通讯录 Tab 的红点:未处理好友申请数(接收方=我)
|
||||
/** 消息 Tab 的红点:所有非免打扰会话的未读总和 */
|
||||
const totalUnread = computed(() => conversationStore.getTotalUnreadCount)
|
||||
/** 通讯录 Tab 的红点:未处理好友申请数(接收方=我) */
|
||||
const unhandledRequestCount = computed(() => friendStore.getUnhandledRequestCount)
|
||||
|
||||
const tabs = [
|
||||
{ name: 'ImHomeConversation', icon: 'ep:chat-round' },
|
||||
{ name: 'ImHomeContact', icon: 'mingcute:contacts-line' }
|
||||
] // 两个主 Tab;用路由 name 而非 path,避免前缀 / 嵌套调整后失效
|
||||
|
||||
/** 当前路由是否命中 Tab:直接比对 route.name */
|
||||
// 当前路由是否命中 Tab:直接比对 route.name
|
||||
const isActive = (name: string) => route.name === name
|
||||
|
||||
/** 切换 Tab:当前已选中时,消息 Tab 触发"滚动到下一个未读"(对齐微信 PC),其它 Tab 无动作 */
|
||||
// 切换 Tab:当前已选中时,消息 Tab 触发"滚动到下一个未读"(对齐微信 PC),其它 Tab 无动作
|
||||
const goTab = (name: string) => {
|
||||
if (route.name === name) {
|
||||
if (name === 'ImHomeConversation') {
|
||||
|
|
@ -43,7 +45,7 @@ const goTab = (name: string) => {
|
|||
router.push({ name })
|
||||
}
|
||||
|
||||
/** 跳转个人中心(路由 name=Profile) */
|
||||
// 跳转个人中心(路由 name=Profile)
|
||||
const goProfile = () => router.push({ name: 'Profile' })
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
<script lang="ts" setup>
|
||||
import type { SystemUserApi } from '#/api/system/user'
|
||||
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
import { useUserStore } from '@vben/stores'
|
||||
|
||||
import { Button, Input, Modal, Spin } from 'ant-design-vue'
|
||||
import { Button, Input, message, Modal, Spin } from 'ant-design-vue'
|
||||
|
||||
import { getSimpleUserListByNickname } from '#/api/system/user'
|
||||
import { getCurrentUserId } from '#/views/im/utils/auth'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
import { getSimpleUserListByNickname, type UserVO } from '#/views/im/utils/system-user'
|
||||
|
||||
import { ImFriendAddSource } from '../../../utils/constants'
|
||||
import { getGenderColor, getGenderIcon } from '../../../utils/user'
|
||||
|
|
@ -17,18 +18,14 @@ import UserAvatar from '../user/UserAvatar.vue'
|
|||
|
||||
defineOptions({ name: 'ImFriendAddDialog' })
|
||||
|
||||
// TODO @AI:变量都走尾注释;
|
||||
const visible = ref(false)
|
||||
/** 预填目标用户:非 null 时跳过搜索步骤,直接进入申请表单(群成员加好友 / 名片加好友等场景) */
|
||||
const presetUser = ref<null | UserVO>(null)
|
||||
/** 添加来源;参见 ImFriendAddSourceEnum,默认 SEARCH */
|
||||
const addSource = ref<number>(ImFriendAddSource.SEARCH)
|
||||
/** 来源附带信息:addSource=ImFriendAddSource.GROUP 时传群名,话术拼为「我是 XX 群的 YY」 */
|
||||
const addSourceExtra = ref<string>('')
|
||||
const visible = ref(false) // 弹窗是否可见
|
||||
const presetUser = ref<null | SystemUserApi.UserSimple>(null) // 预填目标用户:非 null 时跳过搜索步骤,直接进入申请表单(群成员加好友 / 名片加好友等场景)
|
||||
const addSource = ref<number>(ImFriendAddSource.SEARCH) // 添加来源;参见 ImFriendAddSourceEnum,默认 SEARCH
|
||||
const addSourceExtra = ref<string>('') // 来源附带信息:addSource=ImFriendAddSource.GROUP 时传群名,话术拼为「我是 XX 群的 YY」
|
||||
|
||||
defineExpose({
|
||||
/** 打开加好友弹窗:reset → 灌参 → visible=true;不传 opts 走搜索模式 */
|
||||
open(opts?: { addSource?: number; addSourceExtra?: string; presetUser?: null | UserVO; }) {
|
||||
open(opts?: { addSource?: number; addSourceExtra?: string; presetUser?: null | SystemUserApi.UserSimple; }) {
|
||||
presetUser.value = opts?.presetUser ?? null
|
||||
addSource.value = opts?.addSource ?? ImFriendAddSource.SEARCH
|
||||
addSourceExtra.value = opts?.addSourceExtra ?? ''
|
||||
|
|
@ -39,7 +36,6 @@ defineExpose({
|
|||
|
||||
const friendStore = useFriendStore()
|
||||
const userStore = useUserStore()
|
||||
const message = useMessage()
|
||||
|
||||
/** 当前登录用户编号;用 computed 包一层,切账号后随 wsCache 重取,避免顶层求值在 keep-alive 实例里持有旧 id */
|
||||
const currentUserId = computed(() => getCurrentUserId())
|
||||
|
|
@ -48,20 +44,15 @@ const currentUserId = computed(() => getCurrentUserId())
|
|||
const visibleUsers = computed(() =>
|
||||
users.value.filter((user) => user.id !== currentUserId.value)
|
||||
)
|
||||
const keyword = ref('')
|
||||
const users = ref<UserVO[]>([])
|
||||
const searched = ref(false)
|
||||
const loading = ref(false)
|
||||
|
||||
/** 当前步骤:search=搜索列表;apply=申请表单 */
|
||||
const step = ref<'apply' | 'search'>('search')
|
||||
/** 申请目标用户 */
|
||||
const targetUser = ref<null | UserVO>(null)
|
||||
/** 申请理由(默认填「我是 ${当前昵称}」,对齐微信交互) */
|
||||
const applyContent = ref('')
|
||||
/** 对接收方的备注(仅自己可见) */
|
||||
const displayName = ref('')
|
||||
const submitting = ref(false)
|
||||
const keyword = ref('') // 搜索关键字
|
||||
const users = ref<SystemUserApi.UserSimple[]>([]) // 搜索结果
|
||||
const searched = ref(false) // 是否已搜索
|
||||
const loading = ref(false) // 搜索加载中
|
||||
const step = ref<'apply' | 'search'>('search') // 当前步骤:search=搜索列表;apply=申请表单
|
||||
const targetUser = ref<null | SystemUserApi.UserSimple>(null) // 申请目标用户
|
||||
const applyContent = ref('') // 申请理由(默认填「我是 ${当前昵称}」,对齐微信交互)
|
||||
const displayName = ref('') // 对接收方的备注(仅自己可见)
|
||||
const submitting = ref(false) // 提交中
|
||||
|
||||
/** 弹窗标题随步骤切换 */
|
||||
const dialogTitle = computed(() => (step.value === 'apply' ? '申请添加朋友' : '添加好友'))
|
||||
|
|
@ -115,7 +106,7 @@ async function handleSearch() {
|
|||
}
|
||||
|
||||
/** 进入申请步骤:预填申请理由「我是 ${当前用户昵称}」(对齐微信交互) */
|
||||
function enterApply(user: UserVO) {
|
||||
function enterApply(user: SystemUserApi.UserSimple) {
|
||||
targetUser.value = user
|
||||
const myNickname = userStore.userInfo?.nickname || ''
|
||||
applyContent.value = myNickname ? `我是${myNickname}` : ''
|
||||
|
|
@ -184,57 +175,57 @@ async function handleSubmitApply() {
|
|||
|
||||
<Spin :spinning="loading" wrapper-class-name="w-full">
|
||||
<div class="h-[400px] mt-2.5">
|
||||
<div
|
||||
v-if="visibleUsers.length === 0"
|
||||
class="py-10 text-13px text-center text-[var(--ant-color-text-disabled)]"
|
||||
>
|
||||
{{ searched ? '没有搜到用户' : '输入关键字后回车开始搜索' }}
|
||||
</div>
|
||||
<div
|
||||
v-for="user in visibleUsers"
|
||||
:key="user.id"
|
||||
class="flex gap-3 items-center px-2 py-2.5 border-b border-b-solid border-[var(--ant-color-border-secondary)]"
|
||||
>
|
||||
<UserAvatar
|
||||
:id="user.id"
|
||||
:url="user.avatar"
|
||||
:name="user.nickname"
|
||||
:size="42"
|
||||
:clickable="false"
|
||||
/>
|
||||
<div class="flex-1 min-w-0 overflow-hidden">
|
||||
<!-- 昵称 + 性别图标 -->
|
||||
<div
|
||||
class="flex items-center gap-1 text-sm font-semibold text-[var(--ant-color-text)]"
|
||||
>
|
||||
<span class="truncate">{{ user.nickname }}</span>
|
||||
<Icon
|
||||
v-if="getGenderIcon(user.sex)"
|
||||
:icon="getGenderIcon(user.sex)"
|
||||
:size="14"
|
||||
:color="getGenderColor(user.sex)"
|
||||
class="flex-shrink-0"
|
||||
/>
|
||||
</div>
|
||||
<!-- 部门 -->
|
||||
<div
|
||||
v-if="user.deptName"
|
||||
class="mt-0.5 text-xs truncate text-[var(--ant-color-text-secondary)]"
|
||||
>
|
||||
{{ user.deptName }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 已是好友显示「已添加」;否则显示「添加」(点击进入 apply 步骤) -->
|
||||
<Button
|
||||
v-if="!friendStore.isActiveFriend(user.id)"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="enterApply(user)"
|
||||
<div
|
||||
v-if="visibleUsers.length === 0"
|
||||
class="py-10 text-13px text-center text-[var(--ant-color-text-disabled)]"
|
||||
>
|
||||
添加
|
||||
</Button>
|
||||
<Button v-else size="small" disabled>已添加</Button>
|
||||
</div>
|
||||
{{ searched ? '没有搜到用户' : '输入关键字后回车开始搜索' }}
|
||||
</div>
|
||||
<div
|
||||
v-for="user in visibleUsers"
|
||||
:key="user.id"
|
||||
class="flex gap-3 items-center px-2 py-2.5 border-b border-b-solid border-[var(--ant-color-border-secondary)]"
|
||||
>
|
||||
<UserAvatar
|
||||
:id="user.id"
|
||||
:url="user.avatar"
|
||||
:name="user.nickname"
|
||||
:size="42"
|
||||
:clickable="false"
|
||||
/>
|
||||
<div class="flex-1 min-w-0 overflow-hidden">
|
||||
<!-- 昵称 + 性别图标 -->
|
||||
<div
|
||||
class="flex items-center gap-1 text-sm font-semibold text-[var(--ant-color-text)]"
|
||||
>
|
||||
<span class="truncate">{{ user.nickname }}</span>
|
||||
<Icon
|
||||
v-if="getGenderIcon(user.sex)"
|
||||
:icon="getGenderIcon(user.sex)"
|
||||
:size="14"
|
||||
:color="getGenderColor(user.sex)"
|
||||
class="flex-shrink-0"
|
||||
/>
|
||||
</div>
|
||||
<!-- 部门 -->
|
||||
<div
|
||||
v-if="user.deptName"
|
||||
class="mt-0.5 text-xs truncate text-[var(--ant-color-text-secondary)]"
|
||||
>
|
||||
{{ user.deptName }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 已是好友显示「已添加」;否则显示「添加」(点击进入 apply 步骤) -->
|
||||
<Button
|
||||
v-if="!friendStore.isActiveFriend(user.id)"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="enterApply(user)"
|
||||
>
|
||||
添加
|
||||
</Button>
|
||||
<Button v-else size="small" disabled>已添加</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Spin>
|
||||
</template>
|
||||
|
|
@ -272,7 +263,7 @@ async function handleSubmitApply() {
|
|||
:maxlength="255"
|
||||
show-count
|
||||
placeholder="请填写申请理由"
|
||||
/>
|
||||
/>
|
||||
|
||||
<div class="text-13px text-[var(--ant-color-text-secondary)] mt-3 mb-1.5">备注</div>
|
||||
<Input
|
||||
|
|
|
|||
|
|
@ -3,11 +3,10 @@ import type { GroupMemberLite } from './GroupMember.vue'
|
|||
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { Button, Modal } from 'ant-design-vue'
|
||||
import { Button, message, Modal } from 'ant-design-vue'
|
||||
|
||||
import { addGroupAdmin, removeGroupAdmin } from '#/api/im/group'
|
||||
import { GROUP_ADMIN_MAX_COUNT } from '#/views/im/utils/config'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
|
||||
import GroupMemberPickerPanel from '../picker/GroupMemberPickerPanel.vue'
|
||||
|
||||
|
|
@ -18,14 +17,11 @@ const emit = defineEmits<{
|
|||
reload: []
|
||||
}>()
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const visible = ref(false)
|
||||
const submitting = ref(false)
|
||||
const groupId = ref(0)
|
||||
const members = ref<GroupMemberLite[]>([])
|
||||
/** 当前管理员 userId 列表:默认勾选 + 提交时 diff */
|
||||
const currentAdminIds = ref<number[]>([])
|
||||
const currentAdminIds = ref<number[]>([]) // 当前管理员 userId 列表:默认勾选 + 提交时 diff
|
||||
const hideIds = ref<number[]>([])
|
||||
const maxSize = ref(GROUP_ADMIN_MAX_COUNT)
|
||||
const selectedIds = ref<number[]>([])
|
||||
|
|
|
|||
|
|
@ -27,11 +27,13 @@ const props = withDefaults(
|
|||
url?: string // 服务端已设置的群头像 URL;非空则直接用,不拼图
|
||||
}>(),
|
||||
{
|
||||
size: 42,
|
||||
radius: '15%',
|
||||
clickable: false,
|
||||
name: '',
|
||||
previewable: false,
|
||||
previewZIndex: 2000
|
||||
previewZIndex: 2000,
|
||||
radius: '15%',
|
||||
size: 42,
|
||||
url: ''
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@ import type { FriendLite } from '../../types'
|
|||
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { Button, Modal } from 'ant-design-vue'
|
||||
import { Button, message, Modal } from 'ant-design-vue'
|
||||
|
||||
import { createGroup } from '#/api/im/group'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
|
||||
import { buildDefaultGroupName } from '../../../utils/group'
|
||||
import { useFriendStore } from '../../store/friendStore'
|
||||
|
|
@ -20,7 +19,6 @@ const emit = defineEmits<{
|
|||
created: [groupId: number]
|
||||
}>()
|
||||
|
||||
const message = useMessage()
|
||||
const friendStore = useFriendStore()
|
||||
const groupStore = useGroupStore()
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,13 @@ import type { GroupLite } from '../../types'
|
|||
import { computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { prompt } from '@vben/common-ui'
|
||||
|
||||
import { Input, message } from 'ant-design-vue'
|
||||
|
||||
import { applyJoinGroup } from '#/api/im/group/request'
|
||||
|
||||
import { ImConversationType, ImGroupAddSource } from '../../../utils/constants'
|
||||
import { useMessage } from '../../../utils/message-feedback'
|
||||
import { getGroupDisplayName } from '../../../utils/user'
|
||||
import { useConversationStore } from '../../store/conversationStore'
|
||||
import { useGroupStore } from '../../store/groupStore'
|
||||
|
|
@ -20,7 +23,6 @@ const uiStore = useImUiStore()
|
|||
const conversationStore = useConversationStore()
|
||||
const groupStore = useGroupStore()
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
|
||||
const card = computed(() => uiStore.groupInfoCard)
|
||||
|
||||
|
|
@ -65,15 +67,22 @@ function handleChat(group: GroupLite) {
|
|||
/** 加入群聊:先关浮层(避免与 prompt 的 mask 互相遮挡)→ 弹申请理由(可选)→ applyJoinGroup */
|
||||
async function handleApply(group: GroupLite) {
|
||||
handleClose()
|
||||
let applyContent = ''
|
||||
let applyContent: string
|
||||
try {
|
||||
const result = await message.prompt(`申请加入「${group.name || ''}」`, {
|
||||
const result = await prompt<string>({
|
||||
cancelText: '取消',
|
||||
component: Input,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请填写验证消息(可选)'
|
||||
},
|
||||
content: '',
|
||||
defaultValue: '',
|
||||
okText: '发送申请',
|
||||
placeholder: '请填写验证消息(可选)'
|
||||
confirmText: '发送申请',
|
||||
modelPropName: 'value',
|
||||
title: `申请加入「${group.name || ''}」`
|
||||
})
|
||||
applyContent = (result.value || '').trim()
|
||||
applyContent = (result || '').trim()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,12 @@ import { computed, ref } from 'vue'
|
|||
|
||||
import { CommonStatusEnum } from '@vben/constants'
|
||||
|
||||
import { Button, Modal } from 'ant-design-vue'
|
||||
import { Button, message, Modal } from 'ant-design-vue'
|
||||
|
||||
import { inviteGroupMember } from '#/api/im/group/member'
|
||||
import { getCurrentUserId } from '#/views/im/utils/auth'
|
||||
import { GROUP_MAX_MEMBER } from '#/views/im/utils/config'
|
||||
import { ImGroupMemberRole } from '#/views/im/utils/constants'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
|
||||
import { useFriendStore } from '../../store/friendStore'
|
||||
import { useGroupStore } from '../../store/groupStore'
|
||||
|
|
@ -24,7 +23,6 @@ const emit = defineEmits<{
|
|||
reload: [friendIds: number[]]
|
||||
}>()
|
||||
|
||||
const message = useMessage()
|
||||
const friendStore = useFriendStore()
|
||||
const groupStore = useGroupStore()
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@ import type { GroupMemberLite } from './GroupMember.vue'
|
|||
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { Button, Modal } from 'ant-design-vue'
|
||||
import { Button, message, Modal } from 'ant-design-vue'
|
||||
|
||||
import { removeGroupMember } from '#/api/im/group/member'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
|
||||
import GroupMemberPickerPanel from '../picker/GroupMemberPickerPanel.vue'
|
||||
|
||||
|
|
@ -17,8 +16,6 @@ const emit = defineEmits<{
|
|||
reload: []
|
||||
}>()
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const visible = ref(false)
|
||||
const submitting = ref(false)
|
||||
const groupId = ref(0)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { Button, Modal } from 'ant-design-vue'
|
||||
import { Button, message, Modal } from 'ant-design-vue'
|
||||
|
||||
import { muteMember } from '#/api/im/group'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
|
||||
defineOptions({ name: 'ImGroupMuteMemberDialog' })
|
||||
|
||||
|
|
@ -12,8 +11,6 @@ const emit = defineEmits<{
|
|||
success: []
|
||||
}>()
|
||||
|
||||
const { success: successMessage } = useMessage()
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const groupId = ref(0)
|
||||
|
|
@ -49,7 +46,7 @@ async function handleConfirm() {
|
|||
userId: userId.value,
|
||||
mutedSeconds: selected.value
|
||||
})
|
||||
successMessage('禁言成功')
|
||||
message.success('禁言成功')
|
||||
visible.value = false
|
||||
emit('success')
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ import type { GroupMemberLite } from './GroupMember.vue'
|
|||
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { Button, Modal } from 'ant-design-vue'
|
||||
import { confirm } from '@vben/common-ui'
|
||||
|
||||
import { Button, message, Modal } from 'ant-design-vue'
|
||||
|
||||
import { transferGroupOwner } from '#/api/im/group'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
|
||||
import GroupMemberPickerPanel from '../picker/GroupMemberPickerPanel.vue'
|
||||
|
||||
|
|
@ -17,8 +18,6 @@ const emit = defineEmits<{
|
|||
reload: []
|
||||
}>()
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const visible = ref(false)
|
||||
const submitting = ref(false)
|
||||
const groupId = ref(0)
|
||||
|
|
@ -57,7 +56,7 @@ async function handleOk() {
|
|||
return
|
||||
}
|
||||
try {
|
||||
await message.confirm(
|
||||
await confirm(
|
||||
`确定将群主转让给 ${newOwner.value.showName}?转让后你将变为普通成员,无法撤销。`,
|
||||
'确认转让群主'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,25 +1,26 @@
|
|||
<script lang="ts" setup>
|
||||
import type { ImGroupRequestApi } from '#/api/im/group/request'
|
||||
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import { Empty, Modal, Spin } from 'ant-design-vue'
|
||||
import { prompt } from '@vben/common-ui'
|
||||
|
||||
import { getGroupRequestListByGroupId, type ImGroupRequestRespVO } from '#/api/im/group/request'
|
||||
import { Empty, Input, message, Modal, Spin } from 'ant-design-vue'
|
||||
|
||||
import { getGroupRequestListByGroupId } from '#/api/im/group/request'
|
||||
import { ImGroupRequestHandleResult } from '#/views/im/utils/constants'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
|
||||
import { useGroupRequestStore } from '../../store/groupRequestStore'
|
||||
import UserAvatar from '../user/UserAvatar.vue'
|
||||
|
||||
defineOptions({ name: 'ImGroupRequestListDialog' })
|
||||
|
||||
const message = useMessage()
|
||||
const groupRequestStore = useGroupRequestStore()
|
||||
|
||||
const visible = ref(false)
|
||||
/** 当前展示的群编号;undefined 时走全局未处理列表(store.unhandledList) */
|
||||
const groupId = ref<number | undefined>()
|
||||
const groupId = ref<number | undefined>() // 当前展示的群编号;undefined 时走全局未处理列表(store.unhandledList)
|
||||
const loading = ref(false)
|
||||
const groupList = ref<ImGroupRequestRespVO[]>([])
|
||||
const groupList = ref<ImGroupRequestApi.GroupRequestRespVO[]>([])
|
||||
const actingId = ref<null | number>(null)
|
||||
|
||||
defineExpose({
|
||||
|
|
@ -32,7 +33,7 @@ defineExpose({
|
|||
})
|
||||
|
||||
/** 数据源:单群模式用 fetch 回来的 groupList;全局模式直接读 store.unhandledList,处理后 store 自动 reactive 同步 */
|
||||
const list = computed<ImGroupRequestRespVO[]>(() =>
|
||||
const list = computed<ImGroupRequestApi.GroupRequestRespVO[]>(() =>
|
||||
groupId.value ? groupList.value : groupRequestStore.unhandledList
|
||||
)
|
||||
|
||||
|
|
@ -104,7 +105,7 @@ async function fetchList(targetGroupId: number) {
|
|||
}
|
||||
|
||||
/** 同意:走 store 同步全局未处理列表 + 本地更新 handleResult 让按钮变灰 */
|
||||
async function handleAgree(item: ImGroupRequestRespVO) {
|
||||
async function handleAgree(item: ImGroupRequestApi.GroupRequestRespVO) {
|
||||
if (actingId.value !== null) return
|
||||
actingId.value = item.id
|
||||
try {
|
||||
|
|
@ -117,14 +118,21 @@ async function handleAgree(item: ImGroupRequestRespVO) {
|
|||
}
|
||||
|
||||
/** 拒绝:弹理由输入框;为空则不带 handleContent */
|
||||
async function handleRefuse(item: ImGroupRequestRespVO) {
|
||||
async function handleRefuse(item: ImGroupRequestApi.GroupRequestRespVO) {
|
||||
if (actingId.value !== null) return
|
||||
let handleContent = ''
|
||||
let handleContent: string
|
||||
try {
|
||||
const result = await message.prompt('拒绝申请', {
|
||||
placeholder: '请输入拒绝理由(可选)'
|
||||
const result = await prompt<string>({
|
||||
component: Input,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请输入拒绝理由(可选)'
|
||||
},
|
||||
content: '',
|
||||
modelPropName: 'value',
|
||||
title: '拒绝申请'
|
||||
})
|
||||
handleContent = result.value || ''
|
||||
handleContent = result || ''
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ import { computed, ref } from 'vue'
|
|||
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
|
||||
import { Input } from 'ant-design-vue'
|
||||
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
import { Input, message } from 'ant-design-vue'
|
||||
|
||||
import { ImConversationType } from '../../../utils/constants'
|
||||
import { filterConversationsByKeyword, getConversationKey } from '../../../utils/conversation'
|
||||
|
|
@ -40,17 +38,14 @@ const props = withDefaults(
|
|||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
'create-chat': []
|
||||
createChat: []
|
||||
/** 用户在「最近转发」段进入移除模式后点 ×;业务壳收到后调 conversationStore.removeRecentForwardConversationKey 落盘 */
|
||||
'remove-recent': [key: string]
|
||||
removeRecent: [key: string]
|
||||
'update:selectedKeys': [value: string[]]
|
||||
}>()
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const keyword = ref('')
|
||||
/** 「最近转发」段是否处于移除模式:true 时头像右上角变 × 不再切勾选 */
|
||||
const recentRemoveMode = ref(false)
|
||||
const recentRemoveMode = ref(false) // 「最近转发」段是否处于移除模式:true 时头像右上角变 × 不再切勾选
|
||||
|
||||
/** 全量会话的 key→Conversation 映射,已选 / 最近转发反查共用,避免每次 O(N) 扫 */
|
||||
const byKey = computed(() => {
|
||||
|
|
@ -200,7 +195,7 @@ function handleToggle(conversation: Conversation) {
|
|||
<span
|
||||
v-if="recentRemoveMode"
|
||||
class="flex absolute -top-1 -right-1 justify-center items-center w-4 h-4 rounded-full cursor-pointer bg-[var(--ant-color-fill-dark)] text-[var(--ant-color-text)]"
|
||||
@click.stop="emit('remove-recent', getConversationKey(conversation))"
|
||||
@click.stop="emit('removeRecent', getConversationKey(conversation))"
|
||||
>
|
||||
<Icon icon="ant-design:close-outlined" :size="10" />
|
||||
</span>
|
||||
|
|
@ -235,7 +230,7 @@ function handleToggle(conversation: Conversation) {
|
|||
<div
|
||||
v-if="showCreateChat && !keyword.trim()"
|
||||
class="flex gap-2.5 items-center px-3 py-1.5 cursor-pointer hover:bg-[var(--ant-color-fill)]"
|
||||
@click="emit('create-chat')"
|
||||
@click="emit('createChat')"
|
||||
>
|
||||
<span
|
||||
class="flex flex-shrink-0 justify-center items-center w-8 h-8 rounded-full bg-[var(--ant-color-fill)] text-[var(--ant-color-text-secondary)]"
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ import { computed, ref } from 'vue'
|
|||
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
|
||||
import { Input } from 'ant-design-vue'
|
||||
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
import { Input, message } from 'ant-design-vue'
|
||||
|
||||
import { useFriendBuckets } from '../../composables/useFriendBuckets'
|
||||
import { useSelectedItems } from '../../composables/useSelectedItems'
|
||||
|
|
@ -43,8 +41,6 @@ const emit = defineEmits<{
|
|||
'update:selectedIds': [value: number[]]
|
||||
}>()
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const keyword = ref('')
|
||||
|
||||
/** id → friend 映射,已选反查 / 三态判定共用,避免每次 O(N) 扫 */
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ import { computed, ref } from 'vue'
|
|||
import { CommonStatusEnum } from '@vben/constants'
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
|
||||
import { Input } from 'ant-design-vue'
|
||||
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
import { Input, message } from 'ant-design-vue'
|
||||
|
||||
import { useSelectedItems } from '../../composables/useSelectedItems'
|
||||
import GroupMemberGrid from '../group/GroupMemberGrid.vue'
|
||||
|
|
@ -48,8 +46,6 @@ const emit = defineEmits<{
|
|||
'update:selectedIds': [value: number[]]
|
||||
}>()
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const keyword = ref('')
|
||||
|
||||
/** userId → member 映射,已选反查 / 三态判定共用 */
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import type { CallParticipantVM } from './RtcCallParticipantTile.vue'
|
|||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import { useIntervalFn } from '@vueuse/core'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { Track } from 'livekit-client'
|
||||
|
||||
import {
|
||||
|
|
@ -21,7 +22,6 @@ import {
|
|||
ImRtcCallMediaType,
|
||||
ImRtcCallStage
|
||||
} from '#/views/im/utils/constants'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
import { getSenderAvatar, getSenderDisplayName } from '#/views/im/utils/user'
|
||||
|
||||
import { useLiveKitRoom } from '../../composables/useLiveKitRoom'
|
||||
|
|
@ -34,7 +34,6 @@ import RtcCallRunning from './RtcCallRunning.vue'
|
|||
defineOptions({ name: 'ImRtcCallContainer' })
|
||||
|
||||
const rtcStore = useRtcStore()
|
||||
const message = useMessage()
|
||||
const lk = useLiveKitRoom()
|
||||
|
||||
const memberPickerRef = ref<InstanceType<typeof RtcCallMemberPickerDialog>>()
|
||||
|
|
@ -123,16 +122,13 @@ const participants = computed<CallParticipantVM[]>(() => {
|
|||
const conversationType = call.conversationType
|
||||
const targetId = call.groupId ?? 0
|
||||
const myId = getCurrentUserId()
|
||||
const result: CallParticipantVM[] = []
|
||||
|
||||
// 自己
|
||||
result.push({
|
||||
const result: CallParticipantVM[] = [{
|
||||
userId: myId,
|
||||
nickname: getSenderDisplayName(myId, conversationType, targetId),
|
||||
avatar: getSenderAvatar(myId, conversationType, targetId) || undefined,
|
||||
isLocal: true,
|
||||
videoStream: localStream.value
|
||||
})
|
||||
}]
|
||||
|
||||
// 已加入的远端:实际推流;屏幕共享在网格里独占该成员的格子,无则降级 Camera
|
||||
const joined = new Set<number>()
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ const acceptDisabled = computed(() => !!props.accepting || !!props.rejecting)
|
|||
/** 拒绝按钮禁用态 */
|
||||
const rejectDisabled = computed(() => !!props.rejecting || !!props.accepting)
|
||||
|
||||
/** 群通话成员;缓存为空时用 INVITE 载荷里的主叫兜底,避免空白 */
|
||||
// 群通话成员;缓存为空时用 INVITE 载荷里的主叫兜底,避免空白
|
||||
const callMembers = useGroupCallMembers(
|
||||
computed(() => (props.isGroup ? props.payload?.groupId : undefined)),
|
||||
computed(() => props.payload?.inviterUserId)
|
||||
|
|
|
|||
|
|
@ -21,16 +21,11 @@ type PickerMode = 'add' | 'invite'
|
|||
|
||||
const groupStore = useGroupStore()
|
||||
|
||||
/** 弹窗显隐 */
|
||||
const visible = ref(false)
|
||||
/** 当前群编号;open 时由调用方传入 */
|
||||
const groupId = ref(0)
|
||||
/** 弹窗用途;invite=发起群通话选邀请人 / add=通话中追加成员 */
|
||||
const mode = ref<PickerMode>('invite')
|
||||
/** 置灰的 userId 列表;add 场景把已在通话内的人禁用 */
|
||||
const excludeUserIds = ref<number[]>([])
|
||||
/** 当前选中的 userId 列表;GroupMemberPickerPanel v-model 绑过来 */
|
||||
const selectedIds = ref<number[]>([])
|
||||
const visible = ref(false) // 弹窗显隐
|
||||
const groupId = ref(0) // 当前群编号;open 时由调用方传入
|
||||
const mode = ref<PickerMode>('invite') // 弹窗用途;invite=发起群通话选邀请人 / add=通话中追加成员
|
||||
const excludeUserIds = ref<number[]>([]) // 置灰的 userId 列表;add 场景把已在通话内的人禁用
|
||||
const selectedIds = ref<number[]>([]) // 当前选中的 userId 列表;GroupMemberPickerPanel v-model 绑过来
|
||||
|
||||
/** 标题;按用途切换 */
|
||||
const title = computed(() => (mode.value === 'add' ? '添加成员' : '选择成员'))
|
||||
|
|
|
|||
|
|
@ -57,8 +57,7 @@ const remoteAudioRef = useMediaStreamElement<HTMLAudioElement>(() => props.remot
|
|||
/** 1v1 视频:是否有远端视频流 */
|
||||
const hasRemoteVideo = computed(() => !props.isGroup && !!props.remoteVideoStream)
|
||||
|
||||
/** 通话时长;仅 1v1 语音视图需要展示,其它视图不启 tick */
|
||||
const now = ref(Date.now())
|
||||
const now = ref(Date.now()) // 通话时长;仅 1v1 语音视图需要展示,其它视图不启 tick
|
||||
let tick = 0
|
||||
watch(
|
||||
() => props.isGroup || props.isVideo,
|
||||
|
|
|
|||
|
|
@ -5,11 +5,10 @@ import { DICT_TYPE } from '@vben/constants'
|
|||
import { getDictLabel } from '@vben/hooks'
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
|
||||
import { Popover } from 'ant-design-vue'
|
||||
import { message, Popover } from 'ant-design-vue'
|
||||
|
||||
import { getActiveCall, joinCall } from '#/api/im/rtc'
|
||||
import { getCurrentUserId } from '#/views/im/utils/auth'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
|
||||
import { useGroupCallMembers } from '../../composables/useGroupCallMembers'
|
||||
import { useRtcStore } from '../../store/rtcStore'
|
||||
|
|
@ -22,7 +21,6 @@ const props = defineProps<{
|
|||
}>()
|
||||
|
||||
const rtcStore = useRtcStore()
|
||||
const message = useMessage()
|
||||
|
||||
const popoverVisible = ref(false)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,10 @@ import { computed, ref } from 'vue'
|
|||
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
|
||||
import { Button, Input, Modal } from 'ant-design-vue'
|
||||
import { Button, Input, message, Modal } from 'ant-design-vue'
|
||||
|
||||
import { createGroup } from '#/api/im/group'
|
||||
import CardBubble from '#/views/im/home/components/card/CardBubble.vue'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
|
||||
import { ImContentType, ImConversationType, isGroupConversation } from '../../../utils/constants'
|
||||
import { getConversationKey } from '../../../utils/conversation'
|
||||
|
|
@ -26,7 +25,6 @@ import FriendPickerPanel from '../picker/FriendPickerPanel.vue'
|
|||
|
||||
defineOptions({ name: 'ImRecommendCardDialog' })
|
||||
|
||||
const message = useMessage()
|
||||
const conversationStore = useConversationStore()
|
||||
const friendStore = useFriendStore()
|
||||
const groupStore = useGroupStore()
|
||||
|
|
@ -34,14 +32,12 @@ const { sendRaw, send } = useMessageSender()
|
|||
|
||||
const visible = ref(false)
|
||||
const target = ref<CardTarget | null>(null)
|
||||
/** 当前视图:默认会话选择,「创建聊天」入口切到好友选择 */
|
||||
const view = ref<'contact' | 'conversation'>('conversation')
|
||||
const view = ref<'contact' | 'conversation'>('conversation') // 当前视图:默认会话选择,「创建聊天」入口切到好友选择
|
||||
const selectedKeys = ref<string[]>([])
|
||||
const selectedFriendIds = ref<number[]>([])
|
||||
const leaveMessage = ref('')
|
||||
const sending = ref(false)
|
||||
/** 表情面板显隐:右侧 smile icon 切换 */
|
||||
const emojiVisible = ref(false)
|
||||
const emojiVisible = ref(false) // 表情面板显隐:右侧 smile icon 切换
|
||||
|
||||
defineExpose({
|
||||
/** 打开推荐弹窗:reset → 灌参 → visible=true */
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
<script lang="ts" setup>
|
||||
import type { User } from '../../types'
|
||||
|
||||
import { computed, nextTick, ref, watch } from 'vue'
|
||||
import { computed, h, nextTick, ref, watch } from 'vue'
|
||||
|
||||
import { confirm } from '@vben/common-ui'
|
||||
import { DICT_TYPE } from '@vben/constants'
|
||||
import { getDictLabel } from '@vben/hooks'
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
|
||||
import { Button, Dropdown, Input, Menu } from 'ant-design-vue'
|
||||
import { Button, Checkbox, Dropdown, Input, Menu, message } from 'ant-design-vue'
|
||||
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
import { getSimpleUser, type UserVO } from '#/views/im/utils/system-user'
|
||||
import { getSimpleUser, type SystemUserApi } from '#/api/system/user'
|
||||
import { formatDate } from '#/views/im/utils/time'
|
||||
|
||||
import { ImFriendAddSource } from '../../../utils/constants'
|
||||
import { toUserCardTarget } from '../../../utils/message'
|
||||
import { confirmDeleteFriend } from '../../../utils/message-feedback'
|
||||
import { getGenderColor, getGenderIcon } from '../../../utils/user'
|
||||
import { useFriendStore } from '../../store/friendStore'
|
||||
import FriendAddDialog from '../friend/FriendAddDialog.vue'
|
||||
|
|
@ -39,9 +38,11 @@ const props = withDefaults(
|
|||
user: null | User
|
||||
}>(),
|
||||
{
|
||||
relation: 'readonly',
|
||||
addSource: ImFriendAddSource.SEARCH,
|
||||
addSourceExtra: '',
|
||||
displayName: '',
|
||||
previewZIndex: 2000,
|
||||
addSource: ImFriendAddSource.SEARCH
|
||||
relation: 'readonly'
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -63,11 +64,9 @@ const emit = defineEmits<{
|
|||
*/
|
||||
export type UserInfoRelation = 'friend' | 'readonly' | 'self' | 'stranger'
|
||||
|
||||
const message = useMessage()
|
||||
const friendStore = useFriendStore()
|
||||
|
||||
/** 起手 user + getSimpleUser 合并后的完整对象(性别 / 部门补齐用) */
|
||||
const full = ref<null | User>(props.user)
|
||||
const full = ref<null | User>(props.user) // 起手 user + getSimpleUser 合并后的完整对象(性别 / 部门补齐用)
|
||||
|
||||
/** 主标题:备注优先(好友场景),其次原昵称 */
|
||||
const headerName = computed(() => props.displayName || full.value?.nickname || '')
|
||||
|
|
@ -85,8 +84,7 @@ const friendInfo = computed(() =>
|
|||
/** 是否已拉黑:菜单项「加入黑名单 / 移出黑名单」按这个切换 */
|
||||
const isBlocked = computed(() => !!friendInfo.value?.blocked)
|
||||
|
||||
/** 备注内联编辑:editingRemark 控制输入态;user 切换时由下面的 watch 复位避免脏态泄漏 */
|
||||
const editingRemark = ref(false)
|
||||
const editingRemark = ref(false) // 备注内联编辑:editingRemark 控制输入态;user 切换时由下面的 watch 复位避免脏态泄漏
|
||||
const remarkInput = ref('')
|
||||
const remarkInputRef = ref<null | { focus: () => void; select?: () => void }>(null)
|
||||
|
||||
|
|
@ -169,11 +167,9 @@ function handleComingSoon(featureName: string) {
|
|||
|
||||
// ==================== 添加好友 / 删除好友 ====================
|
||||
|
||||
/** 加好友弹窗 ref:handleAddFriend 调 open({ presetUser, addSource, addSourceExtra }) 触发 */
|
||||
const friendAddDialogRef = ref<InstanceType<typeof FriendAddDialog>>()
|
||||
const friendAddDialogRef = ref<InstanceType<typeof FriendAddDialog>>() // 加好友弹窗 ref:handleAddFriend 调 open({ presetUser, addSource, addSourceExtra }) 触发
|
||||
|
||||
/** 推荐名片弹窗 ref:handleRecommend 调用 open({ target }) 打开 */
|
||||
const recommendDialogRef = ref<InstanceType<typeof RecommendCardDialog>>()
|
||||
const recommendDialogRef = ref<InstanceType<typeof RecommendCardDialog>>() // 推荐名片弹窗 ref:handleRecommend 调用 open({ target }) 打开
|
||||
/** 把他推荐给朋友:弹 RecommendCardDialog 选目标会话 */
|
||||
function handleRecommend() {
|
||||
if (!props.user?.id) {
|
||||
|
|
@ -191,14 +187,14 @@ function handleAddFriend() {
|
|||
if (!props.user?.id) {
|
||||
return
|
||||
}
|
||||
const presetUser: UserVO = {
|
||||
const presetUser: SystemUserApi.UserSimple = {
|
||||
id: props.user.id,
|
||||
nickname: props.user.nickname,
|
||||
avatar: props.user.avatar,
|
||||
sex: props.user.sex,
|
||||
deptId: props.user.deptId,
|
||||
deptName: props.user.deptName
|
||||
} as UserVO
|
||||
} as SystemUserApi.UserSimple
|
||||
friendAddDialogRef.value?.open({
|
||||
presetUser,
|
||||
addSource: props.addSource,
|
||||
|
|
@ -213,7 +209,7 @@ async function handleBlock() {
|
|||
}
|
||||
const target = props.user
|
||||
try {
|
||||
await message.confirm(`确定将「${target.nickname || ''}」加入黑名单吗?`, '加入黑名单')
|
||||
await confirm(`确定将「${target.nickname || ''}」加入黑名单吗?`, '加入黑名单')
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
|
@ -238,7 +234,25 @@ async function handleDeleteFriend() {
|
|||
const target = props.user
|
||||
const clearConversation = ref(true)
|
||||
try {
|
||||
await confirmDeleteFriend(target.nickname || '', clearConversation)
|
||||
await confirm({
|
||||
cancelText: '取消',
|
||||
confirmText: '删除',
|
||||
content: h('div', { class: 'flex flex-col gap-3 text-sm' }, [
|
||||
h('div', `确定删除好友「${target.nickname || ''}」?`),
|
||||
h(
|
||||
Checkbox,
|
||||
{
|
||||
checked: clearConversation.value,
|
||||
'onUpdate:checked': (value: boolean) => {
|
||||
clearConversation.value = value
|
||||
}
|
||||
},
|
||||
() => '同时清空聊天记录'
|
||||
)
|
||||
]),
|
||||
icon: 'warning',
|
||||
title: '删除联系人'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ import type { Conversation, Message } from '../types'
|
|||
|
||||
import type { AxiosProgressEvent } from '#/api/infra/file'
|
||||
|
||||
import { isOpenableUrl } from '@vben/utils'
|
||||
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
import { uploadFile } from '#/api/infra/file'
|
||||
import { getCurrentUserId } from '#/views/im/utils/auth'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
import { updateFile } from '#/views/im/utils/upload'
|
||||
import { isOpenableUrl } from '#/views/im/utils/url'
|
||||
|
||||
import {
|
||||
MESSAGE_FILE_MAX_MB,
|
||||
|
|
@ -123,7 +125,7 @@ export interface UploadAndSendMediaOptions {
|
|||
*
|
||||
* 与 useMessageSender.sendRaw 的「先发请求再 ack」不同,媒体链路必须「先占位再上传」:
|
||||
* 1. 立即 insertMessage 写入占位消息(status=SENDING、content 用 blob URL、_localFile 内存留 File)
|
||||
* 2. updateFile 上传,onUploadProgress 回调 patchMessage 更新 uploadProgress;UI 实时显示进度条
|
||||
* 2. uploadFile 上传,onUploadProgress 回调 patchMessage 更新 uploadProgress;UI 实时显示进度条
|
||||
* 3. 上传成功后用真实 url 重生 content,patchMessage 替换;旧 blob URL 由 store 自动 revoke
|
||||
* 4. 走 sendRaw(existingClientMessageId) 复用占位发送请求,避免重复插入两条
|
||||
*
|
||||
|
|
@ -168,7 +170,6 @@ export const useMediaUploader = () => {
|
|||
const conversationStore = useConversationStore()
|
||||
const messageStore = useMessageStore()
|
||||
const muteOverlay = useMuteOverlay()
|
||||
const message = useMessage()
|
||||
const { sendRaw } = useMessageSender()
|
||||
|
||||
/**
|
||||
|
|
@ -361,13 +362,10 @@ export const useMediaUploader = () => {
|
|||
// 2. 上传:进度回调 patch uploadProgress;失败保留 _localFile 供重试
|
||||
let url: string | undefined
|
||||
try {
|
||||
const form = new FormData()
|
||||
form.append('file', opts.file)
|
||||
const res = (await updateFile(
|
||||
form,
|
||||
url = await uploadFile(
|
||||
{ file: opts.file },
|
||||
createUploadProgressHandler(conversation, clientMessageId)
|
||||
)) as { data?: string }
|
||||
url = res?.data
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(`[IM] ${handler.kind}上传失败`, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,14 @@
|
|||
import type { Message } from '../types'
|
||||
|
||||
import type { ImChannelMessageApi } from '#/api/im/message/channel'
|
||||
import type { ImGroupMessageApi } from '#/api/im/message/group'
|
||||
import type { ImPrivateMessageApi } from '#/api/im/message/private'
|
||||
|
||||
import { watch } from 'vue'
|
||||
|
||||
import {
|
||||
pullChannelMessages as apiPullChannelMessages,
|
||||
type ImChannelMessageRespVO
|
||||
} from '#/api/im/message/channel'
|
||||
import {
|
||||
pullGroupMessages as apiPullGroupMessages,
|
||||
type ImGroupMessageRespVO
|
||||
} from '#/api/im/message/group'
|
||||
import {
|
||||
getPrivateMaxReadMessageId as apiGetPrivateMaxReadMessageId,
|
||||
pullPrivateMessages as apiPullPrivateMessages,
|
||||
type ImPrivateMessageRespVO
|
||||
} from '#/api/im/message/private'
|
||||
import { pullChannelMessages as apiPullChannelMessages } from '#/api/im/message/channel'
|
||||
import { pullGroupMessages as apiPullGroupMessages } from '#/api/im/message/group'
|
||||
import { getPrivateMaxReadMessageId as apiGetPrivateMaxReadMessageId, pullPrivateMessages as apiPullPrivateMessages } from '#/api/im/message/private'
|
||||
import { getCurrentUserId } from '#/views/im/utils/auth'
|
||||
|
||||
import { buildChannelConversationStub } from '../../utils/channel'
|
||||
|
|
@ -41,7 +35,7 @@ import { type PulledMessage, useMessageStore } from '../store/messageStore'
|
|||
import { useImWebSocketStore } from '../store/websocketStore'
|
||||
|
||||
/** 三类消息 pull 接口返回的原始 VO 联合类型;runMinIdPull 只需 id 推进游标,具体分发在 applyPage 内按类型 cast */
|
||||
type PulledRawMessage = ImChannelMessageRespVO | ImGroupMessageRespVO | ImPrivateMessageRespVO
|
||||
type PulledRawMessage = ImChannelMessageApi.ChannelMessageRespVO | ImGroupMessageApi.GroupMessageRespVO | ImPrivateMessageApi.PrivateMessageRespVO
|
||||
|
||||
/**
|
||||
* 消息增量拉取:登录后分页拉取离线期间的新消息
|
||||
|
|
@ -74,11 +68,11 @@ export const useMessagePuller = () => {
|
|||
}
|
||||
|
||||
/** 私聊会话归属:自己发的算"发给 receiverId 的会话",否则算"发送方的会话";curry currentUserId 进闭包减少 3 处调用方的样板 */
|
||||
const getPrivatePeerId = (message: ImPrivateMessageRespVO) =>
|
||||
const getPrivatePeerId = (message: ImPrivateMessageApi.PrivateMessageRespVO) =>
|
||||
getPrivateMessagePeerId(message, currentUserId)
|
||||
|
||||
/** 服务端私聊消息 -> 本地 Message:targetId 是会话主键(对端 userId) */
|
||||
const convertPrivateMessage = (message: ImPrivateMessageRespVO): Message => {
|
||||
const convertPrivateMessage = (message: ImPrivateMessageApi.PrivateMessageRespVO): Message => {
|
||||
return {
|
||||
id: message.id,
|
||||
clientMessageId: message.clientMessageId || generateClientMessageId(),
|
||||
|
|
@ -94,7 +88,7 @@ export const useMessagePuller = () => {
|
|||
}
|
||||
|
||||
/** 服务端群聊消息 -> 本地 Message */
|
||||
const convertGroupMessage = (message: ImGroupMessageRespVO): Message => {
|
||||
const convertGroupMessage = (message: ImGroupMessageApi.GroupMessageRespVO): Message => {
|
||||
return {
|
||||
id: message.id,
|
||||
clientMessageId: message.clientMessageId || generateClientMessageId(),
|
||||
|
|
@ -113,7 +107,7 @@ export const useMessagePuller = () => {
|
|||
}
|
||||
|
||||
/** 服务端频道消息 -> 本地 Message */
|
||||
const convertChannelMessage = (message: ImChannelMessageRespVO): Message => {
|
||||
const convertChannelMessage = (message: ImChannelMessageApi.ChannelMessageRespVO): Message => {
|
||||
return {
|
||||
id: message.id,
|
||||
clientMessageId: message.clientMessageId || generateClientMessageId(),
|
||||
|
|
@ -130,11 +124,11 @@ export const useMessagePuller = () => {
|
|||
}
|
||||
|
||||
/** 频道:会话归属到 channelId;name / avatar 暂用占位,将来接入 channelStore 后再填真值 */
|
||||
const convertChannelConversation = (message: ImChannelMessageRespVO) =>
|
||||
const convertChannelConversation = (message: ImChannelMessageApi.ChannelMessageRespVO) =>
|
||||
buildChannelConversationStub(message.channelId)
|
||||
|
||||
/** 私聊:会话归属到对端 userId */
|
||||
const convertPrivateConversation = (message: ImPrivateMessageRespVO) => {
|
||||
const convertPrivateConversation = (message: ImPrivateMessageApi.PrivateMessageRespVO) => {
|
||||
const targetId = getPrivatePeerId(message)
|
||||
const friend = friendStore.getFriend(targetId)
|
||||
return {
|
||||
|
|
@ -147,7 +141,7 @@ export const useMessagePuller = () => {
|
|||
}
|
||||
|
||||
/** 群聊:会话归属到 groupId */
|
||||
const convertGroupConversation = (message: ImGroupMessageRespVO) => {
|
||||
const convertGroupConversation = (message: ImGroupMessageApi.GroupMessageRespVO) => {
|
||||
const group = groupStore.getGroup(message.groupId)
|
||||
return {
|
||||
type: ImConversationType.GROUP,
|
||||
|
|
@ -198,7 +192,7 @@ export const useMessagePuller = () => {
|
|||
// 后端按 id 升序返回,且信号 id 一定 > 原消息 id(先更新 status 再插信号),所以原消息一定先到、recallMessage 找得到
|
||||
for (const raw of list) {
|
||||
if (isChannel) {
|
||||
const message = raw as ImChannelMessageRespVO
|
||||
const message = raw as ImChannelMessageApi.ChannelMessageRespVO
|
||||
pulledMessages.push({
|
||||
kind: 'insert',
|
||||
conversationInfo: convertChannelConversation(message),
|
||||
|
|
@ -207,7 +201,7 @@ export const useMessagePuller = () => {
|
|||
continue
|
||||
}
|
||||
if (isPrivate) {
|
||||
const message = raw as ImPrivateMessageRespVO
|
||||
const message = raw as ImPrivateMessageApi.PrivateMessageRespVO
|
||||
// 特殊:撤回消息的处理
|
||||
if (message.type === ImContentType.RECALL) {
|
||||
pulledMessages.push({
|
||||
|
|
@ -230,7 +224,7 @@ export const useMessagePuller = () => {
|
|||
message: convertPrivateMessage(message)
|
||||
})
|
||||
} else {
|
||||
const message = raw as ImGroupMessageRespVO
|
||||
const message = raw as ImGroupMessageApi.GroupMessageRespVO
|
||||
// 特殊:撤回消息的处理
|
||||
if (message.type === ImContentType.RECALL) {
|
||||
pulledMessages.push({
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ import type { FriendRequest, User } from '../../types'
|
|||
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { prompt } from '@vben/common-ui'
|
||||
import { DICT_TYPE } from '@vben/constants'
|
||||
import { getDictLabel } from '@vben/hooks'
|
||||
|
||||
import { Button } from 'ant-design-vue'
|
||||
import { Button, Input, message } from 'ant-design-vue'
|
||||
|
||||
import { getCurrentUserId } from '#/views/im/utils/auth'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
|
||||
import { ImFriendRequestHandleResult } from '../../../utils/constants'
|
||||
import UserAvatar from '../../components/user/UserAvatar.vue'
|
||||
|
|
@ -27,7 +27,6 @@ const emit = defineEmits<{
|
|||
}>()
|
||||
|
||||
const friendStore = useFriendStore()
|
||||
const message = useMessage()
|
||||
|
||||
/** 当前登录用户编号;用 computed 包一层,切账号后随 wsCache 重取,避免顶层求值在 keep-alive 实例里持有旧 id */
|
||||
const currentUserId = computed(() => getCurrentUserId())
|
||||
|
|
@ -100,15 +99,31 @@ async function handleRefuse() {
|
|||
// 1. 弹 prompt 收集拒绝理由(最多 255 字);用户点「取消」会 reject,中止后续流程
|
||||
let handleContent: string | undefined
|
||||
try {
|
||||
const result = await message.prompt('拒绝好友申请', {
|
||||
const result = await prompt<string>({
|
||||
beforeClose(scope) {
|
||||
if (!scope.isConfirm) {
|
||||
return
|
||||
}
|
||||
if ((scope.value || '').length > 255) {
|
||||
message.error('最多 255 个字符')
|
||||
return false
|
||||
}
|
||||
},
|
||||
cancelText: '取消',
|
||||
component: Input.TextArea,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
maxlength: 255,
|
||||
placeholder: '不填则不告知对方原因',
|
||||
rows: 3
|
||||
},
|
||||
content: '',
|
||||
defaultValue: '',
|
||||
okText: '拒绝',
|
||||
placeholder: '不填则不告知对方原因',
|
||||
textarea: true,
|
||||
validator: (value: string) => (value || '').length <= 255 || '最多 255 个字符'
|
||||
confirmText: '拒绝',
|
||||
modelPropName: 'value',
|
||||
title: '拒绝好友申请'
|
||||
})
|
||||
handleContent = result.value || undefined
|
||||
handleContent = result || undefined
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,8 +47,7 @@ const enrichedRequests = computed(() =>
|
|||
props.requests.map((request) => ({ request, peer: getPeer(request) }))
|
||||
)
|
||||
|
||||
/** 点击「加载更多」拉下一页;store 内部按 maxId 游标分页 + pending 去重 */
|
||||
const loadingMore = ref(false)
|
||||
const loadingMore = ref(false) // 点击「加载更多」拉下一页;store 内部按 maxId 游标分页 + pending 去重
|
||||
async function handleLoadMore() {
|
||||
if (loadingMore.value) {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -4,11 +4,10 @@ import type { FriendLite, FriendRequest, Group, GroupLite, User } from '../../ty
|
|||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { confirm } from '@vben/common-ui'
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
|
||||
import { Input } from 'ant-design-vue'
|
||||
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
import { Input, message } from 'ant-design-vue'
|
||||
|
||||
import { ImConversationType } from '../../../utils/constants'
|
||||
import { StorageKeys } from '../../../utils/db'
|
||||
|
|
@ -30,7 +29,6 @@ const router = useRouter()
|
|||
const conversationStore = useConversationStore()
|
||||
const friendStore = useFriendStore()
|
||||
const groupStore = useGroupStore()
|
||||
const message = useMessage()
|
||||
|
||||
/** 用 type 判别选中是好友 / 群聊 / 好友申请 */
|
||||
type Selection =
|
||||
|
|
@ -204,7 +202,7 @@ function handleChatGroup(group: GroupLite) {
|
|||
/** 删除好友:二次确认 → store 落库 → 清空当前选中 */
|
||||
async function handleDeleteFriend(friend: FriendLite) {
|
||||
try {
|
||||
await message.confirm(`确定删除好友「${friend.nickname}」吗?`, '删除联系人')
|
||||
await confirm(`确定删除好友「${friend.nickname}」吗?`, '删除联系人')
|
||||
// friendStore.deleteFriend 内部已经级联清理对应私聊会话
|
||||
await friendStore.deleteFriend(friend.id)
|
||||
if (selection.value?.type === 'friend' && selection.value.friend.id === friend.id) {
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ import type { Conversation, GroupLite } from '../../../../types'
|
|||
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import { confirm } from '@vben/common-ui'
|
||||
import { CommonStatusEnum } from '@vben/constants'
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
|
||||
import { Button, Drawer, Input, Popover, Switch } from 'ant-design-vue'
|
||||
import { Button, Drawer, Input, message, Popover, Switch } from 'ant-design-vue'
|
||||
|
||||
import {
|
||||
dissolveGroup,
|
||||
|
|
@ -18,7 +19,6 @@ import { quitGroup, updateGroupMember } from '#/api/im/group/member'
|
|||
import { getCurrentUserId } from '#/views/im/utils/auth'
|
||||
import { ImConversationType, ImGroupMemberRole } from '#/views/im/utils/constants'
|
||||
import { toGroupCardTarget } from '#/views/im/utils/message'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
import { isGroupQuit } from '#/views/im/utils/user'
|
||||
|
||||
import GroupAdminSetDialog from '../../../../components/group/GroupAdminSetDialog.vue'
|
||||
|
|
@ -59,7 +59,6 @@ const emit = defineEmits<{
|
|||
const MEMBER_PREVIEW_COUNT = 14
|
||||
const conversationStore = useConversationStore()
|
||||
const groupStore = useGroupStore()
|
||||
const message = useMessage()
|
||||
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
|
|
@ -71,8 +70,7 @@ const visible = computed({
|
|||
const searchText = ref('')
|
||||
const showAllMembers = ref(false)
|
||||
|
||||
/** 邀请好友入群弹窗 ref:handleOpenInvite 调 open({ groupId }) 打开 */
|
||||
const inviteDialogRef = ref<InstanceType<typeof GroupMemberAddDialog>>()
|
||||
const inviteDialogRef = ref<InstanceType<typeof GroupMemberAddDialog>>() // 邀请好友入群弹窗 ref:handleOpenInvite 调 open({ groupId }) 打开
|
||||
/** 打开邀请好友入群弹窗 */
|
||||
function handleOpenInvite() {
|
||||
if (!props.group?.id) {
|
||||
|
|
@ -99,7 +97,7 @@ const isOwnerOrAdmin = computed(
|
|||
(myRole.value === ImGroupMemberRole.OWNER || myRole.value === ImGroupMemberRole.ADMIN)
|
||||
)
|
||||
|
||||
// 排除已退群成员 + 关键字过滤;按角色排序:群主→管理员→普通成员(同角色按 userId 稳定)
|
||||
/** 排除已退群成员 + 关键字过滤;按角色排序:群主→管理员→普通成员(同角色按 userId 稳定) */
|
||||
const visibleMembers = computed(() => {
|
||||
return props.members
|
||||
.filter(
|
||||
|
|
@ -114,7 +112,7 @@ const visibleMembers = computed(() => {
|
|||
})
|
||||
})
|
||||
|
||||
// 折叠规则:搜索 / 已展开 时不折叠,其余只取前 N 个
|
||||
/** 折叠规则:搜索 / 已展开 时不折叠,其余只取前 N 个 */
|
||||
const moreMembersHidden = computed(
|
||||
() =>
|
||||
!searchText.value && !showAllMembers.value && visibleMembers.value.length > MEMBER_PREVIEW_COUNT
|
||||
|
|
@ -284,8 +282,7 @@ async function onMuteAllChange(value: boolean | number | string) {
|
|||
|
||||
// ==================== 进群审批 ====================
|
||||
|
||||
/** 进群申请列表弹窗 ref:handleOpenRequestList 调 open({ groupId }) 触发 */
|
||||
const requestListDialogRef = ref<InstanceType<typeof GroupRequestListDialog>>()
|
||||
const requestListDialogRef = ref<InstanceType<typeof GroupRequestListDialog>>() // 进群申请列表弹窗 ref:handleOpenRequestList 调 open({ groupId }) 触发
|
||||
|
||||
/** 打开当前群的进群申请列表 */
|
||||
function handleOpenRequestList() {
|
||||
|
|
@ -297,8 +294,7 @@ function handleOpenRequestList() {
|
|||
|
||||
// ==================== 分享群名片 ====================
|
||||
|
||||
/** 分享群名片弹窗 ref:handleShareGroupCard 调用 open({ target }) 打开 */
|
||||
const recommendCardDialogRef = ref<InstanceType<typeof RecommendCardDialog>>()
|
||||
const recommendCardDialogRef = ref<InstanceType<typeof RecommendCardDialog>>() // 分享群名片弹窗 ref:handleShareGroupCard 调用 open({ target }) 打开
|
||||
|
||||
/** 分享群名片:把当前群作为名片消息推荐给其他会话 */
|
||||
function handleShareGroupCard() {
|
||||
|
|
@ -316,9 +312,9 @@ async function handleQuit() {
|
|||
if (!props.group) {
|
||||
return
|
||||
}
|
||||
// 二次确认(用户点取消时 message.confirm 抛 reject,吃掉直接 return)
|
||||
// 二次确认
|
||||
try {
|
||||
await message.confirm('退出群聊后将不再接收群里的消息,确认退出吗?', '确认退出')
|
||||
await confirm('退出群聊后将不再接收群里的消息,确认退出吗?', '确认退出')
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
|
@ -340,7 +336,7 @@ async function handleDissolve() {
|
|||
return
|
||||
}
|
||||
try {
|
||||
await message.confirm('解散后所有成员将被移出,且无法恢复,确认解散吗?', '确认解散')
|
||||
await confirm('解散后所有成员将被移出,且无法恢复,确认解散吗?', '确认解散')
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
|
@ -355,12 +351,9 @@ async function handleDissolve() {
|
|||
// ==================== 群主操作 ====================
|
||||
// 移除群成员(群主 / 管理员可见)+ 设置群管理员(仅群主)+ 群主管理权转让(仅群主)
|
||||
|
||||
/** 移除群成员弹窗 ref */
|
||||
const removeDialogRef = ref<InstanceType<typeof GroupMemberRemoveDialog>>()
|
||||
/** 设置群管理员弹窗 ref */
|
||||
const adminSetDialogRef = ref<InstanceType<typeof GroupAdminSetDialog>>()
|
||||
/** 转让群主弹窗 ref */
|
||||
const ownerTransferDialogRef = ref<InstanceType<typeof GroupOwnerTransferDialog>>()
|
||||
const removeDialogRef = ref<InstanceType<typeof GroupMemberRemoveDialog>>() // 移除群成员弹窗 ref
|
||||
const adminSetDialogRef = ref<InstanceType<typeof GroupAdminSetDialog>>() // 设置群管理员弹窗 ref
|
||||
const ownerTransferDialogRef = ref<InstanceType<typeof GroupOwnerTransferDialog>>() // 转让群主弹窗 ref
|
||||
|
||||
// ---------- 移除群成员 ----------
|
||||
|
||||
|
|
@ -434,7 +427,7 @@ function handleOpenTransferOwner() {
|
|||
>
|
||||
<div v-if="group" class="flex flex-col h-full bg-[var(--ant-color-bg-container)]">
|
||||
<!-- 上部:可滚动内容区 -->
|
||||
<div class="flex-1 overflow-y-auto bg-[var(--ant-color-fill-secondary)]">
|
||||
<div class="flex-1 overflow-y-auto bg-[var(--im-conversation-side-bg)]">
|
||||
<!-- ==================== 群成员区 ==================== -->
|
||||
<div class="px-4 pt-4 pb-[10px] bg-[var(--ant-color-bg-container)]">
|
||||
<Input v-model:value="searchText" placeholder="搜索群成员" allow-clear>
|
||||
|
|
@ -772,7 +765,7 @@ function handleOpenTransferOwner() {
|
|||
<!-- ==================== 底部:退出 / 解散群聊(历史退群群隐藏,已退群无需再退) ==================== -->
|
||||
<div
|
||||
v-if="!isQuitGroup"
|
||||
class="flex-shrink-0 px-4 pt-[14px] pb-[18px] bg-[var(--ant-color-bg-container)] border-t border-t-solid border-[var(--ant-color-border-secondary)]"
|
||||
class="flex-shrink-0 px-4 pt-[14px] pb-[18px] bg-[var(--ant-color-bg-container)] border-t border-t-solid border-[var(--im-border-color-lighter)]"
|
||||
>
|
||||
<!-- 群主:解散群聊 -->
|
||||
<Button
|
||||
|
|
@ -820,6 +813,10 @@ function handleOpenTransferOwner() {
|
|||
background-color: var(--ant-color-primary-bg);
|
||||
}
|
||||
|
||||
.im-conversation-group-side__modal {
|
||||
--im-conversation-side-bg: #f5f7fa;
|
||||
}
|
||||
|
||||
/* :deep 穿透 Icon 内部 svg; el-icon 全局 color 在暗色模式下被主题盖过,锁 fill 到当前色 */
|
||||
.im-conversation-group-side__icon-tile :deep(svg) {
|
||||
fill: currentColor !important;
|
||||
|
|
@ -827,14 +824,17 @@ function handleOpenTransferOwner() {
|
|||
|
||||
/* 相邻信息行加分隔线; 相邻兄弟选择器无法用工具类表达 */
|
||||
.im-conversation-group-side__row + .im-conversation-group-side__row {
|
||||
border-top: 1px solid var(--ant-color-border-secondary);
|
||||
border-top: 1px solid var(--im-border-color-lighter);
|
||||
}
|
||||
|
||||
:global(.dark) .im-conversation-group-side__modal {
|
||||
--im-conversation-side-bg: rgb(255 255 255 / 5%);
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- el-drawer 用 append-to-body 后被传送出当前 scoped 边界,scoped CSS 的 data-v 不会落到 body 上;
|
||||
这里靠 modal-class(在 .el-overlay 上,是 .el-drawer__body 的祖先)写一段全局规则压掉默认 padding -->
|
||||
<!-- AntD Drawer 被传送出当前 scoped 边界,这里靠 root-class-name 压掉默认 padding -->
|
||||
<style>
|
||||
.im-conversation-group-side__modal .el-drawer__body {
|
||||
.im-conversation-group-side__modal .ant-drawer-body {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ import type { Conversation } from '../../../../types'
|
|||
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { confirm } from '@vben/common-ui'
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
|
||||
import { Tag } from 'ant-design-vue'
|
||||
|
||||
import { buildRecallTip } from '#/views/im/utils/conversation'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
import { formatConversationTime } from '#/views/im/utils/time'
|
||||
import { getSenderDisplayName } from '#/views/im/utils/user'
|
||||
|
||||
|
|
@ -23,8 +23,7 @@ import { useImUiStore } from '../../../../store/uiStore'
|
|||
|
||||
defineOptions({ name: 'ImConversationItem' })
|
||||
|
||||
/** 周中文名(dayjs 的 day() 返回 0-6,0=周日);项目没全局装 dayjs/locale/zh-cn,本地映射避免引副作用 */
|
||||
|
||||
// 周中文名(dayjs 的 day() 返回 0-6,0=周日);项目没全局装 dayjs/locale/zh-cn,本地映射避免引副作用
|
||||
const props = defineProps<{
|
||||
conversation: Conversation
|
||||
}>()
|
||||
|
|
@ -34,7 +33,6 @@ const friendStore = useFriendStore()
|
|||
const groupStore = useGroupStore()
|
||||
const groupRequestStore = useGroupRequestStore()
|
||||
const uiStore = useImUiStore()
|
||||
const message = useMessage()
|
||||
|
||||
const isActive = computed(
|
||||
() =>
|
||||
|
|
@ -159,7 +157,7 @@ function handleMuted() {
|
|||
/** 删除会话:二次确认后软删 */
|
||||
async function handleDelete() {
|
||||
try {
|
||||
await message.confirm(`确定删除与「${props.conversation.name}」的会话吗?`, '删除会话')
|
||||
await confirm(`确定删除与「${props.conversation.name}」的会话吗?`, '删除会话')
|
||||
conversationStore.removeConversation(props.conversation.type, props.conversation.targetId)
|
||||
} catch {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,12 @@ import { computed, ref, watch } from 'vue'
|
|||
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
|
||||
import { Button, Drawer, Input, Popover, Spin, Switch } from 'ant-design-vue'
|
||||
import { Button, Drawer, Input, message, Popover, Spin, Switch } from 'ant-design-vue'
|
||||
|
||||
import { useConversationStore } from '#/views/im/home/store/conversationStore'
|
||||
import { useFriendStore } from '#/views/im/home/store/friendStore'
|
||||
import { useGroupStore } from '#/views/im/home/store/groupStore'
|
||||
import { ImConversationType } from '#/views/im/utils/constants'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
import { getFriendDisplayName } from '#/views/im/utils/user'
|
||||
|
||||
import GroupCreateDialog from '../../../../components/group/GroupCreateDialog.vue'
|
||||
|
|
@ -26,12 +25,14 @@ const props = withDefaults(
|
|||
modelValue?: boolean // 抽屉开关(v-model)
|
||||
}>(),
|
||||
{
|
||||
conversation: null,
|
||||
friend: undefined,
|
||||
modelValue: false
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
'open-history': [] // 点击 "查找聊天内容" 行 → 父组件打开 MessageHistory 弹窗
|
||||
openHistory: [] // 点击 "查找聊天内容" 行 → 父组件打开 MessageHistory 弹窗
|
||||
'update:modelValue': [value: boolean]
|
||||
}>()
|
||||
|
||||
|
|
@ -43,13 +44,11 @@ const visible = computed({
|
|||
const conversationStore = useConversationStore()
|
||||
const friendStore = useFriendStore()
|
||||
const groupStore = useGroupStore()
|
||||
const message = useMessage()
|
||||
|
||||
/** tile 标签 / 后续聊天界面用的展示名:备注优先 */
|
||||
const displayName = computed(() => (props.friend ? getFriendDisplayName(props.friend) : ''))
|
||||
|
||||
/** 发起群聊弹窗 ref:handleOpenCreateGroup 调 open({ lockedIds }) 锁定对方 */
|
||||
const createGroupDialogRef = ref<InstanceType<typeof GroupCreateDialog>>()
|
||||
const createGroupDialogRef = ref<InstanceType<typeof GroupCreateDialog>>() // 发起群聊弹窗 ref:handleOpenCreateGroup 调 open({ lockedIds }) 锁定对方
|
||||
|
||||
/** 打开发起群聊弹窗:把对方默认勾上且不可取消,对应微信"基于私聊发起群聊" */
|
||||
function handleOpenCreateGroup() {
|
||||
|
|
@ -227,7 +226,7 @@ function handleGroupCreated(groupId: number) {
|
|||
<div class="bg-[var(--ant-color-bg-container)]">
|
||||
<div
|
||||
class="im-conversation-private-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]"
|
||||
@click="emit('open-history')"
|
||||
@click="emit('openHistory')"
|
||||
>
|
||||
<span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">查找聊天内容</span>
|
||||
<Icon
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import type { ImFacePackUserItemVO } from '#/api/im/face/pack'
|
||||
import type { ImFaceUserItemVO } from '#/api/im/face/useritem'
|
||||
import type { ImFacePackApi } from '#/api/im/face/pack'
|
||||
import type { ImFaceUserItemApi } from '#/api/im/face/useritem'
|
||||
|
||||
import { computed, onUnmounted, ref, useTemplateRef, watch } from 'vue'
|
||||
|
||||
|
|
@ -8,10 +8,10 @@ import { IconifyIcon as Icon } from '@vben/icons'
|
|||
|
||||
import { message, Tooltip } from 'ant-design-vue'
|
||||
|
||||
import { uploadFile } from '#/api/infra/file'
|
||||
import { useFaceStore } from '#/views/im/home/store/faceStore'
|
||||
import { IM_EMOJI_LIST } from '#/views/im/utils/emoji'
|
||||
import { probeImageSize } from '#/views/im/utils/image'
|
||||
import { updateFile } from '#/views/im/utils/upload'
|
||||
|
||||
defineOptions({ name: 'ImFacePicker' })
|
||||
|
||||
|
|
@ -26,9 +26,9 @@ const props = withDefaults(
|
|||
|
||||
const emit = defineEmits<{
|
||||
/** 选中 Unicode emoji(如 😀),调用方应插入到输入框走 TEXT 通道 */
|
||||
'select-emoji': [emoji: string]
|
||||
selectEmoji: [emoji: string]
|
||||
/** 选中表情贴图,调用方应走 FACE 消息发送 */
|
||||
'select-face': [face: { height: number; name?: string; url: string; width: number; }]
|
||||
selectFace: [face: { height: number; name?: string; url: string; width: number; }]
|
||||
'update:visible': [value: boolean]
|
||||
}>()
|
||||
|
||||
|
|
@ -40,41 +40,39 @@ const uploadInputRef = useTemplateRef<HTMLInputElement>('uploadInputRef')
|
|||
|
||||
const faceStore = useFaceStore()
|
||||
|
||||
/** tab 标识常量;pack:N 类用 packTabKey() 拼出,避免散落字符串字面量 */
|
||||
// tab 标识常量;pack:N 类用 packTabKey() 拼出,避免散落字符串字面量
|
||||
const FACE_TAB = {
|
||||
EMOJI: 'emoji',
|
||||
MINE: 'mine'
|
||||
} as const
|
||||
const packTabKey = (packId: number) => `pack:${packId}`
|
||||
|
||||
/** 当前激活的 tab */
|
||||
const activeTab = ref<string>(FACE_TAB.EMOJI)
|
||||
const activeTab = ref<string>(FACE_TAB.EMOJI) // 当前激活的 tab
|
||||
|
||||
/** 是否完整模式(含个人 / 系统包 tab) */
|
||||
const isFullMode = computed(() => props.mode === 'full')
|
||||
|
||||
/** 上传中标记,避免连续点击触发并发上传 */
|
||||
const uploading = ref(false)
|
||||
const uploading = ref(false) // 上传中标记,避免连续点击触发并发上传
|
||||
|
||||
/** 选 emoji 字符:插到输入框;选完不关面板,方便用户连发多个 */
|
||||
function handleSelectEmoji(emoji: string) {
|
||||
emit('select-emoji', emoji)
|
||||
emit('selectEmoji', emoji)
|
||||
}
|
||||
|
||||
/** 选个人表情:直接发;点完关面板,对齐微信 */
|
||||
function handleSelectFaceUserItem(item: ImFaceUserItemVO) {
|
||||
emit('select-face', { url: item.url, width: item.width, height: item.height, name: item.name })
|
||||
function handleSelectFaceUserItem(item: ImFaceUserItemApi.FaceUserItem) {
|
||||
emit('selectFace', { url: item.url, width: item.width, height: item.height, name: item.name })
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
/** 选系统表情包内表情:直接发;点完关面板 */
|
||||
function handleSelectPackItem(item: ImFacePackUserItemVO) {
|
||||
emit('select-face', { url: item.url, width: item.width, height: item.height, name: item.name })
|
||||
function handleSelectPackItem(item: ImFacePackApi.FacePackUserItem) {
|
||||
emit('selectFace', { url: item.url, width: item.width, height: item.height, name: item.name })
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
/** 长按 / 右键删除个人表情 */
|
||||
async function handleDeleteUserItem(item: ImFaceUserItemVO) {
|
||||
async function handleDeleteUserItem(item: ImFaceUserItemApi.FaceUserItem) {
|
||||
if (!confirm('确认删除该表情?')) {
|
||||
return
|
||||
}
|
||||
|
|
@ -106,10 +104,7 @@ async function onUploadPicked(e: Event) {
|
|||
}
|
||||
|
||||
try {
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
const uploadRes = (await updateFile(form)) as { data?: string }
|
||||
const url = uploadRes?.data
|
||||
const url = await uploadFile({ file })
|
||||
if (!url) {
|
||||
message.error('上传失败')
|
||||
return
|
||||
|
|
@ -158,7 +153,7 @@ onUnmounted(() => {
|
|||
<!--
|
||||
表情面板(多 tab):emoji / 个人表情 / N 个系统表情包
|
||||
- 对齐微信 PC:底部 tab 栏切换面板内容;emoji 保持 Unicode(仍由 TEXT 通道发送)
|
||||
- 个人表情 / 系统表情走 FACE 内容类型,通过 select-face 事件由调用方走 sendRaw 发送
|
||||
- 个人表情 / 系统表情走 FACE 内容类型,通过 selectFace 事件由调用方走 sendRaw 发送
|
||||
- mode='emoji' 时只显示 emoji tab + 隐藏底部 tab 栏,给留言 / 评论这类只发文本的场景用
|
||||
- 定位由调用方决定(通常浮在表情按钮上方)
|
||||
-->
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const props = withDefaults(
|
|||
canAtAll?: boolean // 当前用户是否能 @ 全员(群主 / 管理员),父组件按角色算好传入
|
||||
members: GroupMemberLite[] // 当前群的成员列表
|
||||
// 浮层位置:x 横坐标 + top / bottom 二选一(bottom 锚定时 picker 下沿贴 @ 上方)
|
||||
position: { bottom?: number; top?: number; x: number; }
|
||||
position?: { bottom?: number; top?: number; x: number; }
|
||||
searchText?: string // @ 后输入的过滤文本
|
||||
visible: boolean // 是否显示
|
||||
}>(),
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ import type { Conversation } from '#/views/im/home/types'
|
|||
import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue'
|
||||
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
import { isOpenableUrl } from '@vben/utils'
|
||||
|
||||
import { Button, Dropdown, Menu, Tooltip } from 'ant-design-vue'
|
||||
import { Button, Dropdown, Menu, message, Tooltip } from 'ant-design-vue'
|
||||
|
||||
import { uploadFile } from '#/api/infra/file'
|
||||
import {
|
||||
ensureMediaSizeWithinLimit,
|
||||
useMediaUploader
|
||||
|
|
@ -28,9 +30,6 @@ import {
|
|||
serializeMessage,
|
||||
withQuotePayload
|
||||
} from '#/views/im/utils/message'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
import { updateFile } from '#/views/im/utils/upload'
|
||||
import { isOpenableUrl } from '#/views/im/utils/url'
|
||||
import { getMemberDisplayName } from '#/views/im/utils/user'
|
||||
|
||||
import ReplyPreview from '../message/ReplyPreview.vue'
|
||||
|
|
@ -43,7 +42,6 @@ defineOptions({ name: 'ImMessageInput' })
|
|||
const conversationStore = useConversationStore()
|
||||
const groupStore = useGroupStore()
|
||||
const friendStore = useFriendStore()
|
||||
const message = useMessage()
|
||||
const { send, sendRaw } = useMessageSender()
|
||||
const {
|
||||
uploadAndSendMedia,
|
||||
|
|
@ -518,8 +516,7 @@ const mentionPosition = ref<{ bottom?: number; top?: number; x: number; }>({ x:
|
|||
/** MentionPicker 的容器宽度(与组件里的 w-50 对齐),用于视口右沿回弹;
|
||||
* 高度不再用常量算位置——bottom 锚定后 picker 内容多寡都不影响下沿位置,自然贴 @ */
|
||||
const MENTION_WIDTH = 200
|
||||
/** 上方剩余空间至少这么多才放上方,否则翻到下方(避免 picker 被视口顶 / 顶部 chat header 切掉) */
|
||||
const MENTION_MIN_FIT_ABOVE = 120
|
||||
const MENTION_MIN_FIT_ABOVE = 120 // 上方剩余空间至少这么多才放上方,否则翻到下方(避免 picker 被视口顶 / 顶部 chat header 切掉)
|
||||
|
||||
/** 当前 @ 关键词在 editor 里的范围;onMentionSelect 用它定位删除 + 插入 token */
|
||||
let mentionRange: null | Range = null
|
||||
|
|
@ -913,17 +910,15 @@ async function uploadAndSendVideo(file: File) {
|
|||
// 2. 三路并行起跑(probe 与两条上传无依赖,封面上传等 probe 出 cover 后立即接力)
|
||||
// 2.1 视频本体上传:async IIFE 包一层让 await 显式可见(lint 不再误判 floating promise),
|
||||
// 失败兜底为 url=undefined,由 step 3 拿不到 url 时收尾
|
||||
const videoForm = new FormData()
|
||||
videoForm.append('file', file)
|
||||
const videoUploadPromise: Promise<{ data?: string }> = (async () => {
|
||||
const videoUploadPromise: Promise<string | undefined> = (async () => {
|
||||
try {
|
||||
return (await updateFile(
|
||||
videoForm,
|
||||
return await uploadFile(
|
||||
{ file },
|
||||
createUploadProgressHandler(conversation, clientMessageId)
|
||||
)) as { data?: string }
|
||||
)
|
||||
} catch (error) {
|
||||
console.warn('[IM] 视频本体上传失败', error)
|
||||
return { data: undefined }
|
||||
return undefined
|
||||
}
|
||||
})()
|
||||
// 2.2 probe 拿元信息 + 封面 blob:解码失败降级为空 probe,不阻断视频上传
|
||||
|
|
@ -937,12 +932,9 @@ async function uploadAndSendVideo(file: File) {
|
|||
return { probe, coverUrl: undefined as string | undefined }
|
||||
}
|
||||
try {
|
||||
const coverForm = new FormData()
|
||||
coverForm.append(
|
||||
'file',
|
||||
new File([probe.cover], `cover-${Date.now()}.jpg`, { type: 'image/jpeg' })
|
||||
)
|
||||
const coverUrl = ((await updateFile(coverForm)) as { data?: string })?.data || undefined
|
||||
const coverUrl = await uploadFile({
|
||||
file: new File([probe.cover], `cover-${Date.now()}.jpg`, { type: 'image/jpeg' })
|
||||
})
|
||||
return { probe, coverUrl }
|
||||
} catch (error) {
|
||||
console.warn('[IM] 视频封面上传失败', error)
|
||||
|
|
@ -957,7 +949,7 @@ async function uploadAndSendVideo(file: File) {
|
|||
coverUploadPromise
|
||||
])
|
||||
// 3.2 视频本体没 url:占位置 FAILED,让用户决定重试 / 删除(_localFile 在内存里)
|
||||
const url = videoRes?.data
|
||||
const url = videoRes
|
||||
if (!url) {
|
||||
markMediaFailed(conversation.type, conversation.targetId, clientMessageId)
|
||||
return
|
||||
|
|
@ -1017,11 +1009,11 @@ async function onVideoPicked(e: Event) {
|
|||
<!-- 禁言 / 封禁覆盖层:优先级 封禁 > 全群禁言 > 成员禁言 -->
|
||||
<div
|
||||
v-if="muteOverlay"
|
||||
class="absolute top-2 right-3 bottom-3 left-3 z-10 flex items-center justify-center gap-2 rounded-lg border border-solid text-sm"
|
||||
class="message-input__mute-overlay absolute top-2 right-3 bottom-3 left-3 z-20 flex items-center justify-center gap-2 rounded-lg border border-solid text-sm"
|
||||
:class="
|
||||
muteOverlay.icon === 'ant-design:stop-outlined'
|
||||
? 'text-[var(--ant-color-error-active)] bg-[var(--ant-color-error-bg)] border-[var(--ant-color-error-hover)]'
|
||||
: 'text-[var(--ant-color-warning-active)] bg-[var(--ant-color-warning-bg)] border-[var(--ant-color-warning-hover)]'
|
||||
? 'message-input__mute-overlay--error'
|
||||
: 'message-input__mute-overlay--warning'
|
||||
"
|
||||
>
|
||||
<Icon :icon="muteOverlay.icon" :size="18" />
|
||||
|
|
@ -1182,6 +1174,30 @@ async function onVideoPicked(e: Event) {
|
|||
color: var(--ant-color-primary) !important;
|
||||
}
|
||||
|
||||
.message-input__mute-overlay--warning {
|
||||
color: #d48806;
|
||||
background: #fff7e6;
|
||||
border-color: #ffd591;
|
||||
}
|
||||
|
||||
.message-input__mute-overlay--error {
|
||||
color: #cf1322;
|
||||
background: #fff1f0;
|
||||
border-color: #ffa39e;
|
||||
}
|
||||
|
||||
:global(.dark) .message-input__mute-overlay--warning {
|
||||
color: #ffd666;
|
||||
background: rgb(77 56 21 / 88%);
|
||||
border-color: #ad6800;
|
||||
}
|
||||
|
||||
:global(.dark) .message-input__mute-overlay--error {
|
||||
color: #ff7875;
|
||||
background: rgb(91 33 33 / 88%);
|
||||
border-color: #a8071a;
|
||||
}
|
||||
|
||||
/* 用 data-empty 而非 :empty:浏览器在删空后会留下 <br>,:empty 不命中;data-empty 由 syncEditorState 维护 */
|
||||
.message-input__editor[data-empty]::before {
|
||||
content: attr(data-placeholder);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import type { Message } from '#/views/im/home/types'
|
|||
|
||||
import { computed, inject } from 'vue'
|
||||
|
||||
import { confirm } from '@vben/common-ui'
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
|
||||
import { useMessageMultiSelect } from '#/views/im/home/composables/useMessageMultiSelect'
|
||||
|
|
@ -10,7 +11,6 @@ import { useConversationStore } from '#/views/im/home/store/conversationStore'
|
|||
import { useMessageStore } from '#/views/im/home/store/messageStore'
|
||||
import { ImForwardMode, isNormalMessage } from '#/views/im/utils/constants'
|
||||
import { getClientConversationId } from '#/views/im/utils/db'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
|
||||
import { IM_FORWARD_DIALOG_KEY } from '../message/forward/keys'
|
||||
|
||||
|
|
@ -18,7 +18,6 @@ defineOptions({ name: 'ImMessageMultiSelectBar' })
|
|||
|
||||
const conversationStore = useConversationStore()
|
||||
const messageStore = useMessageStore()
|
||||
const message = useMessage()
|
||||
const openForwardDialog = inject(IM_FORWARD_DIALOG_KEY)
|
||||
const multiSelect = useMessageMultiSelect()
|
||||
|
||||
|
|
@ -82,7 +81,12 @@ async function handleDelete() {
|
|||
return
|
||||
}
|
||||
try {
|
||||
await message.delConfirm(`确认删除选中的 ${messages.length} 条消息?`)
|
||||
await confirm(`确认删除选中的 ${messages.length} 条消息?`, {
|
||||
cancelText: '取消',
|
||||
confirmText: '确定',
|
||||
icon: 'warning',
|
||||
title: '删除确认'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,8 @@ import {
|
|||
watch
|
||||
} from 'vue'
|
||||
|
||||
import { Button } from 'ant-design-vue'
|
||||
import { Button, message } from 'ant-design-vue'
|
||||
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
import { formatSeconds } from '#/views/im/utils/time'
|
||||
|
||||
defineOptions({ name: 'ImVoiceRecorder' })
|
||||
|
|
@ -31,8 +30,6 @@ const emit = defineEmits<{
|
|||
'update:modelValue': [value: boolean]
|
||||
}>()
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const VOICE_MIME_TYPE_OPTIONS = [
|
||||
{ extension: 'webm', mimeType: 'audio/webm;codecs=opus' },
|
||||
{ extension: 'webm', mimeType: 'audio/webm' },
|
||||
|
|
|
|||
|
|
@ -5,13 +5,12 @@ import { computed, ref, watch } from 'vue'
|
|||
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
|
||||
import { Button } from 'ant-design-vue'
|
||||
import { Button, message } from 'ant-design-vue'
|
||||
|
||||
import { unpinGroupMessage as apiUnpinGroupMessage } from '#/api/im/group'
|
||||
import { getCurrentUserId } from '#/views/im/utils/auth'
|
||||
import { ImConversationType, ImGroupMemberRole } from '#/views/im/utils/constants'
|
||||
import { resolveConversationLastContent } from '#/views/im/utils/conversation'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
import { getSenderDisplayName, isGroupQuit } from '#/views/im/utils/user'
|
||||
|
||||
import { useGroupStore } from '../../../../store/groupStore'
|
||||
|
|
@ -29,7 +28,6 @@ const emit = defineEmits<{
|
|||
}>()
|
||||
|
||||
const groupStore = useGroupStore()
|
||||
const message = useMessage()
|
||||
|
||||
/** 当前群(含 pinnedMessages) */
|
||||
const group = computed(() => groupStore.getGroup(props.groupId))
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@ const props = defineProps<{
|
|||
const groupStore = useGroupStore()
|
||||
const groupRequestStore = useGroupRequestStore()
|
||||
|
||||
/** 申请列表弹窗 ref:handleOpen 调 open({ groupId }) 触发 */
|
||||
const requestListDialogRef = ref<InstanceType<typeof GroupRequestListDialog>>()
|
||||
const requestListDialogRef = ref<InstanceType<typeof GroupRequestListDialog>>() // 申请列表弹窗 ref:handleOpen 调 open({ groupId }) 触发
|
||||
|
||||
/** 打开当前群的进群申请列表 */
|
||||
function handleOpen() {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { computed, ref } from 'vue'
|
||||
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
import { openSafeUrl } from '@vben/utils'
|
||||
|
||||
import { Modal, Spin } from 'ant-design-vue'
|
||||
|
||||
|
|
@ -10,7 +11,6 @@ import { useChannelStore } from '#/views/im/home/store/channelStore'
|
|||
import { useConversationStore } from '#/views/im/home/store/conversationStore'
|
||||
import { ImConversationType } from '#/views/im/utils/constants'
|
||||
import { type MaterialMessage, parseMessage } from '#/views/im/utils/message'
|
||||
import { openSafeUrl } from '#/views/im/utils/url'
|
||||
|
||||
const props = defineProps<{
|
||||
content: string
|
||||
|
|
@ -38,7 +38,7 @@ const detailVisible = ref(false)
|
|||
const detailLoading = ref(false)
|
||||
const detailHtml = ref('')
|
||||
|
||||
/** 点击行为:url 非空跳外链;为空则按 payload.materialId 拉富文本正文,全屏 dialog 渲染 */
|
||||
// 点击行为:url 非空跳外链;为空则按 payload.materialId 拉富文本正文,全屏 dialog 渲染
|
||||
const onClick = async () => {
|
||||
if (payload.value.url) {
|
||||
openSafeUrl(payload.value.url)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { computed, onBeforeUnmount } from 'vue'
|
||||
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
import { formatFileSize } from '@vben/utils'
|
||||
import { formatFileSize, openSafeUrl } from '@vben/utils'
|
||||
|
||||
import { Image } from 'ant-design-vue'
|
||||
|
||||
|
|
@ -26,7 +26,6 @@ import {
|
|||
type VideoMessage
|
||||
} from '#/views/im/utils/message'
|
||||
import { formatSeconds } from '#/views/im/utils/time'
|
||||
import { openSafeUrl } from '#/views/im/utils/url'
|
||||
|
||||
import MaterialBubble from './MaterialBubble.vue'
|
||||
import TipSegments from './TipSegments.vue'
|
||||
|
|
@ -48,9 +47,9 @@ const props = defineProps<{
|
|||
|
||||
const emit = defineEmits<{
|
||||
/** 名片点击:调用方决定弹卡片 / 跳群等行为 */
|
||||
'click-card': [card: CardMessage, e: MouseEvent]
|
||||
clickCard: [card: CardMessage, e: MouseEvent]
|
||||
/** 合并消息气泡点击:调用方决定开 dialog 或栈内 push */
|
||||
'open-merge': [content: string]
|
||||
openMerge: [content: string]
|
||||
}>()
|
||||
|
||||
/** 各 type 判定 */
|
||||
|
|
@ -117,8 +116,7 @@ const mergePreviewLines = computed(() => {
|
|||
.map((item) => `${item.senderNickname}:${summarizeMessageContent(item)}`)
|
||||
})
|
||||
|
||||
/** 表情 payload:非法宽高派生成 undefined,让 <img> 走 CSS max-w / max-h 兜底 */
|
||||
const FACE_DIMENSION_MAX = 2048
|
||||
const FACE_DIMENSION_MAX = 2048 // 表情 payload:非法宽高派生成 undefined,让 <img> 走 CSS max-w / max-h 兜底
|
||||
const facePayload = computed(() => {
|
||||
if (!isFace.value) {
|
||||
return null
|
||||
|
|
@ -143,6 +141,7 @@ function bubbleClass(variant: 'file' | 'text' | 'voice'): string[] {
|
|||
case 'file': {
|
||||
return [
|
||||
side,
|
||||
'message-bubble--file',
|
||||
isSelf
|
||||
? 'bg-[#95ec69] border-[var(--ant-color-border-secondary)]'
|
||||
: 'bg-[var(--ant-color-bg-container)] border-[var(--ant-color-border-secondary)] hover:border-[#409eff]'
|
||||
|
|
@ -151,13 +150,14 @@ function bubbleClass(variant: 'file' | 'text' | 'voice'): string[] {
|
|||
case 'text': {
|
||||
return [
|
||||
side,
|
||||
'message-bubble--text',
|
||||
isSelf
|
||||
? 'text-black bg-[#95ec69]'
|
||||
: 'text-[var(--ant-color-text)] bg-[var(--ant-color-fill-secondary)]'
|
||||
: 'text-[var(--ant-color-text)]'
|
||||
]
|
||||
}
|
||||
case 'voice': {
|
||||
return [side, isSelf ? 'bg-[#95ec69]' : 'bg-[var(--ant-color-fill-secondary)]']
|
||||
return [side, 'message-bubble--voice', isSelf ? 'bg-[#95ec69]' : '']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -170,8 +170,7 @@ function handleFileClick() {
|
|||
openSafeUrl(filePayload.value.url)
|
||||
}
|
||||
|
||||
/** 语音点击:托管给 useVoicePlayer 全局互斥播放,新点的语音会停掉旧的 */
|
||||
const voicePlayer = useVoicePlayer()
|
||||
const voicePlayer = useVoicePlayer() // 语音点击:托管给 useVoicePlayer 全局互斥播放,新点的语音会停掉旧的
|
||||
/**
|
||||
* 实例级唯一播放 key:每个 MessageBubble 实例独立一份
|
||||
*
|
||||
|
|
@ -308,14 +307,14 @@ onBeforeUnmount(() => {
|
|||
v-else-if="isCard && cardPayload"
|
||||
:card="cardPayload"
|
||||
clickable
|
||||
@click="(e: MouseEvent) => emit('click-card', cardPayload!, e)"
|
||||
@click="(e: MouseEvent) => emit('clickCard', cardPayload!, e)"
|
||||
/>
|
||||
|
||||
<!-- 合并转发气泡:title + 摘要预览 + 底部「聊天记录」标签 -->
|
||||
<div
|
||||
v-else-if="isMerge && mergePayload"
|
||||
class="flex flex-col w-[260px] rounded-md overflow-hidden cursor-pointer bg-[var(--ant-color-bg-container)] border border-solid border-[var(--ant-color-border)] hover:border-[#409eff]"
|
||||
@click="emit('open-merge', content)"
|
||||
@click="emit('openMerge', content)"
|
||||
>
|
||||
<div class="px-3 py-2 text-sm font-medium text-[var(--ant-color-text)] truncate">
|
||||
{{ mergePayload.title }}
|
||||
|
|
@ -367,10 +366,17 @@ onBeforeUnmount(() => {
|
|||
height: 0;
|
||||
border-style: solid;
|
||||
}
|
||||
.message-bubble--other {
|
||||
--im-message-bubble-other-bg: #f5f5f5;
|
||||
}
|
||||
.message-bubble--other.message-bubble--text,
|
||||
.message-bubble--other.message-bubble--voice {
|
||||
background-color: var(--im-message-bubble-other-bg);
|
||||
}
|
||||
.message-bubble--other::before {
|
||||
left: -5px;
|
||||
border-width: 5px 6px 5px 0;
|
||||
border-color: transparent var(--ant-color-fill-secondary) transparent transparent;
|
||||
border-color: transparent var(--im-message-bubble-other-bg) transparent transparent;
|
||||
}
|
||||
.message-bubble--self::before {
|
||||
right: -5px;
|
||||
|
|
@ -378,6 +384,10 @@ onBeforeUnmount(() => {
|
|||
border-color: transparent transparent transparent #95ec69;
|
||||
}
|
||||
|
||||
:global(.dark) .message-bubble--other {
|
||||
--im-message-bubble-other-bg: rgb(255 255 255 / 12%);
|
||||
}
|
||||
|
||||
/* :deep 穿透 scoped 子组件 DOM;el-icon 在暗色模式下全局 color 被 .el-icon{color:var(--color)} 干扰,把 voice 图标 fill 锁死 */
|
||||
.message-bubble__voice-icon :deep(svg) {
|
||||
fill: #606266 !important;
|
||||
|
|
|
|||
|
|
@ -302,11 +302,11 @@ function matchesActiveFilter(message: Message): boolean {
|
|||
*/
|
||||
const currentList = computed<Message[]>(() => {
|
||||
const trimmedKeyword = keyword.value.trim()
|
||||
let list = allMessages.value.filter(matchesActiveFilter)
|
||||
let list = allMessages.value.filter((message) => matchesActiveFilter(message))
|
||||
if (trimmedKeyword) {
|
||||
list = list.filter((message) => textSnippetOf(message).includes(trimmedKeyword))
|
||||
}
|
||||
return [...list].reverse()
|
||||
return list.toReversed()
|
||||
})
|
||||
|
||||
// ==================== 加载更早消息 ====================
|
||||
|
|
@ -343,9 +343,12 @@ async function loadEarlier() {
|
|||
// 算 maxId(不含,作为后端游标):取当前会话本地缓存里最早一条服务端 id;
|
||||
// 本地乐观占位消息没有服务端 id,要剔除
|
||||
// 全是占位 / 列表为空时 reduce 不更新初值(POSITIVE_INFINITY),转成 undefined → 后端从最新拉
|
||||
const earliestId = allMessages.value
|
||||
.filter((message) => !!message.id && message.id > 0)
|
||||
.reduce((min, message) => Math.min(min, message.id || min), Number.POSITIVE_INFINITY)
|
||||
let earliestId = Number.POSITIVE_INFINITY
|
||||
for (const message of allMessages.value) {
|
||||
if (message.id && message.id > 0) {
|
||||
earliestId = Math.min(earliestId, message.id)
|
||||
}
|
||||
}
|
||||
const maxId = Number.isFinite(earliestId) ? earliestId : undefined
|
||||
|
||||
// 调后端 list 接口:私聊 / 群聊接口签名不同,分支调度;返回结果用 useMessagePuller
|
||||
|
|
@ -358,7 +361,7 @@ async function loadEarlier() {
|
|||
maxId,
|
||||
limit: HISTORY_PAGE_SIZE
|
||||
})
|
||||
earlier = (list || []).map(convertGroupMessage)
|
||||
earlier = (list || []).map((message) => convertGroupMessage(message))
|
||||
pageLength = list?.length ?? 0
|
||||
} else {
|
||||
const list = await apiGetPrivateMessageList({
|
||||
|
|
@ -366,7 +369,7 @@ async function loadEarlier() {
|
|||
maxId,
|
||||
limit: HISTORY_PAGE_SIZE
|
||||
})
|
||||
earlier = (list || []).map(convertPrivateMessage)
|
||||
earlier = (list || []).map((message) => convertPrivateMessage(message))
|
||||
pageLength = list?.length ?? 0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@ import type { Message } from '../../../../types'
|
|||
|
||||
import { computed, inject } from 'vue'
|
||||
|
||||
import { confirm } from '@vben/common-ui'
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
import { useUserStore } from '@vben/stores'
|
||||
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { Tag } from 'ant-design-vue'
|
||||
import { message as antdMessage, Tag } from 'ant-design-vue'
|
||||
|
||||
import { pinGroupMessage as apiPinGroupMessage, cancelMuteMember } from '#/api/im/group'
|
||||
import { removeGroupMember } from '#/api/im/group/member'
|
||||
|
|
@ -50,7 +51,6 @@ import {
|
|||
resolveRtcCallPrivateBubbleText,
|
||||
resolveRtcCallTipSegments
|
||||
} from '#/views/im/utils/message'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
import { formatTimeTip } from '#/views/im/utils/time'
|
||||
import {
|
||||
getMemberDisplayName,
|
||||
|
|
@ -110,8 +110,6 @@ const uiStore = useImUiStore()
|
|||
const { recall, sendRaw } = useMessageSender()
|
||||
const { uploadAndSendMedia } = useMediaUploader()
|
||||
const muteOverlay = useMuteOverlay()
|
||||
// 仅用 confirm,避免 message 跟 props.message 同名冲突(vue/no-dupe-keys)
|
||||
const { confirm: confirmDialog, success: successMessage } = useMessage()
|
||||
// legacy:true 兼容 HTTP 环境,没有 navigator.clipboard 时降级到 execCommand
|
||||
const { copy: copyToClipboard } = useClipboard({ legacy: true })
|
||||
|
||||
|
|
@ -219,8 +217,7 @@ const rtcCallTipSegments = computed(() => resolveRtcCallTipSegments(props.messag
|
|||
/** 引用对象:气泡内嵌入展示;非引用消息返回 null,模板 v-if 不渲染 */
|
||||
const quote = computed(() => getQuoteFromMessage(props.message.content))
|
||||
|
||||
/** MessagePanel 注入的弹窗触发函数 */
|
||||
const openForwardDialog = inject(IM_FORWARD_DIALOG_KEY)
|
||||
const openForwardDialog = inject(IM_FORWARD_DIALOG_KEY) // MessagePanel 注入的弹窗触发函数
|
||||
const openMergeDetail = inject(IM_MERGE_DETAIL_DIALOG_KEY)
|
||||
const redialRtcCall = inject(IM_RTC_REDIAL_KEY)
|
||||
|
||||
|
|
@ -233,8 +230,7 @@ function handleRtcCallBubbleClick() {
|
|||
redialRtcCall?.(mediaType)
|
||||
}
|
||||
|
||||
/** 多选模式:模块级单例 composable */
|
||||
const multiSelect = useMessageMultiSelect()
|
||||
const multiSelect = useMessageMultiSelect() // 多选模式:模块级单例 composable
|
||||
|
||||
/** 合并消息气泡点击:打开详情弹窗(嵌套合并由弹窗内部 push 栈) */
|
||||
function handleMergeOpen(content: string) {
|
||||
|
|
@ -399,7 +395,7 @@ const isAtMe = computed(() => {
|
|||
|
||||
// ==================== 右键菜单 / 操作 ====================
|
||||
|
||||
/** 右键菜单 key 常量;push 端和分发端从同一处取,typo 编译期就能抓 */
|
||||
// 右键菜单 key 常量;push 端和分发端从同一处取,typo 编译期就能抓
|
||||
const MENU_KEYS = {
|
||||
COPY: 'COPY',
|
||||
REPLY: 'REPLY',
|
||||
|
|
@ -605,7 +601,7 @@ async function handleAddToFace() {
|
|||
}
|
||||
const data = await faceStore.addFaceUserItem(payload)
|
||||
if (data) {
|
||||
successMessage('已添加到表情')
|
||||
antdMessage.success('已添加到表情')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -688,9 +684,9 @@ async function handlePin() {
|
|||
return
|
||||
}
|
||||
try {
|
||||
await confirmDialog('将在当前群成员的聊天中置顶', '置顶消息')
|
||||
await confirm('将在当前群成员的聊天中置顶', '置顶消息')
|
||||
await apiPinGroupMessage({ id: group.id, messageId: props.message.id })
|
||||
successMessage('已置顶')
|
||||
antdMessage.success('已置顶')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
|
|
@ -701,7 +697,7 @@ async function handleCopy() {
|
|||
return
|
||||
}
|
||||
await copyToClipboard(text)
|
||||
successMessage('内容已复制到剪贴板')
|
||||
antdMessage.success('内容已复制到剪贴板')
|
||||
}
|
||||
|
||||
/** 进入引用模式:把当前消息构造成 QuoteMessage 写入会话草稿 */
|
||||
|
|
@ -739,7 +735,7 @@ function handleEnterMultiSelect() {
|
|||
*/
|
||||
async function handleRecall() {
|
||||
try {
|
||||
await confirmDialog('确定要撤回这条消息吗?', '撤回消息')
|
||||
await confirm('确定要撤回这条消息吗?', '撤回消息')
|
||||
await recall(props.message)
|
||||
} catch {}
|
||||
}
|
||||
|
|
@ -820,9 +816,9 @@ async function handleUnmute() {
|
|||
return
|
||||
}
|
||||
try {
|
||||
await confirmDialog('确定解除该成员的禁言吗?', '解除禁言')
|
||||
await confirm('确定解除该成员的禁言吗?', '解除禁言')
|
||||
await cancelMuteMember({ id: group.id, userId: props.message.senderId })
|
||||
successMessage('已解除禁言')
|
||||
antdMessage.success('已解除禁言')
|
||||
emit('reload')
|
||||
} catch {}
|
||||
}
|
||||
|
|
@ -835,9 +831,9 @@ async function handleKick() {
|
|||
}
|
||||
const name = senderDisplayName.value || '该成员'
|
||||
try {
|
||||
await confirmDialog(`确定将「${name}」移出群聊吗?`, '移除成员')
|
||||
await confirm(`确定将「${name}」移出群聊吗?`, '移除成员')
|
||||
await removeGroupMember({ groupId: group.id, memberUserIds: [props.message.senderId] })
|
||||
successMessage('已移除')
|
||||
antdMessage.success('已移除')
|
||||
emit('reload')
|
||||
} catch {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,12 @@ import { computed, nextTick, provide, ref, watch } from 'vue'
|
|||
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
|
||||
import { Popover, Tooltip } from 'ant-design-vue'
|
||||
import { message, Popover, Tooltip } from 'ant-design-vue'
|
||||
|
||||
import { createCall } from '#/api/im/rtc'
|
||||
import { ImConversationType, ImRtcCallMediaType, ImRtcCallStatus } from '#/views/im/utils/constants'
|
||||
import { getClientConversationId } from '#/views/im/utils/db'
|
||||
import { resolveCallEndReasonText } from '#/views/im/utils/message'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
import { getMemberDisplayName, isGroupQuit } from '#/views/im/utils/user'
|
||||
|
||||
import GroupMuteMemberDialog from '../../../../components/group/GroupMuteMemberDialog.vue'
|
||||
|
|
@ -49,7 +48,6 @@ const messageStore = useMessageStore()
|
|||
const friendStore = useFriendStore()
|
||||
const uiStore = useImUiStore()
|
||||
const groupStore = useGroupStore()
|
||||
const message = useMessage()
|
||||
const rtcStore = useRtcStore()
|
||||
const listRef = ref<HTMLElement>()
|
||||
|
||||
|
|
@ -196,7 +194,7 @@ const groupInfo = computed<
|
|||
}
|
||||
})
|
||||
|
||||
// 群成员列表:直接取 groupStore 缓存,map 成 GroupMemberLite 给下游消费(@-mention / 邀请等)
|
||||
/** 群成员列表:直接取 groupStore 缓存,map 成 GroupMemberLite 给下游消费(@-mention / 邀请等) */
|
||||
const groupMembers = computed<GroupMemberLite[]>(() => {
|
||||
const conversation = conversationStore.activeConversation
|
||||
if (!conversation || conversation.type !== ImConversationType.GROUP) {
|
||||
|
|
@ -250,13 +248,11 @@ function reloadGroupData() {
|
|||
groupStore.fetchGroupMemberList(conversation.targetId, true)
|
||||
}
|
||||
|
||||
/** 历史消息抽屉 ref:「聊天历史」icon / 抽屉「查找聊天内容」入口都调 open() 触发 */
|
||||
const historyDialogRef = ref<InstanceType<typeof MessageHistory>>()
|
||||
const historyDialogRef = ref<InstanceType<typeof MessageHistory>>() // 历史消息抽屉 ref:「聊天历史」icon / 抽屉「查找聊天内容」入口都调 open() 触发
|
||||
const sideVisible = ref(false) // 信息抽屉开关:群聊 / 私聊共用一个 ref
|
||||
const muteMemberDialogRef = ref<InstanceType<typeof GroupMuteMemberDialog>>()
|
||||
const callMemberPickerRef = ref<InstanceType<typeof RtcCallMemberPickerDialog>>()
|
||||
/** 群通话发起:成员选择弹窗打开期间临时持有的 mediaType */
|
||||
const pendingMediaType = ref<null | number>(null)
|
||||
const pendingMediaType = ref<null | number>(null) // 群通话发起:成员选择弹窗打开期间临时持有的 mediaType
|
||||
|
||||
/** 消息右键菜单「禁言」→ 打开时长选择弹窗 */
|
||||
function handleMuteMember(groupId: number, userId: number, displayName: string) {
|
||||
|
|
@ -268,8 +264,7 @@ function toggleSide() {
|
|||
sideVisible.value = !sideVisible.value
|
||||
}
|
||||
|
||||
/** 私聊通话入口:popover 触发;点 语音 / 视频 直接发起 */
|
||||
const callPopoverVisible = ref(false)
|
||||
const callPopoverVisible = ref(false) // 私聊通话入口:popover 触发;点 语音 / 视频 直接发起
|
||||
const callInviting = ref(false) // 通话发起中
|
||||
async function startPrivateCall(mediaType: number) {
|
||||
callPopoverVisible.value = false
|
||||
|
|
|
|||
|
|
@ -50,8 +50,7 @@ const emit = defineEmits<{
|
|||
locate: [messageId: number]
|
||||
}>()
|
||||
|
||||
/** 文本摘要在引用块里展示的最大字符数 */
|
||||
const MAX_TEXT_PREVIEW_LEN = 60
|
||||
const MAX_TEXT_PREVIEW_LEN = 60 // 文本摘要在引用块里展示的最大字符数
|
||||
|
||||
const conversationStore = useConversationStore()
|
||||
const messageStore = useMessageStore()
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { computed, reactive, ref } from 'vue'
|
|||
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
|
||||
import { Button, Input, Modal } from 'ant-design-vue'
|
||||
import { Button, Input, message, Modal } from 'ant-design-vue'
|
||||
|
||||
import { createGroup } from '#/api/im/group'
|
||||
import ConversationPickerPanel from '#/views/im/home/components/picker/ConversationPickerPanel.vue'
|
||||
|
|
@ -29,14 +29,12 @@ import {
|
|||
removeQuotePayload,
|
||||
serializeMessage
|
||||
} from '#/views/im/utils/message'
|
||||
import { useMessage } from '#/views/im/utils/message-feedback'
|
||||
import { isGroupQuit } from '#/views/im/utils/user'
|
||||
|
||||
import FacePicker from '../../input/FacePicker.vue'
|
||||
|
||||
defineOptions({ name: 'ImMessageForwardDialog' })
|
||||
|
||||
const message = useMessage()
|
||||
const conversationStore = useConversationStore()
|
||||
const friendStore = useFriendStore()
|
||||
const groupStore = useGroupStore()
|
||||
|
|
@ -49,14 +47,12 @@ const state = reactive({
|
|||
sourceConversation: null as Conversation | null
|
||||
})
|
||||
const visible = ref(false)
|
||||
/** 当前视图:默认会话选择,「创建聊天」入口切到好友选择 */
|
||||
const view = ref<'contact' | 'conversation'>('conversation')
|
||||
const view = ref<'contact' | 'conversation'>('conversation') // 当前视图:默认会话选择,「创建聊天」入口切到好友选择
|
||||
const selectedKeys = ref<string[]>([])
|
||||
const selectedFriendIds = ref<number[]>([])
|
||||
const leaveMessage = ref('')
|
||||
const sending = ref(false)
|
||||
/** emoji picker 显隐:右侧笑脸按钮切换 */
|
||||
const emojiVisible = ref(false)
|
||||
const emojiVisible = ref(false) // emoji picker 显隐:右侧笑脸按钮切换
|
||||
|
||||
defineExpose({
|
||||
/** 打开转发弹窗:reset → 灌参 → visible=true */
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ defineOptions({ name: 'ImMessageMergeDetailDialog' })
|
|||
const voicePlayer = useVoicePlayer()
|
||||
const visible = ref(false)
|
||||
|
||||
/** 嵌套层级栈,存 parsed payload 避免切层重 parse */
|
||||
const stack = ref<MergeMessage[]>([])
|
||||
const stack = ref<MergeMessage[]>([]) // 嵌套层级栈,存 parsed payload 避免切层重 parse
|
||||
|
||||
defineExpose({
|
||||
/** 打开详情弹窗,传入顶层合并消息 content */
|
||||
|
|
|
|||
|
|
@ -38,10 +38,9 @@ const filteredConversations = computed(() =>
|
|||
|
||||
// ==================== 置顶相关 ====================
|
||||
|
||||
/** 置顶超过该数量时显示折叠入口;以下数量直接铺开(避免单条置顶就出折叠头视觉太重) */
|
||||
const PINNED_FOLD_THRESHOLD = 3
|
||||
const PINNED_FOLD_THRESHOLD = 3 // 置顶超过该数量时显示折叠入口;以下数量直接铺开(避免单条置顶就出折叠头视觉太重)
|
||||
|
||||
/** 置顶折叠展开态:localStorage 持久化,刷新后保留用户上次的选择,对齐微信 */
|
||||
// 置顶折叠展开态:localStorage 持久化,刷新后保留用户上次的选择,对齐微信
|
||||
const pinnedExpanded = ref(
|
||||
localStorage.getItem(StorageKeys.localStorage.conversationPinnedExpanded) === 'true'
|
||||
)
|
||||
|
|
@ -105,13 +104,11 @@ const showPinnedSection = computed(
|
|||
|
||||
// ==================== 添加朋友 ====================
|
||||
|
||||
/** 添加朋友弹窗 ref:右上角 +-下拉「添加朋友」入口调 open() 触发 */
|
||||
const friendAddDialogRef = ref<InstanceType<typeof FriendAddDialog>>()
|
||||
const friendAddDialogRef = ref<InstanceType<typeof FriendAddDialog>>() // 添加朋友弹窗 ref:右上角 +-下拉「添加朋友」入口调 open() 触发
|
||||
|
||||
// ==================== 建群相关 ====================
|
||||
|
||||
/** 发起群聊弹窗 ref:handleOpenCreateGroup 调 open() 打开 */
|
||||
const createGroupDialogRef = ref<InstanceType<typeof GroupCreateDialog>>()
|
||||
const createGroupDialogRef = ref<InstanceType<typeof GroupCreateDialog>>() // 发起群聊弹窗 ref:handleOpenCreateGroup 调 open() 打开
|
||||
|
||||
/** 打开发起群聊弹窗:无锁定项的全局入口 */
|
||||
function handleOpenCreateGroup() {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import type { ChannelDO } from '../types'
|
||||
import type { ImManagerChannelApi } from '#/api/im/manager/channel'
|
||||
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
|
||||
import { getSimpleChannelList, type ImManagerChannelVO } from '#/api/im/manager/channel'
|
||||
import { getSimpleChannelList } from '#/api/im/manager/channel'
|
||||
|
||||
import { ImConversationType } from '../../utils/constants'
|
||||
import { getDb } from '../../utils/db'
|
||||
|
|
@ -16,12 +16,12 @@ import { useConversationStore } from './conversationStore'
|
|||
*/
|
||||
export const useChannelStore = defineStore('imChannelStore', {
|
||||
state: () => ({
|
||||
channels: [] as ImManagerChannelVO[],
|
||||
channels: [] as ImManagerChannelApi.Channel[],
|
||||
loaded: false
|
||||
}),
|
||||
|
||||
getters: {
|
||||
getChannel(state): (id: number) => ImManagerChannelVO | undefined {
|
||||
getChannel(state): (id: number) => ImManagerChannelApi.Channel | undefined {
|
||||
return (id: number) => state.channels.find((c) => c.id === id)
|
||||
}
|
||||
},
|
||||
|
|
@ -32,7 +32,7 @@ export const useChannelStore = defineStore('imChannelStore', {
|
|||
/** 从 IndexedDB 恢复频道列表 */
|
||||
async loadChannelList(): Promise<boolean> {
|
||||
try {
|
||||
const cached = await getDb().getAll<ChannelDO>('channels')
|
||||
const cached = await getDb().getAll<ImManagerChannelApi.Channel>('channels')
|
||||
if (!cached || cached.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import type {
|
|||
MessageDO
|
||||
} from '../types'
|
||||
|
||||
import type { ImConversationReadRespVO } from '#/api/im/conversation/read'
|
||||
import type { ImConversationReadApi } from '#/api/im/conversation/read'
|
||||
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
|
||||
|
|
@ -113,7 +113,7 @@ function fromConversationReadDO(record: ConversationReadDO): ConversationRead {
|
|||
}
|
||||
|
||||
/** 是否为有效会话读位置 */
|
||||
function isValidConversationReadRecord(record: ImConversationReadRespVO): boolean {
|
||||
function isValidConversationReadRecord(record: ImConversationReadApi.ConversationReadRespVO): boolean {
|
||||
return !!record.conversationType && !!record.targetId && !!record.messageId
|
||||
}
|
||||
|
||||
|
|
@ -351,7 +351,7 @@ export const useConversationStore = defineStore('imConversationStore', {
|
|||
|
||||
/** 应用会话读位置 */
|
||||
async applyConversationReadList(
|
||||
records: ImConversationReadRespVO[],
|
||||
records: ImConversationReadApi.ConversationReadRespVO[],
|
||||
isActive?: () => boolean
|
||||
): Promise<void> {
|
||||
if (records.length === 0) {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,12 @@
|
|||
import type { ImFacePackApi } from '#/api/im/face/pack'
|
||||
import type { ImFaceUserItemApi } from '#/api/im/face/useritem'
|
||||
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
|
||||
import {
|
||||
getFacePackList as apiGetFacePackList,
|
||||
type ImFacePackUserVO
|
||||
} from '#/api/im/face/pack'
|
||||
import {
|
||||
createFaceUserItem as apiCreateFaceUserItem,
|
||||
deleteFaceUserItem as apiDeleteFaceUserItem,
|
||||
getFaceUserItemList as apiGetFaceUserItemList,
|
||||
type ImFaceUserItemSaveReqVO,
|
||||
type ImFaceUserItemVO
|
||||
} from '#/api/im/face/useritem'
|
||||
import { getFacePackList as apiGetFacePackList } from '#/api/im/face/pack'
|
||||
import { createFaceUserItem as apiCreateFaceUserItem, deleteFaceUserItem as apiDeleteFaceUserItem, getFaceUserItemList as apiGetFaceUserItemList } from '#/api/im/face/useritem'
|
||||
|
||||
/**
|
||||
* IM 表情面板数据 store(系统表情包 + 个人表情)
|
||||
|
|
@ -24,9 +18,9 @@ import {
|
|||
export const useFaceStore = defineStore('imFace', () => {
|
||||
|
||||
/** 系统表情包列表(含每个包的 items);运营管理后台维护 */
|
||||
const facePacks = ref<ImFacePackUserVO[]>([])
|
||||
const facePacks = ref<ImFacePackApi.FacePackUser[]>([])
|
||||
/** 个人表情包列表(用户长按「添加到表情」/ 上传产生) */
|
||||
const faceUserItems = ref<ImFaceUserItemVO[]>([])
|
||||
const faceUserItems = ref<ImFaceUserItemApi.FaceUserItem[]>([])
|
||||
|
||||
/** clear() 时递增;旧账号请求返回后不写入新账号内存 */
|
||||
let storeEpoch = 0
|
||||
|
|
@ -89,7 +83,7 @@ export const useFaceStore = defineStore('imFace', () => {
|
|||
*
|
||||
* 来源:1. 用户在表情面板「+」上传图片 2. 长按消息「添加到表情」
|
||||
*/
|
||||
async function addFaceUserItem(reqVO: ImFaceUserItemSaveReqVO): Promise<boolean> {
|
||||
async function addFaceUserItem(reqVO: ImFaceUserItemApi.FaceUserItemSaveReqVO): Promise<boolean> {
|
||||
const requestEpoch = storeEpoch
|
||||
const id = await apiCreateFaceUserItem(reqVO)
|
||||
if (!id) {
|
||||
|
|
|
|||
|
|
@ -1,29 +1,14 @@
|
|||
import type { Friend, FriendDO, FriendLite, FriendRequest, FriendRequestDO } from '../types'
|
||||
import type { Friend, FriendLite, FriendRequest } from '../types'
|
||||
|
||||
import type { ImFriendApi } from '#/api/im/friend'
|
||||
import type { ImFriendRequestApi } from '#/api/im/friend/request'
|
||||
|
||||
import { CommonStatusEnum } from '@vben/constants'
|
||||
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
|
||||
import {
|
||||
blockFriend as apiBlockFriend,
|
||||
deleteFriend as apiDeleteFriend,
|
||||
getFriend as apiGetFriend,
|
||||
getMyFriendList as apiGetMyFriendList,
|
||||
pullMyFriendList as apiPullMyFriendList,
|
||||
unblockFriend as apiUnblockFriend,
|
||||
updateFriend as apiUpdateFriend,
|
||||
type ImFriendRespVO
|
||||
} from '#/api/im/friend'
|
||||
import {
|
||||
agreeFriendRequest as apiAgreeFriendRequest,
|
||||
applyFriendRequest as apiApplyFriendRequest,
|
||||
getMyFriendRequest as apiGetMyFriendRequest,
|
||||
getMyFriendRequestList as apiGetMyFriendRequestList,
|
||||
pullMyFriendRequestList as apiPullMyFriendRequestList,
|
||||
refuseFriendRequest as apiRefuseFriendRequest,
|
||||
type ImFriendRequestApplyReqVO,
|
||||
type ImFriendRequestRespVO
|
||||
} from '#/api/im/friend/request'
|
||||
import { blockFriend as apiBlockFriend, deleteFriend as apiDeleteFriend, getFriend as apiGetFriend, getMyFriendList as apiGetMyFriendList, pullMyFriendList as apiPullMyFriendList, unblockFriend as apiUnblockFriend, updateFriend as apiUpdateFriend } from '#/api/im/friend'
|
||||
import { agreeFriendRequest as apiAgreeFriendRequest, applyFriendRequest as apiApplyFriendRequest, getMyFriendRequest as apiGetMyFriendRequest, getMyFriendRequestList as apiGetMyFriendRequestList, pullMyFriendRequestList as apiPullMyFriendRequestList, refuseFriendRequest as apiRefuseFriendRequest } from '#/api/im/friend/request'
|
||||
import { getCurrentUserId } from '#/views/im/utils/auth'
|
||||
|
||||
import { FRIEND_REQUEST_PAGE_SIZE } from '../../utils/config'
|
||||
|
|
@ -155,8 +140,8 @@ export const useFriendStore = defineStore('imFriendStore', {
|
|||
async loadFriendData(): Promise<boolean> {
|
||||
try {
|
||||
const [friends, friendRequests] = await Promise.all([
|
||||
getDb().getAll<FriendDO>('friends'),
|
||||
getDb().getAll<FriendRequestDO>('friendRequests')
|
||||
getDb().getAll<Friend>('friends'),
|
||||
getDb().getAll<FriendRequest>('friendRequests')
|
||||
])
|
||||
if (friends.length > 0) {
|
||||
this.friends = friends
|
||||
|
|
@ -346,7 +331,7 @@ export const useFriendStore = defineStore('imFriendStore', {
|
|||
// ==================== 申请-审批 ====================
|
||||
|
||||
/** 发起好友申请:成功后等待对方同意(不直接落地为好友) */
|
||||
async applyFriendRequest(reqVO: ImFriendRequestApplyReqVO): Promise<null | number> {
|
||||
async applyFriendRequest(reqVO: ImFriendRequestApi.FriendRequestApplyReqVO): Promise<null | number> {
|
||||
return await apiApplyFriendRequest(reqVO)
|
||||
},
|
||||
|
||||
|
|
@ -816,7 +801,7 @@ export const useFriendStore = defineStore('imFriendStore', {
|
|||
}
|
||||
})
|
||||
|
||||
function convertFriend(vo: ImFriendRespVO): Friend {
|
||||
function convertFriend(vo: ImFriendApi.FriendRespVO): Friend {
|
||||
return {
|
||||
id: vo.id,
|
||||
friendUserId: vo.friendUserId,
|
||||
|
|
@ -835,7 +820,7 @@ function convertFriend(vo: ImFriendRespVO): Friend {
|
|||
}
|
||||
}
|
||||
|
||||
function convertFriendRequest(vo: ImFriendRequestRespVO): FriendRequest {
|
||||
function convertFriendRequest(vo: ImFriendRequestApi.FriendRequestRespVO): FriendRequest {
|
||||
return {
|
||||
id: vo.id,
|
||||
fromUserId: vo.fromUserId,
|
||||
|
|
|
|||
|
|
@ -1,15 +1,8 @@
|
|||
import type { GroupRequestDO } from '../types'
|
||||
import type { ImGroupRequestApi } from '#/api/im/group/request'
|
||||
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
|
||||
import {
|
||||
agreeGroupRequest as apiAgreeGroupRequest,
|
||||
getMyGroupRequest as apiGetMyGroupRequest,
|
||||
getUnhandledRequestList as apiGetUnhandledRequestList,
|
||||
pullMyGroupRequestList as apiPullMyGroupRequestList,
|
||||
refuseGroupRequest as apiRefuseGroupRequest,
|
||||
type ImGroupRequestRespVO
|
||||
} from '#/api/im/group/request'
|
||||
import { agreeGroupRequest as apiAgreeGroupRequest, getMyGroupRequest as apiGetMyGroupRequest, getUnhandledRequestList as apiGetUnhandledRequestList, pullMyGroupRequestList as apiPullMyGroupRequestList, refuseGroupRequest as apiRefuseGroupRequest } from '#/api/im/group/request'
|
||||
import { getCurrentUserId } from '#/views/im/utils/auth'
|
||||
import { ImGroupRequestHandleResult } from '#/views/im/utils/constants'
|
||||
|
||||
|
|
@ -26,7 +19,7 @@ let pendingUnhandledFetch: null | PendingRequest = null
|
|||
* IM 加群申请 Store
|
||||
*
|
||||
* 仅维护「我管理的所有群」下未处理的申请列表(unhandledList);
|
||||
* 横幅 / Drawer 都从这里派生 count 和分组列表,避免给 ImGroupRespVO 挂 pendingRequestCount 字段
|
||||
* 横幅 / Drawer 都从这里派生 count 和分组列表,避免给 ImGroupApi.GroupRespVO 挂 pendingRequestCount 字段
|
||||
*
|
||||
* 数据生命周期:
|
||||
* - 进 IM 后调一次 fetchUnhandledGroupRequestList 拉首页全量
|
||||
|
|
@ -38,7 +31,7 @@ let pendingUnhandledFetch: null | PendingRequest = null
|
|||
export const useGroupRequestStore = defineStore('imGroupRequestStore', {
|
||||
state: () => ({
|
||||
/** 我管理的所有群下未处理申请列表(按 id 倒序) */
|
||||
unhandledList: [] as ImGroupRequestRespVO[],
|
||||
unhandledList: [] as ImGroupRequestApi.GroupRequestRespVO[],
|
||||
/** fetchUnhandledGroupRequestList 是否成功执行过;避免横幅显示 0 然后跳数字的闪烁 */
|
||||
loaded: false
|
||||
}),
|
||||
|
|
@ -61,7 +54,7 @@ export const useGroupRequestStore = defineStore('imGroupRequestStore', {
|
|||
/** 指定群下的未处理申请列表 */
|
||||
getUnhandledGroupRequestListByGroupId:
|
||||
(state) =>
|
||||
(groupId: number): ImGroupRequestRespVO[] =>
|
||||
(groupId: number): ImGroupRequestApi.GroupRequestRespVO[] =>
|
||||
state.unhandledList.filter((r) => r.groupId === groupId)
|
||||
},
|
||||
|
||||
|
|
@ -69,13 +62,13 @@ export const useGroupRequestStore = defineStore('imGroupRequestStore', {
|
|||
/** 从 IndexedDB 恢复加群申请 */
|
||||
async loadGroupRequestList(): Promise<boolean> {
|
||||
try {
|
||||
const cached = await getDb().getAll<GroupRequestDO>('groupRequests')
|
||||
const cached = await getDb().getAll<ImGroupRequestApi.GroupRequestRespVO>('groupRequests')
|
||||
if (!cached || cached.length === 0) {
|
||||
return false
|
||||
}
|
||||
this.unhandledList = cached
|
||||
.filter((request) => request.handleResult === ImGroupRequestHandleResult.UNHANDLED)
|
||||
.sort((requestA, requestB) => requestB.id - requestA.id)
|
||||
.toSorted((requestA, requestB) => requestB.id - requestA.id)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.warn('[IM groupRequestStore] 本地加群申请缓存读取失败', error)
|
||||
|
|
@ -97,12 +90,12 @@ export const useGroupRequestStore = defineStore('imGroupRequestStore', {
|
|||
},
|
||||
|
||||
/** 保存单条加群申请 */
|
||||
async saveGroupRequestRecord(request: ImGroupRequestRespVO): Promise<void> {
|
||||
async saveGroupRequestRecord(request: ImGroupRequestApi.GroupRequestRespVO): Promise<void> {
|
||||
await getDb().put('groupRequests', request)
|
||||
},
|
||||
|
||||
/** 保存单条加群申请 */
|
||||
saveGroupRequest(request: ImGroupRequestRespVO): void {
|
||||
saveGroupRequest(request: ImGroupRequestApi.GroupRequestRespVO): void {
|
||||
void this.saveGroupRequestRecord(request).catch((error) =>
|
||||
console.warn('[IM groupRequestStore] 本地加群申请写入失败', error)
|
||||
)
|
||||
|
|
@ -161,14 +154,14 @@ export const useGroupRequestStore = defineStore('imGroupRequestStore', {
|
|||
*
|
||||
* 未处理的按 id 去重后置顶;已处理的从未处理列表移除,避免补偿时把已同意 / 拒绝的记录塞回红点
|
||||
*/
|
||||
upsertGroupRequest(request: ImGroupRequestRespVO) {
|
||||
upsertGroupRequest(request: ImGroupRequestApi.GroupRequestRespVO) {
|
||||
void this.upsertGroupRequestForPull(request).catch((error) =>
|
||||
console.warn('[IM groupRequestStore] 本地加群申请写入失败', error)
|
||||
)
|
||||
},
|
||||
|
||||
/** 本地合并 / 新增单条加群申请 */
|
||||
async upsertGroupRequestForPull(request: ImGroupRequestRespVO): Promise<void> {
|
||||
async upsertGroupRequestForPull(request: ImGroupRequestApi.GroupRequestRespVO): Promise<void> {
|
||||
if (request.handleResult !== ImGroupRequestHandleResult.UNHANDLED) {
|
||||
await this.removeGroupRequestByIdForPull(request.id)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1,20 +1,14 @@
|
|||
import type { Group, GroupDO, GroupMember, GroupMemberDO, Message } from '../types'
|
||||
import type { Group, GroupDO, GroupMember, Message } from '../types'
|
||||
|
||||
import type { ImGroupApi } from '#/api/im/group'
|
||||
import type { ImGroupMemberApi } from '#/api/im/group/member'
|
||||
|
||||
import { CommonStatusEnum } from '@vben/constants'
|
||||
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
|
||||
import {
|
||||
getGroup as apiGetGroup,
|
||||
getMyGroupList as apiGetMyGroupList,
|
||||
type ImGroupRespVO
|
||||
} from '#/api/im/group'
|
||||
import {
|
||||
getGroupMember as apiGetGroupMember,
|
||||
getGroupMemberList as apiGetGroupMemberList,
|
||||
updateGroupMember as apiUpdateGroupMember,
|
||||
type ImGroupMemberRespVO
|
||||
} from '#/api/im/group/member'
|
||||
import { getGroup as apiGetGroup, getMyGroupList as apiGetMyGroupList } from '#/api/im/group'
|
||||
import { getGroupMember as apiGetGroupMember, getGroupMemberList as apiGetGroupMemberList, updateGroupMember as apiUpdateGroupMember } from '#/api/im/group/member'
|
||||
import { getCurrentUserId } from '#/views/im/utils/auth'
|
||||
|
||||
import {
|
||||
|
|
@ -159,7 +153,7 @@ export const useGroupStore = defineStore('imGroupStore', {
|
|||
return cachedGroup.members
|
||||
}
|
||||
try {
|
||||
const cached = await getDb().getAllByIndex<GroupMemberDO>(
|
||||
const cached = await getDb().getAllByIndex<GroupMember>(
|
||||
'groupMembers',
|
||||
'groupId',
|
||||
groupId
|
||||
|
|
@ -229,7 +223,7 @@ export const useGroupStore = defineStore('imGroupStore', {
|
|||
return
|
||||
}
|
||||
const fresh = (list || []).map((group) => convertGroup(group))
|
||||
// 合并而非全量替换:silent / groupRemark / 成员缓存这些字段不在 ImGroupRespVO 里,得从旧 group 保留
|
||||
// 合并而非全量替换:silent / groupRemark / 成员缓存这些字段不在 ImGroupApi.GroupRespVO 里,得从旧 group 保留
|
||||
const groupMap = new Map(this.groups.map((group) => [group.id, group]))
|
||||
this.groups = fresh.map((group) => {
|
||||
const existing = groupMap.get(group.id)
|
||||
|
|
@ -314,7 +308,7 @@ export const useGroupStore = defineStore('imGroupStore', {
|
|||
if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) {
|
||||
return []
|
||||
}
|
||||
let meRaw: ImGroupMemberRespVO | undefined
|
||||
let meRaw: ImGroupMemberApi.GroupMemberRespVO | undefined
|
||||
const members = (list || []).map((member) => {
|
||||
if (member.userId === requestUserId) {
|
||||
meRaw = member
|
||||
|
|
@ -903,7 +897,7 @@ export const useGroupStore = defineStore('imGroupStore', {
|
|||
}
|
||||
})
|
||||
|
||||
function convertGroup(group: ImGroupRespVO): Group {
|
||||
function convertGroup(group: ImGroupApi.GroupRespVO): Group {
|
||||
return {
|
||||
id: group.id,
|
||||
name: group.name,
|
||||
|
|
@ -918,9 +912,9 @@ function convertGroup(group: ImGroupRespVO): Group {
|
|||
}
|
||||
}
|
||||
|
||||
/** 后端 ImGroupMessageRespVO -> 前端 Message:补 targetId / selfSend / sendTime 等派生字段 */
|
||||
/** 后端 ImGroupMessageApi.GroupMessageRespVO -> 前端 Message:补 targetId / selfSend / sendTime 等派生字段 */
|
||||
function convertGroupMessageVO(
|
||||
message: NonNullable<ImGroupRespVO['pinnedMessages']>[number]
|
||||
message: NonNullable<ImGroupApi.GroupRespVO['pinnedMessages']>[number]
|
||||
): Message {
|
||||
const currentUserId = getCurrentUserId()
|
||||
return {
|
||||
|
|
@ -940,7 +934,7 @@ function convertGroupMessageVO(
|
|||
}
|
||||
}
|
||||
|
||||
function convertGroupMember(member: ImGroupMemberRespVO, groupId: number): GroupMember {
|
||||
function convertGroupMember(member: ImGroupMemberApi.GroupMemberRespVO, groupId: number): GroupMember {
|
||||
return {
|
||||
id: member.id,
|
||||
userId: member.userId,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { ImRtcCallRespVO, ImRtcGroupCallRespVO } from '#/api/im/rtc'
|
||||
import type { ImRtcApi } from '#/api/im/rtc'
|
||||
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ export const useRtcStore = defineStore('imRtc', () => {
|
|||
/** 当前阶段 */
|
||||
const stage = ref<ImRtcCallStageValue>(ImRtcCallStage.IDLE)
|
||||
/** 当前通话;invite / accept / refreshToken 拿到的完整信息 */
|
||||
const call = ref<ImRtcCallRespVO | null>(null)
|
||||
const call = ref<ImRtcApi.RtcCallRespVO | null>(null)
|
||||
/** 来电载荷;仅 INCOMING 阶段使用;status 固定 INVITING,其它字段 INVITE 专属 */
|
||||
const incomingPayload = ref<ImRtcCallNotification | null>(null)
|
||||
/** 进入 RUNNING 的时间戳;用于通话时长展示;reset 时清零 */
|
||||
|
|
@ -115,13 +115,13 @@ export const useRtcStore = defineStore('imRtc', () => {
|
|||
})
|
||||
|
||||
/** 私聊场景对端 userId:自己是主叫则取首个 invitee,否则取 inviter */
|
||||
function resolvePrivatePeerUserId(c: ImRtcCallRespVO): number | undefined {
|
||||
function resolvePrivatePeerUserId(c: ImRtcApi.RtcCallRespVO): number | undefined {
|
||||
const myId = getCurrentUserId()
|
||||
return c.inviterId === myId ? c.inviteeIds?.[0] : c.inviterId
|
||||
}
|
||||
|
||||
/** 群活跃通话索引;groupId -> 群通话摘要;用于群聊顶部胶囊条 */
|
||||
const groupActiveCalls = ref<Map<number, ImRtcGroupCallRespVO>>(new Map())
|
||||
const groupActiveCalls = ref<Map<number, ImRtcApi.RtcGroupCallRespVO>>(new Map())
|
||||
|
||||
/**
|
||||
* 已退出 / 已拒绝的用户编号集合;群通话场景内 pending 占位渲染时排除;
|
||||
|
|
@ -147,7 +147,7 @@ export const useRtcStore = defineStore('imRtc', () => {
|
|||
* 群通话:发起人直接进 RUNNING 多人卡片视图,房内可能只有自己,等其他人陆续加入;
|
||||
* 私聊:按 status 走;RUNNING(已加入已有通话场景)→ RUNNING;CREATED → INVITING 等被叫接通
|
||||
*/
|
||||
function startInviting(data: ImRtcCallRespVO) {
|
||||
function startInviting(data: ImRtcApi.RtcCallRespVO) {
|
||||
call.value = data
|
||||
// 群通话场景写入本地胶囊条缓存
|
||||
syncGroupActiveCall(data)
|
||||
|
|
@ -184,7 +184,7 @@ export const useRtcStore = defineStore('imRtc', () => {
|
|||
}
|
||||
|
||||
/** 进入通话中阶段 */
|
||||
function enterRunning(data: ImRtcCallRespVO) {
|
||||
function enterRunning(data: ImRtcApi.RtcCallRespVO) {
|
||||
call.value = data
|
||||
// 离开 INCOMING 阶段;清空来电载荷
|
||||
incomingPayload.value = null
|
||||
|
|
@ -252,7 +252,7 @@ export const useRtcStore = defineStore('imRtc', () => {
|
|||
* 房内成员同步交给 LiveKit 客户端事件(ParticipantConnected / Disconnected);
|
||||
* 胶囊条不实时刷新 joinedUserIds / inviteeIds,展开 / 加入时再走 getActiveCall 接口拉最新
|
||||
*/
|
||||
function setGroupCall(payload: ImRtcGroupCallRespVO) {
|
||||
function setGroupCall(payload: ImRtcApi.RtcGroupCallRespVO) {
|
||||
if (!payload?.groupId) {
|
||||
return
|
||||
}
|
||||
|
|
@ -267,7 +267,7 @@ export const useRtcStore = defineStore('imRtc', () => {
|
|||
}
|
||||
|
||||
/** 两条群通话摘要内容相等(room / mediaType / inviterId / 两个 userId 数组逐项相等) */
|
||||
function isSameGroupCall(a: ImRtcGroupCallRespVO, b: ImRtcGroupCallRespVO): boolean {
|
||||
function isSameGroupCall(a: ImRtcApi.RtcGroupCallRespVO, b: ImRtcApi.RtcGroupCallRespVO): boolean {
|
||||
if (a.room !== b.room || a.mediaType !== b.mediaType || a.inviterId !== b.inviterId) {
|
||||
return false
|
||||
}
|
||||
|
|
@ -294,7 +294,7 @@ export const useRtcStore = defineStore('imRtc', () => {
|
|||
}
|
||||
|
||||
/** 获取群当前活跃通话;用于胶囊条按 groupId 查询 */
|
||||
function getGroupCall(groupId: number): ImRtcGroupCallRespVO | undefined {
|
||||
function getGroupCall(groupId: number): ImRtcApi.RtcGroupCallRespVO | undefined {
|
||||
return groupActiveCalls.value.get(groupId)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import type {
|
|||
WebSocketFrame
|
||||
} from '../types'
|
||||
|
||||
import type { ImChannelMessageRespVO } from '#/api/im/message/channel'
|
||||
import type { ImChannelMessageApi } from '#/api/im/message/channel'
|
||||
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
|
||||
|
|
@ -215,7 +215,7 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
|||
reconnectAttempts: 0,
|
||||
heartbeatTimer: null as null | ReturnType<typeof setInterval>,
|
||||
messageBuffer: [] as Array<
|
||||
| { conversationType: typeof ImConversationType.CHANNEL; payload: ImChannelMessageRespVO }
|
||||
| { conversationType: typeof ImConversationType.CHANNEL; payload: ImChannelMessageApi.ChannelMessageRespVO }
|
||||
| {
|
||||
conversationType: typeof ImConversationType.GROUP
|
||||
payload: ImGroupMessageNotification
|
||||
|
|
@ -345,7 +345,7 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
|||
}
|
||||
switch (notification.conversationType) {
|
||||
case ImConversationType.CHANNEL: {
|
||||
this.dispatchChannelFrame(payload as ImChannelMessageRespVO)
|
||||
this.dispatchChannelFrame(payload as ImChannelMessageApi.ChannelMessageRespVO)
|
||||
break
|
||||
}
|
||||
case ImConversationType.GROUP: {
|
||||
|
|
@ -394,7 +394,7 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
|||
/**
|
||||
* 频道帧分发:按 payload.type 分到 READ(多端已读同步)或普通素材推送
|
||||
*/
|
||||
dispatchChannelFrame(websocketMessage: ImChannelMessageRespVO) {
|
||||
dispatchChannelFrame(websocketMessage: ImChannelMessageApi.ChannelMessageRespVO) {
|
||||
if (websocketMessage.type === ImContentType.READ) {
|
||||
this.handleChannelRead(websocketMessage)
|
||||
return
|
||||
|
|
@ -403,7 +403,7 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
|||
},
|
||||
|
||||
/** 频道 READ:自己其它终端在某频道里标为已读,本端同步清零该频道未读 */
|
||||
handleChannelRead(websocketMessage: ImChannelMessageRespVO) {
|
||||
handleChannelRead(websocketMessage: ImChannelMessageApi.ChannelMessageRespVO) {
|
||||
void useConversationStore()
|
||||
.applyConversationReadList([
|
||||
{
|
||||
|
|
@ -420,7 +420,7 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
|||
* 频道消息实时入会话;频道消息单向 + 无状态机,直接 insertMessage 即可
|
||||
* pull 与 WS 拿到同一条 id 时,messageStore.insertMessage 内部按 id 去重,不会重复
|
||||
*/
|
||||
handleChannelMessage(websocketMessage: ImChannelMessageRespVO): Promise<void> {
|
||||
handleChannelMessage(websocketMessage: ImChannelMessageApi.ChannelMessageRespVO): Promise<void> {
|
||||
const conversationStore = useConversationStore()
|
||||
const messageStore = useMessageStore()
|
||||
// 离线加载期间先缓冲,等 pull 完成后再统一回放,避免重复或顺序错乱
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ export interface SettingDO<T = unknown> {
|
|||
|
||||
// 群实体(前端内部结构)
|
||||
export interface Group {
|
||||
// ========== 后端字段(对齐 ImGroupRespVO) ==========
|
||||
// ========== 后端字段(对齐 ImGroupApi.GroupRespVO) ==========
|
||||
id: number // 群编号
|
||||
name: string // 群名称
|
||||
avatar?: string // 群头像
|
||||
|
|
@ -203,7 +203,7 @@ export type GroupDO = Omit<Group, 'members' | 'membersExpired' | 'membersLoaded'
|
|||
|
||||
// 群成员实体(前端内部结构)
|
||||
export interface GroupMember {
|
||||
// ========== 后端字段(对齐 ImGroupMemberRespVO) ==========
|
||||
// ========== 后端字段(对齐 ImGroupMemberApi.GroupMemberRespVO) ==========
|
||||
id?: number // 群成员关系记录编号
|
||||
groupId: number // 群编号
|
||||
userId: number // 用户编号
|
||||
|
|
@ -218,13 +218,11 @@ export interface GroupMember {
|
|||
isOwner?: boolean // 是否群主(前端从 Group.ownerUserId 计算)
|
||||
}
|
||||
|
||||
export type GroupMemberDO = GroupMember
|
||||
|
||||
// ==================== 好友 ====================
|
||||
|
||||
// 好友实体(前端内部结构)
|
||||
export interface Friend {
|
||||
// ========== 后端字段(对齐 ImFriendRespVO) ==========
|
||||
// ========== 后端字段(对齐 ImFriendApi.FriendRespVO) ==========
|
||||
id?: number // 好友关系记录编号(本地乐观新增时可能暂缺)
|
||||
friendUserId: number // 好友用户编号(与 Conversation.targetId 对齐)
|
||||
nickname: string // 好友昵称(对方真实昵称,永远不被备注覆盖;UI 显示走 displayName || nickname)
|
||||
|
|
@ -241,13 +239,11 @@ export interface Friend {
|
|||
deleteTime?: number // 删除好友时间(毫秒时间戳;后端为 LocalDateTime 字符串,在 convertFriend 转换)
|
||||
}
|
||||
|
||||
export type FriendDO = Friend
|
||||
|
||||
/**
|
||||
* 好友申请记录(前端内部结构,对齐后端 ImFriendRequestRespVO)
|
||||
* 好友申请记录(前端内部结构,对齐后端 ImFriendRequestApi.FriendRequestRespVO)
|
||||
*/
|
||||
export interface FriendRequest {
|
||||
// ========== 后端字段(对齐 ImFriendRequestRespVO) ==========
|
||||
// ========== 后端字段(对齐 ImFriendRequestApi.FriendRequestRespVO) ==========
|
||||
id: number // 申请编号
|
||||
fromUserId: number // 发起方用户编号
|
||||
toUserId: number // 接收方用户编号
|
||||
|
|
@ -265,12 +261,6 @@ export interface FriendRequest {
|
|||
toAvatar?: string // 接收方头像
|
||||
}
|
||||
|
||||
export type FriendRequestDO = FriendRequest
|
||||
|
||||
export type GroupRequestDO = import('#/api/im/group/request').ImGroupRequestRespVO
|
||||
|
||||
export type ChannelDO = import('#/api/im/manager/channel').ImManagerChannelVO
|
||||
|
||||
// ==================== 用户名片 ====================
|
||||
|
||||
// 用户精简信息(对齐后端 UserSimpleRespVO,名片 / 头像 hover 等场景共用)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table'
|
||||
import type { ImManagerChannelVO } from '#/api/im/manager/channel'
|
||||
import type { ImManagerChannelApi } from '#/api/im/manager/channel'
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui'
|
||||
|
||||
|
|
@ -31,12 +31,12 @@ function handleCreate() {
|
|||
}
|
||||
|
||||
/** 编辑频道 */
|
||||
function handleEdit(row: ImManagerChannelVO) {
|
||||
function handleEdit(row: ImManagerChannelApi.Channel) {
|
||||
formModalApi.setData(row).open()
|
||||
}
|
||||
|
||||
/** 删除频道 */
|
||||
async function handleDelete(row: ImManagerChannelVO) {
|
||||
async function handleDelete(row: ImManagerChannelApi.Channel) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||
duration: 0
|
||||
|
|
@ -77,7 +77,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
refresh: true,
|
||||
search: true
|
||||
}
|
||||
} as VxeTableGridOptions<ImManagerChannelVO>
|
||||
} as VxeTableGridOptions<ImManagerChannelApi.Channel>
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import type { ImManagerChannelVO } from '#/api/im/manager/channel'
|
||||
import type { ImManagerChannelApi } from '#/api/im/manager/channel'
|
||||
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ import { $t } from '#/locales'
|
|||
import { useFormSchema } from '../data'
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
const formData = ref<ImManagerChannelVO>()
|
||||
const formData = ref<ImManagerChannelApi.Channel>()
|
||||
const getTitle = computed(() => {
|
||||
return formData.value?.id
|
||||
? $t('ui.actionTitle.edit', ['频道'])
|
||||
|
|
@ -45,7 +45,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||
return
|
||||
}
|
||||
modalApi.lock()
|
||||
const data = (await formApi.getValues()) as ImManagerChannelVO
|
||||
const data = (await formApi.getValues()) as ImManagerChannelApi.Channel
|
||||
try {
|
||||
await (formData.value?.id ? updateManagerChannel(data) : createManagerChannel(data))
|
||||
await modalApi.close()
|
||||
|
|
@ -60,7 +60,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||
formData.value = undefined
|
||||
return
|
||||
}
|
||||
const data = modalApi.getData<ImManagerChannelVO>()
|
||||
const data = modalApi.getData<ImManagerChannelApi.Channel>()
|
||||
if (!data || !data.id) {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { ImManagerChannelMaterialVO } from '#/api/im/manager/channel/material';
|
||||
import type { ImManagerChannelMaterialApi } from '#/api/im/manager/channel/material';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
|
|
@ -34,12 +34,12 @@ function handleCreate() {
|
|||
}
|
||||
|
||||
/** 编辑素材 */
|
||||
function handleEdit(row: ImManagerChannelMaterialVO) {
|
||||
function handleEdit(row: ImManagerChannelMaterialApi.Material) {
|
||||
formModalApi.setData(row).open();
|
||||
}
|
||||
|
||||
/** 删除素材 */
|
||||
async function handleDelete(row: ImManagerChannelMaterialVO) {
|
||||
async function handleDelete(row: ImManagerChannelMaterialApi.Material) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.title]),
|
||||
duration: 0,
|
||||
|
|
@ -80,7 +80,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<ImManagerChannelMaterialVO>,
|
||||
} as VxeTableGridOptions<ImManagerChannelMaterialApi.Material>,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import type { ImManagerChannelMaterialVO } from '#/api/im/manager/channel/material';
|
||||
import type { ImManagerChannelMaterialApi } from '#/api/im/manager/channel/material';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ import { $t } from '#/locales';
|
|||
import { useFormSchema } from '../data';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const formData = ref<ImManagerChannelMaterialVO>();
|
||||
const formData = ref<ImManagerChannelMaterialApi.Material>();
|
||||
const getTitle = computed(() => {
|
||||
return formData.value?.id
|
||||
? $t('ui.actionTitle.edit', ['素材'])
|
||||
|
|
@ -46,7 +46,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||
}
|
||||
modalApi.lock();
|
||||
const data =
|
||||
(await formApi.getValues()) as ImManagerChannelMaterialVO;
|
||||
(await formApi.getValues()) as ImManagerChannelMaterialApi.Material;
|
||||
try {
|
||||
await (formData.value?.id
|
||||
? updateManagerChannelMaterial(data)
|
||||
|
|
@ -64,7 +64,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||
await formApi.resetForm();
|
||||
return;
|
||||
}
|
||||
const data = modalApi.getData<ImManagerChannelMaterialVO>();
|
||||
const data = modalApi.getData<ImManagerChannelMaterialApi.Material>();
|
||||
await formApi.setValues({ type: 1 });
|
||||
if (!data || !data.id) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { ImManagerChannelMessageVO } from '#/api/im/manager/channel/message';
|
||||
import type { ImManagerChannelMessageApi } from '#/api/im/manager/channel/message';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ function handleSend() {
|
|||
}
|
||||
|
||||
/** 删除频道消息 */
|
||||
async function handleDelete(row: ImManagerChannelMessageVO) {
|
||||
async function handleDelete(row: ImManagerChannelMessageApi.ChannelMessage) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.id]),
|
||||
duration: 0,
|
||||
|
|
@ -75,7 +75,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<ImManagerChannelMessageVO>,
|
||||
} as VxeTableGridOptions<ImManagerChannelMessageApi.ChannelMessage>,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import type { ImManagerChannelVO } from '#/api/im/manager/channel';
|
||||
import type { ImManagerChannelApi } from '#/api/im/manager/channel';
|
||||
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ const emit = defineEmits<{
|
|||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const channelList = ref<ImManagerChannelVO[]>([]);
|
||||
const channelList = ref<ImManagerChannelApi.Channel[]>([]);
|
||||
|
||||
const value = computed({
|
||||
get: () => props.modelValue,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts" setup>
|
||||
import type { ImManagerGroupVO } from '#/api/im/manager/group';
|
||||
import type { ImManagerGroupApi } from '#/api/im/manager/group';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
import { Select } from 'ant-design-vue';
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ const emit = defineEmits<{
|
|||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const groupList = ref<ImManagerGroupVO[]>([]);
|
||||
const groupList = ref<ImManagerGroupApi.Group[]>([]);
|
||||
|
||||
const value = computed({
|
||||
get: () => props.modelValue,
|
||||
|
|
@ -57,7 +57,7 @@ async function loadGroupList() {
|
|||
}
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, loadGroupList, { immediate: true });
|
||||
onMounted(loadGroupList);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import type { ImManagerChannelMaterialVO } from '#/api/im/manager/channel/material';
|
||||
import type { ImManagerChannelMaterialApi } from '#/api/im/manager/channel/material';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ const emit = defineEmits<{
|
|||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const materialList = ref<ImManagerChannelMaterialVO[]>([]);
|
||||
const materialList = ref<ImManagerChannelMaterialApi.Material[]>([]);
|
||||
|
||||
const value = computed({
|
||||
get: () => props.modelValue,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { ImManagerFacePackVO } from '#/api/im/manager/face/pack';
|
||||
import type { ImManagerFacePackApi } from '#/api/im/manager/face/pack';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
|
|
@ -45,17 +45,17 @@ function handleCreate() {
|
|||
}
|
||||
|
||||
/** 编辑表情包 */
|
||||
function handleEdit(row: ImManagerFacePackVO) {
|
||||
function handleEdit(row: ImManagerFacePackApi.FacePack) {
|
||||
formModalApi.setData(row).open();
|
||||
}
|
||||
|
||||
/** 打开表情管理 */
|
||||
function handleItems(row: ImManagerFacePackVO) {
|
||||
function handleItems(row: ImManagerFacePackApi.FacePack) {
|
||||
itemDrawerRef.value?.open(row);
|
||||
}
|
||||
|
||||
/** 删除表情包 */
|
||||
async function handleDelete(row: ImManagerFacePackVO) {
|
||||
async function handleDelete(row: ImManagerFacePackApi.FacePack) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||
duration: 0,
|
||||
|
|
@ -89,7 +89,7 @@ async function handleDeleteBatch() {
|
|||
function handleRowCheckboxChange({
|
||||
records,
|
||||
}: {
|
||||
records: ImManagerFacePackVO[];
|
||||
records: ImManagerFacePackApi.FacePack[];
|
||||
}) {
|
||||
checkedIds.value = records.map((item) => item.id);
|
||||
}
|
||||
|
|
@ -121,7 +121,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<ImManagerFacePackVO>,
|
||||
} as VxeTableGridOptions<ImManagerFacePackApi.FacePack>,
|
||||
gridEvents: {
|
||||
checkboxAll: handleRowCheckboxChange,
|
||||
checkboxChange: handleRowCheckboxChange,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import type { ImManagerFacePackVO } from '#/api/im/manager/face/pack';
|
||||
import type { ImManagerFacePackApi } from '#/api/im/manager/face/pack';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ import { $t } from '#/locales';
|
|||
import { usePackFormSchema } from '../data';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const formData = ref<ImManagerFacePackVO>();
|
||||
const formData = ref<ImManagerFacePackApi.FacePack>();
|
||||
const getTitle = computed(() => {
|
||||
return formData.value?.id ? '修改表情包' : '新增表情包';
|
||||
});
|
||||
|
|
@ -43,7 +43,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
const data = (await formApi.getValues()) as ImManagerFacePackVO;
|
||||
const data = (await formApi.getValues()) as ImManagerFacePackApi.FacePack;
|
||||
try {
|
||||
await (formData.value?.id
|
||||
? updateManagerFacePack(data)
|
||||
|
|
@ -61,7 +61,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||
await formApi.resetForm();
|
||||
return;
|
||||
}
|
||||
const data = modalApi.getData<ImManagerFacePackVO>();
|
||||
const data = modalApi.getData<ImManagerFacePackApi.FacePack>();
|
||||
if (!data || !data.id) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { ImManagerFacePackItemVO } from '#/api/im/manager/face/item';
|
||||
import type { ImManagerFacePackVO } from '#/api/im/manager/face/pack';
|
||||
import type { ImManagerFacePackItemApi } from '#/api/im/manager/face/item';
|
||||
import type { ImManagerFacePackApi } from '#/api/im/manager/face/pack';
|
||||
|
||||
import { computed, nextTick, ref } from 'vue';
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ import { useItemGridColumns, useItemGridFormSchema } from '../data';
|
|||
import ItemForm from './item-form.vue';
|
||||
|
||||
const visible = ref(false);
|
||||
const currentPack = ref<ImManagerFacePackVO>();
|
||||
const currentPack = ref<ImManagerFacePackApi.FacePack>();
|
||||
const checkedIds = ref<number[]>([]);
|
||||
const title = computed(() =>
|
||||
currentPack.value ? `「${currentPack.value.name}」表情管理` : '表情管理',
|
||||
|
|
@ -34,7 +34,7 @@ const [ItemFormModal, itemFormModalApi] = useVbenModal({
|
|||
});
|
||||
|
||||
/** 打开抽屉 */
|
||||
function open(pack: ImManagerFacePackVO) {
|
||||
function open(pack: ImManagerFacePackApi.FacePack) {
|
||||
currentPack.value = pack;
|
||||
visible.value = true;
|
||||
checkedIds.value = [];
|
||||
|
|
@ -62,14 +62,14 @@ function handleCreate() {
|
|||
}
|
||||
|
||||
/** 编辑表情 */
|
||||
function handleEdit(row: ImManagerFacePackItemVO) {
|
||||
function handleEdit(row: ImManagerFacePackItemApi.FacePackItem) {
|
||||
itemFormModalApi
|
||||
.setData({ id: row.id, packId: currentPack.value?.id })
|
||||
.open();
|
||||
}
|
||||
|
||||
/** 删除表情 */
|
||||
async function handleDelete(row: ImManagerFacePackItemVO) {
|
||||
async function handleDelete(row: ImManagerFacePackItemApi.FacePackItem) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.name || row.id]),
|
||||
duration: 0,
|
||||
|
|
@ -103,7 +103,7 @@ async function handleDeleteBatch() {
|
|||
function handleRowCheckboxChange({
|
||||
records,
|
||||
}: {
|
||||
records: ImManagerFacePackItemVO[];
|
||||
records: ImManagerFacePackItemApi.FacePackItem[];
|
||||
}) {
|
||||
checkedIds.value = records.map((item) => item.id);
|
||||
}
|
||||
|
|
@ -136,7 +136,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<ImManagerFacePackItemVO>,
|
||||
} as VxeTableGridOptions<ImManagerFacePackItemApi.FacePackItem>,
|
||||
gridEvents: {
|
||||
checkboxAll: handleRowCheckboxChange,
|
||||
checkboxChange: handleRowCheckboxChange,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import type { ImManagerFacePackItemVO } from '#/api/im/manager/face/item';
|
||||
import type { ImManagerFacePackItemApi } from '#/api/im/manager/face/item';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ import { probeImageSize } from '#/views/im/manager/utils/format';
|
|||
import { useItemFormSchema } from '../data';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const formData = ref<ImManagerFacePackItemVO>();
|
||||
const formData = ref<ImManagerFacePackItemApi.FacePackItem>();
|
||||
const packId = ref(0);
|
||||
const lastUrl = ref('');
|
||||
const getTitle = computed(() => {
|
||||
|
|
@ -41,7 +41,13 @@ const [Form, formApi] = useVbenForm({
|
|||
|
||||
/** 回填图片尺寸 */
|
||||
async function applyImageSize(url?: string) {
|
||||
if (!url || url === lastUrl.value) {
|
||||
if (!url) {
|
||||
lastUrl.value = '';
|
||||
await formApi.setFieldValue('width', undefined);
|
||||
await formApi.setFieldValue('height', undefined);
|
||||
return;
|
||||
}
|
||||
if (url === lastUrl.value) {
|
||||
return;
|
||||
}
|
||||
lastUrl.value = url;
|
||||
|
|
@ -68,7 +74,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
const data = (await formApi.getValues()) as ImManagerFacePackItemVO;
|
||||
const data = (await formApi.getValues()) as ImManagerFacePackItemApi.FacePackItem;
|
||||
try {
|
||||
data.packId = data.packId || packId.value;
|
||||
await (formData.value?.id
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { ImManagerFaceUserItemVO } from '#/api/im/manager/face/useritem';
|
||||
import type { ImManagerFaceUserItemApi } from '#/api/im/manager/face/useritem';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ function handleRefresh() {
|
|||
}
|
||||
|
||||
/** 删除用户表情 */
|
||||
async function handleDelete(row: ImManagerFaceUserItemVO) {
|
||||
async function handleDelete(row: ImManagerFaceUserItemApi.FaceUserItem) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.name || row.id]),
|
||||
duration: 0,
|
||||
|
|
@ -65,7 +65,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<ImManagerFaceUserItemVO>,
|
||||
} as VxeTableGridOptions<ImManagerFaceUserItemApi.FaceUserItem>,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { ImManagerFriendVO } from '#/api/im/manager/friend';
|
||||
import type { ImManagerFriendApi } from '#/api/im/manager/friend';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ defineOptions({ name: 'ImManagerFriend' });
|
|||
const router = useRouter();
|
||||
|
||||
/** 查看私聊消息 */
|
||||
function handleConversation(row: ImManagerFriendVO) {
|
||||
function handleConversation(row: ImManagerFriendApi.Friend) {
|
||||
router.push({
|
||||
name: 'ImPrivateMessage',
|
||||
query: {
|
||||
|
|
@ -54,7 +54,7 @@ const [Grid] = useVbenVxeGrid({
|
|||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<ImManagerFriendVO>,
|
||||
} as VxeTableGridOptions<ImManagerFriendApi.Friend>,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { ImManagerFriendRequestVO } from '#/api/im/manager/friend/request';
|
||||
import type { ImManagerFriendRequestApi } from '#/api/im/manager/friend/request';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ const [Grid] = useVbenVxeGrid({
|
|||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<ImManagerFriendRequestVO>,
|
||||
} as VxeTableGridOptions<ImManagerFriendRequestApi.FriendRequest>,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { ImManagerGroupVO } from '#/api/im/manager/group';
|
||||
import type { ImManagerGroupApi } from '#/api/im/manager/group';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
|
@ -39,12 +39,12 @@ function handleRefresh() {
|
|||
}
|
||||
|
||||
/** 打开详情 */
|
||||
function handleDetail(row: ImManagerGroupVO) {
|
||||
function handleDetail(row: ImManagerGroupApi.Group) {
|
||||
detailRef.value?.open(row);
|
||||
}
|
||||
|
||||
/** 查看群聊消息 */
|
||||
function handleConversation(row: ImManagerGroupVO) {
|
||||
function handleConversation(row: ImManagerGroupApi.Group) {
|
||||
router.push({
|
||||
name: 'ImGroupMessage',
|
||||
query: { groupId: row.id },
|
||||
|
|
@ -52,12 +52,12 @@ function handleConversation(row: ImManagerGroupVO) {
|
|||
}
|
||||
|
||||
/** 打开封禁弹窗 */
|
||||
function handleBan(row: ImManagerGroupVO) {
|
||||
function handleBan(row: ImManagerGroupApi.Group) {
|
||||
banModalApi.setData(row).open();
|
||||
}
|
||||
|
||||
/** 解封群 */
|
||||
async function handleUnban(row: ImManagerGroupVO) {
|
||||
async function handleUnban(row: ImManagerGroupApi.Group) {
|
||||
await confirm(`确认解封群「${row.name}」吗?`);
|
||||
await unbanManagerGroup(row.id);
|
||||
message.success('解封成功');
|
||||
|
|
@ -65,7 +65,7 @@ async function handleUnban(row: ImManagerGroupVO) {
|
|||
}
|
||||
|
||||
/** 解散群 */
|
||||
async function handleDissolve(row: ImManagerGroupVO) {
|
||||
async function handleDissolve(row: ImManagerGroupApi.Group) {
|
||||
await confirm(`确认解散群「${row.name}」吗?`);
|
||||
await dissolveManagerGroup(row.id);
|
||||
message.success('解散成功');
|
||||
|
|
@ -99,7 +99,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<ImManagerGroupVO>,
|
||||
} as VxeTableGridOptions<ImManagerGroupApi.Group>,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue